Greasy Fork is available in English.

GF Code Scanner [Local + Ai]

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

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

You will need to install an extension such as Tampermonkey to install this script.

Tendrás que instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Tendrás que instalar una extensión como Tampermonkey antes de poder instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

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