beast-agent 0.15.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.
- package/.env.example +7 -0
- package/LICENSE +21 -0
- package/README.md +94 -0
- package/assets/app.ico +0 -0
- package/assets/tray.png +0 -0
- package/bin/beast-agent.js +23 -0
- package/config.example.yaml +26 -0
- package/eng.traineddata +0 -0
- package/package.json +101 -0
- package/scripts/swap-electron.js +40 -0
- package/src/agent/bus.js +384 -0
- package/src/agent/computeruse.js +200 -0
- package/src/agent/config.js +284 -0
- package/src/agent/engine.js +3118 -0
- package/src/agent/kb.js +123 -0
- package/src/agent/llm.js +219 -0
- package/src/agent/logger.js +90 -0
- package/src/agent/memory.js +383 -0
- package/src/agent/pdf.js +20 -0
- package/src/agent/scripts/__pycache__/websearch.cpython-312.pyc +0 -0
- package/src/agent/scripts/news.py +113 -0
- package/src/agent/scripts/websearch.py +225 -0
- package/src/agent/skills.js +522 -0
- package/src/agent/tokens.js +39 -0
- package/src/agent/tools.js +911 -0
- package/src/agent/usage.js +125 -0
- package/src/agent/watchers.js +312 -0
- package/src/agent/watext.js +75 -0
- package/src/agent/whatsapp.js +494 -0
- package/src/cron.js +245 -0
- package/src/main.js +3622 -0
- package/src/preload.js +122 -0
- package/src/renderer/browserPreload.js +73 -0
- package/src/renderer/i18n.js +757 -0
- package/src/renderer/index.html +227 -0
- package/src/renderer/renderer.js +3250 -0
- package/src/renderer/style.css +1559 -0
- package/tests/approval.test.js +56 -0
- package/tests/bg-jobs.test.js +315 -0
- package/tests/bus.test.js +46 -0
- package/tests/computeruse-context.test.js +69 -0
- package/tests/cron.test.js +95 -0
- package/tests/engine.test.js +140 -0
- package/tests/eval.test.js +91 -0
- package/tests/fallout.test.js +188 -0
- package/tests/llm.test.js +56 -0
- package/tests/memory.test.js +33 -0
- package/tests/memoryloop.test.js +26 -0
- package/tests/notegen.test.js +63 -0
- package/tests/owner.test.js +32 -0
- package/tests/python.test.js +75 -0
- package/tests/scenarios.json +81 -0
- package/tests/sessioncode.test.js +50 -0
- package/tests/setup.js +9 -0
- package/tests/tokens.test.js +42 -0
- package/tests/tools.test.js +84 -0
- package/tests/usage.test.js +44 -0
- package/tests/v11.test.js +76 -0
- package/tests/watchers.test.js +206 -0
- package/tests/watext.test.js +53 -0
- package/tests/whatsapp.test.js +103 -0
- package/tests/wherewasi.test.js +40 -0
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/* WhatsApp bridge — Baileys tabanlı. QR ile eşleme, auth %APPDATA%\beast\wa-auth,
|
|
4
|
+
gelen özel mesajlar onIncoming(jid, text)'e düşer, send(jid,text) cevap döner. */
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const QRCode = require('qrcode');
|
|
9
|
+
|
|
10
|
+
let baileys = null;
|
|
11
|
+
try {
|
|
12
|
+
baileys = require('@whiskeysockets/baileys');
|
|
13
|
+
} catch (e) {
|
|
14
|
+
baileys = null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/* WAMessageStatus: 0=ERROR 1=PENDING 2=SERVER_ACK(gönderildi)
|
|
18
|
+
3=DELIVERY_ACK(teslim) 4=READ(okundu) 5=PLAYED(ses çalındı) */
|
|
19
|
+
const STATUS_LABELS = {
|
|
20
|
+
0: 'hata',
|
|
21
|
+
1: 'bekliyor',
|
|
22
|
+
2: 'gönderildi ✓',
|
|
23
|
+
3: 'teslim ✓✓',
|
|
24
|
+
4: 'okundu ✓✓',
|
|
25
|
+
5: 'çalındı ✓✓',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function statusLabel(status) {
|
|
29
|
+
return STATUS_LABELS[status] || `bilinmeyen(${status})`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* Baileys kendi debug çıktılarını bastırmak için main enjekte etmezse no-op */
|
|
33
|
+
function waLogSafe(line) {
|
|
34
|
+
try { console.log(`[WA] ${line}`); } catch {}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const TRACK_CAP = 500;
|
|
38
|
+
|
|
39
|
+
/* lastKnownPresence etiketleri */
|
|
40
|
+
const PRESENCE_LABELS = {
|
|
41
|
+
available: 'çevrimiçi',
|
|
42
|
+
composing: 'yazıyor',
|
|
43
|
+
recording: 'ses kaydediyor',
|
|
44
|
+
paused: '',
|
|
45
|
+
unavailable: 'çevrimdışı',
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
class WhatsAppBridge {
|
|
49
|
+
constructor({ authDir, emit, onIncoming, onReaction }) {
|
|
50
|
+
this.authDir = authDir;
|
|
51
|
+
this.emit = emit || (() => {});
|
|
52
|
+
this.onIncoming = onIncoming || null;
|
|
53
|
+
this.onReaction = onReaction || null;
|
|
54
|
+
this.sock = null;
|
|
55
|
+
this.connected = false;
|
|
56
|
+
this.user = null;
|
|
57
|
+
this.stopping = false;
|
|
58
|
+
this.reconnectTimer = null;
|
|
59
|
+
this._tracked = new Map(); // msgId -> { jid, preview, ts, status, receiptDetail }
|
|
60
|
+
this._watchJids = new Set(); // presence aboneliği tutulacak sohbetler
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* main tarafı waChats anahtarlarını bildirir; bağlantı varsa anında abone olunur */
|
|
64
|
+
setWatchJids(jids) {
|
|
65
|
+
this._watchJids = new Set(jids || []);
|
|
66
|
+
if (this.sock && this.connected) {
|
|
67
|
+
this._subscribePresence().catch(() => {});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async _subscribePresence() {
|
|
72
|
+
if (!this.sock || typeof this.sock.presenceSubscribe !== 'function') return;
|
|
73
|
+
for (const jid of this._watchJids) {
|
|
74
|
+
try {
|
|
75
|
+
await this.sock.presenceSubscribe(jid);
|
|
76
|
+
} catch {}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* Baileys medya akışını buffer'a dök */
|
|
81
|
+
async _mediaBuffer(desc, type) {
|
|
82
|
+
if (!baileys || typeof baileys.downloadContentFromMessage !== 'function') {
|
|
83
|
+
throw new Error('medya indirme desteklenmiyor');
|
|
84
|
+
}
|
|
85
|
+
const stream = await baileys.downloadContentFromMessage(desc, type);
|
|
86
|
+
const chunks = [];
|
|
87
|
+
for await (const chunk of stream) chunks.push(chunk);
|
|
88
|
+
return Buffer.concat(chunks);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
get available() {
|
|
92
|
+
return !!baileys;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
snapshot() {
|
|
96
|
+
return {
|
|
97
|
+
status: this.connected ? 'connected' : this.sock ? 'connecting' : 'disconnected',
|
|
98
|
+
user: this.user,
|
|
99
|
+
available: this.available,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
_emitStatus(s) {
|
|
104
|
+
this.emit({ type: 'status', ...s });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async start() {
|
|
108
|
+
if (!baileys) throw new Error('Baileys kurulu değil: npm i @whiskeysockets/baileys qrcode');
|
|
109
|
+
if (this.sock) return;
|
|
110
|
+
this.stopping = false;
|
|
111
|
+
|
|
112
|
+
const { default: makeWASocket, useMultiFileAuthState, DisconnectReason, fetchLatestBaileysVersion } = baileys;
|
|
113
|
+
fs.mkdirSync(this.authDir, { recursive: true });
|
|
114
|
+
|
|
115
|
+
const { state, saveCreds } = await useMultiFileAuthState(this.authDir);
|
|
116
|
+
let version;
|
|
117
|
+
try {
|
|
118
|
+
({ version } = await fetchLatestBaileysVersion());
|
|
119
|
+
} catch {
|
|
120
|
+
version = undefined;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
this.sock = makeWASocket({
|
|
124
|
+
version,
|
|
125
|
+
auth: state,
|
|
126
|
+
printQRInTerminal: false,
|
|
127
|
+
browser: ['Beast Agent', 'Chrome', '1.0.0'],
|
|
128
|
+
syncFullHistory: false,
|
|
129
|
+
markOnlineOnConnect: true, // telefonda ajan çevrimiçi görünsün
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
this.sock.ev.on('creds.update', saveCreds);
|
|
133
|
+
|
|
134
|
+
this.sock.ev.on('connection.update', async (u) => {
|
|
135
|
+
try {
|
|
136
|
+
if (u.qr) {
|
|
137
|
+
const dataUrl = await QRCode.toDataURL(u.qr, { margin: 1, width: 240 });
|
|
138
|
+
this.connected = false;
|
|
139
|
+
this._emitStatus({ status: 'qr', qr: dataUrl });
|
|
140
|
+
}
|
|
141
|
+
if (u.connection === 'open') {
|
|
142
|
+
this.connected = true;
|
|
143
|
+
const su = this.sock.user || {};
|
|
144
|
+
this.user = [su.name || su.verifiedName || '', su.id ? String(su.id).split(':')[0] : '']
|
|
145
|
+
.filter(Boolean)
|
|
146
|
+
.join(' · ');
|
|
147
|
+
/* mention/reply kontrolü için ham id (örn 1234:56@s.whatsapp.net) */
|
|
148
|
+
this._userIdRaw = String(su.id || '');
|
|
149
|
+
this._emitStatus({ status: 'connected', user: this.user });
|
|
150
|
+
this.sock.sendPresenceUpdate('available').catch(() => {});
|
|
151
|
+
this._subscribePresence().catch(() => {});
|
|
152
|
+
}
|
|
153
|
+
if (u.connection === 'close') {
|
|
154
|
+
const code = u.lastDisconnect && u.lastDisconnect.error &&
|
|
155
|
+
u.lastDisconnect.error.output && u.lastDisconnect.error.output.statusCode;
|
|
156
|
+
const loggedOut = code === DisconnectReason.loggedOut;
|
|
157
|
+
this.sock = null;
|
|
158
|
+
if (this.stopping) {
|
|
159
|
+
this.connected = false;
|
|
160
|
+
this._emitStatus({ status: 'disconnected' });
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (loggedOut) {
|
|
164
|
+
try {
|
|
165
|
+
fs.rmSync(this.authDir, { recursive: true, force: true });
|
|
166
|
+
} catch {}
|
|
167
|
+
this.connected = false;
|
|
168
|
+
this.user = null;
|
|
169
|
+
this._emitStatus({ status: 'logged-out' });
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
this._emitStatus({ status: 'reconnecting' });
|
|
173
|
+
clearTimeout(this.reconnectTimer);
|
|
174
|
+
this.reconnectTimer = setTimeout(() => this.start().catch(() => {}), 3000);
|
|
175
|
+
}
|
|
176
|
+
} catch (e) {
|
|
177
|
+
this._emitStatus({ status: 'error', error: String((e && e.message) || e) });
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
this.sock.ev.on('messages.upsert', (m) => this._handleMessages(m));
|
|
182
|
+
|
|
183
|
+
/* Teslim/okundu takibi — sadece kendi gönderdiklerimiz izlenir */
|
|
184
|
+
this.sock.ev.on('messages.update', (ups) => this._handleStatusUpdates(ups));
|
|
185
|
+
this.sock.ev.on('message-receipt.update', (rs) => this._handleReceipts(rs));
|
|
186
|
+
|
|
187
|
+
/* Karşı tarafın çevrimiçi/yazıyor durumu — izlenen sohbetler için */
|
|
188
|
+
this.sock.ev.on('presence.update', (u) => this._handlePresence(u));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
_handlePresence(u) {
|
|
192
|
+
try {
|
|
193
|
+
if (!u || !u.id) return;
|
|
194
|
+
const presences = u.presences || {};
|
|
195
|
+
for (const [pid, p] of Object.entries(presences)) {
|
|
196
|
+
const st = p && p.lastKnownPresence;
|
|
197
|
+
if (!st || !(st in PRESENCE_LABELS)) continue;
|
|
198
|
+
this.emit({
|
|
199
|
+
type: 'presence',
|
|
200
|
+
jid: u.id,
|
|
201
|
+
participant: pid,
|
|
202
|
+
presence: st,
|
|
203
|
+
label: PRESENCE_LABELS[st],
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
} catch {}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
_trackOutgoing(jid, preview, ret) {
|
|
210
|
+
try {
|
|
211
|
+
const id = ret && ret.key && ret.key.id;
|
|
212
|
+
if (!id) return;
|
|
213
|
+
if (this._tracked.size >= TRACK_CAP) {
|
|
214
|
+
const first = this._tracked.keys().next().value;
|
|
215
|
+
this._tracked.delete(first);
|
|
216
|
+
}
|
|
217
|
+
this._tracked.set(id, { jid: String(jid || ''), preview: String(preview || '').slice(0, 40), ts: Date.now(), status: 1, receiptDetail: '' });
|
|
218
|
+
this.emit({ type: 'send', id, jid, preview: this._tracked.get(id).preview });
|
|
219
|
+
} catch {}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
_handleStatusUpdates(ups) {
|
|
223
|
+
for (const up of ups || []) {
|
|
224
|
+
try {
|
|
225
|
+
/* bazı Baileys sürümleri tepkiyi update içinde taşır */
|
|
226
|
+
const rmUp = (up && up.update && (up.update.reactionMessage ||
|
|
227
|
+
(up.update.message && up.update.message.reactionMessage))) || null;
|
|
228
|
+
if (rmUp) {
|
|
229
|
+
this._handleReaction({ key: up.key }, rmUp);
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
const st = up && up.update && up.update.status;
|
|
233
|
+
const id = up && up.key && up.key.id;
|
|
234
|
+
if (typeof st !== 'number' || !id) continue;
|
|
235
|
+
const t = this._tracked.get(id);
|
|
236
|
+
if (!t || st === t.status) continue; // yalnızca bizim mesajlarımız + değişim varsa
|
|
237
|
+
t.status = st;
|
|
238
|
+
this.emit({ type: 'tick', id, jid: t.jid, status: st, label: statusLabel(st), preview: t.preview });
|
|
239
|
+
} catch {}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
_handleReceipts(rs) {
|
|
244
|
+
for (const r of rs || []) {
|
|
245
|
+
try {
|
|
246
|
+
const rc = r && r.receipt;
|
|
247
|
+
const id = r && r.key && r.key.id;
|
|
248
|
+
const t = id && this._tracked.get(id);
|
|
249
|
+
if (!t || !rc) continue;
|
|
250
|
+
const bits = [];
|
|
251
|
+
if (rc.deliveryTimestamp) bits.push(`teslim=${new Date(rc.deliveryTimestamp * 1000).toISOString()}`);
|
|
252
|
+
else if (rc.deliveredAt) bits.push(`teslim=${new Date(rc.deliveredAt).toISOString()}`);
|
|
253
|
+
if (rc.readTimestamp) bits.push(`okundu=${new Date(rc.readTimestamp * 1000).toISOString()}`);
|
|
254
|
+
else if (rc.readAt) bits.push(`okundu=${new Date(rc.readAt).toISOString()}`);
|
|
255
|
+
if (rc.playedTimestamp) bits.push(`çalındı=${new Date(rc.playedTimestamp * 1000).toISOString()}`);
|
|
256
|
+
else if (rc.playedAt) bits.push(`çalındı=${new Date(rc.playedAt).toISOString()}`);
|
|
257
|
+
const detail = bits.join(' ');
|
|
258
|
+
if (!detail || detail === t.receiptDetail) continue;
|
|
259
|
+
t.receiptDetail = detail;
|
|
260
|
+
this.emit({ type: 'receipt', id, jid: t.jid, preview: t.preview, detail });
|
|
261
|
+
} catch {}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
_handleMessages(m) {
|
|
266
|
+
if (m.type !== 'notify') return;
|
|
267
|
+
for (const msg of m.messages || []) {
|
|
268
|
+
/* tepkiler ayrı kanal: onay kapısı bunları dinler */
|
|
269
|
+
const rm = msg.message && msg.message.reactionMessage;
|
|
270
|
+
if (rm) {
|
|
271
|
+
this._handleReaction(msg, rm);
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
this._processIncoming(msg).catch(() => {});
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
_handleReaction(msg, rm) {
|
|
279
|
+
try {
|
|
280
|
+
if (!rm || !rm.key || !rm.key.id) return;
|
|
281
|
+
if (!this.onReaction) {
|
|
282
|
+
this.emit({ type: 'reaction-unhandled', targetId: String(rm.key.id), reason: 'onReaction kanca yok' });
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
/* Baileys bazı sürümlerde tepkiyi kendi mesajı gibi işaretleyebiliyor;
|
|
286
|
+
bizim onay kartımıza gelen tepkilerden kendi attıklarımız zaten yoktur */
|
|
287
|
+
const jid = String(rm.key.remoteJid || msg.key.remoteJid || '');
|
|
288
|
+
if (!jid) return;
|
|
289
|
+
let senderNum = '';
|
|
290
|
+
const raw = msg.key.participant || msg.key.remoteJidAlt || jid;
|
|
291
|
+
const num = String(raw).split('@')[0].split(':')[0];
|
|
292
|
+
if (/^\d+$/.test(num)) senderNum = num;
|
|
293
|
+
const emoji = String(rm.text || '');
|
|
294
|
+
waLogSafe(`reaction event ← target=${String(rm.key.id)} emoji=${JSON.stringify(emoji)} chat=${jid} sender=+${senderNum}`);
|
|
295
|
+
this.emit({
|
|
296
|
+
type: 'reaction',
|
|
297
|
+
jid,
|
|
298
|
+
targetId: String(rm.key.id),
|
|
299
|
+
emoji,
|
|
300
|
+
sender: senderNum,
|
|
301
|
+
});
|
|
302
|
+
this.onReaction(jid, String(rm.key.id), emoji, senderNum);
|
|
303
|
+
} catch {}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/* Tek mesajı işle: metin + medya (resim/ses/belge) çıkarımı.
|
|
307
|
+
Grup sohbetleri (@g.us) de kabul edilir; payload.isGroup ile işaretlenir,
|
|
308
|
+
bot @mention edilmişse payload.mentioned=true gelir. */
|
|
309
|
+
async _processIncoming(msg) {
|
|
310
|
+
try {
|
|
311
|
+
const jid = msg.key && msg.key.remoteJid;
|
|
312
|
+
if (!jid || msg.key.fromMe) return;
|
|
313
|
+
if (jid === 'status@broadcast' || jid.endsWith('@newsletter')) return;
|
|
314
|
+
const isGroup = jid.endsWith('@g.us');
|
|
315
|
+
const participantJid = isGroup && msg.key.participant ? String(msg.key.participant) : '';
|
|
316
|
+
const mm = msg.message || {};
|
|
317
|
+
const text =
|
|
318
|
+
mm.conversation ||
|
|
319
|
+
(mm.extendedTextMessage && mm.extendedTextMessage.text) ||
|
|
320
|
+
(mm.imageMessage && mm.imageMessage.caption) ||
|
|
321
|
+
(mm.documentMessage && mm.documentMessage.caption) ||
|
|
322
|
+
'';
|
|
323
|
+
const t = String(text || '').trim();
|
|
324
|
+
|
|
325
|
+
// gruplarda bot'un kendisi @mention edilmiş mi
|
|
326
|
+
let mentioned = false;
|
|
327
|
+
if (isGroup) {
|
|
328
|
+
const ci = mm.extendedTextMessage && mm.extendedTextMessage.contextInfo;
|
|
329
|
+
const men = (ci && ci.mentionedJid) || [];
|
|
330
|
+
const meBase = String(this._userIdRaw || '').split('@')[0].split(':')[0];
|
|
331
|
+
for (const m of men) {
|
|
332
|
+
if (meBase && String(m).split('?')[0].split(':')[0].includes(meBase)) {
|
|
333
|
+
mentioned = true;
|
|
334
|
+
break;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
if (!mentioned && ci && ci.participant && meBase && String(ci.participant).includes(meBase)) {
|
|
338
|
+
mentioned = true; // mesajımıza reply verilmiş
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// medya çıkarımı (varsa)
|
|
343
|
+
let media = null;
|
|
344
|
+
const MAX_MEDIA = 20 * 1024 * 1024;
|
|
345
|
+
try {
|
|
346
|
+
if (mm.imageMessage) {
|
|
347
|
+
const buf = await this._mediaBuffer(mm.imageMessage, 'image');
|
|
348
|
+
if (buf && buf.length && buf.length <= MAX_MEDIA) {
|
|
349
|
+
media = { kind: 'image', buf, mimetype: mm.imageMessage.mimetype || 'image/jpeg', name: 'gorsel.jpg' };
|
|
350
|
+
}
|
|
351
|
+
} else if (mm.audioMessage) {
|
|
352
|
+
const buf = await this._mediaBuffer(mm.audioMessage, 'audio');
|
|
353
|
+
if (buf && buf.length && buf.length <= MAX_MEDIA) {
|
|
354
|
+
media = { kind: 'audio', buf, mimetype: mm.audioMessage.mimetype || 'audio/ogg; codecs=opus' };
|
|
355
|
+
}
|
|
356
|
+
} else if (mm.documentMessage) {
|
|
357
|
+
const buf = await this._mediaBuffer(mm.documentMessage, 'document');
|
|
358
|
+
if (buf && buf.length && buf.length <= MAX_MEDIA) {
|
|
359
|
+
media = { kind: 'document', buf, mimetype: mm.documentMessage.mimetype || 'application/octet-stream', name: mm.documentMessage.fileName || 'dosya' };
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
} catch {}
|
|
363
|
+
|
|
364
|
+
if (!t && !media) return;
|
|
365
|
+
// Yeni WA LID sistemi: gerçek numara remoteJidAlt'ta geliyor
|
|
366
|
+
const alt =
|
|
367
|
+
msg.key.remoteJidAlt ||
|
|
368
|
+
(msg.message && msg.message.extendedTextMessage && msg.message.extendedTextMessage.remoteJidAlt) ||
|
|
369
|
+
'';
|
|
370
|
+
let senderNum = '';
|
|
371
|
+
const pnSrc = String(alt || '').startsWith('alt:') ? String(alt).slice(4) : String(alt || '');
|
|
372
|
+
if (/^\d+@s\.whatsapp\.net$/.test(pnSrc)) {
|
|
373
|
+
senderNum = pnSrc.split('@')[0];
|
|
374
|
+
} else if (/^\d+@(s\.whatsapp\.net)?$/.test(jid)) {
|
|
375
|
+
senderNum = jid.split('@')[0].split(':')[0];
|
|
376
|
+
}
|
|
377
|
+
if (!senderNum) {
|
|
378
|
+
senderNum = (participantJid || jid).split('@')[0].split(':')[0];
|
|
379
|
+
if (!/^\d+$/.test(senderNum)) senderNum = '';
|
|
380
|
+
}
|
|
381
|
+
this.onIncoming(jid, {
|
|
382
|
+
text: t,
|
|
383
|
+
media,
|
|
384
|
+
isGroup,
|
|
385
|
+
participant: participantJid,
|
|
386
|
+
mentioned,
|
|
387
|
+
}, senderNum);
|
|
388
|
+
} catch {}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/* Cevap: başarı → { id }, başarısızlık → false (hata emit edilir, yutulmaz) */
|
|
392
|
+
async send(jid, text) {
|
|
393
|
+
if (!this.sock || !this.connected) return false;
|
|
394
|
+
const txt = String(text).slice(0, 3500);
|
|
395
|
+
let ret;
|
|
396
|
+
try {
|
|
397
|
+
ret = await this.sock.sendMessage(jid, { text: txt });
|
|
398
|
+
} catch (e) {
|
|
399
|
+
this.emit({
|
|
400
|
+
type: 'send-error',
|
|
401
|
+
jid,
|
|
402
|
+
preview: txt.slice(0, 40),
|
|
403
|
+
error: String((e && e.message) || e),
|
|
404
|
+
});
|
|
405
|
+
return false;
|
|
406
|
+
}
|
|
407
|
+
this._trackOutgoing(jid, txt, ret);
|
|
408
|
+
const id = ret && ret.key && ret.key.id ? String(ret.key.id) : '';
|
|
409
|
+
return id ? { id } : true;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/* Sesli not olarak yanıtla (TTS çıktısı mp3 buffer) */
|
|
413
|
+
async sendAudio(jid, audioBuf) {
|
|
414
|
+
if (!this.sock || !this.connected || !audioBuf) return false;
|
|
415
|
+
try {
|
|
416
|
+
const ret = await this.sock.sendMessage(jid, { audio: audioBuf, ptt: true, mimetype: 'audio/mpeg' });
|
|
417
|
+
this._trackOutgoing(jid, '[sesli yanıt]', ret);
|
|
418
|
+
return true;
|
|
419
|
+
} catch (e) {
|
|
420
|
+
this.emit({ type: 'send-error', jid, preview: '[ses]', error: String((e && e.message) || e) });
|
|
421
|
+
return false;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/* Görsel gönder (jpeg/png buffer) — /screenshot vb. için */
|
|
426
|
+
async sendImage(jid, imgBuf, caption) {
|
|
427
|
+
if (!this.sock || !this.connected || !imgBuf) return false;
|
|
428
|
+
try {
|
|
429
|
+
const ret = await this.sock.sendMessage(jid, {
|
|
430
|
+
image: imgBuf,
|
|
431
|
+
caption: String(caption || '').slice(0, 800),
|
|
432
|
+
});
|
|
433
|
+
this._trackOutgoing(jid, '[görsel]', ret);
|
|
434
|
+
return true;
|
|
435
|
+
} catch (e) {
|
|
436
|
+
this.emit({ type: 'send-error', jid, preview: '[görsel]', error: String((e && e.message) || e) });
|
|
437
|
+
return false;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/* Belge gönder (pdf/doc/xxx buffer) — ajanın send_file aracı için */
|
|
442
|
+
async sendFile(jid, buf, fileName, caption, mimetype) {
|
|
443
|
+
if (!this.sock || !this.connected || !buf) return false;
|
|
444
|
+
try {
|
|
445
|
+
const ret = await this.sock.sendMessage(jid, {
|
|
446
|
+
document: buf,
|
|
447
|
+
fileName: String(fileName || 'dosya'),
|
|
448
|
+
mimetype: mimetype || 'application/octet-stream',
|
|
449
|
+
caption: String(caption || '').slice(0, 800),
|
|
450
|
+
});
|
|
451
|
+
this._trackOutgoing(jid, '[dosya] ' + fileName, ret);
|
|
452
|
+
return true;
|
|
453
|
+
} catch (e) {
|
|
454
|
+
this.emit({ type: 'send-error', jid, preview: '[dosya]', error: String((e && e.message) || e) });
|
|
455
|
+
return false;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/* Karşı chatta durum bildirimi: on=true → "yazıyor…", on=false → durdu */
|
|
460
|
+
async setComposing(jid, on) {
|
|
461
|
+
if (!this.sock || !this.connected) return;
|
|
462
|
+
try {
|
|
463
|
+
await this.sock.sendPresenceUpdate(on ? 'composing' : 'paused', jid);
|
|
464
|
+
} catch {}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
async stop() {
|
|
468
|
+
this.stopping = true;
|
|
469
|
+
clearTimeout(this.reconnectTimer);
|
|
470
|
+
const s = this.sock;
|
|
471
|
+
this.sock = null;
|
|
472
|
+
this.connected = false;
|
|
473
|
+
this.user = null;
|
|
474
|
+
if (s) {
|
|
475
|
+
try {
|
|
476
|
+
await s.logout();
|
|
477
|
+
} catch {
|
|
478
|
+
try {
|
|
479
|
+
s.end();
|
|
480
|
+
} catch {}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
this._emitStatus({ status: 'disconnected' });
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
async resetAuth() {
|
|
487
|
+
await this.stop();
|
|
488
|
+
try {
|
|
489
|
+
fs.rmSync(this.authDir, { recursive: true, force: true });
|
|
490
|
+
} catch {}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
module.exports = { WhatsAppBridge, statusLabel };
|