@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.
Files changed (2) hide show
  1. package/bot.js +58 -0
  2. 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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inetafrica/open-claudia",
3
- "version": "1.1.2",
3
+ "version": "1.1.4",
4
4
  "description": "Your always-on AI coding assistant — Claude Code via Telegram",
5
5
  "main": "bot.js",
6
6
  "bin": {