您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在Twitter/X上标记有劣迹的加密货币KOL,并实时显示其劣迹指数(0-100分)和不良行为记录。整合公共数据和本地自定义功能,帮助用户识别高风险KOL,避免投资陷阱。支持证据查看、数据更新和自定义标记功能。
// ==UserScript== // @name 币圈KOL劣迹标记 - X(Twitter) // @namespace http://tampermonkey.net/ // @icon data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAB7ElEQVR4Ae1XMZLCMAwUdw0ldJQ8ATpKnkBJByUd8ALyA/gBdJTQUtHS8QT4AaRM5ctmThmfogQ75CYNmhGTbGJr45Vk0yAiQzXaF9VsHwIZAofDgYwxqo9GI/K16/X6cqyxvdVqmdvtZh6PhwmCIHXcw7vdrpFj8ny9XhsYxhe8lwWHw2EycLFYpNh0Ok2w8/nsFHy1WrkE1wnAN5tNMkGv10ux3W6XIab5fD5P3ovldCGrP2Ap4LiW8uRJAcIwe1wpArYU0FJimhQgxaQ9cqX4BZYCgSVmS8HBfRP1JQEsY1xKGSmAcTC+l0QrIWDraicVMBBA4O1265ScpQnAMbkMwphjub1HAI7EkxoDK7n0/gQQGATsCmDMo+z++Hf8E5CjPZ9PiqKIZrMZhWFIl8slxcbjMTWbTTqdTuRrXoz5i2WXRIL+WxWw2+Uml13rnJUT4K9E9nMFaF3SxiojoO1u2rJzl4z3/+oIcHBMLiUp2rDe3ozg+BIYtNee87KjGzLGndPx7JD/0K7xog2Gl30ymaSY1jm9CPhsrXnnBK1zOhHgCWWtF7l2TtA6p3S1E+73exoMBrRcLul4PJKL3e93arfbSUeMA1O/36eYPHU6nWQu7pyaqRlfZnezV05anhSN34va7PPXrHYCP+VaTG3LBV1KAAAAAElFTkSuQmCC // @version 1.3 // @description 在Twitter/X上标记有劣迹的加密货币KOL,并实时显示其劣迹指数(0-100分)和不良行为记录。整合公共数据和本地自定义功能,帮助用户识别高风险KOL,避免投资陷阱。支持证据查看、数据更新和自定义标记功能。 // @author @mr96_0x0 (TG: @Mr96_me) // @license GNU General Public License v3.0 or later // @match https://twitter.com/* // @match https://x.com/* // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @connect * // ==/UserScript== (function() { 'use strict'; const developerInfo = { twitter: "@mr96_0x0", telegram: "@Mr96_me" }; const PUBLIC_DATA_URL = "https://gist.githubusercontent.com/Mr96s/b05aa6971cea6407bcb00621b6c20197/raw/cae13b5bc4003a482133a74cee1ea034e4bbc253/kol-data.json"; const indexColors = { 0: { bg: "#e0e0e0", text: "#000000", emoji: "✅" }, // 0-9 10: { bg: "#ccffcc", text: "#000000", emoji: "🔍" }, // 10-19 20: { bg: "#99ff99", text: "#000000", emoji: "🔍" }, // 20-29 30: { bg: "#ffff99", text: "#000000", emoji: "⚠️" }, // 30-39 40: { bg: "#ffeb3b", text: "#000000", emoji: "⚠️" }, // 40-49 50: { bg: "#ffcc00", text: "#000000", emoji: "⚠️" }, // 50-59 60: { bg: "#ff9800", text: "#ffffff", emoji: "🚨" }, // 60-69 70: { bg: "#ff5722", text: "#ffffff", emoji: "🚨" }, // 70-79 80: { bg: "#f44336", text: "#ffffff", emoji: "☠️" }, // 80-89 90: { bg: "#d32f2f", text: "#ffffff", emoji: "☠️" } // 90-100 }; function getColorConfig(index) { const tier = Math.min(Math.floor(index / 10) * 10, 90); return indexColors[tier] || indexColors[50]; } let badKOLs = {}; let usePublicData = GM_getValue('usePublicData', true); let useLocalData = GM_getValue('useLocalData', true); function loadData() { const publicData = GM_getValue('publicData', {}); const localData = GM_getValue('localData', {}); badKOLs = {}; if (usePublicData) Object.assign(badKOLs, publicData); if (useLocalData) Object.assign(badKOLs, localData); checkForKOLs(); } function fetchPublicData() { if (!usePublicData) return; GM_xmlhttpRequest({ method: "GET", url: PUBLIC_DATA_URL, onload: function(response) { try { const data = JSON.parse(response.responseText); GM_setValue('publicData', data); loadData(); } catch (e) { console.error("Failed to parse public data:", e); } }, onerror: function(error) { console.error("Failed to fetch public data:", error); } }); } const observer = new MutationObserver(checkForKOLs); observer.observe(document.body, { childList: true, subtree: true }); function checkForKOLs() { const profileHeader = document.querySelector('[data-testid="UserName"]'); if (profileHeader) { const screenName = document.querySelector('[data-testid="UserName"] div:nth-child(2) div span')?.textContent; if (screenName && badKOLs[screenName]) { addWarningBadge(profileHeader, badKOLs[screenName], true, screenName); } } document.querySelectorAll('[data-testid="tweet"]').forEach(tweet => { const authorLink = tweet.querySelector('a[role="link"][tabindex="-1"]'); if (authorLink) { const author = authorLink.getAttribute('href')?.slice(1); if (author && badKOLs[author]) { addWarningBadge(tweet, badKOLs[author], false, author); } } }); } function addWarningBadge(element, kolData, isProfile, accountName) { if (element.querySelector('.kol-warning-badge')) return; const colorConfig = getColorConfig(kolData.index); const badgeSize = isProfile ? '16px' : '12px'; const badge = document.createElement('div'); badge.className = 'kol-warning-badge'; badge.innerHTML = ` <div style=" background: ${colorConfig.bg}; color: ${colorConfig.text}; padding: 4px 8px; border-radius: 4px; font-weight: bold; display: inline-flex; align-items: center; gap: 4px; margin-left: 8px; cursor: pointer; font-size: ${badgeSize}; line-height: 1; white-space: nowrap; "> ${colorConfig.emoji}劣迹指数:${kolData.index} </div> `; badge.onclick = (e) => { e.stopPropagation(); showKolDetails(kolData, accountName, element); }; if (isProfile) { const nameElement = element.querySelector('div:nth-child(2) div span'); if (nameElement) nameElement.parentNode.appendChild(badge); } else { const authorContainer = element.querySelector('[data-testid="User-Name"]'); if (authorContainer) authorContainer.appendChild(badge); } } function showKolDetails(kolData, accountName, badgeElement) { const modal = document.createElement('div'); modal.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.7); display: flex; justify-content: center; align-items: center; z-index: 9999; `; const content = document.createElement('div'); content.style.cssText = ` background-color: #15202b; color: #ffffff; padding: 20px; border-radius: 12px; max-width: 500px; width: 90%; max-height: 80vh; overflow-y: auto; `; const colorConfig = getColorConfig(kolData.index); let displayName = null; const profileNameElement = document.querySelector('[data-testid="UserName"] div:first-child span'); if (badgeElement.closest('[data-testid="UserName"]')) { displayName = profileNameElement ? profileNameElement.textContent : null; } else { const tweetElement = badgeElement.closest('[data-testid="tweet"]'); if (tweetElement) { const tweetNameElement = tweetElement.querySelector('[data-testid="User-Name"] a div span'); displayName = tweetNameElement ? tweetNameElement.textContent : null; } else { displayName = profileNameElement ? profileNameElement.textContent : null; } } const finalName = displayName || `@${accountName}`; const title = document.createElement('h2'); title.textContent = `⚠️ 【${finalName}】 的 劣迹指数 (${kolData.index})`; title.style.cssText = `margin-top: 0; color: ${colorConfig.bg};`; content.appendChild(title); const recordsTitle = document.createElement('h3'); recordsTitle.textContent = '劣迹记录:'; recordsTitle.style.marginBottom = '8px'; content.appendChild(recordsTitle); const recordsList = document.createElement('ul'); recordsList.style.cssText = 'padding-left: 20px; margin-top: 0;'; kolData.records.forEach(record => { const recordItem = document.createElement('li'); recordItem.style.marginBottom = '12px'; const reason = document.createElement('div'); reason.textContent = record.reason; reason.style.marginBottom = '4px'; recordItem.appendChild(reason); if (record.date && record.date.trim()) { const date = document.createElement('div'); date.textContent = `时间: ${record.date}`; date.style.cssText = 'font-size: 0.9em; color: #8899a6; margin-bottom: 4px;'; recordItem.appendChild(date); } if (record.proof && record.proof.trim()) { const proofLink = document.createElement('a'); proofLink.href = record.proof; proofLink.textContent = '查看详情 →'; proofLink.target = '_blank'; proofLink.style.cssText = 'color: #1da1f2; text-decoration: none; font-size: 0.9em;'; recordItem.appendChild(proofLink); } recordsList.appendChild(recordItem); }); content.appendChild(recordsList); const feedbackSection = document.createElement('div'); feedbackSection.style.cssText = 'margin-top: 20px; padding-top: 15px; border-top: 1px solid #38444d;'; feedbackSection.innerHTML = ` <h4 style="margin-bottom: 8px;">📢 反馈与申诉</h4> <p style="margin-bottom: 8px; font-size: 0.9em;">1.🌟欢迎提交线索!共建币圈透明社区</p> <p style="margin-bottom: 8px; font-size: 0.9em;">2.🗃️数据来源由程序爬取公开信息或用户提交,如您认为标记信息有误,可提交申诉。</p> <h4 style="margin-bottom: 8px;">📬 请通过以下方式提交线索或申诉:</h4> <a href="https://twitter.com/${developerInfo.twitter}" target="_blank" style="display: block; color: #1da1f2; text-decoration: none; font-size: 0.9em; margin-bottom: 4px;">🐦 Twitter: ${developerInfo.twitter}</a> <a href="https://t.me/${developerInfo.telegram.replace('@', '')}" target="_blank" style="display: block; color: #1da1f2; text-decoration: none; font-size: 0.9em;">📨 Telegram: ${developerInfo.telegram}</a> `; content.appendChild(feedbackSection); const closeButton = document.createElement('button'); closeButton.textContent = '关闭'; closeButton.style.cssText = 'margin-top: 15px; padding: 8px 16px; background-color: #1da1f2; color: white; border: none; border-radius: 4px; cursor: pointer;'; closeButton.onclick = () => document.body.removeChild(modal); content.appendChild(closeButton); modal.appendChild(content); document.body.appendChild(modal); modal.onclick = (e) => { if (e.target === modal) document.body.removeChild(modal); }; } function showLocalDataEditor() { const modal = document.createElement('div'); modal.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.7); display: flex; justify-content: center; align-items: center; z-index: 9999; `; const content = document.createElement('div'); content.style.cssText = ` background-color: #15202b; color: #ffffff; padding: 20px; border-radius: 12px; max-width: 600px; width: 90%; max-height: 80vh; overflow-y: auto; `; const title = document.createElement('h2'); title.textContent = '✏️ 编辑本地KOL记录'; title.style.cssText = 'margin-top: 0; color: #1da1f2;'; content.appendChild(title); const localData = GM_getValue('localData', {}); const form = document.createElement('div'); form.innerHTML = ` <div style="margin-bottom: 12px;"> <label style="display: block; margin-bottom: 4px;">KOL用户名 (不含@):</label> <input id="username" type="text" style="width: 100%; padding: 6px; background-color: #253341; color: #ffffff; border: 1px solid #38444d; border-radius: 4px;"> </div> <div style="margin-bottom: 12px;"> <label style="display: block; margin-bottom: 4px;">劣迹指数 (0-100):</label> <input id="index" type="number" min="0" max="100" value="50" style="width: 100%; padding: 6px; background-color: #253341; color: #ffffff; border: 1px solid #38444d; border-radius: 4px;"> </div> <div style="margin-bottom: 12px;"> <label style="display: block; margin-bottom: 4px;">劣迹原因:</label> <input id="reason" type="text" style="width: 100%; padding: 6px; background-color: #253341; color: #ffffff; border: 1px solid #38444d; border-radius: 4px;"> </div> <div style="margin-bottom: 12px;"> <label style="display: block; margin-bottom: 4px;">证据链接 (可选):</label> <input id="proof" type="text" style="width: 100%; padding: 6px; background-color: #253341; color: #ffffff; border: 1px solid #38444d; border-radius: 4px;" placeholder="https://..."> </div> <div style="margin-bottom: 12px;"> <label style="display: block; margin-bottom: 4px;">日期 (可选):</label> <input id="date" type="date" style="width: 100%; padding: 6px; background-color: #253341; color: #ffffff; border: 1px solid #38444d; border-radius: 4px;" value="${new Date().toISOString().split('T')[0]}"> </div> <button id="addRecord" style="padding: 6px 12px; background-color: #1da1f2; color: white; border: none; border-radius: 4px; cursor: pointer;">添加记录</button> `; content.appendChild(form); const usernameInput = form.querySelector('#username'); const indexInput = form.querySelector('#index'); const reasonInput = form.querySelector('#reason'); const proofInput = form.querySelector('#proof'); const dateInput = form.querySelector('#date'); const addButton = form.querySelector('#addRecord'); addButton.onclick = () => { const username = usernameInput.value.trim(); const index = parseInt(indexInput.value); const reason = reasonInput.value.trim(); if (!username || !reason) { alert('用户名和劣迹原因不能为空'); return; } if (isNaN(index) || index < 0 || index > 100) { alert('劣迹指数必须在 0-100 之间'); return; } if (!localData[username]) { localData[username] = { index: index, records: [] }; } else { localData[username].index = index; } localData[username].records.push({ reason: reason, proof: proofInput.value.trim() || '', date: dateInput.value.trim() || '' }); GM_setValue('localData', localData); loadData(); alert(`已添加 ${username} 的记录`); usernameInput.value = ''; reasonInput.value = ''; proofInput.value = ''; updateRecordsList(recordsList, localData); }; const recordsSection = document.createElement('div'); recordsSection.style.marginTop = '20px'; recordsSection.innerHTML = '<h3 style="margin-bottom: 8px;">已有本地记录</h3>'; const recordsList = document.createElement('ul'); recordsList.style.cssText = 'padding-left: 20px; margin-top: 0;'; updateRecordsList(recordsList, localData); recordsSection.appendChild(recordsList); content.appendChild(recordsSection); const closeButton = document.createElement('button'); closeButton.textContent = '关闭'; closeButton.style.cssText = 'margin-top: 15px; padding: 8px 16px; background-color: #1da1f2; color: white; border: none; border-radius: 4px; cursor: pointer;'; closeButton.onclick = () => document.body.removeChild(modal); content.appendChild(closeButton); modal.appendChild(content); document.body.appendChild(modal); modal.onclick = (e) => { if (e.target === modal) document.body.removeChild(modal); }; } function updateRecordsList(recordsList, localData) { recordsList.innerHTML = ''; for (const [username, data] of Object.entries(localData)) { data.records.forEach((record, index) => { const li = document.createElement('li'); li.style.cssText = 'margin-bottom: 12px; display: flex; justify-content: space-between; align-items: center;'; let recordText = `${username} (${data.index}): ${record.reason}`; if (record.date && record.date.trim()) recordText += ` - ${record.date}`; li.innerHTML = ` <span>${recordText}</span> <button style="padding: 4px 8px; background-color: #ff3b30; color: white; border: none; border-radius: 4px; cursor: pointer;">删除</button> `; li.querySelector('button').onclick = () => { data.records.splice(index, 1); if (data.records.length === 0) delete localData[username]; GM_setValue('localData', localData); loadData(); updateRecordsList(recordsList, localData); }; recordsList.appendChild(li); }); } } // 设置界面 function showSettings() { const modal = document.createElement('div'); modal.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.7); display: flex; justify-content: center; align-items: center; z-index: 9999; `; const content = document.createElement('div'); content.style.cssText = ` background-color: #15202b; color: #ffffff; padding: 20px; border-radius: 12px; max-width: 600px; width: 90%; max-height: 80vh; overflow-y: auto; `; const title = document.createElement('h2'); title.textContent = '⚙️ 设置'; title.style.cssText = 'margin-top: 0; color: #1da1f2;'; content.appendChild(title); const dataSourceSection = document.createElement('div'); dataSourceSection.innerHTML = ` <h3 style="margin-bottom: 8px;">数据源设置</h3> <label style="display: block; margin-bottom: 8px;"> <input type="checkbox" id="usePublicData" ${usePublicData ? 'checked' : ''}> 使用公共数据 </label> <label style="display: block; margin-bottom: 8px;"> <input type="checkbox" id="useLocalData" ${useLocalData ? 'checked' : ''}> 使用本地数据 </label> <button id="saveDataSource" style="padding: 6px 12px; background-color: #1da1f2; color: white; border: none; border-radius: 4px; cursor: pointer;">保存</button> `; content.appendChild(dataSourceSection); const publicCheckbox = dataSourceSection.querySelector('#usePublicData'); const localCheckbox = dataSourceSection.querySelector('#useLocalData'); const saveButton = dataSourceSection.querySelector('#saveDataSource'); saveButton.onclick = () => { usePublicData = publicCheckbox.checked; useLocalData = localCheckbox.checked; GM_setValue('usePublicData', usePublicData); GM_setValue('useLocalData', useLocalData); loadData(); alert('数据源设置已保存'); }; const localDataSection = document.createElement('div'); localDataSection.style.marginTop = '20px'; localDataSection.innerHTML = ` <h3 style="margin-bottom: 8px;">本地数据管理</h3> <button id="editLocalData" style="padding: 6px 12px; background-color: #1da1f2; color: white; border: none; border-radius: 4px; cursor: pointer; margin-right: 8px;">编辑本地数据</button> <button id="clearLocalData" style="padding: 6px 12px; background-color: #ff3b30; color: white; border: none; border-radius: 4px; cursor: pointer;">清空本地数据</button> `; localDataSection.querySelector('#editLocalData').onclick = showLocalDataEditor; localDataSection.querySelector('#clearLocalData').onclick = () => { if (confirm('确定清空本地数据吗?')) { GM_setValue('localData', {}); loadData(); alert('本地数据已清空'); } }; content.appendChild(localDataSection); const devInfoSection = document.createElement('div'); devInfoSection.style.cssText = 'margin-top: 20px; padding-top: 15px; border-top: 1px solid #38444d;'; devInfoSection.innerHTML = ` <h3 style="margin-bottom: 8px;">开发者信息</h3> <p style="margin-bottom: 8px; font-size: 0.9em;">Twitter: <a href="https://twitter.com/${developerInfo.twitter}" target="_blank" style="color: #1da1f2; text-decoration: none;">${developerInfo.twitter}</a></p> <p style="margin-bottom: 8px; font-size: 0.9em;">Telegram: <a href="https://t.me/${developerInfo.telegram.replace('@', '')}" target="_blank" style="color: #1da1f2; text-decoration: none;">${developerInfo.telegram}</a></p> `; content.appendChild(devInfoSection); const closeButton = document.createElement('button'); closeButton.textContent = '关闭'; closeButton.style.cssText = 'margin-top: 15px; padding: 8px 16px; background-color: #1da1f2; color: white; border: none; border-radius: 4px; cursor: pointer;'; closeButton.onclick = () => document.body.removeChild(modal); content.appendChild(closeButton); modal.appendChild(content); document.body.appendChild(modal); modal.onclick = (e) => { if (e.target === modal) document.body.removeChild(modal); }; } // 初始化菜单命令 GM_registerMenuCommand('⚙️ 打开设置', showSettings); GM_registerMenuCommand('🔄 更新公共数据', () => { fetchPublicData(); alert('正在更新公共数据...'); }); // 初始化 loadData(); fetchPublicData(); setInterval(fetchPublicData, 24 * 60 * 60 * 1000); // 每24小时更新一次公共数据 })();