mirror of
https://github.com/teloxide/teloxide.git
synced 2024-12-22 14:35:36 +01:00
commit
4a32963901
47 changed files with 1313 additions and 981 deletions
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
|
@ -15,12 +15,12 @@ env:
|
|||
CARGO_NET_RETRY: 10
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
|
||||
rust_nightly: nightly-2022-07-01
|
||||
rust_nightly: nightly-2022-09-01
|
||||
# When updating this, also update:
|
||||
# - README.md
|
||||
# - src/lib.rs
|
||||
# - down below in a matrix
|
||||
rust_msrv: 1.58.0
|
||||
rust_msrv: 1.64.0
|
||||
|
||||
jobs:
|
||||
# Depends on all action that are required for a "successful" CI run.
|
||||
|
@ -82,10 +82,10 @@ jobs:
|
|||
toolchain: beta
|
||||
features: "--features full"
|
||||
- rust: nightly
|
||||
toolchain: nightly-2022-07-01
|
||||
toolchain: nightly-2022-09-01
|
||||
features: "--all-features"
|
||||
- rust: msrv
|
||||
toolchain: 1.58.0
|
||||
toolchain: 1.64.0
|
||||
features: "--features full"
|
||||
|
||||
steps:
|
||||
|
|
299
CHANGELOG.md
299
CHANGELOG.md
|
@ -6,35 +6,63 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## unreleased
|
||||
|
||||
## 0.11.0 - 2022-10-07
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated `teloxide-macros` to v0.7.0; see its [changelog](https://github.com/teloxide/teloxide-macros/blob/master/CHANGELOG.md#070---2022-10-06) for more
|
||||
- Updated `teloxide-core` to v0.8.0; see its [changelog](https://github.com/teloxide/teloxide-core/blob/master/CHANGELOG.md#080---2022-10-03) for more
|
||||
- `UpdateListener` now has an associated type `Err` instead of a generic
|
||||
- `AsUpdateStream` now has an associated type `StreamErr` instead of a generic
|
||||
- Rename `dispatching::stop_token::{AsyncStopToken, AsyncStopFlag}` => `stop::{StopToken, StopFlag}`
|
||||
- Replace the generic error type `E` with `RequestError` for REPLs (`repl(_with_listener)`, `commands_repl(_with_listener)`)
|
||||
- The following functions are now `#[must_use]`:
|
||||
- `BotCommands::ty`.
|
||||
- `CommandDescriptions::{new, global_description, username, username_from_me}`.
|
||||
- `teloxide::filter_command`.
|
||||
- `teloxide::dispatching::dialogue::enter`.
|
||||
- `BotCommands::parse` now accept `bot_username` as `&str`
|
||||
|
||||
### Added
|
||||
|
||||
- `requests::ResponseResult` to `prelude`
|
||||
|
||||
### Removed
|
||||
|
||||
- `dispatching::stop_token::StopToken` trait (all uses are replaced with `stop::StopToken` structure)
|
||||
- Some previously deprecated items
|
||||
- `enable_logging!`, `enable_logging_with_filter!`
|
||||
- `HandlerFactory`, `HandlerExt::dispatch_by`
|
||||
|
||||
## 0.10.1 - 2022-07-22
|
||||
|
||||
### Fixed
|
||||
|
||||
- Mark the following functions with `#[must_use]` ([PR 457](https://github.com/teloxide/teloxide/pull/457)):
|
||||
- `TraceStorage::into_inner`.
|
||||
- `AsyncStopToken::new_pair`.
|
||||
- `AsyncStopFlag::is_stopped`.
|
||||
- All from `crate::utils::{html, markdown}`.
|
||||
- Rendering of GIFs in lib.rs and crates.io ([PR 681](https://github.com/teloxide/teloxide/pull/681)).
|
||||
- Mark the following functions with `#[must_use]` ([PR 457](https://github.com/teloxide/teloxide/pull/457)):
|
||||
- `TraceStorage::into_inner`.
|
||||
- `AsyncStopToken::new_pair`.
|
||||
- `AsyncStopFlag::is_stopped`.
|
||||
- All from `crate::utils::{html, markdown}`.
|
||||
- Rendering of GIFs in lib.rs and crates.io ([PR 681](https://github.com/teloxide/teloxide/pull/681)).
|
||||
|
||||
## 0.10.0 - 2022-07-21
|
||||
|
||||
### Added
|
||||
|
||||
- Security checks based on `secret_token` param of `set_webhook` to built-in webhooks.
|
||||
- `dispatching::update_listeners::{PollingBuilder, Polling, PollingStream}`.
|
||||
- `DispatcherBuilder::enable_ctrlc_handler` method.
|
||||
- Security checks based on `secret_token` param of `set_webhook` to built-in webhooks.
|
||||
- `dispatching::update_listeners::{PollingBuilder, Polling, PollingStream}`.
|
||||
- `DispatcherBuilder::enable_ctrlc_handler` method.
|
||||
|
||||
### Fixed
|
||||
|
||||
- `Dispatcher` no longer "leaks" memory for every inactive user ([PR 657](https://github.com/teloxide/teloxide/pull/657)).
|
||||
- Allow specifying a path to a custom command parser in `parse_with` ([issue 668](https://github.com/teloxide/teloxide/issues/668)).
|
||||
- `Dispatcher` no longer "leaks" memory for every inactive user ([PR 657](https://github.com/teloxide/teloxide/pull/657)).
|
||||
- Allow specifying a path to a custom command parser in `parse_with` ([issue 668](https://github.com/teloxide/teloxide/issues/668)).
|
||||
|
||||
### Changed
|
||||
|
||||
- Add the `Key: Clone` requirement for `impl Dispatcher` [**BC**].
|
||||
- `dispatching::update_listeners::{polling_default, polling}` now return a named, `Polling<_>` type.
|
||||
- Update teloxide-core to v0.7.0 with Bot API 6.1 support, see [its changelog][core07c] for more information [**BC**].
|
||||
- Add the `Key: Clone` requirement for `impl Dispatcher` [**BC**].
|
||||
- `dispatching::update_listeners::{polling_default, polling}` now return a named, `Polling<_>` type.
|
||||
- Update `teloxide-core` to v0.7.0 with Bot API 6.1 support, see [its changelog][core07c] for more information [**BC**].
|
||||
|
||||
[core07c]: https://github.com/teloxide/teloxide-core/blob/master/CHANGELOG.md#070---2022-07-19
|
||||
|
||||
|
@ -47,24 +75,24 @@ 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 contaiins some escapable symbols eg '.'
|
||||
|
||||
## 0.9.1 - 2022-05-27
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix `#[command(rename = "...")]` for custom command names ([issue 633](https://github.com/teloxide/teloxide/issues/633)).
|
||||
- Fix `#[command(rename = "...")]` for custom command names ([issue 633](https://github.com/teloxide/teloxide/issues/633)).
|
||||
|
||||
## 0.9.0 - 2022-04-27
|
||||
|
||||
### Added
|
||||
|
||||
- The `dispatching::filter_command` function (also accessible as `teloxide::filter_command`) as a shortcut for `dptree::entry().filter_command()`.
|
||||
- Re-export `dptree::case!` as `teloxide::handler!` (the former is preferred for new code).
|
||||
- The `dispatching::filter_command` function (also accessible as `teloxide::filter_command`) as a shortcut for `dptree::entry().filter_command()`.
|
||||
- Re-export `dptree::case!` as `teloxide::handler!` (the former is preferred for new code).
|
||||
|
||||
### Changed
|
||||
|
||||
- Update teloxide-core to v0.6.0 with [Bot API 6.0] support [**BC**].
|
||||
- Update `teloxide-core` to v0.6.0 with [Bot API 6.0] support [**BC**].
|
||||
|
||||
[Bot API 6.0]: https://core.telegram.org/bots/api#april-16-2022
|
||||
|
||||
|
@ -72,46 +100,46 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
### Fixed
|
||||
|
||||
- Fix the broken `#[derive(DialogueState)]` (function return type `dptree::Handler`).
|
||||
- Fix the broken `#[derive(DialogueState)]` (function return type `dptree::Handler`).
|
||||
|
||||
## 0.8.1 - 2022-04-24
|
||||
|
||||
### Added
|
||||
|
||||
- Implement `GetChatId` for `Update`.
|
||||
- The `dialogue::enter()` function as a shortcut for `dptree::entry().enter_dialogue()`.
|
||||
- Implement `GetChatId` for `Update`.
|
||||
- The `dialogue::enter()` function as a shortcut for `dptree::entry().enter_dialogue()`.
|
||||
|
||||
## 0.8.0 - 2022-04-18
|
||||
|
||||
### Removed
|
||||
|
||||
- The old dispatching system and related stuff: `dispatching`, `utils::UpState`, `prelude`, `repls2`, `crate::{dialogues_repl, dialogues_repl_with_listener}`, and `#[teloxide(subtransition)]` [**BC**].
|
||||
- The old dispatching system and related stuff: `dispatching`, `utils::UpState`, `prelude`, `repls2`, `crate::{dialogues_repl, dialogues_repl_with_listener}`, and `#[teloxide(subtransition)]` [**BC**].
|
||||
|
||||
### Added
|
||||
|
||||
- The new API for dialogue handlers: `teloxide::handler!` ([issue 567](https://github.com/teloxide/teloxide/issues/567)).
|
||||
- Built-in webhooks support via `teloxide::dispatching::update_listeners::webhooks` module.
|
||||
- `Dialogue::chat_id` for retrieving a chat ID from a dialogue.
|
||||
- The new API for dialogue handlers: `teloxide::handler!` ([issue 567](https://github.com/teloxide/teloxide/issues/567)).
|
||||
- Built-in webhooks support via `teloxide::dispatching::update_listeners::webhooks` module.
|
||||
- `Dialogue::chat_id` for retrieving a chat ID from a dialogue.
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated `teloxide-core` from version `0.4.5` to version [`0.5.0`](https://github.com/teloxide/teloxide-core/releases/tag/v0.5.0) [**BC**]
|
||||
- Rename `dispatching2` => `dispatching` [**BC**].
|
||||
- Rename `prelude2` => `prelude` [**BC**].
|
||||
- Move `update_listeners`, `stop_token`, `IdleShutdownError`, and `ShutdownToken` from the old `dispatching` to the new `dispatching` (previously `dispatching2`).
|
||||
- Replace `crate::{commands_repl, commands_repl_with_listener, repl, repl_with_listener}` with those of the new `dispatching` [**BC**].
|
||||
- `UpdateListener::StopToken` is now always `Send` [**BC**].
|
||||
- Rename `BotCommand` trait to `BotCommands` [**BC**].
|
||||
- `BotCommands::descriptions` now returns `CommandDescriptions` instead of `String` [**BC**].
|
||||
- Mark `Dialogue::new` as `#[must_use]`.
|
||||
- Updated `teloxide-core` from version `0.4.5` to version [`0.5.0`](https://github.com/teloxide/teloxide-core/releases/tag/v0.5.0) [**BC**]
|
||||
- Rename `dispatching2` => `dispatching` [**BC**].
|
||||
- Rename `prelude2` => `prelude` [**BC**].
|
||||
- Move `update_listeners`, `stop_token`, `IdleShutdownError`, and `ShutdownToken` from the old `dispatching` to the new `dispatching` (previously `dispatching2`).
|
||||
- Replace `crate::{commands_repl, commands_repl_with_listener, repl, repl_with_listener}` with those of the new `dispatching` [**BC**].
|
||||
- `UpdateListener::StopToken` is now always `Send` [**BC**].
|
||||
- Rename `BotCommand` trait to `BotCommands` [**BC**].
|
||||
- `BotCommands::descriptions` now returns `CommandDescriptions` instead of `String` [**BC**].
|
||||
- Mark `Dialogue::new` as `#[must_use]`.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Concurrent update handling in the new dispatcher ([issue 536](https://github.com/teloxide/teloxide/issues/536)).
|
||||
- Concurrent update handling in the new dispatcher ([issue 536](https://github.com/teloxide/teloxide/issues/536)).
|
||||
|
||||
### Deprecated
|
||||
|
||||
- `HandlerFactory` and `HandlerExt::dispatch_by` in favour of `teloxide::handler!`.
|
||||
- `HandlerFactory` and `HandlerExt::dispatch_by` in favour of `teloxide::handler!`.
|
||||
|
||||
## 0.7.3 - 2022-04-03
|
||||
|
||||
|
@ -159,8 +187,8 @@ 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`.
|
||||
- `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`.
|
||||
|
||||
### Changed
|
||||
|
||||
|
@ -199,81 +227,83 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
### Added
|
||||
|
||||
- `Storage::get_dialogue` to obtain a dialogue indexed by a chat ID.
|
||||
- `InMemStorageError` with a single variant `DialogueNotFound` to be returned from `InMemStorage::remove_dialogue`.
|
||||
- `RedisStorageError::DialogueNotFound` and `SqliteStorageError::DialogueNotFound` to be returned from `Storage::remove_dialogue`.
|
||||
- A way to `shutdown` dispatcher
|
||||
- `Dispatcher::shutdown_token` function.
|
||||
- `ShutdownToken` with a `shutdown` function.
|
||||
- `Dispatcher::setup_ctrlc_handler` function ([issue 153](https://github.com/teloxide/teloxide/issues/153)).
|
||||
- `IdleShutdownError`
|
||||
- Automatic update filtering ([issue 389](https://github.com/teloxide/teloxide/issues/389)).
|
||||
- Added reply shortcut to every kind of messages ([PR 404](https://github.com/teloxide/teloxide/pull/404)).
|
||||
- `Storage::get_dialogue` to obtain a dialogue indexed by a chat ID.
|
||||
- `InMemStorageError` with a single variant `DialogueNotFound` to be returned from `InMemStorage::remove_dialogue`.
|
||||
- `RedisStorageError::DialogueNotFound` and `SqliteStorageError::DialogueNotFound` to be returned from `Storage::remove_dialogue`.
|
||||
- A way to `shutdown` dispatcher
|
||||
- `Dispatcher::shutdown_token` function.
|
||||
- `ShutdownToken` with a `shutdown` function.
|
||||
- `Dispatcher::setup_ctrlc_handler` function ([issue 153](https://github.com/teloxide/teloxide/issues/153)).
|
||||
- `IdleShutdownError`
|
||||
- Automatic update filtering ([issue 389](https://github.com/teloxide/teloxide/issues/389)).
|
||||
- Added reply shortcut to every kind of messages ([PR 404](https://github.com/teloxide/teloxide/pull/404)).
|
||||
|
||||
### Changed
|
||||
|
||||
- Do not return a dialogue from `Storage::{remove_dialogue, update_dialogue}`.
|
||||
- Return an error from `Storage::remove_dialogue` if a dialogue does not exist.
|
||||
- Require `D: Clone` in `dialogues_repl(_with_listener)` and `InMemStorage`.
|
||||
- Automatically delete a webhook if it was set up in `update_listeners::polling_default` (thereby making it `async`, [issue 319](https://github.com/teloxide/teloxide/issues/319)).
|
||||
- `polling` and `polling_default` now require `R: 'static`
|
||||
- Refactor `UpdateListener` trait:
|
||||
- Add a `StopToken` associated type.
|
||||
- It must implement a new `StopToken` trait which has the only function `fn stop(self);`
|
||||
- Add a `stop_token` function that returns `Self::StopToken` and allows stopping the listener later ([issue 166](https://github.com/teloxide/teloxide/issues/166)).
|
||||
- Remove blanked implementation.
|
||||
- Remove `Stream` from super traits.
|
||||
- Add `AsUpdateStream` to super traits.
|
||||
- Add an `AsUpdateStream` trait that allows turning implementors into streams of updates (GAT workaround).
|
||||
- Add a `timeout_hint` function (with a default implementation).
|
||||
- `Dispatcher::dispatch` and `Dispatcher::dispatch_with_listener` now require mutable reference to self.
|
||||
- Repls can now be stopped by `^C` signal.
|
||||
- `Noop` and `AsyncStopToken`stop tokens.
|
||||
- `StatefulListener`.
|
||||
- Emit not only errors but also warnings and general information from teloxide, when set up by `enable_logging!`.
|
||||
- Use `i64` instead of `i32` for `user_id` in `html::user_mention` and `markdown::user_mention`.
|
||||
- Updated to `teloxide-core` `v0.3.0` (see it's [changelog](https://github.com/teloxide/teloxide-core/blob/master/CHANGELOG.md#030---2021-07-05) for more)
|
||||
- Do not return a dialogue from `Storage::{remove_dialogue, update_dialogue}`.
|
||||
- Return an error from `Storage::remove_dialogue` if a dialogue does not exist.
|
||||
- Require `D: Clone` in `dialogues_repl(_with_listener)` and `InMemStorage`.
|
||||
- Automatically delete a webhook if it was set up in `update_listeners::polling_default` (thereby making it `async`, [issue 319](https://github.com/teloxide/teloxide/issues/319)).
|
||||
- `polling` and `polling_default` now require `R: 'static`
|
||||
- Refactor `UpdateListener` trait:
|
||||
- Add a `StopToken` associated type.
|
||||
- It must implement a new `StopToken` trait which has the only function `fn stop(self);`
|
||||
- Add a `stop_token` function that returns `Self::StopToken` and allows stopping the listener later ([issue 166](https://github.com/teloxide/teloxide/issues/166)).
|
||||
- Remove blanked implementation.
|
||||
- Remove `Stream` from super traits.
|
||||
- Add `AsUpdateStream` to super traits.
|
||||
- Add an `AsUpdateStream` trait that allows turning implementors into streams of updates (GAT workaround).
|
||||
- Add a `timeout_hint` function (with a default implementation).
|
||||
- `Dispatcher::dispatch` and `Dispatcher::dispatch_with_listener` now require mutable reference to self.
|
||||
- Repls can now be stopped by `^C` signal.
|
||||
- `Noop` and `AsyncStopToken`stop tokens.
|
||||
- `StatefulListener`.
|
||||
- Emit not only errors but also warnings and general information from teloxide, when set up by `enable_logging!`.
|
||||
- Use `i64` instead of `i32` for `user_id` in `html::user_mention` and `markdown::user_mention`.
|
||||
- Updated to `teloxide-core` `v0.3.0` (see it's [changelog](https://github.com/teloxide/teloxide-core/blob/master/CHANGELOG.md#030---2021-07-05) for more)
|
||||
|
||||
### 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)).
|
||||
- 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]`.
|
||||
- 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)).
|
||||
- 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]`.
|
||||
|
||||
## 0.4.0 - 2021-03-22
|
||||
|
||||
### Added
|
||||
- Integrate [teloxide-core].
|
||||
- Allow arbitrary error types to be returned from (sub)transitions ([issue 242](https://github.com/teloxide/teloxide/issues/242)).
|
||||
- The `respond` function, a shortcut for `ResponseResult::Ok(())`.
|
||||
- The `sqlite-storage` feature -- enables SQLite support.
|
||||
- `Dispatcher::{my_chat_members_handler, chat_members_handler}`
|
||||
|
||||
- Integrate [teloxide-core].
|
||||
- Allow arbitrary error types to be returned from (sub)transitions ([issue 242](https://github.com/teloxide/teloxide/issues/242)).
|
||||
- The `respond` function, a shortcut for `ResponseResult::Ok(())`.
|
||||
- The `sqlite-storage` feature -- enables SQLite support.
|
||||
- `Dispatcher::{my_chat_members_handler, chat_members_handler}`
|
||||
|
||||
[teloxide-core]: https://github.com/teloxide/teloxide-core
|
||||
|
||||
### Deprecated
|
||||
|
||||
- `UpdateWithCx::answer_str`
|
||||
- `UpdateWithCx::answer_str`
|
||||
|
||||
### Fixed
|
||||
|
||||
- Hide `SubtransitionOutputType` from the docs.
|
||||
- Hide `SubtransitionOutputType` from the docs.
|
||||
|
||||
### Changed
|
||||
- Export `teloxide_macros::teloxide` in `prelude`.
|
||||
- `dispatching::dialogue::serializer::{JSON -> Json, CBOR -> Cbor}`
|
||||
- Allow `bot_name` be `N`, where `N: Into<String> + ...` in `commands_repl` & `commands_repl_with_listener`.
|
||||
- 'Edit methods' (namely `edit_message_live_location`, `stop_message_live_location`, `edit_message_text`,
|
||||
|
||||
- Export `teloxide_macros::teloxide` in `prelude`.
|
||||
- `dispatching::dialogue::serializer::{JSON -> Json, CBOR -> Cbor}`
|
||||
- Allow `bot_name` be `N`, where `N: Into<String> + ...` in `commands_repl` & `commands_repl_with_listener`.
|
||||
- 'Edit methods' (namely `edit_message_live_location`, `stop_message_live_location`, `edit_message_text`,
|
||||
`edit_message_caption`, `edit_message_media` and `edit_message_reply_markup`) are split into common and inline
|
||||
versions (e.g.: `edit_message_text` and `edit_inline_message_text`). Instead of `ChatOrInlineMessage` common versions
|
||||
accept `chat_id: impl Into<ChatId>` and `message_id: i32` whereas inline versions accept
|
||||
`inline_message_id: impl Into<String>`. Also note that return type of inline versions is `True` ([issue 253], [pr 257])
|
||||
- `ChatOrInlineMessage` is renamed to `TargetMessage`, it's `::Chat` variant is renamed to `::Common`,
|
||||
- `ChatOrInlineMessage` is renamed to `TargetMessage`, it's `::Chat` variant is renamed to `::Common`,
|
||||
`#[non_exhaustive]` annotation is removed from the enum, type of `TargetMessage::Inline::inline_message_id` changed
|
||||
`i32` => `String`. `TargetMessage` now implements `From<String>`, `get_game_high_scores` and `set_game_score` use
|
||||
`Into<TargetMessage>` to accept `String`s. ([issue 253], [pr 257])
|
||||
- Remove `ResponseResult` from `prelude`.
|
||||
- Remove `ResponseResult` from `prelude`.
|
||||
|
||||
[issue 253]: https://github.com/teloxide/teloxide/issues/253
|
||||
[pr 257]: https://github.com/teloxide/teloxide/pull/257
|
||||
|
@ -282,75 +312,88 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
### Fixed
|
||||
|
||||
- Failing compilation with `serde::export` ([issue 328](https://github.com/teloxide/teloxide/issues/328)).
|
||||
- Failing compilation with `serde::export` ([issue 328](https://github.com/teloxide/teloxide/issues/328)).
|
||||
|
||||
## 0.3.3 - 2020-10-30
|
||||
|
||||
### Fixed
|
||||
- The `dice` field from `MessageDice` is public now ([issue 306](https://github.com/teloxide/teloxide/issues/306))
|
||||
|
||||
- The `dice` field from `MessageDice` is public now ([issue 306](https://github.com/teloxide/teloxide/issues/306))
|
||||
|
||||
## 0.3.2 - 2020-10-23
|
||||
|
||||
### Added
|
||||
- `LoginUrl::new` ([issue 298](https://github.com/teloxide/teloxide/issues/298))
|
||||
|
||||
- `LoginUrl::new` ([issue 298](https://github.com/teloxide/teloxide/issues/298))
|
||||
|
||||
## 0.3.1 - 2020-08-25
|
||||
|
||||
### Added
|
||||
- `Bot::builder` method ([PR 269](https://github.com/teloxide/teloxide/pull/269)).
|
||||
|
||||
- `Bot::builder` method ([PR 269](https://github.com/teloxide/teloxide/pull/269)).
|
||||
|
||||
## 0.3.0 - 2020-07-31
|
||||
|
||||
### Added
|
||||
- Support for typed bot commands ([issue 152](https://github.com/teloxide/teloxide/issues/152)).
|
||||
- `BotBuilder`, which allows setting a default `ParseMode`.
|
||||
- The `Transition`, `Subtransition`, `SubtransitionOutputType` traits.
|
||||
- A nicer approach to manage dialogues via `#[derive(Transition)]` + `#[teloxide(subtransition)]` (see [`examples/dialogue_bot`](https://github.com/teloxide/teloxide/tree/af2aa218e7bfc442ab4475023a1c661834f576fc/examples/dialogue_bot)).
|
||||
- The `redis-storage` feature -- enables the Redis support.
|
||||
- The `cbor-serializer` feature -- enables the `CBOR` serializer for dialogues.
|
||||
- The `bincode-serializer` feature -- enables the `Bincode` serializer for dialogues.
|
||||
- The `frunk` feature -- enables `teloxide::utils::UpState`, which allows mapping from a structure of `field1, ..., fieldN` to a structure of `field1, ..., fieldN, fieldN+1`.
|
||||
- Upgrade to v4.9 Telegram bots API.
|
||||
- `teloxide::utils::client_from_env` -- constructs a client from the `TELOXIDE_TOKEN` environmental variable.
|
||||
- Import `Transition`, `TransitionIn`, `TransitionOut`, `UpState` to `teloxide::prelude`.
|
||||
- Import `repl`, `commands_repl` to `teloxide`.
|
||||
- Let users inspect an unknown API error using `ApiErrorKind::Unknown(String)`. All the known API errors are placed into `KnownApiErrorKind`.
|
||||
- Setters to all the API types.
|
||||
- `teloxide::dispatching::dialogue::serializer` -- various serializers for memory storages. The `Serializer` trait, `Bincode`, `CBOR`, `JSON`.
|
||||
- `teloxide::{repl, repl_with_listener, commands_repl, commands_repl_with_listener, dialogues_repl, dialogues_repl_with_listener}`
|
||||
- `InputFile::Memory`
|
||||
- Option to hide a command from description ([issue 217](https://github.com/teloxide/teloxide/issues/217)).
|
||||
- Respect the `TELOXIDE_PROXY` environment variable in `Bot::from_env`.
|
||||
|
||||
- Support for typed bot commands ([issue 152](https://github.com/teloxide/teloxide/issues/152)).
|
||||
- `BotBuilder`, which allows setting a default `ParseMode`.
|
||||
- The `Transition`, `Subtransition`, `SubtransitionOutputType` traits.
|
||||
- A nicer approach to manage dialogues via `#[derive(Transition)]` + `#[teloxide(subtransition)]` (see [`examples/dialogue_bot`](https://github.com/teloxide/teloxide/tree/af2aa218e7bfc442ab4475023a1c661834f576fc/examples/dialogue_bot)).
|
||||
- The `redis-storage` feature -- enables the Redis support.
|
||||
- The `cbor-serializer` feature -- enables the `CBOR` serializer for dialogues.
|
||||
- The `bincode-serializer` feature -- enables the `Bincode` serializer for dialogues.
|
||||
- The `frunk` feature -- enables `teloxide::utils::UpState`, which allows mapping from a structure of `field1, ..., fieldN` to a structure of `field1, ..., fieldN, fieldN+1`.
|
||||
- Upgrade to v4.9 Telegram bots API.
|
||||
- `teloxide::utils::client_from_env` -- constructs a client from the `TELOXIDE_TOKEN` environmental variable.
|
||||
- Import `Transition`, `TransitionIn`, `TransitionOut`, `UpState` to `teloxide::prelude`.
|
||||
- Import `repl`, `commands_repl` to `teloxide`.
|
||||
- Let users inspect an unknown API error using `ApiErrorKind::Unknown(String)`. All the known API errors are placed into `KnownApiErrorKind`.
|
||||
- Setters to all the API types.
|
||||
- `teloxide::dispatching::dialogue::serializer` -- various serializers for memory storages. The `Serializer` trait, `Bincode`, `CBOR`, `JSON`.
|
||||
- `teloxide::{repl, repl_with_listener, commands_repl, commands_repl_with_listener, dialogues_repl, dialogues_repl_with_listener}`
|
||||
- `InputFile::Memory`
|
||||
- Option to hide a command from description ([issue 217](https://github.com/teloxide/teloxide/issues/217)).
|
||||
- Respect the `TELOXIDE_PROXY` environment variable in `Bot::from_env`.
|
||||
|
||||
### Deprecated
|
||||
- `Bot::{from_env_with_client, new, with_client}`
|
||||
|
||||
- `Bot::{from_env_with_client, new, with_client}`
|
||||
|
||||
### Changed
|
||||
- `DialogueDispatcherHandlerCx` -> `DialogueWithCx`.
|
||||
- `DispatcherHandlerCx` -> `UpdateWithCx`.
|
||||
- Now provided description of unknown telegram error, by splitting ApiErrorKind at `ApiErrorKind` and `ApiErrorKindKnown` enums ([issue 199](https://github.com/teloxide/teloxide/issues/199)).
|
||||
- Extract `Bot` from `Arc` ([issue 216](https://github.com/teloxide/teloxide/issues/230)).
|
||||
- Mark all the API types as `#[non_exhaustive]`.
|
||||
- Replace all `mime_type: String` with `MimeWrapper`.
|
||||
|
||||
- `DialogueDispatcherHandlerCx` -> `DialogueWithCx`.
|
||||
- `DispatcherHandlerCx` -> `UpdateWithCx`.
|
||||
- Now provided description of unknown telegram error, by splitting ApiErrorKind at `ApiErrorKind` and `ApiErrorKindKnown` enums ([issue 199](https://github.com/teloxide/teloxide/issues/199)).
|
||||
- Extract `Bot` from `Arc` ([issue 216](https://github.com/teloxide/teloxide/issues/230)).
|
||||
- Mark all the API types as `#[non_exhaustive]`.
|
||||
- Replace all `mime_type: String` with `MimeWrapper`.
|
||||
|
||||
### 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)).
|
||||
- If a bot wasn't triggered for several days, it stops responding ([issue 223](https://github.com/teloxide/teloxide/issues/223)).
|
||||
|
||||
- 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)).
|
||||
- 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
|
||||
|
||||
### Added
|
||||
- The functionality to parse commands only with a correct bot's name (breaks backwards compatibility) ([Issue 168](https://github.com/teloxide/teloxide/issues/168)).
|
||||
- This `CHANGELOG.md`.
|
||||
|
||||
- The functionality to parse commands only with a correct bot's name (breaks backwards compatibility) ([Issue 168](https://github.com/teloxide/teloxide/issues/168)).
|
||||
- This `CHANGELOG.md`.
|
||||
|
||||
### Fixed
|
||||
- Fix parsing a pinned message ([Issue 167](https://github.com/teloxide/teloxide/issues/167)).
|
||||
- Replace `LanguageCode` with `String`, because [the official Telegram documentation](https://core.telegram.org/bots/api#getchat) doesn't specify a concrete version of IETF language tag.
|
||||
- Problems with the `poll_type` field ([Issue 178](https://github.com/teloxide/teloxide/issues/178)).
|
||||
- Make `polling_default` actually a long polling update listener ([PR 182](https://github.com/teloxide/teloxide/pull/182)).
|
||||
|
||||
- Fix parsing a pinned message ([Issue 167](https://github.com/teloxide/teloxide/issues/167)).
|
||||
- Replace `LanguageCode` with `String`, because [the official Telegram documentation](https://core.telegram.org/bots/api#getchat) doesn't specify a concrete version of IETF language tag.
|
||||
- Problems with the `poll_type` field ([Issue 178](https://github.com/teloxide/teloxide/issues/178)).
|
||||
- Make `polling_default` actually a long polling update listener ([PR 182](https://github.com/teloxide/teloxide/pull/182)).
|
||||
|
||||
### Removed
|
||||
- [either](https://crates.io/crates/either) from the dependencies in `Cargo.toml`.
|
||||
- `teloxide-macros` migrated into [the separate repository](https://github.com/teloxide/teloxide-macros) to easier releases and testing.
|
||||
|
||||
- [either](https://crates.io/crates/either) from the dependencies in `Cargo.toml`.
|
||||
- `teloxide-macros` migrated into [the separate repository](https://github.com/teloxide/teloxide-macros) to easier releases and testing.
|
||||
|
||||
## 0.1.0 - 2020-02-19
|
||||
|
||||
### Added
|
||||
- This project.
|
||||
- This project.
|
||||
|
|
455
CODE_STYLE.md
455
CODE_STYLE.md
|
@ -1,76 +1,104 @@
|
|||
# Code style
|
||||
This is a description of a coding style that every contributor must follow. Please, read the whole document before you start pushing code.
|
||||
|
||||
This is a description of a coding style that every contributor must follow.
|
||||
Please, read the whole document before you start pushing code.
|
||||
|
||||
## Generics
|
||||
Generics are always written with `where`.
|
||||
|
||||
Bad:
|
||||
All trait bounds should be written in `where`:
|
||||
|
||||
```rust
|
||||
pub fn new<N: Into<String>,
|
||||
T: Into<String>,
|
||||
P: Into<InputFile>,
|
||||
E: Into<String>>
|
||||
// GOOD
|
||||
pub fn new<N, T, P, E>(user_id: i32, name: N, title: T, png_sticker: P, emojis: E) -> Self
|
||||
where
|
||||
N: Into<String>,
|
||||
T: Into<String>,
|
||||
P: Into<InputFile>,
|
||||
E: Into<String>,
|
||||
{ ... }
|
||||
|
||||
// BAD
|
||||
pub fn new<N: Into<String>,
|
||||
T: Into<String>,
|
||||
P: Into<InputFile>,
|
||||
E: Into<String>>
|
||||
(user_id: i32, name: N, title: T, png_sticker: P, emojis: E) -> Self { ... }
|
||||
```
|
||||
|
||||
Good:
|
||||
|
||||
```rust
|
||||
pub fn new<N, T, P, E>(user_id: i32, name: N, title: T, png_sticker: P, emojis: E) -> Self
|
||||
where
|
||||
N: Into<String>,
|
||||
T: Into<String>,
|
||||
P: Into<InputFile>,
|
||||
E: Into<String> { ... }
|
||||
// GOOD
|
||||
impl<T> Trait for Wrap<T>
|
||||
where
|
||||
T: Trait
|
||||
{ ... }
|
||||
|
||||
// BAD
|
||||
impl<T: Trait> Trait for Wrap<T> { ... }
|
||||
```
|
||||
|
||||
## Comments
|
||||
1. Comments must describe what your code does and mustn't describe how your code does it and bla-bla-bla. Be sure that your comments follow the grammar, including punctuation, the first capital letter and so on.
|
||||
**Rationale:**
|
||||
- `where` clauses are easier to read when there are a lot of bounds
|
||||
- uniformity
|
||||
|
||||
Bad:
|
||||
## Documentation comments
|
||||
|
||||
```rust
|
||||
/// this function make request to telegram
|
||||
pub fn make_request(url: &str) -> String { ... }
|
||||
```
|
||||
1. Documentation must describe _what_ your code does and mustn't describe _how_ your code does it and bla-bla-bla.
|
||||
2. Be sure that your comments follow the grammar, including punctuation, the first capital letter and so on:
|
||||
```rust
|
||||
// GOOD
|
||||
/// This function makes a request to Telegram.
|
||||
pub fn make_request(url: &str) -> String { ... }
|
||||
|
||||
// BAD
|
||||
/// this function make request to telegram
|
||||
pub fn make_request(url: &str) -> String { ... }
|
||||
```
|
||||
3. Do not use ending punctuation in short list items (usually containing just one phrase or sentence):
|
||||
```md
|
||||
<!-- GOOD -->
|
||||
- Handle different kinds of Update
|
||||
- Pass dependencies to handlers
|
||||
- Disable a default Ctrl-C handling
|
||||
|
||||
Good:
|
||||
<!-- BAD -->
|
||||
- Handle different kinds of Update.
|
||||
- Pass dependencies to handlers.
|
||||
- Disable a default Ctrl-C handling.
|
||||
|
||||
```rust
|
||||
/// This function makes a request to Telegram.
|
||||
pub fn make_request(url: &str) -> String { ... }
|
||||
```
|
||||
<!-- BAD -->
|
||||
- Handle different kinds of Update;
|
||||
- Pass dependencies to handlers;
|
||||
- Disable a default Ctrl-C handling;
|
||||
```
|
||||
3. Link resources in your comments when possible:
|
||||
```rust
|
||||
/// Download a file from Telegram.
|
||||
///
|
||||
/// `path` can be obtained from the [`Bot::get_file`].
|
||||
///
|
||||
/// To download into [`AsyncWrite`] (e.g. [`tokio::fs::File`]), see
|
||||
/// [`Bot::download_file`].
|
||||
///
|
||||
/// [`Bot::get_file`]: crate::bot::Bot::get_file
|
||||
/// [`AsyncWrite`]: tokio::io::AsyncWrite
|
||||
/// [`tokio::fs::File`]: tokio::fs::File
|
||||
/// [`Bot::download_file`]: crate::Bot::download_file
|
||||
```
|
||||
4. Write `teloxide`, `teloxide-macros`, and `teloxide-core`, not "teloxide", "Teloxide", "teloxide-macros" or any other variant.
|
||||
|
||||
2. Also, link resources in your comments when possible:
|
||||
## Use `Self` where possible
|
||||
|
||||
```rust
|
||||
/// Download a file from Telegram.
|
||||
///
|
||||
/// `path` can be obtained from the [`Bot::get_file`].
|
||||
///
|
||||
/// To download into [`AsyncWrite`] (e.g. [`tokio::fs::File`]), see
|
||||
/// [`Bot::download_file`].
|
||||
///
|
||||
/// [`Bot::get_file`]: crate::bot::Bot::get_file
|
||||
/// [`AsyncWrite`]: tokio::io::AsyncWrite
|
||||
/// [`tokio::fs::File`]: tokio::fs::File
|
||||
/// [`Bot::download_file`]: crate::Bot::download_file
|
||||
#[cfg(feature = "unstable-stream")]
|
||||
pub async fn download_file_stream(
|
||||
&self,
|
||||
path: &str,
|
||||
) -> Result<impl Stream<Item = Result<Bytes, reqwest::Error>>, reqwest::Error>
|
||||
{
|
||||
download_file_stream(&self.client, &self.token, path).await
|
||||
}
|
||||
```
|
||||
|
||||
## Use Self where possible
|
||||
Bad:
|
||||
When referring to the type for which block is implemented, prefer using `Self`, rather than the name of the type:
|
||||
|
||||
```rust
|
||||
impl ErrorKind {
|
||||
// GOOD
|
||||
fn print(&self) {
|
||||
Self::Io => println!("Io"),
|
||||
Self::Network => println!("Network"),
|
||||
Self::Json => println!("Json"),
|
||||
}
|
||||
|
||||
// BAD
|
||||
fn print(&self) {
|
||||
ErrorKind::Io => println!("Io"),
|
||||
ErrorKind::Network => println!("Network"),
|
||||
|
@ -78,50 +106,303 @@ impl ErrorKind {
|
|||
}
|
||||
}
|
||||
```
|
||||
|
||||
Good:
|
||||
```rust
|
||||
impl ErrorKind {
|
||||
fn print(&self) {
|
||||
Self::Io => println!("Io"),
|
||||
Self::Network => println!("Network"),
|
||||
Self::Json => println!("Json"),
|
||||
impl<'a> AnswerCallbackQuery<'a> {
|
||||
// GOOD
|
||||
fn new<C>(bot: &'a Bot, callback_query_id: C) -> Self
|
||||
where
|
||||
C: Into<String>,
|
||||
{ ... }
|
||||
|
||||
// BAD
|
||||
fn new<C>(bot: &'a Bot, callback_query_id: C) -> AnswerCallbackQuery<'a>
|
||||
where
|
||||
C: Into<String>,
|
||||
{ ... }
|
||||
}
|
||||
```
|
||||
|
||||
**Rationale:** `Self` is generally shorter and it's easier to copy-paste code or rename the type.
|
||||
|
||||
## Avoid duplication in fields names
|
||||
|
||||
```rust
|
||||
struct Message {
|
||||
// GOOD
|
||||
#[serde(rename = "message_id")]
|
||||
id: MessageId,
|
||||
|
||||
// BAD
|
||||
message_id: MessageId,
|
||||
}
|
||||
```
|
||||
|
||||
**Rationale:** duplication blurs the focus of code, making it unnecessarily longer.
|
||||
|
||||
## Conventional generic names
|
||||
|
||||
Use a generic parameter name `S` for streams, `Fut` for futures, `F` for functions (where possible).
|
||||
|
||||
**Rationale:** uniformity.
|
||||
|
||||
## Deriving traits
|
||||
|
||||
Derive `Copy`, `Clone`, `Eq`, `PartialEq`, `Hash` and `Debug` for public types when possible.
|
||||
|
||||
**Rationale:** these traits can be useful for users and can be implemented for most types.
|
||||
|
||||
Derive `Default` when there is a reasonable default value for the type.
|
||||
|
||||
**Rationale:** `Default` plays nicely with generic code (for example, `mem::take`).
|
||||
|
||||
## `Into`-polymorphism
|
||||
|
||||
Use `T: Into<Ty>` when this can simplify user code.
|
||||
I.e. when there are types that implement `Into<Ty>` that are likely to be passed to this function.
|
||||
|
||||
**Rationale:** conversions unnecessarily complicate caller code and can be confusing for beginners.
|
||||
|
||||
## `must_use`
|
||||
|
||||
Always mark functions as `#[must_use]` if they don't have side effects and the only reason to call them is to get the result:
|
||||
|
||||
```rust
|
||||
impl User {
|
||||
// GOOD
|
||||
#[must_use]
|
||||
fn full_name(&self) -> String {
|
||||
format!("{} {}", user.first_name, user.last_name)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>More examples</summary>
|
||||
|
||||
Bad:
|
||||
|
||||
**Rationale:** users will get warnings if they forgot to do something with the result, potentially preventing bugs.
|
||||
|
||||
## Creating boxed futures
|
||||
|
||||
Prefer `Box::pin(async { ... })` instead of `async { ... }.boxed()`.
|
||||
|
||||
**Rationale:** the former is generally formatted better by rustfmt.
|
||||
|
||||
## Full paths for logging
|
||||
|
||||
Always write `log::<op>!(...)` instead of importing `use log::<op>;` and invoking `<op>!(...)`.
|
||||
|
||||
```rust
|
||||
impl<'a> AnswerCallbackQuery<'a> {
|
||||
pub(crate) fn new<C>(bot: &'a Bot, callback_query_id: C) -> AnswerCallbackQuery<'a>
|
||||
where
|
||||
C: Into<String>, { ... }
|
||||
// GOOD
|
||||
log::warn!("Everything is on fire");
|
||||
|
||||
// BAD
|
||||
use log::warn;
|
||||
|
||||
warn!("Everything is on fire");
|
||||
```
|
||||
|
||||
Good:
|
||||
|
||||
**Rationale:**
|
||||
- Less polluted import blocks
|
||||
- Uniformity
|
||||
|
||||
## `&str` -> `String` conversion
|
||||
|
||||
Prefer using `.to_owned()`, rather than `.to_string()`, `.into()`, `String::from`, etc.
|
||||
|
||||
**Rationale:** uniformity, intent clarity.
|
||||
|
||||
## Order of imports
|
||||
|
||||
Separate import groups with blank lines. Use one use per crate.
|
||||
|
||||
Module declarations come before the imports.
|
||||
Order them in "suggested reading order" for a person new to the code base.
|
||||
|
||||
```rust
|
||||
impl<'a> AnswerCallbackQuery<'a> {
|
||||
pub(crate) fn new<C>(bot: &'a Bot, callback_query_id: C) -> Self
|
||||
where
|
||||
C: Into<String>, { ... }
|
||||
mod x;
|
||||
mod y;
|
||||
|
||||
// First std.
|
||||
use std::{ ... }
|
||||
|
||||
// Second, external crates (both crates.io crates and other rust-analyzer crates).
|
||||
use crate_foo::{ ... }
|
||||
use crate_bar::{ ... }
|
||||
|
||||
// Then current crate.
|
||||
use crate::{}
|
||||
|
||||
// Finally, parent and child modules, but prefer `use crate::`.
|
||||
use super::{}
|
||||
|
||||
// Re-exports are treated as item definitions rather than imports, so they go
|
||||
// after imports and modules. Use them sparingly.
|
||||
pub use crate::x::Z;
|
||||
```
|
||||
</details>
|
||||
|
||||
## Naming
|
||||
1. Avoid unnecessary duplication (`Message::message_id` -> `Message::id` using `#[serde(rename = "message_id")]`).
|
||||
2. Use a generic parameter name `S` for streams, `Fut` for futures, `F` for functions (where possible).
|
||||
**Rationale:**
|
||||
- Reading order is important for new contributors
|
||||
- Grouping by crate allows spotting unwanted dependencies easier
|
||||
- Consistency
|
||||
|
||||
## Deriving
|
||||
1. Derive `Copy`, `Eq`, `Hash`, `PartialEq`, `Clone`, `Debug` for public types when possible (note: if the default `Debug` implementation is weird, you should manually implement it by yourself).
|
||||
2. Derive `Default` when there is an algorithm to get a default value for your type.
|
||||
## Import Style
|
||||
|
||||
## Misc
|
||||
1. Use `Into<...>` only where there exists at least one conversion **and** it will be logically to use.
|
||||
2. Always mark a function as `#[must_use]` if its return value **must** be used.
|
||||
3. `Box::pin(async [move] { ... })` instead of `async [move] { ... }.boxed()`.
|
||||
4. Always write `log::<op>!(...)` instead of importing `use log::<op>;` and invoking `<op>!(...)`. For example, write `log::info!("blah")`.
|
||||
When implementing traits from `std::fmt` import the module:
|
||||
|
||||
```rust
|
||||
// GOOD
|
||||
use std::fmt;
|
||||
|
||||
impl fmt::Display for RenameError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { .. }
|
||||
}
|
||||
|
||||
// BAD
|
||||
impl std::fmt::Display for RenameError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { .. }
|
||||
}
|
||||
```
|
||||
|
||||
**Rationale:**
|
||||
- Makes it clear that a trait is implemented, rather than used
|
||||
- Less typing
|
||||
|
||||
Prefer `use crate::foo::bar` to `use super::bar` or `use self::bar::baz`. **Rationale:**
|
||||
- Works in all cases
|
||||
- Consistency
|
||||
|
||||
## Order of Items
|
||||
|
||||
Optimize for the reader who sees the file for the first time, and wants to get a general idea about what's going on. People read things from top to bottom, so place most important things first.
|
||||
|
||||
Specifically, if all items except one are private, always put the non-private item on top:
|
||||
```rust
|
||||
// GOOD
|
||||
pub(crate) fn frobnicate() {
|
||||
Helper::act()
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Helper { stuff: i32 }
|
||||
|
||||
impl Helper {
|
||||
fn act(&self) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// BAD
|
||||
#[derive(Default)]
|
||||
struct Helper { stuff: i32 }
|
||||
|
||||
pub(crate) fn frobnicate() {
|
||||
Helper::act()
|
||||
}
|
||||
|
||||
impl Helper {
|
||||
fn act(&self) {
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If there's a mixture of private and public items, put public items first.
|
||||
|
||||
Put structs and enums first, functions and impls last. Order type declarations in a top-down manner:
|
||||
|
||||
```rust
|
||||
// GOOD
|
||||
struct Parent {
|
||||
children: Vec<Child>
|
||||
}
|
||||
|
||||
struct Child;
|
||||
|
||||
impl Parent {
|
||||
}
|
||||
|
||||
impl Child {
|
||||
}
|
||||
|
||||
// BAD
|
||||
struct Child;
|
||||
|
||||
impl Child {
|
||||
}
|
||||
|
||||
struct Parent {
|
||||
children: Vec<Child>
|
||||
}
|
||||
|
||||
impl Parent {
|
||||
}
|
||||
```
|
||||
|
||||
**Rationale:**
|
||||
- Easier to get a sense of the API by visually scanning the file
|
||||
- If function bodies are folded in the editor, the source code should be read as documentation for the public API
|
||||
|
||||
## Early Returns
|
||||
|
||||
Do use early returns:
|
||||
|
||||
```rust
|
||||
// GOOD
|
||||
fn foo() -> Option<Bar> {
|
||||
if !condition() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(...)
|
||||
}
|
||||
|
||||
// BAD
|
||||
fn foo() -> Option<Bar> {
|
||||
if condition() {
|
||||
Some(...)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Rationale:** reduce cognitive stack usage.
|
||||
|
||||
## If-let
|
||||
|
||||
Avoid the `if let ... { } else { }` construct, use `match` instead:
|
||||
|
||||
```rust
|
||||
// GOOD
|
||||
match ctx.expected_type.as_ref() {
|
||||
Some(expected_type) => completion_ty == expected_type && !expected_type.is_unit(),
|
||||
None => false,
|
||||
}
|
||||
|
||||
// BAD
|
||||
if let Some(expected_type) = ctx.expected_type.as_ref() {
|
||||
completion_ty == expected_type && !expected_type.is_unit()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
```
|
||||
|
||||
**Rationale:**
|
||||
- `match` is almost always more compact
|
||||
- The `else` branch can get a more precise pattern: `None` or `Err(_)` instead of `_`
|
||||
|
||||
## Empty Match Arms
|
||||
|
||||
Use `=> (),` when a match arm is intentionally empty:
|
||||
```rust
|
||||
// GOOD
|
||||
match result {
|
||||
Ok(_) => (),
|
||||
Err(err) => error!("{}", err),
|
||||
}
|
||||
|
||||
// BAD
|
||||
match result {
|
||||
Ok(_) => {}
|
||||
Err(err) => error!("{}", err),
|
||||
}
|
||||
```
|
||||
|
||||
**Rationale:** consistency.
|
||||
|
|
10
Cargo.toml
10
Cargo.toml
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "teloxide"
|
||||
version = "0.10.1"
|
||||
version = "0.11.0"
|
||||
edition = "2021"
|
||||
description = "An elegant Telegram bots framework for Rust"
|
||||
repository = "https://github.com/teloxide/teloxide"
|
||||
|
@ -57,8 +57,8 @@ full = [
|
|||
]
|
||||
|
||||
[dependencies]
|
||||
teloxide-core = { version = "0.7.0", default-features = false }
|
||||
teloxide-macros = { version = "0.6.3", optional = true }
|
||||
teloxide-core = { version = "0.8.0", default-features = false }
|
||||
teloxide-macros = { version = "0.7.0", optional = true }
|
||||
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
@ -66,8 +66,8 @@ serde = { version = "1.0", features = ["derive"] }
|
|||
dptree = "0.3.0"
|
||||
|
||||
# These lines are used only for development.
|
||||
# teloxide-core = { git = "https://github.com/teloxide/teloxide-core", rev = "b13393d", default-features = false }
|
||||
# teloxide-macros = { git = "https://github.com/teloxide/teloxide-macros", rev = "44d91c5", optional = true }
|
||||
# teloxide-core = { git = "https://github.com/teloxide/teloxide-core", rev = "00165e6", default-features = false }
|
||||
# teloxide-macros = { git = "https://github.com/teloxide/teloxide-macros", rev = "e715105", optional = true }
|
||||
# dptree = { git = "https://github.com/teloxide/dptree", rev = "df578e4" }
|
||||
|
||||
tokio = { version = "1.8", features = ["fs"] }
|
||||
|
|
|
@ -1,6 +1,108 @@
|
|||
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.10 -> 0.11
|
||||
|
||||
### core
|
||||
|
||||
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
|
||||
-let bot = Bot::from_env().auto_send();
|
||||
+let bot = Bot::from_env();
|
||||
```
|
||||
|
||||
```diff,rust
|
||||
-async fn start(bot: AutoSend<Bot>, dialogue: MyDialogue, msg: Message) -> HandlerResult {
|
||||
+async fn start(bot: Bot, dialogue: MyDialogue, msg: Message) -> HandlerResult {
|
||||
```
|
||||
|
||||
`File`'s and `FileMeta`'s fields now don't have `file_` prefix.
|
||||
If you previously accessed the fields, you'll need to change remove the prefix:
|
||||
|
||||
```diff
|
||||
-_ = file.file_size;
|
||||
+_ = file.size;
|
||||
```
|
||||
|
||||
`Animation`, `Audio`, `Document`, `PassportFile`, `PhotoSize`, `Video`, `VideoNote` and `Voice` now contain `FileMeta` instead of its fields.
|
||||
Together with rename of `FileMeta`'s fields, you'll need to change `_` to `.`:
|
||||
|
||||
```diff
|
||||
-_ = animation.file_size;
|
||||
+_ = animation.file.size;
|
||||
```
|
||||
|
||||
Message id fields and parameters now use `MessageId` type, instead of `i32`.
|
||||
You may need to change code accordingly:
|
||||
|
||||
```diff
|
||||
-let id: i32 = message.id;
|
||||
+let id: MessageId = message.id;
|
||||
```
|
||||
```diff,rust
|
||||
let (cid, mid): (ChatId, i32) = get_message_to_delete_from_db();
|
||||
-bot.delete_message(cid, mid).await?;
|
||||
+bot.delete_message(cid, MessageId(mid)).await?;
|
||||
```
|
||||
|
||||
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
|
||||
-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);
|
||||
```
|
||||
|
||||
Because of API updates `Sticker` type was refactored again.
|
||||
You may need to change code accordingly.
|
||||
See `Sticker` documentation for more information about the new structure.
|
||||
|
||||
### teloxide
|
||||
|
||||
You can now write `Ok(())` instead of `respond(())` at the end of closures provided to RELPs:
|
||||
|
||||
```diff,rust
|
||||
teloxide::repl(bot, |bot: Bot, msg: Message| async move {
|
||||
bot.send_dice(msg.chat.id).await?;
|
||||
- respond(())
|
||||
+ Ok(())
|
||||
})
|
||||
.await;
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
### macros
|
||||
|
||||
`parse_with` now accepts a Rust _path_ to a custom parser function instead of a string:
|
||||
|
||||
```diff,rust
|
||||
fn custom_parser(input: String) -> Result<(u8,), ParseError> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[derive(BotCommands)]
|
||||
enum Command {
|
||||
- #[command(parse_with = "custom_parser")]
|
||||
+ #[command(parse_with = custom_parser)]
|
||||
Num(u8),
|
||||
}
|
||||
```
|
||||
|
||||
`rename` now only renames a command literally; use `rename_rule` to change the case of a command:
|
||||
|
||||
```diff,rust
|
||||
#[derive(BotCommands)]
|
||||
- #[command(rename = "lowercase", description = "These commands are supported:")]
|
||||
+ #[command(rename_rule = "lowercase", description = "These commands are supported:")]
|
||||
enum Command {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## 0.9 -> 0.10
|
||||
|
||||
### core
|
||||
|
|
101
README.md
101
README.md
|
@ -1,8 +1,8 @@
|
|||
> [v0.9 -> v0.10 migration guide >>](MIGRATION_GUIDE.md#09---010)
|
||||
> [v0.10 -> v0.11 migration guide >>](MIGRATION_GUIDE.md#010---011)
|
||||
|
||||
<div align="center">
|
||||
<img src="./ICON.png" width="250"/>
|
||||
<h1>teloxide</h1>
|
||||
<h1><code>teloxide</code></h1>
|
||||
<a href="https://docs.rs/teloxide/">
|
||||
<img src="https://docs.rs/teloxide/badge.svg">
|
||||
</a>
|
||||
|
@ -13,7 +13,7 @@
|
|||
<img src="https://img.shields.io/crates/v/teloxide.svg">
|
||||
</a>
|
||||
<a href="https://core.telegram.org/bots/api">
|
||||
<img src="https://img.shields.io/badge/API%20coverage-Up%20to%206.1%20(inclusively)-green.svg">
|
||||
<img src="https://img.shields.io/badge/API%20coverage-Up%20to%206.2%20(inclusively)-green.svg">
|
||||
</a>
|
||||
<a href="https://t.me/teloxide">
|
||||
<img src="https://img.shields.io/badge/support-t.me%2Fteloxide-blueviolet">
|
||||
|
@ -24,18 +24,20 @@
|
|||
|
||||
## Highlights
|
||||
|
||||
- **Declarative design.** teloxide is based upon [`dptree`], a functional [chain of responsibility] pattern that allows you to express pipelines of message processing in a highly declarative and extensible style.
|
||||
- **Declarative design.** `teloxide` is based upon [`dptree`], a functional [chain of responsibility] pattern that allows you to express pipelines of message processing in a highly declarative and extensible style.
|
||||
|
||||
[`dptree`]: https://github.com/teloxide/dptree
|
||||
[chain of responsibility]: https://en.wikipedia.org/wiki/Chain-of-responsibility_pattern
|
||||
|
||||
- **Dialogues management subsystem.** Our dialogues management 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].
|
||||
- **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.
|
||||
|
||||
- **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].
|
||||
|
||||
[persistence]: https://en.wikipedia.org/wiki/Persistence_(computer_science)
|
||||
[Redis]: https://redis.io/
|
||||
[Sqlite]: https://www.sqlite.org
|
||||
|
||||
- **Strongly typed commands.** You can describe bot commands as enumerations, and then they'll be automatically constructed from strings — 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`].
|
||||
|
||||
[`structopt`]: https://github.com/TeXitoi/structopt
|
||||
[`serde-json`]: https://github.com/serde-rs/json
|
||||
|
@ -54,9 +56,9 @@ $ set TELOXIDE_TOKEN=<Your token here>
|
|||
|
||||
# Windows PowerShell
|
||||
$ $env:TELOXIDE_TOKEN=<Your token here>
|
||||
|
||||
```
|
||||
4. Make sure that your Rust compiler is up to date (teloxide currently requires rustc at least version 1.58):
|
||||
|
||||
4. Make sure that your Rust compiler is up to date (`teloxide` currently requires rustc at least version 1.64):
|
||||
```bash
|
||||
# If you're using stable
|
||||
$ rustup update stable
|
||||
|
@ -70,7 +72,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.10", features = ["macros", "auto-send"] }
|
||||
teloxide = { version = "0.11", features = ["macros", "auto-send"] }
|
||||
log = "0.4"
|
||||
pretty_env_logger = "0.4"
|
||||
tokio = { version = "1.8", features = ["rt-multi-thread", "macros"] }
|
||||
|
@ -92,11 +94,11 @@ async fn main() {
|
|||
pretty_env_logger::init();
|
||||
log::info!("Starting throw dice bot...");
|
||||
|
||||
let bot = Bot::from_env().auto_send();
|
||||
let bot = Bot::from_env();
|
||||
|
||||
teloxide::repl(bot, |message: Message, bot: AutoSend<Bot>| async move {
|
||||
bot.send_dice(message.chat.id).await?;
|
||||
respond(())
|
||||
teloxide::repl(bot, |bot: Bot, msg: Message| async move {
|
||||
bot.send_dice(msg.chat.id).await?;
|
||||
Ok(())
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
@ -122,20 +124,18 @@ Commands are strongly typed and defined declaratively, similar to how we define
|
|||
```rust,no_run
|
||||
use teloxide::{prelude::*, utils::command::BotCommands};
|
||||
|
||||
use std::error::Error;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
pretty_env_logger::init();
|
||||
log::info!("Starting command bot...");
|
||||
|
||||
let bot = Bot::from_env().auto_send();
|
||||
let bot = Bot::from_env();
|
||||
|
||||
teloxide::commands_repl(bot, answer, Command::ty()).await;
|
||||
}
|
||||
|
||||
#[derive(BotCommands, Clone)]
|
||||
#[command(rename = "lowercase", description = "These commands are supported:")]
|
||||
#[command(rename_rule = "lowercase", description = "These commands are supported:")]
|
||||
enum Command {
|
||||
#[command(description = "display this text.")]
|
||||
Help,
|
||||
|
@ -145,24 +145,15 @@ enum Command {
|
|||
UsernameAndAge { username: String, age: u8 },
|
||||
}
|
||||
|
||||
async fn answer(
|
||||
bot: AutoSend<Bot>,
|
||||
message: Message,
|
||||
command: Command,
|
||||
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
match command {
|
||||
Command::Help => {
|
||||
bot.send_message(message.chat.id, Command::descriptions().to_string()).await?
|
||||
}
|
||||
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(message.chat.id, format!("Your username is @{username}.")).await?
|
||||
bot.send_message(msg.chat.id, format!("Your username is @{username}.")).await?
|
||||
}
|
||||
Command::UsernameAndAge { username, age } => {
|
||||
bot.send_message(
|
||||
message.chat.id,
|
||||
format!("Your username is @{username} and age is {age}."),
|
||||
)
|
||||
.await?
|
||||
bot.send_message(msg.chat.id, format!("Your username is @{username} and age is {age}."))
|
||||
.await?
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -190,18 +181,18 @@ use teloxide::{dispatching::dialogue::InMemStorage, prelude::*};
|
|||
type MyDialogue = Dialogue<State, InMemStorage<State>>;
|
||||
type HandlerResult = Result<(), Box<dyn std::error::Error + Send + Sync>>;
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Default)]
|
||||
pub enum State {
|
||||
#[default]
|
||||
Start,
|
||||
ReceiveFullName,
|
||||
ReceiveAge { full_name: String },
|
||||
ReceiveLocation { full_name: String, age: u8 },
|
||||
}
|
||||
|
||||
impl Default for State {
|
||||
fn default() -> Self {
|
||||
Self::Start
|
||||
}
|
||||
ReceiveAge {
|
||||
full_name: String,
|
||||
},
|
||||
ReceiveLocation {
|
||||
full_name: String,
|
||||
age: u8,
|
||||
},
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
|
@ -209,7 +200,7 @@ async fn main() {
|
|||
pretty_env_logger::init();
|
||||
log::info!("Starting dialogue bot...");
|
||||
|
||||
let bot = Bot::from_env().auto_send();
|
||||
let bot = Bot::from_env();
|
||||
|
||||
Dispatcher::builder(
|
||||
bot,
|
||||
|
@ -229,17 +220,13 @@ async fn main() {
|
|||
.await;
|
||||
}
|
||||
|
||||
async fn start(bot: AutoSend<Bot>, msg: Message, dialogue: MyDialogue) -> HandlerResult {
|
||||
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: AutoSend<Bot>,
|
||||
msg: Message,
|
||||
dialogue: MyDialogue,
|
||||
) -> HandlerResult {
|
||||
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?;
|
||||
|
@ -254,10 +241,10 @@ async fn receive_full_name(
|
|||
}
|
||||
|
||||
async fn receive_age(
|
||||
bot: AutoSend<Bot>,
|
||||
msg: Message,
|
||||
bot: Bot,
|
||||
dialogue: MyDialogue,
|
||||
full_name: String, // Available from `State::ReceiveAge`.
|
||||
msg: Message,
|
||||
) -> HandlerResult {
|
||||
match msg.text().map(|text| text.parse::<u8>()) {
|
||||
Some(Ok(age)) => {
|
||||
|
@ -273,15 +260,15 @@ async fn receive_age(
|
|||
}
|
||||
|
||||
async fn receive_location(
|
||||
bot: AutoSend<Bot>,
|
||||
msg: Message,
|
||||
bot: Bot,
|
||||
dialogue: MyDialogue,
|
||||
(full_name, age): (String, u8), // Available from `State::ReceiveLocation`.
|
||||
msg: Message,
|
||||
) -> HandlerResult {
|
||||
match msg.text() {
|
||||
Some(location) => {
|
||||
let message = format!("Full name: {full_name}\nAge: {age}\nLocation: {location}");
|
||||
bot.send_message(msg.chat.id, message).await?;
|
||||
let report = format!("Full name: {full_name}\nAge: {age}\nLocation: {location}");
|
||||
bot.send_message(msg.chat.id, report).await?;
|
||||
dialogue.exit().await?;
|
||||
}
|
||||
None => {
|
||||
|
@ -319,7 +306,7 @@ A: No, only the bots API.
|
|||
|
||||
**Q: Can I use webhooks?**
|
||||
|
||||
A: You can! Teloxide has a built-in support for webhooks in `dispatching::update_listeners::webhooks` module. See how it's used in [`examples/ngrok_ping_pong_bot`](examples/ngrok_ping_pong.rs) and [`examples/heroku_ping_pong_bot`](examples/heroku_ping_pong.rs).
|
||||
A: You can! `teloxide` has a built-in support for webhooks in `dispatching::update_listeners::webhooks` module. See how it's used in [`examples/ngrok_ping_pong_bot`](examples/ngrok_ping_pong.rs) and [`examples/heroku_ping_pong_bot`](examples/heroku_ping_pong.rs).
|
||||
|
||||
**Q: Can I handle both callback queries and messages within a single dialogue?**
|
||||
|
||||
|
@ -343,7 +330,7 @@ Feel free to propose your own bot to our collection!
|
|||
- [`zamazan4ik/npaperbot-telegram`](https://github.com/zamazan4ik/npaperbot-telegram) — Telegram bot for searching via C++ proposals.
|
||||
|
||||
<details>
|
||||
<summary>Show bots using teloxide older than v0.6.0</summary>
|
||||
<summary>Show bots using `teloxide` older than v0.6.0</summary>
|
||||
|
||||
- [`mxseev/logram`](https://github.com/mxseev/logram) — Utility that takes logs from anywhere and sends them to Telegram.
|
||||
- [`alexkonovalov/PedigreeBot`](https://github.com/alexkonovalov/PedigreeBot) — A Telegram bot for building family trees.
|
||||
|
@ -355,7 +342,7 @@ Feel free to propose your own bot to our collection!
|
|||
|
||||
</details>
|
||||
|
||||
See [600+ other public repositories using teloxide >>](https://github.com/teloxide/teloxide/network/dependents)
|
||||
See [700+ other public repositories using `teloxide` >>](https://github.com/teloxide/teloxide/network/dependents)
|
||||
|
||||
## Contributing
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::{error::Error, str::FromStr};
|
||||
use std::str::FromStr;
|
||||
|
||||
use chrono::Duration;
|
||||
use teloxide::{prelude::*, types::ChatPermissions, utils::command::BotCommands};
|
||||
|
@ -14,7 +14,7 @@ use teloxide::{prelude::*, types::ChatPermissions, utils::command::BotCommands};
|
|||
// %PREFIX%%COMMAND% - %DESCRIPTION%
|
||||
#[derive(BotCommands, Clone)]
|
||||
#[command(
|
||||
rename = "lowercase",
|
||||
rename_rule = "lowercase",
|
||||
description = "Use commands in format /%command% %num% %unit%",
|
||||
parse_with = "split"
|
||||
)]
|
||||
|
@ -58,19 +58,13 @@ async fn main() {
|
|||
pretty_env_logger::init();
|
||||
log::info!("Starting admin bot...");
|
||||
|
||||
let bot = teloxide::Bot::from_env().auto_send();
|
||||
let bot = teloxide::Bot::from_env();
|
||||
|
||||
teloxide::commands_repl(bot, action, Command::ty()).await;
|
||||
}
|
||||
|
||||
type Bot = AutoSend<teloxide::Bot>;
|
||||
|
||||
async fn action(
|
||||
bot: Bot,
|
||||
msg: Message,
|
||||
command: Command,
|
||||
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
match command {
|
||||
async fn action(bot: Bot, msg: Message, cmd: Command) -> ResponseResult<()> {
|
||||
match cmd {
|
||||
Command::Help => {
|
||||
bot.send_message(msg.chat.id, Command::descriptions().to_string()).await?;
|
||||
}
|
||||
|
@ -83,7 +77,7 @@ async fn action(
|
|||
}
|
||||
|
||||
// Kick a user with a replied message.
|
||||
async fn kick_user(bot: Bot, msg: Message) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
async fn kick_user(bot: Bot, msg: Message) -> ResponseResult<()> {
|
||||
match msg.reply_to_message() {
|
||||
Some(replied) => {
|
||||
// bot.unban_chat_member can also kicks a user from a group chat.
|
||||
|
@ -97,11 +91,7 @@ async fn kick_user(bot: Bot, msg: Message) -> Result<(), Box<dyn Error + Send +
|
|||
}
|
||||
|
||||
// Ban a user with replied message.
|
||||
async fn ban_user(
|
||||
bot: Bot,
|
||||
msg: Message,
|
||||
time: Duration,
|
||||
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
async fn ban_user(bot: Bot, msg: Message, time: Duration) -> ResponseResult<()> {
|
||||
match msg.reply_to_message() {
|
||||
Some(replied) => {
|
||||
bot.kick_chat_member(
|
||||
|
@ -120,11 +110,7 @@ async fn ban_user(
|
|||
}
|
||||
|
||||
// Mute a user with a replied message.
|
||||
async fn mute_user(
|
||||
bot: Bot,
|
||||
msg: Message,
|
||||
time: Duration,
|
||||
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
async fn mute_user(bot: Bot, msg: Message, time: Duration) -> ResponseResult<()> {
|
||||
match msg.reply_to_message() {
|
||||
Some(replied) => {
|
||||
bot.restrict_chat_member(
|
||||
|
|
|
@ -4,13 +4,13 @@ use teloxide::{
|
|||
prelude::*,
|
||||
types::{
|
||||
InlineKeyboardButton, InlineKeyboardMarkup, InlineQueryResultArticle, InputMessageContent,
|
||||
InputMessageContentText,
|
||||
InputMessageContentText, Me,
|
||||
},
|
||||
utils::command::BotCommands,
|
||||
};
|
||||
|
||||
#[derive(BotCommands)]
|
||||
#[command(rename = "lowercase", description = "These commands are supported:")]
|
||||
#[command(rename_rule = "lowercase", description = "These commands are supported:")]
|
||||
enum Command {
|
||||
#[command(description = "Display this text")]
|
||||
Help,
|
||||
|
@ -23,7 +23,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
|||
pretty_env_logger::init();
|
||||
log::info!("Starting buttons bot...");
|
||||
|
||||
let bot = Bot::from_env().auto_send();
|
||||
let bot = Bot::from_env();
|
||||
|
||||
let handler = dptree::entry()
|
||||
.branch(Update::filter_message().endpoint(message_handler))
|
||||
|
@ -59,23 +59,24 @@ fn make_keyboard() -> InlineKeyboardMarkup {
|
|||
/// or not, then match the command. If the command is `/start` it writes a
|
||||
/// markup with the `InlineKeyboardMarkup`.
|
||||
async fn message_handler(
|
||||
m: Message,
|
||||
bot: AutoSend<Bot>,
|
||||
bot: Bot,
|
||||
msg: Message,
|
||||
me: Me,
|
||||
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
if let Some(text) = m.text() {
|
||||
match BotCommands::parse(text, "buttons") {
|
||||
if let Some(text) = msg.text() {
|
||||
match BotCommands::parse(text, me.username()) {
|
||||
Ok(Command::Help) => {
|
||||
// Just send the description of all commands.
|
||||
bot.send_message(m.chat.id, Command::descriptions().to_string()).await?;
|
||||
bot.send_message(msg.chat.id, Command::descriptions().to_string()).await?;
|
||||
}
|
||||
Ok(Command::Start) => {
|
||||
// Create a list of buttons and send them.
|
||||
let keyboard = make_keyboard();
|
||||
bot.send_message(m.chat.id, "Debian versions:").reply_markup(keyboard).await?;
|
||||
bot.send_message(msg.chat.id, "Debian versions:").reply_markup(keyboard).await?;
|
||||
}
|
||||
|
||||
Err(_) => {
|
||||
bot.send_message(m.chat.id, "Command not found!").await?;
|
||||
bot.send_message(msg.chat.id, "Command not found!").await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -84,8 +85,8 @@ async fn message_handler(
|
|||
}
|
||||
|
||||
async fn inline_query_handler(
|
||||
bot: Bot,
|
||||
q: InlineQuery,
|
||||
bot: AutoSend<Bot>,
|
||||
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
let choose_debian_version = InlineQueryResultArticle::new(
|
||||
"0",
|
||||
|
@ -104,22 +105,21 @@ async fn inline_query_handler(
|
|||
///
|
||||
/// **IMPORTANT**: do not send privacy-sensitive data this way!!!
|
||||
/// Anyone can read data stored in the callback button.
|
||||
async fn callback_handler(
|
||||
q: CallbackQuery,
|
||||
bot: AutoSend<Bot>,
|
||||
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
async fn callback_handler(bot: Bot, q: CallbackQuery) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
if let Some(version) = q.data {
|
||||
let text = format!("You chose: {version}");
|
||||
|
||||
match q.message {
|
||||
Some(Message { id, chat, .. }) => {
|
||||
bot.edit_message_text(chat.id, id, text).await?;
|
||||
}
|
||||
None => {
|
||||
if let Some(id) = q.inline_message_id {
|
||||
bot.edit_message_text_inline(id, text).await?;
|
||||
}
|
||||
}
|
||||
// Tell telegram that we've seen this query, to remove 🕑 icons from the
|
||||
//
|
||||
// clients. You could also use `answer_callback_query`'s optional
|
||||
// parameters to tweak what happens on the client side.
|
||||
bot.answer_callback_query(q.id).await?;
|
||||
|
||||
// Edit text of the message to which the buttons were attached
|
||||
if let Some(Message { id, chat, .. }) = q.message {
|
||||
bot.edit_message_text(chat.id, id, text).await?;
|
||||
} else if let Some(id) = q.inline_message_id {
|
||||
bot.edit_message_text_inline(id, text).await?;
|
||||
}
|
||||
|
||||
log::info!("You chose: {}", version);
|
||||
|
|
|
@ -1,19 +1,17 @@
|
|||
use teloxide::{prelude::*, utils::command::BotCommands};
|
||||
|
||||
use std::error::Error;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
pretty_env_logger::init();
|
||||
log::info!("Starting command bot...");
|
||||
|
||||
let bot = Bot::from_env().auto_send();
|
||||
let bot = Bot::from_env();
|
||||
|
||||
teloxide::commands_repl(bot, answer, Command::ty()).await;
|
||||
}
|
||||
|
||||
#[derive(BotCommands, Clone)]
|
||||
#[command(rename = "lowercase", description = "These commands are supported:")]
|
||||
#[command(rename_rule = "lowercase", description = "These commands are supported:")]
|
||||
enum Command {
|
||||
#[command(description = "display this text.")]
|
||||
Help,
|
||||
|
@ -23,24 +21,15 @@ enum Command {
|
|||
UsernameAndAge { username: String, age: u8 },
|
||||
}
|
||||
|
||||
async fn answer(
|
||||
bot: AutoSend<Bot>,
|
||||
message: Message,
|
||||
command: Command,
|
||||
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
match command {
|
||||
Command::Help => {
|
||||
bot.send_message(message.chat.id, Command::descriptions().to_string()).await?
|
||||
}
|
||||
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(message.chat.id, format!("Your username is @{username}.")).await?
|
||||
bot.send_message(msg.chat.id, format!("Your username is @{username}.")).await?
|
||||
}
|
||||
Command::UsernameAndAge { username, age } => {
|
||||
bot.send_message(
|
||||
message.chat.id,
|
||||
format!("Your username is @{username} and age is {age}."),
|
||||
)
|
||||
.await?
|
||||
bot.send_message(msg.chat.id, format!("Your username is @{username} and age is {age}."))
|
||||
.await?
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ pub enum State {
|
|||
}
|
||||
|
||||
#[derive(Clone, BotCommands)]
|
||||
#[command(rename = "lowercase", description = "These commands are supported:")]
|
||||
#[command(rename_rule = "lowercase", description = "These commands are supported:")]
|
||||
pub enum Command {
|
||||
#[command(description = "get your number.")]
|
||||
Get,
|
||||
|
@ -35,7 +35,7 @@ async fn main() {
|
|||
pretty_env_logger::init();
|
||||
log::info!("Starting DB remember bot...");
|
||||
|
||||
let bot = Bot::from_env().auto_send();
|
||||
let bot = Bot::from_env();
|
||||
|
||||
let storage: MyStorage = if std::env::var("DB_REMEMBER_REDIS").is_ok() {
|
||||
RedisStorage::open("redis://127.0.0.1:6379", Bincode).await.unwrap().erase()
|
||||
|
@ -60,7 +60,7 @@ async fn main() {
|
|||
.await;
|
||||
}
|
||||
|
||||
async fn start(bot: AutoSend<Bot>, msg: Message, dialogue: MyDialogue) -> HandlerResult {
|
||||
async fn start(bot: Bot, dialogue: MyDialogue, msg: Message) -> HandlerResult {
|
||||
match msg.text().map(|text| text.parse::<i32>()) {
|
||||
Some(Ok(n)) => {
|
||||
dialogue.update(State::GotNumber(n)).await?;
|
||||
|
@ -79,10 +79,10 @@ async fn start(bot: AutoSend<Bot>, msg: Message, dialogue: MyDialogue) -> Handle
|
|||
}
|
||||
|
||||
async fn got_number(
|
||||
bot: AutoSend<Bot>,
|
||||
msg: Message,
|
||||
bot: Bot,
|
||||
dialogue: MyDialogue,
|
||||
num: i32,
|
||||
num: i32, // Available from `State::GotNumber`.
|
||||
msg: Message,
|
||||
cmd: Command,
|
||||
) -> HandlerResult {
|
||||
match cmd {
|
||||
|
@ -97,7 +97,7 @@ async fn got_number(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
async fn invalid_command(bot: AutoSend<Bot>, msg: Message) -> HandlerResult {
|
||||
async fn invalid_command(bot: Bot, msg: Message) -> HandlerResult {
|
||||
bot.send_message(msg.chat.id, "Please, send /get or /reset.").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ async fn main() {
|
|||
pretty_env_logger::init();
|
||||
log::info!("Starting dialogue bot...");
|
||||
|
||||
let bot = Bot::from_env().auto_send();
|
||||
let bot = Bot::from_env();
|
||||
|
||||
Dispatcher::builder(
|
||||
bot,
|
||||
|
@ -57,17 +57,13 @@ async fn main() {
|
|||
.await;
|
||||
}
|
||||
|
||||
async fn start(bot: AutoSend<Bot>, msg: Message, dialogue: MyDialogue) -> HandlerResult {
|
||||
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: AutoSend<Bot>,
|
||||
msg: Message,
|
||||
dialogue: MyDialogue,
|
||||
) -> HandlerResult {
|
||||
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?;
|
||||
|
@ -82,10 +78,10 @@ async fn receive_full_name(
|
|||
}
|
||||
|
||||
async fn receive_age(
|
||||
bot: AutoSend<Bot>,
|
||||
msg: Message,
|
||||
bot: Bot,
|
||||
dialogue: MyDialogue,
|
||||
full_name: String, // Available from `State::ReceiveAge`.
|
||||
msg: Message,
|
||||
) -> HandlerResult {
|
||||
match msg.text().map(|text| text.parse::<u8>()) {
|
||||
Some(Ok(age)) => {
|
||||
|
@ -101,15 +97,15 @@ async fn receive_age(
|
|||
}
|
||||
|
||||
async fn receive_location(
|
||||
bot: AutoSend<Bot>,
|
||||
msg: Message,
|
||||
bot: Bot,
|
||||
dialogue: MyDialogue,
|
||||
(full_name, age): (String, u8), // Available from `State::ReceiveLocation`.
|
||||
msg: Message,
|
||||
) -> HandlerResult {
|
||||
match msg.text() {
|
||||
Some(location) => {
|
||||
let message = format!("Full name: {full_name}\nAge: {age}\nLocation: {location}");
|
||||
bot.send_message(msg.chat.id, message).await?;
|
||||
let report = format!("Full name: {full_name}\nAge: {age}\nLocation: {location}");
|
||||
bot.send_message(msg.chat.id, report).await?;
|
||||
dialogue.exit().await?;
|
||||
}
|
||||
None => {
|
||||
|
|
|
@ -14,7 +14,7 @@ async fn main() {
|
|||
pretty_env_logger::init();
|
||||
log::info!("Starting dispatching features bot...");
|
||||
|
||||
let bot = Bot::from_env().auto_send();
|
||||
let bot = Bot::from_env();
|
||||
|
||||
let parameters = ConfigParameters {
|
||||
bot_maintainer: UserId(0), // Paste your ID to run this bot.
|
||||
|
@ -33,29 +33,27 @@ async fn main() {
|
|||
)
|
||||
.branch(
|
||||
// Filter a maintainer by a used ID.
|
||||
dptree::filter(|msg: Message, cfg: ConfigParameters| {
|
||||
dptree::filter(|cfg: ConfigParameters, msg: Message| {
|
||||
msg.from().map(|user| user.id == cfg.bot_maintainer).unwrap_or_default()
|
||||
})
|
||||
.filter_command::<MaintainerCommands>()
|
||||
.endpoint(
|
||||
|msg: Message, bot: AutoSend<Bot>, cmd: MaintainerCommands| async move {
|
||||
match cmd {
|
||||
MaintainerCommands::Rand { from, to } => {
|
||||
let mut rng = rand::rngs::OsRng::default();
|
||||
let value: u64 = rng.gen_range(from..=to);
|
||||
.endpoint(|msg: Message, bot: Bot, cmd: MaintainerCommands| async move {
|
||||
match cmd {
|
||||
MaintainerCommands::Rand { from, to } => {
|
||||
let mut rng = rand::rngs::OsRng::default();
|
||||
let value: u64 = rng.gen_range(from..=to);
|
||||
|
||||
bot.send_message(msg.chat.id, value.to_string()).await?;
|
||||
Ok(())
|
||||
}
|
||||
bot.send_message(msg.chat.id, value.to_string()).await?;
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
),
|
||||
}
|
||||
}),
|
||||
)
|
||||
.branch(
|
||||
// Filtering allow you to filter updates by some condition.
|
||||
dptree::filter(|msg: Message| msg.chat.is_group() || msg.chat.is_supergroup())
|
||||
// An endpoint is the last update handler.
|
||||
.endpoint(|msg: Message, bot: AutoSend<Bot>| async move {
|
||||
.endpoint(|msg: Message, bot: Bot| async move {
|
||||
log::info!("Received a message from a group chat.");
|
||||
bot.send_message(msg.chat.id, "This is a group chat.").await?;
|
||||
respond(())
|
||||
|
@ -64,14 +62,12 @@ async fn main() {
|
|||
.branch(
|
||||
// There are some extension filtering functions on `Message`. The following filter will
|
||||
// filter only messages with dices.
|
||||
Message::filter_dice().endpoint(
|
||||
|msg: Message, dice: Dice, bot: AutoSend<Bot>| async move {
|
||||
bot.send_message(msg.chat.id, format!("Dice value: {}", dice.value))
|
||||
.reply_to_message_id(msg.id)
|
||||
.await?;
|
||||
Ok(())
|
||||
},
|
||||
),
|
||||
Message::filter_dice().endpoint(|bot: Bot, msg: Message, dice: Dice| async move {
|
||||
bot.send_message(msg.chat.id, format!("Dice value: {}", dice.value))
|
||||
.reply_to_message_id(msg.id)
|
||||
.await?;
|
||||
Ok(())
|
||||
}),
|
||||
);
|
||||
|
||||
Dispatcher::builder(bot, handler)
|
||||
|
@ -100,7 +96,7 @@ struct ConfigParameters {
|
|||
}
|
||||
|
||||
#[derive(BotCommands, Clone)]
|
||||
#[command(rename = "lowercase", description = "Simple commands")]
|
||||
#[command(rename_rule = "lowercase", description = "Simple commands")]
|
||||
enum SimpleCommand {
|
||||
#[command(description = "shows this message.")]
|
||||
Help,
|
||||
|
@ -111,18 +107,18 @@ enum SimpleCommand {
|
|||
}
|
||||
|
||||
#[derive(BotCommands, Clone)]
|
||||
#[command(rename = "lowercase", description = "Maintainer commands")]
|
||||
#[command(rename_rule = "lowercase", description = "Maintainer commands")]
|
||||
enum MaintainerCommands {
|
||||
#[command(parse_with = "split", description = "generate a number within range")]
|
||||
Rand { from: u64, to: u64 },
|
||||
}
|
||||
|
||||
async fn simple_commands_handler(
|
||||
msg: Message,
|
||||
bot: AutoSend<Bot>,
|
||||
cmd: SimpleCommand,
|
||||
cfg: ConfigParameters,
|
||||
bot: Bot,
|
||||
me: teloxide::types::Me,
|
||||
msg: Message,
|
||||
cmd: SimpleCommand,
|
||||
) -> Result<(), teloxide::RequestError> {
|
||||
let text = match cmd {
|
||||
SimpleCommand::Help => {
|
||||
|
|
|
@ -21,15 +21,13 @@
|
|||
use std::env;
|
||||
|
||||
use teloxide::{dispatching::update_listeners::webhooks, prelude::*};
|
||||
use url::Url;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
pretty_env_logger::init();
|
||||
log::info!("Starting Heroku ping-pong bot...");
|
||||
|
||||
let bot = Bot::from_env().auto_send();
|
||||
let token = bot.inner().token();
|
||||
let bot = Bot::from_env();
|
||||
|
||||
// Heroku auto defines a port value
|
||||
let port: u16 = env::var("PORT")
|
||||
|
@ -41,7 +39,7 @@ async fn main() {
|
|||
|
||||
// Heroku host example: "heroku-ping-pong-bot.herokuapp.com"
|
||||
let host = env::var("HOST").expect("HOST env variable is not set");
|
||||
let url = Url::parse(&format!("https://{host}/webhooks/{token}")).unwrap();
|
||||
let url = format!("https://{host}/webhook").parse().unwrap();
|
||||
|
||||
let listener = webhooks::axum(bot.clone(), webhooks::Options::new(addr, url))
|
||||
.await
|
||||
|
@ -49,9 +47,9 @@ async fn main() {
|
|||
|
||||
teloxide::repl_with_listener(
|
||||
bot,
|
||||
|msg: Message, bot: AutoSend<Bot>| async move {
|
||||
|bot: Bot, msg: Message| async move {
|
||||
bot.send_message(msg.chat.id, "pong").await?;
|
||||
respond(())
|
||||
Ok(())
|
||||
},
|
||||
listener,
|
||||
)
|
||||
|
|
|
@ -11,10 +11,10 @@ async fn main() {
|
|||
pretty_env_logger::init();
|
||||
log::info!("Starting inline bot...");
|
||||
|
||||
let bot = Bot::from_env().auto_send();
|
||||
let bot = Bot::from_env();
|
||||
|
||||
let handler = Update::filter_inline_query().branch(dptree::endpoint(
|
||||
|query: InlineQuery, bot: AutoSend<Bot>| async move {
|
||||
|bot: Bot, q: InlineQuery| async move {
|
||||
// First, create your actual response
|
||||
let google_search = InlineQueryResultArticle::new(
|
||||
// Each item needs a unique ID, as well as the response container for the
|
||||
|
@ -26,7 +26,7 @@ async fn main() {
|
|||
// What message will be sent when clicked/tapped
|
||||
InputMessageContent::Text(InputMessageContentText::new(format!(
|
||||
"https://www.google.com/search?q={}",
|
||||
query.query,
|
||||
q.query,
|
||||
))),
|
||||
);
|
||||
// While constructing them from the struct itself is possible, it is preferred
|
||||
|
@ -38,7 +38,7 @@ async fn main() {
|
|||
"DuckDuckGo Search".to_string(),
|
||||
InputMessageContent::Text(InputMessageContentText::new(format!(
|
||||
"https://duckduckgo.com/?q={}",
|
||||
query.query
|
||||
q.query
|
||||
))),
|
||||
)
|
||||
.description("DuckDuckGo Search")
|
||||
|
@ -52,7 +52,7 @@ async fn main() {
|
|||
|
||||
// Send it off! One thing to note -- the ID we use here must be of the query
|
||||
// we're responding to.
|
||||
let response = bot.answer_inline_query(&query.id, results).send().await;
|
||||
let response = bot.answer_inline_query(&q.id, results).send().await;
|
||||
if let Err(err) = response {
|
||||
log::error!("Error in handler: {:?}", err);
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ async fn main() {
|
|||
pretty_env_logger::init();
|
||||
log::info!("Starting ngrok ping-pong bot...");
|
||||
|
||||
let bot = Bot::from_env().auto_send();
|
||||
let bot = Bot::from_env();
|
||||
|
||||
let addr = ([127, 0, 0, 1], 8443).into();
|
||||
let url = "Your HTTPS ngrok URL here. Get it by `ngrok http 8443`".parse().unwrap();
|
||||
|
@ -18,9 +18,9 @@ async fn main() {
|
|||
|
||||
teloxide::repl_with_listener(
|
||||
bot,
|
||||
|msg: Message, bot: AutoSend<Bot>| async move {
|
||||
|bot: Bot, msg: Message| async move {
|
||||
bot.send_message(msg.chat.id, "pong").await?;
|
||||
respond(())
|
||||
Ok(())
|
||||
},
|
||||
listener,
|
||||
)
|
||||
|
|
|
@ -33,7 +33,7 @@ pub enum State {
|
|||
}
|
||||
|
||||
#[derive(BotCommands, Clone)]
|
||||
#[command(rename = "lowercase", description = "These commands are supported:")]
|
||||
#[command(rename_rule = "lowercase", description = "These commands are supported:")]
|
||||
enum Command {
|
||||
#[command(description = "display this text.")]
|
||||
Help,
|
||||
|
@ -48,7 +48,7 @@ async fn main() {
|
|||
pretty_env_logger::init();
|
||||
log::info!("Starting purchase bot...");
|
||||
|
||||
let bot = Bot::from_env().auto_send();
|
||||
let bot = Bot::from_env();
|
||||
|
||||
Dispatcher::builder(bot, schema())
|
||||
.dependencies(dptree::deps![InMemStorage::<State>::new()])
|
||||
|
@ -83,34 +83,30 @@ fn schema() -> UpdateHandler<Box<dyn std::error::Error + Send + Sync + 'static>>
|
|||
.branch(callback_query_handler)
|
||||
}
|
||||
|
||||
async fn start(bot: AutoSend<Bot>, msg: Message, dialogue: MyDialogue) -> HandlerResult {
|
||||
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 help(bot: AutoSend<Bot>, msg: Message) -> HandlerResult {
|
||||
async fn help(bot: Bot, msg: Message) -> HandlerResult {
|
||||
bot.send_message(msg.chat.id, Command::descriptions().to_string()).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn cancel(bot: AutoSend<Bot>, msg: Message, dialogue: MyDialogue) -> HandlerResult {
|
||||
async fn cancel(bot: Bot, dialogue: MyDialogue, msg: Message) -> HandlerResult {
|
||||
bot.send_message(msg.chat.id, "Cancelling the dialogue.").await?;
|
||||
dialogue.exit().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn invalid_state(bot: AutoSend<Bot>, msg: Message) -> HandlerResult {
|
||||
async fn invalid_state(bot: Bot, msg: Message) -> HandlerResult {
|
||||
bot.send_message(msg.chat.id, "Unable to handle the message. Type /help to see the usage.")
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn receive_full_name(
|
||||
bot: AutoSend<Bot>,
|
||||
msg: Message,
|
||||
dialogue: MyDialogue,
|
||||
) -> HandlerResult {
|
||||
async fn receive_full_name(bot: Bot, dialogue: MyDialogue, msg: Message) -> HandlerResult {
|
||||
match msg.text().map(ToOwned::to_owned) {
|
||||
Some(full_name) => {
|
||||
let products = ["Apple", "Banana", "Orange", "Potato"]
|
||||
|
@ -130,10 +126,10 @@ async fn receive_full_name(
|
|||
}
|
||||
|
||||
async fn receive_product_selection(
|
||||
bot: AutoSend<Bot>,
|
||||
q: CallbackQuery,
|
||||
bot: Bot,
|
||||
dialogue: MyDialogue,
|
||||
full_name: String,
|
||||
full_name: String, // Available from `State::ReceiveProductChoice`.
|
||||
q: CallbackQuery,
|
||||
) -> HandlerResult {
|
||||
if let Some(product) = &q.data {
|
||||
bot.send_message(
|
||||
|
|
|
@ -12,11 +12,11 @@ async fn main() {
|
|||
pretty_env_logger::init();
|
||||
log::info!("Starting shared state bot...");
|
||||
|
||||
let bot = Bot::from_env().auto_send();
|
||||
let bot = Bot::from_env();
|
||||
let messages_total = Arc::new(AtomicU64::new(0));
|
||||
|
||||
let handler = Update::filter_message().endpoint(
|
||||
|msg: Message, bot: AutoSend<Bot>, messages_total: Arc<AtomicU64>| async move {
|
||||
|bot: Bot, messages_total: Arc<AtomicU64>, msg: Message| async move {
|
||||
let previous = messages_total.fetch_add(1, Ordering::Relaxed);
|
||||
bot.send_message(msg.chat.id, format!("I received {previous} messages in total."))
|
||||
.await?;
|
||||
|
|
|
@ -7,11 +7,11 @@ async fn main() {
|
|||
pretty_env_logger::init();
|
||||
log::info!("Starting throw dice bot...");
|
||||
|
||||
let bot = Bot::from_env().auto_send();
|
||||
let bot = Bot::from_env();
|
||||
|
||||
teloxide::repl(bot, |message: Message, bot: AutoSend<Bot>| async move {
|
||||
bot.send_dice(message.chat.id).await?;
|
||||
respond(())
|
||||
teloxide::repl(bot, |bot: Bot, msg: Message| async move {
|
||||
bot.send_dice(msg.chat.id).await?;
|
||||
Ok(())
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
[toolchain]
|
||||
channel = "nightly-2022-07-01"
|
||||
channel = "nightly-2022-09-01"
|
||||
components = ["rustfmt", "clippy"]
|
||||
profile = "minimal"
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
//! ```no_run
|
||||
//! # use teloxide::utils::command::BotCommands;
|
||||
//! #[derive(BotCommands, Clone)]
|
||||
//! #[command(rename = "lowercase", description = "These commands are supported:")]
|
||||
//! #[command(rename_rule = "lowercase", description = "These commands are supported:")]
|
||||
//! enum Command {
|
||||
//! #[command(description = "display this text.")]
|
||||
//! Help,
|
||||
|
@ -102,10 +102,10 @@
|
|||
//! -- no problem, reuse [`dptree::Handler::filter`], [`dptree::case!`], and
|
||||
//! other combinators in the same way!
|
||||
//!
|
||||
//! Finally, we define our endpoints like this:
|
||||
//! Finally, we define our endpoints:
|
||||
//!
|
||||
//! ```no_run
|
||||
//! # use teloxide::{Bot, adaptors::AutoSend};
|
||||
//! # use teloxide::Bot;
|
||||
//! # use teloxide::types::{Message, CallbackQuery};
|
||||
//! # use teloxide::dispatching::dialogue::{InMemStorage, Dialogue};
|
||||
//! # enum State{}
|
||||
|
@ -113,47 +113,38 @@
|
|||
//! type MyDialogue = Dialogue<State, InMemStorage<State>>;
|
||||
//! type HandlerResult = Result<(), Box<dyn std::error::Error + Send + Sync>>;
|
||||
//!
|
||||
//! async fn start(bot: AutoSend<Bot>, msg: Message, dialogue: MyDialogue) -> HandlerResult {
|
||||
//! async fn start(bot: Bot, dialogue: MyDialogue, msg: Message) -> HandlerResult {
|
||||
//! todo!()
|
||||
//! }
|
||||
//!
|
||||
//! async fn help(bot: AutoSend<Bot>, msg: Message) -> HandlerResult {
|
||||
//! async fn help(bot: Bot, msg: Message) -> HandlerResult {
|
||||
//! todo!()
|
||||
//! }
|
||||
//!
|
||||
//! async fn cancel(bot: AutoSend<Bot>, msg: Message, dialogue: MyDialogue) -> HandlerResult {
|
||||
//! async fn cancel(bot: Bot, dialogue: MyDialogue, msg: Message) -> HandlerResult {
|
||||
//! todo!()
|
||||
//! }
|
||||
//!
|
||||
//! async fn invalid_state(bot: AutoSend<Bot>, msg: Message) -> HandlerResult {
|
||||
//! async fn invalid_state(bot: Bot, msg: Message) -> HandlerResult {
|
||||
//! todo!()
|
||||
//! }
|
||||
//!
|
||||
//! async fn receive_full_name(
|
||||
//! bot: AutoSend<Bot>,
|
||||
//! msg: Message,
|
||||
//! dialogue: MyDialogue,
|
||||
//! ) -> HandlerResult {
|
||||
//! async fn receive_full_name(bot: Bot, dialogue: MyDialogue, msg: Message) -> HandlerResult {
|
||||
//! todo!()
|
||||
//! }
|
||||
//!
|
||||
//! async fn receive_product_selection(
|
||||
//! bot: AutoSend<Bot>,
|
||||
//! q: CallbackQuery,
|
||||
//! bot: Bot,
|
||||
//! dialogue: MyDialogue,
|
||||
//! full_name: String,
|
||||
//! full_name: String, // Available from `State::ReceiveProductChoice`.
|
||||
//! q: CallbackQuery,
|
||||
//! ) -> HandlerResult {
|
||||
//! todo!()
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! Each parameter is supplied as a dependency by teloxide. In particular:
|
||||
//! - `bot: AutoSend<Bot>` comes from the dispatcher (see below);
|
||||
//! - `msg: Message` comes from [`Update::filter_message`];
|
||||
//! - `q: CallbackQuery` comes from [`Update::filter_callback_query`];
|
||||
//! - `dialogue: MyDialogue` comes from [`dialogue::enter`];
|
||||
//! Each parameter is supplied as a dependency by `teloxide`. In particular:
|
||||
//! - `bot: Bot` comes from the dispatcher (see below)
|
||||
//! - `msg: Message` comes from [`Update::filter_message`]
|
||||
//! - `q: CallbackQuery` comes from [`Update::filter_callback_query`]
|
||||
//! - `dialogue: MyDialogue` comes from [`dialogue::enter`]
|
||||
//! - `full_name: String` comes from `dptree::case![State::ReceiveProductChoice
|
||||
//! { full_name }]`.
|
||||
//! { full_name }]`
|
||||
//!
|
||||
//! Inside `main`, we plug the schema into [`Dispatcher`] like this:
|
||||
//!
|
||||
|
@ -165,7 +156,7 @@
|
|||
//! # fn schema() -> teloxide::dispatching::UpdateHandler<Box<dyn std::error::Error + Send + Sync + 'static>> { teloxide::dptree::entry() }
|
||||
//! #[tokio::main]
|
||||
//! async fn main() {
|
||||
//! let bot = Bot::from_env().auto_send();
|
||||
//! let bot = Bot::from_env();
|
||||
//!
|
||||
//! Dispatcher::builder(bot, schema())
|
||||
//! .dependencies(dptree::deps![InMemStorage::<State>::new()])
|
||||
|
@ -187,12 +178,36 @@
|
|||
//! useful features. See [`examples/dispatching_features.rs`] as a more involved
|
||||
//! example.
|
||||
//!
|
||||
//! ## Dispatching or REPLs?
|
||||
//!
|
||||
//! The difference between dispatching and the REPLs ([`crate::repl`] & co) is
|
||||
//! that dispatching gives you a greater degree of flexibility at the cost of a
|
||||
//! bit more complicated setup.
|
||||
//!
|
||||
//! Here are things that dispatching can do, but REPLs can't:
|
||||
//! - Handle different kinds of [`Update`]
|
||||
//! - [Pass dependencies] to handlers
|
||||
//! - Disable a [default Ctrl-C handling]
|
||||
//! - Control your [default] and [error] handlers
|
||||
//! - Use [dialogues]
|
||||
//! - Use [`dptree`]-related functionality
|
||||
//! - Probably more
|
||||
//!
|
||||
//! Thus, REPLs are good for simple bots and rapid prototyping, but for more
|
||||
//! involved scenarios, we recommend using dispatching over REPLs.
|
||||
//!
|
||||
//! [Pass dependencies]: DispatcherBuilder#method.dependencies
|
||||
//! [default Ctrl-C handling]: DispatcherBuilder#method.enable_ctrlc_handler
|
||||
//! [default]: DispatcherBuilder#method.default_handler
|
||||
//! [error]: DispatcherBuilder#method.error_handler
|
||||
//! [dialogues]: dialogue
|
||||
//! [`examples/purchase.rs`]: https://github.com/teloxide/teloxide/blob/master/examples/purchase.rs
|
||||
//! [`Update::filter_message`]: crate::types::Update::filter_message
|
||||
//! [`Update::filter_callback_query`]: crate::types::Update::filter_callback_query
|
||||
//! [chain of responsibility]: https://en.wikipedia.org/wiki/Chain-of-responsibility_pattern
|
||||
//! [dependency injection (DI)]: https://en.wikipedia.org/wiki/Dependency_injection
|
||||
//! [`examples/dispatching_features.rs`]: https://github.com/teloxide/teloxide/blob/master/examples/dispatching_features.rs
|
||||
//! [`Update`]: crate::types::Update
|
||||
|
||||
#[cfg(all(feature = "ctrlc_handler"))]
|
||||
pub mod repls;
|
||||
|
@ -203,8 +218,6 @@ mod distribution;
|
|||
mod filter_ext;
|
||||
mod handler_description;
|
||||
mod handler_ext;
|
||||
mod handler_factory;
|
||||
pub mod stop_token;
|
||||
pub mod update_listeners;
|
||||
|
||||
pub use crate::utils::shutdown_token::{IdleShutdownError, ShutdownToken};
|
||||
|
@ -213,5 +226,3 @@ pub use distribution::DefaultKey;
|
|||
pub use filter_ext::{MessageFilterExt, UpdateFilterExt};
|
||||
pub use handler_description::DpHandlerDescription;
|
||||
pub use handler_ext::{filter_command, HandlerExt};
|
||||
#[allow(deprecated)]
|
||||
pub use handler_factory::HandlerFactory;
|
||||
|
|
|
@ -13,26 +13,35 @@
|
|||
//! [`examples/dialogue.rs`] clearly demonstrates the typical usage of
|
||||
//! dialogues. Your dialogue state can be represented as an enumeration:
|
||||
//!
|
||||
//! ```ignore
|
||||
//! ```no_run
|
||||
//! #[derive(Clone, Default)]
|
||||
//! pub enum State {
|
||||
//! #[default]
|
||||
//! Start,
|
||||
//! ReceiveFullName,
|
||||
//! ReceiveAge { full_name: String },
|
||||
//! ReceiveLocation { full_name: String, age: u8 },
|
||||
//! ReceiveAge {
|
||||
//! full_name: String,
|
||||
//! },
|
||||
//! ReceiveLocation {
|
||||
//! full_name: String,
|
||||
//! age: u8,
|
||||
//! },
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! Each state is associated with its respective handler: e.g., when a dialogue
|
||||
//! state is `ReceiveAge`, `receive_age` is invoked:
|
||||
//!
|
||||
//! ```ignore
|
||||
//! ```no_run
|
||||
//! # use teloxide::{dispatching::dialogue::InMemStorage, prelude::*};
|
||||
//! # type MyDialogue = Dialogue<State, InMemStorage<State>>;
|
||||
//! # type HandlerResult = Result<(), Box<dyn std::error::Error + Send + Sync>>;
|
||||
//! # #[derive(Clone, Debug)] enum State { ReceiveLocation { full_name: String, age: u8 } }
|
||||
//! async fn receive_age(
|
||||
//! bot: AutoSend<Bot>,
|
||||
//! msg: Message,
|
||||
//! bot: Bot,
|
||||
//! dialogue: MyDialogue,
|
||||
//! full_name: String, // Available from `State::ReceiveAge`.
|
||||
//! msg: Message,
|
||||
//! ) -> HandlerResult {
|
||||
//! match msg.text().map(|text| text.parse::<u8>()) {
|
||||
//! Some(Ok(age)) => {
|
||||
|
@ -55,13 +64,17 @@
|
|||
//! the dialogue, just call [`Dialogue::exit`] and it will be removed from the
|
||||
//! underlying storage:
|
||||
//!
|
||||
//! ```ignore
|
||||
//! ```no_run
|
||||
//! # use teloxide::{dispatching::dialogue::InMemStorage, prelude::*};
|
||||
//! # type MyDialogue = Dialogue<State, InMemStorage<State>>;
|
||||
//! # type HandlerResult = Result<(), Box<dyn std::error::Error + Send + Sync>>;
|
||||
//! # #[derive(Clone, Debug)] enum State {}
|
||||
//! async fn receive_location(
|
||||
//! bot: AutoSend<Bot>,
|
||||
//! msg: Message,
|
||||
//! bot: Bot,
|
||||
//! dialogue: MyDialogue,
|
||||
//! (full_name, age): (String, u8), // Available from `State::ReceiveLocation`.
|
||||
//! ) -> anyhow::Result<()> {
|
||||
//! msg: Message,
|
||||
//! ) -> HandlerResult {
|
||||
//! match msg.text() {
|
||||
//! Some(location) => {
|
||||
//! let message =
|
||||
|
@ -198,6 +211,7 @@ where
|
|||
/// - `Upd`
|
||||
///
|
||||
/// [`HandlerExt::enter_dialogue`]: super::HandlerExt::enter_dialogue
|
||||
#[must_use]
|
||||
pub fn enter<Upd, S, D, Output>() -> Handler<'static, DependencyMap, Output, DpHandlerDescription>
|
||||
where
|
||||
S: Storage<D> + ?Sized + Send + Sync + 'static,
|
||||
|
@ -206,18 +220,17 @@ where
|
|||
Upd: GetChatId + Clone + Send + Sync + 'static,
|
||||
Output: Send + Sync + 'static,
|
||||
{
|
||||
dptree::entry()
|
||||
.chain(dptree::filter_map(|storage: Arc<S>, upd: Upd| {
|
||||
let chat_id = upd.chat_id()?;
|
||||
Some(Dialogue::new(storage, chat_id))
|
||||
}))
|
||||
.chain(dptree::filter_map_async(|dialogue: Dialogue<D, S>| async move {
|
||||
match dialogue.get_or_default().await {
|
||||
Ok(dialogue) => Some(dialogue),
|
||||
Err(err) => {
|
||||
log::error!("dialogue.get_or_default() failed: {:?}", err);
|
||||
None
|
||||
}
|
||||
dptree::filter_map(|storage: Arc<S>, upd: Upd| {
|
||||
let chat_id = upd.chat_id()?;
|
||||
Some(Dialogue::new(storage, chat_id))
|
||||
})
|
||||
.filter_map_async(|dialogue: Dialogue<D, S>| async move {
|
||||
match dialogue.get_or_default().await {
|
||||
Ok(dialogue) => Some(dialogue),
|
||||
Err(err) => {
|
||||
log::error!("dialogue.get_or_default() failed: {:?}", err);
|
||||
None
|
||||
}
|
||||
}))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
dispatching::{
|
||||
distribution::default_distribution_function, stop_token::StopToken, update_listeners,
|
||||
distribution::default_distribution_function, update_listeners,
|
||||
update_listeners::UpdateListener, DefaultKey, DpHandlerDescription, ShutdownToken,
|
||||
},
|
||||
error_handlers::{ErrorHandler, LoggingErrorHandler},
|
||||
|
@ -27,6 +27,9 @@ use std::{
|
|||
};
|
||||
|
||||
/// The builder for [`Dispatcher`].
|
||||
///
|
||||
/// See also: ["Dispatching or
|
||||
/// REPLs?"](../dispatching/index.html#dispatching-or-repls)
|
||||
pub struct DispatcherBuilder<R, Err, Key> {
|
||||
bot: R,
|
||||
dependencies: DependencyMap,
|
||||
|
@ -171,11 +174,14 @@ where
|
|||
|
||||
/// The base for update dispatching.
|
||||
///
|
||||
/// Updates from different chats are handles concurrently, whereas updates from
|
||||
/// Updates from different chats are handled concurrently, whereas updates from
|
||||
/// the same chats are handled sequentially. If the dispatcher is unable to
|
||||
/// determine a chat ID of an incoming update, it will be handled concurrently.
|
||||
/// Note that this behaviour can be altered with [`distribution_function`].
|
||||
///
|
||||
/// See also: ["Dispatching or
|
||||
/// REPLs?"](../dispatching/index.html#dispatching-or-repls)
|
||||
///
|
||||
/// [`distribution_function`]: DispatcherBuilder::distribution_function
|
||||
pub struct Dispatcher<R, Err, Key> {
|
||||
bot: R,
|
||||
|
@ -281,14 +287,14 @@ where
|
|||
/// This method adds the same dependencies as [`Dispatcher::dispatch`].
|
||||
///
|
||||
/// [`shutdown`]: ShutdownToken::shutdown
|
||||
pub async fn dispatch_with_listener<'a, UListener, ListenerE, Eh>(
|
||||
pub async fn dispatch_with_listener<'a, UListener, Eh>(
|
||||
&'a mut self,
|
||||
mut update_listener: UListener,
|
||||
update_listener_error_handler: Arc<Eh>,
|
||||
) where
|
||||
UListener: UpdateListener<ListenerE> + 'a,
|
||||
Eh: ErrorHandler<ListenerE> + 'a,
|
||||
ListenerE: Debug,
|
||||
UListener: UpdateListener + 'a,
|
||||
Eh: ErrorHandler<UListener::Err> + 'a,
|
||||
UListener::Err: Debug,
|
||||
{
|
||||
// FIXME: there should be a way to check if dependency is already inserted
|
||||
let me = self.bot.get_me().send().await.expect("Failed to retrieve 'me'");
|
||||
|
|
|
@ -56,11 +56,11 @@ mod private {
|
|||
macro_rules! define_message_ext {
|
||||
($( ($func:ident, $fn_name:path) ,)*) => {
|
||||
define_ext! {
|
||||
MessageFilterExt, crate::types::Message =>
|
||||
MessageFilterExt, Message =>
|
||||
$((
|
||||
$func,
|
||||
(|x| $fn_name(&x).map(ToOwned::to_owned)),
|
||||
concat!("Applies the [`crate::types::", stringify!($fn_name), "`] filter.")
|
||||
concat!("Applies the [`", stringify!($fn_name), "`] filter.")
|
||||
),)*
|
||||
}
|
||||
}
|
||||
|
@ -89,14 +89,14 @@ define_message_ext! {
|
|||
macro_rules! define_update_ext {
|
||||
($( ($func:ident, $kind:path, $Allowed:ident) ,)*) => {
|
||||
define_ext! {
|
||||
UpdateFilterExt, crate::types::Update =>
|
||||
UpdateFilterExt, Update =>
|
||||
$((
|
||||
$func,
|
||||
|update: Update| match update.kind {
|
||||
$kind(x) => Some(x),
|
||||
_ => None,
|
||||
},
|
||||
concat!("Filters out [`crate::types::", stringify!($kind), "`] objects."),
|
||||
concat!("Filters out [`", stringify!($kind), "`] objects."),
|
||||
$Allowed
|
||||
),)*
|
||||
}
|
||||
|
|
|
@ -86,7 +86,7 @@ mod tests {
|
|||
use crate as teloxide; // fixup for the `BotCommands` macro
|
||||
|
||||
#[derive(BotCommands, Clone)]
|
||||
#[command(rename = "lowercase")]
|
||||
#[command(rename_rule = "lowercase")]
|
||||
enum Cmd {
|
||||
B,
|
||||
}
|
||||
|
|
|
@ -8,9 +8,6 @@ use crate::{
|
|||
};
|
||||
use dptree::{di::DependencyMap, Handler};
|
||||
|
||||
#[allow(deprecated)]
|
||||
use crate::dispatching::HandlerFactory;
|
||||
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// Extension methods for working with `dptree` handlers.
|
||||
|
@ -51,13 +48,6 @@ pub trait HandlerExt<Output> {
|
|||
<S as Storage<D>>::Error: Debug + Send,
|
||||
D: Default + Send + Sync + 'static,
|
||||
Upd: GetChatId + Clone + Send + Sync + 'static;
|
||||
|
||||
#[must_use]
|
||||
#[deprecated(note = "Use the teloxide::handler! API")]
|
||||
#[allow(deprecated)]
|
||||
fn dispatch_by<F>(self) -> Self
|
||||
where
|
||||
F: HandlerFactory<Out = Output>;
|
||||
}
|
||||
|
||||
impl<Output> HandlerExt<Output> for Handler<'static, DependencyMap, Output, DpHandlerDescription>
|
||||
|
@ -80,14 +70,6 @@ where
|
|||
{
|
||||
self.chain(super::dialogue::enter::<Upd, S, D, Output>())
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
fn dispatch_by<F>(self) -> Self
|
||||
where
|
||||
F: HandlerFactory<Out = Output>,
|
||||
{
|
||||
self.chain(F::handler())
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a handler that accepts a parsed command `C`.
|
||||
|
@ -100,13 +82,14 @@ where
|
|||
///
|
||||
/// - [`crate::types::Message`]
|
||||
/// - [`crate::types::Me`]
|
||||
#[must_use]
|
||||
pub fn filter_command<C, Output>() -> Handler<'static, DependencyMap, Output, DpHandlerDescription>
|
||||
where
|
||||
C: BotCommands + Send + Sync + 'static,
|
||||
Output: Send + Sync + 'static,
|
||||
{
|
||||
dptree::entry().chain(dptree::filter_map(move |message: Message, me: Me| {
|
||||
dptree::filter_map(move |message: Message, me: Me| {
|
||||
let bot_name = me.user.username.expect("Bots must have a username");
|
||||
message.text().and_then(|text| C::parse(text, bot_name).ok())
|
||||
}))
|
||||
message.text().and_then(|text| C::parse(text, &bot_name).ok())
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
use dptree::{di::DependencyMap, Handler};
|
||||
|
||||
use crate::dispatching::DpHandlerDescription;
|
||||
|
||||
/// Something that can construct a handler.
|
||||
#[deprecated(note = "Use the teloxide::handler! API")]
|
||||
pub trait HandlerFactory {
|
||||
type Out;
|
||||
|
||||
fn handler() -> Handler<'static, DependencyMap, Self::Out, DpHandlerDescription>;
|
||||
}
|
|
@ -1,4 +1,12 @@
|
|||
//! REPLs for dispatching updates.
|
||||
//! [REPL]s for dispatching updates.
|
||||
//!
|
||||
//! This module provides utilities for easy update handling. They accept a
|
||||
//! single "handler" function that processes all updates of a certain kind. Note
|
||||
//! that REPLs are meant to be used for simple scenarios, such as prototyping,
|
||||
//! inasmuch they lack configuration and some [advanced features].
|
||||
//!
|
||||
//! [REPL]: https://en.wikipedia.org/wiki/Read-eval-print_loop
|
||||
//! [advanced features]: crate::dispatching#dispatching-or-repls
|
||||
|
||||
mod commands_repl;
|
||||
mod repl;
|
||||
|
|
2
src/dispatching/repls/caution.md
Normal file
2
src/dispatching/repls/caution.md
Normal file
|
@ -0,0 +1,2 @@
|
|||
**DO NOT** use this function together with [`Dispatcher`] and other REPLs,
|
||||
because Telegram disallow multiple requests at the same time from the same bot.
|
|
@ -3,41 +3,68 @@ use crate::{
|
|||
update_listeners, update_listeners::UpdateListener, HandlerExt, UpdateFilterExt,
|
||||
},
|
||||
error_handlers::LoggingErrorHandler,
|
||||
requests::{Requester, ResponseResult},
|
||||
types::Update,
|
||||
utils::command::BotCommands,
|
||||
};
|
||||
use dptree::di::{DependencyMap, Injectable};
|
||||
use std::{fmt::Debug, marker::PhantomData};
|
||||
use teloxide_core::requests::Requester;
|
||||
|
||||
/// A [REPL] for commands.
|
||||
//
|
||||
///
|
||||
/// All errors from an update listener and handler will be logged.
|
||||
///
|
||||
/// 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`].
|
||||
///
|
||||
/// ## Caution
|
||||
///
|
||||
/// **DO NOT** use this function together with [`Dispatcher`] and other REPLs,
|
||||
/// because Telegram disallow multiple requests at the same time from the same
|
||||
/// bot.
|
||||
///
|
||||
/// ## Dependency requirements
|
||||
///
|
||||
/// - Those of [`HandlerExt::filter_command`].
|
||||
//
|
||||
#[doc = include_str!("preamble.md")]
|
||||
///
|
||||
/// [REPL]: https://en.wikipedia.org/wiki/Read-eval-print_loop
|
||||
/// [`Dispatcher`]: crate::dispatching::Dispatcher
|
||||
///
|
||||
/// ## Signature
|
||||
///
|
||||
/// Don't be scared by many trait bounds in the signature, 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. `cmd` is a type hint for your command enumeration
|
||||
/// `MyCommand`: just write `MyCommand::ty()`. Note that `MyCommand` must
|
||||
/// implement the [`BotCommands`] trait, typically via
|
||||
/// `#[derive(BotCommands)]`.
|
||||
///
|
||||
/// 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 async fn commands_repl<'a, R, Cmd, H, E, 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
|
||||
Cmd: BotCommands + Send + Sync + 'static,
|
||||
H: Injectable<DependencyMap, Result<(), E>, Args> + Send + Sync + 'static,
|
||||
R: Requester + Clone + Send + Sync + 'static,
|
||||
<R as Requester>::GetUpdates: Send,
|
||||
E: Debug + Send + Sync + 'static,
|
||||
H: Injectable<DependencyMap, ResponseResult<()>, Args> + Send + Sync + 'static,
|
||||
Cmd: BotCommands + Send + Sync + 'static,
|
||||
{
|
||||
let cloned_bot = bot.clone();
|
||||
|
||||
|
@ -50,57 +77,83 @@ where
|
|||
.await;
|
||||
}
|
||||
|
||||
/// Like [`commands_repl`], but with a custom [`UpdateListener`].
|
||||
/// A [REPL] for commands, with a custom [`UpdateListener`].
|
||||
//
|
||||
///
|
||||
/// All errors from an update listener and handler will be logged.
|
||||
//
|
||||
#[doc = include_str!("preamble.md")]
|
||||
///
|
||||
/// 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`].
|
||||
/// [REPL]: https://en.wikipedia.org/wiki/Read-eval-print_loop
|
||||
///
|
||||
/// ## Signature
|
||||
///
|
||||
/// Don't be scared by many trait bounds in the signature, 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`].
|
||||
/// 4. `cmd` is a type hint for your command enumeration `MyCommand`: just
|
||||
/// write `MyCommand::ty()`. Note that `MyCommand` must implement the
|
||||
/// [`BotCommands`] trait, typically via `#[derive(BotCommands)]`.
|
||||
///
|
||||
/// 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")]
|
||||
///
|
||||
/// **DO NOT** use this function together with [`Dispatcher`] and other REPLs,
|
||||
/// because Telegram disallow multiple requests at the same time from the same
|
||||
/// bot.
|
||||
///
|
||||
/// ## Dependency requirements
|
||||
///
|
||||
/// - Those of [`HandlerExt::filter_command`].
|
||||
///
|
||||
/// [`Dispatcher`]: crate::dispatching::Dispatcher
|
||||
/// [`commands_repl`]: crate::dispatching::repls::commands_repl()
|
||||
/// [`UpdateListener`]: crate::dispatching::update_listeners::UpdateListener
|
||||
#[cfg(feature = "ctrlc_handler")]
|
||||
pub async fn commands_repl_with_listener<'a, R, Cmd, H, L, ListenerE, E, Args>(
|
||||
pub async fn commands_repl_with_listener<'a, R, Cmd, H, L, Args>(
|
||||
bot: R,
|
||||
handler: H,
|
||||
listener: L,
|
||||
_cmd: PhantomData<Cmd>,
|
||||
cmd: PhantomData<Cmd>,
|
||||
) where
|
||||
Cmd: BotCommands + Send + Sync + 'static,
|
||||
H: Injectable<DependencyMap, Result<(), E>, Args> + Send + Sync + 'static,
|
||||
L: UpdateListener<ListenerE> + Send + 'a,
|
||||
ListenerE: Debug + Send + 'a,
|
||||
H: Injectable<DependencyMap, ResponseResult<()>, Args> + Send + Sync + 'static,
|
||||
L: UpdateListener + Send + 'a,
|
||||
L::Err: Debug + Send + 'a,
|
||||
R: Requester + Clone + Send + Sync + 'static,
|
||||
E: Debug + Send + Sync + 'static,
|
||||
{
|
||||
use crate::dispatching::Dispatcher;
|
||||
|
||||
let _ = cmd;
|
||||
|
||||
// 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 {});
|
||||
|
||||
Dispatcher::builder(
|
||||
bot,
|
||||
Update::filter_message().filter_command::<Cmd>().chain(dptree::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;
|
||||
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;
|
||||
}
|
||||
|
|
7
src/dispatching/repls/preamble.md
Normal file
7
src/dispatching/repls/preamble.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
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?"](dispatching/index.html#dispatching-or-repls).
|
||||
|
||||
[`Dispatcher`]: crate::dispatching::Dispatcher
|
||||
|
||||
All errors from the handler and update listener will be logged.
|
|
@ -1,67 +1,113 @@
|
|||
use crate::{
|
||||
dispatching::{update_listeners, update_listeners::UpdateListener, UpdateFilterExt},
|
||||
error_handlers::{LoggingErrorHandler, OnError},
|
||||
error_handlers::LoggingErrorHandler,
|
||||
requests::{Requester, ResponseResult},
|
||||
types::Update,
|
||||
};
|
||||
use dptree::di::{DependencyMap, Injectable};
|
||||
use std::fmt::Debug;
|
||||
use teloxide_core::requests::Requester;
|
||||
|
||||
/// A [REPL] for messages.
|
||||
//
|
||||
///
|
||||
/// All errors from an update listener and a handler will be logged.
|
||||
///
|
||||
/// 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`].
|
||||
///
|
||||
/// ## Caution
|
||||
///
|
||||
/// **DO NOT** use this function together with [`Dispatcher`] and other REPLs,
|
||||
/// because Telegram disallow multiple requests at the same time from the same
|
||||
/// bot.
|
||||
//
|
||||
#[doc = include_str!("preamble.md")]
|
||||
///
|
||||
/// [REPL]: https://en.wikipedia.org/wiki/Read-eval-print_loop
|
||||
/// [`Dispatcher`]: crate::dispatching::Dispatcher
|
||||
///
|
||||
/// ## Signature
|
||||
///
|
||||
/// Don't be scared by many trait bounds in the signature, 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`].
|
||||
///
|
||||
/// ## Handler arguments
|
||||
///
|
||||
/// `teloxide` provides the following types to the `handler`:
|
||||
/// - [`Message`]
|
||||
/// - `R` (type of the `bot`)
|
||||
/// - [`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 message without [`Me`].
|
||||
///
|
||||
/// [`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 async fn repl<R, H, E, Args>(bot: R, handler: H)
|
||||
pub async fn repl<R, H, Args>(bot: R, handler: H)
|
||||
where
|
||||
H: Injectable<DependencyMap, Result<(), E>, Args> + Send + Sync + 'static,
|
||||
Result<(), E>: OnError<E>,
|
||||
E: Debug + Send + Sync + 'static,
|
||||
R: Requester + Send + Sync + Clone + 'static,
|
||||
<R as Requester>::GetUpdates: Send,
|
||||
H: Injectable<DependencyMap, ResponseResult<()>, Args> + Send + Sync + 'static,
|
||||
{
|
||||
let cloned_bot = bot.clone();
|
||||
repl_with_listener(bot, handler, update_listeners::polling_default(cloned_bot).await).await;
|
||||
}
|
||||
|
||||
/// Like [`repl`], but with a custom [`UpdateListener`].
|
||||
/// A [REPL] for messages, with a custom [`UpdateListener`].
|
||||
//
|
||||
///
|
||||
/// All errors from an update listener and handler will be logged.
|
||||
//
|
||||
#[doc = include_str!("preamble.md")]
|
||||
///
|
||||
/// 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`].
|
||||
///
|
||||
/// # Caution
|
||||
///
|
||||
/// **DO NOT** use this function together with [`Dispatcher`] and other REPLs,
|
||||
/// because Telegram disallow multiple requests at the same time from the same
|
||||
/// bot.
|
||||
///
|
||||
/// [`Dispatcher`]: crate::dispatching::Dispatcher
|
||||
/// [`repl`]: crate::dispatching::repls::repl()
|
||||
/// [REPL]: https://en.wikipedia.org/wiki/Read-eval-print_loop
|
||||
/// [`UpdateListener`]: crate::dispatching::update_listeners::UpdateListener
|
||||
///
|
||||
/// ## Signature
|
||||
///
|
||||
/// Don't be scared by many trait bounds in the signature, 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`].
|
||||
///
|
||||
/// ## Handler arguments
|
||||
///
|
||||
/// `teloxide` provides the following types to the `handler`:
|
||||
/// - [`Message`]
|
||||
/// - `R` (type of the `bot`)
|
||||
/// - [`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 message without [`Me`].
|
||||
///
|
||||
/// [`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 async fn repl_with_listener<'a, R, H, E, L, ListenerE, Args>(bot: R, handler: H, listener: L)
|
||||
pub async fn repl_with_listener<R, H, L, Args>(bot: R, handler: H, listener: L)
|
||||
where
|
||||
H: Injectable<DependencyMap, Result<(), E>, Args> + Send + Sync + 'static,
|
||||
L: UpdateListener<ListenerE> + Send + 'a,
|
||||
ListenerE: Debug,
|
||||
Result<(), E>: OnError<E>,
|
||||
E: Debug + Send + Sync + 'static,
|
||||
R: Requester + Clone + Send + Sync + 'static,
|
||||
H: Injectable<DependencyMap, ResponseResult<()>, Args> + Send + Sync + 'static,
|
||||
L: UpdateListener + Send,
|
||||
L::Err: Debug,
|
||||
{
|
||||
use crate::dispatching::Dispatcher;
|
||||
|
||||
|
@ -69,7 +115,7 @@ where
|
|||
// messages. See <https://github.com/teloxide/teloxide/issues/557>.
|
||||
let ignore_update = |_upd| Box::pin(async {});
|
||||
|
||||
Dispatcher::builder(bot, Update::filter_message().chain(dptree::endpoint(handler)))
|
||||
Dispatcher::builder(bot, Update::filter_message().endpoint(handler))
|
||||
.default_handler(ignore_update)
|
||||
.enable_ctrlc_handler()
|
||||
.build()
|
||||
|
@ -83,7 +129,7 @@ where
|
|||
#[test]
|
||||
fn repl_is_send() {
|
||||
let bot = crate::Bot::new("");
|
||||
let repl = crate::repl(bot, || async { crate::respond(()) });
|
||||
let repl = crate::repl(bot, || async { Ok(()) });
|
||||
assert_send(&repl);
|
||||
|
||||
fn assert_send(_: &impl Send) {}
|
||||
|
|
4
src/dispatching/repls/stopping.md
Normal file
4
src/dispatching/repls/stopping.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
To stop a REPL, simply press `Ctrl`+`C` in the terminal where you run the program.
|
||||
Note that graceful shutdown may take some time (we plan to improve this, see [#711]).
|
||||
|
||||
[#711]: https://github.com/teloxide/teloxide/issues/711
|
|
@ -1,79 +0,0 @@
|
|||
//! A stop token used to stop a listener.
|
||||
|
||||
use std::{future::Future, pin::Pin, task};
|
||||
|
||||
use futures::future::{pending, AbortHandle, Abortable, Pending};
|
||||
|
||||
/// A stop token allows you to stop a listener.
|
||||
///
|
||||
/// See also: [`UpdateListener::stop_token`].
|
||||
///
|
||||
/// [`UpdateListener::stop_token`]:
|
||||
/// crate::dispatching::update_listeners::UpdateListener::stop_token
|
||||
pub trait StopToken {
|
||||
/// Stop the listener linked to this token.
|
||||
fn stop(self);
|
||||
}
|
||||
|
||||
/// A stop token which does nothing. May be used in prototyping or in cases
|
||||
/// where you do not care about graceful shutdowning.
|
||||
pub struct Noop;
|
||||
|
||||
impl StopToken for Noop {
|
||||
fn stop(self) {}
|
||||
}
|
||||
|
||||
/// A stop token which corresponds to [`AsyncStopFlag`].
|
||||
#[derive(Clone)]
|
||||
pub struct AsyncStopToken(AbortHandle);
|
||||
|
||||
/// A flag which corresponds to [`AsyncStopToken`].
|
||||
///
|
||||
/// To know if the stop token was used you can either repeatedly call
|
||||
/// [`is_stopped`] or use this type as a `Future`.
|
||||
///
|
||||
/// [`is_stopped`]: AsyncStopFlag::is_stopped
|
||||
#[pin_project::pin_project]
|
||||
#[derive(Clone)]
|
||||
pub struct AsyncStopFlag(#[pin] Abortable<Pending<()>>);
|
||||
|
||||
impl AsyncStopToken {
|
||||
/// Create a new token/flag pair.
|
||||
#[must_use = "This function is pure, that is does nothing unless its output is used"]
|
||||
pub fn new_pair() -> (Self, AsyncStopFlag) {
|
||||
let (handle, reg) = AbortHandle::new_pair();
|
||||
let token = Self(handle);
|
||||
let flag = AsyncStopFlag(Abortable::new(pending(), reg));
|
||||
|
||||
(token, flag)
|
||||
}
|
||||
}
|
||||
|
||||
impl StopToken for AsyncStopToken {
|
||||
fn stop(self) {
|
||||
self.0.abort()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncStopFlag {
|
||||
/// Returns true if the stop token linked to `self` was used.
|
||||
#[must_use = "This function is pure, that is does nothing unless its output is used"]
|
||||
pub fn is_stopped(&self) -> bool {
|
||||
self.0.is_aborted()
|
||||
}
|
||||
}
|
||||
|
||||
/// This future resolves when a stop token was used.
|
||||
impl Future for AsyncStopFlag {
|
||||
type Output = ();
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll<Self::Output> {
|
||||
self.project().0.poll(cx).map(|res| {
|
||||
debug_assert!(
|
||||
res.is_err(),
|
||||
"Pending Future can't ever be resolved, so Abortable is only resolved when \
|
||||
canceled"
|
||||
);
|
||||
})
|
||||
}
|
||||
}
|
|
@ -35,7 +35,7 @@ use futures::Stream;
|
|||
use std::time::Duration;
|
||||
|
||||
use crate::{
|
||||
dispatching::stop_token::StopToken,
|
||||
stop::StopToken,
|
||||
types::{AllowedUpdate, Update},
|
||||
};
|
||||
|
||||
|
@ -59,9 +59,11 @@ pub use self::{
|
|||
/// - [`AsUpdateStream::as_stream`]
|
||||
///
|
||||
/// [module-level documentation]: mod@self
|
||||
pub trait UpdateListener<E>: for<'a> AsUpdateStream<'a, E> {
|
||||
/// The type of token which allows to stop this listener.
|
||||
type StopToken: StopToken + Send;
|
||||
pub trait UpdateListener:
|
||||
for<'a> AsUpdateStream<'a, StreamErr = <Self as UpdateListener>::Err>
|
||||
{
|
||||
/// The type of errors that can be returned from this listener.
|
||||
type Err;
|
||||
|
||||
/// Returns a token which stops this listener.
|
||||
///
|
||||
|
@ -76,7 +78,7 @@ pub trait UpdateListener<E>: for<'a> AsUpdateStream<'a, E> {
|
|||
/// soon as all cached updates are returned.
|
||||
#[must_use = "This function doesn't stop listening, to stop listening you need to call `stop` \
|
||||
on the returned token"]
|
||||
fn stop_token(&mut self) -> Self::StopToken;
|
||||
fn stop_token(&mut self) -> StopToken;
|
||||
|
||||
/// Hint which updates should the listener listen for.
|
||||
///
|
||||
|
@ -110,16 +112,19 @@ pub trait UpdateListener<E>: for<'a> AsUpdateStream<'a, E> {
|
|||
/// [`UpdateListener`]'s supertrait/extension.
|
||||
///
|
||||
/// This trait is a workaround to not require GAT.
|
||||
pub trait AsUpdateStream<'a, E> {
|
||||
pub trait AsUpdateStream<'a> {
|
||||
/// Error that can be returned from the [`Stream`]
|
||||
///
|
||||
/// [`Stream`]: AsUpdateStream::Stream
|
||||
// NB: This should be named differently to `UpdateListener::Err`, so that it's
|
||||
// unambiguous
|
||||
type StreamErr;
|
||||
|
||||
/// The stream of updates from Telegram.
|
||||
// HACK: There is currently no way to write something like
|
||||
// `-> impl for<'a> AsUpdateStream<'a, E, Stream: Send>`. Since we return
|
||||
// `impl UpdateListener<E>` from `polling`, we need to have `Send` bound here,
|
||||
// to make the stream `Send`.
|
||||
//
|
||||
// Without this it's, for example, impossible to spawn a tokio task with
|
||||
// teloxide polling.
|
||||
type Stream: Stream<Item = Result<Update, E>> + Send + 'a;
|
||||
// NB: `Send` is not strictly required here, but it makes it easier to return
|
||||
// `impl AsUpdateStream` and also you want `Send` streams almost (?) always
|
||||
// anyway.
|
||||
type Stream: Stream<Item = Result<Update, Self::StreamErr>> + Send + 'a;
|
||||
|
||||
/// Creates the update [`Stream`].
|
||||
///
|
||||
|
@ -128,9 +133,9 @@ pub trait AsUpdateStream<'a, E> {
|
|||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn assert_update_listener<L, E>(listener: L) -> L
|
||||
pub(crate) fn assert_update_listener<L>(listener: L) -> L
|
||||
where
|
||||
L: UpdateListener<E>,
|
||||
L: UpdateListener,
|
||||
{
|
||||
listener
|
||||
}
|
||||
|
|
|
@ -13,11 +13,9 @@ use std::{
|
|||
use futures::{ready, stream::Stream};
|
||||
|
||||
use crate::{
|
||||
dispatching::{
|
||||
stop_token::{AsyncStopFlag, AsyncStopToken},
|
||||
update_listeners::{assert_update_listener, AsUpdateStream, UpdateListener},
|
||||
},
|
||||
dispatching::update_listeners::{assert_update_listener, AsUpdateStream, UpdateListener},
|
||||
requests::{HasPayload, Request, Requester},
|
||||
stop::{mk_stop_token, StopFlag, StopToken},
|
||||
types::{AllowedUpdate, Update},
|
||||
};
|
||||
|
||||
|
@ -69,7 +67,7 @@ where
|
|||
///
|
||||
/// ## Note
|
||||
///
|
||||
/// Teloxide normally (when using [`Dispatcher`] or [`repl`]s) sets this
|
||||
/// `teloxide` normally (when using [`Dispatcher`] or [`repl`]s) sets this
|
||||
/// automatically via [`hint_allowed_updates`], so you rarely need to use
|
||||
/// `allowed_updates` explicitly.
|
||||
///
|
||||
|
@ -98,7 +96,7 @@ where
|
|||
/// See also: [`polling_default`], [`Polling`].
|
||||
pub fn build(self) -> Polling<R> {
|
||||
let Self { bot, timeout, limit, allowed_updates, drop_pending_updates } = self;
|
||||
let (token, flag) = AsyncStopToken::new_pair();
|
||||
let (token, flag) = mk_stop_token();
|
||||
let polling =
|
||||
Polling { bot, timeout, limit, allowed_updates, drop_pending_updates, flag, token };
|
||||
|
||||
|
@ -242,8 +240,8 @@ pub struct Polling<B: Requester> {
|
|||
limit: Option<u8>,
|
||||
allowed_updates: Option<Vec<AllowedUpdate>>,
|
||||
drop_pending_updates: bool,
|
||||
flag: AsyncStopFlag,
|
||||
token: AsyncStopToken,
|
||||
flag: StopFlag,
|
||||
token: StopToken,
|
||||
}
|
||||
|
||||
impl<R> Polling<R>
|
||||
|
@ -291,10 +289,10 @@ pub struct PollingStream<'a, B: Requester> {
|
|||
in_flight: Option<<B::GetUpdates as Request>::Send>,
|
||||
}
|
||||
|
||||
impl<B: Requester + Send + 'static> UpdateListener<B::Err> for Polling<B> {
|
||||
type StopToken = AsyncStopToken;
|
||||
impl<B: Requester + Send + 'static> UpdateListener for Polling<B> {
|
||||
type Err = B::Err;
|
||||
|
||||
fn stop_token(&mut self) -> Self::StopToken {
|
||||
fn stop_token(&mut self) -> StopToken {
|
||||
self.token.clone()
|
||||
}
|
||||
|
||||
|
@ -309,7 +307,8 @@ impl<B: Requester + Send + 'static> UpdateListener<B::Err> for Polling<B> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, B: Requester + Send + 'a> AsUpdateStream<'a, B::Err> for Polling<B> {
|
||||
impl<'a, B: Requester + Send + 'a> AsUpdateStream<'a> for Polling<B> {
|
||||
type StreamErr = B::Err;
|
||||
type Stream = PollingStream<'a, B>;
|
||||
|
||||
fn as_stream(&'a mut self) -> Self::Stream {
|
||||
|
|
|
@ -3,10 +3,8 @@ use std::time::Duration;
|
|||
use futures::Stream;
|
||||
|
||||
use crate::{
|
||||
dispatching::{
|
||||
stop_token::{self, StopToken},
|
||||
update_listeners::{AsUpdateStream, UpdateListener},
|
||||
},
|
||||
dispatching::update_listeners::{AsUpdateStream, UpdateListener},
|
||||
stop::StopToken,
|
||||
types::{AllowedUpdate, Update},
|
||||
};
|
||||
|
||||
|
@ -30,7 +28,7 @@ pub struct StatefulListener<St, Assf, Sf, Hauf, Thf> {
|
|||
|
||||
/// The function used as [`UpdateListener::stop_token`].
|
||||
///
|
||||
/// Must implement `FnMut(&mut St) -> impl StopToken`.
|
||||
/// Must implement `FnMut(&mut St) -> StopToken`.
|
||||
pub stop_token: Sf,
|
||||
|
||||
/// The function used as [`UpdateListener::hint_allowed_updates`].
|
||||
|
@ -68,42 +66,7 @@ impl<St, Assf, Sf, Hauf, Thf> StatefulListener<St, Assf, Sf, Hauf, Thf> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<S, E>
|
||||
StatefulListener<
|
||||
S,
|
||||
for<'a> fn(&'a mut S) -> &'a mut S,
|
||||
for<'a> fn(&'a mut S) -> stop_token::Noop,
|
||||
Haufn<S>,
|
||||
Thfn<S>,
|
||||
>
|
||||
where
|
||||
S: Stream<Item = Result<Update, E>> + Unpin + Send + 'static,
|
||||
{
|
||||
/// Creates a new update listener from a stream of updates which ignores
|
||||
/// stop signals.
|
||||
///
|
||||
/// It won't be possible to ever stop this listener with a stop token.
|
||||
pub fn from_stream_without_graceful_shutdown(stream: S) -> Self {
|
||||
let this = Self::new_with_hints(
|
||||
stream,
|
||||
|s| s,
|
||||
|_| stop_token::Noop,
|
||||
None,
|
||||
Some(|_| {
|
||||
// FIXME: replace this by just Duration::MAX once 1.53 releases
|
||||
// be released
|
||||
const NANOS_PER_SEC: u32 = 1_000_000_000;
|
||||
let dmax = Duration::new(u64::MAX, NANOS_PER_SEC - 1);
|
||||
|
||||
Some(dmax)
|
||||
}),
|
||||
);
|
||||
|
||||
assert_update_listener(this)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, St, Assf, Sf, Hauf, Thf, Strm, E> AsUpdateStream<'a, E>
|
||||
impl<'a, St, Assf, Sf, Hauf, Thf, Strm, E> AsUpdateStream<'a>
|
||||
for StatefulListener<St, Assf, Hauf, Sf, Thf>
|
||||
where
|
||||
(St, Strm): 'a,
|
||||
|
@ -111,6 +74,7 @@ where
|
|||
Assf: FnMut(&'a mut St) -> Strm,
|
||||
Strm: Stream<Item = Result<Update, E>>,
|
||||
{
|
||||
type StreamErr = E;
|
||||
type Stream = Strm;
|
||||
|
||||
fn as_stream(&'a mut self) -> Self::Stream {
|
||||
|
@ -118,18 +82,16 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<St, Assf, Sf, Hauf, Stt, Thf, E> UpdateListener<E>
|
||||
for StatefulListener<St, Assf, Sf, Hauf, Thf>
|
||||
impl<St, Assf, Sf, Hauf, Thf, E> UpdateListener for StatefulListener<St, Assf, Sf, Hauf, Thf>
|
||||
where
|
||||
Self: for<'a> AsUpdateStream<'a, E>,
|
||||
Sf: FnMut(&mut St) -> Stt,
|
||||
Stt: StopToken + Send,
|
||||
Self: for<'a> AsUpdateStream<'a, StreamErr = E>,
|
||||
Sf: FnMut(&mut St) -> StopToken,
|
||||
Hauf: FnMut(&mut St, &mut dyn Iterator<Item = AllowedUpdate>),
|
||||
Thf: Fn(&St) -> Option<Duration>,
|
||||
{
|
||||
type StopToken = Stt;
|
||||
type Err = E;
|
||||
|
||||
fn stop_token(&mut self) -> Stt {
|
||||
fn stop_token(&mut self) -> StopToken {
|
||||
(self.stop_token)(&mut self.state)
|
||||
}
|
||||
|
||||
|
@ -143,10 +105,3 @@ where
|
|||
self.timeout_hint.as_ref().and_then(|f| f(&self.state))
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_update_listener<L, E>(l: L) -> L
|
||||
where
|
||||
L: UpdateListener<E>,
|
||||
{
|
||||
l
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ pub struct Options {
|
|||
/// `a-z`, `0-9`, `_` and `-` are allowed. The header is useful to ensure
|
||||
/// that the request comes from a webhook set by you.
|
||||
///
|
||||
/// Default - teloxide will generate a random token.
|
||||
/// Default - `teloxide` will generate a random token.
|
||||
pub secret_token: Option<String>,
|
||||
}
|
||||
|
||||
|
|
|
@ -6,11 +6,9 @@ use axum::{
|
|||
};
|
||||
|
||||
use crate::{
|
||||
dispatching::{
|
||||
stop_token::{AsyncStopFlag, StopToken},
|
||||
update_listeners::{webhooks::Options, UpdateListener},
|
||||
},
|
||||
dispatching::update_listeners::{webhooks::Options, UpdateListener},
|
||||
requests::Requester,
|
||||
stop::StopFlag,
|
||||
};
|
||||
|
||||
/// Webhook implementation based on the [mod@axum] framework.
|
||||
|
@ -22,7 +20,7 @@ use crate::{
|
|||
///
|
||||
/// [`set_webhook`]: crate::payloads::SetWebhook
|
||||
/// [`delete_webhook`]: crate::payloads::DeleteWebhook
|
||||
/// [`stop`]: StopToken::stop
|
||||
/// [`stop`]: crate::stop::StopToken::stop
|
||||
///
|
||||
/// ## Panics
|
||||
///
|
||||
|
@ -38,7 +36,10 @@ use crate::{
|
|||
///
|
||||
/// [`axum_to_router`] and [`axum_no_setup`] for lower-level versions of this
|
||||
/// function.
|
||||
pub async fn axum<R>(bot: R, options: Options) -> Result<impl UpdateListener<Infallible>, R::Err>
|
||||
pub async fn axum<R>(
|
||||
bot: R,
|
||||
options: Options,
|
||||
) -> Result<impl UpdateListener<Err = Infallible>, R::Err>
|
||||
where
|
||||
R: Requester + Send + 'static,
|
||||
<R as Requester>::DeleteWebhook: Send,
|
||||
|
@ -85,7 +86,7 @@ where
|
|||
///
|
||||
/// [`set_webhook`]: crate::payloads::SetWebhook
|
||||
/// [`delete_webhook`]: crate::payloads::DeleteWebhook
|
||||
/// [`stop`]: StopToken::stop
|
||||
/// [`stop`]: crate::stop::StopToken::stop
|
||||
/// [`options.address`]: Options::address
|
||||
/// [`with_graceful_shutdown`]: axum::Server::with_graceful_shutdown
|
||||
///
|
||||
|
@ -107,7 +108,10 @@ where
|
|||
pub async fn axum_to_router<R>(
|
||||
bot: R,
|
||||
mut options: Options,
|
||||
) -> Result<(impl UpdateListener<Infallible>, impl Future<Output = ()> + Send, axum::Router), R::Err>
|
||||
) -> Result<
|
||||
(impl UpdateListener<Err = Infallible>, impl Future<Output = ()> + Send, axum::Router),
|
||||
R::Err,
|
||||
>
|
||||
where
|
||||
R: Requester + Send,
|
||||
<R as Requester>::DeleteWebhook: Send,
|
||||
|
@ -148,12 +152,10 @@ where
|
|||
/// function.
|
||||
pub fn axum_no_setup(
|
||||
options: Options,
|
||||
) -> (impl UpdateListener<Infallible>, impl Future<Output = ()>, axum::Router) {
|
||||
) -> (impl UpdateListener<Err = Infallible>, impl Future<Output = ()>, axum::Router) {
|
||||
use crate::{
|
||||
dispatching::{
|
||||
stop_token::AsyncStopToken,
|
||||
update_listeners::{self, webhooks::tuple_first_mut},
|
||||
},
|
||||
dispatching::update_listeners::{self, webhooks::tuple_first_mut},
|
||||
stop::{mk_stop_token, StopToken},
|
||||
types::Update,
|
||||
};
|
||||
use axum::{extract::Extension, response::IntoResponse, routing::post};
|
||||
|
@ -172,7 +174,7 @@ pub fn axum_no_setup(
|
|||
secret_header: XTelegramBotApiSecretToken,
|
||||
secret: Extension<Option<String>>,
|
||||
tx: Extension<CSender>,
|
||||
flag: Extension<AsyncStopFlag>,
|
||||
flag: Extension<StopFlag>,
|
||||
) -> impl IntoResponse {
|
||||
// FIXME: use constant time comparison here
|
||||
if secret_header.0.as_deref() != secret.as_deref().map(str::as_bytes) {
|
||||
|
@ -208,7 +210,7 @@ pub fn axum_no_setup(
|
|||
StatusCode::OK
|
||||
}
|
||||
|
||||
let (stop_token, stop_flag) = AsyncStopToken::new_pair();
|
||||
let (stop_token, stop_flag) = mk_stop_token();
|
||||
|
||||
let app = axum::Router::new().route(options.url.path(), post(telegram_request)).layer(
|
||||
ServiceBuilder::new()
|
||||
|
@ -225,7 +227,7 @@ pub fn axum_no_setup(
|
|||
let listener = update_listeners::StatefulListener::new(
|
||||
(stream, stop_token),
|
||||
tuple_first_mut,
|
||||
|state: &mut (_, AsyncStopToken)| state.1.clone(),
|
||||
|state: &mut (_, StopToken)| state.1.clone(),
|
||||
);
|
||||
|
||||
(listener, stop_flag, app)
|
||||
|
|
|
@ -6,13 +6,13 @@
|
|||
| `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**). |
|
||||
| `auto-send` | Enables the [`AutoSend`](adaptors::AutoSend) bot adaptor (**enabled by default; DEPRECATED**). |
|
||||
| `throttle` | Enables the [`Throttle`](adaptors::Throttle) bot adaptor. |
|
||||
| `cache-me` | Enables the [`CacheMe`](adaptors::CacheMe) bot adaptor. |
|
||||
| `trace-adaptor` | Enables the [`Trace`](adaptors::Trace) bot adaptor. |
|
||||
| `erased` | Enables the [`ErasedRequester`](adaptors::ErasedRequester) bot adaptor. |
|
||||
| `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**). |
|
||||
| `rustls` | Enables the [`rustls`] TLS implementation. |
|
||||
| `redis-storage` | Enables the [Redis] storage support for dialogues. |
|
||||
|
@ -29,6 +29,6 @@
|
|||
[`native-tls`]: https://docs.rs/native-tls
|
||||
[`rustls`]: https://docs.rs/rustls
|
||||
[`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
|
11
src/lib.rs
11
src/lib.rs
|
@ -13,11 +13,11 @@
|
|||
//! pretty_env_logger::init();
|
||||
//! log::info!("Starting throw dice bot...");
|
||||
//!
|
||||
//! let bot = Bot::from_env().auto_send();
|
||||
//! let bot = Bot::from_env();
|
||||
//!
|
||||
//! teloxide::repl(bot, |message: Message, bot: AutoSend<Bot>| async move {
|
||||
//! bot.send_dice(message.chat.id).await?;
|
||||
//! respond(())
|
||||
//! teloxide::repl(bot, |bot: Bot, msg: Message| async move {
|
||||
//! bot.send_dice(msg.chat.id).await?;
|
||||
//! Ok(())
|
||||
//! })
|
||||
//! .await;
|
||||
//! # }
|
||||
|
@ -62,11 +62,10 @@ pub use dispatching::repls::{
|
|||
commands_repl, commands_repl_with_listener, repl, repl_with_listener,
|
||||
};
|
||||
|
||||
mod logging;
|
||||
|
||||
pub mod dispatching;
|
||||
pub mod error_handlers;
|
||||
pub mod prelude;
|
||||
pub mod stop;
|
||||
pub mod utils;
|
||||
|
||||
#[doc(inline)]
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
/// Enables logging through [pretty-env-logger].
|
||||
///
|
||||
/// A logger will **only** print errors, warnings, and general information from
|
||||
/// teloxide and **all** logs from your program.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Calling this macro **is not mandatory**; you can setup if your own logger if
|
||||
/// you want.
|
||||
///
|
||||
/// [pretty-env-logger]: https://crates.io/crates/pretty_env_logger
|
||||
#[macro_export]
|
||||
#[deprecated = "Choose logging implementation yourself"]
|
||||
macro_rules! enable_logging {
|
||||
() => {
|
||||
#[allow(deprecated)]
|
||||
teloxide::enable_logging_with_filter!(log::LevelFilter::Trace);
|
||||
};
|
||||
}
|
||||
|
||||
/// Enables logging through [pretty-env-logger] with a custom filter for your
|
||||
/// program.
|
||||
///
|
||||
/// A logger will **only** print errors, warnings, and general information from
|
||||
/// teloxide and restrict logs from your program by the specified filter.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Allow printing all logs from your program up to [`LevelFilter::Debug`] (i.e.
|
||||
/// do not print traces):
|
||||
///
|
||||
/// ```no_compile
|
||||
/// teloxide::enable_logging_with_filter!(log::LevelFilter::Debug);
|
||||
/// ```
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Calling this macro **is not mandatory**; you can setup if your own logger if
|
||||
/// you want.
|
||||
///
|
||||
/// [pretty-env-logger]: https://crates.io/crates/pretty_env_logger
|
||||
/// [`LevelFilter::Debug`]: https://docs.rs/log/0.4.10/log/enum.LevelFilter.html
|
||||
#[macro_export]
|
||||
#[deprecated = "Choose logging implementation yourself"]
|
||||
macro_rules! enable_logging_with_filter {
|
||||
($filter:expr) => {
|
||||
pretty_env_logger::formatted_builder()
|
||||
.write_style(pretty_env_logger::env_logger::WriteStyle::Auto)
|
||||
.filter(Some(&env!("CARGO_CRATE_NAME").replace("-", "_")), $filter)
|
||||
.filter(Some("teloxide"), log::LevelFilter::Info)
|
||||
.init();
|
||||
};
|
||||
}
|
|
@ -1,20 +1,24 @@
|
|||
//! Commonly used items.
|
||||
|
||||
pub use crate::{
|
||||
error_handlers::{LoggingErrorHandler, OnError},
|
||||
respond,
|
||||
};
|
||||
pub use crate::error_handlers::{LoggingErrorHandler, OnError};
|
||||
|
||||
#[allow(deprecated)]
|
||||
pub use crate::respond;
|
||||
|
||||
pub use crate::dispatching::{
|
||||
dialogue::Dialogue, Dispatcher, HandlerExt as _, MessageFilterExt as _, UpdateFilterExt as _,
|
||||
};
|
||||
|
||||
pub use teloxide_core::types::{
|
||||
CallbackQuery, ChatMemberUpdated, ChosenInlineResult, InlineQuery, Message, Poll, PollAnswer,
|
||||
PreCheckoutQuery, ShippingQuery, Update,
|
||||
pub use teloxide_core::{
|
||||
requests::ResponseResult,
|
||||
types::{
|
||||
CallbackQuery, ChatMemberUpdated, ChosenInlineResult, InlineQuery, Message, Poll,
|
||||
PollAnswer, PreCheckoutQuery, ShippingQuery, Update,
|
||||
},
|
||||
};
|
||||
|
||||
#[cfg(feature = "auto-send")]
|
||||
#[allow(deprecated)]
|
||||
pub use crate::adaptors::AutoSend;
|
||||
|
||||
#[doc(no_inline)]
|
||||
|
|
64
src/stop.rs
Normal file
64
src/stop.rs
Normal file
|
@ -0,0 +1,64 @@
|
|||
//! This module contains stop [token] and stop [flag] that are used to stop
|
||||
//! async tasks, for example [listeners].
|
||||
//!
|
||||
//! [token]: StopToken
|
||||
//! [flag]: StopFlag
|
||||
//! [listeners]: crate::dispatching::update_listeners
|
||||
|
||||
use std::{convert::Infallible, future::Future, pin::Pin, task};
|
||||
|
||||
use futures::future::{pending, AbortHandle, Abortable, Pending};
|
||||
|
||||
/// Create a new token/flag pair.
|
||||
#[must_use]
|
||||
pub fn mk_stop_token() -> (StopToken, StopFlag) {
|
||||
let (handle, reg) = AbortHandle::new_pair();
|
||||
let token = StopToken(handle);
|
||||
let flag = StopFlag(Abortable::new(pending(), reg));
|
||||
|
||||
(token, flag)
|
||||
}
|
||||
|
||||
/// A stop token which corresponds to a [`StopFlag`].
|
||||
#[derive(Clone)]
|
||||
pub struct StopToken(AbortHandle);
|
||||
|
||||
/// A flag which corresponds to [`StopToken`].
|
||||
///
|
||||
/// To know if the stop token was used you can either repeatedly call
|
||||
/// [`is_stopped`] or use this type as a `Future`.
|
||||
///
|
||||
/// [`is_stopped`]: StopFlag::is_stopped
|
||||
#[pin_project::pin_project]
|
||||
#[derive(Clone)]
|
||||
pub struct StopFlag(#[pin] Abortable<Pending<Infallible>>);
|
||||
|
||||
impl StopToken {
|
||||
/// "Stops" the flag associated with this token.
|
||||
///
|
||||
/// Note that calling this function multiple times does nothing, only the
|
||||
/// first call changes the state.
|
||||
pub fn stop(&self) {
|
||||
self.0.abort()
|
||||
}
|
||||
}
|
||||
|
||||
impl StopFlag {
|
||||
/// Returns true if the stop token linked to `self` was used.
|
||||
#[must_use]
|
||||
pub fn is_stopped(&self) -> bool {
|
||||
self.0.is_aborted()
|
||||
}
|
||||
}
|
||||
|
||||
/// This future resolves when a stop token was used.
|
||||
impl Future for StopFlag {
|
||||
type Output = ();
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll<Self::Output> {
|
||||
self.project().0.poll(cx).map(|res| match res {
|
||||
Err(_aborted) => (),
|
||||
Ok(unreachable) => match unreachable {},
|
||||
})
|
||||
}
|
||||
}
|
|
@ -13,7 +13,7 @@
|
|||
//! type UnitOfTime = u8;
|
||||
//!
|
||||
//! #[derive(BotCommands, PartialEq, Debug)]
|
||||
//! #[command(rename = "lowercase", parse_with = "split")]
|
||||
//! #[command(rename_rule = "lowercase", parse_with = "split")]
|
||||
//! enum AdminCommand {
|
||||
//! Mute(UnitOfTime, char),
|
||||
//! Ban(UnitOfTime, char),
|
||||
|
@ -70,7 +70,7 @@ pub use teloxide_macros::BotCommands;
|
|||
/// type UnitOfTime = u8;
|
||||
///
|
||||
/// #[derive(BotCommands, PartialEq, Debug)]
|
||||
/// #[command(rename = "lowercase", parse_with = "split")]
|
||||
/// #[command(rename_rule = "lowercase", parse_with = "split")]
|
||||
/// enum AdminCommand {
|
||||
/// Mute(UnitOfTime, char),
|
||||
/// Ban(UnitOfTime, char),
|
||||
|
@ -82,11 +82,10 @@ pub use teloxide_macros::BotCommands;
|
|||
/// ```
|
||||
///
|
||||
/// # Enum attributes
|
||||
/// 1. `#[command(rename = "rule")]`
|
||||
/// Rename all commands by `rule`. If you will not use this attribute, commands
|
||||
/// will be parsed by their original names. Allowed rules are `lowercase`,
|
||||
/// `UPPERCASE`, `PascalCase`, `camelCase`, `snake_case`,
|
||||
/// `SCREAMING_SNAKE_CASE`, `kebab-case`, and `SCREAMING-KEBAB-CASE`.
|
||||
/// 1. `#[command(rename_rule = "rule")]`
|
||||
/// Rename all commands by `rule`. Allowed rules are `lowercase`, `UPPERCASE`,
|
||||
/// `PascalCase`, `camelCase`, `snake_case`, `SCREAMING_SNAKE_CASE`,
|
||||
/// `kebab-case`, and `SCREAMING-KEBAB-CASE`.
|
||||
///
|
||||
/// 2. `#[command(prefix = "prefix")]`
|
||||
/// Change a prefix for all commands (the default is `/`).
|
||||
|
@ -106,7 +105,7 @@ pub use teloxide_macros::BotCommands;
|
|||
/// use teloxide::utils::command::BotCommands;
|
||||
///
|
||||
/// #[derive(BotCommands, PartialEq, Debug)]
|
||||
/// #[command(rename = "lowercase")]
|
||||
/// #[command(rename_rule = "lowercase")]
|
||||
/// enum Command {
|
||||
/// Text(String),
|
||||
/// }
|
||||
|
@ -126,7 +125,7 @@ pub use teloxide_macros::BotCommands;
|
|||
/// use teloxide::utils::command::BotCommands;
|
||||
///
|
||||
/// #[derive(BotCommands, PartialEq, Debug)]
|
||||
/// #[command(rename = "lowercase", parse_with = "split")]
|
||||
/// #[command(rename_rule = "lowercase", parse_with = "split")]
|
||||
/// enum Command {
|
||||
/// Nums(u8, u16, i32),
|
||||
/// }
|
||||
|
@ -146,7 +145,7 @@ pub use teloxide_macros::BotCommands;
|
|||
/// use teloxide::utils::command::BotCommands;
|
||||
///
|
||||
/// #[derive(BotCommands, PartialEq, Debug)]
|
||||
/// #[command(rename = "lowercase", parse_with = "split", separator = "|")]
|
||||
/// #[command(rename_rule = "lowercase", parse_with = "split", separator = "|")]
|
||||
/// enum Command {
|
||||
/// Nums(u8, u16, i32),
|
||||
/// }
|
||||
|
@ -159,21 +158,23 @@ pub use teloxide_macros::BotCommands;
|
|||
/// # Variant attributes
|
||||
/// All variant attributes override the corresponding `enum` attributes.
|
||||
///
|
||||
/// 1. `#[command(rename = "rule")]`
|
||||
/// 1. `#[command(rename_rule = "rule")]`
|
||||
/// Rename one command by a rule. Allowed rules are `lowercase`, `UPPERCASE`,
|
||||
/// `PascalCase`, `camelCase`, `snake_case`, `SCREAMING_SNAKE_CASE`,
|
||||
/// `kebab-case`, `SCREAMING-KEBAB-CASE`, and `%some_name%`, where `%some_name%`
|
||||
/// is any string, a new name.
|
||||
/// `kebab-case`, `SCREAMING-KEBAB-CASE`.
|
||||
///
|
||||
/// 2. `#[command(description = "description")]`
|
||||
/// 2. `#[command(rename = "name")]`
|
||||
/// Rename one command to `name` (literal renaming; do not confuse with
|
||||
/// `rename_rule`).
|
||||
///
|
||||
/// 3. `#[command(description = "description")]`
|
||||
/// Give your command a description. Write `"off"` for `"description"` to hide a
|
||||
/// command.
|
||||
///
|
||||
/// 3. `#[command(parse_with = "parser")]`
|
||||
/// One more option is available for variants.
|
||||
/// - `custom_parser` - your own parser of the signature `fn(String) ->
|
||||
/// Result<Tuple, ParseError>`, where `Tuple` corresponds to the variant's
|
||||
/// arguments.
|
||||
/// 4. `#[command(parse_with = "parser")]`
|
||||
/// Parse arguments of one command with a given parser. `parser` must be a
|
||||
/// function of the signature `fn(String) -> Result<Tuple, ParseError>`, where
|
||||
/// `Tuple` corresponds to the variant's arguments.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```
|
||||
|
@ -191,9 +192,9 @@ pub use teloxide_macros::BotCommands;
|
|||
/// }
|
||||
///
|
||||
/// #[derive(BotCommands, PartialEq, Debug)]
|
||||
/// #[command(rename = "lowercase")]
|
||||
/// #[command(rename_rule = "lowercase")]
|
||||
/// enum Command {
|
||||
/// #[command(parse_with = "accept_two_digits")]
|
||||
/// #[command(parse_with = accept_two_digits)]
|
||||
/// Num(u8),
|
||||
/// }
|
||||
///
|
||||
|
@ -204,8 +205,8 @@ pub use teloxide_macros::BotCommands;
|
|||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// 4. `#[command(prefix = "prefix")]`
|
||||
/// 5. `#[command(separator = "sep")]`
|
||||
/// 5. `#[command(prefix = "prefix")]`
|
||||
/// 6. `#[command(separator = "sep")]`
|
||||
///
|
||||
/// These attributes just override the corresponding `enum` attributes for a
|
||||
/// specific variant.
|
||||
|
@ -217,9 +218,7 @@ pub trait BotCommands: Sized {
|
|||
///
|
||||
/// `bot_username` is required to parse commands like
|
||||
/// `/cmd@username_of_the_bot`.
|
||||
fn parse<N>(s: &str, bot_username: N) -> Result<Self, ParseError>
|
||||
where
|
||||
N: Into<String>;
|
||||
fn parse(s: &str, bot_username: &str) -> Result<Self, ParseError>;
|
||||
|
||||
/// Returns descriptions of the commands suitable to be shown to the user
|
||||
/// (for example when `/help` command is used).
|
||||
|
@ -235,6 +234,7 @@ pub trait BotCommands: Sized {
|
|||
/// Returns `PhantomData<Self>` that is used as a param of [`commands_repl`]
|
||||
///
|
||||
/// [`commands_repl`]: (crate::repls2::commands_repl)
|
||||
#[must_use]
|
||||
fn ty() -> PhantomData<Self> {
|
||||
PhantomData
|
||||
}
|
||||
|
@ -296,11 +296,13 @@ pub struct CommandDescription<'a> {
|
|||
|
||||
impl<'a> CommandDescriptions<'a> {
|
||||
/// Creates new [`CommandDescriptions`] from a list of command descriptions.
|
||||
#[must_use]
|
||||
pub fn new(descriptions: &'a [CommandDescription<'a>]) -> Self {
|
||||
Self { global_description: None, descriptions, bot_username: None }
|
||||
}
|
||||
|
||||
/// Sets the global description of these commands.
|
||||
#[must_use]
|
||||
pub fn global_description(self, global_description: &'a str) -> Self {
|
||||
Self { global_description: Some(global_description), ..self }
|
||||
}
|
||||
|
@ -328,6 +330,7 @@ impl<'a> CommandDescriptions<'a> {
|
|||
/// message"
|
||||
/// );
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub fn username(self, bot_username: &'a str) -> Self {
|
||||
Self { bot_username: Some(bot_username), ..self }
|
||||
}
|
||||
|
@ -338,6 +341,7 @@ impl<'a> CommandDescriptions<'a> {
|
|||
/// method to get the username.
|
||||
///
|
||||
/// [`username`]: self::CommandDescriptions::username
|
||||
#[must_use]
|
||||
pub fn username_from_me(self, me: &'a Me) -> CommandDescriptions<'a> {
|
||||
self.username(me.user.username.as_deref().expect("Bots must have usernames"))
|
||||
}
|
||||
|
|
|
@ -93,7 +93,7 @@ impl fmt::Display for IdleShutdownError {
|
|||
|
||||
impl std::error::Error for IdleShutdownError {}
|
||||
|
||||
pub(crate) fn shutdown_check_timeout_for<E>(update_listener: &impl UpdateListener<E>) -> Duration {
|
||||
pub(crate) fn shutdown_check_timeout_for(update_listener: &impl UpdateListener) -> Duration {
|
||||
const MIN_SHUTDOWN_CHECK_TIMEOUT: Duration = Duration::from_secs(1);
|
||||
const DZERO: Duration = Duration::ZERO;
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ use teloxide::utils::command::BotCommands;
|
|||
#[cfg(feature = "macros")]
|
||||
fn parse_command_with_args() {
|
||||
#[derive(BotCommands, Debug, PartialEq)]
|
||||
#[command(rename = "lowercase")]
|
||||
#[command(rename_rule = "lowercase")]
|
||||
enum DefaultCommands {
|
||||
Start(String),
|
||||
Help,
|
||||
|
@ -27,7 +27,7 @@ fn parse_command_with_args() {
|
|||
#[cfg(feature = "macros")]
|
||||
fn parse_command_with_non_string_arg() {
|
||||
#[derive(BotCommands, Debug, PartialEq)]
|
||||
#[command(rename = "lowercase")]
|
||||
#[command(rename_rule = "lowercase")]
|
||||
enum DefaultCommands {
|
||||
Start(i32),
|
||||
Help,
|
||||
|
@ -43,7 +43,7 @@ fn parse_command_with_non_string_arg() {
|
|||
#[cfg(feature = "macros")]
|
||||
fn attribute_prefix() {
|
||||
#[derive(BotCommands, Debug, PartialEq)]
|
||||
#[command(rename = "lowercase")]
|
||||
#[command(rename_rule = "lowercase")]
|
||||
enum DefaultCommands {
|
||||
#[command(prefix = "!")]
|
||||
Start(String),
|
||||
|
@ -60,7 +60,7 @@ fn attribute_prefix() {
|
|||
#[cfg(feature = "macros")]
|
||||
fn many_attributes() {
|
||||
#[derive(BotCommands, Debug, PartialEq)]
|
||||
#[command(rename = "lowercase")]
|
||||
#[command(rename_rule = "lowercase")]
|
||||
enum DefaultCommands {
|
||||
#[command(prefix = "!", description = "desc")]
|
||||
Start,
|
||||
|
@ -75,7 +75,7 @@ fn many_attributes() {
|
|||
#[cfg(feature = "macros")]
|
||||
fn global_attributes() {
|
||||
#[derive(BotCommands, Debug, PartialEq)]
|
||||
#[command(prefix = "!", rename = "lowercase", description = "Bot commands")]
|
||||
#[command(prefix = "!", rename_rule = "lowercase", description = "Bot commands")]
|
||||
enum DefaultCommands {
|
||||
#[command(prefix = "/")]
|
||||
Start,
|
||||
|
@ -91,7 +91,7 @@ fn global_attributes() {
|
|||
#[cfg(feature = "macros")]
|
||||
fn parse_command_with_bot_name() {
|
||||
#[derive(BotCommands, Debug, PartialEq)]
|
||||
#[command(rename = "lowercase")]
|
||||
#[command(rename_rule = "lowercase")]
|
||||
enum DefaultCommands {
|
||||
#[command(prefix = "/")]
|
||||
Start,
|
||||
|
@ -108,7 +108,7 @@ fn parse_command_with_bot_name() {
|
|||
#[cfg(feature = "macros")]
|
||||
fn parse_with_split() {
|
||||
#[derive(BotCommands, Debug, PartialEq)]
|
||||
#[command(rename = "lowercase")]
|
||||
#[command(rename_rule = "lowercase")]
|
||||
#[command(parse_with = "split")]
|
||||
enum DefaultCommands {
|
||||
Start(u8, String),
|
||||
|
@ -125,7 +125,7 @@ fn parse_with_split() {
|
|||
#[cfg(feature = "macros")]
|
||||
fn parse_with_split2() {
|
||||
#[derive(BotCommands, Debug, PartialEq)]
|
||||
#[command(rename = "lowercase")]
|
||||
#[command(rename_rule = "lowercase")]
|
||||
#[command(parse_with = "split", separator = "|")]
|
||||
enum DefaultCommands {
|
||||
Start(u8, String),
|
||||
|
@ -159,13 +159,13 @@ fn parse_custom_parser() {
|
|||
use parser::custom_parse_function;
|
||||
|
||||
#[derive(BotCommands, Debug, PartialEq)]
|
||||
#[command(rename = "lowercase")]
|
||||
#[command(rename_rule = "lowercase")]
|
||||
enum DefaultCommands {
|
||||
#[command(parse_with = "custom_parse_function")]
|
||||
#[command(parse_with = custom_parse_function)]
|
||||
Start(u8, String),
|
||||
|
||||
// Test <https://github.com/teloxide/teloxide/issues/668>.
|
||||
#[command(parse_with = "parser::custom_parse_function")]
|
||||
#[command(parse_with = parser::custom_parse_function)]
|
||||
TestPath(u8, String),
|
||||
|
||||
Help,
|
||||
|
@ -185,7 +185,7 @@ fn parse_custom_parser() {
|
|||
#[cfg(feature = "macros")]
|
||||
fn parse_named_fields() {
|
||||
#[derive(BotCommands, Debug, PartialEq)]
|
||||
#[command(rename = "lowercase")]
|
||||
#[command(rename_rule = "lowercase")]
|
||||
#[command(parse_with = "split")]
|
||||
enum DefaultCommands {
|
||||
Start { num: u8, data: String },
|
||||
|
@ -202,7 +202,7 @@ fn parse_named_fields() {
|
|||
#[cfg(feature = "macros")]
|
||||
fn descriptions_off() {
|
||||
#[derive(BotCommands, Debug, PartialEq)]
|
||||
#[command(rename = "lowercase")]
|
||||
#[command(rename_rule = "lowercase")]
|
||||
enum DefaultCommands {
|
||||
#[command(description = "off")]
|
||||
Start,
|
||||
|
@ -217,21 +217,21 @@ fn descriptions_off() {
|
|||
fn rename_rules() {
|
||||
#[derive(BotCommands, Debug, PartialEq)]
|
||||
enum DefaultCommands {
|
||||
#[command(rename = "lowercase")]
|
||||
#[command(rename_rule = "lowercase")]
|
||||
AaaAaa,
|
||||
#[command(rename = "UPPERCASE")]
|
||||
#[command(rename_rule = "UPPERCASE")]
|
||||
BbbBbb,
|
||||
#[command(rename = "PascalCase")]
|
||||
#[command(rename_rule = "PascalCase")]
|
||||
CccCcc,
|
||||
#[command(rename = "camelCase")]
|
||||
#[command(rename_rule = "camelCase")]
|
||||
DddDdd,
|
||||
#[command(rename = "snake_case")]
|
||||
#[command(rename_rule = "snake_case")]
|
||||
EeeEee,
|
||||
#[command(rename = "SCREAMING_SNAKE_CASE")]
|
||||
#[command(rename_rule = "SCREAMING_SNAKE_CASE")]
|
||||
FffFff,
|
||||
#[command(rename = "kebab-case")]
|
||||
#[command(rename_rule = "kebab-case")]
|
||||
GggGgg,
|
||||
#[command(rename = "SCREAMING-KEBAB-CASE")]
|
||||
#[command(rename_rule = "SCREAMING-KEBAB-CASE")]
|
||||
HhhHhh,
|
||||
#[command(rename = "Bar")]
|
||||
Foo,
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
#![allow(deprecated)]
|
||||
|
||||
#[cfg(feature = "macros")]
|
||||
use teloxide::macros::DialogueState;
|
||||
// We put tests here because macro expand in unit tests in the crate was a
|
||||
// failure
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "macros")]
|
||||
fn compile_test() {
|
||||
#[allow(dead_code)]
|
||||
#[derive(DialogueState, Clone)]
|
||||
#[handler_out(Result<(), teloxide::RequestError>)]
|
||||
enum State {
|
||||
#[handler(handle_start)]
|
||||
Start,
|
||||
|
||||
#[handler(handle_have_data)]
|
||||
HaveData(String),
|
||||
}
|
||||
|
||||
impl Default for State {
|
||||
fn default() -> Self {
|
||||
Self::Start
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_start() -> Result<(), teloxide::RequestError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_have_data() -> Result<(), teloxide::RequestError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "macros")]
|
||||
fn compile_test_generics() {
|
||||
#[allow(dead_code)]
|
||||
#[derive(DialogueState, Clone)]
|
||||
#[handler_out(Result<(), teloxide::RequestError>)]
|
||||
enum State<X: Clone + Send + Sync + 'static> {
|
||||
#[handler(handle_start)]
|
||||
Start,
|
||||
|
||||
#[handler(handle_have_data)]
|
||||
HaveData(X),
|
||||
}
|
||||
|
||||
impl<X: Clone + Send + Sync + 'static> Default for State<X> {
|
||||
fn default() -> Self {
|
||||
Self::Start
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_start() -> Result<(), teloxide::RequestError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_have_data() -> Result<(), teloxide::RequestError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue