Ezmary commited on
Commit
0ded4c9
·
verified ·
1 Parent(s): 76bc6d8

Update server/index.js

Browse files
Files changed (1) hide show
  1. server/index.js +97 -188
server/index.js CHANGED
@@ -1,5 +1,4 @@
1
  // server/index.js
2
-
3
  const express = require('express');
4
  const path = require('node:path');
5
  const { WebSocketServer, WebSocket } = require('ws');
@@ -10,209 +9,119 @@ const app = express();
10
  const server = http.createServer(app);
11
  const wss = new WebSocketServer({ server });
12
 
13
- // --- START: منطق جدید و بهبود یافته برای مدیریت API Key ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
- // ۱. خواندن تمام کلیدها از یک Secret واحد (جدا شده با کاما)
16
  const apiKeysEnv = process.env.ALL_GEMINI_API_KEYS;
17
  const apiKeys = apiKeysEnv ? apiKeysEnv.split(',').map(key => key.trim()).filter(key => key) : [];
18
-
19
- const numKeys = apiKeys.length;
20
-
21
- if (numKeys === 0) {
22
- console.error(
23
- 'خطای حیاتی: هیچ Secret با نام ALL_GEMINI_API_KEYS یافت نشد یا خالی است!' +
24
- ' لطفاً کلیدهای خود را به صورت جدا شده با کاما در این Secret در تنظیمات Space خود اضافه کنید.'
25
- );
26
  process.exit(1);
27
- } else {
28
- console.log(`✅ تعداد ${numKeys} کلید API جیمینای با موفقیت بارگذاری شد.`);
29
  }
30
-
31
- // ۲. شمارنده سراسری برای توزیع بار بین کاربران (انتخاب کلید شروع)
32
  let currentKeyIndex = 0;
33
-
34
- function getStartingKeyIndex() {
35
- const startingIndex = currentKeyIndex % numKeys;
36
  currentKeyIndex++;
37
- return startingIndex;
38
- }
39
-
40
- // --- END: منطق جدید مدیریت API Key ---
41
-
42
-
43
- // این تابع رویدادهای لازم را به یک اتصال موفق جیمینای متصل می‌کند
44
  function attachGeminiEventHandlers(clientWs, geminiWs, apiKeyUsed) {
45
- // ارسال پیام از جیمینای به کلاینت
46
- geminiWs.on('message', (data) => {
47
- try {
48
- if (clientWs.readyState === WebSocket.OPEN) {
49
- // پیام‌ها به صورت باینری (Blob) برای کلاینت ارسال می‌شوند
50
- clientWs.send(data, { binary: true });
51
- }
52
- } catch (error) {
53
- console.error(`🔴 خطا در پردازش پیام جیمینای (کلید ...${apiKeyUsed.slice(-4)}):`, error);
54
- }
55
- });
56
-
57
- // رسیدگی به خطای اتصال جیمینای
58
- geminiWs.on('error', (error) => {
59
- console.error(`🔴 خطای WebSocket جیمینای (کلید ...${apiKeyUsed.slice(-4)}):`, error.message);
60
- if (clientWs.readyState === WebSocket.OPEN) clientWs.close();
61
- });
62
-
63
- // رسیدگی به بسته شدن اتصال جیمینای
64
- geminiWs.on('close', (code, reason) => {
65
- // لاگ بسته شدن اتصال فقط در صورتی که غیرعادی باشد
66
- if (code !== 1000) {
67
- console.log(`🟡 اتصال WebSocket جیمینای بسته شد (کلید ...${apiKeyUsed.slice(-4)}). کد: ${code}, دلیل: ${reason.toString()}`);
68
- }
69
- if (clientWs.readyState === WebSocket.OPEN) clientWs.close();
70
- });
71
  }
72
-
73
-
74
- // --- START: تابع هوشمند جدید برای اتصال با قابلیت Failover ---
75
-
76
- /**
77
- * تلاش برای اتصال به جیمینای با استفاده از لیست کلیدها.
78
- * در صورت شکست، به صورت بازگشتی کلید بعدی را امتحان می‌کند.
79
- * @param {WebSocket} clientWs - وب‌سوکت کلاینت کاربر.
80
- * @param {object} setupData - داده‌های setup ارسال شده توسط کلاینت.
81
- * @param {number} attemptIndex - اندیس کلیدی که باید امتحان شود.
82
- * @returns {Promise<WebSocket|null>} - در صورت موفقیت، وب‌سوکت جیمینای را برمی‌گرداند.
83
- */
84
  async function tryConnectToGemini(clientWs, setupData, startIndex, attemptCount = 0) {
85
- if (attemptCount >= numKeys) {
86
- console.error(`🔴 تمام ${numKeys} کلید API امتحان شدند و ناموفق بودند. قطع اتصال کلاینت.`);
87
- if (clientWs.readyState === WebSocket.OPEN) {
88
- clientWs.send(JSON.stringify({ error: "خطای داخلی سرور: هیچ سرویس‌دهنده‌ای در دسترس نیست." }));
89
- clientWs.close();
90
- }
91
- return null;
92
- }
93
-
94
- const keyIndexToTry = (startIndex + attemptCount) % numKeys;
95
- const apiKey = apiKeys[keyIndexToTry];
96
- // console.log(`⏳ تلاش برای اتصال با کلید اندیس ${keyIndexToTry} (کلید ...${apiKey.slice(-4)})`);
97
-
98
- return new Promise((resolve) => {
99
- const geminiWs = new WebSocket(
100
- `wss://generativelanguage.googleapis.com/ws/google.ai.generativelanguage.v1alpha.GenerativeService.BidiGenerateContent?key=${apiKey}`
101
- );
102
-
103
- const timeout = setTimeout(() => {
104
- geminiWs.removeAllListeners();
105
- geminiWs.close();
106
- console.warn(`🟡 تلاش برای اتصال با کلید ...${apiKey.slice(-4)} زمان‌بر شد. امتحان کلید بعدی...`);
107
- resolve(tryConnectToGemini(clientWs, setupData, startIndex, attemptCount + 1));
108
- }, 8000); // ۸ ثانیه مهلت برای اتصال
109
-
110
- geminiWs.on('open', () => {
111
- clearTimeout(timeout);
112
- console.log(`✅ اتصال به جیمینای با کلید اندیس ${keyIndexToTry} (کلید ...${apiKey.slice(-4)}) موفقیت‌آمیز بود.`);
113
-
114
- // ارسال پیام setup به محض برقراری اتصال
115
- try {
116
- geminiWs.send(JSON.stringify(setupData));
117
- } catch (sendError) {
118
- console.error(`🔴 خطا در ارسال پیام setup اولیه به جیمینای:`, sendError);
119
- clientWs.close();
120
- resolve(null); // اتصال ناموفق بود
121
- return;
122
- }
123
-
124
- // اتصال سایر event handler ها
125
- attachGeminiEventHandlers(clientWs, geminiWs, apiKey);
126
- resolve(geminiWs); // اتصال موفق را برگردان
127
- });
128
-
129
- geminiWs.on('error', (error) => {
130
- clearTimeout(timeout);
131
- geminiWs.removeAllListeners();
132
- console.warn(`🟡 اتصال با کلید اندیس ${keyIndexToTry} (کلید ...${apiKey.slice(-4)}) ناموفق بود. دلیل: ${error.message.split('Unexpected server response: ')[1] || 'خطای نامشخص'}. امتحان کلید بعدی...`);
133
- // به صورت بازگشتی کلید بعدی را امتحان کن
134
- resolve(tryConnectToGemini(clientWs, setupData, startIndex, attemptCount + 1));
135
- });
136
  });
 
137
  }
138
 
139
- // --- END: تابع هوشمند جدید ---
140
-
141
-
142
- // سرو کردن فایل‌های استاتیک از پوشه build ری‌اکت
143
  app.use(express.static(path.join(__dirname, '../build')));
144
 
145
- wss.on('connection', (ws) => {
146
- // به هر کلاینت یک کلید شروع اختصاص داده می‌شود تا بار توزیع شود
147
- const startingKeyIndex = getStartingKeyIndex();
148
- console.log(`🔌 یک کلاینت جدید متصل شد. کلید شروع اختصاص یافته: اندیس ${startingKeyIndex} (کلید ...${apiKeys[startingKeyIndex].slice(-4)})`);
149
-
150
- let geminiWs = null; // اتصال جیمینای برای این کلاینت
151
-
152
- ws.on('message', async (message) => {
153
- try {
154
- const data = JSON.parse(message.toString());
155
-
156
- // اولین پیام باید `setup` باشد
157
- if (data.setup) {
158
- if (geminiWs) {
159
- // console.warn("کلاینت دوباره پیام setup ارسال کرد. نادیده گرفته شد.");
160
- return;
161
- }
162
-
163
- // شروع فرآیند اتصال با قابلیت Failover
164
- geminiWs = await tryConnectToGemini(ws, data, startingKeyIndex);
165
-
166
- if (!geminiWs) {
167
- console.error("🔴 اتصال به سرویس جیمینای برای این کلاینت پس از امتحان همه کلیدها ناموفق بود.");
168
- }
169
- return;
170
- }
171
-
172
- // ارسال پیام‌های بعدی به جیمینای
173
- if (geminiWs && geminiWs.readyState === WebSocket.OPEN) {
174
- geminiWs.send(JSON.stringify(data));
175
- } else if (!geminiWs) {
176
- console.warn("🟡 پیام از کلاینت دریافت شد اما اتصال جیمینای هنوز برقرار نشده یا ناموفق بوده است.");
177
- // می توان یک پیام خطا به کلاینت فرستاد
178
- }
179
-
180
- } catch (error) {
181
- console.error('🔴 خطا در پردازش پیام کلاینت (احتمالا JSON نامعتبر):', error);
182
- }
183
- });
184
-
185
- ws.on('close', () => {
186
- console.log(`🔌 کلاینت با کلید شروع اندیس ${startingKeyIndex} قطع شد.`);
187
- if (geminiWs && geminiWs.readyState !== WebSocket.CLOSED) {
188
- geminiWs.close();
189
- }
190
- });
191
-
192
- ws.on('error', (error) => {
193
- console.error(`🔴 خطای WebSocket کلاینت (کلید شروع اندیس ${startingKeyIndex}):`, error);
194
- if (geminiWs && geminiWs.readyState !== WebSocket.CLOSED) {
195
- geminiWs.close();
196
- }
197
- });
198
  });
199
 
200
- // رسیدگی به درخواست‌های باقیمانده با برگرداندن اپ React
201
- app.get('*', (req, res) => {
202
- const indexPath = path.join(__dirname, '../build', 'index.html');
203
- res.sendFile(indexPath, (err) => {
204
- if (err) {
205
- if (!res.headersSent) {
206
- res.status(500).send('خطا در بارگذاری برنامه');
207
- }
208
- }
209
- });
210
- });
211
 
212
  const PORT = process.env.PORT || 3001;
213
- server.listen(PORT, () => {
214
- console.log(`🚀 سرور در حال اجرا روی پورت ${PORT}`);
215
- if (numKeys > 0) {
216
- console.log(`🔑 برای استفاده، Secret با نام 'ALL_GEMINI_API_KEYS' را با کلید(های) خود تنظیم کنید.`);
217
- }
218
- });
 
1
  // server/index.js
 
2
  const express = require('express');
3
  const path = require('node:path');
4
  const { WebSocketServer, WebSocket } = require('ws');
 
9
  const server = http.createServer(app);
10
  const wss = new WebSocketServer({ server });
11
 
12
+ // بخش خواندن دستورالعمل‌های شخصیت از Secrets
13
+ const instructionSecretNames = {
14
+ default: 'PERSONALITY_DEFAULT',
15
+ teacher: 'PERSONALITY_TEACHER',
16
+ poetic: 'PERSONALITY_POETIC',
17
+ funny: 'PERSONALITY_FUNNY',
18
+ };
19
+ const personalityInstructions = {};
20
+ console.log('در حال خواندن دستورالعمل‌های شخصیت از Secrets...');
21
+ Object.keys(instructionSecretNames).forEach(key => {
22
+ const secretName = instructionSecretNames[key];
23
+ const instruction = process.env[secretName];
24
+ if (instruction) {
25
+ personalityInstructions[key] = instruction;
26
+ console.log(`- دستورالعمل '${key}' با موفقیت خوانده شد.`);
27
+ } else {
28
+ personalityInstructions[key] = `دستورالعمل '${key}' از سرور بارگذاری نشد.`;
29
+ console.warn(`** هشدار: Secret با نام '${secretName}' یافت نشد! **`);
30
+ }
31
+ });
32
 
33
+ // بخش مدیریت کلیدهای API (کد هوشمند شما بدون تغییر)
34
  const apiKeysEnv = process.env.ALL_GEMINI_API_KEYS;
35
  const apiKeys = apiKeysEnv ? apiKeysEnv.split(',').map(key => key.trim()).filter(key => key) : [];
36
+ if (apiKeys.length === 0) {
37
+ console.error('🔴 خطای حیاتی: Secret با نام ALL_GEMINI_API_KEYS یافت نشد!');
 
 
 
 
 
 
38
  process.exit(1);
 
 
39
  }
40
+ console.log(`✅ تعداد ${apiKeys.length} کلید API بارگذاری شد.`);
 
41
  let currentKeyIndex = 0;
42
+ const getStartingKeyIndex = () => {
43
+ const index = currentKeyIndex % apiKeys.length;
 
44
  currentKeyIndex++;
45
+ return index;
46
+ };
47
+ // توابع attachGeminiEventHandlers و tryConnectToGemini شما اینجا بدون تغییر قرار می‌گیرند.
48
+ // ... (برای کوتاهی اینجا حذف شده، اما شما باید آنها را نگه دارید) ...
 
 
 
49
  function attachGeminiEventHandlers(clientWs, geminiWs, apiKeyUsed) {
50
+ geminiWs.on('message', data => clientWs.readyState === WebSocket.OPEN && clientWs.send(data, { binary: true }));
51
+ geminiWs.on('error', error => {
52
+ console.error(`🔴 خطای WebSocket جیمینای (کلید ...${apiKeyUsed.slice(-4)}):`, error.message);
53
+ clientWs.readyState === WebSocket.OPEN && clientWs.close();
54
+ });
55
+ geminiWs.on('close', code => {
56
+ if (code !== 1000) console.log(`🟡 اتصال جیمینای بسته شد (کلید ...${apiKeyUsed.slice(-4)}). ��د: ${code}`);
57
+ clientWs.readyState === WebSocket.OPEN && clientWs.close();
58
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  }
 
 
 
 
 
 
 
 
 
 
 
 
60
  async function tryConnectToGemini(clientWs, setupData, startIndex, attemptCount = 0) {
61
+ if (attemptCount >= apiKeys.length) {
62
+ console.error(`🔴 تمام ${apiKeys.length} کلید API ناموفق بودند.`);
63
+ if (clientWs.readyState === WebSocket.OPEN) clientWs.send(JSON.stringify({ error: "خطای داخلی سرور." })) && clientWs.close();
64
+ return null;
65
+ }
66
+ const keyIndexToTry = (startIndex + attemptCount) % apiKeys.length;
67
+ const apiKey = apiKeys[keyIndexToTry];
68
+ return new Promise(resolve => {
69
+ const geminiWs = new WebSocket(`wss://generativelanguage.googleapis.com/ws/google.ai.generativelanguage.v1alpha.GenerativeService.BidiGenerateContent?key=${apiKey}`);
70
+ const timeout = setTimeout(() => {
71
+ geminiWs.removeAllListeners() && geminiWs.close();
72
+ console.warn(`🟡 اتصال با کلید ...${apiKey.slice(-4)} زمان‌بر شد. امتحان کلید بعدی...`);
73
+ resolve(tryConnectToGemini(clientWs, setupData, startIndex, attemptCount + 1));
74
+ }, 8000);
75
+ geminiWs.on('open', () => {
76
+ clearTimeout(timeout);
77
+ console.log(`✅ اتصال با کلید اندیس ${keyIndexToTry} موفق بود.`);
78
+ try {
79
+ geminiWs.send(JSON.stringify(setupData));
80
+ } catch (e) {
81
+ clientWs.close();
82
+ resolve(null);
83
+ return;
84
+ }
85
+ attachGeminiEventHandlers(clientWs, geminiWs, apiKey);
86
+ resolve(geminiWs);
87
+ });
88
+ geminiWs.on('error', () => {
89
+ clearTimeout(timeout);
90
+ geminiWs.removeAllListeners();
91
+ console.warn(`🟡 اتصال با کلید ...${apiKey.slice(-4)} ناموفق بود. امتحان کلید بعدی...`);
92
+ resolve(tryConnectToGemini(clientWs, setupData, startIndex, attemptCount + 1));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
  });
94
+ });
95
  }
96
 
97
+ // سرو کردن فایل‌های استاتیک
 
 
 
98
  app.use(express.static(path.join(__dirname, '../build')));
99
 
100
+ // API Endpoint جدید برای ارسال دستورالعمل‌ها
101
+ app.get('/api/instructions', (req, res) => res.json(personalityInstructions));
102
+
103
+ // مدیریت اتصال WebSocket کلاینت (بدون تغییر)
104
+ wss.on('connection', ws => {
105
+ const startingKeyIndex = getStartingKeyIndex();
106
+ console.log(`🔌 کلاینت جدید متصل شد. کلید شروع: اندیس ${startingKeyIndex}`);
107
+ let geminiWs = null;
108
+ ws.on('message', async message => {
109
+ try {
110
+ const data = JSON.parse(message.toString());
111
+ if (data.setup) {
112
+ if (geminiWs) return;
113
+ geminiWs = await tryConnectToGemini(ws, data, startingKeyIndex);
114
+ } else if (geminiWs?.readyState === WebSocket.OPEN) {
115
+ geminiWs.send(JSON.stringify(data));
116
+ }
117
+ } catch (e) { /* خطا */ }
118
+ });
119
+ ws.on('close', () => console.log(`🔌 کلاینت با کلید شروع اندیس ${startingKeyIndex} قطع شد.`) && geminiWs?.close());
120
+ ws.on('error', error => console.error(`🔴 خطای WebSocket کلاینت:`, error) && geminiWs?.close());
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
  });
122
 
123
+ // رسیدگی به سایر درخواست‌ها
124
+ app.get('*', (req, res) => res.sendFile(path.join(__dirname, '../build', 'index.html')));
 
 
 
 
 
 
 
 
 
125
 
126
  const PORT = process.env.PORT || 3001;
127
+ server.listen(PORT, () => console.log(`🚀 سرور در حال اجرا روی پورت ${PORT}`));