@ryuu-reinzz/haruka-lib 3.3.4 → 3.4.0-beta.1

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/main/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import addProperty from "./socket.js";
2
- import useSQLiteAuthState from "./sqliteAuth.js";
2
+ import useSQLiteAuthState from "./sqlite3-auth/auth.js";
3
3
  import Sticker from './sticker-engine/index.js';
4
+ import downloader from './downloader.js';
4
5
  import {
5
6
  stickerVid
6
7
  } from './sticker-engine/video-to-webp.js';
@@ -13,7 +14,8 @@ import {
13
14
 
14
15
  const haruka = {
15
16
  addProperty,
16
- useSQLiteAuthState
17
+ useSQLiteAuthState,
18
+ downloader
17
19
  };
18
20
 
19
21
  export default haruka;
@@ -0,0 +1,342 @@
1
+ /**
2
+ * @file SQLite-based authentication state management for Baileys
3
+ * @module auth/sqlite-auth
4
+ * @description Production-grade authentication state persistence with transaction support,
5
+ * connection pooling, and robust error handling for WhatsApp Web sessions.
6
+ * @author RyuuReinzz
7
+ */
8
+
9
+ import {
10
+ AsyncLocalStorage
11
+ } from "async_hooks";
12
+ import {
13
+ Mutex
14
+ } from "async-mutex";
15
+ import PQueue from "p-queue";
16
+ import getAuthDatabase from "./core.js";
17
+ import {
18
+ makeKey,
19
+ validateKey,
20
+ validateValue
21
+ } from "./config.js";
22
+
23
+ const DEFAULT_TRANSACTION_OPTIONS = {
24
+ maxCommitRetries: 5,
25
+ delayBetweenTriesMs: 200,
26
+ transactionTimeout: 30000,
27
+ };
28
+
29
+ function delay(ms) {
30
+ return new Promise((resolve) => setTimeout(resolve, ms));
31
+ }
32
+
33
+ function exponentialBackoff(attempt, baseDelay) {
34
+ const exponentialDelay = baseDelay * Math.pow(2, attempt);
35
+ const jitter = Math.random() * baseDelay;
36
+ return Math.min(exponentialDelay + jitter, 10000);
37
+ }
38
+
39
+ export default function useSQLiteAuthState(_dbPath, options = {}) {
40
+ const txOptions = {
41
+ ...DEFAULT_TRANSACTION_OPTIONS,
42
+ ...options
43
+ };
44
+ const {
45
+ initAuthCreds
46
+ } = options;
47
+
48
+ let creds;
49
+ const db = getAuthDatabase(_dbPath, options);
50
+
51
+ try {
52
+ const row = db.get("creds");
53
+ if (row?.value) {
54
+ creds = row.value;
55
+ if (!creds || typeof creds !== "object") {
56
+ creds = initAuthCreds();
57
+ }
58
+ } else {
59
+ creds = initAuthCreds();
60
+ }
61
+ } catch {
62
+ creds = initAuthCreds();
63
+ }
64
+
65
+ const txStorage = new AsyncLocalStorage();
66
+ const keyQueues = new Map();
67
+ const txMutexes = new Map();
68
+
69
+ function getQueue(key) {
70
+ if (!keyQueues.has(key)) {
71
+ keyQueues.set(key, new PQueue({
72
+ concurrency: 1
73
+ }));
74
+ }
75
+ return keyQueues.get(key);
76
+ }
77
+
78
+ function getTxMutex(key) {
79
+ if (!txMutexes.has(key)) {
80
+ txMutexes.set(key, new Mutex());
81
+ }
82
+ return txMutexes.get(key);
83
+ }
84
+
85
+ function isInTransaction() {
86
+ return !!txStorage.getStore();
87
+ }
88
+
89
+ async function commitWithRetry(mutations) {
90
+ if (Object.keys(mutations).length === 0) {
91
+ return;
92
+ }
93
+
94
+ for (let attempt = 0; attempt < txOptions.maxCommitRetries; attempt++) {
95
+ try {
96
+ for (const type in mutations) {
97
+ const bucket = mutations[type];
98
+ for (const id in bucket) {
99
+ const k = makeKey(type, id);
100
+ const v = bucket[id];
101
+
102
+ if (!validateKey(k)) continue;
103
+
104
+ if (v === null || v === undefined) {
105
+ db.del(k);
106
+ } else {
107
+ db.set(k, v);
108
+ }
109
+ }
110
+ }
111
+
112
+ return;
113
+ } catch (error) {
114
+ const retriesLeft = txOptions.maxCommitRetries - attempt - 1;
115
+
116
+ if (retriesLeft === 0) {
117
+ throw error;
118
+ }
119
+
120
+ await delay(exponentialBackoff(attempt, txOptions.delayBetweenTriesMs));
121
+ }
122
+ }
123
+ }
124
+
125
+ async function keysGet(type, ids) {
126
+ if (!type || !Array.isArray(ids)) {
127
+ return {};
128
+ }
129
+
130
+ const ctx = txStorage.getStore();
131
+
132
+ if (!ctx) {
133
+ const result = {};
134
+
135
+ for (const id of ids) {
136
+ const k = makeKey(type, id);
137
+ if (!validateKey(k)) continue;
138
+
139
+ try {
140
+ const row = db.get(k);
141
+ if (row?.value) {
142
+ result[id] = row.value;
143
+ }
144
+ } catch {
145
+ // Silent fail, continue processing
146
+ }
147
+ }
148
+
149
+ return result;
150
+ }
151
+
152
+ const cached = ctx.cache[type] || {};
153
+ const missing = ids.filter((id) => !(id in cached));
154
+
155
+ if (missing.length > 0) {
156
+ ctx.dbQueries++;
157
+
158
+ const fetched = await getTxMutex(type).runExclusive(async () => {
159
+ const result = {};
160
+
161
+ for (const id of missing) {
162
+ const k = makeKey(type, id);
163
+ if (!validateKey(k)) continue;
164
+
165
+ try {
166
+ const row = db.get(k);
167
+ if (row?.value) {
168
+ result[id] = row.value;
169
+ }
170
+ } catch {
171
+ // Silent fail
172
+ }
173
+ }
174
+
175
+ return result;
176
+ });
177
+
178
+ ctx.cache[type] = ctx.cache[type] || {};
179
+ Object.assign(ctx.cache[type], fetched);
180
+ }
181
+
182
+ const result = {};
183
+ for (const id of ids) {
184
+ const value = ctx.cache[type]?.[id];
185
+ if (value !== undefined && value !== null) {
186
+ result[id] = value;
187
+ }
188
+ }
189
+
190
+ return result;
191
+ }
192
+
193
+ async function keysSet(data) {
194
+ if (!data || typeof data !== "object") {
195
+ return;
196
+ }
197
+
198
+ const ctx = txStorage.getStore();
199
+
200
+ if (!ctx) {
201
+ const types = Object.keys(data);
202
+
203
+ await Promise.all(
204
+ types.map((type) =>
205
+ getQueue(type).add(async () => {
206
+ const bucket = data[type];
207
+
208
+ for (const id in bucket) {
209
+ try {
210
+ const k = makeKey(type, id);
211
+ const v = bucket[id];
212
+
213
+ if (!validateKey(k)) continue;
214
+ if (!validateValue(v)) continue;
215
+
216
+ if (v === null || v === undefined) {
217
+ db.del(k);
218
+ } else {
219
+ db.set(k, v);
220
+ }
221
+ } catch {
222
+ // Silent fail
223
+ }
224
+ }
225
+ })
226
+ )
227
+ );
228
+
229
+ return;
230
+ }
231
+
232
+ for (const type in data) {
233
+ const bucket = data[type];
234
+
235
+ ctx.cache[type] = ctx.cache[type] || {};
236
+ ctx.mutations[type] = ctx.mutations[type] || {};
237
+
238
+ Object.assign(ctx.cache[type], bucket);
239
+ Object.assign(ctx.mutations[type], bucket);
240
+ }
241
+ }
242
+
243
+ async function keysClear() {
244
+ try {
245
+ db.db.exec("DELETE FROM baileys_state WHERE key LIKE '%-%'");
246
+ db.db.exec("PRAGMA wal_checkpoint(PASSIVE)");
247
+ db.cache.clear();
248
+ } catch {
249
+ // Silent fail
250
+ }
251
+ }
252
+
253
+ async function transaction(work, key = "default") {
254
+ if (typeof work !== "function") {
255
+ return null;
256
+ }
257
+
258
+ const existing = txStorage.getStore();
259
+
260
+ if (existing) {
261
+ return work();
262
+ }
263
+
264
+ const timeoutPromise = new Promise((_, reject) =>
265
+ setTimeout(() => reject(new Error("Transaction timeout")), txOptions.transactionTimeout)
266
+ );
267
+
268
+ const txPromise = getTxMutex(key).runExclusive(async () => {
269
+ const ctx = {
270
+ cache: {},
271
+ mutations: {},
272
+ dbQueries: 0,
273
+ };
274
+
275
+ const result = await txStorage.run(ctx, work);
276
+ await commitWithRetry(ctx.mutations);
277
+ return result;
278
+ });
279
+
280
+ try {
281
+ return await Promise.race([txPromise, timeoutPromise]);
282
+ } catch {
283
+ return null;
284
+ }
285
+ }
286
+
287
+ function saveCreds() {
288
+ try {
289
+ if (!creds || typeof creds !== "object") {
290
+ return false;
291
+ }
292
+
293
+ db.set("creds", creds);
294
+ return true;
295
+ } catch {
296
+ return false;
297
+ }
298
+ }
299
+
300
+ const keys = {
301
+ get: keysGet,
302
+ set: keysSet,
303
+ clear: keysClear,
304
+ };
305
+
306
+ return {
307
+ state: {
308
+ creds,
309
+ keys
310
+ },
311
+ saveCreds,
312
+ transaction,
313
+ isInTransaction,
314
+ _flushNow: async () => {
315
+ try {
316
+ await db.flush();
317
+ } catch {
318
+ // Silent fail
319
+ }
320
+ },
321
+ _forceVacuum: async () => {
322
+ try {
323
+ await db.forceVacuum();
324
+ } catch {
325
+ // Silent fail
326
+ }
327
+ },
328
+ _dispose: async () => {
329
+ try {
330
+ await db.flush();
331
+ keyQueues.clear();
332
+ txMutexes.clear();
333
+ } catch {
334
+ // Silent fail
335
+ }
336
+ },
337
+ db: db.db,
338
+ get closed() {
339
+ return db.disposed;
340
+ },
341
+ };
342
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * @file Signal handler and database utilities for Liora bot
3
+ * @module core/utils
4
+ * @description Provides signal/process management, database configuration,
5
+ * and validation utilities for graceful shutdown and resource management.
6
+ * @author RyuuReinzz
7
+ */
8
+
9
+ import path from "path";
10
+
11
+ export const DEFAULT_DB = path.join(process.cwd(), "session", "auth.db");
12
+
13
+ export const makeKey = (type, id) => `${type}-${id}`;
14
+
15
+ export function validateKey(key) {
16
+ return typeof key === "string" && key.length > 0 && key.length < 512;
17
+ }
18
+
19
+ export function validateValue(value) {
20
+ return value !== undefined;
21
+ }
22
+
23
+ const signalHandlers = new Map();
24
+ let signalHandlersInitialized = false;
25
+ let isExiting = false;
26
+ let exitTimeout = null;
27
+
28
+ function exitHandler() {
29
+ if (isExiting) return;
30
+ isExiting = true;
31
+
32
+ for (const [, handler] of signalHandlers) {
33
+ try {
34
+ handler();
35
+ } catch {
36
+ // Silent fail
37
+ }
38
+ }
39
+ }
40
+
41
+ function fullExitHandler(signal) {
42
+ exitHandler();
43
+ const code = signal === "SIGINT" ? 130 : 143;
44
+
45
+ if (exitTimeout) {
46
+ clearTimeout(exitTimeout);
47
+ }
48
+
49
+ exitTimeout = setTimeout(() => process.exit(code), 500);
50
+ exitTimeout.unref?.();
51
+ }
52
+
53
+ export function initializeSignalHandlers() {
54
+ if (signalHandlersInitialized) return;
55
+ signalHandlersInitialized = true;
56
+
57
+ try {
58
+ process.once("exit", () => exitHandler());
59
+ process.once("SIGINT", () => fullExitHandler("SIGINT"));
60
+ process.once("SIGTERM", () => fullExitHandler("SIGTERM"));
61
+ process.on("uncaughtException", () => {
62
+ // Silent fail in production
63
+ });
64
+
65
+ process.on("unhandledRejection", () => {
66
+ // Silent fail in production
67
+ });
68
+ } catch {
69
+ // Silent fail
70
+ }
71
+ }
72
+
73
+ export function registerSignalHandler(id, handler) {
74
+ if (typeof handler !== "function" || !id) {
75
+ return false;
76
+ }
77
+ signalHandlers.set(id, handler);
78
+ return true;
79
+ }
80
+
81
+ export function unregisterSignalHandler(id) {
82
+ return signalHandlers.delete(id);
83
+ }
@@ -0,0 +1,393 @@
1
+ /**
2
+ * @file Baileys authentication database with caching and write buffering
3
+ * @module database/auth
4
+ * @description SQLite-based storage system for Baileys session management
5
+ * with memory caching, write buffering, and automatic vacuuming.
6
+ * @author RyuuReinzz
7
+ */
8
+
9
+ import {
10
+ Database
11
+ } from "better-sqlite3";
12
+ import {
13
+ Mutex
14
+ } from "async-mutex";
15
+ import {
16
+ randomUUID
17
+ } from "node:crypto";
18
+ import {
19
+ DEFAULT_DB,
20
+ validateKey,
21
+ validateValue,
22
+ initializeSignalHandlers,
23
+ registerSignalHandler,
24
+ } from "./config.js";
25
+
26
+ class LRUCache {
27
+ constructor(maxSize = 10000, BufferJSON) {
28
+ this.cache = new Map();
29
+ this.maxSize = maxSize;
30
+ this.BufferJSON = BufferJSON;
31
+ }
32
+
33
+ get(key) {
34
+ if (!this.cache.has(key)) return undefined;
35
+ const value = this.cache.get(key);
36
+ this.cache.delete(key);
37
+ this.cache.set(key, value);
38
+ return value;
39
+ }
40
+
41
+ set(key, value) {
42
+ if (this.cache.has(key)) {
43
+ this.cache.delete(key);
44
+ } else if (this.cache.size >= this.maxSize) {
45
+ const firstKey = this.cache.keys().next().value;
46
+ this.cache.delete(firstKey);
47
+ }
48
+ this.cache.set(key, value);
49
+ }
50
+
51
+ delete(key) {
52
+ return this.cache.delete(key);
53
+ }
54
+
55
+ has(key) {
56
+ return this.cache.has(key);
57
+ }
58
+
59
+ clear() {
60
+ this.cache.clear();
61
+ }
62
+
63
+ get size() {
64
+ return this.cache.size;
65
+ }
66
+ }
67
+
68
+ class WriteBuffer {
69
+ constructor() {
70
+ this.upserts = new Map();
71
+ this.deletes = new Set();
72
+ }
73
+
74
+ addUpsert(k, v) {
75
+ if (!validateKey(k)) return false;
76
+ this.upserts.set(k, v);
77
+ this.deletes.delete(k);
78
+ return true;
79
+ }
80
+
81
+ addDelete(k) {
82
+ if (!validateKey(k)) return false;
83
+ this.deletes.add(k);
84
+ this.upserts.delete(k);
85
+ return true;
86
+ }
87
+
88
+ clear() {
89
+ this.upserts.clear();
90
+ this.deletes.clear();
91
+ }
92
+
93
+ hasChanges() {
94
+ return this.upserts.size > 0 || this.deletes.size > 0;
95
+ }
96
+
97
+ toArrays() {
98
+ return {
99
+ upserts: Array.from(this.upserts.entries()),
100
+ deletes: Array.from(this.deletes.values()),
101
+ };
102
+ }
103
+ }
104
+
105
+ class AuthDatabase {
106
+ constructor(dbPath = DEFAULT_DB, options = {}, BufferJSON) {
107
+ this.dbPath = dbPath;
108
+ this.instanceId = `auth-${Date.now()}-${randomUUID()}`;
109
+ this.disposed = false;
110
+ this.isInitialized = false;
111
+ this.BufferJSON = BufferJSON;
112
+ this.cache = new LRUCache(options.cacheSize || 10000, BufferJSON);
113
+
114
+ this.db = this._initDatabase();
115
+ this._prepareStatements();
116
+ this._initWriteBuffer(options);
117
+ this._initVacuum(options);
118
+ this._registerCleanup();
119
+ this.isInitialized = true;
120
+ }
121
+
122
+ _initDatabase() {
123
+ const db = new Database(this.dbPath);
124
+
125
+ db.pragma("journal_mode = WAL");
126
+ db.pragma("synchronous = NORMAL");
127
+ db.pragma("temp_store = MEMORY");
128
+ db.pragma("cache_size = -131072");
129
+ db.pragma("mmap_size = 134217728");
130
+ db.pragma("page_size = 8192");
131
+ db.pragma("auto_vacuum = INCREMENTAL");
132
+ db.pragma("busy_timeout = 5000");
133
+
134
+ db.exec(`
135
+ CREATE TABLE IF NOT EXISTS baileys_state (
136
+ key TEXT PRIMARY KEY NOT NULL CHECK(length(key) > 0 AND length(key) < 512),
137
+ value TEXT NOT NULL,
138
+ last_access INTEGER DEFAULT (unixepoch())
139
+ ) WITHOUT ROWID;
140
+ `);
141
+
142
+ db.exec(`
143
+ CREATE INDEX IF NOT EXISTS idx_key_prefix ON baileys_state(key)
144
+ WHERE key LIKE '%-%';
145
+ `);
146
+
147
+ db.exec(`
148
+ CREATE INDEX IF NOT EXISTS idx_last_access ON baileys_state(last_access);
149
+ `);
150
+
151
+ return db;
152
+ }
153
+
154
+ _prepareStatements() {
155
+ this.stmtGet = this.db.prepare("SELECT value FROM baileys_state WHERE key = ?");
156
+ this.stmtSet = this.db.prepare(
157
+ "INSERT OR REPLACE INTO baileys_state (key, value, last_access) VALUES (?, ?, unixepoch())"
158
+ );
159
+ this.stmtDel = this.db.prepare("DELETE FROM baileys_state WHERE key = ?");
160
+ this.stmtUpdateAccess = this.db.prepare(
161
+ "UPDATE baileys_state SET last_access = unixepoch() WHERE key = ?"
162
+ );
163
+ this.stmtGetOldKeys = this.db.prepare(
164
+ "SELECT key FROM baileys_state WHERE last_access < ? AND key LIKE '%-%' LIMIT ?"
165
+ );
166
+ this.stmtCountKeys = this.db.prepare(
167
+ "SELECT COUNT(*) as count FROM baileys_state WHERE key LIKE '%-%'"
168
+ );
169
+
170
+ this.txCommit = this.db.transaction((upsertsArr, deletesArr) => {
171
+ for (const [k, v] of upsertsArr) {
172
+ try {
173
+ const jsonString = JSON.stringify(v, this.BufferJSON.replacer);
174
+ this.stmtSet.run(k, jsonString);
175
+ } catch {}
176
+ }
177
+ for (const k of deletesArr) {
178
+ try {
179
+ this.stmtDel.run(k);
180
+ } catch {}
181
+ }
182
+ });
183
+ }
184
+
185
+ _initWriteBuffer(options) {
186
+ this.writeBuffer = new WriteBuffer();
187
+ this.writeMutex = new Mutex();
188
+ this.flushIntervalMs = Number(options.flushIntervalMs ?? 200);
189
+ this.maxBatch = Number(options.maxBatch ?? 1000);
190
+ this.flushTimer = null;
191
+ }
192
+
193
+ _initVacuum(options) {
194
+ this.vacuumEnabled = options.vacuumEnabled !== false;
195
+ this.vacuumIntervalMs = Number(options.vacuumIntervalMs ?? 3600000);
196
+ this.vacuumMaxAge = Number(options.vacuumMaxAge ?? 604800);
197
+ this.vacuumBatchSize = Number(options.vacuumBatchSize ?? 500);
198
+ this.vacuumTimer = null;
199
+ this.lastVacuumTime = 0;
200
+
201
+ if (this.vacuumEnabled) {
202
+ this._scheduleVacuum();
203
+ }
204
+ }
205
+
206
+ _scheduleVacuum() {
207
+ if (!this.vacuumEnabled || this.disposed || !this.isInitialized) return;
208
+
209
+ if (this.vacuumTimer) clearTimeout(this.vacuumTimer);
210
+
211
+ this.vacuumTimer = setTimeout(() => {
212
+ this.vacuumTimer = null;
213
+ this._performVacuum().catch(() => this._scheduleVacuum());
214
+ }, this.vacuumIntervalMs);
215
+
216
+ this.vacuumTimer.unref?.();
217
+ }
218
+
219
+ async _performVacuum() {
220
+ if (this.disposed || !this.isInitialized) return;
221
+
222
+ const now = Date.now();
223
+ if (now - this.lastVacuumTime < this.vacuumIntervalMs) {
224
+ this._scheduleVacuum();
225
+ return;
226
+ }
227
+
228
+ await this.writeMutex.runExclusive(async () => {
229
+ try {
230
+ const cutoffTime = Math.floor(Date.now() / 1000) - this.vacuumMaxAge;
231
+ let totalDeleted = 0;
232
+
233
+ while (true) {
234
+ const oldKeys = this.stmtGetOldKeys.all(cutoffTime, this.vacuumBatchSize);
235
+ if (oldKeys.length === 0) break;
236
+
237
+ const deleted = this.db.transaction(() => {
238
+ let count = 0;
239
+ for (const row of oldKeys) {
240
+ this.stmtDel.run(row.key);
241
+ this.cache.delete(row.key);
242
+ count++;
243
+ }
244
+ return count;
245
+ })();
246
+
247
+ totalDeleted += deleted;
248
+ await new Promise((resolve) => setImmediate(resolve));
249
+ }
250
+
251
+ if (totalDeleted > 0) {
252
+ this.db.pragma("incremental_vacuum");
253
+ this.db.pragma("wal_checkpoint(PASSIVE)");
254
+ }
255
+
256
+ this.lastVacuumTime = now;
257
+ this._scheduleVacuum();
258
+ } catch {
259
+ this._scheduleVacuum();
260
+ }
261
+ });
262
+ }
263
+
264
+ _registerCleanup() {
265
+ initializeSignalHandlers();
266
+ registerSignalHandler(this.instanceId, () => this._cleanup());
267
+ }
268
+
269
+ get(key) {
270
+ if (!validateKey(key)) return undefined;
271
+
272
+ if (this.cache.has(key)) {
273
+ return {
274
+ value: this.cache.get(key)
275
+ };
276
+ }
277
+
278
+ try {
279
+ const row = this.stmtGet.get(key);
280
+ if (!row || !row.value) return undefined;
281
+
282
+ const value = JSON.parse(row.value, this.BufferJSON.reviver);
283
+ this.cache.set(key, value);
284
+
285
+ setImmediate(() => {
286
+ try {
287
+ this.stmtUpdateAccess.run(key);
288
+ } catch {}
289
+ });
290
+
291
+ return {
292
+ value
293
+ };
294
+ } catch {
295
+ return undefined;
296
+ }
297
+ }
298
+
299
+ set(key, value) {
300
+ if (!validateKey(key) || !validateValue(value)) return false;
301
+
302
+ this.cache.set(key, value);
303
+ this.writeBuffer.addUpsert(key, value);
304
+ this._scheduleFlush();
305
+ return true;
306
+ }
307
+
308
+ del(key) {
309
+ if (!validateKey(key)) return false;
310
+
311
+ this.cache.delete(key);
312
+ this.writeBuffer.addDelete(key);
313
+ this._scheduleFlush();
314
+ return true;
315
+ }
316
+
317
+ _scheduleFlush() {
318
+ if (!this.flushTimer && !this.disposed && this.isInitialized) {
319
+ this.flushTimer = setTimeout(() => {
320
+ this.flushTimer = null;
321
+ this.flush().catch(() => {});
322
+ }, this.flushIntervalMs);
323
+
324
+ this.flushTimer.unref?.();
325
+ }
326
+ }
327
+
328
+ async flush() {
329
+ if (this.disposed || !this.isInitialized) return;
330
+
331
+ await this.writeMutex.runExclusive(async () => {
332
+ if (!this.writeBuffer.hasChanges()) return;
333
+
334
+ const {
335
+ upserts,
336
+ deletes
337
+ } = this.writeBuffer.toArrays();
338
+ this.writeBuffer.clear();
339
+
340
+ try {
341
+ this.txCommit(upserts, deletes);
342
+ this.db.pragma("wal_checkpoint(PASSIVE)");
343
+ } catch (e) {
344
+ for (const [k, v] of upserts) this.writeBuffer.addUpsert(k, v);
345
+ for (const k of deletes) this.writeBuffer.addDelete(k);
346
+ throw e;
347
+ }
348
+ });
349
+ }
350
+
351
+ async forceVacuum() {
352
+ if (!this.vacuumEnabled) return;
353
+ this.lastVacuumTime = 0;
354
+ await this._performVacuum();
355
+ }
356
+
357
+ _cleanup() {
358
+ if (this.disposed) return;
359
+ this.disposed = true;
360
+
361
+ try {
362
+ if (this.flushTimer) clearTimeout(this.flushTimer);
363
+ if (this.vacuumTimer) clearTimeout(this.vacuumTimer);
364
+
365
+ const {
366
+ upserts,
367
+ deletes
368
+ } = this.writeBuffer.toArrays();
369
+ if (upserts.length || deletes.length) {
370
+ this.txCommit(upserts, deletes);
371
+ }
372
+
373
+ this.db.pragma("wal_checkpoint(TRUNCATE)");
374
+ this.db.pragma("incremental_vacuum");
375
+ this.db.pragma("optimize");
376
+
377
+ this.db.close();
378
+ this.cache.clear();
379
+ } catch {}
380
+ }
381
+ }
382
+
383
+ let dbInstance = null;
384
+
385
+ export default function getAuthDatabase(dbPath = DEFAULT_DB, options = {}) {
386
+ if (!dbInstance || dbInstance.disposed) {
387
+ const {
388
+ BufferJSON
389
+ } = options;
390
+ dbInstance = new AuthDatabase(dbPath, options, BufferJSON);
391
+ }
392
+ return dbInstance;
393
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ryuu-reinzz/haruka-lib",
3
- "version": "3.3.4",
3
+ "version": "3.4.0-beta.1",
4
4
  "description": "Library extra for bot WhatsApp",
5
5
  "main": "main/index.js",
6
6
  "type": "module",
@@ -16,6 +16,8 @@
16
16
  ],
17
17
  "dependencies": {
18
18
  "axios": "^1.12.1",
19
+ "async_hooks": "^1.0.0",
20
+ "async-mutex": "^0.5.0",
19
21
  "audio-decode": "2.2.3",
20
22
  "better-sqlite3": "^12.5.0",
21
23
  "file-type": "^16.5.3",
@@ -23,6 +25,7 @@
23
25
  "ffmpeg-static": "^5.3.0",
24
26
  "ffprobe-static": "^3.1.0",
25
27
  "fs-extra": "^11.2.0",
28
+ "p-queue": "^9.2.0",
26
29
  "path": "^0.12.7",
27
30
  "node-fetch": "^2.6.1",
28
31
  "node-webpmux": "^3.2.1",
@@ -1,114 +0,0 @@
1
- /**
2
- * Custom SQLite Auth Store untuk Baileys
3
- * by Ryuu
4
- */
5
-
6
- //import Database from "bun:sqlite";
7
- //jika menggunakan bun runtime
8
- import Database from "better-sqlite3";
9
- /**
10
- * Membuat atau mengambil auth state dari SQLite
11
- * @param {string} dbPath - Lokasi file SQLite (contoh: "./auth.db")
12
- */
13
- export default async function useSQLiteAuthState(dbPath = "./auth.db", baileys) {
14
- const {
15
- proto,
16
- BufferJSON,
17
- initAuthCreds
18
- } = baileys;
19
- const db = new Database(dbPath);
20
- db.pragma("journal_mode = WAL");
21
-
22
- db.prepare(`
23
- CREATE TABLE IF NOT EXISTS baileys_state (
24
- key TEXT PRIMARY KEY,
25
- value BLOB
26
- )
27
- `).run();
28
-
29
- const load = (key) => {
30
- const row = db.prepare("SELECT value FROM baileys_state WHERE key = ?").get(key);
31
- if (!row) return null;
32
- try {
33
- return JSON.parse(row.value.toString(), BufferJSON.reviver);
34
- } catch {
35
- return null;
36
- }
37
- };
38
-
39
- const save = (key, data) => {
40
- const json = JSON.stringify(data, BufferJSON.replacer);
41
- const buf = Buffer.from(json, "utf8");
42
- db.prepare("REPLACE INTO baileys_state (key, value) VALUES (?, ?)").run(key, buf);
43
- };
44
-
45
- const creds = load("creds") || initAuthCreds();
46
-
47
- const keys = {};
48
- const categories = [
49
- "pre-key",
50
- "session",
51
- "sender-key",
52
- "app-state-sync-key",
53
- "app-state-sync-version"
54
- ];
55
-
56
- for (const category of categories) {
57
- keys[category] = {};
58
- const rows = db
59
- .prepare("SELECT key, value FROM baileys_state WHERE key LIKE ?")
60
- .all(`${category}:%`);
61
- for (const row of rows) {
62
- try {
63
- keys[category][row.key.slice(category.length + 1)] = JSON.parse(row.value.toString());
64
- } catch {}
65
- }
66
- }
67
-
68
- async function saveCreds() {
69
- save("creds", creds);
70
- }
71
-
72
- const set = (category, id, value) => {
73
- const key = `${category}:${id}`;
74
- save(key, value);
75
- };
76
-
77
- const get = (category, id) => {
78
- const key = `${category}:${id}`;
79
- return load(key);
80
- };
81
-
82
- const del = (category, id) => {
83
- const key = `${category}:${id}`;
84
- db.prepare("DELETE FROM baileys_state WHERE key = ?").run(key);
85
- };
86
-
87
- return {
88
- state: {
89
- creds,
90
- keys: {
91
- get: async (type, ids) => {
92
- const data = {};
93
- for (const id of ids) {
94
- const value = load(`${type}:${id}`);
95
- if (value) data[id] = value;
96
- }
97
- return data;
98
- },
99
- set: async (data) => {
100
- for (const category in data) {
101
- for (const id in data[category]) {
102
- const value = data[category][id];
103
- save(`${category}:${id}`, value);
104
- }
105
- }
106
- }
107
- }
108
- },
109
- saveCreds: async () => save("creds", creds)
110
- };
111
- setInterval(async () => {
112
- await save("creds", creds);
113
- }, 30_000);
114
- }