@crysnovax/baileys 2.6.7 → 2.6.9
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 +282 -142
- package/lib/Signal/lid-mapping.js +8 -4
- package/lib/Socket/business.js +70 -42
- package/lib/Socket/chats.js +10 -2
- package/lib/Socket/groups.js +84 -6
- package/lib/Socket/messages-send.js +311 -138
- package/lib/Socket/{messages-send.js[failed] → messages-send.js[latest]} +14 -17
- package/lib/Socket/{messages-send.js[old] → messages-send.js[working]} +117 -61
- package/lib/Socket/newsletter.js +1 -26
- package/lib/Socket/socket.js +54 -0
- package/lib/Utils/messages.js +12 -6
- package/lib/Utils/meta-compositing.js +6 -4
- package/lib/index.d.ts +59 -0
- package/package.json +10 -8
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { LRUCache } from 'lru-cache';
|
|
2
|
-
import { isHostedPnUser, isLidUser, isPnUser, jidDecode, jidNormalizedUser, WAJIDDomains } from '../WABinary/index.js';
|
|
2
|
+
import { isHostedLidUser, isHostedPnUser, isLidUser, isPnUser, jidDecode, jidNormalizedUser, WAJIDDomains } from '../WABinary/index.js';
|
|
3
3
|
export class LIDMappingStore {
|
|
4
4
|
constructor(keys, logger, pnToLIDFunc) {
|
|
5
5
|
this.mappingCache = new LRUCache({
|
|
@@ -226,13 +226,17 @@ export class LIDMappingStore {
|
|
|
226
226
|
return false;
|
|
227
227
|
}
|
|
228
228
|
const lidDevice = decoded.device !== undefined ? decoded.device : 0;
|
|
229
|
-
const
|
|
229
|
+
const deviceSuffix = lidDevice ? `:${lidDevice}` : '';
|
|
230
|
+
const pnServer = decoded.domainType === WAJIDDomains.HOSTED_LID || decoded.server === 'hosted.lid'
|
|
231
|
+
? 'hosted'
|
|
232
|
+
: 's.whatsapp.net';
|
|
233
|
+
const pnJid = `${pnUser}${deviceSuffix}@${pnServer}`;
|
|
230
234
|
this.logger.trace(`Found reverse mapping: ${lid} → ${pnJid}`);
|
|
231
235
|
successfulPairs[lid] = { lid, pn: pnJid };
|
|
232
236
|
return true;
|
|
233
237
|
};
|
|
234
238
|
for (const lid of lids) {
|
|
235
|
-
if (!isLidUser(lid))
|
|
239
|
+
if (!isLidUser(lid) && !isHostedLidUser(lid))
|
|
236
240
|
continue;
|
|
237
241
|
const decoded = jidDecode(lid);
|
|
238
242
|
if (!decoded)
|
|
@@ -273,4 +277,4 @@ export class LIDMappingStore {
|
|
|
273
277
|
close() {
|
|
274
278
|
this.mappingCache.clear();
|
|
275
279
|
}
|
|
276
|
-
}
|
|
280
|
+
}
|
package/lib/Socket/business.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { unlink } from 'node:fs/promises';
|
|
1
2
|
import { getRawMediaUploadData } from '../Utils/index.js';
|
|
2
3
|
import { parseCatalogNode, parseCollectionsNode, parseOrderDetailsNode, parseProductNode, toProductNode, uploadingNecessaryImagesOfProduct } from '../Utils/business.js';
|
|
3
4
|
import { jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary/index.js';
|
|
@@ -6,28 +7,42 @@ import { makeMessagesRecvSocket } from './messages-recv.js';
|
|
|
6
7
|
export const makeBusinessSocket = (config) => {
|
|
7
8
|
const sock = makeMessagesRecvSocket(config);
|
|
8
9
|
const { authState, query, waUploadToServer } = sock;
|
|
9
|
-
const
|
|
10
|
+
const updateBusinessProfile = async (args = {}) => {
|
|
10
11
|
const node = [];
|
|
11
12
|
const simpleFields = ['address', 'email', 'description'];
|
|
12
13
|
node.push(...simpleFields
|
|
13
|
-
.filter(key => args[key] !== undefined
|
|
14
|
-
.map(key =>
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
.filter(key => args[key] !== undefined)
|
|
15
|
+
.map(key => {
|
|
16
|
+
if (args[key] !== null && typeof args[key] !== 'string') {
|
|
17
|
+
throw new TypeError(`${key} must be a string or null`);
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
tag: key,
|
|
21
|
+
attrs: {},
|
|
22
|
+
content: args[key] ?? ''
|
|
23
|
+
};
|
|
24
|
+
}));
|
|
19
25
|
if (args.websites !== undefined) {
|
|
20
|
-
|
|
26
|
+
if (!Array.isArray(args.websites) || args.websites.some(website => typeof website !== 'string')) {
|
|
27
|
+
throw new TypeError('websites must be an array of strings');
|
|
28
|
+
}
|
|
29
|
+
node.push(...(args.websites.length ? args.websites : ['']).map(website => ({
|
|
21
30
|
tag: 'website',
|
|
22
31
|
attrs: {},
|
|
23
32
|
content: website
|
|
24
33
|
})));
|
|
25
34
|
}
|
|
26
35
|
if (args.hours !== undefined) {
|
|
36
|
+
if (!args.hours || typeof args.hours.timezone !== 'string' || !Array.isArray(args.hours.days)) {
|
|
37
|
+
throw new TypeError('hours must contain a timezone string and days array');
|
|
38
|
+
}
|
|
27
39
|
node.push({
|
|
28
40
|
tag: 'business_hours',
|
|
29
41
|
attrs: { timezone: args.hours.timezone },
|
|
30
42
|
content: args.hours.days.map(dayConfig => {
|
|
43
|
+
if (!dayConfig || typeof dayConfig.day !== 'string' || typeof dayConfig.mode !== 'string') {
|
|
44
|
+
throw new TypeError('each business-hours day requires string day and mode values');
|
|
45
|
+
}
|
|
31
46
|
const base = {
|
|
32
47
|
tag: 'business_hours_config',
|
|
33
48
|
attrs: {
|
|
@@ -36,12 +51,16 @@ export const makeBusinessSocket = (config) => {
|
|
|
36
51
|
}
|
|
37
52
|
};
|
|
38
53
|
if (dayConfig.mode === 'specific_hours') {
|
|
54
|
+
const { openTimeInMinutes, closeTimeInMinutes } = dayConfig;
|
|
55
|
+
if (!Number.isInteger(openTimeInMinutes) || !Number.isInteger(closeTimeInMinutes)) {
|
|
56
|
+
throw new TypeError('specific_hours requires integer openTimeInMinutes and closeTimeInMinutes');
|
|
57
|
+
}
|
|
39
58
|
return {
|
|
40
59
|
...base,
|
|
41
60
|
attrs: {
|
|
42
61
|
...base.attrs,
|
|
43
|
-
open_time:
|
|
44
|
-
close_time:
|
|
62
|
+
open_time: String(openTimeInMinutes),
|
|
63
|
+
close_time: String(closeTimeInMinutes)
|
|
45
64
|
}
|
|
46
65
|
};
|
|
47
66
|
}
|
|
@@ -70,36 +89,45 @@ export const makeBusinessSocket = (config) => {
|
|
|
70
89
|
return result;
|
|
71
90
|
};
|
|
72
91
|
const updateCoverPhoto = async (photo) => {
|
|
73
|
-
const { fileSha256, filePath } = await getRawMediaUploadData(photo, 'biz-cover-photo');
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
{
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
92
|
+
const { fileSha256, filePath } = await getRawMediaUploadData(photo, 'biz-cover-photo', config.logger);
|
|
93
|
+
try {
|
|
94
|
+
const fileSha256B64 = fileSha256.toString('base64');
|
|
95
|
+
const upload = await waUploadToServer(filePath, {
|
|
96
|
+
fileEncSha256B64: fileSha256B64,
|
|
97
|
+
mediaType: 'biz-cover-photo'
|
|
98
|
+
});
|
|
99
|
+
const { meta_hmac, fbid, ts } = upload || {};
|
|
100
|
+
if (fbid === undefined || fbid === null || !meta_hmac || ts === undefined || ts === null) {
|
|
101
|
+
throw new Error('Business cover photo upload returned incomplete metadata');
|
|
102
|
+
}
|
|
103
|
+
await query({
|
|
104
|
+
tag: 'iq',
|
|
105
|
+
attrs: {
|
|
106
|
+
to: S_WHATSAPP_NET,
|
|
107
|
+
type: 'set',
|
|
108
|
+
xmlns: 'w:biz'
|
|
109
|
+
},
|
|
110
|
+
content: [
|
|
111
|
+
{
|
|
112
|
+
tag: 'business_profile',
|
|
113
|
+
attrs: {
|
|
114
|
+
v: '3',
|
|
115
|
+
mutation_type: 'delta'
|
|
116
|
+
},
|
|
117
|
+
content: [
|
|
118
|
+
{
|
|
119
|
+
tag: 'cover_photo',
|
|
120
|
+
attrs: { id: String(fbid), op: 'update', token: String(meta_hmac), ts: String(ts) }
|
|
121
|
+
}
|
|
122
|
+
]
|
|
123
|
+
}
|
|
124
|
+
]
|
|
125
|
+
});
|
|
126
|
+
return fbid;
|
|
127
|
+
}
|
|
128
|
+
finally {
|
|
129
|
+
await unlink(filePath).catch(() => undefined);
|
|
130
|
+
}
|
|
103
131
|
};
|
|
104
132
|
const removeCoverPhoto = async (id) => {
|
|
105
133
|
return await query({
|
|
@@ -371,8 +399,8 @@ export const makeBusinessSocket = (config) => {
|
|
|
371
399
|
productCreate,
|
|
372
400
|
productDelete,
|
|
373
401
|
productUpdate,
|
|
374
|
-
updateBussinesProfile,
|
|
375
|
-
updateBusinessProfile
|
|
402
|
+
updateBussinesProfile: updateBusinessProfile,
|
|
403
|
+
updateBusinessProfile,
|
|
376
404
|
updateCoverPhoto,
|
|
377
405
|
removeCoverPhoto
|
|
378
406
|
};
|
package/lib/Socket/chats.js
CHANGED
|
@@ -197,11 +197,13 @@ export const makeChatsSocket = (config) => {
|
|
|
197
197
|
};
|
|
198
198
|
if (isPnUser(normalizedJid) || isHostedPnUser(normalizedJid)) {
|
|
199
199
|
userId.phoneNumber = normalizedJid;
|
|
200
|
-
|
|
200
|
+
const resolvedLid = (await signalRepository.lidMapping.getLIDsForPNs([normalizedJid]))?.[0]?.lid;
|
|
201
|
+
userId.lid = resolvedLid ? jidNormalizedUser(resolvedLid) : undefined;
|
|
201
202
|
}
|
|
202
203
|
else if (isLidUser(normalizedJid) || isHostedLidUser(normalizedJid)) {
|
|
203
204
|
userId.lid = normalizedJid;
|
|
204
|
-
|
|
205
|
+
const resolvedPhoneNumber = (await signalRepository.lidMapping.getPNsForLIDs([normalizedJid]))?.[0]?.pn;
|
|
206
|
+
userId.phoneNumber = resolvedPhoneNumber ? jidNormalizedUser(resolvedPhoneNumber) : undefined;
|
|
205
207
|
}
|
|
206
208
|
else {
|
|
207
209
|
throw new Boom('Invalid id input to find user ids', { statusCode: 400 });
|
|
@@ -275,6 +277,9 @@ export const makeChatsSocket = (config) => {
|
|
|
275
277
|
};
|
|
276
278
|
/** update the profile status for yourself */
|
|
277
279
|
const updateProfileStatus = async (status) => {
|
|
280
|
+
if (typeof status !== 'string') {
|
|
281
|
+
throw new TypeError('Profile status must be a string');
|
|
282
|
+
}
|
|
278
283
|
await query({
|
|
279
284
|
tag: 'iq',
|
|
280
285
|
attrs: {
|
|
@@ -292,6 +297,9 @@ export const makeChatsSocket = (config) => {
|
|
|
292
297
|
});
|
|
293
298
|
};
|
|
294
299
|
const updateProfileName = async (name) => {
|
|
300
|
+
if (typeof name !== 'string') {
|
|
301
|
+
throw new TypeError('Profile name must be a string');
|
|
302
|
+
}
|
|
295
303
|
await chatModify({ pushNameSetting: name }, '');
|
|
296
304
|
};
|
|
297
305
|
const fetchBlocklist = async () => {
|
package/lib/Socket/groups.js
CHANGED
|
@@ -2,11 +2,55 @@ import { Boom } from '@hapi/boom';
|
|
|
2
2
|
import { proto } from '../../WAProto/index.js';
|
|
3
3
|
import { WAMessageAddressingMode, WAMessageStubType } from '../Types/index.js';
|
|
4
4
|
import { generateMessageIDV2, unixTimestampSeconds } from '../Utils/index.js';
|
|
5
|
-
import { getBinaryNodeChild, getBinaryNodeChildren, getBinaryNodeChildString, isLidUser, isPnUser, jidEncode, jidNormalizedUser } from '../WABinary/index.js';
|
|
5
|
+
import { getBinaryNodeChild, getBinaryNodeChildren, getBinaryNodeChildString, isJidGroup, isLidUser, isPnUser, jidEncode, jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary/index.js';
|
|
6
6
|
import { makeChatsSocket } from './chats.js';
|
|
7
|
+
|
|
8
|
+
export const buildReportNode = (jid, messageKeys = []) => ({
|
|
9
|
+
tag: 'spam_list',
|
|
10
|
+
attrs: {},
|
|
11
|
+
content: [{
|
|
12
|
+
tag: 'spam',
|
|
13
|
+
attrs: { jid },
|
|
14
|
+
content: messageKeys.map(key => {
|
|
15
|
+
if (!key?.id) {
|
|
16
|
+
throw new Boom('Every reported message key must include an id', { statusCode: 400 });
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
tag: 'message',
|
|
20
|
+
attrs: {
|
|
21
|
+
id: key.id,
|
|
22
|
+
...(key.fromMe !== undefined ? { from_me: key.fromMe ? 'true' : 'false' } : {}),
|
|
23
|
+
...(key.participant ? { participant: jidNormalizedUser(key.participant) } : {})
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
})
|
|
27
|
+
}]
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export const buildGroupStatusRevokeMessage = (jid, key) => ({
|
|
31
|
+
groupStatusMessageV2: {
|
|
32
|
+
message: {
|
|
33
|
+
protocolMessage: {
|
|
34
|
+
key: {
|
|
35
|
+
remoteJid: jid,
|
|
36
|
+
fromMe: key.fromMe ?? true,
|
|
37
|
+
id: key.id,
|
|
38
|
+
...(key.participant ? { participant: jidNormalizedUser(key.participant) } : {})
|
|
39
|
+
},
|
|
40
|
+
type: proto.Message.ProtocolMessage.Type.REVOKE
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
7
46
|
export const makeGroupsSocket = (config) => {
|
|
8
47
|
const sock = makeChatsSocket(config);
|
|
9
|
-
const { authState, ev, query, upsertMessage } = sock;
|
|
48
|
+
const { authState, ev, query, relayMessage, updateBlockStatus, upsertMessage } = sock;
|
|
49
|
+
const reportJid = async (jid, messageKeys = []) => query({
|
|
50
|
+
tag: 'iq',
|
|
51
|
+
attrs: { type: 'set', xmlns: 'spam', to: S_WHATSAPP_NET },
|
|
52
|
+
content: [buildReportNode(jid, messageKeys)]
|
|
53
|
+
});
|
|
10
54
|
const groupQuery = async (jid, type, content) => query({
|
|
11
55
|
tag: 'iq',
|
|
12
56
|
attrs: {
|
|
@@ -268,11 +312,45 @@ export const makeGroupsSocket = (config) => {
|
|
|
268
312
|
]);
|
|
269
313
|
},
|
|
270
314
|
groupFetchAllParticipating,
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
315
|
+
/** Report a contact to WhatsApp, then block it after the report succeeds. */
|
|
316
|
+
reportContact: async (jid, messageKeys = []) => {
|
|
317
|
+
const normalizedJid = jidNormalizedUser(jid);
|
|
318
|
+
if (!isPnUser(normalizedJid) && !isLidUser(normalizedJid)) {
|
|
319
|
+
throw new Boom('reportContact requires a valid contact JID', { statusCode: 400 });
|
|
320
|
+
}
|
|
321
|
+
const reportResult = await reportJid(normalizedJid, messageKeys);
|
|
322
|
+
await updateBlockStatus(normalizedJid, 'block');
|
|
323
|
+
return reportResult;
|
|
324
|
+
},
|
|
325
|
+
/** Report a group to WhatsApp, then leave it after the report succeeds. */
|
|
326
|
+
reportGroup: async (jid, messageKeys = []) => {
|
|
327
|
+
if (!isJidGroup(jid)) {
|
|
328
|
+
throw new Boom('reportGroup requires a valid group JID', { statusCode: 400 });
|
|
329
|
+
}
|
|
330
|
+
const reportResult = await reportJid(jid, messageKeys);
|
|
331
|
+
await groupQuery('@g.us', 'set', [{
|
|
332
|
+
tag: 'leave',
|
|
333
|
+
attrs: {},
|
|
334
|
+
content: [{ tag: 'group', attrs: { id: jid } }]
|
|
335
|
+
}]);
|
|
336
|
+
return reportResult;
|
|
337
|
+
},
|
|
338
|
+
/** Revoke a previously sent group status using its original message key. */
|
|
274
339
|
deleteGroupStatus: async (jid, key) => {
|
|
275
|
-
|
|
340
|
+
if (!isJidGroup(jid)) {
|
|
341
|
+
throw new Boom('deleteGroupStatus requires a valid group JID', { statusCode: 400 });
|
|
342
|
+
}
|
|
343
|
+
if (!key?.id) {
|
|
344
|
+
throw new Boom('deleteGroupStatus requires a message key with an id', { statusCode: 400 });
|
|
345
|
+
}
|
|
346
|
+
if (key.remoteJid && key.remoteJid !== jid) {
|
|
347
|
+
throw new Boom('The group status key does not belong to the target group', { statusCode: 400 });
|
|
348
|
+
}
|
|
349
|
+
return relayMessage(
|
|
350
|
+
jid,
|
|
351
|
+
buildGroupStatusRevokeMessage(jid, key),
|
|
352
|
+
{ messageId: generateMessageIDV2(authState.creds.me?.id) }
|
|
353
|
+
);
|
|
276
354
|
}
|
|
277
355
|
};
|
|
278
356
|
};
|