teloxide/teloxide
GitHub: teloxide/teloxide
teloxide:一个功能齐全的 Rust 框架,用于轻松构建 Telegram 机器人。
Stars: 4136 | Forks: 298
## 亮点
- **声明式设计。** `teloxide` 基于 [`dptree`],这是一种函数式[责任链]模式,允许您以高度声明式和可扩展的方式表达消息处理管道。
- **功能丰富。** 您可以使用长轮询和 Webhook,配置底层的 HTTPS 客户端,设置 Telegram API 服务器的自定义 URL,实现优雅关闭等等。
- **简单的对话系统。** 我们的对话子系统简单易用,并且与对话的存储方式/位置无关。例如,您只需更改一行代码即可实现[持久化]。开箱即用的存储包括 [Redis] 和 [Sqlite]。
- **强类型命令。** 将机器人命令定义为 `enum`,`teloxide` 会自动解析它们——就像在 [`serde-json`] 中解析 JSON 结构和在 [`structopt`] 中解析命令行参数一样。
## 环境设置
1. [下载 Rust](http://rustup.rs/)。
2. 使用 [@Botfather](https://t.me/botfather) 创建一个新机器人以获取格式为 `123456789:blablabla` 的令牌。
3. 初始化 `TELOXIDE_TOKEN` 环境变量为您的令牌:
```
# 类Unix
$ export TELOXIDE_TOKEN=
# Windows 命令行
$ set TELOXIDE_TOKEN=
# Windows PowerShell
$ $env:TELOXIDE_TOKEN=
```
4. 确保您的 Rust 编译器是最新的(`teloxide` 当前要求 rustc 版本至少为 1.82):
```
# 如果您正在使用 stable
$ rustup update stable
$ rustup override set stable
# 如果您正在使用 nightly
$ rustup update nightly
$ rustup override set nightly
```
5. 运行 `cargo new my_bot`,进入目录并将以下行添加到您的 `Cargo.toml` 文件中:
```
[dependencies]
teloxide = { version = "0.17.0", features = ["macros"] }
log = "0.4"
pretty_env_logger = "0.5"
tokio = { version = "1.39", features = ["rt-multi-thread", "macros"] }
```
_注意:如果主分支中有尚未发布的新功能,您可以通过如下方式拉取 Git 仓库:_
```
teloxide = { git = "https://github.com/teloxide/teloxide.git", features = ["macros"] }
```
## API 概览
### 骰子机器人
这个机器人会对每条收到的消息回复一个骰子:
[[`examples/throw_dice.rs`](crates/teloxide/examples/throw_dice.rs)]
```
use teloxide::prelude::*;
#[tokio::main]
async fn main() {
pretty_env_logger::init();
log::info!("Starting throw dice bot...");
let bot = Bot::from_env();
teloxide::repl(bot, |bot: Bot, msg: Message| async move {
bot.send_dice(msg.chat.id).await?;
Ok(())
})
.await;
}
```
### 命令
命令是强类型且声明式定义的,类似于我们使用 [structopt] 定义 CLI 和在 [serde-json] 中定义 JSON 结构的方式。以下机器人接受这些命令:
- `/username <您的用户名>`
- `/usernameandage <您的用户名> <您的年龄>`
- `/help`
[[`examples/command.rs`](crates/teloxide/examples/command.rs)]
```
use teloxide::{prelude::*, utils::command::BotCommands};
#[tokio::main]
async fn main() {
pretty_env_logger::init();
log::info!("Starting command bot...");
let bot = Bot::from_env();
Command::repl(bot, answer).await;
}
#[derive(BotCommands, Clone)]
#[command(rename_rule = "lowercase", description = "These commands are supported:")]
enum Command {
#[command(description = "display this text.")]
Help,
#[command(description = "handle a username.")]
Username(String),
#[command(description = "handle a username and an age.", parse_with = "split")]
UsernameAndAge { username: String, age: u8 },
}
async fn answer(bot: Bot, msg: Message, cmd: Command) -> ResponseResult<()> {
match cmd {
Command::Help => bot.send_message(msg.chat.id, Command::descriptions().to_string()).await?,
Command::Username(username) => {
bot.send_message(msg.chat.id, format!("Your username is @{username}.")).await?
}
Command::UsernameAndAge { username, age } => {
bot.send_message(msg.chat.id, format!("Your username is @{username} and age is {age}."))
.await?
}
};
Ok(())
}
```
### 对话管理
对话通常由一个枚举来描述,枚举的每个变体代表对话的一种可能状态。此外还有_状态处理函数_,它们可以将对话从一个状态转换为另一个状态,从而形成一个[有限状态机]。
下面是一个向您询问三个问题然后将答案发回的机器人:
[[`examples/dialogue.rs`](crates/teloxide/examples/dialogue.rs)]
```
use teloxide::{dispatching::dialogue::InMemStorage, prelude::*};
type MyDialogue = Dialogue>;
type HandlerResult = Result<(), Box>;
#[derive(Clone, Default)]
pub enum State {
#[default]
Start,
ReceiveFullName,
ReceiveAge {
full_name: String,
},
ReceiveLocation {
full_name: String,
age: u8,
},
}
#[tokio::main]
async fn main() {
pretty_env_logger::init();
log::info!("Starting dialogue bot...");
let bot = Bot::from_env();
Dispatcher::builder(
bot,
Update::filter_message()
.enter_dialogue::, State>()
.branch(dptree::case![State::Start].endpoint(start))
.branch(dptree::case![State::ReceiveFullName].endpoint(receive_full_name))
.branch(dptree::case![State::ReceiveAge { full_name }].endpoint(receive_age))
.branch(
dptree::case![State::ReceiveLocation { full_name, age }].endpoint(receive_location),
),
)
.dependencies(dptree::deps![InMemStorage::::new()])
.enable_ctrlc_handler()
.build()
.dispatch()
.await;
}
async fn start(bot: Bot, dialogue: MyDialogue, msg: Message) -> HandlerResult {
bot.send_message(msg.chat.id, "Let's start! What's your full name?").await?;
dialogue.update(State::ReceiveFullName).await?;
Ok(())
}
async fn receive_full_name(bot: Bot, dialogue: MyDialogue, msg: Message) -> HandlerResult {
match msg.text() {
Some(text) => {
bot.send_message(msg.chat.id, "How old are you?").await?;
dialogue.update(State::ReceiveAge { full_name: text.into() }).await?;
}
None => {
bot.send_message(msg.chat.id, "Send me plain text.").await?;
}
}
Ok(())
}
async fn receive_age(
bot: Bot,
dialogue: MyDialogue,
full_name: String, // Available from `State::ReceiveAge`.
msg: Message,
) -> HandlerResult {
match msg.text().map(|text| text.parse::()) {
Some(Ok(age)) => {
bot.send_message(msg.chat.id, "What's your location?").await?;
dialogue.update(State::ReceiveLocation { full_name, age }).await?;
}
_ => {
bot.send_message(msg.chat.id, "Send me a number.").await?;
}
}
Ok(())
}
async fn receive_location(
bot: Bot,
dialogue: MyDialogue,
(full_name, age): (String, u8), // Available from `State::ReceiveLocation`.
msg: Message,
) -> HandlerResult {
match msg.text() {
Some(location) => {
let report = format!("Full name: {full_name}\nAge: {age}\nLocation: {location}");
bot.send_message(msg.chat.id, report).await?;
dialogue.exit().await?;
}
None => {
bot.send_message(msg.chat.id, "Send me plain text.").await?;
}
}
Ok(())
}
```
[更多示例 >>](crates/teloxide/examples/)
## 测试
社区创建了一个名为 [`teloxide_tests`](https://github.com/LasterAlex/teloxide_tests) 的 crate,用于测试 `teloxide` 机器人。
[查看一些测试示例 >>](https://github.com/LasterAlex/teloxide_tests/tree/master/examples)
## 教程
- [_`dptree` 与 teloxide 入门指南_](https://github.com/teloxide/teloxide/blob/master/DPTREE_GUIDE.md)
- _"将我的家庭财务机器人从 Python 迁移到 Rust (teloxide),因为我厌倦了异常(第一部分)"_,作者:Troy Köhler。
- _"将我的家庭财务机器人从 Python 迁移到 Rust (teloxide) [第二部分]"_,作者:Troy Köhler。
## 常见问题
**问:我在哪里可以提问?**
答:
- [Issues] 是提出关于库设计、功能增强和错误报告的规范问题的好地方。
- [GitHub Discussions] 是一个您可以以更不正式的方式向我们寻求帮助的地方。
- 如果您需要快速实时的帮助,应该在[我们的官方 Telegram 群组]中提问。
**问:你们支持客户端的 Telegram API 吗?**
答:不支持,仅支持机器人 API。
**问:我可以使用 Webhook 吗?**
答:可以!`teloxide` 在 `dispatching::update_listeners::webhooks` 模块中内置了对 Webhook 的支持。请参阅其在 [`examples/ngrok_ping_pong_bot.rs`](crates/teloxide/examples/ngrok_ping_pong.rs) 和 [`examples/heroku_ping_pong_bot.rs`](crates/teloxide/examples/heroku_ping_pong.rs) 中的用法。
**问:我可以在同一个对话中同时处理回调查询和消息吗?**
答:可以,请参阅 [`examples/purchase.rs`](crates/teloxide/examples/purchase.rs)。
**问:我该如何组织复杂的逻辑?**
答:您可以使用 [`CommonVoiceBot`] 作为示例,这是一个具有分布在不同文件中的嵌套对话结构的机器人。
**问:我在哪里可以找到 WebApp 的示例?**
答:请查看 [@TheAwiteb] 的 [WebApp `teloxide` 示例]。
## 参与贡献
请参阅 [`CONRIBUTING.md`](CONTRIBUTING.md)。标签:API 覆盖, HTTPS 客户端, Redis, Rust 框架, Rust 编程, SOC Prime, SQLite, Telegram API, Telegram 机器人, Webhooks, 功能丰富, 即时通讯, 可视化界面, 声明式编程, 对话管理, 开发工具, 强类型命令, 持久化存储, 机器人框架, 网络调试, 聊天机器人开发, 自动化, 通知系统, 长轮询