X 推文关键词逐条搜索滚动器(增强版)

一条一条匹配推文关键词,找到后将其定位在页面顶部,支持停止按钮,暗黑界面优化,美观浮窗,滚动速度可调

  1. // ==UserScript==
  2. // @name X 推文关键词逐条搜索滚动器(增强版)
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.3
  5. // @description 一条一条匹配推文关键词,找到后将其定位在页面顶部,支持停止按钮,暗黑界面优化,美观浮窗,滚动速度可调
  6. // @author _Sure.Lee
  7. // @match https://x.com/*
  8. // @match https://twitter.com/*
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. (function () {
  13. 'use strict';
  14.  
  15. let stopRequested = false;
  16. let processedTweets = new Set();
  17. let currentSearchKeyword = '';
  18. let scrollStep = 1000; // 默认滚动像素
  19. let searchStatus = '准备就绪';
  20. let matchCount = 0;
  21. let totalProcessed = 0;
  22.  
  23. function createSearchUI() {
  24. const container = document.createElement('div');
  25. container.id = 'custom-search-ui';
  26. container.style.position = 'fixed';
  27. container.style.top = '10px';
  28. container.style.left = '10px';
  29. container.style.zIndex = '9999';
  30. container.style.backgroundColor = '#1e1e1e';
  31. container.style.color = '#f5f5f5';
  32. container.style.border = '1px solid #444';
  33. container.style.padding = '10px';
  34. container.style.borderRadius = '10px';
  35. container.style.boxShadow = '0 4px 12px rgba(0,0,0,0.6)';
  36. container.style.fontSize = '14px';
  37. container.style.display = 'flex';
  38. container.style.flexDirection = 'column';
  39. container.style.gap = '8px';
  40. container.style.minWidth = '300px';
  41. container.style.transition = 'height 0.3s ease';
  42.  
  43. let isCollapsed = false;
  44.  
  45. const header = document.createElement('div');
  46. header.style.display = 'flex';
  47. header.style.justifyContent = 'space-between';
  48. header.style.alignItems = 'center';
  49. header.style.cursor = 'move';
  50.  
  51. const title = document.createElement('div');
  52. title.textContent = 'X 推文搜索';
  53. title.style.fontWeight = 'bold';
  54. title.style.color = '#ff9632';
  55.  
  56. const collapseBtn = document.createElement('button');
  57. collapseBtn.textContent = '—';
  58. collapseBtn.style.background = 'none';
  59. collapseBtn.style.border = 'none';
  60. collapseBtn.style.fontSize = '18px';
  61. collapseBtn.style.cursor = 'pointer';
  62. collapseBtn.style.color = '#aaa';
  63. collapseBtn.addEventListener('click', () => {
  64. isCollapsed = !isCollapsed;
  65. contentArea.style.display = isCollapsed ? 'none' : 'flex';
  66. collapseBtn.textContent = isCollapsed ? '+' : '—';
  67. container.style.height = isCollapsed ? '30px' : 'auto';
  68. });
  69.  
  70. header.appendChild(title);
  71. header.appendChild(collapseBtn);
  72.  
  73. const contentArea = document.createElement('div');
  74. contentArea.style.display = 'flex';
  75. contentArea.style.flexDirection = 'column';
  76. contentArea.style.gap = '6px';
  77.  
  78. const input = document.createElement('input');
  79. input.type = 'text';
  80. input.placeholder = '输入关键词';
  81. input.style.padding = '6px';
  82. input.style.borderRadius = '4px';
  83. input.style.border = '1px solid #666';
  84. input.style.backgroundColor = '#2a2a2a';
  85. input.style.color = '#fff';
  86.  
  87. const scrollSpeedInput = document.createElement('input');
  88. scrollSpeedInput.type = 'number';
  89. scrollSpeedInput.placeholder = '滚动速度';
  90. scrollSpeedInput.min = '100';
  91. scrollSpeedInput.step = '100';
  92. scrollSpeedInput.value = scrollStep;
  93. scrollSpeedInput.style.width = '30%';
  94. scrollSpeedInput.style.padding = '6px';
  95. scrollSpeedInput.style.borderRadius = '4px';
  96. scrollSpeedInput.style.border = '1px solid #666';
  97. scrollSpeedInput.style.backgroundColor = '#2a2a2a';
  98. scrollSpeedInput.style.color = '#ccc';
  99. scrollSpeedInput.title = '滚动步长(像素)';
  100. scrollSpeedInput.addEventListener('change', () => {
  101. scrollStep = parseInt(scrollSpeedInput.value) || 1000;
  102. });
  103.  
  104. const searchBtn = document.createElement('button');
  105. searchBtn.textContent = '开始搜索';
  106. searchBtn.style.padding = '6px';
  107. searchBtn.style.backgroundColor = '#007acc';
  108. searchBtn.style.border = 'none';
  109. searchBtn.style.color = '#fff';
  110. searchBtn.style.borderRadius = '4px';
  111. searchBtn.style.cursor = 'pointer';
  112. searchBtn.style.width = '48%';
  113.  
  114. const stopBtn = document.createElement('button');
  115. stopBtn.textContent = '停止';
  116. stopBtn.style.padding = '6px';
  117. stopBtn.style.backgroundColor = '#c62828';
  118. stopBtn.style.border = 'none';
  119. stopBtn.style.color = '#fff';
  120. stopBtn.style.borderRadius = '4px';
  121. stopBtn.style.cursor = 'pointer';
  122. stopBtn.style.width = '48%';
  123.  
  124. const statusText = document.createElement('div');
  125. statusText.textContent = searchStatus;
  126. statusText.style.fontSize = '12px';
  127. statusText.style.color = '#aaa';
  128.  
  129. contentArea.appendChild(input);
  130. contentArea.appendChild(scrollSpeedInput);
  131. contentArea.appendChild(searchBtn);
  132. contentArea.appendChild(stopBtn);
  133. contentArea.appendChild(statusText);
  134.  
  135. container.appendChild(header);
  136. container.appendChild(contentArea);
  137. document.body.appendChild(container);
  138.  
  139. let isDragging = false;
  140. let offsetX, offsetY;
  141.  
  142. header.addEventListener('mousedown', (e) => {
  143. isDragging = true;
  144. offsetX = e.clientX - container.getBoundingClientRect().left;
  145. offsetY = e.clientY - container.getBoundingClientRect().top;
  146. });
  147.  
  148. document.addEventListener('mousemove', (e) => {
  149. if (isDragging) {
  150. container.style.left = (e.clientX - offsetX) + 'px';
  151. container.style.top = (e.clientY - offsetY) + 'px';
  152. }
  153. });
  154. document.addEventListener('mouseup', () => isDragging = false);
  155.  
  156. searchBtn.addEventListener('click', () => {
  157. const keyword = input.value.trim();
  158. if (keyword) {
  159. stopRequested = false;
  160. currentSearchKeyword = keyword;
  161. searchStatus = '开始搜索: ' + keyword;
  162. statusText.textContent = searchStatus;
  163. processedTweets.clear();
  164. startScrolling(keyword);
  165. }
  166. });
  167.  
  168. stopBtn.addEventListener('click', () => {
  169. stopRequested = true;
  170. searchStatus = '已停止';
  171. statusText.textContent = searchStatus;
  172. });
  173. }
  174.  
  175. async function startScrolling(keyword) {
  176. const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
  177. while (!stopRequested) {
  178. const tweets = document.querySelectorAll('[data-testid="tweet"]');
  179. for (let tweet of tweets) {
  180. const textBlock = tweet.querySelector('[data-testid="tweetText"]');
  181. const tweetText = textBlock?.innerText || '';
  182. const id = tweet.getAttribute('data-tweet-id') || tweet.innerText.slice(0, 50);
  183. if (processedTweets.has(id)) continue;
  184. processedTweets.add(id);
  185. totalProcessed++;
  186.  
  187. if (tweetText.includes(keyword)) {
  188. matchCount++;
  189. tweet.scrollIntoView({ behavior: 'smooth', block: 'start' });
  190. tweet.style.border = '2px solid #ff9632';
  191. tweet.style.backgroundColor = '#333';
  192. return;
  193. }
  194. }
  195. window.scrollBy({ top: scrollStep, behavior: 'smooth' });
  196. await delay(600);
  197. }
  198. }
  199.  
  200. function init() {
  201. if (!document.getElementById('custom-search-ui')) {
  202. createSearchUI();
  203. }
  204. }
  205.  
  206. if (document.readyState === 'loading') {
  207. document.addEventListener('DOMContentLoaded', init);
  208. } else {
  209. init();
  210. }
  211.  
  212. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址