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
|
||||
|
||||
## 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
|
||||
|
||||
### Changed
|
||||
|
@ -75,7 +86,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
### 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
|
||||
|
||||
|
@ -188,7 +199,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
### Added
|
||||
|
||||
- `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
|
||||
|
||||
|
@ -265,7 +276,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
### Fixed
|
||||
|
||||
- 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)).
|
||||
- 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
|
||||
|
||||
- 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)).
|
||||
|
||||
## 0.2.0 - 2020-02-25
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "teloxide"
|
||||
version = "0.11.0"
|
||||
version = "0.11.1"
|
||||
edition = "2021"
|
||||
description = "An elegant Telegram bots framework for Rust"
|
||||
repository = "https://github.com/teloxide/teloxide"
|
||||
|
@ -19,6 +19,7 @@ webhooks-axum = ["webhooks", "axum", "tower", "tower-http"]
|
|||
|
||||
sqlite-storage = ["sqlx"]
|
||||
redis-storage = ["redis"]
|
||||
rocksdb-storage = ["rocksdb"]
|
||||
cbor-serializer = ["serde_cbor"]
|
||||
bincode-serializer = ["bincode"]
|
||||
|
||||
|
@ -42,6 +43,7 @@ full = [
|
|||
"webhooks-axum",
|
||||
"sqlite-storage",
|
||||
"redis-storage",
|
||||
"rocksdb-storage",
|
||||
"cbor-serializer",
|
||||
"bincode-serializer",
|
||||
"macros",
|
||||
|
@ -92,6 +94,9 @@ sqlx = { version = "0.6", optional = true, default-features = false, features =
|
|||
"sqlite",
|
||||
] }
|
||||
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 }
|
||||
bincode = { version = "1.3", 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.
|
||||
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
|
||||
|
||||
### 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`.
|
||||
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();
|
||||
```
|
||||
|
||||
```diff,rust
|
||||
```diff
|
||||
-async fn start(bot: AutoSend<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: MessageId = message.id;
|
||||
```
|
||||
```diff,rust
|
||||
```diff
|
||||
let (cid, mid): (ChatId, i32) = get_message_to_delete_from_db();
|
||||
-bot.delete_message(cid, 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.
|
||||
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?;
|
||||
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:
|
||||
|
||||
```diff,rust
|
||||
```diff
|
||||
teloxide::repl(bot, |bot: Bot, msg: Message| async move {
|
||||
bot.send_dice(msg.chat.id).await?;
|
||||
- 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.
|
||||
|
||||
"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
|
||||
|
||||
`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> {
|
||||
todo!()
|
||||
}
|
||||
|
@ -94,7 +120,7 @@ enum Command {
|
|||
|
||||
`rename` now only renames a command literally; use `rename_rule` to change the case of a command:
|
||||
|
||||
```diff,rust
|
||||
```diff
|
||||
#[derive(BotCommands)]
|
||||
- #[command(rename = "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`.
|
||||
|
||||
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.
|
||||
|
||||
|
@ -237,7 +263,7 @@ List of changed 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.
|
||||
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:
|
||||
- `get_chat_administrators`: `ChatMember` -> `Vec<ChatMember>`
|
||||
|
|
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">
|
||||
<img src="./ICON.png" width="250"/>
|
||||
|
@ -29,12 +29,13 @@
|
|||
[`dptree`]: https://github.com/teloxide/dptree
|
||||
[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)
|
||||
[Redis]: https://redis.io/
|
||||
[RocksDB]: https://rocksdb.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`].
|
||||
|
@ -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`:
|
||||
```toml
|
||||
[dependencies]
|
||||
teloxide = { version = "0.11", features = ["macros", "auto-send"] }
|
||||
teloxide = { version = "0.11", features = ["macros"] }
|
||||
log = "0.4"
|
||||
pretty_env_logger = "0.4"
|
||||
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
|
||||
|
||||
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)]
|
||||
|
||||
|
@ -131,7 +132,7 @@ async fn main() {
|
|||
|
||||
let bot = Bot::from_env();
|
||||
|
||||
teloxide::commands_repl(bot, answer, Command::ty()).await;
|
||||
Command::repl(bot, answer).await;
|
||||
}
|
||||
|
||||
#[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.
|
||||
- [`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.
|
||||
- [`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.
|
||||
|
||||
<details>
|
||||
|
|
|
@ -5,7 +5,7 @@ use teloxide::{prelude::*, types::ChatPermissions, utils::command::BotCommands};
|
|||
|
||||
// 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.
|
||||
//
|
||||
// That is, you can just call Command::descriptions() to get a description of
|
||||
|
@ -60,7 +60,7 @@ async fn main() {
|
|||
|
||||
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<()> {
|
||||
|
|
|
@ -7,7 +7,7 @@ async fn main() {
|
|||
|
||||
let bot = Bot::from_env();
|
||||
|
||||
teloxide::commands_repl(bot, answer, Command::ty()).await;
|
||||
Command::repl(bot, answer).await;
|
||||
}
|
||||
|
||||
#[derive(BotCommands, Clone)]
|
||||
|
|
|
@ -91,7 +91,7 @@ async fn got_number(
|
|||
}
|
||||
Command::Reset => {
|
||||
dialogue.reset().await?;
|
||||
bot.send_message(msg.chat.id, "Number resetted.").await?;
|
||||
bot.send_message(msg.chat.id, "Number reset.").await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
@ -32,7 +32,7 @@ async fn main() {
|
|||
.endpoint(simple_commands_handler),
|
||||
)
|
||||
.branch(
|
||||
// Filter a maintainer by a used ID.
|
||||
// Filter a maintainer by a user ID.
|
||||
dptree::filter(|cfg: ConfigParameters, msg: Message| {
|
||||
msg.from().map(|user| user.id == cfg.bot_maintainer).unwrap_or_default()
|
||||
})
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
// heroku create --buildpack emk/rust
|
||||
// ```
|
||||
//
|
||||
// To set buildpack for existing applicaton:
|
||||
// To set buildpack for existing application:
|
||||
//
|
||||
// ```
|
||||
// heroku buildpacks:set emk/rust
|
||||
|
|
|
@ -9,6 +9,9 @@ mod redis_storage;
|
|||
#[cfg(feature = "sqlite-storage")]
|
||||
mod sqlite_storage;
|
||||
|
||||
#[cfg(feature = "rocksdb-storage")]
|
||||
mod rocksdb_storage;
|
||||
|
||||
use futures::future::BoxFuture;
|
||||
use teloxide_core::types::ChatId;
|
||||
|
||||
|
@ -25,6 +28,9 @@ use std::sync::Arc;
|
|||
#[cfg(feature = "sqlite-storage")]
|
||||
pub use sqlite_storage::{SqliteStorage, SqliteStorageError};
|
||||
|
||||
#[cfg(feature = "rocksdb-storage")]
|
||||
pub use rocksdb_storage::{RocksDbStorage, RocksDbStorageError};
|
||||
|
||||
/// A storage with an erased error type.
|
||||
pub type ErasedStorage<D> =
|
||||
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`].
|
||||
/// - [`RedisStorage`] -- a Redis-based storage.
|
||||
/// - [`RocksDbStorage`] -- a RocksDB-based persistent storage.
|
||||
/// - [`SqliteStorage`] -- an SQLite-based persistent storage.
|
||||
///
|
||||
/// [`InMemStorage`]: crate::dispatching::dialogue::InMemStorage
|
||||
/// [`RedisStorage`]: crate::dispatching::dialogue::RedisStorage
|
||||
/// [`RocksDbStorage`]: crate::dispatching::dialogue::RocksDbStorage
|
||||
/// [`SqliteStorage`]: crate::dispatching::dialogue::SqliteStorage
|
||||
pub trait Storage<D> {
|
||||
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 repl;
|
||||
|
||||
pub use commands_repl::CommandReplExt;
|
||||
#[allow(deprecated)]
|
||||
pub use commands_repl::{commands_repl, commands_repl_with_listener};
|
||||
pub use repl::{repl, repl_with_listener};
|
||||
|
|
|
@ -8,8 +8,147 @@ use crate::{
|
|||
utils::command::BotCommands,
|
||||
};
|
||||
use dptree::di::{DependencyMap, Injectable};
|
||||
use futures::future::BoxFuture;
|
||||
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.
|
||||
//
|
||||
///
|
||||
|
@ -59,6 +198,7 @@ use std::{fmt::Debug, marker::PhantomData};
|
|||
#[doc = include_str!("caution.md")]
|
||||
///
|
||||
#[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>)
|
||||
where
|
||||
R: Requester + Clone + Send + Sync + 'static,
|
||||
|
@ -68,6 +208,7 @@ where
|
|||
{
|
||||
let cloned_bot = bot.clone();
|
||||
|
||||
#[allow(deprecated)]
|
||||
commands_repl_with_listener(
|
||||
bot,
|
||||
handler,
|
||||
|
@ -127,6 +268,7 @@ where
|
|||
#[doc = include_str!("caution.md")]
|
||||
///
|
||||
#[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>(
|
||||
bot: R,
|
||||
handler: H,
|
||||
|
|
|
@ -225,7 +225,7 @@ where
|
|||
///
|
||||
/// C->>P: next
|
||||
///
|
||||
/// P->>T: *Acknolegment of update(5)*
|
||||
/// P->>T: *Acknowledgement of update(5)*
|
||||
/// T->>P: ok
|
||||
///
|
||||
/// P->>C: None
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
## Cargo features
|
||||
|
||||
| Feature | Description |
|
||||
|----------------------|--------------------------------------------------------------------------------------------|
|
||||
| `webhooks` | Enables general webhook utilities (almost useless on its own) |
|
||||
| `webhooks-axum` | Enables webhook implementation based on axum framework |
|
||||
|----------------------|-------------|
|
||||
| `webhooks` | Enables general webhook utilities (almost useless on its own). |
|
||||
| `webhooks-axum` | Enables webhook implementation based on axum framework. |
|
||||
| `macros` | Re-exports macros from [`teloxide-macros`]. |
|
||||
| `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**). |
|
||||
|
@ -16,12 +16,13 @@
|
|||
| `native-tls` | Enables the [`native-tls`] TLS implementation (**enabled by default**). |
|
||||
| `rustls` | Enables the [`rustls`] TLS implementation. |
|
||||
| `redis-storage` | Enables the [Redis] storage support for dialogues. |
|
||||
| `rocksdb-storage` | Enables the [RocksDB] storage support for dialogues. |
|
||||
| `sqlite-storage` | Enables the [Sqlite] storage support for dialogues. |
|
||||
| `cbor-serializer` | Enables the [CBOR] serializer for dialogues. |
|
||||
| `bincode-serializer` | Enables the [Bincode] serializer for dialogues. |
|
||||
|
||||
|
||||
[Redis]: https://redis.io/
|
||||
[RocksDB]: https://rocksdb.org/
|
||||
[Sqlite]: https://www.sqlite.org/
|
||||
[CBOR]: https://en.wikipedia.org/wiki/CBOR
|
||||
[Bincode]: https://github.com/servo/bincode
|
||||
|
|
|
@ -58,9 +58,10 @@
|
|||
#![allow(clippy::nonstandard_macro_braces)]
|
||||
|
||||
#[cfg(feature = "ctrlc_handler")]
|
||||
pub use dispatching::repls::{
|
||||
commands_repl, commands_repl_with_listener, repl, repl_with_listener,
|
||||
};
|
||||
pub use dispatching::repls::{repl, repl_with_listener};
|
||||
|
||||
#[allow(deprecated)]
|
||||
pub use dispatching::repls::{commands_repl, commands_repl_with_listener};
|
||||
|
||||
pub mod dispatching;
|
||||
pub mod error_handlers;
|
||||
|
|
|
@ -6,7 +6,8 @@ pub use crate::error_handlers::{LoggingErrorHandler, OnError};
|
|||
pub use crate::respond;
|
||||
|
||||
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::{
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
//! This module contains stop [token] and stop [flag] that are used to stop
|
||||
//! async tasks, for example [listeners].
|
||||
//! Stopping asynchronous tasks, e.g., [listeners].
|
||||
//!
|
||||
//! [token]: StopToken
|
||||
//! [flag]: StopFlag
|
||||
//! [listeners]: crate::dispatching::update_listeners
|
||||
|
||||
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 `/`).
|
||||
///
|
||||
/// 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")]`
|
||||
/// 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,
|
||||
/// which must implement [`FromStr`].
|
||||
///
|
||||
|
@ -233,8 +233,9 @@ pub trait BotCommands: Sized {
|
|||
|
||||
/// 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]
|
||||
#[deprecated(note = "Use `CommandReplExt` instead")]
|
||||
fn ty() -> PhantomData<Self> {
|
||||
PhantomData
|
||||
}
|
||||
|
@ -412,9 +413,9 @@ where
|
|||
return None;
|
||||
}
|
||||
let mut words = text.split_whitespace();
|
||||
let mut splited = words.next()?[prefix.len()..].split('@');
|
||||
let command = splited.next()?;
|
||||
let bot = splited.next();
|
||||
let mut split = words.next()?[prefix.len()..].split('@');
|
||||
let command = split.next()?;
|
||||
let bot = split.next();
|
||||
match bot {
|
||||
Some(name) if name.eq_ignore_ascii_case(bot_name.as_ref()) => {}
|
||||
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.
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
|
|
@ -95,7 +95,7 @@ pub fn code_inline(s: &str) -> String {
|
|||
/// style.
|
||||
///
|
||||
/// 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
|
||||
#[must_use = "This function returns a new string, rather than mutating the argument, so calling it \
|
||||
|
@ -176,7 +176,7 @@ mod tests {
|
|||
assert_eq!(
|
||||
code_block_with_lang(
|
||||
"<p>pre-'formatted'\n & fixed-width \\code `block`</p>",
|
||||
"<html>\""
|
||||
"<html>\"",
|
||||
),
|
||||
concat!(
|
||||
"<pre><code class=\"language-<html>"\">",
|
||||
|
|
|
@ -38,7 +38,7 @@ pub fn italic(s: &str) -> String {
|
|||
without using its output does nothing useful"]
|
||||
pub fn underline(s: &str) -> String {
|
||||
// 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
|
||||
// ___italic underline_\r__, where \r is a character with code 13, which
|
||||
// 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