koto's Site 🤪 About me Search Post Tags Archives Friendly sites Switch theme Settings

在 Vercel 上部署 Telegram Bot

如何在 Vercel 平台上部署一个 Serverless Telegram Bot

2023-10-28 20:35+0800 About 3 mins

 Tagged with:

1. 申请 Bot

先在 BotFather 申请一个 Bot,输入 bot 的显示名称和 ID 拿到 Bot Token.

Bot Token 大概长这样子:

66XXXXXX88:AAAAAQQQeeefffpppaaalllnnnppp111ooo

拿到新鲜的 Bot Token 之后取出备用。

2. 设定 Webhook

Telegram bot 有两种获取讯息的方式:

Polling(轮询)模式。即 bot 每隔一段时间向 Telegram 服务器拉取讯息。

Webhook 模式。即 Telegram 服务器在收取到讯息的时候向指定的 Webhook 地址发送请求。

由于 Vercel Serverless Function 的特殊性质,我们只能使用 Webhook。 或者说我太低能不会拿 Vercel Serverless Function 写 Polling 模式的 Serverless Bot

为了方便调试,我们需要在本地设定一个 Cloudflare Tunnel,你可以查看这篇博文学到更多:设定 Cloudflare Tunnel

通过 Telegram API 将 Bot 设定为 Webhook 模式:

curl "https://api.telegram.org/bot{token}/setWebhook?url={webhook}"

此时我们假定我们已经设定的 Tunnel 跑在 bot-test.ooze.gq,则应该执行

curl "https://api.telegram.org/bot66XXXXXX88:AAAAAQQQeeefffpppaaalllnnnppp111ooo/setWebhook?url=https://bot-test.ooze.gq/api/webhook"

如果你收到类似 {"ok":true,"result":true,"description":"Webhook was set"} 的返回结果,则为设置成功。

3. 开发

3.1 回声机器人

执行 mkdir Vercel-Telegram-Bot && cd Vercel-Telegram-Bot 创建并切换到工作目录.

执行 pnpm i node-telegram-bot-api 安装必要的依赖.

执行 mkdir api && touch webhook.js 创建 webhook api.

选择你喜欢的编辑器开始编辑 webhook.js ,下面是一个简单的回声机器人的示范:

const TelegramBot = require("node-telegram-bot-api");

module.exports = async (request, response) => {
    try {
        const bot = new TelegramBot(process.env.TELEGRAM_TOKEN);
        const { body } = request;
        const { chat: { id }, text } = body.message;
        
        // Echo Bot Example:
        if (body.message.chat.type === "private" && body.message) {
            await bot.sendMessage(id, `You have sent **"${text}"**!`, { parse_mode: "Markdown" });
        }
    }
    catch (error) {
        console.error("Error to sending message, " + error.toString());
    }
    response.send("OK");
};

接下来使用 TELEGRAM_TOKEN="66XXXXXX88:AAAAAQQQeeefffpppaaalllnnnppp111ooo" vercel dev 启动开发服务器

如果你以上操作的执行正确,向你的 Bot 发送 /start,此时你应该就会收到 You have sent "/start"!

3.2 接入 hitokoto API 示例

由于是 bot, 可能会被用在群里,我们添加一段指令:

const username = process.env.TELEGRAM_BOT_USERNAME || `@${await bot.getMe().username}`;

这回优先从环境变量 TELEGRAM_BOT_USERNAME 中获取 Bot 的 username, 如果没有则自动获取。

判断 /hitokoto 指令:

if (
	text.toString().startsWith("/hitokoto ") ||
	text.toString() === "/hitokoto" ||
	text.toString().startsWith(`/hitokoto@${username}`)
) {
    // do your job
}

接下来使用 fetch 从 hitokoto 获取并处理数据:

let hitokotoText = await fetch(`https://v1.hitokoto.cn/?t=${new Date().getTime()}`)
	.then(res => res.json())
	.then(res => {
		return `「 ${res.hitokoto} 」 \n` +
				`來自${res.from_who ? res.from_who + "的" : ""}「 ${res.from} 」`;
	})
	.catch(() => {
		return "API 或者 Bot 爆炸了捏😋"
    })

最后发送:

await bot.sendMessage(id, hitokotoText, { parse_mode: "Markdown" });

汇总如下:

const TelegramBot = require("node-telegram-bot-api");

module.exports = async (request, response) => {
    try {
        const bot = new TelegramBot(process.env.TELEGRAM_TOKEN);
        const username = process.env.TELEGRAM_BOT_USERNAME || `@${await bot.getMe().username}`;
        const { body } = request;
        const { chat: { id }, text } = body.message;

        console.log(text)

        // Hitokoto Bot Example:
        if (
            text.toString().startsWith("/hitokoto ") ||
            text.toString() === "/hitokoto" ||
            text.toString().startsWith(`/hitokoto@${username}`)
        ) {
            let hitokotoText = await fetch(`https://v1.hitokoto.cn/?t=${new Date().getTime()}`)
                .then(res => res.json())
                .then(res => {
                    return `「 ${res.hitokoto} 」 \n` +
                           `來自${res.from_who ? res.from_who + "的" : ""}「 ${res.from} 」`;
                })
                .catch(() => {
                    return "API 或者 Bot 爆炸了捏😋"
                })
            await bot.sendMessage(id, hitokotoText, { parse_mode: "Markdown" })
        }
    }
    catch (error) {
        console.error("Error to sending message, " + error.toString());
    }
    response.send("OK");
};

4. 部署

我懒得写了,你看着办吧