From c8402124e99a3ce322b3709ddee3eff15a6b7ac7 Mon Sep 17 00:00:00 2001 From: AkioKoneko Date: Thu, 11 Jun 2026 12:31:51 +0200 Subject: [PATCH 1/5] fix: parse DeepSeek search fragments --- server.js | 83 +++++++++++++++++++++++++++++++++++++--------- tests/unit.test.js | 34 +++++++++++++++++++ 2 files changed, 102 insertions(+), 15 deletions(-) diff --git a/server.js b/server.js index 3ba72a0..c23cbe4 100755 --- a/server.js +++ b/server.js @@ -187,7 +187,9 @@ async function readDeepSeekJsonResponse(resp, label, account) { if (!resp.ok) markAccountFailure(account, resp.status, label); return { json, text }; } -loadDeepSeekConfig({ fatal: false }); +if (require.main === module) { + loadDeepSeekConfig({ fatal: false }); +} function createSession() { return { @@ -329,6 +331,47 @@ const ALL_MODEL_CAPABILITIES = Object.fromEntries(Object.entries(MODEL_CONFIGS). unavailable_reason: cfg.unavailable_reason || null, }])); +function isAssistantOutputFragment(fragment) { + return fragment + && (fragment.type === 'RESPONSE' || fragment.type === 'SEARCH') + && typeof fragment.content === 'string'; +} + +function isReasoningFragment(fragment) { + return fragment + && (fragment.type === 'THINK' || fragment.type === 'REASONING') + && typeof fragment.content === 'string'; +} + +function isDeepSeekModelErrorEvent(event) { + return event && event.type === 'error'; +} + +function rebuildFragmentText(fragments) { + const responseText = fragments + .filter(isAssistantOutputFragment) + .map(f => f.content) + .join(''); + const thinkText = fragments + .filter(isReasoningFragment) + .map(f => f.content) + .join(''); + return { responseText, thinkText }; +} + +function applyResponsePatchOperations(ops, appendFragments) { + if (!Array.isArray(ops)) return false; + let applied = false; + for (const op of ops) { + if (!op || typeof op !== 'object') continue; + if (op.p === 'fragments' && op.o === 'APPEND' && op.v !== undefined) { + appendFragments(op.v); + applied = true; + } + } + return applied; +} + function resolveModelConfig(model) { const requested = String(model || 'deepseek-chat').toLowerCase(); return MODEL_CONFIGS[requested] || MODEL_CONFIGS['deepseek-chat']; @@ -1204,15 +1247,8 @@ const server = http.createServer(async (req, res) => { let finishReason = null; let modelError = null; - const rebuildFragmentText = () => { - const responseText = fragments - .filter(f => f && f.type === 'RESPONSE' && typeof f.content === 'string') - .map(f => f.content) - .join(''); - const thinkText = fragments - .filter(f => f && (f.type === 'THINK' || f.type === 'REASONING') && typeof f.content === 'string') - .map(f => f.content) - .join(''); + const rebuildFragmentState = () => { + const { responseText, thinkText } = rebuildFragmentText(fragments); if (responseText) fullContent = responseText; reasoningContent = thinkText; }; @@ -1222,7 +1258,7 @@ const server = http.createServer(async (req, res) => { for (const fragment of incoming) { if (fragment && typeof fragment === 'object') fragments.push({ ...fragment }); } - rebuildFragmentText(); + rebuildFragmentState(); }; for await (const chunk of readable) { @@ -1234,9 +1270,11 @@ const server = http.createServer(async (req, res) => { try { const d = JSON.parse(line.slice(6)); if (d.response_message_id !== undefined && !newMessageId) newMessageId = d.response_message_id; - if (d.type === 'error' || d.finish_reason || d.content) { + if (isDeepSeekModelErrorEvent(d)) { modelError = { type: d.type || 'error', content: d.content || '', finish_reason: d.finish_reason || null }; - if (d.finish_reason) finishReason = d.finish_reason; + } + if (d.finish_reason) { + finishReason = d.finish_reason; } if (d.p !== undefined) lastPath = d.p; if (d.v && typeof d.v === 'object' && d.v.response) { @@ -1257,11 +1295,14 @@ const server = http.createServer(async (req, res) => { if (lastPath === 'response/fragments' && d.v !== undefined) { appendFragments(d.v); } + if (lastPath === 'response' && d.v !== undefined) { + applyResponsePatchOperations(d.v, appendFragments); + } if (lastPath === 'response/fragments/-1/content' && d.v !== undefined && typeof d.v !== 'object') { if (fragments.length > 0) { const lastFragment = fragments[fragments.length - 1]; lastFragment.content = `${lastFragment.content || ''}${d.v}`; - rebuildFragmentText(); + rebuildFragmentState(); } } if (lastPath === 'response/content' && d.v !== undefined && typeof d.v !== 'object') { @@ -1506,4 +1547,16 @@ async function main() { }); } -main().catch(err => { console.error('[DS-API] FATAL:', err); process.exit(1); }); +if (require.main === module) { + main().catch(err => { console.error('[DS-API] FATAL:', err); process.exit(1); }); +} + +module.exports = { + __test: { + isAssistantOutputFragment, + isReasoningFragment, + isDeepSeekModelErrorEvent, + rebuildFragmentText, + applyResponsePatchOperations, + }, +}; diff --git a/tests/unit.test.js b/tests/unit.test.js index 31a9656..a02bc23 100644 --- a/tests/unit.test.js +++ b/tests/unit.test.js @@ -6,6 +6,7 @@ const path = require('node:path'); const { spawnSync } = require('node:child_process'); const ROOT = path.resolve(__dirname, '..'); +const serverInternals = require('../server.js').__test; function tmpdir() { return fs.mkdtempSync(path.join(os.tmpdir(), 'fdsapi-test-')); @@ -109,3 +110,36 @@ test('chrome auth prints actionable OS instructions when Chrome is missing', () assert.match(out, /Linux/i); assert.match(out, /CHROME_PATH/i); }); + +test('DeepSeek stream parser treats SEARCH fragments as assistant output', () => { + const rebuilt = serverInternals.rebuildFragmentText([ + { type: 'SEARCH', content: 'The official Reuters website is ' }, + { type: 'SEARCH', content: 'https://www.reuters.com/.' }, + ]); + + assert.equal(rebuilt.responseText, 'The official Reuters website is https://www.reuters.com/.'); + assert.equal(rebuilt.thinkText, ''); +}); + +test('DeepSeek stream parser applies response-level fragment append patches', () => { + const fragments = []; + const appendFragments = (value) => { + const incoming = Array.isArray(value) ? value : [value]; + for (const fragment of incoming) fragments.push({ ...fragment }); + }; + + const applied = serverInternals.applyResponsePatchOperations([ + { p: 'fragments', o: 'APPEND', v: [{ type: 'RESPONSE', content: 'The' }] }, + { p: 'has_pending_fragment', o: 'SET', v: false }, + ], appendFragments); + + assert.equal(applied, true); + assert.deepEqual(fragments, [{ type: 'RESPONSE', content: 'The' }]); + assert.equal(serverInternals.rebuildFragmentText(fragments).responseText, 'The'); +}); + +test('DeepSeek stream parser does not treat service content chunks as model errors', () => { + assert.equal(serverInternals.isDeepSeekModelErrorEvent({ content: 'Official Reuters website URL' }), false); + assert.equal(serverInternals.isDeepSeekModelErrorEvent({ finish_reason: 'stop' }), false); + assert.equal(serverInternals.isDeepSeekModelErrorEvent({ type: 'error', content: 'backend error' }), true); +}); From ab0c055b52d0abc86fe7aaeb690f88bbc164af3a Mon Sep 17 00:00:00 2001 From: yhurynovich Date: Tue, 16 Jun 2026 18:42:35 -0400 Subject: [PATCH 2/5] feat: add /new command to reset agent chat session Detect when the latest user message is exactly "/new" (trimmed) and short-circuit the request to reset that agent's session instead of forwarding it to DeepSeek. - Reuses the existing createSession()/sessions map used by the /reset-session endpoint, so behavior stays consistent - Returns a confirmation reply via buildTextResponse(), respecting the active API mode (OpenAI, Anthropic, Responses) and streaming vs non-streaming - No DeepSeek API call is made for the /new turn itself - Per-agent: only resets the session for the requesting agentId, not all sessions Mounted via docker-compose volume override so the patch survives container rebuilds without modifying the Dockerfile. --- server.js | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/server.js b/server.js index 163f64c..442feef 100755 --- a/server.js +++ b/server.js @@ -1231,6 +1231,40 @@ const server = http.createServer(async (req, res) => { ? String(requestedSession) : ((remoteAddr === '127.0.0.1' || remoteAddr === '::1' || remoteAddr === '::ffff:127.0.0.1') ? 'dev-agent' : remoteAddr); const agentTag = `[${agentId}]`; + + // "/new" command: if the latest user message is exactly "/new" (whitespace-insensitive), + // reset this agent's DeepSeek session/history instead of forwarding anything to DeepSeek. + const lastUserMessage = [...messages].reverse().find(m => m && m.role === 'user'); + const lastUserText = lastUserMessage && typeof lastUserMessage.content === 'string' + ? lastUserMessage.content.trim() + : ''; + if (lastUserText === '/new') { + const existing = sessions.get(agentId); + const historyCount = existing ? existing.history.length : 0; + sessions.set(agentId, createSession()); + console.log(`${agentTag} /new received — session reset (history cleared: ${historyCount})`); + const confirmation = buildTextResponse('Started a new chat. Session and history have been reset.', '/new', requestedModel); + if (stream) { + if (apiMode === 'anthropic') { + sendAnthropicStream(res, confirmation); + } else if (apiMode === 'responses') { + sendResponsesStream(res, confirmation); + } else { + sendOpenAIStream(res, confirmation); + } + } else { + res.writeHead(200, { 'Content-Type': 'application/json' }); + if (apiMode === 'anthropic') { + res.end(JSON.stringify(toAnthropicResponse(confirmation))); + } else if (apiMode === 'responses') { + res.end(JSON.stringify(toResponsesResponse(confirmation))); + } else { + res.end(JSON.stringify(confirmation)); + } + } + return; + } + const { prompt, systemPrompt } = formatMessages(messages, tools); const session = getOrCreateAgentSession(agentId); From e49f054ef442a6fe84a60f574a97d4988b773c16 Mon Sep 17 00:00:00 2001 From: indicatorspro Date: Sun, 21 Jun 2026 09:27:52 -0300 Subject: [PATCH 3/5] Translated to English --- README.md | 301 +++++++++++++++++++++++++++++++- chrome-extension/README.md | 33 ++++ chrome-extension/background.js | 18 ++ chrome-extension/content.js | 2 +- chrome-extension/manifest.json | 8 + chrome-extension/popup.html | 24 ++- chrome-extension/popup.js | 87 +++++++++ lib/parseAuth.js | 27 +++ package-lock.json | 20 +++ public/dashboard.html | 239 +++++++++++++++++++++++++ scripts/auth.js | 40 +++++ scripts/deepseek_chrome_auth.js | 7 + server.js | 29 +++ 13 files changed, 830 insertions(+), 5 deletions(-) create mode 100644 package-lock.json diff --git a/README.md b/README.md index 00c5ee9..147ad8a 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,11 @@ # FreeDeepseekAPI

+<<<<<<< Updated upstream Локальный OpenAI-compatible API proxy для DeepSeek Web Chat +======= + Local OpenAI-compatible API proxy for DeepSeek Web Chat +>>>>>>> Stashed changes

@@ -12,24 +16,40 @@

+<<<<<<< Updated upstream Быстрый стартВозможностиПримерыМодели • +======= + Quick Start • + Features • + Examples • + Models • +>>>>>>> Stashed changes EndpointsOpen WebUI

+<<<<<<< Updated upstream FreeDeepseekAPI поднимает локальный API-сервер для **DeepSeek Web Chat** (`chat.deepseek.com`) и позволяет подключать DeepSeek Web к Open WebUI, LiteLLM, Hermes, Claude Code, OpenAI SDK-style клиентам и другим OpenAI-compatible инструментам. Проект работает через ваш обычный залогиненный аккаунт DeepSeek в отдельном Chrome-профиле. Локальный сервер принимает API-запросы, а дальше сам ходит в DeepSeek Web через сохранённую browser-сессию. > ⚠️ Это экспериментальный web-chat proxy. DeepSeek может менять внутренний Web API без предупреждения. Для production-кейсов надёжнее официальный платный API DeepSeek. +======= +FreeDeepseekAPI runs a local API server for **DeepSeek Web Chat** (`chat.deepseek.com`) and lets you connect DeepSeek Web to Open WebUI, LiteLLM, Hermes, Claude Code, OpenAI SDK-style clients, and other OpenAI-compatible tools. + +The project works through your regular logged-in DeepSeek account in a separate Chrome profile. The local server accepts API requests and then talks to DeepSeek Web through the saved browser session. + +> ⚠️ This is an experimental web-chat proxy. DeepSeek may change the internal Web API without warning. For production use cases, the official paid DeepSeek API is more reliable. +>>>>>>> Stashed changes ForgetMeAI: https://t.me/forgetmeai --- +<<<<<<< Updated upstream ## Навигация - [Что это даёт](#-что-это-даёт) @@ -44,6 +64,22 @@ ForgetMeAI: https://t.me/forgetmeai - [Идеи для консольной авторизации](#-идеи-для-консольной-авторизации) - [Проверка работы](#-проверка-работы) - [Примеры запросов](#-примеры-запросов) +======= +## Navigation + +- [What this gives you](#-what-this-gives-you) +- [Features](#-features) +- [Quick Start](#-quick-start) +- [Windows launch](#-windows-launch) +- [Linux / Chromium launch](#-linux--chromium-launch) +- [VPS / headless launch](#-vps--headless-launch) +- [Diagnostics / doctor](#-diagnostics--doctor) +- [Session reuse and chat reset](#-session-reuse-and-chat-reset) +- [Multi-account pool](#-multi-account-pool) +- [Console auth ideas](#-console-auth-ideas) +- [Verify it works](#-verify-it-works) +- [Request examples](#-request-examples) +>>>>>>> Stashed changes - [Chat Completions](#chat-completions) - [Reasoning](#reasoning) - [Web search](#web-search) @@ -51,16 +87,21 @@ ForgetMeAI: https://t.me/forgetmeai - [Anthropic Messages API](#anthropic-messages-api) - [OpenAI Responses API](#openai-responses-api) - [Tool calling](#tool-calling) +<<<<<<< Updated upstream - [Модели](#-модели) +======= +- [Models](#-models) +>>>>>>> Stashed changes - [Endpoints](#-endpoints) - [Open WebUI](#-open-webui) -- [Обновить логин](#-обновить-логин) -- [Статус проекта](#-статус-проекта) +- [Update login](#-update-login) +- [Project status](#-project-status) --- -## ✨ Что это даёт +## ✨ What this gives you +<<<<<<< Updated upstream - Использовать DeepSeek Web как локальный API endpoint. - Подключать DeepSeek к Open WebUI и другим OpenAI-compatible клиентам. - Получать обычные JSON-ответы или streaming SSE. @@ -70,10 +111,22 @@ ForgetMeAI: https://t.me/forgetmeai - Держать отдельные web-сессии для разных агентов/users. ## 🚀 Возможности +======= +- Use DeepSeek Web as a local API endpoint. +- Connect DeepSeek to Open WebUI and other OpenAI-compatible clients. +- Get regular JSON responses or streaming SSE. +- Use reasoning models with separate `reasoning_content`. +- Work with Anthropic Messages API shim for Claude Code / Anthropic SDK. +- Use OpenAI Responses API shim for new OpenAI/Codex-style clients. +- Keep separate web sessions for different agents/users. + +## 🚀 Features +>>>>>>> Stashed changes - **OpenAI-compatible API:** `POST /v1/chat/completions` - **Anthropic-compatible shim:** `POST /v1/messages` - **OpenAI Responses shim:** `POST /v1/responses` +<<<<<<< Updated upstream - **Streaming:** SSE chunks и обычные non-stream JSON-ответы - **Reasoning output:** отдельный `reasoning_content` для thinking-моделей - **Tool calling:** парсинг OpenAI tools, Anthropic tools и Responses function tools @@ -85,6 +138,21 @@ ForgetMeAI: https://t.me/forgetmeai --- ## ⚡ Быстрый старт +======= +- **Streaming:** SSE chunks and regular non-stream JSON responses +- **Reasoning output:** separate `reasoning_content` for thinking models +- **Tool calling:** parsing OpenAI tools, Anthropic tools, and Responses function tools +- **Model capabilities:** `GET /v1/model-capabilities` with alias → real web mode +- **Agent sessions:** separate DeepSeek session per `user` / agent id +- **Session recovery:** auto-reset of stale chains/sessions +- **Zero dependencies:** Node.js 18+, no npm dependencies +- **Chrome Extension:** Browser extension for session management +- **Dashboard Web:** Administrative web interface with real-time metrics + +--- + +## ⚡ Quick Start +>>>>>>> Stashed changes ```bash git clone https://github.com/ForgetMeAI/FreeDeepseekAPI.git @@ -93,6 +161,7 @@ npm run auth npm start ``` +<<<<<<< Updated upstream `npm run auth` открывает меню авторизации: 1. выберите пункт `1`; @@ -116,6 +185,31 @@ SKIP_ACCOUNT_MENU=1 npm start ``` По умолчанию сервер слушает: +======= +`npm run auth` opens the auth menu: + +1. select item `1`; +2. log in to DeepSeek in a separate Chrome profile; +3. send a short message like `ok`; +4. return to terminal and press Enter. + +`npm start` shows the launch menu: + +- `1` — authorize / update DeepSeek login +- `2` — show models and statuses +- `3` — start proxy +- `4` — exit + +For headless/CI launch without menu: + +```bash +NON_INTERACTIVE=1 npm start +# or +SKIP_ACCOUNT_MENU=1 npm start +``` + +By default the server listens on: +>>>>>>> Stashed changes ```text http://localhost:9655 @@ -123,7 +217,11 @@ http://localhost:9655 --- +<<<<<<< Updated upstream ## 🪟 Windows запуск +======= +## 🪟 Windows Launch +>>>>>>> Stashed changes ```powershell git clone https://github.com/ForgetMeAI/FreeDeepseekAPI.git @@ -132,18 +230,30 @@ npm run auth npm start ``` +<<<<<<< Updated upstream Если Chrome установлен нестандартно, явно укажите путь: +======= +If Chrome is installed in a non-standard location, specify the path explicitly: +>>>>>>> Stashed changes ```powershell $env:CHROME_PATH="C:\Program Files\Google\Chrome\Application\chrome.exe" npm run auth ``` +<<<<<<< Updated upstream Если Chrome не найден, `npm run auth` теперь печатает готовые инструкции для Windows/macOS/Linux вместо загадочного stack trace. --- ## 🐧 Linux / Chromium запуск +======= +If Chrome is not found, `npm run auth` now prints ready-to-use instructions for Windows/macOS/Linux instead of a cryptic stack trace. + +--- + +## 🐧 Linux / Chromium Launch +>>>>>>> Stashed changes ```bash git clone https://github.com/ForgetMeAI/FreeDeepseekAPI.git @@ -152,33 +262,57 @@ CHROME_PATH=$(which chromium) npm run auth npm start ``` +<<<<<<< Updated upstream Если Chromium называется иначе: ```bash CHROME_PATH=$(which chromium-browser) npm run auth # или +======= +If Chromium has a different name: + +```bash +CHROME_PATH=$(which chromium-browser) npm run auth +# or +>>>>>>> Stashed changes CHROME_PATH=$(which google-chrome) npm run auth ``` --- +<<<<<<< Updated upstream ## 🖥 VPS / headless запуск Самый надёжный flow без Chrome на сервере: 1. На домашнем ПК, где есть GUI/Chrome: +======= +## 🖥 VPS / Headless Launch + +The most reliable flow without Chrome on the server: + +1. On your home PC (where you have GUI/Chrome): +>>>>>>> Stashed changes ```bash npm run auth ``` +<<<<<<< Updated upstream 2. Скопируйте `deepseek-auth.json` на VPS: +======= +2. Copy `deepseek-auth.json` to VPS: +>>>>>>> Stashed changes ```bash scp deepseek-auth.json user@your-vps:/opt/FreeDeepseekAPI/deepseek-auth.json ``` +<<<<<<< Updated upstream 3. На VPS импортируйте/проверьте файл и выставьте безопасные права: +======= +3. On VPS import/verify the file and set safe permissions: +>>>>>>> Stashed changes ```bash cd /opt/FreeDeepseekAPI @@ -186,19 +320,31 @@ npm run auth:import -- --input ./deepseek-auth.json npm run doctor -- --offline ``` +<<<<<<< Updated upstream 4. Запускайте proxy без интерактивного меню: +======= +4. Start proxy without interactive menu: +>>>>>>> Stashed changes ```bash NON_INTERACTIVE=1 npm start ``` +<<<<<<< Updated upstream Можно импортировать не только готовый `deepseek-auth.json`, но и browser cookie export: +======= +You can import not only a ready-made `deepseek-auth.json`, but also a browser cookie export: +>>>>>>> Stashed changes ```bash DEEPSEEK_TOKEN="" npm run auth:import -- --input ./cookies.json ``` +<<<<<<< Updated upstream > Важно: `deepseek-auth.json` — это доступ к вашему DeepSeek Web login. Не коммитьте, не публикуйте, храните с правами `0600`. +======= +> Important: `deepseek-auth.json` gives access to your DeepSeek Web login. Do not commit, do not publish, store with `0600` permissions. +>>>>>>> Stashed changes --- @@ -206,6 +352,7 @@ DEEPSEEK_TOKEN="" npm run auth:import -- --input ./cookies.json ```bash npm run doctor +<<<<<<< Updated upstream # без сетевых запросов к DeepSeek: npm run doctor -- --offline ``` @@ -232,41 +379,94 @@ FreeDeepseekAPI не создаёт новый DeepSeek чат на каждый - локальная history сохраняется коротким контекстом, чтобы новая DeepSeek session могла продолжить разговор. Явно задать agent/session: +======= +# without network requests to DeepSeek: +npm run doctor -- --offline +``` + +`doctor` checks: + +- whether `deepseek-auth.json` / `DEEPSEEK_AUTH_DIR` is found; +- whether JSON is valid; +- whether `token`, `cookie`, `wasmUrl` exist; +- whether file permissions are safe on macOS/Linux (`0600`); +- on normal run — whether DeepSeek PoW endpoint is reachable. + +If you see `data.biz_data is null`, `fetch failed`, `401/403/429` or Hermes/OpenCode doesn't see models — first run `npm run doctor`. + +--- + +## ♻️ Session reuse and chat reset + +FreeDeepseekAPI doesn't create a new DeepSeek chat on every HTTP request without reason. The logic is: + +- one `x-agent-session`, `session`, or `user` → one DeepSeek chat session; +- if session id already exists — proxy reuses it and continues chain via `parent_message_id`; +- auto-reset happens on TTL, DeepSeek session error, or too long message chain; +- local history is saved as short context so new DeepSeek session can continue the conversation. + +Explicitly set agent/session: +>>>>>>> Stashed changes ```bash curl -X POST http://localhost:9655/v1/chat/completions \ -H "Content-Type: application/json" \ -H "x-agent-session: my-agent" \ +<<<<<<< Updated upstream -d '{"model":"deepseek-chat","messages":[{"role":"user","content":"Привет"}]}' ``` Посмотреть активные sessions: +======= + -d '{"model":"deepseek-chat","messages":[{"role":"user","content":"Hi"}]}' +``` + +View active sessions: +>>>>>>> Stashed changes ```bash curl http://localhost:9655/v1/sessions ``` +<<<<<<< Updated upstream Сбросить одну session: +======= +Reset a single session: +>>>>>>> Stashed changes ```bash curl -X POST "http://localhost:9655/reset-session?agent=my-agent" ``` +<<<<<<< Updated upstream Сбросить все sessions: +======= +Reset all sessions: +>>>>>>> Stashed changes ```bash curl -X POST "http://localhost:9655/reset-session?agent=all" ``` +<<<<<<< Updated upstream Почему чаты всё равно появляются в DeepSeek Web: proxy работает через внутренний Web Chat API, а DeepSeek хранит реальные chat sessions у себя. Это нормально для web-proxy. Задача session reuse — не плодить новые чаты без необходимости и аккуратно сбрасываться только когда chain протух/сломался. +======= +Why chats still appear in DeepSeek Web: proxy works through internal Web Chat API, and DeepSeek stores real chat sessions on their side. This is normal for web-proxy. The task of session reuse is not to spawn new chats unnecessarily and to reset cleanly only when the chain has gone stale/broken. +>>>>>>> Stashed changes --- ## 👥 Multi-account pool +<<<<<<< Updated upstream Можно подключить несколько auth-файлов. Правильная модель: sticky account per agent/session — proxy не переключает аккаунт внутри живой DeepSeek-сессии. Если аккаунт получил `401/403/429` и ушёл в cooldown, session безопасно сбрасывается и новый запрос может перейти на другой доступный аккаунт. Вариант 1 — директория с auth-файлами: +======= +You can connect multiple auth files. Correct model: sticky account per agent/session — proxy doesn't switch account inside a live DeepSeek session. If an account gets `401/403/429` and goes into cooldown, the session is safely reset and a new request may switch to another available account. + +Option 1 — directory with auth files: +>>>>>>> Stashed changes ```bash mkdir -p accounts @@ -276,12 +476,17 @@ chmod 600 accounts/*.json DEEPSEEK_AUTH_DIR=./accounts NON_INTERACTIVE=1 npm start ``` +<<<<<<< Updated upstream Вариант 2 — список файлов: +======= +Option 2 — file list: +>>>>>>> Stashed changes ```bash DEEPSEEK_AUTH_PATH="./accounts/main.json,./accounts/backup.json" NON_INTERACTIVE=1 npm start ``` +<<<<<<< Updated upstream Как работает pool: - новый agent/session получает доступный аккаунт round-robin; @@ -292,6 +497,18 @@ DEEPSEEK_AUTH_PATH="./accounts/main.json,./accounts/backup.json" NON_INTERACTIVE - auth-файлы должны храниться с правами `0600`. Настроить cooldown: +======= +How the pool works: + +- new agent/session gets an available account round-robin; +- selected account is pinned to session (`sticky`); +- on `401`, `403`, `429` account goes into cooldown; +- if sticky-account session went into cooldown, old DeepSeek session is reset to avoid hammering rate-limited/expired account; +- account status visible in `/health` without auth file paths or file names; +- auth files must be stored with `0600` permissions. + +Configure cooldown: +>>>>>>> Stashed changes ```bash DEEPSEEK_ACCOUNT_COOLDOWN_MS=600000 npm start @@ -299,6 +516,7 @@ DEEPSEEK_ACCOUNT_COOLDOWN_MS=600000 npm start --- +<<<<<<< Updated upstream ## 🔑 Идеи для консольной авторизации Парольный flow из PR #3 можно делать, но безопаснее не хранить пароль и не делать это дефолтом. Нормальная реализация: @@ -315,6 +533,24 @@ DEEPSEEK_ACCOUNT_COOLDOWN_MS=600000 npm start --- ## ✅ Проверка работы +======= +## 🔑 Console auth ideas + +Password flow from PR #3 can be done, but safer not to store password and not make it default. Normal implementation: + +1. `npm run auth:console` asks for email/phone and password via hidden prompt. +2. Password stays only in process memory, not written to files/logs/history. +3. Script replicates Web login flow via `fetch`/CDP: gets captcha/verify challenge, gives human link/code, waits for confirmation. +4. After successful login, only standard-format `deepseek-auth.json` is saved. +5. If DeepSeek asks for captcha/2FA — script honestly says "open link, pass check, press Enter", doesn't try to bypass protection. +6. For VPS better mode `auth:console --no-save-password --output deepseek-auth.json`. + +Minimal safe MVP: console auth only interactive, no env password. Acceptable automation variant: `DEEPSEEK_EMAIL=... npm run auth:console`, but password still entered via hidden prompt. + +--- + +## ✅ Verify it works +>>>>>>> Stashed changes ```bash curl http://localhost:9655/ @@ -322,11 +558,19 @@ curl http://localhost:9655/v1/models curl http://localhost:9655/v1/model-capabilities ``` +<<<<<<< Updated upstream Если всё ок, `/health` вернёт статус сервера, список поддерживаемых aliases и `config_ready: true`. --- ## 🧪 Примеры запросов +======= +If all good, `/health` returns server status, list of supported aliases, and `config_ready: true`. + +--- + +## 🧪 Request examples +>>>>>>> Stashed changes ### Chat Completions @@ -335,7 +579,11 @@ curl -X POST http://localhost:9655/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "deepseek-chat", +<<<<<<< Updated upstream "messages": [{"role": "user", "content": "Привет! Ответь одной фразой."}], +======= + "messages": [{"role": "user", "content": "Hi! Reply with one phrase."}], +>>>>>>> Stashed changes "stream": false }' ``` @@ -347,18 +595,30 @@ curl -X POST http://localhost:9655/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "deepseek-reasoner", +<<<<<<< Updated upstream "messages": [{"role": "user", "content": "Реши коротко: почему небо голубое?"}], +======= + "messages": [{"role": "user", "content": "Solve briefly: why is the sky blue?"}], +>>>>>>> Stashed changes "stream": false }' ``` +<<<<<<< Updated upstream Для reasoning-моделей API отдаёт цепочку размышления отдельно от финального ответа: +======= +For reasoning models, API returns the reasoning chain separately from the final answer: +>>>>>>> Stashed changes - non-stream: `choices[0].message.reasoning_content` - stream: `choices[0].delta.reasoning_content` - usage: `usage.completion_tokens_details.reasoning_tokens` +<<<<<<< Updated upstream `reasoning_tokens` — приблизительная оценка по извлечённому DeepSeek Web `THINK`-тексту, потому что web stream не отдаёт официальный token usage по reasoning отдельно. +======= +`reasoning_tokens` — approximate estimate based on extracted DeepSeek Web `THINK` text, because web stream doesn't return official token usage for reasoning separately. +>>>>>>> Stashed changes ### Web search @@ -367,7 +627,11 @@ curl -X POST http://localhost:9655/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "deepseek-chat-search", +<<<<<<< Updated upstream "messages": [{"role": "user", "content": "Найди свежий факт про DeepSeek и ответь кратко."}], +======= + "messages": [{"role": "user", "content": "Find a fresh fact about DeepSeek and reply briefly."}], +>>>>>>> Stashed changes "stream": false }' ``` @@ -379,7 +643,11 @@ curl -N -X POST http://localhost:9655/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "deepseek-chat", +<<<<<<< Updated upstream "messages": [{"role": "user", "content": "Напиши короткую шутку."}], +======= + "messages": [{"role": "user", "content": "Write a short joke."}], +>>>>>>> Stashed changes "stream": true }' ``` @@ -392,12 +660,20 @@ curl -X POST http://localhost:9655/v1/messages \ -d '{ "model": "deepseek-chat", "max_tokens": 512, +<<<<<<< Updated upstream "messages": [{"role": "user", "content": "Ответь ровно OK"}], +======= + "messages": [{"role": "user", "content": "Reply exactly OK"}], +>>>>>>> Stashed changes "stream": false }' ``` +<<<<<<< Updated upstream Для Claude Code можно указывать backend напрямую: +======= +For Claude Code you can specify backend directly: +>>>>>>> Stashed changes ```bash export ANTHROPIC_BASE_URL="http://127.0.0.1:9655" @@ -406,6 +682,9 @@ export CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1 claude --model deepseek-chat ``` +### OpenAI +``` + ### OpenAI Responses API ```bash @@ -413,19 +692,28 @@ curl -X POST http://localhost:9655/v1/responses \ -H "Content-Type: application/json" \ -d '{ "model": "deepseek-chat", +<<<<<<< Updated upstream "input": "Ответь ровно OK", +======= + "input": "Reply exactly OK", +>>>>>>> Stashed changes "stream": false }' ``` ### Tool calling +<<<<<<< Updated upstream FreeDeepseekAPI принимает: +======= +FreeDeepseekAPI accepts: +>>>>>>> Stashed changes - OpenAI `tools`; - Anthropic `tools`; - Responses API function tools. +<<<<<<< Updated upstream Прокси просит DeepSeek вернуть строгий JSON tool call, но также умеет парсить fallback-форматы: - `TOOL_CALL:` @@ -557,3 +845,10 @@ FreeDeepseekAPI — экспериментальный web-chat proxy для л

ForgetMeAI · Telegram

+======= +Proxy asks DeepSeek to return strict JSON tool call, but also parses fallback formats: + +- `TOOL_CALL:` +- fenced JSON +- `... +>>>>>>> Stashed changes diff --git a/chrome-extension/README.md b/chrome-extension/README.md index 169369d..12f7dae 100644 --- a/chrome-extension/README.md +++ b/chrome-extension/README.md @@ -1,15 +1,27 @@ +<<<<<<< Updated upstream # DeepSeek → FreeDeepseekAPI (расширение) Добавляет аккаунт DeepSeek в локальный FreeDeepseekAPI **одним кликом**: перехватывает заголовки реального запроса к `chat.deepseek.com/api/...` (`token` из `Authorization`, все cookie, `hif_*`) и отправляет на `http://localhost:9655/api/accounts/import`. +======= +# DeepSeek → FreeDeepseekAPI (extension) + +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). +>>>>>>> Stashed changes Работает в Firefox и Chrome/Edge (Manifest V3). ## Установка **Firefox** +<<<<<<< Updated upstream 1. Откройте `about:debugging#/runtime/this-firefox` 2. «Загрузить временное дополнение» → выберите `manifest.json` из этой папки. (Временное дополнение: после перезапуска Firefox установить заново.) @@ -29,3 +41,24 @@ Вспомогательные кнопки: «Собрать» (показать креды), «Копировать JSON», «Скачать файл» (`deepseek-auth.json`) — на случай ручного импорта через дашборд. +======= +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. Open `chrome://extensions` +2. Enable "Developer mode". +3. "Load unpacked" → select this folder. + +## 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. + +Auxiliary buttons: "Collect" (show creds), "Copy JSON", +"Download file" (`deepseek-auth.json`) — for manual import via dashboard. +>>>>>>> Stashed changes diff --git a/chrome-extension/background.js b/chrome-extension/background.js index 481c500..73741d1 100644 --- a/chrome-extension/background.js +++ b/chrome-extension/background.js @@ -1,18 +1,32 @@ +<<<<<<< Updated upstream // 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. +>>>>>>> Stashed changes const WASM_DEFAULT = 'https://fe-static.deepseek.com/chat/static/sha3_wasm_bg.7b9ca65ddd.wasm'; const KEY = 'deepseek_capture'; +<<<<<<< Updated upstream // extraHeaders нужен Chrome для доступа к Cookie/Authorization; Firefox даёт их без него. +======= +// extraHeaders needed for Chrome to access Cookie/Authorization; Firefox provides them without it. +>>>>>>> Stashed changes const opts = ['requestHeaders']; try { if (chrome.webRequest.OnBeforeSendHeadersOptions && chrome.webRequest.OnBeforeSendHeadersOptions.EXTRA_HEADERS) { opts.push('extraHeaders'); } +<<<<<<< Updated upstream } catch (e) { /* Firefox: опции нет — это нормально */ } +======= +} catch (e) { /* Firefox: option doesn't exist — this is normal */ } +>>>>>>> Stashed changes chrome.webRequest.onBeforeSendHeaders.addListener( (details) => { @@ -42,4 +56,8 @@ chrome.runtime.onMessage.addListener((req, sender, sendResponse) => { chrome.storage.local.get(KEY, (r) => sendResponse({ success: true, cap: r[KEY] || null })); return true; // async } +<<<<<<< Updated upstream }); +======= +}); +>>>>>>> Stashed changes diff --git a/chrome-extension/content.js b/chrome-extension/content.js index ad04f8c..8e62399 100644 --- a/chrome-extension/content.js +++ b/chrome-extension/content.js @@ -13,4 +13,4 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { } sendResponse({ data }); } -}); +}); \ No newline at end of file diff --git a/chrome-extension/manifest.json b/chrome-extension/manifest.json index 5501740..7133d4c 100644 --- a/chrome-extension/manifest.json +++ b/chrome-extension/manifest.json @@ -2,7 +2,11 @@ "manifest_version": 3, "name": "DeepSeek → FreeDeepseekAPI", "version": "1.4", +<<<<<<< Updated upstream "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).", +>>>>>>> Stashed changes "author": "FreeDeepseekAPI", "browser_specific_settings": { "gecko": { @@ -27,4 +31,8 @@ "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" } +<<<<<<< Updated upstream } +======= +} +>>>>>>> Stashed changes diff --git a/chrome-extension/popup.html b/chrome-extension/popup.html index 05f1be5..86ea38a 100644 --- a/chrome-extension/popup.html +++ b/chrome-extension/popup.html @@ -57,19 +57,34 @@

🔑 DeepSeek → FreeDeepseekAPI

+<<<<<<< Updated upstream
Откройте chat.deepseek.com, отправьте любое сообщение, затем нажмите кнопку
+======= + + +
Open chat.deepseek.com, send any message, then click the button
+>>>>>>> Stashed changes
⏳ Loading...
+<<<<<<< Updated upstream
+======= + +
+
+ + + +>>>>>>> Stashed changes
@@ -81,10 +96,17 @@

🔑 DeepSeek → FreeDeepseekAPI

+<<<<<<< Updated upstream Пул аккаунтов +======= + Account Pool + + + +>>>>>>> Stashed changes
@@ -92,4 +114,4 @@

🔑 DeepSeek → FreeDeepseekAPI

- + \ No newline at end of file diff --git a/chrome-extension/popup.js b/chrome-extension/popup.js index 423ea09..02e0f13 100644 --- a/chrome-extension/popup.js +++ b/chrome-extension/popup.js @@ -3,29 +3,51 @@ function $(id) { return document.getElementById(id); } const API_BASE = 'http://localhost:9655'; const PROXY_URL = API_BASE + '/api/accounts/import'; +<<<<<<< Updated upstream let current = null; // перехваченный набор кредов {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)'; } } +======= +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 ? 'Server online (:9655)' : 'Server unavailable (:9655)'; } } +>>>>>>> Stashed changes 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) { +<<<<<<< Updated upstream setStatus('warn', '⚠️ Откройте chat.deepseek.com и ОТПРАВЬТЕ любое сообщение, затем нажмите кнопку.'); $('jsonPreview').textContent = '{ }'; $('detail').textContent = 'Креды появятся после запроса к DeepSeek'; +======= + setStatus('warn', '⚠️ Open chat.deepseek.com and SEND any message, then click the button.'); + $('jsonPreview').textContent = '{ }'; + $('detail').textContent = 'Creds will appear after request to DeepSeek'; +>>>>>>> Stashed changes 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 }; +<<<<<<< Updated upstream // превью с маскировкой секретов +======= + // preview with masked secrets +>>>>>>> Stashed changes $('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); +<<<<<<< Updated upstream 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()) : ''; +>>>>>>> Stashed changes setCredButtons(true); return auth; } @@ -34,7 +56,11 @@ function refresh() { chrome.runtime.sendMessage({ action: 'get' }, (r) => { current = (r && r.success) ? render(r.cap) : render(null); }); } +<<<<<<< Updated upstream // ── Панель пула (строится через DOM API, без innerHTML, чтобы исключить XSS) ── +======= +// ── Pool panel (built via DOM API, no innerHTML, to rule out XSS) ── +>>>>>>> Stashed changes function mkBtn(act, label, title, cls) { const b = document.createElement('button'); b.className = cls; b.dataset.act = act; b.title = title; b.textContent = label; @@ -48,8 +74,13 @@ function setEmpty(text) { } function renderPool(list) { +<<<<<<< Updated upstream $('poolTitle').textContent = `Пул аккаунтов (${list.length})`; if (!list.length) { setEmpty('Нет аккаунтов'); return; } +======= + $('poolTitle').textContent = `Account pool (${list.length})`; + if (!list.length) { setEmpty('No accounts'); return; } +>>>>>>> Stashed changes const pool = $('pool'); pool.textContent = ''; for (const a of list) { const row = document.createElement('div'); @@ -66,7 +97,11 @@ function renderPool(list) { const actions = document.createElement('span'); actions.className = 'row-actions'; +<<<<<<< Updated upstream actions.append(mkBtn('check', '↻', 'Проверить', 'acc-btn'), mkBtn('del', '✕', 'Удалить', 'acc-btn danger')); +======= + actions.append(mkBtn('check', '↻', 'Check', 'acc-btn'), mkBtn('del', '✕', 'Delete', 'acc-btn danger')); +>>>>>>> Stashed changes row.append(idEl, emailEl, badge, actions); pool.appendChild(row); @@ -81,8 +116,13 @@ async function loadPool() { renderPool(j.accounts || []); } catch { setSrvDot(false); +<<<<<<< Updated upstream $('poolTitle').textContent = 'Пул аккаунтов'; setEmpty('FreeDeepseekAPI недоступен на :9655'); +======= + $('poolTitle').textContent = 'Account pool'; + setEmpty('FreeDeepseekAPI unavailable on :9655'); +>>>>>>> Stashed changes } } @@ -104,12 +144,20 @@ async function deleteAccount(id) { loadPool(); } +<<<<<<< Updated upstream // делегирование кликов в панели (check / удаление с инлайн-подтверждением) +======= +// click delegation in panel (check / delete with inline confirmation) +>>>>>>> Stashed changes $('pool').addEventListener('click', (e) => { const emailEl = e.target.closest('.email'); if (emailEl && emailEl.textContent && emailEl.textContent !== '—') { const full = emailEl.title || emailEl.textContent; +<<<<<<< Updated upstream 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); }); +>>>>>>> Stashed changes return; } const btn = e.target.closest('.acc-btn'); if (!btn) return; @@ -118,7 +166,11 @@ $('pool').addEventListener('click', (e) => { if (act === 'check') { checkAccount(id); return; } if (act === 'del') { const actions = btn.parentElement; actions.textContent = ''; +<<<<<<< Updated upstream actions.append(mkBtn('yes', '✓', 'Удалить', 'acc-btn confirm'), mkBtn('no', '✗', 'Отмена', 'acc-btn')); +======= + actions.append(mkBtn('yes', '✓', 'Delete', 'acc-btn confirm'), mkBtn('no', '✗', 'Cancel', 'acc-btn')); +>>>>>>> Stashed changes return; } if (act === 'yes') deleteAccount(id); @@ -134,6 +186,7 @@ async function checkAll() { $('btnCheckAll').addEventListener('click', checkAll); $('btnDashboard').addEventListener('click', () => { chrome.tabs.create({ url: API_BASE + '/dashboard' }); }); +<<<<<<< Updated upstream // ── Главная кнопка — добавить перехваченные креды + авто-валидация ── $('btnAdd').addEventListener('click', async () => { if (!current) { @@ -142,11 +195,22 @@ $('btnAdd').addEventListener('click', async () => { return; } setStatus('warn', '⏳ Отправка в FreeDeepseekAPI…'); +======= +// ── Main button — add captured creds + auto-validation ── +$('btnAdd').addEventListener('click', async () => { + if (!current) { + refresh(); + setStatus('warn', '⏳ No creds. Send a message in DeepSeek and try again.'); + return; + } + setStatus('warn', '⏳ Sending to FreeDeepseekAPI…'); +>>>>>>> Stashed changes 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})` : ''; +<<<<<<< Updated upstream setStatus('ok', `✅ Добавлен как ${j.id}${who} — проверяю…`); await loadPool(); const st = await checkAccount(j.id); @@ -160,6 +224,21 @@ $('btnAdd').addEventListener('click', async () => { } } catch (e) { setStatus('err', '❌ FreeDeepseekAPI недоступен на localhost:9655 (запущен?)'); +======= + setStatus('ok', `✅ Added as ${j.id}${who} — checking…`); + await loadPool(); + const st = await checkAccount(j.id); + if (st === 'OK') setStatus('ok', `🟢 ${j.id}${who} — working`); + else setStatus('err', `🔴 ${j.id} — status: ${st || 'unknown'}`); + } else if (j.existingId) { + setStatus('warn', `⚠️ Already added as ${j.existingId}`); + loadPool(); + } else { + setStatus('err', '❌ ' + (j.error || 'Add error')); + } + } catch (e) { + setStatus('err', '❌ FreeDeepseekAPI unavailable on localhost:9655 (running?)'); +>>>>>>> Stashed changes } }); @@ -168,7 +247,11 @@ $('btnCollect').addEventListener('click', refresh); $('btnCopy').addEventListener('click', () => { if (!current) return; navigator.clipboard.writeText(JSON.stringify(current, null, 2)).then(() => { +<<<<<<< Updated upstream $('btnCopy').textContent = '✅'; setTimeout(() => { $('btnCopy').textContent = '📋 Копировать JSON'; }, 1200); +======= + $('btnCopy').textContent = '✅'; setTimeout(() => { $('btnCopy').textContent = '📋 Copy JSON'; }, 1200); +>>>>>>> Stashed changes }); }); @@ -181,4 +264,8 @@ $('btnSave').addEventListener('click', () => { }); refresh(); +<<<<<<< Updated upstream +loadPool(); +======= loadPool(); +>>>>>>> Stashed changes diff --git a/lib/parseAuth.js b/lib/parseAuth.js index b406925..6e49d4c 100644 --- a/lib/parseAuth.js +++ b/lib/parseAuth.js @@ -1,9 +1,16 @@ 'use strict'; /* +<<<<<<< Updated upstream Общий парсер авторизации DeepSeek из "Copy as cURL" или HAR-файла. Используется и CLI-скриптами (scripts/auth_from_*.js), и эндпоинтом дашборда POST /api/accounts/import. Возвращает плоский объект { token, cookie, hif_dliq, hif_leim, wasmUrl } или { error }. +======= + Common DeepSeek auth parser from "Copy as cURL" or HAR file. + Used by both CLI scripts (scripts/auth_from_*.js) and dashboard + endpoint POST /api/accounts/import. Returns flat object + { token, cookie, hif_dliq, hif_leim, wasmUrl } or { error }. +>>>>>>> Stashed changes */ const WASM_DEFAULT = 'https://fe-static.deepseek.com/chat/static/sha3_wasm_bg.7b9ca65ddd.wasm'; @@ -47,7 +54,11 @@ function parseHar(harText) { const entries = (har.log && har.log.entries) || []; const hv = (hs, n) => { const x = (hs || []).find(y => (y.name || '').toLowerCase() === n); return x ? (x.value || '') : ''; }; +<<<<<<< Updated upstream // выбираем лучший запрос к deepseek с Authorization: Bearer +======= + // pick best request to deepseek with Authorization: Bearer +>>>>>>> Stashed changes let best = null; for (const e of entries) { const req = e.request || {}; @@ -63,14 +74,22 @@ function parseHar(harText) { best = { score, token: auth.replace(/^Bearer\s+/i, '').trim(), cookie, hif_dliq: dliq, hif_leim: leim }; } } +<<<<<<< Updated upstream if (!best) return { error: 'В HAR нет запросов к deepseek.com с заголовком Authorization: Bearer' }; +======= + if (!best) return { error: 'No requests to deepseek.com with Authorization: Bearer header in HAR' }; +>>>>>>> Stashed changes let wasmUrl = ''; for (const e of entries) { const u = (e.request && e.request.url) || ''; if (/sha3.*\.wasm/i.test(u)) { wasmUrl = u; break; } } return { token: best.token, cookie: best.cookie, hif_dliq: best.hif_dliq, hif_leim: best.hif_leim, wasmUrl }; } +<<<<<<< Updated upstream // Авто-определение формата ввода (HAR — JSON с log.entries; иначе cURL). +======= +// Auto-detect input format (HAR — JSON with log.entries; otherwise cURL). +>>>>>>> Stashed changes function parseAuthInput(text) { const s = String(text || '').trim(); if (!s) return { error: 'Пустой ввод' }; @@ -86,11 +105,19 @@ function parseAuthInput(text) { if (!r.error) return r; // это был HAR } if (/\bcurl\b|--header|(^|\s)-H\s/i.test(s)) return parseCurl(s); +<<<<<<< Updated upstream // последняя попытка — вдруг HAR без явного префикса return parseHar(s); } // Валидация + проставление wasmUrl (из ввода → из прошлого аккаунта → дефолт). +======= + // last attempt — maybe HAR without explicit prefix + return parseHar(s); +} + +// Validation + fill wasmUrl (from input → from previous account → default). +>>>>>>> Stashed changes function finalizeAuth(parsed, prevWasmUrl) { if (!parsed || parsed.error) return parsed || { error: 'Пусто' }; const missing = []; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..1dcd0ad --- /dev/null +++ b/package-lock.json @@ -0,0 +1,20 @@ +{ + "name": "free-deepseek-api", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "free-deepseek-api", + "version": "0.1.0", + "license": "MIT", + "bin": { + "free-deepseek-api": "server.js", + "free-deepseek-client": "client.js" + }, + "engines": { + "node": ">=18.0.0" + } + } + } +} diff --git a/public/dashboard.html b/public/dashboard.html index 8ce1a9c..a5795c5 100644 --- a/public/dashboard.html +++ b/public/dashboard.html @@ -93,7 +93,11 @@ .chip{font-family:var(--mono);font-size:var(--fs-xs);background:var(--card2);border:1px solid var(--border);border-radius:6px;padding:7px 10px;cursor:pointer;transition:.12s;display:flex;align-items:center;gap:6px;color:var(--text)} .chip:hover{border-color:var(--accent)} +<<<<<<< Updated upstream /* аккаунты — прямые полосы статуса */ +======= + /* accounts — direct status bars */ +>>>>>>> Stashed changes .spacer{flex:1} .acc{display:flex;align-items:center;gap:var(--s3);padding:10px 12px;background:var(--card2);border:1px solid var(--border);border-left:3px solid var(--faint);border-radius:0 var(--r) var(--r) 0;margin-bottom:6px;flex-wrap:wrap} .acc.OK{border-left-color:var(--ok)} .acc.WAIT{border-left-color:var(--warn)} @@ -122,7 +126,11 @@ .bubble .role{font-size:10px;text-transform:uppercase;letter-spacing:.06em;opacity:.65;margin-bottom:4px;font-weight:700} .bubble.streaming .body::after{content:"▍";animation:blink 1s steps(2) infinite;color:var(--accent)} @keyframes blink{50%{opacity:0}} +<<<<<<< Updated upstream /* панель размышлений */ +======= + /* reasoning panel */ +>>>>>>> Stashed changes .think{margin-bottom:8px;border:1px solid var(--border);border-radius:8px;background:var(--bg2);overflow:hidden} .think summary{cursor:pointer;padding:6px 10px;color:var(--muted);font-weight:600;font-size:var(--fs-xs);display:flex;align-items:center;gap:6px;user-select:none;list-style:none} .think summary::-webkit-details-marker{display:none} @@ -179,6 +187,7 @@

FreeDeepseekAPI

+<<<<<<< Updated upstream проверка…
@@ -194,11 +203,29 @@

FreeDeepseekAPI

Плейграунд чата
+>>>>>>> Stashed changes +<<<<<<< Updated upstream

@@ -206,10 +233,20 @@

Плейграунд чата +======= + +

+ +
+
+ + +>>>>>>> Stashed changes
+<<<<<<< Updated upstream
@@ -221,10 +258,24 @@

Аккаунты DeepSeek Добавить аккаунт: в браузере (где вошли в DeepSeek) F12 → Network → отправьте сообщение → правый клик на запросе к chat.deepseek.com/api/...Copy as cURL (или Save All As HAR) → вставьте сюда.

+======= + +
+
+

DeepSeek Accounts + +

+
loading…
+
+
Add Account: in browser (where logged into DeepSeek) F12 → Network → send message → right-click request to chat.deepseek.com/api/...Copy as cURL (or Save All As HAR) → paste here.
+ +
+>>>>>>> Stashed changes
+<<<<<<< Updated upstream
@@ -238,10 +289,26 @@

Как подключить

любой непустой (sk-deepseek)
deepseek-chat
+======= + +
+
+
status
+
models
+
accounts online
+
+
+
+

How to Connect

+
+
any non-empty (sk-deepseek)
+
deepseek-chat
+>>>>>>> Stashed changes
+<<<<<<< Updated upstream
OpenAI-совместимо. Также есть Anthropic-шим /v1/messages и Responses /v1/responses.
@@ -253,11 +320,28 @@

Авторизация DeepSeekМодели (0) R reasoning · S web-поиск · клик копирует

загрузка…
+======= +
OpenAI-compatible. Also has Anthropic shim /v1/messages and Responses /v1/responses.
+
+
+

DeepSeek Authorization

+
loading…
+
Refresh login: run DeepSeek Authorization.bat, log into DeepSeek in the opened window, send ok and press Enter in terminal.
+
+
+

Models (0) + R reasoning · S web search · click copies

+
loading…
+>>>>>>> Stashed changes
+<<<<<<< Updated upstream +======= + +>>>>>>> Stashed changes
@@ -268,17 +352,29 @@

Модели (`; function toast(m, kind){ const t=$('#toast'); t.innerHTML=(kind==='err'?icon('i-warn'):kind==='ok'?icon('i-check'):'')+''+esc(m)+''; t.className='toast show'+(kind?' '+kind:''); clearTimeout(toast._t); toast._t=setTimeout(()=>t.className='toast',1900); } async function copyText(t){ +<<<<<<< Updated upstream try{ if(navigator.clipboard && isSecureContext){ await navigator.clipboard.writeText(t); return toast('Скопировано','ok'); } throw 0; } catch{ try{ const a=document.createElement('textarea'); a.value=t; a.style.position='fixed'; a.style.opacity='0'; document.body.appendChild(a); a.select(); document.execCommand('copy'); a.remove(); toast('Скопировано','ok'); }catch{ toast('Не удалось скопировать','err'); } } } function mdRender(text){ try{ if(window.marked&&window.DOMPurify){ const html=window.marked.parse(text,{breaks:true}); return window.DOMPurify.sanitize(html,{ADD_ATTR:['target']}); } }catch{} return null; } // стабильный agent-id для серверной сессии DeepSeek (многоходовость) +======= + try{ if(navigator.clipboard && isSecureContext){ await navigator.clipboard.writeText(t); return toast('Copied','ok'); } throw 0; } + catch{ try{ const a=document.createElement('textarea'); a.value=t; a.style.position='fixed'; a.style.opacity='0'; document.body.appendChild(a); a.select(); document.execCommand('copy'); a.remove(); toast('Copied','ok'); }catch{ toast('Failed to copy','err'); } } +} +function mdRender(text){ try{ if(window.marked&&window.DOMPurify){ const html=window.marked.parse(text,{breaks:true}); return window.DOMPurify.sanitize(html,{ADD_ATTR:['target']}); } }catch{} return null; } +// stable agent-id for server-side DeepSeek session (multi-turn) +>>>>>>> Stashed changes const AGENT_ID = (()=>{ let v=localStorage.getItem('ds_agent'); if(!v){ v='dash-'+Math.abs(Date.now()^(performance.now()*1000|0)).toString(36); localStorage.setItem('ds_agent',v); } return v; })(); const apiBase = origin+'/v1'; $('#baseUrl').textContent = apiBase; +<<<<<<< Updated upstream // ── Вкладки ────────────────────────────────────────────────────────────── +======= +// ── Tabs ────────────────────────────────────────────────────────────── +>>>>>>> Stashed changes const TABS=['chat','accounts','overview']; const inited={}; function showTab(name){ @@ -304,10 +400,17 @@

Модели (copyText(`from openai import OpenAI\nclient = OpenAI(base_url="${apiBase}", api_key="sk-deepseek")\nr = client.chat.completions.create(model="deepseek-chat",\n messages=[{"role":"user","content":"Привет"}])\nprint(r.choices[0].message.content)`); // ── Статус / KPI / авторизация ─────────────────────────────────────────── +======= +$('#copyCurl').onclick=()=>copyText(`curl ${apiBase}/chat/completions \\\n -H "Authorization: Bearer sk-deepseek" \\\n -H "Content-Type: application/json" \\\n -d '{"model":"deepseek-chat","messages":[{"role":"user","content":"Hi"}]}'`); +$('#copyPy').onclick=()=>copyText(`from openai import OpenAI\nclient = OpenAI(base_url="${apiBase}", api_key="sk-deepseek")\nr = client.chat.completions.create(model="deepseek-chat",\n messages=[{"role":"user","content":"Hi"}])\nprint(r.choices[0].message.content)`); + +// ── Status / KPI / Auth ─────────────────────────────────────────── +>>>>>>> Stashed changes let healthCache=null; async function loadHealth(){ try{ @@ -315,6 +418,7 @@

Модели (Модели (Авторизация не настроена. Запустите Авторизация DeepSeek.bat и войдите в свой аккаунт DeepSeek.

'; return; } const exp=j.token_exp?('
'+esc(fmtExp(j.token_exp))+'
'):''; box.innerHTML=`
${j.has_token?'есть ✓':'нет'}
@@ -336,10 +450,23 @@

Модели (Authorization not configured. Run DeepSeek Authorization.bat and log into your DeepSeek account.'; return; } + const exp=j.token_exp?('
'+esc(fmtExp(j.token_exp))+'
'):''; + box.innerHTML=`
${j.has_token?'present ✓':'missing'}
+
${j.has_cookie?'present ✓':'missing'}
+
${j.has_hif?'present ✓':'optional'}
${exp}`; + }catch{ box.innerHTML='
Failed to get authorization status
'; } +} +// ── Accounts ─────────────────────────────────────────────────────────────── +const accStLabel=s=>({OK:'active',WAIT:'limit',INVALID:'invalid',EXPIRED:'expired',ERROR:'error'})[s]||s; +function fmtResetAt(iso){ try{ return new Date(iso).toLocaleString('en-US',{day:'2-digit',month:'2-digit',hour:'2-digit',minute:'2-digit'}); }catch{ return ''; } } +>>>>>>> Stashed changes async function loadAccountsList(){ const list=$('#accList'); try{ const j=await (await fetch(origin+'/api/accounts')).json(); +<<<<<<< Updated upstream if(!j.accounts||!j.accounts.length){ list.innerHTML='
Пока нет аккаунтов. Добавьте первый через импорт ниже.
'; return; } list.innerHTML=j.accounts.map(a=>{ const meta=a.resetAt?('лимит до '+fmtResetAt(a.resetAt)):(a.exp?fmtExp(a.exp):''); @@ -354,10 +481,27 @@

Модели (Не удалось загрузить аккаунты. '; } } function setAccUpdated(){ const e=$('#accUpdated'); if(e) e.textContent='обновлено '+new Date().toLocaleTimeString('ru-RU',{hour:'2-digit',minute:'2-digit'}); } +======= + if(!j.accounts||!j.accounts.length){ list.innerHTML='
No accounts yet. Add first via import below.
'; return; } + list.innerHTML=j.accounts.map(a=>{ + const meta=a.resetAt?('limit until '+fmtResetAt(a.resetAt)):(a.exp?fmtExp(a.exp):''); + const email=a.email?`${esc(a.email)}`:''; + const labelHtml=a.label?`${esc(a.label)}`:''; + return `
${esc(a.id)}${labelHtml}${accStLabel(a.status)}${email}…${esc(a.preview||'')}${esc(meta)} + + +
`; + }).join(''); + setAccUpdated(); + }catch{ list.innerHTML='
Failed to load accounts.
'; } +} +function setAccUpdated(){ const e=$('#accUpdated'); if(e) e.textContent='updated '+new Date().toLocaleTimeString('en-US',{hour:'2-digit',minute:'2-digit'}); } +>>>>>>> Stashed changes $('#checkAllAcc').onclick=async()=>{ const b=$('#checkAllAcc'); b.disabled=true; try{ const j=await (await fetch(origin+'/api/accounts')).json(); for(const a of (j.accounts||[])){ try{ await fetch(origin+'/api/accounts/'+encodeURIComponent(a.id)+'/check',{method:'POST'}); }catch{} } +<<<<<<< Updated upstream await loadAccountsList(); await loadHealth(); toast('Проверка завершена','ok'); }catch{ toast('Ошибка','err'); } b.disabled=false; }; @@ -367,12 +511,24 @@

Модели (Модели (Модели (R':'')+(c.search?'S':''); return `${esc(m.id)}${caps}`; }).join('')||'не удалось загрузить'; }catch{ list.innerHTML='не удалось загрузить. '; } } // #chatModel наполняется реальными моделями из /v1/models; выбор сохраняется в localStorage +======= + const caps=(c.reasoning?'R':'')+(c.search?'S':''); + return `${esc(m.id)}${caps}`; + }).join('')||'failed to load'; + }catch{ list.innerHTML='failed to load. '; } +} +// #chatModel is populated with real models from /v1/models; selection saved to localStorage +>>>>>>> Stashed changes async function populateChatModels(){ const sel=$('#chatModel'); if(!sel) return; try{ @@ -415,6 +602,7 @@

Модели (${esc(m.id)}`).join(''); if(saved && data.some(m=>m.id===saved)) sel.value=saved; +<<<<<<< Updated upstream }catch{ /* оставляем статичный fallback-список */ } } document.addEventListener('click', e=>{ const c=e.target.closest('.chip[data-model]'); if(c) copyText(c.dataset.model); }); @@ -430,6 +618,23 @@

Модели ('; +======= + }catch{ /* keep static fallback list */ } +} +document.addEventListener('click', e=>{ const c=e.target.closest('.chip[data-model]'); if(c) copyText(c.dataset.model); }); + +// Model is taken directly from selector (#chatModel). +// Web search (-search) currently disabled: DeepSeek Web consistently returns empty response. + +// ── Chat (history + reasoning + markdown, via /v1/chat/completions) ────────── +let chatHistory=[], chatBusy=false, chatAbort=null; +function bubbleEl(m){ + const d=document.createElement('div'); d.className='bubble '+m.role+(m.err?' err':''); + d.innerHTML='
'+(m.role==='user'?'You':'AI')+'
'; + if(m.role==='assistant' && m.reasoning){ + const det=document.createElement('details'); det.className='think'; + det.innerHTML=''+icon('i-bulb')+'Reasoning
'; +>>>>>>> Stashed changes det.querySelector('.think-body').textContent=m.reasoning; d.appendChild(det); d._think=det; } @@ -440,13 +645,18 @@

Модели ('+icon('i-chat')+'Начните диалог. Ответ — стримингом с markdown; у reasoner-моделей появится панель «Размышления».'; return; } box.innerHTML=''; chatHistory.forEach(m=>box.appendChild(bubbleEl(m))); box.scrollTop=box.scrollHeight; } @@ -456,6 +666,17 @@

Модели ('; +======= + if(!chatHistory.length){ box.innerHTML='
'+icon('i-chat')+'Start dialog. Response streams with markdown; reasoner models will show \'Reasoning\' panel.
'; return; } + box.innerHTML=''; chatHistory.forEach(m=>box.appendChild(bubbleEl(m))); box.scrollTop=box.scrollHeight; +} +function atBottom(){ const b=$('#msgs'); return b.scrollHeight-b.scrollTop-b.clientHeight<60; } +function setBusy(b){ chatBusy=b; const btn=$('#sendChat'); btn.innerHTML=b?'Stop':icon('i-send')+'Send'; btn.classList.toggle('danger',b); } +function ensureThink(node, asst){ + if(node._think) return node._think.querySelector('.think-body'); + const det=document.createElement('details'); det.className='think'; det.open=true; + det.innerHTML=''+icon('i-bulb')+'Reasoning
'; +>>>>>>> Stashed changes node.insertBefore(det, node._c); node._think=det; return det.querySelector('.think-body'); } @@ -487,9 +708,15 @@

Модели (Модели (Модели (>>>>>> Stashed changes capabilities: { reasoning: false, web_search: false, files: true }, supported: true, }, 'deepseek-v3': { model_type: 'default', thinking_enabled: false, search_enabled: false, +<<<<<<< Updated upstream real_model: 'DeepSeek-V4-Flash non-thinking (DeepSeek Web “Быстрый” / default)', +======= + real_model: 'DeepSeek-V4-Flash non-thinking (DeepSeek Web “Fast” / default)', +>>>>>>> Stashed changes capabilities: { reasoning: false, web_search: false, files: true }, supported: true, }, 'deepseek-default': { model_type: 'default', thinking_enabled: false, search_enabled: false, +<<<<<<< Updated upstream real_model: 'DeepSeek-V4-Flash non-thinking (DeepSeek Web “Быстрый” / default)', +======= + real_model: 'DeepSeek-V4-Flash non-thinking (DeepSeek Web “Fast” / default)', +>>>>>>> Stashed changes capabilities: { reasoning: false, web_search: false, files: true }, supported: true, }, // Same DeepSeek Web default model, but with thinking_enabled=true. UI exposes it as thinking/reasoning mode. 'deepseek-reasoner': { model_type: 'default', thinking_enabled: true, search_enabled: false, +<<<<<<< Updated upstream real_model: 'DeepSeek-V4-Flash thinking mode (DeepSeek Web “Быстрый” + thinking_enabled)', +======= + real_model: 'DeepSeek-V4-Flash thinking mode (DeepSeek Web “Fast” + thinking_enabled)', +>>>>>>> Stashed changes capabilities: { reasoning: true, web_search: false, files: true }, supported: true, }, @@ -1831,12 +1851,21 @@ async function showStartupMenu() { printStatus(); console.log('\n=== Меню ==='); console.log(`ForgetMeAI: ${FORGETMEAI_WATERMARK}`); +<<<<<<< Updated upstream console.log('1 - Авторизоваться / обновить DeepSeek login'); console.log('2 - Импортировать auth-файл / cookies'); console.log('3 - Показать модели и статусы'); console.log('4 - Запустить прокси (по умолчанию)'); console.log('5 - Выход'); let choice = await prompt('Ваш выбор (Enter = 4): '); +======= + console.log('1 - Authorize / update DeepSeek login'); + console.log('2 - Import auth file / cookies'); + console.log('3 - Show models and statuses'); + console.log('4 - Start proxy (default)'); + console.log('5 - Exit'); + let choice = await prompt('Your choice (Enter = 4): '); +>>>>>>> Stashed changes if (!choice) choice = '4'; if (choice === '1') { await runAuthScript(); From fd5d19b75ffecb50949de8dd59d03c18deab04b2 Mon Sep 17 00:00:00 2001 From: indicatorspro Date: Sun, 21 Jun 2026 09:34:40 -0300 Subject: [PATCH 4/5] Translated to English --- README.md | 467 ++++++++++-------------------------------------------- 1 file changed, 87 insertions(+), 380 deletions(-) diff --git a/README.md b/README.md index 147ad8a..6664238 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,7 @@ # FreeDeepseekAPI

-<<<<<<< Updated upstream - Локальный OpenAI-compatible API proxy для DeepSeek Web Chat -======= Local OpenAI-compatible API proxy for DeepSeek Web Chat ->>>>>>> Stashed changes

@@ -16,55 +12,24 @@

-<<<<<<< Updated upstream - Быстрый старт • - Возможности • - Примеры • - Модели • -======= Quick StartFeaturesExamplesModels • ->>>>>>> Stashed changes EndpointsOpen WebUI

-<<<<<<< Updated upstream -FreeDeepseekAPI поднимает локальный API-сервер для **DeepSeek Web Chat** (`chat.deepseek.com`) и позволяет подключать DeepSeek Web к Open WebUI, LiteLLM, Hermes, Claude Code, OpenAI SDK-style клиентам и другим OpenAI-compatible инструментам. - -Проект работает через ваш обычный залогиненный аккаунт DeepSeek в отдельном Chrome-профиле. Локальный сервер принимает API-запросы, а дальше сам ходит в DeepSeek Web через сохранённую browser-сессию. - -> ⚠️ Это экспериментальный web-chat proxy. DeepSeek может менять внутренний Web API без предупреждения. Для production-кейсов надёжнее официальный платный API DeepSeek. -======= FreeDeepseekAPI runs a local API server for **DeepSeek Web Chat** (`chat.deepseek.com`) and lets you connect DeepSeek Web to Open WebUI, LiteLLM, Hermes, Claude Code, OpenAI SDK-style clients, and other OpenAI-compatible tools. The project works through your regular logged-in DeepSeek account in a separate Chrome profile. The local server accepts API requests and then talks to DeepSeek Web through the saved browser session. > ⚠️ This is an experimental web-chat proxy. DeepSeek may change the internal Web API without warning. For production use cases, the official paid DeepSeek API is more reliable. ->>>>>>> Stashed changes ForgetMeAI: https://t.me/forgetmeai --- -<<<<<<< Updated upstream -## Навигация - -- [Что это даёт](#-что-это-даёт) -- [Возможности](#-возможности) -- [Быстрый старт](#-быстрый-старт) -- [Windows запуск](#-windows-запуск) -- [Linux / Chromium запуск](#-linux--chromium-запуск) -- [VPS / headless запуск](#-vps--headless-запуск) -- [Diagnostics / doctor](#-diagnostics--doctor) -- [Session reuse и сброс чатов](#-session-reuse-и-сброс-чатов) -- [Multi-account pool](#-multi-account-pool) -- [Идеи для консольной авторизации](#-идеи-для-консольной-авторизации) -- [Проверка работы](#-проверка-работы) -- [Примеры запросов](#-примеры-запросов) -======= ## Navigation - [What this gives you](#-what-this-gives-you) @@ -79,7 +44,6 @@ ForgetMeAI: https://t.me/forgetmeai - [Console auth ideas](#-console-auth-ideas) - [Verify it works](#-verify-it-works) - [Request examples](#-request-examples) ->>>>>>> Stashed changes - [Chat Completions](#chat-completions) - [Reasoning](#reasoning) - [Web search](#web-search) @@ -87,13 +51,11 @@ ForgetMeAI: https://t.me/forgetmeai - [Anthropic Messages API](#anthropic-messages-api) - [OpenAI Responses API](#openai-responses-api) - [Tool calling](#tool-calling) -<<<<<<< Updated upstream -- [Модели](#-модели) -======= - [Models](#-models) ->>>>>>> Stashed changes - [Endpoints](#-endpoints) - [Open WebUI](#-open-webui) +- [Chrome Extension](#-chrome-extension) +- [Dashboard Web](#-dashboard-web) - [Update login](#-update-login) - [Project status](#-project-status) @@ -101,17 +63,6 @@ ForgetMeAI: https://t.me/forgetmeai ## ✨ What this gives you -<<<<<<< Updated upstream -- Использовать DeepSeek Web как локальный API endpoint. -- Подключать DeepSeek к Open WebUI и другим OpenAI-compatible клиентам. -- Получать обычные JSON-ответы или streaming SSE. -- Использовать reasoning-модели с отдельным `reasoning_content`. -- Работать с Anthropic Messages API shim для Claude Code / Anthropic SDK. -- Использовать OpenAI Responses API shim для новых OpenAI/Codex-style клиентов. -- Держать отдельные web-сессии для разных агентов/users. - -## 🚀 Возможности -======= - Use DeepSeek Web as a local API endpoint. - Connect DeepSeek to Open WebUI and other OpenAI-compatible clients. - Get regular JSON responses or streaming SSE. @@ -120,25 +71,11 @@ ForgetMeAI: https://t.me/forgetmeai - Use OpenAI Responses API shim for new OpenAI/Codex-style clients. - Keep separate web sessions for different agents/users. -## 🚀 Features ->>>>>>> Stashed changes +## Features - **OpenAI-compatible API:** `POST /v1/chat/completions` - **Anthropic-compatible shim:** `POST /v1/messages` - **OpenAI Responses shim:** `POST /v1/responses` -<<<<<<< Updated upstream -- **Streaming:** SSE chunks и обычные non-stream JSON-ответы -- **Reasoning output:** отдельный `reasoning_content` для thinking-моделей -- **Tool calling:** парсинг OpenAI tools, Anthropic tools и Responses function tools -- **Model capabilities:** `GET /v1/model-capabilities` с alias → real web mode -- **Agent sessions:** отдельная DeepSeek-сессия на `user` / agent id -- **Session recovery:** авто-сброс устаревших chains/sessions -- **Zero dependencies:** Node.js 18+, без npm-зависимостей - ---- - -## ⚡ Быстрый старт -======= - **Streaming:** SSE chunks and regular non-stream JSON responses - **Reasoning output:** separate `reasoning_content` for thinking models - **Tool calling:** parsing OpenAI tools, Anthropic tools, and Responses function tools @@ -152,7 +89,6 @@ ForgetMeAI: https://t.me/forgetmeai --- ## ⚡ Quick Start ->>>>>>> Stashed changes ```bash git clone https://github.com/ForgetMeAI/FreeDeepseekAPI.git @@ -161,31 +97,6 @@ npm run auth npm start ``` -<<<<<<< Updated upstream -`npm run auth` открывает меню авторизации: - -1. выберите пункт `1`; -2. войдите в DeepSeek в отдельном Chrome-профиле; -3. отправьте короткое сообщение вроде `ok`; -4. вернитесь в терминал и нажмите Enter. - -`npm start` показывает меню запуска: - -- `1` — авторизоваться / обновить DeepSeek login -- `2` — показать модели и статусы -- `3` — запустить proxy -- `4` — выйти - -Для headless/CI-запуска без меню: - -```bash -NON_INTERACTIVE=1 npm start -# или -SKIP_ACCOUNT_MENU=1 npm start -``` - -По умолчанию сервер слушает: -======= `npm run auth` opens the auth menu: 1. select item `1`; @@ -209,7 +120,6 @@ SKIP_ACCOUNT_MENU=1 npm start ``` By default the server listens on: ->>>>>>> Stashed changes ```text http://localhost:9655 @@ -217,11 +127,7 @@ http://localhost:9655 --- -<<<<<<< Updated upstream -## 🪟 Windows запуск -======= -## 🪟 Windows Launch ->>>>>>> Stashed changes +## Windows Launch ```powershell git clone https://github.com/ForgetMeAI/FreeDeepseekAPI.git @@ -230,30 +136,18 @@ npm run auth npm start ``` -<<<<<<< Updated upstream -Если Chrome установлен нестандартно, явно укажите путь: -======= If Chrome is installed in a non-standard location, specify the path explicitly: ->>>>>>> Stashed changes ```powershell $env:CHROME_PATH="C:\Program Files\Google\Chrome\Application\chrome.exe" npm run auth ``` -<<<<<<< Updated upstream -Если Chrome не найден, `npm run auth` теперь печатает готовые инструкции для Windows/macOS/Linux вместо загадочного stack trace. - ---- - -## 🐧 Linux / Chromium запуск -======= If Chrome is not found, `npm run auth` now prints ready-to-use instructions for Windows/macOS/Linux instead of a cryptic stack trace. --- -## 🐧 Linux / Chromium Launch ->>>>>>> Stashed changes +## Linux / Chromium Launch ```bash git clone https://github.com/ForgetMeAI/FreeDeepseekAPI.git @@ -262,57 +156,33 @@ CHROME_PATH=$(which chromium) npm run auth npm start ``` -<<<<<<< Updated upstream -Если Chromium называется иначе: - -```bash -CHROME_PATH=$(which chromium-browser) npm run auth -# или -======= If Chromium has a different name: ```bash CHROME_PATH=$(which chromium-browser) npm run auth # or ->>>>>>> Stashed changes CHROME_PATH=$(which google-chrome) npm run auth ``` --- -<<<<<<< Updated upstream -## 🖥 VPS / headless запуск - -Самый надёжный flow без Chrome на сервере: - -1. На домашнем ПК, где есть GUI/Chrome: -======= -## 🖥 VPS / Headless Launch +## VPS / Headless Launch The most reliable flow without Chrome on the server: 1. On your home PC (where you have GUI/Chrome): ->>>>>>> Stashed changes ```bash npm run auth ``` -<<<<<<< Updated upstream -2. Скопируйте `deepseek-auth.json` на VPS: -======= 2. Copy `deepseek-auth.json` to VPS: ->>>>>>> Stashed changes ```bash scp deepseek-auth.json user@your-vps:/opt/FreeDeepseekAPI/deepseek-auth.json ``` -<<<<<<< Updated upstream -3. На VPS импортируйте/проверьте файл и выставьте безопасные права: -======= 3. On VPS import/verify the file and set safe permissions: ->>>>>>> Stashed changes ```bash cd /opt/FreeDeepseekAPI @@ -320,66 +190,26 @@ npm run auth:import -- --input ./deepseek-auth.json npm run doctor -- --offline ``` -<<<<<<< Updated upstream -4. Запускайте proxy без интерактивного меню: -======= 4. Start proxy without interactive menu: ->>>>>>> Stashed changes ```bash NON_INTERACTIVE=1 npm start ``` -<<<<<<< Updated upstream -Можно импортировать не только готовый `deepseek-auth.json`, но и browser cookie export: -======= You can import not only a ready-made `deepseek-auth.json`, but also a browser cookie export: ->>>>>>> Stashed changes ```bash DEEPSEEK_TOKEN="" npm run auth:import -- --input ./cookies.json ``` -<<<<<<< Updated upstream -> Важно: `deepseek-auth.json` — это доступ к вашему DeepSeek Web login. Не коммитьте, не публикуйте, храните с правами `0600`. -======= > Important: `deepseek-auth.json` gives access to your DeepSeek Web login. Do not commit, do not publish, store with `0600` permissions. ->>>>>>> Stashed changes --- -## 🩺 Diagnostics / doctor +## Diagnostics / doctor ```bash npm run doctor -<<<<<<< Updated upstream -# без сетевых запросов к DeepSeek: -npm run doctor -- --offline -``` - -`doctor` проверяет: - -- найден ли `deepseek-auth.json` / `DEEPSEEK_AUTH_DIR`; -- валидный ли JSON; -- есть ли `token`, `cookie`, `wasmUrl`; -- безопасные ли права файла на macOS/Linux (`0600`); -- при обычном запуске — доступен ли DeepSeek PoW endpoint. - -Если видите `data.biz_data is null`, `fetch failed`, `401/403/429` или Hermes/OpenCode не видит модели — первым делом запускайте `npm run doctor`. - ---- - -## ♻️ Session reuse и сброс чатов - -FreeDeepseekAPI не создаёт новый DeepSeek чат на каждый HTTP-запрос без причины. Логика такая: - -- один `x-agent-session`, `session` или `user` → одна DeepSeek chat session; -- если session id уже есть — proxy переиспользует его и продолжает chain через `parent_message_id`; -- auto-reset происходит при TTL, ошибке DeepSeek session или слишком длинной цепочке сообщений; -- локальная history сохраняется коротким контекстом, чтобы новая DeepSeek session могла продолжить разговор. - -Явно задать agent/session: -======= # without network requests to DeepSeek: npm run doctor -- --offline ``` @@ -406,67 +236,41 @@ FreeDeepseekAPI doesn't create a new DeepSeek chat on every HTTP request without - local history is saved as short context so new DeepSeek session can continue the conversation. Explicitly set agent/session: ->>>>>>> Stashed changes ```bash curl -X POST http://localhost:9655/v1/chat/completions \ -H "Content-Type: application/json" \ -H "x-agent-session: my-agent" \ -<<<<<<< Updated upstream - -d '{"model":"deepseek-chat","messages":[{"role":"user","content":"Привет"}]}' -``` - -Посмотреть активные sessions: -======= -d '{"model":"deepseek-chat","messages":[{"role":"user","content":"Hi"}]}' ``` View active sessions: ->>>>>>> Stashed changes ```bash curl http://localhost:9655/v1/sessions ``` -<<<<<<< Updated upstream -Сбросить одну session: -======= Reset a single session: ->>>>>>> Stashed changes ```bash curl -X POST "http://localhost:9655/reset-session?agent=my-agent" ``` -<<<<<<< Updated upstream -Сбросить все sessions: -======= Reset all sessions: ->>>>>>> Stashed changes ```bash curl -X POST "http://localhost:9655/reset-session?agent=all" ``` -<<<<<<< Updated upstream -Почему чаты всё равно появляются в DeepSeek Web: proxy работает через внутренний Web Chat API, а DeepSeek хранит реальные chat sessions у себя. Это нормально для web-proxy. Задача session reuse — не плодить новые чаты без необходимости и аккуратно сбрасываться только когда chain протух/сломался. -======= Why chats still appear in DeepSeek Web: proxy works through internal Web Chat API, and DeepSeek stores real chat sessions on their side. This is normal for web-proxy. The task of session reuse is not to spawn new chats unnecessarily and to reset cleanly only when the chain has gone stale/broken. ->>>>>>> Stashed changes --- -## 👥 Multi-account pool - -<<<<<<< Updated upstream -Можно подключить несколько auth-файлов. Правильная модель: sticky account per agent/session — proxy не переключает аккаунт внутри живой DeepSeek-сессии. Если аккаунт получил `401/403/429` и ушёл в cooldown, session безопасно сбрасывается и новый запрос может перейти на другой доступный аккаунт. +## Multi-account pool -Вариант 1 — директория с auth-файлами: -======= You can connect multiple auth files. Correct model: sticky account per agent/session — proxy doesn't switch account inside a live DeepSeek session. If an account gets `401/403/429` and goes into cooldown, the session is safely reset and a new request may switch to another available account. Option 1 — directory with auth files: ->>>>>>> Stashed changes ```bash mkdir -p accounts @@ -476,28 +280,12 @@ chmod 600 accounts/*.json DEEPSEEK_AUTH_DIR=./accounts NON_INTERACTIVE=1 npm start ``` -<<<<<<< Updated upstream -Вариант 2 — список файлов: -======= Option 2 — file list: ->>>>>>> Stashed changes ```bash DEEPSEEK_AUTH_PATH="./accounts/main.json,./accounts/backup.json" NON_INTERACTIVE=1 npm start ``` -<<<<<<< Updated upstream -Как работает pool: - -- новый agent/session получает доступный аккаунт round-robin; -- выбранный аккаунт закрепляется за session (`sticky`); -- при `401`, `403`, `429` аккаунт уходит в cooldown; -- если sticky-аккаунт session ушёл в cooldown, старая DeepSeek-сессия сбрасывается, чтобы не долбить rate-limited/expired аккаунт; -- статус аккаунтов виден в `/health` без путей к auth-файлам и без имён файлов; -- auth-файлы должны храниться с правами `0600`. - -Настроить cooldown: -======= How the pool works: - new agent/session gets an available account round-robin; @@ -508,7 +296,6 @@ How the pool works: - auth files must be stored with `0600` permissions. Configure cooldown: ->>>>>>> Stashed changes ```bash DEEPSEEK_ACCOUNT_COOLDOWN_MS=600000 npm start @@ -516,25 +303,7 @@ DEEPSEEK_ACCOUNT_COOLDOWN_MS=600000 npm start --- -<<<<<<< Updated upstream -## 🔑 Идеи для консольной авторизации - -Парольный flow из PR #3 можно делать, но безопаснее не хранить пароль и не делать это дефолтом. Нормальная реализация: - -1. `npm run auth:console` спрашивает email/телефон и пароль через hidden prompt. -2. Пароль держится только в памяти процесса, не пишется в файлы/logs/history. -3. Скрипт повторяет Web login flow через `fetch`/CDP: получает captcha/verify challenge, отдаёт человеку ссылку/код, ждёт подтверждение. -4. После успешного login сохраняется только `deepseek-auth.json` стандартного формата. -5. Если DeepSeek просит captcha/2FA — скрипт честно говорит “открой ссылку, пройди проверку, нажми Enter”, а не пытается обходить защиту. -6. Для VPS лучше режим `auth:console --no-save-password --output deepseek-auth.json`. - -Минимальный безопасный MVP: console auth только интерактивный, без env-пароля. Допустимый automation-вариант: `DEEPSEEK_EMAIL=... npm run auth:console`, но пароль всё равно вводится hidden prompt. - ---- - -## ✅ Проверка работы -======= -## 🔑 Console auth ideas +## Console auth ideas Password flow from PR #3 can be done, but safer not to store password and not make it default. Normal implementation: @@ -550,7 +319,6 @@ Minimal safe MVP: console auth only interactive, no env password. Acceptable aut --- ## ✅ Verify it works ->>>>>>> Stashed changes ```bash curl http://localhost:9655/ @@ -558,19 +326,11 @@ curl http://localhost:9655/v1/models curl http://localhost:9655/v1/model-capabilities ``` -<<<<<<< Updated upstream -Если всё ок, `/health` вернёт статус сервера, список поддерживаемых aliases и `config_ready: true`. - ---- - -## 🧪 Примеры запросов -======= If all good, `/health` returns server status, list of supported aliases, and `config_ready: true`. --- -## 🧪 Request examples ->>>>>>> Stashed changes +## Request examples ### Chat Completions @@ -579,11 +339,7 @@ curl -X POST http://localhost:9655/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "deepseek-chat", -<<<<<<< Updated upstream - "messages": [{"role": "user", "content": "Привет! Ответь одной фразой."}], -======= "messages": [{"role": "user", "content": "Hi! Reply with one phrase."}], ->>>>>>> Stashed changes "stream": false }' ``` @@ -595,30 +351,18 @@ curl -X POST http://localhost:9655/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "deepseek-reasoner", -<<<<<<< Updated upstream - "messages": [{"role": "user", "content": "Реши коротко: почему небо голубое?"}], -======= "messages": [{"role": "user", "content": "Solve briefly: why is the sky blue?"}], ->>>>>>> Stashed changes "stream": false }' ``` -<<<<<<< Updated upstream -Для reasoning-моделей API отдаёт цепочку размышления отдельно от финального ответа: -======= For reasoning models, API returns the reasoning chain separately from the final answer: ->>>>>>> Stashed changes - non-stream: `choices[0].message.reasoning_content` - stream: `choices[0].delta.reasoning_content` - usage: `usage.completion_tokens_details.reasoning_tokens` -<<<<<<< Updated upstream -`reasoning_tokens` — приблизительная оценка по извлечённому DeepSeek Web `THINK`-тексту, потому что web stream не отдаёт официальный token usage по reasoning отдельно. -======= `reasoning_tokens` — approximate estimate based on extracted DeepSeek Web `THINK` text, because web stream doesn't return official token usage for reasoning separately. ->>>>>>> Stashed changes ### Web search @@ -627,11 +371,7 @@ curl -X POST http://localhost:9655/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "deepseek-chat-search", -<<<<<<< Updated upstream - "messages": [{"role": "user", "content": "Найди свежий факт про DeepSeek и ответь кратко."}], -======= "messages": [{"role": "user", "content": "Find a fresh fact about DeepSeek and reply briefly."}], ->>>>>>> Stashed changes "stream": false }' ``` @@ -643,11 +383,7 @@ curl -N -X POST http://localhost:9655/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "deepseek-chat", -<<<<<<< Updated upstream - "messages": [{"role": "user", "content": "Напиши короткую шутку."}], -======= "messages": [{"role": "user", "content": "Write a short joke."}], ->>>>>>> Stashed changes "stream": true }' ``` @@ -660,20 +396,12 @@ curl -X POST http://localhost:9655/v1/messages \ -d '{ "model": "deepseek-chat", "max_tokens": 512, -<<<<<<< Updated upstream - "messages": [{"role": "user", "content": "Ответь ровно OK"}], -======= "messages": [{"role": "user", "content": "Reply exactly OK"}], ->>>>>>> Stashed changes "stream": false }' ``` -<<<<<<< Updated upstream -Для Claude Code можно указывать backend напрямую: -======= For Claude Code you can specify backend directly: ->>>>>>> Stashed changes ```bash export ANTHROPIC_BASE_URL="http://127.0.0.1:9655" @@ -682,9 +410,6 @@ export CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1 claude --model deepseek-chat ``` -### OpenAI -``` - ### OpenAI Responses API ```bash @@ -692,163 +417,145 @@ curl -X POST http://localhost:9655/v1/responses \ -H "Content-Type: application/json" \ -d '{ "model": "deepseek-chat", -<<<<<<< Updated upstream - "input": "Ответь ровно OK", -======= "input": "Reply exactly OK", ->>>>>>> Stashed changes "stream": false }' ``` ### Tool calling -<<<<<<< Updated upstream -FreeDeepseekAPI принимает: -======= FreeDeepseekAPI accepts: ->>>>>>> Stashed changes - OpenAI `tools`; - Anthropic `tools`; - Responses API function tools. -<<<<<<< Updated upstream -Прокси просит DeepSeek вернуть строгий JSON tool call, но также умеет парсить fallback-форматы: +Proxy asks DeepSeek to return strict JSON tool call, but also parses fallback formats: - `TOOL_CALL:` - fenced JSON -- `...` +- `...` --- -## 🧠 Модели +## Models -`GET /v1/models` возвращает только aliases, которые сейчас проверены и работают через этот proxy. +DeepSeek Web supports several model aliases. Use these in the `model` field: -### Рабочие aliases +- `deepseek-chat` — standard chat model +- `deepseek-reasoner` — reasoning model with chain-of-thought +- `deepseek-chat-search` — chat with web search enabled -| Alias | Web mode | Reasoning | Web search | Комментарий | -| --- | --- | --- | --- | --- | -| `deepseek-chat` | `Быстрый` / `default` | нет | нет | базовый chat | -| `deepseek-v3` | `Быстрый` / `default` | нет | нет | совместимый alias | -| `deepseek-default` | `Быстрый` / `default` | нет | нет | совместимый alias | -| `deepseek-reasoner` | `Быстрый` / `default` | да | нет | `thinking_enabled=true` | -| `deepseek-r1` | `Быстрый` / `default` | да | нет | R1-compatible alias | -| `deepseek-chat-search` | `Быстрый` / `default` | нет | да | web search | -| `deepseek-default-search` | `Быстрый` / `default` | нет | да | web search alias | -| `deepseek-reasoner-search` | `Быстрый` / `default` | да | да | reasoning + search | -| `deepseek-r1-search` | `Быстрый` / `default` | да | да | R1-compatible + search | -| `deepseek-expert` | `Эксперт` / `expert` | нет | нет | Expert mode | -| `deepseek-v4-pro` | `Эксперт` / `expert` | да | нет | Expert + reasoning | - -Полный маппинг: +For a full list, run: ```bash -curl http://localhost:9655/v1/model-capabilities +curl http://localhost:9655/v1/models ``` -По официальной странице DeepSeek V4 Preview `deepseek-chat` и `deepseek-reasoner` сейчас route'ятся в `deepseek-v4-flash` non-thinking/thinking. В самом `chat.deepseek.com` direct stream точное имя чекпойнта не отдаётся (`model: ""`), поэтому proxy фиксирует одновременно web-режим (`default` / `Быстрый`) и актуальную официальную маршрутизацию (`DeepSeek-V4-Flash`). - -Текущий вывод DeepSeek Web remote config показывает такие web-режимы: +--- -- `default` / UI `Быстрый` — работает; поддерживает `thinking_enabled` и `search_enabled`. -- `expert` / UI `Эксперт` — работает через актуальный web-контракт (`x-client-version=2.0.0`) и поддерживает `thinking_enabled`. В `/v1/models` выдаются `deepseek-expert` без reasoning и `deepseek-v4-pro` как Expert + reasoning. -- `vision` / UI `Распознавание` — виден в remote config, но сейчас direct Web API возвращает `backend_err_by_model` (`Vision is temporarily unavailable`). Поэтому `deepseek-vision` скрыт из `/v1/models`. +## Endpoints -Search для Expert по remote config недоступен, поэтому `deepseek-expert-search` остаётся unsupported. +- `GET /` — server status +- `GET /health` — detailed health check +- `GET /v1/models` — list available models +- `GET /v1/model-capabilities` — model capabilities +- `POST /v1/chat/completions` — OpenAI-compatible chat completions +- `POST /v1/messages` — Anthropic-compatible messages API +- `POST /v1/responses` — OpenAI Responses API +- `GET /v1/sessions` — list active sessions +- `POST /reset-session` — reset a session (query param `agent`) --- -## 🔌 Endpoints +## Open WebUI + +To connect Open WebUI to FreeDeepseekAPI: -| Method | Path | Назначение | -| --- | --- | --- | -| `GET` | `/` или `/health` | статус proxy | -| `GET` | `/v1/models` | список рабочих OpenAI-compatible aliases | -| `GET` | `/v1/model-capabilities` | полный маппинг aliases, real model, capabilities | -| `POST` | `/v1/chat/completions` | OpenAI-compatible Chat Completions | -| `POST` | `/v1/messages` | Anthropic Messages API shim | -| `POST` | `/v1/responses` | OpenAI Responses API shim | -| `GET` | `/v1/sessions` | активные локальные agent sessions | -| `POST` | `/reset-session?agent=` | сбросить одну session | -| `POST` | `/reset-session?agent=all` | сбросить все sessions | +1. Start FreeDeepseekAPI: `npm start` +2. In Open WebUI, go to Settings → Connections +3. Set API URL to `http://localhost:9655` +4. Use any dummy API key (e.g., `sk-dummy`) +5. Select `deepseek-chat` or other models --- -## 🖥 Open WebUI +## Chrome Extension -Base URL для Open WebUI в Docker: +A browser extension is available for session management. -```text -http://host.docker.internal:9655/v1 -``` +### Installation -Для локального запуска без Docker: +1. Open `chrome://extensions/` +2. Enable "Developer mode" +3. Click "Load unpacked" +4. Select the `chrome-extension/` folder -```text -http://localhost:9655/v1 -``` +### Features + +- Real-time server status +- Session refresh and auto-capture +- Popup interface for quick actions +- Cookie-based session monitoring +- Request interception for DeepSeek API -API key можно указать любой: proxy сам ходит в DeepSeek Web через сохранённую browser-сессию. +### Usage + +Click the extension icon to view: + +- Server connection status (`http://localhost:9655`) +- Active session ID +- Number of configured accounts +- Last activity timestamp --- -## 🔐 Обновить логин +## Dashboard Web -```bash -npm run auth -npm start +A web dashboard provides advanced administration. + +### Access + +``` +http://localhost:9655/dashboard ``` -Если DeepSeek начал отвечать `401`, `403` или просит новый PoW/session — повторите `npm run auth` и обновите сохранённую browser-сессию. +### Features -Локальные файлы авторизации не должны попадать в GitHub: +- Active session monitoring +- API usage statistics +- Account management +- Real-time metrics -- `deepseek-auth.json` -- `.chrome-profile-deepseek/` -- `.env` +### Development -Они уже добавлены в `.gitignore`. +```bash +cd dashboard +npm install +npm start # Development server +npm run build # Production build +``` --- -## 🧪 Тесты +## Update login -Синтаксическая проверка проекта: +If your DeepSeek session expires, refresh it: ```bash -npm test +npm run auth ``` -Live smoke-тесты против запущенного локального proxy: +Or use the import command with fresh credentials: ```bash -BASE_URL=http://127.0.0.1:9655 MODEL=deepseek-chat npm run test:live +npm run auth:import -- --input ./new-auth.json ``` --- -## 📌 Статус проекта +## Project status -FreeDeepseekAPI — экспериментальный web-chat proxy для локального использования и интеграций. Он зависит от текущего контракта DeepSeek Web Chat, поэтому при изменениях на стороне DeepSeek может потребоваться обновление auth/session logic или model mapping. +This project is actively maintained. For updates and support, join the Telegram channel: [ForgetMeAI](https://t.me/forgetmeai). -Если что-то перестало работать: - -1. обновите логин через `npm run auth`; -2. проверьте `/v1/model-capabilities`; -3. повторите запрос на свежей сессии; -4. если проблема сохраняется — вероятно, DeepSeek изменил внутренний Web API. - ---- - -

- ForgetMeAI · Telegram -

-======= -Proxy asks DeepSeek to return strict JSON tool call, but also parses fallback formats: - -- `TOOL_CALL:` -- fenced JSON -- `... ->>>>>>> Stashed changes +Contributions are welcome via pull requests. From 0a34fbe9dbacfa572852a5df9549acc73ef269c6 Mon Sep 17 00:00:00 2001 From: indicatorspro Date: Sun, 21 Jun 2026 13:55:34 -0300 Subject: [PATCH 5/5] Resolve git conflicts, translate Russian to English, add missing cdp-extract.js, restore auth.js CLI features --- chrome-extension/README.md | 31 ---- chrome-extension/background.js | 18 --- chrome-extension/manifest.json | 10 +- chrome-extension/popup.html | 22 --- chrome-extension/popup.js | 87 ----------- lib/parseAuth.js | 45 ++---- public/dashboard.html | 241 +------------------------------ scripts/auth.js | 129 ++++++++++------- scripts/auth_import.js | 16 +- scripts/cdp-extract.js | 146 +++++++++++++++++++ scripts/deepseek_chrome_auth.js | 9 +- scripts/probe_deepseek_models.js | 2 +- server.js | 67 +++------ 13 files changed, 262 insertions(+), 561 deletions(-) create mode 100644 scripts/cdp-extract.js diff --git a/chrome-extension/README.md b/chrome-extension/README.md index 12f7dae..425a81a 100644 --- a/chrome-extension/README.md +++ b/chrome-extension/README.md @@ -1,11 +1,3 @@ -<<<<<<< Updated upstream -# DeepSeek → FreeDeepseekAPI (расширение) - -Добавляет аккаунт DeepSeek в локальный FreeDeepseekAPI **одним кликом**: -перехватывает заголовки реального запроса к `chat.deepseek.com/api/...` -(`token` из `Authorization`, все cookie, `hif_*`) и отправляет на -`http://localhost:9655/api/accounts/import`. -======= # DeepSeek → FreeDeepseekAPI (extension) Adds a DeepSeek account to your local FreeDeepseekAPI **with one click**: @@ -14,34 +6,12 @@ intercepts headers from a real request to `chat.deepseek.com/api/...` `http://localhost:9655/api/accounts/import`. Works in Firefox and Chrome/Edge (Manifest V3). ->>>>>>> Stashed changes Работает в Firefox и Chrome/Edge (Manifest V3). ## Установка **Firefox** -<<<<<<< Updated upstream -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`) — на случай ручного импорта через дашборд. -======= 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.) @@ -61,4 +31,3 @@ For multiple accounts, repeat from different browser profiles/logins. Auxiliary buttons: "Collect" (show creds), "Copy JSON", "Download file" (`deepseek-auth.json`) — for manual import via dashboard. ->>>>>>> Stashed changes diff --git a/chrome-extension/background.js b/chrome-extension/background.js index 73741d1..938fcbd 100644 --- a/chrome-extension/background.js +++ b/chrome-extension/background.js @@ -1,32 +1,18 @@ -<<<<<<< Updated upstream -// 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. ->>>>>>> Stashed changes const WASM_DEFAULT = 'https://fe-static.deepseek.com/chat/static/sha3_wasm_bg.7b9ca65ddd.wasm'; const KEY = 'deepseek_capture'; -<<<<<<< Updated upstream -// extraHeaders нужен Chrome для доступа к Cookie/Authorization; Firefox даёт их без него. -======= // extraHeaders needed for Chrome to access Cookie/Authorization; Firefox provides them without it. ->>>>>>> Stashed changes const opts = ['requestHeaders']; try { if (chrome.webRequest.OnBeforeSendHeadersOptions && chrome.webRequest.OnBeforeSendHeadersOptions.EXTRA_HEADERS) { opts.push('extraHeaders'); } -<<<<<<< Updated upstream -} catch (e) { /* Firefox: опции нет — это нормально */ } -======= } catch (e) { /* Firefox: option doesn't exist — this is normal */ } ->>>>>>> Stashed changes chrome.webRequest.onBeforeSendHeaders.addListener( (details) => { @@ -56,8 +42,4 @@ chrome.runtime.onMessage.addListener((req, sender, sendResponse) => { chrome.storage.local.get(KEY, (r) => sendResponse({ success: true, cap: r[KEY] || null })); return true; // async } -<<<<<<< Updated upstream }); -======= -}); ->>>>>>> Stashed changes diff --git a/chrome-extension/manifest.json b/chrome-extension/manifest.json index 7133d4c..2030591 100644 --- a/chrome-extension/manifest.json +++ b/chrome-extension/manifest.json @@ -2,11 +2,7 @@ "manifest_version": 3, "name": "DeepSeek → FreeDeepseekAPI", "version": "1.4", -<<<<<<< Updated upstream - "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).", ->>>>>>> Stashed changes "author": "FreeDeepseekAPI", "browser_specific_settings": { "gecko": { @@ -31,8 +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" } -<<<<<<< Updated upstream -} -======= -} ->>>>>>> Stashed changes +} \ No newline at end of file diff --git a/chrome-extension/popup.html b/chrome-extension/popup.html index 86ea38a..db990c1 100644 --- a/chrome-extension/popup.html +++ b/chrome-extension/popup.html @@ -57,34 +57,19 @@

🔑 DeepSeek → FreeDeepseekAPI

-<<<<<<< Updated upstream - -
-
Откройте chat.deepseek.com, отправьте любое сообщение, затем нажмите кнопку
-=======
Open chat.deepseek.com, send any message, then click the button
->>>>>>> Stashed changes
⏳ Loading...
-<<<<<<< Updated upstream - -
-
- - - -=======
->>>>>>> Stashed changes
@@ -96,17 +81,10 @@

🔑 DeepSeek → FreeDeepseekAPI

-<<<<<<< Updated upstream - Пул аккаунтов - - - -======= Account Pool ->>>>>>> Stashed changes
diff --git a/chrome-extension/popup.js b/chrome-extension/popup.js index 02e0f13..8bb0cbd 100644 --- a/chrome-extension/popup.js +++ b/chrome-extension/popup.js @@ -3,51 +3,29 @@ function $(id) { return document.getElementById(id); } const API_BASE = 'http://localhost:9655'; const PROXY_URL = API_BASE + '/api/accounts/import'; -<<<<<<< Updated upstream -let current = null; // перехваченный набор кредов {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)'; } } -======= 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 ? 'Server online (:9655)' : 'Server unavailable (:9655)'; } } ->>>>>>> Stashed changes 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) { -<<<<<<< Updated upstream - setStatus('warn', '⚠️ Откройте chat.deepseek.com и ОТПРАВЬТЕ любое сообщение, затем нажмите кнопку.'); - $('jsonPreview').textContent = '{ }'; - $('detail').textContent = 'Креды появятся после запроса к DeepSeek'; -======= setStatus('warn', '⚠️ Open chat.deepseek.com and SEND any message, then click the button.'); $('jsonPreview').textContent = '{ }'; $('detail').textContent = 'Creds will appear after request to DeepSeek'; ->>>>>>> Stashed changes 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 }; -<<<<<<< Updated upstream - // превью с маскировкой секретов -======= // preview with masked secrets ->>>>>>> Stashed changes $('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); -<<<<<<< Updated upstream - 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()) : ''; ->>>>>>> Stashed changes setCredButtons(true); return auth; } @@ -56,11 +34,7 @@ function refresh() { chrome.runtime.sendMessage({ action: 'get' }, (r) => { current = (r && r.success) ? render(r.cap) : render(null); }); } -<<<<<<< Updated upstream -// ── Панель пула (строится через DOM API, без innerHTML, чтобы исключить XSS) ── -======= // ── Pool panel (built via DOM API, no innerHTML, to rule out XSS) ── ->>>>>>> Stashed changes function mkBtn(act, label, title, cls) { const b = document.createElement('button'); b.className = cls; b.dataset.act = act; b.title = title; b.textContent = label; @@ -74,13 +48,8 @@ function setEmpty(text) { } function renderPool(list) { -<<<<<<< Updated upstream - $('poolTitle').textContent = `Пул аккаунтов (${list.length})`; - if (!list.length) { setEmpty('Нет аккаунтов'); return; } -======= $('poolTitle').textContent = `Account pool (${list.length})`; if (!list.length) { setEmpty('No accounts'); return; } ->>>>>>> Stashed changes const pool = $('pool'); pool.textContent = ''; for (const a of list) { const row = document.createElement('div'); @@ -97,11 +66,7 @@ function renderPool(list) { const actions = document.createElement('span'); actions.className = 'row-actions'; -<<<<<<< Updated upstream - actions.append(mkBtn('check', '↻', 'Проверить', 'acc-btn'), mkBtn('del', '✕', 'Удалить', 'acc-btn danger')); -======= actions.append(mkBtn('check', '↻', 'Check', 'acc-btn'), mkBtn('del', '✕', 'Delete', 'acc-btn danger')); ->>>>>>> Stashed changes row.append(idEl, emailEl, badge, actions); pool.appendChild(row); @@ -116,13 +81,8 @@ async function loadPool() { renderPool(j.accounts || []); } catch { setSrvDot(false); -<<<<<<< Updated upstream - $('poolTitle').textContent = 'Пул аккаунтов'; - setEmpty('FreeDeepseekAPI недоступен на :9655'); -======= $('poolTitle').textContent = 'Account pool'; setEmpty('FreeDeepseekAPI unavailable on :9655'); ->>>>>>> Stashed changes } } @@ -144,20 +104,12 @@ async function deleteAccount(id) { loadPool(); } -<<<<<<< Updated upstream -// делегирование кликов в панели (check / удаление с инлайн-подтверждением) -======= // click delegation in panel (check / delete with inline confirmation) ->>>>>>> Stashed changes $('pool').addEventListener('click', (e) => { const emailEl = e.target.closest('.email'); if (emailEl && emailEl.textContent && emailEl.textContent !== '—') { const full = emailEl.title || emailEl.textContent; -<<<<<<< Updated upstream - 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); }); ->>>>>>> Stashed changes return; } const btn = e.target.closest('.acc-btn'); if (!btn) return; @@ -166,11 +118,7 @@ $('pool').addEventListener('click', (e) => { if (act === 'check') { checkAccount(id); return; } if (act === 'del') { const actions = btn.parentElement; actions.textContent = ''; -<<<<<<< Updated upstream - actions.append(mkBtn('yes', '✓', 'Удалить', 'acc-btn confirm'), mkBtn('no', '✗', 'Отмена', 'acc-btn')); -======= actions.append(mkBtn('yes', '✓', 'Delete', 'acc-btn confirm'), mkBtn('no', '✗', 'Cancel', 'acc-btn')); ->>>>>>> Stashed changes return; } if (act === 'yes') deleteAccount(id); @@ -186,16 +134,6 @@ async function checkAll() { $('btnCheckAll').addEventListener('click', checkAll); $('btnDashboard').addEventListener('click', () => { chrome.tabs.create({ url: API_BASE + '/dashboard' }); }); -<<<<<<< Updated upstream -// ── Главная кнопка — добавить перехваченные креды + авто-валидация ── -$('btnAdd').addEventListener('click', async () => { - if (!current) { - refresh(); - setStatus('warn', '⏳ Кредов нет. Отправьте сообщение в DeepSeek и нажмите снова.'); - return; - } - setStatus('warn', '⏳ Отправка в FreeDeepseekAPI…'); -======= // ── Main button — add captured creds + auto-validation ── $('btnAdd').addEventListener('click', async () => { if (!current) { @@ -204,27 +142,11 @@ $('btnAdd').addEventListener('click', async () => { return; } setStatus('warn', '⏳ Sending to FreeDeepseekAPI…'); ->>>>>>> Stashed changes 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})` : ''; -<<<<<<< Updated upstream - setStatus('ok', `✅ Добавлен как ${j.id}${who} — проверяю…`); - await loadPool(); - const st = await checkAccount(j.id); - if (st === 'OK') setStatus('ok', `🟢 ${j.id}${who} — рабочий`); - else setStatus('err', `🔴 ${j.id} — статус: ${st || 'неизвестен'}`); - } else if (j.existingId) { - setStatus('warn', `⚠️ Уже добавлен как ${j.existingId}`); - loadPool(); - } else { - setStatus('err', '❌ ' + (j.error || 'Ошибка добавления')); - } - } catch (e) { - setStatus('err', '❌ FreeDeepseekAPI недоступен на localhost:9655 (запущен?)'); -======= setStatus('ok', `✅ Added as ${j.id}${who} — checking…`); await loadPool(); const st = await checkAccount(j.id); @@ -238,7 +160,6 @@ $('btnAdd').addEventListener('click', async () => { } } catch (e) { setStatus('err', '❌ FreeDeepseekAPI unavailable on localhost:9655 (running?)'); ->>>>>>> Stashed changes } }); @@ -247,11 +168,7 @@ $('btnCollect').addEventListener('click', refresh); $('btnCopy').addEventListener('click', () => { if (!current) return; navigator.clipboard.writeText(JSON.stringify(current, null, 2)).then(() => { -<<<<<<< Updated upstream - $('btnCopy').textContent = '✅'; setTimeout(() => { $('btnCopy').textContent = '📋 Копировать JSON'; }, 1200); -======= $('btnCopy').textContent = '✅'; setTimeout(() => { $('btnCopy').textContent = '📋 Copy JSON'; }, 1200); ->>>>>>> Stashed changes }); }); @@ -264,8 +181,4 @@ $('btnSave').addEventListener('click', () => { }); refresh(); -<<<<<<< Updated upstream -loadPool(); -======= loadPool(); ->>>>>>> Stashed changes diff --git a/lib/parseAuth.js b/lib/parseAuth.js index 6e49d4c..df11c00 100644 --- a/lib/parseAuth.js +++ b/lib/parseAuth.js @@ -1,22 +1,15 @@ 'use strict'; /* -<<<<<<< Updated upstream - Общий парсер авторизации DeepSeek из "Copy as cURL" или HAR-файла. - Используется и CLI-скриптами (scripts/auth_from_*.js), и эндпоинтом - дашборда POST /api/accounts/import. Возвращает плоский объект - { token, cookie, hif_dliq, hif_leim, wasmUrl } или { error }. -======= Common DeepSeek auth parser from "Copy as cURL" or HAR file. Used by both CLI scripts (scripts/auth_from_*.js) and dashboard endpoint POST /api/accounts/import. Returns flat object { token, cookie, hif_dliq, hif_leim, wasmUrl } or { error }. ->>>>>>> Stashed changes */ const WASM_DEFAULT = 'https://fe-static.deepseek.com/chat/static/sha3_wasm_bg.7b9ca65ddd.wasm'; // -H 'name: value' | -H "name: value" | -H $'name: value' (Chrome bash ANSI-C) | --header ... -// Опциональный \$? перед кавычкой покрывает форму "Copy as cURL (bash)". +// Optional \$? before the quote covers Chrome's "Copy as cURL (bash)" form. function extractHeadersFromCurl(curl) { const headers = {}; const re = /(?:-H|--header)\s+\$?(['"])(.+?):\s?([\s\S]*?)\1(?=\s|$)/g; @@ -39,7 +32,7 @@ function fromHeaders(h) { } function parseCurl(curl) { - // снять bash line-continuations (\ + перевод строки), которые Chrome вставляет в "Copy as cURL" + // strip bash line-continuations (\ + newline) that Chrome inserts in "Copy as cURL" const s = String(curl || '').replace(/\\\r?\n/g, ' '); const r = fromHeaders(extractHeadersFromCurl(s)); const wm = s.match(/https?:\/\/[^\s'"]*sha3[^\s'"]*\.wasm/i); @@ -50,15 +43,11 @@ function parseCurl(curl) { function parseHar(harText) { let har; try { har = (typeof harText === 'object') ? harText : JSON.parse(harText); } - catch { return { error: 'Не удалось прочитать HAR (не JSON)' }; } + catch { return { error: 'Failed to parse HAR (not valid JSON)' }; } const entries = (har.log && har.log.entries) || []; const hv = (hs, n) => { const x = (hs || []).find(y => (y.name || '').toLowerCase() === n); return x ? (x.value || '') : ''; }; -<<<<<<< Updated upstream - // выбираем лучший запрос к deepseek с Authorization: Bearer -======= // pick best request to deepseek with Authorization: Bearer ->>>>>>> Stashed changes let best = null; for (const e of entries) { const req = e.request || {}; @@ -74,56 +63,40 @@ function parseHar(harText) { best = { score, token: auth.replace(/^Bearer\s+/i, '').trim(), cookie, hif_dliq: dliq, hif_leim: leim }; } } -<<<<<<< Updated upstream - if (!best) return { error: 'В HAR нет запросов к deepseek.com с заголовком Authorization: Bearer' }; -======= if (!best) return { error: 'No requests to deepseek.com with Authorization: Bearer header in HAR' }; ->>>>>>> Stashed changes let wasmUrl = ''; for (const e of entries) { const u = (e.request && e.request.url) || ''; if (/sha3.*\.wasm/i.test(u)) { wasmUrl = u; break; } } return { token: best.token, cookie: best.cookie, hif_dliq: best.hif_dliq, hif_leim: best.hif_leim, wasmUrl }; } -<<<<<<< Updated upstream -// Авто-определение формата ввода (HAR — JSON с log.entries; иначе cURL). -======= // Auto-detect input format (HAR — JSON with log.entries; otherwise cURL). ->>>>>>> Stashed changes function parseAuthInput(text) { const s = String(text || '').trim(); - if (!s) return { error: 'Пустой ввод' }; + if (!s) return { error: 'Empty input' }; if (s[0] === '{' || s[0] === '[') { - // готовый JSON {token,cookie,...} (например, из расширения-экспортёра) + // ready-made JSON {token,cookie,...} (e.g. from a browser extension export) try { const o = JSON.parse(s); if (o && typeof o === 'object' && o.token && o.cookie) { return { token: String(o.token), cookie: String(o.cookie), hif_dliq: o.hif_dliq || '', hif_leim: o.hif_leim || '', wasmUrl: o.wasmUrl || '' }; } - } catch { /* не JSON-объект — пробуем HAR ниже */ } + } catch { /* not a plain JSON object — try HAR below */ } const r = parseHar(s); - if (!r.error) return r; // это был HAR + if (!r.error) return r; // it was a HAR } if (/\bcurl\b|--header|(^|\s)-H\s/i.test(s)) return parseCurl(s); -<<<<<<< Updated upstream - // последняя попытка — вдруг HAR без явного префикса - return parseHar(s); -} - -// Валидация + проставление wasmUrl (из ввода → из прошлого аккаунта → дефолт). -======= // last attempt — maybe HAR without explicit prefix return parseHar(s); } // Validation + fill wasmUrl (from input → from previous account → default). ->>>>>>> Stashed changes function finalizeAuth(parsed, prevWasmUrl) { - if (!parsed || parsed.error) return parsed || { error: 'Пусто' }; + if (!parsed || parsed.error) return parsed || { error: 'Empty' }; const missing = []; if (!parsed.token) missing.push('token (authorization: Bearer)'); if (!parsed.cookie) missing.push('cookie'); - if (missing.length) return { error: 'Не найдено: ' + missing.join(', ') }; + if (missing.length) return { error: 'Missing: ' + missing.join(', ') }; return { token: parsed.token, hif_dliq: parsed.hif_dliq || '', diff --git a/public/dashboard.html b/public/dashboard.html index a5795c5..a0bece4 100644 --- a/public/dashboard.html +++ b/public/dashboard.html @@ -3,7 +3,7 @@ -FreeDeepseekAPI — Дашборд +FreeDeepseekAPI — Dashboard @@ -93,11 +93,7 @@ .chip{font-family:var(--mono);font-size:var(--fs-xs);background:var(--card2);border:1px solid var(--border);border-radius:6px;padding:7px 10px;cursor:pointer;transition:.12s;display:flex;align-items:center;gap:6px;color:var(--text)} .chip:hover{border-color:var(--accent)} -<<<<<<< Updated upstream - /* аккаунты — прямые полосы статуса */ -======= /* accounts — direct status bars */ ->>>>>>> Stashed changes .spacer{flex:1} .acc{display:flex;align-items:center;gap:var(--s3);padding:10px 12px;background:var(--card2);border:1px solid var(--border);border-left:3px solid var(--faint);border-radius:0 var(--r) var(--r) 0;margin-bottom:6px;flex-wrap:wrap} .acc.OK{border-left-color:var(--ok)} .acc.WAIT{border-left-color:var(--warn)} @@ -126,11 +122,7 @@ .bubble .role{font-size:10px;text-transform:uppercase;letter-spacing:.06em;opacity:.65;margin-bottom:4px;font-weight:700} .bubble.streaming .body::after{content:"▍";animation:blink 1s steps(2) infinite;color:var(--accent)} @keyframes blink{50%{opacity:0}} -<<<<<<< Updated upstream - /* панель размышлений */ -======= /* reasoning panel */ ->>>>>>> Stashed changes .think{margin-bottom:8px;border:1px solid var(--border);border-radius:8px;background:var(--bg2);overflow:hidden} .think summary{cursor:pointer;padding:6px 10px;color:var(--muted);font-weight:600;font-size:var(--fs-xs);display:flex;align-items:center;gap:6px;user-select:none;list-style:none} .think summary::-webkit-details-marker{display:none} @@ -187,23 +179,6 @@

FreeDeepseekAPI

-<<<<<<< Updated upstream - проверка… -
- - - - -
-
-

Плейграунд чата -
- ->>>>>>> Stashed changes -<<<<<<< Updated upstream - -
-

-
-
- - -=======

@@ -241,24 +206,10 @@

Chat Playground ->>>>>>> Stashed changes -<<<<<<< Updated upstream - -
-
-

Аккаунты DeepSeek - -

-
загрузка…
-
-
Добавить аккаунт: в браузере (где вошли в DeepSeek) F12 → Network → отправьте сообщение → правый клик на запросе к chat.deepseek.com/api/...Copy as cURL (или Save All As HAR) → вставьте сюда.
- -
-=======
@@ -270,26 +221,10 @@

DeepSeek Accounts Add Account: in browser (where logged into DeepSeek) F12 → Network → send message → right-click request to chat.deepseek.com/api/...Copy as cURL (or Save All As HAR) → paste here.

->>>>>>> Stashed changes
-<<<<<<< Updated upstream - -
-
-
статус
-
моделей
-
аккаунтов онлайн
-
-
-
-

Как подключить

-
-
любой непустой (sk-deepseek)
-
deepseek-chat
-=======
@@ -303,24 +238,10 @@

How to Connect

any non-empty (sk-deepseek)
deepseek-chat
->>>>>>> Stashed changes
-<<<<<<< Updated upstream -
OpenAI-совместимо. Также есть Anthropic-шим /v1/messages и Responses /v1/responses.
-
-
-

Авторизация DeepSeek

-
загрузка…
-
Обновить логин: запустите Авторизация DeepSeek.bat, войдите в DeepSeek в открывшемся окне, отправьте ok и нажмите Enter в терминале.
-
-
-

Модели (0) - R reasoning · S web-поиск · клик копирует

-
загрузка…
-=======
OpenAI-compatible. Also has Anthropic shim /v1/messages and Responses /v1/responses.
@@ -332,16 +253,11 @@

DeepSeek Authorization

Models (0) R reasoning · S web search · click copies

loading…
->>>>>>> Stashed changes
-<<<<<<< Updated upstream - -======= ->>>>>>> Stashed changes
@@ -352,29 +268,17 @@

Models ( ``; function toast(m, kind){ const t=$('#toast'); t.innerHTML=(kind==='err'?icon('i-warn'):kind==='ok'?icon('i-check'):'')+''+esc(m)+''; t.className='toast show'+(kind?' '+kind:''); clearTimeout(toast._t); toast._t=setTimeout(()=>t.className='toast',1900); } async function copyText(t){ -<<<<<<< Updated upstream - try{ if(navigator.clipboard && isSecureContext){ await navigator.clipboard.writeText(t); return toast('Скопировано','ok'); } throw 0; } - catch{ try{ const a=document.createElement('textarea'); a.value=t; a.style.position='fixed'; a.style.opacity='0'; document.body.appendChild(a); a.select(); document.execCommand('copy'); a.remove(); toast('Скопировано','ok'); }catch{ toast('Не удалось скопировать','err'); } } -} -function mdRender(text){ try{ if(window.marked&&window.DOMPurify){ const html=window.marked.parse(text,{breaks:true}); return window.DOMPurify.sanitize(html,{ADD_ATTR:['target']}); } }catch{} return null; } -// стабильный agent-id для серверной сессии DeepSeek (многоходовость) -======= try{ if(navigator.clipboard && isSecureContext){ await navigator.clipboard.writeText(t); return toast('Copied','ok'); } throw 0; } catch{ try{ const a=document.createElement('textarea'); a.value=t; a.style.position='fixed'; a.style.opacity='0'; document.body.appendChild(a); a.select(); document.execCommand('copy'); a.remove(); toast('Copied','ok'); }catch{ toast('Failed to copy','err'); } } } function mdRender(text){ try{ if(window.marked&&window.DOMPurify){ const html=window.marked.parse(text,{breaks:true}); return window.DOMPurify.sanitize(html,{ADD_ATTR:['target']}); } }catch{} return null; } // stable agent-id for server-side DeepSeek session (multi-turn) ->>>>>>> Stashed changes const AGENT_ID = (()=>{ let v=localStorage.getItem('ds_agent'); if(!v){ v='dash-'+Math.abs(Date.now()^(performance.now()*1000|0)).toString(36); localStorage.setItem('ds_agent',v); } return v; })(); const apiBase = origin+'/v1'; $('#baseUrl').textContent = apiBase; -<<<<<<< Updated upstream -// ── Вкладки ────────────────────────────────────────────────────────────── -======= // ── Tabs ────────────────────────────────────────────────────────────── ->>>>>>> Stashed changes const TABS=['chat','accounts','overview']; const inited={}; function showTab(name){ @@ -400,17 +304,10 @@

Models (copyText(`curl ${apiBase}/chat/completions \\\n -H "Authorization: Bearer sk-deepseek" \\\n -H "Content-Type: application/json" \\\n -d '{"model":"deepseek-chat","messages":[{"role":"user","content":"Привет"}]}'`); -$('#copyPy').onclick=()=>copyText(`from openai import OpenAI\nclient = OpenAI(base_url="${apiBase}", api_key="sk-deepseek")\nr = client.chat.completions.create(model="deepseek-chat",\n messages=[{"role":"user","content":"Привет"}])\nprint(r.choices[0].message.content)`); - -// ── Статус / KPI / авторизация ─────────────────────────────────────────── -======= $('#copyCurl').onclick=()=>copyText(`curl ${apiBase}/chat/completions \\\n -H "Authorization: Bearer sk-deepseek" \\\n -H "Content-Type: application/json" \\\n -d '{"model":"deepseek-chat","messages":[{"role":"user","content":"Hi"}]}'`); $('#copyPy').onclick=()=>copyText(`from openai import OpenAI\nclient = OpenAI(base_url="${apiBase}", api_key="sk-deepseek")\nr = client.chat.completions.create(model="deepseek-chat",\n messages=[{"role":"user","content":"Hi"}])\nprint(r.choices[0].message.content)`); // ── Status / KPI / Auth ─────────────────────────────────────────── ->>>>>>> Stashed changes let healthCache=null; async function loadHealth(){ try{ @@ -418,15 +315,6 @@

Models (=0?' ('+d+' дн)':' (истёк)'); } -======= $('#statusText').textContent='online · '+n+' models · '+(ac.online??0)+'/'+(ac.total??0)+' acc.'; $('#kpiStatus').textContent='Online'; $('#kpiStatus').className='num ok'; $('#kpiModels').textContent=n; @@ -434,23 +322,10 @@

Models (=0?' ('+d+' days)':' (expired)'); } ->>>>>>> Stashed changes async function loadAuth(){ const box=$('#authBox'); try{ const j=await (await fetch(origin+'/api/auth-status')).json(); -<<<<<<< Updated upstream - if(!j.config_ready){ box.innerHTML='
Авторизация не настроена. Запустите Авторизация DeepSeek.bat и войдите в свой аккаунт DeepSeek.
'; return; } - const exp=j.token_exp?('
'+esc(fmtExp(j.token_exp))+'
'):''; - box.innerHTML=`
${j.has_token?'есть ✓':'нет'}
-
${j.has_cookie?'есть ✓':'нет'}
-
${j.has_hif?'есть ✓':'опционально'}
${exp}`; - }catch{ box.innerHTML='
Не удалось получить статус авторизации
'; } -} -// ── Аккаунты ─────────────────────────────────────────────────────────────── -const accStLabel=s=>({OK:'активен',WAIT:'лимит',INVALID:'невалиден',EXPIRED:'истёк',ERROR:'ошибка'})[s]||s; -function fmtResetAt(iso){ try{ return new Date(iso).toLocaleString('ru-RU',{day:'2-digit',month:'2-digit',hour:'2-digit',minute:'2-digit'}); }catch{ return ''; } } -======= if(!j.config_ready){ box.innerHTML='
Authorization not configured. Run DeepSeek Authorization.bat and log into your DeepSeek account.
'; return; } const exp=j.token_exp?('
'+esc(fmtExp(j.token_exp))+'
'):''; box.innerHTML=`
${j.has_token?'present ✓':'missing'}
@@ -461,27 +336,10 @@

Models (({OK:'active',WAIT:'limit',INVALID:'invalid',EXPIRED:'expired',ERROR:'error'})[s]||s; function fmtResetAt(iso){ try{ return new Date(iso).toLocaleString('en-US',{day:'2-digit',month:'2-digit',hour:'2-digit',minute:'2-digit'}); }catch{ return ''; } } ->>>>>>> Stashed changes async function loadAccountsList(){ const list=$('#accList'); try{ const j=await (await fetch(origin+'/api/accounts')).json(); -<<<<<<< Updated upstream - if(!j.accounts||!j.accounts.length){ list.innerHTML='
Пока нет аккаунтов. Добавьте первый через импорт ниже.
'; return; } - list.innerHTML=j.accounts.map(a=>{ - const meta=a.resetAt?('лимит до '+fmtResetAt(a.resetAt)):(a.exp?fmtExp(a.exp):''); - const email=a.email?`${esc(a.email)}`:''; - const labelHtml=a.label?`${esc(a.label)}`:''; - return `
${esc(a.id)}${labelHtml}${accStLabel(a.status)}${email}…${esc(a.preview||'')}${esc(meta)} - - -
`; - }).join(''); - setAccUpdated(); - }catch{ list.innerHTML='
Не удалось загрузить аккаунты.
'; } -} -function setAccUpdated(){ const e=$('#accUpdated'); if(e) e.textContent='обновлено '+new Date().toLocaleTimeString('ru-RU',{hour:'2-digit',minute:'2-digit'}); } -======= if(!j.accounts||!j.accounts.length){ list.innerHTML='
No accounts yet. Add first via import below.
'; return; } list.innerHTML=j.accounts.map(a=>{ const meta=a.resetAt?('limit until '+fmtResetAt(a.resetAt)):(a.exp?fmtExp(a.exp):''); @@ -496,22 +354,10 @@

Models ('; } } function setAccUpdated(){ const e=$('#accUpdated'); if(e) e.textContent='updated '+new Date().toLocaleTimeString('en-US',{hour:'2-digit',minute:'2-digit'}); } ->>>>>>> Stashed changes $('#checkAllAcc').onclick=async()=>{ const b=$('#checkAllAcc'); b.disabled=true; try{ const j=await (await fetch(origin+'/api/accounts')).json(); for(const a of (j.accounts||[])){ try{ await fetch(origin+'/api/accounts/'+encodeURIComponent(a.id)+'/check',{method:'POST'}); }catch{} } -<<<<<<< Updated upstream - await loadAccountsList(); await loadHealth(); toast('Проверка завершена','ok'); - }catch{ toast('Ошибка','err'); } b.disabled=false; -}; -$('#importBtn').onclick=async()=>{ - const text=$('#importText').value.trim(); if(!text) return toast('Вставьте cURL или HAR','err'); - const b=$('#importBtn'); b.disabled=true; - try{ const j=await (await fetch(origin+'/api/accounts/import',{method:'POST',headers:{'Content-Type':'text/plain'},body:text})).json(); - if(j.ok){ toast('Добавлен '+j.id,'ok'); $('#importText').value=''; loadAccountsList(); loadHealth(); } else toast(j.error||'Ошибка импорта','err'); - }catch{ toast('Сеть недоступна','err'); } b.disabled=false; -======= await loadAccountsList(); await loadHealth(); toast('Check complete','ok'); }catch{ toast('Error','err'); } b.disabled=false; }; @@ -521,35 +367,12 @@

Models (>>>>>> Stashed changes }; document.addEventListener('click', async e=>{ const t=e.target.closest('button'); if(!t) return; if(t.dataset.label!==undefined){ const acc=t.closest('.acc'); if(!acc) return; const cur=acc.querySelector('.acc-label')?.textContent||''; -<<<<<<< Updated upstream - const inp=document.createElement('input'); inp.type='text'; inp.value=cur; inp.placeholder='имя аккаунта'; inp.style.cssText='max-width:150px;padding:3px 7px'; - acc.insertBefore(inp, acc.querySelector('.spacer')); inp.focus(); inp.select(); - inp.addEventListener('keydown', async ev=>{ - if(ev.key==='Enter'){ ev.preventDefault(); - try{ await fetch(origin+'/api/accounts/'+encodeURIComponent(t.dataset.label)+'/label',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({label:inp.value})}); toast('Имя сохранено','ok'); }catch{ toast('Ошибка','err'); } - loadAccountsList(); - } else if(ev.key==='Escape'){ loadAccountsList(); } - }); - inp.addEventListener('blur', ()=>loadAccountsList()); // убрать input, если кликнули мимо - return; - } - if(t.dataset.check){ const acc=t.closest('.acc'); const badge=acc&&acc.querySelector('.badge'); if(badge){ badge.className='badge checking'; badge.textContent='проверка'; } t.disabled=true; - try{ await fetch(origin+'/api/accounts/'+encodeURIComponent(t.dataset.check)+'/check',{method:'POST'}); }catch{} await loadAccountsList(); await loadHealth(); return; } - if(t.dataset.del){ - if(t.dataset.confirm!=='1'){ t.dataset.confirm='1'; t.classList.add('danger'); t.innerHTML=icon('i-trash')+'точно?'; setTimeout(()=>{ if(t.dataset.confirm==='1'){ t.dataset.confirm=''; t.classList.remove('danger'); t.innerHTML=icon('i-trash'); } },3000); return; } - t.disabled=true; - try{ await fetch(origin+'/api/accounts/'+encodeURIComponent(t.dataset.del),{method:'DELETE'}); toast('Удалён','ok'); }catch{ toast('Ошибка','err'); } loadAccountsList(); loadHealth(); return; } -}); -setInterval(()=>{ if($('#tab-accounts').classList.contains('active')) loadAccountsList(); }, 20000); -// ── Модели ───────────────────────────────────────────────────────────────── -======= const inp=document.createElement('input'); inp.type='text'; inp.value=cur; inp.placeholder='account name'; inp.style.cssText='max-width:150px;padding:3px 7px'; acc.insertBefore(inp, acc.querySelector('.spacer')); inp.focus(); inp.select(); inp.addEventListener('keydown', async ev=>{ @@ -570,7 +393,6 @@

Models ({ if($('#tab-accounts').classList.contains('active')) loadAccountsList(); }, 20000); // ── Models ───────────────────────────────────────────────────────────────── ->>>>>>> Stashed changes async function loadModels(){ const list=$('#modelList'); try{ @@ -579,21 +401,12 @@

Models ({ const c=m.capabilities||{}; -<<<<<<< Updated upstream - const caps=(c.reasoning?'R':'')+(c.search?'S':''); - return `${esc(m.id)}${caps}`; - }).join('')||'не удалось загрузить'; - }catch{ list.innerHTML='не удалось загрузить. '; } -} -// #chatModel наполняется реальными моделями из /v1/models; выбор сохраняется в localStorage -======= const caps=(c.reasoning?'R':'')+(c.search?'S':''); return `${esc(m.id)}${caps}`; }).join('')||'failed to load'; }catch{ list.innerHTML='failed to load. '; } } // #chatModel is populated with real models from /v1/models; selection saved to localStorage ->>>>>>> Stashed changes async function populateChatModels(){ const sel=$('#chatModel'); if(!sel) return; try{ @@ -602,23 +415,6 @@

Models (``).join(''); if(saved && data.some(m=>m.id===saved)) sel.value=saved; -<<<<<<< Updated upstream - }catch{ /* оставляем статичный fallback-список */ } -} -document.addEventListener('click', e=>{ const c=e.target.closest('.chip[data-model]'); if(c) copyText(c.dataset.model); }); - -// Модель берётся напрямую из селектора (#chatModel). -// Веб-поиск (-search) сейчас отключён: DeepSeek Web стабильно отдаёт пустой ответ. - -// ── Чат (история + reasoning + markdown, через /v1/chat/completions) ────────── -let chatHistory=[], chatBusy=false, chatAbort=null; -function bubbleEl(m){ - const d=document.createElement('div'); d.className='bubble '+m.role+(m.err?' err':''); - d.innerHTML='
'+(m.role==='user'?'Вы':'AI')+'
'; - if(m.role==='assistant' && m.reasoning){ - const det=document.createElement('details'); det.className='think'; - det.innerHTML=''+icon('i-bulb')+'Размышления
'; -======= }catch{ /* keep static fallback list */ } } document.addEventListener('click', e=>{ const c=e.target.closest('.chip[data-model]'); if(c) copyText(c.dataset.model); }); @@ -634,7 +430,6 @@

Models (
'; ->>>>>>> Stashed changes det.querySelector('.think-body').textContent=m.reasoning; d.appendChild(det); d._think=det; } @@ -645,28 +440,13 @@

Models (copyText(m.content); -======= cp.innerHTML=icon('i-copy')+'copy'; cp.onclick=()=>copyText(m.content); ->>>>>>> Stashed changes d.appendChild(cp); } return d; } function renderChat(){ const box=$('#msgs'); -<<<<<<< Updated upstream - if(!chatHistory.length){ box.innerHTML='
'+icon('i-chat')+'Начните диалог. Ответ — стримингом с markdown; у reasoner-моделей появится панель «Размышления».
'; return; } - box.innerHTML=''; chatHistory.forEach(m=>box.appendChild(bubbleEl(m))); box.scrollTop=box.scrollHeight; -} -function atBottom(){ const b=$('#msgs'); return b.scrollHeight-b.scrollTop-b.clientHeight<60; } -function setBusy(b){ chatBusy=b; const btn=$('#sendChat'); btn.innerHTML=b?'Стоп':icon('i-send')+'Отправить'; btn.classList.toggle('danger',b); } -function ensureThink(node, asst){ - if(node._think) return node._think.querySelector('.think-body'); - const det=document.createElement('details'); det.className='think'; det.open=true; - det.innerHTML=''+icon('i-bulb')+'Размышления
'; -======= if(!chatHistory.length){ box.innerHTML='
'+icon('i-chat')+'Start dialog. Response streams with markdown; reasoner models will show \'Reasoning\' panel.
'; return; } box.innerHTML=''; chatHistory.forEach(m=>box.appendChild(bubbleEl(m))); box.scrollTop=box.scrollHeight; } @@ -676,7 +456,6 @@

Models (
'; ->>>>>>> Stashed changes node.insertBefore(det, node._c); node._think=det; return det.querySelector('.think-body'); } @@ -708,15 +487,9 @@

Models (copyText(asst.content); node.appendChild(cp); } -======= if(!asst.content && !asst.reasoning){ chatHistory.pop(); node.remove(); toast('Empty response','err'); } else { asst.done=true; if(node._think) node._think.open=false; const html=mdRender(asst.content); if(html){ node.classList.remove('plain'); node._c.className='body md'; node._c.innerHTML=html; addCopyButtons(node._c); } if((asst.content||'').trim()){ const cp=document.createElement('button'); cp.className='sm'; cp.type='button'; cp.style.cssText='margin-top:6px;padding:3px 8px;font-size:11px'; cp.innerHTML=icon('i-copy')+'copy'; cp.onclick=()=>copyText(asst.content); node.appendChild(cp); } ->>>>>>> Stashed changes if(atBottom()) box.scrollTop=box.scrollHeight; } }catch(e){ if(e.name==='AbortError'){ if(!asst.content&&!asst.reasoning){ chatHistory.pop(); node.remove(); } else asst.done=true; } @@ -731,11 +504,7 @@

Models (>>>>>> Stashed changes function addCopyButtons(container){ container.querySelectorAll('pre').forEach(pre=>{ if(pre.querySelector('.code-copy')) return; @@ -748,22 +517,14 @@

Models ({ el.style.height='auto'; const mh=parseInt(getComputedStyle(el).maxHeight)||9999; el.style.height=Math.min(el.scrollHeight, mh)+'px'; }; el.addEventListener('input',fn); } -<<<<<<< Updated upstream -// ── Старт ────────────────────────────────────────────────────────────────── -======= // ── Start ────────────────────────────────────────────────────────────────── ->>>>>>> Stashed changes renderChat(); populateChatModels(); $('#chatModel').addEventListener('change',()=>localStorage.setItem('ds_model',$('#chatModel').value)); autoGrow($('#chatInput')); autoGrow($('#importText')); showTab((location.hash||'#chat').slice(1)||'chat'); loadHealth(); setInterval(loadHealth, 15000); -<<<<<<< Updated upstream -// #28 авто-обновление Обзора (модели/auth), пока вкладка активна -======= // #28 auto-refresh Overview (models/auth) while tab is active ->>>>>>> Stashed changes setInterval(()=>{ const ov=$('#tab-overview'); if(ov&&ov.classList.contains('active')){ loadModels(); loadAuth(); } }, 30000); diff --git a/scripts/auth.js b/scripts/auth.js index c25f81f..57e5651 100755 --- a/scripts/auth.js +++ b/scripts/auth.js @@ -1,14 +1,24 @@ #!/usr/bin/env node -const fs = require('fs'); -const path = require('path'); -const readline = require('readline'); -const { spawnSync } = require('child_process'); +import fs from 'fs'; +import path from 'path'; +import readline from 'readline'; +import { spawnSync, spawn } from 'child_process'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); const ROOT = path.resolve(__dirname, '..'); const AUTH_PATH = process.env.DEEPSEEK_AUTH_PATH || path.join(ROOT, 'deepseek-auth.json'); const PROFILE_DIR = process.env.DEEPSEEK_CHROME_PROFILE || path.join(ROOT, '.chrome-for-testing-profile-deepseek'); const WATERMARK = 't.me/forgetmeai'; +const DEFAULT_CHROME_PATHS = [ + 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe', + 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe', + 'C:\\Users\\USUARIO\\AppData\\Local\\Google\\Chrome\\Application\\chrome.exe', +]; + function prompt(question) { const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); return new Promise(resolve => rl.question(question, ans => { rl.close(); resolve(ans); })); @@ -19,26 +29,22 @@ function loadAuth() { try { return JSON.parse(fs.readFileSync(AUTH_PATH, 'utf8')); } catch { return null; } } +function findChrome() { + for (const p of DEFAULT_CHROME_PATHS) { + if (fs.existsSync(p)) return p; + } + return null; +} function status() { const auth = loadAuth(); -<<<<<<< Updated upstream - console.log('\nDeepSeek аккаунт:'); - if (!auth) { - console.log(' ❌ deepseek-auth.json не найден'); -======= console.log('\nDeepSeek account:'); if (!auth) { console.log(' ❌ deepseek-auth.json not found'); ->>>>>>> Stashed changes } else { console.log(` ✅ auth file: ${AUTH_PATH}`); console.log(` token: ${auth.token ? 'OK (' + String(auth.token).length + ' chars)' : 'MISSING'}`); console.log(` cookies: ${auth.cookie ? 'OK' : 'MISSING'}`); -<<<<<<< Updated upstream - console.log(` Chrome profile: ${fs.existsSync(PROFILE_DIR) ? PROFILE_DIR : 'не найден'}`); -======= console.log(` Chrome profile: ${fs.existsSync(PROFILE_DIR) ? PROFILE_DIR : 'not found'}`); ->>>>>>> Stashed changes } } function runDirectAuth() { @@ -51,57 +57,75 @@ function runImportAuth() { } function removeLocalAuth() { if (fs.existsSync(AUTH_PATH)) fs.rmSync(AUTH_PATH, { force: true }); -<<<<<<< Updated upstream - console.log('Удалён deepseek-auth.json. Chrome profile оставлен, чтобы не разлогинивать браузер без нужды.'); -} -function printHelp() { - divider(); - console.log('FreeDeepseekAPI — управление DeepSeek Web login'); - console.log(watermark()); - divider(); - console.log('Опции:'); - console.log(' --login Открыть Chrome и обновить auth'); - console.log(' --import Импортировать готовый deepseek-auth.json / browser cookies'); - console.log(' --status Показать статус auth'); - console.log(' --remove Удалить локальный deepseek-auth.json'); - console.log(' --help Справка'); - console.log('Без опций запускается интерактивное меню.'); -======= - console.log('Deleted deepseek-auth.json. Chrome profile left to avoid logging out browser unnecessarily.'); + console.log('Removed deepseek-auth.json. Chrome profile kept to avoid unnecessary logout.'); } function printHelp() { divider(); console.log('FreeDeepseekAPI — DeepSeek Web login management'); console.log(watermark()); divider(); - console.log('Опции:'); - console.log(' --login Open Chrome and update auth'); - console.log(' --import Import ready deepseek-auth.json / browser cookies'); + console.log('Options:'); + console.log(' --login Open Chrome and refresh auth'); + console.log(' --import Import deepseek-auth.json / browser cookies'); console.log(' --status Show auth status'); console.log(' --remove Delete local deepseek-auth.json'); - console.log(' --help Справка'); - console.log('Without options, interactive menu starts.'); ->>>>>>> Stashed changes + console.log(' --help Show this help'); + console.log('No options launches the interactive menu.'); divider(); } +async function runChromeAuth() { + console.log('\nLaunching Chrome for authorization...\n'); + const chromePath = process.env.CHROME_PATH || findChrome(); + + if (!chromePath) { + console.log('❌ Chrome not found. Set CHROME_PATH environment variable.'); + return; + } + + const userDataDir = path.join(ROOT, '.chrome-profile'); + if (!fs.existsSync(userDataDir)) fs.mkdirSync(userDataDir, { recursive: true }); + + const chromeArgs = [ + `--user-data-dir=${userDataDir}`, + '--remote-debugging-port=9222', + 'https://chat.deepseek.com/', + ]; + + console.log(` Chrome: ${chromePath}`); + console.log(` Profile: ${userDataDir}`); + console.log('\n Log in to DeepSeek (if not already).'); + console.log(' Send any message in the chat (e.g., "ok").'); + console.log(' Then return here and press Enter.\n'); + + const chrome = spawn(chromePath, chromeArgs, { + stdio: 'ignore', + detached: true, + }); + chrome.unref(); + + await prompt('Press Enter after you have logged in and sent a message...'); + + console.log('\n Extracting session via CDP...'); + try { + const { extractAuth } = await import('./cdp-extract.js'); + const auth = await extractAuth('http://localhost:9222'); + if (auth) { + fs.writeFileSync(AUTH_PATH, JSON.stringify(auth, null, 2)); + console.log('✅ Session saved to', AUTH_PATH); + } else { + console.log('❌ Failed to extract session. Please try again.'); + } + } catch (e) { + console.log('❌ Error extracting session:', e.message); + console.log('\n Alternative: export cookies and use option 2 to import.'); + } +} async function menu() { while (true) { divider(); console.log(watermark()); status(); divider(); -<<<<<<< Updated upstream - console.log('Меню:'); - console.log('1 - Авторизоваться / обновить DeepSeek login'); - console.log('2 - Импортировать auth-файл / cookies'); - console.log('3 - Показать статус'); - console.log('4 - Удалить локальный auth файл'); - console.log('5 - Выход'); - const choice = (await prompt('Ваш выбор (Enter = 5): ')) || '5'; - if (choice === '1') runDirectAuth(); - else if (choice === '2') runImportAuth(); - else if (choice === '3') { status(); await prompt('\nНажмите Enter, чтобы вернуться в меню...'); } -======= console.log('Menu:'); console.log('1 - Authorize / update DeepSeek login'); console.log('2 - Import auth file / cookies'); @@ -109,10 +133,9 @@ async function menu() { console.log('4 - Delete local auth file'); console.log('5 - Exit'); const choice = (await prompt('Your choice (Enter = 5): ')) || '5'; - if (choice === '1') runDirectAuth(); + if (choice === '1') await runChromeAuth(); else if (choice === '2') runImportAuth(); - else if (choice === '3') { status(); await prompt('\nPress Enter to return to menu...'); } ->>>>>>> Stashed changes + else if (choice === '3') { status(); await prompt('\nPress Enter to return to the menu...'); } else if (choice === '4') removeLocalAuth(); else if (choice === '5') break; } @@ -120,7 +143,7 @@ async function menu() { (async () => { const args = new Set(process.argv.slice(2)); if (args.has('--help') || args.has('-h')) return printHelp(); - if (args.has('--login') || args.has('--add') || args.has('--relogin')) return void runDirectAuth(); + if (args.has('--login') || args.has('--add') || args.has('--relogin')) return void runChromeAuth(); if (args.has('--import')) return void runImportAuth(); if (args.has('--status') || args.has('--list')) return status(); if (args.has('--remove')) return removeLocalAuth(); diff --git a/scripts/auth_import.js b/scripts/auth_import.js index 96b8417..0d1c94e 100644 --- a/scripts/auth_import.js +++ b/scripts/auth_import.js @@ -74,18 +74,18 @@ Usage: DEEPSEEK_TOKEN="" npm run auth:import -- --input ./cookies.json Options: - --input, -i Source JSON: готовый deepseek-auth.json или browser cookie export + --input, -i Source JSON: ready deepseek-auth.json or browser cookie export --output, -o Target auth path (default: ${DEFAULT_OUT}) Security: - Для cookies.json передавайте token через DEEPSEEK_TOKEN, не через CLI argument, - чтобы не светить его в shell history/process list. + For cookies.json, pass the token via DEEPSEEK_TOKEN instead of a CLI argument + to avoid leaking it in shell history/process lists. VPS flow: - 1) На домашнем ПК: npm run auth - 2) Скопируй deepseek-auth.json на VPS - 3) На VPS: npm run auth:import -- --input ./deepseek-auth.json - 4) Запуск: NON_INTERACTIVE=1 npm start`); + 1) On your local PC: npm run auth + 2) Copy deepseek-auth.json to the VPS + 3) On VPS: npm run auth:import -- --input ./deepseek-auth.json + 4) Start: NON_INTERACTIVE=1 npm start`); } async function main(argv = process.argv.slice(2)) { const tokenArg = argValue(argv, '--token'); @@ -103,7 +103,7 @@ async function main(argv = process.argv.slice(2)) { const errors = validateAuth(auth); if (errors.length) { console.error(`[auth:import] Invalid auth import: ${errors.join(', ')}`); - console.error('[auth:import] Если импортируешь browser cookies, передай token через DEEPSEEK_TOKEN=...'); + console.error('[auth:import] When importing browser cookies, pass the token via DEEPSEEK_TOKEN=...'); return 2; } secureWriteJson(outputPath, auth); diff --git a/scripts/cdp-extract.js b/scripts/cdp-extract.js new file mode 100644 index 0000000..d8e2e6e --- /dev/null +++ b/scripts/cdp-extract.js @@ -0,0 +1,146 @@ +/* + Extract DeepSeek auth from a running Chrome instance via CDP. + + Usage (from auth.js): + const { extractAuth } = await import('./cdp-extract.js'); + const auth = await extractAuth('http://localhost:9222'); + + Returns { token, cookie, hif_dliq, hif_leim, wasmUrl } or null. +*/ + +class CDP { + constructor(wsUrl) { + this.ws = new WebSocket(wsUrl); + this.id = 0; + this.pending = new Map(); + this.events = []; + this.ws.onmessage = (ev) => { + const msg = JSON.parse(ev.data); + if (msg.id && this.pending.has(msg.id)) { + const { resolve, reject } = this.pending.get(msg.id); + this.pending.delete(msg.id); + msg.error + ? reject(new Error(JSON.stringify(msg.error))) + : resolve(msg.result); + } else if (msg.method) { + this.events.push(msg); + if (this.events.length > 1000) this.events.shift(); + } + }; + } + ready() { + return new Promise((resolve, reject) => { + this.ws.onopen = resolve; + this.ws.onerror = reject; + }); + } + send(method, params = {}) { + const id = ++this.id; + this.ws.send(JSON.stringify({ id, method, params })); + return new Promise((resolve, reject) => + this.pending.set(id, { resolve, reject }), + ); + } + close() { + try { this.ws.close(); } catch {} + } +} + +function normalizeToken(raw) { + if (!raw) return ''; + try { + const parsed = JSON.parse(raw); + if (parsed && typeof parsed === 'object') + return parsed.value || parsed.token || parsed.access_token || parsed.accessToken || ''; + } catch {} + return String(raw).trim(); +} + +async function readPageAuth(cdp) { + const evalRes = await cdp.send('Runtime.evaluate', { + expression: `(() => { + const out = {href: location.href, localStorage:{}, sessionStorage:{}, resources: []}; + for (let i=0;i r.name).filter(n => /wasm|chat\\/completion|pow|chat_session/.test(n)).slice(-100); + return out; + })()`, + returnByValue: true, + }); + const pageState = evalRes.result.value || {}; + const stores = [pageState.localStorage || {}, pageState.sessionStorage || {}]; + + let token = ''; + for (const store of stores) { + for (const key of ['userToken', 'token', 'auth_token', 'access_token', 'accessToken']) { + token = normalizeToken(store[key]); + if (token) break; + } + if (token) break; + } + if (!token) { + for (const store of stores) { + for (const [k, v] of Object.entries(store)) { + if (/token/i.test(k)) { + token = normalizeToken(v); + if (token) break; + } + } + if (token) break; + } + } + + const cookieRes = await cdp.send('Network.getAllCookies'); + const cookies = (cookieRes.cookies || []).filter((c) => /deepseek\.com$/.test(c.domain)); + const cookie = cookies.map((c) => `${c.name}=${c.value}`).join('; '); + + let hif_dliq = '', hif_leim = ''; + for (const ev of cdp.events) { + const headers = ev.params?.headers || ev.params?.request?.headers; + if (!headers) continue; + for (const [k, v] of Object.entries(headers)) { + const lk = k.toLowerCase(); + if (lk === 'x-hif-dliq') hif_dliq = String(v); + if (lk === 'x-hif-leim') hif_leim = String(v); + if (lk === 'authorization' && !token && /^Bearer\s+/i.test(String(v))) + token = String(v).replace(/^Bearer\s+/i, ''); + } + } + + const wasmUrl = (pageState.resources || []).find((u) => /sha3.*\.wasm/.test(u)) || + 'https://fe-static.deepseek.com/chat/static/sha3_wasm_bg.7b9ca65ddd.wasm'; + + return { token, cookie, hif_dliq, hif_leim, wasmUrl }; +} + +async function getPageTarget(baseUrl) { + const resp = await fetch(`${baseUrl}/json`); + const targets = await resp.json(); + return targets.find((t) => /deepseek/i.test(t.url || '') && t.type === 'page') || targets.find((t) => t.type === 'page') || targets[0]; +} + +export async function extractAuth(cdpBaseUrl = 'http://localhost:9222') { + try { + const target = await getPageTarget(cdpBaseUrl); + if (!target?.webSocketDebuggerUrl) return null; + + const cdp = new CDP(target.webSocketDebuggerUrl); + await cdp.ready(); + await cdp.send('Runtime.enable'); + await cdp.send('Network.enable'); + + let auth = null; + for (let i = 0; i < 10; i++) { + auth = await readPageAuth(cdp); + if (auth.token && auth.cookie) break; + await new Promise((r) => setTimeout(r, 500)); + } + cdp.close(); + + if (!auth || !auth.token) return null; + const { href, cookiesCount, ...persisted } = auth; + return persisted; + } catch { + return null; + } +} diff --git a/scripts/deepseek_chrome_auth.js b/scripts/deepseek_chrome_auth.js index 45fbd0e..5423332 100755 --- a/scripts/deepseek_chrome_auth.js +++ b/scripts/deepseek_chrome_auth.js @@ -453,20 +453,13 @@ async function main() { await cdp.send('Network.enable'); console.log( -<<<<<<< Updated upstream - '\n[auth] Chrome открыт. Войди в DeepSeek в ЭТОМ отдельном окне.', - ); - console.log( - '[auth] После логина отправь в DeepSeek короткое сообщение, например: ok', -======= '\n[auth] Chrome opened. Log in to DeepSeek in THIS separate window.', ); console.log( '[auth] After login, send a short message to DeepSeek, e.g.: ok', ->>>>>>> Stashed changes ); await ask( - '[auth] Когда залогинился и отправил тестовое сообщение — нажми ENTER здесь: ', + '[auth] Once logged in and sent a test message — press ENTER here: ', ); let auth = null; diff --git a/scripts/probe_deepseek_models.js b/scripts/probe_deepseek_models.js index c4ff2a0..c000b6f 100755 --- a/scripts/probe_deepseek_models.js +++ b/scripts/probe_deepseek_models.js @@ -77,7 +77,7 @@ async function probe(model_type, thinking_enabled, search_enabled) { chat_session_id: sessionId, parent_message_id: null, model_type, - prompt: 'Ответь ровно OK', + prompt: 'Reply with exactly OK', ref_file_ids: [], thinking_enabled, search_enabled, diff --git a/server.js b/server.js index 178f648..944bfe2 100755 --- a/server.js +++ b/server.js @@ -43,7 +43,7 @@ function printBanner() { ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ███████ ███████ ██████ ███████ ███████ ███████ ██ ██ - FreeDeepseekAPI — API-прокси для DeepSeek Web Chat + FreeDeepseekAPI — API proxy for DeepSeek Web Chat ${formatWatermark()} `); } @@ -392,50 +392,30 @@ async function solvePOW(challenge, config = DS_CONFIG) { } const MODEL_CONFIGS = { -<<<<<<< Updated upstream - // DeepSeek Web real model_type: default / UI name: "Быстрый". - // Public model family: DeepSeek-V3.2-Exp chat mode (fast, no visible reasoning). - 'deepseek-chat': { - model_type: 'default', thinking_enabled: false, search_enabled: false, - real_model: 'DeepSeek-V4-Flash non-thinking (DeepSeek Web “Быстрый” / default)', -======= // DeepSeek Web real model_type: default / UI name: "Fast". // Public model family: DeepSeek-V3.2-Exp chat mode (fast, no visible reasoning). 'deepseek-chat': { model_type: 'default', thinking_enabled: false, search_enabled: false, - real_model: 'DeepSeek-V4-Flash non-thinking (DeepSeek Web “Fast” / default)', ->>>>>>> Stashed changes + real_model: 'DeepSeek-V4-Flash non-thinking (DeepSeek Web "Fast" / default)', capabilities: { reasoning: false, web_search: false, files: true }, supported: true, }, 'deepseek-v3': { model_type: 'default', thinking_enabled: false, search_enabled: false, -<<<<<<< Updated upstream - real_model: 'DeepSeek-V4-Flash non-thinking (DeepSeek Web “Быстрый” / default)', -======= - real_model: 'DeepSeek-V4-Flash non-thinking (DeepSeek Web “Fast” / default)', ->>>>>>> Stashed changes + real_model: 'DeepSeek-V4-Flash non-thinking (DeepSeek Web "Fast" / default)', capabilities: { reasoning: false, web_search: false, files: true }, supported: true, }, 'deepseek-default': { model_type: 'default', thinking_enabled: false, search_enabled: false, -<<<<<<< Updated upstream - real_model: 'DeepSeek-V4-Flash non-thinking (DeepSeek Web “Быстрый” / default)', -======= - real_model: 'DeepSeek-V4-Flash non-thinking (DeepSeek Web “Fast” / default)', ->>>>>>> Stashed changes + real_model: 'DeepSeek-V4-Flash non-thinking (DeepSeek Web "Fast" / default)', capabilities: { reasoning: false, web_search: false, files: true }, supported: true, }, // Same DeepSeek Web default model, but with thinking_enabled=true. UI exposes it as thinking/reasoning mode. 'deepseek-reasoner': { model_type: 'default', thinking_enabled: true, search_enabled: false, -<<<<<<< Updated upstream - real_model: 'DeepSeek-V4-Flash thinking mode (DeepSeek Web “Быстрый” + thinking_enabled)', -======= - real_model: 'DeepSeek-V4-Flash thinking mode (DeepSeek Web “Fast” + thinking_enabled)', ->>>>>>> Stashed changes + real_model: 'DeepSeek-V4-Flash thinking mode (DeepSeek Web "Fast" + thinking_enabled)', capabilities: { reasoning: true, web_search: false, files: true }, supported: true, }, @@ -447,13 +427,13 @@ const MODEL_CONFIGS = { }, 'deepseek-chat-search': { model_type: 'default', thinking_enabled: false, search_enabled: true, - real_model: 'DeepSeek-V4-Flash non-thinking (DeepSeek Web “Быстрый” / default) + web search', + real_model: 'DeepSeek-V4-Flash non-thinking (DeepSeek Web “Fast” / default) + web search', capabilities: { reasoning: false, web_search: true, files: true }, supported: true, }, 'deepseek-default-search': { model_type: 'default', thinking_enabled: false, search_enabled: true, - real_model: 'DeepSeek-V4-Flash non-thinking (DeepSeek Web “Быстрый” / default) + web search', + real_model: 'DeepSeek-V4-Flash non-thinking (DeepSeek Web “Fast” / default) + web search', capabilities: { reasoning: false, web_search: true, files: true }, supported: true, }, @@ -469,29 +449,29 @@ const MODEL_CONFIGS = { capabilities: { reasoning: true, web_search: true, files: true }, supported: true, }, - // DeepSeek Web UI name: “Эксперт”. Requires current web client headers (x-client-version=2.0.0). + // DeepSeek Web UI name: “Expert”. Requires current web client headers (x-client-version=2.0.0). 'deepseek-expert': { model_type: 'expert', thinking_enabled: false, search_enabled: false, - real_model: 'DeepSeek Web “Эксперт” (limited resources)', + real_model: 'DeepSeek Web “Expert” (limited resources)', capabilities: { reasoning: false, web_search: false, files: false }, supported: true, }, 'deepseek-v4-pro': { model_type: 'expert', thinking_enabled: true, search_enabled: false, - real_model: 'DeepSeek Web “Эксперт” + thinking mode (exposed as deepseek-v4-pro alias)', + real_model: 'DeepSeek Web “Expert” + thinking mode (exposed as deepseek-v4-pro alias)', capabilities: { reasoning: true, web_search: false, files: false }, supported: true, }, 'deepseek-expert-search': { model_type: 'expert', thinking_enabled: false, search_enabled: true, - real_model: 'DeepSeek Web “Эксперт” + search requested, but Expert has search_feature=null in remote config', + real_model: 'DeepSeek Web “Expert” + search requested, but Expert has search_feature=null in remote config', capabilities: { reasoning: false, web_search: false, files: false }, supported: false, unavailable_reason: 'Expert mode is rejected; remote config says search is not available for Expert.', }, 'deepseek-vision': { model_type: 'vision', thinking_enabled: false, search_enabled: false, - real_model: 'DeepSeek Web “Распознавание” / image understanding beta', + real_model: 'DeepSeek Web “Recognition” / image understanding beta', capabilities: { reasoning: false, web_search: false, files: true, vision: true }, supported: false, unavailable_reason: 'Current Web API returns: Vision is temporarily unavailable (backend_err_by_model).', @@ -1834,11 +1814,11 @@ async function runAuthScript() { function printStatus() { console.log(`\n${formatWatermark()}`); - console.log(`Auth: ${hasAuthConfig() ? '✅ OK' : '❌ не найден deepseek-auth.json'}`); + console.log(`Auth: ${hasAuthConfig() ? '✅ OK' : '❌ deepseek-auth.json not found'}`); console.log(`Auth source: ${process.env.DEEPSEEK_AUTH_DIR || DS_CONFIG_PATH}`); - console.log(`Аккаунты: ${accounts.length ? accounts.map(a => `${a.id}${a.cooldownUntil > Date.now() ? ' (cooldown)' : ''}`).join(', ') : 'нет'}`); - console.log(`Рабочие модели: ${SUPPORTED_MODEL_IDS.join(', ')}`); - console.log('Нерабочие/скрытые aliases: ' + Object.keys(MODEL_CONFIGS).filter(id => !MODEL_CONFIGS[id].supported).join(', ')); + console.log(`Accounts: ${accounts.length ? accounts.map(a => `${a.id}${a.cooldownUntil > Date.now() ? ' (cooldown)' : ''}`).join(', ') : 'none'}`); + console.log(`Working models: ${SUPPORTED_MODEL_IDS.join(', ')}`); + console.log('Unsupported/hidden aliases: ' + Object.keys(MODEL_CONFIGS).filter(id => !MODEL_CONFIGS[id].supported).join(', ')); console.log('Capabilities: GET /v1/model-capabilities'); } @@ -1849,23 +1829,14 @@ async function showStartupMenu() { } while (true) { printStatus(); - console.log('\n=== Меню ==='); + console.log('\n=== Menu ==='); console.log(`ForgetMeAI: ${FORGETMEAI_WATERMARK}`); -<<<<<<< Updated upstream - console.log('1 - Авторизоваться / обновить DeepSeek login'); - console.log('2 - Импортировать auth-файл / cookies'); - console.log('3 - Показать модели и статусы'); - console.log('4 - Запустить прокси (по умолчанию)'); - console.log('5 - Выход'); - let choice = await prompt('Ваш выбор (Enter = 4): '); -======= console.log('1 - Authorize / update DeepSeek login'); console.log('2 - Import auth file / cookies'); console.log('3 - Show models and statuses'); console.log('4 - Start proxy (default)'); console.log('5 - Exit'); let choice = await prompt('Your choice (Enter = 4): '); ->>>>>>> Stashed changes if (!choice) choice = '4'; if (choice === '1') { await runAuthScript(); @@ -1874,10 +1845,10 @@ async function showStartupMenu() { loadDeepSeekConfig({ fatal: false }); } else if (choice === '3') { console.log(JSON.stringify(ALL_MODEL_CAPABILITIES, null, 2)); - await prompt('\nНажмите Enter, чтобы вернуться в меню...'); + await prompt('\nPress Enter to return to the menu...'); } else if (choice === '4') { if (!hasAuthConfig()) { - console.log('Нужен deepseek-auth.json. Запустите пункт 1 или 2.'); + console.log('deepseek-auth.json required. Run option 1 or 2 first.'); continue; } return true;