@delt/claude-alarm 0.5.2 → 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 +189 -69
- package/dist/cli.js.map +1 -1
- package/dist/dashboard/index.html +136 -20
- package/dist/hub/server.js +170 -51
- package/dist/hub/server.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +175 -56
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/dashboard/index.html +136 -20
|
@@ -650,24 +650,64 @@
|
|
|
650
650
|
</div>
|
|
651
651
|
</div>
|
|
652
652
|
<div id="settingsTelegram" style="display:none">
|
|
653
|
-
|
|
654
|
-
<div
|
|
655
|
-
<
|
|
656
|
-
|
|
653
|
+
<!-- Step 1: Bot Token -->
|
|
654
|
+
<div id="tgStep1">
|
|
655
|
+
<div style="display:flex;align-items:center;gap:8px;margin-bottom:8px">
|
|
656
|
+
<span style="background:var(--accent);color:#fff;border-radius:50%;width:22px;height:22px;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;flex-shrink:0">1</span>
|
|
657
|
+
<span style="font-size:13px;font-weight:600;color:var(--text)">Create a bot & paste the token</span>
|
|
658
|
+
</div>
|
|
659
|
+
<p style="margin:0 0 10px 30px;font-size:12px;color:var(--text-dim)">Open <strong>@BotFather</strong> on Telegram → <code>/newbot</code> → copy the token</p>
|
|
660
|
+
<div style="margin-left:30px;margin-bottom:12px">
|
|
661
|
+
<input type="text" id="tgBotToken" placeholder="123456:ABC-DEF1234ghIkl-zyx57W2v..." autocomplete="off" style="width:100%;box-sizing:border-box;padding:10px;background:var(--input-bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:13px;font-family:monospace">
|
|
662
|
+
</div>
|
|
663
|
+
<div style="margin-left:30px">
|
|
664
|
+
<button id="tgNextStep2" style="padding:8px 20px;font-size:13px">Next →</button>
|
|
665
|
+
</div>
|
|
657
666
|
</div>
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
667
|
+
|
|
668
|
+
<!-- Step 2: Detect Chat ID -->
|
|
669
|
+
<div id="tgStep2" style="display:none">
|
|
670
|
+
<div style="display:flex;align-items:center;gap:8px;margin-bottom:8px">
|
|
671
|
+
<span style="background:var(--accent);color:#fff;border-radius:50%;width:22px;height:22px;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;flex-shrink:0">2</span>
|
|
672
|
+
<span style="font-size:13px;font-weight:600;color:var(--text)">Find your Chat ID</span>
|
|
673
|
+
</div>
|
|
674
|
+
<p style="margin:0 0 10px 30px;font-size:12px;color:var(--text-dim)">Send any message to your bot on Telegram, then click Detect.</p>
|
|
675
|
+
<div style="margin-left:30px;margin-bottom:10px;display:flex;gap:8px">
|
|
676
|
+
<button id="tgDetect" style="padding:8px 20px;font-size:13px;background:none;color:var(--accent);border:1px solid var(--accent);border-radius:6px;cursor:pointer;font-weight:500">Detect Chat ID</button>
|
|
677
|
+
<span id="tgDetectStatus" style="font-size:12px;color:var(--text-dim);display:flex;align-items:center"></span>
|
|
678
|
+
</div>
|
|
679
|
+
<div id="tgChatList" style="margin-left:30px;margin-bottom:10px"></div>
|
|
680
|
+
<div style="margin-left:30px;margin-bottom:12px">
|
|
681
|
+
<label style="display:block;font-size:11px;color:var(--text-dim);margin-bottom:4px">Or enter manually:</label>
|
|
682
|
+
<input type="text" id="tgChatId" placeholder="-1001234567890" autocomplete="off" style="width:100%;box-sizing:border-box;padding:10px;background:var(--input-bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:13px;font-family:monospace">
|
|
683
|
+
</div>
|
|
684
|
+
<div style="margin-left:30px;display:flex;gap:8px">
|
|
685
|
+
<button id="tgBackStep1" style="padding:8px 20px;font-size:13px;background:none;color:var(--text-dim);border:1px solid var(--border);border-radius:6px;cursor:pointer">← Back</button>
|
|
686
|
+
<button id="tgNextStep3" style="padding:8px 20px;font-size:13px">Next →</button>
|
|
687
|
+
</div>
|
|
665
688
|
</div>
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
<
|
|
670
|
-
|
|
689
|
+
|
|
690
|
+
<!-- Step 3: Test & Save -->
|
|
691
|
+
<div id="tgStep3" style="display:none">
|
|
692
|
+
<div style="display:flex;align-items:center;gap:8px;margin-bottom:8px">
|
|
693
|
+
<span style="background:var(--accent);color:#fff;border-radius:50%;width:22px;height:22px;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;flex-shrink:0">3</span>
|
|
694
|
+
<span style="font-size:13px;font-weight:600;color:var(--text)">Test & Enable</span>
|
|
695
|
+
</div>
|
|
696
|
+
<div style="margin-left:30px;margin-bottom:12px;padding:10px;background:var(--input-bg);border-radius:6px;font-size:12px;color:var(--text-dim)">
|
|
697
|
+
<div>Bot Token: <span id="tgSummaryToken" style="color:var(--text);font-family:monospace"></span></div>
|
|
698
|
+
<div style="margin-top:4px">Chat ID: <span id="tgSummaryChatId" style="color:var(--text);font-family:monospace"></span></div>
|
|
699
|
+
</div>
|
|
700
|
+
<div style="margin-left:30px;margin-bottom:12px;display:flex;align-items:center;gap:8px">
|
|
701
|
+
<input type="checkbox" id="tgEnabled" checked style="width:16px;height:16px;accent-color:var(--accent)">
|
|
702
|
+
<label for="tgEnabled" style="font-size:13px;color:var(--text)">Enable Telegram notifications</label>
|
|
703
|
+
</div>
|
|
704
|
+
<div id="tgStatus" style="display:none;margin:0 0 12px 30px;padding:8px 12px;border-radius:6px;font-size:12px"></div>
|
|
705
|
+
<div style="margin-left:30px;display:flex;gap:8px">
|
|
706
|
+
<button id="tgBackStep2" style="padding:8px 20px;font-size:13px;background:none;color:var(--text-dim);border:1px solid var(--border);border-radius:6px;cursor:pointer">← Back</button>
|
|
707
|
+
<button id="tgTest" style="flex:1;padding:10px;background:none;color:var(--accent);border:1px solid var(--accent);border-radius:6px;cursor:pointer;font-size:13px;font-weight:500">Send Test</button>
|
|
708
|
+
<button id="tgSave" style="flex:1;padding:10px">Save</button>
|
|
709
|
+
<button id="settingsCancel2" style="padding:10px 16px;background:none;color:var(--text);border:1px solid var(--border);border-radius:6px;cursor:pointer;font-size:13px">Cancel</button>
|
|
710
|
+
</div>
|
|
671
711
|
</div>
|
|
672
712
|
</div>
|
|
673
713
|
</div>
|
|
@@ -1217,7 +1257,17 @@
|
|
|
1217
1257
|
}
|
|
1218
1258
|
$('#tgChatId').value = tg.chatId || '';
|
|
1219
1259
|
$('#tgEnabled').checked = !!tg.enabled;
|
|
1220
|
-
|
|
1260
|
+
// Jump to appropriate step
|
|
1261
|
+
if (tg.chatId && tg.botToken) {
|
|
1262
|
+
$('#tgSummaryToken').textContent = (tg.botToken || '').slice(0, 8) + '...';
|
|
1263
|
+
$('#tgSummaryChatId').textContent = tg.chatId;
|
|
1264
|
+
showTgStep(3);
|
|
1265
|
+
} else if (tg.botToken) {
|
|
1266
|
+
showTgStep(2);
|
|
1267
|
+
} else {
|
|
1268
|
+
showTgStep(1);
|
|
1269
|
+
}
|
|
1270
|
+
} catch { showTgStep(1); }
|
|
1221
1271
|
$('#tgStatus').style.display = 'none';
|
|
1222
1272
|
$('#settingsOverlay').classList.remove('hidden');
|
|
1223
1273
|
});
|
|
@@ -1268,7 +1318,13 @@
|
|
|
1268
1318
|
});
|
|
1269
1319
|
}
|
|
1270
1320
|
|
|
1271
|
-
// Telegram
|
|
1321
|
+
// Telegram wizard
|
|
1322
|
+
function showTgStep(step) {
|
|
1323
|
+
$('#tgStep1').style.display = step === 1 ? 'block' : 'none';
|
|
1324
|
+
$('#tgStep2').style.display = step === 2 ? 'block' : 'none';
|
|
1325
|
+
$('#tgStep3').style.display = step === 3 ? 'block' : 'none';
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1272
1328
|
function showTgStatus(msg, ok) {
|
|
1273
1329
|
const el = $('#tgStatus');
|
|
1274
1330
|
el.textContent = msg;
|
|
@@ -1277,11 +1333,70 @@
|
|
|
1277
1333
|
el.style.color = ok ? 'var(--green)' : 'var(--red)';
|
|
1278
1334
|
}
|
|
1279
1335
|
|
|
1336
|
+
// Step navigation
|
|
1337
|
+
$('#tgNextStep2').addEventListener('click', () => {
|
|
1338
|
+
const token = $('#tgBotToken').value.trim();
|
|
1339
|
+
if (!token) { $('#tgBotToken').style.borderColor = 'var(--red)'; return; }
|
|
1340
|
+
$('#tgBotToken').style.borderColor = 'var(--border)';
|
|
1341
|
+
showTgStep(2);
|
|
1342
|
+
});
|
|
1343
|
+
$('#tgBackStep1').addEventListener('click', () => showTgStep(1));
|
|
1344
|
+
$('#tgNextStep3').addEventListener('click', () => {
|
|
1345
|
+
const chatId = $('#tgChatId').value.trim();
|
|
1346
|
+
if (!chatId) { $('#tgChatId').style.borderColor = 'var(--red)'; return; }
|
|
1347
|
+
$('#tgChatId').style.borderColor = 'var(--border)';
|
|
1348
|
+
const token = $('#tgBotToken').value.trim();
|
|
1349
|
+
$('#tgSummaryToken').textContent = token.slice(0, 8) + '...';
|
|
1350
|
+
$('#tgSummaryChatId').textContent = chatId;
|
|
1351
|
+
$('#tgStatus').style.display = 'none';
|
|
1352
|
+
showTgStep(3);
|
|
1353
|
+
});
|
|
1354
|
+
$('#tgBackStep2').addEventListener('click', () => showTgStep(2));
|
|
1355
|
+
|
|
1356
|
+
// Detect Chat ID
|
|
1357
|
+
$('#tgDetect').addEventListener('click', async () => {
|
|
1358
|
+
const botToken = $('#tgBotToken').value.trim();
|
|
1359
|
+
if (!botToken || botToken.includes('...')) { $('#tgDetectStatus').textContent = 'Enter a valid Bot Token first.'; return; }
|
|
1360
|
+
$('#tgDetectStatus').textContent = 'Detecting...';
|
|
1361
|
+
$('#tgChatList').innerHTML = '';
|
|
1362
|
+
try {
|
|
1363
|
+
const tokenQuery = state.token ? `?token=${encodeURIComponent(state.token)}` : '';
|
|
1364
|
+
const res = await fetch(`/api/telegram/detect${tokenQuery}`, {
|
|
1365
|
+
method: 'POST',
|
|
1366
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1367
|
+
body: JSON.stringify({ botToken }),
|
|
1368
|
+
});
|
|
1369
|
+
const data = await res.json();
|
|
1370
|
+
if (data.error) { $('#tgDetectStatus').textContent = data.error; return; }
|
|
1371
|
+
if (!data.ok || !data.chats.length) {
|
|
1372
|
+
$('#tgDetectStatus').textContent = 'No messages found. Send a message to your bot first!';
|
|
1373
|
+
return;
|
|
1374
|
+
}
|
|
1375
|
+
$('#tgDetectStatus').textContent = 'Found ' + data.chats.length + ' chat(s):';
|
|
1376
|
+
$('#tgChatList').innerHTML = data.chats.map(c =>
|
|
1377
|
+
`<button class="tg-chat-pick" data-id="${esc(c.id)}" style="display:block;width:100%;text-align:left;padding:8px 12px;margin-bottom:4px;background:var(--input-bg);border:1px solid var(--border);border-radius:6px;color:var(--text);cursor:pointer;font-size:12px;transition:border-color 0.15s">
|
|
1378
|
+
<strong>${esc(c.name)}</strong> <span style="color:var(--text-dim)">(${esc(c.type)}, ID: ${esc(c.id)})</span>
|
|
1379
|
+
</button>`
|
|
1380
|
+
).join('');
|
|
1381
|
+
$('#tgChatList').querySelectorAll('.tg-chat-pick').forEach(btn => {
|
|
1382
|
+
btn.addEventListener('click', () => {
|
|
1383
|
+
$('#tgChatId').value = btn.dataset.id;
|
|
1384
|
+
$('#tgChatList').querySelectorAll('.tg-chat-pick').forEach(b => b.style.borderColor = 'var(--border)');
|
|
1385
|
+
btn.style.borderColor = 'var(--accent)';
|
|
1386
|
+
});
|
|
1387
|
+
btn.addEventListener('mouseenter', () => { btn.style.borderColor = 'var(--accent)'; });
|
|
1388
|
+
btn.addEventListener('mouseleave', () => {
|
|
1389
|
+
if ($('#tgChatId').value !== btn.dataset.id) btn.style.borderColor = 'var(--border)';
|
|
1390
|
+
});
|
|
1391
|
+
});
|
|
1392
|
+
} catch { $('#tgDetectStatus').textContent = 'Connection error.'; }
|
|
1393
|
+
});
|
|
1394
|
+
|
|
1395
|
+
// Test
|
|
1280
1396
|
$('#tgTest').addEventListener('click', async () => {
|
|
1281
1397
|
const botToken = $('#tgBotToken').value.trim();
|
|
1282
1398
|
const chatId = $('#tgChatId').value.trim();
|
|
1283
1399
|
if (!botToken || !chatId) { showTgStatus('Bot Token and Chat ID are required.', false); return; }
|
|
1284
|
-
if (botToken.includes('...')) { showTgStatus('Please enter full Bot Token (not masked).', false); return; }
|
|
1285
1400
|
try {
|
|
1286
1401
|
const tokenQuery = state.token ? `?token=${encodeURIComponent(state.token)}` : '';
|
|
1287
1402
|
const res = await fetch(`/api/telegram/test${tokenQuery}`, {
|
|
@@ -1295,11 +1410,12 @@
|
|
|
1295
1410
|
} catch (e) { showTgStatus('Connection error.', false); }
|
|
1296
1411
|
});
|
|
1297
1412
|
|
|
1413
|
+
// Save
|
|
1298
1414
|
$('#tgSave').addEventListener('click', async () => {
|
|
1299
1415
|
const botToken = $('#tgBotToken').value.trim();
|
|
1300
1416
|
const chatId = $('#tgChatId').value.trim();
|
|
1301
1417
|
const enabled = $('#tgEnabled').checked;
|
|
1302
|
-
if (
|
|
1418
|
+
if (!botToken || !chatId) { showTgStatus('Bot Token and Chat ID are required.', false); return; }
|
|
1303
1419
|
try {
|
|
1304
1420
|
const tokenQuery = state.token ? `?token=${encodeURIComponent(state.token)}` : '';
|
|
1305
1421
|
await fetch(`/api/telegram${tokenQuery}`, {
|
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;
|
|
@@ -551,29 +616,31 @@ var HubServer = class {
|
|
|
551
616
|
this.handleTelegramSave(req, res);
|
|
552
617
|
} else if (url.pathname === "/api/telegram/test" && req.method === "POST") {
|
|
553
618
|
this.handleTelegramTest(req, res);
|
|
619
|
+
} else if (url.pathname === "/api/telegram/detect" && req.method === "POST") {
|
|
620
|
+
this.handleTelegramDetect(req, res);
|
|
554
621
|
} else {
|
|
555
622
|
this.jsonResponse(res, 404, { error: "Not found" });
|
|
556
623
|
}
|
|
557
624
|
}
|
|
558
625
|
serveDashboard(res) {
|
|
559
626
|
const candidates = [
|
|
560
|
-
|
|
627
|
+
path4.join(__dirname, "..", "dashboard", "index.html"),
|
|
561
628
|
// from dist/hub/
|
|
562
|
-
|
|
629
|
+
path4.join(__dirname, "dashboard", "index.html"),
|
|
563
630
|
// from dist/ (bundled index.js)
|
|
564
|
-
|
|
631
|
+
path4.join(__dirname, "..", "..", "src", "dashboard", "index.html"),
|
|
565
632
|
// from dist/hub/ -> src/
|
|
566
|
-
|
|
633
|
+
path4.join(__dirname, "..", "src", "dashboard", "index.html"),
|
|
567
634
|
// from dist/ -> src/
|
|
568
|
-
|
|
635
|
+
path4.join(process.cwd(), "dist", "dashboard", "index.html"),
|
|
569
636
|
// from cwd
|
|
570
|
-
|
|
637
|
+
path4.join(process.cwd(), "src", "dashboard", "index.html")
|
|
571
638
|
// from cwd/src
|
|
572
639
|
];
|
|
573
640
|
logger.debug(`Dashboard candidates: ${JSON.stringify(candidates)}`);
|
|
574
641
|
for (const candidate of candidates) {
|
|
575
|
-
if (
|
|
576
|
-
const html =
|
|
642
|
+
if (fs3.existsSync(candidate)) {
|
|
643
|
+
const html = fs3.readFileSync(candidate, "utf-8");
|
|
577
644
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
578
645
|
res.end(html);
|
|
579
646
|
return;
|
|
@@ -751,11 +818,11 @@ var HubServer = class {
|
|
|
751
818
|
logger.warn("Image upload rejected: exceeds 10MB");
|
|
752
819
|
return;
|
|
753
820
|
}
|
|
754
|
-
|
|
821
|
+
fs3.mkdirSync(UPLOADS_DIR, { recursive: true });
|
|
755
822
|
const ext = mimeType.split("/")[1] === "jpeg" ? "jpg" : mimeType.split("/")[1];
|
|
756
|
-
const filename = `${
|
|
757
|
-
const filePath =
|
|
758
|
-
|
|
823
|
+
const filename = `${randomUUID3()}.${ext}`;
|
|
824
|
+
const filePath = path4.join(UPLOADS_DIR, filename);
|
|
825
|
+
fs3.writeFileSync(filePath, buffer);
|
|
759
826
|
const forwardMsg = {
|
|
760
827
|
type: "image_to_session",
|
|
761
828
|
sessionId,
|
|
@@ -768,7 +835,7 @@ var HubServer = class {
|
|
|
768
835
|
logger.info(`Image saved and forwarded: ${filename} (${buffer.length} bytes)`);
|
|
769
836
|
setTimeout(() => {
|
|
770
837
|
try {
|
|
771
|
-
|
|
838
|
+
fs3.unlinkSync(filePath);
|
|
772
839
|
} catch {
|
|
773
840
|
}
|
|
774
841
|
}, 5 * 60 * 1e3);
|
|
@@ -801,6 +868,14 @@ var HubServer = class {
|
|
|
801
868
|
logger.info(`Telegram message forwarded to session: ${sessionId}`);
|
|
802
869
|
}
|
|
803
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
|
+
};
|
|
804
879
|
this.notifier.configure({ telegramBot: this.telegramBot });
|
|
805
880
|
this.telegramBot.startPolling();
|
|
806
881
|
logger.info("Telegram bot initialized");
|
|
@@ -859,13 +934,57 @@ var HubServer = class {
|
|
|
859
934
|
this.jsonResponse(res, 500, { error: err.message });
|
|
860
935
|
}
|
|
861
936
|
}
|
|
937
|
+
async handleTelegramDetect(req, res) {
|
|
938
|
+
const body = await this.readBody(req);
|
|
939
|
+
if (!body) {
|
|
940
|
+
this.jsonResponse(res, 400, { error: "Invalid JSON" });
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
const { botToken } = body;
|
|
944
|
+
if (!botToken) {
|
|
945
|
+
this.jsonResponse(res, 400, { error: "botToken required" });
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
try {
|
|
949
|
+
const detectRes = await fetch(`https://api.telegram.org/bot${botToken}/getUpdates?timeout=0&limit=10`, {
|
|
950
|
+
signal: AbortSignal.timeout(1e4)
|
|
951
|
+
});
|
|
952
|
+
if (!detectRes.ok) {
|
|
953
|
+
const err = await detectRes.json();
|
|
954
|
+
this.jsonResponse(res, 400, { error: err.description || "Invalid bot token" });
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
const data = await detectRes.json();
|
|
958
|
+
if (!data.ok || !data.result.length) {
|
|
959
|
+
this.jsonResponse(res, 200, { ok: false, chats: [] });
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
962
|
+
const chatMap = /* @__PURE__ */ new Map();
|
|
963
|
+
for (const update of data.result) {
|
|
964
|
+
if (update.message?.chat) {
|
|
965
|
+
const chat = update.message.chat;
|
|
966
|
+
const id = String(chat.id);
|
|
967
|
+
if (!chatMap.has(id)) {
|
|
968
|
+
chatMap.set(id, {
|
|
969
|
+
id,
|
|
970
|
+
name: chat.title || chat.first_name || id,
|
|
971
|
+
type: chat.type
|
|
972
|
+
});
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
this.jsonResponse(res, 200, { ok: true, chats: [...chatMap.values()] });
|
|
977
|
+
} catch (err) {
|
|
978
|
+
this.jsonResponse(res, 500, { error: err.message });
|
|
979
|
+
}
|
|
980
|
+
}
|
|
862
981
|
cleanupUploads() {
|
|
863
982
|
try {
|
|
864
|
-
if (!
|
|
865
|
-
const files =
|
|
983
|
+
if (!fs3.existsSync(UPLOADS_DIR)) return;
|
|
984
|
+
const files = fs3.readdirSync(UPLOADS_DIR);
|
|
866
985
|
for (const file of files) {
|
|
867
986
|
try {
|
|
868
|
-
|
|
987
|
+
fs3.unlinkSync(path4.join(UPLOADS_DIR, file));
|
|
869
988
|
} catch {
|
|
870
989
|
}
|
|
871
990
|
}
|