Telegram 영상 생성 봇: Sume Job 후 sendVideo

Telegram 봇은 /video 커맨드를 Sume Job으로 바꿔 곧바로 답한 뒤, Sume의 서명된 웹훅이 도착하면 아티팩트 URL로 sendVideo를 호출할 수 있습니다.

읽는 시간 6분Sume
전체 글

Telegram 봇이 영상을 생성하게 하려면, 봇의 웹훅이 /video 커맨드를 model: "sume/auto"와 callback_url을 담은 POST /v1/videos로 바꾸고 곧바로 답하게 하세요. 그리고 Sume의 서명된 job.completed 웹훅이 도착하면 영상 아티팩트의 media.sume.com URL로 sendVideo를 호출하세요. Telegram은 URL로 보낸 영상을 20 MB까지만 가져오므로, 더 큰 파일은 multipart/form-data로 50 MB까지 업로드하거나 링크를 보내세요.

Telegram 관련 내용은 Telegram Bot API 레퍼런스에서, Sume 관련 내용은 영상 생성 (영문), 웹훅 (영문), 웹훅 검증 문서와, 아티팩트 필드의 경우 Sume API 레퍼런스에서 가져왔습니다. 모두 2026-09-27에 확인했습니다. Sume에는 Telegram 봇이나 전용 연동이 없으며, 여러분 봇의 서버가 HTTPS로 Sume를 직접 호출하는 방식입니다. 같은 흐름의 Discord 버전은 Discord 봇 AI 영상 생성에 있습니다.

봇은 /video 커맨드를 어떻게 받나요?

setWebhook과 secret_token으로 HTTPS 엔드포인트를 등록하세요. 그러면 Telegram은 모든 업데이트의 X-Telegram-Bot-Api-Secret-Token 헤더에 그 토큰을 담아 보내므로, 라우트는 그 밖의 요청을 모두 거부할 수 있습니다. 서버가 2XX가 아닌 상태로 응답한 업데이트는 Telegram이 다시 보내며, update_id로 반복 여부를 알 수 있으므로 이 값으로 Sume의 Idempotency-Key를 만드세요. 다시 보낸 요청은 새 Job을 시작하지 않고 원래 Job을 반환합니다.

답장은 웹훅 응답에 실어 보낼 수 있습니다. Telegram은 응답 본문에 지정된 Bot API 메서드를 실행하지만, 그 호출이 성공했는지는 알 수 없습니다.

import crypto from "node:crypto";
import express from "express";

const app = express();
app.post("/telegram", express.json(), async (req, res) => {
  const got = Buffer.from(req.get("X-Telegram-Bot-Api-Secret-Token") ?? "");
  const want = Buffer.from(process.env.TELEGRAM_SECRET_TOKEN);
  if (got.length !== want.length || !crypto.timingSafeEqual(got, want)) return res.sendStatus(401);
  const msg = req.body.message;
  if (!msg?.text?.startsWith("/video ")) return res.sendStatus(200);
  const r = await fetch("https://api.sume.com/v1/videos", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.SUME_API_KEY}`,
      "Content-Type": "application/json",
      "Idempotency-Key": `telegram-${req.body.update_id}`,
    },
    body: JSON.stringify({ model: "sume/auto", prompt: msg.text.slice(7), callback_url: "https://bot.example.com/hooks/sume" }),
  });
  const job = await r.json();
  if (r.ok) await saveJob(job.id, msg.chat.id); // your store: job id -> chat id
  // A Bot API call in the webhook response: Telegram runs it, you get no result.
  res.json({ method: "sendMessage", chat_id: msg.chat.id, text: r.ok ? "Rendering your video..." : "Sume refused the request." });
});

봇은 완성된 영상을 어떻게 보내나요?

Sume는 Job이 종료 상태에 도달하면 서명된 Job 웹훅을 한 번 POST하며, 시도마다 10초씩 최대 10회 시도합니다. 원본 본문은 @sume-com/sdk의 verifyWebhook으로 검증하세요. 이 함수는 또 시크릿 교체 중에 오는 sume-v1= 항목 중 어느 쪽이든 받아들입니다. 재시도된 전달을 한 번만 처리하도록 job_id를 선점하고, 204로 응답한 뒤 Telegram을 호출하세요. job.completed에서는 payload.artifacts[]에 video 항목이 있고, 그 url은 공개된 media.sume.com 아티팩트입니다. API 레퍼런스에 따르면 각 아티팩트에는 content_type과 null일 수 있는 size_bytes가 있습니다. 코드의 두 가지 선택은 Telegram이 아니라 이 글이 정한 것입니다. URL 한도인 20 MB를 20,000,000바이트로 해석하고, size_bytes가 없거나 sendVideo가 실패하면 링크를 텍스트로 게시합니다.

import { verifyWebhook } from "@sume-com/sdk";

const tg = (method, params) =>
  fetch(`https://api.telegram.org/bot${process.env.TELEGRAM_BOT_TOKEN}/${method}`, {
    method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(params),
  }).then((r) => r.json()); // { ok: true, result } or { ok: false, description }

app.post("/hooks/sume", express.raw({ type: "application/json" }), async (req, res) => {
  const ok = await verifyWebhook({ body: req.body, headers: req.headers, secret: process.env.SUME_COM_WEBHOOK_SIGNING_SECRET });
  if (!ok) return res.sendStatus(401);
  const event = JSON.parse(req.body);
  const chatId = await claimJob(event.job_id); // your store: null if unknown or already handled
  res.sendStatus(204); // Sume allows 10 s per attempt
  if (!chatId) return;
  const video = event.event === "job.completed" && event.payload.artifacts.find((a) => a.type === "video");
  if (!video) return tg("sendMessage", { chat_id: chatId, text: "The video job did not complete." });
  const byUrl = video.size_bytes != null && video.size_bytes <= 20_000_000; // our reading of "20 MB"
  const sent = byUrl ? await tg("sendVideo", { chat_id: chatId, video: video.url }) : { ok: false };
  if (!sent.ok) await tg("sendMessage", { chat_id: chatId, text: video.url }); // our fallback; or upload (50 MB)
});

봇은 파일을 어떤 방식으로 보내야 하나요?

sendVideo는 file_id, HTTP URL, multipart 업로드를 받으며, Telegram 클라이언트는 MPEG4 영상을 지원합니다. 크기 한도는 방식에 따라 다릅니다.

Telegram Bot API의 파일 보내기와 sendVideo 섹션, API 레퍼런스의 아티팩트 스키마 기준, 2026-09-27 확인.
방식한도사용할 때
video에 HTTP URL 지정20 MBsize_bytes가 한도 미만일 때입니다. Telegram이 파일을 내려받으며, 파일의 MIME 유형이 올바라야 합니다.
multipart/form-data로 업로드50 MB파일이 20 MB보다 클 때입니다. media.sume.com에서 내려받은 뒤 업로드하세요.
file_id한도 없음영상이 이미 Telegram 서버에 있을 때입니다. file_id는 그 파일을 받은 봇에서만 동작합니다.
Local Bot API Server업로드 최대 2000 MBTelegram의 Bot API 서버를 직접 운영할 때입니다. 그렇지 않다면 링크를 텍스트로 보내세요.

어떤 한도가 있나요?

봇을 채팅에 공개하기 전에 다음 사항에 대비하세요.

  • Sume는 종료 Job 이벤트만 보내고 진행 상황은 전달하지 않으며, 영상 생성은 보통 30초에서 몇 분이 걸립니다. upload_video를 담은 sendChatAction은 상태를 5초 이하로만 표시하므로, 기다리는 동안에는 텍스트로 답하세요.
  • 아티팩트 URL은 링크만 있으면 열리므로, 채팅을 읽을 수 있는 사람은 누구나 영상을 열어 볼 수 있습니다.
  • 커맨드 하나하나가 워크스페이스 잔액으로 비용을 내는 Job을 시작합니다. Sume 문서는 요청을 전달하기 전에 사용자 입력을 검증하고 자체 인가를 적용하라고 안내하므로, 어떤 채팅에서 봇을 쓸 수 있는지 정하세요.
  • Telegram 웹훅은 443, 80, 88, 8443 포트를 허용하지만, 현재 Sume 코드는 :8443처럼 기본 포트가 아닌 callback_url을 거부합니다. Sume용 라우트는 기본 HTTPS 포트로 서비스하세요.
  • 끝내 도착하지 않은 전달은 Job을 바꾸지 않습니다. GET /v1/jobs/{id}/status 읽기를 백업으로 유지하세요.

출처

관련 글

연동 카테고리의 다른 글

연동 글 전체 보기

작성자 Sume