@shoru/kitten 0.0.1-beta
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/LICENSE +21 -0
- package/README.md +24 -0
- package/package.json +41 -0
- package/src/auth/index.js +2 -0
- package/src/auth/init-session.js +21 -0
- package/src/auth/lmdb-auth-state.js +171 -0
- package/src/client/client.js +601 -0
- package/src/client/getConnectionConfig.js +51 -0
- package/src/client/index.js +2 -0
- package/src/config/default.js +39 -0
- package/src/formatter/format-message.js +144 -0
- package/src/formatter/index.js +11 -0
- package/src/index.js +4 -0
- package/src/internals/config.js +40 -0
- package/src/internals/index.js +5 -0
- package/src/internals/lmdb-manager.js +52 -0
- package/src/internals/logger.js +15 -0
- package/src/internals/plugin-manager.js +480 -0
- package/src/internals/spinner.js +3 -0
- package/src/utils/buffer-json.js +11 -0
- package/src/utils/get-pn.js +9 -0
- package/src/utils/index.js +5 -0
- package/src/utils/pause-spinner.js +12 -0
- package/src/utils/time-string.js +19 -0
- package/src/utils/type-conversions.js +5 -0
|
@@ -0,0 +1,601 @@
|
|
|
1
|
+
import { DisconnectReason } from 'baileys';
|
|
2
|
+
import { Boom } from '@hapi/boom';
|
|
3
|
+
import qrcode from 'qrcode-terminal';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { logger, pino, pluginManager } from '#internals.js';
|
|
6
|
+
import { initSession, listSessions } from '#auth.js';
|
|
7
|
+
import { getConnectionConfig } from './getConnectionConfig.js';
|
|
8
|
+
|
|
9
|
+
export const ConnectionState = Object.freeze({
|
|
10
|
+
DISCONNECTED: 'disconnected',
|
|
11
|
+
CONNECTING: 'connecting',
|
|
12
|
+
CONNECTED: 'connected',
|
|
13
|
+
RECONNECTING: 'reconnecting',
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
class ConnectionError extends Error {
|
|
17
|
+
constructor(message, { statusCode, recoverable = true } = {}) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.name = 'ConnectionError';
|
|
20
|
+
this.statusCode = statusCode;
|
|
21
|
+
this.recoverable = recoverable;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const DISCONNECT_HANDLERS = new Map([
|
|
26
|
+
[DisconnectReason.connectionClosed, { message: 'Connection closed', recoverable: true }],
|
|
27
|
+
[DisconnectReason.restartRequired, { message: 'QR Scanned', recoverable: true }],
|
|
28
|
+
[DisconnectReason.timedOut, { message: 'Connection timed out', recoverable: true }],
|
|
29
|
+
[DisconnectReason.connectionLost, { message: 'Connection lost', recoverable: true }],
|
|
30
|
+
[DisconnectReason.unavailableService, { message: 'Service unavailable', recoverable: true }],
|
|
31
|
+
[DisconnectReason.loggedOut, { message: 'Session logged out', recoverable: true, deleteSession: true }],
|
|
32
|
+
[DisconnectReason.forbidden, { message: 'Account banned', recoverable: false, deleteSession: true }],
|
|
33
|
+
[405, { message: 'Not logged in', recoverable: true, deleteSession: true }],
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
const silentLogger = Object.freeze({
|
|
37
|
+
trace: () => {},
|
|
38
|
+
debug: () => {},
|
|
39
|
+
info: () => {},
|
|
40
|
+
warn: () => {},
|
|
41
|
+
error: () => {},
|
|
42
|
+
fatal: () => {},
|
|
43
|
+
prompt: () => {},
|
|
44
|
+
child: () => silentLogger,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const silentPino = pino({ level: 'silent' });
|
|
48
|
+
|
|
49
|
+
export class Client {
|
|
50
|
+
static #registry = new Map();
|
|
51
|
+
|
|
52
|
+
static #isSyncing = false;
|
|
53
|
+
static #isConfiguring = false;
|
|
54
|
+
|
|
55
|
+
// Static Registry API
|
|
56
|
+
|
|
57
|
+
static get(id) {
|
|
58
|
+
return Client.#registry.get(id);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
static has(id) {
|
|
62
|
+
return Client.#registry.has(id);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
static get size() {
|
|
66
|
+
return Client.#registry.size;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
static keys() {
|
|
70
|
+
return Client.#registry.keys();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
static values() {
|
|
74
|
+
return Client.#registry.values();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
static entries() {
|
|
78
|
+
return Client.#registry.entries();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
static [Symbol.iterator]() {
|
|
82
|
+
return Client.#registry.values();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Instance Properties
|
|
86
|
+
|
|
87
|
+
sock = null;
|
|
88
|
+
session = null;
|
|
89
|
+
id = null;
|
|
90
|
+
|
|
91
|
+
#flag = '';
|
|
92
|
+
#plugins = null;
|
|
93
|
+
#qr = null;
|
|
94
|
+
#state = ConnectionState.DISCONNECTED;
|
|
95
|
+
#cancelWait = null;
|
|
96
|
+
#hasConnectedOnce = false;
|
|
97
|
+
|
|
98
|
+
#socketConfig = null;
|
|
99
|
+
#authConfig = null;
|
|
100
|
+
|
|
101
|
+
#reconnectAttempts = 0;
|
|
102
|
+
#reconnectTimer = null;
|
|
103
|
+
#isShuttingDown = false;
|
|
104
|
+
|
|
105
|
+
#pendingConnect = null;
|
|
106
|
+
|
|
107
|
+
#maxRetries;
|
|
108
|
+
#backoff;
|
|
109
|
+
|
|
110
|
+
// Options
|
|
111
|
+
#silent;
|
|
112
|
+
#sync;
|
|
113
|
+
#logger;
|
|
114
|
+
|
|
115
|
+
// Callbacks
|
|
116
|
+
#onPairing;
|
|
117
|
+
#onConnect;
|
|
118
|
+
#onReconnect;
|
|
119
|
+
#onDisconnect;
|
|
120
|
+
#onStateChange;
|
|
121
|
+
|
|
122
|
+
constructor(options = {}) {
|
|
123
|
+
const {
|
|
124
|
+
id,
|
|
125
|
+
maxRetries = 30,
|
|
126
|
+
backoff = (attempt) => Math.min(1000 * 2 ** (attempt - 1), 60_000),
|
|
127
|
+
silent = false,
|
|
128
|
+
sync = false,
|
|
129
|
+
onPairing = null,
|
|
130
|
+
onConnect = null,
|
|
131
|
+
onReconnect = null,
|
|
132
|
+
onDisconnect = null,
|
|
133
|
+
onStateChange = null,
|
|
134
|
+
socketConfig = {},
|
|
135
|
+
} = options;
|
|
136
|
+
|
|
137
|
+
this.id = id;
|
|
138
|
+
this.#socketConfig = socketConfig;
|
|
139
|
+
this.#maxRetries = maxRetries;
|
|
140
|
+
this.#backoff = backoff;
|
|
141
|
+
this.#silent = silent;
|
|
142
|
+
this.#sync = sync;
|
|
143
|
+
this.#logger = silent ? silentLogger : logger;
|
|
144
|
+
this.#onPairing = onPairing;
|
|
145
|
+
this.#onConnect = onConnect;
|
|
146
|
+
this.#onReconnect = onReconnect;
|
|
147
|
+
this.#onDisconnect = onDisconnect;
|
|
148
|
+
this.#onStateChange = onStateChange;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
get state() {
|
|
152
|
+
return this.#state;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
get isConnected() {
|
|
156
|
+
return this.#state === ConnectionState.CONNECTED;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
get reconnectAttempts() {
|
|
160
|
+
return this.#reconnectAttempts;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Registry Management
|
|
164
|
+
|
|
165
|
+
#register() {
|
|
166
|
+
if (this.id != null) {
|
|
167
|
+
Client.#registry.set(this.id, this);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
#unregister() {
|
|
172
|
+
if (this.id != null) {
|
|
173
|
+
Client.#registry.delete(this.id);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// State Management
|
|
178
|
+
|
|
179
|
+
#setState(newState) {
|
|
180
|
+
const oldState = this.#state;
|
|
181
|
+
if (oldState === newState) return;
|
|
182
|
+
|
|
183
|
+
this.#state = newState;
|
|
184
|
+
this.#emit('stateChange', { oldState, newState });
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
#emit(event, data = {}) {
|
|
188
|
+
const callbacks = {
|
|
189
|
+
connect: this.#onConnect,
|
|
190
|
+
reconnect: this.#onReconnect,
|
|
191
|
+
disconnect: this.#onDisconnect,
|
|
192
|
+
stateChange: this.#onStateChange,
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const callback = callbacks[event];
|
|
196
|
+
if (typeof callback !== 'function') return;
|
|
197
|
+
|
|
198
|
+
queueMicrotask(() => {
|
|
199
|
+
try {
|
|
200
|
+
callback({ ...data, client: this });
|
|
201
|
+
} catch (err) {
|
|
202
|
+
this.#logger.error(err, `[${this.#flag}] Error in ${event} callback`);
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Connection Management
|
|
208
|
+
|
|
209
|
+
async connect() {
|
|
210
|
+
if (this.#isShuttingDown) {
|
|
211
|
+
throw new Error(`[${this.#flag}] Client is shutting down`);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (this.#state === ConnectionState.CONNECTED) {
|
|
215
|
+
return { sock: this.sock, session: this.session, id: this.id };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (this.#pendingConnect) {
|
|
219
|
+
return this.#pendingConnect.promise;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return this.#initConnection();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async #initConnection() {
|
|
226
|
+
this.#setState(ConnectionState.CONNECTING);
|
|
227
|
+
this.#reconnectAttempts = 0;
|
|
228
|
+
this.#pendingConnect = this.#createDeferred();
|
|
229
|
+
|
|
230
|
+
try {
|
|
231
|
+
await this.#createSocket();
|
|
232
|
+
} catch (err) {
|
|
233
|
+
this.#setState(ConnectionState.DISCONNECTED);
|
|
234
|
+
this.#resolvePending(null, err);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return this.#pendingConnect.promise;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
#createDeferred() {
|
|
241
|
+
let resolve, reject;
|
|
242
|
+
const promise = new Promise((res, rej) => {
|
|
243
|
+
resolve = res;
|
|
244
|
+
reject = rej;
|
|
245
|
+
});
|
|
246
|
+
return { promise, resolve, reject };
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
#resolvePending(value, error = null) {
|
|
250
|
+
if (!this.#pendingConnect) return;
|
|
251
|
+
|
|
252
|
+
const { resolve, reject } = this.#pendingConnect;
|
|
253
|
+
this.#pendingConnect = null;
|
|
254
|
+
|
|
255
|
+
if (error) {
|
|
256
|
+
reject(error);
|
|
257
|
+
} else {
|
|
258
|
+
resolve(value);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async #createSocket() {
|
|
263
|
+
this.#cleanupSocket();
|
|
264
|
+
|
|
265
|
+
const socketConfig = this.#silent
|
|
266
|
+
? { ...this.#socketConfig, logger: silentPino }
|
|
267
|
+
: this.#socketConfig;
|
|
268
|
+
|
|
269
|
+
const { sock, session } = await initSession({
|
|
270
|
+
socketConfig,
|
|
271
|
+
id: this.id,
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
this.sock = sock;
|
|
275
|
+
this.session = session;
|
|
276
|
+
this.id = session.id;
|
|
277
|
+
this.#flag = `CLIENT-${session.id}`;
|
|
278
|
+
|
|
279
|
+
try {
|
|
280
|
+
this.#plugins = await pluginManager(this.sock);
|
|
281
|
+
} catch (err) {
|
|
282
|
+
this.#logger.error(err, `[${this.#flag}] Failed to initialize plugins`);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
this.sock.ev.on('connection.update', (update) => {
|
|
286
|
+
this.#handleConnectionUpdate(update);
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
async #handleConnectionUpdate({ connection, lastDisconnect, qr }) {
|
|
291
|
+
if (this.#isShuttingDown) return;
|
|
292
|
+
|
|
293
|
+
try {
|
|
294
|
+
if (qr) {
|
|
295
|
+
await this.#handleAuth(qr);
|
|
296
|
+
} else if (connection === 'open') {
|
|
297
|
+
await this.#onConnectionOpen();
|
|
298
|
+
} else if (connection === 'close') {
|
|
299
|
+
await this.#onConnectionClose(lastDisconnect);
|
|
300
|
+
}
|
|
301
|
+
} catch (err) {
|
|
302
|
+
this.#logger.error(err, `[${this.#flag}] Error in connection update handler`);
|
|
303
|
+
this.#resolvePending(null, err);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
async #onConnectionOpen() {
|
|
308
|
+
const wasReconnecting = this.#state === ConnectionState.RECONNECTING;
|
|
309
|
+
this.#setState(ConnectionState.CONNECTED);
|
|
310
|
+
|
|
311
|
+
this.#register();
|
|
312
|
+
|
|
313
|
+
const attempts = this.#reconnectAttempts;
|
|
314
|
+
this.#reconnectAttempts = 0;
|
|
315
|
+
|
|
316
|
+
if (wasReconnecting) {
|
|
317
|
+
this.#emit('reconnect', { attempts });
|
|
318
|
+
this.#logger.debug(`[${this.#flag}] Reconnected after ${attempts} attempt(s)`);
|
|
319
|
+
} else {
|
|
320
|
+
this.#hasConnectedOnce = true;
|
|
321
|
+
this.#emit('connect');
|
|
322
|
+
this.#logger.debug(`[${this.#flag}] Connected successfully`);
|
|
323
|
+
this.#resolvePending({ sock: this.sock, session: this.session, id: this.id });
|
|
324
|
+
|
|
325
|
+
if (!this.#sync) {
|
|
326
|
+
this.#syncOtherSessions();
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Automatic Session Synchronization
|
|
332
|
+
|
|
333
|
+
async #syncOtherSessions() {
|
|
334
|
+
if (Client.#isSyncing) return;
|
|
335
|
+
Client.#isSyncing = true;
|
|
336
|
+
|
|
337
|
+
try {
|
|
338
|
+
const allSessionIds = listSessions();
|
|
339
|
+
const otherSessionIds = allSessionIds.filter((id) => id !== this.id);
|
|
340
|
+
|
|
341
|
+
if (otherSessionIds.length === 0) {
|
|
342
|
+
this.#logger.debug(`[${this.#flag}] No other sessions to sync`);
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
this.#logger.debug(
|
|
347
|
+
`[${this.#flag}] Syncing ${otherSessionIds.length} other session(s) in background`
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
const results = await Promise.allSettled(
|
|
351
|
+
otherSessionIds.map((sessionId) => this.#restoreSession(sessionId))
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
let successCount = 0;
|
|
355
|
+
let skipCount = 0;
|
|
356
|
+
let failCount = 0;
|
|
357
|
+
|
|
358
|
+
for (const result of results) {
|
|
359
|
+
if (result.status === 'fulfilled') {
|
|
360
|
+
if (result.value === true) successCount++;
|
|
361
|
+
else skipCount++;
|
|
362
|
+
} else {
|
|
363
|
+
failCount++;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
this.#logger.debug(
|
|
368
|
+
`[${this.#flag}] Session sync complete: ${successCount} restored, ${skipCount} skipped, ${failCount} failed`
|
|
369
|
+
);
|
|
370
|
+
} catch (err) {
|
|
371
|
+
this.#logger.error(err, `[${this.#flag}] Error during session sync`);
|
|
372
|
+
} finally {
|
|
373
|
+
Client.#isSyncing = false;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
async #restoreSession(sessionId) {
|
|
378
|
+
if (Client.has(sessionId)) {
|
|
379
|
+
return false;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const client = new Client({
|
|
383
|
+
id: sessionId,
|
|
384
|
+
silent: true,
|
|
385
|
+
sync: true,
|
|
386
|
+
maxRetries: 3,
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
await client.connect();
|
|
390
|
+
return true;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
async #onConnectionClose(lastDisconnect) {
|
|
394
|
+
const disconnectInfo = this.#parseDisconnectReason(lastDisconnect);
|
|
395
|
+
const { message, statusCode, recoverable, deleteSession } = disconnectInfo;
|
|
396
|
+
|
|
397
|
+
if (message === 'QR Scanned' && !this.#onPairing && !this.#silent) {
|
|
398
|
+
console.clear();
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const level = recoverable ? 'debug' : 'warn';
|
|
402
|
+
this.#logger[level](`[${this.#flag}] Disconnected: ${message} (code: ${statusCode})`);
|
|
403
|
+
|
|
404
|
+
if (this.#hasConnectedOnce) {
|
|
405
|
+
this.#emit('disconnect', { message, statusCode, recoverable });
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
this.#unregister();
|
|
409
|
+
|
|
410
|
+
if (deleteSession) {
|
|
411
|
+
await this.session?.delete().catch((err) => {
|
|
412
|
+
this.#logger.error(err, `[${this.#flag}] Failed to delete session`);
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (!recoverable || this.#isShuttingDown) {
|
|
417
|
+
this.#setState(ConnectionState.DISCONNECTED);
|
|
418
|
+
this.#resolvePending(null, new ConnectionError(message, { statusCode, recoverable }));
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
await this.#scheduleReconnect(message);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Reconnection Logic
|
|
426
|
+
|
|
427
|
+
async #scheduleReconnect(reason) {
|
|
428
|
+
this.#reconnectAttempts++;
|
|
429
|
+
|
|
430
|
+
if (this.#reconnectAttempts > this.#maxRetries) {
|
|
431
|
+
const err = new ConnectionError(
|
|
432
|
+
`Max reconnection attempts (${this.#maxRetries}) exceeded`,
|
|
433
|
+
{ recoverable: false }
|
|
434
|
+
);
|
|
435
|
+
this.#setState(ConnectionState.DISCONNECTED);
|
|
436
|
+
this.#resolvePending(null, err);
|
|
437
|
+
this.#logger.error(err, `[${this.#flag}] ${err.message}`);
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
if (this.#hasConnectedOnce) {
|
|
442
|
+
this.#setState(ConnectionState.RECONNECTING);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const delay = this.#backoff(this.#reconnectAttempts);
|
|
446
|
+
const retriesInfo =
|
|
447
|
+
this.#maxRetries !== Infinity
|
|
448
|
+
? `(${this.#reconnectAttempts}/${this.#maxRetries})`
|
|
449
|
+
: '';
|
|
450
|
+
|
|
451
|
+
this.#logger.debug(`[${this.#flag}] ${reason}. Reconnecting in ${delay}ms`);
|
|
452
|
+
|
|
453
|
+
const cancelled = await this.#wait(delay);
|
|
454
|
+
if (cancelled || this.#isShuttingDown) return;
|
|
455
|
+
|
|
456
|
+
this.#logger.debug(`[${this.#flag}] Executing reconnect attempt ${retriesInfo}`);
|
|
457
|
+
|
|
458
|
+
try {
|
|
459
|
+
await this.#createSocket();
|
|
460
|
+
} catch (err) {
|
|
461
|
+
this.#logger.error(err, `[${this.#flag}] Socket creation failed during reconnect`);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
#wait(ms) {
|
|
466
|
+
return new Promise((resolve) => {
|
|
467
|
+
this.#reconnectTimer = setTimeout(() => {
|
|
468
|
+
this.#reconnectTimer = null;
|
|
469
|
+
resolve(false);
|
|
470
|
+
}, ms);
|
|
471
|
+
|
|
472
|
+
this.#cancelWait = () => resolve(true);
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
#parseDisconnectReason(lastDisconnect) {
|
|
477
|
+
const boom = new Boom(lastDisconnect?.error);
|
|
478
|
+
const statusCode = boom?.output?.statusCode;
|
|
479
|
+
const handler = DISCONNECT_HANDLERS.get(statusCode);
|
|
480
|
+
|
|
481
|
+
if (!handler) {
|
|
482
|
+
return {
|
|
483
|
+
message: `Unknown disconnect reason (code: ${statusCode ?? 'unknown'})`,
|
|
484
|
+
statusCode,
|
|
485
|
+
recoverable: true,
|
|
486
|
+
deleteSession: false,
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
return { ...handler, statusCode };
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Authentication
|
|
494
|
+
|
|
495
|
+
async #handleAuth(qr) {
|
|
496
|
+
this.#qr = qr;
|
|
497
|
+
|
|
498
|
+
if (this.#sync) {
|
|
499
|
+
const err = new ConnectionError('Authentication required for sync connection', {
|
|
500
|
+
recoverable: false,
|
|
501
|
+
});
|
|
502
|
+
this.#logger.debug(`[${this.#flag}] Sync connection requires auth, aborting`);
|
|
503
|
+
this.#cleanupSocket();
|
|
504
|
+
this.#setState(ConnectionState.DISCONNECTED);
|
|
505
|
+
this.#resolvePending(null, err);
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
if (Client.#isConfiguring) return;
|
|
510
|
+
|
|
511
|
+
if (typeof this.#onPairing === 'function') {
|
|
512
|
+
const requestPairingCode = this.sock?.requestPairingCode?.bind(this.sock);
|
|
513
|
+
await this.#onPairing({ qr: this.#qr, requestPairingCode });
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
if (this.#silent) {
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
Client.#isConfiguring = true;
|
|
522
|
+
try {
|
|
523
|
+
this.#authConfig ??= await getConnectionConfig();
|
|
524
|
+
} finally {
|
|
525
|
+
Client.#isConfiguring = false;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
if (this.#authConfig.type === 'pn') {
|
|
529
|
+
const code = await this.sock.requestPairingCode(this.#authConfig.pn);
|
|
530
|
+
this.#logger.prompt(this.#formatPairingCode(code));
|
|
531
|
+
} else {
|
|
532
|
+
qrcode.generate(this.#qr, { small: true });
|
|
533
|
+
process.stdout.write('\n');
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
#formatPairingCode(code) {
|
|
538
|
+
const formatted = code.match(/.{1,4}/g)?.join(' ') ?? code;
|
|
539
|
+
return `\n${chalk.green('> Your OTP Code: ')}${chalk.bold(formatted)}`;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// Cleanup & Shutdown
|
|
543
|
+
|
|
544
|
+
#cleanupSocket() {
|
|
545
|
+
if (this.#plugins && !this.#plugins.destroyed) {
|
|
546
|
+
this.#plugins.destroy();
|
|
547
|
+
this.#plugins = null;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
if (!this.sock) return;
|
|
551
|
+
|
|
552
|
+
try {
|
|
553
|
+
this.sock.ev.removeAllListeners();
|
|
554
|
+
} catch {
|
|
555
|
+
/* noop */
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
this.sock = null;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
#clearReconnectTimer() {
|
|
562
|
+
if (this.#reconnectTimer) {
|
|
563
|
+
clearTimeout(this.#reconnectTimer);
|
|
564
|
+
this.#reconnectTimer = null;
|
|
565
|
+
this.#cancelWait?.();
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
async disconnect() {
|
|
570
|
+
if (this.#isShuttingDown) return;
|
|
571
|
+
this.#isShuttingDown = true;
|
|
572
|
+
|
|
573
|
+
this.#clearReconnectTimer();
|
|
574
|
+
this.#resolvePending(null, new Error('Client disconnected'));
|
|
575
|
+
|
|
576
|
+
// Unregister from registry
|
|
577
|
+
this.#unregister();
|
|
578
|
+
|
|
579
|
+
this.#cleanupSocket();
|
|
580
|
+
this.#setState(ConnectionState.DISCONNECTED);
|
|
581
|
+
|
|
582
|
+
this.#isShuttingDown = false;
|
|
583
|
+
this.#hasConnectedOnce = false;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
async logout() {
|
|
587
|
+
try {
|
|
588
|
+
await this.sock?.logout();
|
|
589
|
+
await this.disconnect();
|
|
590
|
+
await this.session?.delete();
|
|
591
|
+
} catch (err) {
|
|
592
|
+
this.#logger.error(err, `[${this.#flag}] Logging out failed`);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
export const getClient = async (options) => {
|
|
598
|
+
const client = new Client(options);
|
|
599
|
+
await client.connect();
|
|
600
|
+
return client;
|
|
601
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { select, input } from "@inquirer/prompts";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { logger } from "#internals.js";
|
|
4
|
+
import { pauseSpinner } from "#utils.js";
|
|
5
|
+
|
|
6
|
+
const connectionConfig = async () => {
|
|
7
|
+
try {
|
|
8
|
+
process.stdout.write("\n");
|
|
9
|
+
const type = await select({
|
|
10
|
+
message: "How would you like to connect?",
|
|
11
|
+
choices: [
|
|
12
|
+
{
|
|
13
|
+
name: " ⛶ QR Code",
|
|
14
|
+
value: "qr",
|
|
15
|
+
description: "Generate a secure code to scan"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: " 🔑 Phone Number",
|
|
19
|
+
value: "pn",
|
|
20
|
+
description: "Receive a One Time Password"
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
if (type === "pn") {
|
|
26
|
+
console.log(chalk.yellow("\n 🔑 Phone Number Selected\n"));
|
|
27
|
+
|
|
28
|
+
const pn = await input({
|
|
29
|
+
message: "Enter your phone number:",
|
|
30
|
+
validate: (value) => {
|
|
31
|
+
const digitsOnly = /^\d+$/.test(value);
|
|
32
|
+
if (!digitsOnly) return "Digits only (+1 (234) 567-8901 → 12345678901)";
|
|
33
|
+
const correctLength = /^.{7,15}$/.test(value);
|
|
34
|
+
if (!correctLength) return "Phone number length should be from 7 to 15 digits";
|
|
35
|
+
return true;
|
|
36
|
+
},
|
|
37
|
+
transformer: (value) => chalk.cyan(value)
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
return { type, pn }
|
|
41
|
+
} else {
|
|
42
|
+
logger.prompt(chalk.cyan("\n ⛶ QR Code Selected\n"));
|
|
43
|
+
return { type };
|
|
44
|
+
}
|
|
45
|
+
} catch {
|
|
46
|
+
logger.prompt(chalk.red("\nOperation cancelled"));
|
|
47
|
+
process.exit(0);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const getConnectionConfig = () => pauseSpinner(connectionConfig);
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Browsers } from "baileys";
|
|
2
|
+
import pino from "pino";
|
|
3
|
+
|
|
4
|
+
const socket = {
|
|
5
|
+
browser: Browsers.ubuntu('Chrome'),
|
|
6
|
+
markOnlineOnConnect: false,
|
|
7
|
+
syncFullHistory: false,
|
|
8
|
+
generateHighQualityLinkPreview: true,
|
|
9
|
+
shouldIgnoreJid: () => false,
|
|
10
|
+
shouldSyncHistoryMessage: () => false,
|
|
11
|
+
logger: pino({ level: "silent" }),
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const db = {
|
|
15
|
+
path: './db',
|
|
16
|
+
compression: true,
|
|
17
|
+
mapSize: 2 * 1024 * 1024 * 1024, // 2 GB
|
|
18
|
+
maxReaders: 126,
|
|
19
|
+
noSync: false,
|
|
20
|
+
noMetaSync: false,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const plugins = {
|
|
24
|
+
dir: 'plugins',
|
|
25
|
+
defaultEvent: 'messages.upsert',
|
|
26
|
+
hmr: {
|
|
27
|
+
enable: false,
|
|
28
|
+
debounce: 200,
|
|
29
|
+
debug: false
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export default {
|
|
34
|
+
db,
|
|
35
|
+
socket,
|
|
36
|
+
plugins,
|
|
37
|
+
timeZone: 'Africa/Casablanca',
|
|
38
|
+
prefixes: ['!', '.']
|
|
39
|
+
}
|