// ==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);
});
});
})();