From 0e5880af9112e388c873d3b930a95f604d683a1a Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Tue, 12 Jan 2021 19:42:33 +0600 Subject: [PATCH 001/131] Clarify the purpose of Storage in the docs --- src/dispatching/dialogue/storage/mod.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/dispatching/dialogue/storage/mod.rs b/src/dispatching/dialogue/storage/mod.rs index 1ca36fb1..35d56c56 100644 --- a/src/dispatching/dialogue/storage/mod.rs +++ b/src/dispatching/dialogue/storage/mod.rs @@ -28,6 +28,9 @@ pub use sqlite_storage::{SqliteStorage, SqliteStorageError}; /// You can implement this trait for a structure that communicates with a DB and /// be sure that after you restart your bot, all the dialogues won't be lost. /// +/// `Storage` is used only to store dialogue states, i.e. it can't be used as a +/// generic database. +/// /// Currently we support the following storages out of the box: /// /// - [`InMemStorage`] - a storage based on a simple hash map From 751af600f75639812338554974d4117041b8f18f Mon Sep 17 00:00:00 2001 From: Goldstein Date: Sun, 31 Jan 2021 19:44:53 +0300 Subject: [PATCH 002/131] Provide new url for tg-vimhelpbot --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 88066ea3..abb8e67c 100644 --- a/README.md +++ b/README.md @@ -429,7 +429,7 @@ Feel free to push your own bot into our collection! - [_ArtHome12/vzmuinebot -- Telegram bot for food menu navigate_](https://github.com/ArtHome12/vzmuinebot) - [_Hermitter/tepe -- A CLI to command a bot to send messages and files over Telegram_](https://github.com/Hermitter/tepe) - [_ArtHome12/cognito_bot -- The bot is designed to anonymize messages to a group_](https://github.com/ArtHome12/cognito_bot) - - [_GoldsteinE/tg-vimhelpbot -- Link `:help` for Vim in Telegram_](https://github.com/GoldsteinE/tg-vimhelpbot) + - [ pro-vim/tg-vimhelpbot -- Link `:help` for Vim in Telegram_](https://github.com/pro-vim/tg-vimhelpbot) - [_sschiz/janitor-bot_ -- A bot that removes users trying to join to a chat that is designed for comments](https://github.com/sschiz/janitor-bot) - [ myblackbeard/basketball-betting-bot -- The bot lets you bet on NBA games against your buddies](https://github.com/myblackbeard/basketball-betting-bot) From 155f7aed5a83b9795917f4dbbf81ff31153fffa0 Mon Sep 17 00:00:00 2001 From: Goldstein Date: Sun, 31 Jan 2021 22:15:42 +0300 Subject: [PATCH 003/131] Fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index abb8e67c..a8ac4a53 100644 --- a/README.md +++ b/README.md @@ -429,7 +429,7 @@ Feel free to push your own bot into our collection! - [_ArtHome12/vzmuinebot -- Telegram bot for food menu navigate_](https://github.com/ArtHome12/vzmuinebot) - [_Hermitter/tepe -- A CLI to command a bot to send messages and files over Telegram_](https://github.com/Hermitter/tepe) - [_ArtHome12/cognito_bot -- The bot is designed to anonymize messages to a group_](https://github.com/ArtHome12/cognito_bot) - - [ pro-vim/tg-vimhelpbot -- Link `:help` for Vim in Telegram_](https://github.com/pro-vim/tg-vimhelpbot) + - [_pro-vim/tg-vimhelpbot -- Link `:help` for Vim in Telegram_](https://github.com/pro-vim/tg-vimhelpbot) - [_sschiz/janitor-bot_ -- A bot that removes users trying to join to a chat that is designed for comments](https://github.com/sschiz/janitor-bot) - [ myblackbeard/basketball-betting-bot -- The bot lets you bet on NBA games against your buddies](https://github.com/myblackbeard/basketball-betting-bot) From b1201c4506f16cd4d8e658587b1e01cd5f4664fe Mon Sep 17 00:00:00 2001 From: "Eric S. Londres" Date: Wed, 10 Mar 2021 16:47:05 -0500 Subject: [PATCH 004/131] Add BeerHolderBot to community bot list --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 88066ea3..f535832a 100644 --- a/README.md +++ b/README.md @@ -432,6 +432,7 @@ Feel free to push your own bot into our collection! - [_GoldsteinE/tg-vimhelpbot -- Link `:help` for Vim in Telegram_](https://github.com/GoldsteinE/tg-vimhelpbot) - [_sschiz/janitor-bot_ -- A bot that removes users trying to join to a chat that is designed for comments](https://github.com/sschiz/janitor-bot) - [ myblackbeard/basketball-betting-bot -- The bot lets you bet on NBA games against your buddies](https://github.com/myblackbeard/basketball-betting-bot) + - [_slondr/BeerHolderBot -- A bot that holds your beer_](https://gitlab.com/slondr/BeerHolderBot) ## Contributing See [CONRIBUTING.md](https://github.com/teloxide/teloxide/blob/master/CONTRIBUTING.md). From 865c3f28a0de629e2c2ba74cd8c4f541d183dbf8 Mon Sep 17 00:00:00 2001 From: "Eric S. Londres" Date: Wed, 10 Mar 2021 16:48:16 -0500 Subject: [PATCH 005/131] Fix formatting of basketball betting bot in readme Previously the title/desc of the bot was not italicized, unlike other bot names/descriptions --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f535832a..71066c35 100644 --- a/README.md +++ b/README.md @@ -431,7 +431,7 @@ Feel free to push your own bot into our collection! - [_ArtHome12/cognito_bot -- The bot is designed to anonymize messages to a group_](https://github.com/ArtHome12/cognito_bot) - [_GoldsteinE/tg-vimhelpbot -- Link `:help` for Vim in Telegram_](https://github.com/GoldsteinE/tg-vimhelpbot) - [_sschiz/janitor-bot_ -- A bot that removes users trying to join to a chat that is designed for comments](https://github.com/sschiz/janitor-bot) - - [ myblackbeard/basketball-betting-bot -- The bot lets you bet on NBA games against your buddies](https://github.com/myblackbeard/basketball-betting-bot) + - [_myblackbeard/basketball-betting-bot -- The bot lets you bet on NBA games against your buddies_](https://github.com/myblackbeard/basketball-betting-bot) - [_slondr/BeerHolderBot -- A bot that holds your beer_](https://gitlab.com/slondr/BeerHolderBot) ## Contributing From 60618cbdb519b700b7202119353cf9b6d0b818a2 Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Mon, 22 Mar 2021 01:27:37 +0600 Subject: [PATCH 006/131] Embed an updating guide to v0.4.0 (README.md) --- README.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 874f0703..0ba4763f 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,14 @@ +
+ FYI: Updating to v0.4.0 + + - `answer_str` -> `answer` + - Don't write `.send()` after each request, now it is done automatically. + - Use `.auto_send()` to construct your bot: `let bot = Bot::from_env().auto_send();`. + - `UpdateWithCx` -> `UpdateWithCx, Message>` + - `ResponseResult<()>` -> `Result<(), Box>` (or import `ResponseResult` beforehand: `use teloxide::requests::ResponseResult;`) + +
+

teloxide

@@ -178,7 +189,7 @@ async fn main() {
### Dialogues management -A dialogue is described by an enumeration where each variant is one of possible dialogue's states. There are also _subtransition functions_, which turn a dialogue from one state to another, thereby forming a [FSM]. +A dialogue is described by an enumeration where each variant is one of possible dialogue's states. There are also _subtransition functions_, which turn a dialogue from one state to another, thereby forming an [FSM]. [FSM]: https://en.wikipedia.org/wiki/Finite-state_machine From 63611cc7aecf603737f673c993d2cdf3e249ea7a Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Mon, 22 Mar 2021 01:28:50 +0600 Subject: [PATCH 007/131] Fix the v0.4.0 release date (CHANGELOG.md) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 256bc43e..5cb01ec4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [unreleased] -## [0.4.0] - 2021-03-19 +## [0.4.0] - 2021-03-22 ### Added - Integrate [teloxide-core]. From eb384defe3111f22593d86e65739fa7a240ca944 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Mon, 22 Feb 2021 08:14:33 +0600 Subject: [PATCH 008/131] Update the dependencies --- Cargo.toml | 45 ++++++++++--------- rustfmt.toml | 2 +- src/bot/mod.rs | 2 +- .../dialogue/dialogue_dispatcher.rs | 5 ++- src/dispatching/repls/commands_repl.rs | 16 ++++--- src/dispatching/repls/repl.rs | 3 +- src/net/request.rs | 2 +- 7 files changed, 42 insertions(+), 33 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 21a64b23..fce35c78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,43 +36,46 @@ macros = ["teloxide-macros"] nightly = [] # currently used for `README.md` tests and building docs for `docsrs` to add `This is supported on feature="..." only.` [dependencies] -serde_json = "1.0.55" -serde = { version = "1.0.114", features = ["derive"] } +teloxide-core = "0.1" -tokio = { version = "0.2.21", features = ["fs", "stream"] } -tokio-util = "0.3.1" +serde_json = "1.0" +serde = { version = "1.0", features = ["derive"] } -reqwest = { version = "0.10.6", features = ["json", "stream"] } -log = "0.4.8" +tokio = { version = "1.2", features = ["fs"] } +tokio-util = "0.6" +tokio-stream = "0.1" + +reqwest = { version = "0.11", features = ["json", "stream"] } +log = "0.4" lockfree = "0.5.1" -bytes = "0.5.5" -mime = "0.3.16" +bytes = "1.0" +mime = "0.3" -derive_more = "0.99.9" -thiserror = "1.0.20" -async-trait = "0.1.36" -futures = "0.3.5" -pin-project = "0.4.22" -serde_with_macros = "1.1.0" +derive_more = "0.99" +thiserror = "1.0" +async-trait = "0.1" +futures = "0.3" +pin-project = "1.0" +serde_with_macros = "1.4" -sqlx = { version = "0.4.0-beta.1", optional = true, default-features = false, features = [ +sqlx = { version = "0.5", optional = true, default-features = false, features = [ "runtime-tokio-native-tls", "macros", "sqlite", ] } -redis = { version = "0.16.0", optional = true } -serde_cbor = { version = "0.11.1", optional = true } -bincode = { version = "1.3.1", optional = true } -frunk = { version = "0.3.1", optional = true } +redis = { version = "0.16", optional = true } +serde_cbor = { version = "0.11", optional = true } +bincode = { version = "1.3", optional = true } +frunk = { version = "0.3", optional = true } teloxide-macros = { git = "https://github.com/teloxide/teloxide-macros", branch = "master", optional = true } [dev-dependencies] smart-default = "0.6.0" -rand = "0.7.3" +rand = "0.8.3" pretty_env_logger = "0.4.0" lazy_static = "1.4.0" -tokio = { version = "0.2.21", features = ["fs", "stream", "rt-threaded", "macros"] } +tokio = { version = "1.2.0", features = ["fs", "rt", "macros"] } [package.metadata.docs.rs] all-features = true diff --git a/rustfmt.toml b/rustfmt.toml index c61c5a92..38db4219 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,6 +1,6 @@ format_code_in_doc_comments = true wrap_comments = true format_strings = true -merge_imports = true +imports_granularity = "Crate" use_small_heuristics = "Max" use_field_init_shorthand = true diff --git a/src/bot/mod.rs b/src/bot/mod.rs index 28fe1a24..b936266f 100644 --- a/src/bot/mod.rs +++ b/src/bot/mod.rs @@ -126,7 +126,7 @@ pub(crate) fn sound_bot() -> ClientBuilder { ClientBuilder::new() .connect_timeout(connect_timeout) .timeout(Duration::from_secs(connect_timeout.as_secs() + timeout + 2)) - .tcp_nodelay_(true) + .tcp_nodelay(true) .default_headers(headers) } diff --git a/src/dispatching/dialogue/dialogue_dispatcher.rs b/src/dispatching/dialogue/dialogue_dispatcher.rs index 0e1f97b6..e555d84e 100644 --- a/src/dispatching/dialogue/dialogue_dispatcher.rs +++ b/src/dispatching/dialogue/dialogue_dispatcher.rs @@ -11,6 +11,7 @@ use tokio::sync::mpsc; use lockfree::map::Map; use std::sync::{Arc, Mutex}; +use tokio_stream::wrappers::UnboundedReceiverStream; /// A dispatcher of dialogues. /// @@ -84,7 +85,7 @@ where let handler = Arc::clone(&self.handler); let senders = Arc::clone(&self.senders); - tokio::spawn(rx.for_each(move |cx: UpdateWithCx| { + tokio::spawn(UnboundedReceiverStream::new(rx).for_each(move |cx: UpdateWithCx| { let storage = Arc::clone(&storage); let handler = Arc::clone(&handler); let senders = Arc::clone(&senders); @@ -137,7 +138,7 @@ where { let this = Arc::new(self); - updates + UnboundedReceiverStream::new(updates) .for_each(move |cx| { let this = Arc::clone(&this); let chat_id = cx.update.chat_id(); diff --git a/src/dispatching/repls/commands_repl.rs b/src/dispatching/repls/commands_repl.rs index d80138ca..cc90ad0c 100644 --- a/src/dispatching/repls/commands_repl.rs +++ b/src/dispatching/repls/commands_repl.rs @@ -10,6 +10,7 @@ use crate::{ }; use futures::StreamExt; use std::{fmt::Debug, future::Future, sync::Arc}; +use tokio_stream::wrappers::UnboundedReceiverStream; /// A [REPL] for commands. /// @@ -73,13 +74,16 @@ pub async fn commands_repl_with_listener<'a, Cmd, H, Fut, L, ListenerE, HandlerE Dispatcher::new(bot) .messages_handler(move |rx: DispatcherHandlerRx| { - rx.commands::(bot_name).for_each_concurrent(None, move |(cx, cmd)| { - let handler = Arc::clone(&handler); + UnboundedReceiverStream::new(rx).commands::(bot_name).for_each_concurrent( + None, + move |(cx, cmd)| { + let handler = Arc::clone(&handler); - async move { - handler(cx, cmd).await.log_on_error().await; - } - }) + async move { + handler(cx, cmd).await.log_on_error().await; + } + }, + ) }) .dispatch_with_listener( listener, diff --git a/src/dispatching/repls/repl.rs b/src/dispatching/repls/repl.rs index 9a50355f..877c6869 100644 --- a/src/dispatching/repls/repl.rs +++ b/src/dispatching/repls/repl.rs @@ -9,6 +9,7 @@ use crate::{ }; use futures::StreamExt; use std::{fmt::Debug, future::Future, sync::Arc}; +use tokio_stream::wrappers::UnboundedReceiverStream; /// A [REPL] for messages. /// @@ -57,7 +58,7 @@ where Dispatcher::new(bot) .messages_handler(|rx: DispatcherHandlerRx| { - rx.for_each_concurrent(None, move |message| { + UnboundedReceiverStream::new(rx).for_each_concurrent(None, move |message| { let handler = Arc::clone(&handler); async move { diff --git a/src/net/request.rs b/src/net/request.rs index e376c3fd..05f0af5e 100644 --- a/src/net/request.rs +++ b/src/net/request.rs @@ -52,7 +52,7 @@ where T: DeserializeOwned, { if response.status().is_server_error() { - tokio::time::delay_for(DELAY_ON_SERVER_ERROR).await; + tokio::time::sleep(DELAY_ON_SERVER_ERROR).await; } serde_json::from_str::>( From 539bc6763b7185e57c8c6e55edb3b7822ec7c84f Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sat, 6 Mar 2021 03:18:03 +0600 Subject: [PATCH 009/131] Integrate teloxide-core --- CHANGELOG.md | 9 + Cargo.toml | 2 +- src/bot/api.rs | 1733 ---------------- src/bot/download.rs | 67 - src/bot/mod.rs | 252 --- .../dialogue/dialogue_dispatcher.rs | 32 +- .../dialogue/dialogue_dispatcher_handler.rs | 22 +- src/dispatching/dialogue/dialogue_with_cx.rs | 12 +- src/dispatching/dialogue/get_chat_id.rs | 2 +- .../dialogue/storage/redis_storage.rs | 5 +- .../dialogue/storage/sqlite_storage.rs | 4 +- src/dispatching/dialogue/transition.rs | 15 +- src/dispatching/dispatcher.rs | 120 +- src/dispatching/dispatcher_handler.rs | 14 +- src/dispatching/dispatcher_handler_rx_ext.rs | 29 +- src/dispatching/mod.rs | 4 +- src/dispatching/repls/commands_repl.rs | 26 +- src/dispatching/repls/dialogues_repl.rs | 29 +- src/dispatching/repls/repl.rs | 28 +- src/dispatching/update_listeners.rs | 43 +- src/dispatching/update_with_cx.rs | 124 +- src/errors.rs | 515 ----- src/lib.rs | 18 +- src/net/download.rs | 51 - src/net/mod.rs | 63 - src/net/request.rs | 63 - src/net/telegram_response.rs | 70 - src/prelude.rs | 11 +- src/requests/all/add_sticker_to_set.rs | 109 - src/requests/all/answer_callback_query.rs | 101 - src/requests/all/answer_inline_query.rs | 146 -- src/requests/all/answer_pre_checkout_query.rs | 82 - src/requests/all/answer_shipping_query.rs | 86 - src/requests/all/create_new_sticker_set.rs | 134 -- src/requests/all/delete_chat_photo.rs | 50 - src/requests/all/delete_chat_sticker_set.rs | 55 - src/requests/all/delete_message.rs | 67 - src/requests/all/delete_sticker_from_set.rs | 47 - src/requests/all/delete_webhook.rs | 37 - .../all/edit_inline_message_caption.rs | 83 - .../all/edit_inline_message_live_location.rs | 77 - src/requests/all/edit_inline_message_media.rs | 78 - .../all/edit_inline_message_reply_markup.rs | 62 - src/requests/all/edit_inline_message_text.rs | 98 - src/requests/all/edit_message_caption.rs | 91 - .../all/edit_message_live_location.rs | 91 - src/requests/all/edit_message_media.rs | 85 - src/requests/all/edit_message_reply_markup.rs | 69 - src/requests/all/edit_message_text.rs | 107 - src/requests/all/export_chat_invite_link.rs | 65 - src/requests/all/forward_message.rs | 81 - src/requests/all/get_chat.rs | 50 - src/requests/all/get_chat_administrators.rs | 53 - src/requests/all/get_chat_member.rs | 55 - src/requests/all/get_chat_members_count.rs | 48 - src/requests/all/get_file.rs | 62 - src/requests/all/get_game_high_scores.rs | 63 - src/requests/all/get_me.rs | 33 - src/requests/all/get_my_commands.rs | 33 - src/requests/all/get_sticker_set.rs | 47 - src/requests/all/get_updates.rs | 122 -- src/requests/all/get_user_profile_photos.rs | 58 - src/requests/all/get_webhook_info.rs | 38 - src/requests/all/kick_chat_member.rs | 72 - src/requests/all/leave_chat.rs | 48 - src/requests/all/mod.rs | 152 -- src/requests/all/pin_chat_message.rs | 69 - src/requests/all/promote_chat_member.rs | 134 -- src/requests/all/restrict_chat_member.rs | 76 - src/requests/all/send_animation.rs | 193 -- src/requests/all/send_audio.rs | 206 -- src/requests/all/send_chat_action.rs | 105 - src/requests/all/send_contact.rs | 129 -- src/requests/all/send_dice.rs | 94 - src/requests/all/send_document.rs | 163 -- src/requests/all/send_game.rs | 92 - src/requests/all/send_invoice.rs | 299 --- src/requests/all/send_location.rs | 110 -- src/requests/all/send_media_group.rs | 85 - src/requests/all/send_message.rs | 121 -- src/requests/all/send_photo.rs | 140 -- src/requests/all/send_poll.rs | 217 -- src/requests/all/send_sticker.rs | 117 -- src/requests/all/send_venue.rs | 159 -- src/requests/all/send_video.rs | 207 -- src/requests/all/send_video_note.rs | 160 -- src/requests/all/send_voice.rs | 159 -- .../set_chat_administrator_custom_title.rs | 75 - src/requests/all/set_chat_description.rs | 62 - src/requests/all/set_chat_permissions.rs | 58 - src/requests/all/set_chat_photo.rs | 58 - src/requests/all/set_chat_sticker_set.rs | 64 - src/requests/all/set_chat_title.rs | 63 - src/requests/all/set_game_score.rs | 89 - src/requests/all/set_my_commands.rs | 50 - .../all/set_sticker_position_in_set.rs | 56 - src/requests/all/set_sticker_set_thumb.rs | 74 - src/requests/all/set_webhook.rs | 114 -- .../all/stop_inline_message_live_location.rs | 62 - .../all/stop_message_live_location.rs | 70 - src/requests/all/stop_poll.rs | 66 - src/requests/all/unban_chat_member.rs | 58 - src/requests/all/unpin_chat_message.rs | 52 - src/requests/all/upload_sticker_file.rs | 55 - src/requests/form_builder.rs | 156 -- src/requests/mod.rs | 37 - src/requests/utils.rs | 31 - src/types/allowed_update.rs | 14 - src/types/animation.rs | 165 -- src/types/audio.rs | 152 -- src/types/bot_command.rs | 42 - src/types/callback_game.rs | 13 - src/types/callback_query.rs | 158 -- src/types/chat.rs | 353 ---- src/types/chat_action.rs | 17 - src/types/chat_id.rs | 39 - src/types/chat_member.rs | 262 --- src/types/chat_permissions.rs | 48 - src/types/chat_photo.rs | 82 - src/types/chosen_inline_result.rs | 85 - src/types/contact.rs | 79 - src/types/dice.rs | 38 - src/types/dice_emoji.rs | 16 - src/types/document.rs | 92 - src/types/encrypted_credentials.rs | 94 - src/types/encrypted_passport_element.rs | 794 -------- src/types/file.rs | 75 - src/types/force_reply.rs | 41 - src/types/game.rs | 106 - src/types/game_high_score.rs | 40 - src/types/inline_keyboard_button.rs | 135 -- src/types/inline_keyboard_markup.rs | 109 - src/types/inline_query.rs | 74 - src/types/inline_query_result.rs | 102 - src/types/inline_query_result_article.rs | 128 -- src/types/inline_query_result_audio.rs | 133 -- src/types/inline_query_result_cached_audio.rs | 96 - .../inline_query_result_cached_document.rs | 121 -- src/types/inline_query_result_cached_gif.rs | 109 - .../inline_query_result_cached_mpeg4_gif.rs | 101 - src/types/inline_query_result_cached_photo.rs | 120 -- .../inline_query_result_cached_sticker.rs | 70 - src/types/inline_query_result_cached_video.rs | 121 -- src/types/inline_query_result_cached_voice.rs | 109 - src/types/inline_query_result_contact.rs | 140 -- src/types/inline_query_result_document.rs | 138 -- src/types/inline_query_result_game.rs | 55 - src/types/inline_query_result_gif.rs | 148 -- src/types/inline_query_result_location.rs | 128 -- src/types/inline_query_result_mpeg4_gif.rs | 149 -- src/types/inline_query_result_photo.rs | 152 -- src/types/inline_query_result_venue.rs | 157 -- src/types/inline_query_result_video.rs | 182 -- src/types/inline_query_result_voice.rs | 119 -- src/types/input_file.rs | 101 - src/types/input_media.rs | 511 ----- src/types/input_message_content.rs | 318 --- src/types/invoice.rs | 91 - src/types/keyboard_button.rs | 156 -- src/types/keyboard_button_poll_type.rs | 24 - src/types/label_price.rs | 56 - src/types/location.rs | 28 - src/types/login_url.rs | 61 - src/types/mask_position.rs | 58 - src/types/me.rs | 57 - src/types/message.rs | 1753 ----------------- src/types/message_entity.rs | 162 -- src/types/mod.rs | 194 -- src/types/non_telegram_types/country_code.rs | 254 --- src/types/non_telegram_types/currency.rs | 89 - src/types/non_telegram_types/mime_wrapper.rs | 46 - src/types/non_telegram_types/mod.rs | 7 - src/types/order_info.rs | 72 - src/types/parse_mode.rs | 189 -- src/types/passport_data.rs | 40 - src/types/passport_element_error.rs | 558 ------ src/types/passport_file.rs | 66 - src/types/photo_size.rs | 94 - src/types/poll.rs | 250 --- src/types/poll_answer.rs | 48 - src/types/poll_type.rs | 9 - src/types/pre_checkout_query.rs | 108 - src/types/reply_keyboard_markup.rs | 98 - src/types/reply_keyboard_remove.rs | 52 - src/types/reply_markup.rs | 27 - src/types/response_parameters.rs | 43 - src/types/send_invoice.rs | 229 --- src/types/shipping_address.rs | 100 - src/types/shipping_option.rs | 71 - src/types/shipping_query.rs | 63 - src/types/sticker.rs | 135 -- src/types/sticker_set.rs | 89 - src/types/sticker_type.rs | 28 - src/types/successful_payment.rs | 112 -- src/types/target_message.rs | 17 - src/types/unit_false.rs | 76 - src/types/unit_true.rs | 79 - src/types/update.rs | 321 --- src/types/user.rs | 132 -- src/types/user_profile_photos.rs | 40 - src/types/venue.rs | 74 - src/types/video.rs | 108 - src/types/video_note.rs | 89 - src/types/voice.rs | 74 - src/types/webhook_info.rs | 98 - src/utils/client_from_env.rs | 26 - src/utils/html.rs | 4 +- src/utils/markdown.rs | 5 +- src/utils/mod.rs | 3 +- 209 files changed, 326 insertions(+), 23551 deletions(-) delete mode 100644 src/bot/api.rs delete mode 100644 src/bot/download.rs delete mode 100644 src/bot/mod.rs delete mode 100644 src/errors.rs delete mode 100644 src/net/download.rs delete mode 100644 src/net/mod.rs delete mode 100644 src/net/request.rs delete mode 100644 src/net/telegram_response.rs delete mode 100644 src/requests/all/add_sticker_to_set.rs delete mode 100644 src/requests/all/answer_callback_query.rs delete mode 100644 src/requests/all/answer_inline_query.rs delete mode 100644 src/requests/all/answer_pre_checkout_query.rs delete mode 100644 src/requests/all/answer_shipping_query.rs delete mode 100644 src/requests/all/create_new_sticker_set.rs delete mode 100644 src/requests/all/delete_chat_photo.rs delete mode 100644 src/requests/all/delete_chat_sticker_set.rs delete mode 100644 src/requests/all/delete_message.rs delete mode 100644 src/requests/all/delete_sticker_from_set.rs delete mode 100644 src/requests/all/delete_webhook.rs delete mode 100644 src/requests/all/edit_inline_message_caption.rs delete mode 100644 src/requests/all/edit_inline_message_live_location.rs delete mode 100644 src/requests/all/edit_inline_message_media.rs delete mode 100644 src/requests/all/edit_inline_message_reply_markup.rs delete mode 100644 src/requests/all/edit_inline_message_text.rs delete mode 100644 src/requests/all/edit_message_caption.rs delete mode 100644 src/requests/all/edit_message_live_location.rs delete mode 100644 src/requests/all/edit_message_media.rs delete mode 100644 src/requests/all/edit_message_reply_markup.rs delete mode 100644 src/requests/all/edit_message_text.rs delete mode 100644 src/requests/all/export_chat_invite_link.rs delete mode 100644 src/requests/all/forward_message.rs delete mode 100644 src/requests/all/get_chat.rs delete mode 100644 src/requests/all/get_chat_administrators.rs delete mode 100644 src/requests/all/get_chat_member.rs delete mode 100644 src/requests/all/get_chat_members_count.rs delete mode 100644 src/requests/all/get_file.rs delete mode 100644 src/requests/all/get_game_high_scores.rs delete mode 100644 src/requests/all/get_me.rs delete mode 100644 src/requests/all/get_my_commands.rs delete mode 100644 src/requests/all/get_sticker_set.rs delete mode 100644 src/requests/all/get_updates.rs delete mode 100644 src/requests/all/get_user_profile_photos.rs delete mode 100644 src/requests/all/get_webhook_info.rs delete mode 100644 src/requests/all/kick_chat_member.rs delete mode 100644 src/requests/all/leave_chat.rs delete mode 100644 src/requests/all/mod.rs delete mode 100644 src/requests/all/pin_chat_message.rs delete mode 100644 src/requests/all/promote_chat_member.rs delete mode 100644 src/requests/all/restrict_chat_member.rs delete mode 100644 src/requests/all/send_animation.rs delete mode 100644 src/requests/all/send_audio.rs delete mode 100644 src/requests/all/send_chat_action.rs delete mode 100644 src/requests/all/send_contact.rs delete mode 100644 src/requests/all/send_dice.rs delete mode 100644 src/requests/all/send_document.rs delete mode 100644 src/requests/all/send_game.rs delete mode 100644 src/requests/all/send_invoice.rs delete mode 100644 src/requests/all/send_location.rs delete mode 100644 src/requests/all/send_media_group.rs delete mode 100644 src/requests/all/send_message.rs delete mode 100644 src/requests/all/send_photo.rs delete mode 100644 src/requests/all/send_poll.rs delete mode 100644 src/requests/all/send_sticker.rs delete mode 100644 src/requests/all/send_venue.rs delete mode 100644 src/requests/all/send_video.rs delete mode 100644 src/requests/all/send_video_note.rs delete mode 100644 src/requests/all/send_voice.rs delete mode 100644 src/requests/all/set_chat_administrator_custom_title.rs delete mode 100644 src/requests/all/set_chat_description.rs delete mode 100644 src/requests/all/set_chat_permissions.rs delete mode 100644 src/requests/all/set_chat_photo.rs delete mode 100644 src/requests/all/set_chat_sticker_set.rs delete mode 100644 src/requests/all/set_chat_title.rs delete mode 100644 src/requests/all/set_game_score.rs delete mode 100644 src/requests/all/set_my_commands.rs delete mode 100644 src/requests/all/set_sticker_position_in_set.rs delete mode 100644 src/requests/all/set_sticker_set_thumb.rs delete mode 100644 src/requests/all/set_webhook.rs delete mode 100644 src/requests/all/stop_inline_message_live_location.rs delete mode 100644 src/requests/all/stop_message_live_location.rs delete mode 100644 src/requests/all/stop_poll.rs delete mode 100644 src/requests/all/unban_chat_member.rs delete mode 100644 src/requests/all/unpin_chat_message.rs delete mode 100644 src/requests/all/upload_sticker_file.rs delete mode 100644 src/requests/form_builder.rs delete mode 100644 src/requests/mod.rs delete mode 100644 src/requests/utils.rs delete mode 100644 src/types/allowed_update.rs delete mode 100644 src/types/animation.rs delete mode 100644 src/types/audio.rs delete mode 100644 src/types/bot_command.rs delete mode 100644 src/types/callback_game.rs delete mode 100644 src/types/callback_query.rs delete mode 100644 src/types/chat.rs delete mode 100644 src/types/chat_action.rs delete mode 100644 src/types/chat_id.rs delete mode 100644 src/types/chat_member.rs delete mode 100644 src/types/chat_permissions.rs delete mode 100644 src/types/chat_photo.rs delete mode 100644 src/types/chosen_inline_result.rs delete mode 100644 src/types/contact.rs delete mode 100644 src/types/dice.rs delete mode 100644 src/types/dice_emoji.rs delete mode 100644 src/types/document.rs delete mode 100644 src/types/encrypted_credentials.rs delete mode 100644 src/types/encrypted_passport_element.rs delete mode 100644 src/types/file.rs delete mode 100644 src/types/force_reply.rs delete mode 100644 src/types/game.rs delete mode 100644 src/types/game_high_score.rs delete mode 100644 src/types/inline_keyboard_button.rs delete mode 100644 src/types/inline_keyboard_markup.rs delete mode 100644 src/types/inline_query.rs delete mode 100644 src/types/inline_query_result.rs delete mode 100644 src/types/inline_query_result_article.rs delete mode 100644 src/types/inline_query_result_audio.rs delete mode 100644 src/types/inline_query_result_cached_audio.rs delete mode 100644 src/types/inline_query_result_cached_document.rs delete mode 100644 src/types/inline_query_result_cached_gif.rs delete mode 100644 src/types/inline_query_result_cached_mpeg4_gif.rs delete mode 100644 src/types/inline_query_result_cached_photo.rs delete mode 100644 src/types/inline_query_result_cached_sticker.rs delete mode 100644 src/types/inline_query_result_cached_video.rs delete mode 100644 src/types/inline_query_result_cached_voice.rs delete mode 100644 src/types/inline_query_result_contact.rs delete mode 100644 src/types/inline_query_result_document.rs delete mode 100644 src/types/inline_query_result_game.rs delete mode 100644 src/types/inline_query_result_gif.rs delete mode 100644 src/types/inline_query_result_location.rs delete mode 100644 src/types/inline_query_result_mpeg4_gif.rs delete mode 100644 src/types/inline_query_result_photo.rs delete mode 100644 src/types/inline_query_result_venue.rs delete mode 100644 src/types/inline_query_result_video.rs delete mode 100644 src/types/inline_query_result_voice.rs delete mode 100644 src/types/input_file.rs delete mode 100644 src/types/input_media.rs delete mode 100644 src/types/input_message_content.rs delete mode 100644 src/types/invoice.rs delete mode 100644 src/types/keyboard_button.rs delete mode 100644 src/types/keyboard_button_poll_type.rs delete mode 100644 src/types/label_price.rs delete mode 100644 src/types/location.rs delete mode 100644 src/types/login_url.rs delete mode 100644 src/types/mask_position.rs delete mode 100644 src/types/me.rs delete mode 100644 src/types/message.rs delete mode 100644 src/types/message_entity.rs delete mode 100644 src/types/mod.rs delete mode 100644 src/types/non_telegram_types/country_code.rs delete mode 100644 src/types/non_telegram_types/currency.rs delete mode 100644 src/types/non_telegram_types/mime_wrapper.rs delete mode 100644 src/types/non_telegram_types/mod.rs delete mode 100644 src/types/order_info.rs delete mode 100644 src/types/parse_mode.rs delete mode 100644 src/types/passport_data.rs delete mode 100644 src/types/passport_element_error.rs delete mode 100644 src/types/passport_file.rs delete mode 100644 src/types/photo_size.rs delete mode 100644 src/types/poll.rs delete mode 100644 src/types/poll_answer.rs delete mode 100644 src/types/poll_type.rs delete mode 100644 src/types/pre_checkout_query.rs delete mode 100644 src/types/reply_keyboard_markup.rs delete mode 100644 src/types/reply_keyboard_remove.rs delete mode 100644 src/types/reply_markup.rs delete mode 100644 src/types/response_parameters.rs delete mode 100644 src/types/send_invoice.rs delete mode 100644 src/types/shipping_address.rs delete mode 100644 src/types/shipping_option.rs delete mode 100644 src/types/shipping_query.rs delete mode 100644 src/types/sticker.rs delete mode 100644 src/types/sticker_set.rs delete mode 100644 src/types/sticker_type.rs delete mode 100644 src/types/successful_payment.rs delete mode 100644 src/types/target_message.rs delete mode 100644 src/types/unit_false.rs delete mode 100644 src/types/unit_true.rs delete mode 100644 src/types/update.rs delete mode 100644 src/types/user.rs delete mode 100644 src/types/user_profile_photos.rs delete mode 100644 src/types/venue.rs delete mode 100644 src/types/video.rs delete mode 100644 src/types/video_note.rs delete mode 100644 src/types/voice.rs delete mode 100644 src/types/webhook_info.rs delete mode 100644 src/utils/client_from_env.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index d4e583b3..6f1a9643 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [unreleased] ### Added + - Integrate [teloxide-core]. - Allow arbitrary error types to be returned from (sub)transitions ([issue 242](https://github.com/teloxide/teloxide/issues/242)). - The `respond` function, a shortcut for `ResponseResult::Ok(())`. - The `sqlite-storage` feature -- enables SQLite support. +### Deprecated + + - `UpdateWithCx::answer_str` + +### Fixed + + - Hide `SubtransitionOutputType` from the docs. + ### Changed - Allow `bot_name` be `N`, where `N: Into + ...` in `commands_repl` & `commands_repl_with_listener`. - 'Edit methods' (namely `edit_message_live_location`, `stop_message_live_location`, `edit_message_text`, diff --git a/Cargo.toml b/Cargo.toml index fce35c78..10f575ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,7 @@ macros = ["teloxide-macros"] nightly = [] # currently used for `README.md` tests and building docs for `docsrs` to add `This is supported on feature="..." only.` [dependencies] -teloxide-core = "0.1" +teloxide-core = { git = "https://github.com/teloxide/teloxide-core.git", rev = "b465da5f1650893cc033d995343858371505eaf1", features = ["full"] } serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } diff --git a/src/bot/api.rs b/src/bot/api.rs deleted file mode 100644 index 10df2dd5..00000000 --- a/src/bot/api.rs +++ /dev/null @@ -1,1733 +0,0 @@ -use crate::{ - requests::{ - AddStickerToSet, AnswerCallbackQuery, AnswerInlineQuery, AnswerPreCheckoutQuery, - AnswerShippingQuery, CreateNewStickerSet, DeleteChatPhoto, DeleteChatStickerSet, - DeleteMessage, DeleteStickerFromSet, DeleteWebhook, EditInlineMessageCaption, - EditInlineMessageLiveLocation, EditInlineMessageMedia, EditInlineMessageReplyMarkup, - EditInlineMessageText, EditMessageCaption, EditMessageLiveLocation, EditMessageMedia, - EditMessageReplyMarkup, EditMessageText, ExportChatInviteLink, ForwardMessage, GetChat, - GetChatAdministrators, GetChatMember, GetChatMembersCount, GetFile, GetGameHighScores, - GetMe, GetMyCommands, GetStickerSet, GetUpdates, GetUserProfilePhotos, GetWebhookInfo, - KickChatMember, LeaveChat, PinChatMessage, PromoteChatMember, RestrictChatMember, - SendAnimation, SendAudio, SendChatAction, SendChatActionKind, SendContact, SendDice, - SendDocument, SendGame, SendInvoice, SendLocation, SendMediaGroup, SendMessage, SendPhoto, - SendPoll, SendSticker, SendVenue, SendVideo, SendVideoNote, SendVoice, - SetChatAdministratorCustomTitle, SetChatDescription, SetChatPermissions, SetChatPhoto, - SetChatStickerSet, SetChatTitle, SetGameScore, SetMyCommands, SetStickerPositionInSet, - SetStickerSetThumb, SetWebhook, StopInlineMessageLiveLocation, StopMessageLiveLocation, - StopPoll, UnbanChatMember, UnpinChatMessage, UploadStickerFile, - }, - types::{ - BotCommand, ChatId, ChatPermissions, InlineQueryResult, InputFile, InputMedia, - LabeledPrice, ParseMode, StickerType, TargetMessage, - }, - Bot, -}; - -impl Bot { - /// Use this method to receive incoming updates using long polling ([wiki]). - /// - /// **Notes:** - /// 1. This method will not work if an outgoing webhook is set up. - /// 2. In order to avoid getting duplicate updates, - /// recalculate offset after each server response. - /// - /// [The official docs](https://core.telegram.org/bots/api#getupdates). - /// - /// [wiki]: https://en.wikipedia.org/wiki/Push_technology#Long_polling - pub fn get_updates(&self) -> GetUpdates { - GetUpdates::new(self.clone()) - } - - /// Use this method to specify a url and receive incoming updates via an - /// outgoing webhook. - /// - /// Whenever there is an update for the bot, we will send an - /// HTTPS POST request to the specified url, containing a JSON-serialized - /// [`Update`]. In case of an unsuccessful request, we will give up after a - /// reasonable amount of attempts. - /// - /// If you'd like to make sure that the Webhook request comes from Telegram, - /// we recommend using a secret path in the URL, e.g. - /// `https://www.example.com/`. Since nobody else knows your bot‘s - /// token, you can be pretty sure it’s us. - /// - /// [The official docs](https://core.telegram.org/bots/api#setwebhook). - /// - /// # Params - /// - `url`: HTTPS url to send updates to. - /// - /// Use an empty string to remove webhook integration. - /// - /// [`Update`]: crate::types::Update - pub fn set_webhook(&self, url: U) -> SetWebhook - where - U: Into, - { - SetWebhook::new(self.clone(), url) - } - - /// Use this method to remove webhook integration if you decide to switch - /// back to [Bot::get_updates]. - /// - /// [The official docs](https://core.telegram.org/bots/api#deletewebhook). - /// - /// [Bot::get_updates]: crate::Bot::get_updates - pub fn delete_webhook(&self) -> DeleteWebhook { - DeleteWebhook::new(self.clone()) - } - - /// Use this method to get current webhook status. - /// - /// If the bot is using [`Bot::get_updates`], will return an object with the - /// url field empty. - /// - /// [The official docs](https://core.telegram.org/bots/api#getwebhookinfo). - /// - /// [`Bot::get_updates`]: crate::Bot::get_updates - pub fn get_webhook_info(&self) -> GetWebhookInfo { - GetWebhookInfo::new(self.clone()) - } - - /// A simple method for testing your bot's auth token. Requires no - /// parameters. - /// - /// [The official docs](https://core.telegram.org/bots/api#getme). - pub fn get_me(&self) -> GetMe { - GetMe::new(self.clone()) - } - - /// Use this method to send text messages. - /// - /// [The official docs](https://core.telegram.org/bots/api#sendmessage). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `text`: Text of the message to be sent. - /// - /// # Notes - /// Uses [a default parse mode] if specified in [`BotBuilder`]. - /// - /// [a default parse mode]: crate::BotBuilder::parse_mode - /// [`BotBuilder`]: crate::BotBuilder - pub fn send_message(&self, chat_id: C, text: T) -> SendMessage - where - C: Into, - T: Into, - { - self.with_default_parse_mode_if_specified( - SendMessage::new(self.clone(), chat_id, text), - SendMessage::parse_mode, - ) - } - - /// Use this method to forward messages of any kind. - /// - /// [`The official docs`](https://core.telegram.org/bots/api#forwardmessage). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `from_chat_id`: Unique identifier for the chat where the original - /// message was sent (or channel username in the format - /// `@channelusername`). - /// - `message_id`: Message identifier in the chat specified in - /// [`from_chat_id`]. - /// - /// [`from_chat_id`]: ForwardMessage::from_chat_id - pub fn forward_message( - &self, - chat_id: C, - from_chat_id: F, - message_id: i32, - ) -> ForwardMessage - where - C: Into, - F: Into, - { - ForwardMessage::new(self.clone(), chat_id, from_chat_id, message_id) - } - - /// Use this method to send photos. - /// - /// [The official docs](https://core.telegram.org/bots/api#sendphoto). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `photo`: Pass [`InputFile::FileId`] to send a photo that exists on - /// the Telegram servers (recommended), pass an [`InputFile::Url`] for - /// Telegram to get a photo from the Internet (5MB max.), pass - /// [`InputFile::File`] to upload a picture from the file system or - /// [`InputFile::Memory`] to upload a photo from memory (10MB max. - /// each). [More info on Sending Files »]. - /// - /// [`InputFile::FileId`]: crate::types::InputFile::FileId - /// [`InputFile::Url`]: crate::types::InputFile::Url - /// [`InputFile::File`]: crate::types::InputFile::File - /// [`InputFile::Memory`]: crate::types::InputFile::Memory - /// - /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files - /// - /// # Notes - /// Uses [a default parse mode] if specified in [`BotBuilder`]. - /// - /// [a default parse mode]: crate::BotBuilder::parse_mode - /// [`BotBuilder`]: crate::BotBuilder - pub fn send_photo(&self, chat_id: C, photo: InputFile) -> SendPhoto - where - C: Into, - { - self.with_default_parse_mode_if_specified( - SendPhoto::new(self.clone(), chat_id, photo), - SendPhoto::parse_mode, - ) - } - - /// Use this method to send audio files - /// - /// [The official docs](https://core.telegram.org/bots/api#sendaudio). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `audio`: Pass [`InputFile::FileId`] to send an audio file that - /// exists on the Telegram servers (recommended), pass an - /// [`InputFile::Url`] for Telegram to get a file from the Internet - /// (20MB max.), pass [`InputFile::File`] to upload a file from the file - /// system or [`InputFile::Memory`] to upload a file from memory (50MB - /// max. each). [More info on Sending Files »]. - /// - /// [`InputFile::FileId`]: crate::types::InputFile::FileId - /// [`InputFile::Url`]: crate::types::InputFile::Url - /// [`InputFile::File`]: crate::types::InputFile::File - /// [`InputFile::Memory`]: crate::types::InputFile::Memory - /// - /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files - /// - /// # Notes - /// Uses [a default parse mode] if specified in [`BotBuilder`]. - /// - /// [a default parse mode]: crate::BotBuilder::parse_mode - /// [`BotBuilder`]: crate::BotBuilder - pub fn send_audio(&self, chat_id: C, audio: InputFile) -> SendAudio - where - C: Into, - { - self.with_default_parse_mode_if_specified( - SendAudio::new(self.clone(), chat_id, audio), - SendAudio::parse_mode, - ) - } - - /// Use this method to send general files. - /// - /// [The official docs](https://core.telegram.org/bots/api#senddocument). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `document`: Pass [`InputFile::FileId`] to send a file that exists on - /// the Telegram servers (recommended), pass an [`InputFile::Url`] for - /// Telegram to get a file from the Internet (20MB max.), pass - /// [`InputFile::File`] to upload a file from the file system or - /// [`InputFile::Memory`] to upload a file from memory (50MB max. each). - /// [More info on Sending Files »]. - /// - /// [`InputFile::FileId`]: crate::types::InputFile::FileId - /// [`InputFile::Url`]: crate::types::InputFile::Url - /// [`InputFile::File`]: crate::types::InputFile::File - /// [`InputFile::Memory`]: crate::types::InputFile::Memory - /// - /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files - /// - /// # Notes - /// Uses [a default parse mode] if specified in [`BotBuilder`]. - /// - /// [a default parse mode]: crate::BotBuilder::parse_mode - /// [`BotBuilder`]: crate::BotBuilder - pub fn send_document(&self, chat_id: C, document: InputFile) -> SendDocument - where - C: Into, - { - self.with_default_parse_mode_if_specified( - SendDocument::new(self.clone(), chat_id, document), - SendDocument::parse_mode, - ) - } - - /// Use this method to send video files, Telegram clients support mp4 videos - /// (other formats may be sent as Document). - /// - /// [The official docs](https://core.telegram.org/bots/api#sendvideo). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `video`: Pass [`InputFile::FileId`] to send a file that exists on - /// the Telegram servers (recommended), pass an [`InputFile::Url`] for - /// Telegram to get a file from the Internet (20MB max.), pass - /// [`InputFile::File`] to upload a file from the file system or - /// [`InputFile::Memory`] to upload a file from memory (50MB max. each). - /// [More info on Sending Files »]. - /// - /// [`InputFile::FileId`]: crate::types::InputFile::FileId - /// [`InputFile::Url`]: crate::types::InputFile::Url - /// [`InputFile::File`]: crate::types::InputFile::File - /// [`InputFile::Memory`]: crate::types::InputFile::Memory - /// - /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files - /// - /// # Notes - /// Uses [a default parse mode] if specified in [`BotBuilder`]. - /// - /// [a default parse mode]: crate::BotBuilder::parse_mode - /// [`BotBuilder`]: crate::BotBuilder - pub fn send_video(&self, chat_id: C, video: InputFile) -> SendVideo - where - C: Into, - { - self.with_default_parse_mode_if_specified( - SendVideo::new(self.clone(), chat_id, video), - SendVideo::parse_mode, - ) - } - - /// Use this method to send animation files (GIF or H.264/MPEG-4 AVC video - /// without sound). - /// - /// [The official docs](https://core.telegram.org/bots/api#sendanimation). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `animation`: Pass [`InputFile::FileId`] to send a file that exists - /// on the Telegram servers (recommended), pass an [`InputFile::Url`] - /// for Telegram to get a file from the Internet (20MB max.), pass - /// [`InputFile::File`] to upload a file from the file system or - /// [`InputFile::Memory`] to upload a file from memory (50MB max. each). - /// [More info on Sending Files »]. - /// - /// [`InputFile::FileId`]: crate::types::InputFile::FileId - /// [`InputFile::Url`]: crate::types::InputFile::Url - /// [`InputFile::File`]: crate::types::InputFile::File - /// [`InputFile::Memory`]: crate::types::InputFile::Memory - /// - /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files - /// - /// # Notes - /// Uses [a default parse mode] if specified in [`BotBuilder`]. - /// - /// [a default parse mode]: crate::BotBuilder::parse_mode - /// [`BotBuilder`]: crate::BotBuilder - pub fn send_animation(&self, chat_id: C, animation: InputFile) -> SendAnimation - where - C: Into, - { - self.with_default_parse_mode_if_specified( - SendAnimation::new(self.clone(), chat_id, animation), - SendAnimation::parse_mode, - ) - } - - /// Use this method to send audio files, if you want Telegram clients to - /// display the file as a playable voice message. - /// - /// For this to work, your audio must be in an .ogg file encoded with OPUS - /// (other formats may be sent as [`Audio`] or [`Document`]). - /// - /// [The official docs](https://core.telegram.org/bots/api#sendvoice). - /// - /// [`Audio`]: crate::types::Audio - /// [`Document`]: crate::types::Document - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `voice`: Pass [`InputFile::FileId`] to send a file that exists on - /// the Telegram servers (recommended), pass an [`InputFile::Url`] for - /// Telegram to get a file from the Internet (20MB max.), pass - /// [`InputFile::File`] to upload a file from the file system or - /// [`InputFile::Memory`] to upload a file from memory (50MB max. each). - /// [More info on Sending Files »]. - /// - /// [`InputFile::FileId`]: crate::types::InputFile::FileId - /// [`InputFile::Url`]: crate::types::InputFile::Url - /// [`InputFile::File`]: crate::types::InputFile::File - /// [`InputFile::Memory`]: crate::types::InputFile::Memory - /// - /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files - /// - /// # Notes - /// Uses [a default parse mode] if specified in [`BotBuilder`]. - /// - /// [a default parse mode]: crate::BotBuilder::parse_mode - /// [`BotBuilder`]: crate::BotBuilder - pub fn send_voice(&self, chat_id: C, voice: InputFile) -> SendVoice - where - C: Into, - { - self.with_default_parse_mode_if_specified( - SendVoice::new(self.clone(), chat_id, voice), - SendVoice::parse_mode, - ) - } - - /// As of [v.4.0], Telegram clients support rounded square mp4 videos of up - /// to 1 minute long. Use this method to send video messages. - /// - /// [The official docs](https://core.telegram.org/bots/api#sendvideonote). - /// - /// [v.4.0]: https://telegram.org/blog/video-messages-and-telescope - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `video_note`: Pass [`InputFile::FileId`] to send a file that exists - /// on the Telegram servers (recommended), pass an [`InputFile::Url`] - /// for Telegram to get a file from the Internet (20MB max.), pass - /// [`InputFile::File`] to upload a file from the file system or - /// [`InputFile::Memory`] to upload a file from memory (50MB max. each). - /// [More info on Sending Files »]. - /// - /// [`InputFile::FileId`]: crate::types::InputFile::FileId - /// [`InputFile::Url`]: crate::types::InputFile::Url - /// [`InputFile::File`]: crate::types::InputFile::File - /// [`InputFile::Memory`]: crate::types::InputFile::Memory - /// - /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files - - pub fn send_video_note(&self, chat_id: C, video_note: InputFile) -> SendVideoNote - where - C: Into, - { - SendVideoNote::new(self.clone(), chat_id, video_note) - } - - /// Use this method to send a group of photos or videos as an album. - /// - /// [The official docs](https://core.telegram.org/bots/api#sendmediagroup). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `media`: A vector of photos and videos as [`InputMedia`] to be sent, - /// must include 2–10 items. - pub fn send_media_group(&self, chat_id: C, media: M) -> SendMediaGroup - where - C: Into, - M: Into>, - { - SendMediaGroup::new(self.clone(), chat_id, media) - } - - /// Use this method to send point on the map. - /// - /// [The official docs](https://core.telegram.org/bots/api#sendlocation). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `latitude`: Latitude of the location. - /// - `longitude`: Latitude of the location. - pub fn send_location(&self, chat_id: C, latitude: f32, longitude: f32) -> SendLocation - where - C: Into, - { - SendLocation::new(self.clone(), chat_id, latitude, longitude) - } - - /// Use this method to edit live location messages. - /// - /// A location can be edited until its live_period expires or editing is - /// explicitly disabled by a call to stopMessageLiveLocation. On success, - /// the edited [`Message`] is returned. - /// - /// [The official docs](https://core.telegram.org/bots/api#editmessagelivelocation). - /// - /// [`Message`]: crate::types::Message - /// - /// # Params - /// - `latitude`: Latitude of new location. - /// - `longitude`: Longitude of new location. - pub fn edit_message_live_location( - &self, - chat_id: C, - message_id: i32, - latitude: f32, - longitude: f32, - ) -> EditMessageLiveLocation - where - C: Into, - { - EditMessageLiveLocation::new(self.clone(), chat_id, message_id, latitude, longitude) - } - - /// Use this method to edit live location messages sent via the bot. - /// - /// A location can be edited until its live_period expires or editing is - /// explicitly disabled by a call to stopMessageLiveLocation. On success, - /// [`True`] is returned. - /// - /// [The official docs](https://core.telegram.org/bots/api#editmessagelivelocation). - /// - /// [`True`]: crate::types::True - /// - /// # Params - /// - `latitude`: Latitude of new location. - /// - `longitude`: Longitude of new location. - pub fn edit_inline_message_live_location( - &self, - inline_message_id: I, - latitude: f32, - longitude: f32, - ) -> EditInlineMessageLiveLocation - where - I: Into, - { - EditInlineMessageLiveLocation::new(self.clone(), inline_message_id, latitude, longitude) - } - - /// Use this method to stop updating a live location message before - /// `live_period` expires. - /// - /// On success, the sent [`Message`] is returned. - /// - /// [The official docs](https://core.telegram.org/bots/api#stopmessagelivelocation). - /// - /// [`Message`]: crate::types::Message - pub fn stop_message_live_location( - &self, - chat_id: C, - message_id: i32, - ) -> StopMessageLiveLocation - where - C: Into, - { - StopMessageLiveLocation::new(self.clone(), chat_id, message_id) - } - - /// Use this method to stop updating a live location message (sent via the - /// bot) before `live_period` expires. - /// - /// On success, [`True`] is returned. - /// - /// [The official docs](https://core.telegram.org/bots/api#stopmessagelivelocation). - /// - /// [`True`]: crate::types::True - pub fn stop_inline_message_live_location( - &self, - inline_message_id: I, - ) -> StopInlineMessageLiveLocation - where - I: Into, - { - StopInlineMessageLiveLocation::new(self.clone(), inline_message_id) - } - - /// Use this method to send information about a venue. - /// - /// [The official docs](https://core.telegram.org/bots/api#sendvenue). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `latitude`: Latitude of the venue. - /// - `longitude`: Longitude of the venue. - /// - `title`: Name of the venue. - /// - `address`: Address of the venue. - pub fn send_venue( - &self, - chat_id: C, - latitude: f32, - longitude: f32, - title: T, - address: A, - ) -> SendVenue - where - C: Into, - T: Into, - A: Into, - { - SendVenue::new(self.clone(), chat_id, latitude, longitude, title, address) - } - - /// Use this method to send phone contacts. - /// - /// [The official docs](https://core.telegram.org/bots/api#sendcontact). - /// - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `phone_number`: Contact's phone number. - /// - `first_name`: Contact's first name. - pub fn send_contact(&self, chat_id: C, phone_number: P, first_name: F) -> SendContact - where - C: Into, - P: Into, - F: Into, - { - SendContact::new(self.clone(), chat_id, phone_number, first_name) - } - - /// Use this method to send a native poll. A native poll can't be sent to a - /// private chat. - /// - /// [The official docs](https://core.telegram.org/bots/api#sendpoll). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `question`: Poll question, 1-255 characters. - /// - `options`: List of answer options, 2-10 strings 1-100 characters - /// each. - /// - /// # Notes - /// Uses [a default parse mode] ([`SendPoll::explanation_parse_mode`]) if - /// specified in [`BotBuilder`]. - /// - /// [a default parse mode]: crate::BotBuilder::parse_mode - /// [`BotBuilder`]: crate::BotBuilder - /// [`SendPoll::explanation_parse_mode`]: - /// [`SendPoll::explanation_parse_mode`]: - /// crate::types::SendPoll::explanation_parse_mode - pub fn send_poll(&self, chat_id: C, question: Q, options: O) -> SendPoll - where - C: Into, - Q: Into, - O: Into>, - { - self.with_default_parse_mode_if_specified( - SendPoll::new(self.clone(), chat_id, question, options), - SendPoll::explanation_parse_mode, - ) - } - - /// Use this method when you need to tell the user that something is - /// happening on the bot's side. - /// - /// The status is set for 5 seconds or less (when a message arrives from - /// your bot, Telegram clients clear its typing status). - /// - /// ## Note - /// Example: The [ImageBot] needs some time to process a request and upload - /// the image. Instead of sending a text message along the lines of - /// “Retrieving image, please wait…”, the bot may use - /// [`Bot::send_chat_action`] with `action = upload_photo`. The user - /// will see a `sending photo` status for the bot. - /// - /// We only recommend using this method when a response from the bot will - /// take a **noticeable** amount of time to arrive. - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - /// [ImageBot]: https://t.me/imagebot - /// [`Bot::send_chat_action`]: crate::Bot::send_chat_action - pub fn send_chat_action(&self, chat_id: C, action: SendChatActionKind) -> SendChatAction - where - C: Into, - { - SendChatAction::new(self.clone(), chat_id, action) - } - - /// Use this method to get a list of profile pictures for a user. - /// - /// [The official docs](https://core.telegram.org/bots/api#getuserprofilephotos). - /// - /// # Params - /// - `user_id`: Unique identifier of the target user. - pub fn get_user_profile_photos(&self, user_id: i32) -> GetUserProfilePhotos { - GetUserProfilePhotos::new(self.clone(), user_id) - } - - /// Use this method to get basic info about a file and prepare it for - /// downloading. - /// - /// For the moment, bots can download files of up to `20MB` in size. - /// - /// The file can then be downloaded via the link - /// `https://api.telegram.org/file/bot/`, where `` - /// is taken from the response. It is guaranteed that the link will be valid - /// for at least `1` hour. When the link expires, a new one can be requested - /// by calling [`GetFile`] again. - /// - /// **Note**: This function may not preserve the original file name and MIME - /// type. You should save the file's MIME type and name (if available) when - /// the [`File`] object is received. - /// - /// [The official docs](https://core.telegram.org/bots/api#getfile). - /// - /// # Params - /// - `file_id`: File identifier to get info about. - /// - /// [`File`]: crate::types::File - /// [`GetFile`]: self::GetFile - pub fn get_file(&self, file_id: F) -> GetFile - where - F: Into, - { - GetFile::new(self.clone(), file_id) - } - - /// Use this method to kick a user from a group, a supergroup or a channel. - /// - /// In the case of supergroups and channels, the user will not be able to - /// return to the group on their own using invite links, etc., unless - /// [unbanned] first. The bot must be an administrator in the chat for - /// this to work and must have the appropriate admin rights. - /// - /// [The official docs](https://core.telegram.org/bots/api#kickchatmember). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `user_id`: Unique identifier of the target user. - /// - /// [unbanned]: crate::Bot::unban_chat_member - pub fn kick_chat_member(&self, chat_id: C, user_id: i32) -> KickChatMember - where - C: Into, - { - KickChatMember::new(self.clone(), chat_id, user_id) - } - - /// Use this method to unban a previously kicked user in a supergroup or - /// channel. The user will **not** return to the group or channel - /// automatically, but will be able to join via link, etc. The bot must - /// be an administrator for this to work. - /// - /// [The official docs](https://core.telegram.org/bots/api#unbanchatmember). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `user_id`: Unique identifier of the target user. - pub fn unban_chat_member(&self, chat_id: C, user_id: i32) -> UnbanChatMember - where - C: Into, - { - UnbanChatMember::new(self.clone(), chat_id, user_id) - } - - /// Use this method to restrict a user in a supergroup. - /// - /// The bot must be an administrator in the supergroup for this to work and - /// must have the appropriate admin rights. Pass `true` for all - /// permissions to lift restrictions from a user. - /// - /// [The official docs](https://core.telegram.org/bots/api#restrictchatmember). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `user_id`: Unique identifier of the target user. - /// - `permissions`: New user permissions. - pub fn restrict_chat_member( - &self, - chat_id: C, - user_id: i32, - permissions: ChatPermissions, - ) -> RestrictChatMember - where - C: Into, - { - RestrictChatMember::new(self.clone(), chat_id, user_id, permissions) - } - - /// Use this method to promote or demote a user in a supergroup or a - /// channel. - /// - /// The bot must be an administrator in the chat for this to work and must - /// have the appropriate admin rights. Pass False for all boolean - /// parameters to demote a user. - /// - /// [The official docs](https://core.telegram.org/bots/api#promotechatmember). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `user_id`: Unique identifier of the target user. - pub fn promote_chat_member(&self, chat_id: C, user_id: i32) -> PromoteChatMember - where - C: Into, - { - PromoteChatMember::new(self.clone(), chat_id, user_id) - } - - /// Use this method to set default chat permissions for all members. - /// - /// The bot must be an administrator in the group or a supergroup for this - /// to work and must have the can_restrict_members admin rights. - /// - /// [The official docs](https://core.telegram.org/bots/api#setchatpermissions). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `permissions`: New default chat permissions. - pub fn set_chat_permissions( - &self, - chat_id: C, - permissions: ChatPermissions, - ) -> SetChatPermissions - where - C: Into, - { - SetChatPermissions::new(self.clone(), chat_id, permissions) - } - - /// Use this method to generate a new invite link for a chat; any previously - /// generated link is revoked. - /// - /// The bot must be an administrator in the chat for this to work and must - /// have the appropriate admin rights. - /// - /// # Note - /// Each administrator in a chat generates their own invite links. Bots - /// can't use invite links generated by other administrators. If you - /// want your bot to work with invite links, it will need to generate - /// its own link using [`Bot::export_chat_invite_link`] – after this the - /// link will become available to the bot via the [`Bot::get_chat`] - /// method. If your bot needs to generate a new invite link replacing - /// its previous one, use [`Bot::export_chat_invite_link`] again. - /// - /// [The official docs](https://core.telegram.org/bots/api#exportchatinvitelink). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - /// [`Bot::export_chat_invite_link`]: crate::Bot::export_chat_invite_link - /// [`Bot::get_chat`]: crate::Bot::get_chat - pub fn export_chat_invite_link(&self, chat_id: C) -> ExportChatInviteLink - where - C: Into, - { - ExportChatInviteLink::new(self.clone(), chat_id) - } - - /// Use this method to set a new profile photo for the chat. - /// - /// Photos can't be changed for private chats. The bot must be an - /// administrator in the chat for this to work and must have the - /// appropriate admin rights. - /// - /// [The official docs](https://core.telegram.org/bots/api#setchatphoto). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `photo`: New chat photo, pass [`InputFile::File`] to upload a file - /// from the file system or [`InputFile::Memory`] to upload a file from - /// memory (10MB max. each). [More info on Sending Files »]. - /// - /// [`InputFile::File`]: crate::types::InputFile::File - /// [`InputFile::Memory`]: crate::types::InputFile::Memory - /// - /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files - pub fn set_chat_photo(&self, chat_id: C, photo: InputFile) -> SetChatPhoto - where - C: Into, - { - SetChatPhoto::new(self.clone(), chat_id, photo) - } - - /// Use this method to delete a chat photo. Photos can't be changed for - /// private chats. The bot must be an administrator in the chat for this - /// to work and must have the appropriate admin rights. - /// - /// [The official docs](https://core.telegram.org/bots/api#deletechatphoto). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - pub fn delete_chat_photo(&self, chat_id: C) -> DeleteChatPhoto - where - C: Into, - { - DeleteChatPhoto::new(self.clone(), chat_id) - } - - /// Use this method to change the title of a chat. - /// - /// Titles can't be changed for private chats. The bot must be an - /// administrator in the chat for this to work and must have the - /// appropriate admin rights. - /// - /// [The official docs](https://core.telegram.org/bots/api#setchattitle). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `title`: New chat title, 1-255 characters. - pub fn set_chat_title(&self, chat_id: C, title: T) -> SetChatTitle - where - C: Into, - T: Into, - { - SetChatTitle::new(self.clone(), chat_id, title) - } - - /// Use this method to change the description of a group, a supergroup or a - /// channel. - /// - /// The bot must be an administrator in the chat for this to work and must - /// have the appropriate admin rights. - /// - /// [The official docs](https://core.telegram.org/bots/api#setchatdescription). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - pub fn set_chat_description(&self, chat_id: C) -> SetChatDescription - where - C: Into, - { - SetChatDescription::new(self.clone(), chat_id) - } - - /// Use this method to pin a message in a group, a supergroup, or a channel. - /// - /// The bot must be an administrator in the chat for this to work and must - /// have the `can_pin_messages` admin right in the supergroup or - /// `can_edit_messages` admin right in the channel. - /// - /// [The official docs](https://core.telegram.org/bots/api#pinchatmessage). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `message_id`: Identifier of a message to pin. - pub fn pin_chat_message(&self, chat_id: C, message_id: i32) -> PinChatMessage - where - C: Into, - { - PinChatMessage::new(self.clone(), chat_id, message_id) - } - - /// Use this method to unpin a message in a group, a supergroup, or a - /// channel. - /// - /// The bot must be an administrator in the chat for this to work and must - /// have the `can_pin_messages` admin right in the supergroup or - /// `can_edit_messages` admin right in the channel. - /// - /// [The official docs](https://core.telegram.org/bots/api#unpinchatmessage). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - pub fn unpin_chat_message(&self, chat_id: C) -> UnpinChatMessage - where - C: Into, - { - UnpinChatMessage::new(self.clone(), chat_id) - } - - /// Use this method for your bot to leave a group, supergroup or channel. - /// - /// [The official docs](https://core.telegram.org/bots/api#leavechat). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - pub fn leave_chat(&self, chat_id: C) -> LeaveChat - where - C: Into, - { - LeaveChat::new(self.clone(), chat_id) - } - - /// Use this method to get up to date information about the chat (current - /// name of the user for one-on-one conversations, current username of a - /// user, group or channel, etc.). - /// - /// [The official docs](https://core.telegram.org/bots/api#getchat). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - pub fn get_chat(&self, chat_id: C) -> GetChat - where - C: Into, - { - GetChat::new(self.clone(), chat_id) - } - - /// Use this method to get a list of administrators in a chat. - /// - /// If the chat is a group or a supergroup and no administrators were - /// appointed, only the creator will be returned. - /// - /// [The official docs](https://core.telegram.org/bots/api#getchatadministrators). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - pub fn get_chat_administrators(&self, chat_id: C) -> GetChatAdministrators - where - C: Into, - { - GetChatAdministrators::new(self.clone(), chat_id) - } - - /// Use this method to get the number of members in a chat. - /// - /// [The official docs](https://core.telegram.org/bots/api#getchatmemberscount). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - pub fn get_chat_members_count(&self, chat_id: C) -> GetChatMembersCount - where - C: Into, - { - GetChatMembersCount::new(self.clone(), chat_id) - } - - /// Use this method to get information about a member of a chat. - /// - /// [The official docs](https://core.telegram.org/bots/api#getchatmember). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `user_id`: Unique identifier of the target user. - pub fn get_chat_member(&self, chat_id: C, user_id: i32) -> GetChatMember - where - C: Into, - { - GetChatMember::new(self.clone(), chat_id, user_id) - } - - /// Use this method to set a new group sticker set for a supergroup. - /// - /// The bot must be an administrator in the chat for this to work and must - /// have the appropriate admin rights. Use the field can_set_sticker_set - /// optionally returned in getChat requests to check if the bot can use - /// this method. - /// - /// [The official docs](https://core.telegram.org/bots/api#setchatstickerset). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup (in the format `@supergroupusername`). - /// - `sticker_set_name`: Name of the sticker set to be set as the group - /// sticker set. - pub fn set_chat_sticker_set(&self, chat_id: C, sticker_set_name: S) -> SetChatStickerSet - where - C: Into, - S: Into, - { - SetChatStickerSet::new(self.clone(), chat_id, sticker_set_name) - } - - /// Use this method to delete a group sticker set from a supergroup. - /// - /// The bot must be an administrator in the chat for this to work and must - /// have the appropriate admin rights. Use the field - /// `can_set_sticker_set` optionally returned in [`Bot::get_chat`] - /// requests to check if the bot can use this method. - /// - /// [The official docs](https://core.telegram.org/bots/api#deletechatstickerset). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup (in the format `@supergroupusername`). - /// - /// [`Bot::get_chat`]: crate::Bot::get_chat - pub fn delete_chat_sticker_set(&self, chat_id: C) -> DeleteChatStickerSet - where - C: Into, - { - DeleteChatStickerSet::new(self.clone(), chat_id) - } - - /// Use this method to send answers to callback queries sent from [inline - /// keyboards]. - /// - /// The answer will be displayed to the user as a notification at - /// the top of the chat screen or as an alert. - /// - /// [The official docs](https://core.telegram.org/bots/api#answercallbackquery). - /// - /// # Params - /// - `callback_query_id`: Unique identifier for the query to be answered. - /// - /// [inline keyboards]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub fn answer_callback_query(&self, callback_query_id: C) -> AnswerCallbackQuery - where - C: Into, - { - AnswerCallbackQuery::new(self.clone(), callback_query_id) - } - - /// Use this method to edit text and game messages. - /// - /// On success, the edited [`Message`] is returned. - /// - /// [The official docs](https://core.telegram.org/bots/api#editmessagetext). - /// - /// [`Message`]: crate::types::Message - /// - /// # Params - /// - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target channel (in the format `@channelusername`). - /// - `message_id`: Identifier of the message to edit. - /// - `text`: New text of the message. - /// - /// # Notes - /// - /// Uses [a default parse mode] if specified in [`BotBuilder`]. - /// - /// [a default parse mode]: crate::BotBuilder::parse_mode - /// [`BotBuilder`]: crate::BotBuilder - pub fn edit_message_text(&self, chat_id: C, message_id: i32, text: T) -> EditMessageText - where - C: Into, - T: Into, - { - match self.parse_mode { - None => EditMessageText::new(self.clone(), chat_id, message_id, text), - Some(parse_mode) => { - EditMessageText::new(self.clone(), chat_id, message_id, text).parse_mode(parse_mode) - } - } - } - - /// Use this method to edit text and game messages sent via the bot. - /// - /// On success, [`True`] is returned. - /// - /// [The official docs](https://core.telegram.org/bots/api#editmessagetext). - /// - /// [`True`]: crate::types::True - /// - /// # Params - /// - /// - `inline_message_id`: Identifier of the inline message. - /// - `text`: New text of the message. - /// - /// # Notes - /// - /// Uses [a default parse mode] if specified in [`BotBuilder`]. - /// - /// [a default parse mode]: crate::BotBuilder::parse_mode - /// [`BotBuilder`]: crate::BotBuilder - pub fn edit_inline_message_text( - &self, - inline_message_id: I, - text: T, - ) -> EditInlineMessageText - where - I: Into, - T: Into, - { - match self.parse_mode { - None => EditInlineMessageText::new(self.clone(), inline_message_id, text), - Some(parse_mode) => EditInlineMessageText::new(self.clone(), inline_message_id, text) - .parse_mode(parse_mode), - } - } - - /// Use this method to edit captions of messages sent via the bot. - /// - /// On success, [`True`] is returned. - /// - /// [The official docs](https://core.telegram.org/bots/api#editmessagecaption). - /// - /// [`True`]: crate::types::True - /// - /// # Notes - /// - /// Uses [a default parse mode] if specified in [`BotBuilder`]. - /// - /// [a default parse mode]: crate::BotBuilder::parse_mode - /// [`BotBuilder`]: crate::BotBuilder - pub fn edit_message_caption(&self, chat_id: C, message_id: i32) -> EditMessageCaption - where - C: Into, - { - match self.parse_mode { - None => EditMessageCaption::new(self.clone(), chat_id, message_id), - Some(parse_mode) => { - EditMessageCaption::new(self.clone(), chat_id, message_id).parse_mode(parse_mode) - } - } - } - - /// Use this method to edit captions of messages sent via the bot. - /// - /// On success, [`True`] is returned. - /// - /// [The official docs](https://core.telegram.org/bots/api#editmessagecaption). - /// - /// [`True`]: crate::types::True - /// - /// # Notes - /// Uses [a default parse mode] if specified in [`BotBuilder`]. - /// - /// [a default parse mode]: crate::BotBuilder::parse_mode - /// [`BotBuilder`]: crate::BotBuilder - pub fn edit_inline_message_caption(&self, inline_message_id: I) -> EditInlineMessageCaption - where - I: Into, - { - match self.parse_mode { - None => EditInlineMessageCaption::new(self.clone(), inline_message_id), - Some(parse_mode) => EditInlineMessageCaption::new(self.clone(), inline_message_id) - .parse_mode(parse_mode), - } - } - - /// Use this method to edit animation, audio, document, photo, or video - /// messages. - /// - /// If a message is a part of a message album, then it can be edited only to - /// a photo or a video. Otherwise, message type can be changed - /// arbitrarily. On success, the edited [`Message`] is returned. - /// - /// [The official docs](https://core.telegram.org/bots/api#editmessagemedia). - /// - /// [`Message`]: crate::types::Message - pub fn edit_message_media( - &self, - chat_id: C, - message_id: i32, - media: InputMedia, - ) -> EditMessageMedia - where - C: Into, - { - EditMessageMedia::new(self.clone(), chat_id, message_id, media) - } - - /// Use this method to edit animation, audio, document, photo, or video - /// messages sent via the bot. - /// - /// If a message is a part of a message album, then it can be edited only to - /// a photo or a video. Otherwise, message type can be changed - /// arbitrarily. When this method is used, new file can't be uploaded. - /// Use previously uploaded file via its `file_id` or specify a URL. On - /// success, [`True`] is returned. - /// - /// [The official docs](https://core.telegram.org/bots/api#editmessagemedia). - /// - /// [`True`]: crate::types::True - pub fn edit_inline_message_media( - &self, - inline_message_id: I, - media: InputMedia, - ) -> EditInlineMessageMedia - where - I: Into, - { - EditInlineMessageMedia::new(self.clone(), inline_message_id, media) - } - - /// Use this method to edit only the reply markup of messages. - /// - /// On success, the edited [`Message`] is returned. - /// - /// [The official docs](https://core.telegram.org/bots/api#editmessagereplymarkup). - /// - /// [`Message`]: crate::types::Message - pub fn edit_message_reply_markup( - &self, - chat_id: C, - message_id: i32, - ) -> EditMessageReplyMarkup - where - C: Into, - { - EditMessageReplyMarkup::new(self.clone(), chat_id, message_id) - } - - /// Use this method to edit only the reply markup of messages sent via the - /// bot. - /// - /// On success, [`True`] is returned. - /// - /// [The official docs](https://core.telegram.org/bots/api#editmessagereplymarkup). - /// - /// [`Message`]: crate::types::Message - /// [`True`]: crate::types::True - pub fn edit_inline_message_reply_markup( - &self, - inline_message_id: I, - ) -> EditInlineMessageReplyMarkup - where - I: Into, - { - EditInlineMessageReplyMarkup::new(self.clone(), inline_message_id) - } - - /// Use this method to stop a poll which was sent by the bot. - /// - /// [The official docs](https://core.telegram.org/bots/api#stoppoll). - /// - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target channel (in the format `@channelusername`). - /// - `message_id`: Identifier of the original message with the poll. - pub fn stop_poll(&self, chat_id: C, message_id: i32) -> StopPoll - where - C: Into, - { - StopPoll::new(self.clone(), chat_id, message_id) - } - - /// Use this method to delete a message, including service messages. - /// - /// The limitations are: - /// - A message can only be deleted if it was sent less than 48 hours ago. - /// - Bots can delete outgoing messages in private chats, groups, and - /// supergroups. - /// - Bots can delete incoming messages in private chats. - /// - Bots granted can_post_messages permissions can delete outgoing - /// messages in channels. - /// - If the bot is an administrator of a group, it can delete any message - /// there. - /// - If the bot has can_delete_messages permission in a supergroup or a - /// channel, it can delete any message there. - /// - /// [The official docs](https://core.telegram.org/bots/api#deletemessage). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target channel (in the format `@channelusername`). - /// - `message_id`: Identifier of the message to delete. - pub fn delete_message(&self, chat_id: C, message_id: i32) -> DeleteMessage - where - C: Into, - { - DeleteMessage::new(self.clone(), chat_id, message_id) - } - - /// Use this method to send static .WEBP or [animated] .TGS stickers. - /// - /// [The official docs](https://core.telegram.org/bots/api#sendsticker). - /// - /// [animated]: https://telegram.org/blog/animated-stickers - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target channel (in the format `@channelusername`). - /// - `sticker`: Pass [`InputFile::FileId`] to send a sticker that exists - /// on the Telegram servers (recommended), pass an [`InputFile::Url`] - /// for Telegram to get a sticker (.WEBP file) from the Internet, pass - /// [`InputFile::File`] to upload a sticker from the file system or - /// [`InputFile::Memory`] to upload a sticker from memory [More info on - /// Sending Files »]. - /// - /// [`InputFile::FileId`]: crate::types::InputFile::FileId - /// [`InputFile::Url`]: crate::types::InputFile::Url - /// [`InputFile::File`]: crate::types::InputFile::File - /// [`InputFile::Memory`]: crate::types::InputFile::Memory - /// - /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files - pub fn send_sticker(&self, chat_id: C, sticker: InputFile) -> SendSticker - where - C: Into, - { - SendSticker::new(self.clone(), chat_id, sticker) - } - - /// Use this method to get a sticker set. - /// - /// [The official docs](https://core.telegram.org/bots/api#getstickerset). - /// - /// # Params - /// - `name`: Name of the sticker set. - pub fn get_sticker_set(&self, name: N) -> GetStickerSet - where - N: Into, - { - GetStickerSet::new(self.clone(), name) - } - - /// Use this method to upload a .png file with a sticker for later use in - /// [`Bot::create_new_sticker_set`] and [`Bot::add_sticker_to_set`] methods - /// (can be used multiple times). - /// - /// [The official docs](https://core.telegram.org/bots/api#uploadstickerfile). - /// - /// # Params - /// - `user_id`: User identifier of sticker file owner. - /// - `png_sticker`: **Png** image with the sticker, must be up to 512 - /// kilobytes in size, dimensions must not exceed 512px, and either - /// width or height must be exactly 512px. Pass [`InputFile::File`] to - /// upload a file from the file system or [`InputFile::Memory`] to - /// upload a file from memory. [More info on Sending Files »]. - /// - /// [`InputFile::File`]: crate::types::InputFile::File - /// [`InputFile::Memory`]: crate::types::InputFile::Memory - /// - /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files - pub fn upload_sticker_file(&self, user_id: i32, png_sticker: InputFile) -> UploadStickerFile { - UploadStickerFile::new(self.clone(), user_id, png_sticker) - } - - /// Use this method to create new sticker set owned by a user. The bot will - /// be able to edit the created sticker set. - /// - /// [The official docs](https://core.telegram.org/bots/api#createnewstickerset). - /// - /// # Params - /// - `user_id`: User identifier of created sticker set owner. - /// - `name`: Short name of sticker set, to be used in `t.me/addstickers/` - /// URLs (e.g., animals). Can contain only english letters, digits and - /// underscores. - /// - /// Must begin with a letter, can't contain consecutive underscores and must - /// end in `_by_`. `` is case insensitive. 1-64 - /// characters. - /// - `title`: Sticker set title, 1-64 characters. - pub fn create_new_sticker_set( - &self, - user_id: i32, - name: N, - title: T, - sticker_type: StickerType, - emojis: E, - ) -> CreateNewStickerSet - where - N: Into, - T: Into, - E: Into, - { - CreateNewStickerSet::new(self.clone(), user_id, name, title, sticker_type, emojis) - } - - /// Use this method to add a new sticker to a set created by the bot. - /// - /// [The official docs](https://core.telegram.org/bots/api#addstickertoset). - /// - /// # Params - /// - `user_id`: User identifier of sticker set owner. - /// - `name`: Sticker set name. - /// - `emojis`: One or more emoji corresponding to the sticker. - pub fn add_sticker_to_set( - &self, - user_id: i32, - name: N, - sticker_type: StickerType, - emojis: E, - ) -> AddStickerToSet - where - N: Into, - E: Into, - { - AddStickerToSet::new(self.clone(), user_id, name, sticker_type, emojis) - } - - /// Use this method to move a sticker in a set created by the bot to a - /// specific position. - /// - /// [The official docs](https://core.telegram.org/bots/api#setstickerpositioninset). - /// - /// # Params - /// - `sticker`: File identifier of the sticker. - /// - `position`: New sticker position in the set, zero-based. - pub fn set_sticker_position_in_set( - &self, - sticker: S, - position: i32, - ) -> SetStickerPositionInSet - where - S: Into, - { - SetStickerPositionInSet::new(self.clone(), sticker, position) - } - - /// Use this method to delete a sticker from a set created by the bot. - /// - /// [The official docs](https://core.telegram.org/bots/api#deletestickerfromset). - /// - /// # Params - /// - `sticker`: File identifier of the sticker. - pub fn delete_sticker_from_set(&self, sticker: S) -> DeleteStickerFromSet - where - S: Into, - { - DeleteStickerFromSet::new(self.clone(), sticker) - } - - /// Use this method to send answers to an inline query. - /// - /// No more than **50** results per query are allowed. - /// - /// [The official docs](https://core.telegram.org/bots/api#answerinlinequery). - /// - /// # Params - /// - `inline_query_id`: Unique identifier for the answered query. - /// - `results`: A JSON-serialized array of results for the inline query. - pub fn answer_inline_query(&self, inline_query_id: I, results: R) -> AnswerInlineQuery - where - I: Into, - R: Into>, - { - AnswerInlineQuery::new(self.clone(), inline_query_id, results) - } - - /// Use this method to send invoices. - /// - /// [The official docs](https://core.telegram.org/bots/api#sendinvoice). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target private chat. - /// - `title`: Product name, 1-32 characters. - /// - `description`: Product description, 1-255 characters. - /// - `payload`: Bot-defined invoice payload, 1-128 bytes. This will not - /// be displayed to the user, use for your internal processes. - /// - `provider_token`: Payments provider token, obtained via - /// [@Botfather]. - /// - `start_parameter`: Unique deep-linking parameter that can be used to - /// generate this invoice when used as a start parameter. - /// - `currency`: Three-letter ISO 4217 currency code, see [more on - /// currencies]. - /// - `prices`: Price breakdown, a list of components (e.g. product price, - /// tax, discount, delivery cost, delivery tax, bonus, etc.). - /// - /// [more on currencies]: https://core.telegram.org/bots/payments#supported-currencies - /// [@Botfather]: https://t.me/botfather - #[allow(clippy::too_many_arguments)] - pub fn send_invoice( - &self, - chat_id: i32, - title: T, - description: D, - payload: Pl, - provider_token: Pt, - start_parameter: S, - currency: C, - prices: Pr, - ) -> SendInvoice - where - T: Into, - D: Into, - Pl: Into, - Pt: Into, - S: Into, - C: Into, - Pr: Into>, - { - SendInvoice::new( - self.clone(), - chat_id, - title, - description, - payload, - provider_token, - start_parameter, - currency, - prices, - ) - } - - /// Once the user has confirmed their payment and shipping details, the Bot - /// API sends the final confirmation in the form of an [`Update`] with - /// the field `pre_checkout_query`. Use this method to respond to such - /// pre-checkout queries. Note: The Bot API must receive an answer - /// within 10 seconds after the pre-checkout query was sent. - /// - /// [The official docs](https://core.telegram.org/bots/api#answerprecheckoutquery). - /// - /// # Params - /// - `shipping_query_id`: Unique identifier for the query to be answered. - /// - `ok`: Specify `true` if delivery to the specified address is - /// possible and `false` if there are any problems (for example, if - /// delivery to the specified address is not possible). - /// - /// [`Update`]: crate::types::Update - pub fn answer_shipping_query(&self, shipping_query_id: S, ok: bool) -> AnswerShippingQuery - where - S: Into, - { - AnswerShippingQuery::new(self.clone(), shipping_query_id, ok) - } - - /// Once the user has confirmed their payment and shipping details, the Bot - /// API sends the final confirmation in the form of an [`Update`] with - /// the field `pre_checkout_query`. Use this method to respond to such - /// pre-checkout queries. Note: The Bot API must receive an answer - /// within 10 seconds after the pre-checkout query was sent. - /// - /// [The official docs](https://core.telegram.org/bots/api#answerprecheckoutquery). - /// - /// # Params - /// - `pre_checkout_query_id`: Unique identifier for the query to be - /// answered. - /// - `ok`: Specify `true` if everything is alright (goods are available, - /// etc.) and the bot is ready to proceed with the order. Use False if - /// there are any problems. - /// - /// [`Update`]: crate::types::Update - pub fn answer_pre_checkout_query

( - &self, - pre_checkout_query_id: P, - ok: bool, - ) -> AnswerPreCheckoutQuery - where - P: Into, - { - AnswerPreCheckoutQuery::new(self.clone(), pre_checkout_query_id, ok) - } - - /// Use this method to send a game. - /// - /// [The official docs](https://core.telegram.org/bots/api#sendgame). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat. - /// - `game_short_name`: Short name of the game, serves as the unique - /// identifier for the game. Set up your games via [@Botfather]. - /// - /// [@Botfather]: https://t.me/botfather - pub fn send_game(&self, chat_id: i32, game_short_name: G) -> SendGame - where - G: Into, - { - SendGame::new(self.clone(), chat_id, game_short_name) - } - - /// Use this method to set the score of the specified user in a game. - /// - /// On success, if the message was sent by the bot, returns the edited - /// [`Message`], otherwise returns [`True`]. Returns an error, if the new - /// score is not greater than the user's current score in the chat and - /// force is `false`. - /// - /// [The official docs](https://core.telegram.org/bots/api#setgamescore). - /// - /// # Params - /// - `target`: Target message, either chat id and message id or inline - /// message id. - /// - `user_id`: User identifier. - /// - `score`: New score, must be non-negative. - /// - /// [`Message`]: crate::types::Message - /// [`True`]: crate::types::True - pub fn set_game_score(&self, target: T, user_id: i32, score: i32) -> SetGameScore - where - T: Into, - { - SetGameScore::new(self.clone(), target, user_id, score) - } - - /// Use this method to get data for high score tables. - /// - /// Will return the score of the specified user and several of his neighbors - /// in a game. - /// - /// # Note - /// This method will currently return scores for the target user, plus two - /// of his closest neighbors on each side. Will also return the top - /// three users if the user and his neighbors are not among them. Please - /// note that this behavior is subject to change. - /// - /// [The official docs](https://core.telegram.org/bots/api#getgamehighscores). - /// - /// # Params - /// - `target`: Target message, either chat id and message id or inline - /// message id. - /// - `user_id`: Target user id. - pub fn get_game_high_scores(&self, target: T, user_id: i32) -> GetGameHighScores - where - T: Into, - { - GetGameHighScores::new(self.clone(), target, user_id) - } - - /// Use this method to set a custom title for an administrator in a - /// supergroup promoted by the bot. - /// - /// [The official docs](https://core.telegram.org/bots/api#setchatadministratorcustomtitle). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target channel (in the format `@channelusername`). - /// - `user_id`: Unique identifier of the target user. - /// - `custom_title`: New custom title for the administrator; 0-16 - /// characters, emoji are not allowed. - pub fn set_chat_administrator_custom_title( - &self, - chat_id: C, - user_id: i32, - custom_title: CT, - ) -> SetChatAdministratorCustomTitle - where - C: Into, - CT: Into, - { - SetChatAdministratorCustomTitle::new(self.clone(), chat_id, user_id, custom_title) - } - - /// Use this method to send an animated emoji that will display a random - /// value. - /// - /// [The official docs](https://core.telegram.org/bots/api#senddice). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target channel (in the format `@channelusername`). - pub fn send_dice(&self, chat_id: C) -> SendDice - where - C: Into, - { - SendDice::new(self.clone(), chat_id) - } - - /// Use this method to get the current list of the bot's commands. - /// - /// [The official docs](https://core.telegram.org/bots/api#getmycommands). - pub fn get_my_commands(&self) -> GetMyCommands { - GetMyCommands::new(self.clone()) - } - - /// Use this method to change the list of the bot's commands. - /// - /// [The official docs](https://core.telegram.org/bots/api#setmycommands). - /// - /// # Params - /// - `commands`: A JSON-serialized list of bot commands to be set as the - /// list of the bot's commands. At most 100 commands can be specified. - pub fn set_my_commands(&self, commands: C) -> SetMyCommands - where - C: Into>, - { - SetMyCommands::new(self.clone(), commands) - } - - /// Use this method to set the thumbnail of a sticker set. Animated - /// thumbnails can be set for animated sticker sets only. - /// - /// [The official docs](https://core.telegram.org/bots/api#setstickersetthumb). - /// - /// # Params - /// - `name`: Sticker set name. - /// - `user_id`: User identifier of the sticker set owner. - pub fn set_sticker_set_thumb(&self, name: S, user_id: i32) -> SetStickerSetThumb - where - S: Into, - { - SetStickerSetThumb::new(self.clone(), name, user_id) - } - - fn with_default_parse_mode_if_specified( - &self, - builder: Builder, - f: fn(Builder, ParseMode) -> Builder, - ) -> Builder { - match self.parse_mode { - None => builder, - Some(parse_mode) => f(builder, parse_mode), - } - } -} diff --git a/src/bot/download.rs b/src/bot/download.rs deleted file mode 100644 index 0dec6543..00000000 --- a/src/bot/download.rs +++ /dev/null @@ -1,67 +0,0 @@ -use tokio::io::AsyncWrite; - -#[cfg(feature = "unstable-stream")] -use ::{bytes::Bytes, tokio::stream::Stream}; - -#[cfg(feature = "unstable-stream")] -use crate::net::download_file_stream; -use crate::{bot::Bot, net::download_file, DownloadError}; - -impl Bot { - /// Download a file from Telegram into `destination`. - /// - /// `path` can be obtained from [`Bot::get_file`]. - /// - /// To download as a stream of chunks, see [`Bot::download_file_stream`]. - /// - /// ## Examples - /// - /// ```no_run - /// use teloxide::types::File as TgFile; - /// use tokio::fs::File; - /// # use teloxide::RequestError; - /// use teloxide::{requests::Request, Bot}; - /// - /// # async fn run() -> Result<(), Box> { - /// let bot = Bot::new("TOKEN"); - /// let mut file = File::create("/home/waffle/Pictures/test.png").await?; - /// - /// let TgFile { file_path, .. } = bot.get_file("*file_id*").send().await?; - /// bot.download_file(&file_path, &mut file).await?; - /// # Ok(()) } - /// ``` - /// - /// [`Bot::get_file`]: crate::Bot::get_file - /// [`Bot::download_file_stream`]: crate::Bot::download_file_stream - pub async fn download_file( - &self, - path: &str, - destination: &mut D, - ) -> Result<(), DownloadError> - where - D: AsyncWrite + Unpin, - { - download_file(&self.client, &self.token, path, destination).await - } - - /// Download a file from Telegram. - /// - /// `path` can be obtained from the [`Bot::get_file`]. - /// - /// To download into [`AsyncWrite`] (e.g. [`tokio::fs::File`]), see - /// [`Bot::download_file`]. - /// - /// [`Bot::get_file`]: crate::bot::Bot::get_file - /// [`AsyncWrite`]: tokio::io::AsyncWrite - /// [`tokio::fs::File`]: tokio::fs::File - /// [`Bot::download_file`]: crate::Bot::download_file - #[cfg(feature = "unstable-stream")] - // FIXME(waffle): use `docsrs` here when issue with combine is resolved - #[cfg_attr(all(teloxide_docsrs, feature = "nightly"), doc(cfg(feature = "unstable-stream")))] - pub async fn download_file_stream( - &self, - path: &str, - ) -> Result>, reqwest::Error> { - download_file_stream(&self.client, &self.token, path).await - } -} diff --git a/src/bot/mod.rs b/src/bot/mod.rs deleted file mode 100644 index b936266f..00000000 --- a/src/bot/mod.rs +++ /dev/null @@ -1,252 +0,0 @@ -use crate::types::ParseMode; -use reqwest::{ - header::{HeaderMap, CONNECTION}, - Client, ClientBuilder, -}; -use std::{sync::Arc, time::Duration}; - -mod api; -mod download; - -pub(crate) const TELOXIDE_TOKEN: &str = "TELOXIDE_TOKEN"; -pub(crate) const TELOXIDE_PROXY: &str = "TELOXIDE_PROXY"; - -/// A requests sender. -/// -/// No need to put it into [`Arc`], because it's already in. -/// -/// [`Arc`]: std::sync::Arc -#[derive(Debug, Clone)] -pub struct Bot { - token: Arc, - client: Client, - parse_mode: Option, -} - -impl Bot { - /// Creates new [`BotBuilder`] see it's [docs] for more - /// - /// [docs]: BotBuilder - #[must_use] - pub fn builder() -> BotBuilder { - BotBuilder::new() - } - - /// Creates a new `Bot` with the `TELOXIDE_TOKEN` & `TELOXIDE_PROXY` - /// environmental variables (a bot's token & a proxy) and the default - /// [`reqwest::Client`]. - /// - /// This function passes the value of `TELOXIDE_PROXY` into - /// [`reqwest::Proxy::all`], if it exists, otherwise returns the default - /// client. - /// - /// # Panics - /// - If cannot get the `TELOXIDE_TOKEN` environmental variable. - /// - If it cannot create [`reqwest::Client`]. - /// - /// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html - /// [`reqwest::Proxy::all`]: https://docs.rs/reqwest/latest/reqwest/struct.Proxy.html#method.all - #[must_use] - pub fn from_env() -> Self { - BotBuilder::new().build() - } - - /// Creates a new `Bot` with the `TELOXIDE_TOKEN` environmental variable (a - /// bot's token) and your [`reqwest::Client`]. - /// - /// # Panics - /// If cannot get the `TELOXIDE_TOKEN` environmental variable. - /// - /// # Caution - /// Your custom client might not be configured correctly to be able to work - /// in long time durations, see [issue 223]. - /// - /// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html - /// [issue 223]: https://github.com/teloxide/teloxide/issues/223 - #[deprecated = "Deprecated in favour of BotBuilder because the later provides more options \ - (notably default parse_mode)"] - pub fn from_env_with_client(client: Client) -> Self { - #[allow(deprecated)] - Self::with_client(&get_env(TELOXIDE_TOKEN), client) - } - - /// Creates a new `Bot` with the specified token and the default - /// [`reqwest::Client`]. - /// - /// # Panics - /// If it cannot create [`reqwest::Client`]. - /// - /// [`reqwest::Client`]: https://docs.rs/reqwest/latest/reqwest/struct.Client.html - #[deprecated = "Deprecated in favour of BotBuilder because the later provides more options \ - (notably default parse_mode)"] - pub fn new(token: S) -> Self - where - S: Into, - { - #[allow(deprecated)] - Self::with_client(token, build_sound_bot()) - } - - /// Creates a new `Bot` with the specified token and your - /// [`reqwest::Client`]. - /// - /// # Caution - /// Your custom client might not be configured correctly to be able to work - /// in long time durations, see [issue 223]. - /// - /// [`reqwest::Client`]: https://docs.rs/reqwest/latest/reqwest/struct.Client.html - /// [issue 223]: https://github.com/teloxide/teloxide/issues/223 - #[deprecated = "Deprecated in favour of BotBuilder because the later provides more options \ - (notably default parse_mode)"] - pub fn with_client(token: S, client: Client) -> Self - where - S: Into, - { - Self { - token: Into::>::into(Into::::into(token)), - client, - parse_mode: None, - } - } -} - -/// Returns a builder with safe settings. -/// -/// By "safe settings" I mean that a client will be able to work in long time -/// durations, see the [issue 223]. -/// -/// [issue 223]: https://github.com/teloxide/teloxide/issues/223 -pub(crate) fn sound_bot() -> ClientBuilder { - let mut headers = HeaderMap::new(); - headers.insert(CONNECTION, "keep-alive".parse().unwrap()); - - let connect_timeout = Duration::from_secs(5); - let timeout = 10; - - ClientBuilder::new() - .connect_timeout(connect_timeout) - .timeout(Duration::from_secs(connect_timeout.as_secs() + timeout + 2)) - .tcp_nodelay(true) - .default_headers(headers) -} - -pub(crate) fn build_sound_bot() -> Client { - sound_bot().build().expect("creating reqwest::Client") -} - -fn get_env(env: &'static str) -> String { - std::env::var(env).unwrap_or_else(|_| panic!("Cannot get the {} env variable", env)) -} - -impl Bot { - // TODO: const fn - pub fn token(&self) -> &str { - &self.token - } - - // TODO: const fn - pub fn client(&self) -> &Client { - &self.client - } -} - -/// A builder of [`Bot`], supporting some extra settings. -/// -/// [`Bot`]: crate::Bot -#[derive(Debug, Default)] -pub struct BotBuilder { - token: Option, - client: Option, - parse_mode: Option, -} - -impl BotBuilder { - #[must_use] - pub fn new() -> Self { - Self::default() - } - - /// Specifies a custom HTTPS client. Otherwise, the default will be used. - /// - /// # Caution - /// - Your custom client might not be configured correctly to be able to - /// work - /// in long time durations, see [issue 223]. - /// - /// - If this method is used, the `TELOXIDE_PROXY` environmental variable - /// won't be extracted in [`BotBuilder::build`]. - /// - /// [issue 223]: https://github.com/teloxide/teloxide/issues/223 - /// [`BotBuilder::build`]: crate::BotBuilder::build - #[must_use] - pub fn client(mut self, client: Client) -> Self { - self.client = Some(client); - self - } - - /// Specified a custom token. - /// - /// Otherwise, a token will be extracted from the `TELOXIDE_TOKEN` - /// environmental variable. - #[must_use] - pub fn token(mut self, token: S) -> Self - where - S: Into, - { - self.token = Some(token.into()); - self - } - - /// Specifies [`ParseMode`], which will be used during all calls to: - /// - /// - [`send_message`] - /// - [`send_photo`] - /// - [`send_video`] - /// - [`send_audio`] - /// - [`send_document`] - /// - [`send_animation`] - /// - [`send_voice`] - /// - [`send_poll`] - /// - [`edit_message_text`] - /// - [`edit_message_caption`] - /// - /// [`send_message`]: crate::Bot::send_message - /// [`send_photo`]: crate::Bot::send_photo - /// [`send_video`]: crate::Bot::send_video - /// [`send_audio`]: crate::Bot::send_audio - /// [`send_document`]: crate::Bot::send_document - /// [`send_animation`]: crate::Bot::send_animation - /// [`send_voice`]: crate::Bot::send_voice - /// [`send_poll`]: crate::Bot::send_poll - /// [`edit_message_text`]: crate::Bot::edit_message_text - /// [`edit_message_caption`]: crate::Bot::edit_message_caption - #[must_use] - pub fn parse_mode(mut self, parse_mode: ParseMode) -> Self { - self.parse_mode = Some(parse_mode); - self - } - - /// Builds [`Bot`]. - /// - /// This method will attempt to build a new client with a proxy, specified - /// in the `TELOXIDE_PROXY` (passed into [`reqwest::Proxy::all`]) - /// environmental variable, if a client haven't been specified. If - /// `TELOXIDE_PROXY` is unspecified, it'll use no proxy. - /// - /// # Panics - /// - If cannot get the `TELOXIDE_TOKEN` environmental variable. - /// - If it cannot create [`reqwest::Client`]. - /// - /// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html - /// - /// [`Bot`]: crate::Bot - /// [`reqwest::Proxy::all`]: https://docs.rs/reqwest/latest/reqwest/struct.Proxy.html#method.all - #[must_use] - pub fn build(self) -> Bot { - Bot { - client: self.client.unwrap_or_else(crate::utils::client_from_env), - token: self.token.unwrap_or_else(|| get_env(TELOXIDE_TOKEN)).into(), - parse_mode: self.parse_mode, - } - } -} diff --git a/src/dispatching/dialogue/dialogue_dispatcher.rs b/src/dispatching/dialogue/dialogue_dispatcher.rs index e555d84e..2e722d70 100644 --- a/src/dispatching/dialogue/dialogue_dispatcher.rs +++ b/src/dispatching/dialogue/dialogue_dispatcher.rs @@ -11,6 +11,7 @@ use tokio::sync::mpsc; use lockfree::map::Map; use std::sync::{Arc, Mutex}; +use teloxide_core::requests::Requester; use tokio_stream::wrappers::UnboundedReceiverStream; /// A dispatcher of dialogues. @@ -23,7 +24,7 @@ use tokio_stream::wrappers::UnboundedReceiverStream; /// /// [`Dispatcher`]: crate::dispatching::Dispatcher /// [`DispatcherHandler`]: crate::dispatching::DispatcherHandler -pub struct DialogueDispatcher { +pub struct DialogueDispatcher { storage: Arc, handler: Arc, _phantom: PhantomData>, @@ -34,12 +35,12 @@ pub struct DialogueDispatcher { /// A value is the TX part of an unbounded asynchronous MPSC channel. A /// handler that executes updates from the same chat ID sequentially /// handles the RX part. - senders: Arc>>>, + senders: Arc>>>, } -impl DialogueDispatcher, H, Upd> +impl DialogueDispatcher, H, Upd> where - H: DialogueDispatcherHandler + Send + Sync + 'static, + H: DialogueDispatcherHandler + Send + Sync + 'static, Upd: GetChatId + Send + 'static, D: Default + Send + 'static, { @@ -58,9 +59,9 @@ where } } -impl DialogueDispatcher +impl DialogueDispatcher where - H: DialogueDispatcherHandler + Send + Sync + 'static, + H: DialogueDispatcherHandler + Send + Sync + 'static, Upd: GetChatId + Send + 'static, D: Default + Send + 'static, S: Storage + Send + Sync + 'static, @@ -78,14 +79,17 @@ where } #[must_use] - fn new_tx(&self) -> mpsc::UnboundedSender> { + fn new_tx(&self) -> mpsc::UnboundedSender> + where + R: Requester + Send + 'static, + { let (tx, rx) = mpsc::unbounded_channel(); let storage = Arc::clone(&self.storage); let handler = Arc::clone(&self.handler); let senders = Arc::clone(&self.senders); - tokio::spawn(UnboundedReceiverStream::new(rx).for_each(move |cx: UpdateWithCx| { + tokio::spawn(UnboundedReceiverStream::new(rx).for_each(move |cx: UpdateWithCx| { let storage = Arc::clone(&storage); let handler = Arc::clone(&handler); let senders = Arc::clone(&senders); @@ -124,17 +128,21 @@ where } } -impl DispatcherHandler for DialogueDispatcher +impl DispatcherHandler for DialogueDispatcher where - H: DialogueDispatcherHandler + Send + Sync + 'static, + H: DialogueDispatcherHandler + Send + Sync + 'static, Upd: GetChatId + Send + 'static, D: Default + Send + 'static, S: Storage + Send + Sync + 'static, S::Error: Send + 'static, + R: Requester + Send, { - fn handle(self, updates: mpsc::UnboundedReceiver>) -> BoxFuture<'static, ()> + fn handle( + self, + updates: mpsc::UnboundedReceiver>, + ) -> BoxFuture<'static, ()> where - UpdateWithCx: 'static, + UpdateWithCx: 'static, { let this = Arc::new(self); diff --git a/src/dispatching/dialogue/dialogue_dispatcher_handler.rs b/src/dispatching/dialogue/dialogue_dispatcher_handler.rs index 1a596bcc..827809ea 100644 --- a/src/dispatching/dialogue/dialogue_dispatcher_handler.rs +++ b/src/dispatching/dialogue/dialogue_dispatcher_handler.rs @@ -8,24 +8,32 @@ use std::{future::Future, sync::Arc}; /// overview](crate::dispatching::dialogue). /// /// [`DialogueDispatcher`]: crate::dispatching::dialogue::DialogueDispatcher -pub trait DialogueDispatcherHandler { +pub trait DialogueDispatcherHandler { #[must_use] fn handle( self: Arc, - cx: DialogueWithCx, + cx: DialogueWithCx, ) -> BoxFuture<'static, DialogueStage> where - DialogueWithCx: Send + 'static; + DialogueWithCx: Send + 'static, + R: Send, + Upd: Send, + D: Send, + E: Send; } -impl DialogueDispatcherHandler for F +impl DialogueDispatcherHandler for F where - F: Fn(DialogueWithCx) -> Fut + Send + Sync + 'static, + F: Fn(DialogueWithCx) -> Fut + Send + Sync + 'static, Fut: Future> + Send + 'static, { - fn handle(self: Arc, cx: DialogueWithCx) -> BoxFuture<'static, Fut::Output> + fn handle(self: Arc, cx: DialogueWithCx) -> BoxFuture<'static, Fut::Output> where - DialogueWithCx: Send + 'static, + DialogueWithCx: Send + 'static, + R: Send, + Upd: Send, + D: Send, + E: Send, { Box::pin(async move { self(cx).await }) } diff --git a/src/dispatching/dialogue/dialogue_with_cx.rs b/src/dispatching/dialogue/dialogue_with_cx.rs index 90ab4556..8b718122 100644 --- a/src/dispatching/dialogue/dialogue_with_cx.rs +++ b/src/dispatching/dialogue/dialogue_with_cx.rs @@ -1,5 +1,6 @@ use crate::dispatching::{dialogue::GetChatId, UpdateWithCx}; use std::fmt::Debug; +use teloxide_core::requests::Requester; /// A context of a [`DialogueDispatcher`]'s message handler. /// @@ -8,21 +9,22 @@ use std::fmt::Debug; /// /// [`DialogueDispatcher`]: crate::dispatching::dialogue::DialogueDispatcher #[derive(Debug)] -pub struct DialogueWithCx { - pub cx: UpdateWithCx, +pub struct DialogueWithCx { + pub cx: UpdateWithCx, pub dialogue: Result, } -impl DialogueWithCx { +impl DialogueWithCx { /// Creates a new instance with the provided fields. - pub fn new(cx: UpdateWithCx, dialogue: D) -> Self { + pub fn new(cx: UpdateWithCx, dialogue: D) -> Self { Self { cx, dialogue: Ok(dialogue) } } } -impl GetChatId for DialogueWithCx +impl GetChatId for DialogueWithCx where Upd: GetChatId, + R: Requester, { fn chat_id(&self) -> i64 { self.cx.update.chat_id() diff --git a/src/dispatching/dialogue/get_chat_id.rs b/src/dispatching/dialogue/get_chat_id.rs index d7e64206..9d492a43 100644 --- a/src/dispatching/dialogue/get_chat_id.rs +++ b/src/dispatching/dialogue/get_chat_id.rs @@ -1,4 +1,4 @@ -use crate::types::Message; +use teloxide_core::types::Message; /// Something that has a chat ID. pub trait GetChatId { diff --git a/src/dispatching/dialogue/storage/redis_storage.rs b/src/dispatching/dialogue/storage/redis_storage.rs index a8514355..31a358e8 100644 --- a/src/dispatching/dialogue/storage/redis_storage.rs +++ b/src/dispatching/dialogue/storage/redis_storage.rs @@ -91,14 +91,13 @@ where Box::pin(async move { let dialogue = self.serializer.serialize(&dialogue).map_err(RedisStorageError::SerdeError)?; - Ok(self - .conn + self.conn .lock() .await .getset::<_, Vec, Option>>(chat_id, dialogue) .await? .map(|d| self.serializer.deserialize(&d).map_err(RedisStorageError::SerdeError)) - .transpose()?) + .transpose() }) } } diff --git a/src/dispatching/dialogue/storage/sqlite_storage.rs b/src/dispatching/dialogue/storage/sqlite_storage.rs index ca68b693..244da6d0 100644 --- a/src/dispatching/dialogue/storage/sqlite_storage.rs +++ b/src/dispatching/dialogue/storage/sqlite_storage.rs @@ -114,7 +114,7 @@ where } #[derive(sqlx::FromRow)] -struct DialogueDBRow { +struct DialogueDbRow { dialogue: Vec, } @@ -123,7 +123,7 @@ async fn get_dialogue( chat_id: i64, ) -> Result>>, sqlx::Error> { Ok( - match sqlx::query_as::<_, DialogueDBRow>( + match sqlx::query_as::<_, DialogueDbRow>( "SELECT dialogue FROM teloxide_dialogues WHERE chat_id = ?", ) .bind(chat_id) diff --git a/src/dispatching/dialogue/transition.rs b/src/dispatching/dialogue/transition.rs index 3150db98..5674ae3c 100644 --- a/src/dispatching/dialogue/transition.rs +++ b/src/dispatching/dialogue/transition.rs @@ -1,20 +1,19 @@ -use crate::{ - dispatching::{dialogue::DialogueStage, UpdateWithCx}, - types::Message, -}; +use crate::dispatching::{dialogue::DialogueStage, UpdateWithCx}; use futures::future::BoxFuture; +use teloxide_core::types::Message; /// Represents a transition function of a dialogue FSM. pub trait Transition: Sized { type Aux; type Error; + type Requester; /// Turns itself into another state, depending on the input message. /// /// `aux` will be passed to each subtransition function. fn react( self, - cx: TransitionIn, + cx: TransitionIn, aux: Self::Aux, ) -> BoxFuture<'static, TransitionOut>; } @@ -29,6 +28,7 @@ where type Aux; type Dialogue; type Error; + type Requester; /// Turns itself into another state, depending on the input message. /// @@ -36,7 +36,7 @@ where /// message's text. fn react( self, - cx: TransitionIn, + cx: TransitionIn, aux: Self::Aux, ) -> BoxFuture<'static, TransitionOut>; } @@ -44,6 +44,7 @@ where /// A type returned from a FSM subtransition function. /// /// Now it is used only inside `#[teloxide(subtransition)]` for type inference. +#[doc(hidden)] pub trait SubtransitionOutputType { type Output; type Error; @@ -55,7 +56,7 @@ impl SubtransitionOutputType for TransitionOut { } /// An input passed into a FSM (sub)transition function. -pub type TransitionIn = UpdateWithCx; +pub type TransitionIn = UpdateWithCx; /// A type returned from a FSM (sub)transition function. pub type TransitionOut = Result, E>; diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index 95b067c2..fa5eb7de 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -3,34 +3,37 @@ use crate::{ update_listeners, update_listeners::UpdateListener, DispatcherHandler, UpdateWithCx, }, error_handlers::{ErrorHandler, LoggingErrorHandler}, +}; +use futures::StreamExt; +use std::{fmt::Debug, sync::Arc}; +use teloxide_core::{ + requests::Requester, types::{ CallbackQuery, ChosenInlineResult, InlineQuery, Message, Poll, PollAnswer, PreCheckoutQuery, ShippingQuery, UpdateKind, }, - Bot, }; -use futures::StreamExt; -use std::{fmt::Debug, sync::Arc}; use tokio::sync::mpsc; -type Tx = Option>>; +type Tx = Option>>; #[macro_use] mod macros { /// Pushes an update to a queue. macro_rules! send { - ($bot:expr, $tx:expr, $update:expr, $variant:expr) => { - send($bot, $tx, $update, stringify!($variant)); + ($requester:expr, $tx:expr, $update:expr, $variant:expr) => { + send($requester, $tx, $update, stringify!($variant)); }; } } -fn send<'a, Upd>(bot: &'a Bot, tx: &'a Tx, update: Upd, variant: &'static str) +fn send<'a, R, Upd>(requester: &'a R, tx: &'a Tx, update: Upd, variant: &'static str) where Upd: Debug, + R: Requester + Clone, { if let Some(tx) = tx { - if let Err(error) = tx.send(UpdateWithCx { bot: bot.clone(), update }) { + if let Err(error) = tx.send(UpdateWithCx { requester: requester.clone(), update }) { log::error!( "The RX part of the {} channel is closed, but an update is received.\nError:{}\n", variant, @@ -44,28 +47,31 @@ where /// /// See the [module-level documentation](crate::dispatching) for the design /// overview. -pub struct Dispatcher { - bot: Bot, +pub struct Dispatcher { + requester: R, - messages_queue: Tx, - edited_messages_queue: Tx, - channel_posts_queue: Tx, - edited_channel_posts_queue: Tx, - inline_queries_queue: Tx, - chosen_inline_results_queue: Tx, - callback_queries_queue: Tx, - shipping_queries_queue: Tx, - pre_checkout_queries_queue: Tx, - polls_queue: Tx, - poll_answers_queue: Tx, + messages_queue: Tx, + edited_messages_queue: Tx, + channel_posts_queue: Tx, + edited_channel_posts_queue: Tx, + inline_queries_queue: Tx, + chosen_inline_results_queue: Tx, + callback_queries_queue: Tx, + shipping_queries_queue: Tx, + pre_checkout_queries_queue: Tx, + polls_queue: Tx, + poll_answers_queue: Tx, } -impl Dispatcher { - /// Constructs a new dispatcher with the specified `bot`. +impl Dispatcher +where + R: Send + 'static, +{ + /// Constructs a new dispatcher with the specified `requester`. #[must_use] - pub fn new(bot: Bot) -> Self { + pub fn new(requester: R) -> Self { Self { - bot, + requester, messages_queue: None, edited_messages_queue: None, channel_posts_queue: None, @@ -81,10 +87,12 @@ impl Dispatcher { } #[must_use] - fn new_tx(&self, h: H) -> Tx + #[allow(clippy::unnecessary_wraps)] + fn new_tx(&self, h: H) -> Tx where - H: DispatcherHandler + Send + 'static, + H: DispatcherHandler + Send + 'static, Upd: Send + 'static, + R: Send + 'static, { let (tx, rx) = mpsc::unbounded_channel(); tokio::spawn(async move { @@ -97,7 +105,7 @@ impl Dispatcher { #[must_use] pub fn messages_handler(mut self, h: H) -> Self where - H: DispatcherHandler + 'static + Send, + H: DispatcherHandler + 'static + Send, { self.messages_queue = self.new_tx(h); self @@ -106,7 +114,7 @@ impl Dispatcher { #[must_use] pub fn edited_messages_handler(mut self, h: H) -> Self where - H: DispatcherHandler + 'static + Send, + H: DispatcherHandler + 'static + Send, { self.edited_messages_queue = self.new_tx(h); self @@ -115,7 +123,7 @@ impl Dispatcher { #[must_use] pub fn channel_posts_handler(mut self, h: H) -> Self where - H: DispatcherHandler + 'static + Send, + H: DispatcherHandler + 'static + Send, { self.channel_posts_queue = self.new_tx(h); self @@ -124,7 +132,7 @@ impl Dispatcher { #[must_use] pub fn edited_channel_posts_handler(mut self, h: H) -> Self where - H: DispatcherHandler + 'static + Send, + H: DispatcherHandler + 'static + Send, { self.edited_channel_posts_queue = self.new_tx(h); self @@ -133,7 +141,7 @@ impl Dispatcher { #[must_use] pub fn inline_queries_handler(mut self, h: H) -> Self where - H: DispatcherHandler + 'static + Send, + H: DispatcherHandler + 'static + Send, { self.inline_queries_queue = self.new_tx(h); self @@ -142,7 +150,7 @@ impl Dispatcher { #[must_use] pub fn chosen_inline_results_handler(mut self, h: H) -> Self where - H: DispatcherHandler + 'static + Send, + H: DispatcherHandler + 'static + Send, { self.chosen_inline_results_queue = self.new_tx(h); self @@ -151,7 +159,7 @@ impl Dispatcher { #[must_use] pub fn callback_queries_handler(mut self, h: H) -> Self where - H: DispatcherHandler + 'static + Send, + H: DispatcherHandler + 'static + Send, { self.callback_queries_queue = self.new_tx(h); self @@ -160,7 +168,7 @@ impl Dispatcher { #[must_use] pub fn shipping_queries_handler(mut self, h: H) -> Self where - H: DispatcherHandler + 'static + Send, + H: DispatcherHandler + 'static + Send, { self.shipping_queries_queue = self.new_tx(h); self @@ -169,7 +177,7 @@ impl Dispatcher { #[must_use] pub fn pre_checkout_queries_handler(mut self, h: H) -> Self where - H: DispatcherHandler + 'static + Send, + H: DispatcherHandler + 'static + Send, { self.pre_checkout_queries_queue = self.new_tx(h); self @@ -178,7 +186,7 @@ impl Dispatcher { #[must_use] pub fn polls_handler(mut self, h: H) -> Self where - H: DispatcherHandler + 'static + Send, + H: DispatcherHandler + 'static + Send, { self.polls_queue = self.new_tx(h); self @@ -187,7 +195,7 @@ impl Dispatcher { #[must_use] pub fn poll_answers_handler(mut self, h: H) -> Self where - H: DispatcherHandler + 'static + Send, + H: DispatcherHandler + 'static + Send, { self.poll_answers_queue = self.new_tx(h); self @@ -197,9 +205,13 @@ impl Dispatcher { /// /// The default parameters are a long polling update listener and log all /// errors produced by this listener). - pub async fn dispatch(&self) { + pub async fn dispatch(&self) + where + R: Requester + Clone, + ::GetUpdatesFaultTolerant: Send, + { self.dispatch_with_listener( - update_listeners::polling_default(self.bot.clone()), + update_listeners::polling_default(self.requester.clone()), LoggingErrorHandler::with_custom_text("An error from the update listener"), ) .await; @@ -215,6 +227,7 @@ impl Dispatcher { UListener: UpdateListener + 'a, Eh: ErrorHandler + 'a, ListenerE: Debug, + R: Requester + Clone, { let update_listener = Box::pin(update_listener); @@ -235,11 +248,16 @@ impl Dispatcher { match update.kind { UpdateKind::Message(message) => { - send!(&self.bot, &self.messages_queue, message, UpdateKind::Message); + send!( + &self.requester, + &self.messages_queue, + message, + UpdateKind::Message + ); } UpdateKind::EditedMessage(message) => { send!( - &self.bot, + &self.requester, &self.edited_messages_queue, message, UpdateKind::EditedMessage @@ -247,7 +265,7 @@ impl Dispatcher { } UpdateKind::ChannelPost(post) => { send!( - &self.bot, + &self.requester, &self.channel_posts_queue, post, UpdateKind::ChannelPost @@ -255,7 +273,7 @@ impl Dispatcher { } UpdateKind::EditedChannelPost(post) => { send!( - &self.bot, + &self.requester, &self.edited_channel_posts_queue, post, UpdateKind::EditedChannelPost @@ -263,7 +281,7 @@ impl Dispatcher { } UpdateKind::InlineQuery(query) => { send!( - &self.bot, + &self.requester, &self.inline_queries_queue, query, UpdateKind::InlineQuery @@ -271,7 +289,7 @@ impl Dispatcher { } UpdateKind::ChosenInlineResult(result) => { send!( - &self.bot, + &self.requester, &self.chosen_inline_results_queue, result, UpdateKind::ChosenInlineResult @@ -279,7 +297,7 @@ impl Dispatcher { } UpdateKind::CallbackQuery(query) => { send!( - &self.bot, + &self.requester, &self.callback_queries_queue, query, UpdateKind::CallbackQuer @@ -287,7 +305,7 @@ impl Dispatcher { } UpdateKind::ShippingQuery(query) => { send!( - &self.bot, + &self.requester, &self.shipping_queries_queue, query, UpdateKind::ShippingQuery @@ -295,18 +313,18 @@ impl Dispatcher { } UpdateKind::PreCheckoutQuery(query) => { send!( - &self.bot, + &self.requester, &self.pre_checkout_queries_queue, query, UpdateKind::PreCheckoutQuery ); } UpdateKind::Poll(poll) => { - send!(&self.bot, &self.polls_queue, poll, UpdateKind::Poll); + send!(&self.requester, &self.polls_queue, poll, UpdateKind::Poll); } UpdateKind::PollAnswer(answer) => { send!( - &self.bot, + &self.requester, &self.poll_answers_queue, answer, UpdateKind::PollAnswer diff --git a/src/dispatching/dispatcher_handler.rs b/src/dispatching/dispatcher_handler.rs index 94ec6dbd..cd05f3cd 100644 --- a/src/dispatching/dispatcher_handler.rs +++ b/src/dispatching/dispatcher_handler.rs @@ -9,21 +9,21 @@ use futures::future::BoxFuture; /// overview. /// /// [`Dispatcher`]: crate::dispatching::Dispatcher -pub trait DispatcherHandler { +pub trait DispatcherHandler { #[must_use] - fn handle(self, updates: DispatcherHandlerRx) -> BoxFuture<'static, ()> + fn handle(self, updates: DispatcherHandlerRx) -> BoxFuture<'static, ()> where - UpdateWithCx: Send + 'static; + UpdateWithCx: Send + 'static; } -impl DispatcherHandler for F +impl DispatcherHandler for F where - F: FnOnce(DispatcherHandlerRx) -> Fut + Send + 'static, + F: FnOnce(DispatcherHandlerRx) -> Fut + Send + 'static, Fut: Future + Send + 'static, { - fn handle(self, updates: DispatcherHandlerRx) -> BoxFuture<'static, ()> + fn handle(self, updates: DispatcherHandlerRx) -> BoxFuture<'static, ()> where - UpdateWithCx: Send + 'static, + UpdateWithCx: Send + 'static, { Box::pin(async move { self(updates).await }) } diff --git a/src/dispatching/dispatcher_handler_rx_ext.rs b/src/dispatching/dispatcher_handler_rx_ext.rs index 6142b133..7a0dddf6 100644 --- a/src/dispatching/dispatcher_handler_rx_ext.rs +++ b/src/dispatching/dispatcher_handler_rx_ext.rs @@ -1,5 +1,6 @@ -use crate::{prelude::UpdateWithCx, types::Message, utils::command::BotCommand}; +use crate::{dispatching::UpdateWithCx, utils::command::BotCommand}; use futures::{stream::BoxStream, Stream, StreamExt}; +use teloxide_core::types::Message; /// An extension trait to be used with [`DispatcherHandlerRx`]. /// @@ -7,37 +8,41 @@ use futures::{stream::BoxStream, Stream, StreamExt}; /// overview. /// /// [`DispatcherHandlerRx`]: crate::dispatching::DispatcherHandlerRx -pub trait DispatcherHandlerRxExt { +pub trait DispatcherHandlerRxExt { /// Extracts only text messages from this stream of arbitrary messages. - fn text_messages(self) -> BoxStream<'static, (UpdateWithCx, String)> + fn text_messages(self) -> BoxStream<'static, (UpdateWithCx, String)> where - Self: Stream>; + Self: Stream>, + R: Send + 'static; /// Extracts only commands with their arguments from this stream of /// arbitrary messages. - fn commands(self, bot_name: N) -> BoxStream<'static, (UpdateWithCx, C)> + fn commands(self, bot_name: N) -> BoxStream<'static, (UpdateWithCx, C)> where - Self: Stream>, + Self: Stream>, C: BotCommand, - N: Into + Send; + N: Into + Send, + R: Send + 'static; } -impl DispatcherHandlerRxExt for T +impl DispatcherHandlerRxExt for T where T: Send + 'static, { - fn text_messages(self) -> BoxStream<'static, (UpdateWithCx, String)> + fn text_messages(self) -> BoxStream<'static, (UpdateWithCx, String)> where - Self: Stream>, + Self: Stream>, + R: Send + 'static, { self.filter_map(|cx| async move { cx.update.text_owned().map(|text| (cx, text)) }).boxed() } - fn commands(self, bot_name: N) -> BoxStream<'static, (UpdateWithCx, C)> + fn commands(self, bot_name: N) -> BoxStream<'static, (UpdateWithCx, C)> where - Self: Stream>, + Self: Stream>, C: BotCommand, N: Into + Send, + R: Send + 'static, { let bot_name = bot_name.into(); diff --git a/src/dispatching/mod.rs b/src/dispatching/mod.rs index 05e367dd..373202f3 100644 --- a/src/dispatching/mod.rs +++ b/src/dispatching/mod.rs @@ -55,9 +55,9 @@ pub use dispatcher::Dispatcher; pub use dispatcher_handler::DispatcherHandler; pub use dispatcher_handler_rx_ext::DispatcherHandlerRxExt; use tokio::sync::mpsc::UnboundedReceiver; -pub use update_with_cx::UpdateWithCx; +pub use update_with_cx::{UpdateWithCx, UpdateWithCxRequesterType}; /// A type of a stream, consumed by [`Dispatcher`]'s handlers. /// /// [`Dispatcher`]: crate::dispatching::Dispatcher -pub type DispatcherHandlerRx = UnboundedReceiver>; +pub type DispatcherHandlerRx = UnboundedReceiver>; diff --git a/src/dispatching/repls/commands_repl.rs b/src/dispatching/repls/commands_repl.rs index cc90ad0c..249d4861 100644 --- a/src/dispatching/repls/commands_repl.rs +++ b/src/dispatching/repls/commands_repl.rs @@ -4,12 +4,11 @@ use crate::{ DispatcherHandlerRxExt, UpdateWithCx, }, error_handlers::{LoggingErrorHandler, OnError}, - types::Message, utils::command::BotCommand, - Bot, }; use futures::StreamExt; use std::{fmt::Debug, future::Future, sync::Arc}; +use teloxide_core::{requests::Requester, types::Message}; use tokio_stream::wrappers::UnboundedReceiverStream; /// A [REPL] for commands. @@ -23,22 +22,24 @@ use tokio_stream::wrappers::UnboundedReceiverStream; /// /// [REPL]: https://en.wikipedia.org/wiki/Read-eval-print_loop /// [`Dispatcher`]: crate::dispatching::Dispatcher -pub async fn commands_repl(bot: Bot, bot_name: N, handler: H) +pub async fn commands_repl(requester: R, bot_name: N, handler: H) where Cmd: BotCommand + Send + 'static, - H: Fn(UpdateWithCx, Cmd) -> Fut + Send + Sync + 'static, + H: Fn(UpdateWithCx, Cmd) -> Fut + Send + Sync + 'static, Fut: Future> + Send + 'static, Result<(), HandlerE>: OnError, HandlerE: Debug + Send, N: Into + Send + 'static, + R: Requester + Send + Clone + 'static, + ::GetUpdatesFaultTolerant: Send, { - let cloned_bot = bot.clone(); + let cloned_requester = requester.clone(); commands_repl_with_listener( - bot, + requester, bot_name, handler, - update_listeners::polling_default(cloned_bot), + update_listeners::polling_default(cloned_requester), ) .await; } @@ -55,25 +56,26 @@ where /// [`Dispatcher`]: crate::dispatching::Dispatcher /// [`commands_repl`]: crate::dispatching::repls::commands_repl() /// [`UpdateListener`]: crate::dispatching::update_listeners::UpdateListener -pub async fn commands_repl_with_listener<'a, Cmd, H, Fut, L, ListenerE, HandlerE, N>( - bot: Bot, +pub async fn commands_repl_with_listener<'a, R, Cmd, H, Fut, L, ListenerE, HandlerE, N>( + requester: R, bot_name: N, handler: H, listener: L, ) where Cmd: BotCommand + Send + 'static, - H: Fn(UpdateWithCx, Cmd) -> Fut + Send + Sync + 'static, + H: Fn(UpdateWithCx, Cmd) -> Fut + Send + Sync + 'static, Fut: Future> + Send + 'static, L: UpdateListener + Send + 'a, ListenerE: Debug + Send + 'a, Result<(), HandlerE>: OnError, HandlerE: Debug + Send, N: Into + Send + 'static, + R: Requester + Clone + Send + 'static, { let handler = Arc::new(handler); - Dispatcher::new(bot) - .messages_handler(move |rx: DispatcherHandlerRx| { + Dispatcher::::new(requester) + .messages_handler(move |rx: DispatcherHandlerRx| { UnboundedReceiverStream::new(rx).commands::(bot_name).for_each_concurrent( None, move |(cx, cmd)| { diff --git a/src/dispatching/repls/dialogues_repl.rs b/src/dispatching/repls/dialogues_repl.rs index 883e74de..706d26a1 100644 --- a/src/dispatching/repls/dialogues_repl.rs +++ b/src/dispatching/repls/dialogues_repl.rs @@ -6,10 +6,9 @@ use crate::{ Dispatcher, UpdateWithCx, }, error_handlers::LoggingErrorHandler, - types::Message, - Bot, }; use std::{convert::Infallible, fmt::Debug, future::Future, sync::Arc}; +use teloxide_core::{requests::Requester, types::Message}; /// A [REPL] for dialogues. /// @@ -24,15 +23,22 @@ use std::{convert::Infallible, fmt::Debug, future::Future, sync::Arc}; /// [REPL]: https://en.wikipedia.org/wiki/Read-eval-print_loop /// [`Dispatcher`]: crate::dispatching::Dispatcher /// [`InMemStorage`]: crate::dispatching::dialogue::InMemStorage -pub async fn dialogues_repl<'a, H, D, Fut>(bot: Bot, handler: H) +pub async fn dialogues_repl<'a, R, H, D, Fut>(requester: R, handler: H) where - H: Fn(UpdateWithCx, D) -> Fut + Send + Sync + 'static, + H: Fn(UpdateWithCx, D) -> Fut + Send + Sync + 'static, D: Default + Send + 'static, Fut: Future> + Send + 'static, + R: Requester + Send + Clone + 'static, + ::GetUpdatesFaultTolerant: Send, { - let cloned_bot = bot.clone(); + let cloned_requester = requester.clone(); - dialogues_repl_with_listener(bot, handler, update_listeners::polling_default(cloned_bot)).await; + dialogues_repl_with_listener( + requester, + handler, + update_listeners::polling_default(cloned_requester), + ) + .await; } /// Like [`dialogues_repl`], but with a custom [`UpdateListener`]. @@ -49,22 +55,23 @@ where /// [`dialogues_repl`]: crate::dispatching::repls::dialogues_repl() /// [`UpdateListener`]: crate::dispatching::update_listeners::UpdateListener /// [`InMemStorage`]: crate::dispatching::dialogue::InMemStorage -pub async fn dialogues_repl_with_listener<'a, H, D, Fut, L, ListenerE>( - bot: Bot, +pub async fn dialogues_repl_with_listener<'a, R, H, D, Fut, L, ListenerE>( + requester: R, handler: H, listener: L, ) where - H: Fn(UpdateWithCx, D) -> Fut + Send + Sync + 'static, + H: Fn(UpdateWithCx, D) -> Fut + Send + Sync + 'static, D: Default + Send + 'static, Fut: Future> + Send + 'static, L: UpdateListener + Send + 'a, ListenerE: Debug + Send + 'a, + R: Requester + Send + Clone + 'static, { let handler = Arc::new(handler); - Dispatcher::new(bot) + Dispatcher::new(requester) .messages_handler(DialogueDispatcher::new( - move |DialogueWithCx { cx, dialogue }: DialogueWithCx| { + move |DialogueWithCx { cx, dialogue }: DialogueWithCx| { let handler = Arc::clone(&handler); async move { diff --git a/src/dispatching/repls/repl.rs b/src/dispatching/repls/repl.rs index 877c6869..31075f60 100644 --- a/src/dispatching/repls/repl.rs +++ b/src/dispatching/repls/repl.rs @@ -4,11 +4,10 @@ use crate::{ UpdateWithCx, }, error_handlers::{LoggingErrorHandler, OnError}, - types::Message, - Bot, }; use futures::StreamExt; use std::{fmt::Debug, future::Future, sync::Arc}; +use teloxide_core::{requests::Requester, types::Message}; use tokio_stream::wrappers::UnboundedReceiverStream; /// A [REPL] for messages. @@ -22,15 +21,18 @@ use tokio_stream::wrappers::UnboundedReceiverStream; /// /// [REPL]: https://en.wikipedia.org/wiki/Read-eval-print_loop /// [`Dispatcher`]: crate::dispatching::Dispatcher -pub async fn repl(bot: Bot, handler: H) +pub async fn repl(requester: R, handler: H) where - H: Fn(UpdateWithCx) -> Fut + Send + Sync + 'static, + H: Fn(UpdateWithCx) -> Fut + Send + Sync + 'static, Fut: Future> + Send + 'static, Result<(), E>: OnError, E: Debug + Send, + R: Requester + Send + Clone + 'static, + ::GetUpdatesFaultTolerant: Send, { - let cloned_bot = bot.clone(); - repl_with_listener(bot, handler, update_listeners::polling_default(cloned_bot)).await; + let cloned_requester = requester.clone(); + repl_with_listener(requester, handler, update_listeners::polling_default(cloned_requester)) + .await; } /// Like [`repl`], but with a custom [`UpdateListener`]. @@ -45,19 +47,23 @@ where /// [`Dispatcher`]: crate::dispatching::Dispatcher /// [`repl`]: crate::dispatching::repls::repl() /// [`UpdateListener`]: crate::dispatching::update_listeners::UpdateListener -pub async fn repl_with_listener<'a, H, Fut, E, L, ListenerE>(bot: Bot, handler: H, listener: L) -where - H: Fn(UpdateWithCx) -> Fut + Send + Sync + 'static, +pub async fn repl_with_listener<'a, R, H, Fut, E, L, ListenerE>( + requester: R, + handler: H, + listener: L, +) where + H: Fn(UpdateWithCx) -> Fut + Send + Sync + 'static, Fut: Future> + Send + 'static, L: UpdateListener + Send + 'a, ListenerE: Debug, Result<(), E>: OnError, E: Debug + Send, + R: Requester + Clone + Send + 'static, { let handler = Arc::new(handler); - Dispatcher::new(bot) - .messages_handler(|rx: DispatcherHandlerRx| { + Dispatcher::new(requester) + .messages_handler(|rx: DispatcherHandlerRx| { UnboundedReceiverStream::new(rx).for_each_concurrent(None, move |message| { let handler = Arc::clone(&handler); diff --git a/src/dispatching/update_listeners.rs b/src/dispatching/update_listeners.rs index 05b30cc7..8f7432c7 100644 --- a/src/dispatching/update_listeners.rs +++ b/src/dispatching/update_listeners.rs @@ -105,14 +105,11 @@ use futures::{stream, Stream, StreamExt}; -use crate::{ - bot::Bot, - requests::Request, - types::{AllowedUpdate, Update}, - RequestError, -}; - use std::{convert::TryInto, time::Duration}; +use teloxide_core::{ + requests::{HasPayload, Request, Requester}, + types::{AllowedUpdate, SemiparsedVec, Update}, +}; /// A generic update listener. pub trait UpdateListener: Stream> { @@ -123,8 +120,12 @@ impl UpdateListener for S where S: Stream> {} /// Returns a long polling update listener with `timeout` of 10 seconds. /// /// See also: [`polling`](polling). -pub fn polling_default(bot: Bot) -> impl UpdateListener { - polling(bot, Some(Duration::from_secs(10)), None, None) +pub fn polling_default(requester: R) -> impl UpdateListener +where + R: Requester, + ::GetUpdatesFaultTolerant: Send, +{ + polling(requester, Some(Duration::from_secs(10)), None, None) } /// Returns a long/short polling update listener with some additional options. @@ -139,25 +140,31 @@ pub fn polling_default(bot: Bot) -> impl UpdateListener { /// See also: [`polling_default`](polling_default). /// /// [`GetUpdates`]: crate::requests::GetUpdates -pub fn polling( - bot: Bot, +pub fn polling( + requester: R, timeout: Option, limit: Option, allowed_updates: Option>, -) -> impl UpdateListener { +) -> impl UpdateListener +where + R: Requester, + ::GetUpdatesFaultTolerant: Send, +{ let timeout = timeout.map(|t| t.as_secs().try_into().expect("timeout is too big")); stream::unfold( - (allowed_updates, bot, 0), + (allowed_updates, requester, 0), move |(mut allowed_updates, bot, mut offset)| async move { - let mut req = bot.get_updates().offset(offset); - req.timeout = timeout; - req.limit = limit; - req.allowed_updates = allowed_updates.take(); + let mut req = bot.get_updates_fault_tolerant(); + let payload = &mut req.payload_mut().0; + payload.offset = Some(offset); + payload.timeout = timeout; + payload.limit = limit; + payload.allowed_updates = allowed_updates.take(); let updates = match req.send().await { Err(err) => vec![Err(err)], - Ok(updates) => { + Ok(SemiparsedVec(updates)) => { // Set offset to the last update's id + 1 if let Some(upd) = updates.last() { let id: i32 = match upd { diff --git a/src/dispatching/update_with_cx.rs b/src/dispatching/update_with_cx.rs index f3fa74e1..6ac78034 100644 --- a/src/dispatching/update_with_cx.rs +++ b/src/dispatching/update_with_cx.rs @@ -1,13 +1,8 @@ -use crate::{ - dispatching::dialogue::GetChatId, - requests::{ - DeleteMessage, EditMessageCaption, EditMessageText, ForwardMessage, PinChatMessage, - Request, ResponseResult, SendAnimation, SendAudio, SendContact, SendDice, SendDocument, - SendLocation, SendMediaGroup, SendMessage, SendPhoto, SendSticker, SendVenue, SendVideo, - SendVideoNote, SendVoice, - }, +use crate::dispatching::dialogue::GetChatId; +use teloxide_core::{ + payloads::SendMessageSetters, + requests::{Request, Requester}, types::{ChatId, InputFile, InputMedia, Message}, - Bot, }; /// A [`Dispatcher`]'s handler's context of a bot and an update. @@ -17,12 +12,12 @@ use crate::{ /// /// [`Dispatcher`]: crate::dispatching::Dispatcher #[derive(Debug)] -pub struct UpdateWithCx { - pub bot: Bot, +pub struct UpdateWithCx { + pub requester: R, pub update: Upd, } -impl GetChatId for UpdateWithCx +impl GetChatId for UpdateWithCx where Upd: GetChatId, { @@ -31,121 +26,136 @@ where } } -impl UpdateWithCx { +#[doc(hidden)] +// Now it is used only inside `#[teloxide(subtransition)]` for type inference. +pub trait UpdateWithCxRequesterType { + type Requester; +} + +impl UpdateWithCxRequesterType for UpdateWithCx { + type Requester = R; +} + +impl UpdateWithCx +where + R: Requester, +{ /// A shortcut for `.answer(text).send().await`. - pub async fn answer_str(&self, text: T) -> ResponseResult + #[deprecated(note = "Use .answer(text).await instead")] + pub async fn answer_str(&self, text: T) -> Result where T: Into, + R::SendMessage: std::future::Future, { self.answer(text).send().await } - pub fn answer(&self, text: T) -> SendMessage + pub fn answer(&self, text: T) -> R::SendMessage where T: Into, { - self.bot.send_message(self.chat_id(), text) + self.requester.send_message(self.chat_id(), text) } - pub fn reply_to(&self, text: T) -> SendMessage + pub fn reply_to(&self, text: T) -> R::SendMessage where T: Into, { - self.bot.send_message(self.chat_id(), text).reply_to_message_id(self.update.id) + self.requester.send_message(self.chat_id(), text).reply_to_message_id(self.update.id) } - pub fn answer_photo(&self, photo: InputFile) -> SendPhoto { - self.bot.send_photo(self.update.chat.id, photo) + pub fn answer_photo(&self, photo: InputFile) -> R::SendPhoto { + self.requester.send_photo(self.update.chat.id, photo) } - pub fn answer_audio(&self, audio: InputFile) -> SendAudio { - self.bot.send_audio(self.update.chat.id, audio) + pub fn answer_audio(&self, audio: InputFile) -> R::SendAudio { + self.requester.send_audio(self.update.chat.id, audio) } - pub fn answer_animation(&self, animation: InputFile) -> SendAnimation { - self.bot.send_animation(self.update.chat.id, animation) + pub fn answer_animation(&self, animation: InputFile) -> R::SendAnimation { + self.requester.send_animation(self.update.chat.id, animation) } - pub fn answer_document(&self, document: InputFile) -> SendDocument { - self.bot.send_document(self.update.chat.id, document) + pub fn answer_document(&self, document: InputFile) -> R::SendDocument { + self.requester.send_document(self.update.chat.id, document) } - pub fn answer_video(&self, video: InputFile) -> SendVideo { - self.bot.send_video(self.update.chat.id, video) + pub fn answer_video(&self, video: InputFile) -> R::SendVideo { + self.requester.send_video(self.update.chat.id, video) } - pub fn answer_voice(&self, voice: InputFile) -> SendVoice { - self.bot.send_voice(self.update.chat.id, voice) + pub fn answer_voice(&self, voice: InputFile) -> R::SendVoice { + self.requester.send_voice(self.update.chat.id, voice) } - pub fn answer_media_group(&self, media_group: T) -> SendMediaGroup + pub fn answer_media_group(&self, media_group: T) -> R::SendMediaGroup where - T: Into>, + T: IntoIterator, { - self.bot.send_media_group(self.update.chat.id, media_group) + self.requester.send_media_group(self.update.chat.id, media_group) } - pub fn answer_location(&self, latitude: f32, longitude: f32) -> SendLocation { - self.bot.send_location(self.update.chat.id, latitude, longitude) + pub fn answer_location(&self, latitude: f64, longitude: f64) -> R::SendLocation { + self.requester.send_location(self.update.chat.id, latitude, longitude) } pub fn answer_venue( &self, - latitude: f32, - longitude: f32, + latitude: f64, + longitude: f64, title: T, address: U, - ) -> SendVenue + ) -> R::SendVenue where T: Into, U: Into, { - self.bot.send_venue(self.update.chat.id, latitude, longitude, title, address) + self.requester.send_venue(self.update.chat.id, latitude, longitude, title, address) } - pub fn answer_video_note(&self, video_note: InputFile) -> SendVideoNote { - self.bot.send_video_note(self.update.chat.id, video_note) + pub fn answer_video_note(&self, video_note: InputFile) -> R::SendVideoNote { + self.requester.send_video_note(self.update.chat.id, video_note) } - pub fn answer_contact(&self, phone_number: T, first_name: U) -> SendContact + pub fn answer_contact(&self, phone_number: T, first_name: U) -> R::SendContact where T: Into, U: Into, { - self.bot.send_contact(self.chat_id(), phone_number, first_name) + self.requester.send_contact(self.chat_id(), phone_number, first_name) } - pub fn answer_sticker(&self, sticker: InputFile) -> SendSticker { - self.bot.send_sticker(self.update.chat.id, sticker) + pub fn answer_sticker(&self, sticker: InputFile) -> R::SendSticker { + self.requester.send_sticker(self.update.chat.id, sticker) } - pub fn forward_to(&self, chat_id: T) -> ForwardMessage + pub fn forward_to(&self, chat_id: T) -> R::ForwardMessage where T: Into, { - self.bot.forward_message(chat_id, self.update.chat.id, self.update.id) + self.requester.forward_message(chat_id, self.update.chat.id, self.update.id) } - pub fn edit_message_text(&self, text: T) -> EditMessageText + pub fn edit_message_text(&self, text: T) -> R::EditMessageText where T: Into, { - self.bot.edit_message_text(self.update.chat.id, self.update.id, text) + self.requester.edit_message_text(self.update.chat.id, self.update.id, text) } - pub fn edit_message_caption(&self) -> EditMessageCaption { - self.bot.edit_message_caption(self.update.chat.id, self.update.id) + pub fn edit_message_caption(&self) -> R::EditMessageCaption { + self.requester.edit_message_caption(self.update.chat.id, self.update.id) } - pub fn delete_message(&self) -> DeleteMessage { - self.bot.delete_message(self.update.chat.id, self.update.id) + pub fn delete_message(&self) -> R::DeleteMessage { + self.requester.delete_message(self.update.chat.id, self.update.id) } - pub fn pin_message(&self) -> PinChatMessage { - self.bot.pin_chat_message(self.update.chat.id, self.update.id) + pub fn pin_message(&self) -> R::PinChatMessage { + self.requester.pin_chat_message(self.update.chat.id, self.update.id) } - pub fn answer_dice(&self) -> SendDice { - self.bot.send_dice(self.update.chat.id) + pub fn answer_dice(&self) -> R::SendDice { + self.requester.send_dice(self.update.chat.id) } } diff --git a/src/errors.rs b/src/errors.rs deleted file mode 100644 index 466cc501..00000000 --- a/src/errors.rs +++ /dev/null @@ -1,515 +0,0 @@ -use derive_more::From; -use reqwest::StatusCode; -use serde::Deserialize; -use thiserror::Error; - -/// An error caused by downloading a file. -#[derive(Debug, Error, From)] -pub enum DownloadError { - #[error("A network error: {0}")] - NetworkError(#[source] reqwest::Error), - - #[error("An I/O error: {0}")] - Io(#[source] std::io::Error), -} - -/// An error caused by sending a request to Telegram. -#[derive(Debug, Error)] -pub enum RequestError { - #[error("A Telegram's error #{status_code}: {kind:?}")] - ApiError { status_code: StatusCode, kind: ApiErrorKind }, - - /// The group has been migrated to a supergroup with the specified - /// identifier. - #[error("The group has been migrated to a supergroup with ID #{0}")] - MigrateToChatId(i64), - - /// In case of exceeding flood control, the number of seconds left to wait - /// before the request can be repeated. - #[error("Retry after {0} seconds")] - RetryAfter(i32), - - #[error("A network error: {0}")] - NetworkError(#[source] reqwest::Error), - - #[error("An error while parsing JSON: {0}")] - InvalidJson(#[source] serde_json::Error), -} - -/// A kind of an API error. -/// -/// If you receive [`ApiErrorKind::Unknown`], please [open an issue] with -/// the description of the error. -/// -/// [`ApiErrorKind::Unknown`]: crate::ApiErrorKind::Unknown -/// [open an issue]: https://github.com/teloxide/teloxide/issues/new -#[derive(Debug, Deserialize, PartialEq, Hash, Eq, Clone)] -#[serde(untagged)] -pub enum ApiErrorKind { - Known(KnownApiErrorKind), - Unknown(String), -} - -/// A kind of a known API error. -#[derive(Debug, Deserialize, PartialEq, Copy, Hash, Eq, Clone)] -pub enum KnownApiErrorKind { - /// Occurs when the bot tries to send message to user who blocked the bot. - #[serde(rename = "Forbidden: bot was blocked by the user")] - BotBlocked, - - /// Occurs when bot tries to modify a message without modification content. - /// - /// May happen in methods: - /// 1. [`EditMessageText`] - /// - /// [`EditMessageText`]: crate::requests::EditMessageText - #[serde(rename = "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 the message")] - MessageNotModified, - - /// Occurs when bot tries to forward or delete a message which was deleted. - /// - /// May happen in methods: - /// 1. [`ForwardMessage`] - /// 2. [`DeleteMessage`] - /// - /// [`ForwardMessage`]: crate::requests::ForwardMessage - /// [`DeleteMessage`]: crate::requests::DeleteMessage - #[serde(rename = "Bad Request: MESSAGE_ID_INVALID")] - MessageIdInvalid, - - /// Occurs when bot tries to forward a message which does not exists. - /// - /// May happen in methods: - /// 1. [`ForwardMessage`] - /// - /// [`ForwardMessage`]: crate::requests::ForwardMessage - #[serde(rename = "Bad Request: message to forward not found")] - MessageToForwardNotFound, - - /// Occurs when bot tries to delete a message which does not exists. - /// - /// May happen in methods: - /// 1. [`DeleteMessage`] - /// - /// [`DeleteMessage`]: crate::requests::DeleteMessage - #[serde(rename = "Bad Request: message to delete not found")] - MessageToDeleteNotFound, - - /// Occurs when bot tries to send a text message without text. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Bad Request: message text is empty")] - MessageTextIsEmpty, - - /// Occurs when bot tries to edit a message after long time. - /// - /// May happen in methods: - /// 1. [`EditMessageText`] - /// - /// [`EditMessageText`]: crate::requests::EditMessageText - #[serde(rename = "Bad Request: message can't be edited")] - MessageCantBeEdited, - - /// Occurs when bot tries to delete a someone else's message in group where - /// it does not have enough rights. - /// - /// May happen in methods: - /// 1. [`DeleteMessage`] - /// - /// [`DeleteMessage`]: crate::requests::DeleteMessage - #[serde(rename = "Bad Request: message can't be deleted")] - MessageCantBeDeleted, - - /// Occurs when bot tries to edit a message which does not exists. - /// - /// May happen in methods: - /// 1. [`EditMessageText`] - /// - /// [`EditMessageText`]: crate::requests::EditMessageText - #[serde(rename = "Bad Request: message to edit not found")] - MessageToEditNotFound, - - /// Occurs when bot tries to reply to a message which does not exists. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Bad Request: reply message not found")] - MessageToReplyNotFound, - - /// Occurs when bot tries to - #[serde(rename = "Bad Request: message identifier is not specified")] - MessageIdentifierNotSpecified, - - /// Occurs when bot tries to send a message with text size greater then - /// 4096 symbols. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Bad Request: message is too long")] - MessageIsTooLong, - - /// Occurs when bot tries to send media group with more than 10 items. - /// - /// May happen in methods: - /// 1. [`SendMediaGroup`] - /// - /// [`SendMediaGroup`]: crate::requests::SendMediaGroup - #[serde(rename = "Bad Request: Too much messages to send as an album")] - ToMuchMessages, - - /// Occurs when bot tries to stop poll that has already been stopped. - /// - /// May happen in methods: - /// 1. [`SendPoll`] - /// - /// [`SendPoll`]: crate::requests::SendPoll - #[serde(rename = "Bad Request: poll has already been closed")] - PollHasAlreadyClosed, - - /// Occurs when bot tries to send poll with less than 2 options. - /// - /// May happen in methods: - /// 1. [`SendPoll`] - /// - /// [`SendPoll`]: crate::requests::SendPoll - #[serde(rename = "Bad Request: poll must have at least 2 option")] - PollMustHaveMoreOptions, - - /// Occurs when bot tries to send poll with more than 10 options. - /// - /// May happen in methods: - /// 1. [`SendPoll`] - /// - /// [`SendPoll`]: crate::requests::SendPoll - #[serde(rename = "Bad Request: poll can't have more than 10 options")] - PollCantHaveMoreOptions, - - /// Occurs when bot tries to send poll with empty option (without text). - /// - /// May happen in methods: - /// 1. [`SendPoll`] - /// - /// [`SendPoll`]: crate::requests::SendPoll - #[serde(rename = "Bad Request: poll options must be non-empty")] - PollOptionsMustBeNonEmpty, - - /// Occurs when bot tries to send poll with empty question (without text). - /// - /// May happen in methods: - /// 1. [`SendPoll`] - /// - /// [`SendPoll`]: crate::requests::SendPoll - #[serde(rename = "Bad Request: poll question must be non-empty")] - PollQuestionMustBeNonEmpty, - - /// Occurs when bot tries to send poll with total size of options more than - /// 100 symbols. - /// - /// May happen in methods: - /// 1. [`SendPoll`] - /// - /// [`SendPoll`]: crate::requests::SendPoll - #[serde(rename = "Bad Request: poll options length must not exceed 100")] - PollOptionsLengthTooLong, - - /// Occurs when bot tries to send poll with question size more than 255 - /// symbols. - /// - /// May happen in methods: - /// 1. [`SendPoll`] - /// - /// [`SendPoll`]: crate::requests::SendPoll - #[serde(rename = "Bad Request: poll question length must not exceed 255")] - PollQuestionLengthTooLong, - - /// Occurs when bot tries to stop poll with message without poll. - /// - /// May happen in methods: - /// 1. [`StopPoll`] - /// - /// [`StopPoll`]: crate::requests::StopPoll - #[serde(rename = "Bad Request: message with poll to stop not found")] - MessageWithPollNotFound, - - /// Occurs when bot tries to stop poll with message without poll. - /// - /// May happen in methods: - /// 1. [`StopPoll`] - /// - /// [`StopPoll`]: crate::requests::StopPoll - #[serde(rename = "Bad Request: message is not a poll")] - MessageIsNotAPoll, - - /// Occurs when bot tries to send a message to chat in which it is not a - /// member. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Bad Request: chat not found")] - ChatNotFound, - - /// Occurs when bot tries to send method with unknown user_id. - /// - /// May happen in methods: - /// 1. [`getUserProfilePhotos`] - /// - /// [`getUserProfilePhotos`]: - /// crate::requests::GetUserProfilePhotos - #[serde(rename = "Bad Request: user not found")] - UserNotFound, - - /// Occurs when bot tries to send [`SetChatDescription`] with same text as - /// in the current description. - /// - /// May happen in methods: - /// 1. [`SetChatDescription`] - /// - /// [`SetChatDescription`]: crate::requests::SetChatDescription - #[serde(rename = "Bad Request: chat description is not modified")] - ChatDescriptionIsNotModified, - - /// Occurs when bot tries to answer to query after timeout expire. - /// - /// May happen in methods: - /// 1. [`AnswerCallbackQuery`] - /// - /// [`AnswerCallbackQuery`]: crate::requests::AnswerCallbackQuery - #[serde(rename = "Bad Request: query is too old and response timeout expired or query id is \ - invalid")] - InvalidQueryID, - - /// Occurs when bot tries to send InlineKeyboardMarkup with invalid button - /// url. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Bad Request: BUTTON_URL_INVALID")] - ButtonURLInvalid, - - /// Occurs when bot tries to send button with data size more than 64 bytes. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Bad Request: BUTTON_DATA_INVALID")] - ButtonDataInvalid, - - /// Occurs when bot tries to send button with data size == 0. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Bad Request: can't parse inline keyboard button: Text buttons are \ - unallowed in the inline keyboard")] - TextButtonsAreUnallowed, - - /// Occurs when bot tries to get file by wrong file id. - /// - /// May happen in methods: - /// 1. [`GetFile`] - /// - /// [`GetFile`]: crate::requests::GetFile - #[serde(rename = "Bad Request: wrong file id")] - WrongFileID, - - /// Occurs when bot tries to do some with group which was deactivated. - #[serde(rename = "Bad Request: group is deactivated")] - GroupDeactivated, - - /// Occurs when bot tries to set chat photo from file ID - /// - /// May happen in methods: - /// 1. [`SetChatPhoto`] - /// - /// [`SetChatPhoto`]: crate::requests::SetChatPhoto - #[serde(rename = "Bad Request: Photo should be uploaded as an InputFile")] - PhotoAsInputFileRequired, - - /// Occurs when bot tries to add sticker to stickerset by invalid name. - /// - /// May happen in methods: - /// 1. [`AddStickerToSet`] - /// - /// [`AddStickerToSet`]: crate::requests::AddStickerToSet - #[serde(rename = "Bad Request: STICKERSET_INVALID")] - InvalidStickersSet, - - /// Occurs when bot tries to pin a message without rights to pin in this - /// chat. - /// - /// May happen in methods: - /// 1. [`PinChatMessage`] - /// - /// [`PinChatMessage`]: crate::requests::PinChatMessage - #[serde(rename = "Bad Request: not enough rights to pin a message")] - NotEnoughRightsToPinMessage, - - /// Occurs when bot tries to use method in group which is allowed only in a - /// supergroup or channel. - #[serde(rename = "Bad Request: method is available only for supergroups and channel")] - MethodNotAvailableInPrivateChats, - - /// Occurs when bot tries to demote chat creator. - /// - /// May happen in methods: - /// 1. [`PromoteChatMember`] - /// - /// [`PromoteChatMember`]: crate::requests::PromoteChatMember - #[serde(rename = "Bad Request: can't demote chat creator")] - CantDemoteChatCreator, - - /// Occurs when bot tries to restrict self in group chats. - /// - /// May happen in methods: - /// 1. [`RestrictChatMember`] - /// - /// [`RestrictChatMember`]: crate::requests::RestrictChatMember - #[serde(rename = "Bad Request: can't restrict self")] - CantRestrictSelf, - - /// Occurs when bot tries to restrict chat member without rights to - /// restrict in this chat. - /// - /// May happen in methods: - /// 1. [`RestrictChatMember`] - /// - /// [`RestrictChatMember`]: crate::requests::RestrictChatMember - #[serde(rename = "Bad Request: not enough rights to restrict/unrestrict chat member")] - NotEnoughRightsToRestrict, - - /// Occurs when bot tries set webhook to protocol other than HTTPS. - /// - /// May happen in methods: - /// 1. [`SetWebhook`] - /// - /// [`SetWebhook`]: crate::requests::SetWebhook - #[serde(rename = "Bad Request: bad webhook: HTTPS url must be provided for webhook")] - WebhookRequireHTTPS, - - /// Occurs when bot tries to set webhook to port other than 80, 88, 443 or - /// 8443. - /// - /// May happen in methods: - /// 1. [`SetWebhook`] - /// - /// [`SetWebhook`]: crate::requests::SetWebhook - #[serde(rename = "Bad Request: bad webhook: Webhook can be set up only on ports 80, 88, 443 \ - or 8443")] - BadWebhookPort, - - /// Occurs when bot tries to set webhook to unknown host. - /// - /// May happen in methods: - /// 1. [`SetWebhook`] - /// - /// [`SetWebhook`]: crate::requests::SetWebhook - #[serde(rename = "Bad Request: bad webhook: Failed to resolve host: Name or service not known")] - UnknownHost, - - /// Occurs when bot tries to set webhook to invalid URL. - /// - /// May happen in methods: - /// 1. [`SetWebhook`] - /// - /// [`SetWebhook`]: crate::requests::SetWebhook - #[serde(rename = "Bad Request: can't parse URL")] - CantParseUrl, - - /// Occurs when bot tries to send message with unfinished entities. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Bad Request: can't parse entities")] - CantParseEntities, - - /// Occurs when bot tries to use getUpdates while webhook is active. - /// - /// May happen in methods: - /// 1. [`GetUpdates`] - /// - /// [`GetUpdates`]: crate::requests::GetUpdates - #[serde(rename = "can't use getUpdates method while webhook is active")] - CantGetUpdates, - - /// Occurs when bot tries to do some in group where bot was kicked. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Unauthorized: bot was kicked from a chat")] - BotKicked, - - /// Occurs when bot tries to send message to deactivated user. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Unauthorized: user is deactivated")] - UserDeactivated, - - /// Occurs when you tries to initiate conversation with a user. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Unauthorized: bot can't initiate conversation with a user")] - CantInitiateConversation, - - /// Occurs when you tries to send message to bot. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Unauthorized: bot can't send messages to bots")] - CantTalkWithBots, - - /// Occurs when bot tries to send button with invalid http url. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Bad Request: wrong HTTP URL")] - WrongHTTPurl, - - /// Occurs when bot tries GetUpdate before the timeout. Make sure that only - /// one Updater is running. - /// - /// May happen in methods: - /// 1. [`GetUpdates`] - /// - /// [`GetUpdates`]: crate::requests::GetUpdates - #[serde(rename = "Conflict: terminated by other getUpdates request; make sure that only one \ - bot instance is running")] - TerminatedByOtherGetUpdates, - - /// Occurs when bot tries to get file by invalid file id. - /// - /// May happen in methods: - /// 1. [`GetFile`] - /// - /// [`GetFile`]: crate::requests::GetFile - #[serde(rename = "Bad Request: invalid file id")] - FileIdInvalid, -} diff --git a/src/lib.rs b/src/lib.rs index 8639abd6..47f37e90 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,25 +51,22 @@ // FIXME(waffle): use `docsrs` here when issue with combine is resolved #![cfg_attr(all(teloxide_docsrs, feature = "nightly"), feature(doc_cfg))] -pub use bot::{Bot, BotBuilder}; pub use dispatching::repls::{ commands_repl, commands_repl_with_listener, dialogues_repl, dialogues_repl_with_listener, repl, repl_with_listener, }; -pub use errors::{ApiErrorKind, DownloadError, KnownApiErrorKind, RequestError}; +pub use teloxide_core::{ApiError, DownloadError, RequestError}; -mod errors; -mod net; +mod logging; -mod bot; pub mod dispatching; pub mod error_handlers; -mod logging; pub mod prelude; -pub mod requests; -pub mod types; pub mod utils; +pub use teloxide_core as core; + +use teloxide_core::requests::ResponseResult; #[cfg(feature = "macros")] // FIXME(waffle): use `docsrs` here when issue with combine is resolved #[cfg_attr(all(teloxide_docsrs, feature = "nightly"), doc(cfg(feature = "macros")))] @@ -78,3 +75,8 @@ pub use teloxide_macros::teloxide; #[cfg(all(feature = "nightly", doctest))] #[doc(include = "../README.md")] enum ReadmeDocTests {} + +/// A shortcut for `ResponseResult::Ok(val)`. +pub fn respond(val: T) -> ResponseResult { + ResponseResult::Ok(val) +} diff --git a/src/net/download.rs b/src/net/download.rs deleted file mode 100644 index 6f9c87b0..00000000 --- a/src/net/download.rs +++ /dev/null @@ -1,51 +0,0 @@ -use reqwest::Client; -use tokio::io::{AsyncWrite, AsyncWriteExt}; - -use crate::errors::DownloadError; - -use super::TELEGRAM_API_URL; - -pub async fn download_file( - client: &Client, - token: &str, - path: &str, - destination: &mut D, -) -> Result<(), DownloadError> -where - D: AsyncWrite + Unpin, -{ - let mut res = client - .get(&super::file_url(TELEGRAM_API_URL, token, path)) - .send() - .await? - .error_for_status()?; - - while let Some(chunk) = res.chunk().await? { - destination.write_all(&chunk).await?; - } - - Ok(()) -} - -#[cfg(feature = "unstable-stream")] -// FIXME(waffle): use `docsrs` here when issue with combine is resolved -#[cfg_attr(all(teloxide_docsrs, feature = "nightly"), doc(cfg(feature = "unstable-stream")))] -pub async fn download_file_stream( - client: &Client, - token: &str, - path: &str, -) -> Result>, reqwest::Error> { - let res = client - .get(&super::file_url(TELEGRAM_API_URL, token, path)) - .send() - .await? - .error_for_status()?; - - Ok(futures::stream::unfold(res, |mut res| async { - match res.chunk().await { - Err(err) => Some((Err(err), res)), - Ok(Some(c)) => Some((Ok(c), res)), - Ok(None) => None, - } - })) -} diff --git a/src/net/mod.rs b/src/net/mod.rs deleted file mode 100644 index 118b6036..00000000 --- a/src/net/mod.rs +++ /dev/null @@ -1,63 +0,0 @@ -#[cfg(feature = "unstable-stream")] -// FIXME(waffle): use `docsrs` here when issue with combine is resolved -#[cfg_attr(all(teloxide_docsrs, feature = "nightly"), doc(cfg(feature = "unstable-stream")))] -pub use download::download_file_stream; - -pub use self::{ - download::download_file, - request::{request_json, request_multipart}, - telegram_response::TelegramResponse, -}; - -mod download; -mod request; -mod telegram_response; - -const TELEGRAM_API_URL: &str = "https://api.telegram.org"; - -/// Creates URL for making HTTPS requests. See the [Telegram documentation]. -/// -/// [Telegram documentation]: https://core.telegram.org/bots/api#making-requests -fn method_url(base: &str, token: &str, method_name: &str) -> String { - format!("{url}/bot{token}/{method}", url = base, token = token, method = method_name,) -} - -/// Creates URL for downloading a file. See the [Telegram documentation]. -/// -/// [Telegram documentation]: https://core.telegram.org/bots/api#file -fn file_url(base: &str, token: &str, file_path: &str) -> String { - format!("{url}/file/bot{token}/{file}", url = base, token = token, file = file_path,) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn method_url_test() { - let url = method_url( - TELEGRAM_API_URL, - "535362388:AAF7-g0gYncWnm5IyfZlpPRqRRv6kNAGlao", - "methodName", - ); - - assert_eq!( - url, - "https://api.telegram.org/bot535362388:AAF7-g0gYncWnm5IyfZlpPRqRRv6kNAGlao/methodName" - ); - } - - #[test] - fn file_url_test() { - let url = file_url( - TELEGRAM_API_URL, - "535362388:AAF7-g0gYncWnm5IyfZlpPRqRRv6kNAGlao", - "AgADAgADyqoxG2g8aEsu_KjjVsGF4-zetw8ABAEAAwIAA20AA_8QAwABFgQ", - ); - - assert_eq!( - url, - "https://api.telegram.org/file/bot535362388:AAF7-g0gYncWnm5IyfZlpPRqRRv6kNAGlao/AgADAgADyqoxG2g8aEsu_KjjVsGF4-zetw8ABAEAAwIAA20AA_8QAwABFgQ" - ); - } -} diff --git a/src/net/request.rs b/src/net/request.rs deleted file mode 100644 index 05f0af5e..00000000 --- a/src/net/request.rs +++ /dev/null @@ -1,63 +0,0 @@ -use reqwest::{multipart::Form, Client, Response}; -use serde::{de::DeserializeOwned, Serialize}; - -use crate::{requests::ResponseResult, RequestError}; - -use super::{TelegramResponse, TELEGRAM_API_URL}; -use std::time::Duration; - -const DELAY_ON_SERVER_ERROR: Duration = Duration::from_secs(10); - -pub async fn request_multipart( - client: &Client, - token: &str, - method_name: &str, - params: Form, -) -> ResponseResult -where - T: DeserializeOwned, -{ - let response = client - .post(&super::method_url(TELEGRAM_API_URL, token, method_name)) - .multipart(params) - .send() - .await - .map_err(RequestError::NetworkError)?; - - process_response(response).await -} - -pub async fn request_json( - client: &Client, - token: &str, - method_name: &str, - params: &P, -) -> ResponseResult -where - T: DeserializeOwned, - P: Serialize, -{ - let response = client - .post(&super::method_url(TELEGRAM_API_URL, token, method_name)) - .json(params) - .send() - .await - .map_err(RequestError::NetworkError)?; - - process_response(response).await -} - -async fn process_response(response: Response) -> ResponseResult -where - T: DeserializeOwned, -{ - if response.status().is_server_error() { - tokio::time::sleep(DELAY_ON_SERVER_ERROR).await; - } - - serde_json::from_str::>( - &response.text().await.map_err(RequestError::NetworkError)?, - ) - .map_err(RequestError::InvalidJson)? - .into() -} diff --git a/src/net/telegram_response.rs b/src/net/telegram_response.rs deleted file mode 100644 index d5062e8f..00000000 --- a/src/net/telegram_response.rs +++ /dev/null @@ -1,70 +0,0 @@ -use reqwest::StatusCode; -use serde::Deserialize; - -use crate::{ - requests::ResponseResult, - types::{False, ResponseParameters, True}, - ApiErrorKind, RequestError, -}; - -#[derive(Deserialize)] -#[serde(untagged)] -pub enum TelegramResponse { - Ok { - /// A dummy field. Used only for deserialization. - #[allow(dead_code)] - ok: True, - - result: R, - }, - Err { - /// A dummy field. Used only for deserialization. - #[allow(dead_code)] - ok: False, - - #[serde(rename = "description")] - kind: ApiErrorKind, - error_code: u16, - response_parameters: Option, - }, -} - -impl Into> for TelegramResponse { - fn into(self) -> Result { - match self { - TelegramResponse::Ok { result, .. } => Ok(result), - TelegramResponse::Err { kind, error_code, response_parameters, .. } => { - if let Some(params) = response_parameters { - match params { - ResponseParameters::RetryAfter(i) => Err(RequestError::RetryAfter(i)), - ResponseParameters::MigrateToChatId(to) => { - Err(RequestError::MigrateToChatId(to)) - } - } - } else { - Err(RequestError::ApiError { - kind, - status_code: StatusCode::from_u16(error_code).unwrap(), - }) - } - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{errors::KnownApiErrorKind, types::Update}; - - #[test] - fn terminated_by_other_get_updates() { - let expected = ApiErrorKind::Known(KnownApiErrorKind::TerminatedByOtherGetUpdates); - if let TelegramResponse::Err{ kind, .. } = serde_json::from_str::>(r#"{"ok":false,"error_code":409,"description":"Conflict: terminated by other getUpdates request; make sure that only one bot instance is running"}"#).unwrap() { - assert_eq!(expected, kind); - } - else { - panic!("Expected ApiErrorKind::TerminatedByOtherGetUpdates"); - } - } -} diff --git a/src/prelude.rs b/src/prelude.rs index 61773b4e..1a5741e7 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -9,9 +9,16 @@ pub use crate::{ Dispatcher, DispatcherHandlerRx, DispatcherHandlerRxExt, UpdateWithCx, }, error_handlers::{LoggingErrorHandler, OnError}, - requests::{respond, Request, ResponseResult}, + respond, +}; + +pub use teloxide_core::prelude::*; + +pub use teloxide_core::{ + adaptors::AutoSend, + requests::{Request, ResponseResult}, types::{Message, Update}, - Bot, RequestError, + RequestError, }; #[cfg(feature = "frunk")] diff --git a/src/requests/all/add_sticker_to_set.rs b/src/requests/all/add_sticker_to_set.rs deleted file mode 100644 index 8cc0188f..00000000 --- a/src/requests/all/add_sticker_to_set.rs +++ /dev/null @@ -1,109 +0,0 @@ -use crate::{ - net, - requests::form_builder::FormBuilder, - types::{MaskPosition, True}, - Bot, -}; - -use crate::{ - requests::{RequestWithFile, ResponseResult}, - types::StickerType, -}; - -/// Use this method to add a new sticker to a set created by the bot. -/// -/// [The official docs](https://core.telegram.org/bots/api#addstickertoset). -#[derive(Debug, Clone)] -pub struct AddStickerToSet { - bot: Bot, - user_id: i32, - name: String, - sticker_type: StickerType, - emojis: String, - mask_position: Option, -} - -#[async_trait::async_trait] -impl RequestWithFile for AddStickerToSet { - type Output = True; - - async fn send(&self) -> tokio::io::Result> { - let builder = - FormBuilder::new().add_text("user_id", &self.user_id).add_text("name", &self.name); - - let builder = match &self.sticker_type { - StickerType::Png(file) => builder.add_input_file("png_sticker", &file), - StickerType::Tgs(file) => builder.add_input_file("tgs_sticker", &file), - } - .await? - .add_text("emojis", &self.emojis) - .add_text("mask_position", &self.mask_position); - - Ok(net::request_multipart( - self.bot.client(), - self.bot.token(), - "addStickerToSet", - builder.build(), - ) - .await) - } -} - -impl AddStickerToSet { - pub(crate) fn new( - bot: Bot, - user_id: i32, - name: N, - sticker_type: StickerType, - emojis: E, - ) -> Self - where - N: Into, - E: Into, - { - Self { - bot, - user_id, - name: name.into(), - sticker_type, - emojis: emojis.into(), - mask_position: None, - } - } - - /// User identifier of sticker set owner. - pub fn user_id(mut self, val: i32) -> Self { - self.user_id = val; - self - } - - /// Sticker set name. - pub fn name(mut self, val: T) -> Self - where - T: Into, - { - self.name = val.into(); - self - } - - pub fn sticker_type(mut self, val: StickerType) -> Self { - self.sticker_type = val; - self - } - - /// One or more emoji corresponding to the sticker. - pub fn emojis(mut self, val: T) -> Self - where - T: Into, - { - self.emojis = val.into(); - self - } - - /// A JSON-serialized object for position where the mask should be placed on - /// faces. - pub fn mask_position(mut self, val: MaskPosition) -> Self { - self.mask_position = Some(val); - self - } -} diff --git a/src/requests/all/answer_callback_query.rs b/src/requests/all/answer_callback_query.rs deleted file mode 100644 index c74b1e88..00000000 --- a/src/requests/all/answer_callback_query.rs +++ /dev/null @@ -1,101 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::True, - Bot, -}; - -/// Use this method to send answers to callback queries sent from [inline -/// keyboards]. -/// -/// The answer will be displayed to the user as a notification at -/// the top of the chat screen or as an alert. -/// -/// [The official docs](https://core.telegram.org/bots/api#answercallbackquery). -/// -/// [inline keyboards]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct AnswerCallbackQuery { - #[serde(skip_serializing)] - bot: Bot, - callback_query_id: String, - text: Option, - show_alert: Option, - url: Option, - cache_time: Option, -} - -#[async_trait::async_trait] -impl Request for AnswerCallbackQuery { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "answerCallbackQuery", &self).await - } -} - -impl AnswerCallbackQuery { - pub(crate) fn new(bot: Bot, callback_query_id: C) -> Self - where - C: Into, - { - let callback_query_id = callback_query_id.into(); - Self { bot, callback_query_id, text: None, show_alert: None, url: None, cache_time: None } - } - - /// Unique identifier for the query to be answered. - pub fn callback_query_id(mut self, val: T) -> Self - where - T: Into, - { - self.callback_query_id = val.into(); - self - } - - /// Text of the notification. If not specified, nothing will be shown to the - /// user, 0-200 characters. - pub fn text(mut self, val: T) -> Self - where - T: Into, - { - self.text = Some(val.into()); - self - } - - /// If `true`, an alert will be shown by the client instead of a - /// notification at the top of the chat screen. Defaults to `false`. - pub fn show_alert(mut self, val: bool) -> Self { - self.show_alert = Some(val); - self - } - - /// URL that will be opened by the user's client. If you have created a - /// [`Game`] and accepted the conditions via [@Botfather], specify the - /// URL that opens your game – note that this will only work if the - /// query comes from a [`callback_game`] button. - /// - /// Otherwise, you may use links like `t.me/your_bot?start=XXXX` that open - /// your bot with a parameter. - /// - /// [@Botfather]: https://t.me/botfather - /// [`callback_game`]: crate::types::InlineKeyboardButton - /// [`Game`]: crate::types::Game - pub fn url(mut self, val: T) -> Self - where - T: Into, - { - self.url = Some(val.into()); - self - } - - /// The maximum amount of time in seconds that the result of the callback - /// query may be cached client-side. Telegram apps will support caching - /// starting in version 3.14. Defaults to 0. - pub fn cache_time(mut self, val: i32) -> Self { - self.cache_time = Some(val); - self - } -} diff --git a/src/requests/all/answer_inline_query.rs b/src/requests/all/answer_inline_query.rs deleted file mode 100644 index dd1b6a38..00000000 --- a/src/requests/all/answer_inline_query.rs +++ /dev/null @@ -1,146 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{InlineQueryResult, True}, - Bot, -}; - -/// Use this method to send answers to an inline query. -/// -/// No more than **50** results per query are allowed. -/// -/// [The official docs](https://core.telegram.org/bots/api#answerinlinequery). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct AnswerInlineQuery { - #[serde(skip_serializing)] - bot: Bot, - inline_query_id: String, - results: Vec, - cache_time: Option, - is_personal: Option, - next_offset: Option, - switch_pm_text: Option, - switch_pm_parameter: Option, -} - -#[async_trait::async_trait] -impl Request for AnswerInlineQuery { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "answerInlineQuery", &self).await - } -} - -impl AnswerInlineQuery { - pub(crate) fn new(bot: Bot, inline_query_id: I, results: R) -> Self - where - I: Into, - R: Into>, - { - let inline_query_id = inline_query_id.into(); - let results = results.into(); - Self { - bot, - inline_query_id, - results, - cache_time: None, - is_personal: None, - next_offset: None, - switch_pm_text: None, - switch_pm_parameter: None, - } - } - - /// Unique identifier for the answered query. - pub fn inline_query_id(mut self, val: T) -> Self - where - T: Into, - { - self.inline_query_id = val.into(); - self - } - - /// A JSON-serialized array of results for the inline query. - pub fn results(mut self, val: T) -> Self - where - T: Into>, - { - self.results = val.into(); - self - } - - /// The maximum amount of time in seconds that the result of the inline - /// query may be cached on the server. - /// - /// Defaults to 300. - pub fn cache_time(mut self, val: i32) -> Self { - self.cache_time = Some(val); - self - } - - /// Pass `true`, if results may be cached on the server side only for the - /// user that sent the query. - /// - /// By default, results may be returned to any user who sends the same - /// query. - #[allow(clippy::wrong_self_convention)] - pub fn is_personal(mut self, val: bool) -> Self { - self.is_personal = Some(val); - self - } - - /// Pass the offset that a client should send in the next query with the - /// same text to receive more results. - /// - /// Pass an empty string if there are no more results or if you don‘t - /// support pagination. Offset length can’t exceed 64 bytes. - pub fn next_offset(mut self, val: T) -> Self - where - T: Into, - { - self.next_offset = Some(val.into()); - self - } - - /// If passed, clients will display a button with specified text that - /// switches the user to a private chat with the bot and sends the bot a - /// start message with the parameter [`switch_pm_parameter`]. - /// - /// [`switch_pm_parameter`]: - /// crate::requests::AnswerInlineQuery::switch_pm_parameter - pub fn switch_pm_text(mut self, val: T) -> Self - where - T: Into, - { - self.switch_pm_text = Some(val.into()); - self - } - - /// [Deep-linking] parameter for the /start message sent to the bot when - /// user presses the switch button. 1-64 characters, only `A-Z`, `a-z`, - /// `0-9`, `_` and `-` are allowed. - /// - /// Example: An inline bot that sends YouTube videos can ask the user to - /// connect the bot to their YouTube account to adapt search results - /// accordingly. To do this, it displays a ‘Connect your YouTube account’ - /// button above the results, or even before showing any. The user presses - /// the button, switches to a private chat with the bot and, in doing so, - /// passes a start parameter that instructs the bot to return an oauth link. - /// Once done, the bot can offer a [`switch_inline`] button so that the user - /// can easily return to the chat where they wanted to use the bot's - /// inline capabilities. - /// - /// [Deep-linking]: https://core.telegram.org/bots#deep-linking - /// [`switch_inline`]: crate::types::InlineKeyboardMarkup - pub fn switch_pm_parameter(mut self, val: T) -> Self - where - T: Into, - { - self.switch_pm_parameter = Some(val.into()); - self - } -} diff --git a/src/requests/all/answer_pre_checkout_query.rs b/src/requests/all/answer_pre_checkout_query.rs deleted file mode 100644 index 2ac4df42..00000000 --- a/src/requests/all/answer_pre_checkout_query.rs +++ /dev/null @@ -1,82 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::True, - Bot, -}; - -/// Once the user has confirmed their payment and shipping details, the Bot API -/// sends the final confirmation in the form of an [`Update`] with the field -/// `pre_checkout_query`. Use this method to respond to such pre-checkout -/// queries. -/// -/// # Note -/// The Bot API must receive an answer within 10 seconds after the pre-checkout -/// query was sent. -/// -/// [The official docs](https://core.telegram.org/bots/api#answerprecheckoutquery). -/// -/// [`Update`]: crate::types::Update -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct AnswerPreCheckoutQuery { - #[serde(skip_serializing)] - bot: Bot, - pre_checkout_query_id: String, - ok: bool, - error_message: Option, -} - -#[async_trait::async_trait] -impl Request for AnswerPreCheckoutQuery { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "answerPreCheckoutQuery", &self) - .await - } -} - -impl AnswerPreCheckoutQuery { - pub(crate) fn new

(bot: Bot, pre_checkout_query_id: P, ok: bool) -> Self - where - P: Into, - { - let pre_checkout_query_id = pre_checkout_query_id.into(); - Self { bot, pre_checkout_query_id, ok, error_message: None } - } - - /// Unique identifier for the query to be answered. - pub fn pre_checkout_query_id(mut self, val: T) -> Self - where - T: Into, - { - self.pre_checkout_query_id = val.into(); - self - } - - /// Specify `true` if everything is alright (goods are available, etc.) and - /// the bot is ready to proceed with the order. Use False if there are any - /// problems. - pub fn ok(mut self, val: bool) -> Self { - self.ok = val; - self - } - - /// Required if ok is `false`. Error message in human readable form that - /// explains the reason for failure to proceed with the checkout (e.g. - /// "Sorry, somebody just bought the last of our amazing black T-shirts - /// while you were busy filling out your payment details. Please choose a - /// different color or garment!"). - /// - /// Telegram will display this message to the user. - pub fn error_message(mut self, val: T) -> Self - where - T: Into, - { - self.error_message = Some(val.into()); - self - } -} diff --git a/src/requests/all/answer_shipping_query.rs b/src/requests/all/answer_shipping_query.rs deleted file mode 100644 index be4d6662..00000000 --- a/src/requests/all/answer_shipping_query.rs +++ /dev/null @@ -1,86 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ShippingOption, True}, - Bot, -}; - -/// If you sent an invoice requesting a shipping address and the parameter -/// `is_flexible` was specified, the Bot API will send an [`Update`] with a -/// shipping_query field to the bot. Use this method to reply to shipping -/// queries. -/// -/// [The official docs](https://core.telegram.org/bots/api#answershippingquery). -/// -/// [`Update`]: crate::types::Update -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct AnswerShippingQuery { - #[serde(skip_serializing)] - bot: Bot, - shipping_query_id: String, - ok: bool, - shipping_options: Option>, - error_message: Option, -} - -#[async_trait::async_trait] -impl Request for AnswerShippingQuery { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "answerShippingQuery", &self).await - } -} - -impl AnswerShippingQuery { - pub(crate) fn new(bot: Bot, shipping_query_id: S, ok: bool) -> Self - where - S: Into, - { - let shipping_query_id = shipping_query_id.into(); - Self { bot, shipping_query_id, ok, shipping_options: None, error_message: None } - } - - /// Unique identifier for the query to be answered. - pub fn shipping_query_id(mut self, val: T) -> Self - where - T: Into, - { - self.shipping_query_id = val.into(); - self - } - - /// Specify `true` if delivery to the specified address is possible and - /// `false` if there are any problems (for example, if delivery to the - /// specified address is not possible). - pub fn ok(mut self, val: bool) -> Self { - self.ok = val; - self - } - - /// Required if ok is `true`. A JSON-serialized array of available shipping - /// options. - pub fn shipping_options(mut self, val: T) -> Self - where - T: Into>, - { - self.shipping_options = Some(val.into()); - self - } - - /// Required if ok is `false`. Error message in human readable form that - /// explains why it is impossible to complete the order (e.g. "Sorry, - /// delivery to your desired address is unavailable'). - /// - /// Telegram will display this message to the user. - pub fn error_message(mut self, val: T) -> Self - where - T: Into, - { - self.error_message = Some(val.into()); - self - } -} diff --git a/src/requests/all/create_new_sticker_set.rs b/src/requests/all/create_new_sticker_set.rs deleted file mode 100644 index a49cb08b..00000000 --- a/src/requests/all/create_new_sticker_set.rs +++ /dev/null @@ -1,134 +0,0 @@ -use crate::{ - net, - requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult}, - types::{MaskPosition, StickerType, True}, - Bot, -}; - -/// Use this method to create new sticker set owned by a user. The bot will be -/// able to edit the created sticker set. -/// -/// [The official docs](https://core.telegram.org/bots/api#createnewstickerset). -#[derive(Debug, Clone)] -pub struct CreateNewStickerSet { - bot: Bot, - user_id: i32, - name: String, - title: String, - sticker_type: StickerType, - emojis: String, - contains_masks: Option, - mask_position: Option, -} - -#[async_trait::async_trait] -impl RequestWithFile for CreateNewStickerSet { - type Output = True; - - async fn send(&self) -> tokio::io::Result> { - let builder = FormBuilder::new() - .add_text("user_id", &self.user_id) - .add_text("name", &self.name) - .add_text("title", &self.title); - - let builder = match &self.sticker_type { - StickerType::Png(file) => builder.add_input_file("png_sticker", &file), - StickerType::Tgs(file) => builder.add_input_file("tgs_sticker", &file), - } - .await? - .add_text("emojis", &self.emojis) - .add_text("contains_masks", &self.contains_masks) - .add_text("mask_position", &self.mask_position); - - Ok(net::request_multipart( - self.bot.client(), - self.bot.token(), - "createNewStickerSet", - builder.build(), - ) - .await) - } -} - -impl CreateNewStickerSet { - pub(crate) fn new( - bot: Bot, - user_id: i32, - name: N, - title: T, - sticker_type: StickerType, - emojis: E, - ) -> Self - where - N: Into, - T: Into, - E: Into, - { - Self { - bot, - user_id, - name: name.into(), - title: title.into(), - sticker_type, - emojis: emojis.into(), - contains_masks: None, - mask_position: None, - } - } - - /// User identifier of created sticker set owner. - pub fn user_id(mut self, val: i32) -> Self { - self.user_id = val; - self - } - - /// Short name of sticker set, to be used in `t.me/addstickers/` URLs (e.g., - /// animals). Can contain only english letters, digits and underscores. - /// - /// Must begin with a letter, can't contain consecutive underscores and must - /// end in `_by_`. `` is case insensitive. - /// 1-64 characters. - pub fn name(mut self, val: T) -> Self - where - T: Into, - { - self.name = val.into(); - self - } - - /// Sticker set title, 1-64 characters. - pub fn title(mut self, val: T) -> Self - where - T: Into, - { - self.title = val.into(); - self - } - - pub fn sticker_type(mut self, val: StickerType) -> Self { - self.sticker_type = val; - self - } - - /// One or more emoji corresponding to the sticker. - pub fn emojis(mut self, val: T) -> Self - where - T: Into, - { - self.emojis = val.into(); - self - } - - /// Pass `true`, if a set of mask stickers should be created. - pub fn contains_masks(mut self, val: bool) -> Self { - self.contains_masks = Some(val); - self - } - - /// A JSON-serialized object for position where the mask should be placed on - /// faces. - pub fn mask_position(mut self, val: MaskPosition) -> Self { - self.mask_position = Some(val); - self - } -} diff --git a/src/requests/all/delete_chat_photo.rs b/src/requests/all/delete_chat_photo.rs deleted file mode 100644 index 01439bfa..00000000 --- a/src/requests/all/delete_chat_photo.rs +++ /dev/null @@ -1,50 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, True}, - Bot, -}; - -/// Use this method to delete a chat photo. Photos can't be changed for private -/// chats. The bot must be an administrator in the chat for this to work and -/// must have the appropriate admin rights. -/// -/// [The official docs](https://core.telegram.org/bots/api#deletechatphoto). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct DeleteChatPhoto { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, -} - -#[async_trait::async_trait] -impl Request for DeleteChatPhoto { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "deleteChatPhoto", &self).await - } -} - -impl DeleteChatPhoto { - pub(crate) fn new(bot: Bot, chat_id: C) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { bot, chat_id } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } -} diff --git a/src/requests/all/delete_chat_sticker_set.rs b/src/requests/all/delete_chat_sticker_set.rs deleted file mode 100644 index 9fe228c5..00000000 --- a/src/requests/all/delete_chat_sticker_set.rs +++ /dev/null @@ -1,55 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, True}, - Bot, -}; - -/// Use this method to delete a group sticker set from a supergroup. -/// -/// The bot must be an administrator in the chat for this to work and must have -/// the appropriate admin rights. Use the field `can_set_sticker_set` optionally -/// returned in [`Bot::get_chat`] requests to check if the bot can use this -/// method. -/// -/// [The official docs](https://core.telegram.org/bots/api#deletechatstickerset). -/// -/// [`Bot::get_chat`]: crate::Bot::get_chat -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct DeleteChatStickerSet { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, -} - -#[async_trait::async_trait] -impl Request for DeleteChatStickerSet { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "deleteChatStickerSet", &self).await - } -} - -impl DeleteChatStickerSet { - pub(crate) fn new(bot: Bot, chat_id: C) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { bot, chat_id } - } - - /// Unique identifier for the target chat or username of the target - /// supergroup (in the format `@supergroupusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } -} diff --git a/src/requests/all/delete_message.rs b/src/requests/all/delete_message.rs deleted file mode 100644 index 35ead60a..00000000 --- a/src/requests/all/delete_message.rs +++ /dev/null @@ -1,67 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, True}, - Bot, -}; - -/// Use this method to delete a message, including service messages. -/// -/// The limitations are: -/// - A message can only be deleted if it was sent less than 48 hours ago. -/// - Bots can delete outgoing messages in private chats, groups, and -/// supergroups. -/// - Bots can delete incoming messages in private chats. -/// - Bots granted can_post_messages permissions can delete outgoing messages -/// in channels. -/// - If the bot is an administrator of a group, it can delete any message -/// there. -/// - If the bot has can_delete_messages permission in a supergroup or a -/// channel, it can delete any message there. -/// -/// [The official docs](https://core.telegram.org/bots/api#deletemessage). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct DeleteMessage { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, - message_id: i32, -} - -#[async_trait::async_trait] -impl Request for DeleteMessage { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "deleteMessage", &self).await - } -} - -impl DeleteMessage { - pub(crate) fn new(bot: Bot, chat_id: C, message_id: i32) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { bot, chat_id, message_id } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Identifier of the message to delete. - pub fn message_id(mut self, val: i32) -> Self { - self.message_id = val; - self - } -} diff --git a/src/requests/all/delete_sticker_from_set.rs b/src/requests/all/delete_sticker_from_set.rs deleted file mode 100644 index 59269f7a..00000000 --- a/src/requests/all/delete_sticker_from_set.rs +++ /dev/null @@ -1,47 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::True, - Bot, -}; - -/// Use this method to delete a sticker from a set created by the bot. -/// -/// [The official docs](https://core.telegram.org/bots/api#deletestickerfromset). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct DeleteStickerFromSet { - #[serde(skip_serializing)] - bot: Bot, - sticker: String, -} - -#[async_trait::async_trait] -impl Request for DeleteStickerFromSet { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "deleteStickerFromSet", &self).await - } -} - -impl DeleteStickerFromSet { - pub(crate) fn new(bot: Bot, sticker: S) -> Self - where - S: Into, - { - let sticker = sticker.into(); - Self { bot, sticker } - } - - /// File identifier of the sticker. - pub fn sticker(mut self, val: T) -> Self - where - T: Into, - { - self.sticker = val.into(); - self - } -} diff --git a/src/requests/all/delete_webhook.rs b/src/requests/all/delete_webhook.rs deleted file mode 100644 index fd3bd3ba..00000000 --- a/src/requests/all/delete_webhook.rs +++ /dev/null @@ -1,37 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::True, - Bot, -}; - -/// Use this method to remove webhook integration if you decide to switch back -/// to [Bot::get_updates]. -/// -/// [The official docs](https://core.telegram.org/bots/api#deletewebhook). -/// -/// [Bot::get_updates]: crate::Bot::get_updates -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct DeleteWebhook { - #[serde(skip_serializing)] - bot: Bot, -} - -#[async_trait::async_trait] -impl Request for DeleteWebhook { - type Output = True; - - #[allow(clippy::trivially_copy_pass_by_ref)] - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "deleteWebhook", &self).await - } -} - -impl DeleteWebhook { - pub(crate) fn new(bot: Bot) -> Self { - Self { bot } - } -} diff --git a/src/requests/all/edit_inline_message_caption.rs b/src/requests/all/edit_inline_message_caption.rs deleted file mode 100644 index e777f17c..00000000 --- a/src/requests/all/edit_inline_message_caption.rs +++ /dev/null @@ -1,83 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{InlineKeyboardMarkup, ParseMode, True}, - Bot, -}; - -/// Use this method to edit captions of messages sent via the bot. -/// -/// On success, [`True`] is returned. -/// -/// [The official docs](https://core.telegram.org/bots/api#editmessagecaption). -/// -/// [`True`]: crate::types::True -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct EditInlineMessageCaption { - #[serde(skip_serializing)] - bot: Bot, - inline_message_id: String, - caption: Option, - parse_mode: Option, - reply_markup: Option, -} - -#[async_trait::async_trait] -impl Request for EditInlineMessageCaption { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "editMessageCaption", &self).await - } -} - -impl EditInlineMessageCaption { - pub(crate) fn new(bot: Bot, inline_message_id: I) -> Self - where - I: Into, - { - let inline_message_id = inline_message_id.into(); - Self { bot, inline_message_id, caption: None, parse_mode: None, reply_markup: None } - } - - /// Identifier of the inline message. - pub fn inline_message_id(mut self, val: T) -> Self - where - T: Into, - { - self.inline_message_id = val.into(); - self - } - - /// New caption of the message. - pub fn caption(mut self, val: T) -> Self - where - T: Into, - { - self.caption = Some(val.into()); - self - } - - /// Send [Markdown] or [HTML], if you want Telegram apps to show - /// [bold, italic, fixed-width text or inline URLs] in the media caption. - /// - /// [Markdown]: crate::types::ParseMode::Markdown - /// [HTML]: crate::types::ParseMode::HTML - /// [bold, italic, fixed-width text or inline URLs]: - /// crate::types::ParseMode - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - /// A JSON-serialized object for an [inline keyboard]. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/edit_inline_message_live_location.rs b/src/requests/all/edit_inline_message_live_location.rs deleted file mode 100644 index 4b267427..00000000 --- a/src/requests/all/edit_inline_message_live_location.rs +++ /dev/null @@ -1,77 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{InlineKeyboardMarkup, True}, - Bot, -}; - -/// Use this method to edit live location messages sent via the bot. -/// -/// A location can be edited until its live_period expires or editing is -/// explicitly disabled by a call to stopMessageLiveLocation. On success, -/// [`True`] is returned. -/// -/// [The official docs](https://core.telegram.org/bots/api#editmessagelivelocation). -/// -/// [`True`]: crate::types::True -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct EditInlineMessageLiveLocation { - #[serde(skip_serializing)] - bot: Bot, - inline_message_id: String, - latitude: f32, - longitude: f32, - reply_markup: Option, -} - -#[async_trait::async_trait] -impl Request for EditInlineMessageLiveLocation { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "editMessageLiveLocation", &self) - .await - } -} - -impl EditInlineMessageLiveLocation { - pub(crate) fn new(bot: Bot, inline_message_id: I, latitude: f32, longitude: f32) -> Self - where - I: Into, - { - let inline_message_id = inline_message_id.into(); - Self { bot, inline_message_id, latitude, longitude, reply_markup: None } - } - - /// Identifier of the inline message. - pub fn inline_message_id(mut self, val: T) -> Self - where - T: Into, - { - self.inline_message_id = val.into(); - self - } - - /// Latitude of new location. - pub fn latitude(mut self, val: f32) -> Self { - self.latitude = val; - self - } - - /// Longitude of new location. - pub fn longitude(mut self, val: f32) -> Self { - self.longitude = val; - self - } - - /// A JSON-serialized object for a new [inline keyboard]. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/edit_inline_message_media.rs b/src/requests/all/edit_inline_message_media.rs deleted file mode 100644 index 9a11f126..00000000 --- a/src/requests/all/edit_inline_message_media.rs +++ /dev/null @@ -1,78 +0,0 @@ -use crate::{ - net, - requests::{form_builder::FormBuilder, Request, ResponseResult}, - types::{InlineKeyboardMarkup, InputMedia, True}, - Bot, -}; - -/// Use this method to edit animation, audio, document, photo, or video -/// messages sent via the bot. -/// -/// If a message is a part of a message album, then it can be edited only to a -/// photo or a video. Otherwise, message type can be changed arbitrarily. When -/// this method is used, new file can't be uploaded. Use previously -/// uploaded file via its `file_id` or specify a URL. On success, [`True`] is -/// returned. -/// -/// [The official docs](https://core.telegram.org/bots/api#editmessagemedia). -/// -/// [`True`]: crate::types::True -#[derive(Debug, Clone)] -pub struct EditInlineMessageMedia { - bot: Bot, - inline_message_id: String, - media: InputMedia, - reply_markup: Option, -} - -#[async_trait::async_trait] -impl Request for EditInlineMessageMedia { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_multipart( - self.bot.client(), - self.bot.token(), - "editMessageMedia", - FormBuilder::new() - .add_text("media", &self.media) - .add_text("reply_markup", &self.reply_markup) - .add_text("inline_message_id", &self.inline_message_id) - .build(), - ) - .await - } -} - -impl EditInlineMessageMedia { - pub(crate) fn new(bot: Bot, inline_message_id: I, media: InputMedia) -> Self - where - I: Into, - { - let inline_message_id = inline_message_id.into(); - Self { bot, inline_message_id, media, reply_markup: None } - } - - /// Identifier of the inline message. - pub fn inline_message_id(mut self, val: T) -> Self - where - T: Into, - { - self.inline_message_id = val.into(); - self - } - - /// A JSON-serialized object for a new media content of the message. - pub fn media(mut self, val: InputMedia) -> Self { - self.media = val; - self - } - - /// A JSON-serialized object for a new [inline keyboard]. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/edit_inline_message_reply_markup.rs b/src/requests/all/edit_inline_message_reply_markup.rs deleted file mode 100644 index cd96c686..00000000 --- a/src/requests/all/edit_inline_message_reply_markup.rs +++ /dev/null @@ -1,62 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{InlineKeyboardMarkup, True}, - Bot, -}; - -/// Use this method to edit only the reply markup of messages sent via the bot. -/// -/// On success, [`True`] is returned. -/// -/// [The official docs](https://core.telegram.org/bots/api#editmessagereplymarkup). -/// -/// [`Message`]: crate::types::Message -/// [`True`]: crate::types::True -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct EditInlineMessageReplyMarkup { - #[serde(skip_serializing)] - bot: Bot, - inline_message_id: String, - reply_markup: Option, -} - -#[async_trait::async_trait] -impl Request for EditInlineMessageReplyMarkup { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "editMessageReplyMarkup", &self) - .await - } -} - -impl EditInlineMessageReplyMarkup { - pub(crate) fn new(bot: Bot, inline_message_id: I) -> Self - where - I: Into, - { - let inline_message_id = inline_message_id.into(); - Self { bot, inline_message_id, reply_markup: None } - } - - /// Identifier of the inline message. - pub fn inline_message_id(mut self, val: T) -> Self - where - T: Into, - { - self.inline_message_id = val.into(); - self - } - - /// A JSON-serialized object for an [inline keyboard]. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/edit_inline_message_text.rs b/src/requests/all/edit_inline_message_text.rs deleted file mode 100644 index 2b266314..00000000 --- a/src/requests/all/edit_inline_message_text.rs +++ /dev/null @@ -1,98 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{InlineKeyboardMarkup, Message, ParseMode}, - Bot, -}; - -/// Use this method to edit text and game messages sent via the bot. -/// -/// On success, [`True`] is returned. -/// -/// [The official docs](https://core.telegram.org/bots/api#editmessagetext). -/// -/// [`True`]: crate::types::True -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct EditInlineMessageText { - #[serde(skip_serializing)] - bot: Bot, - inline_message_id: String, - text: String, - parse_mode: Option, - disable_web_page_preview: Option, - reply_markup: Option, -} - -#[async_trait::async_trait] -impl Request for EditInlineMessageText { - type Output = Message; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "editMessageText", &self).await - } -} - -impl EditInlineMessageText { - pub(crate) fn new(bot: Bot, inline_message_id: I, text: T) -> Self - where - I: Into, - T: Into, - { - let inline_message_id = inline_message_id.into(); - let text = text.into(); - Self { - bot, - inline_message_id, - text, - parse_mode: None, - disable_web_page_preview: None, - reply_markup: None, - } - } - - /// Identifier of the inline message. - pub fn inline_message_id(mut self, val: T) -> Self - where - T: Into, - { - self.inline_message_id = val.into(); - self - } - - /// New text of the message. - pub fn text(mut self, val: T) -> Self - where - T: Into, - { - self.text = val.into(); - self - } - - /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, - /// italic, fixed-width text or inline URLs] in your bot's message. - /// - /// [Markdown]: https://core.telegram.org/bots/api#markdown-style - /// [HTML]: https://core.telegram.org/bots/api#html-style - /// [bold, italic, fixed-width text or inline URLs]: https://core.telegram.org/bots/api#formatting-options - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - /// Disables link previews for links in this message. - pub fn disable_web_page_preview(mut self, val: bool) -> Self { - self.disable_web_page_preview = Some(val); - self - } - - /// A JSON-serialized object for an [inline keyboard]. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/edit_message_caption.rs b/src/requests/all/edit_message_caption.rs deleted file mode 100644 index 9294af93..00000000 --- a/src/requests/all/edit_message_caption.rs +++ /dev/null @@ -1,91 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, InlineKeyboardMarkup, Message, ParseMode}, - Bot, -}; - -/// Use this method to edit captions of messages sent by the bot. -/// -/// On success, the edited [`Message`] is returned. -/// -/// [The official docs](https://core.telegram.org/bots/api#editmessagecaption). -/// -/// [`Message`]: crate::types::Message -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct EditMessageCaption { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, - message_id: i32, - caption: Option, - parse_mode: Option, - reply_markup: Option, -} - -#[async_trait::async_trait] -impl Request for EditMessageCaption { - type Output = Message; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "editMessageCaption", &self).await - } -} - -impl EditMessageCaption { - pub(crate) fn new(bot: Bot, chat_id: C, message_id: i32) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { bot, chat_id, message_id, caption: None, parse_mode: None, reply_markup: None } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`) - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Identifier of the message to edit - pub fn message_id(mut self, val: i32) -> Self { - self.message_id = val; - self - } - - /// New caption of the message. - pub fn caption(mut self, val: T) -> Self - where - T: Into, - { - self.caption = Some(val.into()); - self - } - - /// Send [Markdown] or [HTML], if you want Telegram apps to show - /// [bold, italic, fixed-width text or inline URLs] in the media caption. - /// - /// [Markdown]: crate::types::ParseMode::Markdown - /// [HTML]: crate::types::ParseMode::HTML - /// [bold, italic, fixed-width text or inline URLs]: - /// crate::types::ParseMode - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - /// A JSON-serialized object for an [inline keyboard]. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/edit_message_live_location.rs b/src/requests/all/edit_message_live_location.rs deleted file mode 100644 index c5afd295..00000000 --- a/src/requests/all/edit_message_live_location.rs +++ /dev/null @@ -1,91 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, InlineKeyboardMarkup, Message}, - Bot, -}; - -/// Use this method to edit live location messages. -/// -/// A location can be edited until its live_period expires or editing is -/// explicitly disabled by a call to stopMessageLiveLocation. On success, the -/// edited [`Message`] is returned. -/// -/// [The official docs](https://core.telegram.org/bots/api#editmessagelivelocation). -/// -/// [`Message`]: crate::types::Message -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct EditMessageLiveLocation { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, - message_id: i32, - latitude: f32, - longitude: f32, - reply_markup: Option, -} - -#[async_trait::async_trait] -impl Request for EditMessageLiveLocation { - type Output = Message; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "editMessageLiveLocation", &self) - .await - } -} - -impl EditMessageLiveLocation { - pub(crate) fn new( - bot: Bot, - chat_id: C, - message_id: i32, - latitude: f32, - longitude: f32, - ) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { bot, chat_id, message_id, latitude, longitude, reply_markup: None } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`) - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Identifier of the message to edit - pub fn message_id(mut self, val: i32) -> Self { - self.message_id = val; - self - } - - /// Latitude of new location. - pub fn latitude(mut self, val: f32) -> Self { - self.latitude = val; - self - } - - /// Longitude of new location. - pub fn longitude(mut self, val: f32) -> Self { - self.longitude = val; - self - } - - /// A JSON-serialized object for a new [inline keyboard]. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/edit_message_media.rs b/src/requests/all/edit_message_media.rs deleted file mode 100644 index 7f15ed5f..00000000 --- a/src/requests/all/edit_message_media.rs +++ /dev/null @@ -1,85 +0,0 @@ -use crate::{ - net, - requests::{form_builder::FormBuilder, Request, ResponseResult}, - types::{ChatId, InlineKeyboardMarkup, InputMedia, Message}, - Bot, -}; - -/// Use this method to edit animation, audio, document, photo, or video -/// messages. -/// -/// If a message is a part of a message album, then it can be edited only to a -/// photo or a video. Otherwise, message type can be changed arbitrarily. On -/// success, the edited [`Message`] is returned. -/// -/// [The official docs](https://core.telegram.org/bots/api#editmessagemedia). -/// -/// [`Message`]: crate::types::Message -#[derive(Debug, Clone)] -pub struct EditMessageMedia { - bot: Bot, - chat_id: ChatId, - message_id: i32, - media: InputMedia, - reply_markup: Option, -} - -#[async_trait::async_trait] -impl Request for EditMessageMedia { - type Output = Message; - - async fn send(&self) -> ResponseResult { - net::request_multipart( - self.bot.client(), - self.bot.token(), - "editMessageMedia", - FormBuilder::new() - .add_text("chat_id", &self.chat_id) - .add_text("message_id", &self.message_id) - .add_text("media", &self.media) - .add_text("reply_markup", &self.reply_markup) - .build(), - ) - .await - } -} - -impl EditMessageMedia { - pub(crate) fn new(bot: Bot, chat_id: C, message_id: i32, media: InputMedia) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { bot, chat_id, message_id, media, reply_markup: None } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`) - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Identifier of the message to edit - pub fn message_id(mut self, val: i32) -> Self { - self.message_id = val; - self - } - - /// A JSON-serialized object for a new media content of the message. - pub fn media(mut self, val: InputMedia) -> Self { - self.media = val; - self - } - - /// A JSON-serialized object for a new [inline keyboard]. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/edit_message_reply_markup.rs b/src/requests/all/edit_message_reply_markup.rs deleted file mode 100644 index 66aab5bf..00000000 --- a/src/requests/all/edit_message_reply_markup.rs +++ /dev/null @@ -1,69 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, InlineKeyboardMarkup, Message}, - Bot, -}; - -/// Use this method to edit only the reply markup of messages. -/// -/// On success, the edited [`Message`] is returned. -/// -/// [The official docs](https://core.telegram.org/bots/api#editmessagereplymarkup). -/// -/// [`Message`]: crate::types::Message -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct EditMessageReplyMarkup { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, - message_id: i32, - reply_markup: Option, -} - -#[async_trait::async_trait] -impl Request for EditMessageReplyMarkup { - type Output = Message; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "editMessageReplyMarkup", &self) - .await - } -} - -impl EditMessageReplyMarkup { - pub(crate) fn new(bot: Bot, chat_id: C, message_id: i32) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { bot, chat_id, message_id, reply_markup: None } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`) - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Identifier of the message to edit - pub fn message_id(mut self, val: i32) -> Self { - self.message_id = val; - self - } - - /// A JSON-serialized object for an [inline keyboard]. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/edit_message_text.rs b/src/requests/all/edit_message_text.rs deleted file mode 100644 index 12251313..00000000 --- a/src/requests/all/edit_message_text.rs +++ /dev/null @@ -1,107 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, InlineKeyboardMarkup, Message, ParseMode}, - Bot, -}; - -/// Use this method to edit text and game messages. -/// -/// On success, the edited [`Message`] is returned. -/// -/// [The official docs](https://core.telegram.org/bots/api#editmessagetext). -/// -/// [`Message`]: crate::types::Message -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct EditMessageText { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, - message_id: i32, - text: String, - parse_mode: Option, - disable_web_page_preview: Option, - reply_markup: Option, -} - -#[async_trait::async_trait] -impl Request for EditMessageText { - type Output = Message; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "editMessageText", &self).await - } -} - -impl EditMessageText { - pub(crate) fn new(bot: Bot, chat_id: C, message_id: i32, text: T) -> Self - where - C: Into, - T: Into, - { - let chat_id = chat_id.into(); - let text = text.into(); - Self { - bot, - chat_id, - message_id, - text, - parse_mode: None, - disable_web_page_preview: None, - reply_markup: None, - } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`) - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Identifier of the message to edit - pub fn message_id(mut self, val: i32) -> Self { - self.message_id = val; - self - } - - /// New text of the message. - pub fn text(mut self, val: T) -> Self - where - T: Into, - { - self.text = val.into(); - self - } - - /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, - /// italic, fixed-width text or inline URLs] in your bot's message. - /// - /// [Markdown]: https://core.telegram.org/bots/api#markdown-style - /// [HTML]: https://core.telegram.org/bots/api#html-style - /// [bold, italic, fixed-width text or inline URLs]: https://core.telegram.org/bots/api#formatting-options - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - /// Disables link previews for links in this message. - pub fn disable_web_page_preview(mut self, val: bool) -> Self { - self.disable_web_page_preview = Some(val); - self - } - - /// A JSON-serialized object for an [inline keyboard]. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/export_chat_invite_link.rs b/src/requests/all/export_chat_invite_link.rs deleted file mode 100644 index 51a26437..00000000 --- a/src/requests/all/export_chat_invite_link.rs +++ /dev/null @@ -1,65 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::ChatId, - Bot, -}; - -/// Use this method to generate a new invite link for a chat; any previously -/// generated link is revoked. -/// -/// The bot must be an administrator in the chat for this to work and must have -/// the appropriate admin rights. -/// -/// ## Note -/// Each administrator in a chat generates their own invite links. Bots can't -/// use invite links generated by other administrators. If you want your bot to -/// work with invite links, it will need to generate its own link using -/// [`Bot::export_chat_invite_link`] – after this the link will become available -/// to the bot via the [`Bot::get_chat`] method. If your bot needs to generate a -/// new invite link replacing its previous one, use -/// [`Bot::export_chat_invite_link`] again. -/// -/// [The official docs](https://core.telegram.org/bots/api#exportchatinvitelink). -/// -/// [`Bot::export_chat_invite_link`]: crate::Bot::export_chat_invite_link -/// [`Bot::get_chat`]: crate::Bot::get_chat -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct ExportChatInviteLink { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, -} - -#[async_trait::async_trait] -impl Request for ExportChatInviteLink { - type Output = String; - - /// Returns the new invite link as `String` on success. - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "exportChatInviteLink", &self).await - } -} - -impl ExportChatInviteLink { - pub(crate) fn new(bot: Bot, chat_id: C) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { bot, chat_id } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } -} diff --git a/src/requests/all/forward_message.rs b/src/requests/all/forward_message.rs deleted file mode 100644 index feb9ec02..00000000 --- a/src/requests/all/forward_message.rs +++ /dev/null @@ -1,81 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, Message}, - Bot, -}; - -/// Use this method to forward messages of any kind. -/// -/// [`The official docs`](https://core.telegram.org/bots/api#forwardmessage). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct ForwardMessage { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, - from_chat_id: ChatId, - disable_notification: Option, - message_id: i32, -} - -#[async_trait::async_trait] -impl Request for ForwardMessage { - type Output = Message; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "forwardMessage", &self).await - } -} - -impl ForwardMessage { - pub(crate) fn new(bot: Bot, chat_id: C, from_chat_id: F, message_id: i32) -> Self - where - C: Into, - F: Into, - { - let chat_id = chat_id.into(); - let from_chat_id = from_chat_id.into(); - Self { bot, chat_id, from_chat_id, message_id, disable_notification: None } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Unique identifier for the chat where the original message was sent (or - /// channel username in the format `@channelusername`). - #[allow(clippy::wrong_self_convention)] - pub fn from_chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.from_chat_id = val.into(); - self - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } - - /// Message identifier in the chat specified in [`from_chat_id`]. - /// - /// [`from_chat_id`]: ForwardMessage::from_chat_id - pub fn message_id(mut self, val: i32) -> Self { - self.message_id = val; - self - } -} diff --git a/src/requests/all/get_chat.rs b/src/requests/all/get_chat.rs deleted file mode 100644 index 11406776..00000000 --- a/src/requests/all/get_chat.rs +++ /dev/null @@ -1,50 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{Chat, ChatId}, - Bot, -}; - -/// Use this method to get up to date information about the chat (current name -/// of the user for one-on-one conversations, current username of a user, group -/// or channel, etc.). -/// -/// [The official docs](https://core.telegram.org/bots/api#getchat). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct GetChat { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, -} - -#[async_trait::async_trait] -impl Request for GetChat { - type Output = Chat; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "getChat", &self).await - } -} - -impl GetChat { - pub(crate) fn new(bot: Bot, chat_id: C) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { bot, chat_id } - } - - /// Unique identifier for the target chat or username of the target - /// supergroup or channel (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } -} diff --git a/src/requests/all/get_chat_administrators.rs b/src/requests/all/get_chat_administrators.rs deleted file mode 100644 index 06657ec5..00000000 --- a/src/requests/all/get_chat_administrators.rs +++ /dev/null @@ -1,53 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, ChatMember}, - Bot, -}; - -/// Use this method to get a list of administrators in a chat. -/// -/// If the chat is a group or a supergroup and no administrators were appointed, -/// only the creator will be returned. -/// -/// [The official docs](https://core.telegram.org/bots/api#getchatadministrators). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct GetChatAdministrators { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, -} - -#[async_trait::async_trait] -impl Request for GetChatAdministrators { - type Output = Vec; - - /// On success, returns an array that contains information about all chat - /// administrators except other bots. - async fn send(&self) -> ResponseResult> { - net::request_json(self.bot.client(), self.bot.token(), "getChatAdministrators", &self).await - } -} - -impl GetChatAdministrators { - pub(crate) fn new(bot: Bot, chat_id: C) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { bot, chat_id } - } - - /// Unique identifier for the target chat or username of the target - /// supergroup or channel (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } -} diff --git a/src/requests/all/get_chat_member.rs b/src/requests/all/get_chat_member.rs deleted file mode 100644 index 4ebe3f56..00000000 --- a/src/requests/all/get_chat_member.rs +++ /dev/null @@ -1,55 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, ChatMember}, - Bot, -}; - -/// Use this method to get information about a member of a chat. -/// -/// [The official docs](https://core.telegram.org/bots/api#getchatmember). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct GetChatMember { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, - user_id: i32, -} - -#[async_trait::async_trait] -impl Request for GetChatMember { - type Output = ChatMember; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "getChatMember", &self).await - } -} - -impl GetChatMember { - pub(crate) fn new(bot: Bot, chat_id: C, user_id: i32) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { bot, chat_id, user_id } - } - - /// Unique identifier for the target chat or username of the target - /// supergroup or channel (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Unique identifier of the target user. - pub fn user_id(mut self, val: i32) -> Self { - self.user_id = val; - self - } -} diff --git a/src/requests/all/get_chat_members_count.rs b/src/requests/all/get_chat_members_count.rs deleted file mode 100644 index 5c78596b..00000000 --- a/src/requests/all/get_chat_members_count.rs +++ /dev/null @@ -1,48 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::ChatId, - Bot, -}; - -/// Use this method to get the number of members in a chat. -/// -/// [The official docs](https://core.telegram.org/bots/api#getchatmemberscount). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct GetChatMembersCount { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, -} - -#[async_trait::async_trait] -impl Request for GetChatMembersCount { - type Output = i32; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "getChatMembersCount", &self).await - } -} - -impl GetChatMembersCount { - pub(crate) fn new(bot: Bot, chat_id: C) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { bot, chat_id } - } - - /// Unique identifier for the target chat or username of the target - /// supergroup or channel (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } -} diff --git a/src/requests/all/get_file.rs b/src/requests/all/get_file.rs deleted file mode 100644 index d2d0556c..00000000 --- a/src/requests/all/get_file.rs +++ /dev/null @@ -1,62 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::File, - Bot, -}; - -/// Use this method to get basic info about a file and prepare it for -/// downloading. -/// -/// For the moment, bots can download files of up to `20MB` in size. -/// -/// The file can then be downloaded via the link -/// `https://api.telegram.org/file/bot/`, where `` -/// is taken from the response. It is guaranteed that the link will be valid -/// for at least `1` hour. When the link expires, a new one can be requested by -/// calling [`GetFile`] again. -/// -/// **Note**: This function may not preserve the original file name and MIME -/// type. You should save the file's MIME type and name (if available) when the -/// [`File`] object is received. -/// -/// [The official docs](https://core.telegram.org/bots/api#getfile). -/// -/// [`File`]: crate::types::File -/// [`GetFile`]: self::GetFile -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct GetFile { - #[serde(skip_serializing)] - bot: Bot, - file_id: String, -} - -#[async_trait::async_trait] -impl Request for GetFile { - type Output = File; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "getFile", &self).await - } -} - -impl GetFile { - pub(crate) fn new(bot: Bot, file_id: F) -> Self - where - F: Into, - { - Self { bot, file_id: file_id.into() } - } - - /// File identifier to get info about. - pub fn file_id(mut self, value: F) -> Self - where - F: Into, - { - self.file_id = value.into(); - self - } -} diff --git a/src/requests/all/get_game_high_scores.rs b/src/requests/all/get_game_high_scores.rs deleted file mode 100644 index 1a43d63e..00000000 --- a/src/requests/all/get_game_high_scores.rs +++ /dev/null @@ -1,63 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{GameHighScore, TargetMessage}, - Bot, -}; - -/// Use this method to get data for high score tables. -/// -/// Will return the score of the specified user and several of his neighbors in -/// a game. -/// -/// ## Note -/// This method will currently return scores for the target user, plus two of -/// his closest neighbors on each side. Will also return the top three users if -/// the user and his neighbors are not among them. Please note that this -/// behavior is subject to change. -/// -/// [The official docs](https://core.telegram.org/bots/api#getgamehighscores) -#[derive(Debug, Clone, Serialize)] -pub struct GetGameHighScores { - #[serde(skip_serializing)] - bot: Bot, - #[serde(flatten)] - target: TargetMessage, - user_id: i32, -} - -#[async_trait::async_trait] -impl Request for GetGameHighScores { - type Output = Vec; - - async fn send(&self) -> ResponseResult> { - net::request_json(self.bot.client(), self.bot.token(), "getGameHighScores", &self).await - } -} - -impl GetGameHighScores { - pub(crate) fn new(bot: Bot, target: T, user_id: i32) -> Self - where - T: Into, - { - let target = target.into(); - Self { bot, target, user_id } - } - - /// Target message, either chat id and message id or inline message id. - pub fn target(mut self, val: T) -> Self - where - T: Into, - { - self.target = val.into(); - self - } - - /// Target user id. - pub fn user_id(mut self, val: i32) -> Self { - self.user_id = val; - self - } -} diff --git a/src/requests/all/get_me.rs b/src/requests/all/get_me.rs deleted file mode 100644 index 567b4375..00000000 --- a/src/requests/all/get_me.rs +++ /dev/null @@ -1,33 +0,0 @@ -use crate::{ - net, - requests::{Request, ResponseResult}, - types::Me, - Bot, -}; -use serde::Serialize; - -/// A simple method for testing your bot's auth token. Requires no parameters. -/// -/// [The official docs](https://core.telegram.org/bots/api#getme). -#[derive(Debug, Clone, Serialize)] -pub struct GetMe { - #[serde(skip_serializing)] - bot: Bot, -} - -#[async_trait::async_trait] -impl Request for GetMe { - type Output = Me; - - /// Returns basic information about the bot. - #[allow(clippy::trivially_copy_pass_by_ref)] - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "getMe", &self).await - } -} - -impl GetMe { - pub(crate) fn new(bot: Bot) -> Self { - Self { bot } - } -} diff --git a/src/requests/all/get_my_commands.rs b/src/requests/all/get_my_commands.rs deleted file mode 100644 index 586bfa28..00000000 --- a/src/requests/all/get_my_commands.rs +++ /dev/null @@ -1,33 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::BotCommand, - Bot, -}; - -/// Use this method to get the current list of the bot's commands. -/// -/// [The official docs](https://core.telegram.org/bots/api#getmycommands). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct GetMyCommands { - #[serde(skip_serializing)] - bot: Bot, -} - -#[async_trait::async_trait] -impl Request for GetMyCommands { - type Output = Vec; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "getMyCommands", &self).await - } -} - -impl GetMyCommands { - pub(crate) fn new(bot: Bot) -> Self { - Self { bot } - } -} diff --git a/src/requests/all/get_sticker_set.rs b/src/requests/all/get_sticker_set.rs deleted file mode 100644 index a8a83bea..00000000 --- a/src/requests/all/get_sticker_set.rs +++ /dev/null @@ -1,47 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::StickerSet, - Bot, -}; - -/// Use this method to get a sticker set. -/// -/// [The official docs](https://core.telegram.org/bots/api#getstickerset). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct GetStickerSet { - #[serde(skip_serializing)] - bot: Bot, - name: String, -} - -#[async_trait::async_trait] -impl Request for GetStickerSet { - type Output = StickerSet; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "getStickerSet", &self).await - } -} - -impl GetStickerSet { - pub(crate) fn new(bot: Bot, name: N) -> Self - where - N: Into, - { - let name = name.into(); - Self { bot, name } - } - - /// Name of the sticker set. - pub fn name(mut self, val: T) -> Self - where - T: Into, - { - self.name = val.into(); - self - } -} diff --git a/src/requests/all/get_updates.rs b/src/requests/all/get_updates.rs deleted file mode 100644 index 8bc2db59..00000000 --- a/src/requests/all/get_updates.rs +++ /dev/null @@ -1,122 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{AllowedUpdate, Update}, - Bot, RequestError, -}; -use serde_json::Value; - -/// Use this method to receive incoming updates using long polling ([wiki]). -/// -/// **Notes:** -/// 1. This method will not work if an outgoing webhook is set up. -/// 2. In order to avoid getting duplicate updates, -/// recalculate offset after each server response. -/// -/// [The official docs](https://core.telegram.org/bots/api#getupdates). -/// -/// [wiki]: https://en.wikipedia.org/wiki/Push_technology#Long_polling -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct GetUpdates { - #[serde(skip_serializing)] - bot: Bot, - pub(crate) offset: Option, - pub(crate) limit: Option, - pub(crate) timeout: Option, - pub(crate) allowed_updates: Option>, -} - -#[async_trait::async_trait] -impl Request for GetUpdates { - type Output = Vec>; - - /// Deserialize to `Vec>` instead of - /// `Vec`, because we want to parse the rest of updates even if our - /// library hasn't parsed one. - async fn send(&self) -> ResponseResult>> { - let value: Value = - net::request_json(self.bot.client(), self.bot.token(), "getUpdates", &self).await?; - - match value { - Value::Array(array) => Ok(array - .into_iter() - .map(|value| Update::try_parse(&value).map_err(|error| (value, error))) - .collect()), - _ => Err(RequestError::InvalidJson( - serde_json::from_value::>(value) - .expect_err("get_update must return Value::Array"), - )), - } - } -} - -impl GetUpdates { - pub(crate) fn new(bot: Bot) -> Self { - Self { bot, offset: None, limit: None, timeout: None, allowed_updates: None } - } - - /// Identifier of the first update to be returned. - /// - /// Must be greater by one than the highest among the identifiers of - /// previously received updates. By default, updates starting with the - /// earliest unconfirmed update are returned. An update is considered - /// confirmed as soon as [`GetUpdates`] is called with an [`offset`] - /// higher than its [`id`]. The negative offset can be specified to - /// retrieve updates starting from `-offset` update from the end of the - /// updates queue. All previous updates will forgotten. - /// - /// [`GetUpdates`]: self::GetUpdates - /// [`offset`]: self::GetUpdates::offset - /// [`id`]: crate::types::Update::id - pub fn offset(mut self, value: i32) -> Self { - self.offset = Some(value); - self - } - - /// Limits the number of updates to be retrieved. - /// - /// Values between `1`—`100` are accepted. Defaults to `100`. - pub fn limit(mut self, value: u8) -> Self { - self.limit = Some(value); - self - } - - /// Timeout in seconds for long polling. - /// - /// Defaults to `0`, i.e. usual short polling. Should be positive, short - /// polling should be used for testing purposes only. - pub fn timeout(mut self, value: u32) -> Self { - self.timeout = Some(value); - self - } - - /// List the types of updates you want your bot to receive. - /// - /// For example, specify [[`Message`], [`EditedChannelPost`], - /// [`CallbackQuery`]] to only receive updates of these types. - /// See [`AllowedUpdate`] for a complete list of available update types. - /// - /// Specify an empty list to receive all updates regardless of type - /// (default). If not specified, the previous setting will be used. - /// - /// **Note:** - /// This parameter doesn't affect updates created before the call to the - /// [`Bot::get_updates`], so unwanted updates may be received for a short - /// period of time. - /// - /// [`Message`]: self::AllowedUpdate::Message - /// [`EditedChannelPost`]: self::AllowedUpdate::EditedChannelPost - /// [`CallbackQuery`]: self::AllowedUpdate::CallbackQuery - /// [`AllowedUpdate`]: self::AllowedUpdate - /// [`Bot::get_updates`]: crate::Bot::get_updates - pub fn allowed_updates(mut self, value: T) -> Self - where - T: Into>, - { - self.allowed_updates = Some(value.into()); - self - } -} diff --git a/src/requests/all/get_user_profile_photos.rs b/src/requests/all/get_user_profile_photos.rs deleted file mode 100644 index 663a07a4..00000000 --- a/src/requests/all/get_user_profile_photos.rs +++ /dev/null @@ -1,58 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::UserProfilePhotos, - Bot, -}; - -/// Use this method to get a list of profile pictures for a user. -/// -/// [The official docs](https://core.telegram.org/bots/api#getuserprofilephotos). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct GetUserProfilePhotos { - #[serde(skip_serializing)] - bot: Bot, - user_id: i32, - offset: Option, - limit: Option, -} - -#[async_trait::async_trait] -impl Request for GetUserProfilePhotos { - type Output = UserProfilePhotos; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "getUserProfilePhotos", &self).await - } -} - -impl GetUserProfilePhotos { - pub(crate) fn new(bot: Bot, user_id: i32) -> Self { - Self { bot, user_id, offset: None, limit: None } - } - - /// Unique identifier of the target user. - pub fn user_id(mut self, val: i32) -> Self { - self.user_id = val; - self - } - - /// Sequential number of the first photo to be returned. By default, all - /// photos are returned. - pub fn offset(mut self, val: i32) -> Self { - self.offset = Some(val); - self - } - - /// Limits the number of photos to be retrieved. Values between 1—100 are - /// accepted. - /// - /// Defaults to 100. - pub fn limit(mut self, val: i32) -> Self { - self.limit = Some(val); - self - } -} diff --git a/src/requests/all/get_webhook_info.rs b/src/requests/all/get_webhook_info.rs deleted file mode 100644 index 51424471..00000000 --- a/src/requests/all/get_webhook_info.rs +++ /dev/null @@ -1,38 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::WebhookInfo, - Bot, -}; - -/// Use this method to get current webhook status. -/// -/// If the bot is using [`Bot::get_updates`], will return an object with the url -/// field empty. -/// -/// [The official docs](https://core.telegram.org/bots/api#getwebhookinfo). -/// -/// [`Bot::get_updates`]: crate::Bot::get_updates -#[derive(Debug, Clone, Serialize)] -pub struct GetWebhookInfo { - #[serde(skip_serializing)] - bot: Bot, -} - -#[async_trait::async_trait] -impl Request for GetWebhookInfo { - type Output = WebhookInfo; - - #[allow(clippy::trivially_copy_pass_by_ref)] - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "getWebhookInfo", &self).await - } -} - -impl GetWebhookInfo { - pub(crate) fn new(bot: Bot) -> Self { - Self { bot } - } -} diff --git a/src/requests/all/kick_chat_member.rs b/src/requests/all/kick_chat_member.rs deleted file mode 100644 index 31953949..00000000 --- a/src/requests/all/kick_chat_member.rs +++ /dev/null @@ -1,72 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, True}, - Bot, -}; - -/// Use this method to kick a user from a group, a supergroup or a channel. -/// -/// In the case of supergroups and channels, the user will not be able to return -/// to the group on their own using invite links, etc., unless [unbanned] first. -/// The bot must be an administrator in the chat for this to work and must have -/// the appropriate admin rights. -/// -/// [The official docs](https://core.telegram.org/bots/api#kickchatmember). -/// -/// [unbanned]: crate::Bot::unban_chat_member -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct KickChatMember { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, - user_id: i32, - until_date: Option, -} - -#[async_trait::async_trait] -impl Request for KickChatMember { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "kickChatMember", &self).await - } -} - -impl KickChatMember { - pub(crate) fn new(bot: Bot, chat_id: C, user_id: i32) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { bot, chat_id, user_id, until_date: None } - } - - /// Unique identifier for the target group or username of the target - /// supergroup or channel (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Unique identifier of the target user. - pub fn user_id(mut self, val: i32) -> Self { - self.user_id = val; - self - } - - /// 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. - pub fn until_date(mut self, val: i32) -> Self { - self.until_date = Some(val); - self - } -} diff --git a/src/requests/all/leave_chat.rs b/src/requests/all/leave_chat.rs deleted file mode 100644 index d0411efb..00000000 --- a/src/requests/all/leave_chat.rs +++ /dev/null @@ -1,48 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, True}, - Bot, -}; - -/// Use this method for your bot to leave a group, supergroup or channel. -/// -/// [The official docs](https://core.telegram.org/bots/api#leavechat). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct LeaveChat { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, -} - -#[async_trait::async_trait] -impl Request for LeaveChat { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "leaveChat", &self).await - } -} - -impl LeaveChat { - pub(crate) fn new(bot: Bot, chat_id: C) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { bot, chat_id } - } - - /// Unique identifier for the target chat or username of the target - /// supergroup or channel (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } -} diff --git a/src/requests/all/mod.rs b/src/requests/all/mod.rs deleted file mode 100644 index 3de4d87a..00000000 --- a/src/requests/all/mod.rs +++ /dev/null @@ -1,152 +0,0 @@ -mod add_sticker_to_set; -mod answer_callback_query; -mod answer_inline_query; -mod answer_pre_checkout_query; -mod answer_shipping_query; -mod create_new_sticker_set; -mod delete_chat_photo; -mod delete_chat_sticker_set; -mod delete_message; -mod delete_sticker_from_set; -mod delete_webhook; -mod edit_inline_message_caption; -mod edit_inline_message_live_location; -mod edit_inline_message_media; -mod edit_inline_message_reply_markup; -mod edit_inline_message_text; -mod edit_message_caption; -mod edit_message_live_location; -mod edit_message_media; -mod edit_message_reply_markup; -mod edit_message_text; -mod export_chat_invite_link; -mod forward_message; -mod get_chat; -mod get_chat_administrators; -mod get_chat_member; -mod get_chat_members_count; -mod get_file; -mod get_game_high_scores; -mod get_me; -mod get_my_commands; -mod get_sticker_set; -mod get_updates; -mod get_user_profile_photos; -mod get_webhook_info; -mod kick_chat_member; -mod leave_chat; -mod pin_chat_message; -mod promote_chat_member; -mod restrict_chat_member; -mod send_animation; -mod send_audio; -mod send_chat_action; -mod send_contact; -mod send_dice; -mod send_document; -mod send_game; -mod send_invoice; -mod send_location; -mod send_media_group; -mod send_message; -mod send_photo; -mod send_poll; -mod send_sticker; -mod send_venue; -mod send_video; -mod send_video_note; -mod send_voice; -mod set_chat_administrator_custom_title; -mod set_chat_description; -mod set_chat_permissions; -mod set_chat_photo; -mod set_chat_sticker_set; -mod set_chat_title; -mod set_game_score; -mod set_my_commands; -mod set_sticker_position_in_set; -mod set_sticker_set_thumb; -mod set_webhook; -mod stop_inline_message_live_location; -mod stop_message_live_location; -mod stop_poll; -mod unban_chat_member; -mod unpin_chat_message; -mod upload_sticker_file; - -pub use add_sticker_to_set::*; -pub use answer_callback_query::*; -pub use answer_inline_query::*; -pub use answer_pre_checkout_query::*; -pub use answer_shipping_query::*; -pub use create_new_sticker_set::*; -pub use delete_chat_photo::*; -pub use delete_chat_sticker_set::*; -pub use delete_message::*; -pub use delete_sticker_from_set::*; -pub use delete_webhook::*; -pub use edit_inline_message_caption::*; -pub use edit_inline_message_live_location::*; -pub use edit_inline_message_media::*; -pub use edit_inline_message_reply_markup::*; -pub use edit_inline_message_text::*; -pub use edit_message_caption::*; -pub use edit_message_live_location::*; -pub use edit_message_media::*; -pub use edit_message_reply_markup::*; -pub use edit_message_text::*; -pub use export_chat_invite_link::*; -pub use forward_message::*; -pub use get_chat::*; -pub use get_chat_administrators::*; -pub use get_chat_member::*; -pub use get_chat_members_count::*; -pub use get_file::*; -pub use get_game_high_scores::*; -pub use get_me::*; -pub use get_my_commands::*; -pub use get_sticker_set::*; -pub use get_updates::*; -pub use get_user_profile_photos::*; -pub use get_webhook_info::*; -pub use kick_chat_member::*; -pub use leave_chat::*; -pub use pin_chat_message::*; -pub use promote_chat_member::*; -pub use restrict_chat_member::*; -pub use send_animation::*; -pub use send_audio::*; -pub use send_chat_action::*; -pub use send_contact::*; -pub use send_dice::*; -pub use send_document::*; -pub use send_game::*; -pub use send_invoice::*; -pub use send_location::*; -pub use send_media_group::*; -pub use send_message::*; -pub use send_photo::*; -pub use send_poll::*; -pub use send_sticker::*; -pub use send_venue::*; -pub use send_video::*; -pub use send_video_note::*; -pub use send_voice::*; -pub use set_chat_administrator_custom_title::*; -pub use set_chat_description::*; -pub use set_chat_permissions::*; -pub use set_chat_photo::*; -pub use set_chat_sticker_set::*; -pub use set_chat_title::*; -pub use set_game_score::*; -pub use set_my_commands::*; -pub use set_sticker_position_in_set::*; -pub use set_sticker_set_thumb::*; -pub use set_webhook::*; -pub use std::pin::Pin; -pub use stop_inline_message_live_location::*; -pub use stop_message_live_location::*; -pub use stop_poll::*; -pub use unban_chat_member::*; -pub use unpin_chat_message::*; -pub use upload_sticker_file::*; diff --git a/src/requests/all/pin_chat_message.rs b/src/requests/all/pin_chat_message.rs deleted file mode 100644 index ae1f3227..00000000 --- a/src/requests/all/pin_chat_message.rs +++ /dev/null @@ -1,69 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, True}, - Bot, -}; - -/// Use this method to pin a message in a group, a supergroup, or a channel. -/// -/// The bot must be an administrator in the chat for this to work and must have -/// the `can_pin_messages` admin right in the supergroup or `can_edit_messages` -/// admin right in the channel. -/// -/// [The official docs](https://core.telegram.org/bots/api#pinchatmessage). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct PinChatMessage { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, - message_id: i32, - disable_notification: Option, -} - -#[async_trait::async_trait] -impl Request for PinChatMessage { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "pinChatMessage", &self).await - } -} - -impl PinChatMessage { - pub(crate) fn new(bot: Bot, chat_id: C, message_id: i32) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { bot, chat_id, message_id, disable_notification: None } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Identifier of a message to pin. - pub fn message_id(mut self, val: i32) -> Self { - self.message_id = val; - self - } - - /// Pass `true`, if it is not necessary to send a notification to all chat - /// members about the new pinned message. - /// - /// Notifications are always disabled in channels. - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } -} diff --git a/src/requests/all/promote_chat_member.rs b/src/requests/all/promote_chat_member.rs deleted file mode 100644 index 468b3c48..00000000 --- a/src/requests/all/promote_chat_member.rs +++ /dev/null @@ -1,134 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, True}, - Bot, -}; - -/// Use this method to promote or demote a user in a supergroup or a channel. -/// -/// The bot must be an administrator in the chat for this to work and must have -/// the appropriate admin rights. Pass False for all boolean parameters to -/// demote a user. -/// -/// [The official docs](https://core.telegram.org/bots/api#promotechatmember). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct PromoteChatMember { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, - user_id: i32, - can_change_info: Option, - can_post_messages: Option, - can_edit_messages: Option, - can_delete_messages: Option, - can_invite_users: Option, - can_restrict_members: Option, - can_pin_messages: Option, - can_promote_members: Option, -} - -#[async_trait::async_trait] -impl Request for PromoteChatMember { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "promoteChatMember", &self).await - } -} - -impl PromoteChatMember { - pub(crate) fn new(bot: Bot, chat_id: C, user_id: i32) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { - bot, - chat_id, - user_id, - can_change_info: None, - can_post_messages: None, - can_edit_messages: None, - can_delete_messages: None, - can_invite_users: None, - can_restrict_members: None, - can_pin_messages: None, - can_promote_members: None, - } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Unique identifier of the target user. - pub fn user_id(mut self, val: i32) -> Self { - self.user_id = val; - self - } - - /// Pass `true`, if the administrator can change chat title, photo and other - /// settings. - pub fn can_change_info(mut self, val: bool) -> Self { - self.can_change_info = Some(val); - self - } - - /// Pass `true`, if the administrator can create channel posts, channels - /// only. - pub fn can_post_messages(mut self, val: bool) -> Self { - self.can_post_messages = Some(val); - self - } - - /// Pass `true`, if the administrator can edit messages of other users and - /// can pin messages, channels only. - pub fn can_edit_messages(mut self, val: bool) -> Self { - self.can_edit_messages = Some(val); - self - } - - /// Pass `true`, if the administrator can delete messages of other users. - pub fn can_delete_messages(mut self, val: bool) -> Self { - self.can_delete_messages = Some(val); - self - } - - /// Pass `true`, if the administrator can invite new users to the chat. - pub fn can_invite_users(mut self, val: bool) -> Self { - self.can_invite_users = Some(val); - self - } - - /// Pass `true`, if the administrator can restrict, ban or unban chat - /// members. - pub fn can_restrict_members(mut self, val: bool) -> Self { - self.can_restrict_members = Some(val); - self - } - - /// Pass `true`, if the administrator can pin messages, supergroups only. - pub fn can_pin_messages(mut self, val: bool) -> Self { - self.can_pin_messages = Some(val); - self - } - - /// Pass `true`, if the administrator 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 by him). - pub fn can_promote_members(mut self, val: bool) -> Self { - self.can_promote_members = Some(val); - self - } -} diff --git a/src/requests/all/restrict_chat_member.rs b/src/requests/all/restrict_chat_member.rs deleted file mode 100644 index 6b825b91..00000000 --- a/src/requests/all/restrict_chat_member.rs +++ /dev/null @@ -1,76 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, ChatPermissions, True}, - Bot, -}; - -/// Use this method to restrict a user in a supergroup. -/// -/// The bot must be an administrator in the supergroup for this to work and must -/// have the appropriate admin rights. Pass `true` for all permissions to lift -/// restrictions from a user. -/// -/// [The official docs](https://core.telegram.org/bots/api#restrictchatmember). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct RestrictChatMember { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, - user_id: i32, - permissions: ChatPermissions, - until_date: Option, -} - -#[async_trait::async_trait] -impl Request for RestrictChatMember { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "restrictChatMember", &self).await - } -} - -impl RestrictChatMember { - pub(crate) fn new(bot: Bot, chat_id: C, user_id: i32, permissions: ChatPermissions) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { bot, chat_id, user_id, permissions, until_date: None } - } - - /// Unique identifier for the target chat or username of the target - /// supergroup (in the format `@supergroupusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Unique identifier of the target user. - pub fn user_id(mut self, val: i32) -> Self { - self.user_id = val; - self - } - - /// New user permissions. - pub fn permissions(mut self, val: ChatPermissions) -> Self { - self.permissions = val; - self - } - - /// Date when restrictions will be lifted for the user, unix time. - /// - /// If user is restricted for more than 366 days or less than 30 seconds - /// from the current time, they are considered to be restricted forever. - pub fn until_date(mut self, val: i32) -> Self { - self.until_date = Some(val); - self - } -} diff --git a/src/requests/all/send_animation.rs b/src/requests/all/send_animation.rs deleted file mode 100644 index df52f20c..00000000 --- a/src/requests/all/send_animation.rs +++ /dev/null @@ -1,193 +0,0 @@ -use crate::{ - net, - requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult}, - types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, - Bot, -}; - -/// Use this method to send animation files (GIF or H.264/MPEG-4 AVC video -/// without sound). -/// -/// Bots can currently send animation files of up to 50 MB in size, this limit -/// may be changed in the future. -/// -/// [The official docs](https://core.telegram.org/bots/api#sendanimation). -#[derive(Debug, Clone)] -pub struct SendAnimation { - bot: Bot, - pub chat_id: ChatId, - pub animation: InputFile, - pub duration: Option, - pub width: Option, - pub height: Option, - pub thumb: Option, - pub caption: Option, - pub parse_mode: Option, - pub disable_notification: Option, - pub reply_to_message_id: Option, - pub reply_markup: Option, -} - -#[async_trait::async_trait] -impl RequestWithFile for SendAnimation { - type Output = Message; - - async fn send(&self) -> tokio::io::Result> { - let mut builder = FormBuilder::new() - .add_text("chat_id", &self.chat_id) - .add_input_file("animation", &self.animation) - .await? - .add_text("duration", &self.duration) - .add_text("width", &self.width) - .add_text("height", &self.height) - .add_text("caption", &self.caption) - .add_text("parse_mode", &self.parse_mode) - .add_text("disable_notification", &self.disable_notification) - .add_text("reply_to_message_id", &self.reply_to_message_id) - .add_text("reply_markup", &self.reply_markup); - if let Some(thumb) = self.thumb.as_ref() { - builder = builder.add_input_file("thumb", thumb).await?; - } - Ok(net::request_multipart( - self.bot.client(), - self.bot.token(), - "sendAnimation", - builder.build(), - ) - .await) - } -} - -impl SendAnimation { - pub(crate) fn new(bot: Bot, chat_id: C, animation: InputFile) -> Self - where - C: Into, - { - Self { - bot, - chat_id: chat_id.into(), - animation, - duration: None, - width: None, - height: None, - thumb: None, - caption: None, - parse_mode: None, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, value: T) -> Self - where - T: Into, - { - self.chat_id = value.into(); - self - } - - /// Animation to send. - /// - /// Pass [`InputFile::FileId`] to send a file that exists on the Telegram - /// servers (recommended), pass an [`InputFile::Url`] for Telegram to get a - /// file from the Internet (20MB max.), pass [`InputFile::File`] to upload - /// a file from the file system or [`InputFile::Memory`] to upload a file - /// from memory (50MB max. each). [More info on Sending Files »]. - /// - /// [`InputFile::FileId`]: crate::types::InputFile::FileId - /// [`InputFile::Url`]: crate::types::InputFile::Url - /// [`InputFile::File`]: crate::types::InputFile::File - /// [`InputFile::Memory`]: crate::types::InputFile::Memory - /// - /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files - pub fn animation(mut self, val: InputFile) -> Self { - self.animation = val; - self - } - - /// Duration of sent animation in seconds. - pub fn duration(mut self, value: u32) -> Self { - self.duration = Some(value); - self - } - - /// Animation width. - pub fn width(mut self, value: u32) -> Self { - self.width = Some(value); - self - } - - /// Animation height. - pub fn height(mut self, value: u32) -> Self { - self.height = Some(value); - self - } - - /// Thumbnail of the file sent; can be ignored if thumbnail generation for - /// the file is supported server-side. - /// - /// The thumbnail should be in JPEG format and less than 200kB in size. A - /// thumbnail‘s width and height should not exceed 320. Ignored if the - /// animation is not uploaded using [`InputFile::File`] or - /// [`InputFile::Memory`]. Thumbnails can’t be reused and can be only - /// uploaded as a new file. Pass [`InputFile::File`] to upload a file from - /// the file system or [`InputFile::Memory`] to upload a file from memory. - /// [More info on Sending Files »]. - /// - /// [`InputFile::File`]: crate::types::InputFile::File - /// [`InputFile::Memory`]: crate::types::InputFile::Memory - /// - /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files - pub fn thumb(mut self, value: InputFile) -> Self { - self.thumb = Some(value); - self - } - - /// Animation caption, `0`-`1024` characters. - pub fn caption(mut self, value: T) -> Self - where - T: Into, - { - self.caption = Some(value.into()); - self - } - - /// Send [Markdown] or [HTML], if you want Telegram apps to show - /// [bold, italic, fixed-width text or inline URLs] in the media caption. - /// - /// [Markdown]: crate::types::ParseMode::Markdown - /// [HTML]: crate::types::ParseMode::HTML - /// [bold, italic, fixed-width text or inline URLs]: - /// crate::types::ParseMode - pub fn parse_mode(mut self, value: ParseMode) -> Self { - self.parse_mode = Some(value); - self - } - - /// Sends the message silently. Users will receive a notification with no - /// sound. - pub fn disable_notification(mut self, value: bool) -> Self { - self.disable_notification = Some(value); - self - } - - /// If the message is a reply, [id] of the original message. - /// - /// [id]: crate::types::Message::id - pub fn reply_to_message_id(mut self, value: i32) -> Self { - self.reply_to_message_id = Some(value); - self - } - - /// Additional interface options. - pub fn reply_markup(mut self, value: T) -> Self - where - T: Into, - { - self.reply_markup = Some(value.into()); - self - } -} diff --git a/src/requests/all/send_audio.rs b/src/requests/all/send_audio.rs deleted file mode 100644 index 4d9d751a..00000000 --- a/src/requests/all/send_audio.rs +++ /dev/null @@ -1,206 +0,0 @@ -use crate::{ - net, - requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult}, - types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, - Bot, -}; - -/// Use this method to send audio files, if you want Telegram clients to display -/// them in the music player. -/// -/// Your audio must be in the .MP3 or .M4A format. Bots can currently send audio -/// files of up to 50 MB in size, this limit may be changed in the future. -/// -/// For sending voice messages, use the [`Bot::send_voice`] method instead. -/// -/// [The official docs](https://core.telegram.org/bots/api#sendaudio). -/// -/// [`Bot::send_voice`]: crate::Bot::send_voice -#[derive(Debug, Clone)] -pub struct SendAudio { - bot: Bot, - chat_id: ChatId, - audio: InputFile, - caption: Option, - parse_mode: Option, - duration: Option, - performer: Option, - title: Option, - thumb: Option, - disable_notification: Option, - reply_to_message_id: Option, - reply_markup: Option, -} - -#[async_trait::async_trait] -impl RequestWithFile for SendAudio { - type Output = Message; - - async fn send(&self) -> tokio::io::Result> { - let mut builder = FormBuilder::new() - .add_text("chat_id", &self.chat_id) - .add_input_file("audio", &self.audio) - .await? - .add_text("caption", &self.caption) - .add_text("parse_mode", &self.parse_mode) - .add_text("duration", &self.duration) - .add_text("performer", &self.performer) - .add_text("title", &self.title) - .add_text("disable_notification", &self.disable_notification) - .add_text("reply_to_message_id", &self.reply_to_message_id) - .add_text("reply_markup", &self.reply_markup); - if let Some(thumb) = self.thumb.as_ref() { - builder = builder.add_input_file("thumb", thumb).await?; - } - Ok(net::request_multipart( - self.bot.client(), - self.bot.token(), - "sendAudio", - builder.build(), - ) - .await) - } -} - -impl SendAudio { - pub(crate) fn new(bot: Bot, chat_id: C, audio: InputFile) -> Self - where - C: Into, - { - Self { - bot, - chat_id: chat_id.into(), - audio, - caption: None, - parse_mode: None, - duration: None, - performer: None, - title: None, - thumb: None, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Audio file to send. - /// - /// Pass [`InputFile::FileId`] to send an audio file that exists on the - /// Telegram servers (recommended), pass an [`InputFile::Url`] for Telegram - /// to get a file from the Internet (20MB max.), pass [`InputFile::File`] - /// to upload a file from the file system or [`InputFile::Memory`] to - /// upload a file from memory (50MB max. each). - /// [More info on Sending Files »]. - /// - /// [`InputFile::FileId`]: crate::types::InputFile::FileId - /// [`InputFile::Url`]: crate::types::InputFile::Url - /// [`InputFile::File`]: crate::types::InputFile::File - /// [`InputFile::Memory`]: crate::types::InputFile::Memory - /// - /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files - pub fn audio(mut self, val: InputFile) -> Self { - self.audio = val; - self - } - - /// Audio caption, 0-1024 characters. - pub fn caption(mut self, val: T) -> Self - where - T: Into, - { - self.caption = Some(val.into()); - self - } - - /// Send [Markdown] or [HTML], if you want Telegram apps to show - /// [bold, italic, fixed-width text or inline URLs] in the media caption. - /// - /// [Markdown]: crate::types::ParseMode::Markdown - /// [HTML]: crate::types::ParseMode::HTML - /// [bold, italic, fixed-width text or inline URLs]: - /// crate::types::ParseMode - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - /// Duration of the audio in seconds. - pub fn duration(mut self, val: i32) -> Self { - self.duration = Some(val); - self - } - - /// Performer. - pub fn performer(mut self, val: T) -> Self - where - T: Into, - { - self.performer = Some(val.into()); - self - } - - /// Track name. - pub fn title(mut self, val: T) -> Self - where - T: Into, - { - self.title = Some(val.into()); - self - } - - /// Thumbnail of the file sent; can be ignored if thumbnail generation for - /// the file is supported server-side. - /// - /// The thumbnail should be in JPEG format and less than 200kB in size. A - /// thumbnail‘s width and height should not exceed 320. Ignored if the - /// audio file is not uploaded using [`InputFile::File`] or - /// [`InputFile::Memory`]. Thumbnails can’t be reused and can be only - /// uploaded as a new file. Pass [`InputFile::File`] to upload a file from - /// the file system or [`InputFile::Memory`] to upload a file from memory. - /// [More info on Sending Files »]. - /// - /// [`InputFile::File`]: crate::types::InputFile::File - /// [`InputFile::Memory`]: crate::types::InputFile::Memory - /// - /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files - pub fn thumb(mut self, val: InputFile) -> Self { - self.thumb = Some(val); - self - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } - - /// If the message is a reply, ID of the original message. - pub fn reply_to_message_id(mut self, val: i32) -> Self { - self.reply_to_message_id = Some(val); - self - } - - /// Additional interface options. A JSON-serialized object for an [inline - /// keyboard], [custom reply keyboard], instructions to remove reply - /// keyboard or to force a reply from the user. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards - pub fn reply_markup(mut self, val: ReplyMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/send_chat_action.rs b/src/requests/all/send_chat_action.rs deleted file mode 100644 index 5fe853b2..00000000 --- a/src/requests/all/send_chat_action.rs +++ /dev/null @@ -1,105 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, True}, - Bot, -}; - -/// Use this method when you need to tell the user that something is happening -/// on the bot's side. -/// -/// The status is set for 5 seconds or less (when a message arrives from your -/// bot, Telegram clients clear its typing status). -/// -/// ## Note -/// Example: The [ImageBot] needs some time to process a request and upload the -/// image. Instead of sending a text message along the lines of “Retrieving -/// image, please wait…”, the bot may use [`Bot::send_chat_action`] with `action -/// = upload_photo`. The user will see a `sending photo` status for the bot. -/// -/// We only recommend using this method when a response from the bot will take a -/// **noticeable** amount of time to arrive. -/// -/// [ImageBot]: https://t.me/imagebot -/// [`Bot::send_chat_action`]: crate::Bot::send_chat_action -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SendChatAction { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, - action: SendChatActionKind, -} - -/// A type of action used in [`SendChatAction`]. -/// -/// [`SendChatAction`]: crate::requests::SendChatAction -#[derive(Copy, Clone, Debug, Hash, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum SendChatActionKind { - /// For [text messages](crate::Bot::send_message). - Typing, - - /// For [photos](crate::Bot::send_photo). - UploadPhoto, - - /// For [videos](crate::Bot::send_video). - RecordVideo, - - /// For [videos](crate::Bot::send_video). - UploadVideo, - - /// For [audio files](crate::Bot::send_audio). - RecordAudio, - - /// For [audio files](crate::Bot::send_audio). - UploadAudio, - - /// For [general files](crate::Bot::send_document). - UploadDocument, - - /// For [location data](crate::Bot::send_location). - FindLocation, - - /// For [video notes](crate::Bot::send_video_note). - RecordVideoNote, - - /// For [video notes](crate::Bot::send_video_note). - UploadVideoNote, -} - -#[async_trait::async_trait] -impl Request for SendChatAction { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "sendChatAction", &self).await - } -} - -impl SendChatAction { - pub(crate) fn new(bot: Bot, chat_id: C, action: SendChatActionKind) -> Self - where - C: Into, - { - Self { bot, chat_id: chat_id.into(), action } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Type of action to broadcast. - pub fn action(mut self, val: SendChatActionKind) -> Self { - self.action = val; - self - } -} diff --git a/src/requests/all/send_contact.rs b/src/requests/all/send_contact.rs deleted file mode 100644 index ef3ff034..00000000 --- a/src/requests/all/send_contact.rs +++ /dev/null @@ -1,129 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, Message, ReplyMarkup}, - Bot, -}; - -/// Use this method to send phone contacts. -/// -/// [The official docs](https://core.telegram.org/bots/api#sendcontact). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SendContact { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, - phone_number: String, - first_name: String, - last_name: Option, - vcard: Option, - disable_notification: Option, - reply_to_message_id: Option, - reply_markup: Option, -} - -#[async_trait::async_trait] -impl Request for SendContact { - type Output = Message; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "sendContact", &self).await - } -} - -impl SendContact { - pub(crate) fn new(bot: Bot, chat_id: C, phone_number: P, first_name: F) -> Self - where - C: Into, - P: Into, - F: Into, - { - let chat_id = chat_id.into(); - let phone_number = phone_number.into(); - let first_name = first_name.into(); - Self { - bot, - chat_id, - phone_number, - first_name, - last_name: None, - vcard: None, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Contact's phone number. - pub fn phone_number(mut self, val: T) -> Self - where - T: Into, - { - self.phone_number = val.into(); - self - } - - /// Contact's first name. - pub fn first_name(mut self, val: T) -> Self - where - T: Into, - { - self.first_name = val.into(); - self - } - - /// Contact's last name. - pub fn last_name(mut self, val: T) -> Self - where - T: Into, - { - self.last_name = Some(val.into()); - self - } - - /// Additional data about the contact in the form of a [vCard], 0-2048 - /// bytes. - /// - /// [vCard]: https://en.wikipedia.org/wiki/VCard - pub fn vcard(mut self, val: T) -> Self - where - T: Into, - { - self.vcard = Some(val.into()); - self - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } - - /// If the message is a reply, ID of the original message. - pub fn reply_to_message_id(mut self, val: i32) -> Self { - self.reply_to_message_id = Some(val); - self - } - - /// Additional interface options. - pub fn reply_markup(mut self, val: ReplyMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/send_dice.rs b/src/requests/all/send_dice.rs deleted file mode 100644 index 663d7001..00000000 --- a/src/requests/all/send_dice.rs +++ /dev/null @@ -1,94 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, DiceEmoji, Message, ReplyMarkup}, - Bot, -}; - -/// Use this method to send an animated emoji that will display a random value. -/// -/// [The official docs](https://core.telegram.org/bots/api#senddice). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SendDice { - #[serde(skip_serializing)] - bot: Bot, - - chat_id: ChatId, - #[serde(flatten)] - emoji: Option, - disable_notification: Option, - reply_to_message_id: Option, - reply_markup: Option, -} - -#[async_trait::async_trait] -impl Request for SendDice { - type Output = Message; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "sendDice", &self).await - } -} - -impl SendDice { - pub(crate) fn new(bot: Bot, chat_id: C) -> Self - where - C: Into, - { - Self { - bot, - chat_id: chat_id.into(), - emoji: None, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, value: T) -> Self - where - T: Into, - { - self.chat_id = value.into(); - self - } - - /// Emoji on which the dice throw animation is based. - pub fn emoji(mut self, val: DiceEmoji) -> Self { - self.emoji = Some(val); - self - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, value: bool) -> Self { - self.disable_notification = Some(value); - self - } - - /// If the message is a reply, ID of the original message. - pub fn reply_to_message_id(mut self, value: i32) -> Self { - self.reply_to_message_id = Some(value); - self - } - - /// Additional interface options. - /// - /// A JSON-serialized object for an [inline keyboard], [custom reply - /// keyboard], instructions to remove reply keyboard or to force a reply - /// from the user. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards - pub fn reply_markup(mut self, val: ReplyMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/send_document.rs b/src/requests/all/send_document.rs deleted file mode 100644 index 3bed2c65..00000000 --- a/src/requests/all/send_document.rs +++ /dev/null @@ -1,163 +0,0 @@ -use crate::{ - net, - requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult}, - types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, - Bot, -}; - -/// Use this method to send general files. -/// -/// Bots can currently send files of any type of up to 50 MB in size, this limit -/// may be changed in the future. -/// -/// [The official docs](https://core.telegram.org/bots/api#senddocument). -#[derive(Debug, Clone)] -pub struct SendDocument { - bot: Bot, - chat_id: ChatId, - document: InputFile, - thumb: Option, - caption: Option, - parse_mode: Option, - disable_notification: Option, - reply_to_message_id: Option, - reply_markup: Option, -} - -#[async_trait::async_trait] -impl RequestWithFile for SendDocument { - type Output = Message; - - async fn send(&self) -> tokio::io::Result> { - let mut builder = FormBuilder::new() - .add_text("chat_id", &self.chat_id) - .add_input_file("document", &self.document) - .await? - .add_text("caption", &self.caption) - .add_text("parse_mode", &self.parse_mode) - .add_text("disable_notification", &self.disable_notification) - .add_text("reply_to_message_id", &self.reply_to_message_id) - .add_text("reply_markup", &self.reply_markup); - if let Some(thumb) = self.thumb.as_ref() { - builder = builder.add_input_file("thumb", thumb).await?; - } - Ok(net::request_multipart( - self.bot.client(), - self.bot.token(), - "sendDocument", - builder.build(), - ) - .await) - } -} - -impl SendDocument { - pub(crate) fn new(bot: Bot, chat_id: C, document: InputFile) -> Self - where - C: Into, - { - Self { - bot, - chat_id: chat_id.into(), - document, - thumb: None, - caption: None, - parse_mode: None, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// File to send. - /// - /// Pass [`InputFile::FileId`] to send a file that exists on the Telegram - /// servers (recommended), pass an [`InputFile::Url`] for Telegram to get a - /// file from the Internet (20MB max.), pass [`InputFile::File`] to upload - /// a file from the file system or [`InputFile::Memory`] to upload a file - /// from memory (50MB max. each). [More info on Sending Files »]. - /// - /// [`InputFile::FileId`]: crate::types::InputFile::FileId - /// [`InputFile::Url`]: crate::types::InputFile::Url - /// [`InputFile::File`]: crate::types::InputFile::File - /// [`InputFile::Memory`]: crate::types::InputFile::Memory - /// - /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files - pub fn document(mut self, val: InputFile) -> Self { - self.document = val; - self - } - - /// Thumbnail of the file sent; can be ignored if thumbnail generation for - /// the file is supported server-side. - /// - /// The thumbnail should be in JPEG format and less than 200kB in size. A - /// thumbnail‘s width and height should not exceed 320. Ignored if the - /// document is not uploaded using [`InputFile::File`] or - /// [`InputFile::Memory`]. Thumbnails can’t be reused and can be only - /// uploaded as a new file. Pass [`InputFile::File`] to upload a file from - /// the file system or [`InputFile::Memory`] to upload a file from memory. - /// [More info on Sending Files »]. - /// - /// [`InputFile::File`]: crate::types::InputFile::File - /// [`InputFile::Memory`]: crate::types::InputFile::Memory - /// - /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files - pub fn thumb(mut self, val: InputFile) -> Self { - self.thumb = Some(val); - self - } - - /// Document caption (may also be used when resending documents by - /// `file_id`), 0-1024 characters. - pub fn caption(mut self, val: T) -> Self - where - T: Into, - { - self.caption = Some(val.into()); - self - } - - /// Send [Markdown] or [HTML], if you want Telegram apps to show - /// [bold, italic, fixed-width text or inline URLs] in the media caption. - /// - /// [Markdown]: crate::types::ParseMode::Markdown - /// [HTML]: crate::types::ParseMode::HTML - /// [bold, italic, fixed-width text or inline URLs]: - /// crate::types::ParseMode - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } - - /// If the message is a reply, ID of the original message. - pub fn reply_to_message_id(mut self, val: i32) -> Self { - self.reply_to_message_id = Some(val); - self - } - - /// Additional interface options. - pub fn reply_markup(mut self, val: ReplyMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/send_game.rs b/src/requests/all/send_game.rs deleted file mode 100644 index 03a0b0e2..00000000 --- a/src/requests/all/send_game.rs +++ /dev/null @@ -1,92 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{InlineKeyboardMarkup, Message}, - Bot, -}; - -/// Use this method to send a game. -/// -/// [The official docs](https://core.telegram.org/bots/api#sendgame). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SendGame { - #[serde(skip_serializing)] - bot: Bot, - chat_id: i32, - game_short_name: String, - disable_notification: Option, - reply_to_message_id: Option, - reply_markup: Option, -} - -#[async_trait::async_trait] -impl Request for SendGame { - type Output = Message; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "sendGame", &self).await - } -} - -impl SendGame { - pub(crate) fn new(bot: Bot, chat_id: i32, game_short_name: G) -> Self - where - G: Into, - { - let game_short_name = game_short_name.into(); - Self { - bot, - chat_id, - game_short_name, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - } - } - - /// Unique identifier for the target chat. - pub fn chat_id(mut self, val: i32) -> Self { - self.chat_id = val; - self - } - - /// Short name of the game, serves as the unique identifier for the game. - /// Set up your games via [@Botfather]. - /// - /// [@Botfather]: https://t.me/botfather - pub fn game_short_name(mut self, val: T) -> Self - where - T: Into, - { - self.game_short_name = val.into(); - self - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } - - /// If the message is a reply, ID of the original message. - pub fn reply_to_message_id(mut self, val: i32) -> Self { - self.reply_to_message_id = Some(val); - self - } - - /// A JSON-serialized object for an [inline keyboard]. If empty, one `Play - /// game_title` button will be shown. If not empty, the first button must - /// launch the game. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/send_invoice.rs b/src/requests/all/send_invoice.rs deleted file mode 100644 index 8466cdba..00000000 --- a/src/requests/all/send_invoice.rs +++ /dev/null @@ -1,299 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{InlineKeyboardMarkup, LabeledPrice, Message}, - Bot, -}; - -/// Use this method to send invoices. -/// -/// [The official docs](https://core.telegram.org/bots/api#sendinvoice). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SendInvoice { - #[serde(skip_serializing)] - bot: Bot, - chat_id: i32, - title: String, - description: String, - payload: String, - provider_token: String, - start_parameter: String, - currency: String, - prices: Vec, - provider_data: Option, - photo_url: Option, - photo_size: Option, - photo_width: Option, - photo_height: Option, - need_name: Option, - need_phone_number: Option, - need_email: Option, - need_shipping_address: Option, - send_phone_number_to_provider: Option, - send_email_to_provider: Option, - is_flexible: Option, - disable_notification: Option, - reply_to_message_id: Option, - reply_markup: Option, -} - -#[async_trait::async_trait] -impl Request for SendInvoice { - type Output = Message; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "sendInvoice", &self).await - } -} - -impl SendInvoice { - #[allow(clippy::too_many_arguments)] - pub(crate) fn new( - bot: Bot, - chat_id: i32, - title: T, - description: D, - payload: Pl, - provider_token: Pt, - start_parameter: S, - currency: C, - prices: Pr, - ) -> Self - where - T: Into, - D: Into, - Pl: Into, - Pt: Into, - S: Into, - C: Into, - Pr: Into>, - { - let title = title.into(); - let description = description.into(); - let payload = payload.into(); - let provider_token = provider_token.into(); - let start_parameter = start_parameter.into(); - let currency = currency.into(); - let prices = prices.into(); - Self { - bot, - chat_id, - title, - description, - payload, - provider_token, - start_parameter, - currency, - prices, - provider_data: None, - photo_url: None, - photo_size: None, - photo_width: None, - photo_height: None, - need_name: None, - need_phone_number: None, - need_email: None, - need_shipping_address: None, - send_phone_number_to_provider: None, - send_email_to_provider: None, - is_flexible: None, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - } - } - - /// Unique identifier for the target private chat. - pub fn chat_id(mut self, val: i32) -> Self { - self.chat_id = val; - self - } - - /// Product name, 1-32 characters. - pub fn title(mut self, val: T) -> Self - where - T: Into, - { - self.title = val.into(); - self - } - - /// Product description, 1-255 characters. - pub fn description(mut self, val: T) -> Self - where - T: Into, - { - self.description = val.into(); - self - } - - /// Bot-defined invoice payload, 1-128 bytes. This will not be displayed to - /// the user, use for your internal processes. - pub fn payload(mut self, val: T) -> Self - where - T: Into, - { - self.payload = val.into(); - self - } - - /// Payments provider token, obtained via [@Botfather]. - /// - /// [@Botfather]: https://t.me/botfather - pub fn provider_token(mut self, val: T) -> Self - where - T: Into, - { - self.provider_token = val.into(); - self - } - - /// Unique deep-linking parameter that can be used to generate this invoice - /// when used as a start parameter. - pub fn start_parameter(mut self, val: T) -> Self - where - T: Into, - { - self.start_parameter = val.into(); - self - } - - /// Three-letter ISO 4217 currency code, see [more on currencies]. - /// - /// [more on currencies]: https://core.telegram.org/bots/payments#supported-currencies - pub fn currency(mut self, val: T) -> Self - where - T: Into, - { - self.currency = val.into(); - self - } - - /// Price breakdown, a list of components (e.g. product price, tax, - /// discount, delivery cost, delivery tax, bonus, etc.). - pub fn prices(mut self, val: T) -> Self - where - T: Into>, - { - self.prices = val.into(); - self - } - - /// JSON-encoded data about the invoice, which will be shared with the - /// payment provider. - /// - /// A detailed description of required fields should be provided by the - /// payment provider. - pub fn provider_data(mut self, val: T) -> Self - where - T: Into, - { - self.provider_data = Some(val.into()); - self - } - - /// URL of the product photo for the invoice. - /// - /// Can be a photo of the goods or a marketing image for a service. People - /// like it better when they see what they are paying for. - pub fn photo_url(mut self, val: T) -> Self - where - T: Into, - { - self.photo_url = Some(val.into()); - self - } - - /// Photo size. - pub fn photo_size(mut self, val: i32) -> Self { - self.photo_size = Some(val); - self - } - - /// Photo width. - pub fn photo_width(mut self, val: i32) -> Self { - self.photo_width = Some(val); - self - } - - /// Photo height. - pub fn photo_height(mut self, val: i32) -> Self { - self.photo_height = Some(val); - self - } - - /// Pass `true`, if you require the user's full name to complete the order. - pub fn need_name(mut self, val: bool) -> Self { - self.need_name = Some(val); - self - } - - /// Pass `true`, if you require the user's phone number to complete the - /// order. - pub fn need_phone_number(mut self, val: bool) -> Self { - self.need_phone_number = Some(val); - self - } - - /// Pass `true`, if you require the user's email address to complete the - /// order. - pub fn need_email(mut self, val: bool) -> Self { - self.need_email = Some(val); - self - } - - /// Pass `true`, if you require the user's shipping address to complete the - /// order. - pub fn need_shipping_address(mut self, val: bool) -> Self { - self.need_shipping_address = Some(val); - self - } - - /// Pass `true`, if user's phone number should be sent to provider. - pub fn send_phone_number_to_provider(mut self, val: bool) -> Self { - self.send_phone_number_to_provider = Some(val); - self - } - - /// Pass `true`, if user's email address should be sent to provider. - pub fn send_email_to_provider(mut self, val: bool) -> Self { - self.send_email_to_provider = Some(val); - self - } - - /// Pass `true`, if the final price depends on the shipping method. - #[allow(clippy::wrong_self_convention)] - pub fn is_flexible(mut self, val: bool) -> Self { - self.is_flexible = Some(val); - self - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } - - /// If the message is a reply, ID of the original message. - pub fn reply_to_message_id(mut self, val: i32) -> Self { - self.reply_to_message_id = Some(val); - self - } - - /// A JSON-serialized object for an [inline keyboard]. - /// - /// If empty, one 'Pay `total price`' button will be shown. If not empty, - /// the first button must be a Pay button. - /// - /// [inlint keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/send_location.rs b/src/requests/all/send_location.rs deleted file mode 100644 index 20201ad1..00000000 --- a/src/requests/all/send_location.rs +++ /dev/null @@ -1,110 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, Message, ReplyMarkup}, - Bot, -}; - -/// Use this method to send point on the map. -/// -/// [The official docs](https://core.telegram.org/bots/api#sendlocation). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SendLocation { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, - latitude: f32, - longitude: f32, - live_period: Option, - disable_notification: Option, - reply_to_message_id: Option, - reply_markup: Option, -} - -#[async_trait::async_trait] -impl Request for SendLocation { - type Output = Message; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "sendLocation", &self).await - } -} - -impl SendLocation { - pub(crate) fn new(bot: Bot, chat_id: C, latitude: f32, longitude: f32) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { - bot, - chat_id, - latitude, - longitude, - live_period: None, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Latitude of the location. - pub fn latitude(mut self, val: f32) -> Self { - self.latitude = val; - self - } - - /// Longitude of the location. - pub fn longitude(mut self, val: f32) -> Self { - self.longitude = val; - self - } - - /// Period in seconds for which the location will be updated (see [Live - /// Locations], should be between 60 and 86400). - /// - /// [Live Locations]: https://telegram.org/blog/live-locations - pub fn live_period(mut self, val: i64) -> Self { - self.live_period = Some(val); - self - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } - - /// If the message is a reply, ID of the original message. - pub fn reply_to_message_id(mut self, val: i32) -> Self { - self.reply_to_message_id = Some(val); - self - } - - /// A JSON-serialized object for an [inline keyboard]. - /// - /// If empty, one 'Pay `total price`' button will be shown. If not empty, - /// the first button must be a Pay button. - /// - /// [inlint keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub fn reply_markup(mut self, val: ReplyMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/send_media_group.rs b/src/requests/all/send_media_group.rs deleted file mode 100644 index 7484fc62..00000000 --- a/src/requests/all/send_media_group.rs +++ /dev/null @@ -1,85 +0,0 @@ -use crate::{ - net, - requests::{form_builder::FormBuilder, Request, ResponseResult}, - types::{ChatId, InputMedia, Message}, - Bot, -}; - -/// Use this method to send a group of photos or videos as an album. -/// -/// [The official docs](https://core.telegram.org/bots/api#sendmediagroup). -#[derive(Debug, Clone)] -pub struct SendMediaGroup { - bot: Bot, - chat_id: ChatId, - media: Vec, // TODO: InputMediaPhoto and InputMediaVideo - disable_notification: Option, - reply_to_message_id: Option, -} - -#[async_trait::async_trait] -impl Request for SendMediaGroup { - type Output = Vec; - - async fn send(&self) -> ResponseResult> { - net::request_multipart( - self.bot.client(), - self.bot.token(), - "sendMediaGroup", - FormBuilder::new() - .add_text("chat_id", &self.chat_id) - .add_text("media", &self.media) - .add_text("disable_notification", &self.disable_notification) - .add_text("reply_to_message_id", &self.reply_to_message_id) - .build(), - ) - .await - } -} - -impl SendMediaGroup { - pub(crate) fn new(bot: Bot, chat_id: C, media: M) -> Self - where - C: Into, - M: Into>, - { - let chat_id = chat_id.into(); - let media = media.into(); - Self { bot, chat_id, media, disable_notification: None, reply_to_message_id: None } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// A JSON-serialized array describing photos and videos to be sent, must - /// include 2–10 items. - pub fn media(mut self, val: T) -> Self - where - T: Into>, - { - self.media = val.into(); - self - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } - - /// If the messages are a reply, ID of the original message. - pub fn reply_to_message_id(mut self, val: i32) -> Self { - self.reply_to_message_id = Some(val); - self - } -} diff --git a/src/requests/all/send_message.rs b/src/requests/all/send_message.rs deleted file mode 100644 index 29525cb9..00000000 --- a/src/requests/all/send_message.rs +++ /dev/null @@ -1,121 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, Message, ParseMode, ReplyMarkup}, - Bot, -}; - -/// Use this method to send text messages. -/// -/// [The official docs](https://core.telegram.org/bots/api#sendmessage). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SendMessage { - #[serde(skip_serializing)] - bot: Bot, - pub chat_id: ChatId, - pub text: String, - pub parse_mode: Option, - pub disable_web_page_preview: Option, - pub disable_notification: Option, - pub reply_to_message_id: Option, - pub reply_markup: Option, -} - -#[async_trait::async_trait] -impl Request for SendMessage { - type Output = Message; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "sendMessage", &self).await - } -} - -impl SendMessage { - pub(crate) fn new(bot: Bot, chat_id: C, text: T) -> Self - where - C: Into, - T: Into, - { - Self { - bot, - chat_id: chat_id.into(), - text: text.into(), - parse_mode: None, - disable_web_page_preview: None, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, value: T) -> Self - where - T: Into, - { - self.chat_id = value.into(); - self - } - - /// Text of the message to be sent. - pub fn text(mut self, value: T) -> Self - where - T: Into, - { - self.text = value.into(); - self - } - - /// Send [Markdown] or [HTML], if you want Telegram apps to show - /// [bold, italic, fixed-width text or inline URLs] in the media caption. - /// - /// [Markdown]: crate::types::ParseMode::Markdown - /// [HTML]: crate::types::ParseMode::HTML - /// [bold, italic, fixed-width text or inline URLs]: - /// crate::types::ParseMode - pub fn parse_mode(mut self, value: ParseMode) -> Self { - self.parse_mode = Some(value); - self - } - - /// Disables link previews for links in this message. - pub fn disable_web_page_preview(mut self, value: bool) -> Self { - self.disable_web_page_preview = Some(value); - self - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, value: bool) -> Self { - self.disable_notification = Some(value); - self - } - - /// If the message is a reply, ID of the original message. - pub fn reply_to_message_id(mut self, value: i32) -> Self { - self.reply_to_message_id = Some(value); - self - } - - /// Additional interface options. - /// - /// A JSON-serialized object for an [inline keyboard], [custom reply - /// keyboard], instructions to remove reply keyboard or to force a reply - /// from the user. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards - pub fn reply_markup(mut self, value: T) -> Self - where - T: Into, - { - self.reply_markup = Some(value.into()); - self - } -} diff --git a/src/requests/all/send_photo.rs b/src/requests/all/send_photo.rs deleted file mode 100644 index c66e6f02..00000000 --- a/src/requests/all/send_photo.rs +++ /dev/null @@ -1,140 +0,0 @@ -use crate::{ - net, - requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult}, - types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, - Bot, -}; - -/// Use this method to send photos. -/// -/// [The official docs](https://core.telegram.org/bots/api#sendphoto). -#[derive(Debug, Clone)] -pub struct SendPhoto { - bot: Bot, - chat_id: ChatId, - photo: InputFile, - caption: Option, - parse_mode: Option, - disable_notification: Option, - reply_to_message_id: Option, - reply_markup: Option, -} - -#[async_trait::async_trait] -impl RequestWithFile for SendPhoto { - type Output = Message; - - async fn send(&self) -> tokio::io::Result> { - Ok(net::request_multipart( - self.bot.client(), - self.bot.token(), - "sendPhoto", - FormBuilder::new() - .add_text("chat_id", &self.chat_id) - .add_input_file("photo", &self.photo) - .await? - .add_text("caption", &self.caption) - .add_text("parse_mode", &self.parse_mode) - .add_text("disable_notification", &self.disable_notification) - .add_text("reply_to_message_id", &self.reply_to_message_id) - .add_text("reply_markup", &self.reply_markup) - .build(), - ) - .await) - } -} - -impl SendPhoto { - pub(crate) fn new(bot: Bot, chat_id: C, photo: InputFile) -> Self - where - C: Into, - { - Self { - bot, - chat_id: chat_id.into(), - photo, - caption: None, - parse_mode: None, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Photo to send. - /// - /// Pass [`InputFile::FileId`] to send a photo that exists on the Telegram - /// servers (recommended), pass an [`InputFile::Url`] for Telegram to get a - /// photo from the Internet (5MB max.), pass [`InputFile::File`] to upload - /// a picture from the file system or [`InputFile::Memory`] to upload a - /// photo from memory (10MB max. each). [More info on Sending Files »]. - /// - /// [`InputFile::FileId`]: crate::types::InputFile::FileId - /// [`InputFile::Url`]: crate::types::InputFile::Url - /// [`InputFile::File`]: crate::types::InputFile::File - /// [`InputFile::Memory`]: crate::types::InputFile::Memory - /// - /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files - pub fn photo(mut self, val: InputFile) -> Self { - self.photo = val; - self - } - - ///Photo caption (may also be used when resending photos by file_id), - /// 0-1024 characters. - pub fn caption(mut self, val: T) -> Self - where - T: Into, - { - self.caption = Some(val.into()); - self - } - - /// Send [Markdown] or [HTML], if you want Telegram apps to show - /// [bold, italic, fixed-width text or inline URLs] in the media caption. - /// - /// [Markdown]: crate::types::ParseMode::Markdown - /// [HTML]: crate::types::ParseMode::HTML - /// [bold, italic, fixed-width text or inline URLs]: - /// crate::types::ParseMode - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } - - /// If the message is a reply, ID of the original message. - pub fn reply_to_message_id(mut self, val: i32) -> Self { - self.reply_to_message_id = Some(val); - self - } - - /// Additional interface options. A JSON-serialized object for an [inline - /// keyboard], [custom reply keyboard], instructions to remove reply - /// keyboard or to force a reply from the user. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards - pub fn reply_markup(mut self, val: ReplyMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/send_poll.rs b/src/requests/all/send_poll.rs deleted file mode 100644 index 3dd412a1..00000000 --- a/src/requests/all/send_poll.rs +++ /dev/null @@ -1,217 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, Message, ParseMode, PollType, ReplyMarkup}, - Bot, -}; - -/// Use this method to send a native poll. -/// -/// [The official docs](https://core.telegram.org/bots/api#sendpoll). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SendPoll { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, - question: String, - options: Vec, - is_anonymous: Option, - poll_type: Option, - allows_multiple_answers: Option, - correct_option_id: Option, - explanation: Option, - explanation_parse_mode: Option, - open_period: Option, - close_date: Option, - is_closed: Option, - disable_notification: Option, - reply_to_message_id: Option, - reply_markup: Option, -} - -#[async_trait::async_trait] -impl Request for SendPoll { - type Output = Message; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "sendPoll", &self).await - } -} - -impl SendPoll { - pub(crate) fn new(bot: Bot, chat_id: C, question: Q, options: O) -> Self - where - C: Into, - Q: Into, - O: Into>, - { - let chat_id = chat_id.into(); - let question = question.into(); - let options = options.into(); - Self { - bot, - chat_id, - question, - options, - is_anonymous: None, - poll_type: None, - allows_multiple_answers: None, - correct_option_id: None, - explanation: None, - explanation_parse_mode: None, - is_closed: None, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - close_date: None, - open_period: None, - } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - /// - /// A native poll can't be sent to a private chat. - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Poll question, 1-255 characters. - pub fn question(mut self, val: T) -> Self - where - T: Into, - { - self.question = val.into(); - self - } - - /// List of answer options, 2-10 strings 1-100 characters each. - pub fn options(mut self, val: T) -> Self - where - T: Into>, - { - self.options = val.into(); - self - } - - /// `true`, if the poll needs to be anonymous, defaults to `true`. - #[allow(clippy::wrong_self_convention)] - pub fn is_anonymous(mut self, val: T) -> Self - where - T: Into, - { - self.is_anonymous = Some(val.into()); - self - } - - /// Poll type, `quiz` or `regular`, defaults to `regular`. - pub fn poll_type(mut self, val: PollType) -> Self { - self.poll_type = Some(val); - self - } - - /// `true`, if the poll allows multiple answers, ignored for polls in quiz - /// mode. - /// - /// Defaults to `false`. - pub fn allows_multiple_answers(mut self, val: T) -> Self - where - T: Into, - { - self.allows_multiple_answers = Some(val.into()); - self - } - - /// 0-based identifier of the correct answer option, required for polls in - /// quiz mode. - pub fn correct_option_id(mut self, val: T) -> Self - where - T: Into, - { - self.correct_option_id = Some(val.into()); - self - } - - /// Text that is shown when a user chooses an incorrect answer or taps on - /// the lamp icon in a quiz-style poll, 0-200 characters with at most 2 line - /// feeds after entities parsing. - pub fn explanation(mut self, val: T) -> Self - where - T: Into, - { - self.explanation = Some(val.into()); - self - } - - /// Mode for parsing entities in the explanation. See [formatting options] - /// for more details. - /// - /// [formatting options]: https://core.telegram.org/bots/api#formatting-options - pub fn explanation_parse_mode(mut self, val: ParseMode) -> Self { - self.explanation_parse_mode = Some(val); - self - } - - /// Amount of time in seconds the poll will be active after creation, 5-600. - /// Can't be used together with `close_date`. - pub fn open_period(mut self, val: i32) -> Self { - self.open_period = Some(val); - self - } - - /// Point in time (Unix timestamp) when the poll will be automatically - /// closed. Must be at least 5 and no more than 600 seconds in the future. - /// Can't be used together with `open_period`. - pub fn close_date(mut self, val: i32) -> Self { - self.close_date = Some(val); - self - } - - /// Pass `true`, if the poll needs to be immediately closed. - /// - /// This can be useful for poll preview. - #[allow(clippy::wrong_self_convention)] - pub fn is_closed(mut self, val: T) -> Self - where - T: Into, - { - self.is_closed = Some(val.into()); - self - } - - /// Sends the message [silently]. - /// - /// Users will receive a notification with no sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } - - /// If the message is a reply, ID of the original message. - pub fn reply_to_message_id(mut self, val: i32) -> Self { - self.reply_to_message_id = Some(val); - self - } - - /// Additional interface options. - /// - /// A JSON-serialized object for an [inline keyboard], [custom reply - /// keyboard], instructions to remove reply keyboard or to force a reply - /// from the user. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards - pub fn reply_markup(mut self, val: ReplyMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/send_sticker.rs b/src/requests/all/send_sticker.rs deleted file mode 100644 index 9c683a30..00000000 --- a/src/requests/all/send_sticker.rs +++ /dev/null @@ -1,117 +0,0 @@ -use crate::{ - net, - requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult}, - types::{ChatId, InputFile, Message, ReplyMarkup}, - Bot, -}; - -/// Use this method to send static .WEBP or [animated] .TGS stickers. -/// -/// [The official docs](https://core.telegram.org/bots/api#sendsticker). -/// -/// [animated]: https://telegram.org/blog/animated-stickers -#[derive(Debug, Clone)] -pub struct SendSticker { - bot: Bot, - chat_id: ChatId, - sticker: InputFile, - disable_notification: Option, - reply_to_message_id: Option, - reply_markup: Option, -} - -#[async_trait::async_trait] -impl RequestWithFile for SendSticker { - type Output = Message; - - async fn send(&self) -> tokio::io::Result> { - Ok(net::request_multipart( - self.bot.client(), - self.bot.token(), - "sendSticker", - FormBuilder::new() - .add_text("chat_id", &self.chat_id) - .add_input_file("sticker", &self.sticker) - .await? - .add_text("disable_notification", &self.disable_notification) - .add_text("reply_to_message_id", &self.reply_to_message_id) - .add_text("reply_markup", &self.reply_markup) - .build(), - ) - .await) - } -} - -impl SendSticker { - pub(crate) fn new(bot: Bot, chat_id: C, sticker: InputFile) -> Self - where - C: Into, - { - Self { - bot, - chat_id: chat_id.into(), - sticker, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Sticker to send. - /// - /// Pass [`InputFile::FileId`] to send a sticker that exists on the - /// Telegram servers (recommended), pass an [`InputFile::Url`] for Telegram - /// to get a sticker (.WEBP file) from the Internet, pass - /// [`InputFile::File`] to upload a sticker from the file system or - /// [`InputFile::Memory`] to upload a sticker from memory - /// [More info on Sending Files »]. - /// - /// [`InputFile::FileId`]: crate::types::InputFile::FileId - /// [`InputFile::Url`]: crate::types::InputFile::Url - /// [`InputFile::File`]: crate::types::InputFile::File - /// [`InputFile::Memory`]: crate::types::InputFile::Memory - /// - /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files - pub fn sticker(mut self, val: InputFile) -> Self { - self.sticker = val; - self - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } - - /// If the message is a reply, ID of the original message. - pub fn reply_to_message_id(mut self, val: i32) -> Self { - self.reply_to_message_id = Some(val); - self - } - - /// Additional interface options. - /// - /// A JSON-serialized object for an [inline keyboard], [custom reply - /// keyboard], instructions to remove reply keyboard or to force a reply - /// from the user. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards - pub fn reply_markup(mut self, val: ReplyMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/send_venue.rs b/src/requests/all/send_venue.rs deleted file mode 100644 index 513ece7d..00000000 --- a/src/requests/all/send_venue.rs +++ /dev/null @@ -1,159 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, Message, ReplyMarkup}, - Bot, -}; - -/// Use this method to send information about a venue. -/// -/// [The official docs](https://core.telegram.org/bots/api#sendvenue). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SendVenue { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, - latitude: f32, - longitude: f32, - title: String, - address: String, - foursquare_id: Option, - foursquare_type: Option, - disable_notification: Option, - reply_to_message_id: Option, - reply_markup: Option, -} - -#[async_trait::async_trait] -impl Request for SendVenue { - type Output = Message; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "sendVenue", &self).await - } -} - -impl SendVenue { - pub(crate) fn new( - bot: Bot, - chat_id: C, - latitude: f32, - longitude: f32, - title: T, - address: A, - ) -> Self - where - C: Into, - T: Into, - A: Into, - { - let chat_id = chat_id.into(); - let title = title.into(); - let address = address.into(); - Self { - bot, - chat_id, - latitude, - longitude, - title, - address, - foursquare_id: None, - foursquare_type: None, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Latitude of the venue. - pub fn latitude(mut self, val: f32) -> Self { - self.latitude = val; - self - } - - /// Longitude of the venue. - pub fn longitude(mut self, val: f32) -> Self { - self.longitude = val; - self - } - - /// Name of the venue. - pub fn title(mut self, val: T) -> Self - where - T: Into, - { - self.title = val.into(); - self - } - - /// Address of the venue. - pub fn address(mut self, val: T) -> Self - where - T: Into, - { - self.address = val.into(); - self - } - - /// Foursquare identifier of the venue. - pub fn foursquare_id(mut self, val: T) -> Self - where - T: Into, - { - self.foursquare_id = Some(val.into()); - self - } - - /// Foursquare type of the venue, if known. - /// - /// For example, `arts_entertainment/default`, `arts_entertainment/aquarium` - /// or `food/icecream`. - pub fn foursquare_type(mut self, val: T) -> Self - where - T: Into, - { - self.foursquare_type = Some(val.into()); - self - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } - - /// If the message is a reply, ID of the original message. - pub fn reply_to_message_id(mut self, val: i32) -> Self { - self.reply_to_message_id = Some(val); - self - } - - /// Additional interface options. - /// - /// A JSON-serialized object for an [inline keyboard], [custom reply - /// keyboard], instructions to remove reply keyboard or to force a reply - /// from the user. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards - pub fn reply_markup(mut self, val: ReplyMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/send_video.rs b/src/requests/all/send_video.rs deleted file mode 100644 index 395bf875..00000000 --- a/src/requests/all/send_video.rs +++ /dev/null @@ -1,207 +0,0 @@ -use crate::{ - net, - requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult}, - types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, - Bot, -}; - -/// Use this method to send video files, Telegram clients support mp4 videos -/// (other formats may be sent as Document). -/// -/// Bots can currently send video files of up to 50 MB in size, this -/// limit may be changed in the future. -/// -/// [The official docs](https://core.telegram.org/bots/api#sendvideo). -#[derive(Debug, Clone)] -pub struct SendVideo { - bot: Bot, - chat_id: ChatId, - video: InputFile, - duration: Option, - width: Option, - height: Option, - thumb: Option, - caption: Option, - parse_mode: Option, - supports_streaming: Option, - disable_notification: Option, - reply_to_message_id: Option, - reply_markup: Option, -} - -#[async_trait::async_trait] -impl RequestWithFile for SendVideo { - type Output = Message; - - async fn send(&self) -> tokio::io::Result> { - let mut builder = FormBuilder::new() - .add_text("chat_id", &self.chat_id) - .add_input_file("video", &self.video) - .await? - .add_text("duration", &self.duration) - .add_text("width", &self.width) - .add_text("height", &self.height) - .add_text("caption", &self.caption) - .add_text("parse_mode", &self.parse_mode) - .add_text("supports_streaming", &self.supports_streaming) - .add_text("disable_notification", &self.disable_notification) - .add_text("reply_to_message_id", &self.reply_to_message_id) - .add_text("reply_markup", &self.reply_markup); - if let Some(thumb) = self.thumb.as_ref() { - builder = builder.add_input_file("thumb", thumb).await?; - } - Ok(net::request_multipart( - self.bot.client(), - self.bot.token(), - "sendVideo", - builder.build(), - ) - .await) - } -} - -impl SendVideo { - pub(crate) fn new(bot: Bot, chat_id: C, video: InputFile) -> Self - where - C: Into, - { - Self { - bot, - chat_id: chat_id.into(), - video, - duration: None, - width: None, - height: None, - thumb: None, - caption: None, - parse_mode: None, - supports_streaming: None, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Video to send. - /// - /// Pass [`InputFile::FileId`] to send a file that exists on the Telegram - /// servers (recommended), pass an [`InputFile::Url`] for Telegram to get a - /// file from the Internet (20MB max.), pass [`InputFile::File`] to upload - /// a file from the file system or [`InputFile::Memory`] to upload a file - /// from memory (50MB max. each). [More info on Sending Files »]. - /// - /// [`InputFile::FileId`]: crate::types::InputFile::FileId - /// [`InputFile::Url`]: crate::types::InputFile::Url - /// [`InputFile::File`]: crate::types::InputFile::File - /// [`InputFile::Memory`]: crate::types::InputFile::Memory - /// - /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files - pub fn video(mut self, val: InputFile) -> Self { - self.video = val; - self - } - - /// Duration of sent video in seconds. - pub fn duration(mut self, val: i32) -> Self { - self.duration = Some(val); - self - } - - /// Video width. - pub fn width(mut self, val: i32) -> Self { - self.width = Some(val); - self - } - - /// Video height. - pub fn height(mut self, val: i32) -> Self { - self.height = Some(val); - self - } - - /// Thumbnail of the file sent; can be ignored if thumbnail generation for - /// the file is supported server-side. - /// - /// The thumbnail should be in JPEG format and less than 200kB in size. A - /// thumbnail‘s width and height should not exceed 320. Ignored if the - /// video file is not uploaded using [`InputFile::File`] or - /// [`InputFile::Memory`]. Thumbnails can’t be reused and can be only - /// uploaded as a new file. Pass [`InputFile::File`] to upload a file from - /// the file system or [`InputFile::Memory`] to upload a file from memory. - /// [More info on Sending Files »]. - /// - /// [`InputFile::File`]: crate::types::InputFile::File - /// [`InputFile::Memory`]: crate::types::InputFile::Memory - /// - /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files - pub fn thumb(mut self, val: InputFile) -> Self { - self.thumb = Some(val); - self - } - - /// Video caption (may also be used when resending videos by file_id), - /// 0-1024 characters. - pub fn caption(mut self, val: T) -> Self - where - T: Into, - { - self.caption = Some(val.into()); - self - } - - /// Send [Markdown] or [HTML], if you want Telegram apps to show - /// [bold, italic, fixed-width text or inline URLs] in the media caption. - /// - /// [Markdown]: crate::types::ParseMode::Markdown - /// [HTML]: crate::types::ParseMode::HTML - /// [bold, italic, fixed-width text or inline URLs]: - /// crate::types::ParseMode - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - /// Pass `true`, if the uploaded video is suitable for streaming. - pub fn supports_streaming(mut self, val: bool) -> Self { - self.supports_streaming = Some(val); - self - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } - - /// If the message is a reply, ID of the original message. - pub fn reply_to_message_id(mut self, val: i32) -> Self { - self.reply_to_message_id = Some(val); - self - } - - /// Additional interface options. - /// - /// A JSON-serialized object for an [inline keyboard], [custom reply - /// keyboard], instructions to remove reply keyboard or to force a reply - /// from the user. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards - pub fn reply_markup(mut self, val: ReplyMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/send_video_note.rs b/src/requests/all/send_video_note.rs deleted file mode 100644 index 81638f68..00000000 --- a/src/requests/all/send_video_note.rs +++ /dev/null @@ -1,160 +0,0 @@ -use crate::{ - net, - requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult}, - types::{ChatId, InputFile, Message, ReplyMarkup}, - Bot, -}; - -/// As of [v.4.0], Telegram clients support rounded square mp4 videos of up to 1 -/// minute long. Use this method to send video messages. -/// -/// [The official docs](https://core.telegram.org/bots/api#sendvideonote). -/// -/// [v.4.0]: https://telegram.org/blog/video-messages-and-telescope -#[derive(Debug, Clone)] -pub struct SendVideoNote { - bot: Bot, - chat_id: ChatId, - video_note: InputFile, - duration: Option, - length: Option, - thumb: Option, - disable_notification: Option, - reply_to_message_id: Option, - reply_markup: Option, -} - -#[async_trait::async_trait] -impl RequestWithFile for SendVideoNote { - type Output = Message; - - async fn send(&self) -> tokio::io::Result> { - let mut builder = FormBuilder::new() - .add_text("chat_id", &self.chat_id) - .add_input_file("video_note", &self.video_note) - .await? - .add_text("duration", &self.duration) - .add_text("length", &self.length) - .add_text("disable_notification", &self.disable_notification) - .add_text("reply_to_message_id", &self.reply_to_message_id) - .add_text("reply_markup", &self.reply_markup); - if let Some(thumb) = self.thumb.as_ref() { - builder = builder.add_input_file("thumb", thumb).await?; - } - Ok(net::request_multipart( - self.bot.client(), - self.bot.token(), - "sendVideoNote", - builder.build(), - ) - .await) - } -} - -impl SendVideoNote { - pub(crate) fn new(bot: Bot, chat_id: C, video_note: InputFile) -> Self - where - C: Into, - { - Self { - bot, - chat_id: chat_id.into(), - video_note, - duration: None, - length: None, - thumb: None, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Video note to send. - /// - /// Pass [`InputFile::FileId`] to send a file that exists on the Telegram - /// servers (recommended), pass an [`InputFile::Url`] for Telegram to get a - /// file from the Internet (20MB max.), pass [`InputFile::File`] to upload - /// a file from the file system or [`InputFile::Memory`] to upload a file - /// from memory (50MB max. each). [More info on Sending Files »]. - /// - /// [`InputFile::FileId`]: crate::types::InputFile::FileId - /// [`InputFile::Url`]: crate::types::InputFile::Url - /// [`InputFile::File`]: crate::types::InputFile::File - /// [`InputFile::Memory`]: crate::types::InputFile::Memory - /// - /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files - pub fn video_note(mut self, val: InputFile) -> Self { - self.video_note = val; - self - } - - /// Duration of sent video in seconds. - pub fn duration(mut self, val: i32) -> Self { - self.duration = Some(val); - self - } - - /// Video width and height, i.e. diameter of the video message. - pub fn length(mut self, val: i32) -> Self { - self.length = Some(val); - self - } - - /// Thumbnail of the file sent; can be ignored if thumbnail generation for - /// the file is supported server-side. - /// - /// The thumbnail should be in JPEG format and less than 200kB in size. A - /// thumbnail‘s width and height should not exceed 320. Ignored if the - /// video note is not uploaded using [`InputFile::File`] or - /// [`InputFile::Memory`]. Thumbnails can’t be reused and can be only - /// uploaded as a new file. Pass [`InputFile::File`] to upload a file from - /// the file system or [`InputFile::Memory`] to upload a file from memory. - /// [More info on Sending Files »]. - /// - /// [`InputFile::File`]: crate::types::InputFile::File - /// [`InputFile::Memory`]: crate::types::InputFile::Memory - /// - /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files - pub fn thumb(mut self, val: InputFile) -> Self { - self.thumb = Some(val); - self - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } - - /// If the message is a reply, ID of the original message. - pub fn reply_to_message_id(mut self, val: i32) -> Self { - self.reply_to_message_id = Some(val); - self - } - - /// Additional interface options. - /// - /// A JSON-serialized object for an [inline keyboard], [custom reply - /// keyboard], instructions to remove reply keyboard or to force a reply - /// from the user. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards - pub fn reply_markup(mut self, val: ReplyMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/send_voice.rs b/src/requests/all/send_voice.rs deleted file mode 100644 index 29b7f6c1..00000000 --- a/src/requests/all/send_voice.rs +++ /dev/null @@ -1,159 +0,0 @@ -use crate::{ - net, - requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult}, - types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, - Bot, -}; - -/// Use this method to send audio files, if you want Telegram clients to display -/// the file as a playable voice message. -/// -/// For this to work, your audio must be in an .ogg file encoded with OPUS -/// (other formats may be sent as [`Audio`] or [`Document`]). Bots can currently -/// send voice messages of up to 50 MB in size, this limit may be changed in the -/// future. -/// -/// [The official docs](https://core.telegram.org/bots/api#sendvoice). -/// -/// [`Audio`]: crate::types::Audio -/// [`Document`]: crate::types::Document -#[derive(Debug, Clone)] -pub struct SendVoice { - bot: Bot, - chat_id: ChatId, - voice: InputFile, - caption: Option, - parse_mode: Option, - duration: Option, - disable_notification: Option, - reply_to_message_id: Option, - reply_markup: Option, -} - -#[async_trait::async_trait] -impl RequestWithFile for SendVoice { - type Output = Message; - - async fn send(&self) -> tokio::io::Result> { - Ok(net::request_multipart( - self.bot.client(), - self.bot.token(), - "sendVoice", - FormBuilder::new() - .add_text("chat_id", &self.chat_id) - .add_input_file("voice", &self.voice) - .await? - .add_text("caption", &self.caption) - .add_text("parse_mode", &self.parse_mode) - .add_text("duration", &self.duration) - .add_text("disable_notification", &self.disable_notification) - .add_text("reply_to_message_id", &self.reply_to_message_id) - .add_text("reply_markup", &self.reply_markup) - .build(), - ) - .await) - } -} - -impl SendVoice { - pub(crate) fn new(bot: Bot, chat_id: C, voice: InputFile) -> Self - where - C: Into, - { - Self { - bot, - chat_id: chat_id.into(), - voice, - caption: None, - parse_mode: None, - duration: None, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Audio file to send. - /// - /// Pass [`InputFile::FileId`] to send a file that exists on the Telegram - /// servers (recommended), pass an [`InputFile::Url`] for Telegram to get a - /// file from the Internet (20MB max.), pass [`InputFile::File`] to upload - /// a file from the file system or [`InputFile::Memory`] to upload a file - /// from memory (50MB max. each). [More info on Sending Files »]. - /// - /// [`InputFile::FileId`]: crate::types::InputFile::FileId - /// [`InputFile::Url`]: crate::types::InputFile::Url - /// [`InputFile::File`]: crate::types::InputFile::File - /// [`InputFile::Memory`]: crate::types::InputFile::Memory - /// - /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files - pub fn voice(mut self, val: InputFile) -> Self { - self.voice = val; - self - } - - /// Voice message caption, 0-1024 characters. - pub fn caption(mut self, val: T) -> Self - where - T: Into, - { - self.caption = Some(val.into()); - self - } - - /// Send [Markdown] or [HTML], if you want Telegram apps to show - /// [bold, italic, fixed-width text or inline URLs] in the media caption. - /// - /// [Markdown]: crate::types::ParseMode::Markdown - /// [HTML]: crate::types::ParseMode::HTML - /// [bold, italic, fixed-width text or inline URLs]: - /// crate::types::ParseMode - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - /// Duration of the voice message in seconds. - pub fn duration(mut self, val: i32) -> Self { - self.duration = Some(val); - self - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } - - /// If the message is a reply, ID of the original message. - pub fn reply_to_message_id(mut self, val: i32) -> Self { - self.reply_to_message_id = Some(val); - self - } - - /// Additional interface options. - /// - /// A JSON-serialized object for an [inline keyboard], [custom reply - /// keyboard], instructions to remove reply keyboard or to force a reply - /// from the user. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards - pub fn reply_markup(mut self, val: ReplyMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/set_chat_administrator_custom_title.rs b/src/requests/all/set_chat_administrator_custom_title.rs deleted file mode 100644 index a3211811..00000000 --- a/src/requests/all/set_chat_administrator_custom_title.rs +++ /dev/null @@ -1,75 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, True}, - Bot, -}; - -/// Use this method to set a custom title for an administrator in a supergroup -/// promoted by the bot. -/// -/// [The official docs](https://core.telegram.org/bots/api#setchatadministratorcustomtitle). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SetChatAdministratorCustomTitle { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, - user_id: i32, - custom_title: String, -} - -#[async_trait::async_trait] -impl Request for SetChatAdministratorCustomTitle { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json( - self.bot.client(), - self.bot.token(), - "setChatAdministratorCustomTitle", - &self, - ) - .await - } -} - -impl SetChatAdministratorCustomTitle { - pub(crate) fn new(bot: Bot, chat_id: C, user_id: i32, custom_title: CT) -> Self - where - C: Into, - CT: Into, - { - let chat_id = chat_id.into(); - let custom_title = custom_title.into(); - Self { bot, chat_id, user_id, custom_title } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Unique identifier of the target user. - pub fn user_id(mut self, val: i32) -> Self { - self.user_id = val; - self - } - - /// New custom title for the administrator; 0-16 characters, emoji are not - /// allowed. - pub fn custom_title(mut self, val: T) -> Self - where - T: Into, - { - self.custom_title = val.into(); - self - } -} diff --git a/src/requests/all/set_chat_description.rs b/src/requests/all/set_chat_description.rs deleted file mode 100644 index 092c495b..00000000 --- a/src/requests/all/set_chat_description.rs +++ /dev/null @@ -1,62 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, True}, - Bot, -}; - -/// Use this method to change the description of a group, a supergroup or a -/// channel. -/// -/// The bot must be an administrator in the chat for this to work and must have -/// the appropriate admin rights. -/// -/// [The official docs](https://core.telegram.org/bots/api#setchatdescription). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SetChatDescription { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, - description: Option, -} - -#[async_trait::async_trait] -impl Request for SetChatDescription { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "setChatDescription", &self).await - } -} - -impl SetChatDescription { - pub(crate) fn new(bot: Bot, chat_id: C) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { bot, chat_id, description: None } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// New chat description, 0-255 characters. - pub fn description(mut self, val: T) -> Self - where - T: Into, - { - self.description = Some(val.into()); - self - } -} diff --git a/src/requests/all/set_chat_permissions.rs b/src/requests/all/set_chat_permissions.rs deleted file mode 100644 index aaa1881b..00000000 --- a/src/requests/all/set_chat_permissions.rs +++ /dev/null @@ -1,58 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, ChatPermissions, True}, - Bot, -}; - -/// Use this method to set default chat permissions for all members. -/// -/// The bot must be an administrator in the group or a supergroup for this to -/// work and must have the can_restrict_members admin rights. -/// -/// [The official docs](https://core.telegram.org/bots/api#setchatpermissions). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SetChatPermissions { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, - permissions: ChatPermissions, -} - -#[async_trait::async_trait] -impl Request for SetChatPermissions { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "sendChatPermissions", &self).await - } -} - -impl SetChatPermissions { - pub(crate) fn new(bot: Bot, chat_id: C, permissions: ChatPermissions) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { bot, chat_id, permissions } - } - - /// Unique identifier for the target chat or username of the target - /// supergroup (in the format `@supergroupusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// New default chat permissions. - pub fn permissions(mut self, val: ChatPermissions) -> Self { - self.permissions = val; - self - } -} diff --git a/src/requests/all/set_chat_photo.rs b/src/requests/all/set_chat_photo.rs deleted file mode 100644 index dcc5febc..00000000 --- a/src/requests/all/set_chat_photo.rs +++ /dev/null @@ -1,58 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, InputFile, True}, - Bot, -}; - -/// Use this method to set a new profile photo for the chat. -/// -/// Photos can't be changed for private chats. The bot must be an administrator -/// in the chat for this to work and must have the appropriate admin rights. -/// -/// [The official docs](https://core.telegram.org/bots/api#setchatphoto). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SetChatPhoto { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, - photo: InputFile, -} - -#[async_trait::async_trait] -impl Request for SetChatPhoto { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "setChatPhoto", &self).await - } -} - -impl SetChatPhoto { - pub(crate) fn new(bot: Bot, chat_id: C, photo: InputFile) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { bot, chat_id, photo } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// New chat photo, uploaded using `multipart/form-data`. - pub fn photo(mut self, val: InputFile) -> Self { - self.photo = val; - self - } -} diff --git a/src/requests/all/set_chat_sticker_set.rs b/src/requests/all/set_chat_sticker_set.rs deleted file mode 100644 index 010a10f5..00000000 --- a/src/requests/all/set_chat_sticker_set.rs +++ /dev/null @@ -1,64 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, True}, - Bot, -}; - -/// Use this method to set a new group sticker set for a supergroup. -/// -/// The bot must be an administrator in the chat for this to work and must have -/// the appropriate admin rights. Use the field can_set_sticker_set optionally -/// returned in getChat requests to check if the bot can use this method. -/// -/// [The official docs](https://core.telegram.org/bots/api#setchatstickerset). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SetChatStickerSet { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, - sticker_set_name: String, -} - -#[async_trait::async_trait] -impl Request for SetChatStickerSet { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "setChatStickerSet", &self).await - } -} - -impl SetChatStickerSet { - pub(crate) fn new(bot: Bot, chat_id: C, sticker_set_name: S) -> Self - where - C: Into, - S: Into, - { - let chat_id = chat_id.into(); - let sticker_set_name = sticker_set_name.into(); - Self { bot, chat_id, sticker_set_name } - } - - /// Unique identifier for the target chat or username of the target - /// supergroup (in the format `@supergroupusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Name of the sticker set to be set as the group sticker set. - pub fn sticker_set_name(mut self, val: T) -> Self - where - T: Into, - { - self.sticker_set_name = val.into(); - self - } -} diff --git a/src/requests/all/set_chat_title.rs b/src/requests/all/set_chat_title.rs deleted file mode 100644 index 7d6ae6d1..00000000 --- a/src/requests/all/set_chat_title.rs +++ /dev/null @@ -1,63 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, True}, - Bot, -}; - -/// Use this method to change the title of a chat. -/// -/// Titles can't be changed for private chats. The bot must be an administrator -/// in the chat for this to work and must have the appropriate admin rights. -/// -/// [The official docs](https://core.telegram.org/bots/api#setchattitle). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SetChatTitle { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, - title: String, -} - -#[async_trait::async_trait] -impl Request for SetChatTitle { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "setChatTitle", &self).await - } -} - -impl SetChatTitle { - pub(crate) fn new(bot: Bot, chat_id: C, title: T) -> Self - where - C: Into, - T: Into, - { - let chat_id = chat_id.into(); - let title = title.into(); - Self { bot, chat_id, title } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// New chat title, 1-255 characters. - pub fn title(mut self, val: T) -> Self - where - T: Into, - { - self.title = val.into(); - self - } -} diff --git a/src/requests/all/set_game_score.rs b/src/requests/all/set_game_score.rs deleted file mode 100644 index 8e429e3a..00000000 --- a/src/requests/all/set_game_score.rs +++ /dev/null @@ -1,89 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{Message, TargetMessage}, - Bot, -}; - -/// Use this method to set the score of the specified user in a game. -/// -/// On success, if the message was sent by the bot, returns the edited -/// [`Message`], otherwise returns [`True`]. Returns an error, if the new score -/// is not greater than the user's current score in the chat and force is -/// `false`. -/// -/// [The official docs](https://core.telegram.org/bots/api#setgamescore). -/// -/// [`Message`]: crate::types::Message -/// [`True`]: crate::types::True -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SetGameScore { - #[serde(skip_serializing)] - bot: Bot, - #[serde(flatten)] - target: TargetMessage, - user_id: i32, - score: i32, - force: Option, - disable_edit_message: Option, -} - -#[async_trait::async_trait] -impl Request for SetGameScore { - type Output = Message; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "setGameScore", &self).await - } -} - -impl SetGameScore { - pub(crate) fn new(bot: Bot, target: T, user_id: i32, score: i32) -> Self - where - T: Into, - { - let target = target.into(); - Self { bot, target, user_id, score, force: None, disable_edit_message: None } - } - - /// Target message, either chat id and message id or inline message id. - pub fn target(mut self, val: T) -> Self - where - T: Into, - { - self.target = val.into(); - self - } - - /// User identifier. - pub fn user_id(mut self, val: i32) -> Self { - self.user_id = val; - self - } - - /// New score, must be non-negative. - pub fn score(mut self, val: i32) -> Self { - self.score = val; - self - } - - /// Pass `true`, if the high score is allowed to decrease. - /// - /// This can be useful when fixing mistakes or banning cheaters. - pub fn force(mut self, val: bool) -> Self { - self.force = Some(val); - self - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_edit_message(mut self, val: bool) -> Self { - self.disable_edit_message = Some(val); - self - } -} diff --git a/src/requests/all/set_my_commands.rs b/src/requests/all/set_my_commands.rs deleted file mode 100644 index 831f9705..00000000 --- a/src/requests/all/set_my_commands.rs +++ /dev/null @@ -1,50 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{BotCommand, True}, - Bot, -}; - -/// Use this method to change the list of the bot's commands. -/// -/// [The official docs](https://core.telegram.org/bots/api#setmycommands). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SetMyCommands { - #[serde(skip_serializing)] - bot: Bot, - - commands: Vec, -} - -#[async_trait::async_trait] -impl Request for SetMyCommands { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "setMyCommands", &self).await - } -} - -impl SetMyCommands { - pub(crate) fn new(bot: Bot, commands: C) -> Self - where - C: Into>, - { - Self { bot, commands: commands.into() } - } - - /// A JSON-serialized list of bot commands to be set as the list of the - /// bot's commands. - /// - /// At most 100 commands can be specified. - pub fn commands(mut self, commands: C) -> Self - where - C: Into>, - { - self.commands = commands.into(); - self - } -} diff --git a/src/requests/all/set_sticker_position_in_set.rs b/src/requests/all/set_sticker_position_in_set.rs deleted file mode 100644 index 4ed78551..00000000 --- a/src/requests/all/set_sticker_position_in_set.rs +++ /dev/null @@ -1,56 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::True, - Bot, -}; - -/// Use this method to move a sticker in a set created by the bot to a specific -/// position. -/// -/// [The official docs](https://core.telegram.org/bots/api#setstickerpositioninset). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SetStickerPositionInSet { - #[serde(skip_serializing)] - bot: Bot, - sticker: String, - position: i32, -} - -#[async_trait::async_trait] -impl Request for SetStickerPositionInSet { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "setStickerPositionInSet", &self) - .await - } -} - -impl SetStickerPositionInSet { - pub(crate) fn new(bot: Bot, sticker: S, position: i32) -> Self - where - S: Into, - { - let sticker = sticker.into(); - Self { bot, sticker, position } - } - - /// File identifier of the sticker. - pub fn sticker(mut self, val: T) -> Self - where - T: Into, - { - self.sticker = val.into(); - self - } - - /// New sticker position in the set, zero-based. - pub fn position(mut self, val: i32) -> Self { - self.position = val; - self - } -} diff --git a/src/requests/all/set_sticker_set_thumb.rs b/src/requests/all/set_sticker_set_thumb.rs deleted file mode 100644 index ecf153f4..00000000 --- a/src/requests/all/set_sticker_set_thumb.rs +++ /dev/null @@ -1,74 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{InputFile, True}, - Bot, -}; - -/// Use this method to set the thumbnail of a sticker set. Animated thumbnails -/// can be set for animated sticker sets only. -/// -/// [The official docs](https://core.telegram.org/bots/api#setstickersetthumb). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SetStickerSetThumb { - #[serde(skip_serializing)] - bot: Bot, - name: String, - user_id: i32, - thumb: Option, -} - -#[async_trait::async_trait] -impl Request for SetStickerSetThumb { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "setStickerSetThumb", &self).await - } -} - -impl SetStickerSetThumb { - pub(crate) fn new(bot: Bot, name: S, user_id: i32) -> Self - where - S: Into, - { - Self { bot, name: name.into(), user_id, thumb: None } - } - - /// Sticker set name. - pub fn name(mut self, val: T) -> Self - where - T: Into, - { - self.name = val.into(); - self - } - - /// User identifier of the sticker set owner. - pub fn user_id(mut self, val: i32) -> Self { - self.user_id = val; - self - } - - /// A PNG image with the thumbnail, must be up to 128 kilobytes in size and - /// have width and height exactly 100px, or a TGS animation with the - /// thumbnail up to 32 kilobytes in size; see https://core.telegram.org/animated_stickers#technical-requirements - /// for animated sticker technical requirements. - /// - /// Pass [`InputFile::FileId`] to send a file that exists on the Telegram - /// servers (recommended), pass an [`InputFile::Url`] for Telegram to get a - /// file from the Internet (20MB max.), pass [`InputFile::File`] to upload - /// a file from the file system or [`InputFile::Memory`] to upload a file - /// from memory (50MB max. each). [More info on Sending Files »]. Animated - /// sticker set thumbnail can't be uploaded via HTTP URL. - /// - /// [`InputFile::FileId`]: crate::types::InputFile::FileId - /// [`InputFile::Url]: crate::types::InputFile::Url - pub fn thumb(mut self, val: InputFile) -> Self { - self.thumb = Some(val); - self - } -} diff --git a/src/requests/all/set_webhook.rs b/src/requests/all/set_webhook.rs deleted file mode 100644 index c2aff792..00000000 --- a/src/requests/all/set_webhook.rs +++ /dev/null @@ -1,114 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{AllowedUpdate, InputFile, True}, - Bot, -}; - -/// Use this method to specify a url and receive incoming updates via an -/// outgoing webhook. -/// -/// Whenever there is an update for the bot, we will send an -/// HTTPS POST request to the specified url, containing a JSON-serialized -/// [`Update`]. In case of an unsuccessful request, we will give up after a -/// reasonable amount of attempts. -/// -/// If you'd like to make sure that the Webhook request comes from Telegram, -/// we recommend using a secret path in the URL, e.g. -/// `https://www.example.com/`. Since nobody else knows your bot‘s -/// token, you can be pretty sure it’s us. -/// -/// [The official docs](https://core.telegram.org/bots/api#setwebhook). -/// -/// [`Update`]: crate::types::Update -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SetWebhook { - #[serde(skip_serializing)] - bot: Bot, - url: String, - certificate: Option, - max_connections: Option, - allowed_updates: Option>, -} - -#[async_trait::async_trait] -impl Request for SetWebhook { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "setWebhook", &self).await - } -} - -impl SetWebhook { - pub(crate) fn new(bot: Bot, url: U) -> Self - where - U: Into, - { - let url = url.into(); - Self { bot, url, certificate: None, max_connections: None, allowed_updates: None } - } - - /// HTTPS url to send updates to. - /// - /// Use an empty string to remove webhook integration. - pub fn url(mut self, val: T) -> Self - where - T: Into, - { - self.url = val.into(); - self - } - - /// Upload your public key certificate so that the root certificate in use - /// can be checked. - /// - /// See our [self-signed guide] for details. - /// - /// [self-signed guide]: https://core.telegram.org/bots/self-signed - pub fn certificate(mut self, val: InputFile) -> Self { - self.certificate = Some(val); - self - } - - /// Maximum allowed number of simultaneous HTTPS connections to the webhook - /// for update delivery, 1-100. - /// - /// Defaults to 40. Use lower values to limit the load on your bot‘s server, - /// and higher values to increase your bot’s throughput. - pub fn max_connections(mut self, val: i32) -> Self { - self.max_connections = Some(val); - self - } - - /// List the types of updates you want your bot to receive. - /// - /// For example, specify [`AllowedUpdate::Message`], - /// [`AllowedUpdate::EditedChannelPost`], [`AllowedUpdate::CallbackQuery`] - /// to only receive updates of these types. Specify an empty list to receive - /// all updates regardless of type (default). If not specified, the - /// previous setting will be used. See [`AllowedUpdate`] for a complete list - /// of available update types. - /// - /// Please note that this parameter doesn't affect updates created before - /// the call to the [`Bot::set_webhook`], so unwanted updates may be - /// received for a short period of time. - /// - /// [`Bot::set_webhook`]: crate::Bot::set_webhook - /// [`AllowedUpdate::Message`]: crate::types::AllowedUpdate::Message - /// [`AllowedUpdate::EditedChannelPost`]: - /// crate::types::AllowedUpdate::EditedChannelPost - /// [`AllowedUpdate::CallbackQuery`]: - /// crate::types::AllowedUpdate::CallbackQuery - /// [`AllowedUpdate`]: crate::types::AllowedUpdate - pub fn allowed_updates(mut self, val: T) -> Self - where - T: Into>, - { - self.allowed_updates = Some(val.into()); - self - } -} diff --git a/src/requests/all/stop_inline_message_live_location.rs b/src/requests/all/stop_inline_message_live_location.rs deleted file mode 100644 index 40fb5604..00000000 --- a/src/requests/all/stop_inline_message_live_location.rs +++ /dev/null @@ -1,62 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{InlineKeyboardMarkup, Message}, - Bot, -}; - -/// Use this method to stop updating a live location message (sent via the bot) -/// before `live_period` expires. -/// -/// On success, [`True`] is returned. -/// -/// [The official docs](https://core.telegram.org/bots/api#stopmessagelivelocation). -/// -/// [`True`]: crate::types::True -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct StopInlineMessageLiveLocation { - #[serde(skip_serializing)] - bot: Bot, - inline_message_id: String, - reply_markup: Option, -} - -#[async_trait::async_trait] -impl Request for StopInlineMessageLiveLocation { - type Output = Message; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "stopMessageLiveLocation", &self) - .await - } -} - -impl StopInlineMessageLiveLocation { - pub(crate) fn new(bot: Bot, inline_message_id: I) -> Self - where - I: Into, - { - let inline_message_id = inline_message_id.into(); - Self { bot, inline_message_id, reply_markup: None } - } - - /// Identifier of the inline message. - pub fn inline_message_id(mut self, val: T) -> Self - where - T: Into, - { - self.inline_message_id = val.into(); - self - } - - /// A JSON-serialized object for a new [inline keyboard]. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/stop_message_live_location.rs b/src/requests/all/stop_message_live_location.rs deleted file mode 100644 index 892492ea..00000000 --- a/src/requests/all/stop_message_live_location.rs +++ /dev/null @@ -1,70 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, InlineKeyboardMarkup, Message}, - Bot, -}; - -/// Use this method to stop updating a live location message before -/// `live_period` expires. -/// -/// On success, the sent [`Message`] is returned. -/// -/// [The official docs](https://core.telegram.org/bots/api#stopmessagelivelocation). -/// -/// [`Message`]: crate::types::Message -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct StopMessageLiveLocation { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, - message_id: i32, - reply_markup: Option, -} - -#[async_trait::async_trait] -impl Request for StopMessageLiveLocation { - type Output = Message; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "stopMessageLiveLocation", &self) - .await - } -} - -impl StopMessageLiveLocation { - pub(crate) fn new(bot: Bot, chat_id: C, message_id: i32) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { bot, chat_id, message_id, reply_markup: None } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`) - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Identifier of the message to edit - pub fn message_id(mut self, val: i32) -> Self { - self.message_id = val; - self - } - - /// A JSON-serialized object for a new [inline keyboard]. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/stop_poll.rs b/src/requests/all/stop_poll.rs deleted file mode 100644 index 946b4760..00000000 --- a/src/requests/all/stop_poll.rs +++ /dev/null @@ -1,66 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, InlineKeyboardMarkup, Poll}, - Bot, -}; - -/// Use this method to stop a poll which was sent by the bot. -/// -/// [The official docs](https://core.telegram.org/bots/api#stoppoll). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct StopPoll { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, - message_id: i32, - reply_markup: Option, -} - -#[async_trait::async_trait] -impl Request for StopPoll { - type Output = Poll; - - /// On success, the stopped [`Poll`] with the final results is returned. - /// - /// [`Poll`]: crate::types::Poll - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "stopPoll", &self).await - } -} -impl StopPoll { - pub(crate) fn new(bot: Bot, chat_id: C, message_id: i32) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { bot, chat_id, message_id, reply_markup: None } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Identifier of the original message with the poll. - pub fn message_id(mut self, val: i32) -> Self { - self.message_id = val; - self - } - - /// A JSON-serialized object for a new [inline keyboard]. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/unban_chat_member.rs b/src/requests/all/unban_chat_member.rs deleted file mode 100644 index 5aee625c..00000000 --- a/src/requests/all/unban_chat_member.rs +++ /dev/null @@ -1,58 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, True}, - Bot, -}; - -/// Use this method to unban a previously kicked user in a supergroup or -/// channel. The user will **not** return to the group or channel automatically, -/// but will be able to join via link, etc. The bot must be an administrator for -/// this to work. -/// -/// [The official docs](https://core.telegram.org/bots/api#unbanchatmember). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct UnbanChatMember { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, - user_id: i32, -} - -#[async_trait::async_trait] -impl Request for UnbanChatMember { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "unbanChatMember", &self).await - } -} - -impl UnbanChatMember { - pub(crate) fn new(bot: Bot, chat_id: C, user_id: i32) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { bot, chat_id, user_id } - } - - /// Unique identifier for the target group or username of the target - /// supergroup or channel (in the format `@username`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Unique identifier of the target user. - pub fn user_id(mut self, val: i32) -> Self { - self.user_id = val; - self - } -} diff --git a/src/requests/all/unpin_chat_message.rs b/src/requests/all/unpin_chat_message.rs deleted file mode 100644 index 15e76a99..00000000 --- a/src/requests/all/unpin_chat_message.rs +++ /dev/null @@ -1,52 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, True}, - Bot, -}; - -/// Use this method to unpin a message in a group, a supergroup, or a channel. -/// -/// The bot must be an administrator in the chat for this to work and must have -/// the `can_pin_messages` admin right in the supergroup or `can_edit_messages` -/// admin right in the channel. -/// -/// [The official docs](https://core.telegram.org/bots/api#unpinchatmessage). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct UnpinChatMessage { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, -} - -#[async_trait::async_trait] -impl Request for UnpinChatMessage { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "unpinChatMessage", &self).await - } -} - -impl UnpinChatMessage { - pub(crate) fn new(bot: Bot, chat_id: C) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { bot, chat_id } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } -} diff --git a/src/requests/all/upload_sticker_file.rs b/src/requests/all/upload_sticker_file.rs deleted file mode 100644 index b09b143f..00000000 --- a/src/requests/all/upload_sticker_file.rs +++ /dev/null @@ -1,55 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{File, InputFile}, - Bot, -}; - -/// Use this method to upload a .png file with a sticker for later use in -/// [`Bot::create_new_sticker_set`] and [`Bot::add_sticker_to_set`] methods (can -/// be used multiple times). -/// -/// [The official docs](https://core.telegram.org/bots/api#uploadstickerfile). -/// -/// [`Bot::create_new_sticker_set`]: crate::Bot::create_new_sticker_set -/// [`Bot::add_sticker_to_set`]: crate::Bot::add_sticker_to_set -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct UploadStickerFile { - #[serde(skip_serializing)] - bot: Bot, - user_id: i32, - png_sticker: InputFile, -} -#[async_trait::async_trait] -impl Request for UploadStickerFile { - type Output = File; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "uploadStickerFile", &self).await - } -} - -impl UploadStickerFile { - pub(crate) fn new(bot: Bot, user_id: i32, png_sticker: InputFile) -> Self { - Self { bot, user_id, png_sticker } - } - - /// User identifier of sticker file owner. - pub fn user_id(mut self, val: i32) -> Self { - self.user_id = val; - self - } - - /// **Png** image with the sticker, must be up to 512 kilobytes in size, - /// dimensions must not exceed 512px, and either width or height must be - /// exactly 512px. [More info on Sending Files »]. - /// - /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files - pub fn png_sticker(mut self, val: InputFile) -> Self { - self.png_sticker = val; - self - } -} diff --git a/src/requests/form_builder.rs b/src/requests/form_builder.rs deleted file mode 100644 index 3c7f70b9..00000000 --- a/src/requests/form_builder.rs +++ /dev/null @@ -1,156 +0,0 @@ -use std::{borrow::Cow, path::PathBuf}; - -use reqwest::multipart::Form; - -use crate::{ - requests::utils::{file_from_memory_to_part, file_to_part}, - types::{ - ChatId, InlineKeyboardMarkup, InputFile, InputMedia, MaskPosition, ParseMode, ReplyMarkup, - }, -}; - -/// This is a convenient struct that builds `reqwest::multipart::Form` -/// from scratch. -pub(crate) struct FormBuilder { - form: Form, -} - -impl FormBuilder { - pub(crate) fn new() -> Self { - Self { form: Form::new() } - } - - pub fn add_text<'a, T, N>(self, name: N, value: &T) -> Self - where - N: Into>, - T: IntoFormText, - { - match value.into_form_text() { - Some(val) => Self { form: self.form.text(name.into().into_owned(), val) }, - None => self, - } - } - - pub async fn add_input_file<'a, N>(self, name: N, value: &InputFile) -> tokio::io::Result - where - N: Into>, - { - Ok(match value { - InputFile::File(path) => self.add_file(name, path.clone()).await?, - InputFile::Memory { file_name, data } => { - self.add_file_from_memory(name, file_name.clone(), data.clone()) - } - InputFile::Url(url) => self.add_text(name, url), - InputFile::FileId(file_id) => self.add_text(name, file_id), - }) - } - - // used in SendMediaGroup - pub async fn add_file<'a, N>(self, name: N, path_to_file: PathBuf) -> tokio::io::Result - where - N: Into>, - { - Ok(Self { - form: self.form.part(name.into().into_owned(), file_to_part(path_to_file).await?), - }) - } - - fn add_file_from_memory<'a, N>( - self, - name: N, - file_name: String, - data: Cow<'static, [u8]>, - ) -> Self - where - N: Into>, - { - Self { - form: self - .form - .part(name.into().into_owned(), file_from_memory_to_part(data, file_name)), - } - } - - pub fn build(self) -> Form { - self.form - } -} - -pub(crate) trait IntoFormText { - fn into_form_text(&self) -> Option; -} - -macro_rules! impl_for_struct { - ($($name:ty),*) => { - $( - impl IntoFormText for $name { - fn into_form_text(&self) -> Option { - let json = serde_json::to_string(self) - .expect("serde_json::to_string failed"); - Some(json) - } - } - )* - }; -} - -impl_for_struct!(bool, i32, i64, u32, ReplyMarkup, InlineKeyboardMarkup, MaskPosition); - -impl IntoFormText for Option -where - T: IntoFormText, -{ - fn into_form_text(&self) -> Option { - self.as_ref().and_then(IntoFormText::into_form_text) - } -} - -// TODO: fix InputMedia implementation of IntoFormValue (for now it doesn't -// encode files :|) -impl IntoFormText for Vec { - fn into_form_text(&self) -> Option { - let json = serde_json::to_string(self).expect("serde_json::to_string failed"); - Some(json) - } -} - -impl IntoFormText for InputMedia { - fn into_form_text(&self) -> Option { - let json = serde_json::to_string(self).expect("serde_json::to_string failed"); - Some(json) - } -} - -impl IntoFormText for str { - fn into_form_text(&self) -> Option { - Some(self.to_owned()) - } -} - -impl IntoFormText for ParseMode { - fn into_form_text(&self) -> Option { - let string = match self { - ParseMode::MarkdownV2 => String::from("MarkdownV2"), - ParseMode::HTML => String::from("HTML"), - #[allow(deprecated)] - ParseMode::Markdown => String::from("Markdown"), - }; - Some(string) - } -} - -impl IntoFormText for ChatId { - fn into_form_text(&self) -> Option { - let string = match self { - ChatId::Id(id) => id.to_string(), - ChatId::ChannelUsername(username) => username.clone(), - }; - Some(string) - } -} - -impl IntoFormText for String { - fn into_form_text(&self) -> Option { - Some(self.clone()) - } -} diff --git a/src/requests/mod.rs b/src/requests/mod.rs deleted file mode 100644 index bb104ddf..00000000 --- a/src/requests/mod.rs +++ /dev/null @@ -1,37 +0,0 @@ -//! API requests. - -mod all; -mod form_builder; -mod utils; - -pub use all::*; - -/// A type that is returned after making a request to Telegram. -pub type ResponseResult = Result; - -/// A shortcut for `ResponseResult::Ok(val)`. -pub fn respond(val: T) -> ResponseResult { - ResponseResult::Ok(val) -} - -/// Designates an API request. -#[async_trait::async_trait] -pub trait Request { - /// A data structure returned if success. - type Output; - - /// Asynchronously sends this request to Telegram and returns the result. - async fn send(&self) -> ResponseResult; -} - -/// Designates an API request with possibly sending file. -#[async_trait::async_trait] -pub trait RequestWithFile { - /// A data structure returned if success. - type Output; - - /// Asynchronously sends this request to Telegram and returns the result. - /// Returns `tokio::io::Result::Err` when trying to send file which does not - /// exists. - async fn send(&self) -> tokio::io::Result>; -} diff --git a/src/requests/utils.rs b/src/requests/utils.rs deleted file mode 100644 index f10c9dc5..00000000 --- a/src/requests/utils.rs +++ /dev/null @@ -1,31 +0,0 @@ -use std::{borrow::Cow, path::PathBuf}; - -use bytes::{Bytes, BytesMut}; -use reqwest::{multipart::Part, Body}; -use tokio_util::codec::{Decoder, FramedRead}; - -struct FileDecoder; - -impl Decoder for FileDecoder { - type Item = Bytes; - type Error = std::io::Error; - - fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { - if src.is_empty() { - return Ok(None); - } - Ok(Some(src.split().freeze())) - } -} - -pub async fn file_to_part(path_to_file: PathBuf) -> std::io::Result { - let file_name = path_to_file.file_name().unwrap().to_string_lossy().into_owned(); - - let file = FramedRead::new(tokio::fs::File::open(path_to_file).await?, FileDecoder); - - Ok(Part::stream(Body::wrap_stream(file)).file_name(file_name)) -} - -pub fn file_from_memory_to_part(data: Cow<'static, [u8]>, name: String) -> Part { - Part::bytes(data).file_name(name) -} diff --git a/src/types/allowed_update.rs b/src/types/allowed_update.rs deleted file mode 100644 index f3b920ac..00000000 --- a/src/types/allowed_update.rs +++ /dev/null @@ -1,14 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum AllowedUpdate { - Message, - EditedMessage, - ChannelPost, - EditedChannelPost, - InlineQuery, - ChosenInlineResult, - CallbackQuery, -} diff --git a/src/types/animation.rs b/src/types/animation.rs deleted file mode 100644 index dc52901c..00000000 --- a/src/types/animation.rs +++ /dev/null @@ -1,165 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{MimeWrapper, PhotoSize}; - -/// This object represents an animation file (GIF or H.264/MPEG-4 AVC video -/// without sound). -/// -/// [The official docs](https://core.telegram.org/bots/api#animation). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct Animation { - /// An identifier for this file. - pub file_id: String, - - /// Unique identifier for this file, which is supposed to be the same over - /// time and for different bots. Can't be used to download or reuse the - /// file. - pub file_unique_id: String, - - /// A video width as defined by a sender. - pub width: u32, - - /// A video height as defined by a sender. - pub height: u32, - - /// A duration of the video in seconds as defined by a sender. - pub duration: u32, - - /// An animation thumbnail as defined by a sender. - pub thumb: Option, - - /// An original animation filename as defined by a sender. - pub file_name: Option, - - /// A MIME type of the file as defined by a sender. - pub mime_type: Option, - - /// A size of a file. - pub file_size: Option, -} - -impl Animation { - pub fn new( - file_id: S1, - file_unique_id: S2, - width: u32, - height: u32, - duration: u32, - ) -> Self - where - S1: Into, - S2: Into, - { - Self { - file_id: file_id.into(), - file_unique_id: file_unique_id.into(), - width, - height, - duration, - thumb: None, - file_name: None, - mime_type: None, - file_size: None, - } - } - - pub fn file_id(mut self, val: S) -> Self - where - S: Into, - { - self.file_id = val.into(); - self - } - - pub fn file_unique_id(mut self, val: S) -> Self - where - S: Into, - { - self.file_unique_id = val.into(); - self - } - - pub fn width(mut self, val: u32) -> Self { - self.width = val; - self - } - - pub fn height(mut self, val: u32) -> Self { - self.height = val; - self - } - - pub fn duration(mut self, val: u32) -> Self { - self.duration = val; - self - } - - pub fn thumb(mut self, val: PhotoSize) -> Self { - self.thumb = Some(val); - self - } - - pub fn file_name(mut self, val: S) -> Self - where - S: Into, - { - self.file_name = Some(val.into()); - self - } - - pub fn mime_type(mut self, val: MimeWrapper) -> Self { - self.mime_type = Some(val); - self - } - - pub fn file_size(mut self, val: u32) -> Self { - self.file_size = Some(val); - self - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn deserialize() { - let json = r#"{ - "file_id":"id", - "file_unique_id":"", - "width":320, - "height":320, - "duration":59, - "thumb":{ - "file_id":"id", - "file_unique_id":"", - "width":320, - "height":320, - "file_size":3452 - }, - "file_name":"some", - "mime_type":"video/gif", - "file_size":6500}"#; - let expected = Animation { - file_id: "id".to_string(), - file_unique_id: "".to_string(), - width: 320, - height: 320, - duration: 59, - thumb: Some(PhotoSize { - file_id: "id".to_string(), - file_unique_id: "".to_string(), - width: 320, - height: 320, - file_size: Some(3452), - }), - file_name: Some("some".to_string()), - mime_type: Some(MimeWrapper("video/gif".parse().unwrap())), - file_size: Some(6500), - }; - let actual = serde_json::from_str::(json).unwrap(); - assert_eq!(actual, expected) - } -} diff --git a/src/types/audio.rs b/src/types/audio.rs deleted file mode 100644 index d5599c20..00000000 --- a/src/types/audio.rs +++ /dev/null @@ -1,152 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{MimeWrapper, PhotoSize}; - -/// This object represents an audio file to be treated as music by the Telegram -/// clients. -/// -/// [The official docs](https://core.telegram.org/bots/api#audio). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct Audio { - /// An identifier for this file. - pub file_id: String, - - /// Unique identifier for this file, which is supposed to be the same over - /// time and for different bots. Can't be used to download or reuse the - /// file. - pub file_unique_id: String, - - /// A duration of the audio in seconds as defined by a sender. - pub duration: u32, - - /// A performer of the audio as defined by a sender or by audio tags. - pub performer: Option, - - /// A title of the audio as defined by sender or by audio tags. - pub title: Option, - - /// A MIME type of the file as defined by a sender. - pub mime_type: Option, - - /// A size of a file. - pub file_size: Option, - - /// A thumbnail of the album cover to which the music file belongs. - pub thumb: Option, -} - -impl Audio { - pub fn new(file_id: S1, file_unique_id: S2, duration: u32) -> Self - where - S1: Into, - S2: Into, - { - Self { - file_id: file_id.into(), - file_unique_id: file_unique_id.into(), - duration, - performer: None, - title: None, - mime_type: None, - file_size: None, - thumb: None, - } - } - - pub fn file_id(mut self, val: S) -> Self - where - S: Into, - { - self.file_id = val.into(); - self - } - - pub fn file_unique_id(mut self, val: S) -> Self - where - S: Into, - { - self.file_unique_id = val.into(); - self - } - - pub fn duration(mut self, val: u32) -> Self { - self.duration = val; - self - } - - pub fn performer(mut self, val: S) -> Self - where - S: Into, - { - self.performer = Some(val.into()); - self - } - - pub fn title(mut self, val: S) -> Self - where - S: Into, - { - self.title = Some(val.into()); - self - } - - pub fn mime_type(mut self, val: MimeWrapper) -> Self { - self.mime_type = Some(val); - self - } - - pub fn file_size(mut self, val: u32) -> Self { - self.file_size = Some(val); - self - } - - pub fn thumb(mut self, val: PhotoSize) -> Self { - self.thumb = Some(val); - self - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn deserialize() { - let json = r#"{ - "file_id":"id", - "file_unique_id":"", - "duration":60, - "performer":"Performer", - "title":"Title", - "mime_type":"application/zip", - "file_size":123456, - "thumb":{ - "file_id":"id", - "file_unique_id":"", - "width":320, - "height":320, - "file_size":3452 - } - }"#; - let expected = Audio { - file_id: "id".to_string(), - file_unique_id: "".to_string(), - duration: 60, - performer: Some("Performer".to_string()), - title: Some("Title".to_string()), - mime_type: Some(serde_json::from_str("\"application/zip\"").unwrap()), - file_size: Some(123_456), - thumb: Some(PhotoSize { - file_id: "id".to_string(), - file_unique_id: "".to_string(), - width: 320, - height: 320, - file_size: Some(3452), - }), - }; - let actual = serde_json::from_str::

(mut self, val: P) -> Self - where - P: Into>, - { - self.translation = Some(val.into()); - self - } -} - -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct EncryptedPassportElementDriverLicense { - /// Base64-encoded encrypted Telegram Passport element data provided - /// by the user, available for `personal_details`, `passport`, - /// `driver_license`, `identity_card`, `internal_passport` and - /// `address` types. Can be decrypted and verified using the - /// accompanying [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub data: String, - - /// Encrypted file with the front side of the document, provided by the - /// user. Available for `passport`, `driver_license`, `identity_card` - /// and `internal_passport`. The file can be decrypted and verified - /// using the accompanying [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub front_side: PassportFile, - - /// Encrypted file with the reverse side of the document, provided by - /// the user. Available for `driver_license` and `identity_card`. The - /// file can be decrypted and verified using the accompanying - /// [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub reverse_side: PassportFile, - - /// Encrypted file with the selfie of the user holding a document, - /// provided by the user; available for `passport`, `driver_license`, - /// `identity_card` and `internal_passport`. The file can be decrypted - /// and verified using the accompanying [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub selfie: PassportFile, - - /// Array of encrypted files with translated versions of documents - /// provided by the user. Available if requested for `passport`, - /// `driver_license`, `identity_card`, `internal_passport`, - /// `utility_bill`, `bank_statement`, `rental_agreement`, - /// `passport_registration` and `temporary_registration` types. Files - /// can be decrypted and verified using the accompanying - /// [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub translation: Option>, -} - -impl EncryptedPassportElementDriverLicense { - pub fn new( - data: S, - front_side: PassportFile, - reverse_side: PassportFile, - selfie: PassportFile, - ) -> Self - where - S: Into, - { - Self { data: data.into(), front_side, reverse_side, selfie, translation: None } - } - - pub fn data(mut self, val: S) -> Self - where - S: Into, - { - self.data = val.into(); - self - } - - pub fn front_side(mut self, val: PassportFile) -> Self { - self.front_side = val; - self - } - - pub fn reverse_side(mut self, val: PassportFile) -> Self { - self.reverse_side = val; - self - } - - pub fn selfie(mut self, val: PassportFile) -> Self { - self.selfie = val; - self - } - pub fn translation

(mut self, val: P) -> Self - where - P: Into>, - { - self.translation = Some(val.into()); - self - } -} - -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct EncryptedPassportElementIdentityCard { - /// Base64-encoded encrypted Telegram Passport element data provided - /// by the user, available for `personal_details`, `passport`, - /// `driver_license`, `identity_card`, `internal_passport` and - /// `address` types. Can be decrypted and verified using the - /// accompanying [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub data: String, - - /// Encrypted file with the front side of the document, provided by the - /// user. Available for `passport`, `driver_license`, `identity_card` - /// and `internal_passport`. The file can be decrypted and verified - /// using the accompanying [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub front_side: PassportFile, - - /// Encrypted file with the reverse side of the document, provided by - /// the user. Available for `driver_license` and `identity_card`. The - /// file can be decrypted and verified using the accompanying - /// [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub reverse_side: PassportFile, - - /// Encrypted file with the selfie of the user holding a document, - /// provided by the user; available for `passport`, `driver_license`, - /// `identity_card` and `internal_passport`. The file can be decrypted - /// and verified using the accompanying [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub selfie: PassportFile, - - /// Array of encrypted files with translated versions of documents - /// provided by the user. Available if requested for `passport`, - /// `driver_license`, `identity_card`, `internal_passport`, - /// `utility_bill`, `bank_statement`, `rental_agreement`, - /// `passport_registration` and `temporary_registration` types. Files - /// can be decrypted and verified using the accompanying - /// [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub translation: Option>, -} - -impl EncryptedPassportElementIdentityCard { - pub fn new( - data: S, - front_side: PassportFile, - reverse_side: PassportFile, - selfie: PassportFile, - ) -> Self - where - S: Into, - { - Self { data: data.into(), front_side, reverse_side, selfie, translation: None } - } - - pub fn data(mut self, val: S) -> Self - where - S: Into, - { - self.data = val.into(); - self - } - - pub fn front_side(mut self, val: PassportFile) -> Self { - self.front_side = val; - self - } - - pub fn reverse_side(mut self, val: PassportFile) -> Self { - self.reverse_side = val; - self - } - - pub fn selfie(mut self, val: PassportFile) -> Self { - self.selfie = val; - self - } - pub fn translation

(mut self, val: P) -> Self - where - P: Into>, - { - self.translation = Some(val.into()); - self - } -} - -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct EncryptedPassportElementInternalPassport { - /// Base64-encoded encrypted Telegram Passport element data provided - /// by the user, available for `personal_details`, `passport`, - /// `driver_license`, `identity_card`, `internal_passport` and - /// `address` types. Can be decrypted and verified using the - /// accompanying [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub data: String, - - /// Encrypted file with the front side of the document, provided by the - /// user. Available for `passport`, `driver_license`, `identity_card` - /// and `internal_passport`. The file can be decrypted and verified - /// using the accompanying [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub front_side: PassportFile, - - /// Encrypted file with the selfie of the user holding a document, - /// provided by the user; available for `passport`, `driver_license`, - /// `identity_card` and `internal_passport`. The file can be decrypted - /// and verified using the accompanying [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub selfie: PassportFile, - - /// Array of encrypted files with translated versions of documents - /// provided by the user. Available if requested for `passport`, - /// `driver_license`, `identity_card`, `internal_passport`, - /// `utility_bill`, `bank_statement`, `rental_agreement`, - /// `passport_registration` and `temporary_registration` types. Files - /// can be decrypted and verified using the accompanying - /// [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub translation: Option>, -} - -impl EncryptedPassportElementInternalPassport { - pub fn new(data: S, front_side: PassportFile, selfie: PassportFile) -> Self - where - S: Into, - { - Self { data: data.into(), front_side, selfie, translation: None } - } - - pub fn data(mut self, val: S) -> Self - where - S: Into, - { - self.data = val.into(); - self - } - - pub fn front_side(mut self, val: PassportFile) -> Self { - self.front_side = val; - self - } - - pub fn selfie(mut self, val: PassportFile) -> Self { - self.selfie = val; - self - } - - pub fn translation

(mut self, val: P) -> Self - where - P: Into>, - { - self.translation = Some(val.into()); - self - } -} - -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct EncryptedPassportElementAddress { - /// Base64-encoded encrypted Telegram Passport element data provided - /// by the user, available for `personal_details`, `passport`, - /// `driver_license`, `identity_card`, `internal_passport` and - /// `address` types. Can be decrypted and verified using the - /// accompanying [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub data: String, -} - -impl EncryptedPassportElementAddress { - pub fn new(data: S) -> Self - where - S: Into, - { - Self { data: data.into() } - } - - pub fn data(mut self, val: S) -> Self - where - S: Into, - { - self.data = val.into(); - self - } -} - -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct EncryptedPassportElementUtilityBill { - /// Array of encrypted files with documents provided by the user, - /// available for `utility_bill`, `bank_statement`, `rental_agreement`, - /// `passport_registration` and `temporary_registration` types. Files - /// can be decrypted and verified using the accompanying - /// [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub files: Vec, - - /// Array of encrypted files with translated versions of documents - /// provided by the user. Available if requested for `passport`, - /// `driver_license`, `identity_card`, `internal_passport`, - /// `utility_bill`, `bank_statement`, `rental_agreement`, - /// `passport_registration` and `temporary_registration` types. Files - /// can be decrypted and verified using the accompanying - /// [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub translation: Option>, -} - -impl EncryptedPassportElementUtilityBill { - pub fn new(files: F) -> Self - where - F: Into>, - { - Self { files: files.into(), translation: None } - } - - pub fn files

(mut self, val: P) -> Self - where - P: Into>, - { - self.files = val.into(); - self - } - - pub fn translation

(mut self, val: P) -> Self - where - P: Into>, - { - self.translation = Some(val.into()); - self - } -} - -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct EncryptedPassportElementBankStatement { - /// Array of encrypted files with documents provided by the user, - /// available for `utility_bill`, `bank_statement`, `rental_agreement`, - /// `passport_registration` and `temporary_registration` types. Files - /// can be decrypted and verified using the accompanying - /// [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub files: Vec, - - /// Array of encrypted files with translated versions of documents - /// provided by the user. Available if requested for `passport`, - /// `driver_license`, `identity_card`, `internal_passport`, - /// `utility_bill`, `bank_statement`, `rental_agreement`, - /// `passport_registration` and `temporary_registration` types. Files - /// can be decrypted and verified using the accompanying - /// [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub translation: Option>, -} - -impl EncryptedPassportElementBankStatement { - pub fn new(files: F) -> Self - where - F: Into>, - { - Self { files: files.into(), translation: None } - } - - pub fn files

(mut self, val: P) -> Self - where - P: Into>, - { - self.files = val.into(); - self - } - - pub fn translation

(mut self, val: P) -> Self - where - P: Into>, - { - self.translation = Some(val.into()); - self - } -} - -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct EncryptedPassportElementRentalAgreement { - /// Array of encrypted files with documents provided by the user, - /// available for `utility_bill`, `bank_statement`, `rental_agreement`, - /// `passport_registration` and `temporary_registration` types. Files - /// can be decrypted and verified using the accompanying - /// [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub files: Vec, - - /// Array of encrypted files with translated versions of documents - /// provided by the user. Available if requested for `passport`, - /// `driver_license`, `identity_card`, `internal_passport`, - /// `utility_bill`, `bank_statement`, `rental_agreement`, - /// `passport_registration` and `temporary_registration` types. Files - /// can be decrypted and verified using the accompanying - /// [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub translation: Option>, -} - -impl EncryptedPassportElementRentalAgreement { - pub fn new(files: F) -> Self - where - F: Into>, - { - Self { files: files.into(), translation: None } - } - - pub fn files

(mut self, val: P) -> Self - where - P: Into>, - { - self.files = val.into(); - self - } - - pub fn translation

(mut self, val: P) -> Self - where - P: Into>, - { - self.translation = Some(val.into()); - self - } -} - -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct EncryptedPassportElementPassportRegistration { - /// Array of encrypted files with documents provided by the user, - /// available for `utility_bill`, `bank_statement`, `rental_agreement`, - /// `passport_registration` and `temporary_registration` types. Files - /// can be decrypted and verified using the accompanying - /// [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub files: Vec, - - /// Array of encrypted files with translated versions of documents - /// provided by the user. Available if requested for `passport`, - /// `driver_license`, `identity_card`, `internal_passport`, - /// `utility_bill`, `bank_statement`, `rental_agreement`, - /// `passport_registration` and `temporary_registration` types. Files - /// can be decrypted and verified using the accompanying - /// [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub translation: Option>, -} - -impl EncryptedPassportElementPassportRegistration { - pub fn new(files: F) -> Self - where - F: Into>, - { - Self { files: files.into(), translation: None } - } - - pub fn files

(mut self, val: P) -> Self - where - P: Into>, - { - self.files = val.into(); - self - } - - pub fn translation

(mut self, val: P) -> Self - where - P: Into>, - { - self.translation = Some(val.into()); - self - } -} - -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct EncryptedPassportElementTemporaryRegistration { - /// Array of encrypted files with documents provided by the user, - /// available for `utility_bill`, `bank_statement`, `rental_agreement`, - /// `passport_registration` and `temporary_registration` types. Files - /// can be decrypted and verified using the accompanying - /// [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub files: Vec, - - /// Array of encrypted files with translated versions of documents - /// provided by the user. Available if requested for `passport`, - /// `driver_license`, `identity_card`, `internal_passport`, - /// `utility_bill`, `bank_statement`, `rental_agreement`, - /// `passport_registration` and `temporary_registration` types. Files - /// can be decrypted and verified using the accompanying - /// [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub translation: Option>, -} - -impl EncryptedPassportElementTemporaryRegistration { - pub fn new(files: F) -> Self - where - F: Into>, - { - Self { files: files.into(), translation: None } - } - - pub fn files

(mut self, val: P) -> Self - where - P: Into>, - { - self.files = val.into(); - self - } - - pub fn translation

(mut self, val: P) -> Self - where - P: Into>, - { - self.translation = Some(val.into()); - self - } -} - -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct EncryptedPassportElementPhoneNumber { - /// User's verified phone number, available only for `phone_number` - /// type. - pub phone_number: String, -} - -impl EncryptedPassportElementPhoneNumber { - pub fn new(phone_number: S) -> Self - where - S: Into, - { - Self { phone_number: phone_number.into() } - } - - pub fn phone_number(mut self, val: S) -> Self - where - S: Into, - { - self.phone_number = val.into(); - self - } -} - -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct EncryptedPassportElementEmail { - /// User's verified email address, available only for `email` type. - pub email: String, -} - -impl EncryptedPassportElementEmail { - pub fn new(email: S) -> Self - where - S: Into, - { - Self { email: email.into() } - } - - pub fn email(mut self, val: S) -> Self - where - S: Into, - { - self.email = val.into(); - self - } -} diff --git a/src/types/file.rs b/src/types/file.rs deleted file mode 100644 index 721f61c0..00000000 --- a/src/types/file.rs +++ /dev/null @@ -1,75 +0,0 @@ -use serde::{Deserialize, Serialize}; - -/// This object represents a file ready to be downloaded. -/// -/// The file can be downloaded via the link `https://api.telegram.org/file/bot/`. -/// It is guaranteed that the link will be valid for at least 1 hour. When the -/// link expires, a new one can be requested by calling [`Bot::get_file`]. -/// -/// [The official docs](https://core.telegram.org/bots/api#file). -/// -/// [`Bot::get_file`]: crate::Bot::get_file -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct File { - /// Identifier for this file. - pub file_id: String, - - /// Unique identifier for this file, which is supposed to be the same over - /// time and for different bots. Can't be used to download or reuse the - /// file. - pub file_unique_id: String, - - /// File size, if known. - pub file_size: u32, - - // TODO: chacge "Use ..." to use bot.download... - /// File path. Use `https://api.telegram.org/file/bot/` - /// to get the file. - pub file_path: String, -} - -impl File { - pub fn new(file_id: S1, file_unique_id: S2, file_size: u32, file_path: S3) -> Self - where - S1: Into, - S2: Into, - S3: Into, - { - Self { - file_id: file_id.into(), - file_unique_id: file_unique_id.into(), - file_size, - file_path: file_path.into(), - } - } - - pub fn file_id(mut self, val: S) -> Self - where - S: Into, - { - self.file_id = val.into(); - self - } - - pub fn file_unique_id(mut self, val: S) -> Self - where - S: Into, - { - self.file_unique_id = val.into(); - self - } - - pub fn file_size(mut self, val: u32) -> Self { - self.file_size = val; - self - } - - pub fn file_path(mut self, val: S) -> Self - where - S: Into, - { - self.file_id = val.into(); - self - } -} diff --git a/src/types/force_reply.rs b/src/types/force_reply.rs deleted file mode 100644 index a6a74408..00000000 --- a/src/types/force_reply.rs +++ /dev/null @@ -1,41 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::True; - -/// Upon receiving a message with this object, Telegram clients will display a -/// reply interface to the user (act as if the user has selected the bot‘s -/// message and tapped ’Reply'). -/// -/// This can be extremely useful if you want to create user-friendly -/// step-by-step interfaces without having to sacrifice [privacy mode]. -/// -/// [The official docs](https://core.telegram.org/bots/api#forcereply). -/// -/// [privacy mode]: https://core.telegram.org/bots#privacy-mode -#[serde_with_macros::skip_serializing_none] -#[derive(Copy, Clone, Default, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct ForceReply { - /// Shows reply interface to the user, as if they manually selected the - /// bot‘s message and tapped ’Reply'. - pub force_reply: True, - - /// Use this parameter if you want to force reply from specific users only. - /// Targets: 1) users that are `@mentioned` in the text of the - /// [`Message`] object; 2) if the bot's message is a reply - /// (has reply_to_message_id), sender of the original message. - /// - /// [`Message`]: crate::types::Message - pub selective: Option, -} - -impl ForceReply { - pub fn new() -> Self { - Self::default() - } - - pub fn selective(mut self, val: bool) -> Self { - self.selective = Some(val); - self - } -} diff --git a/src/types/game.rs b/src/types/game.rs deleted file mode 100644 index 52fe01b8..00000000 --- a/src/types/game.rs +++ /dev/null @@ -1,106 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{Animation, MessageEntity, PhotoSize}; - -/// This object represents a game. -/// -/// Use [@Botfather] to create and edit games, their short names will act as -/// unique identifiers. -/// -/// [@Botfather]: https://t.me/botfather -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct Game { - /// Title of the game. - pub title: String, - - /// Description of the game. - pub description: String, - - /// Photo that will be displayed in the game message in chats. - pub photo: Vec, - - /// Brief description of the game or high scores included in the game - /// message. Can be automatically edited to include current high scores - /// for the game when the bot calls [`Bot::set_game_score`], or manually - /// edited using [`Bot::edit_message_text`]. 0-4096 characters. - /// - /// [`Bot::set_game_score`]: crate::Bot::set_game_score - /// - /// [`Bot::edit_message_text`]: crate::Bot::edit_message_text - pub text: Option, - - /// Special entities that appear in text, such as usernames, URLs, bot - /// commands, etc. - pub text_entities: Option>, - - /// Animation that will be displayed in the game message in chats. Upload - /// via [@Botfather]. - /// - /// [@Botfather]: https://t.me/botfather - pub animation: Option, -} - -impl Game { - pub fn new(title: S1, description: S2, photo: P) -> Self - where - S1: Into, - S2: Into, - P: Into>, - { - Self { - title: title.into(), - description: description.into(), - photo: photo.into(), - text: None, - text_entities: None, - animation: None, - } - } - - pub fn title(mut self, val: S) -> Self - where - S: Into, - { - self.title = val.into(); - self - } - - pub fn description(mut self, val: S) -> Self - where - S: Into, - { - self.description = val.into(); - self - } - - pub fn photo

(mut self, val: P) -> Self - where - P: Into>, - { - self.photo = val.into(); - self - } - - pub fn text(mut self, val: S) -> Self - where - S: Into, - { - self.text = Some(val.into()); - self - } - - pub fn text_entities(mut self, val: T) -> Self - where - T: Into>, - { - self.text_entities = Some(val.into()); - self - } - - pub fn animation(mut self, val: Animation) -> Self { - self.animation = Some(val); - self - } -} diff --git a/src/types/game_high_score.rs b/src/types/game_high_score.rs deleted file mode 100644 index f713b18e..00000000 --- a/src/types/game_high_score.rs +++ /dev/null @@ -1,40 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::user::User; - -/// This object represents one row of the high scores table for a game. -/// -/// [The official docs](https://core.telegram.org/bots/api#gamehighscore). -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct GameHighScore { - /// Position in high score table for the game. - pub position: u32, - - /// User. - pub user: User, - - /// Score. - pub score: u32, -} - -impl GameHighScore { - pub fn new(position: u32, user: User, score: u32) -> Self { - Self { position, user, score } - } - - pub fn position(mut self, val: u32) -> Self { - self.position = val; - self - } - - pub fn user(mut self, val: User) -> Self { - self.user = val; - self - } - - pub fn score(mut self, val: u32) -> Self { - self.score = val; - self - } -} diff --git a/src/types/inline_keyboard_button.rs b/src/types/inline_keyboard_button.rs deleted file mode 100644 index 12497463..00000000 --- a/src/types/inline_keyboard_button.rs +++ /dev/null @@ -1,135 +0,0 @@ -use crate::types::{CallbackGame, LoginUrl}; -use serde::{Deserialize, Serialize}; - -/// This object represents one button of an inline keyboard. -/// -/// [The official docs](https://core.telegram.org/bots/api#inlinekeyboardbutton). -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InlineKeyboardButton { - /// Label text on the button. - pub text: String, - - #[serde(flatten)] - pub kind: InlineKeyboardButtonKind, -} - -impl InlineKeyboardButton { - pub fn new(text: S, kind: InlineKeyboardButtonKind) -> Self - where - S: Into, - { - Self { text: text.into(), kind } - } - - pub fn text(mut self, val: S) -> Self - where - S: Into, - { - self.text = val.into(); - self - } - - pub fn kind(mut self, val: InlineKeyboardButtonKind) -> Self { - self.kind = val; - self - } -} - -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum InlineKeyboardButtonKind { - /// HTTP or tg:// url to be opened when button is pressed. - Url(String), - - /// An HTTP URL used to automatically authorize the user. Can be used as a - /// replacement for the [Telegram Login Widget](). - /// - /// [Telegram Login Widget]: https://core.telegram.org/widgets/login - LoginUrl(LoginUrl), - - /// Data to be sent in a [`CallbackQuery`] to the bot when button is - /// pressed, 1-64 bytes. - /// - /// [`CallbackQuery`]: crate::types::CallbackQuery - CallbackData(String), - - /// If set, pressing the button will prompt the user to select one of their - /// chats, open that chat and insert the bot‘s username and the specified - /// inline query in the input field. Can be empty, in which case just the - /// bot’s username will be inserted. - /// - /// Note: This offers an easy way for users to start using your bot in - /// [inline mode] when they are currently in a private chat with it. - /// Especially useful when combined with [switch_pm…] actions – in this - /// case the user will be automatically returned to the chat they - /// switched from, skipping the chat selection screen. - /// - /// [inline mode]: https://core.telegram.org/bots/inline - /// [switch_pm…]: https://core.telegram.org/bots/api#answerinlinequery - SwitchInlineQuery(String), - - /// If set, pressing the button will insert the bot‘s username and the - /// specified inline query in the current chat's input field. - /// Can be empty, in which case only the bot’s username will be - /// inserted. - /// - ///This offers a quick way for the user to open your bot in inline mode in - /// the same chat – good for selecting something from multiple options. - SwitchInlineQueryCurrentChat(String), - - /// Description of the game that will be launched when the user presses the - /// button. - /// - /// ## Note - /// This type of button **must** always be the first button in the first - /// row. - CallbackGame(CallbackGame), - - /// Specify True, to send a [Pay button]. - /// - /// ## Note - /// This type of button **must** always be the first button in the first - /// row. - /// - /// [Pay button]: https://core.telegram.org/bots/api#payments - Pay(bool), -} - -/// Build buttons. -/// -/// # Examples -/// ``` -/// use teloxide::types::InlineKeyboardButton; -/// -/// let url_button = InlineKeyboardButton::url("Text".to_string(), "http://url.com".to_string()); -/// ``` -impl InlineKeyboardButton { - pub fn url(text: String, url: String) -> InlineKeyboardButton { - InlineKeyboardButton { text, kind: InlineKeyboardButtonKind::Url(url) } - } - - pub fn callback(text: String, callback_data: String) -> InlineKeyboardButton { - InlineKeyboardButton { text, kind: InlineKeyboardButtonKind::CallbackData(callback_data) } - } - - pub fn switch_inline_query(text: String, switch_inline_query: String) -> InlineKeyboardButton { - InlineKeyboardButton { - text, - kind: InlineKeyboardButtonKind::SwitchInlineQuery(switch_inline_query), - } - } - - pub fn switch_inline_query_current_chat( - text: String, - switch_inline_query_current_chat: String, - ) -> InlineKeyboardButton { - InlineKeyboardButton { - text, - kind: InlineKeyboardButtonKind::SwitchInlineQueryCurrentChat( - switch_inline_query_current_chat, - ), - } - } -} diff --git a/src/types/inline_keyboard_markup.rs b/src/types/inline_keyboard_markup.rs deleted file mode 100644 index b6884a96..00000000 --- a/src/types/inline_keyboard_markup.rs +++ /dev/null @@ -1,109 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::InlineKeyboardButton; - -/// This object represents an [inline keyboard] that appears right next to the -/// message it belongs to. -/// -/// *Note*: This will only work in Telegram versions released after 9 April, -/// 2016. Older clients will display unsupported message. -/// -/// [The official docs](https://core.telegram.org/bots/api#inlinekeyboardmarkup). -/// -/// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize, Default)] -#[non_exhaustive] -pub struct InlineKeyboardMarkup { - /// Array of button rows, each represented by an array of - /// [`InlineKeyboardButton`] objects. - /// - /// [`InlineKeyboardButton`]: crate::types::InlineKeyboardButton - pub inline_keyboard: Vec>, -} - -/// Build `InlineKeyboardMarkup`. -/// -/// # Examples -/// ``` -/// use teloxide::types::{InlineKeyboardButton, InlineKeyboardMarkup}; -/// -/// let url_button = InlineKeyboardButton::url("text".to_string(), "http://url.com".to_string()); -/// let keyboard = InlineKeyboardMarkup::default().append_row(vec![url_button]); -/// ``` -impl InlineKeyboardMarkup { - pub fn new(inline_keyboard: I1) -> Self - where - I1: Into>, - I2: Into>, - { - Self { inline_keyboard: inline_keyboard.into().into_iter().map(Into::into).collect() } - } - - pub fn inline_keyboard(mut self, val: I1) -> Self - where - I1: Into>, - I2: Into>, - { - self.inline_keyboard = val.into().into_iter().map(Into::into).collect(); - self - } - - pub fn append_row(mut self, buttons: Vec) -> Self { - self.inline_keyboard.push(buttons); - self - } - - pub fn append_to_row(mut self, button: InlineKeyboardButton, index: usize) -> Self { - match self.inline_keyboard.get_mut(index) { - Some(buttons) => buttons.push(button), - None => self.inline_keyboard.push(vec![button]), - }; - self - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn append_row() { - let button1 = InlineKeyboardButton::url("text 1".to_string(), "url 1".to_string()); - let button2 = InlineKeyboardButton::url("text 2".to_string(), "url 2".to_string()); - - let markup = - InlineKeyboardMarkup::default().append_row(vec![button1.clone(), button2.clone()]); - - let expected = InlineKeyboardMarkup { inline_keyboard: vec![vec![button1, button2]] }; - - assert_eq!(markup, expected); - } - - #[test] - fn append_to_row_existent_row() { - let button1 = InlineKeyboardButton::url("text 1".to_string(), "url 1".to_string()); - let button2 = InlineKeyboardButton::url("text 2".to_string(), "url 2".to_string()); - - let markup = InlineKeyboardMarkup::default() - .append_row(vec![button1.clone()]) - .append_to_row(button2.clone(), 0); - - let expected = InlineKeyboardMarkup { inline_keyboard: vec![vec![button1, button2]] }; - - assert_eq!(markup, expected); - } - - #[test] - fn append_to_row_nonexistent_row() { - let button1 = InlineKeyboardButton::url("text 1".to_string(), "url 1".to_string()); - let button2 = InlineKeyboardButton::url("text 2".to_string(), "url 2".to_string()); - - let markup = InlineKeyboardMarkup::default() - .append_row(vec![button1.clone()]) - .append_to_row(button2.clone(), 1); - - let expected = InlineKeyboardMarkup { inline_keyboard: vec![vec![button1], vec![button2]] }; - - assert_eq!(markup, expected); - } -} diff --git a/src/types/inline_query.rs b/src/types/inline_query.rs deleted file mode 100644 index a1c15fc8..00000000 --- a/src/types/inline_query.rs +++ /dev/null @@ -1,74 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{Location, User}; - -/// This object represents an incoming inline query. -/// -/// When the user sends an empty query, your bot could return some default or -/// trending results. -/// -/// [The official docs](https://core.telegram.org/bots/api#inlinequery). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InlineQuery { - /// Unique identifier for this query. - pub id: String, - - /// Sender. - pub from: User, - - /// Sender location, only for bots that request user location. - pub location: Option, - - /// Text of the query (up to 512 characters). - pub query: String, - - /// Offset of the results to be returned, can be controlled by the bot. - pub offset: String, -} - -impl InlineQuery { - pub fn new(id: S1, from: User, query: S2, offset: S3) -> Self - where - S1: Into, - S2: Into, - S3: Into, - { - Self { id: id.into(), from, location: None, query: query.into(), offset: offset.into() } - } - - pub fn id(mut self, val: S) -> Self - where - S: Into, - { - self.id = val.into(); - self - } - - pub fn from(mut self, val: User) -> Self { - self.from = val; - self - } - - pub fn location(mut self, val: Location) -> Self { - self.location = Some(val); - self - } - - pub fn query(mut self, val: S) -> Self - where - S: Into, - { - self.query = val.into(); - self - } - - pub fn offset(mut self, val: S) -> Self - where - S: Into, - { - self.offset = val.into(); - self - } -} diff --git a/src/types/inline_query_result.rs b/src/types/inline_query_result.rs deleted file mode 100644 index cb9976de..00000000 --- a/src/types/inline_query_result.rs +++ /dev/null @@ -1,102 +0,0 @@ -#![allow(clippy::large_enum_variant)] - -use derive_more::From; -use serde::{Deserialize, Serialize}; - -use crate::types::{ - InlineQueryResultArticle, InlineQueryResultAudio, InlineQueryResultCachedAudio, - InlineQueryResultCachedDocument, InlineQueryResultCachedGif, InlineQueryResultCachedMpeg4Gif, - InlineQueryResultCachedPhoto, InlineQueryResultCachedSticker, InlineQueryResultCachedVideo, - InlineQueryResultCachedVoice, InlineQueryResultContact, InlineQueryResultDocument, - InlineQueryResultGame, InlineQueryResultGif, InlineQueryResultLocation, - InlineQueryResultMpeg4Gif, InlineQueryResultPhoto, InlineQueryResultVenue, - InlineQueryResultVideo, InlineQueryResultVoice, -}; - -/// This object represents one result of an inline query. -/// -/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresult). -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, From)] -#[serde(tag = "type")] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum InlineQueryResult { - #[serde(rename = "audio")] - CachedAudio(InlineQueryResultCachedAudio), - #[serde(rename = "document")] - CachedDocument(InlineQueryResultCachedDocument), - #[serde(rename = "gif")] - CachedGif(InlineQueryResultCachedGif), - #[serde(rename = "mpeg4_gif")] - CachedMpeg4Gif(InlineQueryResultCachedMpeg4Gif), - #[serde(rename = "photo")] - CachedPhoto(InlineQueryResultCachedPhoto), - #[serde(rename = "sticker")] - CachedSticker(InlineQueryResultCachedSticker), - #[serde(rename = "video")] - CachedVideo(InlineQueryResultCachedVideo), - #[serde(rename = "voice")] - CachedVoice(InlineQueryResultCachedVoice), - - Article(InlineQueryResultArticle), - Audio(InlineQueryResultAudio), - Contact(InlineQueryResultContact), - Game(InlineQueryResultGame), - Document(InlineQueryResultDocument), - Gif(InlineQueryResultGif), - Location(InlineQueryResultLocation), - #[serde(rename = "mpeg4_gif")] - Mpeg4Gif(InlineQueryResultMpeg4Gif), - Photo(InlineQueryResultPhoto), - Venue(InlineQueryResultVenue), - Video(InlineQueryResultVideo), - Voice(InlineQueryResultVoice), -} - -#[cfg(test)] -mod tests { - use crate::types::{ - inline_keyboard_markup::InlineKeyboardMarkup, parse_mode::ParseMode, InlineQueryResult, - InlineQueryResultCachedAudio, InputMessageContent, InputMessageContentText, - }; - - #[test] - fn cached_audio_min_serialize() { - let structure = InlineQueryResult::CachedAudio(InlineQueryResultCachedAudio { - id: String::from("id"), - audio_file_id: String::from("audio_file_id"), - caption: None, - parse_mode: None, - reply_markup: None, - input_message_content: None, - }); - - let expected_json = r#"{"type":"audio","id":"id","audio_file_id":"audio_file_id"}"#; - let actual_json = serde_json::to_string(&structure).unwrap(); - - assert_eq!(expected_json, actual_json); - } - - #[test] - fn cached_audio_full_serialize() { - let structure = InlineQueryResult::CachedAudio(InlineQueryResultCachedAudio { - id: String::from("id"), - audio_file_id: String::from("audio_file_id"), - caption: Some(String::from("caption")), - parse_mode: Some(ParseMode::HTML), - reply_markup: Some(InlineKeyboardMarkup::default()), - input_message_content: Some(InputMessageContent::Text(InputMessageContentText { - message_text: String::from("message_text"), - parse_mode: Some(ParseMode::MarkdownV2), - disable_web_page_preview: Some(true), - })), - }); - - let expected_json = r#"{"type":"audio","id":"id","audio_file_id":"audio_file_id","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2","disable_web_page_preview":true}}"#; - let actual_json = serde_json::to_string(&structure).unwrap(); - - assert_eq!(expected_json, actual_json); - } - - // TODO: Add more tests -} diff --git a/src/types/inline_query_result_article.rs b/src/types/inline_query_result_article.rs deleted file mode 100644 index 2f9bd689..00000000 --- a/src/types/inline_query_result_article.rs +++ /dev/null @@ -1,128 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{InlineKeyboardMarkup, InputMessageContent}; - -/// Represents a link to an article or web page. -/// -/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultarticle). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InlineQueryResultArticle { - /// Unique identifier for this result, 1-64 Bytes. - pub id: String, - - /// Title of the result. - pub title: String, - - /// Content of the message to be sent. - pub input_message_content: InputMessageContent, - - /// Inline keyboard attached to the message. - pub reply_markup: Option, - - /// URL of the result. - pub url: Option, - - /// Pass `true`, if you don't want the URL to be shown in the - /// message. - pub hide_url: Option, - - /// Short description of the result. - pub description: Option, - - /// Url of the thumbnail for the result. - pub thumb_url: Option, - - /// Thumbnail width. - pub thumb_width: Option, - - /// Thumbnail height. - pub thumb_height: Option, -} - -impl InlineQueryResultArticle { - pub fn new(id: S1, title: S2, input_message_content: InputMessageContent) -> Self - where - S1: Into, - S2: Into, - { - Self { - id: id.into(), - title: title.into(), - input_message_content, - reply_markup: None, - url: None, - hide_url: None, - description: None, - thumb_url: None, - thumb_width: None, - thumb_height: None, - } - } - - pub fn id(mut self, val: S) -> Self - where - S: Into, - { - self.id = val.into(); - self - } - - pub fn title(mut self, val: S) -> Self - where - S: Into, - { - self.title = val.into(); - self - } - - pub fn input_message_content(mut self, val: InputMessageContent) -> Self { - self.input_message_content = val; - self - } - - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } - - pub fn url(mut self, val: S) -> Self - where - S: Into, - { - self.url = Some(val.into()); - self - } - - pub fn hide_url(mut self, val: bool) -> Self { - self.hide_url = Some(val); - self - } - - pub fn description(mut self, val: S) -> Self - where - S: Into, - { - self.description = Some(val.into()); - self - } - - pub fn thumb_url(mut self, val: S) -> Self - where - S: Into, - { - self.thumb_url = Some(val.into()); - self - } - - pub fn thumb_width(mut self, val: i32) -> Self { - self.thumb_width = Some(val); - self - } - - pub fn thumb_height(mut self, val: i32) -> Self { - self.thumb_height = Some(val); - self - } -} diff --git a/src/types/inline_query_result_audio.rs b/src/types/inline_query_result_audio.rs deleted file mode 100644 index 648c1bee..00000000 --- a/src/types/inline_query_result_audio.rs +++ /dev/null @@ -1,133 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; - -/// Represents a link to an MP3 audio file. By default, this audio file will be -/// sent by the user. -/// -/// Alternatively, you can use `input_message_content` to send a message with -/// the specified content instead of the audio. -/// -/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultaudio). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InlineQueryResultAudio { - /// Unique identifier for this result, 1-64 bytes. - pub id: String, - - /// A valid URL for the audio file. - pub audio_url: String, - - /// Title. - pub title: String, - - /// Caption, 0-1024 characters. - pub caption: Option, - - /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, - /// italic, fixed-width text or inline URLs] in the media caption. - /// - /// [Markdown]: https://core.telegram.org/bots/api#markdown-style - /// [HTML]: https://core.telegram.org/bots/api#html-style - /// [bold, italic, fixed-width text or inline URLs]: https://core.telegram.org/bots/api#formatting-options - pub parse_mode: Option, - - /// Performer. - pub performer: Option, - - /// Audio duration in seconds. - pub audio_duration: Option, - - /// [Inline keyboard] attached to the message. - /// - /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub reply_markup: Option, - - /// Content of the message to be sent instead of the audio. - pub input_message_content: Option, -} - -impl InlineQueryResultAudio { - pub fn new(id: S1, audio_url: S2, title: S3) -> Self - where - S1: Into, - S2: Into, - S3: Into, - { - Self { - id: id.into(), - audio_url: audio_url.into(), - title: title.into(), - caption: None, - parse_mode: None, - performer: None, - audio_duration: None, - reply_markup: None, - input_message_content: None, - } - } - - pub fn id(mut self, val: S) -> Self - where - S: Into, - { - self.id = val.into(); - self - } - - pub fn audio_url(mut self, val: S) -> Self - where - S: Into, - { - self.audio_url = val.into(); - self - } - - pub fn title(mut self, val: S) -> Self - where - S: Into, - { - self.title = val.into(); - self - } - - pub fn caption(mut self, val: S) -> Self - where - S: Into, - { - self.caption = Some(val.into()); - self - } - - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - pub fn performer(mut self, val: S) -> Self - where - S: Into, - { - self.performer = Some(val.into()); - self - } - - pub fn audio_duration(mut self, val: S) -> Self - where - S: Into, - { - self.audio_duration = Some(val.into()); - self - } - - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } - - pub fn input_message_content(mut self, val: InputMessageContent) -> Self { - self.input_message_content = Some(val); - self - } -} diff --git a/src/types/inline_query_result_cached_audio.rs b/src/types/inline_query_result_cached_audio.rs deleted file mode 100644 index 5efec634..00000000 --- a/src/types/inline_query_result_cached_audio.rs +++ /dev/null @@ -1,96 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; - -/// Represents a link to an MP3 audio file stored on the Telegram servers. -/// -/// By default, this audio file will be sent by the user. Alternatively, you can -/// use `input_message_content` to send a message with the specified content -/// instead of the audio. -/// -/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultcachedaudio). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InlineQueryResultCachedAudio { - /// Unique identifier for this result, 1-64 bytes. - pub id: String, - - /// A valid file identifier for the audio file. - pub audio_file_id: String, - - /// Caption, 0-1024 characters. - pub caption: Option, - - /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, - /// italic, fixed-width text or inline URLs] in the media caption. - /// - /// [Markdown]: https://core.telegram.org/bots/api#markdown-style - /// [HTML]: https://core.telegram.org/bots/api#html-style - /// [bold, italic, fixed-width text or inline URLs]: https://core.telegram.org/bots/api#formatting-options - pub parse_mode: Option, - - /// [Inline keyboard] attached to the message. - /// - /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub reply_markup: Option, - - /// Content of the message to be sent instead of the audio. - pub input_message_content: Option, -} - -impl InlineQueryResultCachedAudio { - pub fn new(id: S1, audio_file_id: S2) -> Self - where - S1: Into, - S2: Into, - { - Self { - id: id.into(), - audio_file_id: audio_file_id.into(), - caption: None, - parse_mode: None, - reply_markup: None, - input_message_content: None, - } - } - - pub fn id(mut self, val: S) -> Self - where - S: Into, - { - self.id = val.into(); - self - } - - pub fn audio_file_id(mut self, val: S) -> Self - where - S: Into, - { - self.audio_file_id = val.into(); - self - } - - pub fn caption(mut self, val: S) -> Self - where - S: Into, - { - self.caption = Some(val.into()); - self - } - - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } - - pub fn input_message_content(mut self, val: InputMessageContent) -> Self { - self.input_message_content = Some(val); - self - } -} diff --git a/src/types/inline_query_result_cached_document.rs b/src/types/inline_query_result_cached_document.rs deleted file mode 100644 index 882c5858..00000000 --- a/src/types/inline_query_result_cached_document.rs +++ /dev/null @@ -1,121 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; - -/// Represents a link to a file stored on the Telegram servers. -/// -/// By default, this file will be sent by the user with an optional caption. -/// Alternatively, you can use `input_message_content` to send a message with -/// the specified content instead of the file. -/// -/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultcacheddocument). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InlineQueryResultCachedDocument { - /// Unique identifier for this result, 1-64 bytes. - pub id: String, - - /// Title for the result. - pub title: String, - - /// A valid file identifier for the file. - pub document_file_id: String, - - /// Short description of the result. - pub description: Option, - - /// Caption of the document to be sent, 0-1024 characters. - pub caption: Option, - - /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, - /// italic, fixed-width text or inline URLs] in the media caption. - /// - /// [Markdown]: https://core.telegram.org/bots/api#markdown-style - /// [HTML]: https://core.telegram.org/bots/api#html-style - /// [bold, italic, fixed-width text or inline URLs]: https://core.telegram.org/bots/api#formatting-options - pub parse_mode: Option, - - /// [Inline keyboard] attached to the message. - /// - /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub reply_markup: Option, - - /// Content of the message to be sent instead of the file. - pub input_message_content: Option, -} - -impl InlineQueryResultCachedDocument { - pub fn new(id: S1, title: S2, document_file_id: S3) -> Self - where - S1: Into, - S2: Into, - S3: Into, - { - Self { - id: id.into(), - title: title.into(), - document_file_id: document_file_id.into(), - description: None, - caption: None, - parse_mode: None, - reply_markup: None, - input_message_content: None, - } - } - - pub fn id(mut self, val: S) -> Self - where - S: Into, - { - self.id = val.into(); - self - } - - pub fn title(mut self, val: S) -> Self - where - S: Into, - { - self.title = val.into(); - self - } - - pub fn document_file_id(mut self, val: S) -> Self - where - S: Into, - { - self.document_file_id = val.into(); - self - } - - pub fn description(mut self, val: S) -> Self - where - S: Into, - { - self.description = Some(val.into()); - self - } - - pub fn caption(mut self, val: S) -> Self - where - S: Into, - { - self.caption = Some(val.into()); - self - } - - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } - - pub fn input_message_content(mut self, val: InputMessageContent) -> Self { - self.input_message_content = Some(val); - self - } -} diff --git a/src/types/inline_query_result_cached_gif.rs b/src/types/inline_query_result_cached_gif.rs deleted file mode 100644 index 79fb44db..00000000 --- a/src/types/inline_query_result_cached_gif.rs +++ /dev/null @@ -1,109 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; - -/// Represents a link to an animated GIF file stored on the Telegram servers. -/// -/// By default, this animated GIF file will be sent by the user with an optional -/// caption. Alternatively, you can use `input_message_content` to send a -/// message with specified content instead of the animation. -/// -/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultcachedgif). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InlineQueryResultCachedGif { - /// Unique identifier for this result, 1-64 bytes. - pub id: String, - - /// A valid file identifier for the GIF file. - pub gif_file_id: String, - - /// Title for the result. - pub title: Option, - - /// Caption of the GIF file to be sent, 0-1024 characters. - pub caption: Option, - - /// Send [`ParseMode::Markdown`] or [`ParseMode::HTML`], if you want - /// Telegram apps to show [bold, italic, fixed-width text or inline - /// URLs] in the media caption. - /// - /// [`ParseMode::Markdown`]: crate::types::ParseMode::Markdown - /// [`ParseMode::HTML`]: crate::types::ParseMode::HTML - /// [bold, italic, fixed-width text or inline URLs]: https://core.telegram.org/bots/api#formatting-options - pub parse_mode: Option, - - /// [Inline keyboard] attached to the message. - /// - /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub reply_markup: Option, - - /// Content of the message to be sent instead of the GIF animation. - pub input_message_content: Option, -} - -impl InlineQueryResultCachedGif { - pub fn new(id: S1, gif_file_id: S2) -> Self - where - S1: Into, - S2: Into, - { - Self { - id: id.into(), - gif_file_id: gif_file_id.into(), - title: None, - caption: None, - parse_mode: None, - reply_markup: None, - input_message_content: None, - } - } - - pub fn id(mut self, val: S) -> Self - where - S: Into, - { - self.id = val.into(); - self - } - - pub fn gif_file_id(mut self, val: S) -> Self - where - S: Into, - { - self.gif_file_id = val.into(); - self - } - - pub fn title(mut self, val: S) -> Self - where - S: Into, - { - self.title = Some(val.into()); - self - } - - pub fn caption(mut self, val: S) -> Self - where - S: Into, - { - self.caption = Some(val.into()); - self - } - - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } - - pub fn input_message_content(mut self, val: InputMessageContent) -> Self { - self.input_message_content = Some(val); - self - } -} diff --git a/src/types/inline_query_result_cached_mpeg4_gif.rs b/src/types/inline_query_result_cached_mpeg4_gif.rs deleted file mode 100644 index 8f97667d..00000000 --- a/src/types/inline_query_result_cached_mpeg4_gif.rs +++ /dev/null @@ -1,101 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; - -/// Represents a link to a video animation (H.264/MPEG-4 AVC video without -/// sound) stored on the Telegram servers. -/// -/// By default, this animated MPEG-4 file will be sent by the user with an -/// optional caption. Alternatively, you can use `input_message_content` to send -/// a message with the specified content instead of the animation. -/// -/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultcachedmpeg4gif). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InlineQueryResultCachedMpeg4Gif { - /// Unique identifier for this result, 1-64 bytes. - pub id: String, - - /// A valid file identifier for the MP4 file. - pub mpeg4_file_id: String, - - /// Title for the result. - pub title: Option, - - /// Caption of the MPEG-4 file to be sent, 0-1024 characters. - pub caption: Option, - - /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, - /// italic, fixed-width text or inline URLs] in the media caption. - /// - /// [Markdown]: https://core.telegram.org/bots/api#markdown-style - /// [HTML]: https://core.telegram.org/bots/api#html-style - /// [bold, italic, fixed-width text or inline URLs]: https://core.telegram.org/bots/api#formatting-options - pub parse_mode: Option, - - /// [Inline keyboard] attached to the message. - /// - /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub reply_markup: Option, - - /// Content of the message to be sent instead of the video animation. - pub input_message_content: Option, -} - -impl InlineQueryResultCachedMpeg4Gif { - pub fn new(id: S1, mpeg4_file_id: S2) -> Self - where - S1: Into, - S2: Into, - { - Self { - id: id.into(), - mpeg4_file_id: mpeg4_file_id.into(), - title: None, - caption: None, - parse_mode: None, - reply_markup: None, - input_message_content: None, - } - } - - pub fn id(mut self, val: S) -> Self - where - S: Into, - { - self.id = val.into(); - self - } - - pub fn title(mut self, val: S) -> Self - where - S: Into, - { - self.title = Some(val.into()); - self - } - - pub fn caption(mut self, val: S) -> Self - where - S: Into, - { - self.caption = Some(val.into()); - self - } - - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } - - pub fn input_message_content(mut self, val: InputMessageContent) -> Self { - self.input_message_content = Some(val); - self - } -} diff --git a/src/types/inline_query_result_cached_photo.rs b/src/types/inline_query_result_cached_photo.rs deleted file mode 100644 index ac9d28e8..00000000 --- a/src/types/inline_query_result_cached_photo.rs +++ /dev/null @@ -1,120 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; - -/// Represents a link to a photo stored on the Telegram servers. -/// -/// By default, this photo will be sent by the user with an optional caption. -/// Alternatively, you can use `input_message_content` to send a message with -/// the specified content instead of the photo. -/// -/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultcachedphoto). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InlineQueryResultCachedPhoto { - /// Unique identifier for this result, 1-64 bytes. - pub id: String, - - /// A valid file identifier of the photo. - pub photo_file_id: String, - - /// Title for the result. - pub title: Option, - - /// Short description of the result. - pub description: Option, - - /// Caption of the photo to be sent, 0-1024 characters. - pub caption: Option, - - /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, - /// italic, fixed-width text or inline URLs] in the media caption. - /// - /// [Markdown]: https://core.telegram.org/bots/api#markdown-style - /// [HTML]: https://core.telegram.org/bots/api#html-style - /// [bold, italic, fixed-width text or inline URLs]: https://core.telegram.org/bots/api#formatting-options - pub parse_mode: Option, - - /// [Inline keyboard] attached to the message. - /// - /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub reply_markup: Option, - - /// Content of the message to be sent instead of the photo. - pub input_message_content: Option, -} - -impl InlineQueryResultCachedPhoto { - pub fn new(id: S1, photo_file_id: S2) -> Self - where - S1: Into, - S2: Into, - { - Self { - id: id.into(), - photo_file_id: photo_file_id.into(), - title: None, - description: None, - caption: None, - parse_mode: None, - reply_markup: None, - input_message_content: None, - } - } - - pub fn id(mut self, val: S) -> Self - where - S: Into, - { - self.id = val.into(); - self - } - - pub fn photo_file_id(mut self, val: S) -> Self - where - S: Into, - { - self.photo_file_id = val.into(); - self - } - - pub fn title(mut self, val: S) -> Self - where - S: Into, - { - self.title = Some(val.into()); - self - } - - pub fn description(mut self, val: S) -> Self - where - S: Into, - { - self.description = Some(val.into()); - self - } - - pub fn caption(mut self, val: S) -> Self - where - S: Into, - { - self.caption = Some(val.into()); - self - } - - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } - - pub fn input_message_content(mut self, val: InputMessageContent) -> Self { - self.input_message_content = Some(val); - self - } -} diff --git a/src/types/inline_query_result_cached_sticker.rs b/src/types/inline_query_result_cached_sticker.rs deleted file mode 100644 index 0c4e9b88..00000000 --- a/src/types/inline_query_result_cached_sticker.rs +++ /dev/null @@ -1,70 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{InlineKeyboardMarkup, InputMessageContent}; - -/// Represents a link to a sticker stored on the Telegram servers. -/// -/// By default, this sticker will be sent by the user. Alternatively, you can -/// use `input_message_content` to send a message with the specified content -/// instead of the sticker. -/// -/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultcachedsticker). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InlineQueryResultCachedSticker { - /// Unique identifier for this result, 1-64 bytes. - pub id: String, - - /// A valid file identifier of the sticker. - pub sticker_file_id: String, - - /// [Inline keyboard] attached to the message. - /// - /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub reply_markup: Option, - - /// Content of the message to be sent instead of the sticker. - pub input_message_content: Option, -} - -impl InlineQueryResultCachedSticker { - pub fn new(id: S1, sticker_file_id: S2) -> Self - where - S1: Into, - S2: Into, - { - Self { - id: id.into(), - sticker_file_id: sticker_file_id.into(), - reply_markup: None, - input_message_content: None, - } - } - - pub fn id(mut self, val: S) -> Self - where - S: Into, - { - self.id = val.into(); - self - } - - pub fn sticker_file_id(mut self, val: S) -> Self - where - S: Into, - { - self.sticker_file_id = val.into(); - self - } - - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } - - pub fn input_message_content(mut self, val: InputMessageContent) -> Self { - self.input_message_content = Some(val); - self - } -} diff --git a/src/types/inline_query_result_cached_video.rs b/src/types/inline_query_result_cached_video.rs deleted file mode 100644 index 645892b0..00000000 --- a/src/types/inline_query_result_cached_video.rs +++ /dev/null @@ -1,121 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; - -/// Represents a link to a video file stored on the Telegram servers. -/// -/// By default, this video file will be sent by the user with an optional -/// caption. Alternatively, you can use `input_message_content` to send a -/// message with the specified content instead of the video. -/// -/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultcachedvideo). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InlineQueryResultCachedVideo { - /// Unique identifier for this result, 1-64 bytes. - pub id: String, - - /// A valid file identifier for the video file. - pub video_file_id: String, - - /// Title for each result. - pub title: String, - - /// Short description of the result. - pub description: Option, - - /// Caption of the video to be sent, 0-1024 characters. - pub caption: Option, - - /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, - /// italic, fixed-width text or inline URLs] in the media caption. - /// - /// [Markdown]: https://core.telegram.org/bots/api#markdown-style - /// [HTML]: https://core.telegram.org/bots/api#html-style - /// [bold, italic, fixed-width text or inline URLs]: https://core.telegram.org/bots/api#formatting-options - pub parse_mode: Option, - - /// [Inline keyboard] attached to the message. - /// - /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub reply_markup: Option, - - /// Content of the message to be sent instead of the video. - pub input_message_content: Option, -} - -impl InlineQueryResultCachedVideo { - pub fn new(id: S1, video_file_id: S2, title: S3) -> Self - where - S1: Into, - S2: Into, - S3: Into, - { - Self { - id: id.into(), - video_file_id: video_file_id.into(), - title: title.into(), - description: None, - caption: None, - parse_mode: None, - reply_markup: None, - input_message_content: None, - } - } - - pub fn id(mut self, val: S) -> Self - where - S: Into, - { - self.id = val.into(); - self - } - - pub fn video_file_id(mut self, val: S) -> Self - where - S: Into, - { - self.video_file_id = val.into(); - self - } - - pub fn title(mut self, val: S) -> Self - where - S: Into, - { - self.title = val.into(); - self - } - - pub fn description(mut self, val: S) -> Self - where - S: Into, - { - self.description = Some(val.into()); - self - } - - pub fn caption(mut self, val: S) -> Self - where - S: Into, - { - self.caption = Some(val.into()); - self - } - - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } - - pub fn input_message_content(mut self, val: InputMessageContent) -> Self { - self.input_message_content = Some(val); - self - } -} diff --git a/src/types/inline_query_result_cached_voice.rs b/src/types/inline_query_result_cached_voice.rs deleted file mode 100644 index 1f50672c..00000000 --- a/src/types/inline_query_result_cached_voice.rs +++ /dev/null @@ -1,109 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; - -/// Represents a link to a voice message stored on the Telegram servers. -/// -/// By default, this voice message will be sent by the user. Alternatively, you -/// can use `input_message_content` to send a message with the specified content -/// instead of the voice message. -/// -/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultcachedvideo). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InlineQueryResultCachedVoice { - /// Unique identifier for this result, 1-64 bytes. - pub id: String, - - /// A valid file identifier for the voice message. - pub voice_file_id: String, - - /// Voice message title. - pub title: String, - - /// Caption, 0-1024 characters. - pub caption: Option, - - /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, - /// italic, fixed-width text or inline URLs] in the media caption. - /// - /// [Markdown]: https://core.telegram.org/bots/api#markdown-style - /// [HTML]: https://core.telegram.org/bots/api#html-style - /// [bold, italic, fixed-width text or inline URLs]: https://core.telegram.org/bots/api#formatting-options - pub parse_mode: Option, - - /// [Inline keyboard] attached to the message. - /// - /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub reply_markup: Option, - - /// Content of the message to be sent instead of the voice message. - pub input_message_content: Option, -} - -impl InlineQueryResultCachedVoice { - pub fn new(id: S1, voice_file_id: S2, title: S3) -> Self - where - S1: Into, - S2: Into, - S3: Into, - { - Self { - id: id.into(), - voice_file_id: voice_file_id.into(), - title: title.into(), - caption: None, - parse_mode: None, - reply_markup: None, - input_message_content: None, - } - } - - pub fn id(mut self, val: S) -> Self - where - S: Into, - { - self.id = val.into(); - self - } - - pub fn voice_file_id(mut self, val: S) -> Self - where - S: Into, - { - self.voice_file_id = val.into(); - self - } - - pub fn title(mut self, val: S) -> Self - where - S: Into, - { - self.title = val.into(); - self - } - - pub fn caption(mut self, val: S) -> Self - where - S: Into, - { - self.caption = Some(val.into()); - self - } - - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } - - pub fn input_message_content(mut self, val: InputMessageContent) -> Self { - self.input_message_content = Some(val); - self - } -} diff --git a/src/types/inline_query_result_contact.rs b/src/types/inline_query_result_contact.rs deleted file mode 100644 index 0b4ae0d8..00000000 --- a/src/types/inline_query_result_contact.rs +++ /dev/null @@ -1,140 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{InlineKeyboardMarkup, InputMessageContent}; - -/// Represents a contact with a phone number. -/// -/// By default, this contact will be sent by the user. Alternatively, you can -/// use `input_message_content` to send a message with the specified content -/// instead of the contact. -/// -/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultcachedvideo). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InlineQueryResultContact { - /// Unique identifier for this result, 1-64 Bytes. - pub id: String, - - /// Contact's phone number. - pub phone_number: String, - - /// Contact's first name. - pub first_name: String, - - /// Contact's last name. - pub last_name: Option, - - /// Additional data about the contact in the form of a [vCard], 0-2048 - /// bytes. - /// - /// [VCard]: https://en.wikipedia.org/wiki/VCard - pub vcard: Option, - - /// [Inline keyboard] attached to the message. - /// - /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub reply_markup: Option, - - /// Content of the message to be sent instead of the contact. - pub input_message_content: Option, - - /// Url of the thumbnail for the result. - pub thumb_url: Option, - - /// Thumbnail width. - pub thumb_width: Option, - - /// Thumbnail height. - pub thumb_height: Option, -} - -impl InlineQueryResultContact { - pub fn new(id: S1, phone_number: S2, first_name: S3) -> Self - where - S1: Into, - S2: Into, - S3: Into, - { - Self { - id: id.into(), - phone_number: phone_number.into(), - first_name: first_name.into(), - last_name: None, - vcard: None, - reply_markup: None, - input_message_content: None, - thumb_url: None, - thumb_width: None, - thumb_height: None, - } - } - - pub fn id(mut self, val: S) -> Self - where - S: Into, - { - self.id = val.into(); - self - } - - pub fn phone_number(mut self, val: S) -> Self - where - S: Into, - { - self.phone_number = val.into(); - self - } - - pub fn first_name(mut self, val: S) -> Self - where - S: Into, - { - self.first_name = val.into(); - self - } - - pub fn last_name(mut self, val: S) -> Self - where - S: Into, - { - self.last_name = Some(val.into()); - self - } - - pub fn vcard(mut self, val: S) -> Self - where - S: Into, - { - self.vcard = Some(val.into()); - self - } - - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } - - pub fn input_message_content(mut self, val: InputMessageContent) -> Self { - self.input_message_content = Some(val); - self - } - - pub fn thumb_url(mut self, val: S) -> Self - where - S: Into, - { - self.thumb_url = Some(val.into()); - self - } - - pub fn thumb_width(mut self, val: i32) -> Self { - self.thumb_width = Some(val); - self - } - - pub fn thumb_height(mut self, val: i32) -> Self { - self.thumb_height = Some(val); - self - } -} diff --git a/src/types/inline_query_result_document.rs b/src/types/inline_query_result_document.rs deleted file mode 100644 index ea66f561..00000000 --- a/src/types/inline_query_result_document.rs +++ /dev/null @@ -1,138 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{InlineKeyboardMarkup, InputMessageContent, MimeWrapper, ParseMode}; - -/// Represents a link to a file. -/// -/// By default, this file will be sent by the user with an optional caption. -/// Alternatively, you can use `input_message_content` to send a message with -/// the specified content instead of the file. Currently, only **.PDF** and -/// **.ZIP** files can be sent using this method. -/// -/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultdocument). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InlineQueryResultDocument { - /// Unique identifier for this result, 1-64 bytes. - pub id: String, - - /// Title for the result. - pub title: String, - - /// Caption of the document to be sent, 0-1024 characters. - pub caption: Option, - - /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, - /// italic, fixed-width text or inline URLs] in the media caption. - /// - /// [Markdown]: https://core.telegram.org/bots/api#markdown-style - /// [HTML]: https://core.telegram.org/bots/api#html-style - /// [bold, italic, fixed-width text or inline URLs]: https://core.telegram.org/bots/api#formatting-options - pub parse_mode: Option, - - /// A valid URL for the file. - pub document_url: String, - - /// Mime type of the content of the file, either `application/pdf` or - /// `application/zip`. - pub mime_type: MimeWrapper, - - /// Short description of the result. - pub description: Option, - - /// Inline keyboard attached to the message. - pub reply_markup: Option, - - /// Content of the message to be sent instead of the file. - pub input_message_content: Option, - - /// URL of the thumbnail (jpeg only) for the file. - pub thumb_url: Option, - - /// Thumbnail width. - pub thumb_width: Option, - - /// Thumbnail height. - pub thumb_height: Option, -} - -impl InlineQueryResultDocument { - pub fn id(mut self, val: S) -> Self - where - S: Into, - { - self.id = val.into(); - self - } - - pub fn title(mut self, val: S) -> Self - where - S: Into, - { - self.title = val.into(); - self - } - - pub fn caption(mut self, val: S) -> Self - where - S: Into, - { - self.caption = Some(val.into()); - self - } - - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - pub fn document_url(mut self, val: S) -> Self - where - S: Into, - { - self.document_url = val.into(); - self - } - - pub fn mime_type(mut self, val: MimeWrapper) -> Self { - self.mime_type = val; - self - } - - pub fn description(mut self, val: S) -> Self - where - S: Into, - { - self.description = Some(val.into()); - self - } - - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } - - pub fn input_message_content(mut self, val: InputMessageContent) -> Self { - self.input_message_content = Some(val); - self - } - - pub fn thumb_url(mut self, val: S) -> Self - where - S: Into, - { - self.thumb_url = Some(val.into()); - self - } - - pub fn thumb_width(mut self, val: i32) -> Self { - self.thumb_width = Some(val); - self - } - - pub fn thumb_height(mut self, val: i32) -> Self { - self.thumb_height = Some(val); - self - } -} diff --git a/src/types/inline_query_result_game.rs b/src/types/inline_query_result_game.rs deleted file mode 100644 index c795547c..00000000 --- a/src/types/inline_query_result_game.rs +++ /dev/null @@ -1,55 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::InlineKeyboardMarkup; - -/// Represents a [game]. -/// -/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultgame). -/// -/// [game]: https://core.telegram.org/bots/api#games -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InlineQueryResultGame { - /// Unique identifier for this result, 1-64 bytes. - pub id: String, - - /// Short name of the game. - pub game_short_name: String, - - /// [Inline keyboard] attached to the message. - /// - /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub reply_markup: Option, -} - -impl InlineQueryResultGame { - pub fn new(id: S1, game_short_name: S2) -> Self - where - S1: Into, - S2: Into, - { - Self { id: id.into(), game_short_name: game_short_name.into(), reply_markup: None } - } - - pub fn id(mut self, val: S) -> Self - where - S: Into, - { - self.id = val.into(); - self - } - - pub fn game_short_name(mut self, val: S) -> Self - where - S: Into, - { - self.game_short_name = val.into(); - self - } - - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/types/inline_query_result_gif.rs b/src/types/inline_query_result_gif.rs deleted file mode 100644 index 71145de3..00000000 --- a/src/types/inline_query_result_gif.rs +++ /dev/null @@ -1,148 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; - -/// Represents a link to an animated GIF file. -/// -/// By default, this animated GIF file will be sent by the user with optional -/// caption. Alternatively, you can use `input_message_content` to send a -/// message with the specified content instead of the animation. -/// -/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultgif). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InlineQueryResultGif { - /// Unique identifier for this result, 1-64 bytes. - pub id: String, - - /// A valid URL for the GIF file. File size must not exceed 1MB. - pub gif_url: String, - - /// Width of the GIF. - pub gif_width: Option, - - /// Height of the GIFv. - pub gif_height: Option, - - /// Duration of the GIF. - pub gif_duration: Option, - - /// URL of the static thumbnail for the result (jpeg or gif). - pub thumb_url: String, - - /// Title for the result. - pub title: Option, - - /// Caption of the GIF file to be sent, 0-1024 characters. - pub caption: Option, - - /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, - /// italic, fixed-width text or inline URLs] in the media caption. - /// - /// [Markdown]: https://core.telegram.org/bots/api#markdown-style - /// [HTML]: https://core.telegram.org/bots/api#html-style - /// [bold, italic, fixed-width text or inline URLs]: https://core.telegram.org/bots/api#formatting-options - pub parse_mode: Option, - - /// [Inline keyboard] attached to the message. - /// - /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub reply_markup: Option, - - /// Content of the message to be sent instead of the GIF animation. - pub input_message_content: Option, -} - -impl InlineQueryResultGif { - pub fn new(id: S1, gif_url: S2, thumb_url: S3) -> Self - where - S1: Into, - S2: Into, - S3: Into, - { - Self { - id: id.into(), - gif_url: gif_url.into(), - gif_width: None, - gif_height: None, - gif_duration: None, - thumb_url: thumb_url.into(), - title: None, - caption: None, - parse_mode: None, - reply_markup: None, - input_message_content: None, - } - } - - pub fn id(mut self, val: S) -> Self - where - S: Into, - { - self.id = val.into(); - self - } - - pub fn gif_url(mut self, val: S) -> Self - where - S: Into, - { - self.gif_url = val.into(); - self - } - - pub fn gif_width(mut self, val: i32) -> Self { - self.gif_width = Some(val); - self - } - - pub fn gif_height(mut self, val: i32) -> Self { - self.gif_height = Some(val); - self - } - - pub fn gif_duration(mut self, val: i32) -> Self { - self.gif_duration = Some(val); - self - } - - pub fn thumb_url(mut self, val: S) -> Self - where - S: Into, - { - self.thumb_url = val.into(); - self - } - - pub fn title(mut self, val: S) -> Self - where - S: Into, - { - self.title = Some(val.into()); - self - } - - pub fn caption(mut self, val: S) -> Self - where - S: Into, - { - self.caption = Some(val.into()); - self - } - - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } - - pub fn input_message_content(mut self, val: InputMessageContent) -> Self { - self.input_message_content = Some(val); - self - } -} diff --git a/src/types/inline_query_result_location.rs b/src/types/inline_query_result_location.rs deleted file mode 100644 index 1d6d9270..00000000 --- a/src/types/inline_query_result_location.rs +++ /dev/null @@ -1,128 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{InlineKeyboardMarkup, InputMessageContent}; - -/// Represents a location on a map. -/// -/// By default, the location will be sent by the user. Alternatively, you can -/// use `input_message_content` to send a message with the specified content -/// instead of the location. -/// -/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultlocation). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InlineQueryResultLocation { - /// Unique identifier for this result, 1-64 Bytes. - pub id: String, - - /// Location latitude in degrees. - pub latitude: f64, - - /// Location longitude in degrees. - pub longitude: f64, - - /// Location title. - pub title: String, - - /// Period in seconds for which the location can be updated, should be - /// between 60 and 86400. - pub live_period: Option, - - /// [Inline keyboard] attached to the message. - /// - /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub reply_markup: Option, - - /// Content of the message to be sent instead of the location. - pub input_message_content: Option, - - /// Url of the thumbnail for the result. - pub thumb_url: Option, - - /// Thumbnail width. - pub thumb_width: Option, - - /// Thumbnail height. - pub thumb_height: Option, -} - -impl InlineQueryResultLocation { - pub fn new(id: S1, title: S2, latitude: f64, longitude: f64) -> Self - where - S1: Into, - S2: Into, - { - Self { - id: id.into(), - title: title.into(), - latitude, - longitude, - live_period: None, - reply_markup: None, - input_message_content: None, - thumb_url: None, - thumb_width: None, - thumb_height: None, - } - } - - pub fn id(mut self, val: S) -> Self - where - S: Into, - { - self.id = val.into(); - self - } - - pub fn latitude(mut self, val: f64) -> Self { - self.latitude = val; - self - } - - pub fn longitude(mut self, val: f64) -> Self { - self.longitude = val; - self - } - - pub fn title(mut self, val: S) -> Self - where - S: Into, - { - self.title = val.into(); - self - } - - pub fn live_period(mut self, val: i32) -> Self { - self.live_period = Some(val); - self - } - - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } - - pub fn input_message_content(mut self, val: InputMessageContent) -> Self { - self.input_message_content = Some(val); - self - } - - pub fn thumb_url(mut self, val: S) -> Self - where - S: Into, - { - self.thumb_url = Some(val.into()); - self - } - - pub fn thumb_width(mut self, val: i32) -> Self { - self.thumb_width = Some(val); - self - } - - pub fn thumb_height(mut self, val: i32) -> Self { - self.thumb_height = Some(val); - self - } -} diff --git a/src/types/inline_query_result_mpeg4_gif.rs b/src/types/inline_query_result_mpeg4_gif.rs deleted file mode 100644 index 6c01ceb7..00000000 --- a/src/types/inline_query_result_mpeg4_gif.rs +++ /dev/null @@ -1,149 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; - -/// Represents a link to a video animation (H.264/MPEG-4 AVC video without -/// sound). -/// -/// By default, this animated MPEG-4 file will be sent by the user with optional -/// caption. Alternatively, you can use `input_message_content` to send -/// a message with the specified content instead of the animation. -/// -/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultmpeg4gif). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InlineQueryResultMpeg4Gif { - /// Unique identifier for this result, 1-64 bytes. - pub id: String, - - /// A valid URL for the MP4 file. File size must not exceed 1MB. - pub mpeg4_url: String, - - /// Video width. - pub mpeg4_width: Option, - - /// Video height. - pub mpeg4_height: Option, - - /// Video duration. - pub mpeg4_duration: Option, - - /// URL of the static thumbnail (jpeg or gif) for the result. - pub thumb_url: String, - - /// Title for the result. - pub title: Option, - - /// Caption of the MPEG-4 file to be sent, 0-1024 characters. - pub caption: Option, - - /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, - /// italic, fixed-width text or inline URLs] in the media caption. - /// - /// [Markdown]: https://core.telegram.org/bots/api#markdown-style - /// [HTML]: https://core.telegram.org/bots/api#html-style - /// [bold, italic, fixed-width text or inline URLs]: https://core.telegram.org/bots/api#formatting-options - pub parse_mode: Option, - - /// [Inline keyboard] attached to the message. - /// - /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub reply_markup: Option, - - /// Content of the message to be sent instead of the video animation. - pub input_message_content: Option, -} - -impl InlineQueryResultMpeg4Gif { - pub fn new(id: S1, mpeg4_url: S2, thumb_url: S3) -> Self - where - S1: Into, - S2: Into, - S3: Into, - { - Self { - id: id.into(), - mpeg4_url: mpeg4_url.into(), - thumb_url: thumb_url.into(), - mpeg4_width: None, - mpeg4_height: None, - mpeg4_duration: None, - title: None, - caption: None, - parse_mode: None, - reply_markup: None, - input_message_content: None, - } - } - - pub fn id(mut self, val: S) -> Self - where - S: Into, - { - self.id = val.into(); - self - } - - pub fn mpeg4_url(mut self, val: S) -> Self - where - S: Into, - { - self.mpeg4_url = val.into(); - self - } - - pub fn mpeg4_width(mut self, val: i32) -> Self { - self.mpeg4_width = Some(val); - self - } - - pub fn mpeg4_height(mut self, val: i32) -> Self { - self.mpeg4_height = Some(val); - self - } - - pub fn mpeg4_duration(mut self, val: i32) -> Self { - self.mpeg4_duration = Some(val); - self - } - - pub fn thumb_url(mut self, val: S) -> Self - where - S: Into, - { - self.thumb_url = val.into(); - self - } - - pub fn title(mut self, val: S) -> Self - where - S: Into, - { - self.title = Some(val.into()); - self - } - - pub fn caption(mut self, val: S) -> Self - where - S: Into, - { - self.caption = Some(val.into()); - self - } - - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } - - pub fn input_message_content(mut self, val: InputMessageContent) -> Self { - self.input_message_content = Some(val); - self - } -} diff --git a/src/types/inline_query_result_photo.rs b/src/types/inline_query_result_photo.rs deleted file mode 100644 index e1d32dc9..00000000 --- a/src/types/inline_query_result_photo.rs +++ /dev/null @@ -1,152 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; - -/// Represents a link to a photo. -/// -/// By default, this photo will be sent by the user with optional caption. -/// Alternatively, you can use `input_message_content` to send a message with -/// the specified content instead of the photo. -/// -/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultphoto). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InlineQueryResultPhoto { - /// Unique identifier for this result, 1-64 bytes. - pub id: String, - - /// A valid URL of the photo. Photo must be in **jpeg** format. Photo size - /// must not exceed 5MB. - pub photo_url: String, - - /// URL of the thumbnail for the photo. - pub thumb_url: String, - - /// Width of the photo. - pub photo_width: Option, - - /// Height of the photo. - pub photo_height: Option, - - /// Title for the result. - pub title: Option, - - /// Short description of the result. - pub description: Option, - - /// Caption of the photo to be sent, 0-1024 characters. - pub caption: Option, - - /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, - /// italic, fixed-width text or inline URLs] in the media caption. - /// - /// [Markdown]: https://core.telegram.org/bots/api#markdown-style - /// [HTML]: https://core.telegram.org/bots/api#html-style - /// [bold, italic, fixed-width text or inline URLs]: https://core.telegram.org/bots/api#formatting-options - pub parse_mode: Option, - - /// [Inline keyboard] attached to the message. - /// - /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub reply_markup: Option, - - /// Content of the message to be sent instead of the photo. - pub input_message_content: Option, -} - -impl InlineQueryResultPhoto { - pub fn new(id: S1, photo_url: S2, thumb_url: S3) -> Self - where - S1: Into, - S2: Into, - S3: Into, - { - Self { - id: id.into(), - photo_url: photo_url.into(), - thumb_url: thumb_url.into(), - photo_width: None, - photo_height: None, - title: None, - description: None, - caption: None, - parse_mode: None, - reply_markup: None, - input_message_content: None, - } - } - - pub fn id(mut self, val: S) -> Self - where - S: Into, - { - self.id = val.into(); - self - } - - pub fn photo_url(mut self, val: S) -> Self - where - S: Into, - { - self.photo_url = val.into(); - self - } - - pub fn thumb_url(mut self, val: S) -> Self - where - S: Into, - { - self.thumb_url = val.into(); - self - } - - pub fn photo_width(mut self, val: i32) -> Self { - self.photo_width = Some(val); - self - } - - pub fn photo_height(mut self, val: i32) -> Self { - self.photo_height = Some(val); - self - } - - pub fn title(mut self, val: S) -> Self - where - S: Into, - { - self.title = Some(val.into()); - self - } - - pub fn description(mut self, val: S) -> Self - where - S: Into, - { - self.description = Some(val.into()); - self - } - - pub fn caption(mut self, val: S) -> Self - where - S: Into, - { - self.caption = Some(val.into()); - self - } - - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } - - pub fn input_message_content(mut self, val: InputMessageContent) -> Self { - self.input_message_content = Some(val); - self - } -} diff --git a/src/types/inline_query_result_venue.rs b/src/types/inline_query_result_venue.rs deleted file mode 100644 index c7e67443..00000000 --- a/src/types/inline_query_result_venue.rs +++ /dev/null @@ -1,157 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{InlineKeyboardMarkup, InputMessageContent}; - -/// Represents a venue. -/// -/// By default, the venue will be sent by the user. Alternatively, you can use -/// `input_message_content` to send a message with the specified content instead -/// of the venue. -/// -/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultvenue). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InlineQueryResultVenue { - /// Unique identifier for this result, 1-64 Bytes. - pub id: String, - - /// Latitude of the venue location in degrees. - pub latitude: f64, - - /// Longitude of the venue location in degrees. - pub longitude: f64, - - /// Title of the venue. - pub title: String, - - /// Address of the venue. - pub address: String, - - /// Foursquare identifier of the venue if known. - pub foursquare_id: Option, - - /// Foursquare type of the venue, if known. (For example, - /// `arts_entertainment/default`, `arts_entertainment/aquarium` or - /// `food/icecream`.) - pub foursquare_type: Option, - - /// [Inline keyboard] attached to the message. - /// - /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub reply_markup: Option, - - /// Content of the message to be sent instead of the venue. - pub input_message_content: Option, - - /// Url of the thumbnail for the result. - pub thumb_url: Option, - - /// Thumbnail width. - pub thumb_width: Option, - - /// Thumbnail height. - pub thumb_height: Option, -} - -impl InlineQueryResultVenue { - pub fn new(id: S1, latitude: f64, longitude: f64, title: S2, address: S3) -> Self - where - S1: Into, - S2: Into, - S3: Into, - { - Self { - id: id.into(), - latitude, - longitude, - title: title.into(), - address: address.into(), - foursquare_id: None, - foursquare_type: None, - reply_markup: None, - input_message_content: None, - thumb_url: None, - thumb_width: None, - thumb_height: None, - } - } - - pub fn id(mut self, val: S) -> Self - where - S: Into, - { - self.id = val.into(); - self - } - - pub fn latitude(mut self, val: f64) -> Self { - self.latitude = val; - self - } - - pub fn longitude(mut self, val: f64) -> Self { - self.longitude = val; - self - } - - pub fn title(mut self, val: S) -> Self - where - S: Into, - { - self.title = val.into(); - self - } - - pub fn address(mut self, val: S) -> Self - where - S: Into, - { - self.address = val.into(); - self - } - - pub fn foursquare_id(mut self, val: S) -> Self - where - S: Into, - { - self.foursquare_id = Some(val.into()); - self - } - - pub fn foursquare_type(mut self, val: S) -> Self - where - S: Into, - { - self.foursquare_type = Some(val.into()); - self - } - - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } - - pub fn input_message_content(mut self, val: InputMessageContent) -> Self { - self.input_message_content = Some(val); - self - } - - pub fn thumb_url(mut self, val: S) -> Self - where - S: Into, - { - self.thumb_url = Some(val.into()); - self - } - - pub fn thumb_width(mut self, val: i32) -> Self { - self.thumb_width = Some(val); - self - } - - pub fn thumb_height(mut self, val: i32) -> Self { - self.thumb_height = Some(val); - self - } -} diff --git a/src/types/inline_query_result_video.rs b/src/types/inline_query_result_video.rs deleted file mode 100644 index d51258fc..00000000 --- a/src/types/inline_query_result_video.rs +++ /dev/null @@ -1,182 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{InlineKeyboardMarkup, InputMessageContent, MimeWrapper, ParseMode}; - -/// Represents a link to a page containing an embedded video player or a video -/// file. -/// -/// By default, this video file will be sent by the user with an optional -/// caption. Alternatively, you can use `input_messaage_content` to send a -/// message with the specified content instead of the video. -/// -/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultvideo). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InlineQueryResultVideo { - /// Unique identifier for this result, 1-64 bytes. - pub id: String, - - /// A valid URL for the embedded video player or video file. - pub video_url: String, - - /// Mime type of the content of video url, `text/html` or `video/mp4`. - pub mime_type: MimeWrapper, - - /// URL of the thumbnail (jpeg only) for the video. - pub thumb_url: String, - - /// Title for the result. - pub title: String, - - /// Caption of the video to be sent, 0-1024 characters. - pub caption: Option, - - /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, - /// italic, fixed-width text or inline URLs] in the media caption. - /// - /// [Markdown]: https://core.telegram.org/bots/api#markdown-style - /// [HTML]: https://core.telegram.org/bots/api#html-style - /// [bold, italic, fixed-width text or inline URLs]: https://core.telegram.org/bots/api#formatting-options - pub parse_mode: Option, - - /// Video width. - pub video_width: Option, - - /// Video height. - pub video_height: Option, - - /// Video duration in seconds. - pub video_duration: Option, - - /// Short description of the result. - pub description: Option, - - /// [Inline keyboard] attached to the message. - /// - /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub reply_markup: Option, - - /// Content of the message to be sent instead of the video. This field is - /// **required** if [`InlineQueryResultVideo`] is used to send an HTML-page - /// as a result (e.g., a YouTube video). - /// - /// [`InlineQueryResultVideo`]: - /// crate::types::InlineQueryResultVideo - pub input_message_content: Option, -} - -impl InlineQueryResultVideo { - pub fn new( - id: S1, - video_url: S2, - mime_type: MimeWrapper, - thumb_url: S3, - title: S4, - ) -> Self - where - S1: Into, - S2: Into, - S3: Into, - S4: Into, - { - Self { - id: id.into(), - video_url: video_url.into(), - mime_type, - thumb_url: thumb_url.into(), - title: title.into(), - caption: None, - parse_mode: None, - video_width: None, - video_height: None, - video_duration: None, - description: None, - reply_markup: None, - input_message_content: None, - } - } - - pub fn id(mut self, val: S) -> Self - where - S: Into, - { - self.id = val.into(); - self - } - - pub fn video_url(mut self, val: S) -> Self - where - S: Into, - { - self.video_url = val.into(); - self - } - - pub fn mime_type(mut self, val: MimeWrapper) -> Self { - self.mime_type = val; - self - } - - pub fn thumb_url(mut self, val: S) -> Self - where - S: Into, - { - self.thumb_url = val.into(); - self - } - - pub fn title(mut self, val: S) -> Self - where - S: Into, - { - self.title = val.into(); - self - } - - pub fn caption(mut self, val: S) -> Self - where - S: Into, - { - self.caption = Some(val.into()); - self - } - - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - pub fn video_width(mut self, val: i32) -> Self { - self.video_width = Some(val); - self - } - - pub fn video_height(mut self, val: i32) -> Self { - self.video_height = Some(val); - self - } - - pub fn video_duration(mut self, val: i32) -> Self { - self.video_duration = Some(val); - self - } - - pub fn description(mut self, val: S) -> Self - where - S: Into, - { - self.description = Some(val.into()); - self - } - - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } - - pub fn input_message_content(mut self, val: InputMessageContent) -> Self { - self.input_message_content = Some(val); - self - } -} diff --git a/src/types/inline_query_result_voice.rs b/src/types/inline_query_result_voice.rs deleted file mode 100644 index db812b7e..00000000 --- a/src/types/inline_query_result_voice.rs +++ /dev/null @@ -1,119 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; - -/// Represents a link to a voice recording in an .ogg container encoded with -/// OPUS. -/// -/// By default, this voice recording will be sent by the user. Alternatively, -/// you can use `input_message_content` to send a message with the specified -/// content instead of the the voice message. -/// -/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultvoice). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InlineQueryResultVoice { - /// Unique identifier for this result, 1-64 bytes. - pub id: String, - - /// A valid URL for the voice recording. - pub voice_url: String, - - /// Recording title. - pub title: String, - - /// Caption, 0-1024 characters. - pub caption: Option, - - /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, - /// italic, fixed-width text or inline URLs] in the media caption. - /// - /// [Markdown]: https://core.telegram.org/bots/api#markdown-style - /// [HTML]: https://core.telegram.org/bots/api#html-style - /// [bold, italic, fixed-width text or inline URLs]: https://core.telegram.org/bots/api#formatting-options - pub parse_mode: Option, - - /// Recording duration in seconds. - pub voice_duration: Option, - - /// [Inline keyboard] attached to the message. - /// - /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub reply_markup: Option, - - /// Content of the message to be sent instead of the voice recording. - pub input_message_content: Option, -} - -impl InlineQueryResultVoice { - pub fn new(id: S1, voice_url: S2, title: S3) -> Self - where - S1: Into, - S2: Into, - S3: Into, - { - Self { - id: id.into(), - voice_url: voice_url.into(), - title: title.into(), - caption: None, - parse_mode: None, - voice_duration: None, - reply_markup: None, - input_message_content: None, - } - } - - pub fn id(mut self, val: S) -> Self - where - S: Into, - { - self.id = val.into(); - self - } - - pub fn voice_url(mut self, val: S) -> Self - where - S: Into, - { - self.voice_url = val.into(); - self - } - - pub fn title(mut self, val: S) -> Self - where - S: Into, - { - self.title = val.into(); - self - } - - pub fn caption(mut self, val: S) -> Self - where - S: Into, - { - self.caption = Some(val.into()); - self - } - - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - pub fn voice_duration(mut self, value: i32) -> Self { - self.voice_duration = Some(value); - self - } - - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } - - pub fn input_message_content(mut self, val: InputMessageContent) -> Self { - self.input_message_content = Some(val); - self - } -} diff --git a/src/types/input_file.rs b/src/types/input_file.rs deleted file mode 100644 index 55e15e14..00000000 --- a/src/types/input_file.rs +++ /dev/null @@ -1,101 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use std::{borrow::Cow, path::PathBuf}; - -/// This object represents the contents of a file to be uploaded. -/// -/// [The official docs](https://core.telegram.org/bots/api#inputfile). -#[derive(Clone, Debug, Eq, Hash, PartialEq, Deserialize)] -#[non_exhaustive] -pub enum InputFile { - File(PathBuf), - Memory { file_name: String, data: Cow<'static, [u8]> }, - Url(String), - FileId(String), -} - -impl InputFile { - pub fn file

(path: P) -> Self - where - P: Into, - { - Self::File(path.into()) - } - - pub fn memory(file_name: S, data: D) -> Self - where - S: Into, - D: Into>, - { - Self::Memory { file_name: file_name.into(), data: data.into() } - } - - pub fn url(url: T) -> Self - where - T: Into, - { - Self::Url(url.into()) - } - - pub fn file_id(file_id: T) -> Self - where - T: Into, - { - Self::FileId(file_id.into()) - } - - pub fn as_file(&self) -> Option<&PathBuf> { - match self { - Self::File(path) => Some(path), - _ => None, - } - } - - pub fn as_url(&self) -> Option<&String> { - match self { - Self::Url(url) => Some(url), - _ => None, - } - } - - pub fn as_file_id(&self) -> Option<&String> { - match self { - Self::FileId(id) => Some(id), - _ => None, - } - } -} - -impl From for Option { - fn from(file: InputFile) -> Self { - match file { - InputFile::File(path) => Some(path), - _ => None, - } - } -} - -impl Serialize for InputFile { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - match self { - InputFile::File(path) => { - // NOTE: file should be actually attached with - // multipart/form-data - serializer.serialize_str( - // TODO: remove unwrap (?) - &format!("attach://{}", path.file_name().unwrap().to_string_lossy()), - ) - } - InputFile::Memory { data, .. } => { - // NOTE: file should be actually attached with - // multipart/form-data - serializer.serialize_str(&format!("attach://{}", String::from_utf8_lossy(data))) - } - InputFile::Url(url) => serializer.serialize_str(url), - InputFile::FileId(id) => serializer.serialize_str(id), - } - } -} diff --git a/src/types/input_media.rs b/src/types/input_media.rs deleted file mode 100644 index a37ebda4..00000000 --- a/src/types/input_media.rs +++ /dev/null @@ -1,511 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{InputFile, ParseMode}; - -/// This object represents the content of a media message to be sent. -/// -/// [The official docs](https://core.telegram.org/bots/api#inputmedia). -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[serde(tag = "type")] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum InputMedia { - Photo(InputMediaPhoto), - Video(InputMediaVideo), - Animation(InputMediaAnimation), - Audio(InputMediaAudio), - Document(InputMediaDocument), -} - -/// Represents a photo to be sent. -/// -/// [The official docs](https://core.telegram.org/bots/api#inputmediaphoto). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InputMediaPhoto { - /// File to send. - pub media: InputFile, - - /// Caption of the photo to be sent, 0-1024 characters. - pub caption: Option, - - /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, - /// italic, fixed-width text or inline URLs] in the media caption. - /// - /// [Markdown]: https://core.telegram.org/bots/api#markdown-style - /// [HTML]: https://core.telegram.org/bots/api#html-style - /// [bold, italic, fixed-width text or inline URLs]: https://core.telegram.org/bots/api#formatting-options - pub parse_mode: Option, -} - -impl InputMediaPhoto { - pub fn new(media: InputFile) -> Self { - Self { media, caption: None, parse_mode: None } - } - - pub fn media(mut self, val: InputFile) -> Self { - self.media = val; - self - } - - pub fn caption(mut self, val: S) -> Self - where - S: Into, - { - self.caption = Some(val.into()); - self - } - - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } -} - -/// Represents a video to be sent. -/// -/// [The official docs](https://core.telegram.org/bots/api#inputmediavideo). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InputMediaVideo { - // File to send. - pub media: InputFile, - - /// Thumbnail of the file sent; can be ignored if thumbnail generation - /// for the file is supported server-side. The thumbnail should be in - /// JPEG format and less than 200 kB in size. A thumbnail‘s width and - /// height should not exceed 320. Ignored if the file is not uploaded - /// using multipart/form-data. - pub thumb: Option, - - /// Caption of the video to be sent, 0-1024 characters. - pub caption: Option, - - /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, - /// italic, fixed-width text or inline URLs] in the media caption. - /// - /// [Markdown]: https://core.telegram.org/bots/api#markdown-style - /// [HTML]: https://core.telegram.org/bots/api#html-style - /// [bold, italic, fixed-width text or inline URLs]: https://core.telegram.org/bots/api#formatting-options - pub parse_mode: Option, - - /// Video width. - pub width: Option, - - /// Video height. - pub height: Option, - - /// Video duration. - pub duration: Option, - - /// Pass `true`, if the uploaded video is suitable for streaming. - pub supports_streaming: Option, -} - -impl InputMediaVideo { - pub fn new(media: InputFile) -> Self { - Self { - media, - thumb: None, - caption: None, - parse_mode: None, - width: None, - height: None, - duration: None, - supports_streaming: None, - } - } - - pub fn media(mut self, val: InputFile) -> Self { - self.media = val; - self - } - - pub fn thumb(mut self, val: InputFile) -> Self { - self.thumb = Some(val); - self - } - - pub fn caption(mut self, val: S) -> Self - where - S: Into, - { - self.caption = Some(val.into()); - self - } - - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - pub fn width(mut self, val: u16) -> Self { - self.width = Some(val); - self - } - - pub fn height(mut self, val: u16) -> Self { - self.height = Some(val); - self - } - - pub fn duration(mut self, val: u16) -> Self { - self.duration = Some(val); - self - } - - pub fn supports_streaming(mut self, val: bool) -> Self { - self.supports_streaming = Some(val); - self - } -} - -/// Represents an animation file (GIF or H.264/MPEG-4 AVC video without -/// sound) to be sent. -/// -/// [The official docs](https://core.telegram.org/bots/api#inputmediaanimation). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InputMediaAnimation { - /// File to send. - pub media: InputFile, - - /// Thumbnail of the file sent; can be ignored if thumbnail generation - /// for the file is supported server-side. The thumbnail should be in - /// JPEG format and less than 200 kB in size. A thumbnail‘s width and - /// height should not exceed 320. Ignored if the file is not uploaded - /// using multipart/form-data. - pub thumb: Option, - - /// Caption of the animation to be sent, 0-1024 characters. - pub caption: Option, - - /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, - /// italic, fixed-width text or inline URLs] in the media caption. - /// - /// [Markdown]: https://core.telegram.org/bots/api#markdown-style - /// [HTML]: https://core.telegram.org/bots/api#html-style - /// [bold, italic, fixed-width text or inline URLs]: https://core.telegram.org/bots/api#formatting-options - pub parse_mode: Option, - - /// Animation width. - pub width: Option, - - /// Animation height. - pub height: Option, - - /// Animation duration. - pub duration: Option, -} - -impl InputMediaAnimation { - pub fn new(media: InputFile) -> Self { - Self { - media, - thumb: None, - caption: None, - parse_mode: None, - width: None, - height: None, - duration: None, - } - } - - pub fn media(mut self, val: InputFile) -> Self { - self.media = val; - self - } - - pub fn thumb(mut self, val: InputFile) -> Self { - self.thumb = Some(val); - self - } - - pub fn caption(mut self, val: S) -> Self - where - S: Into, - { - self.caption = Some(val.into()); - self - } - - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - pub fn width(mut self, val: u16) -> Self { - self.width = Some(val); - self - } - - pub fn height(mut self, val: u16) -> Self { - self.height = Some(val); - self - } - - pub fn duration(mut self, val: u16) -> Self { - self.duration = Some(val); - self - } -} - -/// Represents an audio file to be treated as music to be sent. -/// -/// [The official docs](https://core.telegram.org/bots/api#inputmediaaudio). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InputMediaAudio { - /// File to send. - pub media: InputFile, - - /// Thumbnail of the file sent; can be ignored if thumbnail generation - /// for the file is supported server-side. The thumbnail should be in - /// JPEG format and less than 200 kB in size. A thumbnail‘s width and - /// height should not exceed 320. Ignored if the file is not uploaded - /// using multipart/form-data. - pub thumb: Option, - - /// Caption of the audio to be sent, 0-1024 characters. - pub caption: Option, - - /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, - /// italic, fixed-width text or inline URLs] in the media caption. - /// - /// [Markdown]: https://core.telegram.org/bots/api#markdown-style - /// [HTML]: https://core.telegram.org/bots/api#html-style - /// [bold, italic, fixed-width text or inline URLs]: https://core.telegram.org/bots/api#formatting-options - pub parse_mode: Option, - - /// Duration of the audio in seconds. - pub duration: Option, - - /// Performer of the audio. - pub performer: Option, - - /// Title of the audio. - pub title: Option, -} - -impl InputMediaAudio { - pub fn new(media: InputFile) -> Self { - Self { - media, - thumb: None, - caption: None, - parse_mode: None, - performer: None, - title: None, - duration: None, - } - } - - pub fn media(mut self, val: InputFile) -> Self { - self.media = val; - self - } - - pub fn thumb(mut self, val: InputFile) -> Self { - self.thumb = Some(val); - self - } - - pub fn caption(mut self, val: S) -> Self - where - S: Into, - { - self.caption = Some(val.into()); - self - } - - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - pub fn duration(mut self, val: u16) -> Self { - self.duration = Some(val); - self - } - - pub fn performer(mut self, val: S) -> Self - where - S: Into, - { - self.performer = Some(val.into()); - self - } - - pub fn title(mut self, val: S) -> Self - where - S: Into, - { - self.title = Some(val.into()); - self - } -} - -/// Represents a general file to be sent. -/// -/// [The official docs](https://core.telegram.org/bots/api#inputmediadocument). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InputMediaDocument { - /// File to send. - pub media: InputFile, - - /// Thumbnail of the file sent; can be ignored if thumbnail generation - /// for the file is supported server-side. The thumbnail should be in - /// JPEG format and less than 200 kB in size. A thumbnail‘s width and - /// height should not exceed 320. Ignored if the file is not uploaded - /// using multipart/form-data. - pub thumb: Option, - - /// Caption of the document to be sent, 0-1024 charactersю - pub caption: Option, - - /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, - /// italic, fixed-width text or inline URLs] in the media caption. - /// - /// [Markdown]: https://core.telegram.org/bots/api#markdown-style - /// [HTML]: https://core.telegram.org/bots/api#html-style - /// [bold, italic, fixed-width text or inline URLs]: https://core.telegram.org/bots/api#formatting-options - pub parse_mode: Option, -} - -impl InputMediaDocument { - pub fn new(media: InputFile) -> Self { - Self { media, thumb: None, caption: None, parse_mode: None } - } - - pub fn thumb(mut self, val: InputFile) -> Self { - self.thumb = Some(val); - self - } - - pub fn caption(mut self, val: S) -> Self - where - S: Into, - { - self.caption = Some(val.into()); - self - } - - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } -} - -impl InputMedia { - pub fn media(&self) -> &InputFile { - match self { - InputMedia::Photo(InputMediaPhoto { media, .. }) - | InputMedia::Document(InputMediaDocument { media, .. }) - | InputMedia::Audio(InputMediaAudio { media, .. }) - | InputMedia::Animation(InputMediaAnimation { media, .. }) - | InputMedia::Video(InputMediaVideo { media, .. }) => media, - } - } -} - -impl From for InputFile { - fn from(media: InputMedia) -> InputFile { - match media { - InputMedia::Photo(InputMediaPhoto { media, .. }) - | InputMedia::Document(InputMediaDocument { media, .. }) - | InputMedia::Audio(InputMediaAudio { media, .. }) - | InputMedia::Animation(InputMediaAnimation { media, .. }) - | InputMedia::Video(InputMediaVideo { media, .. }) => media, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn photo_serialize() { - let expected_json = r#"{"type":"photo","media":"123456"}"#; - let photo = InputMedia::Photo(InputMediaPhoto { - media: InputFile::FileId(String::from("123456")), - caption: None, - parse_mode: None, - }); - - let actual_json = serde_json::to_string(&photo).unwrap(); - assert_eq!(expected_json, actual_json); - } - - #[test] - fn video_serialize() { - let expected_json = r#"{"type":"video","media":"123456"}"#; - let video = InputMedia::Video(InputMediaVideo { - media: InputFile::FileId(String::from("123456")), - thumb: None, - caption: None, - parse_mode: None, - width: None, - height: None, - duration: None, - supports_streaming: None, - }); - - let actual_json = serde_json::to_string(&video).unwrap(); - assert_eq!(expected_json, actual_json); - } - - #[test] - fn animation_serialize() { - let expected_json = r#"{"type":"animation","media":"123456"}"#; - let video = InputMedia::Animation(InputMediaAnimation { - media: InputFile::FileId(String::from("123456")), - thumb: None, - caption: None, - parse_mode: None, - width: None, - height: None, - duration: None, - }); - - let actual_json = serde_json::to_string(&video).unwrap(); - assert_eq!(expected_json, actual_json); - } - - #[test] - fn audio_serialize() { - let expected_json = r#"{"type":"audio","media":"123456"}"#; - let video = InputMedia::Audio(InputMediaAudio { - media: InputFile::FileId(String::from("123456")), - thumb: None, - caption: None, - parse_mode: None, - duration: None, - performer: None, - title: None, - }); - - let actual_json = serde_json::to_string(&video).unwrap(); - assert_eq!(expected_json, actual_json); - } - - #[test] - fn document_serialize() { - let expected_json = r#"{"type":"document","media":"123456"}"#; - let video = InputMedia::Document(InputMediaDocument { - media: InputFile::FileId(String::from("123456")), - thumb: None, - caption: None, - parse_mode: None, - }); - - let actual_json = serde_json::to_string(&video).unwrap(); - assert_eq!(expected_json, actual_json); - } -} diff --git a/src/types/input_message_content.rs b/src/types/input_message_content.rs deleted file mode 100644 index caf57650..00000000 --- a/src/types/input_message_content.rs +++ /dev/null @@ -1,318 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::ParseMode; - -/// This object represents the content of a message to be sent as a result of an -/// inline query. -/// -/// [The official docs](https://core.telegram.org/bots/api#inputmessagecontent). -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[serde(untagged)] -#[non_exhaustive] -pub enum InputMessageContent { - Text(InputMessageContentText), - Location(InputMessageContentLocation), - Venue(InputMessageContentVenue), - Contact(InputMessageContentContact), -} -/// Represents the content of a text message to be sent as the result of an -/// inline query. -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InputMessageContentText { - /// Text of the message to be sent, 1-4096 characters. - pub message_text: String, - - /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, - /// italic, fixed-width text or inline URLs] in the media caption. - /// - /// [Markdown]: https://core.telegram.org/bots/api#markdown-style - /// [HTML]: https://core.telegram.org/bots/api#html-style - /// [bold, italic, fixed-width text or inline URLs]: https://core.telegram.org/bots/api#formatting-options - pub parse_mode: Option, - - /// Disables link previews for links in the sent message. - pub disable_web_page_preview: Option, -} - -impl InputMessageContentText { - pub fn new(message_text: S) -> Self - where - S: Into, - { - Self { message_text: message_text.into(), parse_mode: None, disable_web_page_preview: None } - } - - pub fn message_text(mut self, val: S) -> Self - where - S: Into, - { - self.message_text = val.into(); - self - } - - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - pub fn disable_web_page_preview(mut self, val: bool) -> Self { - self.disable_web_page_preview = Some(val); - self - } -} - -/// Represents the content of a location message to be sent as the result of an -/// inline query. -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InputMessageContentLocation { - /// Latitude of the location in degrees. - pub latitude: f64, - - /// Longitude of the location in degrees. - pub longitude: f64, - - /// Period in seconds for which the location can be updated, should be - /// between 60 and 86400. - pub live_period: Option, -} - -impl InputMessageContentLocation { - pub fn new(latitude: f64, longitude: f64) -> Self { - Self { latitude, longitude, live_period: None } - } - - pub fn latitude(mut self, val: f64) -> Self { - self.latitude = val; - self - } - - pub fn longitude(mut self, val: f64) -> Self { - self.longitude = val; - self - } - - pub fn live_period(mut self, val: u32) -> Self { - self.live_period = Some(val); - self - } -} - -/// Represents the content of a venue message to be sent as the result of -/// an inline query. -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InputMessageContentVenue { - /// Latitude of the venue in degrees. - pub latitude: f64, - - /// Longitude of the venue in degrees. - pub longitude: f64, - - /// Name of the venue. - pub title: String, - - /// Address of the venue. - pub address: String, - - /// Foursquare identifier of the venue, if known. - pub foursquare_id: Option, - - /// Foursquare type of the venue, if known. (For example, - /// `arts_entertainment/default`, `arts_entertainment/aquarium` - /// or `food/icecream`.) - pub foursquare_type: Option, -} - -impl InputMessageContentVenue { - pub fn new(latitude: f64, longitude: f64, title: S1, address: S2) -> Self - where - S1: Into, - S2: Into, - { - Self { - latitude, - longitude, - title: title.into(), - address: address.into(), - foursquare_id: None, - foursquare_type: None, - } - } - - pub fn latitude(mut self, val: f64) -> Self { - self.latitude = val; - self - } - - pub fn longitude(mut self, val: f64) -> Self { - self.longitude = val; - self - } - - pub fn title(mut self, val: S) -> Self - where - S: Into, - { - self.title = val.into(); - self - } - - pub fn address(mut self, val: S) -> Self - where - S: Into, - { - self.address = val.into(); - self - } - - pub fn foursquare_id(mut self, val: S) -> Self - where - S: Into, - { - self.foursquare_id = Some(val.into()); - self - } - - pub fn foursquare_type(mut self, val: S) -> Self - where - S: Into, - { - self.foursquare_type = Some(val.into()); - self - } -} - -/// Represents the content of a contact message to be sent as the result of -/// an inline query. -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InputMessageContentContact { - /// Contact's phone number. - pub phone_number: String, - - /// Contact's first name. - pub first_name: String, - - /// Contact's last name. - pub last_name: Option, - - /// Additional data about the contact in the form of a [vCard], 0-2048 - /// bytes. - /// - /// [vCard]: https://en.wikipedia.org/wiki/VCard - pub vcard: Option, -} - -impl InputMessageContentContact { - pub fn new(phone_number: S1, first_name: S2) -> Self - where - S1: Into, - S2: Into, - { - Self { - phone_number: phone_number.into(), - first_name: first_name.into(), - last_name: None, - vcard: None, - } - } - - pub fn phone_number(mut self, val: S) -> Self - where - S: Into, - { - self.phone_number = val.into(); - self - } - - pub fn first_name(mut self, val: S) -> Self - where - S: Into, - { - self.first_name = val.into(); - self - } - - pub fn last_name(mut self, val: S) -> Self - where - S: Into, - { - self.last_name = Some(val.into()); - self - } - - pub fn vcard(mut self, val: S) -> Self - where - S: Into, - { - self.vcard = Some(val.into()); - self - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn text_serialize() { - let expected_json = r#"{"message_text":"text"}"#; - let text_content = InputMessageContent::Text(InputMessageContentText { - message_text: String::from("text"), - parse_mode: None, - disable_web_page_preview: None, - }); - - let actual_json = serde_json::to_string(&text_content).unwrap(); - assert_eq!(expected_json, actual_json); - } - - #[test] - fn location_serialize() { - let expected_json = r#"{"latitude":59.08,"longitude":38.4326}"#; - let location_content = InputMessageContent::Location(InputMessageContentLocation { - latitude: 59.08, - longitude: 38.4326, - live_period: None, - }); - - let actual_json = serde_json::to_string(&location_content).unwrap(); - assert_eq!(expected_json, actual_json); - } - - #[test] - fn venue_serialize() { - let expected_json = r#"{"latitude":59.08,"longitude":38.4326,"title":"some title","address":"some address"}"#; - let venue_content = InputMessageContent::Venue(InputMessageContentVenue { - latitude: 59.08, - longitude: 38.4326, - title: String::from("some title"), - address: String::from("some address"), - foursquare_id: None, - foursquare_type: None, - }); - - let actual_json = serde_json::to_string(&venue_content).unwrap(); - assert_eq!(expected_json, actual_json); - } - - #[test] - fn contact_serialize() { - let expected_json = r#"{"phone_number":"+3800000000","first_name":"jhon"}"#; - let contact_content = InputMessageContent::Contact(InputMessageContentContact { - phone_number: String::from("+3800000000"), - first_name: String::from("jhon"), - last_name: None, - vcard: None, - }); - - let actual_json = serde_json::to_string(&contact_content).unwrap(); - assert_eq!(expected_json, actual_json); - } -} diff --git a/src/types/invoice.rs b/src/types/invoice.rs deleted file mode 100644 index 979881e1..00000000 --- a/src/types/invoice.rs +++ /dev/null @@ -1,91 +0,0 @@ -use serde::{Deserialize, Serialize}; - -/// This object contains basic information about an invoice. -/// -/// [The official docs](https://core.telegram.org/bots/api#invoice). -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct Invoice { - /// Product name. - pub title: String, - - /// Product description. - pub description: String, - - /// Unique bot deep-linking parameter that can be used to generate this - /// invoice. - pub start_parameter: String, - - /// Three-letter ISO 4217 currency code. - pub currency: String, - - /// Total price in the smallest units of the currency (integer, **not** - /// float/double). For example, for a price of `US$ 1.45` pass `amount = - /// 145`. See the exp parameter in [`currencies.json`], it shows the number - /// of digits past the decimal point for each currency (2 for the - /// majority of currencies). - /// - /// [`currencies.json`]: https://core.telegram.org/bots/payments/currencies.json - pub total_amount: i32, -} - -impl Invoice { - pub fn new( - title: S1, - description: S2, - start_parameter: S3, - currency: S4, - total_amount: i32, - ) -> Self - where - S1: Into, - S2: Into, - S3: Into, - S4: Into, - { - Self { - title: title.into(), - description: description.into(), - start_parameter: start_parameter.into(), - currency: currency.into(), - total_amount, - } - } - - pub fn title(mut self, val: S) -> Self - where - S: Into, - { - self.title = val.into(); - self - } - - pub fn description(mut self, val: S) -> Self - where - S: Into, - { - self.description = val.into(); - self - } - - pub fn start_parameter(mut self, val: S) -> Self - where - S: Into, - { - self.start_parameter = val.into(); - self - } - - pub fn currency(mut self, val: S) -> Self - where - S: Into, - { - self.currency = val.into(); - self - } - - pub fn total_amount(mut self, val: i32) -> Self { - self.total_amount = val; - self - } -} diff --git a/src/types/keyboard_button.rs b/src/types/keyboard_button.rs deleted file mode 100644 index fc37134a..00000000 --- a/src/types/keyboard_button.rs +++ /dev/null @@ -1,156 +0,0 @@ -use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer}; - -use crate::types::{KeyboardButtonPollType, True}; - -/// This object represents one button of the reply keyboard. -/// -/// For filter text buttons String can be used instead of this object to specify -/// text of the button. -/// -/// [The official docs](https://core.telegram.org/bots/api#keyboardbutton). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct KeyboardButton { - /// Text of the button. If none of the optional fields are used, it will - /// be sent as a message when the button is pressed. - pub text: String, - - /// Request something from user. - /// - If `Some(Contact)`, the user's phone number will be sent as a contact - /// when the button is pressed. Available in private chats only - /// - If `Some(Location)`, the user's current location will be sent when the - /// button is pressed. Available in private chats only - #[serde(flatten)] - pub request: Option, -} - -impl KeyboardButton { - pub fn new(text: T) -> Self - where - T: Into, - { - Self { text: text.into(), request: None } - } - - pub fn request(mut self, val: T) -> Self - where - T: Into>, - { - self.request = val.into(); - self - } -} - -// Serialize + Deserialize are implemented by hand -#[derive(Clone, Debug, Eq, Hash, PartialEq)] -#[non_exhaustive] -pub enum ButtonRequest { - Location, - Contact, - KeyboardButtonPollType(KeyboardButtonPollType), -} - -/// Helper struct for (de)serializing [`ButtonRequest`](ButtonRequest) -#[serde_with_macros::skip_serializing_none] -#[derive(Serialize, Deserialize)] -#[non_exhaustive] -struct RawRequest { - /// If `true`, the user's phone number will be sent as a contact - /// when the button is pressed. Available in private chats only. - #[serde(rename = "request_contact")] - contact: Option, - - /// If `true`, the user's current location will be sent when the - /// button is pressed. Available in private chats only. - #[serde(rename = "request_location")] - location: Option, - - /// 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. - #[serde(rename = "request_poll")] - poll: Option, -} - -impl<'de> Deserialize<'de> for ButtonRequest { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let raw = RawRequest::deserialize(deserializer)?; - match raw { - RawRequest { contact: Some(_), location: Some(_), poll: Some(_) } => { - Err(D::Error::custom( - "`request_contact` and `request_location` fields are mutually exclusive, but \ - both were provided", - )) - } - RawRequest { contact: Some(_), .. } => Ok(Self::Contact), - RawRequest { location: Some(_), .. } => Ok(Self::Location), - RawRequest { poll: Some(poll_type), .. } => Ok(Self::KeyboardButtonPollType(poll_type)), - _ => Err(D::Error::custom( - "Either one of `request_contact` and `request_location` fields is required", - )), - } - } -} - -impl Serialize for ButtonRequest { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - match self { - Self::Contact => { - RawRequest { contact: Some(True), location: None, poll: None }.serialize(serializer) - } - Self::Location => { - RawRequest { contact: None, location: Some(True), poll: None }.serialize(serializer) - } - Self::KeyboardButtonPollType(poll_type) => { - RawRequest { contact: None, location: None, poll: Some(poll_type.clone()) } - .serialize(serializer) - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn serialize_no_request() { - let button = KeyboardButton { text: String::from(""), request: None }; - let expected = r#"{"text":""}"#; - let actual = serde_json::to_string(&button).unwrap(); - assert_eq!(expected, actual); - } - - #[test] - fn serialize_request_contact() { - let button = - KeyboardButton { text: String::from(""), request: Some(ButtonRequest::Contact) }; - let expected = r#"{"text":"","request_contact":true}"#; - let actual = serde_json::to_string(&button).unwrap(); - assert_eq!(expected, actual); - } - - #[test] - fn deserialize_no_request() { - let json = r#"{"text":""}"#; - let expected = KeyboardButton { text: String::from(""), request: None }; - let actual = serde_json::from_str(json).unwrap(); - assert_eq!(expected, actual); - } - - #[test] - fn deserialize_request_contact() { - let json = r#"{"text":"","request_contact":true}"#; - let expected = - KeyboardButton { text: String::from(""), request: Some(ButtonRequest::Contact) }; - let actual = serde_json::from_str(json).unwrap(); - assert_eq!(expected, actual); - } -} diff --git a/src/types/keyboard_button_poll_type.rs b/src/types/keyboard_button_poll_type.rs deleted file mode 100644 index 6b24d540..00000000 --- a/src/types/keyboard_button_poll_type.rs +++ /dev/null @@ -1,24 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct KeyboardButtonPollType { - poll_type: String, -} - -impl KeyboardButtonPollType { - pub fn new(poll_type: S) -> Self - where - S: Into, - { - Self { poll_type: poll_type.into() } - } - - pub fn poll_type(mut self, val: S) -> Self - where - S: Into, - { - self.poll_type = val.into(); - self - } -} diff --git a/src/types/label_price.rs b/src/types/label_price.rs deleted file mode 100644 index 4c6841a9..00000000 --- a/src/types/label_price.rs +++ /dev/null @@ -1,56 +0,0 @@ -use serde::{Deserialize, Serialize}; - -/// This object represents a portion of the price for goods or services. -/// -/// [The official docs](https://core.telegram.org/bots/api#labeledprice). -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct LabeledPrice { - /// Portion label. - pub label: String, - - /// Price of the product in the smallest units of the [currency] (integer, - /// **not** float/double). For example, for a price of `US$ 1.45` pass - /// `amount = 145`. See the exp parameter in [`currencies.json`], it shows - /// the number of digits past the decimal point for each currency (2 - /// for the majority of currencies). - /// - /// [currency]: https://core.telegram.org/bots/payments#supported-currencies - /// [`currencies.json`]: https://core.telegram.org/bots/payments/currencies.json - pub amount: i32, -} - -impl LabeledPrice { - pub fn new(label: S, amount: i32) -> Self - where - S: Into, - { - Self { label: label.into(), amount } - } - - pub fn label(mut self, val: S) -> Self - where - S: Into, - { - self.label = val.into(); - self - } - - pub fn amount(mut self, val: i32) -> Self { - self.amount = val; - self - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn serialize() { - let labeled_price = LabeledPrice { label: "Label".to_string(), amount: 60 }; - let expected = r#"{"label":"Label","amount":60}"#; - let actual = serde_json::to_string(&labeled_price).unwrap(); - assert_eq!(actual, expected); - } -} diff --git a/src/types/location.rs b/src/types/location.rs deleted file mode 100644 index e34cee1a..00000000 --- a/src/types/location.rs +++ /dev/null @@ -1,28 +0,0 @@ -use serde::{Deserialize, Serialize}; - -/// This object represents a point on the map. -#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct Location { - /// Longitude as defined by sender. - pub longitude: f64, - - /// Latitude as defined by sender. - pub latitude: f64, -} - -impl Location { - pub fn new(longitude: f64, latitude: f64) -> Self { - Self { longitude, latitude } - } - - pub fn latitude(mut self, val: f64) -> Self { - self.latitude = val; - self - } - - pub fn longitude(mut self, val: f64) -> Self { - self.longitude = val; - self - } -} diff --git a/src/types/login_url.rs b/src/types/login_url.rs deleted file mode 100644 index 36515cd6..00000000 --- a/src/types/login_url.rs +++ /dev/null @@ -1,61 +0,0 @@ -use serde::{Deserialize, Serialize}; - -/// This object represents a parameter of the inline keyboard button used to -/// automatically authorize a user. -/// -/// Serves as a great replacement for the [Telegram Login Widget] when the user -/// is coming from Telegram. All the user needs to do is tap/click a button and -/// confirm that they want to log in: -/// -///

-/// -///
-/// -/// [Telegram Login Widget]: https://core.telegram.org/widgets/login -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct LoginUrl { - pub url: String, - pub forward_text: Option, - pub bot_username: Option, - pub request_write_access: Option, -} - -impl LoginUrl { - pub fn new(url: S) -> Self - where - S: Into, - { - Self { url: url.into(), forward_text: None, bot_username: None, request_write_access: None } - } - - pub fn url(mut self, val: S) -> Self - where - S: Into, - { - self.url = val.into(); - self - } - - pub fn forward_text(mut self, val: S) -> Self - where - S: Into, - { - self.forward_text = Some(val.into()); - self - } - - pub fn bot_username(mut self, val: S) -> Self - where - S: Into, - { - self.bot_username = Some(val.into()); - self - } - - pub fn request_write_access(mut self, val: bool) -> Self { - self.request_write_access = Some(val); - self - } -} diff --git a/src/types/mask_position.rs b/src/types/mask_position.rs deleted file mode 100644 index 8bca0273..00000000 --- a/src/types/mask_position.rs +++ /dev/null @@ -1,58 +0,0 @@ -use serde::{Deserialize, Serialize}; - -/// This object describes the position on faces where a mask should be placed by -/// default. -/// -/// [The official docs](https://core.telegram.org/bots/api#maskposition). -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MaskPosition { - /// The part of the face relative to which the mask should be placed. One - /// of `forehead`, `eyes`, `mouth`, or `chin`. - pub point: String, - - /// Shift by X-axis measured in widths of the mask scaled to the face size, - /// from left to right. For example, choosing `-1.0` will place mask just - /// to the left of the default mask position. - pub x_shift: f64, - - /// Shift by Y-axis measured in heights of the mask scaled to the face - /// size, from top to bottom. For example, `1.0` will place the mask just - /// below the default mask position. - pub y_shift: f64, - - /// Mask scaling coefficient. For example, `2.0` means double size. - pub scale: f64, -} - -impl MaskPosition { - pub fn new(point: S, x_shift: f64, y_shift: f64, scale: f64) -> Self - where - S: Into, - { - Self { point: point.into(), x_shift, y_shift, scale } - } - - pub fn point(mut self, val: S) -> Self - where - S: Into, - { - self.point = val.into(); - self - } - - pub fn x_shift(mut self, val: f64) -> Self { - self.x_shift = val; - self - } - - pub fn y_shift(mut self, val: f64) -> Self { - self.y_shift = val; - self - } - - pub fn scale(mut self, val: f64) -> Self { - self.scale = val; - self - } -} diff --git a/src/types/me.rs b/src/types/me.rs deleted file mode 100644 index 0b46a030..00000000 --- a/src/types/me.rs +++ /dev/null @@ -1,57 +0,0 @@ -use crate::types::User; -use serde::{Deserialize, Serialize}; - -/// Returned only in [`Bot::get_me`]. -/// -/// [`Bot::get_me`]: crate::Bot::get_me -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct Me { - #[serde(flatten)] - pub user: User, - - /// `true`, if the bot can be invited to groups. - pub can_join_groups: bool, - - /// `true`, if [privacy mode] is disabled for the bot. - /// - /// [privacy mode]: https://core.telegram.org/bots#privacy-mode - pub can_read_all_group_messages: bool, - - /// `true`, if the bot supports inline queries. - pub supports_inline_queries: bool, -} - -impl Me { - pub fn new( - user: User, - can_join_groups: bool, - can_read_all_group_messages: bool, - supports_inline_queries: bool, - ) -> Self { - Self { user, can_join_groups, can_read_all_group_messages, supports_inline_queries } - } - - pub fn user(mut self, val: User) -> Self { - self.user = val; - self - } - - #[warn(clippy::wrong_self_convention)] - pub fn can_join_groups(mut self, val: bool) -> Self { - self.can_join_groups = val; - self - } - - #[warn(clippy::wrong_self_convention)] - pub fn can_read_all_group_messages(mut self, val: bool) -> Self { - self.can_read_all_group_messages = val; - self - } - - #[warn(clippy::wrong_self_convention)] - pub fn supports_inline_queries(mut self, val: bool) -> Self { - self.supports_inline_queries = val; - self - } -} diff --git a/src/types/message.rs b/src/types/message.rs deleted file mode 100644 index 79aa8366..00000000 --- a/src/types/message.rs +++ /dev/null @@ -1,1753 +0,0 @@ -#![allow(clippy::large_enum_variant)] - -use serde::{Deserialize, Serialize}; - -use crate::types::{ - chat::{ChatKind, PublicChatKind}, - Animation, Audio, Chat, ChatPublic, Contact, Dice, Document, Game, InlineKeyboardMarkup, - Invoice, Location, MessageEntity, PassportData, PhotoSize, Poll, PublicChatChannel, - PublicChatSupergroup, Sticker, SuccessfulPayment, True, User, Venue, Video, VideoNote, Voice, -}; - -/// This object represents a message. -/// -/// [The official docs](https://core.telegram.org/bots/api#message). -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct Message { - /// Unique message identifier inside this chat. - #[serde(rename = "message_id")] - pub id: i32, - - /// Date the message was sent in Unix time. - pub date: i32, - - /// Conversation the message belongs to. - pub chat: Chat, - - /// Bot through which the message was sent. - pub via_bot: Option, - - #[serde(flatten)] - pub kind: MessageKind, -} - -impl Message { - pub fn new(id: i32, date: i32, chat: Chat, kind: MessageKind) -> Self { - Self { id, date, chat, kind, via_bot: None } - } - - pub fn id(mut self, val: i32) -> Self { - self.id = val; - self - } - - pub fn date(mut self, val: i32) -> Self { - self.date = val; - self - } - - pub fn chat(mut self, val: Chat) -> Self { - self.chat = val; - self - } - - pub fn kind(mut self, val: MessageKind) -> Self { - self.kind = val; - self - } - - pub fn via_bot(mut self, val: User) -> Self { - self.via_bot = Some(val); - self - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[serde(untagged)] -#[non_exhaustive] -pub enum MessageKind { - Common(MessageCommon), - NewChatMembers(MessageNewChatMembers), - LeftChatMember(MessageLeftChatMember), - NewChatTitle(MessageNewChatTitle), - NewChatPhoto(MessageNewChatPhoto), - DeleteChatPhoto(MessageDeleteChatPhoto), - GroupChatCreated(MessageGroupChatCreated), - SupergroupChatCreated(MessageSupergroupChatCreated), - ChannelChatCreated(MessageChannelChatCreated), - Migrate(MessageMigrate), - Pinned(MessagePinned), - Invoice(MessageInvoice), - SuccessfulPayment(MessageSuccessfulPayment), - ConnectedWebsite(MessageConnectedWebsite), - PassportData(MessagePassportData), - Dice(MessageDice), -} - -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MessageCommon { - /// Sender, empty for messages sent to channels. - pub from: Option, - - #[serde(flatten)] - pub forward_kind: ForwardKind, - - /// Date the message was last edited in Unix time. - pub edit_date: Option, - - #[serde(flatten)] - pub media_kind: MediaKind, - - /// Inline keyboard attached to the message. `login_url` buttons are - /// represented as ordinary `url` buttons. - pub reply_markup: Option, -} - -impl MessageCommon { - pub fn new(forward_kind: ForwardKind, media_kind: MediaKind) -> Self { - Self { from: None, forward_kind, edit_date: None, media_kind, reply_markup: None } - } - - pub fn from(mut self, val: User) -> Self { - self.from = Some(val); - self - } - - pub fn forward_kind(mut self, val: ForwardKind) -> Self { - self.forward_kind = val; - self - } - - pub fn edit_date(mut self, val: i32) -> Self { - self.edit_date = Some(val); - self - } - - pub fn media_kind(mut self, val: MediaKind) -> Self { - self.media_kind = val; - self - } - - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MessageNewChatMembers { - /// New members that were added to the group or supergroup and - /// information about them (the bot itself may be one of these - /// members). - pub new_chat_members: Vec, -} - -impl MessageNewChatMembers { - pub fn new(new_chat_members: N) -> Self - where - N: Into>, - { - Self { new_chat_members: new_chat_members.into() } - } - - pub fn new_chat_members(mut self, val: N) -> Self - where - N: Into>, - { - self.new_chat_members = val.into(); - self - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MessageLeftChatMember { - /// A member was removed from the group, information about them (this - /// member may be the bot itself). - pub left_chat_member: User, -} - -impl MessageLeftChatMember { - pub fn new(left_chat_member: N) -> Self - where - N: Into, - { - Self { left_chat_member: left_chat_member.into() } - } - - pub fn left_chat_member(mut self, val: N) -> Self - where - N: Into, - { - self.left_chat_member = val.into(); - self - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MessageNewChatTitle { - /// A chat title was changed to this value. - pub new_chat_title: String, -} - -impl MessageNewChatTitle { - pub fn new(new_chat_title: N) -> Self - where - N: Into, - { - Self { new_chat_title: new_chat_title.into() } - } - - pub fn new_chat_title(mut self, val: N) -> Self - where - N: Into, - { - self.new_chat_title = val.into(); - self - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MessageNewChatPhoto { - /// A chat photo was change to this value. - pub new_chat_photo: Vec, -} - -impl MessageNewChatPhoto { - pub fn new(new_chat_photo: N) -> Self - where - N: Into>, - { - Self { new_chat_photo: new_chat_photo.into() } - } - - pub fn new_chat_photo(mut self, val: N) -> Self - where - N: Into>, - { - self.new_chat_photo = val.into(); - self - } -} - -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MessageDeleteChatPhoto { - /// Service message: the chat photo was deleted. - pub delete_chat_photo: True, -} - -impl MessageDeleteChatPhoto { - pub fn new() -> Self { - Self::default() - } -} - -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MessageGroupChatCreated { - /// Service message: the group has been created. - pub group_chat_created: True, -} - -impl MessageGroupChatCreated { - pub fn new() -> Self { - Self::default() - } -} - -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MessageSupergroupChatCreated { - /// Service message: the supergroup has been created. This field can‘t - /// be received in a message coming through updates, because bot can’t - /// be a member of a supergroup when it is created. It can only be - /// found in `reply_to_message` if someone replies to a very first - /// message in a directly created supergroup. - pub supergroup_chat_created: True, -} - -impl MessageSupergroupChatCreated { - pub fn new() -> Self { - Self::default() - } -} - -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MessageChannelChatCreated { - /// Service message: the channel has been created. This field can‘t be - /// received in a message coming through updates, because bot can’t be - /// a member of a channel when it is created. It can only be found in - /// `reply_to_message` if someone replies to a very first message in a - /// channel. - pub channel_chat_created: True, -} - -impl MessageChannelChatCreated { - pub fn new() -> Self { - Self::default() - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MessageMigrate { - /// The group has been migrated to a supergroup with the specified - /// identifier. This number may be greater than 32 bits and some - /// programming languages may have difficulty/silent defects in - /// interpreting it. But it is smaller than 52 bits, so a signed 64 bit - /// integer or double-precision float type are safe for storing this - /// identifier. - pub migrate_to_chat_id: i64, - - /// The supergroup has been migrated from a group with the specified - /// identifier. This number may be greater than 32 bits and some - /// programming languages may have difficulty/silent defects in - /// interpreting it. But it is smaller than 52 bits, so a signed 64 bit - /// integer or double-precision float type are safe for storing this - /// identifier. - pub migrate_from_chat_id: i64, -} - -impl MessageMigrate { - pub fn new(migrate_to_chat_id: i64, migrate_from_chat_id: i64) -> Self { - Self { migrate_to_chat_id, migrate_from_chat_id } - } - - pub fn migrate_to_chat_id(mut self, val: i64) -> Self { - self.migrate_to_chat_id = val; - self - } - - pub fn migrate_from_chat_id(mut self, val: i64) -> Self { - self.migrate_from_chat_id = val; - self - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MessagePinned { - /// Specified message was pinned. Note that the Message object in this - /// field will not contain further `reply_to_message` fields even if it - /// is itself a reply. - #[serde(rename = "pinned_message")] - pub pinned: Box, -} - -impl MessagePinned { - pub fn new(pinned: Message) -> Self { - Self { pinned: Box::new(pinned) } - } - - pub fn pinned(mut self, val: Message) -> Self { - self.pinned = Box::new(val); - self - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MessageInvoice { - /// Message is an invoice for a [payment], information about the - /// invoice. [More about payments »]. - /// - /// [payment]: https://core.telegram.org/bots/api#payments - /// [More about payments »]: https://core.telegram.org/bots/api#payments - pub invoice: Invoice, -} - -impl MessageInvoice { - pub fn new(invoice: Invoice) -> Self { - Self { invoice } - } - - pub fn invoice(mut self, val: Invoice) -> Self { - self.invoice = val; - self - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MessageSuccessfulPayment { - /// Message is a service message about a successful payment, - /// information about the payment. [More about payments »]. - /// - /// [More about payments »]: https://core.telegram.org/bots/api#payments - pub successful_payment: SuccessfulPayment, -} - -impl MessageSuccessfulPayment { - pub fn new(successful_payment: SuccessfulPayment) -> Self { - Self { successful_payment } - } - - pub fn successful_payment(mut self, val: SuccessfulPayment) -> Self { - self.successful_payment = val; - self - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MessageConnectedWebsite { - /// The domain name of the website on which the user has logged in. - /// [More about Telegram Login »]. - /// - /// [More about Telegram Login »]: https://core.telegram.org/widgets/login - pub connected_website: String, -} - -impl MessageConnectedWebsite { - pub fn new(connected_website: S) -> Self - where - S: Into, - { - Self { connected_website: connected_website.into() } - } - - pub fn connected_website(mut self, val: S) -> Self - where - S: Into, - { - self.connected_website = val.into(); - self - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MessagePassportData { - /// Telegram Passport data. - pub passport_data: PassportData, -} - -impl MessagePassportData { - pub fn new(passport_data: PassportData) -> Self { - Self { passport_data } - } - - pub fn passport_data(mut self, val: PassportData) -> Self { - self.passport_data = val; - self - } -} - -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub enum ForwardedFrom { - #[serde(rename = "forward_from")] - User(User), - #[serde(rename = "forward_sender_name")] - SenderName(String), -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[serde(untagged)] -#[non_exhaustive] -pub enum ForwardKind { - Channel(ForwardChannel), - NonChannel(ForwardNonChannel), - Origin(ForwardOrigin), -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct ForwardChannel { - #[serde(rename = "forward_date")] - pub date: i32, - - #[serde(rename = "forward_from_chat")] - pub chat: Chat, - - #[serde(rename = "forward_from_message_id")] - pub message_id: i32, - - #[serde(rename = "forward_signature")] - pub signature: Option, -} - -impl ForwardChannel { - pub fn new(date: i32, chat: Chat, message_id: i32) -> Self { - Self { date, chat, message_id, signature: None } - } - - pub fn date(mut self, val: i32) -> Self { - self.date = val; - self - } - - pub fn chat(mut self, val: Chat) -> Self { - self.chat = val; - self - } - - pub fn message_id(mut self, val: i32) -> Self { - self.message_id = val; - self - } - - pub fn signature(mut self, val: S) -> Self - where - S: Into, - { - self.signature = Some(val.into()); - self - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct ForwardNonChannel { - #[serde(rename = "forward_date")] - pub date: i32, - - #[serde(flatten)] - pub from: ForwardedFrom, -} - -impl ForwardNonChannel { - pub fn new(date: i32, from: ForwardedFrom) -> Self { - Self { date, from } - } - - pub fn date(mut self, val: i32) -> Self { - self.date = val; - self - } - - pub fn from(mut self, val: ForwardedFrom) -> Self { - self.from = val; - self - } -} - -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct ForwardOrigin { - pub reply_to_message: Option>, -} - -impl ForwardOrigin { - pub fn new() -> Self { - Self::default() - } - - pub fn reply_to_message(mut self, val: Message) -> Self { - self.reply_to_message = Some(Box::new(val)); - self - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[serde(untagged)] -#[non_exhaustive] -pub enum MediaKind { - Animation(MediaAnimation), - Audio(MediaAudio), - Contact(MediaContact), - Document(MediaDocument), - Game(MediaGame), - Location(MediaLocation), - Photo(MediaPhoto), - Poll(MediaPoll), - Sticker(MediaSticker), - Text(MediaText), - Video(MediaVideo), - VideoNote(MediaVideoNote), - Voice(MediaVoice), - Venue(MediaVenue), -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MediaAnimation { - /// Message is an animation, information about the animation. For - /// backward compatibility, when this field is set, the document field - /// will also be set. - pub animation: Animation, - - #[doc(hidden)] - /// "For backward compatibility" (c) Telegram Docs. - #[serde(skip)] - pub document: (), - - /// Caption for the animation, 0-1024 characters. - pub caption: Option, - - /// For messages with a caption, special entities like usernames, URLs, - /// bot commands, etc. that appear in the caption. - #[serde(default = "Vec::new")] - pub caption_entities: Vec, -} - -impl MediaAnimation { - pub fn new(animation: Animation, caption_entities: CE) -> Self - where - CE: Into>, - { - Self { animation, document: (), caption: None, caption_entities: caption_entities.into() } - } - - pub fn animation(mut self, val: Animation) -> Self { - self.animation = val; - self - } - - pub fn caption(mut self, val: S) -> Self - where - S: Into, - { - self.caption = Some(val.into()); - self - } - - pub fn caption_entities(mut self, val: CE) -> Self - where - CE: Into>, - { - self.caption_entities = val.into(); - self - } -} - -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MediaAudio { - /// Message is an audio file, information about the file. - pub audio: Audio, - - /// Caption for the audio, 0-1024 characters. - pub caption: Option, - - /// For messages with a caption, special entities like usernames, URLs, - /// bot commands, etc. that appear in the caption. - #[serde(default = "Vec::new")] - pub caption_entities: Vec, -} - -impl MediaAudio { - pub fn new(audio: Audio, caption_entities: CE) -> Self - where - CE: Into>, - { - Self { audio, caption: None, caption_entities: caption_entities.into() } - } - - pub fn audio(mut self, val: Audio) -> Self { - self.audio = val; - self - } - - pub fn caption(mut self, val: S) -> Self - where - S: Into, - { - self.caption = Some(val.into()); - self - } - - pub fn caption_entities(mut self, val: CE) -> Self - where - CE: Into>, - { - self.caption_entities = val.into(); - self - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MediaContact { - /// Message is a shared contact, information about the contact. - contact: Contact, -} - -impl MediaContact { - pub fn new(contact: Contact) -> Self { - Self { contact } - } - - pub fn contact(mut self, val: Contact) -> Self { - self.contact = val; - self - } -} - -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MediaDocument { - /// Message is a general file, information about the file. - pub document: Document, - - /// Caption for the document, 0-1024 characters. - pub caption: Option, - - /// For messages with a caption, special entities like usernames, URLs, - /// bot commands, etc. that appear in the caption. - #[serde(default = "Vec::new")] - pub caption_entities: Vec, -} - -impl MediaDocument { - pub fn new(document: Document, caption_entities: CE) -> Self - where - CE: Into>, - { - Self { document, caption: None, caption_entities: caption_entities.into() } - } - - pub fn document(mut self, val: Document) -> Self { - self.document = val; - self - } - - pub fn caption(mut self, val: S) -> Self - where - S: Into, - { - self.caption = Some(val.into()); - self - } - - pub fn caption_entities(mut self, val: CE) -> Self - where - CE: Into>, - { - self.caption_entities = val.into(); - self - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MediaGame { - /// Message is a game, information about the game. [More - /// about games »]. - /// - /// [More about games »]: https://core.telegram.org/bots/api#games - pub game: Game, -} - -impl MediaGame { - pub fn new(game: Game) -> Self { - Self { game } - } - - pub fn game(mut self, val: Game) -> Self { - self.game = val; - self - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MediaLocation { - /// Message is a shared location, information about the location. - pub location: Location, -} - -impl MediaLocation { - pub fn new(location: Location) -> Self { - Self { location } - } - - pub fn location(mut self, val: Location) -> Self { - self.location = val; - self - } -} - -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MediaPhoto { - /// Message is a photo, available sizes of the photo. - pub photo: Vec, - - /// Caption for the photo, 0-1024 characters. - pub caption: Option, - - /// For messages with a caption, special entities like usernames, URLs, - /// bot commands, etc. that appear in the caption. - #[serde(default = "Vec::new")] - pub caption_entities: Vec, - - /// The unique identifier of a media message group this message belongs - /// to. - pub media_group_id: Option, -} - -impl MediaPhoto { - pub fn new(photo: P, caption_entities: CE) -> Self - where - P: Into>, - CE: Into>, - { - Self { - photo: photo.into(), - caption: None, - caption_entities: caption_entities.into(), - media_group_id: None, - } - } - - pub fn photo

(mut self, val: P) -> Self - where - P: Into>, - { - self.photo = val.into(); - self - } - - pub fn caption(mut self, val: S) -> Self - where - S: Into, - { - self.caption = Some(val.into()); - self - } - - pub fn caption_entities(mut self, val: CE) -> Self - where - CE: Into>, - { - self.caption_entities = val.into(); - self - } - - pub fn media_group_id(mut self, val: S) -> Self - where - S: Into, - { - self.media_group_id = Some(val.into()); - self - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MediaPoll { - /// Message is a native poll, information about the poll. - pub poll: Poll, -} - -impl MediaPoll { - pub fn new(poll: Poll) -> Self { - Self { poll } - } - - pub fn poll(mut self, val: Poll) -> Self { - self.poll = val; - self - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MediaSticker { - /// Message is a sticker, information about the sticker. - pub sticker: Sticker, -} - -impl MediaSticker { - pub fn new(sticker: Sticker) -> Self { - Self { sticker } - } - - pub fn poll(mut self, val: Sticker) -> Self { - self.sticker = val; - self - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MediaText { - /// For text messages, the actual UTF-8 text of the message, 0-4096 - /// characters. - pub text: String, - - /// For text messages, special entities like usernames, URLs, bot - /// commands, etc. that appear in the text. - #[serde(default = "Vec::new")] - pub entities: Vec, -} - -impl MediaText { - pub fn new(text: S, entities: E) -> Self - where - S: Into, - E: Into>, - { - Self { text: text.into(), entities: entities.into() } - } - - pub fn text(mut self, val: S) -> Self - where - S: Into, - { - self.text = val.into(); - self - } - - pub fn entities(mut self, val: CE) -> Self - where - CE: Into>, - { - self.entities = val.into(); - self - } -} - -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MediaVideo { - /// Message is a video, information about the video. - pub video: Video, - - /// Caption for the video, 0-1024 characters. - pub caption: Option, - - /// For messages with a caption, special entities like usernames, URLs, - /// bot commands, etc. that appear in the caption. - #[serde(default = "Vec::new")] - pub caption_entities: Vec, - - /// The unique identifier of a media message group this message belongs - /// to. - pub media_group_id: Option, -} - -impl MediaVideo { - pub fn new(video: Video, caption_entities: CE) -> Self - where - CE: Into>, - { - Self { - video, - caption: None, - caption_entities: caption_entities.into(), - media_group_id: None, - } - } - - pub fn video(mut self, val: Video) -> Self { - self.video = val; - self - } - - pub fn caption(mut self, val: S) -> Self - where - S: Into, - { - self.caption = Some(val.into()); - self - } - - pub fn caption_entities(mut self, val: CE) -> Self - where - CE: Into>, - { - self.caption_entities = val.into(); - self - } - - pub fn media_group_id(mut self, val: S) -> Self - where - S: Into, - { - self.media_group_id = Some(val.into()); - self - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MediaVideoNote { - /// Message is a [video note], information about the video message. - /// - /// [video note]: https://telegram.org/blog/video-messages-and-telescope - pub video_note: VideoNote, -} - -impl MediaVideoNote { - pub fn new(video_note: VideoNote) -> Self { - Self { video_note } - } - - pub fn video_note(mut self, val: VideoNote) -> Self { - self.video_note = val; - self - } -} - -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MediaVoice { - /// Message is a voice message, information about the file. - pub voice: Voice, - - /// Caption for the voice, 0-1024 characters. - pub caption: Option, - - /// For messages with a caption, special entities like usernames, URLs, - /// bot commands, etc. that appear in the caption. - #[serde(default = "Vec::new")] - pub caption_entities: Vec, -} - -impl MediaVoice { - pub fn new(voice: Voice, caption_entities: CE) -> Self - where - CE: Into>, - { - Self { voice, caption: None, caption_entities: caption_entities.into() } - } - - pub fn voice(mut self, val: Voice) -> Self { - self.voice = val; - self - } - - pub fn caption(mut self, val: S) -> Self - where - S: Into, - { - self.caption = Some(val.into()); - self - } - - pub fn caption_entities(mut self, val: CE) -> Self - where - CE: Into>, - { - self.caption_entities = val.into(); - self - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MediaVenue { - /// Message is a venue, information about the venue. - pub venue: Venue, -} - -impl MediaVenue { - pub fn new(venue: Venue) -> Self { - Self { venue } - } - - pub fn venue(mut self, val: Venue) -> Self { - self.venue = val; - self - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MessageDice { - /// Message is a dice with random value from 1 to 6. - pub dice: Dice, -} - -mod getters { - use std::ops::Deref; - - use crate::types::{ - self, - message::{ - ForwardKind::NonChannel, - MessageKind::{ - ChannelChatCreated, Common, ConnectedWebsite, DeleteChatPhoto, GroupChatCreated, - Invoice, LeftChatMember, Migrate, NewChatMembers, NewChatPhoto, NewChatTitle, - PassportData, Pinned, SuccessfulPayment, SupergroupChatCreated, - }, - }, - Chat, ForwardChannel, ForwardKind, ForwardNonChannel, ForwardOrigin, ForwardedFrom, - MediaAnimation, MediaAudio, MediaContact, MediaDocument, MediaGame, MediaKind, - MediaLocation, MediaPhoto, MediaPoll, MediaSticker, MediaText, MediaVenue, MediaVideo, - MediaVideoNote, MediaVoice, Message, MessageChannelChatCreated, MessageCommon, - MessageConnectedWebsite, MessageDeleteChatPhoto, MessageEntity, MessageGroupChatCreated, - MessageInvoice, MessageLeftChatMember, MessageMigrate, MessageNewChatMembers, - MessageNewChatPhoto, MessageNewChatTitle, MessagePassportData, MessagePinned, - MessageSuccessfulPayment, MessageSupergroupChatCreated, PhotoSize, True, User, - }; - - /// Getters for [Message] fields from [telegram docs]. - /// - /// [Message]: crate::types::Message - /// [telegram docs]: https://core.telegram.org/bots/api#message - impl Message { - /// NOTE: this is getter for both `from` and `author_signature` - pub fn from(&self) -> Option<&User> { - match &self.kind { - Common(MessageCommon { from, .. }) => from.as_ref(), - _ => None, - } - } - - pub fn chat_id(&self) -> i64 { - self.chat.id - } - - /// NOTE: this is getter for both `forward_from` and - /// `forward_sender_name` - pub fn forward_from(&self) -> Option<&ForwardedFrom> { - match &self.kind { - Common(MessageCommon { - forward_kind: NonChannel(ForwardNonChannel { from, .. }), - .. - }) => Some(from), - _ => None, - } - } - - pub fn forward_from_chat(&self) -> Option<&Chat> { - match &self.kind { - Common(MessageCommon { - forward_kind: ForwardKind::Channel(ForwardChannel { chat, .. }), - .. - }) => Some(chat), - _ => None, - } - } - - pub fn forward_from_message_id(&self) -> Option<&i32> { - match &self.kind { - Common(MessageCommon { - forward_kind: ForwardKind::Channel(ForwardChannel { message_id, .. }), - .. - }) => Some(message_id), - _ => None, - } - } - - pub fn forward_signature(&self) -> Option<&str> { - match &self.kind { - Common(MessageCommon { - forward_kind: ForwardKind::Channel(ForwardChannel { signature, .. }), - .. - }) => signature.as_ref().map(Deref::deref), - _ => None, - } - } - - pub fn forward_date(&self) -> Option<&i32> { - match &self.kind { - Common(MessageCommon { - forward_kind: ForwardKind::Channel(ForwardChannel { date, .. }), - .. - }) - | Common(MessageCommon { - forward_kind: ForwardKind::NonChannel(ForwardNonChannel { date, .. }), - .. - }) => Some(date), - _ => None, - } - } - - pub fn reply_to_message(&self) -> Option<&Message> { - match &self.kind { - Common(MessageCommon { - forward_kind: ForwardKind::Origin(ForwardOrigin { reply_to_message, .. }), - .. - }) => reply_to_message.as_ref().map(Deref::deref), - _ => None, - } - } - - pub fn edit_date(&self) -> Option<&i32> { - match &self.kind { - Common(MessageCommon { edit_date, .. }) => edit_date.as_ref(), - _ => None, - } - } - - pub fn media_group_id(&self) -> Option<&str> { - match &self.kind { - Common(MessageCommon { - media_kind: MediaKind::Video(MediaVideo { media_group_id, .. }), - .. - }) - | Common(MessageCommon { - media_kind: MediaKind::Photo(MediaPhoto { media_group_id, .. }), - .. - }) => media_group_id.as_ref().map(Deref::deref), - _ => None, - } - } - - pub fn text(&self) -> Option<&str> { - match &self.kind { - Common(MessageCommon { - media_kind: MediaKind::Text(MediaText { text, .. }), - .. - }) => Some(text), - _ => None, - } - } - - pub fn text_owned(&self) -> Option { - self.text().map(ToOwned::to_owned) - } - - pub fn entities(&self) -> Option<&[MessageEntity]> { - match &self.kind { - Common(MessageCommon { - media_kind: MediaKind::Text(MediaText { entities, .. }), - .. - }) => Some(entities), - _ => None, - } - } - - pub fn caption_entities(&self) -> Option<&[MessageEntity]> { - match &self.kind { - Common(MessageCommon { - media_kind: MediaKind::Animation(MediaAnimation { caption_entities, .. }), - .. - }) - | Common(MessageCommon { - media_kind: MediaKind::Audio(MediaAudio { caption_entities, .. }), - .. - }) - | Common(MessageCommon { - media_kind: MediaKind::Document(MediaDocument { caption_entities, .. }), - .. - }) - | Common(MessageCommon { - media_kind: MediaKind::Photo(MediaPhoto { caption_entities, .. }), - .. - }) - | Common(MessageCommon { - media_kind: MediaKind::Video(MediaVideo { caption_entities, .. }), - .. - }) - | Common(MessageCommon { - media_kind: MediaKind::Voice(MediaVoice { caption_entities, .. }), - .. - }) => Some(caption_entities), - _ => None, - } - } - - pub fn audio(&self) -> Option<&types::Audio> { - match &self.kind { - Common(MessageCommon { - media_kind: MediaKind::Audio(MediaAudio { audio, .. }), - .. - }) => Some(audio), - _ => None, - } - } - - pub fn document(&self) -> Option<&types::Document> { - match &self.kind { - Common(MessageCommon { - media_kind: MediaKind::Document(MediaDocument { document, .. }), - .. - }) => Some(document), - _ => None, - } - } - - pub fn animation(&self) -> Option<&types::Animation> { - match &self.kind { - Common(MessageCommon { - media_kind: MediaKind::Animation(MediaAnimation { animation, .. }), - .. - }) => Some(animation), - _ => None, - } - } - - pub fn game(&self) -> Option<&types::Game> { - match &self.kind { - Common(MessageCommon { - media_kind: MediaKind::Game(MediaGame { game, .. }), - .. - }) => Some(game), - _ => None, - } - } - - pub fn photo(&self) -> Option<&[PhotoSize]> { - match &self.kind { - Common(MessageCommon { - media_kind: MediaKind::Photo(MediaPhoto { photo, .. }), - .. - }) => Some(photo), - _ => None, - } - } - - pub fn sticker(&self) -> Option<&types::Sticker> { - match &self.kind { - Common(MessageCommon { - media_kind: MediaKind::Sticker(MediaSticker { sticker, .. }), - .. - }) => Some(sticker), - _ => None, - } - } - - pub fn video(&self) -> Option<&types::Video> { - match &self.kind { - Common(MessageCommon { - media_kind: MediaKind::Video(MediaVideo { video, .. }), - .. - }) => Some(video), - _ => None, - } - } - - pub fn voice(&self) -> Option<&types::Voice> { - match &self.kind { - Common(MessageCommon { - media_kind: MediaKind::Voice(MediaVoice { voice, .. }), - .. - }) => Some(voice), - _ => None, - } - } - - pub fn video_note(&self) -> Option<&types::VideoNote> { - match &self.kind { - Common(MessageCommon { - media_kind: MediaKind::VideoNote(MediaVideoNote { video_note, .. }), - .. - }) => Some(video_note), - _ => None, - } - } - - pub fn caption(&self) -> Option<&str> { - match &self.kind { - Common(MessageCommon { media_kind, .. }) => match media_kind { - MediaKind::Animation(MediaAnimation { caption, .. }) - | MediaKind::Audio(MediaAudio { caption, .. }) - | MediaKind::Document(MediaDocument { caption, .. }) - | MediaKind::Photo(MediaPhoto { caption, .. }) - | MediaKind::Video(MediaVideo { caption, .. }) - | MediaKind::Voice(MediaVoice { caption, .. }) => { - caption.as_ref().map(Deref::deref) - } - _ => None, - }, - _ => None, - } - } - - pub fn contact(&self) -> Option<&types::Contact> { - match &self.kind { - Common(MessageCommon { - media_kind: MediaKind::Contact(MediaContact { contact, .. }), - .. - }) => Some(contact), - _ => None, - } - } - - pub fn location(&self) -> Option<&types::Location> { - match &self.kind { - Common(MessageCommon { - media_kind: MediaKind::Location(MediaLocation { location, .. }), - .. - }) => Some(location), - _ => None, - } - } - - pub fn venue(&self) -> Option<&types::Venue> { - match &self.kind { - Common(MessageCommon { - media_kind: MediaKind::Venue(MediaVenue { venue, .. }), - .. - }) => Some(venue), - _ => None, - } - } - - pub fn poll(&self) -> Option<&types::Poll> { - match &self.kind { - Common(MessageCommon { - media_kind: MediaKind::Poll(MediaPoll { poll, .. }), - .. - }) => Some(poll), - _ => None, - } - } - - pub fn new_chat_members(&self) -> Option<&[User]> { - match &self.kind { - NewChatMembers(MessageNewChatMembers { new_chat_members }) => { - Some(new_chat_members.as_ref()) - } - _ => None, - } - } - - pub fn left_chat_member(&self) -> Option<&User> { - match &self.kind { - LeftChatMember(MessageLeftChatMember { left_chat_member }) => { - Some(left_chat_member) - } - _ => None, - } - } - - pub fn new_chat_title(&self) -> Option<&str> { - match &self.kind { - NewChatTitle(MessageNewChatTitle { new_chat_title }) => Some(new_chat_title), - _ => None, - } - } - - pub fn new_chat_photo(&self) -> Option<&[PhotoSize]> { - match &self.kind { - NewChatPhoto(MessageNewChatPhoto { new_chat_photo }) => Some(new_chat_photo), - _ => None, - } - } - - // TODO: OK, `Option` is weird, can we do something with it? - // mb smt like `is_delete_chat_photo(&self) -> bool`? - pub fn delete_chat_photo(&self) -> Option { - match &self.kind { - DeleteChatPhoto(MessageDeleteChatPhoto { delete_chat_photo }) => { - Some(*delete_chat_photo) - } - _ => None, - } - } - - pub fn group_chat_created(&self) -> Option { - match &self.kind { - GroupChatCreated(MessageGroupChatCreated { group_chat_created }) => { - Some(*group_chat_created) - } - _ => None, - } - } - - pub fn super_group_chat_created(&self) -> Option { - match &self.kind { - SupergroupChatCreated(MessageSupergroupChatCreated { supergroup_chat_created }) => { - Some(*supergroup_chat_created) - } - _ => None, - } - } - - pub fn channel_chat_created(&self) -> Option { - match &self.kind { - ChannelChatCreated(MessageChannelChatCreated { channel_chat_created }) => { - Some(*channel_chat_created) - } - _ => None, - } - } - - pub fn migrate_to_chat_id(&self) -> Option { - match &self.kind { - Migrate(MessageMigrate { migrate_to_chat_id, .. }) => Some(*migrate_to_chat_id), - _ => None, - } - } - - pub fn migrate_from_chat_id(&self) -> Option { - match &self.kind { - Migrate(MessageMigrate { migrate_from_chat_id, .. }) => Some(*migrate_from_chat_id), - _ => None, - } - } - - pub fn pinned_message(&self) -> Option<&Message> { - match &self.kind { - Pinned(MessagePinned { pinned }) => Some(pinned), - _ => None, - } - } - - pub fn invoice(&self) -> Option<&types::Invoice> { - match &self.kind { - Invoice(MessageInvoice { invoice }) => Some(invoice), - _ => None, - } - } - - pub fn successful_payment(&self) -> Option<&types::SuccessfulPayment> { - match &self.kind { - SuccessfulPayment(MessageSuccessfulPayment { successful_payment }) => { - Some(successful_payment) - } - _ => None, - } - } - - pub fn connected_website(&self) -> Option<&str> { - match &self.kind { - ConnectedWebsite(MessageConnectedWebsite { connected_website }) => { - Some(connected_website) - } - _ => None, - } - } - - pub fn passport_data(&self) -> Option<&types::PassportData> { - match &self.kind { - PassportData(MessagePassportData { passport_data }) => Some(passport_data), - _ => None, - } - } - - pub fn reply_markup(&self) -> Option<&types::InlineKeyboardMarkup> { - match &self.kind { - Common(MessageCommon { reply_markup, .. }) => reply_markup.as_ref(), - _ => None, - } - } - } -} - -impl Message { - pub fn url(&self) -> Option { - match &self.chat.kind { - ChatKind::Public(ChatPublic { - kind: PublicChatKind::Channel(PublicChatChannel { username: Some(username) }), - .. - }) - | ChatKind::Public(ChatPublic { - kind: - PublicChatKind::Supergroup(PublicChatSupergroup { - username: Some(username), .. - }), - .. - }) => Some( - reqwest::Url::parse(format!("https://t.me/{0}/{1}/", username, self.id).as_str()) - .unwrap(), - ), - _ => None, - } - } -} - -#[cfg(test)] -mod tests { - use serde_json::from_str; - - use crate::types::*; - - #[test] - fn de_media_forwarded() { - let json = r#"{ - "message_id": 198283, - "from": { - "id": 250918540, - "is_bot": false, - "first_name": "Андрей", - "last_name": "Власов", - "username": "aka_dude", - "language_code": "en" - }, - "chat": { - "id": 250918540, - "first_name": "Андрей", - "last_name": "Власов", - "username": "aka_dude", - "type": "private" - }, - "date": 1567927221, - "video": { - "duration": 13, - "width": 512, - "height": 640, - "mime_type": "video/mp4", - "thumb": { - "file_id": "AAQCAAOmBAACBf2oS53pByA-I4CWWCObDwAEAQAHbQADMWcAAhYE", - "file_unique_id":"", - "file_size": 10339, - "width": 256, - "height": 320 - }, - "file_id": "BAADAgADpgQAAgX9qEud6QcgPiOAlhYE", - "file_unique_id":"", - "file_size": 1381334 - } - }"#; - let message = from_str::(json); - assert!(message.is_ok()); - } - - #[test] - fn de_media_group_forwarded() { - let json = r#"{ - "message_id": 198283, - "from": { - "id": 250918540, - "is_bot": false, - "first_name": "Андрей", - "last_name": "Власов", - "username": "aka_dude", - "language_code": "en" - }, - "chat": { - "id": 250918540, - "first_name": "Андрей", - "last_name": "Власов", - "username": "aka_dude", - "type": "private" - }, - "date": 1567927221, - "media_group_id": "12543417770506682", - "video": { - "duration": 13, - "width": 512, - "height": 640, - "mime_type": "video/mp4", - "thumb": { - "file_id": "AAQCAAOmBAACBf2oS53pByA-I4CWWCObDwAEAQAHbQADMWcAAhYE", - "file_unique_id":"", - "file_size": 10339, - "width": 256, - "height": 320 - }, - "file_id": "BAADAgADpgQAAgX9qEud6QcgPiOAlhYE", - "file_unique_id":"", - "file_size": 1381334 - } - }"#; - let message = from_str::(json); - assert!(message.is_ok()); - } - - #[test] - fn de_text() { - let json = r#"{ - "message_id": 199785, - "from": { - "id": 250918540, - "is_bot": false, - "first_name": "Андрей", - "last_name": "Власов", - "username": "aka_dude", - "language_code": "en" - }, - "chat": { - "id": 250918540, - "first_name": "Андрей", - "last_name": "Власов", - "username": "aka_dude", - "type": "private" - }, - "date": 1568289890, - "text": "Лол кек 😂" - }"#; - let message = from_str::(json); - assert!(message.is_ok()); - } - - #[test] - fn de_sticker() { - let json = r#"{ - "message_id": 199787, - "from": { - "id": 250918540, - "is_bot": false, - "first_name": "Андрей", - "last_name": "Власов", - "username": "aka_dude", - "language_code": "en" - }, - "chat": { - "id": 250918540, - "first_name": "Андрей", - "last_name": "Власов", - "username": "aka_dude", - "type": "private" - }, - "date": 1568290188, - "sticker": { - "width": 512, - "height": 512, - "emoji": "😡", - "set_name": "AdvenTimeAnim", - "is_animated": true, - "thumb": { - "file_id": "AAQCAAMjAAOw0PgMaabKAcaXKCBLubkPAAQBAAdtAAPGKwACFgQ", - "file_unique_id":"", - "file_size": 4118, - "width": 128, - "height": 128 - }, - "file_id": "CAADAgADIwADsND4DGmmygHGlyggFgQ", - "file_unique_id":"", - "file_size": 16639 - } - }"#; - let message = from_str::(json); - assert!(message.is_ok()); - } - - #[test] - fn de_image() { - let json = r#"{ - "message_id": 199791, - "from": { - "id": 250918540, - "is_bot": false, - "first_name": "Андрей", - "last_name": "Власов", - "username": "aka_dude", - "language_code": "en" - }, - "chat": { - "id": 250918540, - "first_name": "Андрей", - "last_name": "Власов", - "username": "aka_dude", - "type": "private" - }, - "date": 1568290622, - "photo": [ - { - "file_id": "AgADAgAD36sxG-PX0UvQSXIn9rccdw-ACA4ABAEAAwIAA20AAybcBAABFgQ", - "file_unique_id":"", - "file_size": 18188, - "width": 320, - "height": 239 - }, - { - "file_id": "AgADAgAD36sxG-PX0UvQSXIn9rccdw-ACA4ABAEAAwIAA3gAAyfcBAABFgQ", - "file_unique_id":"", - "file_size": 62123, - "width": 800, - "height": 598 - }, - { - "file_id": "AgADAgAD36sxG-PX0UvQSXIn9rccdw-ACA4ABAEAAwIAA3kAAyTcBAABFgQ", - "file_unique_id":"", - "file_size": 75245, - "width": 962, - "height": 719 - } - ] - }"#; - let message = from_str::(json); - assert!(message.is_ok()); - } -} diff --git a/src/types/message_entity.rs b/src/types/message_entity.rs deleted file mode 100644 index c39459be..00000000 --- a/src/types/message_entity.rs +++ /dev/null @@ -1,162 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{Message, User}; - -/// This object represents one special entity in a text message. -/// -/// For example, hashtags, usernames, URLs, etc. -/// -/// [The official docs](https://core.telegram.org/bots/api#messageentity). -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MessageEntity { - #[serde(flatten)] - pub kind: MessageEntityKind, - - /// Offset in UTF-16 code units to the start of the entity. - pub offset: usize, - - /// Length of the entity in UTF-16 code units. - pub length: usize, -} - -impl MessageEntity { - pub fn new(kind: MessageEntityKind, offset: usize, length: usize) -> Self { - Self { kind, offset, length } - } - - pub fn kind(mut self, val: MessageEntityKind) -> Self { - self.kind = val; - self - } - - pub fn offset(mut self, val: usize) -> Self { - self.offset = val; - self - } - - pub fn length(mut self, val: usize) -> Self { - self.length = val; - self - } -} - -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -#[serde(tag = "type")] -#[non_exhaustive] -pub enum MessageEntityKind { - Mention, - Hashtag, - Cashtag, - BotCommand, - Url, - Email, - PhoneNumber, - Bold, - Italic, - Code, - Pre { language: Option }, - TextLink { url: String }, - TextMention { user: User }, - Underline, - Strikethrough, -} - -impl MessageEntity { - pub fn text_from(&self, message: &Message) -> Option { - let text = message.text(); - Some(String::from(&text?[self.offset..self.offset + self.length])) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::types::{ - Chat, ChatKind, ChatPrivate, ForwardKind, ForwardOrigin, MediaKind, MediaText, - MessageCommon, MessageKind, - }; - - #[test] - fn recursive_kind() { - use serde_json::from_str; - - assert_eq!( - MessageEntity { - kind: MessageEntityKind::TextLink { url: "ya.ru".into() }, - offset: 1, - length: 2, - }, - from_str::( - r#"{"type":"text_link","url":"ya.ru","offset":1,"length":2}"# - ) - .unwrap() - ); - } - - #[test] - fn pre() { - use serde_json::from_str; - - assert_eq!( - MessageEntity { - kind: MessageEntityKind::Pre { language: Some("rust".to_string()) }, - offset: 1, - length: 2, - }, - from_str::( - r#"{"type":"pre","url":"ya.ru","offset":1,"length":2,"language":"rust"}"# - ) - .unwrap() - ); - } - - #[test] - fn text_from() { - let message = message(); - let expected = Some("yes".to_string()); - let entity = message.entities().unwrap()[0].clone(); - let actual = entity.text_from(&message); - assert_eq!(actual, expected); - } - - fn message() -> Message { - Message { - via_bot: None, - id: 0, - date: 0, - chat: Chat { - id: 0, - kind: ChatKind::Private(ChatPrivate { - type_: (), - username: None, - first_name: None, - last_name: None, - }), - photo: None, - }, - kind: MessageKind::Common(MessageCommon { - from: Some(User { - id: 0, - is_bot: false, - first_name: "".to_string(), - last_name: None, - username: None, - language_code: None, - }), - forward_kind: ForwardKind::Origin(ForwardOrigin { reply_to_message: None }), - edit_date: None, - media_kind: MediaKind::Text(MediaText { - text: "no yes no".to_string(), - entities: vec![MessageEntity { - kind: MessageEntityKind::Mention, - offset: 3, - length: 3, - }], - }), - reply_markup: None, - }), - } - } -} diff --git a/src/types/mod.rs b/src/types/mod.rs deleted file mode 100644 index e6f7e5fa..00000000 --- a/src/types/mod.rs +++ /dev/null @@ -1,194 +0,0 @@ -//! API types. - -pub use allowed_update::*; -pub use animation::*; -pub use audio::*; -pub use bot_command::*; -pub use callback_game::*; -pub use callback_query::*; -pub use chat::*; -pub use chat_action::*; -pub use chat_id::*; -pub use chat_member::*; -pub use chat_permissions::*; -pub use chat_photo::*; -pub use chosen_inline_result::*; -pub use contact::*; -pub use dice::*; -pub use dice_emoji::*; -pub use document::*; -pub use encrypted_credentials::*; -pub use encrypted_passport_element::*; -pub use file::*; -pub use force_reply::*; -pub use game::*; -pub use game_high_score::*; -pub use inline_keyboard_button::*; -pub use inline_keyboard_markup::*; -pub use inline_query::*; -pub use inline_query_result::*; -pub use inline_query_result_article::*; -pub use inline_query_result_audio::*; -pub use inline_query_result_cached_audio::*; -pub use inline_query_result_cached_document::*; -pub use inline_query_result_cached_gif::*; -pub use inline_query_result_cached_mpeg4_gif::*; -pub use inline_query_result_cached_photo::*; -pub use inline_query_result_cached_sticker::*; -pub use inline_query_result_cached_video::*; -pub use inline_query_result_cached_voice::*; -pub use inline_query_result_contact::*; -pub use inline_query_result_document::*; -pub use inline_query_result_game::*; -pub use inline_query_result_gif::*; -pub use inline_query_result_location::*; -pub use inline_query_result_mpeg4_gif::*; -pub use inline_query_result_photo::*; -pub use inline_query_result_venue::*; -pub use inline_query_result_video::*; -pub use inline_query_result_voice::*; -pub use input_file::*; -pub use input_media::*; -pub use input_message_content::*; -pub use invoice::*; -pub use keyboard_button::*; -pub use keyboard_button_poll_type::*; -pub use label_price::*; -pub use location::*; -pub use login_url::*; -pub use mask_position::*; -pub use me::*; -pub use message::*; -pub use message_entity::*; -pub use order_info::*; -pub use parse_mode::*; -pub use passport_data::*; -pub use passport_element_error::*; -pub use passport_file::*; -pub use photo_size::*; -pub use poll::*; -pub use poll_answer::*; -pub use poll_type::*; -pub use pre_checkout_query::*; -pub use reply_keyboard_markup::*; -pub use reply_keyboard_remove::*; -pub use reply_markup::*; -pub use response_parameters::*; -pub use send_invoice::*; -pub use shipping_address::*; -pub use shipping_option::*; -pub use shipping_query::*; -pub use sticker::*; -pub use sticker_set::*; -pub use sticker_type::*; -pub use successful_payment::*; -pub use target_message::*; -pub use unit_false::*; -pub use unit_true::*; -pub use update::*; -pub use user::*; -pub use user_profile_photos::*; -pub use venue::*; -pub use video::*; -pub use video_note::*; -pub use voice::*; -pub use webhook_info::*; - -mod allowed_update; -mod animation; -mod audio; -mod bot_command; -mod callback_game; -mod callback_query; -mod chat; -mod chat_action; -mod chat_id; -mod chat_member; -mod chat_permissions; -mod chat_photo; -mod chosen_inline_result; -mod contact; -mod dice; -mod dice_emoji; -mod document; -mod file; -mod force_reply; -mod game; -mod game_high_score; -mod inline_keyboard_button; -mod inline_keyboard_markup; -mod input_file; -mod input_media; -mod input_message_content; -mod invoice; -mod keyboard_button; -mod keyboard_button_poll_type; -mod label_price; -mod location; -mod login_url; -mod mask_position; -mod me; -mod message; -mod message_entity; -mod order_info; -mod parse_mode; -mod photo_size; -mod poll; -mod poll_answer; -mod poll_type; -mod pre_checkout_query; -mod reply_keyboard_markup; -mod reply_keyboard_remove; -mod reply_markup; -mod response_parameters; -mod send_invoice; -mod shipping_address; -mod shipping_option; -mod shipping_query; -mod sticker; -mod sticker_set; -mod sticker_type; -mod successful_payment; -mod target_message; -mod unit_false; -mod unit_true; -mod update; -mod user; -mod user_profile_photos; -mod venue; -mod video; -mod video_note; -mod voice; -mod webhook_info; - -mod inline_query; -mod inline_query_result; -mod inline_query_result_article; -mod inline_query_result_audio; -mod inline_query_result_cached_audio; -mod inline_query_result_cached_document; -mod inline_query_result_cached_gif; -mod inline_query_result_cached_mpeg4_gif; -mod inline_query_result_cached_photo; -mod inline_query_result_cached_sticker; -mod inline_query_result_cached_video; -mod inline_query_result_cached_voice; -mod inline_query_result_contact; -mod inline_query_result_document; -mod inline_query_result_game; -mod inline_query_result_gif; -mod inline_query_result_location; -mod inline_query_result_mpeg4_gif; -mod inline_query_result_photo; -mod inline_query_result_venue; -mod inline_query_result_video; -mod inline_query_result_voice; - -mod encrypted_credentials; -mod encrypted_passport_element; -mod passport_data; -mod passport_element_error; -mod passport_file; - -pub use non_telegram_types::*; -mod non_telegram_types; diff --git a/src/types/non_telegram_types/country_code.rs b/src/types/non_telegram_types/country_code.rs deleted file mode 100644 index 4f7705ac..00000000 --- a/src/types/non_telegram_types/country_code.rs +++ /dev/null @@ -1,254 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -pub enum CountryCode { - AD, - AE, - AF, - AG, - AI, - AL, - AM, - AO, - AQ, - AR, - AS, - AT, - AU, - AW, - AX, - AZ, - BA, - BB, - BD, - BE, - BF, - BG, - BH, - BI, - BJ, - BL, - BM, - BN, - BO, - BQ, - BR, - BS, - BT, - BV, - BW, - BY, - BZ, - CA, - CC, - CD, - CF, - CG, - CH, - CI, - CK, - CL, - CM, - CN, - CO, - CR, - CU, - CV, - CW, - CX, - CY, - CZ, - DE, - DJ, - DK, - DM, - DO, - DZ, - EC, - EE, - EG, - EH, - ER, - ES, - ET, - FI, - FJ, - FK, - FM, - FO, - FR, - GA, - GB, - GD, - GE, - GF, - GG, - GH, - GI, - GL, - GM, - GN, - GP, - GQ, - GR, - GS, - GT, - GU, - GW, - GY, - HK, - HM, - HN, - HR, - HT, - HU, - ID, - IE, - IL, - IM, - IN, - IO, - IQ, - IR, - IS, - IT, - JE, - JM, - JO, - JP, - KE, - KG, - KH, - KI, - KM, - KN, - KP, - KR, - KW, - KY, - KZ, - LA, - LB, - LC, - LI, - LK, - LR, - LS, - LT, - LU, - LV, - LY, - MA, - MC, - MD, - ME, - MF, - MG, - MH, - MK, - ML, - MM, - MN, - MO, - MP, - MQ, - MR, - MS, - MT, - MU, - MV, - MW, - MX, - MY, - MZ, - NA, - NC, - NE, - NF, - NG, - NI, - NL, - NO, - NP, - NR, - NU, - NZ, - OM, - PA, - PE, - PF, - PG, - PH, - PK, - PL, - PM, - PN, - PR, - PS, - PT, - PW, - PY, - QA, - RE, - RO, - RS, - RU, - RW, - SA, - SB, - SC, - SD, - SE, - SG, - SH, - SI, - SJ, - SK, - SL, - SM, - SN, - SO, - SR, - SS, - ST, - SV, - SX, - SY, - SZ, - TC, - TD, - TF, - TG, - TH, - TJ, - TK, - TL, - TM, - TN, - TO, - TR, - TT, - TV, - TW, - TZ, - UA, - UG, - UM, - US, - UY, - UZ, - VA, - VC, - VE, - VG, - VI, - VN, - VU, - WF, - WS, - YE, - YT, - ZA, - ZM, - ZW, -} diff --git a/src/types/non_telegram_types/currency.rs b/src/types/non_telegram_types/currency.rs deleted file mode 100644 index 0cdebce6..00000000 --- a/src/types/non_telegram_types/currency.rs +++ /dev/null @@ -1,89 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -pub enum Currency { - AED, - AFN, - ALL, - AMD, - ARS, - AUD, - AZN, - BAM, - BDT, - BGN, - BND, - BOB, - BRL, - CAD, - CHF, - CLP, - CNY, - COP, - CRC, - CZK, - DKK, - DOP, - DZD, - EGP, - EUR, - GBP, - GEL, - GTQ, - HKD, - HNL, - HRK, - HUF, - IDR, - ILS, - INR, - ISK, - JMD, - JPY, - KES, - KGS, - KRW, - KZT, - LBP, - LKR, - MAD, - MDL, - MNT, - MUR, - MVR, - MXN, - MYR, - MZN, - NGN, - NIO, - NOK, - NPR, - NZD, - PAB, - PEN, - PHP, - PKR, - PLN, - PYG, - QAR, - RON, - RSD, - RUB, - SAR, - SEK, - SGD, - THB, - TJS, - TRY, - TTD, - TWD, - TZS, - UAH, - UGX, - USD, - UYU, - UZS, - VND, - YER, - ZAR, -} diff --git a/src/types/non_telegram_types/mime_wrapper.rs b/src/types/non_telegram_types/mime_wrapper.rs deleted file mode 100644 index 848b3376..00000000 --- a/src/types/non_telegram_types/mime_wrapper.rs +++ /dev/null @@ -1,46 +0,0 @@ -use std::fmt::Formatter; - -use derive_more::From; -use mime::Mime; -use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; - -/// Serializable & deserializable `MIME` wrapper. -#[derive(Clone, Debug, Eq, Hash, PartialEq, From)] -pub struct MimeWrapper(pub Mime); - -impl Serialize for MimeWrapper { - fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> - where - S: Serializer, - { - serializer.serialize_str(self.0.as_ref()) - } -} - -struct MimeVisitor; -impl<'a> Visitor<'a> for MimeVisitor { - type Value = MimeWrapper; - - fn expecting(&self, formatter: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { - formatter.write_str("mime type") - } - - fn visit_str(self, v: &str) -> Result - where - E: serde::de::Error, - { - match v.parse::() { - Ok(mime_type) => Ok(MimeWrapper(mime_type)), - Err(e) => Err(E::custom(e)), - } - } -} - -impl<'de> Deserialize<'de> for MimeWrapper { - fn deserialize(deserializer: D) -> Result>::Error> - where - D: Deserializer<'de>, - { - deserializer.deserialize_str(MimeVisitor) - } -} diff --git a/src/types/non_telegram_types/mod.rs b/src/types/non_telegram_types/mod.rs deleted file mode 100644 index 8add54b6..00000000 --- a/src/types/non_telegram_types/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub use country_code::*; -pub use currency::*; -pub use mime_wrapper::*; - -mod country_code; -mod currency; -mod mime_wrapper; diff --git a/src/types/order_info.rs b/src/types/order_info.rs deleted file mode 100644 index 7f41f7a9..00000000 --- a/src/types/order_info.rs +++ /dev/null @@ -1,72 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::ShippingAddress; - -/// This object represents information about an order. -/// -/// [The official docs](https://core.telegram.org/bots/api#orderinfo). -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct OrderInfo { - /// User's name. - pub name: String, - - /// User's phone number. - pub phone_number: String, - - /// User's email. - pub email: String, - - /// User's shipping address. - pub shipping_address: ShippingAddress, -} - -impl OrderInfo { - pub fn new( - name: S1, - phone_number: S2, - email: S3, - shipping_address: ShippingAddress, - ) -> Self - where - S1: Into, - S2: Into, - S3: Into, - { - Self { - name: name.into(), - phone_number: phone_number.into(), - email: email.into(), - shipping_address, - } - } - - pub fn name(mut self, val: S) -> Self - where - S: Into, - { - self.name = val.into(); - self - } - - pub fn phone_number(mut self, val: S) -> Self - where - S: Into, - { - self.phone_number = val.into(); - self - } - - pub fn email(mut self, val: S) -> Self - where - S: Into, - { - self.email = val.into(); - self - } - - pub fn shipping_address(mut self, val: ShippingAddress) -> Self { - self.shipping_address = val; - self - } -} diff --git a/src/types/parse_mode.rs b/src/types/parse_mode.rs deleted file mode 100644 index 44a0ecff..00000000 --- a/src/types/parse_mode.rs +++ /dev/null @@ -1,189 +0,0 @@ -// see https://github.com/rust-lang/rust/issues/38832 -// (for built ins there no warnings, but for (De)Serialize, there are) -#![allow(deprecated)] - -use std::{ - convert::{TryFrom, TryInto}, - str::FromStr, -}; - -use serde::{Deserialize, Serialize}; - -/// Formatting options. -/// -/// The Bot API supports basic formatting for messages. You can use bold, -/// italic, underlined and strikethrough text, as well as inline links and -/// pre-formatted code in your bots' messages. Telegram clients will render -/// them accordingly. You can use either markdown-style or HTML-style -/// formatting. -/// -/// Note that Telegram clients will display an **alert** to the user before -/// opening an inline link (‘Open this link?’ together with the full URL). -/// -/// Links `tg://user?id=` can be used to mention a user by their ID -/// without using a username. Please note: -/// -/// - These links will work **only** if they are used inside an inline link. For -/// example, they will not work, when used in an inline keyboard button or in -/// a message text. -/// - These mentions are only guaranteed to work if the user has contacted the -/// bot in the past, has sent a callback query to the bot via inline button or -/// is a member in the group where he was mentioned. -/// -/// ## MarkdownV2 style -/// -/// To use this mode, pass [`MarkdownV2`] in the `parse_mode` field. -/// Use the following syntax in your message: -/// ````text -/// *bold \*text* -/// _italic \*text_ -/// __underline__ -/// ~strikethrough~ -/// *bold _italic bold ~italic bold strikethrough~ __underline italic bold___ bold* -/// [inline URL](http://www.example.com/) -/// [inline mention of a user](tg://user?id=123456789) -/// `inline fixed-width code` -/// ``` -/// pre-formatted fixed-width code block -/// ``` -/// ```rust -/// pre-formatted fixed-width code block written in the Rust programming -/// language ``` -/// ```` -/// -/// Please note: -/// - Any character between 1 and 126 inclusively can be escaped anywhere with a -/// preceding '\' character, in which case it is treated as an ordinary -/// character and not a part of the markup. -/// - Inside `pre` and `code` entities, all '`‘ and ’\‘ characters must be -/// escaped with a preceding ’\' character. -/// - Inside `(...)` part of inline link definition, all ')‘ and ’\‘ must be -/// escaped with a preceding ’\' character. -/// - In all other places characters ’_‘, ’*‘, ’[‘, ’]‘, ’(‘, ’)‘, ’~‘, ’`‘, -/// ’>‘, ’#‘, ’+‘, ’+‘, ’-‘, ’|‘, ’{‘, ’}‘, ’.‘, ’!‘ must be escaped with the -/// preceding character ’\'. -/// - In case of ambiguity between `italic` and `underline` entities ‘__’ is -/// always greadily treated from left to right as beginning or end of -/// `underline` entity, so instead of `___italic underline___` use `___italic -/// underline_\r__`, where `\r` is a character with code `13`, which will be -/// ignored. -/// -/// ## HTML style -/// To use this mode, pass [`HTML`] in the `parse_mode` field. -/// The following tags are currently supported: -/// ````text -/// bold, bold -/// italic, italic -/// underline, underline -/// strikethrough, strikethrough, -/// strikethrough bold italic bold italic bold -/// strikethrough underline italic bold bold inline URL -/// inline mention of a user -/// inline fixed-width code -///

pre-formatted fixed-width code block
-///
pre-formatted fixed-width code block
-/// written in the Rust programming language
```` -/// -/// Please note: -/// -/// - Only the tags mentioned above are currently supported. -/// - All `<`, `>` and `&` symbols that are not a part of a tag or an HTML -/// entity must be replaced with the corresponding HTML entities (`<` with -/// `<`, `>` with `>` and `&` with `&`). -/// - All numerical HTML entities are supported. -/// - The API currently supports only the following named HTML entities: `<`, -/// `>`, `&` and `"`. -/// - Use nested `pre` and `code` tags, to define programming language for `pre` -/// entity. -/// - Programming language can't be specified for standalone `code` tags. -/// -/// ## Markdown style -/// This is a legacy mode, retained for backward compatibility. To use this -/// mode, pass [`Markdown`] in the `parse_mode` field. -/// Use the following syntax in your message: -/// ````text -/// *bold text* -/// _italic text_ -/// [inline URL](http://www.example.com/) -/// [inline mention of a user](tg://user?id=123456789) -/// `inline fixed-width code` -/// ```rust -/// pre-formatted fixed-width code block written in the Rust programming -/// language ``` -/// ```` -/// -/// Please note: -/// - Entities must not be nested, use parse mode [`MarkdownV2`] instead. -/// - There is no way to specify underline and strikethrough entities, use parse -/// mode [`MarkdownV2`] instead. -/// - To escape characters ’_‘, ’*‘, ’`‘, ’[‘ outside of an entity, prepend the -/// characters ’\' before them. -/// - Escaping inside entities is not allowed, so entity must be closed first -/// and reopened again: use `_snake_\__case_` for italic `snake_case` and -/// `*2*\**2=4*` for bold `2*2=4`. -/// -/// [`MarkdownV2`]: ParseMode::MarkdownV2 -/// [`HTML`]: ParseMode::HTML -/// [`Markdown`]: ParseMode::Markdown -#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub enum ParseMode { - MarkdownV2, - HTML, - #[deprecated = "This is a legacy mode, retained for backward compatibility. Use `MarkdownV2` \ - instead."] - Markdown, -} - -impl TryFrom<&str> for ParseMode { - type Error = (); - - fn try_from(value: &str) -> Result { - let normalized = value.to_lowercase(); - match normalized.as_ref() { - "html" => Ok(ParseMode::HTML), - "markdown" => Ok(ParseMode::Markdown), - "markdownv2" => Ok(ParseMode::MarkdownV2), - _ => Err(()), - } - } -} - -impl TryFrom for ParseMode { - type Error = (); - - fn try_from(value: String) -> Result { - value.as_str().try_into() - } -} - -impl FromStr for ParseMode { - type Err = (); - - fn from_str(s: &str) -> Result { - s.try_into() - } -} - -#[cfg(test)] -mod tests { - #![allow(deprecated)] - - use super::*; - - #[test] - fn html_serialization() { - let expected_json = String::from(r#""HTML""#); - let actual_json = serde_json::to_string(&ParseMode::HTML).unwrap(); - - assert_eq!(expected_json, actual_json) - } - - #[test] - fn markdown_serialization() { - let expected_json = String::from(r#""Markdown""#); - let actual_json = serde_json::to_string(&ParseMode::Markdown).unwrap(); - - assert_eq!(expected_json, actual_json) - } -} diff --git a/src/types/passport_data.rs b/src/types/passport_data.rs deleted file mode 100644 index 3abaef93..00000000 --- a/src/types/passport_data.rs +++ /dev/null @@ -1,40 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use super::{EncryptedCredentials, EncryptedPassportElement}; - -/// Contains information about Telegram Passport data shared with the bot by the -/// user. -/// -/// [The official docs](https://core.telegram.org/bots/api#passportdata). -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct PassportData { - /// Array with information about documents and other Telegram Passport - /// elements that was shared with the bot. - pub data: Vec, - - /// Encrypted credentials required to decrypt the data. - pub credentials: EncryptedCredentials, -} - -impl PassportData { - pub fn new(data: E, credentials: EncryptedCredentials) -> Self - where - E: Into>, - { - Self { data: data.into(), credentials } - } - - pub fn data(mut self, val: E) -> Self - where - E: Into>, - { - self.data = val.into(); - self - } - - pub fn credentials(mut self, val: EncryptedCredentials) -> Self { - self.credentials = val; - self - } -} diff --git a/src/types/passport_element_error.rs b/src/types/passport_element_error.rs deleted file mode 100644 index 521584e5..00000000 --- a/src/types/passport_element_error.rs +++ /dev/null @@ -1,558 +0,0 @@ -use serde::{Deserialize, Serialize}; - -/// This object represents an error in the Telegram Passport element which was -/// submitted that should be resolved by the user. -/// -/// [The official docs](https://core.telegram.org/bots/api#passportelementerror). -#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct PassportElementError { - /// Error message. - message: String, - - #[serde(flatten)] - kind: PassportElementErrorKind, -} - -impl PassportElementError { - pub fn new(message: S, kind: PassportElementErrorKind) -> Self - where - S: Into, - { - Self { message: message.into(), kind } - } - - pub fn message(mut self, val: S) -> Self - where - S: Into, - { - self.message = val.into(); - self - } - - pub fn kind(mut self, val: PassportElementErrorKind) -> Self { - self.kind = val; - self - } -} - -#[serde(tag = "source")] -#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub enum PassportElementErrorKind { - #[serde(rename = "data")] - DataField(PassportElementErrorDataField), - - #[serde(rename = "snake_case")] - FrontSide(PassportElementErrorFrontSide), - - #[serde(rename = "snake_case")] - ReverseSide(PassportElementErrorReverseSide), - - #[serde(rename = "snake_case")] - Selfie(PassportElementErrorSelfie), - - #[serde(rename = "snake_case")] - File(PassportElementErrorFile), - - #[serde(rename = "snake_case")] - Files(PassportElementErrorFiles), - - #[serde(rename = "snake_case")] - TranslationFile(PassportElementErrorTranslationFile), - - #[serde(rename = "snake_case")] - TranslationFiles(PassportElementErrorTranslationFiles), - - #[serde(rename = "snake_case")] - Unspecified(PassportElementErrorUnspecified), -} - -/// Represents an issue in one of the data fields that was provided by the -/// user. -/// -/// The error is considered resolved when the field's value changes. -/// -/// [The official docs](https://core.telegram.org/bots/api#passportelementerrordatafield). -#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct PassportElementErrorDataField { - /// The section of the user's Telegram Passport which has the error. - pub r#type: PassportElementErrorDataFieldType, - - /// Name of the data field which has the error. - pub field_name: String, - - /// Base64-encoded data hash. - pub data_hash: String, -} - -impl PassportElementErrorDataField { - pub fn new( - r#type: PassportElementErrorDataFieldType, - field_name: S1, - data_hash: S2, - ) -> Self - where - S1: Into, - S2: Into, - { - Self { r#type, field_name: field_name.into(), data_hash: data_hash.into() } - } - - pub fn r#type(mut self, val: PassportElementErrorDataFieldType) -> Self { - self.r#type = val; - self - } - - pub fn field_name(mut self, val: S) -> Self - where - S: Into, - { - self.field_name = val.into(); - self - } - - pub fn data_hash(mut self, val: S) -> Self - where - S: Into, - { - self.data_hash = val.into(); - self - } -} - -/// Represents an issue with the front side of a document. -/// -/// The error is considered resolved when the file with the front side of the -/// document changes. -/// -/// [The official docs](https://core.telegram.org/bots/api#passportelementerrorfrontside). -#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct PassportElementErrorFrontSide { - /// The section of the user's Telegram Passport which has the issue. - pub r#type: PassportElementErrorFrontSideType, - - /// Base64-encoded hash of the file with the front side of the - /// document. - pub file_hash: String, -} - -impl PassportElementErrorFrontSide { - pub fn new(r#type: PassportElementErrorFrontSideType, file_hash: S) -> Self - where - S: Into, - { - Self { r#type, file_hash: file_hash.into() } - } - - pub fn r#type(mut self, val: PassportElementErrorFrontSideType) -> Self { - self.r#type = val; - self - } - - pub fn file_hash(mut self, val: S) -> Self - where - S: Into, - { - self.file_hash = val.into(); - self - } -} - -/// Represents an issue with the reverse side of a document. -/// -/// The error is considered resolved when the file with reverse side of the -/// document changes. -/// -/// [The official docs](https://core.telegram.org/bots/api#passportelementerrorreverseside). -#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct PassportElementErrorReverseSide { - /// The section of the user's Telegram Passport which has the issue. - pub r#type: PassportElementErrorReverseSideType, - - //// Base64-encoded hash of the file with the reverse side of the - //// document. - pub file_hash: String, -} - -impl PassportElementErrorReverseSide { - pub fn new(r#type: PassportElementErrorReverseSideType, file_hash: S) -> Self - where - S: Into, - { - Self { r#type, file_hash: file_hash.into() } - } - - pub fn r#type(mut self, val: PassportElementErrorReverseSideType) -> Self { - self.r#type = val; - self - } - - pub fn file_hash(mut self, val: S) -> Self - where - S: Into, - { - self.file_hash = val.into(); - self - } -} - -//// Represents an issue with the selfie with a document. -// -/// The error is considered resolved when the file with the selfie changes. -/// -/// [The official docs](https://core.telegram.org/bots/api#passportelementerrorselfie). -#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct PassportElementErrorSelfie { - /// The section of the user's Telegram Passport which has the issue. - pub r#type: PassportElementErrorSelfieType, - - /// Base64-encoded hash of the file with the selfie. - pub file_hash: String, -} - -impl PassportElementErrorSelfie { - pub fn new(r#type: PassportElementErrorSelfieType, file_hash: S) -> Self - where - S: Into, - { - Self { r#type, file_hash: file_hash.into() } - } - - pub fn r#type(mut self, val: PassportElementErrorSelfieType) -> Self { - self.r#type = val; - self - } - - pub fn file_hash(mut self, val: S) -> Self - where - S: Into, - { - self.file_hash = val.into(); - self - } -} - -/// Represents an issue with a document scan. -/// -/// The error is considered resolved when the file with the document scan -/// changes. -/// -/// [The official docs](https://core.telegram.org/bots/api#passportelementerrorfile). -#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct PassportElementErrorFile { - /// The section of the user's Telegram Passport which has the issue. - pub r#type: PassportElementErrorFileType, - - /// Base64-encoded file hash. - pub file_hash: String, -} - -impl PassportElementErrorFile { - pub fn new(r#type: PassportElementErrorFileType, file_hash: S) -> Self - where - S: Into, - { - Self { r#type, file_hash: file_hash.into() } - } - - pub fn r#type(mut self, val: PassportElementErrorFileType) -> Self { - self.r#type = val; - self - } - - pub fn file_hash(mut self, val: S) -> Self - where - S: Into, - { - self.file_hash = val.into(); - self - } -} - -/// Represents an issue with a list of scans. -/// -/// The error is considered resolved when the list of files containing the scans -/// changes. -/// -/// [The official docs](https://core.telegram.org/bots/api#passportelementerrorfiles). -#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct PassportElementErrorFiles { - /// The section of the user's Telegram Passport which has the issue. - pub r#type: PassportElementErrorFilesType, - - /// List of base64-encoded file hashes. - pub file_hashes: Vec, -} - -impl PassportElementErrorFiles { - pub fn new(r#type: PassportElementErrorFilesType, file_hashes: S) -> Self - where - S: Into>, - { - Self { r#type, file_hashes: file_hashes.into() } - } - - pub fn r#type(mut self, val: PassportElementErrorFilesType) -> Self { - self.r#type = val; - self - } - - pub fn file_hashes(mut self, val: S) -> Self - where - S: Into>, - { - self.file_hashes = val.into(); - self - } -} - -/// Represents an issue with one of the files that constitute the -/// translation of a document. -/// -/// The error is considered resolved when the file changes. -/// -/// [The official docs](https://core.telegram.org/bots/api#passportelementerrortranslationfile). -#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct PassportElementErrorTranslationFile { - /// Type of element of the user's Telegram Passport which has the - /// issue. - pub r#type: PassportElementErrorTranslationFileType, - - /// Base64-encoded file hash. - pub file_hash: String, -} - -impl PassportElementErrorTranslationFile { - pub fn new(r#type: PassportElementErrorTranslationFileType, file_hash: S) -> Self - where - S: Into, - { - Self { r#type, file_hash: file_hash.into() } - } - - pub fn r#type(mut self, val: PassportElementErrorTranslationFileType) -> Self { - self.r#type = val; - self - } - - pub fn file_hash(mut self, val: S) -> Self - where - S: Into, - { - self.file_hash = val.into(); - self - } -} - -/// Represents an issue with the translated version of a document. -/// -/// The error is considered resolved when a file with the document translation -/// change. -/// -/// [The official docs](https://core.telegram.org/bots/api#passportelementerrortranslationfiles). -#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct PassportElementErrorTranslationFiles { - /// Type of element of the user's Telegram Passport which has the issue - pub r#type: PassportElementErrorTranslationFilesType, - - /// List of base64-encoded file hashes - pub file_hashes: Vec, -} - -impl PassportElementErrorTranslationFiles { - pub fn new(r#type: PassportElementErrorTranslationFilesType, file_hashes: S) -> Self - where - S: Into>, - { - Self { r#type, file_hashes: file_hashes.into() } - } - - pub fn r#type(mut self, val: PassportElementErrorTranslationFilesType) -> Self { - self.r#type = val; - self - } - - pub fn file_hashes(mut self, val: S) -> Self - where - S: Into>, - { - self.file_hashes = val.into(); - self - } -} - -/// Represents an issue in an unspecified place. -/// -/// The error is considered resolved when new data is added. -/// -/// [The official docs](https://core.telegram.org/bots/api#passportelementerrorunspecified). -#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct PassportElementErrorUnspecified { - /// Type of element of the user's Telegram Passport which has the - /// issue. - pub r#type: PassportElementErrorUnspecifiedType, - - /// Base64-encoded element hash. - pub element_hash: String, -} - -impl PassportElementErrorUnspecified { - pub fn new(r#type: PassportElementErrorUnspecifiedType, file_hash: S) -> Self - where - S: Into, - { - Self { r#type, element_hash: file_hash.into() } - } - - pub fn r#type(mut self, val: PassportElementErrorUnspecifiedType) -> Self { - self.r#type = val; - self - } - - pub fn element_hash(mut self, val: S) -> Self - where - S: Into, - { - self.element_hash = val.into(); - self - } -} - -#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum PassportElementErrorDataFieldType { - PersonalDetails, - Passport, - DriverLicense, - IdentityCard, - InternalPassport, - Address, -} - -#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum PassportElementErrorFrontSideType { - Passport, - DriverLicense, - IdentityCard, - InternalPassport, -} - -#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum PassportElementErrorReverseSideType { - DriverLicense, - IdentityCard, -} - -#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum PassportElementErrorSelfieType { - Passport, - DriverLicense, - IdentityCard, - InternalPassport, -} - -#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum PassportElementErrorFileType { - UtilityBill, - BankStatement, - RentalAgreement, - PassportRegistration, - TemporaryRegistration, -} - -#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum PassportElementErrorFilesType { - UtilityBill, - BankStatement, - RentalAgreement, - PassportRegistration, - TemporaryRegistration, -} - -#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum PassportElementErrorTranslationFileType { - Passport, - DriverLicense, - IdentityCard, - InternalPassport, - UtilityBill, - BankStatement, - RentalAgreement, - PassportRegistration, - TemporaryRegistration, -} - -#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum PassportElementErrorTranslationFilesType { - Passport, - DriverLicense, - IdentityCard, - InternalPassport, - UtilityBill, - BankStatement, - RentalAgreement, - PassportRegistration, - TemporaryRegistration, -} - -#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum PassportElementErrorUnspecifiedType { - DataField, - FrontSide, - ReverseSide, - Selfie, - File, - Files, - TranslationFile, - TranslationFiles, - Unspecified, -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn serialize_data_field() { - let data = PassportElementError { - message: "This is an error message!".to_owned(), - kind: PassportElementErrorKind::DataField(PassportElementErrorDataField { - r#type: PassportElementErrorDataFieldType::InternalPassport, - field_name: "The field name".to_owned(), - data_hash: "This is a data hash".to_owned(), - }), - }; - - assert_eq!( - serde_json::to_string(&data).unwrap(), - r#"{"message":"This is an error message!","source":"data","type":"internal_passport","field_name":"The field name","data_hash":"This is a data hash"}"# - ); - } -} diff --git a/src/types/passport_file.rs b/src/types/passport_file.rs deleted file mode 100644 index 448d0c62..00000000 --- a/src/types/passport_file.rs +++ /dev/null @@ -1,66 +0,0 @@ -use serde::{Deserialize, Serialize}; - -/// This object represents a file uploaded to Telegram Passport. -/// -/// Currently all Telegram Passport files are in JPEG format when decrypted and -/// don't exceed 10MB. -/// -/// [The official docs](https://core.telegram.org/bots/api#passportfile). -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct PassportFile { - /// Identifier for this file. - pub file_id: String, - - /// Unique identifier for this file, which is supposed to be the same over - /// time and for different bots. Can't be used to download or reuse the - /// file. - pub file_unique_id: String, - - /// File size. - pub file_size: u64, - - /// Unix time when the file was uploaded. - pub file_date: u64, -} - -impl PassportFile { - pub fn new(file_id: S1, file_unique_id: S2, file_size: u64, file_date: u64) -> Self - where - S1: Into, - S2: Into, - { - Self { - file_id: file_id.into(), - file_unique_id: file_unique_id.into(), - file_size, - file_date, - } - } - - pub fn file_id(mut self, val: S) -> Self - where - S: Into, - { - self.file_id = val.into(); - self - } - - pub fn file_unique_id(mut self, val: S) -> Self - where - S: Into, - { - self.file_unique_id = val.into(); - self - } - - pub fn file_size(mut self, val: u64) -> Self { - self.file_size = val; - self - } - - pub fn file_date(mut self, val: u64) -> Self { - self.file_date = val; - self - } -} diff --git a/src/types/photo_size.rs b/src/types/photo_size.rs deleted file mode 100644 index 64ddfac7..00000000 --- a/src/types/photo_size.rs +++ /dev/null @@ -1,94 +0,0 @@ -use serde::{Deserialize, Serialize}; - -/// This object represents one size of a photo or a [file]/[sticker] thumbnail. -/// -/// [file]: crate::types::Document -/// [sticker]: crate::types::Sticker -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct PhotoSize { - /// Identifier for this file. - pub file_id: String, - - /// Unique identifier for this file, which is supposed to be the same over - /// time and for different bots. Can't be used to download or reuse the - /// file. - pub file_unique_id: String, - - /// Photo width. - pub width: i32, - - /// Photo height. - pub height: i32, - - /// File size. - pub file_size: Option, -} - -impl PhotoSize { - pub fn new(file_id: S1, file_unique_id: S2, width: i32, height: i32) -> Self - where - S1: Into, - S2: Into, - { - Self { - file_id: file_id.into(), - file_unique_id: file_unique_id.into(), - width, - height, - file_size: None, - } - } - - pub fn file_id(mut self, val: S) -> Self - where - S: Into, - { - self.file_id = val.into(); - self - } - - pub fn file_unique_id(mut self, val: S) -> Self - where - S: Into, - { - self.file_unique_id = val.into(); - self - } - - pub fn width(mut self, val: i32) -> Self { - self.width = val; - self - } - - pub fn height(mut self, val: i32) -> Self { - self.height = val; - self - } - - pub fn file_size(mut self, val: u32) -> Self { - self.file_size = Some(val); - self - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn deserialize() { - let json = r#"{"file_id":"id","file_unique_id":"","width":320,"height":320, - "file_size":3452}"#; - let expected = PhotoSize { - file_id: "id".to_string(), - file_unique_id: "".to_string(), - width: 320, - height: 320, - file_size: Some(3452), - }; - let actual = serde_json::from_str::(json).unwrap(); - assert_eq!(actual, expected); - } -} diff --git a/src/types/poll.rs b/src/types/poll.rs deleted file mode 100644 index c3646e3b..00000000 --- a/src/types/poll.rs +++ /dev/null @@ -1,250 +0,0 @@ -use crate::types::{MessageEntity, PollType}; -use serde::{Deserialize, Serialize}; - -/// This object contains information about a poll. -/// -/// [The official docs](https://core.telegram.org/bots/api#poll). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct Poll { - /// Unique poll identifier. - pub id: String, - - /// Poll question, 1-255 characters. - pub question: String, - - /// List of poll options. - pub options: Vec, - - /// `true`, if the poll is closed. - pub is_closed: bool, - - /// Total number of users that voted in the poll - pub total_voter_count: i32, - - /// True, if the poll is anonymous - pub is_anonymous: bool, - - /// Poll type, currently can be “regular” or “quiz” - #[serde(rename = "type")] - pub poll_type: PollType, - - /// True, if the poll allows multiple answers - pub allows_multiple_answers: bool, - - /// 0-based identifier of the correct answer option. Available only for - /// polls in the quiz mode, which are closed, or was sent (not - /// forwarded) by the bot or to the private chat with the bot. - pub correct_option_id: Option, - - /// Text that is shown when a user chooses an incorrect answer or taps on - /// the lamp icon in a quiz-style poll, 0-200 characters. - pub explanation: Option, - - /// Special entities like usernames, URLs, bot commands, etc. that appear in - /// the explanation. - pub explanation_entities: Option>, - - /// Amount of time in seconds the poll will be active after creation. - open_period: Option, - - /// Point in time (Unix timestamp) when the poll will be automatically - /// closed. - close_date: Option, -} - -impl Poll { - #[allow(clippy::too_many_arguments)] - pub fn new( - id: S1, - question: S2, - options: O, - is_closed: bool, - total_voter_count: i32, - is_anonymous: bool, - poll_type: PollType, - allows_multiple_answers: bool, - ) -> Self - where - S1: Into, - S2: Into, - O: Into>, - { - Self { - id: id.into(), - question: question.into(), - options: options.into(), - is_closed, - total_voter_count, - is_anonymous, - poll_type, - allows_multiple_answers, - correct_option_id: None, - explanation: None, - explanation_entities: None, - open_period: None, - close_date: None, - } - } - - pub fn id(mut self, val: S) -> Self - where - S: Into, - { - self.id = val.into(); - self - } - - pub fn question(mut self, val: S) -> Self - where - S: Into, - { - self.question = val.into(); - self - } - - pub fn options

(mut self, val: P) -> Self - where - P: Into>, - { - self.options = val.into(); - self - } - - #[allow(clippy::wrong_self_convention)] - pub fn is_closed(mut self, val: bool) -> Self { - self.is_closed = val; - self - } - - pub fn total_voter_count(mut self, val: i32) -> Self { - self.total_voter_count = val; - self - } - - #[allow(clippy::wrong_self_convention)] - pub fn is_anonymous(mut self, val: bool) -> Self { - self.is_anonymous = val; - self - } - - pub fn poll_type(mut self, val: PollType) -> Self { - self.poll_type = val; - self - } - - pub fn allows_multiple_answers(mut self, val: bool) -> Self { - self.allows_multiple_answers = val; - self - } - - pub fn correct_option_id(mut self, val: i32) -> Self { - self.correct_option_id = Some(val); - self - } - - pub fn explanation(mut self, val: S) -> Self - where - S: Into, - { - self.explanation = Some(val.into()); - self - } - - pub fn explanation_entities(mut self, val: S) -> Self - where - S: Into>, - { - self.explanation_entities = Some(val.into()); - self - } - - pub fn open_period(mut self, val: i32) -> Self { - self.open_period = Some(val); - self - } - - pub fn close_date(mut self, val: i32) -> Self { - self.close_date = Some(val); - self - } -} - -/// This object contains information about one answer option in a poll. -/// -/// [The official docs](https://core.telegram.org/bots/api#polloption). -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct PollOption { - /// Option text, 1-100 characters. - pub text: String, - - /// Number of users that voted for this option. - pub voter_count: i32, -} - -impl PollOption { - pub fn new(text: S, voter_count: i32) -> Self - where - S: Into, - { - Self { text: text.into(), voter_count } - } - - pub fn text(mut self, val: S) -> Self - where - S: Into, - { - self.text = val.into(); - self - } - - pub fn voter_count(mut self, val: i32) -> Self { - self.voter_count = val; - self - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn deserialize() { - let data = r#" - { - "allows_multiple_answers": false, - "id": "5377643193141559299", - "is_anonymous": true, - "is_closed": false, - "options": [ - { - "text": "1", - "voter_count": 1 - }, - { - "text": "2", - "voter_count": 0 - }, - { - "text": "3", - "voter_count": 0 - }, - { - "text": "4", - "voter_count": 0 - }, - { - "text": "5", - "voter_count": 0 - } - ], - "question": "Rate me from 1 to 5.", - "total_voter_count": 1, - "type": "regular" - } - "#; - serde_json::from_str::(data).unwrap(); - } -} diff --git a/src/types/poll_answer.rs b/src/types/poll_answer.rs deleted file mode 100644 index 6ddb0b06..00000000 --- a/src/types/poll_answer.rs +++ /dev/null @@ -1,48 +0,0 @@ -use crate::types::User; -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct PollAnswer { - /// Unique poll identifier. - pub poll_id: String, - - /// The user, who changed the answer to the poll. - pub user: User, - - /// 0-based identifiers of answer options, chosen by the user. - /// - /// May be empty if the user retracted their vote. - pub option_ids: Vec, -} - -impl PollAnswer { - pub fn new(poll_id: S, user: User, option_ids: O) -> Self - where - S: Into, - O: Into>, - { - Self { poll_id: poll_id.into(), user, option_ids: option_ids.into() } - } - - pub fn poll_id(mut self, val: S) -> Self - where - S: Into, - { - self.poll_id = val.into(); - self - } - - pub fn user(mut self, val: User) -> Self { - self.user = val; - self - } - - pub fn option_ids(mut self, val: S) -> Self - where - S: Into>, - { - self.option_ids = val.into(); - self - } -} diff --git a/src/types/poll_type.rs b/src/types/poll_type.rs deleted file mode 100644 index 0243f7c1..00000000 --- a/src/types/poll_type.rs +++ /dev/null @@ -1,9 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] -#[non_exhaustive] -pub enum PollType { - Quiz, - Regular, -} diff --git a/src/types/pre_checkout_query.rs b/src/types/pre_checkout_query.rs deleted file mode 100644 index 87fd9707..00000000 --- a/src/types/pre_checkout_query.rs +++ /dev/null @@ -1,108 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{Currency, OrderInfo, User}; - -/// This object contains information about an incoming pre-checkout query. -/// -/// [The official docs](https://core.telegram.org/bots/api#precheckoutquery). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct PreCheckoutQuery { - /// Unique query identifier. - pub id: String, - - /// User who sent the query. - pub from: User, - - /// Three-letter ISO 4217 [currency] code. - /// - /// [currency]: https://core.telegram.org/bots/payments#supported-currencies - pub currency: Currency, - - /// Total price in the _smallest units_ of the currency (integer, **not** - /// float/double). For example, for a price of `US$ 1.45` pass `amount = - /// 145`. See the exp parameter in [`currencies.json`], it shows the number - /// of digits past the decimal point for each currency (2 for the - /// majority of currencies). - /// - /// [`currencies.json`]: https://core.telegram.org/bots/payments/currencies.json - pub total_amount: i32, - - /// Bot specified invoice payload. - pub invoice_payload: String, - - /// Identifier of the shipping option chosen by the user. - pub shipping_option_id: Option, - - /// Order info provided by the user. - pub order_info: Option, -} - -impl PreCheckoutQuery { - pub fn new( - id: S1, - from: User, - currency: Currency, - total_amount: i32, - invoice_payload: S2, - ) -> Self - where - S1: Into, - S2: Into, - { - Self { - id: id.into(), - from, - currency, - total_amount, - invoice_payload: invoice_payload.into(), - shipping_option_id: None, - order_info: None, - } - } - - pub fn id(mut self, val: S) -> Self - where - S: Into, - { - self.id = val.into(); - self - } - - pub fn from(mut self, val: User) -> Self { - self.from = val; - self - } - - pub fn currency(mut self, val: Currency) -> Self { - self.currency = val; - self - } - - pub fn total_amount(mut self, val: i32) -> Self { - self.total_amount = val; - self - } - - pub fn invoice_payload(mut self, val: S) -> Self - where - S: Into, - { - self.invoice_payload = val.into(); - self - } - - pub fn shipping_option_id(mut self, val: S) -> Self - where - S: Into, - { - self.shipping_option_id = Some(val.into()); - self - } - - pub fn order_info(mut self, val: OrderInfo) -> Self { - self.order_info = Some(val); - self - } -} diff --git a/src/types/reply_keyboard_markup.rs b/src/types/reply_keyboard_markup.rs deleted file mode 100644 index a8d5983e..00000000 --- a/src/types/reply_keyboard_markup.rs +++ /dev/null @@ -1,98 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::KeyboardButton; - -/// This object represents a [custom keyboard] with reply options (see -/// [Introduction to bots] for details and examples). -/// -/// [The official docs](https://core.telegram.org/bots/api#replykeyboardmarkup). -/// -/// [custom keyboard]: https://core.telegram.org/bots#keyboards -/// [Introduction to bots]: https://core.telegram.org/bots#keyboards -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize, Default)] -#[non_exhaustive] -pub struct ReplyKeyboardMarkup { - /// Array of button rows, each represented by an Array of - /// [`KeyboardButton`] objects - /// - /// [`KeyboardButton`]: crate::types::KeyboardButton - pub keyboard: Vec>, - - /// Requests clients to resize the keyboard vertically for optimal fit - /// (e.g., make the keyboard smaller if there are just two rows of - /// buttons). Defaults to `false`, in which case the custom keyboard is - /// always of the same height as the app's standard keyboard. - pub resize_keyboard: Option, - - /// Requests clients to hide the keyboard as soon as it's been used. The - /// keyboard will still be available, but clients will automatically - /// display the usual letter-keyboard in the chat – the user can press a - /// special button in the input field to see the custom keyboard again. - /// Defaults to `false`. - pub one_time_keyboard: Option, - - /// Use this parameter if you want to show the keyboard to specific users - /// only. Targets: 1) users that are `@mentioned` in the `text` of the - /// [`Message`] object; 2) if the bot's message is a reply (has - /// `reply_to_message_id`), sender of the original message. - /// - /// Example: A user requests to change the bot‘s language, bot replies to - /// the request with a keyboard to select the new language. Other users - /// in the group don’t see the keyboard. - /// - /// [`Message`]: crate::types::Message - pub selective: Option, -} - -impl ReplyKeyboardMarkup { - pub fn new(keyboard: K1) -> Self - where - K1: Into>, - K2: Into>, - { - Self { - keyboard: keyboard.into().into_iter().map(Into::into).collect(), - resize_keyboard: None, - one_time_keyboard: None, - selective: None, - } - } - - pub fn append_row(mut self, buttons: Vec) -> Self { - self.keyboard.push(buttons); - self - } - - pub fn append_to_row(mut self, button: KeyboardButton, index: usize) -> Self { - match self.keyboard.get_mut(index) { - Some(buttons) => buttons.push(button), - None => self.keyboard.push(vec![button]), - }; - self - } - - pub fn resize_keyboard(mut self, val: T) -> Self - where - T: Into>, - { - self.resize_keyboard = val.into(); - self - } - - pub fn one_time_keyboard(mut self, val: T) -> Self - where - T: Into>, - { - self.one_time_keyboard = val.into(); - self - } - - pub fn selective(mut self, val: T) -> Self - where - T: Into>, - { - self.selective = val.into(); - self - } -} diff --git a/src/types/reply_keyboard_remove.rs b/src/types/reply_keyboard_remove.rs deleted file mode 100644 index deb8351b..00000000 --- a/src/types/reply_keyboard_remove.rs +++ /dev/null @@ -1,52 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::True; - -/// Upon receiving a message with this object, Telegram clients will remove the -/// current custom keyboard and display the default letter-keyboard. -/// -/// By default, custom keyboards are displayed until a new keyboard is sent by a -/// bot. An exception is made for one-time keyboards that are hidden immediately -/// after the user presses a button (see [`ReplyKeyboardMarkup`]). -/// -/// [The official docs](https://core.telegram.org/bots/api#replykeyboardremove). -/// -/// [`ReplyKeyboardMarkup`]: crate::types::ReplyKeyboardMarkup -#[serde_with_macros::skip_serializing_none] -#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct ReplyKeyboardRemove { - /// Requests clients to remove the custom keyboard (user will not be able - /// to summon this keyboard; if you want to hide the keyboard from sight - /// but keep it accessible, use one_time_keyboard in - /// [`ReplyKeyboardMarkup`]). - /// - /// [`ReplyKeyboardMarkup`]: crate::types::ReplyKeyboardMarkup - pub remove_keyboard: True, - - /// Use this parameter if you want to remove the keyboard for specific - /// users only. Targets: 1) users that are `@mentioned` in the `text` of - /// the [`Message`] object; 2) if the bot's message is a reply (has - /// `reply_to_message_id`), sender of the original message. - /// - /// Example: A user votes in a poll, bot returns confirmation message in - /// reply to the vote and removes the keyboard for that user, while still - /// showing the keyboard with poll options to users who haven't voted yet. - /// - /// [`Message`]: crate::types::Message - pub selective: Option, -} - -impl ReplyKeyboardRemove { - pub fn new() -> Self { - Self::default() - } - - pub fn selective(mut self, val: T) -> Self - where - T: Into, - { - self.selective = Some(val.into()); - self - } -} diff --git a/src/types/reply_markup.rs b/src/types/reply_markup.rs deleted file mode 100644 index 4bddc56c..00000000 --- a/src/types/reply_markup.rs +++ /dev/null @@ -1,27 +0,0 @@ -use derive_more::From; -use serde::{Deserialize, Serialize}; - -use crate::types::{ForceReply, InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove}; - -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize, From)] -#[serde(untagged)] -#[non_exhaustive] -pub enum ReplyMarkup { - InlineKeyboardMarkup(InlineKeyboardMarkup), - ReplyKeyboardMarkup(ReplyKeyboardMarkup), - ReplyKeyboardRemove(ReplyKeyboardRemove), - ForceReply(ForceReply), -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn inline_keyboard_markup() { - let data = InlineKeyboardMarkup::default(); - let expected = ReplyMarkup::InlineKeyboardMarkup(data.clone()); - let actual: ReplyMarkup = data.into(); - assert_eq!(actual, expected) - } -} diff --git a/src/types/response_parameters.rs b/src/types/response_parameters.rs deleted file mode 100644 index 2f0fb43f..00000000 --- a/src/types/response_parameters.rs +++ /dev/null @@ -1,43 +0,0 @@ -use serde::{Deserialize, Serialize}; - -/// Contains information about why a request was unsuccessful. -/// -/// [The official docs](https://core.telegram.org/bots/api#responseparameters). -#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum ResponseParameters { - /// The group has been migrated to a supergroup with the specified - /// identifier. This number may be greater than 32 bits and some - /// programming languages may have difficulty/silent defects in - /// interpreting it. But it is smaller than 52 bits, so a signed 64 bit - /// integer or double-precision float type are safe for storing this - /// identifier. - MigrateToChatId(i64), - - /// In case of exceeding flood control, the number of seconds left to wait - /// before the request can be repeated. - RetryAfter(i32), -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn migrate_to_chat_id_deserialization() { - let expected = ResponseParameters::MigrateToChatId(123_456); - let actual: ResponseParameters = - serde_json::from_str(r#"{"migrate_to_chat_id":123456}"#).unwrap(); - - assert_eq!(expected, actual); - } - - #[test] - fn retry_after_deserialization() { - let expected = ResponseParameters::RetryAfter(123_456); - let actual: ResponseParameters = serde_json::from_str(r#"{"retry_after":123456}"#).unwrap(); - - assert_eq!(expected, actual); - } -} diff --git a/src/types/send_invoice.rs b/src/types/send_invoice.rs deleted file mode 100644 index bc527b45..00000000 --- a/src/types/send_invoice.rs +++ /dev/null @@ -1,229 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{ChatId, InlineKeyboardMarkup, LabeledPrice}; - -// TODO: missing docs -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct SendInvoice { - pub chat_id: ChatId, - pub title: String, - pub description: String, - pub payload: String, - pub provider_token: String, - pub start_parameter: String, - pub currency: String, - pub prices: Vec, - pub provider_data: Option, - pub photo_url: Option, - pub photo_size: Option, - pub photo_width: Option, - pub photo_height: Option, - pub need_name: Option, - pub need_phone_number: Option, - pub need_email: Option, - pub need_shipping_address: Option, - pub send_phone_number_to_provider: Option, - pub send_email_to_provider: Option, - pub is_flexible: Option, - pub disable_notification: Option, - pub reply_to_message_id: Option, - pub reply_markup: Option, -} - -impl SendInvoice { - #[allow(clippy::too_many_arguments)] - pub fn new( - chat_id: C, - title: S1, - description: S2, - payload: S3, - provider_token: S4, - start_parameter: S5, - currency: S6, - prices: P, - ) -> Self - where - C: Into, - S1: Into, - S2: Into, - S3: Into, - S4: Into, - S5: Into, - S6: Into, - P: Into>, - { - Self { - chat_id: chat_id.into(), - title: title.into(), - description: description.into(), - payload: payload.into(), - provider_token: provider_token.into(), - start_parameter: start_parameter.into(), - currency: currency.into(), - prices: prices.into(), - provider_data: None, - photo_url: None, - photo_size: None, - photo_width: None, - photo_height: None, - need_name: None, - need_phone_number: None, - need_email: None, - need_shipping_address: None, - send_phone_number_to_provider: None, - send_email_to_provider: None, - is_flexible: None, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - } - } - - pub fn chat_id(mut self, val: C) -> Self - where - C: Into, - { - self.chat_id = val.into(); - self - } - - pub fn title(mut self, val: S) -> Self - where - S: Into, - { - self.title = val.into(); - self - } - - pub fn description(mut self, val: S) -> Self - where - S: Into, - { - self.description = val.into(); - self - } - - pub fn payload(mut self, val: S) -> Self - where - S: Into, - { - self.payload = val.into(); - self - } - - pub fn provider_token(mut self, val: S) -> Self - where - S: Into, - { - self.provider_token = val.into(); - self - } - - pub fn start_parameter(mut self, val: S) -> Self - where - S: Into, - { - self.start_parameter = val.into(); - self - } - - pub fn currency(mut self, val: S) -> Self - where - S: Into, - { - self.currency = val.into(); - self - } - - pub fn prices

(mut self, val: P) -> Self - where - P: Into>, - { - self.prices = val.into(); - self - } - - pub fn provider_data(mut self, val: S) -> Self - where - S: Into, - { - self.provider_data = Some(val.into()); - self - } - - pub fn photo_url(mut self, val: S) -> Self - where - S: Into, - { - self.photo_url = Some(val.into()); - self - } - - pub fn photo_size(mut self, val: i32) -> Self { - self.photo_size = Some(val); - self - } - - pub fn photo_width(mut self, val: i32) -> Self { - self.photo_width = Some(val); - self - } - - pub fn photo_height(mut self, val: i32) -> Self { - self.photo_height = Some(val); - self - } - - pub fn need_name(mut self, val: bool) -> Self { - self.need_name = Some(val); - self - } - - pub fn need_phone_number(mut self, val: bool) -> Self { - self.need_phone_number = Some(val); - self - } - - pub fn need_email(mut self, val: bool) -> Self { - self.need_email = Some(val); - self - } - - pub fn need_shipping_address(mut self, val: bool) -> Self { - self.need_shipping_address = Some(val); - self - } - - pub fn send_phone_number_to_provider(mut self, val: bool) -> Self { - self.send_phone_number_to_provider = Some(val); - self - } - - pub fn send_email_to_provider(mut self, val: bool) -> Self { - self.send_email_to_provider = Some(val); - self - } - - #[allow(clippy::wrong_self_convention)] - pub fn is_flexible(mut self, val: bool) -> Self { - self.is_flexible = Some(val); - self - } - - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } - - pub fn reply_to_message_id(mut self, value: i32) -> Self { - self.reply_to_message_id = Some(value); - self - } - - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/types/shipping_address.rs b/src/types/shipping_address.rs deleted file mode 100644 index 3a0adb58..00000000 --- a/src/types/shipping_address.rs +++ /dev/null @@ -1,100 +0,0 @@ -use crate::types::CountryCode; -use serde::{Deserialize, Serialize}; - -/// This object represents a shipping address. -/// -/// [The official docs](https://core.telegram.org/bots/api#shippingaddress). -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct ShippingAddress { - /// ISO 3166-1 alpha-2 country code. - pub country_code: CountryCode, - - /// State, if applicable. - pub state: String, - - /// City. - pub city: String, - - /// First line for the address. - pub street_line1: String, - - /// Second line for the address. - pub street_line2: String, - - /// Address post code. - pub post_code: String, -} - -impl ShippingAddress { - pub fn new( - country_code: CountryCode, - - state: S1, - city: S2, - street_line1: S3, - street_line2: S4, - post_code: S5, - ) -> Self - where - S1: Into, - S2: Into, - S3: Into, - S4: Into, - S5: Into, - { - Self { - country_code, - state: state.into(), - city: city.into(), - street_line1: street_line1.into(), - street_line2: street_line2.into(), - post_code: post_code.into(), - } - } - - pub fn country_code(mut self, val: CountryCode) -> Self { - self.country_code = val; - self - } - - pub fn state(mut self, val: S) -> Self - where - S: Into, - { - self.state = val.into(); - self - } - - pub fn city(mut self, val: S) -> Self - where - S: Into, - { - self.city = val.into(); - self - } - - pub fn street_line1(mut self, val: S) -> Self - where - S: Into, - { - self.street_line1 = val.into(); - self - } - - pub fn street_line2(mut self, val: S) -> Self - where - S: Into, - { - self.street_line2 = val.into(); - self - } - - pub fn post_code(mut self, val: S) -> Self - where - S: Into, - { - self.post_code = val.into(); - self - } -} diff --git a/src/types/shipping_option.rs b/src/types/shipping_option.rs deleted file mode 100644 index 983c7eef..00000000 --- a/src/types/shipping_option.rs +++ /dev/null @@ -1,71 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::LabeledPrice; - -/// This object represents one shipping option. -/// -/// [The official docs](https://core.telegram.org/bots/api#shippingoption). -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct ShippingOption { - /// Shipping option identifier. - pub id: String, - - /// Option title. - pub title: String, - - /// List of price portions. - pub prices: Vec, -} - -impl ShippingOption { - pub fn new(id: S1, title: S2, prices: P) -> Self - where - S1: Into, - S2: Into, - P: Into>, - { - Self { id: id.into(), title: title.into(), prices: prices.into() } - } - - pub fn id(mut self, val: S) -> Self - where - S: Into, - { - self.id = val.into(); - self - } - - pub fn title(mut self, val: S) -> Self - where - S: Into, - { - self.title = val.into(); - self - } - - pub fn prices

(mut self, val: P) -> Self - where - P: Into>, - { - self.prices = val.into(); - self - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn serialize() { - let shipping_option = ShippingOption { - id: "0".to_string(), - title: "Option".to_string(), - prices: vec![LabeledPrice { label: "Label".to_string(), amount: 60 }], - }; - let expected = r#"{"id":"0","title":"Option","prices":[{"label":"Label","amount":60}]}"#; - let actual = serde_json::to_string(&shipping_option).unwrap(); - assert_eq!(actual, expected); - } -} diff --git a/src/types/shipping_query.rs b/src/types/shipping_query.rs deleted file mode 100644 index bfdf7dc8..00000000 --- a/src/types/shipping_query.rs +++ /dev/null @@ -1,63 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{ShippingAddress, User}; - -/// This object contains information about an incoming shipping query. -/// -/// [The official docs](https://core.telegram.org/bots/api#shippingquery). -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct ShippingQuery { - /// Unique query identifier. - pub id: String, - - /// User who sent the query. - pub from: User, - - /// Bot specified invoice payload. - pub invoice_payload: String, - - /// User specified shipping address. - pub shipping_address: ShippingAddress, -} - -impl ShippingQuery { - pub fn new( - id: S1, - from: User, - invoice_payload: S2, - shipping_address: ShippingAddress, - ) -> Self - where - S1: Into, - S2: Into, - { - Self { id: id.into(), from, invoice_payload: invoice_payload.into(), shipping_address } - } - - pub fn id(mut self, val: S) -> Self - where - S: Into, - { - self.id = val.into(); - self - } - - pub fn from(mut self, val: User) -> Self { - self.from = val; - self - } - - pub fn invoice_payload(mut self, val: S) -> Self - where - S: Into, - { - self.invoice_payload = val.into(); - self - } - - pub fn shipping_address(mut self, val: ShippingAddress) -> Self { - self.shipping_address = val; - self - } -} diff --git a/src/types/sticker.rs b/src/types/sticker.rs deleted file mode 100644 index d133f1cc..00000000 --- a/src/types/sticker.rs +++ /dev/null @@ -1,135 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{MaskPosition, PhotoSize}; - -/// This object represents a sticker. -/// -/// [The official docs](https://core.telegram.org/bots/api#sticker). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct Sticker { - /// Identifier for this file. - pub file_id: String, - - /// Unique identifier for this file, which is supposed to be the same over - /// time and for different bots. Can't be used to download or reuse the - /// file. - pub file_unique_id: String, - - /// Sticker width. - pub width: u16, - - /// Sticker height. - pub height: u16, - - /// `true`, if the sticker is [animated]. - /// - /// [animated]: https://telegram.org/blog/animated-stickers - pub is_animated: bool, - - /// Sticker thumbnail in the .webp or .jpg format. - pub thumb: Option, - - /// Emoji associated with the sticker. - pub emoji: Option, - - /// Name of the sticker set to which the sticker belongs. - pub set_name: Option, - - /// For mask stickers, the position where the mask should be placed. - pub mask_position: Option, - - /// File size. - pub file_size: Option, -} - -impl Sticker { - pub fn new( - file_id: S1, - file_unique_id: S2, - width: u16, - height: u16, - is_animated: bool, - ) -> Self - where - S1: Into, - S2: Into, - { - Self { - file_id: file_id.into(), - file_unique_id: file_unique_id.into(), - width, - height, - is_animated, - thumb: None, - emoji: None, - set_name: None, - mask_position: None, - file_size: None, - } - } - - pub fn file_id(mut self, val: S) -> Self - where - S: Into, - { - self.file_id = val.into(); - self - } - - pub fn file_unique_id(mut self, val: S) -> Self - where - S: Into, - { - self.file_unique_id = val.into(); - self - } - - pub fn height(mut self, val: u16) -> Self { - self.height = val; - self - } - - pub fn width(mut self, val: u16) -> Self { - self.width = val; - self - } - - #[allow(clippy::wrong_self_convention)] - pub fn is_animated(mut self, val: bool) -> Self { - self.is_animated = val; - self - } - - pub fn thumb(mut self, val: PhotoSize) -> Self { - self.thumb = Some(val); - self - } - - pub fn emoji(mut self, val: S) -> Self - where - S: Into, - { - self.emoji = Some(val.into()); - self - } - - pub fn set_name(mut self, val: S) -> Self - where - S: Into, - { - self.set_name = Some(val.into()); - self - } - - pub fn mask_position(mut self, val: MaskPosition) -> Self { - self.mask_position = Some(val); - self - } - - pub fn file_size(mut self, val: u32) -> Self { - self.file_size = Some(val); - self - } -} diff --git a/src/types/sticker_set.rs b/src/types/sticker_set.rs deleted file mode 100644 index 0c617205..00000000 --- a/src/types/sticker_set.rs +++ /dev/null @@ -1,89 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{PhotoSize, Sticker}; - -/// This object represents a sticker set. -/// -/// [The official docs](https://core.telegram.org/bots/api#stickerset). -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct StickerSet { - /// Sticker set name. - pub name: String, - - /// Sticker set title. - pub title: String, - - /// `true`, if the sticker set contains [animated stickers]. - /// - /// [animates stickers]: https://telegram.org/blog/animated-stickers - pub is_animated: bool, - - /// `true`, if the sticker set contains masks. - pub contains_masks: bool, - - /// List of all set stickers. - pub stickers: Vec, - - /// Sticker set thumbnail in the .WEBP or .TGS format. - thumb: Option, -} - -impl StickerSet { - pub fn new( - name: S1, - title: S2, - is_animated: bool, - contains_masks: bool, - stickers: St, - ) -> Self - where - S1: Into, - S2: Into, - St: Into>, - { - Self { - name: name.into(), - title: title.into(), - is_animated, - contains_masks, - stickers: stickers.into(), - thumb: None, - } - } - - pub fn name(mut self, val: S) -> Self - where - S: Into, - { - self.name = val.into(); - self - } - - pub fn title(mut self, val: S) -> Self - where - S: Into, - { - self.title = val.into(); - self - } - - #[allow(clippy::wrong_self_convention)] - pub fn is_animated(mut self, val: bool) -> Self { - self.is_animated = val; - self - } - - pub fn contains_masks(mut self, val: bool) -> Self { - self.contains_masks = val; - self - } - - pub fn stickers(mut self, val: S) -> Self - where - S: Into>, - { - self.stickers = val.into(); - self - } -} diff --git a/src/types/sticker_type.rs b/src/types/sticker_type.rs deleted file mode 100644 index 398ffb3a..00000000 --- a/src/types/sticker_type.rs +++ /dev/null @@ -1,28 +0,0 @@ -use crate::types::InputFile; - -#[derive(Clone, Debug, Eq, Hash, PartialEq)] -#[non_exhaustive] -pub enum StickerType { - /// PNG image with the sticker, must be up to 512 kilobytes in size, - /// dimensions must not exceed 512px, and either width or height must be - /// exactly 512px. - /// - /// Pass [`InputFile::FileId`] to send a sticker that exists on the Telegram - /// servers (recommended), pass an [`InputFile::Url`] for Telegram to get a - /// sticker (.WEBP file) from the Internet, pass [`InputFile::File`] to - /// upload a sticker from the file system or [`InputFile::Memory`] to upload - /// a sticker from memory [More info on Sending Files »]. - /// - /// [`InputFile::FileId`]: crate::types::InputFile::FileId - /// [`InputFile::Url`]: crate::types::InputFile::Url - /// [`InputFile::File`]: crate::types::InputFile::File - /// [`InputFile::Memory`]: crate::types::InputFile::Memory - /// - /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files - Png(InputFile), - - /// TGS animation with the sticker, uploaded using multipart/form-data. - /// - /// See https://core.telegram.org/animated_stickers#technical-requirements for technical requirements - Tgs(InputFile), -} diff --git a/src/types/successful_payment.rs b/src/types/successful_payment.rs deleted file mode 100644 index 439dbc23..00000000 --- a/src/types/successful_payment.rs +++ /dev/null @@ -1,112 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{Currency, OrderInfo}; - -/// This object contains basic information about a successful payment. -/// -/// [The official docs](https://core.telegram.org/bots/api#successfulpayment). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct SuccessfulPayment { - /// Three-letter ISO 4217 [currency] code. - /// - /// [currency]: https://core.telegram.org/bots/payments#supported-currencies - pub currency: Currency, - - /// Total price in the smallest units of the currency (integer, not - /// float/double). For example, for a price of `US$ 1.45` pass `amount = - /// 145`. See the exp parameter in [`currencies.json`], it shows the - /// number of digits past the decimal point for each currency (2 for - /// the majority of currencies). - /// - /// [`currencies.json`]: https://core.telegram.org/bots/payments/currencies.json - pub total_amount: i32, - - /// Bot specified invoice payload. - pub invoice_payload: String, - - /// Identifier of the shipping option chosen by the user. - pub shipping_option_id: Option, - - /// Order info provided by the user. - pub order_info: Option, - - /// Telegram payment identifier. - pub telegram_payment_charge_id: String, - - /// Provider payment identifier. - pub provider_payment_charge_id: String, -} - -impl SuccessfulPayment { - pub fn new( - currency: Currency, - total_amount: i32, - invoice_payload: S1, - telegram_payment_charge_id: S2, - provider_payment_charge_id: S3, - ) -> Self - where - S1: Into, - S2: Into, - S3: Into, - { - Self { - currency, - total_amount, - invoice_payload: invoice_payload.into(), - shipping_option_id: None, - order_info: None, - telegram_payment_charge_id: telegram_payment_charge_id.into(), - provider_payment_charge_id: provider_payment_charge_id.into(), - } - } - - pub fn currency(mut self, val: Currency) -> Self { - self.currency = val; - self - } - - pub fn total_amount(mut self, val: i32) -> Self { - self.total_amount = val; - self - } - - pub fn invoice_payload(mut self, val: S) -> Self - where - S: Into, - { - self.invoice_payload = val.into(); - self - } - - pub fn shipping_option_id(mut self, val: S) -> Self - where - S: Into, - { - self.shipping_option_id = Some(val.into()); - self - } - - pub fn order_info(mut self, val: OrderInfo) -> Self { - self.order_info = Some(val); - self - } - - pub fn telegram_payment_charge_id(mut self, val: S) -> Self - where - S: Into, - { - self.telegram_payment_charge_id = val.into(); - self - } - - pub fn provider_payment_charge_id(mut self, val: S) -> Self - where - S: Into, - { - self.provider_payment_charge_id = val.into(); - self - } -} diff --git a/src/types/target_message.rs b/src/types/target_message.rs deleted file mode 100644 index 0c0d8cc1..00000000 --- a/src/types/target_message.rs +++ /dev/null @@ -1,17 +0,0 @@ -use crate::types::ChatId; - -use serde::{Deserialize, Serialize}; - -/// A message in chat or inline message. -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[serde(untagged)] -pub enum TargetMessage { - Common { chat_id: ChatId, message_id: i32 }, - Inline { inline_message_id: String }, -} - -impl From for TargetMessage { - fn from(inline_message_id: String) -> Self { - Self::Inline { inline_message_id } - } -} diff --git a/src/types/unit_false.rs b/src/types/unit_false.rs deleted file mode 100644 index bde6410b..00000000 --- a/src/types/unit_false.rs +++ /dev/null @@ -1,76 +0,0 @@ -use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; - -/// A type that is always false. -#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Default)] -pub struct False; - -impl std::convert::TryFrom for False { - type Error = (); - - fn try_from(value: bool) -> Result { - match value { - true => Err(()), - false => Ok(False), - } - } -} - -impl<'de> Deserialize<'de> for False { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - deserializer.deserialize_bool(FalseVisitor) - } -} - -struct FalseVisitor; - -impl<'de> Visitor<'de> for FalseVisitor { - type Value = False; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(formatter, "bool, equal to `false`") - } - - fn visit_bool(self, value: bool) -> Result - where - E: serde::de::Error, - { - match value { - true => Err(E::custom("expected `false`, found `true`")), - false => Ok(False), - } - } -} - -impl Serialize for False { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_bool(false) - } -} - -#[cfg(test)] -mod tests { - use serde_json::{from_str, to_string}; - - use super::False; - - #[test] - fn unit_false_de() { - let json = "false"; - let expected = False; - let actual = from_str(json).unwrap(); - assert_eq!(expected, actual); - } - - #[test] - fn unit_false_se() { - let actual = to_string(&False).unwrap(); - let expected = "false"; - assert_eq!(expected, actual); - } -} diff --git a/src/types/unit_true.rs b/src/types/unit_true.rs deleted file mode 100644 index cd71e5c2..00000000 --- a/src/types/unit_true.rs +++ /dev/null @@ -1,79 +0,0 @@ -use serde::{ - de::{self, Deserialize, Deserializer, Visitor}, - ser::{Serialize, Serializer}, -}; - -/// A type that is always true. -#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Default)] -pub struct True; - -impl std::convert::TryFrom for True { - type Error = (); - - fn try_from(value: bool) -> Result { - match value { - true => Ok(True), - false => Err(()), - } - } -} - -impl<'de> Deserialize<'de> for True { - fn deserialize(des: D) -> Result - where - D: Deserializer<'de>, - { - des.deserialize_bool(TrueVisitor) - } -} - -struct TrueVisitor; - -impl<'de> Visitor<'de> for TrueVisitor { - type Value = True; - - fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "bool, equal to `true`") - } - - fn visit_bool(self, value: bool) -> Result - where - E: de::Error, - { - match value { - true => Ok(True), - false => Err(E::custom("expected `true`, found `false`")), - } - } -} - -impl Serialize for True { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_bool(true) - } -} - -#[cfg(test)] -mod tests { - use serde_json::{from_str, to_string}; - - use super::True; - - #[test] - fn unit_true_de() { - let json = "true"; - let expected = True; - let actual = from_str(json).unwrap(); - assert_eq!(expected, actual); - } - - #[test] - fn unit_true_se() { - let actual = to_string(&True).unwrap(); - let expected = "true"; - assert_eq!(expected, actual); - } -} diff --git a/src/types/update.rs b/src/types/update.rs deleted file mode 100644 index 6b047dcf..00000000 --- a/src/types/update.rs +++ /dev/null @@ -1,321 +0,0 @@ -#![allow(clippy::large_enum_variant)] - -use serde::{Deserialize, Serialize}; - -use crate::types::{ - CallbackQuery, Chat, ChosenInlineResult, InlineQuery, Message, Poll, PollAnswer, - PreCheckoutQuery, ShippingQuery, User, -}; -use serde_json::Value; - -/// This [object] represents an incoming update. -/// -/// [The official docs](https://core.telegram.org/bots/api#update). -/// -/// [object]: https://core.telegram.org/bots/api#available-types -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct Update { - /// The update‘s unique identifier. Update identifiers start from a certain - /// positive number and increase sequentially. This ID becomes especially - /// handy if you’re using [Webhooks], since it allows you to ignore - /// repeated updates or to restore the correct update sequence, should - /// they get out of order. If there are no new updates for at least a - /// week, then identifier of the next update will be chosen randomly - /// instead of sequentially. - /// - /// [Webhooks]: crate::Bot::set_webhook - #[serde(rename = "update_id")] - pub id: i32, - - #[serde(flatten)] - pub kind: UpdateKind, -} - -impl Update { - pub fn new(id: i32, kind: UpdateKind) -> Self { - Self { id, kind } - } - - pub fn id(mut self, val: i32) -> Self { - self.id = val; - self - } - - pub fn kind(mut self, val: UpdateKind) -> Self { - self.kind = val; - self - } -} - -impl Update { - /// Tries to parse `value` into `Update`, logging an error if failed. - /// - /// It is used to implement update listeners. - pub fn try_parse(value: &Value) -> Result { - match serde_json::from_str(&value.to_string()) { - Ok(update) => Ok(update), - Err(error) => { - log::error!( - "Cannot parse an update.\nError: {:?}\nValue: {}\n\ - This is a bug in teloxide, please open an issue here: \ - https://github.com/teloxide/teloxide/issues.", - error, - value - ); - Err(error) - } - } - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum UpdateKind { - /// New incoming message of any kind — text, photo, sticker, etc. - Message(Message), - - /// New version of a message that is known to the bot and was edited. - EditedMessage(Message), - - /// New incoming channel post of any kind — text, photo, sticker, etc. - ChannelPost(Message), - - /// New version of a channel post that is known to the bot and was edited. - EditedChannelPost(Message), - - /// New incoming [inline] query. - /// - /// [inline]: https://core.telegram.org/bots/api#inline-mode - InlineQuery(InlineQuery), - - /// The result of an [inline] query that was chosen by a user and sent to - /// their chat partner. Please see our documentation on the [feedback - /// collecting] for details on how to enable these updates for your bot. - /// - /// [inline]: https://core.telegram.org/bots/api#inline-mode - /// [feedback collecting]: https://core.telegram.org/bots/inline#collecting-feedback - ChosenInlineResult(ChosenInlineResult), - - /// New incoming callback query. - CallbackQuery(CallbackQuery), - - /// New incoming shipping query. Only for invoices with flexible price. - ShippingQuery(ShippingQuery), - - /// New incoming pre-checkout query. Contains full information about - /// checkout. - PreCheckoutQuery(PreCheckoutQuery), - - /// New poll state. Bots receive only updates about stopped polls and - /// polls, which are sent by the bot. - Poll(Poll), - - /// A user changed their answer in a non-anonymous poll. Bots receive new - /// votes only in polls that were sent by the bot itself. - PollAnswer(PollAnswer), -} - -impl Update { - pub fn user(&self) -> Option<&User> { - match &self.kind { - UpdateKind::Message(m) => m.from(), - UpdateKind::EditedMessage(m) => m.from(), - UpdateKind::CallbackQuery(query) => Some(&query.from), - UpdateKind::ChosenInlineResult(chosen) => Some(&chosen.from), - UpdateKind::InlineQuery(query) => Some(&query.from), - UpdateKind::ShippingQuery(query) => Some(&query.from), - UpdateKind::PreCheckoutQuery(query) => Some(&query.from), - UpdateKind::PollAnswer(answer) => Some(&answer.user), - _ => None, - } - } - - pub fn chat(&self) -> Option<&Chat> { - match &self.kind { - UpdateKind::Message(m) => Some(&m.chat), - UpdateKind::EditedMessage(m) => Some(&m.chat), - UpdateKind::ChannelPost(p) => Some(&p.chat), - UpdateKind::EditedChannelPost(p) => Some(&p.chat), - UpdateKind::CallbackQuery(q) => Some(&q.message.as_ref()?.chat), - _ => None, - } - } -} - -#[cfg(test)] -mod test { - use crate::types::{ - Chat, ChatKind, ChatPrivate, ForwardKind, ForwardOrigin, MediaKind, MediaText, Message, - MessageCommon, MessageKind, Update, UpdateKind, User, - }; - - // TODO: more tests for deserialization - #[test] - fn message() { - let json = r#"{ - "update_id":892252934, - "message":{ - "message_id":6557, - "from":{ - "id":218485655, - "is_bot": false, - "first_name":"Waffle", - "username":"WaffleLapkin", - "language_code":"en" - }, - "chat":{ - "id":218485655, - "first_name":"Waffle", - "username":"WaffleLapkin", - "type":"private" - }, - "date":1569518342, - "text":"hello there" - } - }"#; - - let expected = Update { - id: 892_252_934, - kind: UpdateKind::Message(Message { - via_bot: None, - id: 6557, - date: 1_569_518_342, - chat: Chat { - id: 218_485_655, - kind: ChatKind::Private(ChatPrivate { - type_: (), - username: Some(String::from("WaffleLapkin")), - first_name: Some(String::from("Waffle")), - last_name: None, - }), - photo: None, - }, - kind: MessageKind::Common(MessageCommon { - from: Some(User { - id: 218_485_655, - is_bot: false, - first_name: String::from("Waffle"), - last_name: None, - username: Some(String::from("WaffleLapkin")), - language_code: Some(String::from("en")), - }), - forward_kind: ForwardKind::Origin(ForwardOrigin { reply_to_message: None }), - edit_date: None, - media_kind: MediaKind::Text(MediaText { - text: String::from("hello there"), - entities: vec![], - }), - reply_markup: None, - }), - }), - }; - - let actual = serde_json::from_str::(json).unwrap(); - assert_eq!(expected, actual); - } - - #[test] - fn de_private_chat_text_message() { - let text = r#" - { - "message": { - "chat": { - "first_name": "Hirrolot", - "id": 408258968, - "type": "private", - "username": "hirrolot" - }, - "date": 1581448857, - "from": { - "first_name": "Hirrolot", - "id": 408258968, - "is_bot": false, - "language_code": "en", - "username": "hirrolot" - }, - "message_id": 154, - "text": "4" - }, - "update_id": 306197398 - } -"#; - - assert!(serde_json::from_str::(text).is_ok()); - } - - #[test] - fn pinned_message_works() { - let json = r#"{ - "message": { - "chat": { - "id": -1001276785818, - "title": "teloxide dev", - "type": "supergroup", - "username": "teloxide_dev" - }, - "date": 1582134655, - "from": { - "first_name": "Hirrolot", - "id": 408258968, - "is_bot": false, - "username": "hirrolot" - }, - "message_id": 20225, - "pinned_message": { - "chat": { - "id": -1001276785818, - "title": "teloxide dev", - "type": "supergroup", - "username": "teloxide_dev" - }, - "date": 1582134643, - "from": { - "first_name": "Hirrolot", - "id": 408258968, - "is_bot": false, - "username": "hirrolot" - }, - "message_id": 20224, - "text": "Faster than a bullet" - } - }, - "update_id": 845402291 -}"#; - - serde_json::from_str::(json).unwrap(); - } - - #[test] - fn dice_works() { - let json = r#" - { - "message": { - "chat": { - "id": -1001276785818, - "title": "bla bla bla chat", - "type": "supergroup", - "username": "teloxide_dev" - }, - "date": 1596014550, - "dice": { - "emoji": "🎲", - "value": 2 - }, - "from": { - "first_name": "Hirrolot", - "id": 408258968, - "is_bot": false, - "language_code": "en", - "username": "hirrolot" - }, - "message_id": 35410 - }, - "update_id": 573255266 -} - "#; - - serde_json::from_str::(json).unwrap(); - } -} diff --git a/src/types/user.rs b/src/types/user.rs deleted file mode 100644 index d24f5caa..00000000 --- a/src/types/user.rs +++ /dev/null @@ -1,132 +0,0 @@ -use serde::{Deserialize, Serialize}; - -/// This object represents a Telegram user or bot. -/// -/// [The official docs](https://core.telegram.org/bots/api#user). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct User { - /// Unique identifier for this user or bot. - pub id: i32, - - /// `true`, if this user is a bot. - pub is_bot: bool, - - /// User‘s or bot’s first name. - pub first_name: String, - - /// User‘s or bot’s last name. - pub last_name: Option, - - /// User‘s or bot’s username. - pub username: Option, - - /// [IETF language tag] of the user's language. - /// - /// [IETF language tag]: https://en.wikipedia.org/wiki/IETF_language_tag - pub language_code: Option, -} - -impl User { - pub fn new(id: i32, is_bot: bool, first_name: S) -> Self - where - S: Into, - { - Self { - id, - is_bot, - first_name: first_name.into(), - last_name: None, - username: None, - language_code: None, - } - } - - pub fn id(mut self, val: i32) -> Self { - self.id = val; - self - } - - #[allow(clippy::wrong_self_convention)] - pub fn is_bot(mut self, val: bool) -> Self { - self.is_bot = val; - self - } - - pub fn first_name(mut self, val: S) -> Self - where - S: Into, - { - self.first_name = val.into(); - self - } - - pub fn last_name(mut self, val: S) -> Self - where - S: Into, - { - self.last_name = Some(val.into()); - self - } - - pub fn username(mut self, val: S) -> Self - where - S: Into, - { - self.username = Some(val.into()); - self - } - - pub fn language_code(mut self, val: S) -> Self - where - S: Into, - { - self.language_code = Some(val.into()); - self - } -} - -impl User { - pub fn full_name(&self) -> String { - match &self.last_name { - Some(last_name) => (format!("{0} {1}", self.first_name, last_name)), - None => self.first_name.clone(), - } - } - - pub fn mention(&self) -> Option { - Some(format!("@{}", self.username.as_ref()?)) - } - - pub fn url(&self) -> reqwest::Url { - reqwest::Url::parse(format!("tg://user/?id={}", self.id).as_str()).unwrap() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn deserialize() { - let json = r#"{ - "id":12345, - "is_bot":false, - "first_name":"firstName", - "last_name":"lastName", - "username":"Username", - "language_code":"ru" - }"#; - let expected = User { - id: 12345, - is_bot: false, - first_name: "firstName".to_string(), - last_name: Some("lastName".to_string()), - username: Some("Username".to_string()), - language_code: Some(String::from("ru")), - }; - let actual = serde_json::from_str::(&json).unwrap(); - assert_eq!(actual, expected) - } -} diff --git a/src/types/user_profile_photos.rs b/src/types/user_profile_photos.rs deleted file mode 100644 index 26d8a934..00000000 --- a/src/types/user_profile_photos.rs +++ /dev/null @@ -1,40 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::PhotoSize; - -/// This object represent a user's profile pictures. -/// -/// [The official docs](https://core.telegram.org/bots/api#userprofilephotos). -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct UserProfilePhotos { - /// Total number of profile pictures the target user has. - pub total_count: u32, - - /// Requested profile pictures (in up to 4 sizes each). - pub photos: Vec>, -} - -impl UserProfilePhotos { - pub fn new(total_count: u32, photos: P1) -> Self - where - P1: Into>, - P2: Into>, - { - Self { total_count, photos: photos.into().into_iter().map(Into::into).collect() } - } - - pub fn total_count(mut self, val: u32) -> Self { - self.total_count = val; - self - } - - pub fn photos(mut self, val: P1) -> Self - where - P1: Into>, - P2: Into>, - { - self.photos = val.into().into_iter().map(Into::into).collect(); - self - } -} diff --git a/src/types/venue.rs b/src/types/venue.rs deleted file mode 100644 index 3a8ff38a..00000000 --- a/src/types/venue.rs +++ /dev/null @@ -1,74 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::Location; - -/// This object represents a venue. -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct Venue { - /// Venue location. - pub location: Location, - - /// Name of the venue. - pub title: String, - - /// Address of the venue. - pub address: String, - - /// Foursquare identifier of the venue. - pub foursquare_id: Option, - - /// Foursquare type of the venue. (For example, - /// `arts_entertainment/default`, `arts_entertainment/aquarium` or - /// `food/icecream`.) - pub foursquare_type: Option, -} - -impl Venue { - pub fn new(location: Location, title: S1, address: S2) -> Self - where - S1: Into, - S2: Into, - { - Self { - location, - title: title.into(), - address: address.into(), - foursquare_id: None, - foursquare_type: None, - } - } - - pub fn title(mut self, val: S) -> Self - where - S: Into, - { - self.title = val.into(); - self - } - - pub fn address(mut self, val: S) -> Self - where - S: Into, - { - self.address = val.into(); - self - } - - pub fn foursquare_id(mut self, val: S) -> Self - where - S: Into, - { - self.foursquare_id = Some(val.into()); - self - } - - pub fn foursquare_type(mut self, val: S) -> Self - where - S: Into, - { - self.foursquare_type = Some(val.into()); - self - } -} diff --git a/src/types/video.rs b/src/types/video.rs deleted file mode 100644 index 84de086a..00000000 --- a/src/types/video.rs +++ /dev/null @@ -1,108 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{MimeWrapper, PhotoSize}; - -/// This object represents a video file. -/// -/// [The official docs](https://core.telegram.org/bots/api#video). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct Video { - /// Identifier for this file. - pub file_id: String, - - /// Unique identifier for this file, which is supposed to be the same over - /// time and for different bots. Can't be used to download or reuse the - /// file. - pub file_unique_id: String, - - /// Video width as defined by sender. - pub width: u32, - - /// Video height as defined by sender. - pub height: u32, - - /// Duration of the video in seconds as defined by sender. - pub duration: u32, - - /// Video thumbnail. - pub thumb: Option, - - /// Mime type of a file as defined by sender. - pub mime_type: Option, - - /// File size. - pub file_size: Option, -} - -impl Video { - pub fn new( - file_id: S1, - file_unique_id: S2, - width: u32, - height: u32, - duration: u32, - ) -> Self - where - S1: Into, - S2: Into, - { - Self { - file_id: file_id.into(), - file_unique_id: file_unique_id.into(), - width, - height, - duration, - thumb: None, - mime_type: None, - file_size: None, - } - } - - pub fn file_id(mut self, val: S) -> Self - where - S: Into, - { - self.file_id = val.into(); - self - } - - pub fn file_unique_id(mut self, val: S) -> Self - where - S: Into, - { - self.file_unique_id = val.into(); - self - } - - pub fn width(mut self, val: u32) -> Self { - self.width = val; - self - } - - pub fn height(mut self, val: u32) -> Self { - self.height = val; - self - } - - pub fn duration(mut self, val: u32) -> Self { - self.duration = val; - self - } - - pub fn thumb(mut self, val: PhotoSize) -> Self { - self.thumb = Some(val); - self - } - - pub fn mime_type(mut self, val: MimeWrapper) -> Self { - self.mime_type = Some(val); - self - } - - pub fn file_size(mut self, val: u32) -> Self { - self.file_size = Some(val); - self - } -} diff --git a/src/types/video_note.rs b/src/types/video_note.rs deleted file mode 100644 index 467ace0e..00000000 --- a/src/types/video_note.rs +++ /dev/null @@ -1,89 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::PhotoSize; - -/// This object represents a [video message] (available in Telegram apps as of -/// [v.4.0]). -/// -/// [The official docs](https://core.telegram.org/bots/api#videonote). -/// -/// [video message]: https://telegram.org/blog/video-messages-and-telescope -/// [v4.0]: https://telegram.org/blog/video-messages-and-telescope -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct VideoNote { - /// Identifier for this file. - pub file_id: String, - - /// Unique identifier for this file, which is supposed to be the same over - /// time and for different bots. Can't be used to download or reuse the - /// file. - pub file_unique_id: String, - - /// Video width and height (diameter of the video message) as defined by - /// sender. - pub length: u32, - - /// Duration of the video in seconds as defined by sender. - pub duration: u32, - - /// Video thumbnail. - pub thumb: Option, - - /// File size. - pub file_size: Option, -} - -impl VideoNote { - pub fn new(file_id: S1, file_unique_id: S2, length: u32, duration: u32) -> Self - where - S1: Into, - S2: Into, - { - Self { - file_id: file_id.into(), - file_unique_id: file_unique_id.into(), - length, - duration, - thumb: None, - file_size: None, - } - } - - pub fn file_id(mut self, val: S) -> Self - where - S: Into, - { - self.file_id = val.into(); - self - } - - pub fn file_unique_id(mut self, val: S) -> Self - where - S: Into, - { - self.file_unique_id = val.into(); - self - } - - pub fn length(mut self, val: u32) -> Self { - self.length = val; - self - } - - pub fn duration(mut self, val: u32) -> Self { - self.duration = val; - self - } - - pub fn thumb(mut self, val: PhotoSize) -> Self { - self.thumb = Some(val); - self - } - - pub fn file_size(mut self, val: u32) -> Self { - self.file_size = Some(val); - self - } -} diff --git a/src/types/voice.rs b/src/types/voice.rs deleted file mode 100644 index bd522cbb..00000000 --- a/src/types/voice.rs +++ /dev/null @@ -1,74 +0,0 @@ -use crate::types::MimeWrapper; -use serde::{Deserialize, Serialize}; - -/// This object represents a voice note. -/// -/// [The official docs](https://core.telegram.org/bots/api#voice). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct Voice { - /// Identifier for this file. - pub file_id: String, - - /// Unique identifier for this file, which is supposed to be the same over - /// time and for different bots. Can't be used to download or reuse the - /// file. - pub file_unique_id: String, - - /// Duration of the audio in seconds as defined by sender. - pub duration: u32, - - /// MIME type of the file as defined by sender. - pub mime_type: Option, - - /// File size. - pub file_size: Option, -} - -impl Voice { - pub fn new(file_id: S1, file_unique_id: S2, duration: u32) -> Self - where - S1: Into, - S2: Into, - { - Self { - file_id: file_id.into(), - file_unique_id: file_unique_id.into(), - duration, - mime_type: None, - file_size: None, - } - } - - pub fn file_id(mut self, val: S) -> Self - where - S: Into, - { - self.file_id = val.into(); - self - } - - pub fn file_unique_id(mut self, val: S) -> Self - where - S: Into, - { - self.file_unique_id = val.into(); - self - } - - pub fn duration(mut self, val: u32) -> Self { - self.duration = val; - self - } - - pub fn mime_type(mut self, val: MimeWrapper) -> Self { - self.mime_type = Some(val); - self - } - - pub fn file_size(mut self, val: u64) -> Self { - self.file_size = Some(val); - self - } -} diff --git a/src/types/webhook_info.rs b/src/types/webhook_info.rs deleted file mode 100644 index 782a588d..00000000 --- a/src/types/webhook_info.rs +++ /dev/null @@ -1,98 +0,0 @@ -use serde::{Deserialize, Serialize}; - -/// Contains information about the current status of a webhook. -/// -/// [The official docs](https://core.telegram.org/bots/api#webhookinfo). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct WebhookInfo { - /// Webhook URL, may be empty if webhook is not set up. - pub url: String, - - /// `true`, if a custom certificate was provided for webhook certificate - /// checks. - pub has_custom_certificate: bool, - - /// Number of updates awaiting delivery. - pub pending_update_count: u32, - - /// Unix time for the most recent error that happened when trying to - /// deliver an update via webhook. - pub last_error_date: Option, - - /// Error message in human-readable format for the most recent error that - /// happened when trying to deliver an update via webhook. - pub last_error_message: Option, - - /// Maximum allowed number of simultaneous HTTPS connections to the webhook - /// for update delivery. - pub max_connections: Option, - - /// A list of update types the bot is subscribed to. Defaults to all update - /// types. - pub allowed_updates: Option>, -} - -impl WebhookInfo { - pub fn new(url: S, has_custom_certificate: bool, pending_update_count: u32) -> Self - where - S: Into, - { - Self { - url: url.into(), - has_custom_certificate, - pending_update_count, - last_error_date: None, - - last_error_message: None, - max_connections: None, - allowed_updates: None, - } - } - - pub fn url(mut self, val: S) -> Self - where - S: Into, - { - self.url = val.into(); - self - } - - pub fn has_custom_certificate(mut self, val: bool) -> Self { - self.has_custom_certificate = val; - self - } - - pub fn pending_update_count(mut self, val: u32) -> Self { - self.pending_update_count = val; - self - } - - pub fn last_error_date(mut self, val: u64) -> Self { - self.last_error_date = Some(val); - self - } - - pub fn last_error_message(mut self, val: S) -> Self - where - S: Into, - { - self.last_error_message = Some(val.into()); - self - } - - pub fn max_connections(mut self, val: u32) -> Self { - self.max_connections = Some(val); - self - } - - pub fn allowed_updates(mut self, val: A) -> Self - where - A: Into>, - S: Into, - { - self.allowed_updates = Some(val.into().into_iter().map(Into::into).collect()); - self - } -} diff --git a/src/utils/client_from_env.rs b/src/utils/client_from_env.rs deleted file mode 100644 index 8857e387..00000000 --- a/src/utils/client_from_env.rs +++ /dev/null @@ -1,26 +0,0 @@ -use crate::bot::{sound_bot, TELOXIDE_PROXY}; - -/// Constructs a client from the `TELOXIDE_PROXY` environmental variable. -/// -/// This function passes the value of `TELOXIDE_PROXY` into -/// [`reqwest::Proxy::all`], if it exists, otherwise returns the default -/// client. -/// -/// # Note -/// The created client will have safe settings, meaning that it will be able to -/// work in long time durations, see the [issue 223]. -/// -/// [`reqwest::Proxy::all`]: https://docs.rs/reqwest/latest/reqwest/struct.Proxy.html#method.all -/// [issue 223]: https://github.com/teloxide/teloxide/issues/223 -pub fn client_from_env() -> reqwest::Client { - use reqwest::Proxy; - - let builder = sound_bot(); - - match std::env::var(TELOXIDE_PROXY).ok() { - Some(proxy) => builder.proxy(Proxy::all(&proxy).expect("creating reqwest::Proxy")), - None => builder, - } - .build() - .expect("creating reqwest::Client") -} diff --git a/src/utils/html.rs b/src/utils/html.rs index 71c7bec9..cf22acc4 100644 --- a/src/utils/html.rs +++ b/src/utils/html.rs @@ -1,8 +1,8 @@ //! Utils for working with the [HTML message style][spec]. //! //! [spec]: https://core.telegram.org/bots/api#html-style -use crate::types::User; -use std::string::String; + +use teloxide_core::types::User; /// Applies the bold font style to the string. /// diff --git a/src/utils/markdown.rs b/src/utils/markdown.rs index f96236d6..f96fafaa 100644 --- a/src/utils/markdown.rs +++ b/src/utils/markdown.rs @@ -1,8 +1,8 @@ //! Utils for working with the [Markdown V2 message style][spec]. //! //! [spec]: https://core.telegram.org/bots/api#markdownv2-style -use crate::types::User; -use std::string::String; + +use teloxide_core::types::User; /// Applies the bold font style to the string. /// @@ -131,6 +131,7 @@ pub fn user_mention_or_link(user: &User) -> String { #[cfg(test)] mod tests { use super::*; + use teloxide_core::types::User; #[test] fn test_bold() { diff --git a/src/utils/mod.rs b/src/utils/mod.rs index e20a7a81..e34561ae 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,12 +1,11 @@ //! Some useful utilities. -mod client_from_env; pub mod command; pub mod html; pub mod markdown; mod up_state; -pub use client_from_env::client_from_env; +pub use teloxide_core::net::client_from_env; #[cfg(feature = "frunk")] // FIXME(waffle): use `docsrs` here when issue with combine is resolved From d52382e2710364eaa52dc9f48cd5b536be03aa6e Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sat, 6 Mar 2021 03:24:10 +0600 Subject: [PATCH 010/131] Oops, push the examples too --- examples/admin_bot/src/main.rs | 27 ++++++++++--------- .../src/dialogue/states/receive_age.rs | 6 ++--- .../src/dialogue/states/receive_full_name.rs | 4 +-- .../src/dialogue/states/receive_location.rs | 4 +-- .../dialogue_bot/src/dialogue/states/start.rs | 8 ++++-- examples/dialogue_bot/src/main.rs | 9 ++++--- examples/dices_bot/src/main.rs | 4 +-- examples/heroku_ping_pong_bot/src/main.rs | 10 +++---- examples/ngrok_ping_pong_bot/src/main.rs | 9 +++---- examples/redis_remember_bot/src/main.rs | 11 +++++--- .../redis_remember_bot/src/transitions.rs | 18 ++++++++----- examples/shared_state_bot/Cargo.toml | 1 + examples/shared_state_bot/src/main.rs | 9 ++++--- examples/simple_commands_bot/src/main.rs | 8 +++--- examples/sqlite_remember_bot/src/main.rs | 12 ++++++--- .../sqlite_remember_bot/src/transitions.rs | 18 ++++++++----- 16 files changed, 92 insertions(+), 66 deletions(-) diff --git a/examples/admin_bot/src/main.rs b/examples/admin_bot/src/main.rs index ee239fa0..e98eef97 100644 --- a/examples/admin_bot/src/main.rs +++ b/examples/admin_bot/src/main.rs @@ -1,6 +1,8 @@ -use std::str::FromStr; +use std::{convert::TryInto, str::FromStr}; -use teloxide::{prelude::*, types::ChatPermissions, utils::command::BotCommand}; +use teloxide::{prelude::*, utils::command::BotCommand}; + +use teloxide::core::types::ChatPermissions; // Derive BotCommand to parse text with a command into this enumeration. // @@ -60,20 +62,19 @@ fn calc_restrict_time(time: u32, unit: UnitOfTime) -> u32 { } } -type Cx = UpdateWithCx; +type Cx = UpdateWithCx, Message>; // Mute a user with a replied message. async fn mute_user(cx: &Cx, time: u32) -> ResponseResult<()> { match cx.update.reply_to_message() { Some(msg1) => { - cx.bot + cx.requester .restrict_chat_member( cx.update.chat_id(), msg1.from().expect("Must be MessageKind::Common").id, ChatPermissions::default(), ) - .until_date(cx.update.date + time as i32) - .send() + .until_date((cx.update.date + time as i32).try_into().unwrap()) .await?; } None => { @@ -88,7 +89,10 @@ async fn kick_user(cx: &Cx) -> ResponseResult<()> { match cx.update.reply_to_message() { Some(mes) => { // bot.unban_chat_member can also kicks a user from a group chat. - cx.bot.unban_chat_member(cx.update.chat_id(), mes.from().unwrap().id).send().await?; + cx.requester + .unban_chat_member(cx.update.chat_id(), mes.from().unwrap().id) + .send() + .await?; } None => { cx.reply_to("Use this command in reply to another message").send().await?; @@ -101,13 +105,12 @@ async fn kick_user(cx: &Cx) -> ResponseResult<()> { async fn ban_user(cx: &Cx, time: u32) -> ResponseResult<()> { match cx.update.reply_to_message() { Some(message) => { - cx.bot + cx.requester .kick_chat_member( cx.update.chat_id(), message.from().expect("Must be MessageKind::Common").id, ) - .until_date(cx.update.date + time as i32) - .send() + .until_date((cx.update.date + time as i32).try_into().unwrap()) .await?; } None => { @@ -117,7 +120,7 @@ async fn ban_user(cx: &Cx, time: u32) -> ResponseResult<()> { Ok(()) } -async fn action(cx: UpdateWithCx, command: Command) -> ResponseResult<()> { +async fn action(cx: Cx, command: Command) -> ResponseResult<()> { match command { Command::Help => cx.answer(Command::descriptions()).send().await.map(|_| ())?, Command::Kick => kick_user(&cx).await?, @@ -137,7 +140,7 @@ async fn run() { teloxide::enable_logging!(); log::info!("Starting admin_bot..."); - let bot = Bot::from_env(); + let bot = Bot::from_env().auto_send(); let bot_name: String = panic!("Your bot's name here"); teloxide::commands_repl(bot, bot_name, action).await; diff --git a/examples/dialogue_bot/src/dialogue/states/receive_age.rs b/examples/dialogue_bot/src/dialogue/states/receive_age.rs index faa42bc7..7c3c33e6 100644 --- a/examples/dialogue_bot/src/dialogue/states/receive_age.rs +++ b/examples/dialogue_bot/src/dialogue/states/receive_age.rs @@ -10,16 +10,16 @@ pub struct ReceiveAgeState { #[teloxide(subtransition)] async fn receive_age_state( state: ReceiveAgeState, - cx: TransitionIn, + cx: TransitionIn>, ans: String, ) -> TransitionOut { match ans.parse::() { Ok(ans) => { - cx.answer_str("What's your location?").await?; + cx.answer("What's your location?").await?; next(ReceiveLocationState::up(state, ans)) } _ => { - cx.answer_str("Send me a number.").await?; + cx.answer("Send me a number.").await?; next(state) } } diff --git a/examples/dialogue_bot/src/dialogue/states/receive_full_name.rs b/examples/dialogue_bot/src/dialogue/states/receive_full_name.rs index 85f19ad2..dc32a1a2 100644 --- a/examples/dialogue_bot/src/dialogue/states/receive_full_name.rs +++ b/examples/dialogue_bot/src/dialogue/states/receive_full_name.rs @@ -8,9 +8,9 @@ pub struct ReceiveFullNameState; #[teloxide(subtransition)] async fn receive_full_name( state: ReceiveFullNameState, - cx: TransitionIn, + cx: TransitionIn>, ans: String, ) -> TransitionOut { - cx.answer_str("How old are you?").await?; + cx.answer("How old are you?").await?; next(ReceiveAgeState::up(state, ans)) } diff --git a/examples/dialogue_bot/src/dialogue/states/receive_location.rs b/examples/dialogue_bot/src/dialogue/states/receive_location.rs index d64f35f3..ee881bbd 100644 --- a/examples/dialogue_bot/src/dialogue/states/receive_location.rs +++ b/examples/dialogue_bot/src/dialogue/states/receive_location.rs @@ -11,10 +11,10 @@ pub struct ReceiveLocationState { #[teloxide(subtransition)] async fn receive_location( state: ReceiveLocationState, - cx: TransitionIn, + cx: TransitionIn>, ans: String, ) -> TransitionOut { - cx.answer_str(format!("Full name: {}\nAge: {}\nLocation: {}", state.full_name, state.age, ans)) + cx.answer(format!("Full name: {}\nAge: {}\nLocation: {}", state.full_name, state.age, ans)) .await?; exit() } diff --git a/examples/dialogue_bot/src/dialogue/states/start.rs b/examples/dialogue_bot/src/dialogue/states/start.rs index e5354159..dfe28be9 100644 --- a/examples/dialogue_bot/src/dialogue/states/start.rs +++ b/examples/dialogue_bot/src/dialogue/states/start.rs @@ -5,7 +5,11 @@ use teloxide_macros::teloxide; pub struct StartState; #[teloxide(subtransition)] -async fn start(_state: StartState, cx: TransitionIn, _ans: String) -> TransitionOut { - cx.answer_str("Let's start! What's your full name?").await?; +async fn start( + _state: StartState, + cx: TransitionIn>, + _ans: String, +) -> TransitionOut { + cx.answer("Let's start! What's your full name?").await?; next(ReceiveFullNameState) } diff --git a/examples/dialogue_bot/src/main.rs b/examples/dialogue_bot/src/main.rs index e2c64b07..310a69a3 100644 --- a/examples/dialogue_bot/src/main.rs +++ b/examples/dialogue_bot/src/main.rs @@ -34,7 +34,7 @@ async fn run() { teloxide::enable_logging!(); log::info!("Starting dialogue_bot..."); - let bot = Bot::from_env(); + let bot = Bot::from_env().auto_send(); teloxide::dialogues_repl(bot, |message, dialogue| async move { handle_message(message, dialogue).await.expect("Something wrong with the bot!") @@ -42,10 +42,13 @@ async fn run() { .await; } -async fn handle_message(cx: UpdateWithCx, dialogue: Dialogue) -> TransitionOut { +async fn handle_message( + cx: UpdateWithCx, Message>, + dialogue: Dialogue, +) -> TransitionOut { match cx.update.text_owned() { None => { - cx.answer_str("Send me a text message.").await?; + cx.answer("Send me a text message.").await?; next(dialogue) } Some(ans) => dialogue.react(cx, ans).await, diff --git a/examples/dices_bot/src/main.rs b/examples/dices_bot/src/main.rs index 0f5e46bd..99cb370e 100644 --- a/examples/dices_bot/src/main.rs +++ b/examples/dices_bot/src/main.rs @@ -11,10 +11,10 @@ async fn run() { teloxide::enable_logging!(); log::info!("Starting dices_bot..."); - let bot = Bot::from_env(); + let bot = Bot::from_env().auto_send(); teloxide::repl(bot, |message| async move { - message.answer_dice().send().await?; + message.answer_dice().await?; respond(()) }) .await; diff --git a/examples/heroku_ping_pong_bot/src/main.rs b/examples/heroku_ping_pong_bot/src/main.rs index 23bae8f6..1cfdb396 100644 --- a/examples/heroku_ping_pong_bot/src/main.rs +++ b/examples/heroku_ping_pong_bot/src/main.rs @@ -19,7 +19,7 @@ async fn handle_rejection(error: warp::Rejection) -> Result(bot: Bot) -> impl update_listeners::UpdateListener { +pub async fn webhook<'a>(bot: AutoSend) -> impl update_listeners::UpdateListener { // Heroku defines auto defines a port value let teloxide_token = env::var("TELOXIDE_TOKEN").expect("TELOXIDE_TOKEN env variable missing"); let port: u16 = env::var("PORT") @@ -31,7 +31,7 @@ pub async fn webhook<'a>(bot: Bot) -> impl update_listeners::UpdateListener::Ok(()) + message.answer("pong").await?; + respond(()) }, webhook(cloned_bot).await, ) diff --git a/examples/ngrok_ping_pong_bot/src/main.rs b/examples/ngrok_ping_pong_bot/src/main.rs index 0fb745fb..9f7ae1d0 100644 --- a/examples/ngrok_ping_pong_bot/src/main.rs +++ b/examples/ngrok_ping_pong_bot/src/main.rs @@ -19,11 +19,10 @@ async fn handle_rejection(error: warp::Rejection) -> Result(bot: Bot) -> impl update_listeners::UpdateListener { +pub async fn webhook<'a>(bot: AutoSend) -> impl update_listeners::UpdateListener { // You might want to specify a self-signed certificate via .certificate // method on SetWebhook. bot.set_webhook("Your HTTPS ngrok URL here. Get it by 'ngrok http 80'") - .send() .await .expect("Cannot setup a webhook"); @@ -53,14 +52,14 @@ async fn run() { teloxide::enable_logging!(); log::info!("Starting ngrok_ping_pong_bot..."); - let bot = Bot::from_env(); + let bot = Bot::from_env().auto_send(); let cloned_bot = bot.clone(); teloxide::repl_with_listener( bot, |message| async move { - message.answer_str("pong").await?; - ResponseResult::<()>::Ok(()) + message.answer("pong").await?; + respond(()) }, webhook(cloned_bot).await, ) diff --git a/examples/redis_remember_bot/src/main.rs b/examples/redis_remember_bot/src/main.rs index 08f77db9..7176b75b 100644 --- a/examples/redis_remember_bot/src/main.rs +++ b/examples/redis_remember_bot/src/main.rs @@ -22,7 +22,7 @@ enum Error { StorageError(#[from] StorageError), } -type In = DialogueWithCx; +type In = DialogueWithCx, Message, Dialogue, StorageError>; #[tokio::main] async fn main() { @@ -30,7 +30,7 @@ async fn main() { } async fn run() { - let bot = Bot::from_env(); + let bot = Bot::from_env().auto_send(); Dispatcher::new(bot) .messages_handler(DialogueDispatcher::with_storage( |DialogueWithCx { cx, dialogue }: In| async move { @@ -47,10 +47,13 @@ async fn run() { .await; } -async fn handle_message(cx: UpdateWithCx, dialogue: Dialogue) -> TransitionOut { +async fn handle_message( + cx: UpdateWithCx, Message>, + dialogue: Dialogue, +) -> TransitionOut { match cx.update.text_owned() { None => { - cx.answer_str("Send me a text message.").await?; + cx.answer("Send me a text message.").await?; next(dialogue) } Some(ans) => dialogue.react(cx, ans).await, diff --git a/examples/redis_remember_bot/src/transitions.rs b/examples/redis_remember_bot/src/transitions.rs index dcc78db9..263f1991 100644 --- a/examples/redis_remember_bot/src/transitions.rs +++ b/examples/redis_remember_bot/src/transitions.rs @@ -4,12 +4,16 @@ use teloxide_macros::teloxide; use super::states::*; #[teloxide(subtransition)] -async fn start(state: StartState, cx: TransitionIn, ans: String) -> TransitionOut { +async fn start( + state: StartState, + cx: TransitionIn>, + ans: String, +) -> TransitionOut { if let Ok(number) = ans.parse() { - cx.answer_str(format!("Remembered number {}. Now use /get or /reset", number)).await?; + cx.answer(format!("Remembered number {}. Now use /get or /reset", number)).await?; next(HaveNumberState { number }) } else { - cx.answer_str("Please, send me a number").await?; + cx.answer("Please, send me a number").await?; next(state) } } @@ -17,19 +21,19 @@ async fn start(state: StartState, cx: TransitionIn, ans: String) -> TransitionOu #[teloxide(subtransition)] async fn have_number( state: HaveNumberState, - cx: TransitionIn, + cx: TransitionIn>, ans: String, ) -> TransitionOut { let num = state.number; if ans.starts_with("/get") { - cx.answer_str(format!("Here is your number: {}", num)).await?; + cx.answer(format!("Here is your number: {}", num)).await?; next(state) } else if ans.starts_with("/reset") { - cx.answer_str("Resetted number").await?; + cx.answer("Resetted number").await?; next(StartState) } else { - cx.answer_str("Please, send /get or /reset").await?; + cx.answer("Please, send /get or /reset").await?; next(state) } } diff --git a/examples/shared_state_bot/Cargo.toml b/examples/shared_state_bot/Cargo.toml index be1fbf6b..f8127852 100644 --- a/examples/shared_state_bot/Cargo.toml +++ b/examples/shared_state_bot/Cargo.toml @@ -10,5 +10,6 @@ edition = "2018" log = "0.4.8" pretty_env_logger = "0.4.0" tokio = { version = "0.2.11", features = ["rt-threaded", "macros"] } +tokio-stream = "0.1.3" teloxide = { path = "../../" } lazy_static = "1.4.0" diff --git a/examples/shared_state_bot/src/main.rs b/examples/shared_state_bot/src/main.rs index 164f5a7a..123de9dc 100644 --- a/examples/shared_state_bot/src/main.rs +++ b/examples/shared_state_bot/src/main.rs @@ -4,6 +4,7 @@ use std::sync::atomic::{AtomicU64, Ordering}; use lazy_static::lazy_static; use teloxide::prelude::*; +use tokio_stream::wrappers::UnboundedReceiverStream; lazy_static! { static ref MESSAGES_TOTAL: AtomicU64 = AtomicU64::new(0); @@ -18,15 +19,15 @@ async fn run() { teloxide::enable_logging!(); log::info!("Starting shared_state_bot..."); - let bot = Bot::from_env(); + let bot = Bot::from_env().auto_send(); Dispatcher::new(bot) - .messages_handler(|rx: DispatcherHandlerRx| { - rx.for_each_concurrent(None, |message| async move { + .messages_handler(|rx: DispatcherHandlerRx, Message>| { + UnboundedReceiverStream::new(rx).for_each_concurrent(None, |message| async move { let previous = MESSAGES_TOTAL.fetch_add(1, Ordering::Relaxed); message - .answer_str(format!("I received {} messages in total.", previous)) + .answer(format!("I received {} messages in total.", previous)) .await .log_on_error() .await; diff --git a/examples/simple_commands_bot/src/main.rs b/examples/simple_commands_bot/src/main.rs index 86a61bb6..a510a72c 100644 --- a/examples/simple_commands_bot/src/main.rs +++ b/examples/simple_commands_bot/src/main.rs @@ -11,14 +11,14 @@ enum Command { UsernameAndAge { username: String, age: u8 }, } -async fn answer(cx: UpdateWithCx, command: Command) -> ResponseResult<()> { +async fn answer(cx: UpdateWithCx, Message>, command: Command) -> ResponseResult<()> { match command { Command::Help => cx.answer(Command::descriptions()).send().await?, Command::Username(username) => { - cx.answer_str(format!("Your username is @{}.", username)).await? + cx.answer(format!("Your username is @{}.", username)).await? } Command::UsernameAndAge { username, age } => { - cx.answer_str(format!("Your username is @{} and age is {}.", username, age)).await? + cx.answer(format!("Your username is @{} and age is {}.", username, age)).await? } }; @@ -34,7 +34,7 @@ async fn run() { teloxide::enable_logging!(); log::info!("Starting simple_commands_bot..."); - let bot = Bot::from_env(); + let bot = Bot::from_env().auto_send(); let bot_name: String = panic!("Your bot's name here"); teloxide::commands_repl(bot, bot_name, answer).await; diff --git a/examples/sqlite_remember_bot/src/main.rs b/examples/sqlite_remember_bot/src/main.rs index f48ed648..337bea8a 100644 --- a/examples/sqlite_remember_bot/src/main.rs +++ b/examples/sqlite_remember_bot/src/main.rs @@ -22,12 +22,15 @@ enum Error { StorageError(#[from] StorageError), } -type In = DialogueWithCx; +type In = DialogueWithCx, Message, Dialogue, StorageError>; -async fn handle_message(cx: UpdateWithCx, dialogue: Dialogue) -> TransitionOut { +async fn handle_message( + cx: UpdateWithCx, Message>, + dialogue: Dialogue, +) -> TransitionOut { match cx.update.text_owned() { None => { - cx.answer_str("Send me a text message.").await?; + cx.answer("Send me a text message.").await?; next(dialogue) } Some(ans) => dialogue.react(cx, ans).await, @@ -36,7 +39,8 @@ async fn handle_message(cx: UpdateWithCx, dialogue: Dialogue) -> Transi #[tokio::main] async fn main() { - let bot = Bot::from_env(); + let bot = Bot::from_env().auto_send(); + Dispatcher::new(bot) .messages_handler(DialogueDispatcher::with_storage( |DialogueWithCx { cx, dialogue }: In| async move { diff --git a/examples/sqlite_remember_bot/src/transitions.rs b/examples/sqlite_remember_bot/src/transitions.rs index dcc78db9..263f1991 100644 --- a/examples/sqlite_remember_bot/src/transitions.rs +++ b/examples/sqlite_remember_bot/src/transitions.rs @@ -4,12 +4,16 @@ use teloxide_macros::teloxide; use super::states::*; #[teloxide(subtransition)] -async fn start(state: StartState, cx: TransitionIn, ans: String) -> TransitionOut { +async fn start( + state: StartState, + cx: TransitionIn>, + ans: String, +) -> TransitionOut { if let Ok(number) = ans.parse() { - cx.answer_str(format!("Remembered number {}. Now use /get or /reset", number)).await?; + cx.answer(format!("Remembered number {}. Now use /get or /reset", number)).await?; next(HaveNumberState { number }) } else { - cx.answer_str("Please, send me a number").await?; + cx.answer("Please, send me a number").await?; next(state) } } @@ -17,19 +21,19 @@ async fn start(state: StartState, cx: TransitionIn, ans: String) -> TransitionOu #[teloxide(subtransition)] async fn have_number( state: HaveNumberState, - cx: TransitionIn, + cx: TransitionIn>, ans: String, ) -> TransitionOut { let num = state.number; if ans.starts_with("/get") { - cx.answer_str(format!("Here is your number: {}", num)).await?; + cx.answer(format!("Here is your number: {}", num)).await?; next(state) } else if ans.starts_with("/reset") { - cx.answer_str("Resetted number").await?; + cx.answer("Resetted number").await?; next(StartState) } else { - cx.answer_str("Please, send /get or /reset").await?; + cx.answer("Please, send /get or /reset").await?; next(state) } } From 226249441af4995d529a95af99392f38e97ff45a Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sat, 6 Mar 2021 03:50:11 +0600 Subject: [PATCH 011/131] Fix the tests --- .../dialogue/dialogue_dispatcher.rs | 21 ++++++++++--------- tests/sqlite.rs | 6 +++--- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/dispatching/dialogue/dialogue_dispatcher.rs b/src/dispatching/dialogue/dialogue_dispatcher.rs index 2e722d70..4353debc 100644 --- a/src/dispatching/dialogue/dialogue_dispatcher.rs +++ b/src/dispatching/dialogue/dialogue_dispatcher.rs @@ -177,15 +177,15 @@ where mod tests { use super::*; - use crate::Bot; use futures::{stream, StreamExt}; use lazy_static::lazy_static; + use teloxide_core::Bot; use tokio::{ sync::{mpsc, Mutex}, - time::{delay_for, Duration}, + time::Duration, }; - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] #[allow(deprecated)] async fn updates_from_same_chat_executed_sequentially() { #[derive(Debug)] @@ -212,9 +212,9 @@ mod tests { static ref SEQ3: Mutex> = Mutex::new(Vec::new()); } - let dispatcher = - DialogueDispatcher::new(|cx: DialogueWithCx| async move { - delay_for(Duration::from_millis(300)).await; + let dispatcher = DialogueDispatcher::new( + |cx: DialogueWithCx| async move { + tokio::time::sleep(Duration::from_millis(300)).await; match cx.cx.update { MyUpdate { chat_id: 1, unique_number } => { @@ -230,7 +230,8 @@ mod tests { } DialogueStage::Next(()) - }); + }, + ); let updates = stream::iter( vec![ @@ -257,8 +258,8 @@ mod tests { MyUpdate::new(3, 1611), ] .into_iter() - .map(|update| UpdateWithCx { update, bot: Bot::new("Doesn't matter here") }) - .collect::>>(), + .map(|update| UpdateWithCx { update, requester: Bot::new("Doesn't matter here") }) + .collect::>>(), ); let (tx, rx) = mpsc::unbounded_channel(); @@ -278,7 +279,7 @@ mod tests { dispatcher.handle(rx).await; // Wait until our futures to be finished. - delay_for(Duration::from_millis(3000)).await; + tokio::time::sleep(Duration::from_millis(3000)).await; assert_eq!(*SEQ1.lock().await, vec![174, 125, 2, 193, 104, 7, 7778]); assert_eq!(*SEQ2.lock().await, vec![411, 515, 623, 2222, 737, 10, 55456]); diff --git a/tests/sqlite.rs b/tests/sqlite.rs index f2af4f65..152852bd 100644 --- a/tests/sqlite.rs +++ b/tests/sqlite.rs @@ -5,7 +5,7 @@ use std::{ }; use teloxide::dispatching::dialogue::{Serializer, SqliteStorage, Storage}; -#[tokio::test(threaded_scheduler)] +#[tokio::test(flavor = "multi_thread")] async fn test_sqlite_json() { let storage = SqliteStorage::open("./test_db1.sqlite", teloxide::dispatching::dialogue::serializer::JSON) @@ -14,7 +14,7 @@ async fn test_sqlite_json() { test_sqlite(storage).await; } -#[tokio::test(threaded_scheduler)] +#[tokio::test(flavor = "multi_thread")] async fn test_sqlite_bincode() { let storage = SqliteStorage::open( "./test_db2.sqlite", @@ -25,7 +25,7 @@ async fn test_sqlite_bincode() { test_sqlite(storage).await; } -#[tokio::test(threaded_scheduler)] +#[tokio::test(flavor = "multi_thread")] async fn test_sqlite_cbor() { let storage = SqliteStorage::open("./test_db3.sqlite", teloxide::dispatching::dialogue::serializer::CBOR) From 03915422148969bb31c4c7585a64b8482bf8b767 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sat, 13 Mar 2021 22:13:24 +0600 Subject: [PATCH 012/131] Fix the examples --- Cargo.toml | 2 +- examples/admin_bot/Cargo.toml | 4 +-- examples/dialogue_bot/Cargo.toml | 2 +- examples/dices_bot/Cargo.toml | 4 +-- examples/heroku_ping_pong_bot/Cargo.toml | 5 ++-- examples/heroku_ping_pong_bot/src/main.rs | 20 +++---------- examples/ngrok_ping_pong_bot/Cargo.toml | 5 ++-- examples/ngrok_ping_pong_bot/src/main.rs | 3 +- examples/redis_remember_bot/Cargo.toml | 2 +- examples/shared_state_bot/Cargo.toml | 2 +- examples/simple_commands_bot/Cargo.toml | 2 +- examples/sqlite_remember_bot/Cargo.toml | 2 +- .../sqlite_remember_bot/db.sqlite-journal | Bin 0 -> 512 bytes .../dialogue/storage/sqlite_storage.rs | 27 +++++++----------- src/dispatching/update_listeners.rs | 15 ++++++++-- 15 files changed, 44 insertions(+), 51 deletions(-) create mode 100644 examples/sqlite_remember_bot/db.sqlite-journal diff --git a/Cargo.toml b/Cargo.toml index 10f575ff..5eae9a4f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,7 @@ macros = ["teloxide-macros"] nightly = [] # currently used for `README.md` tests and building docs for `docsrs` to add `This is supported on feature="..." only.` [dependencies] -teloxide-core = { git = "https://github.com/teloxide/teloxide-core.git", rev = "b465da5f1650893cc033d995343858371505eaf1", features = ["full"] } +teloxide-core = { git = "https://github.com/teloxide/teloxide-core.git", features = ["full"] } serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } diff --git a/examples/admin_bot/Cargo.toml b/examples/admin_bot/Cargo.toml index 8f2031bc..e598c3f9 100644 --- a/examples/admin_bot/Cargo.toml +++ b/examples/admin_bot/Cargo.toml @@ -9,8 +9,8 @@ edition = "2018" [dependencies] log = "0.4.8" pretty_env_logger = "0.4.0" -tokio = { version = "0.2.11", features = ["rt-threaded", "macros"] } +tokio = { version = "1.3.0", features = ["rt-multi-thread", "macros"] } teloxide = { path = "../../", features = ["macros"] } [profile.release] -lto = true \ No newline at end of file +lto = true diff --git a/examples/dialogue_bot/Cargo.toml b/examples/dialogue_bot/Cargo.toml index 3d36bb3c..c8e7cbc4 100644 --- a/examples/dialogue_bot/Cargo.toml +++ b/examples/dialogue_bot/Cargo.toml @@ -14,7 +14,7 @@ frunk = "0.3.1" frunk_core = "0.3.1" futures = "0.3.5" -tokio = { version = "0.2.11", features = ["rt-threaded", "macros"] } +tokio = { version = "1.3.0", features = ["rt-multi-thread", "macros"] } teloxide = { path = "../../", features = ["frunk"] } teloxide-macros = { git = "https://github.com/teloxide/teloxide-macros", branch = "master" } diff --git a/examples/dices_bot/Cargo.toml b/examples/dices_bot/Cargo.toml index 3c2a6ed8..b1b22c42 100644 --- a/examples/dices_bot/Cargo.toml +++ b/examples/dices_bot/Cargo.toml @@ -9,8 +9,8 @@ edition = "2018" [dependencies] log = "0.4.8" pretty_env_logger = "0.4.0" -tokio = { version = "0.2.11", features = ["rt-threaded", "macros"] } +tokio = { version = "1.3.0", features = ["rt-multi-thread", "macros"] } teloxide = { path = "../../" } [profile.release] -lto = true \ No newline at end of file +lto = true diff --git a/examples/heroku_ping_pong_bot/Cargo.toml b/examples/heroku_ping_pong_bot/Cargo.toml index d568e8a8..b27602b2 100644 --- a/examples/heroku_ping_pong_bot/Cargo.toml +++ b/examples/heroku_ping_pong_bot/Cargo.toml @@ -9,10 +9,11 @@ edition = "2018" [dependencies] log = "0.4.8" pretty_env_logger = "0.4.0" -tokio = { version = "0.2.11", features = ["rt-threaded", "macros"] } +tokio = { version = "1.3.0", features = ["rt-multi-thread", "macros"] } teloxide = { path = "../../" } +tokio-stream = "0.1.4" # Used to setup a webhook -warp = "0.2.2" +warp = "0.3.0" reqwest = "0.10.4" serde_json = "1.0.50" diff --git a/examples/heroku_ping_pong_bot/src/main.rs b/examples/heroku_ping_pong_bot/src/main.rs index 1cfdb396..8c4837ab 100644 --- a/examples/heroku_ping_pong_bot/src/main.rs +++ b/examples/heroku_ping_pong_bot/src/main.rs @@ -5,6 +5,7 @@ use teloxide::{dispatching::update_listeners, prelude::*}; use std::{convert::Infallible, env, net::SocketAddr}; use tokio::sync::mpsc; +use tokio_stream::wrappers::UnboundedReceiverStream; use warp::Filter; use reqwest::StatusCode; @@ -39,20 +40,7 @@ pub async fn webhook<'a>(bot: AutoSend) -> impl update_listeners::UpdateLis .and(warp::path(path)) .and(warp::body::json()) .map(move |json: serde_json::Value| { - let try_parse = match serde_json::from_str(&json.to_string()) { - Ok(update) => Ok(update), - Err(error) => { - log::error!( - "Cannot parse an update.\nError: {:?}\nValue: {}\n\ - This is a bug in teloxide, please open an issue here: \ - https://github.com/teloxide/teloxide/issues.", - error, - json - ); - Err(error) - } - }; - if let Ok(update) = try_parse { + if let Ok(update) = Update::try_parse(&json) { tx.send(Ok(update)).expect("Cannot send an incoming update from the webhook") } @@ -64,7 +52,7 @@ pub async fn webhook<'a>(bot: AutoSend) -> impl update_listeners::UpdateLis let address = format!("0.0.0.0:{}", port); tokio::spawn(serve.run(address.parse::().unwrap())); - rx + UnboundedReceiverStream::new(rx) } async fn run() { @@ -78,7 +66,7 @@ async fn run() { bot, |message| async move { message.answer("pong").await?; - respond(()) + respond(()) }, webhook(cloned_bot).await, ) diff --git a/examples/ngrok_ping_pong_bot/Cargo.toml b/examples/ngrok_ping_pong_bot/Cargo.toml index 9522426f..524484e3 100644 --- a/examples/ngrok_ping_pong_bot/Cargo.toml +++ b/examples/ngrok_ping_pong_bot/Cargo.toml @@ -9,10 +9,11 @@ edition = "2018" [dependencies] log = "0.4.8" pretty_env_logger = "0.4.0" -tokio = { version = "0.2.11", features = ["rt-threaded", "macros"] } +tokio = { version = "1.3.0", features = ["rt-multi-thread", "macros"] } +tokio-stream = "0.1.4" teloxide = { path = "../../" } # Used to setup a webhook -warp = "0.2.2" +warp = "0.3.0" reqwest = "0.10.4" serde_json = "1.0.50" diff --git a/examples/ngrok_ping_pong_bot/src/main.rs b/examples/ngrok_ping_pong_bot/src/main.rs index 9f7ae1d0..9d614807 100644 --- a/examples/ngrok_ping_pong_bot/src/main.rs +++ b/examples/ngrok_ping_pong_bot/src/main.rs @@ -5,6 +5,7 @@ use teloxide::{dispatching::update_listeners, prelude::*}; use std::{convert::Infallible, net::SocketAddr}; use tokio::sync::mpsc; +use tokio_stream::wrappers::UnboundedReceiverStream; use warp::Filter; use reqwest::StatusCode; @@ -45,7 +46,7 @@ pub async fn webhook<'a>(bot: AutoSend) -> impl update_listeners::UpdateLis // setup a self-signed TLS certificate. tokio::spawn(serve.run("127.0.0.1:80".parse::().unwrap())); - rx + UnboundedReceiverStream::new(rx) } async fn run() { diff --git a/examples/redis_remember_bot/Cargo.toml b/examples/redis_remember_bot/Cargo.toml index f7843219..18f4e618 100644 --- a/examples/redis_remember_bot/Cargo.toml +++ b/examples/redis_remember_bot/Cargo.toml @@ -7,7 +7,7 @@ edition = "2018" [dependencies] log = "0.4.8" pretty_env_logger = "0.4.0" -tokio = { version = "0.2.11", features = ["rt-threaded", "macros"] } +tokio = { version = "1.3.0", features = ["rt-multi-thread", "macros"] } # You can also choose "cbor-serializer" or built-in JSON serializer teloxide = { path = "../../", features = ["redis-storage", "bincode-serializer"] } diff --git a/examples/shared_state_bot/Cargo.toml b/examples/shared_state_bot/Cargo.toml index f8127852..ecb937ac 100644 --- a/examples/shared_state_bot/Cargo.toml +++ b/examples/shared_state_bot/Cargo.toml @@ -9,7 +9,7 @@ edition = "2018" [dependencies] log = "0.4.8" pretty_env_logger = "0.4.0" -tokio = { version = "0.2.11", features = ["rt-threaded", "macros"] } +tokio = { version = "1.3.0", features = ["rt-multi-thread", "macros"] } tokio-stream = "0.1.3" teloxide = { path = "../../" } lazy_static = "1.4.0" diff --git a/examples/simple_commands_bot/Cargo.toml b/examples/simple_commands_bot/Cargo.toml index dcf9aa20..38814c5f 100644 --- a/examples/simple_commands_bot/Cargo.toml +++ b/examples/simple_commands_bot/Cargo.toml @@ -9,5 +9,5 @@ edition = "2018" [dependencies] log = "0.4.8" pretty_env_logger = "0.4.0" -tokio = { version = "0.2.11", features = ["rt-threaded", "macros"] } +tokio = { version = "1.3.0", features = ["rt-multi-thread", "macros"] } teloxide = { path = "../../", features = ["macros"] } diff --git a/examples/sqlite_remember_bot/Cargo.toml b/examples/sqlite_remember_bot/Cargo.toml index cf4cb204..c61fbfd2 100644 --- a/examples/sqlite_remember_bot/Cargo.toml +++ b/examples/sqlite_remember_bot/Cargo.toml @@ -7,7 +7,7 @@ edition = "2018" [dependencies] log = "0.4.8" pretty_env_logger = "0.4.0" -tokio = { version = "0.2.11", features = ["rt-threaded", "macros"] } +tokio = { version = "1.3.0", features = ["rt-multi-thread", "macros"] } # You can also choose "cbor-serializer" or built-in JSON serializer teloxide = { path = "../../", features = ["sqlite-storage", "bincode-serializer", "redis-storage"] } diff --git a/examples/sqlite_remember_bot/db.sqlite-journal b/examples/sqlite_remember_bot/db.sqlite-journal new file mode 100644 index 0000000000000000000000000000000000000000..5d08624e1abe3276630bb4f22c8496cfd97f32c5 GIT binary patch literal 512 bcmZQzK!C#yyA&ZjCLkq%kQikR+YkT%M6m)f literal 0 HcmV?d00001 diff --git a/src/dispatching/dialogue/storage/sqlite_storage.rs b/src/dispatching/dialogue/storage/sqlite_storage.rs index 244da6d0..f4e4d98c 100644 --- a/src/dispatching/dialogue/storage/sqlite_storage.rs +++ b/src/dispatching/dialogue/storage/sqlite_storage.rs @@ -86,12 +86,10 @@ where dialogue: D, ) -> BoxFuture<'static, Result, Self::Error>> { Box::pin(async move { - let prev_dialogue = match get_dialogue(&self.pool, chat_id).await? { - Some(d) => { - Some(self.serializer.deserialize(&d).map_err(SqliteStorageError::SerdeError)?) - } - _ => None, - }; + let prev_dialogue = get_dialogue(&self.pool, chat_id) + .await? + .map(|d| self.serializer.deserialize(&d).map_err(SqliteStorageError::SerdeError)) + .transpose()?; let upd_dialogue = self.serializer.serialize(&dialogue).map_err(SqliteStorageError::SerdeError)?; self.pool @@ -122,16 +120,11 @@ async fn get_dialogue( pool: &SqlitePool, chat_id: i64, ) -> Result>>, sqlx::Error> { - Ok( - match sqlx::query_as::<_, DialogueDbRow>( - "SELECT dialogue FROM teloxide_dialogues WHERE chat_id = ?", - ) - .bind(chat_id) - .fetch_optional(pool) - .await? - { - Some(r) => Some(Box::new(r.dialogue)), - _ => None, - }, + Ok(sqlx::query_as::<_, DialogueDbRow>( + "SELECT dialogue FROM teloxide_dialogues WHERE chat_id = ?", ) + .bind(chat_id) + .fetch_optional(pool) + .await? + .map(|r| Box::new(r.dialogue))) } diff --git a/src/dispatching/update_listeners.rs b/src/dispatching/update_listeners.rs index 8f7432c7..0632611d 100644 --- a/src/dispatching/update_listeners.rs +++ b/src/dispatching/update_listeners.rs @@ -179,10 +179,19 @@ where offset = id + 1; } - let updates = - updates.into_iter().filter_map(Result::ok).collect::>(); + for update in &updates { + if let Err((value, e)) = update { + log::error!( + "Cannot parse an update.\nError: {:?}\nValue: {}\n\ + This is a bug in teloxide-core, please open an issue here: \ + https://github.com/teloxide/teloxide-core/issues.", + e, + value + ); + } + } - updates.into_iter().map(Ok).collect::>() + updates.into_iter().filter_map(Result::ok).map(Ok).collect::>() } }; From 8b1c3b81014e8e203e7bdf487ae77856c8436c1c Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sat, 13 Mar 2021 23:05:50 +0600 Subject: [PATCH 013/131] Fix the tests --- Cargo.toml | 2 +- src/dispatching/dialogue/dialogue_dispatcher.rs | 4 ++-- src/dispatching/mod.rs | 7 ++++--- tests/command.rs | 14 +++++++------- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5eae9a4f..40b0133b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,7 +75,7 @@ smart-default = "0.6.0" rand = "0.8.3" pretty_env_logger = "0.4.0" lazy_static = "1.4.0" -tokio = { version = "1.2.0", features = ["fs", "rt", "macros"] } +tokio = { version = "1.2.0", features = ["fs", "rt-multi-thread", "macros"] } [package.metadata.docs.rs] all-features = true diff --git a/src/dispatching/dialogue/dialogue_dispatcher.rs b/src/dispatching/dialogue/dialogue_dispatcher.rs index 4353debc..706cf952 100644 --- a/src/dispatching/dialogue/dialogue_dispatcher.rs +++ b/src/dispatching/dialogue/dialogue_dispatcher.rs @@ -185,14 +185,14 @@ mod tests { time::Duration, }; - #[tokio::test(flavor = "multi_thread")] + #[tokio::test] #[allow(deprecated)] async fn updates_from_same_chat_executed_sequentially() { #[derive(Debug)] struct MyUpdate { chat_id: i64, unique_number: u32, - }; + } impl MyUpdate { fn new(chat_id: i64, unique_number: u32) -> Self { diff --git a/src/dispatching/mod.rs b/src/dispatching/mod.rs index 373202f3..5c1e86c4 100644 --- a/src/dispatching/mod.rs +++ b/src/dispatching/mod.rs @@ -9,10 +9,11 @@ //! //! ``` //! use teloxide::prelude::*; +//! use tokio_stream::wrappers::UnboundedReceiverStream; //! -//! async fn handle_messages(rx: DispatcherHandlerRx) { -//! rx.for_each_concurrent(None, |message| async move { -//! dbg!(message); +//! async fn handle_messages(rx: DispatcherHandlerRx, Message>) { +//! UnboundedReceiverStream::new(rx).for_each_concurrent(None, |message| async move { +//! dbg!(message.update); //! }) //! .await; //! } diff --git a/tests/command.rs b/tests/command.rs index 4b059614..657bc98d 100644 --- a/tests/command.rs +++ b/tests/command.rs @@ -7,8 +7,8 @@ use teloxide::utils::command::{BotCommand, ParseError}; #[test] #[cfg(feature = "macros")] fn parse_command_with_args() { - #[command(rename = "lowercase")] #[derive(BotCommand, Debug, PartialEq)] + #[command(rename = "lowercase")] enum DefaultCommands { Start(String), Help, @@ -23,8 +23,8 @@ fn parse_command_with_args() { #[test] #[cfg(feature = "macros")] fn parse_command_with_non_string_arg() { - #[command(rename = "lowercase")] #[derive(BotCommand, Debug, PartialEq)] + #[command(rename = "lowercase")] enum DefaultCommands { Start(i32), Help, @@ -39,8 +39,8 @@ fn parse_command_with_non_string_arg() { #[test] #[cfg(feature = "macros")] fn attribute_prefix() { - #[command(rename = "lowercase")] #[derive(BotCommand, Debug, PartialEq)] + #[command(rename = "lowercase")] enum DefaultCommands { #[command(prefix = "!")] Start(String), @@ -121,9 +121,9 @@ fn parse_with_split() { #[test] #[cfg(feature = "macros")] fn parse_with_split2() { + #[derive(BotCommand, Debug, PartialEq)] #[command(rename = "lowercase")] #[command(parse_with = "split", separator = "|")] - #[derive(BotCommand, Debug, PartialEq)] enum DefaultCommands { Start(u8, String), Help, @@ -149,8 +149,8 @@ fn parse_custom_parser() { .map_err(|_| ParseError::Custom("First argument must be a integer!".to_owned().into())) } - #[command(rename = "lowercase")] #[derive(BotCommand, Debug, PartialEq)] + #[command(rename = "lowercase")] enum DefaultCommands { #[command(parse_with = "custom_parse_function")] Start(u8, String), @@ -166,9 +166,9 @@ fn parse_custom_parser() { #[test] #[cfg(feature = "macros")] fn parse_named_fields() { + #[derive(BotCommand, Debug, PartialEq)] #[command(rename = "lowercase")] #[command(parse_with = "split")] - #[derive(BotCommand, Debug, PartialEq)] enum DefaultCommands { Start { num: u8, data: String }, Help, @@ -183,8 +183,8 @@ fn parse_named_fields() { #[test] #[cfg(feature = "macros")] fn descriptions_off() { - #[command(rename = "lowercase")] #[derive(BotCommand, Debug, PartialEq)] + #[command(rename = "lowercase")] enum DefaultCommands { #[command(description = "off")] Start, From 8826263e58e66d25ff52884f3d9e79ace7bf28e6 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sat, 13 Mar 2021 23:08:35 +0600 Subject: [PATCH 014/131] Fmt --- src/dispatching/mod.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/dispatching/mod.rs b/src/dispatching/mod.rs index 5c1e86c4..651cae53 100644 --- a/src/dispatching/mod.rs +++ b/src/dispatching/mod.rs @@ -12,10 +12,11 @@ //! use tokio_stream::wrappers::UnboundedReceiverStream; //! //! async fn handle_messages(rx: DispatcherHandlerRx, Message>) { -//! UnboundedReceiverStream::new(rx).for_each_concurrent(None, |message| async move { -//! dbg!(message.update); -//! }) -//! .await; +//! UnboundedReceiverStream::new(rx) +//! .for_each_concurrent(None, |message| async move { +//! dbg!(message.update); +//! }) +//! .await; //! } //! ``` //! From 239ff94c0e203dca60bdb0ca7ec9063e2659b8e6 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sat, 13 Mar 2021 23:35:48 +0600 Subject: [PATCH 015/131] Fix this fucking bullshit --- README.md | 22 +++++++++++----------- src/dispatching/dialogue/mod.rs | 10 +++++----- src/lib.rs | 6 +++--- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 71066c35..8d02f42a 100644 --- a/README.md +++ b/README.md @@ -102,11 +102,11 @@ async fn main() { teloxide::enable_logging!(); log::info!("Starting dices_bot..."); - let bot = Bot::from_env(); + let bot = Bot::from_env().auto_send(); teloxide::repl(bot, |message| async move { - message.answer_dice().send().await?; - ResponseResult::<()>::Ok(()) + message.answer_dice().await?; + respond(()) }) .await; } @@ -143,7 +143,7 @@ enum Command { UsernameAndAge { username: String, age: u8 }, } -async fn answer(cx: UpdateWithCx, command: Command) -> ResponseResult<()> { +async fn answer(cx: UpdateWithCx, Message>, command: Command) -> ResponseResult<()> { match command { Command::Help => cx.answer(Command::descriptions()).send().await?, Command::Username(username) => { @@ -162,7 +162,7 @@ async fn main() { teloxide::enable_logging!(); log::info!("Starting simple_commands_bot..."); - let bot = Bot::from_env(); + let bot = Bot::from_env().auto_send(); let bot_name: String = panic!("Your bot's name here"); teloxide::commands_repl(bot, bot_name, answer).await; @@ -213,7 +213,7 @@ When a user sends a message to our bot and such a dialogue does not exist yet, a pub struct StartState; #[teloxide(subtransition)] -async fn start(_state: StartState, cx: TransitionIn, _ans: String) -> TransitionOut { +async fn start(_state: StartState, cx: TransitionIn>, _ans: String) -> TransitionOut { cx.answer_str("Let's start! What's your full name?").await?; next(ReceiveFullNameState) } @@ -234,7 +234,7 @@ pub struct ReceiveFullNameState; #[teloxide(subtransition)] async fn receive_full_name( state: ReceiveFullNameState, - cx: TransitionIn, + cx: TransitionIn>, ans: String, ) -> TransitionOut { cx.answer_str("How old are you?").await?; @@ -259,7 +259,7 @@ pub struct ReceiveAgeState { #[teloxide(subtransition)] async fn receive_age_state( state: ReceiveAgeState, - cx: TransitionIn, + cx: TransitionIn>, ans: String, ) -> TransitionOut { match ans.parse::() { @@ -293,7 +293,7 @@ pub struct ReceiveLocationState { #[teloxide(subtransition)] async fn receive_location( state: ReceiveLocationState, - cx: TransitionIn, + cx: TransitionIn>, ans: String, ) -> TransitionOut { cx.answer_str(format!("Full name: {}\nAge: {}\nLocation: {}", state.full_name, state.age, ans)) @@ -317,7 +317,7 @@ async fn main() { teloxide::enable_logging!(); log::info!("Starting dialogue_bot..."); - let bot = Bot::from_env(); + let bot = Bot::from_env().auto_send(); teloxide::dialogues_repl(bot, |message, dialogue| async move { handle_message(message, dialogue).await.expect("Something wrong with the bot!") @@ -325,7 +325,7 @@ async fn main() { .await; } -async fn handle_message(cx: UpdateWithCx, dialogue: Dialogue) -> TransitionOut { +async fn handle_message(cx: UpdateWithCx, Message>, dialogue: Dialogue) -> TransitionOut { match cx.update.text_owned() { None => { cx.answer_str("Send me a text message.").await?; diff --git a/src/dispatching/dialogue/mod.rs b/src/dispatching/dialogue/mod.rs index a8cdddc7..bf919e94 100644 --- a/src/dispatching/dialogue/mod.rs +++ b/src/dispatching/dialogue/mod.rs @@ -42,17 +42,17 @@ //! type Out = TransitionOut; //! //! #[teloxide(subtransition)] -//! async fn _1_transition(_state: _1State, _cx: TransitionIn) -> Out { +//! async fn _1_transition(_state: _1State, _cx: TransitionIn>) -> Out { //! todo!() //! } //! //! #[teloxide(subtransition)] -//! async fn _2_transition(_state: _2State, _cx: TransitionIn) -> Out { +//! async fn _2_transition(_state: _2State, _cx: TransitionIn>) -> Out { //! todo!() //! } //! //! #[teloxide(subtransition)] -//! async fn _3_transition(_state: _3State, _cx: TransitionIn) -> Out { +//! async fn _3_transition(_state: _3State, _cx: TransitionIn>) -> Out { //! todo!() //! } //! @@ -69,7 +69,7 @@ //! } //! } //! -//! type In = DialogueWithCx; +//! type In = DialogueWithCx, Message, D, Infallible>; //! //! #[tokio::main] //! async fn main() { @@ -80,7 +80,7 @@ //! teloxide::enable_logging!(); //! log::info!("Starting dialogue_bot!"); //! -//! let bot = Bot::from_env(); +//! let bot = Bot::from_env().auto_send(); //! //! Dispatcher::new(bot) //! .messages_handler(DialogueDispatcher::new( diff --git a/src/lib.rs b/src/lib.rs index 47f37e90..e5c27cd0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,11 +13,11 @@ //! teloxide::enable_logging!(); //! log::info!("Starting dices_bot..."); //! -//! let bot = Bot::from_env(); +//! let bot = Bot::from_env().auto_send(); //! //! teloxide::repl(bot, |message| async move { -//! message.answer_dice().send().await?; -//! ResponseResult::<()>::Ok(()) +//! message.answer_dice().await?; +//! respond(()) //! }) //! .await; //! # } From 1a76a0e34dae3d6722ae8c1951e1cfcf2e2f2f7f Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Mon, 15 Mar 2021 09:22:10 +0600 Subject: [PATCH 016/131] Apply review suggestions --- examples/admin_bot/src/main.rs | 2 +- examples/heroku_ping_pong_bot/src/main.rs | 2 +- examples/ngrok_ping_pong_bot/src/main.rs | 2 +- examples/redis_remember_bot/src/main.rs | 1 + examples/sqlite_remember_bot/src/main.rs | 1 + src/dispatching/update_listeners.rs | 10 +++++----- src/lib.rs | 3 ++- src/prelude.rs | 8 ++++---- tests/command.rs | 8 ++++---- 9 files changed, 20 insertions(+), 17 deletions(-) diff --git a/examples/admin_bot/src/main.rs b/examples/admin_bot/src/main.rs index e98eef97..3b427c72 100644 --- a/examples/admin_bot/src/main.rs +++ b/examples/admin_bot/src/main.rs @@ -2,7 +2,7 @@ use std::{convert::TryInto, str::FromStr}; use teloxide::{prelude::*, utils::command::BotCommand}; -use teloxide::core::types::ChatPermissions; +use teloxide::types::ChatPermissions; // Derive BotCommand to parse text with a command into this enumeration. // diff --git a/examples/heroku_ping_pong_bot/src/main.rs b/examples/heroku_ping_pong_bot/src/main.rs index 8c4837ab..8f02734a 100644 --- a/examples/heroku_ping_pong_bot/src/main.rs +++ b/examples/heroku_ping_pong_bot/src/main.rs @@ -1,7 +1,7 @@ // The version of Heroku ping-pong-bot, which uses a webhook to receive updates // from Telegram, instead of long polling. -use teloxide::{dispatching::update_listeners, prelude::*}; +use teloxide::{dispatching::update_listeners, prelude::*, types::Update}; use std::{convert::Infallible, env, net::SocketAddr}; use tokio::sync::mpsc; diff --git a/examples/ngrok_ping_pong_bot/src/main.rs b/examples/ngrok_ping_pong_bot/src/main.rs index 9d614807..a00c60ee 100644 --- a/examples/ngrok_ping_pong_bot/src/main.rs +++ b/examples/ngrok_ping_pong_bot/src/main.rs @@ -1,7 +1,7 @@ // The version of ngrok ping-pong-bot, which uses a webhook to receive updates // from Telegram, instead of long polling. -use teloxide::{dispatching::update_listeners, prelude::*}; +use teloxide::{dispatching::update_listeners, prelude::*, types::Update}; use std::{convert::Infallible, net::SocketAddr}; use tokio::sync::mpsc; diff --git a/examples/redis_remember_bot/src/main.rs b/examples/redis_remember_bot/src/main.rs index 7176b75b..8b8fed65 100644 --- a/examples/redis_remember_bot/src/main.rs +++ b/examples/redis_remember_bot/src/main.rs @@ -9,6 +9,7 @@ use states::*; use teloxide::{ dispatching::dialogue::{serializer::Bincode, RedisStorage, Storage}, prelude::*, + RequestError, }; use thiserror::Error; diff --git a/examples/sqlite_remember_bot/src/main.rs b/examples/sqlite_remember_bot/src/main.rs index 337bea8a..ba13ad1e 100644 --- a/examples/sqlite_remember_bot/src/main.rs +++ b/examples/sqlite_remember_bot/src/main.rs @@ -9,6 +9,7 @@ use states::*; use teloxide::{ dispatching::dialogue::{serializer::JSON, SqliteStorage, Storage}, prelude::*, + RequestError, }; use thiserror::Error; diff --git a/src/dispatching/update_listeners.rs b/src/dispatching/update_listeners.rs index 0632611d..131c3757 100644 --- a/src/dispatching/update_listeners.rs +++ b/src/dispatching/update_listeners.rs @@ -120,9 +120,9 @@ impl UpdateListener for S where S: Stream> {} /// Returns a long polling update listener with `timeout` of 10 seconds. /// /// See also: [`polling`](polling). -pub fn polling_default(requester: R) -> impl UpdateListener +pub fn polling_default(requester: R) -> impl UpdateListener where - R: Requester, + R: Requester, ::GetUpdatesFaultTolerant: Send, { polling(requester, Some(Duration::from_secs(10)), None, None) @@ -140,14 +140,14 @@ where /// See also: [`polling_default`](polling_default). /// /// [`GetUpdates`]: crate::requests::GetUpdates -pub fn polling( +pub fn polling( requester: R, timeout: Option, limit: Option, allowed_updates: Option>, -) -> impl UpdateListener +) -> impl UpdateListener where - R: Requester, + R: Requester, ::GetUpdatesFaultTolerant: Send, { let timeout = timeout.map(|t| t.as_secs().try_into().expect("timeout is too big")); diff --git a/src/lib.rs b/src/lib.rs index e5c27cd0..afa15b2a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -64,7 +64,8 @@ pub mod error_handlers; pub mod prelude; pub mod utils; -pub use teloxide_core as core; +#[doc(inline)] +pub use teloxide_core::*; use teloxide_core::requests::ResponseResult; #[cfg(feature = "macros")] diff --git a/src/prelude.rs b/src/prelude.rs index 1a5741e7..dac0b14f 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -12,15 +12,15 @@ pub use crate::{ respond, }; -pub use teloxide_core::prelude::*; - pub use teloxide_core::{ adaptors::AutoSend, requests::{Request, ResponseResult}, - types::{Message, Update}, - RequestError, + types::Message, }; +#[doc(inline)] +pub use teloxide_core::prelude::*; + #[cfg(feature = "frunk")] // FIXME(waffle): use `docsrs` here when issue with combine is resolved #[cfg_attr(all(teloxide_docsrs, feature = "nightly"), doc(cfg(feature = "frunk")))] diff --git a/tests/command.rs b/tests/command.rs index 657bc98d..deed8647 100644 --- a/tests/command.rs +++ b/tests/command.rs @@ -56,8 +56,8 @@ fn attribute_prefix() { #[test] #[cfg(feature = "macros")] fn many_attributes() { - #[command(rename = "lowercase")] #[derive(BotCommand, Debug, PartialEq)] + #[command(rename = "lowercase")] enum DefaultCommands { #[command(prefix = "!", description = "desc")] Start, @@ -71,8 +71,8 @@ fn many_attributes() { #[test] #[cfg(feature = "macros")] fn global_attributes() { - #[command(prefix = "!", rename = "lowercase", description = "Bot commands")] #[derive(BotCommand, Debug, PartialEq)] + #[command(prefix = "!", rename = "lowercase", description = "Bot commands")] enum DefaultCommands { #[command(prefix = "/")] Start, @@ -87,8 +87,8 @@ fn global_attributes() { #[test] #[cfg(feature = "macros")] fn parse_command_with_bot_name() { - #[command(rename = "lowercase")] #[derive(BotCommand, Debug, PartialEq)] + #[command(rename = "lowercase")] enum DefaultCommands { #[command(prefix = "/")] Start, @@ -104,9 +104,9 @@ fn parse_command_with_bot_name() { #[test] #[cfg(feature = "macros")] fn parse_with_split() { + #[derive(BotCommand, Debug, PartialEq)] #[command(rename = "lowercase")] #[command(parse_with = "split")] - #[derive(BotCommand, Debug, PartialEq)] enum DefaultCommands { Start(u8, String), Help, From f3a0f9dc525db8f8126722cab229e9efe60cb756 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Mon, 15 Mar 2021 09:51:00 +0600 Subject: [PATCH 017/131] Fix the docs --- src/dispatching/dialogue/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dispatching/dialogue/mod.rs b/src/dispatching/dialogue/mod.rs index bf919e94..b04974b1 100644 --- a/src/dispatching/dialogue/mod.rs +++ b/src/dispatching/dialogue/mod.rs @@ -33,7 +33,7 @@ //! # #[cfg(feature = "macros")] { //! use std::convert::Infallible; //! -//! use teloxide::{dispatching::dialogue::Transition, prelude::*, teloxide}; +//! use teloxide::{dispatching::dialogue::Transition, prelude::*, RequestError}; //! //! struct _1State; //! struct _2State; From df63014269fef21f6ff0e8b3fe37c66e9db3bc70 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Mon, 15 Mar 2021 20:21:50 +0600 Subject: [PATCH 018/131] Fix the doc tests --- src/dispatching/dialogue/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dispatching/dialogue/mod.rs b/src/dispatching/dialogue/mod.rs index b04974b1..2bd7f39e 100644 --- a/src/dispatching/dialogue/mod.rs +++ b/src/dispatching/dialogue/mod.rs @@ -33,7 +33,7 @@ //! # #[cfg(feature = "macros")] { //! use std::convert::Infallible; //! -//! use teloxide::{dispatching::dialogue::Transition, prelude::*, RequestError}; +//! use teloxide::{dispatching::dialogue::Transition, prelude::*, teloxide, RequestError}; //! //! struct _1State; //! struct _2State; From 1453bc2565cbd5c3dc7acb75b75a43199fbd6339 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Mon, 15 Mar 2021 20:23:44 +0600 Subject: [PATCH 019/131] Remove examples/sqlite_remember_bot/db.sqlite-journal --- examples/sqlite_remember_bot/db.sqlite-journal | Bin 512 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 examples/sqlite_remember_bot/db.sqlite-journal diff --git a/examples/sqlite_remember_bot/db.sqlite-journal b/examples/sqlite_remember_bot/db.sqlite-journal deleted file mode 100644 index 5d08624e1abe3276630bb4f22c8496cfd97f32c5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 512 bcmZQzK!C#yyA&ZjCLkq%kQikR+YkT%M6m)f From 31a739f5b4f89f76d5cc92d7dc778071b5d2d211 Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Mon, 15 Mar 2021 20:26:50 +0600 Subject: [PATCH 020/131] Update src/lib.rs Co-authored-by: Waffle Lapkin --- src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index afa15b2a..8e1bbd8a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -55,7 +55,6 @@ pub use dispatching::repls::{ commands_repl, commands_repl_with_listener, dialogues_repl, dialogues_repl_with_listener, repl, repl_with_listener, }; -pub use teloxide_core::{ApiError, DownloadError, RequestError}; mod logging; From 07eb4c1148ec571de95dcceeb7cab7a8cb945b52 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Wed, 17 Mar 2021 23:36:51 +0600 Subject: [PATCH 021/131] Support UpdateKind::{MyChatMember, ChatMember} --- CHANGELOG.md | 3 ++ Cargo.toml | 2 +- src/dispatching/dispatcher.rs | 42 +++++++++++++++++++- src/dispatching/dispatcher_handler_rx_ext.rs | 6 ++- src/prelude.rs | 5 ++- 5 files changed, 53 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f1a9643..060024a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Allow arbitrary error types to be returned from (sub)transitions ([issue 242](https://github.com/teloxide/teloxide/issues/242)). - The `respond` function, a shortcut for `ResponseResult::Ok(())`. - The `sqlite-storage` feature -- enables SQLite support. + - `Dispatcher::{my_chat_members_handler, chat_members_handler}` + +[teloxide-core]: https://github.com/teloxide/teloxide-core ### Deprecated diff --git a/Cargo.toml b/Cargo.toml index 40b0133b..04d36c73 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,7 @@ macros = ["teloxide-macros"] nightly = [] # currently used for `README.md` tests and building docs for `docsrs` to add `This is supported on feature="..." only.` [dependencies] -teloxide-core = { git = "https://github.com/teloxide/teloxide-core.git", features = ["full"] } +teloxide-core = { version = "0.2", features = ["full"] } serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index fa5eb7de..e7635ebb 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -9,8 +9,8 @@ use std::{fmt::Debug, sync::Arc}; use teloxide_core::{ requests::Requester, types::{ - CallbackQuery, ChosenInlineResult, InlineQuery, Message, Poll, PollAnswer, - PreCheckoutQuery, ShippingQuery, UpdateKind, + CallbackQuery, ChatMemberUpdated, ChosenInlineResult, InlineQuery, Message, Poll, + PollAnswer, PreCheckoutQuery, ShippingQuery, UpdateKind, }, }; use tokio::sync::mpsc; @@ -61,6 +61,8 @@ pub struct Dispatcher { pre_checkout_queries_queue: Tx, polls_queue: Tx, poll_answers_queue: Tx, + my_chat_members_queue: Tx, + chat_members_queue: Tx, } impl Dispatcher @@ -83,6 +85,8 @@ where pre_checkout_queries_queue: None, polls_queue: None, poll_answers_queue: None, + my_chat_members_queue: None, + chat_members_queue: None, } } @@ -201,6 +205,24 @@ where self } + #[must_use] + pub fn my_chat_members_handler(mut self, h: H) -> Self + where + H: DispatcherHandler + 'static + Send, + { + self.my_chat_members_queue = self.new_tx(h); + self + } + + #[must_use] + pub fn chat_members_handler(mut self, h: H) -> Self + where + H: DispatcherHandler + 'static + Send, + { + self.chat_members_queue = self.new_tx(h); + self + } + /// Starts your bot with the default parameters. /// /// The default parameters are a long polling update listener and log all @@ -330,6 +352,22 @@ where UpdateKind::PollAnswer ); } + UpdateKind::MyChatMember(chat_member_updated) => { + send!( + &self.requester, + &self.my_chat_members_queue, + chat_member_updated, + UpdateKind::MyChatMember + ); + } + UpdateKind::ChatMember(chat_member_updated) => { + send!( + &self.requester, + &self.chat_members_queue, + chat_member_updated, + UpdateKind::MyChatMember + ); + } } } }) diff --git a/src/dispatching/dispatcher_handler_rx_ext.rs b/src/dispatching/dispatcher_handler_rx_ext.rs index 7a0dddf6..d9616645 100644 --- a/src/dispatching/dispatcher_handler_rx_ext.rs +++ b/src/dispatching/dispatcher_handler_rx_ext.rs @@ -34,7 +34,11 @@ where Self: Stream>, R: Send + 'static, { - self.filter_map(|cx| async move { cx.update.text_owned().map(|text| (cx, text)) }).boxed() + self.filter_map(|cx| async move { + let text = cx.update.text().map(ToOwned::to_owned); + text.map(move |text| (cx, text)) + }) + .boxed() } fn commands(self, bot_name: N) -> BoxStream<'static, (UpdateWithCx, C)> diff --git a/src/prelude.rs b/src/prelude.rs index dac0b14f..d246a85e 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -15,7 +15,10 @@ pub use crate::{ pub use teloxide_core::{ adaptors::AutoSend, requests::{Request, ResponseResult}, - types::Message, + types::{ + CallbackQuery, ChatMemberUpdated, ChosenInlineResult, InlineQuery, Message, Poll, + PollAnswer, PreCheckoutQuery, ShippingQuery, + }, }; #[doc(inline)] From 8178191d5afd3b9978ad6170eb3061c76bfeb2cc Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Thu, 18 Mar 2021 01:50:48 +0600 Subject: [PATCH 022/131] Fix examples/redis_remember_bot --- examples/redis_remember_bot/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/redis_remember_bot/src/main.rs b/examples/redis_remember_bot/src/main.rs index 8b8fed65..9fc32f22 100644 --- a/examples/redis_remember_bot/src/main.rs +++ b/examples/redis_remember_bot/src/main.rs @@ -52,7 +52,7 @@ async fn handle_message( cx: UpdateWithCx, Message>, dialogue: Dialogue, ) -> TransitionOut { - match cx.update.text_owned() { + match cx.update.text().map(ToOwned::to_owned) { None => { cx.answer("Send me a text message.").await?; next(dialogue) From d625b699385359ba05a91b26d3b122de86d004c5 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Thu, 18 Mar 2021 01:56:41 +0600 Subject: [PATCH 023/131] Fix examples/dialogue_bot --- examples/dialogue_bot/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/dialogue_bot/src/main.rs b/examples/dialogue_bot/src/main.rs index 310a69a3..cc2e6e76 100644 --- a/examples/dialogue_bot/src/main.rs +++ b/examples/dialogue_bot/src/main.rs @@ -46,7 +46,7 @@ async fn handle_message( cx: UpdateWithCx, Message>, dialogue: Dialogue, ) -> TransitionOut { - match cx.update.text_owned() { + match cx.update.text().map(ToOwned::to_owned) { None => { cx.answer("Send me a text message.").await?; next(dialogue) From 09cca0faff2010ceb996e52e5afbfb5ce4f43f6f Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Fri, 19 Mar 2021 17:58:22 +0600 Subject: [PATCH 024/131] Use Box in the examples --- CHANGELOG.md | 1 + README.md | 13 +++++++++---- examples/admin_bot/src/main.rs | 10 +++++----- examples/simple_commands_bot/src/main.rs | 7 ++++++- examples/sqlite_remember_bot/src/main.rs | 2 +- src/lib.rs | 3 ++- src/prelude.rs | 2 +- 7 files changed, 25 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 060024a8..37b67e56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 `#[non_exhaustive]` annotation is removed from the enum, type of `TargetMessage::Inline::inline_message_id` changed `i32` => `String`. `TargetMessage` now implements `From`, `get_game_high_scores` and `set_game_score` use `Into` to accept `String`s. ([issue 253], [pr 257]) + - Remove `ResponseResult` from `prelude`. [issue 253]: https://github.com/teloxide/teloxide/issues/253 [pr 257]: https://github.com/teloxide/teloxide/pull/257 diff --git a/README.md b/README.md index 8d02f42a..dde2f4d9 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,9 @@ Commands are strongly typed and defined declaratively, similar to how we define ([Full](./examples/simple_commands_bot/src/main.rs)) ```rust,no_run -use teloxide::{utils::command::BotCommand, prelude::*}; +use teloxide::{prelude::*, utils::command::BotCommand}; + +use std::error::Error; #[derive(BotCommand)] #[command(rename = "lowercase", description = "These commands are supported:")] @@ -143,14 +145,17 @@ enum Command { UsernameAndAge { username: String, age: u8 }, } -async fn answer(cx: UpdateWithCx, Message>, command: Command) -> ResponseResult<()> { +async fn answer( + cx: UpdateWithCx, Message>, + command: Command, +) -> Result<(), Box> { match command { Command::Help => cx.answer(Command::descriptions()).send().await?, Command::Username(username) => { - cx.answer_str(format!("Your username is @{}.", username)).await? + cx.answer(format!("Your username is @{}.", username)).await? } Command::UsernameAndAge { username, age } => { - cx.answer_str(format!("Your username is @{} and age is {}.", username, age)).await? + cx.answer(format!("Your username is @{} and age is {}.", username, age)).await? } }; diff --git a/examples/admin_bot/src/main.rs b/examples/admin_bot/src/main.rs index 3b427c72..0351f9bd 100644 --- a/examples/admin_bot/src/main.rs +++ b/examples/admin_bot/src/main.rs @@ -1,4 +1,4 @@ -use std::{convert::TryInto, str::FromStr}; +use std::{convert::TryInto, error::Error, str::FromStr}; use teloxide::{prelude::*, utils::command::BotCommand}; @@ -65,7 +65,7 @@ fn calc_restrict_time(time: u32, unit: UnitOfTime) -> u32 { type Cx = UpdateWithCx, Message>; // Mute a user with a replied message. -async fn mute_user(cx: &Cx, time: u32) -> ResponseResult<()> { +async fn mute_user(cx: &Cx, time: u32) -> Result<(), Box> { match cx.update.reply_to_message() { Some(msg1) => { cx.requester @@ -85,7 +85,7 @@ async fn mute_user(cx: &Cx, time: u32) -> ResponseResult<()> { } // Kick a user with a replied message. -async fn kick_user(cx: &Cx) -> ResponseResult<()> { +async fn kick_user(cx: &Cx) -> Result<(), Box> { match cx.update.reply_to_message() { Some(mes) => { // bot.unban_chat_member can also kicks a user from a group chat. @@ -102,7 +102,7 @@ async fn kick_user(cx: &Cx) -> ResponseResult<()> { } // Ban a user with replied message. -async fn ban_user(cx: &Cx, time: u32) -> ResponseResult<()> { +async fn ban_user(cx: &Cx, time: u32) -> Result<(), Box> { match cx.update.reply_to_message() { Some(message) => { cx.requester @@ -120,7 +120,7 @@ async fn ban_user(cx: &Cx, time: u32) -> ResponseResult<()> { Ok(()) } -async fn action(cx: Cx, command: Command) -> ResponseResult<()> { +async fn action(cx: Cx, command: Command) -> Result<(), Box> { match command { Command::Help => cx.answer(Command::descriptions()).send().await.map(|_| ())?, Command::Kick => kick_user(&cx).await?, diff --git a/examples/simple_commands_bot/src/main.rs b/examples/simple_commands_bot/src/main.rs index a510a72c..8b34c7b0 100644 --- a/examples/simple_commands_bot/src/main.rs +++ b/examples/simple_commands_bot/src/main.rs @@ -1,5 +1,7 @@ use teloxide::{prelude::*, utils::command::BotCommand}; +use std::error::Error; + #[derive(BotCommand)] #[command(rename = "lowercase", description = "These commands are supported:")] enum Command { @@ -11,7 +13,10 @@ enum Command { UsernameAndAge { username: String, age: u8 }, } -async fn answer(cx: UpdateWithCx, Message>, command: Command) -> ResponseResult<()> { +async fn answer( + cx: UpdateWithCx, Message>, + command: Command, +) -> Result<(), Box> { match command { Command::Help => cx.answer(Command::descriptions()).send().await?, Command::Username(username) => { diff --git a/examples/sqlite_remember_bot/src/main.rs b/examples/sqlite_remember_bot/src/main.rs index ba13ad1e..a6e62b79 100644 --- a/examples/sqlite_remember_bot/src/main.rs +++ b/examples/sqlite_remember_bot/src/main.rs @@ -29,7 +29,7 @@ async fn handle_message( cx: UpdateWithCx, Message>, dialogue: Dialogue, ) -> TransitionOut { - match cx.update.text_owned() { + match cx.update.text().map(ToOwned::to_owned) { None => { cx.answer("Send me a text message.").await?; next(dialogue) diff --git a/src/lib.rs b/src/lib.rs index 8e1bbd8a..7a3403da 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -66,7 +66,6 @@ pub mod utils; #[doc(inline)] pub use teloxide_core::*; -use teloxide_core::requests::ResponseResult; #[cfg(feature = "macros")] // FIXME(waffle): use `docsrs` here when issue with combine is resolved #[cfg_attr(all(teloxide_docsrs, feature = "nightly"), doc(cfg(feature = "macros")))] @@ -76,6 +75,8 @@ pub use teloxide_macros::teloxide; #[doc(include = "../README.md")] enum ReadmeDocTests {} +use teloxide_core::requests::ResponseResult; + /// A shortcut for `ResponseResult::Ok(val)`. pub fn respond(val: T) -> ResponseResult { ResponseResult::Ok(val) diff --git a/src/prelude.rs b/src/prelude.rs index d246a85e..4ccebaa4 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -14,7 +14,7 @@ pub use crate::{ pub use teloxide_core::{ adaptors::AutoSend, - requests::{Request, ResponseResult}, + requests::Request, types::{ CallbackQuery, ChatMemberUpdated, ChosenInlineResult, InlineQuery, Message, Poll, PollAnswer, PreCheckoutQuery, ShippingQuery, From 5657a321657d5cd2e8ab040e0b1e694aedbfa8de Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Sat, 20 Mar 2021 22:57:32 +0600 Subject: [PATCH 025/131] Update src/prelude.rs Co-authored-by: Waffle Lapkin --- src/prelude.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/prelude.rs b/src/prelude.rs index 4ccebaa4..211a1b49 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -14,7 +14,6 @@ pub use crate::{ pub use teloxide_core::{ adaptors::AutoSend, - requests::Request, types::{ CallbackQuery, ChatMemberUpdated, ChosenInlineResult, InlineQuery, Message, Poll, PollAnswer, PreCheckoutQuery, ShippingQuery, From 934ee94a5e2d803b43fcc4319f240394926e2bc1 Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Sun, 21 Mar 2021 18:34:23 +0600 Subject: [PATCH 026/131] Get ready for v0.4.0 (#347) * Get ready for v0.4.0 * Export teloxide_macros as macros in lib.rs * Use tokio v1.3 in README.md * Return the feature 'macros' back * Export teloxide_macros::teloxide in the prelude * Document the 'macros' feature in README.md * Update src/lib.rs Co-authored-by: Waffle Lapkin * Update src/prelude.rs Co-authored-by: Waffle Lapkin * Propagate features from teloxide-core * teloxide-core = 0.2.1 * Fix CI * Require teloxide-core/auto_send instead of teloxide/auto-send * default-features = false (teloxide-core) * Specify each teloxide-core feature separately * Fix Cargo.toml * JSON -> Json, CBOR -> Cbor * Update Cargo.toml Co-authored-by: Waffle Lapkin Co-authored-by: Waffle Lapkin --- .github/workflows/ci.yml | 76 +++++++++++-------- CHANGELOG.md | 4 + Cargo.toml | 33 ++++++-- README.md | 45 +++++++---- examples/admin_bot/Cargo.toml | 2 +- examples/dialogue_bot/Cargo.toml | 14 ++-- examples/dialogue_bot/src/dialogue/mod.rs | 2 +- .../src/dialogue/states/receive_age.rs | 1 - .../src/dialogue/states/receive_full_name.rs | 1 - .../src/dialogue/states/receive_location.rs | 1 - .../dialogue_bot/src/dialogue/states/start.rs | 1 - examples/dices_bot/Cargo.toml | 2 +- examples/heroku_ping_pong_bot/Cargo.toml | 2 +- examples/ngrok_ping_pong_bot/Cargo.toml | 2 +- examples/redis_remember_bot/Cargo.toml | 7 +- examples/redis_remember_bot/src/states.rs | 2 +- .../redis_remember_bot/src/transitions.rs | 1 - examples/shared_state_bot/Cargo.toml | 2 +- examples/simple_commands_bot/Cargo.toml | 2 +- examples/sqlite_remember_bot/Cargo.toml | 7 +- examples/sqlite_remember_bot/src/main.rs | 6 +- examples/sqlite_remember_bot/src/states.rs | 2 +- .../sqlite_remember_bot/src/transitions.rs | 2 +- .../dialogue/storage/serializer.rs | 8 +- src/dispatching/update_listeners.rs | 6 +- src/lib.rs | 5 ++ src/prelude.rs | 17 +++-- tests/redis.rs | 4 +- tests/sqlite.rs | 4 +- 29 files changed, 159 insertions(+), 102 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0044f83e..f33adb24 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,3 @@ - on: push: branches: [ master ] @@ -8,8 +7,9 @@ on: name: Continuous integration jobs: - code-checks: + style: runs-on: ubuntu-latest + steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 @@ -17,49 +17,65 @@ jobs: profile: minimal toolchain: nightly override: true - components: rustfmt, clippy - - name: Cargo fmt - run: cargo +nightly fmt --all -- --check - - uses: actions-rs/toolchain@v1 + components: rustfmt + + - name: fmt + uses: actions-rs/cargo@v1 with: - profile: minimal - toolchain: stable - override: true - - name: Cargo clippy - run: cargo clippy --all --all-targets --all-features -- -D warnings + command: fmt + args: --all -- --check - test: + clippy: runs-on: ubuntu-latest - strategy: - matrix: - rust: - - stable - - beta - - nightly - - include: - - rust: stable - features: "--features \"redis-storage sqlite-storage cbor-serializer bincode-serializer frunk-\"" - - rust: beta - features: "--features \"redis-storage sqlite-storage cbor-serializer bincode-serializer frunk-\"" - - rust: nightly - features: "--all-features" steps: - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly + override: true + components: clippy + + - name: clippy + uses: actions-rs/cargo@v1 + with: + command: clippy + args: --all-targets --all-features -- -D warnings + + test: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - stable + - beta + - nightly + + include: + - rust: stable + features: "--features full" + - rust: beta + features: "--features full" + - rust: nightly + features: "--all-features" + + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: ${{ matrix.rust }} override: true - - - name: Build + + - name: build uses: actions-rs/cargo@v1 with: command: build args: --verbose ${{ matrix.features }} - + - name: Setup redis run: | sudo apt install redis-server @@ -67,7 +83,7 @@ jobs: redis-server --port 7778 > /dev/null & redis-server --port 7779 > /dev/null & - - name: Test + - name: test uses: actions-rs/cargo@v1 with: command: test diff --git a/CHANGELOG.md b/CHANGELOG.md index 37b67e56..256bc43e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [unreleased] +## [0.4.0] - 2021-03-19 + ### Added - Integrate [teloxide-core]. - Allow arbitrary error types to be returned from (sub)transitions ([issue 242](https://github.com/teloxide/teloxide/issues/242)). @@ -24,6 +26,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Hide `SubtransitionOutputType` from the docs. ### Changed + - Export `teloxide_macros::teloxide` in `prelude`. + - `dispatching::dialogue::serializer::{JSON -> Json, CBOR -> Cbor}` - Allow `bot_name` be `N`, where `N: Into + ...` in `commands_repl` & `commands_repl_with_listener`. - 'Edit methods' (namely `edit_message_live_location`, `stop_message_live_location`, `edit_message_text`, `edit_message_caption`, `edit_message_media` and `edit_message_reply_markup`) are split into common and inline diff --git a/Cargo.toml b/Cargo.toml index 04d36c73..9de1b661 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "teloxide" -version = "0.3.4" +version = "0.4.0" edition = "2018" description = "An elegant Telegram bots framework for Rust" repository = "https://github.com/teloxide/teloxide" @@ -30,13 +30,36 @@ cbor-serializer = ["serde_cbor"] bincode-serializer = ["bincode"] frunk- = ["frunk"] - macros = ["teloxide-macros"] -nightly = [] # currently used for `README.md` tests and building docs for `docsrs` to add `This is supported on feature="..." only.` +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"] + +# currently used for `README.md` tests, building docs for `docsrs` to add `This is supported on feature="..." only.`, +# and for teloxide-core. +nightly = ["teloxide-core/nightly"] + +full = [ + "sqlite-storage", + "redis-storage", + "cbor-serializer", + "bincode-serializer", + "frunk", + "macros", + "teloxide-core/full", + "native-tls", + "rustls", + "auto-send", + "throttle", + "cache-me" +] [dependencies] -teloxide-core = { version = "0.2", features = ["full"] } +teloxide-core = { version = "0.2.1", default-features = false } +teloxide-macros = { version = "0.4", optional = true } serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } @@ -68,8 +91,6 @@ serde_cbor = { version = "0.11", optional = true } bincode = { version = "1.3", optional = true } frunk = { version = "0.3", optional = true } -teloxide-macros = { git = "https://github.com/teloxide/teloxide-macros", branch = "master", optional = true } - [dev-dependencies] smart-default = "0.6.0" rand = "0.8.3" diff --git a/README.md b/README.md index dde2f4d9..d9b3b6a1 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ - + @@ -79,13 +79,10 @@ $ rustup override set nightly 5. Run `cargo new my_bot`, enter the directory and put these lines into your `Cargo.toml`: ```toml [dependencies] -teloxide = "0.3" -teloxide-macros = "0.3" - +teloxide = "0.4" log = "0.4.8" pretty_env_logger = "0.4.0" - -tokio = { version = "0.2.11", features = ["rt-threaded", "macros"] } +tokio = { version = "1.3", features = ["rt-threaded", "macros"] } ``` ## API overview @@ -218,8 +215,12 @@ When a user sends a message to our bot and such a dialogue does not exist yet, a pub struct StartState; #[teloxide(subtransition)] -async fn start(_state: StartState, cx: TransitionIn>, _ans: String) -> TransitionOut { - cx.answer_str("Let's start! What's your full name?").await?; +async fn start( + _state: StartState, + cx: TransitionIn>, + _ans: String, +) -> TransitionOut { + cx.answer("Let's start! What's your full name?").await?; next(ReceiveFullNameState) } ``` @@ -242,7 +243,7 @@ async fn receive_full_name( cx: TransitionIn>, ans: String, ) -> TransitionOut { - cx.answer_str("How old are you?").await?; + cx.answer("How old are you?").await?; next(ReceiveAgeState::up(state, ans)) } ``` @@ -269,11 +270,11 @@ async fn receive_age_state( ) -> TransitionOut { match ans.parse::() { Ok(ans) => { - cx.answer_str("What's your location?").await?; + cx.answer("What's your location?").await?; next(ReceiveLocationState::up(state, ans)) } _ => { - cx.answer_str("Send me a number.").await?; + cx.answer("Send me a number.").await?; next(state) } } @@ -301,7 +302,7 @@ async fn receive_location( cx: TransitionIn>, ans: String, ) -> TransitionOut { - cx.answer_str(format!("Full name: {}\nAge: {}\nLocation: {}", state.full_name, state.age, ans)) + cx.answer(format!("Full name: {}\nAge: {}\nLocation: {}", state.full_name, state.age, ans)) .await?; exit() } @@ -330,10 +331,13 @@ async fn main() { .await; } -async fn handle_message(cx: UpdateWithCx, Message>, dialogue: Dialogue) -> TransitionOut { - match cx.update.text_owned() { +async fn handle_message( + cx: UpdateWithCx, Message>, + dialogue: Dialogue, +) -> TransitionOut { + match cx.update.text().map(ToOwned::to_owned) { None => { - cx.answer_str("Send me a text message.").await?; + cx.answer("Send me a text message.").await?; next(dialogue) } Some(ans) => dialogue.react(cx, ans).await, @@ -381,10 +385,21 @@ The second one produces very strange compiler messages due to the `#[tokio::main - `cbor-serializer` -- enables the [CBOR] serializer for dialogues. - `bincode-serializer` -- enables the [Bincode] serializer for dialogues. - `frunk` -- enables [`teloxide::utils::UpState`], which allows mapping from a structure of `field1, ..., fieldN` to a structure of `field1, ..., fieldN, fieldN+1`. + - `macros` -- re-exports macros from [`teloxide-macros`]. + - `native-tls` -- enables the [`native-tls`] TLS implementation (enabled by default). + - `rustls` -- enables the [`rustls`] TLS implementation. + - `auto-send` -- enables `AutoSend` bot adaptor. + - `cache-me` -- enables the `CacheMe` bot adaptor. + - `full` -- enables all the features except `nightly`. + - `nightly` -- enables nightly-only features (see the [teloxide-core's features]). [CBOR]: https://en.wikipedia.org/wiki/CBOR [Bincode]: https://github.com/servo/bincode [`teloxide::utils::UpState`]: https://docs.rs/teloxide/latest/teloxide/utils/trait.UpState.html +[`teloxide-macros`]: https://github.com/teloxide/teloxide-macros +[`native-tls`]: https://docs.rs/native-tls +[`rustls`]: https://docs.rs/rustls +[teloxide-core's features]: https://docs.rs/teloxide-core/0.2.1/teloxide_core/#cargo-features ## FAQ **Q: Where I can ask questions?** diff --git a/examples/admin_bot/Cargo.toml b/examples/admin_bot/Cargo.toml index e598c3f9..ccaf49c0 100644 --- a/examples/admin_bot/Cargo.toml +++ b/examples/admin_bot/Cargo.toml @@ -7,10 +7,10 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +teloxide = { path = "../../", features = ["macros", "auto-send"] } log = "0.4.8" pretty_env_logger = "0.4.0" tokio = { version = "1.3.0", features = ["rt-multi-thread", "macros"] } -teloxide = { path = "../../", features = ["macros"] } [profile.release] lto = true diff --git a/examples/dialogue_bot/Cargo.toml b/examples/dialogue_bot/Cargo.toml index c8e7cbc4..45f928ea 100644 --- a/examples/dialogue_bot/Cargo.toml +++ b/examples/dialogue_bot/Cargo.toml @@ -7,19 +7,17 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -log = "0.4.8" -pretty_env_logger = "0.4.0" - -frunk = "0.3.1" -frunk_core = "0.3.1" +teloxide = { path = "../../", features = ["frunk", "macros", "auto-send"] } futures = "0.3.5" tokio = { version = "1.3.0", features = ["rt-multi-thread", "macros"] } -teloxide = { path = "../../", features = ["frunk"] } -teloxide-macros = { git = "https://github.com/teloxide/teloxide-macros", branch = "master" } - +log = "0.4.8" +pretty_env_logger = "0.4.0" derive_more = "0.99.9" +frunk = "0.3.1" +frunk_core = "0.3.1" + [profile.release] lto = true diff --git a/examples/dialogue_bot/src/dialogue/mod.rs b/examples/dialogue_bot/src/dialogue/mod.rs index 176474b6..4bae759a 100644 --- a/examples/dialogue_bot/src/dialogue/mod.rs +++ b/examples/dialogue_bot/src/dialogue/mod.rs @@ -4,7 +4,7 @@ use crate::dialogue::states::{ ReceiveAgeState, ReceiveFullNameState, ReceiveLocationState, StartState, }; use derive_more::From; -use teloxide_macros::Transition; +use teloxide::macros::Transition; #[derive(Transition, From)] pub enum Dialogue { diff --git a/examples/dialogue_bot/src/dialogue/states/receive_age.rs b/examples/dialogue_bot/src/dialogue/states/receive_age.rs index 7c3c33e6..36c72a23 100644 --- a/examples/dialogue_bot/src/dialogue/states/receive_age.rs +++ b/examples/dialogue_bot/src/dialogue/states/receive_age.rs @@ -1,6 +1,5 @@ use crate::dialogue::{states::receive_location::ReceiveLocationState, Dialogue}; use teloxide::prelude::*; -use teloxide_macros::teloxide; #[derive(Generic)] pub struct ReceiveAgeState { diff --git a/examples/dialogue_bot/src/dialogue/states/receive_full_name.rs b/examples/dialogue_bot/src/dialogue/states/receive_full_name.rs index dc32a1a2..21d3fef2 100644 --- a/examples/dialogue_bot/src/dialogue/states/receive_full_name.rs +++ b/examples/dialogue_bot/src/dialogue/states/receive_full_name.rs @@ -1,6 +1,5 @@ use crate::dialogue::{states::receive_age::ReceiveAgeState, Dialogue}; use teloxide::prelude::*; -use teloxide_macros::teloxide; #[derive(Generic)] pub struct ReceiveFullNameState; diff --git a/examples/dialogue_bot/src/dialogue/states/receive_location.rs b/examples/dialogue_bot/src/dialogue/states/receive_location.rs index ee881bbd..aaa1af2d 100644 --- a/examples/dialogue_bot/src/dialogue/states/receive_location.rs +++ b/examples/dialogue_bot/src/dialogue/states/receive_location.rs @@ -1,6 +1,5 @@ use crate::dialogue::Dialogue; use teloxide::prelude::*; -use teloxide_macros::teloxide; #[derive(Generic)] pub struct ReceiveLocationState { diff --git a/examples/dialogue_bot/src/dialogue/states/start.rs b/examples/dialogue_bot/src/dialogue/states/start.rs index dfe28be9..a4f3c192 100644 --- a/examples/dialogue_bot/src/dialogue/states/start.rs +++ b/examples/dialogue_bot/src/dialogue/states/start.rs @@ -1,6 +1,5 @@ use crate::dialogue::{states::ReceiveFullNameState, Dialogue}; use teloxide::prelude::*; -use teloxide_macros::teloxide; pub struct StartState; diff --git a/examples/dices_bot/Cargo.toml b/examples/dices_bot/Cargo.toml index b1b22c42..763f2c8b 100644 --- a/examples/dices_bot/Cargo.toml +++ b/examples/dices_bot/Cargo.toml @@ -7,10 +7,10 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +teloxide = { path = "../../", features = ["auto-send"] } log = "0.4.8" pretty_env_logger = "0.4.0" tokio = { version = "1.3.0", features = ["rt-multi-thread", "macros"] } -teloxide = { path = "../../" } [profile.release] lto = true diff --git a/examples/heroku_ping_pong_bot/Cargo.toml b/examples/heroku_ping_pong_bot/Cargo.toml index b27602b2..edcc76f5 100644 --- a/examples/heroku_ping_pong_bot/Cargo.toml +++ b/examples/heroku_ping_pong_bot/Cargo.toml @@ -7,10 +7,10 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +teloxide = { path = "../../", features = ["auto-send"] } log = "0.4.8" pretty_env_logger = "0.4.0" tokio = { version = "1.3.0", features = ["rt-multi-thread", "macros"] } -teloxide = { path = "../../" } tokio-stream = "0.1.4" # Used to setup a webhook diff --git a/examples/ngrok_ping_pong_bot/Cargo.toml b/examples/ngrok_ping_pong_bot/Cargo.toml index 524484e3..571fe1ff 100644 --- a/examples/ngrok_ping_pong_bot/Cargo.toml +++ b/examples/ngrok_ping_pong_bot/Cargo.toml @@ -7,11 +7,11 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +teloxide = { path = "../../", features = ["auto-send"] } log = "0.4.8" pretty_env_logger = "0.4.0" tokio = { version = "1.3.0", features = ["rt-multi-thread", "macros"] } tokio-stream = "0.1.4" -teloxide = { path = "../../" } # Used to setup a webhook warp = "0.3.0" diff --git a/examples/redis_remember_bot/Cargo.toml b/examples/redis_remember_bot/Cargo.toml index 18f4e618..c134f0a4 100644 --- a/examples/redis_remember_bot/Cargo.toml +++ b/examples/redis_remember_bot/Cargo.toml @@ -5,14 +5,13 @@ authors = ["Maximilian Siling "] edition = "2018" [dependencies] +# You can also choose "cbor-serializer" or built-in JSON serializer +teloxide = { path = "../../", features = ["redis-storage", "bincode-serializer", "macros", "auto-send"] } + log = "0.4.8" pretty_env_logger = "0.4.0" tokio = { version = "1.3.0", features = ["rt-multi-thread", "macros"] } -# You can also choose "cbor-serializer" or built-in JSON serializer -teloxide = { path = "../../", features = ["redis-storage", "bincode-serializer"] } -teloxide-macros = { git = "https://github.com/teloxide/teloxide-macros", branch = "master" } - serde = "1.0.104" futures = "0.3.5" diff --git a/examples/redis_remember_bot/src/states.rs b/examples/redis_remember_bot/src/states.rs index 0bb65bd7..6da08fe8 100644 --- a/examples/redis_remember_bot/src/states.rs +++ b/examples/redis_remember_bot/src/states.rs @@ -1,4 +1,4 @@ -use teloxide_macros::Transition; +use teloxide::dispatching::dialogue::Transition; use serde::{Deserialize, Serialize}; diff --git a/examples/redis_remember_bot/src/transitions.rs b/examples/redis_remember_bot/src/transitions.rs index 263f1991..7cfccd13 100644 --- a/examples/redis_remember_bot/src/transitions.rs +++ b/examples/redis_remember_bot/src/transitions.rs @@ -1,5 +1,4 @@ use teloxide::prelude::*; -use teloxide_macros::teloxide; use super::states::*; diff --git a/examples/shared_state_bot/Cargo.toml b/examples/shared_state_bot/Cargo.toml index ecb937ac..eb613ba1 100644 --- a/examples/shared_state_bot/Cargo.toml +++ b/examples/shared_state_bot/Cargo.toml @@ -7,9 +7,9 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +teloxide = { path = "../../", features = ["auto-send"] } log = "0.4.8" pretty_env_logger = "0.4.0" tokio = { version = "1.3.0", features = ["rt-multi-thread", "macros"] } tokio-stream = "0.1.3" -teloxide = { path = "../../" } lazy_static = "1.4.0" diff --git a/examples/simple_commands_bot/Cargo.toml b/examples/simple_commands_bot/Cargo.toml index 38814c5f..7aef1030 100644 --- a/examples/simple_commands_bot/Cargo.toml +++ b/examples/simple_commands_bot/Cargo.toml @@ -7,7 +7,7 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +teloxide = { path = "../../", features = ["macros", "auto-send"] } log = "0.4.8" pretty_env_logger = "0.4.0" tokio = { version = "1.3.0", features = ["rt-multi-thread", "macros"] } -teloxide = { path = "../../", features = ["macros"] } diff --git a/examples/sqlite_remember_bot/Cargo.toml b/examples/sqlite_remember_bot/Cargo.toml index c61fbfd2..d224d528 100644 --- a/examples/sqlite_remember_bot/Cargo.toml +++ b/examples/sqlite_remember_bot/Cargo.toml @@ -5,14 +5,13 @@ authors = ["Maximilian Siling ", "Sergey Levitin as Storage>::Error; +type StorageError = as Storage>::Error; #[derive(Debug, Error)] enum Error { @@ -48,7 +48,7 @@ async fn main() { let dialogue = dialogue.expect("std::convert::Infallible"); handle_message(cx, dialogue).await.expect("Something wrong with the bot!") }, - SqliteStorage::open("db.sqlite", JSON).await.unwrap(), + SqliteStorage::open("db.sqlite", Json).await.unwrap(), )) .dispatch() .await; diff --git a/examples/sqlite_remember_bot/src/states.rs b/examples/sqlite_remember_bot/src/states.rs index 0bb65bd7..1c007b5a 100644 --- a/examples/sqlite_remember_bot/src/states.rs +++ b/examples/sqlite_remember_bot/src/states.rs @@ -1,4 +1,4 @@ -use teloxide_macros::Transition; +use teloxide::macros::Transition; use serde::{Deserialize, Serialize}; diff --git a/examples/sqlite_remember_bot/src/transitions.rs b/examples/sqlite_remember_bot/src/transitions.rs index 263f1991..2606e203 100644 --- a/examples/sqlite_remember_bot/src/transitions.rs +++ b/examples/sqlite_remember_bot/src/transitions.rs @@ -1,5 +1,5 @@ use teloxide::prelude::*; -use teloxide_macros::teloxide; +use teloxide::macros::teloxide; use super::states::*; diff --git a/src/dispatching/dialogue/storage/serializer.rs b/src/dispatching/dialogue/storage/serializer.rs index 16ce266a..aa1aaf01 100644 --- a/src/dispatching/dialogue/storage/serializer.rs +++ b/src/dispatching/dialogue/storage/serializer.rs @@ -11,9 +11,9 @@ pub trait Serializer { } /// The JSON serializer for memory storages. -pub struct JSON; +pub struct Json; -impl Serializer for JSON +impl Serializer for Json where D: Serialize + DeserializeOwned, { @@ -34,12 +34,12 @@ where #[cfg(feature = "cbor-serializer")] // FIXME(waffle): use `docsrs` here when issue with combine is resolved #[cfg_attr(all(teloxide_docsrs, feature = "nightly"), doc(cfg(feature = "cbor-serializer")))] -pub struct CBOR; +pub struct Cbor; #[cfg(feature = "cbor-serializer")] // FIXME(waffle): use `docsrs` here when issue with combine is resolved #[cfg_attr(all(teloxide_docsrs, feature = "nightly"), doc(cfg(feature = "cbor-serializer")))] -impl Serializer for CBOR +impl Serializer for Cbor where D: Serialize + DeserializeOwned, { diff --git a/src/dispatching/update_listeners.rs b/src/dispatching/update_listeners.rs index 131c3757..523eb5cf 100644 --- a/src/dispatching/update_listeners.rs +++ b/src/dispatching/update_listeners.rs @@ -182,11 +182,11 @@ where for update in &updates { if let Err((value, e)) = update { log::error!( - "Cannot parse an update.\nError: {:?}\nValue: {}\n\ + "Cannot parse an update.\nError: {:?}\nValue: {}\n\ This is a bug in teloxide-core, please open an issue here: \ https://github.com/teloxide/teloxide-core/issues.", - e, - value + e, + value ); } } diff --git a/src/lib.rs b/src/lib.rs index 7a3403da..6fc2e995 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -69,6 +69,11 @@ pub use teloxide_core::*; #[cfg(feature = "macros")] // FIXME(waffle): use `docsrs` here when issue with combine is resolved #[cfg_attr(all(teloxide_docsrs, feature = "nightly"), doc(cfg(feature = "macros")))] +pub use teloxide_macros as macros; + +// FIXME(waffle): use `docsrs` here when issue with combine is resolved +#[cfg_attr(all(teloxide_docsrs, feature = "nightly"), doc(cfg(feature = "macros")))] +#[cfg(feature = "macros")] pub use teloxide_macros::teloxide; #[cfg(all(feature = "nightly", doctest))] diff --git a/src/prelude.rs b/src/prelude.rs index 211a1b49..fb84de67 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -12,14 +12,19 @@ pub use crate::{ respond, }; -pub use teloxide_core::{ - adaptors::AutoSend, - types::{ - CallbackQuery, ChatMemberUpdated, ChosenInlineResult, InlineQuery, Message, Poll, - PollAnswer, PreCheckoutQuery, ShippingQuery, - }, +// FIXME(waffle): use `docsrs` here when issue with combine is resolved +#[cfg_attr(all(teloxide_docsrs, feature = "nightly"), doc(cfg(feature = "macros")))] +#[cfg(feature = "macros")] +pub use crate::teloxide; + +pub use teloxide_core::types::{ + CallbackQuery, ChatMemberUpdated, ChosenInlineResult, InlineQuery, Message, Poll, PollAnswer, + PreCheckoutQuery, ShippingQuery, }; +#[cfg(feature = "auto-send")] +pub use crate::adaptors::AutoSend; + #[doc(inline)] pub use teloxide_core::prelude::*; diff --git a/tests/redis.rs b/tests/redis.rs index 0a06c06e..2b88ad8e 100644 --- a/tests/redis.rs +++ b/tests/redis.rs @@ -9,7 +9,7 @@ use teloxide::dispatching::dialogue::{RedisStorage, Serializer, Storage}; async fn test_redis_json() { let storage = RedisStorage::open( "redis://127.0.0.1:7777", - teloxide::dispatching::dialogue::serializer::JSON, + teloxide::dispatching::dialogue::serializer::Json, ) .await .unwrap(); @@ -31,7 +31,7 @@ async fn test_redis_bincode() { async fn test_redis_cbor() { let storage = RedisStorage::open( "redis://127.0.0.1:7779", - teloxide::dispatching::dialogue::serializer::CBOR, + teloxide::dispatching::dialogue::serializer::Cbor, ) .await .unwrap(); diff --git a/tests/sqlite.rs b/tests/sqlite.rs index 152852bd..bf08142d 100644 --- a/tests/sqlite.rs +++ b/tests/sqlite.rs @@ -8,7 +8,7 @@ use teloxide::dispatching::dialogue::{Serializer, SqliteStorage, Storage}; #[tokio::test(flavor = "multi_thread")] async fn test_sqlite_json() { let storage = - SqliteStorage::open("./test_db1.sqlite", teloxide::dispatching::dialogue::serializer::JSON) + SqliteStorage::open("./test_db1.sqlite", teloxide::dispatching::dialogue::serializer::Json) .await .unwrap(); test_sqlite(storage).await; @@ -28,7 +28,7 @@ async fn test_sqlite_bincode() { #[tokio::test(flavor = "multi_thread")] async fn test_sqlite_cbor() { let storage = - SqliteStorage::open("./test_db3.sqlite", teloxide::dispatching::dialogue::serializer::CBOR) + SqliteStorage::open("./test_db3.sqlite", teloxide::dispatching::dialogue::serializer::Cbor) .await .unwrap(); test_sqlite(storage).await; From c72bba682c537cd2e4e3c6a9dbdf464fe370290c Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Sun, 21 Mar 2021 21:43:19 +0300 Subject: [PATCH 027/131] Resolve fixme (#350) * Update redis * Resolve fixme(waffle) related to building docs * Fix broken links * Reexport teloxide_core::prelude as `no_inline` --- Cargo.toml | 2 +- src/dispatching/dialogue/mod.rs | 6 ++---- src/dispatching/dialogue/storage/mod.rs | 3 +-- src/dispatching/dialogue/storage/serializer.rs | 12 ++++-------- src/dispatching/update_listeners.rs | 4 ++-- src/lib.rs | 12 ++++-------- src/prelude.rs | 8 +++----- src/utils/command.rs | 3 +-- src/utils/mod.rs | 3 +-- 9 files changed, 19 insertions(+), 34 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9de1b661..8873a8c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,7 +86,7 @@ sqlx = { version = "0.5", optional = true, default-features = false, features = "macros", "sqlite", ] } -redis = { version = "0.16", optional = true } +redis = { version = "0.20", features = ["tokio-comp"], optional = true } serde_cbor = { version = "0.11", optional = true } bincode = { version = "1.3", optional = true } frunk = { version = "0.3", optional = true } diff --git a/src/dispatching/dialogue/mod.rs b/src/dispatching/dialogue/mod.rs index 2bd7f39e..303d161c 100644 --- a/src/dispatching/dialogue/mod.rs +++ b/src/dispatching/dialogue/mod.rs @@ -158,13 +158,11 @@ pub use transition::{ }; #[cfg(feature = "macros")] -// FIXME(waffle): use `docsrs` here when issue with combine is resolved -#[cfg_attr(all(teloxide_docsrs, feature = "nightly"), doc(cfg(feature = "macros")))] +#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "macros")))] pub use teloxide_macros::Transition; #[cfg(feature = "redis-storage")] -// FIXME(waffle): use `docsrs` here when issue with combine is resolved -#[cfg_attr(all(teloxide_docsrs, feature = "nightly"), doc(cfg(feature = "redis-storage")))] +#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "redis-storage")))] pub use storage::{RedisStorage, RedisStorageError}; #[cfg(feature = "sqlite-storage")] diff --git a/src/dispatching/dialogue/storage/mod.rs b/src/dispatching/dialogue/storage/mod.rs index 1ca36fb1..31664425 100644 --- a/src/dispatching/dialogue/storage/mod.rs +++ b/src/dispatching/dialogue/storage/mod.rs @@ -14,8 +14,7 @@ use futures::future::BoxFuture; pub use self::{in_mem_storage::InMemStorage, trace_storage::TraceStorage}; #[cfg(feature = "redis-storage")] -// FIXME(waffle): use `docsrs` here when issue with combine is resolved -#[cfg_attr(all(teloxide_docsrs, feature = "nightly"), doc(cfg(feature = "redis-storage")))] +#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "redis-storage")))] pub use redis_storage::{RedisStorage, RedisStorageError}; pub use serializer::Serializer; use std::sync::Arc; diff --git a/src/dispatching/dialogue/storage/serializer.rs b/src/dispatching/dialogue/storage/serializer.rs index aa1aaf01..2e0aa945 100644 --- a/src/dispatching/dialogue/storage/serializer.rs +++ b/src/dispatching/dialogue/storage/serializer.rs @@ -32,13 +32,11 @@ where /// /// [CBOR]: https://en.wikipedia.org/wiki/CBOR #[cfg(feature = "cbor-serializer")] -// FIXME(waffle): use `docsrs` here when issue with combine is resolved -#[cfg_attr(all(teloxide_docsrs, feature = "nightly"), doc(cfg(feature = "cbor-serializer")))] +#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "cbor-serializer")))] pub struct Cbor; #[cfg(feature = "cbor-serializer")] -// FIXME(waffle): use `docsrs` here when issue with combine is resolved -#[cfg_attr(all(teloxide_docsrs, feature = "nightly"), doc(cfg(feature = "cbor-serializer")))] +#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "cbor-serializer")))] impl Serializer for Cbor where D: Serialize + DeserializeOwned, @@ -58,13 +56,11 @@ where /// /// [Bincode]: https://github.com/servo/bincode #[cfg(feature = "bincode-serializer")] -// FIXME(waffle): use `docsrs` here when issue with combine is resolved -#[cfg_attr(all(teloxide_docsrs, feature = "nightly"), doc(cfg(feature = "bincode-serializer")))] +#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "bincode-serializer")))] pub struct Bincode; #[cfg(feature = "bincode-serializer")] -// FIXME(waffle): use `docsrs` here when issue with combine is resolved -#[cfg_attr(all(teloxide_docsrs, feature = "nightly"), doc(cfg(feature = "bincode-serializer")))] +#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "bincode-serializer")))] impl Serializer for Bincode where D: Serialize + DeserializeOwned, diff --git a/src/dispatching/update_listeners.rs b/src/dispatching/update_listeners.rs index 523eb5cf..a3ec5097 100644 --- a/src/dispatching/update_listeners.rs +++ b/src/dispatching/update_listeners.rs @@ -97,7 +97,7 @@ //! [`UpdateListener`]: UpdateListener //! [`polling_default`]: polling_default //! [`polling`]: polling -//! [`Box::get_updates`]: crate::Bot::get_updates +//! [`Box::get_updates`]: crate::requests::Requester::get_updates //! [getting updates]: https://core.telegram.org/bots/api#getting-updates //! [long]: https://en.wikipedia.org/wiki/Push_technology#Long_polling //! [short]: https://en.wikipedia.org/wiki/Polling_(computer_science) @@ -139,7 +139,7 @@ where /// /// See also: [`polling_default`](polling_default). /// -/// [`GetUpdates`]: crate::requests::GetUpdates +/// [`GetUpdates`]: crate::payloads::GetUpdates pub fn polling( requester: R, timeout: Option, diff --git a/src/lib.rs b/src/lib.rs index 6fc2e995..5ecb64cf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -45,11 +45,9 @@ // // To properly build docs of this crate run // ```console -// FIXME(waffle): use `docsrs` here when issue with combine is resolved -// $ RUSTDOCFLAGS="--cfg teloxide_docsrs" cargo +nightly doc --open --all-features +// $ RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --open --all-features // ``` -// FIXME(waffle): use `docsrs` here when issue with combine is resolved -#![cfg_attr(all(teloxide_docsrs, feature = "nightly"), feature(doc_cfg))] +#![cfg_attr(all(docsrs, feature = "nightly"), feature(doc_cfg))] pub use dispatching::repls::{ commands_repl, commands_repl_with_listener, dialogues_repl, dialogues_repl_with_listener, repl, @@ -67,12 +65,10 @@ pub mod utils; pub use teloxide_core::*; #[cfg(feature = "macros")] -// FIXME(waffle): use `docsrs` here when issue with combine is resolved -#[cfg_attr(all(teloxide_docsrs, feature = "nightly"), doc(cfg(feature = "macros")))] +#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "macros")))] pub use teloxide_macros as macros; -// FIXME(waffle): use `docsrs` here when issue with combine is resolved -#[cfg_attr(all(teloxide_docsrs, feature = "nightly"), doc(cfg(feature = "macros")))] +#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "macros")))] #[cfg(feature = "macros")] pub use teloxide_macros::teloxide; diff --git a/src/prelude.rs b/src/prelude.rs index fb84de67..b7f9a040 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -12,8 +12,7 @@ pub use crate::{ respond, }; -// FIXME(waffle): use `docsrs` here when issue with combine is resolved -#[cfg_attr(all(teloxide_docsrs, feature = "nightly"), doc(cfg(feature = "macros")))] +#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "macros")))] #[cfg(feature = "macros")] pub use crate::teloxide; @@ -25,12 +24,11 @@ pub use teloxide_core::types::{ #[cfg(feature = "auto-send")] pub use crate::adaptors::AutoSend; -#[doc(inline)] +#[doc(no_inline)] pub use teloxide_core::prelude::*; #[cfg(feature = "frunk")] -// FIXME(waffle): use `docsrs` here when issue with combine is resolved -#[cfg_attr(all(teloxide_docsrs, feature = "nightly"), doc(cfg(feature = "frunk")))] +#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "frunk")))] pub use crate::utils::UpState; pub use tokio::sync::mpsc::UnboundedReceiver; diff --git a/src/utils/command.rs b/src/utils/command.rs index 83778813..22a795e4 100644 --- a/src/utils/command.rs +++ b/src/utils/command.rs @@ -52,8 +52,7 @@ use std::{ }; #[cfg(feature = "macros")] -// FIXME(waffle): use `docsrs` here when issue with combine is resolved -#[cfg_attr(all(teloxide_docsrs, feature = "nightly"), doc(cfg(feature = "macros")))] +#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "macros")))] pub use teloxide_macros::BotCommand; /// An enumeration of bot's commands. diff --git a/src/utils/mod.rs b/src/utils/mod.rs index e34561ae..6e29632d 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -8,6 +8,5 @@ mod up_state; pub use teloxide_core::net::client_from_env; #[cfg(feature = "frunk")] -// FIXME(waffle): use `docsrs` here when issue with combine is resolved -#[cfg_attr(all(teloxide_docsrs, feature = "nightly"), doc(cfg(feature = "frunk")))] +#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "frunk")))] pub use up_state::UpState; From 13e4bdb4e7f5038ee23b0ba2f5d49c2989faac5c Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Wed, 24 Mar 2021 04:20:28 +0600 Subject: [PATCH 028/131] Apply review suggestions --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 0ba4763f..8142b2cb 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@

- FYI: Updating to v0.4.0 + FYI: Updating from v0.3.4 to v0.4.0 - `answer_str` -> `answer` - - Don't write `.send()` after each request, now it is done automatically. - - Use `.auto_send()` to construct your bot: `let bot = Bot::from_env().auto_send();`. - - `UpdateWithCx` -> `UpdateWithCx, Message>` + - Use `.auto_send()` to construct your bot: `let bot = Bot::from_env().auto_send();`. This allows not to write `.send()` after each request; now it is done automatically. Also, rewrite `UpdateWithCx` -> `UpdateWithCx, Message>`. - `ResponseResult<()>` -> `Result<(), Box>` (or import `ResponseResult` beforehand: `use teloxide::requests::ResponseResult;`) + - Tokio updated to v1.2. + - `msg.text_owned()` -> `msg.map(ToOwned::to_owned)`
From 31eda80da0f8a4219286af954d4fa0909ea07cd7 Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Fri, 26 Mar 2021 10:22:19 -0700 Subject: [PATCH 029/131] Update README.md --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8142b2cb..68d74f53 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,15 @@ FYI: Updating from v0.3.4 to v0.4.0 - `answer_str` -> `answer` + - `msg.text_owned()` -> `msg.map(ToOwned::to_owned)` - Use `.auto_send()` to construct your bot: `let bot = Bot::from_env().auto_send();`. This allows not to write `.send()` after each request; now it is done automatically. Also, rewrite `UpdateWithCx` -> `UpdateWithCx, Message>`. - `ResponseResult<()>` -> `Result<(), Box>` (or import `ResponseResult` beforehand: `use teloxide::requests::ResponseResult;`) - Tokio updated to v1.2. - - `msg.text_owned()` -> `msg.map(ToOwned::to_owned)` + +Note: this list is non-exhaustive; for the full list of changes, see [teloxide-core's changelog] and the [changelog of teloxide]. + +[teloxide-core's changelog]: https://github.com/teloxide/teloxide-core/blob/master/CHANGELOG.md +[changelog of teloxide]: CHANGELOG.md From ffdc7043c48699bfbbb94070ab6f730e3853d15e Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Fri, 26 Mar 2021 10:29:17 -0700 Subject: [PATCH 030/131] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 68d74f53..2a131e2d 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,10 @@ - `ResponseResult<()>` -> `Result<(), Box>` (or import `ResponseResult` beforehand: `use teloxide::requests::ResponseResult;`) - Tokio updated to v1.2. -Note: this list is non-exhaustive; for the full list of changes, see [teloxide-core's changelog] and the [changelog of teloxide]. +Note: this list is non-exhaustive; for the full list of changes, see the [teloxide-core changelog] and [teloxide changelog]. -[teloxide-core's changelog]: https://github.com/teloxide/teloxide-core/blob/master/CHANGELOG.md -[changelog of teloxide]: CHANGELOG.md +[teloxide-core changelog]: https://github.com/teloxide/teloxide-core/blob/master/CHANGELOG.md +[teloxide changelog]: CHANGELOG.md From 0322a19a48cf2e04154535bf8d04993a50c48028 Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Sat, 27 Mar 2021 02:29:40 +0600 Subject: [PATCH 031/131] Enhance README.md --- README.md | 41 +++++++++++++++-------------------------- 1 file changed, 15 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index aec17f11..ae0b1262 100644 --- a/README.md +++ b/README.md @@ -39,19 +39,6 @@ Note: this list is non-exhaustive; for the full list of changes, see the [teloxi A full-featured framework that empowers you to easily build [Telegram bots](https://telegram.org/blog/bot-revolution) using the [`async`/`.await`](https://rust-lang.github.io/async-book/01_getting_started/01_chapter.html) syntax in [Rust](https://www.rust-lang.org/). It handles all the difficult stuff so you can focus only on your business logic. -## Table of contents - - [Highlights](#highlights) - - [Setting up your environment](#setting-up-your-environment) - - [API overview](#api-overview) - - [The dices bot](#the-dices-bot) - - [Commands](#commands) - - [Dialogues management](#dialogues-management) - - [Recommendations](#recommendations) - - [Cargo features](#cargo-features) - - [FAQ](#faq) - - [Community bots](#community-bots) - - [Contributing](#contributing) - ## Highlights - **Functional reactive design.** teloxide follows [functional reactive design], allowing you to declaratively manipulate streams of updates from Telegram using filters, maps, folds, zips, and a lot of [other adaptors]. @@ -396,18 +383,20 @@ The second one produces very strange compiler messages due to the `#[tokio::main ## Cargo features - - `redis-storage` -- enables the [Redis] support. - - `sqlite-storage` -- enables the [Sqlite] support. - - `cbor-serializer` -- enables the [CBOR] serializer for dialogues. - - `bincode-serializer` -- enables the [Bincode] serializer for dialogues. - - `frunk` -- enables [`teloxide::utils::UpState`], which allows mapping from a structure of `field1, ..., fieldN` to a structure of `field1, ..., fieldN, fieldN+1`. - - `macros` -- re-exports macros from [`teloxide-macros`]. - - `native-tls` -- enables the [`native-tls`] TLS implementation (enabled by default). - - `rustls` -- enables the [`rustls`] TLS implementation. - - `auto-send` -- enables `AutoSend` bot adaptor. - - `cache-me` -- enables the `CacheMe` bot adaptor. - - `full` -- enables all the features except `nightly`. - - `nightly` -- enables nightly-only features (see the [teloxide-core's features]). +| Feature | Description | +|----------|----------| +| `redis-storage` | Enables the [Redis] support.| +| `sqlite-storage` | Enables the [Sqlite] support. | +| `cbor-serializer` | Enables the [CBOR] serializer for dialogues. | +| `bincode-serializer` | Enables the [Bincode] serializer for dialogues. | +| `macros` | Re-exports macros from [`teloxide-macros`]. | +| `native-tls` | Enables the [`native-tls`] TLS implementation (enabled by default). | +| `rustls` | Enables the [`rustls`] TLS implementation. | +| `auto-send` | Enables the `AutoSend` bot adaptor. | +| `cache-me` | Enables the `CacheMe` bot adaptor. | +| `frunk` | Enables [`teloxide::utils::UpState`]. | +| `full` | Enables all the features except `nightly`. | +| `nightly` | Enables nightly-only features (see the [teloxide-core features]). | [CBOR]: https://en.wikipedia.org/wiki/CBOR [Bincode]: https://github.com/servo/bincode @@ -415,7 +404,7 @@ The second one produces very strange compiler messages due to the `#[tokio::main [`teloxide-macros`]: https://github.com/teloxide/teloxide-macros [`native-tls`]: https://docs.rs/native-tls [`rustls`]: https://docs.rs/rustls -[teloxide-core's features]: https://docs.rs/teloxide-core/0.2.1/teloxide_core/#cargo-features +[teloxide-core features]: https://docs.rs/teloxide-core/latest/teloxide_core/#cargo-features ## FAQ **Q: Where I can ask questions?** From 6724f265e35737e8cb6aad7432ee0e4410fb300c Mon Sep 17 00:00:00 2001 From: Diego Roig <2140622+diegopy@users.noreply.github.com> Date: Fri, 26 Mar 2021 17:34:39 -0500 Subject: [PATCH 032/131] remove reqwest dependency --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 8873a8c2..84f7fcf6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,7 +68,6 @@ tokio = { version = "1.2", features = ["fs"] } tokio-util = "0.6" tokio-stream = "0.1" -reqwest = { version = "0.11", features = ["json", "stream"] } log = "0.4" lockfree = "0.5.1" bytes = "1.0" From 10a7dfb7216028b7213b1a017e7b8e8b52b206c3 Mon Sep 17 00:00:00 2001 From: Diego Roig <2140622+diegopy@users.noreply.github.com> Date: Fri, 26 Mar 2021 17:34:39 -0500 Subject: [PATCH 033/131] remove reqwest dependency --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cb01ec4..4761c3d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [unreleased] +### Fixed + +- Remove `reqwest` dependency. It's not needed after the [teloxide-core] integration. ## [0.4.0] - 2021-03-22 ### Added From b4eea72203b3d384ac791c8b62841dbc70071ed3 Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Sun, 28 Mar 2021 00:02:19 +0600 Subject: [PATCH 034/131] Fix the usage of Tokio in deps (README.md) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fc4aa533..e3bd0795 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ $ rustup override set nightly teloxide = "0.4" log = "0.4.8" pretty_env_logger = "0.4.0" -tokio = { version = "1.3", features = ["rt-threaded", "macros"] } +tokio = { version = "1.3", features = ["rt-multi-thread", "macros"] } ``` ## API overview From bf1ed601ac805326fc3035e1bc14303da2641205 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sun, 28 Mar 2021 05:34:25 +0600 Subject: [PATCH 035/131] Fix the storage persistency bug --- CHANGELOG.md | 12 ++++++ .../dialogue/dialogue_dispatcher.rs | 24 +++++------ .../dialogue/storage/in_mem_storage.rs | 30 +++++++++---- src/dispatching/dialogue/storage/mod.rs | 20 ++++----- .../dialogue/storage/trace_storage.rs | 42 +++++++++---------- src/dispatching/repls/dialogues_repl.rs | 4 +- 6 files changed, 76 insertions(+), 56 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4761c3d0..64fa0e81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [unreleased] +### Added + + - `Storage::get_dialogue` + +### Changed + + - Do not return a dialogue from `Storage::{remove_dialogue, update_dialogue}`. + +### Fixed + + - A storage persistency bug ([issue 304](https://github.com/teloxide/teloxide/issues/304)). + ### Fixed - Remove `reqwest` dependency. It's not needed after the [teloxide-core] integration. diff --git a/src/dispatching/dialogue/dialogue_dispatcher.rs b/src/dispatching/dialogue/dialogue_dispatcher.rs index 706cf952..f2418596 100644 --- a/src/dispatching/dialogue/dialogue_dispatcher.rs +++ b/src/dispatching/dialogue/dialogue_dispatcher.rs @@ -4,7 +4,7 @@ use crate::dispatching::{ }, DispatcherHandler, UpdateWithCx, }; -use std::{convert::Infallible, marker::PhantomData}; +use std::{convert::Infallible, fmt::Debug, marker::PhantomData}; use futures::{future::BoxFuture, FutureExt, StreamExt}; use tokio::sync::mpsc; @@ -65,7 +65,7 @@ where Upd: GetChatId + Send + 'static, D: Default + Send + 'static, S: Storage + Send + Sync + 'static, - S::Error: Send + 'static, + S::Error: Debug + Send + 'static, { /// Creates a dispatcher with the specified `handler` and `storage`. #[must_use] @@ -97,18 +97,13 @@ where async move { let chat_id = cx.update.chat_id(); - let dialogue = Arc::clone(&storage) - .remove_dialogue(chat_id) - .await - .map(Option::unwrap_or_default); + let dialogue = + Arc::clone(&storage).get_dialogue(chat_id).await.map(Option::unwrap_or_default); match handler.handle(DialogueWithCx { cx, dialogue }).await { DialogueStage::Next(new_dialogue) => { - if let Ok(Some(_)) = storage.update_dialogue(chat_id, new_dialogue).await { - panic!( - "Oops, you have an bug in your Storage: update_dialogue returns \ - Some after remove_dialogue" - ); + if let Err(e) = storage.update_dialogue(chat_id, new_dialogue).await { + log::error!("Storage::update_dialogue failed: {:?}", e); } } DialogueStage::Exit => { @@ -117,8 +112,9 @@ where // sender right here: senders.remove(&chat_id); - // We already removed a dialogue from `storage` (see - // the beginning of this async block). + if let Err(e) = storage.remove_dialogue(chat_id).await { + log::error!("Storage::remove_dialogue failed: {:?}", e); + } } } } @@ -134,7 +130,7 @@ where Upd: GetChatId + Send + 'static, D: Default + Send + 'static, S: Storage + Send + Sync + 'static, - S::Error: Send + 'static, + S::Error: Debug + Send + 'static, R: Requester + Send, { fn handle( diff --git a/src/dispatching/dialogue/storage/in_mem_storage.rs b/src/dispatching/dialogue/storage/in_mem_storage.rs index 468a305e..476010d3 100644 --- a/src/dispatching/dialogue/storage/in_mem_storage.rs +++ b/src/dispatching/dialogue/storage/in_mem_storage.rs @@ -25,27 +25,41 @@ impl InMemStorage { } } -impl Storage for InMemStorage { +impl Storage for InMemStorage +where + D: ToOwned, + D: Send + 'static, +{ type Error = std::convert::Infallible; - fn remove_dialogue( - self: Arc, - chat_id: i64, - ) -> BoxFuture<'static, Result, Self::Error>> + fn remove_dialogue(self: Arc, chat_id: i64) -> BoxFuture<'static, Result<(), Self::Error>> where D: Send + 'static, { - Box::pin(async move { Ok(self.map.lock().await.remove(&chat_id)) }) + Box::pin(async move { + self.map.lock().await.remove(&chat_id); + Ok(()) + }) } fn update_dialogue( self: Arc, chat_id: i64, dialogue: D, - ) -> BoxFuture<'static, Result, Self::Error>> + ) -> BoxFuture<'static, Result<(), Self::Error>> where D: Send + 'static, { - Box::pin(async move { Ok(self.map.lock().await.insert(chat_id, dialogue)) }) + Box::pin(async move { + self.map.lock().await.insert(chat_id, dialogue); + Ok(()) + }) + } + + fn get_dialogue( + self: Arc, + chat_id: i64, + ) -> BoxFuture<'static, Result, Self::Error>> { + Box::pin(async move { Ok(self.map.lock().await.get(&chat_id).map(ToOwned::to_owned)) }) } } diff --git a/src/dispatching/dialogue/storage/mod.rs b/src/dispatching/dialogue/storage/mod.rs index 65cedcea..a7cd5a32 100644 --- a/src/dispatching/dialogue/storage/mod.rs +++ b/src/dispatching/dialogue/storage/mod.rs @@ -42,26 +42,26 @@ pub use sqlite_storage::{SqliteStorage, SqliteStorageError}; pub trait Storage { type Error; - /// Removes a dialogue with the specified `chat_id`. - /// - /// Returns `None` if there wasn't such a dialogue, `Some(dialogue)` if a - /// `dialogue` was deleted. + /// Removes a dialogue indexed by `chat_id`. fn remove_dialogue( self: Arc, chat_id: i64, - ) -> BoxFuture<'static, Result, Self::Error>> + ) -> BoxFuture<'static, Result<(), Self::Error>> where D: Send + 'static; - /// Updates a dialogue with the specified `chat_id`. - /// - /// Returns `None` if there wasn't such a dialogue, `Some(dialogue)` if a - /// `dialogue` was updated. + /// Updates a dialogue indexed by `chat_id` with `dialogue`. fn update_dialogue( self: Arc, chat_id: i64, dialogue: D, - ) -> BoxFuture<'static, Result, Self::Error>> + ) -> BoxFuture<'static, Result<(), Self::Error>> where D: Send + 'static; + + /// Provides a dialogue indexed by `chat_id`. + fn get_dialogue( + self: Arc, + chat_id: i64, + ) -> BoxFuture<'static, Result, Self::Error>>; } diff --git a/src/dispatching/dialogue/storage/trace_storage.rs b/src/dispatching/dialogue/storage/trace_storage.rs index 2e28263f..80b37887 100644 --- a/src/dispatching/dialogue/storage/trace_storage.rs +++ b/src/dispatching/dialogue/storage/trace_storage.rs @@ -5,14 +5,13 @@ use std::{ }; use futures::future::BoxFuture; -use log::{log_enabled, trace, Level::Trace}; use crate::dispatching::dialogue::Storage; -/// Storage wrapper for logging purposes +/// Storage wrapper for logging purposes. /// -/// Reports about any dialogue update or removal action on `trace` level -/// using `log` crate. +/// Reports about any dialogue action using the `trace` level in the `log` +/// crate. pub struct TraceStorage { inner: Arc, } @@ -35,14 +34,11 @@ where { type Error = >::Error; - fn remove_dialogue( - self: Arc, - chat_id: i64, - ) -> BoxFuture<'static, Result, Self::Error>> + fn remove_dialogue(self: Arc, chat_id: i64) -> BoxFuture<'static, Result<(), Self::Error>> where D: Send + 'static, { - trace!("Removing dialogue with {}", chat_id); + log::trace!("Removing dialogue #{}", chat_id); >::remove_dialogue(self.inner.clone(), chat_id) } @@ -50,21 +46,23 @@ where self: Arc, chat_id: i64, dialogue: D, - ) -> BoxFuture<'static, Result, Self::Error>> + ) -> BoxFuture<'static, Result<(), Self::Error>> where D: Send + 'static, { - if log_enabled!(Trace) { - Box::pin(async move { - let to = format!("{:#?}", dialogue); - let from = - >::update_dialogue(self.inner.clone(), chat_id, dialogue) - .await?; - trace!("Updated dialogue with {}, {:#?} -> {}", chat_id, from, to); - Ok(from) - }) - } else { - >::update_dialogue(self.inner.clone(), chat_id, dialogue) - } + Box::pin(async move { + let to = format!("{:#?}", dialogue); + >::update_dialogue(self.inner.clone(), chat_id, dialogue).await?; + log::trace!("Updated a dialogue #{}: {:#?}", chat_id, to); + Ok(()) + }) + } + + fn get_dialogue( + self: Arc, + chat_id: i64, + ) -> BoxFuture<'static, Result, Self::Error>> { + log::trace!("Requested a dialogue #{}", chat_id); + >::get_dialogue(self.inner.clone(), chat_id) } } diff --git a/src/dispatching/repls/dialogues_repl.rs b/src/dispatching/repls/dialogues_repl.rs index 706d26a1..c46a2dc4 100644 --- a/src/dispatching/repls/dialogues_repl.rs +++ b/src/dispatching/repls/dialogues_repl.rs @@ -26,7 +26,7 @@ use teloxide_core::{requests::Requester, types::Message}; pub async fn dialogues_repl<'a, R, H, D, Fut>(requester: R, handler: H) where H: Fn(UpdateWithCx, D) -> Fut + Send + Sync + 'static, - D: Default + Send + 'static, + D: ToOwned + Default + Send + 'static, Fut: Future> + Send + 'static, R: Requester + Send + Clone + 'static, ::GetUpdatesFaultTolerant: Send, @@ -61,7 +61,7 @@ pub async fn dialogues_repl_with_listener<'a, R, H, D, Fut, L, ListenerE>( listener: L, ) where H: Fn(UpdateWithCx, D) -> Fut + Send + Sync + 'static, - D: Default + Send + 'static, + D: ToOwned + Default + Send + 'static, Fut: Future> + Send + 'static, L: UpdateListener + Send + 'a, ListenerE: Debug + Send + 'a, From abbbc41892c314b2562cbff51c728b02e19709eb Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sun, 28 Mar 2021 06:20:13 +0600 Subject: [PATCH 036/131] Fix RedisStorage, SqliteStorage --- .../dialogue/storage/redis_storage.rs | 21 ++++++-- .../dialogue/storage/sqlite_storage.rs | 48 ++++++++++--------- 2 files changed, 42 insertions(+), 27 deletions(-) diff --git a/src/dispatching/dialogue/storage/redis_storage.rs b/src/dispatching/dialogue/storage/redis_storage.rs index 31a358e8..a89576cf 100644 --- a/src/dispatching/dialogue/storage/redis_storage.rs +++ b/src/dispatching/dialogue/storage/redis_storage.rs @@ -56,7 +56,7 @@ where fn remove_dialogue( self: Arc, chat_id: i64, - ) -> BoxFuture<'static, Result, Self::Error>> { + ) -> BoxFuture<'static, Result<(), Self::Error>> { Box::pin(async move { let res = redis::pipe() .atomic() @@ -70,13 +70,14 @@ where // bulk, so all other branches should be unreachable match res { redis::Value::Bulk(bulk) if bulk.len() == 1 => { - Ok(Option::>::from_redis_value(&bulk[0])? + Option::>::from_redis_value(&bulk[0])? .map(|v| { self.serializer .deserialize(&v) .map_err(RedisStorageError::SerdeError) }) - .transpose()?) + .transpose()?; + Ok(()) } _ => unreachable!(), } @@ -87,14 +88,24 @@ where self: Arc, chat_id: i64, dialogue: D, - ) -> BoxFuture<'static, Result, Self::Error>> { + ) -> BoxFuture<'static, Result<(), Self::Error>> { Box::pin(async move { let dialogue = self.serializer.serialize(&dialogue).map_err(RedisStorageError::SerdeError)?; + self.conn.lock().await.getset::<_, Vec, Option>>(chat_id, dialogue).await?; + Ok(()) + }) + } + + fn get_dialogue( + self: Arc, + chat_id: i64, + ) -> BoxFuture<'static, Result, Self::Error>> { + Box::pin(async move { self.conn .lock() .await - .getset::<_, Vec, Option>>(chat_id, dialogue) + .get::<_, Option>>(chat_id) .await? .map(|d| self.serializer.deserialize(&d).map_err(RedisStorageError::SerdeError)) .transpose() diff --git a/src/dispatching/dialogue/storage/sqlite_storage.rs b/src/dispatching/dialogue/storage/sqlite_storage.rs index f4e4d98c..ceaeacf2 100644 --- a/src/dispatching/dialogue/storage/sqlite_storage.rs +++ b/src/dispatching/dialogue/storage/sqlite_storage.rs @@ -63,20 +63,16 @@ where fn remove_dialogue( self: Arc, chat_id: i64, - ) -> BoxFuture<'static, Result, Self::Error>> { + ) -> BoxFuture<'static, Result<(), Self::Error>> { Box::pin(async move { - Ok(match get_dialogue(&self.pool, chat_id).await? { - Some(d) => { - let prev_dialogue = - self.serializer.deserialize(&d).map_err(SqliteStorageError::SerdeError)?; - sqlx::query("DELETE FROM teloxide_dialogues WHERE chat_id = ?") - .bind(chat_id) - .execute(&self.pool) - .await?; - Some(prev_dialogue) - } - _ => None, - }) + if get_dialogue(&self.pool, chat_id).await?.is_some() { + sqlx::query("DELETE FROM teloxide_dialogues WHERE chat_id = ?") + .bind(chat_id) + .execute(&self.pool) + .await?; + } + + Ok(()) }) } @@ -84,14 +80,10 @@ where self: Arc, chat_id: i64, dialogue: D, - ) -> BoxFuture<'static, Result, Self::Error>> { + ) -> BoxFuture<'static, Result<(), Self::Error>> { Box::pin(async move { - let prev_dialogue = get_dialogue(&self.pool, chat_id) - .await? - .map(|d| self.serializer.deserialize(&d).map_err(SqliteStorageError::SerdeError)) - .transpose()?; - let upd_dialogue = - self.serializer.serialize(&dialogue).map_err(SqliteStorageError::SerdeError)?; + let d = self.serializer.serialize(&dialogue).map_err(SqliteStorageError::SerdeError)?; + self.pool .acquire() .await? @@ -103,10 +95,22 @@ where "#, ) .bind(chat_id) - .bind(upd_dialogue), + .bind(d), ) .await?; - Ok(prev_dialogue) + Ok(()) + }) + } + + fn get_dialogue( + self: Arc, + chat_id: i64, + ) -> BoxFuture<'static, Result, Self::Error>> { + Box::pin(async move { + get_dialogue(&self.pool, chat_id) + .await? + .map(|d| self.serializer.deserialize(&d).map_err(SqliteStorageError::SerdeError)) + .transpose() }) } } From 01b7b91bda595daff7159b9cca4e662d9135027f Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sun, 28 Mar 2021 06:30:35 +0600 Subject: [PATCH 037/131] Document how Storage failures are handled in DialogueDispatcher --- src/dispatching/dialogue/dialogue_dispatcher.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/dispatching/dialogue/dialogue_dispatcher.rs b/src/dispatching/dialogue/dialogue_dispatcher.rs index f2418596..b98c029c 100644 --- a/src/dispatching/dialogue/dialogue_dispatcher.rs +++ b/src/dispatching/dialogue/dialogue_dispatcher.rs @@ -19,6 +19,11 @@ use tokio_stream::wrappers::UnboundedReceiverStream; /// Note that it implements [`DispatcherHandler`], so you can just put an /// instance of this dispatcher into the [`Dispatcher`]'s methods. /// +/// Note that when the storage methods [`Storage::remove_dialogue`] and +/// [`Storage::update_dialogue`] are failed, the errors are logged, but a result +/// from [`Storage::get_dialogue`] is provided to a user handler as-is so you +/// can respond to a concrete user with an error description. +/// /// See the [module-level documentation](crate::dispatching::dialogue) for the /// design overview. /// From 9b75378572dec42a5d9d4891bf55cf8c17eea447 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sun, 28 Mar 2021 08:20:35 +0600 Subject: [PATCH 038/131] Fix the tests and examples --- CHANGELOG.md | 1 + examples/dialogue_bot/src/dialogue/mod.rs | 2 +- .../src/dialogue/states/receive_age.rs | 2 +- .../src/dialogue/states/receive_full_name.rs | 2 +- .../src/dialogue/states/receive_location.rs | 2 +- .../dialogue_bot/src/dialogue/states/start.rs | 1 + src/dispatching/dialogue/mod.rs | 5 ++- tests/redis.rs | 42 ++++++++++--------- tests/sqlite.rs | 42 ++++++++++--------- 9 files changed, 54 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64fa0e81..b42a9c83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Do not return a dialogue from `Storage::{remove_dialogue, update_dialogue}`. + - Require `D: ToOwned` in `dialogues_repl` and `InMemStorage`. ### Fixed diff --git a/examples/dialogue_bot/src/dialogue/mod.rs b/examples/dialogue_bot/src/dialogue/mod.rs index 4bae759a..43ad8db3 100644 --- a/examples/dialogue_bot/src/dialogue/mod.rs +++ b/examples/dialogue_bot/src/dialogue/mod.rs @@ -6,7 +6,7 @@ use crate::dialogue::states::{ use derive_more::From; use teloxide::macros::Transition; -#[derive(Transition, From)] +#[derive(Transition, Clone, From)] pub enum Dialogue { Start(StartState), ReceiveFullName(ReceiveFullNameState), diff --git a/examples/dialogue_bot/src/dialogue/states/receive_age.rs b/examples/dialogue_bot/src/dialogue/states/receive_age.rs index 36c72a23..099b3407 100644 --- a/examples/dialogue_bot/src/dialogue/states/receive_age.rs +++ b/examples/dialogue_bot/src/dialogue/states/receive_age.rs @@ -1,7 +1,7 @@ use crate::dialogue::{states::receive_location::ReceiveLocationState, Dialogue}; use teloxide::prelude::*; -#[derive(Generic)] +#[derive(Clone, Generic)] pub struct ReceiveAgeState { pub full_name: String, } diff --git a/examples/dialogue_bot/src/dialogue/states/receive_full_name.rs b/examples/dialogue_bot/src/dialogue/states/receive_full_name.rs index 21d3fef2..2ea60a1c 100644 --- a/examples/dialogue_bot/src/dialogue/states/receive_full_name.rs +++ b/examples/dialogue_bot/src/dialogue/states/receive_full_name.rs @@ -1,7 +1,7 @@ use crate::dialogue::{states::receive_age::ReceiveAgeState, Dialogue}; use teloxide::prelude::*; -#[derive(Generic)] +#[derive(Clone, Generic)] pub struct ReceiveFullNameState; #[teloxide(subtransition)] diff --git a/examples/dialogue_bot/src/dialogue/states/receive_location.rs b/examples/dialogue_bot/src/dialogue/states/receive_location.rs index aaa1af2d..3c1f6407 100644 --- a/examples/dialogue_bot/src/dialogue/states/receive_location.rs +++ b/examples/dialogue_bot/src/dialogue/states/receive_location.rs @@ -1,7 +1,7 @@ use crate::dialogue::Dialogue; use teloxide::prelude::*; -#[derive(Generic)] +#[derive(Clone, Generic)] pub struct ReceiveLocationState { pub full_name: String, pub age: u8, diff --git a/examples/dialogue_bot/src/dialogue/states/start.rs b/examples/dialogue_bot/src/dialogue/states/start.rs index a4f3c192..f3f12e0c 100644 --- a/examples/dialogue_bot/src/dialogue/states/start.rs +++ b/examples/dialogue_bot/src/dialogue/states/start.rs @@ -1,6 +1,7 @@ use crate::dialogue::{states::ReceiveFullNameState, Dialogue}; use teloxide::prelude::*; +#[derive(Clone)] pub struct StartState; #[teloxide(subtransition)] diff --git a/src/dispatching/dialogue/mod.rs b/src/dispatching/dialogue/mod.rs index 303d161c..ee182dce 100644 --- a/src/dispatching/dialogue/mod.rs +++ b/src/dispatching/dialogue/mod.rs @@ -35,8 +35,11 @@ //! //! use teloxide::{dispatching::dialogue::Transition, prelude::*, teloxide, RequestError}; //! +//! #[derive(Clone)] //! struct _1State; +//! #[derive(Clone)] //! struct _2State; +//! #[derive(Clone)] //! struct _3State; //! //! type Out = TransitionOut; @@ -56,7 +59,7 @@ //! todo!() //! } //! -//! #[derive(Transition)] +//! #[derive(Clone, Transition)] //! enum D { //! _1(_1State), //! _2(_2State), diff --git a/tests/redis.rs b/tests/redis.rs index 2b88ad8e..9777597e 100644 --- a/tests/redis.rs +++ b/tests/redis.rs @@ -1,6 +1,5 @@ use std::{ fmt::{Debug, Display}, - future::Future, sync::Arc, }; use teloxide::dispatching::dialogue::{RedisStorage, Serializer, Storage}; @@ -40,32 +39,35 @@ async fn test_redis_cbor() { type Dialogue = String; +macro_rules! test_dialogues { + ($storage:expr, $_0:expr, $_1:expr, $_2:expr) => { + assert_eq!(Arc::clone(&$storage).get_dialogue(1).await.unwrap(), $_0); + assert_eq!(Arc::clone(&$storage).get_dialogue(11).await.unwrap(), $_1); + assert_eq!(Arc::clone(&$storage).get_dialogue(256).await.unwrap(), $_2); + }; +} + async fn test_redis(storage: Arc>) where S: Send + Sync + Serializer + 'static, >::Error: Debug + Display, { - check_dialogue(None, Arc::clone(&storage).update_dialogue(1, "ABC".to_owned())).await; - check_dialogue(None, Arc::clone(&storage).update_dialogue(11, "DEF".to_owned())).await; - check_dialogue(None, Arc::clone(&storage).update_dialogue(256, "GHI".to_owned())).await; + test_dialogues!(storage, None, None, None); - // 1 - ABC, 11 - DEF, 256 - GHI + Arc::clone(&storage).update_dialogue(1, "ABC".to_owned()).await.unwrap(); + Arc::clone(&storage).update_dialogue(11, "DEF".to_owned()).await.unwrap(); + Arc::clone(&storage).update_dialogue(256, "GHI".to_owned()).await.unwrap(); - check_dialogue("ABC", Arc::clone(&storage).update_dialogue(1, "JKL".to_owned())).await; - check_dialogue("GHI", Arc::clone(&storage).update_dialogue(256, "MNO".to_owned())).await; + test_dialogues!( + storage, + Some("ABC".to_owned()), + Some("DEF".to_owned()), + Some("GHI".to_owned()) + ); - // 1 - GKL, 11 - DEF, 256 - MNO + Arc::clone(&storage).remove_dialogue(1).await.unwrap(); + Arc::clone(&storage).remove_dialogue(11).await.unwrap(); + Arc::clone(&storage).remove_dialogue(256).await.unwrap(); - check_dialogue("JKL", Arc::clone(&storage).remove_dialogue(1)).await; - check_dialogue("DEF", Arc::clone(&storage).remove_dialogue(11)).await; - check_dialogue("MNO", Arc::clone(&storage).remove_dialogue(256)).await; -} - -async fn check_dialogue( - expected: impl Into>, - actual: impl Future, E>>, -) where - E: Debug, -{ - assert_eq!(expected.into().map(ToOwned::to_owned), actual.await.unwrap()) + test_dialogues!(storage, None, None, None); } diff --git a/tests/sqlite.rs b/tests/sqlite.rs index bf08142d..de37de6c 100644 --- a/tests/sqlite.rs +++ b/tests/sqlite.rs @@ -1,6 +1,5 @@ use std::{ fmt::{Debug, Display}, - future::Future, sync::Arc, }; use teloxide::dispatching::dialogue::{Serializer, SqliteStorage, Storage}; @@ -36,32 +35,35 @@ async fn test_sqlite_cbor() { type Dialogue = String; +macro_rules! test_dialogues { + ($storage:expr, $_0:expr, $_1:expr, $_2:expr) => { + assert_eq!(Arc::clone(&$storage).get_dialogue(1).await.unwrap(), $_0); + assert_eq!(Arc::clone(&$storage).get_dialogue(11).await.unwrap(), $_1); + assert_eq!(Arc::clone(&$storage).get_dialogue(256).await.unwrap(), $_2); + }; +} + async fn test_sqlite(storage: Arc>) where S: Send + Sync + Serializer + 'static, >::Error: Debug + Display, { - check_dialogue(None, Arc::clone(&storage).update_dialogue(1, "ABC".to_owned())).await; - check_dialogue(None, Arc::clone(&storage).update_dialogue(11, "DEF".to_owned())).await; - check_dialogue(None, Arc::clone(&storage).update_dialogue(256, "GHI".to_owned())).await; + test_dialogues!(storage, None, None, None); - // 1 - ABC, 11 - DEF, 256 - GHI + Arc::clone(&storage).update_dialogue(1, "ABC".to_owned()).await.unwrap(); + Arc::clone(&storage).update_dialogue(11, "DEF".to_owned()).await.unwrap(); + Arc::clone(&storage).update_dialogue(256, "GHI".to_owned()).await.unwrap(); - check_dialogue("ABC", Arc::clone(&storage).update_dialogue(1, "JKL".to_owned())).await; - check_dialogue("GHI", Arc::clone(&storage).update_dialogue(256, "MNO".to_owned())).await; + test_dialogues!( + storage, + Some("ABC".to_owned()), + Some("DEF".to_owned()), + Some("GHI".to_owned()) + ); - // 1 - GKL, 11 - DEF, 256 - MNO + Arc::clone(&storage).remove_dialogue(1).await.unwrap(); + Arc::clone(&storage).remove_dialogue(11).await.unwrap(); + Arc::clone(&storage).remove_dialogue(256).await.unwrap(); - check_dialogue("JKL", Arc::clone(&storage).remove_dialogue(1)).await; - check_dialogue("DEF", Arc::clone(&storage).remove_dialogue(11)).await; - check_dialogue("MNO", Arc::clone(&storage).remove_dialogue(256)).await; -} - -async fn check_dialogue( - expected: impl Into>, - actual: impl Future, E>>, -) where - E: Debug, -{ - assert_eq!(expected.into().map(ToOwned::to_owned), actual.await.unwrap()) + test_dialogues!(storage, None, None, None); } From eac67af27a06699102c092223d6c8ff94d34ddc5 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sun, 28 Mar 2021 08:30:05 +0600 Subject: [PATCH 039/131] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b42a9c83..d1e267b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - A storage persistency bug ([issue 304](https://github.com/teloxide/teloxide/issues/304)). + - Log errors from `Storage::{remove_dialogue, update_dialogue}` in `DialogueDispatcher` ([issue 302](https://github.com/teloxide/teloxide/issues/302)). ### Fixed From b7e2f1430736e165ec747a6f2353d8174366dc12 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sun, 28 Mar 2021 08:51:57 +0600 Subject: [PATCH 040/131] Automatically delete a webhook if it was set up --- CHANGELOG.md | 5 +++++ src/dispatching/dispatcher.rs | 2 +- src/dispatching/repls/commands_repl.rs | 2 +- src/dispatching/repls/dialogues_repl.rs | 2 +- src/dispatching/repls/repl.rs | 8 +++++-- src/dispatching/update_listeners.rs | 28 ++++++++++++++++++++++++- 6 files changed, 41 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4761c3d0..f53895ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,9 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [unreleased] +### Changed + + - Automatically delete a webhook if it was set up in `update_listeners::polling_default` (thereby making it `async`, [issue 319](https://github.com/teloxide/teloxide/issues/319)). + ### Fixed - Remove `reqwest` dependency. It's not needed after the [teloxide-core] integration. + ## [0.4.0] - 2021-03-22 ### Added diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index e7635ebb..93bf8a7e 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -233,7 +233,7 @@ where ::GetUpdatesFaultTolerant: Send, { self.dispatch_with_listener( - update_listeners::polling_default(self.requester.clone()), + update_listeners::polling_default(self.requester.clone()).await, LoggingErrorHandler::with_custom_text("An error from the update listener"), ) .await; diff --git a/src/dispatching/repls/commands_repl.rs b/src/dispatching/repls/commands_repl.rs index 249d4861..88a7c33d 100644 --- a/src/dispatching/repls/commands_repl.rs +++ b/src/dispatching/repls/commands_repl.rs @@ -39,7 +39,7 @@ where requester, bot_name, handler, - update_listeners::polling_default(cloned_requester), + update_listeners::polling_default(cloned_requester).await, ) .await; } diff --git a/src/dispatching/repls/dialogues_repl.rs b/src/dispatching/repls/dialogues_repl.rs index 706d26a1..1863af8b 100644 --- a/src/dispatching/repls/dialogues_repl.rs +++ b/src/dispatching/repls/dialogues_repl.rs @@ -36,7 +36,7 @@ where dialogues_repl_with_listener( requester, handler, - update_listeners::polling_default(cloned_requester), + update_listeners::polling_default(cloned_requester).await, ) .await; } diff --git a/src/dispatching/repls/repl.rs b/src/dispatching/repls/repl.rs index 31075f60..3c498696 100644 --- a/src/dispatching/repls/repl.rs +++ b/src/dispatching/repls/repl.rs @@ -31,8 +31,12 @@ where ::GetUpdatesFaultTolerant: Send, { let cloned_requester = requester.clone(); - repl_with_listener(requester, handler, update_listeners::polling_default(cloned_requester)) - .await; + repl_with_listener( + requester, + handler, + update_listeners::polling_default(cloned_requester).await, + ) + .await; } /// Like [`repl`], but with a custom [`UpdateListener`]. diff --git a/src/dispatching/update_listeners.rs b/src/dispatching/update_listeners.rs index a3ec5097..bdece104 100644 --- a/src/dispatching/update_listeners.rs +++ b/src/dispatching/update_listeners.rs @@ -120,11 +120,16 @@ impl UpdateListener for S where S: Stream> {} /// Returns a long polling update listener with `timeout` of 10 seconds. /// /// See also: [`polling`](polling). -pub fn polling_default(requester: R) -> impl UpdateListener +/// +/// ## Notes +/// +/// This function will automatically delete a webhook if it was set up. +pub async fn polling_default(requester: R) -> impl UpdateListener where R: Requester, ::GetUpdatesFaultTolerant: Send, { + delete_webhook_if_setup(&requester).await; polling(requester, Some(Duration::from_secs(10)), None, None) } @@ -200,3 +205,24 @@ where ) .flatten() } + +async fn delete_webhook_if_setup(requester: &R) +where + R: Requester, +{ + let webhook_info = match requester.get_webhook_info().send().await { + Ok(ok) => ok, + Err(e) => { + log::error!("Failed to get webhook info: {:?}", e); + return; + } + }; + + let is_webhook_setup = !webhook_info.url.is_empty(); + + if is_webhook_setup { + if let Err(e) = requester.delete_webhook().send().await { + log::error!("Failed to delete a webhook: {:?}", e); + } + } +} From 5affdf1759e1860a109d63069e5f50a1137bdc0c Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Sun, 28 Mar 2021 09:00:36 +0600 Subject: [PATCH 041/131] Enforce writing log::!(...) in CODE_STYLE.md --- CODE_STYLE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CODE_STYLE.md b/CODE_STYLE.md index 964aea8c..0fa0756b 100644 --- a/CODE_STYLE.md +++ b/CODE_STYLE.md @@ -124,3 +124,4 @@ C: Into, { ... } 1. Use `Into<...>` only where there exists at least one conversion **and** it will be logically to use. 2. Always mark a function as `#[must_use]` if its return value **must** be used. 3. `Box::pin(async [move] { ... })` instead of `async [move] { ... }.boxed()`. + 4. Always write `log::!(...)` instead of importing `use log::;` and invoking `!(...)`. For example, write `log::info!("blah")`. From 68135d004f377ce800a817e482982d6aabdd65ea Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sun, 28 Mar 2021 16:30:12 +0600 Subject: [PATCH 042/131] ToOwned -> D: Clone --- CHANGELOG.md | 2 +- src/dispatching/dialogue/storage/in_mem_storage.rs | 2 +- src/dispatching/repls/dialogues_repl.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1e267b8..e0a688d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Do not return a dialogue from `Storage::{remove_dialogue, update_dialogue}`. - - Require `D: ToOwned` in `dialogues_repl` and `InMemStorage`. + - Require `D: Clone` in `dialogues_repl(_with_listener)` and `InMemStorage`. ### Fixed diff --git a/src/dispatching/dialogue/storage/in_mem_storage.rs b/src/dispatching/dialogue/storage/in_mem_storage.rs index 476010d3..e5ca38b9 100644 --- a/src/dispatching/dialogue/storage/in_mem_storage.rs +++ b/src/dispatching/dialogue/storage/in_mem_storage.rs @@ -27,7 +27,7 @@ impl InMemStorage { impl Storage for InMemStorage where - D: ToOwned, + D: Clone, D: Send + 'static, { type Error = std::convert::Infallible; diff --git a/src/dispatching/repls/dialogues_repl.rs b/src/dispatching/repls/dialogues_repl.rs index c46a2dc4..3d2c0f66 100644 --- a/src/dispatching/repls/dialogues_repl.rs +++ b/src/dispatching/repls/dialogues_repl.rs @@ -26,7 +26,7 @@ use teloxide_core::{requests::Requester, types::Message}; pub async fn dialogues_repl<'a, R, H, D, Fut>(requester: R, handler: H) where H: Fn(UpdateWithCx, D) -> Fut + Send + Sync + 'static, - D: ToOwned + Default + Send + 'static, + D: Clone + Default + Send + 'static, Fut: Future> + Send + 'static, R: Requester + Send + Clone + 'static, ::GetUpdatesFaultTolerant: Send, @@ -61,7 +61,7 @@ pub async fn dialogues_repl_with_listener<'a, R, H, D, Fut, L, ListenerE>( listener: L, ) where H: Fn(UpdateWithCx, D) -> Fut + Send + Sync + 'static, - D: ToOwned + Default + Send + 'static, + D: Clone + Default + Send + 'static, Fut: Future> + Send + 'static, L: UpdateListener + Send + 'a, ListenerE: Debug + Send + 'a, From 701dcbcb6864801bbafb35bd01bc88af20584fba Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Sun, 28 Mar 2021 16:37:51 +0600 Subject: [PATCH 043/131] Amalgamate 'Fixed' sections in CHANGELOG.md --- CHANGELOG.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 391b6db5..d34b2fb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,13 +18,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed + - Remove the `reqwest` dependency. It's not needed after the [teloxide-core] integration. - A storage persistency bug ([issue 304](https://github.com/teloxide/teloxide/issues/304)). - Log errors from `Storage::{remove_dialogue, update_dialogue}` in `DialogueDispatcher` ([issue 302](https://github.com/teloxide/teloxide/issues/302)). -### Fixed - -- Remove `reqwest` dependency. It's not needed after the [teloxide-core] integration. - ## [0.4.0] - 2021-03-22 ### Added From ca60e52f4397f9874f4fd8a3a472d5fc2f90a5f2 Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Sun, 28 Mar 2021 22:18:47 -0700 Subject: [PATCH 044/131] Update src/dispatching/dialogue/storage/redis_storage.rs Co-authored-by: Waffle Lapkin --- src/dispatching/dialogue/storage/redis_storage.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dispatching/dialogue/storage/redis_storage.rs b/src/dispatching/dialogue/storage/redis_storage.rs index a89576cf..540fad0e 100644 --- a/src/dispatching/dialogue/storage/redis_storage.rs +++ b/src/dispatching/dialogue/storage/redis_storage.rs @@ -92,7 +92,7 @@ where Box::pin(async move { let dialogue = self.serializer.serialize(&dialogue).map_err(RedisStorageError::SerdeError)?; - self.conn.lock().await.getset::<_, Vec, Option>>(chat_id, dialogue).await?; + self.conn.lock().await.set::<_, Vec, Option>>(chat_id, dialogue).await?; Ok(()) }) } From fff0b670fb0c55b0a3f10eea42986e2bed07a7a9 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Mon, 29 Mar 2021 12:20:17 +0600 Subject: [PATCH 045/131] Fix RedisStorage::update_dialogue --- src/dispatching/dialogue/storage/redis_storage.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dispatching/dialogue/storage/redis_storage.rs b/src/dispatching/dialogue/storage/redis_storage.rs index 540fad0e..03313624 100644 --- a/src/dispatching/dialogue/storage/redis_storage.rs +++ b/src/dispatching/dialogue/storage/redis_storage.rs @@ -92,7 +92,7 @@ where Box::pin(async move { let dialogue = self.serializer.serialize(&dialogue).map_err(RedisStorageError::SerdeError)?; - self.conn.lock().await.set::<_, Vec, Option>>(chat_id, dialogue).await?; + self.conn.lock().await.set::<_, Vec, _>(chat_id, dialogue).await?; Ok(()) }) } From 3e2548cd1dd6cbd29f829d6578be34fb471e8ad0 Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Mon, 29 Mar 2021 20:10:52 +0600 Subject: [PATCH 046/131] Specify the 'auto-send' feature in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e3bd0795..54f0c841 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ $ rustup override set nightly 5. Run `cargo new my_bot`, enter the directory and put these lines into your `Cargo.toml`: ```toml [dependencies] -teloxide = "0.4" +teloxide = { verison = "0.4", features = ["auto-send"] } log = "0.4.8" pretty_env_logger = "0.4.0" tokio = { version = "1.3", features = ["rt-multi-thread", "macros"] } From 0c7c9232a5bb962698bef4cbaca4a882d90f1770 Mon Sep 17 00:00:00 2001 From: ukewea <60734042+ukewea@users.noreply.github.com> Date: Fri, 2 Apr 2021 20:13:13 +0800 Subject: [PATCH 047/131] Fix a typo in the Cargo.toml example in README The current example will cause warnings while compiling: warning: dependency (teloxide) specified without providing a local path, Git repository, or version to use. This will be considered an error in future versions warning: unused manifest key: dependencies.teloxide.verison By fixing the typo we can get rid of them. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 54f0c841..8c794026 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ $ rustup override set nightly 5. Run `cargo new my_bot`, enter the directory and put these lines into your `Cargo.toml`: ```toml [dependencies] -teloxide = { verison = "0.4", features = ["auto-send"] } +teloxide = { version = "0.4", features = ["auto-send"] } log = "0.4.8" pretty_env_logger = "0.4.0" tokio = { version = "1.3", features = ["rt-multi-thread", "macros"] } From 617c861d26706e2124d2e2b741908345e3566296 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sat, 3 Apr 2021 06:53:02 +0600 Subject: [PATCH 048/131] Mark all the functions of Storage as #[must_use] --- CHANGELOG.md | 1 + src/dispatching/dialogue/storage/mod.rs | 3 +++ 2 files changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d34b2fb2..9491dffc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Remove the `reqwest` dependency. It's not needed after the [teloxide-core] integration. - A storage persistency bug ([issue 304](https://github.com/teloxide/teloxide/issues/304)). - Log errors from `Storage::{remove_dialogue, update_dialogue}` in `DialogueDispatcher` ([issue 302](https://github.com/teloxide/teloxide/issues/302)). + - Mark all the functions of `Storage` as `#[must_use]`. ## [0.4.0] - 2021-03-22 diff --git a/src/dispatching/dialogue/storage/mod.rs b/src/dispatching/dialogue/storage/mod.rs index a7cd5a32..4dc3165c 100644 --- a/src/dispatching/dialogue/storage/mod.rs +++ b/src/dispatching/dialogue/storage/mod.rs @@ -43,6 +43,7 @@ pub trait Storage { type Error; /// Removes a dialogue indexed by `chat_id`. + #[must_use] fn remove_dialogue( self: Arc, chat_id: i64, @@ -51,6 +52,7 @@ pub trait Storage { D: Send + 'static; /// Updates a dialogue indexed by `chat_id` with `dialogue`. + #[must_use] fn update_dialogue( self: Arc, chat_id: i64, @@ -60,6 +62,7 @@ pub trait Storage { D: Send + 'static; /// Provides a dialogue indexed by `chat_id`. + #[must_use] fn get_dialogue( self: Arc, chat_id: i64, From 44956a4f858396154d6058535ee5a8d7c3baccd3 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sat, 3 Apr 2021 13:28:26 +0600 Subject: [PATCH 049/131] Add descriptions of #[must_use] in Storage --- src/dispatching/dialogue/storage/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dispatching/dialogue/storage/mod.rs b/src/dispatching/dialogue/storage/mod.rs index 4dc3165c..cfcf7853 100644 --- a/src/dispatching/dialogue/storage/mod.rs +++ b/src/dispatching/dialogue/storage/mod.rs @@ -43,7 +43,7 @@ pub trait Storage { type Error; /// Removes a dialogue indexed by `chat_id`. - #[must_use] + #[must_use = "You must .await it as futures are lazy"] fn remove_dialogue( self: Arc, chat_id: i64, @@ -52,7 +52,7 @@ pub trait Storage { D: Send + 'static; /// Updates a dialogue indexed by `chat_id` with `dialogue`. - #[must_use] + #[must_use = "You must .await it as futures are lazy"] fn update_dialogue( self: Arc, chat_id: i64, @@ -62,7 +62,7 @@ pub trait Storage { D: Send + 'static; /// Provides a dialogue indexed by `chat_id`. - #[must_use] + #[must_use = "You must .await it as futures are lazy"] fn get_dialogue( self: Arc, chat_id: i64, From bf8d2fa51bab56cad0a77bd732edcbd0a1fa83b5 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sat, 3 Apr 2021 14:21:06 +0600 Subject: [PATCH 050/131] Paraphrase #[must_use] --- src/dispatching/dialogue/storage/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dispatching/dialogue/storage/mod.rs b/src/dispatching/dialogue/storage/mod.rs index cfcf7853..3430de5e 100644 --- a/src/dispatching/dialogue/storage/mod.rs +++ b/src/dispatching/dialogue/storage/mod.rs @@ -43,7 +43,7 @@ pub trait Storage { type Error; /// Removes a dialogue indexed by `chat_id`. - #[must_use = "You must .await it as futures are lazy"] + #[must_use = "Futures are lazy and do nothing unless polled with .await"] fn remove_dialogue( self: Arc, chat_id: i64, @@ -52,7 +52,7 @@ pub trait Storage { D: Send + 'static; /// Updates a dialogue indexed by `chat_id` with `dialogue`. - #[must_use = "You must .await it as futures are lazy"] + #[must_use = "Futures are lazy and do nothing unless polled with .await"] fn update_dialogue( self: Arc, chat_id: i64, @@ -62,7 +62,7 @@ pub trait Storage { D: Send + 'static; /// Provides a dialogue indexed by `chat_id`. - #[must_use = "You must .await it as futures are lazy"] + #[must_use = "Futures are lazy and do nothing unless polled with .await"] fn get_dialogue( self: Arc, chat_id: i64, From bc9972bdf2dc4daf852e7bd6692dbd5889095dac Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Mon, 5 Apr 2021 19:48:56 +0600 Subject: [PATCH 051/131] Move the features section to lib.rs --- README.md | 25 ------------------------- src/features.txt | 26 ++++++++++++++++++++++++++ src/lib.rs | 4 ++++ 3 files changed, 30 insertions(+), 25 deletions(-) create mode 100644 src/features.txt diff --git a/README.md b/README.md index 8c794026..e0936e05 100644 --- a/README.md +++ b/README.md @@ -381,31 +381,6 @@ async fn handle_message( The second one produces very strange compiler messages due to the `#[tokio::main]` macro. However, the examples in this README use the second variant for brevity. -## Cargo features - -| Feature | Description | -|----------|----------| -| `redis-storage` | Enables the [Redis] support.| -| `sqlite-storage` | Enables the [Sqlite] support. | -| `cbor-serializer` | Enables the [CBOR] serializer for dialogues. | -| `bincode-serializer` | Enables the [Bincode] serializer for dialogues. | -| `macros` | Re-exports macros from [`teloxide-macros`]. | -| `native-tls` | Enables the [`native-tls`] TLS implementation (enabled by default). | -| `rustls` | Enables the [`rustls`] TLS implementation. | -| `auto-send` | Enables the `AutoSend` bot adaptor. | -| `cache-me` | Enables the `CacheMe` bot adaptor. | -| `frunk` | Enables [`teloxide::utils::UpState`]. | -| `full` | Enables all the features except `nightly`. | -| `nightly` | Enables nightly-only features (see the [teloxide-core features]). | - -[CBOR]: https://en.wikipedia.org/wiki/CBOR -[Bincode]: https://github.com/servo/bincode -[`teloxide::utils::UpState`]: https://docs.rs/teloxide/latest/teloxide/utils/trait.UpState.html -[`teloxide-macros`]: https://github.com/teloxide/teloxide-macros -[`native-tls`]: https://docs.rs/native-tls -[`rustls`]: https://docs.rs/rustls -[teloxide-core features]: https://docs.rs/teloxide-core/latest/teloxide_core/#cargo-features - ## FAQ **Q: Where I can ask questions?** diff --git a/src/features.txt b/src/features.txt new file mode 100644 index 00000000..c0fa8e20 --- /dev/null +++ b/src/features.txt @@ -0,0 +1,26 @@ +## Cargo features + +| Feature | Description | +|----------|----------| +| `redis-storage` | Enables the [Redis] storage support for dialogues.| +| `sqlite-storage` | Enables the [Sqlite] storage support for dialogues. | +| `cbor-serializer` | Enables the [CBOR] serializer for dialogues. | +| `bincode-serializer` | Enables the [Bincode] serializer for dialogues. | +| `macros` | Re-exports macros from [`teloxide-macros`]. | +| `native-tls` | Enables the [`native-tls`] TLS implementation (enabled by default). | +| `rustls` | Enables the [`rustls`] TLS implementation. | +| `auto-send` | Enables the `AutoSend` bot adaptor. | +| `cache-me` | Enables the `CacheMe` bot adaptor. | +| `frunk` | Enables [`teloxide::utils::UpState`]. | +| `full` | Enables all the features except `nightly`. | +| `nightly` | Enables nightly-only features (see the [teloxide-core features]). | + +[Redis]: https://redis.io/ +[Sqlite]: https://www.sqlite.org/ +[CBOR]: https://en.wikipedia.org/wiki/CBOR +[Bincode]: https://github.com/servo/bincode +[`teloxide-macros`]: https://github.com/teloxide/teloxide-macros +[`native-tls`]: https://docs.rs/native-tls +[`rustls`]: https://docs.rs/rustls +[`teloxide::utils::UpState`]: utils::UpState +[teloxide-core features]: https://docs.rs/teloxide-core/latest/teloxide_core/#cargo-features diff --git a/src/lib.rs b/src/lib.rs index 5ecb64cf..052bdd9f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +#![cfg_attr(feature = "nightly", feature(extended_key_value_attributes))] + //! A full-featured framework that empowers you to easily build [Telegram bots] //! using the [`async`/`.await`] syntax in [Rust]. It handles all the difficult //! stuff so you can focus only on your business logic. @@ -33,6 +35,8 @@ //! [`async`/`.await`]: https://rust-lang.github.io/async-book/01_getting_started/01_chapter.html //! [Rust]: https://www.rust-lang.org/ +// This hack is used to cancel formatting for a Markdown table. See . +#![cfg_attr(feature = "nightly", doc = include_str!("features.txt"))] // https://github.com/teloxide/teloxide/raw/master/logo.svg doesn't work in html_logo_url, I don't know why. #![doc( html_logo_url = "https://github.com/teloxide/teloxide/raw/master/ICON.png", From 17d14d2d5343d8a470d2ffdd4d5d68aa16050db4 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Mon, 5 Apr 2021 21:19:29 +0600 Subject: [PATCH 052/131] Yet another dirty hack to please rustfmt --- src/lib.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 052bdd9f..c8a472b0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,8 +35,12 @@ //! [`async`/`.await`]: https://rust-lang.github.io/async-book/01_getting_started/01_chapter.html //! [Rust]: https://www.rust-lang.org/ -// This hack is used to cancel formatting for a Markdown table. See . -#![cfg_attr(feature = "nightly", doc = include_str!("features.txt"))] +// This hack is used to cancel formatting for a Markdown table. See [1], [2], and [3]. +// +// [1]: https://github.com/rust-lang/rustfmt/issues/4210 +// [2]: https://github.com/rust-lang/rustfmt/issues/4787 +// [3]: https://github.com/rust-lang/rust/issues/82768#issuecomment-803935643 +#![cfg_attr(feature = "nightly", cfg_attr(feature = "nightly", doc = include_str!("features.txt")))] // https://github.com/teloxide/teloxide/raw/master/logo.svg doesn't work in html_logo_url, I don't know why. #![doc( html_logo_url = "https://github.com/teloxide/teloxide/raw/master/ICON.png", From 9e0de4691840773cad70fc4a05861d33d9d04076 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Thu, 22 Apr 2021 05:42:05 +0600 Subject: [PATCH 053/131] Always remove a dialogue in SqliteStorage::remove_dialogue --- CHANGELOG.md | 1 + src/dispatching/dialogue/storage/sqlite_storage.rs | 11 ++++------- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9491dffc..ce4aa433 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - A storage persistency bug ([issue 304](https://github.com/teloxide/teloxide/issues/304)). - Log errors from `Storage::{remove_dialogue, update_dialogue}` in `DialogueDispatcher` ([issue 302](https://github.com/teloxide/teloxide/issues/302)). - Mark all the functions of `Storage` as `#[must_use]`. + - Try to remove a dialogue in `SqliteStorage::remove_dialogue` even if it does not exist. ## [0.4.0] - 2021-03-22 diff --git a/src/dispatching/dialogue/storage/sqlite_storage.rs b/src/dispatching/dialogue/storage/sqlite_storage.rs index ceaeacf2..3251469f 100644 --- a/src/dispatching/dialogue/storage/sqlite_storage.rs +++ b/src/dispatching/dialogue/storage/sqlite_storage.rs @@ -65,13 +65,10 @@ where chat_id: i64, ) -> BoxFuture<'static, Result<(), Self::Error>> { Box::pin(async move { - if get_dialogue(&self.pool, chat_id).await?.is_some() { - sqlx::query("DELETE FROM teloxide_dialogues WHERE chat_id = ?") - .bind(chat_id) - .execute(&self.pool) - .await?; - } - + sqlx::query("DELETE FROM teloxide_dialogues WHERE chat_id = ?") + .bind(chat_id) + .execute(&self.pool) + .await?; Ok(()) }) } From ef8c8f4cb5a83f775297c4a2835f8bd4165ed768 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Thu, 22 Apr 2021 05:45:05 +0600 Subject: [PATCH 054/131] Revert CHANGELOG.md This fix affects only the current dev branch. --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce4aa433..9491dffc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - A storage persistency bug ([issue 304](https://github.com/teloxide/teloxide/issues/304)). - Log errors from `Storage::{remove_dialogue, update_dialogue}` in `DialogueDispatcher` ([issue 302](https://github.com/teloxide/teloxide/issues/302)). - Mark all the functions of `Storage` as `#[must_use]`. - - Try to remove a dialogue in `SqliteStorage::remove_dialogue` even if it does not exist. ## [0.4.0] - 2021-03-22 From 63526986552b495f361a642320d6c37412fd735e Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Tue, 27 Apr 2021 19:10:24 +0300 Subject: [PATCH 055/131] Update issue templates Change labels for `Parse error` and `Unknown telegram error` templates --- .github/ISSUE_TEMPLATE/parse-error.md | 2 +- .github/ISSUE_TEMPLATE/unknown-telegram-error.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/parse-error.md b/.github/ISSUE_TEMPLATE/parse-error.md index 0e3a05b8..641c99e4 100644 --- a/.github/ISSUE_TEMPLATE/parse-error.md +++ b/.github/ISSUE_TEMPLATE/parse-error.md @@ -2,7 +2,7 @@ name: Parse error about: Report issue with `teloxide` parsing of telegram response title: 'Parse Error: ' -labels: FIXME, bug +labels: bug, FIXME, core assignees: WaffleLapkin --- diff --git a/.github/ISSUE_TEMPLATE/unknown-telegram-error.md b/.github/ISSUE_TEMPLATE/unknown-telegram-error.md index 6b5eafe2..98ab04dd 100644 --- a/.github/ISSUE_TEMPLATE/unknown-telegram-error.md +++ b/.github/ISSUE_TEMPLATE/unknown-telegram-error.md @@ -2,7 +2,7 @@ name: Unknown telegram error about: You've found telegram error which is not known to teloxide title: 'Unknown Error: ' -labels: FIXME, bug, good first issue +labels: bug, good first issue, FIXME, core, Unknown API error assignees: '' --- From 42dbd7b423efb074f55419182868b1b4612d6814 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Tue, 27 Apr 2021 19:22:39 +0300 Subject: [PATCH 056/131] Point users to discussions --- .github/ISSUE_TEMPLATE/config.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/config.yml diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..96317650 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,4 @@ +contact_links: + - name: Teloxide Discussions + url: https://github.com/teloxide/teloxide/discussions/categories/q-a + about: Please ask and answer questions here. From 08bf40e5559b420f27ef380754f8a7301d310f2a Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sat, 8 May 2021 17:21:24 +0600 Subject: [PATCH 057/131] Fail Storage::remove_dialogue if a dialogue doesn't exist --- CHANGELOG.md | 5 +- .../dialogue/dialogue_dispatcher.rs | 5 +- src/dispatching/dialogue/mod.rs | 2 +- .../dialogue/storage/in_mem_storage.rs | 30 +++++++----- src/dispatching/dialogue/storage/mod.rs | 16 ++++-- .../dialogue/storage/redis_storage.rs | 49 +++++++++---------- .../dialogue/storage/serializer.rs | 2 +- .../dialogue/storage/sqlite_storage.rs | 37 ++++++++------ .../dialogue/storage/trace_storage.rs | 6 +-- src/dispatching/repls/dialogues_repl.rs | 11 +++-- tests/redis.rs | 14 ++++-- tests/sqlite.rs | 9 +++- 12 files changed, 114 insertions(+), 72 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9491dffc..11d24a0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,11 +8,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - - `Storage::get_dialogue` + - `Storage::get_dialogue` to obtain a dialogue indexed by a chat ID. + - `RedisStorageError::RowNotFound` to be returned from `RedisStorage::remove_dialogue`. + - `InMemStorageError` with a single variant `RowNotFound` to be returned from `InMemStorage::remove_dialogue`. ### Changed - Do not return a dialogue from `Storage::{remove_dialogue, update_dialogue}`. + - Return an error from `Storage::remove_dialogue` if a dialogue does not exist. - Require `D: Clone` in `dialogues_repl(_with_listener)` and `InMemStorage`. - Automatically delete a webhook if it was set up in `update_listeners::polling_default` (thereby making it `async`, [issue 319](https://github.com/teloxide/teloxide/issues/319)). diff --git a/src/dispatching/dialogue/dialogue_dispatcher.rs b/src/dispatching/dialogue/dialogue_dispatcher.rs index b98c029c..b04b39d5 100644 --- a/src/dispatching/dialogue/dialogue_dispatcher.rs +++ b/src/dispatching/dialogue/dialogue_dispatcher.rs @@ -4,11 +4,12 @@ use crate::dispatching::{ }, DispatcherHandler, UpdateWithCx, }; -use std::{convert::Infallible, fmt::Debug, marker::PhantomData}; +use std::{fmt::Debug, marker::PhantomData}; use futures::{future::BoxFuture, FutureExt, StreamExt}; use tokio::sync::mpsc; +use crate::dispatching::dialogue::InMemStorageError; use lockfree::map::Map; use std::sync::{Arc, Mutex}; use teloxide_core::requests::Requester; @@ -45,7 +46,7 @@ pub struct DialogueDispatcher { impl DialogueDispatcher, H, Upd> where - H: DialogueDispatcherHandler + Send + Sync + 'static, + H: DialogueDispatcherHandler + Send + Sync + 'static, Upd: GetChatId + Send + 'static, D: Default + Send + 'static, { diff --git a/src/dispatching/dialogue/mod.rs b/src/dispatching/dialogue/mod.rs index ee182dce..f4bb933e 100644 --- a/src/dispatching/dialogue/mod.rs +++ b/src/dispatching/dialogue/mod.rs @@ -166,7 +166,7 @@ pub use teloxide_macros::Transition; #[cfg(feature = "redis-storage")] #[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "redis-storage")))] -pub use storage::{RedisStorage, RedisStorageError}; +pub use storage::{InMemStorageError, RedisStorage, RedisStorageError}; #[cfg(feature = "sqlite-storage")] pub use storage::{SqliteStorage, SqliteStorageError}; diff --git a/src/dispatching/dialogue/storage/in_mem_storage.rs b/src/dispatching/dialogue/storage/in_mem_storage.rs index e5ca38b9..c01498ff 100644 --- a/src/dispatching/dialogue/storage/in_mem_storage.rs +++ b/src/dispatching/dialogue/storage/in_mem_storage.rs @@ -1,18 +1,23 @@ use super::Storage; use futures::future::BoxFuture; use std::{collections::HashMap, sync::Arc}; +use thiserror::Error; use tokio::sync::Mutex; -/// A memory storage based on a hash map. Stores all the dialogues directly in -/// RAM. +/// An error returned from [`InMemStorage`]. +#[derive(Debug, Error)] +pub enum InMemStorageError { + /// Returned from [`InMemStorage::remove_dialogue`]. + #[error("row not found")] + RowNotFound, +} + +/// A dialogue storage based on [`std::collections::HashMap`]. /// /// ## Note -/// All the dialogues will be lost after you restart your bot. If you need to -/// store them somewhere on a drive, you should use [`SqliteStorage`], -/// [`RedisStorage`] or implement your own. -/// -/// [`RedisStorage`]: crate::dispatching::dialogue::RedisStorage -/// [`SqliteStorage`]: crate::dispatching::dialogue::SqliteStorage +/// All your dialogues will be lost after you restart your bot. If you need to +/// store them somewhere on a drive, you should use e.g. +/// [`super::SqliteStorage`] or implement your own. #[derive(Debug)] pub struct InMemStorage { map: Mutex>, @@ -30,15 +35,18 @@ where D: Clone, D: Send + 'static, { - type Error = std::convert::Infallible; + type Error = InMemStorageError; fn remove_dialogue(self: Arc, chat_id: i64) -> BoxFuture<'static, Result<(), Self::Error>> where D: Send + 'static, { Box::pin(async move { - self.map.lock().await.remove(&chat_id); - Ok(()) + self.map + .lock() + .await + .remove(&chat_id) + .map_or_else(|| Err(InMemStorageError::RowNotFound), |_| Ok(())) }) } diff --git a/src/dispatching/dialogue/storage/mod.rs b/src/dispatching/dialogue/storage/mod.rs index 3430de5e..a3dbdf9d 100644 --- a/src/dispatching/dialogue/storage/mod.rs +++ b/src/dispatching/dialogue/storage/mod.rs @@ -11,7 +11,10 @@ mod sqlite_storage; use futures::future::BoxFuture; -pub use self::{in_mem_storage::InMemStorage, trace_storage::TraceStorage}; +pub use self::{ + in_mem_storage::{InMemStorage, InMemStorageError}, + trace_storage::TraceStorage, +}; #[cfg(feature = "redis-storage")] #[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "redis-storage")))] @@ -32,9 +35,9 @@ pub use sqlite_storage::{SqliteStorage, SqliteStorageError}; /// /// Currently we support the following storages out of the box: /// -/// - [`InMemStorage`] - a storage based on a simple hash map -/// - [`RedisStorage`] - a Redis-based storage -/// - [`SqliteStorage`] - an SQLite-based persistent storage +/// - [`InMemStorage`] -- A storage based on [`std::collections::HashMap`]. +/// - [`RedisStorage`] -- A Redis-based storage. +/// - [`SqliteStorage`] -- An SQLite-based persistent storage. /// /// [`InMemStorage`]: crate::dispatching::dialogue::InMemStorage /// [`RedisStorage`]: crate::dispatching::dialogue::RedisStorage @@ -43,6 +46,9 @@ pub trait Storage { type Error; /// Removes a dialogue indexed by `chat_id`. + /// + /// If the dialogue indexed by `chat_id` does not exist, this function + /// results in an error. #[must_use = "Futures are lazy and do nothing unless polled with .await"] fn remove_dialogue( self: Arc, @@ -61,7 +67,7 @@ pub trait Storage { where D: Send + 'static; - /// Provides a dialogue indexed by `chat_id`. + /// Extracts a dialogue indexed by `chat_id`. #[must_use = "Futures are lazy and do nothing unless polled with .await"] fn get_dialogue( self: Arc, diff --git a/src/dispatching/dialogue/storage/redis_storage.rs b/src/dispatching/dialogue/storage/redis_storage.rs index 03313624..00603fac 100644 --- a/src/dispatching/dialogue/storage/redis_storage.rs +++ b/src/dispatching/dialogue/storage/redis_storage.rs @@ -1,6 +1,6 @@ use super::{serializer::Serializer, Storage}; use futures::future::BoxFuture; -use redis::{AsyncCommands, FromRedisValue, IntoConnectionInfo}; +use redis::{AsyncCommands, IntoConnectionInfo}; use serde::{de::DeserializeOwned, Serialize}; use std::{ convert::Infallible, @@ -12,8 +12,6 @@ use thiserror::Error; use tokio::sync::Mutex; /// An error returned from [`RedisStorage`]. -/// -/// [`RedisStorage`]: struct.RedisStorage.html #[derive(Debug, Error)] pub enum RedisStorageError where @@ -21,11 +19,16 @@ where { #[error("parsing/serializing error: {0}")] SerdeError(SE), + #[error("error from Redis: {0}")] RedisError(#[from] redis::RedisError), + + /// Returned from [`RedisStorage::remove_dialogue`]. + #[error("row not found")] + RowNotFound, } -/// A memory storage based on [Redis](https://redis.io/). +/// A dialogue storage based on [Redis](https://redis.io/). pub struct RedisStorage { conn: Mutex, serializer: S, @@ -51,36 +54,30 @@ where { type Error = RedisStorageError<>::Error>; - // `.del().ignore()` is much more readable than `.del()\n.ignore()` - #[rustfmt::skip] fn remove_dialogue( self: Arc, chat_id: i64, ) -> BoxFuture<'static, Result<(), Self::Error>> { Box::pin(async move { - let res = redis::pipe() + let deleted_rows_count = redis::pipe() .atomic() - .get(chat_id) - .del(chat_id).ignore() - .query_async::<_, redis::Value>( - self.conn.lock().await.deref_mut(), - ) + .del(chat_id) + .query_async::<_, redis::Value>(self.conn.lock().await.deref_mut()) .await?; - // We're expecting `.pipe()` to return us an exactly one result in - // bulk, so all other branches should be unreachable - match res { - redis::Value::Bulk(bulk) if bulk.len() == 1 => { - Option::>::from_redis_value(&bulk[0])? - .map(|v| { - self.serializer - .deserialize(&v) - .map_err(RedisStorageError::SerdeError) - }) - .transpose()?; - Ok(()) - } - _ => unreachable!(), + + let deleted_rows_count = match deleted_rows_count { + redis::Value::Bulk(values) => match values[0] { + redis::Value::Int(x) => x, + _ => unreachable!("Must return redis::Value::Int"), + }, + _ => unreachable!("Must return redis::Value::Bulk"), + }; + + if deleted_rows_count == 0 { + return Err(RedisStorageError::RowNotFound); } + + Ok(()) }) } diff --git a/src/dispatching/dialogue/storage/serializer.rs b/src/dispatching/dialogue/storage/serializer.rs index 2e0aa945..2fb30cbc 100644 --- a/src/dispatching/dialogue/storage/serializer.rs +++ b/src/dispatching/dialogue/storage/serializer.rs @@ -1,4 +1,4 @@ -//! Various serializers for memory storages. +//! Various serializers for dialogue storages. use serde::{de::DeserializeOwned, ser::Serialize}; diff --git a/src/dispatching/dialogue/storage/sqlite_storage.rs b/src/dispatching/dialogue/storage/sqlite_storage.rs index 3251469f..90b97d4b 100644 --- a/src/dispatching/dialogue/storage/sqlite_storage.rs +++ b/src/dispatching/dialogue/storage/sqlite_storage.rs @@ -10,15 +10,13 @@ use std::{ }; use thiserror::Error; -/// A persistent storage based on [SQLite](https://www.sqlite.org/). +/// A persistent dialogue storage based on [SQLite](https://www.sqlite.org/). pub struct SqliteStorage { pool: SqlitePool, serializer: S, } /// An error returned from [`SqliteStorage`]. -/// -/// [`SqliteStorage`]: struct.SqliteStorage.html #[derive(Debug, Error)] pub enum SqliteStorageError where @@ -26,6 +24,7 @@ where { #[error("dialogue serialization error: {0}")] SerdeError(SE), + #[error("sqlite error: {0}")] SqliteError(#[from] sqlx::Error), } @@ -60,15 +59,23 @@ where { type Error = SqliteStorageError<>::Error>; + /// Returns [`sqlx::Error::RowNotFound`] if a dialogue does not exist. fn remove_dialogue( self: Arc, chat_id: i64, ) -> BoxFuture<'static, Result<(), Self::Error>> { Box::pin(async move { - sqlx::query("DELETE FROM teloxide_dialogues WHERE chat_id = ?") - .bind(chat_id) - .execute(&self.pool) - .await?; + let deleted_rows_count = + sqlx::query("DELETE FROM teloxide_dialogues WHERE chat_id = ?; SELECT changes()") + .bind(chat_id) + .execute(&self.pool) + .await? + .rows_affected(); + + if deleted_rows_count == 0 { + return Err(SqliteStorageError::SqliteError(sqlx::Error::RowNotFound)); + } + Ok(()) }) } @@ -112,20 +119,22 @@ where } } -#[derive(sqlx::FromRow)] -struct DialogueDbRow { - dialogue: Vec, -} - async fn get_dialogue( pool: &SqlitePool, chat_id: i64, ) -> Result>>, sqlx::Error> { - Ok(sqlx::query_as::<_, DialogueDbRow>( + #[derive(sqlx::FromRow)] + struct DialogueDbRow { + dialogue: Vec, + } + + let bytes = sqlx::query_as::<_, DialogueDbRow>( "SELECT dialogue FROM teloxide_dialogues WHERE chat_id = ?", ) .bind(chat_id) .fetch_optional(pool) .await? - .map(|r| Box::new(r.dialogue))) + .map(|r| Box::new(r.dialogue)); + + Ok(bytes) } diff --git a/src/dispatching/dialogue/storage/trace_storage.rs b/src/dispatching/dialogue/storage/trace_storage.rs index 80b37887..f4e22d8e 100644 --- a/src/dispatching/dialogue/storage/trace_storage.rs +++ b/src/dispatching/dialogue/storage/trace_storage.rs @@ -8,10 +8,10 @@ use futures::future::BoxFuture; use crate::dispatching::dialogue::Storage; -/// Storage wrapper for logging purposes. +/// A dialogue storage wrapper which logs all actions performed on an underlying +/// storage. /// -/// Reports about any dialogue action using the `trace` level in the `log` -/// crate. +/// Reports about any dialogue action via [`log::Level::Trace`]. pub struct TraceStorage { inner: Arc, } diff --git a/src/dispatching/repls/dialogues_repl.rs b/src/dispatching/repls/dialogues_repl.rs index fabe237d..0fa00188 100644 --- a/src/dispatching/repls/dialogues_repl.rs +++ b/src/dispatching/repls/dialogues_repl.rs @@ -1,13 +1,13 @@ use crate::{ dispatching::{ - dialogue::{DialogueDispatcher, DialogueStage, DialogueWithCx}, + dialogue::{DialogueDispatcher, DialogueStage, DialogueWithCx, InMemStorageError}, update_listeners, update_listeners::UpdateListener, Dispatcher, UpdateWithCx, }, error_handlers::LoggingErrorHandler, }; -use std::{convert::Infallible, fmt::Debug, future::Future, sync::Arc}; +use std::{fmt::Debug, future::Future, sync::Arc}; use teloxide_core::{requests::Requester, types::Message}; /// A [REPL] for dialogues. @@ -71,7 +71,12 @@ pub async fn dialogues_repl_with_listener<'a, R, H, D, Fut, L, ListenerE>( Dispatcher::new(requester) .messages_handler(DialogueDispatcher::new( - move |DialogueWithCx { cx, dialogue }: DialogueWithCx| { + move |DialogueWithCx { cx, dialogue }: DialogueWithCx< + R, + Message, + D, + InMemStorageError, + >| { let handler = Arc::clone(&handler); async move { diff --git a/tests/redis.rs b/tests/redis.rs index 9777597e..ea46c6e8 100644 --- a/tests/redis.rs +++ b/tests/redis.rs @@ -2,12 +2,12 @@ use std::{ fmt::{Debug, Display}, sync::Arc, }; -use teloxide::dispatching::dialogue::{RedisStorage, Serializer, Storage}; +use teloxide::dispatching::dialogue::{RedisStorage, RedisStorageError, Serializer, Storage}; #[tokio::test] async fn test_redis_json() { let storage = RedisStorage::open( - "redis://127.0.0.1:7777", + "redis://127.0.0.1:9000", teloxide::dispatching::dialogue::serializer::Json, ) .await @@ -18,7 +18,7 @@ async fn test_redis_json() { #[tokio::test] async fn test_redis_bincode() { let storage = RedisStorage::open( - "redis://127.0.0.1:7778", + "redis://127.0.0.1:9001", teloxide::dispatching::dialogue::serializer::Bincode, ) .await @@ -29,7 +29,7 @@ async fn test_redis_bincode() { #[tokio::test] async fn test_redis_cbor() { let storage = RedisStorage::open( - "redis://127.0.0.1:7779", + "redis://127.0.0.1:9002", teloxide::dispatching::dialogue::serializer::Cbor, ) .await @@ -70,4 +70,10 @@ where Arc::clone(&storage).remove_dialogue(256).await.unwrap(); test_dialogues!(storage, None, None, None); + + // Check that a try to remove a non-existing dialogue results in an error. + assert!(matches!( + Arc::clone(&storage).remove_dialogue(1).await.unwrap_err(), + RedisStorageError::RowNotFound + )); } diff --git a/tests/sqlite.rs b/tests/sqlite.rs index de37de6c..f8b76fb4 100644 --- a/tests/sqlite.rs +++ b/tests/sqlite.rs @@ -2,7 +2,7 @@ use std::{ fmt::{Debug, Display}, sync::Arc, }; -use teloxide::dispatching::dialogue::{Serializer, SqliteStorage, Storage}; +use teloxide::dispatching::dialogue::{Serializer, SqliteStorage, SqliteStorageError, Storage}; #[tokio::test(flavor = "multi_thread")] async fn test_sqlite_json() { @@ -66,4 +66,11 @@ where Arc::clone(&storage).remove_dialogue(256).await.unwrap(); test_dialogues!(storage, None, None, None); + + // Check that a try to remove a non-existing dialogue results in an error. + let err = Arc::clone(&storage).remove_dialogue(1).await.unwrap_err(); + match err { + SqliteStorageError::SqliteError(err) => assert!(matches!(err, sqlx::Error::RowNotFound)), + _ => panic!("Must be sqlx::Error::RowNotFound"), + } } From 58c8015e783c97f3ab92a2ba9dd18554a5b1f363 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sat, 8 May 2021 17:25:53 +0600 Subject: [PATCH 058/131] Re-export InMemStorageError unconditionally --- src/dispatching/dialogue/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dispatching/dialogue/mod.rs b/src/dispatching/dialogue/mod.rs index f4bb933e..fb7a8371 100644 --- a/src/dispatching/dialogue/mod.rs +++ b/src/dispatching/dialogue/mod.rs @@ -166,9 +166,9 @@ pub use teloxide_macros::Transition; #[cfg(feature = "redis-storage")] #[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "redis-storage")))] -pub use storage::{InMemStorageError, RedisStorage, RedisStorageError}; +pub use storage::{RedisStorage, RedisStorageError}; #[cfg(feature = "sqlite-storage")] pub use storage::{SqliteStorage, SqliteStorageError}; -pub use storage::{serializer, InMemStorage, Serializer, Storage, TraceStorage}; +pub use storage::{serializer, InMemStorage, InMemStorageError, Serializer, Storage, TraceStorage}; From 721feb0082c46c8a8cb7bd5025ddcbf84cded63e Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sat, 8 May 2021 17:38:03 +0600 Subject: [PATCH 059/131] Fix the tests --- src/dispatching/dialogue/dialogue_dispatcher.rs | 2 +- src/dispatching/dialogue/mod.rs | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/dispatching/dialogue/dialogue_dispatcher.rs b/src/dispatching/dialogue/dialogue_dispatcher.rs index b04b39d5..31fbdc90 100644 --- a/src/dispatching/dialogue/dialogue_dispatcher.rs +++ b/src/dispatching/dialogue/dialogue_dispatcher.rs @@ -215,7 +215,7 @@ mod tests { } let dispatcher = DialogueDispatcher::new( - |cx: DialogueWithCx| async move { + |cx: DialogueWithCx| async move { tokio::time::sleep(Duration::from_millis(300)).await; match cx.cx.update { diff --git a/src/dispatching/dialogue/mod.rs b/src/dispatching/dialogue/mod.rs index fb7a8371..097c7180 100644 --- a/src/dispatching/dialogue/mod.rs +++ b/src/dispatching/dialogue/mod.rs @@ -33,7 +33,11 @@ //! # #[cfg(feature = "macros")] { //! use std::convert::Infallible; //! -//! use teloxide::{dispatching::dialogue::Transition, prelude::*, teloxide, RequestError}; +//! use teloxide::{ +//! dispatching::dialogue::{InMemStorageError, Transition}, +//! prelude::*, +//! teloxide, RequestError, +//! }; //! //! #[derive(Clone)] //! struct _1State; @@ -72,7 +76,7 @@ //! } //! } //! -//! type In = DialogueWithCx, Message, D, Infallible>; +//! type In = DialogueWithCx, Message, D, InMemStorageError>; //! //! #[tokio::main] //! async fn main() { From 03837c66f2574e98454dc70a4dc5a7ec92a26c7a Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sat, 8 May 2021 17:44:22 +0600 Subject: [PATCH 060/131] Revert the previous ports in tests/redis.rs --- tests/redis.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/redis.rs b/tests/redis.rs index ea46c6e8..ab2466f1 100644 --- a/tests/redis.rs +++ b/tests/redis.rs @@ -7,7 +7,7 @@ use teloxide::dispatching::dialogue::{RedisStorage, RedisStorageError, Serialize #[tokio::test] async fn test_redis_json() { let storage = RedisStorage::open( - "redis://127.0.0.1:9000", + "redis://127.0.0.1:7777", teloxide::dispatching::dialogue::serializer::Json, ) .await @@ -18,7 +18,7 @@ async fn test_redis_json() { #[tokio::test] async fn test_redis_bincode() { let storage = RedisStorage::open( - "redis://127.0.0.1:9001", + "redis://127.0.0.1:7778", teloxide::dispatching::dialogue::serializer::Bincode, ) .await @@ -29,7 +29,7 @@ async fn test_redis_bincode() { #[tokio::test] async fn test_redis_cbor() { let storage = RedisStorage::open( - "redis://127.0.0.1:9002", + "redis://127.0.0.1:7779", teloxide::dispatching::dialogue::serializer::Cbor, ) .await From 5585143ac28f9f3bcf5f262df33c4d3cab1fc2dc Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sat, 8 May 2021 18:21:11 +0600 Subject: [PATCH 061/131] Don't capitalise letters after dashes --- src/dispatching/dialogue/storage/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dispatching/dialogue/storage/mod.rs b/src/dispatching/dialogue/storage/mod.rs index a3dbdf9d..22965ff3 100644 --- a/src/dispatching/dialogue/storage/mod.rs +++ b/src/dispatching/dialogue/storage/mod.rs @@ -35,9 +35,9 @@ pub use sqlite_storage::{SqliteStorage, SqliteStorageError}; /// /// Currently we support the following storages out of the box: /// -/// - [`InMemStorage`] -- A storage based on [`std::collections::HashMap`]. -/// - [`RedisStorage`] -- A Redis-based storage. -/// - [`SqliteStorage`] -- An SQLite-based persistent storage. +/// - [`InMemStorage`] -- a storage based on [`std::collections::HashMap`]. +/// - [`RedisStorage`] -- a Redis-based storage. +/// - [`SqliteStorage`] -- an SQLite-based persistent storage. /// /// [`InMemStorage`]: crate::dispatching::dialogue::InMemStorage /// [`RedisStorage`]: crate::dispatching::dialogue::RedisStorage From 94e61d3403a79e29a19d7949be4c3b14b8fd65b7 Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Sat, 8 May 2021 05:39:18 -0700 Subject: [PATCH 062/131] Refactor src/dispatching/dialogue/storage/redis_storage.rs Co-authored-by: Waffle Lapkin --- .../dialogue/storage/redis_storage.rs | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/dispatching/dialogue/storage/redis_storage.rs b/src/dispatching/dialogue/storage/redis_storage.rs index 00603fac..0b1219a0 100644 --- a/src/dispatching/dialogue/storage/redis_storage.rs +++ b/src/dispatching/dialogue/storage/redis_storage.rs @@ -65,19 +65,16 @@ where .query_async::<_, redis::Value>(self.conn.lock().await.deref_mut()) .await?; - let deleted_rows_count = match deleted_rows_count { - redis::Value::Bulk(values) => match values[0] { - redis::Value::Int(x) => x, - _ => unreachable!("Must return redis::Value::Int"), - }, - _ => unreachable!("Must return redis::Value::Bulk"), - }; - - if deleted_rows_count == 0 { - return Err(RedisStorageError::RowNotFound); - } - - Ok(()) + if let redis::Value::Bulk(values) = deleted_rows_count { + if let redis::Value::Int(deleted_rows_count) = values[0] { + match deleted_rows_count { + 0 => return Err(RedisStorageError::RowNotFound), + _ => return Ok(()) + } + } + } + + unreachable!("Must return redis::Value::Bulk(redis::Value::Int(_))"); }) } From cb02b83520c907b80d23166a245899290332cf02 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sat, 8 May 2021 18:41:34 +0600 Subject: [PATCH 063/131] Apply review suggestions --- src/dispatching/dialogue/storage/sqlite_storage.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/dispatching/dialogue/storage/sqlite_storage.rs b/src/dispatching/dialogue/storage/sqlite_storage.rs index 90b97d4b..10a1b403 100644 --- a/src/dispatching/dialogue/storage/sqlite_storage.rs +++ b/src/dispatching/dialogue/storage/sqlite_storage.rs @@ -66,7 +66,7 @@ where ) -> BoxFuture<'static, Result<(), Self::Error>> { Box::pin(async move { let deleted_rows_count = - sqlx::query("DELETE FROM teloxide_dialogues WHERE chat_id = ?; SELECT changes()") + sqlx::query("DELETE FROM teloxide_dialogues WHERE chat_id = ?") .bind(chat_id) .execute(&self.pool) .await? @@ -119,10 +119,7 @@ where } } -async fn get_dialogue( - pool: &SqlitePool, - chat_id: i64, -) -> Result>>, sqlx::Error> { +async fn get_dialogue(pool: &SqlitePool, chat_id: i64) -> Result>, sqlx::Error> { #[derive(sqlx::FromRow)] struct DialogueDbRow { dialogue: Vec, @@ -134,7 +131,7 @@ async fn get_dialogue( .bind(chat_id) .fetch_optional(pool) .await? - .map(|r| Box::new(r.dialogue)); + .map(|r| r.dialogue); Ok(bytes) } From 80e1afaeed60af394b8f092fa2998b05fb4d92b9 Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Sat, 8 May 2021 05:43:27 -0700 Subject: [PATCH 064/131] Update src/dispatching/dialogue/storage/in_mem_storage.rs Co-authored-by: Waffle Lapkin --- src/dispatching/dialogue/storage/in_mem_storage.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dispatching/dialogue/storage/in_mem_storage.rs b/src/dispatching/dialogue/storage/in_mem_storage.rs index c01498ff..c7989c28 100644 --- a/src/dispatching/dialogue/storage/in_mem_storage.rs +++ b/src/dispatching/dialogue/storage/in_mem_storage.rs @@ -46,7 +46,7 @@ where .lock() .await .remove(&chat_id) - .map_or_else(|| Err(InMemStorageError::RowNotFound), |_| Ok(())) + .map_or(Err(InMemStorageError::RowNotFound), |_| Ok(())) }) } From de52ea25f961a044b11d0cb1bb5dca50b9b3cb85 Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Sat, 8 May 2021 05:43:34 -0700 Subject: [PATCH 065/131] Update src/dispatching/dialogue/storage/mod.rs Co-authored-by: Waffle Lapkin --- src/dispatching/dialogue/storage/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dispatching/dialogue/storage/mod.rs b/src/dispatching/dialogue/storage/mod.rs index 22965ff3..dbf4c25a 100644 --- a/src/dispatching/dialogue/storage/mod.rs +++ b/src/dispatching/dialogue/storage/mod.rs @@ -67,7 +67,7 @@ pub trait Storage { where D: Send + 'static; - /// Extracts a dialogue indexed by `chat_id`. + /// Returns the dialogue indexed by `chat_id`. #[must_use = "Futures are lazy and do nothing unless polled with .await"] fn get_dialogue( self: Arc, From 27eda5759cb9df864c3c6c58c9fda1370ca3f797 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sat, 8 May 2021 19:00:38 +0600 Subject: [PATCH 066/131] RowNotFound -> DialogueNotFound --- CHANGELOG.md | 3 ++- .../dialogue/storage/in_mem_storage.rs | 4 ++-- .../dialogue/storage/redis_storage.rs | 20 +++++++++---------- .../dialogue/storage/sqlite_storage.rs | 6 +++++- tests/redis.rs | 2 +- tests/sqlite.rs | 9 ++++----- 6 files changed, 24 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11d24a0d..24fd3825 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Storage::get_dialogue` to obtain a dialogue indexed by a chat ID. - `RedisStorageError::RowNotFound` to be returned from `RedisStorage::remove_dialogue`. - - `InMemStorageError` with a single variant `RowNotFound` to be returned from `InMemStorage::remove_dialogue`. + - `InMemStorageError` with a single variant `DialogueNotFound` to be returned from `InMemStorage::remove_dialogue`. + - `RedisStorageError::DialogueNotFound` and `SqliteStorageError::DialogueNotFound` to be returned from `Storage::remove_dialogue`. ### Changed diff --git a/src/dispatching/dialogue/storage/in_mem_storage.rs b/src/dispatching/dialogue/storage/in_mem_storage.rs index c01498ff..2407792f 100644 --- a/src/dispatching/dialogue/storage/in_mem_storage.rs +++ b/src/dispatching/dialogue/storage/in_mem_storage.rs @@ -9,7 +9,7 @@ use tokio::sync::Mutex; pub enum InMemStorageError { /// Returned from [`InMemStorage::remove_dialogue`]. #[error("row not found")] - RowNotFound, + DialogueNotFound, } /// A dialogue storage based on [`std::collections::HashMap`]. @@ -46,7 +46,7 @@ where .lock() .await .remove(&chat_id) - .map_or_else(|| Err(InMemStorageError::RowNotFound), |_| Ok(())) + .map_or_else(|| Err(InMemStorageError::DialogueNotFound), |_| Ok(())) }) } diff --git a/src/dispatching/dialogue/storage/redis_storage.rs b/src/dispatching/dialogue/storage/redis_storage.rs index 0b1219a0..5e2eb843 100644 --- a/src/dispatching/dialogue/storage/redis_storage.rs +++ b/src/dispatching/dialogue/storage/redis_storage.rs @@ -25,7 +25,7 @@ where /// Returned from [`RedisStorage::remove_dialogue`]. #[error("row not found")] - RowNotFound, + DialogueNotFound, } /// A dialogue storage based on [Redis](https://redis.io/). @@ -66,15 +66,15 @@ where .await?; if let redis::Value::Bulk(values) = deleted_rows_count { - if let redis::Value::Int(deleted_rows_count) = values[0] { - match deleted_rows_count { - 0 => return Err(RedisStorageError::RowNotFound), - _ => return Ok(()) - } - } - } - - unreachable!("Must return redis::Value::Bulk(redis::Value::Int(_))"); + if let redis::Value::Int(deleted_rows_count) = values[0] { + match deleted_rows_count { + 0 => return Err(RedisStorageError::DialogueNotFound), + _ => return Ok(()), + } + } + } + + unreachable!("Must return redis::Value::Bulk(redis::Value::Int(_))"); }) } diff --git a/src/dispatching/dialogue/storage/sqlite_storage.rs b/src/dispatching/dialogue/storage/sqlite_storage.rs index 10a1b403..a562b5e5 100644 --- a/src/dispatching/dialogue/storage/sqlite_storage.rs +++ b/src/dispatching/dialogue/storage/sqlite_storage.rs @@ -27,6 +27,10 @@ where #[error("sqlite error: {0}")] SqliteError(#[from] sqlx::Error), + + /// Returned from [`SqliteStorage::remove_dialogue`]. + #[error("row not found")] + DialogueNotFound, } impl SqliteStorage { @@ -73,7 +77,7 @@ where .rows_affected(); if deleted_rows_count == 0 { - return Err(SqliteStorageError::SqliteError(sqlx::Error::RowNotFound)); + return Err(SqliteStorageError::DialogueNotFound); } Ok(()) diff --git a/tests/redis.rs b/tests/redis.rs index ab2466f1..2e011596 100644 --- a/tests/redis.rs +++ b/tests/redis.rs @@ -74,6 +74,6 @@ where // Check that a try to remove a non-existing dialogue results in an error. assert!(matches!( Arc::clone(&storage).remove_dialogue(1).await.unwrap_err(), - RedisStorageError::RowNotFound + RedisStorageError::DialogueNotFound )); } diff --git a/tests/sqlite.rs b/tests/sqlite.rs index f8b76fb4..a66c745f 100644 --- a/tests/sqlite.rs +++ b/tests/sqlite.rs @@ -68,9 +68,8 @@ where test_dialogues!(storage, None, None, None); // Check that a try to remove a non-existing dialogue results in an error. - let err = Arc::clone(&storage).remove_dialogue(1).await.unwrap_err(); - match err { - SqliteStorageError::SqliteError(err) => assert!(matches!(err, sqlx::Error::RowNotFound)), - _ => panic!("Must be sqlx::Error::RowNotFound"), - } + assert!(matches!( + Arc::clone(&storage).remove_dialogue(1).await.unwrap_err(), + SqliteStorageError::DialogueNotFound + )); } From f911ab99748a29c11f38d4ca625646b0363c6c09 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Sat, 8 May 2021 16:31:19 +0300 Subject: [PATCH 067/131] Remove duplicate from README --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24fd3825..63def763 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - `Storage::get_dialogue` to obtain a dialogue indexed by a chat ID. - - `RedisStorageError::RowNotFound` to be returned from `RedisStorage::remove_dialogue`. - `InMemStorageError` with a single variant `DialogueNotFound` to be returned from `InMemStorage::remove_dialogue`. - `RedisStorageError::DialogueNotFound` and `SqliteStorageError::DialogueNotFound` to be returned from `Storage::remove_dialogue`. From cf3639f68b4c59eac53d30d19951f3838c3cc600 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Mon, 10 May 2021 08:55:43 +0600 Subject: [PATCH 068/131] Document the "off" value as a command description --- src/utils/command.rs | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/utils/command.rs b/src/utils/command.rs index 22a795e4..8c3b445d 100644 --- a/src/utils/command.rs +++ b/src/utils/command.rs @@ -76,7 +76,7 @@ pub use teloxide_macros::BotCommand; /// # } /// ``` /// -/// ## Enum attributes +/// # Enum attributes /// 1. `#[command(rename = "rule")]` /// Rename all commands by `rule`. Allowed rules are `lowercase`. If you will /// not use this attribute, commands will be parsed by their original names. @@ -93,7 +93,7 @@ pub use teloxide_macros::BotCommand; /// after the first space into the first argument, which must implement /// [`FromStr`]. /// -/// ### Example +/// ## Example /// ``` /// # #[cfg(feature = "macros")] { /// use teloxide::utils::command::BotCommand; @@ -113,7 +113,7 @@ pub use teloxide_macros::BotCommand; /// space character) and parses each part into the corresponding arguments, /// which must implement [`FromStr`]. /// -/// ### Example +/// ## Example /// ``` /// # #[cfg(feature = "macros")] { /// use teloxide::utils::command::BotCommand; @@ -133,7 +133,7 @@ pub use teloxide_macros::BotCommand; /// Specify separator used by the `split` parser. It will be ignored when /// accompanied by another type of parsers. /// -/// ### Example +/// ## Example /// ``` /// # #[cfg(feature = "macros")] { /// use teloxide::utils::command::BotCommand; @@ -149,20 +149,24 @@ pub use teloxide_macros::BotCommand; /// # } /// ``` /// -/// ## Variant attributes +/// # Variant attributes /// All variant attributes override the corresponding `enum` attributes. /// /// 1. `#[command(rename = "rule")]` /// Rename one command by a rule. Allowed rules are `lowercase`, `%some_name%`, /// where `%some_name%` is any string, a new name. /// -/// 2. `#[command(parse_with = "parser")]` +/// 2. `#[command(description = "description")]` +/// Give your command a description. Write `"off"` for `"description"` to hide a +/// command. +/// +/// 3. `#[command(parse_with = "parser")]` /// One more option is available for variants. /// - `custom_parser` - your own parser of the signature `fn(String) -> /// Result`, where `Tuple` corresponds to the variant's /// arguments. /// -/// ### Example +/// ## Example /// ``` /// # #[cfg(feature = "macros")] { /// use teloxide::utils::command::{BotCommand, ParseError}; @@ -191,11 +195,11 @@ pub use teloxide_macros::BotCommand; /// # } /// ``` /// -/// 3. `#[command(prefix = "prefix")]` -/// 4. `#[command(description = "description")]` +/// 4. `#[command(prefix = "prefix")]` /// 5. `#[command(separator = "sep")]` /// -/// Analogous to the descriptions above. +/// These boys just override the corresponding `enum` attributes for a specific +/// variant. /// /// [`FromStr`]: https://doc.rust-lang.org/std/str/trait.FromStr.html /// [`BotCommand`]: crate::utils::command::BotCommand From 7f578a8918c6f5e545f1aa43ae35c677ca582b89 Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Sun, 9 May 2021 23:06:35 -0700 Subject: [PATCH 069/131] Paraphrase src/utils/command.rs Co-authored-by: Waffle Lapkin --- src/utils/command.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/command.rs b/src/utils/command.rs index 8c3b445d..ae82f713 100644 --- a/src/utils/command.rs +++ b/src/utils/command.rs @@ -198,7 +198,7 @@ pub use teloxide_macros::BotCommand; /// 4. `#[command(prefix = "prefix")]` /// 5. `#[command(separator = "sep")]` /// -/// These boys just override the corresponding `enum` attributes for a specific +/// These attributes just override the corresponding `enum` attributes for a specific /// variant. /// /// [`FromStr`]: https://doc.rust-lang.org/std/str/trait.FromStr.html From c0e3064785b9ba64b4923b5d0e32a84f1d7f664d Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Mon, 10 May 2021 12:13:07 +0600 Subject: [PATCH 070/131] Fmt --- src/utils/command.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/command.rs b/src/utils/command.rs index ae82f713..5dd3f852 100644 --- a/src/utils/command.rs +++ b/src/utils/command.rs @@ -198,8 +198,8 @@ pub use teloxide_macros::BotCommand; /// 4. `#[command(prefix = "prefix")]` /// 5. `#[command(separator = "sep")]` /// -/// These attributes just override the corresponding `enum` attributes for a specific -/// variant. +/// These attributes just override the corresponding `enum` attributes for a +/// specific variant. /// /// [`FromStr`]: https://doc.rust-lang.org/std/str/trait.FromStr.html /// [`BotCommand`]: crate::utils::command::BotCommand From 9b34586a04dab226dea9daa296cb970d9617c1c9 Mon Sep 17 00:00:00 2001 From: Waffle Date: Sat, 15 May 2021 23:26:20 +0300 Subject: [PATCH 071/131] Normalize docs --- Cargo.toml | 2 +- netlify.toml | 2 +- src/lib.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 84f7fcf6..b9840fa9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -99,7 +99,7 @@ tokio = { version = "1.2.0", features = ["fs", "rt-multi-thread", "macros"] } [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] +rustdoc-args = ["--cfg", "docsrs", "-Znormalize-docs"] [[test]] name = "redis" diff --git a/netlify.toml b/netlify.toml index 1197ce45..7cd234d1 100644 --- a/netlify.toml +++ b/netlify.toml @@ -1,6 +1,6 @@ [build] command = "rustup install nightly --profile minimal && cargo +nightly doc --all-features --no-deps && cp -r target/doc _netlify_out" -environment = { RUSTDOCFLAGS= "--cfg docsrs" } +environment = { RUSTDOCFLAGS= "--cfg docsrs -Znormalize-docs" } publish = "_netlify_out" [[redirects]] diff --git a/src/lib.rs b/src/lib.rs index c8a472b0..d14d0175 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -53,7 +53,7 @@ // // To properly build docs of this crate run // ```console -// $ RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --open --all-features +// $ RUSTDOCFLAGS="--cfg docsrs -Znormalize-docs" cargo +nightly doc --open --all-features // ``` #![cfg_attr(all(docsrs, feature = "nightly"), feature(doc_cfg))] From 02a4df4b5c516d933fb30970c8f77c8d5ac24ecc Mon Sep 17 00:00:00 2001 From: Waffle Date: Sat, 15 May 2021 23:31:24 +0300 Subject: [PATCH 072/131] Switch to developement teloxide-core version --- Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index b9840fa9..f71171ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,7 +58,8 @@ full = [ ] [dependencies] -teloxide-core = { version = "0.2.1", default-features = false } +#teloxide-core = { version = "0.2.1", default-features = false } +teloxide-core = { git = "https://github.com/teloxide/teloxide-core.git", rev = "897ba7c941b651cf6b7e614b7d373d14426ed1da", default-features = false } teloxide-macros = { version = "0.4", optional = true } serde_json = "1.0" From 0eef5b8487172a637b610acde346841b334c519f Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 18 May 2021 18:52:29 +0300 Subject: [PATCH 073/131] Enable TLS by default We use HTTPS which doesn't work without TLS implementation. --- Cargo.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index f71171ad..01112022 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,8 @@ authors = [ maintenance = { status = "actively-developed" } [features] +default = ["native-tls", "teloxide-core/default"] + sqlite-storage = ["sqlx"] redis-storage = ["redis"] cbor-serializer = ["serde_cbor"] From c3bcdca549f5c0fb7e0586bf08ece99124592f29 Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Thu, 20 May 2021 21:37:53 +0600 Subject: [PATCH 074/131] Don't use `.send()` in examples/simple_commands_bot --- README.md | 2 +- examples/simple_commands_bot/src/main.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e0936e05..6b2d4b86 100644 --- a/README.md +++ b/README.md @@ -150,7 +150,7 @@ async fn answer( command: Command, ) -> Result<(), Box> { match command { - Command::Help => cx.answer(Command::descriptions()).send().await?, + Command::Help => cx.answer(Command::descriptions()).await?, Command::Username(username) => { cx.answer(format!("Your username is @{}.", username)).await? } diff --git a/examples/simple_commands_bot/src/main.rs b/examples/simple_commands_bot/src/main.rs index 8b34c7b0..5c459402 100644 --- a/examples/simple_commands_bot/src/main.rs +++ b/examples/simple_commands_bot/src/main.rs @@ -18,7 +18,7 @@ async fn answer( command: Command, ) -> Result<(), Box> { match command { - Command::Help => cx.answer(Command::descriptions()).send().await?, + Command::Help => cx.answer(Command::descriptions()).await?, Command::Username(username) => { cx.answer(format!("Your username is @{}.", username)).await? } From 1d5859507ac40086f71f1044d895347bb4208bae Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Thu, 20 May 2021 21:54:41 +0600 Subject: [PATCH 075/131] Remove `feature(extended_key_value_attributes)` --- src/lib.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d14d0175..ce711e6e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,3 @@ -#![cfg_attr(feature = "nightly", feature(extended_key_value_attributes))] - //! A full-featured framework that empowers you to easily build [Telegram bots] //! using the [`async`/`.await`] syntax in [Rust]. It handles all the difficult //! stuff so you can focus only on your business logic. From 86515b13ed9ff2a83f5f2a274cd847a3c7173981 Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Tue, 25 May 2021 09:00:52 +0600 Subject: [PATCH 076/131] Add more community bots --- README.md | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 6b2d4b86..c80985a1 100644 --- a/README.md +++ b/README.md @@ -423,16 +423,20 @@ A: Yes. You can setup any logger, for example, [fern], e.g. teloxide has no spec [`enable_logging_with_filter!`]: https://docs.rs/teloxide/latest/teloxide/macro.enable_logging_with_filter.html ## Community bots -Feel free to push your own bot into our collection! +Feel free to propose your own bot to our collection! - - [_steadylearner/subreddit_reader_](https://github.com/steadylearner/Rust-Full-Stack/tree/master/commits/teloxide/subreddit_reader) - - [_ArtHome12/vzmuinebot -- Telegram bot for food menu navigate_](https://github.com/ArtHome12/vzmuinebot) - - [_Hermitter/tepe -- A CLI to command a bot to send messages and files over Telegram_](https://github.com/Hermitter/tepe) - - [_ArtHome12/cognito_bot -- The bot is designed to anonymize messages to a group_](https://github.com/ArtHome12/cognito_bot) - - [_pro-vim/tg-vimhelpbot -- Link `:help` for Vim in Telegram_](https://github.com/pro-vim/tg-vimhelpbot) - - [_sschiz/janitor-bot_ -- A bot that removes users trying to join to a chat that is designed for comments](https://github.com/sschiz/janitor-bot) - - [_myblackbeard/basketball-betting-bot -- The bot lets you bet on NBA games against your buddies_](https://github.com/myblackbeard/basketball-betting-bot) - - [_slondr/BeerHolderBot -- A bot that holds your beer_](https://gitlab.com/slondr/BeerHolderBot) + - [steadylearner/subreddit_reader](https://github.com/steadylearner/Rust-Full-Stack/tree/master/commits/teloxide/subreddit_reader) -- A bot that shows the latest posts at Rust subreddit. + - [ArtHome12/vzmuinebot](https://github.com/ArtHome12/vzmuinebot) -- Telegram bot for food menu navigate. + - [ArtHome12/cognito_bot](https://github.com/ArtHome12/cognito_bot) -- The bot is designed to anonymize messages to a group. + - [Hermitter/tepe](https://github.com/Hermitter/tepe) -- A CLI to command a bot to send messages and files over Telegram. + - [pro-vim/tg-vimhelpbot](https://github.com/pro-vim/tg-vimhelpbot) -- Link `:help` for Vim in Telegram. + - [sschiz/janitor-bot](https://github.com/sschiz/janitor-bot) -- A bot that removes users trying to join to a chat that is designed for comments. + - [myblackbeard/basketball-betting-bot](https://github.com/myblackbeard/basketball-betting-bot) -- The bot lets you bet on NBA games against your buddies. + - [slondr/BeerHolderBot](https://gitlab.com/slondr/BeerHolderBot) -- A bot that holds your beer. + - [mxseev/logram](https://github.com/mxseev/logram) -- Utility that takes logs from anywhere and sends them to Telegram. + - [msfjarvis/walls-bot-rs](https://github.com/msfjarvis/walls-bot-rs) -- Telegram bot for my wallpapers collection, in Rust. + - [MustafaSalih1993/Miss-Vodka-Telegram-Bot](https://github.com/MustafaSalih1993/Miss-Vodka-Telegram-Bot) -- A telegram bot written in rust using "Teloxide" library. + - [x13a/tg-prompt](https://github.com/x13a/tg-prompt) -- Telegram prompt. ## Contributing See [CONRIBUTING.md](https://github.com/teloxide/teloxide/blob/master/CONTRIBUTING.md). From e3a3c1ed504c970566ce9c8dcb1d682b20862d5a Mon Sep 17 00:00:00 2001 From: Robin Lange Date: Sat, 29 May 2021 16:48:00 +1000 Subject: [PATCH 077/131] README: enable the "macros" feature in the quickstart Cargo.toml The "commands" example later in the README does not compile without it. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c80985a1..68493ac4 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ $ rustup override set nightly 5. Run `cargo new my_bot`, enter the directory and put these lines into your `Cargo.toml`: ```toml [dependencies] -teloxide = { version = "0.4", features = ["auto-send"] } +teloxide = { version = "0.4", features = ["auto-send", "macros"] } log = "0.4.8" pretty_env_logger = "0.4.0" tokio = { version = "1.3", features = ["rt-multi-thread", "macros"] } From f4260ed078e4092f7248eb41e819c76a2f7c8923 Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Fri, 18 Jun 2021 20:53:03 +0600 Subject: [PATCH 078/131] Replace `lockfree` with `flurry` --- Cargo.toml | 2 +- src/dispatching/dialogue/dialogue_dispatcher.rs | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 01112022..509ca839 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,8 +71,8 @@ tokio = { version = "1.2", features = ["fs"] } tokio-util = "0.6" tokio-stream = "0.1" +flurry = "0.3" log = "0.4" -lockfree = "0.5.1" bytes = "1.0" mime = "0.3" diff --git a/src/dispatching/dialogue/dialogue_dispatcher.rs b/src/dispatching/dialogue/dialogue_dispatcher.rs index 31fbdc90..cee26a29 100644 --- a/src/dispatching/dialogue/dialogue_dispatcher.rs +++ b/src/dispatching/dialogue/dialogue_dispatcher.rs @@ -10,7 +10,7 @@ use futures::{future::BoxFuture, FutureExt, StreamExt}; use tokio::sync::mpsc; use crate::dispatching::dialogue::InMemStorageError; -use lockfree::map::Map; +use flurry::HashMap; use std::sync::{Arc, Mutex}; use teloxide_core::requests::Requester; use tokio_stream::wrappers::UnboundedReceiverStream; @@ -41,7 +41,7 @@ pub struct DialogueDispatcher { /// A value is the TX part of an unbounded asynchronous MPSC channel. A /// handler that executes updates from the same chat ID sequentially /// handles the RX part. - senders: Arc>>>, + senders: Arc>>>, } impl DialogueDispatcher, H, Upd> @@ -59,7 +59,7 @@ where Self { storage: InMemStorage::new(), handler: Arc::new(handler), - senders: Arc::new(Map::new()), + senders: Arc::new(HashMap::new()), _phantom: PhantomData, } } @@ -79,7 +79,7 @@ where Self { storage, handler: Arc::new(handler), - senders: Arc::new(Map::new()), + senders: Arc::new(HashMap::new()), _phantom: PhantomData, } } @@ -116,7 +116,7 @@ where // On the next .poll() call, the spawned future will // return Poll::Ready, because we are dropping the // sender right here: - senders.remove(&chat_id); + senders.pin().remove(&chat_id); if let Err(e) = storage.remove_dialogue(chat_id).await { log::error!("Storage::remove_dialogue failed: {:?}", e); @@ -153,10 +153,10 @@ where let this = Arc::clone(&this); let chat_id = cx.update.chat_id(); - match this.senders.get(&chat_id) { + match this.senders.pin().get(&chat_id) { // An old dialogue Some(tx) => { - if tx.1.send(cx).is_err() { + if tx.send(cx).is_err() { panic!("We are not dropping a receiver or call .close() on it",); } } @@ -165,7 +165,7 @@ where if tx.send(cx).is_err() { panic!("We are not dropping a receiver or call .close() on it",); } - this.senders.insert(chat_id, tx); + this.senders.pin().insert(chat_id, tx); } } From fb0b267b94b7d70d56a67e7efa3ec16f6ae35019 Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Fri, 18 Jun 2021 21:35:54 +0600 Subject: [PATCH 079/131] Fix the removed feature `external_doc` --- src/lib.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ce711e6e..daed1b98 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -47,13 +47,15 @@ #![allow(clippy::match_bool)] #![forbid(unsafe_code)] #![cfg_attr(all(feature = "nightly", doctest), feature(external_doc))] -// we pass "--cfg docsrs" when building docs to add `This is supported on feature="..." only.` +#![cfg_attr(feature = "nightly", feature(doc_cfg))] + +// we pass "--cfg docsrs" when building docs to add `This is supported on +// feature="..." only.` // // To properly build docs of this crate run // ```console // $ RUSTDOCFLAGS="--cfg docsrs -Znormalize-docs" cargo +nightly doc --open --all-features // ``` -#![cfg_attr(all(docsrs, feature = "nightly"), feature(doc_cfg))] pub use dispatching::repls::{ commands_repl, commands_repl_with_listener, dialogues_repl, dialogues_repl_with_listener, repl, @@ -79,7 +81,7 @@ pub use teloxide_macros as macros; pub use teloxide_macros::teloxide; #[cfg(all(feature = "nightly", doctest))] -#[doc(include = "../README.md")] +#[cfg_attr(feature = "nightly", cfg_attr(feature = "nightly", doc = include_str!("../README.md")))] enum ReadmeDocTests {} use teloxide_core::requests::ResponseResult; From 74348b7236679da12da55914260f05d47dd3c7fb Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Fri, 18 Jun 2021 21:44:10 +0600 Subject: [PATCH 080/131] Remove `feature(external_doc)` completely --- src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index daed1b98..52f21955 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,7 +46,6 @@ )] #![allow(clippy::match_bool)] #![forbid(unsafe_code)] -#![cfg_attr(all(feature = "nightly", doctest), feature(external_doc))] #![cfg_attr(feature = "nightly", feature(doc_cfg))] // we pass "--cfg docsrs" when building docs to add `This is supported on From 6a7ad31fc9757c6a48ecec75884d2ae7d5d49281 Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Fri, 18 Jun 2021 22:07:37 +0600 Subject: [PATCH 081/131] Place the comment above `feature(doc_cfg)` --- src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 52f21955..a80ab278 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,8 +46,6 @@ )] #![allow(clippy::match_bool)] #![forbid(unsafe_code)] -#![cfg_attr(feature = "nightly", feature(doc_cfg))] - // we pass "--cfg docsrs" when building docs to add `This is supported on // feature="..." only.` // @@ -55,6 +53,7 @@ // ```console // $ RUSTDOCFLAGS="--cfg docsrs -Znormalize-docs" cargo +nightly doc --open --all-features // ``` +#![cfg_attr(feature = "nightly", feature(doc_cfg))] pub use dispatching::repls::{ commands_repl, commands_repl_with_listener, dialogues_repl, dialogues_repl_with_listener, repl, From 0f857e944b66c5693b210ed9284b7017b2d9f82f Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Fri, 18 Jun 2021 22:17:08 +0600 Subject: [PATCH 082/131] `feature = "nightly"` => `all(docsrs, feature = "nightly")` --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index a80ab278..7079bbce 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -53,7 +53,7 @@ // ```console // $ RUSTDOCFLAGS="--cfg docsrs -Znormalize-docs" cargo +nightly doc --open --all-features // ``` -#![cfg_attr(feature = "nightly", feature(doc_cfg))] +#![cfg_attr(all(docsrs, feature = "nightly"), feature(doc_cfg))] pub use dispatching::repls::{ commands_repl, commands_repl_with_listener, dialogues_repl, dialogues_repl_with_listener, repl, From d97dfa3058d0af830f4067c48eb2b40dfb7e58d4 Mon Sep 17 00:00:00 2001 From: dracarys18 Date: Tue, 22 Jun 2021 11:33:42 +0530 Subject: [PATCH 083/131] add community bot: dracary18/grpmr-rs Signed-off-by: dracarys18 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 68493ac4..088dd691 100644 --- a/README.md +++ b/README.md @@ -425,6 +425,7 @@ A: Yes. You can setup any logger, for example, [fern], e.g. teloxide has no spec ## Community bots Feel free to propose your own bot to our collection! + - [dracarys18/grpmr-rs](https://github.com/dracarys18/grpmr-rs) -- A telegram group manager bot with variety of extra features. - [steadylearner/subreddit_reader](https://github.com/steadylearner/Rust-Full-Stack/tree/master/commits/teloxide/subreddit_reader) -- A bot that shows the latest posts at Rust subreddit. - [ArtHome12/vzmuinebot](https://github.com/ArtHome12/vzmuinebot) -- Telegram bot for food menu navigate. - [ArtHome12/cognito_bot](https://github.com/ArtHome12/cognito_bot) -- The bot is designed to anonymize messages to a group. From aeca45a579d70e14fe48efa439943be43f824eb3 Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 18 May 2021 14:46:45 +0300 Subject: [PATCH 084/131] Refactor `UpdateListner` trait Instead of `Stream` super trait we now require `for<'a> AsUpdateStream<'a, E'>` as a super trait. `AsUpdateStream` in turn provides `as_stream` function which takes `&mut self` and returns `Self::Stream` (the trait is needed to workaround lack of GAT). This patch also adds a `stop` function that allows stopping the listener. Since `UpdateListner` now isn't `Stream` and required methods, it's blanked implementation for streams was removed. `polling` and `polling_default` functions now also require `R: 'static`. --- CHANGELOG.md | 7 ++ src/dispatching/dispatcher.rs | 5 +- src/dispatching/update_listeners.rs | 180 +++++++++++++++++++++++++--- 3 files changed, 171 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 63def763..2ae661dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Return an error from `Storage::remove_dialogue` if a dialogue does not exist. - Require `D: Clone` in `dialogues_repl(_with_listener)` and `InMemStorage`. - Automatically delete a webhook if it was set up in `update_listeners::polling_default` (thereby making it `async`, [issue 319](https://github.com/teloxide/teloxide/issues/319)). + - `polling` and `polling_default` now require `R: 'static` + - Refactor `UpdateListener` trait: + - Add a `stop` function that allows stopping the listener. + - Remove blanked implementation. + - Remove `Stream` from super traits. + - Add `AsUpdateStream` to super traits. + - Add an `AsUpdateStream` trait that allows turning implementors into streams of updates (GAT workaround). ### Fixed diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index 93bf8a7e..e0a8aa4d 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -243,7 +243,7 @@ where /// `update_listener_error_handler`. pub async fn dispatch_with_listener<'a, UListener, ListenerE, Eh>( &'a self, - update_listener: UListener, + mut update_listener: UListener, update_listener_error_handler: Arc, ) where UListener: UpdateListener + 'a, @@ -251,9 +251,8 @@ where ListenerE: Debug, R: Requester + Clone, { - let update_listener = Box::pin(update_listener); - update_listener + .as_stream() .for_each(move |update| { let update_listener_error_handler = Arc::clone(&update_listener_error_handler); diff --git a/src/dispatching/update_listeners.rs b/src/dispatching/update_listeners.rs index bdece104..b577b9ab 100644 --- a/src/dispatching/update_listeners.rs +++ b/src/dispatching/update_listeners.rs @@ -111,11 +111,50 @@ use teloxide_core::{ types::{AllowedUpdate, SemiparsedVec, Update}, }; -/// A generic update listener. -pub trait UpdateListener: Stream> { - // TODO: add some methods here (.shutdown(), etc). +/// An update listener. +/// +/// Implementors of this trait allow getting updates from Telegram. +/// +/// Currently Telegram has 2 ways of getting updates -- [polling] and +/// [webhooks]. Currently, only the former one is implemented (see [`polling`] +/// and [`polling_default`]) +/// +/// Some functions of this trait are located in the supertrait +/// ([`AsUpdateStream`]), see also: +/// - [`Stream`] +/// - [`as_stream`] +/// +/// [polling]: self#long-polling +/// [webhooks]: self#webhooks +/// [`Stream`]: AsUpdateStream::Stream +/// [`as_stream`]: AsUpdateStream::as_stream +pub trait UpdateListener: for<'a> AsUpdateStream<'a, E> { + /// Stop listening for updates. + /// + /// This function is not guaranteed to have an immidiate effect. That is + /// some listners can return updates even after [`stop`] is called (e.g.: + /// because of buffering). + /// + /// [`stop`]: UpdateListener::stop + /// + /// Implementors of this function are encouraged to stop listening for + /// updates as soon as possible and return `None` from the update stream as + /// soon as all cached updates are returned. + fn stop(&mut self); +} + +/// [`UpdateListener`]'s supertrait/extension. +/// +/// This trait is a workaround to not require GAT. +pub trait AsUpdateStream<'a, E> { + /// Stream of updates from Telegram. + type Stream: Stream> + 'a; + + /// Creates the update [`Stream`]. + /// + /// [`Stream`]: AsUpdateStream::Stream + fn as_stream(&'a mut self) -> Self::Stream; } -impl UpdateListener for S where S: Stream> {} /// Returns a long polling update listener with `timeout` of 10 seconds. /// @@ -126,7 +165,7 @@ impl UpdateListener for S where S: Stream> {} /// This function will automatically delete a webhook if it was set up. pub async fn polling_default(requester: R) -> impl UpdateListener where - R: Requester, + R: Requester + 'static, ::GetUpdatesFaultTolerant: Send, { delete_webhook_if_setup(&requester).await; @@ -152,19 +191,58 @@ pub fn polling( allowed_updates: Option>, ) -> impl UpdateListener where - R: Requester, + R: Requester + 'static, ::GetUpdatesFaultTolerant: Send, { - let timeout = timeout.map(|t| t.as_secs().try_into().expect("timeout is too big")); + enum RunningState { + Polling, + Stopping, + Stopped, + } + + struct State { + bot: B, + timeout: Option, + limit: Option, + allowed_updates: Option>, + offset: i32, + run_state: RunningState, + } + + fn stream(st: &mut State) -> impl Stream> + '_ + where + B: Requester, + { + stream::unfold(st, move |state| async move { + let State { timeout, limit, allowed_updates, bot, offset, run_state, .. } = &mut *state; + + match run_state { + RunningState::Polling => {} + RunningState::Stopped => return None, + RunningState::Stopping => { + let mut req = bot.get_updates_fault_tolerant(); + + let payload = &mut req.payload_mut().0; + payload.offset = Some(*offset); + payload.timeout = *timeout; + payload.limit = Some(1); + payload.allowed_updates = allowed_updates.take(); + + return match req.send().await { + Ok(_) => { + *run_state = RunningState::Stopped; + None + } + Err(err) => Some((stream::iter(vec![Err(err)]), state)), + }; + } + } - stream::unfold( - (allowed_updates, requester, 0), - move |(mut allowed_updates, bot, mut offset)| async move { let mut req = bot.get_updates_fault_tolerant(); let payload = &mut req.payload_mut().0; - payload.offset = Some(offset); - payload.timeout = timeout; - payload.limit = limit; + payload.offset = Some(*offset); + payload.timeout = *timeout; + payload.limit = *limit; payload.allowed_updates = allowed_updates.take(); let updates = match req.send().await { @@ -181,7 +259,7 @@ where .expect("update_id must be i32"), }; - offset = id + 1; + *offset = id + 1; } for update in &updates { @@ -200,10 +278,27 @@ where } }; - Some((stream::iter(updates), (allowed_updates, bot, offset))) - }, - ) - .flatten() + Some((stream::iter(updates), state)) + }) + .flatten() + } + + let timeout = timeout.map(|t| t.as_secs().try_into().expect("timeout is too big")); + + let state = State { + bot: requester, + timeout, + limit, + allowed_updates, + offset: 0, + run_state: RunningState::Polling, + }; + + let stop = assert_stop_fn(|st: &mut State<_>| { + st.run_state = RunningState::Stopping; + }); + + StatefulListner { state, stream, stop } } async fn delete_webhook_if_setup(requester: &R) @@ -226,3 +321,52 @@ where } } } + +/// A listner created from `state` and `stream`/`stop` functions. +struct StatefulListner { + /// The state of the listner. + state: St, + + /// Function used as `AsUpdateStream::as_stream`. + /// + /// Must be of type `for<'a> &'a mut St -> impl Stream + 'a` and callable by + /// `&mut`. + stream: Sf, + + /// Function used as `UpdateListner::stop`. + /// + /// Must be of type `for<'a> &'a mut St`. + stop: Option, +} + +impl<'a, St, Sf, F, Strm, E> AsUpdateStream<'a, E> for StatefulListner +where + (St, Strm): 'a, + Sf: FnMut(&'a mut St) -> Strm, + Strm: Stream>, +{ + type Stream = Strm; + + fn as_stream(&'a mut self) -> Self::Stream { + (self.stream)(&mut self.state) + } +} + +impl UpdateListener for StatefulListner +where + Self: for<'a> AsUpdateStream<'a, E>, + F: FnOnce(&mut St), +{ + fn stop(&mut self) { + self.stop.take().map(|stop| stop(&mut self.state)); + } +} + +/// Assert (at compile tume) that `f` is fine as a stop-function (closure +/// lifetime inference workaround). +fn assert_stop_fn(f: F) -> Option +where + F: FnOnce(&mut St), +{ + Some(f) +} From c288a540b9890bbd3c45d132f63e939f26cd1513 Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 18 May 2021 17:09:20 +0300 Subject: [PATCH 085/131] Store fetched updates in `polling::State` This way updates aren't lost when the stream is dropped. --- src/dispatching/update_listeners.rs | 131 +++++++++++++++++----------- 1 file changed, 81 insertions(+), 50 deletions(-) diff --git a/src/dispatching/update_listeners.rs b/src/dispatching/update_listeners.rs index b577b9ab..e88d2635 100644 --- a/src/dispatching/update_listeners.rs +++ b/src/dispatching/update_listeners.rs @@ -105,7 +105,7 @@ use futures::{stream, Stream, StreamExt}; -use std::{convert::TryInto, time::Duration}; +use std::{convert::TryInto, iter, time::Duration, vec}; use teloxide_core::{ requests::{HasPayload, Request, Requester}, types::{AllowedUpdate, SemiparsedVec, Update}, @@ -153,6 +153,8 @@ pub trait AsUpdateStream<'a, E> { /// Creates the update [`Stream`]. /// /// [`Stream`]: AsUpdateStream::Stream + /// + /// Returned stream **must not** be stateful. State must be kept in `self`. fn as_stream(&'a mut self) -> Self::Stream; } @@ -200,13 +202,28 @@ where Stopped, } - struct State { + struct State { bot: B, timeout: Option, limit: Option, allowed_updates: Option>, offset: i32, run_state: RunningState, + + // Updates fetched last time. + // + // We need to store them here so we can drop stream without loosing state. + fetched: Option< + iter::Map< + iter::FilterMap< + vec::IntoIter>, + fn( + Result, + ) -> std::option::Option, + >, + fn(Update) -> Result, + >, + >, } fn stream(st: &mut State) -> impl Stream> + '_ @@ -214,71 +231,84 @@ where B: Requester, { stream::unfold(st, move |state| async move { - let State { timeout, limit, allowed_updates, bot, offset, run_state, .. } = &mut *state; + let State { timeout, limit, allowed_updates, bot, offset, run_state, fetched, .. } = + &mut *state; - match run_state { - RunningState::Polling => {} - RunningState::Stopped => return None, - RunningState::Stopping => { - let mut req = bot.get_updates_fault_tolerant(); + let fetched_is_none_or_empty = fetched + .as_ref() + .map(|f| matches!(f.size_hint(), (_lower, Some(0)))) + .unwrap_or(true); - let payload = &mut req.payload_mut().0; - payload.offset = Some(*offset); - payload.timeout = *timeout; - payload.limit = Some(1); - payload.allowed_updates = allowed_updates.take(); + if fetched_is_none_or_empty { + match run_state { + RunningState::Polling => {} + RunningState::Stopped => return None, + RunningState::Stopping => { + let mut req = bot.get_updates_fault_tolerant(); - return match req.send().await { - Ok(_) => { - *run_state = RunningState::Stopped; - None - } - Err(err) => Some((stream::iter(vec![Err(err)]), state)), - }; - } - } + let payload = &mut req.payload_mut().0; + payload.offset = Some(*offset); + payload.timeout = *timeout; + payload.limit = Some(1); + payload.allowed_updates = allowed_updates.take(); - let mut req = bot.get_updates_fault_tolerant(); - let payload = &mut req.payload_mut().0; - payload.offset = Some(*offset); - payload.timeout = *timeout; - payload.limit = *limit; - payload.allowed_updates = allowed_updates.take(); - - let updates = match req.send().await { - Err(err) => vec![Err(err)], - Ok(SemiparsedVec(updates)) => { - // Set offset to the last update's id + 1 - if let Some(upd) = updates.last() { - let id: i32 = match upd { - Ok(ok) => ok.id, - Err((value, _)) => value["update_id"] - .as_i64() - .expect("The 'update_id' field must always exist in Update") - .try_into() - .expect("update_id must be i32"), + return match req.send().await { + Ok(_) => { + *run_state = RunningState::Stopped; + None + } + Err(err) => Some((stream::iter(Some(Err(err))), state)), }; - - *offset = id + 1; } + } - for update in &updates { - if let Err((value, e)) = update { - log::error!( + let mut req = bot.get_updates_fault_tolerant(); + let payload = &mut req.payload_mut().0; + payload.offset = Some(*offset); + payload.timeout = *timeout; + payload.limit = *limit; + payload.allowed_updates = allowed_updates.take(); + + let updates = match req.send().await { + Err(err) => return Some((stream::iter(Some(Err(err))), state)), + Ok(SemiparsedVec(updates)) => { + // Set offset to the last update's id + 1 + if let Some(upd) = updates.last() { + let id: i32 = match upd { + Ok(ok) => ok.id, + Err((value, _)) => value["update_id"] + .as_i64() + .expect("The 'update_id' field must always exist in Update") + .try_into() + .expect("update_id must be i32"), + }; + + *offset = id + 1; + } + + for update in &updates { + if let Err((value, e)) = update { + log::error!( "Cannot parse an update.\nError: {:?}\nValue: {}\n\ This is a bug in teloxide-core, please open an issue here: \ https://github.com/teloxide/teloxide-core/issues.", e, value ); + } } + + updates + .into_iter() + .filter_map(Result::ok as fn(_) -> _) + .map(Ok as fn(_) -> _) } + }; - updates.into_iter().filter_map(Result::ok).map(Ok).collect::>() - } - }; + *fetched = Some(updates); + } - Some((stream::iter(updates), state)) + Some((stream::iter(fetched.as_mut().and_then(|f| f.next())), state)) }) .flatten() } @@ -292,6 +322,7 @@ where allowed_updates, offset: 0, run_state: RunningState::Polling, + fetched: None, }; let stop = assert_stop_fn(|st: &mut State<_>| { From 9f5a222ed7ae4e1ab7bb4f6357253b0710e500a4 Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 18 May 2021 17:29:45 +0300 Subject: [PATCH 086/131] Add `UpdateListener::timeout_hint` --- src/dispatching/update_listeners.rs | 49 ++++++++++++++++------------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/src/dispatching/update_listeners.rs b/src/dispatching/update_listeners.rs index e88d2635..cc648ad4 100644 --- a/src/dispatching/update_listeners.rs +++ b/src/dispatching/update_listeners.rs @@ -141,6 +141,11 @@ pub trait UpdateListener: for<'a> AsUpdateStream<'a, E> { /// updates as soon as possible and return `None` from the update stream as /// soon as all cached updates are returned. fn stop(&mut self); + + /// Timeout duration hint. + fn timeout_hint(&self) -> Option { + None + } } /// [`UpdateListener`]'s supertrait/extension. @@ -313,11 +318,9 @@ where .flatten() } - let timeout = timeout.map(|t| t.as_secs().try_into().expect("timeout is too big")); - let state = State { bot: requester, - timeout, + timeout: timeout.map(|t| t.as_secs().try_into().expect("timeout is too big")), limit, allowed_updates, offset: 0, @@ -325,11 +328,11 @@ where fetched: None, }; - let stop = assert_stop_fn(|st: &mut State<_>| { - st.run_state = RunningState::Stopping; - }); + let stop = Some(|st: &mut State<_>| st.run_state = RunningState::Stopping); - StatefulListner { state, stream, stop } + let timeout_hint = Some(move |_: &State<_>| timeout); + + StatefulListner { state, stream, stop, timeout_hint } } async fn delete_webhook_if_setup(requester: &R) @@ -354,7 +357,7 @@ where } /// A listner created from `state` and `stream`/`stop` functions. -struct StatefulListner { +struct StatefulListner { /// The state of the listner. state: St, @@ -362,18 +365,24 @@ struct StatefulListner { /// /// Must be of type `for<'a> &'a mut St -> impl Stream + 'a` and callable by /// `&mut`. - stream: Sf, + stream: Assf, /// Function used as `UpdateListner::stop`. /// /// Must be of type `for<'a> &'a mut St`. - stop: Option, + stop: Option, + + /// Function used as `UpdateListner::timeout_hint`. + /// + /// Must be of type `for<'a> &'a St -> Option` and callable by + /// `&`. + timeout_hint: Option, } -impl<'a, St, Sf, F, Strm, E> AsUpdateStream<'a, E> for StatefulListner +impl<'a, St, Assf, Sf, Thf, Strm, E> AsUpdateStream<'a, E> for StatefulListner where (St, Strm): 'a, - Sf: FnMut(&'a mut St) -> Strm, + Assf: FnMut(&'a mut St) -> Strm, Strm: Stream>, { type Stream = Strm; @@ -383,21 +392,17 @@ where } } -impl UpdateListener for StatefulListner +impl UpdateListener for StatefulListner where Self: for<'a> AsUpdateStream<'a, E>, - F: FnOnce(&mut St), + Sf: FnOnce(&mut St), + Thf: Fn(&St) -> Option, { fn stop(&mut self) { self.stop.take().map(|stop| stop(&mut self.state)); } -} -/// Assert (at compile tume) that `f` is fine as a stop-function (closure -/// lifetime inference workaround). -fn assert_stop_fn(f: F) -> Option -where - F: FnOnce(&mut St), -{ - Some(f) + fn timeout_hint(&self) -> Option { + self.timeout_hint.as_ref().and_then(|f| f(&self.state)) + } } From eae2bced6df1a0378f041388e17ae4579263a16e Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 18 May 2021 17:38:47 +0300 Subject: [PATCH 087/131] Simplify `polling` a little bit --- src/dispatching/update_listeners.rs | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/dispatching/update_listeners.rs b/src/dispatching/update_listeners.rs index cc648ad4..602e8c19 100644 --- a/src/dispatching/update_listeners.rs +++ b/src/dispatching/update_listeners.rs @@ -107,6 +107,7 @@ use futures::{stream, Stream, StreamExt}; use std::{convert::TryInto, iter, time::Duration, vec}; use teloxide_core::{ + payloads::GetUpdates, requests::{HasPayload, Request, Requester}, types::{AllowedUpdate, SemiparsedVec, Update}, }; @@ -251,11 +252,12 @@ where RunningState::Stopping => { let mut req = bot.get_updates_fault_tolerant(); - let payload = &mut req.payload_mut().0; - payload.offset = Some(*offset); - payload.timeout = *timeout; - payload.limit = Some(1); - payload.allowed_updates = allowed_updates.take(); + req.payload_mut().0 = GetUpdates { + offset: Some(*offset), + timeout: *timeout, + limit: Some(1), + allowed_updates: allowed_updates.take(), + }; return match req.send().await { Ok(_) => { @@ -268,11 +270,12 @@ where } let mut req = bot.get_updates_fault_tolerant(); - let payload = &mut req.payload_mut().0; - payload.offset = Some(*offset); - payload.timeout = *timeout; - payload.limit = *limit; - payload.allowed_updates = allowed_updates.take(); + req.payload_mut().0 = GetUpdates { + offset: Some(*offset), + timeout: *timeout, + limit: *limit, + allowed_updates: allowed_updates.take(), + }; let updates = match req.send().await { Err(err) => return Some((stream::iter(Some(Err(err))), state)), From 41a95079b2bd40ccdd996a55ea105699b6075926 Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 18 May 2021 18:04:55 +0300 Subject: [PATCH 088/131] Add Dispatcher::shutdown function This function allows to gracefuly shutdown dispatching. --- CHANGELOG.md | 1 + src/dispatching/dispatcher.rs | 370 +++++++++++++++++++++++----------- 2 files changed, 252 insertions(+), 119 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ae661dc..5d4310c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Storage::get_dialogue` to obtain a dialogue indexed by a chat ID. - `InMemStorageError` with a single variant `DialogueNotFound` to be returned from `InMemStorage::remove_dialogue`. - `RedisStorageError::DialogueNotFound` and `SqliteStorageError::DialogueNotFound` to be returned from `Storage::remove_dialogue`. + - `Dispatcher::shutdown` function. ### Changed diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index e0a8aa4d..ea5d5844 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -4,16 +4,27 @@ use crate::{ }, error_handlers::{ErrorHandler, LoggingErrorHandler}, }; -use futures::StreamExt; -use std::{fmt::Debug, sync::Arc}; +use core::panic; +use futures::{Future, StreamExt}; +use std::{ + fmt::Debug, + sync::{ + atomic::{AtomicU8, Ordering}, + Arc, + }, + time::Duration, +}; use teloxide_core::{ requests::Requester, types::{ CallbackQuery, ChatMemberUpdated, ChosenInlineResult, InlineQuery, Message, Poll, - PollAnswer, PreCheckoutQuery, ShippingQuery, UpdateKind, + PollAnswer, PreCheckoutQuery, ShippingQuery, Update, UpdateKind, }, }; -use tokio::sync::mpsc; +use tokio::{ + sync::{mpsc, Notify}, + time::timeout, +}; type Tx = Option>>; @@ -63,6 +74,9 @@ pub struct Dispatcher { poll_answers_queue: Tx, my_chat_members_queue: Tx, chat_members_queue: Tx, + + shutdown_state: AtomicShutdownState, + shutdown_notify_back: Notify, } impl Dispatcher @@ -87,6 +101,10 @@ where poll_answers_queue: None, my_chat_members_queue: None, chat_members_queue: None, + shutdown_state: AtomicShutdownState { + inner: AtomicU8::new(ShutdownState::IsntRunning as _), + }, + shutdown_notify_back: Notify::new(), } } @@ -251,125 +269,239 @@ where ListenerE: Debug, R: Requester + Clone, { - update_listener - .as_stream() - .for_each(move |update| { - let update_listener_error_handler = Arc::clone(&update_listener_error_handler); + use ShutdownState::*; - async move { - log::trace!("Dispatcher received an update: {:?}", update); + const MIN_SHUTDOWN_CHECK_TIMEOUT: Duration = Duration::from_secs(1); - let update = match update { - Ok(update) => update, - Err(error) => { - Arc::clone(&update_listener_error_handler).handle_error(error).await; - return; - } - }; + let shutdown_check_timeout = + update_listener.timeout_hint().unwrap_or(Duration::ZERO) + MIN_SHUTDOWN_CHECK_TIMEOUT; - match update.kind { - UpdateKind::Message(message) => { - send!( - &self.requester, - &self.messages_queue, - message, - UpdateKind::Message - ); - } - UpdateKind::EditedMessage(message) => { - send!( - &self.requester, - &self.edited_messages_queue, - message, - UpdateKind::EditedMessage - ); - } - UpdateKind::ChannelPost(post) => { - send!( - &self.requester, - &self.channel_posts_queue, - post, - UpdateKind::ChannelPost - ); - } - UpdateKind::EditedChannelPost(post) => { - send!( - &self.requester, - &self.edited_channel_posts_queue, - post, - UpdateKind::EditedChannelPost - ); - } - UpdateKind::InlineQuery(query) => { - send!( - &self.requester, - &self.inline_queries_queue, - query, - UpdateKind::InlineQuery - ); - } - UpdateKind::ChosenInlineResult(result) => { - send!( - &self.requester, - &self.chosen_inline_results_queue, - result, - UpdateKind::ChosenInlineResult - ); - } - UpdateKind::CallbackQuery(query) => { - send!( - &self.requester, - &self.callback_queries_queue, - query, - UpdateKind::CallbackQuer - ); - } - UpdateKind::ShippingQuery(query) => { - send!( - &self.requester, - &self.shipping_queries_queue, - query, - UpdateKind::ShippingQuery - ); - } - UpdateKind::PreCheckoutQuery(query) => { - send!( - &self.requester, - &self.pre_checkout_queries_queue, - query, - UpdateKind::PreCheckoutQuery - ); - } - UpdateKind::Poll(poll) => { - send!(&self.requester, &self.polls_queue, poll, UpdateKind::Poll); - } - UpdateKind::PollAnswer(answer) => { - send!( - &self.requester, - &self.poll_answers_queue, - answer, - UpdateKind::PollAnswer - ); - } - UpdateKind::MyChatMember(chat_member_updated) => { - send!( - &self.requester, - &self.my_chat_members_queue, - chat_member_updated, - UpdateKind::MyChatMember - ); - } - UpdateKind::ChatMember(chat_member_updated) => { - send!( - &self.requester, - &self.chat_members_queue, - chat_member_updated, - UpdateKind::MyChatMember - ); - } + if let Err(_) = self.shutdown_state.compare_exchange(IsntRunning, Running) { + panic!("Dispatching is already running"); + } + + { + let stream = update_listener.as_stream(); + tokio::pin!(stream); + + loop { + if let Ok(upd) = timeout(shutdown_check_timeout, stream.next()).await { + match upd { + None => break, + Some(upd) => self.process_update(upd, &update_listener_error_handler).await, } } - }) - .await + + if let ShuttingDown = self.shutdown_state.load() { + break; + } + } + } + + update_listener.stop(); + + update_listener + .as_stream() + .for_each(|upd| self.process_update(upd, &update_listener_error_handler)) + .await; + + if let ShuttingDown = self.shutdown_state.load() { + // Stopped because of a `shutdown` call. + + // Notify `shutdown`s that we finished + self.shutdown_notify_back.notify_waiters(); + } + + self.shutdown_state.store(IsntRunning); + } + + /// Tries shutting down dispatching. + /// + /// Returns error if this dispather isn't dispathing at the moment. + /// + /// If you don't need to wait for shutdown returned future can be ignored. + pub fn shutdown(&self) -> Result + '_, ShutdownError> { + use ShutdownState::*; + + let res = self.shutdown_state.compare_exchange(Running, ShuttingDown); + + match res { + Ok(_) | Err(ShuttingDown) => { + Ok(async move { self.shutdown_notify_back.notified().await }) + } + Err(IsntRunning) => return Err(ShutdownError::IsntRunning), + Err(Running) => unreachable!(), + } + } + + async fn process_update( + &self, + update: Result, + update_listener_error_handler: &Arc, + ) where + R: Requester + Clone, + Eh: ErrorHandler, + ListenerE: Debug, + { + { + log::trace!("Dispatcher received an update: {:?}", update); + + let update = match update { + Ok(update) => update, + Err(error) => { + Arc::clone(update_listener_error_handler).handle_error(error).await; + return; + } + }; + + match update.kind { + UpdateKind::Message(message) => { + send!(&self.requester, &self.messages_queue, message, UpdateKind::Message); + } + UpdateKind::EditedMessage(message) => { + send!( + &self.requester, + &self.edited_messages_queue, + message, + UpdateKind::EditedMessage + ); + } + UpdateKind::ChannelPost(post) => { + send!( + &self.requester, + &self.channel_posts_queue, + post, + UpdateKind::ChannelPost + ); + } + UpdateKind::EditedChannelPost(post) => { + send!( + &self.requester, + &self.edited_channel_posts_queue, + post, + UpdateKind::EditedChannelPost + ); + } + UpdateKind::InlineQuery(query) => { + send!( + &self.requester, + &self.inline_queries_queue, + query, + UpdateKind::InlineQuery + ); + } + UpdateKind::ChosenInlineResult(result) => { + send!( + &self.requester, + &self.chosen_inline_results_queue, + result, + UpdateKind::ChosenInlineResult + ); + } + UpdateKind::CallbackQuery(query) => { + send!( + &self.requester, + &self.callback_queries_queue, + query, + UpdateKind::CallbackQuer + ); + } + UpdateKind::ShippingQuery(query) => { + send!( + &self.requester, + &self.shipping_queries_queue, + query, + UpdateKind::ShippingQuery + ); + } + UpdateKind::PreCheckoutQuery(query) => { + send!( + &self.requester, + &self.pre_checkout_queries_queue, + query, + UpdateKind::PreCheckoutQuery + ); + } + UpdateKind::Poll(poll) => { + send!(&self.requester, &self.polls_queue, poll, UpdateKind::Poll); + } + UpdateKind::PollAnswer(answer) => { + send!( + &self.requester, + &self.poll_answers_queue, + answer, + UpdateKind::PollAnswer + ); + } + UpdateKind::MyChatMember(chat_member_updated) => { + send!( + &self.requester, + &self.my_chat_members_queue, + chat_member_updated, + UpdateKind::MyChatMember + ); + } + UpdateKind::ChatMember(chat_member_updated) => { + send!( + &self.requester, + &self.chat_members_queue, + chat_member_updated, + UpdateKind::MyChatMember + ); + } + } + } + } +} + +#[derive(Debug)] +pub enum ShutdownError { + IsntRunning, +} + +struct AtomicShutdownState { + inner: AtomicU8, +} + +impl AtomicShutdownState { + fn load(&self) -> ShutdownState { + ShutdownState::from_u8(self.inner.load(Ordering::SeqCst)) + } + + fn store(&self, new: ShutdownState) { + self.inner.store(new as _, Ordering::SeqCst) + } + + fn compare_exchange( + &self, + current: ShutdownState, + new: ShutdownState, + ) -> Result { + self.inner + .compare_exchange(current as _, new as _, Ordering::SeqCst, Ordering::SeqCst) + .map(ShutdownState::from_u8) + .map_err(ShutdownState::from_u8) + } +} + +#[repr(u8)] +enum ShutdownState { + Running, + ShuttingDown, + IsntRunning, +} + +impl ShutdownState { + fn from_u8(n: u8) -> Self { + const RUNNING: u8 = ShutdownState::Running as u8; + const SHUTTING_DOWN: u8 = ShutdownState::ShuttingDown as u8; + const ISNT_RUNNING: u8 = ShutdownState::IsntRunning as u8; + + match n { + RUNNING => ShutdownState::Running, + SHUTTING_DOWN => ShutdownState::ShuttingDown, + ISNT_RUNNING => ShutdownState::IsntRunning, + _ => unreachable!(), + } } } From f0de55ad5545e31d48510372254c227751877136 Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 18 May 2021 18:30:57 +0300 Subject: [PATCH 089/131] Add `Dispatcher::setup_ctrlc_handler` function. This function sets up `^C` handler which shuts down dispatching. --- CHANGELOG.md | 3 ++- Cargo.toml | 3 +++ src/dispatching/dispatcher.rs | 46 +++++++++++++++++++++++++++-------- src/features.txt | 1 + 4 files changed, 42 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d4310c6..4a7b64c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `InMemStorageError` with a single variant `DialogueNotFound` to be returned from `InMemStorage::remove_dialogue`. - `RedisStorageError::DialogueNotFound` and `SqliteStorageError::DialogueNotFound` to be returned from `Storage::remove_dialogue`. - `Dispatcher::shutdown` function. + - `Dispatcher::setup_ctrlc_handler` function ([issue 153](https://github.com/teloxide/teloxide/issues/153)). ### Changed @@ -21,7 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Automatically delete a webhook if it was set up in `update_listeners::polling_default` (thereby making it `async`, [issue 319](https://github.com/teloxide/teloxide/issues/319)). - `polling` and `polling_default` now require `R: 'static` - Refactor `UpdateListener` trait: - - Add a `stop` function that allows stopping the listener. + - Add a `stop` function that allows stopping the listener ([issue 166](https://github.com/teloxide/teloxide/issues/166)). - Remove blanked implementation. - Remove `Stream` from super traits. - Add `AsUpdateStream` to super traits. diff --git a/Cargo.toml b/Cargo.toml index 509ca839..ec2eba2d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,8 @@ bincode-serializer = ["bincode"] frunk- = ["frunk"] macros = ["teloxide-macros"] +ctrlc_handler = ["tokio/signal"] + native-tls = ["teloxide-core/native-tls"] rustls = ["teloxide-core/rustls"] auto-send = ["teloxide-core/auto_send"] @@ -51,6 +53,7 @@ full = [ "bincode-serializer", "frunk", "macros", + "ctrlc_handler", "teloxide-core/full", "native-tls", "rustls", diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index ea5d5844..bf088394 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -75,7 +75,7 @@ pub struct Dispatcher { my_chat_members_queue: Tx, chat_members_queue: Tx, - shutdown_state: AtomicShutdownState, + shutdown_state: Arc, shutdown_notify_back: Notify, } @@ -101,9 +101,9 @@ where poll_answers_queue: None, my_chat_members_queue: None, chat_members_queue: None, - shutdown_state: AtomicShutdownState { + shutdown_state: Arc::new(AtomicShutdownState { inner: AtomicU8::new(ShutdownState::IsntRunning as _), - }, + }), shutdown_notify_back: Notify::new(), } } @@ -124,6 +124,25 @@ where Some(tx) } + /// Setup `^C` handler which [`shutdown`]s dispatching. + /// + /// [`shutdown`]: Dispatcher::shutdown + #[cfg(feature = "ctrlc_handler")] + #[cfg_attr(docsrs, doc(cfg(feature = "ctrlc_handler")))] + pub fn setup_ctrlc_handler(self) -> Self { + let shutdown_state = Arc::clone(&self.shutdown_state); + tokio::spawn(async move { + loop { + tokio::signal::ctrl_c().await.expect("Failed to listen for ^C"); + + // If dispatcher wasn't running, then there is nothing to do + Self::shutdown_inner(&shutdown_state).ok(); + } + }); + + self + } + #[must_use] pub fn messages_handler(mut self, h: H) -> Self where @@ -293,6 +312,7 @@ where } if let ShuttingDown = self.shutdown_state.load() { + log::debug!("Start shutting down dispatching"); break; } } @@ -310,25 +330,31 @@ where // Notify `shutdown`s that we finished self.shutdown_notify_back.notify_waiters(); + log::debug!("Dispatching shut down"); + } else { + log::debug!("Dispatching stopped (listner returned `None`)"); } self.shutdown_state.store(IsntRunning); } - /// Tries shutting down dispatching. + /// Tries to shutdown dispatching. /// - /// Returns error if this dispather isn't dispathing at the moment. + /// Returns error if this dispather isn't dispatching at the moment. /// - /// If you don't need to wait for shutdown returned future can be ignored. + /// If you don't need to wait for shutdown, returned future can be ignored. pub fn shutdown(&self) -> Result + '_, ShutdownError> { + Self::shutdown_inner(&self.shutdown_state) + .map(|()| async move { self.shutdown_notify_back.notified().await }) + } + + fn shutdown_inner(shutdown_state: &AtomicShutdownState) -> Result<(), ShutdownError> { use ShutdownState::*; - let res = self.shutdown_state.compare_exchange(Running, ShuttingDown); + let res = shutdown_state.compare_exchange(Running, ShuttingDown); match res { - Ok(_) | Err(ShuttingDown) => { - Ok(async move { self.shutdown_notify_back.notified().await }) - } + Ok(_) | Err(ShuttingDown) => Ok(()), Err(IsntRunning) => return Err(ShutdownError::IsntRunning), Err(Running) => unreachable!(), } diff --git a/src/features.txt b/src/features.txt index c0fa8e20..ee738058 100644 --- a/src/features.txt +++ b/src/features.txt @@ -9,6 +9,7 @@ | `macros` | Re-exports macros from [`teloxide-macros`]. | | `native-tls` | Enables the [`native-tls`] TLS implementation (enabled by default). | | `rustls` | Enables the [`rustls`] TLS implementation. | +| `ctrlc_handler` | Enables [`Dispatcher::setup_ctrlc_handler`](dispatching::Dispatcher::setup_ctrlc_handler) function. | | `auto-send` | Enables the `AutoSend` bot adaptor. | | `cache-me` | Enables the `CacheMe` bot adaptor. | | `frunk` | Enables [`teloxide::utils::UpState`]. | From d745f9bdb1a117dd990efd6531e10baedd775108 Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 18 May 2021 18:42:02 +0300 Subject: [PATCH 090/131] Remove send! macro --- src/dispatching/dispatcher.rs | 170 ++++++++++++++-------------------- 1 file changed, 69 insertions(+), 101 deletions(-) diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index bf088394..99551f8b 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -28,16 +28,6 @@ use tokio::{ type Tx = Option>>; -#[macro_use] -mod macros { - /// Pushes an update to a queue. - macro_rules! send { - ($requester:expr, $tx:expr, $update:expr, $variant:expr) => { - send($requester, $tx, $update, stringify!($variant)); - }; - } -} - fn send<'a, R, Upd>(requester: &'a R, tx: &'a Tx, update: Upd, variant: &'static str) where Upd: Debug, @@ -355,7 +345,7 @@ where match res { Ok(_) | Err(ShuttingDown) => Ok(()), - Err(IsntRunning) => return Err(ShutdownError::IsntRunning), + Err(IsntRunning) => Err(ShutdownError::IsntRunning), Err(Running) => unreachable!(), } } @@ -382,99 +372,77 @@ where match update.kind { UpdateKind::Message(message) => { - send!(&self.requester, &self.messages_queue, message, UpdateKind::Message); - } - UpdateKind::EditedMessage(message) => { - send!( - &self.requester, - &self.edited_messages_queue, - message, - UpdateKind::EditedMessage - ); - } - UpdateKind::ChannelPost(post) => { - send!( - &self.requester, - &self.channel_posts_queue, - post, - UpdateKind::ChannelPost - ); - } - UpdateKind::EditedChannelPost(post) => { - send!( - &self.requester, - &self.edited_channel_posts_queue, - post, - UpdateKind::EditedChannelPost - ); - } - UpdateKind::InlineQuery(query) => { - send!( - &self.requester, - &self.inline_queries_queue, - query, - UpdateKind::InlineQuery - ); - } - UpdateKind::ChosenInlineResult(result) => { - send!( - &self.requester, - &self.chosen_inline_results_queue, - result, - UpdateKind::ChosenInlineResult - ); - } - UpdateKind::CallbackQuery(query) => { - send!( - &self.requester, - &self.callback_queries_queue, - query, - UpdateKind::CallbackQuer - ); - } - UpdateKind::ShippingQuery(query) => { - send!( - &self.requester, - &self.shipping_queries_queue, - query, - UpdateKind::ShippingQuery - ); - } - UpdateKind::PreCheckoutQuery(query) => { - send!( - &self.requester, - &self.pre_checkout_queries_queue, - query, - UpdateKind::PreCheckoutQuery - ); + send(&self.requester, &self.messages_queue, message, "UpdateKind::Message") } + UpdateKind::EditedMessage(message) => send( + &self.requester, + &self.edited_messages_queue, + message, + "UpdateKind::EditedMessage", + ), + UpdateKind::ChannelPost(post) => send( + &self.requester, + &self.channel_posts_queue, + post, + "UpdateKind::ChannelPost", + ), + UpdateKind::EditedChannelPost(post) => send( + &self.requester, + &self.edited_channel_posts_queue, + post, + "UpdateKind::EditedChannelPost", + ), + UpdateKind::InlineQuery(query) => send( + &self.requester, + &self.inline_queries_queue, + query, + "UpdateKind::InlineQuery", + ), + UpdateKind::ChosenInlineResult(result) => send( + &self.requester, + &self.chosen_inline_results_queue, + result, + "UpdateKind::ChosenInlineResult", + ), + UpdateKind::CallbackQuery(query) => send( + &self.requester, + &self.callback_queries_queue, + query, + "UpdateKind::CallbackQuer", + ), + UpdateKind::ShippingQuery(query) => send( + &self.requester, + &self.shipping_queries_queue, + query, + "UpdateKind::ShippingQuery", + ), + UpdateKind::PreCheckoutQuery(query) => send( + &self.requester, + &self.pre_checkout_queries_queue, + query, + "UpdateKind::PreCheckoutQuery", + ), UpdateKind::Poll(poll) => { - send!(&self.requester, &self.polls_queue, poll, UpdateKind::Poll); - } - UpdateKind::PollAnswer(answer) => { - send!( - &self.requester, - &self.poll_answers_queue, - answer, - UpdateKind::PollAnswer - ); - } - UpdateKind::MyChatMember(chat_member_updated) => { - send!( - &self.requester, - &self.my_chat_members_queue, - chat_member_updated, - UpdateKind::MyChatMember - ); - } - UpdateKind::ChatMember(chat_member_updated) => { - send!( - &self.requester, - &self.chat_members_queue, - chat_member_updated, - UpdateKind::MyChatMember - ); + send(&self.requester, &self.polls_queue, poll, "UpdateKind::Poll") } + UpdateKind::PollAnswer(answer) => send( + &self.requester, + &self.poll_answers_queue, + answer, + "UpdateKind::PollAnswer", + ), + UpdateKind::MyChatMember(chat_member_updated) => send( + &self.requester, + &self.my_chat_members_queue, + chat_member_updated, + "UpdateKind::MyChatMember", + ), + UpdateKind::ChatMember(chat_member_updated) => send( + &self.requester, + &self.chat_members_queue, + chat_member_updated, + "UpdateKind::MyChatMember", + ), } } } From a5192a9ecb97f2ae0486c2b7f5e9b8a3fbf6d016 Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 18 May 2021 18:42:33 +0300 Subject: [PATCH 091/131] Clippy --- src/dispatching/update_listeners.rs | 5 ++++- src/lib.rs | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/dispatching/update_listeners.rs b/src/dispatching/update_listeners.rs index 602e8c19..c6dc3e6a 100644 --- a/src/dispatching/update_listeners.rs +++ b/src/dispatching/update_listeners.rs @@ -219,6 +219,7 @@ where // Updates fetched last time. // // We need to store them here so we can drop stream without loosing state. + #[allow(clippy::type_complexity)] fetched: Option< iter::Map< iter::FilterMap< @@ -402,7 +403,9 @@ where Thf: Fn(&St) -> Option, { fn stop(&mut self) { - self.stop.take().map(|stop| stop(&mut self.state)); + if let Some(stop) = self.stop.take() { + stop(&mut self.state) + } } fn timeout_hint(&self) -> Option { diff --git a/src/lib.rs b/src/lib.rs index 7079bbce..a22f0731 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -54,6 +54,7 @@ // $ RUSTDOCFLAGS="--cfg docsrs -Znormalize-docs" cargo +nightly doc --open --all-features // ``` #![cfg_attr(all(docsrs, feature = "nightly"), feature(doc_cfg))] +#![allow(clippy::redundant_pattern_matching)] pub use dispatching::repls::{ commands_repl, commands_repl_with_listener, dialogues_repl, dialogues_repl_with_listener, repl, From 00093664c70c14d89a115a16557213fc7c59e038 Mon Sep 17 00:00:00 2001 From: Waffle Date: Wed, 19 May 2021 10:19:41 +0300 Subject: [PATCH 092/131] Remove use of unstable feature (duration_zero) The feature has been stabilized in 1.53. --- src/dispatching/dispatcher.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index 99551f8b..e538fa37 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -282,8 +282,11 @@ where const MIN_SHUTDOWN_CHECK_TIMEOUT: Duration = Duration::from_secs(1); + // FIXME: replace this by just Duration::ZERO once 1.53 will be released + const DZERO: Duration = Duration::from_secs(0); + let shutdown_check_timeout = - update_listener.timeout_hint().unwrap_or(Duration::ZERO) + MIN_SHUTDOWN_CHECK_TIMEOUT; + update_listener.timeout_hint().unwrap_or(DZERO) + MIN_SHUTDOWN_CHECK_TIMEOUT; if let Err(_) = self.shutdown_state.compare_exchange(IsntRunning, Running) { panic!("Dispatching is already running"); From a8306ec3ffdbb613aebf7f1e19fb8beff737a5a6 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Fri, 21 May 2021 13:27:50 +0300 Subject: [PATCH 093/131] Apply suggestions from code review Co-authored-by: Hirrolot --- src/dispatching/update_listeners.rs | 4 ++-- src/features.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dispatching/update_listeners.rs b/src/dispatching/update_listeners.rs index c6dc3e6a..0e56edd7 100644 --- a/src/dispatching/update_listeners.rs +++ b/src/dispatching/update_listeners.rs @@ -132,8 +132,8 @@ use teloxide_core::{ pub trait UpdateListener: for<'a> AsUpdateStream<'a, E> { /// Stop listening for updates. /// - /// This function is not guaranteed to have an immidiate effect. That is - /// some listners can return updates even after [`stop`] is called (e.g.: + /// This function is not guaranteed to have an immediate effect. That is + /// some listeners can return updates even after [`stop`] is called (e.g.: /// because of buffering). /// /// [`stop`]: UpdateListener::stop diff --git a/src/features.txt b/src/features.txt index ee738058..f6ef21ba 100644 --- a/src/features.txt +++ b/src/features.txt @@ -9,7 +9,7 @@ | `macros` | Re-exports macros from [`teloxide-macros`]. | | `native-tls` | Enables the [`native-tls`] TLS implementation (enabled by default). | | `rustls` | Enables the [`rustls`] TLS implementation. | -| `ctrlc_handler` | Enables [`Dispatcher::setup_ctrlc_handler`](dispatching::Dispatcher::setup_ctrlc_handler) function. | +| `ctrlc_handler` | Enables the [`Dispatcher::setup_ctrlc_handler`](dispatching::Dispatcher::setup_ctrlc_handler) function. | | `auto-send` | Enables the `AutoSend` bot adaptor. | | `cache-me` | Enables the `CacheMe` bot adaptor. | | `frunk` | Enables [`teloxide::utils::UpState`]. | From a1624783461a9de2629180d1c636aa14777bf8cb Mon Sep 17 00:00:00 2001 From: Waffle Date: Fri, 21 May 2021 23:58:46 +0300 Subject: [PATCH 094/131] Switch graceful shutdown to cancelation token-like system --- src/dispatching/dispatcher.rs | 21 ++- src/dispatching/mod.rs | 7 +- src/dispatching/stop_token.rs | 74 ++++++++++ src/dispatching/update_listeners.rs | 202 +++++++++++++--------------- 4 files changed, 179 insertions(+), 125 deletions(-) create mode 100644 src/dispatching/stop_token.rs diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index e538fa37..362d4157 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -1,6 +1,8 @@ use crate::{ dispatching::{ - update_listeners, update_listeners::UpdateListener, DispatcherHandler, UpdateWithCx, + stop_token::StopToken, + update_listeners::{self, UpdateListener}, + DispatcherHandler, UpdateWithCx, }, error_handlers::{ErrorHandler, LoggingErrorHandler}, }; @@ -288,6 +290,8 @@ where let shutdown_check_timeout = update_listener.timeout_hint().unwrap_or(DZERO) + MIN_SHUTDOWN_CHECK_TIMEOUT; + let mut stop_token = Some(update_listener.stop_token()); + if let Err(_) = self.shutdown_state.compare_exchange(IsntRunning, Running) { panic!("Dispatching is already running"); } @@ -305,19 +309,14 @@ where } if let ShuttingDown = self.shutdown_state.load() { - log::debug!("Start shutting down dispatching"); - break; + if let Some(token) = stop_token.take() { + log::debug!("Start shutting down dispatching"); + token.stop(); + } } } } - update_listener.stop(); - - update_listener - .as_stream() - .for_each(|upd| self.process_update(upd, &update_listener_error_handler)) - .await; - if let ShuttingDown = self.shutdown_state.load() { // Stopped because of a `shutdown` call. @@ -325,7 +324,7 @@ where self.shutdown_notify_back.notify_waiters(); log::debug!("Dispatching shut down"); } else { - log::debug!("Dispatching stopped (listner returned `None`)"); + log::debug!("Dispatching stopped (listener returned `None`)"); } self.shutdown_state.store(IsntRunning); diff --git a/src/dispatching/mod.rs b/src/dispatching/mod.rs index 651cae53..ee2adee5 100644 --- a/src/dispatching/mod.rs +++ b/src/dispatching/mod.rs @@ -46,11 +46,14 @@ //! [examples/dialogue_bot]: https://github.com/teloxide/teloxide/tree/master/examples/dialogue_bot pub mod dialogue; +pub mod stop_token; +pub mod update_listeners; + +pub(crate) mod repls; + mod dispatcher; mod dispatcher_handler; mod dispatcher_handler_rx_ext; -pub(crate) mod repls; -pub mod update_listeners; mod update_with_cx; pub use dispatcher::Dispatcher; diff --git a/src/dispatching/stop_token.rs b/src/dispatching/stop_token.rs new file mode 100644 index 00000000..faea1ed2 --- /dev/null +++ b/src/dispatching/stop_token.rs @@ -0,0 +1,74 @@ +use std::{future::Future, pin::Pin, task}; + +use futures::future::{pending, AbortHandle, Abortable, Pending}; + +/// A stop token allows you to stop listener. +/// +/// See also: [`UpdateListener::stop_token`]. +/// +/// [`UpdateListener::stop_token`]: +/// crate::dispatching::update_listeners::UpdateListener::stop_token +pub trait StopToken { + /// Stop the listener linked to this token. + fn stop(self); +} + +/// A stop token which does nothing. May be used in prototyping or in cases +/// where you do not care about graceful shutdowning. +pub struct Noop; + +impl StopToken for Noop { + fn stop(self) {} +} + +/// A stop token which corresponds to [`AsyncStopFlag`]. +#[derive(Clone)] +pub struct AsyncStopToken(AbortHandle); + +/// A flag which corresponds to [`AsyncStopToken`]. +/// +/// To know if stop token was used you can either repeatedly call [`is_stopped`] +/// or use this type as a `Future`. +/// +/// [`is_stopped`]: AsyncStopFlag::is_stopped +#[pin_project::pin_project] +pub struct AsyncStopFlag(#[pin] Abortable>); + +impl AsyncStopToken { + /// Create a new token/flag pair. + pub fn new_pair() -> (Self, AsyncStopFlag) { + let (handle, reg) = AbortHandle::new_pair(); + let token = Self(handle); + let flag = AsyncStopFlag(Abortable::new(pending(), reg)); + + (token, flag) + } +} + +impl StopToken for AsyncStopToken { + fn stop(self) { + self.0.abort() + } +} + +impl AsyncStopFlag { + /// Returns true if stop token linked to `self` was used. + pub fn is_stopped(&self) -> bool { + self.0.is_aborted() + } +} + +/// This future resolves when a stop token was used. +impl Future for AsyncStopFlag { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll { + self.project().0.poll(cx).map(|res| { + debug_assert!( + res.is_err(), + "Pending Future can't ever be resolved, so Abortable is only resolved when \ + canceled" + ); + }) + } +} diff --git a/src/dispatching/update_listeners.rs b/src/dispatching/update_listeners.rs index 0e56edd7..d74d36cf 100644 --- a/src/dispatching/update_listeners.rs +++ b/src/dispatching/update_listeners.rs @@ -103,15 +103,20 @@ //! [short]: https://en.wikipedia.org/wiki/Polling_(computer_science) //! [webhook]: https://en.wikipedia.org/wiki/Webhook -use futures::{stream, Stream, StreamExt}; +use futures::{ + future::{ready, Either}, + stream, Stream, StreamExt, +}; -use std::{convert::TryInto, iter, time::Duration, vec}; +use std::{convert::TryInto, time::Duration}; use teloxide_core::{ payloads::GetUpdates, requests::{HasPayload, Request, Requester}, types::{AllowedUpdate, SemiparsedVec, Update}, }; +use crate::dispatching::stop_token::{AsyncStopFlag, AsyncStopToken, StopToken}; + /// An update listener. /// /// Implementors of this trait allow getting updates from Telegram. @@ -130,20 +135,33 @@ use teloxide_core::{ /// [`Stream`]: AsUpdateStream::Stream /// [`as_stream`]: AsUpdateStream::as_stream pub trait UpdateListener: for<'a> AsUpdateStream<'a, E> { - /// Stop listening for updates. + /// Type of token which allows ti stop this listener. + type StopToken: StopToken; + + /// Returns a token which stops this listener. /// - /// This function is not guaranteed to have an immediate effect. That is - /// some listeners can return updates even after [`stop`] is called (e.g.: - /// because of buffering). + /// The [`stop`] function of the token is not guaranteed to have an + /// immediate effect. That is some listeners can return updates even + /// after [`stop`] is called (e.g.: because of buffering). /// - /// [`stop`]: UpdateListener::stop + /// [`stop`]: StopToken::stop /// /// Implementors of this function are encouraged to stop listening for /// updates as soon as possible and return `None` from the update stream as /// soon as all cached updates are returned. - fn stop(&mut self); + #[must_use = "This function doesn't stop listening, to stop listening you need to call stop on \ + the returned token"] + fn stop_token(&mut self) -> Self::StopToken; /// Timeout duration hint. + /// + /// This hints how often dispatcher should check for shutdown. E.g. for + /// [`polling`] this returns the [`timeout`]. + /// + /// [`timeout`]: crate::payloads::GetUpdates::timeout + /// + /// If you are implementing this trait and not sure what to return from this + /// function, just leave it with default implementation. fn timeout_hint(&self) -> Option { None } @@ -159,8 +177,6 @@ pub trait AsUpdateStream<'a, E> { /// Creates the update [`Stream`]. /// /// [`Stream`]: AsUpdateStream::Stream - /// - /// Returned stream **must not** be stateful. State must be kept in `self`. fn as_stream(&'a mut self) -> Self::Stream; } @@ -202,35 +218,14 @@ where R: Requester + 'static, ::GetUpdatesFaultTolerant: Send, { - enum RunningState { - Polling, - Stopping, - Stopped, - } - struct State { bot: B, timeout: Option, limit: Option, allowed_updates: Option>, offset: i32, - run_state: RunningState, - - // Updates fetched last time. - // - // We need to store them here so we can drop stream without loosing state. - #[allow(clippy::type_complexity)] - fetched: Option< - iter::Map< - iter::FilterMap< - vec::IntoIter>, - fn( - Result, - ) -> std::option::Option, - >, - fn(Update) -> Result, - >, - >, + flag: AsyncStopFlag, + token: AsyncStopToken, } fn stream(st: &mut State) -> impl Stream> + '_ @@ -238,105 +233,87 @@ where B: Requester, { stream::unfold(st, move |state| async move { - let State { timeout, limit, allowed_updates, bot, offset, run_state, fetched, .. } = - &mut *state; - - let fetched_is_none_or_empty = fetched - .as_ref() - .map(|f| matches!(f.size_hint(), (_lower, Some(0)))) - .unwrap_or(true); - - if fetched_is_none_or_empty { - match run_state { - RunningState::Polling => {} - RunningState::Stopped => return None, - RunningState::Stopping => { - let mut req = bot.get_updates_fault_tolerant(); - - req.payload_mut().0 = GetUpdates { - offset: Some(*offset), - timeout: *timeout, - limit: Some(1), - allowed_updates: allowed_updates.take(), - }; - - return match req.send().await { - Ok(_) => { - *run_state = RunningState::Stopped; - None - } - Err(err) => Some((stream::iter(Some(Err(err))), state)), - }; - } - } + let State { timeout, limit, allowed_updates, bot, offset, flag, .. } = &mut *state; + if flag.is_stopped() { let mut req = bot.get_updates_fault_tolerant(); + req.payload_mut().0 = GetUpdates { offset: Some(*offset), - timeout: *timeout, - limit: *limit, + timeout: Some(0), + limit: Some(1), allowed_updates: allowed_updates.take(), }; - let updates = match req.send().await { - Err(err) => return Some((stream::iter(Some(Err(err))), state)), - Ok(SemiparsedVec(updates)) => { - // Set offset to the last update's id + 1 - if let Some(upd) = updates.last() { - let id: i32 = match upd { - Ok(ok) => ok.id, - Err((value, _)) => value["update_id"] - .as_i64() - .expect("The 'update_id' field must always exist in Update") - .try_into() - .expect("update_id must be i32"), - }; + return match req.send().await { + Ok(_) => None, + Err(err) => Some((Either::Left(stream::once(ready(Err(err)))), state)), + }; + } - *offset = id + 1; - } + let mut req = bot.get_updates_fault_tolerant(); + req.payload_mut().0 = GetUpdates { + offset: Some(*offset), + timeout: *timeout, + limit: *limit, + allowed_updates: allowed_updates.take(), + }; - for update in &updates { - if let Err((value, e)) = update { - log::error!( + let updates = match req.send().await { + Err(err) => return Some((Either::Left(stream::once(ready(Err(err)))), state)), + Ok(SemiparsedVec(updates)) => { + // Set offset to the last update's id + 1 + if let Some(upd) = updates.last() { + let id: i32 = match upd { + Ok(ok) => ok.id, + Err((value, _)) => value["update_id"] + .as_i64() + .expect("The 'update_id' field must always exist in Update") + .try_into() + .expect("update_id must be i32"), + }; + + *offset = id + 1; + } + + for update in &updates { + if let Err((value, e)) = update { + log::error!( "Cannot parse an update.\nError: {:?}\nValue: {}\n\ This is a bug in teloxide-core, please open an issue here: \ https://github.com/teloxide/teloxide-core/issues.", e, value ); - } } - - updates - .into_iter() - .filter_map(Result::ok as fn(_) -> _) - .map(Ok as fn(_) -> _) } - }; - *fetched = Some(updates); - } + updates.into_iter().filter_map(Result::ok).map(Ok) + } + }; - Some((stream::iter(fetched.as_mut().and_then(|f| f.next())), state)) + Some((Either::Right(stream::iter(updates)), state)) }) .flatten() } + let (token, flag) = AsyncStopToken::new_pair(); + let state = State { bot: requester, timeout: timeout.map(|t| t.as_secs().try_into().expect("timeout is too big")), limit, allowed_updates, offset: 0, - run_state: RunningState::Polling, - fetched: None, + flag, + token, }; - let stop = Some(|st: &mut State<_>| st.run_state = RunningState::Stopping); + let stop = |st: &mut State<_>| st.token.clone(); let timeout_hint = Some(move |_: &State<_>| timeout); - StatefulListner { state, stream, stop, timeout_hint } + StatefulListener { state, stream, stop, timeout_hint } } async fn delete_webhook_if_setup(requester: &R) @@ -360,9 +337,9 @@ where } } -/// A listner created from `state` and `stream`/`stop` functions. -struct StatefulListner { - /// The state of the listner. +/// A listener created from `state` and `stream`/`stop` functions. +struct StatefulListener { + /// The state of the listener. state: St, /// Function used as `AsUpdateStream::as_stream`. @@ -371,19 +348,19 @@ struct StatefulListner { /// `&mut`. stream: Assf, - /// Function used as `UpdateListner::stop`. + /// Function used as `UpdateListener::stop`. /// - /// Must be of type `for<'a> &'a mut St`. - stop: Option, + /// Must be of type `for<'a> &'a mut St -> impl StopToken`. + stop: Sf, - /// Function used as `UpdateListner::timeout_hint`. + /// Function used as `UpdateListener::timeout_hint`. /// /// Must be of type `for<'a> &'a St -> Option` and callable by /// `&`. timeout_hint: Option, } -impl<'a, St, Assf, Sf, Thf, Strm, E> AsUpdateStream<'a, E> for StatefulListner +impl<'a, St, Assf, Sf, Thf, Strm, E> AsUpdateStream<'a, E> for StatefulListener where (St, Strm): 'a, Assf: FnMut(&'a mut St) -> Strm, @@ -396,16 +373,17 @@ where } } -impl UpdateListener for StatefulListner +impl UpdateListener for StatefulListener where Self: for<'a> AsUpdateStream<'a, E>, - Sf: FnOnce(&mut St), + Sf: FnMut(&mut St) -> Stt, + Stt: StopToken, Thf: Fn(&St) -> Option, { - fn stop(&mut self) { - if let Some(stop) = self.stop.take() { - stop(&mut self.state) - } + type StopToken = Stt; + + fn stop_token(&mut self) -> Stt { + (self.stop)(&mut self.state) } fn timeout_hint(&self) -> Option { From 881aa3d6b6c2e18d0f1a6793a737f8107c74d5c9 Mon Sep 17 00:00:00 2001 From: Waffle Date: Sat, 22 May 2021 00:08:03 +0300 Subject: [PATCH 095/131] Split `update_listeners` into multiple modules --- src/dispatching/update_listeners.rs | 236 +----------------- src/dispatching/update_listeners/polling.rs | 173 +++++++++++++ .../update_listeners/stateful_listener.rs | 63 +++++ 3 files changed, 247 insertions(+), 225 deletions(-) create mode 100644 src/dispatching/update_listeners/polling.rs create mode 100644 src/dispatching/update_listeners/stateful_listener.rs diff --git a/src/dispatching/update_listeners.rs b/src/dispatching/update_listeners.rs index d74d36cf..1d9652bb 100644 --- a/src/dispatching/update_listeners.rs +++ b/src/dispatching/update_listeners.rs @@ -96,33 +96,30 @@ //! //! [`UpdateListener`]: UpdateListener //! [`polling_default`]: polling_default -//! [`polling`]: polling +//! [`polling`]: polling() //! [`Box::get_updates`]: crate::requests::Requester::get_updates //! [getting updates]: https://core.telegram.org/bots/api#getting-updates //! [long]: https://en.wikipedia.org/wiki/Push_technology#Long_polling //! [short]: https://en.wikipedia.org/wiki/Polling_(computer_science) //! [webhook]: https://en.wikipedia.org/wiki/Webhook -use futures::{ - future::{ready, Either}, - stream, Stream, StreamExt, -}; +use futures::Stream; -use std::{convert::TryInto, time::Duration}; -use teloxide_core::{ - payloads::GetUpdates, - requests::{HasPayload, Request, Requester}, - types::{AllowedUpdate, SemiparsedVec, Update}, -}; +use std::time::Duration; -use crate::dispatching::stop_token::{AsyncStopFlag, AsyncStopToken, StopToken}; +use crate::{dispatching::stop_token::StopToken, types::Update}; + +mod polling; +mod stateful_listener; + +pub use self::polling::{polling, polling_default}; /// An update listener. /// /// Implementors of this trait allow getting updates from Telegram. /// /// Currently Telegram has 2 ways of getting updates -- [polling] and -/// [webhooks]. Currently, only the former one is implemented (see [`polling`] +/// [webhooks]. Currently, only the former one is implemented (see [`polling()`] /// and [`polling_default`]) /// /// Some functions of this trait are located in the supertrait @@ -156,7 +153,7 @@ pub trait UpdateListener: for<'a> AsUpdateStream<'a, E> { /// Timeout duration hint. /// /// This hints how often dispatcher should check for shutdown. E.g. for - /// [`polling`] this returns the [`timeout`]. + /// [`polling()`] this returns the [`timeout`]. /// /// [`timeout`]: crate::payloads::GetUpdates::timeout /// @@ -179,214 +176,3 @@ pub trait AsUpdateStream<'a, E> { /// [`Stream`]: AsUpdateStream::Stream fn as_stream(&'a mut self) -> Self::Stream; } - -/// Returns a long polling update listener with `timeout` of 10 seconds. -/// -/// See also: [`polling`](polling). -/// -/// ## Notes -/// -/// This function will automatically delete a webhook if it was set up. -pub async fn polling_default(requester: R) -> impl UpdateListener -where - R: Requester + 'static, - ::GetUpdatesFaultTolerant: Send, -{ - delete_webhook_if_setup(&requester).await; - polling(requester, Some(Duration::from_secs(10)), None, None) -} - -/// Returns a long/short polling update listener with some additional options. -/// -/// - `bot`: Using this bot, the returned update listener will receive updates. -/// - `timeout`: A timeout for polling. -/// - `limit`: Limits the number of updates to be retrieved at once. Values -/// between 1—100 are accepted. -/// - `allowed_updates`: A list the types of updates you want to receive. -/// See [`GetUpdates`] for defaults. -/// -/// See also: [`polling_default`](polling_default). -/// -/// [`GetUpdates`]: crate::payloads::GetUpdates -pub fn polling( - requester: R, - timeout: Option, - limit: Option, - allowed_updates: Option>, -) -> impl UpdateListener -where - R: Requester + 'static, - ::GetUpdatesFaultTolerant: Send, -{ - struct State { - bot: B, - timeout: Option, - limit: Option, - allowed_updates: Option>, - offset: i32, - flag: AsyncStopFlag, - token: AsyncStopToken, - } - - fn stream(st: &mut State) -> impl Stream> + '_ - where - B: Requester, - { - stream::unfold(st, move |state| async move { - let State { timeout, limit, allowed_updates, bot, offset, flag, .. } = &mut *state; - - if flag.is_stopped() { - let mut req = bot.get_updates_fault_tolerant(); - - req.payload_mut().0 = GetUpdates { - offset: Some(*offset), - timeout: Some(0), - limit: Some(1), - allowed_updates: allowed_updates.take(), - }; - - return match req.send().await { - Ok(_) => None, - Err(err) => Some((Either::Left(stream::once(ready(Err(err)))), state)), - }; - } - - let mut req = bot.get_updates_fault_tolerant(); - req.payload_mut().0 = GetUpdates { - offset: Some(*offset), - timeout: *timeout, - limit: *limit, - allowed_updates: allowed_updates.take(), - }; - - let updates = match req.send().await { - Err(err) => return Some((Either::Left(stream::once(ready(Err(err)))), state)), - Ok(SemiparsedVec(updates)) => { - // Set offset to the last update's id + 1 - if let Some(upd) = updates.last() { - let id: i32 = match upd { - Ok(ok) => ok.id, - Err((value, _)) => value["update_id"] - .as_i64() - .expect("The 'update_id' field must always exist in Update") - .try_into() - .expect("update_id must be i32"), - }; - - *offset = id + 1; - } - - for update in &updates { - if let Err((value, e)) = update { - log::error!( - "Cannot parse an update.\nError: {:?}\nValue: {}\n\ - This is a bug in teloxide-core, please open an issue here: \ - https://github.com/teloxide/teloxide-core/issues.", - e, - value - ); - } - } - - updates.into_iter().filter_map(Result::ok).map(Ok) - } - }; - - Some((Either::Right(stream::iter(updates)), state)) - }) - .flatten() - } - - let (token, flag) = AsyncStopToken::new_pair(); - - let state = State { - bot: requester, - timeout: timeout.map(|t| t.as_secs().try_into().expect("timeout is too big")), - limit, - allowed_updates, - offset: 0, - flag, - token, - }; - - let stop = |st: &mut State<_>| st.token.clone(); - - let timeout_hint = Some(move |_: &State<_>| timeout); - - StatefulListener { state, stream, stop, timeout_hint } -} - -async fn delete_webhook_if_setup(requester: &R) -where - R: Requester, -{ - let webhook_info = match requester.get_webhook_info().send().await { - Ok(ok) => ok, - Err(e) => { - log::error!("Failed to get webhook info: {:?}", e); - return; - } - }; - - let is_webhook_setup = !webhook_info.url.is_empty(); - - if is_webhook_setup { - if let Err(e) = requester.delete_webhook().send().await { - log::error!("Failed to delete a webhook: {:?}", e); - } - } -} - -/// A listener created from `state` and `stream`/`stop` functions. -struct StatefulListener { - /// The state of the listener. - state: St, - - /// Function used as `AsUpdateStream::as_stream`. - /// - /// Must be of type `for<'a> &'a mut St -> impl Stream + 'a` and callable by - /// `&mut`. - stream: Assf, - - /// Function used as `UpdateListener::stop`. - /// - /// Must be of type `for<'a> &'a mut St -> impl StopToken`. - stop: Sf, - - /// Function used as `UpdateListener::timeout_hint`. - /// - /// Must be of type `for<'a> &'a St -> Option` and callable by - /// `&`. - timeout_hint: Option, -} - -impl<'a, St, Assf, Sf, Thf, Strm, E> AsUpdateStream<'a, E> for StatefulListener -where - (St, Strm): 'a, - Assf: FnMut(&'a mut St) -> Strm, - Strm: Stream>, -{ - type Stream = Strm; - - fn as_stream(&'a mut self) -> Self::Stream { - (self.stream)(&mut self.state) - } -} - -impl UpdateListener for StatefulListener -where - Self: for<'a> AsUpdateStream<'a, E>, - Sf: FnMut(&mut St) -> Stt, - Stt: StopToken, - Thf: Fn(&St) -> Option, -{ - type StopToken = Stt; - - fn stop_token(&mut self) -> Stt { - (self.stop)(&mut self.state) - } - - fn timeout_hint(&self) -> Option { - self.timeout_hint.as_ref().and_then(|f| f(&self.state)) - } -} diff --git a/src/dispatching/update_listeners/polling.rs b/src/dispatching/update_listeners/polling.rs new file mode 100644 index 00000000..6d65dc45 --- /dev/null +++ b/src/dispatching/update_listeners/polling.rs @@ -0,0 +1,173 @@ +use std::{convert::TryInto, time::Duration}; + +use futures::{ + future::{ready, Either}, + stream::{self, Stream, StreamExt}, +}; + +use crate::{ + dispatching::{ + stop_token::{AsyncStopFlag, AsyncStopToken}, + update_listeners::{stateful_listener::StatefulListener, UpdateListener}, + }, + payloads::GetUpdates, + requests::{HasPayload, Request, Requester}, + types::{AllowedUpdate, SemiparsedVec, Update}, +}; + +/// Returns a long polling update listener with `timeout` of 10 seconds. +/// +/// See also: [`polling`](polling). +/// +/// ## Notes +/// +/// This function will automatically delete a webhook if it was set up. +pub async fn polling_default(requester: R) -> impl UpdateListener +where + R: Requester + 'static, + ::GetUpdatesFaultTolerant: Send, +{ + delete_webhook_if_setup(&requester).await; + polling(requester, Some(Duration::from_secs(10)), None, None) +} + +/// Returns a long/short polling update listener with some additional options. +/// +/// - `bot`: Using this bot, the returned update listener will receive updates. +/// - `timeout`: A timeout for polling. +/// - `limit`: Limits the number of updates to be retrieved at once. Values +/// between 1—100 are accepted. +/// - `allowed_updates`: A list the types of updates you want to receive. +/// See [`GetUpdates`] for defaults. +/// +/// See also: [`polling_default`](polling_default). +/// +/// [`GetUpdates`]: crate::payloads::GetUpdates +pub fn polling( + requester: R, + timeout: Option, + limit: Option, + allowed_updates: Option>, +) -> impl UpdateListener +where + R: Requester + 'static, + ::GetUpdatesFaultTolerant: Send, +{ + struct State { + bot: B, + timeout: Option, + limit: Option, + allowed_updates: Option>, + offset: i32, + flag: AsyncStopFlag, + token: AsyncStopToken, + } + + fn stream(st: &mut State) -> impl Stream> + '_ + where + B: Requester, + { + stream::unfold(st, move |state| async move { + let State { timeout, limit, allowed_updates, bot, offset, flag, .. } = &mut *state; + + if flag.is_stopped() { + let mut req = bot.get_updates_fault_tolerant(); + + req.payload_mut().0 = GetUpdates { + offset: Some(*offset), + timeout: Some(0), + limit: Some(1), + allowed_updates: allowed_updates.take(), + }; + + return match req.send().await { + Ok(_) => None, + Err(err) => Some((Either::Left(stream::once(ready(Err(err)))), state)), + }; + } + + let mut req = bot.get_updates_fault_tolerant(); + req.payload_mut().0 = GetUpdates { + offset: Some(*offset), + timeout: *timeout, + limit: *limit, + allowed_updates: allowed_updates.take(), + }; + + let updates = match req.send().await { + Err(err) => return Some((Either::Left(stream::once(ready(Err(err)))), state)), + Ok(SemiparsedVec(updates)) => { + // Set offset to the last update's id + 1 + if let Some(upd) = updates.last() { + let id: i32 = match upd { + Ok(ok) => ok.id, + Err((value, _)) => value["update_id"] + .as_i64() + .expect("The 'update_id' field must always exist in Update") + .try_into() + .expect("update_id must be i32"), + }; + + *offset = id + 1; + } + + for update in &updates { + if let Err((value, e)) = update { + log::error!( + "Cannot parse an update.\nError: {:?}\nValue: {}\n\ + This is a bug in teloxide-core, please open an issue here: \ + https://github.com/teloxide/teloxide-core/issues.", + e, + value + ); + } + } + + updates.into_iter().filter_map(Result::ok).map(Ok) + } + }; + + Some((Either::Right(stream::iter(updates)), state)) + }) + .flatten() + } + + let (token, flag) = AsyncStopToken::new_pair(); + + let state = State { + bot: requester, + timeout: timeout.map(|t| t.as_secs().try_into().expect("timeout is too big")), + limit, + allowed_updates, + offset: 0, + flag, + token, + }; + + let stop = |st: &mut State<_>| st.token.clone(); + + let timeout_hint = Some(move |_: &State<_>| timeout); + + StatefulListener { state, stream, stop, timeout_hint } +} + +async fn delete_webhook_if_setup(requester: &R) +where + R: Requester, +{ + let webhook_info = match requester.get_webhook_info().send().await { + Ok(ok) => ok, + Err(e) => { + log::error!("Failed to get webhook info: {:?}", e); + return; + } + }; + + let is_webhook_setup = !webhook_info.url.is_empty(); + + if is_webhook_setup { + if let Err(e) = requester.delete_webhook().send().await { + log::error!("Failed to delete a webhook: {:?}", e); + } + } +} diff --git a/src/dispatching/update_listeners/stateful_listener.rs b/src/dispatching/update_listeners/stateful_listener.rs new file mode 100644 index 00000000..8d6b2913 --- /dev/null +++ b/src/dispatching/update_listeners/stateful_listener.rs @@ -0,0 +1,63 @@ +use std::time::Duration; + +use futures::Stream; +use teloxide_core::types::Update; + +use crate::dispatching::{ + stop_token::StopToken, + update_listeners::{AsUpdateStream, UpdateListener}, +}; + +/// A listener created from `state` and `stream`/`stop` functions. +pub(crate) struct StatefulListener { + /// The state of the listener. + pub(crate) state: St, + + /// Function used as `AsUpdateStream::as_stream`. + /// + /// Must be of type `for<'a> &'a mut St -> impl Stream + 'a` and callable by + /// `&mut`. + pub(crate) stream: Assf, + + /// Function used as `UpdateListener::stop`. + /// + /// Must be of type `for<'a> &'a mut St -> impl StopToken`. + pub(crate) stop: Sf, + + /// Function used as `UpdateListener::timeout_hint`. + /// + /// Must be of type `for<'a> &'a St -> Option` and callable by + /// `&`. + pub(crate) timeout_hint: Option, +} + +impl<'a, St, Assf, Sf, Thf, Strm, E> AsUpdateStream<'a, E> for StatefulListener +where + (St, Strm): 'a, + Assf: FnMut(&'a mut St) -> Strm, + Strm: Stream>, +{ + type Stream = Strm; + + fn as_stream(&'a mut self) -> Self::Stream { + (self.stream)(&mut self.state) + } +} + +impl UpdateListener for StatefulListener +where + Self: for<'a> AsUpdateStream<'a, E>, + Sf: FnMut(&mut St) -> Stt, + Stt: StopToken, + Thf: Fn(&St) -> Option, +{ + type StopToken = Stt; + + fn stop_token(&mut self) -> Stt { + (self.stop)(&mut self.state) + } + + fn timeout_hint(&self) -> Option { + self.timeout_hint.as_ref().and_then(|f| f(&self.state)) + } +} From f58ae9b9ca82bc011a8e80a2efa0dd352b37a85b Mon Sep 17 00:00:00 2001 From: Waffle Date: Sat, 22 May 2021 00:37:16 +0300 Subject: [PATCH 096/131] Make StatefulListener pub --- src/dispatching/update_listeners.rs | 5 +- src/dispatching/update_listeners/polling.rs | 2 +- .../update_listeners/stateful_listener.rs | 77 ++++++++++++++++--- 3 files changed, 71 insertions(+), 13 deletions(-) diff --git a/src/dispatching/update_listeners.rs b/src/dispatching/update_listeners.rs index 1d9652bb..b8448023 100644 --- a/src/dispatching/update_listeners.rs +++ b/src/dispatching/update_listeners.rs @@ -112,7 +112,10 @@ use crate::{dispatching::stop_token::StopToken, types::Update}; mod polling; mod stateful_listener; -pub use self::polling::{polling, polling_default}; +pub use self::{ + polling::{polling, polling_default}, + stateful_listener::StatefulListener, +}; /// An update listener. /// diff --git a/src/dispatching/update_listeners/polling.rs b/src/dispatching/update_listeners/polling.rs index 6d65dc45..62cec6d6 100644 --- a/src/dispatching/update_listeners/polling.rs +++ b/src/dispatching/update_listeners/polling.rs @@ -148,7 +148,7 @@ where let timeout_hint = Some(move |_: &State<_>| timeout); - StatefulListener { state, stream, stop, timeout_hint } + StatefulListener { state, stream, stop_token: stop, timeout_hint } } async fn delete_webhook_if_setup(requester: &R) diff --git a/src/dispatching/update_listeners/stateful_listener.rs b/src/dispatching/update_listeners/stateful_listener.rs index 8d6b2913..9150daef 100644 --- a/src/dispatching/update_listeners/stateful_listener.rs +++ b/src/dispatching/update_listeners/stateful_listener.rs @@ -4,31 +4,79 @@ use futures::Stream; use teloxide_core::types::Update; use crate::dispatching::{ - stop_token::StopToken, + stop_token::{self, StopToken}, update_listeners::{AsUpdateStream, UpdateListener}, }; -/// A listener created from `state` and `stream`/`stop` functions. -pub(crate) struct StatefulListener { +/// A listener created from functions. +/// +/// This type allows to turn a stream of updates (+some additional functions) +/// into an [`UpdateListener`]. +/// +/// For an example of usage see [`polling`] +/// +/// [`polling`]: crate::dispatching::update_listeners::polling() +#[non_exhaustive] +pub struct StatefulListener { /// The state of the listener. - pub(crate) state: St, + pub state: St, - /// Function used as `AsUpdateStream::as_stream`. + /// Function used as [`AsUpdateStream::as_stream`]. /// /// Must be of type `for<'a> &'a mut St -> impl Stream + 'a` and callable by /// `&mut`. - pub(crate) stream: Assf, + pub stream: Assf, - /// Function used as `UpdateListener::stop`. + /// Function used as [`UpdateListener::stop_token`]. /// /// Must be of type `for<'a> &'a mut St -> impl StopToken`. - pub(crate) stop: Sf, + pub stop_token: Sf, - /// Function used as `UpdateListener::timeout_hint`. + /// Function used as [`UpdateListener::timeout_hint`]. /// /// Must be of type `for<'a> &'a St -> Option` and callable by /// `&`. - pub(crate) timeout_hint: Option, + pub timeout_hint: Option, +} + +impl StatefulListener { + /// Creates new stateful listener from it's components. + pub fn new(state: St, stream: Assf, stop_token: Sf, timeout_hint: Option) -> Self { + Self { state, stream, stop_token, timeout_hint } + } +} + +impl + StatefulListener< + S, + for<'a> fn(&'a mut S) -> &'a mut S, + for<'a> fn(&'a mut S) -> stop_token::Noop, + for<'a> fn(&'a S) -> Option, + > +where + S: Stream> + Unpin + 'static, +{ + /// Creates a new update listner from a stream of updates which ignore stop + /// signals. + /// + /// It won't be possible to ever stop this listener with stop token. + pub fn from_stream_without_graceful_shutdown(stream: S) -> Self { + let this = Self { + state: stream, + stream: |s| s, + stop_token: |_| stop_token::Noop, + timeout_hint: Some(|_| { + // FIXME: replace this by just Duration::MAX once 1.53 releases + // be released + const NANOS_PER_SEC: u32 = 1_000_000_000; + let dmax = Duration::new(u64::MAX, NANOS_PER_SEC - 1); + + Some(dmax) + }), + }; + + assert_update_listener(this) + } } impl<'a, St, Assf, Sf, Thf, Strm, E> AsUpdateStream<'a, E> for StatefulListener @@ -54,10 +102,17 @@ where type StopToken = Stt; fn stop_token(&mut self) -> Stt { - (self.stop)(&mut self.state) + (self.stop_token)(&mut self.state) } fn timeout_hint(&self) -> Option { self.timeout_hint.as_ref().and_then(|f| f(&self.state)) } } + +fn assert_update_listener(l: L) -> L +where + L: UpdateListener, +{ + l +} From 8785b8263cb4caebf212e2a66a19f73e653eb060 Mon Sep 17 00:00:00 2001 From: Waffle Date: Sat, 22 May 2021 00:37:53 +0300 Subject: [PATCH 097/131] Fix overflow in `dispatch_with_listener` --- src/dispatching/dispatcher.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index 362d4157..1cca7f09 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -287,8 +287,12 @@ where // FIXME: replace this by just Duration::ZERO once 1.53 will be released const DZERO: Duration = Duration::from_secs(0); - let shutdown_check_timeout = - update_listener.timeout_hint().unwrap_or(DZERO) + MIN_SHUTDOWN_CHECK_TIMEOUT; + let shutdown_check_timeout = update_listener.timeout_hint().unwrap_or(DZERO); + + // FIXME: replace this by just saturating_add once 1.53 will be released + let shutdown_check_timeout = shutdown_check_timeout + .checked_add(MIN_SHUTDOWN_CHECK_TIMEOUT) + .unwrap_or(shutdown_check_timeout); let mut stop_token = Some(update_listener.stop_token()); From 76dee997e0ee15321602cc99e77635cd6ea8e747 Mon Sep 17 00:00:00 2001 From: Waffle Date: Sat, 22 May 2021 01:15:32 +0300 Subject: [PATCH 098/131] Fix webhook examples (support graceful shutdown) --- examples/heroku_ping_pong_bot/src/main.rs | 24 +++++++++++++++------ examples/ngrok_ping_pong_bot/src/main.rs | 20 +++++++++++------ src/dispatching/update_listeners/polling.rs | 4 ++-- 3 files changed, 33 insertions(+), 15 deletions(-) diff --git a/examples/heroku_ping_pong_bot/src/main.rs b/examples/heroku_ping_pong_bot/src/main.rs index 8f02734a..ddb7769f 100644 --- a/examples/heroku_ping_pong_bot/src/main.rs +++ b/examples/heroku_ping_pong_bot/src/main.rs @@ -1,7 +1,7 @@ // The version of Heroku ping-pong-bot, which uses a webhook to receive updates // from Telegram, instead of long polling. -use teloxide::{dispatching::update_listeners, prelude::*, types::Update}; +use teloxide::{dispatching::{update_listeners::{self, StatefulListener}, stop_token::AsyncStopToken}, prelude::*, types::Update}; use std::{convert::Infallible, env, net::SocketAddr}; use tokio::sync::mpsc; @@ -20,8 +20,8 @@ async fn handle_rejection(error: warp::Rejection) -> Result(bot: AutoSend) -> impl update_listeners::UpdateListener { - // Heroku defines auto defines a port value +pub async fn webhook(bot: AutoSend) -> impl update_listeners::UpdateListener { + // Heroku auto defines a port value let teloxide_token = env::var("TELOXIDE_TOKEN").expect("TELOXIDE_TOKEN env variable missing"); let port: u16 = env::var("PORT") .expect("PORT env variable missing") @@ -48,11 +48,21 @@ pub async fn webhook<'a>(bot: AutoSend) -> impl update_listeners::UpdateLis }) .recover(handle_rejection); - let serve = warp::serve(server); + let (stop_token, stop_flag) = AsyncStopToken::new_pair(); - let address = format!("0.0.0.0:{}", port); - tokio::spawn(serve.run(address.parse::().unwrap())); - UnboundedReceiverStream::new(rx) + let addr = format!("0.0.0.0:{}", port).parse::().unwrap(); + let server = warp::serve(server); + let (_addr, fut) = server.bind_with_graceful_shutdown(addr, stop_flag); + + // You might want to use serve.key_path/serve.cert_path methods here to + // setup a self-signed TLS certificate. + + tokio::spawn(fut); + let stream = UnboundedReceiverStream::new(rx); + + fn streamf(state: &mut (S, T)) -> &mut S { &mut state.0 } + + StatefulListener::new((stream, stop_token), streamf, |state: &mut (_, AsyncStopToken)| state.1.clone(), None:: fn(&'a _) -> _>) } async fn run() { diff --git a/examples/ngrok_ping_pong_bot/src/main.rs b/examples/ngrok_ping_pong_bot/src/main.rs index a00c60ee..8863eb8f 100644 --- a/examples/ngrok_ping_pong_bot/src/main.rs +++ b/examples/ngrok_ping_pong_bot/src/main.rs @@ -1,7 +1,7 @@ // The version of ngrok ping-pong-bot, which uses a webhook to receive updates // from Telegram, instead of long polling. -use teloxide::{dispatching::update_listeners, prelude::*, types::Update}; +use teloxide::{dispatching::{update_listeners::{self, StatefulListener}, stop_token::AsyncStopToken}, prelude::*, types::Update}; use std::{convert::Infallible, net::SocketAddr}; use tokio::sync::mpsc; @@ -20,10 +20,10 @@ async fn handle_rejection(error: warp::Rejection) -> Result(bot: AutoSend) -> impl update_listeners::UpdateListener { +pub async fn webhook(bot: AutoSend) -> impl update_listeners::UpdateListener { // You might want to specify a self-signed certificate via .certificate // method on SetWebhook. - bot.set_webhook("Your HTTPS ngrok URL here. Get it by 'ngrok http 80'") + bot.set_webhook("Your HTTPS ngrok URL here. Get it by `ngrok http 80`") .await .expect("Cannot setup a webhook"); @@ -40,13 +40,21 @@ pub async fn webhook<'a>(bot: AutoSend) -> impl update_listeners::UpdateLis }) .recover(handle_rejection); - let serve = warp::serve(server); + let (stop_token, stop_flag) = AsyncStopToken::new_pair(); + + let addr = "127.0.0.1:80".parse::().unwrap(); + let server = warp::serve(server); + let (_addr, fut) = server.bind_with_graceful_shutdown(addr, stop_flag); // You might want to use serve.key_path/serve.cert_path methods here to // setup a self-signed TLS certificate. - tokio::spawn(serve.run("127.0.0.1:80".parse::().unwrap())); - UnboundedReceiverStream::new(rx) + tokio::spawn(fut); + let stream = UnboundedReceiverStream::new(rx); + + fn streamf(state: &mut (S, T)) -> &mut S { &mut state.0 } + + StatefulListener::new((stream, stop_token), streamf, |state: &mut (_, AsyncStopToken)| state.1.clone(), None:: fn(&'a _) -> _>) } async fn run() { diff --git a/src/dispatching/update_listeners/polling.rs b/src/dispatching/update_listeners/polling.rs index 62cec6d6..50f1626c 100644 --- a/src/dispatching/update_listeners/polling.rs +++ b/src/dispatching/update_listeners/polling.rs @@ -144,11 +144,11 @@ where token, }; - let stop = |st: &mut State<_>| st.token.clone(); + let stop_token = |st: &mut State<_>| st.token.clone(); let timeout_hint = Some(move |_: &State<_>| timeout); - StatefulListener { state, stream, stop_token: stop, timeout_hint } + StatefulListener { state, stream, stop_token, timeout_hint } } async fn delete_webhook_if_setup(requester: &R) From c378d6ef4e524da96718beec6f989e8ac51d1531 Mon Sep 17 00:00:00 2001 From: Waffle Date: Fri, 25 Jun 2021 17:24:31 +0300 Subject: [PATCH 099/131] Make `StatefulListener::new` a little more convinient Remove `timeout_hint` from the `StatefulListener::new` function. This parameter is confusing and is likely to be set to `None` in most cases. Add a `StatefulListener::new_with_timeout_hint` parameter which is the same as the old `StatefulListener::new`. --- examples/heroku_ping_pong_bot/src/main.rs | 2 +- examples/ngrok_ping_pong_bot/src/main.rs | 2 +- .../update_listeners/stateful_listener.rs | 14 +++++++++++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/examples/heroku_ping_pong_bot/src/main.rs b/examples/heroku_ping_pong_bot/src/main.rs index ddb7769f..2043ec49 100644 --- a/examples/heroku_ping_pong_bot/src/main.rs +++ b/examples/heroku_ping_pong_bot/src/main.rs @@ -62,7 +62,7 @@ pub async fn webhook(bot: AutoSend) -> impl update_listeners::UpdateListene fn streamf(state: &mut (S, T)) -> &mut S { &mut state.0 } - StatefulListener::new((stream, stop_token), streamf, |state: &mut (_, AsyncStopToken)| state.1.clone(), None:: fn(&'a _) -> _>) + StatefulListener::new((stream, stop_token), streamf, |state: &mut (_, AsyncStopToken)| state.1.clone()) } async fn run() { diff --git a/examples/ngrok_ping_pong_bot/src/main.rs b/examples/ngrok_ping_pong_bot/src/main.rs index 8863eb8f..8a35d5ad 100644 --- a/examples/ngrok_ping_pong_bot/src/main.rs +++ b/examples/ngrok_ping_pong_bot/src/main.rs @@ -54,7 +54,7 @@ pub async fn webhook(bot: AutoSend) -> impl update_listeners::UpdateListene fn streamf(state: &mut (S, T)) -> &mut S { &mut state.0 } - StatefulListener::new((stream, stop_token), streamf, |state: &mut (_, AsyncStopToken)| state.1.clone(), None:: fn(&'a _) -> _>) + StatefulListener::new((stream, stop_token), streamf, |state: &mut (_, AsyncStopToken)| state.1.clone()) } async fn run() { diff --git a/src/dispatching/update_listeners/stateful_listener.rs b/src/dispatching/update_listeners/stateful_listener.rs index 9150daef..b7a243c6 100644 --- a/src/dispatching/update_listeners/stateful_listener.rs +++ b/src/dispatching/update_listeners/stateful_listener.rs @@ -39,9 +39,21 @@ pub struct StatefulListener { pub timeout_hint: Option, } +impl StatefulListener fn(&'a St) -> Option> { + /// Creates new stateful listener from it's components. + pub fn new(state: St, stream: Assf, stop_token: Sf) -> Self { + Self { state, stream, stop_token, timeout_hint: None } + } +} + impl StatefulListener { /// Creates new stateful listener from it's components. - pub fn new(state: St, stream: Assf, stop_token: Sf, timeout_hint: Option) -> Self { + pub fn new_with_timeout_hint( + state: St, + stream: Assf, + stop_token: Sf, + timeout_hint: Option, + ) -> Self { Self { state, stream, stop_token, timeout_hint } } } From 569ef222fcc37b1b1d29ead76b9964dde20cf4e4 Mon Sep 17 00:00:00 2001 From: Waffle Date: Fri, 25 Jun 2021 17:41:02 +0300 Subject: [PATCH 100/131] Add dispatcher shutdown token This commit adds `ShutdownToken` which can be obrained throught `Dispatcher::shutdown_token` and then later be used to shutdown dispatching. --- src/dispatching/dispatcher.rs | 83 +++++++++++++++++++++++------------ 1 file changed, 54 insertions(+), 29 deletions(-) diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index 1cca7f09..069b016e 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -30,22 +30,6 @@ use tokio::{ type Tx = Option>>; -fn send<'a, R, Upd>(requester: &'a R, tx: &'a Tx, update: Upd, variant: &'static str) -where - Upd: Debug, - R: Requester + Clone, -{ - if let Some(tx) = tx { - if let Err(error) = tx.send(UpdateWithCx { requester: requester.clone(), update }) { - log::error!( - "The RX part of the {} channel is closed, but an update is received.\nError:{}\n", - variant, - error - ); - } - } -} - /// One dispatcher to rule them all. /// /// See the [module-level documentation](crate::dispatching) for the design @@ -68,7 +52,7 @@ pub struct Dispatcher { chat_members_queue: Tx, shutdown_state: Arc, - shutdown_notify_back: Notify, + shutdown_notify_back: Arc, } impl Dispatcher @@ -96,7 +80,7 @@ where shutdown_state: Arc::new(AtomicShutdownState { inner: AtomicU8::new(ShutdownState::IsntRunning as _), }), - shutdown_notify_back: Notify::new(), + shutdown_notify_back: Arc::new(Notify::new()), } } @@ -128,7 +112,7 @@ where tokio::signal::ctrl_c().await.expect("Failed to listen for ^C"); // If dispatcher wasn't running, then there is nothing to do - Self::shutdown_inner(&shutdown_state).ok(); + shutdown_inner(&shutdown_state).ok(); } }); @@ -340,19 +324,15 @@ where /// /// If you don't need to wait for shutdown, returned future can be ignored. pub fn shutdown(&self) -> Result + '_, ShutdownError> { - Self::shutdown_inner(&self.shutdown_state) + shutdown_inner(&self.shutdown_state) .map(|()| async move { self.shutdown_notify_back.notified().await }) } - fn shutdown_inner(shutdown_state: &AtomicShutdownState) -> Result<(), ShutdownError> { - use ShutdownState::*; - - let res = shutdown_state.compare_exchange(Running, ShuttingDown); - - match res { - Ok(_) | Err(ShuttingDown) => Ok(()), - Err(IsntRunning) => Err(ShutdownError::IsntRunning), - Err(Running) => unreachable!(), + /// Returns shutdown token, which can later be used to shutdown dispatching. + pub fn shutdown_token(&self) -> ShutdownToken { + ShutdownToken { + shutdown_state: Arc::clone(&self.shutdown_state), + shutdown_notify_back: Arc::clone(&self.shutdown_notify_back), } } @@ -454,6 +434,23 @@ where } } +pub struct ShutdownToken { + shutdown_state: Arc, + shutdown_notify_back: Arc, +} + +impl ShutdownToken { + /// Tries to shutdown dispatching. + /// + /// Returns error if this dispather isn't dispatching at the moment. + /// + /// If you don't need to wait for shutdown, returned future can be ignored. + pub fn shutdown(&self) -> Result + '_, ShutdownError> { + shutdown_inner(&self.shutdown_state) + .map(|()| async move { self.shutdown_notify_back.notified().await }) + } +} + #[derive(Debug)] pub enum ShutdownError { IsntRunning, @@ -505,3 +502,31 @@ impl ShutdownState { } } } + +fn shutdown_inner(shutdown_state: &AtomicShutdownState) -> Result<(), ShutdownError> { + use ShutdownState::*; + + let res = shutdown_state.compare_exchange(Running, ShuttingDown); + + match res { + Ok(_) | Err(ShuttingDown) => Ok(()), + Err(IsntRunning) => Err(ShutdownError::IsntRunning), + Err(Running) => unreachable!(), + } +} + +fn send<'a, R, Upd>(requester: &'a R, tx: &'a Tx, update: Upd, variant: &'static str) +where + Upd: Debug, + R: Requester + Clone, +{ + if let Some(tx) = tx { + if let Err(error) = tx.send(UpdateWithCx { requester: requester.clone(), update }) { + log::error!( + "The RX part of the {} channel is closed, but an update is received.\nError:{}\n", + variant, + error + ); + } + } +} From 7cfb207b447b34c98efa9556e73ad5ac9e82ad7b Mon Sep 17 00:00:00 2001 From: Waffle Date: Sat, 26 Jun 2021 14:33:10 +0300 Subject: [PATCH 101/131] Wait for handlers to finish before shutting down dispatcher This commit makes `Dispatcher::dispatch_with_listener` (and all it's derivatives like `Dispatcher::dispatch`, `teloxide::repl`) to wait for handlers to finish berfore shutting down. This commit also changes self-type for `Dispatcher::dispatch_with_listener` and `Dispatcher::dispatch` from `&Self` to `&mut Self` (this is required to wait for handlers to finish). Since unique reference is now required for listening,`Dispatcher::shutdown` function is removed. --- src/dispatching/dispatcher.rs | 60 ++++++++++++++++++++++++++--------- 1 file changed, 45 insertions(+), 15 deletions(-) diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index 069b016e..0cbdcefe 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -7,7 +7,7 @@ use crate::{ error_handlers::{ErrorHandler, LoggingErrorHandler}, }; use core::panic; -use futures::{Future, StreamExt}; +use futures::{stream::FuturesUnordered, Future, StreamExt}; use std::{ fmt::Debug, sync::{ @@ -25,6 +25,7 @@ use teloxide_core::{ }; use tokio::{ sync::{mpsc, Notify}, + task::JoinHandle, time::timeout, }; @@ -51,6 +52,8 @@ pub struct Dispatcher { my_chat_members_queue: Tx, chat_members_queue: Tx, + running_handlers: FuturesUnordered>, + shutdown_state: Arc, shutdown_notify_back: Arc, } @@ -77,6 +80,7 @@ where poll_answers_queue: None, my_chat_members_queue: None, chat_members_queue: None, + running_handlers: FuturesUnordered::new(), shutdown_state: Arc::new(AtomicShutdownState { inner: AtomicU8::new(ShutdownState::IsntRunning as _), }), @@ -86,17 +90,20 @@ where #[must_use] #[allow(clippy::unnecessary_wraps)] - fn new_tx(&self, h: H) -> Tx + fn new_tx(&mut self, h: H) -> Tx where H: DispatcherHandler + Send + 'static, Upd: Send + 'static, R: Send + 'static, { let (tx, rx) = mpsc::unbounded_channel(); - tokio::spawn(async move { + let join_handle = tokio::spawn(async move { let fut = h.handle(rx); fut.await; }); + + self.running_handlers.push(join_handle); + Some(tx) } @@ -111,6 +118,8 @@ where loop { tokio::signal::ctrl_c().await.expect("Failed to listen for ^C"); + log::debug!("^C receieved, trying to shutdown dispatcher"); + // If dispatcher wasn't running, then there is nothing to do shutdown_inner(&shutdown_state).ok(); } @@ -240,7 +249,13 @@ where /// /// The default parameters are a long polling update listener and log all /// errors produced by this listener). - pub async fn dispatch(&self) + /// + /// Please note that after shutting down (either because of [`shutdown`], + /// [ctrlc signal], or `update_listener` returning `None`) + /// + /// [`shutdown`]; ShutdownToken::shutdown + /// [ctrlc signal]: Dispatcher::setup_ctrlc_handler + pub async fn dispatch(&mut self) where R: Requester + Clone, ::GetUpdatesFaultTolerant: Send, @@ -254,8 +269,15 @@ where /// Starts your bot with custom `update_listener` and /// `update_listener_error_handler`. + /// + /// Please note that after shutting down (either because of [`shutdown`], + /// [ctrlc signal], or `update_listener` returning `None`) all handlers will + /// be gone. As such, to restart listening you need to re-add handlers. + /// + /// [`shutdown`]; ShutdownToken::shutdown + /// [ctrlc signal]: Dispatcher::setup_ctrlc_handler pub async fn dispatch_with_listener<'a, UListener, ListenerE, Eh>( - &'a self, + &'a mut self, mut update_listener: UListener, update_listener_error_handler: Arc, ) where @@ -305,6 +327,24 @@ where } } + // Drop all senders, so handlers can stop + self.messages_queue.take(); + self.edited_messages_queue.take(); + self.channel_posts_queue.take(); + self.edited_channel_posts_queue.take(); + self.inline_queries_queue.take(); + self.chosen_inline_results_queue.take(); + self.callback_queries_queue.take(); + self.shipping_queries_queue.take(); + self.pre_checkout_queries_queue.take(); + self.polls_queue.take(); + self.poll_answers_queue.take(); + self.my_chat_members_queue.take(); + self.chat_members_queue.take(); + + // Wait untill all handlers process all updates + self.running_handlers.by_ref().for_each(|_| async {}).await; + if let ShuttingDown = self.shutdown_state.load() { // Stopped because of a `shutdown` call. @@ -318,16 +358,6 @@ where self.shutdown_state.store(IsntRunning); } - /// Tries to shutdown dispatching. - /// - /// Returns error if this dispather isn't dispatching at the moment. - /// - /// If you don't need to wait for shutdown, returned future can be ignored. - pub fn shutdown(&self) -> Result + '_, ShutdownError> { - shutdown_inner(&self.shutdown_state) - .map(|()| async move { self.shutdown_notify_back.notified().await }) - } - /// Returns shutdown token, which can later be used to shutdown dispatching. pub fn shutdown_token(&self) -> ShutdownToken { ShutdownToken { From 8e3ef4ab67e796e7f1f196377a38e558b342592d Mon Sep 17 00:00:00 2001 From: Waffle Date: Sat, 26 Jun 2021 15:08:32 +0300 Subject: [PATCH 102/131] Minor `Dispatcher` cleanup --- src/dispatching/dispatcher.rs | 128 +++++++++++++++++++--------------- 1 file changed, 72 insertions(+), 56 deletions(-) diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index 0cbdcefe..a26e55c2 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -1,13 +1,3 @@ -use crate::{ - dispatching::{ - stop_token::StopToken, - update_listeners::{self, UpdateListener}, - DispatcherHandler, UpdateWithCx, - }, - error_handlers::{ErrorHandler, LoggingErrorHandler}, -}; -use core::panic; -use futures::{stream::FuturesUnordered, Future, StreamExt}; use std::{ fmt::Debug, sync::{ @@ -16,6 +6,17 @@ use std::{ }, time::Duration, }; + +use crate::{ + dispatching::{ + stop_token::StopToken, + update_listeners::{self, UpdateListener}, + DispatcherHandler, UpdateWithCx, + }, + error_handlers::{ErrorHandler, LoggingErrorHandler}, +}; + +use futures::{stream::FuturesUnordered, Future, StreamExt}; use teloxide_core::{ requests::Requester, types::{ @@ -81,15 +82,12 @@ where my_chat_members_queue: None, chat_members_queue: None, running_handlers: FuturesUnordered::new(), - shutdown_state: Arc::new(AtomicShutdownState { - inner: AtomicU8::new(ShutdownState::IsntRunning as _), - }), - shutdown_notify_back: Arc::new(Notify::new()), + shutdown_state: <_>::default(), + shutdown_notify_back: <_>::default(), } } #[must_use] - #[allow(clippy::unnecessary_wraps)] fn new_tx(&mut self, h: H) -> Tx where H: DispatcherHandler + Send + 'static, @@ -97,10 +95,7 @@ where R: Send + 'static, { let (tx, rx) = mpsc::unbounded_channel(); - let join_handle = tokio::spawn(async move { - let fut = h.handle(rx); - fut.await; - }); + let join_handle = tokio::spawn(h.handle(rx)); self.running_handlers.push(join_handle); @@ -251,7 +246,8 @@ where /// errors produced by this listener). /// /// Please note that after shutting down (either because of [`shutdown`], - /// [ctrlc signal], or `update_listener` returning `None`) + /// [ctrlc signal], or `update_listener` returning `None`) all handlers will + /// be gone. As such, to restart listening you need to re-add handlers. /// /// [`shutdown`]; ShutdownToken::shutdown /// [ctrlc signal]: Dispatcher::setup_ctrlc_handler @@ -260,11 +256,11 @@ where R: Requester + Clone, ::GetUpdatesFaultTolerant: Send, { - self.dispatch_with_listener( - update_listeners::polling_default(self.requester.clone()).await, - LoggingErrorHandler::with_custom_text("An error from the update listener"), - ) - .await; + let listener = update_listeners::polling_default(self.requester.clone()).await; + let error_handler = + LoggingErrorHandler::with_custom_text("An error from the update listener"); + + self.dispatch_with_listener(listener, error_handler).await; } /// Starts your bot with custom `update_listener` and @@ -288,22 +284,14 @@ where { use ShutdownState::*; - const MIN_SHUTDOWN_CHECK_TIMEOUT: Duration = Duration::from_secs(1); - - // FIXME: replace this by just Duration::ZERO once 1.53 will be released - const DZERO: Duration = Duration::from_secs(0); - - let shutdown_check_timeout = update_listener.timeout_hint().unwrap_or(DZERO); - - // FIXME: replace this by just saturating_add once 1.53 will be released - let shutdown_check_timeout = shutdown_check_timeout - .checked_add(MIN_SHUTDOWN_CHECK_TIMEOUT) - .unwrap_or(shutdown_check_timeout); - + let shutdown_check_timeout = shutdown_check_timeout_for(&update_listener); let mut stop_token = Some(update_listener.stop_token()); - if let Err(_) = self.shutdown_state.compare_exchange(IsntRunning, Running) { - panic!("Dispatching is already running"); + if let Err(actual) = self.shutdown_state.compare_exchange(IsntRunning, Running) { + unreachable!( + "Dispatching is already running: expected `IsntRunning` state, found `{:?}`", + actual + ); } { @@ -327,23 +315,7 @@ where } } - // Drop all senders, so handlers can stop - self.messages_queue.take(); - self.edited_messages_queue.take(); - self.channel_posts_queue.take(); - self.edited_channel_posts_queue.take(); - self.inline_queries_queue.take(); - self.chosen_inline_results_queue.take(); - self.callback_queries_queue.take(); - self.shipping_queries_queue.take(); - self.pre_checkout_queries_queue.take(); - self.polls_queue.take(); - self.poll_answers_queue.take(); - self.my_chat_members_queue.take(); - self.chat_members_queue.take(); - - // Wait untill all handlers process all updates - self.running_handlers.by_ref().for_each(|_| async {}).await; + self.wait_for_handlers().await; if let ShuttingDown = self.shutdown_state.load() { // Stopped because of a `shutdown` call. @@ -462,8 +434,32 @@ where } } } + + async fn wait_for_handlers(&mut self) { + log::debug!("Waiting for handlers to finish"); + + // Drop all senders, so handlers can stop + self.messages_queue.take(); + self.edited_messages_queue.take(); + self.channel_posts_queue.take(); + self.edited_channel_posts_queue.take(); + self.inline_queries_queue.take(); + self.chosen_inline_results_queue.take(); + self.callback_queries_queue.take(); + self.shipping_queries_queue.take(); + self.pre_checkout_queries_queue.take(); + self.polls_queue.take(); + self.poll_answers_queue.take(); + self.my_chat_members_queue.take(); + self.chat_members_queue.take(); + + // Wait untill all handlers finish + self.running_handlers.by_ref().for_each(|_| async {}).await; + } } +/// A token which can be used to shutdown dispatcher. +#[derive(Clone)] pub struct ShutdownToken { shutdown_state: Arc, shutdown_notify_back: Arc, @@ -481,6 +477,7 @@ impl ShutdownToken { } } +/// Error occured while trying to shutdown dispatcher. #[derive(Debug)] pub enum ShutdownError { IsntRunning, @@ -511,7 +508,14 @@ impl AtomicShutdownState { } } +impl Default for AtomicShutdownState { + fn default() -> Self { + Self { inner: AtomicU8::new(ShutdownState::IsntRunning as _) } + } +} + #[repr(u8)] +#[derive(Debug)] enum ShutdownState { Running, ShuttingDown, @@ -533,6 +537,18 @@ impl ShutdownState { } } +fn shutdown_check_timeout_for(update_listener: &impl UpdateListener) -> Duration { + const MIN_SHUTDOWN_CHECK_TIMEOUT: Duration = Duration::from_secs(1); + + // FIXME: replace this by just Duration::ZERO once 1.53 will be released + const DZERO: Duration = Duration::from_secs(0); + + let shutdown_check_timeout = update_listener.timeout_hint().unwrap_or(DZERO); + + // FIXME: replace this by just saturating_add once 1.53 will be released + shutdown_check_timeout.checked_add(MIN_SHUTDOWN_CHECK_TIMEOUT).unwrap_or(shutdown_check_timeout) +} + fn shutdown_inner(shutdown_state: &AtomicShutdownState) -> Result<(), ShutdownError> { use ShutdownState::*; From 49848abd614937f64023e1fa597acc60c79a3826 Mon Sep 17 00:00:00 2001 From: Waffle Date: Sat, 26 Jun 2021 15:33:33 +0300 Subject: [PATCH 103/131] Doc fixes --- src/dispatching/dispatcher.rs | 6 +++--- src/dispatching/mod.rs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index a26e55c2..820a0f17 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -104,7 +104,7 @@ where /// Setup `^C` handler which [`shutdown`]s dispatching. /// - /// [`shutdown`]: Dispatcher::shutdown + /// [`shutdown`]: ShutdownToken::shutdown #[cfg(feature = "ctrlc_handler")] #[cfg_attr(docsrs, doc(cfg(feature = "ctrlc_handler")))] pub fn setup_ctrlc_handler(self) -> Self { @@ -249,7 +249,7 @@ where /// [ctrlc signal], or `update_listener` returning `None`) all handlers will /// be gone. As such, to restart listening you need to re-add handlers. /// - /// [`shutdown`]; ShutdownToken::shutdown + /// [`shutdown`]: ShutdownToken::shutdown /// [ctrlc signal]: Dispatcher::setup_ctrlc_handler pub async fn dispatch(&mut self) where @@ -270,7 +270,7 @@ where /// [ctrlc signal], or `update_listener` returning `None`) all handlers will /// be gone. As such, to restart listening you need to re-add handlers. /// - /// [`shutdown`]; ShutdownToken::shutdown + /// [`shutdown`]: ShutdownToken::shutdown /// [ctrlc signal]: Dispatcher::setup_ctrlc_handler pub async fn dispatch_with_listener<'a, UListener, ListenerE, Eh>( &'a mut self, diff --git a/src/dispatching/mod.rs b/src/dispatching/mod.rs index ee2adee5..1d68bd41 100644 --- a/src/dispatching/mod.rs +++ b/src/dispatching/mod.rs @@ -27,7 +27,7 @@ //! that: //! - You are able to supply [`DialogueDispatcher`] as a handler. //! - You are able to supply functions that accept -//! [`tokio::sync::mpsc::UnboundedReceiver`] and return `Future` //! as a handler. //! //! Since they implement [`DispatcherHandler`] too. @@ -56,7 +56,7 @@ mod dispatcher_handler; mod dispatcher_handler_rx_ext; mod update_with_cx; -pub use dispatcher::Dispatcher; +pub use dispatcher::{Dispatcher, ShutdownError, ShutdownToken}; pub use dispatcher_handler::DispatcherHandler; pub use dispatcher_handler_rx_ext::DispatcherHandlerRxExt; use tokio::sync::mpsc::UnboundedReceiver; From 1795cb22c2bb243100420c74eee17f9cd8672809 Mon Sep 17 00:00:00 2001 From: Waffle Date: Sat, 26 Jun 2021 17:19:56 +0300 Subject: [PATCH 104/131] setup ^C handler in repls --- Cargo.toml | 2 +- src/dispatching/repls/commands_repl.rs | 3 +++ src/dispatching/repls/dialogues_repl.rs | 3 +++ src/dispatching/repls/repl.rs | 3 +++ 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ec2eba2d..dd93a201 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ authors = [ maintenance = { status = "actively-developed" } [features] -default = ["native-tls", "teloxide-core/default"] +default = ["native-tls", "ctrlc_handler", "teloxide-core/default"] sqlite-storage = ["sqlx"] redis-storage = ["redis"] diff --git a/src/dispatching/repls/commands_repl.rs b/src/dispatching/repls/commands_repl.rs index 88a7c33d..a3625910 100644 --- a/src/dispatching/repls/commands_repl.rs +++ b/src/dispatching/repls/commands_repl.rs @@ -22,6 +22,7 @@ use tokio_stream::wrappers::UnboundedReceiverStream; /// /// [REPL]: https://en.wikipedia.org/wiki/Read-eval-print_loop /// [`Dispatcher`]: crate::dispatching::Dispatcher +#[cfg(feature = "ctrlc_handler")] pub async fn commands_repl(requester: R, bot_name: N, handler: H) where Cmd: BotCommand + Send + 'static, @@ -56,6 +57,7 @@ where /// [`Dispatcher`]: crate::dispatching::Dispatcher /// [`commands_repl`]: crate::dispatching::repls::commands_repl() /// [`UpdateListener`]: crate::dispatching::update_listeners::UpdateListener +#[cfg(feature = "ctrlc_handler")] pub async fn commands_repl_with_listener<'a, R, Cmd, H, Fut, L, ListenerE, HandlerE, N>( requester: R, bot_name: N, @@ -87,6 +89,7 @@ pub async fn commands_repl_with_listener<'a, R, Cmd, H, Fut, L, ListenerE, Handl }, ) }) + .setup_ctrlc_handler() .dispatch_with_listener( listener, LoggingErrorHandler::with_custom_text("An error from the update listener"), diff --git a/src/dispatching/repls/dialogues_repl.rs b/src/dispatching/repls/dialogues_repl.rs index 0fa00188..755d4dd3 100644 --- a/src/dispatching/repls/dialogues_repl.rs +++ b/src/dispatching/repls/dialogues_repl.rs @@ -23,6 +23,7 @@ use teloxide_core::{requests::Requester, types::Message}; /// [REPL]: https://en.wikipedia.org/wiki/Read-eval-print_loop /// [`Dispatcher`]: crate::dispatching::Dispatcher /// [`InMemStorage`]: crate::dispatching::dialogue::InMemStorage +#[cfg(feature = "ctrlc_handler")] pub async fn dialogues_repl<'a, R, H, D, Fut>(requester: R, handler: H) where H: Fn(UpdateWithCx, D) -> Fut + Send + Sync + 'static, @@ -55,6 +56,7 @@ where /// [`dialogues_repl`]: crate::dispatching::repls::dialogues_repl() /// [`UpdateListener`]: crate::dispatching::update_listeners::UpdateListener /// [`InMemStorage`]: crate::dispatching::dialogue::InMemStorage +#[cfg(feature = "ctrlc_handler")] pub async fn dialogues_repl_with_listener<'a, R, H, D, Fut, L, ListenerE>( requester: R, handler: H, @@ -85,6 +87,7 @@ pub async fn dialogues_repl_with_listener<'a, R, H, D, Fut, L, ListenerE>( } }, )) + .setup_ctrlc_handler() .dispatch_with_listener( listener, LoggingErrorHandler::with_custom_text("An error from the update listener"), diff --git a/src/dispatching/repls/repl.rs b/src/dispatching/repls/repl.rs index 3c498696..a298e768 100644 --- a/src/dispatching/repls/repl.rs +++ b/src/dispatching/repls/repl.rs @@ -21,6 +21,7 @@ use tokio_stream::wrappers::UnboundedReceiverStream; /// /// [REPL]: https://en.wikipedia.org/wiki/Read-eval-print_loop /// [`Dispatcher`]: crate::dispatching::Dispatcher +#[cfg(feature = "ctrlc_handler")] pub async fn repl(requester: R, handler: H) where H: Fn(UpdateWithCx) -> Fut + Send + Sync + 'static, @@ -51,6 +52,7 @@ where /// [`Dispatcher`]: crate::dispatching::Dispatcher /// [`repl`]: crate::dispatching::repls::repl() /// [`UpdateListener`]: crate::dispatching::update_listeners::UpdateListener +#[cfg(feature = "ctrlc_handler")] pub async fn repl_with_listener<'a, R, H, Fut, E, L, ListenerE>( requester: R, handler: H, @@ -76,6 +78,7 @@ pub async fn repl_with_listener<'a, R, H, Fut, E, L, ListenerE>( } }) }) + .setup_ctrlc_handler() .dispatch_with_listener( listener, LoggingErrorHandler::with_custom_text("An error from the update listener"), From 483e544223b569057a78861fcb847d70ecccbbcd Mon Sep 17 00:00:00 2001 From: Waffle Date: Sat, 26 Jun 2021 23:04:36 +0300 Subject: [PATCH 105/131] Apply suggestions from the review: rename AtomicShutdownState => DispatcherState, IsntRunning => Idle --- src/dispatching/dispatcher.rs | 49 ++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index 820a0f17..6a94258b 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -55,7 +55,7 @@ pub struct Dispatcher { running_handlers: FuturesUnordered>, - shutdown_state: Arc, + state: Arc, shutdown_notify_back: Arc, } @@ -82,7 +82,7 @@ where my_chat_members_queue: None, chat_members_queue: None, running_handlers: FuturesUnordered::new(), - shutdown_state: <_>::default(), + state: <_>::default(), shutdown_notify_back: <_>::default(), } } @@ -108,7 +108,7 @@ where #[cfg(feature = "ctrlc_handler")] #[cfg_attr(docsrs, doc(cfg(feature = "ctrlc_handler")))] pub fn setup_ctrlc_handler(self) -> Self { - let shutdown_state = Arc::clone(&self.shutdown_state); + let state = Arc::clone(&self.state); tokio::spawn(async move { loop { tokio::signal::ctrl_c().await.expect("Failed to listen for ^C"); @@ -116,7 +116,7 @@ where log::debug!("^C receieved, trying to shutdown dispatcher"); // If dispatcher wasn't running, then there is nothing to do - shutdown_inner(&shutdown_state).ok(); + shutdown_inner(&state).ok(); } }); @@ -287,10 +287,10 @@ where let shutdown_check_timeout = shutdown_check_timeout_for(&update_listener); let mut stop_token = Some(update_listener.stop_token()); - if let Err(actual) = self.shutdown_state.compare_exchange(IsntRunning, Running) { + if let Err(actual) = self.state.compare_exchange(Idle, Running) { unreachable!( - "Dispatching is already running: expected `IsntRunning` state, found `{:?}`", - actual + "Dispatching is already running: expected `{:?}` state, found `{:?}`", + Idle, actual ); } @@ -306,7 +306,7 @@ where } } - if let ShuttingDown = self.shutdown_state.load() { + if let ShuttingDown = self.state.load() { if let Some(token) = stop_token.take() { log::debug!("Start shutting down dispatching"); token.stop(); @@ -317,7 +317,7 @@ where self.wait_for_handlers().await; - if let ShuttingDown = self.shutdown_state.load() { + if let ShuttingDown = self.state.load() { // Stopped because of a `shutdown` call. // Notify `shutdown`s that we finished @@ -327,13 +327,13 @@ where log::debug!("Dispatching stopped (listener returned `None`)"); } - self.shutdown_state.store(IsntRunning); + self.state.store(Idle); } /// Returns shutdown token, which can later be used to shutdown dispatching. pub fn shutdown_token(&self) -> ShutdownToken { ShutdownToken { - shutdown_state: Arc::clone(&self.shutdown_state), + dispatcher_state: Arc::clone(&self.state), shutdown_notify_back: Arc::clone(&self.shutdown_notify_back), } } @@ -461,7 +461,7 @@ where /// A token which can be used to shutdown dispatcher. #[derive(Clone)] pub struct ShutdownToken { - shutdown_state: Arc, + dispatcher_state: Arc, shutdown_notify_back: Arc, } @@ -472,7 +472,7 @@ impl ShutdownToken { /// /// If you don't need to wait for shutdown, returned future can be ignored. pub fn shutdown(&self) -> Result + '_, ShutdownError> { - shutdown_inner(&self.shutdown_state) + shutdown_inner(&self.dispatcher_state) .map(|()| async move { self.shutdown_notify_back.notified().await }) } } @@ -480,14 +480,15 @@ impl ShutdownToken { /// Error occured while trying to shutdown dispatcher. #[derive(Debug)] pub enum ShutdownError { - IsntRunning, + /// Couldn"t stop dispatcher since it wasn't running. + Idle, } -struct AtomicShutdownState { +struct DispatcherState { inner: AtomicU8, } -impl AtomicShutdownState { +impl DispatcherState { fn load(&self) -> ShutdownState { ShutdownState::from_u8(self.inner.load(Ordering::SeqCst)) } @@ -508,9 +509,9 @@ impl AtomicShutdownState { } } -impl Default for AtomicShutdownState { +impl Default for DispatcherState { fn default() -> Self { - Self { inner: AtomicU8::new(ShutdownState::IsntRunning as _) } + Self { inner: AtomicU8::new(ShutdownState::Idle as _) } } } @@ -519,19 +520,19 @@ impl Default for AtomicShutdownState { enum ShutdownState { Running, ShuttingDown, - IsntRunning, + Idle, } impl ShutdownState { fn from_u8(n: u8) -> Self { const RUNNING: u8 = ShutdownState::Running as u8; const SHUTTING_DOWN: u8 = ShutdownState::ShuttingDown as u8; - const ISNT_RUNNING: u8 = ShutdownState::IsntRunning as u8; + const IDLE: u8 = ShutdownState::Idle as u8; match n { RUNNING => ShutdownState::Running, SHUTTING_DOWN => ShutdownState::ShuttingDown, - ISNT_RUNNING => ShutdownState::IsntRunning, + IDLE => ShutdownState::Idle, _ => unreachable!(), } } @@ -549,14 +550,14 @@ fn shutdown_check_timeout_for(update_listener: &impl UpdateListener) -> Du shutdown_check_timeout.checked_add(MIN_SHUTDOWN_CHECK_TIMEOUT).unwrap_or(shutdown_check_timeout) } -fn shutdown_inner(shutdown_state: &AtomicShutdownState) -> Result<(), ShutdownError> { +fn shutdown_inner(state: &DispatcherState) -> Result<(), ShutdownError> { use ShutdownState::*; - let res = shutdown_state.compare_exchange(Running, ShuttingDown); + let res = state.compare_exchange(Running, ShuttingDown); match res { Ok(_) | Err(ShuttingDown) => Ok(()), - Err(IsntRunning) => Err(ShutdownError::IsntRunning), + Err(Idle) => Err(ShutdownError::Idle), Err(Running) => unreachable!(), } } From 0347f9e62744612b1f80a0a9ba81030c1143a6a0 Mon Sep 17 00:00:00 2001 From: Waffle Date: Sat, 26 Jun 2021 23:21:44 +0300 Subject: [PATCH 106/131] Replace `ShutdownError` with `()` --- src/dispatching/dispatcher.rs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index 6a94258b..763c2c2e 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -468,22 +468,15 @@ pub struct ShutdownToken { impl ShutdownToken { /// Tries to shutdown dispatching. /// - /// Returns error if this dispather isn't dispatching at the moment. + /// Returns error if this dispather is idle at the moment. /// /// If you don't need to wait for shutdown, returned future can be ignored. - pub fn shutdown(&self) -> Result + '_, ShutdownError> { + pub fn shutdown(&self) -> Result + '_, ()> { shutdown_inner(&self.dispatcher_state) .map(|()| async move { self.shutdown_notify_back.notified().await }) } } -/// Error occured while trying to shutdown dispatcher. -#[derive(Debug)] -pub enum ShutdownError { - /// Couldn"t stop dispatcher since it wasn't running. - Idle, -} - struct DispatcherState { inner: AtomicU8, } From 558e7d5a7263529ef3660c0de5acfb6f2adfea36 Mon Sep 17 00:00:00 2001 From: Waffle Date: Sun, 27 Jun 2021 10:50:47 +0300 Subject: [PATCH 107/131] Add `IdleShutdownError` --- src/dispatching/dispatcher.rs | 21 +++++++++++++++++---- src/dispatching/mod.rs | 2 +- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index 763c2c2e..88c3aa1b 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -1,5 +1,5 @@ use std::{ - fmt::Debug, + fmt::{self, Debug}, sync::{ atomic::{AtomicU8, Ordering}, Arc, @@ -458,6 +458,19 @@ where } } +/// This error is returned from [`ShutdownToken::shutdown`] when trying to +/// shutdown idle dispatcher. +#[derive(Debug)] +pub struct IdleShutdownError; + +impl fmt::Display for IdleShutdownError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Dispatcher was idle and as such couldn't be shut down") + } +} + +impl std::error::Error for IdleShutdownError {} + /// A token which can be used to shutdown dispatcher. #[derive(Clone)] pub struct ShutdownToken { @@ -471,7 +484,7 @@ impl ShutdownToken { /// Returns error if this dispather is idle at the moment. /// /// If you don't need to wait for shutdown, returned future can be ignored. - pub fn shutdown(&self) -> Result + '_, ()> { + pub fn shutdown(&self) -> Result + '_, IdleShutdownError> { shutdown_inner(&self.dispatcher_state) .map(|()| async move { self.shutdown_notify_back.notified().await }) } @@ -543,14 +556,14 @@ fn shutdown_check_timeout_for(update_listener: &impl UpdateListener) -> Du shutdown_check_timeout.checked_add(MIN_SHUTDOWN_CHECK_TIMEOUT).unwrap_or(shutdown_check_timeout) } -fn shutdown_inner(state: &DispatcherState) -> Result<(), ShutdownError> { +fn shutdown_inner(state: &DispatcherState) -> Result<(), IdleShutdownError> { use ShutdownState::*; let res = state.compare_exchange(Running, ShuttingDown); match res { Ok(_) | Err(ShuttingDown) => Ok(()), - Err(Idle) => Err(ShutdownError::Idle), + Err(Idle) => Err(IdleShutdownError), Err(Running) => unreachable!(), } } diff --git a/src/dispatching/mod.rs b/src/dispatching/mod.rs index 1d68bd41..9936de96 100644 --- a/src/dispatching/mod.rs +++ b/src/dispatching/mod.rs @@ -56,7 +56,7 @@ mod dispatcher_handler; mod dispatcher_handler_rx_ext; mod update_with_cx; -pub use dispatcher::{Dispatcher, ShutdownError, ShutdownToken}; +pub use dispatcher::{Dispatcher, IdleShutdownError, ShutdownToken}; pub use dispatcher_handler::DispatcherHandler; pub use dispatcher_handler_rx_ext::DispatcherHandlerRxExt; use tokio::sync::mpsc::UnboundedReceiver; From 8796bc11baf0d83dd2af18e2515fe29fc2b6dece Mon Sep 17 00:00:00 2001 From: Waffle Date: Sun, 27 Jun 2021 11:04:55 +0300 Subject: [PATCH 108/131] Update changelog --- CHANGELOG.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a7b64c0..4ff0cb6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,8 +11,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Storage::get_dialogue` to obtain a dialogue indexed by a chat ID. - `InMemStorageError` with a single variant `DialogueNotFound` to be returned from `InMemStorage::remove_dialogue`. - `RedisStorageError::DialogueNotFound` and `SqliteStorageError::DialogueNotFound` to be returned from `Storage::remove_dialogue`. - - `Dispatcher::shutdown` function. + - A way to `shutdown` dispatcher + - `Dispatcher::shutdown_token` function. + - `ShutdownToken` with a `shutdown` function. - `Dispatcher::setup_ctrlc_handler` function ([issue 153](https://github.com/teloxide/teloxide/issues/153)). + - `IdleShutdownError` ### Changed @@ -22,11 +25,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Automatically delete a webhook if it was set up in `update_listeners::polling_default` (thereby making it `async`, [issue 319](https://github.com/teloxide/teloxide/issues/319)). - `polling` and `polling_default` now require `R: 'static` - Refactor `UpdateListener` trait: - - Add a `stop` function that allows stopping the listener ([issue 166](https://github.com/teloxide/teloxide/issues/166)). + - Add a `StopToken` associated type. + - It must implement a new `StopToken` trait which has the only function `fn stop(self);` + - Add a `stop_token` function that returns `Self::StopToken` and allows stopping the listener later ([issue 166](https://github.com/teloxide/teloxide/issues/166)). - Remove blanked implementation. - Remove `Stream` from super traits. - Add `AsUpdateStream` to super traits. - Add an `AsUpdateStream` trait that allows turning implementors into streams of updates (GAT workaround). + - Add a `timeout_hint` function (with a default implementation). + - `Dispatcher::dispatch` and `Dispatcher::dispatch_with_listener` now require mutable reference to self. + - Repls can now be stopped by `^C` signal. + - `Noop` and `AsyncStopToken`stop tokens. + - `StatefulListener`. ### Fixed From afe5a9716b0e21c903cbf1b3bdf04384012b521a Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Sun, 27 Jun 2021 14:57:02 +0600 Subject: [PATCH 109/131] Enhance the docs --- src/dispatching/dispatcher.rs | 28 +++++++++++-------- src/dispatching/stop_token.rs | 10 ++++--- src/dispatching/update_listeners.rs | 18 ++++++------ .../update_listeners/stateful_listener.rs | 20 ++++++------- 4 files changed, 40 insertions(+), 36 deletions(-) diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index 88c3aa1b..5dd64783 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -102,7 +102,7 @@ where Some(tx) } - /// Setup `^C` handler which [`shutdown`]s dispatching. + /// Setup the `^C` handler which [`shutdown`]s dispatching. /// /// [`shutdown`]: ShutdownToken::shutdown #[cfg(feature = "ctrlc_handler")] @@ -246,11 +246,12 @@ where /// errors produced by this listener). /// /// Please note that after shutting down (either because of [`shutdown`], - /// [ctrlc signal], or `update_listener` returning `None`) all handlers will - /// be gone. As such, to restart listening you need to re-add handlers. + /// [a ctrlc signal], or [`UpdateListener`] returning `None`) all handlers + /// will be gone. As such, to restart listening you need to re-add + /// handlers. /// /// [`shutdown`]: ShutdownToken::shutdown - /// [ctrlc signal]: Dispatcher::setup_ctrlc_handler + /// [a ctrlc signal]: Dispatcher::setup_ctrlc_handler pub async fn dispatch(&mut self) where R: Requester + Clone, @@ -267,11 +268,12 @@ where /// `update_listener_error_handler`. /// /// Please note that after shutting down (either because of [`shutdown`], - /// [ctrlc signal], or `update_listener` returning `None`) all handlers will - /// be gone. As such, to restart listening you need to re-add handlers. + /// [a ctrlc signal], or [`UpdateListener`] returning `None`) all handlers + /// will be gone. As such, to restart listening you need to re-add + /// handlers. /// /// [`shutdown`]: ShutdownToken::shutdown - /// [ctrlc signal]: Dispatcher::setup_ctrlc_handler + /// [a ctrlc signal]: Dispatcher::setup_ctrlc_handler pub async fn dispatch_with_listener<'a, UListener, ListenerE, Eh>( &'a mut self, mut update_listener: UListener, @@ -330,7 +332,8 @@ where self.state.store(Idle); } - /// Returns shutdown token, which can later be used to shutdown dispatching. + /// Returns a shutdown token, which can later be used to shutdown + /// dispatching. pub fn shutdown_token(&self) -> ShutdownToken { ShutdownToken { dispatcher_state: Arc::clone(&self.state), @@ -459,7 +462,7 @@ where } /// This error is returned from [`ShutdownToken::shutdown`] when trying to -/// shutdown idle dispatcher. +/// shutdown an idle [`Dispatcher`]. #[derive(Debug)] pub struct IdleShutdownError; @@ -471,7 +474,7 @@ impl fmt::Display for IdleShutdownError { impl std::error::Error for IdleShutdownError {} -/// A token which can be used to shutdown dispatcher. +/// A token which used to shutdown [`Dispatcher`]. #[derive(Clone)] pub struct ShutdownToken { dispatcher_state: Arc, @@ -481,9 +484,10 @@ pub struct ShutdownToken { impl ShutdownToken { /// Tries to shutdown dispatching. /// - /// Returns error if this dispather is idle at the moment. + /// Returns an error if the dispatcher is idle at the moment. /// - /// If you don't need to wait for shutdown, returned future can be ignored. + /// If you don't need to wait for shutdown, the returned future can be + /// ignored. pub fn shutdown(&self) -> Result + '_, IdleShutdownError> { shutdown_inner(&self.dispatcher_state) .map(|()| async move { self.shutdown_notify_back.notified().await }) diff --git a/src/dispatching/stop_token.rs b/src/dispatching/stop_token.rs index faea1ed2..4198d4c0 100644 --- a/src/dispatching/stop_token.rs +++ b/src/dispatching/stop_token.rs @@ -1,8 +1,10 @@ +//! A stop token used to stop a listener. + use std::{future::Future, pin::Pin, task}; use futures::future::{pending, AbortHandle, Abortable, Pending}; -/// A stop token allows you to stop listener. +/// A stop token allows you to stop a listener. /// /// See also: [`UpdateListener::stop_token`]. /// @@ -27,8 +29,8 @@ pub struct AsyncStopToken(AbortHandle); /// A flag which corresponds to [`AsyncStopToken`]. /// -/// To know if stop token was used you can either repeatedly call [`is_stopped`] -/// or use this type as a `Future`. +/// To know if the stop token was used you can either repeatedly call +/// [`is_stopped`] or use this type as a `Future`. /// /// [`is_stopped`]: AsyncStopFlag::is_stopped #[pin_project::pin_project] @@ -52,7 +54,7 @@ impl StopToken for AsyncStopToken { } impl AsyncStopFlag { - /// Returns true if stop token linked to `self` was used. + /// Returns true if the stop token linked to `self` was used. pub fn is_stopped(&self) -> bool { self.0.is_aborted() } diff --git a/src/dispatching/update_listeners.rs b/src/dispatching/update_listeners.rs index b8448023..3e2b1316 100644 --- a/src/dispatching/update_listeners.rs +++ b/src/dispatching/update_listeners.rs @@ -127,21 +127,19 @@ pub use self::{ /// /// Some functions of this trait are located in the supertrait /// ([`AsUpdateStream`]), see also: -/// - [`Stream`] -/// - [`as_stream`] +/// - [`AsUpdateStream::Stream`] +/// - [`AsUpdateStream::as_stream`] /// /// [polling]: self#long-polling /// [webhooks]: self#webhooks -/// [`Stream`]: AsUpdateStream::Stream -/// [`as_stream`]: AsUpdateStream::as_stream pub trait UpdateListener: for<'a> AsUpdateStream<'a, E> { - /// Type of token which allows ti stop this listener. + /// The type of token which allows to stop this listener. type StopToken: StopToken; /// Returns a token which stops this listener. /// /// The [`stop`] function of the token is not guaranteed to have an - /// immediate effect. That is some listeners can return updates even + /// immediate effect. That is, some listeners can return updates even /// after [`stop`] is called (e.g.: because of buffering). /// /// [`stop`]: StopToken::stop @@ -153,15 +151,15 @@ pub trait UpdateListener: for<'a> AsUpdateStream<'a, E> { the returned token"] fn stop_token(&mut self) -> Self::StopToken; - /// Timeout duration hint. + /// The timeout duration hint. /// - /// This hints how often dispatcher should check for shutdown. E.g. for + /// This hints how often dispatcher should check for a shutdown. E.g., for /// [`polling()`] this returns the [`timeout`]. /// /// [`timeout`]: crate::payloads::GetUpdates::timeout /// /// If you are implementing this trait and not sure what to return from this - /// function, just leave it with default implementation. + /// function, just leave it with the default implementation. fn timeout_hint(&self) -> Option { None } @@ -171,7 +169,7 @@ pub trait UpdateListener: for<'a> AsUpdateStream<'a, E> { /// /// This trait is a workaround to not require GAT. pub trait AsUpdateStream<'a, E> { - /// Stream of updates from Telegram. + /// The stream of updates from Telegram. type Stream: Stream> + 'a; /// Creates the update [`Stream`]. diff --git a/src/dispatching/update_listeners/stateful_listener.rs b/src/dispatching/update_listeners/stateful_listener.rs index b7a243c6..ac1f8655 100644 --- a/src/dispatching/update_listeners/stateful_listener.rs +++ b/src/dispatching/update_listeners/stateful_listener.rs @@ -10,10 +10,10 @@ use crate::dispatching::{ /// A listener created from functions. /// -/// This type allows to turn a stream of updates (+some additional functions) +/// 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::dispatching::update_listeners::polling() #[non_exhaustive] @@ -21,18 +21,18 @@ pub struct StatefulListener { /// The state of the listener. pub state: St, - /// Function used as [`AsUpdateStream::as_stream`]. + /// The function used as [`AsUpdateStream::as_stream`]. /// /// Must be of type `for<'a> &'a mut St -> impl Stream + 'a` and callable by /// `&mut`. pub stream: Assf, - /// Function used as [`UpdateListener::stop_token`]. + /// The function used as [`UpdateListener::stop_token`]. /// /// Must be of type `for<'a> &'a mut St -> impl StopToken`. pub stop_token: Sf, - /// Function used as [`UpdateListener::timeout_hint`]. + /// The function used as [`UpdateListener::timeout_hint`]. /// /// Must be of type `for<'a> &'a St -> Option` and callable by /// `&`. @@ -40,14 +40,14 @@ pub struct StatefulListener { } impl StatefulListener fn(&'a St) -> Option> { - /// Creates new stateful listener from it's components. + /// Creates a new stateful listener from its components. pub fn new(state: St, stream: Assf, stop_token: Sf) -> Self { Self { state, stream, stop_token, timeout_hint: None } } } impl StatefulListener { - /// Creates new stateful listener from it's components. + /// Creates a new stateful listener from its components. pub fn new_with_timeout_hint( state: St, stream: Assf, @@ -68,10 +68,10 @@ impl where S: Stream> + Unpin + 'static, { - /// Creates a new update listner from a stream of updates which ignore stop - /// signals. + /// Creates a new update listener from a stream of updates which ignores + /// stop signals. /// - /// It won't be possible to ever stop this listener with stop token. + /// It won't be possible to ever stop this listener with a stop token. pub fn from_stream_without_graceful_shutdown(stream: S) -> Self { let this = Self { state: stream, From a6c480930a31aabb1bbb54e9011a6687c22fa724 Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Sun, 27 Jun 2021 15:44:46 +0600 Subject: [PATCH 110/131] Print info about graceful shutdown to users --- CHANGELOG.md | 1 + src/dispatching/dispatcher.rs | 8 ++++---- src/logging.rs | 6 +++--- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ff0cb6c..9ce019ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Repls can now be stopped by `^C` signal. - `Noop` and `AsyncStopToken`stop tokens. - `StatefulListener`. + - Emit not only errors but also warnings and general information from teloxide, when set up by `enable_logging!`. ### Fixed diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index 5dd64783..5f1243cb 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -113,7 +113,7 @@ where loop { tokio::signal::ctrl_c().await.expect("Failed to listen for ^C"); - log::debug!("^C receieved, trying to shutdown dispatcher"); + log::info!("^C received, trying to shutdown the dispatcher..."); // If dispatcher wasn't running, then there is nothing to do shutdown_inner(&state).ok(); @@ -310,7 +310,7 @@ where if let ShuttingDown = self.state.load() { if let Some(token) = stop_token.take() { - log::debug!("Start shutting down dispatching"); + log::debug!("Start shutting down dispatching..."); token.stop(); } } @@ -324,9 +324,9 @@ where // Notify `shutdown`s that we finished self.shutdown_notify_back.notify_waiters(); - log::debug!("Dispatching shut down"); + log::info!("Dispatching has been shut down."); } else { - log::debug!("Dispatching stopped (listener returned `None`)"); + log::debug!("Dispatching has been stopped (listener returned `None`)."); } self.state.store(Idle); diff --git a/src/logging.rs b/src/logging.rs index a1a7e11d..4dddac89 100644 --- a/src/logging.rs +++ b/src/logging.rs @@ -1,7 +1,7 @@ /// Enables logging through [pretty-env-logger]. /// -/// A logger will **only** print errors from teloxide and **all** logs from -/// your program. +/// A logger will **only** print errors, warnings, and general information from +/// teloxide and **all** logs from your program. /// /// # Example /// ```no_compile @@ -46,7 +46,7 @@ macro_rules! enable_logging_with_filter { pretty_env_logger::formatted_builder() .write_style(pretty_env_logger::env_logger::WriteStyle::Auto) .filter(Some(&env!("CARGO_PKG_NAME").replace("-", "_")), $filter) - .filter(Some("teloxide"), log::LevelFilter::Error) + .filter(Some("teloxide"), log::LevelFilter::Info) .init(); }; } From c5ac8b5f9c2873fe2d4bad8c88036dcdf43d51cb Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Sun, 27 Jun 2021 16:36:36 +0600 Subject: [PATCH 111/131] Emit graceful shutdown info in all cases --- src/dispatching/dispatcher.rs | 8 +++++--- src/logging.rs | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index 5f1243cb..2deb38e2 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -326,7 +326,7 @@ where self.shutdown_notify_back.notify_waiters(); log::info!("Dispatching has been shut down."); } else { - log::debug!("Dispatching has been stopped (listener returned `None`)."); + log::info!("Dispatching has been stopped (listener returned `None`)."); } self.state.store(Idle); @@ -489,8 +489,10 @@ impl ShutdownToken { /// If you don't need to wait for shutdown, the returned future can be /// ignored. pub fn shutdown(&self) -> Result + '_, IdleShutdownError> { - shutdown_inner(&self.dispatcher_state) - .map(|()| async move { self.shutdown_notify_back.notified().await }) + shutdown_inner(&self.dispatcher_state).map(|()| async move { + log::info!("Trying to shutdown the dispatcher..."); + self.shutdown_notify_back.notified().await + }) } } diff --git a/src/logging.rs b/src/logging.rs index 4dddac89..fd7e5192 100644 --- a/src/logging.rs +++ b/src/logging.rs @@ -23,8 +23,8 @@ macro_rules! enable_logging { /// Enables logging through [pretty-env-logger] with a custom filter for your /// program. /// -/// A logger will **only** print errors from teloxide and restrict logs from -/// your program by the specified filter. +/// A logger will **only** print errors, warnings, and general information from +/// teloxide and restrict logs from your program by the specified filter. /// /// # Example /// Allow printing all logs from your program up to [`LevelFilter::Debug`] (i.e. From faef0c6ffbebce34c6b4fadff68d10a5ef437ecf Mon Sep 17 00:00:00 2001 From: Waffle Date: Sun, 27 Jun 2021 14:29:17 +0300 Subject: [PATCH 112/131] Implement automatic update filtering Add `UpdateListener::hint_allowed_updates` and `StatefulListener::hint_allowed_updates`. Make `Dispatcher` call `UpdateListener::hint_allowed_updates` when starting dispatching. --- CHANGELOG.md | 1 + src/dispatching/dispatcher.rs | 67 ++++++++++++++++++- src/dispatching/update_listeners.rs | 20 +++++- src/dispatching/update_listeners/polling.rs | 8 ++- .../update_listeners/stateful_listener.rs | 61 +++++++++++------ 5 files changed, 134 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ce019ad..07131416 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `ShutdownToken` with a `shutdown` function. - `Dispatcher::setup_ctrlc_handler` function ([issue 153](https://github.com/teloxide/teloxide/issues/153)). - `IdleShutdownError` + - Automatic update filtering ([issue 389](https://github.com/teloxide/teloxide/issues/389)). ### Changed diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index 5f1243cb..4c3e2a47 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -20,8 +20,8 @@ use futures::{stream::FuturesUnordered, Future, StreamExt}; use teloxide_core::{ requests::Requester, types::{ - CallbackQuery, ChatMemberUpdated, ChosenInlineResult, InlineQuery, Message, Poll, - PollAnswer, PreCheckoutQuery, ShippingQuery, Update, UpdateKind, + AllowedUpdate, CallbackQuery, ChatMemberUpdated, ChosenInlineResult, InlineQuery, Message, + Poll, PollAnswer, PreCheckoutQuery, ShippingQuery, Update, UpdateKind, }, }; use tokio::{ @@ -286,6 +286,8 @@ where { use ShutdownState::*; + self.hint_allowed_updates(&mut update_listener); + let shutdown_check_timeout = shutdown_check_timeout_for(&update_listener); let mut stop_token = Some(update_listener.stop_token()); @@ -438,6 +440,67 @@ where } } + fn hint_allowed_updates(&self, listener: &mut impl UpdateListener) { + let mut allowed = self + .messages_queue + .as_ref() + .map(|_| AllowedUpdate::Message) + .into_iter() + .chain( + self.edited_messages_queue + .as_ref() + .map(|_| AllowedUpdate::EditedMessage) + .into_iter(), + ) + .chain( + self.channel_posts_queue.as_ref().map(|_| AllowedUpdate::ChannelPost).into_iter(), + ) + .chain( + self.edited_channel_posts_queue + .as_ref() + .map(|_| AllowedUpdate::EditedChannelPost) + .into_iter(), + ) + .chain( + self.inline_queries_queue.as_ref().map(|_| AllowedUpdate::InlineQuery).into_iter(), + ) + .chain( + self.chosen_inline_results_queue + .as_ref() + .map(|_| AllowedUpdate::ChosenInlineResult) + .into_iter(), + ) + .chain( + self.callback_queries_queue + .as_ref() + .map(|_| AllowedUpdate::CallbackQuery) + .into_iter(), + ) + .chain( + self.shipping_queries_queue + .as_ref() + .map(|_| AllowedUpdate::ShippingQuery) + .into_iter(), + ) + .chain( + self.pre_checkout_queries_queue + .as_ref() + .map(|_| AllowedUpdate::PreCheckoutQuery) + .into_iter(), + ) + .chain(self.polls_queue.as_ref().map(|_| AllowedUpdate::Poll).into_iter()) + .chain(self.poll_answers_queue.as_ref().map(|_| AllowedUpdate::PollAnswer).into_iter()) + .chain( + self.my_chat_members_queue + .as_ref() + .map(|_| AllowedUpdate::MyChatMember) + .into_iter(), + ) + .chain(self.chat_members_queue.as_ref().map(|_| AllowedUpdate::ChatMember).into_iter()); + + listener.hint_allowed_updates(&mut allowed); + } + async fn wait_for_handlers(&mut self) { log::debug!("Waiting for handlers to finish"); diff --git a/src/dispatching/update_listeners.rs b/src/dispatching/update_listeners.rs index 3e2b1316..a0d9edca 100644 --- a/src/dispatching/update_listeners.rs +++ b/src/dispatching/update_listeners.rs @@ -107,7 +107,10 @@ use futures::Stream; use std::time::Duration; -use crate::{dispatching::stop_token::StopToken, types::Update}; +use crate::{ + dispatching::stop_token::StopToken, + types::{AllowedUpdate, Update}, +}; mod polling; mod stateful_listener; @@ -151,6 +154,21 @@ pub trait UpdateListener: for<'a> AsUpdateStream<'a, E> { the returned token"] fn stop_token(&mut self) -> Self::StopToken; + /// Hint which updates should the listener listen for. + /// + /// 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 + /// listener is not guaranteed to only return updates which types are listed + /// in the hint. + /// + /// [`GetUpdates::allowed_updates`]: + /// crate::payloads::GetUpdates::allowed_updates + fn hint_allowed_updates(&mut self, hint: &mut dyn Iterator) { + let _ = hint; + } + /// The timeout duration hint. /// /// This hints how often dispatcher should check for a shutdown. E.g., for diff --git a/src/dispatching/update_listeners/polling.rs b/src/dispatching/update_listeners/polling.rs index 50f1626c..5440fd6a 100644 --- a/src/dispatching/update_listeners/polling.rs +++ b/src/dispatching/update_listeners/polling.rs @@ -146,9 +146,15 @@ where let stop_token = |st: &mut State<_>| st.token.clone(); + let hint_allowed_updates = + Some(|state: &mut State<_>, allowed: &mut dyn Iterator| { + // TODO: we should probably warn if there already were different allowed updates + // before + state.allowed_updates = Some(allowed.collect()); + }); let timeout_hint = Some(move |_: &State<_>| timeout); - StatefulListener { state, stream, stop_token, timeout_hint } + StatefulListener::new_with_hints(state, stream, stop_token, hint_allowed_updates, timeout_hint) } async fn delete_webhook_if_setup(requester: &R) diff --git a/src/dispatching/update_listeners/stateful_listener.rs b/src/dispatching/update_listeners/stateful_listener.rs index ac1f8655..a9c26576 100644 --- a/src/dispatching/update_listeners/stateful_listener.rs +++ b/src/dispatching/update_listeners/stateful_listener.rs @@ -1,11 +1,13 @@ use std::time::Duration; use futures::Stream; -use teloxide_core::types::Update; -use crate::dispatching::{ - stop_token::{self, StopToken}, - update_listeners::{AsUpdateStream, UpdateListener}, +use crate::{ + dispatching::{ + stop_token::{self, StopToken}, + update_listeners::{AsUpdateStream, UpdateListener}, + }, + types::{AllowedUpdate, Update}, }; /// A listener created from functions. @@ -17,7 +19,7 @@ use crate::dispatching::{ /// /// [`polling`]: crate::dispatching::update_listeners::polling() #[non_exhaustive] -pub struct StatefulListener { +pub struct StatefulListener { /// The state of the listener. pub state: St, @@ -32,6 +34,12 @@ pub struct StatefulListener { /// Must be of type `for<'a> &'a mut St -> impl StopToken`. pub stop_token: Sf, + /// The function used as [`UpdateListener::hint_allowed_updates`]. + /// + /// Must be of type `for<'a, 'b> &'a mut St, &'b mut dyn Iterator -> ()`. + pub hint_allowed_updates: Option, + /// The function used as [`UpdateListener::timeout_hint`]. /// /// Must be of type `for<'a> &'a St -> Option` and callable by @@ -39,22 +47,26 @@ pub struct StatefulListener { pub timeout_hint: Option, } -impl StatefulListener fn(&'a St) -> Option> { +type Haufn = for<'a, 'b> fn(&'a mut State, &'b mut dyn Iterator); +type Thfn = for<'a> fn(&'a State) -> Option; + +impl StatefulListener, Thfn> { /// Creates a new stateful listener from its components. pub fn new(state: St, stream: Assf, stop_token: Sf) -> Self { - Self { state, stream, stop_token, timeout_hint: None } + Self::new_with_hints(state, stream, stop_token, None, None) } } -impl StatefulListener { +impl StatefulListener { /// Creates a new stateful listener from its components. - pub fn new_with_timeout_hint( + pub fn new_with_hints( state: St, stream: Assf, stop_token: Sf, + hint_allowed_updates: Option, timeout_hint: Option, ) -> Self { - Self { state, stream, stop_token, timeout_hint } + Self { state, stream, stop_token, hint_allowed_updates, timeout_hint } } } @@ -63,7 +75,8 @@ impl S, for<'a> fn(&'a mut S) -> &'a mut S, for<'a> fn(&'a mut S) -> stop_token::Noop, - for<'a> fn(&'a S) -> Option, + Haufn, + Thfn, > where S: Stream> + Unpin + 'static, @@ -73,11 +86,12 @@ where /// /// It won't be possible to ever stop this listener with a stop token. pub fn from_stream_without_graceful_shutdown(stream: S) -> Self { - let this = Self { - state: stream, - stream: |s| s, - stop_token: |_| stop_token::Noop, - timeout_hint: Some(|_| { + let this = Self::new_with_hints( + stream, + |s| s, + |_| stop_token::Noop, + None, + Some(|_| { // FIXME: replace this by just Duration::MAX once 1.53 releases // be released const NANOS_PER_SEC: u32 = 1_000_000_000; @@ -85,13 +99,14 @@ where Some(dmax) }), - }; + ); assert_update_listener(this) } } -impl<'a, St, Assf, Sf, Thf, Strm, E> AsUpdateStream<'a, E> for StatefulListener +impl<'a, St, Assf, Sf, Hauf, Thf, Strm, E> AsUpdateStream<'a, E> + for StatefulListener where (St, Strm): 'a, Assf: FnMut(&'a mut St) -> Strm, @@ -104,11 +119,13 @@ where } } -impl UpdateListener for StatefulListener +impl UpdateListener + for StatefulListener where Self: for<'a> AsUpdateStream<'a, E>, Sf: FnMut(&mut St) -> Stt, Stt: StopToken, + Hauf: FnMut(&mut St, &mut dyn Iterator), Thf: Fn(&St) -> Option, { type StopToken = Stt; @@ -117,6 +134,12 @@ where (self.stop_token)(&mut self.state) } + fn hint_allowed_updates(&mut self, hint: &mut dyn Iterator) { + if let Some(f) = &mut self.hint_allowed_updates { + f(&mut self.state, hint); + } + } + fn timeout_hint(&self) -> Option { self.timeout_hint.as_ref().and_then(|f| f(&self.state)) } From 7859294ff63c0eae239fca7c722fd16fe20a26ef Mon Sep 17 00:00:00 2001 From: Waffle Date: Sun, 27 Jun 2021 15:52:23 +0300 Subject: [PATCH 113/131] Apply suggestion from the review --- src/dispatching/dispatcher.rs | 106 ++++++++++++++++------------------ 1 file changed, 50 insertions(+), 56 deletions(-) diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index 4c3e2a47..4137e95f 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -441,62 +441,56 @@ where } fn hint_allowed_updates(&self, listener: &mut impl UpdateListener) { - let mut allowed = self - .messages_queue - .as_ref() - .map(|_| AllowedUpdate::Message) - .into_iter() - .chain( - self.edited_messages_queue - .as_ref() - .map(|_| AllowedUpdate::EditedMessage) - .into_iter(), - ) - .chain( - self.channel_posts_queue.as_ref().map(|_| AllowedUpdate::ChannelPost).into_iter(), - ) - .chain( - self.edited_channel_posts_queue - .as_ref() - .map(|_| AllowedUpdate::EditedChannelPost) - .into_iter(), - ) - .chain( - self.inline_queries_queue.as_ref().map(|_| AllowedUpdate::InlineQuery).into_iter(), - ) - .chain( - self.chosen_inline_results_queue - .as_ref() - .map(|_| AllowedUpdate::ChosenInlineResult) - .into_iter(), - ) - .chain( - self.callback_queries_queue - .as_ref() - .map(|_| AllowedUpdate::CallbackQuery) - .into_iter(), - ) - .chain( - self.shipping_queries_queue - .as_ref() - .map(|_| AllowedUpdate::ShippingQuery) - .into_iter(), - ) - .chain( - self.pre_checkout_queries_queue - .as_ref() - .map(|_| AllowedUpdate::PreCheckoutQuery) - .into_iter(), - ) - .chain(self.polls_queue.as_ref().map(|_| AllowedUpdate::Poll).into_iter()) - .chain(self.poll_answers_queue.as_ref().map(|_| AllowedUpdate::PollAnswer).into_iter()) - .chain( - self.my_chat_members_queue - .as_ref() - .map(|_| AllowedUpdate::MyChatMember) - .into_iter(), - ) - .chain(self.chat_members_queue.as_ref().map(|_| AllowedUpdate::ChatMember).into_iter()); + fn hint_handler_allowed_update( + queue: &Option, + kind: AllowedUpdate, + ) -> std::option::IntoIter { + queue.as_ref().map(|_| kind).into_iter() + } + + let mut allowed = hint_handler_allowed_update(&self.messages_queue, AllowedUpdate::Message) + .chain(hint_handler_allowed_update( + &self.edited_messages_queue, + AllowedUpdate::EditedMessage, + )) + .chain(hint_handler_allowed_update( + &self.channel_posts_queue, + AllowedUpdate::ChannelPost, + )) + .chain(hint_handler_allowed_update( + &self.edited_channel_posts_queue, + AllowedUpdate::EditedChannelPost, + )) + .chain(hint_handler_allowed_update( + &self.inline_queries_queue, + AllowedUpdate::InlineQuery, + )) + .chain(hint_handler_allowed_update( + &self.chosen_inline_results_queue, + AllowedUpdate::ChosenInlineResult, + )) + .chain(hint_handler_allowed_update( + &self.callback_queries_queue, + AllowedUpdate::CallbackQuery, + )) + .chain(hint_handler_allowed_update( + &self.shipping_queries_queue, + AllowedUpdate::ShippingQuery, + )) + .chain(hint_handler_allowed_update( + &self.pre_checkout_queries_queue, + AllowedUpdate::PreCheckoutQuery, + )) + .chain(hint_handler_allowed_update(&self.polls_queue, AllowedUpdate::Poll)) + .chain(hint_handler_allowed_update(&self.poll_answers_queue, AllowedUpdate::PollAnswer)) + .chain(hint_handler_allowed_update( + &self.my_chat_members_queue, + AllowedUpdate::MyChatMember, + )) + .chain(hint_handler_allowed_update( + &self.chat_members_queue, + AllowedUpdate::ChatMember, + )); listener.hint_allowed_updates(&mut allowed); } From 6bb18ea40b0e9c3c582bfe20caf84d4374c026e9 Mon Sep 17 00:00:00 2001 From: dracarys18 Date: Wed, 30 Jun 2021 17:53:33 +0530 Subject: [PATCH 114/131] Add reply shortcuts for every kind of message Signed-off-by: dracarys18 --- src/dispatching/update_with_cx.rs | 88 ++++++++++++++++++++++++++++++- 1 file changed, 87 insertions(+), 1 deletion(-) diff --git a/src/dispatching/update_with_cx.rs b/src/dispatching/update_with_cx.rs index 6ac78034..c0b60722 100644 --- a/src/dispatching/update_with_cx.rs +++ b/src/dispatching/update_with_cx.rs @@ -1,6 +1,11 @@ use crate::dispatching::dialogue::GetChatId; use teloxide_core::{ - payloads::SendMessageSetters, + payloads::{ + SendAnimationSetters, SendAudioSetters, SendContactSetters, SendDocumentSetters, + SendLocationSetters, SendMediaGroupSetters, SendMessageSetters, SendPhotoSetters, + SendStickerSetters, SendVenueSetters, SendVideoNoteSetters, SendVideoSetters, + SendVoiceSetters, + }, requests::{Request, Requester}, types::{ChatId, InputFile, InputMedia, Message}, }; @@ -64,6 +69,87 @@ where self.requester.send_message(self.chat_id(), text).reply_to_message_id(self.update.id) } + pub fn reply_audio(&self, audio: InputFile) -> R::SendAudio { + self.requester.send_audio(self.update.chat.id, audio).reply_to_message_id(self.update.id) + } + + pub fn reply_animation(&self, animation: InputFile) -> R::SendAnimation { + self.requester + .send_animation(self.update.chat.id, animation) + .reply_to_message_id(self.update.id) + } + + pub fn reply_document(&self, document: InputFile) -> R::SendDocument { + self.requester + .send_document(self.update.chat.id, document) + .reply_to_message_id(self.update.id) + } + + pub fn reply_photo(&self, photo: InputFile) -> R::SendPhoto { + self.requester.send_photo(self.update.chat.id, photo).reply_to_message_id(self.update.id) + } + + pub fn reply_video(&self, video: InputFile) -> R::SendVideo { + self.requester.send_video(self.update.chat.id, video).reply_to_message_id(self.update.id) + } + + pub fn reply_voice(&self, voice: InputFile) -> R::SendVoice { + self.requester.send_voice(self.update.chat.id, voice).reply_to_message_id(self.update.id) + } + + pub fn reply_media_group(&self, media_group: T) -> R::SendMediaGroup + where + T: IntoIterator, + { + self.requester + .send_media_group(self.update.chat.id, media_group) + .reply_to_message_id(self.update.id) + } + + pub fn reply_location(&self, latitude: f64, longitude: f64) -> R::SendLocation { + self.requester + .send_location(self.update.chat.id, latitude, longitude) + .reply_to_message_id(self.update.id) + } + + pub fn reply_venue( + &self, + latitude: f64, + longitude: f64, + title: T, + address: U, + ) -> R::SendVenue + where + T: Into, + U: Into, + { + self.requester + .send_venue(self.update.chat.id, latitude, longitude, title, address) + .reply_to_message_id(self.update.id) + } + + pub fn reply_video_note(&self, video_note: InputFile) -> R::SendVideoNote { + self.requester + .send_video_note(self.update.chat.id, video_note) + .reply_to_message_id(self.update.id) + } + + pub fn reply_contact(&self, phone_number: T, first_name: U) -> R::SendContact + where + T: Into, + U: Into, + { + self.requester + .send_contact(self.update.chat.id, phone_number, first_name) + .reply_to_message_id(self.update.id) + } + + pub fn reply_sticker(&self, sticker: InputFile) -> R::SendSticker { + self.requester + .send_sticker(self.update.chat.id, sticker) + .reply_to_message_id(self.update.id) + } + pub fn answer_photo(&self, photo: InputFile) -> R::SendPhoto { self.requester.send_photo(self.update.chat.id, photo) } From 9476636d73ac66dea8b29d0782ffd2d885855b1d Mon Sep 17 00:00:00 2001 From: dracarys18 Date: Wed, 30 Jun 2021 19:44:12 +0530 Subject: [PATCH 115/131] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07131416..4f390fb4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Dispatcher::setup_ctrlc_handler` function ([issue 153](https://github.com/teloxide/teloxide/issues/153)). - `IdleShutdownError` - Automatic update filtering ([issue 389](https://github.com/teloxide/teloxide/issues/389)). + - Added reply shortcut to every kind of messages. ### Changed From 0a89f467021108c8cea5d4a307de17f70915ce1c Mon Sep 17 00:00:00 2001 From: Kartikeya Hegde Date: Wed, 30 Jun 2021 19:47:43 +0530 Subject: [PATCH 116/131] Update CHANGELOG.md Co-authored-by: Hirrolot --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f390fb4..d4f575c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Dispatcher::setup_ctrlc_handler` function ([issue 153](https://github.com/teloxide/teloxide/issues/153)). - `IdleShutdownError` - Automatic update filtering ([issue 389](https://github.com/teloxide/teloxide/issues/389)). - - Added reply shortcut to every kind of messages. + - Added reply shortcut to every kind of messages ([PR 404](https://github.com/teloxide/teloxide/pull/404)). ### Changed From 1f1a3abd6bd3fc6cee4562e6a9e61c63da158f8f Mon Sep 17 00:00:00 2001 From: dracarys18 Date: Sat, 3 Jul 2021 17:31:57 +0530 Subject: [PATCH 117/131] utils: Change the user_id type to i64 user_id is of type i64 in User struct (https://github.com/teloxide/teloxide-core/blob/bd104a0a08568810b5850978419dd9c8f6a4b9b5/src/types/user.rs#L10) Signed-off-by: dracarys18 --- CHANGELOG.md | 1 + src/utils/html.rs | 2 +- src/utils/markdown.rs | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4f575c6..2303557b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Noop` and `AsyncStopToken`stop tokens. - `StatefulListener`. - Emit not only errors but also warnings and general information from teloxide, when set up by `enable_logging!`. + - `user_id` should be passed as i64 in `html::user_mention` and `markdown::user_mention`. ### Fixed diff --git a/src/utils/html.rs b/src/utils/html.rs index cf22acc4..dc26a299 100644 --- a/src/utils/html.rs +++ b/src/utils/html.rs @@ -44,7 +44,7 @@ pub fn link(url: &str, text: &str) -> String { } /// Builds an inline user mention link with an anchor. -pub fn user_mention(user_id: i32, text: &str) -> String { +pub fn user_mention(user_id: i64, text: &str) -> String { link(format!("tg://user?id={}", user_id).as_str(), text) } diff --git a/src/utils/markdown.rs b/src/utils/markdown.rs index f96fafaa..d778aee2 100644 --- a/src/utils/markdown.rs +++ b/src/utils/markdown.rs @@ -59,7 +59,7 @@ pub fn link(url: &str, text: &str) -> String { } /// Builds an inline user mention link with an anchor. -pub fn user_mention(user_id: i32, text: &str) -> String { +pub fn user_mention(user_id: i64, text: &str) -> String { link(format!("tg://user?id={}", user_id).as_str(), text) } From 39e0ee094a461732142cc82c551bea5f6d74863a Mon Sep 17 00:00:00 2001 From: Waffle Date: Sun, 4 Jul 2021 00:22:11 +0300 Subject: [PATCH 118/131] Ignore buggy nonstandard_macro_braces clippy lint --- src/lib.rs | 2 ++ tests/command.rs | 3 +++ 2 files changed, 5 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index a22f0731..2241c4a4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -55,6 +55,8 @@ // ``` #![cfg_attr(all(docsrs, feature = "nightly"), feature(doc_cfg))] #![allow(clippy::redundant_pattern_matching)] +// https://github.com/rust-lang/rust-clippy/issues/7422 +#![allow(clippy::nonstandard_macro_braces)] pub use dispatching::repls::{ commands_repl, commands_repl_with_listener, dialogues_repl, dialogues_repl_with_listener, repl, diff --git a/tests/command.rs b/tests/command.rs index deed8647..0a114911 100644 --- a/tests/command.rs +++ b/tests/command.rs @@ -1,3 +1,6 @@ +// https://github.com/rust-lang/rust-clippy/issues/7422 +#![allow(clippy::nonstandard_macro_braces)] + #[cfg(feature = "macros")] use teloxide::utils::command::{BotCommand, ParseError}; From f69abcc938d821041db0c5c6cc3dd6ad24d16dba Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Sun, 4 Jul 2021 10:29:46 +0300 Subject: [PATCH 119/131] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2303557b..3d42b5d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,7 +40,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Noop` and `AsyncStopToken`stop tokens. - `StatefulListener`. - Emit not only errors but also warnings and general information from teloxide, when set up by `enable_logging!`. - - `user_id` should be passed as i64 in `html::user_mention` and `markdown::user_mention`. + - Use `i64` instead of `i32` for `user_id` in `html::user_mention` and `markdown::user_mention`. ### Fixed From 369e43aa7ed1b192d326e6bdfe76f3560001353f Mon Sep 17 00:00:00 2001 From: Waffle Date: Mon, 5 Jul 2021 21:28:03 +0300 Subject: [PATCH 120/131] Update dependencies --- CHANGELOG.md | 1 + Cargo.toml | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d42b5d4..b06d07d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `StatefulListener`. - Emit not only errors but also warnings and general information from teloxide, when set up by `enable_logging!`. - Use `i64` instead of `i32` for `user_id` in `html::user_mention` and `markdown::user_mention`. + - Updated to `teloxide-core` `v0.3.0` (see it's [changelog](https://github.com/teloxide/teloxide-core/blob/master/CHANGELOG.md#030---2021-07-05) for more) ### Fixed diff --git a/Cargo.toml b/Cargo.toml index dd93a201..a9a8e3af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,14 +63,14 @@ full = [ ] [dependencies] -#teloxide-core = { version = "0.2.1", default-features = false } -teloxide-core = { git = "https://github.com/teloxide/teloxide-core.git", rev = "897ba7c941b651cf6b7e614b7d373d14426ed1da", default-features = false } +teloxide-core = { version = "0.3.0", default-features = false } +#teloxide-core = { git = "https://github.com/teloxide/teloxide-core.git", rev = "...", default-features = false } teloxide-macros = { version = "0.4", optional = true } serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } -tokio = { version = "1.2", features = ["fs"] } +tokio = { version = "1.8", features = ["fs"] } tokio-util = "0.6" tokio-stream = "0.1" @@ -94,14 +94,14 @@ sqlx = { version = "0.5", optional = true, default-features = false, features = redis = { version = "0.20", features = ["tokio-comp"], optional = true } serde_cbor = { version = "0.11", optional = true } bincode = { version = "1.3", optional = true } -frunk = { version = "0.3", optional = true } +frunk = { version = "0.4", optional = true } [dev-dependencies] smart-default = "0.6.0" rand = "0.8.3" pretty_env_logger = "0.4.0" lazy_static = "1.4.0" -tokio = { version = "1.2.0", features = ["fs", "rt-multi-thread", "macros"] } +tokio = { version = "1.8", features = ["fs", "rt-multi-thread", "macros"] } [package.metadata.docs.rs] all-features = true From 30644ab1628cf9632c941fd1c7cdfdbe29ca43c0 Mon Sep 17 00:00:00 2001 From: Waffle Date: Mon, 5 Jul 2021 22:16:29 +0300 Subject: [PATCH 121/131] Update `admin_bot` example --- examples/admin_bot/Cargo.toml | 1 + examples/admin_bot/src/main.rs | 40 +++++++++++++++++++++------------- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/examples/admin_bot/Cargo.toml b/examples/admin_bot/Cargo.toml index ccaf49c0..52c6f017 100644 --- a/examples/admin_bot/Cargo.toml +++ b/examples/admin_bot/Cargo.toml @@ -11,6 +11,7 @@ teloxide = { path = "../../", features = ["macros", "auto-send"] } log = "0.4.8" pretty_env_logger = "0.4.0" tokio = { version = "1.3.0", features = ["rt-multi-thread", "macros"] } +chrono = "0.4" [profile.release] lto = true diff --git a/examples/admin_bot/src/main.rs b/examples/admin_bot/src/main.rs index 0351f9bd..904c9369 100644 --- a/examples/admin_bot/src/main.rs +++ b/examples/admin_bot/src/main.rs @@ -1,8 +1,7 @@ -use std::{convert::TryInto, error::Error, str::FromStr}; +use std::{error::Error, str::FromStr}; -use teloxide::{prelude::*, utils::command::BotCommand}; - -use teloxide::types::ChatPermissions; +use chrono::{DateTime, Duration, NaiveDateTime, Utc}; +use teloxide::{prelude::*, types::{ChatPermissions, Me}, utils::command::BotCommand}; // Derive BotCommand to parse text with a command into this enumeration. // @@ -24,12 +23,12 @@ enum Command { Kick, #[command(description = "ban user in chat.")] Ban { - time: u32, + time: u64, unit: UnitOfTime, }, #[command(description = "mute user in chat.")] Mute { - time: u32, + time: u64, unit: UnitOfTime, }, Help, @@ -54,18 +53,18 @@ impl FromStr for UnitOfTime { } // Calculates time of user restriction. -fn calc_restrict_time(time: u32, unit: UnitOfTime) -> u32 { +fn calc_restrict_time(time: u64, unit: UnitOfTime) -> Duration { match unit { - UnitOfTime::Hours => time * 3600, - UnitOfTime::Minutes => time * 60, - UnitOfTime::Seconds => time, + UnitOfTime::Hours => Duration::hours(time as i64), + UnitOfTime::Minutes => Duration::minutes(time as i64), + UnitOfTime::Seconds => Duration::seconds(time as i64), } } type Cx = UpdateWithCx, Message>; // Mute a user with a replied message. -async fn mute_user(cx: &Cx, time: u32) -> Result<(), Box> { +async fn mute_user(cx: &Cx, time: Duration) -> Result<(), Box> { match cx.update.reply_to_message() { Some(msg1) => { cx.requester @@ -74,7 +73,12 @@ async fn mute_user(cx: &Cx, time: u32) -> Result<(), Box::from_utc( + NaiveDateTime::from_timestamp(cx.update.date as i64, 0), + Utc, + ) + time, + ) .await?; } None => { @@ -102,7 +106,7 @@ async fn kick_user(cx: &Cx) -> Result<(), Box> { } // Ban a user with replied message. -async fn ban_user(cx: &Cx, time: u32) -> Result<(), Box> { +async fn ban_user(cx: &Cx, time: Duration) -> Result<(), Box> { match cx.update.reply_to_message() { Some(message) => { cx.requester @@ -110,7 +114,12 @@ async fn ban_user(cx: &Cx, time: u32) -> Result<(), Box cx.update.chat_id(), message.from().expect("Must be MessageKind::Common").id, ) - .until_date((cx.update.date + time as i32).try_into().unwrap()) + .until_date( + DateTime::::from_utc( + NaiveDateTime::from_timestamp(cx.update.date as i64, 0), + Utc, + ) + time, + ) .await?; } None => { @@ -142,6 +151,7 @@ async fn run() { let bot = Bot::from_env().auto_send(); - let bot_name: String = panic!("Your bot's name here"); + let Me { user: bot_user, .. } = bot.get_me().await.unwrap(); + let bot_name = bot_user.username.expect("Bots must have usernames"); teloxide::commands_repl(bot, bot_name, action).await; } From 183486a04490d1904117ba2e16c96401a81e7abf Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 6 Jul 2021 15:10:22 +0300 Subject: [PATCH 122/131] Update heroku and ngrok examples --- examples/heroku_ping_pong_bot/src/main.rs | 4 ++-- examples/ngrok_ping_pong_bot/src/main.rs | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/examples/heroku_ping_pong_bot/src/main.rs b/examples/heroku_ping_pong_bot/src/main.rs index 2043ec49..653fc9f8 100644 --- a/examples/heroku_ping_pong_bot/src/main.rs +++ b/examples/heroku_ping_pong_bot/src/main.rs @@ -8,7 +8,7 @@ use tokio::sync::mpsc; use tokio_stream::wrappers::UnboundedReceiverStream; use warp::Filter; -use reqwest::StatusCode; +use reqwest::{StatusCode, Url}; #[tokio::main] async fn main() { @@ -30,7 +30,7 @@ pub async fn webhook(bot: AutoSend) -> impl update_listeners::UpdateListene // Heroku host example .: "heroku-ping-pong-bot.herokuapp.com" let host = env::var("HOST").expect("have HOST env variable"); let path = format!("bot{}", teloxide_token); - let url = format!("https://{}/{}", host, path); + let url = Url::parse(&format!("https://{}/{}", host, path)).unwrap(); bot.set_webhook(url).await.expect("Cannot setup a webhook"); diff --git a/examples/ngrok_ping_pong_bot/src/main.rs b/examples/ngrok_ping_pong_bot/src/main.rs index 8a35d5ad..c748e0c8 100644 --- a/examples/ngrok_ping_pong_bot/src/main.rs +++ b/examples/ngrok_ping_pong_bot/src/main.rs @@ -8,7 +8,7 @@ use tokio::sync::mpsc; use tokio_stream::wrappers::UnboundedReceiverStream; use warp::Filter; -use reqwest::StatusCode; +use reqwest::{StatusCode, Url}; #[tokio::main] async fn main() { @@ -21,9 +21,11 @@ async fn handle_rejection(error: warp::Rejection) -> Result) -> impl update_listeners::UpdateListener { + let url = Url::parse("Your HTTPS ngrok URL here. Get it by `ngrok http 80`").unwrap(); + // You might want to specify a self-signed certificate via .certificate // method on SetWebhook. - bot.set_webhook("Your HTTPS ngrok URL here. Get it by `ngrok http 80`") + bot.set_webhook(url) .await .expect("Cannot setup a webhook"); From 18f88cc034e97fd437c48930728c1d5d2da7a14d Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 6 Jul 2021 23:15:12 +0300 Subject: [PATCH 123/131] Update `dialogue_bot` example --- examples/dialogue_bot/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/dialogue_bot/Cargo.toml b/examples/dialogue_bot/Cargo.toml index 45f928ea..8d2ea09a 100644 --- a/examples/dialogue_bot/Cargo.toml +++ b/examples/dialogue_bot/Cargo.toml @@ -16,8 +16,8 @@ log = "0.4.8" pretty_env_logger = "0.4.0" derive_more = "0.99.9" -frunk = "0.3.1" -frunk_core = "0.3.1" +frunk = "0.4" +frunk_core = "0.4" [profile.release] lto = true From 81775a7217b97c382194cf3549dedf4ddb813a30 Mon Sep 17 00:00:00 2001 From: Waffle Date: Wed, 7 Jul 2021 20:27:38 +0300 Subject: [PATCH 124/131] Show "This is supported on feature="..." only." in socs of reexported teloxide-core items --- Cargo.toml | 3 ++- netlify.toml | 5 ++++- src/lib.rs | 6 ++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a9a8e3af..d86f5f0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,7 +63,7 @@ full = [ ] [dependencies] -teloxide-core = { version = "0.3.0", default-features = false } +teloxide-core = { version = "0.3.1", default-features = false } #teloxide-core = { git = "https://github.com/teloxide/teloxide-core.git", rev = "...", default-features = false } teloxide-macros = { version = "0.4", optional = true } @@ -106,6 +106,7 @@ tokio = { version = "1.8", features = ["fs", "rt-multi-thread", "macros"] } [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs", "-Znormalize-docs"] +rustc-args = ["--cfg", "dep_docsrs"] [[test]] name = "redis" diff --git a/netlify.toml b/netlify.toml index 7cd234d1..62ef6ccc 100644 --- a/netlify.toml +++ b/netlify.toml @@ -1,6 +1,9 @@ [build] command = "rustup install nightly --profile minimal && cargo +nightly doc --all-features --no-deps && cp -r target/doc _netlify_out" -environment = { RUSTDOCFLAGS= "--cfg docsrs -Znormalize-docs" } +environment = { + RUSTFLAGS="--cfg dep_docsrs", + RUSTDOCFLAGS= "--cfg docsrs -Znormalize-docs" +} publish = "_netlify_out" [[redirects]] diff --git a/src/lib.rs b/src/lib.rs index 2241c4a4..d09cda25 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,12 +46,14 @@ )] #![allow(clippy::match_bool)] #![forbid(unsafe_code)] -// we pass "--cfg docsrs" when building docs to add `This is supported on +// We pass "--cfg docsrs" when building docs to add `This is supported on // feature="..." only.` // +// "--cfg dep_docsrs" is used for the same reason, but for `teloxide-core`. +// // To properly build docs of this crate run // ```console -// $ RUSTDOCFLAGS="--cfg docsrs -Znormalize-docs" cargo +nightly doc --open --all-features +// $ RUSTFLAGS="--cfg dep_docsrs" RUSTDOCFLAGS="--cfg docsrs -Znormalize-docs" cargo +nightly doc --open --all-features // ``` #![cfg_attr(all(docsrs, feature = "nightly"), feature(doc_cfg))] #![allow(clippy::redundant_pattern_matching)] From 6698633023a60a900adfddf672d84395a7d3db14 Mon Sep 17 00:00:00 2001 From: Waffle Date: Wed, 7 Jul 2021 20:55:55 +0300 Subject: [PATCH 125/131] fix netlify config --- netlify.toml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/netlify.toml b/netlify.toml index 62ef6ccc..21b40399 100644 --- a/netlify.toml +++ b/netlify.toml @@ -1,9 +1,6 @@ [build] command = "rustup install nightly --profile minimal && cargo +nightly doc --all-features --no-deps && cp -r target/doc _netlify_out" -environment = { - RUSTFLAGS="--cfg dep_docsrs", - RUSTDOCFLAGS= "--cfg docsrs -Znormalize-docs" -} +environment = { RUSTFLAGS="--cfg dep_docsrs", RUSTDOCFLAGS= "--cfg docsrs -Znormalize-docs" } publish = "_netlify_out" [[redirects]] From bb0d31300379597380909078fcf2197bc83739a0 Mon Sep 17 00:00:00 2001 From: Waffle Date: Thu, 8 Jul 2021 02:21:12 +0300 Subject: [PATCH 126/131] Add migration guide --- MIGRATION_GUIDE.md | 162 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 MIGRATION_GUIDE.md diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md new file mode 100644 index 00000000..d945a6e6 --- /dev/null +++ b/MIGRATION_GUIDE.md @@ -0,0 +1,162 @@ +This document describes breaking changes of `teloxide` crate, as well as the ways to update code. +Note that the list of required changes is not fully exhaustive and it may lack something in rare cases. + +## 0.4 -> 0.5 + +### core + +#### Field type changes + +Types of some fields were changed to be more accurate. +If you used them, you may need to change types in your code too. + +Example: +```diff +let ps: PhotoSize = /* ... */; +-let w: i32 = ps.width; ++let w: u32 = ps.width; +``` + +List of changed types: +- `PhotoSoze::width`: `i32` -> `u32` +- `PhotoSoze::height`: `i32` -> `u32` +- `Restricted::until_date`: `i32` -> `DateTime` +- `Kicked::until_date` (`Banned::until_date`): `i32` -> `DateTime` +- `PublicChatSupergroup::slow_mode_delay`: `Option` -> `Option` +- `User::id`: `i32` -> `i64` (note: all methods which are accepting `user_id` were changed too) + + +#### Method output types + +In teloxide `v0.4` (core `v0.2`) some API methods had wrong return types. +This made them practically unusable as they've always returned parsing error. +On the offchance you were using the methods, you may need to adjust types in your code. + +List of changed return types: +- `get_chat_administrators`: `ChatMember` -> `Vec` +- `send_chat_action`: `Message` -> `True` +- `leave_chat`: `String` -> `True` +- `pin_chat_message`: `String` -> `True` +- `set_chat_description`: `String` -> `True` +- `set_chat_photo`: `String` -> `True` +- `set_chat_title`: `String` -> `True` +- `unpin_all_chat_messages`: `String` -> `True` +- `unpin_chat_message`: `String` -> `True` + + +#### Method parameter types + +Some API methods accept different types now. +If you've used changed parameters, you need to adjust code for new types. + +Examples: +```diff +let bot = Bot::new("TOKEN").auto_send(); + +-bot.set_webhook("url").await?; ++bot.set_webhook(Url::parse("url").unwrap()).await?; + +let link = bot + .create_chat_invite_link(chat_id) +- .expire_date(timestamp) +# Note: this is not the only way to create `DateTime`. Refer to `chrono` docs for more. ++ .expire_date(DateTime::::from_utc( ++ NaiveDateTime::from_timestamp(timestamp, 0), Utc) ++ ) + .await?; +``` + +See also: [teloxide examples fixes](https://github.com/teloxide/teloxide/pull/408/files/369e43aa7ed1b192d326e6bdfe76f3560001353f..18f88cc034e97fd437c48930728c1d5d2da7a14d). + +List of changed required params: +- `SetWebhook::url`: `String` -> `Url` + +List of changed optional params: +- `AnswerCallbackQuery::url`: `String` -> `Url` +- `SendInvoice::photo_url`: `String` -> `Url` +- `CreateChatInviteLink::expire_date`: `i64` -> `DateTime` +- `EditChatInviteLink::expire_date`: `i64` -> `DateTime` +- `KickChatMember::until_date`: `u64` -> `DateTime` +- `RestrictChatMember::until_date`: `u64` -> `DateTime` +- `SendPoll::close_date`: `u64` -> `DateTime` + + +#### Renamed items + +Some items (fields, variants, types, methods) were renamed. +If you used them, you should start using new names. + +Example: +```diff +-bot.send_chat_action(chat, ChatAction::RecordAudio).await?; ++bot.send_chat_action(chat, ChatAction::RecordVoice).await?; + +-if chat_member.is_kicked() { ++if chat_member.is_banned() { + /* ... */ +} +``` + +List of renamed items: +- `ChatAction::RecordAudio` -> `RecordVoice` +- `ChatAction::UploadAudio` -> `UploadVoice` +- `ChatMemberKind::Creator` -> `Owner` +- `ChatMemberKind::Kicked` -> `Banned` +- `Creator` -> `Owner` +- `Kicked` -> `Banned` +- `ChatMemberKind::is_Creator` -> `is_owner` * +- `ChatMemberKind::is_kicked` -> `is_banned` * +- `ChatMemberStatus::Creator` -> `Owner` +- `ChatMemberStatus::Kicked` -> `Banned` +- `kick_chat_member` -> `ban_chat_member` * +- `get_chat_members_count` -> `get_chat_member_count` * + +\* Old methods are still accessible, but deprecated + + +#### Added `impl Clone` for {`CacheMe`, `DefaultParseMode`, `Throttle`} + +Previously said bot adaptors were lacking `Clone` implementation. +To workaround this issue it was proposed to wrap bot in `Arc`. +Now it's not required, so you can remove the `Arc`: + +```diff +let bot = Bot::new(token).parse_mode(ParseMode::MarkdownV2); +-let bot = Arc::new(bot); +``` + + +### teloxide + +#### Mutable reference for dispatching + +`Dispatcher::dispatch` and `Dispatcher::dispatch_with_listener` now require mutable (unique) reference to self. +If you've used variable to store `Dispatcher`, you need to make it mutable: + +```diff +-let dp = Dispatcher::new(); ++let mut dp = Dispatcher::new(); +/* ... */ +dp.dispatch(); +``` + + +#### Listener refactor + +`UpdateListener` trait was refactored. +If you've used `polling`/`polling_default` provided by teloxide, no changes are required. +If, however, you've used or implemented `UpdateListener` directly or used a `Stream` as a listener, +then you need to refactor your code too. + +See also: [teloxide examples fixes](https://github.com/teloxide/teloxide/pull/385/files/8785b8263cb4caebf212e2a66a19f73e653eb060..c378d6ef4e524da96718beec6f989e8ac51d1531). + + +#### `polling_default` + +`polling_default` is now async, but removes webhook. + +Example fix: +```diff +-let listener = polling_default(bot); ++let listener = polling_default(bot).await; +``` From 674b1f974ad170f5e78c5f1e4320ddcabe94fc7f Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Thu, 8 Jul 2021 15:22:13 +0600 Subject: [PATCH 127/131] Prepare for v0.5.0 --- CHANGELOG.md | 2 ++ Cargo.toml | 5 +++-- README.md | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b06d07d4..ac7d6ca8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [unreleased] +## [0.5.0] - 2021-07-08 + ### Added - `Storage::get_dialogue` to obtain a dialogue indexed by a chat ID. diff --git a/Cargo.toml b/Cargo.toml index d86f5f0c..33560178 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,16 +1,17 @@ [package] name = "teloxide" -version = "0.4.0" +version = "0.5.0" edition = "2018" description = "An elegant Telegram bots framework for Rust" repository = "https://github.com/teloxide/teloxide" documentation = "https://docs.rs/teloxide/" readme = "README.md" keywords = ["teloxide", "telegram", "telegram-bot", "telegram-bot-api"] +categories = ["web-programming", "api-bindings"] license = "MIT" exclude = ["media"] authors = [ - "Temirkhan Myrzamadi ", + "Hirrolot ", "Waffle Lapkin ", "p0lunin ", "Mishko torop'izhko", diff --git a/README.md b/README.md index 088dd691..1189d1da 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Note: this list is non-exhaustive; for the full list of changes, see the [teloxi - + From f85c77159061a142e2091acca0e7c4f69a483314 Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Thu, 8 Jul 2021 15:24:55 +0600 Subject: [PATCH 128/131] Point to the migration guide from `README.md` --- README.md | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/README.md b/README.md index 1189d1da..32ef799e 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,4 @@ -
- FYI: Updating from v0.3.4 to v0.4.0 - - - `answer_str` -> `answer` - - `msg.text_owned()` -> `msg.map(ToOwned::to_owned)` - - Use `.auto_send()` to construct your bot: `let bot = Bot::from_env().auto_send();`. This allows not to write `.send()` after each request; now it is done automatically. Also, rewrite `UpdateWithCx` -> `UpdateWithCx, Message>`. - - `ResponseResult<()>` -> `Result<(), Box>` (or import `ResponseResult` beforehand: `use teloxide::requests::ResponseResult;`) - - Tokio updated to v1.2. - -Note: this list is non-exhaustive; for the full list of changes, see the [teloxide-core changelog] and [teloxide changelog]. - -[teloxide-core changelog]: https://github.com/teloxide/teloxide-core/blob/master/CHANGELOG.md -[teloxide changelog]: CHANGELOG.md - -
+[v0.4.0 => v0.5.0 migration guide >>](MIGRATION_GUIDE.md)
From 3e59041f9b9515c56d6d76080796c1a529bc3d7b Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Thu, 8 Jul 2021 15:26:49 +0600 Subject: [PATCH 129/131] Italicise the migration guide link (`README.md`) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 32ef799e..58bcecff 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[v0.4.0 => v0.5.0 migration guide >>](MIGRATION_GUIDE.md) +[_v0.4.0 => v0.5.0 migration guide >>_](MIGRATION_GUIDE.md)
From 72e9821b15c81f27a671541ee6392609f8120b04 Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Thu, 8 Jul 2021 15:31:35 +0600 Subject: [PATCH 130/131] Add the `asynchronous` category to `Cargo.toml` --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 33560178..a6410918 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ repository = "https://github.com/teloxide/teloxide" documentation = "https://docs.rs/teloxide/" readme = "README.md" keywords = ["teloxide", "telegram", "telegram-bot", "telegram-bot-api"] -categories = ["web-programming", "api-bindings"] +categories = ["web-programming", "api-bindings", "asynchronous"] license = "MIT" exclude = ["media"] authors = [ From cdfadb3517fd6a360a7b730e872fa1e8c370ade0 Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Thu, 8 Jul 2021 02:43:40 -0700 Subject: [PATCH 131/131] Update README.md Co-authored-by: Waffle Lapkin --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 58bcecff..6e2a1a86 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[_v0.4.0 => v0.5.0 migration guide >>_](MIGRATION_GUIDE.md) +[_v0.4.0 => v0.5.0 migration guide >>_](MIGRATION_GUIDE.md#04---05)