@shoru/kitten 0.0.6 → 0.1.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 CHANGED
@@ -141,14 +141,19 @@ export default {
141
141
  browser: Browsers.ubuntu('Chrome'),
142
142
  markOnlineOnConnect: false,
143
143
  syncFullHistory: false,
144
- generateHighQualityLinkPreview: true,
144
+ generateHighQualityLinkPreview: false,
145
+ shouldIgnoreJid: () => false,
146
+ shouldSyncHistoryMessage: () => false,
145
147
  logger: pino({ level: 'silent' })
146
148
  },
147
149
 
148
150
  db: {
149
151
  path: './db',
150
152
  compression: true,
151
- mapSize: 2 * 1024 * 1024 * 1024 // 2GB
153
+ mapSize: 2 * 1024 * 1024 * 1024, // 2 GB
154
+ maxReaders: 126,
155
+ noSync: true,
156
+ noMetaSync: true
152
157
  },
153
158
 
154
159
  plugins: {
@@ -0,0 +1,4 @@
1
+ import { logger, getClient } from '../src/index.js';
2
+
3
+ logger.level = 'debug';
4
+ await getClient();
@@ -0,0 +1,33 @@
1
+ import { fileURLToPath } from 'url';
2
+ import path from 'path';
3
+
4
+ const __filename = fileURLToPath(import.meta.url);
5
+ const __dirname = path.dirname(__filename);
6
+
7
+ const pluginsDir = path.resolve(__dirname, 'plugins');
8
+
9
+ const db = {
10
+ path: path.resolve(__dirname, '../db'),
11
+ compression: true,
12
+ mapSize: 2 * 1024 * 1024 * 1024, // 2 GB
13
+ maxReaders: 126,
14
+ noSync: false,
15
+ noMetaSync: false,
16
+ };
17
+
18
+ const plugins = {
19
+ dir: pluginsDir,
20
+ prefixes: ['.', '\\', '!'],
21
+ defaultEvent: 'messages.upsert',
22
+ hmr: {
23
+ enable: true,
24
+ debounce: 200,
25
+ debug: true
26
+ }
27
+ }
28
+
29
+ export default {
30
+ db,
31
+ plugins,
32
+ timeZone: 'Africa/Casablanca'
33
+ }
@@ -0,0 +1,7 @@
1
+ export const plugin = async (sock, ctx) => {
2
+ console.log(JSON.stringify({...ctx, raw: undefined}, null, 1));
3
+ sock.sendMessage(ctx.roomId, { text: ctx.body }, { quoted: ctx.raw });
4
+ }
5
+
6
+ plugin.events = ['messages.upsert'];
7
+ plugin.match = ['tst'];
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@shoru/kitten",
3
3
  "type": "module",
4
- "version": "0.0.6",
4
+ "version": "0.1.1",
5
5
  "description": "A powerful Node.js framework to simplify making WhatsApp Bots",
6
6
  "main": "src/index.js",
7
7
  "repository": {
@@ -9,7 +9,7 @@
9
9
  },
10
10
  "scripts": {
11
11
  "start": "node --no-warnings src/index.js",
12
- "test": "echo \"Error: no test specified\" && exit 1"
12
+ "test": "node --no-warnings internal-test/index.js"
13
13
  },
14
14
  "imports": {
15
15
  "#utils.js": "./src/utils/index.js",
@@ -26,16 +26,16 @@
26
26
  "license": "MIT",
27
27
  "dependencies": {
28
28
  "@hapi/boom": "^10.0.1",
29
- "@inquirer/prompts": "^8.1.0",
30
- "@shoru/spindle": "^1.0.6",
29
+ "@inquirer/prompts": "^8.7.0",
30
+ "@shoru/spindle": "^1.0.7",
31
31
  "async-mutex": "^0.5.0",
32
- "baileys": "^7.0.0-rc.9",
33
- "chalk": "^5.6.2",
32
+ "baileys": "github:WhiskeySockets/Baileys",
33
+ "chalk": "^6.0.0",
34
34
  "chokidar": "^5.0.0",
35
- "cosmiconfig": "^9.0.0",
36
- "defu": "^6.1.4",
37
- "lmdb": "^3.4.4",
38
- "pino": "^10.1.0",
35
+ "cosmiconfig": "^10.0.0",
36
+ "defu": "^6.1.7",
37
+ "lmdb": "^3.5.6",
38
+ "pino": "^10.3.1",
39
39
  "pino-pretty": "^13.1.3",
40
40
  "qrcode-terminal": "^0.12.0"
41
41
  }
@@ -1,19 +1,32 @@
1
- import { makeWASocket } from "baileys";
1
+ import { makeWASocket, fetchLatestWaWebVersion } from "baileys";
2
2
  import { useLMDBAuthState } from "./lmdb-auth-state.js";
3
3
  import { getConfig } from "#internals.js";
4
4
 
5
5
  const config = await getConfig();
6
+ let cachedWaVersion = null;
7
+
8
+ const getWaVersion = async () => {
9
+ if (!cachedWaVersion) {
10
+ try {
11
+ const { version } = await fetchLatestWaWebVersion();
12
+ cachedWaVersion = version;
13
+ } catch {/* noop */}
14
+ }
15
+ return cachedWaVersion;
16
+ };
6
17
 
7
18
  export const initSession = async ({ socketConfig, id } = {}) => {
8
19
  try {
9
20
  const { state, saveCreds, session } = await useLMDBAuthState(id);
10
-
11
- const sock = makeWASocket({
21
+ const version = (await getWaVersion()) || config.socket?.version;
22
+
23
+ const sock = makeWASocket({
24
+ version: version || undefined,
12
25
  auth: state,
13
- ...config.socket,
26
+ ...config.socket,
14
27
  ...socketConfig
15
28
  });
16
-
29
+
17
30
  sock.ev.on("creds.update", saveCreds);
18
31
 
19
32
  return { sock, session };
@@ -1,177 +1,271 @@
1
- import { proto, initAuthCreds } from "baileys";
2
- import { LMDBManager, logger } from "#internals.js";
3
- import { serialize, deserialize } from "#utils.js";
4
-
5
- const KEY_PREFIX = "baileys";
6
- const COUNTER_KEY = `${KEY_PREFIX}:__meta__:counter`;
7
- const SESSION_PREFIX = `${KEY_PREFIX}:__sessions__:`;
8
-
9
- const keyBuilder = (sessionId) => {
10
- const prefix = `${KEY_PREFIX}:${sessionId}:`;
11
- return {
12
- sessionId,
13
- sessionPrefix: prefix,
14
- creds: `${prefix}creds`,
15
- forKey: (type, id) => `${prefix}${type}:${id}`,
16
- };
17
- };
18
-
19
- const genID = async (db) => {
20
- const id = (db.get(COUNTER_KEY) ?? 0) + 1;
21
- await db.put(COUNTER_KEY, id);
22
- return id;
23
- };
24
-
25
- const getSessionId = async (db, input) => {
26
- if (input == null) return genID(db);
27
- if (Number.isInteger(input) && input >= 0) return input;
28
- throw new TypeError(
29
- 'Invalid sessionId: expected null/undefined or non-negative integer'
30
- );
31
- };
32
-
33
- export async function useLMDBAuthState(inputSessionId) {
34
- const { db } = LMDBManager;
35
- const sessionId = await getSessionId(db, inputSessionId);
36
- const keys = keyBuilder(sessionId);
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
-
49
- const writeCreds = async (credsData) => {
50
- await db.put(keys.creds, serialize(credsData));
51
- };
52
-
53
- const getKeys = (type, ids) => {
54
- if (!ids.length) return {};
55
-
56
- const result = {};
57
- for (const id of ids) {
58
- const dbKey = keys.forKey(type, id);
59
- const rawValue = db.get(dbKey);
60
-
61
- if (rawValue != null) {
62
- try {
63
- let parsed = deserialize(rawValue);
64
- if (type === "app-state-sync-key" && parsed) {
65
- parsed = proto.Message.AppStateSyncKeyData.fromObject(parsed);
66
- }
67
- result[id] = parsed;
68
- } catch (err) {
69
- logger.error(
70
- err,
71
- `[LMDBAuthState] Deserialize error: ${type}:${id}`
72
- );
73
- result[id] = null;
74
- }
75
- } else {
76
- result[id] = null;
77
- }
78
- }
79
- return result;
80
- };
81
-
82
- const setKeys = async (data) => {
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));
93
- }
94
- }
95
- }
96
-
97
- if (writes.length > 0) {
98
- await Promise.all(writes);
99
- }
100
- };
101
-
102
- const clearKeys = async () => {
103
- let count = 0;
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++;
113
- }
114
- }
115
-
116
- if (writes.length > 0) {
117
- await Promise.all(writes);
118
- }
119
- logger.debug(`[LMDBAuthState] Cleared ${count} keys`);
120
- };
121
-
122
- const deleteSession = async () => {
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);
134
- logger.debug(`[LMDBAuthState] Deleted session ${sessionId}`);
135
- };
136
-
137
- return {
138
- state: {
139
- creds,
140
- keys: {
141
- get: getKeys,
142
- set: setKeys,
143
- clear: clearKeys,
144
- },
145
- },
146
- saveCreds: () => writeCreds(creds),
147
- session: {
148
- delete: deleteSession,
149
- clear: clearKeys,
150
- id: sessionId,
151
- },
152
- };
153
- }
154
-
155
- export function listSessions() {
156
- if (!LMDBManager.isOpen) return [];
157
-
158
- const { db } = LMDBManager;
159
- const sessions = [];
160
-
161
- for (const { key } of db.getRange({
162
- start: SESSION_PREFIX,
163
- end: `${SESSION_PREFIX}\xFF`,
164
- })) {
165
- const id = parseInt(key.slice(SESSION_PREFIX.length), 10);
166
- if (!isNaN(id)) sessions.push(id);
167
- }
168
-
169
- return sessions.sort((a, b) => a - b);
170
- }
171
-
172
- export function sessionExists(sessionId) {
173
- if (!Number.isInteger(sessionId) || sessionId < 0 || !LMDBManager.isOpen)
174
- return false;
175
- const { db } = LMDBManager;
176
- return db.get(`${SESSION_PREFIX}${sessionId}`) != null;
1
+ import { proto, initAuthCreds } from "baileys";
2
+ import { LMDBManager, logger } from "#internals.js";
3
+ import { serialize, deserialize } from "#utils.js";
4
+
5
+ const KEY_PREFIX = "baileys";
6
+ const COUNTER_KEY = `${KEY_PREFIX}:__meta__:counter`;
7
+ const SESSION_PREFIX = `${KEY_PREFIX}:__sessions__:`;
8
+
9
+ const keyBuilder = (sessionId) => {
10
+ const prefix = `${KEY_PREFIX}:${sessionId}:`;
11
+ return {
12
+ sessionId,
13
+ sessionPrefix: prefix,
14
+ creds: `${prefix}creds`,
15
+ forKey: (type, id) => `${prefix}${type}:${id}`,
16
+ };
17
+ };
18
+
19
+ const genID = async (db) => {
20
+ const current = db.get(COUNTER_KEY);
21
+ let id;
22
+ if (current == null) {
23
+ const existing = listSessions();
24
+ id = existing.length > 0 ? Math.max(...existing) + 1 : 0;
25
+ } else {
26
+ id = current + 1;
27
+ }
28
+ await db.put(COUNTER_KEY, id);
29
+ return id;
30
+ };
31
+
32
+ const getSessionId = async (db, input) => {
33
+ if (input == null) {
34
+ const existing = listSessions();
35
+ if (existing.length > 0) {
36
+ return existing[0];
37
+ }
38
+ return genID(db);
39
+ }
40
+ if (Number.isInteger(input) && input >= 0) return input;
41
+ throw new TypeError(
42
+ 'Invalid sessionId: expected null/undefined or non-negative integer'
43
+ );
44
+ };
45
+
46
+ export async function useLMDBAuthState(inputSessionId) {
47
+ const { db } = LMDBManager;
48
+ const sessionId = await getSessionId(db, inputSessionId);
49
+ const keys = keyBuilder(sessionId);
50
+
51
+ const existingCreds = db.get(keys.creds);
52
+ let creds = null;
53
+ let isNew = false;
54
+
55
+ if (existingCreds != null) {
56
+ try {
57
+ creds = deserialize(existingCreds);
58
+ } catch {
59
+ logger.warn(`[LMDBAuthState] Corrupted credentials for session ${sessionId}, purging session`);
60
+ creds = null;
61
+ }
62
+
63
+ if (creds && (creds.registered || creds.me)) {
64
+ await db.put(`${SESSION_PREFIX}${sessionId}`, true);
65
+ } else {
66
+ // Credentials not registered or corrupted: clean up old session keys and start fresh
67
+ for (const { key } of db.getRange({
68
+ start: keys.sessionPrefix,
69
+ end: `${keys.sessionPrefix}\xFF`,
70
+ })) {
71
+ await db.remove(key);
72
+ }
73
+ await db.remove(`${SESSION_PREFIX}${sessionId}`);
74
+ creds = initAuthCreds();
75
+ await db.put(keys.creds, serialize(creds));
76
+ isNew = true;
77
+ }
78
+ } else {
79
+ creds = initAuthCreds();
80
+ await db.put(keys.creds, serialize(creds));
81
+ isNew = true;
82
+ }
83
+
84
+ const writeCreds = async (credsData) => {
85
+ await db.put(keys.creds, serialize(credsData));
86
+ if (credsData?.registered || credsData?.me) {
87
+ await db.put(`${SESSION_PREFIX}${sessionId}`, true);
88
+ }
89
+ };
90
+
91
+ const getKeys = (type, ids) => {
92
+ if (!ids.length) return {};
93
+
94
+ const result = {};
95
+ for (const id of ids) {
96
+ const dbKey = keys.forKey(type, id);
97
+ const rawValue = db.get(dbKey);
98
+
99
+ if (rawValue != null) {
100
+ try {
101
+ let parsed = deserialize(rawValue);
102
+ if (type === "app-state-sync-key" && parsed) {
103
+ parsed = proto.Message.AppStateSyncKeyData.fromObject(parsed);
104
+ }
105
+ result[id] = parsed;
106
+ } catch (err) {
107
+ logger.error(
108
+ err,
109
+ `[LMDBAuthState] Deserialize error: ${type}:${id}`
110
+ );
111
+ result[id] = null;
112
+ }
113
+ } else {
114
+ result[id] = null;
115
+ }
116
+ }
117
+ return result;
118
+ };
119
+
120
+ const setKeys = async (data) => {
121
+ const writes = [];
122
+
123
+ for (const [category, categoryData] of Object.entries(data)) {
124
+ if (!categoryData) continue;
125
+ for (const [id, value] of Object.entries(categoryData)) {
126
+ const key = keys.forKey(category, id);
127
+ if (value != null) {
128
+ writes.push(db.put(key, serialize(value)));
129
+ } else {
130
+ writes.push(db.remove(key));
131
+ }
132
+ }
133
+ }
134
+
135
+ if (writes.length > 0) {
136
+ await Promise.all(writes);
137
+ }
138
+ };
139
+
140
+ const clearKeys = async () => {
141
+ let count = 0;
142
+ const writes = [];
143
+
144
+ for (const { key } of db.getRange({
145
+ start: keys.sessionPrefix,
146
+ end: `${keys.sessionPrefix}\xFF`,
147
+ })) {
148
+ if (key !== keys.creds) {
149
+ writes.push(db.remove(key));
150
+ count++;
151
+ }
152
+ }
153
+
154
+ if (writes.length > 0) {
155
+ await Promise.all(writes);
156
+ }
157
+ logger.debug(`[LMDBAuthState] Cleared ${count} keys`);
158
+ };
159
+
160
+ const deleteSession = async () => {
161
+ const writes = [];
162
+
163
+ for (const { key } of db.getRange({
164
+ start: keys.sessionPrefix,
165
+ end: `${keys.sessionPrefix}\xFF`,
166
+ })) {
167
+ writes.push(db.remove(key));
168
+ }
169
+ writes.push(db.remove(`${SESSION_PREFIX}${sessionId}`));
170
+
171
+ await Promise.all(writes);
172
+ logger.debug(`[LMDBAuthState] Deleted session ${sessionId}`);
173
+ };
174
+
175
+ return {
176
+ state: {
177
+ creds,
178
+ keys: {
179
+ get: getKeys,
180
+ set: setKeys,
181
+ clear: clearKeys,
182
+ },
183
+ },
184
+ saveCreds: () => writeCreds(creds),
185
+ session: {
186
+ delete: deleteSession,
187
+ clear: clearKeys,
188
+ id: sessionId,
189
+ isNew,
190
+ },
191
+ };
192
+ }
193
+
194
+ export function listSessions() {
195
+ const { db } = LMDBManager;
196
+ if (!db) return [];
197
+
198
+ const sessions = new Set();
199
+
200
+ for (const { key } of db.getRange({
201
+ start: SESSION_PREFIX,
202
+ end: `${SESSION_PREFIX}\xFF`,
203
+ })) {
204
+ const id = parseInt(key.slice(SESSION_PREFIX.length), 10);
205
+ if (!isNaN(id)) {
206
+ const rawCreds = db.get(`${KEY_PREFIX}:${id}:creds`);
207
+ if (rawCreds != null) {
208
+ try {
209
+ const creds = deserialize(rawCreds);
210
+ if (creds?.registered || creds?.me) {
211
+ sessions.add(id);
212
+ } else {
213
+ db.remove(key);
214
+ }
215
+ } catch {
216
+ db.remove(key);
217
+ }
218
+ } else {
219
+ db.remove(key);
220
+ }
221
+ }
222
+ }
223
+
224
+ // Also check for existing credentials as fallback
225
+ for (const { key, value } of db.getRange({
226
+ start: `${KEY_PREFIX}:`,
227
+ end: `${KEY_PREFIX}:\xFF`,
228
+ })) {
229
+ if (key.endsWith(':creds')) {
230
+ const parts = key.split(':');
231
+ if (parts.length === 3 && parts[0] === KEY_PREFIX && parts[2] === 'creds') {
232
+ const id = parseInt(parts[1], 10);
233
+ if (!isNaN(id) && !sessions.has(id)) {
234
+ try {
235
+ const creds = deserialize(value);
236
+ if (creds?.registered || creds?.me) {
237
+ sessions.add(id);
238
+ db.put(`${SESSION_PREFIX}${id}`, true);
239
+ }
240
+ } catch {
241
+ /* ignore corrupted creds */
242
+ }
243
+ }
244
+ }
245
+ }
246
+ }
247
+
248
+ return [...sessions].sort((a, b) => a - b);
249
+ }
250
+
251
+ export function sessionExists(sessionId) {
252
+ if (!Number.isInteger(sessionId) || sessionId < 0) return false;
253
+ const { db } = LMDBManager;
254
+ if (!db) return false;
255
+
256
+ const rawCreds = db.get(`${KEY_PREFIX}:${sessionId}:creds`);
257
+ if (rawCreds != null) {
258
+ try {
259
+ const creds = deserialize(rawCreds);
260
+ if (creds?.registered || creds?.me) {
261
+ db.put(`${SESSION_PREFIX}${sessionId}`, true);
262
+ return true;
263
+ }
264
+ } catch {
265
+ db.remove(`${SESSION_PREFIX}${sessionId}`);
266
+ return false;
267
+ }
268
+ }
269
+ db.remove(`${SESSION_PREFIX}${sessionId}`);
270
+ return false;
177
271
  }