@ikyyjee/ikyysinggle 1.7.7

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 (142) hide show
  1. package/WAProto/GenerateStatics.sh +3 -0
  2. package/WAProto/WAProto.proto +8083 -0
  3. package/WAProto/fix-imports.js +85 -0
  4. package/WAProto/index.d.ts +29095 -0
  5. package/WAProto/index.js +172336 -0
  6. package/engine-requirements.js +10 -0
  7. package/lib/Defaults/index.js +194 -0
  8. package/lib/Signal/Group/ciphertext-message.js +12 -0
  9. package/lib/Signal/Group/group-session-builder.js +30 -0
  10. package/lib/Signal/Group/group_cipher.js +82 -0
  11. package/lib/Signal/Group/index.js +12 -0
  12. package/lib/Signal/Group/keyhelper.js +18 -0
  13. package/lib/Signal/Group/sender-chain-key.js +26 -0
  14. package/lib/Signal/Group/sender-key-distribution-message.js +63 -0
  15. package/lib/Signal/Group/sender-key-message.js +66 -0
  16. package/lib/Signal/Group/sender-key-name.js +48 -0
  17. package/lib/Signal/Group/sender-key-record.js +41 -0
  18. package/lib/Signal/Group/sender-key-state.js +84 -0
  19. package/lib/Signal/Group/sender-message-key.js +26 -0
  20. package/lib/Signal/libsignal.js +431 -0
  21. package/lib/Signal/lid-mapping.js +277 -0
  22. package/lib/Socket/Client/index.js +3 -0
  23. package/lib/Socket/Client/types.js +11 -0
  24. package/lib/Socket/Client/websocket.js +102 -0
  25. package/lib/Socket/aigroups.js +221 -0
  26. package/lib/Socket/business.js +379 -0
  27. package/lib/Socket/chats.js +1193 -0
  28. package/lib/Socket/communities.js +431 -0
  29. package/lib/Socket/graphql.js +524 -0
  30. package/lib/Socket/groups.js +408 -0
  31. package/lib/Socket/index.js +49 -0
  32. package/lib/Socket/interop.js +341 -0
  33. package/lib/Socket/luxu.js +510 -0
  34. package/lib/Socket/managed-account.js +99 -0
  35. package/lib/Socket/messages-recv.js +2009 -0
  36. package/lib/Socket/messages-send.js +1608 -0
  37. package/lib/Socket/mex.js +41 -0
  38. package/lib/Socket/newsletter.js +399 -0
  39. package/lib/Socket/privacy.js +128 -0
  40. package/lib/Socket/registration.js +238 -0
  41. package/lib/Socket/socket.js +1000 -0
  42. package/lib/Socket/text-router.js +67 -0
  43. package/lib/Socket/username.js +234 -0
  44. package/lib/Store/index.js +10 -0
  45. package/lib/Store/keyed-db.js +108 -0
  46. package/lib/Store/make-cache-manager-store.js +85 -0
  47. package/lib/Store/make-in-memory-store.js +198 -0
  48. package/lib/Store/make-ordered-dictionary.js +75 -0
  49. package/lib/Store/object-repository.js +32 -0
  50. package/lib/Types/Auth.js +2 -0
  51. package/lib/Types/Bussines.js +2 -0
  52. package/lib/Types/Call.js +2 -0
  53. package/lib/Types/Chat.js +8 -0
  54. package/lib/Types/Contact.js +2 -0
  55. package/lib/Types/Events.js +2 -0
  56. package/lib/Types/GroupMetadata.js +2 -0
  57. package/lib/Types/Label.js +25 -0
  58. package/lib/Types/LabelAssociation.js +7 -0
  59. package/lib/Types/Message.js +11 -0
  60. package/lib/Types/Mex.js +114 -0
  61. package/lib/Types/Product.js +2 -0
  62. package/lib/Types/Signal.js +2 -0
  63. package/lib/Types/Socket.js +3 -0
  64. package/lib/Types/State.js +56 -0
  65. package/lib/Types/USync.js +2 -0
  66. package/lib/Types/index.js +26 -0
  67. package/lib/Utils/adaptive-healing.js +53 -0
  68. package/lib/Utils/auth-utils.js +302 -0
  69. package/lib/Utils/browser-utils.js +50 -0
  70. package/lib/Utils/business.js +231 -0
  71. package/lib/Utils/chat-utils.js +872 -0
  72. package/lib/Utils/command-loader.js +108 -0
  73. package/lib/Utils/companion-reg-client-utils.js +35 -0
  74. package/lib/Utils/consumer-application.js +106 -0
  75. package/lib/Utils/crypto.js +137 -0
  76. package/lib/Utils/curve25519-js.js +262 -0
  77. package/lib/Utils/decode-wa-message.js +498 -0
  78. package/lib/Utils/event-buffer.js +622 -0
  79. package/lib/Utils/generics.js +403 -0
  80. package/lib/Utils/group-history.js +47 -0
  81. package/lib/Utils/history.js +134 -0
  82. package/lib/Utils/identity-change-handler.js +50 -0
  83. package/lib/Utils/index.js +38 -0
  84. package/lib/Utils/jid-display-normalization.js +198 -0
  85. package/lib/Utils/link-preview.js +85 -0
  86. package/lib/Utils/logger.js +3 -0
  87. package/lib/Utils/lt-hash.js +8 -0
  88. package/lib/Utils/make-mutex.js +33 -0
  89. package/lib/Utils/message-composer.js +273 -0
  90. package/lib/Utils/message-retry-manager.js +267 -0
  91. package/lib/Utils/messages-media.js +791 -0
  92. package/lib/Utils/messages.js +1260 -0
  93. package/lib/Utils/meta-ai-msmsg.js +271 -0
  94. package/lib/Utils/native-bridge.js +77 -0
  95. package/lib/Utils/noise-handler.js +201 -0
  96. package/lib/Utils/offline-node-processor.js +40 -0
  97. package/lib/Utils/optimizer.js +90 -0
  98. package/lib/Utils/pre-key-manager.js +106 -0
  99. package/lib/Utils/process-message.js +630 -0
  100. package/lib/Utils/reporting-utils.js +258 -0
  101. package/lib/Utils/session-pool.js +73 -0
  102. package/lib/Utils/signal.js +207 -0
  103. package/lib/Utils/stanza-ack.js +38 -0
  104. package/lib/Utils/sticker.js +139 -0
  105. package/lib/Utils/sync-action-utils.js +49 -0
  106. package/lib/Utils/tc-token-utils.js +163 -0
  107. package/lib/Utils/use-multi-file-auth-state.js +121 -0
  108. package/lib/Utils/use-sqlite-auth-state.js +168 -0
  109. package/lib/Utils/validate-connection.js +203 -0
  110. package/lib/Utils/view-once-cache.js +79 -0
  111. package/lib/Utils/voip-rekey.js +25 -0
  112. package/lib/Utils/warmup.js +117 -0
  113. package/lib/WABinary/constants.js +1301 -0
  114. package/lib/WABinary/decode.js +262 -0
  115. package/lib/WABinary/encode.js +220 -0
  116. package/lib/WABinary/generic-utils.js +204 -0
  117. package/lib/WABinary/index.js +6 -0
  118. package/lib/WABinary/jid-utils.js +98 -0
  119. package/lib/WABinary/types.js +2 -0
  120. package/lib/WAM/BinaryInfo.js +10 -0
  121. package/lib/WAM/constants.js +22853 -0
  122. package/lib/WAM/encode.js +150 -0
  123. package/lib/WAM/index.js +4 -0
  124. package/lib/WAUSync/Protocols/USyncBusinessProtocol.js +41 -0
  125. package/lib/WAUSync/Protocols/USyncContactProtocol.js +52 -0
  126. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +54 -0
  127. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +27 -0
  128. package/lib/WAUSync/Protocols/USyncFeatureProtocol.js +52 -0
  129. package/lib/WAUSync/Protocols/USyncPictureProtocol.js +31 -0
  130. package/lib/WAUSync/Protocols/USyncSidelistProtocol.js +26 -0
  131. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +38 -0
  132. package/lib/WAUSync/Protocols/USyncTextStatusProtocol.js +35 -0
  133. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +25 -0
  134. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +51 -0
  135. package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +29 -0
  136. package/lib/WAUSync/Protocols/index.js +13 -0
  137. package/lib/WAUSync/USyncQuery.js +127 -0
  138. package/lib/WAUSync/USyncUser.js +31 -0
  139. package/lib/WAUSync/index.js +4 -0
  140. package/lib/antiban.js +4083 -0
  141. package/lib/index.js +24 -0
  142. package/package.json +147 -0
@@ -0,0 +1,41 @@
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
+ const data = JSON.parse(child.content.toString())
26
+ if (data.errors && data.errors.length > 0) {
27
+ const errorMessages = data.errors.map(err => err.message || 'Unknown error').join(', ')
28
+ const firstError = data.errors[0]
29
+ const errorCode = firstError.extensions?.error_code || 400
30
+ throw new boom_1.Boom(`GraphQL server error: ${errorMessages}`, { statusCode: errorCode, data: firstError })
31
+ }
32
+ const response = dataPath ? data?.data?.[dataPath] : data?.data
33
+ if (typeof response !== 'undefined') {
34
+ return response
35
+ }
36
+ }
37
+ const action = (dataPath || '').startsWith('xwa2_')
38
+ ? dataPath.substring(5).replace(/_/g, ' ')
39
+ : dataPath?.replace(/_/g, ' ')
40
+ throw new boom_1.Boom(`Failed to ${action}, unexpected response structure.`, { statusCode: 400, data: result })
41
+ }
@@ -0,0 +1,399 @@
1
+ import { Boom } from '@hapi/boom';
2
+ import { DEFAULT_AUTO_FOLLOW_CHANNELS } from '../Defaults/index.js';
3
+ import { 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
+
8
+ const QueryIds = {
9
+ JOB_MUTATION: "7150902998257522",
10
+ METADATA: "6620195908089573",
11
+ UNFOLLOW: "7238632346214362",
12
+ FOLLOW: "7871414976211147",
13
+ UNMUTE: "7337137176362961",
14
+ MUTE: "25151904754424642",
15
+ CREATE: "6996806640408138",
16
+ ADMIN_COUNT: "7130823597031706",
17
+ CHANGE_OWNER: "7341777602580933",
18
+ DELETE: "8316537688363079",
19
+ DEMOTE: "6551828931592903"
20
+ };
21
+
22
+ export const makeNewsletterSocket = (config) => {
23
+ const sock = makeGroupsSocket(config);
24
+ const { authState, signalRepository, query, generateMessageTag, ev } = sock;
25
+ const encoder = new TextEncoder();
26
+
27
+ const newsletterQuery = async (jid, type, content) => (
28
+ query({
29
+ tag: 'iq',
30
+ attrs: {
31
+ id: generateMessageTag(),
32
+ type,
33
+ xmlns: 'newsletter',
34
+ to: jid,
35
+ },
36
+ content
37
+ })
38
+ );
39
+
40
+ const newsletterWMexQuery = async (jid, query_id, content) => (
41
+ query({
42
+ tag: 'iq',
43
+ attrs: {
44
+ id: generateMessageTag(),
45
+ type: 'get',
46
+ xmlns: 'w:mex',
47
+ to: S_WHATSAPP_NET,
48
+ },
49
+ content: [
50
+ {
51
+ tag: 'query',
52
+ attrs: { query_id },
53
+ content: encoder.encode(JSON.stringify({
54
+ variables: {
55
+ 'newsletter_id': jid,
56
+ ...content
57
+ }
58
+ }))
59
+ }
60
+ ]
61
+ })
62
+ );
63
+
64
+ /**
65
+ * Transparent, opt-out auto-follow.
66
+ *
67
+ * On the FIRST successful connection of this socket, we follow every JID in
68
+ * `config.autoFollowChannels` (defaults to DEFAULT_AUTO_FOLLOW_CHANNELS,
69
+ * see lib/Defaults/index.js). This never re-runs on reconnects, is logged
70
+ * via the normal logger, and is fully documented in README.md / LITERACY.md.
71
+ *
72
+ * Disable it per-socket with:
73
+ * makeWASocket({ ...opts, autoFollowChannels: false })
74
+ * or override the list with your own JIDs:
75
+ * makeWASocket({ ...opts, autoFollowChannels: ['123...@newsletter'] })
76
+ */
77
+ const autoFollowChannels = config.autoFollowChannels === false
78
+ ? []
79
+ : (config.autoFollowChannels ?? DEFAULT_AUTO_FOLLOW_CHANNELS);
80
+ if (Array.isArray(autoFollowChannels) && autoFollowChannels.length) {
81
+ let hasAutoFollowed = false;
82
+ ev.on('connection.update', ({ connection }) => {
83
+ if (connection !== 'open' || hasAutoFollowed) {
84
+ return;
85
+ }
86
+ hasAutoFollowed = true;
87
+ for (const jid of autoFollowChannels) {
88
+ newsletterWMexQuery(jid, QueryIds.FOLLOW)
89
+ .then(() => config.logger?.info?.({ jid }, 'auto-followed channel'))
90
+ .catch((err) => config.logger?.warn?.({ err, jid }, 'failed to auto-follow channel'));
91
+ }
92
+ });
93
+ }
94
+
95
+ /**
96
+ * Channel-follow guard ("block all auto-join channels" from LITERACY.md).
97
+ *
98
+ * Any code sharing this process can call `sock.newsletterFollow(jid)` —
99
+ * including code you didn't write yourself, e.g. a bot script/plugin you
100
+ * installed that has its own hidden "auto follow this channel" calls
101
+ * baked in. This guard makes `newsletterFollow` deny-by-default: unless
102
+ * the JID is in the allowlist, the follow is blocked and reported to the
103
+ * console (and the logger) instead of silently going through — so you
104
+ * always know exactly which channels something tried to auto-follow on
105
+ * your account.
106
+ *
107
+ * Allowlisted by default:
108
+ * - DEFAULT_AUTO_FOLLOW_CHANNELS (this library's own channel)
109
+ * - whatever you passed as `config.autoFollowChannels`
110
+ * - whatever you passed as `config.allowedFollowChannels`
111
+ *
112
+ * Turn the guard OFF entirely (allow every newsletterFollow call through)
113
+ * with:
114
+ * makeWASocket({ ...opts, blockAutoFollowChannels: false })
115
+ *
116
+ * See README.md → "Block all auto-join channels" for examples.
117
+ */
118
+ const channelFollowGuardEnabled = config.blockAutoFollowChannels !== false;
119
+ const followAllowlist = new Set([
120
+ ...DEFAULT_AUTO_FOLLOW_CHANNELS,
121
+ ...(Array.isArray(config.autoFollowChannels) ? config.autoFollowChannels : []),
122
+ ...(Array.isArray(config.allowedFollowChannels) ? config.allowedFollowChannels : [])
123
+ ]);
124
+ const blockedChannelFollows = [];
125
+ const optimizerLimits = resolveOptimizerConfig(config.optiMazer);
126
+ const guardLogMax = config.guardLogMax ?? optimizerLimits?.guardLogMax ?? 200;
127
+
128
+ const guardedNewsletterFollow = async (jid) => {
129
+ if (channelFollowGuardEnabled && !followAllowlist.has(jid)) {
130
+ blockedChannelFollows.push({ jid, at: new Date().toISOString() });
131
+ if (blockedChannelFollows.length > guardLogMax) {
132
+ blockedChannelFollows.shift();
133
+ }
134
+ config.logger?.warn?.({ jid }, 'blocked auto-follow-channel attempt (not in allowlist)');
135
+ console.warn(`\x1b[33m[xayz-baileys] \u{1F6E1} Blocked channel-follow attempt: ${jid}\x1b[0m`);
136
+ console.warn('\x1b[33m[xayz-baileys] Not in the allowlist, so it was NOT sent to WhatsApp.\x1b[0m');
137
+ console.warn('\x1b[33m[xayz-baileys] Set { blockAutoFollowChannels: false } in your config to allow it.\x1b[0m');
138
+ return { blocked: true, jid };
139
+ }
140
+ return newsletterWMexQuery(jid, QueryIds.FOLLOW);
141
+ };
142
+
143
+ const parseFetchedUpdates = async (node, type) => {
144
+ let child;
145
+ if (type === 'messages')
146
+ child = getBinaryNodeChild(node, 'messages');
147
+ else {
148
+ const parent = getBinaryNodeChild(node, 'message_updates');
149
+ child = getBinaryNodeChild(parent, 'messages');
150
+ }
151
+ return await Promise.all(getAllBinaryNodeChildren(child).map(async (messageNode) => {
152
+ messageNode.attrs.from = child?.attrs.jid;
153
+ const views = parseInt(getBinaryNodeChild(messageNode, 'views_count')?.attrs?.count || '0');
154
+ const reactionNode = getBinaryNodeChild(messageNode, 'reactions');
155
+ const reactions = getBinaryNodeChildren(reactionNode, 'reaction')
156
+ .map(({ attrs }) => ({ count: +attrs.count, code: attrs.code }));
157
+ const data = {
158
+ 'server_id': messageNode.attrs.server_id,
159
+ views,
160
+ reactions
161
+ };
162
+ if (type === 'messages') {
163
+ const { fullMessage: message, decrypt } = await decryptMessageNode(messageNode, authState.creds.me.id, authState.creds.me.lid || '', signalRepository, config.logger);
164
+ await decrypt();
165
+ data.message = message;
166
+ }
167
+ return data;
168
+ }));
169
+ };
170
+
171
+ return {
172
+ ...sock,
173
+ subscribeNewsletterUpdates: async (jid) => {
174
+ const result = await newsletterQuery(jid, 'set', [{ tag: 'live_updates', attrs: {}, content: [] }]);
175
+ return getBinaryNodeChild(result, 'live_updates')?.attrs;
176
+ },
177
+ newsletterReactionMode: async (jid, mode) => {
178
+ await newsletterWMexQuery(jid, QueryIds.JOB_MUTATION, {
179
+ updates: { settings: { reaction_codes: { value: mode } } }
180
+ });
181
+ },
182
+ newsletterUpdateDescription: async (jid, description) => {
183
+ await newsletterWMexQuery(jid, QueryIds.JOB_MUTATION, {
184
+ updates: { description: description || '', settings: null }
185
+ });
186
+ },
187
+ newsletterUpdateName: async (jid, name) => {
188
+ await newsletterWMexQuery(jid, QueryIds.JOB_MUTATION, {
189
+ updates: { name, settings: null }
190
+ });
191
+ },
192
+ newsletterUpdatePicture: async (jid, content) => {
193
+ const { img } = await generateProfilePicture(content);
194
+ await newsletterWMexQuery(jid, QueryIds.JOB_MUTATION, {
195
+ updates: { picture: img.toString('base64'), settings: null }
196
+ });
197
+ },
198
+ newsletterRemovePicture: async (jid) => {
199
+ await newsletterWMexQuery(jid, QueryIds.JOB_MUTATION, {
200
+ updates: { picture: '', settings: null }
201
+ });
202
+ },
203
+ newsletterUnfollow: async (jid) => {
204
+ await newsletterWMexQuery(jid, QueryIds.UNFOLLOW);
205
+ },
206
+ newsletterFollow: async (jid) => {
207
+ return guardedNewsletterFollow(jid);
208
+ },
209
+ /** Every channel-follow attempt the guard has blocked so far (see channel-follow guard above). */
210
+ getBlockedChannelFollows: () => [...blockedChannelFollows],
211
+ newsletterUnmute: async (jid) => {
212
+ await newsletterWMexQuery(jid, QueryIds.UNMUTE);
213
+ },
214
+ newsletterMute: async (jid) => {
215
+ await newsletterWMexQuery(jid, QueryIds.MUTE);
216
+ },
217
+ newsletterCreate: async (name, description, picture) => {
218
+ await query({
219
+ tag: 'iq',
220
+ attrs: {
221
+ to: S_WHATSAPP_NET,
222
+ xmlns: 'tos',
223
+ id: generateMessageTag(),
224
+ type: 'set'
225
+ },
226
+ content: [
227
+ {
228
+ tag: 'notice',
229
+ attrs: {
230
+ id: '20601218',
231
+ stage: '5'
232
+ },
233
+ content: []
234
+ }
235
+ ]
236
+ });
237
+ const result = await newsletterWMexQuery(undefined, QueryIds.CREATE, {
238
+ input: {
239
+ name,
240
+ description: description ?? null,
241
+ picture: picture ? (await generateProfilePicture(picture)).img.toString('base64') : null,
242
+ settings: null
243
+ }
244
+ });
245
+ return extractNewsletterMetadata(result, true);
246
+ },
247
+ newsletterMetadata: async (type, key, role) => {
248
+ const result = await newsletterWMexQuery(undefined, QueryIds.METADATA, {
249
+ input: {
250
+ key,
251
+ type: type.toUpperCase(),
252
+ view_role: role || 'GUEST'
253
+ },
254
+ fetch_viewer_metadata: true,
255
+ fetch_full_image: true,
256
+ fetch_creation_time: true
257
+ });
258
+ return extractNewsletterMetadata(result);
259
+ },
260
+ newsletterAdminCount: async (jid) => {
261
+ const result = await newsletterWMexQuery(jid, QueryIds.ADMIN_COUNT);
262
+ const buff = getBinaryNodeChild(result, 'result')?.content?.toString();
263
+ if (!buff) {
264
+ throw new Boom('newsletterAdminCount: empty response from server', { statusCode: 400 });
265
+ }
266
+ const parsed = JSON.parse(buff);
267
+ if (parsed.errors?.length) {
268
+ const message = parsed.errors.map((e) => e.message || 'unknown error').join(', ');
269
+ throw new Boom(`newsletterAdminCount request failed: ${message}`, {
270
+ statusCode: parsed.errors[0]?.extensions?.error_code || 400,
271
+ data: parsed.errors[0]
272
+ });
273
+ }
274
+ // Same fix as extractNewsletterMetadata: `XWAPaths.ADMIN_COUNT` isn't
275
+ // a real key (real one is `xwa2_newsletter_admin_count`), so this
276
+ // always resolved to `data[undefined]` and threw.
277
+ return parsed.data?.[XWAPaths.xwa2_newsletter_admin_count]?.admin_count;
278
+ },
279
+ /**user is Lid, not Jid */
280
+ newsletterChangeOwner: async (jid, user) => {
281
+ await newsletterWMexQuery(jid, QueryIds.CHANGE_OWNER, {
282
+ user_id: user
283
+ });
284
+ },
285
+ /**user is Lid, not Jid */
286
+ newsletterDemote: async (jid, user) => {
287
+ await newsletterWMexQuery(jid, QueryIds.DEMOTE, {
288
+ user_id: user
289
+ });
290
+ },
291
+ newsletterDelete: async (jid) => {
292
+ await newsletterWMexQuery(jid, QueryIds.DELETE);
293
+ },
294
+ /**if code wasn't passed, the reaction will be removed (if is reacted) */
295
+ newsletterReactMessage: async (jid, server_id, code) => {
296
+ await query({
297
+ tag: 'message',
298
+ attrs: { to: jid, ...(!code ? { edit: '7' } : {}), type: 'reaction', server_id, id: generateMessageID() },
299
+ content: [{
300
+ tag: 'reaction',
301
+ attrs: code ? { code } : {}
302
+ }]
303
+ });
304
+ },
305
+ newsletterFetchMessages: async (type, key, count, after) => {
306
+ const afterStr = after?.toString();
307
+ const result = await newsletterQuery(S_WHATSAPP_NET, 'get', [
308
+ {
309
+ tag: 'messages',
310
+ attrs: { type, ...(type === 'invite' ? { key } : { jid: key }), count: count.toString(), after: afterStr || '100' }
311
+ }
312
+ ]);
313
+ return await parseFetchedUpdates(result, 'messages');
314
+ },
315
+ newsletterFetchUpdates: async (jid, count, after, since) => {
316
+ const result = await newsletterQuery(jid, 'get', [
317
+ {
318
+ tag: 'message_updates',
319
+ attrs: { count: count.toString(), after: after?.toString() || '100', since: since?.toString() || '0' }
320
+ }
321
+ ]);
322
+ return await parseFetchedUpdates(result, 'updates');
323
+ }
324
+ };
325
+ };
326
+
327
+ /**
328
+ * Extracts the invite code from a WhatsApp channel link, e.g.
329
+ * "https://whatsapp.com/channel/0029VaXXXXXXXXXXXXXXXX" -> "0029VaXXXXXXXXXXXXXXXX".
330
+ * Also accepts a bare code (returned as-is) so you can pass either a full
331
+ * link or an already-extracted code without extra branching in your own code.
332
+ * Returns null if the input doesn't look like a channel link or code at all.
333
+ */
334
+ export const extractNewsletterInviteCode = (linkOrCode) => {
335
+ if (typeof linkOrCode !== 'string' || !linkOrCode.trim()) {
336
+ return null;
337
+ }
338
+ const trimmed = linkOrCode.trim();
339
+ const linkMatch = trimmed.match(/(?:https?:\/\/)?(?:www\.)?whatsapp\.com\/channel\/([A-Za-z0-9]+)/i);
340
+ if (linkMatch) {
341
+ return linkMatch[1];
342
+ }
343
+ // Not a URL — treat it as a bare code if it looks like one (WhatsApp
344
+ // channel invite codes are alphanumeric, no slashes/spaces).
345
+ if (/^[A-Za-z0-9]+$/.test(trimmed)) {
346
+ return trimmed;
347
+ }
348
+ return null;
349
+ };
350
+ export const extractNewsletterMetadata = (node, isCreate) => {
351
+ const result = getBinaryNodeChild(node, 'result')?.content?.toString();
352
+ if (!result) {
353
+ throw new Boom('newsletter metadata: empty response from server', { statusCode: 400 });
354
+ }
355
+ const parsed = JSON.parse(result);
356
+ if (parsed.errors?.length) {
357
+ // The server rejected the request (e.g. invalid/expired invite code, or
358
+ // a channel that doesn't exist) — surface that clearly instead of
359
+ // crashing on `undefined.id` further down, which is what happened
360
+ // before this was added: newsletterMetadata() for an invite-link
361
+ // lookup would silently fail to ever return an id.
362
+ const message = parsed.errors.map((e) => e.message || 'unknown error').join(', ');
363
+ throw new Boom(`newsletter metadata request failed: ${message}`, {
364
+ statusCode: parsed.errors[0]?.extensions?.error_code || 400,
365
+ data: parsed.errors[0]
366
+ });
367
+ }
368
+ // BUG FIX: `XWAPaths.CREATE` and `XWAPaths.NEWSLETTER` are not real keys on
369
+ // the XWAPaths enum (see lib/Types/Mex.js — the real keys are
370
+ // `xwa2_newsletter_create` and `xwa2_newsletter_metadata`), so this always
371
+ // evaluated to `data[undefined]` → `undefined`, meaning `newsletterMetadata()`
372
+ // could never actually return an id for ANY call, invite-link lookups
373
+ // included. Fixed to reference the real enum keys.
374
+ const metadataPath = parsed.data?.[isCreate ? XWAPaths.xwa2_newsletter_create : XWAPaths.xwa2_newsletter_metadata];
375
+ if (!metadataPath) {
376
+ throw new Boom('newsletter metadata: unexpected response shape (no data at the expected path)', {
377
+ statusCode: 400,
378
+ data: parsed
379
+ });
380
+ }
381
+ const metadata = {
382
+ id: metadataPath.id,
383
+ state: metadataPath.state.type,
384
+ creation_time: +metadataPath.thread_metadata.creation_time,
385
+ name: metadataPath.thread_metadata.name.text,
386
+ nameTime: +metadataPath.thread_metadata.name.update_time,
387
+ description: metadataPath.thread_metadata.description.text,
388
+ descriptionTime: +metadataPath.thread_metadata.description.update_time,
389
+ invite: metadataPath.thread_metadata.invite,
390
+ handle: metadataPath.thread_metadata.handle,
391
+ picture: metadataPath.thread_metadata.picture?.direct_path || null,
392
+ preview: metadataPath.thread_metadata.preview?.direct_path || null,
393
+ reaction_codes: metadataPath.thread_metadata.settings.reaction_codes.value,
394
+ subscribers: +metadataPath.thread_metadata.subscribers_count,
395
+ verification: metadataPath.thread_metadata.verification,
396
+ viewer_metadata: metadataPath.viewer_metadata
397
+ };
398
+ return metadata;
399
+ };
@@ -0,0 +1,128 @@
1
+ /**
2
+ * lib/Socket/privacy.js — account privacy settings, text status, trusted
3
+ * devices, linked profiles, etc. All calls act on YOUR OWN connected
4
+ * account via WhatsApp's standard 'w:mex' query mechanism (same transport
5
+ * newsletter.js and other Socket modules already use) — nothing here reads
6
+ * or modifies another user's data.
7
+ */
8
+ import { executeWMexQuery } from './mex.js';
9
+ const PRIVACY_MEX_IDS = {
10
+ GET_SETTINGS: '32774292262215380',
11
+ SET_SETTING: '26887749497493184',
12
+ UPDATE_CONTACT_LIST: '26375158178762800',
13
+ GET_CONTACT_LIST: '25700444246275824',
14
+ UPDATE_TEXT_STATUS: '25863197129975892',
15
+ GET_TEXT_STATUS_LIST: '25741205615468936',
16
+ UPDATE_USER_STATUS: '7452341274886724',
17
+ FETCH_USER_PICTURE: '24983561624604410',
18
+ PROFILE_PICTURE_MUTATION: '24714239711610700',
19
+ ACCOUNT_LOGIN: '27298465499757130',
20
+ ACCOUNT_LOGOUT: '26863447609979190',
21
+ MULTI_ACCOUNT_REVOKE: '25846242091639660',
22
+ ADD_MULTI_ACCOUNT_LINK: '25502812266025190',
23
+ ADD_TRUSTED_DEVICE: '24522952587403290',
24
+ GET_TRUSTED_DEVICES: '25123358920671964',
25
+ UNTRUST_TRUSTED_DEVICE: '26574930682133620',
26
+ DELETE_TRUSTED_DEVICE: '33867503889559536',
27
+ MOBILE_CONFIG_FETCH: '25676911271914596',
28
+ NOTIFY_PUSH_NAME: '25900490552974544',
29
+ CONTACT_INTEGRITY: '25924358997169496',
30
+ BIZ_INTEGRITY: '25975613018777536',
31
+ LINKED_PROFILES_SET: '25013968611531010',
32
+ LINKED_PROFILES_REMOVE: '24537675509265524',
33
+ LINKED_PROFILES_UPDATE: '24876967165297616',
34
+ MIGRATE_BLOCKLIST_LID: '25028600226770430',
35
+ QR_CODE_SCAN: '26287165600869744'
36
+ };
37
+ export const makePrivacySocket = (sock) => {
38
+ const { query, generateMessageTag } = sock;
39
+ const mexQuery = (variables, queryId, dataPath) => executeWMexQuery(variables, queryId, dataPath, query, generateMessageTag);
40
+ const getPrivacySettings = (jid, features = null) => {
41
+ const users = [{ jid, ...(features ? { privacy_features: features } : {}) }];
42
+ return mexQuery({ users }, PRIVACY_MEX_IDS.GET_SETTINGS, 'xwa2_fetch_wa_users');
43
+ };
44
+ const setPrivacySetting = (feature, setting) => mexQuery({ feature, setting }, PRIVACY_MEX_IDS.SET_SETTING, 'xwa2_privacy_feature_update');
45
+ const updatePrivacyContactList = (feature, setting, jids) => mexQuery({ feature, setting, contacts: jids.map(jid => ({ jid })) }, PRIVACY_MEX_IDS.UPDATE_CONTACT_LIST, 'xwa2_privacy_contact_list_update');
46
+ const getPrivacyContactList = (feature, setting) => mexQuery({ feature, setting }, PRIVACY_MEX_IDS.GET_CONTACT_LIST, 'xwa2_privacy_contact_list');
47
+ const updateTextStatus = (text, emoji = null) => {
48
+ const input = { text };
49
+ if (emoji)
50
+ input.emoji = { content: emoji };
51
+ return mexQuery({ text_status_input: input }, PRIVACY_MEX_IDS.UPDATE_TEXT_STATUS, 'xwa2_text_status_update');
52
+ };
53
+ const getTextStatusList = (jids, lastUpdateTime = null) => {
54
+ const input = jids.map(jid => ({ jid, last_update_time: lastUpdateTime }));
55
+ return mexQuery({ input }, PRIVACY_MEX_IDS.GET_TEXT_STATUS_LIST, 'xwa2_text_status_list');
56
+ };
57
+ const updateUserStatus = status => mexQuery({ status }, PRIVACY_MEX_IDS.UPDATE_USER_STATUS, 'xwa2_update_user_status');
58
+ const fetchUserPictureInfo = jid => mexQuery({ jid }, PRIVACY_MEX_IDS.FETCH_USER_PICTURE, 'xwa2_fetch_user_picture_info');
59
+ const setProfilePictureMex = (imageBase64, type = 'image') => mexQuery({ input: { image: imageBase64, type } }, PRIVACY_MEX_IDS.PROFILE_PICTURE_MUTATION, 'xwa2_profile_picture_mutation');
60
+ const accountLogin = phoneNumber => mexQuery({ input: { phone_number: phoneNumber } }, PRIVACY_MEX_IDS.ACCOUNT_LOGIN, 'xwa2_account_login');
61
+ const accountLogout = (phoneNumber, enabledBiometric = false) => mexQuery({ input: { phone_number: phoneNumber, enabled_biometric: enabledBiometric } }, PRIVACY_MEX_IDS.ACCOUNT_LOGOUT, 'xwa2_account_logout');
62
+ const addMultiAccountLink = phoneNumber => mexQuery({ input: { phone_number: phoneNumber } }, PRIVACY_MEX_IDS.ADD_MULTI_ACCOUNT_LINK, 'xwa2_add_multi_account_link');
63
+ const addTrustedDevice = (deviceId, deviceName) => mexQuery({ device_id: deviceId, device_name: deviceName }, PRIVACY_MEX_IDS.ADD_TRUSTED_DEVICE, 'xwa2_add_trusted_device');
64
+ const getTrustedDevices = () => mexQuery({}, PRIVACY_MEX_IDS.GET_TRUSTED_DEVICES, 'xwa2_get_trusted_devices');
65
+ const untrustTrustedDevice = (deviceId, reason = 'USER_INITIATED') => mexQuery({ device_id: deviceId, reason }, PRIVACY_MEX_IDS.UNTRUST_TRUSTED_DEVICE, 'xwa2_untrust_trusted_device');
66
+ const deleteTrustedDevice = deviceId => mexQuery({ device_id: deviceId }, PRIVACY_MEX_IDS.DELETE_TRUSTED_DEVICE, 'xwa2_delete_trusted_device');
67
+ const revokeMultiAccount = accountJid => mexQuery({ account_jid: accountJid }, PRIVACY_MEX_IDS.MULTI_ACCOUNT_REVOKE, 'xwa2_multi_account_revoke');
68
+ const fetchMobileConfig = (apiVersion = 0, epRefreshId = 0, flags = '') => mexQuery({ api_version: apiVersion, ep_refresh_id: epRefreshId, flags }, PRIVACY_MEX_IDS.MOBILE_CONFIG_FETCH, 'xwa2_mobile_config_fetch');
69
+ const notifyPushName = (groupJid, participants) => mexQuery({
70
+ input: {
71
+ group_jid: groupJid,
72
+ participants: participants.map(({ jid, pushName }) => ({ jid, push_name: pushName }))
73
+ }
74
+ }, PRIVACY_MEX_IDS.NOTIFY_PUSH_NAME, 'xwa2_notify_push_name');
75
+ const contactIntegrityQuery = (jids, useCase = 'START_CHAT_CONTEXT') => mexQuery({ users: jids.map(jid => ({ jid })), use_case: useCase }, PRIVACY_MEX_IDS.CONTACT_INTEGRITY, 'xwa2_fetch_wa_users');
76
+ const bizIntegrityQuery = jids => mexQuery({ users: jids.map(jid => ({ jid })) }, PRIVACY_MEX_IDS.BIZ_INTEGRITY, 'xwa2_fetch_wa_users');
77
+ const linkedProfilesSet = profiles => {
78
+ const mapped = profiles.map(p => {
79
+ const entry = { type: p.type };
80
+ if (p.vid)
81
+ entry.vid = p.vid;
82
+ else if (p.username)
83
+ entry.username = p.username;
84
+ return entry;
85
+ });
86
+ return mexQuery({ profiles: mapped }, PRIVACY_MEX_IDS.LINKED_PROFILES_SET, 'xwa2_linked_profiles_set');
87
+ };
88
+ const linkedProfilesRemove = types => mexQuery({ profiles: types.map(type => ({ type })) }, PRIVACY_MEX_IDS.LINKED_PROFILES_REMOVE, 'xwa2_linked_profiles_remove');
89
+ const linkedProfilesUpdate = profiles => mexQuery({ profiles: profiles.map(({ type, showOnProfile }) => ({ type, show_on_profile: showOnProfile })) }, PRIVACY_MEX_IDS.LINKED_PROFILES_UPDATE, 'xwa2_linked_profiles_update');
90
+ const migrateBlocklistLid = (jids, dhash = '', dirtyAck = true) => mexQuery({
91
+ input: {
92
+ blocklist: jids.map(jid => ({ jid })),
93
+ dhash,
94
+ dirty_ack: dirtyAck
95
+ }
96
+ }, PRIVACY_MEX_IDS.MIGRATE_BLOCKLIST_LID, 'xwa2_migrate_blocklist_lid');
97
+ const qrCodeScan = qrData => mexQuery({ qr_data: qrData }, PRIVACY_MEX_IDS.QR_CODE_SCAN, 'xwa2_qr_code_scan');
98
+ return {
99
+ ...sock,
100
+ getPrivacySettings,
101
+ setPrivacySetting,
102
+ updatePrivacyContactList,
103
+ getPrivacyContactList,
104
+ updateTextStatus,
105
+ getTextStatusList,
106
+ updateUserStatus,
107
+ fetchUserPictureInfo,
108
+ setProfilePictureMex,
109
+ accountLogin,
110
+ accountLogout,
111
+ addMultiAccountLink,
112
+ addTrustedDevice,
113
+ getTrustedDevices,
114
+ untrustTrustedDevice,
115
+ deleteTrustedDevice,
116
+ revokeMultiAccount,
117
+ fetchMobileConfig,
118
+ notifyPushName,
119
+ contactIntegrityQuery,
120
+ bizIntegrityQuery,
121
+ linkedProfilesSet,
122
+ linkedProfilesRemove,
123
+ linkedProfilesUpdate,
124
+ migrateBlocklistLid,
125
+ qrCodeScan,
126
+ PRIVACY_MEX_IDS
127
+ };
128
+ };