fix: split telegram captions in bot delivery (#1063) (thanks @mukhtharcm)
This commit is contained in:
parent
6e10f1c1f2
commit
1cdd3f29da
@ -30,6 +30,7 @@
|
|||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
- Sub-agents: route announce delivery through the correct channel account IDs. (#1061, #1058) — thanks @adam91holt.
|
- Sub-agents: route announce delivery through the correct channel account IDs. (#1061, #1058) — thanks @adam91holt.
|
||||||
|
- Telegram: split long media captions into follow-up text messages in bot delivery. (#1063) — thanks @mukhtharcm.
|
||||||
- Repo: fix oxlint config filename and move ignore pattern into config. (#1064) — thanks @connorshea.
|
- Repo: fix oxlint config filename and move ignore pattern into config. (#1064) — thanks @connorshea.
|
||||||
- Messages: `/stop` now hard-aborts queued followups and sub-agent runs; suppress zero-count stop notes.
|
- Messages: `/stop` now hard-aborts queued followups and sub-agent runs; suppress zero-count stop notes.
|
||||||
- Sessions: reset `compactionCount` on `/new` and `/reset`, and preserve `sessions.json` file mode (0600).
|
- Sessions: reset `compactionCount` on `/new` and `/reset`, and preserve `sessions.json` file mode (0600).
|
||||||
|
|||||||
@ -74,4 +74,51 @@ describe("deliverReplies", () => {
|
|||||||
expect(sendVoice).toHaveBeenCalledTimes(1);
|
expect(sendVoice).toHaveBeenCalledTimes(1);
|
||||||
expect(events).toEqual(["recordVoice", "sendVoice"]);
|
expect(events).toEqual(["recordVoice", "sendVoice"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("splits long captions into media + follow-up text after the first media", async () => {
|
||||||
|
const events: string[] = [];
|
||||||
|
const runtime = { error: vi.fn() };
|
||||||
|
const sendPhoto = vi.fn(async () => {
|
||||||
|
events.push("photo");
|
||||||
|
return { message_id: 1, chat: { id: "123" } };
|
||||||
|
});
|
||||||
|
const sendMessage = vi.fn(async () => {
|
||||||
|
events.push("text");
|
||||||
|
return { message_id: 2, chat: { id: "123" } };
|
||||||
|
});
|
||||||
|
const bot = { api: { sendPhoto, sendMessage } } as unknown as Bot;
|
||||||
|
const longText = "A".repeat(1100);
|
||||||
|
|
||||||
|
loadWebMedia
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
buffer: Buffer.from("photo-a"),
|
||||||
|
contentType: "image/jpeg",
|
||||||
|
fileName: "a.jpg",
|
||||||
|
})
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
buffer: Buffer.from("photo-b"),
|
||||||
|
contentType: "image/jpeg",
|
||||||
|
fileName: "b.jpg",
|
||||||
|
});
|
||||||
|
|
||||||
|
await deliverReplies({
|
||||||
|
replies: [{ text: longText, mediaUrls: ["https://example.com/a.jpg", "https://example.com/b.jpg"] }],
|
||||||
|
chatId: "123",
|
||||||
|
token: "tok",
|
||||||
|
runtime,
|
||||||
|
bot,
|
||||||
|
replyToMode: "off",
|
||||||
|
textLimit: 4000,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(sendPhoto).toHaveBeenCalledTimes(2);
|
||||||
|
expect(sendPhoto).toHaveBeenNthCalledWith(
|
||||||
|
1,
|
||||||
|
"123",
|
||||||
|
expect.anything(),
|
||||||
|
expect.objectContaining({ caption: undefined }),
|
||||||
|
);
|
||||||
|
expect(sendMessage).toHaveBeenCalledWith("123", longText, {});
|
||||||
|
expect(events).toEqual(["photo", "text", "photo"]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -73,6 +73,7 @@ export async function deliverReplies(params: {
|
|||||||
// (when caption exceeds Telegram's 1024-char limit)
|
// (when caption exceeds Telegram's 1024-char limit)
|
||||||
let pendingFollowUpText: string | undefined;
|
let pendingFollowUpText: string | undefined;
|
||||||
for (const mediaUrl of mediaList) {
|
for (const mediaUrl of mediaList) {
|
||||||
|
const isFirstMedia = first;
|
||||||
const media = await loadWebMedia(mediaUrl);
|
const media = await loadWebMedia(mediaUrl);
|
||||||
const kind = mediaKindFromMime(media.contentType ?? undefined);
|
const kind = mediaKindFromMime(media.contentType ?? undefined);
|
||||||
const isGif = isGifMedia({
|
const isGif = isGifMedia({
|
||||||
@ -82,11 +83,12 @@ export async function deliverReplies(params: {
|
|||||||
const fileName = media.fileName ?? (isGif ? "animation.gif" : "file");
|
const fileName = media.fileName ?? (isGif ? "animation.gif" : "file");
|
||||||
const file = new InputFile(media.buffer, fileName);
|
const file = new InputFile(media.buffer, fileName);
|
||||||
// Caption only on first item; if text exceeds limit, defer to follow-up message.
|
// Caption only on first item; if text exceeds limit, defer to follow-up message.
|
||||||
const rawCaption = first ? (reply.text ?? undefined) : undefined;
|
const rawCaption = isFirstMedia ? (reply.text ?? undefined) : undefined;
|
||||||
const captionTooLong = rawCaption != null && rawCaption.length > TELEGRAM_MAX_CAPTION_LENGTH;
|
const trimmedCaption = rawCaption?.trim() ?? "";
|
||||||
const caption = captionTooLong ? undefined : rawCaption;
|
const captionTooLong = trimmedCaption.length > TELEGRAM_MAX_CAPTION_LENGTH;
|
||||||
if (captionTooLong && rawCaption) {
|
const caption = captionTooLong ? undefined : trimmedCaption || undefined;
|
||||||
pendingFollowUpText = rawCaption;
|
if (captionTooLong && trimmedCaption) {
|
||||||
|
pendingFollowUpText = trimmedCaption;
|
||||||
}
|
}
|
||||||
first = false;
|
first = false;
|
||||||
const replyToMessageId =
|
const replyToMessageId =
|
||||||
@ -138,22 +140,26 @@ export async function deliverReplies(params: {
|
|||||||
if (replyToId && !hasReplied) {
|
if (replyToId && !hasReplied) {
|
||||||
hasReplied = true;
|
hasReplied = true;
|
||||||
}
|
}
|
||||||
}
|
// Send deferred follow-up text right after the first media item.
|
||||||
// Send deferred follow-up text when caption was too long for media.
|
// Chunk it in case it's extremely long (same logic as text-only replies).
|
||||||
// Chunk it in case it's extremely long (same logic as text-only replies).
|
if (pendingFollowUpText && isFirstMedia) {
|
||||||
if (pendingFollowUpText) {
|
const chunks = markdownToTelegramChunks(pendingFollowUpText, textLimit);
|
||||||
const chunks = markdownToTelegramChunks(pendingFollowUpText, textLimit);
|
for (const chunk of chunks) {
|
||||||
for (const chunk of chunks) {
|
const replyToMessageIdFollowup =
|
||||||
await sendTelegramText(bot, chatId, chunk.html, runtime, {
|
replyToId && (replyToMode === "all" || !hasReplied) ? replyToId : undefined;
|
||||||
replyToMessageId:
|
const textParams: Record<string, unknown> = {};
|
||||||
replyToId && (replyToMode === "all" || !hasReplied) ? replyToId : undefined,
|
if (replyToMessageIdFollowup) {
|
||||||
messageThreadId,
|
textParams.reply_to_message_id = replyToMessageIdFollowup;
|
||||||
textMode: "html",
|
}
|
||||||
plainText: chunk.text,
|
if (threadParams) {
|
||||||
});
|
textParams.message_thread_id = threadParams.message_thread_id;
|
||||||
if (replyToId && !hasReplied) {
|
}
|
||||||
hasReplied = true;
|
await bot.api.sendMessage(chatId, chunk.text, textParams);
|
||||||
|
if (replyToId && !hasReplied) {
|
||||||
|
hasReplied = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
pendingFollowUpText = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user