@whanext/core 0.17.0 → 0.17.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.17.1
4
+
5
+ ### Fixed
6
+
7
+ - Compatibilidade com o novo formato criptografado de edições do WhatsApp (`secretEncryptedMessage`).
8
+ - O provider interpreta `targetMessageKey.fromMe` pela perspectiva do editor, deriva a chave com `messageSecret` e descriptografa edições com AES-256-GCM.
9
+ - Edições em conversas privadas preservam a chave local da mensagem original após a descriptografia.
10
+ - Edições consecutivas preservam o `messageSecret` no cache para que a versão anterior continue disponível.
11
+ - `messageEdited` continua com a mesma API pública da 0.17.0.
12
+
3
13
  ## 0.17.0
4
14
 
5
15
  ### Adicionado
package/README.md CHANGED
@@ -175,7 +175,7 @@ O evento também informa `deletedById` quando o WhatsApp fornece a identidade re
175
175
 
176
176
  ## Mensagens editadas
177
177
 
178
- Edições recebidas pelo WhatsApp são expostas pelo evento `messageEdited`. Quando a versão anterior ainda estiver no cache recente, o payload inclui `previous`; `message` contém a versão atual.
178
+ Edições recebidas pelo WhatsApp são expostas pelo evento `messageEdited`. A partir da 0.17.1, o provider inclui suporte ao envelope criptografado `secretEncryptedMessage` usado atualmente pelo WhatsApp para edições. Quando a versão anterior ainda estiver no cache recente, o payload inclui `previous`; `message` contém a versão atual.
179
179
 
180
180
  ```ts
181
181
  app.on('messageEdited', async ({ previous, message, editedByMe }) => {
package/dist/index.js CHANGED
@@ -2305,7 +2305,7 @@ import {
2305
2305
  generateWAMessageFromContent,
2306
2306
  isJidGroup,
2307
2307
  makeWASocket,
2308
- proto,
2308
+ proto as proto2,
2309
2309
  useMultiFileAuthState
2310
2310
  } from "@whiskeysockets/baileys";
2311
2311
 
@@ -2587,6 +2587,179 @@ function toDate(value) {
2587
2587
  return new Date(seconds * 1e3);
2588
2588
  }
2589
2589
 
2590
+ // src/provider/baileys/secret-edit.ts
2591
+ import { createDecipheriv, hkdfSync } from "crypto";
2592
+ import {
2593
+ jidNormalizedUser,
2594
+ normalizeMessageContent as normalizeMessageContent2,
2595
+ proto
2596
+ } from "@whiskeysockets/baileys";
2597
+ function isSecretEncryptedEdit(message) {
2598
+ const content = normalizeMessageContent2(message.message);
2599
+ return content?.secretEncryptedMessage?.secretEncType === proto.Message.SecretEncryptedMessage.SecretEncType.MESSAGE_EDIT;
2600
+ }
2601
+ function secretEditTargetKey(message) {
2602
+ const content = normalizeMessageContent2(message.message);
2603
+ const secret = content?.secretEncryptedMessage;
2604
+ if (secret?.secretEncType !== proto.Message.SecretEncryptedMessage.SecretEncType.MESSAGE_EDIT || !secret.targetMessageKey?.id) {
2605
+ return void 0;
2606
+ }
2607
+ return secret.targetMessageKey;
2608
+ }
2609
+ function decryptSecretEncryptedEdit(envelope, original, meId, meLid) {
2610
+ const content = normalizeMessageContent2(envelope.message);
2611
+ const secret = content?.secretEncryptedMessage;
2612
+ const targetKey = secret?.targetMessageKey;
2613
+ if (secret?.secretEncType !== proto.Message.SecretEncryptedMessage.SecretEncType.MESSAGE_EDIT || !targetKey?.id || !secret.encPayload?.length || secret.encIv?.length !== 12) {
2614
+ return void 0;
2615
+ }
2616
+ const messageSecret = findMessageSecret(original.message);
2617
+ if (messageSecret?.length !== 32) {
2618
+ return void 0;
2619
+ }
2620
+ const editorCandidates = envelope.key.fromMe ? uniqueUserJids([meId, meLid]) : uniqueUserJids([
2621
+ envelope.key.participant,
2622
+ envelope.key.participantAlt,
2623
+ envelope.key.remoteJid,
2624
+ envelope.key.remoteJidAlt
2625
+ ]);
2626
+ const originalSenderCandidates = targetKey.fromMe ? editorCandidates : isUserJid(envelope.key.remoteJid) ? uniqueUserJids([
2627
+ targetKey.remoteJid,
2628
+ targetKey.remoteJidAlt,
2629
+ original.key.remoteJid,
2630
+ original.key.remoteJidAlt
2631
+ ]) : uniqueUserJids([
2632
+ targetKey.participant,
2633
+ targetKey.participantAlt,
2634
+ original.key.participant,
2635
+ original.key.participantAlt
2636
+ ]);
2637
+ const fallbackOriginalCandidates = original.key.fromMe ? uniqueUserJids([meId, meLid]) : uniqueUserJids([
2638
+ original.key.participant,
2639
+ original.key.participantAlt,
2640
+ original.key.remoteJid,
2641
+ original.key.remoteJidAlt
2642
+ ]);
2643
+ const senders = uniqueUserJids([
2644
+ ...originalSenderCandidates,
2645
+ ...fallbackOriginalCandidates
2646
+ ]);
2647
+ if (!editorCandidates.length || !senders.length) {
2648
+ return void 0;
2649
+ }
2650
+ let decoded;
2651
+ for (const originalSender of senders) {
2652
+ for (const editor of editorCandidates) {
2653
+ try {
2654
+ decoded = decryptPayload(
2655
+ targetKey.id,
2656
+ originalSender,
2657
+ editor,
2658
+ messageSecret,
2659
+ secret.encIv,
2660
+ secret.encPayload
2661
+ );
2662
+ break;
2663
+ } catch {
2664
+ continue;
2665
+ }
2666
+ }
2667
+ if (decoded) {
2668
+ break;
2669
+ }
2670
+ }
2671
+ const protocol = decoded?.protocolMessage;
2672
+ if (protocol?.type !== proto.Message.ProtocolMessage.Type.MESSAGE_EDIT || !protocol.editedMessage || protocol.key?.id && protocol.key.id !== targetKey.id) {
2673
+ return void 0;
2674
+ }
2675
+ const edited = protocol.editedMessage;
2676
+ if (!edited.messageContextInfo?.messageSecret?.length) {
2677
+ edited.messageContextInfo = {
2678
+ ...edited.messageContextInfo,
2679
+ messageSecret
2680
+ };
2681
+ }
2682
+ return {
2683
+ message: edited,
2684
+ ...protocol.timestampMs ? { timestamp: Math.floor(numberValue(protocol.timestampMs) / 1e3) } : {}
2685
+ };
2686
+ }
2687
+ function decryptPayload(messageId, originalSender, editor, messageSecret, iv, payload) {
2688
+ const info = Buffer.concat([
2689
+ Buffer.from(messageId, "utf8"),
2690
+ Buffer.from(originalSender, "utf8"),
2691
+ Buffer.from(editor, "utf8"),
2692
+ Buffer.from("Message Edit", "utf8")
2693
+ ]);
2694
+ const key = Buffer.from(hkdfSync(
2695
+ "sha256",
2696
+ Buffer.from(messageSecret),
2697
+ Buffer.alloc(32),
2698
+ info,
2699
+ 32
2700
+ ));
2701
+ const encrypted = Buffer.from(payload);
2702
+ if (encrypted.length <= 16) {
2703
+ throw new Error("INVALID_EDIT_PAYLOAD");
2704
+ }
2705
+ const decipher = createDecipheriv("aes-256-gcm", key, Buffer.from(iv));
2706
+ decipher.setAAD(Buffer.alloc(0));
2707
+ decipher.setAuthTag(encrypted.subarray(encrypted.length - 16));
2708
+ return proto.Message.decode(Buffer.concat([
2709
+ decipher.update(encrypted.subarray(0, encrypted.length - 16)),
2710
+ decipher.final()
2711
+ ]));
2712
+ }
2713
+ function findMessageSecret(message) {
2714
+ if (!message) {
2715
+ return void 0;
2716
+ }
2717
+ const direct = message.messageContextInfo?.messageSecret;
2718
+ if (direct?.length) {
2719
+ return direct;
2720
+ }
2721
+ const normalized = normalizeMessageContent2(message);
2722
+ const normalizedSecret = normalized?.messageContextInfo?.messageSecret;
2723
+ if (normalizedSecret?.length) {
2724
+ return normalizedSecret;
2725
+ }
2726
+ const deviceSecret = message.deviceSentMessage?.message?.messageContextInfo?.messageSecret;
2727
+ if (deviceSecret?.length) {
2728
+ return deviceSecret;
2729
+ }
2730
+ return void 0;
2731
+ }
2732
+ function uniqueUserJids(values) {
2733
+ return [...new Set(
2734
+ values.map(normalizeUserJid).filter((value) => value !== void 0)
2735
+ )];
2736
+ }
2737
+ function normalizeUserJid(value) {
2738
+ if (!isUserJid(value)) {
2739
+ return void 0;
2740
+ }
2741
+ return jidNormalizedUser(value);
2742
+ }
2743
+ function isUserJid(value) {
2744
+ if (!value) {
2745
+ return false;
2746
+ }
2747
+ const normalized = jidNormalizedUser(value);
2748
+ return normalized.endsWith("@s.whatsapp.net") || normalized.endsWith("@lid") || normalized.endsWith("@hosted") || normalized.endsWith("@hosted.lid");
2749
+ }
2750
+ function numberValue(value) {
2751
+ if (typeof value === "number") {
2752
+ return value;
2753
+ }
2754
+ if (typeof value === "bigint") {
2755
+ return Number(value);
2756
+ }
2757
+ if (value && typeof value === "object" && "toString" in value) {
2758
+ return Number(String(value));
2759
+ }
2760
+ return Number(value);
2761
+ }
2762
+
2590
2763
  // src/provider/baileys/baileys-provider.ts
2591
2764
  var BaileysProvider = class {
2592
2765
  #options;
@@ -2827,7 +3000,7 @@ var BaileysProvider = class {
2827
3000
  async setMessagePin(groupId, key, pinned) {
2828
3001
  await this.#requireSocket().sendMessage(groupId, {
2829
3002
  pin: this.#toWaKey(key),
2830
- type: pinned ? proto.PinInChat.Type.PIN_FOR_ALL : proto.PinInChat.Type.UNPIN_FOR_ALL,
3003
+ type: pinned ? proto2.PinInChat.Type.PIN_FOR_ALL : proto2.PinInChat.Type.UNPIN_FOR_ALL,
2831
3004
  ...pinned ? { time: 604800 } : {}
2832
3005
  });
2833
3006
  }
@@ -2859,6 +3032,7 @@ var BaileysProvider = class {
2859
3032
  socket.ev.on("messages.upsert", ({ messages, type }) => {
2860
3033
  if (type !== "notify") return;
2861
3034
  for (const raw of messages) {
3035
+ if (this.#handleSecretEncryptedEdit(raw)) continue;
2862
3036
  if (raw.key.id && raw.message) this.#remember(raw);
2863
3037
  const quoted = extractQuotedBaileysMessage(raw);
2864
3038
  if (quoted?.key.id && quoted.message) this.#remember(quoted);
@@ -2871,13 +3045,13 @@ var BaileysProvider = class {
2871
3045
  if (!key.id || !key.remoteJid) continue;
2872
3046
  const stored = this.#messageStore.get(this.#messageStoreKey(key));
2873
3047
  if (update.message === null) {
2874
- const message2 = stored ? normalizeBaileysMessage(stored) : void 0;
3048
+ const message = stored ? normalizeBaileysMessage(stored) : void 0;
2875
3049
  const deletionKey = update.key;
2876
3050
  const deletedByMe = deletionKey?.fromMe === true;
2877
3051
  const deletedById = deletionKey?.participant ?? deletionKey?.participantAlt ?? (deletedByMe ? this.#socket?.user?.id : deletionKey?.remoteJid ?? void 0);
2878
3052
  void this.#events.emit("messageDeleted", {
2879
- key: message2?.keys ?? normalizeKey(key),
2880
- ...message2 ? { message: message2 } : {},
3053
+ key: message?.keys ?? normalizeKey(key),
3054
+ ...message ? { message } : {},
2881
3055
  deletedByMe,
2882
3056
  ...deletedById ? { deletedById } : {},
2883
3057
  deletedAt: /* @__PURE__ */ new Date()
@@ -2894,20 +3068,7 @@ var BaileysProvider = class {
2894
3068
  message: update.message,
2895
3069
  messageTimestamp: update.messageTimestamp ?? stored?.messageTimestamp ?? Math.floor(Date.now() / 1e3)
2896
3070
  };
2897
- const previous = stored ? normalizeBaileysMessage(stored) : void 0;
2898
- const message = normalizeBaileysMessage(editedRaw);
2899
- if (!message) continue;
2900
- this.#remember(editedRaw);
2901
- const editedByMe = key.fromMe === true;
2902
- const editedById = key.participant ?? key.participantAlt ?? (editedByMe ? this.#socket?.user?.id : key.remoteJid ?? void 0);
2903
- void this.#events.emit("messageEdited", {
2904
- key: message.keys,
2905
- ...previous ? { previous } : {},
2906
- message,
2907
- editedByMe,
2908
- ...editedById ? { editedById } : {},
2909
- editedAt: message.timestamp
2910
- });
3071
+ this.#emitMessageEdited(key, editedRaw, stored);
2911
3072
  }
2912
3073
  });
2913
3074
  socket.ev.on("groups.update", (groups) => {
@@ -3007,7 +3168,7 @@ var BaileysProvider = class {
3007
3168
  "WhatsApp did not expose the current account identity for the interactive message."
3008
3169
  );
3009
3170
  }
3010
- const interactiveMessage = proto.Message.InteractiveMessage.create({
3171
+ const interactiveMessage = proto2.Message.InteractiveMessage.create({
3011
3172
  ...content.title !== void 0 ? {
3012
3173
  header: {
3013
3174
  title: content.title,
@@ -3199,6 +3360,68 @@ var BaileysProvider = class {
3199
3360
  ];
3200
3361
  return known.find((value) => value === status) ?? "timeout";
3201
3362
  }
3363
+ #handleSecretEncryptedEdit(raw) {
3364
+ if (!isSecretEncryptedEdit(raw)) return false;
3365
+ const targetKey = secretEditTargetKey(raw);
3366
+ if (!targetKey?.id) return true;
3367
+ const stored = this.#findStoredMessage(targetKey);
3368
+ if (!stored) return true;
3369
+ try {
3370
+ const decrypted = decryptSecretEncryptedEdit(
3371
+ raw,
3372
+ stored,
3373
+ this.#socket?.user?.id,
3374
+ this.#socket?.user?.lid
3375
+ );
3376
+ if (!decrypted) return true;
3377
+ const key = {
3378
+ ...stored.key,
3379
+ id: targetKey.id
3380
+ };
3381
+ const editedRaw = {
3382
+ ...stored,
3383
+ key,
3384
+ message: {
3385
+ editedMessage: {
3386
+ message: decrypted.message
3387
+ }
3388
+ },
3389
+ messageTimestamp: decrypted.timestamp ?? raw.messageTimestamp ?? stored.messageTimestamp ?? Math.floor(Date.now() / 1e3)
3390
+ };
3391
+ this.#emitMessageEdited(key, editedRaw, stored);
3392
+ } catch (error) {
3393
+ this.#logger.warn("Could not decrypt the encrypted message edit.", {
3394
+ messageId: targetKey.id,
3395
+ error: error instanceof Error ? error.message : String(error)
3396
+ });
3397
+ }
3398
+ return true;
3399
+ }
3400
+ #emitMessageEdited(key, editedRaw, stored) {
3401
+ const previous = stored ? normalizeBaileysMessage(stored) : void 0;
3402
+ const message = normalizeBaileysMessage(editedRaw);
3403
+ if (!message) return;
3404
+ this.#remember(editedRaw);
3405
+ const editedByMe = key.fromMe === true;
3406
+ const editedById = key.participant ?? key.participantAlt ?? (editedByMe ? this.#socket?.user?.id : key.remoteJid ?? void 0);
3407
+ void this.#events.emit("messageEdited", {
3408
+ key: message.keys,
3409
+ ...previous ? { previous } : {},
3410
+ message,
3411
+ editedByMe,
3412
+ ...editedById ? { editedById } : {},
3413
+ editedAt: message.timestamp
3414
+ });
3415
+ }
3416
+ #findStoredMessage(key) {
3417
+ const direct = this.#messageStore.get(this.#messageStoreKey(key));
3418
+ if (direct) return direct;
3419
+ if (!key.id) return void 0;
3420
+ for (const message of this.#messageStore.values()) {
3421
+ if (message.key.id === key.id) return message;
3422
+ }
3423
+ return void 0;
3424
+ }
3202
3425
  #remember(message) {
3203
3426
  const key = this.#messageStoreKey(message.key);
3204
3427
  this.#messageStore.delete(key);