@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.
@@ -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 pnJid = `${pnUser}:${lidDevice}@${decoded.domainType === WAJIDDomains.HOSTED_LID ? 'hosted' : 's.whatsapp.net'}`;
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
+ }
@@ -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 updateBussinesProfile = async (args) => {
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 && args[key] !== null)
14
- .map(key => ({
15
- tag: key,
16
- attrs: {},
17
- content: args[key]
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
- node.push(...args.websites.map(website => ({
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: dayConfig.openTimeInMinutes,
44
- close_time: dayConfig.closeTimeInMinutes
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
- const fileSha256B64 = fileSha256.toString('base64');
75
- const { meta_hmac, fbid, ts } = await waUploadToServer(filePath, {
76
- fileEncSha256B64: fileSha256B64,
77
- mediaType: 'biz-cover-photo'
78
- });
79
- await query({
80
- tag: 'iq',
81
- attrs: {
82
- to: S_WHATSAPP_NET,
83
- type: 'set',
84
- xmlns: 'w:biz'
85
- },
86
- content: [
87
- {
88
- tag: 'business_profile',
89
- attrs: {
90
- v: '3',
91
- mutation_type: 'delta'
92
- },
93
- content: [
94
- {
95
- tag: 'cover_photo',
96
- attrs: { id: String(fbid), op: 'update', token: meta_hmac, ts: String(ts) }
97
- }
98
- ]
99
- }
100
- ]
101
- });
102
- return fbid;
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: updateBussinesProfile,
402
+ updateBussinesProfile: updateBusinessProfile,
403
+ updateBusinessProfile,
376
404
  updateCoverPhoto,
377
405
  removeCoverPhoto
378
406
  };
@@ -197,11 +197,13 @@ export const makeChatsSocket = (config) => {
197
197
  };
198
198
  if (isPnUser(normalizedJid) || isHostedPnUser(normalizedJid)) {
199
199
  userId.phoneNumber = normalizedJid;
200
- userId.lid = jidNormalizedUser((await signalRepository.lidMapping.getLIDsForPNs([normalizedJid]))?.[0]?.lid);
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
- userId.phoneNumber = jidNormalizedUser((await signalRepository.lidMapping.getPNsForLIDs([normalizedJid]))?.[0]?.pn);
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 () => {
@@ -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
- // crysnovax@GroupStatus --- Delete a group status by its message key.
272
- // Pass the key object from the groupStatusMessageV2 you want to remove.
273
- // Usage: await sock.deleteGroupStatus(jid, message.key)
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
- await sock.sendMessage(jid, { delete: key });
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
  };