mirror of
https://github.com/teloxide/teloxide.git
synced 2024-12-22 14:35:36 +01:00
Merge pull request #758 from teloxide/dev
Merge v0.11.1 Former-commit-id: 637dcb0d59090dbcf13b2e44a3cc7a27f03ac298
This commit is contained in:
commit
2c52e2a1d4
22 changed files with 472 additions and 68 deletions
19
CHANGELOG.md
19
CHANGELOG.md
|
@ -6,6 +6,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
## unreleased
|
## unreleased
|
||||||
|
|
||||||
|
## 0.11.1 - 2022-10-31
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- The `rocksdb-storage` feature -- enables the RocksDB support ([PR #753](https://github.com/teloxide/teloxide/pull/753))
|
||||||
|
- `teloxide::dispatching::repls::CommandReplExt`, `teloxide::prelude::CommandReplExt` ([issue #740](https://github.com/teloxide/teloxide/issues/740))
|
||||||
|
|
||||||
|
### Deprecated
|
||||||
|
|
||||||
|
- `teloxide::dispatching::repls::{commands_repl, commands_repl_with_listener}`, `teloxide::utils::command::BotCommands::ty` (use `CommandReplExt` instead)
|
||||||
|
|
||||||
## 0.11.0 - 2022-10-07
|
## 0.11.0 - 2022-10-07
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
@ -75,7 +86,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fix Api Unknown error (Can't parse entities) on message created with `utils::markdown::user_mention_or_link` if user full name contaiins some escapable symbols eg '.'
|
- Fix Api Unknown error (Can't parse entities) on message created with `utils::markdown::user_mention_or_link` if user full name contains some escapable symbols eg '.'
|
||||||
|
|
||||||
## 0.9.1 - 2022-05-27
|
## 0.9.1 - 2022-05-27
|
||||||
|
|
||||||
|
@ -188,7 +199,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- `BotCommand::bot_commands` to obtain Telegram API commands ([issue 262](https://github.com/teloxide/teloxide/issues/262)).
|
- `BotCommand::bot_commands` to obtain Telegram API commands ([issue 262](https://github.com/teloxide/teloxide/issues/262)).
|
||||||
- The `dispatching2` and `prelude2` modules. They presents a new dispatching model based on `dptree`.
|
- The `dispatching2` and `prelude2` modules. They present a new dispatching model based on `dptree`.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
@ -265,7 +276,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Remove the `reqwest` dependency. It's not needed after the [teloxide-core] integration.
|
- Remove the `reqwest` dependency. It's not needed after the [teloxide-core] integration.
|
||||||
- A storage persistency bug ([issue 304](https://github.com/teloxide/teloxide/issues/304)).
|
- A storage persistence bug ([issue 304](https://github.com/teloxide/teloxide/issues/304)).
|
||||||
- Log errors from `Storage::{remove_dialogue, update_dialogue}` in `DialogueDispatcher` ([issue 302](https://github.com/teloxide/teloxide/issues/302)).
|
- Log errors from `Storage::{remove_dialogue, update_dialogue}` in `DialogueDispatcher` ([issue 302](https://github.com/teloxide/teloxide/issues/302)).
|
||||||
- Mark all the functions of `Storage` as `#[must_use]`.
|
- Mark all the functions of `Storage` as `#[must_use]`.
|
||||||
|
|
||||||
|
@ -371,7 +382,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Now methods which can send file to Telegram returns `tokio::io::Result<T>`. Early its could panic ([issue 216](https://github.com/teloxide/teloxide/issues/216)).
|
- Now methods which can send file to Telegram return `tokio::io::Result<T>`. Before that it could panic ([issue 216](https://github.com/teloxide/teloxide/issues/216)).
|
||||||
- If a bot wasn't triggered for several days, it stops responding ([issue 223](https://github.com/teloxide/teloxide/issues/223)).
|
- If a bot wasn't triggered for several days, it stops responding ([issue 223](https://github.com/teloxide/teloxide/issues/223)).
|
||||||
|
|
||||||
## 0.2.0 - 2020-02-25
|
## 0.2.0 - 2020-02-25
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "teloxide"
|
name = "teloxide"
|
||||||
version = "0.11.0"
|
version = "0.11.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "An elegant Telegram bots framework for Rust"
|
description = "An elegant Telegram bots framework for Rust"
|
||||||
repository = "https://github.com/teloxide/teloxide"
|
repository = "https://github.com/teloxide/teloxide"
|
||||||
|
@ -19,6 +19,7 @@ webhooks-axum = ["webhooks", "axum", "tower", "tower-http"]
|
||||||
|
|
||||||
sqlite-storage = ["sqlx"]
|
sqlite-storage = ["sqlx"]
|
||||||
redis-storage = ["redis"]
|
redis-storage = ["redis"]
|
||||||
|
rocksdb-storage = ["rocksdb"]
|
||||||
cbor-serializer = ["serde_cbor"]
|
cbor-serializer = ["serde_cbor"]
|
||||||
bincode-serializer = ["bincode"]
|
bincode-serializer = ["bincode"]
|
||||||
|
|
||||||
|
@ -42,6 +43,7 @@ full = [
|
||||||
"webhooks-axum",
|
"webhooks-axum",
|
||||||
"sqlite-storage",
|
"sqlite-storage",
|
||||||
"redis-storage",
|
"redis-storage",
|
||||||
|
"rocksdb-storage",
|
||||||
"cbor-serializer",
|
"cbor-serializer",
|
||||||
"bincode-serializer",
|
"bincode-serializer",
|
||||||
"macros",
|
"macros",
|
||||||
|
@ -92,6 +94,9 @@ sqlx = { version = "0.6", optional = true, default-features = false, features =
|
||||||
"sqlite",
|
"sqlite",
|
||||||
] }
|
] }
|
||||||
redis = { version = "0.21", features = ["tokio-comp"], optional = true }
|
redis = { version = "0.21", features = ["tokio-comp"], optional = true }
|
||||||
|
rocksdb = { version = "0.19", optional = true, default-features = false, features = [
|
||||||
|
"lz4",
|
||||||
|
] }
|
||||||
serde_cbor = { version = "0.11", optional = true }
|
serde_cbor = { version = "0.11", optional = true }
|
||||||
bincode = { version = "1.3", optional = true }
|
bincode = { version = "1.3", optional = true }
|
||||||
axum = { version = "0.5.13", optional = true }
|
axum = { version = "0.5.13", optional = true }
|
||||||
|
|
|
@ -1,6 +1,22 @@
|
||||||
This document describes breaking changes of `teloxide` crate, as well as the ways to update code.
|
This document describes breaking changes of `teloxide` crate, as well as the ways to update code.
|
||||||
Note that the list of required changes is not fully exhaustive and it may lack something in rare cases.
|
Note that the list of required changes is not fully exhaustive and it may lack something in rare cases.
|
||||||
|
|
||||||
|
## 0.11 -> 0.11.1
|
||||||
|
|
||||||
|
### teloxide
|
||||||
|
|
||||||
|
We have introduced the new trait `CommandRepl` that replaces the old `commands_repl_(with_listener)` functions:
|
||||||
|
|
||||||
|
```diff
|
||||||
|
- teloxide::commands_repl(bot, answer, Command::ty())
|
||||||
|
+ Command::repl(bot, answer)
|
||||||
|
```
|
||||||
|
|
||||||
|
```diff
|
||||||
|
- teloxide::commands_repl_with_listener(bot, answer, listener, Command::ty())
|
||||||
|
+ Command::repl_with_listener(bot, answer, listener)
|
||||||
|
```
|
||||||
|
|
||||||
## 0.10 -> 0.11
|
## 0.10 -> 0.11
|
||||||
|
|
||||||
### core
|
### core
|
||||||
|
@ -8,12 +24,12 @@ Note that the list of required changes is not fully exhaustive and it may lack s
|
||||||
Requests can now be `.await`ed directly, without need of `.send()` or `AutoSend`.
|
Requests can now be `.await`ed directly, without need of `.send()` or `AutoSend`.
|
||||||
If you previously used `AutoSend` adaptor, you can safely remove it:
|
If you previously used `AutoSend` adaptor, you can safely remove it:
|
||||||
|
|
||||||
```diff,rust
|
```diff
|
||||||
-let bot = Bot::from_env().auto_send();
|
-let bot = Bot::from_env().auto_send();
|
||||||
+let bot = Bot::from_env();
|
+let bot = Bot::from_env();
|
||||||
```
|
```
|
||||||
|
|
||||||
```diff,rust
|
```diff
|
||||||
-async fn start(bot: AutoSend<Bot>, dialogue: MyDialogue, msg: Message) -> HandlerResult {
|
-async fn start(bot: AutoSend<Bot>, dialogue: MyDialogue, msg: Message) -> HandlerResult {
|
||||||
+async fn start(bot: Bot, dialogue: MyDialogue, msg: Message) -> HandlerResult {
|
+async fn start(bot: Bot, dialogue: MyDialogue, msg: Message) -> HandlerResult {
|
||||||
```
|
```
|
||||||
|
@ -41,7 +57,7 @@ You may need to change code accordingly:
|
||||||
-let id: i32 = message.id;
|
-let id: i32 = message.id;
|
||||||
+let id: MessageId = message.id;
|
+let id: MessageId = message.id;
|
||||||
```
|
```
|
||||||
```diff,rust
|
```diff
|
||||||
let (cid, mid): (ChatId, i32) = get_message_to_delete_from_db();
|
let (cid, mid): (ChatId, i32) = get_message_to_delete_from_db();
|
||||||
-bot.delete_message(cid, mid).await?;
|
-bot.delete_message(cid, mid).await?;
|
||||||
+bot.delete_message(cid, MessageId(mid)).await?;
|
+bot.delete_message(cid, MessageId(mid)).await?;
|
||||||
|
@ -50,7 +66,7 @@ let (cid, mid): (ChatId, i32) = get_message_to_delete_from_db();
|
||||||
Note that at the same time `MessageId` is now a tuple struct.
|
Note that at the same time `MessageId` is now a tuple struct.
|
||||||
If you've accessed its only field you'll need to change it too:
|
If you've accessed its only field you'll need to change it too:
|
||||||
|
|
||||||
```diff,rust
|
```diff
|
||||||
-let MessageId { message_id } = bot.copy_message(dst_chat, src_chat, mid).await?;
|
-let MessageId { message_id } = bot.copy_message(dst_chat, src_chat, mid).await?;
|
||||||
+let MessageId(message_id) = bot.copy_message(dst_chat, src_chat, mid).await?;
|
+let MessageId(message_id) = bot.copy_message(dst_chat, src_chat, mid).await?;
|
||||||
save_to_db(message_id);
|
save_to_db(message_id);
|
||||||
|
@ -64,7 +80,7 @@ See `Sticker` documentation for more information about the new structure.
|
||||||
|
|
||||||
You can now write `Ok(())` instead of `respond(())` at the end of closures provided to RELPs:
|
You can now write `Ok(())` instead of `respond(())` at the end of closures provided to RELPs:
|
||||||
|
|
||||||
```diff,rust
|
```diff
|
||||||
teloxide::repl(bot, |bot: Bot, msg: Message| async move {
|
teloxide::repl(bot, |bot: Bot, msg: Message| async move {
|
||||||
bot.send_dice(msg.chat.id).await?;
|
bot.send_dice(msg.chat.id).await?;
|
||||||
- respond(())
|
- respond(())
|
||||||
|
@ -75,11 +91,21 @@ teloxide::repl(bot, |bot: Bot, msg: Message| async move {
|
||||||
|
|
||||||
This is because REPLs now require the closure to return `RequestError` instead of a generic error type, so type inference works perfectly for a return value. If you use something other than `RequestError`, you can transfer your code to `teloxide::dispatching`, which still permits a generic error type.
|
This is because REPLs now require the closure to return `RequestError` instead of a generic error type, so type inference works perfectly for a return value. If you use something other than `RequestError`, you can transfer your code to `teloxide::dispatching`, which still permits a generic error type.
|
||||||
|
|
||||||
|
"Stop tokens" were refactored, the trait is now removed and the types were renamed:
|
||||||
|
|
||||||
|
```diff
|
||||||
|
-use teloxide::dispatching::stop_token::{AsyncStopToken, AsyncStopFlag};
|
||||||
|
+use teloxide::stop::{StopToken, StopFlag, mk_stop_token};
|
||||||
|
|
||||||
|
-let (token, flag): (AsyncStopToken, AsyncStopFlag) = AsyncStopToken::new_pair();
|
||||||
|
+let (token, flag): (StopToken, StopFlag) = mk_stop_token();
|
||||||
|
```
|
||||||
|
|
||||||
### macros
|
### macros
|
||||||
|
|
||||||
`parse_with` now accepts a Rust _path_ to a custom parser function instead of a string:
|
`parse_with` now accepts a Rust _path_ to a custom parser function instead of a string:
|
||||||
|
|
||||||
```diff,rust
|
```diff
|
||||||
fn custom_parser(input: String) -> Result<(u8,), ParseError> {
|
fn custom_parser(input: String) -> Result<(u8,), ParseError> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
@ -94,7 +120,7 @@ enum Command {
|
||||||
|
|
||||||
`rename` now only renames a command literally; use `rename_rule` to change the case of a command:
|
`rename` now only renames a command literally; use `rename_rule` to change the case of a command:
|
||||||
|
|
||||||
```diff,rust
|
```diff
|
||||||
#[derive(BotCommands)]
|
#[derive(BotCommands)]
|
||||||
- #[command(rename = "lowercase", description = "These commands are supported:")]
|
- #[command(rename = "lowercase", description = "These commands are supported:")]
|
||||||
+ #[command(rename_rule = "lowercase", description = "These commands are supported:")]
|
+ #[command(rename_rule = "lowercase", description = "These commands are supported:")]
|
||||||
|
@ -193,7 +219,7 @@ In order to make `Dispatcher` implement `Send`, `DispatcherBuilder::{default_han
|
||||||
|
|
||||||
v0.6 of teloxide introduces a new dispatching model based on the [chain of responsibility pattern]. To use it, you need to replace `prelude` with `prelude2` and `dispatching` with `dispatching2`. Instead of using old REPLs, you should now use `teloxide::repls2`.
|
v0.6 of teloxide introduces a new dispatching model based on the [chain of responsibility pattern]. To use it, you need to replace `prelude` with `prelude2` and `dispatching` with `dispatching2`. Instead of using old REPLs, you should now use `teloxide::repls2`.
|
||||||
|
|
||||||
The whole design is different than the previous one based on Tokio streams. In this section, we are only to address the most common usage scenarios.
|
The whole design is different from the previous one based on Tokio streams. In this section, we are only to address the most common usage scenarios.
|
||||||
|
|
||||||
First of all, now there are no streams. Instead of using streams, you use [`dptree`], which is a more suitable alternative for our purposes. Thus, if you previously used `Dispatcher::messages_handler`, now you should use `Update::filter_message()`, and so on.
|
First of all, now there are no streams. Instead of using streams, you use [`dptree`], which is a more suitable alternative for our purposes. Thus, if you previously used `Dispatcher::messages_handler`, now you should use `Update::filter_message()`, and so on.
|
||||||
|
|
||||||
|
@ -237,7 +263,7 @@ List of changed types:
|
||||||
|
|
||||||
In teloxide `v0.4` (core `v0.2`) some API methods had wrong return types.
|
In teloxide `v0.4` (core `v0.2`) some API methods had wrong return types.
|
||||||
This made them practically unusable as they've always returned parsing error.
|
This made them practically unusable as they've always returned parsing error.
|
||||||
On the offchance you were using the methods, you may need to adjust types in your code.
|
On the off-chance you were using the methods, you may need to adjust types in your code.
|
||||||
|
|
||||||
List of changed return types:
|
List of changed return types:
|
||||||
- `get_chat_administrators`: `ChatMember` -> `Vec<ChatMember>`
|
- `get_chat_administrators`: `ChatMember` -> `Vec<ChatMember>`
|
||||||
|
@ -324,7 +350,7 @@ List of renamed items:
|
||||||
#### Added `impl Clone` for {`CacheMe`, `DefaultParseMode`, `Throttle`}
|
#### Added `impl Clone` for {`CacheMe`, `DefaultParseMode`, `Throttle`}
|
||||||
|
|
||||||
Previously said bot adaptors were lacking `Clone` implementation.
|
Previously said bot adaptors were lacking `Clone` implementation.
|
||||||
To workaround this issue it was proposed to wrap bot in `Arc`.
|
To work around this issue it was proposed to wrap bot in `Arc`.
|
||||||
Now it's not required, so you can remove the `Arc`:
|
Now it's not required, so you can remove the `Arc`:
|
||||||
|
|
||||||
```diff
|
```diff
|
||||||
|
|
15
README.md
15
README.md
|
@ -1,4 +1,4 @@
|
||||||
> [v0.10 -> v0.11 migration guide >>](MIGRATION_GUIDE.md#010---011)
|
> [v0.11 -> v0.11.1 migration guide >>](MIGRATION_GUIDE.md#011---0111)
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<img src="./ICON.png" width="250"/>
|
<img src="./ICON.png" width="250"/>
|
||||||
|
@ -29,12 +29,13 @@
|
||||||
[`dptree`]: https://github.com/teloxide/dptree
|
[`dptree`]: https://github.com/teloxide/dptree
|
||||||
[chain of responsibility]: https://en.wikipedia.org/wiki/Chain-of-responsibility_pattern
|
[chain of responsibility]: https://en.wikipedia.org/wiki/Chain-of-responsibility_pattern
|
||||||
|
|
||||||
- **Feature-rich.** You can use both long polling and webhooks, configure an underlying HTTPS client, set a custom URL of a Telegram API server, and much more.
|
- **Feature-rich.** You can use both long polling and webhooks, configure an underlying HTTPS client, set a custom URL of a Telegram API server, do graceful shutdown, and much more.
|
||||||
|
|
||||||
- **Simple dialogues.** Our dialogues subsystem is simple and easy-to-use, and, furthermore, is agnostic of how/where dialogues are stored. For example, you can just replace a one line to achieve [persistence]. Out-of-the-box storages include [Redis] and [Sqlite].
|
- **Simple dialogues.** Our dialogues subsystem is simple and easy-to-use, and, furthermore, is agnostic of how/where dialogues are stored. For example, you can just replace a one line to achieve [persistence]. Out-of-the-box storages include [Redis], [RocksDB] and [Sqlite].
|
||||||
|
|
||||||
[persistence]: https://en.wikipedia.org/wiki/Persistence_(computer_science)
|
[persistence]: https://en.wikipedia.org/wiki/Persistence_(computer_science)
|
||||||
[Redis]: https://redis.io/
|
[Redis]: https://redis.io/
|
||||||
|
[RocksDB]: https://rocksdb.org/
|
||||||
[Sqlite]: https://www.sqlite.org
|
[Sqlite]: https://www.sqlite.org
|
||||||
|
|
||||||
- **Strongly typed commands.** Define bot commands as an `enum` and teloxide will parse them automatically — just like JSON structures in [`serde-json`] and command-line arguments in [`structopt`].
|
- **Strongly typed commands.** Define bot commands as an `enum` and teloxide will parse them automatically — just like JSON structures in [`serde-json`] and command-line arguments in [`structopt`].
|
||||||
|
@ -72,7 +73,7 @@ $ rustup override set nightly
|
||||||
5. Run `cargo new my_bot`, enter the directory and put these lines into your `Cargo.toml`:
|
5. Run `cargo new my_bot`, enter the directory and put these lines into your `Cargo.toml`:
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
teloxide = { version = "0.11", features = ["macros", "auto-send"] }
|
teloxide = { version = "0.11", features = ["macros"] }
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
pretty_env_logger = "0.4"
|
pretty_env_logger = "0.4"
|
||||||
tokio = { version = "1.8", features = ["rt-multi-thread", "macros"] }
|
tokio = { version = "1.8", features = ["rt-multi-thread", "macros"] }
|
||||||
|
@ -82,7 +83,7 @@ tokio = { version = "1.8", features = ["rt-multi-thread", "macros"] }
|
||||||
|
|
||||||
### The dices bot
|
### The dices bot
|
||||||
|
|
||||||
This bot replies with a dice throw to each received message:
|
This bot replies with a die throw to each received message:
|
||||||
|
|
||||||
[[`examples/throw_dice.rs`](examples/throw_dice.rs)]
|
[[`examples/throw_dice.rs`](examples/throw_dice.rs)]
|
||||||
|
|
||||||
|
@ -131,7 +132,7 @@ async fn main() {
|
||||||
|
|
||||||
let bot = Bot::from_env();
|
let bot = Bot::from_env();
|
||||||
|
|
||||||
teloxide::commands_repl(bot, answer, Command::ty()).await;
|
Command::repl(bot, answer).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(BotCommands, Clone)]
|
#[derive(BotCommands, Clone)]
|
||||||
|
@ -326,7 +327,7 @@ Feel free to propose your own bot to our collection!
|
||||||
- [`modos189/tg_blackbox_bot`](https://gitlab.com/modos189/tg_blackbox_bot) — Anonymous feedback for your Telegram project.
|
- [`modos189/tg_blackbox_bot`](https://gitlab.com/modos189/tg_blackbox_bot) — Anonymous feedback for your Telegram project.
|
||||||
- [`0xNima/spacecraft`](https://github.com/0xNima/spacecraft) — Yet another telegram bot to downloading Twitter spaces.
|
- [`0xNima/spacecraft`](https://github.com/0xNima/spacecraft) — Yet another telegram bot to downloading Twitter spaces.
|
||||||
- [`0xNima/Twideo`](https://github.com/0xNima/Twideo) — Simple Telegram Bot for downloading videos from Twitter via their links.
|
- [`0xNima/Twideo`](https://github.com/0xNima/Twideo) — Simple Telegram Bot for downloading videos from Twitter via their links.
|
||||||
- [`mattrighetti/libgen-bot-rs`](https://github.com/mattrighetti/libgen-bot-rs) — Telgram bot to interface with libgen.
|
- [`mattrighetti/libgen-bot-rs`](https://github.com/mattrighetti/libgen-bot-rs) — Telegram bot to interface with libgen.
|
||||||
- [`zamazan4ik/npaperbot-telegram`](https://github.com/zamazan4ik/npaperbot-telegram) — Telegram bot for searching via C++ proposals.
|
- [`zamazan4ik/npaperbot-telegram`](https://github.com/zamazan4ik/npaperbot-telegram) — Telegram bot for searching via C++ proposals.
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
|
|
|
@ -5,8 +5,8 @@ use teloxide::{prelude::*, types::ChatPermissions, utils::command::BotCommands};
|
||||||
|
|
||||||
// Derive BotCommands to parse text with a command into this enumeration.
|
// Derive BotCommands to parse text with a command into this enumeration.
|
||||||
//
|
//
|
||||||
// 1. rename = "lowercase" turns all the commands into lowercase letters.
|
// 1. `rename_rule = "lowercase"` turns all the commands into lowercase letters.
|
||||||
// 2. `description = "..."` specifies a text before all the commands.
|
// 2. `description = "..."` specifies a text before all the commands.
|
||||||
//
|
//
|
||||||
// That is, you can just call Command::descriptions() to get a description of
|
// That is, you can just call Command::descriptions() to get a description of
|
||||||
// your commands in this format:
|
// your commands in this format:
|
||||||
|
@ -60,7 +60,7 @@ async fn main() {
|
||||||
|
|
||||||
let bot = teloxide::Bot::from_env();
|
let bot = teloxide::Bot::from_env();
|
||||||
|
|
||||||
teloxide::commands_repl(bot, action, Command::ty()).await;
|
Command::repl(bot, action).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn action(bot: Bot, msg: Message, cmd: Command) -> ResponseResult<()> {
|
async fn action(bot: Bot, msg: Message, cmd: Command) -> ResponseResult<()> {
|
||||||
|
|
|
@ -7,7 +7,7 @@ async fn main() {
|
||||||
|
|
||||||
let bot = Bot::from_env();
|
let bot = Bot::from_env();
|
||||||
|
|
||||||
teloxide::commands_repl(bot, answer, Command::ty()).await;
|
Command::repl(bot, answer).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(BotCommands, Clone)]
|
#[derive(BotCommands, Clone)]
|
||||||
|
|
|
@ -91,7 +91,7 @@ async fn got_number(
|
||||||
}
|
}
|
||||||
Command::Reset => {
|
Command::Reset => {
|
||||||
dialogue.reset().await?;
|
dialogue.reset().await?;
|
||||||
bot.send_message(msg.chat.id, "Number resetted.").await?;
|
bot.send_message(msg.chat.id, "Number reset.").await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -32,7 +32,7 @@ async fn main() {
|
||||||
.endpoint(simple_commands_handler),
|
.endpoint(simple_commands_handler),
|
||||||
)
|
)
|
||||||
.branch(
|
.branch(
|
||||||
// Filter a maintainer by a used ID.
|
// Filter a maintainer by a user ID.
|
||||||
dptree::filter(|cfg: ConfigParameters, msg: Message| {
|
dptree::filter(|cfg: ConfigParameters, msg: Message| {
|
||||||
msg.from().map(|user| user.id == cfg.bot_maintainer).unwrap_or_default()
|
msg.from().map(|user| user.id == cfg.bot_maintainer).unwrap_or_default()
|
||||||
})
|
})
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
// heroku create --buildpack emk/rust
|
// heroku create --buildpack emk/rust
|
||||||
// ```
|
// ```
|
||||||
//
|
//
|
||||||
// To set buildpack for existing applicaton:
|
// To set buildpack for existing application:
|
||||||
//
|
//
|
||||||
// ```
|
// ```
|
||||||
// heroku buildpacks:set emk/rust
|
// heroku buildpacks:set emk/rust
|
||||||
|
|
|
@ -9,6 +9,9 @@ mod redis_storage;
|
||||||
#[cfg(feature = "sqlite-storage")]
|
#[cfg(feature = "sqlite-storage")]
|
||||||
mod sqlite_storage;
|
mod sqlite_storage;
|
||||||
|
|
||||||
|
#[cfg(feature = "rocksdb-storage")]
|
||||||
|
mod rocksdb_storage;
|
||||||
|
|
||||||
use futures::future::BoxFuture;
|
use futures::future::BoxFuture;
|
||||||
use teloxide_core::types::ChatId;
|
use teloxide_core::types::ChatId;
|
||||||
|
|
||||||
|
@ -25,6 +28,9 @@ use std::sync::Arc;
|
||||||
#[cfg(feature = "sqlite-storage")]
|
#[cfg(feature = "sqlite-storage")]
|
||||||
pub use sqlite_storage::{SqliteStorage, SqliteStorageError};
|
pub use sqlite_storage::{SqliteStorage, SqliteStorageError};
|
||||||
|
|
||||||
|
#[cfg(feature = "rocksdb-storage")]
|
||||||
|
pub use rocksdb_storage::{RocksDbStorage, RocksDbStorageError};
|
||||||
|
|
||||||
/// A storage with an erased error type.
|
/// A storage with an erased error type.
|
||||||
pub type ErasedStorage<D> =
|
pub type ErasedStorage<D> =
|
||||||
dyn Storage<D, Error = Box<dyn std::error::Error + Send + Sync>> + Send + Sync;
|
dyn Storage<D, Error = Box<dyn std::error::Error + Send + Sync>> + Send + Sync;
|
||||||
|
@ -41,10 +47,12 @@ pub type ErasedStorage<D> =
|
||||||
///
|
///
|
||||||
/// - [`InMemStorage`] -- a storage based on [`std::collections::HashMap`].
|
/// - [`InMemStorage`] -- a storage based on [`std::collections::HashMap`].
|
||||||
/// - [`RedisStorage`] -- a Redis-based storage.
|
/// - [`RedisStorage`] -- a Redis-based storage.
|
||||||
|
/// - [`RocksDbStorage`] -- a RocksDB-based persistent storage.
|
||||||
/// - [`SqliteStorage`] -- an SQLite-based persistent storage.
|
/// - [`SqliteStorage`] -- an SQLite-based persistent storage.
|
||||||
///
|
///
|
||||||
/// [`InMemStorage`]: crate::dispatching::dialogue::InMemStorage
|
/// [`InMemStorage`]: crate::dispatching::dialogue::InMemStorage
|
||||||
/// [`RedisStorage`]: crate::dispatching::dialogue::RedisStorage
|
/// [`RedisStorage`]: crate::dispatching::dialogue::RedisStorage
|
||||||
|
/// [`RocksDbStorage`]: crate::dispatching::dialogue::RocksDbStorage
|
||||||
/// [`SqliteStorage`]: crate::dispatching::dialogue::SqliteStorage
|
/// [`SqliteStorage`]: crate::dispatching::dialogue::SqliteStorage
|
||||||
pub trait Storage<D> {
|
pub trait Storage<D> {
|
||||||
type Error;
|
type Error;
|
||||||
|
|
113
src/dispatching/dialogue/storage/rocksdb_storage.rs
Normal file
113
src/dispatching/dialogue/storage/rocksdb_storage.rs
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
use super::{serializer::Serializer, Storage};
|
||||||
|
use futures::future::BoxFuture;
|
||||||
|
use rocksdb::{DBCompressionType, DBWithThreadMode, MultiThreaded};
|
||||||
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
|
use std::{
|
||||||
|
convert::Infallible,
|
||||||
|
fmt::{Debug, Display},
|
||||||
|
str,
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
use teloxide_core::types::ChatId;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
/// A persistent dialogue storage based on [RocksDb](http://rocksdb.org/).
|
||||||
|
pub struct RocksDbStorage<S> {
|
||||||
|
db: DBWithThreadMode<MultiThreaded>,
|
||||||
|
serializer: S,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An error returned from [`RocksDbStorage`].
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum RocksDbStorageError<SE>
|
||||||
|
where
|
||||||
|
SE: Debug + Display,
|
||||||
|
{
|
||||||
|
#[error("dialogue serialization error: {0}")]
|
||||||
|
SerdeError(SE),
|
||||||
|
|
||||||
|
#[error("RocksDb error: {0}")]
|
||||||
|
RocksDbError(#[from] rocksdb::Error),
|
||||||
|
|
||||||
|
/// Returned from [`RocksDbStorage::remove_dialogue`].
|
||||||
|
#[error("row not found")]
|
||||||
|
DialogueNotFound,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> RocksDbStorage<S> {
|
||||||
|
pub async fn open(
|
||||||
|
path: &str,
|
||||||
|
serializer: S,
|
||||||
|
options: Option<rocksdb::Options>,
|
||||||
|
) -> Result<Arc<Self>, RocksDbStorageError<Infallible>> {
|
||||||
|
let options = match options {
|
||||||
|
Some(opts) => opts,
|
||||||
|
None => {
|
||||||
|
let mut opts = rocksdb::Options::default();
|
||||||
|
opts.set_compression_type(DBCompressionType::Lz4);
|
||||||
|
opts.create_if_missing(true);
|
||||||
|
opts
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let db = DBWithThreadMode::<MultiThreaded>::open(&options, path)?;
|
||||||
|
Ok(Arc::new(Self { db, serializer }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, D> Storage<D> for RocksDbStorage<S>
|
||||||
|
where
|
||||||
|
S: Send + Sync + Serializer<D> + 'static,
|
||||||
|
D: Send + Serialize + DeserializeOwned + 'static,
|
||||||
|
<S as Serializer<D>>::Error: Debug + Display,
|
||||||
|
{
|
||||||
|
type Error = RocksDbStorageError<<S as Serializer<D>>::Error>;
|
||||||
|
|
||||||
|
/// Returns [`RocksDbStorageError::DialogueNotFound`] if a dialogue does not
|
||||||
|
/// exist.
|
||||||
|
fn remove_dialogue(
|
||||||
|
self: Arc<Self>,
|
||||||
|
ChatId(chat_id): ChatId,
|
||||||
|
) -> BoxFuture<'static, Result<(), Self::Error>> {
|
||||||
|
Box::pin(async move {
|
||||||
|
let key = chat_id.to_le_bytes();
|
||||||
|
|
||||||
|
if self.db.get(&key)?.is_none() {
|
||||||
|
return Err(RocksDbStorageError::DialogueNotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.db.delete(&key).unwrap();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_dialogue(
|
||||||
|
self: Arc<Self>,
|
||||||
|
ChatId(chat_id): ChatId,
|
||||||
|
dialogue: D,
|
||||||
|
) -> BoxFuture<'static, Result<(), Self::Error>> {
|
||||||
|
Box::pin(async move {
|
||||||
|
let d =
|
||||||
|
self.serializer.serialize(&dialogue).map_err(RocksDbStorageError::SerdeError)?;
|
||||||
|
|
||||||
|
let key = chat_id.to_le_bytes();
|
||||||
|
self.db.put(&key, &d)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_dialogue(
|
||||||
|
self: Arc<Self>,
|
||||||
|
ChatId(chat_id): ChatId,
|
||||||
|
) -> BoxFuture<'static, Result<Option<D>, Self::Error>> {
|
||||||
|
Box::pin(async move {
|
||||||
|
let key = chat_id.to_le_bytes();
|
||||||
|
self.db
|
||||||
|
.get(&key)?
|
||||||
|
.map(|d| self.serializer.deserialize(&d).map_err(RocksDbStorageError::SerdeError))
|
||||||
|
.transpose()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,5 +11,7 @@
|
||||||
mod commands_repl;
|
mod commands_repl;
|
||||||
mod repl;
|
mod repl;
|
||||||
|
|
||||||
|
pub use commands_repl::CommandReplExt;
|
||||||
|
#[allow(deprecated)]
|
||||||
pub use commands_repl::{commands_repl, commands_repl_with_listener};
|
pub use commands_repl::{commands_repl, commands_repl_with_listener};
|
||||||
pub use repl::{repl, repl_with_listener};
|
pub use repl::{repl, repl_with_listener};
|
||||||
|
|
|
@ -8,8 +8,147 @@ use crate::{
|
||||||
utils::command::BotCommands,
|
utils::command::BotCommands,
|
||||||
};
|
};
|
||||||
use dptree::di::{DependencyMap, Injectable};
|
use dptree::di::{DependencyMap, Injectable};
|
||||||
|
use futures::future::BoxFuture;
|
||||||
use std::{fmt::Debug, marker::PhantomData};
|
use std::{fmt::Debug, marker::PhantomData};
|
||||||
|
|
||||||
|
/// A [REPL] for commands.
|
||||||
|
///
|
||||||
|
/// REPLs are meant only for simple bots and rapid prototyping. If you need to
|
||||||
|
/// supply dependencies or describe more complex dispatch logic, please use
|
||||||
|
/// [`Dispatcher`]. See also: ["Dispatching or
|
||||||
|
/// REPLs?"](../index.html#dispatching-or-repls).
|
||||||
|
///
|
||||||
|
/// [`Dispatcher`]: crate::dispatching::Dispatcher
|
||||||
|
///
|
||||||
|
/// All errors from the handler and update listener will be logged.
|
||||||
|
///
|
||||||
|
/// [REPL]: https://en.wikipedia.org/wiki/Read-eval-print_loop
|
||||||
|
///
|
||||||
|
/// This trait extends your [`BotCommands`] type with REPL facilities.
|
||||||
|
///
|
||||||
|
/// ## Signatures
|
||||||
|
///
|
||||||
|
/// Don't be scared by many trait bounds in the signatures, in essence they
|
||||||
|
/// require:
|
||||||
|
///
|
||||||
|
/// 1. `bot` is a bot, client for the Telegram bot API. It is represented via
|
||||||
|
/// the [`Requester`] trait.
|
||||||
|
/// 2. `handler` is an `async` function that takes arguments from
|
||||||
|
/// [`DependencyMap`] (see below) and returns [`ResponseResult`].
|
||||||
|
/// 3. `listener` is something that takes updates from a Telegram server and
|
||||||
|
/// implements [`UpdateListener`].
|
||||||
|
///
|
||||||
|
/// All the other requirements are about thread safety and data validity and can
|
||||||
|
/// be ignored for most of the time.
|
||||||
|
///
|
||||||
|
/// ## Handler arguments
|
||||||
|
///
|
||||||
|
/// `teloxide` provides the following types to the `handler`:
|
||||||
|
/// - [`Message`]
|
||||||
|
/// - `R` (type of the `bot`)
|
||||||
|
/// - `Cmd` (type of the parsed command)
|
||||||
|
/// - [`Me`]
|
||||||
|
///
|
||||||
|
/// Each of these types can be accepted as a handler parameter. Note that they
|
||||||
|
/// aren't all required at the same time: e.g., you can take only the bot and
|
||||||
|
/// the command without [`Me`] and [`Message`].
|
||||||
|
///
|
||||||
|
/// [`Me`]: crate::types::Me
|
||||||
|
/// [`Message`]: crate::types::Message
|
||||||
|
///
|
||||||
|
/// ## Stopping
|
||||||
|
//
|
||||||
|
#[doc = include_str!("stopping.md")]
|
||||||
|
///
|
||||||
|
/// ## Caution
|
||||||
|
//
|
||||||
|
#[doc = include_str!("caution.md")]
|
||||||
|
///
|
||||||
|
#[cfg(feature = "ctrlc_handler")]
|
||||||
|
pub trait CommandReplExt {
|
||||||
|
/// A REPL for commands.
|
||||||
|
///
|
||||||
|
/// See [`CommandReplExt`] for more details.
|
||||||
|
#[must_use]
|
||||||
|
fn repl<'a, R, H, Args>(bot: R, handler: H) -> BoxFuture<'a, ()>
|
||||||
|
where
|
||||||
|
R: Requester + Clone + Send + Sync + 'static,
|
||||||
|
<R as Requester>::GetUpdates: Send,
|
||||||
|
<R as Requester>::GetWebhookInfo: Send,
|
||||||
|
<R as Requester>::GetMe: Send,
|
||||||
|
<R as Requester>::DeleteWebhook: Send,
|
||||||
|
H: Injectable<DependencyMap, ResponseResult<()>, Args> + Send + Sync + 'static;
|
||||||
|
|
||||||
|
/// A REPL for commands with a custom [`UpdateListener`].
|
||||||
|
///
|
||||||
|
/// See [`CommandReplExt`] for more details.
|
||||||
|
#[must_use]
|
||||||
|
fn repl_with_listener<'a, R, H, L, Args>(bot: R, handler: H, listener: L) -> BoxFuture<'a, ()>
|
||||||
|
where
|
||||||
|
H: Injectable<DependencyMap, ResponseResult<()>, Args> + Send + Sync + 'static,
|
||||||
|
L: UpdateListener + Send + 'a,
|
||||||
|
L::Err: Debug + Send + 'a,
|
||||||
|
R: Requester + Clone + Send + Sync + 'static,
|
||||||
|
<R as Requester>::GetMe: Send;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ctrlc_handler")]
|
||||||
|
impl<Cmd> CommandReplExt for Cmd
|
||||||
|
where
|
||||||
|
Cmd: BotCommands + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
fn repl<'a, R, H, Args>(bot: R, handler: H) -> BoxFuture<'a, ()>
|
||||||
|
where
|
||||||
|
R: Requester + Clone + Send + Sync + 'static,
|
||||||
|
<R as Requester>::GetUpdates: Send,
|
||||||
|
<R as Requester>::GetWebhookInfo: Send,
|
||||||
|
<R as Requester>::GetMe: Send,
|
||||||
|
<R as Requester>::DeleteWebhook: Send,
|
||||||
|
H: Injectable<DependencyMap, ResponseResult<()>, Args> + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
let cloned_bot = bot.clone();
|
||||||
|
|
||||||
|
Box::pin(async move {
|
||||||
|
Self::repl_with_listener(
|
||||||
|
bot,
|
||||||
|
handler,
|
||||||
|
update_listeners::polling_default(cloned_bot).await,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn repl_with_listener<'a, R, H, L, Args>(bot: R, handler: H, listener: L) -> BoxFuture<'a, ()>
|
||||||
|
where
|
||||||
|
H: Injectable<DependencyMap, ResponseResult<()>, Args> + Send + Sync + 'static,
|
||||||
|
L: UpdateListener + Send + 'a,
|
||||||
|
L::Err: Debug + Send + 'a,
|
||||||
|
R: Requester + Clone + Send + Sync + 'static,
|
||||||
|
<R as Requester>::GetMe: Send,
|
||||||
|
{
|
||||||
|
use crate::dispatching::Dispatcher;
|
||||||
|
|
||||||
|
// Other update types are of no interest to use since this REPL is only for
|
||||||
|
// commands. See <https://github.com/teloxide/teloxide/issues/557>.
|
||||||
|
let ignore_update = |_upd| Box::pin(async {});
|
||||||
|
|
||||||
|
Box::pin(async move {
|
||||||
|
Dispatcher::builder(
|
||||||
|
bot,
|
||||||
|
Update::filter_message().filter_command::<Cmd>().endpoint(handler),
|
||||||
|
)
|
||||||
|
.default_handler(ignore_update)
|
||||||
|
.enable_ctrlc_handler()
|
||||||
|
.build()
|
||||||
|
.dispatch_with_listener(
|
||||||
|
listener,
|
||||||
|
LoggingErrorHandler::with_custom_text("An error from the update listener"),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A [REPL] for commands.
|
/// A [REPL] for commands.
|
||||||
//
|
//
|
||||||
///
|
///
|
||||||
|
@ -59,6 +198,7 @@ use std::{fmt::Debug, marker::PhantomData};
|
||||||
#[doc = include_str!("caution.md")]
|
#[doc = include_str!("caution.md")]
|
||||||
///
|
///
|
||||||
#[cfg(feature = "ctrlc_handler")]
|
#[cfg(feature = "ctrlc_handler")]
|
||||||
|
#[deprecated(note = "Use `CommandsRepl::repl` instead")]
|
||||||
pub async fn commands_repl<'a, R, Cmd, H, Args>(bot: R, handler: H, cmd: PhantomData<Cmd>)
|
pub async fn commands_repl<'a, R, Cmd, H, Args>(bot: R, handler: H, cmd: PhantomData<Cmd>)
|
||||||
where
|
where
|
||||||
R: Requester + Clone + Send + Sync + 'static,
|
R: Requester + Clone + Send + Sync + 'static,
|
||||||
|
@ -68,6 +208,7 @@ where
|
||||||
{
|
{
|
||||||
let cloned_bot = bot.clone();
|
let cloned_bot = bot.clone();
|
||||||
|
|
||||||
|
#[allow(deprecated)]
|
||||||
commands_repl_with_listener(
|
commands_repl_with_listener(
|
||||||
bot,
|
bot,
|
||||||
handler,
|
handler,
|
||||||
|
@ -127,6 +268,7 @@ where
|
||||||
#[doc = include_str!("caution.md")]
|
#[doc = include_str!("caution.md")]
|
||||||
///
|
///
|
||||||
#[cfg(feature = "ctrlc_handler")]
|
#[cfg(feature = "ctrlc_handler")]
|
||||||
|
#[deprecated(note = "Use `CommandsRepl::repl_with_listener` instead")]
|
||||||
pub async fn commands_repl_with_listener<'a, R, Cmd, H, L, Args>(
|
pub async fn commands_repl_with_listener<'a, R, Cmd, H, L, Args>(
|
||||||
bot: R,
|
bot: R,
|
||||||
handler: H,
|
handler: H,
|
||||||
|
|
|
@ -225,7 +225,7 @@ where
|
||||||
///
|
///
|
||||||
/// C->>P: next
|
/// C->>P: next
|
||||||
///
|
///
|
||||||
/// P->>T: *Acknolegment of update(5)*
|
/// P->>T: *Acknowledgement of update(5)*
|
||||||
/// T->>P: ok
|
/// T->>P: ok
|
||||||
///
|
///
|
||||||
/// P->>C: None
|
/// P->>C: None
|
||||||
|
|
|
@ -1,27 +1,28 @@
|
||||||
## Cargo features
|
## Cargo features
|
||||||
|
|
||||||
| Feature | Description |
|
| Feature | Description |
|
||||||
|----------------------|--------------------------------------------------------------------------------------------|
|
|----------------------|-------------|
|
||||||
| `webhooks` | Enables general webhook utilities (almost useless on its own) |
|
| `webhooks` | Enables general webhook utilities (almost useless on its own). |
|
||||||
| `webhooks-axum` | Enables webhook implementation based on axum framework |
|
| `webhooks-axum` | Enables webhook implementation based on axum framework. |
|
||||||
| `macros` | Re-exports macros from [`teloxide-macros`]. |
|
| `macros` | Re-exports macros from [`teloxide-macros`]. |
|
||||||
| `ctrlc_handler` | Enables the [`DispatcherBuilder::enable_ctrlc_handler`] function (**enabled by default**). |
|
| `ctrlc_handler` | Enables the [`DispatcherBuilder::enable_ctrlc_handler`] function (**enabled by default**). |
|
||||||
| `auto-send` | Enables the [`AutoSend`](adaptors::AutoSend) bot adaptor (**enabled by default; DEPRECATED**). |
|
| `auto-send` | Enables the [`AutoSend`](adaptors::AutoSend) bot adaptor (**enabled by default; DEPRECATED**). |
|
||||||
| `throttle` | Enables the [`Throttle`](adaptors::Throttle) bot adaptor. |
|
| `throttle` | Enables the [`Throttle`](adaptors::Throttle) bot adaptor. |
|
||||||
| `cache-me` | Enables the [`CacheMe`](adaptors::CacheMe) bot adaptor. |
|
| `cache-me` | Enables the [`CacheMe`](adaptors::CacheMe) bot adaptor. |
|
||||||
| `trace-adaptor` | Enables the [`Trace`](adaptors::Trace) bot adaptor. |
|
| `trace-adaptor` | Enables the [`Trace`](adaptors::Trace) bot adaptor. |
|
||||||
| `erased` | Enables the [`ErasedRequester`](adaptors::ErasedRequester) bot adaptor. |
|
| `erased` | Enables the [`ErasedRequester`](adaptors::ErasedRequester) bot adaptor. |
|
||||||
| `full` | Enables all the features except `nightly`. |
|
| `full` | Enables all the features except `nightly`. |
|
||||||
| `nightly` | Enables nightly-only features (see the [`teloxide-core` features]). |
|
| `nightly` | Enables nightly-only features (see the [`teloxide-core` features]). |
|
||||||
| `native-tls` | Enables the [`native-tls`] TLS implementation (**enabled by default**). |
|
| `native-tls` | Enables the [`native-tls`] TLS implementation (**enabled by default**). |
|
||||||
| `rustls` | Enables the [`rustls`] TLS implementation. |
|
| `rustls` | Enables the [`rustls`] TLS implementation. |
|
||||||
| `redis-storage` | Enables the [Redis] storage support for dialogues. |
|
| `redis-storage` | Enables the [Redis] storage support for dialogues. |
|
||||||
| `sqlite-storage` | Enables the [Sqlite] storage support for dialogues. |
|
| `rocksdb-storage` | Enables the [RocksDB] storage support for dialogues. |
|
||||||
| `cbor-serializer` | Enables the [CBOR] serializer for dialogues. |
|
| `sqlite-storage` | Enables the [Sqlite] storage support for dialogues. |
|
||||||
| `bincode-serializer` | Enables the [Bincode] serializer for dialogues. |
|
| `cbor-serializer` | Enables the [CBOR] serializer for dialogues. |
|
||||||
|
| `bincode-serializer` | Enables the [Bincode] serializer for dialogues. |
|
||||||
|
|
||||||
[Redis]: https://redis.io/
|
[Redis]: https://redis.io/
|
||||||
|
[RocksDB]: https://rocksdb.org/
|
||||||
[Sqlite]: https://www.sqlite.org/
|
[Sqlite]: https://www.sqlite.org/
|
||||||
[CBOR]: https://en.wikipedia.org/wiki/CBOR
|
[CBOR]: https://en.wikipedia.org/wiki/CBOR
|
||||||
[Bincode]: https://github.com/servo/bincode
|
[Bincode]: https://github.com/servo/bincode
|
||||||
|
@ -31,4 +32,4 @@
|
||||||
[`teloxide::utils::UpState`]: utils::UpState
|
[`teloxide::utils::UpState`]: utils::UpState
|
||||||
[`teloxide-core` features]: https://docs.rs/teloxide-core/latest/teloxide_core/#cargo-features
|
[`teloxide-core` features]: https://docs.rs/teloxide-core/latest/teloxide_core/#cargo-features
|
||||||
|
|
||||||
[`DispatcherBuilder::enable_ctrlc_handler`]: dispatching::DispatcherBuilder::enable_ctrlc_handler
|
[`DispatcherBuilder::enable_ctrlc_handler`]: dispatching::DispatcherBuilder::enable_ctrlc_handler
|
||||||
|
|
|
@ -58,9 +58,10 @@
|
||||||
#![allow(clippy::nonstandard_macro_braces)]
|
#![allow(clippy::nonstandard_macro_braces)]
|
||||||
|
|
||||||
#[cfg(feature = "ctrlc_handler")]
|
#[cfg(feature = "ctrlc_handler")]
|
||||||
pub use dispatching::repls::{
|
pub use dispatching::repls::{repl, repl_with_listener};
|
||||||
commands_repl, commands_repl_with_listener, repl, repl_with_listener,
|
|
||||||
};
|
#[allow(deprecated)]
|
||||||
|
pub use dispatching::repls::{commands_repl, commands_repl_with_listener};
|
||||||
|
|
||||||
pub mod dispatching;
|
pub mod dispatching;
|
||||||
pub mod error_handlers;
|
pub mod error_handlers;
|
||||||
|
|
|
@ -6,7 +6,8 @@ pub use crate::error_handlers::{LoggingErrorHandler, OnError};
|
||||||
pub use crate::respond;
|
pub use crate::respond;
|
||||||
|
|
||||||
pub use crate::dispatching::{
|
pub use crate::dispatching::{
|
||||||
dialogue::Dialogue, Dispatcher, HandlerExt as _, MessageFilterExt as _, UpdateFilterExt as _,
|
dialogue::Dialogue, repls::CommandReplExt as _, Dispatcher, HandlerExt as _,
|
||||||
|
MessageFilterExt as _, UpdateFilterExt as _,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use teloxide_core::{
|
pub use teloxide_core::{
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
//! This module contains stop [token] and stop [flag] that are used to stop
|
//! Stopping asynchronous tasks, e.g., [listeners].
|
||||||
//! async tasks, for example [listeners].
|
|
||||||
//!
|
//!
|
||||||
//! [token]: StopToken
|
|
||||||
//! [flag]: StopFlag
|
|
||||||
//! [listeners]: crate::dispatching::update_listeners
|
//! [listeners]: crate::dispatching::update_listeners
|
||||||
|
|
||||||
use std::{convert::Infallible, future::Future, pin::Pin, task};
|
use std::{convert::Infallible, future::Future, pin::Pin, task};
|
||||||
|
|
|
@ -91,7 +91,7 @@ pub use teloxide_macros::BotCommands;
|
||||||
/// Change a prefix for all commands (the default is `/`).
|
/// Change a prefix for all commands (the default is `/`).
|
||||||
///
|
///
|
||||||
/// 3. `#[command(description = "description")]`
|
/// 3. `#[command(description = "description")]`
|
||||||
/// Add a sumary description of commands before all commands.
|
/// Add a summary description of commands before all commands.
|
||||||
///
|
///
|
||||||
/// 4. `#[command(parse_with = "parser")]`
|
/// 4. `#[command(parse_with = "parser")]`
|
||||||
/// Change the parser of arguments. Possible values:
|
/// Change the parser of arguments. Possible values:
|
||||||
|
@ -115,7 +115,7 @@ pub use teloxide_macros::BotCommands;
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// - `split` - separates a messsage by a given separator (the default is the
|
/// - `split` - separates a message by a given separator (the default is the
|
||||||
/// space character) and parses each part into the corresponding arguments,
|
/// space character) and parses each part into the corresponding arguments,
|
||||||
/// which must implement [`FromStr`].
|
/// which must implement [`FromStr`].
|
||||||
///
|
///
|
||||||
|
@ -233,8 +233,9 @@ pub trait BotCommands: Sized {
|
||||||
|
|
||||||
/// Returns `PhantomData<Self>` that is used as a param of [`commands_repl`]
|
/// Returns `PhantomData<Self>` that is used as a param of [`commands_repl`]
|
||||||
///
|
///
|
||||||
/// [`commands_repl`]: (crate::repls2::commands_repl)
|
/// [`commands_repl`]: (crate::repls::commands_repl)
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
#[deprecated(note = "Use `CommandReplExt` instead")]
|
||||||
fn ty() -> PhantomData<Self> {
|
fn ty() -> PhantomData<Self> {
|
||||||
PhantomData
|
PhantomData
|
||||||
}
|
}
|
||||||
|
@ -412,9 +413,9 @@ where
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let mut words = text.split_whitespace();
|
let mut words = text.split_whitespace();
|
||||||
let mut splited = words.next()?[prefix.len()..].split('@');
|
let mut split = words.next()?[prefix.len()..].split('@');
|
||||||
let command = splited.next()?;
|
let command = split.next()?;
|
||||||
let bot = splited.next();
|
let bot = split.next();
|
||||||
match bot {
|
match bot {
|
||||||
Some(name) if name.eq_ignore_ascii_case(bot_name.as_ref()) => {}
|
Some(name) if name.eq_ignore_ascii_case(bot_name.as_ref()) => {}
|
||||||
None => {}
|
None => {}
|
||||||
|
@ -485,7 +486,7 @@ impl Display for CommandDescriptions<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The rest of tests are integrational due to problems with macro expansion in
|
// The rest of tests are integration due to problems with macro expansion in
|
||||||
// unit tests.
|
// unit tests.
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
|
@ -95,7 +95,7 @@ pub fn code_inline(s: &str) -> String {
|
||||||
/// style.
|
/// style.
|
||||||
///
|
///
|
||||||
/// Does not escape ' and " characters (as should be for usual HTML), because
|
/// Does not escape ' and " characters (as should be for usual HTML), because
|
||||||
/// they shoudn't be escaped by the [spec].
|
/// they shouldn't be escaped by the [spec].
|
||||||
///
|
///
|
||||||
/// [spec]: https://core.telegram.org/bots/api#html-style
|
/// [spec]: https://core.telegram.org/bots/api#html-style
|
||||||
#[must_use = "This function returns a new string, rather than mutating the argument, so calling it \
|
#[must_use = "This function returns a new string, rather than mutating the argument, so calling it \
|
||||||
|
@ -176,7 +176,7 @@ mod tests {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
code_block_with_lang(
|
code_block_with_lang(
|
||||||
"<p>pre-'formatted'\n & fixed-width \\code `block`</p>",
|
"<p>pre-'formatted'\n & fixed-width \\code `block`</p>",
|
||||||
"<html>\""
|
"<html>\"",
|
||||||
),
|
),
|
||||||
concat!(
|
concat!(
|
||||||
"<pre><code class=\"language-<html>"\">",
|
"<pre><code class=\"language-<html>"\">",
|
||||||
|
|
|
@ -38,7 +38,7 @@ pub fn italic(s: &str) -> String {
|
||||||
without using its output does nothing useful"]
|
without using its output does nothing useful"]
|
||||||
pub fn underline(s: &str) -> String {
|
pub fn underline(s: &str) -> String {
|
||||||
// In case of ambiguity between italic and underline entities
|
// In case of ambiguity between italic and underline entities
|
||||||
// ‘__’ is always greadily treated from left to right as beginning or end of
|
// ‘__’ is always greedily treated from left to right as beginning or end of
|
||||||
// underline entity, so instead of ___italic underline___ we should use
|
// underline entity, so instead of ___italic underline___ we should use
|
||||||
// ___italic underline_\r__, where \r is a character with code 13, which
|
// ___italic underline_\r__, where \r is a character with code 13, which
|
||||||
// will be ignored.
|
// will be ignored.
|
||||||
|
|
95
tests/rocksdb.rs
Normal file
95
tests/rocksdb.rs
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
use std::{
|
||||||
|
fmt::{Debug, Display},
|
||||||
|
fs,
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
use teloxide::{
|
||||||
|
dispatching::dialogue::{RocksDbStorage, RocksDbStorageError, Serializer, Storage},
|
||||||
|
types::ChatId,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn test_rocksdb_json() {
|
||||||
|
fs::remove_dir_all("./test_db1").ok();
|
||||||
|
fs::create_dir("./test_db1").unwrap();
|
||||||
|
let storage = RocksDbStorage::open(
|
||||||
|
"./test_db1/test_db1.rocksdb",
|
||||||
|
teloxide::dispatching::dialogue::serializer::Json,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
test_rocksdb(storage).await;
|
||||||
|
fs::remove_dir_all("./test_db1").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn test_rocksdb_bincode() {
|
||||||
|
fs::remove_dir_all("./test_db2").ok();
|
||||||
|
fs::create_dir("./test_db2").unwrap();
|
||||||
|
let storage = RocksDbStorage::open(
|
||||||
|
"./test_db2/test_db2.rocksdb",
|
||||||
|
teloxide::dispatching::dialogue::serializer::Bincode,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
test_rocksdb(storage).await;
|
||||||
|
fs::remove_dir_all("./test_db2").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn test_rocksdb_cbor() {
|
||||||
|
fs::remove_dir_all("./test_db3").ok();
|
||||||
|
fs::create_dir("./test_db3").unwrap();
|
||||||
|
let storage = RocksDbStorage::open(
|
||||||
|
"./test_db3/test_db3.rocksdb",
|
||||||
|
teloxide::dispatching::dialogue::serializer::Cbor,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
test_rocksdb(storage).await;
|
||||||
|
fs::remove_dir_all("./test_db3").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
type Dialogue = String;
|
||||||
|
|
||||||
|
macro_rules! test_dialogues {
|
||||||
|
($storage:expr, $_0:expr, $_1:expr, $_2:expr) => {
|
||||||
|
assert_eq!(Arc::clone(&$storage).get_dialogue(ChatId(1)).await.unwrap(), $_0);
|
||||||
|
assert_eq!(Arc::clone(&$storage).get_dialogue(ChatId(11)).await.unwrap(), $_1);
|
||||||
|
assert_eq!(Arc::clone(&$storage).get_dialogue(ChatId(256)).await.unwrap(), $_2);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn test_rocksdb<S>(storage: Arc<RocksDbStorage<S>>)
|
||||||
|
where
|
||||||
|
S: Send + Sync + Serializer<Dialogue> + 'static,
|
||||||
|
<S as Serializer<Dialogue>>::Error: Debug + Display,
|
||||||
|
{
|
||||||
|
test_dialogues!(storage, None, None, None);
|
||||||
|
|
||||||
|
Arc::clone(&storage).update_dialogue(ChatId(1), "ABC".to_owned()).await.unwrap();
|
||||||
|
Arc::clone(&storage).update_dialogue(ChatId(11), "DEF".to_owned()).await.unwrap();
|
||||||
|
Arc::clone(&storage).update_dialogue(ChatId(256), "GHI".to_owned()).await.unwrap();
|
||||||
|
|
||||||
|
test_dialogues!(
|
||||||
|
storage,
|
||||||
|
Some("ABC".to_owned()),
|
||||||
|
Some("DEF".to_owned()),
|
||||||
|
Some("GHI".to_owned())
|
||||||
|
);
|
||||||
|
|
||||||
|
Arc::clone(&storage).remove_dialogue(ChatId(1)).await.unwrap();
|
||||||
|
Arc::clone(&storage).remove_dialogue(ChatId(11)).await.unwrap();
|
||||||
|
Arc::clone(&storage).remove_dialogue(ChatId(256)).await.unwrap();
|
||||||
|
|
||||||
|
test_dialogues!(storage, None, None, None);
|
||||||
|
|
||||||
|
// Check that a try to remove a non-existing dialogue results in an error.
|
||||||
|
assert!(matches!(
|
||||||
|
Arc::clone(&storage).remove_dialogue(ChatId(1)).await.unwrap_err(),
|
||||||
|
RocksDbStorageError::DialogueNotFound
|
||||||
|
));
|
||||||
|
}
|
Loading…
Reference in a new issue