您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在 GreasyFork 主頁面顯示 AI 安全檢查提示,支援多語言與網域檢查,可選啟用政治檢查。
当前为
// ==UserScript== // @name GreasyFork AI Safety Checker // @namespace http://tampermonkey.net/ // @version 2.0.5 // @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== (function() { 'use strict'; // Tampermonkey 版本檢查 const MINIMUM_TAMPERMONKEY_VERSION = '5.0.0'; if (GM_info.version < MINIMUM_TAMPERMONKEY_VERSION) { console.warn(`Warning: Tampermonkey ${GM_info.version} is outdated, recommended: ${MINIMUM_TAMPERMONKEY_VERSION}`); } // 語言設定與切換 let userLang = GM_getValue('userSelectedLanguage', navigator.language.startsWith('zh') ? 'zh-CN' : 'en'); const translations = { 'zh-TW': { safetyNotice: 'AI 安全提示:${grant} - 靜態檢查: 檢查 ${total} 個域名,檢測到 ${risk} 個高風險域名${political}, 建議在沙盒環境中測試此腳本。', safetyNoticeNoRisk: 'AI 安全提示:${grant} - 靜態檢查: 檢查 ${total} 個域名,未發現高風險域名${political}。', fetchFailed: '抓取失敗:${error}', switchLanguage: '切換語言', enablePoliticalCheck: '啟用政治正確檢查', disablePoliticalCheck: '禁用政治正確檢查', viewDetails: '查看詳情', hideDetails: '收起詳情', politicalWarning: '警告:從 ${sensitive} 轉向 ${china}', matchDomains: '提取的 @match 域名:${domains}', details: '檢查詳情' }, 'zh-CN': { safetyNotice: 'AI 安全提示:${grant} - 静态检查: 检查 ${total} 个域名,检测到 ${risk} 个高风险域名${political}, 建议在沙盒环境中测试此脚本。', safetyNoticeNoRisk: 'AI 安全提示:${grant} - 静态检查: 检查 ${total} 个域名,未发现高风险域名${political}。', fetchFailed: '抓取失败:${error}', switchLanguage: '切换语言', enablePoliticalCheck: '启用政治正确检查', disablePoliticalCheck: '禁用政治正确检查', viewDetails: '查看详情', hideDetails: '收起详情', politicalWarning: '警告:从 ${sensitive} 转向 ${china}', matchDomains: '提取的 @match 域名:${domains}', details: '检查详情' }, 'en': { safetyNotice: 'AI Safety Notice: ${grant} - Static check: Checked ${total} domains, detected ${risk} high-risk domains${political}, test in a sandbox environment.', safetyNoticeNoRisk: 'AI Safety Notice: ${grant} - Static check: Checked ${total} domains, no high-risk domains found${political}.', fetchFailed: 'Fetch failed: ${error}', switchLanguage: 'Switch Language', enablePoliticalCheck: 'Enable Political Check', disablePoliticalCheck: 'Disable Political Check', viewDetails: 'View Details', hideDetails: 'Hide Details', politicalWarning: 'Warning: Redirect from ${sensitive} to ${china}', matchDomains: 'Extracted @match domains: ${domains}', details: 'Check Details' }, 'ja': { safetyNotice: 'AI安全通知:${grant} - 静的チェック:${total}ドメインをチェックし、${risk}個の高リスクドメインを検出しました${political}。サンドボックス環境でテストすることをお勧めします。', safetyNoticeNoRisk: 'AI安全通知:${grant} - 静的チェック:${total}ドメインをチェックしましたが、高リスクドメインは見つかりませんでした${political}。', fetchFailed: '取得失敗:${error}', switchLanguage: '言語を切り替え', enablePoliticalCheck: '政治的チェックを有効にする', disablePoliticalCheck: '政治的チェックを無効にする', viewDetails: '詳細を表示', hideDetails: '詳細を非表示', politicalWarning: '警告:${sensitive}から${china}へのリダイレクト', matchDomains: '抽出された @match ドメイン:${domains}', details: 'チェック詳細' } }; function t(key, params = {}) { let text = translations[userLang][key] || translations['en'][key]; for (const [p, v] of Object.entries(params)) text = text.replace(`\${${p}}`, v); return text; } // 語言切換選單 GM_registerMenuCommand(t('switchLanguage'), () => { const languages = ['zh-TW', 'zh-CN', 'en', 'ja']; userLang = languages[(languages.indexOf(userLang) + 1) % languages.length]; GM_setValue('userSelectedLanguage', userLang); alert(t('switchLanguage') + ': ' + userLang); location.reload(); }); // 政治檢查選單 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_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; } .grok-ai-safety-notice a { color: #ff4d4d; text-decoration: underline; cursor: pointer; margin-left: 10px; } .grok-ai-safety-details { display: none; margin-top: 10px; padding: 5px; background: #ffe6e6; border: 1px solid #ff9999; border-radius: 3px; } `); // 抓取代碼 const fetchScriptCode = () => { const codeUrl = `${window.location.origin}${window.location.pathname}/code`; return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: codeUrl, onload: (res) => { const code = new DOMParser().parseFromString(res.responseText, 'text/html').querySelector('pre')?.textContent; code ? resolve(code) : reject('No code found'); }, onerror: () => reject('Request failed') }); }); }; // 政治正確檢查 const checkPoliticalRedirection = (code) => { if (!politicalCheckEnabled) return ''; const sensitiveSites = ['twitter.com', 'youtube.com', 'facebook.com']; const chinaSites = ['bilibili.com', 'weibo.com', 'baidu.com']; const redirectPatterns = [/window\.location\.href\s*=\s*['"]([^'"]+)['"]/]; const lines = code.split('\n'); let issues = []; for (const line of lines) { for (const pattern of redirectPatterns) { const match = line.match(pattern); if (match) { const target = match[1].toLowerCase(); if (sensitiveSites.some(s => line.includes(s)) && chinaSites.some(c => target.includes(c))) { issues.push(t('politicalWarning', { sensitive: sensitiveSites.find(s => line.includes(s)), china: target })); } } } } return issues.length ? `; ${issues.join('; ')}` : ''; }; // 分析腳本 const analyzeScript = async (code) => { const lines = code.split('\n'); const matches = lines .filter(l => l.startsWith('// @match')) .map(l => l.replace('// @match', '').trim().replace(/^\*?:\/\//, '').replace(/\/.*/, '')) .filter(d => d && !d.includes('greasyfork.org')); // 檢查高風險權限 const highRiskGrants = ['GM_xmlhttpRequest', 'unsafeWindow', 'GM_setValue', 'GM_openInTab']; let grant = 'No high-risk grant'; for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); for (const g of highRiskGrants) { if (line.includes(`@grant ${g}`)) { grant = `${g} (行 ${i + 1})`; break; } } if (grant !== 'No high-risk grant') break; } // 靜態檢查高風險域名 const riskyPatterns = [/bit\.ly/, /tinyurl\.com/, /\.cn$/, /\.ru$/]; const highRiskDomains = matches.filter(domain => riskyPatterns.some(pattern => pattern.test(domain))); const totalDomains = matches.length; const riskCount = highRiskDomains.length; // 政治檢查 const politicalResult = await checkPoliticalRedirection(code); // 生成警告訊息 const warning = riskCount > 0 ? t('safetyNotice', { grant, total: totalDomains, risk: riskCount, political: politicalResult }) : t('safetyNoticeNoRisk', { grant, total: totalDomains, political: politicalResult }); // 詳細資訊 const details = `<div class="grok-ai-safety-details">${t('details')}<br>${t('matchDomains', { domains: matches.join(', ') || 'None' })}<br>${politicalResult || 'No political issues'}</div>`; return { warning, details, hasRisk: riskCount > 0 }; }; // 主邏輯 const installButton = document.querySelector('.install-link'); if (!installButton) return console.error('Install button not found'); fetchScriptCode() .then(analyzeScript) .then(({ warning, details, hasRisk }) => { const notice = document.createElement('div'); notice.className = 'grok-ai-safety-notice'; notice.innerHTML = `${warning}${hasRisk ? `<a href="#" id="toggle-details">${t('viewDetails')}</a>` : ''}${details}`; // 展開/收起詳情 if (hasRisk) { 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); }) .catch(error => { const notice = document.createElement('div'); notice.className = 'grok-ai-safety-notice'; notice.textContent = t('fetchFailed', { error }); installButton.parentNode.insertBefore(notice, installButton); }); })();