├── .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 |
--------------------------------------------------------------------------------
/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 |
Donate
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 | 
34 |
35 | 4. Go to the "This Firefox" section through the left navigation bar.
36 |
37 | 
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 = `
`;
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 |
--------------------------------------------------------------------------------