Skip to content
Open
402 changes: 202 additions & 200 deletions README.md

Large diffs are not rendered by default.

38 changes: 20 additions & 18 deletions chrome-extension/README.md
Original file line number Diff line number Diff line change
@@ -1,31 +1,33 @@
# DeepSeek → FreeDeepseekAPI (расширение)
# DeepSeek → FreeDeepseekAPI (extension)

Добавляет аккаунт DeepSeek в локальный FreeDeepseekAPI **одним кликом**:
перехватывает заголовки реального запроса к `chat.deepseek.com/api/...`
(`token` из `Authorization`, все cookie, `hif_*`) и отправляет на
Adds a DeepSeek account to your local FreeDeepseekAPI **with one click**:
intercepts headers from a real request to `chat.deepseek.com/api/...`
(`token` from `Authorization`, all cookies, `hif_*`) and sends them to
`http://localhost:9655/api/accounts/import`.

Works in Firefox and Chrome/Edge (Manifest V3).

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

## Установка

**Firefox**
1. Откройте `about:debugging#/runtime/this-firefox`
2. «Загрузить временное дополнение»выберите `manifest.json` из этой папки.
(Временное дополнение: после перезапуска Firefox установить заново.)
1. Open `about:debugging#/runtime/this-firefox`
2. "Load Temporary Add-on"select `manifest.json` from this folder.
(Temporary add-on: after Firefox restart, install again.)

**Chrome / Edge**
1. Откройте `chrome://extensions`
2. Включите «Режим разработчика».
3. «Загрузить распакованное»выберите эту папку.
1. Open `chrome://extensions`
2. Enable "Developer mode".
3. "Load unpacked"select this folder.

## Использование
1. Запустите FreeDeepseekAPI (порт 9655).
2. Откройте `chat.deepseek.com` и войдите в нужный аккаунт.
3. **Отправьте любое сообщение** (например `ok`) — чтобы прошёл запрос, из которого берутся креды.
4. Клик по иконке расширения → **«➕ Добавить в FreeDeepseekAPI»**.
## Usage
1. Start FreeDeepseekAPI (port 9655).
2. Open `chat.deepseek.com` and log in to the desired account.
3. **Send any message** (e.g. `ok`) — so the request from which creds are taken occurs.
4. Click extension icon → **"➕ Add to FreeDeepseekAPI"**.

Для нескольких аккаунтов повторите из разных профилей/логинов браузера.
For multiple accounts, repeat from different browser profiles/logins.

Вспомогательные кнопки: «Собрать» (показать креды), «Копировать JSON»,
«Скачать файл» (`deepseek-auth.json`) — на случай ручного импорта через дашборд.
Auxiliary buttons: "Collect" (show creds), "Copy JSON",
"Download file" (`deepseek-auth.json`) — for manual import via dashboard.
10 changes: 5 additions & 5 deletions chrome-extension/background.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
// DeepSeek → FreeDeepseekAPI — перехват заголовков реального запроса.
// token (Authorization: Bearer), cookie (все), hif (x-hif-*) берутся из
// настоящего запроса к chat.deepseek.com/api/... — как в HAR/cURL.
// DeepSeek → FreeDeepseekAPI — intercepts headers of real request.
// token (Authorization: Bearer), cookie (all), hif (x-hif-*) are taken from
// actual request to chat.deepseek.com/api/... — same as 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 даёт их без него.
// extraHeaders needed for Chrome to access Cookie/Authorization; Firefox provides them without it.
const opts = ['requestHeaders'];
try {
if (chrome.webRequest.OnBeforeSendHeadersOptions &&
chrome.webRequest.OnBeforeSendHeadersOptions.EXTRA_HEADERS) {
opts.push('extraHeaders');
}
} catch (e) { /* Firefox: опции нет — это нормально */ }
} catch (e) { /* Firefox: option doesn't exist — this is normal */ }

chrome.webRequest.onBeforeSendHeaders.addListener(
(details) => {
Expand Down
2 changes: 1 addition & 1 deletion chrome-extension/content.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
}
sendResponse({ data });
}
});
});
4 changes: 2 additions & 2 deletions chrome-extension/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"manifest_version": 3,
"name": "DeepSeek → FreeDeepseekAPI",
"version": "1.4",
"description": "Добавляет аккаунт DeepSeek в локальный FreeDeepseekAPI одним кликом. Перехватывает заголовки запроса chat.deepseek.com (token + cookie + hif).",
"description": "Adds a DeepSeek account to local FreeDeepseekAPI with one click. Intercepts chat.deepseek.com request headers (token + cookie + hif).",
"author": "FreeDeepseekAPI",
"browser_specific_settings": {
"gecko": {
Expand All @@ -27,4 +27,4 @@
"default_icon": { "16": "icons/icon16.png", "48": "icons/icon48.png", "128": "icons/icon128.png" }
},
"icons": { "16": "icons/icon16.png", "48": "icons/icon48.png", "128": "icons/icon128.png" }
}
}
20 changes: 10 additions & 10 deletions chrome-extension/popup.html
Original file line number Diff line number Diff line change
Expand Up @@ -57,19 +57,19 @@
<body>
<div class="hdr">
<h1>🔑 DeepSeek → FreeDeepseekAPI</h1>
<span id="srvDot" class="srv-dot" title="Статус сервера FreeDeepseekAPI"></span>
<span id="srvDot" class="srv-dot" title="FreeDeepseekAPI server status"></span>
</div>
<div class="sub">Откройте <code>chat.deepseek.com</code>, <b>отправьте любое сообщение</b>, затем нажмите кнопку</div>
<div class="sub">Open <code>chat.deepseek.com</code>, <b>send any message</b>, then click the button</div>

<div id="status" class="status warn">⏳ Loading...</div>

<div class="btn-row">
<button id="btnAdd" class="btn-primary" style="font-size:13px;padding:11px">➕ Добавить в FreeDeepseekAPI</button>
<button id="btnAdd" class="btn-primary" style="font-size:13px;padding:11px">➕ Add to FreeDeepseekAPI</button>
</div>
<div class="btn-row">
<button id="btnCollect" class="btn-success">🔄 Собрать</button>
<button id="btnCopy" class="btn-success">📋 Копировать JSON</button>
<button id="btnSave" class="btn-warn">💾 Скачать файл</button>
<button id="btnCollect" class="btn-success">🔄 Collect</button>
<button id="btnCopy" class="btn-success">📋 Copy JSON</button>
<button id="btnSave" class="btn-warn">💾 Download file</button>
</div>

<div class="field">
Expand All @@ -81,15 +81,15 @@ <h1>🔑 DeepSeek → FreeDeepseekAPI</h1>

<div class="pool-section">
<div class="pool-header">
<span class="title" id="poolTitle">Пул аккаунтов</span>
<span class="title" id="poolTitle">Account Pool</span>
<span style="display:flex; gap:6px">
<button id="btnDashboard" class="link-btn">🖥 Дашборд</button>
<button id="btnCheckAll" class="link-btn">↻ Проверить все</button>
<button id="btnDashboard" class="link-btn">🖥 Dashboard</button>
<button id="btnCheckAll" class="link-btn">↻ Check all</button>
</span>
</div>
<div id="pool" class="pool-list"></div>
</div>

<script src="popup.js"></script>
</body>
</html>
</html>
52 changes: 26 additions & 26 deletions chrome-extension/popup.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,29 @@ function $(id) { return document.getElementById(id); }
const API_BASE = 'http://localhost:9655';
const PROXY_URL = API_BASE + '/api/accounts/import';

let current = null; // перехваченный набор кредов {token,cookie,hif_*,wasmUrl}
let current = null; // intercepted creds set {token,cookie,hif_*,wasmUrl}

function setStatus(cls, text) { $('status').className = 'status ' + cls; $('status').textContent = text; }
function setSrvDot(online) { const d = $('srvDot'); if (d) { d.className = 'srv-dot ' + (online ? 'online' : 'offline'); d.title = online ? 'Сервер онлайн (:9655)' : 'Сервер недоступен (:9655)'; } }
function setSrvDot(online) { const d = $('srvDot'); if (d) { d.className = 'srv-dot ' + (online ? 'online' : 'offline'); d.title = online ? 'Server online (:9655)' : 'Server unavailable (:9655)'; } }
function setCredButtons(has) { for (const id of ['btnAdd', 'btnCopy', 'btnSave']) { const b = $(id); if (b) b.disabled = !has; } }

function render(cap) {
if (!cap || !cap.token || !cap.cookie) {
setStatus('warn', '⚠️ Откройте chat.deepseek.com и ОТПРАВЬТЕ любое сообщение, затем нажмите кнопку.');
setStatus('warn', '⚠️ Open chat.deepseek.com and SEND any message, then click the button.');
$('jsonPreview').textContent = '{ }';
$('detail').textContent = 'Креды появятся после запроса к DeepSeek';
$('detail').textContent = 'Creds will appear after request to DeepSeek';
setCredButtons(false);
return null;
}
const auth = { token: cap.token, hif_dliq: cap.hif_dliq || '', hif_leim: cap.hif_leim || '', cookie: cap.cookie, wasmUrl: cap.wasmUrl };
// превью с маскировкой секретов
// preview with masked secrets
$('jsonPreview').textContent = JSON.stringify({
token: auth.token.slice(0, 6) + '…(' + auth.token.length + ')',
cookie: auth.cookie.slice(0, 48) + '…',
hif_leim: auth.hif_leim ? ('…(' + auth.hif_leim.length + ')') : '',
}, null, 2);
setStatus('ok', '✅ Перехвачено: token + cookie' + (auth.hif_leim ? ' + hif' : '') + ' — готово');
$('detail').textContent = cap._t ? ('Обновлено: ' + new Date(cap._t).toLocaleTimeString()) : '';
setStatus('ok', '✅ Captured: token + cookie' + (auth.hif_leim ? ' + hif' : '') + ' — ready');
$('detail').textContent = cap._t ? ('Updated: ' + new Date(cap._t).toLocaleTimeString()) : '';
setCredButtons(true);
return auth;
}
Expand All @@ -34,7 +34,7 @@ function refresh() {
chrome.runtime.sendMessage({ action: 'get' }, (r) => { current = (r && r.success) ? render(r.cap) : render(null); });
}

// ── Панель пула (строится через DOM API, без innerHTML, чтобы исключить XSS) ──
// ── Pool panel (built via DOM API, no innerHTML, to rule out XSS) ──
function mkBtn(act, label, title, cls) {
const b = document.createElement('button');
b.className = cls; b.dataset.act = act; b.title = title; b.textContent = label;
Expand All @@ -48,8 +48,8 @@ function setEmpty(text) {
}

function renderPool(list) {
$('poolTitle').textContent = `Пул аккаунтов (${list.length})`;
if (!list.length) { setEmpty('Нет аккаунтов'); return; }
$('poolTitle').textContent = `Account pool (${list.length})`;
if (!list.length) { setEmpty('No accounts'); return; }
const pool = $('pool'); pool.textContent = '';
for (const a of list) {
const row = document.createElement('div');
Expand All @@ -66,7 +66,7 @@ function renderPool(list) {

const actions = document.createElement('span');
actions.className = 'row-actions';
actions.append(mkBtn('check', '↻', 'Проверить', 'acc-btn'), mkBtn('del', '✕', 'Удалить', 'acc-btn danger'));
actions.append(mkBtn('check', '↻', 'Check', 'acc-btn'), mkBtn('del', '✕', 'Delete', 'acc-btn danger'));

row.append(idEl, emailEl, badge, actions);
pool.appendChild(row);
Expand All @@ -81,8 +81,8 @@ async function loadPool() {
renderPool(j.accounts || []);
} catch {
setSrvDot(false);
$('poolTitle').textContent = 'Пул аккаунтов';
setEmpty('FreeDeepseekAPI недоступен на :9655');
$('poolTitle').textContent = 'Account pool';
setEmpty('FreeDeepseekAPI unavailable on :9655');
}
}

Expand All @@ -104,12 +104,12 @@ async function deleteAccount(id) {
loadPool();
}

// делегирование кликов в панели (check / удаление с инлайн-подтверждением)
// click delegation in panel (check / delete with inline confirmation)
$('pool').addEventListener('click', (e) => {
const emailEl = e.target.closest('.email');
if (emailEl && emailEl.textContent && emailEl.textContent !== '—') {
const full = emailEl.title || emailEl.textContent;
navigator.clipboard.writeText(full).then(() => { const o = emailEl.textContent; emailEl.textContent = '✓ скопировано'; setTimeout(() => { emailEl.textContent = o; }, 900); });
navigator.clipboard.writeText(full).then(() => { const o = emailEl.textContent; emailEl.textContent = '✓ copied'; setTimeout(() => { emailEl.textContent = o; }, 900); });
return;
}
const btn = e.target.closest('.acc-btn'); if (!btn) return;
Expand All @@ -118,7 +118,7 @@ $('pool').addEventListener('click', (e) => {
if (act === 'check') { checkAccount(id); return; }
if (act === 'del') {
const actions = btn.parentElement; actions.textContent = '';
actions.append(mkBtn('yes', '✓', 'Удалить', 'acc-btn confirm'), mkBtn('no', '✗', 'Отмена', 'acc-btn'));
actions.append(mkBtn('yes', '✓', 'Delete', 'acc-btn confirm'), mkBtn('no', '✗', 'Cancel', 'acc-btn'));
return;
}
if (act === 'yes') deleteAccount(id);
Expand All @@ -134,32 +134,32 @@ async function checkAll() {
$('btnCheckAll').addEventListener('click', checkAll);
$('btnDashboard').addEventListener('click', () => { chrome.tabs.create({ url: API_BASE + '/dashboard' }); });

// ── Главная кнопкадобавить перехваченные креды + авто-валидация ──
// ── Main buttonadd captured creds + auto-validation ──
$('btnAdd').addEventListener('click', async () => {
if (!current) {
refresh();
setStatus('warn', '⏳ Кредов нет. Отправьте сообщение в DeepSeek и нажмите снова.');
setStatus('warn', '⏳ No creds. Send a message in DeepSeek and try again.');
return;
}
setStatus('warn', '⏳ Отправка в FreeDeepseekAPI…');
setStatus('warn', '⏳ Sending to FreeDeepseekAPI…');
try {
const r = await fetch(PROXY_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(current) });
const j = await r.json();
if (j.ok) {
const who = j.email ? ` (${j.email})` : '';
setStatus('ok', `✅ Добавлен как ${j.id}${who} — проверяю…`);
setStatus('ok', `✅ Added as ${j.id}${who} — checking…`);
await loadPool();
const st = await checkAccount(j.id);
if (st === 'OK') setStatus('ok', `🟢 ${j.id}${who} — рабочий`);
else setStatus('err', `🔴 ${j.id} — статус: ${st || 'неизвестен'}`);
if (st === 'OK') setStatus('ok', `🟢 ${j.id}${who} — working`);
else setStatus('err', `🔴 ${j.id} — status: ${st || 'unknown'}`);
} else if (j.existingId) {
setStatus('warn', `⚠️ Уже добавлен как ${j.existingId}`);
setStatus('warn', `⚠️ Already added as ${j.existingId}`);
loadPool();
} else {
setStatus('err', '❌ ' + (j.error || 'Ошибка добавления'));
setStatus('err', '❌ ' + (j.error || 'Add error'));
}
} catch (e) {
setStatus('err', '❌ FreeDeepseekAPI недоступен на localhost:9655 (запущен?)');
setStatus('err', '❌ FreeDeepseekAPI unavailable on localhost:9655 (running?)');
}
});

Expand All @@ -168,7 +168,7 @@ $('btnCollect').addEventListener('click', refresh);
$('btnCopy').addEventListener('click', () => {
if (!current) return;
navigator.clipboard.writeText(JSON.stringify(current, null, 2)).then(() => {
$('btnCopy').textContent = '✅'; setTimeout(() => { $('btnCopy').textContent = '📋 Копировать JSON'; }, 1200);
$('btnCopy').textContent = '✅'; setTimeout(() => { $('btnCopy').textContent = '📋 Copy JSON'; }, 1200);
});
});

Expand Down
Loading