GreasyFork AI Safety Checker (Multi API)

在 GreasyFork 主頁面顯示 AI 安全檢查提示,含詳細高風險域名列表與權限檢測,支援多國語系

目前為 2025-03-14 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         GreasyFork AI Safety Checker (Multi API)
// @namespace    http://tampermonkey.net/
// @version      2.1.3
// @description  在 GreasyFork 主頁面顯示 AI 安全檢查提示,含詳細高風險域名列表與權限檢測,支援多國語系
// @match        https://greasyfork.org/*scripts/*
// @exclude      https://greasyfork.org/*scripts/*/code*
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @grant        GM_info
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    let userLang = GM_getValue('userSelectedLanguage', '');
    if (!userLang) {
        userLang = navigator.language === 'zh-TW' ? 'zh-TW' : 
                   navigator.language.startsWith('zh') ? 'zh-CN' : 
                   navigator.language.startsWith('ja') ? 'ja' : 'en';
    }

    const translations = {
        'zh-TW': {
            safetyNotice: 'AI 安全提示:${grant} - 靜態檢查: 檢查 ${count} 個域名,檢測到 ${riskCount} 個高風險域名${apiSummary}${politicalWarning}, 建議在沙盒環境中測試此腳本。',
            safetyNoticeNoRisk: 'AI 安全提示:${grant} - 靜態檢查: 檢查 ${count} 個域名,未發現匹配${apiSummary}${politicalWarning}.',
            staticRiskNotice: 'AI 安全提示:靜態檢查發現風險域名 - 檢查 ${count} 個域名,檢測到 ${riskCount} 個高風險域名${apiSummary}${politicalWarning}, 建議在沙盒環境中測試此腳本。',
            noHighRiskGrant: '未檢測到高風險權限',
            fetchCodeFailed: 'AI 安全提示:無法獲取腳本代碼,請手動檢查。',
            viewDetails: '查看詳情',
            hideDetails: '收起詳情',
            gamblingRelated: '可能與博彩相關',
            ptcSite: 'PTC(點擊賺錢)網站',
            urlShortening: '縮短連結服務',
            fileSharing: '文件分享網站',
            highRiskTld: '高風險 TLD',
            GM_xmlhttpRequest: 'GM 跨站請求',
            GM_setValue: 'GM 設置值',
            unsafeWindow: '不安全窗口',
            GM_openInTab: 'GM 新標籤開啟'
        },
        'zh-CN': {
            safetyNotice: 'AI 安全提示:${grant} - 静态检查: 检查 ${count} 个域名,检测到 ${riskCount} 个高风险域名${apiSummary}${politicalWarning}, 建议在沙盒环境中测试此脚本。',
            safetyNoticeNoRisk: 'AI 安全提示:${grant} - 静态检查: 检查 ${count} 个域名,未发现匹配${apiSummary}${politicalWarning}.',
            staticRiskNotice: 'AI 安全提示:静态检查发现风险域名 - 检查 ${count} 个域名,检测到 ${riskCount} 个高风险域名${apiSummary}${politicalWarning}, 建议在沙盒环境中测试此脚本。',
            noHighRiskGrant: '未检测到高风险权限',
            fetchCodeFailed: 'AI 安全提示:无法获取脚本代码,请手动检查。',
            viewDetails: '查看详情',
            hideDetails: '收起详情',
            gamblingRelated: '可能与博彩相关',
            ptcSite: 'PTC(点击赚钱)网站',
            urlShortening: '缩短链接服务',
            fileSharing: '文件分享网站',
            highRiskTld: '高风险 TLD',
            GM_xmlhttpRequest: 'GM 跨站请求',
            GM_setValue: 'GM 设置值',
            unsafeWindow: '不安全窗口',
            GM_openInTab: 'GM 新标签开启'
        },
        'en': {
            safetyNotice: 'AI Safety Notice: ${grant} - Static check: checked ${count} domains, detected ${riskCount} high-risk domains${apiSummary}${politicalWarning}, test in a sandbox environment.',
            safetyNoticeNoRisk: 'AI Safety Notice: ${grant} - Static check: checked ${count} domains, no matches${apiSummary}${politicalWarning}.',
            staticRiskNotice: 'AI Safety Notice: Static check detected risks - checked ${count} domains, detected ${riskCount} high-risk domains${apiSummary}${politicalWarning}, test in a sandbox environment.',
            noHighRiskGrant: 'No high-risk permissions detected',
            fetchCodeFailed: 'AI Safety Notice: Unable to fetch script code, please check manually.',
            viewDetails: 'View Details',
            hideDetails: 'Hide Details',
            gamblingRelated: 'Possibly gambling-related',
            ptcSite: 'PTC (Pay-to-Click) site',
            urlShortening: 'URL shortening service',
            fileSharing: 'File sharing site',
            highRiskTld: 'High-risk TLD',
            GM_xmlhttpRequest: 'GM_xmlhttpRequest',
            GM_setValue: 'GM_setValue',
            unsafeWindow: 'unsafeWindow',
            GM_openInTab: 'GM_openInTab'
        },
        'ja': {
            safetyNotice: 'AI安全通知:${grant} - 静的チェック:${count}ドメインをチェックし、${riskCount}個の高リスクドメインを検出しました${apiSummary}${politicalWarning}。サンドボックス環境でテストすることをお勧めします。',
            safetyNoticeNoRisk: 'AI安全通知:${grant} - 静的チェック:${count}ドメインをチェックしましたが、一致するものはありません${apiSummary}${politicalWarning}。',
            staticRiskNotice: 'AI安全通知:静的チェックでリスクを検出 - ${count}ドメインをチェックし、${riskCount}個の高リスクドメインを検出しました${apiSummary}${politicalWarning}。サンドボックス環境でテストすることをお勧めします。',
            noHighRiskGrant: '高リスク権限は検出されませんでした',
            fetchCodeFailed: 'AI安全通知:スクリプトコードを取得できません。手動で確認してください。',
            viewDetails: '詳細を表示',
            hideDetails: '詳細を非表示',
            gamblingRelated: 'ギャンブル関連の可能性',
            ptcSite: 'PTC(クリックで稼ぐ)サイト',
            urlShortening: 'URL短縮サービス',
            fileSharing: 'ファイル共有サイト',
            highRiskTld: '高リスクTLD',
            GM_xmlhttpRequest: 'GM_xmlhttpRequest',
            GM_setValue: 'GM_setValue',
            unsafeWindow: 'unsafeWindow',
            GM_openInTab: 'GM_openInTab'
        }
    };

    function t(key, params = {}) {
        let text = translations[userLang][key] || translations['en'][key];
        for (const [param, value] of Object.entries(params)) {
            text = text.replace(`\${${param}}`, value);
        }
        return text;
    }

    GM_registerMenuCommand('切換語言 / Switch Language', () => {
        const languages = ['zh-TW', 'zh-CN', 'en', 'ja'];
        const currentIndex = languages.indexOf(userLang);
        const nextIndex = (currentIndex + 1) % languages.length;
        userLang = languages[nextIndex];
        GM_setValue('userSelectedLanguage', userLang);
        alert('語言已切換 / Language switched to: ' + userLang);
        location.reload();
    });

    if (/\/code/.test(window.location.href)) return;

    const fetchScriptCode = () => {
        const scriptId = window.location.pathname.match(/scripts\/(\d+)/)?.[1];
        if (!scriptId) throw new Error('無法提取腳本 ID');
        const codeUrl = `${window.location.origin}${window.location.pathname}/code`;
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: 'GET', url: codeUrl, onload: (response) => {
                    const parser = new DOMParser();
                    const doc = parser.parseFromString(response.responseText, 'text/html');
                    const code = doc.querySelector('pre')?.textContent;
                    if (code) resolve(code);
                    else reject('未找到代碼');
                }, onerror: () => reject('獲取代碼失敗')
            });
        });
    };

    const analyzeScript = async (code) => {
        const lines = code.split('\n');
        const matches = lines
            .filter(line => line.trim().startsWith('// @match'))
            .map(line => line.replace('// @match', '').trim().replace(/^\*?:\/\//, '').replace(/\/.*/, ''))
            .filter(url => url && !url.includes('greasyfork.org'));

        const highRiskDomains = [];
        const riskReasons = {};
        const knownSafe = ['google.com', 'youtube.com', 'facebook.com', 'instagram.com', 'twitter.com', 'wikipedia.org'];
        const riskyPatterns = [
            /vn88\..*/, /fb88\..*/, /m88\..*/, /bet88li\.com/, /yeumoney\.com/, /165\.22\.63\.250/, /188\.166\.185\.213/,
            /aylink\.co/, /gplinks\.co/, /v2links\.me/, /coinclix\.co/, /cutyion\.com/, /upfion\.com/,
            /modsfire\.com/, /dropbox\.com/, /drive\.google\.com/, /mega\.nz/,
            /ourcoincash\.xyz/, /bitcotasks\.com/, /freepayz\.com/, /gwaher\.com/, /geekgrove\.net/, /cricketlegacy\.com/,
            /.*\.techyuth\.xyz/, /.*\.idblogmarket\.com/, /.*\.phonesparrow\.com/, /.*\.wikijankari\.com/,
            /financewada\.com/, /financenova\.online/, /utkarshonlinetest\.com/, /rajasthantopnews\.com/,
            /.*\.devnote\.in/, /naamlist\.com/, /modijiurl\.com/, /gmsrweb\.org/
        ];

        let staticCheckedCount = 0;
        matches.forEach(domain => {
            staticCheckedCount++;
            if (knownSafe.some(safe => domain.includes(safe))) return;
            let reason = '未知風險';
            if (riskyPatterns.some(pattern => pattern.test(domain))) {
                if (/vn88\..*|fb88\..*|m88\..*|bet88li\.com|yeumoney\.com/.test(domain)) reason = t('gamblingRelated');
                else if (/aylink\.co|gplinks\.co|v2links\.me|coinclix\.co|cutyion\.com|upfion\.com/.test(domain)) reason = t('urlShortening');
                else if (/modsfire\.com|dropbox\.com|drive\.google\.com|mega\.nz/.test(domain)) reason = t('fileSharing');
                else if (/ourcoincash\.xyz|bitcotasks\.com|freepayz\.com|gwaher\.com|geekgrove\.net|cricketlegacy\.com/.test(domain)) reason = t('ptcSite');
                else if (/xyz|in|online|wtf/.test(domain)) reason = t('highRiskTld');
                highRiskDomains.push(domain);
                riskReasons[domain] = reason;
            }
        });

        const highRiskGrants = ['GM_xmlhttpRequest', 'unsafeWindow', 'GM_setValue', 'GM_openInTab'];
        let firstRiskyGrant = '';
        for (let i = 0; i < lines.length; i++) {
            const line = lines[i].trim();
            for (const grant of highRiskGrants) {
                if (line.match(new RegExp(`@grant\\s+${grant}`))) {
                    firstRiskyGrant = `${t(grant)} (行 ${i + 1})`;
                    break;
                }
            }
            if (firstRiskyGrant) break;
        }
        if (!firstRiskyGrant) firstRiskyGrant = t('noHighRiskGrant');

        let warning;
        const details = highRiskDomains.length > 0 
            ? `<div class="grok-ai-safety-details" style="display: none;">${highRiskDomains.map(d => `${d}: ${riskReasons[d]}`).join('<br>')}</div>` 
            : '';

        if (highRiskDomains.length > 0) {
            warning = firstRiskyGrant === t('noHighRiskGrant') 
                ? `${t('staticRiskNotice', { count: staticCheckedCount, riskCount: highRiskDomains.length, apiSummary: '', politicalWarning: '' })}<a id="toggle-details" href="#">${t('viewDetails')}</a>${details}`
                : `${t('safetyNotice', { grant: firstRiskyGrant, count: staticCheckedCount, riskCount: highRiskDomains.length, apiSummary: '', politicalWarning: '' })}<a id="toggle-details" href="#">${t('viewDetails')}</a>${details}`;
        } else {
            warning = t('safetyNoticeNoRisk', { grant: firstRiskyGrant, count: staticCheckedCount, apiSummary: '', politicalWarning: '' });
        }

        return { warning, highRiskDomains, riskReasons };
    };

    const waitForElement = (selector) => new Promise((resolve) => {
        const element = document.querySelector(selector);
        if (element) return resolve(element);
        const observer = new MutationObserver(() => {
            const el = document.querySelector(selector);
            if (el) {
                observer.disconnect();
                resolve(el);
            }
        });
        observer.observe(document.body, { childList: true, subtree: true });
    });

    GM_addStyle(`
        .grok-ai-safety-notice { background: #fff3f3; border: 2px solid #ff4d4d; padding: 10px; margin-bottom: 15px; border-radius: 5px; color: #333; }
        .grok-ai-safety-details { margin-top: 10px; padding: 5px; background: #ffe6e6; border: 1px solid #ff9999; border-radius: 3px; }
    `);

    waitForElement('.install-link').then((installButton) => {
        fetchScriptCode().then(async (code) => {
            const { warning, highRiskDomains } = await analyzeScript(code);
            const notice = document.createElement('div');
            notice.className = 'grok-ai-safety-notice';
            notice.innerHTML = warning;

            if (highRiskDomains.length > 0) {
                const toggleLink = notice.querySelector('#toggle-details');
                const detailsDiv = notice.querySelector('.grok-ai-safety-details');
                toggleLink.addEventListener('click', (e) => {
                    e.preventDefault();
                    if (detailsDiv.style.display === 'block') {
                        detailsDiv.style.display = 'none';
                        toggleLink.textContent = t('viewDetails');
                    } else {
                        detailsDiv.style.display = 'block';
                        toggleLink.textContent = t('hideDetails');
                    }
                });
            }

            installButton.parentNode.insertBefore(notice, installButton);
            console.log('提示已插入 / Notice inserted');
        }).catch(() => {
            const notice = document.createElement('div');
            notice.className = 'grok-ai-safety-notice';
            notice.textContent = t('fetchCodeFailed');
            installButton.parentNode.insertBefore(notice, installButton);
        });
    });
})();