QwenAI / index.js
imseldrith's picture
Initial upload from Google Colab
9de864e verified
import express from 'express';
import bodyParser from 'body-parser';
import { fileURLToPath } from 'url';
import path from 'path';
import readline from 'readline';
import { initBrowser, shutdownBrowser } from './src/browser/browser.js';
import apiRoutes from './src/api/routes.js';
import { getAvailableModelsFromFile, getApiKeys } from './src/api/chat.js';
import { loadTokens } from './src/api/tokenManager.js';
import { addAccountInteractive } from './src/utils/accountSetup.js';
import { logHttpRequest, logInfo, logError, logWarn } from './src/logger/index.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const app = express();
const DEFAULT_PORT = 3264;
const port = Number.parseInt(process.env.PORT ?? DEFAULT_PORT, 10);
const host = process.env.HOST || '0.0.0.0';
if (Number.isNaN(port) || port <= 0 || port > 65535) {
throw new Error(`Некорректное значение переменной PORT: ${process.env.PORT}`);
}
const skipAccountMenu = toBoolean(process.env.SKIP_ACCOUNT_MENU) || toBoolean(process.env.NON_INTERACTIVE);
function prompt(question) {
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
return new Promise(res => rl.question(question, ans => { rl.close(); res(ans.trim()); }));
}
function toBoolean(value) {
if (typeof value !== 'string') return false;
return ['1', 'true', 'yes', 'on'].includes(value.trim().toLowerCase());
}
function ensureNonInteractiveTokens() {
const tokens = loadTokens();
if (!tokens.length) {
logError('Не найдено ни одного аккаунта. Запустите скрипт авторизации перед запуском сервера.');
process.exit(1);
}
const now = Date.now();
const validTokens = tokens.filter(t => (!t.resetAt || new Date(t.resetAt).getTime() <= now) && !t.invalid);
if (!validTokens.length) {
logError('Все аккаунты недоступны. Перезапустите авторизацию перед запуском сервера.');
process.exit(1);
}
logInfo(`Автоматический запуск: обнаружено ${tokens.length} аккаунтов, из них ${validTokens.length} активны.`);
}
// Middleware для логирования HTTP-запросов
app.use(logHttpRequest);
app.use(bodyParser.json({ limit: '150mb' }));
app.use(bodyParser.urlencoded({ limit: '150mb', extended: true }));
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization');
if (req.method === 'OPTIONS') {
return res.sendStatus(200);
}
next();
});
app.use('/api', apiRoutes);
// Обработчик 404
app.use((req, res) => {
logWarn(`404 Not Found: ${req.method} ${req.originalUrl}`);
res.status(404).json({ error: 'Эндпоинт не найден' });
});
// Обработчик ошибок
app.use((err, req, res, next) => {
logError('Внутренняя ошибка сервера', err);
res.status(500).json({ error: 'Внутренняя ошибка сервера' });
});
process.on('SIGINT', handleShutdown);
process.on('SIGTERM', handleShutdown);
process.on('SIGHUP', handleShutdown);
process.on('uncaughtException', async (error) => {
logError('Необработанное исключение', error);
await handleShutdown();
});
async function handleShutdown() {
logInfo('\nПолучен сигнал завершения. Закрываем браузер...');
await shutdownBrowser();
logInfo('Завершение работы.');
process.exit(0);
}
async function startServer() {
console.log(`
███████ ██████ ███████ ███████ ██████ ██ ██ ███████ ███ ██ █████ ██████ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██
█████ ██████ █████ █████ ██ ██ ██ █ ██ █████ ██ ██ ██ ███████ ██████ ██
██ ██ ██ ██ ██ ██ ▄▄ ██ ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ███████ ███████ ██████ ███ ███ ███████ ██ ████ ██ ██ ██ ██
▀▀
API-прокси для Qwen
`);
logInfo('Запуск сервера...');
if (!skipAccountMenu) {
// Меню управления аккаунтами перед запуском прокси
while (true) {
const tokens = loadTokens();
console.log('\nСписок аккаунтов:');
if (!tokens.length) {
console.log(' (пусто)');
} else {
tokens.forEach((token, i) => {
const now = Date.now();
const isInvalid = token.invalid === true;
const isWaiting = Boolean(token.resetAt && new Date(token.resetAt).getTime() > now);
const statusCode = isInvalid ? 0 : isWaiting ? 1 : 2;
const statusLabel = isInvalid ? '❌ Недействителен' : isWaiting ? '⏳ Ожидание сброса' : '✅ OK';
console.log(`${String(i + 1).padStart(2, ' ')} | ${token.id} | ${statusLabel} (${statusCode})`);
});
}
console.log('\n=== Меню ===');
console.log('1 - Добавить новый аккаунт');
console.log('2 - Перелогинить аккаунт с истекшим токеном');
console.log('3 - Запустить прокси (по умолчанию)');
console.log('4 - Удалить аккаунт');
let choice = await prompt('Ваш выбор (Enter = 3): ');
if (!choice) choice = '3';
if (choice === '1') {
await addAccountInteractive();
} else if (choice === '2') {
const { reloginAccountInteractive } = await import('./src/utils/accountSetup.js');
await reloginAccountInteractive();
} else if (choice === '3') {
const hasValidToken = tokens.some(token => {
if (token.invalid) return false;
if (!token.resetAt) return true;
return new Date(token.resetAt).getTime() <= Date.now();
});
if (!tokens.length || !hasValidToken) {
console.log('Нужен хотя бы один валидный аккаунт для запуска.');
continue;
}
break;
} else if (choice === '4') {
const { removeAccountInteractive } = await import('./src/utils/accountSetup.js');
await removeAccountInteractive();
}
}
} else {
ensureNonInteractiveTokens();
}
//=====================================================================================================
//const sim = await prompt('Смоделировать ошибку RateLimited для первого запроса? (y/N): ');
//if (sim.toLowerCase() === 'y') {
// global.simulateRateLimit = true;
// }
//=====================================================================================================
const browserInitialized = await initBrowser(false);
if (!browserInitialized) {
logError('Не удалось инициализировать браузер. Завершение работы.');
process.exit(1);
}
try {
app.listen(port, host, () => {
const displayHost = host === '0.0.0.0' ? 'localhost' : host;
logInfo(`Сервер запущен на ${host}:${port}`);
logInfo(`API доступен по адресу: http://${displayHost}:${port}/api`);
logInfo('Для проверки статуса авторизации: GET /api/status');
logInfo('Для отправки сообщения: POST /api/chat');
logInfo('Для получения списка моделей: GET /api/models');
logInfo('======================================================');
logInfo('API v2 - История чатов хранится на серверах Qwen');
logInfo('Создать новый чат: POST /api/chats');
logInfo('Отправить сообщение: POST /api/chat (с chatId и parentId)');
logInfo('======================================================');
logInfo('Доступно 19 моделей Qwen (через систему маппинга):');
logInfo('- Стандартные: qwen-max, qwen-plus, qwen-turbo и их latest-версии');
logInfo('- Coder: qwen3-coder-plus, qwen2.5-coder-*b-instruct (0.5b - 32b)');
logInfo('- Визуальные: qwen-vl-max, qwen-vl-plus и их latest-версии');
logInfo('- Qwen 3: qwen3, qwen3-max, qwen3-plus, qwen3-omni-flash');
logInfo('======================================================');
logInfo('Формат JSON запроса на чат:');
logInfo('{ "message": "текст сообщения", "model": "название модели (опционально)", "chatId": "ID чата (опционально)", "parentId": "ID родительского сообщения (опционально)" }');
logInfo('Пример первого запроса: { "message": "Привет, как дела?" }');
logInfo('Пример второго запроса: { "message": "А что ты умеешь?", "chatId": "полученный_id_чата", "parentId": "полученный_parentId" }');
logInfo('======================================================');
logInfo('Поддержка OpenAI совместимого API: POST /api/chat/completions');
logInfo('В ответе возвращаются chatId и parentId для продолжения диалога');
logInfo('======================================================');
getApiKeys();
getAvailableModelsFromFile();
});
} catch (err) {
if (err.code === 'EADDRINUSE') {
logError(`Порт ${port} уже используется. Возможно, сервер уже запущен.`);
logError('Завершите работу существующего сервера или используйте другой порт.');
await shutdownBrowser();
process.exit(1);
} else {
throw err;
}
}
}
startServer().catch(async error => {
logError('Ошибка при запуске сервера:', error);
await shutdownBrowser();
process.exit(1);
});