whalibmob 5.29.1 → 5.29.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/README.md +13 -1
- package/index.d.ts +60 -2
- package/lib/Client.js +128 -19
- package/lib/messages/MessageSender.js +4 -4
- package/lib/proto/MessageProto.js +47 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3284,7 +3284,7 @@ connect()
|
|
|
3284
3284
|
| `presence` | `{ from, available }` | Contact came online or went offline |
|
|
3285
3285
|
| `group_update` | `{ type, groupJid, actor, participants, subject, timestamp }` | Member added / removed / promoted / demoted, subject or settings changed |
|
|
3286
3286
|
| `notification` | node object | Group or contact update notification |
|
|
3287
|
-
| `call` | `{ from }` |
|
|
3287
|
+
| `call` | `{ from, id, status, tag, creator, creatorAlt, groupJid, platform, version, reason, ts, node }` | A call stanza. `id` is what `rejectCall()` needs, `creator` is who placed the call (it differs from `from` in a group), and `status` is the stage: `offer`, `offer_notice`, `ringing`, `accept`, `preaccept`, `transport`, `terminate`, `reject` or `unknown` |
|
|
3288
3288
|
| `chat_read` | `{ jid, read, remote?, synced? }` | Chat marked read (`read: true`) or unread (`read: false`) |
|
|
3289
3289
|
| `chat_muted` | `{ jid, muted, until, remote?, synced? }` | Chat muted or unmuted; `until` is epoch ms (−1 = indefinite) |
|
|
3290
3290
|
| `chat_pinned` | `{ jid, pinned, remote?, synced? }` | Chat pinned or unpinned |
|
|
@@ -3322,9 +3322,21 @@ The message object contains:
|
|
|
3322
3322
|
ts: number, // Unix timestamp (seconds)
|
|
3323
3323
|
node: object, // raw XML node — node.attrs.sender_pn holds the real phone JID
|
|
3324
3324
|
decoded: object, // structured payload — shape depends on message type (see below)
|
|
3325
|
+
fromMe: boolean, // you sent this from another of your own devices
|
|
3326
|
+
chat: string, // the conversation — equals from unless fromMe is true
|
|
3325
3327
|
}
|
|
3326
3328
|
```
|
|
3327
3329
|
|
|
3330
|
+
> [!NOTE]
|
|
3331
|
+
> When you send a message from your phone, WhatsApp echoes a copy of it to every
|
|
3332
|
+
> other device on the account, wrapped in a `DeviceSentMessage`. On that copy
|
|
3333
|
+
> `from` is **your own JID** — it names the sender, not the chat. Use `chat`,
|
|
3334
|
+
> which is the conversation whichever direction the message went, and `fromMe`
|
|
3335
|
+
> to tell the two apart. The envelope's own fields are on
|
|
3336
|
+
> `decoded.deviceSentMeta` (`destinationJid`, `phash`) if you need them raw.
|
|
3337
|
+
> These messages are not answered with a read receipt: nothing was read by
|
|
3338
|
+
> receiving your own message back.
|
|
3339
|
+
|
|
3328
3340
|
> [!NOTE]
|
|
3329
3341
|
> WhatsApp Multi-Device uses **LID JIDs** internally. The `from` field may be a LID like
|
|
3330
3342
|
> `112345678901234@s.whatsapp.net` rather than the real phone number. To get the actual
|
package/index.d.ts
CHANGED
|
@@ -271,9 +271,23 @@ export interface DecodedBase {
|
|
|
271
271
|
ephemeral?: boolean;
|
|
272
272
|
/** The message is the new text of an edit. */
|
|
273
273
|
edited?: boolean;
|
|
274
|
+
/**
|
|
275
|
+
* Present only on a message this account sent from one of its other devices.
|
|
276
|
+
* The stanza names us as the sender, so the chat it belongs to is written
|
|
277
|
+
* here and nowhere else.
|
|
278
|
+
*/
|
|
279
|
+
deviceSentMeta?: DeviceSentMeta;
|
|
274
280
|
[key: string]: any;
|
|
275
281
|
}
|
|
276
282
|
|
|
283
|
+
/** What a `DeviceSentMessage` envelope said about the message inside it. */
|
|
284
|
+
export interface DeviceSentMeta {
|
|
285
|
+
/** The chat the message was originally addressed to. */
|
|
286
|
+
destinationJid?: Jid;
|
|
287
|
+
/** Hash of the participant list the sending device used, when it set one. */
|
|
288
|
+
phash?: string;
|
|
289
|
+
}
|
|
290
|
+
|
|
277
291
|
export interface DecodedText extends DecodedBase {
|
|
278
292
|
type: 'text';
|
|
279
293
|
text: string;
|
|
@@ -335,6 +349,39 @@ export type DecodedMessage =
|
|
|
335
349
|
| DecodedProtocol
|
|
336
350
|
| DecodedBase;
|
|
337
351
|
|
|
352
|
+
/** The stage a call is at, from the child tag inside the `<call>` stanza. */
|
|
353
|
+
export type CallStatus =
|
|
354
|
+
| 'offer' | 'offer_notice' | 'ringing' | 'accept'
|
|
355
|
+
| 'preaccept' | 'transport' | 'terminate' | 'reject' | 'unknown';
|
|
356
|
+
|
|
357
|
+
/** The payload of the `call` event. */
|
|
358
|
+
export interface CallEvent {
|
|
359
|
+
/** Who the stanza came from. Equals `creator` on a one-to-one call. */
|
|
360
|
+
from: Jid;
|
|
361
|
+
/** The call id — what `rejectCall` needs. `null` on a stanza that names none. */
|
|
362
|
+
id: string | null;
|
|
363
|
+
/** `'ringing'` is the `relaylatency` stanza, under the name it has always had. */
|
|
364
|
+
status: CallStatus;
|
|
365
|
+
/** The child tag verbatim, so a stage not in `CallStatus` is still readable. */
|
|
366
|
+
tag: string;
|
|
367
|
+
/** The account that placed the call. Differs from `from` in a group. */
|
|
368
|
+
creator: Jid;
|
|
369
|
+
/** The creator's other name — phone JID for a LID creator, and the reverse. */
|
|
370
|
+
creatorAlt: Jid | null;
|
|
371
|
+
/** The group the call is in, when it is a group call. */
|
|
372
|
+
groupJid: Jid | null;
|
|
373
|
+
/** The caller's platform, on the stanzas that carry it. */
|
|
374
|
+
platform: string | null;
|
|
375
|
+
/** The caller's client version, on the stanzas that carry it. */
|
|
376
|
+
version: string | null;
|
|
377
|
+
/** Why the call ended, on a `terminate`. */
|
|
378
|
+
reason: string | null;
|
|
379
|
+
/** Unix timestamp, seconds. */
|
|
380
|
+
ts: number;
|
|
381
|
+
/** Raw binary node. */
|
|
382
|
+
node: any;
|
|
383
|
+
}
|
|
384
|
+
|
|
338
385
|
/**
|
|
339
386
|
* The payload of the `message` event.
|
|
340
387
|
*
|
|
@@ -361,6 +408,17 @@ export interface IncomingMessage {
|
|
|
361
408
|
text?: string | null;
|
|
362
409
|
/** `true` on a group message. */
|
|
363
410
|
isGroup?: boolean;
|
|
411
|
+
/**
|
|
412
|
+
* `true` when this account sent the message from another of its devices and
|
|
413
|
+
* the server echoed it back here.
|
|
414
|
+
*/
|
|
415
|
+
fromMe?: boolean;
|
|
416
|
+
/**
|
|
417
|
+
* The conversation the message belongs to, whichever direction it went. Same
|
|
418
|
+
* as `from` for anything we received; for one of our own messages it is who
|
|
419
|
+
* we sent it to, which `from` cannot say.
|
|
420
|
+
*/
|
|
421
|
+
chat?: Jid;
|
|
364
422
|
}
|
|
365
423
|
|
|
366
424
|
// ────────────────────────────────────────────────────────────────────────────
|
|
@@ -695,7 +753,7 @@ export interface WhalibmobEvents {
|
|
|
695
753
|
message: (msg: IncomingMessage) => void;
|
|
696
754
|
receipt: (r: { type: string; id: string; from: Jid }) => void;
|
|
697
755
|
presence: (p: { from: Jid; available: boolean }) => void;
|
|
698
|
-
call: (c:
|
|
756
|
+
call: (c: CallEvent) => void;
|
|
699
757
|
notification: (node: any) => void;
|
|
700
758
|
decrypt_error: (e: { id: string; from: Jid; participant?: Jid; err: Error }) => void;
|
|
701
759
|
session_refresh: (e: { node: any }) => void;
|
|
@@ -936,7 +994,7 @@ export declare class WhalibmobClient extends EventEmitter {
|
|
|
936
994
|
|
|
937
995
|
/**
|
|
938
996
|
* Refuse one incoming call. Only reachable when the client was built with
|
|
939
|
-
* `autoRejectCalls: false`; the id and
|
|
997
|
+
* `autoRejectCalls: false`; pass the `id` and `creator` off the `call` event.
|
|
940
998
|
*/
|
|
941
999
|
rejectCall(callId: string, from: Jid): boolean;
|
|
942
1000
|
|
package/lib/Client.js
CHANGED
|
@@ -166,6 +166,23 @@ const PRIVACY_WIRE_TO_KEY = {
|
|
|
166
166
|
stickers: 'stickers'
|
|
167
167
|
};
|
|
168
168
|
|
|
169
|
+
// The stage a call is at, keyed by the child tag the server puts inside <call>.
|
|
170
|
+
//
|
|
171
|
+
// These are the eight the protocol uses; anything else is reported as
|
|
172
|
+
// 'unknown' with the tag itself alongside, so a new one is visible rather than
|
|
173
|
+
// silently dropped. `relaylatency` keeps the name it has always been announced
|
|
174
|
+
// under here — it is the stanza that arrives while the phone is ringing.
|
|
175
|
+
const CALL_STATUS = {
|
|
176
|
+
offer: 'offer',
|
|
177
|
+
offer_notice: 'offer_notice',
|
|
178
|
+
relaylatency: 'ringing',
|
|
179
|
+
accept: 'accept',
|
|
180
|
+
preaccept: 'preaccept',
|
|
181
|
+
transport: 'transport',
|
|
182
|
+
terminate: 'terminate',
|
|
183
|
+
reject: 'reject'
|
|
184
|
+
};
|
|
185
|
+
|
|
169
186
|
// Reduce a JID to its bare user. Blocking, and anything else that names an
|
|
170
187
|
// account rather than one of its devices, wants this form.
|
|
171
188
|
function toNonAdJid(input) {
|
|
@@ -2830,7 +2847,10 @@ class WhalibmobClient extends EventEmitter {
|
|
|
2830
2847
|
|
|
2831
2848
|
this._sendMessageAck(id, fromRaw, partRaw);
|
|
2832
2849
|
this._sendDeliveryReceipt(id, fromRaw, partRaw);
|
|
2833
|
-
this.emit('message', {
|
|
2850
|
+
this.emit('message', {
|
|
2851
|
+
id, from, participant, ts, text, node,
|
|
2852
|
+
fromMe: false, chat: from
|
|
2853
|
+
});
|
|
2834
2854
|
this._sendReadReceipt(id, fromRaw, partRaw);
|
|
2835
2855
|
}
|
|
2836
2856
|
|
|
@@ -3111,8 +3131,24 @@ class WhalibmobClient extends EventEmitter {
|
|
|
3111
3131
|
// If we had asked the phone to resend this one, it has answered.
|
|
3112
3132
|
this._resolvePlaceholderResend(id);
|
|
3113
3133
|
|
|
3114
|
-
|
|
3115
|
-
|
|
3134
|
+
// A DeviceSentMessage is one we sent ourselves from another device. The
|
|
3135
|
+
// stanza's `from` is our own JID, so it says who but never where: the
|
|
3136
|
+
// chat is named inside the envelope and nowhere else. Both are put on
|
|
3137
|
+
// the event — `chat` is the conversation whichever direction the message
|
|
3138
|
+
// went, so a handler can answer without having to know which case it is
|
|
3139
|
+
// looking at.
|
|
3140
|
+
const dsm = decoded && decoded.deviceSentMeta;
|
|
3141
|
+
const dest = dsm && dsm.destinationJid ? String(dsm.destinationJid) : null;
|
|
3142
|
+
this.emit('message', {
|
|
3143
|
+
id, from, participant, ts, decoded, node,
|
|
3144
|
+
fromMe: !!dest,
|
|
3145
|
+
chat: dest || from
|
|
3146
|
+
});
|
|
3147
|
+
|
|
3148
|
+
// Nothing is read by receiving our own message back. A read receipt for
|
|
3149
|
+
// it would be addressed to ourselves and marks the chat read on every
|
|
3150
|
+
// other device — which is not what happened.
|
|
3151
|
+
if (!dest) this._sendReadReceipt(id, fromRaw, partRaw);
|
|
3116
3152
|
|
|
3117
3153
|
// ── pkmsg = peer opened a new Signal session (identity/device change) ──
|
|
3118
3154
|
// Re-issue our tcToken to them so the fresh session carries a valid
|
|
@@ -3153,7 +3189,10 @@ class WhalibmobClient extends EventEmitter {
|
|
|
3153
3189
|
}
|
|
3154
3190
|
return;
|
|
3155
3191
|
}
|
|
3156
|
-
this.emit('message', {
|
|
3192
|
+
this.emit('message', {
|
|
3193
|
+
id, from, participant, ts, decoded, isGroup: true, node,
|
|
3194
|
+
fromMe: false, chat: from
|
|
3195
|
+
});
|
|
3157
3196
|
this._sendReadReceipt(id, fromRaw, partRaw);
|
|
3158
3197
|
})
|
|
3159
3198
|
.catch(err => {
|
|
@@ -4185,7 +4224,8 @@ class WhalibmobClient extends EventEmitter {
|
|
|
4185
4224
|
// A <call> can also be the answer to something we asked for — creating a
|
|
4186
4225
|
// call link, for one. Those come back on the same tag with the id we sent,
|
|
4187
4226
|
// so they have to be handed to the waiting caller and not treated as an
|
|
4188
|
-
// incoming call to reject.
|
|
4227
|
+
// incoming call to reject. A reply to us is not a server push and gets no
|
|
4228
|
+
// ack, which is why this stays ahead of everything below.
|
|
4189
4229
|
if (attrs.id) {
|
|
4190
4230
|
const handler = this._pendingIqs.get(attrs.id);
|
|
4191
4231
|
if (handler) {
|
|
@@ -4195,23 +4235,63 @@ class WhalibmobClient extends EventEmitter {
|
|
|
4195
4235
|
}
|
|
4196
4236
|
}
|
|
4197
4237
|
|
|
4238
|
+
// The server expects to hear that the stanza arrived, for a call exactly as
|
|
4239
|
+
// it does for a message or a notification. Nothing here acknowledged one, so
|
|
4240
|
+
// every incoming call was re-delivered on the next connection — and it goes
|
|
4241
|
+
// out first, before the shape below is looked at, because an unrecognised
|
|
4242
|
+
// call is still a call that reached us.
|
|
4243
|
+
this._sendStanzaAck(node);
|
|
4244
|
+
|
|
4198
4245
|
// What the stanza actually says, rather than the raw node alone: which call
|
|
4199
4246
|
// it is, and what stage it is at. A caller deciding whether to reject needs
|
|
4200
4247
|
// both, and digging them out of the node is not its job.
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4248
|
+
//
|
|
4249
|
+
// All of it lives on the single child, not on the <call> itself, and under
|
|
4250
|
+
// hyphenated names: `call-id` and `call-creator`. This read `call_id` off
|
|
4251
|
+
// the child, which no stanza has ever carried — so the id was always null,
|
|
4252
|
+
// the event announced a call nobody could identify, and the automatic
|
|
4253
|
+
// refusal below was skipped for every call that ever came in. Both spellings
|
|
4254
|
+
// are in the binary token table and only the hyphenated ones are real.
|
|
4255
|
+
const children = Array.isArray(node.content)
|
|
4256
|
+
? node.content.filter(c => c && c.description) : [];
|
|
4257
|
+
const child = children.length === 1 ? children[0] : null;
|
|
4258
|
+
const cattrs = (child && child.attrs) || {};
|
|
4259
|
+
const tag = child ? child.description : '';
|
|
4260
|
+
|
|
4261
|
+
const from = attrs.from ? String(attrs.from) : '';
|
|
4262
|
+
const callId = cattrs['call-id'] ? String(cattrs['call-id']) : null;
|
|
4263
|
+
// The creator is the account that placed the call. On a one-to-one call it
|
|
4264
|
+
// is the same as the stanza's `from`; in a group they differ, and the
|
|
4265
|
+
// refusal has to name the creator.
|
|
4266
|
+
const creator = cattrs['call-creator'] ? String(cattrs['call-creator']) : from;
|
|
4267
|
+
const status = CALL_STATUS[tag] || 'unknown';
|
|
4268
|
+
|
|
4269
|
+
// The caller's other name. A LID creator carries its phone number, a phone
|
|
4270
|
+
// creator its LID — whichever side the stanza did not use to address it.
|
|
4271
|
+
const creatorAlt = creator.endsWith('@lid')
|
|
4272
|
+
? (cattrs.caller_pn ? String(cattrs.caller_pn) : null)
|
|
4273
|
+
: (cattrs.caller_lid ? String(cattrs.caller_lid) : null);
|
|
4274
|
+
|
|
4275
|
+
this.emit('call', {
|
|
4276
|
+
from,
|
|
4277
|
+
id: callId,
|
|
4278
|
+
status,
|
|
4279
|
+
tag,
|
|
4280
|
+
creator,
|
|
4281
|
+
creatorAlt,
|
|
4282
|
+
groupJid: cattrs['group-jid'] ? String(cattrs['group-jid']) : null,
|
|
4283
|
+
platform: attrs.platform ? String(attrs.platform) : null,
|
|
4284
|
+
version: attrs.version ? String(attrs.version) : null,
|
|
4285
|
+
reason: cattrs.reason ? String(cattrs.reason) : null,
|
|
4286
|
+
ts: attrs.t ? parseInt(attrs.t, 10) : 0,
|
|
4287
|
+
node
|
|
4288
|
+
});
|
|
4209
4289
|
|
|
4210
4290
|
// Reject every incoming call, unless the caller asked to decide for itself.
|
|
4211
4291
|
// Turning this off does not answer calls — nothing here can — it only stops
|
|
4212
4292
|
// the automatic refusal, so a caller can reject some and let the rest ring.
|
|
4213
4293
|
if (this._autoRejectCalls && callId && status === 'offer') {
|
|
4214
|
-
this.rejectCall(callId,
|
|
4294
|
+
this.rejectCall(callId, creator);
|
|
4215
4295
|
}
|
|
4216
4296
|
}
|
|
4217
4297
|
|
|
@@ -4220,21 +4300,50 @@ class WhalibmobClient extends EventEmitter {
|
|
|
4220
4300
|
*
|
|
4221
4301
|
* Only needed when the client was built with `autoRejectCalls: false`; with
|
|
4222
4302
|
* the default every call is refused before this could be reached. The call id
|
|
4223
|
-
* and the caller both come off the `call` event
|
|
4303
|
+
* and the caller both come off the `call` event — `id` and `creator`.
|
|
4224
4304
|
*
|
|
4225
4305
|
* @param {string} callId the `id` from the call event
|
|
4226
|
-
* @param {string} from the `
|
|
4306
|
+
* @param {string} from the `creator` from the call event
|
|
4227
4307
|
* @returns {boolean} whether the refusal could be sent
|
|
4228
4308
|
*/
|
|
4229
4309
|
rejectCall(callId, from) {
|
|
4230
4310
|
if (!callId || !this._socket || !this._connected) return false;
|
|
4311
|
+
// Both ends are named without their device: a call belongs to an account,
|
|
4312
|
+
// not to the device that happened to place it.
|
|
4313
|
+
const creator = toNonAdJid(String(from || ''));
|
|
4314
|
+
const ownJid = toNonAdJid(this._ownJidFor(creator));
|
|
4231
4315
|
this._socket.sendNode(new BinaryNode('call', {
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4316
|
+
id: this._genMsgId(),
|
|
4317
|
+
from: jidStrToObj(ownJid),
|
|
4318
|
+
to: jidStrToObj(creator)
|
|
4319
|
+
}, [new BinaryNode('reject', {
|
|
4320
|
+
'call-id': callId,
|
|
4321
|
+
'call-creator': jidStrToObj(creator),
|
|
4322
|
+
count: '0'
|
|
4323
|
+
}, null)]));
|
|
4235
4324
|
return true;
|
|
4236
4325
|
}
|
|
4237
4326
|
|
|
4327
|
+
// The acknowledgement a server push gets, whatever tag it came in on.
|
|
4328
|
+
//
|
|
4329
|
+
// Mirrors the one whatsmeow sends: the stanza's own tag as the class, its id,
|
|
4330
|
+
// and back to whoever sent it, carrying `participant` and `recipient` through
|
|
4331
|
+
// when the stanza named them. `type` rides along for everything but a message,
|
|
4332
|
+
// where it means something else entirely.
|
|
4333
|
+
_sendStanzaAck(node) {
|
|
4334
|
+
if (!this._socket || !this._connected) return;
|
|
4335
|
+
const attrs = node.attrs || {};
|
|
4336
|
+
const ack = {
|
|
4337
|
+
id: attrs.id || '',
|
|
4338
|
+
class: node.description,
|
|
4339
|
+
to: attrs.from || 's.whatsapp.net'
|
|
4340
|
+
};
|
|
4341
|
+
if (attrs.participant) ack.participant = attrs.participant;
|
|
4342
|
+
if (attrs.recipient) ack.recipient = attrs.recipient;
|
|
4343
|
+
if (attrs.type && node.description !== 'message') ack.type = String(attrs.type);
|
|
4344
|
+
this._socket.sendNode(new BinaryNode('ack', ack, null));
|
|
4345
|
+
}
|
|
4346
|
+
|
|
4238
4347
|
// ─── Auth failure (server forces logout during active session) ──────────────
|
|
4239
4348
|
|
|
4240
4349
|
// <failure> ends the connection, but not every reason means the session died.
|
|
@@ -1026,13 +1026,13 @@ class MessageSender {
|
|
|
1026
1026
|
// set than the server's.
|
|
1027
1027
|
//
|
|
1028
1028
|
// It is not put on the stanza: phash belongs on a group message, not a 1:1
|
|
1029
|
-
// one. It is
|
|
1030
|
-
//
|
|
1031
|
-
//
|
|
1029
|
+
// one. It is kept only to be compared against the phash the server echoes
|
|
1030
|
+
// back on the ack, so a drifted device cache gets flushed — see the check
|
|
1031
|
+
// after the dispatch below.
|
|
1032
1032
|
const phash = computePhash([...otherJids, ownPrimaryJid, ...ownLinkedJids]);
|
|
1033
1033
|
|
|
1034
1034
|
const dsmBuf = ownLinkedJids.length > 0
|
|
1035
|
-
? encodeDeviceSentMessage(toJid, plaintext
|
|
1035
|
+
? encodeDeviceSentMessage(toJid, plaintext)
|
|
1036
1036
|
: null;
|
|
1037
1037
|
|
|
1038
1038
|
// Acquire mutex before touching Signal sessions — concurrent sends on the
|
|
@@ -353,14 +353,32 @@ function encodeMessage(type, payload) {
|
|
|
353
353
|
// Used when sending to own linked devices — wraps the original message so other
|
|
354
354
|
// devices know where the message was originally addressed (destinationJid).
|
|
355
355
|
// DeviceSentMessage { destinationJid=1, message=2, phash=3 }
|
|
356
|
-
|
|
357
|
-
|
|
356
|
+
//
|
|
357
|
+
// The envelope is not the whole outer Message. Whatever messageContextInfo the
|
|
358
|
+
// message carries is copied up beside the envelope as well, because that is
|
|
359
|
+
// where a recipient reads it from: it is a field of Message, and once the
|
|
360
|
+
// message is buried inside a DeviceSentMessage the copy inside it is a level
|
|
361
|
+
// too deep for anything that does not know to go looking. A poll echoed to our
|
|
362
|
+
// own devices reached them without a message secret, so none of them could ever
|
|
363
|
+
// decrypt a vote on it. whatsmeow (`MessageContextInfo: message.MessageContextInfo`
|
|
364
|
+
// in marshalMessage) and Baileys both duplicate it for the same reason.
|
|
365
|
+
//
|
|
366
|
+
// phash is left unset, as it is by every other implementation: it describes the
|
|
367
|
+
// participant list, the server checks that against the hash on the stanza, and
|
|
368
|
+
// no client has ever read it back out of here.
|
|
369
|
+
function encodeDeviceSentMessage(destinationJid, messageBuf) {
|
|
358
370
|
const inner = Buffer.concat([
|
|
359
371
|
field(1, WIRE_LEN, str(destinationJid)),
|
|
360
|
-
field(2, WIRE_LEN, messageBuf)
|
|
361
|
-
phash ? field(3, WIRE_LEN, str(phash)) : Buffer.alloc(0)
|
|
372
|
+
field(2, WIRE_LEN, messageBuf)
|
|
362
373
|
]);
|
|
363
|
-
|
|
374
|
+
|
|
375
|
+
let contextInfo = Buffer.alloc(0);
|
|
376
|
+
try {
|
|
377
|
+
const mci = _decodeFields(messageBuf)[35];
|
|
378
|
+
if (Buffer.isBuffer(mci) && mci.length) contextInfo = field(35, WIRE_LEN, mci);
|
|
379
|
+
} catch (_) { /* nothing to lift */ }
|
|
380
|
+
|
|
381
|
+
return Buffer.concat([field(31, WIRE_LEN, inner), contextInfo]);
|
|
364
382
|
}
|
|
365
383
|
|
|
366
384
|
// ─── ProtocolMessage (field 12 of Message) ───────────────────────────────────
|
|
@@ -733,6 +751,9 @@ function decodeMessageContainer(buf, depth) {
|
|
|
733
751
|
// still belongs to what comes out, which is why both are read first and
|
|
734
752
|
// carried onto the result — unwrapping used to return straight out of here
|
|
735
753
|
// and lose them.
|
|
754
|
+
// What a DeviceSentMessage envelope said about itself, kept for the caller.
|
|
755
|
+
let deviceSentMeta = null;
|
|
756
|
+
|
|
736
757
|
if (level < MAX_WRAPPER_DEPTH) {
|
|
737
758
|
for (const w of MESSAGE_WRAPPERS) {
|
|
738
759
|
const inner = _unwrap(f[w.field], w.inner);
|
|
@@ -744,6 +765,24 @@ function decodeMessageContainer(buf, depth) {
|
|
|
744
765
|
if (!decoded || decoded.type === 'unknown') continue;
|
|
745
766
|
msgResult = decoded;
|
|
746
767
|
if (w.flag) envelopeFlags = Object.assign({}, envelopeFlags, { [w.flag]: true });
|
|
768
|
+
// A DeviceSentMessage is a message this account sent from one of its
|
|
769
|
+
// other devices, echoed back to us. The stanza's `from` is therefore our
|
|
770
|
+
// own JID and names nothing: the conversation it belongs to is only ever
|
|
771
|
+
// written here, in the envelope. It was being opened and thrown away, so
|
|
772
|
+
// a message sent from the phone arrived with no way to tell which chat
|
|
773
|
+
// it was in. whatsmeow keeps the same two fields as Info.DeviceSentMeta.
|
|
774
|
+
if (w.field === 31) {
|
|
775
|
+
try {
|
|
776
|
+
const env = _decodeFields(f[31]);
|
|
777
|
+
const dest = _str(env[1]);
|
|
778
|
+
const ph = _str(env[3]);
|
|
779
|
+
if (dest || ph) {
|
|
780
|
+
deviceSentMeta = {};
|
|
781
|
+
if (dest) deviceSentMeta.destinationJid = dest;
|
|
782
|
+
if (ph) deviceSentMeta.phash = ph;
|
|
783
|
+
}
|
|
784
|
+
} catch (_) { /* the envelope named nothing */ }
|
|
785
|
+
}
|
|
747
786
|
break;
|
|
748
787
|
}
|
|
749
788
|
}
|
|
@@ -1017,6 +1056,9 @@ function decodeMessageContainer(buf, depth) {
|
|
|
1017
1056
|
// this level only fills in what is still missing.
|
|
1018
1057
|
if (msgResult) {
|
|
1019
1058
|
if (envelopeFlags) msgResult = Object.assign({}, msgResult, envelopeFlags);
|
|
1059
|
+
if (deviceSentMeta && !msgResult.deviceSentMeta) {
|
|
1060
|
+
msgResult = Object.assign({}, msgResult, { deviceSentMeta });
|
|
1061
|
+
}
|
|
1020
1062
|
if (messageSecret && !msgResult.messageSecret) {
|
|
1021
1063
|
msgResult = Object.assign({}, msgResult, { messageSecret });
|
|
1022
1064
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "whalibmob",
|
|
3
|
-
"version": "5.29.
|
|
3
|
+
"version": "5.29.2",
|
|
4
4
|
"description": "Node.js library for WhatsApp — register a number over SMS, or link as a companion by QR. Signal E2E encryption, media, groups, channels.",
|
|
5
5
|
"author": "Kunboruto20",
|
|
6
6
|
"main": "index.js",
|