@will-17173/telegram-cli 0.2.0 → 0.3.0
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 +65 -9
- package/README.zh-CN.md +64 -9
- package/dist/cli/app.js +1 -1
- package/dist/commands/account.js +48 -4
- package/dist/commands/account.js.map +1 -1
- package/dist/commands/group-write.js +88 -0
- package/dist/commands/group-write.js.map +1 -0
- package/dist/commands/group.js +20 -6
- package/dist/commands/group.js.map +1 -1
- package/dist/commands/telegram.js +90 -71
- package/dist/commands/telegram.js.map +1 -1
- package/dist/group-commands/catalog.js +69 -0
- package/dist/group-commands/catalog.js.map +1 -0
- package/dist/group-commands/executor.js +93 -0
- package/dist/group-commands/executor.js.map +1 -0
- package/dist/group-commands/parser.js +268 -0
- package/dist/group-commands/parser.js.map +1 -0
- package/dist/group-commands/tokenize.js +51 -0
- package/dist/group-commands/tokenize.js.map +1 -0
- package/dist/group-commands/types.js +2 -0
- package/dist/group-commands/types.js.map +1 -0
- package/dist/index.js +0 -0
- package/dist/listen-commands/catalog.js +61 -0
- package/dist/listen-commands/catalog.js.map +1 -0
- package/dist/listen-commands/dispatch.js +46 -0
- package/dist/listen-commands/dispatch.js.map +1 -0
- package/dist/listen-commands/match.js +113 -0
- package/dist/listen-commands/match.js.map +1 -0
- package/dist/presenters/group.js +24 -0
- package/dist/presenters/group.js.map +1 -1
- package/dist/presenters/human.js +22 -0
- package/dist/presenters/human.js.map +1 -1
- package/dist/presenters/ink/display-width.js +45 -7
- package/dist/presenters/ink/display-width.js.map +1 -1
- package/dist/presenters/ink/group-command-confirm.js +13 -0
- package/dist/presenters/ink/group-command-confirm.js.map +1 -0
- package/dist/presenters/ink/group-command-result.js +21 -0
- package/dist/presenters/ink/group-command-result.js.map +1 -0
- package/dist/presenters/ink/listen-command-menu.js +38 -0
- package/dist/presenters/ink/listen-command-menu.js.map +1 -0
- package/dist/presenters/ink/listen-scroll.js +5 -1
- package/dist/presenters/ink/listen-scroll.js.map +1 -1
- package/dist/presenters/ink/listen.js +445 -45
- package/dist/presenters/ink/listen.js.map +1 -1
- package/dist/presenters/ink/use-group-command.js +125 -0
- package/dist/presenters/ink/use-group-command.js.map +1 -0
- package/dist/presenters/listen-message.js +23 -6
- package/dist/presenters/listen-message.js.map +1 -1
- package/dist/presenters/logical-message.js +78 -0
- package/dist/presenters/logical-message.js.map +1 -0
- package/dist/services/group-service.js.map +1 -1
- package/dist/services/group-write-service.js +165 -0
- package/dist/services/group-write-service.js.map +1 -0
- package/dist/services/listen-album-aggregator.js +9 -29
- package/dist/services/listen-album-aggregator.js.map +1 -1
- package/dist/services/listen-attachment.js +23 -6
- package/dist/services/listen-attachment.js.map +1 -1
- package/dist/services/listen-composer-command.js +102 -0
- package/dist/services/listen-composer-command.js.map +1 -0
- package/dist/services/listen-reply-resolver.js +126 -0
- package/dist/services/listen-reply-resolver.js.map +1 -0
- package/dist/services/query-service.js +59 -3
- package/dist/services/query-service.js.map +1 -1
- package/dist/services/reply-context.js +29 -0
- package/dist/services/reply-context.js.map +1 -0
- package/dist/storage/message-db.js +241 -5
- package/dist/storage/message-db.js.map +1 -1
- package/dist/telegram/fake-group-management.js +117 -0
- package/dist/telegram/fake-group-management.js.map +1 -1
- package/dist/telegram/group-types.js +40 -0
- package/dist/telegram/group-types.js.map +1 -1
- package/dist/telegram/group-write-types.js +2 -0
- package/dist/telegram/group-write-types.js.map +1 -0
- package/dist/telegram/mtcute-client.js +1 -1
- package/dist/telegram/mtcute-client.js.map +1 -1
- package/dist/telegram/mtcute-group-helpers.js +56 -0
- package/dist/telegram/mtcute-group-helpers.js.map +1 -0
- package/dist/telegram/mtcute-group-invites.js +82 -0
- package/dist/telegram/mtcute-group-invites.js.map +1 -0
- package/dist/telegram/mtcute-group-management.js +60 -31
- package/dist/telegram/mtcute-group-management.js.map +1 -1
- package/dist/telegram/mtcute-group-members.js +118 -0
- package/dist/telegram/mtcute-group-members.js.map +1 -0
- package/dist/telegram/mtcute-group-settings.js +146 -0
- package/dist/telegram/mtcute-group-settings.js.map +1 -0
- package/dist/telegram/mtcute-group-topics.js +63 -0
- package/dist/telegram/mtcute-group-topics.js.map +1 -0
- package/dist/telegram/raw-message.js +46 -0
- package/dist/telegram/raw-message.js.map +1 -0
- package/package.json +20 -10
|
@@ -10,10 +10,23 @@ import { ListenAlbumAggregator } from '../../services/listen-album-aggregator.js
|
|
|
10
10
|
import { AutoDownloadCoordinator } from '../../services/auto-download-coordinator.js';
|
|
11
11
|
import { attachmentDownloadTarget, attachmentFileName, discoverListenAttachments, listenAttachmentKey } from '../../services/listen-attachment.js';
|
|
12
12
|
import { buildListenMessage } from '../listen-message.js';
|
|
13
|
-
import { applyMessageArrival,
|
|
13
|
+
import { applyMessageArrival, takeListenViewport } from './listen-scroll.js';
|
|
14
14
|
import { decodeImagePreview } from './image-preview.js';
|
|
15
15
|
import { ListenScrollbar, calculateScrollbar, listenContentWidth, useTransientScrollbar } from './listen-scrollbar.js';
|
|
16
|
-
import {
|
|
16
|
+
import { DISABLE_MOUSE_REPORTING, isMouseInput } from './mouse-scroll.js';
|
|
17
|
+
import { createListenReplyResolver } from '../../services/listen-reply-resolver.js';
|
|
18
|
+
import { formatReplyContext } from '../../services/reply-context.js';
|
|
19
|
+
import { executeListenReply, parseListenComposerInput } from '../../services/listen-composer-command.js';
|
|
20
|
+
import { ListenCommandMenu, listenCommandMenuAvailability, moveListenCommandSelectionEnabled } from './listen-command-menu.js';
|
|
21
|
+
import { completeListenCommand, visibleListenCommandMatches } from '../../listen-commands/match.js';
|
|
22
|
+
import { parseSelectedListenCommand } from '../../listen-commands/dispatch.js';
|
|
23
|
+
import { GroupCommandResult } from './group-command-result.js';
|
|
24
|
+
import { useGroupCommand } from './use-group-command.js';
|
|
25
|
+
import { executeGroupCommand } from '../../group-commands/executor.js';
|
|
26
|
+
import { GroupWriteService } from '../../services/group-write-service.js';
|
|
27
|
+
import { ADMIN_RIGHT_KEYS } from '../../services/group-write-service.js';
|
|
28
|
+
import { GroupCommandConfirm } from './group-command-confirm.js';
|
|
29
|
+
import { truncateCell } from './display-width.js';
|
|
17
30
|
const MESSAGE_SEPARATOR = '────────────────────────────────────────────';
|
|
18
31
|
/** Maximum number of grouped messages retained by a long-running interactive listener. */
|
|
19
32
|
export const LISTEN_HISTORY_LIMIT = 500;
|
|
@@ -268,6 +281,81 @@ export function ListenAttachmentWithPreview({ label, downloadable = true, select
|
|
|
268
281
|
? _jsx(ListenAttachmentLine, { label: label, selected: selected, state: state })
|
|
269
282
|
: _jsxs(Text, { wrap: "truncate-end", children: [" ", label] }), previewCells == null ? null : _jsx(ListenImagePreview, { rows: previewCells })] }));
|
|
270
283
|
}
|
|
284
|
+
export function ListenMessageBody({ message }) {
|
|
285
|
+
return (_jsxs(Box, { flexDirection: "column", children: [message.replyContext == null ? null : _jsx(Text, { wrap: "truncate-end", children: formatReplyContext(message.replyContext) }), message.content == null ? null : _jsx(Text, { wrap: "truncate-end", children: message.content }), message.mediaSummary == null ? null : _jsx(Text, { wrap: "truncate-end", children: message.mediaSummary }), message.media.map((item, mediaIndex) => (_jsx(ListenAttachmentWithPreview, { label: item.label, downloadable: item.downloadable, selected: false, state: { status: 'idle' }, previewCells: item.previewCells }, attachmentDownloadKeyAt(message.media, mediaIndex))))] }));
|
|
286
|
+
}
|
|
287
|
+
export function createInteractiveListenGroupQueue(options) {
|
|
288
|
+
const pending = [];
|
|
289
|
+
const schedule = options.schedule ?? ((run) => setImmediate(run));
|
|
290
|
+
let scheduled = null;
|
|
291
|
+
let resolveScheduled = null;
|
|
292
|
+
let closing = false;
|
|
293
|
+
let closed = false;
|
|
294
|
+
let closePromise = null;
|
|
295
|
+
const closeResolver = async () => {
|
|
296
|
+
if (closed)
|
|
297
|
+
return;
|
|
298
|
+
closed = true;
|
|
299
|
+
if (options.resolver.closeAsync != null)
|
|
300
|
+
await options.resolver.closeAsync();
|
|
301
|
+
else
|
|
302
|
+
options.resolver.close();
|
|
303
|
+
};
|
|
304
|
+
const drain = async () => {
|
|
305
|
+
try {
|
|
306
|
+
while (pending.length > 0) {
|
|
307
|
+
const messages = pending.shift();
|
|
308
|
+
try {
|
|
309
|
+
const replyContext = options.resolver.resolveAsync == null
|
|
310
|
+
? options.resolver.resolve(messages)
|
|
311
|
+
: await options.resolver.resolveAsync(messages);
|
|
312
|
+
options.resolver.remember(messages);
|
|
313
|
+
const first = messages[0];
|
|
314
|
+
if (first != null && options.isActive()) {
|
|
315
|
+
options.onGroup({ key: `${first.chat_id}:${first.msg_id}`, messages, replyContext });
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
catch (error) {
|
|
319
|
+
options.onError(error);
|
|
320
|
+
pending.length = 0;
|
|
321
|
+
break;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
finally {
|
|
326
|
+
resolveScheduled?.();
|
|
327
|
+
resolveScheduled = null;
|
|
328
|
+
scheduled = null;
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
return {
|
|
332
|
+
enqueue(messages) {
|
|
333
|
+
if (closing)
|
|
334
|
+
return;
|
|
335
|
+
pending.push(messages);
|
|
336
|
+
if (scheduled != null)
|
|
337
|
+
return;
|
|
338
|
+
scheduled = new Promise((resolve) => { resolveScheduled = resolve; });
|
|
339
|
+
schedule(() => { void drain(); });
|
|
340
|
+
},
|
|
341
|
+
close() {
|
|
342
|
+
closePromise ??= (async () => {
|
|
343
|
+
closing = true;
|
|
344
|
+
const drainCompletion = scheduled;
|
|
345
|
+
if (drainCompletion != null)
|
|
346
|
+
await drainCompletion;
|
|
347
|
+
await closeResolver();
|
|
348
|
+
})();
|
|
349
|
+
return closePromise;
|
|
350
|
+
},
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
export function createInteractiveListenRuntime(dbPath, factory, options) {
|
|
354
|
+
return createInteractiveListenGroupQueue({
|
|
355
|
+
...options,
|
|
356
|
+
resolver: factory(dbPath, LISTEN_HISTORY_LIMIT),
|
|
357
|
+
});
|
|
358
|
+
}
|
|
271
359
|
export function ListenStatus({ status, unseenCount }) {
|
|
272
360
|
return _jsxs(Text, { dimColor: true, children: [status, unseenCount > 0 ? ` · ↓ ${unseenCount} new messages` : ''] });
|
|
273
361
|
}
|
|
@@ -277,6 +365,9 @@ export function formatInteractiveListenSender(message) {
|
|
|
277
365
|
: `${message.sender} (${message.senderId})`;
|
|
278
366
|
return message.chatName == null ? sender : `${message.chatName} | ${sender}`;
|
|
279
367
|
}
|
|
368
|
+
export function formatInteractiveListenHeader(message) {
|
|
369
|
+
return `[${message.time}] #${message.msgId} ${formatInteractiveListenSender(message)}`;
|
|
370
|
+
}
|
|
280
371
|
export function ListenStatusArea({ status, unseenCount, autoDownload, }) {
|
|
281
372
|
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(ListenStatus, { status: status, unseenCount: unseenCount }), autoDownload ? _jsx(Text, { dimColor: true, children: "Auto-download enabled" }) : null] }));
|
|
282
373
|
}
|
|
@@ -285,7 +376,8 @@ export function calculateListenMessagePaneHeight(terminalHeight, hasNote, autoDo
|
|
|
285
376
|
return Math.max(2, terminalHeight - reservedLines);
|
|
286
377
|
}
|
|
287
378
|
export async function runInteractiveListen(write, run) {
|
|
288
|
-
|
|
379
|
+
write(DISABLE_MOUSE_REPORTING);
|
|
380
|
+
return run();
|
|
289
381
|
}
|
|
290
382
|
export async function renderInteractiveListen(options) {
|
|
291
383
|
await runInteractiveListen(process.stdout.write.bind(process.stdout), async () => {
|
|
@@ -293,7 +385,7 @@ export async function renderInteractiveListen(options) {
|
|
|
293
385
|
await app.waitUntilExit();
|
|
294
386
|
});
|
|
295
387
|
}
|
|
296
|
-
function InteractiveListen({ chats, persist, retrySeconds, sendTo, showMedia, autoDownload, showChatName, createClient, stopSignal, onRequestStop, }) {
|
|
388
|
+
export function InteractiveListen({ dbPath, chats, persist, retrySeconds, sendTo, showMedia, autoDownload, showChatName, createClient, stopSignal, onRequestStop, createReplyResolver, }) {
|
|
297
389
|
const { exit } = useApp();
|
|
298
390
|
const { stdout } = useStdout();
|
|
299
391
|
const terminalMetrics = useTerminalMetrics(stdout);
|
|
@@ -307,23 +399,79 @@ function InteractiveListen({ chats, persist, retrySeconds, sendTo, showMedia, au
|
|
|
307
399
|
const [downloadStates, setDownloadStates] = useState({});
|
|
308
400
|
const [scrollState, setScrollState] = useState({ offset: 0, unseenCount: 0 });
|
|
309
401
|
const [sendTargetLabel, setSendTargetLabel] = useState(sendTo == null ? '' : buildSendTargetLabel(sendTo));
|
|
402
|
+
const [knownGroup, setKnownGroup] = useState(undefined);
|
|
403
|
+
const clientRef = useRef(null);
|
|
404
|
+
const replyExecutionLockRef = useRef(false);
|
|
405
|
+
const inputGenerationRef = useRef(0);
|
|
406
|
+
const commandSelectionRef = useRef(0);
|
|
407
|
+
const knownGroupRef = useRef(undefined);
|
|
408
|
+
const groupLookupGenerationRef = useRef(0);
|
|
409
|
+
useEffect(() => {
|
|
410
|
+
groupLookupGenerationRef.current++;
|
|
411
|
+
knownGroupRef.current = undefined;
|
|
412
|
+
setKnownGroup(undefined);
|
|
413
|
+
}, [sendTo]);
|
|
414
|
+
const groupCommand = useGroupCommand(useCallback(async (request, options) => {
|
|
415
|
+
const client = clientRef.current;
|
|
416
|
+
if (client == null)
|
|
417
|
+
return { ok: false, error: { code: 'connection_not_ready', message: 'Telegram connection is not ready.' } };
|
|
418
|
+
if (sendTo == null)
|
|
419
|
+
return { ok: false, error: { code: 'ambiguous_chat', message: 'Select exactly one target chat with --send-to.' } };
|
|
420
|
+
let knownGroup = knownGroupRef.current;
|
|
421
|
+
if (knownGroup == null) {
|
|
422
|
+
const lookup = ++groupLookupGenerationRef.current;
|
|
423
|
+
knownGroup = await client.groups.getGroup(sendTo);
|
|
424
|
+
if (lookup === groupLookupGenerationRef.current && clientRef.current === client) {
|
|
425
|
+
knownGroupRef.current = knownGroup;
|
|
426
|
+
setKnownGroup(knownGroup);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
return executeGroupCommand(request, {
|
|
430
|
+
chat: sendTo,
|
|
431
|
+
groups: new GroupWriteService(client.groups),
|
|
432
|
+
confirmed: options?.confirmed ?? false,
|
|
433
|
+
confirmationTitle: options?.confirmationTitle,
|
|
434
|
+
knownGroup,
|
|
435
|
+
connectionReady: true,
|
|
436
|
+
targetAvailable: true,
|
|
437
|
+
targetCount: 1,
|
|
438
|
+
invalidateGroup: async () => {
|
|
439
|
+
knownGroupRef.current = undefined;
|
|
440
|
+
setKnownGroup(undefined);
|
|
441
|
+
if (request.key === 'chat delete' || request.key === 'chat leave')
|
|
442
|
+
return;
|
|
443
|
+
const lookup = ++groupLookupGenerationRef.current;
|
|
444
|
+
try {
|
|
445
|
+
const refreshed = await client.groups.getGroup(sendTo);
|
|
446
|
+
if (lookup === groupLookupGenerationRef.current && clientRef.current === client) {
|
|
447
|
+
knownGroupRef.current = refreshed;
|
|
448
|
+
setKnownGroup(refreshed);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
catch (error) {
|
|
452
|
+
if (lookup === groupLookupGenerationRef.current && clientRef.current === client)
|
|
453
|
+
setNote(`Group updated; refresh failed: ${messageFromError(error)}`);
|
|
454
|
+
}
|
|
455
|
+
},
|
|
456
|
+
});
|
|
457
|
+
}, [sendTo]));
|
|
310
458
|
const terminalWidth = terminalMetrics.columns;
|
|
311
459
|
const terminalHeight = terminalMetrics.rows;
|
|
312
460
|
const previewColorDepth = interactiveListenPreviewColorDepth(terminalMetrics.colorDepth);
|
|
313
461
|
const contentWidth = listenContentWidth(terminalWidth);
|
|
314
462
|
const previewWidth = Math.max(1, Math.min(24, contentWidth - 2));
|
|
463
|
+
const renderContextRef = useRef({ showMedia, previewWidth, colorDepth: previewColorDepth, showChatName });
|
|
464
|
+
renderContextRef.current = { showMedia, previewWidth, colorDepth: previewColorDepth, showChatName };
|
|
315
465
|
const messageViewCacheRef = useRef(null);
|
|
316
466
|
if (messageViewCacheRef.current == null)
|
|
317
467
|
messageViewCacheRef.current = new ListenMessageViewCache();
|
|
318
|
-
const messages = useMemo(() => messageViewCacheRef.current.build(messageGroups,
|
|
319
|
-
showMedia,
|
|
320
|
-
previewWidth,
|
|
321
|
-
colorDepth: previewColorDepth,
|
|
322
|
-
showChatName,
|
|
323
|
-
}), [messageGroups, showMedia, previewWidth, previewColorDepth, showChatName]);
|
|
468
|
+
const messages = useMemo(() => messageViewCacheRef.current.build(messageGroups, renderContextRef.current), [messageGroups, showMedia, previewWidth, previewColorDepth, showChatName]);
|
|
324
469
|
const messagePaneHeight = calculateListenMessagePaneHeight(terminalHeight, note.length > 0, autoDownload);
|
|
325
470
|
const visibleMessages = takeListenViewport(messages, messagePaneHeight, scrollState.offset);
|
|
326
|
-
const
|
|
471
|
+
const closeGroupCommand = () => {
|
|
472
|
+
commandSelectionRef.current = 0;
|
|
473
|
+
groupCommand.close();
|
|
474
|
+
};
|
|
327
475
|
const albumAggregatorRef = useRef(null);
|
|
328
476
|
const autoDownloaderRef = useRef(null);
|
|
329
477
|
const pendingAttachmentKeysRef = useRef(new Set());
|
|
@@ -335,18 +483,13 @@ function InteractiveListen({ chats, persist, retrySeconds, sendTo, showMedia, au
|
|
|
335
483
|
const seenOrderRef = useRef([]);
|
|
336
484
|
const downloadableAttachments = collectDownloadableAttachments(visibleMessages);
|
|
337
485
|
const selectedAttachment = downloadableAttachments[selectedAttachmentIndex] ?? downloadableAttachments[0];
|
|
338
|
-
const { visible: scrollbarVisible
|
|
486
|
+
const { visible: scrollbarVisible } = useTransientScrollbar();
|
|
339
487
|
const scrollbarGeometry = calculateScrollbar({
|
|
340
488
|
height: terminalHeight,
|
|
341
489
|
total: messages.length,
|
|
342
490
|
visible: visibleMessages.length,
|
|
343
491
|
offset: scrollState.offset,
|
|
344
492
|
});
|
|
345
|
-
const handleMouseScroll = useCallback((direction) => {
|
|
346
|
-
showScrollbar();
|
|
347
|
-
setScrollState((current) => applyScroll(current, direction, Math.max(0, messages.length - 1)));
|
|
348
|
-
}, [messages.length, showScrollbar]);
|
|
349
|
-
useMouseScroll(handleMouseScroll);
|
|
350
493
|
useEffect(() => {
|
|
351
494
|
const maxOffset = Math.max(0, messages.length - 1);
|
|
352
495
|
setScrollState((current) => {
|
|
@@ -373,6 +516,139 @@ function InteractiveListen({ chats, persist, retrySeconds, sendTo, showMedia, au
|
|
|
373
516
|
stopListening();
|
|
374
517
|
return;
|
|
375
518
|
}
|
|
519
|
+
const modal = groupCommand.state;
|
|
520
|
+
if (modal.kind === 'confirm') {
|
|
521
|
+
if (key.escape) {
|
|
522
|
+
closeGroupCommand();
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
if (key.upArrow || key.downArrow) {
|
|
526
|
+
groupCommand.setState({ ...modal, selectedIndex: modal.selectedIndex === 0 ? 1 : 0 });
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
if (key.return) {
|
|
530
|
+
if (modal.selectedIndex === 0)
|
|
531
|
+
void groupCommand.runConfirmed(modal.request);
|
|
532
|
+
else
|
|
533
|
+
closeGroupCommand();
|
|
534
|
+
}
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
if (modal.kind === 'confirm-title') {
|
|
538
|
+
if (key.escape) {
|
|
539
|
+
if (modal.stage === 'title')
|
|
540
|
+
groupCommand.setState({ ...modal, stage: 'confirm', confirmText: '', mismatch: false });
|
|
541
|
+
else
|
|
542
|
+
closeGroupCommand();
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
if (modal.stage === 'confirm') {
|
|
546
|
+
if (key.upArrow || key.downArrow)
|
|
547
|
+
groupCommand.setState({ ...modal, selectedIndex: modal.selectedIndex === 0 ? 1 : 0 });
|
|
548
|
+
else if (key.return)
|
|
549
|
+
modal.selectedIndex === 0 ? groupCommand.setState({ ...modal, stage: 'title' }) : closeGroupCommand();
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
if (key.return) {
|
|
553
|
+
const client = clientRef.current;
|
|
554
|
+
if (client == null || sendTo == null) {
|
|
555
|
+
groupCommand.setState({ kind: 'error', message: 'Telegram connection is not ready.' });
|
|
556
|
+
}
|
|
557
|
+
else {
|
|
558
|
+
const submittedTitle = modal.confirmText;
|
|
559
|
+
const lookup = ++groupLookupGenerationRef.current;
|
|
560
|
+
void client.groups.getGroup(sendTo).then((fresh) => {
|
|
561
|
+
if (lookup !== groupLookupGenerationRef.current || clientRef.current !== client)
|
|
562
|
+
return;
|
|
563
|
+
knownGroupRef.current = fresh;
|
|
564
|
+
setKnownGroup(fresh);
|
|
565
|
+
if (fresh.title === submittedTitle)
|
|
566
|
+
void groupCommand.runConfirmed(modal.request, submittedTitle);
|
|
567
|
+
else if ('confirmation' in modal.pending)
|
|
568
|
+
groupCommand.setState({
|
|
569
|
+
...modal,
|
|
570
|
+
pending: { ...modal.pending, confirmation: { ...modal.pending.confirmation, title: fresh.title, target: fresh.title } },
|
|
571
|
+
mismatch: true,
|
|
572
|
+
});
|
|
573
|
+
}).catch((error) => {
|
|
574
|
+
if (lookup === groupLookupGenerationRef.current && clientRef.current === client)
|
|
575
|
+
groupCommand.setState({ kind: 'error', message: messageFromError(error) });
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
else if (key.backspace || key.delete)
|
|
580
|
+
groupCommand.setState({ ...modal, confirmText: modal.confirmText.slice(0, -1), mismatch: false });
|
|
581
|
+
else if (!key.ctrl && !key.meta && inputText)
|
|
582
|
+
groupCommand.setState({ ...modal, confirmText: modal.confirmText + inputText, mismatch: false });
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
if (modal.kind === 'select-permissions') {
|
|
586
|
+
if (key.escape) {
|
|
587
|
+
closeGroupCommand();
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
if (key.upArrow || key.downArrow) {
|
|
591
|
+
const delta = key.upArrow ? -1 : 1;
|
|
592
|
+
groupCommand.setState({ ...modal, selectedIndex: (modal.selectedIndex + delta + ADMIN_RIGHT_KEYS.length) % ADMIN_RIGHT_KEYS.length });
|
|
593
|
+
}
|
|
594
|
+
else if (inputText === ' ') {
|
|
595
|
+
const right = ADMIN_RIGHT_KEYS[modal.selectedIndex];
|
|
596
|
+
groupCommand.setState({ ...modal, selected: modal.selected.includes(right) ? modal.selected.filter(item => item !== right) : [...modal.selected, right], warning: undefined });
|
|
597
|
+
}
|
|
598
|
+
else if (key.return) {
|
|
599
|
+
if (modal.selected.length === 0)
|
|
600
|
+
groupCommand.setState({ ...modal, warning: 'Select at least one permission.' });
|
|
601
|
+
else {
|
|
602
|
+
const request = Object.freeze({ ...modal.request, values: Object.freeze({ ...modal.request.values, permissions: Object.freeze([...modal.selected]) }) });
|
|
603
|
+
void (async () => {
|
|
604
|
+
const result = await groupCommand.submit(`${modal.originalInput} ${modal.selected.join(',')}`, 0);
|
|
605
|
+
if (result.kind === 'pending' && 'confirmation' in result.pending)
|
|
606
|
+
groupCommand.setState({ kind: 'confirm', pending: result.pending, request, originalInput: modal.originalInput, selectedIndex: 1 });
|
|
607
|
+
})();
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
if (groupCommand.state.kind === 'result') {
|
|
613
|
+
if (key.escape)
|
|
614
|
+
closeGroupCommand();
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
if (key.escape && groupCommand.state.kind === 'error') {
|
|
618
|
+
closeGroupCommand();
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
const slashMode = input.trimStart().startsWith('/');
|
|
622
|
+
if (slashMode && key.escape) {
|
|
623
|
+
if (replyExecutionLockRef.current) {
|
|
624
|
+
inputGenerationRef.current++;
|
|
625
|
+
setSending(false);
|
|
626
|
+
}
|
|
627
|
+
closeGroupCommand();
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
if (slashMode && (key.upArrow || key.downArrow)) {
|
|
631
|
+
const count = visibleListenCommandMatches(input).length;
|
|
632
|
+
const disabled = listenCommandMenuAvailability(input, knownGroup).map(Boolean);
|
|
633
|
+
const selectedIndex = moveListenCommandSelectionEnabled(commandSelectionRef.current, key.upArrow ? -1 : 1, disabled.slice(0, count));
|
|
634
|
+
commandSelectionRef.current = selectedIndex;
|
|
635
|
+
groupCommand.setState({ kind: 'menu', selectedIndex });
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
if (slashMode && key.tab) {
|
|
639
|
+
const selected = commandSelectionRef.current;
|
|
640
|
+
const failure = listenCommandMenuAvailability(input, knownGroup)[selected];
|
|
641
|
+
if (failure && 'error' in failure) {
|
|
642
|
+
setNote(failure.error.message);
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
inputGenerationRef.current++;
|
|
646
|
+
setInput(completeListenCommand(input, selected));
|
|
647
|
+
commandSelectionRef.current = 0;
|
|
648
|
+
groupCommand.setState({ kind: 'menu', selectedIndex: 0 });
|
|
649
|
+
setFocus('input');
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
376
652
|
if (key.tab) {
|
|
377
653
|
if (focus === 'attachments') {
|
|
378
654
|
setFocus('input');
|
|
@@ -406,13 +682,86 @@ function InteractiveListen({ chats, persist, retrySeconds, sendTo, showMedia, au
|
|
|
406
682
|
}
|
|
407
683
|
return;
|
|
408
684
|
}
|
|
409
|
-
if (sending)
|
|
685
|
+
if (sending || groupCommand.state.kind === 'executing')
|
|
410
686
|
return;
|
|
411
687
|
if (key.return) {
|
|
688
|
+
if (slashMode) {
|
|
689
|
+
const selected = commandSelectionRef.current;
|
|
690
|
+
const failure = listenCommandMenuAvailability(input, knownGroup)[selected];
|
|
691
|
+
if (failure && 'error' in failure) {
|
|
692
|
+
setNote(failure.error.message);
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
const match = visibleListenCommandMatches(input)[selected];
|
|
696
|
+
if (!match) {
|
|
697
|
+
setNote('No matching command.');
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
const parsed = parseSelectedListenCommand(input, match);
|
|
701
|
+
if (parsed.kind === 'complete') {
|
|
702
|
+
inputGenerationRef.current++;
|
|
703
|
+
setInput(parsed.input);
|
|
704
|
+
commandSelectionRef.current = 0;
|
|
705
|
+
groupCommand.setState({ kind: 'menu', selectedIndex: 0 });
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
if (parsed.kind === 'error') {
|
|
709
|
+
setNote(parsed.usage == null ? parsed.message : `${parsed.message} · usage: /${parsed.usage}`);
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
if (parsed.kind === 'reply') {
|
|
713
|
+
if (replyExecutionLockRef.current)
|
|
714
|
+
return;
|
|
715
|
+
if (sendTo == null) {
|
|
716
|
+
setNote('set --send-to before replying');
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
const client = clientRef.current;
|
|
720
|
+
if (client == null) {
|
|
721
|
+
setNote('connection is not ready');
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
replyExecutionLockRef.current = true;
|
|
725
|
+
const ownedGeneration = inputGenerationRef.current;
|
|
726
|
+
const originalInput = input;
|
|
727
|
+
setSending(true);
|
|
728
|
+
setNote('sending...');
|
|
729
|
+
void executeListenReply(client, sendTo, parsed.command).then((sentMessages) => {
|
|
730
|
+
if (ownedGeneration !== inputGenerationRef.current)
|
|
731
|
+
return;
|
|
732
|
+
for (const sentMessage of sentMessages)
|
|
733
|
+
acceptListenMessage(sentMessage, seenRef.current, seenOrderRef.current, (message) => {
|
|
734
|
+
registerPendingAttachmentKeys(pendingAttachmentKeysRef.current, message, showMedia);
|
|
735
|
+
autoDownloaderRef.current?.enqueue(message);
|
|
736
|
+
albumAggregatorRef.current?.add(message);
|
|
737
|
+
});
|
|
738
|
+
setInput('');
|
|
739
|
+
setNote(`replied to #${parsed.command.reply}`);
|
|
740
|
+
}).catch((error) => {
|
|
741
|
+
if (ownedGeneration === inputGenerationRef.current) {
|
|
742
|
+
setInput(originalInput);
|
|
743
|
+
setNote(`send failed: ${messageFromError(error)}`);
|
|
744
|
+
}
|
|
745
|
+
}).finally(() => {
|
|
746
|
+
replyExecutionLockRef.current = false;
|
|
747
|
+
if (ownedGeneration === inputGenerationRef.current)
|
|
748
|
+
setSending(false);
|
|
749
|
+
});
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
void groupCommand.submitParsed(parsed.request, input, selected).then((outcome) => {
|
|
753
|
+
if (!outcome.applied)
|
|
754
|
+
return;
|
|
755
|
+
if (outcome.kind === 'result' && outcome.result.ok)
|
|
756
|
+
setInput('');
|
|
757
|
+
});
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
412
760
|
void sendMessage(input);
|
|
413
761
|
return;
|
|
414
762
|
}
|
|
415
763
|
if (key.backspace || key.delete) {
|
|
764
|
+
inputGenerationRef.current++;
|
|
416
765
|
setInput((current) => current.slice(0, -1));
|
|
417
766
|
return;
|
|
418
767
|
}
|
|
@@ -420,7 +769,16 @@ function InteractiveListen({ chats, persist, retrySeconds, sendTo, showMedia, au
|
|
|
420
769
|
return;
|
|
421
770
|
}
|
|
422
771
|
if (!key.ctrl && !key.meta && inputText.length > 0) {
|
|
423
|
-
|
|
772
|
+
inputGenerationRef.current++;
|
|
773
|
+
setInput((current) => {
|
|
774
|
+
const next = current + inputText;
|
|
775
|
+
if (next.trimStart().startsWith('/')) {
|
|
776
|
+
setFocus('input');
|
|
777
|
+
commandSelectionRef.current = 0;
|
|
778
|
+
groupCommand.setState({ kind: 'menu', selectedIndex: 0 });
|
|
779
|
+
}
|
|
780
|
+
return next;
|
|
781
|
+
});
|
|
424
782
|
}
|
|
425
783
|
});
|
|
426
784
|
useEffect(() => {
|
|
@@ -434,12 +792,25 @@ function InteractiveListen({ chats, persist, retrySeconds, sendTo, showMedia, au
|
|
|
434
792
|
stopListening();
|
|
435
793
|
};
|
|
436
794
|
stopSignal.addEventListener('abort', stopFromSignal);
|
|
795
|
+
const groupQueue = createInteractiveListenRuntime(dbPath, createReplyResolver ?? createListenReplyResolver, {
|
|
796
|
+
isActive,
|
|
797
|
+
onGroup: (group) => {
|
|
798
|
+
setScrollState((current) => applyMessageArrival(current));
|
|
799
|
+
setMessageGroups((current) => [...current, group].slice(-LISTEN_HISTORY_LIMIT));
|
|
800
|
+
},
|
|
801
|
+
onError: (error) => {
|
|
802
|
+
if (!isActive())
|
|
803
|
+
return;
|
|
804
|
+
setStatus(`listen failed: ${messageFromError(error)}`);
|
|
805
|
+
onRequestStop();
|
|
806
|
+
exit();
|
|
807
|
+
},
|
|
808
|
+
});
|
|
437
809
|
const albumAggregator = new ListenAlbumAggregator({
|
|
438
810
|
emit: (group) => {
|
|
439
811
|
if (!isActive())
|
|
440
812
|
return;
|
|
441
|
-
|
|
442
|
-
setMessageGroups((current) => pruneListenMessageGroups([...current, group]).groups);
|
|
813
|
+
groupQueue.enqueue(group);
|
|
443
814
|
},
|
|
444
815
|
});
|
|
445
816
|
albumAggregatorRef.current = albumAggregator;
|
|
@@ -472,8 +843,18 @@ function InteractiveListen({ chats, persist, retrySeconds, sendTo, showMedia, au
|
|
|
472
843
|
onClient: (client) => {
|
|
473
844
|
if (!isActive())
|
|
474
845
|
return;
|
|
846
|
+
groupLookupGenerationRef.current++;
|
|
847
|
+
knownGroupRef.current = undefined;
|
|
848
|
+
setKnownGroup(undefined);
|
|
475
849
|
clientRef.current = client;
|
|
476
850
|
if (client != null && sendTo != null) {
|
|
851
|
+
const lookup = ++groupLookupGenerationRef.current;
|
|
852
|
+
void client.groups.getGroup(sendTo).then((group) => {
|
|
853
|
+
if (isActive() && lookup === groupLookupGenerationRef.current && clientRef.current === client) {
|
|
854
|
+
knownGroupRef.current = group;
|
|
855
|
+
setKnownGroup(group);
|
|
856
|
+
}
|
|
857
|
+
}).catch(() => undefined);
|
|
477
858
|
void resolveSendTargetLabel(client, sendTo).then((label) => {
|
|
478
859
|
if (isActive() && label != null)
|
|
479
860
|
setSendTargetLabel(label);
|
|
@@ -528,9 +909,13 @@ function InteractiveListen({ chats, persist, retrySeconds, sendTo, showMedia, au
|
|
|
528
909
|
}
|
|
529
910
|
});
|
|
530
911
|
return () => {
|
|
531
|
-
|
|
912
|
+
groupLookupGenerationRef.current += 1;
|
|
913
|
+
knownGroupRef.current = undefined;
|
|
532
914
|
stopSignal.removeEventListener('abort', stopFromSignal);
|
|
915
|
+
albumAggregator.flush();
|
|
916
|
+
generation.dispose();
|
|
533
917
|
albumAggregator.dispose();
|
|
918
|
+
void groupQueue.close();
|
|
534
919
|
if (albumAggregatorRef.current === albumAggregator)
|
|
535
920
|
albumAggregatorRef.current = null;
|
|
536
921
|
void clientRef.current?.close().catch(() => undefined);
|
|
@@ -540,11 +925,16 @@ function InteractiveListen({ chats, persist, retrySeconds, sendTo, showMedia, au
|
|
|
540
925
|
if (autoDownloaderRef.current === autoDownloader)
|
|
541
926
|
autoDownloaderRef.current = null;
|
|
542
927
|
};
|
|
543
|
-
}, [autoDownload, chats, createClient, persist, retrySeconds, sendTo, showMedia, exit, stopSignal, onRequestStop]);
|
|
928
|
+
}, [autoDownload, chats, createClient, createReplyResolver, dbPath, persist, retrySeconds, sendTo, showMedia, exit, stopSignal, onRequestStop]);
|
|
544
929
|
const sendMessage = async (text) => {
|
|
545
930
|
const trimmed = text.trim();
|
|
546
931
|
if (!trimmed)
|
|
547
932
|
return;
|
|
933
|
+
const command = parseListenComposerInput(trimmed);
|
|
934
|
+
if (command.kind === 'error') {
|
|
935
|
+
setNote(command.error);
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
548
938
|
if (sendTo == null) {
|
|
549
939
|
setNote('set --send-to before sending');
|
|
550
940
|
return;
|
|
@@ -559,20 +949,23 @@ function InteractiveListen({ chats, persist, retrySeconds, sendTo, showMedia, au
|
|
|
559
949
|
setSending(true);
|
|
560
950
|
setNote('sending...');
|
|
561
951
|
try {
|
|
562
|
-
const
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
952
|
+
const sentMessages = command.kind === 'reply'
|
|
953
|
+
? await executeListenReply(client, sendTo, command)
|
|
954
|
+
: [await client.sendMessage({
|
|
955
|
+
chat: sendTo,
|
|
956
|
+
message: command.content,
|
|
957
|
+
linkPreview: true,
|
|
958
|
+
})].flatMap(({ sent_message: message }) => message == null ? [] : [message]);
|
|
959
|
+
if (isCurrent()) {
|
|
960
|
+
for (const sentMessage of sentMessages)
|
|
961
|
+
acceptListenMessage(sentMessage, seenRef.current, seenOrderRef.current, (message) => {
|
|
962
|
+
registerPendingAttachmentKeys(pendingAttachmentKeysRef.current, message, showMedia);
|
|
963
|
+
autoDownloaderRef.current?.enqueue(message);
|
|
964
|
+
albumAggregatorRef.current?.add(message);
|
|
965
|
+
});
|
|
573
966
|
}
|
|
574
967
|
if (isCurrent())
|
|
575
|
-
setNote('sent');
|
|
968
|
+
setNote(command.kind === 'reply' ? `replied to #${command.reply}` : 'sent');
|
|
576
969
|
}
|
|
577
970
|
catch (error) {
|
|
578
971
|
if (isCurrent())
|
|
@@ -628,10 +1021,13 @@ function InteractiveListen({ chats, persist, retrySeconds, sendTo, showMedia, au
|
|
|
628
1021
|
autoDownloaderRef.current?.stop();
|
|
629
1022
|
clientRef.current?.close().catch(() => undefined);
|
|
630
1023
|
};
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
1024
|
+
const permissionState = groupCommand.state.kind === 'select-permissions' ? groupCommand.state : null;
|
|
1025
|
+
return (_jsxs(Box, { flexDirection: "row", width: terminalWidth, height: terminalHeight, overflow: "hidden", children: [_jsxs(Box, { flexDirection: "column", width: contentWidth, height: terminalHeight, overflow: "hidden", children: [_jsx(ListenStatusArea, { status: status, unseenCount: scrollState.unseenCount, autoDownload: autoDownload }), note ? _jsx(Text, { dimColor: true, children: note }) : null, groupCommand.state.kind === 'result' ? _jsx(Box, { flexGrow: 1, flexDirection: "column", children: _jsx(GroupCommandResult, { state: groupCommand.state, width: contentWidth }) }) : _jsxs(Box, { flexGrow: 1, flexDirection: "column", children: [_jsx(GroupCommandResult, { state: groupCommand.state, width: contentWidth }), groupCommand.state.kind === 'confirm' && 'confirmation' in groupCommand.state.pending ? _jsx(GroupCommandConfirm, { confirmation: groupCommand.state.pending.confirmation, selectedIndex: groupCommand.state.selectedIndex, width: contentWidth }) : null, groupCommand.state.kind === 'confirm-title' && 'confirmation' in groupCommand.state.pending ? groupCommand.state.stage === 'confirm'
|
|
1026
|
+
? _jsx(GroupCommandConfirm, { confirmation: groupCommand.state.pending.confirmation, selectedIndex: groupCommand.state.selectedIndex, width: contentWidth })
|
|
1027
|
+
: _jsxs(Box, { flexDirection: "column", width: contentWidth, children: [_jsx(Text, { color: "yellow", children: truncateCell('Type the exact title to permanently delete this chat:', contentWidth) }), _jsx(Text, { children: truncateCell(groupCommand.state.pending.confirmation.title ?? '', contentWidth) }), _jsx(Text, { color: "#8ecbff", children: truncateCell(`› ${groupCommand.state.confirmText}`, contentWidth) }), groupCommand.state.mismatch ? _jsx(Text, { color: "red", children: truncateCell('Title does not match exactly.', contentWidth) }) : null, _jsx(Text, { dimColor: true, children: truncateCell('Enter verify · Esc back', contentWidth) })] }) : null, permissionState ? _jsxs(Box, { flexDirection: "column", width: contentWidth, children: [_jsx(Text, { color: "#8ecbff", children: truncateCell('Administrator permissions', contentWidth) }), ADMIN_RIGHT_KEYS.map((right, index) => _jsx(Text, { color: index === permissionState.selectedIndex ? '#8ecbff' : undefined, children: truncateCell(`${index === permissionState.selectedIndex ? '› ' : ' '}[${permissionState.selected.includes(right) ? 'x' : ' '}] ${right}`, contentWidth) }, right)), permissionState.warning ? _jsx(Text, { color: "yellow", children: truncateCell(permissionState.warning, contentWidth) }) : null, _jsx(Text, { dimColor: true, children: truncateCell('↑/↓ select · Space toggle · Enter continue · Esc cancel', contentWidth) })] }) : null, _jsxs(Box, { marginTop: 1, flexDirection: "column", flexGrow: 1, overflow: "hidden", children: [messages.length === 0 ? _jsx(Text, { dimColor: true, children: "Waiting for new messages..." }) : null, visibleMessages.map((message) => (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { dimColor: true, wrap: "truncate-end", children: formatInteractiveListenHeader(message) }), message.replyContext == null ? null : _jsx(Text, { wrap: "truncate-end", children: formatReplyContext(message.replyContext) }), message.content == null ? null : _jsx(Text, { wrap: "truncate-end", children: message.content }), message.mediaSummary == null ? null : _jsx(Text, { wrap: "truncate-end", children: message.mediaSummary }), message.media.map((item, mediaIndex) => {
|
|
1028
|
+
const attachmentKey = attachmentDownloadKeyAt(message.media, mediaIndex);
|
|
1029
|
+
return (_jsx(ListenAttachmentWithPreview, { label: item.label, downloadable: item.downloadable, selected: focus === 'attachments' && selectedAttachment?.key === attachmentKey, state: downloadStates[attachmentKey] ?? { status: 'idle' }, previewCells: item.previewCells }, attachmentKey));
|
|
1030
|
+
}), _jsx(Text, { dimColor: true, children: MESSAGE_SEPARATOR })] }, message.key)))] })] }), groupCommand.state.kind !== 'result' ? _jsxs(Box, { flexDirection: "column", children: [sendTo == null ? _jsx(Text, { dimColor: true, children: "Set --send-to <chat> (or pass one chat to listen) before sending messages." }) : null, _jsxs(Box, { marginTop: 1, flexDirection: "column", flexShrink: 0, children: [input.trimStart().startsWith('/') && groupCommand.state.kind === 'menu' ? _jsx(ListenCommandMenu, { input: input, selectedIndex: groupCommand.state.selectedIndex, width: contentWidth, knownGroup: knownGroup }) : null, _jsx(ListenComposer, { input: input, sendTargetLabel: sendTo == null ? '(not selected)' : sendTargetLabel, terminalWidth: contentWidth, sending: sending, hint: focus === 'attachments' ? '↑/↓ select · Enter download · Tab input' : 'Enter send · /reply <id> ... · Tab attachments · Ctrl+C exit' })] })] }) : null] }), _jsx(ListenScrollbar, { height: terminalHeight, visible: scrollbarVisible, geometry: scrollbarGeometry })] }));
|
|
635
1031
|
}
|
|
636
1032
|
export class ListenMessageViewCache {
|
|
637
1033
|
entries = new Map();
|
|
@@ -641,13 +1037,16 @@ export class ListenMessageViewCache {
|
|
|
641
1037
|
build(groups, context) {
|
|
642
1038
|
const previewWidth = normalizedPreviewWidth(context.previewWidth);
|
|
643
1039
|
const nextEntries = new Map();
|
|
644
|
-
const messages = groups.map((
|
|
1040
|
+
const messages = groups.map((input) => {
|
|
1041
|
+
const group = Array.isArray(input) ? input : input.messages;
|
|
1042
|
+
const replyContext = Array.isArray(input) ? context.replyContext : input.replyContext;
|
|
645
1043
|
const first = group[0];
|
|
646
1044
|
if (first == null)
|
|
647
1045
|
throw new Error('Cannot render an empty listen message group');
|
|
648
|
-
const key = `${first.chat_id}:${first.msg_id}
|
|
1046
|
+
const key = Array.isArray(input) ? `${first.chat_id}:${first.msg_id}` : input.key;
|
|
649
1047
|
const cached = this.entries.get(key);
|
|
650
1048
|
if (cached?.group === group
|
|
1049
|
+
&& cached.replyContext === replyContext
|
|
651
1050
|
&& cached.showMedia === context.showMedia
|
|
652
1051
|
&& cached.previewWidth === previewWidth
|
|
653
1052
|
&& cached.colorDepth === context.colorDepth
|
|
@@ -655,9 +1054,10 @@ export class ListenMessageViewCache {
|
|
|
655
1054
|
nextEntries.set(key, cached);
|
|
656
1055
|
return cached.message;
|
|
657
1056
|
}
|
|
658
|
-
const message = toListenMessage(group, { ...context, previewWidth });
|
|
1057
|
+
const message = toListenMessage(group, { ...context, previewWidth, replyContext });
|
|
659
1058
|
nextEntries.set(key, {
|
|
660
1059
|
group,
|
|
1060
|
+
replyContext,
|
|
661
1061
|
showMedia: context.showMedia,
|
|
662
1062
|
previewWidth,
|
|
663
1063
|
colorDepth: context.colorDepth,
|
|
@@ -677,7 +1077,7 @@ export function pruneListenMessageGroups(groups, limit = LISTEN_HISTORY_LIMIT) {
|
|
|
677
1077
|
return {
|
|
678
1078
|
groups: groups.slice(removeCount),
|
|
679
1079
|
removedKeys: removed.flatMap((group) => {
|
|
680
|
-
const first = group[0];
|
|
1080
|
+
const first = Array.isArray(group) ? group[0] : group.messages[0];
|
|
681
1081
|
return first == null ? [] : [`${first.chat_id}:${first.msg_id}`];
|
|
682
1082
|
}),
|
|
683
1083
|
};
|
|
@@ -689,8 +1089,8 @@ export function toListenMessage(messages, context) {
|
|
|
689
1089
|
const renderContext = typeof context === 'boolean'
|
|
690
1090
|
? { showMedia: context, previewWidth: 1, colorDepth: 1, showChatName: false }
|
|
691
1091
|
: context;
|
|
692
|
-
const { showMedia, previewWidth, colorDepth, showChatName = false } = renderContext;
|
|
693
|
-
const formatted = buildListenMessage(messages, { showMedia, showChatName });
|
|
1092
|
+
const { showMedia, previewWidth, colorDepth, showChatName = false, replyContext } = renderContext;
|
|
1093
|
+
const formatted = buildListenMessage(messages, { showMedia, showChatName, replyContext });
|
|
694
1094
|
const decodePreview = renderContext.decodePreview ?? decodeImagePreview;
|
|
695
1095
|
const media = formatted.media.map((attachment) => {
|
|
696
1096
|
if (attachment.previewJpegBase64 == null || colorDepth < 24)
|