聚合搜索 - 增强版(支持暗色模式)

整合百度、必应、谷歌搜索,支持无刷新自动翻页,根据系统主题自动切换暗色模式

// ==UserScript==
// @name         聚合搜索 - 增强版(支持暗色模式)
// @description  整合百度、必应、谷歌搜索,支持无刷新自动翻页,根据系统主题自动切换暗色模式
// @version      0.5.0
// @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://gf.qytechs.cn/users/1489016
// ==/UserScript==

// 主要搜索引擎配置
const mainSearchEngines = [
    {
        name: '百度',
        searchUrl: 'https://www.baidu.com/s?wd=',
        keyName: 'wd',
        testUrl: /https:\/\/www\.baidu\.com\/s.*/,
        nextPageSelector: '#page .n',
        resultsSelector: '#content_left',
        pageParamName: 'pn'
    },
    {
        name: '必应',
        searchUrl: 'https://www.bing.com/search?q=',
        keyName: 'q',
        testUrl: /https:\/\/(www|cn)\.bing\.com\/search.*/,
        nextPageSelector: '.sb_pagN',
        resultsSelector: '#b_results',
        pageParamName: 'first'
    },
    {
        name: 'Google',
        searchUrl: 'https://www.google.com/search?q=',
        keyName: 'q',
        testUrl: /https:\/\/www\.google\.com(\.hk)?\/search.*/,
        nextPageSelector: '#pnnext',
        resultsSelector: '#search',
        pageParamName: 'start'
    }
];

// 专业搜索配置(已移除)

// 暗色模式相关变量
let isDarkMode = false;

// 主题配置
const themes = {
    light: {
        background: '#EEEEEE',
        border: '#ccc',
        shadow: 'rgba(0, 0, 0, 0.1)',
        textPrimary: '#333',
        textSecondary: '#666',
        textMuted: '#999',
        activeBackground: '#4CAF50',
        hoverBackground: '#f5f5f5',
        separatorBorder: '#ddd',
        specialBorder: '#eee',
        specialText: '#666',
        specialHover: '#f0f0f0',
        specialHoverText: '#333',
        toggleOnBackground: '#e8f5e8',
        toggleOffBackground: '#f5f5f5',
        pageIndicatorBackground: 'linear-gradient(90deg, #f0f0f0, #e0e0e0, #f0f0f0)',
        loadingBackground: 'rgba(0, 0, 0, 0.8)',
        loadingText: 'white'
    },
    dark: {
        background: '#2d2d2d',
        border: '#555',
        shadow: 'rgba(0, 0, 0, 0.3)',
        textPrimary: '#e0e0e0',
        textSecondary: '#b0b0b0',
        textMuted: '#888',
        activeBackground: '#4CAF50',
        hoverBackground: '#3a3a3a',
        separatorBorder: '#444',
        specialBorder: '#3a3a3a',
        specialText: '#a0a0a0',
        specialHover: '#3a3a3a',
        specialHoverText: '#e0e0e0',
        toggleOnBackground: '#2d4a2d',
        toggleOffBackground: '#3a3a3a',
        pageIndicatorBackground: 'linear-gradient(90deg, #3a3a3a, #2d2d2d, #3a3a3a)',
        loadingBackground: 'rgba(0, 0, 0, 0.9)',
        loadingText: 'white'
    }
};

// 检测系统主题
function detectSystemTheme() {
    if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
        return 'dark';
    }
    return 'light';
}

// 获取当前主题配置
function getCurrentTheme() {
    return isDarkMode ? themes.dark : themes.light;
}

// 监听系统主题变化
function initThemeDetection() {
    // 初始化主题
    isDarkMode = detectSystemTheme() === 'dark';
    
    // 监听主题变化
    if (window.matchMedia) {
        const darkModeQuery = window.matchMedia('(prefers-color-scheme: dark)');
        darkModeQuery.addEventListener('change', (e) => {
            isDarkMode = e.matches;
            updateTheme();
        });
    }
}

// 更新主题样式
function updateTheme() {
    const box = document.getElementById('search-app-box');
    if (box) {
        // 重新应用主题样式
        applyThemeToBox(box);
    }
    
    // 更新加载指示器样式
    const loadingIndicator = document.getElementById('loading-indicator');
    if (loadingIndicator) {
        const theme = getCurrentTheme();
        loadingIndicator.style.background = theme.loadingBackground;
        loadingIndicator.style.color = theme.loadingText;
    }
    
    // 更新页面分隔符样式
    const pageIndicators = document.querySelectorAll('[data-page-indicator]');
    pageIndicators.forEach(indicator => {
        const theme = getCurrentTheme();
        indicator.style.background = theme.pageIndicatorBackground;
        indicator.style.color = theme.textSecondary;
    });
}

// 为搜索框应用主题
function applyThemeToBox(box) {
    const theme = getCurrentTheme();
    
    // 主容器样式
    box.style.backgroundColor = theme.background;
    box.style.borderColor = theme.border;
    box.style.boxShadow = `0 4px 8px ${theme.shadow}`;
    
    // 更新所有子元素的样式
    const author = box.querySelector('[data-author]');
    if (author) {
        author.style.color = theme.textSecondary;
    }
    
    const title = box.querySelector('[data-title]');
    if (title) {
        title.style.color = theme.textPrimary;
    }
    
    // 更新主要搜索引擎链接样式
    const mainLinks = box.querySelectorAll('[data-main-link]');
    mainLinks.forEach(link => {
        if (link.dataset.active === 'true') {
            link.style.backgroundColor = theme.activeBackground;
            link.style.color = '#fff';
        } else {
            link.style.color = theme.textPrimary;
            link.onmouseover = function() {
                this.style.backgroundColor = theme.hoverBackground;
            };
            link.onmouseout = function() {
                this.style.backgroundColor = '';
            };
        }
        link.style.borderTopColor = theme.separatorBorder;
    });
    
    // 更新自动翻页开关样式
    const autoPageToggle = box.querySelector('[data-auto-page-toggle]');
    if (autoPageToggle) {
        autoPageToggle.style.borderTopColor = theme.separatorBorder;
        autoPageToggle.style.color = theme.textPrimary;
        const isEnabled = autoPageToggle.dataset.enabled === 'true';
        autoPageToggle.style.backgroundColor = isEnabled ? theme.toggleOnBackground : theme.toggleOffBackground;
    }
}

// 增强的URL参数获取函数 - 支持特殊字符
function getQueryVariable(variable) {
    try {
        const urlParams = new URLSearchParams(window.location.search);
        const value = urlParams.get(variable);
        return value ? decodeURIComponent(value) : '';
    } catch (e) {
        // 如果URLSearchParams失败,使用传统方法
        const query = window.location.search.substring(1);
        const vars = query.split('&');
        for (let i = 0; i < vars.length; i++) {
            const pair = vars[i].split('=');
            if (pair[0] === variable) {
                try {
                    return decodeURIComponent(pair[1] || '');
                } catch (e) {
                    return pair[1] || '';
                }
            }
        }
        return '';
    }
}

// 从URL中获取搜索关键词
function getKeywords() {
    const currentUrl = window.location.href;
    const engine = mainSearchEngines.find(item => item.testUrl.test(currentUrl));
    if (engine) {
        const keywords = getQueryVariable(engine.keyName);
        return keywords.trim();
    }
    return '';
}

// 获取当前搜索引擎
function getCurrentEngine() {
    const currentUrl = window.location.href;
    return mainSearchEngines.find(item => item.testUrl.test(currentUrl));
}

// 安全的URL编码函数
function safeEncodeURIComponent(str) {
    try {
        return encodeURIComponent(str);
    } catch (e) {
        // 如果编码失败,尝试替换特殊字符
        return str.replace(/[^\w\s\u4e00-\u9fff]/g, function(char) {
            try {
                return encodeURIComponent(char);
            } catch (e) {
                return char;
            }
        });
    }
}

// 自动翻页相关变量
let isAutoPageEnabled = true;
let isLoading = false;
let currentPage = 1;
let maxPages = 10; // 最多自动翻10页,防止无限加载

// 获取下一页URL
function getNextPageUrl() {
    const currentEngine = getCurrentEngine();
    if (!currentEngine) return null;
    
    const currentUrl = new URL(window.location.href);
    const params = currentUrl.searchParams;
    
    let nextPageValue;
    
    if (currentEngine.name === '百度') {
        // 百度: pn参数,每页10个结果
        const currentPn = parseInt(params.get('pn') || '0');
        nextPageValue = currentPn + 10;
        params.set('pn', nextPageValue.toString());
    } else if (currentEngine.name === '必应') {
        // 必应: first参数,每页约10个结果
        const currentFirst = parseInt(params.get('first') || '1');
        nextPageValue = currentFirst + 10;
        params.set('first', nextPageValue.toString());
    } else if (currentEngine.name === 'Google') {
        // Google: start参数,每页10个结果
        const currentStart = parseInt(params.get('start') || '0');
        nextPageValue = currentStart + 10;
        params.set('start', nextPageValue.toString());
    }
    
    return currentUrl.toString();
}

// 加载下一页内容
async function loadNextPage() {
    if (isLoading || currentPage >= maxPages) return;
    
    const nextUrl = getNextPageUrl();
    if (!nextUrl) return;
    
    isLoading = true;
    currentPage++;
    
    // 显示加载状态
    showLoadingIndicator();
    
    try {
        const response = await fetch(nextUrl);
        const html = await response.text();
        
        // 创建临时DOM来解析HTML
        const parser = new DOMParser();
        const doc = parser.parseFromString(html, 'text/html');
        
        const currentEngine = getCurrentEngine();
        const newResults = doc.querySelector(currentEngine.resultsSelector);
        
        if (newResults) {
            // 获取新结果的子元素
            const newItems = Array.from(newResults.children);
            const currentResults = document.querySelector(currentEngine.resultsSelector);
            
            if (currentResults && newItems.length > 0) {
                // 添加分页标识
                const pageIndicator = document.createElement('div');
                const theme = getCurrentTheme();
                pageIndicator.setAttribute('data-page-indicator', 'true');
                pageIndicator.style = `
                    margin: 20px 0;
                    padding: 10px;
                    background: ${theme.pageIndicatorBackground};
                    text-align: center;
                    font-size: 14px;
                    color: ${theme.textSecondary};
                    border-radius: 5px;
                    position: relative;
                `;
                pageIndicator.innerHTML = `━━━ 第 ${currentPage} 页 ━━━`;
                currentResults.appendChild(pageIndicator);
                
                // 添加新结果
                newItems.forEach(item => {
                    // 跳过分页导航等无关元素
                    if (item.classList.contains('result') || 
                        item.classList.contains('b_algo') || 
                        item.classList.contains('g') ||
                        item.id.includes('result') ||
                        !item.classList.contains('page')) {
                        currentResults.appendChild(item);
                    }
                });
                
                // 更新URL历史记录(不刷新页面)
                history.pushState({page: currentPage}, '', nextUrl);
                
                console.log(`已加载第 ${currentPage} 页内容`);
            }
        }
    } catch (error) {
        console.error('加载下一页失败:', error);
    } finally {
        isLoading = false;
        hideLoadingIndicator();
    }
}

// 显示加载指示器
function showLoadingIndicator() {
    let indicator = document.getElementById('loading-indicator');
    const theme = getCurrentTheme();
    
    if (!indicator) {
        indicator = document.createElement('div');
        indicator.id = 'loading-indicator';
        indicator.style = `
            position: fixed;
            bottom: 50px;
            left: 50%;
            transform: translateX(-50%);
            background: ${theme.loadingBackground};
            color: ${theme.loadingText};
            padding: 10px 20px;
            border-radius: 20px;
            font-size: 14px;
            z-index: 10000;
            animation: fadeIn 0.3s ease-in-out;
        `;
        indicator.innerHTML = '🔄 大宝子疯狂加载下一页...';
        document.body.appendChild(indicator);
        
        // 添加CSS动画
        if (!document.getElementById('loading-animation-style')) {
            const style = document.createElement('style');
            style.id = 'loading-animation-style';
            style.textContent = `
                @keyframes fadeIn {
                    from { opacity: 0; transform: translateX(-50%) translateY(10px); }
                    to { opacity: 1; transform: translateX(-50%) translateY(0); }
                }
                @keyframes fadeOut {
                    from { opacity: 1; transform: translateX(-50%) translateY(0); }
                    to { opacity: 0; transform: translateX(-50%) translateY(-10px); }
                }
            `;
            document.head.appendChild(style);
        }
    } else {
        // 更新已存在的指示器颜色
        indicator.style.background = theme.loadingBackground;
        indicator.style.color = theme.loadingText;
    }
    indicator.style.display = 'block';
}

// 隐藏加载指示器
function hideLoadingIndicator() {
    const indicator = document.getElementById('loading-indicator');
    if (indicator) {
        indicator.style.animation = 'fadeOut 0.3s ease-in-out';
        setTimeout(() => {
            indicator.style.display = 'none';
        }, 300);
    }
}

// 检测是否滚动到底部
function isScrolledToBottom() {
    const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
    const windowHeight = window.innerHeight;
    const documentHeight = document.documentElement.scrollHeight;
    
    // 提前200px开始加载
    return scrollTop + windowHeight >= documentHeight - 200;
}

// 滚动事件监听
function initAutoPageScroll() {
    let scrollTimer = null;
    
    window.addEventListener('scroll', () => {
        if (scrollTimer) clearTimeout(scrollTimer);
        
        scrollTimer = setTimeout(() => {
            if (isAutoPageEnabled && isScrolledToBottom() && !isLoading) {
                loadNextPage();
            }
        }, 100); // 防抖,避免频繁触发
    });
}

function jumpToSearch(targetUrl) {
    const keywords = getKeywords();
    if (!keywords) {
        alert('未检测到搜索关键词');
        return;
    }
    
    const encodedKeywords = safeEncodeURIComponent(keywords);
    window.open(targetUrl + encodedKeywords, '_blank');
}

// 添加搜索框
function addBox() {
    const currentEngine = getCurrentEngine();
    const theme = getCurrentTheme();
    
    const div = document.createElement('div');
    div.id = 'search-app-box';
    div.style = `
        position: fixed; 
        top: 50%; 
        left: 20px; 
        transform: translateY(-50%);
        width: 120px; 
        background-color: ${theme.background}; 
        font-size: 12px; 
        z-index: 99999; 
        border: 1px solid ${theme.border}; 
        border-radius: 8px; 
        box-shadow: 0 4px 8px ${theme.shadow}; 
        padding-bottom: 10px;
        font-family: 'Microsoft YaHei', Arial, sans-serif;
        transition: background-color 0.3s ease, border-color 0.3s ease;
    `;
    document.body.appendChild(div);

    // 作者信息
    const author = document.createElement('div');
    author.setAttribute('data-author', 'true');
    author.innerText = "by 丶恩嗯";
    author.style = `text-align: center; font-size: 12px; color: ${theme.textSecondary}; margin: 8px 0; transition: color 0.3s ease;`;
    div.appendChild(author);

    // 标题
    const title = document.createElement('span');
    title.setAttribute('data-title', 'true');
    title.innerText = "🔍聚合搜索";
    title.style = `display: block; text-align: center; margin: 10px 0; font-size: 14px; font-weight: bold; color: ${theme.textPrimary}; transition: color 0.3s ease;`;
    div.appendChild(title);

    // 主要搜索引擎列表
    mainSearchEngines.forEach(engine => {
        const a = document.createElement('a');
        a.href = 'javascript:;';
        a.innerText = engine.name;
        a.setAttribute('data-main-link', 'true');
        
        // 当前搜索引擎高亮显示
        if (currentEngine && currentEngine.name === engine.name) {
            a.setAttribute('data-active', 'true');
            a.style = `
                display: block; 
                padding: 8px 0; 
                text-align: center; 
                color: #fff; 
                text-decoration: none; 
                border-top: 1px solid ${theme.separatorBorder};
                background-color: ${theme.activeBackground};
                font-weight: bold;
                transition: all 0.3s ease;
            `;
        } else {
            a.setAttribute('data-active', 'false');
            a.style = `
                display: block; 
                padding: 8px 0; 
                text-align: center; 
                color: ${theme.textPrimary}; 
                text-decoration: none; 
                border-top: 1px solid ${theme.separatorBorder};
                transition: all 0.3s ease;
            `;
            
            // 添加悬停效果
            a.onmouseover = function() {
                this.style.backgroundColor = theme.hoverBackground;
            };
            a.onmouseout = function() {
                this.style.backgroundColor = '';
            };
        }
        
        a.onclick = () => {
            if (currentEngine && currentEngine.name === engine.name) {
                return; // 当前引擎不需要跳转
            }
            jumpToSearch(engine.searchUrl);
        };
        div.appendChild(a);
    });

    // 自动翻页开关
    const autoPageToggle = document.createElement('div');
    autoPageToggle.setAttribute('data-auto-page-toggle', 'true');
    autoPageToggle.setAttribute('data-enabled', isAutoPageEnabled.toString());
    autoPageToggle.style = `
        border-top: 1px solid ${theme.separatorBorder};
        margin: 5px 0;
        text-align: center;
        font-size: 10px;
        color: ${theme.textPrimary};
        padding: 8px 0;
        cursor: pointer;
        background-color: ${isAutoPageEnabled ? theme.toggleOnBackground : theme.toggleOffBackground};
        transition: all 0.3s ease;
    `;
    autoPageToggle.innerHTML = `🔄 自动翻页: ${isAutoPageEnabled ? 'ON' : 'OFF'}`;
    
    autoPageToggle.onclick = () => {
        isAutoPageEnabled = !isAutoPageEnabled;
        autoPageToggle.setAttribute('data-enabled', isAutoPageEnabled.toString());
        autoPageToggle.innerHTML = `🔄 自动翻页: ${isAutoPageEnabled ? 'ON' : 'OFF'}`;
        autoPageToggle.style.backgroundColor = isAutoPageEnabled ? theme.toggleOnBackground : theme.toggleOffBackground;
        
        // 保存设置到localStorage
        try {
            localStorage.setItem('autoPageEnabled', isAutoPageEnabled.toString());
        } catch (e) {
            // 忽略localStorage错误
        }
    };
    
    div.appendChild(autoPageToggle);

    // 添加拖拽功能
    let isDragging = false;
    let startX, startY, initialX, initialY;

    div.onmousedown = function(e) {
        if (e.target === div || e.target === title || e.target === author) {
            isDragging = true;
            startX = e.clientX;
            startY = e.clientY;
            const rect = div.getBoundingClientRect();
            initialX = rect.left;
            initialY = rect.top;
            div.style.cursor = 'move';
            e.preventDefault();
        }
    };

    document.onmousemove = function(e) {
        if (isDragging) {
            const deltaX = e.clientX - startX;
            const deltaY = e.clientY - startY;
            div.style.left = (initialX + deltaX) + 'px';
            div.style.top = (initialY + deltaY) + 'px';
        }
    };

    document.onmouseup = function() {
        if (isDragging) {
            isDragging = false;
            div.style.cursor = '';
        }
    };
}

// 检测页面是否为搜索结果页
function isSearchPage() {
    return mainSearchEngines.some(engine => engine.testUrl.test(window.location.href));
}

// 主函数
(function() {
    'use strict';
    
    if (!isSearchPage()) {
        return;
    }
    
    // 初始化主题检测
    initThemeDetection();
    
    // 从localStorage读取自动翻页设置
    try {
        const saved = localStorage.getItem('autoPageEnabled');
        if (saved !== null) {
            isAutoPageEnabled = saved === 'true';
        }
    } catch (e) {
        // 忽略localStorage错误
    }
    
    // 延迟执行,确保页面加载完成
    setTimeout(() => {
        // 检查是否已存在搜索框,避免重复添加
        if (!document.getElementById('search-app-box')) {
            addBox();
        }
        
        // 初始化自动翻页功能
        initAutoPageScroll();
        
        console.log('聚合搜索增强版已加载(支持暗色模式)');
        console.log('自动翻页功能已启用,滚动到底部将自动加载下一页');
        console.log(`当前主题模式: ${isDarkMode ? '暗色' : '亮色'}`);
    }, 1000);
})();

QingJ © 2025

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