Skip to navigation

Telegram-bot voor transcriptie

Bouw een Telegram-bot die audio- en videoberichten in meer dan 90 talen transcribeert met TypeScript met Deno in Supabase Edge Functions.

Handleiding · Er wordt van uitgegaan dat je de Speech to Text quickstart hebt voltooid en een Telegram-bottoken en Supabase-account hebt.

Inleiding

In deze tutorial leer je hoe je een Telegram-bot bouwt die audio- en videoberichten in meer dan 90 talen transcribeert met TypeScript en het ElevenLabs Scribe-model via de speech-to-text-API.

Vereisten

Instellen

Een Telegram-bot registreren

Gebruik BotFather om een nieuwe Telegram-bot te maken. Voer de opdracht /newbot uit en volg de instructies om een nieuwe bot te maken. Aan het einde ontvang je je geheime bottoken. Bewaar deze veilig voor de volgende stap.

BotFather

Lokaal een Supabase-project maken

Voer na het installeren van de Supabase CLI de volgende opdracht uit om lokaal een nieuw Supabase-project te maken:

supabase init

Een databasetabel maken om de transcriptieresultaten te loggen

Maak vervolgens een nieuwe databasetabel om de transcriptieresultaten te loggen:

supabase migrations new init

Hiermee wordt een nieuw migratiebestand gemaakt in de directory supabase/migrations. Open het bestand en voeg de volgende SQL toe:

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;

Een Supabase Edge Function maken om Telegram-webhookrequests af te handelen

Maak vervolgens een nieuwe Edge Function om Telegram-webhookrequests af te handelen:

supabase functions new scribe-bot

Als je VS Code of Cursor gebruikt, selecteer je y wanneer de CLI vraagt: “Generate VS Code settings for Deno? [y/N]”!

De omgevingsvariabelen instellen

Maak in de directory supabase/functions een nieuw .env-bestand en voeg de volgende variabelen toe:

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

Afhankelijkheden

Het project gebruikt enkele afhankelijkheden:

Omdat Supabase Edge Function de Deno-runtime gebruikt, hoef je de afhankelijkheden niet te installeren. Je kunt ze via het voorvoegsel npm: importeren.

De Telegram-bot programmeren

Voeg de volgende code toe aan je nieuw gemaakte bestand scribe-bot/index.ts:

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);
}
});

Toelichting op de code

Er zijn een paar zaken in de code die het vermelden waard zijn. We lopen ze stap voor stap door.

1

De inkomende request afhandelen

Gebruik de handler Deno.serve om de inkomende request af te handelen. De handler controleert of de request het juiste geheim bevat en geeft de request vervolgens door aan de functie 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

Spraak-, audio- en videoberichten afhandelen

Het grammY-framework biedt een handige manier om op specifieke berichttypen te filteren. In dit geval luistert de bot naar spraak-, audio- en videoberichten.

Met de requestcontext haalt de bot de bestandsmetadata op en gebruikt vervolgens Supabase Background Tasks EdgeRuntime.waitUntil om de transcriptie op de achtergrond uit te voeren.

Zo kun je de gebruiker direct een reactie geven en de transcriptie van het bestand op de achtergrond afhandelen.

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

Transcriptie met de ElevenLabs API

Ten slotte gebruikt de bot in de achtergrondworker de ElevenLabs JavaScript SDK om het bestand te transcriberen. Zodra de transcriptie is voltooid, antwoordt de bot de gebruiker met het transcript en schrijft deze een logvermelding naar de Supabase-database met supabase-js.

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 });
}

Implementeren in Supabase

Maak, als je dat nog niet hebt gedaan, een nieuw Supabase-account aan op database.new en koppel het lokale project aan je Supabase-account:

supabase link

De databasemigraties toepassen

Voer de volgende opdracht uit om de databasemigraties uit de directory supabase/migrations toe te passen:

supabase db push

Ga naar de tabeleditor in je Supabase-dashboard. Daar zou je een lege tabel transcription_logs moeten zien.

Lege tabel

Voer ten slotte de volgende opdracht uit om de Edge Function te implementeren:

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

Ga naar de weergave Edge Functions in je Supabase-dashboard. Daar zou je de geïmplementeerde functie scribe-bot moeten zien. Noteer de functie-URL, want je hebt deze later nodig. Deze ziet er ongeveer zo uit: https://<project-ref>.functions.supabase.co/scribe-bot.

Edge Function geïmplementeerd

De webhook instellen

Stel de webhook-URL van je bot in op https://<PROJECT_REFERENCE>.functions.supabase.co/telegram-bot (vervang <...> door de juiste waarden). Voer hiervoor bijvoorbeeld in je browser een GET-request uit naar de volgende URL:

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

FUNCTION_SECRET is het geheim dat je in je .env-bestand hebt ingesteld.

Webhook instellen

De functiegeheimen instellen

Nu je alle geheimen lokaal hebt ingesteld, kun je de volgende opdracht uitvoeren om de geheimen in je Supabase-project in te stellen:

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

De bot testen

Ten slotte kun je de bot testen door er een spraakbericht, audiobestand of videobestand naartoe te sturen.

De bot testen

Nadat je het transcript als antwoord hebt gezien, ga je terug naar de tabeleditor in het Supabase-dashboard. Daar zou je een nieuwe rij in de tabel transcription_logs moeten zien.

Nieuwe rij in tabel

Volgende stappen