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.10
// @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"
  };

  // 历史记录管理
  const MAX_HISTORY = 20;
  
  // 主面板HTML - 增加历史面板
  const container = document.createElement('div');
  container.id = 'tw-search-container';
  container.innerHTML = `
    <div id="tw-search-assistant">
      <div class="panel-header">
        <span>搜索助手</span>
      </div>
      <div class="mode-indicator clickable">单选模式</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>
    </div>
    <div id="tw-history-panel">
      <div class="history-header">
        <span>搜索历史</span>
        <button class="clear-history" title="清空历史">🗑️</button>
      </div>
      <div class="history-list"></div>
    </div>
  `;

  // 样式 - 修改间距、宽度和位置
  const style = document.createElement('style');
  style.textContent = `
    #tw-search-container {
      position: fixed;
      top: 5px;
      right: 177.5px; /* 搜索框整体偏移距离,数值越小越靠右 */
      display: flex;
      gap: 4px; /* 紧贴间距从8px减少到4px */
      z-index: 10000;
      align-items: stretch;
    }
    
    #tw-search-assistant, #tw-history-panel {
      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 {
      flex: 0 0 280px; /* 固定主面板宽度 */
    }
    
    #tw-history-panel {
      flex: 0 0 160px; /* 历史栏宽度从200px减少到160px */
    }
    
    #tw-search-assistant.show, #tw-history-panel.show {
      opacity: 1;
      transform: translateY(0);
    }
    
    #tw-search-assistant.hidden, #tw-history-panel.hidden {
      opacity: 0 !important;
      pointer-events: none !important;
      z-index: -1 !important;
    }
    
    #tw-search-assistant:hover, #tw-history-panel:hover {
      box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
    }
    
    .panel-header, .history-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 12px 16px 8px;
      border-bottom: 1px solid #eff3f4;
      font-weight: 600;
    }
    
    .history-header {
      padding: 10px 12px 6px; /* 历史栏标题稍小一点 */
    }
    
    .mode-indicator.clickable {
      padding: 6px 16px;
      font-size: 12px;
      color: #536471;
      background: #f7f9fa;
      margin: 0 16px 8px;
      border-radius: 6px;
      text-align: center;
      cursor: pointer;
      transition: all 0.2s;
      user-select: none;
    }
    
    .mode-indicator.clickable:hover {
      background: #e1e8ed;
    }
    
    .mode-indicator.multi-mode {
      background: #e8f5fe;
      color: #1da1f2;
    }
    
    .mode-indicator.multi-mode:hover {
      background: #d0e9f9;
    }
    
    .clear-history {
      background: none;
      border: none;
      cursor: pointer;
      font-size: 14px;
      padding: 2px 4px;
      border-radius: 4px;
      transition: background 0.2s;
    }
    
    .clear-history:hover {
      background: #f7f9fa;
    }
    
    .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;
    }
    
    .history-list {
      padding: 4px 8px; /* 历史栏内边距稍小 */
      max-height: 400px;
      overflow-y: auto;
    }
    
    .history-item {
      padding: 6px 8px; /* 历史项内边距也相应缩小 */
      border-radius: 6px;
      cursor: pointer;
      transition: background 0.2s;
      font-size: 12px;
      color: #0f1419;
      margin-bottom: 2px; /* 历史项间距缩小 */
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    }
    
    .history-item:hover {
      background: #f7f9fa;
    }
    
    .history-item:active {
      background: #e1e8ed;
    }
    
    .empty-history {
      padding: 16px 8px; /* 空状态提示也调整 */
      text-align: center;
      color: #536471;
      font-size: 11px; /* 字体稍小一点 */
    }
    
    .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(container);

  // 状态管理
  let isMultiSelectMode = false;
  let selectedPresets = new Set();
  
  // 历史记录功能
  function getHistory() {
    try {
      const history = localStorage.getItem('tw-search-history');
      return history ? JSON.parse(history) : [];
    } catch (e) {
      return [];
    }
  }
  
  function saveHistory(keyword) {
    if (!keyword || keyword.trim() === '') return;
    
    keyword = keyword.trim();
    let history = getHistory();
    
    // 移除重复项
    history = history.filter(item => item !== keyword);
    
    // 添加到最前面
    history.unshift(keyword);
    
    // 限制数量
    if (history.length > MAX_HISTORY) {
      history = history.slice(0, MAX_HISTORY);
    }
    
    try {
      localStorage.setItem('tw-search-history', JSON.stringify(history));
    } catch (e) {
      // 存储失败时的静默处理
    }
    
    renderHistory();
  }
  
  function clearAllHistory() {
    try {
      localStorage.removeItem('tw-search-history');
      renderHistory();
    } catch (e) {
      // 清空失败时的静默处理
    }
  }
  
  function renderHistory() {
    const historyList = document.querySelector('.history-list');
    const history = getHistory();
    
    if (history.length === 0) {
      historyList.innerHTML = '<div class="empty-history">暂无搜索历史</div>';
      return;
    }
    
    historyList.innerHTML = history.map(item => 
      `<div class="history-item" data-keyword="${encodeURIComponent(item)}">${escapeHtml(item)}</div>`
    ).join('');
    
    // 绑定点击事件
    historyList.querySelectorAll('.history-item').forEach(item => {
      item.addEventListener('click', () => {
        const keyword = decodeURIComponent(item.getAttribute('data-keyword'));
        document.getElementById('tw-keyword').value = keyword;
        // 不触发搜索,只是填充
      });
    });
  }
  
  function escapeHtml(text) {
    const div = document.createElement('div');
    div.textContent = text;
    return div.innerHTML;
  }

  // 极速消失检测 - 监听特定元素出现
  function initMediaDetection() {
    const observer = new MutationObserver(() => {
      const modal = document.querySelector('[aria-modal="true"]') || 
                   document.querySelector('[data-testid="swipe-to-dismiss-container"]') ||
                   document.querySelector('[data-testid="media-modal"]');
      
      const assistant = document.getElementById('tw-search-assistant');
      const history = document.getElementById('tw-history-panel');
      
      if (modal) {
        assistant.classList.add('hidden');
        history.classList.add('hidden');
      } else {
        assistant.classList.remove('hidden');
        history.classList.remove('hidden');
      }
    });

    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]);
      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;
      }
      
      saveHistory(keyword);
      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;
    }
    
    saveHistory(keyword);
    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('.mode-indicator').onclick = toggleMode; // 直接点击文字切换模式
  document.querySelector('.btn-clear').onclick = clearSelection;
  document.querySelector('.btn-apply').onclick = applyMultiSelect;
  document.querySelector('.clear-history').onclick = clearAllHistory;

  // 初始化
  initButtons();
  autoFillKeyword();
  renderHistory();
  initMediaDetection();
  
  // 显示面板
  setTimeout(() => {
    document.getElementById('tw-search-assistant').classList.add('show');
    document.getElementById('tw-history-panel').classList.add('show');
  }, 100);

})();