本章将开始与 Telegram 机器人进行交互。首先从最简单的文本消息开始。
本章代码在02/处理文本信息分支。
Telegram 向 Webhook 推送了什么数据
开始之前,我们必须清楚,当有新消息的时候,Telegram 向 Webhook 推送了什么数据。
pub async fn hook(msg: String) -> String {
tracing::debug!("{}", msg);
msg
}
重新运行我们的程序,然后向机器人发送一条信息。之后,我们可以看到控制台打印出了类以下面的内容:
完善数据结构
对照上面的打印可以发现,我们定义的数据结构少了很多字段需要把它们补齐:
#[derive(Deserialize, Debug)]
pub struct Update {
pub update_id: u64,
pub message: Message,
}
#[derive(Deserialize, Debug)]
pub struct Message {
pub message_id: u64,
pub from: User,
pub chat: Chat,
pub date: u64,
pub text: String,
}
#[derive(Deserialize, Debug)]
pub struct User {
pub id: u64,
pub is_bot: bool,
pub first_name: String,
pub username: String,
pub language_code: String,
}
#[derive(Deserialize, Debug)]
pub struct Chat {
pub id: u64,
pub first_name: String,
pub username: String,
#[serde(rename(deserialize = "type"))]
pub types: String,
}
即便我们对数据结构进行补充完善,但它还不是完整的,只是从目前我们的需求来看,它是符合要求的。完整的数据结构请查看官方文档。
将 handler 修改回之前接收 JSON 并反序列化为结构体:
pub async fn hook(Json(payload): Json<Update>, Extension(state): Extension<AppState>) -> String {
let msg = format!("{:?}", payload);
tracing::debug!("received: {}", msg);
msg
}
回复文本消息
现在我们实现一个echo
程序:将用户输入的信息原样返回,为了作区分,我们加上ECHO:
字样。
要回复文本信息,我们需要调用 Telegram 的sendMessage
API,它有两个必填项:
-
chat_id
:你要回复的信息的 ID。在我们的Chat
中,有个id
字段,这个chat_id
就是从这个字段中取值。 -
text
:你要回复的内容。
TextMessage
结构体
针对上面的分析,我们定义一个 TextMessage
,它位于 src/types/request.rs
:
#[derive(Serialize, Debug)]
pub struct TextMessage {
pub chat_id: u64,
pub text: String,
}
发送 HTTP 请求来调用 Telegram 的 API
sendMessage
的完整请求地址是https://api.telegram.org/bot{你的TOKEN}/sendMessage
:
// 回复信息
let send_data = request::TextMessage {
chat_id: payload.message.chat.id,
text: format!("ECHO: {}", payload.message.text),
};
let api_addr = format!(
"https://api.telegram.org/bot{}/{}",
state.bot.token.clone(),
"sendMessage"
);
let res = reqwest::Client::new()
.post(&api_addr)
.form(&send_data)
.send()
.await
.unwrap()
.text()
.await
.unwrap();
tracing::debug!("sendMessage: {}", &res);
配置文件与状态共享
我们把机器人 Token 放在了配置文件.env
中:
WEB.ADDR=127.0.0.1:9527
TG_BOT.TOKEN=你的TG机器人TOKEN
TG_BOT.WEBHOOK=https://tg.axum.rs/
对应的结构体及方法:
// src/config.rs
#[derive(Deserialize)]
pub struct WebConfig {
pub addr: String,
}
#[derive(Deserialize, Clone)]
pub struct TgBotConfig {
pub token: String,
pub webhook: String,
}
#[derive(Deserialize)]
pub struct Config {
pub web: WebConfig,
pub tg_bot: TgBotConfig,
}
impl Config {
pub fn from_env() -> Result<Self, config::ConfigError> {
let mut cfg = config::Config::new();
cfg.merge(config::Environment::new())?;
cfg.try_into()
}
}
为了共享这个 Token,我们需要定义 AppState
并将 Token 放到其中:
// src/model.rs
#[derive(Clone)]
pub struct AppState {
pub bot: config::TgBotConfig,
}
最后,在路由定义的时候,加入这个共享状态:
// src/main.rs
dotenv().ok();
let cfg = config::Config::from_env().expect("初始化配置失败");
let app = Router::new()
.route("/", routing::post(handler::hook).get(handler::index))
.layer(AddExtensionLayer::new(model::AppState {
bot: cfg.tg_bot.clone(),
}));
测试
现在,我们给机器人发送一条消息,不但收到了回复,同时控制台也打印出来了 Telegram API 的返回信息: