Cookie manager AI

Convenient import of sessions in AI services

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

Advertisement:

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

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