// ==UserScript== // @name Twitter/X Pro (CTO领哥版 V10.5) // @namespace http://tampermonkey.net/ // @version 10.5 // @description 1.无感VIP标记 2.备注防丢 3.漏翻修复 4.智能翻译 // @author LingGe // @license MIT // @match https://twitter.com/* // @match https://x.com/* // @match https://pro.twitter.com/* // @match https://pro.x.com/* // @match https://tweetdeck.twitter.com/* // @icon https://www.google.com/s2/favicons?sz=64&domain=twitter.com // @grant GM_xmlhttpRequest // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @downloadURL none // ==/UserScript== (function() { 'use strict'; console.log("🚀 领哥情报终端 V10.5 (MIT开源版) 已启动..."); const TARGET_LANG = 'zh-CN'; const GMGN_REF = '1DRFPE0z'; const DEFAULT_CONFIG = { transColor: '#00E676', transFontSize: '14px', noteColor: '#1D9BF0', noteFontSize: '11px', vipBorderColor: '#F3BA2F' }; // 内置初始 VIP 名单 const INITIAL_VIP_MAP = { 'vitalikbuterin': ['🏛️ ETH创始人', '#716b94', '#fff'], 'cz_binance': ['🔶 币安创始人', '#F0B90B', '#000'], 'elonmusk': ['🚀 狗狗教父', '#000000', '#fff'], 'brian_armstrong': ['🛡️ Coinbase CEO', '#0052FF', '#fff'], 'aeyakovenko': ['🟣 Solana创始人', '#9945FF', '#fff'], 'cryptohayes': ['📝 Arthur Hayes', '#444', '#fff'], 'zachxbt': ['🚨 链上侦探', '#FF0000', '#fff'], 'a16z': ['💰 a16z', '#FF6600', '#fff'], 'paradigm': ['🧠 Paradigm', '#000', '#fff'] }; // ================= 1. 存储管理模块 (本地数据库) ================= const Storage = { // 读取配置,如果不存在则使用默认 getConfig: () => ({ ...DEFAULT_CONFIG, ...JSON.parse(GM_getValue('ling_config', '{}')) }), // 保存配置 setConfig: (cfg) => GM_setValue('ling_config', JSON.stringify(cfg)), getNotes: () => JSON.parse(GM_getValue('ling_user_notes', '{}')), setNotes: (notes) => GM_setValue('ling_user_notes', JSON.stringify(notes)), // 安全添加备注:防止误操作 addNote: (handle, note) => { const notes = Storage.getNotes(); const h = handle.toLowerCase(); if (note && note.trim()) { notes[h] = note.trim(); } else { // 如果用户输入空白,且确认要删除,则删除 delete notes[h]; } Storage.setNotes(notes); }, getNote: (handle) => Storage.getNotes()[handle.toLowerCase()] || null, getVips: () => { let vips = JSON.parse(GM_getValue('ling_vips', 'null')); if (!vips) { vips = INITIAL_VIP_MAP; GM_setValue('ling_vips', JSON.stringify(vips)); } return vips; }, setVips: (vips) => GM_setValue('ling_vips', JSON.stringify(vips)), getVipInfo: (handle) => { const vips = Storage.getVips(); return vips[handle.toLowerCase()] || null; }, addVip: (handle, label) => { const vips = Storage.getVips(); vips[handle.toLowerCase()] = [label, '#F3BA2F', '#000']; Storage.setVips(vips); }, removeVip: (handle) => { const vips = Storage.getVips(); delete vips[handle.toLowerCase()]; Storage.setVips(vips); }, // 全量备份:备注 + VIP + 设置 export: () => { const data = { version: "10.5", timestamp: new Date().getTime(), notes: Storage.getNotes(), vips: Storage.getVips(), config: Storage.getConfig() }; const blob = new Blob([JSON.stringify(data)], {type: 'application/json'}); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `LingGe_Intel_Backup_${new Date().toISOString().slice(0,10)}.json`; a.click(); }, // 恢复备份 import: () => { const input = document.createElement('input'); input.type = 'file'; input.accept = '.json,.txt'; input.onchange = (e) => { const reader = new FileReader(); reader.onload = (event) => { try { const raw = JSON.parse(event.target.result); // 兼容旧版数据结构 if (raw['$myTwitterNoteItems']) { const notes = Storage.getNotes(); for (let k in raw['$myTwitterNoteItems']) { if(raw['$myTwitterNoteItems'][k]?.tag) notes[k.toLowerCase()] = raw['$myTwitterNoteItems'][k].tag; } Storage.setNotes(notes); } else { if(raw.notes) Storage.setNotes({...Storage.getNotes(), ...raw.notes}); if(raw.vips) Storage.setVips({...Storage.getVips(), ...raw.vips}); if(raw.config) Storage.setConfig({...Storage.getConfig(), ...raw.config}); } alert("✅ 数据恢复成功!页面将刷新..."); location.reload(); } catch (err) { alert('❌ 备份文件格式错误'); } }; reader.readAsText(e.target.files[0]); }; input.click(); } }; // ================= 2. 动态样式 (持久化应用) ================= function updateStyles() { const cfg = Storage.getConfig(); const oldStyle = document.getElementById('ling-style'); if (oldStyle) oldStyle.remove(); const css = ` .ling-vip-tweet { border: 2px solid ${cfg.vipBorderColor} !important; background: rgba(243, 186, 47, 0.05) !important; border-radius: 8px !important; } .ling-identity-badge { font-weight: 900; font-size: 10px; padding: 2px 5px; border-radius: 3px; margin-left: 5px; vertical-align: middle; display: inline-block; box-shadow: 0 1px 2px rgba(0,0,0,0.5); color: #000; background: ${cfg.vipBorderColor}; } .ling-trans-box { margin-top: 6px; padding: 8px 10px; background: #000; border: 1px solid ${cfg.transColor}; border-left: 4px solid ${cfg.transColor}; border-radius: 4px; color: ${cfg.transColor} !important; font-size: ${cfg.transFontSize}; line-height: 1.5; font-family: "Consolas", monospace; } .ling-ca-link { color: #F3BA2F !important; font-weight: bold; cursor: pointer; background: rgba(243, 186, 47, 0.15); padding: 0 4px; border-radius: 3px; margin-right: 4px; text-decoration: none !important; } .ling-ca-link:hover { background: rgba(243, 186, 47, 0.4); } .ling-action-btn { cursor: pointer; margin-left: 6px; font-size: 14px; vertical-align: middle; display: inline-block; opacity: 0.4; transition: all 0.2s; filter: grayscale(100%); } .ling-action-btn:hover { opacity: 1; filter: grayscale(0%); transform: scale(1.2); } .ling-action-btn.active { opacity: 1; filter: grayscale(0%); text-shadow: 0 0 8px gold; } .ling-user-note { background-color: ${cfg.noteColor}; color: #fff; font-size: ${cfg.noteFontSize}; padding: 2px 6px; border-radius: 4px; margin-left: 5px; vertical-align: middle; display: inline-block; cursor: pointer; max-width: 150px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-weight: bold; } #ling-settings-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.7); z-index: 99999; display: flex; justify-content: center; align-items: center; } #ling-settings-box { background: #16181c; border: 1px solid #333; border-radius: 12px; padding: 20px; width: 300px; color: #fff; } .ling-row { margin-bottom: 15px; display: flex; justify-content: space-between; align-items: center; } .ling-btn { background: #00E676; color: #000; border: none; padding: 8px; border-radius: 5px; width: 100%; font-weight: bold; cursor: pointer; } `; const node = document.createElement('style'); node.id = 'ling-style'; node.innerHTML = css; document.head.appendChild(node); } // ================= 3. 核心逻辑 ================= // 设置菜单 function openSettings() { if (document.getElementById('ling-settings-overlay')) return; const cfg = Storage.getConfig(); const div = document.createElement('div'); div.id = 'ling-settings-overlay'; div.innerHTML = `

⚙️ 终端设置

`; document.body.appendChild(div); document.getElementById('ling-close').onclick = () => div.remove(); document.getElementById('ling-save').onclick = () => { Storage.setConfig({ transColor: document.getElementById('c-tc').value, transFontSize: document.getElementById('c-ts').value, noteColor: document.getElementById('c-nc').value, noteFontSize: cfg.noteFontSize, vipBorderColor: document.getElementById('c-vc').value }); updateStyles(); div.remove(); }; } GM_registerMenuCommand("⚙️ 设置", openSettings); GM_registerMenuCommand("📤 导出备份", Storage.export); GM_registerMenuCommand("📥 导入备份", Storage.import); // 翻译 + CA识别 function translateText(element, text) { if (!text || text.trim().length === 0) return; const cnMatch = text.match(/[\u4e00-\u9fa5]/g); if (cnMatch && (cnMatch.length / text.length > 0.4)) return; if (element.dataset.lingTranslating) return; element.dataset.lingTranslating = "true"; const url = `https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=${TARGET_LANG}&dt=t&q=${encodeURIComponent(text)}`; GM_xmlhttpRequest({ method: "GET", url: url, onload: (res) => { try { const data = JSON.parse(res.responseText); let result = ""; if (data && data[0]) data[0].forEach(i => { if(i[0]) result += i[0]; }); if (result) { if (element.parentNode.querySelector('.ling-trans-box')) return; const div = document.createElement('div'); div.className = 'ling-trans-box'; let processedHtml = result; // 1. Solana (Base58) -> gmgn.ai/sol/token/INVITECODE_ADDRESS processedHtml = processedHtml.replace(/\b([1-9A-HJ-NP-Za-km-z]{32,44})\b/g, (match) => `[SOL:⚡]` ); // 2. ETH & BSC (EVM) -> gmgn.ai/chain/token/INVITECODE_ADDRESS processedHtml = processedHtml.replace(/\b(0x[a-fA-F0-9]{40})\b/g, (match) => `|` + `[ETH]` + `[BSC]` ); div.innerHTML = `[🤖 领哥AI译]
${processedHtml}`; element.parentNode.appendChild(div); } } catch(e){} delete element.dataset.lingTranslating; } }); } // 动态更新界面 function refreshUI(handle, container) { const note = Storage.getNote(handle); let noteSpan = container.querySelector('.ling-user-note'); if (note) { if (!noteSpan) { noteSpan = document.createElement('span'); noteSpan.className = 'ling-user-note'; const toolbar = container.querySelector('.ling-toolbar'); if (toolbar) container.insertBefore(noteSpan, toolbar); else container.appendChild(noteSpan); } noteSpan.innerText = note; noteSpan.onclick = (e) => { e.preventDefault(); e.stopPropagation(); editNote(handle, container); }; } else if (noteSpan) { noteSpan.remove(); } const vipInfo = Storage.getVipInfo(handle); let vipBadge = container.querySelector('.ling-identity-badge'); const article = container.closest('article'); if (vipInfo) { if (article) article.classList.add('ling-vip-tweet'); if (!vipBadge) { vipBadge = document.createElement('span'); vipBadge.className = 'ling-identity-badge'; container.appendChild(vipBadge); } vipBadge.innerText = vipInfo[0]; const starBtn = container.querySelector('.ling-star-btn'); if (starBtn) starBtn.classList.add('active'); } else { if (article) article.classList.remove('ling-vip-tweet'); if (vipBadge) vipBadge.remove(); const starBtn = container.querySelector('.ling-star-btn'); if (starBtn) starBtn.classList.remove('active'); } } function editNote(handle, container) { const old = Storage.getNote(handle) || ""; const val = prompt(`📝 备注 @${handle}:`, old); if (val !== null) { Storage.addNote(handle, val); refreshUI(handle, container); } } function toggleVip(handle, container) { const info = Storage.getVipInfo(handle); if (info) { if (confirm(`⚠️ 取消 @${handle} 的重点关注?`)) { Storage.removeVip(handle); refreshUI(handle, container); } } else { const label = prompt(`🔥 设为重点关注 @${handle}\n输入标签 (如: 顶级VC):`, "重点关注"); if (label) { Storage.addVip(handle, label); refreshUI(handle, container); } } } function processArticle(article) { if (article.dataset.lingProcessed) return; let handle = null, container = null; const links = article.querySelectorAll('a[href*="/"]'); for (let link of links) { const h = link.getAttribute('href'); if (h && h.length > 1 && !h.includes('/status/') && !h.includes('/hashtag/')) { const cleanHandle = h.replace('/', '').toLowerCase(); const userNameDiv = article.querySelector('div[data-testid="User-Name"]'); if (userNameDiv && userNameDiv.contains(link)) { handle = cleanHandle; container = link.querySelector('div[dir="ltr"]') || link.parentNode; break; } } } if (handle && container) { article.dataset.lingProcessed = "true"; if (!container.querySelector('.ling-toolbar')) { const toolbar = document.createElement('span'); toolbar.className = 'ling-toolbar'; toolbar.style.whiteSpace = "nowrap"; const pen = document.createElement('span'); pen.className = 'ling-action-btn'; pen.innerHTML = '✏️'; pen.onclick = (e) => { e.preventDefault(); e.stopPropagation(); editNote(handle, container); }; const star = document.createElement('span'); star.className = 'ling-action-btn ling-star-btn'; star.innerHTML = '⭐'; star.onclick = (e) => { e.preventDefault(); e.stopPropagation(); toggleVip(handle, container); }; toolbar.appendChild(pen); toolbar.appendChild(star); container.appendChild(toolbar); } refreshUI(handle, container); } let textDiv = article.querySelector('div[data-testid="tweetText"]'); if (!textDiv && location.host.includes('tweetdeck')) textDiv = article.querySelector('.tweet-text'); if (textDiv) { setTimeout(() => translateText(textDiv, textDiv.innerText), 1000); } } // ================= 4. 启动 ================= updateStyles(); const observer = new MutationObserver((ms) => { ms.forEach(m => m.addedNodes.forEach(n => { if (n.nodeType === 1) { if(n.tagName === 'ARTICLE') processArticle(n); else n.querySelectorAll('article').forEach(processArticle); } })); }); const start = () => { if(document.body) { document.querySelectorAll('article').forEach(processArticle); observer.observe(document.body, {childList: true, subtree: true}); } else setTimeout(start, 500); }; start(); })();