@wppconnect/wa-proto 0.0.5

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/index-2x.js ADDED
@@ -0,0 +1,488 @@
1
+ /*!
2
+ * Copyright 2024 WPPConnect Team
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ /**
18
+ * Based on https://github.com/WhiskeySockets/Baileys/tree/master/WAProto
19
+ */
20
+ const request = require('request-promise-native');
21
+ const acorn = require('acorn');
22
+ const walk = require('acorn-walk');
23
+ const fs = require('fs/promises');
24
+
25
+ let whatsAppVersion = 'latest';
26
+
27
+ const addPrefix = (lines, prefix) => lines.map((line) => prefix + line);
28
+
29
+ const extractAllExpressions = (node) => {
30
+ const expressions = [node];
31
+ const exp = node.expression;
32
+ if (exp) {
33
+ expressions.push(exp);
34
+ }
35
+
36
+ if (node.expression?.expressions?.length) {
37
+ for (const exp of node.expression?.expressions) {
38
+ expressions.push(...extractAllExpressions(exp));
39
+ }
40
+ }
41
+
42
+ return expressions;
43
+ };
44
+
45
+ async function findAppModules() {
46
+ const ua = {
47
+ headers: {
48
+ 'User-Agent':
49
+ 'Mozilla/5.0 (X11; Linux x86_64; rv:100.0) Gecko/20100101 Firefox/100.0',
50
+ 'Sec-Fetch-Dest': 'script',
51
+ 'Sec-Fetch-Mode': 'no-cors',
52
+ 'Sec-Fetch-Site': 'same-origin',
53
+ Referer: 'https://web.whatsapp.com/',
54
+ Accept: '*/*',
55
+ 'Accept-Language': 'Accept-Language: en-US,en;q=0.5',
56
+ },
57
+ };
58
+ const baseURL = 'https://web.whatsapp.com';
59
+ const serviceworker = await request.get(`${baseURL}/serviceworker.js`, ua);
60
+
61
+ const versions = [
62
+ ...serviceworker.matchAll(/assets-manifest-([\d\.]+).json/g),
63
+ ].map((r) => r[1]);
64
+ const version = versions[0];
65
+
66
+ let bootstrapQRURL = '';
67
+ if (version) {
68
+ const asset = await request.get(
69
+ `${baseURL}/assets-manifest-${version}.json`,
70
+ ua
71
+ );
72
+ const hashFiles = JSON.parse(asset);
73
+ const files = Object.keys(hashFiles);
74
+ const app = files.find((f) => /^app\./.test(f));
75
+ bootstrapQRURL = `${baseURL}/${app}`;
76
+ } else {
77
+ const index = await request.get(baseURL, ua);
78
+ const bootstrapQRID = index.match(/src="\/app.([0-9a-z]{10,}).js"/)[1];
79
+ bootstrapQRURL = baseURL + '/app.' + bootstrapQRID + '.js';
80
+ }
81
+
82
+ console.error('Found source JS URL:', bootstrapQRURL);
83
+
84
+ const qrData = await request.get(bootstrapQRURL, ua);
85
+ const waVersion = qrData.match(
86
+ /(?:appVersion:|VERSION_STR=)"(\d\.\d+\.\d+)"/
87
+ )[1];
88
+ console.log('Current version:', waVersion);
89
+ whatsAppVersion = waVersion;
90
+ // This one list of types is so long that it's split into two JavaScript declarations.
91
+ // The module finder below can't handle it, so just patch it manually here.
92
+ const patchedQrData = qrData.replace(
93
+ 't.ActionLinkSpec=void 0,t.TemplateButtonSpec',
94
+ 't.ActionLinkSpec=t.TemplateButtonSpec'
95
+ );
96
+ //const patchedQrData = qrData.replace("Spec=void 0,t.", "Spec=t.")
97
+ const qrModules =
98
+ acorn.parse(patchedQrData).body[0].expression.arguments[0].elements[1]
99
+ .properties;
100
+
101
+ const result = qrModules.filter((m) => {
102
+ const hasProto = !!m.value.body.body.find((b) => {
103
+ const expressions = extractAllExpressions(b);
104
+ return expressions?.find(
105
+ (e) => e?.left?.property?.name === 'internalSpec'
106
+ );
107
+ });
108
+ if (hasProto) {
109
+ return true;
110
+ }
111
+ });
112
+
113
+ return result;
114
+ }
115
+
116
+ (async () => {
117
+ const unspecName = (name) =>
118
+ name.endsWith('Spec') ? name.slice(0, -4) : name;
119
+ const unnestName = (name) => name.split('$').slice(-1)[0];
120
+ const getNesting = (name) => name.split('$').slice(0, -1).join('$');
121
+ const makeRenameFunc = () => (name) => {
122
+ name = unspecName(name);
123
+ return name; // .replaceAll('$', '__')
124
+ // return renames[name] ?? unnestName(name)
125
+ };
126
+ // The constructor IDs that can be used for enum types
127
+ // const enumConstructorIDs = [76672, 54302]
128
+
129
+ const modules = await findAppModules();
130
+
131
+ // Sort modules so that whatsapp module id changes don't change the order in the output protobuf schema
132
+ // const modules = []
133
+ // for (const mod of wantedModules) {
134
+ // modules.push(unsortedModules.find(node => node.key.value === mod))
135
+ // }
136
+
137
+ // find aliases of cross references between the wanted modules
138
+ const modulesInfo = {};
139
+ const moduleIndentationMap = {};
140
+ modules.forEach(({ key, value }) => {
141
+ const requiringParam = value.params[2].name;
142
+ modulesInfo[key.value] = { crossRefs: [] };
143
+ walk.simple(value, {
144
+ VariableDeclarator(node) {
145
+ if (
146
+ node.init &&
147
+ node.init.type === 'CallExpression' &&
148
+ node.init.callee.name === requiringParam &&
149
+ node.init.arguments.length === 1
150
+ ) {
151
+ modulesInfo[key.value].crossRefs.push({
152
+ alias: node.id.name,
153
+ module: node.init.arguments[0].value,
154
+ });
155
+ }
156
+ },
157
+ });
158
+ });
159
+
160
+ // find all identifiers and, for enums, their array of values
161
+ for (const mod of modules) {
162
+ const modInfo = modulesInfo[mod.key.value];
163
+ const rename = makeRenameFunc(mod.key.value);
164
+
165
+ // all identifiers will be initialized to "void 0" (i.e. "undefined") at the start, so capture them here
166
+ walk.ancestor(mod, {
167
+ UnaryExpression(node, anc) {
168
+ if (!modInfo.identifiers && node.operator === 'void') {
169
+ const assignments = [];
170
+ let i = 1;
171
+ anc.reverse();
172
+ while (anc[i].type === 'AssignmentExpression') {
173
+ assignments.push(anc[i++].left);
174
+ }
175
+
176
+ const makeBlankIdent = (a) => {
177
+ const key = rename(a.property.name);
178
+ const indentation = getNesting(key);
179
+ const value = { name: key };
180
+
181
+ moduleIndentationMap[key] = moduleIndentationMap[key] || {};
182
+ moduleIndentationMap[key].indentation = indentation;
183
+
184
+ if (indentation.length) {
185
+ moduleIndentationMap[indentation] =
186
+ moduleIndentationMap[indentation] || {};
187
+ moduleIndentationMap[indentation].members =
188
+ moduleIndentationMap[indentation].members || new Set();
189
+ moduleIndentationMap[indentation].members.add(key);
190
+ }
191
+
192
+ return [key, value];
193
+ };
194
+
195
+ modInfo.identifiers = Object.fromEntries(
196
+ assignments.map(makeBlankIdent).reverse()
197
+ );
198
+ }
199
+ },
200
+ });
201
+ const enumAliases = {};
202
+ // enums are defined directly, and both enums and messages get a one-letter alias
203
+ walk.simple(mod, {
204
+ VariableDeclarator(node) {
205
+ if (
206
+ node.init?.type === 'CallExpression' &&
207
+ // && enumConstructorIDs.includes(node.init.callee?.arguments?.[0]?.value)
208
+ !!node.init.arguments.length &&
209
+ node.init.arguments[0].type === 'ObjectExpression' &&
210
+ node.init.arguments[0].properties.length
211
+ ) {
212
+ const values = node.init.arguments[0].properties.map((p) => ({
213
+ name: p.key.name,
214
+ id: p.value.value,
215
+ }));
216
+ enumAliases[node.id.name] = values;
217
+ }
218
+ },
219
+ AssignmentExpression(node) {
220
+ if (
221
+ node.left.type === 'MemberExpression' &&
222
+ modInfo.identifiers[rename(node.left.property.name)]
223
+ ) {
224
+ const ident = modInfo.identifiers[rename(node.left.property.name)];
225
+ ident.alias = node.right.name;
226
+ // enumAliases[ident.alias] = enumAliases[ident.alias] || []
227
+ ident.enumValues = enumAliases[ident.alias];
228
+ console.log(ident.enumValues);
229
+ }
230
+ },
231
+ });
232
+ }
233
+
234
+ // find the contents for all protobuf messages
235
+ for (const mod of modules) {
236
+ const modInfo = modulesInfo[mod.key.value];
237
+ const rename = makeRenameFunc(mod.key.value);
238
+
239
+ // message specifications are stored in a "internalSpec" attribute of the respective identifier alias
240
+ walk.simple(mod, {
241
+ AssignmentExpression(node) {
242
+ if (
243
+ node.left.type === 'MemberExpression' &&
244
+ node.left.property.name === 'internalSpec' &&
245
+ node.right.type === 'ObjectExpression'
246
+ ) {
247
+ const targetIdent = Object.values(modInfo.identifiers).find(
248
+ (v) => v.alias === node.left.object.name
249
+ );
250
+ if (!targetIdent) {
251
+ console.warn(
252
+ `found message specification for unknown identifier alias: ${node.left.object.name}`
253
+ );
254
+ return;
255
+ }
256
+
257
+ // partition spec properties by normal members and constraints (like "__oneofs__") which will be processed afterwards
258
+ const constraints = [];
259
+ let members = [];
260
+ for (const p of node.right.properties) {
261
+ p.key.name = p.key.type === 'Identifier' ? p.key.name : p.key.value;
262
+ const arr =
263
+ p.key.name.substr(0, 2) === '__' ? constraints : members;
264
+ arr.push(p);
265
+ }
266
+
267
+ members = members.map(({ key: { name }, value: { elements } }) => {
268
+ let type;
269
+ const flags = [];
270
+ const unwrapBinaryOr = (n) =>
271
+ n.type === 'BinaryExpression' && n.operator === '|'
272
+ ? [].concat(unwrapBinaryOr(n.left), unwrapBinaryOr(n.right))
273
+ : [n];
274
+
275
+ // find type and flags
276
+ unwrapBinaryOr(elements[1]).forEach((m) => {
277
+ if (
278
+ m.type === 'MemberExpression' &&
279
+ m.object.type === 'MemberExpression'
280
+ ) {
281
+ if (m.object.property.name === 'TYPES') {
282
+ type = m.property.name.toLowerCase();
283
+ } else if (m.object.property.name === 'FLAGS') {
284
+ flags.push(m.property.name.toLowerCase());
285
+ }
286
+ }
287
+ });
288
+
289
+ // determine cross reference name from alias if this member has type "message" or "enum"
290
+ if (type === 'message' || type === 'enum') {
291
+ const currLoc = ` from member '${name}' of message '${targetIdent.name}'`;
292
+ if (elements[2].type === 'Identifier') {
293
+ type = Object.values(modInfo.identifiers).find(
294
+ (v) => v.alias === elements[2].name
295
+ )?.name;
296
+ if (!type) {
297
+ console.warn(
298
+ `unable to find reference of alias '${elements[2].name}'` +
299
+ currLoc
300
+ );
301
+ }
302
+ } else if (elements[2].type === 'MemberExpression') {
303
+ const crossRef = modInfo.crossRefs.find(
304
+ (r) => r.alias === elements[2].object.name
305
+ );
306
+ if (
307
+ crossRef &&
308
+ modulesInfo[crossRef.module].identifiers[
309
+ rename(elements[2].property.name)
310
+ ]
311
+ ) {
312
+ type = rename(elements[2].property.name);
313
+ } else {
314
+ console.warn(
315
+ `unable to find reference of alias to other module '${elements[2].object.name}' or to message ${elements[2].property.name} of this module` +
316
+ currLoc
317
+ );
318
+ }
319
+ }
320
+ }
321
+
322
+ return { name, id: elements[0].value, type, flags };
323
+ });
324
+
325
+ // resolve constraints for members
326
+ constraints.forEach((c) => {
327
+ if (
328
+ c.key.name === '__oneofs__' &&
329
+ c.value.type === 'ObjectExpression'
330
+ ) {
331
+ const newOneOfs = c.value.properties.map((p) => ({
332
+ name: p.key.name,
333
+ type: '__oneof__',
334
+ members: p.value.elements.map((e) => {
335
+ const idx = members.findIndex((m) => m.name === e.value);
336
+ const member = members[idx];
337
+ members.splice(idx, 1);
338
+ return member;
339
+ }),
340
+ }));
341
+ members.push(...newOneOfs);
342
+ }
343
+ });
344
+
345
+ targetIdent.members = members;
346
+ }
347
+ },
348
+ });
349
+ }
350
+
351
+ const decodedProtoMap = {};
352
+ const spaceIndent = ' '.repeat(4);
353
+ for (const mod of modules) {
354
+ const modInfo = modulesInfo[mod.key.value];
355
+ const identifiers = Object.values(modInfo.identifiers);
356
+
357
+ // enum stringifying function
358
+ const stringifyEnum = (ident, overrideName = null) =>
359
+ [].concat(
360
+ [`enum ${overrideName || ident.displayName || ident.name} {`],
361
+ addPrefix(
362
+ ident.enumValues.map((v) => `${v.name} = ${v.id};`),
363
+ spaceIndent
364
+ ),
365
+ ['}']
366
+ );
367
+
368
+ // message specification member stringifying function
369
+ const stringifyMessageSpecMember = (
370
+ info,
371
+ completeFlags,
372
+ parentName = undefined
373
+ ) => {
374
+ if (info.type === '__oneof__') {
375
+ return [].concat(
376
+ [`oneof ${info.name} {`],
377
+ addPrefix(
378
+ [].concat(
379
+ ...info.members.map((m) => stringifyMessageSpecMember(m, false))
380
+ ),
381
+ spaceIndent
382
+ ),
383
+ ['}']
384
+ );
385
+ } else {
386
+ if (info.flags.includes('packed')) {
387
+ info.flags.splice(info.flags.indexOf('packed'));
388
+ info.packed = ' [packed=true]';
389
+ }
390
+
391
+ if (completeFlags && info.flags.length === 0) {
392
+ info.flags.push('optional');
393
+ }
394
+
395
+ const ret = [];
396
+ const indentation = moduleIndentationMap[info.type]?.indentation;
397
+ let typeName = unnestName(info.type);
398
+ if (indentation !== parentName && indentation) {
399
+ typeName = `${indentation.replaceAll('$', '.')}.${typeName}`;
400
+ }
401
+
402
+ // if(info.enumValues) {
403
+ // // typeName = unnestName(info.type)
404
+ // ret = stringifyEnum(info, typeName)
405
+ // }
406
+
407
+ ret.push(
408
+ `${
409
+ info.flags.join(' ') + (info.flags.length === 0 ? '' : ' ')
410
+ }${typeName} ${info.name} = ${info.id}${info.packed || ''};`
411
+ );
412
+ return ret;
413
+ }
414
+ };
415
+
416
+ // message specification stringifying function
417
+ const stringifyMessageSpec = (ident) => {
418
+ const members = moduleIndentationMap[ident.name]?.members;
419
+ const result = [];
420
+ result.push(
421
+ `message ${ident.displayName || ident.name} {`,
422
+ ...addPrefix(
423
+ [].concat(
424
+ ...ident.members.map((m) =>
425
+ stringifyMessageSpecMember(m, true, ident.name)
426
+ )
427
+ ),
428
+ spaceIndent
429
+ )
430
+ );
431
+
432
+ if (members?.size) {
433
+ const sortedMembers = Array.from(members).sort();
434
+ for (const memberName of sortedMembers) {
435
+ let entity = modInfo.identifiers[memberName];
436
+ if (entity) {
437
+ const displayName = entity.name.slice(ident.name.length + 1);
438
+ entity = { ...entity, displayName };
439
+ result.push(...addPrefix(getEntity(entity), spaceIndent));
440
+ } else {
441
+ console.log('missing nested entity ', memberName);
442
+ }
443
+ }
444
+ }
445
+
446
+ result.push('}');
447
+ result.push('');
448
+
449
+ return result;
450
+ };
451
+
452
+ const getEntity = (v) => {
453
+ let result;
454
+ if (v.members) {
455
+ result = stringifyMessageSpec(v);
456
+ } else if (v.enumValues?.length) {
457
+ result = stringifyEnum(v);
458
+ } else {
459
+ result = ['// Unknown entity ' + v.name];
460
+ }
461
+
462
+ return result;
463
+ };
464
+
465
+ const stringifyEntity = (v) => {
466
+ return {
467
+ content: getEntity(v).join('\n'),
468
+ name: v.name,
469
+ };
470
+ };
471
+
472
+ for (const value of identifiers) {
473
+ const { name, content } = stringifyEntity(value);
474
+ if (!moduleIndentationMap[name]?.indentation?.length) {
475
+ decodedProtoMap[name] = content;
476
+ }
477
+ }
478
+ }
479
+
480
+ const decodedProto = Object.keys(decodedProtoMap).sort();
481
+ const sortedStr = decodedProto.map((d) => decodedProtoMap[d]).join('\n');
482
+
483
+ const decodedProtoStr = `syntax = "proto2";\npackage waproto;\n\n/// WhatsApp Version: ${whatsAppVersion}\n\n${sortedStr}`;
484
+ const destinationPath = 'WAProto.proto';
485
+ await fs.writeFile(destinationPath, decodedProtoStr);
486
+
487
+ console.log(`Extracted protobuf schema to "${destinationPath}"`);
488
+ })();