@ryuu-reinzz/haruka-lib 3.3.3 → 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/README.md +1 -1
- package/main/index.js +4 -2
- package/main/sqlite3-auth/auth.js +342 -0
- package/main/sqlite3-auth/config.js +83 -0
- package/main/sqlite3-auth/core.js +393 -0
- package/main/sticker-engine/image-to-webp.js +1 -1
- package/main/sticker-engine/main-sticker.js +11 -5
- package/main/sticker-engine/video-to-webp.js +1 -1
- package/package.json +9 -6
- package/main/sqliteAuth.js +0 -114
package/README.md
CHANGED
package/main/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import addProperty from "./socket.js";
|
|
2
|
-
import useSQLiteAuthState from "./
|
|
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
|
+
}
|
|
@@ -41,7 +41,7 @@ export async function stickerImg(media, metadata) {
|
|
|
41
41
|
if (metadata.packname || metadata.author) {
|
|
42
42
|
const img = new webp.Image();
|
|
43
43
|
const json = {
|
|
44
|
-
'sticker-pack-id': 'https://
|
|
44
|
+
'sticker-pack-id': 'https://www.npmjs.com/package/@ryuu-reinzz/baileys',
|
|
45
45
|
'sticker-pack-name': metadata.packname,
|
|
46
46
|
'sticker-pack-publisher': metadata.author,
|
|
47
47
|
'emojis': metadata.categories ?? ['']
|
|
@@ -17,23 +17,29 @@ import Crypto from 'crypto';
|
|
|
17
17
|
ffmpeg.setFfmpegPath(ffmpegPath);
|
|
18
18
|
ffmpeg.setFfprobePath(ffprobePath.path);
|
|
19
19
|
|
|
20
|
-
function
|
|
20
|
+
function isAnimated(input) {
|
|
21
21
|
return new Promise((resolve) => {
|
|
22
22
|
ffmpeg.ffprobe(input, (err, metadata) => {
|
|
23
23
|
if (err) {
|
|
24
|
-
console.
|
|
24
|
+
console.error("ffprobe error:", err);
|
|
25
25
|
return resolve(false);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
const
|
|
28
|
+
const videoStream = metadata.streams.find(
|
|
29
29
|
(s) => s.codec_type === "video"
|
|
30
30
|
);
|
|
31
31
|
|
|
32
|
-
resolve(
|
|
32
|
+
if (!videoStream) return resolve(false);
|
|
33
|
+
const frameCount = parseInt(videoStream.nb_frames);
|
|
34
|
+
const duration = parseFloat(metadata.format.duration);
|
|
35
|
+
const isMoving = frameCount > 1 || duration > 0;
|
|
36
|
+
|
|
37
|
+
resolve(isMoving);
|
|
33
38
|
});
|
|
34
39
|
});
|
|
35
40
|
}
|
|
36
41
|
|
|
42
|
+
|
|
37
43
|
async function bufferToTmp(buffer, ext = '.bin') {
|
|
38
44
|
const tmp = path.join(
|
|
39
45
|
tmpdir(),
|
|
@@ -99,7 +105,7 @@ class Sticker {
|
|
|
99
105
|
Buffer.alloc(0);
|
|
100
106
|
|
|
101
107
|
const tmpPath = await bufferToTmp(buff);
|
|
102
|
-
const thisVideo = await
|
|
108
|
+
const thisVideo = await isAnimated(tmpPath);
|
|
103
109
|
|
|
104
110
|
return thisVideo ?
|
|
105
111
|
await this.toStickerVid() :
|
|
@@ -60,7 +60,7 @@ export async function stickerVid(media, metadata) {
|
|
|
60
60
|
if (metadata.packname || metadata.author) {
|
|
61
61
|
const img = new webp.Image();
|
|
62
62
|
const json = {
|
|
63
|
-
'sticker-pack-id': 'https://
|
|
63
|
+
'sticker-pack-id': 'https://www.npmjs.com/package/@ryuu-reinzz/baileys',
|
|
64
64
|
'sticker-pack-name': metadata.packname,
|
|
65
65
|
'sticker-pack-publisher': metadata.author,
|
|
66
66
|
'emojis': metadata.categories ?? ['']
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ryuu-reinzz/haruka-lib",
|
|
3
|
-
"version": "3.
|
|
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",
|
|
@@ -15,17 +15,20 @@
|
|
|
15
15
|
":v"
|
|
16
16
|
],
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"audio-decode": "2.2.3",
|
|
19
18
|
"axios": "^1.12.1",
|
|
19
|
+
"async_hooks": "^1.0.0",
|
|
20
|
+
"async-mutex": "^0.5.0",
|
|
21
|
+
"audio-decode": "2.2.3",
|
|
20
22
|
"better-sqlite3": "^12.5.0",
|
|
23
|
+
"file-type": "^16.5.3",
|
|
24
|
+
"fluent-ffmpeg": "^2.1.3",
|
|
21
25
|
"ffmpeg-static": "^5.3.0",
|
|
22
26
|
"ffprobe-static": "^3.1.0",
|
|
23
|
-
"file-type": "^22.0.1",
|
|
24
|
-
"fluent-ffmpeg": "^2.1.3",
|
|
25
27
|
"fs-extra": "^11.2.0",
|
|
28
|
+
"p-queue": "^9.2.0",
|
|
29
|
+
"path": "^0.12.7",
|
|
26
30
|
"node-fetch": "^2.6.1",
|
|
27
31
|
"node-webpmux": "^3.2.1",
|
|
28
|
-
"path": "^0.12.7",
|
|
29
32
|
"sharp": "^0.34.1"
|
|
30
33
|
}
|
|
31
|
-
}
|
|
34
|
+
}
|
package/main/sqliteAuth.js
DELETED
|
@@ -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
|
-
}
|