|
|
|
|
|
const express = require('express'); |
|
|
const path = require('node:path'); |
|
|
const { WebSocketServer, WebSocket } = require('ws'); |
|
|
const http = require('node:http'); |
|
|
require('dotenv').config(); |
|
|
|
|
|
const app = express(); |
|
|
|
|
|
|
|
|
const server = http.createServer({ |
|
|
maxHeaderSize: 16384 |
|
|
}, app); |
|
|
|
|
|
const wss = new WebSocketServer({ |
|
|
server, |
|
|
clientTracking: true, |
|
|
perMessageDeflate: false |
|
|
}); |
|
|
|
|
|
|
|
|
const instructionSecretNames = { |
|
|
default: 'PERSONALITY_DEFAULT', |
|
|
teacher: 'PERSONALITY_TEACHER', |
|
|
poetic: 'PERSONALITY_POETIC', |
|
|
funny: 'PERSONALITY_FUNNY', |
|
|
}; |
|
|
|
|
|
const personalityInstructions = {}; |
|
|
console.log('🔄 در حال خواندن دستورالعملهای شخصیت از Secrets...'); |
|
|
Object.keys(instructionSecretNames).forEach(key => { |
|
|
const secretName = instructionSecretNames[key]; |
|
|
const instruction = process.env[secretName]; |
|
|
if (instruction) { |
|
|
personalityInstructions[key] = instruction; |
|
|
} else { |
|
|
personalityInstructions[key] = `دستورالعمل '${key}' یافت نشد.`; |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
const apiKeysEnv = process.env.ALL_GEMINI_API_KEYS; |
|
|
|
|
|
const apiKeys = apiKeysEnv ? apiKeysEnv.split(',').map(key => key.trim()).filter(key => key) : []; |
|
|
|
|
|
if (apiKeys.length === 0) { |
|
|
console.error('🔴 خطای حیاتی: هیچ کلید API یافت نشد! لطفا متغیر ALL_GEMINI_API_KEYS را تنظیم کنید.'); |
|
|
process.exit(1); |
|
|
} |
|
|
console.log(`🚀 سرور با ${apiKeys.length} کلید API آمادهسازی شد. (حالت انتخاب تصادفی)`); |
|
|
|
|
|
|
|
|
|
|
|
function heartbeat() { |
|
|
this.isAlive = true; |
|
|
} |
|
|
|
|
|
const interval = setInterval(function ping() { |
|
|
wss.clients.forEach(function each(ws) { |
|
|
if (ws.isAlive === false) { |
|
|
|
|
|
return ws.terminate(); |
|
|
} |
|
|
|
|
|
ws.isAlive = false; |
|
|
ws.ping(); |
|
|
}); |
|
|
}, 30000); |
|
|
|
|
|
wss.on('close', function close() { |
|
|
clearInterval(interval); |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function attachGeminiEventHandlers(clientWs, geminiWs, apiKeyUsed) { |
|
|
|
|
|
geminiWs.on('message', (data) => { |
|
|
if (clientWs.readyState === WebSocket.OPEN) { |
|
|
clientWs.send(data, { binary: true }); |
|
|
} |
|
|
}); |
|
|
|
|
|
geminiWs.on('error', (error) => { |
|
|
console.error(`🔴 خطای جمینای (کلید ...${apiKeyUsed.slice(-4)}):`, error.message); |
|
|
|
|
|
|
|
|
if (clientWs.readyState === WebSocket.OPEN) clientWs.close(); |
|
|
}); |
|
|
|
|
|
geminiWs.on('close', (code) => { |
|
|
if (clientWs.readyState === WebSocket.OPEN) clientWs.close(); |
|
|
}); |
|
|
|
|
|
geminiWs.on('ping', () => { |
|
|
try { geminiWs.pong(); } catch (e) {} |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function tryConnectToGemini(clientWs, setupData, startIndex, attemptCount = 0) { |
|
|
|
|
|
if (clientWs.readyState !== WebSocket.OPEN) return null; |
|
|
|
|
|
|
|
|
if (attemptCount >= apiKeys.length) { |
|
|
console.error(`⛔ تمام ${apiKeys.length} کلید API با شکست مواجه شدند.`); |
|
|
if (clientWs.readyState === WebSocket.OPEN) { |
|
|
clientWs.send(JSON.stringify({ error: "سرویس موقتاً در دسترس نیست (ظرفیت تکمیل)." })); |
|
|
clientWs.close(); |
|
|
} |
|
|
return null; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const keyIndexToTry = (startIndex + attemptCount) % apiKeys.length; |
|
|
const apiKey = apiKeys[keyIndexToTry]; |
|
|
|
|
|
const url = `wss://generativelanguage.googleapis.com/ws/google.ai.generativelanguage.v1alpha.GenerativeService.BidiGenerateContent?key=${apiKey}`; |
|
|
|
|
|
return new Promise((resolve) => { |
|
|
const geminiWs = new WebSocket(url); |
|
|
|
|
|
|
|
|
const timeout = setTimeout(() => { |
|
|
console.warn(`⏳ تایماوت اتصال (کلید ...${apiKey.slice(-4)}). رفتن به کلید بعدی...`); |
|
|
geminiWs.terminate(); |
|
|
resolve(tryConnectToGemini(clientWs, setupData, startIndex, attemptCount + 1)); |
|
|
}, 8000); |
|
|
|
|
|
geminiWs.on('open', () => { |
|
|
clearTimeout(timeout); |
|
|
|
|
|
if (clientWs.readyState !== WebSocket.OPEN) { |
|
|
geminiWs.close(); |
|
|
return resolve(null); |
|
|
} |
|
|
|
|
|
console.log(`🔗 اتصال موفق با کلید شماره ${keyIndexToTry} (تلاش ${attemptCount + 1})`); |
|
|
|
|
|
try { |
|
|
geminiWs.send(JSON.stringify(setupData)); |
|
|
attachGeminiEventHandlers(clientWs, geminiWs, apiKey); |
|
|
resolve(geminiWs); |
|
|
} catch (e) { |
|
|
console.error("خطا در ارسال تنظیمات اولیه:", e); |
|
|
geminiWs.close(); |
|
|
resolve(tryConnectToGemini(clientWs, setupData, startIndex, attemptCount + 1)); |
|
|
} |
|
|
}); |
|
|
|
|
|
geminiWs.on('error', (err) => { |
|
|
clearTimeout(timeout); |
|
|
console.warn(`⚠️ خطای اتصال (کلید ...${apiKey.slice(-4)}): ${err.message || 'Unknown'}. تلاش با کلید بعدی...`); |
|
|
|
|
|
resolve(tryConnectToGemini(clientWs, setupData, startIndex, attemptCount + 1)); |
|
|
}); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
app.use(express.static(path.join(__dirname, '../build'))); |
|
|
|
|
|
|
|
|
app.get('/api/instructions', (req, res) => { |
|
|
res.setHeader('Cache-Control', 'no-store'); |
|
|
res.json(personalityInstructions); |
|
|
}); |
|
|
|
|
|
|
|
|
wss.on('connection', (ws, req) => { |
|
|
ws.isAlive = true; |
|
|
ws.on('pong', heartbeat); |
|
|
|
|
|
|
|
|
|
|
|
const randomStartIndex = Math.floor(Math.random() * apiKeys.length); |
|
|
|
|
|
let geminiWs = null; |
|
|
let isConnecting = false; |
|
|
|
|
|
ws.on('message', async (message) => { |
|
|
try { |
|
|
if (!Buffer.isBuffer(message) || message[0] === 123) { |
|
|
const msgStr = message.toString(); |
|
|
if (msgStr.startsWith('{')) { |
|
|
const data = JSON.parse(msgStr); |
|
|
if (data.setup) { |
|
|
if (isConnecting || geminiWs) return; |
|
|
isConnecting = true; |
|
|
|
|
|
geminiWs = await tryConnectToGemini(ws, data, randomStartIndex); |
|
|
isConnecting = false; |
|
|
return; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
if (geminiWs && geminiWs.readyState === WebSocket.OPEN) { |
|
|
geminiWs.send(message); |
|
|
} |
|
|
} catch (e) { |
|
|
console.error("خطا در پردازش پیام کلاینت:", e.message); |
|
|
} |
|
|
}); |
|
|
|
|
|
ws.on('close', () => { |
|
|
if (geminiWs) { |
|
|
|
|
|
try { |
|
|
geminiWs.terminate(); |
|
|
} catch(e) {} |
|
|
geminiWs = null; |
|
|
} |
|
|
}); |
|
|
|
|
|
ws.on('error', (error) => { |
|
|
if (geminiWs) { |
|
|
try { geminiWs.terminate(); } catch(e) {} |
|
|
geminiWs = null; |
|
|
} |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
app.get('*', (req, res) => res.sendFile(path.join(__dirname, '../build', 'index.html'))); |
|
|
|
|
|
const PORT = process.env.PORT || 3001; |
|
|
server.listen(PORT, () => console.log(`🚀 سرور (حالت تصادفی) روی پورت ${PORT} اجرا شد.`)); |