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