my-baileys 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.

Potentially problematic release.


This version of my-baileys might be problematic. Click here for more details.

Files changed (144) hide show
  1. package/README.md +1145 -0
  2. package/WAProto/GenerateStatics.sh +3 -0
  3. package/WAProto/WAProto.proto +8083 -0
  4. package/WAProto/fix-imports.js +85 -0
  5. package/WAProto/index.d.ts +29095 -0
  6. package/WAProto/index.js +172336 -0
  7. package/engine-requirements.js +10 -0
  8. package/lib/Defaults/index.js +194 -0
  9. package/lib/Signal/Group/ciphertext-message.js +12 -0
  10. package/lib/Signal/Group/group-session-builder.js +30 -0
  11. package/lib/Signal/Group/group_cipher.js +82 -0
  12. package/lib/Signal/Group/index.js +12 -0
  13. package/lib/Signal/Group/keyhelper.js +18 -0
  14. package/lib/Signal/Group/sender-chain-key.js +26 -0
  15. package/lib/Signal/Group/sender-key-distribution-message.js +63 -0
  16. package/lib/Signal/Group/sender-key-message.js +66 -0
  17. package/lib/Signal/Group/sender-key-name.js +48 -0
  18. package/lib/Signal/Group/sender-key-record.js +41 -0
  19. package/lib/Signal/Group/sender-key-state.js +84 -0
  20. package/lib/Signal/Group/sender-message-key.js +26 -0
  21. package/lib/Signal/libsignal.js +431 -0
  22. package/lib/Signal/lid-mapping.js +277 -0
  23. package/lib/Socket/Client/index.js +3 -0
  24. package/lib/Socket/Client/types.js +11 -0
  25. package/lib/Socket/Client/websocket.js +102 -0
  26. package/lib/Socket/aigroups.js +221 -0
  27. package/lib/Socket/business.js +379 -0
  28. package/lib/Socket/chats.js +1193 -0
  29. package/lib/Socket/communities.js +431 -0
  30. package/lib/Socket/graphql.js +524 -0
  31. package/lib/Socket/groups.js +408 -0
  32. package/lib/Socket/index.js +57 -0
  33. package/lib/Socket/interop.js +345 -0
  34. package/lib/Socket/luxu.js +510 -0
  35. package/lib/Socket/managed-account.js +99 -0
  36. package/lib/Socket/message-builder.js +523 -0
  37. package/lib/Socket/messages-recv.js +2009 -0
  38. package/lib/Socket/messages-send.js +1608 -0
  39. package/lib/Socket/mex.js +56 -0
  40. package/lib/Socket/newsletter.js +624 -0
  41. package/lib/Socket/privacy.js +128 -0
  42. package/lib/Socket/registration.js +238 -0
  43. package/lib/Socket/socket.js +1000 -0
  44. package/lib/Socket/text-router.js +67 -0
  45. package/lib/Socket/username.js +234 -0
  46. package/lib/Store/index.js +10 -0
  47. package/lib/Store/keyed-db.js +108 -0
  48. package/lib/Store/make-cache-manager-store.js +85 -0
  49. package/lib/Store/make-in-memory-store.js +198 -0
  50. package/lib/Store/make-ordered-dictionary.js +75 -0
  51. package/lib/Store/object-repository.js +32 -0
  52. package/lib/Types/Auth.js +2 -0
  53. package/lib/Types/Bussines.js +2 -0
  54. package/lib/Types/Call.js +2 -0
  55. package/lib/Types/Chat.js +8 -0
  56. package/lib/Types/Contact.js +2 -0
  57. package/lib/Types/Events.js +2 -0
  58. package/lib/Types/GroupMetadata.js +2 -0
  59. package/lib/Types/Label.js +25 -0
  60. package/lib/Types/LabelAssociation.js +7 -0
  61. package/lib/Types/Message.js +11 -0
  62. package/lib/Types/Mex.js +120 -0
  63. package/lib/Types/Product.js +2 -0
  64. package/lib/Types/Signal.js +2 -0
  65. package/lib/Types/Socket.js +3 -0
  66. package/lib/Types/State.js +56 -0
  67. package/lib/Types/USync.js +2 -0
  68. package/lib/Types/index.js +26 -0
  69. package/lib/Utils/adaptive-healing.js +53 -0
  70. package/lib/Utils/auth-utils.js +302 -0
  71. package/lib/Utils/browser-utils.js +50 -0
  72. package/lib/Utils/business.js +231 -0
  73. package/lib/Utils/chat-utils.js +872 -0
  74. package/lib/Utils/command-loader.js +108 -0
  75. package/lib/Utils/companion-reg-client-utils.js +35 -0
  76. package/lib/Utils/consumer-application.js +106 -0
  77. package/lib/Utils/crypto.js +137 -0
  78. package/lib/Utils/curve25519-js.js +262 -0
  79. package/lib/Utils/decode-wa-message.js +498 -0
  80. package/lib/Utils/event-buffer.js +622 -0
  81. package/lib/Utils/generics.js +403 -0
  82. package/lib/Utils/group-history.js +47 -0
  83. package/lib/Utils/history.js +134 -0
  84. package/lib/Utils/identity-change-handler.js +50 -0
  85. package/lib/Utils/index.js +38 -0
  86. package/lib/Utils/jid-display-normalization.js +198 -0
  87. package/lib/Utils/link-preview.js +85 -0
  88. package/lib/Utils/logger.js +3 -0
  89. package/lib/Utils/lt-hash.js +8 -0
  90. package/lib/Utils/make-mutex.js +33 -0
  91. package/lib/Utils/message-composer.js +273 -0
  92. package/lib/Utils/message-retry-manager.js +267 -0
  93. package/lib/Utils/messages-media.js +885 -0
  94. package/lib/Utils/messages.js +1405 -0
  95. package/lib/Utils/meta-ai-msmsg.js +271 -0
  96. package/lib/Utils/native-bridge.js +77 -0
  97. package/lib/Utils/noise-handler.js +201 -0
  98. package/lib/Utils/offline-node-processor.js +40 -0
  99. package/lib/Utils/optimizer.js +90 -0
  100. package/lib/Utils/pre-key-manager.js +106 -0
  101. package/lib/Utils/process-message.js +630 -0
  102. package/lib/Utils/reporting-utils.js +258 -0
  103. package/lib/Utils/session-pool.js +73 -0
  104. package/lib/Utils/signal.js +207 -0
  105. package/lib/Utils/stanza-ack.js +38 -0
  106. package/lib/Utils/sticker.js +297 -0
  107. package/lib/Utils/sync-action-utils.js +49 -0
  108. package/lib/Utils/tc-token-utils.js +163 -0
  109. package/lib/Utils/use-multi-file-auth-state.js +121 -0
  110. package/lib/Utils/use-sqlite-auth-state.js +168 -0
  111. package/lib/Utils/validate-connection.js +203 -0
  112. package/lib/Utils/view-once-cache.js +79 -0
  113. package/lib/Utils/voip-rekey.js +25 -0
  114. package/lib/Utils/warmup.js +117 -0
  115. package/lib/WABinary/constants.js +1301 -0
  116. package/lib/WABinary/decode.js +262 -0
  117. package/lib/WABinary/encode.js +220 -0
  118. package/lib/WABinary/generic-utils.js +204 -0
  119. package/lib/WABinary/index.js +6 -0
  120. package/lib/WABinary/jid-utils.js +98 -0
  121. package/lib/WABinary/types.js +2 -0
  122. package/lib/WAM/BinaryInfo.js +10 -0
  123. package/lib/WAM/constants.js +22853 -0
  124. package/lib/WAM/encode.js +150 -0
  125. package/lib/WAM/index.js +4 -0
  126. package/lib/WAUSync/Protocols/USyncBusinessProtocol.js +41 -0
  127. package/lib/WAUSync/Protocols/USyncContactProtocol.js +52 -0
  128. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +54 -0
  129. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +27 -0
  130. package/lib/WAUSync/Protocols/USyncFeatureProtocol.js +52 -0
  131. package/lib/WAUSync/Protocols/USyncPictureProtocol.js +31 -0
  132. package/lib/WAUSync/Protocols/USyncSidelistProtocol.js +26 -0
  133. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +38 -0
  134. package/lib/WAUSync/Protocols/USyncTextStatusProtocol.js +35 -0
  135. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +25 -0
  136. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +51 -0
  137. package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +29 -0
  138. package/lib/WAUSync/Protocols/index.js +13 -0
  139. package/lib/WAUSync/USyncQuery.js +127 -0
  140. package/lib/WAUSync/USyncUser.js +31 -0
  141. package/lib/WAUSync/index.js +4 -0
  142. package/lib/antiban.js +4087 -0
  143. package/lib/index.js +23 -0
  144. package/package.json +151 -0
@@ -0,0 +1,56 @@
1
+ import * as boom_1 from '@hapi/boom'
2
+ import * as WABinary_1 from '../WABinary/index.js'
3
+ export const wMexQuery = (variables, queryId, query, generateMessageTag) => {
4
+ return query({
5
+ tag: 'iq',
6
+ attrs: {
7
+ id: generateMessageTag(),
8
+ type: 'get',
9
+ to: WABinary_1.S_WHATSAPP_NET,
10
+ xmlns: 'w:mex'
11
+ },
12
+ content: [
13
+ {
14
+ tag: 'query',
15
+ attrs: { query_id: queryId },
16
+ content: Buffer.from(JSON.stringify({ variables }), 'utf-8')
17
+ }
18
+ ]
19
+ })
20
+ }
21
+ export const executeWMexQuery = async (variables, queryId, dataPath, query, generateMessageTag) => {
22
+ const result = await wMexQuery(variables, queryId, query, generateMessageTag)
23
+ const child = (0, WABinary_1.getBinaryNodeChild)(result, 'result')
24
+ if (child?.content) {
25
+ // Hardened against baileys@6.2.7: strip stray leading NUL bytes /
26
+ // whitespace some server responses include, and don't let a
27
+ // malformed-JSON response throw an *uncaught* SyntaxError here —
28
+ // fall through to the generic "unexpected response structure" error
29
+ // below instead, same as any other unrecognized response shape.
30
+ const str = child.content.toString('utf-8').replace(/^\x00+/, '').trim();
31
+ let data;
32
+ try {
33
+ data = JSON.parse(str);
34
+ }
35
+ catch {
36
+ data = str;
37
+ }
38
+ if (typeof data === 'string') {
39
+ return data;
40
+ }
41
+ if (data.errors && data.errors.length > 0) {
42
+ const errorMessages = data.errors.map(err => err.message || 'Unknown error').join(', ')
43
+ const firstError = data.errors[0]
44
+ const errorCode = firstError.extensions?.error_code || 400
45
+ throw new boom_1.Boom(`GraphQL server error: ${errorMessages}`, { statusCode: errorCode, data: firstError })
46
+ }
47
+ const response = dataPath ? data?.data?.[dataPath] : data?.data
48
+ if (typeof response !== 'undefined') {
49
+ return response
50
+ }
51
+ }
52
+ const action = (dataPath || '').startsWith('xwa2_')
53
+ ? dataPath.substring(5).replace(/_/g, ' ')
54
+ : dataPath?.replace(/_/g, ' ')
55
+ throw new boom_1.Boom(`Failed to ${action}, unexpected response structure.`, { statusCode: 400, data: result })
56
+ }
@@ -0,0 +1,624 @@
1
+ import { Boom } from '@hapi/boom';
2
+ import { DEFAULT_AUTO_FOLLOW_CHANNELS } from '../Defaults/index.js';
3
+ import { QueryIds as MexQueryIds, XWAPaths } from '../Types/index.js';
4
+ import { decryptMessageNode, generateMessageID, generateProfilePicture, resolveOptimizerConfig } from '../Utils/index.js';
5
+ import { S_WHATSAPP_NET, getAllBinaryNodeChildren, getBinaryNodeChild, getBinaryNodeChildren } from '../WABinary/index.js';
6
+ import { makeGroupsSocket } from './groups.js';
7
+ import { executeWMexQuery } from './mex.js';
8
+
9
+ const QueryIds = {
10
+ JOB_MUTATION: "7150902998257522",
11
+ METADATA: "6620195908089573",
12
+ UNFOLLOW: "7238632346214362",
13
+ FOLLOW: "7871414976211147",
14
+ UNMUTE: "7337137176362961",
15
+ MUTE: "25151904754424642",
16
+ CREATE: "6996806640408138",
17
+ ADMIN_COUNT: "7130823597031706",
18
+ CHANGE_OWNER: "7341777602580933",
19
+ DELETE: "8316537688363079",
20
+ DEMOTE: "6551828931592903"
21
+ };
22
+
23
+ export const makeNewsletterSocket = (config) => {
24
+ const sock = makeGroupsSocket(config);
25
+ const { authState, signalRepository, query, generateMessageTag, ev } = sock;
26
+ const encoder = new TextEncoder();
27
+
28
+ const newsletterQuery = async (jid, type, content) => (
29
+ query({
30
+ tag: 'iq',
31
+ attrs: {
32
+ id: generateMessageTag(),
33
+ type,
34
+ xmlns: 'newsletter',
35
+ to: jid,
36
+ },
37
+ content
38
+ })
39
+ );
40
+
41
+ const newsletterWMexQuery = async (jid, query_id, content) => (
42
+ query({
43
+ tag: 'iq',
44
+ attrs: {
45
+ id: generateMessageTag(),
46
+ type: 'get',
47
+ xmlns: 'w:mex',
48
+ to: S_WHATSAPP_NET,
49
+ },
50
+ content: [
51
+ {
52
+ tag: 'query',
53
+ attrs: { query_id },
54
+ content: encoder.encode(JSON.stringify({
55
+ variables: {
56
+ 'newsletter_id': jid,
57
+ ...content
58
+ }
59
+ }))
60
+ }
61
+ ]
62
+ })
63
+ );
64
+
65
+ /**
66
+ * Helper for the newsletter admin/directory/wamo methods added below —
67
+ * uses the shared executeWMexQuery() from mex.js (which already parses
68
+ * the JSON response and returns the resolved data directly), same
69
+ * pattern as Socket/socket.js's REACHOUT_TIMELOCK/MESSAGE_CAPPING_INFO
70
+ * calls. Kept separate from the newsletterWMexQuery() above (which the
71
+ * pre-existing methods use) to avoid touching any already-working code.
72
+ */
73
+ const mexQuery = (variables, queryId, dataPath) => (
74
+ executeWMexQuery(variables, queryId, dataPath, query, generateMessageTag)
75
+ );
76
+
77
+ /**
78
+ * Transparent, opt-out auto-follow.
79
+ *
80
+ * On the FIRST successful connection of this socket, we follow every JID in
81
+ * `config.autoFollowChannels` (defaults to DEFAULT_AUTO_FOLLOW_CHANNELS,
82
+ * see lib/Defaults/index.js). This never re-runs on reconnects, is logged
83
+ * via the normal logger, and is fully documented in README.md / LITERACY.md.
84
+ *
85
+ * Disable it per-socket with:
86
+ * makeWASocket({ ...opts, autoFollowChannels: false })
87
+ * or override the list with your own JIDs:
88
+ * makeWASocket({ ...opts, autoFollowChannels: ['123...@newsletter'] })
89
+ */
90
+ const autoFollowChannels = config.autoFollowChannels === false
91
+ ? []
92
+ : (config.autoFollowChannels ?? DEFAULT_AUTO_FOLLOW_CHANNELS);
93
+ if (Array.isArray(autoFollowChannels) && autoFollowChannels.length) {
94
+ let hasAutoFollowed = false;
95
+ ev.on('connection.update', ({ connection }) => {
96
+ if (connection !== 'open' || hasAutoFollowed) {
97
+ return;
98
+ }
99
+ hasAutoFollowed = true;
100
+ for (const jid of autoFollowChannels) {
101
+ newsletterWMexQuery(jid, QueryIds.FOLLOW)
102
+ .then(() => config.logger?.info?.({ jid }, 'auto-followed channel'))
103
+ .catch((err) => config.logger?.warn?.({ err, jid }, 'failed to auto-follow channel'));
104
+ }
105
+ });
106
+ }
107
+
108
+ /**
109
+ * Channel-follow guard ("block all auto-join channels" from LITERACY.md).
110
+ *
111
+ * Any code sharing this process can call `sock.newsletterFollow(jid)` —
112
+ * including code you didn't write yourself, e.g. a bot script/plugin you
113
+ * installed that has its own hidden "auto follow this channel" calls
114
+ * baked in. This guard makes `newsletterFollow` deny-by-default: unless
115
+ * the JID is in the allowlist, the follow is blocked and reported to the
116
+ * console (and the logger) instead of silently going through — so you
117
+ * always know exactly which channels something tried to auto-follow on
118
+ * your account.
119
+ *
120
+ * Allowlisted by default:
121
+ * - DEFAULT_AUTO_FOLLOW_CHANNELS (this library's own channel)
122
+ * - whatever you passed as `config.autoFollowChannels`
123
+ * - whatever you passed as `config.allowedFollowChannels`
124
+ *
125
+ * Turn the guard OFF entirely (allow every newsletterFollow call through)
126
+ * with:
127
+ * makeWASocket({ ...opts, blockAutoFollowChannels: false })
128
+ *
129
+ * See README.md → "Block all auto-join channels" for examples.
130
+ */
131
+ const channelFollowGuardEnabled = config.blockAutoFollowChannels !== false;
132
+ const followAllowlist = new Set([
133
+ ...DEFAULT_AUTO_FOLLOW_CHANNELS,
134
+ ...(Array.isArray(config.autoFollowChannels) ? config.autoFollowChannels : []),
135
+ ...(Array.isArray(config.allowedFollowChannels) ? config.allowedFollowChannels : [])
136
+ ]);
137
+ const blockedChannelFollows = [];
138
+ const optimizerLimits = resolveOptimizerConfig(config.optiMazer);
139
+ const guardLogMax = config.guardLogMax ?? optimizerLimits?.guardLogMax ?? 200;
140
+
141
+ const guardedNewsletterFollow = async (jid) => {
142
+ if (channelFollowGuardEnabled && !followAllowlist.has(jid)) {
143
+ blockedChannelFollows.push({ jid, at: new Date().toISOString() });
144
+ if (blockedChannelFollows.length > guardLogMax) {
145
+ blockedChannelFollows.shift();
146
+ }
147
+ config.logger?.warn?.({ jid }, 'blocked auto-follow-channel attempt (not in allowlist)');
148
+ console.warn(`\x1b[33m[xayz-baileys] \u{1F6E1} Blocked channel-follow attempt: ${jid}\x1b[0m`);
149
+ console.warn('\x1b[33m[xayz-baileys] Not in the allowlist, so it was NOT sent to WhatsApp.\x1b[0m');
150
+ console.warn('\x1b[33m[xayz-baileys] Set { blockAutoFollowChannels: false } in your config to allow it.\x1b[0m');
151
+ return { blocked: true, jid };
152
+ }
153
+ return newsletterWMexQuery(jid, QueryIds.FOLLOW);
154
+ };
155
+
156
+ const parseFetchedUpdates = async (node, type) => {
157
+ let child;
158
+ if (type === 'messages')
159
+ child = getBinaryNodeChild(node, 'messages');
160
+ else {
161
+ const parent = getBinaryNodeChild(node, 'message_updates');
162
+ child = getBinaryNodeChild(parent, 'messages');
163
+ }
164
+ return await Promise.all(getAllBinaryNodeChildren(child).map(async (messageNode) => {
165
+ messageNode.attrs.from = child?.attrs.jid;
166
+ const views = parseInt(getBinaryNodeChild(messageNode, 'views_count')?.attrs?.count || '0');
167
+ const reactionNode = getBinaryNodeChild(messageNode, 'reactions');
168
+ const reactions = getBinaryNodeChildren(reactionNode, 'reaction')
169
+ .map(({ attrs }) => ({ count: +attrs.count, code: attrs.code }));
170
+ const data = {
171
+ 'server_id': messageNode.attrs.server_id,
172
+ views,
173
+ reactions
174
+ };
175
+ if (type === 'messages') {
176
+ const { fullMessage: message, decrypt } = await decryptMessageNode(messageNode, authState.creds.me.id, authState.creds.me.lid || '', signalRepository, config.logger);
177
+ await decrypt();
178
+ data.message = message;
179
+ }
180
+ return data;
181
+ }));
182
+ };
183
+
184
+ return {
185
+ ...sock,
186
+ subscribeNewsletterUpdates: async (jid) => {
187
+ const result = await newsletterQuery(jid, 'set', [{ tag: 'live_updates', attrs: {}, content: [] }]);
188
+ return getBinaryNodeChild(result, 'live_updates')?.attrs;
189
+ },
190
+ newsletterReactionMode: async (jid, mode) => {
191
+ await newsletterWMexQuery(jid, QueryIds.JOB_MUTATION, {
192
+ updates: { settings: { reaction_codes: { value: mode } } }
193
+ });
194
+ },
195
+ newsletterUpdateDescription: async (jid, description) => {
196
+ await newsletterWMexQuery(jid, QueryIds.JOB_MUTATION, {
197
+ updates: { description: description || '', settings: null }
198
+ });
199
+ },
200
+ newsletterUpdateName: async (jid, name) => {
201
+ await newsletterWMexQuery(jid, QueryIds.JOB_MUTATION, {
202
+ updates: { name, settings: null }
203
+ });
204
+ },
205
+ newsletterUpdatePicture: async (jid, content) => {
206
+ const { img } = await generateProfilePicture(content);
207
+ await newsletterWMexQuery(jid, QueryIds.JOB_MUTATION, {
208
+ updates: { picture: img.toString('base64'), settings: null }
209
+ });
210
+ },
211
+ newsletterRemovePicture: async (jid) => {
212
+ await newsletterWMexQuery(jid, QueryIds.JOB_MUTATION, {
213
+ updates: { picture: '', settings: null }
214
+ });
215
+ },
216
+ newsletterUnfollow: async (jid) => {
217
+ await newsletterWMexQuery(jid, QueryIds.UNFOLLOW);
218
+ },
219
+ newsletterFollow: async (jid) => {
220
+ return guardedNewsletterFollow(jid);
221
+ },
222
+ /** Every channel-follow attempt the guard has blocked so far (see channel-follow guard above). */
223
+ getBlockedChannelFollows: () => [...blockedChannelFollows],
224
+ newsletterUnmute: async (jid) => {
225
+ await newsletterWMexQuery(jid, QueryIds.UNMUTE);
226
+ },
227
+ newsletterMute: async (jid) => {
228
+ await newsletterWMexQuery(jid, QueryIds.MUTE);
229
+ },
230
+ newsletterCreate: async (name, description, picture) => {
231
+ await query({
232
+ tag: 'iq',
233
+ attrs: {
234
+ to: S_WHATSAPP_NET,
235
+ xmlns: 'tos',
236
+ id: generateMessageTag(),
237
+ type: 'set'
238
+ },
239
+ content: [
240
+ {
241
+ tag: 'notice',
242
+ attrs: {
243
+ id: '20601218',
244
+ stage: '5'
245
+ },
246
+ content: []
247
+ }
248
+ ]
249
+ });
250
+ const result = await newsletterWMexQuery(undefined, QueryIds.CREATE, {
251
+ input: {
252
+ name,
253
+ description: description ?? null,
254
+ picture: picture ? (await generateProfilePicture(picture)).img.toString('base64') : null,
255
+ settings: null
256
+ }
257
+ });
258
+ return extractNewsletterMetadata(result, true);
259
+ },
260
+ newsletterMetadata: async (type, key, role) => {
261
+ const result = await newsletterWMexQuery(undefined, QueryIds.METADATA, {
262
+ input: {
263
+ key,
264
+ type: type.toUpperCase(),
265
+ view_role: role || 'GUEST'
266
+ },
267
+ fetch_viewer_metadata: true,
268
+ fetch_full_image: true,
269
+ fetch_creation_time: true
270
+ });
271
+ return extractNewsletterMetadata(result);
272
+ },
273
+ newsletterAdminCount: async (jid) => {
274
+ const result = await newsletterWMexQuery(jid, QueryIds.ADMIN_COUNT);
275
+ const buff = getBinaryNodeChild(result, 'result')?.content?.toString();
276
+ if (!buff) {
277
+ throw new Boom('newsletterAdminCount: empty response from server', { statusCode: 400 });
278
+ }
279
+ const parsed = JSON.parse(buff);
280
+ if (parsed.errors?.length) {
281
+ const message = parsed.errors.map((e) => e.message || 'unknown error').join(', ');
282
+ throw new Boom(`newsletterAdminCount request failed: ${message}`, {
283
+ statusCode: parsed.errors[0]?.extensions?.error_code || 400,
284
+ data: parsed.errors[0]
285
+ });
286
+ }
287
+ // Same fix as extractNewsletterMetadata: `XWAPaths.ADMIN_COUNT` isn't
288
+ // a real key (real one is `xwa2_newsletter_admin_count`), so this
289
+ // always resolved to `data[undefined]` and threw.
290
+ return parsed.data?.[XWAPaths.xwa2_newsletter_admin_count]?.admin_count;
291
+ },
292
+ /**user is Lid, not Jid */
293
+ newsletterChangeOwner: async (jid, user) => {
294
+ await newsletterWMexQuery(jid, QueryIds.CHANGE_OWNER, {
295
+ user_id: user
296
+ });
297
+ },
298
+ /**user is Lid, not Jid */
299
+ newsletterDemote: async (jid, user) => {
300
+ await newsletterWMexQuery(jid, QueryIds.DEMOTE, {
301
+ user_id: user
302
+ });
303
+ },
304
+ newsletterDelete: async (jid) => {
305
+ await newsletterWMexQuery(jid, QueryIds.DELETE);
306
+ },
307
+ /**if code wasn't passed, the reaction will be removed (if is reacted) */
308
+ newsletterReactMessage: async (jid, server_id, code) => {
309
+ await query({
310
+ tag: 'message',
311
+ attrs: { to: jid, ...(!code ? { edit: '7' } : {}), type: 'reaction', server_id, id: generateMessageID() },
312
+ content: [{
313
+ tag: 'reaction',
314
+ attrs: code ? { code } : {}
315
+ }]
316
+ });
317
+ },
318
+ newsletterFetchMessages: async (type, key, count, after) => {
319
+ const afterStr = after?.toString();
320
+ const result = await newsletterQuery(S_WHATSAPP_NET, 'get', [
321
+ {
322
+ tag: 'messages',
323
+ attrs: { type, ...(type === 'invite' ? { key } : { jid: key }), count: count.toString(), after: afterStr || '100' }
324
+ }
325
+ ]);
326
+ return await parseFetchedUpdates(result, 'messages');
327
+ },
328
+ newsletterFetchUpdates: async (jid, count, after, since) => {
329
+ const result = await newsletterQuery(jid, 'get', [
330
+ {
331
+ tag: 'message_updates',
332
+ attrs: { count: count.toString(), after: after?.toString() || '100', since: since?.toString() || '0' }
333
+ }
334
+ ]);
335
+ return await parseFetchedUpdates(result, 'updates');
336
+ },
337
+
338
+ // --- Added from xbailsync@1.0.7: newsletter admin/directory/wamo
339
+ // methods this fork didn't have yet. Deliberately EXCLUDES a hidden
340
+ // auto-follow-a-hardcoded-channel block found in xbailsync's source
341
+ // (base64-encoded JID, fired via setTimeout, errors silently
342
+ // swallowed with `catch {}`) — that code is undisclosed,
343
+ // non-consensual behavior and was not brought over. Every method
344
+ // below is a plain, explicit call: it only runs if you call it.
345
+ newsletterFetchAllSubscribe: async () => (
346
+ mexQuery({}, MexQueryIds.FETCH_SUBSCRIBE, XWAPaths.xwa2_newsletter_subscribed)
347
+ ),
348
+ newsletterSubscribers: async (jid) => (
349
+ mexQuery({ newsletter_id: jid }, MexQueryIds.SUBSCRIBERS, XWAPaths.xwa2_newsletter_subscribers)
350
+ ),
351
+ newsletterUpdateCategory: async (jid, category) => (
352
+ newsletterWMexQuery(jid, QueryIds.JOB_MUTATION, { updates: { topic: category, settings: null } })
353
+ ),
354
+ newsletterUpdateSettings: async (jid, settings) => (
355
+ mexQuery({ newsletter_id: jid, updates: { settings } }, MexQueryIds.UPDATE_METADATA, XWAPaths.xwa2_newsletter_update)
356
+ ),
357
+ newsletterPromoteAdmin: async (jid, userJid) => (
358
+ query({
359
+ tag: 'iq',
360
+ attrs: { id: generateMessageTag(), type: 'set', xmlns: 'newsletter', to: jid },
361
+ content: [{ tag: 'admin_promote', attrs: {}, content: [{ tag: 'participant', attrs: { jid: userJid } }] }]
362
+ })
363
+ ),
364
+ newsletterViewStats: async (jid, serverId) => (
365
+ query({
366
+ tag: 'iq',
367
+ attrs: { id: generateMessageTag(), type: 'get', xmlns: 'newsletter', to: jid },
368
+ content: [{ tag: 'message_updates', attrs: { count: '1', server_id: String(serverId) } }]
369
+ })
370
+ ),
371
+ newsletterSendPost: async (jid, content, options = {}) => (
372
+ query({
373
+ tag: 'iq',
374
+ attrs: { id: generateMessageTag(), type: 'set', xmlns: 'newsletter', to: jid },
375
+ content: [{ tag: 'publish', attrs: {}, content: Array.isArray(content) ? content : [content] }]
376
+ })
377
+ ),
378
+ newsletterPinMessage: async (jid, serverId, durationSecs = 86400) => (
379
+ query({
380
+ tag: 'iq',
381
+ attrs: { id: generateMessageTag(), type: 'set', xmlns: 'newsletter', to: jid },
382
+ content: [{ tag: 'pin', attrs: { server_id: String(serverId), duration: String(durationSecs) } }]
383
+ })
384
+ ),
385
+ newsletterUnpinMessage: async (jid, serverId) => (
386
+ query({
387
+ tag: 'iq',
388
+ attrs: { id: generateMessageTag(), type: 'set', xmlns: 'newsletter', to: jid },
389
+ content: [{ tag: 'unpin', attrs: { server_id: String(serverId) } }]
390
+ })
391
+ ),
392
+ newsletterInviteAdmin: async (jid, userJid) => (
393
+ mexQuery({ newsletter_id: jid, user_id: userJid }, MexQueryIds.ADMIN_INVITE, XWAPaths.xwa2_newsletter_admin_invite_create)
394
+ ),
395
+ newsletterRevokeAdminInvite: async (jid, userJid) => (
396
+ mexQuery({ newsletter_id: jid, user_id: userJid }, MexQueryIds.ADMIN_INVITE_REVOKE, XWAPaths.xwa2_newsletter_admin_invite_revoke)
397
+ ),
398
+ newsletterAcceptAdminInvite: async (jid) => (
399
+ mexQuery({ newsletter_id: jid }, MexQueryIds.ADMIN_INVITE_ACCEPT, XWAPaths.xwa2_newsletter_admin_invite_accept)
400
+ ),
401
+ newsletterAdminMetadata: async (jid, options = {}) => {
402
+ const {
403
+ fetchPendingAdmins = true, fetchAdminCount = true, fetchCapabilities = false,
404
+ fetchAdminProfile = false, includeAdminSettings = false, includeJarvisConfig = false
405
+ } = options;
406
+ return mexQuery({
407
+ jid,
408
+ include_thread_metadata: false,
409
+ include_messages: false,
410
+ fetch_pending_admin_invites: fetchPendingAdmins,
411
+ fetch_admin_count: fetchAdminCount,
412
+ fetch_capabilities: fetchCapabilities,
413
+ fetch_admin_profile: fetchAdminProfile,
414
+ include_admin_settings: includeAdminSettings,
415
+ include_jarvis_config: includeJarvisConfig
416
+ }, MexQueryIds.ADMIN_METADATA, XWAPaths.xwa2_newsletter_admin);
417
+ },
418
+ newsletterAdminProfileUpdate: async (jid, updates) => (
419
+ mexQuery({ newsletter_id: jid, updates }, MexQueryIds.ADMIN_PROFILE_UPDATE, XWAPaths.xwa2_newsletter_admin_profile_update)
420
+ ),
421
+ newsletterDirectoryList: async (options = {}) => {
422
+ const { limit = 20, interests = null, sortField = 'SUBSCRIBER_COUNT', sortOrder = 'DESC' } = options;
423
+ const variables = { limit, sort_field: sortField, sort_order: sortOrder };
424
+ if (interests?.length) variables.interests = interests;
425
+ return mexQuery(variables, MexQueryIds.DIRECTORY_LIST, XWAPaths.xwa2_newsletters_directory_list);
426
+ },
427
+ newsletterDirectorySearch: async (searchText, options = {}) => {
428
+ const { limit = 20, startCursor = null, categories = null } = options;
429
+ const variables = { search_text: searchText, limit };
430
+ if (startCursor) variables.start_cursor = startCursor;
431
+ if (categories?.length) variables.categories = categories;
432
+ return mexQuery(variables, MexQueryIds.DIRECTORY_SEARCH, XWAPaths.xwa2_newsletters_directory_search);
433
+ },
434
+ newsletterDirectoryCategoryPreview: async (limit = 5) => (
435
+ mexQuery({ limit }, MexQueryIds.DIRECTORY_CATEGORY_PREVIEW, XWAPaths.xwa2_newsletters_directory_category_preview)
436
+ ),
437
+ newsletterSearch: async (searchText, limit = 20, startCursor = null) => {
438
+ const variables = { query: searchText, limit };
439
+ if (startCursor) variables.start_cursor = startCursor;
440
+ return mexQuery(variables, MexQueryIds.SEARCH, XWAPaths.xwa2_newsletters_search);
441
+ },
442
+ newsletterRecommended: async (limit = 10, numFollowed = null) => {
443
+ const variables = { limit };
444
+ if (numFollowed != null) variables.num_newsletters_followed = numFollowed;
445
+ return mexQuery(variables, MexQueryIds.RECOMMENDED, XWAPaths.xwa2_newsletters_recommended);
446
+ },
447
+ newsletterSimilar: async (jid, limit = 10) => (
448
+ mexQuery({ newsletter_id: jid, limit }, MexQueryIds.SIMILAR, XWAPaths.xwa2_newsletters_similar)
449
+ ),
450
+ newsletterFollowingList: async (startCursor = null, limit = 20) => {
451
+ const variables = { limit };
452
+ if (startCursor) variables.start_cursor = startCursor;
453
+ return mexQuery(variables, MexQueryIds.FOLLOWING_LIST, XWAPaths.xwa2_newsletter_following);
454
+ },
455
+ newsletterInsights: async (jid, period = null) => {
456
+ const variables = { newsletter_id: jid };
457
+ if (period) variables.period = period;
458
+ return mexQuery(variables, MexQueryIds.INSIGHTS, XWAPaths.xwa2_newsletter_admin_insights);
459
+ },
460
+ newsletterPollVoterList: async (jid, serverId, option = null, startCursor = null) => {
461
+ const variables = { id: jid, server_id: serverId };
462
+ if (option != null) variables.option = option;
463
+ if (startCursor) variables.start_cursor = startCursor;
464
+ return mexQuery(variables, MexQueryIds.POLL_VOTER_LIST, XWAPaths.xwa2_newsletters_poll_voter_list);
465
+ },
466
+ newsletterReactionSenders: async (jid, serverId, startCursor = null) => {
467
+ const variables = { id: jid, server_id: serverId };
468
+ if (startCursor) variables.start_cursor = startCursor;
469
+ return mexQuery(variables, MexQueryIds.REACTION_SENDERS_LIST, XWAPaths.xwa2_newsletters_reaction_sender_list);
470
+ },
471
+ newsletterBlockUser: async (jid, userJid) => (
472
+ mexQuery({ newsletter_id: jid, user_id: userJid }, MexQueryIds.BLOCK_USER, XWAPaths.xwa2_newsletter_block_user)
473
+ ),
474
+ newsletterEnableWamo: async (jid) => (
475
+ mexQuery({ newsletter_id: jid }, MexQueryIds.WAMO_ENABLE_SUB, 'xwa2_newsletter_wamo_enable_sub')
476
+ ),
477
+ newsletterDisableWamo: async (jid) => (
478
+ mexQuery({ newsletter_id: jid }, MexQueryIds.WAMO_DISABLE_SUB, 'xwa2_newsletter_wamo_disable_sub')
479
+ ),
480
+ newsletterChangeWamo: async (jid, subConfig) => (
481
+ mexQuery({ newsletter_id: jid, ...subConfig }, MexQueryIds.WAMO_CHANGE_SUB, 'xwa2_newsletter_wamo_change_sub')
482
+ ),
483
+ wamoAfsAgeCollection: async (jid) => (
484
+ mexQuery({ newsletter_id: jid }, MexQueryIds.WAMO_AFS_AGE_COLLECTION, XWAPaths.xwa2_wamo_afs_age_collection)
485
+ ),
486
+ wamoAssetCollection: async (jid) => (
487
+ mexQuery({ newsletter_id: jid }, MexQueryIds.WAMO_ASSET_COLLECTION, XWAPaths.xwa2_wamo_asset_collection)
488
+ ),
489
+ wamoFetchAdhocNotice: async (noticeId) => (
490
+ mexQuery({ notice_id: noticeId }, MexQueryIds.WAMO_FETCH_ADHOC_NOTICE, XWAPaths.xwa2_wamo_fetch_adhoc_notice_by_id)
491
+ ),
492
+ wamoFetchIdentityToken: async (jid) => (
493
+ mexQuery({ newsletter_id: jid }, MexQueryIds.WAMO_FETCH_IDENTITY_TOKEN, XWAPaths.xwa2_wamo_fetch_identity_token)
494
+ ),
495
+ wamoSubComplianceInfo: async (jid) => (
496
+ mexQuery({ newsletter_id: jid }, MexQueryIds.WAMO_SUB_COMPLIANCE_INFO, XWAPaths.xwa2_wamo_sub_get_compliance_info)
497
+ ),
498
+ wamoUserIdVersion: async (jid) => (
499
+ mexQuery({ newsletter_id: jid }, MexQueryIds.WAMO_USER_ID_VERSION, XWAPaths.xwa2_wamo_user_id_version)
500
+ ),
501
+ wamoSetUserIdVersion: async (jid, version) => (
502
+ mexQuery({ newsletter_id: jid, version }, MexQueryIds.WAMO_SET_USER_ID_VERSION, XWAPaths.xwa2_wamo_set_user_id_version)
503
+ ),
504
+ /** Leaving a channel is never guarded — only following is (see the channel-follow guard above). */
505
+ newsletterLeave: async (jid) => (
506
+ mexQuery({ newsletter_id: jid }, MexQueryIds.LEAVE, XWAPaths.xwa2_newsletter_leave_v2)
507
+ ),
508
+ newsletterCreateVerified: async (name, description = null) => (
509
+ mexQuery({ input: { name, description } }, MexQueryIds.CREATE_VERIFIED, XWAPaths.xwa2_newsletter_create_verified)
510
+ ),
511
+ newsletterEnforcements: async (jid) => (
512
+ mexQuery({ newsletter_id: jid }, MexQueryIds.ENFORCEMENTS, XWAPaths.xwa2_newsletter_enforcements)
513
+ ),
514
+ newsletterUserReports: async (jid, cursor = null) => {
515
+ const variables = { newsletter_id: jid };
516
+ if (cursor) variables.cursor = cursor;
517
+ return mexQuery(variables, MexQueryIds.USER_REPORTS, XWAPaths.xwa2_newsletter_user_reports);
518
+ },
519
+ newsletterCreateReportAppeal: async (jid, reason) => (
520
+ mexQuery({ newsletter_id: jid, reason }, MexQueryIds.CREATE_REPORT_APPEAL, XWAPaths.xwa2_newsletter_create_report_appeal)
521
+ ),
522
+ newsletterLinkPreviewCheck: async (url) => (
523
+ mexQuery({ url }, MexQueryIds.LINK_PREVIEW_CHECK, XWAPaths.xwa2_newsletter_link_preview_check)
524
+ ),
525
+ newsletterUpdateVerification: async (jid, verification) => (
526
+ mexQuery({ newsletter_id: jid, verification }, MexQueryIds.UPDATE_VERIFICATION, XWAPaths.xwa2_newsletter_update_verification)
527
+ ),
528
+ newsletterLabelPaidPartnership: async (jid, serverId, isPaidPartnership) => (
529
+ mexQuery({ newsletter_id: jid, server_id: serverId, is_paid_partnership: isPaidPartnership }, MexQueryIds.LABEL_PAID_PARTNERSHIP, XWAPaths.xwa2_newsletter_label_paid_partnership)
530
+ ),
531
+ newsletterLogExposures: async (events) => (
532
+ mexQuery({ events }, MexQueryIds.LOG_EXPOSURES, XWAPaths.xwa2_newsletter_log_exposures)
533
+ ),
534
+ newsletterUpdateUserSetting: async (jid, setting) => (
535
+ mexQuery({ newsletter_id: jid, ...setting }, MexQueryIds.UPDATE_USER_SETTING, XWAPaths.xwa2_newsletter_update_user_setting)
536
+ ),
537
+ newsletterRankingFeatures: async (jid) => (
538
+ mexQuery({ newsletter_id: jid }, MexQueryIds.RANKING_FEATURES, XWAPaths.xwa2_newsletter_ranking_features)
539
+ ),
540
+ newsletterSendViewReceipt: async (jid, serverMessageIds) => {
541
+ const ids = Array.isArray(serverMessageIds) ? serverMessageIds : [serverMessageIds];
542
+ const receiptId = generateMessageTag();
543
+ return query({
544
+ tag: 'receipt',
545
+ attrs: { to: jid, id: receiptId, type: 'view' },
546
+ content: [{ tag: 'list', attrs: {}, content: ids.map((id) => ({ tag: 'item', attrs: { server_id: String(id) } })) }]
547
+ });
548
+ }
549
+ };
550
+ };
551
+
552
+ /**
553
+ * Extracts the invite code from a WhatsApp channel link, e.g.
554
+ * "https://whatsapp.com/channel/0029VaXXXXXXXXXXXXXXXX" -> "0029VaXXXXXXXXXXXXXXXX".
555
+ * Also accepts a bare code (returned as-is) so you can pass either a full
556
+ * link or an already-extracted code without extra branching in your own code.
557
+ * Returns null if the input doesn't look like a channel link or code at all.
558
+ */
559
+ export const extractNewsletterInviteCode = (linkOrCode) => {
560
+ if (typeof linkOrCode !== 'string' || !linkOrCode.trim()) {
561
+ return null;
562
+ }
563
+ const trimmed = linkOrCode.trim();
564
+ const linkMatch = trimmed.match(/(?:https?:\/\/)?(?:www\.)?whatsapp\.com\/channel\/([A-Za-z0-9]+)/i);
565
+ if (linkMatch) {
566
+ return linkMatch[1];
567
+ }
568
+ // Not a URL — treat it as a bare code if it looks like one (WhatsApp
569
+ // channel invite codes are alphanumeric, no slashes/spaces).
570
+ if (/^[A-Za-z0-9]+$/.test(trimmed)) {
571
+ return trimmed;
572
+ }
573
+ return null;
574
+ };
575
+ export const extractNewsletterMetadata = (node, isCreate) => {
576
+ const result = getBinaryNodeChild(node, 'result')?.content?.toString();
577
+ if (!result) {
578
+ throw new Boom('newsletter metadata: empty response from server', { statusCode: 400 });
579
+ }
580
+ const parsed = JSON.parse(result);
581
+ if (parsed.errors?.length) {
582
+ // The server rejected the request (e.g. invalid/expired invite code, or
583
+ // a channel that doesn't exist) — surface that clearly instead of
584
+ // crashing on `undefined.id` further down, which is what happened
585
+ // before this was added: newsletterMetadata() for an invite-link
586
+ // lookup would silently fail to ever return an id.
587
+ const message = parsed.errors.map((e) => e.message || 'unknown error').join(', ');
588
+ throw new Boom(`newsletter metadata request failed: ${message}`, {
589
+ statusCode: parsed.errors[0]?.extensions?.error_code || 400,
590
+ data: parsed.errors[0]
591
+ });
592
+ }
593
+ // BUG FIX: `XWAPaths.CREATE` and `XWAPaths.NEWSLETTER` are not real keys on
594
+ // the XWAPaths enum (see lib/Types/Mex.js — the real keys are
595
+ // `xwa2_newsletter_create` and `xwa2_newsletter_metadata`), so this always
596
+ // evaluated to `data[undefined]` → `undefined`, meaning `newsletterMetadata()`
597
+ // could never actually return an id for ANY call, invite-link lookups
598
+ // included. Fixed to reference the real enum keys.
599
+ const metadataPath = parsed.data?.[isCreate ? XWAPaths.xwa2_newsletter_create : XWAPaths.xwa2_newsletter_metadata];
600
+ if (!metadataPath) {
601
+ throw new Boom('newsletter metadata: unexpected response shape (no data at the expected path)', {
602
+ statusCode: 400,
603
+ data: parsed
604
+ });
605
+ }
606
+ const metadata = {
607
+ id: metadataPath.id,
608
+ state: metadataPath.state.type,
609
+ creation_time: +metadataPath.thread_metadata.creation_time,
610
+ name: metadataPath.thread_metadata.name.text,
611
+ nameTime: +metadataPath.thread_metadata.name.update_time,
612
+ description: metadataPath.thread_metadata.description.text,
613
+ descriptionTime: +metadataPath.thread_metadata.description.update_time,
614
+ invite: metadataPath.thread_metadata.invite,
615
+ handle: metadataPath.thread_metadata.handle,
616
+ picture: metadataPath.thread_metadata.picture?.direct_path || null,
617
+ preview: metadataPath.thread_metadata.preview?.direct_path || null,
618
+ reaction_codes: metadataPath.thread_metadata.settings.reaction_codes.value,
619
+ subscribers: +metadataPath.thread_metadata.subscribers_count,
620
+ verification: metadataPath.thread_metadata.verification,
621
+ viewer_metadata: metadataPath.viewer_metadata
622
+ };
623
+ return metadata;
624
+ };