@fer2809fl/baileys 7.0.4 → 7.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.
Files changed (51) hide show
  1. package/README.md +347 -50
  2. package/lib/Defaults/index.js +1 -1
  3. package/lib/Modded/message_builder.js +2356 -0
  4. package/lib/Socket/chats.d.ts +14 -0
  5. package/lib/Socket/chats.js +48 -0
  6. package/lib/Socket/index.d.ts +17 -0
  7. package/lib/Socket/messages-send.d.ts +18 -0
  8. package/lib/Socket/messages-send.js +50 -2
  9. package/lib/Utils/anti-ban.d.ts +41 -0
  10. package/lib/Utils/anti-ban.js +182 -0
  11. package/lib/Utils/banner.d.ts +8 -0
  12. package/lib/Utils/banner.js +76 -0
  13. package/lib/Utils/bot-utils.d.ts +57 -0
  14. package/lib/Utils/bot-utils.js +241 -0
  15. package/lib/Utils/enhanced-cache.d.ts +40 -0
  16. package/lib/Utils/enhanced-cache.js +242 -0
  17. package/lib/Utils/enhanced-logger.d.ts +41 -0
  18. package/lib/Utils/enhanced-logger.js +185 -0
  19. package/lib/Utils/index.d.ts +14 -0
  20. package/lib/Utils/index.js +13 -0
  21. package/lib/Utils/lid-utils.d.ts +139 -0
  22. package/lib/Utils/lid-utils.js +503 -0
  23. package/lib/Utils/message-queue.d.ts +47 -0
  24. package/lib/Utils/message-queue.js +226 -0
  25. package/lib/Utils/rich-message-utils.d.ts +21 -0
  26. package/lib/Utils/rich-message-utils.js +229 -0
  27. package/lib/Utils/rich-messages.d.ts +52 -0
  28. package/lib/Utils/rich-messages.js +185 -0
  29. package/lib/Utils/scheduled-messages.d.ts +122 -0
  30. package/lib/Utils/scheduled-messages.js +289 -0
  31. package/lib/Utils/smart-reconnect.d.ts +48 -0
  32. package/lib/Utils/smart-reconnect.js +207 -0
  33. package/lib/Utils/use-sqlite-auth-state.d.ts +11 -0
  34. package/lib/Utils/use-sqlite-auth-state.js +95 -0
  35. package/lib/VoIP/audio-feeder.d.ts +15 -0
  36. package/lib/VoIP/audio-feeder.js +132 -0
  37. package/lib/VoIP/index.js +277 -0
  38. package/lib/VoIP/relay-transport.d.ts +43 -0
  39. package/lib/VoIP/relay-transport.js +559 -0
  40. package/lib/VoIP/signaling.js +594 -0
  41. package/lib/VoIP/types.d.ts +69 -0
  42. package/lib/VoIP/types.js +17 -0
  43. package/lib/VoIP/wasm-engine.d.ts +103 -0
  44. package/lib/VoIP/wasm-engine.js +1214 -0
  45. package/lib/VoIP/worker-bootstrap.js +1042 -0
  46. package/lib/assets/wasm/loader.js +5 -0
  47. package/lib/assets/wasm/whatsapp.wasm +0 -0
  48. package/lib/assets/wasm/worker-modules.js +273 -0
  49. package/lib/index.d.ts +41 -0
  50. package/lib/index.js +3 -0
  51. package/package.json +10 -2
@@ -0,0 +1,207 @@
1
+ import { DisconnectReason } from '../Types/index.js';
2
+
3
+ const RECONNECT_CONFIG = {
4
+ MAX_RETRIES: 15,
5
+ INITIAL_DELAY: 1000,
6
+ MAX_DELAY: 300000,
7
+ BACKOFF_FACTOR: 1.5,
8
+ JITTER_FACTOR: 0.3,
9
+ STABLE_CONNECTION_TIME: 60000,
10
+ RECOVERABLE_REASONS: [
11
+ DisconnectReason.connectionClosed,
12
+ DisconnectReason.connectionLost,
13
+ DisconnectReason.connectionReplaced,
14
+ DisconnectReason.timedOut,
15
+ DisconnectReason.restartRequired
16
+ ],
17
+ FATAL_REASONS: [
18
+ DisconnectReason.loggedOut,
19
+ DisconnectReason.badSession,
20
+ DisconnectReason.multideviceMismatch
21
+ ]
22
+ };
23
+
24
+ class SmartReconnect {
25
+ constructor(logger, config = {}) {
26
+ this.logger = logger;
27
+ this.config = { ...RECONNECT_CONFIG, ...config };
28
+ this.retryCount = 0;
29
+ this.lastConnectTime = null;
30
+ this.lastDisconnectReason = null;
31
+ this.connectionStable = false;
32
+ this.reconnectTimer = null;
33
+ this.healthCheckInterval = null;
34
+ this.stats = {
35
+ totalReconnects: 0,
36
+ successfulReconnects: 0,
37
+ failedReconnects: 0,
38
+ lastSuccessfulConnect: null,
39
+ averageDowntime: 0
40
+ };
41
+ }
42
+
43
+ calculateDelay() {
44
+ const baseDelay = this.config.INITIAL_DELAY * Math.pow(this.config.BACKOFF_FACTOR, this.retryCount);
45
+ const cappedDelay = Math.min(baseDelay, this.config.MAX_DELAY);
46
+ const jitter = cappedDelay * this.config.JITTER_FACTOR * (Math.random() - 0.5);
47
+ return Math.floor(cappedDelay + jitter);
48
+ }
49
+
50
+ shouldReconnect(reason, statusCode) {
51
+ if (this.config.FATAL_REASONS.includes(statusCode)) {
52
+ this.logger?.warn({ reason, statusCode }, 'Fatal disconnect reason - not reconnecting');
53
+ return { shouldReconnect: false, reason: 'Fatal error - session invalidated' };
54
+ }
55
+ if (this.retryCount >= this.config.MAX_RETRIES) {
56
+ this.logger?.error({ retryCount: this.retryCount }, 'Max retries exceeded');
57
+ return { shouldReconnect: false, reason: 'Max retries exceeded' };
58
+ }
59
+ if (this.config.RECOVERABLE_REASONS.includes(statusCode)) {
60
+ return { shouldReconnect: true, delay: this.calculateDelay() };
61
+ }
62
+ return { shouldReconnect: true, delay: this.calculateDelay() * 2 };
63
+ }
64
+
65
+ async handleDisconnect(reason, statusCode, reconnectCallback) {
66
+ this.lastDisconnectReason = { reason, statusCode, time: Date.now() };
67
+ this.connectionStable = false;
68
+ const decision = this.shouldReconnect(reason, statusCode);
69
+ if (!decision.shouldReconnect) {
70
+ this.logger?.info({ reason: decision.reason }, 'Not attempting reconnection');
71
+ return { reconnecting: false, reason: decision.reason };
72
+ }
73
+ this.retryCount++;
74
+ this.stats.totalReconnects++;
75
+ const delay = decision.delay;
76
+ this.logger?.info({ attempt: this.retryCount, maxRetries: this.config.MAX_RETRIES, delay, reason }, 'Scheduling reconnection');
77
+ return new Promise((resolve) => {
78
+ this.reconnectTimer = setTimeout(async () => {
79
+ try {
80
+ await reconnectCallback();
81
+ this.onSuccessfulReconnect();
82
+ resolve({ reconnecting: true, success: true });
83
+ } catch (error) {
84
+ this.stats.failedReconnects++;
85
+ this.logger?.error({ error }, 'Reconnection failed');
86
+ resolve({ reconnecting: true, success: false, error });
87
+ }
88
+ }, delay);
89
+ });
90
+ }
91
+
92
+ onSuccessfulReconnect() {
93
+ const previousRetries = this.retryCount;
94
+ this.retryCount = 0;
95
+ this.lastConnectTime = Date.now();
96
+ this.stats.successfulReconnects++;
97
+ this.stats.lastSuccessfulConnect = new Date().toISOString();
98
+ this.logger?.info({ previousRetries }, 'Successfully reconnected');
99
+ setTimeout(() => {
100
+ if (this.lastConnectTime && Date.now() - this.lastConnectTime >= this.config.STABLE_CONNECTION_TIME) {
101
+ this.connectionStable = true;
102
+ this.logger?.debug('Connection marked as stable');
103
+ }
104
+ }, this.config.STABLE_CONNECTION_TIME);
105
+ }
106
+
107
+ cancelPendingReconnect() {
108
+ if (this.reconnectTimer) {
109
+ clearTimeout(this.reconnectTimer);
110
+ this.reconnectTimer = null;
111
+ }
112
+ }
113
+
114
+ reset() {
115
+ this.retryCount = 0;
116
+ this.lastDisconnectReason = null;
117
+ this.cancelPendingReconnect();
118
+ }
119
+
120
+ getStats() {
121
+ return {
122
+ ...this.stats,
123
+ currentRetryCount: this.retryCount,
124
+ isStable: this.connectionStable,
125
+ lastDisconnect: this.lastDisconnectReason
126
+ };
127
+ }
128
+
129
+ startHealthCheck(pingCallback, interval = 30000) {
130
+ this.stopHealthCheck();
131
+ this.healthCheckInterval = setInterval(async () => {
132
+ try {
133
+ await pingCallback();
134
+ this.logger?.debug('Health check passed');
135
+ } catch (error) {
136
+ this.logger?.warn({ error }, 'Health check failed');
137
+ }
138
+ }, interval);
139
+ }
140
+
141
+ stopHealthCheck() {
142
+ if (this.healthCheckInterval) {
143
+ clearInterval(this.healthCheckInterval);
144
+ this.healthCheckInterval = null;
145
+ }
146
+ }
147
+
148
+ cleanup() {
149
+ this.cancelPendingReconnect();
150
+ this.stopHealthCheck();
151
+ }
152
+ }
153
+
154
+ function createConnectionHandler(sock, logger, options = {}) {
155
+ const reconnect = new SmartReconnect(logger, options);
156
+ return {
157
+ reconnect,
158
+ async handleConnectionUpdate(update, startSock) {
159
+ const { connection, lastDisconnect, qr } = update;
160
+ if (connection === 'close') {
161
+ const statusCode = lastDisconnect?.error?.output?.statusCode;
162
+ const reason = lastDisconnect?.error?.message || 'Unknown';
163
+ const result = await reconnect.handleDisconnect(reason, statusCode, startSock);
164
+ return result;
165
+ }
166
+ if (connection === 'open') {
167
+ reconnect.onSuccessfulReconnect();
168
+ return { connected: true };
169
+ }
170
+ if (qr) {
171
+ reconnect.reset();
172
+ return { qr };
173
+ }
174
+ return update;
175
+ },
176
+ getStatus() {
177
+ return {
178
+ stats: reconnect.getStats(),
179
+ isHealthy: reconnect.connectionStable && reconnect.retryCount === 0
180
+ };
181
+ }
182
+ };
183
+ }
184
+
185
+ async function withRetry(fn, options = {}) {
186
+ const { maxRetries = 3, delay = 1000, backoff = 2, onRetry = null } = options;
187
+ let lastError;
188
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
189
+ try {
190
+ return await fn();
191
+ } catch (error) {
192
+ lastError = error;
193
+ if (attempt === maxRetries) throw error;
194
+ const waitTime = delay * Math.pow(backoff, attempt);
195
+ if (onRetry) onRetry(error, attempt + 1, waitTime);
196
+ await new Promise(resolve => setTimeout(resolve, waitTime));
197
+ }
198
+ }
199
+ throw lastError;
200
+ }
201
+
202
+ export {
203
+ RECONNECT_CONFIG,
204
+ SmartReconnect,
205
+ createConnectionHandler,
206
+ withRetry
207
+ };
@@ -0,0 +1,11 @@
1
+ import type { AuthenticationState } from "../Types/index.js";
2
+
3
+ export declare function useSqliteAuthState(opts: {
4
+ dbPath?: string;
5
+ database?: any;
6
+ }): Promise<{
7
+ state: AuthenticationState;
8
+ saveCreds: () => Promise<void>;
9
+ clearKeys: () => Promise<void>;
10
+ close: () => void;
11
+ }>;
@@ -0,0 +1,95 @@
1
+ import { proto } from '../../WAProto/index.js';
2
+ import { initAuthCreds } from './auth-utils.js';
3
+ import { BufferJSON } from './generics.js';
4
+
5
+ const CREDS_ROW_KEY = '__creds__';
6
+
7
+ const CREATE_SCHEMA_SQL = `
8
+ CREATE TABLE IF NOT EXISTS creds (
9
+ key TEXT PRIMARY KEY,
10
+ value TEXT NOT NULL
11
+ );
12
+ CREATE TABLE IF NOT EXISTS signal_keys (
13
+ type TEXT NOT NULL,
14
+ id TEXT NOT NULL,
15
+ value TEXT NOT NULL,
16
+ PRIMARY KEY (type, id)
17
+ );
18
+ CREATE INDEX IF NOT EXISTS signal_keys_type_idx ON signal_keys(type);
19
+ `;
20
+
21
+ async function useSqliteAuthState(opts) {
22
+ let Database;
23
+ try {
24
+ Database = (await import('better-sqlite3')).default;
25
+ } catch (err) {
26
+ throw new Error('`better-sqlite3` is required for `useSqliteAuthState`. Install with: npm install better-sqlite3');
27
+ }
28
+
29
+ const db = opts.database || new Database(opts.dbPath);
30
+ db.pragma('journal_mode = WAL');
31
+ db.pragma('synchronous = NORMAL');
32
+ db.exec(CREATE_SCHEMA_SQL);
33
+
34
+ const stmts = {
35
+ credsSelect: db.prepare('SELECT value FROM creds WHERE key = ?'),
36
+ credsUpsert: db.prepare('INSERT INTO creds (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value'),
37
+ keySelect: db.prepare('SELECT value FROM signal_keys WHERE type = ? AND id = ?'),
38
+ keyUpsert: db.prepare('INSERT INTO signal_keys (type, id, value) VALUES (?, ?, ?) ON CONFLICT(type, id) DO UPDATE SET value = excluded.value'),
39
+ keyDelete: db.prepare('DELETE FROM signal_keys WHERE type = ? AND id = ?'),
40
+ keyListIds: db.prepare('SELECT id FROM signal_keys WHERE type = ?'),
41
+ keyList: db.prepare('SELECT id, value FROM signal_keys WHERE type = ?'),
42
+ clearKeys: db.prepare('DELETE FROM signal_keys')
43
+ };
44
+
45
+ const loadCreds = () => {
46
+ const row = stmts.credsSelect.get(CREDS_ROW_KEY);
47
+ if (!row) return initAuthCreds();
48
+ return JSON.parse(row.value, BufferJSON.reviver);
49
+ };
50
+
51
+ const persistCreds = creds => {
52
+ stmts.credsUpsert.run(CREDS_ROW_KEY, JSON.stringify(creds, BufferJSON.replacer));
53
+ };
54
+
55
+ const creds = loadCreds();
56
+
57
+ return {
58
+ state: {
59
+ creds,
60
+ keys: {
61
+ get: async (type, ids) => {
62
+ const data = {};
63
+ for (const id of ids) {
64
+ const row = stmts.keySelect.get(type, id);
65
+ if (row) {
66
+ let value = JSON.parse(row.value, BufferJSON.reviver);
67
+ if (type === 'app-state-sync-key' && value) {
68
+ value = proto.Message.AppStateSyncKeyData.fromObject(value);
69
+ }
70
+ data[id] = value;
71
+ }
72
+ }
73
+ return data;
74
+ },
75
+ set: async data => {
76
+ const writeTx = db.transaction(() => {
77
+ for (const category in data) {
78
+ for (const id in data[category]) {
79
+ const value = data[category][id];
80
+ if (value) stmts.keyUpsert.run(category, id, JSON.stringify(value, BufferJSON.replacer));
81
+ else stmts.keyDelete.run(category, id);
82
+ }
83
+ }
84
+ });
85
+ writeTx();
86
+ }
87
+ }
88
+ },
89
+ saveCreds: async () => { persistCreds(creds); },
90
+ clearKeys: async () => { stmts.clearKeys.run(); },
91
+ close: () => { db.close(); }
92
+ };
93
+ }
94
+
95
+ export { useSqliteAuthState };
@@ -0,0 +1,15 @@
1
+ export declare class AudioFeeder {
2
+ #private;
3
+ private readonly sampleRate;
4
+ private readonly channels;
5
+ private readonly framesPerChunk;
6
+ private readonly onChunk;
7
+ private readonly source;
8
+ droppedChunks: number;
9
+ underflowChunks: number;
10
+ bytesProduced: number;
11
+ chunksEmitted: number;
12
+ constructor(sampleRate: number, channels: number, framesPerChunk: number, onChunk: (chunk: Float32Array) => void, source?: string);
13
+ start: () => void;
14
+ stop: () => void;
15
+ }
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Audio feeder.
3
+ *
4
+ * Spawns ffmpeg to decode `source` into f32le PCM at the requested rate, then
5
+ * meters frames out at chunk-cadence to the WASM uplink.
6
+ *
7
+ * @author ShellTear
8
+ */
9
+ import { spawn } from "node:child_process";
10
+ const LOW_WATERMARK_CHUNKS = 16;
11
+ const MAX_QUEUED_CHUNKS = 1024;
12
+ const DEFAULT_WARMUP_MS = 500;
13
+ export class AudioFeeder {
14
+ sampleRate;
15
+ channels;
16
+ framesPerChunk;
17
+ onChunk;
18
+ source;
19
+ #proc = null;
20
+ #pending = Buffer.alloc(0);
21
+ #queue = [];
22
+ #emitTimer = null;
23
+ #nextEmitAtMs = 0;
24
+ #warmupUntilMs = 0;
25
+ droppedChunks = 0;
26
+ underflowChunks = 0;
27
+ bytesProduced = 0;
28
+ chunksEmitted = 0;
29
+ constructor(sampleRate, channels, framesPerChunk, onChunk, source = "silence") {
30
+ this.sampleRate = sampleRate;
31
+ this.channels = channels;
32
+ this.framesPerChunk = framesPerChunk;
33
+ this.onChunk = onChunk;
34
+ this.source = source;
35
+ }
36
+ start = () => {
37
+ if (this.#proc)
38
+ return;
39
+ const chunkSamples = this.framesPerChunk * this.channels;
40
+ const chunkBytes = chunkSamples * Float32Array.BYTES_PER_ELEMENT;
41
+ const chunkIntervalMs = (this.framesPerChunk / this.sampleRate) * 1000;
42
+ const inputArgs = this.#resolveInputArgs();
43
+ this.#proc = spawn("ffmpeg", [
44
+ "-hide_banner",
45
+ "-loglevel", "error",
46
+ "-thread_queue_size", "512",
47
+ ...inputArgs,
48
+ "-f", "f32le",
49
+ "-ac", String(this.channels),
50
+ "-ar", String(this.sampleRate),
51
+ "pipe:1",
52
+ ]);
53
+ this.#proc.stdout.on("data", (chunk) => {
54
+ this.#pending = Buffer.concat([this.#pending, chunk]);
55
+ while (this.#pending.length >= chunkBytes) {
56
+ if (this.#queue.length >= MAX_QUEUED_CHUNKS) {
57
+ this.#proc?.stdout.pause();
58
+ break;
59
+ }
60
+ const frame = this.#pending.subarray(0, chunkBytes);
61
+ this.#pending = this.#pending.subarray(chunkBytes);
62
+ const out = new Float32Array(chunkSamples);
63
+ out.set(new Float32Array(frame.buffer, frame.byteOffset, chunkSamples));
64
+ this.bytesProduced += chunkBytes;
65
+ this.#queue.push(out);
66
+ }
67
+ });
68
+ this.#proc.stderr.on("data", (chunk) => {
69
+ process.stderr.write(`[AudioFeeder] ${chunk.toString().trim()}\n`);
70
+ });
71
+ this.#proc.on("exit", (code) => {
72
+ if (code !== 0 && code !== null) {
73
+ process.stderr.write(`[AudioFeeder] ffmpeg exited with code=${code}\n`);
74
+ }
75
+ this.#proc = null;
76
+ });
77
+ this.#nextEmitAtMs = 0;
78
+ this.#warmupUntilMs = Date.now() + DEFAULT_WARMUP_MS;
79
+ this.#scheduleNext(chunkSamples, chunkIntervalMs);
80
+ };
81
+ stop = () => {
82
+ if (this.#emitTimer) {
83
+ clearTimeout(this.#emitTimer);
84
+ this.#emitTimer = null;
85
+ }
86
+ this.#proc?.kill("SIGTERM");
87
+ this.#proc = null;
88
+ this.#pending = Buffer.alloc(0);
89
+ this.#queue = [];
90
+ this.#warmupUntilMs = 0;
91
+ };
92
+ #resolveInputArgs = () => {
93
+ if (!this.source || this.source === "silence") {
94
+ return ["-f", "lavfi", "-i", `aevalsrc=0:d=3600:s=${this.sampleRate}`];
95
+ }
96
+ if (this.source.startsWith("lavfi:")) {
97
+ return ["-f", "lavfi", "-i", this.source.slice("lavfi:".length)];
98
+ }
99
+ return ["-i", this.source];
100
+ };
101
+ #scheduleNext = (chunkSamples, chunkIntervalMs) => {
102
+ if (!this.#proc)
103
+ return;
104
+ const now = Date.now();
105
+ if (this.#nextEmitAtMs === 0)
106
+ this.#nextEmitAtMs = now;
107
+ const delayMs = Math.max(0, this.#nextEmitAtMs - now);
108
+ this.#emitTimer = setTimeout(() => {
109
+ this.#emitTimer = null;
110
+ if (this.#queue.length < LOW_WATERMARK_CHUNKS && Date.now() < this.#warmupUntilMs) {
111
+ this.#nextEmitAtMs = Date.now() + 10;
112
+ this.#scheduleNext(chunkSamples, chunkIntervalMs);
113
+ return;
114
+ }
115
+ this.#flushOne(chunkSamples);
116
+ this.#nextEmitAtMs += chunkIntervalMs;
117
+ this.#scheduleNext(chunkSamples, chunkIntervalMs);
118
+ }, delayMs);
119
+ };
120
+ #flushOne = (chunkSamples) => {
121
+ let nextChunk = this.#queue.shift();
122
+ if (!nextChunk) {
123
+ nextChunk = new Float32Array(chunkSamples);
124
+ this.underflowChunks += 1;
125
+ }
126
+ this.chunksEmitted += 1;
127
+ this.onChunk(nextChunk);
128
+ if (this.#proc?.stdout.isPaused() && this.#queue.length <= MAX_QUEUED_CHUNKS / 4) {
129
+ this.#proc.stdout.resume();
130
+ }
131
+ };
132
+ }