GreasyFork AI Safety Checker (Multi API)

在 GreasyFork 主頁面顯示簡約的 AI 安全檢查提示,整合多重 API(Google, VirusTotal, AbuseIPDB),支援政治正確檢查

目前為 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.0.6
// @description  在 GreasyFork 主頁面顯示簡約的 AI 安全檢查提示,整合多重 API(Google, VirusTotal, AbuseIPDB),支援政治正確檢查
// @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' : 'en';
    }

    const translations = {
        'zh-TW': {
            safetyNotice: 'AI 安全提示:${grant} - 靜態檢查: 檢查 ${count} 個域名,檢測到 ${riskCount} 個高風險域名${apiSummary}${politicalWarning}, 建議在沙盒環境中測試此腳本。',
            safetyNoticeNoRisk: 'AI 安全提示:${grant} - 靜態檢查: 檢查 ${count} 個域名,未發現匹配${apiSummary}${politicalWarning}.',
            noHighRiskGrant: '未檢測到高風險權限',
            fetchCodeFailed: 'AI 安全提示:無法獲取腳本代碼,請手動檢查。',
            politicalWarning: '警告:檢測到從 ${sensitiveSites} 轉向到中國網站 ${chinaSites}',
            gamblingRelated: '可能與博彩相關',
            ptcSite: 'PTC(點擊賺錢)網站',
            urlShortening: '縮短連結服務',
            fileSharing: '文件分享網站'
        },
        'zh-CN': {
            safetyNotice: 'AI 安全提示:${grant} - 静态检查: 检查 ${count} 个域名,检测到 ${riskCount} 个高风险域名${apiSummary}${politicalWarning}, 建议在沙盒环境中测试此脚本。',
            safetyNoticeNoRisk: 'AI 安全提示:${grant} - 静态检查: 检查 ${count} 个域名,未发现匹配${apiSummary}${politicalWarning}.',
            noHighRiskGrant: '未检测到高风险权限',
            fetchCodeFailed: 'AI 安全提示:无法获取脚本代码,请手动检查。',
            politicalWarning: '警告:检测到从 ${sensitiveSites} 转向到中国网站 ${chinaSites}',
            gamblingRelated: '可能与博彩相关',
            ptcSite: 'PTC(点击赚钱)网站',
            urlShortening: '缩短链接服务',
            fileSharing: '文件分享网站'
        },
        '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}.',
            noHighRiskGrant: 'No high-risk permissions detected',
            fetchCodeFailed: 'AI Safety Notice: Unable to fetch script code, please check manually.',
            politicalWarning: 'Warning: Detected redirection from ${sensitiveSites} to Chinese sites ${chinaSites}',
            gamblingRelated: 'Possibly gambling-related',
            ptcSite: 'PTC (Pay-to-Click) site',
            urlShortening: 'URL shortening service',
            fileSharing: 'File sharing site'
        }
    };

    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;
    }

    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/, // 賭博相關(擴展自 Bypass It)
            /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/, // PTC 網站
            /.*\.glitch\.me/, /.*\.techyuth\.xyz/, /165\.22\.63\.250/, /188\.166\.185\.213/ // 可疑主機
        ];

        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/.test(domain)) reason = t('ptcSite');
                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();
            if (line.includes('@grant GM_xmlhttpRequest')) {
                firstRiskyGrant = userLang === 'en' ? 'GM_xmlhttpRequest' : 
                                 userLang === 'zh-TW' ? 'GM 跨站請求' : 'GM 跨站请求';
                break;
            }
            if (!firstRiskyGrant && highRiskGrants.some(grant => line.includes(`@grant ${grant}`))) {
                firstRiskyGrant = highRiskGrants.find(grant => line.includes(`@grant ${grant}`));
                break;
            }
        }
        if (!firstRiskyGrant) firstRiskyGrant = t('noHighRiskGrant');

        // 政治正確檢查(註釋掉以應對審查)
        let politicalWarning = '';
        // const politicalIssues = checkPoliticalRedirection(code);
        // if (politicalIssues) politicalWarning = `; ${politicalIssues}`;

        let warning = highRiskDomains.length > 0
            ? t('safetyNotice', { grant: firstRiskyGrant, count: staticCheckedCount, riskCount: highRiskDomains.length, apiSummary: '', politicalWarning })
            : 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; }');

    waitForElement('.install-link').then((installButton) => {
        fetchScriptCode().then(async (code) => {
            const { warning } = await analyzeScript(code);
            const notice = document.createElement('div');
            notice.className = 'grok-ai-safety-notice';
            notice.textContent = warning;
            installButton.parentNode.insertBefore(notice, installButton);
            console.log('提示已插入');
        }).catch(() => {
            const notice = document.createElement('div');
            notice.className = 'grok-ai-safety-notice';
            notice.textContent = t('fetchCodeFailed');
            installButton.parentNode.insertBefore(notice, installButton);
        });
    });
})();