beast-agent 0.26.3 → 0.26.5

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "beast-agent",
3
3
  "productName": "Beast Agent",
4
- "version": "0.26.3",
4
+ "version": "0.26.5",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
@@ -48,6 +48,20 @@ function bodySnippet(data) {
48
48
  return s ? ` — gövde: ${s.slice(0, 140)}` : '';
49
49
  }
50
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
+
51
65
  class DiscordBridge {
52
66
  constructor({ token, emit, onIncoming }) {
53
67
  this.token = String(token || '').trim();
@@ -101,14 +115,17 @@ class DiscordBridge {
101
115
  const data = await res.text();
102
116
  let j = null;
103
117
  try { j = data ? JSON.parse(data) : null; } catch {}
104
- if (res.ok) return j;
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
+ }
105
122
  throw new Error(
106
123
  `discord ${path}: HTTP ${res.status}${j && j.message ? ' ' + j.message : ''}${bodySnippet(data)}`
107
124
  );
108
125
  } catch (e) {
109
126
  /* kendi formatladığımız API hatasıysa yukarı taşı; transport hatasıysa
110
127
  Node https yoluna düş (orada esnek TLS şansı var) */
111
- if (/^discord /.test(String((e && e.message) || ''))) throw e;
128
+ if (/^discord |Discord bu ağ/.test(String((e && e.message) || ''))) throw e;
112
129
  }
113
130
  }
114
131
 
@@ -134,6 +151,9 @@ class DiscordBridge {
134
151
  let j = null;
135
152
  try { j = data ? JSON.parse(data) : null; } catch {}
136
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
+ }
137
157
  if (res.statusCode >= 200 && res.statusCode < 300) {
138
158
  return reject(new Error(`discord ${path}: bozuk yanıt (HTTP ${res.statusCode}${ctype ? ', ' + ctype : ''})${bodySnippet(data)}`));
139
159
  }
@@ -337,8 +337,20 @@ class WhatsAppBridge {
337
337
  async _processIncoming(msg) {
338
338
  try {
339
339
  const jid = msg.key && msg.key.remoteJid;
340
- if (!jid || msg.key.fromMe) return;
340
+ if (!jid) return;
341
341
  if (jid === 'status@broadcast' || jid.endsWith('@newsletter')) return;
342
+ /* botun kendi kimliği: telefon base'i + LID base'i (LID dönemi) */
343
+ const meBase = String(this._userIdRaw || '').split('@')[0].split(':')[0];
344
+ const lidBase = String((this.sock && this.sock.user && this.sock.user.lid) || '').split('@')[0].split(':')[0];
345
+ /* KENDİ KENDİNE SOHBET (Saved Messages): kullanıcı kendi numarasını izinliye
346
+ ekleyip kendi sohbetinden bota yazabilsin. Bu sohbetteki HER mesaj fromMe
347
+ gelir; botun KENDİ gönderdiği mesajlar (_tracked) filtrelenir → döngü yok.
348
+ Diğer sohbetlerdeki elle yazılan fromMe mesajları bot etkilenmesin diye yutulur. */
349
+ const selfChat = !!(meBase && (jid === meBase + '@s.whatsapp.net' || (lidBase && jid === lidBase + '@lid')));
350
+ if (msg.key.fromMe) {
351
+ if (this._tracked.has(String(msg.key.id))) return; // botun kendi cevabı — echo
352
+ if (!selfChat) return;
353
+ }
342
354
  const isGroup = jid.endsWith('@g.us');
343
355
  const participantJid = isGroup && msg.key.participant ? String(msg.key.participant) : '';
344
356
  const mm = msg.message || {};
@@ -351,20 +363,35 @@ class WhatsAppBridge {
351
363
  const t = String(text || '').trim();
352
364
 
353
365
  // gruplarda bot'un kendisi @mention edilmiş mi
366
+ /* LİD DÖNEMİ UYARISI: mentionedJid artık çoğu zaman botun @lid numarasıyla
367
+ gelir (telefon JID'i DEĞİL) — bu yüzden yalnız meBase ile karşılaştırma
368
+ mention'ı ıskalıyordu. Şimdi: botun telefon base'i + LID base'i ikisiyle
369
+ de eşitlik aranır; contextInfo görsel/video/belge caption'larından da alınır. */
354
370
  let mentioned = false;
355
371
  if (isGroup) {
356
- const ci = mm.extendedTextMessage && mm.extendedTextMessage.contextInfo;
372
+ const ci =
373
+ (mm.extendedTextMessage && mm.extendedTextMessage.contextInfo) ||
374
+ (mm.imageMessage && mm.imageMessage.contextInfo) ||
375
+ (mm.videoMessage && mm.videoMessage.contextInfo) ||
376
+ (mm.documentMessage && mm.documentMessage.contextInfo) ||
377
+ null;
357
378
  const men = (ci && ci.mentionedJid) || [];
358
- const meBase = String(this._userIdRaw || '').split('@')[0].split(':')[0];
379
+ const baseOf = (j) => String(j || '').split('?')[0].split(':')[0].split('@')[0];
359
380
  for (const m of men) {
360
- if (meBase && String(m).split('?')[0].split(':')[0].includes(meBase)) {
381
+ const base = baseOf(m);
382
+ if (base && ((meBase && base === meBase) || (lidBase && base === lidBase))) {
361
383
  mentioned = true;
362
384
  break;
363
385
  }
364
386
  }
365
- if (!mentioned && ci && ci.participant && meBase && String(ci.participant).includes(meBase)) {
366
- mentioned = true; // mesajımıza reply verilmiş
387
+ if (!mentioned && ci && ci.participant) {
388
+ const pBase = baseOf(ci.participant);
389
+ if ((meBase && pBase === meBase) || (lidBase && pBase === lidBase)) {
390
+ mentioned = true; // mesajımıza reply verilmiş
391
+ }
367
392
  }
393
+ /* metin yedeği: @<bot numarası> elle yazılmışsa da mention sayılır */
394
+ if (!mentioned && meBase && t.includes('@' + meBase)) mentioned = true;
368
395
  }
369
396
 
370
397
  // medya çıkarımı (varsa)
@@ -406,6 +433,9 @@ class WhatsAppBridge {
406
433
  senderNum = (participantJid || jid).split('@')[0].split(':')[0];
407
434
  if (!/^\d+$/.test(senderNum)) senderNum = '';
408
435
  }
436
+ /* kendi kendine sohbette gönderen = botun telefon numarası — izin listesi
437
+ telefon numarasıyla eşleşsin (LID self-chat'te jid @lid olur) */
438
+ if (selfChat && meBase) senderNum = meBase;
409
439
  this.onIncoming(jid, {
410
440
  text: t,
411
441
  media,
@@ -195,8 +195,9 @@
195
195
  it_connect: 'QR ile Bağlan',
196
196
  it_disconnect: 'Kes',
197
197
  it_reset_pair: 'Eşlemeyi Sıfırla',
198
- it_groups_on: 'WhatsApp gruplarında çalış — bot @mention edilince cevap verir',
199
- it_groups_all: 'Mention şart değil: grup içindeki her mesaja karış',
198
+ it_groups_on: 'WhatsApp gruplarında çalış',
199
+ it_groups_mention: 'Sadece @mention edilince cevap ver (grupta botu etiketle)',
200
+ it_groups_all: 'Grubun her mesajına karış',
200
201
  it_allow_label: 'İzinli numaralar',
201
202
  it_name_ph: 'İsim (zorunlu)',
202
203
  it_num_ph: 'Numara 905xxxxxxxxx',
@@ -215,6 +216,7 @@
215
216
  it_dc_token_bad: 'Token doğrulanamadı — Developer Portal\u2019dan aldığın tokenı kontrol et',
216
217
  it_dc_id_ph: 'Kullanıcı ID veya @kullanıcı_adı',
217
218
  it_dc_note: 'İzin listesi boşsa kimse cevap almaz. Bot oluşturma: discord.com/developers → New Application → Bot → Token. "MESSAGE CONTENT INTENT"i AÇ (yoksa mesajlar boş gelir). Sunucu daveti: OAuth2 → bot yetkisiyle. Kendi ID\u2019ni öğrenmek için geliştirici modunu açıp kendine sağ tıkla → ID\u2019yi kopyala.',
219
+ it_dc_vpn: 'Türkiye\u2019de Discord erişimi zaman zaman engellenir — bağlanamıyorsan VPN açıp tekrar dene.',
218
220
  ev_h2: 'Olay Merkezi',
219
221
  ev_sub: 'Cron\u2019suz canlı olaylar — kaynakları aç, abonelikleri agent kendisi kurar (event_subscribe). Tetiklenen olay ilgili sohbete düşer, cevap WhatsApp\u2019a gider.',
220
222
  ev_on: 'Olay merkezi açık',
@@ -722,8 +724,9 @@
722
724
  it_connect: 'Connect via QR',
723
725
  it_disconnect: 'Disconnect',
724
726
  it_reset_pair: 'Reset pairing',
725
- it_groups_on: 'Work in WhatsApp groups — bot replies when @mentioned',
726
- it_groups_all: 'Mention not required: respond to every group message',
727
+ it_groups_on: 'Work in WhatsApp groups',
728
+ it_groups_mention: 'Reply only when @mentioned (tag the bot in the group)',
729
+ it_groups_all: 'Respond to every group message',
727
730
  it_allow_label: 'Allowed numbers',
728
731
  it_name_ph: 'Name (required)',
729
732
  it_num_ph: 'Number 905xxxxxxxxx',
@@ -742,6 +745,7 @@
742
745
  it_dc_token_bad: 'Token could not be verified — check the token from the Developer Portal',
743
746
  it_dc_id_ph: 'User ID or @username',
744
747
  it_dc_note: 'Empty allow list = nobody gets a reply. Create a bot: discord.com/developers → New Application → Bot → Token. Turn ON "MESSAGE CONTENT INTENT". Invite via OAuth2 bot URL. To learn your ID: enable developer mode, right-click yourself → Copy ID.',
748
+ it_dc_vpn: 'Discord may be blocked on some networks — if it won\u2019t connect, turn on a VPN and retry.',
745
749
  ev_h2: 'Event Center',
746
750
  ev_sub: 'Live events without cron — open sources, the agent sets up subscriptions itself (event_subscribe). The triggered event drops into the relevant chat, the reply goes to WhatsApp.',
747
751
  ev_on: 'Event center on',
@@ -1498,7 +1498,8 @@ async function renderIntegrationsPane() {
1498
1498
  </div>
1499
1499
  <div style="height:14px"></div>
1500
1500
  <label class="lock-row"><input type="checkbox" id="waGroupsOn" ${g.enabled ? 'checked' : ''}/><span>${_t('it_groups_on')}</span></label>
1501
- <label class="lock-row" style="${g.enabled ? '' : 'opacity:.45'}"><input type="checkbox" id="waGroupsAll" ${g.mentionOnly === false ? 'checked' : ''}/><span>${_t('it_groups_all')}</span></label>
1501
+ <label class="lock-row" style="${g.enabled ? '' : 'opacity:.45'}"><input type="radio" name="waGroupMode" id="waGroupsMention" ${g.mentionOnly !== false ? 'checked' : ''}/><span>${_t('it_groups_mention')}</span></label>
1502
+ <label class="lock-row" style="${g.enabled ? '' : 'opacity:.45'}"><input type="radio" name="waGroupMode" id="waGroupsAll" ${g.mentionOnly === false ? 'checked' : ''}/><span>${_t('it_groups_all')}</span></label>
1502
1503
  <div class="divider"></div>
1503
1504
  <label class="mem-label" style="margin-top:0">${_t('it_allow_label')}</label>
1504
1505
  <div id="waAllowChips" class="chips-inline"></div>
@@ -1557,6 +1558,7 @@ async function renderIntegrationsPane() {
1557
1558
  <div class="wa-actions" style="margin-top:6px">
1558
1559
  <button id="dcStopBtn" class="btn ghost">${_t('it_disconnect')}</button>
1559
1560
  </div>
1561
+ <div class="sub" style="margin-top:6px">ⓘ ${_t('it_dc_vpn')}</div>
1560
1562
  <div class="divider"></div>
1561
1563
  <label class="mem-label" style="margin-top:0">${_t('it_allow_label')} — Discord</label>
1562
1564
  <div id="dcAllowChips" class="chips-inline"></div>
@@ -1570,16 +1572,25 @@ async function renderIntegrationsPane() {
1570
1572
  </div>`;
1571
1573
 
1572
1574
  const waGroupsOn = $('#waGroupsOn');
1575
+ const waGroupsMention = $('#waGroupsMention');
1573
1576
  const waGroupsAll = $('#waGroupsAll');
1577
+ /* iki mod BİRBİRİNE HARIÇTİR: ya @mention ya her mesaja karışma (radio) */
1578
+ const syncGroupMode = () => {
1579
+ const op = waGroupsOn.checked ? '' : '.45';
1580
+ if (waGroupsMention) waGroupsMention.parentElement.style.opacity = op;
1581
+ if (waGroupsAll) waGroupsAll.parentElement.style.opacity = op;
1582
+ };
1574
1583
  const saveGroups = async () => {
1575
1584
  const r = await beast.waSetGroups({ enabled: waGroupsOn.checked, mentionOnly: !waGroupsAll.checked });
1576
1585
  toast(r.enabled ? (r.mentionOnly ? 'Gruplar açık — @mention bekler' : 'Gruplar açık — tüm mesajlara karışır') : 'Gruplar kapalı');
1577
1586
  };
1578
1587
  waGroupsOn.addEventListener('change', async () => {
1579
- waGroupsAll.parentElement.style.opacity = waGroupsOn.checked ? '' : '.45';
1588
+ syncGroupMode();
1580
1589
  await saveGroups();
1581
1590
  });
1591
+ waGroupsMention.addEventListener('change', saveGroups);
1582
1592
  waGroupsAll.addEventListener('change', saveGroups);
1593
+ syncGroupMode();
1583
1594
 
1584
1595
  $('#waStartBtn').addEventListener('click', async () => {
1585
1596
  toast('Bağlanıyor…');