一个功能齐全的框架,让你能使用Rust中的async/.await
语法轻松构建电报群机器人。将困难的工作交给框架,你只需关注业务逻辑。
123456789:blablabla
的认证凭证。TELOXIDE_TOKEN
数值初始化为你得到的认证凭证的哈希。# Unix-like
$ export TELOXIDE_TOKEN=<Your token here>
# Windows
$ set TELOXIDE_TOKEN=<Your token here>
# 如果你在用稳定分支
$ rustup update stable
$ rustup override set stable
# 如果你在用每日构建分支
$ rustup update nightly
$ rustup override set nightly
cargo new my_bot
, 进入文件目录将以下内容放进 Cargo.toml
:[dependencies]
teloxide = "0.3"
teloxide-macros = "0.3"
log = "0.4.8"
pretty_env_logger = "0.4.0"
tokio = { version = "0.2.11", features = ["rt-threaded", "macros"] }
这个机器人会在每次收到消息时掷出随机骰子:
(完整)
use teloxide::prelude::*;
#[tokio::main]
async fn main() {
teloxide::enable_logging!();
log::info!("机器人启动中...");
let bot = Bot::from_env();
teloxide::repl(bot, |message| async move {
message.answer_dice().send().await?;
ResponseResult::<()>::Ok(())
})
.await;
}
命令
命令是强类型的声明式, 和我们在serde-json中使用 structopt 和JSON结构定义CLI类似,下列机器人接受以下命令
/username <你的用户名>
/usernameandage <你的用户名> <你的年龄>
/help
(完整)
use teloxide::{utils::command::BotCommand, prelude::*};
#[derive(BotCommand)]
#[command(rename = "lowercase", description = "These commands are supported:")]
enum Command {
#[command(description = "帮助文本内容")]
Help,
#[command(description = "处理用户名")]
Username(String),
#[command(description = "处理用户名和年龄", parse_with = "split")]
UsernameAndAge { username: String, age: u8 },
}
async fn answer(cx: UpdateWithCx<Message>, command: Command) -> ResponseResult<()> {
match command {
Command::Help => cx.answer(Command::descriptions()).send().await?,
Command::Username(username) => {
cx.answer_str(format!("你的用户名是 @{}.", username)).await?
}
Command::UsernameAndAge { username, age } => {
cx.answer_str(format!("你的用户名是 @{} 年龄是 {}.", username, age)).await?
}
};
Ok(())
}
#[tokio::main]
async fn main() {
teloxide::enable_logging!();
log::info!("命令机器人启动中...");
let bot = Bot::from_env();
let bot_name: String = panic!("机器人名称");
teloxide::commands_repl(bot, bot_name, answer).await;
}
对话管理
对话是由一个枚举来描述的,其中每个变体都是可能的对话状态之一。还有子过渡函数,将对话从一个状态转到另一个状态,从而形成一个有限状态机。
下面是一个机器人,它会问你三个问题,然后把答案反馈给你。首先,让我们从一个枚举(我们对话的状态集合)开始。
(dialogue_bot/src/dialogue/mod.rs)
// 略去Imports...
#[derive(Transition, From)]
pub enum Dialogue {
Start(StartState),
ReceiveFullName(ReceiveFullNameState),
ReceiveAge(ReceiveAgeState),
ReceiveLocation(ReceiveLocationState),
}
impl Default for Dialogue {
fn default() -> Self {
Self::Start(StartState)
}
}
当用户向我们的机器人发送消息,而这样的对话还不存在时,Dialogue::default()
将会被调用,本例中为Dialogue::Start
。每收到一条消息,就会提取一个相关的对话,然后传递给一个相应的子过渡函数:
Dialogue::Start
Dialogue::ReceiveFullName
Dialogue::ReceiveAge
Dialogue::ReceiveLocation
所有这些子过渡函数都接受一个相应的状态("对话 "的许多变体之一)、上下文或文本消息。它们返回TransitionOut<Dialogue>
,例如从<你的状态类型>
到Dialogue
的映射。
最后,main
函数是这样的:
(dialogue_bot/src/main.rs)
// 略去Imports...
#[tokio::main]
async fn main() {
teloxide::enable_logging!();
log::info!("对话机器人启动中");
let bot = Bot::from_env();
teloxide::dialogues_repl(bot, |message, dialogue| async move {
handle_message(message, dialogue).await.expect("机器人出错!")
})
.await;
}
async fn handle_message(cx: UpdateWithCx<Message>, dialogue: Dialogue) -> TransitionOut<Dialogue> {
match cx.update.text_owned() {
None => {
cx.answer_str("Send me a text message.").await?;
next(dialogue)
}
Some(ans) => dialogue.react(cx, ans).await,
}
}