语音转录Telegram机器人
使用Supabase边缘函数中的TypeScript与Deno,构建一个能转录99种语言音频和视频消息的Telegram机器人。
简介
在本教程中,您将学习如何使用TypeScript和ElevenLabs的Scribe模型,通过语音转文本API构建一个支持99种语言的Telegram机器人,用于转录音频和视频消息。
要查看最终效果,您可以测试t.me/ElevenLabsScribeBot示例机器人。
前提条件
- 拥有ElevenLabs账号及API密钥
- 注册Supabase账号(可通过database.new免费注册)
- 在本地机器安装Supabase CLI
- 安装Deno运行时环境,可选在常用IDE中配置
- 拥有Telegram账号
设置
注册Telegram机器人
使用BotFather创建新的Telegram机器人。执行/newbot
命令并按照指引创建机器人。完成后您将获得机器人密钥,请妥善保存供后续步骤使用。
本地创建Supabase项目
安装Supabase CLI后,执行以下命令在本地创建新项目:
1supabase init
创建数据库表记录转录结果
接下来,创建一个新的数据库表来记录语音转录结果:
1supabase migrations new init
这将在 supabase/migrations
目录中创建一个新的迁移文件。打开该文件并添加以下SQL:
1234567891011121314CREATE 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;
创建Supabase边缘函数处理Telegram webhook请求
接下来,创建一个新的边缘函数来处理Telegram webhook请求:
1supabase functions new scribe-bot
如果您使用VS Code或Cursor,当CLI提示"Generate VS Code settings for Deno? [y/N]"时选择y
!
配置环境变量
在 supabase/functions
目录下,创建一个新的 .env
文件并添加以下变量:
12345678910# 在 https://elevenlabs.io/app/settings/api-keys 获取/创建API密钥ELEVENLABS_API_KEY=your_api_key# 从BotFather获取的机器人令牌TELEGRAM_BOT_TOKEN=your_bot_token# 您选择的随机密钥,用于保护函数安全FUNCTION_SECRET=random_secret
依赖项
本项目使用了以下依赖项:
- 开源的 grammY Framework 用于处理 Telegram webhook 请求
- @supabase/supabase-js 库用于与 Supabase 数据库交互
- ElevenLabs 的 JavaScript SDK 用于与语音转文本 API 交互
由于 Supabase 边缘函数使用 Deno 运行时,您无需安装这些依赖项,而是可以通过 npm:
前缀直接导入它们。
编写 Telegram 机器人代码
在新建的 scribe-bot/index.ts
文件中,添加以下代码:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128import { 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(`函数 "elevenlabs-scribe-bot" 已启动并运行!`)const elevenLabsClient = 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 elevenLabsClient.speechToText.convert({ file: sourceBlob, model_id: 'scribe_v1', tag_audio_events: false, }) transcript = scribeResult.text languageCode = scribeResult.language_code // 向用户回复转录文本 await bot.api.sendMessage(chatId, transcript, { reply_parameters: { message_id: messageId }, }) } catch (error) { errorMsg = error.message console.log(errorMsg) await bot.api.sendMessage(chatId, '抱歉,发生错误。请重试。', { reply_parameters: { message_id: messageId }, }) } // 将日志写入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 = `欢迎使用 ElevenLabs 转录机器人!我可以以超高准确度转录99种语言的语音! \n尝试发送或转发语音消息、视频或音频文件给我吧! \n[了解 Scribe 更多信息](https://elevenlabs.io/speech-to-text) 或 [构建您自己的机器人](https://elevenlabs.io/docs/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('未找到视频|音频|语音元数据。请重试。') } // 在后台运行转录任务 EdgeRuntime.waitUntil( scribe({ fileURL, fileType: fileMeta.mime_type!, duration: fileMeta.duration, chatId: ctx.chat.id, messageId: ctx.message?.message_id!, username: ctx.from?.username || '', }) ) // 立即回复用户告知已收到文件 return ctx.reply('已收到。正在转录中...') } catch (error) { console.error(error) return ctx.reply( '抱歉,获取文件时出错。请尝试发送较小的文件!' ) }})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('不允许访问', { status: 405 }) } return await handleUpdate(req) } catch (err) { console.error(err) }})
部署到 Supabase
如果尚未创建,请先在 database.new 创建一个新的 Supabase 账户,并将本地项目链接到您的 Supabase 账户:
1supabase link
应用数据库迁移
运行以下命令来应用 supabase/migrations
目录中的数据库迁移:
1supabase db push
在 Supabase 仪表板的表编辑器中,您应该能看到一个空的 transcription_logs
表。
最后,运行以下命令部署边缘函数:
1supabase functions deploy --no-verify-jwt scribe-bot
在 Supabase 仪表板的边缘函数视图中,您应该能看到已部署的 scribe-bot
函数。请记下函数 URL,稍后将会用到,其格式类似于 https://<project-ref>.functions.supabase.co/scribe-bot
。
设置 Webhook
将您的机器人 webhook URL 设置为 https://<PROJECT_REFERENCE>.functions.supabase.co/telegram-bot
(将 <...>
替换为实际值)。为此,可以向以下 URL 发送 GET 请求(例如在浏览器中):
1https://api.telegram.org/bot<TELEGRAM_BOT_TOKEN>/setWebhook?url=https://<PROJECT_REFERENCE>.supabase.co/functions/v1/scribe-bot?secret=<FUNCTION_SECRET>
请注意 FUNCTION_SECRET
是您在 .env
文件中设置的密钥。
设置函数密钥
现在您已在本地设置了所有密钥,可以运行以下命令在 Supabase 项目中设置这些密钥:
1supabase secrets set --env-file supabase/functions/.env
测试机器人
最后,您可以通过发送语音消息、音频或视频文件来测试机器人。
当您看到回复中的文字转录后,返回 Supabase 仪表板中的表编辑器,您应该在 transcription_logs
表中看到一个新行。