whalibmob 5.7.2 → 5.9.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.
@@ -0,0 +1,106 @@
1
+ 'use strict';
2
+
3
+ // Which collection, index and api version each kind of change is filed under.
4
+ //
5
+ // None of this is ours to pick. The server keys a mutation by its index, sorts
6
+ // it by collection, and rejects an api version it does not expect — get any of
7
+ // the three wrong and the patch is either refused or silently lands somewhere
8
+ // the phone will never look. The index is the identity of the setting: writing
9
+ // ['pin_v1', jid] twice replaces, rather than accumulates.
10
+
11
+ function mutePatch(jid, muteEndTimestampMs) {
12
+ return {
13
+ name: 'regular_high',
14
+ index: ['mute', jid],
15
+ apiVersion: 2,
16
+ action: {
17
+ muteAction: {
18
+ muted: !!muteEndTimestampMs,
19
+ muteEndTimestamp: muteEndTimestampMs || undefined
20
+ }
21
+ }
22
+ };
23
+ }
24
+
25
+ function archivePatch(jid, archived) {
26
+ return {
27
+ name: 'regular_low',
28
+ index: ['archive', jid],
29
+ apiVersion: 3,
30
+ action: { archiveChatAction: { archived: !!archived } }
31
+ };
32
+ }
33
+
34
+ function markReadPatch(jid, read) {
35
+ return {
36
+ name: 'regular_low',
37
+ index: ['markChatAsRead', jid],
38
+ apiVersion: 3,
39
+ action: { markChatAsReadAction: { read: !!read } }
40
+ };
41
+ }
42
+
43
+ function pinPatch(jid, pinned) {
44
+ return {
45
+ name: 'regular_low',
46
+ index: ['pin_v1', jid],
47
+ apiVersion: 5,
48
+ action: { pinAction: { pinned: !!pinned } }
49
+ };
50
+ }
51
+
52
+ // A star is per message, so the index carries the message it belongs to as well
53
+ // as the chat — otherwise starring a second message would unstar the first.
54
+ function starPatch(jid, msgId, fromMe, starred) {
55
+ return {
56
+ name: 'regular_low',
57
+ index: ['star', jid, msgId, fromMe ? '1' : '0', '0'],
58
+ apiVersion: 2,
59
+ action: { starAction: { starred: !!starred } }
60
+ };
61
+ }
62
+
63
+ function pushNamePatch(name) {
64
+ return {
65
+ name: 'critical_block',
66
+ index: ['setting_pushName'],
67
+ apiVersion: 1,
68
+ action: { pushNameSetting: { name } }
69
+ };
70
+ }
71
+
72
+ function contactPatch(jid, contact) {
73
+ return {
74
+ name: 'critical_unblock_low',
75
+ index: ['contact', jid],
76
+ apiVersion: 2,
77
+ action: { contactAction: contact || {} },
78
+ // No contact means forget this one, which is a removal rather than an
79
+ // empty value.
80
+ operation: contact ? 0 : 1
81
+ };
82
+ }
83
+
84
+ // The index tells you what a decoded mutation was about. The first element is
85
+ // the kind; what follows depends on it.
86
+ function describeIndex(index) {
87
+ if (!Array.isArray(index) || !index.length) return null;
88
+ const [kind, jid, msgId, fromMe] = index;
89
+ switch (kind) {
90
+ case 'mute': return { kind: 'mute', jid };
91
+ case 'archive': return { kind: 'archive', jid };
92
+ case 'markChatAsRead': return { kind: 'markRead', jid };
93
+ case 'pin_v1': return { kind: 'pin', jid };
94
+ case 'star': return { kind: 'star', jid, msgId, fromMe: fromMe === '1' };
95
+ case 'contact': return { kind: 'contact', jid };
96
+ case 'setting_pushName':return { kind: 'pushName' };
97
+ case 'clearChat': return { kind: 'clear', jid };
98
+ case 'deleteChat': return { kind: 'delete', jid };
99
+ default: return { kind, jid };
100
+ }
101
+ }
102
+
103
+ module.exports = {
104
+ mutePatch, archivePatch, markReadPatch, pinPatch, starPatch,
105
+ pushNamePatch, contactPatch, describeIndex
106
+ };
@@ -0,0 +1,263 @@
1
+ 'use strict';
2
+
3
+ // The wire format app state travels in.
4
+ //
5
+ // A collection ("regular_low", "critical_block", …) is a versioned list of
6
+ // mutations. Each mutation names an index — a JSON array like ["pin_v1", jid] —
7
+ // and carries the encrypted value filed under it. The server hands them over
8
+ // either as a snapshot (the whole collection, from scratch) or as patches (what
9
+ // changed since a version you name).
10
+ //
11
+ // SyncdPatch { version=1, mutations=2, externalMutations=3,
12
+ // snapshotMac=4, patchMac=5, keyId=6 }
13
+ // SyncdSnapshot{ version=1, records=2, mac=3, keyId=4 }
14
+ // SyncdMutation{ operation=1 (SET=0 REMOVE=1), record=2 }
15
+ // SyncdRecord { index=1, value=2, keyId=3 }
16
+ // SyncdIndex { blob=1 } SyncdValue { blob=1 } KeyId { id=1 }
17
+ // SyncdVersion { version=1 }
18
+ // SyncdMutations { mutations=1 }
19
+ // ExternalBlobReference { mediaKey=1, directPath=2, handle=3,
20
+ // fileSizeBytes=4, fileSha256=5, fileEncSha256=6 }
21
+ // SyncActionData { index=1, value=2, padding=3, version=4 }
22
+
23
+ const {
24
+ decodeFields, field, varint, str, bytes, WIRE_VARINT, WIRE_LEN
25
+ } = require('../proto/MessageProto');
26
+
27
+ const SET = 0;
28
+ const REMOVE = 1;
29
+
30
+ const _b = v => (v == null ? null : (Buffer.isBuffer(v) ? v : Buffer.from(v)));
31
+ const _arr = v => (v == null ? [] : (Array.isArray(v) ? v : [v]));
32
+ const _num = v => {
33
+ if (v == null) return 0;
34
+ if (typeof v === 'number') return v;
35
+ if (Buffer.isBuffer(v)) { let n = 0; for (let i = v.length - 1; i >= 0; i--) n = n * 256 + v[i]; return n; }
36
+ return Number(v) || 0;
37
+ };
38
+ const _str = v => (v == null ? '' : (Buffer.isBuffer(v) ? v.toString('utf8') : String(v)));
39
+
40
+ // ─── decoding ────────────────────────────────────────────────────────────────
41
+
42
+ function decodeKeyId(buf) {
43
+ if (!buf) return null;
44
+ const f = decodeFields(buf);
45
+ return _b(f[1]);
46
+ }
47
+
48
+ function decodeRecord(buf) {
49
+ const f = decodeFields(buf);
50
+ return {
51
+ indexBlob: f[1] ? _b(decodeFields(f[1])[1]) : null,
52
+ valueBlob: f[2] ? _b(decodeFields(f[2])[1]) : null,
53
+ keyId: decodeKeyId(f[3])
54
+ };
55
+ }
56
+
57
+ // A bare record is a SET; only a SyncdMutation carries an operation of its own.
58
+ function decodeMutation(buf) {
59
+ const f = decodeFields(buf);
60
+ if (!f[2]) return Object.assign({ operation: SET }, decodeRecord(buf));
61
+ return Object.assign({ operation: _num(f[1]) }, decodeRecord(f[2]));
62
+ }
63
+
64
+ function decodeExternalBlobReference(buf) {
65
+ if (!buf) return null;
66
+ const f = decodeFields(buf);
67
+ return {
68
+ mediaKey: _b(f[1]),
69
+ directPath: _str(f[2]),
70
+ handle: _str(f[3]),
71
+ fileSizeBytes: _num(f[4]),
72
+ fileSha256: _b(f[5]),
73
+ fileEncSha256: _b(f[6])
74
+ };
75
+ }
76
+
77
+ function decodePatch(buf) {
78
+ const f = decodeFields(buf);
79
+ return {
80
+ version: f[1] ? _num(decodeFields(f[1])[1]) : null,
81
+ mutations: _arr(f[2]).filter(Buffer.isBuffer).map(decodeMutation),
82
+ externalMutations: decodeExternalBlobReference(f[3]),
83
+ snapshotMac: _b(f[4]),
84
+ patchMac: _b(f[5]),
85
+ keyId: decodeKeyId(f[6])
86
+ };
87
+ }
88
+
89
+ function decodeSnapshot(buf) {
90
+ const f = decodeFields(buf);
91
+ return {
92
+ version: f[1] ? _num(decodeFields(f[1])[1]) : 0,
93
+ records: _arr(f[2]).filter(Buffer.isBuffer)
94
+ .map(r => Object.assign({ operation: SET }, decodeRecord(r))),
95
+ mac: _b(f[3]),
96
+ keyId: decodeKeyId(f[4])
97
+ };
98
+ }
99
+
100
+ // The blob an oversized patch is moved out into.
101
+ function decodeMutations(buf) {
102
+ const f = decodeFields(buf);
103
+ return _arr(f[1]).filter(Buffer.isBuffer).map(decodeMutation);
104
+ }
105
+
106
+ function decodeSyncActionData(buf) {
107
+ const f = decodeFields(buf);
108
+ return {
109
+ index: _b(f[1]),
110
+ value: f[2] ? decodeSyncActionValue(f[2]) : null,
111
+ version: _num(f[4])
112
+ };
113
+ }
114
+
115
+ // ─── SyncActionValue ─────────────────────────────────────────────────────────
116
+ //
117
+ // One field per kind of change. Only the ones this library acts on are decoded;
118
+ // anything else is reported by name so a caller can see it went by.
119
+ const ACTION_FIELDS = {
120
+ 2: 'starAction',
121
+ 3: 'contactAction',
122
+ 4: 'muteAction',
123
+ 5: 'pinAction',
124
+ 7: 'pushNameSetting',
125
+ 17: 'archiveChatAction',
126
+ 20: 'markChatAsReadAction',
127
+ 21: 'clearChatAction',
128
+ 22: 'deleteChatAction'
129
+ };
130
+
131
+ function decodeSyncActionValue(buf) {
132
+ const f = decodeFields(buf);
133
+ const out = { timestamp: _num(f[1]) };
134
+
135
+ for (const [num, name] of Object.entries(ACTION_FIELDS)) {
136
+ const raw = f[num];
137
+ if (!Buffer.isBuffer(raw)) continue;
138
+ const a = decodeFields(raw);
139
+ switch (name) {
140
+ case 'starAction':
141
+ out.starAction = { starred: !!_num(a[1]) }; break;
142
+ case 'pinAction':
143
+ out.pinAction = { pinned: !!_num(a[1]) }; break;
144
+ case 'muteAction':
145
+ out.muteAction = {
146
+ muted: !!_num(a[1]),
147
+ muteEndTimestamp: _num(a[2]),
148
+ autoMuted: !!_num(a[3])
149
+ };
150
+ break;
151
+ case 'archiveChatAction':
152
+ out.archiveChatAction = { archived: !!_num(a[1]) }; break;
153
+ case 'markChatAsReadAction':
154
+ out.markChatAsReadAction = { read: !!_num(a[1]) }; break;
155
+ case 'clearChatAction':
156
+ out.clearChatAction = {}; break;
157
+ case 'deleteChatAction':
158
+ out.deleteChatAction = {}; break;
159
+ case 'pushNameSetting':
160
+ out.pushNameSetting = { name: _str(a[1]) }; break;
161
+ case 'contactAction':
162
+ out.contactAction = {
163
+ fullName: _str(a[1]) || null,
164
+ firstName: _str(a[2]) || null,
165
+ lidJid: _str(a[3]) || null,
166
+ pnJid: _str(a[5]) || null,
167
+ username: _str(a[6]) || null
168
+ };
169
+ break;
170
+ default: break;
171
+ }
172
+ }
173
+
174
+ // Whatever else the server sent, so a caller is not left guessing why nothing
175
+ // happened for a mutation that plainly said something.
176
+ const known = new Set(['1', ...Object.keys(ACTION_FIELDS)]);
177
+ out.otherFields = Object.keys(f).filter(k => !known.has(k)).map(Number);
178
+ return out;
179
+ }
180
+
181
+ // ─── encoding (the write path) ───────────────────────────────────────────────
182
+
183
+ function encodeKeyId(id) { return field(1, WIRE_LEN, bytes(id)); }
184
+ function encodeVersion(v) { return field(1, WIRE_VARINT, varint(v)); }
185
+
186
+ function encodeSyncActionValue(action, timestampMs) {
187
+ const parts = [field(1, WIRE_VARINT, varint(timestampMs))];
188
+
189
+ if (action.starAction) {
190
+ parts.push(field(2, WIRE_LEN,
191
+ field(1, WIRE_VARINT, varint(action.starAction.starred ? 1 : 0))));
192
+ }
193
+ if (action.contactAction) {
194
+ const c = action.contactAction;
195
+ parts.push(field(3, WIRE_LEN, Buffer.concat([
196
+ c.fullName ? field(1, WIRE_LEN, str(c.fullName)) : Buffer.alloc(0),
197
+ c.firstName ? field(2, WIRE_LEN, str(c.firstName)) : Buffer.alloc(0)
198
+ ])));
199
+ }
200
+ if (action.muteAction) {
201
+ const m = action.muteAction;
202
+ parts.push(field(4, WIRE_LEN, Buffer.concat([
203
+ field(1, WIRE_VARINT, varint(m.muted ? 1 : 0)),
204
+ m.muteEndTimestamp
205
+ ? field(2, WIRE_VARINT, varint(m.muteEndTimestamp))
206
+ : Buffer.alloc(0)
207
+ ])));
208
+ }
209
+ if (action.pinAction) {
210
+ parts.push(field(5, WIRE_LEN,
211
+ field(1, WIRE_VARINT, varint(action.pinAction.pinned ? 1 : 0))));
212
+ }
213
+ if (action.pushNameSetting) {
214
+ parts.push(field(7, WIRE_LEN, field(1, WIRE_LEN, str(action.pushNameSetting.name))));
215
+ }
216
+ if (action.archiveChatAction) {
217
+ parts.push(field(17, WIRE_LEN,
218
+ field(1, WIRE_VARINT, varint(action.archiveChatAction.archived ? 1 : 0))));
219
+ }
220
+ if (action.markChatAsReadAction) {
221
+ parts.push(field(20, WIRE_LEN,
222
+ field(1, WIRE_VARINT, varint(action.markChatAsReadAction.read ? 1 : 0))));
223
+ }
224
+ return Buffer.concat(parts);
225
+ }
226
+
227
+ function encodeSyncActionData({ index, value, apiVersion, timestampMs }) {
228
+ return Buffer.concat([
229
+ field(1, WIRE_LEN, bytes(index)),
230
+ field(2, WIRE_LEN, encodeSyncActionValue(value, timestampMs)),
231
+ field(3, WIRE_LEN, Buffer.alloc(0)), // padding, always empty
232
+ field(4, WIRE_VARINT, varint(apiVersion || 1))
233
+ ]);
234
+ }
235
+
236
+ function encodeMutation({ operation, indexMac, valueBlob, keyId }) {
237
+ const record = Buffer.concat([
238
+ field(1, WIRE_LEN, field(1, WIRE_LEN, bytes(indexMac))),
239
+ field(2, WIRE_LEN, field(1, WIRE_LEN, bytes(valueBlob))),
240
+ field(3, WIRE_LEN, encodeKeyId(keyId))
241
+ ]);
242
+ return Buffer.concat([
243
+ field(1, WIRE_VARINT, varint(operation)),
244
+ field(2, WIRE_LEN, record)
245
+ ]);
246
+ }
247
+
248
+ function encodePatch({ version, mutations, snapshotMac, patchMac, keyId }) {
249
+ return Buffer.concat([
250
+ field(1, WIRE_LEN, encodeVersion(version)),
251
+ ...mutations.map(m => field(2, WIRE_LEN, encodeMutation(m))),
252
+ field(4, WIRE_LEN, bytes(snapshotMac)),
253
+ field(5, WIRE_LEN, bytes(patchMac)),
254
+ field(6, WIRE_LEN, encodeKeyId(keyId))
255
+ ]);
256
+ }
257
+
258
+ module.exports = {
259
+ SET, REMOVE,
260
+ decodePatch, decodeSnapshot, decodeMutations, decodeMutation,
261
+ decodeExternalBlobReference, decodeSyncActionData, decodeSyncActionValue,
262
+ encodePatch, encodeMutation, encodeSyncActionData, encodeSyncActionValue
263
+ };
@@ -15,6 +15,8 @@ const {
15
15
  encodeStickerMessage, encodeReactionMessage,
16
16
  encodePollCreationMessage,
17
17
  encodeLocationMessage, encodeContactMessage,
18
+ encodeGroupInviteMessage,
19
+ GROUP_INVITE_TYPE_DEFAULT, GROUP_INVITE_TYPE_PARENT,
18
20
  encodeDeviceSentMessage, encodeProtocolMessage,
19
21
  encodeExtendedText,
20
22
  encodeSenderKeyDistributionMessage,
@@ -535,6 +537,36 @@ class MessageSender {
535
537
  Object.assign({}, opts, { _mediaSubtype: 'vcard' }));
536
538
  }
537
539
 
540
+ // ─── Group invite ─────────────────────────────────────────────────────────
541
+ // sendGroupInvite(to, groupJid, inviteCode, inviteExpiration, opts)
542
+ //
543
+ // The bubble that carries a personal invitation into a group. It is what you
544
+ // fall back to when adding somebody outright is refused because their privacy
545
+ // settings do not allow it — the server hands back a code with that refusal
546
+ // and this is what turns the code into something the person can tap.
547
+ //
548
+ // opts: { groupName, caption, jpegThumbnail, isCommunity, id, contextInfo }
549
+
550
+ async sendGroupInvite(to, groupJid, inviteCode, inviteExpiration, opts) {
551
+ opts = opts || {};
552
+ const msgId = opts.id || generateMessageId();
553
+ const toJid = makeJid(to);
554
+ const invBuf = encodeMessage('groupInvite', encodeGroupInviteMessage({
555
+ groupJid: makeJid(groupJid),
556
+ inviteCode,
557
+ inviteExpiration: inviteExpiration || 0,
558
+ groupName: opts.groupName || '',
559
+ caption: opts.caption || '',
560
+ jpegThumbnail: opts.jpegThumbnail || opts.thumbnail || null,
561
+ groupType: opts.isCommunity ? GROUP_INVITE_TYPE_PARENT
562
+ : GROUP_INVITE_TYPE_DEFAULT,
563
+ contextInfo: opts.contextInfo
564
+ }));
565
+ // No media travels with it and it is not a reaction or a poll, so as far as
566
+ // the envelope is concerned this is an ordinary message.
567
+ return this._sendMessage(toJid, msgId, invBuf, 'text', opts);
568
+ }
569
+
538
570
  // ─── Forward decoded message ───────────────────────────────────────────────
539
571
  //
540
572
  // Forwards a received message to a new recipient without re-uploading media.
@@ -166,6 +166,50 @@ class TcTokenStore {
166
166
  return true;
167
167
  }
168
168
 
169
+ /**
170
+ * Fold an entry filed under one JID into the entry for another.
171
+ *
172
+ * The same contact can end up with two entries — one under their phone JID
173
+ * and one under their LID — when a send happens before the LID mapping is
174
+ * known. That is not just untidy: whichever key the next send resolves to is
175
+ * the only one it looks at, so a live token under the LID goes unused and the
176
+ * message ships without one.
177
+ *
178
+ * The live token wins, newest first. The sender timestamp takes the newer of
179
+ * the two, since an older one would say we are due to issue again when we are
180
+ * not.
181
+ *
182
+ * @returns {boolean} whether anything was moved
183
+ */
184
+ mergeEntry(fromJid, toJid) {
185
+ const fromKey = normalizeJidForTcToken(fromJid);
186
+ const toKey = normalizeJidForTcToken(toJid);
187
+ if (fromKey === toKey) return false;
188
+
189
+ const from = this._map.get(fromKey);
190
+ if (!from) return false;
191
+
192
+ const to = this._map.get(toKey) || {};
193
+
194
+ const fromLive = !!(from.token && from.token.length) && !tcTokenExpired(from.timestamp);
195
+ const toLive = !!(to.token && to.token.length) && !tcTokenExpired(to.timestamp);
196
+
197
+ let token = to.token != null ? to.token : null;
198
+ let timestamp = to.timestamp != null ? to.timestamp : null;
199
+ if (fromLive && (!toLive || Number(from.timestamp) > Number(to.timestamp || 0))) {
200
+ token = from.token;
201
+ timestamp = from.timestamp;
202
+ }
203
+
204
+ const senderTimestamp =
205
+ Math.max(Number(to.senderTimestamp || 0), Number(from.senderTimestamp || 0)) || null;
206
+
207
+ this._map.set(toKey, { token, timestamp, senderTimestamp });
208
+ this._map.delete(fromKey);
209
+ this._save();
210
+ return true;
211
+ }
212
+
169
213
  clearToken(jid) {
170
214
  const key = normalizeJidForTcToken(jid);
171
215
  const existing = this._map.get(key) || {};
@@ -281,6 +281,34 @@ function encodeContactMessage(opts) {
281
281
  return Buffer.concat(parts);
282
282
  }
283
283
 
284
+ // ─── GroupInviteMessage (field 28 of Message) ────────────────────────────────
285
+ //
286
+ // The bubble you send someone directly when they cannot simply be added to a
287
+ // group: it carries the group's JID plus a one-off invite code and the moment
288
+ // that code stops working. Tapping it makes their client send the accept IQ.
289
+ //
290
+ // GroupInviteMessage { groupJid=1, inviteCode=2, inviteExpiration=3,
291
+ // groupName=4, jpegThumbnail=5, caption=6, contextInfo=7,
292
+ // groupType=8 }
293
+ //
294
+ // groupType is 0 for an ordinary group and 1 for a community.
295
+ const GROUP_INVITE_TYPE_DEFAULT = 0;
296
+ const GROUP_INVITE_TYPE_PARENT = 1;
297
+
298
+ function encodeGroupInviteMessage(opts) {
299
+ const parts = [
300
+ field(1, WIRE_LEN, str(opts.groupJid || '')),
301
+ field(2, WIRE_LEN, str(opts.inviteCode || '')),
302
+ field(3, WIRE_VARINT, varint(Number(opts.inviteExpiration) || 0))
303
+ ];
304
+ if (opts.groupName) parts.push(field(4, WIRE_LEN, str(opts.groupName)));
305
+ if (opts.jpegThumbnail) parts.push(field(5, WIRE_LEN, bytes(opts.jpegThumbnail)));
306
+ if (opts.caption) parts.push(field(6, WIRE_LEN, str(opts.caption)));
307
+ if (opts.contextInfo) parts.push(field(7, WIRE_LEN, encodeContextInfo(opts.contextInfo)));
308
+ if (opts.groupType) parts.push(field(8, WIRE_VARINT, varint(Number(opts.groupType) || 0)));
309
+ return Buffer.concat(parts);
310
+ }
311
+
284
312
  function encodeReactionMessage(opts) {
285
313
  const parts = [
286
314
  field(1, WIRE_LEN, encodeMessageKey(opts.key)),
@@ -306,6 +334,7 @@ function encodeMessage(type, payload) {
306
334
  case 'audio': return field(8, WIRE_LEN, payload);
307
335
  case 'video': return field(9, WIRE_LEN, payload);
308
336
  case 'sticker': return field(26, WIRE_LEN, payload);
337
+ case 'groupInvite': return field(28, WIRE_LEN, payload);
309
338
  case 'reaction': return field(46, WIRE_LEN, payload);
310
339
  case 'protocol': return field(12, WIRE_LEN, payload);
311
340
  // extendedTextMessage is field 6. It used to be written to field 2, which is
@@ -548,6 +577,27 @@ function decodeMessageContainer(buf) {
548
577
  } catch (_) { msgResult = { type: 'location', latitude: 0, longitude: 0 }; }
549
578
  }
550
579
 
580
+ // Field 28: groupInviteMessage — groupJid=1, inviteCode=2, inviteExpiration=3,
581
+ // groupName=4, jpegThumbnail=5, caption=6, groupType=8
582
+ //
583
+ // Everything acceptGroupInviteMessage() needs is in here, so an invite that
584
+ // arrives can be acted on without the caller re-parsing the payload.
585
+ if (!msgResult && f[28] && Buffer.isBuffer(f[28])) {
586
+ try {
587
+ const inv = _decodeFields(f[28]);
588
+ msgResult = {
589
+ type: 'groupInvite',
590
+ groupJid: _str(inv[1]),
591
+ inviteCode: _str(inv[2]),
592
+ inviteExpiration: _num(inv[3]),
593
+ groupName: _str(inv[4]),
594
+ jpegThumbnail: inv[5] || null,
595
+ caption: _str(inv[6]),
596
+ isCommunity: _num(inv[8]) === 1
597
+ };
598
+ } catch (_) { msgResult = { type: 'groupInvite', groupJid: '', inviteCode: '' }; }
599
+ }
600
+
551
601
  // Field 7: documentMessage
552
602
  if (!msgResult && f[7] && Buffer.isBuffer(f[7])) {
553
603
  try {
@@ -757,6 +807,7 @@ module.exports = {
757
807
  encodePollCreationMessage,
758
808
  encodeLocationMessage,
759
809
  encodeContactMessage,
810
+ encodeGroupInviteMessage,
760
811
  encodeMessageKey,
761
812
  encodeMessage,
762
813
  encodeDeviceSentMessage,
@@ -766,6 +817,8 @@ module.exports = {
766
817
  encodeSenderKeyDistributionMessage,
767
818
  encodeMessageContextInfo,
768
819
  encodeVarint,
820
+ // The generic field walker, for the protobufs that live outside this file.
821
+ decodeFields: _decodeFields,
769
822
  field,
770
823
  varint,
771
824
  str,
@@ -774,5 +827,7 @@ module.exports = {
774
827
  WIRE_LEN,
775
828
  PROTOCOL_MSG_REVOKE,
776
829
  PROTOCOL_MSG_EPHEMERAL,
830
+ GROUP_INVITE_TYPE_DEFAULT,
831
+ GROUP_INVITE_TYPE_PARENT,
777
832
  decodeMessageContainer
778
833
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whalibmob",
3
- "version": "5.7.2",
3
+ "version": "5.9.0",
4
4
  "description": "WhatsApp library for interaction with WhatsApp Mobile API no web",
5
5
  "author": "Kunboruto50",
6
6
  "main": "index.js",