fix(telegram): guard voice note sends
This commit is contained in:
committed by
Peter Steinberger
parent
2f036f7173
commit
ce786762db
@@ -60,6 +60,7 @@ import { resolveTelegramAccount } from "./accounts.js";
|
||||
import { createTelegramDraftStream } from "./draft-stream.js";
|
||||
import { resolveTelegramFetch } from "./fetch.js";
|
||||
import { markdownToTelegramHtml } from "./format.js";
|
||||
import { isTelegramVoiceCompatible } from "./voice.js";
|
||||
import {
|
||||
readTelegramAllowFromStore,
|
||||
upsertTelegramPairingRequest,
|
||||
@@ -1387,7 +1388,19 @@ async function deliverReplies(params: {
|
||||
...mediaParams,
|
||||
});
|
||||
} else if (kind === "audio") {
|
||||
const useVoice = reply.audioAsVoice === true; // default false (backward compatible)
|
||||
const wantsVoice = reply.audioAsVoice === true; // default false (backward compatible)
|
||||
const canVoice = wantsVoice
|
||||
? isTelegramVoiceCompatible({
|
||||
contentType: media.contentType,
|
||||
fileName,
|
||||
})
|
||||
: false;
|
||||
const useVoice = wantsVoice && canVoice;
|
||||
if (wantsVoice && !canVoice) {
|
||||
logVerbose(
|
||||
`Telegram voice requested but media is ${media.contentType ?? "unknown"} (${fileName}); sending as audio file instead.`,
|
||||
);
|
||||
}
|
||||
if (useVoice) {
|
||||
// Voice message - displays as round playable bubble (opt-in via [[audio_as_voice]])
|
||||
await bot.api.sendVoice(chatId, file, {
|
||||
|
||||
@@ -324,6 +324,40 @@ describe("sendMessageTelegram", () => {
|
||||
expect(sendAudio).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("falls back to audio when asVoice is true but media is not voice compatible", async () => {
|
||||
const chatId = "123";
|
||||
const sendAudio = vi.fn().mockResolvedValue({
|
||||
message_id: 14,
|
||||
chat: { id: chatId },
|
||||
});
|
||||
const sendVoice = vi.fn().mockResolvedValue({
|
||||
message_id: 15,
|
||||
chat: { id: chatId },
|
||||
});
|
||||
const api = { sendAudio, sendVoice } as unknown as {
|
||||
sendAudio: typeof sendAudio;
|
||||
sendVoice: typeof sendVoice;
|
||||
};
|
||||
|
||||
loadWebMedia.mockResolvedValueOnce({
|
||||
buffer: Buffer.from("audio"),
|
||||
contentType: "audio/mpeg",
|
||||
fileName: "clip.mp3",
|
||||
});
|
||||
|
||||
await sendMessageTelegram(chatId, "caption", {
|
||||
token: "tok",
|
||||
api,
|
||||
mediaUrl: "https://example.com/clip.mp3",
|
||||
asVoice: true,
|
||||
});
|
||||
|
||||
expect(sendAudio).toHaveBeenCalledWith(chatId, expect.anything(), {
|
||||
caption: "caption",
|
||||
});
|
||||
expect(sendVoice).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("includes message_thread_id for forum topic messages", async () => {
|
||||
const chatId = "-1001234567890";
|
||||
const sendMessage = vi.fn().mockResolvedValue({
|
||||
|
||||
@@ -10,6 +10,7 @@ import { formatErrorMessage } from "../infra/errors.js";
|
||||
import { recordProviderActivity } from "../infra/provider-activity.js";
|
||||
import type { RetryConfig } from "../infra/retry.js";
|
||||
import { createTelegramRetryRunner } from "../infra/retry-policy.js";
|
||||
import { logVerbose } from "../globals.js";
|
||||
import { mediaKindFromMime } from "../media/constants.js";
|
||||
import { isGifMedia } from "../media/mime.js";
|
||||
import { loadWebMedia } from "../web/media.js";
|
||||
@@ -20,6 +21,7 @@ import {
|
||||
parseTelegramTarget,
|
||||
stripTelegramInternalPrefixes,
|
||||
} from "./targets.js";
|
||||
import { resolveTelegramVoiceDecision } from "./voice.js";
|
||||
|
||||
type TelegramSendOpts = {
|
||||
token?: string;
|
||||
@@ -237,7 +239,16 @@ export async function sendMessageTelegram(
|
||||
throw wrapChatNotFound(err);
|
||||
});
|
||||
} else if (kind === "audio") {
|
||||
const useVoice = opts.asVoice === true; // default false (backward compatible)
|
||||
const { useVoice, reason } = resolveTelegramVoiceDecision({
|
||||
wantsVoice: opts.asVoice === true, // default false (backward compatible)
|
||||
contentType: media.contentType,
|
||||
fileName,
|
||||
});
|
||||
if (reason) {
|
||||
logVerbose(
|
||||
`Telegram voice requested but ${reason}; sending as audio file instead.`,
|
||||
);
|
||||
}
|
||||
if (useVoice) {
|
||||
result = await request(
|
||||
() => api.sendVoice(chatId, file, mediaParams),
|
||||
|
||||
15
src/telegram/voice.ts
Normal file
15
src/telegram/voice.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import path from "node:path";
|
||||
|
||||
export function isTelegramVoiceCompatible(opts: {
|
||||
contentType?: string | null;
|
||||
fileName?: string | null;
|
||||
}): boolean {
|
||||
const mime = opts.contentType?.toLowerCase();
|
||||
if (mime && (mime.includes("ogg") || mime.includes("opus"))) {
|
||||
return true;
|
||||
}
|
||||
const fileName = opts.fileName?.trim();
|
||||
if (!fileName) return false;
|
||||
const ext = path.extname(fileName).toLowerCase();
|
||||
return ext === ".ogg" || ext === ".opus" || ext === ".oga";
|
||||
}
|
||||
Reference in New Issue
Block a user