fix: preserve Telegram local Bot API MIME types (#54603) (thanks @jzakirov)

* fix(telegram): preserve content type for local Bot API media files

* fix: preserve Telegram local Bot API MIME types (#54603) (thanks @jzakirov)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
This commit is contained in:
Jamil Zakirov
2026-04-01 06:38:31 +03:00
committed by GitHub
parent 098125e998
commit 69685f99fe
3 changed files with 33 additions and 4 deletions

View File

@@ -9,6 +9,10 @@ Docs: https://docs.openclaw.ai
- Agents/compaction: resolve `agents.defaults.compaction.model` consistently for manual `/compact` and other context-engine compaction paths, so engine-owned compaction uses the configured override model across runtime entrypoints. (#56710) Thanks @oliviareid-svg
- Channels/session routing: move provider-specific session conversation grammar into plugin-owned session-key surfaces, preserving Telegram topic routing and Feishu scoped inheritance across bootstrap, model override, restart, and tool-policy paths.
### Fixes
- Telegram/local Bot API: preserve media MIME types for absolute-path downloads so local audio files still trigger transcription and other MIME-based handling. (#54603) Thanks @jzakirov
## 2026.3.31
### Breaking

View File

@@ -40,7 +40,7 @@ const BOT_TOKEN = "tok123";
function makeCtx(
mediaField: "voice" | "audio" | "photo" | "video" | "document" | "animation" | "sticker",
getFile: TelegramContext["getFile"],
opts?: { file_name?: string },
opts?: { file_name?: string; mime_type?: string },
): TelegramContext {
const msg: Record<string, unknown> = {
message_id: 1,
@@ -48,7 +48,12 @@ function makeCtx(
chat: { id: 1, type: "private" },
};
if (mediaField === "voice") {
msg.voice = { file_id: "v1", duration: 5, file_unique_id: "u1" };
msg.voice = {
file_id: "v1",
duration: 5,
file_unique_id: "u1",
...(opts?.mime_type && { mime_type: opts.mime_type }),
};
}
if (mediaField === "audio") {
msg.audio = {
@@ -56,6 +61,7 @@ function makeCtx(
duration: 5,
file_unique_id: "u2",
...(opts?.file_name && { file_name: opts.file_name }),
...(opts?.mime_type && { mime_type: opts.mime_type }),
};
}
if (mediaField === "photo") {
@@ -74,6 +80,7 @@ function makeCtx(
file_id: "d1",
file_unique_id: "u4",
...(opts?.file_name && { file_name: opts.file_name }),
...(opts?.mime_type && { mime_type: opts.mime_type }),
};
}
if (mediaField === "animation") {
@@ -359,13 +366,18 @@ describe("resolveMedia getFile retry", () => {
it("uses local absolute file paths directly for media downloads", async () => {
const getFile = vi.fn().mockResolvedValue({ file_path: "/var/lib/telegram-bot-api/file.pdf" });
const result = await resolveMedia(makeCtx("document", getFile), MAX_MEDIA_BYTES, BOT_TOKEN);
const result = await resolveMedia(
makeCtx("document", getFile, { mime_type: "application/pdf" }),
MAX_MEDIA_BYTES,
BOT_TOKEN,
);
expect(fetchRemoteMedia).not.toHaveBeenCalled();
expect(saveMediaBuffer).not.toHaveBeenCalled();
expect(result).toEqual(
expect.objectContaining({
path: "/var/lib/telegram-bot-api/file.pdf",
contentType: "application/pdf",
placeholder: "<media:document>",
}),
);

View File

@@ -90,6 +90,17 @@ function resolveTelegramFileName(msg: TelegramContext["message"]): string | unde
);
}
function resolveTelegramMimeType(msg: TelegramContext["message"]): string | undefined {
return (
msg.audio?.mime_type ??
msg.voice?.mime_type ??
msg.video?.mime_type ??
msg.document?.mime_type ??
msg.animation?.mime_type ??
undefined
);
}
async function resolveTelegramFileWithRetry(
ctx: TelegramContext,
): Promise<{ file_path?: string } | null> {
@@ -152,10 +163,11 @@ async function downloadAndSaveTelegramFile(params: {
transport: TelegramTransport;
maxBytes: number;
telegramFileName?: string;
mimeType?: string;
apiRoot?: string;
}) {
if (path.isAbsolute(params.filePath)) {
return { path: params.filePath, contentType: undefined };
return { path: params.filePath, contentType: params.mimeType };
}
const apiBase = resolveTelegramApiBase(params.apiRoot);
const url = `${apiBase}/file/bot${params.token}/${params.filePath}`;
@@ -320,6 +332,7 @@ export async function resolveMedia(
transport: resolveRequiredTelegramTransport(transport),
maxBytes,
telegramFileName: resolveTelegramFileName(msg),
mimeType: resolveTelegramMimeType(msg),
apiRoot,
});
const placeholder = resolveTelegramMediaPlaceholder(msg) ?? "<media:document>";