Greasy Fork is available in English.
一条一条匹配推文关键词,找到后将其定位在页面顶部,支持停止按钮,暗黑界面优化,美观浮窗,滚动速度可调
当前为
// ==UserScript==
// @name X 推文关键词逐条搜索滚动器(增强版)
// @namespace http://tampermonkey.net/
// @version 2.3
// @description 一条一条匹配推文关键词,找到后将其定位在页面顶部,支持停止按钮,暗黑界面优化,美观浮窗,滚动速度可调
// @author _Sure.Lee
// @match https://x.com/*
// @match https://twitter.com/*
// @grant none
// ==/UserScript==
(function () {
'use strict';
let stopRequested = false;
let processedTweets = new Set();
let currentSearchKeyword = '';
let scrollStep = 1000; // 默认滚动像素
let searchStatus = '准备就绪';
let matchCount = 0;
let totalProcessed = 0;
function createSearchUI() {
const container = document.createElement('div');
container.id = 'custom-search-ui';
container.style.position = 'fixed';
container.style.top = '10px';
container.style.left = '10px';
container.style.zIndex = '9999';
container.style.backgroundColor = '#1e1e1e';
container.style.color = '#f5f5f5';
container.style.border = '1px solid #444';
container.style.padding = '10px';
container.style.borderRadius = '10px';
container.style.boxShadow = '0 4px 12px rgba(0,0,0,0.6)';
container.style.fontSize = '14px';
container.style.display = 'flex';
container.style.flexDirection = 'column';
container.style.gap = '8px';
container.style.minWidth = '300px';
container.style.transition = 'height 0.3s ease';
let isCollapsed = false;
const header = document.createElement('div');
header.style.display = 'flex';
header.style.justifyContent = 'space-between';
header.style.alignItems = 'center';
header.style.cursor = 'move';
const title = document.createElement('div');
title.textContent = 'X 推文搜索';
title.style.fontWeight = 'bold';
title.style.color = '#ff9632';
const collapseBtn = document.createElement('button');
collapseBtn.textContent = '—';
collapseBtn.style.background = 'none';
collapseBtn.style.border = 'none';
collapseBtn.style.fontSize = '18px';
collapseBtn.style.cursor = 'pointer';
collapseBtn.style.color = '#aaa';
collapseBtn.addEventListener('click', () => {
isCollapsed = !isCollapsed;
contentArea.style.display = isCollapsed ? 'none' : 'flex';
collapseBtn.textContent = isCollapsed ? '+' : '—';
container.style.height = isCollapsed ? '30px' : 'auto';
});
header.appendChild(title);
header.appendChild(collapseBtn);
const contentArea = document.createElement('div');
contentArea.style.display = 'flex';
contentArea.style.flexDirection = 'column';
contentArea.style.gap = '6px';
const input = document.createElement('input');
input.type = 'text';
input.placeholder = '输入关键词';
input.style.padding = '6px';
input.style.borderRadius = '4px';
input.style.border = '1px solid #666';
input.style.backgroundColor = '#2a2a2a';
input.style.color = '#fff';
const scrollSpeedInput = document.createElement('input');
scrollSpeedInput.type = 'number';
scrollSpeedInput.placeholder = '滚动速度';
scrollSpeedInput.min = '100';
scrollSpeedInput.step = '100';
scrollSpeedInput.value = scrollStep;
scrollSpeedInput.style.width = '30%';
scrollSpeedInput.style.padding = '6px';
scrollSpeedInput.style.borderRadius = '4px';
scrollSpeedInput.style.border = '1px solid #666';
scrollSpeedInput.style.backgroundColor = '#2a2a2a';
scrollSpeedInput.style.color = '#ccc';
scrollSpeedInput.title = '滚动步长(像素)';
scrollSpeedInput.addEventListener('change', () => {
scrollStep = parseInt(scrollSpeedInput.value) || 1000;
});
const searchBtn = document.createElement('button');
searchBtn.textContent = '开始搜索';
searchBtn.style.padding = '6px';
searchBtn.style.backgroundColor = '#007acc';
searchBtn.style.border = 'none';
searchBtn.style.color = '#fff';
searchBtn.style.borderRadius = '4px';
searchBtn.style.cursor = 'pointer';
searchBtn.style.width = '48%';
const stopBtn = document.createElement('button');
stopBtn.textContent = '停止';
stopBtn.style.padding = '6px';
stopBtn.style.backgroundColor = '#c62828';
stopBtn.style.border = 'none';
stopBtn.style.color = '#fff';
stopBtn.style.borderRadius = '4px';
stopBtn.style.cursor = 'pointer';
stopBtn.style.width = '48%';
const statusText = document.createElement('div');
statusText.textContent = searchStatus;
statusText.style.fontSize = '12px';
statusText.style.color = '#aaa';
contentArea.appendChild(input);
contentArea.appendChild(scrollSpeedInput);
contentArea.appendChild(searchBtn);
contentArea.appendChild(stopBtn);
contentArea.appendChild(statusText);
container.appendChild(header);
container.appendChild(contentArea);
document.body.appendChild(container);
let isDragging = false;
let offsetX, offsetY;
header.addEventListener('mousedown', (e) => {
isDragging = true;
offsetX = e.clientX - container.getBoundingClientRect().left;
offsetY = e.clientY - container.getBoundingClientRect().top;
});
document.addEventListener('mousemove', (e) => {
if (isDragging) {
container.style.left = (e.clientX - offsetX) + 'px';
container.style.top = (e.clientY - offsetY) + 'px';
}
});
document.addEventListener('mouseup', () => isDragging = false);
searchBtn.addEventListener('click', () => {
const keyword = input.value.trim();
if (keyword) {
stopRequested = false;
currentSearchKeyword = keyword;
searchStatus = '开始搜索: ' + keyword;
statusText.textContent = searchStatus;
processedTweets.clear();
startScrolling(keyword);
}
});
stopBtn.addEventListener('click', () => {
stopRequested = true;
searchStatus = '已停止';
statusText.textContent = searchStatus;
});
}
async function startScrolling(keyword) {
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
while (!stopRequested) {
const tweets = document.querySelectorAll('[data-testid="tweet"]');
for (let tweet of tweets) {
const textBlock = tweet.querySelector('[data-testid="tweetText"]');
const tweetText = textBlock?.innerText || '';
const id = tweet.getAttribute('data-tweet-id') || tweet.innerText.slice(0, 50);
if (processedTweets.has(id)) continue;
processedTweets.add(id);
totalProcessed++;
if (tweetText.includes(keyword)) {
matchCount++;
tweet.scrollIntoView({ behavior: 'smooth', block: 'start' });
tweet.style.border = '2px solid #ff9632';
tweet.style.backgroundColor = '#333';
return;
}
}
window.scrollBy({ top: scrollStep, behavior: 'smooth' });
await delay(600);
}
}
function init() {
if (!document.getElementById('custom-search-ui')) {
createSearchUI();
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();