fix(telegram): honor forced document videos

Fixes #80389. Thanks @jbetala7.
This commit is contained in:
Jayesh Betala
2026-05-11 20:27:36 +05:30
committed by GitHub
parent ea663dbb56
commit 0c9f34eac1
5 changed files with 25 additions and 12 deletions

View File

@@ -64,6 +64,7 @@ Docs: https://docs.openclaw.ai
- Doctor/GitHub CLI: surface a `GH_CONFIG_DIR` hint when the GitHub skill is usable but `gh` auth lives under a different operator HOME than the agent process, without warning for disabled or filtered skills. Fixes #78063. (#78095) Thanks @tmimmanuel.
- Gateway: dedupe concurrent `send`, `poll`, and `message.action` requests while delivery is still in flight, preventing duplicate outbound work for the same idempotency key. (#68341) Thanks @thesomewhatyou.
- Cron: keep main-session `systemEvent` heartbeat wakes on their bound session route for both direct and queued wake paths by dropping inherited explicit heartbeat destinations when forcing `target: "last"`. Fixes #73900. Thanks @richardmqq.
- Telegram: honor forced document delivery for video media so `--force-document` sends MP4s as documents instead of typed videos. Fixes #80389. (#80405) Thanks @jbetala7.
- Gateway: clear speculative node wake state when APNs registration is missing, preventing unregistered or mistyped node IDs from retaining wake throttle entries. Fixes #68847. (#68848) Thanks @Feelw00.
- Auto-reply: keep late follow-up queue drain finalizers from deleting a replacement queue registered after `/stop`, preventing immediate follow-up messages from being orphaned. Fixes #68838. (#68839) Thanks @Feelw00.
- Feishu: make manual App ID/App Secret setup the default channel-binding path while keeping QR scan-to-create as an optional best-effort flow, and document the manual fallback for domestic Feishu mobile clients that do not react to the QR code. Fixes #80591. Thanks @wei-wei-zhao.

View File

@@ -840,7 +840,7 @@ openclaw message poll --channel telegram --target -1001234567890:topic:42 \
- `--presentation` with `buttons` blocks for inline keyboards when `channels.telegram.capabilities.inlineButtons` allows it
- `--pin` or `--delivery '{"pin":true}'` to request pinned delivery when the bot can pin in that chat
- `--force-document` to send outbound images and GIFs as documents instead of compressed photo or animated-media uploads
- `--force-document` to send outbound images, GIFs, and videos as documents instead of compressed photo, animated-media, or video uploads
Action gating:

View File

@@ -72,7 +72,7 @@ Name lookup:
- Optional: `--media`, `--presentation`, `--delivery`, `--pin`, `--reply-to`, `--thread-id`, `--gif-playback`, `--force-document`, `--silent`
- Shared presentation payloads: `--presentation` sends semantic blocks (`text`, `context`, `divider`, `buttons`, `select`) that core renders through the selected channel's declared capabilities. See [Message Presentation](/plugins/message-presentation).
- Generic delivery preferences: `--delivery` accepts delivery hints such as `{ "pin": true }`; `--pin` is shorthand for pinned delivery when the channel supports it.
- Telegram only: `--force-document` (send images and GIFs as documents to avoid Telegram compression)
- Telegram only: `--force-document` (send images, GIFs, and videos as documents to avoid Telegram compression)
- Telegram only: `--thread-id` (forum topic id)
- Slack only: `--thread-id` (thread timestamp; `--reply-to` uses the same field)
- Telegram + Discord: `--silent`

View File

@@ -1385,6 +1385,13 @@ describe("sendMessageTelegram", () => {
fileName: "fun.gif",
mediaUrl: "https://example.com/fun.gif",
},
{
name: "videos",
buffer: Buffer.from("fake-video"),
contentType: "video/mp4",
fileName: "clip.mp4",
mediaUrl: "https://example.com/clip.mp4",
},
])("sends $name as documents when forceDocument is true", async (testCase) => {
const chatId = "123";
const sendAnimation = vi.fn();
@@ -1393,10 +1400,12 @@ describe("sendMessageTelegram", () => {
chat: { id: chatId },
});
const sendPhoto = vi.fn();
const api = { sendAnimation, sendDocument, sendPhoto } as unknown as {
const sendVideo = vi.fn();
const api = { sendAnimation, sendDocument, sendPhoto, sendVideo } as unknown as {
sendAnimation: typeof sendAnimation;
sendDocument: typeof sendDocument;
sendPhoto: typeof sendPhoto;
sendVideo: typeof sendVideo;
};
mockLoadedMedia({
@@ -1420,6 +1429,8 @@ describe("sendMessageTelegram", () => {
});
expect(sendPhoto, testCase.name).not.toHaveBeenCalled();
expect(sendAnimation, testCase.name).not.toHaveBeenCalled();
expect(sendVideo, testCase.name).not.toHaveBeenCalled();
expect(probeVideoDimensions, testCase.name).not.toHaveBeenCalled();
expect(res.messageId).toBe("10");
});

View File

@@ -814,12 +814,13 @@ export async function sendMessageTelegram(
fileName: media.fileName,
});
// Validate photo dimensions before attempting sendPhoto
let sendImageAsPhoto = true;
if (kind === "image" && !isGif && !opts.forceDocument) {
const deliveryKind =
opts.forceDocument === true && (kind === "image" || kind === "video") ? "document" : kind;
if (deliveryKind === "image" && !isGif) {
sendImageAsPhoto = await shouldSendTelegramImageAsPhoto(media.buffer);
}
const isVideoNote = kind === "video" && opts.asVideoNote === true;
const isVideoNote = deliveryKind === "video" && opts.asVideoNote === true;
const fileName =
media.fileName ?? (isGif ? "animation.gif" : inferFilename(kind ?? "document")) ?? "file";
const file = new InputFileCtor(media.buffer, fileName);
@@ -845,7 +846,9 @@ export async function sendMessageTelegram(
...(!needsSeparateText && replyMarkup ? { reply_markup: replyMarkup } : {}),
};
const videoDimensions =
kind === "video" && !isVideoNote ? await probeVideoDimensions(media.buffer) : undefined;
deliveryKind === "video" && !isVideoNote
? await probeVideoDimensions(media.buffer)
: undefined;
const mediaParams = {
...(htmlCaption ? { caption: htmlCaption, parse_mode: "HTML" as const } : {}),
...baseMediaParams,
@@ -868,7 +871,7 @@ export async function sendMessageTelegram(
);
const mediaSender = (() => {
if (isGif && !opts.forceDocument) {
if (isGif && deliveryKind !== "document") {
return {
label: "animation",
sender: (effectiveParams: TelegramThreadScopedParams | undefined) =>
@@ -879,7 +882,7 @@ export async function sendMessageTelegram(
) as Promise<TelegramMessageLike>,
};
}
if (kind === "image" && !opts.forceDocument && sendImageAsPhoto) {
if (deliveryKind === "image" && !isGif && sendImageAsPhoto) {
return {
label: "photo",
sender: (effectiveParams: TelegramThreadScopedParams | undefined) =>
@@ -890,7 +893,7 @@ export async function sendMessageTelegram(
) as Promise<TelegramMessageLike>,
};
}
if (kind === "video") {
if (deliveryKind === "video") {
if (isVideoNote) {
return {
label: "video_note",
@@ -946,8 +949,6 @@ export async function sendMessageTelegram(
api.sendDocument(
chatId,
file,
// Only force Telegram to keep the uploaded media type when callers explicitly
// opt into document delivery for image/GIF uploads.
(opts.forceDocument
? { ...effectiveParams, disable_content_type_detection: true }
: effectiveParams) as Parameters<typeof api.sendDocument>[2],