whalibmob 5.5.90 → 5.6.2
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/README.md +463 -4
- package/cli.js +151 -5
- package/index.js +28 -0
- package/lib/Client.js +607 -62
- package/lib/CompanionPairing.js +247 -0
- package/lib/HistorySyncHandler.js +6 -1
- package/lib/MediaService.js +58 -12
- package/lib/PairingCode.js +198 -0
- package/lib/WebSocketStream.js +98 -0
- package/lib/WebStore.js +117 -0
- package/lib/constants.js +25 -0
- package/lib/messages/MessageSender.js +79 -19
- package/lib/messages/TcTokenStore.js +90 -3
- package/lib/noise.js +72 -7
- package/lib/signal/SignalProtocol.js +14 -5
- package/lib/webproto.js +273 -0
- package/package.json +3 -2
package/lib/webproto.js
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// ClientPayload encoders for the WhatsApp Web (companion) handshake.
|
|
4
|
+
//
|
|
5
|
+
// The mobile handshake in proto.js builds a payload around `username` and a
|
|
6
|
+
// phone-shaped UserAgent. A companion sends the same message type with a
|
|
7
|
+
// different set of fields: on its very first connection it carries
|
|
8
|
+
// devicePairingData — the registration bundle the account's primary device
|
|
9
|
+
// signs during pairing — and on every connection after that it carries the
|
|
10
|
+
// device index the server issued and asks to be caught up.
|
|
11
|
+
//
|
|
12
|
+
// Written by hand against the wire format for the same reason proto.js is:
|
|
13
|
+
// keeping the handshake independent of a protobuf runtime.
|
|
14
|
+
|
|
15
|
+
const crypto = require('crypto');
|
|
16
|
+
|
|
17
|
+
// ─── primitives ──────────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
function encodeVarint(n) {
|
|
20
|
+
const out = [];
|
|
21
|
+
let v = n >>> 0;
|
|
22
|
+
do {
|
|
23
|
+
let b = v & 0x7f;
|
|
24
|
+
v >>>= 7;
|
|
25
|
+
if (v) b |= 0x80;
|
|
26
|
+
out.push(b);
|
|
27
|
+
} while (v);
|
|
28
|
+
return Buffer.from(out);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// username is a uint64 — the 32-bit helper above truncates long international
|
|
32
|
+
// numbers, so it gets its own encoder.
|
|
33
|
+
function encodeVarint64(value) {
|
|
34
|
+
const out = [];
|
|
35
|
+
let v = BigInt(value);
|
|
36
|
+
do {
|
|
37
|
+
let b = Number(v & 0x7fn);
|
|
38
|
+
v >>= 7n;
|
|
39
|
+
if (v > 0n) b |= 0x80;
|
|
40
|
+
out.push(b);
|
|
41
|
+
} while (v > 0n);
|
|
42
|
+
return Buffer.from(out);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function field(num, wireType, valueBytes) {
|
|
46
|
+
return Buffer.concat([encodeVarint((num << 3) | wireType), valueBytes]);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function bytesField(num, buf) { return field(num, 2, Buffer.concat([encodeVarint(buf.length), buf])); }
|
|
50
|
+
function stringField(num, str) { return bytesField(num, Buffer.from(String(str), 'utf8')); }
|
|
51
|
+
function varintField(num, n) { return field(num, 0, encodeVarint(n)); }
|
|
52
|
+
function uint64Field(num, n) { return field(num, 0, encodeVarint64(n)); }
|
|
53
|
+
function boolField(num, v) { return varintField(num, v ? 1 : 0); }
|
|
54
|
+
|
|
55
|
+
// WhatsApp encodes registration ids and pre-key ids inside the pairing bundle
|
|
56
|
+
// as fixed-width big-endian byte strings, not as varints.
|
|
57
|
+
function encodeBigEndian(value, length) {
|
|
58
|
+
length = length || 4;
|
|
59
|
+
const buf = Buffer.alloc(length);
|
|
60
|
+
for (let i = length - 1; i >= 0; i--) {
|
|
61
|
+
buf[i] = value & 0xff;
|
|
62
|
+
value = Math.floor(value / 256);
|
|
63
|
+
}
|
|
64
|
+
return buf;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function stripKeyPrefix(pub) {
|
|
68
|
+
if (pub && pub.length === 33 && pub[0] === 0x05) return pub.slice(1);
|
|
69
|
+
return pub;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ─── DeviceProps ─────────────────────────────────────────────────────────────
|
|
73
|
+
//
|
|
74
|
+
// How the companion describes itself to the primary. `os` and the platform type
|
|
75
|
+
// are what the account owner reads in the linked-devices list on their phone;
|
|
76
|
+
// historySyncConfig decides how much of the past the primary ships over once
|
|
77
|
+
// the link is established.
|
|
78
|
+
|
|
79
|
+
const PLATFORM_TYPE = {
|
|
80
|
+
UNKNOWN: 0, CHROME: 1, FIREFOX: 2, IE: 3, OPERA: 4, SAFARI: 5, EDGE: 6,
|
|
81
|
+
DESKTOP: 7, IPAD: 8, ANDROID_TABLET: 9, OHANA: 10, ALOHA: 11, CATALINA: 12,
|
|
82
|
+
TCL_TV: 13, IOS_PHONE: 14, IOS_CATALYST: 15, ANDROID_PHONE: 16,
|
|
83
|
+
ANDROID_AMBIGUOUS: 17, WEAR_OS: 18, AR_WRIST: 19, AR_DEVICE: 20, UWP: 21,
|
|
84
|
+
VR: 22, CLOUD_API: 23, SMARTGLASSES: 24
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
function platformTypeFor(browserName) {
|
|
88
|
+
const key = String(browserName || '').toUpperCase();
|
|
89
|
+
if (key === 'ANDROID') return PLATFORM_TYPE.ANDROID_PHONE;
|
|
90
|
+
return PLATFORM_TYPE[key] !== undefined ? PLATFORM_TYPE[key] : PLATFORM_TYPE.CHROME;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// DeviceProps.HistorySyncConfig
|
|
94
|
+
function encodeHistorySyncConfig(opts) {
|
|
95
|
+
opts = opts || {};
|
|
96
|
+
const parts = [
|
|
97
|
+
varintField(3, opts.storageQuotaMb != null ? opts.storageQuotaMb : 10240),
|
|
98
|
+
boolField(4, opts.inlineInitialPayloadInE2EeMsg !== false),
|
|
99
|
+
boolField(6, !!opts.supportCallLogHistory),
|
|
100
|
+
boolField(7, opts.supportBotUserAgentChatHistory !== false),
|
|
101
|
+
boolField(8, opts.supportCagReactionsAndPolls !== false),
|
|
102
|
+
boolField(9, opts.supportBizHostedMsg !== false),
|
|
103
|
+
boolField(10, opts.supportRecentSyncChunkMessageCountTuning !== false),
|
|
104
|
+
boolField(11, opts.supportHostedGroupMsg !== false),
|
|
105
|
+
boolField(12, opts.supportFbidBotChatHistory !== false),
|
|
106
|
+
boolField(14, opts.supportMessageAssociation !== false),
|
|
107
|
+
boolField(15, !!opts.supportGroupHistory)
|
|
108
|
+
];
|
|
109
|
+
if (opts.fullSyncDaysLimit) parts.unshift(varintField(1, opts.fullSyncDaysLimit));
|
|
110
|
+
if (opts.fullSyncSizeMbLimit) parts.unshift(varintField(2, opts.fullSyncSizeMbLimit));
|
|
111
|
+
return Buffer.concat(parts);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// DeviceProps.AppVersion — the companion's own version, not the account's.
|
|
115
|
+
function encodeDevicePropsVersion(primary, secondary, tertiary) {
|
|
116
|
+
return Buffer.concat([
|
|
117
|
+
varintField(1, primary),
|
|
118
|
+
varintField(2, secondary),
|
|
119
|
+
varintField(3, tertiary)
|
|
120
|
+
]);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function encodeDeviceProps(opts) {
|
|
124
|
+
opts = opts || {};
|
|
125
|
+
return Buffer.concat([
|
|
126
|
+
stringField(1, opts.os || 'Ubuntu'),
|
|
127
|
+
bytesField(2, encodeDevicePropsVersion(10, 15, 7)),
|
|
128
|
+
varintField(3, platformTypeFor(opts.browserName || 'Chrome')),
|
|
129
|
+
boolField(4, !!opts.requireFullSync),
|
|
130
|
+
bytesField(5, encodeHistorySyncConfig(opts.historySync))
|
|
131
|
+
]);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ─── ClientPayload.UserAgent ─────────────────────────────────────────────────
|
|
135
|
+
//
|
|
136
|
+
// Platform 14 = WEB. Carries no phone identifiers at all — no real mcc/mnc, no
|
|
137
|
+
// device id, no phone id. That absence is part of what marks the connection as
|
|
138
|
+
// a companion rather than a primary.
|
|
139
|
+
|
|
140
|
+
const PLATFORM_WEB = 14;
|
|
141
|
+
const PLATFORM_ANDROID = 0;
|
|
142
|
+
|
|
143
|
+
function encodeAppVersion(v) {
|
|
144
|
+
return Buffer.concat([
|
|
145
|
+
varintField(1, v[0]),
|
|
146
|
+
varintField(2, v[1]),
|
|
147
|
+
varintField(3, v[2])
|
|
148
|
+
]);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function encodeWebUserAgent(opts) {
|
|
152
|
+
const version = opts.version || [2, 3000, 1];
|
|
153
|
+
const browser = opts.browser || ['Ubuntu', 'Chrome', '120.0.0.0'];
|
|
154
|
+
const isAndroid = String(browser[1] || '').toLowerCase().includes('android');
|
|
155
|
+
return Buffer.concat([
|
|
156
|
+
varintField(1, isAndroid ? PLATFORM_ANDROID : PLATFORM_WEB),
|
|
157
|
+
bytesField(2, encodeAppVersion(version)),
|
|
158
|
+
stringField(3, '000'), // mcc
|
|
159
|
+
stringField(4, '000'), // mnc
|
|
160
|
+
stringField(5, '0.1'), // osVersion
|
|
161
|
+
stringField(6, 'Desktop'), // manufacturer
|
|
162
|
+
stringField(7, 'Desktop'), // device
|
|
163
|
+
stringField(8, '0.1'), // osBuildNumber
|
|
164
|
+
varintField(10, 0), // releaseChannel = RELEASE
|
|
165
|
+
stringField(11, 'en'), // localeLanguageIso6391
|
|
166
|
+
stringField(12, String(opts.countryCode || 'US')) // localeCountryIso31661Alpha2
|
|
167
|
+
]);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ClientPayload.WebInfo — webSubPlatform (field 4), WEB_BROWSER = 0.
|
|
171
|
+
function encodeWebInfo() {
|
|
172
|
+
return varintField(4, 0);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ─── ClientPayload.DevicePairingRegistrationData ─────────────────────────────
|
|
176
|
+
//
|
|
177
|
+
// The bundle the primary device signs. eIdent is our identity public key, and
|
|
178
|
+
// the account signature returned in pair-success is made over it — that is the
|
|
179
|
+
// link between this companion and the account.
|
|
180
|
+
function encodeDevicePairingData(o) {
|
|
181
|
+
return Buffer.concat([
|
|
182
|
+
bytesField(1, o.eRegid),
|
|
183
|
+
bytesField(2, o.eKeytype),
|
|
184
|
+
bytesField(3, o.eIdent),
|
|
185
|
+
bytesField(4, o.eSkeyId),
|
|
186
|
+
bytesField(5, o.eSkeyVal),
|
|
187
|
+
bytesField(6, o.eSkeySig),
|
|
188
|
+
bytesField(7, o.buildHash),
|
|
189
|
+
bytesField(8, o.deviceProps)
|
|
190
|
+
]);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ─── ClientPayload ───────────────────────────────────────────────────────────
|
|
194
|
+
//
|
|
195
|
+
// Field numbers, straight off the schema:
|
|
196
|
+
// 1 username · 3 passive · 5 userAgent · 6 webInfo · 7 pushName
|
|
197
|
+
// 12 connectType · 13 connectReason · 18 device · 19 devicePairingData
|
|
198
|
+
// 33 pull
|
|
199
|
+
|
|
200
|
+
const CONNECT_TYPE_WIFI_UNKNOWN = 1;
|
|
201
|
+
const CONNECT_REASON_USER_ACTIVATED = 1;
|
|
202
|
+
|
|
203
|
+
function commonWebPayload(opts) {
|
|
204
|
+
const parts = [
|
|
205
|
+
bytesField(5, encodeWebUserAgent(opts)),
|
|
206
|
+
bytesField(6, encodeWebInfo()),
|
|
207
|
+
varintField(12, CONNECT_TYPE_WIFI_UNKNOWN),
|
|
208
|
+
varintField(13, CONNECT_REASON_USER_ACTIVATED)
|
|
209
|
+
];
|
|
210
|
+
if (opts.pushName) parts.push(stringField(7, opts.pushName));
|
|
211
|
+
return parts;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* First connection of a brand-new companion. There is no JID yet, so the
|
|
216
|
+
* payload carries the pairing bundle; the server answers with a pair-device
|
|
217
|
+
* (the QR path) or, once a pairing code has been requested and typed into the
|
|
218
|
+
* phone, with a pair-success.
|
|
219
|
+
*/
|
|
220
|
+
function encodeCompanionRegisterPayload(opts) {
|
|
221
|
+
const version = opts.version || [2, 3000, 1];
|
|
222
|
+
const buildHash = crypto.createHash('md5').update(version.join('.')).digest();
|
|
223
|
+
|
|
224
|
+
const deviceProps = encodeDeviceProps({
|
|
225
|
+
os: (opts.browser && opts.browser[0]) || 'Ubuntu',
|
|
226
|
+
browserName: (opts.browser && opts.browser[1]) || 'Chrome',
|
|
227
|
+
requireFullSync: !!opts.syncFullHistory,
|
|
228
|
+
historySync: opts.historySync
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
const pairing = encodeDevicePairingData({
|
|
232
|
+
buildHash,
|
|
233
|
+
deviceProps,
|
|
234
|
+
eRegid: encodeBigEndian(opts.registrationId, 4),
|
|
235
|
+
eKeytype: Buffer.from([5]),
|
|
236
|
+
eIdent: stripKeyPrefix(opts.identityKeyPair.public),
|
|
237
|
+
eSkeyId: encodeBigEndian(opts.signedPreKey.id, 3),
|
|
238
|
+
eSkeyVal: stripKeyPrefix(opts.signedPreKey.public),
|
|
239
|
+
eSkeySig: opts.signedPreKey.signature
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
return Buffer.concat([
|
|
243
|
+
...commonWebPayload(opts),
|
|
244
|
+
boolField(3, false), // passive
|
|
245
|
+
boolField(33, false), // pull
|
|
246
|
+
bytesField(19, pairing) // devicePairingData
|
|
247
|
+
]);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Every connection after pairing. `username` is the account's phone number and
|
|
252
|
+
* `device` is the index the server assigned us; together they form the JID the
|
|
253
|
+
* primary lists under Linked Devices.
|
|
254
|
+
*/
|
|
255
|
+
function encodeCompanionLoginPayload(opts) {
|
|
256
|
+
return Buffer.concat([
|
|
257
|
+
uint64Field(1, opts.username),
|
|
258
|
+
...commonWebPayload(opts),
|
|
259
|
+
boolField(3, true), // passive
|
|
260
|
+
boolField(33, true), // pull
|
|
261
|
+
varintField(18, opts.device || 0)
|
|
262
|
+
]);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
module.exports = {
|
|
266
|
+
encodeCompanionRegisterPayload,
|
|
267
|
+
encodeCompanionLoginPayload,
|
|
268
|
+
encodeDeviceProps,
|
|
269
|
+
encodeHistorySyncConfig,
|
|
270
|
+
encodeBigEndian,
|
|
271
|
+
platformTypeFor,
|
|
272
|
+
PLATFORM_TYPE
|
|
273
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "whalibmob",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.6.2",
|
|
4
4
|
"description": "WhatsApp library for interaction with WhatsApp Mobile API no web",
|
|
5
5
|
"author": "Kunboruto50",
|
|
6
6
|
"main": "index.js",
|
|
@@ -47,7 +47,8 @@
|
|
|
47
47
|
"dotenv": "^16.4.5",
|
|
48
48
|
"pino": "^9.0.0",
|
|
49
49
|
"protobufjs": "^6.11.4",
|
|
50
|
-
"uuid": "^11.1.0"
|
|
50
|
+
"uuid": "^11.1.0",
|
|
51
|
+
"ws": "^8.21.1"
|
|
51
52
|
},
|
|
52
53
|
"optionalDependencies": {
|
|
53
54
|
"jimp": "^1.6.0"
|