@delt/claude-alarm 0.8.2 → 0.9.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 +43 -4
- package/dist/channel/server.js +69 -5
- package/dist/channel/server.js.map +1 -1
- package/dist/cli.js +280 -69
- package/dist/cli.js.map +1 -1
- package/dist/dashboard/index.html +239 -42
- package/dist/hub/server.js +160 -33
- package/dist/hub/server.js.map +1 -1
- package/dist/index.d.ts +12 -1
- package/dist/index.js +164 -35
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
- package/src/dashboard/index.html +239 -42
package/src/dashboard/index.html
CHANGED
|
@@ -187,6 +187,7 @@
|
|
|
187
187
|
outline: none;
|
|
188
188
|
font-family: inherit;
|
|
189
189
|
}
|
|
190
|
+
.name-edit-input.invalid { border-color: var(--red); }
|
|
190
191
|
.session-name {
|
|
191
192
|
font-size: 14px;
|
|
192
193
|
font-weight: 500;
|
|
@@ -400,6 +401,39 @@
|
|
|
400
401
|
}
|
|
401
402
|
.drag-overlay.active { display: flex; }
|
|
402
403
|
|
|
404
|
+
.mention-error {
|
|
405
|
+
display: none;
|
|
406
|
+
padding: 6px 20px 0;
|
|
407
|
+
color: var(--red);
|
|
408
|
+
font-size: 12px;
|
|
409
|
+
}
|
|
410
|
+
.mention-error.show { display: block; }
|
|
411
|
+
.message-input-area { position: relative; }
|
|
412
|
+
.mention-popup {
|
|
413
|
+
display: none;
|
|
414
|
+
position: absolute;
|
|
415
|
+
bottom: calc(100% + 4px);
|
|
416
|
+
left: 20px;
|
|
417
|
+
min-width: 220px;
|
|
418
|
+
max-height: 200px;
|
|
419
|
+
overflow-y: auto;
|
|
420
|
+
background: var(--surface);
|
|
421
|
+
border: 1px solid var(--border);
|
|
422
|
+
border-radius: 6px;
|
|
423
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
|
|
424
|
+
z-index: 20;
|
|
425
|
+
}
|
|
426
|
+
.mention-popup.open { display: block; }
|
|
427
|
+
.mention-item {
|
|
428
|
+
display: flex;
|
|
429
|
+
justify-content: space-between;
|
|
430
|
+
gap: 12px;
|
|
431
|
+
padding: 6px 10px;
|
|
432
|
+
font-size: 13px;
|
|
433
|
+
cursor: pointer;
|
|
434
|
+
}
|
|
435
|
+
.mention-item.active, .mention-item:hover { background: var(--border); }
|
|
436
|
+
.mention-folder { color: var(--text-dim); font-size: 11px; }
|
|
403
437
|
.message-input-area {
|
|
404
438
|
border-top: 1px solid var(--border);
|
|
405
439
|
padding: 12px 20px;
|
|
@@ -837,7 +871,7 @@
|
|
|
837
871
|
<div class="token-overlay hidden" id="tokenOverlay">
|
|
838
872
|
<div class="token-form">
|
|
839
873
|
<h2>Authentication Required</h2>
|
|
840
|
-
<p>Enter the hub token to connect.
|
|
874
|
+
<p>Enter the hub token to connect. Run <code>claude-alarm token</code>, or open the login link printed by <code>claude-alarm hub start</code>.</p>
|
|
841
875
|
<input type="text" id="tokenInput" placeholder="Paste token here..." autocomplete="off">
|
|
842
876
|
<button id="tokenSubmit">Connect</button>
|
|
843
877
|
<div class="token-error" id="tokenError">Connection failed. Check your token.</div>
|
|
@@ -963,7 +997,9 @@
|
|
|
963
997
|
<span class="preview-name" id="previewName"></span>
|
|
964
998
|
<button class="preview-cancel" id="previewCancel">×</button>
|
|
965
999
|
</div>
|
|
1000
|
+
<div class="mention-error" id="mentionError"></div>
|
|
966
1001
|
<div class="message-input-area">
|
|
1002
|
+
<div class="mention-popup" id="mentionPopup"></div>
|
|
967
1003
|
<button class="attach-btn" id="attachBtn" disabled title="Attach image">📎</button>
|
|
968
1004
|
<input type="file" id="fileInput" accept="image/*" style="display:none">
|
|
969
1005
|
<textarea id="msgInput" placeholder="Send a message to session... (Shift+Enter for new line)" disabled rows="1"></textarea>
|
|
@@ -990,7 +1026,6 @@
|
|
|
990
1026
|
selectedSession: null,
|
|
991
1027
|
messages: {},
|
|
992
1028
|
notifications: [],
|
|
993
|
-
token: null,
|
|
994
1029
|
pendingImage: null,
|
|
995
1030
|
unread: {},
|
|
996
1031
|
waitingReply: {},
|
|
@@ -1095,6 +1130,146 @@
|
|
|
1095
1130
|
return state.sessionMeta.names[s.id] || s.displayName || s.name;
|
|
1096
1131
|
}
|
|
1097
1132
|
|
|
1133
|
+
function normName(s) {
|
|
1134
|
+
return String(s).normalize('NFC').trim().toLowerCase();
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
function folderName(s) {
|
|
1138
|
+
return s.displayName || s.name;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
function renameError(val, id) {
|
|
1142
|
+
if (/[\[\]@]/.test(val)) return 'Name cannot contain [, ] or @';
|
|
1143
|
+
const key = normName(val);
|
|
1144
|
+
const taken = Object.values(state.sessions).some(o => o.id !== id &&
|
|
1145
|
+
(normName(sessionDisplayName(o)) === key || normName(folderName(o)) === key));
|
|
1146
|
+
return taken ? 'Name already used by another session' : null;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
function sessionNames(s) {
|
|
1150
|
+
return [state.sessionMeta.names[s.id], folderName(s), s.peerName].filter(Boolean);
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
// Built-in SendMessage only reaches sessions on the hub's own machine.
|
|
1154
|
+
function mentionTargets() {
|
|
1155
|
+
const selected = state.sessions[state.selectedSession];
|
|
1156
|
+
if (!selected || !selected.isLocal) return [];
|
|
1157
|
+
return Object.values(state.sessions).filter(s => s.id !== selected.id && s.isLocal && s.peerName);
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
function resolveMention(name) {
|
|
1161
|
+
const key = normName(name);
|
|
1162
|
+
const matches = mentionTargets().filter(s => sessionNames(s).some(n => normName(n) === key));
|
|
1163
|
+
return matches.length === 1 ? matches[0] : null;
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
function mentionLabel(s) {
|
|
1167
|
+
return [sessionDisplayName(s), folderName(s), s.peerName].find(n => resolveMention(n) === s) || s.peerName;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
// A token only counts as a mention when it names (or starts with) some
|
|
1171
|
+
// session, so pasted code like @Override or npm @types/x is left alone.
|
|
1172
|
+
function looksLikeSessionMention(name) {
|
|
1173
|
+
const key = normName(name);
|
|
1174
|
+
return Object.values(state.sessions).some(s => sessionNames(s).some(n => key.startsWith(normName(n))));
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
function extractMentions(text) {
|
|
1178
|
+
const prose = text.replace(/```[\s\S]*?(```|$)/g, ' ').replace(/`[^`\n]*`/g, ' ');
|
|
1179
|
+
const re = /(^|\s)@(?:\[([^\]\n]+)\]|([^\s\[\]]+))/g;
|
|
1180
|
+
const found = [];
|
|
1181
|
+
let m;
|
|
1182
|
+
while ((m = re.exec(prose))) {
|
|
1183
|
+
const bracketed = m[2] !== undefined;
|
|
1184
|
+
const name = bracketed ? m[2] : m[3].replace(/[.,!?;:)]+$/, '');
|
|
1185
|
+
found.push({ raw: m[0].trim(), name, bracketed });
|
|
1186
|
+
}
|
|
1187
|
+
return found;
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
// The routing line is the contract with the channel instructions in
|
|
1191
|
+
// src/channel/server.ts (ROUTING) — change both together.
|
|
1192
|
+
function buildRouting(text) {
|
|
1193
|
+
const mentions = extractMentions(text);
|
|
1194
|
+
if (!mentions.length) return { ok: true, content: text };
|
|
1195
|
+
const unknown = [];
|
|
1196
|
+
const lines = [];
|
|
1197
|
+
const seen = new Set();
|
|
1198
|
+
for (const m of mentions) {
|
|
1199
|
+
const s = resolveMention(m.name);
|
|
1200
|
+
if (!s) {
|
|
1201
|
+
if (m.bracketed || looksLikeSessionMention(m.name)) unknown.push(m.raw);
|
|
1202
|
+
continue;
|
|
1203
|
+
}
|
|
1204
|
+
if (seen.has(s.id)) continue;
|
|
1205
|
+
seen.add(s.id);
|
|
1206
|
+
lines.push(`[claude-alarm] @${m.name} = SendMessage to "${s.peerName}"`);
|
|
1207
|
+
}
|
|
1208
|
+
if (unknown.length) return { ok: false, unknown };
|
|
1209
|
+
if (!lines.length) return { ok: true, content: text };
|
|
1210
|
+
return { ok: true, content: `${text}\n\n${lines.join('\n')}` };
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
function showMentionError(msg) {
|
|
1214
|
+
const el = $('#mentionError');
|
|
1215
|
+
el.textContent = msg || '';
|
|
1216
|
+
el.classList.toggle('show', !!msg);
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
let mentionState = { open: false, items: [], index: 0, start: -1 };
|
|
1220
|
+
|
|
1221
|
+
function currentMentionQuery(input) {
|
|
1222
|
+
const upto = input.value.slice(0, input.selectionStart);
|
|
1223
|
+
const m = upto.match(/(^|\s)@(\[[^\]\n]*|[^\s\[\]]*)$/);
|
|
1224
|
+
if (!m) return null;
|
|
1225
|
+
return { start: upto.length - m[2].length - 1, query: m[2].replace(/^\[/, '') };
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
function closeMentionPopup() {
|
|
1229
|
+
mentionState = { open: false, items: [], index: 0, start: -1 };
|
|
1230
|
+
$('#mentionPopup').classList.remove('open');
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
function renderMentionPopup() {
|
|
1234
|
+
$('#mentionPopup').innerHTML = mentionState.items.map((s, i) =>
|
|
1235
|
+
`<div class="mention-item${i === mentionState.index ? ' active' : ''}" data-idx="${i}">` +
|
|
1236
|
+
`<span>${esc(sessionDisplayName(s))}</span><span class="mention-folder">${esc(folderName(s))}</span></div>`
|
|
1237
|
+
).join('');
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
function updateMentionPopup() {
|
|
1241
|
+
const q = currentMentionQuery($('#msgInput'));
|
|
1242
|
+
if (!q) return closeMentionPopup();
|
|
1243
|
+
const key = normName(q.query);
|
|
1244
|
+
const items = mentionTargets().filter(s =>
|
|
1245
|
+
normName(sessionDisplayName(s)).includes(key) || normName(folderName(s)).includes(key));
|
|
1246
|
+
if (!items.length) return closeMentionPopup();
|
|
1247
|
+
const index = mentionState.open ? Math.min(mentionState.index, items.length - 1) : 0;
|
|
1248
|
+
mentionState = { open: true, items, index, start: q.start };
|
|
1249
|
+
renderMentionPopup();
|
|
1250
|
+
$('#mentionPopup').classList.add('open');
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
function applyMention(s) {
|
|
1254
|
+
const input = $('#msgInput');
|
|
1255
|
+
const label = mentionLabel(s);
|
|
1256
|
+
const token = /\s/.test(label) ? `@[${label}] ` : `@${label} `;
|
|
1257
|
+
const before = input.value.slice(0, mentionState.start);
|
|
1258
|
+
const after = input.value.slice(input.selectionStart);
|
|
1259
|
+
input.value = before + token + after;
|
|
1260
|
+
const pos = before.length + token.length;
|
|
1261
|
+
input.setSelectionRange(pos, pos);
|
|
1262
|
+
closeMentionPopup();
|
|
1263
|
+
input.focus();
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
$('#mentionPopup').addEventListener('mousedown', (e) => {
|
|
1267
|
+
const item = e.target.closest('.mention-item');
|
|
1268
|
+
if (!item) return;
|
|
1269
|
+
e.preventDefault();
|
|
1270
|
+
applyMention(mentionState.items[Number(item.dataset.idx)]);
|
|
1271
|
+
});
|
|
1272
|
+
|
|
1098
1273
|
// --- Theme ---
|
|
1099
1274
|
function initTheme() {
|
|
1100
1275
|
const saved = localStorage.getItem('claude-alarm-theme') || 'dark';
|
|
@@ -1115,25 +1290,28 @@
|
|
|
1115
1290
|
initTheme();
|
|
1116
1291
|
|
|
1117
1292
|
// --- Token handling ---
|
|
1118
|
-
function
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
return urlToken;
|
|
1293
|
+
async function checkAuth() {
|
|
1294
|
+
try {
|
|
1295
|
+
return (await fetch('/api/status')).status !== 401;
|
|
1296
|
+
} catch {
|
|
1297
|
+
return true;
|
|
1124
1298
|
}
|
|
1125
|
-
return sessionStorage.getItem('claude-alarm-token');
|
|
1126
1299
|
}
|
|
1127
1300
|
|
|
1128
1301
|
function showTokenForm() { $('#tokenOverlay').classList.remove('hidden'); }
|
|
1129
1302
|
function hideTokenForm() { $('#tokenOverlay').classList.add('hidden'); }
|
|
1130
1303
|
|
|
1131
|
-
$('#tokenSubmit').addEventListener('click', () => {
|
|
1304
|
+
$('#tokenSubmit').addEventListener('click', async () => {
|
|
1132
1305
|
const token = $('#tokenInput').value.trim();
|
|
1133
1306
|
if (!token) return;
|
|
1134
|
-
|
|
1135
|
-
|
|
1307
|
+
const res = await fetch('/api/login', {
|
|
1308
|
+
method: 'POST',
|
|
1309
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1310
|
+
body: JSON.stringify({ token }),
|
|
1311
|
+
}).catch(() => null);
|
|
1312
|
+
if (!res || !res.ok) { $('#tokenError').style.display = 'block'; return; }
|
|
1136
1313
|
$('#tokenError').style.display = 'none';
|
|
1314
|
+
$('#tokenInput').value = '';
|
|
1137
1315
|
hideTokenForm();
|
|
1138
1316
|
connect();
|
|
1139
1317
|
});
|
|
@@ -1144,25 +1322,23 @@
|
|
|
1144
1322
|
// --- WebSocket ---
|
|
1145
1323
|
function connect() {
|
|
1146
1324
|
const proto = location.protocol === 'https:' ? 'wss' : 'ws';
|
|
1147
|
-
const
|
|
1148
|
-
|
|
1325
|
+
const ws = new WebSocket(`${proto}://${location.host}/ws/dashboard`);
|
|
1326
|
+
let opened = false;
|
|
1149
1327
|
|
|
1150
1328
|
ws.onopen = () => {
|
|
1329
|
+
opened = true;
|
|
1151
1330
|
state.ws = ws;
|
|
1152
1331
|
hideTokenForm();
|
|
1153
1332
|
$('#connDot').classList.add('connected');
|
|
1154
1333
|
$('#connLabel').textContent = 'Connected';
|
|
1155
1334
|
};
|
|
1156
|
-
ws.onclose = () => {
|
|
1335
|
+
ws.onclose = async () => {
|
|
1157
1336
|
state.ws = null;
|
|
1158
1337
|
$('#connDot').classList.remove('connected');
|
|
1159
1338
|
$('#connLabel').textContent = 'Disconnected';
|
|
1339
|
+
if (!opened && !(await checkAuth())) { showTokenForm(); return; }
|
|
1160
1340
|
setTimeout(connect, 3000);
|
|
1161
1341
|
};
|
|
1162
|
-
ws.onerror = () => {
|
|
1163
|
-
if (!state.token) { showTokenForm(); } else { $('#tokenError').style.display = 'block'; showTokenForm(); }
|
|
1164
|
-
ws.close();
|
|
1165
|
-
};
|
|
1166
1342
|
ws.onmessage = (e) => { try { handleMessage(JSON.parse(e.data)); } catch {} };
|
|
1167
1343
|
}
|
|
1168
1344
|
|
|
@@ -1307,12 +1483,18 @@
|
|
|
1307
1483
|
input.select();
|
|
1308
1484
|
|
|
1309
1485
|
let done = false;
|
|
1310
|
-
const commit = (save) => {
|
|
1486
|
+
const commit = (save, fromBlur) => {
|
|
1311
1487
|
if (done) return;
|
|
1488
|
+
const val = input.value.trim();
|
|
1489
|
+
const baseName = s.displayName || s.name;
|
|
1490
|
+
const err = save && val && val !== baseName ? renameError(val, id) : null;
|
|
1491
|
+
if (err && !fromBlur) {
|
|
1492
|
+
input.classList.add('invalid');
|
|
1493
|
+
input.title = err;
|
|
1494
|
+
return;
|
|
1495
|
+
}
|
|
1312
1496
|
done = true;
|
|
1313
|
-
if (save) {
|
|
1314
|
-
const val = input.value.trim();
|
|
1315
|
-
const baseName = s.displayName || s.name;
|
|
1497
|
+
if (save && !err) {
|
|
1316
1498
|
if (!val || val === baseName) delete state.sessionMeta.names[id];
|
|
1317
1499
|
else state.sessionMeta.names[id] = val;
|
|
1318
1500
|
saveSessionMeta();
|
|
@@ -1321,10 +1503,11 @@
|
|
|
1321
1503
|
};
|
|
1322
1504
|
input.addEventListener('keydown', (e) => {
|
|
1323
1505
|
e.stopPropagation();
|
|
1324
|
-
if (e.key === 'Enter') { e.preventDefault(); commit(true); }
|
|
1325
|
-
else if (e.key === 'Escape') { e.preventDefault(); commit(false); }
|
|
1506
|
+
if (e.key === 'Enter') { e.preventDefault(); commit(true, false); }
|
|
1507
|
+
else if (e.key === 'Escape') { e.preventDefault(); commit(false, false); }
|
|
1326
1508
|
});
|
|
1327
|
-
input.addEventListener('
|
|
1509
|
+
input.addEventListener('input', () => { input.classList.remove('invalid'); input.title = ''; });
|
|
1510
|
+
input.addEventListener('blur', () => commit(true, true));
|
|
1328
1511
|
input.addEventListener('click', (e) => e.stopPropagation());
|
|
1329
1512
|
}
|
|
1330
1513
|
|
|
@@ -1577,6 +1760,13 @@
|
|
|
1577
1760
|
if (!content && !hasImage) return;
|
|
1578
1761
|
if (!state.selectedSession || !state.ws) return;
|
|
1579
1762
|
|
|
1763
|
+
const routing = buildRouting(content);
|
|
1764
|
+
if (!routing.ok) {
|
|
1765
|
+
showMentionError(`Unknown session: ${routing.unknown.join(', ')} — pick from the @ list, or put a space right after the name.`);
|
|
1766
|
+
return;
|
|
1767
|
+
}
|
|
1768
|
+
showMentionError(null);
|
|
1769
|
+
|
|
1580
1770
|
if (hasImage) {
|
|
1581
1771
|
// Send image + text together
|
|
1582
1772
|
state.ws.send(JSON.stringify({
|
|
@@ -1585,11 +1775,11 @@
|
|
|
1585
1775
|
imageData: state.pendingImage.data,
|
|
1586
1776
|
mimeType: state.pendingImage.mimeType,
|
|
1587
1777
|
originalName: state.pendingImage.name,
|
|
1588
|
-
content: content || '',
|
|
1778
|
+
content: routing.content || '',
|
|
1589
1779
|
}));
|
|
1590
1780
|
} else if (content) {
|
|
1591
1781
|
// Text only
|
|
1592
|
-
state.ws.send(JSON.stringify({ type: 'message_to_session', sessionId: state.selectedSession, content }));
|
|
1782
|
+
state.ws.send(JSON.stringify({ type: 'message_to_session', sessionId: state.selectedSession, content: routing.content }));
|
|
1593
1783
|
}
|
|
1594
1784
|
|
|
1595
1785
|
// Add to local messages
|
|
@@ -1604,6 +1794,7 @@
|
|
|
1604
1794
|
saveMessages();
|
|
1605
1795
|
|
|
1606
1796
|
input.value = '';
|
|
1797
|
+
closeMentionPopup();
|
|
1607
1798
|
input.style.height = 'auto';
|
|
1608
1799
|
clearPendingImage();
|
|
1609
1800
|
state.waitingReply[state.selectedSession] = true;
|
|
@@ -1612,11 +1803,24 @@
|
|
|
1612
1803
|
|
|
1613
1804
|
$('#sendBtn').addEventListener('click', sendMessage);
|
|
1614
1805
|
$('#msgInput').addEventListener('keydown', (e) => {
|
|
1806
|
+
if (mentionState.open) {
|
|
1807
|
+
// Enter while an IME (e.g. Korean) is composing only commits the syllable.
|
|
1808
|
+
if (e.isComposing) return;
|
|
1809
|
+
const n = mentionState.items.length;
|
|
1810
|
+
if (e.key === 'ArrowDown') { e.preventDefault(); mentionState.index = (mentionState.index + 1) % n; renderMentionPopup(); return; }
|
|
1811
|
+
if (e.key === 'ArrowUp') { e.preventDefault(); mentionState.index = (mentionState.index - 1 + n) % n; renderMentionPopup(); return; }
|
|
1812
|
+
if (e.key === 'Enter' || e.key === 'Tab') { e.preventDefault(); applyMention(mentionState.items[mentionState.index]); return; }
|
|
1813
|
+
if (e.key === 'Escape') { e.preventDefault(); closeMentionPopup(); return; }
|
|
1814
|
+
}
|
|
1615
1815
|
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); }
|
|
1616
1816
|
});
|
|
1817
|
+
$('#msgInput').addEventListener('blur', closeMentionPopup);
|
|
1818
|
+
$('#msgInput').addEventListener('click', updateMentionPopup);
|
|
1617
1819
|
$('#msgInput').addEventListener('input', function() {
|
|
1820
|
+
showMentionError(null);
|
|
1618
1821
|
this.style.height = 'auto';
|
|
1619
1822
|
this.style.height = Math.min(this.scrollHeight, 120) + 'px';
|
|
1823
|
+
updateMentionPopup();
|
|
1620
1824
|
});
|
|
1621
1825
|
|
|
1622
1826
|
function relativeTime(ts) {
|
|
@@ -1924,16 +2128,14 @@
|
|
|
1924
2128
|
$('#settingsToggle').addEventListener('click', async () => {
|
|
1925
2129
|
// Load webhook data
|
|
1926
2130
|
try {
|
|
1927
|
-
const
|
|
1928
|
-
const res = await fetch(`/api/webhooks${tokenQuery}`);
|
|
2131
|
+
const res = await fetch(`/api/webhooks`);
|
|
1929
2132
|
const data = await res.json();
|
|
1930
2133
|
webhookData = data.webhooks || [];
|
|
1931
2134
|
} catch { webhookData = []; }
|
|
1932
2135
|
renderWebhooks();
|
|
1933
2136
|
// Load telegram data
|
|
1934
2137
|
try {
|
|
1935
|
-
const
|
|
1936
|
-
const res = await fetch(`/api/telegram${tokenQuery}`);
|
|
2138
|
+
const res = await fetch(`/api/telegram`);
|
|
1937
2139
|
const data = await res.json();
|
|
1938
2140
|
const tg = data.telegram || {};
|
|
1939
2141
|
if (!$('#tgBotToken').value || $('#tgBotToken').value.includes('...')) {
|
|
@@ -1968,8 +2170,7 @@
|
|
|
1968
2170
|
$('#webhookSave').addEventListener('click', async () => {
|
|
1969
2171
|
const valid = webhookData.filter(w => w.url.trim());
|
|
1970
2172
|
try {
|
|
1971
|
-
|
|
1972
|
-
await fetch(`/api/webhooks${tokenQuery}`, {
|
|
2173
|
+
await fetch(`/api/webhooks`, {
|
|
1973
2174
|
method: 'POST',
|
|
1974
2175
|
headers: { 'Content-Type': 'application/json' },
|
|
1975
2176
|
body: JSON.stringify({ webhooks: valid }),
|
|
@@ -2044,8 +2245,7 @@
|
|
|
2044
2245
|
$('#tgDetectStatus').textContent = 'Detecting...';
|
|
2045
2246
|
$('#tgChatList').innerHTML = '';
|
|
2046
2247
|
try {
|
|
2047
|
-
const
|
|
2048
|
-
const res = await fetch(`/api/telegram/detect${tokenQuery}`, {
|
|
2248
|
+
const res = await fetch(`/api/telegram/detect`, {
|
|
2049
2249
|
method: 'POST',
|
|
2050
2250
|
headers: { 'Content-Type': 'application/json' },
|
|
2051
2251
|
body: JSON.stringify({ botToken }),
|
|
@@ -2082,8 +2282,7 @@
|
|
|
2082
2282
|
const chatId = $('#tgChatId').value.trim();
|
|
2083
2283
|
if (!botToken || !chatId) { showTgStatus('Bot Token and Chat ID are required.', false); return; }
|
|
2084
2284
|
try {
|
|
2085
|
-
const
|
|
2086
|
-
const res = await fetch(`/api/telegram/test${tokenQuery}`, {
|
|
2285
|
+
const res = await fetch(`/api/telegram/test`, {
|
|
2087
2286
|
method: 'POST',
|
|
2088
2287
|
headers: { 'Content-Type': 'application/json' },
|
|
2089
2288
|
body: JSON.stringify({ botToken, chatId }),
|
|
@@ -2102,8 +2301,7 @@
|
|
|
2102
2301
|
if (!botToken || !chatId) { showTgStatus('Bot Token and Chat ID are required.', false); return; }
|
|
2103
2302
|
if (botToken.includes('...')) { showTgStatus('Bot Token is masked. Please enter the full token.', false); return; }
|
|
2104
2303
|
try {
|
|
2105
|
-
|
|
2106
|
-
await fetch(`/api/telegram${tokenQuery}`, {
|
|
2304
|
+
await fetch(`/api/telegram`, {
|
|
2107
2305
|
method: 'POST',
|
|
2108
2306
|
headers: { 'Content-Type': 'application/json' },
|
|
2109
2307
|
body: JSON.stringify({ telegram: { botToken, chatId, enabled } }),
|
|
@@ -2131,8 +2329,7 @@
|
|
|
2131
2329
|
document.querySelector('.sessions-panel').classList.add('mobile-active');
|
|
2132
2330
|
}
|
|
2133
2331
|
|
|
2134
|
-
|
|
2135
|
-
connect();
|
|
2332
|
+
checkAuth().then((ok) => (ok ? connect() : showTokenForm()));
|
|
2136
2333
|
})();
|
|
2137
2334
|
</script>
|
|
2138
2335
|
</body>
|