Bot Telegram de transcription

Créez un bot Telegram qui transcrit des messages audio et vidéo dans plus de 90 langues avec TypeScript et Deno dans les Supabase Edge Functions.

Guide pratique · Suppose que vous avez suivi le démarrage rapide de Speech to Text et que vous disposez d’un jeton de bot Telegram ainsi que d’un compte Supabase.

Introduction

Dans ce tutoriel, vous apprendrez à créer un bot Telegram qui transcrit des messages audio et vidéo dans plus de 90 langues avec TypeScript et le modèle Scribe d’ElevenLabs via l’API Speech to Text.

Prérequis

Configuration

Enregistrer un bot Telegram

Utilisez BotFather pour créer un bot Telegram. Exécutez la commande /newbot et suivez les instructions pour créer votre bot. À la fin, vous recevrez le jeton secret de votre bot. Notez-le en lieu sûr pour l’étape suivante.

BotFather

Créer un projet Supabase localement

Après avoir installé la CLI Supabase, exécutez la commande suivante pour créer un projet Supabase localement :

supabase init

Créer une table de base de données pour enregistrer les résultats de transcription

Créez ensuite une table de base de données pour enregistrer les résultats de transcription :

supabase migrations new init

Cette commande crée un fichier de migration dans le répertoire supabase/migrations. Ouvrez ce fichier et ajoutez le SQL suivant :

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;

Créer une Supabase Edge Function pour traiter les requêtes webhook de Telegram

Créez ensuite une Edge Function pour traiter les requêtes webhook de Telegram :

supabase functions new scribe-bot

Si vous utilisez VS Code ou Cursor, sélectionnez y lorsque la CLI affiche « Generate VS Code settings for Deno? [y/N] ».

Configurer les variables d’environnement

Dans le répertoire supabase/functions, créez un fichier .env et ajoutez les variables suivantes :

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

Dépendances

Le projet utilise plusieurs dépendances :

  • Le framework open source grammY pour traiter les requêtes webhook de Telegram.
  • La bibliothèque @supabase/supabase-js pour interagir avec la base de données Supabase.
  • Le SDK JavaScript d’ElevenLabs pour interagir avec l’API Speech to Text.

Comme les Supabase Edge Functions utilisent le runtime Deno, vous n’avez pas besoin d’installer les dépendances. Vous pouvez les importer via le préfixe npm:.

Coder le bot Telegram

Dans le fichier scribe-bot/index.ts que vous venez de créer, ajoutez le code suivant :

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

Analyse approfondie du code

Quelques éléments du code méritent d’être soulignés. Examinons-les étape par étape.

1

Traiter la requête entrante

Pour traiter la requête entrante, utilisez le gestionnaire Deno.serve. Il vérifie que la requête contient le bon secret, puis la transmet à la fonction 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

Traiter les messages vocaux, audio et vidéo

Le framework grammY offre un moyen pratique de filtrer des types de messages spécifiques. Dans ce cas, le bot écoute les messages vocaux, audio et vidéo.

À l’aide du contexte de la requête, le bot extrait les métadonnées du fichier, puis utilise EdgeRuntime.waitUntil de Supabase Background Tasks pour exécuter la transcription en arrière-plan.

Vous pouvez ainsi répondre immédiatement à l’utilisateur tout en traitant la transcription du fichier en arrière-plan.

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

Transcription avec l’API ElevenLabs

Enfin, dans le worker en arrière-plan, le bot utilise le SDK JavaScript d’ElevenLabs pour transcrire le fichier. Une fois la transcription terminée, il répond à l’utilisateur avec la transcription et écrit une entrée de journal dans la base de données Supabase avec 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 });
}

Déployer sur Supabase

Si ce n’est pas déjà fait, créez un compte Supabase sur database.new, puis associez le projet local à votre compte Supabase :

supabase link

Appliquer les migrations de la base de données

Exécutez la commande suivante pour appliquer les migrations de base de données du répertoire supabase/migrations :

supabase db push

Accédez à l’éditeur de tables dans votre Dashboard Supabase. Vous devriez voir une table transcription_logs vide.

Table vide

Enfin, exécutez la commande suivante pour déployer l’Edge Function :

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

Accédez à la vue Edge Functions dans votre Dashboard Supabase. Vous devriez voir la fonction scribe-bot déployée. Notez l’URL de la fonction, car vous en aurez besoin plus tard. Elle doit ressembler à https://<project-ref>.functions.supabase.co/scribe-bot.

Edge Function déployée

Configurer le webhook

Définissez l’URL du webhook de votre bot sur https://<PROJECT_REFERENCE>.functions.supabase.co/telegram-bot (remplacez <...> par les valeurs correspondantes). Pour ce faire, exécutez simplement une requête GET vers l’URL suivante, par exemple dans votre navigateur :

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

Notez que FUNCTION_SECRET correspond au secret que vous avez défini dans votre fichier .env.

Configurer le webhook

Définir les secrets de la fonction

Maintenant que tous vos secrets sont configurés localement, vous pouvez exécuter la commande suivante pour les définir dans votre projet Supabase :

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

Tester le bot

Vous pouvez enfin tester le bot en lui envoyant un message vocal ou un fichier audio ou vidéo.

Tester le bot

Une fois la transcription reçue en réponse, revenez à l’éditeur de tables de votre Dashboard Supabase. Vous devriez voir une nouvelle ligne dans votre table transcription_logs.

Nouvelle ligne dans la table

Étapes suivantes