├── .gitignore ├── manifest.json ├── icons └── logo.svg ├── popup.html ├── readme.md ├── popup.css ├── redirectState.function.js ├── background.js └── main.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea/prettier.xml 3 | .idea/workspace.xml 4 | .zip -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "Chartink To TradingView", 4 | "version": "2.5.1", 5 | "description": "Redirects Chartink Symbol Links to TradingView Chart", 6 | "icons": { 7 | "48": "icons/logo.svg" 8 | }, 9 | "content_scripts": [ 10 | { 11 | "matches": ["*://*.chartink.com/*"], 12 | "js": ["main.js"], 13 | "css": ["popup.css"] 14 | } 15 | ], 16 | "permissions": ["clipboardWrite", "storage"], 17 | "background": { 18 | "scripts": ["background.js"], 19 | "persistent": false, 20 | "type": "module" 21 | }, 22 | "browser_action": { 23 | "default_popup": "popup.html", 24 | "default_title": "TradingView to Chartink", 25 | "default_icon": "icons/logo.svg" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /icons/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Document 6 | 7 | 8 | 9 | 10 |
11 |

Chartink To TradingView

12 |

By devAgam

13 | 18 | 23 | 24 | 25 | 29 | 30 |
31 | 34 |
35 | 36 | 37 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Chartink to TradingView. 2 | 3 | This is a firefox extension meant to redirect chart links in Chartink Scanner output to TradingView charts. 4 | 5 | Chartink provides great screening tools but lacks a good charting tool, whereas TradingView has an average screener but an excellent charting system. So this extension aims to bridge the gap between the two services. 6 | 7 | ~~The Firefox extension is submitted but still under review; I will update the URL as soon as it's live.~~ 8 | 9 | The extesion is live 🎉 10 | 11 | [Firefox Store](https://addons.mozilla.org/en-US/firefox/addon/chartink-to-tradingview/) 12 | 13 | [Chrome Web Store](https://chrome.google.com/webstore/detail/chartink-to-tradingview/gnokdahlhlefhgfpogfhhgbdlofhnfad) 14 | 15 | \*This extension only works with chartink and supports only NSE stocks. 16 | 17 | --- 18 | 19 | Made by Pennytalks discord server 20 | 21 | # How to install 22 | 23 | [Official Firefox Guide](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Your_first_WebExtension#installing) 24 | 25 | ## Steps to install (Firefox) (Development Purposes Only) 26 | 27 | 1. Download the files, [here](https://codeload.github.com/devAgam/chartink-to-tradingview-firefox-extension/zip/refs/heads/main). 28 | 29 | 2. Unarchive/Extract the download file. 30 | 31 | 3. Go to this link `about:debugging` . 32 | 33 | ![about:debugging page](https://stonk-code-assets.s3.ap-south-1.amazonaws.com/Screenshot+2023-01-19+at+10.31.58+AM.png "about:debugging") 34 | 35 | 4. Go to the "This Firefox" section through the left navigation bar. 36 | 37 | ![about:debugging page](https://stonk-code-assets.s3.ap-south-1.amazonaws.com/Screenshot+2023-01-19+at+10.36.20+AM.png "about:debugging") 38 | 39 | 5. Click on Load Temporary Add-On. 40 | 41 | 6. Go into the extracted folder, and select the `manifest.json` file. 42 | 43 | 7. Done!. 44 | -------------------------------------------------------------------------------- /popup.css: -------------------------------------------------------------------------------- 1 | .chartink-to-tv-container { 2 | width: 300px; 3 | padding: 10px; 4 | text-align: center; 5 | font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, 6 | Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; 7 | } 8 | 9 | h1 { 10 | font-size: 20px; 11 | margin-bottom: 0px; 12 | margin-top: 0; 13 | text-align: left; 14 | } 15 | .description { 16 | font-size: 14px; 17 | margin-bottom: 20px; 18 | text-align: left; 19 | margin-top: 0; 20 | } 21 | .checkbox-container { 22 | display: block; 23 | position: relative; 24 | padding-left: 25px; 25 | margin-bottom: 10px; 26 | cursor: pointer; 27 | text-align: left; 28 | } 29 | 30 | .checkbox-container input { 31 | position: absolute; 32 | opacity: 0; 33 | cursor: pointer; 34 | } 35 | 36 | .checkmark { 37 | position: absolute; 38 | top: 0; 39 | left: 0; 40 | height: 15px; 41 | width: 15px; 42 | background-color: #eee; 43 | border-radius: 3px; 44 | } 45 | 46 | .checkbox-container:hover input ~ .checkmark { 47 | background-color: #ccc; 48 | } 49 | 50 | .checkbox-container input:checked ~ .checkmark { 51 | background-color: #2196f3; 52 | } 53 | 54 | .checkmark:after { 55 | content: ""; 56 | position: absolute; 57 | display: none; 58 | } 59 | 60 | .checkbox-container input:checked ~ .checkmark:after { 61 | display: block; 62 | } 63 | 64 | .checkbox-container .checkmark:after { 65 | left: 5px; 66 | top: 2px; 67 | width: 4px; 68 | height: 8px; 69 | border: solid white; 70 | border-width: 0 2px 2px 0; 71 | transform: rotate(45deg); 72 | } 73 | 74 | /* CSS */ 75 | .donate-bt { 76 | appearance: none; 77 | background-color: #3999af; 78 | border: 1px solid rgba(27, 31, 35, 0.15); 79 | border-radius: 6px; 80 | box-shadow: rgba(27, 31, 35, 0.1) 0 1px 0; 81 | box-sizing: border-box; 82 | color: #fff; 83 | cursor: pointer; 84 | display: inline-block; 85 | font-family: -apple-system, system-ui, "Segoe UI", Helvetica, Arial, 86 | sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; 87 | font-size: 14px; 88 | font-weight: 600; 89 | line-height: 20px; 90 | padding: 6px 16px; 91 | position: relative; 92 | text-align: center; 93 | text-decoration: none; 94 | user-select: none; 95 | -webkit-user-select: none; 96 | touch-action: manipulation; 97 | vertical-align: middle; 98 | white-space: nowrap; 99 | width: 100%; 100 | } 101 | 102 | .donate-bt:focus:not(:focus-visible):not(.focus-visible) { 103 | box-shadow: none; 104 | outline: none; 105 | } 106 | 107 | .donate-bt:hover { 108 | background-color: #33889c; 109 | } 110 | 111 | .donate-bt:focus { 112 | box-shadow: rgba(46, 164, 79, 0.4) 0 0 0 3px; 113 | outline: none; 114 | } 115 | 116 | .donate-bt:disabled { 117 | background-color: rgba(27, 31, 35, 0.1); 118 | border-color: rgba(27, 31, 35, 0.1); 119 | color: rgba(255, 255, 255, 0.8); 120 | cursor: default; 121 | } 122 | 123 | .donate-bt:active { 124 | background-color: #2b7a8c; 125 | box-shadow: rgba(20, 70, 32, 0.2) 0 1px 0 inset; 126 | } 127 | .chart-type { 128 | display: flex; 129 | justify-content: space-between; 130 | margin-bottom: 5px; 131 | } 132 | .chart-dropdown { 133 | margin-bottom: 10px; 134 | } 135 | .chart-dropdown { 136 | width: 100%; 137 | padding: 5px; 138 | border-radius: 5px; 139 | border: 1px solid #ccc; 140 | background-color: #fff; 141 | color: #333; 142 | font-size: 14px; 143 | cursor: pointer; 144 | } 145 | -------------------------------------------------------------------------------- /redirectState.function.js: -------------------------------------------------------------------------------- 1 | function attachToRedirectStateSetting() { 2 | var redirectStateSetting = document.getElementById("redirect"); 3 | // redirect element is an input with type checkbox, if it's checked then send a message to the background script 4 | // to set chartRedirect state to true 5 | redirectStateSetting.addEventListener("change", function () { 6 | if (this.checked) { 7 | browser.runtime.sendMessage({ 8 | message: "setChartRedirectState", 9 | state: true, 10 | }); 11 | } else { 12 | browser.runtime.sendMessage({ 13 | message: "setChartRedirectState", 14 | state: false, 15 | }); 16 | } 17 | }); 18 | } 19 | 20 | // execute the function on DOM content loaded 21 | document.addEventListener("DOMContentLoaded", attachToRedirectStateSetting); 22 | 23 | function updateCheckBoxState() { 24 | // get the state and update the checkbox 25 | browser.runtime.sendMessage( 26 | { message: "getChartRedirectState" }, 27 | function (response) { 28 | console.log(response); 29 | if (response.chartRedirectState) { 30 | document.getElementById("redirect").defaultChecked = true; 31 | } else { 32 | document.getElementById("redirect").defaultChecked = false; 33 | } 34 | } 35 | ); 36 | } 37 | 38 | // execute the function on DOM content loaded 39 | document.addEventListener("DOMContentLoaded", updateCheckBoxState); 40 | 41 | // KITE REDIRECT BT 42 | 43 | function attachToKiteBt() { 44 | var kiteBtState = document.getElementById("kite-bt"); 45 | // redirect element is an input with type checkbox, if it's checked then send a message to the background script 46 | // to set chartRedirect state to true 47 | kiteBtState.addEventListener("change", function () { 48 | if (this.checked) { 49 | browser.runtime.sendMessage({ 50 | message: "setKiteEnabled", 51 | state: true, 52 | }); 53 | } else { 54 | browser.runtime.sendMessage({ 55 | message: "setKiteEnabled", 56 | state: false, 57 | }); 58 | } 59 | }); 60 | } 61 | 62 | // execute the function on DOM content loaded 63 | document.addEventListener("DOMContentLoaded", attachToKiteBt); 64 | 65 | function updateKiteCheckBoxState() { 66 | // get the state and update the checkbox 67 | browser.runtime.sendMessage( 68 | { message: "getKiteEnabled" }, 69 | function (response) { 70 | console.log(response); 71 | if (response.kiteEnabled) { 72 | document.getElementById("kite-bt").defaultChecked = true; 73 | } else { 74 | document.getElementById("kite-bt").defaultChecked = false; 75 | } 76 | } 77 | ); 78 | } 79 | 80 | function attachToKiteChartType() { 81 | var kiteChartType = document.getElementById("chart-type"); 82 | // redirect element is an input with type checkbox, if it's checked then send a message to the background script 83 | // to set chartRedirect state to true 84 | kiteChartType.addEventListener("change", function () { 85 | browser.runtime.sendMessage({ 86 | message: "setKiteChartType", 87 | type: this.value, 88 | }); 89 | }); 90 | } 91 | 92 | function updateKiteChartType() { 93 | // get the state and update the checkbox 94 | browser.runtime.sendMessage( 95 | { message: "getKiteChartType" }, 96 | function (response) { 97 | console.log(response); 98 | document.getElementById("chart-type").value = response.kiteChartType; 99 | } 100 | ); 101 | } 102 | 103 | // execute the functions on DOM content loaded 104 | document.addEventListener("DOMContentLoaded", updateKiteCheckBoxState); 105 | document.addEventListener("DOMContentLoaded", attachToKiteChartType); 106 | document.addEventListener("DOMContentLoaded", updateKiteChartType); 107 | -------------------------------------------------------------------------------- /background.js: -------------------------------------------------------------------------------- 1 | const CHARTINK_STOCKS_URL = "https://chartink.com/stocks/"; 2 | const TRADINGVIEW_BASE_URL = "https://in.tradingview.com/chart/?symbol=NSE:"; 3 | const KITE_BASE_URL_TVC = "https://kite.zerodha.com/chart/web/tvc/NSE/"; 4 | const KITE_BASE_URL_CIQ = "https://kite.zerodha.com/chart/web/ciq/NSE/"; 5 | 6 | // Add a listener for when a tab is updated 7 | browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { 8 | if (changeInfo.url) { 9 | handleTabUpdate(tabId, changeInfo.url); 10 | } 11 | }); 12 | 13 | // Handle tab update events 14 | async function handleTabUpdate(tabId, url) { 15 | const chartRedirect = await getChartRedirectState(); 16 | if (chartRedirect && url.startsWith(CHARTINK_STOCKS_URL)) { 17 | const stockSymbol = extractStockSymbolFromChartinkURL(url); 18 | redirectToTradingView(tabId, stockSymbol); 19 | } 20 | } 21 | 22 | // Extract stock symbol from Chartink URL 23 | function extractStockSymbolFromChartinkURL(url) { 24 | return url.replace(CHARTINK_STOCKS_URL, "").replace(".html", ""); 25 | } 26 | 27 | // Redirect to TradingView 28 | function redirectToTradingView(tabId, stockSymbol) { 29 | browser.tabs.update(tabId, { url: `${TRADINGVIEW_BASE_URL}${stockSymbol}` }); 30 | } 31 | 32 | // Store chart redirect setting state in local storage 33 | function setChartRedirectState(state) { 34 | browser.storage.local.set({ chartRedirect: state }); 35 | } 36 | 37 | // Get chart redirect setting state from local storage 38 | function getChartRedirectState() { 39 | return new Promise((resolve) => { 40 | browser.storage.local.get("chartRedirect").then((result) => { 41 | resolve(result.chartRedirect); 42 | }); 43 | }); 44 | } 45 | 46 | // Get Kite enabled state from local storage 47 | function getKiteEnabled() { 48 | return new Promise((resolve) => { 49 | browser.storage.local.get("kiteEnabled").then((result) => { 50 | resolve(result.kiteEnabled); 51 | }); 52 | }); 53 | } 54 | 55 | // Set Kite enabled state in local storage 56 | function setKiteEnabled(state) { 57 | console.log("setting state", state); 58 | browser.storage.local.set({ kiteEnabled: state }); 59 | } 60 | 61 | // Get Kite chart type state from local storage 62 | function getKiteChartType() { 63 | return new Promise((resolve) => { 64 | browser.storage.local.get("kiteChartType").then((result) => { 65 | resolve(result.kiteChartType || "tvc"); // default to 'tvc' if not set 66 | }); 67 | }); 68 | } 69 | 70 | // Set Kite chart type state in local storage 71 | function setKiteChartType(type) { 72 | console.log("setting chart type", type); 73 | browser.storage.local.set({ kiteChartType: type }); 74 | } 75 | 76 | // Listener to listen for messages from popup.js 77 | browser.runtime.onMessage.addListener((request, sender, sendResponse) => { 78 | handleRuntimeMessage(request, sendResponse); 79 | return true; 80 | }); 81 | 82 | // Handle runtime messages 83 | async function handleRuntimeMessage(request, sendResponse) { 84 | switch (request.message) { 85 | case "getChartRedirectState": 86 | const chartRedirectState = await getChartRedirectState(); 87 | sendResponse({ chartRedirectState }); 88 | break; 89 | case "setChartRedirectState": 90 | setChartRedirectState(request.state); 91 | break; 92 | case "redirectToKite": 93 | const symbol = extractSymbolFromTradingViewURL(request.href); 94 | const symbolInfo = await getSymbolInfoFromCDN(symbol); 95 | redirectToKite(symbol, symbolInfo.instrument_token); 96 | break; 97 | case "getKiteEnabled": 98 | const kiteEnabled = await getKiteEnabled(); 99 | sendResponse({ kiteEnabled }); 100 | break; 101 | case "setKiteEnabled": 102 | setKiteEnabled(request.state); 103 | break; 104 | case "getKiteChartType": 105 | const kiteChartType = await getKiteChartType(); 106 | sendResponse({ kiteChartType }); 107 | break; 108 | case "setKiteChartType": 109 | setKiteChartType(request.type); 110 | break; 111 | default: 112 | break; 113 | } 114 | } 115 | 116 | // Extract symbol from TradingView URL 117 | function extractSymbolFromTradingViewURL(url) { 118 | if (url.includes("NSE:")) { 119 | return url.split("/")[4].split(":")[1]; 120 | } else if (url.includes("/stocks-new")) { 121 | const urlParams = new URLSearchParams(url); 122 | return urlParams.get("symbol"); 123 | } else if (url.includes("/stocks/")) { 124 | return url.split("/").pop().replace(".html", ""); 125 | } 126 | } 127 | 128 | // Get symbol info from CDN 129 | function getSymbolInfoFromCDN(symbol) { 130 | return fetch( 131 | `https://d1t7yromaetmio.cloudfront.net/default/chartink-kite-symbol-matcher?tradingsymbol=${symbol}` 132 | ).then((response) => response.json()); 133 | } 134 | 135 | // Redirect to Kite 136 | async function redirectToKite(symbol, instrumentToken) { 137 | const kiteChartType = await getKiteChartType(); 138 | const KITE_BASE_URL = 139 | kiteChartType === "ciq" ? KITE_BASE_URL_CIQ : KITE_BASE_URL_TVC; 140 | browser.tabs.create({ 141 | url: `${KITE_BASE_URL}${symbol.toUpperCase()}/${instrumentToken}`, 142 | }); 143 | } 144 | 145 | // On install, set the chartRedirect and kiteEnabled states to true (maintain legacy functionality) 146 | browser.runtime.onInstalled.addListener(() => { 147 | setChartRedirectState(true); 148 | setKiteEnabled(true); 149 | }); 150 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | window.onload = function () { 2 | changeURL(); 3 | }; 4 | 5 | const dateHeader = `### ${new Date().toLocaleDateString("en-GB", { 6 | day: "numeric", 7 | month: "short", 8 | year: "numeric", 9 | })}`; 10 | 11 | /** 12 | * Changes the URL of certain links on the page based on the chart redirect state and kite enabled state. 13 | * Adds a copy button next to the modified links. 14 | */ 15 | function changeURL() { 16 | // Get the chart redirect state from the background script 17 | browser.runtime.sendMessage( 18 | { message: "getChartRedirectState" }, 19 | function (response) { 20 | if (!response.chartRedirectState) { 21 | return; 22 | } 23 | 24 | // Find all links with href starting with "/stocks" 25 | var links = document.querySelectorAll('a[href^="/stocks"]'); 26 | for (var i = 0; i < links.length; i++) { 27 | // Modify the href to redirect to TradingView with the appropriate symbol 28 | links[ 29 | i 30 | ].href = `https://in.tradingview.com/chart/?symbol=NSE:${compatabilitySymbolFunc( 31 | links[i].href 32 | )}`; 33 | } 34 | } 35 | ); 36 | 37 | // Get the kite enabled state from the background script 38 | browser.runtime.sendMessage( 39 | { message: "getKiteEnabled" }, 40 | function (response) { 41 | if (!response.kiteEnabled) { 42 | return; 43 | } 44 | 45 | // Get the chart redirect state again 46 | browser.runtime.sendMessage( 47 | { message: "getChartRedirectState" }, 48 | function (response) { 49 | var links = []; 50 | if (response.chartRedirectState) { 51 | // Find all links with href starting with "https://in.tradingview.com/chart/?symbol=NSE:" 52 | links = document.querySelectorAll( 53 | 'a[href^="https://in.tradingview.com/chart/?symbol=NSE:"]' 54 | ); 55 | } else { 56 | // Find all links with href starting with "/stocks" 57 | links = document.querySelectorAll('a[href^="/stocks"]'); 58 | } 59 | 60 | for (var i = 0; i < links.length; i++) { 61 | // Skip every other link if not on the dashboard page 62 | if (i % 2 !== 0 && !window.location.href.includes("/dashboard/")) { 63 | continue; 64 | } 65 | 66 | // Skip if a copy button already exists 67 | if (links[i].parentNode.querySelector(".copy-to-kite")) { 68 | continue; 69 | } 70 | 71 | // Create a copy button 72 | const copyButton = document.createElement("button"); 73 | copyButton.innerHTML = `copy`; 74 | copyButton.style.backgroundColor = "transparent"; 75 | copyButton.style.border = "none"; 76 | copyButton.style.cursor = "pointer"; 77 | copyButton.style.marginLeft = "5px"; 78 | copyButton.className = "copy-to-kite"; 79 | 80 | // Add an onclick event to the copy button 81 | copyButton.onclick = function () { 82 | const parentNode = copyButton.parentNode; 83 | const aTagInParentNode = parentNode.querySelector("a"); 84 | const href = aTagInParentNode.href; 85 | // Send a message to the background script to redirect to Kite with the copied link 86 | browser.runtime.sendMessage({ 87 | message: "redirectToKite", 88 | href: href, 89 | }); 90 | }; 91 | 92 | // Append the copy button to the parent node of the link 93 | links[i].parentNode.appendChild(copyButton); 94 | } 95 | } 96 | ); 97 | } 98 | ); 99 | } 100 | 101 | /** 102 | * Extracts the symbol from the URL based on the URL format. 103 | * @param {string} url - The URL of the link. 104 | * @returns {string|null} - The extracted symbol or null if not found. 105 | */ 106 | function compatabilitySymbolFunc(url) { 107 | if (url.includes("stocks-new")) { 108 | return new URL(url).searchParams.get("symbol"); 109 | } 110 | return url.substring(url.lastIndexOf("/") + 1, url.lastIndexOf(".html")); 111 | } 112 | 113 | // Create a mutation observer to detect changes in the DOM 114 | var observer = new MutationObserver(function (mutations) { 115 | mutations.forEach(function (mutation) { 116 | setTimeout(function () { 117 | changeURL(); 118 | }, 100); 119 | }); 120 | }); 121 | 122 | var config = { 123 | childList: true, 124 | subtree: true, 125 | }; 126 | 127 | // Observe the document body for changes 128 | observer.observe(document.body, config); 129 | 130 | const screenerButtonsClass = "btn btn-default btn-primary"; 131 | 132 | /** 133 | * Adds a copy button to the TradingView screener buttons. 134 | * @param {string} buttonText - The text to display on the button. 135 | * @param {string} buttonClass - The CSS class of the button. 136 | * @param {string} buttonId - The ID of the button. 137 | * @param {function} buttonFunction - The function to execute when the button is clicked. 138 | */ 139 | const addCopyToTradingViewButton = ( 140 | buttonText, 141 | buttonClass, 142 | buttonId, 143 | buttonFunction 144 | ) => { 145 | const screenerButtons = document.getElementsByClassName(screenerButtonsClass); 146 | if (screenerButtons.length === 0) return; 147 | const screenerButtonsParent = screenerButtons[0].parentNode; 148 | const screenerButton = document.createElement("button"); 149 | screenerButton.innerHTML = buttonText; 150 | screenerButton.className = buttonClass; 151 | screenerButton.id = buttonId; 152 | screenerButton.onclick = buttonFunction; 153 | screenerButtonsParent.appendChild(screenerButton); 154 | }; 155 | 156 | // Add a copy button to the TradingView screener buttons 157 | addCopyToTradingViewButton( 158 | "Copy to TradingView", 159 | "btn btn-default btn-primary", 160 | "add-to-watchlist", 161 | copyAllTickersOnScreen 162 | ); 163 | 164 | /** 165 | * Gets the length of the pagination. 166 | * @returns {number} - The length of the pagination. 167 | */ 168 | function getPaginationLength() { 169 | const paginationList = document 170 | .getElementsByClassName("pagination")[0] 171 | .getElementsByTagName("li"); 172 | 173 | return paginationList[paginationList.length - 2].innerText; 174 | } 175 | 176 | // Clicks the next page button 177 | function nextPage() { 178 | document 179 | .evaluate( 180 | "//a[text()='Next']", 181 | document, 182 | null, 183 | XPathResult.FIRST_ORDERED_NODE_TYPE, 184 | null 185 | ) 186 | .singleNodeValue.click(); 187 | } 188 | 189 | /** 190 | * Gets the number of stocks displayed on the screen. 191 | * @returns {number} - The number of stocks. 192 | */ 193 | function getNumberOfStocks() { 194 | const el = document.getElementsByClassName("dataTables_info")[0]; 195 | const innerText = el.innerText; 196 | const numberOfStocks = innerText.match(/\d+/)[0]; 197 | return numberOfStocks; 198 | } 199 | 200 | /** 201 | * Delays the execution of the code. 202 | * @param {number} t - The delay time in milliseconds. 203 | * @returns {Promise} - A promise that resolves after the delay. 204 | */ 205 | const delay = (t) => { 206 | return new Promise((res) => setTimeout(res, t)); 207 | }; 208 | 209 | /** 210 | * Copies all the tickers on the screen to the clipboard. 211 | */ 212 | async function copyAllTickersOnScreen() { 213 | // Get the chart redirect state from the background script 214 | browser.runtime.sendMessage( 215 | { message: "getChartRedirectState" }, 216 | async function (response) { 217 | if (response.chartRedirectState) { 218 | let allTickersArray = []; 219 | let allTags = []; 220 | const numberOfPages = getPaginationLength(); 221 | 222 | // Iterate through each page 223 | for (let i = 0; i < numberOfPages; i++) { 224 | if (i > 0) { 225 | await delay(200); 226 | } 227 | 228 | // Find all tags with href starting with "https://in.tradingview.com/chart/?symbol=NSE:" 229 | allTags.push( 230 | document.querySelectorAll( 231 | 'a[href^="https://in.tradingview.com/chart/?symbol=NSE:"]' 232 | ) 233 | ); 234 | 235 | nextPage(); 236 | } 237 | 238 | // Flatten the array of tags 239 | const allTickers = allTags.map((tag) => Array.from(tag)).flat(); 240 | 241 | // Extract the symbols from the URLs and add them to the tickers array 242 | allTickers.forEach((ticker) => { 243 | allTickersArray.push( 244 | replaceSpecialCharsWithUnderscore( 245 | extracrtSymbolFromURL(ticker.href) 246 | ) 247 | ); 248 | }); 249 | 250 | // Add "NSE:" prefix to the tickers 251 | allTickersArray = addColonNSEtoTickers(allTickersArray); 252 | 253 | // Create a fake textarea to copy the tickers to the clipboard 254 | createFakeTextAreaToCopyText( 255 | [...removeDuplicateTickers(allTickersArray)].join(", ") 256 | ); 257 | replaceButtonText("add-to-watchlist"); 258 | return; 259 | } 260 | 261 | let allTickersArray = []; 262 | let allTags = []; 263 | const numberOfPages = getPaginationLength(); 264 | 265 | console.log(numberOfPages); 266 | // Iterate through each page 267 | for (let i = 0; i < numberOfPages; i++) { 268 | if (i > 0) { 269 | await delay(200); 270 | } 271 | 272 | allTags.push(document.querySelectorAll('a[href^="/stocks-new"]')); 273 | 274 | nextPage(); 275 | } 276 | console.log(allTags); 277 | // Flatten the array of tags 278 | const allTickers = allTags.map((tag) => Array.from(tag)).flat(); 279 | // Extract the symbols from the URLs and add them to the tickers array 280 | allTickers.forEach((ticker) => { 281 | allTickersArray.push( 282 | replaceSpecialCharsWithUnderscore( 283 | extractSymbolFromTradingViewURL(ticker.href) 284 | ) 285 | ); 286 | }); 287 | // Add "NSE:" prefix to the tickers 288 | allTickersArray = addColonNSEtoTickers(allTickersArray); 289 | 290 | // Create a fake textarea to copy the tickers to the clipboard 291 | createFakeTextAreaToCopyText( 292 | [...removeDuplicateTickers(allTickersArray)].join(", ") 293 | ); 294 | replaceButtonText("add-to-watchlist"); 295 | } 296 | ); 297 | } 298 | 299 | /** 300 | * Replaces the text of a button with a success message and then restores it after a delay. 301 | * @param {string} buttonId - The ID of the button. 302 | */ 303 | function replaceButtonText(buttonId) { 304 | const button = document.getElementById(buttonId); 305 | if (!button) return; 306 | button.innerHTML = "Copied to clipboard 📋"; 307 | setTimeout(() => { 308 | button.innerHTML = "Copy to TradingView"; 309 | }, 2000); 310 | } 311 | 312 | /** 313 | * Creates a fake textarea, copies the text to it, and then copies the text from the textarea to the clipboard. 314 | * @param {string} text - The text to copy to the clipboard. 315 | */ 316 | function createFakeTextAreaToCopyText(text) { 317 | const fakeTextArea = document.createElement("textarea"); 318 | fakeTextArea.value = `${dateHeader},${text}`; 319 | document.body.appendChild(fakeTextArea); 320 | fakeTextArea.select(); 321 | document.execCommand("copy"); 322 | document.body.removeChild(fakeTextArea); 323 | } 324 | 325 | /** 326 | * Removes duplicate tickers from an array. 327 | * @param {string[]} tickers - The array of tickers. 328 | * @returns {string[]} - The array of tickers with duplicates removed. 329 | */ 330 | function removeDuplicateTickers(tickers) { 331 | return [...new Set(tickers)]; 332 | } 333 | 334 | /** 335 | * Adds "NSE:" prefix to each ticker in an array. 336 | * @param {string[]} tickers - The array of tickers. 337 | * @returns {string[]} - The array of tickers with "NSE:" prefix added. 338 | */ 339 | function addColonNSEtoTickers(tickers) { 340 | return tickers.map((ticker) => `NSE:${ticker}`); 341 | } 342 | 343 | /** 344 | * Replaces special characters in a ticker with underscores. 345 | * @param {string} ticker - The ticker. 346 | * @returns {string} - The ticker with special characters replaced. 347 | */ 348 | function replaceSpecialCharsWithUnderscore(ticker) { 349 | return ticker.replace(/[^a-zA-Z0-9]/g, "_"); 350 | } 351 | 352 | /** 353 | * Adds copy buttons to the TradingView charts. 354 | */ 355 | const addCopyBtOnTradingView = () => { 356 | const copyBts = document.querySelectorAll('div[title="Copy widget"]'); 357 | copyBts.forEach((copyBt) => { 358 | copyBt.style.fontSize = "20px"; 359 | 360 | // Replace the original element with a clone to remove all event listeners 361 | const newCopyBt = copyBt.cloneNode(true); 362 | copyBt.parentNode.replaceChild(newCopyBt, copyBt); 363 | 364 | newCopyBt.onclick = (e) => { 365 | e.stopPropagation(); 366 | e.preventDefault(); 367 | 368 | const tables = 369 | newCopyBt.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.querySelector( 370 | "table" 371 | ); 372 | const allTickers = tables.querySelectorAll( 373 | 'a[href^="https://in.tradingview.com/chart/?symbol=NSE:"]' 374 | ); 375 | let allTickersArray = []; 376 | 377 | allTickers.forEach((ticker) => { 378 | allTickersArray.push( 379 | replaceSpecialCharsWithUnderscore(ticker.href.substring(45)) 380 | ); 381 | }); 382 | 383 | allTickersArray = addColonNSEtoTickers(allTickersArray); 384 | createFakeTextAreaToCopyText( 385 | removeDuplicateTickers(allTickersArray).join(",") 386 | ); 387 | 388 | // Use the existing button update logic instead of alert 389 | alert("Copied to clipboard 📋"); 390 | 391 | return false; 392 | }; 393 | }); 394 | }; 395 | 396 | // Add copy buttons to the TradingView charts 397 | addCopyBtOnTradingView(); 398 | 399 | /** 400 | * Removes the ".html" extension from a ticker. 401 | * @param {string} ticker - The ticker. 402 | * @returns {string} - The ticker without the ".html" extension. 403 | */ 404 | function removeDotHTML(ticker) { 405 | return ticker.replace(".html", ""); 406 | } 407 | 408 | /** 409 | * Extracts the symbol from the URL. 410 | * @param {string} url - The URL of the link. 411 | * @returns {string|null} - The extracted symbol or null if not found. 412 | */ 413 | function extracrtSymbolFromURL(url) { 414 | const urlParams = new URLSearchParams(new URL(url).search); 415 | const symbol = urlParams.get("symbol"); 416 | return symbol ? symbol.split(":")[1] : null; 417 | } 418 | function extractSymbolFromTradingViewURL(url) { 419 | if (url.includes("NSE:")) { 420 | return url.split("/")[4].split(":")[1]; 421 | } else if (url.includes("/stocks-new")) { 422 | const urlParams = new URLSearchParams(url); 423 | return urlParams.get("symbol"); 424 | } else if (url.includes("/stocks/")) { 425 | return url.split("/").pop().replace(".html", ""); 426 | } 427 | } 428 | --------------------------------------------------------------------------------