levix-bot 3.3.0-rc.2 → 4.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/README.md +13 -9
- package/app.cjs +49 -46
- package/package.json +9 -1
- package/public/dashboard/assets/index-BBBXWzM0.js +391 -0
- package/public/dashboard/assets/index-pYaivHON.css +1 -0
- package/public/dashboard/index.html +19 -0
- package/public/dashboard.css +391 -66
- package/public/dashboard.js +593 -42
- package/scheduler.cjs +9 -14
- package/src/auth/use-database-auth-state.js +2 -4
- package/src/bootstrap/core.js +2 -5
- package/src/bootstrap/panel.js +10 -5
- package/src/cli.js +3 -5
- package/src/commands/autoschedule.cjs +1 -4
- package/src/commands/blacklist.cjs +22 -20
- package/src/commands/block.cjs +2 -4
- package/src/commands/calc.cjs +9 -16
- package/src/commands/digit.cjs +97 -0
- package/src/commands/gemini.cjs +147 -161
- package/src/commands/group/add.cjs +7 -16
- package/src/commands/group/antiSpam.cjs +5 -11
- package/src/commands/group/antilink.cjs +34 -28
- package/src/commands/group/approveall.cjs +3 -11
- package/src/commands/group/clearwarns.cjs +2 -5
- package/src/commands/group/demote.cjs +3 -8
- package/src/commands/group/kick.cjs +12 -15
- package/src/commands/group/media.cjs +17 -24
- package/src/commands/group/members.cjs +1 -2
- package/src/commands/group/promote.cjs +4 -12
- package/src/commands/group/removeall.cjs +2 -5
- package/src/commands/group/setpp.cjs +1 -1
- package/src/commands/group/setrules.cjs +2 -8
- package/src/commands/group/setwarn.cjs +4 -2
- package/src/commands/group/tagadmins.cjs +2 -5
- package/src/commands/group/warn.cjs +26 -24
- package/src/commands/group/warns.cjs +1 -2
- package/src/commands/group/welcome.cjs +13 -17
- package/src/commands/group.cjs +7 -10
- package/src/commands/help.cjs +6 -12
- package/src/commands/lang.cjs +104 -0
- package/src/commands/listschedules.cjs +2 -6
- package/src/commands/loop.cjs +6 -13
- package/src/commands/memory.cjs +19 -32
- package/src/commands/mod.cjs +97 -35
- package/src/commands/perm.cjs +18 -56
- package/src/commands/ping.cjs +1 -5
- package/src/commands/poll.cjs +1 -1
- package/src/commands/rand.cjs +2 -5
- package/src/commands/schedule.cjs +4 -8
- package/src/commands/score.cjs +8 -2
- package/src/commands/setprefix.cjs +2 -2
- package/src/commands/shortlink.cjs +7 -15
- package/src/commands/shutdown.cjs +1 -1
- package/src/commands/sticker.cjs +222 -0
- package/src/commands/stt.cjs +136 -68
- package/src/commands/todo.cjs +9 -15
- package/src/commands/tts.cjs +10 -16
- package/src/commands/unblock.cjs +2 -6
- package/src/commands/weather.cjs +5 -12
- package/src/config/ai-persona.md +5 -3
- package/src/config/available-models.json +1156 -0
- package/src/config/baileys.config.js +2 -1
- package/src/config/brand.esm.js +1 -10
- package/src/config/constants.js +1 -1
- package/src/config/defaults.cjs +66 -66
- package/src/config/lock.cjs +2 -2
- package/src/config/modelCapabilities.cjs +172 -0
- package/src/config/runtime-config.cjs +8 -13
- package/src/config/secrets.cjs +18 -10
- package/src/config/settings.cjs +94 -10
- package/src/config/vault.cjs +107 -0
- package/src/core/connection.js +13 -12
- package/src/core/events.js +41 -23
- package/src/core/proxy.js +7 -7
- package/src/core/session.js +91 -48
- package/src/core/socket.js +12 -15
- package/src/db/db.cjs +14 -6
- package/src/db/store.cjs +221 -87
- package/src/domain/caddy.js +1 -1
- package/src/domain/command.js +40 -19
- package/src/domain/detect.js +1 -1
- package/src/domain/system.js +2 -5
- package/src/handlers/command.handler.js +22 -43
- package/src/handlers/group.handler.js +13 -13
- package/src/handlers/message.handler.js +11 -21
- package/src/index.js +8 -4
- package/src/middleware/antispam.middleware.js +46 -23
- package/src/middleware/blacklist.middleware.js +14 -9
- package/src/middleware/forward-tracking.middleware.js +3 -2
- package/src/middleware/permissions.middleware.js +7 -10
- package/src/panel/login-throttle.cjs +43 -0
- package/src/panel/session-auth.cjs +17 -0
- package/src/routes/dashboard.api.esm.js +394 -72
- package/src/services/aiAgent.cjs +136 -49
- package/src/services/aiProviders.cjs +193 -47
- package/src/services/aiRouter.cjs +137 -0
- package/src/services/aiTools.cjs +305 -128
- package/src/services/llmDiscovery.cjs +481 -0
- package/src/services/modelRegistry.cjs +412 -0
- package/src/utils/datetime.cjs +2 -2
- package/src/utils/lid-helper.esm.js +7 -7
- package/src/utils/logger.cjs +1 -1
- package/src/utils/markdownParser.cjs +159 -0
- package/src/utils/memory.cjs +43 -13
- package/src/utils/messageContent.cjs +40 -0
- package/src/utils/normalizeJid.esm.js +7 -7
- package/src/utils/numerals.cjs +75 -0
- package/src/utils/permissions.esm.js +10 -15
- package/src/utils/providerUrl.cjs +281 -0
- package/src/utils/recurrence.cjs +7 -11
- package/src/utils/sendBotMessage.esm.js +4 -14
- package/src/utils/statusMessage.cjs +5 -13
- package/src/utils/storage.cjs +10 -0
- package/src/utils/storage.esm.js +11 -0
- package/src/utils/textDecode.cjs +4 -9
- package/src/utils/thumbnail.cjs +8 -14
- package/src/utils/typing.esm.js +12 -12
- package/views/dashboard.ejs +134 -106
package/src/core/socket.js
CHANGED
|
@@ -1,17 +1,14 @@
|
|
|
1
|
-
import { makeWASocket } from
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
} from
|
|
7
|
-
import {
|
|
8
|
-
import { useDatabaseAuthState } from '../auth/use-database-auth-state.js';
|
|
9
|
-
import { getMessageFromRecent } from '../utils/recentMessageCache.esm.js';
|
|
10
|
-
import { createRequire } from 'module';
|
|
1
|
+
import { makeWASocket } from "@whiskeysockets/baileys";
|
|
2
|
+
import { createRequire } from "module";
|
|
3
|
+
import NodeCache from "node-cache";
|
|
4
|
+
import { useDatabaseAuthState } from "../auth/use-database-auth-state.js";
|
|
5
|
+
import { getBaileysConfig, setRecentMessageGetter } from "../config/baileys.config.js";
|
|
6
|
+
import { CACHE_CONFIG } from "../config/constants.js";
|
|
7
|
+
import { getMessageFromRecent } from "../utils/recentMessageCache.esm.js";
|
|
11
8
|
|
|
12
9
|
const require = createRequire(import.meta.url);
|
|
13
|
-
const logger = require(
|
|
14
|
-
const { withMediaThumbnail } = require(
|
|
10
|
+
const logger = require("../utils/logger.cjs");
|
|
11
|
+
const { withMediaThumbnail } = require("../utils/thumbnail.cjs");
|
|
15
12
|
|
|
16
13
|
// Group metadata cache
|
|
17
14
|
export const groupMetadataCache = new NodeCache(CACHE_CONFIG);
|
|
@@ -36,7 +33,7 @@ function withThumbnailSupport(sock) {
|
|
|
36
33
|
try {
|
|
37
34
|
payload = await withMediaThumbnail(content);
|
|
38
35
|
} catch (err) {
|
|
39
|
-
logger.debug({ err: err?.message },
|
|
36
|
+
logger.debug({ err: err?.message }, "[Socket] thumbnail step skipped");
|
|
40
37
|
payload = content;
|
|
41
38
|
}
|
|
42
39
|
return original(jid, payload, options);
|
|
@@ -58,7 +55,7 @@ export async function createWhatsAppSocket({ proxy = null, pairingCode = false }
|
|
|
58
55
|
logger.info(
|
|
59
56
|
proxy
|
|
60
57
|
? `[Socket] Initializing WhatsApp socket through ${proxy.label}`
|
|
61
|
-
:
|
|
58
|
+
: "[Socket] Initializing WhatsApp socket with database auth",
|
|
62
59
|
);
|
|
63
60
|
|
|
64
61
|
const { state, saveCreds, clearAll } = await useDatabaseAuthState();
|
|
@@ -90,7 +87,7 @@ export async function createWhatsAppSocket({ proxy = null, pairingCode = false }
|
|
|
90
87
|
makeWASocket({
|
|
91
88
|
auth: state,
|
|
92
89
|
...config,
|
|
93
|
-
})
|
|
90
|
+
}),
|
|
94
91
|
);
|
|
95
92
|
|
|
96
93
|
return { sock, saveCreds, clearAll, isPaired };
|
package/src/db/db.cjs
CHANGED
|
@@ -181,12 +181,22 @@ const MIGRATIONS = [
|
|
|
181
181
|
ALTER TABLE schedules ADD COLUMN last_error TEXT;
|
|
182
182
|
`);
|
|
183
183
|
},
|
|
184
|
+
// v3 — persisted group names/sizes so the panel still shows them after a
|
|
185
|
+
// restart, and so unlink can wipe the WhatsApp directory in one place.
|
|
186
|
+
(database) => {
|
|
187
|
+
database.exec(`
|
|
188
|
+
CREATE TABLE IF NOT EXISTS group_directory (
|
|
189
|
+
group_id TEXT PRIMARY KEY,
|
|
190
|
+
subject TEXT,
|
|
191
|
+
participant_count INTEGER,
|
|
192
|
+
updated_at INTEGER NOT NULL
|
|
193
|
+
);
|
|
194
|
+
`);
|
|
195
|
+
},
|
|
184
196
|
];
|
|
185
197
|
|
|
186
198
|
function migrate(database, migrations = MIGRATIONS) {
|
|
187
|
-
const { user_version: current } = database
|
|
188
|
-
.prepare("PRAGMA user_version")
|
|
189
|
-
.get();
|
|
199
|
+
const { user_version: current } = database.prepare("PRAGMA user_version").get();
|
|
190
200
|
|
|
191
201
|
if (current >= migrations.length) return;
|
|
192
202
|
|
|
@@ -247,9 +257,7 @@ function parseJson(text, fallback) {
|
|
|
247
257
|
/** Delete whatever has aged out. Cheap; called at boot and on a timer. */
|
|
248
258
|
function sweepExpired() {
|
|
249
259
|
try {
|
|
250
|
-
const { changes } = q("DELETE FROM forward_scores WHERE expires_at < ?").run(
|
|
251
|
-
Date.now()
|
|
252
|
-
);
|
|
260
|
+
const { changes } = q("DELETE FROM forward_scores WHERE expires_at < ?").run(Date.now());
|
|
253
261
|
if (changes) logger.debug(`[DB] Swept ${changes} expired forward score(s)`);
|
|
254
262
|
} catch (err) {
|
|
255
263
|
logger.error({ err }, "[DB] sweep failed");
|
package/src/db/store.cjs
CHANGED
|
@@ -30,15 +30,43 @@ const DEFAULT_FORWARD_TTL_DAYS = 30;
|
|
|
30
30
|
|
|
31
31
|
let sweepTimer = null;
|
|
32
32
|
|
|
33
|
+
function migrateSecretsAtRest() {
|
|
34
|
+
const vault = require("../config/vault.cjs");
|
|
35
|
+
const rows = q("SELECT key, value FROM baileys_auth").all();
|
|
36
|
+
for (const row of rows) {
|
|
37
|
+
if (row.value && !vault.isSealed(row.value)) {
|
|
38
|
+
q("UPDATE baileys_auth SET value = ? WHERE key = ?").run(vault.seal(row.value), row.key);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let secretKeys = [];
|
|
43
|
+
try {
|
|
44
|
+
secretKeys = require("../config/settings.cjs").SECRET_SETTING_KEYS || [];
|
|
45
|
+
} catch {
|
|
46
|
+
secretKeys = [];
|
|
47
|
+
}
|
|
48
|
+
for (const key of secretKeys) {
|
|
49
|
+
const stored = getBotSetting(`setting:${key}`, null);
|
|
50
|
+
if (typeof stored === "string" && stored && !vault.isSealed(stored)) {
|
|
51
|
+
saveBotSetting(`setting:${key}`, vault.seal(stored));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
33
56
|
async function initStore() {
|
|
34
57
|
sweepExpired();
|
|
58
|
+
try {
|
|
59
|
+
migrateSecretsAtRest();
|
|
60
|
+
} catch (err) {
|
|
61
|
+
logger.error({ err }, "[Store] Failed to encrypt existing secrets at rest");
|
|
62
|
+
}
|
|
35
63
|
if (!sweepTimer) {
|
|
36
64
|
sweepTimer = setInterval(sweepExpired, 6 * 60 * 60 * 1000);
|
|
37
65
|
sweepTimer.unref();
|
|
38
66
|
}
|
|
39
67
|
logger.info(
|
|
40
68
|
`[Store] SQLite ready at ${DB_PATH} — ${countGroups()} group(s), ` +
|
|
41
|
-
`${countUsers()} user(s), ${countNotes()} note(s), ${countLidMappings()} LID mapping(s)
|
|
69
|
+
`${countUsers()} user(s), ${countNotes()} note(s), ${countLidMappings()} LID mapping(s)`,
|
|
42
70
|
);
|
|
43
71
|
}
|
|
44
72
|
|
|
@@ -97,7 +125,7 @@ function getBotSetting(key, defaultValue = null) {
|
|
|
97
125
|
function saveBotSetting(key, value) {
|
|
98
126
|
q(
|
|
99
127
|
`INSERT INTO bot_settings (key, value) VALUES (?, ?)
|
|
100
|
-
ON CONFLICT(key) DO UPDATE SET value = excluded.value
|
|
128
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value`,
|
|
101
129
|
).run(key, JSON.stringify(value ?? null));
|
|
102
130
|
}
|
|
103
131
|
|
|
@@ -110,16 +138,14 @@ function deleteBotSetting(key) {
|
|
|
110
138
|
// ===================================================================
|
|
111
139
|
|
|
112
140
|
function getGroupSettings(groupId) {
|
|
113
|
-
const row = q("SELECT settings FROM group_settings WHERE group_id = ?").get(
|
|
114
|
-
groupId
|
|
115
|
-
);
|
|
141
|
+
const row = q("SELECT settings FROM group_settings WHERE group_id = ?").get(groupId);
|
|
116
142
|
return row ? parseJson(row.settings, {}) : {};
|
|
117
143
|
}
|
|
118
144
|
|
|
119
145
|
function saveGroupSettings(groupId, settings) {
|
|
120
146
|
q(
|
|
121
147
|
`INSERT INTO group_settings (group_id, settings) VALUES (?, ?)
|
|
122
|
-
ON CONFLICT(group_id) DO UPDATE SET settings = excluded.settings
|
|
148
|
+
ON CONFLICT(group_id) DO UPDATE SET settings = excluded.settings`,
|
|
123
149
|
).run(groupId, JSON.stringify(settings || {}));
|
|
124
150
|
}
|
|
125
151
|
|
|
@@ -136,29 +162,83 @@ function countGroups() {
|
|
|
136
162
|
return q("SELECT COUNT(*) AS n FROM group_settings").get().n;
|
|
137
163
|
}
|
|
138
164
|
|
|
165
|
+
function upsertGroupDirectory(groupId, { subject = null, participantCount = null } = {}) {
|
|
166
|
+
if (!groupId) return;
|
|
167
|
+
const existing = q("SELECT subject, participant_count FROM group_directory WHERE group_id = ?").get(
|
|
168
|
+
groupId,
|
|
169
|
+
);
|
|
170
|
+
q(
|
|
171
|
+
`INSERT INTO group_directory (group_id, subject, participant_count, updated_at)
|
|
172
|
+
VALUES (?, ?, ?, ?)
|
|
173
|
+
ON CONFLICT(group_id) DO UPDATE SET
|
|
174
|
+
subject = COALESCE(excluded.subject, group_directory.subject),
|
|
175
|
+
participant_count = COALESCE(excluded.participant_count, group_directory.participant_count),
|
|
176
|
+
updated_at = excluded.updated_at`,
|
|
177
|
+
).run(
|
|
178
|
+
groupId,
|
|
179
|
+
subject || existing?.subject || null,
|
|
180
|
+
participantCount == null ? (existing?.participant_count ?? null) : participantCount,
|
|
181
|
+
Date.now(),
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function getGroupDirectory(groupId) {
|
|
186
|
+
return q("SELECT group_id, subject, participant_count, updated_at FROM group_directory WHERE group_id = ?").get(
|
|
187
|
+
groupId,
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function getAllGroupDirectory() {
|
|
192
|
+
return q(
|
|
193
|
+
"SELECT group_id, subject, participant_count, updated_at FROM group_directory ORDER BY subject COLLATE NOCASE",
|
|
194
|
+
).all();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function clearGroupDirectory() {
|
|
198
|
+
q("DELETE FROM group_directory").run();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function clearGroupSettings() {
|
|
202
|
+
q("DELETE FROM group_settings").run();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function clearUserMetadata() {
|
|
206
|
+
q("DELETE FROM user_metadata").run();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function clearLidMappings() {
|
|
210
|
+
q("DELETE FROM lid_mapping").run();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** Drop WhatsApp-derived directory data after unlink / logged-out. */
|
|
214
|
+
function clearWhatsAppDirectory() {
|
|
215
|
+
clearGroupDirectory();
|
|
216
|
+
clearGroupSettings();
|
|
217
|
+
clearUserMetadata();
|
|
218
|
+
clearLidMappings();
|
|
219
|
+
}
|
|
220
|
+
|
|
139
221
|
// ===================================================================
|
|
140
222
|
// --- Warnings ---
|
|
141
223
|
// ===================================================================
|
|
142
224
|
|
|
143
225
|
function getUserWarnings(groupId, userId) {
|
|
144
|
-
const row = q(
|
|
145
|
-
|
|
146
|
-
|
|
226
|
+
const row = q("SELECT warnings FROM warnings WHERE group_id = ? AND user_id = ?").get(
|
|
227
|
+
groupId,
|
|
228
|
+
userId,
|
|
229
|
+
);
|
|
147
230
|
return row ? parseJson(row.warnings, []) : [];
|
|
148
231
|
}
|
|
149
232
|
|
|
150
233
|
function saveUserWarnings(groupId, userId, warningsArray) {
|
|
151
234
|
q(
|
|
152
235
|
`INSERT INTO warnings (group_id, user_id, warnings) VALUES (?, ?, ?)
|
|
153
|
-
ON CONFLICT(group_id, user_id) DO UPDATE SET warnings = excluded.warnings
|
|
236
|
+
ON CONFLICT(group_id, user_id) DO UPDATE SET warnings = excluded.warnings`,
|
|
154
237
|
).run(groupId, userId, JSON.stringify(warningsArray || []));
|
|
155
238
|
}
|
|
156
239
|
|
|
157
240
|
function clearUserWarnings(groupId, userId) {
|
|
158
|
-
q("DELETE FROM warnings WHERE group_id = ? AND user_id = ?").run(
|
|
159
|
-
groupId,
|
|
160
|
-
userId
|
|
161
|
-
);
|
|
241
|
+
q("DELETE FROM warnings WHERE group_id = ? AND user_id = ?").run(groupId, userId);
|
|
162
242
|
}
|
|
163
243
|
|
|
164
244
|
function getAllWarnings() {
|
|
@@ -187,7 +267,7 @@ function getUserTodos(userId) {
|
|
|
187
267
|
function saveUserTodos(userId, tasksArray) {
|
|
188
268
|
q(
|
|
189
269
|
`INSERT INTO todos (user_id, tasks) VALUES (?, ?)
|
|
190
|
-
ON CONFLICT(user_id) DO UPDATE SET tasks = excluded.tasks
|
|
270
|
+
ON CONFLICT(user_id) DO UPDATE SET tasks = excluded.tasks`,
|
|
191
271
|
).run(userId, JSON.stringify(tasksArray || []));
|
|
192
272
|
}
|
|
193
273
|
|
|
@@ -208,14 +288,15 @@ function countTodos() {
|
|
|
208
288
|
function saveNote(groupId, keyword, text) {
|
|
209
289
|
q(
|
|
210
290
|
`INSERT INTO notes (group_id, keyword, note_text) VALUES (?, ?, ?)
|
|
211
|
-
ON CONFLICT(group_id, keyword) DO UPDATE SET note_text = excluded.note_text
|
|
291
|
+
ON CONFLICT(group_id, keyword) DO UPDATE SET note_text = excluded.note_text`,
|
|
212
292
|
).run(groupId, keyword, text);
|
|
213
293
|
}
|
|
214
294
|
|
|
215
295
|
function getNote(groupId, keyword) {
|
|
216
|
-
const row = q(
|
|
217
|
-
|
|
218
|
-
|
|
296
|
+
const row = q("SELECT note_text FROM notes WHERE group_id = ? AND keyword = ?").get(
|
|
297
|
+
groupId,
|
|
298
|
+
keyword,
|
|
299
|
+
);
|
|
219
300
|
return row ? row.note_text : null;
|
|
220
301
|
}
|
|
221
302
|
|
|
@@ -226,16 +307,17 @@ function getAllNotes(groupId) {
|
|
|
226
307
|
}
|
|
227
308
|
|
|
228
309
|
function deleteNote(groupId, keyword) {
|
|
229
|
-
const { changes } = q(
|
|
230
|
-
|
|
231
|
-
|
|
310
|
+
const { changes } = q("DELETE FROM notes WHERE group_id = ? AND keyword = ?").run(
|
|
311
|
+
groupId,
|
|
312
|
+
keyword,
|
|
313
|
+
);
|
|
232
314
|
return changes > 0;
|
|
233
315
|
}
|
|
234
316
|
|
|
235
317
|
function getAllNotesFlat(limit = 100) {
|
|
236
318
|
return q(
|
|
237
319
|
`SELECT group_id, keyword, note_text FROM notes
|
|
238
|
-
ORDER BY group_id, keyword LIMIT
|
|
320
|
+
ORDER BY group_id, keyword LIMIT ?`,
|
|
239
321
|
).all(limit);
|
|
240
322
|
}
|
|
241
323
|
|
|
@@ -250,7 +332,7 @@ function countNotes() {
|
|
|
250
332
|
function saveQrCode(qr) {
|
|
251
333
|
q(
|
|
252
334
|
`INSERT INTO qr_codes (id, qr_string) VALUES (1, ?)
|
|
253
|
-
ON CONFLICT(id) DO UPDATE SET qr_string = excluded.qr_string
|
|
335
|
+
ON CONFLICT(id) DO UPDATE SET qr_string = excluded.qr_string`,
|
|
254
336
|
).run(qr);
|
|
255
337
|
}
|
|
256
338
|
|
|
@@ -273,7 +355,7 @@ function storeLidPnMapping(lid, pn, deviceIndex = 0) {
|
|
|
273
355
|
ON CONFLICT(lid) DO UPDATE SET
|
|
274
356
|
pn = excluded.pn,
|
|
275
357
|
device_index = excluded.device_index,
|
|
276
|
-
updated_at = excluded.updated_at
|
|
358
|
+
updated_at = excluded.updated_at`,
|
|
277
359
|
).run(lid, pn, deviceIndex, Date.now());
|
|
278
360
|
logger.debug(`[LID] Stored mapping: ${lid} <-> ${pn}`);
|
|
279
361
|
}
|
|
@@ -357,17 +439,9 @@ function saveUserMetadata(userData) {
|
|
|
357
439
|
user_lid: userData.lid || existing?.user_lid || null,
|
|
358
440
|
phone_number: userData.phone || existing?.phone_number || null,
|
|
359
441
|
is_owner:
|
|
360
|
-
userData.isOwner === undefined
|
|
361
|
-
? existing?.is_owner === 1
|
|
362
|
-
? 1
|
|
363
|
-
: 0
|
|
364
|
-
: bool(userData.isOwner),
|
|
442
|
+
userData.isOwner === undefined ? (existing?.is_owner === 1 ? 1 : 0) : bool(userData.isOwner),
|
|
365
443
|
is_admin:
|
|
366
|
-
userData.isAdmin === undefined
|
|
367
|
-
? existing?.is_admin === 1
|
|
368
|
-
? 1
|
|
369
|
-
: 0
|
|
370
|
-
: bool(userData.isAdmin),
|
|
444
|
+
userData.isAdmin === undefined ? (existing?.is_admin === 1 ? 1 : 0) : bool(userData.isAdmin),
|
|
371
445
|
first_seen: existing?.first_seen ?? now,
|
|
372
446
|
last_seen: now,
|
|
373
447
|
display_name: userData.displayName || existing?.display_name || null,
|
|
@@ -383,7 +457,7 @@ function saveUserMetadata(userData) {
|
|
|
383
457
|
is_owner = excluded.is_owner,
|
|
384
458
|
is_admin = excluded.is_admin,
|
|
385
459
|
last_seen = excluded.last_seen,
|
|
386
|
-
display_name = excluded.display_name
|
|
460
|
+
display_name = excluded.display_name`,
|
|
387
461
|
).run(
|
|
388
462
|
row.user_jid,
|
|
389
463
|
row.user_lid,
|
|
@@ -392,7 +466,7 @@ function saveUserMetadata(userData) {
|
|
|
392
466
|
row.is_admin,
|
|
393
467
|
row.first_seen,
|
|
394
468
|
row.last_seen,
|
|
395
|
-
row.display_name
|
|
469
|
+
row.display_name,
|
|
396
470
|
);
|
|
397
471
|
}
|
|
398
472
|
|
|
@@ -411,9 +485,7 @@ function getUserMetadata(identifier) {
|
|
|
411
485
|
}
|
|
412
486
|
|
|
413
487
|
if (!row) {
|
|
414
|
-
row = q("SELECT * FROM user_metadata WHERE phone_number = ?").get(
|
|
415
|
-
identifier
|
|
416
|
-
);
|
|
488
|
+
row = q("SELECT * FROM user_metadata WHERE phone_number = ?").get(identifier);
|
|
417
489
|
}
|
|
418
490
|
|
|
419
491
|
if (!row) {
|
|
@@ -424,7 +496,7 @@ function getUserMetadata(identifier) {
|
|
|
424
496
|
q(
|
|
425
497
|
`SELECT * FROM user_metadata
|
|
426
498
|
WHERE user_jid LIKE ? OR user_lid LIKE ?
|
|
427
|
-
LIMIT 1
|
|
499
|
+
LIMIT 1`,
|
|
428
500
|
).get(`${phone}@%`, `${phone}@%`);
|
|
429
501
|
}
|
|
430
502
|
}
|
|
@@ -445,21 +517,15 @@ function isUserBotAdmin(identifier) {
|
|
|
445
517
|
}
|
|
446
518
|
|
|
447
519
|
function getAllOwners() {
|
|
448
|
-
return q("SELECT * FROM user_metadata WHERE is_owner = 1")
|
|
449
|
-
.all()
|
|
450
|
-
.map(rowToUser);
|
|
520
|
+
return q("SELECT * FROM user_metadata WHERE is_owner = 1").all().map(rowToUser);
|
|
451
521
|
}
|
|
452
522
|
|
|
453
523
|
function getAllBotAdmins() {
|
|
454
|
-
return q("SELECT * FROM user_metadata WHERE is_admin = 1")
|
|
455
|
-
.all()
|
|
456
|
-
.map(rowToUser);
|
|
524
|
+
return q("SELECT * FROM user_metadata WHERE is_admin = 1").all().map(rowToUser);
|
|
457
525
|
}
|
|
458
526
|
|
|
459
527
|
function getAllUsers() {
|
|
460
|
-
return q("SELECT * FROM user_metadata ORDER BY last_seen DESC")
|
|
461
|
-
.all()
|
|
462
|
-
.map(rowToUser);
|
|
528
|
+
return q("SELECT * FROM user_metadata ORDER BY last_seen DESC").all().map(rowToUser);
|
|
463
529
|
}
|
|
464
530
|
|
|
465
531
|
function countUsers() {
|
|
@@ -493,8 +559,7 @@ function setUserRole(identifier, role, enabled = true) {
|
|
|
493
559
|
|
|
494
560
|
saveUserMetadata({
|
|
495
561
|
jid,
|
|
496
|
-
lid:
|
|
497
|
-
existing?.lid || (String(identifier).endsWith("@lid") ? identifier : null),
|
|
562
|
+
lid: existing?.lid || (String(identifier).endsWith("@lid") ? identifier : null),
|
|
498
563
|
phone: existing?.phone || digitsOf(identifier),
|
|
499
564
|
displayName: existing?.displayName || null,
|
|
500
565
|
[wanted]: !!enabled,
|
|
@@ -504,10 +569,7 @@ function setUserRole(identifier, role, enabled = true) {
|
|
|
504
569
|
}
|
|
505
570
|
|
|
506
571
|
function updateUserLastSeen(jid) {
|
|
507
|
-
q("UPDATE user_metadata SET last_seen = ? WHERE user_jid = ?").run(
|
|
508
|
-
Date.now(),
|
|
509
|
-
jid
|
|
510
|
-
);
|
|
572
|
+
q("UPDATE user_metadata SET last_seen = ? WHERE user_jid = ?").run(Date.now(), jid);
|
|
511
573
|
}
|
|
512
574
|
|
|
513
575
|
// ===================================================================
|
|
@@ -524,20 +586,17 @@ function incrementForwardScore(messageId, groupId, senderId) {
|
|
|
524
586
|
ON CONFLICT(message_id) DO UPDATE SET
|
|
525
587
|
forward_count = forward_count + 1,
|
|
526
588
|
last_forwarded_at = excluded.last_forwarded_at,
|
|
527
|
-
expires_at = excluded.expires_at
|
|
589
|
+
expires_at = excluded.expires_at`,
|
|
528
590
|
).run(messageId, groupId, senderId, now, now, forwardExpiry());
|
|
529
591
|
|
|
530
592
|
return (
|
|
531
|
-
q("SELECT forward_count FROM forward_scores WHERE message_id = ?").get(
|
|
532
|
-
|
|
533
|
-
)?.forward_count ?? 1
|
|
593
|
+
q("SELECT forward_count FROM forward_scores WHERE message_id = ?").get(messageId)
|
|
594
|
+
?.forward_count ?? 1
|
|
534
595
|
);
|
|
535
596
|
}
|
|
536
597
|
|
|
537
598
|
function getForwardScore(messageId) {
|
|
538
|
-
const row = q("SELECT * FROM forward_scores WHERE message_id = ?").get(
|
|
539
|
-
messageId
|
|
540
|
-
);
|
|
599
|
+
const row = q("SELECT * FROM forward_scores WHERE message_id = ?").get(messageId);
|
|
541
600
|
return row
|
|
542
601
|
? {
|
|
543
602
|
count: row.forward_count,
|
|
@@ -554,7 +613,7 @@ function getTopForwardedMessages(groupId, limit = 10) {
|
|
|
554
613
|
FROM forward_scores
|
|
555
614
|
WHERE group_id = ?
|
|
556
615
|
ORDER BY forward_count DESC
|
|
557
|
-
LIMIT
|
|
616
|
+
LIMIT ?`,
|
|
558
617
|
).all(groupId, limit);
|
|
559
618
|
}
|
|
560
619
|
|
|
@@ -567,19 +626,12 @@ function debtRow(row) {
|
|
|
567
626
|
return { ...row, settled: row.settled === 1 };
|
|
568
627
|
}
|
|
569
628
|
|
|
570
|
-
function addDebt({
|
|
571
|
-
groupId,
|
|
572
|
-
debtorId,
|
|
573
|
-
creditorId,
|
|
574
|
-
amount,
|
|
575
|
-
currency = "USD",
|
|
576
|
-
description = null,
|
|
577
|
-
}) {
|
|
629
|
+
function addDebt({ groupId, debtorId, creditorId, amount, currency = "USD", description = null }) {
|
|
578
630
|
const createdAt = Date.now();
|
|
579
631
|
const { lastInsertRowid } = q(
|
|
580
632
|
`INSERT INTO debts
|
|
581
633
|
(group_id, debtor_id, creditor_id, amount, currency, description, created_at, settled, settled_at)
|
|
582
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, 0, NULL)
|
|
634
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, 0, NULL)`,
|
|
583
635
|
).run(groupId, debtorId, creditorId, amount, currency, description, createdAt);
|
|
584
636
|
|
|
585
637
|
return getDebt(Number(lastInsertRowid));
|
|
@@ -597,22 +649,18 @@ function deleteDebt(id) {
|
|
|
597
649
|
function listDebts(groupId, { settled = false } = {}) {
|
|
598
650
|
return q(
|
|
599
651
|
`SELECT * FROM debts WHERE group_id = ? AND settled = ?
|
|
600
|
-
ORDER BY created_at DESC
|
|
652
|
+
ORDER BY created_at DESC`,
|
|
601
653
|
)
|
|
602
654
|
.all(groupId, bool(settled))
|
|
603
655
|
.map(debtRow);
|
|
604
656
|
}
|
|
605
657
|
|
|
606
658
|
function getRecentDebts(limit = 50) {
|
|
607
|
-
return q("SELECT * FROM debts ORDER BY created_at DESC LIMIT ?")
|
|
608
|
-
.all(limit)
|
|
609
|
-
.map(debtRow);
|
|
659
|
+
return q("SELECT * FROM debts ORDER BY created_at DESC LIMIT ?").all(limit).map(debtRow);
|
|
610
660
|
}
|
|
611
661
|
|
|
612
662
|
function countDebts(settled = false) {
|
|
613
|
-
return q("SELECT COUNT(*) AS n FROM debts WHERE settled = ?").get(
|
|
614
|
-
bool(settled)
|
|
615
|
-
).n;
|
|
663
|
+
return q("SELECT COUNT(*) AS n FROM debts WHERE settled = ?").get(bool(settled)).n;
|
|
616
664
|
}
|
|
617
665
|
|
|
618
666
|
// ===================================================================
|
|
@@ -667,7 +715,7 @@ function saveSchedule(job) {
|
|
|
667
715
|
creator_jid = excluded.creator_jid,
|
|
668
716
|
last_run_at = excluded.last_run_at,
|
|
669
717
|
last_delivery_status = excluded.last_delivery_status,
|
|
670
|
-
last_error = excluded.last_error
|
|
718
|
+
last_error = excluded.last_error`,
|
|
671
719
|
).run(
|
|
672
720
|
String(job.id),
|
|
673
721
|
job.type,
|
|
@@ -680,7 +728,7 @@ function saveSchedule(job) {
|
|
|
680
728
|
job.createdAt ?? Date.now(),
|
|
681
729
|
job.lastRunAt ?? null,
|
|
682
730
|
job.lastDeliveryStatus ?? null,
|
|
683
|
-
job.lastError ?? null
|
|
731
|
+
job.lastError ?? null,
|
|
684
732
|
);
|
|
685
733
|
return getSchedule(job.id);
|
|
686
734
|
}
|
|
@@ -693,7 +741,7 @@ function setScheduleDelivery(id, status, runAt, error = null) {
|
|
|
693
741
|
q(
|
|
694
742
|
`UPDATE schedules
|
|
695
743
|
SET last_delivery_status = ?, last_run_at = ?, last_error = ?
|
|
696
|
-
WHERE id =
|
|
744
|
+
WHERE id = ?`,
|
|
697
745
|
).run(status, runAt, error, String(id));
|
|
698
746
|
return getSchedule(id);
|
|
699
747
|
}
|
|
@@ -703,6 +751,12 @@ function deleteSchedule(id) {
|
|
|
703
751
|
return changes > 0;
|
|
704
752
|
}
|
|
705
753
|
|
|
754
|
+
/** Keep old jobs visible after unlink, but never arm them for a new account. */
|
|
755
|
+
function pauseAllSchedules() {
|
|
756
|
+
return q("UPDATE schedules SET status = 'paused' WHERE status IN ('active', 'pending')").run()
|
|
757
|
+
.changes;
|
|
758
|
+
}
|
|
759
|
+
|
|
706
760
|
function countSchedules() {
|
|
707
761
|
return q("SELECT COUNT(*) AS n FROM schedules").get().n;
|
|
708
762
|
}
|
|
@@ -725,7 +779,7 @@ function saveChatHistory(chatId, historyArray) {
|
|
|
725
779
|
`INSERT INTO ai_history (chat_id, history, updated_at) VALUES (?, ?, ?)
|
|
726
780
|
ON CONFLICT(chat_id) DO UPDATE SET
|
|
727
781
|
history = excluded.history,
|
|
728
|
-
updated_at = excluded.updated_at
|
|
782
|
+
updated_at = excluded.updated_at`,
|
|
729
783
|
).run(chatId, JSON.stringify(historyArray || []), Date.now());
|
|
730
784
|
}
|
|
731
785
|
|
|
@@ -747,14 +801,19 @@ function deleteAllChatHistories() {
|
|
|
747
801
|
|
|
748
802
|
function authRead(key) {
|
|
749
803
|
const row = q("SELECT value FROM baileys_auth WHERE key = ?").get(key);
|
|
750
|
-
|
|
804
|
+
if (!row) return null;
|
|
805
|
+
const vault = require("../config/vault.cjs");
|
|
806
|
+
return vault.open(row.value);
|
|
751
807
|
}
|
|
752
808
|
|
|
753
809
|
function authWrite(key, serialized) {
|
|
810
|
+
const vault = require("../config/vault.cjs");
|
|
811
|
+
const payload = serialized == null ? "" : String(serialized);
|
|
812
|
+
const stored = vault.isSealed(payload) ? payload : vault.seal(payload);
|
|
754
813
|
q(
|
|
755
814
|
`INSERT INTO baileys_auth (key, value) VALUES (?, ?)
|
|
756
|
-
ON CONFLICT(key) DO UPDATE SET value = excluded.value
|
|
757
|
-
).run(key,
|
|
815
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value`,
|
|
816
|
+
).run(key, stored);
|
|
758
817
|
}
|
|
759
818
|
|
|
760
819
|
function authRemove(key) {
|
|
@@ -769,6 +828,68 @@ function hasCredentials() {
|
|
|
769
828
|
return !!q("SELECT 1 AS x FROM baileys_auth WHERE key = 'creds'").get();
|
|
770
829
|
}
|
|
771
830
|
|
|
831
|
+
/**
|
|
832
|
+
* A finished pairing — not merely that a creds row exists. Baileys writes
|
|
833
|
+
* creds on the first socket, and requestPairingCode fills me.id before the
|
|
834
|
+
* phone accepts, so only `registered !== false` plus a me.id is proof.
|
|
835
|
+
*/
|
|
836
|
+
function isPairedSession() {
|
|
837
|
+
const text = authRead("creds");
|
|
838
|
+
if (!text) return false;
|
|
839
|
+
try {
|
|
840
|
+
const creds = JSON.parse(text);
|
|
841
|
+
const meId = creds?.me?.id;
|
|
842
|
+
if (!meId) return false;
|
|
843
|
+
return creds.registered !== false;
|
|
844
|
+
} catch {
|
|
845
|
+
return false;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
function resolveUserPhone(jid) {
|
|
850
|
+
if (!jid || String(jid).endsWith("@g.us")) return null;
|
|
851
|
+
const meta = getUserMetadata(jid);
|
|
852
|
+
if (meta?.phone) return String(meta.phone).replace(/\D/g, "");
|
|
853
|
+
const raw = String(jid);
|
|
854
|
+
const local = raw.split("@")[0].split(":")[0];
|
|
855
|
+
if (raw.includes("@lid") || !/^\d{8,15}$/.test(local)) {
|
|
856
|
+
for (const candidate of [raw, local, `${local}@lid`]) {
|
|
857
|
+
const pn = getPnForLid(candidate);
|
|
858
|
+
if (!pn) continue;
|
|
859
|
+
const digits = String(pn).split("@")[0].replace(/\D/g, "");
|
|
860
|
+
if (digits) return digits;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
if (/^\d{8,15}$/.test(local)) return local;
|
|
864
|
+
return null;
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
function describePeer(jid) {
|
|
868
|
+
const id = String(jid || "");
|
|
869
|
+
if (!id) return { jid: "", kind: "unknown", label: "", phone: null, memberCount: null };
|
|
870
|
+
if (id.endsWith("@g.us")) {
|
|
871
|
+
const dir = getGroupDirectory(id);
|
|
872
|
+
const label = dir?.subject || null;
|
|
873
|
+
return {
|
|
874
|
+
jid: id,
|
|
875
|
+
kind: "group",
|
|
876
|
+
label: label || "Group",
|
|
877
|
+
phone: null,
|
|
878
|
+
memberCount: dir?.participant_count ?? null,
|
|
879
|
+
};
|
|
880
|
+
}
|
|
881
|
+
const phone = resolveUserPhone(id);
|
|
882
|
+
const meta = getUserMetadata(id);
|
|
883
|
+
const label = meta?.displayName || (phone ? `+${phone}` : id.split("@")[0]);
|
|
884
|
+
return {
|
|
885
|
+
jid: id,
|
|
886
|
+
kind: "contact",
|
|
887
|
+
label,
|
|
888
|
+
phone: phone ? `+${phone}` : null,
|
|
889
|
+
memberCount: null,
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
|
|
772
893
|
module.exports = {
|
|
773
894
|
// Lifecycle
|
|
774
895
|
initStore,
|
|
@@ -784,6 +905,14 @@ module.exports = {
|
|
|
784
905
|
saveGroupSettings,
|
|
785
906
|
getAllGroupSettings,
|
|
786
907
|
countGroups,
|
|
908
|
+
upsertGroupDirectory,
|
|
909
|
+
getGroupDirectory,
|
|
910
|
+
getAllGroupDirectory,
|
|
911
|
+
clearGroupDirectory,
|
|
912
|
+
clearGroupSettings,
|
|
913
|
+
clearUserMetadata,
|
|
914
|
+
clearLidMappings,
|
|
915
|
+
clearWhatsAppDirectory,
|
|
787
916
|
// Warnings
|
|
788
917
|
getUserWarnings,
|
|
789
918
|
saveUserWarnings,
|
|
@@ -842,6 +971,7 @@ module.exports = {
|
|
|
842
971
|
setScheduleStatus,
|
|
843
972
|
setScheduleDelivery,
|
|
844
973
|
deleteSchedule,
|
|
974
|
+
pauseAllSchedules,
|
|
845
975
|
countSchedules,
|
|
846
976
|
// AI history
|
|
847
977
|
getChatHistory,
|
|
@@ -854,4 +984,8 @@ module.exports = {
|
|
|
854
984
|
authRemove,
|
|
855
985
|
authClearAll,
|
|
856
986
|
hasCredentials,
|
|
987
|
+
isPairedSession,
|
|
988
|
+
resolveUserPhone,
|
|
989
|
+
describePeer,
|
|
990
|
+
migrateSecretsAtRest,
|
|
857
991
|
};
|
package/src/domain/caddy.js
CHANGED
|
@@ -44,7 +44,7 @@ export function applySite(system, { domain, port, caddyfile = "/etc/caddy/Caddyf
|
|
|
44
44
|
if (!current.includes(IMPORT_LINE)) {
|
|
45
45
|
system.writeFile(
|
|
46
46
|
caddyfile,
|
|
47
|
-
`${current}${current.endsWith("\n") || current === "" ? "" : "\n"}\n# Added by Levix — loads its site files.\n${IMPORT_LINE}\n
|
|
47
|
+
`${current}${current.endsWith("\n") || current === "" ? "" : "\n"}\n# Added by Levix — loads its site files.\n${IMPORT_LINE}\n`,
|
|
48
48
|
);
|
|
49
49
|
}
|
|
50
50
|
|