@delt/claude-alarm 0.5.3 → 0.5.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/dist/cli.js +143 -69
- package/dist/cli.js.map +1 -1
- package/dist/hub/server.js +124 -51
- package/dist/hub/server.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +129 -56
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/hub/server.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
// src/hub/server.ts
|
|
4
4
|
import http from "http";
|
|
5
|
-
import
|
|
6
|
-
import
|
|
5
|
+
import fs3 from "fs";
|
|
6
|
+
import path4 from "path";
|
|
7
7
|
import { fileURLToPath } from "url";
|
|
8
8
|
import { WebSocketServer, WebSocket } from "ws";
|
|
9
9
|
|
|
@@ -26,7 +26,7 @@ var logger = {
|
|
|
26
26
|
};
|
|
27
27
|
|
|
28
28
|
// src/hub/server.ts
|
|
29
|
-
import { randomUUID as
|
|
29
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
30
30
|
|
|
31
31
|
// src/shared/constants.ts
|
|
32
32
|
import path from "path";
|
|
@@ -187,6 +187,9 @@ var Notifier = class {
|
|
|
187
187
|
};
|
|
188
188
|
|
|
189
189
|
// src/hub/telegram.ts
|
|
190
|
+
import fs from "fs";
|
|
191
|
+
import path2 from "path";
|
|
192
|
+
import { randomUUID } from "crypto";
|
|
190
193
|
var TELEGRAM_API = "https://api.telegram.org/bot";
|
|
191
194
|
var TelegramBot = class {
|
|
192
195
|
config;
|
|
@@ -195,13 +198,15 @@ var TelegramBot = class {
|
|
|
195
198
|
pollTimer = null;
|
|
196
199
|
// message_id -> sessionId mapping for reply-based routing
|
|
197
200
|
messageSessionMap = /* @__PURE__ */ new Map();
|
|
198
|
-
// Callback: when a message arrives from Telegram for a session
|
|
201
|
+
// Callback: when a text message arrives from Telegram for a session
|
|
199
202
|
onMessageToSession;
|
|
203
|
+
// Callback: when an image arrives from Telegram for a session
|
|
204
|
+
onImageToSession;
|
|
200
205
|
// Callback: get current sessions list
|
|
201
206
|
getSessions;
|
|
202
|
-
//
|
|
207
|
+
// Pending messages for session selection
|
|
203
208
|
pendingMessages = /* @__PURE__ */ new Map();
|
|
204
|
-
// chatId -> pending
|
|
209
|
+
// chatId -> pending
|
|
205
210
|
constructor(config) {
|
|
206
211
|
this.config = config;
|
|
207
212
|
}
|
|
@@ -297,31 +302,42 @@ ${message}`;
|
|
|
297
302
|
if (!this.polling) return;
|
|
298
303
|
this.pollTimer = setTimeout(() => this.poll(), delay);
|
|
299
304
|
}
|
|
300
|
-
handleIncomingMessage(msg) {
|
|
301
|
-
if (!msg.text) return;
|
|
305
|
+
async handleIncomingMessage(msg) {
|
|
302
306
|
if (String(msg.chat.id) !== String(this.config.chatId)) return;
|
|
303
|
-
const
|
|
307
|
+
const hasPhoto = msg.photo && msg.photo.length > 0;
|
|
308
|
+
const text = (msg.text || msg.caption || "").trim();
|
|
309
|
+
if (!text && !hasPhoto) return;
|
|
304
310
|
if (msg.reply_to_message) {
|
|
305
311
|
const sessionId = this.messageSessionMap.get(msg.reply_to_message.message_id);
|
|
306
312
|
if (sessionId) {
|
|
307
|
-
|
|
313
|
+
if (hasPhoto) {
|
|
314
|
+
await this.deliverPhotoToSession(sessionId, msg.photo, text);
|
|
315
|
+
} else {
|
|
316
|
+
this.deliverToSession(sessionId, text);
|
|
317
|
+
}
|
|
308
318
|
return;
|
|
309
319
|
}
|
|
310
320
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
321
|
+
if (text) {
|
|
322
|
+
const selectMatch = text.match(/^\/s_(\d+)$/);
|
|
323
|
+
if (selectMatch) {
|
|
324
|
+
const pending = this.pendingMessages.get(msg.chat.id);
|
|
325
|
+
if (pending) {
|
|
326
|
+
this.pendingMessages.delete(msg.chat.id);
|
|
327
|
+
const sessions2 = this.getSessions?.() ?? [];
|
|
328
|
+
const idx = parseInt(selectMatch[1], 10) - 1;
|
|
329
|
+
if (idx >= 0 && idx < sessions2.length) {
|
|
330
|
+
if (pending.photoFileId) {
|
|
331
|
+
await this.deliverPhotoToSessionByFileId(sessions2[idx].id, pending.photoFileId, pending.caption);
|
|
332
|
+
} else if (pending.text) {
|
|
333
|
+
this.deliverToSession(sessions2[idx].id, pending.text);
|
|
334
|
+
}
|
|
335
|
+
this.sendMessage(`Sent to [${this.getLabel(sessions2[idx])}]`);
|
|
336
|
+
} else {
|
|
337
|
+
this.sendMessage("Invalid session number.");
|
|
338
|
+
}
|
|
339
|
+
return;
|
|
323
340
|
}
|
|
324
|
-
return;
|
|
325
341
|
}
|
|
326
342
|
}
|
|
327
343
|
const sessions = this.getSessions?.() ?? [];
|
|
@@ -330,10 +346,19 @@ ${message}`;
|
|
|
330
346
|
return;
|
|
331
347
|
}
|
|
332
348
|
if (sessions.length === 1) {
|
|
333
|
-
|
|
349
|
+
if (hasPhoto) {
|
|
350
|
+
await this.deliverPhotoToSession(sessions[0].id, msg.photo, text);
|
|
351
|
+
} else {
|
|
352
|
+
this.deliverToSession(sessions[0].id, text);
|
|
353
|
+
}
|
|
334
354
|
return;
|
|
335
355
|
}
|
|
336
|
-
|
|
356
|
+
if (hasPhoto) {
|
|
357
|
+
const largest = msg.photo[msg.photo.length - 1];
|
|
358
|
+
this.pendingMessages.set(msg.chat.id, { photoFileId: largest.file_id, caption: text });
|
|
359
|
+
} else {
|
|
360
|
+
this.pendingMessages.set(msg.chat.id, { text });
|
|
361
|
+
}
|
|
337
362
|
const sessionList = sessions.map((s, i) => `/s_${i + 1} - ${this.getLabel(s)}`).join("\n");
|
|
338
363
|
this.sendMessage(`Multiple sessions active. Reply with a command to select:
|
|
339
364
|
|
|
@@ -344,6 +369,46 @@ ${sessionList}`);
|
|
|
344
369
|
this.onMessageToSession(sessionId, content);
|
|
345
370
|
}
|
|
346
371
|
}
|
|
372
|
+
async deliverPhotoToSession(sessionId, photos, caption) {
|
|
373
|
+
const largest = photos[photos.length - 1];
|
|
374
|
+
await this.deliverPhotoToSessionByFileId(sessionId, largest.file_id, caption);
|
|
375
|
+
}
|
|
376
|
+
async deliverPhotoToSessionByFileId(sessionId, fileId, caption) {
|
|
377
|
+
try {
|
|
378
|
+
const fileRes = await fetch(`${this.apiUrl}/getFile?file_id=${fileId}`);
|
|
379
|
+
if (!fileRes.ok) {
|
|
380
|
+
logger.warn("Failed to get Telegram file info");
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
const fileData = await fileRes.json();
|
|
384
|
+
if (!fileData.ok) return;
|
|
385
|
+
const downloadUrl = `https://api.telegram.org/file/bot${this.config.botToken}/${fileData.result.file_path}`;
|
|
386
|
+
const imgRes = await fetch(downloadUrl);
|
|
387
|
+
if (!imgRes.ok) {
|
|
388
|
+
logger.warn("Failed to download Telegram photo");
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
const buffer = Buffer.from(await imgRes.arrayBuffer());
|
|
392
|
+
const ext = fileData.result.file_path.split(".").pop() || "jpg";
|
|
393
|
+
const mimeType = ext === "png" ? "image/png" : ext === "gif" ? "image/gif" : ext === "webp" ? "image/webp" : "image/jpeg";
|
|
394
|
+
fs.mkdirSync(UPLOADS_DIR, { recursive: true });
|
|
395
|
+
const filename = `${randomUUID()}.${ext}`;
|
|
396
|
+
const filePath = path2.join(UPLOADS_DIR, filename);
|
|
397
|
+
fs.writeFileSync(filePath, buffer);
|
|
398
|
+
logger.info(`Telegram photo saved: ${filename} (${buffer.length} bytes)`);
|
|
399
|
+
if (this.onImageToSession) {
|
|
400
|
+
this.onImageToSession(sessionId, filePath, mimeType, caption);
|
|
401
|
+
}
|
|
402
|
+
setTimeout(() => {
|
|
403
|
+
try {
|
|
404
|
+
fs.unlinkSync(filePath);
|
|
405
|
+
} catch {
|
|
406
|
+
}
|
|
407
|
+
}, 5 * 60 * 1e3);
|
|
408
|
+
} catch (err) {
|
|
409
|
+
logger.warn(`Telegram photo download failed: ${err.message}`);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
347
412
|
getLabel(session) {
|
|
348
413
|
return session.cwd?.replace(/^.*[/\\]/, "") || session.name;
|
|
349
414
|
}
|
|
@@ -357,9 +422,9 @@ ${sessionList}`);
|
|
|
357
422
|
};
|
|
358
423
|
|
|
359
424
|
// src/shared/config.ts
|
|
360
|
-
import
|
|
361
|
-
import
|
|
362
|
-
import { randomUUID } from "crypto";
|
|
425
|
+
import fs2 from "fs";
|
|
426
|
+
import path3 from "path";
|
|
427
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
363
428
|
var DEFAULT_CONFIG = {
|
|
364
429
|
hub: {
|
|
365
430
|
host: DEFAULT_HUB_HOST,
|
|
@@ -372,18 +437,18 @@ var DEFAULT_CONFIG = {
|
|
|
372
437
|
webhooks: []
|
|
373
438
|
};
|
|
374
439
|
function ensureConfigDir() {
|
|
375
|
-
if (!
|
|
376
|
-
|
|
440
|
+
if (!fs2.existsSync(CONFIG_DIR)) {
|
|
441
|
+
fs2.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
377
442
|
}
|
|
378
443
|
}
|
|
379
444
|
function loadConfig() {
|
|
380
445
|
ensureConfigDir();
|
|
381
446
|
let config;
|
|
382
|
-
if (!
|
|
447
|
+
if (!fs2.existsSync(CONFIG_FILE)) {
|
|
383
448
|
config = { ...DEFAULT_CONFIG, hub: { ...DEFAULT_CONFIG.hub } };
|
|
384
449
|
} else {
|
|
385
450
|
try {
|
|
386
|
-
const raw =
|
|
451
|
+
const raw = fs2.readFileSync(CONFIG_FILE, "utf-8");
|
|
387
452
|
const parsed = JSON.parse(raw);
|
|
388
453
|
config = { ...DEFAULT_CONFIG, ...parsed, hub: { ...DEFAULT_CONFIG.hub, ...parsed.hub }, ...parsed.telegram ? { telegram: parsed.telegram } : {} };
|
|
389
454
|
} catch {
|
|
@@ -391,18 +456,18 @@ function loadConfig() {
|
|
|
391
456
|
}
|
|
392
457
|
}
|
|
393
458
|
if (!config.hub.token) {
|
|
394
|
-
config.hub.token =
|
|
459
|
+
config.hub.token = randomUUID2();
|
|
395
460
|
saveConfig(config);
|
|
396
461
|
}
|
|
397
462
|
return config;
|
|
398
463
|
}
|
|
399
464
|
function saveConfig(config) {
|
|
400
465
|
ensureConfigDir();
|
|
401
|
-
|
|
466
|
+
fs2.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), { encoding: "utf-8", mode: 384 });
|
|
402
467
|
}
|
|
403
468
|
|
|
404
469
|
// src/hub/server.ts
|
|
405
|
-
var __dirname =
|
|
470
|
+
var __dirname = path4.dirname(fileURLToPath(import.meta.url));
|
|
406
471
|
var HubServer = class {
|
|
407
472
|
httpServer;
|
|
408
473
|
wssChannel;
|
|
@@ -559,23 +624,23 @@ var HubServer = class {
|
|
|
559
624
|
}
|
|
560
625
|
serveDashboard(res) {
|
|
561
626
|
const candidates = [
|
|
562
|
-
|
|
627
|
+
path4.join(__dirname, "..", "dashboard", "index.html"),
|
|
563
628
|
// from dist/hub/
|
|
564
|
-
|
|
629
|
+
path4.join(__dirname, "dashboard", "index.html"),
|
|
565
630
|
// from dist/ (bundled index.js)
|
|
566
|
-
|
|
631
|
+
path4.join(__dirname, "..", "..", "src", "dashboard", "index.html"),
|
|
567
632
|
// from dist/hub/ -> src/
|
|
568
|
-
|
|
633
|
+
path4.join(__dirname, "..", "src", "dashboard", "index.html"),
|
|
569
634
|
// from dist/ -> src/
|
|
570
|
-
|
|
635
|
+
path4.join(process.cwd(), "dist", "dashboard", "index.html"),
|
|
571
636
|
// from cwd
|
|
572
|
-
|
|
637
|
+
path4.join(process.cwd(), "src", "dashboard", "index.html")
|
|
573
638
|
// from cwd/src
|
|
574
639
|
];
|
|
575
640
|
logger.debug(`Dashboard candidates: ${JSON.stringify(candidates)}`);
|
|
576
641
|
for (const candidate of candidates) {
|
|
577
|
-
if (
|
|
578
|
-
const html =
|
|
642
|
+
if (fs3.existsSync(candidate)) {
|
|
643
|
+
const html = fs3.readFileSync(candidate, "utf-8");
|
|
579
644
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
580
645
|
res.end(html);
|
|
581
646
|
return;
|
|
@@ -753,11 +818,11 @@ var HubServer = class {
|
|
|
753
818
|
logger.warn("Image upload rejected: exceeds 10MB");
|
|
754
819
|
return;
|
|
755
820
|
}
|
|
756
|
-
|
|
821
|
+
fs3.mkdirSync(UPLOADS_DIR, { recursive: true });
|
|
757
822
|
const ext = mimeType.split("/")[1] === "jpeg" ? "jpg" : mimeType.split("/")[1];
|
|
758
|
-
const filename = `${
|
|
759
|
-
const filePath =
|
|
760
|
-
|
|
823
|
+
const filename = `${randomUUID3()}.${ext}`;
|
|
824
|
+
const filePath = path4.join(UPLOADS_DIR, filename);
|
|
825
|
+
fs3.writeFileSync(filePath, buffer);
|
|
761
826
|
const forwardMsg = {
|
|
762
827
|
type: "image_to_session",
|
|
763
828
|
sessionId,
|
|
@@ -770,7 +835,7 @@ var HubServer = class {
|
|
|
770
835
|
logger.info(`Image saved and forwarded: ${filename} (${buffer.length} bytes)`);
|
|
771
836
|
setTimeout(() => {
|
|
772
837
|
try {
|
|
773
|
-
|
|
838
|
+
fs3.unlinkSync(filePath);
|
|
774
839
|
} catch {
|
|
775
840
|
}
|
|
776
841
|
}, 5 * 60 * 1e3);
|
|
@@ -803,6 +868,14 @@ var HubServer = class {
|
|
|
803
868
|
logger.info(`Telegram message forwarded to session: ${sessionId}`);
|
|
804
869
|
}
|
|
805
870
|
};
|
|
871
|
+
this.telegramBot.onImageToSession = (sessionId, imagePath, mimeType, caption) => {
|
|
872
|
+
const channelWs = this.channelSockets.get(sessionId);
|
|
873
|
+
if (channelWs?.readyState === WebSocket.OPEN) {
|
|
874
|
+
const msg = { type: "image_to_session", sessionId, imagePath, mimeType, content: caption };
|
|
875
|
+
channelWs.send(JSON.stringify(msg));
|
|
876
|
+
logger.info(`Telegram photo forwarded to session: ${sessionId}`);
|
|
877
|
+
}
|
|
878
|
+
};
|
|
806
879
|
this.notifier.configure({ telegramBot: this.telegramBot });
|
|
807
880
|
this.telegramBot.startPolling();
|
|
808
881
|
logger.info("Telegram bot initialized");
|
|
@@ -907,11 +980,11 @@ var HubServer = class {
|
|
|
907
980
|
}
|
|
908
981
|
cleanupUploads() {
|
|
909
982
|
try {
|
|
910
|
-
if (!
|
|
911
|
-
const files =
|
|
983
|
+
if (!fs3.existsSync(UPLOADS_DIR)) return;
|
|
984
|
+
const files = fs3.readdirSync(UPLOADS_DIR);
|
|
912
985
|
for (const file of files) {
|
|
913
986
|
try {
|
|
914
|
-
|
|
987
|
+
fs3.unlinkSync(path4.join(UPLOADS_DIR, file));
|
|
915
988
|
} catch {
|
|
916
989
|
}
|
|
917
990
|
}
|