Greasy Fork

Greasy Fork is available in English.

增强版推特搜索助手-Twitter Search Assistant Enhanced

推特搜索助手 - 极速消失版

当前为 2025-11-05 提交的版本,查看 最新版本

// ==UserScript==
// @name         增强版推特搜索助手-Twitter Search Assistant Enhanced
// @namespace    example.twitter.enhanced
// @version      2.1
// @description  推特搜索助手 - 极速消失版
// @match        https://twitter.com/*
// @match        https://x.com/*
// @grant        none
// @license MIT
// ==/UserScript==

(function () {
  'use strict';

  // 定义搜索预设
  const presets = {
    "📷 图片": "filter:images -filter:retweets -filter:replies",
    "🎬 视频": "filter:videos -filter:retweets -filter:replies",
    "🔥 高热度": "min_faves:200 -filter:retweets",
    "🈶 日语": "lang:ja -filter:retweets -filter:replies",
    "🌎 英语": "lang:en -filter:retweets -filter:replies",
    "💬 感想": "感想 OR 評価 lang:ja -filter:retweets -filter:replies",
    "⏰ 近期": "since:2024-07-25 -filter:retweets",
    "🎵 BGM讨论": "BGM OR 音乐 OR OST lang:ja -filter:retweets"
  };

  // 主面板HTML
  const panel = document.createElement('div');
  panel.id = 'tw-search-assistant';
  panel.innerHTML = `
    <div class="panel-header">
      <span>搜索助手</span>
      <button class="toggle-mode" title="切换单选/多选模式">🔄</button>
    </div>
    <div class="mode-indicator">单选模式</div>
    <div class="keyword-container">
      <input id="tw-keyword" type="text" placeholder="输入关键词(自动获取当前搜索词)">
    </div>
    <div class="preset-grid"></div>
    <div class="action-buttons">
      <button class="btn-clear">清空</button>
      <button class="btn-apply" style="display: none;">应用搜索</button>
    </div>
  `;

  // 样式
  const style = document.createElement('style');
  style.textContent = `
    #tw-search-assistant {
      position: fixed;
      top: 5px;
      right: 230px;
      z-index: 10000;
      width: 280px;
      background: #ffffff;
      border: 1px solid #e1e8ed;
      border-radius: 12px;
      box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif;
      font-size: 13px;
      color: #0f1419;
      transition: all 0.3s ease;
      opacity: 0;
      transform: translateY(-10px);
      box-sizing: border-box;
    }
    
    #tw-search-assistant.show {
      opacity: 1;
      transform: translateY(0);
    }
    
    #tw-search-assistant.hidden {
      opacity: 0 !important;
      pointer-events: none !important;
      z-index: -1 !important;
    }
    
    #tw-search-assistant:hover {
      box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
    }
    
    .panel-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 12px 16px 8px;
      border-bottom: 1px solid #eff3f4;
      font-weight: 600;
    }
    
    .toggle-mode {
      background: none;
      border: none;
      cursor: pointer;
      font-size: 14px;
      padding: 2px 4px;
      border-radius: 4px;
      transition: background 0.2s;
    }
    
    .toggle-mode:hover {
      background: #f7f9fa;
    }
    
    .mode-indicator {
      padding: 4px 16px;
      font-size: 11px;
      color: #536471;
      background: #f7f9fa;
      margin: 0 16px 8px;
      border-radius: 6px;
      text-align: center;
    }
    
    .mode-indicator.multi-mode {
      background: #e8f5fe;
      color: #1da1f2;
    }
    
    .keyword-container {
      padding: 0 16px 12px;
    }
    
    #tw-keyword {
      width: 100%;
      padding: 8px 12px;
      border: 1px solid #eff3f4;
      border-radius: 8px;
      font-size: 14px;
      outline: none;
      transition: border-color 0.2s;
      box-sizing: border-box;
    }
    
    #tw-keyword:focus {
      border-color: #1da1f2;
      box-shadow: 0 0 0 3px rgba(29, 161, 242, 0.1);
    }
    
    .preset-grid {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 6px;
      padding: 0 16px 12px;
    }
    
    .preset-btn {
      padding: 8px 12px;
      background: #f7f9fa;
      border: 1px solid #eff3f4;
      border-radius: 8px;
      color: #0f1419;
      cursor: pointer;
      font-size: 12px;
      transition: all 0.2s;
      display: flex;
      align-items: center;
      gap: 4px;
    }
    
    .preset-btn:hover {
      background: #e8f5fe;
      border-color: #cfe5f7;
    }
    
    .preset-btn.selected {
      background: #e8f5fe;
      color: #1da1f2;
      border-color: #1da1f2;
    }
    
    .preset-btn.selected::after {
      content: '✓';
      margin-left: auto;
      font-size: 11px;
      font-weight: bold;
    }
    
    .action-buttons {
      display: flex;
      gap: 8px;
      padding: 0 16px 16px;
    }
    
    .btn-clear, .btn-apply {
      flex: 1;
      padding: 8px;
      border: none;
      border-radius: 8px;
      font-size: 13px;
      cursor: pointer;
      transition: all 0.2s;
      font-weight: 500;
    }
    
    .btn-clear {
      background: #f7f9fa;
      color: #536471;
      border: 1px solid #eff3f4;
    }
    
    .btn-clear:hover {
      background: #e1e8ed;
    }
    
    .btn-apply {
      background: #1da1f2;
      color: white;
    }
    
    .btn-apply:hover {
      background: #1a91da;
    }
    
    .btn-apply.active {
      background: #17bf63;
      box-shadow: 0 2px 8px rgba(23, 191, 99, 0.3);
    }
  `;
  document.head.appendChild(style);
  document.body.appendChild(panel);

  // 状态管理
  let isMultiSelectMode = false;
  let selectedPresets = new Set();

  // 极速消失检测 - 监听特定元素出现
  function initMediaDetection() {
    // 监听图片查看器
    const observer = new MutationObserver(() => {
      // 检测Twitter的模态框元素
      const modal = document.querySelector('[aria-modal="true"]') || 
                   document.querySelector('[data-testid="swipe-to-dismiss-container"]') ||
                   document.querySelector('[data-testid="media-modal"]');
      
      if (modal) {
        panel.classList.add('hidden');
      } else {
        panel.classList.remove('hidden');
      }
    });

    // 监听body变化(最轻量级)
    observer.observe(document.body, {
      childList: true,
      subtree: true,
      attributes: true,
      attributeFilter: ['aria-modal', 'style']
    });
  }

  // 简化:只提取关键词,不要任何过滤条件
  function extractKeywordOnly(url) {
    if (!url.includes('/search?q=')) return '';
    
    try {
      const match = url.match(/\/search\?q=([^&]+)/);
      if (!match) return '';
      
      const query = decodeURIComponent(match[1]);
      // 移除所有以filter、lang、min_faves、since等开头的条件,只保留关键词
      const keyword = query.split(/\s+(?:filter:|lang:|min_faves:|since:|from:|to:|until:|OR|AND|NOT)/)[0].trim();
      return keyword;
    } catch (e) {
      return '';
    }
  }

  // 处理预设点击
  function handlePresetClick(btn, filter) {
    if (isMultiSelectMode) {
      // 多选模式
      if (selectedPresets.has(filter)) {
        selectedPresets.delete(filter);
        btn.classList.remove('selected');
      } else {
        selectedPresets.add(filter);
        btn.classList.add('selected');
      }
      
      updateApplyButton();
    } else {
      // 单选模式:直接搜索
      const keyword = document.getElementById('tw-keyword').value.trim() || extractKeywordOnly(window.location.href);
      if (!keyword) {
        alert('请输入关键词');
        return;
      }
      
      // 单选模式:只使用当前选中的过滤条件,清空所有其他条件
      const searchUrl = `https://twitter.com/search?q=${encodeURIComponent(keyword + ' ' + filter)}&src=typed_query&f=top`;
      window.location.href = searchUrl;
    }
  }

  // 更新应用按钮
  function updateApplyButton() {
    const applyBtn = document.querySelector('.btn-apply');
    if (isMultiSelectMode && selectedPresets.size > 0) {
      applyBtn.classList.add('active');
      applyBtn.textContent = `应用搜索(${selectedPresets.size})`;
    } else if (isMultiSelectMode) {
      applyBtn.classList.remove('active');
      applyBtn.textContent = '应用搜索';
    }
  }

  // 多选模式搜索 - 彻底清空旧条件
  function applyMultiSelect() {
    if (!isMultiSelectMode || selectedPresets.size === 0) {
      alert('多选模式下请至少选择一个筛选条件');
      return;
    }
    
    const keyword = document.getElementById('tw-keyword').value.trim() || extractKeywordOnly(window.location.href);
    if (!keyword) {
      alert('请输入关键词');
      return;
    }
    
    // 多选模式:彻底清空所有旧条件,只使用选中的条件
    const selectedFilters = Array.from(selectedPresets).join(' ');
    const finalQuery = keyword + ' ' + selectedFilters;
    
    const searchUrl = `https://twitter.com/search?q=${encodeURIComponent(finalQuery.trim())}&src=typed_query&f=top`;
    window.location.href = searchUrl;
  }

  // 清空选择
  function clearSelection() {
    selectedPresets.clear();
    document.querySelectorAll('.preset-btn.selected').forEach(btn => {
      btn.classList.remove('selected');
    });
    updateApplyButton();
  }

  // 切换模式
  function toggleMode() {
    isMultiSelectMode = !isMultiSelectMode;
    
    const indicator = document.querySelector('.mode-indicator');
    const applyBtn = document.querySelector('.btn-apply');
    
    if (isMultiSelectMode) {
      indicator.textContent = '多选模式';
      indicator.classList.add('multi-mode');
      applyBtn.style.display = 'block';
      clearSelection();
    } else {
      indicator.textContent = '单选模式';
      indicator.classList.remove('multi-mode');
      applyBtn.style.display = 'none';
      clearSelection();
    }
  }

  // 自动填充关键词
  function autoFillKeyword() {
    const keyword = extractKeywordOnly(window.location.href);
    if (keyword && !document.getElementById('tw-keyword').value) {
      document.getElementById('tw-keyword').value = keyword;
    }
  }

  // 初始化按钮
  function initButtons() {
    const grid = document.querySelector('.preset-grid');
    Object.keys(presets).forEach(name => {
      const btn = document.createElement('button');
      btn.className = 'preset-btn';
      btn.textContent = name;
      btn.onclick = () => handlePresetClick(btn, presets[name]);
      grid.appendChild(btn);
    });
  }

  // 监听URL变化
  observeUrlChanges();

  function observeUrlChanges() {
    let currentUrl = window.location.href;
    
    setInterval(() => {
      if (window.location.href !== currentUrl) {
        currentUrl = window.location.href;
        autoFillKeyword();
        clearSelection();
      }
    }, 500);
  }

  // 绑定事件
  document.querySelector('.toggle-mode').onclick = toggleMode;
  document.querySelector('.btn-clear').onclick = clearSelection;
  document.querySelector('.btn-apply').onclick = applyMultiSelect;

  // 初始化
  initButtons();
  autoFillKeyword();
  initMediaDetection(); // 启动极速消失检测
  
  // 显示面板
  setTimeout(() => {
    panel.classList.add('show');
  }, 100);

})();