Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
MR414N-ID
GitHub Repository: MR414N-ID/botku2
Path: blob/master/node_modules/@adiwajshing/baileys/lib/Socket/chats.js
1129 views
1
"use strict";
2
var __importDefault = (this && this.__importDefault) || function (mod) {
3
return (mod && mod.__esModule) ? mod : { "default": mod };
4
};
5
Object.defineProperty(exports, "__esModule", { value: true });
6
exports.makeChatsSocket = void 0;
7
const boom_1 = require("@hapi/boom");
8
const WAProto_1 = require("../../WAProto");
9
const Types_1 = require("../Types");
10
const Utils_1 = require("../Utils");
11
const make_mutex_1 = require("../Utils/make-mutex");
12
const process_message_1 = __importDefault(require("../Utils/process-message"));
13
const WABinary_1 = require("../WABinary");
14
const socket_1 = require("./socket");
15
const MAX_SYNC_ATTEMPTS = 5;
16
const APP_STATE_SYNC_TIMEOUT_MS = 10000;
17
const makeChatsSocket = (config) => {
18
const { logger, markOnlineOnConnect, downloadHistory, fireInitQueries } = config;
19
const sock = (0, socket_1.makeSocket)(config);
20
const { ev, ws, authState, generateMessageTag, sendNode, query, onUnexpectedError, } = sock;
21
let privacySettings;
22
/** this mutex ensures that the notifications (receipts, messages etc.) are processed in order */
23
const processingMutex = (0, make_mutex_1.makeMutex)();
24
/** cache to ensure new history sync events do not have duplicate items */
25
const historyCache = new Set();
26
let recvChats = {};
27
const appStateSyncTimeout = (0, Utils_1.debouncedTimeout)(APP_STATE_SYNC_TIMEOUT_MS, async () => {
28
if (ws.readyState === ws.OPEN) {
29
logger.info({ recvChats: Object.keys(recvChats).length }, 'doing initial app state sync');
30
await resyncMainAppState(recvChats);
31
}
32
else {
33
logger.warn('connection closed before app state sync');
34
}
35
historyCache.clear();
36
recvChats = {};
37
});
38
/** helper function to fetch the given app state sync key */
39
const getAppStateSyncKey = async (keyId) => {
40
const { [keyId]: key } = await authState.keys.get('app-state-sync-key', [keyId]);
41
return key;
42
};
43
const fetchPrivacySettings = async (force = false) => {
44
if (!privacySettings || force) {
45
const { content } = await query({
46
tag: 'iq',
47
attrs: {
48
xmlns: 'privacy',
49
to: WABinary_1.S_WHATSAPP_NET,
50
type: 'get'
51
},
52
content: [
53
{ tag: 'privacy', attrs: {} }
54
]
55
});
56
privacySettings = (0, WABinary_1.reduceBinaryNodeToDictionary)(content === null || content === void 0 ? void 0 : content[0], 'category');
57
}
58
return privacySettings;
59
};
60
/** helper function to run a generic IQ query */
61
const interactiveQuery = async (userNodes, queryNode) => {
62
const result = await query({
63
tag: 'iq',
64
attrs: {
65
to: WABinary_1.S_WHATSAPP_NET,
66
type: 'get',
67
xmlns: 'usync',
68
},
69
content: [
70
{
71
tag: 'usync',
72
attrs: {
73
sid: generateMessageTag(),
74
mode: 'query',
75
last: 'true',
76
index: '0',
77
context: 'interactive',
78
},
79
content: [
80
{
81
tag: 'query',
82
attrs: {},
83
content: [queryNode]
84
},
85
{
86
tag: 'list',
87
attrs: {},
88
content: userNodes
89
}
90
]
91
}
92
],
93
});
94
const usyncNode = (0, WABinary_1.getBinaryNodeChild)(result, 'usync');
95
const listNode = (0, WABinary_1.getBinaryNodeChild)(usyncNode, 'list');
96
const users = (0, WABinary_1.getBinaryNodeChildren)(listNode, 'user');
97
return users;
98
};
99
const onWhatsApp = async (...jids) => {
100
const results = await interactiveQuery([
101
{
102
tag: 'user',
103
attrs: {},
104
content: jids.map(jid => ({
105
tag: 'contact',
106
attrs: {},
107
content: `+${jid}`
108
}))
109
}
110
], { tag: 'contact', attrs: {} });
111
return results.map(user => {
112
const contact = (0, WABinary_1.getBinaryNodeChild)(user, 'contact');
113
return { exists: (contact === null || contact === void 0 ? void 0 : contact.attrs.type) === 'in', jid: user.attrs.jid };
114
}).filter(item => item.exists);
115
};
116
const fetchStatus = async (jid) => {
117
const [result] = await interactiveQuery([{ tag: 'user', attrs: { jid } }], { tag: 'status', attrs: {} });
118
if (result) {
119
const status = (0, WABinary_1.getBinaryNodeChild)(result, 'status');
120
return {
121
status: status === null || status === void 0 ? void 0 : status.content.toString(),
122
setAt: new Date(+((status === null || status === void 0 ? void 0 : status.attrs.t) || 0) * 1000)
123
};
124
}
125
};
126
/** update the profile picture for yourself or a group */
127
const updateProfilePicture = async (jid, content) => {
128
const { img } = await (0, Utils_1.generateProfilePicture)(content);
129
await query({
130
tag: 'iq',
131
attrs: {
132
to: (0, WABinary_1.jidNormalizedUser)(jid),
133
type: 'set',
134
xmlns: 'w:profile:picture'
135
},
136
content: [
137
{
138
tag: 'picture',
139
attrs: { type: 'image' },
140
content: img
141
}
142
]
143
});
144
};
145
/** update the profile status for yourself */
146
const updateProfileStatus = async (status) => {
147
await query({
148
tag: 'iq',
149
attrs: {
150
to: WABinary_1.S_WHATSAPP_NET,
151
type: 'set',
152
xmlns: 'status'
153
},
154
content: [
155
{
156
tag: 'status',
157
attrs: {},
158
content: Buffer.from(status, 'utf-8')
159
}
160
]
161
});
162
};
163
const updateProfileName = async (name) => {
164
await chatModify({ pushNameSetting: name }, '');
165
};
166
const fetchBlocklist = async () => {
167
const result = await query({
168
tag: 'iq',
169
attrs: {
170
xmlns: 'blocklist',
171
to: WABinary_1.S_WHATSAPP_NET,
172
type: 'get'
173
}
174
});
175
const listNode = (0, WABinary_1.getBinaryNodeChild)(result, 'list');
176
return (0, WABinary_1.getBinaryNodeChildren)(listNode, 'item')
177
.map(n => n.attrs.jid);
178
};
179
const updateBlockStatus = async (jid, action) => {
180
await query({
181
tag: 'iq',
182
attrs: {
183
xmlns: 'blocklist',
184
to: WABinary_1.S_WHATSAPP_NET,
185
type: 'set'
186
},
187
content: [
188
{
189
tag: 'item',
190
attrs: {
191
action,
192
jid
193
}
194
}
195
]
196
});
197
};
198
const getBusinessProfile = async (jid) => {
199
var _a, _b, _c, _d, _e, _f, _g;
200
const results = await query({
201
tag: 'iq',
202
attrs: {
203
to: 's.whatsapp.net',
204
xmlns: 'w:biz',
205
type: 'get'
206
},
207
content: [{
208
tag: 'business_profile',
209
attrs: { v: '244' },
210
content: [{
211
tag: 'profile',
212
attrs: { jid }
213
}]
214
}]
215
});
216
const profileNode = (0, WABinary_1.getBinaryNodeChild)(results, 'business_profile');
217
const profiles = (0, WABinary_1.getBinaryNodeChild)(profileNode, 'profile');
218
if (profiles) {
219
const address = (0, WABinary_1.getBinaryNodeChild)(profiles, 'address');
220
const description = (0, WABinary_1.getBinaryNodeChild)(profiles, 'description');
221
const website = (0, WABinary_1.getBinaryNodeChild)(profiles, 'website');
222
const email = (0, WABinary_1.getBinaryNodeChild)(profiles, 'email');
223
const category = (0, WABinary_1.getBinaryNodeChild)((0, WABinary_1.getBinaryNodeChild)(profiles, 'categories'), 'category');
224
const business_hours = (0, WABinary_1.getBinaryNodeChild)(profiles, 'business_hours');
225
const business_hours_config = business_hours && (0, WABinary_1.getBinaryNodeChildren)(business_hours, 'business_hours_config');
226
const websiteStr = (_a = website === null || website === void 0 ? void 0 : website.content) === null || _a === void 0 ? void 0 : _a.toString();
227
return {
228
wid: (_b = profiles.attrs) === null || _b === void 0 ? void 0 : _b.jid,
229
address: (_c = address === null || address === void 0 ? void 0 : address.content) === null || _c === void 0 ? void 0 : _c.toString(),
230
description: ((_d = description === null || description === void 0 ? void 0 : description.content) === null || _d === void 0 ? void 0 : _d.toString()) || '',
231
website: websiteStr ? [websiteStr] : [],
232
email: (_e = email === null || email === void 0 ? void 0 : email.content) === null || _e === void 0 ? void 0 : _e.toString(),
233
category: (_f = category === null || category === void 0 ? void 0 : category.content) === null || _f === void 0 ? void 0 : _f.toString(),
234
business_hours: {
235
timezone: (_g = business_hours === null || business_hours === void 0 ? void 0 : business_hours.attrs) === null || _g === void 0 ? void 0 : _g.timezone,
236
business_config: business_hours_config === null || business_hours_config === void 0 ? void 0 : business_hours_config.map(({ attrs }) => attrs)
237
}
238
};
239
}
240
};
241
const updateAccountSyncTimestamp = async (fromTimestamp) => {
242
logger.info({ fromTimestamp }, 'requesting account sync');
243
await sendNode({
244
tag: 'iq',
245
attrs: {
246
to: WABinary_1.S_WHATSAPP_NET,
247
type: 'set',
248
xmlns: 'urn:xmpp:whatsapp:dirty',
249
id: generateMessageTag(),
250
},
251
content: [
252
{
253
tag: 'clean',
254
attrs: {
255
type: 'account_sync',
256
timestamp: fromTimestamp.toString(),
257
}
258
}
259
]
260
});
261
};
262
const newAppStateChunkHandler = (recvChats) => {
263
return {
264
onMutation(mutation) {
265
(0, Utils_1.processSyncAction)(mutation, ev, authState.creds.me, recvChats ? { recvChats, accountSettings: authState.creds.accountSettings } : undefined, logger);
266
}
267
};
268
};
269
const resyncAppState = ev.createBufferedFunction(async (collections, recvChats) => {
270
const { onMutation } = newAppStateChunkHandler(recvChats);
271
// we use this to determine which events to fire
272
// otherwise when we resync from scratch -- all notifications will fire
273
const initialVersionMap = {};
274
await authState.keys.transaction(async () => {
275
var _a;
276
const collectionsToHandle = new Set(collections);
277
// in case something goes wrong -- ensure we don't enter a loop that cannot be exited from
278
const attemptsMap = {};
279
// keep executing till all collections are done
280
// sometimes a single patch request will not return all the patches (God knows why)
281
// so we fetch till they're all done (this is determined by the "has_more_patches" flag)
282
while (collectionsToHandle.size) {
283
const states = {};
284
const nodes = [];
285
for (const name of collectionsToHandle) {
286
const result = await authState.keys.get('app-state-sync-version', [name]);
287
let state = result[name];
288
if (state) {
289
if (typeof initialVersionMap[name] === 'undefined') {
290
initialVersionMap[name] = state.version;
291
}
292
}
293
else {
294
state = (0, Utils_1.newLTHashState)();
295
}
296
states[name] = state;
297
logger.info(`resyncing ${name} from v${state.version}`);
298
nodes.push({
299
tag: 'collection',
300
attrs: {
301
name,
302
version: state.version.toString(),
303
// return snapshot if being synced from scratch
304
return_snapshot: (!state.version).toString()
305
}
306
});
307
}
308
const result = await query({
309
tag: 'iq',
310
attrs: {
311
to: WABinary_1.S_WHATSAPP_NET,
312
xmlns: 'w:sync:app:state',
313
type: 'set'
314
},
315
content: [
316
{
317
tag: 'sync',
318
attrs: {},
319
content: nodes
320
}
321
]
322
});
323
const decoded = await (0, Utils_1.extractSyncdPatches)(result); // extract from binary node
324
for (const key in decoded) {
325
const name = key;
326
const { patches, hasMorePatches, snapshot } = decoded[name];
327
try {
328
if (snapshot) {
329
const { state: newState } = await (0, Utils_1.decodeSyncdSnapshot)(name, snapshot, getAppStateSyncKey, initialVersionMap[name], onMutation);
330
states[name] = newState;
331
logger.info(`restored state of ${name} from snapshot to v${newState.version} with mutations`);
332
await authState.keys.set({ 'app-state-sync-version': { [name]: newState } });
333
}
334
// only process if there are syncd patches
335
if (patches.length) {
336
const { newMutations, state: newState } = await (0, Utils_1.decodePatches)(name, patches, states[name], getAppStateSyncKey, onMutation, initialVersionMap[name]);
337
await authState.keys.set({ 'app-state-sync-version': { [name]: newState } });
338
logger.info(`synced ${name} to v${newState.version}`);
339
if (newMutations.length) {
340
logger.trace({ newMutations, name }, 'recv new mutations');
341
}
342
}
343
if (hasMorePatches) {
344
logger.info(`${name} has more patches...`);
345
}
346
else { // collection is done with sync
347
collectionsToHandle.delete(name);
348
}
349
}
350
catch (error) {
351
// if retry attempts overshoot
352
// or key not found
353
const isIrrecoverableError = attemptsMap[name] >= MAX_SYNC_ATTEMPTS || ((_a = error.output) === null || _a === void 0 ? void 0 : _a.statusCode) === 404;
354
logger.info({ name, error: error.stack }, `failed to sync state from version${isIrrecoverableError ? '' : ', removing and trying from scratch'}`);
355
await authState.keys.set({ 'app-state-sync-version': { [name]: null } });
356
// increment number of retries
357
attemptsMap[name] = (attemptsMap[name] || 0) + 1;
358
if (isIrrecoverableError) {
359
// stop retrying
360
collectionsToHandle.delete(name);
361
}
362
}
363
}
364
}
365
});
366
});
367
/**
368
* fetch the profile picture of a user/group
369
* type = "preview" for a low res picture
370
* type = "image for the high res picture"
371
*/
372
const profilePictureUrl = async (jid, type = 'preview', timeoutMs) => {
373
var _a;
374
jid = (0, WABinary_1.jidNormalizedUser)(jid);
375
const result = await query({
376
tag: 'iq',
377
attrs: {
378
to: jid,
379
type: 'get',
380
xmlns: 'w:profile:picture'
381
},
382
content: [
383
{ tag: 'picture', attrs: { type, query: 'url' } }
384
]
385
}, timeoutMs);
386
const child = (0, WABinary_1.getBinaryNodeChild)(result, 'picture');
387
return (_a = child === null || child === void 0 ? void 0 : child.attrs) === null || _a === void 0 ? void 0 : _a.url;
388
};
389
const sendPresenceUpdate = async (type, toJid) => {
390
const me = authState.creds.me;
391
if (type === 'available' || type === 'unavailable') {
392
if (!me.name) {
393
logger.warn('no name present, ignoring presence update request...');
394
return;
395
}
396
ev.emit('connection.update', { isOnline: type === 'available' });
397
await sendNode({
398
tag: 'presence',
399
attrs: {
400
name: me.name,
401
type
402
}
403
});
404
}
405
else {
406
await sendNode({
407
tag: 'chatstate',
408
attrs: {
409
from: me.id,
410
to: toJid,
411
},
412
content: [
413
{
414
tag: type === 'recording' ? 'composing' : type,
415
attrs: type === 'recording' ? { media: 'audio' } : {}
416
}
417
]
418
});
419
}
420
};
421
const presenceSubscribe = (toJid) => (sendNode({
422
tag: 'presence',
423
attrs: {
424
to: toJid,
425
id: generateMessageTag(),
426
type: 'subscribe'
427
}
428
}));
429
const handlePresenceUpdate = ({ tag, attrs, content }) => {
430
var _a;
431
let presence;
432
const jid = attrs.from;
433
const participant = attrs.participant || attrs.from;
434
if (tag === 'presence') {
435
presence = {
436
lastKnownPresence: attrs.type === 'unavailable' ? 'unavailable' : 'available',
437
lastSeen: attrs.last && attrs.last !== 'deny' ? +attrs.last : undefined
438
};
439
}
440
else if (Array.isArray(content)) {
441
const [firstChild] = content;
442
let type = firstChild.tag;
443
if (type === 'paused') {
444
type = 'available';
445
}
446
if (((_a = firstChild.attrs) === null || _a === void 0 ? void 0 : _a.media) === 'audio') {
447
type = 'recording';
448
}
449
presence = { lastKnownPresence: type };
450
}
451
else {
452
logger.error({ tag, attrs, content }, 'recv invalid presence node');
453
}
454
if (presence) {
455
ev.emit('presence.update', { id: jid, presences: { [participant]: presence } });
456
}
457
};
458
const resyncMainAppState = async (ctx) => {
459
logger.debug('resyncing main app state');
460
await (processingMutex.mutex(() => resyncAppState(Types_1.ALL_WA_PATCH_NAMES, ctx))
461
.catch(err => (onUnexpectedError(err, 'main app sync'))));
462
};
463
const appPatch = async (patchCreate) => {
464
const name = patchCreate.type;
465
const myAppStateKeyId = authState.creds.myAppStateKeyId;
466
if (!myAppStateKeyId) {
467
throw new boom_1.Boom('App state key not present!', { statusCode: 400 });
468
}
469
let initial;
470
let encodeResult;
471
await processingMutex.mutex(async () => {
472
await authState.keys.transaction(async () => {
473
logger.debug({ patch: patchCreate }, 'applying app patch');
474
await resyncAppState([name], undefined);
475
const { [name]: currentSyncVersion } = await authState.keys.get('app-state-sync-version', [name]);
476
initial = currentSyncVersion || (0, Utils_1.newLTHashState)();
477
encodeResult = await (0, Utils_1.encodeSyncdPatch)(patchCreate, myAppStateKeyId, initial, getAppStateSyncKey);
478
const { patch, state } = encodeResult;
479
const node = {
480
tag: 'iq',
481
attrs: {
482
to: WABinary_1.S_WHATSAPP_NET,
483
type: 'set',
484
xmlns: 'w:sync:app:state'
485
},
486
content: [
487
{
488
tag: 'sync',
489
attrs: {},
490
content: [
491
{
492
tag: 'collection',
493
attrs: {
494
name,
495
version: (state.version - 1).toString(),
496
return_snapshot: 'false'
497
},
498
content: [
499
{
500
tag: 'patch',
501
attrs: {},
502
content: WAProto_1.proto.SyncdPatch.encode(patch).finish()
503
}
504
]
505
}
506
]
507
}
508
]
509
};
510
await query(node);
511
await authState.keys.set({ 'app-state-sync-version': { [name]: state } });
512
});
513
});
514
if (config.emitOwnEvents) {
515
const { onMutation } = newAppStateChunkHandler(undefined);
516
await (0, Utils_1.decodePatches)(name, [{ ...encodeResult.patch, version: { version: encodeResult.state.version }, }], initial, getAppStateSyncKey, onMutation, undefined, logger);
517
}
518
};
519
/** sending abt props may fix QR scan fail if server expects */
520
const fetchAbt = async () => {
521
const abtNode = await query({
522
tag: 'iq',
523
attrs: {
524
to: WABinary_1.S_WHATSAPP_NET,
525
xmlns: 'abt',
526
type: 'get',
527
},
528
content: [
529
{ tag: 'props', attrs: { protocol: '1' } }
530
]
531
});
532
const propsNode = (0, WABinary_1.getBinaryNodeChild)(abtNode, 'props');
533
let props = {};
534
if (propsNode) {
535
props = (0, WABinary_1.reduceBinaryNodeToDictionary)(propsNode, 'prop');
536
}
537
logger.debug('fetched abt');
538
return props;
539
};
540
/** sending non-abt props may fix QR scan fail if server expects */
541
const fetchProps = async () => {
542
const resultNode = await query({
543
tag: 'iq',
544
attrs: {
545
to: WABinary_1.S_WHATSAPP_NET,
546
xmlns: 'w',
547
type: 'get',
548
},
549
content: [
550
{ tag: 'props', attrs: {} }
551
]
552
});
553
const propsNode = (0, WABinary_1.getBinaryNodeChild)(resultNode, 'props');
554
let props = {};
555
if (propsNode) {
556
props = (0, WABinary_1.reduceBinaryNodeToDictionary)(propsNode, 'prop');
557
}
558
logger.debug('fetched props');
559
return props;
560
};
561
/**
562
* modify a chat -- mark unread, read etc.
563
* lastMessages must be sorted in reverse chronologically
564
* requires the last messages till the last message received; required for archive & unread
565
*/
566
const chatModify = (mod, jid) => {
567
const patch = (0, Utils_1.chatModificationToAppPatch)(mod, jid);
568
return appPatch(patch);
569
};
570
/**
571
* queries need to be fired on connection open
572
* help ensure parity with WA Web
573
* */
574
const executeInitQueries = async () => {
575
await Promise.all([
576
fetchAbt(),
577
fetchProps(),
578
fetchBlocklist(),
579
fetchPrivacySettings(),
580
]);
581
};
582
const upsertMessage = ev.createBufferedFunction(async (msg, type) => {
583
var _a;
584
ev.emit('messages.upsert', { messages: [msg], type });
585
if (!!msg.pushName) {
586
let jid = msg.key.fromMe ? authState.creds.me.id : (msg.key.participant || msg.key.remoteJid);
587
jid = (0, WABinary_1.jidNormalizedUser)(jid);
588
if (!msg.key.fromMe) {
589
ev.emit('contacts.update', [{ id: jid, notify: msg.pushName, verifiedName: msg.verifiedBizName }]);
590
}
591
// update our pushname too
592
if (msg.key.fromMe && ((_a = authState.creds.me) === null || _a === void 0 ? void 0 : _a.name) !== msg.pushName) {
593
ev.emit('creds.update', { me: { ...authState.creds.me, name: msg.pushName } });
594
}
595
}
596
// process message and emit events
597
await (0, process_message_1.default)(msg, {
598
downloadHistory,
599
ev,
600
historyCache,
601
recvChats,
602
creds: authState.creds,
603
keyStore: authState.keys,
604
logger,
605
});
606
const isAnyHistoryMsg = (0, Utils_1.isHistoryMsg)(msg.message);
607
if (isAnyHistoryMsg) {
608
// we only want to sync app state once we've all the history
609
// restart the app state sync timeout
610
logger.debug('restarting app sync timeout');
611
appStateSyncTimeout.start();
612
}
613
});
614
ws.on('CB:presence', handlePresenceUpdate);
615
ws.on('CB:chatstate', handlePresenceUpdate);
616
ws.on('CB:ib,,dirty', async (node) => {
617
const { attrs } = (0, WABinary_1.getBinaryNodeChild)(node, 'dirty');
618
const type = attrs.type;
619
switch (type) {
620
case 'account_sync':
621
if (attrs.timestamp) {
622
let { lastAccountSyncTimestamp } = authState.creds;
623
if (lastAccountSyncTimestamp) {
624
await updateAccountSyncTimestamp(lastAccountSyncTimestamp);
625
}
626
lastAccountSyncTimestamp = +attrs.timestamp;
627
ev.emit('creds.update', { lastAccountSyncTimestamp });
628
}
629
break;
630
default:
631
logger.info({ node }, 'received unknown sync');
632
break;
633
}
634
});
635
ev.on('connection.update', ({ connection }) => {
636
if (connection === 'open') {
637
if (fireInitQueries) {
638
executeInitQueries()
639
.catch(error => onUnexpectedError(error, 'init queries'));
640
}
641
sendPresenceUpdate(markOnlineOnConnect ? 'available' : 'unavailable')
642
.catch(error => onUnexpectedError(error, 'presence update requests'));
643
}
644
});
645
return {
646
...sock,
647
processingMutex,
648
fetchPrivacySettings,
649
upsertMessage,
650
appPatch,
651
sendPresenceUpdate,
652
presenceSubscribe,
653
profilePictureUrl,
654
onWhatsApp,
655
fetchBlocklist,
656
fetchStatus,
657
updateProfilePicture,
658
updateProfileStatus,
659
updateProfileName,
660
updateBlockStatus,
661
getBusinessProfile,
662
resyncAppState,
663
chatModify,
664
resyncMainAppState,
665
};
666
};
667
exports.makeChatsSocket = makeChatsSocket;
668
669