Merge pull request #738 from teloxide/dev

Merge v0.11.0
This commit is contained in:
Waffle Maybe 2022-10-07 16:25:56 +04:00 committed by GitHub
commit 4a32963901
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
47 changed files with 1313 additions and 981 deletions

View file

@ -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:

View file

@ -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.

View file

@ -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.

View file

@ -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"] }

View file

@ -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
View file

@ -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

View file

@ -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(

View file

@ -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);

View file

@ -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?
}
};

View file

@ -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(())
}

View file

@ -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 => {

View file

@ -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 => {

View file

@ -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,
)

View file

@ -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);
}

View file

@ -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,
)

View file

@ -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(

View file

@ -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?;

View file

@ -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;
}

View file

@ -1,4 +1,4 @@
[toolchain]
channel = "nightly-2022-07-01"
channel = "nightly-2022-09-01"
components = ["rustfmt", "clippy"]
profile = "minimal"

View file

@ -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;

View file

@ -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
}
}))
}
})
}

View file

@ -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'");

View file

@ -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
),)*
}

View file

@ -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,
}

View file

@ -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())
})
}

View file

@ -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>;
}

View file

@ -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;

View 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.

View file

@ -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;
}

View 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.

View file

@ -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) {}

View 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

View file

@ -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"
);
})
}
}

View file

@ -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
}

View file

@ -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 {

View file

@ -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
}

View file

@ -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>,
}

View file

@ -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)

View file

@ -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

View file

@ -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)]

View file

@ -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();
};
}

View file

@ -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
View 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 {},
})
}
}

View file

@ -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"))
}

View file

@ -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;

View file

@ -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,

View file

@ -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(())
}
}