Greasy Fork

Greasy Fork is available in English.

Google Search Infinite Scroll & Open New Tab / 谷歌搜索无限滚动与新标签页

Automatically open links in a new tab and auto-load the next page results. / 自动在新标签页打开谷歌搜索结果,并实现无限滚动翻页。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Google Search Infinite Scroll & Open New Tab / 谷歌搜索无限滚动与新标签页
// @namespace    http://tampermonkey.net/
// @version      2.0
// @description  Automatically open links in a new tab and auto-load the next page results. / 自动在新标签页打开谷歌搜索结果,并实现无限滚动翻页。
// @author       Gemini
// @match        *://www.google.com/search*
// @match        *://www.google.com.hk/search*
// @match        *://www.google.co.jp/search*
// @match        *://www.google.co.uk/search*
// @match        *://www.google.cn/search*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=google.com
// @grant        none
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    // --- 配置 ---
    const DISTANCE_TO_BOTTOM = 800; // 距离底部多少像素时开始加载
    let isLoading = false; // 防止重复加载标志位

    // --- 功能 1: 强制新标签页打开 ---
    function openInNewTab() {
        // 选择主要结果、广告结果
        const selectors = [
            '#search a[href^="http"]:not([target="_blank"])',
            '#rso a[href^="http"]:not([target="_blank"])',
            '#tads a[href^="http"]:not([target="_blank"])',
            '#bottomads a[href^="http"]:not([target="_blank"])',
            // 针对追加加载的容器
            '.g a[href^="http"]:not([target="_blank"])'
        ];

        const links = document.querySelectorAll(selectors.join(','));

        links.forEach(link => {
            // 排除页码链接、功能链接
            if (link.getAttribute('href') &&
                !link.getAttribute('href').startsWith('#') &&
                !link.id.includes('pn') // 排除分页按钮
            ) {
                link.setAttribute('target', '_blank');
                link.setAttribute('rel', 'noopener noreferrer');
                link.addEventListener('click', (e) => e.stopPropagation(), { passive: true });
            }
        });
    }

    // --- 功能 2: 无限滚动逻辑 ---

    // 获取下一页的链接元素
    function getNextPageElement() {
        return document.querySelector('#pnnext'); // 谷歌标准的“下一页”ID
    }

    // 获取“更多结果”按钮(现代版谷歌有时不分页,而是显示按钮)
    function getMoreButton() {
        // 查找常见的“更多结果”按钮,通常包含特定类名或文本
        // 谷歌经常变换类名,这里尝试通过属性或层级查找
        const buttons = Array.from(document.querySelectorAll('a, div[role="button"]'));
        return buttons.find(el => el.innerText && (el.innerText.includes('More results') || el.innerText.includes('更多结果')));
    }

    async function loadNextPage() {
        if (isLoading) return;

        // 1. 优先检查是否有“更多结果”按钮(现代动态加载模式)
        const moreBtn = getMoreButton();
        if (moreBtn && moreBtn.offsetParent !== null) {
            isLoading = true;
            moreBtn.click(); // 直接点击谷歌原生的加载按钮
            console.log('Gemini Script: Clicked "More results" button.');

            // 等待一下重置状态
            setTimeout(() => { isLoading = false; }, 2000);
            return;
        }

        // 2. 传统分页模式(获取 #pnnext 链接)
        const nextLink = getNextPageElement();
        if (!nextLink) return; // 没有下一页了

        isLoading = true;
        const url = nextLink.href;

        // 插入一个加载提示
        const loadingIndicator = document.createElement('div');
        loadingIndicator.style.cssText = 'text-align:center; padding: 20px; color: #666; font-size: 14px;';
        loadingIndicator.innerText = '正在加载下一页...';
        document.querySelector('#center_col').appendChild(loadingIndicator);

        try {
            const response = await fetch(url);
            const text = await response.text();

            // 解析返回的 HTML
            const parser = new DOMParser();
            const doc = parser.parseFromString(text, 'text/html');

            // 提取新结果 (#rso 是搜索结果的主要容器)
            const newResults = doc.querySelector('#rso');
            const currentResults = document.querySelector('#rso');

            if (newResults && currentResults) {
                // 为了保持样式,我们创建一个分隔线
                const separator = document.createElement('div');
                separator.style.cssText = 'border-bottom: 1px dashed #dfe1e5; margin: 20px 0; text-align: center; color: #888; font-size: 12px;';
                separator.innerText = `--- 第 ${getPageNumber(url)} 页 ---`;
                currentResults.appendChild(separator);

                // 将新结果追加到当前页面
                // 注意:直接追加 innerHTML 可能会丢失事件,最好追加子节点
                while (newResults.firstChild) {
                    currentResults.appendChild(newResults.firstChild);
                }

                // 关键:更新页面底部的“下一页”链接,以便下次滚动能加载再下一页
                const newNextLink = doc.querySelector('#pnnext');
                const oldNextLink = document.querySelector('#pnnext');
                const navTable = document.querySelector('[role="navigation"] table'); // 分页导航条

                if (oldNextLink && newNextLink) {
                    oldNextLink.href = newNextLink.href; // 更新链接地址
                } else if (!newNextLink && navTable) {
                     // 如果没有下一页了,移除分页条
                    navTable.style.display = 'none';
                    loadingIndicator.innerText = '已到达底部';
                }
            }
        } catch (err) {
            console.error('Gemini Script Error:', err);
        } finally {
            loadingIndicator.remove();
            isLoading = false;
        }
    }

    // 辅助:尝试从URL提取页码
    function getPageNumber(url) {
        const match = url.match(/start=(\d+)/);
        if (match) {
            return (parseInt(match[1]) / 10) + 1;
        }
        return 'Next';
    }

    // --- 初始化与监听 ---

    // 1. 运行一次新标签页逻辑
    openInNewTab();

    // 2. 监听滚动事件(防抖处理)
    let scrollTimeout;
    window.addEventListener('scroll', () => {
        if (scrollTimeout) clearTimeout(scrollTimeout);
        scrollTimeout = setTimeout(() => {
            // 检查是否滚动到底部
            if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight - DISTANCE_TO_BOTTOM) {
                loadNextPage();
            }
        }, 100);
    });

    // 3. 监听 DOM 变化 (用于处理新加载的内容,使其符合“新标签页打开”规则)
    const observer = new MutationObserver((mutations) => {
        openInNewTab();
    });

    observer.observe(document.body, {
        childList: true,
        subtree: true
    });

})();