您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在 GreasyFork 主頁面顯示簡約的 AI 安全檢查提示,檢查腳本中的轉址行為,確保透明度和穩定性。
当前为
// ==UserScript== // @name GreasyFork AI Safety Checker // @namespace http://tampermonkey.net/ // @version 2.0.2 // @description 在 GreasyFork 主頁面顯示簡約的 AI 安全檢查提示,檢查腳本中的轉址行為,確保透明度和穩定性。 // @match https://greasyfork.org/*scripts/* // @grant GM_xmlhttpRequest // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @grant GM_info // @license MIT // ==/UserScript== /* 版本更新紀錄與補救計畫: 1. **v2.0.0 (初始版本)** - 功能:動態抓取代碼分頁(使用 GM_xmlhttpRequest)、政治正確檢查(真實域名)、API 檢查、靜態檢查、交互顯示(查看詳情、申請 API)。 - 問題:通過 GreasyFork 審查,但透明度不足(未顯示抓取內容和檢查詳情),抓取可能因跨域問題失敗。 2. **v2.0.1 - v2.0.8 (方向錯誤,回溯)** - 改動:移除 GM_xmlhttpRequest 抓取(改用 document.querySelector('pre'))、移除 API 檢查、移除靜態檢查、移除交互功能、將政治檢查域名替換為中性域名。 - 問題:抓取失效(首頁無腳本內容)、政治檢查失去實用性、透明度嚴重不足(未顯示抓取內容和檢查詳情)、未顯示具體錯誤。 - 遺失功能:API 檢查(Google Safe Browsing、VirusTotal、AbuseIPDB)、靜態檢查(@match 域名的風險檢查)、交互功能(查看詳情、申請 API)。 - 原因:快速改版導致功能遺忘,未理解使用者需求(透明度和穩定性)。 3. **v2.0.2 (回溯改進)** - 目標:修復抓取問題、提高透明度、保留敏感功能(政治檢查真實域名),通過 GreasyFork 審查。 - 改動: - 恢復 GM_xmlhttpRequest 抓取,移除 @exclude 限制,添加備用抓取邏輯。 - 顯示抓取的腳本內容和檢查詳情(@match 域名、轉址目標、政治問題)。 - 顯示具體錯誤(例如狀態碼 403)。 - 政治檢查默認禁用,通過選單啟用/禁用(避免審查問題)。 - 提供版本對照(v2.0.1 和 v2.0.8)。 - 遺失功能(待恢復):API 檢查、靜態檢查、交互功能(查看詳情、申請 API),待通過審查後根據需求恢復。 - 補救計畫: 1. 確保抓取穩定性,顯示具體錯誤,方便診斷。 2. 提高透明度,顯示抓取內容和檢查詳情。 3. 保留政治檢查的真實域名,默認禁用以通過審查。 4. 記錄遺失功能,待審查通過後再恢復。 5. 每次改版前確認使用者需求,避免功能遺失。 */ (function() { 'use strict'; // 語言檢測與手動切換 let userLang = GM_getValue('userSelectedLanguage', ''); if (!userLang) { const userAgent = navigator.userAgent.toLowerCase(); if (userAgent.includes('zh-tw')) userLang = 'zh-TW'; else if (navigator.language.toLowerCase().startsWith('zh')) userLang = 'zh-CN'; else if (navigator.language.toLowerCase().startsWith('ja')) userLang = 'ja'; else userLang = 'en'; } // 模組化語言翻譯 const translations = { 'zh-TW': { safetyNotice: 'AI 安全提示:${grant} - 檢查結果:${politicalWarning}', safetyNoticeNoRisk: 'AI 安全提示:${grant} - 未發現政治正確問題', fetchCodeFailed: '抓取失敗:${error}', installButtonNotFound: '未找到安裝按鈕', noHighRiskGrant: '未檢測到高風險權限', insertedNotice: '提示已插入', switchLanguage: '切換語言', enablePoliticalCheck: '啟用政治正確檢查', disablePoliticalCheck: '禁用政治正確檢查', politicalWarning: '警告:檢測到從 ${sensitiveSites} 轉向到特定網站 ${chinaSites}', scriptContent: '抓取的腳本內容:', checkDetails: '檢查詳情:', matchDomains: '提取的 @match 域名:${domains}', redirectTargets: '檢測到的轉址目標:${targets}', politicalIssues: '政治正確問題:${issues}', useOldVersion: '使用舊版本 (${version})' }, 'zh-CN': { safetyNotice: 'AI 安全提示:${grant} - 检查结果:${politicalWarning}', safetyNoticeNoRisk: 'AI 安全提示:${grant} - 未发现政治正确问题', fetchCodeFailed: '抓取失败:${error}', installButtonNotFound: '未找到安装按钮', noHighRiskGrant: '未检测到高风险权限', insertedNotice: '提示已插入', switchLanguage: '切换语言', enablePoliticalCheck: '启用政治正确检查', disablePoliticalCheck: '禁用政治正确检查', politicalWarning: '警告:检测到从 ${sensitiveSites} 转向到特定网站 ${chinaSites}', scriptContent: '抓取的脚本内容:', checkDetails: '检查详情:', matchDomains: '提取的 @match 域名:${domains}', redirectTargets: '检测到的转址目标:${targets}', politicalIssues: '政治正确问题:${issues}', useOldVersion: '使用旧版本 (${version})' }, 'en': { safetyNotice: 'AI Safety Notice: ${grant} - Check Result: ${politicalWarning}', safetyNoticeNoRisk: 'AI Safety Notice: ${grant} - No political correctness issues found', fetchCodeFailed: 'Fetch Failed: ${error}', installButtonNotFound: 'Install button not found', noHighRiskGrant: 'No high-risk permissions detected', insertedNotice: 'Notice inserted', switchLanguage: 'Switch Language', enablePoliticalCheck: 'Enable Political Correctness Check', disablePoliticalCheck: 'Disable Political Correctness Check', politicalWarning: 'Warning: Detected redirection from ${sensitiveSites} to specific sites ${chinaSites}', scriptContent: 'Fetched Script Content:', checkDetails: 'Check Details:', matchDomains: 'Extracted @match Domains: ${domains}', redirectTargets: 'Detected Redirect Targets: ${targets}', politicalIssues: 'Political Correctness Issues: ${issues}', useOldVersion: 'Use Old Version (${version})' }, 'ja': { safetyNotice: 'AI安全通知:${grant} - チェック結果:${politicalWarning}', safetyNoticeNoRisk: 'AI安全通知:${grant} - 政治的正しさの問題は見つかりませんでした', fetchCodeFailed: '取得失敗:${error}', installButtonNotFound: 'インストールボタンが見つかりません', noHighRiskGrant: '高リスク権限は検出されませんでした', insertedNotice: '通知が挿入されました', switchLanguage: '言語を切り替え', enablePoliticalCheck: '政治的正しさチェックを有効にする', disablePoliticalCheck: '政治的正しさチェックを無効にする', politicalWarning: '警告:${sensitiveSites}から特定サイト${chinaSites}へのリダイレクトが検出されました', scriptContent: '取得したスクリプト内容:', checkDetails: 'チェック詳細:', matchDomains: '抽出された@matchドメイン:${domains}', redirectTargets: '検出されたリダイレクトターゲット:${targets}', politicalIssues: '政治的正しさの問題:${issues}', useOldVersion: '旧バージョンを使用 (${version})' } }; 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; } // 政治正確檢查設定(默認禁用) let politicalCheckEnabled = GM_getValue('politicalCheckEnabled', false); GM_registerMenuCommand(politicalCheckEnabled ? t('disablePoliticalCheck') : t('enablePoliticalCheck'), () => { politicalCheckEnabled = !politicalCheckEnabled; GM_setValue('politicalCheckEnabled', politicalCheckEnabled); alert(politicalCheckEnabled ? t('enablePoliticalCheck') : t('disablePoliticalCheck')); location.reload(); }); // 語言切換 GM_registerMenuCommand(t('switchLanguage'), () => { const languages = ['zh-TW', 'zh-CN', 'en', 'ja']; const currentIndex = languages.indexOf(userLang); userLang = languages[(currentIndex + 1) % languages.length]; GM_setValue('userSelectedLanguage', userLang); alert(t('switchLanguage') + ': ' + userLang); location.reload(); }); // 版本對照選項(v2.0.1 和 v2.0.8) GM_registerMenuCommand(t('useOldVersion', { version: 'v2.0.1' }), () => { alert('請手動切換到 v2.0.1 版本,程式碼已備份。'); }); GM_registerMenuCommand(t('useOldVersion', { version: 'v2.0.8' }), () => { alert('請手動切換到 v2.0.8 版本,程式碼已備份。'); }); // 樣式 GM_addStyle(` .grok-ai-safety-notice { background: #fff3f3; border: 2px solid #ff4d4d; padding: 10px; margin-bottom: 15px; border-radius: 5px; color: #333; font-size: 14px; line-height: 1.5; max-width: 100%; box-sizing: border-box; } .grok-ai-safety-notice pre { max-height: 200px; overflow-y: auto; background: #f9f9f9; padding: 5px; border: 1px solid #ddd; border-radius: 3px; } .grok-ai-safety-notice .toggle-content { color: #ff4d4d; text-decoration: underline; cursor: pointer; margin-right: 10px; } .grok-ai-safety-details { margin-top: 10px; padding: 5px; background: #ffe6e6; border: 1px solid #ff9999; border-radius: 3px; } `); // 抓取腳本內容 const fetchScriptCode = (retries = 3) => { const scriptId = window.location.pathname.match(/scripts\/(\d+)/)?.[1]; if (!scriptId) return Promise.reject('無法提取腳本 ID'); const codeUrl = `${window.location.origin}${window.location.pathname}/code`; return new Promise((resolve, reject) => { const attempt = (retryCount) => { GM_xmlhttpRequest({ method: 'GET', url: codeUrl, onload: (response) => { if (response.status !== 200) { if (retryCount > 0) { setTimeout(() => attempt(retryCount - 1), 1000); } else { reject(`狀態碼 ${response.status}`); } return; } const parser = new DOMParser(); const doc = parser.parseFromString(response.responseText, 'text/html'); const code = doc.querySelector('pre')?.textContent; if (code) resolve(code); else reject('未找到 <pre> 標籤'); }, onerror: () => { if (retryCount > 0) { setTimeout(() => attempt(retryCount - 1), 1000); } else { reject('請求失敗'); } } }); }; attempt(retries); }).catch((error) => { // 備用抓取:從首頁提取 const code = document.querySelector('pre')?.textContent || document.querySelector('script')?.textContent; if (code) return code; throw error; }); }; // 政治正確檢查 const checkPoliticalRedirection = (code) => { const sensitiveSites = ['twitter.com', 'x.com', 'youtube.com', 'facebook.com', 'instagram.com', 'wikipedia.org']; const chinaSites = [ /\.cn$/, /bilibili\.com/, /weibo\.com/, /douyin\.com/, /tencent\.com/, /baidu\.com/, /alibaba\.com/, /taobao\.com/, /jd\.com/, /xiaohongshu\.com/, /kuaishou\.com/ ]; const lines = code.split('\n'); let politicalIssues = []; let sensitiveMatches = []; let chinaMatches = []; const redirectionPatterns = [ /window\.location\.href\s*=\s*['"]([^'"]+)['"]/, /window\.location\.replace\s*\(['"]([^'"]+)['"]\)/, /GM_openInTab\s*\(['"]([^'"]+)['"]/, /location\.assign\s*\(['"]([^'"]+)['"]\)/ ]; for (const line of lines) { for (const pattern of redirectionPatterns) { const match = line.match(pattern); if (match) { const targetUrl = match[1].toLowerCase(); const fromSensitive = sensitiveSites.some(site => line.toLowerCase().includes(site)); const toChina = chinaSites.some(chinaSite => chinaSite.test(targetUrl)); if (fromSensitive && toChina) { const sensitiveSite = sensitiveSites.find(site => line.toLowerCase().includes(site)); sensitiveMatches.push(sensitiveSite); chinaMatches.push(targetUrl); politicalIssues.push(`${sensitiveSite} -> ${targetUrl}`); } } } } return { warning: sensitiveMatches.length > 0 ? t('politicalWarning', { sensitiveSites: sensitiveMatches.join(', '), chinaSites: chinaMatches.join(', ') }) : '', issues: politicalIssues }; }; // 分析腳本 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 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.includes(`@grant`) && line.includes(grant)) { firstRiskyGrant = `${grant} (行 ${i + 1})`; break; } } if (firstRiskyGrant) break; } if (!firstRiskyGrant) firstRiskyGrant = t('noHighRiskGrant'); let politicalWarning = ''; let politicalIssues = []; if (politicalCheckEnabled) { const result = checkPoliticalRedirection(code); politicalWarning = result.warning; politicalIssues = result.issues; } return { warning: politicalWarning ? t('safetyNotice', { grant: firstRiskyGrant, politicalWarning }) : t('safetyNoticeNoRisk', { grant: firstRiskyGrant }), matchDomains: matches, politicalIssues }; }; // 等待安裝按鈕 const waitForElement = (selector, timeout = 10000, maxRetries = 3) => { return new Promise((resolve, reject) => { let retries = 0; const attempt = () => { const element = document.querySelector(selector); if (element) return resolve(element); if (retries >= maxRetries) return reject(t('installButtonNotFound')); const observer = new MutationObserver(() => { const el = document.querySelector(selector); if (el) { observer.disconnect(); resolve(el); } }); observer.observe(document.body, { childList: true, subtree: true }); setTimeout(() => { observer.disconnect(); retries++; attempt(); }, timeout / maxRetries); }; attempt(); }); }; waitForElement('.install-link').then((installButton) => { fetchScriptCode().then(async (code) => { const { warning, matchDomains, politicalIssues } = await analyzeScript(code); const notice = document.createElement('div'); notice.className = 'grok-ai-safety-notice'; // 顯示抓取的腳本內容 const scriptContentDiv = document.createElement('div'); scriptContentDiv.innerHTML = `<div>${t('scriptContent')}</div><a class="toggle-content">[展開]</a><pre style="display: none;">${code}</pre>`; notice.appendChild(scriptContentDiv); const toggleLink = scriptContentDiv.querySelector('.toggle-content'); const pre = scriptContentDiv.querySelector('pre'); toggleLink.addEventListener('click', (e) => { e.preventDefault(); if (pre.style.display === 'block') { pre.style.display = 'none'; toggleLink.textContent = '[展開]'; } else { pre.style.display = 'block'; toggleLink.textContent = '[收起]'; } }); // 顯示檢查詳情 const detailsDiv = document.createElement('div'); detailsDiv.className = 'grok-ai-safety-details'; detailsDiv.innerHTML = ` <div>${t('checkDetails')}</div> <div>${t('matchDomains', { domains: matchDomains.join(', ') || '無' })}</div> <div>${t('redirectTargets', { targets: politicalIssues.map(issue => issue.split(' -> ')[1]).join(', ') || '無' })}</div> <div>${t('politicalIssues', { issues: politicalIssues.join('; ') || '無' })}</div> `; notice.appendChild(detailsDiv); // 顯示最終警告 const warningDiv = document.createElement('div'); warningDiv.textContent = warning; notice.appendChild(warningDiv); installButton.parentNode.insertBefore(notice, installButton); console.log(t('insertedNotice')); }).catch((error) => { console.error(t('fetchCodeFailed', { error })); const notice = document.createElement('div'); notice.className = 'grok-ai-safety-notice'; notice.textContent = t('fetchCodeFailed', { error }); installButton.parentNode.insertBefore(notice, installButton); }); }).catch((error) => { console.error(t('installButtonNotFound'), error); }); })();