@shoru/kitten 0.0.1-beta → 0.0.2
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/package.json +1 -1
- package/src/auth/init-session.js +3 -1
- package/src/auth/lmdb-auth-state.js +63 -57
- package/src/client/client.js +8 -6
- package/src/config/default.js +2 -2
- package/src/formatter/index.js +7 -4
- package/src/index.js +3 -2
- package/src/internals/config.js +7 -2
- package/src/internals/index.js +2 -2
- package/src/internals/lmdb-manager.js +3 -1
- package/src/internals/plugin-manager.js +20 -21
- package/src/utils/buffer-json.js +18 -3
- package/src/utils/time-string.js +2 -2
package/package.json
CHANGED
package/src/auth/init-session.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { makeWASocket } from "baileys";
|
|
2
2
|
import { useLMDBAuthState } from "./lmdb-auth-state.js";
|
|
3
|
-
import {
|
|
3
|
+
import { getConfig } from "#internals.js";
|
|
4
|
+
|
|
5
|
+
const config = await getConfig();
|
|
4
6
|
|
|
5
7
|
export const initSession = async ({ socketConfig, id } = {}) => {
|
|
6
8
|
try {
|
|
@@ -17,11 +17,9 @@ const keyBuilder = (sessionId) => {
|
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
const genID = async (db) => {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
return id;
|
|
24
|
-
});
|
|
20
|
+
const id = (db.get(COUNTER_KEY) ?? 0) + 1;
|
|
21
|
+
await db.put(COUNTER_KEY, id);
|
|
22
|
+
return id;
|
|
25
23
|
};
|
|
26
24
|
|
|
27
25
|
const getSessionId = async (db, input) => {
|
|
@@ -37,6 +35,17 @@ export async function useLMDBAuthState(inputSessionId) {
|
|
|
37
35
|
const sessionId = await getSessionId(db, inputSessionId);
|
|
38
36
|
const keys = keyBuilder(sessionId);
|
|
39
37
|
|
|
38
|
+
const existingCreds = db.get(keys.creds);
|
|
39
|
+
let creds;
|
|
40
|
+
|
|
41
|
+
if (existingCreds != null) {
|
|
42
|
+
creds = deserialize(existingCreds);
|
|
43
|
+
} else {
|
|
44
|
+
creds = initAuthCreds();
|
|
45
|
+
await db.put(keys.creds, serialize(creds));
|
|
46
|
+
await db.put(`${SESSION_PREFIX}${sessionId}`, true);
|
|
47
|
+
}
|
|
48
|
+
|
|
40
49
|
const writeCreds = async (credsData) => {
|
|
41
50
|
await db.put(keys.creds, serialize(credsData));
|
|
42
51
|
};
|
|
@@ -44,90 +53,87 @@ export async function useLMDBAuthState(inputSessionId) {
|
|
|
44
53
|
const getKeys = (type, ids) => {
|
|
45
54
|
if (!ids.length) return {};
|
|
46
55
|
|
|
47
|
-
const keyList = ids.map((id) => keys.forKey(type, id));
|
|
48
|
-
const values = db.getMany(keyList);
|
|
49
|
-
|
|
50
56
|
const result = {};
|
|
51
|
-
for (
|
|
52
|
-
const
|
|
57
|
+
for (const id of ids) {
|
|
58
|
+
const dbKey = keys.forKey(type, id);
|
|
59
|
+
const rawValue = db.get(dbKey);
|
|
53
60
|
|
|
54
|
-
if (rawValue) {
|
|
61
|
+
if (rawValue != null) {
|
|
55
62
|
try {
|
|
56
63
|
let parsed = deserialize(rawValue);
|
|
57
64
|
if (type === "app-state-sync-key" && parsed) {
|
|
58
65
|
parsed = proto.Message.AppStateSyncKeyData.fromObject(parsed);
|
|
59
66
|
}
|
|
60
|
-
result[
|
|
67
|
+
result[id] = parsed;
|
|
61
68
|
} catch (err) {
|
|
62
69
|
logger.error(
|
|
63
70
|
err,
|
|
64
|
-
`[LMDBAuthState] Deserialize error: ${type}:${
|
|
71
|
+
`[LMDBAuthState] Deserialize error: ${type}:${id}`
|
|
65
72
|
);
|
|
66
|
-
|
|
73
|
+
result[id] = null;
|
|
67
74
|
}
|
|
75
|
+
} else {
|
|
76
|
+
result[id] = null;
|
|
68
77
|
}
|
|
69
78
|
}
|
|
70
79
|
return result;
|
|
71
80
|
};
|
|
72
81
|
|
|
73
82
|
const setKeys = async (data) => {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
83
|
+
const writes = [];
|
|
84
|
+
|
|
85
|
+
for (const [category, categoryData] of Object.entries(data)) {
|
|
86
|
+
if (!categoryData) continue;
|
|
87
|
+
for (const [id, value] of Object.entries(categoryData)) {
|
|
88
|
+
const key = keys.forKey(category, id);
|
|
89
|
+
if (value != null) {
|
|
90
|
+
writes.push(db.put(key, serialize(value)));
|
|
91
|
+
} else {
|
|
92
|
+
writes.push(db.remove(key));
|
|
84
93
|
}
|
|
85
94
|
}
|
|
86
|
-
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (writes.length > 0) {
|
|
98
|
+
await Promise.all(writes);
|
|
99
|
+
}
|
|
87
100
|
};
|
|
88
101
|
|
|
89
102
|
const clearKeys = async () => {
|
|
90
103
|
let count = 0;
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
104
|
+
const writes = [];
|
|
105
|
+
|
|
106
|
+
for (const { key } of db.getRange({
|
|
107
|
+
start: keys.sessionPrefix,
|
|
108
|
+
end: `${keys.sessionPrefix}\xFF`,
|
|
109
|
+
})) {
|
|
110
|
+
if (key !== keys.creds) {
|
|
111
|
+
writes.push(db.remove(key));
|
|
112
|
+
count++;
|
|
100
113
|
}
|
|
101
|
-
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (writes.length > 0) {
|
|
117
|
+
await Promise.all(writes);
|
|
118
|
+
}
|
|
102
119
|
logger.debug(`[LMDBAuthState] Cleared ${count} keys`);
|
|
103
120
|
};
|
|
104
121
|
|
|
105
122
|
const deleteSession = async () => {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
});
|
|
115
|
-
|
|
123
|
+
const writes = [];
|
|
124
|
+
|
|
125
|
+
for (const { key } of db.getRange({
|
|
126
|
+
start: keys.sessionPrefix,
|
|
127
|
+
end: `${keys.sessionPrefix}\xFF`,
|
|
128
|
+
})) {
|
|
129
|
+
writes.push(db.remove(key));
|
|
130
|
+
}
|
|
131
|
+
writes.push(db.remove(`${SESSION_PREFIX}${sessionId}`));
|
|
132
|
+
|
|
133
|
+
await Promise.all(writes);
|
|
116
134
|
logger.debug(`[LMDBAuthState] Deleted session ${sessionId}`);
|
|
117
135
|
};
|
|
118
136
|
|
|
119
|
-
const creds = await db.transaction(() => {
|
|
120
|
-
const existing = db.get(keys.creds);
|
|
121
|
-
if (existing != null) {
|
|
122
|
-
return deserialize(existing);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const newCreds = initAuthCreds();
|
|
126
|
-
db.put(keys.creds, serialize(newCreds));
|
|
127
|
-
db.put(`${SESSION_PREFIX}${sessionId}`, true);
|
|
128
|
-
return newCreds;
|
|
129
|
-
});
|
|
130
|
-
|
|
131
137
|
return {
|
|
132
138
|
state: {
|
|
133
139
|
creds,
|
package/src/client/client.js
CHANGED
|
@@ -276,12 +276,6 @@ export class Client {
|
|
|
276
276
|
this.id = session.id;
|
|
277
277
|
this.#flag = `CLIENT-${session.id}`;
|
|
278
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
279
|
this.sock.ev.on('connection.update', (update) => {
|
|
286
280
|
this.#handleConnectionUpdate(update);
|
|
287
281
|
});
|
|
@@ -310,6 +304,14 @@ export class Client {
|
|
|
310
304
|
|
|
311
305
|
this.#register();
|
|
312
306
|
|
|
307
|
+
if (!this.#plugins || this.#plugins.destroyed) {
|
|
308
|
+
try {
|
|
309
|
+
this.#plugins = await pluginManager(this.sock);
|
|
310
|
+
} catch (err) {
|
|
311
|
+
this.#logger.error(err, `[${this.#flag}] Failed to initialize plugins`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
313
315
|
const attempts = this.#reconnectAttempts;
|
|
314
316
|
this.#reconnectAttempts = 0;
|
|
315
317
|
|
package/src/config/default.js
CHANGED
|
@@ -22,6 +22,7 @@ const db = {
|
|
|
22
22
|
|
|
23
23
|
const plugins = {
|
|
24
24
|
dir: 'plugins',
|
|
25
|
+
prefixes: ['.', '\\', '!'],
|
|
25
26
|
defaultEvent: 'messages.upsert',
|
|
26
27
|
hmr: {
|
|
27
28
|
enable: false,
|
|
@@ -34,6 +35,5 @@ export default {
|
|
|
34
35
|
db,
|
|
35
36
|
socket,
|
|
36
37
|
plugins,
|
|
37
|
-
timeZone: 'Africa/Casablanca'
|
|
38
|
-
prefixes: ['!', '.']
|
|
38
|
+
timeZone: 'Africa/Casablanca'
|
|
39
39
|
}
|
package/src/formatter/index.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { formatMessage } from './format-message.js';
|
|
2
2
|
|
|
3
|
-
export const formatter =
|
|
4
|
-
eventName ??= '
|
|
3
|
+
export const formatter = (wa, event, eventName) => {
|
|
4
|
+
eventName ??= 'messages.upsert';
|
|
5
5
|
|
|
6
6
|
switch (eventName) {
|
|
7
|
-
case '
|
|
8
|
-
return
|
|
7
|
+
case 'messages.upsert':
|
|
8
|
+
return {
|
|
9
|
+
...formatMessage(wa, event),
|
|
10
|
+
event: eventName
|
|
11
|
+
};
|
|
9
12
|
default: return event;
|
|
10
13
|
}
|
|
11
14
|
}
|
package/src/index.js
CHANGED
package/src/internals/config.js
CHANGED
|
@@ -35,6 +35,11 @@ export const loadConfig = async () => {
|
|
|
35
35
|
return defu(userConfig, defaultConfig);
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
let cachedConfig = null;
|
|
39
39
|
|
|
40
|
-
export const
|
|
40
|
+
export const getConfig = async () => {
|
|
41
|
+
if (!cachedConfig) {
|
|
42
|
+
cachedConfig = Object.freeze(await loadConfig());
|
|
43
|
+
}
|
|
44
|
+
return cachedConfig;
|
|
45
|
+
};
|
package/src/internals/index.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { Mutex } from 'async-mutex';
|
|
2
|
-
import {
|
|
2
|
+
import { getConfig, logger } from '#internals.js';
|
|
3
3
|
import { watch } from 'chokidar';
|
|
4
4
|
import fs from 'fs/promises';
|
|
5
5
|
import path from 'path';
|
|
6
6
|
import { pathToFileURL } from 'url';
|
|
7
7
|
import { formatter } from '#formatter.js';
|
|
8
8
|
|
|
9
|
+
const config = await getConfig();
|
|
10
|
+
|
|
9
11
|
const {
|
|
10
12
|
dir,
|
|
11
13
|
defaultEvent,
|
|
@@ -17,7 +19,7 @@ const {
|
|
|
17
19
|
}
|
|
18
20
|
} = config.plugins;
|
|
19
21
|
|
|
20
|
-
const PLUGIN_DIR = path.
|
|
22
|
+
const PLUGIN_DIR = path.join(process.cwd(), dir);
|
|
21
23
|
|
|
22
24
|
const EVENTS = new Set([
|
|
23
25
|
'messaging-history.set', 'chats.upsert', 'chats.update', 'chats.delete',
|
|
@@ -55,10 +57,8 @@ export class PluginManager {
|
|
|
55
57
|
?? PluginManager.#fileLocks.set(filePath, new Mutex()).get(filePath);
|
|
56
58
|
}
|
|
57
59
|
|
|
58
|
-
static #handleError(context, err
|
|
60
|
+
static #handleError(context, err) {
|
|
59
61
|
if (isDebug) {
|
|
60
|
-
logger.error(err, context);
|
|
61
|
-
} else if (level === 'error') {
|
|
62
62
|
logger.warn(`${context} ${err?.message ?? 'Unknown error'}`);
|
|
63
63
|
}
|
|
64
64
|
}
|
|
@@ -138,14 +138,15 @@ export class PluginManager {
|
|
|
138
138
|
|
|
139
139
|
let loaded = 0;
|
|
140
140
|
let failed = 0;
|
|
141
|
-
|
|
141
|
+
|
|
142
142
|
for (let i = 0; i < results.length; i++) {
|
|
143
143
|
const result = results[i];
|
|
144
144
|
if (result.status === 'fulfilled') {
|
|
145
145
|
loaded += result.value?.size ?? 0;
|
|
146
146
|
} else {
|
|
147
147
|
failed++;
|
|
148
|
-
|
|
148
|
+
const rel = path.relative(PLUGIN_DIR, files[i].path);
|
|
149
|
+
PluginManager.#handleError(`[PluginManager:${rel}] Failed to load:`, result.reason);
|
|
149
150
|
}
|
|
150
151
|
}
|
|
151
152
|
|
|
@@ -198,7 +199,7 @@ export class PluginManager {
|
|
|
198
199
|
return null;
|
|
199
200
|
}
|
|
200
201
|
|
|
201
|
-
static #compile(match, prefixOpt) {
|
|
202
|
+
static #compile(match, prefixOpt = PREFIXES) {
|
|
202
203
|
if (!Array.isArray(match) || !match.length) return null;
|
|
203
204
|
|
|
204
205
|
const strings = match.filter(m => typeof m === 'string').map(s => s.toLowerCase());
|
|
@@ -234,12 +235,12 @@ export class PluginManager {
|
|
|
234
235
|
|
|
235
236
|
if (cmd) {
|
|
236
237
|
if (matchers.set.has(cmd)) {
|
|
237
|
-
return {
|
|
238
|
+
return { match: cmd, prefix };
|
|
238
239
|
}
|
|
239
240
|
|
|
240
241
|
for (const s of matchers.strings) {
|
|
241
242
|
if (cmd.length > s.length && cmd.startsWith(s)) {
|
|
242
|
-
return {
|
|
243
|
+
return { match: s, prefix };
|
|
243
244
|
}
|
|
244
245
|
}
|
|
245
246
|
}
|
|
@@ -249,7 +250,7 @@ export class PluginManager {
|
|
|
249
250
|
for (const re of matchers.regexes) {
|
|
250
251
|
re.lastIndex = 0;
|
|
251
252
|
const m = re.exec(body);
|
|
252
|
-
if (m) return {
|
|
253
|
+
if (m) return { match: m, prefix: null };
|
|
253
254
|
}
|
|
254
255
|
|
|
255
256
|
return null;
|
|
@@ -288,7 +289,7 @@ export class PluginManager {
|
|
|
288
289
|
if (!active.has(event)) {
|
|
289
290
|
this.#sock.ev.off(event, handler);
|
|
290
291
|
this.#handlers.delete(event);
|
|
291
|
-
if (isDebug) logger.debug(`[Events]
|
|
292
|
+
if (isDebug) logger.debug(`[Events] (-) ${event}`);
|
|
292
293
|
}
|
|
293
294
|
}
|
|
294
295
|
|
|
@@ -297,7 +298,7 @@ export class PluginManager {
|
|
|
297
298
|
const handler = this.#createHandler(event);
|
|
298
299
|
this.#sock.ev.on(event, handler);
|
|
299
300
|
this.#handlers.set(event, handler);
|
|
300
|
-
if (isDebug) logger.debug(`[Events]
|
|
301
|
+
if (isDebug) logger.debug(`[Events] (+) ${event}`);
|
|
301
302
|
}
|
|
302
303
|
}
|
|
303
304
|
}
|
|
@@ -317,11 +318,11 @@ export class PluginManager {
|
|
|
317
318
|
'messages.upsert': ({ messages, type }) => {
|
|
318
319
|
if (type !== 'notify') return;
|
|
319
320
|
for (const msg of messages) {
|
|
320
|
-
if (!msg?.key?.remoteJid || msg.key.remoteJid === 'status@broadcast'
|
|
321
|
+
if (!msg?.key?.remoteJid || msg.key.remoteJid === 'status@broadcast') continue;
|
|
321
322
|
try {
|
|
322
323
|
dispatch(formatter(sock, msg, event));
|
|
323
324
|
} catch (err) {
|
|
324
|
-
PluginManager.#handleError('[PluginManager] Format error:', err
|
|
325
|
+
PluginManager.#handleError('[PluginManager] Format error:', err);
|
|
325
326
|
}
|
|
326
327
|
}
|
|
327
328
|
},
|
|
@@ -386,7 +387,7 @@ export class PluginManager {
|
|
|
386
387
|
.on('add', p => PluginManager.#debounce(p, 'add'))
|
|
387
388
|
.on('change', p => PluginManager.#debounce(p, 'change'))
|
|
388
389
|
.on('unlink', p => PluginManager.#debounce(p, 'unlink'))
|
|
389
|
-
.on('error', e => PluginManager.#handleError('[Watcher]', e
|
|
390
|
+
.on('error', e => PluginManager.#handleError('[Watcher]', e));
|
|
390
391
|
}
|
|
391
392
|
|
|
392
393
|
static #debounce(filePath, type) {
|
|
@@ -402,14 +403,12 @@ export class PluginManager {
|
|
|
402
403
|
|
|
403
404
|
static async #hmr(filePath, type) {
|
|
404
405
|
const rel = path.relative(PLUGIN_DIR, filePath);
|
|
405
|
-
const ts = isDebug ? new Date().toISOString().slice(11, 19) : '';
|
|
406
|
-
const tag = isDebug ? `[HMR:${ts}]` : '[HMR]';
|
|
407
406
|
|
|
408
407
|
try {
|
|
409
408
|
await PluginManager.#getLock(filePath).runExclusive(async () => {
|
|
410
409
|
if (type === 'unlink') {
|
|
411
410
|
const n = PluginManager.#unloadFile(filePath);
|
|
412
|
-
logger.info(
|
|
411
|
+
logger.info(`[HMR] Unloaded: ${rel} (${n})`);
|
|
413
412
|
} else {
|
|
414
413
|
const parent = PluginManager.#getParent(path.dirname(filePath));
|
|
415
414
|
const plugins = await PluginManager.#loadFile(filePath, parent, false);
|
|
@@ -421,13 +420,13 @@ export class PluginManager {
|
|
|
421
420
|
PluginManager.#register(id, plugin);
|
|
422
421
|
}
|
|
423
422
|
|
|
424
|
-
logger.info(
|
|
423
|
+
logger.info(`[HMR] ${type === 'add' ? 'Added' : 'Reloaded'}: ${rel} (${plugins.size})`);
|
|
425
424
|
}
|
|
426
425
|
|
|
427
426
|
PluginManager.#syncAll();
|
|
428
427
|
});
|
|
429
428
|
} catch (err) {
|
|
430
|
-
PluginManager.#handleError(
|
|
429
|
+
PluginManager.#handleError(`[HMR:${rel}] Failed:`, err);
|
|
431
430
|
} finally {
|
|
432
431
|
if (type === 'unlink') {
|
|
433
432
|
const lock = PluginManager.#fileLocks.get(filePath);
|
package/src/utils/buffer-json.js
CHANGED
|
@@ -2,10 +2,25 @@ import { BufferJSON } from 'baileys';
|
|
|
2
2
|
|
|
3
3
|
export const serialize = (data) => {
|
|
4
4
|
if (data == null) return null;
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
try {
|
|
6
|
+
return JSON.stringify(data, BufferJSON.replacer);
|
|
7
|
+
} catch (err) {
|
|
8
|
+
throw new Error(`Serialization failed: ${err.message}`, { cause: err });
|
|
9
|
+
}
|
|
10
|
+
};
|
|
7
11
|
|
|
8
12
|
export const deserialize = (json) => {
|
|
9
13
|
if (json == null) return null;
|
|
10
|
-
|
|
14
|
+
|
|
15
|
+
const str = typeof json === 'string'
|
|
16
|
+
? json
|
|
17
|
+
: Buffer.isBuffer(json)
|
|
18
|
+
? json.toString('utf8')
|
|
19
|
+
: String(json);
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
return JSON.parse(str, BufferJSON.reviver);
|
|
23
|
+
} catch (err) {
|
|
24
|
+
throw new Error(`Deserialization failed: ${err.message}`, { cause: err });
|
|
25
|
+
}
|
|
11
26
|
};
|
package/src/utils/time-string.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getConfig } from '#internals.js';
|
|
2
2
|
|
|
3
|
-
const { timeZone } = await
|
|
3
|
+
const { timeZone } = await getConfig();
|
|
4
4
|
|
|
5
5
|
export const getTimeString = (timestamp, TIME_ZONE = timeZone) => {
|
|
6
6
|
const date = new Date(timestamp * 1000);
|