// ==UserScript== // @name X 推文关键词逐条搜索滚动器(隐形悬浮版) // @namespace http://tampermonkey.net/ // @version 3.0 // @description 一条一条匹配推文关键词,支持悬浮球模式,不遮挡视野。找到后定位页面顶部,支持停止、暗黑界面优化。 // @author @喂你吃药 // @match https://x.com/* // @match https://twitter.com/* // @grant GM_addStyle // @downloadURL none // ==/UserScript== (function () { 'use strict'; // --- 核心配置 --- let stopRequested = false; let processedTweets = new Set(); let scrollStep = 1000; let isRunning = false; // --- CSS 样式注入 (现代化 UI) --- const styles = ` #ts-floater { position: fixed; z-index: 9999; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; user-select: none; transition: opacity 0.3s; } /* 悬浮小球模式 */ .ts-mini-ball { width: 40px; height: 40px; background: rgba(29, 155, 240, 0.6); /* Twitter Blue 半透明 */ border-radius: 50%; box-shadow: 0 4px 10px rgba(0,0,0,0.3); cursor: pointer; display: flex; align-items: center; justify-content: center; color: white; font-size: 20px; backdrop-filter: blur(4px); transition: transform 0.2s, background 0.3s; } .ts-mini-ball:hover { background: rgba(29, 155, 240, 1); transform: scale(1.1); } /* 展开面板模式 */ .ts-panel { width: 320px; background: rgba(0, 0, 0, 0.85); backdrop-filter: blur(10px); border: 1px solid #333; border-radius: 16px; padding: 16px; box-shadow: 0 8px 32px rgba(0,0,0,0.5); color: #fff; display: none; /* 默认隐藏 */ flex-direction: column; gap: 12px; } .ts-header { display: flex; justify-content: space-between; align-items: center; cursor: move; border-bottom: 1px solid #333; padding-bottom: 8px; margin-bottom: 4px; } .ts-title { font-weight: 700; font-size: 14px; color: #eff3f4; } .ts-controls { display: flex; gap: 8px; } .ts-btn-icon { background: none; border: none; color: #71767b; cursor: pointer; font-size: 16px; padding: 0 4px; } .ts-btn-icon:hover { color: #1d9bf0; } input.ts-input { background: #202327; border: 1px solid #333; color: #eff3f4; padding: 8px 12px; border-radius: 20px; outline: none; font-size: 14px; width: 100%; box-sizing: border-box; } input.ts-input:focus { border-color: #1d9bf0; } .ts-row { display: flex; gap: 10px; align-items: center; } .ts-btn { flex: 1; padding: 8px; border-radius: 20px; border: none; font-weight: bold; cursor: pointer; font-size: 13px; transition: opacity 0.2s; } .ts-btn-primary { background: #1d9bf0; color: white; } .ts-btn-danger { background: #f4212e; color: white; } .ts-btn:hover { opacity: 0.9; } .ts-btn:disabled { background: #555; cursor: not-allowed; } .ts-status { font-size: 12px; color: #71767b; text-align: center; min-height: 1.2em; } /* 高亮样式 */ .ts-highlight { border: 2px solid #1d9bf0 !important; background: rgba(29, 155, 240, 0.1) !important; box-shadow: 0 0 15px rgba(29, 155, 240, 0.3); transition: all 0.5s; } `; // 注入 CSS const styleEl = document.createElement('style'); styleEl.innerHTML = styles; document.head.appendChild(styleEl); // --- UI 构建 --- function createUI() { const container = document.createElement('div'); container.id = 'ts-floater'; // 读取上次保存的位置 const savedPos = JSON.parse(localStorage.getItem('ts_pos') || '{"top":"100px","left":"20px"}'); container.style.top = savedPos.top; container.style.left = savedPos.left; // HTML 结构 container.innerHTML = `
🔍
X 推文搜索器
像素/次
准备就绪 (点击小球隐藏)
`; document.body.appendChild(container); // 绑定元素 const ball = document.getElementById('ts-ball'); const panel = document.getElementById('ts-panel'); const minimizeBtn = document.getElementById('ts-minimize'); const startBtn = document.getElementById('ts-start'); const stopBtn = document.getElementById('ts-stop'); const statusText = document.getElementById('ts-status'); const header = document.getElementById('ts-header'); const keywordInput = document.getElementById('ts-keyword'); const speedInput = document.getElementById('ts-speed'); // --- 交互逻辑 --- // 切换显示模式 function toggleMode(showPanel) { if (showPanel) { ball.style.display = 'none'; panel.style.display = 'flex'; } else { panel.style.display = 'none'; ball.style.display = 'flex'; } } ball.addEventListener('click', () => toggleMode(true)); minimizeBtn.addEventListener('click', () => toggleMode(false)); // 拖拽逻辑 (通用) let isDragging = false; let startX, startY, initialLeft, initialTop; function startDrag(e) { // 如果点的是输入框或按钮,不拖拽 if (['INPUT', 'BUTTON'].includes(e.target.tagName)) return; isDragging = true; startX = e.clientX; startY = e.clientY; const rect = container.getBoundingClientRect(); initialLeft = rect.left; initialTop = rect.top; // 拖拽时增加一点透明度,方便看后面 container.style.opacity = '0.8'; } function onDrag(e) { if (!isDragging) return; e.preventDefault(); const dx = e.clientX - startX; const dy = e.clientY - startY; const newLeft = initialLeft + dx; const newTop = initialTop + dy; container.style.left = newLeft + 'px'; container.style.top = newTop + 'px'; } function stopDrag() { if (isDragging) { isDragging = false; container.style.opacity = '1'; // 保存位置 localStorage.setItem('ts_pos', JSON.stringify({ top: container.style.top, left: container.style.left })); } } // 允许拖拽小球和面板头部 ball.addEventListener('mousedown', startDrag); header.addEventListener('mousedown', startDrag); document.addEventListener('mousemove', onDrag); document.addEventListener('mouseup', stopDrag); // --- 搜索业务逻辑 --- startBtn.addEventListener('click', () => { const keyword = keywordInput.value.trim(); if (!keyword) { statusText.textContent = "请输入关键词"; return; } scrollStep = parseInt(speedInput.value) || 1000; stopRequested = false; isRunning = true; processedTweets.clear(); // UI 状态更新 statusText.textContent = `正在搜索: ${keyword}...`; startBtn.disabled = true; stopBtn.disabled = false; startScrolling(keyword); }); stopBtn.addEventListener('click', () => { stopRequested = true; statusText.textContent = "已手动停止"; isRunning = false; startBtn.disabled = false; stopBtn.disabled = true; }); // 辅助函数:延迟 const delay = ms => new Promise(resolve => setTimeout(resolve, ms)); // 滚动逻辑 async function startScrolling(keyword) { let notFoundCount = 0; while (!stopRequested) { const tweets = document.querySelectorAll('[data-testid="tweet"]'); let foundInBatch = false; for (let tweet of tweets) { // 获取推文唯一标识(优先用链接,其次用文本hash模拟) const linkNode = tweet.querySelector('a[href*="/status/"]'); const id = linkNode ? linkNode.href : tweet.innerText.slice(0, 30); if (processedTweets.has(id)) continue; processedTweets.add(id); const textBlock = tweet.querySelector('[data-testid="tweetText"]'); const tweetText = textBlock ? textBlock.innerText : ''; if (tweetText.includes(keyword)) { foundInBatch = true; // 找到目标 tweet.scrollIntoView({ behavior: 'smooth', block: 'center' }); // 高亮处理 tweet.classList.add('ts-highlight'); // 自动停止 stopRequested = true; isRunning = false; statusText.textContent = `找到匹配!`; startBtn.disabled = false; stopBtn.disabled = true; // 稍微闪烁一下 UI 提醒 panel.style.borderColor = '#1d9bf0'; setTimeout(() => panel.style.borderColor = '#333', 1000); return; // 退出函数 } } // 没找到,继续滚 if (!foundInBatch) { notFoundCount++; } window.scrollBy({ top: scrollStep, behavior: 'smooth' }); // 动态调整等待时间,防止网速慢加载不出来 await delay(800); // 简单的防死循环机制(如果页面到底了或者很久没新内容) // 这里可以根据需要添加逻辑,比如检测高度是否变化 } } } // --- 初始化 --- if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', createUI); } else { createUI(); } })();