levix-bot 2.0.0
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/LICENSE +21 -0
- package/README.md +105 -0
- package/SETUP.md +242 -0
- package/app.cjs +337 -0
- package/bin/levix.js +7 -0
- package/deploy/install.sh +182 -0
- package/deploy/levix.service +56 -0
- package/deploy/nginx/levix.leviro.net.conf +79 -0
- package/package.json +78 -0
- package/public/brand/apple-touch-icon.png +0 -0
- package/public/brand/banner.jpg +0 -0
- package/public/brand/banner.webp +0 -0
- package/public/brand/icon-16.png +0 -0
- package/public/brand/icon-192.png +0 -0
- package/public/brand/icon-32.png +0 -0
- package/public/brand/icon-512.png +0 -0
- package/public/brand/icon-512.webp +0 -0
- package/public/brand/mark.png +0 -0
- package/public/brand/mark.svg +16 -0
- package/public/brand/mark.webp +0 -0
- package/public/brand/wordmark.png +0 -0
- package/public/brand/wordmark.webp +0 -0
- package/public/dashboard.css +881 -0
- package/public/dashboard.js +1057 -0
- package/public/logo.svg +16 -0
- package/public/qrcode.min.js +1 -0
- package/public/socket.io.min.js +7 -0
- package/scheduler.cjs +197 -0
- package/src/auth/auth-storage.cjs +74 -0
- package/src/auth/use-database-auth-state.js +130 -0
- package/src/bootstrap/core.js +74 -0
- package/src/bootstrap/events.cjs +43 -0
- package/src/bootstrap/panel.js +97 -0
- package/src/cli.js +153 -0
- package/src/commands/autoschedule.cjs +84 -0
- package/src/commands/blacklist.cjs +116 -0
- package/src/commands/block.cjs +70 -0
- package/src/commands/calc.cjs +387 -0
- package/src/commands/debt.cjs +175 -0
- package/src/commands/deleteschedule.cjs +36 -0
- package/src/commands/gemini.cjs +727 -0
- package/src/commands/group/add.cjs +136 -0
- package/src/commands/group/all.cjs +43 -0
- package/src/commands/group/antiSpam.cjs +96 -0
- package/src/commands/group/antilink.cjs +167 -0
- package/src/commands/group/approveall.cjs +54 -0
- package/src/commands/group/clearwarns.cjs +41 -0
- package/src/commands/group/deletenote.cjs +33 -0
- package/src/commands/group/demote.cjs +62 -0
- package/src/commands/group/kick.cjs +85 -0
- package/src/commands/group/media.cjs +120 -0
- package/src/commands/group/members.cjs +59 -0
- package/src/commands/group/note.cjs +32 -0
- package/src/commands/group/promote.cjs +60 -0
- package/src/commands/group/removeall.cjs +42 -0
- package/src/commands/group/rules.cjs +31 -0
- package/src/commands/group/save.cjs +36 -0
- package/src/commands/group/setname.cjs +41 -0
- package/src/commands/group/setpp.cjs +51 -0
- package/src/commands/group/setrules.cjs +46 -0
- package/src/commands/group/setwarn.cjs +58 -0
- package/src/commands/group/tagadmins.cjs +50 -0
- package/src/commands/group/warn.cjs +105 -0
- package/src/commands/group/warns.cjs +66 -0
- package/src/commands/group/welcome.cjs +109 -0
- package/src/commands/group.cjs +160 -0
- package/src/commands/help.cjs +230 -0
- package/src/commands/listschedules.cjs +47 -0
- package/src/commands/loop.cjs +163 -0
- package/src/commands/memory.cjs +201 -0
- package/src/commands/mod.cjs +155 -0
- package/src/commands/notes.cjs +28 -0
- package/src/commands/perm.cjs +236 -0
- package/src/commands/ping.cjs +17 -0
- package/src/commands/poll.cjs +80 -0
- package/src/commands/prayer.cjs +64 -0
- package/src/commands/qr.cjs +39 -0
- package/src/commands/rand.cjs +130 -0
- package/src/commands/restart.cjs +25 -0
- package/src/commands/schedule.cjs +86 -0
- package/src/commands/score.cjs +130 -0
- package/src/commands/setprefix.cjs +40 -0
- package/src/commands/shortlink.cjs +74 -0
- package/src/commands/shutdown.cjs +45 -0
- package/src/commands/status.cjs +75 -0
- package/src/commands/stt.cjs +150 -0
- package/src/commands/todo.cjs +88 -0
- package/src/commands/tts.cjs +170 -0
- package/src/commands/unblock.cjs +57 -0
- package/src/commands/weather.cjs +79 -0
- package/src/config/ai-persona.md +17 -0
- package/src/config/baileys.config.js +51 -0
- package/src/config/brand.cjs +36 -0
- package/src/config/brand.esm.js +17 -0
- package/src/config/constants.js +16 -0
- package/src/config/defaults.cjs +91 -0
- package/src/config/lock.cjs +110 -0
- package/src/config/paths.cjs +90 -0
- package/src/config/runtime-config.cjs +219 -0
- package/src/config/secrets.cjs +130 -0
- package/src/config/settings.cjs +386 -0
- package/src/core/connection.js +199 -0
- package/src/core/events.js +61 -0
- package/src/core/socket.js +64 -0
- package/src/db/db.cjs +282 -0
- package/src/db/store.cjs +837 -0
- package/src/db/store.esm.js +17 -0
- package/src/domain/caddy.js +120 -0
- package/src/domain/command.js +380 -0
- package/src/domain/detect.js +173 -0
- package/src/domain/nginx.js +138 -0
- package/src/domain/system.js +148 -0
- package/src/handlers/command.handler.js +414 -0
- package/src/handlers/group.handler.js +73 -0
- package/src/handlers/message.handler.js +149 -0
- package/src/index.js +217 -0
- package/src/middleware/antispam.middleware.js +68 -0
- package/src/middleware/blacklist.middleware.js +29 -0
- package/src/middleware/forward-tracking.middleware.js +49 -0
- package/src/middleware/permissions.middleware.js +114 -0
- package/src/routes/dashboard.api.esm.js +779 -0
- package/src/services/aiAgent.cjs +317 -0
- package/src/services/aiTools.cjs +635 -0
- package/src/utils/datetime.cjs +61 -0
- package/src/utils/lid-helper.esm.js +143 -0
- package/src/utils/logger.cjs +72 -0
- package/src/utils/memory.cjs +385 -0
- package/src/utils/normalizeJid.cjs +65 -0
- package/src/utils/normalizeJid.esm.js +37 -0
- package/src/utils/openBrowser.cjs +129 -0
- package/src/utils/permissions.cjs +154 -0
- package/src/utils/permissions.esm.js +375 -0
- package/src/utils/recentMessageCache.esm.js +70 -0
- package/src/utils/requestOrigin.cjs +102 -0
- package/src/utils/sendBotMessage.cjs +43 -0
- package/src/utils/sendBotMessage.esm.js +139 -0
- package/src/utils/statusMessage.cjs +179 -0
- package/src/utils/storage-hub.cjs +49 -0
- package/src/utils/storage-hub.esm.js +24 -0
- package/src/utils/storage.cjs +80 -0
- package/src/utils/storage.esm.js +88 -0
- package/src/utils/textDecode.cjs +197 -0
- package/src/utils/thumbnail.cjs +591 -0
- package/src/utils/typing.esm.js +102 -0
- package/views/dashboard.ejs +329 -0
- package/views/login.ejs +105 -0
- package/views/qr.ejs +108 -0
- package/views/setup.ejs +122 -0
package/src/db/store.cjs
ADDED
|
@@ -0,0 +1,837 @@
|
|
|
1
|
+
// The bot's view of the database.
|
|
2
|
+
//
|
|
3
|
+
// Every read and every write in here is synchronous, because SQLite is
|
|
4
|
+
// synchronous and so are the ~40 call sites that use it — the anti-spam and
|
|
5
|
+
// blacklist middleware, the prefix lookup, every permission check all run
|
|
6
|
+
// inline on the per-message path with no `await` in sight.
|
|
7
|
+
//
|
|
8
|
+
// There is no cache and no write-behind queue: a query against a local SQLite
|
|
9
|
+
// file is a function call, not a round trip. What you read is what is on disk.
|
|
10
|
+
//
|
|
11
|
+
// The file itself, the schema and the migrations are in src/db/db.cjs.
|
|
12
|
+
//
|
|
13
|
+
// This module is CommonJS on purpose: both the CJS command files and the ESM
|
|
14
|
+
// handlers need the *same* singleton, and only a CJS module can be required
|
|
15
|
+
// synchronously from both sides.
|
|
16
|
+
|
|
17
|
+
const logger = require("../utils/logger.cjs");
|
|
18
|
+
const { q, parseJson, sweepExpired, checkpoint, DB_PATH } = require("./db.cjs");
|
|
19
|
+
|
|
20
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
21
|
+
const DEFAULT_FORWARD_TTL_DAYS = 30;
|
|
22
|
+
|
|
23
|
+
// ===================================================================
|
|
24
|
+
// --- Lifecycle ---
|
|
25
|
+
// ===================================================================
|
|
26
|
+
//
|
|
27
|
+
// The database is opened the moment db.cjs is required, so there is nothing
|
|
28
|
+
// to await. These exist because the rest of the codebase calls them; they now
|
|
29
|
+
// do the housekeeping the boot sequence needs: sweep, and report.
|
|
30
|
+
|
|
31
|
+
let sweepTimer = null;
|
|
32
|
+
|
|
33
|
+
async function initStore() {
|
|
34
|
+
sweepExpired();
|
|
35
|
+
if (!sweepTimer) {
|
|
36
|
+
sweepTimer = setInterval(sweepExpired, 6 * 60 * 60 * 1000);
|
|
37
|
+
sweepTimer.unref();
|
|
38
|
+
}
|
|
39
|
+
logger.info(
|
|
40
|
+
`[Store] SQLite ready at ${DB_PATH} — ${countGroups()} group(s), ` +
|
|
41
|
+
`${countUsers()} user(s), ${countNotes()} note(s), ${countLidMappings()} LID mapping(s)`
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Nothing is ever in flight, but shutdown paths call this. Flush the WAL. */
|
|
46
|
+
async function flushStore() {
|
|
47
|
+
checkpoint();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function isStoreReady() {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Kept for the dashboard's health card. Always zero now. */
|
|
55
|
+
function pendingWrites() {
|
|
56
|
+
return 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ===================================================================
|
|
60
|
+
// --- Helpers ---
|
|
61
|
+
// ===================================================================
|
|
62
|
+
|
|
63
|
+
function digitsOf(value) {
|
|
64
|
+
const match = String(value || "").match(/\d{5,}/);
|
|
65
|
+
return match ? match[0] : null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const bool = (value) => (value ? 1 : 0);
|
|
69
|
+
|
|
70
|
+
/** Forward-score retention. Read at call time so the dashboard can change it. */
|
|
71
|
+
function forwardExpiry() {
|
|
72
|
+
let days = DEFAULT_FORWARD_TTL_DAYS;
|
|
73
|
+
try {
|
|
74
|
+
days = require("../config/settings.cjs").get("forward_score_ttl_days");
|
|
75
|
+
} catch {
|
|
76
|
+
// settings.cjs reads through this module; during its own load we take the
|
|
77
|
+
// default rather than recursing.
|
|
78
|
+
}
|
|
79
|
+
return Date.now() + Number(days || DEFAULT_FORWARD_TTL_DAYS) * DAY_MS;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ===================================================================
|
|
83
|
+
// --- Bot settings ---
|
|
84
|
+
// ===================================================================
|
|
85
|
+
//
|
|
86
|
+
// One table for the prefix, everything the dashboard saves ("setting:*"), the
|
|
87
|
+
// permission / alias / disabled override maps, and the generated secrets.
|
|
88
|
+
// Values are JSON, so an object or an array round-trips as itself.
|
|
89
|
+
|
|
90
|
+
function getBotSetting(key, defaultValue = null) {
|
|
91
|
+
const row = q("SELECT value FROM bot_settings WHERE key = ?").get(key);
|
|
92
|
+
if (!row) return defaultValue;
|
|
93
|
+
const value = parseJson(row.value, undefined);
|
|
94
|
+
return value === undefined ? defaultValue : value;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function saveBotSetting(key, value) {
|
|
98
|
+
q(
|
|
99
|
+
`INSERT INTO bot_settings (key, value) VALUES (?, ?)
|
|
100
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value`
|
|
101
|
+
).run(key, JSON.stringify(value ?? null));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function deleteBotSetting(key) {
|
|
105
|
+
q("DELETE FROM bot_settings WHERE key = ?").run(key);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ===================================================================
|
|
109
|
+
// --- Group settings ---
|
|
110
|
+
// ===================================================================
|
|
111
|
+
|
|
112
|
+
function getGroupSettings(groupId) {
|
|
113
|
+
const row = q("SELECT settings FROM group_settings WHERE group_id = ?").get(
|
|
114
|
+
groupId
|
|
115
|
+
);
|
|
116
|
+
return row ? parseJson(row.settings, {}) : {};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function saveGroupSettings(groupId, settings) {
|
|
120
|
+
q(
|
|
121
|
+
`INSERT INTO group_settings (group_id, settings) VALUES (?, ?)
|
|
122
|
+
ON CONFLICT(group_id) DO UPDATE SET settings = excluded.settings`
|
|
123
|
+
).run(groupId, JSON.stringify(settings || {}));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function getAllGroupSettings() {
|
|
127
|
+
return q("SELECT group_id, settings FROM group_settings")
|
|
128
|
+
.all()
|
|
129
|
+
.map((row) => ({
|
|
130
|
+
group_id: row.group_id,
|
|
131
|
+
settings: parseJson(row.settings, {}),
|
|
132
|
+
}));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function countGroups() {
|
|
136
|
+
return q("SELECT COUNT(*) AS n FROM group_settings").get().n;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ===================================================================
|
|
140
|
+
// --- Warnings ---
|
|
141
|
+
// ===================================================================
|
|
142
|
+
|
|
143
|
+
function getUserWarnings(groupId, userId) {
|
|
144
|
+
const row = q(
|
|
145
|
+
"SELECT warnings FROM warnings WHERE group_id = ? AND user_id = ?"
|
|
146
|
+
).get(groupId, userId);
|
|
147
|
+
return row ? parseJson(row.warnings, []) : [];
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function saveUserWarnings(groupId, userId, warningsArray) {
|
|
151
|
+
q(
|
|
152
|
+
`INSERT INTO warnings (group_id, user_id, warnings) VALUES (?, ?, ?)
|
|
153
|
+
ON CONFLICT(group_id, user_id) DO UPDATE SET warnings = excluded.warnings`
|
|
154
|
+
).run(groupId, userId, JSON.stringify(warningsArray || []));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function clearUserWarnings(groupId, userId) {
|
|
158
|
+
q("DELETE FROM warnings WHERE group_id = ? AND user_id = ?").run(
|
|
159
|
+
groupId,
|
|
160
|
+
userId
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function getAllWarnings() {
|
|
165
|
+
return q("SELECT group_id, user_id, warnings FROM warnings")
|
|
166
|
+
.all()
|
|
167
|
+
.map((row) => ({
|
|
168
|
+
group_id: row.group_id,
|
|
169
|
+
user_id: row.user_id,
|
|
170
|
+
warnings: parseJson(row.warnings, []),
|
|
171
|
+
}));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function countWarnings() {
|
|
175
|
+
return q("SELECT COUNT(*) AS n FROM warnings").get().n;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ===================================================================
|
|
179
|
+
// --- Todos ---
|
|
180
|
+
// ===================================================================
|
|
181
|
+
|
|
182
|
+
function getUserTodos(userId) {
|
|
183
|
+
const row = q("SELECT tasks FROM todos WHERE user_id = ?").get(userId);
|
|
184
|
+
return row ? parseJson(row.tasks, []) : [];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function saveUserTodos(userId, tasksArray) {
|
|
188
|
+
q(
|
|
189
|
+
`INSERT INTO todos (user_id, tasks) VALUES (?, ?)
|
|
190
|
+
ON CONFLICT(user_id) DO UPDATE SET tasks = excluded.tasks`
|
|
191
|
+
).run(userId, JSON.stringify(tasksArray || []));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function getAllTodos() {
|
|
195
|
+
return q("SELECT user_id, tasks FROM todos")
|
|
196
|
+
.all()
|
|
197
|
+
.map((row) => ({ user_id: row.user_id, tasks: parseJson(row.tasks, []) }));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function countTodos() {
|
|
201
|
+
return q("SELECT COUNT(*) AS n FROM todos").get().n;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ===================================================================
|
|
205
|
+
// --- Notes ---
|
|
206
|
+
// ===================================================================
|
|
207
|
+
|
|
208
|
+
function saveNote(groupId, keyword, text) {
|
|
209
|
+
q(
|
|
210
|
+
`INSERT INTO notes (group_id, keyword, note_text) VALUES (?, ?, ?)
|
|
211
|
+
ON CONFLICT(group_id, keyword) DO UPDATE SET note_text = excluded.note_text`
|
|
212
|
+
).run(groupId, keyword, text);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function getNote(groupId, keyword) {
|
|
216
|
+
const row = q(
|
|
217
|
+
"SELECT note_text FROM notes WHERE group_id = ? AND keyword = ?"
|
|
218
|
+
).get(groupId, keyword);
|
|
219
|
+
return row ? row.note_text : null;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function getAllNotes(groupId) {
|
|
223
|
+
return q("SELECT keyword FROM notes WHERE group_id = ? ORDER BY keyword")
|
|
224
|
+
.all(groupId)
|
|
225
|
+
.map((row) => row.keyword);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function deleteNote(groupId, keyword) {
|
|
229
|
+
const { changes } = q(
|
|
230
|
+
"DELETE FROM notes WHERE group_id = ? AND keyword = ?"
|
|
231
|
+
).run(groupId, keyword);
|
|
232
|
+
return changes > 0;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function getAllNotesFlat(limit = 100) {
|
|
236
|
+
return q(
|
|
237
|
+
`SELECT group_id, keyword, note_text FROM notes
|
|
238
|
+
ORDER BY group_id, keyword LIMIT ?`
|
|
239
|
+
).all(limit);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function countNotes() {
|
|
243
|
+
return q("SELECT COUNT(*) AS n FROM notes").get().n;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// ===================================================================
|
|
247
|
+
// --- Pairing QR ---
|
|
248
|
+
// ===================================================================
|
|
249
|
+
|
|
250
|
+
function saveQrCode(qr) {
|
|
251
|
+
q(
|
|
252
|
+
`INSERT INTO qr_codes (id, qr_string) VALUES (1, ?)
|
|
253
|
+
ON CONFLICT(id) DO UPDATE SET qr_string = excluded.qr_string`
|
|
254
|
+
).run(qr);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function getQrCode() {
|
|
258
|
+
const row = q("SELECT qr_string FROM qr_codes WHERE id = 1").get();
|
|
259
|
+
return row ? row.qr_string : null;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function deleteQrCode() {
|
|
263
|
+
q("DELETE FROM qr_codes WHERE id = 1").run();
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// ===================================================================
|
|
267
|
+
// --- LID <-> phone number (Baileys v7) ---
|
|
268
|
+
// ===================================================================
|
|
269
|
+
|
|
270
|
+
function storeLidPnMapping(lid, pn, deviceIndex = 0) {
|
|
271
|
+
q(
|
|
272
|
+
`INSERT INTO lid_mapping (lid, pn, device_index, updated_at) VALUES (?, ?, ?, ?)
|
|
273
|
+
ON CONFLICT(lid) DO UPDATE SET
|
|
274
|
+
pn = excluded.pn,
|
|
275
|
+
device_index = excluded.device_index,
|
|
276
|
+
updated_at = excluded.updated_at`
|
|
277
|
+
).run(lid, pn, deviceIndex, Date.now());
|
|
278
|
+
logger.debug(`[LID] Stored mapping: ${lid} <-> ${pn}`);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function storeLidPnMappings(mappings) {
|
|
282
|
+
if (!mappings?.length) return;
|
|
283
|
+
for (const { lid, pn, deviceIndex = 0 } of mappings) {
|
|
284
|
+
storeLidPnMapping(lid, pn, deviceIndex);
|
|
285
|
+
}
|
|
286
|
+
logger.info(`[LID] Stored ${mappings.length} mappings`);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function getLidForPn(pn) {
|
|
290
|
+
const row = q("SELECT lid FROM lid_mapping WHERE pn = ?").get(pn);
|
|
291
|
+
return row ? row.lid : null;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function getLidsForPns(pns) {
|
|
295
|
+
const map = new Map();
|
|
296
|
+
if (!pns?.length) return map;
|
|
297
|
+
for (const pn of pns) {
|
|
298
|
+
const lid = getLidForPn(pn);
|
|
299
|
+
if (lid) map.set(pn, lid);
|
|
300
|
+
}
|
|
301
|
+
return map;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function getPnForLid(lid) {
|
|
305
|
+
const row = q("SELECT pn FROM lid_mapping WHERE lid = ?").get(lid);
|
|
306
|
+
return row ? row.pn : null;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function getAllLidMappings() {
|
|
310
|
+
return q("SELECT lid, pn, device_index FROM lid_mapping").all();
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function countLidMappings() {
|
|
314
|
+
return q("SELECT COUNT(*) AS n FROM lid_mapping").get().n;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// ===================================================================
|
|
318
|
+
// --- User metadata & bot roles ---
|
|
319
|
+
// ===================================================================
|
|
320
|
+
|
|
321
|
+
function rowToUser(row) {
|
|
322
|
+
if (!row) return null;
|
|
323
|
+
return {
|
|
324
|
+
jid: row.user_jid,
|
|
325
|
+
lid: row.user_lid ?? null,
|
|
326
|
+
phone: row.phone_number ?? null,
|
|
327
|
+
isOwner: row.is_owner === 1,
|
|
328
|
+
isAdmin: row.is_admin === 1,
|
|
329
|
+
firstSeen: row.first_seen,
|
|
330
|
+
lastSeen: row.last_seen,
|
|
331
|
+
displayName: row.display_name ?? null,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function userRow(jid) {
|
|
336
|
+
return q("SELECT * FROM user_metadata WHERE user_jid = ?").get(jid);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Save or update user metadata.
|
|
341
|
+
*
|
|
342
|
+
* Role flags (`isOwner` / `isAdmin`) are written ONLY when the caller passes
|
|
343
|
+
* them. The message handler calls this for every incoming message without any
|
|
344
|
+
* role information, and resetting the flags there demoted real owners on their
|
|
345
|
+
* next message. Same story for `lid` / `phone` / `displayName`: a missing
|
|
346
|
+
* field keeps whatever we already knew instead of nulling it out.
|
|
347
|
+
*
|
|
348
|
+
* @param {object} userData - { jid, lid?, phone?, isOwner?, isAdmin?, displayName? }
|
|
349
|
+
*/
|
|
350
|
+
function saveUserMetadata(userData) {
|
|
351
|
+
if (!userData?.jid) return;
|
|
352
|
+
const now = Date.now();
|
|
353
|
+
const existing = userRow(userData.jid);
|
|
354
|
+
|
|
355
|
+
const row = {
|
|
356
|
+
user_jid: userData.jid,
|
|
357
|
+
user_lid: userData.lid || existing?.user_lid || null,
|
|
358
|
+
phone_number: userData.phone || existing?.phone_number || null,
|
|
359
|
+
is_owner:
|
|
360
|
+
userData.isOwner === undefined
|
|
361
|
+
? existing?.is_owner === 1
|
|
362
|
+
? 1
|
|
363
|
+
: 0
|
|
364
|
+
: bool(userData.isOwner),
|
|
365
|
+
is_admin:
|
|
366
|
+
userData.isAdmin === undefined
|
|
367
|
+
? existing?.is_admin === 1
|
|
368
|
+
? 1
|
|
369
|
+
: 0
|
|
370
|
+
: bool(userData.isAdmin),
|
|
371
|
+
first_seen: existing?.first_seen ?? now,
|
|
372
|
+
last_seen: now,
|
|
373
|
+
display_name: userData.displayName || existing?.display_name || null,
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
q(
|
|
377
|
+
`INSERT INTO user_metadata
|
|
378
|
+
(user_jid, user_lid, phone_number, is_owner, is_admin, first_seen, last_seen, display_name)
|
|
379
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
380
|
+
ON CONFLICT(user_jid) DO UPDATE SET
|
|
381
|
+
user_lid = excluded.user_lid,
|
|
382
|
+
phone_number = excluded.phone_number,
|
|
383
|
+
is_owner = excluded.is_owner,
|
|
384
|
+
is_admin = excluded.is_admin,
|
|
385
|
+
last_seen = excluded.last_seen,
|
|
386
|
+
display_name = excluded.display_name`
|
|
387
|
+
).run(
|
|
388
|
+
row.user_jid,
|
|
389
|
+
row.user_lid,
|
|
390
|
+
row.phone_number,
|
|
391
|
+
row.is_owner,
|
|
392
|
+
row.is_admin,
|
|
393
|
+
row.first_seen,
|
|
394
|
+
row.last_seen,
|
|
395
|
+
row.display_name
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Get user metadata by JID, LID, or bare phone number. Four steps, in order:
|
|
401
|
+
* exact JID, then LID, then phone, then a digits-only match against either
|
|
402
|
+
* identifier (which is what handles the `:12` device suffixes).
|
|
403
|
+
*/
|
|
404
|
+
function getUserMetadata(identifier) {
|
|
405
|
+
if (!identifier) return null;
|
|
406
|
+
|
|
407
|
+
let row = userRow(identifier);
|
|
408
|
+
|
|
409
|
+
if (!row) {
|
|
410
|
+
row = q("SELECT * FROM user_metadata WHERE user_lid = ?").get(identifier);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if (!row) {
|
|
414
|
+
row = q("SELECT * FROM user_metadata WHERE phone_number = ?").get(
|
|
415
|
+
identifier
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if (!row) {
|
|
420
|
+
const phone = digitsOf(identifier);
|
|
421
|
+
if (phone) {
|
|
422
|
+
row =
|
|
423
|
+
q("SELECT * FROM user_metadata WHERE phone_number = ?").get(phone) ||
|
|
424
|
+
q(
|
|
425
|
+
`SELECT * FROM user_metadata
|
|
426
|
+
WHERE user_jid LIKE ? OR user_lid LIKE ?
|
|
427
|
+
LIMIT 1`
|
|
428
|
+
).get(`${phone}@%`, `${phone}@%`);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
return rowToUser(row);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function isUserOwner(identifier) {
|
|
436
|
+
return getUserMetadata(identifier)?.isOwner === true;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Check if user carries the bot-level admin role (not the same thing as being
|
|
441
|
+
* an admin of a WhatsApp group).
|
|
442
|
+
*/
|
|
443
|
+
function isUserBotAdmin(identifier) {
|
|
444
|
+
return getUserMetadata(identifier)?.isAdmin === true;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function getAllOwners() {
|
|
448
|
+
return q("SELECT * FROM user_metadata WHERE is_owner = 1")
|
|
449
|
+
.all()
|
|
450
|
+
.map(rowToUser);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function getAllBotAdmins() {
|
|
454
|
+
return q("SELECT * FROM user_metadata WHERE is_admin = 1")
|
|
455
|
+
.all()
|
|
456
|
+
.map(rowToUser);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function getAllUsers() {
|
|
460
|
+
return q("SELECT * FROM user_metadata ORDER BY last_seen DESC")
|
|
461
|
+
.all()
|
|
462
|
+
.map(rowToUser);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function countUsers() {
|
|
466
|
+
return q("SELECT COUNT(*) AS n FROM user_metadata").get().n;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Grant or revoke a bot-level role, creating the user record when needed.
|
|
471
|
+
*
|
|
472
|
+
* @param {string} identifier - JID / LID / phone
|
|
473
|
+
* @param {"owner"|"admin"} role
|
|
474
|
+
* @param {boolean} enabled
|
|
475
|
+
* @returns {object|null} the stored user record
|
|
476
|
+
*/
|
|
477
|
+
function setUserRole(identifier, role, enabled = true) {
|
|
478
|
+
if (!identifier) return null;
|
|
479
|
+
|
|
480
|
+
const wanted = String(role).toLowerCase() === "owner" ? "isOwner" : "isAdmin";
|
|
481
|
+
const existing = getUserMetadata(identifier);
|
|
482
|
+
|
|
483
|
+
// Keep LIDs as LIDs; anything else becomes a PN JID so the roster stays
|
|
484
|
+
// comparable with what the permission checks resolve senders to.
|
|
485
|
+
let jid = existing?.jid;
|
|
486
|
+
if (!jid) {
|
|
487
|
+
if (String(identifier).includes("@")) jid = identifier;
|
|
488
|
+
else {
|
|
489
|
+
const phone = digitsOf(identifier);
|
|
490
|
+
jid = phone ? `${phone}@s.whatsapp.net` : identifier;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
saveUserMetadata({
|
|
495
|
+
jid,
|
|
496
|
+
lid:
|
|
497
|
+
existing?.lid || (String(identifier).endsWith("@lid") ? identifier : null),
|
|
498
|
+
phone: existing?.phone || digitsOf(identifier),
|
|
499
|
+
displayName: existing?.displayName || null,
|
|
500
|
+
[wanted]: !!enabled,
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
return getUserMetadata(jid);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function updateUserLastSeen(jid) {
|
|
507
|
+
q("UPDATE user_metadata SET last_seen = ? WHERE user_jid = ?").run(
|
|
508
|
+
Date.now(),
|
|
509
|
+
jid
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// ===================================================================
|
|
514
|
+
// --- Forward scores ---
|
|
515
|
+
// ===================================================================
|
|
516
|
+
|
|
517
|
+
function incrementForwardScore(messageId, groupId, senderId) {
|
|
518
|
+
const now = Date.now();
|
|
519
|
+
q(
|
|
520
|
+
`INSERT INTO forward_scores
|
|
521
|
+
(message_id, group_id, original_sender, forward_count,
|
|
522
|
+
first_forwarded_at, last_forwarded_at, expires_at)
|
|
523
|
+
VALUES (?, ?, ?, 1, ?, ?, ?)
|
|
524
|
+
ON CONFLICT(message_id) DO UPDATE SET
|
|
525
|
+
forward_count = forward_count + 1,
|
|
526
|
+
last_forwarded_at = excluded.last_forwarded_at,
|
|
527
|
+
expires_at = excluded.expires_at`
|
|
528
|
+
).run(messageId, groupId, senderId, now, now, forwardExpiry());
|
|
529
|
+
|
|
530
|
+
return (
|
|
531
|
+
q("SELECT forward_count FROM forward_scores WHERE message_id = ?").get(
|
|
532
|
+
messageId
|
|
533
|
+
)?.forward_count ?? 1
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
function getForwardScore(messageId) {
|
|
538
|
+
const row = q("SELECT * FROM forward_scores WHERE message_id = ?").get(
|
|
539
|
+
messageId
|
|
540
|
+
);
|
|
541
|
+
return row
|
|
542
|
+
? {
|
|
543
|
+
count: row.forward_count,
|
|
544
|
+
sender: row.original_sender,
|
|
545
|
+
firstForwardedAt: row.first_forwarded_at,
|
|
546
|
+
lastForwardedAt: row.last_forwarded_at,
|
|
547
|
+
}
|
|
548
|
+
: null;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function getTopForwardedMessages(groupId, limit = 10) {
|
|
552
|
+
return q(
|
|
553
|
+
`SELECT message_id, original_sender, forward_count, first_forwarded_at
|
|
554
|
+
FROM forward_scores
|
|
555
|
+
WHERE group_id = ?
|
|
556
|
+
ORDER BY forward_count DESC
|
|
557
|
+
LIMIT ?`
|
|
558
|
+
).all(groupId, limit);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// ===================================================================
|
|
562
|
+
// --- Debts ---
|
|
563
|
+
// ===================================================================
|
|
564
|
+
|
|
565
|
+
function debtRow(row) {
|
|
566
|
+
if (!row) return null;
|
|
567
|
+
return { ...row, settled: row.settled === 1 };
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
function addDebt({
|
|
571
|
+
groupId,
|
|
572
|
+
debtorId,
|
|
573
|
+
creditorId,
|
|
574
|
+
amount,
|
|
575
|
+
currency = "USD",
|
|
576
|
+
description = null,
|
|
577
|
+
}) {
|
|
578
|
+
const createdAt = Date.now();
|
|
579
|
+
const { lastInsertRowid } = q(
|
|
580
|
+
`INSERT INTO debts
|
|
581
|
+
(group_id, debtor_id, creditor_id, amount, currency, description, created_at, settled, settled_at)
|
|
582
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, 0, NULL)`
|
|
583
|
+
).run(groupId, debtorId, creditorId, amount, currency, description, createdAt);
|
|
584
|
+
|
|
585
|
+
return getDebt(Number(lastInsertRowid));
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
function getDebt(id) {
|
|
589
|
+
return debtRow(q("SELECT * FROM debts WHERE id = ?").get(id));
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
function deleteDebt(id) {
|
|
593
|
+
const { changes } = q("DELETE FROM debts WHERE id = ?").run(id);
|
|
594
|
+
return changes > 0;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
function listDebts(groupId, { settled = false } = {}) {
|
|
598
|
+
return q(
|
|
599
|
+
`SELECT * FROM debts WHERE group_id = ? AND settled = ?
|
|
600
|
+
ORDER BY created_at DESC`
|
|
601
|
+
)
|
|
602
|
+
.all(groupId, bool(settled))
|
|
603
|
+
.map(debtRow);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function getRecentDebts(limit = 50) {
|
|
607
|
+
return q("SELECT * FROM debts ORDER BY created_at DESC LIMIT ?")
|
|
608
|
+
.all(limit)
|
|
609
|
+
.map(debtRow);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
function countDebts(settled = false) {
|
|
613
|
+
return q("SELECT COUNT(*) AS n FROM debts WHERE settled = ?").get(
|
|
614
|
+
bool(settled)
|
|
615
|
+
).n;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// ===================================================================
|
|
619
|
+
// --- Scheduled messages ---
|
|
620
|
+
// ===================================================================
|
|
621
|
+
//
|
|
622
|
+
// Used to be config/schedule.json. In the database the dashboard can list
|
|
623
|
+
// them, and a crash in the middle of a write can't truncate the file.
|
|
624
|
+
//
|
|
625
|
+
// A job is `{ id, type, targetJid, message, cronString?, date?, status,
|
|
626
|
+
// creatorJid }` — the shape !schedule and !autoschedule have always built.
|
|
627
|
+
|
|
628
|
+
function scheduleRow(row) {
|
|
629
|
+
if (!row) return null;
|
|
630
|
+
return {
|
|
631
|
+
id: row.id,
|
|
632
|
+
type: row.type,
|
|
633
|
+
targetJid: row.target_jid,
|
|
634
|
+
message: row.message,
|
|
635
|
+
cronString: row.cron_string ?? undefined,
|
|
636
|
+
date: row.date ?? undefined,
|
|
637
|
+
status: row.status,
|
|
638
|
+
creatorJid: row.creator_jid ?? null,
|
|
639
|
+
createdAt: row.created_at,
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
function getSchedules() {
|
|
644
|
+
return q("SELECT * FROM schedules ORDER BY created_at").all().map(scheduleRow);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
function getSchedule(id) {
|
|
648
|
+
return scheduleRow(q("SELECT * FROM schedules WHERE id = ?").get(String(id)));
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
function saveSchedule(job) {
|
|
652
|
+
q(
|
|
653
|
+
`INSERT INTO schedules
|
|
654
|
+
(id, type, target_jid, message, cron_string, date, status, creator_jid, created_at)
|
|
655
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
656
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
657
|
+
type = excluded.type,
|
|
658
|
+
target_jid = excluded.target_jid,
|
|
659
|
+
message = excluded.message,
|
|
660
|
+
cron_string = excluded.cron_string,
|
|
661
|
+
date = excluded.date,
|
|
662
|
+
status = excluded.status,
|
|
663
|
+
creator_jid = excluded.creator_jid`
|
|
664
|
+
).run(
|
|
665
|
+
String(job.id),
|
|
666
|
+
job.type,
|
|
667
|
+
job.targetJid,
|
|
668
|
+
job.message,
|
|
669
|
+
job.cronString ?? null,
|
|
670
|
+
job.date ?? null,
|
|
671
|
+
job.status || "pending",
|
|
672
|
+
job.creatorJid ?? null,
|
|
673
|
+
job.createdAt ?? Date.now()
|
|
674
|
+
);
|
|
675
|
+
return getSchedule(job.id);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
function setScheduleStatus(id, status) {
|
|
679
|
+
q("UPDATE schedules SET status = ? WHERE id = ?").run(status, String(id));
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
function deleteSchedule(id) {
|
|
683
|
+
const { changes } = q("DELETE FROM schedules WHERE id = ?").run(String(id));
|
|
684
|
+
return changes > 0;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
function countSchedules() {
|
|
688
|
+
return q("SELECT COUNT(*) AS n FROM schedules").get().n;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// ===================================================================
|
|
692
|
+
// --- AI conversation history ---
|
|
693
|
+
// ===================================================================
|
|
694
|
+
//
|
|
695
|
+
// One row per chat, no expiry. `!del` clears one chat, `!delall` clears the
|
|
696
|
+
// table. The callers are async (they always were, back when this was a
|
|
697
|
+
// separate database), so storage-hub keeps the async signatures.
|
|
698
|
+
|
|
699
|
+
function getChatHistory(chatId) {
|
|
700
|
+
const row = q("SELECT history FROM ai_history WHERE chat_id = ?").get(chatId);
|
|
701
|
+
return row ? parseJson(row.history, []) : [];
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
function saveChatHistory(chatId, historyArray) {
|
|
705
|
+
q(
|
|
706
|
+
`INSERT INTO ai_history (chat_id, history, updated_at) VALUES (?, ?, ?)
|
|
707
|
+
ON CONFLICT(chat_id) DO UPDATE SET
|
|
708
|
+
history = excluded.history,
|
|
709
|
+
updated_at = excluded.updated_at`
|
|
710
|
+
).run(chatId, JSON.stringify(historyArray || []), Date.now());
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
function deleteChatHistory(chatId) {
|
|
714
|
+
const { changes } = q("DELETE FROM ai_history WHERE chat_id = ?").run(chatId);
|
|
715
|
+
return changes > 0;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
function deleteAllChatHistories() {
|
|
719
|
+
q("DELETE FROM ai_history").run();
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// ===================================================================
|
|
723
|
+
// --- Baileys auth ---
|
|
724
|
+
// ===================================================================
|
|
725
|
+
//
|
|
726
|
+
// Values arrive already serialized by BufferJSON (see auth-storage.cjs), so
|
|
727
|
+
// they are stored as text and handed back as text.
|
|
728
|
+
|
|
729
|
+
function authRead(key) {
|
|
730
|
+
const row = q("SELECT value FROM baileys_auth WHERE key = ?").get(key);
|
|
731
|
+
return row ? row.value : null;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
function authWrite(key, serialized) {
|
|
735
|
+
q(
|
|
736
|
+
`INSERT INTO baileys_auth (key, value) VALUES (?, ?)
|
|
737
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value`
|
|
738
|
+
).run(key, serialized);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
function authRemove(key) {
|
|
742
|
+
q("DELETE FROM baileys_auth WHERE key = ?").run(key);
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
function authClearAll() {
|
|
746
|
+
q("DELETE FROM baileys_auth").run();
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
function hasCredentials() {
|
|
750
|
+
return !!q("SELECT 1 AS x FROM baileys_auth WHERE key = 'creds'").get();
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
module.exports = {
|
|
754
|
+
// Lifecycle
|
|
755
|
+
initStore,
|
|
756
|
+
flushStore,
|
|
757
|
+
isStoreReady,
|
|
758
|
+
pendingWrites,
|
|
759
|
+
// Bot settings
|
|
760
|
+
getBotSetting,
|
|
761
|
+
saveBotSetting,
|
|
762
|
+
deleteBotSetting,
|
|
763
|
+
// Group settings
|
|
764
|
+
getGroupSettings,
|
|
765
|
+
saveGroupSettings,
|
|
766
|
+
getAllGroupSettings,
|
|
767
|
+
countGroups,
|
|
768
|
+
// Warnings
|
|
769
|
+
getUserWarnings,
|
|
770
|
+
saveUserWarnings,
|
|
771
|
+
clearUserWarnings,
|
|
772
|
+
getAllWarnings,
|
|
773
|
+
countWarnings,
|
|
774
|
+
// Todos
|
|
775
|
+
getUserTodos,
|
|
776
|
+
saveUserTodos,
|
|
777
|
+
countTodos,
|
|
778
|
+
getAllTodos,
|
|
779
|
+
// Notes
|
|
780
|
+
saveNote,
|
|
781
|
+
getNote,
|
|
782
|
+
getAllNotes,
|
|
783
|
+
deleteNote,
|
|
784
|
+
getAllNotesFlat,
|
|
785
|
+
countNotes,
|
|
786
|
+
// QR
|
|
787
|
+
saveQrCode,
|
|
788
|
+
getQrCode,
|
|
789
|
+
deleteQrCode,
|
|
790
|
+
// LID mapping
|
|
791
|
+
storeLidPnMapping,
|
|
792
|
+
storeLidPnMappings,
|
|
793
|
+
getLidForPn,
|
|
794
|
+
getLidsForPns,
|
|
795
|
+
getPnForLid,
|
|
796
|
+
getAllLidMappings,
|
|
797
|
+
// User metadata & roles
|
|
798
|
+
saveUserMetadata,
|
|
799
|
+
getUserMetadata,
|
|
800
|
+
isUserOwner,
|
|
801
|
+
isUserBotAdmin,
|
|
802
|
+
getAllOwners,
|
|
803
|
+
getAllBotAdmins,
|
|
804
|
+
getAllUsers,
|
|
805
|
+
countUsers,
|
|
806
|
+
setUserRole,
|
|
807
|
+
updateUserLastSeen,
|
|
808
|
+
// Forward scores
|
|
809
|
+
incrementForwardScore,
|
|
810
|
+
getForwardScore,
|
|
811
|
+
getTopForwardedMessages,
|
|
812
|
+
// Debts
|
|
813
|
+
addDebt,
|
|
814
|
+
getDebt,
|
|
815
|
+
deleteDebt,
|
|
816
|
+
listDebts,
|
|
817
|
+
getRecentDebts,
|
|
818
|
+
countDebts,
|
|
819
|
+
// Schedules
|
|
820
|
+
getSchedules,
|
|
821
|
+
getSchedule,
|
|
822
|
+
saveSchedule,
|
|
823
|
+
setScheduleStatus,
|
|
824
|
+
deleteSchedule,
|
|
825
|
+
countSchedules,
|
|
826
|
+
// AI history
|
|
827
|
+
getChatHistory,
|
|
828
|
+
saveChatHistory,
|
|
829
|
+
deleteChatHistory,
|
|
830
|
+
deleteAllChatHistories,
|
|
831
|
+
// Baileys auth
|
|
832
|
+
authRead,
|
|
833
|
+
authWrite,
|
|
834
|
+
authRemove,
|
|
835
|
+
authClearAll,
|
|
836
|
+
hasCredentials,
|
|
837
|
+
};
|