Convenient import of sessions in AI services
// ==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;
}
})();