GreasyFork AI Safety Checker

在 GreasyFork 主頁面顯示 AI 安全檢查提示,分析腳本的域名和權限,幫助用戶識別潛在風險,支援多國語系。免責聲明:本腳本由 xAI 的 Grok 開發,旨在提供靜態檢查參考,不保證完全準確,可能存在誤報或漏報。使用者應自行驗證並承擔風險,有問題請留言。本聲明最終解釋權歸公布者所有。

  1. // ==UserScript==
  2. // @name GreasyFork AI Safety Checker
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.1.4
  5. // @description 在 GreasyFork 主頁面顯示 AI 安全檢查提示,分析腳本的域名和權限,幫助用戶識別潛在風險,支援多國語系。免責聲明:本腳本由 xAI 的 Grok 開發,旨在提供靜態檢查參考,不保證完全準確,可能存在誤報或漏報。使用者應自行驗證並承擔風險,有問題請留言。本聲明最終解釋權歸公布者所有。
  6. // @match https://gf.qytechs.cn/*scripts/*
  7. // @exclude https://gf.qytechs.cn/*scripts/*/code*
  8. // @grant GM_xmlhttpRequest
  9. // @grant GM_addStyle
  10. // @grant GM_setValue
  11. // @grant GM_getValue
  12. // @grant GM_registerMenuCommand
  13. // @grant GM_info
  14. // @license MIT
  15. // ==/UserScript==
  16.  
  17. (function() {
  18. 'use strict';
  19.  
  20. let userLang = GM_getValue('userSelectedLanguage', '');
  21. if (!userLang) {
  22. userLang = navigator.language === 'zh-TW' ? 'zh-TW' :
  23. navigator.language.startsWith('zh') ? 'zh-CN' :
  24. navigator.language.startsWith('ja') ? 'ja' : 'en';
  25. }
  26.  
  27. const translations = {
  28. 'zh-TW': {
  29. safetyNotice: 'AI 安全提示:${grant} - 靜態檢查: 檢查 ${count} 個域名,檢測到 ${riskCount} 個高風險域名${apiSummary}${politicalWarning}, 建議在沙盒環境中測試此腳本。',
  30. safetyNoticeNoRisk: 'AI 安全提示:${grant} - 靜態檢查: 檢查 ${count} 個域名,未發現匹配${apiSummary}${politicalWarning}.',
  31. staticRiskNotice: 'AI 安全提示:靜態檢查發現風險域名 - 檢查 ${count} 個域名,檢測到 ${riskCount} 個高風險域名${apiSummary}${politicalWarning}, 建議在沙盒環境中測試此腳本。',
  32. noHighRiskGrant: '未檢測到高風險權限',
  33. fetchCodeFailed: 'AI 安全提示:無法獲取腳本代碼,請手動檢查。',
  34. viewDetails: '查看詳情',
  35. hideDetails: '收起詳情',
  36. gamblingRelated: '可能與博彩相關',
  37. ptcSite: 'PTC(點擊賺錢)網站',
  38. urlShortening: '縮短連結服務',
  39. fileSharing: '文件分享網站',
  40. highRiskTld: '高風險 TLD',
  41. GM_xmlhttpRequest: 'GM 跨站請求',
  42. GM_setValue: 'GM 設置值',
  43. unsafeWindow: '不安全窗口',
  44. GM_openInTab: 'GM 新標籤開啟'
  45. },
  46. 'zh-CN': {
  47. safetyNotice: 'AI 安全提示:${grant} - 静态检查: 检查 ${count} 个域名,检测到 ${riskCount} 个高风险域名${apiSummary}${politicalWarning}, 建议在沙盒环境中测试此脚本。',
  48. safetyNoticeNoRisk: 'AI 安全提示:${grant} - 静态检查: 检查 ${count} 个域名,未发现匹配${apiSummary}${politicalWarning}.',
  49. staticRiskNotice: 'AI 安全提示:静态检查发现风险域名 - 检查 ${count} 个域名,检测到 ${riskCount} 个高风险域名${apiSummary}${politicalWarning}, 建议在沙盒环境中测试此脚本。',
  50. noHighRiskGrant: '未检测到高风险权限',
  51. fetchCodeFailed: 'AI 安全提示:无法获取脚本代码,请手动检查。',
  52. viewDetails: '查看详情',
  53. hideDetails: '收起详情',
  54. gamblingRelated: '可能与博彩相关',
  55. ptcSite: 'PTC(点击赚钱)网站',
  56. urlShortening: '缩短链接服务',
  57. fileSharing: '文件分享网站',
  58. highRiskTld: '高风险 TLD',
  59. GM_xmlhttpRequest: 'GM 跨站请求',
  60. GM_setValue: 'GM 设置值',
  61. unsafeWindow: '不安全窗口',
  62. GM_openInTab: 'GM 新标签开启'
  63. },
  64. 'en': {
  65. safetyNotice: 'AI Safety Notice: ${grant} - Static check: checked ${count} domains, detected ${riskCount} high-risk domains${apiSummary}${politicalWarning}, test in a sandbox environment.',
  66. safetyNoticeNoRisk: 'AI Safety Notice: ${grant} - Static check: checked ${count} domains, no matches${apiSummary}${politicalWarning}.',
  67. staticRiskNotice: 'AI Safety Notice: Static check detected risks - checked ${count} domains, detected ${riskCount} high-risk domains${apiSummary}${politicalWarning}, test in a sandbox environment.',
  68. noHighRiskGrant: 'No high-risk permissions detected',
  69. fetchCodeFailed: 'AI Safety Notice: Unable to fetch script code, please check manually.',
  70. viewDetails: 'View Details',
  71. hideDetails: 'Hide Details',
  72. gamblingRelated: 'Possibly gambling-related',
  73. ptcSite: 'PTC (Pay-to-Click) site',
  74. urlShortening: 'URL shortening service',
  75. fileSharing: 'File sharing site',
  76. highRiskTld: 'High-risk TLD',
  77. GM_xmlhttpRequest: 'GM_xmlhttpRequest',
  78. GM_setValue: 'GM_setValue',
  79. unsafeWindow: 'unsafeWindow',
  80. GM_openInTab: 'GM_openInTab'
  81. },
  82. 'ja': {
  83. safetyNotice: 'AI安全通知:${grant} - 静的チェック:${count}ドメインをチェックし、${riskCount}個の高リスクドメインを検出しました${apiSummary}${politicalWarning}。サンドボックス環境でテストすることをお勧めします。',
  84. safetyNoticeNoRisk: 'AI安全通知:${grant} - 静的チェック:${count}ドメインをチェックしましたが、一致するものはありません${apiSummary}${politicalWarning}。',
  85. staticRiskNotice: 'AI安全通知:静的チェックでリスクを検出 - ${count}ドメインをチェックし、${riskCount}個の高リスクドメインを検出しました${apiSummary}${politicalWarning}。サンドボックス環境でテストすることをお勧めします。',
  86. noHighRiskGrant: '高リスク権限は検出されませんでした',
  87. fetchCodeFailed: 'AI安全通知:スクリプトコードを取得できません。手動で確認してください。',
  88. viewDetails: '詳細を表示',
  89. hideDetails: '詳細を非表示',
  90. gamblingRelated: 'ギャンブル関連の可能性',
  91. ptcSite: 'PTC(クリックで稼ぐ)サイト',
  92. urlShortening: 'URL短縮サービス',
  93. fileSharing: 'ファイル共有サイト',
  94. highRiskTld: '高リスクTLD',
  95. GM_xmlhttpRequest: 'GM_xmlhttpRequest',
  96. GM_setValue: 'GM_setValue',
  97. unsafeWindow: 'unsafeWindow',
  98. GM_openInTab: 'GM_openInTab'
  99. }
  100. };
  101.  
  102. function t(key, params = {}) {
  103. let text = translations[userLang][key] || translations['en'][key];
  104. for (const [param, value] of Object.entries(params)) {
  105. text = text.replace(`\${${param}}`, value);
  106. }
  107. return text;
  108. }
  109.  
  110. GM_registerMenuCommand('切換語言 / Switch Language', () => {
  111. const languages = ['zh-TW', 'zh-CN', 'en', 'ja'];
  112. const currentIndex = languages.indexOf(userLang);
  113. const nextIndex = (currentIndex + 1) % languages.length;
  114. userLang = languages[nextIndex];
  115. GM_setValue('userSelectedLanguage', userLang);
  116. alert('語言已切換 / Language switched to: ' + userLang);
  117. location.reload();
  118. });
  119.  
  120. if (/\/code/.test(window.location.href)) return;
  121.  
  122. const fetchScriptCode = () => {
  123. const scriptId = window.location.pathname.match(/scripts\/(\d+)/)?.[1];
  124. if (!scriptId) throw new Error('無法提取腳本 ID');
  125. const codeUrl = `${window.location.origin}${window.location.pathname}/code`;
  126. return new Promise((resolve, reject) => {
  127. GM_xmlhttpRequest({
  128. method: 'GET', url: codeUrl, onload: (response) => {
  129. const parser = new DOMParser();
  130. const doc = parser.parseFromString(response.responseText, 'text/html');
  131. const code = doc.querySelector('pre')?.textContent;
  132. if (code) resolve(code);
  133. else reject('未找到代碼');
  134. }, onerror: () => reject('獲取代碼失敗')
  135. });
  136. });
  137. };
  138.  
  139. const analyzeScript = async (code) => {
  140. const lines = code.split('\n');
  141. const matches = lines
  142. .filter(line => line.trim().startsWith('// @match'))
  143. .map(line => line.replace('// @match', '').trim().replace(/^\*?:\/\//, '').replace(/\/.*/, ''))
  144. .filter(url => url && !url.includes('gf.qytechs.cn'));
  145.  
  146. const highRiskDomains = [];
  147. const riskReasons = {};
  148. const knownSafe = ['google.com', 'youtube.com', 'facebook.com', 'instagram.com', 'twitter.com', 'wikipedia.org'];
  149. const riskyPatterns = [
  150. /vn88\..*/, /fb88\..*/, /m88\..*/, /bet88li\.com/, /yeumoney\.com/, /165\.22\.63\.250/, /188\.166\.185\.213/,
  151. /aylink\.co/, /gplinks\.co/, /v2links\.me/, /coinclix\.co/, /cutyion\.com/, /upfion\.com/,
  152. /modsfire\.com/, /dropbox\.com/, /drive\.google\.com/, /mega\.nz/,
  153. /ourcoincash\.xyz/, /bitcotasks\.com/, /freepayz\.com/, /gwaher\.com/, /geekgrove\.net/, /cricketlegacy\.com/,
  154. /.*\.techyuth\.xyz/, /.*\.idblogmarket\.com/, /.*\.phonesparrow\.com/, /.*\.wikijankari\.com/,
  155. /financewada\.com/, /financenova\.online/, /utkarshonlinetest\.com/, /rajasthantopnews\.com/,
  156. /.*\.devnote\.in/, /naamlist\.com/, /modijiurl\.com/, /gmsrweb\.org/
  157. ];
  158.  
  159. let staticCheckedCount = 0;
  160. matches.forEach(domain => {
  161. staticCheckedCount++;
  162. if (knownSafe.some(safe => domain.includes(safe))) return;
  163. let reason = '未知風險';
  164. if (riskyPatterns.some(pattern => pattern.test(domain))) {
  165. if (/vn88\..*|fb88\..*|m88\..*|bet88li\.com|yeumoney\.com/.test(domain)) reason = t('gamblingRelated');
  166. else if (/aylink\.co|gplinks\.co|v2links\.me|coinclix\.co|cutyion\.com|upfion\.com/.test(domain)) reason = t('urlShortening');
  167. else if (/modsfire\.com|dropbox\.com|drive\.google\.com|mega\.nz/.test(domain)) reason = t('fileSharing');
  168. else if (/ourcoincash\.xyz|bitcotasks\.com|freepayz\.com|gwaher\.com|geekgrove\.net|cricketlegacy\.com/.test(domain)) reason = t('ptcSite');
  169. else if (/xyz|in|online|wtf/.test(domain)) reason = t('highRiskTld');
  170. highRiskDomains.push(domain);
  171. riskReasons[domain] = reason;
  172. }
  173. });
  174.  
  175. const highRiskGrants = ['GM_xmlhttpRequest', 'unsafeWindow', 'GM_setValue', 'GM_openInTab'];
  176. let firstRiskyGrant = '';
  177. for (let i = 0; i < lines.length; i++) {
  178. const line = lines[i].trim();
  179. for (const grant of highRiskGrants) {
  180. if (line.match(new RegExp(`@grant\\s+${grant}`))) {
  181. firstRiskyGrant = `${t(grant)} (行 ${i + 1})`;
  182. break;
  183. }
  184. }
  185. if (firstRiskyGrant) break;
  186. }
  187. if (!firstRiskyGrant) firstRiskyGrant = t('noHighRiskGrant');
  188.  
  189. let warning;
  190. const details = highRiskDomains.length > 0
  191. ? `<div class="grok-ai-safety-details" style="display: none;">${highRiskDomains.map(d => `${d}: ${riskReasons[d]}`).join('<br>')}</div>`
  192. : '';
  193.  
  194. if (highRiskDomains.length > 0) {
  195. warning = firstRiskyGrant === t('noHighRiskGrant')
  196. ? `${t('staticRiskNotice', { count: staticCheckedCount, riskCount: highRiskDomains.length, apiSummary: '', politicalWarning: '' })}<a id="toggle-details" href="#">${t('viewDetails')}</a>${details}`
  197. : `${t('safetyNotice', { grant: firstRiskyGrant, count: staticCheckedCount, riskCount: highRiskDomains.length, apiSummary: '', politicalWarning: '' })}<a id="toggle-details" href="#">${t('viewDetails')}</a>${details}`;
  198. } else {
  199. warning = t('safetyNoticeNoRisk', { grant: firstRiskyGrant, count: staticCheckedCount, apiSummary: '', politicalWarning: '' });
  200. }
  201.  
  202. return { warning, highRiskDomains, riskReasons };
  203. };
  204.  
  205. const waitForElement = (selector) => new Promise((resolve) => {
  206. const element = document.querySelector(selector);
  207. if (element) return resolve(element);
  208. const observer = new MutationObserver(() => {
  209. const el = document.querySelector(selector);
  210. if (el) {
  211. observer.disconnect();
  212. resolve(el);
  213. }
  214. });
  215. observer.observe(document.body, { childList: true, subtree: true });
  216. });
  217.  
  218. GM_addStyle(`
  219. .grok-ai-safety-notice { background: #fff3f3; border: 2px solid #ff4d4d; padding: 10px; margin-bottom: 15px; border-radius: 5px; color: #333; }
  220. .grok-ai-safety-details { margin-top: 10px; padding: 5px; background: #ffe6e6; border: 1px solid #ff9999; border-radius: 3px; }
  221. `);
  222.  
  223. waitForElement('.install-link').then((installButton) => {
  224. fetchScriptCode().then(async (code) => {
  225. const { warning, highRiskDomains } = await analyzeScript(code);
  226. const notice = document.createElement('div');
  227. notice.className = 'grok-ai-safety-notice';
  228. notice.innerHTML = warning;
  229.  
  230. if (highRiskDomains.length > 0) {
  231. const toggleLink = notice.querySelector('#toggle-details');
  232. const detailsDiv = notice.querySelector('.grok-ai-safety-details');
  233. toggleLink.addEventListener('click', (e) => {
  234. e.preventDefault();
  235. if (detailsDiv.style.display === 'block') {
  236. detailsDiv.style.display = 'none';
  237. toggleLink.textContent = t('viewDetails');
  238. } else {
  239. detailsDiv.style.display = 'block';
  240. toggleLink.textContent = t('hideDetails');
  241. }
  242. });
  243. }
  244.  
  245. installButton.parentNode.insertBefore(notice, installButton);
  246. console.log('提示已插入 / Notice inserted');
  247. }).catch(() => {
  248. const notice = document.createElement('div');
  249. notice.className = 'grok-ai-safety-notice';
  250. notice.textContent = t('fetchCodeFailed');
  251. installButton.parentNode.insertBefore(notice, installButton);
  252. });
  253. });
  254. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址