Google Search Link Checker

Adds 'no access' or safety warnings to Google search results.

2025-06-18 या दिनांकाला. सर्वात नवीन आवृत्ती पाहा.

// ==UserScript==
// @name         Google Search Link Checker
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  Adds 'no access' or safety warnings to Google search results.
// @author       Bui Quoc Dung
// @match        https://www.google.com/search*
// @grant        GM_xmlhttpRequest
// @connect      *
// ==/UserScript==

(function () {
    'use strict';

    async function checkLinkStatus(url) {
        return new Promise((resolve) => {
            GM_xmlhttpRequest({
                method: "HEAD",
                url,
                timeout: 5000,
                onload: res => {
                    const ok = (res.status >= 200 && res.status < 400) || res.status === 0 || res.status === 403 || res.status === 405;
                    resolve(ok);
                },
                onerror: () => resolve(false),
                ontimeout: () => resolve(false)
            });
        });
    }

    async function checkSafetyStatus(url) {
        try {
            const hostname = new URL(url).hostname;
            const apiUrl = `https://check.getsafeonline.org/check/${hostname}?inputUrl=${hostname}`;

            return new Promise((resolve) => {
                GM_xmlhttpRequest({
                    method: "GET",
                    url: apiUrl,
                    timeout: 10000,
                    onload: res => {
                        if (res.status >= 200 && res.status < 300) {
                            const parser = new DOMParser();
                            const doc = parser.parseFromString(res.responseText, 'text/html');
                            const warningElement = doc.querySelector('div.font-light.text-2xl.mt-6 span.text-primary-red.font-bold');
                            if (warningElement) {
                                resolve(warningElement.textContent.trim());
                            } else {
                                resolve(null);
                            }
                        } else {
                            resolve(null);
                        }
                    },
                    onerror: () => resolve(null),
                    ontimeout: () => resolve(null)
                });
            });
        } catch (e) {
            console.error("Could not parse URL for safety check:", url, e);
            return Promise.resolve(null);
        }
    }

    function processLink(link) {
        if (!link || link.dataset.statusChecked) return;
        link.dataset.statusChecked = 'true';

        const url = link.href;
        if (!url || url.startsWith('javascript:') || url.startsWith('#')) return;

        const h3 = link.querySelector('h3');
        const targetElement = h3 || link;

        checkLinkStatus(url).then(accessible => {
            if (!accessible) {
                if (targetElement.querySelector('.no-access-tag')) return;
                const tag = document.createElement('span');
                tag.textContent = '[no access]';
                tag.className = 'no-access-tag';
                tag.style.color = 'orange';
                tag.style.fontSize = '0.8em';
                tag.style.marginLeft = '4px';
                targetElement.appendChild(tag);
            }
        });

        checkSafetyStatus(url).then(safetyText => {
            if (safetyText) {
                if (targetElement.querySelector('.safety-warning-tag')) return;
                const tag = document.createElement('span');
                tag.textContent = ` [${safetyText}]`;
                tag.className = 'safety-warning-tag';
                tag.style.color = 'red';
                tag.style.fontSize = '0.8em';
                tag.style.marginLeft = '4px';
                targetElement.appendChild(tag);
            }
        });
    }

    let scanTimeoutId = null;
    function scanLinksDebounced() {
        clearTimeout(scanTimeoutId);
        scanTimeoutId = setTimeout(() => {
            document.querySelectorAll('div.g a:has(h3), div.MjjYud a:has(h3), div.hlcw0c a:has(h3), span.V9tjod a.zReHs').forEach(processLink);
        }, 300);
    }

    const observer = new MutationObserver(mutations => {
        for (const m of mutations) {
            for (const node of m.addedNodes) {
                if (node.nodeType === 1 &&
                    (node.querySelector('div.g a:has(h3), div.MjjYud a:has(h3), div.hlcw0c a:has(h3)') ||
                     node.matches('.g, .MjjYud, .hlcw0c'))) {
                    scanLinksDebounced();
                    return;
                }
            }
        }
    });

    const container = document.getElementById('center_col') || document.getElementById('rcnt') || document.body;
    observer.observe(container, { childList: true, subtree: true });

    if (document.readyState === 'loading') {
        window.addEventListener('DOMContentLoaded', () => setTimeout(scanLinksDebounced, 1000));
    } else {
        setTimeout(scanLinksDebounced, 1000);
    }
})();
长期地址
遇到问题?请前往 GitHub 提 Issues。