Spaces:
Running
Running
File size: 9,442 Bytes
5058361 7f2a14a 0fd4af6 7f2a14a 5058361 f0896a3 7f2a14a f0896a3 5058361 f0896a3 0ded4c9 9e404a1 0ded4c9 f0896a3 0ded4c9 f0896a3 0ded4c9 9e404a1 f0896a3 9e404a1 0ded4c9 3ffbc23 5058361 0fd4af6 5058361 0fd4af6 f0896a3 0ded4c9 5058361 9e404a1 3ffbc23 5058361 f0896a3 5058361 f0896a3 5058361 f0896a3 5058361 f0896a3 5058361 f0896a3 0fd4af6 f0896a3 9e404a1 f0896a3 5058361 f0896a3 9e404a1 f0896a3 5058361 0fd4af6 f0896a3 5058361 f0896a3 0fd4af6 5058361 f0896a3 5058361 9e404a1 f0896a3 9e404a1 f0896a3 5058361 9e404a1 5058361 f0896a3 5058361 9e404a1 5058361 9e404a1 f0896a3 9e404a1 5058361 f0896a3 5058361 f0896a3 9e404a1 f0896a3 9e404a1 f0896a3 9e404a1 0fd4af6 f0896a3 9e404a1 5058361 9e404a1 0fd4af6 3ffbc23 f0896a3 affece8 335046c f0896a3 5058361 f0896a3 5058361 affece8 5058361 f0896a3 9e404a1 f0896a3 9e404a1 f0896a3 5058361 f0896a3 5058361 f0896a3 9e404a1 f0896a3 5058361 f0896a3 5058361 9e404a1 7f2a14a f0896a3 0ded4c9 7f2a14a 0fd4af6 5058361 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 |
// server/index.js (نسخه بهینه شده: انتخاب تصادفی کلیدها + پایداری بالا)
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 // 16KB
}, app);
const wss = new WebSocketServer({
server,
clientTracking: true, // برای مدیریت هارتبیت و بستن اتصالات مرده
perMessageDeflate: false // غیرفعال کردن فشردهسازی برای کاهش بار CPU و حافظه
});
// --- بخش مدیریت تنظیمات شخصیتها ---
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}' یافت نشد.`;
}
});
// --- بخش مدیریت کلیدهای API (Random Selection) ---
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 آمادهسازی شد. (حالت انتخاب تصادفی)`);
// --- مکانیزم Heartbeat برای جلوگیری از قطعی و هنگ کردن سرور ---
// این تابع هر ۳۰ ثانیه اتصالات مرده را پاک میکند تا حافظه آزاد شود
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) {}
});
}
/**
* تلاش برای اتصال به جمینای با انتخاب تصادفی و امتحان مجدد
* @param {WebSocket} clientWs سوکت کاربر
* @param {Object} setupData دادههای اولیه (کانفیگ)
* @param {Number} startIndex ایندکس شروع (که به صورت تصادفی انتخاب شده)
* @param {Number} attemptCount شمارنده تلاشها
*/
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;
}
// منطق انتخاب کلید:
// بار اول: ایندکس کاملا تصادفی (startIndex)
// دفعات بعد (در صورت خطا): کلید بعدی در لیست به صورت چرخشی
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')));
// API Endpoint دستورالعملها
app.get('/api/instructions', (req, res) => {
res.setHeader('Cache-Control', 'no-store');
res.json(personalityInstructions);
});
// --- مدیریت WebSocket کلاینت ---
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;
}
});
});
// ارسال تمام درخواستهای دیگر به React
app.get('*', (req, res) => res.sendFile(path.join(__dirname, '../build', 'index.html')));
const PORT = process.env.PORT || 3001;
server.listen(PORT, () => console.log(`🚀 سرور (حالت تصادفی) روی پورت ${PORT} اجرا شد.`)); |