聚合搜索

AI生成,整合百度、谷歌、必应搜索,支持无刷新自动翻页,关键词高亮,快捷键切换,根据系统主题自动切换暗色模式

// ==UserScript==
// @name         聚合搜索
// @description  AI生成,整合百度、谷歌、必应搜索,支持无刷新自动翻页,关键词高亮,快捷键切换,根据系统主题自动切换暗色模式
// @version      1.0.2
// @author       yinbao77
// @website      https://github.com/yinbao77
// @match        *://www.baidu.com/s*
// @match        *://www.bing.com/search*
// @match        *://cn.bing.com/search*
// @match        *://www.google.com.hk/search*
// @match        *://www.google.com/search*
// @namespace https://greasyfork.org/users/1489016
// ==/UserScript==

(function() {
    'use strict';
    
    // 配置
    const engines = [
        { name: '百度', url: 'https://www.baidu.com/s?wd=', param: 'wd', test: /baidu\.com/, key: '1' },
        { name: '必应', url: 'https://www.bing.com/search?q=', param: 'q', test: /bing\.com/, key: '2' },
        { name: 'Google', url: 'https://www.google.com/search?q=', param: 'q', test: /google\.com/, key: '3' }
    ];
    
    let isAutoPageOn = true;
    let currentPage = 1;
    let isLoading = false;
    let isDarkMode = false;
    
    // 主题
    function detectDarkMode() {
        return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
    }
    
    function getTheme() {
        return isDarkMode ? {
            bg: '#2d2d2d', border: '#555', text: '#e0e0e0', textSec: '#b0b0b0',
            active: '#4CAF50', hover: '#3a3a3a', highlight: '#ffd700'
        } : {
            bg: '#EEEEEE', border: '#ccc', text: '#333', textSec: '#666',
            active: '#4CAF50', hover: '#f5f5f5', highlight: '#ffff00'
        };
    }
    
    // 工具函数
    function getCurrentEngine() {
        return engines.find(e => e.test.test(location.href));
    }
    
    function getKeywords() {
        const engine = getCurrentEngine();
        if (!engine) return '';
        const params = new URLSearchParams(location.search);
        return params.get(engine.param) || '';
    }
    
    function showTip(text) {
        const theme = getTheme();
        let tip = document.getElementById('search-tip');
        if (!tip) {
            tip = document.createElement('div');
            tip.id = 'search-tip';
            document.body.appendChild(tip);
        }
        
        tip.textContent = text;
        tip.style.cssText = `
            position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
            background: rgba(0,0,0,0.8); color: white; padding: 15px 25px;
            border-radius: 10px; font-size: 16px; z-index: 10001; display: block;
        `;
        
        setTimeout(() => tip.style.display = 'none', 1500);
    }
    
    function jumpTo(engineUrl) {
        const keywords = getKeywords();
        if (keywords) {
            showTip('正在跳转...');
            setTimeout(() => {
                location.href = engineUrl + encodeURIComponent(keywords);
            }, 300);
        }
    }
    
    // 创建侧边栏
    function createSidebar() {
        const current = getCurrentEngine();
        if (!current) return;
        
        const theme = getTheme();
        const sidebar = document.createElement('div');
        sidebar.id = 'search-sidebar';
        
        sidebar.style.cssText = `
            position: fixed; top: 50%; left: 20px; transform: translateY(-50%);
            width: 120px; background: ${theme.bg}; border: 1px solid ${theme.border};
            border-radius: 8px; font-size: 12px; z-index: 99999;
            box-shadow: 0 4px 8px rgba(0,0,0,0.1); font-family: Arial, sans-serif;
        `;
        
        // 作者
        const author = document.createElement('div');
        author.textContent = 'by 丶恩嗯';
        author.style.cssText = `text-align: center; margin: 8px 0; font-size: 12px; color: ${theme.textSec};`;
        sidebar.appendChild(author);
        
        // 标题
        const title = document.createElement('div');
        title.textContent = '🔍聚合搜索';
        title.style.cssText = `text-align: center; margin: 10px 0; font-size: 14px; font-weight: bold; color: ${theme.text};`;
        sidebar.appendChild(title);
        
        // 引擎按钮
        engines.forEach(engine => {
            const btn = document.createElement('div');
            btn.textContent = engine.name;
            btn.title = `快捷键: Alt+${engine.key}`;
            
            if (current.name === engine.name) {
                btn.style.cssText = `
                    padding: 8px 0; text-align: center; cursor: pointer;
                    border-top: 1px solid ${theme.border}; background: ${theme.active};
                    color: white; font-weight: bold;
                `;
            } else {
                btn.style.cssText = `
                    padding: 8px 0; text-align: center; cursor: pointer;
                    border-top: 1px solid ${theme.border}; color: ${theme.text};
                `;
                btn.onmouseover = () => btn.style.background = theme.hover;
                btn.onmouseout = () => btn.style.background = '';
                btn.onclick = () => jumpTo(engine.url);
            }
            sidebar.appendChild(btn);
        });
        
        // 自动翻页开关
        const toggle = document.createElement('div');
        toggle.innerHTML = `🔄 自动翻页: ${isAutoPageOn ? 'ON' : 'OFF'}`;
        toggle.style.cssText = `
            border-top: 1px solid ${theme.border}; padding: 8px 0; text-align: center;
            font-size: 10px; cursor: pointer; color: ${theme.text};
            background: ${isAutoPageOn ? (isDarkMode ? '#2d4a2d' : '#e8f5e8') : (isDarkMode ? '#3a3a3a' : '#f5f5f5')};
            border-bottom-left-radius: 8px; border-bottom-right-radius: 8px;
        `;
        toggle.onclick = () => {
            isAutoPageOn = !isAutoPageOn;
            toggle.innerHTML = `🔄 自动翻页: ${isAutoPageOn ? 'ON' : 'OFF'}`;
            toggle.style.background = isAutoPageOn ? (isDarkMode ? '#2d4a2d' : '#e8f5e8') : (isDarkMode ? '#3a3a3a' : '#f5f5f5');
            localStorage.setItem('autoPageOn', isAutoPageOn);
        };
        sidebar.appendChild(toggle);
        
        // 拖拽功能
        let isDragging = false;
        let startX, startY, initialX, initialY;
        
        const dragElements = [sidebar, author, title];
        dragElements.forEach(el => {
            el.onmousedown = function(e) {
                isDragging = true;
                startX = e.clientX;
                startY = e.clientY;
                const rect = sidebar.getBoundingClientRect();
                initialX = rect.left;
                initialY = rect.top;
                sidebar.style.cursor = 'move';
                e.preventDefault();
            };
        });
        
        document.onmousemove = function(e) {
            if (isDragging) {
                const deltaX = e.clientX - startX;
                const deltaY = e.clientY - startY;
                sidebar.style.left = (initialX + deltaX) + 'px';
                sidebar.style.top = (initialY + deltaY) + 'px';
                sidebar.style.transform = 'none';
            }
        };
        
        document.onmouseup = function() {
            if (isDragging) {
                isDragging = false;
                sidebar.style.cursor = '';
            }
        };
        
        document.body.appendChild(sidebar);
    }
    
    // 回到顶部按钮
    function createBackToTop() {
        const btn = document.createElement('div');
        btn.innerHTML = '⬆';
        btn.id = 'back-to-top';
        btn.style.cssText = `
            position: fixed; bottom: 20px; right: 20px; width: 50px; height: 50px;
            background: #007bff; color: white; border-radius: 50%; text-align: center;
            line-height: 50px; font-size: 20px; cursor: pointer; display: none; z-index: 9999;
            transition: all 0.3s ease;
        `;
        
        btn.onmouseover = () => {
            btn.style.backgroundColor = '#0056b3';
            btn.style.transform = 'scale(1.1)';
        };
        btn.onmouseout = () => {
            btn.style.backgroundColor = '#007bff';
            btn.style.transform = 'scale(1)';
        };
        btn.onclick = () => window.scrollTo({top: 0, behavior: 'smooth'});
        
        document.body.appendChild(btn);
        
        // 滚动监听
        window.addEventListener('scroll', () => {
            const scrollTop = window.pageYOffset;
            btn.style.display = scrollTop > 300 ? 'block' : 'none';
            
            // 自动翻页
            if (isAutoPageOn && !isLoading) {
                const windowHeight = window.innerHeight;
                const documentHeight = document.documentElement.scrollHeight;
                if (scrollTop + windowHeight >= documentHeight - 200) {
                    loadNextPage();
                }
            }
        });
    }
    
    // 自动翻页
    async function loadNextPage() {
        const engine = getCurrentEngine();
        if (!engine || currentPage >= 10) return;
        
        isLoading = true;
        currentPage++;
        showTip(`正在加载第 ${currentPage} 页...`);
        
        try {
            const url = new URL(location.href);
            if (engine.name === '百度') {
                url.searchParams.set('pn', (parseInt(url.searchParams.get('pn') || '0') + 10).toString());
            } else if (engine.name === '必应') {
                url.searchParams.set('start', (parseInt(url.searchParams.get('start') || '0') + 10).toString());
            } else if (engine.name === 'Google') {
                url.searchParams.set('first', (parseInt(url.searchParams.get('first') || '1') + 10).toString());
            }
            
            const response = await fetch(url);
            const html = await response.text();
            const parser = new DOMParser();
            const doc = parser.parseFromString(html, 'text/html');
            
            let selector = '#content_left';
            if (engine.name === '必应') selector = '#b_results';
            if (engine.name === 'Google') selector = '#search';
            
            const newResults = doc.querySelector(selector);
            const currentResults = document.querySelector(selector);
            
            if (newResults && currentResults) {
                const theme = getTheme();
                const pageIndicator = document.createElement('div');
                pageIndicator.innerHTML = `━━━ 第 ${currentPage} 页 ━━━`;
                pageIndicator.style.cssText = `
                    margin: 20px 0; padding: 10px; text-align: center; border-radius: 5px;
                    background: ${theme.bg === '#EEEEEE' ? '#f0f0f0' : '#3a3a3a'}; color: ${theme.text};
                `;
                currentResults.appendChild(pageIndicator);
                
                Array.from(newResults.children).forEach(item => {
                    if (!item.classList.contains('page')) {
                        currentResults.appendChild(item);
                    }
                });
                
                setTimeout(highlightKeywords, 300);
            }
        } catch (e) {
            showTip('翻页失败');
        }
        
        isLoading = false;
    }
    
    // 关键词高亮
    function highlightKeywords() {
        const keywords = getKeywords();
        if (!keywords) return;
        
        const keywordList = keywords.split(/\s+/).filter(word => word.length > 1);
        if (!keywordList.length) return;
        
        const engine = getCurrentEngine();
        const selectors = ['#content_left', '#search', '#b_results'];
        let container = null;
        
        for (const sel of selectors) {
            container = document.querySelector(sel);
            if (container) break;
        }
        if (!container) return;
        
        const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT);
        const textNodes = [];
        let node;
        while (node = walker.nextNode()) {
            const parent = node.parentNode;
            if (parent.tagName !== 'SCRIPT' && parent.tagName !== 'STYLE' &&
                !parent.closest('#search-sidebar, #search-tip, #back-to-top')) {
                textNodes.push(node);
            }
        }
        
        const theme = getTheme();
        textNodes.forEach(textNode => {
            let text = textNode.textContent;
            let hasMatch = false;
            
            keywordList.forEach(keyword => {
                const regex = new RegExp(`(${keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
                if (regex.test(text)) {
                    text = text.replace(regex, `<mark style="background: ${theme.highlight}; font-weight: bold; padding: 1px 2px; border-radius: 2px;">$1</mark>`);
                    hasMatch = true;
                }
            });
            
            if (hasMatch) {
                const wrapper = document.createElement('span');
                wrapper.innerHTML = text;
                textNode.parentNode.replaceChild(wrapper, textNode);
            }
        });
    }
    
    // 快捷键
    function initShortcuts() {
        document.addEventListener('keydown', (e) => {
            if (e.altKey && !['INPUT', 'TEXTAREA'].includes(e.target.tagName)) {
                const engine = engines.find(eng => eng.key === e.key);
                if (engine) {
                    e.preventDefault();
                    showTip(`🚀 正在跳转到 ${engine.name}...`);
                    setTimeout(() => jumpTo(engine.url), 300);
                }
            }
        });
    }
    
    // 主题切换监听
    function initTheme() {
        isDarkMode = detectDarkMode();
        if (window.matchMedia) {
            window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
                isDarkMode = e.matches;
                // 简单重载以应用新主题
                location.reload();
            });
        }
    }
    
    // 初始化
    function init() {
        // 检查是否在搜索页面
        if (!engines.some(e => e.test.test(location.href))) {
            return;
        }
        
        // 初始化主题
        initTheme();
        
        // 读取设置
        try {
            const saved = localStorage.getItem('autoPageOn');
            if (saved !== null) isAutoPageOn = saved === 'true';
        } catch (e) {}
        
        // 创建界面
        createSidebar();
        createBackToTop();
        initShortcuts();
        
        // 延迟高亮
        setTimeout(highlightKeywords, 1000);
    }
    
    // 启动
    setTimeout(init, 1500);
    
})();