whalibmob 2.0.0

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/cli.js ADDED
@@ -0,0 +1,509 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const path = require('path');
5
+ const fs = require('fs');
6
+ const readline = require('readline');
7
+ const {
8
+ WhalibmobClient,
9
+ checkIfRegistered,
10
+ checkNumberStatus,
11
+ requestSmsCode,
12
+ verifyCode,
13
+ createNewStore,
14
+ saveStore,
15
+ loadStore,
16
+ toSixParts,
17
+ fromSixParts
18
+ } = require('./lib/Client');
19
+
20
+ const VERSION = '2.0.0';
21
+
22
+ const USAGE = `
23
+ wa — WhatsApp mobile client CLI v${VERSION}
24
+
25
+ Usage:
26
+ wa registration --check <phone>
27
+ wa registration --request-code <phone> [--method sms|voice|wa_old]
28
+ wa registration --register <phone> --code <code>
29
+ wa connect <phone> [--session <dir>]
30
+ wa send <phone> --to <jid> --text <message>
31
+ wa send <phone> --to <jid> --image <file> [--caption <text>]
32
+ wa send <phone> --to <jid> --video <file> [--caption <text>]
33
+ wa send <phone> --to <jid> --audio <file>
34
+ wa send <phone> --to <jid> --ptt <file>
35
+ wa send <phone> --to <jid> --document <file>
36
+ wa send <phone> --to <jid> --sticker <file>
37
+ wa send <phone> --to <jid> --reaction <emoji> --msg-id <id>
38
+ wa listen <phone> [--session <dir>]
39
+ wa version
40
+
41
+ Options:
42
+ --session <dir> Session storage directory (default: ~/.waSession)
43
+ --method Verification method: sms (default), voice, or wa_old
44
+ --code Verification code received via SMS/voice/wa_old
45
+ --to Recipient JID (e.g. 919634847671@s.whatsapp.net or
46
+ groupid@g.us for groups)
47
+ --text Text message to send
48
+ --caption Caption for media messages
49
+ --reaction Emoji reaction (e.g. 👍)
50
+ --msg-id Message ID to react to (required with --reaction)
51
+
52
+ Examples:
53
+ wa registration --check 919634847671
54
+ wa registration --request-code 919634847671 --method sms
55
+ wa registration --request-code 919634847671 --method wa_old
56
+ wa registration --register 919634847671 --code 123456
57
+ wa connect 919634847671
58
+ wa send 919634847671 --to 911234567890@s.whatsapp.net --text "Hello!"
59
+ wa send 919634847671 --to 911234567890@s.whatsapp.net --image ./photo.jpg --caption "Look at this"
60
+ wa send 919634847671 --to 911234567890@s.whatsapp.net --reaction 👍 --msg-id 3EB0ABCDEF123456
61
+ wa listen 919634847671
62
+ `.trim();
63
+
64
+ function parseArgs(argv) {
65
+ const args = argv.slice(2);
66
+ const result = { cmd: null, sub: null, flags: {}, pos: [] };
67
+ if (!args.length) return result;
68
+ result.cmd = args[0];
69
+ let i = 1;
70
+ if (args[i] && !args[i].startsWith('--')) {
71
+ result.sub = args[i++];
72
+ }
73
+ while (i < args.length) {
74
+ const a = args[i];
75
+ if (a.startsWith('--')) {
76
+ const key = a.slice(2);
77
+ const val = args[i + 1] && !args[i + 1].startsWith('--') ? args[++i] : true;
78
+ result.flags[key] = val;
79
+ } else {
80
+ result.pos.push(a);
81
+ }
82
+ i++;
83
+ }
84
+ return result;
85
+ }
86
+
87
+ function defaultSessionDir() {
88
+ const home = process.env.HOME || process.env.USERPROFILE || '.';
89
+ return path.join(home, '.waSession');
90
+ }
91
+
92
+ function printLine(label, value) {
93
+ process.stdout.write(` ${label.padEnd(22)}: ${value}\n`);
94
+ }
95
+
96
+ function printDivider() {
97
+ process.stdout.write('─'.repeat(55) + '\n');
98
+ }
99
+
100
+ function printHeader(title) {
101
+ printDivider();
102
+ process.stdout.write(` ${title}\n`);
103
+ printDivider();
104
+ }
105
+
106
+ function normalizePhone(phone) {
107
+ return String(phone || '').replace(/^\+/, '').replace(/\D/g, '');
108
+ }
109
+
110
+ function normalizeJid(input) {
111
+ if (!input) return null;
112
+ input = String(input).replace(/^\+/, '').replace(/\D/g, '');
113
+ if (!input.includes('@')) input += '@s.whatsapp.net';
114
+ return input;
115
+ }
116
+
117
+ async function cmdRegistrationCheck(phone, flags) {
118
+ phone = normalizePhone(phone);
119
+ if (!phone) { process.stderr.write('Error: phone number required\n'); process.exit(1); }
120
+ printHeader('WhatsApp Number Check');
121
+ printLine('Phone', '+' + phone);
122
+ process.stdout.write('\n Checking (this may take a few seconds)...\n');
123
+ try {
124
+ const result = await checkNumberStatus(phone);
125
+
126
+ const STATUS_LABEL = {
127
+ registered: 'Active on WhatsApp',
128
+ registered_blocked: 'Active on WhatsApp (third-party blocked)',
129
+ not_registered: 'NOT on WhatsApp',
130
+ cooldown: 'Cooldown — try again in 1-2 min',
131
+ unknown: 'Unknown (datacenter IP restriction)'
132
+ };
133
+
134
+ process.stdout.write('\n');
135
+ printLine('Status', STATUS_LABEL[result.status] || result.status);
136
+
137
+ if (result.status === 'registered') {
138
+ printLine('Usable', 'YES — can connect with this library');
139
+ } else if (result.status === 'registered_blocked') {
140
+ printLine('Usable', 'NO — number has ban from third-party clients');
141
+ printLine('Fix', 'Use the official WhatsApp app first, or a residential IP');
142
+ } else if (result.status === 'not_registered') {
143
+ printLine('Usable', 'YES — verification SMS was sent, run --register to complete');
144
+ } else if (result.status === 'cooldown') {
145
+ printLine('Usable', 'WAIT — code sent recently');
146
+ } else {
147
+ printLine('Usable', 'UNKNOWN — try again with a residential proxy');
148
+ }
149
+
150
+ if (result.note) process.stdout.write('\n Note: ' + result.note + '\n');
151
+ process.stdout.write('\n');
152
+
153
+ if (result.status === 'not_registered') {
154
+ process.stdout.write(' To complete registration run:\n');
155
+ process.stdout.write(` wa registration --register ${phone} --code <code>\n\n`);
156
+ }
157
+ } catch (err) {
158
+ printLine('Error', err.message);
159
+ process.exit(1);
160
+ }
161
+ }
162
+
163
+ async function cmdRegistrationRequestCode(phone, flags) {
164
+ phone = normalizePhone(phone);
165
+ const sdir = flags.session || defaultSessionDir();
166
+ if (!phone) { process.stderr.write('Error: phone number required\n'); process.exit(1); }
167
+ const method = flags.method || 'sms';
168
+ printHeader('Request Verification Code');
169
+ printLine('Phone', '+' + phone);
170
+ printLine('Method', method);
171
+ try {
172
+ if (!fs.existsSync(sdir)) fs.mkdirSync(sdir, { recursive: true });
173
+ let store = loadStore(path.join(sdir, `${phone}.json`));
174
+ if (!store) {
175
+ store = createNewStore(phone);
176
+ saveStore(store, path.join(sdir, `${phone}.json`));
177
+ }
178
+ const result = await requestSmsCode(store, method);
179
+ printLine('Result', result && result.status ? result.status : JSON.stringify(result));
180
+ if (result && result.status === 'sent') {
181
+ process.stdout.write('\n Code sent. Run:\n');
182
+ process.stdout.write(` wa registration --register ${phone} --code <code>\n\n`);
183
+ } else if (result && result.status === 'too_many') {
184
+ process.stdout.write('\n Too many attempts. Wait before retrying.\n\n');
185
+ }
186
+ } catch (err) {
187
+ if (err.message && err.message.includes('too_recent')) {
188
+ printLine('Result', 'too_recent');
189
+ process.stdout.write('\n Code already sent recently. Check your phone or wait ~1 minute.\n\n');
190
+ } else {
191
+ printLine('Error', err.message);
192
+ process.exit(1);
193
+ }
194
+ }
195
+ }
196
+
197
+ async function cmdRegistrationRegister(phone, flags) {
198
+ phone = normalizePhone(phone);
199
+ const code = flags.code;
200
+ const sdir = flags.session || defaultSessionDir();
201
+ if (!phone) { process.stderr.write('Error: phone number required\n'); process.exit(1); }
202
+ if (!code) { process.stderr.write('Error: --code required\n'); process.exit(1); }
203
+
204
+ printHeader('Register with Verification Code');
205
+ printLine('Phone', '+' + phone);
206
+ printLine('Code', code);
207
+
208
+ let store = loadStore(path.join(sdir, `${phone}.json`));
209
+ if (!store) {
210
+ store = createNewStore(phone);
211
+ }
212
+
213
+ try {
214
+ const result = await verifyCode(store, code);
215
+ if (result && result.status === 'ok') {
216
+ if (!fs.existsSync(sdir)) fs.mkdirSync(sdir, { recursive: true });
217
+ if (result.store) saveStore(result.store, path.join(sdir, `${phone}.json`));
218
+ else saveStore(store, path.join(sdir, `${phone}.json`));
219
+
220
+ printLine('Status', 'registered');
221
+ if (result.login) printLine('Login', result.login);
222
+ if (result.push_name) printLine('Name', result.push_name);
223
+ printLine('Session', path.join(sdir, `${phone}.json`));
224
+ process.stdout.write('\n Registration successful.\n\n');
225
+ } else {
226
+ printLine('Status', result && result.status ? result.status : JSON.stringify(result));
227
+ if (result && (result.status === 'fail' || result.status === 'bad_token')) {
228
+ process.stdout.write('\n Registration failed. Check your code and try again.\n\n');
229
+ process.exit(1);
230
+ }
231
+ }
232
+ } catch (err) {
233
+ printLine('Error', err.message);
234
+ process.exit(1);
235
+ }
236
+ }
237
+
238
+ async function cmdConnect(phone, flags) {
239
+ phone = normalizePhone(phone);
240
+ const sdir = flags.session || defaultSessionDir();
241
+ if (!phone) { process.stderr.write('Error: phone number required\n'); process.exit(1); }
242
+
243
+ printHeader('Connecting to WhatsApp');
244
+ printLine('Phone', '+' + phone);
245
+ printLine('Session', sdir);
246
+
247
+ const client = new WhalibmobClient({ sessionDir: sdir });
248
+
249
+ client.on('connected', () => {
250
+ printLine('Status', 'connected');
251
+ process.stdout.write('\n Connected. Type messages at the prompt.\n');
252
+ process.stdout.write(' Commands: /send <jid> <text> /quit\n\n');
253
+ startInteractiveShell(client, phone);
254
+ });
255
+
256
+ client.on('message', (msg) => {
257
+ process.stdout.write(`\n [${new Date().toISOString()}] Message from ${msg.from}\n`);
258
+ if (msg.text) process.stdout.write(` Text: ${msg.text}\n`);
259
+ if (msg.plaintext) process.stdout.write(` Decrypted: ${msg.plaintext.toString('utf8').replace(/[\x00-\x1f]/g, '')}\n`);
260
+ process.stdout.write('\n');
261
+ });
262
+
263
+ client.on('receipt', (r) => {
264
+ if (r.type === 'read') {
265
+ process.stdout.write(` [read receipt] ${r.id} from ${r.from}\n`);
266
+ }
267
+ });
268
+
269
+ client.on('auth_failure', (f) => {
270
+ process.stderr.write('Auth failure: ' + f.reason + ' — session revoked. Re-register the number.\n');
271
+ process.exit(1);
272
+ });
273
+ client.on('close', () => { printLine('Status', 'disconnected'); process.exit(0); });
274
+ client.on('error', (e) => { process.stderr.write('Error: ' + e.message + '\n'); });
275
+
276
+ try {
277
+ await client.init(phone);
278
+ } catch (err) {
279
+ process.stderr.write('Error: ' + err.message + '\n');
280
+ process.exit(1);
281
+ }
282
+ }
283
+
284
+ function startInteractiveShell(client, phone) {
285
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout, prompt: 'wa> ' });
286
+ rl.prompt();
287
+ rl.on('line', async (line) => {
288
+ line = line.trim();
289
+ if (!line) { rl.prompt(); return; }
290
+
291
+ if (line === '/quit' || line === '/exit') {
292
+ client.disconnect();
293
+ process.exit(0);
294
+ }
295
+
296
+ const sendMatch = line.match(/^\/send\s+(\S+)\s+(.+)$/);
297
+ if (sendMatch) {
298
+ const jid = normalizeJid(sendMatch[1]);
299
+ const text = sendMatch[2];
300
+ try {
301
+ const id = await client.sendText(jid, text);
302
+ process.stdout.write(` Sent message ${id}\n`);
303
+ } catch (e) {
304
+ process.stderr.write(` Send failed: ${e.message}\n`);
305
+ }
306
+ rl.prompt();
307
+ return;
308
+ }
309
+
310
+ if (line.startsWith('/presence')) {
311
+ const avail = !line.includes('away');
312
+ client.setPresence(avail);
313
+ process.stdout.write(` Presence set to ${avail ? 'available' : 'unavailable'}\n`);
314
+ rl.prompt();
315
+ return;
316
+ }
317
+
318
+ process.stdout.write(' Unknown command. Use /send <jid> <text> or /quit\n');
319
+ rl.prompt();
320
+ });
321
+ rl.on('close', () => { client.disconnect(); process.exit(0); });
322
+ }
323
+
324
+ async function cmdSend(phone, flags) {
325
+ phone = normalizePhone(phone);
326
+ const sdir = flags.session || defaultSessionDir();
327
+ const to = normalizeJid(flags.to);
328
+ if (!phone) { process.stderr.write('Error: phone number required\n'); process.exit(1); }
329
+ if (!to) { process.stderr.write('Error: --to <jid> required\n'); process.exit(1); }
330
+
331
+ const client = new WhalibmobClient({ sessionDir: sdir });
332
+
333
+ client.on('error', (e) => { process.stderr.write('Error: ' + e.message + '\n'); });
334
+ client.on('auth_failure', (f) => {
335
+ process.stderr.write('Auth failure: ' + f.reason + ' — session revoked. Re-register the number.\n');
336
+ process.exit(1);
337
+ });
338
+
339
+ try {
340
+ await client.init(phone);
341
+
342
+ await new Promise((resolve) => {
343
+ client.once('connected', resolve);
344
+ setTimeout(resolve, 8000);
345
+ });
346
+
347
+ let msgId;
348
+
349
+ if (flags.text) {
350
+ msgId = await client.sendText(to, flags.text);
351
+ printLine('Sent text', msgId);
352
+ } else if (flags.image) {
353
+ msgId = await client.sendImage(to, flags.image, { caption: flags.caption });
354
+ printLine('Sent image', msgId);
355
+ } else if (flags.video) {
356
+ msgId = await client.sendVideo(to, flags.video, { caption: flags.caption });
357
+ printLine('Sent video', msgId);
358
+ } else if (flags.ptt) {
359
+ msgId = await client.sendAudio(to, flags.ptt, { ptt: true });
360
+ printLine('Sent voice', msgId);
361
+ } else if (flags.audio) {
362
+ msgId = await client.sendAudio(to, flags.audio, {});
363
+ printLine('Sent audio', msgId);
364
+ } else if (flags.document) {
365
+ const fname = path.basename(flags.document);
366
+ msgId = await client.sendDocument(to, flags.document, { fileName: fname });
367
+ printLine('Sent document', msgId);
368
+ } else if (flags.sticker) {
369
+ msgId = await client.sendSticker(to, flags.sticker, {});
370
+ printLine('Sent sticker', msgId);
371
+ } else if (flags.reaction) {
372
+ const reactionMsgId = flags['msg-id'];
373
+ if (!reactionMsgId) {
374
+ process.stderr.write('Error: --msg-id <id> required when using --reaction\n');
375
+ process.exit(1);
376
+ }
377
+ msgId = await client.sendReaction(to, reactionMsgId, flags.reaction);
378
+ printLine('Sent reaction', flags.reaction + ' on ' + reactionMsgId);
379
+ } else {
380
+ process.stderr.write('Error: specify --text, --image, --video, --audio, --ptt, --document, --sticker, or --reaction\n');
381
+ process.exit(1);
382
+ }
383
+
384
+ await new Promise(r => setTimeout(r, 2000));
385
+ client.disconnect();
386
+ process.exit(0);
387
+ } catch (err) {
388
+ process.stderr.write('Error: ' + err.message + '\n');
389
+ process.exit(1);
390
+ }
391
+ }
392
+
393
+ async function cmdListen(phone, flags) {
394
+ phone = normalizePhone(phone);
395
+ const sdir = flags.session || defaultSessionDir();
396
+ if (!phone) { process.stderr.write('Error: phone number required\n'); process.exit(1); }
397
+
398
+ printHeader('Listening for messages');
399
+ printLine('Phone', '+' + phone);
400
+ printLine('Session', sdir);
401
+
402
+ const client = new WhalibmobClient({ sessionDir: sdir });
403
+
404
+ client.on('connected', () => {
405
+ printLine('Status', 'connected');
406
+ process.stdout.write('\n Listening... Press Ctrl+C to stop.\n\n');
407
+ });
408
+
409
+ client.on('message', (msg) => {
410
+ process.stdout.write('─'.repeat(55) + '\n');
411
+ printLine('From', msg.from);
412
+ printLine('ID', msg.id);
413
+ printLine('Timestamp', new Date(msg.ts * 1000).toISOString());
414
+ if (msg.text) printLine('Text', msg.text);
415
+ if (msg.plaintext) {
416
+ const clean = msg.plaintext.toString('utf8').replace(/[\x00-\x08\x0b-\x1f\x7f]/g, '');
417
+ printLine('Content', clean.substring(0, 200));
418
+ }
419
+ process.stdout.write('\n');
420
+ });
421
+
422
+ client.on('receipt', (r) => {
423
+ if (r.type === 'read') {
424
+ printLine('Read receipt', `${r.id} from ${r.from}`);
425
+ }
426
+ });
427
+
428
+ client.on('presence', (p) => {
429
+ printLine('Presence', `${p.from} is ${p.available ? 'online' : 'offline'}`);
430
+ });
431
+
432
+ client.on('auth_failure', (f) => {
433
+ process.stderr.write('Auth failure: ' + f.reason + ' — session revoked. Re-register the number.\n');
434
+ process.exit(1);
435
+ });
436
+ client.on('close', () => { printLine('Status', 'disconnected'); process.exit(0); });
437
+ client.on('error', (e) => { process.stderr.write('Error: ' + e.message + '\n'); });
438
+
439
+ process.on('SIGINT', () => {
440
+ process.stdout.write('\n Disconnecting...\n');
441
+ client.disconnect();
442
+ process.exit(0);
443
+ });
444
+
445
+ try {
446
+ await client.init(phone);
447
+ } catch (err) {
448
+ process.stderr.write('Error: ' + err.message + '\n');
449
+ process.exit(1);
450
+ }
451
+ }
452
+
453
+ async function main() {
454
+ const parsed = parseArgs(process.argv);
455
+ const { cmd, sub, flags, pos } = parsed;
456
+
457
+ if (!cmd || cmd === '--help' || cmd === '-h' || cmd === 'help') {
458
+ process.stdout.write(USAGE + '\n');
459
+ return;
460
+ }
461
+
462
+ if (cmd === 'version' || cmd === '--version' || cmd === '-v') {
463
+ process.stdout.write(`wa v${VERSION}\n`);
464
+ return;
465
+ }
466
+
467
+ if (cmd === 'registration') {
468
+ const phone = flags.check || flags['request-code'] || flags.register || pos[0] || '';
469
+
470
+ if (flags.check !== undefined) {
471
+ return cmdRegistrationCheck(flags.check === true ? phone : flags.check, flags);
472
+ }
473
+ if (flags['request-code'] !== undefined) {
474
+ const p = flags['request-code'] === true ? pos[0] : flags['request-code'];
475
+ return cmdRegistrationRequestCode(p, flags);
476
+ }
477
+ if (flags.register !== undefined) {
478
+ const p = flags.register === true ? pos[0] : flags.register;
479
+ return cmdRegistrationRegister(p, flags);
480
+ }
481
+
482
+ process.stderr.write('Error: specify --check, --request-code, or --register\n');
483
+ process.exit(1);
484
+ return;
485
+ }
486
+
487
+ if (cmd === 'connect') {
488
+ const phone = sub || pos[0] || flags.phone || '';
489
+ return cmdConnect(phone, flags);
490
+ }
491
+
492
+ if (cmd === 'send') {
493
+ const phone = sub || pos[0] || flags.phone || '';
494
+ return cmdSend(phone, flags);
495
+ }
496
+
497
+ if (cmd === 'listen') {
498
+ const phone = sub || pos[0] || flags.phone || '';
499
+ return cmdListen(phone, flags);
500
+ }
501
+
502
+ process.stderr.write(`Unknown command: ${cmd}\n\n${USAGE}\n`);
503
+ process.exit(1);
504
+ }
505
+
506
+ main().catch(err => {
507
+ process.stderr.write('Fatal: ' + err.message + '\n');
508
+ process.exit(1);
509
+ });
package/index.js ADDED
@@ -0,0 +1,35 @@
1
+ 'use strict';
2
+
3
+ const { WhalibmobClient } = require('./lib/Client');
4
+ const { createNewStore, saveStore, loadStore, toSixParts, fromSixParts } = require('./lib/Store');
5
+ const { checkIfRegistered, requestSmsCode, verifyCode } = require('./lib/Registration');
6
+ const { SignalProtocol } = require('./lib/signal/SignalProtocol');
7
+ const { SignalStore } = require('./lib/signal/SignalStore');
8
+ const { SenderKeyStore, SenderKeyCrypto } = require('./lib/signal/SenderKey');
9
+ const { DeviceManager } = require('./lib/DeviceManager');
10
+ const { encryptMedia, decryptMedia, uploadMedia, downloadMedia } = require('./lib/MediaService');
11
+ const { MessageSender, makeJid, generateMessageId } = require('./lib/messages/MessageSender');
12
+
13
+ module.exports = {
14
+ WhalibmobClient,
15
+ createNewStore,
16
+ saveStore,
17
+ loadStore,
18
+ toSixParts,
19
+ fromSixParts,
20
+ checkIfRegistered,
21
+ requestSmsCode,
22
+ verifyCode,
23
+ SignalProtocol,
24
+ SignalStore,
25
+ SenderKeyStore,
26
+ SenderKeyCrypto,
27
+ DeviceManager,
28
+ encryptMedia,
29
+ decryptMedia,
30
+ uploadMedia,
31
+ downloadMedia,
32
+ MessageSender,
33
+ makeJid,
34
+ generateMessageId
35
+ };