@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.
- package/WAProto/GenerateStatics.sh +3 -0
- package/WAProto/WAProto.proto +8083 -0
- package/WAProto/fix-imports.js +85 -0
- package/WAProto/index.d.ts +29095 -0
- package/WAProto/index.js +172336 -0
- package/engine-requirements.js +10 -0
- package/lib/Defaults/index.js +194 -0
- package/lib/Signal/Group/ciphertext-message.js +12 -0
- package/lib/Signal/Group/group-session-builder.js +30 -0
- package/lib/Signal/Group/group_cipher.js +82 -0
- package/lib/Signal/Group/index.js +12 -0
- package/lib/Signal/Group/keyhelper.js +18 -0
- package/lib/Signal/Group/sender-chain-key.js +26 -0
- package/lib/Signal/Group/sender-key-distribution-message.js +63 -0
- package/lib/Signal/Group/sender-key-message.js +66 -0
- package/lib/Signal/Group/sender-key-name.js +48 -0
- package/lib/Signal/Group/sender-key-record.js +41 -0
- package/lib/Signal/Group/sender-key-state.js +84 -0
- package/lib/Signal/Group/sender-message-key.js +26 -0
- package/lib/Signal/libsignal.js +431 -0
- package/lib/Signal/lid-mapping.js +277 -0
- package/lib/Socket/Client/index.js +3 -0
- package/lib/Socket/Client/types.js +11 -0
- package/lib/Socket/Client/websocket.js +102 -0
- package/lib/Socket/aigroups.js +221 -0
- package/lib/Socket/business.js +379 -0
- package/lib/Socket/chats.js +1193 -0
- package/lib/Socket/communities.js +431 -0
- package/lib/Socket/graphql.js +524 -0
- package/lib/Socket/groups.js +408 -0
- package/lib/Socket/index.js +49 -0
- package/lib/Socket/interop.js +341 -0
- package/lib/Socket/luxu.js +510 -0
- package/lib/Socket/managed-account.js +99 -0
- package/lib/Socket/messages-recv.js +2009 -0
- package/lib/Socket/messages-send.js +1608 -0
- package/lib/Socket/mex.js +41 -0
- package/lib/Socket/newsletter.js +399 -0
- package/lib/Socket/privacy.js +128 -0
- package/lib/Socket/registration.js +238 -0
- package/lib/Socket/socket.js +1000 -0
- package/lib/Socket/text-router.js +67 -0
- package/lib/Socket/username.js +234 -0
- package/lib/Store/index.js +10 -0
- package/lib/Store/keyed-db.js +108 -0
- package/lib/Store/make-cache-manager-store.js +85 -0
- package/lib/Store/make-in-memory-store.js +198 -0
- package/lib/Store/make-ordered-dictionary.js +75 -0
- package/lib/Store/object-repository.js +32 -0
- package/lib/Types/Auth.js +2 -0
- package/lib/Types/Bussines.js +2 -0
- package/lib/Types/Call.js +2 -0
- package/lib/Types/Chat.js +8 -0
- package/lib/Types/Contact.js +2 -0
- package/lib/Types/Events.js +2 -0
- package/lib/Types/GroupMetadata.js +2 -0
- package/lib/Types/Label.js +25 -0
- package/lib/Types/LabelAssociation.js +7 -0
- package/lib/Types/Message.js +11 -0
- package/lib/Types/Mex.js +114 -0
- package/lib/Types/Product.js +2 -0
- package/lib/Types/Signal.js +2 -0
- package/lib/Types/Socket.js +3 -0
- package/lib/Types/State.js +56 -0
- package/lib/Types/USync.js +2 -0
- package/lib/Types/index.js +26 -0
- package/lib/Utils/adaptive-healing.js +53 -0
- package/lib/Utils/auth-utils.js +302 -0
- package/lib/Utils/browser-utils.js +50 -0
- package/lib/Utils/business.js +231 -0
- package/lib/Utils/chat-utils.js +872 -0
- package/lib/Utils/command-loader.js +108 -0
- package/lib/Utils/companion-reg-client-utils.js +35 -0
- package/lib/Utils/consumer-application.js +106 -0
- package/lib/Utils/crypto.js +137 -0
- package/lib/Utils/curve25519-js.js +262 -0
- package/lib/Utils/decode-wa-message.js +498 -0
- package/lib/Utils/event-buffer.js +622 -0
- package/lib/Utils/generics.js +403 -0
- package/lib/Utils/group-history.js +47 -0
- package/lib/Utils/history.js +134 -0
- package/lib/Utils/identity-change-handler.js +50 -0
- package/lib/Utils/index.js +38 -0
- package/lib/Utils/jid-display-normalization.js +198 -0
- package/lib/Utils/link-preview.js +85 -0
- package/lib/Utils/logger.js +3 -0
- package/lib/Utils/lt-hash.js +8 -0
- package/lib/Utils/make-mutex.js +33 -0
- package/lib/Utils/message-composer.js +273 -0
- package/lib/Utils/message-retry-manager.js +267 -0
- package/lib/Utils/messages-media.js +791 -0
- package/lib/Utils/messages.js +1260 -0
- package/lib/Utils/meta-ai-msmsg.js +271 -0
- package/lib/Utils/native-bridge.js +77 -0
- package/lib/Utils/noise-handler.js +201 -0
- package/lib/Utils/offline-node-processor.js +40 -0
- package/lib/Utils/optimizer.js +90 -0
- package/lib/Utils/pre-key-manager.js +106 -0
- package/lib/Utils/process-message.js +630 -0
- package/lib/Utils/reporting-utils.js +258 -0
- package/lib/Utils/session-pool.js +73 -0
- package/lib/Utils/signal.js +207 -0
- package/lib/Utils/stanza-ack.js +38 -0
- package/lib/Utils/sticker.js +139 -0
- package/lib/Utils/sync-action-utils.js +49 -0
- package/lib/Utils/tc-token-utils.js +163 -0
- package/lib/Utils/use-multi-file-auth-state.js +121 -0
- package/lib/Utils/use-sqlite-auth-state.js +168 -0
- package/lib/Utils/validate-connection.js +203 -0
- package/lib/Utils/view-once-cache.js +79 -0
- package/lib/Utils/voip-rekey.js +25 -0
- package/lib/Utils/warmup.js +117 -0
- package/lib/WABinary/constants.js +1301 -0
- package/lib/WABinary/decode.js +262 -0
- package/lib/WABinary/encode.js +220 -0
- package/lib/WABinary/generic-utils.js +204 -0
- package/lib/WABinary/index.js +6 -0
- package/lib/WABinary/jid-utils.js +98 -0
- package/lib/WABinary/types.js +2 -0
- package/lib/WAM/BinaryInfo.js +10 -0
- package/lib/WAM/constants.js +22853 -0
- package/lib/WAM/encode.js +150 -0
- package/lib/WAM/index.js +4 -0
- package/lib/WAUSync/Protocols/USyncBusinessProtocol.js +41 -0
- package/lib/WAUSync/Protocols/USyncContactProtocol.js +52 -0
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +54 -0
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +27 -0
- package/lib/WAUSync/Protocols/USyncFeatureProtocol.js +52 -0
- package/lib/WAUSync/Protocols/USyncPictureProtocol.js +31 -0
- package/lib/WAUSync/Protocols/USyncSidelistProtocol.js +26 -0
- package/lib/WAUSync/Protocols/USyncStatusProtocol.js +38 -0
- package/lib/WAUSync/Protocols/USyncTextStatusProtocol.js +35 -0
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +25 -0
- package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +51 -0
- package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +29 -0
- package/lib/WAUSync/Protocols/index.js +13 -0
- package/lib/WAUSync/USyncQuery.js +127 -0
- package/lib/WAUSync/USyncUser.js +31 -0
- package/lib/WAUSync/index.js +4 -0
- package/lib/antiban.js +4083 -0
- package/lib/index.js +24 -0
- 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
|
+
};
|