内容介绍
本专题将带你使用axum实现一个 Webhook 形式的 Telegram 机器人。webhook
Telegram 机器人支持两种方式:轮询和 Webhook。为了节约资源我们将使用 Webhook 的方式开发 Telegram 机器人。处理文本消息
本章将开始与 Telegram 机器人进行交互。首先从最简单的文本消息开始。处理指令
本章我们将学习如何处理 Telegram 的“指令”(command)。开始之前,我们对之前的代码进行必要的封装。发送图片
本章继续完善我们的机器人。收到用户的`/logo`指令,我们需要把我们的 LOGO 图片发送给用户。让我们来看看如何让 Telegram 机器人发送图片信息。发送Markdown
Telegram 还支持 Markdown 和 HTML 类型的文本消息。本章我们将实现`/help`指令,它会将帮助信息以 Markdown 格式发送给用户。总结
本专题带你实现了一个简单的 Telegram 机器人。我们实现的功能是很简单的,其实 Telegram 支持多种消息
处理指令
本章我们将学习如何处理 Telegram 的“指令”(command)。开始之前,我们对之前的代码进行必要的封装。
本章代码在03/处理指令分支。
封装错误类型
先从封装自定义错误开始:
// src/error.rs
#[derive(Debug)]
pub enum AppErrorType {
HttpError,
SerdeError,
}
#[derive(Debug)]
pub struct AppError {
pub message: Option<String>,
pub cause: Option<String>,
pub error_type: AppErrorType,
}
// impl AppError ...
接着定义自己的Result
:
// src/main.rs
type Result<T> = std::result::Result<T, error::AppError>;
没什么需要多说的,我们每个专题都要这些操作。
封装 Telegram API 请求
现在我们要将发送请求到 Telegram API 的部分封装成一个函数:
// src/bot.rs
async fn invoke_api<T: Serialize>(data: &T, method: &str, token: &str) -> Result<Response> {
let api_addr = format!("https://api.telegram.org/bot{}/{}", token, method);
let res = reqwest::Client::new()
.post(&api_addr)
.form(data)
.send()
.await
.map_err(AppError::from)?
.text()
.await
.map_err(AppError::from)?;
let res = serde_json::from_str(&res).map_err(AppError::from)?;
Ok(res)
}
pub async fn send_text_message(token: &str, chat_id: u64, text: String) -> Result<Response> {
let data = request::TextMessage { chat_id, text };
invoke_api(&data, "sendMessage", token).await
}
#[derive(Deserialize, Debug)]
pub struct Response {
pub ok: bool,
pub result: Option<Message>,
}
#[derive(Deserialize, Debug)]
pub struct Message {
pub message_id: u64,
pub from: Option<User>,
pub chat: Chat,
pub date: u64,
pub text: Option<String>,
}
#[derive(Deserialize, Debug)]
pub struct User {
pub id: u64,
pub is_bot: bool,
pub first_name: Option<String>,
pub username: Option<String>,
pub language_code: Option<String>,
}
#[derive(Deserialize, Debug)]
pub struct Chat {
pub id: u64,
pub first_name: Option<String>,
pub username: Option<String>,
#[serde(rename(deserialize = "type"))]
pub types: String,
}
Telegram API 中,每个数据结构中的必填项并不多,所以我们需要为那些可选字段加上Option<>
,以避免发生无法解析的错误。
关于 Telegram API 中各数据结构是否必填,请参见官方文档。文档中,所有标有
Optional
的,均为可选的。
关于 Telegram API 中各数据结构是否必填,请参见官方文档。文档中,所有标有Optional
的,均为可选的。
处理指令
指令的格式
在 Telegram 中,指令都是以/
开头,后面是有效的标识符,比如:/start
、/help
等。如内容介绍部分所说,我们最终要实现三个指令:
-
/website
:访问 axum 中文网官方 -
/logo
:查看 axum 中文网 LOGO -
/help
:显示帮助信息
/website
:访问 axum 中文网官方
/logo
:查看 axum 中文网 LOGO
/help
:显示帮助信息
/website
指令
指令其实也是用户发送过来的文本消息,所以我们只要判断用户发送的文本消息是不是/website
即可。
识别出指令
在handler::hook()
中,我们需要对用户输入的内容进行处理:
通过对用户输入的内容进行判断,如果是 /website
,则从 command::website()
中获取要返回给用户的内容,否则,返回我们之前写的echo
的内容。
然后将内容通过 Telegram API 发送给用户。
command::website()
很简单,只是返回我们的网站的地址:
pub fn website() -> String {
"https://axum.rs".to_string()
}