Проверка кода скриптов на GreasyFork на безопасность (Локальная + ИИ на русском )
// ==UserScript==
// @name GF Code Scanner [Local + Ai]
// @name:en GF Code Scanner [Local + Ai]
// @namespace http://tampermonkey.net/
// @version 1.0.3
// @description Проверка кода скриптов на GreasyFork на безопасность (Локальная + ИИ на русском )
// @description:en Security check for GreasyFork scripts (Local scan + AI in Russian)
// @match https://greasyfork.org/*/scripts/*/code*
// @match https://greasyfork.org/*/scripts/*
// @grant GM_xmlhttpRequest
// @grant GM_getValue
// @grant GM_setValue
// @connect api.groq.com
// @connect api.deepseek.com
// @connect dashscope.aliyuncs.com
// @connect api.openai.com
// @connect api.x.ai
// @connect generativelanguage.googleapis.com
// @require https://update.greasyfork.org/scripts/34138/223779/markedjs.js
// @run-at document-end
// @license MIT
// ==/UserScript==
(function() {
'use strict';
console.log('[GF-Scanner-v1.0.3] Start');
// 1. КОНФИГ МОДЕЛЕЙ
const MODELS = {
groq: { n:'Groq', u:'https://api.groq.com/openai/v1/chat/completions', m:'llama-3.3-70b-versatile' },
deepseek: { n:'DeepSeek', u:'https://api.deepseek.com/chat/completions', m:'deepseek-chat' },
gemini: { n:'Gemini', u:'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent', m:'gemini-2.5-flash', g:true },
grok: { n:'Grok', u:'https://api.x.ai/v1/chat/completions', m:'grok-beta' },
qwen: { n:'Qwen', u:'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions', m:'qwen-turbo' },
openai: { n:'ChatGPT', u:'https://api.openai.com/v1/chat/completions', m:'gpt-3.5-turbo' }
};
let K = GM_getValue('k', {});
let S = GM_getValue('s', 'groq');
// 2. ПОЛНЫЙ СПИСОК ПРАВИЛ (ВОЗВРАЩЕНО ВСЁ)
const R = [
{ p:/\beval\s*\(/g, v:4, t:'eval()', d:'Выполнение кода' },
{ p:/\bnew\s+Function\s*\(/g, v:4, t:'new F()', d:'Динамика' },
{ p:/coinhive|cryptonight|monero|minero\.js/i, v:4, t:'Miner', d:'Криптомайнинг' },
{ p:/\batob\s*\(/g, v:3, t:'atob()', d:'Скрытый Base64' },
{ p:/_\x04|_0x[a-f0-9]{4,}/i, v:3, t:'Obfus', d:'Запутанный код' },
{ p:/String\.fromCharCode/i, v:3, t:'CharCode', d:'Обфускация' },
{ p:/\bnavigator\.geolocation\b/g, v:3, t:'Geo', d:'GPS' },
{ p:/\bnavigator\.mediaDevices\b/g, v:3, t:'Cam/Mic', d:'Камера/Микрофон' },
{ p:/\bGM_xmlhttpRequest\b/g, v:3, t:'GM_XHR', d:'Скрытые запросы' },
{ p:/\bdocument\.cookie\b/g, v:2, t:'Cookies', d:'Доступ к куки' },
{ p:/\blocalStorage\b/g, v:2, t:'Local', d:'Хранение данных' },
{ p:/\bsessionStorage\b/g, v:2, t:'Session', d:'Врем. хранение' },
{ p:/\bnavigator\.clipboard\b/g, v:2, t:'Clipboard', d:'Буфер обмена' },
{ p:/\bGM_getValue\b|\bGM_setValue\b/g, v:2, t:'GM_Store', d:'Хранилище TM' },
{ p:/\bfetch\s*\(/g, v:1, t:'Fetch', d:'Сетевой запрос' },
{ p:/\bXMLHttpRequest\b/g, v:1, t:'XHR', d:'Сетевой запрос' },
{ p:/\bdocument\.write\s*\(/g, v:2, t:'DocWrite', d:'Перезапись страницы' }
];
const U = /https?:\/\/[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_+.~#?&/=]*)/gi;
const getL = v => ({0:0,1:1,2:2,3:3,4:4}[v]||0);
const E = t => t ? t.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""") : '';
// Поиск строк
const FL = (c, p) => {
let r=[], x=new RegExp(p.source,p.flags);
c.split('\n').forEach((l,i)=>{ if(x.test(l)) r.push('Str '+(i+1)+': '+l.trim().substr(0,70)); x.lastIndex=0; });
return r.slice(0,5);
};
// Поиск URL
const GU = c => {
let u=[...new Set(c.match(U)||[])], d=new Set();
u.forEach(x=>{ try{d.add(new URL(x).hostname)}catch(e){} });
return {u,d:[...d]};
};
const scan = () => {
let old=document.getElementById('gfs'); if(old) old.remove();
let blk=document.querySelector('pre code')||document.querySelector('pre');
if(!blk) return;
let code=blk.innerText, f=[], max=0;
// Запуск полного сканирования
R.forEach(r=>{
let m=code.match(new RegExp(r.p.source,r.p.flags));
if(m){ f.push({...r,c:m.length,l:FL(code,r.p)}); if(r.v>max) max=r.v; }
});
let net=GU(code);
let box=document.createElement('div'); box.id='gfs';
box.style.cssText='background:#222;color:#fff;border:2px solid #0f0;padding:15px;margin:10px 0;font-family:sans-serif;border-radius:8px;';
let col='#0f0', txt='Safe';
if(max===1){col='#ff0';txt='Low Risk';}
if(max===2){col='#fa0';txt='Medium Risk';}
if(max>=3){col='#f00';txt='DANGER';}
box.style.borderColor=col;
// --- ПОЛНЫЙ ОТЧЕТ ---
let h='<h3 style="margin:0;color:'+col+'">Report: '+txt+'</h3>';
h+='<p>Issues: '+f.length+' | Domains: '+net.d.length+'</p>';
// Домены
if(net.d.length) {
h+='<div style="background:#333;padding:8px;margin:5px 0;border-radius:4px;">';
h+='<b style="color:#4fc3f7">Domains:</b> '+net.d.join(', ')+'</div>';
if(net.u.length<=15) {
h+='<details style="font-size:11px;color:#aaa;"><summary>Show URLs</summary><div style="word-break:break-all;">'+net.u.map(E).join('<br>')+'</div></details>';
}
}
// Угрозы (ПОДРОБНО)
if(f.length) {
f.forEach(x=>{
let c=x.v===4?'#f00':'#fa0';
h+='<div style="border-left:3px solid '+c+';padding:5px;margin:5px 0;background:#333;">';
h+='<b style="color:'+c+'">'+x.t+'</b>: '+x.d+' ('+x.c+')';
if(x.l.length) {
h+='<details><summary style="cursor:pointer;font-size:10px;color:#aaa;">Show Lines</summary>';
h+='<div style="background:#111;padding:4px;font-family:monospace;font-size:9px;color:#aaa;">'+x.l.join('<br>')+'</div></details>';
}
h+='</div>';
});
} else if (!net.d.length) {
h+='<p style="color:#aaa">No threats found.</p>';
}
// Кнопки и выбор модели
h+='<div style="margin-top:15px;display:flex;gap:10px;flex-wrap:wrap;">';
h+='<button id="gfb-local" style="flex:1;padding:8px;background:#2196f3;color:#fff;border:none;border-radius:4px;cursor:pointer;font-weight:bold;">Retry Local</button>';
h+='<button id="gfb-ai" style="flex:1;padding:8px;background:linear-gradient(45deg,#667eea,#764ba2);color:#fff;border:none;border-radius:4px;cursor:pointer;font-weight:bold;">Check AI</button>';
h+='</div>';
let opts='';
Object.keys(MODELS).forEach(k=>{ opts+='<option value="'+k+'" '+(k===S?'selected':'')+'>'+MODELS[k].n+'</option>'; });
h+='<select id="gfs-sel" style="margin-top:5px;background:#333;color:#fff;border:1px solid #555;padding:4px;border-radius:4px;">'+opts+'</select>';
h+='<div id="gfr" style="margin-top:10px;display:none;background:#111;padding:10px;border:1px solid #444;"></div>';
box.innerHTML=h;
let tgt=document.querySelector('pre');
if(tgt) tgt.parentNode.insertBefore(box,tgt); else document.body.prepend(box);
// Логика
document.getElementById('gfs-sel').onchange=e=>{ S=e.target.value; GM_setValue('s',S); };
document.getElementById('gfb-local').onclick=()=>scan();
document.getElementById('gfb-ai').onclick=async ()=>{
let btn=document.getElementById('gfb-ai'), res=document.getElementById('gfr');
let key=K[S];
// Запрос ключа
if(!key){
let g={groq:'https://console.groq.com/keys',deepseek:'https://platform.deepseek.com/api_keys',gemini:'https://aistudio.google.com/app/apikey',grok:'https://console.x.ai/',openai:'https://platform.openai.com/api-keys',qwen:'https://dashscope.console.aliyun.com/apiKey'};
key=prompt('Enter Key for '+MODELS[S].n+' ('+g[S]+'):', '');
if(key&&key.length>5){ K[S]=key; GM_setValue('k',K); } else { alert('No key'); return; }
}
btn.disabled=true; btn.innerText='Thinking...';
res.style.display='block'; res.innerHTML='Sending data to '+MODELS[S].n+'...';
let cfg=MODELS[S];
// Формирование данных для ИИ
let localData = f.length ? 'LOCAL SCAN FOUND:\n' + f.map(x=>'- '+x.t+': '+x.d+' (Lines: '+x.l.join('; ')+')').join('\n') : 'Local scan found nothing obvious.';
let domainData = net.d.length ? 'DOMAINS: '+net.d.join(', ') : 'No external domains.';
// ПРОМПТ НА РУССКОМ С ЖЕСТКИМ ТРЕБОВАНИЕМ
let pr = 'YOU ARE A JS SECURITY EXPERT. ANSWER STRICTLY IN RUSSIAN LANGUAGE.\n\n';
pr += localData + '\n' + domainData + '\n\nFULL CODE:\n' + code.substr(0,15000);
pr += '\n\nTASK:\n1. Verify each local threat (Confirm/False Positive).\n2. Find hidden threats.\n3. Verdict (SAFE/DANGEROUS).\n4. Answer in RUSSIAN ONLY.';
let body, headers={'Content-Type':'application/json'};
// Исправленная логика запросов для разных провайдеров
if(cfg.g){
// Gemini
body = JSON.stringify({contents:[{parts:[{text:pr}]}]});
if(cfg.u.includes('?')) {
// Если ключ уже в URL (редко), но обычно для Gemini ключ в query param
} else {
// Для Gemini ключ часто в URL
}
} else {
// OpenAI, DeepSeek, Groq, Grok - стандартный формат
body = JSON.stringify({
model: cfg.m,
messages: [
{role:'system', content:'You are a security expert. Answer strictly in Russian.'},
{role:'user', content:pr}
],
temperature: 0.2
});
headers['Authorization'] = 'Bearer '+key;
}
let url = cfg.u;
if(cfg.g && cfg.u.indexOf('?')===-1) url += '?key='+key;
GM_xmlhttpRequest({
method:'POST',
url:url,
headers:headers,
data:body,
onload:r=>{
try{
let j=JSON.parse(r.responseText);
let ans='';
if(cfg.g){
// Gemini ответ
if(j.candidates && j.candidates[0] && j.candidates[0].content && j.candidates[0].content.parts) {
ans = j.candidates[0].content.parts[0].text;
} else if(j.error) { ans='Error: '+j.error.message; }
} else {
// OpenAI/DeepSeek/Groq ответ
if(j.choices && j.choices[0] && j.choices[0].message) {
ans = j.choices[0].message.content;
} else if(j.error) { ans='Error: '+j.error.message; }
}
if(!ans) ans = 'Empty response from AI.';
// Рендеринг
res.innerHTML = typeof marked!=='undefined' ? marked.parse(ans) : ans.replace(/\n/g,'<br>');
}catch(e){
res.innerHTML='Parse Error: '+e.message+'. Response: '+r.responseText.substr(0,200);
}
btn.disabled=false; btn.innerText='Retry AI';
},
onerror:()=>{
res.innerHTML='Network Error. Check console (F12).';
btn.disabled=false; btn.innerText='Error';
}
});
};
};
setTimeout(scan, 800);
})();