AI Multi-Collector Universal

Универсальный сборщик кода для DeepSeek, Gemini и ChatGPT

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey, Greasemonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

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

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

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

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्क्रिप्ट व्यवस्थापक एक्स्टेंशन इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्क्रिप्ट व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्टाईल व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

// ==UserScript==
// @name         AI Multi-Collector Universal
// @namespace    http://tampermonkey.net/
// @version      1.8
// @description  Универсальный сборщик кода для DeepSeek, Gemini и ChatGPT
// @author       LUMOOOX
// @license      MIT
// @match        https://chat.deepseek.com/*
// @match        https://gemini.google.com/*
// @match        https://chatgpt.com/*
// @icon         data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%233b82f6' viewBox='0 0 24 24'%3E%3Cpath d='M8 6L2 12L8 18L9.5 16.5L5 12L9.5 7.5L8 6ZM16 6L14.5 7.5L19 12L14.5 16.5L16 18L22 12L16 6Z'/%3E%3C/svg%3E
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @run-at       document-end
// @homepageURL  https://github.com/LUMOOOX/ai-multi-collector
// @supportURL   https://github.com/LUMOOOX/ai-multi-collector/issues
// ==/UserScript==

(function() {
    'use strict';

    if (window.__aiCollectorInstalled) return;
    window.__aiCollectorInstalled = true;

// ========== 1. НАСТРОЙКИ ЛОКАЛИЗАЦИИ ==========
    const LANG = (navigator.language || navigator.userLanguage || 'en').toLowerCase().startsWith('ru') ? 'ru' : 'en';

    const TEXTS = {
        en: {
            ready: 'Ready', select: 'Select', copy: 'Copy', reset: 'Reset',
            clickBot: 'Click on bot message', clickArea: 'Click on message area',
            botOnly: 'Bot replies only', selectFirst: 'Select message first',
            noCode: 'No code found', selected: 'Selected', resetMsg: 'Reset',
            copied: 'Copied', block: 'block', blocks: 'blocks', chars: 'chars'
        },
        ru: {
            ready: 'Готов', select: 'Выбрать', copy: 'Копировать', reset: 'Сброс',
            clickBot: 'Кликните по ответу бота', clickArea: 'Кликните по области сообщения',
            botOnly: 'Только ответы бота', selectFirst: 'Сначала выберите',
            noCode: 'Код не найден', selected: 'Выбрано', resetMsg: 'Сброшено',
            copied: 'Скопировано', block: 'блок', blocks: 'блоков', chars: 'симв.'
        }
    };
    const t = TEXTS[LANG];

// ========== 2. КОНФИГУРАЦИЯ ПЛАТФОРМ ==========
    const PLATFORMS = {
        deepseek: {
            name: 'DeepSeek',
            botSelectors: '.ds-markdown, .message-content[data-message-role="assistant"], [data-message-id]',
            sidebarWidth: 280, minTextLength: 60,
            roleAttribute: 'data-message-role', roleValue: 'assistant'
        },
        gemini: {
            name: 'Gemini',
            botSelectors: 'model-response, .message-content',
            sidebarWidth: 300, minTextLength: 60,
            roleAttribute: null, roleValue: null
        },
        chatgpt: {
            name: 'ChatGPT',
            botSelectors: '[data-message-author-role="assistant"], .markdown, .prose',
            sidebarWidth: 260, minTextLength: 60,
            roleAttribute: 'data-message-author-role', roleValue: 'assistant'
        }
    };

// ========== 3. ОПРЕДЕЛЕНИЕ ТЕКУЩЕЙ ПЛАТФОРМЫ ==========
    let currentPlatform = null;
    if (location.hostname.includes('chat.deepseek.com')) {
        currentPlatform = PLATFORMS.deepseek;
    } else if (location.hostname.includes('gemini.google.com')) {
        currentPlatform = PLATFORMS.gemini;
    } else if (location.hostname.includes('chat.openai.com') || location.hostname.includes('chatgpt.com')) {
        currentPlatform = PLATFORMS.chatgpt;
    } else {
        console.warn('[AI-Collector] Platform not supported');
        return;
    }

    const CONFIG = {
        botSelectors: currentPlatform.botSelectors,
        sidebarWidth: currentPlatform.sidebarWidth,
        minTextLength: currentPlatform.minTextLength,
        roleAttribute: currentPlatform.roleAttribute,
        roleValue: currentPlatform.roleValue
    };

    console.log(`[${currentPlatform.name}] Started (${LANG})`);

// ========== 4. CSS СТИЛИ ==========
    GM_addStyle(`
        #ai-collector-panel,
        #ai-collector-panel *,
        #ai-collector-panel .ai-btn,
        #ai-collector-panel .ai-title,
        #ai-collector-panel .ai-status {
            box-sizing: border-box !important;
            text-transform: none !important;
            letter-spacing: normal !important;
            line-height: 1.2 !important;
            text-decoration: none !important;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif !important;
        }

        #ai-collector-panel {
            position: fixed !important;
            top: 150px !important;
            right: 50px !important;
            z-index: 2147483647 !important;
            width: 110px !important;
            overflow: hidden !important;
            margin: 0 !important;
            padding: 0 !important;
            background: #1e1e2e !important;
            border-radius: 12px !important;
            font-size: 13px !important;
            border: 0px solid #3b82f6 !important;
            opacity: 0.1 !important;
            transition: opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
        }
        #ai-collector-panel:hover {
            opacity: 1 !important;
        }
        .ai-title-bar {
            background: #2a2a3a !important;
            padding: 8px 10px !important;
            border-bottom: 1px solid #3b82f6 !important;
            text-align: center !important;
        }
        .ai-title {
            font-size: 11px !important;
            font-weight: 600 !important;
            color: #cbd5e1 !important;
        }
        .ai-content {
            padding: 8px !important;
        }
        .ai-btn {
            display: block !important;
            width: 100% !important;
            height: 32px !important;
            padding: 0px 10px !important;
            margin: 6px 0 !important;
            border: none !important;
            border-radius: 8px !important;
            font-size: 12px !important;
            font-weight: 500 !important;
            line-height: 32px !important;
            cursor: pointer !important;
            color: white !important;
            text-align: center !important;
        }
        .ai-pick { background: #3b82f6 !important; }
        .ai-copy { background: #059669 !important; }
        .ai-reset { background: #6b7280 !important; }
        .ai-status {
            display: block !important;
            color: #94a3b8 !important;
            font-size: 11px !important;
            text-align: center !important;
            margin-top: 8px !important;
            padding-top: 8px !important;
            border-top: 1px solid #334155 !important;
        }
        .ai-selected {
            outline: 1px dashed #3b82f6 !important;
            outline-offset: 2px !important;
            background: rgba(59, 130, 246, 0.05) !important;
            border-radius: 8px !important;
            padding: 2px !important;
            margin: -2px !important;
        }
        .ai-selected .action-bar,
        .ai-selected [data-testid="action-bar"],
        .ai-selected .feedback-buttons,
        .ai-selected .copy-button,
        .ai-selected .regenerate-button {
            outline: none !important;
            background: transparent !important;
        }
        @media (prefers-color-scheme: light) {
            #ai-collector-panel {
                background: #ffffff !important;
                border: 1px solid #cbd5e1 !important;
            }
            .ai-title-bar {
                background: #f1f5f9 !important;
                border-bottom-color: #cbd5e1 !important;
            }
            .ai-title {
                color: #1e293b !important;
            }
            .ai-status {
                border-top-color: #e2e8f0 !important;
            }
        }
        .ai-hidden {
            display: none !important;
        }
    `);

// ========== 5. ПЕРЕМЕННЫЕ И ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ==========
    let selected = null;
    let picking = false;
    let panel = null;
    let timer = null;

    function setStatus(msg, color) {
        const el = document.querySelector('#ai-status');
        if (!el) return;
        if (timer) clearTimeout(timer);
        el.textContent = msg;
        el.style.color = color || '#94a3b8';
        timer = setTimeout(() => {
            if (el.textContent === msg) {
                el.textContent = t.ready;
                el.style.color = '#94a3b8';
            }
        }, 1500);
    }

    function isMainArea(x, y) {
        if (x < CONFIG.sidebarWidth) return false;
        if (y < 50) return false;
        return true;
    }

    function saveLast() {
        if (selected && selected.innerText) {
            const prefix = currentPlatform.name + '_';
            GM_setValue(prefix + 'lastMsg', selected.innerText.slice(0, 100));
        }
    }

// ========== 6. ОСНОВНЫЕ ФУНКЦИИ ==========
    function isBotMessage(element) {
        if (!element) return false;
        let msgElement = element.closest(CONFIG.botSelectors);
        if (!msgElement) return false;
        if (CONFIG.roleAttribute && CONFIG.roleValue) {
            const role = msgElement.getAttribute(CONFIG.roleAttribute);
            if (role === CONFIG.roleValue) return true;
        }
        if (msgElement.tagName === 'MODEL-RESPONSE') return true;
        const classes = msgElement.className || '';
        if (classes.includes('ds-markdown')) return true;
        const text = msgElement.innerText || '';
        if (text.length < 30) return false;
        const hasCode = msgElement.querySelector('pre, code');
        if (hasCode && text.length > 100) return true;
        if (text.length > 200) return true;
        return false;
    }

    function findBotMessage(element) {
        if (!element) return null;

        let botEl = element.closest(CONFIG.botSelectors);
        if (botEl && isBotMessage(botEl)) {
            if (currentPlatform.name === 'Gemini') {
                const textContainer = botEl.querySelector('.markdown, .prose, [data-message-content]');
                if (textContainer) return textContainer;
            }
            return botEl;
        }

        if (currentPlatform.name === 'Gemini') {
            let current = element;
            for (let i = 0; i < 5 && current && current !== document.body; i++) {
                if (isBotMessage(current)) {
                    const textContainer = current.querySelector('.markdown, .prose, [data-message-content]');
                    if (textContainer) return textContainer;
                    return current;
                }
                current = current.parentElement;
            }
        }

        return null;
    }

    function isValidMessage(el) {
        if (!el) return false;
        let len = (el.innerText || '').length;
        return len >= CONFIG.minTextLength && len < 100000;
    }

    function selectMessage(el) {
        if (selected) selected.classList.remove('ai-selected');
        selected = el;
        selected.classList.add('ai-selected');
        saveLast();
        setStatus(t.selected, '#10b981');
        picking = false;
    }

    function resetSelection() {
        if (selected) selected.classList.remove('ai-selected');
        selected = null;
        picking = false;
        setStatus(t.resetMsg, '#94a3b8');
        const prefix = currentPlatform.name + '_';
        GM_setValue(prefix + 'lastMsg', '');
    }

// ========== 7. ФУНКЦИЯ КОПИРОВАНИЯ ==========
    async function copyCode() {
        if (!selected) {
            setStatus(t.selectFirst, '#f59e0b');
            return;
        }

        let blocks = [];
        let seenSignatures = new Set();

        const STOP_WORDS = new Set([
            'javascript', 'python', 'java', 'c++', 'c#', 'c', 'go', 'rust',
            'ruby', 'php', 'html', 'css', 'sql', 'typescript', 'swift',
            'kotlin', 'scala', 'perl', 'shell', 'bash', 'powershell',
            'json', 'xml', 'yaml', 'markdown', 'txt', 'text'
        ]);

        function cleanBlock(text, platform) {
            if (platform !== 'ChatGPT') return text;

            let lines = text.split('\n');
            if (lines.length === 0) return text;

            let firstLine = lines[0].trim().toLowerCase();

            if (firstLine.startsWith('```') && firstLine.length > 3) {
                let lang = firstLine.substring(3).trim();
                if (STOP_WORDS.has(lang) || /^[a-z]+$/i.test(lang)) {
                    lines[0] = '```';
                    return lines.join('\n').trim();
                }
            }

            if (STOP_WORDS.has(firstLine)) {
                console.log(`[AI-Collector] Removed header: "${lines[0].trim()}"`);
                lines.shift();
                return lines.join('\n').trim();
            }
            return text;
        }

        function isTotallyJunk(text) {
            let trimmed = text.trim().toLowerCase();
            if (STOP_WORDS.has(trimmed)) return true;
            return false;
        }

        function getSignature(text) {
            return text.substring(0, 100).trim().replace(/\s+/g, ' ');
        }

        let allPres = selected.querySelectorAll('pre');
        console.log(`[AI-Collector] Pre elements found: ${allPres.length}`);

        let filteredCount = 0;
        let cleanedCount = 0;

        for (let p of allPres) {
            let rawTxt = p.innerText.trim();
            if (!rawTxt) continue;
            if (isTotallyJunk(rawTxt)) {
                filteredCount++;
                console.log(`[AI-Collector] Skipped junk block: "${rawTxt}"`);
                continue;
            }
            let cleanedTxt = cleanBlock(rawTxt, currentPlatform.name);
            if (cleanedTxt !== rawTxt) cleanedCount++;
            if (!cleanedTxt) continue;
            let signature = getSignature(cleanedTxt);
            if (!seenSignatures.has(signature)) {
                blocks.push(cleanedTxt);
                seenSignatures.add(signature);
                console.log(`[AI-Collector] Block added (${cleanedTxt.length} chars)`);
            }
        }

        if (blocks.length === 0 && currentPlatform.name !== 'ChatGPT') {
            let allCodes = selected.querySelectorAll('code');
            for (let c of allCodes) {
                if (c.closest('pre')) continue;
                let rawTxt = c.innerText.trim();
                if (!rawTxt) continue;
                if (isTotallyJunk(rawTxt)) continue;
                let cleanedTxt = cleanBlock(rawTxt, currentPlatform.name);
                if (!cleanedTxt) continue;
                let signature = getSignature(cleanedTxt);
                if (!seenSignatures.has(signature)) {
                    blocks.push(cleanedTxt);
                    seenSignatures.add(signature);
                }
            }
        }

        if (blocks.length === 0) {
            let matches = (selected.innerText || '').match(/```[\s\S]*?```/g) || [];
            for (let m of matches) {
                let clean = m.replace(/^```\w*\n/, '').replace(/\n```$/, '').trim();
                if (!clean) continue;
                if (isTotallyJunk(clean)) continue;
                let cleanedTxt = cleanBlock(clean, currentPlatform.name);
                if (!cleanedTxt) continue;
                let signature = getSignature(cleanedTxt);
                if (!seenSignatures.has(signature)) {
                    blocks.push(cleanedTxt);
                    seenSignatures.add(signature);
                }
            }
        }

        console.log(`[AI-Collector] Junk blocks filtered: ${filteredCount}`);
        console.log(`[AI-Collector] Headers cleaned: ${cleanedCount}`);
        console.log(`[AI-Collector] Total blocks: ${blocks.length}`);

        if (blocks.length === 0) {
            setStatus(t.noCode, '#ef4444');
            return;
        }

        let result = blocks.join('\n\n');
        const totalChars = result.length;

        let blockWord = blocks.length === 1 ? t.block : t.blocks;
        let statusMsg = `${t.copied}: ${blocks.length} ${blockWord} (${totalChars} ${t.chars})`;

        try {
            await navigator.clipboard.writeText(result);
            setStatus(statusMsg, '#10b981');
            console.log(`[AI-Collector] Platform: ${currentPlatform.name}`);
            console.log(`[AI-Collector] Blocks copied: ${blocks.length}`);
            console.log(`[AI-Collector] Total size: ${totalChars} chars`);
        } catch(e) {
            let ta = document.createElement('textarea');
            ta.value = result;
            ta.style.cssText = 'position:fixed;top:-1000px';
            document.body.appendChild(ta);
            ta.select();
            document.execCommand('copy');
            ta.remove();
            setStatus(statusMsg, '#10b981');
            console.log(`[AI-Collector] Platform: ${currentPlatform.name} (fallback)`);
            console.log(`[AI-Collector] Blocks copied: ${blocks.length}`);
            console.log(`[AI-Collector] Total size: ${totalChars} chars`);
        }
    }

// ========== 8. СОЗДАНИЕ UI ПАНЕЛИ ==========
    function createPanel() {
        if (document.getElementById('ai-collector-panel')) return document.getElementById('ai-collector-panel');
        let div = document.createElement('div');
        div.id = 'ai-collector-panel';

        let titleBar = document.createElement('div');
        titleBar.className = 'ai-title-bar';

        let title = document.createElement('span');
        title.className = 'ai-title';
        title.textContent = 'LUMOOOX';

        titleBar.appendChild(title);

        let content = document.createElement('div');
        content.className = 'ai-content';

        let btnPick = document.createElement('button');
        btnPick.textContent = t.select;
        btnPick.className = 'ai-btn ai-pick';
        btnPick.onclick = () => { picking = true; setStatus(t.clickBot, '#f59e0b'); };

        let btnCopy = document.createElement('button');
        btnCopy.textContent = t.copy;
        btnCopy.className = 'ai-btn ai-copy';
        btnCopy.onclick = copyCode;

        let btnReset = document.createElement('button');
        btnReset.textContent = t.reset;
        btnReset.className = 'ai-btn ai-reset';
        btnReset.onclick = resetSelection;

        let statusDiv = document.createElement('div');
        statusDiv.id = 'ai-status';
        statusDiv.className = 'ai-status';
        statusDiv.textContent = t.ready;

        content.appendChild(btnPick);
        content.appendChild(btnCopy);
        content.appendChild(btnReset);
        content.appendChild(statusDiv);

        div.appendChild(titleBar);
        div.appendChild(content);

        document.body.appendChild(div);
        return div;
    }

// ========== 9. ОБРАБОТЧИКИ СОБЫТИЙ ==========
    function handleClick(e) {
        if (!picking && !selected) return;

        if (e.target.closest && e.target.closest('#ai-collector-panel')) return;

        if (picking) {
            if (!isMainArea(e.clientX, e.clientY)) {
                setStatus(t.clickArea, '#ef4444');
                picking = false;
                return;
            }
            let msg = findBotMessage(e.target);
            if (!msg || !isValidMessage(msg)) {
                setStatus(t.botOnly, '#ef4444');
                picking = false;
                return;
            }
            selectMessage(msg);
            e.preventDefault();
            e.stopPropagation();
            return;
        }

        if (selected && !picking) {
            const clickedOnSelected = selected.contains(e.target);
            if (!clickedOnSelected) resetSelection();
        }
    }

    function handleKey(e) {
        if (e.ctrlKey && e.shiftKey && (e.key.toLowerCase() === 'h' || e.key.toLowerCase() === 'р')) {
            e.preventDefault();
            if (panel) {
                panel.classList.toggle('ai-hidden');
            }
            return;
        }

        if (e.ctrlKey && (e.key.toLowerCase() === 'b' || e.key.toLowerCase() === 'и')) {
            e.preventDefault();
            picking = true;
            setStatus(t.clickBot, '#f59e0b');
            return;
        }

        if (e.ctrlKey && (e.key.toLowerCase() === 'c' || e.key.toLowerCase() === 'с')) {
            if (selected) {
                e.preventDefault();
                copyCode();
            }
            return;
        }

        if (e.key === 'Escape') {
            resetSelection();
            picking = false;
        }
    }

// ========== 10. ЗАПУСК СКРИПТА ==========
    function init() {
        panel = createPanel();
        const prefix = currentPlatform.name + '_';
        let last = GM_getValue(prefix + 'lastMsg', '');
        if (last) {
            setTimeout(() => {
                let candidates = document.querySelectorAll(CONFIG.botSelectors);
                for (let m of candidates) {
                    if (isBotMessage(m) && (m.innerText || '').startsWith(last)) {
                        selectMessage(m);
                        break;
                    }
                }
            }, 500);
        }
        console.log(`[${currentPlatform.name}] Ready`);
    }

    document.addEventListener('click', handleClick, true);
    document.addEventListener('keydown', handleKey);

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();