GF Code Scanner [Local + Ai]

Проверка кода скриптов на GreasyFork на безопасность (Локальная + ИИ на русском )

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;") : '';

    // Поиск строк
    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);
})();