// ==UserScript== // @name 智教联盟论坛搜索修复 // @namespace https://github.com/ // @version 1.7 // @description 修复forum.smart-teach.cn论坛搜索功能,使用必应搜索引擎搜索论坛内容 // @author 无不滑稽(以及DeepSeek,Grok和Claude组成的主力) // @match *://forum.smart-teach.cn/* // @icon https://forum.smart-teach.cn/favicon.ico // @grant GM_openInTab // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @grant GM_notification // @license 随你咯,反正AI写的(?) // @run-at document-end // ==/UserScript== (function() { 'use strict'; // 配置 const CONFIG = { searchEngine: 'bing', searchEngines: { bing: 'https://www.bing.com/search?q={query}', baidu: 'https://www.baidu.com/s?wd={query}', google: 'https://www.google.com/search?q={query}' }, searchSelectors: [ 'header input[type="search"]', '.App-header input[type="search"]', '.Search-input', 'input[aria-label="搜索"]', 'input[placeholder*="搜索论坛"]', 'input[placeholder*="Search forum"]' ], debug: false, historySize: 10, autoAddSiteRestriction: true, siteRestriction: 'site:forum.smart-teach.cn' }; let searchHistory = []; function init() { log('智教联盟论坛搜索修复脚本已加载'); loadSearchHistory(); // 清理之前版本误判的补丁 cleanupInvalidPatches(); observeDOMChanges(); setTimeout(findAndPatchSearchBox, 1500); setTimeout(findAndPatchSearchBox, 5000); addSettingsMenu(); } // 新增:清理被误判打上补丁的输入框 function cleanupInvalidPatches() { const patchedInputs = document.querySelectorAll('input[data-patched="true"]'); let cleanedCount = 0; patchedInputs.forEach(input => { // 重新用严格规则验证 const type = (input.type || '').toLowerCase(); const ariaLabel = (input.getAttribute('aria-label') || '').toLowerCase(); const placeholder = (input.placeholder || '').toLowerCase(); // 如果不是真正的搜索框(type!=search 或 aria-label!=搜索),清理它 if (type !== 'search' || ariaLabel !== '搜索') { log(`清理误判的补丁: placeholder="${input.placeholder}"`); // 恢复原始 placeholder if (input.dataset.originalPlaceholder) { input.placeholder = input.dataset.originalPlaceholder; } // 移除补丁标记 delete input.dataset.patched; delete input.dataset.originalPlaceholder; // 移除事件监听器 input.removeEventListener('keydown', handleSearchKeyDown); cleanedCount++; } }); if (cleanedCount > 0) { log(`已清理 ${cleanedCount} 个误判的补丁`); } } function findAndPatchSearchBox() { log('正在查找搜索框...'); let searchInput = null; for (const selector of CONFIG.searchSelectors) { try { const elements = document.querySelectorAll(selector); for (const element of elements) { if (isValidSearchBox(element)) { searchInput = element; log(`找到搜索框: ${selector}`); break; } } if (searchInput) break; } catch (e) { log(`选择器 ${selector} 错误: ${e.message}`); } } if (!searchInput) { searchInput = findSearchBoxByTraversal(); } if (searchInput) { patchSearchBox(searchInput); return true; } log('未找到搜索框,将在DOM变化时重试'); return false; } function isValidSearchBox(element) { if (!element || element.offsetParent === null) return false; const placeholder = (element.placeholder || '').toLowerCase(); const type = (element.type || '').toLowerCase(); const ariaLabel = (element.getAttribute('aria-label') || '').toLowerCase(); // 严格排除关键词 const excludeKeywords = [ '标题', 'title', 'subject', '帖子标题', 'topic title', '回复', 'reply', 'content', '正文', 'body', '编辑', 'edit', '用户名', 'username', 'email', '标签', 'tag', '主标签', '副标签', '请选择' ]; if (excludeKeywords.some(kw => placeholder.includes(kw) || ariaLabel.includes(kw))) { log(`排除 - 含禁词: ${placeholder || ariaLabel}`); return false; } // 检查上下文 let parent = element.parentElement; let contextText = ''; for (let i = 0; i < 5 && parent; i++) { contextText += (parent.textContent || '').toLowerCase() + ' '; parent = parent.parentElement; } if (/发布.*主题|回复.*主题|编辑.*帖子|create.*discussion|reply|post|添加标签|选择标签/i.test(contextText)) { log('排除 - 上下文含发帖/回复/标签相关文字'); return false; } // 可见性检查 const style = window.getComputedStyle(element); if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') { return false; } // **核心判断:必须同时满足 type=search 且 aria-label="搜索"** const isSearchBox = type === 'search' && ariaLabel === '搜索'; if (!isSearchBox) { log(`非搜索框 - type: ${type}, aria-label: ${ariaLabel}`); } return isSearchBox; } function findSearchBoxByTraversal() { log('通过DOM遍历查找搜索框...'); const header = document.querySelector('header, .App-header, #header-primary'); if (header) { const inputs = header.querySelectorAll('input[type="search"][aria-label="搜索"]'); for (const input of inputs) { if (isValidSearchBox(input)) { log('在header区域找到搜索框'); return input; } } } return null; } function patchSearchBox(searchInput) { if (searchInput.dataset.patched === 'true') { log('输入框已打补丁,跳过'); return; } log(`修补搜索框: ${searchInput.outerHTML.substring(0, 100)}...`); searchInput.dataset.patched = 'true'; searchInput.dataset.originalPlaceholder = searchInput.placeholder || '搜索'; searchInput.removeEventListener('keydown', handleSearchKeyDown); searchInput.addEventListener('keydown', handleSearchKeyDown); addContextMenu(searchInput); updatePlaceholder(searchInput); if (searchHistory.length > 0) { addSearchHistoryDropdown(searchInput); } log('搜索框修补完成'); addStyles(); } function updatePlaceholder(searchInput) { const originalPlaceholder = searchInput.dataset.originalPlaceholder || '搜索'; const engineName = CONFIG.searchEngine === 'bing' ? '必应' : CONFIG.searchEngine === 'baidu' ? '百度' : '谷歌'; searchInput.placeholder = `${originalPlaceholder}(回车使用${engineName}站内搜索)`; } function handleSearchKeyDown(event) { if (event.key === 'Enter' || event.keyCode === 13) { event.preventDefault(); event.stopPropagation(); event.stopImmediatePropagation(); const searchText = event.target.value.trim(); if (!searchText) { showNotification('请输入搜索内容', 'warning'); return; } performSearch(searchText); } } function performSearch(searchText) { log(`执行搜索: ${searchText}`); saveToHistory(searchText); let query = searchText.trim(); if (CONFIG.autoAddSiteRestriction && query) { if (!/site\s*[:=]/i.test(query)) { query += ` ${CONFIG.siteRestriction}`; } } const encodedQuery = encodeURIComponent(query); const engine = CONFIG.searchEngine.toLowerCase(); const engineTemplate = CONFIG.searchEngines[engine] || CONFIG.searchEngines.bing; const searchUrl = engineTemplate.replace('{query}', encodedQuery); log(`搜索URL: ${searchUrl}`); if (typeof GM_openInTab === 'function') { GM_openInTab(searchUrl, { active: true, insert: true }); } else { window.open(searchUrl, '_blank'); } const engineName = CONFIG.searchEngine === 'bing' ? '必应' : CONFIG.searchEngine === 'baidu' ? '百度' : '谷歌'; showNotification(`正在使用${engineName}站内搜索: ${searchText}`, 'info'); } function saveToHistory(searchText) { searchHistory = searchHistory.filter(item => item !== searchText); searchHistory.unshift(searchText); if (searchHistory.length > CONFIG.historySize) { searchHistory = searchHistory.slice(0, CONFIG.historySize); } try { GM_setValue('searchHistory', JSON.stringify(searchHistory)); } catch (e) { log('保存历史记录失败: ' + e.message); } } function loadSearchHistory() { try { const historyStr = GM_getValue('searchHistory', '[]'); searchHistory = JSON.parse(historyStr) || []; log(`加载搜索历史: ${searchHistory.length} 条记录`); } catch (e) { searchHistory = []; log('加载搜索历史失败: ' + e.message); } } function addSearchHistoryDropdown(searchInput) { const container = document.createElement('div'); container.className = 'search-fix-history-container'; container.style.cssText = ` position: absolute; background: #1a1a1a; border: 1px solid #333; border-radius: 4px; box-shadow: 0 2px 10px rgba(0,0,0,0.5); max-height: 200px; overflow-y: auto; display: none; z-index: 10000; min-width: 200px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; `; let parent = searchInput.parentNode; if (getComputedStyle(parent).position === 'static') { parent.style.position = 'relative'; } parent.appendChild(container); function updateHistoryList() { container.innerHTML = ''; if (searchHistory.length === 0) { const emptyItem = document.createElement('div'); emptyItem.textContent = '无搜索历史'; emptyItem.style.padding = '8px'; emptyItem.style.color = '#666'; emptyItem.style.fontSize = '12px'; emptyItem.style.textAlign = 'center'; container.appendChild(emptyItem); return; } const title = document.createElement('div'); title.textContent = '搜索历史'; title.style.padding = '6px 12px'; title.style.fontWeight = 'bold'; title.style.fontSize = '12px'; title.style.borderBottom = '1px solid #333'; title.style.backgroundColor = '#0d0d0d'; title.style.color = '#999'; container.appendChild(title); searchHistory.forEach((item, index) => { const historyItem = document.createElement('div'); historyItem.textContent = item.length > 30 ? item.substring(0, 30) + '...' : item; historyItem.title = item; historyItem.style.padding = '6px 12px'; historyItem.style.cursor = 'pointer'; historyItem.style.borderBottom = '1px solid #2a2a2a'; historyItem.style.fontSize = '13px'; historyItem.style.whiteSpace = 'nowrap'; historyItem.style.overflow = 'hidden'; historyItem.style.textOverflow = 'ellipsis'; historyItem.style.color = '#ccc'; historyItem.addEventListener('mouseenter', () => { historyItem.style.backgroundColor = '#2a2a2a'; }); historyItem.addEventListener('mouseleave', () => { historyItem.style.backgroundColor = ''; }); historyItem.addEventListener('click', (e) => { e.stopPropagation(); searchInput.value = item; container.style.display = 'none'; searchInput.focus(); }); container.appendChild(historyItem); }); if (searchHistory.length > 0) { const clearBtn = document.createElement('div'); clearBtn.textContent = '清空历史'; clearBtn.style.padding = '6px 12px'; clearBtn.style.cursor = 'pointer'; clearBtn.style.color = '#ff4d4f'; clearBtn.style.borderTop = '1px solid #333'; clearBtn.style.textAlign = 'center'; clearBtn.style.fontSize = '12px'; clearBtn.style.fontWeight = '500'; clearBtn.addEventListener('click', (e) => { e.stopPropagation(); if (confirm('确定要清空所有搜索历史吗?')) { searchHistory = []; GM_setValue('searchHistory', '[]'); container.style.display = 'none'; showNotification('搜索历史已清空', 'info'); } }); container.appendChild(clearBtn); } } searchInput.addEventListener('focus', (e) => { e.stopPropagation(); updateHistoryList(); const rect = searchInput.getBoundingClientRect(); const parentRect = parent.getBoundingClientRect(); container.style.top = (rect.bottom - parentRect.top) + 'px'; container.style.left = (rect.left - parentRect.left) + 'px'; container.style.width = rect.width + 'px'; container.style.display = 'block'; }); searchInput.addEventListener('blur', () => { setTimeout(() => { if (!container.matches(':hover')) { container.style.display = 'none'; } }, 200); }); container.addEventListener('mouseenter', () => { container.style.display = 'block'; }); container.addEventListener('mouseleave', () => { setTimeout(() => { if (document.activeElement !== searchInput) { container.style.display = 'none'; } }, 100); }); document.addEventListener('click', (e) => { if (!container.contains(e.target) && e.target !== searchInput) { container.style.display = 'none'; } }); } function addContextMenu(searchInput) { searchInput.addEventListener('contextmenu', (e) => { e.preventDefault(); const menu = document.createElement('div'); menu.className = 'search-fix-context-menu'; menu.style.cssText = ` position: fixed; background: #1a1a1a; border: 1px solid #333; border-radius: 4px; box-shadow: 0 2px 10px rgba(0,0,0,0.5); z-index: 10001; min-width: 180px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; `; menu.style.left = e.pageX + 'px'; menu.style.top = e.pageY + 'px'; const items = [ { text: '使用百度搜索', action: () => searchWithEngine('baidu') }, { text: '使用必应搜索', action: () => searchWithEngine('bing') }, { text: '使用谷歌搜索', action: () => searchWithEngine('google') }, { type: 'separator' }, { text: '清空搜索历史', action: clearSearchHistory } ]; items.forEach(item => { if (item.type === 'separator') { const separator = document.createElement('div'); separator.style.height = '1px'; separator.style.backgroundColor = '#333'; separator.style.margin = '4px 0'; menu.appendChild(separator); } else { const menuItem = document.createElement('div'); menuItem.textContent = item.text; menuItem.style.padding = '8px 12px'; menuItem.style.cursor = 'pointer'; menuItem.style.fontSize = '13px'; menuItem.style.color = '#ccc'; menuItem.addEventListener('mouseenter', () => { menuItem.style.backgroundColor = '#2a2a2a'; }); menuItem.addEventListener('mouseleave', () => { menuItem.style.backgroundColor = ''; }); menuItem.addEventListener('click', (e) => { e.stopPropagation(); item.action(); document.body.removeChild(menu); }); menu.appendChild(menuItem); } }); document.body.appendChild(menu); const closeMenu = (e) => { if (!menu.contains(e.target)) { document.body.removeChild(menu); document.removeEventListener('click', closeMenu); } }; setTimeout(() => document.addEventListener('click', closeMenu), 100); }); } function searchWithEngine(engine) { const searchInput = document.querySelector('input[data-patched="true"]'); if (searchInput) { const searchText = searchInput.value.trim(); if (searchText) { const oldEngine = CONFIG.searchEngine; CONFIG.searchEngine = engine; performSearch(searchText); CONFIG.searchEngine = oldEngine; updatePlaceholder(searchInput); } else { showNotification('请输入搜索内容', 'warning'); } } } function clearSearchHistory() { if (searchHistory.length > 0) { if (confirm('确定要清空所有搜索历史吗?')) { searchHistory = []; GM_setValue('searchHistory', '[]'); showNotification('搜索历史已清空', 'info'); } } else { showNotification('搜索历史已经是空的', 'info'); } } function addSettingsMenu() { if (typeof GM_registerMenuCommand === 'function') { GM_registerMenuCommand('切换搜索引擎', () => { const current = CONFIG.searchEngine === 'bing' ? '必应' : CONFIG.searchEngine === 'baidu' ? '百度' : '谷歌'; const newEngine = prompt( `当前: ${current}\n\n请输入新搜索引擎:\n1. bing\n2. baidu\n3. google`, CONFIG.searchEngine ); if (newEngine && ['bing','baidu','google'].includes(newEngine.toLowerCase())) { CONFIG.searchEngine = newEngine.toLowerCase(); document.querySelectorAll('input[data-patched="true"]').forEach(updatePlaceholder); const name = {bing:'必应',baidu:'百度',google:'谷歌'}[CONFIG.searchEngine]; showNotification(`已切换为${name}`, 'success'); } }); GM_registerMenuCommand('查看搜索历史', () => { if (searchHistory.length === 0) { alert('暂无搜索历史'); } else { alert(`搜索历史 (${searchHistory.length}条):\n\n` + searchHistory.map((item,i) => `${i+1}. ${item}`).join('\n')); } }); GM_registerMenuCommand('清空搜索历史', clearSearchHistory); GM_registerMenuCommand('开启/关闭调试模式', () => { CONFIG.debug = !CONFIG.debug; showNotification(`调试模式已${CONFIG.debug?'开启':'关闭'}`, 'info'); }); } } function showNotification(message, type = 'info') { if (typeof GM_notification === 'function') { GM_notification({ text: message, title: '论坛搜索修复', timeout: 3000 }); return; } const notification = document.createElement('div'); notification.textContent = message; notification.style.cssText = ` position: fixed; top: 20px; right: 20px; padding: 12px 20px; background: ${type === 'info' ? '#1890ff' : type === 'success' ? '#52c41a' : type === 'warning' ? '#faad14' : '#ff4d4f'}; color: white; border-radius: 6px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); z-index: 9999; font-size: 14px; max-width: 300px; font-family: system-ui, sans-serif; pointer-events: none; `; document.body.appendChild(notification); setTimeout(() => notification.remove(), 3000); } function observeDOMChanges() { const observer = new MutationObserver((mutations) => { for (const mutation of mutations) { if (mutation.type === 'childList') { mutation.addedNodes.forEach(node => { if (node.nodeType === 1) { if (node.tagName === 'INPUT') { if (isValidSearchBox(node)) { setTimeout(() => patchSearchBox(node), 100); } } else if (node.querySelectorAll) { node.querySelectorAll('input').forEach(input => { if (isValidSearchBox(input)) { setTimeout(() => patchSearchBox(input), 100); } }); } } }); } } }); observer.observe(document.body, { childList: true, subtree: true }); log('DOM变化监听已启用'); } function addStyles() { const styleId = 'search-fix-styles'; if (document.getElementById(styleId)) return; const style = document.createElement('style'); style.id = styleId; style.textContent = ` input[data-patched="true"] { background-color: #1a1a1a !important; color: #ccc !important; border-color: #40a9ff !important; box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2) !important; transition: all 0.3s !important; } input[data-patched="true"]:focus { outline: none !important; border-color: #1890ff !important; box-shadow: 0 0 0 3px rgba(24, 144, 255, 0.2) !important; } input[data-patched="true"]::placeholder { color: #888 !important; } .search-fix-history-container { scrollbar-width: thin; scrollbar-color: #444 #1a1a1a; } .search-fix-history-container::-webkit-scrollbar { width: 6px; } .search-fix-history-container::-webkit-scrollbar-track { background: #1a1a1a; } .search-fix-history-container::-webkit-scrollbar-thumb { background: #444; border-radius: 3px; } .search-fix-history-container::-webkit-scrollbar-thumb:hover { background: #555; } `; document.head.appendChild(style); log('样式已添加'); } function log(message) { if (CONFIG.debug) { console.log(`[论坛搜索修复] ${message}`); } } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();