beast-agent 0.26.0 → 0.26.2
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.
- package/bin/beast-agent.js +34 -17
- package/package.json +2 -2
- package/scripts/fix-electron.js +126 -0
- package/src/agent/bots.js +8 -0
- package/src/agent/discord.js +268 -0
- package/src/agent/engine.js +135 -77
- package/src/agent/llm.js +46 -17
- package/src/agent/research.js +133 -0
- package/src/agent/skills.js +8 -1
- package/src/agent/tools.js +53 -14
- package/src/agent/watext.js +4 -0
- package/src/main.js +534 -351
- package/src/preload.js +8 -5
- package/src/renderer/i18n.js +19 -55
- package/src/renderer/index.html +1 -3
- package/src/renderer/renderer.js +336 -155
- package/src/renderer/style.css +1 -60
- package/tests/bg-jobs.test.js +5 -10
- package/tests/llm.test.js +81 -1
- package/tests/python.test.js +3 -2
- package/tests/research.test.js +116 -0
package/bin/beast-agent.js
CHANGED
|
@@ -82,27 +82,44 @@ if (process.argv[2] === 'update') {
|
|
|
82
82
|
process.exit(1);
|
|
83
83
|
}
|
|
84
84
|
console.log('\n\u2713 beast-agent g\u00FCncellendi \u2014 uygulama ba\u015Flat\u0131l\u0131yor\u2026');
|
|
85
|
-
|
|
86
|
-
const electron = require('electron');
|
|
87
|
-
if (typeof electron === 'string') {
|
|
88
|
-
spawn(electron, [path.resolve(__dirname, '..')], { stdio: 'ignore', detached: true }).unref();
|
|
89
|
-
}
|
|
90
|
-
} catch {}
|
|
85
|
+
launchDetached([]);
|
|
91
86
|
if (isWin) spawnSync('cmd.exe', ['/c', 'timeout /t 3'], { stdio: 'ignore' });
|
|
92
87
|
process.exit(0);
|
|
93
88
|
}
|
|
94
89
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
90
|
+
/* detached başlatıcı + kendini onaran electron:
|
|
91
|
+
taze makinalarda npm postinstall sırasında electron binary indirmesi
|
|
92
|
+
sessizce başarısız olmuş olabilir → burada otomatik tamir edilir. */
|
|
93
|
+
function launchDetached(extraArgs) {
|
|
94
|
+
let electron = null;
|
|
95
|
+
try { electron = require('electron'); } catch {}
|
|
96
|
+
if (typeof electron !== 'string') {
|
|
97
|
+
let fix = null;
|
|
98
|
+
try { fix = require('../scripts/fix-electron'); } catch {}
|
|
99
|
+
if (fix) {
|
|
100
|
+
console.log('\u27F3 electron \u00e7al\u0131\u015Fma dosyalar\u0131 eksik bulundu \u2014 otomatik onar\u0131l\u0131yor\u2026');
|
|
101
|
+
const r = fix.repair({ quiet: false });
|
|
102
|
+
if (r.ok && !r.skipped) console.log('\u2713 electron onar\u0131ld\u0131');
|
|
103
|
+
if (r.ok) {
|
|
104
|
+
try { electron = require('electron'); } catch {}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (typeof electron !== 'string') {
|
|
109
|
+
console.log('\n\u2717 Electron \u00e7al\u0131\u015Fma dosyas\u0131 kurulamad\u0131.');
|
|
110
|
+
console.log(' Elle \u00e7\u00f6z\u00fcm \u2014 \u015fu 2 komutu \u00e7al\u0131\u015Ft\u0131r:');
|
|
111
|
+
console.log(' npm config set ignore-scripts false');
|
|
112
|
+
console.log(' npm install -g beast-agent');
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
const child = spawn(electron, [path.resolve(__dirname, '..'), ...extraArgs], {
|
|
116
|
+
stdio: 'ignore',
|
|
117
|
+
detached: true,
|
|
118
|
+
/* windowsHide KULLANMA: Chromium ilk pencereyi gizli başlatıyor (tray-only bug) */
|
|
119
|
+
});
|
|
120
|
+
child.unref();
|
|
121
|
+
return true;
|
|
99
122
|
}
|
|
100
123
|
|
|
101
|
-
|
|
102
|
-
const child = spawn(electron, [appPath, ...process.argv.slice(2)], {
|
|
103
|
-
stdio: 'ignore',
|
|
104
|
-
detached: true,
|
|
105
|
-
/* windowsHide KULLANMA: Chromium ilk pencereyi gizli başlatıyor (tray-only bug) */
|
|
106
|
-
});
|
|
107
|
-
child.unref();
|
|
124
|
+
if (!launchDetached(process.argv.slice(2))) process.exit(1);
|
|
108
125
|
process.exit(0);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "beast-agent",
|
|
3
3
|
"productName": "Beast Agent",
|
|
4
|
-
"version": "0.26.
|
|
4
|
+
"version": "0.26.2",
|
|
5
5
|
"description": "Ultra-fast local agent shell for Windows.",
|
|
6
6
|
"author": "algokodcom (AlgoKod)",
|
|
7
7
|
"license": "MIT",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"dist": "electron-builder --win nsis portable",
|
|
34
34
|
"test": "node --test \"tests/**/*.test.js\"",
|
|
35
35
|
"release": "node scripts/release.js",
|
|
36
|
+
"postinstall": "node scripts/fix-electron.js --soft",
|
|
36
37
|
"prepublishOnly": "node scripts/swap-electron.js deps",
|
|
37
38
|
"postpublish": "node scripts/swap-electron.js devdeps"
|
|
38
39
|
},
|
|
@@ -54,7 +55,6 @@
|
|
|
54
55
|
"pdfkit": "^0.20.1",
|
|
55
56
|
"qrcode": "^1.5.4",
|
|
56
57
|
"tesseract.js": "^7.0.0",
|
|
57
|
-
"undici": "^7.29.0",
|
|
58
58
|
"ws": "^8.21.3",
|
|
59
59
|
"electron": "40.10.2"
|
|
60
60
|
},
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/* BEAST — electron binary onarıcı.
|
|
4
|
+
SORUN: `electron` npm paketi binary'yi (dist/electron.exe) postinstall'da
|
|
5
|
+
GitHub'dan indirir. Taze makinalarda bu indirme SESSİZCE başarısız olabilir
|
|
6
|
+
(proxy/GitHub engeli, npm ignore-scripts=true, ağ kesintisi) — paket kurulmuş
|
|
7
|
+
görünür ama `require('electron')` "Electron failed to install correctly" fırlatır.
|
|
8
|
+
ÇÖZÜM: install.js'i elle çalıştır; olmazsa yedek aynadan (npmmirror) dene.
|
|
9
|
+
Kullanım yerleri:
|
|
10
|
+
- package.json postinstall → kurulumda otomatik (npm i -g beast-agent)
|
|
11
|
+
- bin/beast-agent.js → her başlatmada bozuksa otomatik onar */
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const { spawnSync } = require('child_process');
|
|
16
|
+
|
|
17
|
+
/* electron paket dizini (index.js'in yeri); paket hiç yoksa null */
|
|
18
|
+
function electronDir() {
|
|
19
|
+
try {
|
|
20
|
+
return path.dirname(require.resolve('electron'));
|
|
21
|
+
} catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/* binary sağlam mı? electron/index.js'in beklentisiyle aynı kontrol:
|
|
27
|
+
path.txt + dist/<exe> varlığı */
|
|
28
|
+
function status() {
|
|
29
|
+
const dir = electronDir();
|
|
30
|
+
if (!dir) return { ok: false, missing: true };
|
|
31
|
+
const exe = process.platform === 'win32' ? 'electron.exe' : 'electron';
|
|
32
|
+
const distOk = fs.existsSync(path.join(dir, 'dist', exe));
|
|
33
|
+
const txtOk = fs.existsSync(path.join(dir, 'path.txt'));
|
|
34
|
+
return distOk && txtOk ? { ok: true, dir } : { ok: false, dir };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* platforma göre dist içindeki çalıştırılabilirin göreli yolu (electron/index.js ile aynı) */
|
|
38
|
+
function platformPath() {
|
|
39
|
+
switch (process.platform) {
|
|
40
|
+
case 'win32':
|
|
41
|
+
return 'electron.exe';
|
|
42
|
+
case 'darwin':
|
|
43
|
+
case 'mas':
|
|
44
|
+
return 'Electron.app/Contents/MacOS/Electron';
|
|
45
|
+
default:
|
|
46
|
+
return 'electron';
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* onarım — 3 katman:
|
|
51
|
+
0) binary sağlam, yalnız path.txt eksik/bozuk → indirimsiz anında yazar
|
|
52
|
+
(install.js bu durumu "zaten kurulu" sanıp atlayabiliyor)
|
|
53
|
+
1) dist bozuk/eksik → dist'i sil + install.js (GitHub) ile taze indir
|
|
54
|
+
2) GitHub engelli → ELECTRON_MIRROR=npmmirror ile tekrar */
|
|
55
|
+
function repair({ quiet } = {}) {
|
|
56
|
+
let st = status();
|
|
57
|
+
if (st.ok) return { ok: true, dir: st.dir, skipped: true };
|
|
58
|
+
if (st.missing) return { ok: false, error: 'electron paketi hi\u00e7 yok — npm install -g beast-agent' };
|
|
59
|
+
const dir = st.dir;
|
|
60
|
+
const exeRel = platformPath();
|
|
61
|
+
const exeAbs = path.join(dir, 'dist', ...exeRel.split('/'));
|
|
62
|
+
const verFile = path.join(dir, 'dist', 'version');
|
|
63
|
+
let wantVer = null;
|
|
64
|
+
try { wantVer = String(require(path.join(dir, 'package.json')).version || ''); } catch {}
|
|
65
|
+
const distIntact =
|
|
66
|
+
fs.existsSync(exeAbs) &&
|
|
67
|
+
(!wantVer ||
|
|
68
|
+
(fs.existsSync(verFile) &&
|
|
69
|
+
fs.readFileSync(verFile, 'utf8').replace(/^v/, '').trim() === wantVer));
|
|
70
|
+
|
|
71
|
+
if (distIntact) {
|
|
72
|
+
/* KATMAN 0: hızlı onarım — path.txt'yi kendimiz yazıyoruz (indirim yok) */
|
|
73
|
+
try { fs.writeFileSync(path.join(dir, 'path.txt'), exeRel, 'utf8'); } catch {}
|
|
74
|
+
if (status().ok) return { ok: true, dir, fast: true };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* KATMAN 1-2: tam indirim */
|
|
78
|
+
try { fs.rmSync(path.join(dir, 'dist'), { recursive: true, force: true }); } catch {}
|
|
79
|
+
try { fs.unlinkSync(path.join(dir, 'path.txt')); } catch {}
|
|
80
|
+
const installJs = path.join(dir, 'install.js');
|
|
81
|
+
if (!fs.existsSync(installJs)) {
|
|
82
|
+
return { ok: false, error: 'install.js yok — yeniden kur: npm install -g beast-agent' };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const sources = [null, 'https://npmmirror.com/mirrors/electron/'];
|
|
86
|
+
for (let i = 0; i < sources.length; i++) {
|
|
87
|
+
const env = { ...process.env };
|
|
88
|
+
if (sources[i]) {
|
|
89
|
+
env.ELECTRON_MIRROR = sources[i];
|
|
90
|
+
if (!quiet) console.log(' \u2022 varsay\u0131lan kaynak ba\u015Far\u0131s\u0131z \u2014 yedek ayna deneniyor (npmmirror)\u2026');
|
|
91
|
+
} else if (!quiet) {
|
|
92
|
+
console.log(' \u2022 electron binary indiriliyor (ilk sefer ~110 MB, 1-2 dk)\u2026');
|
|
93
|
+
}
|
|
94
|
+
const r = spawnSync(process.execPath, [installJs], {
|
|
95
|
+
cwd: dir,
|
|
96
|
+
env,
|
|
97
|
+
stdio: quiet ? 'ignore' : 'inherit',
|
|
98
|
+
timeout: 600000,
|
|
99
|
+
});
|
|
100
|
+
if (!r.error && r.status === 0 && status().ok) {
|
|
101
|
+
return { ok: true, dir, mirror: sources[i] || 'github' };
|
|
102
|
+
}
|
|
103
|
+
if (!quiet && r.error) {
|
|
104
|
+
console.log(' ! deneme ba\u015far\u0131s\u0131z: ' + String(r.error.message || r.error).slice(0, 160));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return { ok: false, error: 'electron binary indirilemedi (a\u011f/proxy engeli olabilir)' };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
module.exports = { electronDir, status, repair };
|
|
111
|
+
|
|
112
|
+
/* CLI: package.json postinstall → `node scripts/fix-electron.js --soft`
|
|
113
|
+
(her durumda exit 0 — npm install bozulmasın, başlatma anında tekrar denenir)
|
|
114
|
+
elle çağrı → hata durumunda exit 1 */
|
|
115
|
+
if (require.main === module) {
|
|
116
|
+
const soft = process.argv.includes('--soft');
|
|
117
|
+
const r = repair({ quiet: false });
|
|
118
|
+
if (r.ok) {
|
|
119
|
+
console.log(r.skipped ? '\u2713 electron binary tamam' : '\u2713 electron binary onar\u0131ld\u0131');
|
|
120
|
+
process.exit(0);
|
|
121
|
+
}
|
|
122
|
+
console.error('\u2717 electron onar\u0131lamad\u0131: ' + (r.error || '?'));
|
|
123
|
+
console.error(' elle \u00e7\u00f6z\u00fcm: npm config set ignore-scripts false');
|
|
124
|
+
console.error(' npm install -g beast-agent');
|
|
125
|
+
process.exit(soft ? 0 : 1);
|
|
126
|
+
}
|
package/src/agent/bots.js
CHANGED
|
@@ -327,6 +327,14 @@ function update(id, patch) {
|
|
|
327
327
|
changes.push('dış tarayıcı komutu güncellendi');
|
|
328
328
|
b.extCommand = String(patch.extCommand).slice(0, 200);
|
|
329
329
|
}
|
|
330
|
+
if (typeof patch.model === 'string') {
|
|
331
|
+
/* bot bazlı model override — boş string = global seçim */
|
|
332
|
+
const next = patch.model.trim().slice(0, 160);
|
|
333
|
+
if (next !== (b.model || '')) {
|
|
334
|
+
changes.push('model güncellendi');
|
|
335
|
+
b.model = next;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
330
338
|
if (Array.isArray(patch.plugins)) {
|
|
331
339
|
const next = [...new Set(patch.plugins.map((p) => String(p).slice(0, 40)).filter((p) => PLUGIN_LIST.includes(p)))];
|
|
332
340
|
if (JSON.stringify(next) !== JSON.stringify(b.plugins || [])) {
|
|
@@ -0,0 +1,268 @@
|
|
|
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: hata tespit edilince bu köprü için TLS doğrulaması esnetilir ve
|
|
27
|
+
uyarı loglanır — YALNIZ Discord bağlantıları etkilenir, app'in geri kalanı
|
|
28
|
+
normal doğrulama kullanmaya devam eder. */
|
|
29
|
+
let tlsRelaxed = false;
|
|
30
|
+
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;
|
|
31
|
+
|
|
32
|
+
function isCertError(e) {
|
|
33
|
+
return CERT_ERR_RE.test(String((e && (e.message || e.code)) || e || ''));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
class DiscordBridge {
|
|
37
|
+
constructor({ token, emit, onIncoming }) {
|
|
38
|
+
this.token = String(token || '').trim();
|
|
39
|
+
this.emit = emit || (() => {});
|
|
40
|
+
this.onIncoming = onIncoming || null;
|
|
41
|
+
this.connected = false;
|
|
42
|
+
this.stopping = false;
|
|
43
|
+
this.status = 'disconnected';
|
|
44
|
+
this.user = null; // { id, username, ... }
|
|
45
|
+
this._ws = null;
|
|
46
|
+
this._hbTimer = null;
|
|
47
|
+
this._seq = null;
|
|
48
|
+
this._backoff = 2000;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
_setStatus(status, user) {
|
|
52
|
+
this.status = status;
|
|
53
|
+
this.connected = status === 'connected';
|
|
54
|
+
this.emit({ type: 'status', status, user: user || null });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* REST çağrısı — JSON (token: "Bot <token>"). Cert engeli tespit edilirse
|
|
58
|
+
TLS esnetilip TEK seferlik yeniden denenir. */
|
|
59
|
+
api(method, path, body, attempt = 0) {
|
|
60
|
+
return new Promise((resolve, reject) => {
|
|
61
|
+
const payload = body ? JSON.stringify(body) : null;
|
|
62
|
+
const req = https.request(
|
|
63
|
+
`${API_BASE}${path}`,
|
|
64
|
+
{
|
|
65
|
+
method,
|
|
66
|
+
headers: {
|
|
67
|
+
'Content-Type': 'application/json',
|
|
68
|
+
Authorization: 'Bot ' + this.token,
|
|
69
|
+
...(payload ? { 'Content-Length': Buffer.byteLength(payload) } : {}),
|
|
70
|
+
},
|
|
71
|
+
timeout: 15000,
|
|
72
|
+
rejectUnauthorized: !tlsRelaxed,
|
|
73
|
+
},
|
|
74
|
+
(res) => {
|
|
75
|
+
let data = '';
|
|
76
|
+
res.setEncoding('utf8');
|
|
77
|
+
res.on('data', (c) => (data += c));
|
|
78
|
+
res.on('end', () => {
|
|
79
|
+
try {
|
|
80
|
+
const j = data ? JSON.parse(data) : null;
|
|
81
|
+
if (res.statusCode >= 200 && res.statusCode < 300) resolve(j);
|
|
82
|
+
else reject(new Error(`discord ${path}: HTTP ${res.statusCode} ${(j && j.message) || ''}`.trim()));
|
|
83
|
+
} catch {
|
|
84
|
+
reject(new Error(`discord ${path}: bozuk yanıt`));
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
);
|
|
89
|
+
req.on('timeout', () => req.destroy(new Error('zaman aşımı')));
|
|
90
|
+
req.on('error', reject);
|
|
91
|
+
if (payload) req.write(payload);
|
|
92
|
+
req.end();
|
|
93
|
+
}).catch((e) => {
|
|
94
|
+
/* ara yazılım sertifikası tespit: esnet ve aynı isteği bir kez daha dene */
|
|
95
|
+
if (isCertError(e) && !tlsRelaxed) {
|
|
96
|
+
tlsRelaxed = true;
|
|
97
|
+
this.emit({
|
|
98
|
+
type: 'warn',
|
|
99
|
+
text: 'ağ ara yazılımı (antivirüs/proxy) self-signed sertifikası tespit edildi — Discord bağlantısı esnek TLS ile devam ediyor',
|
|
100
|
+
});
|
|
101
|
+
if (attempt < 1) return this.api(method, path, body, attempt + 1);
|
|
102
|
+
}
|
|
103
|
+
throw e;
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async start() {
|
|
108
|
+
if (!this.token) {
|
|
109
|
+
this._setStatus('error');
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
this.stopping = false;
|
|
113
|
+
this._setStatus('connecting');
|
|
114
|
+
try {
|
|
115
|
+
const me = await this.api('GET', '/users/@me');
|
|
116
|
+
this.user = me; // { id, username, ... }
|
|
117
|
+
this._setStatus('connected', '@' + (me.username || 'bot'));
|
|
118
|
+
} catch (e) {
|
|
119
|
+
this._setStatus('error');
|
|
120
|
+
throw e;
|
|
121
|
+
}
|
|
122
|
+
this._connect();
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
_connect() {
|
|
127
|
+
if (this.stopping) return;
|
|
128
|
+
clearInterval(this._hbTimer);
|
|
129
|
+
this._hbTimer = null;
|
|
130
|
+
let identified = false;
|
|
131
|
+
const ws = new WebSocket(GATEWAY_URL, { rejectUnauthorized: !tlsRelaxed });
|
|
132
|
+
this._ws = ws;
|
|
133
|
+
|
|
134
|
+
ws.on('message', (raw) => {
|
|
135
|
+
let p = null;
|
|
136
|
+
try { p = JSON.parse(String(raw)); } catch { return; }
|
|
137
|
+
const op = p.op;
|
|
138
|
+
const d = p.d;
|
|
139
|
+
const t = p.t;
|
|
140
|
+
if (typeof p.s === 'number') this._seq = p.s;
|
|
141
|
+
|
|
142
|
+
if (op === 10) {
|
|
143
|
+
/* Hello: heartbeat aralığı gelıyor → hafif erken at (güvenli pay) */
|
|
144
|
+
const iv = (d && d.heartbeat_interval) || 41250;
|
|
145
|
+
clearInterval(this._hbTimer);
|
|
146
|
+
this._hbTimer = setInterval(() => {
|
|
147
|
+
try {
|
|
148
|
+
if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify({ op: 1, d: this._seq }));
|
|
149
|
+
} catch {}
|
|
150
|
+
}, Math.max(5000, Math.floor(iv * 0.8)));
|
|
151
|
+
if (!identified) {
|
|
152
|
+
identified = true;
|
|
153
|
+
try {
|
|
154
|
+
ws.send(
|
|
155
|
+
JSON.stringify({
|
|
156
|
+
op: 2,
|
|
157
|
+
d: {
|
|
158
|
+
token: this.token,
|
|
159
|
+
intents: INTENTS,
|
|
160
|
+
properties: { os: 'windows', browser: 'beast-agent', device: 'beast-agent' },
|
|
161
|
+
},
|
|
162
|
+
})
|
|
163
|
+
);
|
|
164
|
+
} catch {}
|
|
165
|
+
}
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (op === 1) {
|
|
169
|
+
/* sunucu heartbeat istedi — hemen at */
|
|
170
|
+
try {
|
|
171
|
+
if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify({ op: 1, d: this._seq }));
|
|
172
|
+
} catch {}
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (op === 9 || op === 7) {
|
|
176
|
+
/* invalid session / reconnect isteği — kapat, close handler yeniden bağlanır */
|
|
177
|
+
try { ws.close(); } catch {}
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (op === 0 && t === 'READY') {
|
|
181
|
+
this._backoff = 2000;
|
|
182
|
+
this._setStatus('connected', '@' + ((d && d.user && d.user.username) || 'bot'));
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (op === 0 && t === 'MESSAGE_CREATE' && this.onIncoming) {
|
|
186
|
+
try {
|
|
187
|
+
const m = d || {};
|
|
188
|
+
if (!m.author || m.author.bot || m.webhook_id) return;
|
|
189
|
+
let text = String(m.content || '').trim();
|
|
190
|
+
if (!text) return;
|
|
191
|
+
const botId = this.user && this.user.id;
|
|
192
|
+
const mentioned = !!(botId && text.includes('<@' + botId + '>'));
|
|
193
|
+
if (botId) text = text.split('<@' + botId + '>').join(' ').replace(/\s+/g, ' ').trim();
|
|
194
|
+
const payload = {
|
|
195
|
+
text: text.slice(0, 6000),
|
|
196
|
+
senderId: String((m.author && m.author.id) || ''),
|
|
197
|
+
username: String((m.author && m.author.username) || ''),
|
|
198
|
+
senderName: String((m.author && (m.author.global_name || m.author.username)) || ''),
|
|
199
|
+
isGroup: !!m.guild_id,
|
|
200
|
+
mentioned,
|
|
201
|
+
channelId: String(m.channel_id || ''),
|
|
202
|
+
};
|
|
203
|
+
if (!payload.channelId) return;
|
|
204
|
+
/* Sunucu mesajlarında yalnız @mention (spam koruması); DM'de hepsi */
|
|
205
|
+
if (payload.isGroup && !mentioned) return;
|
|
206
|
+
this.onIncoming(payload.channelId, payload);
|
|
207
|
+
} catch {}
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
ws.on('close', () => {
|
|
213
|
+
clearInterval(this._hbTimer);
|
|
214
|
+
this._hbTimer = null;
|
|
215
|
+
if (this.stopping) return;
|
|
216
|
+
this._setStatus('connecting');
|
|
217
|
+
const wait = this._backoff;
|
|
218
|
+
this._backoff = Math.min(30000, this._backoff * 2);
|
|
219
|
+
setTimeout(() => this._connect(), wait);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
ws.on('error', (e) => {
|
|
223
|
+
/* gateway TLS'i de ara yazılımdan etkilenebilir — esnet ve close sonrası
|
|
224
|
+
yeniden bağlanma zaten relaxed ile kurar */
|
|
225
|
+
if (isCertError(e) && !tlsRelaxed) {
|
|
226
|
+
tlsRelaxed = true;
|
|
227
|
+
this.emit({
|
|
228
|
+
type: 'warn',
|
|
229
|
+
text: 'ağ ara yazılımı (antivirüs/proxy) self-signed sertifikası tespit edildi — Discord bağlantısı esnek TLS ile devam ediyor',
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async stop() {
|
|
236
|
+
this.stopping = true;
|
|
237
|
+
clearInterval(this._hbTimer);
|
|
238
|
+
this._hbTimer = null;
|
|
239
|
+
try {
|
|
240
|
+
if (this._ws) this._ws.close();
|
|
241
|
+
} catch {}
|
|
242
|
+
this._ws = null;
|
|
243
|
+
this.connected = false;
|
|
244
|
+
this.status = 'disconnected';
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
snapshot() {
|
|
248
|
+
return {
|
|
249
|
+
status: this.status,
|
|
250
|
+
user: this.user ? '@' + (this.user.username || 'bot') : null,
|
|
251
|
+
connected: this.connected,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/* Metin gönder — 2000 karakter sınırı için parçalara böl */
|
|
256
|
+
async send(channelId, text) {
|
|
257
|
+
const t = String(text || '');
|
|
258
|
+
if (!t.trim() || !channelId) return false;
|
|
259
|
+
const chunks = [];
|
|
260
|
+
for (let i = 0; i < t.length; i += SEND_CHUNK) chunks.push(t.slice(i, i + SEND_CHUNK));
|
|
261
|
+
for (const part of chunks) {
|
|
262
|
+
await this.api('POST', `/channels/${channelId}/messages`, { content: part });
|
|
263
|
+
}
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
module.exports = { DiscordBridge };
|