@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.js ADDED
@@ -0,0 +1,526 @@
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
+ if(node?.expression?.arguments?.length) {
36
+ for (const arg of node?.expression?.arguments) {
37
+ if(arg?.body?.body?.length){
38
+ for(const exp of arg?.body.body) {
39
+ expressions.push(...extractAllExpressions(exp));
40
+ }
41
+ }
42
+ }
43
+ }
44
+ if(node?.body?.body?.length) {
45
+ for (const exp of node?.body?.body) {
46
+ if(exp.expression){
47
+ expressions.push(...extractAllExpressions(exp.expression));
48
+ }
49
+ }
50
+ }
51
+
52
+ if (node.expression?.expressions?.length) {
53
+ for (const exp of node.expression?.expressions) {
54
+ expressions.push(...extractAllExpressions(exp));
55
+ }
56
+ }
57
+
58
+ return expressions;
59
+ };
60
+
61
+
62
+ async function findAppModules() {
63
+ const ua = {
64
+ headers: {
65
+ 'User-Agent':
66
+ 'Mozilla/5.0 (X11; Linux x86_64; rv:100.0) Gecko/20100101 Firefox/100.0',
67
+ 'Sec-Fetch-Dest': 'script',
68
+ 'Sec-Fetch-Mode': 'no-cors',
69
+ 'Sec-Fetch-Site': 'same-origin',
70
+ Referer: 'https://web.whatsapp.com/',
71
+ Accept: '*/*',
72
+ 'Accept-Language': 'Accept-Language: en-US,en;q=0.5',
73
+ },
74
+ };
75
+ const baseURL = 'https://web.whatsapp.com';
76
+ const serviceworker = await request.get(`${baseURL}/sw.js`, ua);
77
+
78
+ const versions = [
79
+ ...serviceworker.matchAll(/client_revision\\":([\d\.]+),/g),
80
+ ].map((r) => r[1]);
81
+ const version = versions[0];
82
+ console.log(`Current version: 2.3000.${version}`);
83
+
84
+ const waVersion = `2.3000.${version}`;
85
+ whatsAppVersion = waVersion;
86
+
87
+ let bootstrapQRURL = '';
88
+ const clearString = serviceworker.replaceAll('/*BTDS*/', '');
89
+ const URLScript = clearString.match(/(?<=importScripts\(["'])(.*?)(?=["']\);)/g);
90
+ bootstrapQRURL = new URL(URLScript[0].replaceAll("\\",'')).href;
91
+
92
+ console.info('Found source JS URL:', bootstrapQRURL);
93
+
94
+ const qrData = await request.get(bootstrapQRURL, ua);
95
+
96
+ // This one list of types is so long that it's split into two JavaScript declarations.
97
+ // The module finder below can't handle it, so just patch it manually here.
98
+ const patchedQrData = qrData.replace(
99
+ 't.ActionLinkSpec=void 0,t.TemplateButtonSpec',
100
+ 't.ActionLinkSpec=t.TemplateButtonSpec'
101
+ );
102
+ //const patchedQrData = qrData.replace("Spec=void 0,t.", "Spec=t.")
103
+
104
+ const qrModules = acorn.parse(patchedQrData).body;
105
+
106
+ const result = qrModules.filter((m) => {
107
+ const expressions = extractAllExpressions(m);
108
+ return expressions?.find(
109
+ (e) => {
110
+ return e?.left?.property?.name === 'internalSpec'
111
+ }
112
+ );
113
+ });
114
+ return result;
115
+ }
116
+
117
+ (async () => {
118
+ const unspecName = (name) =>
119
+ name.endsWith('Spec') ? name.slice(0, -4) : name;
120
+ const unnestName = (name) => name.split('$').slice(-1)[0];
121
+ const getNesting = (name) => name.split('$').slice(0, -1).join('$');
122
+ const makeRenameFunc = () => (name) => {
123
+ name = unspecName(name);
124
+ return name; // .replaceAll('$', '__')
125
+ // return renames[name] ?? unnestName(name)
126
+ };
127
+ // The constructor IDs that can be used for enum types
128
+
129
+ const modules = await findAppModules();
130
+
131
+ // find aliases of cross references between the wanted modules
132
+ const modulesInfo = {};
133
+ const moduleIndentationMap = {};
134
+ modules.forEach((module) => {
135
+ const moduleName = module.expression.arguments[0].value;
136
+ modulesInfo[moduleName] = { crossRefs: [] };
137
+ walk.simple(module, {
138
+ AssignmentExpression(node) {
139
+ if (
140
+ node &&
141
+ node?.right?.type == 'CallExpression' &&
142
+ node?.right?.arguments?.length == 1 &&
143
+ node?.right?.arguments[0].type !== 'ObjectExpression'
144
+ ) {
145
+ /*if(node.right.arguments[0].value == '$InternalEnum') {
146
+ console.log(node);
147
+ console.log(node.right.arguments[0]);
148
+ exit;
149
+ }*/
150
+ modulesInfo[moduleName].crossRefs.push({
151
+ alias: node.left.name,
152
+ module: node.right.arguments[0].value,
153
+ });
154
+ }
155
+ },
156
+ });
157
+ });
158
+
159
+ // find all identifiers and, for enums, their array of values
160
+ for (const mod of modules) {
161
+ const modInfo = modulesInfo[mod.expression.arguments[0].value];
162
+ const rename = makeRenameFunc(mod.expression.arguments[0].value);
163
+
164
+ const assignments = []
165
+ walk.simple(mod, {
166
+ AssignmentExpression(node) {
167
+ const left = node.left;
168
+ if(
169
+ left.property?.name &&
170
+ left.property?.name !== 'internalSpec' &&
171
+ left.property?.name !== 'internalDefaults'
172
+ ) {
173
+ assignments.push(left);
174
+ }
175
+ },
176
+ });
177
+
178
+
179
+ const makeBlankIdent = (a) => {
180
+ const key = rename(a?.property?.name);
181
+ const indentation = getNesting(key);
182
+ const value = { name: key };
183
+
184
+ moduleIndentationMap[key] = moduleIndentationMap[key] || {};
185
+ moduleIndentationMap[key].indentation = indentation;
186
+
187
+ if (indentation.length) {
188
+ moduleIndentationMap[indentation] =
189
+ moduleIndentationMap[indentation] || {};
190
+ moduleIndentationMap[indentation].members =
191
+ moduleIndentationMap[indentation].members || new Set();
192
+ moduleIndentationMap[indentation].members.add(key);
193
+ }
194
+
195
+ return [key, value];
196
+ };
197
+
198
+ modInfo.identifiers = Object.fromEntries(
199
+ assignments.map(makeBlankIdent).reverse()
200
+ );
201
+ const enumAliases = {};
202
+ // enums are defined directly, and both enums and messages get a one-letter alias
203
+ walk.ancestor(mod, {
204
+ Property(node, anc) {
205
+ const fatherNode = anc[anc.length - 3];
206
+ const fatherFather = anc[anc.length - 4];
207
+ if(
208
+ fatherNode?.type === 'AssignmentExpression' &&
209
+ fatherNode?.left?.property?.name == 'internalSpec'
210
+ && fatherNode?.right?.properties.length
211
+ ) {
212
+ const values = fatherNode?.right?.properties.map((p) => ({
213
+ name: p.key.name,
214
+ id: p.value.value,
215
+ }));
216
+ const nameAlias = fatherNode?.left?.name;
217
+ enumAliases[nameAlias] = values;
218
+ }
219
+ else if (node?.key && node?.key?.name && fatherNode.arguments?.length > 0) {
220
+ const values = fatherNode.arguments?.[0]?.properties.map((p) => ({
221
+ name: p.key.name,
222
+ id: p.value.value,
223
+ }));
224
+ const nameAlias = fatherFather?.left?.name || fatherFather.id.name;
225
+ enumAliases[nameAlias] = values;
226
+ }
227
+ },
228
+ });
229
+ walk.simple(mod, {
230
+ AssignmentExpression(node) {
231
+ if (
232
+ node.left.type === 'MemberExpression' &&
233
+ modInfo.identifiers?.[rename(node.left.property.name)]
234
+ ) {
235
+ const ident = modInfo.identifiers[rename(node.left.property.name)];
236
+ ident.alias = node.right.name;
237
+ ident.enumValues = enumAliases[ident.alias];
238
+ }
239
+ },
240
+ });
241
+ }
242
+
243
+ // find the contents for all protobuf messages
244
+ for (const mod of modules) {
245
+ const modInfo = modulesInfo[mod.expression.arguments[0].value];
246
+ const rename = makeRenameFunc(mod.expression.arguments[0].value);
247
+ const findByAliasInIdentifier = (obj, alias) => {
248
+ return Object.values(obj).find(item => item.alias === alias);
249
+ };
250
+
251
+ // message specifications are stored in a "internalSpec" attribute of the respective identifier alias
252
+ walk.simple(mod, {
253
+ AssignmentExpression(node) {
254
+ if (
255
+ node.left.type === 'MemberExpression' &&
256
+ node.left.property.name === 'internalSpec' &&
257
+ node.right.type === 'ObjectExpression'
258
+ ) {
259
+ const targetIdent = Object.values(modInfo.identifiers).find(
260
+ (v) => v.alias === node.left.object.name
261
+ );
262
+ if (!targetIdent) {
263
+ console.warn(
264
+ `found message specification for unknown identifier alias: ${node.left.object.name}`
265
+ );
266
+ return;
267
+ }
268
+
269
+ // partition spec properties by normal members and constraints (like "__oneofs__") which will be processed afterwards
270
+ const constraints = [];
271
+ let members = [];
272
+ for (const p of node.right.properties) {
273
+ p.key.name = p.key.type === 'Identifier' ? p.key.name : p.key.value;
274
+ const arr =
275
+ p.key.name.substr(0, 2) === '__' ? constraints : members;
276
+ arr.push(p);
277
+ }
278
+
279
+ members = members.map(({ key: { name }, value: { elements } }) => {
280
+ let type;
281
+ const flags = [];
282
+ const unwrapBinaryOr = (n) =>
283
+ n.type === 'BinaryExpression' && n.operator === '|'
284
+ ? [].concat(unwrapBinaryOr(n.left), unwrapBinaryOr(n.right))
285
+ : [n];
286
+
287
+ // find type and flags
288
+ unwrapBinaryOr(elements[1]).forEach((m) => {
289
+ if (
290
+ m.type === 'MemberExpression' &&
291
+ m.object.type === 'MemberExpression'
292
+ ) {
293
+ if (m.object.property.name === 'TYPES') {
294
+ type = m.property.name.toLowerCase();
295
+ if(type == 'map'){
296
+
297
+ let typeStr = 'map<';
298
+ if (elements[2]?.type === 'ArrayExpression') {
299
+ const subElements = elements[2].elements;
300
+ subElements.forEach((element, index) => {
301
+ if(element?.property?.name) {
302
+ typeStr += element?.property?.name?.toLowerCase();
303
+ } else {
304
+ const ref = findByAliasInIdentifier(modInfo.identifiers, element.name);
305
+ typeStr += ref.name;
306
+ }
307
+ if (index < subElements.length - 1) {
308
+ typeStr += ', ';
309
+ }
310
+ });
311
+ typeStr += '>';
312
+ type = typeStr;
313
+ }
314
+ }
315
+ } else if (m.object.property.name === 'FLAGS') {
316
+ flags.push(m.property.name.toLowerCase());
317
+ }
318
+ }
319
+ });
320
+
321
+ // determine cross reference name from alias if this member has type "message" or "enum"
322
+
323
+ if (type === 'message' || type === 'enum') {
324
+ const currLoc = ` from member '${name}' of message ${targetIdent.name}'`;
325
+ if (elements[2].type === 'Identifier') {
326
+ type = Object.values(modInfo.identifiers).find(
327
+ (v) => v.alias === elements[2].name
328
+ )?.name;
329
+ if (!type) {
330
+ console.warn(
331
+ `unable to find reference of alias '${elements[2].name}'` +
332
+ currLoc
333
+ );
334
+ }
335
+ } else if (elements[2].type === 'MemberExpression') {
336
+ let crossRef = modInfo.crossRefs.find(
337
+ (r) => r.alias === elements[2]?.object?.name || elements[2]?.object?.left?.name || elements[2]?.object?.callee?.name
338
+ );
339
+ if(elements[1]?.property?.name === 'ENUM' && elements[2]?.property?.name?.includes('Type')) {
340
+ type = rename(elements[2]?.property?.name);
341
+ }
342
+ else if(elements[2]?.property?.name.includes('Spec')) {
343
+ type = rename(elements[2].property.name);
344
+ } else if (
345
+ crossRef &&
346
+ crossRef.module !== '$InternalEnum' &&
347
+ modulesInfo[crossRef.module].identifiers[
348
+ rename(elements[2].property.name)
349
+ ]
350
+ ) {
351
+ type = rename(elements[2].property.name);
352
+ } else {
353
+ console.warn(
354
+ `unable to find reference of alias to other module '${elements[2].object.name}' or to message ${elements[2].property.name} of this module` +
355
+ currLoc
356
+ );
357
+ }
358
+ }
359
+ }
360
+
361
+ return { name, id: elements[0].value, type, flags };
362
+ });
363
+
364
+ // resolve constraints for members
365
+ constraints.forEach((c) => {
366
+ if (
367
+ c.key.name === '__oneofs__' &&
368
+ c.value.type === 'ObjectExpression'
369
+ ) {
370
+ const newOneOfs = c.value.properties.map((p) => ({
371
+ name: p.key.name,
372
+ type: '__oneof__',
373
+ members: p.value.elements.map((e) => {
374
+ const idx = members.findIndex((m) => m.name === e.value);
375
+ const member = members[idx];
376
+ members.splice(idx, 1);
377
+ return member;
378
+ }),
379
+ }));
380
+ members.push(...newOneOfs);
381
+ }
382
+ });
383
+
384
+ targetIdent.members = members;
385
+ }
386
+ },
387
+ });
388
+ }
389
+
390
+ const decodedProtoMap = {};
391
+ const spaceIndent = ' '.repeat(4);
392
+ for (const mod of modules) {
393
+ const modInfo = modulesInfo[mod.expression.arguments[0].value];
394
+ const identifiers = Object.values(modInfo?.identifiers);
395
+
396
+ // enum stringifying function
397
+ const stringifyEnum = (ident, overrideName = null) =>
398
+ [].concat(
399
+ [`enum ${overrideName || ident.displayName || ident.name} {`],
400
+ addPrefix(
401
+ ident.enumValues.map((v) => `${v.name} = ${v.id};`),
402
+ spaceIndent
403
+ ),
404
+ ['}']
405
+ );
406
+
407
+ // message specification member stringifying function
408
+ const stringifyMessageSpecMember = (
409
+ info,
410
+ completeFlags,
411
+ parentName = undefined
412
+ ) => {
413
+ if (info.type === '__oneof__') {
414
+ return [].concat(
415
+ [`oneof ${info.name} {`],
416
+ addPrefix(
417
+ [].concat(
418
+ ...info.members.map((m) => stringifyMessageSpecMember(m, false))
419
+ ),
420
+ spaceIndent
421
+ ),
422
+ ['}']
423
+ );
424
+ } else {
425
+ if (info.flags.includes('packed')) {
426
+ info.flags.splice(info.flags.indexOf('packed'));
427
+ info.packed = ' [packed=true]';
428
+ }
429
+ if (completeFlags && info.flags.length === 0 && !info.type.includes('map')) {
430
+ info.flags.push('optional');
431
+ }
432
+
433
+ const ret = [];
434
+ const indentation = moduleIndentationMap[info.type]?.indentation;
435
+ let typeName = unnestName(info.type);
436
+ if (indentation !== parentName && indentation) {
437
+ typeName = `${indentation.replaceAll('$', '.')}.${typeName}`;
438
+ }
439
+
440
+ // if(info.enumValues) {
441
+ // // typeName = unnestName(info.type)
442
+ // ret = stringifyEnum(info, typeName)
443
+ // }
444
+
445
+ ret.push(
446
+ `${
447
+ info.flags.join(' ') + (info.flags.length === 0 ? '' : ' ')
448
+ }${typeName} ${info.name} = ${info.id}${info.packed || ''};`
449
+ );
450
+ return ret;
451
+ }
452
+ };
453
+
454
+ // message specification stringifying function
455
+ const stringifyMessageSpec = (ident) => {
456
+ const members = moduleIndentationMap[ident.name]?.members;
457
+ const result = [];
458
+ result.push(
459
+ `message ${ident.displayName || ident.name} {`,
460
+ ...addPrefix(
461
+ [].concat(
462
+ ...ident.members.map((m) =>
463
+ stringifyMessageSpecMember(m, true, ident.name)
464
+ )
465
+ ),
466
+ spaceIndent
467
+ )
468
+ );
469
+
470
+ if (members?.size) {
471
+ const sortedMembers = Array.from(members).sort();
472
+ for (const memberName of sortedMembers) {
473
+ let entity = modInfo.identifiers[memberName];
474
+ if (entity) {
475
+ const displayName = entity.name.slice(ident.name.length + 1);
476
+ entity = { ...entity, displayName };
477
+ result.push(...addPrefix(getEntity(entity), spaceIndent));
478
+ } else {
479
+ console.log('missing nested entity ', memberName);
480
+ }
481
+ }
482
+ }
483
+
484
+ result.push('}');
485
+ result.push('');
486
+
487
+ return result;
488
+ };
489
+
490
+ const getEntity = (v) => {
491
+ let result;
492
+ if (v.members) {
493
+ result = stringifyMessageSpec(v);
494
+ } else if (v.enumValues?.length) {
495
+ result = stringifyEnum(v);
496
+ } else {
497
+ result = ['// Unknown entity ' + v.name];
498
+ }
499
+
500
+ return result;
501
+ };
502
+
503
+ const stringifyEntity = (v) => {
504
+ return {
505
+ content: getEntity(v).join('\n'),
506
+ name: v.name,
507
+ };
508
+ };
509
+
510
+ for (const value of identifiers) {
511
+ const { name, content } = stringifyEntity(value);
512
+ if (!moduleIndentationMap[name]?.indentation?.length) {
513
+ decodedProtoMap[name] = content;
514
+ }
515
+ }
516
+ }
517
+
518
+ const decodedProto = Object.keys(decodedProtoMap).sort();
519
+ const sortedStr = decodedProto.map((d) => decodedProtoMap[d]).join('\n');
520
+
521
+ const decodedProtoStr = `syntax = "proto3";\npackage waproto;\n\n/// WhatsApp Version: ${whatsAppVersion}\n\n${sortedStr}`;
522
+ const destinationPath = 'WAProto.proto';
523
+ await fs.writeFile(destinationPath, decodedProtoStr);
524
+
525
+ console.log(`Extracted protobuf schema to "${destinationPath}"`);
526
+ })();
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@wppconnect/wa-proto",
3
+ "version": "0.0.5",
4
+ "description": "Protobuf files from WhatsApp WEB",
5
+ "license": "Apache-2.0",
6
+ "author": {
7
+ "name": "Cleiton Costa",
8
+ "url": "https://github.com/icleitoncosta"
9
+ },
10
+ "main": "dist/index.js",
11
+ "types": "dist/index.d.ts",
12
+ "scripts": {
13
+ "build": "npm run fetch && npm run compile",
14
+ "compile": "npm run compile:js && npm run compile:ts",
15
+ "compile:js": "pbjs -t static-module --sparse -w commonjs -o ./dist/index.js ./WAProto.proto",
16
+ "compile:ts": "pbts -o ./dist/index.d.ts ./dist/index.js",
17
+ "fetch": "node index.js",
18
+ "changelog:last": "conventional-changelog -p angular -r 2",
19
+ "changelog:preview": "conventional-changelog -p angular -u",
20
+ "changelog:update": "conventional-changelog -p angular -i CHANGELOG.md -s",
21
+ "release": "release-it"
22
+ },
23
+ "dependencies": {
24
+ "long": "^5.2.3",
25
+ "protobufjs": "^7.4.0"
26
+ },
27
+ "devDependencies": {
28
+ "acorn": "^8.11.3",
29
+ "acorn-walk": "^8.3.2",
30
+ "conventional-changelog-cli": "^4.1.0",
31
+ "protobufjs-cli": "^1.1.3",
32
+ "release-it": "^17.1.1",
33
+ "request": "^2.88.2",
34
+ "request-promise-core": "^1.1.4",
35
+ "request-promise-native": "^1.0.9"
36
+ },
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "git+https://github.com/wppconnect-team/wa-proto.git"
40
+ },
41
+ "keywords": [
42
+ "wa-proto",
43
+ "whatsapp",
44
+ "proto",
45
+ "whatsapp",
46
+ "protobuf",
47
+ "files",
48
+ "whatsapp",
49
+ "2.3000",
50
+ "3000x"
51
+ ],
52
+ "bugs": {
53
+ "url": "https://github.com/wppconnect-team/wa-proto/issues"
54
+ },
55
+ "homepage": "https://github.com/wppconnect-team/wa-proto#readme"
56
+ }