Merge branch 'master' into postgres_storage

This commit is contained in:
Tima Kinsart 2024-02-19 01:01:29 +08:00 committed by GitHub
commit 507819ddb2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
47 changed files with 989 additions and 910 deletions

View file

@ -3,6 +3,8 @@
# https://github.com/rust-lang/cargo/issues/10333
#
# "tokio/macros" and "tokio/rt-multi-thread" are required for examples
#
# N.B.: when changing this, also change `package.metadata.docs.rs`
docs = """doc
-Zrustdoc-scrape-examples
--features=full --features=nightly
@ -11,4 +13,10 @@ docs = """doc
[build]
# We pass "--cfg docsrs" when building docs to add `This is supported on feature="..." only.`
rustdocflags = ["--cfg", "docsrs", "-Znormalize-docs"]
#
# FIXME: add back `-Znormalize-docs` once <https://github.com/rust-lang/rust/issues/81091> is fixed
# or we don't depend on `generic-array` anymore
#
# N.B.: when changing this, also change `RUSTDOCFLAGS` in `.github/workflows/ci.yml` and
# `package.metadata.docs.rs.rustdoc-args` in `crates/teloxide/Cargo.toml`
rustdocflags = ["--cfg", "docsrs"]

View file

@ -2,7 +2,7 @@
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
labels: K-bug
assignees: ''
---

View file

@ -2,7 +2,7 @@
name: Feature request
about: Suggest an idea for this project
title: 'Feature Request: <feature>'
labels: feature-request
labels: K-feature-request
assignees: Hirrolot, WaffleLapkin
---

View file

@ -2,7 +2,7 @@
name: Parse error
about: Report issue with `teloxide` parsing of telegram response
title: 'Parse Error: <type or error description>'
labels: bug, FIXME, core
labels: K-bug, FIXME, C-core
assignees: WaffleLapkin
---

View file

@ -2,7 +2,7 @@
name: Unknown telegram error
about: You've found telegram error which is not known to teloxide
title: 'Unknown Error: <error description>'
labels: bug, good first issue, FIXME, core, Unknown API error
labels: K-bug, good first issue, FIXME, C-core, A-unknown-error, A-tba-errors
assignees: ''
---

View file

@ -9,7 +9,7 @@ name: Continuous integration
env:
RUSTFLAGS: "--cfg CI_REDIS --cfg CI_POSTGRES -Dwarnings"
RUSTDOCFLAGS: -Dwarnings
RUSTDOCFLAGS: "--cfg docsrs -Dwarnings"
RUST_BACKTRACE: short
CARGO_INCREMENTAL: 0

View file

@ -50,8 +50,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Use `UserId` instead of `i64` for `user_id` in `html::user_mention` and `markdown::user_mention` ([PR 896](https://github.com/teloxide/teloxide/pull/896))
- Greatly improved the speed of graceful shutdown (`^C`) ([PR 938](https://github.com/teloxide/teloxide/pull/938))
- Fix typos in docstrings ([PR 953](https://github.com/teloxide/teloxide/pull/953))
- Fix typos in documentation ([PR 953](https://github.com/teloxide/teloxide/pull/953))
- Use `Seconds` instead of `String` in `InlineQueryResultAudio` for `audio_duration` ([PR 994](https://github.com/teloxide/teloxide/pull/994))
- High CPU usage on network errors ([PR 1002](https://github.com/teloxide/teloxide/pull/1002), [Issue 780](https://github.com/teloxide/teloxide/issues/780))
### Changed

View file

@ -1,15 +1,120 @@
# Contributing
Before contributing, please read [our code style](https://github.com/teloxide/teloxide/blob/master/CODE_STYLE.md) and [the license](https://github.com/teloxide/teloxide/blob/master/LICENSE).
> [!NOTE]
>
> These contributing instructions might not be fully up-to-date or complete.
> However, they should be a good starting point.
>
> If you find inaccuracies/missing things, please expand this or contact us.
To change the source code, fork the `master` branch of this repository and work inside your own branch. Then send us a PR into `master` branch and wait for the CI to check everything. However, you'd better check changes first locally:
## Reporting bugs, questions, feature requests
```
cargo clippy --all --all-features --all-targets
cargo test --all
RUSTDOCFLAGS="--cfg docsrs" cargo doc --open --all-features
# Using nightly rustfmt
cargo +nightly fmt --all -- --check
To report a bug or suggest new functionality, go to the [issues](https://github.com/teloxide/teloxide/issues). Try to make MRE (**M**inimal **R**eproducible **E**xample) and specify your `teloxide` version to let others help you.
If you want to ask a question, you can either
- open a new [GitHub discussion](https://github.com/teloxide/teloxide/discussions), or
- write to our Telegram group ([ENG](https://t.me/teloxide), [RU](https://t.me/teloxide_ru)).
## Code
### Style guide
Before writing code, please read [our code style](./CODE_STYLE.md).
### Git
To change the source code, you need a local copy of it. Fork the `master` branch of this repository via GitHub and clone your fork locally.
When working on a new thing, create a new branch with `git switch -c my-branch-name` (or other commands that work with branches). This way, it will be easier to manage when you want to do other things.
When your changes are ready, you can open a new GitHub pull request.
### Pull Requests
If your pull request fixes/resolves an existing [GitHub issue], please specify so in the PR description. For example:
> Fixes #991.
You can learn more about [using keywords in issues and pull requests] in the GitHub documentation.
If your pull request suggests new functionality or new changes, please explain your point of view and all the necessary details (pros, cons, why you chose the design you chose, your use cases, etc.)
In general, try to make PR title/description as clear as possible, as they are the primary ways of communicating your intent to the reviewer.
[GitHub issue]: https://github.com/teloxide/teloxide/issues
[using keywords in issues and pull requests]: https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/using-keywords-in-issues-and-pull-requests
### Merge conflicts
If your branch has conflicts with master, please resolve them by doing something like this:
```shell
# Temporary switch to master branch
git switch master
# Pull changes from the upstream.
# You may need to use something different from "origin",
# depending on how you setup your remotes.
git pull origin master
# Switch back to your feature branch
git switch -
# Move your changes on top of changes in master branch.
git rebase master
# Here you'll need to resolve the conflicts,
# git commands will print some guidance.
# Once conflicts are resolved,
# forcefully push the changes to your fork
git push --force-with-lease
```
To report a bug, suggest new functionality, or ask a question, go to [Issues](https://github.com/teloxide/teloxide/issues). Try to make MRE (**M**inimal **R**eproducible **E**xample) and specify your teloxide version to let others help you.
### Testing
When you open a PR, it will be tested in the CI. We recommend you test the PR before opening it:
```shell
# Formatting (use `-- --check` if you only want to check)
cargo fmt --all
# Build
cargo build --features "full nightly"
# Run linter
cargo clippy --all-targets --features "full nightly"
# Running tests
cargo test --features "full nightly"
# Documentation (use --open if you want to open it in a browser)
# (note the -s, `docs` is an alias to pass some additional flags to `rustdoc`)
cargo docs
```
## @teloxidebot
`teloxide` uses @teloxidebot as a helper to manage PRs and issues. It is based on triagebot used by rustc developers, which docs can be found [here](https://forge.rust-lang.org/triagebot/index.html).
We will describe here a few most used @teloxidebot's features, but we still recommend you to read the docs.
### PR status tracking
`teloxide` uses `S-*` labels (mainly https://github.com/teloxide/teloxide/labels/S-waiting-on-author and https://github.com/teloxide/teloxide/labels/S-waiting-on-review) to track the status of pull requests.
You can change the status with `@teloxidebot review` and `@teloxidebot ready` (sets the status to https://github.com/teloxide/teloxide/labels/S-waiting-on-review) or `@teloxidebot author` (sets the status to https://github.com/teloxide/teloxide/labels/S-waiting-on-author).
Requesting a review from PR's assignee via GitHub UI will also change the status of the PR to waiting on review. Similarly, submitting a review that requests changes will change the status of the PR to waiting on author.
There is also https://github.com/teloxide/teloxide/labels/S-blocked, which can be set with `@teloxidebot blocked`.
Please note that your PR won't be reviewed unless it's waiting for review :)
### Labels
Normally, GitHub only allows privileged users to change labels. @teloxidebot allows anyone to add or remove certain [labels](https://github.com/teloxide/teloxide/labels/) with `@teloxidebot label +additional_label -removed_label`. See more in the [documentation](https://forge.rust-lang.org/triagebot/index.html).
### PR assignment
When a PR is created, @teloxidebot will automatically assign one of the maintainers to it. If you want to override this assignment, use `r? @ReviewerUsername`.

View file

@ -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.4%20(inclusively)-green.svg">
<img src="https://img.shields.io/badge/API%20coverage-Up%20to%206.5%20(inclusively)-green.svg">
</a>
<a href="https://t.me/teloxide">
<img src="https://img.shields.io/badge/support-t.me%2Fteloxide-blueviolet">
@ -286,6 +286,11 @@ async fn receive_location(
[More examples >>](crates/teloxide/examples/)
## Tutorials
- [_"Migrating my family finance bot from Python to Rust (teloxide) because I am tired of exceptions (part 1)"_](https://trkohler.com/posts/i-migrated-my-family-finance-bot-from-python-to-rust-because-i-am-tired-of-exceptions/) by Troy Köhler.
- [_"Migrating my family finance bot from Python to Rust (teloxide) [part 2]"_](https://trkohler.com/posts/migrating-my-family-finance-bot-from-python-to-rust-teloxide-part-2/) by Troy Köhler.
## FAQ
**Q: Where I can ask questions?**

View file

@ -33,6 +33,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `video_chat_ended`
- `web_app_data`
- `is_delete_chat_photo`, `is_group_chat_created`, `is_super_group_chat_created`, `is_channel_chat_created` functions to `Message` ([#982][pr982])
- Support for TBA 6.5 ([#954][pr954])
- Add `can_send_audios`, `can_send_documents`, `can_send_photos`, `can_send_videos`, `can_send_video_notes`, and `can_send_voice_notes` to `ChatPermissions` and `Restricted`
- Add `use_independent_chat_permissions` optional parameter to `restrict_chat_member` and `set_chat_permissions`
- Add `user_chat_id` field to `ChatJoinRequest`
- Add `KeyboardButtonRequestChat` and `ChatShared` types
- Add `RequestChat` variant to `ButtonRequest`
- Add `ChatShared` variant to `MessageKind`
- Add `shared_chat` method to `Message`
- Add `KeyboardButtonRequestUser` and `UserShared` types
- Add `RequestUser` variant to `ButtonRequest`
- Add `UserShared` variant to `MessageKind`
- Add `shared_user` method to `Message`
[pr851]: https://github.com/teloxide/teloxide/pull/851
[pr887]: https://github.com/teloxide/teloxide/pull/887
@ -45,7 +57,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Remove `latitude` and `longitude` parameters from `stop_message_live_location` and `stop_message_live_location_inline` ([#854][pr854])
- Fix the type of `photo_size`,`photo_width` and `photo_height` in the `send_invoice` method ([#936][pr936])
- Fix roundtrip de/serialization of `InlineQueryResult` ([#990][pr990])
- Deserialization of `ApiError::CantParseEntities` ([#839][pr839])
- Deserialization of empty (content-less) messages that can sometimes appear as a part of callback query ([#850][pr850], issue [#873][issue873])
[pr839]: https://github.com/teloxide/teloxide/pull/839
[pr879]: https://github.com/teloxide/teloxide/pull/879
[issue873]: https://github.com/teloxide/teloxide/issues/873
[pr854]: https://github.com/teloxide/teloxide/pull/854
[pr936]: https://github.com/teloxide/teloxide/pull/936
[pr990]: https://github.com/teloxide/teloxide/pull/990
@ -76,7 +93,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `message_thread_id` method parameters now use `ThreadId` instead of `i32` ([#887][pr887])
- `DiceEmoji` variant order ([#887][pr887])
- `Dice::value` now use `u8`, instead of `i32` ([#887][pr887])
- `Invoice::total_amount`, `LabeledPrice::amount`, `PreCheckoutQuery::total_amount`, `SuccessfulPayment::total_amout` now use `u32`, instead of `i32` ([#887][pr887])
- `Invoice::total_amount`, `LabeledPrice::amount`, `PreCheckoutQuery::total_amount`, `SuccessfulPayment::total_amount` now use `u32`, instead of `i32` ([#887][pr887])
- `Forward::message_id` and `Message::forward_from_message_id` now use `MessageId` instead of `i32` ([#887][pr887])
- `Poll::total_voter_count` and `PollOption::voter_count` now use `u32` instead of `i32` ([#887][pr887])
- `PollAnswer::option_ids` now use `u8` instead of `i32` ([#887][pr887])
@ -106,14 +123,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[pr850]: https://github.com/teloxide/teloxide/pull/850
### Fixed
### Removed
- Deserialization of `ApiError::CantParseEntities` ([#839][pr839])
- Deserialization of empty (content-less) messages that can sometimes appear as a part of callback query ([#850][pr850], issue [#873][issue873])
- Remove `can_send_media_messages` from `ChatPermissions` ([#954][pr954])
- Remove `can_send_media_messages` field from `Restricted` ([#954][pr954])
- Previously deprecated items ([#1013][pr1013])
- `AutoSend` bot adaptor
- `ChatMemberKind::is_kicked` (use `is_banned` instead)
- `ChatMemberKind::is_creator` (use `is_owner` instead)
- `ChatMemberKind::{can_change_info, can_pin_messages, can_invite_users, can_manage_topics, can_send_polls, can_add_web_page_previews, can_send_other_messages, can_send_media_messages, can_send_messages}` (match on `ChatMemberKind` yourself)
- `ChatMemberStatus::is_present` (use `ChatMemberKind::is_present` instead)
- `InlineKeyboardButton::{text, kind}`
`teloxide::dispatching::{update_listeners, repls}` (use `reloxide::{update_listeners, repls}` instead)
- `Dispatcher::setup_ctrlc_handler` (use `enable_ctrlc_handler` on the builder instead)
- `BotCommands::ty` and `repls::{commands_repl, commands_repl_with_listener}` (use `CommandsRepl::{repl, repl_with_listener}` instead)
- `Message::chat_id` (use `.chat.id`)
- `Update::user` (use `Update::from`)
- `update_listeners::polling` (use `Polling::builder` instead)
[pr954]: https://github.com/teloxide/teloxide/pull/954
[pr1013]: https://github.com/teloxide/teloxide/pull/1013
[pr839]: https://github.com/teloxide/teloxide/pull/839
[pr879]: https://github.com/teloxide/teloxide/pull/879
[issue873]: https://github.com/teloxide/teloxide/issues/873
## 0.9.1 - 2023-02-15
@ -147,6 +177,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `can_edit_messages`
- `can_pin_messages`
- `can_manage_topics`
- `ApiError::NotFound` is replaced with `ApiError::InvalidToken` which correctly parses all currently known errors caused by invalid bot tokens ([#998][pr998])
[pr998]: https://github.com/teloxide/teloxide/pull/998
### Added
@ -365,7 +398,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- `user.id` now uses `UserId` type, `ChatId` now represents only _chat id_, not channel username, all `chat_id` function parameters now accept `Recipient` [**BC**]
- Improve `Throttling` adoptor ([#130][pr130])
- Improve `Throttling` adaptor ([#130][pr130])
- Freeze when getting `RetryAfter(_)` error
- Retry requests that previously returned `RetryAfter(_)` error
- `RequestError::RetryAfter` now has a `Duration` field instead of `i32`
@ -491,7 +524,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Mark `ApiError` as `non_exhaustive` ([#125][pr125])
- `InputFile` and related structures now do **not** implement `PartialEq`, `Eq` and `Hash` ([#133][pr133])
- How forwarded messages are represented ([#151][pr151])
- `RequestError::InvalidJson` now has a `raw` field with raw json for easier debugability ([#150][pr150])
- `RequestError::InvalidJson` now has a `raw` field with raw json for easier debuggability ([#150][pr150])
- `ChatPermissions` is now bitflags ([#157][pr157])
- Type of `WebhookInfo::ip_address` from `Option<String>` to `Option<std::net::IpAddr>` ([#172][pr172])
- Type of `WebhookInfo::allowed_updates` from `Option<Vec<String>>` to `Option<Vec<AllowedUpdate>>` ([#174][pr174])
@ -569,7 +602,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `impl Clone` for {`CacheMe`, `DefaultParseMode`, `Throttle`} ([#76][pr76])
- `DefaultParseMode::parse_mode` which allows to get currently used default parse mode ([#77][pr77])
- `Thrrotle::{limits,set_limits}` functions ([#77][pr77])
- `Throttle::{limits,set_limits}` functions ([#77][pr77])
- `Throttle::{with_settings,spawn_with_settings}` and `throttle::Settings` ([#96][pr96])
- Getters for fields nested in `Chat` ([#80][pr80])
- API errors: `ApiError::NotEnoughRightsToManagePins`, `ApiError::BotKickedFromSupergroup` ([#84][pr84])
@ -646,7 +679,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Refactor `ReplyMarkup` ([#pr65][pr65]) (**BC**)
- Rename `ReplyMarkup::{InlineKeyboardMarkup => InlineKeyboard, ReplyKeyboardMarkup => Keyboard, ReplyKeyboardRemove => KeyboardRemove}`
- Add `inline_kb`, `keyboad`, `kb_remove` and `force_reply` `ReplyMarkup` consructors
- Add `inline_kb`, `keyboad`, `kb_remove` and `force_reply` `ReplyMarkup` constructors
- Rename `ReplyKeyboardMarkup` => `KeyboardMarkup`
- Rename `ReplyKeyboardRemove` => `KeyboardRemove`
- Remove useless generic param from `ReplyKeyboardMarkup::new` and `InlineKeyboardMarkup::new`
@ -660,7 +693,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- `GetUpdatesFaultTolerant` - fault toletant version of `GetUpdates` ([#58][pr58]) (**BC**)
- `GetUpdatesFaultTolerant` - fault tolerant version of `GetUpdates` ([#58][pr58]) (**BC**)
- Derive `Clone` for `AutoSend`.
[pr58]: https://github.com/teloxide/teloxide-core/pull/58
@ -762,7 +795,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Make `net::{TELEGRAM_API_URL, download_file{,_stream}}` pub
- Refactor `Bot` ([#29][pr29]):
- Move default parse mode to an adaptor (`DefaultParseMode`)
- Remove bot builder (it's not usefull anymore, since parse_mode is moved away)
- Remove bot builder (it's not useful anymore, since parse_mode is moved away)
- Undeprecate bot constructors (`Bot::{new, with_client, from_env_with_client}`)
- Rename `StickerType` => `InputSticker`, `{CreateNewStickerSet,AddStickerToSet}::sticker_type}` => `sticker` ([#23][pr23], [#43][pr43])
- Use `_: IntoIterator<Item = T>` bound instead of `_: Into<Vec<T>>` in telegram methods which accept collections ([#21][pr21])
@ -790,7 +823,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Removed
- `unstable-stream` feature (now `Bot::download_file_stream` is accesable by default)
- `unstable-stream` feature (now `Bot::download_file_stream` is accessible by default)
- old `Request` trait
- `RequestWithFile`, now multipart requests use `Request`
- Remove all `#[non_exhaustive]` annotations ([#4][pr4])

View file

@ -42,11 +42,8 @@ erased = []
# CacheMe bot adaptor
cache_me = []
# AutoSend bot adaptor
auto_send = []
# All features except nightly and tls-related
full = ["throttle", "trace_adaptor", "erased", "cache_me", "auto_send"]
full = ["throttle", "trace_adaptor", "erased", "cache_me"]
[dependencies]
@ -75,7 +72,6 @@ once_cell = "1.5.0"
takecell = "0.1"
take_mut = "0.2"
rc-box = "1.1.1"
never = "0.1.0"
chrono = { version = "0.4.30", default-features = false }
either = "1.6.1"
bitflags = { version = "1.2" }

View file

@ -12,7 +12,7 @@
<img src="https://img.shields.io/badge/license-MIT-blue.svg">
</a>
<a href="https://core.telegram.org/bots/api">
<img src="https://img.shields.io/badge/API%20coverage-Up%20to%206.4%20(inclusively)-green.svg">
<img src="https://img.shields.io/badge/API%20coverage-Up%20to%206.5%20(inclusively)-green.svg">
</a>
<a href="https://crates.io/crates/teloxide_core">
<img src="https://img.shields.io/crates/v/teloxide_core.svg">

View file

@ -1988,6 +1988,11 @@ Schema(
ty: RawTy("ChatPermissions"),
descr: Doc(md: "A JSON-serialized object for new user permissions")
),
Param(
name: "use_independent_chat_permissions",
ty: Option(bool),
descr: Doc(md: "Pass _True_ if chat permissions are set independently. Otherwise, the _can\\_send\\_other\\_messages_ and _can\\_add\\_web\\_page\\_previews_ permissions will imply the _can\\_send\\_messages_, _can\\_send\\_audios_, _can\\_send\\_documents_, _can\\_send\\_photos_, _can\\_send\\_videos_, _can\\_send\\_video\\_notes_, and _can\\_send\\_voice\\_notes_ permissions; the _can\\_send\\_polls_ permission will imply the _can\\_send\\_messages_ permission.")
),
Param(
name: "until_date",
ty: Option(u64),
@ -2153,6 +2158,11 @@ Schema(
ty: RawTy("ChatPermissions"),
descr: Doc(md: "New default chat permissions")
),
Param(
name: "use_independent_chat_permissions",
ty: Option(bool),
descr: Doc(md: "Pass _True_ if chat permissions are set independently. Otherwise, the _can\\_send\\_other\\_messages_ and _can\\_add\\_web\\_page\\_previews_ permissions will imply the _can\\_send\\_messages_, _can\\_send\\_audios_, _can\\_send\\_documents_, _can\\_send\\_photos_, _can\\_send\\_videos_, _can\\_send\\_video\\_notes_, and _can\\_send\\_voice\\_notes_ permissions; the _can\\_send\\_polls_ permission will imply the _can\\_send\\_messages_ permission.")
),
],
),
Method(

View file

@ -5,18 +5,6 @@
//!
//! [`Requester`]: crate::requests::Requester
/// [`AutoSend`] bot adaptor which used to allow sending a request without
/// calling [`send`].
///
/// [`AutoSend`]: auto_send::AutoSend
/// [`send`]: crate::requests::Request::send
#[cfg(feature = "auto_send")]
#[deprecated(
since = "0.8.0",
note = "`AutoSend` is no longer required to `.await` requests and is now noop"
)]
pub mod auto_send;
/// [`CacheMe`] bot adaptor which caches [`GetMe`] requests.
///
/// [`CacheMe`]: cache_me::CacheMe
@ -47,9 +35,6 @@ pub mod throttle;
mod parse_mode;
#[cfg(feature = "auto_send")]
#[allow(deprecated)]
pub use auto_send::AutoSend;
#[cfg(feature = "cache_me")]
pub use cache_me::CacheMe;
#[cfg(feature = "erased")]

View file

@ -1,233 +0,0 @@
use std::future::IntoFuture;
use url::Url;
use crate::{
requests::{HasPayload, Output, Request, Requester},
types::*,
};
/// Previously was used to send requests automatically.
///
/// Before addition of [`IntoFuture`] you could only `.await` [`Future`]s.
/// This adaptor turned requests into futures, allowing to `.await` them,
/// without calling `.send()`.
///
/// Now, however, all requests are required to implement `IntoFuture`, allowing
/// you to `.await` them directly. This adaptor is noop, and shouldn't be used.
///
/// [`Future`]: std::future::Future
#[derive(Clone, Debug)]
pub struct AutoSend<B> {
bot: B,
}
impl<B> AutoSend<B> {
/// Creates new `AutoSend`.
///
/// Note: it's recommended to use [`RequesterExt::auto_send`] instead.
///
/// [`RequesterExt::auto_send`]: crate::requests::RequesterExt::auto_send
pub fn new(inner: B) -> AutoSend<B> {
Self { bot: inner }
}
/// Allows to access the inner bot.
pub fn inner(&self) -> &B {
&self.bot
}
/// Unwraps the inner bot.
pub fn into_inner(self) -> B {
self.bot
}
}
macro_rules! f {
($m:ident $this:ident ($($arg:ident : $T:ty),*)) => {
AutoRequest::new($this.inner().$m($($arg),*))
};
}
macro_rules! fty {
($T:ident) => {
AutoRequest<B::$T>
};
}
impl<B> Requester for AutoSend<B>
where
B: Requester,
{
type Err = B::Err;
requester_forward! {
get_me,
log_out,
close,
get_updates,
set_webhook,
delete_webhook,
get_webhook_info,
forward_message,
copy_message,
send_message,
send_photo,
send_audio,
send_document,
send_video,
send_animation,
send_voice,
send_video_note,
send_media_group,
send_location,
edit_message_live_location,
edit_message_live_location_inline,
stop_message_live_location,
stop_message_live_location_inline,
send_venue,
send_contact,
send_poll,
send_dice,
send_chat_action,
get_user_profile_photos,
get_file,
kick_chat_member,
ban_chat_member,
unban_chat_member,
restrict_chat_member,
promote_chat_member,
set_chat_administrator_custom_title,
ban_chat_sender_chat,
unban_chat_sender_chat,
set_chat_permissions,
export_chat_invite_link,
create_chat_invite_link,
edit_chat_invite_link,
revoke_chat_invite_link,
set_chat_photo,
delete_chat_photo,
set_chat_title,
set_chat_description,
pin_chat_message,
unpin_chat_message,
unpin_all_chat_messages,
leave_chat,
get_chat,
get_chat_administrators,
get_chat_members_count,
get_chat_member_count,
get_chat_member,
set_chat_sticker_set,
delete_chat_sticker_set,
get_forum_topic_icon_stickers,
create_forum_topic,
edit_forum_topic,
close_forum_topic,
reopen_forum_topic,
delete_forum_topic,
unpin_all_forum_topic_messages,
edit_general_forum_topic,
close_general_forum_topic,
reopen_general_forum_topic,
hide_general_forum_topic,
unhide_general_forum_topic,
answer_callback_query,
set_my_commands,
get_my_commands,
set_chat_menu_button,
get_chat_menu_button,
set_my_default_administrator_rights,
get_my_default_administrator_rights,
delete_my_commands,
answer_inline_query,
answer_web_app_query,
edit_message_text,
edit_message_text_inline,
edit_message_caption,
edit_message_caption_inline,
edit_message_media,
edit_message_media_inline,
edit_message_reply_markup,
edit_message_reply_markup_inline,
stop_poll,
delete_message,
send_sticker,
get_sticker_set,
get_custom_emoji_stickers,
upload_sticker_file,
create_new_sticker_set,
add_sticker_to_set,
set_sticker_position_in_set,
delete_sticker_from_set,
set_sticker_set_thumb,
send_invoice,
create_invoice_link,
answer_shipping_query,
answer_pre_checkout_query,
set_passport_data_errors,
send_game,
set_game_score,
set_game_score_inline,
get_game_high_scores,
approve_chat_join_request,
decline_chat_join_request
=> f, fty
}
}
download_forward! {
B
AutoSend<B>
{ this => this.inner() }
}
#[must_use = "Futures are lazy and do nothing unless polled or awaited"]
pub struct AutoRequest<R>(R);
impl<R> AutoRequest<R>
where
R: Request,
{
pub fn new(inner: R) -> Self {
Self(inner)
}
}
impl<R> Request for AutoRequest<R>
where
R: Request,
{
type Err = R::Err;
type Send = R::Send;
type SendRef = R::SendRef;
fn send(self) -> Self::Send {
self.0.send()
}
fn send_ref(&self) -> Self::SendRef {
self.0.send_ref()
}
}
impl<R: Request> IntoFuture for AutoRequest<R> {
type Output = Result<Output<Self>, <Self as Request>::Err>;
type IntoFuture = <Self as Request>::Send;
fn into_future(self) -> Self::IntoFuture {
self.send()
}
}
impl<R: Request> HasPayload for AutoRequest<R> {
type Payload = R::Payload;
fn payload_mut(&mut self) -> &mut Self::Payload {
self.0.payload_mut()
}
fn payload_ref(&self) -> &Self::Payload {
self.0.payload_ref()
}
}

View file

@ -88,28 +88,13 @@ impl AsResponseParameters for crate::RequestError {
}
}
macro_rules! match_prefix {
("") => {{
|data: &str| Some(data.to_owned())
}};
($prefix:literal) => {{
|data: &str| {
if data.starts_with($prefix) {
Some(data.to_owned())
} else {
None
}
}
}};
}
macro_rules! impl_api_error {
(
$( #[$meta:meta] )*
$vis:vis enum $ident:ident {
$(
$( #[$var_meta:meta] )*
$var_name:ident $( ($var_inner:ty) )? = $var_string:literal $(with $var_parser: block)?
$var_name:ident $( ($var_inner:ty) )? = $var_string:literal $(with $var_parser:expr)?
),*
}
) => {
@ -138,7 +123,7 @@ macro_rules! impl_api_error {
where
E: ::serde::de::Error,
{
$(impl_api_error!(@de v, $var_name, $var_string $(, $var_parser)*);)*
$(impl_api_error!(@de v, $var_name $( ($var_inner) )?, $var_string $(, $var_parser)*);)*
Err(E::unknown_variant(v, &[]))
}
}
@ -153,17 +138,24 @@ macro_rules! impl_api_error {
}
};
};
(@de $value: ident, $variant: ident, $val: literal) => {
(@de $value:ident, $variant:ident, $val:literal) => {
if $value == $val {
return Ok(Self::Value::$variant)
}
};
(@de $value: ident, $variant: ident, $val: literal, $block: expr) => {
(@de $value:ident, $variant:ident ($var_inner:ty), $val:literal, $block:expr) => {
#[allow(clippy::redundant_closure_call)]
match $block($value) {
Some(data) => return Ok(Self::Value::$variant(data)),
_ => {}
}
};
(@de $value:ident, $variant:ident, $val:literal, $block:expr) => {
#[allow(clippy::redundant_closure_call)]
if $block($value) {
return Ok(Self::Value::$variant);
}
};
}
impl_api_error! {
@ -174,9 +166,12 @@ impl_api_error! {
/// Occurs when the bot tries to send message to user who blocked the bot.
BotBlocked = "Forbidden: bot was blocked by the user",
/// Occurs when the bot token is incorrect.
// FIXME: rename this to something akin "InvalidToken"
NotFound = "Unauthorized",
/// Occurs when the bot token is invalid.
// N.B. These errors are actually slightly different, "Unauthorized" is when the bot token
// is formatted mostly right, but is incorrect, whereas "Not Found" is when the url is
// not handled by TBA at all. From user POV both of those are "token is invalid", but
// there might be some cases where this is not right...
InvalidToken = "Invalid bot token" with |text: &str| text == "Unauthorized" || text == "Not Found",
/// Occurs when bot tries to modify a message without modification content.
///
@ -616,7 +611,13 @@ impl_api_error! {
/// 1. [`SendMessage`]
///
/// [`SendMessage`]: crate::payloads::SendMessage
CantParseEntities(String) = "{0}" with {match_prefix!("Bad Request: can't parse entities")},
CantParseEntities(String) = "{0}" with |text: &str| {
if text.starts_with("Bad Request: can't parse entities") {
Some(text.to_owned())
} else {
None
}
},
/// Occurs when bot tries to use getUpdates while webhook is active.
///
@ -712,7 +713,7 @@ impl_api_error! {
/// description of the error.
///
/// [open an issue]: https://github.com/teloxide/teloxide/issues/new
Unknown(String) = "Unknown error: {0:?}" with {match_prefix!("")}
Unknown(String) = "Unknown error: {0:?}" with |text: &str| Some(text.to_owned())
}
}
@ -813,7 +814,8 @@ mod tests {
let cases = &[
("{\"data\": \"Forbidden: bot was blocked by the user\"}", ApiError::BotBlocked),
("{\"data\": \"Unauthorized\"}", ApiError::NotFound),
("{\"data\": \"Unauthorized\"}", ApiError::InvalidToken),
("{\"data\": \"Not Found\"}", ApiError::InvalidToken),
(
"{\"data\": \"Bad Request: message is not modified: specified new message content \
and reply markup are exactly the same as a current content and reply markup of \
@ -1028,6 +1030,7 @@ mod tests {
ApiError::Unknown(_) => {
format!("Unknown error: \"{raw}\"")
}
ApiError::InvalidToken => "Invalid bot token".to_owned(),
_ => raw,
};
assert_eq!(parsed.to_string(), expected_error_message);

View file

@ -1,7 +1,7 @@
//! Core part of the [`teloxide`] library.
//!
//! This library provides tools for making requests to the [Telegram Bot API]
//! (Currently, version `6.4` is supported) with ease. The library is fully
//! (Currently, version `6.5` is supported) with ease. The library is fully
//! asynchronous and built using [`tokio`].
//!
//!```toml
@ -47,7 +47,6 @@
//! - `nightly` — enables nightly-only features, currently:
//! - Removes some future boxing using `#![feature(type_alias_impl_trait)]`
//! - Used to built docs (`#![feature(doc_cfg, doc_notable_trait)]`)
//! - `auto_send` — enables [`AutoSend`] bot adaptor (deprecated)
//!
//! [`AutoSend`]: adaptors::AutoSend
//! [`Trace`]: adaptors::Trace

View file

@ -18,6 +18,8 @@ impl_payload! {
pub permissions: ChatPermissions,
}
optional {
/// Pass _True_ if chat permissions are set independently. Otherwise, the _can\_send\_other\_messages_ and _can\_add\_web\_page\_previews_ permissions will imply the _can\_send\_messages_, _can\_send\_audios_, _can\_send\_documents_, _can\_send\_photos_, _can\_send\_videos_, _can\_send\_video\_notes_, and _can\_send\_voice\_notes_ permissions; the _can\_send\_polls_ permission will imply the _can\_send\_messages_ permission.
pub use_independent_chat_permissions: bool,
/// Date when the user will be unbanned, unix time. If user is banned for more than 366 days or less than 30 seconds from the current time they are considered to be banned forever
#[serde(with = "crate::types::serde_opt_date_from_unix_timestamp")]
pub until_date: DateTime<Utc> [into],

View file

@ -14,5 +14,9 @@ impl_payload! {
/// New default chat permissions
pub permissions: ChatPermissions,
}
optional {
/// Pass _True_ if chat permissions are set independently. Otherwise, the _can\_send\_other\_messages_ and _can\_add\_web\_page\_previews_ permissions will imply the _can\_send\_messages_, _can\_send\_audios_, _can\_send\_documents_, _can\_send\_photos_, _can\_send\_videos_, _can\_send\_video\_notes_, and _can\_send\_voice\_notes_ permissions; the _can\_send\_polls_ permission will imply the _can\_send\_messages_ permission.
pub use_independent_chat_permissions: bool,
}
}
}

View file

@ -3,10 +3,6 @@ use crate::{adaptors::DefaultParseMode, requests::Requester, types::ParseMode};
#[cfg(feature = "cache_me")]
use crate::adaptors::CacheMe;
#[cfg(feature = "auto_send")]
#[allow(deprecated)]
use crate::adaptors::AutoSend;
#[cfg(feature = "erased")]
use crate::adaptors::ErasedRequester;
@ -28,21 +24,6 @@ pub trait RequesterExt: Requester {
CacheMe::new(self)
}
/// Send requests automatically, see [`AutoSend`] for more.
#[cfg(feature = "auto_send")]
#[must_use]
#[deprecated(
since = "0.8.0",
note = "`AutoSend` is no longer required to `.await` requests and is now noop"
)]
#[allow(deprecated)]
fn auto_send(self) -> AutoSend<Self>
where
Self: Sized,
{
AutoSend::new(self)
}
/// Erase requester type.
#[cfg(feature = "erased")]
#[must_use]

View file

@ -17,6 +17,7 @@ pub use chat_member::*;
pub use chat_member_updated::*;
pub use chat_permissions::*;
pub use chat_photo::*;
pub use chat_shared::*;
pub use chat_type::*;
pub use chosen_inline_result::*;
pub use contact::*;
@ -67,6 +68,8 @@ pub use input_sticker::*;
pub use invoice::*;
pub use keyboard_button::*;
pub use keyboard_button_poll_type::*;
pub use keyboard_button_request_chat::*;
pub use keyboard_button_request_user::*;
pub use label_price::*;
pub use location::*;
pub use login_url::*;
@ -106,6 +109,7 @@ pub use unit_true::*;
pub use update::*;
pub use user::*;
pub use user_profile_photos::*;
pub use user_shared::*;
pub use venue::*;
pub use video::*;
pub use video_chat_ended::*;
@ -136,6 +140,7 @@ mod chat_member;
mod chat_member_updated;
mod chat_permissions;
mod chat_photo;
mod chat_shared;
mod chat_type;
mod chosen_inline_result;
mod contact;
@ -162,6 +167,8 @@ mod input_sticker;
mod invoice;
mod keyboard_button;
mod keyboard_button_poll_type;
mod keyboard_button_request_chat;
mod keyboard_button_request_user;
mod label_price;
mod location;
mod login_url;
@ -198,6 +205,7 @@ mod unit_true;
mod update;
mod user;
mod user_profile_photos;
mod user_shared;
mod venue;
mod video;
mod video_chat_ended;

View file

@ -1,7 +1,7 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::types::{Chat, ChatInviteLink, User};
use crate::types::{Chat, ChatId, ChatInviteLink, User};
/// Represents a join request sent to a chat.
#[serde_with_macros::skip_serializing_none]
@ -11,6 +11,11 @@ pub struct ChatJoinRequest {
pub chat: Chat,
/// User that sent the join request
pub from: User,
/// Identifier of a private chat with the user who sent the join request.
/// The bot can use this identifier for 5 minutes to send messages until
/// the join request is processed, assuming no other administrator
/// contacted the user.
pub user_chat_id: ChatId,
/// Date the request was sent in Unix time
#[serde(with = "crate::types::serde_date_from_unix_timestamp")]
pub date: DateTime<Utc>,

View file

@ -120,9 +120,23 @@ pub struct Restricted {
/// venues.
pub can_send_messages: bool,
/// `true` if the user is allowed to send audios, documents, photos, videos,
/// video notes and voice notes.
pub can_send_media_messages: bool,
/// `true` if the user can send audios.
pub can_send_audios: bool,
/// `true` if the user can send documents.
pub can_send_documents: bool,
/// `true` if the user can send photos.
pub can_send_photos: bool,
/// `true` if the user can send videos.
pub can_send_videos: bool,
/// `true` if the user can send video notes.
pub can_send_video_notes: bool,
/// `true` if the user can send voice notes.
pub can_send_voice_notes: bool,
/// `true` if the user is allowed to send animations, games, stickers and
/// use inline bots.
@ -254,24 +268,6 @@ impl ChatMemberKind {
pub fn is_banned(&self) -> bool {
matches!(self, Self::Banned { .. })
}
/// Returns `true` if the user is [kicked] from the given chat.
///
/// [kicked]: ChatMemberKind::Banned
#[deprecated = "use `is_banned` instead"]
#[must_use]
pub fn is_kicked(&self) -> bool {
self.is_banned()
}
/// Returns `true` if the user is the [creator] (owner) of the given chat.
///
/// [creator]: ChatMemberKind::Owner
#[deprecated = "use `is_owner` instead"]
#[must_use]
pub fn is_creator(&self) -> bool {
self.is_owner()
}
}
/// Compound methods for checking a user status.
@ -377,29 +373,6 @@ impl ChatMemberKind {
}
}
/// Returns `true` if the user can change the chat title, photo and other
/// settings.
///
/// I.e. returns `true` if the user
/// - is the owner of the chat
/// - is an administrator in the given chat and has the
/// [`Administrator::can_change_info`] privilege.
/// - is restricted, but does have [`Restricted::can_change_info`] privilege
/// Returns `false` otherwise.
#[deprecated(
since = "0.9.0",
note = "Match manually and use `can_change_info` field directly. Details: https://github.com/teloxide/teloxide/issues/781"
)]
#[must_use]
pub fn can_change_info(&self) -> bool {
match self {
Self::Owner(_) => true,
Self::Administrator(Administrator { can_change_info, .. })
| Self::Restricted(Restricted { can_change_info, .. }) => *can_change_info,
Self::Member | Self::Left | Self::Banned(_) => false,
}
}
/// Returns `true` if the user can post in the channel, channels only.
///
/// I.e. returns `true` if the user
@ -475,35 +448,6 @@ impl ChatMemberKind {
}
}
#[deprecated(since = "0.6.0", note = "renamed to `can_manage_video_chats`")]
#[must_use]
pub fn can_manage_voice_chats(&self) -> bool {
self.can_manage_video_chats()
}
/// Returns `true` if the user can can invite new users to the chat.
///
/// I.e. returns `true` if the user
/// - is the owner of the chat
/// - is an administrator in the given chat and has the
/// [`Administrator::can_invite_users`] privilege.
/// - is restricted, but does have [`Restricted::can_invite_users`]
/// privilege
/// Returns `false` otherwise.
#[deprecated(
since = "0.9.0",
note = "Match manually and use `can_invite_users` field directly. Details: https://github.com/teloxide/teloxide/issues/781"
)]
#[must_use]
pub fn can_invite_users(&self) -> bool {
match &self {
Self::Owner(_) => true,
Self::Administrator(Administrator { can_invite_users, .. })
| Self::Restricted(Restricted { can_invite_users, .. }) => *can_invite_users,
Self::Member | Self::Left | Self::Banned(_) => false,
}
}
/// Returns `true` if the user can restrict, ban or unban chat members.
///
/// I.e. returns `true` if the user
@ -524,54 +468,6 @@ impl ChatMemberKind {
}
}
/// Returns `true` if the user can pin messages, supergroups only.
///
/// I.e. returns `true` if the user
/// - is the owner of the chat (even if the chat is not a supergroup)
/// - is an administrator in the given chat and has the
/// [`Administrator::can_pin_messages`] privilege.
/// - is restricted, but does have [`Restricted::can_pin_messages`]
/// privilege
/// Returns `false` otherwise.
#[deprecated(
since = "0.9.0",
note = "Match manually and use `can_pin_messages` field directly. Details: https://github.com/teloxide/teloxide/issues/781"
)]
#[must_use]
pub fn can_pin_messages(&self) -> bool {
match self {
Self::Owner(_) => true,
Self::Administrator(Administrator { can_pin_messages, .. })
| Self::Restricted(Restricted { can_pin_messages, .. }) => *can_pin_messages,
Self::Member | Self::Left | Self::Banned(_) => false,
}
}
/// Returns `true` if the user is allowed to manage topics.
///
/// I.e. returns `true` if the user
/// - is the owner of the chat (even if the chat is not a supergroup)
/// - is an administrator in the given chat and has the
/// [`Administrator::can_manage_topics`] privilege.
/// - is restricted, but does have [`Restricted::can_manage_topics`]
/// privilege
/// Returns `false` otherwise.
#[deprecated(
since = "0.9.0",
note = "Match manually and use `can_manage_topics` field directly. Details: https://github.com/teloxide/teloxide/issues/781"
)]
#[must_use]
pub fn can_manage_topics(&self) -> bool {
match self {
ChatMemberKind::Owner(_) => true,
ChatMemberKind::Administrator(Administrator { can_manage_topics, .. })
| ChatMemberKind::Restricted(Restricted { can_manage_topics, .. }) => {
*can_manage_topics
}
ChatMemberKind::Member | ChatMemberKind::Left | ChatMemberKind::Banned(_) => false,
}
}
/// Returns `true` if the user can add new administrators with a subset of
/// his own privileges or demote administrators that he has promoted,
/// directly or indirectly (promoted by administrators that were appointed
@ -594,124 +490,6 @@ impl ChatMemberKind {
}
}
/// Methods for checking member rights.
impl ChatMemberKind {
/// Returns `true` if the user can send text messages, contacts, locations
/// and venues.
///
/// I.e. returns **`false`** if the user
/// - has left or has been banned in the chat
/// - is restricted and doesn't have the [`can_send_messages`] right
/// Returns `true` otherwise.
///
/// [`can_send_messages`]: Restricted::can_send_messages
#[deprecated(
since = "0.9.0",
note = "Match manually and use `can_send_messages` field directly. Details: https://github.com/teloxide/teloxide/issues/781"
)]
#[must_use]
pub fn can_send_messages(&self) -> bool {
match &self {
Self::Restricted(Restricted { can_send_messages, .. }) => *can_send_messages,
Self::Owner(_) | Self::Administrator(_) | Self::Member => true,
Self::Left | Self::Banned(_) => false,
}
}
/// Returns `true` if the user is allowed to send audios, documents, photos,
/// videos, video notes and voice notes.
///
/// I.e. returns **`false`** if the user
/// - has left or has been banned in the chat
/// - is restricted and doesn't have the [`can_send_media_messages`] right
/// Returns `true` otherwise.
///
/// [`can_send_media_messages`]: Restricted::can_send_media_messages
#[deprecated(
since = "0.9.0",
note = "Match manually and use `can_send_media_messages` field directly. Details: https://github.com/teloxide/teloxide/issues/781"
)]
#[must_use]
pub fn can_send_media_messages(&self) -> bool {
match &self {
Self::Restricted(Restricted { can_send_media_messages, .. }) => {
*can_send_media_messages
}
Self::Owner(_) | Self::Administrator(_) | Self::Member => true,
Self::Left | Self::Banned(_) => false,
}
}
/// Returns `true` if the user is allowed to send animations, games,
/// stickers and use inline bots.
///
/// I.e. returns **`false`** if the user
/// - has left or has been banned from the chat
/// - is restricted and doesn't have the [`can_send_media_messages`] right
/// Returns `true` otherwise.
///
/// [`can_send_media_messages`]: Restricted::can_send_media_messages
#[deprecated(
since = "0.9.0",
note = "Match manually and use `can_send_other_messages` field directly. Details: https://github.com/teloxide/teloxide/issues/781"
)]
#[must_use]
pub fn can_send_other_messages(&self) -> bool {
match &self {
Self::Restricted(Restricted { can_send_other_messages, .. }) => {
*can_send_other_messages
}
Self::Owner(_) | Self::Administrator(_) | Self::Member => true,
Self::Left | Self::Banned(_) => false,
}
}
/// Returns `true` if the user is allowed to add web page previews to their
/// messages.
///
/// I.e. returns **`false`** if the user
/// - has left or has been banned from the chat
/// - is restricted and doesn't have the [`can_send_media_messages`] right
/// Returns `true` otherwise.
///
/// [`can_send_media_messages`]: Restricted::can_send_media_messages
#[deprecated(
since = "0.9.0",
note = "Match manually and use `can_add_web_page_previews` field directly. Details: https://github.com/teloxide/teloxide/issues/781"
)]
#[must_use]
pub fn can_add_web_page_previews(&self) -> bool {
match &self {
Self::Restricted(Restricted { can_add_web_page_previews, .. }) => {
*can_add_web_page_previews
}
Self::Owner(_) | Self::Administrator(_) | Self::Member => true,
Self::Left | Self::Banned(_) => false,
}
}
/// Returns `true` if the user is allowed to send polls.
///
/// I.e. returns **`false`** if the user
/// - has left or has been banned from the chat
/// - is restricted and doesn't have the [`can_send_polls`] right
/// Returns `true` otherwise.
///
/// [`can_send_polls`]: Restricted::can_send_polls
#[deprecated(
since = "0.9.0",
note = "Match manually and use `can_send_polls` field directly. Details: https://github.com/teloxide/teloxide/issues/781"
)]
#[must_use]
pub fn can_send_polls(&self) -> bool {
match &self {
Self::Restricted(Restricted { can_send_polls, .. }) => *can_send_polls,
Self::Owner(_) | Self::Administrator(_) | Self::Member => true,
Self::Left | Self::Banned(_) => false,
}
}
}
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ChatMemberStatus {
@ -803,20 +581,6 @@ impl ChatMemberStatus {
pub fn is_privileged(&self) -> bool {
self.is_administrator() || self.is_owner()
}
/// Returns `true` if the user is currently present in the chat. i.e. if the
/// user **hasn't** [left] or been [banned].
///
/// [left]: ChatMemberKind::Left
/// [banned]: ChatMemberKind::Banned
#[must_use]
#[deprecated(
since = "0.9.0",
note = "Use `ChatMemberKind::is_present` method instead. Details: https://github.com/teloxide/teloxide/issues/781"
)]
pub fn is_present(&self) -> bool {
!(self.is_left() || self.is_banned())
}
}
#[cfg(test)]
@ -826,7 +590,7 @@ mod tests {
use super::*;
#[test]
fn deserialize() {
fn deserialize_administrator() {
let json = r#"{
"user":{
"id":1029940401,
@ -879,4 +643,70 @@ mod tests {
let actual = serde_json::from_str::<ChatMember>(json).unwrap();
assert_eq!(actual, expected)
}
#[test]
fn deserialize_restricted() {
let json = r#"{
"user":{
"id":1029940401,
"is_bot":false,
"first_name":"First",
"last_name":"Last",
"username":"fl",
"language_code":"en"
},
"status":"restricted",
"is_member": true,
"can_send_messages": true,
"can_send_media_messages": true,
"can_send_audios": false,
"can_send_documents": false,
"can_send_photos": true,
"can_send_videos": true,
"can_send_video_notes": false,
"can_send_voice_notes": true,
"can_manage_topics": false,
"can_send_polls": true,
"can_send_other_messages": true,
"can_add_web_page_previews": true,
"can_change_info": true,
"can_invite_users": true,
"can_pin_messages": true,
"until_date": 1620000000
}"#;
let expected = ChatMember {
user: User {
id: UserId(1029940401),
is_bot: false,
first_name: "First".to_string(),
last_name: Some("Last".to_string()),
username: Some("fl".to_string()),
language_code: Some("en".to_string()),
is_premium: false,
added_to_attachment_menu: false,
},
kind: ChatMemberKind::Restricted(Restricted {
is_member: true,
can_send_messages: true,
can_send_audios: false,
can_send_documents: false,
can_send_photos: true,
can_send_videos: true,
can_send_video_notes: false,
can_send_voice_notes: true,
can_manage_topics: false,
can_send_polls: true,
can_send_other_messages: true,
can_add_web_page_previews: true,
can_change_info: true,
can_invite_users: true,
can_pin_messages: true,
until_date: UntilDate::Date(
chrono::NaiveDateTime::from_timestamp_opt(1620000000, 0).unwrap().and_utc(),
),
}),
};
let actual = serde_json::from_str::<ChatMember>(json).unwrap();
assert_eq!(actual, expected)
}
}

View file

@ -26,23 +26,23 @@ bitflags::bitflags! {
/// assert!(!permissions_v0.contains(ChatPermissions::SEND_MESSAGES));
///
/// // Union, add permissions
/// let permissions_v1 = permissions_v0 | ChatPermissions::SEND_MEDIA_MESSAGES;
/// let permissions_v1 = permissions_v0 | ChatPermissions::SEND_VIDEOS;
/// assert!(permissions_v1.contains(ChatPermissions::INVITE_USERS));
/// assert!(permissions_v1.contains(ChatPermissions::SEND_MEDIA_MESSAGES));
/// assert!(permissions_v1.contains(ChatPermissions::SEND_VIDEOS));
///
/// // Implied by `SEND_MEDIA_MESSAGES`
/// // Implied by `SEND_VIDEOS`
/// assert!(permissions_v1.contains(ChatPermissions::SEND_MESSAGES));
///
/// // Difference, remove permissions
/// let permissions_v2 = permissions_v1 - ChatPermissions::SEND_MEDIA_MESSAGES;
/// assert!(!permissions_v2.contains(ChatPermissions::SEND_MEDIA_MESSAGES));
/// let permissions_v2 = permissions_v1 - ChatPermissions::SEND_VIDEOS;
/// assert!(!permissions_v2.contains(ChatPermissions::SEND_VIDEOS));
///
/// // Removing `SEND_MEDIA_MESSAGES` also removes `SEND_MESSAGES` and vice versa
/// // because `SEND_MESSAGES` is implied by `SEND_MEDIA_MESSAGES`
/// // Removing `SEND_VIDEOS` also removes `SEND_MESSAGES` and vice versa
/// // because `SEND_MESSAGES` is implied by `SEND_VIDEOS`
/// assert!(!permissions_v2.contains(ChatPermissions::SEND_MESSAGES));
///
/// let permissions_v3 = permissions_v1 - ChatPermissions::SEND_MESSAGES;
/// assert!(!permissions_v3.contains(ChatPermissions::SEND_MEDIA_MESSAGES));
/// assert!(!permissions_v3.contains(ChatPermissions::SEND_VIDEOS));
/// ```
#[derive(Serialize, Deserialize)]
#[serde(from = "ChatPermissionsRaw", into = "ChatPermissionsRaw")]
@ -51,22 +51,17 @@ bitflags::bitflags! {
/// locations and venues.
const SEND_MESSAGES = 1;
/// Set if the user is allowed to send audios, documents,
/// photos, videos, video notes and voice notes, implies
/// `SEND_MESSAGES`.
const SEND_MEDIA_MESSAGES = (1 << 1) | Self::SEND_MESSAGES.bits;
/// Set if the user is allowed to send polls, implies
/// `SEND_MESSAGES`.
const SEND_POLLS = (1 << 2) | Self::SEND_MESSAGES.bits;
/// Set if the user is allowed to send animations, games, stickers and
/// use inline bots, implies `SEND_MEDIA_MESSAGES`.
const SEND_OTHER_MESSAGES = (1 << 3) | Self::SEND_MEDIA_MESSAGES.bits;
const SEND_OTHER_MESSAGES = (1 << 3);
/// Set if the user is allowed to add web page previews to
/// their messages, implies `SEND_MEDIA_MESSAGES`.
const ADD_WEB_PAGE_PREVIEWS = (1 << 4) | Self::SEND_MEDIA_MESSAGES.bits;
const ADD_WEB_PAGE_PREVIEWS = (1 << 4);
/// Set if the user is allowed to change the chat title, photo and
/// other settings. Ignored in public supergroups.
@ -81,6 +76,44 @@ bitflags::bitflags! {
/// Set if the user is allowed to create, rename, close, and reopen forum topics.
const MANAGE_TOPICS = (1 << 8);
/// Set if the user is allowed to send audios. implies
/// `SEND_MESSAGES`.
const SEND_AUDIOS = (1 << 9) | Self::SEND_MESSAGES.bits;
/// Set if the user is allowed to send documents. implies
/// `SEND_MESSAGES`.
const SEND_DOCUMENTS = (1 << 10) | Self::SEND_MESSAGES.bits;
/// Set if the user is allowed to send photos. implies
/// `SEND_MESSAGES`.
const SEND_PHOTOS = (1 << 11) | Self::SEND_MESSAGES.bits;
/// Set if the user is allowed to send videos. implies
/// `SEND_MESSAGES`.
const SEND_VIDEOS = (1 << 12) | Self::SEND_MESSAGES.bits;
/// Set if the user is allowed to send video notes. implies
/// `SEND_MESSAGES`.
const SEND_VIDEO_NOTES = (1 << 13) | Self::SEND_MESSAGES.bits;
/// Set if the user is allowed to send voice notes. implies
/// `SEND_MESSAGES`.
const SEND_VOICE_NOTES = (1 << 14) | Self::SEND_MESSAGES.bits;
/// Set if the user is allowed to send audios, documents,
/// photos, videos, video notes and voice notes, implies
/// `SEND_MESSAGES`, `SEND_AUDIOS`, `SEND_DOCUMENTS`,
/// `SEND_PHOTOS`, `SEND_VIDEOS`, `SEND_VIDEO_NOTES` and `SEND_VOICE_NOTES`.
/// Note: this is not a separate permission on it's own, this is just a alias for all the permissions mentioned.
const SEND_MEDIA_MESSAGES = Self::SEND_MESSAGES.bits
| Self::SEND_AUDIOS.bits
| Self::SEND_DOCUMENTS.bits
| Self::SEND_PHOTOS.bits
| Self::SEND_VIDEOS.bits
| Self::SEND_VIDEO_NOTES.bits
| Self::SEND_VOICE_NOTES.bits;
}
}
@ -92,6 +125,48 @@ impl ChatPermissions {
self.contains(ChatPermissions::SEND_MESSAGES)
}
/// Checks for [`SEND_AUDIOS`] permission.
///
/// [`SEND_AUDIOS`]: ChatPermissions::SEND_AUDIOS
pub fn can_send_audios(&self) -> bool {
self.contains(ChatPermissions::SEND_AUDIOS)
}
/// Checks for [`SEND_DOCUMENTS`] permission.
///
/// [`SEND_DOCUMENTS`]: ChatPermissions::SEND_DOCUMENTS
pub fn can_send_documents(&self) -> bool {
self.contains(ChatPermissions::SEND_DOCUMENTS)
}
/// Checks for [`SEND_PHOTOS`] permission.
///
/// [`SEND_PHOTOS`]: ChatPermissions::SEND_PHOTOS
pub fn can_send_photos(&self) -> bool {
self.contains(ChatPermissions::SEND_PHOTOS)
}
/// Checks for [`SEND_VIDEOS`] permission.
///
/// [`SEND_VIDEOS`]: ChatPermissions::SEND_VIDEOS
pub fn can_send_videos(&self) -> bool {
self.contains(ChatPermissions::SEND_VIDEOS)
}
/// Checks for [`SEND_VIDEO_NOTES`] permission.
///
/// [`SEND_VIDEO_NOTES`]: ChatPermissions::SEND_VIDEO_NOTES
pub fn can_send_video_notes(&self) -> bool {
self.contains(ChatPermissions::SEND_VIDEO_NOTES)
}
/// Checks for [`SEND_VOICE_NOTES`] permission.
///
/// [`SEND_VOICE_NOTES`]: ChatPermissions::SEND_VOICE_NOTES
pub fn can_send_voice_notes(&self) -> bool {
self.contains(ChatPermissions::SEND_VOICE_NOTES)
}
/// Checks for [`SEND_MEDIA_MESSAGES`] permission.
///
/// [`SEND_MEDIA_MESSAGES`]: ChatPermissions::SEND_MEDIA_MESSAGES
@ -156,7 +231,22 @@ struct ChatPermissionsRaw {
can_send_messages: bool,
#[serde(default, skip_serializing_if = "Not::not")]
can_send_media_messages: bool,
can_send_audios: bool,
#[serde(default, skip_serializing_if = "Not::not")]
can_send_documents: bool,
#[serde(default, skip_serializing_if = "Not::not")]
can_send_photos: bool,
#[serde(default, skip_serializing_if = "Not::not")]
can_send_videos: bool,
#[serde(default, skip_serializing_if = "Not::not")]
can_send_video_notes: bool,
#[serde(default, skip_serializing_if = "Not::not")]
can_send_voice_notes: bool,
#[serde(default, skip_serializing_if = "Not::not")]
can_send_polls: bool,
@ -188,7 +278,12 @@ impl From<ChatPermissions> for ChatPermissionsRaw {
fn from(this: ChatPermissions) -> Self {
Self {
can_send_messages: this.can_send_messages(),
can_send_media_messages: this.can_send_media_messages(),
can_send_audios: this.contains(ChatPermissions::SEND_AUDIOS),
can_send_documents: this.contains(ChatPermissions::SEND_DOCUMENTS),
can_send_photos: this.contains(ChatPermissions::SEND_PHOTOS),
can_send_videos: this.contains(ChatPermissions::SEND_VIDEOS),
can_send_video_notes: this.contains(ChatPermissions::SEND_VIDEO_NOTES),
can_send_voice_notes: this.contains(ChatPermissions::SEND_VOICE_NOTES),
can_send_polls: this.can_send_polls(),
can_send_other_messages: this.can_send_other_messages(),
can_add_web_page_previews: this.can_add_web_page_previews(),
@ -204,7 +299,12 @@ impl From<ChatPermissionsRaw> for ChatPermissions {
fn from(
ChatPermissionsRaw {
can_send_messages,
can_send_media_messages,
can_send_audios,
can_send_documents,
can_send_photos,
can_send_videos,
can_send_video_notes,
can_send_voice_notes,
can_send_polls,
can_send_other_messages,
can_add_web_page_previews,
@ -219,8 +319,23 @@ impl From<ChatPermissionsRaw> for ChatPermissions {
if can_send_messages {
this |= Self::SEND_MESSAGES;
}
if can_send_media_messages {
this |= Self::SEND_MEDIA_MESSAGES
if can_send_audios {
this |= Self::SEND_AUDIOS;
}
if can_send_documents {
this |= Self::SEND_DOCUMENTS;
}
if can_send_photos {
this |= Self::SEND_PHOTOS;
}
if can_send_videos {
this |= Self::SEND_VIDEOS;
}
if can_send_video_notes {
this |= Self::SEND_VIDEO_NOTES;
}
if can_send_voice_notes {
this |= Self::SEND_VOICE_NOTES;
}
if can_send_polls {
this |= Self::SEND_POLLS;
@ -255,17 +370,16 @@ mod tests {
#[test]
fn serialization() {
let permissions = ChatPermissions::SEND_MEDIA_MESSAGES | ChatPermissions::PIN_MESSAGES;
let expected = r#"{"can_send_messages":true,"can_send_media_messages":true,"can_pin_messages":true,"can_manage_topics":false}"#;
let permissions = ChatPermissions::SEND_AUDIOS | ChatPermissions::PIN_MESSAGES;
let expected = r#"{"can_send_messages":true,"can_send_audios":true,"can_pin_messages":true,"can_manage_topics":false}"#;
let actual = serde_json::to_string(&permissions).unwrap();
assert_eq!(expected, actual);
}
#[test]
fn deserialization() {
let json =
r#"{"can_send_messages":true,"can_send_media_messages":true,"can_pin_messages":true}"#;
let expected = ChatPermissions::SEND_MEDIA_MESSAGES | ChatPermissions::PIN_MESSAGES;
let json = r#"{"can_send_messages":true,"can_send_photos":true,"can_pin_messages":true}"#;
let expected = ChatPermissions::SEND_PHOTOS | ChatPermissions::PIN_MESSAGES;
let actual = serde_json::from_str(json).unwrap();
assert_eq!(expected, actual);
}

View file

@ -0,0 +1,15 @@
use serde::{Deserialize, Serialize};
use crate::types::ChatId;
/// Information about the chat whose identifier was shared with the bot using a
/// [`KeyboardButtonRequestChat`] button.
///
/// [`KeyboardButtonRequestChat`]: crate::types::KeyboardButtonRequestChat
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct ChatShared {
/// Identifier of the request.
pub request_id: i32,
/// Identifier of the shared chat.
pub chat_id: ChatId,
}

View file

@ -192,27 +192,3 @@ impl InlineKeyboardButton {
Self::new(text, InlineKeyboardButtonKind::Pay(True))
}
}
impl InlineKeyboardButton {
#[deprecated(
since = "0.7.0",
note = "set correct text in the constructor or access the field directly"
)]
pub fn text<S>(mut self, val: S) -> Self
where
S: Into<String>,
{
self.text = val.into();
self
}
#[deprecated(
since = "0.7.0",
note = "set correct kind in the constructor or access the field directly"
)]
#[must_use]
pub fn kind(mut self, val: InlineKeyboardButtonKind) -> Self {
self.kind = val;
self
}
}

View file

@ -1,6 +1,8 @@
use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};
use crate::types::{KeyboardButtonPollType, True, WebAppInfo};
use crate::types::{
KeyboardButtonPollType, KeyboardButtonRequestChat, KeyboardButtonRequestUser, True, WebAppInfo,
};
/// This object represents one button of the reply keyboard.
///
@ -59,6 +61,18 @@ pub enum ButtonRequest {
/// 9 April, 2016. Older clients will display unsupported message.
Contact,
/// If this variant is used, pressing the button will open a list of
/// suitable chats. Tapping on a chat will send its identifier to the bot in
/// a [`chat_shared`] service message.
///
/// [`chat_shared`]: crate::types::MessageKind::ChatShared
RequestChat(KeyboardButtonRequestChat),
/// If this variant is used, pressing the button will open a list of
/// suitable users. Tapping on any user will send their identifier to the
/// bot in a “user_shared” service message.
RequestUser(KeyboardButtonRequestUser),
/// If this variant is used, the user will be asked to create a poll and
/// send it to the bot when the button is pressed.
///
@ -89,6 +103,18 @@ struct RawRequest {
#[serde(rename = "request_location")]
location: Option<True>,
/// If specified, pressing the button will open a list of suitable chats.
/// Tapping on a chat will send its identifier to the bot in a “chat_shared”
/// service message. Available in private chats only.
#[serde(rename = "request_chat")]
chat: Option<KeyboardButtonRequestChat>,
/// If specified, pressing the button will open a list of suitable users.
/// Tapping on any user will send their identifier to the bot in a
/// “user_shared” service message. Available in private chats only.
#[serde(rename = "request_user")]
user: Option<KeyboardButtonRequestUser>,
/// If specified, the user will be asked to create a poll and
/// send it to the bot when the button is pressed. Available in private
/// chats only.
@ -108,25 +134,36 @@ impl<'de> Deserialize<'de> for ButtonRequest {
{
let raw = RawRequest::deserialize(deserializer)?;
match raw {
RawRequest { contact, location, poll, web_app }
RawRequest { contact, location, chat, user, poll, web_app }
if 1 < (contact.is_some() as u8
+ location.is_some() as u8
+ chat.is_some() as u8
+ user.is_some() as u8
+ poll.is_some() as u8
+ web_app.is_some() as u8) =>
{
Err(D::Error::custom(
"`request_contact`, `request_location`, `request_poll` and `web_app` fields \
are mutually exclusive",
"`request_contact`, `request_location`, `request_chat`, `request_user`, \
`request_poll` and `web_app` fields are mutually exclusive",
))
}
RawRequest { contact: Some(_), .. } => Ok(Self::Contact),
RawRequest { location: Some(_), .. } => Ok(Self::Location),
RawRequest { contact: Some(True), .. } => Ok(Self::Contact),
RawRequest { location: Some(True), .. } => Ok(Self::Location),
RawRequest { chat: Some(request_chat), .. } => Ok(Self::RequestChat(request_chat)),
RawRequest { user: Some(request_user), .. } => Ok(Self::RequestUser(request_user)),
RawRequest { poll: Some(poll_type), .. } => Ok(Self::Poll(poll_type)),
RawRequest { web_app: Some(web_app), .. } => Ok(Self::WebApp(web_app)),
_ => Err(D::Error::custom(
"Either one of `request_contact`, `request_location`, `request_poll` and \
`web_app` fields is required",
RawRequest {
contact: None,
location: None,
chat: None,
user: None,
poll: None,
web_app: None,
} => Err(D::Error::custom(
"Either one of `request_contact`, `request_chat`, `request_user`, \
`request_location`, `request_poll` and `web_app` fields is required",
)),
}
}
@ -137,11 +174,20 @@ impl Serialize for ButtonRequest {
where
S: Serializer,
{
let mut raw = RawRequest { contact: None, location: None, poll: None, web_app: None };
let mut raw = RawRequest {
contact: None,
location: None,
chat: None,
user: None,
poll: None,
web_app: None,
};
match self {
Self::Contact => raw.contact = Some(True),
Self::Location => raw.location = Some(True),
Self::RequestChat(request_chat) => raw.chat = Some(request_chat.clone()),
Self::RequestUser(request_user) => raw.user = Some(request_user.clone()),
Self::Poll(poll_type) => raw.poll = Some(poll_type.clone()),
Self::WebApp(web_app) => raw.web_app = Some(web_app.clone()),
};
@ -171,6 +217,17 @@ mod tests {
assert_eq!(expected, actual);
}
#[test]
fn serialize_chat_request() {
let button = KeyboardButton {
text: String::from(""),
request: Some(ButtonRequest::RequestChat(KeyboardButtonRequestChat::new(0, false))),
};
let expected = r#"{"text":"","request_chat":{"request_id":0,"chat_is_channel":false}}"#;
let actual = serde_json::to_string(&button).unwrap();
assert_eq!(expected, actual);
}
#[test]
fn deserialize_no_request() {
let json = r#"{"text":""}"#;

View file

@ -0,0 +1,114 @@
use serde::{Deserialize, Serialize};
use crate::types::ChatAdministratorRights;
/// This object defines the criteria used to request a suitable chat. The
/// identifier of the selected chat will be shared with the bot when the
/// corresponding button is pressed. [More about requesting chats »]
///
/// [More about requesting chats »]: https://core.telegram.org/bots/features#chat-and-user-selection
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct KeyboardButtonRequestChat {
/// identifier of the request, which will be received back in the
/// [`ChatShared`] object. Must be unique within the message.
///
/// [`ChatShared`]: crate::types::ChatShared
pub request_id: i32,
/// Pass `true` to request a channel chat, pass `false` to request a group
/// or a supergroup chat.
pub chat_is_channel: bool,
/// Pass `true` to request a forum supergroup, pass `false` to request a
/// non-forum chat. If not specified, no additional restrictions are
/// applied.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub chat_is_forum: Option<bool>,
/// Pass `true` to request a supergroup or a channel with a username, pass
/// `false` to request a chat without a username. If not specified, no
/// additional restrictions are applied.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub chat_has_username: Option<bool>,
/// Pass `true` to request a chat owned by the user. Otherwise, no
/// additional restrictions are applied.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub chat_is_created: Option<bool>,
/// Listing the required administrator rights of the user in the chat. The
/// rights must be a superset of bot_administrator_rights. If not specified,
/// no additional restrictions are applied.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub user_administrator_rights: Option<ChatAdministratorRights>,
/// Listing the required administrator rights of the bot in the chat. The
/// rights must be a subset of user_administrator_rights. If not specified,
/// no additional restrictions are applied.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub bot_administrator_rights: Option<ChatAdministratorRights>,
/// Pass `true` to request a chat with the bot as a member. Otherwise, no
/// additional restrictions are applied.
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub bot_is_member: bool,
}
impl KeyboardButtonRequestChat {
/// Creates a new [`KeyboardButtonRequestChat`].
pub fn new(request_id: i32, chat_is_channel: bool) -> Self {
Self {
request_id,
chat_is_channel,
chat_is_forum: None,
chat_has_username: None,
chat_is_created: None,
user_administrator_rights: None,
bot_administrator_rights: None,
bot_is_member: false,
}
}
/// Setter for `chat_is_forum` field.
#[must_use]
pub fn chat_is_forum(mut self, value: bool) -> Self {
self.chat_is_forum = Some(value);
self
}
/// Setter for `chat_has_username` field.
#[must_use]
pub fn chat_has_username(mut self, value: bool) -> Self {
self.chat_has_username = Some(value);
self
}
/// Setter for `chat_is_created` field.
#[must_use]
pub fn chat_is_created(mut self, value: bool) -> Self {
self.chat_is_created = Some(value);
self
}
/// Request a chat where the user has the specified administrator rights.
#[must_use]
pub fn user_administrator_rights(mut self, rights: ChatAdministratorRights) -> Self {
self.user_administrator_rights = Some(rights);
self
}
/// Request a chat where the bot has the specified administrator rights.
#[must_use]
pub fn bot_administrator_rights(mut self, rights: ChatAdministratorRights) -> Self {
self.bot_administrator_rights = Some(rights);
self
}
/// Setter for `bot_is_member` field.
#[must_use]
pub fn bot_is_member(mut self, value: bool) -> Self {
self.bot_is_member = value;
self
}
}

View file

@ -0,0 +1,44 @@
use serde::{Deserialize, Serialize};
/// This object defines the criteria used to request a suitable user. The
/// identifier of the selected user will be shared with the bot when the
/// corresponding button is pressed. More about requesting users »
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct KeyboardButtonRequestUser {
/// identifier of the request, which will be received back in the
/// [`UserShared`] object. Must be unique within the message.
///
/// [`UserShared`]: crate::types::UserShared
pub request_id: i32,
/// Pass `true` to request a bot, pass `false` to request a regular user. If
/// not specified, no additional restrictions are applied.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub user_is_bot: Option<bool>,
/// Pass `true` to request a premium user, pass `false` to request a
/// non-premium user. If not specified, no additional restrictions are
/// applied.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub user_is_premium: Option<bool>,
}
impl KeyboardButtonRequestUser {
/// Creates a new [`KeyboardButtonRequestUser`].
pub fn new(request_id: i32) -> Self {
Self { request_id, user_is_bot: None, user_is_premium: None }
}
/// Setter for `user_is_bot` field
pub fn user_is_bot(mut self, value: bool) -> Self {
self.user_is_bot = Some(value);
self
}
/// Setter for `user_is_premium` field
pub fn user_is_premium(mut self, value: bool) -> Self {
self.user_is_premium = Some(value);
self
}
}

View file

@ -5,12 +5,12 @@ use serde::{Deserialize, Serialize};
use url::Url;
use crate::types::{
Animation, Audio, BareChatId, Chat, ChatId, Contact, Dice, Document, ForumTopicClosed,
ForumTopicCreated, ForumTopicEdited, ForumTopicReopened, Game, GeneralForumTopicHidden,
GeneralForumTopicUnhidden, InlineKeyboardMarkup, Invoice, Location,
Animation, Audio, BareChatId, Chat, ChatId, ChatShared, Contact, Dice, Document,
ForumTopicClosed, ForumTopicCreated, ForumTopicEdited, ForumTopicReopened, Game,
GeneralForumTopicHidden, GeneralForumTopicUnhidden, InlineKeyboardMarkup, Invoice, Location,
MessageAutoDeleteTimerChanged, MessageEntity, MessageEntityRef, MessageId, PassportData,
PhotoSize, Poll, ProximityAlertTriggered, Sticker, SuccessfulPayment, ThreadId, True, User,
Venue, Video, VideoChatEnded, VideoChatParticipantsInvited, VideoChatScheduled,
UserShared, Venue, Video, VideoChatEnded, VideoChatParticipantsInvited, VideoChatScheduled,
VideoChatStarted, VideoNote, Voice, WebAppData, WriteAccessAllowed,
};
@ -60,6 +60,8 @@ pub enum MessageKind {
ChannelChatCreated(MessageChannelChatCreated),
MessageAutoDeleteTimerChanged(MessageMessageAutoDeleteTimerChanged),
Pinned(MessagePinned),
ChatShared(MessageChatShared),
UserShared(MessageUserShared),
Invoice(MessageInvoice),
SuccessfulPayment(MessageSuccessfulPayment),
ConnectedWebsite(MessageConnectedWebsite),
@ -247,6 +249,18 @@ pub struct MessagePinned {
pub pinned: Box<Message>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct MessageChatShared {
/// A chat was shared with the bot.
pub chat_shared: ChatShared,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct MessageUserShared {
/// A chat was shared with the bot.
pub user_shared: UserShared,
}
#[serde_with_macros::skip_serializing_none]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct MessageInvoice {
@ -655,12 +669,13 @@ mod getters {
self, message::MessageKind::*, Chat, ChatId, ChatMigration, Forward, ForwardedFrom,
MediaAnimation, MediaAudio, MediaContact, MediaDocument, MediaGame, MediaKind,
MediaLocation, MediaPhoto, MediaPoll, MediaSticker, MediaText, MediaVenue, MediaVideo,
MediaVideoNote, MediaVoice, Message, MessageChannelChatCreated, MessageCommon,
MessageConnectedWebsite, MessageDeleteChatPhoto, MessageDice, MessageEntity,
MediaVideoNote, MediaVoice, Message, MessageChannelChatCreated, MessageChatShared,
MessageCommon, MessageConnectedWebsite, MessageDeleteChatPhoto, MessageDice, MessageEntity,
MessageGroupChatCreated, MessageId, MessageInvoice, MessageLeftChatMember,
MessageNewChatMembers, MessageNewChatPhoto, MessageNewChatTitle, MessagePassportData,
MessagePinned, MessageProximityAlertTriggered, MessageSuccessfulPayment,
MessageSupergroupChatCreated, MessageVideoChatParticipantsInvited, PhotoSize, User,
MessageSupergroupChatCreated, MessageUserShared, MessageVideoChatParticipantsInvited,
PhotoSize, User,
};
use super::{
@ -701,12 +716,6 @@ mod getters {
}
}
#[deprecated(since = "0.4.2", note = "use `.chat.id` field instead")]
#[must_use]
pub fn chat_id(&self) -> ChatId {
self.chat.id
}
#[must_use]
pub fn forward(&self) -> Option<&Forward> {
self.common().and_then(|m| m.forward.as_ref())
@ -1269,6 +1278,22 @@ mod getters {
}
}
#[must_use]
pub fn shared_chat(&self) -> Option<&types::ChatShared> {
match &self.kind {
ChatShared(MessageChatShared { chat_shared }) => Some(chat_shared),
_ => None,
}
}
#[must_use]
pub fn shared_user(&self) -> Option<&types::UserShared> {
match &self.kind {
UserShared(MessageUserShared { user_shared }) => Some(user_shared),
_ => None,
}
}
#[must_use]
pub fn dice(&self) -> Option<&types::Dice> {
match &self.kind {
@ -1699,6 +1724,56 @@ mod tests {
assert!(message.is_ok());
}
#[test]
fn de_shared_chat() {
let json = r#"{
"message_id": 198283,
"chat": {
"id": 250918540,
"first_name": "Андрей",
"last_name": "Власов",
"username": "aka_dude",
"type": "private"
},
"date": 1567927221,
"chat_shared": {
"request_id": 348349,
"chat_id": 384939
}
}"#;
let message = from_str::<Message>(json);
assert!(message.is_ok());
assert_eq!(
message.unwrap(),
Message {
id: MessageId(198283),
thread_id: None,
date: chrono::NaiveDateTime::from_timestamp_opt(1567927221, 0).unwrap().and_utc(),
chat: Chat {
id: ChatId(250918540),
kind: ChatKind::Private(ChatPrivate {
first_name: Some("Андрей".to_string()),
last_name: Some("Власов".to_string()),
username: Some("aka_dude".to_string()),
bio: None,
emoji_status_custom_emoji_id: None,
has_private_forwards: None,
has_restricted_voice_and_video_messages: None
}),
photo: None,
has_aggressive_anti_spam_enabled: false,
pinned_message: None,
message_auto_delete_time: None,
has_hidden_members: false
},
kind: MessageKind::ChatShared(MessageChatShared {
chat_shared: ChatShared { request_id: 348349, chat_id: ChatId(384939) }
}),
via_bot: None
}
);
}
#[test]
fn de_media_group_forwarded() {
let json = r#"{

View file

@ -145,8 +145,11 @@ pub enum ParseMode {
MarkdownV2,
#[serde(rename = "HTML")]
Html,
#[deprecated = "This is a legacy mode, retained for backward compatibility. Use `MarkdownV2` \
instead."]
#[deprecated(
since = "0.1.0",
note = "This is a legacy mode, retained for backward compatibility. Use `MarkdownV2` \
instead."
)]
Markdown,
}

View file

@ -231,11 +231,6 @@ impl Update {
Some(chat)
}
#[deprecated(note = "renamed to `from`", since = "0.10.0")]
pub fn user(&self) -> Option<&User> {
self.from()
}
}
impl UpdateId {

View file

@ -0,0 +1,15 @@
use serde::{Deserialize, Serialize};
use crate::types::UserId;
/// Information about the chat whose identifier was shared with the bot using a
/// [`KeyboardButtonRequestUser`] button.
///
/// [`KeyboardButtonRequestUser`]: crate::types::KeyboardButtonRequestUser
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct UserShared {
/// Identifier of the request.
pub request_id: i32,
/// Identifier of the shared user.
pub user_id: UserId,
}

View file

@ -18,12 +18,16 @@ categories = ["web-programming", "api-bindings", "asynchronous"]
[features]
default = ["native-tls", "ctrlc_handler", "teloxide-core/default", "auto-send"]
default = ["native-tls", "ctrlc_handler", "teloxide-core/default"]
webhooks = ["rand"]
webhooks-axum = ["webhooks", "axum", "tower", "tower-http"]
sqlite-storage-nativetls = ["sqlx", "sqlx/runtime-tokio-native-tls", "native-tls"]
sqlite-storage-nativetls = [
"sqlx",
"sqlx/runtime-tokio-native-tls",
"native-tls",
]
sqlite-storage-rustls = ["sqlx", "sqlx/runtime-tokio-rustls", "rustls"]
postgres-storage-nativetls = ["sqlx", "sqlx/runtime-tokio-native-tls", "native-tls"]
postgres-storage-rustls = ["sqlx", "sqlx/runtime-tokio-rustls", "rustls"]
@ -38,7 +42,6 @@ ctrlc_handler = ["tokio/signal"]
native-tls = ["teloxide-core/native-tls"]
rustls = ["teloxide-core/rustls"]
auto-send = ["teloxide-core/auto_send"]
throttle = ["teloxide-core/throttle"]
cache-me = [
"teloxide-core/cache_me",
@ -65,7 +68,6 @@ full = [
"teloxide-core/full",
"native-tls",
"rustls",
"auto-send",
"throttle",
"cache-me",
"trace-adaptor",
@ -98,8 +100,7 @@ derive_more = "0.99"
thiserror = "1.0"
futures = "0.3.15"
pin-project = "1.0"
serde_with_macros = "1.4"
aquamarine = "0.1.11"
aquamarine = "0.5.0"
either = "1.9.0"
sqlx = { version = "0.7.3", optional = true, default-features = false, features = [
@ -107,7 +108,7 @@ sqlx = { version = "0.7.3", optional = true, default-features = false, features
"sqlite",
"postgres"
] }
redis = { version = "0.21", features = ["tokio-comp"], optional = true }
redis = { version = "0.24", features = ["tokio-comp"], optional = true }
serde_cbor = { version = "0.11", optional = true }
bincode = { version = "1.3", optional = true }
axum = { version = "0.6.0", optional = true }
@ -115,10 +116,13 @@ tower = { version = "0.4.12", optional = true }
tower-http = { version = "0.3.4", features = ["trace"], optional = true }
rand = { version = "0.8.5", optional = true }
# HACK: ahash 0.8.7 bumped MSRV to 1.72, to keep MVSR 1.68 we need to depend on an older version.
# this can be removed once our MSRV crosses 1.72.
ahash = "=0.8.7"
[dev-dependencies]
rand = "0.8.3"
pretty_env_logger = "0.4.0"
pretty_env_logger = "0.5.0"
serde = "1"
serde_json = "1"
tokio = { version = "1.8", features = ["fs", "rt-multi-thread", "macros"] }
@ -130,7 +134,6 @@ tokio-stream = "0.1"
[package.metadata.docs.rs]
# NB: can't use `all-features = true`, because `sqlite-storage-nativetls` conflicts with `sqlite-storage-rustls`
features = ["full", "nightly"]
# FIXME: Add back "-Znormalize-docs" when https://github.com/rust-lang/rust/issues/93703 is fixed
rustdoc-args = ["--cfg", "docsrs"]
rustc-args = ["--cfg", "dep_docsrs"]
cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"]
@ -151,7 +154,11 @@ required-features = ["redis-storage", "cbor-serializer", "bincode-serializer"]
[[test]]
name = "sqlite"
path = "tests/sqlite.rs"
required-features = ["sqlite-storage-nativetls", "cbor-serializer", "bincode-serializer"]
required-features = [
"sqlite-storage-nativetls",
"cbor-serializer",
"bincode-serializer",
]
[[test]]
name = "postgres"

View file

@ -0,0 +1,15 @@
use std::time::Duration;
pub type BackoffStrategy = Box<dyn Send + Fn(u32) -> Duration>;
/// Calculates the backoff time in seconds for exponential strategy with base 2
///
/// The maximum duration is limited to a little less than half an hour (1024
/// secs), so the successive timings are(in secs): 1, 2, 4, .., 1024, 1024, ..
///
/// More at: <https://en.wikipedia.org/wiki/Exponential_backoff#Exponential_backoff_algorithm>
pub fn exponential_backoff_strategy(error_count: u32) -> Duration {
// The error_count has to be limited so as not to cause overflow: 2^10 = 1024 ~
// a little less than half an hour
Duration::from_secs(1_u64 << error_count.min(10))
}

View file

@ -217,23 +217,6 @@
pub mod dialogue;
/// This module was moved to [`teloxide::update_listeners`].
///
/// [`teloxide::update_listeners`]: crate::update_listeners
#[deprecated = "This module was moved. Use `teloxide::update_listeners` instead."]
pub mod update_listeners {
pub use crate::update_listeners::*;
}
/// This module was moved to [`teloxide::repls`].
///
/// [`teloxide::repls`]: crate::repls
#[deprecated = "This module was moved. Use `teloxide::repls` instead."]
#[cfg(feature = "ctrlc_handler")]
pub mod repls {
pub use crate::repls::*;
}
mod dispatcher;
mod distribution;
mod filter_ext;

View file

@ -106,6 +106,59 @@ where
/// Specifies the distribution function that decides how updates are grouped
/// before execution.
///
/// ## Update grouping
///
/// When [`Dispatcher`] receives updates, it runs dispatching tree
/// (handlers) concurrently. This means that multiple updates can be
/// processed at the same time.
///
/// However, this is not always convenient. For example, if you have global
/// state, then you may want to process some updates sequentially, to
/// prevent state inconsistencies.
///
/// This is why `teloxide` allows grouping updates. Updates for which the
/// distribution function `f` returns the same "distribution key" `K` will
/// be run in sequence (while still being processed concurrently with the
/// updates with different distribution keys).
///
/// Updates for which `f` returns `None` will always be processed in
/// parallel.
///
/// ## Default distribution function
///
/// By default the distribution function is equivalent to `|upd|
/// upd.chat().map(|chat| chat.id)`, so updates from the same chat will be
/// processed sequentially.
///
/// This pair nicely with dialogue system, which has state attached to
/// chats.
///
/// ## Examples
///
/// Grouping updates by user who caused this update to happen:
///
/// ```
/// use teloxide::{dispatching::Dispatcher, dptree, Bot};
///
/// let bot = Bot::new("TOKEN");
/// let handler = dptree::entry() /* ... */;
/// let dp = Dispatcher::builder(bot, handler)
/// .distribution_function(|upd| upd.from().map(|user| user.id))
/// .build();
/// # let _: Dispatcher<_, (), _> = dp;
/// ```
///
/// Not grouping updates at all, always processing updates concurrently:
///
/// ```
/// use teloxide::{dispatching::Dispatcher, dptree, Bot};
///
/// let bot = Bot::new("TOKEN");
/// let handler = dptree::entry() /* ... */;
/// let dp = Dispatcher::builder(bot, handler).distribution_function(|_| None::<()>).build();
/// # let _: Dispatcher<_, (), _> = dp;
/// ```
#[must_use]
pub fn distribution_function<K>(
self,
@ -184,10 +237,13 @@ where
/// The base for update dispatching.
///
/// 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`].
/// ## Update grouping
///
/// `Dispatcher` generally processes updates concurrently. However, by default,
/// updates from the same chat are processed sequentially. [Learn more about
/// update grouping].
///
/// [update grouping]: distribution_function#update-grouping
///
/// See also: ["Dispatching or
/// REPLs?"](../dispatching/index.html#dispatching-or-repls)
@ -482,15 +538,6 @@ where
}
}
/// Setups the `^C` handler in order to call [`ShutdownToken::shutdown`]
/// when pressed.
#[cfg(feature = "ctrlc_handler")]
#[deprecated(since = "0.10.0", note = "use `enable_ctrlc_handler` on builder instead")]
pub fn setup_ctrlc_handler(&mut self) -> &mut Self {
self.setup_ctrlc_handler_inner();
self
}
/// Returns a shutdown token, which can later be used to
/// [`ShutdownToken::shutdown`].
pub fn shutdown_token(&self) -> ShutdownToken {

View file

@ -6,7 +6,6 @@
| `webhooks-axum` | Enables webhook implementation based on axum framework. |
| `macros` | Re-exports macros from [`teloxide-macros`]. |
| `ctrlc_handler` | Enables the [`DispatcherBuilder::enable_ctrlc_handler`] function (**enabled by default**). |
| `auto-send` | Enables the [`AutoSend`](adaptors::AutoSend) bot adaptor (**enabled by default; DEPRECATED**). |
| `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. |

View file

@ -131,10 +131,7 @@
#[cfg(feature = "ctrlc_handler")]
pub use repls::{repl, repl_with_listener};
#[cfg(feature = "ctrlc_handler")]
#[allow(deprecated)]
pub use repls::{commands_repl, commands_repl_with_listener};
pub mod backoff;
pub mod dispatching;
pub mod error_handlers;
pub mod prelude;

View file

@ -2,7 +2,6 @@
pub use crate::error_handlers::{LoggingErrorHandler, OnError};
#[allow(deprecated)]
pub use crate::respond;
pub use crate::dispatching::{
@ -20,10 +19,6 @@ pub use teloxide_core::{
},
};
#[cfg(feature = "auto-send")]
#[allow(deprecated)]
pub use crate::adaptors::AutoSend;
#[doc(no_inline)]
pub use teloxide_core::prelude::*;

View file

@ -12,6 +12,4 @@ mod commands_repl;
mod repl;
pub use commands_repl::CommandReplExt;
#[allow(deprecated)]
pub use commands_repl::{commands_repl, commands_repl_with_listener};
pub use repl::{repl, repl_with_listener};

View file

@ -8,7 +8,7 @@ use crate::{
};
use dptree::di::{DependencyMap, Injectable};
use futures::future::BoxFuture;
use std::{fmt::Debug, marker::PhantomData};
use std::fmt::Debug;
/// A [REPL] for commands.
///
@ -147,154 +147,3 @@ where
})
}
}
/// A [REPL] for commands.
//
///
//
#[doc = include_str!("preamble.md")]
///
/// [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. `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")]
#[deprecated(note = "Use `CommandsRepl::repl` instead")]
pub async fn commands_repl<'a, R, Cmd, H, Args>(bot: R, handler: H, cmd: PhantomData<Cmd>)
where
R: Requester + Clone + Send + Sync + 'static,
<R as Requester>::GetUpdates: Send,
H: Injectable<DependencyMap, ResponseResult<()>, Args> + Send + Sync + 'static,
Cmd: BotCommands + Send + Sync + 'static,
{
let cloned_bot = bot.clone();
#[allow(deprecated)]
commands_repl_with_listener(
bot,
handler,
update_listeners::polling_default(cloned_bot).await,
cmd,
)
.await;
}
/// A [REPL] for commands, with a custom [`UpdateListener`].
//
///
//
#[doc = include_str!("preamble.md")]
///
/// [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")]
///
#[cfg(feature = "ctrlc_handler")]
#[deprecated(note = "Use `CommandsRepl::repl_with_listener` instead")]
pub async fn commands_repl_with_listener<'a, R, Cmd, H, L, Args>(
bot: R,
handler: H,
listener: L,
cmd: PhantomData<Cmd>,
) where
Cmd: BotCommands + Send + Sync + 'static,
H: Injectable<DependencyMap, ResponseResult<()>, Args> + Send + Sync + 'static,
L: UpdateListener + Send + 'a,
L::Err: Debug + Send + 'a,
R: Requester + Clone + 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>().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

@ -5,7 +5,7 @@
//!
//! - [`polling_default`] function, which returns a default long polling
//! listener.
//! - [`polling`] function, which returns a long polling listener with your
//! - [`Polling`] function, which returns a long polling listener with your
//! configuration.
//! - Various functions in the [`webhooks`] module that return webhook listeners
//!
@ -13,12 +13,11 @@
//! [`Dispatcher`].
//!
//! Telegram supports two ways of [getting updates]: [long polling] and
//! [webhooks]. For the former see [`polling`] and [`polling_default`], for the
//! [webhooks]. For the former see [`Polling`] and [`polling_default`], for the
//! latter see the [`webhooks`] module.
//!
//! [`UpdateListener`]: UpdateListener
//! [`polling_default`]: polling_default
//! [`polling`]: polling()
//! [`Dispatcher`]: crate::dispatching::Dispatcher
//! [`Box::get_updates`]: crate::requests::Requester::get_updates
//! [getting updates]: https://core.telegram.org/bots/api#getting-updates
@ -26,7 +25,7 @@
//! [webhooks]: https://en.wikipedia.org/wiki/Webhook
/// Implementations of webhook update listeners - an alternative (to
/// [`fn@polling`]) way of receiving updates from telegram.
/// [`Polling`]) way of receiving updates from telegram.
#[cfg(feature = "webhooks")]
pub mod webhooks;
@ -42,7 +41,7 @@ mod stateful_listener;
#[allow(deprecated)]
pub use self::{
polling::{polling, polling_default, Polling, PollingBuilder, PollingStream},
polling::{polling_default, Polling, PollingBuilder, PollingStream},
stateful_listener::StatefulListener,
};
@ -80,7 +79,7 @@ pub trait UpdateListener:
/// Hint which updates should the listener listen for.
///
/// For example [`polling()`] should send the hint as
/// For example [`Polling`] should send the hint as
/// [`GetUpdates::allowed_updates`]
///
/// Note however that this is a _hint_ and as such, it can be ignored. The

View file

@ -12,8 +12,10 @@ use std::{
};
use futures::{ready, stream::Stream};
use tokio::time::{sleep, Sleep};
use crate::{
backoff::{exponential_backoff_strategy, BackoffStrategy},
requests::{HasPayload, Request, Requester},
stop::{mk_stop_token, StopFlag, StopToken},
types::{AllowedUpdate, Update},
@ -31,6 +33,7 @@ pub struct PollingBuilder<R> {
pub limit: Option<u8>,
pub allowed_updates: Option<Vec<AllowedUpdate>>,
pub drop_pending_updates: bool,
pub backoff_strategy: BackoffStrategy,
}
impl<R> PollingBuilder<R>
@ -84,6 +87,17 @@ where
Self { drop_pending_updates: true, ..self }
}
/// The backoff strategy that will be used for delay calculation between
/// reconnections caused by network errors.
///
/// By default, the [`exponential_backoff_strategy`] is used.
pub fn backoff_strategy(
self,
backoff_strategy: impl 'static + Send + Fn(u32) -> Duration,
) -> Self {
Self { backoff_strategy: Box::new(backoff_strategy), ..self }
}
/// Deletes webhook if it was set up.
pub async fn delete_webhook(self) -> Self {
delete_webhook_if_setup(&self.bot).await;
@ -96,7 +110,8 @@ where
///
/// See also: [`polling_default`], [`Polling`].
pub fn build(self) -> Polling<R> {
let Self { bot, timeout, limit, allowed_updates, drop_pending_updates } = self;
let Self { bot, timeout, limit, allowed_updates, drop_pending_updates, backoff_strategy } =
self;
let (token, flag) = mk_stop_token();
let polling = Polling {
bot,
@ -107,6 +122,7 @@ where
flag: Some(flag),
token,
stop_token_cloned: false,
backoff_strategy,
};
assert_update_listener(polling)
@ -131,25 +147,6 @@ where
assert_update_listener(polling)
}
/// Returns a long polling update listener with some additional options.
#[deprecated(since = "0.10.0", note = "use `Polling::builder()` instead")]
pub fn polling<R>(
bot: R,
timeout: Option<Duration>,
limit: Option<u8>,
allowed_updates: Option<Vec<AllowedUpdate>>,
) -> Polling<R>
where
R: Requester + Send + 'static,
<R as Requester>::GetUpdates: Send,
{
let mut builder = Polling::builder(bot);
builder.timeout = timeout;
builder.limit = limit;
builder.allowed_updates = allowed_updates;
assert_update_listener(builder.build())
}
async fn delete_webhook_if_setup<R>(requester: &R)
where
R: Requester,
@ -252,6 +249,7 @@ pub struct Polling<B: Requester> {
flag: Option<StopFlag>,
token: StopToken,
stop_token_cloned: bool,
backoff_strategy: BackoffStrategy,
}
impl<R> Polling<R>
@ -270,6 +268,7 @@ where
limit: None,
allowed_updates: None,
drop_pending_updates: false,
backoff_strategy: Box::new(exponential_backoff_strategy),
}
}
@ -317,6 +316,14 @@ pub struct PollingStream<'a, B: Requester> {
/// The flag that notifies polling to stop polling.
#[pin]
flag: StopFlag,
/// How long it takes to make next reconnection attempt
#[pin]
eepy: Option<Sleep>,
/// Counter for network errors occured during the current series of
/// reconnections
error_count: u32,
}
impl<B: Requester + Send + 'static> UpdateListener for Polling<B> {
@ -369,6 +376,8 @@ impl<'a, B: Requester + Send + 'a> AsUpdateStream<'a> for Polling<B> {
buffer: Vec::new().into_iter(),
in_flight: None,
flag,
eepy: None,
error_count: 0,
}
}
}
@ -415,6 +424,9 @@ impl<B: Requester> Stream for PollingStream<'_, B> {
return Ready(Some(Err(err)));
}
Ok(updates) => {
// Once we got the update hense the backoff reconnection strategy worked
*this.error_count = 0;
if let Some(upd) = updates.last() {
*this.offset = upd.id.as_offset();
}
@ -424,9 +436,24 @@ impl<B: Requester> Stream for PollingStream<'_, B> {
true => *this.drop_pending_updates = false,
}
}
Err(err) => return Ready(Some(Err(err))),
Err(err) => {
// Prevents the CPU spike occuring at network connection lose: <https://github.com/teloxide/teloxide/issues/780>
let backoff_strategy = &this.polling.backoff_strategy;
this.eepy.set(Some(sleep(backoff_strategy(*this.error_count))));
log::trace!("set {:?} reconnection delay", backoff_strategy(*this.error_count));
return Ready(Some(Err(err)));
}
}
}
// Poll eepy future until completion, needed for backoff strategy
else if let Some(eepy) = this.eepy.as_mut().as_pin_mut() {
ready!(eepy.poll(cx));
// As soon as delay is waited we increment the counter
*this.error_count = this.error_count.saturating_add(1);
log::trace!("current error count: {}", *this.error_count);
log::trace!("backoff delay completed");
this.eepy.as_mut().set(None);
}
let (offset, limit, timeout) = match (this.stopping, this.drop_pending_updates) {
// Normal `get_updates()` call
@ -467,8 +494,8 @@ impl<B: Requester> Stream for PollingStream<'_, B> {
#[test]
fn polling_is_send() {
let bot = crate::Bot::new("TOKEN");
#[allow(deprecated)]
let mut polling = polling(bot, None, None, None);
let mut polling = Polling::builder(bot).build();
assert_send(&polling);
assert_send(&polling.as_stream());

View file

@ -11,9 +11,9 @@ use crate::{
/// This type allows to turn a stream of updates (+ some additional functions)
/// into an [`UpdateListener`].
///
/// For an example of usage, see [`polling`].
/// For an example of usage, see [`Polling`].
///
/// [`polling`]: crate::update_listeners::polling()
/// [`Polling`]: crate::update_listeners::Polling
#[non_exhaustive]
pub struct StatefulListener<St, Assf, Sf, Hauf> {
/// The state of the listener.

View file

@ -55,7 +55,6 @@ use std::{
fmt::{Display, Formatter, Write},
};
use std::marker::PhantomData;
use teloxide_core::types::{BotCommand, Me};
#[cfg(feature = "macros")]
pub use teloxide_macros::BotCommands;
@ -265,15 +264,6 @@ pub trait BotCommands: Sized {
/// [`BotCommand`]: crate::types::BotCommand
/// [`set_my_commands`]: crate::requests::Requester::set_my_commands
fn bot_commands() -> Vec<BotCommand>;
/// Returns `PhantomData<Self>` that is used as a param of [`commands_repl`]
///
/// [`commands_repl`]: (crate::repls::commands_repl)
#[must_use]
#[deprecated(note = "Use `CommandReplExt` instead")]
fn ty() -> PhantomData<Self> {
PhantomData
}
}
pub type PrefixedBotCommand = String;

View file

@ -28,6 +28,29 @@ trigger_files = ["crates/teloxide/"]
[autolabel."C-macros"]
trigger_files = ["crates/teloxide-macros/"]
[autolabel."A-dialogue"]
trigger_files = ["crates/teloxide/src/dispatching/dialogue/"]
[autolabel."A-dispatching"]
trigger_files = ["crates/teloxide/src/dispatching/"]
[autolabel."A-macros"]
trigger_files = ["crates/teloxide-macros/"]
[autolabel."A-requester"]
trigger_files = ["crates/teloxide-core/src/requests/requester.rs", "crates/teloxide-core/src/requests/requester_ext.rs"]
[autolabel."A-requests"]
trigger_files = ["crates/teloxide-core/src/requests/", "crates/teloxide-core/src/payloads/"]
[autolabel."A-tba-errors"]
trigger_files = ["crates/teloxide-core/src/errors.rs"]
[autolabel."A-tba-types"]
trigger_files = ["crates/teloxide-core/src/types"]
[autolabel."A-update-listeners"]
trigger_files = ["crates/teloxide/src/update_listeners"]
[relabel]
allow-unauthenticated = [