@delt/claude-alarm 0.4.9 → 0.5.1
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 +215 -182
- package/dist/channel/server.js +1 -1
- package/dist/channel/server.js.map +1 -1
- package/dist/cli.js +274 -3
- package/dist/cli.js.map +1 -1
- package/dist/dashboard/index.html +1339 -1227
- package/dist/hub/server.js +266 -3
- package/dist/hub/server.js.map +1 -1
- package/dist/index.d.ts +43 -1
- package/dist/index.js +266 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/dashboard/index.html +1339 -1227
package/dist/index.d.ts
CHANGED
|
@@ -80,6 +80,12 @@ interface WebhookConfig {
|
|
|
80
80
|
events?: string[];
|
|
81
81
|
headers?: Record<string, string>;
|
|
82
82
|
}
|
|
83
|
+
/** Telegram bot configuration */
|
|
84
|
+
interface TelegramConfig {
|
|
85
|
+
botToken: string;
|
|
86
|
+
chatId: string;
|
|
87
|
+
enabled: boolean;
|
|
88
|
+
}
|
|
83
89
|
/** App configuration stored in ~/.claude-alarm/config.json */
|
|
84
90
|
interface AppConfig {
|
|
85
91
|
hub: {
|
|
@@ -92,6 +98,7 @@ interface AppConfig {
|
|
|
92
98
|
sound: boolean;
|
|
93
99
|
};
|
|
94
100
|
webhooks: WebhookConfig[];
|
|
101
|
+
telegram?: TelegramConfig;
|
|
95
102
|
}
|
|
96
103
|
/** Hub status response */
|
|
97
104
|
interface HubStatus {
|
|
@@ -112,6 +119,7 @@ declare class HubServer {
|
|
|
112
119
|
private channelSockets;
|
|
113
120
|
private localChannels;
|
|
114
121
|
private dashboardSockets;
|
|
122
|
+
private telegramBot?;
|
|
115
123
|
private host;
|
|
116
124
|
private port;
|
|
117
125
|
private token?;
|
|
@@ -128,6 +136,9 @@ declare class HubServer {
|
|
|
128
136
|
private broadcastToDashboards;
|
|
129
137
|
private handleImageUpload;
|
|
130
138
|
private handleWebhookSave;
|
|
139
|
+
private initTelegram;
|
|
140
|
+
private handleTelegramSave;
|
|
141
|
+
private handleTelegramTest;
|
|
131
142
|
private cleanupUploads;
|
|
132
143
|
private getSessionLabel;
|
|
133
144
|
private jsonResponse;
|
|
@@ -165,17 +176,48 @@ declare class SessionManager {
|
|
|
165
176
|
count(): number;
|
|
166
177
|
}
|
|
167
178
|
|
|
179
|
+
declare class TelegramBot {
|
|
180
|
+
private config;
|
|
181
|
+
private offset;
|
|
182
|
+
private polling;
|
|
183
|
+
private pollTimer;
|
|
184
|
+
private messageSessionMap;
|
|
185
|
+
onMessageToSession?: (sessionId: string, content: string) => void;
|
|
186
|
+
getSessions?: () => SessionInfo[];
|
|
187
|
+
private pendingMessages;
|
|
188
|
+
constructor(config: TelegramConfig);
|
|
189
|
+
private get apiUrl();
|
|
190
|
+
/** Send a notification message to Telegram */
|
|
191
|
+
sendNotification(sessionId: string, _sessionLabel: string, title: string, message: string): Promise<void>;
|
|
192
|
+
/** Send a text message to the configured chat */
|
|
193
|
+
private sendMessage;
|
|
194
|
+
/** Start long polling for incoming messages */
|
|
195
|
+
startPolling(): void;
|
|
196
|
+
/** Stop polling */
|
|
197
|
+
stopPolling(): void;
|
|
198
|
+
private poll;
|
|
199
|
+
private scheduleNextPoll;
|
|
200
|
+
private handleIncomingMessage;
|
|
201
|
+
private deliverToSession;
|
|
202
|
+
private getLabel;
|
|
203
|
+
/** Update config (e.g., from dashboard settings) */
|
|
204
|
+
updateConfig(config: TelegramConfig): void;
|
|
205
|
+
}
|
|
206
|
+
|
|
168
207
|
declare class Notifier {
|
|
169
208
|
private webhooks;
|
|
170
209
|
private desktopEnabled;
|
|
171
210
|
private notificationSettingsOpened;
|
|
172
211
|
private dashboardUrl?;
|
|
212
|
+
private telegramBot?;
|
|
173
213
|
configure(options: {
|
|
174
214
|
desktop?: boolean;
|
|
175
215
|
webhooks?: WebhookConfig[];
|
|
176
216
|
dashboardUrl?: string;
|
|
217
|
+
telegramBot?: TelegramBot;
|
|
177
218
|
}): void;
|
|
178
219
|
notify(title: string, message: string, level?: NotifyLevel): Promise<void>;
|
|
220
|
+
notifyWithSession(sessionId: string | undefined, sessionLabel: string | undefined, title: string, message: string, level?: NotifyLevel): Promise<void>;
|
|
179
221
|
private sendDesktop;
|
|
180
222
|
private checkWindowsNotifications;
|
|
181
223
|
private openNotificationSettings;
|
|
@@ -213,4 +255,4 @@ declare const WS_PATH_DASHBOARD = "/ws/dashboard";
|
|
|
213
255
|
declare const CHANNEL_SERVER_NAME = "claude-alarm";
|
|
214
256
|
declare const CHANNEL_SERVER_VERSION = "0.1.0";
|
|
215
257
|
|
|
216
|
-
export { type AppConfig, CHANNEL_SERVER_NAME, CHANNEL_SERVER_VERSION, CONFIG_DIR, CONFIG_FILE, type ChannelMessage, DEFAULT_HUB_HOST, DEFAULT_HUB_PORT, HubClient, HubServer, type HubStatus, LOG_FILE, Notifier, type NotifyLevel, PID_FILE, type SessionInfo, SessionManager, type SessionStatus, UPLOADS_DIR, WS_PATH_CHANNEL, WS_PATH_DASHBOARD, type WebhookConfig, loadConfig, logger, saveConfig, setupMcpConfig };
|
|
258
|
+
export { type AppConfig, CHANNEL_SERVER_NAME, CHANNEL_SERVER_VERSION, CONFIG_DIR, CONFIG_FILE, type ChannelMessage, DEFAULT_HUB_HOST, DEFAULT_HUB_PORT, HubClient, HubServer, type HubStatus, LOG_FILE, Notifier, type NotifyLevel, PID_FILE, type SessionInfo, SessionManager, type SessionStatus, type TelegramConfig, UPLOADS_DIR, WS_PATH_CHANNEL, WS_PATH_DASHBOARD, type WebhookConfig, loadConfig, logger, saveConfig, setupMcpConfig };
|
package/dist/index.js
CHANGED
|
@@ -85,12 +85,17 @@ var Notifier = class {
|
|
|
85
85
|
desktopEnabled = true;
|
|
86
86
|
notificationSettingsOpened = false;
|
|
87
87
|
dashboardUrl;
|
|
88
|
+
telegramBot;
|
|
88
89
|
configure(options) {
|
|
89
90
|
if (options.dashboardUrl) this.dashboardUrl = options.dashboardUrl;
|
|
90
91
|
if (options.desktop !== void 0) this.desktopEnabled = options.desktop;
|
|
91
92
|
if (options.webhooks) this.webhooks = options.webhooks;
|
|
93
|
+
if (options.telegramBot) this.telegramBot = options.telegramBot;
|
|
92
94
|
}
|
|
93
95
|
async notify(title, message, level = "info") {
|
|
96
|
+
await this.notifyWithSession(void 0, void 0, title, message, level);
|
|
97
|
+
}
|
|
98
|
+
async notifyWithSession(sessionId, sessionLabel, title, message, level = "info") {
|
|
94
99
|
const promises = [];
|
|
95
100
|
if (this.desktopEnabled) {
|
|
96
101
|
promises.push(this.sendDesktop(title, message, level));
|
|
@@ -98,6 +103,9 @@ var Notifier = class {
|
|
|
98
103
|
for (const webhook of this.webhooks) {
|
|
99
104
|
promises.push(this.sendWebhook(webhook, title, message, level));
|
|
100
105
|
}
|
|
106
|
+
if (this.telegramBot && sessionId && sessionLabel) {
|
|
107
|
+
promises.push(this.telegramBot.sendNotification(sessionId, sessionLabel, title, message));
|
|
108
|
+
}
|
|
101
109
|
await Promise.allSettled(promises);
|
|
102
110
|
}
|
|
103
111
|
async sendDesktop(title, message, _level) {
|
|
@@ -178,6 +186,176 @@ var Notifier = class {
|
|
|
178
186
|
}
|
|
179
187
|
};
|
|
180
188
|
|
|
189
|
+
// src/hub/telegram.ts
|
|
190
|
+
var TELEGRAM_API = "https://api.telegram.org/bot";
|
|
191
|
+
var TelegramBot = class {
|
|
192
|
+
config;
|
|
193
|
+
offset = 0;
|
|
194
|
+
polling = false;
|
|
195
|
+
pollTimer = null;
|
|
196
|
+
// message_id -> sessionId mapping for reply-based routing
|
|
197
|
+
messageSessionMap = /* @__PURE__ */ new Map();
|
|
198
|
+
// Callback: when a message arrives from Telegram for a session
|
|
199
|
+
onMessageToSession;
|
|
200
|
+
// Callback: get current sessions list
|
|
201
|
+
getSessions;
|
|
202
|
+
// Callback: when user needs to select a session (sends inline keyboard)
|
|
203
|
+
pendingMessages = /* @__PURE__ */ new Map();
|
|
204
|
+
// chatId -> pending message text
|
|
205
|
+
constructor(config) {
|
|
206
|
+
this.config = config;
|
|
207
|
+
}
|
|
208
|
+
get apiUrl() {
|
|
209
|
+
return `${TELEGRAM_API}${this.config.botToken}`;
|
|
210
|
+
}
|
|
211
|
+
/** Send a notification message to Telegram */
|
|
212
|
+
async sendNotification(sessionId, _sessionLabel, title, message) {
|
|
213
|
+
const text = `${title}
|
|
214
|
+
${message}`;
|
|
215
|
+
const result = await this.sendMessage(text);
|
|
216
|
+
if (result?.message_id) {
|
|
217
|
+
this.messageSessionMap.set(result.message_id, sessionId);
|
|
218
|
+
if (this.messageSessionMap.size > 200) {
|
|
219
|
+
const keys = [...this.messageSessionMap.keys()];
|
|
220
|
+
for (let i = 0; i < keys.length - 200; i++) {
|
|
221
|
+
this.messageSessionMap.delete(keys[i]);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
/** Send a text message to the configured chat */
|
|
227
|
+
async sendMessage(text, replyToMessageId, replyMarkup) {
|
|
228
|
+
try {
|
|
229
|
+
const body = {
|
|
230
|
+
chat_id: this.config.chatId,
|
|
231
|
+
text,
|
|
232
|
+
parse_mode: "HTML"
|
|
233
|
+
};
|
|
234
|
+
if (replyToMessageId) body.reply_to_message_id = replyToMessageId;
|
|
235
|
+
if (replyMarkup) body.reply_markup = replyMarkup;
|
|
236
|
+
const res = await fetch(`${this.apiUrl}/sendMessage`, {
|
|
237
|
+
method: "POST",
|
|
238
|
+
headers: { "Content-Type": "application/json" },
|
|
239
|
+
body: JSON.stringify(body)
|
|
240
|
+
});
|
|
241
|
+
if (!res.ok) {
|
|
242
|
+
const err = await res.text();
|
|
243
|
+
logger.warn(`Telegram sendMessage failed: ${res.status} ${err}`);
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
const data = await res.json();
|
|
247
|
+
return data.ok ? data.result : null;
|
|
248
|
+
} catch (err) {
|
|
249
|
+
logger.warn(`Telegram sendMessage error: ${err.message}`);
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
/** Start long polling for incoming messages */
|
|
254
|
+
startPolling() {
|
|
255
|
+
if (this.polling) return;
|
|
256
|
+
this.polling = true;
|
|
257
|
+
logger.info("Telegram bot polling started");
|
|
258
|
+
this.poll();
|
|
259
|
+
}
|
|
260
|
+
/** Stop polling */
|
|
261
|
+
stopPolling() {
|
|
262
|
+
this.polling = false;
|
|
263
|
+
if (this.pollTimer) {
|
|
264
|
+
clearTimeout(this.pollTimer);
|
|
265
|
+
this.pollTimer = null;
|
|
266
|
+
}
|
|
267
|
+
logger.info("Telegram bot polling stopped");
|
|
268
|
+
}
|
|
269
|
+
async poll() {
|
|
270
|
+
if (!this.polling) return;
|
|
271
|
+
try {
|
|
272
|
+
const res = await fetch(`${this.apiUrl}/getUpdates?offset=${this.offset}&timeout=30`, {
|
|
273
|
+
signal: AbortSignal.timeout(35e3)
|
|
274
|
+
});
|
|
275
|
+
if (!res.ok) {
|
|
276
|
+
logger.warn(`Telegram getUpdates failed: ${res.status}`);
|
|
277
|
+
this.scheduleNextPoll(5e3);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
const data = await res.json();
|
|
281
|
+
if (data.ok && data.result.length > 0) {
|
|
282
|
+
for (const update of data.result) {
|
|
283
|
+
this.offset = update.update_id + 1;
|
|
284
|
+
if (update.message) {
|
|
285
|
+
this.handleIncomingMessage(update.message);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
} catch (err) {
|
|
290
|
+
if (err.name !== "AbortError") {
|
|
291
|
+
logger.warn(`Telegram poll error: ${err.message}`);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
this.scheduleNextPoll(1e3);
|
|
295
|
+
}
|
|
296
|
+
scheduleNextPoll(delay) {
|
|
297
|
+
if (!this.polling) return;
|
|
298
|
+
this.pollTimer = setTimeout(() => this.poll(), delay);
|
|
299
|
+
}
|
|
300
|
+
handleIncomingMessage(msg) {
|
|
301
|
+
if (!msg.text) return;
|
|
302
|
+
if (String(msg.chat.id) !== String(this.config.chatId)) return;
|
|
303
|
+
const text = msg.text.trim();
|
|
304
|
+
if (msg.reply_to_message) {
|
|
305
|
+
const sessionId = this.messageSessionMap.get(msg.reply_to_message.message_id);
|
|
306
|
+
if (sessionId) {
|
|
307
|
+
this.deliverToSession(sessionId, text);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
const selectMatch = text.match(/^\/s_(\d+)$/);
|
|
312
|
+
if (selectMatch) {
|
|
313
|
+
const pendingText = this.pendingMessages.get(msg.chat.id);
|
|
314
|
+
if (pendingText) {
|
|
315
|
+
this.pendingMessages.delete(msg.chat.id);
|
|
316
|
+
const sessions2 = this.getSessions?.() ?? [];
|
|
317
|
+
const idx = parseInt(selectMatch[1], 10) - 1;
|
|
318
|
+
if (idx >= 0 && idx < sessions2.length) {
|
|
319
|
+
this.deliverToSession(sessions2[idx].id, pendingText);
|
|
320
|
+
this.sendMessage(`Sent to [${this.getLabel(sessions2[idx])}]`);
|
|
321
|
+
} else {
|
|
322
|
+
this.sendMessage("Invalid session number.");
|
|
323
|
+
}
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
const sessions = this.getSessions?.() ?? [];
|
|
328
|
+
if (sessions.length === 0) {
|
|
329
|
+
this.sendMessage("No active sessions connected.");
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
if (sessions.length === 1) {
|
|
333
|
+
this.deliverToSession(sessions[0].id, text);
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
this.pendingMessages.set(msg.chat.id, text);
|
|
337
|
+
const sessionList = sessions.map((s, i) => `/s_${i + 1} - ${this.getLabel(s)}`).join("\n");
|
|
338
|
+
this.sendMessage(`Multiple sessions active. Reply with a command to select:
|
|
339
|
+
|
|
340
|
+
${sessionList}`);
|
|
341
|
+
}
|
|
342
|
+
deliverToSession(sessionId, content) {
|
|
343
|
+
if (this.onMessageToSession) {
|
|
344
|
+
this.onMessageToSession(sessionId, content);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
getLabel(session) {
|
|
348
|
+
return session.cwd?.replace(/^.*[/\\]/, "") || session.name;
|
|
349
|
+
}
|
|
350
|
+
/** Update config (e.g., from dashboard settings) */
|
|
351
|
+
updateConfig(config) {
|
|
352
|
+
const wasPolling = this.polling;
|
|
353
|
+
if (wasPolling) this.stopPolling();
|
|
354
|
+
this.config = config;
|
|
355
|
+
if (wasPolling && config.enabled) this.startPolling();
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
|
|
181
359
|
// src/shared/config.ts
|
|
182
360
|
import fs from "fs";
|
|
183
361
|
import path2 from "path";
|
|
@@ -207,7 +385,7 @@ function loadConfig() {
|
|
|
207
385
|
try {
|
|
208
386
|
const raw = fs.readFileSync(CONFIG_FILE, "utf-8");
|
|
209
387
|
const parsed = JSON.parse(raw);
|
|
210
|
-
config = { ...DEFAULT_CONFIG, ...parsed, hub: { ...DEFAULT_CONFIG.hub, ...parsed.hub } };
|
|
388
|
+
config = { ...DEFAULT_CONFIG, ...parsed, hub: { ...DEFAULT_CONFIG.hub, ...parsed.hub }, ...parsed.telegram ? { telegram: parsed.telegram } : {} };
|
|
211
389
|
} catch {
|
|
212
390
|
config = { ...DEFAULT_CONFIG, hub: { ...DEFAULT_CONFIG.hub } };
|
|
213
391
|
}
|
|
@@ -262,6 +440,7 @@ var HubServer = class {
|
|
|
262
440
|
localChannels = /* @__PURE__ */ new Set();
|
|
263
441
|
// All connected dashboard WebSockets
|
|
264
442
|
dashboardSockets = /* @__PURE__ */ new Set();
|
|
443
|
+
telegramBot;
|
|
265
444
|
host;
|
|
266
445
|
port;
|
|
267
446
|
token;
|
|
@@ -279,6 +458,10 @@ var HubServer = class {
|
|
|
279
458
|
}
|
|
280
459
|
const displayHost = this.host === "0.0.0.0" ? "127.0.0.1" : this.host;
|
|
281
460
|
this.notifier.configure({ dashboardUrl: `http://${displayHost}:${this.port}` });
|
|
461
|
+
const fullConfig = loadConfig();
|
|
462
|
+
if (fullConfig.telegram?.enabled && fullConfig.telegram.botToken && fullConfig.telegram.chatId) {
|
|
463
|
+
this.initTelegram(fullConfig.telegram);
|
|
464
|
+
}
|
|
282
465
|
this.httpServer = http.createServer((req, res) => this.handleHttp(req, res));
|
|
283
466
|
this.wssChannel = new WebSocketServer({ noServer: true });
|
|
284
467
|
this.wssChannel.on("connection", (ws, req) => this.handleChannelConnection(ws, req));
|
|
@@ -320,6 +503,7 @@ var HubServer = class {
|
|
|
320
503
|
}
|
|
321
504
|
stop() {
|
|
322
505
|
return new Promise((resolve) => {
|
|
506
|
+
if (this.telegramBot) this.telegramBot.stopPolling();
|
|
323
507
|
for (const ws of this.channelSockets.values()) ws.terminate();
|
|
324
508
|
for (const ws of this.dashboardSockets) ws.terminate();
|
|
325
509
|
this.channelSockets.clear();
|
|
@@ -381,6 +565,16 @@ var HubServer = class {
|
|
|
381
565
|
this.jsonResponse(res, 200, { webhooks: config.webhooks || [] });
|
|
382
566
|
} else if (url.pathname === "/api/webhooks" && req.method === "POST") {
|
|
383
567
|
this.handleWebhookSave(req, res);
|
|
568
|
+
} else if (url.pathname === "/api/telegram" && req.method === "GET") {
|
|
569
|
+
const cfg = loadConfig();
|
|
570
|
+
const tg = cfg.telegram ?? { botToken: "", chatId: "", enabled: false };
|
|
571
|
+
this.jsonResponse(res, 200, {
|
|
572
|
+
telegram: { ...tg, botToken: tg.botToken ? `${tg.botToken.slice(0, 8)}...` : "" }
|
|
573
|
+
});
|
|
574
|
+
} else if (url.pathname === "/api/telegram" && req.method === "POST") {
|
|
575
|
+
this.handleTelegramSave(req, res);
|
|
576
|
+
} else if (url.pathname === "/api/telegram/test" && req.method === "POST") {
|
|
577
|
+
this.handleTelegramTest(req, res);
|
|
384
578
|
} else {
|
|
385
579
|
this.jsonResponse(res, 404, { error: "Not found" });
|
|
386
580
|
}
|
|
@@ -501,7 +695,7 @@ var HubServer = class {
|
|
|
501
695
|
this.sessions.updateActivity(msg.sessionId);
|
|
502
696
|
const notifySession = this.sessions.get(msg.sessionId);
|
|
503
697
|
const notifyLabel = this.getSessionLabel(notifySession);
|
|
504
|
-
this.notifier.
|
|
698
|
+
this.notifier.notifyWithSession(msg.sessionId, notifyLabel, `[${notifyLabel}] ${msg.title}`, msg.message, msg.level ?? "info");
|
|
505
699
|
this.broadcastToDashboards({
|
|
506
700
|
type: "notification",
|
|
507
701
|
sessionId: msg.sessionId,
|
|
@@ -516,7 +710,7 @@ var HubServer = class {
|
|
|
516
710
|
this.sessions.updateActivity(msg.sessionId);
|
|
517
711
|
const replySession = this.sessions.get(msg.sessionId);
|
|
518
712
|
const replyLabel = this.getSessionLabel(replySession);
|
|
519
|
-
this.notifier.
|
|
713
|
+
this.notifier.notifyWithSession(msg.sessionId, replyLabel, `[${replyLabel}] Reply`, msg.content.slice(0, 200), "info");
|
|
520
714
|
this.broadcastToDashboards({
|
|
521
715
|
type: "reply_from_session",
|
|
522
716
|
sessionId: msg.sessionId,
|
|
@@ -619,6 +813,75 @@ var HubServer = class {
|
|
|
619
813
|
this.notifier.configure({ webhooks });
|
|
620
814
|
this.jsonResponse(res, 200, { ok: true });
|
|
621
815
|
}
|
|
816
|
+
initTelegram(config) {
|
|
817
|
+
this.telegramBot = new TelegramBot(config);
|
|
818
|
+
this.telegramBot.getSessions = () => this.sessions.getAll();
|
|
819
|
+
this.telegramBot.onMessageToSession = (sessionId, content) => {
|
|
820
|
+
const channelWs = this.channelSockets.get(sessionId);
|
|
821
|
+
if (channelWs?.readyState === WebSocket.OPEN) {
|
|
822
|
+
const msg = { type: "message_to_session", sessionId, content };
|
|
823
|
+
channelWs.send(JSON.stringify(msg));
|
|
824
|
+
logger.info(`Telegram message forwarded to session: ${sessionId}`);
|
|
825
|
+
}
|
|
826
|
+
};
|
|
827
|
+
this.notifier.configure({ telegramBot: this.telegramBot });
|
|
828
|
+
this.telegramBot.startPolling();
|
|
829
|
+
logger.info("Telegram bot initialized");
|
|
830
|
+
}
|
|
831
|
+
async handleTelegramSave(req, res) {
|
|
832
|
+
const body = await this.readBody(req);
|
|
833
|
+
if (!body) {
|
|
834
|
+
this.jsonResponse(res, 400, { error: "Invalid JSON" });
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
const { telegram } = body;
|
|
838
|
+
if (!telegram) {
|
|
839
|
+
this.jsonResponse(res, 400, { error: "telegram config required" });
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
const config = loadConfig();
|
|
843
|
+
config.telegram = telegram;
|
|
844
|
+
saveConfig(config);
|
|
845
|
+
if (this.telegramBot) {
|
|
846
|
+
this.telegramBot.stopPolling();
|
|
847
|
+
this.telegramBot = void 0;
|
|
848
|
+
this.notifier.configure({ telegramBot: void 0 });
|
|
849
|
+
}
|
|
850
|
+
if (telegram.enabled && telegram.botToken && telegram.chatId) {
|
|
851
|
+
this.initTelegram(telegram);
|
|
852
|
+
}
|
|
853
|
+
this.jsonResponse(res, 200, { ok: true });
|
|
854
|
+
}
|
|
855
|
+
async handleTelegramTest(req, res) {
|
|
856
|
+
const body = await this.readBody(req);
|
|
857
|
+
if (!body) {
|
|
858
|
+
this.jsonResponse(res, 400, { error: "Invalid JSON" });
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
const { botToken, chatId } = body;
|
|
862
|
+
if (!botToken || !chatId) {
|
|
863
|
+
this.jsonResponse(res, 400, { error: "botToken and chatId required" });
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
try {
|
|
867
|
+
const testRes = await fetch(`https://api.telegram.org/bot${botToken}/sendMessage`, {
|
|
868
|
+
method: "POST",
|
|
869
|
+
headers: { "Content-Type": "application/json" },
|
|
870
|
+
body: JSON.stringify({
|
|
871
|
+
chat_id: chatId,
|
|
872
|
+
text: "Claude Alarm test message! Connection successful."
|
|
873
|
+
})
|
|
874
|
+
});
|
|
875
|
+
if (testRes.ok) {
|
|
876
|
+
this.jsonResponse(res, 200, { ok: true });
|
|
877
|
+
} else {
|
|
878
|
+
const err = await testRes.json();
|
|
879
|
+
this.jsonResponse(res, 400, { error: err.description || "Telegram API error" });
|
|
880
|
+
}
|
|
881
|
+
} catch (err) {
|
|
882
|
+
this.jsonResponse(res, 500, { error: err.message });
|
|
883
|
+
}
|
|
884
|
+
}
|
|
622
885
|
cleanupUploads() {
|
|
623
886
|
try {
|
|
624
887
|
if (!fs2.existsSync(UPLOADS_DIR)) return;
|