您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
insert batch copy button, chart copy button ,chart button and blacklist button in tradingview screener
// ==UserScript== // @name tradingview screener assistant // @namespace http://tampermonkey.net/ // @version 2024-09-20.3 // @description insert batch copy button, chart copy button ,chart button and blacklist button in tradingview screener // @author goodzhuwang // @match https://*.tradingview.com/screener/* // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw== // @grant none // @license MIT // ==/UserScript== (function () { "use strict"; const ext_name = "tv_assistant"; console.debug(`${ext_name} running`); let batch_button_id = "__batch_copybtn"; // 代码黑名单 let blacklist = []; // 最新的中概股名单 : https://stockanalysis.com/list/chinese-stocks-us/ let chinese_stocks = [ "BABA", "NTES", "JD", "BIDU", "PUK", "LI", "BEKE", "ZTO", "TME", "YUMC", "NIO", "EDU", "HTHT", "FUTU", "XPEV", "YMM", "VIPS", "BILI", "BZ", "MNSO", "TAL", "ZK", "QFIN", "GDS", "LOT", "ATHM", "ATAT", "HCM", "MLCO", "RLX", "ZLAB", "IQ", "LU", "WB", "SIMO", "FINV", "DQ", "MOMO", "MSC", "JKS", "SDA", "HUYA", "VNET", "TUYA", "XCH", "EH", "GOTU", "TIGR", "ECX", "DDL", "NOAH", "HSAI", "KC", "ZKH", "SOHU", "RERE", "ICG", "ZJYL", "YRD", "DOGZ", "WDH", "PGHL", "DAO", "QD", "DADA", "YSG", "ZH", "TROO", "JFIN", "UXIN", "YXT", "LX", "DOYU", "RITR", "GHG", "DSY", "XYF", "LANV", "AHG", "BSII", "HPH", "AGBA", "CANG", "RTC", "BZUN", "NIU", "KNDI", "LSB", "EM", "YIBO", "ZBAO", "GSIW", "SRL", "AZI", "SFWL", "CMCM", "CAAS", "TOUR", "XNET", "THCH", "ADAG", "CASI", "QMMM", "LGCL", "QH", "VIOT", "GLAC", "CBAT", "QSG", "STG", "GLXG", "IH", "RGC", "WIMI", "JVSA", "SY", "NHTC", "LICN", "DIST", "XHG", "CNF", "FEBO", "FANH", "WOK", "JUNE", "CCG", "PTHL", "YHNA", "NCTY", "CHSN", "BEDU", "OCFT", "BGM", "NISN", "TOP", "PMAX", "MTC", "PRE", "YI", "FVN", "BEST", "BYU", "ZEPP", "UCL", "EHGO", "RCON", "NAAS", "HKIT", "MATH", "SWIN", "TWG", "EBON", "CDTG", "ABLV", "CLPS", "AGMH", "FENG", "SJ", "BNR", "KUKE", "HUIZ", "RAY", "AIXI", "JG", "ICLK", "MTEN", "HUDI", "IZM", "UTSI", "GMM", "CGA", "MHUA", "RETO", "HAO", "EPOW", "CHR", "CCM", "MI", "HLP", "UCAR", "PSIG", "WETH", "CCTG", "WLGS", "NA", "FEDU", "UBXG", "BHAT", "ATGL", "HOLO", "MGIH", "ILAG", "ABTS", "JFU", "JYD", "JZ", "CPOP", "NCI", "SEED", "SUGP", "PT", "AACG", "EDTK", "MOGU", "MMV", "JZXN", "ZKIN", "XIN", "JDZG", "GSUN", "CLWT", "CJET", "INTJ", "LOBO", "GDHG", "AIHS", "STEC", "JL", "PETZ", "SOS", "YJ", "YQ", "CJJD", "GURE", "JXJT", "WTO", "MFI", "GRFX", "MEGL", "NXTT", "TCTM", "ROMA", "HIHO", "EJH", "UPC", "KRKR", "WAFU", "CLEU", "DTSS", "RAYA", "BON", "CREG", "PWM", "DDC", "SNTG", "LKCO", "CHNR", "YGMZ", "OST", "TCJH", "OCG", "TIRX", "ANTE", "IFBD", "TAOP", "CPHI", "CNET", "BTCT", "EZGO", "SISI", "BAOS", "KXIN", "ZCMD", "ATXG", "JWEL", "TC", "WNW", "LXEH", "DUO", "ITP", "VSME", "FAMI", "SXTC", "BQ", "MLGO", "TANH", "UK", "CNEY", ]; const blacklist_icon_svgstr = '<svg class="_LC-finviz-blacklist-icon" style="position: absolute; left: 80px; top: 0;" width="80" height="36" viewport="0 0 80 36" xmlns="http://www.w3.org/2000/svg"> <g> <title>Layer 1</title> <text stroke-width="4" font-weight="bold" xml:space="preserve" text-anchor="start" font-family="\'Bitter\'" font-size="24" id="svg_1" y="27" x="4.5" fill="#bf0000">黑名单</text> <rect stroke="#bf0000" rx="5" fill-opacity="0" id="svg_4" height="30" width="76" y="3" x="2" stroke-width="4" fill="#000000"/> </g> </svg>'; /** * Returns an array of elements which are the symbol items in the list. * These are the elements which contain the symbol name and code. * * @return {HTMLCollectionOf<Element>} the symbol items */ function getSymbolItems() { // 列表模式下的选择器: 正则比配: tickerName- // 图表模式下的选择器,正则比配:symbolNameBox-. @todo 需要优化,图表模式下,tv会自动删除不必要的图表项目,导致全选无法选中所有的图表。暂时没有办法解决 let list_mode_items = findElementsByClassRegex(/tickerName-/); let chart_mode_items = findElementsByClassRegex(/symbolNameBox-/); if (checkIsChartMode()) { return chart_mode_items; } else { return list_mode_items; } } function copyToClipboard(text) { // 将文本复制到剪贴板 navigator.clipboard .writeText(text) .then(function () { console.debug("Text copied to clipboard"); }) .catch(function (err) { console.error("Failed to copy text to clipboard: ", err); }); } /** * Finds all elements in the document whose class attribute starts with the * given regex pattern. * @param {RegExp} classNameRegex the regex pattern to match against the * element's class attribute * 注意:这个选择器是根据class属性的值匹配的。如果元素有多个class选择器,但是class属性只有一个哦 * @return {!Array.<!Element>} an array of elements that match the given regex * pattern * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector * 不知道为什么 class~=name,会找不到 */ function findElementsByClassRegex(classNameRegex) { const selector = `*[class*="${classNameRegex.source}"]`; return document.querySelectorAll(selector); } function checkIsChartMode() { let el = findElementsByClassRegex(/chartsContent/); return el && el.length > 0; } // 显示Toast消息 function showToast(message) { // 创建一个div元素作为Toast消息容器 var toast = document.createElement("div"); toast.style.position = "fixed"; toast.style.top = "5%"; toast.style.left = "50%"; toast.style.transform = "translate(-50%, -50%)"; toast.style.backgroundColor = "rgba(0, 0, 0, 0.7)"; toast.style.color = "#fff"; toast.style.padding = "10px"; toast.style.borderRadius = "5px"; toast.style.zIndex = "9999"; toast.textContent = message; document.body.appendChild(toast); setTimeout(function () { document.body.removeChild(toast); }, 2000); } function insertStyleNode() { let style_node_id = "_my_button_style"; // 获取具有ID为myDiv的节点 var el = document.getElementById(style_node_id); // 从body中删除myDiv节点 if (el) { document.body.removeChild(el); } // 创建一个style元素 var style = document.createElement("style"); style.id = style_node_id; // 设置style元素的内容为CSS代码 style.innerHTML = ` ._LC-button { border: none; padding: 3px 5px; background-color: #007bff4f; color: #fff; border-radius: 2px; cursor: pointer; font-size: 10px; } ._LC-button:hover { background-color: #f0f3fa; } ._LC-button:active { background-color: #0033664f; } ._LC-batch-copy-button{ border-radius: 6px; font-size: 12px; min-width: 34px; --ui-lib-light-button-default-color-bg: #0000; --ui-lib-light-button-default-color-content: #131722; --ui-lib-light-button-default-color-border: #e0e3eb; align-items: center; background-color: var(--ui-lib-light-button-color-bg, var(--ui-lib-light-button-default-color-bg)); border-color: var(--ui-lib-light-button-color-border, var(--ui-lib-light-button-default-color-border)); border-style: solid; border-width: 1px; box-sizing: border-box; color: var(--ui-lib-light-button-color-content, var(--ui-lib-light-button-default-color-content)); cursor: default; display: flex; justify-content: center; min-width: 36px; outline: none; padding: 0; margin-right: 0; } ._LC-chart-link,._LC-copy-button,._LC-finviz-add-remove-blacklist-button{ font-family: -apple-system, BlinkMacSystemFont, Trebuchet MS, Roboto, Ubuntu, sans-serif; font-feature-settings: "tnum" on, "lnum" on; --ui-lib-typography-line-height: 16px; line-height: var(--ui-lib-typography-line-height); --ui-lib-typography-font-size: 12px; background-color: #f0f3fa; border:none; border-radius: 6px; margin-left:3px; box-sizing: border-box; color: #131722; display: block; font-size: var(--ui-lib-typography-font-size); font-style: normal; font-weight: 600; max-width: 96px; min-width: 36px; overflow: hidden; padding: 4px 8px; text-align: center; text-overflow: ellipsis; text-transform: uppercase; white-space: nowrap; cursor:pointer; } `; // 将style元素添加到body中 document.body.appendChild(style); } // 插入“复制全部”按钮到页面 function insertCopyAllButton() { let buttonWrapper = document.querySelector( ".innerControlContainer-k3vjdDEs" ); if (!buttonWrapper) { buttonWrapper = document.getElementById("js-screener-container"); } if (!buttonWrapper) { buttonWrapper = document.body; } // 获取具有ID为myDiv的节点 var batch_copy_button = document.getElementById(batch_button_id); // 从body中删除myDiv节点 if (batch_copy_button) { buttonWrapper.removeChild(batch_copy_button); } // 创建一个div元素 batch_copy_button = document.createElement("button"); // 设置div元素的属性和样式 batch_copy_button.id = batch_button_id; batch_copy_button.classList.add("_LC-button"); batch_copy_button.classList.add("_LC-batch-copy-button"); batch_copy_button.innerHTML = `<svg width="28" height="28" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg" stroke-width="3" stroke="#000000" fill="none"><rect x="11.13" y="17.72" width="33.92" height="36.85" rx="2.5"/><path d="M19.35,14.23V13.09a3.51,3.51,0,0,1,3.33-3.66H49.54a3.51,3.51,0,0,1,3.33,3.66V42.62a3.51,3.51,0,0,1-3.33,3.66H48.39"/></svg>`; // 将div元素插入到body中 batch_copy_button.title = "批量复制股票代码"; buttonWrapper.appendChild(batch_copy_button); batch_copy_button.addEventListener("click", function (event) { const elements = getSymbolItems(); const texts = []; elements.forEach((el) => { texts.push(el.innerText); }); if (texts.length === 0) { showToast("页面中没有找到代码,请检查脚本选择器"); } else { const res = texts.join(", "); copyToClipboard(res); // 示例用法 if (checkIsChartMode()) { showToast( `已复制 ${texts.length} 个代码到剪贴板。图表模式下可能无法全选复制,请切换到列表模式下复制全部。` ); } else { showToast(`已复制 ${texts.length} 个代码到剪贴板`); } } }); } function insertItemButtons() { let nodes = findElementsByClassRegex(/symbolNameBox-/); if (!nodes.length) { console.debug(`${ext_name} not found chart items`); return; } nodes.forEach((e) => { let target_el = e; let item_wrapper = e.parentNode; let href = e.getAttribute("href") || ""; let exchange_symbol = href .replaceAll(/^\/symbols\//g, "") .replaceAll(/\/$/g, "") .replace("-", ":"); let symbol = exchange_symbol.replace(/^\w+\:/, ""); // 添加移除黑名单 let add_remove_blacklist_button = item_wrapper.querySelector( "._LC-finviz-add-remove-blacklist-button" ); if (!add_remove_blacklist_button) { add_remove_blacklist_button = document.createElement("button"); add_remove_blacklist_button.classList.add("_LC-button"); add_remove_blacklist_button.classList.add( "_LC-finviz-add-remove-blacklist-button" ); if (blacklist.includes(symbol)) { add_remove_blacklist_button.innerText = "-BL"; } else { add_remove_blacklist_button.innerText = "+BL"; } add_remove_blacklist_button.title = "添加/移除黑名单"; item_wrapper.appendChild(add_remove_blacklist_button); add_remove_blacklist_button.addEventListener("click", function (event) { if (blacklist.includes(symbol)) { let index = blacklist.indexOf(symbol); if (index !== -1) { blacklist.splice(index, 1); } add_remove_blacklist_button.innerText = "+BL"; } else { add_remove_blacklist_button.innerText = "-BL"; blacklist.push(symbol); } add_or_remove_blacklist_icon(target_el, symbol); localStorage.setItem("blacklist", JSON.stringify(blacklist)); }); } // 复制按钮 let copybtn = item_wrapper.querySelector("._LC-copy-button"); if (!copybtn) { copybtn = document.createElement("button"); copybtn.title = "复制股票代码"; copybtn.innerText = "复制"; copybtn.classList.add("_LC-button"); copybtn.classList.add("_LC-copy-button"); copybtn.addEventListener("click", function (event) { copyToClipboard(symbol); showToast(`已复制代码:${symbol}`); }); item_wrapper.appendChild(copybtn); } // 跳转tv图表按钮。 let tv_link = item_wrapper.querySelector("._LC-tv-chart-link"); if (!tv_link) { let uri_symbol = encodeURIComponent(exchange_symbol); tv_link = document.createElement("a"); tv_link.classList.add("_LC-button"); tv_link.classList.add("_LC-chart-link"); tv_link.classList.add("_LC-tv-chart-link"); tv_link.href = `https://cn.tradingview.com/chart/700qUKjc/?symbol=${uri_symbol}`; tv_link.target = "_blank"; tv_link.innerText = "图表"; tv_link.title = "查看tradingview图表"; item_wrapper.appendChild(tv_link); } // 跳转finviz图表按钮。 let finviz_link = item_wrapper.querySelector("._LC-finviz-chart-link"); if (!finviz_link) { finviz_link = document.createElement("a"); finviz_link.classList.add("_LC-button"); finviz_link.classList.add("_LC-chart-link"); finviz_link.classList.add("_LC-finviz-chart-link"); finviz_link.href = `https://finviz.com/quote.ashx?t=${symbol}&p=d`; finviz_link.target = "_blank"; finviz_link.innerText = "finviz"; finviz_link.title = "查看finviz图表"; item_wrapper.appendChild(finviz_link); } // 插入黑名单标志 add_or_remove_blacklist_icon(target_el, symbol); }); } // 添加或移除黑名单标志 function add_or_remove_blacklist_icon(target_el, symbol) { let item_wrapper = target_el.parentNode; let blacklist_icon = item_wrapper.querySelector( "._LC-finviz-blacklist-icon" ); if (blacklist.includes(symbol)) { if (!blacklist_icon) { target_el.insertAdjacentHTML("afterend", blacklist_icon_svgstr); } } else { let blacklist_icon = item_wrapper.querySelector( "._LC-finviz-blacklist-icon" ); blacklist_icon && blacklist_icon.remove(); } } function init_blacklist() { let blacklist_str = localStorage.getItem("blacklist"); console.debug("初始化blacklist", blacklist); if (blacklist_str) { try { let list = JSON.parse(blacklist_str); if (Array.isArray(list)) { blacklist = [...chinese_stocks, ...list]; } } catch (error) {} } else { blacklist = [...chinese_stocks]; } } // 初始化blacklist init_blacklist(); insertStyleNode(); insertCopyAllButton(); let _interval = setInterval(function () { console.debug(`${ext_name}定时检测item按钮是否创建`); let nodes = document.querySelectorAll("._LC-chart-link"); // if (nodes && nodes.length > 0) { // console.debug(`${ext_name}item按钮创建完成`) // // if (_interval) { // // clearInterval(_interval) // // _interval = null // // } // } else { // } let items = findElementsByClassRegex(/chartContainer/); if (items && items.length) { insertItemButtons(); } else { console.debug( `${ext_name}没有找到需要添加按钮的item,请检查class属性是否正确: .chartContainer ` ); } }, 5000); })();