> This is a page from the ElevenLabs documentation. For a complete page index, fetch https://elevenlabs.io/docs/llms.txt. For the full documentation in a single file, fetch https://elevenlabs.io/docs/llms-full.txt.

# Bot de Telegram de transcripción

> **Note**
>
> **Guía práctica** · Parte de que ya has completado la [guía de inicio rápido de Voz a Texto](/docs/es/eleven-api/guides/cookbooks/speech-to-text) y cuentas con un token de bot de Telegram y una cuenta de Supabase.

## Introducción

En este tutorial aprenderás a crear un bot de Telegram que transcribe mensajes de audio y vídeo en más de 90 idiomas con TypeScript y el modelo Scribe de ElevenLabs a través de la API de voz a texto.

## Requisitos

* Una cuenta de ElevenLabs con una [clave de API](https://elevenlabs.io/app/settings/api-keys).
* Una cuenta de [Supabase](https://supabase.com) (puedes crear una cuenta gratuita a través de [database.new](https://database.new)).
* La [CLI de Supabase](https://supabase.com/docs/guides/local-development) instalada en tu equipo.
* El [runtime de Deno](https://docs.deno.com/runtime/getting_started/installation/) instalado en tu equipo y, opcionalmente, [configurado en tu IDE favorito](https://docs.deno.com/runtime/getting_started/setup_your_environment).
* Una cuenta de [Telegram](https://telegram.org).

## Configuración

### Registra un bot de Telegram

Usa [BotFather](https://t.me/BotFather) para crear un nuevo bot de Telegram. Ejecuta el comando `/newbot` y sigue las instrucciones para crear un bot. Al final, recibirás el token secreto de tu bot. Guárdalo de forma segura para el siguiente paso.

![BotFather](/docs/_fern-img/28aa856bb6ace1b49b82076a18f1e281a8a4f37bbb6cfc59c22d644564377248.webp)

### Crea un proyecto de Supabase localmente

Después de instalar la [CLI de Supabase](https://supabase.com/docs/guides/local-development), ejecuta el siguiente comando para crear un nuevo proyecto de Supabase localmente:

```bash
supabase init
```

### Crea una tabla de base de datos para registrar los resultados de transcripción

A continuación, crea una tabla de base de datos para registrar los resultados de transcripción:

```bash
supabase migrations new init
```

Esto creará un nuevo archivo de migración en el directorio `supabase/migrations`. Abre el archivo y añade el siguiente SQL:

**`supabase/migrations/init.sql`**

```sql 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;
```

### Crea una Edge Function de Supabase para gestionar las solicitudes de webhook de Telegram

A continuación, crea una Edge Function para gestionar las solicitudes de webhook de Telegram:

```bash
supabase functions new scribe-bot
```

Si usas VS Code o Cursor, selecciona `y` cuando la CLI te pregunte "Generate VS Code settings for Deno? \[y/N]".

### Configura las variables de entorno

En el directorio `supabase/functions`, crea un archivo `.env` y añade las siguientes variables:

**`supabase/functions/.env`**

```env 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
```

### Dependencias

El proyecto usa varias dependencias:

* El framework de código abierto [grammY](https://grammy.dev/) para gestionar las solicitudes de webhook de Telegram.
* La biblioteca [@supabase/supabase-js](https://supabase.com/docs/reference/javascript) para interactuar con la base de datos de Supabase.
* El [SDK de JavaScript](/docs/es/eleven-api/quickstart) de ElevenLabs para interactuar con la API de voz a texto.

Como las Edge Functions de Supabase usan el [runtime de Deno](https://deno.land/), no necesitas instalar las dependencias; puedes [importarlas](https://docs.deno.com/examples/npm/) mediante el prefijo `npm:`.

## Programa el bot de Telegram

En el archivo `scribe-bot/index.ts` que acabas de crear, añade el siguiente código:

**`supabase/functions/scribe-bot/index.ts`**

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

### Análisis detallado del código

Hay algunos aspectos importantes que conviene destacar sobre el código. Vamos a repasarlo paso a paso.

#### Gestión de la solicitud entrante

Para gestionar la solicitud entrante, usa el controlador `Deno.serve`. El controlador comprueba si la solicitud tiene el secreto correcto y, después, la pasa a la función `handleUpdate`.

```ts {1,6,10}
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);
  }
});
```

#### Gestiona mensajes de voz, audio y vídeo

El framework grammY ofrece una forma práctica de [filtrar](https://grammy.dev/guide/filter-queries#combining-multiple-queries) tipos de mensajes específicos. En este caso, el bot escucha mensajes de voz, audio y vídeo.

Mediante el contexto de la solicitud, el bot extrae los metadatos del archivo y usa `EdgeRuntime.waitUntil` de las [tareas en segundo plano de Supabase](https://supabase.com/docs/guides/functions/background-tasks) para ejecutar la transcripción en segundo plano.

Así puedes ofrecer una respuesta inmediata al usuario y gestionar la transcripción del archivo en segundo plano.

```ts {1,3,12,24}
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!'
    );
  }
});
```

#### Transcripción con la API de ElevenLabs

Por último, en el proceso en segundo plano, el bot usa el SDK de JavaScript de ElevenLabs para transcribir el archivo. Cuando se completa la transcripción, el bot responde al usuario con la transcripción y escribe una entrada de registro en la base de datos de Supabase con [supabase-js](https://supabase.com/docs/reference/javascript).

```ts {29-38,43-46,54-65}
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 });
}
```

## Despliega en Supabase

Si aún no lo has hecho, crea una cuenta de Supabase en [database.new](https://database.new) y vincula el proyecto local a tu cuenta de Supabase:

```bash
supabase link
```

### Aplica las migraciones de la base de datos

Ejecuta el siguiente comando para aplicar las migraciones de la base de datos desde el directorio `supabase/migrations`:

```bash
supabase db push
```

Ve al [editor de tablas](https://supabase.com/dashboard/project/_/editor) en tu panel de Supabase; deberías ver una tabla `transcription_logs` vacía.

![Tabla vacía](/docs/_fern-img/c1493deafcd6c53712dcb4fa7a44b3253b571ade0bb2e9c2b4fdb090088b0695.webp)

Por último, ejecuta el siguiente comando para desplegar la Edge Function:

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

Ve a la [vista de Edge Functions](https://supabase.com/dashboard/project/_/functions) en tu panel de Supabase; deberías ver la función `scribe-bot` desplegada. Anota la URL de la función, ya que la necesitarás más adelante. Debería tener un aspecto similar a `https://<project-ref>.functions.supabase.co/scribe-bot`.

![Edge Function desplegada](/docs/_fern-img/d12141a51a09563d625b379201de844800e0c7dcfa9e6c7f21484cbca7ff37cf.webp)

### Configura el webhook

Configura la URL de webhook de tu bot como `https://<PROJECT_REFERENCE>.functions.supabase.co/telegram-bot` (sustituye `<...>` por los valores correspondientes). Para ello, solo tienes que realizar una solicitud GET a la siguiente URL (por ejemplo, en tu navegador):

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

Ten en cuenta que `FUNCTION_SECRET` es el secreto que configuraste en tu archivo `.env`.

![Configurar webhook](/docs/_fern-img/ac72044bd3df138da3d8a0df09e8413e8a9fd8a5c3156a525d6b13664854482f.webp)

### Configura los secretos de la función

Ahora que ya tienes todos los secretos configurados localmente, puedes ejecutar el siguiente comando para configurar los secretos en tu proyecto de Supabase:

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

## Prueba el bot

Por último, puedes probar el bot enviándole un mensaje de voz, un archivo de audio o un vídeo.

![Probar el bot](/docs/_fern-img/a42c784e6324f15575e0299bafe02a253a2bdc57f2ad5844f83e5b64297780e7.webp)

Cuando veas la transcripción como respuesta, vuelve al editor de tablas de tu panel de Supabase; deberías ver una nueva fila en tu tabla `transcription_logs`.

![Nueva fila en la tabla](/docs/_fern-img/25c008858276b7cd4af6f8e72473891176bb9e2ce62e7002af353a2dac205e06.webp)

## Próximos pasos

#### [Referencia de la API](/docs/es/api-reference/speech-to-text)

Referencia y parámetros completos de la API de Voz a Texto.

#### [Integración con Twilio](/docs/es/eleven-api/guides/how-to/text-to-speech/twilio)

Integra TTS de ElevenLabs con Twilio para aplicaciones de voz telefónicas.