dsh-tg-bridge 1.2.7 → 1.2.9
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/bridge.mjs +41 -2
- package/package.json +5 -3
- package/queue-notice.mjs +10 -0
package/bridge.mjs
CHANGED
|
@@ -22,6 +22,7 @@ import dns from 'node:dns';
|
|
|
22
22
|
import net from 'node:net';
|
|
23
23
|
dns.setDefaultResultOrder('ipv4first');
|
|
24
24
|
net.setDefaultAutoSelectFamily(false);
|
|
25
|
+
import { noticeIsObsolete } from './queue-notice.mjs';
|
|
25
26
|
import { handleControl, CONTROL_COMMANDS } from './control.mjs';
|
|
26
27
|
import { renderPrices } from './prices.mjs';
|
|
27
28
|
import { t, resolveLang } from './i18n.mjs';
|
|
@@ -607,6 +608,7 @@ export function toTelegramHtml(input) {
|
|
|
607
608
|
async function send(chatId, text, threadId = 0, replyTo = 0) {
|
|
608
609
|
const html = config.plainText === true ? '' : toTelegramHtml(text);
|
|
609
610
|
const parts = html ? chunk(html, 3800) : chunk(toPlainText(text) || '(пусто)');
|
|
611
|
+
let lastId = 0;
|
|
610
612
|
for (const part of parts) {
|
|
611
613
|
const body = { chat_id: chatId, text: part, disable_web_page_preview: true };
|
|
612
614
|
if (html) body.parse_mode = 'HTML';
|
|
@@ -627,7 +629,9 @@ async function send(chatId, text, threadId = 0, replyTo = 0) {
|
|
|
627
629
|
res = await tg('sendMessage', body);
|
|
628
630
|
}
|
|
629
631
|
log(`sent chat=${chatId}${threadId ? ` topic=${threadId}` : ''} msg=${res.message_id} len=${part.length}${html ? ' html' : ''}`);
|
|
632
|
+
lastId = res.message_id;
|
|
630
633
|
}
|
|
634
|
+
return lastId;
|
|
631
635
|
}
|
|
632
636
|
|
|
633
637
|
/** Remember which topics already got their seed message, so it happens once. */
|
|
@@ -1398,12 +1402,33 @@ function maybeExitForRestart() {
|
|
|
1398
1402
|
setTimeout(() => process.exit(75), 300).unref?.();
|
|
1399
1403
|
}
|
|
1400
1404
|
|
|
1405
|
+
/**
|
|
1406
|
+
* Queue notices, one per topic. A notice exists only while the person is
|
|
1407
|
+
* waiting: the moment the queued turn starts it is deleted, so a long session
|
|
1408
|
+
* does not leave a column of "идёт уже 7 мин" messages behind (it did).
|
|
1409
|
+
*/
|
|
1410
|
+
const busyNotices = new Map();
|
|
1411
|
+
|
|
1412
|
+
async function clearBusyNotice(key) {
|
|
1413
|
+
const notice = busyNotices.get(key);
|
|
1414
|
+
if (!notice) return;
|
|
1415
|
+
busyNotices.delete(key);
|
|
1416
|
+
try {
|
|
1417
|
+
await tg('deleteMessage', { chat_id: notice.chatId, message_id: notice.messageId });
|
|
1418
|
+
} catch (error) {
|
|
1419
|
+
log(`busy notice delete failed: ${error.message}`);
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1401
1423
|
function enqueue(key, task, onBusy) {
|
|
1402
1424
|
const prev = queues.get(key) || Promise.resolve();
|
|
1403
1425
|
const queued = queues.has(key);
|
|
1404
1426
|
const run = prev
|
|
1405
1427
|
.then(() => acquire())
|
|
1406
|
-
.then(() => {
|
|
1428
|
+
.then(async () => {
|
|
1429
|
+
// The person was told "your message is queued"; that notice has done its
|
|
1430
|
+
// job the moment the turn starts and must not linger in the chat.
|
|
1431
|
+
await clearBusyNotice(key);
|
|
1407
1432
|
// Remember when the turn actually started: the queue notice has to say
|
|
1408
1433
|
// "идёт уже 24 мин", not just "занято" — silence is what reads as a hang.
|
|
1409
1434
|
turnStartedAt.set(key, Date.now());
|
|
@@ -1412,6 +1437,7 @@ function enqueue(key, task, onBusy) {
|
|
|
1412
1437
|
.catch((error) => log(`turn queue error ${key}: ${error.message}`))
|
|
1413
1438
|
.finally(() => {
|
|
1414
1439
|
turnStartedAt.delete(key);
|
|
1440
|
+
clearBusyNotice(key).catch(() => {});
|
|
1415
1441
|
release();
|
|
1416
1442
|
if (queues.get(key) === run) queues.delete(key);
|
|
1417
1443
|
// A deferred restart waits for exactly this moment: the answer is out and
|
|
@@ -1726,18 +1752,31 @@ async function main() {
|
|
|
1726
1752
|
// handled inline and must stay silent about it.
|
|
1727
1753
|
msg.text || msg.caption
|
|
1728
1754
|
? async () => {
|
|
1755
|
+
// Which turn was running when this message was queued: if that
|
|
1756
|
+
// value changes, the queued turn has started and the notice is
|
|
1757
|
+
// already obsolete.
|
|
1758
|
+
const turnWhenQueued = turnStartedAt.get(queueKey);
|
|
1729
1759
|
await new Promise((resolve) => setTimeout(resolve, Number(config.busyNoticeMs ?? 45000)));
|
|
1730
1760
|
if (!queues.has(queueKey)) return; // already finished
|
|
1761
|
+
// One notice per topic: a second queued message must not add a
|
|
1762
|
+
// second copy of the same line.
|
|
1763
|
+
if (busyNotices.has(queueKey)) return;
|
|
1731
1764
|
const started = turnStartedAt.get(queueKey);
|
|
1732
1765
|
const mins = started ? Math.max(1, Math.round((Date.now() - started) / 60000)) : 0;
|
|
1733
1766
|
const notice = mins > 0
|
|
1734
1767
|
? t(lang, 'msg.busy.since', { min: mins })
|
|
1735
1768
|
: t(lang, 'msg.busy.now');
|
|
1736
|
-
await send(
|
|
1769
|
+
const messageId = await send(
|
|
1737
1770
|
msg.chat.id,
|
|
1738
1771
|
`⏳ ${notice}`,
|
|
1739
1772
|
thread,
|
|
1740
1773
|
);
|
|
1774
|
+
if (messageId) busyNotices.set(queueKey, { chatId: msg.chat.id, messageId });
|
|
1775
|
+
// The queued turn may have started while the notice was in flight;
|
|
1776
|
+
// in that case nothing else will delete it until the turn ends.
|
|
1777
|
+
if (noticeIsObsolete(turnWhenQueued, turnStartedAt.get(queueKey), queues.has(queueKey))) {
|
|
1778
|
+
await clearBusyNotice(queueKey);
|
|
1779
|
+
}
|
|
1741
1780
|
}
|
|
1742
1781
|
: undefined,
|
|
1743
1782
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-tg-bridge",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.9",
|
|
4
4
|
"description": "Telegram bridge for DeepSeek Harness: text, voice, photos and project-per-topic sessions without opening the desktop app",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "bridge.mjs",
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
"setup": "node setup.mjs",
|
|
13
13
|
"check": "node setup.mjs --check",
|
|
14
14
|
"menu": "node bot-menu.mjs",
|
|
15
|
-
"doctor": "node bridge-kick.mjs"
|
|
15
|
+
"doctor": "node bridge-kick.mjs",
|
|
16
|
+
"test": "node --test"
|
|
16
17
|
},
|
|
17
18
|
"engines": {
|
|
18
19
|
"node": ">=20"
|
|
@@ -43,6 +44,7 @@
|
|
|
43
44
|
"README.md",
|
|
44
45
|
"LICENSE",
|
|
45
46
|
"config.example.json",
|
|
46
|
-
"patches/"
|
|
47
|
+
"patches/",
|
|
48
|
+
"queue-notice.mjs"
|
|
47
49
|
]
|
|
48
50
|
}
|
package/queue-notice.mjs
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// When is a "your message is queued" notice already obsolete?
|
|
2
|
+
//
|
|
3
|
+
// It was written for a specific running turn (`turnWhenQueued`). It must go as
|
|
4
|
+
// soon as that turn is no longer the one in front: either the queue drained, or
|
|
5
|
+
// the queued turn started (its start time differs). Kept in its own module so
|
|
6
|
+
// the rule can be tested without starting the bridge or talking to Telegram.
|
|
7
|
+
|
|
8
|
+
export function noticeIsObsolete(turnWhenQueued, turnNow, stillQueued) {
|
|
9
|
+
return !stillQueued || turnNow !== turnWhenQueued;
|
|
10
|
+
}
|