beast-agent 1.9.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +130 -130
  3. package/bin/beast-agent.js +137 -137
  4. package/package.json +1 -1
  5. package/scripts/fix-electron.js +138 -138
  6. package/scripts/release.js +137 -131
  7. package/scripts/swap-electron.js +40 -40
  8. package/src/agent/agentdefs.js +122 -122
  9. package/src/agent/bots.js +580 -580
  10. package/src/agent/bus.js +389 -389
  11. package/src/agent/computeruse.js +200 -200
  12. package/src/agent/config.js +284 -284
  13. package/src/agent/discord.js +332 -332
  14. package/src/agent/engine.js +106 -14
  15. package/src/agent/kb.js +123 -123
  16. package/src/agent/llm.js +610 -430
  17. package/src/agent/logger.js +90 -90
  18. package/src/agent/mcp.js +427 -427
  19. package/src/agent/mem0.js +605 -605
  20. package/src/agent/memory.js +427 -427
  21. package/src/agent/mqueue.js +124 -124
  22. package/src/agent/pdf.js +20 -20
  23. package/src/agent/research.js +133 -133
  24. package/src/agent/scripts/news.py +113 -113
  25. package/src/agent/scripts/stealthsearch.py +30 -30
  26. package/src/agent/scripts/websearch.py +225 -225
  27. package/src/agent/searxng.js +325 -325
  28. package/src/agent/seeds/brainstorming/SKILL.md +90 -90
  29. package/src/agent/seeds/dispatching-parallel-agents/SKILL.md +120 -120
  30. package/src/agent/seeds/executing-plans/SKILL.md +60 -60
  31. package/src/agent/seeds/subagent-driven-development/SKILL.md +167 -167
  32. package/src/agent/seeds/systematic-debugging/SKILL.md +131 -131
  33. package/src/agent/seeds/test-driven-development/SKILL.md +152 -152
  34. package/src/agent/seeds/verification-before-completion/SKILL.md +63 -63
  35. package/src/agent/seeds/writing-plans/SKILL.md +162 -162
  36. package/src/agent/seeds/writing-skills/SKILL.md +229 -229
  37. package/src/agent/skills.js +652 -652
  38. package/src/agent/store.js +378 -378
  39. package/src/agent/telegram.js +155 -155
  40. package/src/agent/tokens.js +39 -39
  41. package/src/agent/usage.js +125 -125
  42. package/src/agent/watchers.js +312 -312
  43. package/src/agent/watext.js +80 -80
  44. package/src/agent/whatsapp.js +555 -555
  45. package/src/cron.js +255 -255
  46. package/src/main.js +348 -6
  47. package/src/preload.js +9 -0
  48. package/src/renderer/browserPreload.js +73 -73
  49. package/src/renderer/i18n.js +4 -0
  50. package/src/renderer/index.html +448 -409
  51. package/src/renderer/renderer.js +824 -41
  52. package/src/renderer/style.css +264 -5
@@ -1,332 +1,332 @@
1
- 'use strict';
2
-
3
- /* DISCORD KÖPRÜSÜ — Telegram köprüsünün Discord hali.
4
- Bağımlılık: yalnızca `ws` (paket zaten kurulu — baileys ile geliyor).
5
- - Gateway WebSocket: Identify → READY → MESSAGE_CREATE dispatch
6
- - REST (api/v10): mesaj gönderme (2000 karakter sınırında bölerek)
7
- - Sunucu (guild) mesajlarında yalnız @mention'a cevap verilir (spam koruması);
8
- DM'de her izinli kullanıcıya cevap verilir.
9
- - Bağlantı koparsa backoff ile yeniden bağlanır (yeniden Identify;
10
- çevrimdışıyken gelen mesajlar backfill edilmez — Telegram long-poll'un aksine).
11
- ÖNEMLİ: Discord Developer Portal'da bot için "MESSAGE CONTENT INTENT"
12
- açılmalıdır — yoksa mesaj içerikleri boş gelir. */
13
-
14
- const WebSocket = require('ws');
15
- const https = require('https');
16
-
17
- const API_BASE = 'https://discord.com/api/v10';
18
- const GATEWAY_URL = 'wss://gateway.discord.gg/?v=10&encoding=json';
19
- const SEND_CHUNK = 1900; // Discord mesaj sınırı 2000 — güvenli pay
20
- /* GUILDS(1) | GUILD_MESSAGES(512) | DIRECT_MESSAGES(4096) | MESSAGE_CONTENT(32768) */
21
- const INTENTS = 1 | 512 | 4096 | 32768;
22
-
23
- /* AĞ ARA YAZILIMI (antivirüs SSL taraması / proxy / ağ filtresi) Discord
24
- trafiğine araya girip SELF-SIGNED sertifika sunarsa Node varsayılan olarak
25
- bağlantıyı reddeder ("self signed certificate") ve bot hiç bağlanamaz.
26
- Çözüm iki katman:
27
- 1) REST istekleri ÖNCE Electron/Chromium ağ yığınından (net.fetch) geçer —
28
- sistem CA + sistem proxy otomatik tanınır (tarayıcının çalıştığı yol).
29
- 2) Zorlanırsa Node https'e düşülür; self-signed tespit edilirse bu köprü
30
- için TLS doğrulaması esnetilir — YALNIZ Discord bağlantıları etkilenir. */
31
- let tlsRelaxed = false;
32
- const CERT_ERR_RE = /self[ -]?signed|unable to verify the first certificate|depth zero|err_tls|certificate has expired|cert_has_expired|unable_to_get_issuer|self signed certificate/i;
33
-
34
- function isCertError(e) {
35
- return CERT_ERR_RE.test(String((e && (e.message || e.code)) || e || ''));
36
- }
37
-
38
- /* Electron main process'te Chromium ağ yığını (sistem CA + proxy); düz Node
39
- (testler) için require('electron') path string döndürür → net tanımsız kalır */
40
- let _electronNet = null;
41
- try {
42
- const el = require('electron');
43
- if (el && el.net && typeof el.net.fetch === 'function') _electronNet = el.net;
44
- } catch {}
45
-
46
- function bodySnippet(data) {
47
- const s = String(data || '').replace(/\s+/g, ' ').trim();
48
- return s ? ` — gövde: ${s.slice(0, 140)}` : '';
49
- }
50
-
51
- /* Ağ engel sayfası tespiti: Discord API her zaman JSON döner — text/html veya
52
- erişim-engelle sayfası gelirse ağ seviyesinde blok var demektir (Türkiye'de
53
- Discord erişimi zaman zaman engellenir). Kullanıcıya net VPN yönlendirmesi. */
54
- const VPN_HINT = 'Discord bu ağdan ERİŞİME ENGELLİ görünüyor — VPN açıp tekrar dene (engel sayfası döndü)';
55
-
56
- function looksBlocked(ctype, data) {
57
- const ct = String(ctype || '');
58
- const body = String(data || '').slice(0, 4000);
59
- return (
60
- ct.includes('text/html') ||
61
- /erisime[_ ]?engelle|erişime engelle|blocked|block_page/i.test(body)
62
- );
63
- }
64
-
65
- class DiscordBridge {
66
- constructor({ token, emit, onIncoming }) {
67
- this.token = String(token || '').trim();
68
- this.emit = emit || (() => {});
69
- this.onIncoming = onIncoming || null;
70
- this.connected = false;
71
- this.stopping = false;
72
- this.status = 'disconnected';
73
- this.user = null; // { id, username, ... }
74
- this._ws = null;
75
- this._hbTimer = null;
76
- this._seq = null;
77
- this._backoff = 2000;
78
- }
79
-
80
- _setStatus(status, user) {
81
- this.status = status;
82
- this.connected = status === 'connected';
83
- this.emit({ type: 'status', status, user: user || null });
84
- }
85
-
86
- /* REST çağrısı — JSON (token: "Bot <token>").
87
- Yol 1: Chromium (net.fetch — sistem CA/proxy). Yol 2: Node https (+esnek TLS). */
88
- api(method, path, body, attempt = 0) {
89
- return this._api(method, path, body).catch((e) => {
90
- if (isCertError(e) && !tlsRelaxed) {
91
- tlsRelaxed = true;
92
- this.emit({
93
- type: 'warn',
94
- text: 'ağ ara yazılımı (antivirüs/proxy) self-signed sertifikası tespit edildi — Discord bağlantısı esnek TLS ile devam ediyor',
95
- });
96
- if (attempt < 1) return this.api(method, path, body, attempt + 1);
97
- }
98
- throw e;
99
- });
100
- }
101
-
102
- async _api(method, path, body) {
103
- const payload = body ? JSON.stringify(body) : null;
104
- const headers = { 'Content-Type': 'application/json', Authorization: 'Bot ' + this.token };
105
- const url = API_BASE + path;
106
-
107
- /* YOL 1 — Chromium ağ yığını (varsa): sistem sertifikaları + sistem proxy */
108
- if (_electronNet) {
109
- try {
110
- const res = await _electronNet.fetch(url, {
111
- method,
112
- headers,
113
- ...(payload ? { body: payload } : {}),
114
- });
115
- const data = await res.text();
116
- let j = null;
117
- try { j = data ? JSON.parse(data) : null; } catch {}
118
- if (res.ok && j !== null) return j;
119
- if (j === null && looksBlocked(res.headers.get('content-type'), data)) {
120
- throw new Error(VPN_HINT + bodySnippet(data));
121
- }
122
- throw new Error(
123
- `discord ${path}: HTTP ${res.status}${j && j.message ? ' ' + j.message : ''}${bodySnippet(data)}`
124
- );
125
- } catch (e) {
126
- /* kendi formatladığımız API hatasıysa yukarı taşı; transport hatasıysa
127
- Node https yoluna düş (orada esnek TLS şansı var) */
128
- if (/^discord |Discord bu ağ/.test(String((e && e.message) || ''))) throw e;
129
- }
130
- }
131
-
132
- /* YOL 2 — Node https (+ gerektiğinde esnek TLS) */
133
- return new Promise((resolve, reject) => {
134
- const req = https.request(
135
- url,
136
- {
137
- method,
138
- headers: {
139
- ...headers,
140
- ...(payload ? { 'Content-Length': Buffer.byteLength(payload) } : {}),
141
- },
142
- timeout: 15000,
143
- rejectUnauthorized: !tlsRelaxed,
144
- },
145
- (res) => {
146
- let data = '';
147
- res.setEncoding('utf8');
148
- res.on('data', (c) => (data += c));
149
- res.on('end', () => {
150
- const ctype = String(res.headers['content-type'] || '');
151
- let j = null;
152
- try { j = data ? JSON.parse(data) : null; } catch {}
153
- if (res.statusCode >= 200 && res.statusCode < 300 && j !== null) return resolve(j);
154
- if (looksBlocked(ctype, data)) {
155
- return reject(new Error(VPN_HINT + bodySnippet(data)));
156
- }
157
- if (res.statusCode >= 200 && res.statusCode < 300) {
158
- return reject(new Error(`discord ${path}: bozuk yanıt (HTTP ${res.statusCode}${ctype ? ', ' + ctype : ''})${bodySnippet(data)}`));
159
- }
160
- reject(new Error(`discord ${path}: HTTP ${res.statusCode} ${(j && j.message) || ''}`.trim() + bodySnippet(data)));
161
- });
162
- }
163
- );
164
- req.on('timeout', () => req.destroy(new Error('zaman aşımı')));
165
- req.on('error', reject);
166
- if (payload) req.write(payload);
167
- req.end();
168
- });
169
- }
170
-
171
- async start() {
172
- if (!this.token) {
173
- this._setStatus('error');
174
- return false;
175
- }
176
- this.stopping = false;
177
- this._setStatus('connecting');
178
- try {
179
- const me = await this.api('GET', '/users/@me');
180
- this.user = me; // { id, username, ... }
181
- this._setStatus('connected', '@' + (me.username || 'bot'));
182
- } catch (e) {
183
- this._setStatus('error');
184
- throw e;
185
- }
186
- this._connect();
187
- return true;
188
- }
189
-
190
- _connect() {
191
- if (this.stopping) return;
192
- clearInterval(this._hbTimer);
193
- this._hbTimer = null;
194
- let identified = false;
195
- const ws = new WebSocket(GATEWAY_URL, { rejectUnauthorized: !tlsRelaxed });
196
- this._ws = ws;
197
-
198
- ws.on('message', (raw) => {
199
- let p = null;
200
- try { p = JSON.parse(String(raw)); } catch { return; }
201
- const op = p.op;
202
- const d = p.d;
203
- const t = p.t;
204
- if (typeof p.s === 'number') this._seq = p.s;
205
-
206
- if (op === 10) {
207
- /* Hello: heartbeat aralığı gelıyor → hafif erken at (güvenli pay) */
208
- const iv = (d && d.heartbeat_interval) || 41250;
209
- clearInterval(this._hbTimer);
210
- this._hbTimer = setInterval(() => {
211
- try {
212
- if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify({ op: 1, d: this._seq }));
213
- } catch {}
214
- }, Math.max(5000, Math.floor(iv * 0.8)));
215
- if (!identified) {
216
- identified = true;
217
- try {
218
- ws.send(
219
- JSON.stringify({
220
- op: 2,
221
- d: {
222
- token: this.token,
223
- intents: INTENTS,
224
- properties: { os: 'windows', browser: 'beast-agent', device: 'beast-agent' },
225
- },
226
- })
227
- );
228
- } catch {}
229
- }
230
- return;
231
- }
232
- if (op === 1) {
233
- /* sunucu heartbeat istedi — hemen at */
234
- try {
235
- if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify({ op: 1, d: this._seq }));
236
- } catch {}
237
- return;
238
- }
239
- if (op === 9 || op === 7) {
240
- /* invalid session / reconnect isteği — kapat, close handler yeniden bağlanır */
241
- try { ws.close(); } catch {}
242
- return;
243
- }
244
- if (op === 0 && t === 'READY') {
245
- this._backoff = 2000;
246
- this._setStatus('connected', '@' + ((d && d.user && d.user.username) || 'bot'));
247
- return;
248
- }
249
- if (op === 0 && t === 'MESSAGE_CREATE' && this.onIncoming) {
250
- try {
251
- const m = d || {};
252
- if (!m.author || m.author.bot || m.webhook_id) return;
253
- let text = String(m.content || '').trim();
254
- if (!text) return;
255
- const botId = this.user && this.user.id;
256
- const mentioned = !!(botId && text.includes('<@' + botId + '>'));
257
- if (botId) text = text.split('<@' + botId + '>').join(' ').replace(/\s+/g, ' ').trim();
258
- const payload = {
259
- text: text.slice(0, 6000),
260
- senderId: String((m.author && m.author.id) || ''),
261
- username: String((m.author && m.author.username) || ''),
262
- senderName: String((m.author && (m.author.global_name || m.author.username)) || ''),
263
- isGroup: !!m.guild_id,
264
- mentioned,
265
- channelId: String(m.channel_id || ''),
266
- };
267
- if (!payload.channelId) return;
268
- /* Sunucu mesajlarında yalnız @mention (spam koruması); DM'de hepsi */
269
- if (payload.isGroup && !mentioned) return;
270
- this.onIncoming(payload.channelId, payload);
271
- } catch {}
272
- return;
273
- }
274
- });
275
-
276
- ws.on('close', () => {
277
- clearInterval(this._hbTimer);
278
- this._hbTimer = null;
279
- if (this.stopping) return;
280
- this._setStatus('connecting');
281
- const wait = this._backoff;
282
- this._backoff = Math.min(30000, this._backoff * 2);
283
- setTimeout(() => this._connect(), wait);
284
- });
285
-
286
- ws.on('error', (e) => {
287
- /* gateway TLS'i de ara yazılımdan etkilenebilir — esnet ve close sonrası
288
- yeniden bağlanma zaten relaxed ile kurar */
289
- if (isCertError(e) && !tlsRelaxed) {
290
- tlsRelaxed = true;
291
- this.emit({
292
- type: 'warn',
293
- text: 'ağ ara yazılımı (antivirüs/proxy) self-signed sertifikası tespit edildi — Discord bağlantısı esnek TLS ile devam ediyor',
294
- });
295
- }
296
- });
297
- }
298
-
299
- async stop() {
300
- this.stopping = true;
301
- clearInterval(this._hbTimer);
302
- this._hbTimer = null;
303
- try {
304
- if (this._ws) this._ws.close();
305
- } catch {}
306
- this._ws = null;
307
- this.connected = false;
308
- this.status = 'disconnected';
309
- }
310
-
311
- snapshot() {
312
- return {
313
- status: this.status,
314
- user: this.user ? '@' + (this.user.username || 'bot') : null,
315
- connected: this.connected,
316
- };
317
- }
318
-
319
- /* Metin gönder — 2000 karakter sınırı için parçalara böl */
320
- async send(channelId, text) {
321
- const t = String(text || '');
322
- if (!t.trim() || !channelId) return false;
323
- const chunks = [];
324
- for (let i = 0; i < t.length; i += SEND_CHUNK) chunks.push(t.slice(i, i + SEND_CHUNK));
325
- for (const part of chunks) {
326
- await this.api('POST', `/channels/${channelId}/messages`, { content: part });
327
- }
328
- return true;
329
- }
330
- }
331
-
332
- module.exports = { DiscordBridge };
1
+ 'use strict';
2
+
3
+ /* DISCORD KÖPRÜSÜ — Telegram köprüsünün Discord hali.
4
+ Bağımlılık: yalnızca `ws` (paket zaten kurulu — baileys ile geliyor).
5
+ - Gateway WebSocket: Identify → READY → MESSAGE_CREATE dispatch
6
+ - REST (api/v10): mesaj gönderme (2000 karakter sınırında bölerek)
7
+ - Sunucu (guild) mesajlarında yalnız @mention'a cevap verilir (spam koruması);
8
+ DM'de her izinli kullanıcıya cevap verilir.
9
+ - Bağlantı koparsa backoff ile yeniden bağlanır (yeniden Identify;
10
+ çevrimdışıyken gelen mesajlar backfill edilmez — Telegram long-poll'un aksine).
11
+ ÖNEMLİ: Discord Developer Portal'da bot için "MESSAGE CONTENT INTENT"
12
+ açılmalıdır — yoksa mesaj içerikleri boş gelir. */
13
+
14
+ const WebSocket = require('ws');
15
+ const https = require('https');
16
+
17
+ const API_BASE = 'https://discord.com/api/v10';
18
+ const GATEWAY_URL = 'wss://gateway.discord.gg/?v=10&encoding=json';
19
+ const SEND_CHUNK = 1900; // Discord mesaj sınırı 2000 — güvenli pay
20
+ /* GUILDS(1) | GUILD_MESSAGES(512) | DIRECT_MESSAGES(4096) | MESSAGE_CONTENT(32768) */
21
+ const INTENTS = 1 | 512 | 4096 | 32768;
22
+
23
+ /* AĞ ARA YAZILIMI (antivirüs SSL taraması / proxy / ağ filtresi) Discord
24
+ trafiğine araya girip SELF-SIGNED sertifika sunarsa Node varsayılan olarak
25
+ bağlantıyı reddeder ("self signed certificate") ve bot hiç bağlanamaz.
26
+ Çözüm iki katman:
27
+ 1) REST istekleri ÖNCE Electron/Chromium ağ yığınından (net.fetch) geçer —
28
+ sistem CA + sistem proxy otomatik tanınır (tarayıcının çalıştığı yol).
29
+ 2) Zorlanırsa Node https'e düşülür; self-signed tespit edilirse bu köprü
30
+ için TLS doğrulaması esnetilir — YALNIZ Discord bağlantıları etkilenir. */
31
+ let tlsRelaxed = false;
32
+ const CERT_ERR_RE = /self[ -]?signed|unable to verify the first certificate|depth zero|err_tls|certificate has expired|cert_has_expired|unable_to_get_issuer|self signed certificate/i;
33
+
34
+ function isCertError(e) {
35
+ return CERT_ERR_RE.test(String((e && (e.message || e.code)) || e || ''));
36
+ }
37
+
38
+ /* Electron main process'te Chromium ağ yığını (sistem CA + proxy); düz Node
39
+ (testler) için require('electron') path string döndürür → net tanımsız kalır */
40
+ let _electronNet = null;
41
+ try {
42
+ const el = require('electron');
43
+ if (el && el.net && typeof el.net.fetch === 'function') _electronNet = el.net;
44
+ } catch {}
45
+
46
+ function bodySnippet(data) {
47
+ const s = String(data || '').replace(/\s+/g, ' ').trim();
48
+ return s ? ` — gövde: ${s.slice(0, 140)}` : '';
49
+ }
50
+
51
+ /* Ağ engel sayfası tespiti: Discord API her zaman JSON döner — text/html veya
52
+ erişim-engelle sayfası gelirse ağ seviyesinde blok var demektir (Türkiye'de
53
+ Discord erişimi zaman zaman engellenir). Kullanıcıya net VPN yönlendirmesi. */
54
+ const VPN_HINT = 'Discord bu ağdan ERİŞİME ENGELLİ görünüyor — VPN açıp tekrar dene (engel sayfası döndü)';
55
+
56
+ function looksBlocked(ctype, data) {
57
+ const ct = String(ctype || '');
58
+ const body = String(data || '').slice(0, 4000);
59
+ return (
60
+ ct.includes('text/html') ||
61
+ /erisime[_ ]?engelle|erişime engelle|blocked|block_page/i.test(body)
62
+ );
63
+ }
64
+
65
+ class DiscordBridge {
66
+ constructor({ token, emit, onIncoming }) {
67
+ this.token = String(token || '').trim();
68
+ this.emit = emit || (() => {});
69
+ this.onIncoming = onIncoming || null;
70
+ this.connected = false;
71
+ this.stopping = false;
72
+ this.status = 'disconnected';
73
+ this.user = null; // { id, username, ... }
74
+ this._ws = null;
75
+ this._hbTimer = null;
76
+ this._seq = null;
77
+ this._backoff = 2000;
78
+ }
79
+
80
+ _setStatus(status, user) {
81
+ this.status = status;
82
+ this.connected = status === 'connected';
83
+ this.emit({ type: 'status', status, user: user || null });
84
+ }
85
+
86
+ /* REST çağrısı — JSON (token: "Bot <token>").
87
+ Yol 1: Chromium (net.fetch — sistem CA/proxy). Yol 2: Node https (+esnek TLS). */
88
+ api(method, path, body, attempt = 0) {
89
+ return this._api(method, path, body).catch((e) => {
90
+ if (isCertError(e) && !tlsRelaxed) {
91
+ tlsRelaxed = true;
92
+ this.emit({
93
+ type: 'warn',
94
+ text: 'ağ ara yazılımı (antivirüs/proxy) self-signed sertifikası tespit edildi — Discord bağlantısı esnek TLS ile devam ediyor',
95
+ });
96
+ if (attempt < 1) return this.api(method, path, body, attempt + 1);
97
+ }
98
+ throw e;
99
+ });
100
+ }
101
+
102
+ async _api(method, path, body) {
103
+ const payload = body ? JSON.stringify(body) : null;
104
+ const headers = { 'Content-Type': 'application/json', Authorization: 'Bot ' + this.token };
105
+ const url = API_BASE + path;
106
+
107
+ /* YOL 1 — Chromium ağ yığını (varsa): sistem sertifikaları + sistem proxy */
108
+ if (_electronNet) {
109
+ try {
110
+ const res = await _electronNet.fetch(url, {
111
+ method,
112
+ headers,
113
+ ...(payload ? { body: payload } : {}),
114
+ });
115
+ const data = await res.text();
116
+ let j = null;
117
+ try { j = data ? JSON.parse(data) : null; } catch {}
118
+ if (res.ok && j !== null) return j;
119
+ if (j === null && looksBlocked(res.headers.get('content-type'), data)) {
120
+ throw new Error(VPN_HINT + bodySnippet(data));
121
+ }
122
+ throw new Error(
123
+ `discord ${path}: HTTP ${res.status}${j && j.message ? ' ' + j.message : ''}${bodySnippet(data)}`
124
+ );
125
+ } catch (e) {
126
+ /* kendi formatladığımız API hatasıysa yukarı taşı; transport hatasıysa
127
+ Node https yoluna düş (orada esnek TLS şansı var) */
128
+ if (/^discord |Discord bu ağ/.test(String((e && e.message) || ''))) throw e;
129
+ }
130
+ }
131
+
132
+ /* YOL 2 — Node https (+ gerektiğinde esnek TLS) */
133
+ return new Promise((resolve, reject) => {
134
+ const req = https.request(
135
+ url,
136
+ {
137
+ method,
138
+ headers: {
139
+ ...headers,
140
+ ...(payload ? { 'Content-Length': Buffer.byteLength(payload) } : {}),
141
+ },
142
+ timeout: 15000,
143
+ rejectUnauthorized: !tlsRelaxed,
144
+ },
145
+ (res) => {
146
+ let data = '';
147
+ res.setEncoding('utf8');
148
+ res.on('data', (c) => (data += c));
149
+ res.on('end', () => {
150
+ const ctype = String(res.headers['content-type'] || '');
151
+ let j = null;
152
+ try { j = data ? JSON.parse(data) : null; } catch {}
153
+ if (res.statusCode >= 200 && res.statusCode < 300 && j !== null) return resolve(j);
154
+ if (looksBlocked(ctype, data)) {
155
+ return reject(new Error(VPN_HINT + bodySnippet(data)));
156
+ }
157
+ if (res.statusCode >= 200 && res.statusCode < 300) {
158
+ return reject(new Error(`discord ${path}: bozuk yanıt (HTTP ${res.statusCode}${ctype ? ', ' + ctype : ''})${bodySnippet(data)}`));
159
+ }
160
+ reject(new Error(`discord ${path}: HTTP ${res.statusCode} ${(j && j.message) || ''}`.trim() + bodySnippet(data)));
161
+ });
162
+ }
163
+ );
164
+ req.on('timeout', () => req.destroy(new Error('zaman aşımı')));
165
+ req.on('error', reject);
166
+ if (payload) req.write(payload);
167
+ req.end();
168
+ });
169
+ }
170
+
171
+ async start() {
172
+ if (!this.token) {
173
+ this._setStatus('error');
174
+ return false;
175
+ }
176
+ this.stopping = false;
177
+ this._setStatus('connecting');
178
+ try {
179
+ const me = await this.api('GET', '/users/@me');
180
+ this.user = me; // { id, username, ... }
181
+ this._setStatus('connected', '@' + (me.username || 'bot'));
182
+ } catch (e) {
183
+ this._setStatus('error');
184
+ throw e;
185
+ }
186
+ this._connect();
187
+ return true;
188
+ }
189
+
190
+ _connect() {
191
+ if (this.stopping) return;
192
+ clearInterval(this._hbTimer);
193
+ this._hbTimer = null;
194
+ let identified = false;
195
+ const ws = new WebSocket(GATEWAY_URL, { rejectUnauthorized: !tlsRelaxed });
196
+ this._ws = ws;
197
+
198
+ ws.on('message', (raw) => {
199
+ let p = null;
200
+ try { p = JSON.parse(String(raw)); } catch { return; }
201
+ const op = p.op;
202
+ const d = p.d;
203
+ const t = p.t;
204
+ if (typeof p.s === 'number') this._seq = p.s;
205
+
206
+ if (op === 10) {
207
+ /* Hello: heartbeat aralığı gelıyor → hafif erken at (güvenli pay) */
208
+ const iv = (d && d.heartbeat_interval) || 41250;
209
+ clearInterval(this._hbTimer);
210
+ this._hbTimer = setInterval(() => {
211
+ try {
212
+ if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify({ op: 1, d: this._seq }));
213
+ } catch {}
214
+ }, Math.max(5000, Math.floor(iv * 0.8)));
215
+ if (!identified) {
216
+ identified = true;
217
+ try {
218
+ ws.send(
219
+ JSON.stringify({
220
+ op: 2,
221
+ d: {
222
+ token: this.token,
223
+ intents: INTENTS,
224
+ properties: { os: 'windows', browser: 'beast-agent', device: 'beast-agent' },
225
+ },
226
+ })
227
+ );
228
+ } catch {}
229
+ }
230
+ return;
231
+ }
232
+ if (op === 1) {
233
+ /* sunucu heartbeat istedi — hemen at */
234
+ try {
235
+ if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify({ op: 1, d: this._seq }));
236
+ } catch {}
237
+ return;
238
+ }
239
+ if (op === 9 || op === 7) {
240
+ /* invalid session / reconnect isteği — kapat, close handler yeniden bağlanır */
241
+ try { ws.close(); } catch {}
242
+ return;
243
+ }
244
+ if (op === 0 && t === 'READY') {
245
+ this._backoff = 2000;
246
+ this._setStatus('connected', '@' + ((d && d.user && d.user.username) || 'bot'));
247
+ return;
248
+ }
249
+ if (op === 0 && t === 'MESSAGE_CREATE' && this.onIncoming) {
250
+ try {
251
+ const m = d || {};
252
+ if (!m.author || m.author.bot || m.webhook_id) return;
253
+ let text = String(m.content || '').trim();
254
+ if (!text) return;
255
+ const botId = this.user && this.user.id;
256
+ const mentioned = !!(botId && text.includes('<@' + botId + '>'));
257
+ if (botId) text = text.split('<@' + botId + '>').join(' ').replace(/\s+/g, ' ').trim();
258
+ const payload = {
259
+ text: text.slice(0, 6000),
260
+ senderId: String((m.author && m.author.id) || ''),
261
+ username: String((m.author && m.author.username) || ''),
262
+ senderName: String((m.author && (m.author.global_name || m.author.username)) || ''),
263
+ isGroup: !!m.guild_id,
264
+ mentioned,
265
+ channelId: String(m.channel_id || ''),
266
+ };
267
+ if (!payload.channelId) return;
268
+ /* Sunucu mesajlarında yalnız @mention (spam koruması); DM'de hepsi */
269
+ if (payload.isGroup && !mentioned) return;
270
+ this.onIncoming(payload.channelId, payload);
271
+ } catch {}
272
+ return;
273
+ }
274
+ });
275
+
276
+ ws.on('close', () => {
277
+ clearInterval(this._hbTimer);
278
+ this._hbTimer = null;
279
+ if (this.stopping) return;
280
+ this._setStatus('connecting');
281
+ const wait = this._backoff;
282
+ this._backoff = Math.min(30000, this._backoff * 2);
283
+ setTimeout(() => this._connect(), wait);
284
+ });
285
+
286
+ ws.on('error', (e) => {
287
+ /* gateway TLS'i de ara yazılımdan etkilenebilir — esnet ve close sonrası
288
+ yeniden bağlanma zaten relaxed ile kurar */
289
+ if (isCertError(e) && !tlsRelaxed) {
290
+ tlsRelaxed = true;
291
+ this.emit({
292
+ type: 'warn',
293
+ text: 'ağ ara yazılımı (antivirüs/proxy) self-signed sertifikası tespit edildi — Discord bağlantısı esnek TLS ile devam ediyor',
294
+ });
295
+ }
296
+ });
297
+ }
298
+
299
+ async stop() {
300
+ this.stopping = true;
301
+ clearInterval(this._hbTimer);
302
+ this._hbTimer = null;
303
+ try {
304
+ if (this._ws) this._ws.close();
305
+ } catch {}
306
+ this._ws = null;
307
+ this.connected = false;
308
+ this.status = 'disconnected';
309
+ }
310
+
311
+ snapshot() {
312
+ return {
313
+ status: this.status,
314
+ user: this.user ? '@' + (this.user.username || 'bot') : null,
315
+ connected: this.connected,
316
+ };
317
+ }
318
+
319
+ /* Metin gönder — 2000 karakter sınırı için parçalara böl */
320
+ async send(channelId, text) {
321
+ const t = String(text || '');
322
+ if (!t.trim() || !channelId) return false;
323
+ const chunks = [];
324
+ for (let i = 0; i < t.length; i += SEND_CHUNK) chunks.push(t.slice(i, i + SEND_CHUNK));
325
+ for (const part of chunks) {
326
+ await this.api('POST', `/channels/${channelId}/messages`, { content: part });
327
+ }
328
+ return true;
329
+ }
330
+ }
331
+
332
+ module.exports = { DiscordBridge };