feat(telegram): ingest and render inbound voice messages
This commit is contained in:
76
frontend/server/api/omni/telegram/media.get.ts
Normal file
76
frontend/server/api/omni/telegram/media.get.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { getQuery, setHeader } from "h3";
|
||||
import { getAuthContext } from "../../../utils/auth";
|
||||
import { prisma } from "../../../utils/prisma";
|
||||
import { requireTelegramBotToken, telegramApiBase, telegramBotApi } from "../../../utils/telegram";
|
||||
|
||||
const TELEGRAM_AUDIO_FILE_MARKER = "tg-file:";
|
||||
|
||||
type TelegramFileMeta = {
|
||||
file_id: string;
|
||||
file_path?: string;
|
||||
};
|
||||
|
||||
function parseTelegramFileId(audioUrl: string | null | undefined) {
|
||||
const raw = String(audioUrl ?? "").trim();
|
||||
if (!raw.startsWith(TELEGRAM_AUDIO_FILE_MARKER)) return null;
|
||||
const fileId = raw.slice(TELEGRAM_AUDIO_FILE_MARKER.length).trim();
|
||||
return fileId || null;
|
||||
}
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const auth = await getAuthContext(event);
|
||||
const query = getQuery(event);
|
||||
const messageId = String(query.messageId ?? "").trim();
|
||||
|
||||
if (!messageId) {
|
||||
throw createError({ statusCode: 400, statusMessage: "messageId is required" });
|
||||
}
|
||||
|
||||
const message = await prisma.contactMessage.findFirst({
|
||||
where: {
|
||||
id: messageId,
|
||||
channel: "TELEGRAM",
|
||||
contact: { teamId: auth.teamId },
|
||||
},
|
||||
select: {
|
||||
audioUrl: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!message) {
|
||||
throw createError({ statusCode: 404, statusMessage: "telegram message not found" });
|
||||
}
|
||||
|
||||
const fileId = parseTelegramFileId(message.audioUrl);
|
||||
if (!fileId) {
|
||||
throw createError({ statusCode: 404, statusMessage: "telegram audio is missing" });
|
||||
}
|
||||
|
||||
const meta = await telegramBotApi<TelegramFileMeta>("getFile", { file_id: fileId });
|
||||
const filePath = String(meta?.file_path ?? "").trim();
|
||||
if (!filePath) {
|
||||
throw createError({ statusCode: 502, statusMessage: "telegram file path is unavailable" });
|
||||
}
|
||||
|
||||
const apiBase = telegramApiBase().replace(/\/+$/, "");
|
||||
const token = requireTelegramBotToken();
|
||||
const remote = await fetch(`${apiBase}/file/bot${token}/${filePath}`);
|
||||
if (!remote.ok) {
|
||||
throw createError({ statusCode: 502, statusMessage: `telegram file fetch failed (${remote.status})` });
|
||||
}
|
||||
|
||||
const contentType = remote.headers.get("content-type") || "audio/ogg";
|
||||
const contentLength = remote.headers.get("content-length");
|
||||
const body = Buffer.from(await remote.arrayBuffer());
|
||||
|
||||
setHeader(event, "content-type", contentType);
|
||||
setHeader(event, "cache-control", "private, max-age=60");
|
||||
if (contentLength) {
|
||||
const parsedLength = Number(contentLength);
|
||||
if (Number.isFinite(parsedLength) && parsedLength >= 0) {
|
||||
setHeader(event, "content-length", parsedLength);
|
||||
}
|
||||
}
|
||||
|
||||
return body;
|
||||
});
|
||||
Reference in New Issue
Block a user