Cookie manager AI

Convenient import of sessions in AI services

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)

Advertisement:

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)

Advertisement:

// ==UserScript==
// @name         Cookie manager AI
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Convenient import of sessions in AI services
// @author       satoshi
// @match        https://*.cursor.com/*
// @match        https://*.cursor.sh/*
// @match        https://*.claude.ai/*
// @match        https://*.chatgpt.com/*
// @match        https://chatgpt.com/*
// @grant        GM_cookie
// @license      MIT
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    const currentHost = window.location.hostname;
    const isClaude = currentHost.includes('claude.ai');
    const isCursor = currentHost.includes('cursor.com') || currentHost.includes('cursor.sh');
    const isChatGPT = currentHost.includes('chatgpt.com');

    if (!isClaude && !isCursor && !isChatGPT) return;

    // Определение названий для UI
    let serviceName = 'AI Service';
    let placeholderText = 'Вставь токен ИЛИ текст Netscape куков...';
    if (isClaude) { serviceName = 'Claude Key'; placeholderText = 'Вставь sessionKey ИЛИ текст Netscape куков...'; }
    else if (isCursor) { serviceName = 'Cursor Token'; placeholderText = 'Вставь WorkosCursorSessionToken ИЛИ текст Netscape куков...'; }
    else if (isChatGPT) { serviceName = 'ChatGPT Token'; placeholderText = 'Вставь __Secure-next-auth.session-token ИЛИ текст Netscape куков...'; }

    // --- 1. Плавающая кнопка ---
    const btn = document.createElement('button');
    btn.innerHTML = `
        <svg style="width:16px; height:16px; fill:currentColor;" viewBox="0 0 24 24">
            <path d="M12.65 10C11.83 7.67 9.61 6 7 6c-3.31 0-6 2.69-6 6s2.69 6 6 6c2.61 0 4.83-1.67 5.65-4H17v4h4v-4h2v-4H12.65zM7 14c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2z"/>
        </svg>
        <span>Set ${serviceName}</span>
    `;
    
    btn.style = `
        position: fixed; bottom: 24px; right: 24px; z-index: 999999;
        display: flex; align-items: center; gap: 8px; padding: 12px 18px;
        background: #2563eb; color: #ffffff; border: none; border-radius: 50px;
        cursor: pointer; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
        font-size: 14px; font-weight: 600; letter-spacing: -0.2px;
        box-shadow: 0 10px 25px -5px rgba(37, 99, 235, 0.4);
        transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
    `;
    
    btn.addEventListener('mouseover', () => { btn.style.background = '#1d4ed8'; btn.style.transform = 'translateY(-2px)'; });
    btn.addEventListener('mouseleave', () => { btn.style.background = '#2563eb'; btn.style.transform = 'translateY(0)'; });
    document.body.appendChild(btn);

    // --- 2. Модальное окно ---
    const modal = document.createElement('div');
    modal.style = `
        display: none; position: fixed; bottom: 85px; right: 24px; z-index: 999999;
        width: 340px; background: rgba(23, 23, 23, 0.85); backdrop-filter: blur(16px);
        -webkit-backdrop-filter: blur(16px); border: 1px solid rgba(255, 255, 255, 0.08);
        border-radius: 16px; padding: 20px; box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.3);
        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; color: #f3f4f6;
    `;

    modal.innerHTML = `
        <div style="font-weight: 600; margin-bottom: 12px; font-size: 15px; display: flex; align-items: center; gap: 6px;">
            <span style="color: #3b82f6;">●</span> Импорт сессии ${serviceName.split(' ')[0]}
        </div>
        
        <input type="file" id="cookie-file-file" accept=".txt,.json" style="display: none;" />
        
        <div id="file-upload-area" style="
            border: 2px dashed rgba(255, 255, 255, 0.15); border-radius: 10px;
            padding: 12px; text-align: center; margin-bottom: 12px; cursor: pointer;
            background: rgba(255, 255, 255, 0.02); transition: all 0.2s;
        ">
            <span style="font-size: 13px; color: #9ca3af; font-weight: 500;">📁 Выбрать .txt/.json файл</span>
        </div>

        <textarea id="cookie-input" placeholder="${placeholderText}" style="
            width: 100%; height: 80px; padding: 10px 12px; background: rgba(0, 0, 0, 0.2); 
            border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 10px; resize: none; 
            font-size: 11px; font-family: monospace; color: #e5e7eb; box-sizing: border-box; 
            margin-bottom: 14px; outline: none;
        "></textarea>

        <div style="display: flex; justify-content: flex-end; gap: 10px;">
            <button id="cookie-cancel-btn" style="
                padding: 8px 14px; background: transparent; border: 1px solid rgba(255, 255, 255, 0.15); 
                color: #f3f4f6; border-radius: 8px; cursor: pointer; font-size: 13px;
            ">Отмена</button>
            <button id="cookie-save-btn" style="
                padding: 8px 16px; background: #2563eb; color: white; border: none; 
                border-radius: 8px; cursor: pointer; font-size: 13px; font-weight: 600;
            ">Импортировать</button>
        </div>
    `;
    document.body.appendChild(modal);

    const tx = modal.querySelector('#cookie-input');
    const fileInput = modal.querySelector('#cookie-file-file');
    const uploadArea = modal.querySelector('#file-upload-area');
    const cBtn = modal.querySelector('#cookie-cancel-btn');
    const sBtn = modal.querySelector('#cookie-save-btn');

    uploadArea.addEventListener('mouseover', () => { uploadArea.style.borderColor = 'rgba(37, 99, 235, 0.5)'; uploadArea.style.background = 'rgba(255, 255, 255, 0.05)'; });
    uploadArea.addEventListener('mouseleave', () => { uploadArea.style.borderColor = 'rgba(255, 255, 255, 0.15)'; uploadArea.style.background = 'rgba(255, 255, 255, 0.02)'; });
    uploadArea.addEventListener('click', () => fileInput.click());

    fileInput.addEventListener('change', (e) => {
        const file = e.target.files[0];
        if (!file) return;
        uploadArea.querySelector('span').innerText = `📄 ${file.name}`;
        const reader = new FileReader();
        reader.onload = (ev) => { tx.value = ev.target.result; };
        reader.readAsText(file);
    });

    const setCookieAsync = (details) => {
        return new Promise((resolve) => {
            try {
                GM_cookie.set(details, (err) => {
                    if (err) {
                        console.error('Ошибка GM_cookie.set:', err, details);
                        resolve({ success: false, error: err });
                    } else {
                        resolve({ success: true });
                    }
                });
            } catch (e) {
                console.error('Исключение GM_cookie:', e);
                resolve({ success: false, error: e.message });
            }
        });
    };

    function parseNetscape(text, targetName) {
        const lines = text.split('\n');
        for (let line of lines) {
            line = line.trim();
            if (!line || line.startsWith('#')) continue;
            
            let parts = line.split(/\t| {2,}/);
            if (parts.length >= 6) {
                const name = parts[parts.length - 2].trim();
                const value = parts[parts.length - 1].trim();
                if (name === targetName) {
                    return value;
                }
            }
        }
        return null;
    }

    btn.addEventListener('click', () => { modal.style.display = modal.style.display === 'none' ? 'block' : 'none'; });
    cBtn.addEventListener('click', () => {
        modal.style.display = 'none'; tx.value = ''; fileInput.value = '';
        uploadArea.querySelector('span').innerText = '📁 Выбрать .txt/.json файл';
    });

    sBtn.addEventListener('click', async () => {
        let value = tx.value.trim();
        if (!value) return;

        sBtn.innerText = 'Импорт...';
        sBtn.style.background = '#1e40af';
        sBtn.disabled = true;

        const exp = Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 90; // 90 дней
        let res;

        // --- ЛОГИКА ДЛЯ CLAUDE ---
        if (isClaude) {
            if (value.includes('\t') || value.includes('  ')) {
                const parsedKey = parseNetscape(value, 'sessionKey');
                if (parsedKey) value = parsedKey;
                else { alert('Ошибка: кук "sessionKey" не обнаружен!'); resetButton(); return; }
            }

            res = await setCookieAsync({
                url: window.location.origin, name: 'sessionKey', value: value,
                domain: '.claude.ai', path: '/', secure: true, expirationDate: exp
            });

            if (res.success) location.reload();
            else { alert('Ошибка установки кука Claude: ' + JSON.stringify(res.error)); resetButton(); }
        } 
        
        // --- ЛОГИКА ДЛЯ CHATGPT ---
        else if (isChatGPT) {
            if (value.includes('\t') || value.includes('  ')) {
                const parsedToken = parseNetscape(value, '__Secure-next-auth.session-token');
                if (parsedToken) value = parsedToken;
                else { alert('Ошибка: кук "__Secure-next-auth.session-token" не обнаружен!'); resetButton(); return; }
            }

            res = await setCookieAsync({
                url: 'https://chatgpt.com/', name: '__Secure-next-auth.session-token', value: value,
                domain: '.chatgpt.com', path: '/', secure: true, httpOnly: true, expirationDate: exp
            });

            if (res.success) location.reload();
            else { alert('Ошибка установки токена ChatGPT: ' + JSON.stringify(res.error)); resetButton(); }
        }

        // --- ЛОГИКА ДЛЯ CURSOR ---
        else if (isCursor) {
            if (value.includes('\t') || value.includes('  ')) {
                const parsedToken = parseNetscape(value, 'WorkosCursorSessionToken');
                if (parsedToken) value = parsedToken;
                else { alert('Ошибка: токен "WorkosCursorSessionToken" не обнаружен!'); resetButton(); return; }
            }

            let userId = 'user_unknown';
            const match = value.match(/(user_[A-Za-z0-9]+)/);
            if (match) userId = match[1];

            res = await setCookieAsync({
                url: 'https://www.cursor.com/', name: 'WorkosCursorSessionToken', value: value,
                domain: '.cursor.com', path: '/', secure: true, httpOnly: true, expirationDate: exp
            });
            if (!res.success) { alert('Ошибка на шаге 1: ' + JSON.stringify(res.error)); resetButton(); return; }

            await setCookieAsync({
                url: 'https://www.cursor.com/', name: 'cursor-web-target-synced-user', value: userId,
                domain: '.cursor.com', path: '/', secure: true, expirationDate: exp
            });

            await setCookieAsync({
                url: 'https://www.cursor.com/', name: 'workos_id', value: userId,
                domain: '.cursor.com', path: '/', secure: true, expirationDate: exp
            });

            await setCookieAsync({
                url: 'https://authenticator.cursor.sh/', name: 'WorkosCursorSessionToken', value: value,
                domain: 'authenticator.cursor.sh', path: '/', secure: true, httpOnly: true, expirationDate: exp
            });

            if (window.location.hostname.includes('authenticator.cursor.sh')) {
                window.location.href = 'https://www.cursor.com/';
            } else {
                location.reload();
            }
        }
    });

    function resetButton() {
        sBtn.innerText = 'Импортировать';
        sBtn.style.background = '#2563eb';
        sBtn.disabled = false;
    }
})();