plogme 1.0.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.
Files changed (154) hide show
  1. package/LICENSE +61 -0
  2. package/README.md +212 -0
  3. package/WAProto/V +1 -0
  4. package/WAProto/WAProto.proto +8051 -0
  5. package/WAProto/index.js +289292 -0
  6. package/engine-requirements.js +51 -0
  7. package/lib/Defaults/ban-checker.js +44 -0
  8. package/lib/Defaults/index.js +158 -0
  9. package/lib/Signal/Group/ciphertext-message.js +11 -0
  10. package/lib/Signal/Group/group-session-builder.js +29 -0
  11. package/lib/Signal/Group/group_cipher.js +81 -0
  12. package/lib/Signal/Group/index.js +11 -0
  13. package/lib/Signal/Group/keyhelper.js +17 -0
  14. package/lib/Signal/Group/sender-chain-key.js +25 -0
  15. package/lib/Signal/Group/sender-key-distribution-message.js +62 -0
  16. package/lib/Signal/Group/sender-key-message.js +65 -0
  17. package/lib/Signal/Group/sender-key-name.js +47 -0
  18. package/lib/Signal/Group/sender-key-record.js +40 -0
  19. package/lib/Signal/Group/sender-key-state.js +83 -0
  20. package/lib/Signal/Group/sender-message-key.js +25 -0
  21. package/lib/Signal/libsignal.js +428 -0
  22. package/lib/Signal/lid-mapping.js +280 -0
  23. package/lib/Socket/Client/index.js +2 -0
  24. package/lib/Socket/Client/types.js +10 -0
  25. package/lib/Socket/Client/websocket.js +53 -0
  26. package/lib/Socket/aigroups.js +315 -0
  27. package/lib/Socket/ban-checker.js +153 -0
  28. package/lib/Socket/business.js +407 -0
  29. package/lib/Socket/chats.js +1831 -0
  30. package/lib/Socket/chats.js[old] +1227 -0
  31. package/lib/Socket/communities.js +431 -0
  32. package/lib/Socket/graphql.js +899 -0
  33. package/lib/Socket/groups.js +549 -0
  34. package/lib/Socket/groups.js[old] +347 -0
  35. package/lib/Socket/index.js +71 -0
  36. package/lib/Socket/interactive-handler.js +600 -0
  37. package/lib/Socket/interop.js +563 -0
  38. package/lib/Socket/managed-account.js +208 -0
  39. package/lib/Socket/messages-recv.js +2144 -0
  40. package/lib/Socket/messages-send.js +2650 -0
  41. package/lib/Socket/messages-send.js[latest working] +1759 -0
  42. package/lib/Socket/mex.js +41 -0
  43. package/lib/Socket/newsletter.js +226 -0
  44. package/lib/Socket/privacy.js +446 -0
  45. package/lib/Socket/registration.js +428 -0
  46. package/lib/Socket/socket.js +1050 -0
  47. package/lib/Socket/username.js +229 -0
  48. package/lib/Store/index.js +4 -0
  49. package/lib/Store/make-cache-manager-store.js +89 -0
  50. package/lib/Store/make-in-memory-store.js +420 -0
  51. package/lib/Store/make-ordered-dictionary.js +78 -0
  52. package/lib/Store/object-repository.js +23 -0
  53. package/lib/Types/Auth.js +1 -0
  54. package/lib/Types/Bussines.js +1 -0
  55. package/lib/Types/Call.js +1 -0
  56. package/lib/Types/Chat.js +7 -0
  57. package/lib/Types/Contact.js +1 -0
  58. package/lib/Types/Events.js +1 -0
  59. package/lib/Types/GroupMetadata.js +1 -0
  60. package/lib/Types/Label.js +24 -0
  61. package/lib/Types/LabelAssociation.js +6 -0
  62. package/lib/Types/Message.js +17 -0
  63. package/lib/Types/Mex.js +39 -0
  64. package/lib/Types/Product.js +1 -0
  65. package/lib/Types/RichType.js +22 -0
  66. package/lib/Types/Signal.js +1 -0
  67. package/lib/Types/Socket.js +2 -0
  68. package/lib/Types/State.js +48 -0
  69. package/lib/Types/USync.js +1 -0
  70. package/lib/Types/index.js +25 -0
  71. package/lib/Utils/auth-utils.js +301 -0
  72. package/lib/Utils/bot-planning-replay.js +169 -0
  73. package/lib/Utils/browser-utils.js +28 -0
  74. package/lib/Utils/business.js +230 -0
  75. package/lib/Utils/channel-policy.js +1 -0
  76. package/lib/Utils/chat-utils.js +885 -0
  77. package/lib/Utils/chat-utils.js[old] +871 -0
  78. package/lib/Utils/companion-reg-client-utils.js +32 -0
  79. package/lib/Utils/consumer-application.js +102 -0
  80. package/lib/Utils/crypto.js +117 -0
  81. package/lib/Utils/decode-wa-message.js +484 -0
  82. package/lib/Utils/event-buffer.js +589 -0
  83. package/lib/Utils/games/slot-machine.js +258 -0
  84. package/lib/Utils/games/website-preview.js +293 -0
  85. package/lib/Utils/generics.js +390 -0
  86. package/lib/Utils/generics.js@old +385 -0
  87. package/lib/Utils/group-history.js +56 -0
  88. package/lib/Utils/history.js +130 -0
  89. package/lib/Utils/identity-change-handler.js +48 -0
  90. package/lib/Utils/index.js +35 -0
  91. package/lib/Utils/integrity.js +296 -0
  92. package/lib/Utils/jid-display-normalization.js +209 -0
  93. package/lib/Utils/link-preview.js +84 -0
  94. package/lib/Utils/logger.js +2 -0
  95. package/lib/Utils/lt-hash.js +7 -0
  96. package/lib/Utils/make-mutex.js +32 -0
  97. package/lib/Utils/message-composer.js +467 -0
  98. package/lib/Utils/message-inspect.js +67 -0
  99. package/lib/Utils/message-retry-manager.js +241 -0
  100. package/lib/Utils/messages-media.js +1006 -0
  101. package/lib/Utils/messages-media.js[old] +916 -0
  102. package/lib/Utils/messages.js +1982 -0
  103. package/lib/Utils/messages.js[old] +1902 -0
  104. package/lib/Utils/meta-ai-msmsg.js +130 -0
  105. package/lib/Utils/meta-compositing.js +202 -0
  106. package/lib/Utils/native-flow.js +100 -0
  107. package/lib/Utils/noise-handler.js +200 -0
  108. package/lib/Utils/offline-node-processor.js +39 -0
  109. package/lib/Utils/pre-key-manager.js +105 -0
  110. package/lib/Utils/process-message.js +582 -0
  111. package/lib/Utils/reporting-utils.js +257 -0
  112. package/lib/Utils/rich-message-utils.js +942 -0
  113. package/lib/Utils/rich-message-utils.js[Old] +387 -0
  114. package/lib/Utils/signal.js +200 -0
  115. package/lib/Utils/stanza-ack.js +37 -0
  116. package/lib/Utils/sync-action-utils.js +47 -0
  117. package/lib/Utils/tc-token-utils.js +162 -0
  118. package/lib/Utils/use-multi-file-auth-state.js +120 -0
  119. package/lib/Utils/use-single-file-auth-state.js +96 -0
  120. package/lib/Utils/use-sqlite-auth-state.js +108 -0
  121. package/lib/Utils/validate-connection.js +206 -0
  122. package/lib/Utils/view-once-utils.js +206 -0
  123. package/lib/Utils/voip-rekey.js +20 -0
  124. package/lib/Utils/welcome-flow.js +220 -0
  125. package/lib/WABinary/constants.js +1463 -0
  126. package/lib/WABinary/decode.js +261 -0
  127. package/lib/WABinary/encode.js +219 -0
  128. package/lib/WABinary/generic-utils.js +227 -0
  129. package/lib/WABinary/index.js +5 -0
  130. package/lib/WABinary/jid-utils.js +95 -0
  131. package/lib/WABinary/types.js +1 -0
  132. package/lib/WAM/BinaryInfo.js +9 -0
  133. package/lib/WAM/constants.js +22852 -0
  134. package/lib/WAM/encode.js +149 -0
  135. package/lib/WAM/index.js +3 -0
  136. package/lib/WAUSync/Protocols/USyncBusinessProtocol.js +96 -0
  137. package/lib/WAUSync/Protocols/USyncContactProtocol.js +56 -0
  138. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +53 -0
  139. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +26 -0
  140. package/lib/WAUSync/Protocols/USyncFeatureProtocol.js +70 -0
  141. package/lib/WAUSync/Protocols/USyncPictureProtocol.js +28 -0
  142. package/lib/WAUSync/Protocols/USyncSidelistProtocol.js +25 -0
  143. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +37 -0
  144. package/lib/WAUSync/Protocols/USyncTextStatusProtocol.js +34 -0
  145. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +24 -0
  146. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +50 -0
  147. package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +28 -0
  148. package/lib/WAUSync/Protocols/index.js +12 -0
  149. package/lib/WAUSync/USyncQuery.js +201 -0
  150. package/lib/WAUSync/USyncUser.js +54 -0
  151. package/lib/WAUSync/index.js +3 -0
  152. package/lib/index.d.ts +406 -0
  153. package/lib/index.js +11 -0
  154. package/package.json +125 -0
@@ -0,0 +1,153 @@
1
+ import { randomBytes, randomUUID } from 'crypto';
2
+ import { initAuthCreds } from '../Utils/auth-utils.js';
3
+ import { Curve, md5, signedKeyPair } from '../Utils/crypto.js';
4
+ import {
5
+ GRAPHQL_ACCESS_TOKEN,
6
+ GRAPHQL_BAN_STATUS_DOC_ID,
7
+ GRAPHQL_ENDPOINT,
8
+ MOBILE_COUNTRY_CODE,
9
+ MOBILE_REGISTRATION_ENDPOINT,
10
+ MOBILE_TOKEN,
11
+ MOBILE_USERAGENT
12
+ } from '../Defaults/ban-checker.js';
13
+
14
+ const COUNTRY_CODES = [
15
+ '1', '20', '27', '30', '31', '32', '33', '34', '36', '39', '40', '41', '43', '44', '45', '46', '47', '48', '49',
16
+ '51', '52', '53', '54', '55', '56', '57', '58', '60', '61', '62', '63', '64', '65', '66', '81', '82', '84', '86',
17
+ '90', '91', '92', '93', '94', '95', '98', '211', '212', '213', '216', '218', '220', '221', '222', '223', '224', '225',
18
+ '226', '227', '228', '229', '230', '231', '232', '233', '234', '235', '236', '237', '238', '239', '240', '241', '242',
19
+ '243', '244', '245', '246', '248', '249', '250', '251', '252', '253', '254', '255', '256', '257', '258', '260', '261',
20
+ '262', '263', '264', '265', '266', '267', '268', '269', '290', '291', '297', '298', '299', '350', '351', '352', '353',
21
+ '354', '355', '356', '357', '358', '359', '370', '371', '372', '373', '374', '375', '376', '377', '378', '380', '381',
22
+ '382', '383', '385', '386', '387', '389', '420', '421', '423', '500', '501', '502', '503', '504', '505', '506', '507',
23
+ '508', '509', '590', '591', '592', '593', '594', '595', '596', '597', '598', '599', '670', '672', '673', '674', '675',
24
+ '676', '677', '678', '679', '680', '681', '682', '683', '685', '686', '687', '688', '689', '690', '691', '692', '850',
25
+ '852', '853', '855', '856', '880', '886', '960', '961', '962', '963', '964', '965', '966', '967', '968', '970', '971',
26
+ '972', '973', '974', '975', '976', '977', '992', '993', '994', '995', '996', '998'
27
+ ].sort((a, b) => b.length - a.length);
28
+
29
+ const PLACEHOLDER = /^REPLACE_WITH_/;
30
+
31
+ const cleanNumber = (value) => String(value || '').replace(/[\s().\-/]/g, '').replace(/^00/, '');
32
+
33
+ const normalizePhone = (value, options = {}) => {
34
+ const raw = cleanNumber(typeof value === 'object' ? value.number : value).replace(/^\+/, '');
35
+ if (!/^\d{6,15}$/.test(raw)) throw new TypeError('number must be an international phone number');
36
+ const requestedCountry = String(options.countryCode || MOBILE_COUNTRY_CODE || '').replace(/\D/g, '');
37
+ const countryCode = requestedCountry || COUNTRY_CODES.find((code) => raw.startsWith(code));
38
+ if (!countryCode || !raw.startsWith(countryCode)) throw new TypeError('number must include a recognized country calling code');
39
+ const nationalNumber = raw.slice(countryCode.length);
40
+ if (!nationalNumber) throw new TypeError('number is missing its national part');
41
+ return { countryCode, nationalNumber, e164: `+${raw}` };
42
+ };
43
+
44
+ const urlEncode = (value) => encodeURIComponent(String(value)).replace(/%25([0-9a-f]{2})/gi, '%$1');
45
+ const urlHex = (buffer) => [...buffer].map((byte) => `%${byte.toString(16).padStart(2, '0')}`).join('');
46
+
47
+ const makeRegistrationParams = (countryCode, nationalNumber) => {
48
+ const creds = initAuthCreds();
49
+ const eRegId = Buffer.alloc(4);
50
+ eRegId.writeInt32BE(creds.registrationId);
51
+ const eSkeyId = Buffer.alloc(3);
52
+ eSkeyId.writeInt16BE(creds.signedPreKey.keyId);
53
+ return {
54
+ cc: countryCode,
55
+ in: nationalNumber,
56
+ Rc: '0', lg: 'en', lc: 'GB', mistyped: '6',
57
+ authkey: Buffer.from(creds.noiseKey.public).toString('base64url'),
58
+ e_regid: eRegId.toString('base64url'),
59
+ e_keytype: 'BQ',
60
+ e_ident: Buffer.from(creds.signedIdentityKey.public).toString('base64url'),
61
+ e_skey_id: eSkeyId.toString('base64url'),
62
+ e_skey_val: Buffer.from(creds.signedPreKey.keyPair.public).toString('base64url'),
63
+ e_skey_sig: Buffer.from(creds.signedPreKey.signature).toString('base64url'),
64
+ fdid: randomUUID(),
65
+ network_ratio_type: '1', expid: randomUUID(), simnum: '1', hasinrc: '1',
66
+ pid: String(Math.floor(Math.random() * 1000)),
67
+ id: urlHex(randomBytes(16)),
68
+ backup_token: urlHex(randomBytes(32)),
69
+ token: Buffer.from(md5(Buffer.concat([MOBILE_TOKEN, Buffer.from(nationalNumber)]))).toString('hex')
70
+ };
71
+ };
72
+
73
+ const requestJson = async (url, options = {}) => {
74
+ const response = await fetch(url, { ...options, headers: { 'User-Agent': MOBILE_USERAGENT, ...(options.headers || {}) } });
75
+ let body = null;
76
+ try { body = await response.json(); } catch { body = {}; }
77
+ return { response, body };
78
+ };
79
+
80
+ const getBanDetails = async (appealToken) => {
81
+ if (!appealToken || PLACEHOLDER.test(GRAPHQL_ACCESS_TOKEN) || PLACEHOLDER.test(GRAPHQL_BAN_STATUS_DOC_ID)) return null;
82
+ const { response, body } = await requestJson(GRAPHQL_ENDPOINT, {
83
+ method: 'POST',
84
+ headers: { 'Content-Type': 'application/json' },
85
+ body: JSON.stringify({
86
+ variables: JSON.stringify({ app_id: 'dev.app.id', request_token: appealToken }),
87
+ access_token: GRAPHQL_ACCESS_TOKEN,
88
+ doc_id: GRAPHQL_BAN_STATUS_DOC_ID,
89
+ lang: 'en_GB'
90
+ })
91
+ });
92
+ if (!response.ok) return null;
93
+ return body?.data?.whatsapp_support_ban_appeal_status || null;
94
+ };
95
+
96
+ const result = (number, status, isBanned = false, isNeedOfficialWa = false, banInfo = null) => ({ number, status, isBanned, isNeedOfficialWa, banInfo });
97
+
98
+ const getDiagnostic = (response, body) => {
99
+ const bodyObject = body && typeof body === 'object' && !Array.isArray(body) ? body : {};
100
+ const nestedData = bodyObject.data && typeof bodyObject.data === 'object' && !Array.isArray(bodyObject.data)
101
+ ? bodyObject.data
102
+ : {};
103
+ const keys = Object.keys(bodyObject).sort();
104
+ return {
105
+ httpStatus: Number.isInteger(response?.status) ? response.status : null,
106
+ ok: response?.ok === true,
107
+ contentType: response?.headers?.get?.('content-type') || null,
108
+ bodyKeys: keys,
109
+ dataKeys: Object.keys(nestedData).sort(),
110
+ signals: {
111
+ hasReason: Object.prototype.hasOwnProperty.call(bodyObject, 'reason'),
112
+ hasError: Object.prototype.hasOwnProperty.call(bodyObject, 'error'),
113
+ hasStatus: Object.prototype.hasOwnProperty.call(bodyObject, 'status'),
114
+ hasAppealToken: Boolean(bodyObject.appeal_token || bodyObject.appealToken || nestedData.appeal_token),
115
+ hasBanFields: Boolean(bodyObject.ban_type || bodyObject.banType || bodyObject.violation_type || bodyObject.violationType)
116
+ }
117
+ };
118
+ };
119
+
120
+ const withDiagnostic = (value, diagnostic) => diagnostic ? { ...value, diagnostics: diagnostic } : value;
121
+
122
+ export const checkStatusWA = async (number, options = {}) => {
123
+ const normalized = normalizePhone(number, options);
124
+ const params = makeRegistrationParams(normalized.countryCode, normalized.nationalNumber);
125
+ const query = Object.entries(params).map(([key, value]) => `${key}=${urlEncode(value)}`).join('&');
126
+ const { response, body } = await requestJson(`${MOBILE_REGISTRATION_ENDPOINT}/exist?${query}`);
127
+ const diagnostic = options.diagnostic === true ? getDiagnostic(response, body) : null;
128
+ const reason = String(body?.reason || body?.error || body?.status || '').toLowerCase();
129
+ const appealToken = body?.appeal_token || body?.appealToken || body?.data?.appeal_token;
130
+
131
+ if (response.status === 429 || /rate.?limit|too many/.test(reason)) return withDiagnostic(result(normalized.e164, 'rate_limited'), diagnostic);
132
+ if (/blocked|official/.test(reason) && !appealToken) return withDiagnostic(result(normalized.e164, 'blocked', false, true), diagnostic);
133
+ if (appealToken || /banned|ban|temporar|perman/.test(reason)) {
134
+ const details = await getBanDetails(appealToken);
135
+ const banType = details?.ban_type || details?.banType || body?.ban_type || body?.banType || (reason.includes('perman') ? 'permanent' : 'temporary');
136
+ return withDiagnostic(result(normalized.e164, 'banned', true, false, {
137
+ banType,
138
+ violationType: body?.violation_type || body?.violationType || null,
139
+ violationReason: body?.violation_reason || body?.violationReason || null,
140
+ canAppeal: banType !== 'permanent',
141
+ appealToken: appealToken || null,
142
+ banTime: details?.ban_time || details?.banTime || null,
143
+ banDate: details?.ban_date || details?.banDate || null,
144
+ appealStatus: details?.appeal_status || details?.appealStatus || null,
145
+ appealCreatedAt: details?.appeal_created_at || details?.appealCreatedAt || null
146
+ }), diagnostic);
147
+ }
148
+ if (response.ok && (!body?.reason || /^(ok|sent|active|registered)$/.test(reason))) return withDiagnostic(result(normalized.e164, 'active'), diagnostic);
149
+ if (/not.?registered|does.?not.?exist/.test(reason)) return withDiagnostic(result(normalized.e164, 'not_registered'), diagnostic);
150
+ return withDiagnostic(result(normalized.e164, 'unknown'), diagnostic);
151
+ };
152
+
153
+ export { getBanDetails, normalizePhone };
@@ -0,0 +1,407 @@
1
+ import { unlink } from 'node:fs/promises';
2
+ import { getRawMediaUploadData } from '../Utils/index.js';
3
+ import { parseCatalogNode, parseCollectionsNode, parseOrderDetailsNode, parseProductNode, toProductNode, uploadingNecessaryImagesOfProduct } from '../Utils/business.js';
4
+ import { jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary/index.js';
5
+ import { getBinaryNodeChild } from '../WABinary/generic-utils.js';
6
+ import { makeMessagesRecvSocket } from './messages-recv.js';
7
+ export const makeBusinessSocket = (config) => {
8
+ const sock = makeMessagesRecvSocket(config);
9
+ const { authState, query, waUploadToServer } = sock;
10
+ const updateBusinessProfile = async (args = {}) => {
11
+ const node = [];
12
+ const simpleFields = ['address', 'email', 'description'];
13
+ node.push(...simpleFields
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
+ }));
25
+ if (args.websites !== undefined) {
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 => ({
30
+ tag: 'website',
31
+ attrs: {},
32
+ content: website
33
+ })));
34
+ }
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
+ }
39
+ node.push({
40
+ tag: 'business_hours',
41
+ attrs: { timezone: args.hours.timezone },
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
+ }
46
+ const base = {
47
+ tag: 'business_hours_config',
48
+ attrs: {
49
+ day_of_week: dayConfig.day,
50
+ mode: dayConfig.mode
51
+ }
52
+ };
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
+ }
58
+ return {
59
+ ...base,
60
+ attrs: {
61
+ ...base.attrs,
62
+ open_time: String(openTimeInMinutes),
63
+ close_time: String(closeTimeInMinutes)
64
+ }
65
+ };
66
+ }
67
+ return base;
68
+ })
69
+ });
70
+ }
71
+ const result = await query({
72
+ tag: 'iq',
73
+ attrs: {
74
+ to: S_WHATSAPP_NET,
75
+ type: 'set',
76
+ xmlns: 'w:biz'
77
+ },
78
+ content: [
79
+ {
80
+ tag: 'business_profile',
81
+ attrs: {
82
+ v: '3',
83
+ mutation_type: 'delta'
84
+ },
85
+ content: node
86
+ }
87
+ ]
88
+ });
89
+ return result;
90
+ };
91
+ const updateCoverPhoto = async (photo) => {
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
+ }
131
+ };
132
+ const removeCoverPhoto = async (id) => {
133
+ return await query({
134
+ tag: 'iq',
135
+ attrs: {
136
+ to: S_WHATSAPP_NET,
137
+ type: 'set',
138
+ xmlns: 'w:biz'
139
+ },
140
+ content: [
141
+ {
142
+ tag: 'business_profile',
143
+ attrs: {
144
+ v: '3',
145
+ mutation_type: 'delta'
146
+ },
147
+ content: [
148
+ {
149
+ tag: 'cover_photo',
150
+ attrs: { op: 'delete', id }
151
+ }
152
+ ]
153
+ }
154
+ ]
155
+ });
156
+ };
157
+ const getCatalog = async ({ jid, limit, cursor }) => {
158
+ jid = jid || authState.creds.me?.id;
159
+ jid = jidNormalizedUser(jid);
160
+ const queryParamNodes = [
161
+ {
162
+ tag: 'limit',
163
+ attrs: {},
164
+ content: Buffer.from((limit || 10).toString())
165
+ },
166
+ {
167
+ tag: 'width',
168
+ attrs: {},
169
+ content: Buffer.from('100')
170
+ },
171
+ {
172
+ tag: 'height',
173
+ attrs: {},
174
+ content: Buffer.from('100')
175
+ }
176
+ ];
177
+ if (cursor) {
178
+ queryParamNodes.push({
179
+ tag: 'after',
180
+ attrs: {},
181
+ content: cursor
182
+ });
183
+ }
184
+ const result = await query({
185
+ tag: 'iq',
186
+ attrs: {
187
+ to: S_WHATSAPP_NET,
188
+ type: 'get',
189
+ xmlns: 'w:biz:catalog'
190
+ },
191
+ content: [
192
+ {
193
+ tag: 'product_catalog',
194
+ attrs: {
195
+ jid,
196
+ allow_shop_source: 'true'
197
+ },
198
+ content: queryParamNodes
199
+ }
200
+ ]
201
+ });
202
+ return parseCatalogNode(result);
203
+ };
204
+ const getCollections = async (jid, limit = 51) => {
205
+ jid = jid || authState.creds.me?.id;
206
+ jid = jidNormalizedUser(jid);
207
+ const result = await query({
208
+ tag: 'iq',
209
+ attrs: {
210
+ to: S_WHATSAPP_NET,
211
+ type: 'get',
212
+ xmlns: 'w:biz:catalog',
213
+ smax_id: '35'
214
+ },
215
+ content: [
216
+ {
217
+ tag: 'collections',
218
+ attrs: {
219
+ biz_jid: jid
220
+ },
221
+ content: [
222
+ {
223
+ tag: 'collection_limit',
224
+ attrs: {},
225
+ content: Buffer.from(limit.toString())
226
+ },
227
+ {
228
+ tag: 'item_limit',
229
+ attrs: {},
230
+ content: Buffer.from(limit.toString())
231
+ },
232
+ {
233
+ tag: 'width',
234
+ attrs: {},
235
+ content: Buffer.from('100')
236
+ },
237
+ {
238
+ tag: 'height',
239
+ attrs: {},
240
+ content: Buffer.from('100')
241
+ }
242
+ ]
243
+ }
244
+ ]
245
+ });
246
+ return parseCollectionsNode(result);
247
+ };
248
+ const getOrderDetails = async (orderId, tokenBase64) => {
249
+ const result = await query({
250
+ tag: 'iq',
251
+ attrs: {
252
+ to: S_WHATSAPP_NET,
253
+ type: 'get',
254
+ xmlns: 'fb:thrift_iq',
255
+ smax_id: '5'
256
+ },
257
+ content: [
258
+ {
259
+ tag: 'order',
260
+ attrs: {
261
+ op: 'get',
262
+ id: orderId
263
+ },
264
+ content: [
265
+ {
266
+ tag: 'image_dimensions',
267
+ attrs: {},
268
+ content: [
269
+ {
270
+ tag: 'width',
271
+ attrs: {},
272
+ content: Buffer.from('100')
273
+ },
274
+ {
275
+ tag: 'height',
276
+ attrs: {},
277
+ content: Buffer.from('100')
278
+ }
279
+ ]
280
+ },
281
+ {
282
+ tag: 'token',
283
+ attrs: {},
284
+ content: Buffer.from(tokenBase64)
285
+ }
286
+ ]
287
+ }
288
+ ]
289
+ });
290
+ return parseOrderDetailsNode(result);
291
+ };
292
+ const productUpdate = async (productId, update) => {
293
+ update = await uploadingNecessaryImagesOfProduct(update, waUploadToServer);
294
+ const editNode = toProductNode(productId, update);
295
+ const result = await query({
296
+ tag: 'iq',
297
+ attrs: {
298
+ to: S_WHATSAPP_NET,
299
+ type: 'set',
300
+ xmlns: 'w:biz:catalog'
301
+ },
302
+ content: [
303
+ {
304
+ tag: 'product_catalog_edit',
305
+ attrs: { v: '1' },
306
+ content: [
307
+ editNode,
308
+ {
309
+ tag: 'width',
310
+ attrs: {},
311
+ content: '100'
312
+ },
313
+ {
314
+ tag: 'height',
315
+ attrs: {},
316
+ content: '100'
317
+ }
318
+ ]
319
+ }
320
+ ]
321
+ });
322
+ const productCatalogEditNode = getBinaryNodeChild(result, 'product_catalog_edit');
323
+ const productNode = getBinaryNodeChild(productCatalogEditNode, 'product');
324
+ return parseProductNode(productNode);
325
+ };
326
+ const productCreate = async (create) => {
327
+ // ensure isHidden is defined
328
+ create.isHidden = !!create.isHidden;
329
+ create = await uploadingNecessaryImagesOfProduct(create, waUploadToServer);
330
+ const createNode = toProductNode(undefined, create);
331
+ const result = await query({
332
+ tag: 'iq',
333
+ attrs: {
334
+ to: S_WHATSAPP_NET,
335
+ type: 'set',
336
+ xmlns: 'w:biz:catalog'
337
+ },
338
+ content: [
339
+ {
340
+ tag: 'product_catalog_add',
341
+ attrs: { v: '1' },
342
+ content: [
343
+ createNode,
344
+ {
345
+ tag: 'width',
346
+ attrs: {},
347
+ content: '100'
348
+ },
349
+ {
350
+ tag: 'height',
351
+ attrs: {},
352
+ content: '100'
353
+ }
354
+ ]
355
+ }
356
+ ]
357
+ });
358
+ const productCatalogAddNode = getBinaryNodeChild(result, 'product_catalog_add');
359
+ const productNode = getBinaryNodeChild(productCatalogAddNode, 'product');
360
+ return parseProductNode(productNode);
361
+ };
362
+ const productDelete = async (productIds) => {
363
+ const result = await query({
364
+ tag: 'iq',
365
+ attrs: {
366
+ to: S_WHATSAPP_NET,
367
+ type: 'set',
368
+ xmlns: 'w:biz:catalog'
369
+ },
370
+ content: [
371
+ {
372
+ tag: 'product_catalog_delete',
373
+ attrs: { v: '1' },
374
+ content: productIds.map(id => ({
375
+ tag: 'product',
376
+ attrs: {},
377
+ content: [
378
+ {
379
+ tag: 'id',
380
+ attrs: {},
381
+ content: Buffer.from(id)
382
+ }
383
+ ]
384
+ }))
385
+ }
386
+ ]
387
+ });
388
+ const productCatalogDelNode = getBinaryNodeChild(result, 'product_catalog_delete');
389
+ return {
390
+ deleted: +(productCatalogDelNode?.attrs.deleted_count || 0)
391
+ };
392
+ };
393
+ return {
394
+ ...sock,
395
+ logger: config.logger,
396
+ getOrderDetails,
397
+ getCatalog,
398
+ getCollections,
399
+ productCreate,
400
+ productDelete,
401
+ productUpdate,
402
+ updateBussinesProfile: updateBusinessProfile,
403
+ updateBusinessProfile,
404
+ updateCoverPhoto,
405
+ removeCoverPhoto
406
+ };
407
+ };