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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 15 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ ForgetMeAI: https://t.me/forgetmeai
- [Diagnostics / doctor](#-diagnostics--doctor)
- [Session reuse и сброс чатов](#-session-reuse-и-сброс-чатов)
- [Multi-account pool](#-multi-account-pool)
- [Идеи для консольной авторизации](#-идеи-для-консольной-авторизации)
- [Консольная авторизация](#-консольная-авторизация)
- [Проверка работы](#-проверка-работы)
- [Примеры запросов](#-примеры-запросов)
- [Chat Completions](#chat-completions)
Expand Down Expand Up @@ -401,18 +401,23 @@ DEEPSEEK_ACCOUNT_COOLDOWN_MS=600000 npm start

---

## 🔑 Идеи для консольной авторизации
## 🔑 Консольная авторизация

Парольный flow из PR #3 можно делать, но безопаснее не хранить пароль и не делать это дефолтом. Нормальная реализация:
Если Chrome недоступен, авторизацию можно выполнить напрямую через HTTP API DeepSeek:

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`.
```bash
npm run auth -- --login-console
```

Команда запросит логин и пароль в терминале; пароль вводится скрыто. Для автоматизированного запуска можно использовать переменные окружения:

```bash
DEEPSEEK_LOGIN="email@example.com" DEEPSEEK_PASSWORD="your-password" npm run auth -- --login-console
```

После успешного входа скрипт проверяет web-сессию и сохраняет токен и cookies в `deepseek-auth.json`. При необходимости captcha или 2FA DeepSeek может отклонить HTTP-вход — в таком случае используйте браузерный режим `npm run auth -- --login`.

Минимальный безопасный MVP: console auth только интерактивный, без env-пароля. Допустимый automation-вариант: `DEEPSEEK_EMAIL=... npm run auth:console`, но пароль всё равно вводится hidden prompt.
> ⚠️ Не храните логин и пароль в общих скриптах, истории shell или CI-логах. Файл `deepseek-auth.json` содержит действующие credentials и не должен попадать в Git.

---

Expand Down
11 changes: 11 additions & 0 deletions ecosystem.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module.exports = {
apps: [{
name: 'deepseek-api',
script: 'server.js',
cwd: __dirname,
env: {
NON_INTERACTIVE: '1',
PORT: '9655',
},
}],
};
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"doctor": "node scripts/doctor.js",
"deepseek:auth": "node scripts/deepseek_chrome_auth.js",
"client": "node client.js",
"test": "node --check server.js && node --check lib/pow.js && node --check scripts/auth.js && node --check scripts/auth_import.js && node --check scripts/doctor.js && node --check scripts/deepseek_chrome_auth.js && node --check scripts/probe_deepseek_models.js && node --check client.js && node --check scripts/live_agentic_smoke_tests.mjs && node --test tests/unit.test.js",
"test": "node --check server.js && node --check lib/pow.js && node --check scripts/auth.js && node --check scripts/auth_import.js && node --check scripts/doctor.js && node --check scripts/deepseek_console_auth.js && node --check scripts/deepseek_chrome_auth.js && node --check scripts/probe_deepseek_models.js && node --check client.js && node --check scripts/live_agentic_smoke_tests.mjs && node --test tests/unit.test.js",
"test:live": "node scripts/live_agentic_smoke_tests.mjs"
},
"keywords": [
Expand Down
72 changes: 52 additions & 20 deletions scripts/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,43 +13,75 @@ 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); }));
}
function promptHidden(question) {
if (!process.stdin.isTTY) return prompt(question);
return new Promise(resolve => {
const stdin = process.stdin;
const stdout = process.stdout;
let value = '';
stdout.write(question);
const wasRaw = stdin.isRaw;
stdin.setRawMode(true);
stdin.resume();
stdin.setEncoding('utf8');
const cleanup = () => {
stdin.removeListener('data', onData);
stdin.setRawMode(wasRaw);
stdin.pause();
};
const onData = chunk => {
for (const c of String(chunk)) {
if (c === '\n' || c === '\r') { stdout.write('\n'); cleanup(); resolve(value); return; }
if (c === '\u0003') { cleanup(); process.exit(130); }
if (c === '\b' || c === '\x7f') { if (value.length) { value = value.slice(0, -1); stdout.write('\b \b'); } continue; }
value += c;
stdout.write('*');
}
};
stdin.on('data', onData);
});
}
function divider() { console.log('======================================================'); }
function watermark(prefix = 'ForgetMeAI') { return `${prefix}: ${WATERMARK}`; }
function loadAuth() {
try { return JSON.parse(fs.readFileSync(AUTH_PATH, 'utf8')); }
catch { return null; }
}
function loadAuth() { try { return JSON.parse(fs.readFileSync(AUTH_PATH, 'utf8')); } catch { return null; } }
function status() {
const auth = loadAuth();
console.log('\nDeepSeek аккаунт:');
if (!auth) {
console.log(' ❌ deepseek-auth.json не найден');
} else {
if (!auth) console.log(' ❌ deepseek-auth.json не найден');
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'}`);
console.log(` Chrome profile: ${fs.existsSync(PROFILE_DIR) ? PROFILE_DIR : 'не найден'}`);
}
}
function runDirectAuth() {
const script = path.join(__dirname, 'deepseek_chrome_auth.js');
return spawnSync(process.execPath, [script], { stdio: 'inherit', env: process.env }).status === 0;
return spawnSync(process.execPath, [path.join(__dirname, 'deepseek_chrome_auth.js')], { stdio: 'inherit', env: process.env }).status === 0;
}
function runImportAuth() {
const script = path.join(__dirname, 'auth_import.js');
return spawnSync(process.execPath, [script], { stdio: 'inherit', env: process.env }).status === 0;
return spawnSync(process.execPath, [path.join(__dirname, 'auth_import.js')], { stdio: 'inherit', env: process.env }).status === 0;
}
function removeLocalAuth() {
if (fs.existsSync(AUTH_PATH)) fs.rmSync(AUTH_PATH, { force: true });
console.log('Удалён deepseek-auth.json. Chrome profile оставлен, чтобы не разлогинивать браузер без нужды.');
}
async function runConsoleAuth() {
const login = (process.env.DEEPSEEK_LOGIN ?? '').trim() || (await prompt('DeepSeek логин (email/phone): ')).trim();
const password = (process.env.DEEPSEEK_PASSWORD ?? '').trim() || (await promptHidden('DeepSeek пароль: ')).trim();
if (!login || !password) { console.error('[auth] Нужны DEEPSEEK_LOGIN и DEEPSEEK_PASSWORD (логин и пароль).'); process.exitCode = 2; return; }
const result = spawnSync(process.execPath, [path.join(__dirname, 'deepseek_console_auth.js')], {
stdio: 'inherit', env: { ...process.env, DEEPSEEK_LOGIN: login, DEEPSEEK_PASSWORD: password },
});
process.exitCode = result.status === 0 ? 0 : 2;
}
function printHelp() {
divider();
console.log('FreeDeepseekAPI — управление DeepSeek Web login');
console.log(watermark());
divider();
console.log('Опции:');
console.log(' --login Открыть Chrome и обновить auth');
console.log(' --login-console Ввести логин/пароль в консоли и обновить auth');
console.log(' --import Импортировать готовый deepseek-auth.json / browser cookies');
console.log(' --status Показать статус auth');
console.log(' --remove Удалить локальный deepseek-auth.json');
Expand All @@ -59,28 +91,28 @@ function printHelp() {
}
async function menu() {
while (true) {
divider();
console.log(watermark());
status();
divider();
divider(); console.log(watermark()); status(); divider();
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';
console.log('4 - Авторизация в консоли (логин/пароль)');
console.log('5 - Удалить локальный auth файл');
console.log('6 - Выход');
const choice = (await prompt('Ваш выбор (Enter = 6): ')) || '6';
if (choice === '1') runDirectAuth();
else if (choice === '2') runImportAuth();
else if (choice === '3') { status(); await prompt('\nНажмите Enter, чтобы вернуться в меню...'); }
else if (choice === '4') removeLocalAuth();
else if (choice === '5') break;
else if (choice === '4') await runConsoleAuth();
else if (choice === '5') removeLocalAuth();
else if (choice === '6') break;
}
}
(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-console') || args.has('--console-login')) return void runConsoleAuth();
if (args.has('--import')) return void runImportAuth();
if (args.has('--status') || args.has('--list')) return status();
if (args.has('--remove')) return removeLocalAuth();
Expand Down
Loading