@inetafrica/open-claudia 1.1.2 → 1.1.4
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/bot.js +58 -0
- package/package.json +1 -1
package/bot.js
CHANGED
|
@@ -7,6 +7,45 @@ const cron = require("node-cron");
|
|
|
7
7
|
const Vault = require("./vault");
|
|
8
8
|
const CONFIG_DIR = require("./config-dir");
|
|
9
9
|
|
|
10
|
+
// ── Single instance lock ───────────────────────────────────────────
|
|
11
|
+
const LOCK_FILE = path.join(CONFIG_DIR, "bot.lock");
|
|
12
|
+
|
|
13
|
+
function acquireLock() {
|
|
14
|
+
// Check if another instance is running
|
|
15
|
+
if (fs.existsSync(LOCK_FILE)) {
|
|
16
|
+
try {
|
|
17
|
+
const pid = parseInt(fs.readFileSync(LOCK_FILE, "utf-8").trim(), 10);
|
|
18
|
+
// Check if that PID is still alive
|
|
19
|
+
process.kill(pid, 0); // throws if process doesn't exist
|
|
20
|
+
console.error(`Another instance is running (PID ${pid}). Exiting.`);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
} catch (e) {
|
|
23
|
+
if (e.code === "ESRCH") {
|
|
24
|
+
// Process is dead, stale lock — take over
|
|
25
|
+
} else if (e.code === "EPERM") {
|
|
26
|
+
// Process exists but we can't signal it
|
|
27
|
+
console.error("Another instance is running. Exiting.");
|
|
28
|
+
process.exit(1);
|
|
29
|
+
} else {
|
|
30
|
+
// Lock file is corrupt or unreadable — take over
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
fs.writeFileSync(LOCK_FILE, String(process.pid));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function releaseLock() {
|
|
38
|
+
try {
|
|
39
|
+
const pid = fs.readFileSync(LOCK_FILE, "utf-8").trim();
|
|
40
|
+
if (pid === String(process.pid)) fs.unlinkSync(LOCK_FILE);
|
|
41
|
+
} catch (e) {}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
acquireLock();
|
|
45
|
+
process.on("exit", releaseLock);
|
|
46
|
+
process.on("SIGINT", () => { releaseLock(); process.exit(0); });
|
|
47
|
+
process.on("SIGTERM", () => { releaseLock(); process.exit(0); });
|
|
48
|
+
|
|
10
49
|
// ── Load Config from .env ───────────────────────────────────────────
|
|
11
50
|
function loadEnv() {
|
|
12
51
|
const envPath = path.join(CONFIG_DIR, ".env");
|
|
@@ -110,6 +149,20 @@ function saveState() {
|
|
|
110
149
|
try { fs.writeFileSync(STATE_FILE, JSON.stringify(data)); } catch (e) {}
|
|
111
150
|
}
|
|
112
151
|
|
|
152
|
+
// ── Message deduplication ──────────────────────────────────────────
|
|
153
|
+
const processedMessages = new Set();
|
|
154
|
+
function isDuplicate(msgId) {
|
|
155
|
+
if (processedMessages.has(msgId)) return true;
|
|
156
|
+
processedMessages.add(msgId);
|
|
157
|
+
// Keep set from growing unbounded
|
|
158
|
+
if (processedMessages.size > 200) {
|
|
159
|
+
const arr = [...processedMessages];
|
|
160
|
+
processedMessages.clear();
|
|
161
|
+
arr.slice(-100).forEach((id) => processedMessages.add(id));
|
|
162
|
+
}
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
|
|
113
166
|
// ── Per-project session history ────────────────────────────────────
|
|
114
167
|
|
|
115
168
|
function loadSessions() {
|
|
@@ -984,6 +1037,7 @@ bot.on("callback_query", async (q) => {
|
|
|
984
1037
|
// ── Media Handlers ──────────────────────────────────────────────────
|
|
985
1038
|
|
|
986
1039
|
bot.on("voice", async (msg) => {
|
|
1040
|
+
if (isDuplicate(msg.message_id)) return;
|
|
987
1041
|
if (!isAuthorized(msg)) return;
|
|
988
1042
|
if (!requireSession(msg)) return;
|
|
989
1043
|
try {
|
|
@@ -998,6 +1052,7 @@ bot.on("voice", async (msg) => {
|
|
|
998
1052
|
});
|
|
999
1053
|
|
|
1000
1054
|
bot.on("audio", async (msg) => {
|
|
1055
|
+
if (isDuplicate(msg.message_id)) return;
|
|
1001
1056
|
if (!isAuthorized(msg)) return;
|
|
1002
1057
|
if (!requireSession(msg)) return;
|
|
1003
1058
|
try {
|
|
@@ -1012,6 +1067,7 @@ bot.on("audio", async (msg) => {
|
|
|
1012
1067
|
});
|
|
1013
1068
|
|
|
1014
1069
|
bot.on("photo", async (msg) => {
|
|
1070
|
+
if (isDuplicate(msg.message_id)) return;
|
|
1015
1071
|
if (!isAuthorized(msg)) return;
|
|
1016
1072
|
if (!requireSession(msg)) return;
|
|
1017
1073
|
try {
|
|
@@ -1022,6 +1078,7 @@ bot.on("photo", async (msg) => {
|
|
|
1022
1078
|
});
|
|
1023
1079
|
|
|
1024
1080
|
bot.on("document", async (msg) => {
|
|
1081
|
+
if (isDuplicate(msg.message_id)) return;
|
|
1025
1082
|
if (!isAuthorized(msg)) return;
|
|
1026
1083
|
if (!requireSession(msg)) return;
|
|
1027
1084
|
try {
|
|
@@ -1036,6 +1093,7 @@ bot.on("document", async (msg) => {
|
|
|
1036
1093
|
// ── Text Message Handler (handles onboarding, vault password, normal messages) ──
|
|
1037
1094
|
|
|
1038
1095
|
bot.on("message", async (msg) => {
|
|
1096
|
+
if (isDuplicate(msg.message_id)) return;
|
|
1039
1097
|
if (!isAuthorized(msg)) return;
|
|
1040
1098
|
if (!msg.text || msg.text.startsWith("/")) return;
|
|
1041
1099
|
if (msg.voice || msg.audio || msg.photo || msg.document || msg.video || msg.sticker) return;
|