Cookie manager AI

Convenient import of sessions in AI services

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

Advertisement:

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

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