您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
仅在搜索页(/search?q=)过滤 Solana 合约广告;离开搜索页自动停用并恢复。兼容不同语言/实验UI。
// ==UserScript== // @name X/Twitter SOL CA Spam Killer (v2.6 – search page only, robust) // @namespace https://x.com/ // @version 2.6.0 // @description 仅在搜索页(/search?q=)过滤 Solana 合约广告;离开搜索页自动停用并恢复。兼容不同语言/实验UI。 // @match https://x.com/* // @match https://twitter.com/* // @run-at document-start // @grant GM_registerMenuCommand // ==/UserScript== (function () { 'use strict'; /*** 仅搜索页判定(更宽松:startsWith + 必须有 q 参数) ***/ const isSearchURL = (u = location.href) => { try { const url = new URL(u); return url.pathname.startsWith('/search') && url.searchParams.has('q'); } catch { return false; } }; /*** 监听 SPA 路由变化 ***/ const listeners = new Set(); const notify = () => listeners.forEach(fn => fn()); for (const k of ['pushState', 'replaceState']) { const orig = history[k]; history[k] = function () { const r = orig.apply(this, arguments); setTimeout(notify, 0); return r; }; } addEventListener('popstate', notify); /*** 配置与规则 ***/ const STORAGE_KEY = 'sol_ca_filter_enabled_v26'; let ENABLED_MENU = JSON.parse(localStorage.getItem(STORAGE_KEY) || 'true'); let ACTIVE = false; const BLOCKED_DOMAINS = [ 'okai.hk','okai.hk/alpha','alpha.mevx.io', 'gmgn.ai','gmgn.ai/sol/token','gmgn.cc','gmgn.org', 'photon-sol.com','dexscreener.com','birdeye.so','rugcheck.xyz', 'pump.fun','pumpswap','t.me' ]; const KEYWORDS = [ 'token alert','token stats','links','security', 'mc $','market cap','vol','lp','ath', 'watch your entry','called at','quick buy','signal', 'up up up','gmgn','bonkbot','trojan', 'chain: solana','dev: holds token','mint authority: no','freeze authority: no', '📍ca',' ca:',' ca:',' ca,',' ca,',' ca;',' ca;',' ca>',' ca>>','ca>' ]; const WHITELIST = ['$smiley','#smiley']; const SOL_ADDR_RE = /\b(?=.{32,44}\b)(?!.*[OIl0])[1-9A-HJ-NP-Za-km-z]{32,44}\b/g; const CA_NEAR_ADDR_RE = /\b(?:ca|contract|合约)\b[\s::,,;;>>»》›]+[\s\r\n]{0,40}[1-9A-HJ-NP-Za-km-z]{32,44}\b/i; const TICKER_RE = /\$[A-Z]{2,8}\b/; const normalize = (s) => (s||'').replace(/>/gi,'>').toLowerCase() .replace(/[\u200B-\u200D\uFEFF]/g,'') .replace(/[:﹕꞉⦂︰]/g,':') .replace(/\s+/g,' ').trim(); const normKeywords = KEYWORDS.map(normalize); const normWhitelist = WHITELIST.map(normalize); const hasBlockedDomain = (el) => { const links = el.querySelectorAll('a[href], a[role="link"]'); for (const a of links) { const href = (a.getAttribute('href') || a.textContent || '').toLowerCase(); for (const d of BLOCKED_DOMAINS) if (href.includes(d)) return true; } const t = (el.innerText || '').toLowerCase(); return BLOCKED_DOMAINS.some(d => t.includes(d)); }; const keywordScore = (t) => normKeywords.reduce((n,k)=> n + (k && t.includes(k) ? 1 : 0), 0); const hitWhitelist = (t) => normWhitelist.some(w => w && t.includes(w)); function isSpamArticle(article) { const raw = article.innerText || article.textContent || ''; if (!raw) return false; if (hasBlockedDomain(article)) return true; const text = normalize(raw); if (hitWhitelist(text)) return false; const addrs = raw.match(SOL_ADDR_RE) || []; const counts = {}; addrs.forEach(a => counts[a] = (counts[a] || 0) + 1); const repeated = Object.values(counts).some(c => c >= 2); if (CA_NEAR_ADDR_RE.test(raw)) return true; if (addrs.length) { if (repeated) return true; const score = keywordScore(text); const hasTicker = TICKER_RE.test(raw); if (hasTicker && score >= 1) return true; // 地址 + $TICKER + ≥1 版式词 if (!hasTicker && score >= 2) return true; // 地址 + ≥2 版式词 } if (!addrs.length && keywordScore(text) >= 4) return true; return false; } /*** DOM 处理(在搜索页时对整页 article 扫描;离开即清空) ***/ const TWEET_SELECTOR = 'article[data-testid="tweet"], article[role="article"]'; const HIDE_CLASS = 'sol-ca-hide'; const style = document.createElement('style'); style.textContent = `.${HIDE_CLASS}{display:none !important;}`; document.documentElement.appendChild(style); const handleTweet = (a) => { if (!ACTIVE || !a || a.dataset.__solCaChecked==='1') return; a.dataset.__solCaChecked = '1'; if (isSpamArticle(a)) a.classList.add(HIDE_CLASS); }; const scanAll = () => { if (!ACTIVE) return; document.querySelectorAll(TWEET_SELECTOR).forEach(handleTweet); }; const clearAll = () => { document.querySelectorAll(`.${HIDE_CLASS}`).forEach(n => n.classList.remove(HIDE_CLASS)); document.querySelectorAll(TWEET_SELECTOR).forEach(n => { n.dataset.__solCaChecked = ''; }); }; let obs = null; const observe = () => { if (obs) return; obs = new MutationObserver(muts => { if (!ACTIVE) return; for (const m of muts) for (const node of m.addedNodes) { if (!(node instanceof HTMLElement)) continue; if (node.matches?.(TWEET_SELECTOR)) handleTweet(node); else node.querySelectorAll?.(TWEET_SELECTOR).forEach(handleTweet); } }); obs.observe(document.body, { childList: true, subtree: true }); }; const unobserve = () => { if (obs) { obs.disconnect(); obs = null; } }; /*** 激活/停用(仅搜索页) ***/ function reevaluate() { const shouldRun = ENABLED_MENU && isSearchURL(); if (shouldRun && !ACTIVE) { ACTIVE = true; observe(); scanAll(); setTimeout(scanAll, 600); setTimeout(scanAll, 2000); } else if (!shouldRun && ACTIVE) { ACTIVE = false; unobserve(); clearAll(); // 离开搜索页恢复 } } listeners.add(reevaluate); /*** 菜单 ***/ function menu() { if (typeof GM_registerMenuCommand !== 'function') return; GM_registerMenuCommand(`过滤器(仅搜索页):${ENABLED_MENU ? '✅ 开启' : '⛔ 关闭'}`, ()=>{}); GM_registerMenuCommand(ENABLED_MENU ? '🔕 关闭过滤器' : '🔔 开启过滤器', () => { ENABLED_MENU = !ENABLED_MENU; localStorage.setItem(STORAGE_KEY, JSON.stringify(ENABLED_MENU)); alert(ENABLED_MENU ? '过滤器开启(仅搜索页)' : '过滤器已关闭'); reevaluate(); }); } /*** 启动 ***/ const ready = (fn) => (document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', fn, { once:true }) : fn()); ready(() => { menu(); reevaluate(); setInterval(reevaluate, 1500); // 兜底 }); })();