Telegram-Bot für Transkriptionen

Erstellen Sie einen Telegram-Bot, der Audio- und Videonachrichten in über 90 Sprachen mit TypeScript, Deno und Supabase Edge Functions transkribiert.

Anleitung · Setzt voraus, dass Sie den Speech to Text Schnellstart abgeschlossen haben und über ein Telegram-Bot-Token sowie ein Supabase-Konto verfügen.

Einführung

In diesem Tutorial lernen Sie, wie Sie einen Telegram-Bot erstellen, der Audio- und Videonachrichten in über 90 Sprachen mit TypeScript und dem ElevenLabs-Scribe-Modell über die Speech-to-Text-API transkribiert.

Voraussetzungen

Einrichtung

Einen Telegram-Bot registrieren

Erstellen Sie mit dem BotFather einen neuen Telegram-Bot. Führen Sie den Befehl /newbot aus und folgen Sie den Anweisungen, um einen neuen Bot zu erstellen. Am Ende erhalten Sie Ihr geheimes Bot-Token. Notieren Sie es sicher für den nächsten Schritt.

BotFather

Ein Supabase-Projekt lokal erstellen

Führen Sie nach der Installation der Supabase CLI den folgenden Befehl aus, um lokal ein neues Supabase-Projekt zu erstellen:

supabase init

Eine Datenbanktabelle für Transkriptionsergebnisse erstellen

Erstellen Sie als Nächstes eine neue Datenbanktabelle, um die Transkriptionsergebnisse zu protokollieren:

supabase migrations new init

Dadurch wird im Verzeichnis supabase/migrations eine neue Migrationsdatei erstellt. Öffnen Sie die Datei und fügen Sie das folgende SQL hinzu:

supabase/migrations/init.sql
CREATE TABLE IF NOT EXISTS transcription_logs (
id BIGSERIAL PRIMARY KEY,
file_type VARCHAR NOT NULL,
duration INTEGER NOT NULL,
chat_id BIGINT NOT NULL,
message_id BIGINT NOT NULL,
username VARCHAR,
transcript TEXT,
language_code VARCHAR,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
error TEXT
);
ALTER TABLE transcription_logs ENABLE ROW LEVEL SECURITY;

Eine Supabase Edge Function für Telegram-Webhook-Anfragen erstellen

Erstellen Sie als Nächstes eine neue Edge Function, die Telegram-Webhook-Anfragen verarbeitet:

supabase functions new scribe-bot

Wenn Sie VS Code oder Cursor verwenden, wählen Sie y, wenn die CLI „Generate VS Code settings for Deno? [y/N]“ abfragt.

Umgebungsvariablen einrichten

Erstellen Sie im Verzeichnis supabase/functions eine neue .env-Datei und fügen Sie die folgenden Variablen hinzu:

supabase/functions/.env
# Find / create an API key at https://elevenlabs.io/app/settings/api-keys
ELEVENLABS_API_KEY=your_api_key
# The bot token you received from the BotFather.
TELEGRAM_BOT_TOKEN=your_bot_token
# A random secret chosen by you to secure the function.
FUNCTION_SECRET=random_secret

Abhängigkeiten

Das Projekt verwendet einige Abhängigkeiten:

Da Supabase Edge Function die Deno-Runtime verwendet, müssen Sie die Abhängigkeiten nicht installieren. Sie können sie über das Präfix npm: importieren.

Telegram-Bot programmieren

Fügen Sie in Ihrer neu erstellten Datei scribe-bot/index.ts den folgenden Code hinzu:

supabase/functions/scribe-bot/index.ts
import { Bot, webhookCallback } from "https://deno.land/x/grammy@v1.34.0/mod.ts";
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
import { createClient } from "jsr:@supabase/supabase-js@2";
import { ElevenLabsClient } from "npm:elevenlabs@1.50.5";
console.log(`Function "elevenlabs-scribe-bot" up and running!`);
const elevenlabs = new ElevenLabsClient({
apiKey: Deno.env.get("ELEVENLABS_API_KEY") || "",
});
const supabase = createClient(
Deno.env.get("SUPABASE_URL") || "",
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") || ""
);
async function scribe({
fileURL,
fileType,
duration,
chatId,
messageId,
username,
}: {
fileURL: string;
fileType: string;
duration: number;
chatId: number;
messageId: number;
username: string;
}) {
let transcript: string | null = null;
let languageCode: string | null = null;
let errorMsg: string | null = null;
try {
const sourceFileArrayBuffer = await fetch(fileURL).then((res) => res.arrayBuffer());
const sourceBlob = new Blob([sourceFileArrayBuffer], {
type: fileType,
});
const scribeResult = await elevenlabs.speechToText.convert({
file: sourceBlob,
model_id: "scribe_v2",
tag_audio_events: false,
});
transcript = scribeResult.text;
languageCode = scribeResult.language_code;
// Reply to the user with the transcript
await bot.api.sendMessage(chatId, transcript, {
reply_parameters: { message_id: messageId },
});
} catch (error) {
errorMsg = error.message;
console.log(errorMsg);
await bot.api.sendMessage(chatId, "Sorry, there was an error. Please try again.", {
reply_parameters: { message_id: messageId },
});
}
// Write log to Supabase.
const logLine = {
file_type: fileType,
duration,
chat_id: chatId,
message_id: messageId,
username,
language_code: languageCode,
error: errorMsg,
};
console.log({ logLine });
await supabase.from("transcription_logs").insert({ ...logLine, transcript });
}
const telegramBotToken = Deno.env.get("TELEGRAM_BOT_TOKEN");
const bot = new Bot(telegramBotToken || "");
const startMessage = `Welcome to the ElevenLabs Scribe Bot\\! I can transcribe speech in 90\\+ languages with super high accuracy\\!
\nTry it out by sending or forwarding me a voice message, video, or audio file\\!
\n[Learn more about Scribe](https://elevenlabs.io/speech-to-text) or [build your own bot](https://elevenlabs.io/developers/guides/cookbooks/speech-to-text/telegram-bot)\\!
`;
bot.command("start", (ctx) => ctx.reply(startMessage.trim(), { parse_mode: "MarkdownV2" }));
bot.on([":voice", ":audio", ":video"], async (ctx) => {
try {
const file = await ctx.getFile();
const fileURL = `https://api.telegram.org/file/bot${telegramBotToken}/${file.file_path}`;
const fileMeta = ctx.message?.video ?? ctx.message?.voice ?? ctx.message?.audio;
if (!fileMeta) {
return ctx.reply("No video|audio|voice metadata found. Please try again.");
}
// Run the transcription in the background.
EdgeRuntime.waitUntil(
scribe({
fileURL,
fileType: fileMeta.mime_type!,
duration: fileMeta.duration,
chatId: ctx.chat.id,
messageId: ctx.message?.message_id!,
username: ctx.from?.username || "",
})
);
// Reply to the user immediately to let them know we received their file.
return ctx.reply("Received. Scribing...");
} catch (error) {
console.error(error);
return ctx.reply(
"Sorry, there was an error getting the file. Please try again with a smaller file!"
);
}
});
const handleUpdate = webhookCallback(bot, "std/http");
Deno.serve(async (req) => {
try {
const url = new URL(req.url);
if (url.searchParams.get("secret") !== Deno.env.get("FUNCTION_SECRET")) {
return new Response("not allowed", { status: 405 });
}
return await handleUpdate(req);
} catch (err) {
console.error(err);
}
});

Code im Detail

Im Code gibt es einige wichtige Punkte. Sehen wir sie uns Schritt für Schritt an.

1

Eingehende Anfrage verarbeiten

Verwenden Sie zur Verarbeitung der eingehenden Anfrage den Handler Deno.serve. Der Handler prüft, ob die Anfrage das richtige Secret enthält, und übergibt die Anfrage dann an die Funktion handleUpdate.

const handleUpdate = webhookCallback(bot, 'std/http');
Deno.serve(async (req) => {
try {
const url = new URL(req.url);
if (url.searchParams.get('secret') !== Deno.env.get('FUNCTION_SECRET')) {
return new Response('not allowed', { status: 405 });
}
return await handleUpdate(req);
} catch (err) {
console.error(err);
}
});
2

Sprach-, Audio- und Videonachrichten verarbeiten

Das grammY-Framework bietet eine praktische Möglichkeit, nach bestimmten Nachrichtentypen zu filtern. In diesem Fall lauscht der Bot auf Sprach-, Audio- und Videonachrichten.

Über den Anfragekontext extrahiert der Bot die Dateimetadaten und verwendet dann Supabase Background Tasks EdgeRuntime.waitUntil, um die Transkription im Hintergrund auszuführen.

So können Sie dem Nutzer sofort antworten und die Transkription der Datei im Hintergrund verarbeiten.

bot.on([':voice', ':audio', ':video'], async (ctx) => {
try {
const file = await ctx.getFile();
const fileURL = `https://api.telegram.org/file/bot${telegramBotToken}/${file.file_path}`;
const fileMeta = ctx.message?.video ?? ctx.message?.voice ?? ctx.message?.audio;
if (!fileMeta) {
return ctx.reply('No video|audio|voice metadata found. Please try again.');
}
// Run the transcription in the background.
EdgeRuntime.waitUntil(
scribe({
fileURL,
fileType: fileMeta.mime_type!,
duration: fileMeta.duration,
chatId: ctx.chat.id,
messageId: ctx.message?.message_id!,
username: ctx.from?.username || '',
})
);
// Reply to the user immediately to let them know we received their file.
return ctx.reply('Received. Scribing...');
} catch (error) {
console.error(error);
return ctx.reply(
'Sorry, there was an error getting the file. Please try again with a smaller file!'
);
}
});
3

Transkription mit der ElevenLabs-API

Schließlich verwendet der Bot im Hintergrund-Worker das ElevenLabs JavaScript SDK, um die Datei zu transkribieren. Sobald die Transkription abgeschlossen ist, antwortet der Bot dem Nutzer mit dem Transkript und schreibt mit supabase-js einen Protokolleintrag in die Supabase-Datenbank.

const elevenlabs = new ElevenLabsClient({
apiKey: Deno.env.get('ELEVENLABS_API_KEY') || '',
});
const supabase = createClient(
Deno.env.get('SUPABASE_URL') || '',
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') || ''
);
async function scribe({
fileURL,
fileType,
duration,
chatId,
messageId,
username,
}: {
fileURL: string;
fileType: string;
duration: number;
chatId: number;
messageId: number;
username: string;
}) {
let transcript: string | null = null;
let languageCode: string | null = null;
let errorMsg: string | null = null;
try {
const sourceFileArrayBuffer = await fetch(fileURL).then((res) => res.arrayBuffer());
const sourceBlob = new Blob([sourceFileArrayBuffer], {
type: fileType,
});
const scribeResult = await elevenlabs.speechToText.convert({
file: sourceBlob,
model_id: 'scribe_v2',
tag_audio_events: false,
});
transcript = scribeResult.text;
languageCode = scribeResult.language_code;
// Reply to the user with the transcript
await bot.api.sendMessage(chatId, transcript, {
reply_parameters: { message_id: messageId },
});
} catch (error) {
errorMsg = error.message;
console.log(errorMsg);
await bot.api.sendMessage(chatId, 'Sorry, there was an error. Please try again.', {
reply_parameters: { message_id: messageId },
});
}
// Write log to Supabase.
const logLine = {
file_type: fileType,
duration,
chat_id: chatId,
message_id: messageId,
username,
language_code: languageCode,
error: errorMsg,
};
console.log({ logLine });
await supabase.from('transcription_logs').insert({ ...logLine, transcript });
}

In Supabase bereitstellen

Falls noch nicht geschehen, erstellen Sie unter database.new ein neues Supabase-Konto und verknüpfen Sie das lokale Projekt mit Ihrem Supabase-Konto:

supabase link

Datenbankmigrationen anwenden

Führen Sie den folgenden Befehl aus, um die Datenbankmigrationen aus dem Verzeichnis supabase/migrations anzuwenden:

supabase db push

Öffnen Sie im Supabase-Dashboard den Tabelleneditor. Dort sollte eine leere Tabelle transcription_logs angezeigt werden.

Leere Tabelle

Führen Sie abschließend den folgenden Befehl aus, um die Edge Function bereitzustellen:

supabase functions deploy --no-verify-jwt scribe-bot

Öffnen Sie im Supabase-Dashboard die Edge-Functions-Ansicht. Dort sollte die bereitgestellte Funktion scribe-bot angezeigt werden. Notieren Sie sich die Funktions-URL, da Sie sie später benötigen. Sie sollte etwa so aussehen: https://<project-ref>.functions.supabase.co/scribe-bot.

Bereitgestellte Edge Function

Webhook einrichten

Setzen Sie die Webhook-URL Ihres Bots auf https://<PROJECT_REFERENCE>.functions.supabase.co/telegram-bot (ersetzen Sie <...> durch die jeweiligen Werte). Senden Sie dazu einfach eine GET-Anfrage an die folgende URL, beispielsweise in Ihrem Browser:

https://api.telegram.org/bot<TELEGRAM_BOT_TOKEN>/setWebhook?url=https://<PROJECT_REFERENCE>.supabase.co/functions/v1/scribe-bot?secret=<FUNCTION_SECRET>

Beachten Sie, dass FUNCTION_SECRET dem Secret entspricht, das Sie in Ihrer .env-Datei festgelegt haben.

Webhook festlegen

Funktions-Secrets festlegen

Nachdem Sie alle Secrets lokal festgelegt haben, können Sie mit dem folgenden Befehl die Secrets in Ihrem Supabase-Projekt setzen:

supabase secrets set --env-file supabase/functions/.env

Bot testen

Testen Sie den Bot abschließend, indem Sie ihm eine Sprach-, Audio- oder Videonachricht senden.

Bot testen

Nachdem Sie das Transkript als Antwort sehen, wechseln Sie zurück zum Tabelleneditor im Supabase-Dashboard. Dort sollte eine neue Zeile in Ihrer Tabelle transcription_logs angezeigt werden.

Neue Zeile in der Tabelle

Nächste Schritte