Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ deepseek-auth.json
/tmp/deepseek_response_*
.DS_Store
*.pdf

deepseek-accounts.json
130 changes: 130 additions & 0 deletions accountManager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
'use strict';
/*
Пул аккаунтов DeepSeek с round-robin и пометкой лимитов/инвалида.
Аналог FreeQwenApi/src/api/tokenManager.js, адаптирован под плоскую
структуру DeepSeek (без браузерных профилей на аккаунт).

Хранилище: deepseek-accounts.json — массив
{ id, token, cookie, hif_dliq, hif_leim, wasmUrl, resetAt, invalid }
Миграция: если файла нет, но есть deepseek-auth.json (один аккаунт) —
создаём пул из одного acc_1 (обратная совместимость).
*/
const fs = require('fs');
const path = require('path');

const ACCOUNTS_PATH = process.env.DEEPSEEK_ACCOUNTS_PATH || path.join(__dirname, 'deepseek-accounts.json');
const AUTH_PATH = process.env.DEEPSEEK_AUTH_PATH || path.join(__dirname, 'deepseek-auth.json');
const COOLDOWN_HOURS = Number(process.env.DEEPSEEK_RATELIMIT_HOURS || 6);

let pointer = 0;

function decodeTokenInfo(token) {
try {
const p = JSON.parse(Buffer.from(String(token).split('.')[1], 'base64url').toString());
return { exp: p.exp ? p.exp * 1000 : null };
} catch { return { exp: null }; }
}

function saveAccounts(accounts) {
try { fs.writeFileSync(ACCOUNTS_PATH, JSON.stringify(accounts, null, 2), 'utf8'); }
catch (e) { console.error('[accounts] ошибка сохранения:', e.message); }
}

function loadAccounts() {
if (fs.existsSync(ACCOUNTS_PATH)) {
try { const a = JSON.parse(fs.readFileSync(ACCOUNTS_PATH, 'utf8')); if (Array.isArray(a)) return a; }
catch (e) { console.error('[accounts] ошибка чтения deepseek-accounts.json:', e.message); }
}
// миграция из единственного deepseek-auth.json
if (fs.existsSync(AUTH_PATH)) {
try {
const one = JSON.parse(fs.readFileSync(AUTH_PATH, 'utf8'));
if (one && one.token) {
const acc = [{
id: 'acc_1', token: one.token, cookie: one.cookie || '',
hif_dliq: one.hif_dliq || '', hif_leim: one.hif_leim || '',
wasmUrl: one.wasmUrl || '', resetAt: null, invalid: false,
}];
saveAccounts(acc);
console.log('[accounts] миграция deepseek-auth.json -> deepseek-accounts.json (acc_1)');
return acc;
}
} catch (e) { console.error('[accounts] ошибка миграции:', e.message); }
}
return [];
}

function isExpired(a) { const { exp } = decodeTokenInfo(a.token); return exp ? exp <= Date.now() : false; }

function isUsable(a, now) {
return !a.invalid && (!a.resetAt || Date.parse(a.resetAt) <= now) && !isExpired(a);
}

function getAvailableAccount() {
const accounts = loadAccounts();
const now = Date.now();
const valid = accounts.filter(a => isUsable(a, now));
if (!valid.length) return null;
const acc = valid[pointer % valid.length];
pointer = (pointer + 1) % valid.length;
return acc;
}

function hasValidAccounts() {
const now = Date.now();
return loadAccounts().some(a => isUsable(a, now));
}
function hasAnyAccount() { return loadAccounts().length > 0; }
function listAccounts() { return loadAccounts(); }
function getAccountById(id) { return loadAccounts().find(a => a.id === id) || null; }

function _update(id, fn) {
const a = loadAccounts();
const i = a.findIndex(x => x.id === id);
if (i < 0) return false;
fn(a[i]); saveAccounts(a); return true;
}
function markRateLimited(id, hours) {
const h = Number(hours) || COOLDOWN_HOURS;
return _update(id, a => { a.resetAt = new Date(Date.now() + h * 3600 * 1000).toISOString(); });
}
function markInvalid(id) { return _update(id, a => { a.invalid = true; }); }
function markValid(id) { return _update(id, a => { a.invalid = false; a.resetAt = null; }); }
function setEmail(id, email) { return _update(id, a => { a.email = String(email || ''); }); }

function addAccount(obj) {
if (!obj || !obj.token) return { error: 'Нужен token' };
if (!obj.cookie) return { error: 'Нужен cookie' };
const a = loadAccounts();
const dup = a.find(x => x.token === obj.token);
if (dup) return { error: 'Этот аккаунт уже добавлен', existingId: dup.id };
let n = 1; const ids = new Set(a.map(x => x.id));
while (ids.has('acc_' + n)) n++;
const id = 'acc_' + n;
a.push({
id, token: obj.token, cookie: obj.cookie,
hif_dliq: obj.hif_dliq || '', hif_leim: obj.hif_leim || '',
wasmUrl: obj.wasmUrl || '', email: obj.email || '', resetAt: null, invalid: false,
});
saveAccounts(a);
const { exp } = decodeTokenInfo(obj.token);
return { ok: true, id, exp, email: obj.email || '' };
}

function deleteAccount(id) {
if (typeof id !== 'string' || !/^acc_[a-zA-Z0-9]+$/.test(id)) return { error: 'Некорректный id аккаунта' };
const a = loadAccounts();
const next = a.filter(x => x.id !== id);
if (next.length === a.length) return { error: 'Аккаунт не найден' };
saveAccounts(next);
return { ok: true };
}

// первый известный рабочий wasmUrl — чтобы подставлять при импорте без wasm в cURL
function anyWasmUrl() { const a = loadAccounts().find(x => x.wasmUrl); return a ? a.wasmUrl : ''; }

module.exports = {
loadAccounts, saveAccounts, listAccounts, getAvailableAccount, getAccountById,
hasValidAccounts, hasAnyAccount, markRateLimited, markInvalid, markValid,
addAccount, deleteAccount, setEmail, decodeTokenInfo, anyWasmUrl, COOLDOWN_HOURS,
};
31 changes: 31 additions & 0 deletions chrome-extension/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# DeepSeek → FreeDeepseekAPI (расширение)

Добавляет аккаунт DeepSeek в локальный FreeDeepseekAPI **одним кликом**:
перехватывает заголовки реального запроса к `chat.deepseek.com/api/...`
(`token` из `Authorization`, все cookie, `hif_*`) и отправляет на
`http://localhost:9655/api/accounts/import`.

Работает в Firefox и Chrome/Edge (Manifest V3).

## Установка

**Firefox**
1. Откройте `about:debugging#/runtime/this-firefox`
2. «Загрузить временное дополнение» → выберите `manifest.json` из этой папки.
(Временное дополнение: после перезапуска Firefox установить заново.)

**Chrome / Edge**
1. Откройте `chrome://extensions`
2. Включите «Режим разработчика».
3. «Загрузить распакованное» → выберите эту папку.

## Использование
1. Запустите FreeDeepseekAPI (порт 9655).
2. Откройте `chat.deepseek.com` и войдите в нужный аккаунт.
3. **Отправьте любое сообщение** (например `ok`) — чтобы прошёл запрос, из которого берутся креды.
4. Клик по иконке расширения → **«➕ Добавить в FreeDeepseekAPI»**.

Для нескольких аккаунтов повторите из разных профилей/логинов браузера.

Вспомогательные кнопки: «Собрать» (показать креды), «Копировать JSON»,
«Скачать файл» (`deepseek-auth.json`) — на случай ручного импорта через дашборд.
138 changes: 43 additions & 95 deletions chrome-extension/background.js
Original file line number Diff line number Diff line change
@@ -1,97 +1,45 @@
// DeepSeek Auth Exporter — Background Service Worker
// Reads cookies from Chrome, forwards content-script localStorage data.

const STORAGE_KEY = 'deepseek_auth';

// Read all needed cookies from chat.deepseek.com
async function readCookies() {
const needed = ['token', 'ds_session_id', 'smidV2'];
const results = {};
for (const name of needed) {
const cookie = await new Promise((resolve) =>
chrome.cookies.get({ url: 'https://chat.deepseek.com', name }, resolve)
);
results[name] = cookie ? cookie.value : '';
}

// Build cookie header string
const parts = [];
if (results.ds_session_id) parts.push(`ds_session_id=${results.ds_session_id}`);
if (results.smidV2) parts.push(`smidV2=${results.smidV2}`);
results.cookie = parts.join('; ');

return results;
}

// Read localStorage values via content script injection
async function readLocalStorage(tabId) {
const keys = ['hif_dliq', 'hif_leim'];
try {
const results = await new Promise((resolve, reject) => {
chrome.tabs.sendMessage(
tabId,
{ action: 'readLocalStorage', keys },
(response) => {
if (chrome.runtime.lastError) reject(chrome.runtime.lastError.message);
else resolve(response.data || {});
// DeepSeek → FreeDeepseekAPI — перехват заголовков реального запроса.
// token (Authorization: Bearer), cookie (все), hif (x-hif-*) берутся из
// настоящего запроса к chat.deepseek.com/api/... — как в HAR/cURL.

const WASM_DEFAULT = 'https://fe-static.deepseek.com/chat/static/sha3_wasm_bg.7b9ca65ddd.wasm';
const KEY = 'deepseek_capture';

// extraHeaders нужен Chrome для доступа к Cookie/Authorization; Firefox даёт их без него.
const opts = ['requestHeaders'];
try {
if (chrome.webRequest.OnBeforeSendHeadersOptions &&
chrome.webRequest.OnBeforeSendHeadersOptions.EXTRA_HEADERS) {
opts.push('extraHeaders');
}
} catch (e) { /* Firefox: опции нет — это нормально */ }

chrome.webRequest.onBeforeSendHeaders.addListener(
(details) => {
const h = {};
for (const x of (details.requestHeaders || [])) h[x.name.toLowerCase()] = x.value;
const auth = h['authorization'] || '';
const token = /^bearer\s+\S/i.test(auth) ? auth.replace(/^bearer\s+/i, '').trim() : '';
const cookie = h['cookie'] || '';
if (token && cookie) {
const cap = {
token,
cookie,
hif_dliq: h['x-hif-dliq'] || '',
hif_leim: h['x-hif-leim'] || '',
wasmUrl: WASM_DEFAULT,
_t: Date.now(),
};
chrome.storage.local.set({ [KEY]: cap });
}
);
});
return results;
} catch (e) {
return {};
}
}

// Find an open DeepSeek tab
function findDeepSeekTab() {
return new Promise((resolve) => {
chrome.tabs.query(
{ url: 'https://chat.deepseek.com/*' },
(tabs) => resolve(tabs.length > 0 ? tabs[0] : null)
);
});
}

async function collectAndStore(tabId) {
const cookies = await readCookies();
let ls = {};
if (tabId) ls = await readLocalStorage(tabId);

const merged = {
token: cookies.token || '',
ds_session_id: cookies.ds_session_id || '',
smidV2: cookies.smidV2 || '',
cookie: cookies.cookie || '',
hif_dliq: ls.hif_dliq || '',
hif_leim: ls.hif_leim || '',
_lastUpdated: new Date().toISOString(),
};

await new Promise((resolve) =>
chrome.storage.local.set({ [STORAGE_KEY]: merged }, resolve)
);
return merged;
}

// Message handler — popup requests
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === 'collect') {
findDeepSeekTab().then(async (tab) => {
if (!tab) {
sendResponse({ success: false, error: 'No DeepSeek tab open' });
return;
}
const auth = await collectAndStore(tab.id);
sendResponse({ success: true, auth });
});
return true; // keep channel open for async
}

if (request.action === 'export') {
chrome.storage.local.get(STORAGE_KEY, (result) => {
sendResponse({ success: true, auth: result[STORAGE_KEY] || {} });
});
return true;
}
},
{ urls: ['https://chat.deepseek.com/api/*'] },
opts
);

chrome.runtime.onMessage.addListener((req, sender, sendResponse) => {
if (req.action === 'get') {
chrome.storage.local.get(KEY, (r) => sendResponse({ success: true, cap: r[KEY] || null }));
return true; // async
}
});
16 changes: 0 additions & 16 deletions chrome-extension/content.js

This file was deleted.

35 changes: 18 additions & 17 deletions chrome-extension/manifest.json
Original file line number Diff line number Diff line change
@@ -1,27 +1,28 @@
{
"manifest_version": 3,
"name": "DeepSeek Auth Exporter",
"version": "1.0",
"description": "Extract DeepSeek Chat credentials from cookies + localStorage for use with the web API proxy",
"permissions": ["cookies", "storage", "downloads"],
"host_permissions": ["https://chat.deepseek.com/*"],
"content_scripts": [
{
"matches": ["https://chat.deepseek.com/*"],
"js": ["content.js"],
"run_at": "document_idle"
"name": "DeepSeek → FreeDeepseekAPI",
"version": "1.3",
"description": "Добавляет аккаунт DeepSeek в локальный FreeDeepseekAPI одним кликом. Перехватывает заголовки запроса chat.deepseek.com (token + cookie + hif).",
"author": "FreeDeepseekAPI",
"browser_specific_settings": {
"gecko": {
"id": "freedeepseek-auth@forgetmeai",
"strict_min_version": "121.0",
"data_collection_permissions": { "required": ["none"] }
}
},
"permissions": ["webRequest", "storage"],
"host_permissions": [
"https://chat.deepseek.com/*",
"http://localhost:9655/*",
"http://127.0.0.1:9655/*"
],
"background": {
"service_worker": "background.js"
"service_worker": "background.js",
"scripts": ["background.js"]
},
"action": {
"default_popup": "popup.html",
"default_icon": {
"48": "icon.png"
}
},
"icons": {
"48": "icon.png"
"default_title": "DeepSeek → FreeDeepseekAPI"
}
}
Loading