From b8004a803ac7d063d95f4209ac63f5cd83a84f69 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Thu, 9 Jun 2022 18:00:15 +0400 Subject: [PATCH 01/71] use built-in webhook support in heroku example Former-commit-id: 0b582882eb3bf9cd14a268cd8c7a3623a1ca7cf9 --- Cargo.toml | 5 +- examples/heroku_ping_pong.rs | 94 +++++++++--------------------------- 2 files changed, 27 insertions(+), 72 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2fc06eb3..94be4d36 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -99,7 +99,6 @@ once_cell = "1.9.0" serde = "1" serde_json = "1" tokio = { version = "1.8", features = ["fs", "rt-multi-thread", "macros"] } -warp = "0.3.0" reqwest = "0.10.4" chrono = "0.4" tokio-stream = "0.1" @@ -153,6 +152,10 @@ required-features = ["macros"] name = "ngrok_ping_pong" required-features = ["webhooks-axum"] +[[example]] +name = "heroku_ping_pong" +required-features = ["webhooks-axum"] + [[example]] name = "purchase" required-features = ["macros"] diff --git a/examples/heroku_ping_pong.rs b/examples/heroku_ping_pong.rs index 6374ed78..72ff8034 100644 --- a/examples/heroku_ping_pong.rs +++ b/examples/heroku_ping_pong.rs @@ -16,25 +16,12 @@ // heroku buildpacks:set emk/rust // ``` // -// [1] https://github.com/emk/heroku-buildpack-rust +// [1]: https://github.com/emk/heroku-buildpack-rust -// TODO: use built-in webhook support +use std::env; -use teloxide::{ - dispatching::{ - stop_token::AsyncStopToken, - update_listeners::{self, StatefulListener}, - }, - prelude::*, - types::Update, -}; - -use std::{convert::Infallible, env, net::SocketAddr}; -use tokio::sync::mpsc; -use tokio_stream::wrappers::UnboundedReceiverStream; -use warp::Filter; - -use reqwest::{StatusCode, Url}; +use teloxide::{dispatching::update_listeners::webhooks, prelude::*}; +use url::Url; #[tokio::main] async fn main() { @@ -42,66 +29,31 @@ async fn main() { log::info!("Starting Heroku ping-pong bot..."); let bot = Bot::from_env().auto_send(); + let token = bot.inner().token(); + + // Heroku auto defines a port value + let port: u16 = env::var("PORT") + .expect("PORT env variable is not set") + .parse() + .expect("PORT env variable value is not an integer"); + + let addr = ([127, 0, 0, 1], port).into(); + + // Heroku host example: "heroku-ping-pong-bot.herokuapp.com" + let host = env::var("HOST").expect("HOST env variable is not set"); + let url = Url::parse(&format!("https://{host}/webhooks/{token}")).unwrap(); + + let listener = webhooks::axum(bot.clone(), webhooks::Options::new(addr, url)) + .await + .expect("Couldn't setup webhook"); teloxide::repl_with_listener( - bot.clone(), + bot, |msg: Message, bot: AutoSend| async move { bot.send_message(msg.chat.id, "pong").await?; respond(()) }, - webhook(bot).await, + listener, ) .await; } - -async fn handle_rejection(error: warp::Rejection) -> Result { - log::error!("Cannot process the request due to: {:?}", error); - Ok(StatusCode::INTERNAL_SERVER_ERROR) -} - -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") - .parse() - .expect("PORT value to be integer"); - // 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 = Url::parse(&format!("https://{host}/{path}")).unwrap(); - - bot.set_webhook(url).await.expect("Cannot setup a webhook"); - - let (tx, rx) = mpsc::unbounded_channel(); - - let server = warp::post() - .and(warp::path(path)) - .and(warp::body::json()) - .map(move |update: Update| { - tx.send(Ok(update)).expect("Cannot send an incoming update from the webhook"); - - StatusCode::OK - }) - .recover(handle_rejection); - - let (stop_token, stop_flag) = AsyncStopToken::new_pair(); - - 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() - }) -} From 5a103ed35410f0b0ca4b1cf25a45af608b0902b3 Mon Sep 17 00:00:00 2001 From: Alexander Danilov Date: Thu, 16 Jun 2022 21:19:03 +0300 Subject: [PATCH 02/71] Add modos189/tg_blackbox_bot Former-commit-id: ecef7ec8cc9426b3f4f9ed7d4024bb91addc7234 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 0e758242..afdb7323 100644 --- a/README.md +++ b/README.md @@ -363,6 +363,7 @@ Feel free to propose your own bot to our collection! - [crapstone/hsctt](https://codeberg.org/crapstones-bots/hsctt) — A Telegram bot that searches for HTTP status codes in all messages and replies with the text form. - [alenpaul2001/AurSearchBot](https://gitlab.com/alenpaul2001/aursearchbot) — Telegram bot for searching AUR in inline mode. - [studiedlist/EddieBot](https://gitlab.com/studiedlist/eddie-bot) — Chatting bot with several entertainment features. + - [modos189/tg_blackbox_bot](https://gitlab.com/modos189/tg_blackbox_bot) — Anonymous feedback for your Telegram project. This bot in Docker from scratch container. ## Contributing From 2f880395b97ed844b74c003a0bbd688d1c7c1e6e Mon Sep 17 00:00:00 2001 From: 0xNima Date: Fri, 17 Jun 2022 18:21:04 +0430 Subject: [PATCH 03/71] update community bots Former-commit-id: 174b64e6f49d77782282c5a3ad7c8045f0fb89ff --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index afdb7323..351e6949 100644 --- a/README.md +++ b/README.md @@ -364,7 +364,8 @@ Feel free to propose your own bot to our collection! - [alenpaul2001/AurSearchBot](https://gitlab.com/alenpaul2001/aursearchbot) — Telegram bot for searching AUR in inline mode. - [studiedlist/EddieBot](https://gitlab.com/studiedlist/eddie-bot) — Chatting bot with several entertainment features. - [modos189/tg_blackbox_bot](https://gitlab.com/modos189/tg_blackbox_bot) — Anonymous feedback for your Telegram project. This bot in Docker from scratch container. - + - [0xNima/spacecraft](https://github.com/0xNima/spacecraft) — Yet another telegram bot to downloading twitter spaces + - [0xNima/Twideo](https://github.com/0xNima/Twideo) — Telegram Bot for downloading videos from Twitter via their links, as well as converting tweets to telegram messages ## Contributing See [`CONRIBUTING.md`](CONTRIBUTING.md). From c18a9fe6441452899d6232878b9a4ccec153d896 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Fri, 17 Jun 2022 23:01:26 +0400 Subject: [PATCH 04/71] Add "GC" for dispatcher workers Former-commit-id: 9cb7ca9bd317429420c930b7e2d5febd59aa0c55 --- src/dispatching/dispatcher.rs | 96 ++++++++++++++++++++++++++++----- src/dispatching/distribution.rs | 2 +- 2 files changed, 85 insertions(+), 13 deletions(-) diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index f458c23f..7dd875c8 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -16,7 +16,10 @@ use std::{ fmt::Debug, hash::Hash, ops::{ControlFlow, Deref}, - sync::Arc, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, }; use tokio::time::timeout; use tokio_stream::wrappers::ReceiverStream; @@ -32,6 +35,7 @@ pub struct DispatcherBuilder { error_handler: Arc + Send + Sync>, distribution_f: fn(&Update) -> Option, worker_queue_size: usize, + gc_worker_count_trigger: usize, } impl DispatcherBuilder @@ -83,6 +87,17 @@ where Self { worker_queue_size: size, ..self } } + /// Maximum number of inactive workers. + /// + /// When number of workers exceeds this limit dispatcher will try to remove + /// inactive workers. + /// + /// By default it's 32. + #[must_use] + pub fn gc_worker_count_trigger(self, count: usize) -> Self { + Self { gc_worker_count_trigger: count, ..self } + } + /// Specifies the distribution function that decides how updates are grouped /// before execution. pub fn distribution_function( @@ -100,6 +115,7 @@ where error_handler, distribution_f: _, worker_queue_size, + gc_worker_count_trigger: worker_count_gc, } = self; DispatcherBuilder { @@ -110,6 +126,7 @@ where error_handler, distribution_f: f, worker_queue_size, + gc_worker_count_trigger: worker_count_gc, } } @@ -124,6 +141,7 @@ where error_handler, distribution_f, worker_queue_size, + gc_worker_count_trigger, } = self; Dispatcher { @@ -137,6 +155,7 @@ where worker_queue_size, workers: HashMap::new(), default_worker: None, + gc_worker_count_trigger, } } } @@ -158,6 +177,7 @@ pub struct Dispatcher { distribution_f: fn(&Update) -> Option, worker_queue_size: usize, + gc_worker_count_trigger: usize, // Tokio TX channel parts associated with chat IDs that consume updates sequentially. workers: HashMap, // The default TX part that consume updates concurrently. @@ -171,6 +191,7 @@ pub struct Dispatcher { struct Worker { tx: tokio::sync::mpsc::Sender, handle: tokio::task::JoinHandle<()>, + is_waiting: Arc, } // TODO: it is allowed to return message as response on telegram request in @@ -194,6 +215,7 @@ where Err: Debug, { const DEFAULT_WORKER_QUEUE_SIZE: usize = 64; + const DEFAULT_GC_WORKER_COUNT_TRIGGER: usize = 32; DispatcherBuilder { bot, @@ -206,6 +228,7 @@ where error_handler: LoggingErrorHandler::new(), worker_queue_size: DEFAULT_WORKER_QUEUE_SIZE, distribution_f: default_distribution_function, + gc_worker_count_trigger: DEFAULT_GC_WORKER_COUNT_TRIGGER, } } } @@ -214,7 +237,7 @@ impl Dispatcher where R: Requester + Clone + Send + Sync + 'static, Err: Send + Sync + 'static, - Key: Hash + Eq, + Key: Hash + Eq + Clone, { /// Starts your bot with the default parameters. /// @@ -280,6 +303,8 @@ where tokio::pin!(stream); loop { + self.gc_workers_if_needed().await; + // False positive #[allow(clippy::collapsible_match)] if let Ok(upd) = timeout(shutdown_check_timeout, stream.next()).await { @@ -367,6 +392,45 @@ where } } + async fn gc_workers_if_needed(&mut self) { + if self.workers.len() <= self.gc_worker_count_trigger { + return; + } + + self.gc_workers().await; + } + + #[inline(never)] + async fn gc_workers(&mut self) { + let handles = self + .workers + .iter() + .filter(|(_, worker)| { + worker.tx.capacity() == self.worker_queue_size + && worker.is_waiting.load(Ordering::Relaxed) + }) + .map(|(k, _)| k) + .cloned() + .collect::>() + .into_iter() + .map(|key| { + let Worker { tx, handle, .. } = self.workers.remove(&key).unwrap(); + + // Close channel, worker should stop almost immediately + // (it's been supposedly waiting on the channel) + drop(tx); + + handle + }); + + for handle in handles { + // We must wait for worker to stop anyway, even though it should stop + // immediately. This helps in case if we've checked that the worker + // is waiting in between it received the update and set the flag. + let _ = handle.await; + } + } + /// Setups the `^C` handler that [`shutdown`]s dispatching. /// /// [`shutdown`]: ShutdownToken::shutdown @@ -410,20 +474,28 @@ fn spawn_worker( where Err: Send + Sync + 'static, { - let (tx, rx) = tokio::sync::mpsc::channel(queue_size); + let (tx, mut rx) = tokio::sync::mpsc::channel(queue_size); + let is_waiting = Arc::new(AtomicBool::new(true)); + let is_waiting_local = Arc::clone(&is_waiting); let deps = Arc::new(deps); - let handle = tokio::spawn(ReceiverStream::new(rx).for_each(move |update| { - let deps = Arc::clone(&deps); - let handler = Arc::clone(&handler); - let default_handler = Arc::clone(&default_handler); - let error_handler = Arc::clone(&error_handler); + let handle = tokio::spawn(async move { + while let Some(update) = rx.recv().await { + is_waiting_local.store(false, Ordering::Relaxed); - handle_update(update, deps, handler, default_handler, error_handler) - })); + let deps = Arc::clone(&deps); + let handler = Arc::clone(&handler); + let default_handler = Arc::clone(&default_handler); + let error_handler = Arc::clone(&error_handler); - Worker { tx, handle } + handle_update(update, deps, handler, default_handler, error_handler).await; + + is_waiting_local.store(true, Ordering::Relaxed); + } + }); + + Worker { tx, handle, is_waiting } } fn spawn_default_worker( @@ -449,7 +521,7 @@ where handle_update(update, deps, handler, default_handler, error_handler) })); - Worker { tx, handle } + Worker { tx, handle, is_waiting: Arc::new(AtomicBool::new(true)) } } async fn handle_update( diff --git a/src/dispatching/distribution.rs b/src/dispatching/distribution.rs index 208e0018..2089b7b6 100644 --- a/src/dispatching/distribution.rs +++ b/src/dispatching/distribution.rs @@ -1,7 +1,7 @@ use teloxide_core::types::{ChatId, Update}; /// Default distribution key for dispatching. -#[derive(Debug, Hash, PartialEq, Eq)] +#[derive(Debug, Hash, PartialEq, Eq, Clone)] pub struct DefaultKey(ChatId); pub(crate) fn default_distribution_function(update: &Update) -> Option { From f33499da32ffdbfd5d2e4f80c41ba4a80eb01e94 Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Wed, 22 Jun 2022 15:26:23 +0600 Subject: [PATCH 05/71] Update README.md Former-commit-id: ef0b2f2795033277e024f3e1dc282dea8baa04e4 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 351e6949..73a4719c 100644 --- a/README.md +++ b/README.md @@ -364,8 +364,8 @@ Feel free to propose your own bot to our collection! - [alenpaul2001/AurSearchBot](https://gitlab.com/alenpaul2001/aursearchbot) — Telegram bot for searching AUR in inline mode. - [studiedlist/EddieBot](https://gitlab.com/studiedlist/eddie-bot) — Chatting bot with several entertainment features. - [modos189/tg_blackbox_bot](https://gitlab.com/modos189/tg_blackbox_bot) — Anonymous feedback for your Telegram project. This bot in Docker from scratch container. - - [0xNima/spacecraft](https://github.com/0xNima/spacecraft) — Yet another telegram bot to downloading twitter spaces - - [0xNima/Twideo](https://github.com/0xNima/Twideo) — Telegram Bot for downloading videos from Twitter via their links, as well as converting tweets to telegram messages + - [0xNima/spacecraft](https://github.com/0xNima/spacecraft) — Yet another telegram bot to downloading Twitter spaces. + - [0xNima/Twideo](https://github.com/0xNima/Twideo) — Telegram Bot for downloading videos from Twitter via their links, as well as converting tweets to telegram messages. ## Contributing See [`CONRIBUTING.md`](CONTRIBUTING.md). From 0b307aa177b08907ba145a52e865a12b0743cfbf Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Sun, 26 Jun 2022 22:53:41 +0400 Subject: [PATCH 06/71] Auto-magically detect how much workers need to be kept alive Former-commit-id: a820dedd50b87541d23053a2ffdaf67a0282fe8e --- src/dispatching/dispatcher.rs | 59 +++++++++++++++++------------------ 1 file changed, 28 insertions(+), 31 deletions(-) diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index 7dd875c8..a8178be1 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -11,20 +11,20 @@ use crate::{ use dptree::di::{DependencyMap, DependencySupplier}; use futures::{future::BoxFuture, stream::FuturesUnordered, StreamExt}; -use std::{ - collections::HashMap, - fmt::Debug, - hash::Hash, - ops::{ControlFlow, Deref}, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }, -}; use tokio::time::timeout; use tokio_stream::wrappers::ReceiverStream; -use std::future::Future; +use std::{ + collections::HashMap, + fmt::Debug, + future::Future, + hash::Hash, + ops::{ControlFlow, Deref}, + sync::{ + atomic::{AtomicBool, AtomicU32, Ordering}, + Arc, + }, +}; /// The builder for [`Dispatcher`]. pub struct DispatcherBuilder { @@ -35,7 +35,6 @@ pub struct DispatcherBuilder { error_handler: Arc + Send + Sync>, distribution_f: fn(&Update) -> Option, worker_queue_size: usize, - gc_worker_count_trigger: usize, } impl DispatcherBuilder @@ -87,17 +86,6 @@ where Self { worker_queue_size: size, ..self } } - /// Maximum number of inactive workers. - /// - /// When number of workers exceeds this limit dispatcher will try to remove - /// inactive workers. - /// - /// By default it's 32. - #[must_use] - pub fn gc_worker_count_trigger(self, count: usize) -> Self { - Self { gc_worker_count_trigger: count, ..self } - } - /// Specifies the distribution function that decides how updates are grouped /// before execution. pub fn distribution_function( @@ -115,7 +103,6 @@ where error_handler, distribution_f: _, worker_queue_size, - gc_worker_count_trigger: worker_count_gc, } = self; DispatcherBuilder { @@ -126,7 +113,6 @@ where error_handler, distribution_f: f, worker_queue_size, - gc_worker_count_trigger: worker_count_gc, } } @@ -141,7 +127,6 @@ where error_handler, distribution_f, worker_queue_size, - gc_worker_count_trigger, } = self; Dispatcher { @@ -155,7 +140,8 @@ where worker_queue_size, workers: HashMap::new(), default_worker: None, - gc_worker_count_trigger, + current_number_of_active_workers: Default::default(), + max_number_of_active_workers: Default::default(), } } } @@ -177,7 +163,8 @@ pub struct Dispatcher { distribution_f: fn(&Update) -> Option, worker_queue_size: usize, - gc_worker_count_trigger: usize, + current_number_of_active_workers: Arc, + max_number_of_active_workers: Arc, // Tokio TX channel parts associated with chat IDs that consume updates sequentially. workers: HashMap, // The default TX part that consume updates concurrently. @@ -215,7 +202,6 @@ where Err: Debug, { const DEFAULT_WORKER_QUEUE_SIZE: usize = 64; - const DEFAULT_GC_WORKER_COUNT_TRIGGER: usize = 32; DispatcherBuilder { bot, @@ -228,7 +214,6 @@ where error_handler: LoggingErrorHandler::new(), worker_queue_size: DEFAULT_WORKER_QUEUE_SIZE, distribution_f: default_distribution_function, - gc_worker_count_trigger: DEFAULT_GC_WORKER_COUNT_TRIGGER, } } } @@ -367,6 +352,8 @@ where handler, default_handler, error_handler, + Arc::clone(&self.current_number_of_active_workers), + Arc::clone(&self.max_number_of_active_workers), self.worker_queue_size, ) }), @@ -393,7 +380,10 @@ where } async fn gc_workers_if_needed(&mut self) { - if self.workers.len() <= self.gc_worker_count_trigger { + let workers = self.workers.len(); + let max = self.max_number_of_active_workers.load(Ordering::Relaxed) as usize; + + if workers <= max { return; } @@ -469,6 +459,8 @@ fn spawn_worker( handler: Arc>, default_handler: DefaultHandler, error_handler: Arc + Send + Sync>, + current_number_of_active_workers: Arc, + max_number_of_active_workers: Arc, queue_size: usize, ) -> Worker where @@ -483,6 +475,10 @@ where let handle = tokio::spawn(async move { while let Some(update) = rx.recv().await { is_waiting_local.store(false, Ordering::Relaxed); + { + let current = current_number_of_active_workers.fetch_add(1, Ordering::Relaxed) + 1; + max_number_of_active_workers.fetch_max(current, Ordering::Relaxed); + } let deps = Arc::clone(&deps); let handler = Arc::clone(&handler); @@ -491,6 +487,7 @@ where handle_update(update, deps, handler, default_handler, error_handler).await; + current_number_of_active_workers.fetch_sub(1, Ordering::Relaxed); is_waiting_local.store(true, Ordering::Relaxed); } }); From 3dfd861299c542eb38b24617c4cd072c9228e89a Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Mon, 27 Jun 2022 01:57:27 +0600 Subject: [PATCH 07/71] Fix GIFs resizing in `README.md` Former-commit-id: d232319dfab1c4080ed57ec880bf3ef7958b00a6 --- README.md | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 73a4719c..55246a54 100644 --- a/README.md +++ b/README.md @@ -105,9 +105,7 @@ async fn main() { ```
- - - +
### Commands @@ -175,9 +173,7 @@ async fn answer( ```
- - - +
### Dialogues management @@ -300,9 +296,7 @@ async fn receive_location( ```
- - - +
[More examples >>](examples/) From 98a892e6bdd4434f347b45a76d4be43975e96e62 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Mon, 27 Jun 2022 00:01:31 +0400 Subject: [PATCH 08/71] Apply suggestions from the review Former-commit-id: 8489464bd3c47a6252dce1914cfa1411b0ea2b98 --- src/dispatching/dispatcher.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index a8178be1..74eace82 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -288,7 +288,7 @@ where tokio::pin!(stream); loop { - self.gc_workers_if_needed().await; + self.remove_inactive_workers_if_needed().await; // False positive #[allow(clippy::collapsible_match)] @@ -379,7 +379,7 @@ where } } - async fn gc_workers_if_needed(&mut self) { + async fn remove_inactive_workers_if_needed(&mut self) { let workers = self.workers.len(); let max = self.max_number_of_active_workers.load(Ordering::Relaxed) as usize; @@ -387,11 +387,11 @@ where return; } - self.gc_workers().await; + self.remove_inactive_workers().await; } - #[inline(never)] - async fn gc_workers(&mut self) { + #[inline(never)] // Cold function. + async fn remove_inactive_workers(&mut self) { let handles = self .workers .iter() From d5e8b67332393f64026e04ce85c344036faa0a0f Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Mon, 27 Jun 2022 00:02:27 +0400 Subject: [PATCH 09/71] Update changelog Former-commit-id: b45472973c40b905bcab9cef1d320703ccc2c554 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e5fa740..3ab583ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## unreleased +### Fixed + +- `Dispatcher` no longer "leaks" memory for every user + ## 0.9.2 - 2022-06-07 ### Fixed From 407efd17ee87da14b008c7014f8c9d4c45338b81 Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Mon, 27 Jun 2022 02:30:39 +0600 Subject: [PATCH 10/71] Update CHANGELOG.md Former-commit-id: 3e35d40e84cb0b97e10a6c39e3fb1456b9bdac34 --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ab583ec..53ff1e5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed -- `Dispatcher` no longer "leaks" memory for every user + - `Dispatcher` no longer "leaks" memory for every inactive user ([PR 657](https://github.com/teloxide/teloxide/pull/657)). + +### Changed + + - Add the `Key: Clone` requirement for `impl Dispatcher` [**BC**]. ## 0.9.2 - 2022-06-07 From 6fa2818bcd90506feda44d7cfcf4045d16393ef5 Mon Sep 17 00:00:00 2001 From: Hugo Cornago Date: Mon, 27 Jun 2022 10:31:09 +0200 Subject: [PATCH 11/71] fix typo Former-commit-id: 22b554650e1447b6ffdbda3c34138029766dccb5 --- src/dispatching.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dispatching.rs b/src/dispatching.rs index ef958901..902c601a 100644 --- a/src/dispatching.rs +++ b/src/dispatching.rs @@ -1,6 +1,6 @@ //! An update dispatching model based on [`dptree`]. //! -//! In teloxide, updates are dispatched by a pipleine. The central type is +//! In teloxide, updates are dispatched by a pipeline. The central type is //! [`dptree::Handler`] -- it represents a handler of an update; since the API //! is highly declarative, you can combine handlers with each other via such //! methods as [`dptree::Handler::chain`] and [`dptree::Handler::branch`]. The From d3f1e4058c84a3be84a3134c50cfea619632fdf9 Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Wed, 29 Jun 2022 01:49:49 +0600 Subject: [PATCH 12/71] Use `Arc` in `examples/shared_state.rs` Former-commit-id: a48f6c8a446c7461ae565b409942737ca6f0cd35 --- Cargo.toml | 1 - examples/shared_state.rs | 25 ++++++++++++++++--------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 94be4d36..a4ece2c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -95,7 +95,6 @@ tower-http = { version = "0.2.5", features = ["trace"], optional = true } [dev-dependencies] rand = "0.8.3" pretty_env_logger = "0.4.0" -once_cell = "1.9.0" serde = "1" serde_json = "1" tokio = { version = "1.8", features = ["fs", "rt-multi-thread", "macros"] } diff --git a/examples/shared_state.rs b/examples/shared_state.rs index 33f5d4b6..c09fab93 100644 --- a/examples/shared_state.rs +++ b/examples/shared_state.rs @@ -1,27 +1,34 @@ // This bot answers how many messages it received in total on every message. -use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::{ + atomic::{AtomicU64, Ordering}, + Arc, +}; -use once_cell::sync::Lazy; use teloxide::prelude::*; -static MESSAGES_TOTAL: Lazy = Lazy::new(AtomicU64::default); - #[tokio::main] async fn main() { pretty_env_logger::init(); log::info!("Starting shared state bot..."); let bot = Bot::from_env().auto_send(); + let messages_total = Arc::new(AtomicU64::new(0)); - let handler = Update::filter_message().branch(dptree::endpoint( - |msg: Message, bot: AutoSend| async move { - let previous = MESSAGES_TOTAL.fetch_add(1, Ordering::Relaxed); + let handler = Update::filter_message().endpoint( + |msg: Message, bot: AutoSend, messages_total: Arc| async move { + let previous = messages_total.fetch_add(1, Ordering::Relaxed); bot.send_message(msg.chat.id, format!("I received {previous} messages in total.")) .await?; respond(()) }, - )); + ); - Dispatcher::builder(bot, handler).build().setup_ctrlc_handler().dispatch().await; + Dispatcher::builder(bot, handler) + // Pass the shared state to the handler as a dependency. + .dependencies(dptree::deps![messages_total]) + .build() + .setup_ctrlc_handler() + .dispatch() + .await; } From 5e8682da8c7fe2817affe989cf007229e51b9f99 Mon Sep 17 00:00:00 2001 From: Alessio <35380179+AlecsFerra@users.noreply.github.com> Date: Wed, 29 Jun 2022 15:10:45 +0200 Subject: [PATCH 13/71] Update the heroku example to use 0.0.0.0 as ip On heroku we canno't bind on 127.0.0.1 Former-commit-id: c95203759a00b6da9bad84fe8c7309edcea7ecea --- examples/heroku_ping_pong.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/heroku_ping_pong.rs b/examples/heroku_ping_pong.rs index 72ff8034..8238dd9a 100644 --- a/examples/heroku_ping_pong.rs +++ b/examples/heroku_ping_pong.rs @@ -37,7 +37,7 @@ async fn main() { .parse() .expect("PORT env variable value is not an integer"); - let addr = ([127, 0, 0, 1], port).into(); + let addr = ([0, 0, 0, 0], port).into(); // Heroku host example: "heroku-ping-pong-bot.herokuapp.com" let host = env::var("HOST").expect("HOST env variable is not set"); From d813555459e01ecaa8596f920f6929c485870ba6 Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Wed, 29 Jun 2022 21:36:36 +0600 Subject: [PATCH 14/71] Make a TODO in place of the dispatching doc example Former-commit-id: 69b5d8b3d5191872287dfe6d94dcbd0673214c42 --- src/dispatching.rs | 30 +++--------------------------- 1 file changed, 3 insertions(+), 27 deletions(-) diff --git a/src/dispatching.rs b/src/dispatching.rs index 902c601a..6b05d88c 100644 --- a/src/dispatching.rs +++ b/src/dispatching.rs @@ -29,31 +29,7 @@ //! ([Full](https://github.com/teloxide/teloxide/blob/master/examples/shared_state.rs)) //! //! ```no_run -//! use std::sync::atomic::{AtomicU64, Ordering}; -//! -//! use once_cell::sync::Lazy; -//! use teloxide::prelude::*; -//! -//! static MESSAGES_TOTAL: Lazy = Lazy::new(AtomicU64::default); -//! -//! # #[tokio::main] -//! # async fn main() { -//! pretty_env_logger::init(); -//! log::info!("Starting shared state bot..."); -//! -//! let bot = Bot::from_env().auto_send(); -//! -//! let handler = Update::filter_message().branch(dptree::endpoint( -//! |msg: Message, bot: AutoSend| async move { -//! let previous = MESSAGES_TOTAL.fetch_add(1, Ordering::Relaxed); -//! bot.send_message(msg.chat.id, format!("I received {} messages in total.", previous)) -//! .await?; -//! respond(()) -//! }, -//! )); -//! -//! Dispatcher::builder(bot, handler).build().setup_ctrlc_handler().dispatch().await; -//! # } +//! // TODO: examples/purchase.rs //! ``` //! //! 1. First, we create the bot: `let bot = Bot::from_env().auto_send()`. @@ -61,8 +37,8 @@ //! kinds of [`crate::types::Update`], here we are only interested in //! [`crate::types::Message`]: [`UpdateFilterExt::filter_message`] create a //! handler object which filters all messages out of a generic update. -//! 3. By doing `.branch(dptree::endpoint(...))`, we set up a custom handling -//! closure that receives `msg: Message` and `bot: AutoSend`. There are +//! 3. By doing `.endpoint(...)` we set up a custom handling closure that +//! receives `msg: Message` and `bot: AutoSend`. There are //! called dependencies: `msg` is supplied by //! [`UpdateFilterExt::filter_message`], while `bot` is supplied by //! [`Dispatcher`]. From 6bfa21a02e2c94d072390cc5e1c0abb90924636a Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Sun, 3 Jul 2022 14:24:42 +0400 Subject: [PATCH 15/71] Update pinned nightly version Former-commit-id: 004837016908691b71112f0d84aa1c35da503cec --- .github/workflows/ci.yml | 6 +++--- rust-toolchain.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e990ad6a..8db7d963 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,7 @@ jobs: - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: nightly-2022-05-06 + toolchain: nightly-2022-07-01 override: true components: rustfmt @@ -37,7 +37,7 @@ jobs: - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: nightly-2022-05-06 + toolchain: nightly-2022-07-01 override: true components: clippy @@ -65,7 +65,7 @@ jobs: toolchain: beta features: "--features full" - rust: nightly - toolchain: nightly-2022-05-06 + toolchain: nightly-2022-07-01 features: "--all-features" - rust: msrv toolchain: "1.58.0" diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 43d17c19..ea79a434 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "nightly-2022-05-06" +channel = "nightly-2022-07-01" components = ["rustfmt", "clippy"] profile = "minimal" From 41197bf515c59e9723a894abb43e1a3d0d8aa461 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Sun, 3 Jul 2022 14:33:46 +0400 Subject: [PATCH 16/71] Change recommended way to build docs Former-commit-id: a4ad44fae96b8bf5858d673ff875fb7a46d4e126 --- .cargo/config.toml | 12 ++++++++++++ src/lib.rs | 8 ++------ 2 files changed, 14 insertions(+), 6 deletions(-) create mode 100644 .cargo/config.toml diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 00000000..d6e070ff --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,12 @@ +[alias] +# We pass "--cfg docsrs" when building docs to turn on nightly-only rustdoc features like +# `This is supported on feature="..." only.` +# +# "--cfg dep_docsrs" is used for the same reason, but for `teloxide-core`. +docs = """ +doc + --all-features + --config build.rustflags=["--cfg=dep_docsrs"] + --config build.rustdocflags=["--cfg=docsrs","-Znormalize-docs"] + -Zrustdoc-scrape-examples=examples +""" diff --git a/src/lib.rs b/src/lib.rs index f53ed5c5..e43af26c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,15 +44,11 @@ html_logo_url = "https://github.com/teloxide/teloxide/raw/master/ICON.png", html_favicon_url = "https://github.com/teloxide/teloxide/raw/master/ICON.png" )] -// 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 -// $ RUSTFLAGS="--cfg dep_docsrs" RUSTDOCFLAGS="--cfg docsrs -Znormalize-docs" cargo +nightly doc --open --all-features +// $ cargo docs --open // ``` +// (docs is an alias from `.cargo/config.toml`) #![cfg_attr(all(docsrs, feature = "nightly"), feature(doc_cfg, doc_auto_cfg))] #![forbid(unsafe_code)] #![warn(rustdoc::broken_intra_doc_links)] From 21cd47dcfd117c18026d2df6fb4bf6e4180bfa32 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Sun, 3 Jul 2022 14:35:56 +0400 Subject: [PATCH 17/71] Improve CI Former-commit-id: a9886d841967215af550b74b9eaca0a5356b0148 --- .github/workflows/ci.yml | 167 +++++++++++++++++++++++++++++---------- 1 file changed, 124 insertions(+), 43 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8db7d963..7a1ab60b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,47 +7,64 @@ on: name: Continuous integration env: - RUSTFLAGS: "--cfg CI_REDIS" + RUSTFLAGS: "--cfg CI_REDIS -Dwarnings" + RUSTDOCFLAGS: -Dwarnings + RUST_BACKTRACE: short + + CARGO_INCREMENTAL: 0 + CARGO_NET_RETRY: 10 + RUSTUP_MAX_RETRIES: 10 + + rust_nightly: nightly-2022-07-01 + # When updating this, also update: + # - README.md + # - src/lib.rs + # - down below in a matrix + rust_msrv: 1.58.0 jobs: - style: + # Depends on all action that are required for a "successful" CI run. + ci-pass: + name: CI succeeded + runs-on: ubuntu-latest + + needs: + - fmt + - test + - check-examples + - clippy + - doc + + steps: + - run: exit 0 + + fmt: + name: fmt runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 + - name: Checkout + uses: actions/checkout@v3 + + - name: Install Rust ${{ env.rust_nightly }} + uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: nightly-2022-07-01 + toolchain: ${{ env.rust_nightly }} override: true components: rustfmt - - name: fmt + - name: Cache Dependencies + uses: Swatinem/rust-cache@v1 + + - name: Check formatting uses: actions-rs/cargo@v1 with: command: fmt - args: --all -- --check - - clippy: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: nightly-2022-07-01 - override: true - components: clippy - - - name: clippy - uses: actions-rs/cargo@v1 - with: - command: clippy - args: --all-targets --all-features -- -D warnings + args: --all -- --check test: + name: Test runs-on: ubuntu-latest strategy: matrix: @@ -68,45 +85,109 @@ jobs: toolchain: nightly-2022-07-01 features: "--all-features" - rust: msrv - toolchain: "1.58.0" + toolchain: 1.58.0 features: "--features full" - steps: - - uses: actions/checkout@v2 - - - uses: actions-rs/toolchain@v1 + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Install Rust ${{ matrix.toolchain }} + uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: ${{ matrix.toolchain }} override: true - - - name: build + + - name: Cache Dependencies + uses: Swatinem/rust-cache@v1 + + - name: Compile uses: actions-rs/cargo@v1 with: - command: build - args: --verbose ${{ matrix.features }} - + command: test + args: --no-run --verbose ${{ matrix.features }} + - name: Setup redis run: | sudo apt install redis-server redis-server --port 7777 > /dev/null & redis-server --port 7778 > /dev/null & redis-server --port 7779 > /dev/null & - - - name: test + + - name: Test uses: actions-rs/cargo@v1 with: command: test args: --verbose ${{ matrix.features }} - build-example: + check-examples: runs-on: ubuntu-latest + steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 + - name: Checkout + uses: actions/checkout@v3 + + - name: Install Rust stable + uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable override: true - - name: Check the examples - run: cargo check --examples --features="full" + + - name: Cache Dependencies + uses: Swatinem/rust-cache@v1 + + - name: Check examples + uses: actions-rs/cargo@v1 + with: + command: check + args: --examples --features full + + clippy: + name: Run linter + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Install Rust ${{ env.rust_nightly }} + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ env.rust_nightly }} + override: true + components: clippy + + - name: Cache Dependencies + uses: Swatinem/rust-cache@v1 + + - name: clippy + uses: actions-rs/cargo@v1 + with: + command: clippy + args: --all-targets --all-features + + doc: + name: check docs + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Install Rust ${{ env.rust_nightly }} + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ env.rust_nightly }} + override: true + + - name: Cache Dependencies + uses: Swatinem/rust-cache@v1 + + - name: rustdoc + uses: actions-rs/cargo@v1 + with: + command: docs # from .cargo/config.toml From 82ff78b5f637eebab36ae8acd2e5cbde830abcee Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Sun, 3 Jul 2022 14:38:14 +0400 Subject: [PATCH 18/71] Don't build examples when testing on CI Former-commit-id: 77d1c4524d60a6a2979636957d93cfb7e880bdc3 --- .github/workflows/ci.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7a1ab60b..65b4c62f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -115,11 +115,18 @@ jobs: redis-server --port 7778 > /dev/null & redis-server --port 7779 > /dev/null & - - name: Test + # NB. Don't test (build) examples so we can use non-msrv features in them + - name: Test unit & integration tests uses: actions-rs/cargo@v1 with: command: test - args: --verbose ${{ matrix.features }} + args: --tests --verbose ${{ matrix.features }} + + - name: Test documentation tests + uses: actions-rs/cargo@v1 + with: + command: test + args: --doc --verbose ${{ matrix.features }} check-examples: runs-on: ubuntu-latest From 074b991189e4745c66108e74541429d4d6cadd02 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Sun, 3 Jul 2022 15:21:17 +0400 Subject: [PATCH 19/71] fix doctest Former-commit-id: cabc045c06b8118bd244810d2986de77138d4a3e --- src/dispatching.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dispatching.rs b/src/dispatching.rs index 6b05d88c..1c093048 100644 --- a/src/dispatching.rs +++ b/src/dispatching.rs @@ -30,6 +30,7 @@ //! //! ```no_run //! // TODO: examples/purchase.rs +//! fn main() {} //! ``` //! //! 1. First, we create the bot: `let bot = Bot::from_env().auto_send()`. From 7f6d2c28010c0b998e717db5a0964ba0bee0a66a Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Fri, 1 Jul 2022 23:10:00 +0400 Subject: [PATCH 20/71] Update teloxide-core to master Former-commit-id: c129b6a53dff921adcc8cc3d60eb15032980624a --- Cargo.toml | 3 ++- src/utils/html.rs | 4 ++++ src/utils/markdown.rs | 4 ++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a4ece2c9..2ae8da8b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,7 +56,8 @@ full = [ ] [dependencies] -teloxide-core = { version = "0.6.0", default-features = false } +#teloxide-core = { version = "0.6.0", default-features = false } +teloxide-core = { git = "https://github.com/teloxide/teloxide-core", rev = "b13393d", default-features = false } teloxide-macros = { version = "0.6.2", optional = true } serde_json = "1.0" diff --git a/src/utils/html.rs b/src/utils/html.rs index 11ae475d..dc655fbf 100644 --- a/src/utils/html.rs +++ b/src/utils/html.rs @@ -191,6 +191,8 @@ mod tests { last_name: None, username: Some("abcd".to_string()), language_code: None, + is_premium: false, + added_to_attachment_menu: false, }; assert_eq!(user_mention_or_link(&user_with_username), "@abcd"); let user_without_username = User { @@ -200,6 +202,8 @@ mod tests { last_name: None, username: None, language_code: None, + is_premium: false, + added_to_attachment_menu: false, }; assert_eq!( user_mention_or_link(&user_without_username), diff --git a/src/utils/markdown.rs b/src/utils/markdown.rs index c07d53d9..4419feb9 100644 --- a/src/utils/markdown.rs +++ b/src/utils/markdown.rs @@ -240,6 +240,8 @@ mod tests { last_name: None, username: Some("abcd".to_string()), language_code: None, + is_premium: false, + added_to_attachment_menu: false, }; assert_eq!(user_mention_or_link(&user_with_username), "@abcd"); let user_without_username = User { @@ -249,6 +251,8 @@ mod tests { last_name: None, username: None, language_code: None, + is_premium: false, + added_to_attachment_menu: false, }; assert_eq!( user_mention_or_link(&user_without_username), From 1360aa96c366baaf8751a6b3b08e8a90916cb369 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Fri, 1 Jul 2022 23:21:39 +0400 Subject: [PATCH 21/71] Add support for secret_token in built-in webhooks Former-commit-id: 8806cb9d78b37a22c34031d9d98410596c7695c1 --- Cargo.toml | 4 +- src/dispatching/update_listeners.rs | 2 +- src/dispatching/update_listeners/webhooks.rs | 81 ++++++++++++++++++- .../update_listeners/webhooks/axum.rs | 65 ++++++++++++--- 4 files changed, 136 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2ae8da8b..5dfc708b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,8 @@ exclude = ["media"] [features] default = ["native-tls", "ctrlc_handler", "teloxide-core/default", "auto-send"] -webhooks-axum = ["axum", "tower", "tower-http"] +webhooks = ["rand"] +webhooks-axum = ["webhooks", "axum", "tower", "tower-http"] sqlite-storage = ["sqlx"] redis-storage = ["redis"] @@ -92,6 +93,7 @@ bincode = { version = "1.3", optional = true } axum = { version = "0.4.8", optional = true } tower = { version = "0.4.12", optional = true } tower-http = { version = "0.2.5", features = ["trace"], optional = true } +rand = { version = "0.8.5", optional = true } [dev-dependencies] rand = "0.8.3" diff --git a/src/dispatching/update_listeners.rs b/src/dispatching/update_listeners.rs index 4c01d174..f3e358c5 100644 --- a/src/dispatching/update_listeners.rs +++ b/src/dispatching/update_listeners.rs @@ -27,7 +27,7 @@ /// Implementations of webhook update listeners - an alternative (to /// [`fn@polling`]) way of receiving updates from telegram. -#[cfg(any(feature = "webhooks-axum"))] +#[cfg(feature = "webhooks")] pub mod webhooks; use futures::Stream; diff --git a/src/dispatching/update_listeners/webhooks.rs b/src/dispatching/update_listeners/webhooks.rs index b20afd86..828f2683 100644 --- a/src/dispatching/update_listeners/webhooks.rs +++ b/src/dispatching/update_listeners/webhooks.rs @@ -42,13 +42,28 @@ pub struct Options { /// /// Default - false. pub drop_pending_updates: bool, + + /// A secret token to be sent in a header “X-Telegram-Bot-Api-Secret-Token” + /// in every webhook request, 1-256 characters. Only characters `A-Z`, + /// `a-z`, `0-9`, `_` and `-` are allowed. The header is useful to ensure + /// that the request comes from a webhook set by you. + /// + /// Default - teloxide will generate a random token. + pub secret_token: Option, } impl Options { /// Construct a new webhook options, see [`Options::address`] and /// [`Options::url`] for details. pub fn new(address: SocketAddr, url: url::Url) -> Self { - Self { address, url, certificate: None, max_connections: None, drop_pending_updates: false } + Self { + address, + url, + certificate: None, + max_connections: None, + drop_pending_updates: false, + secret_token: None, + } } /// Upload your public key certificate so that the root certificate in use @@ -71,6 +86,32 @@ impl Options { pub fn drop_pending_updates(self) -> Self { Self { drop_pending_updates: true, ..self } } + + /// A secret token to be sent in a header “X-Telegram-Bot-Api-Secret-Token” + /// in every webhook request, 1-256 characters. Only characters `A-Z`, + /// `a-z`, `0-9`, `_` and `-` are allowed. The header is useful to ensure + /// that the request comes from a webhook set by you. + /// + /// ## Panics + /// + /// If the token is invalid. + #[track_caller] + pub fn secret_token(self, token: String) -> Self { + check_secret(token.as_bytes()).expect("Invalid secret token"); + + Self { secret_token: Some(token), ..self } + } + + /// Returns `self.secret_token`, generating a new one if it's `None`. + /// + /// After a call to this function `self.secret_token` is always `Some(_)`. + /// + /// **Note**: if you leave webhook setup to teloxide, it will automatically + /// generate a secret token. Call this function only if you need to know the + /// secret (for example because you are calling `set_webhook` by yourself). + pub fn get_or_gen_secret_token(&mut self) -> &str { + self.secret_token.get_or_insert_with(gen_secret_token) + } } #[cfg(feature = "webhooks-axum")] @@ -91,6 +132,7 @@ where use crate::requests::Request; use teloxide_core::requests::HasPayload; + let secret = options.get_or_gen_secret_token().to_owned(); let &mut Options { ref url, ref mut certificate, max_connections, drop_pending_updates, .. } = options; @@ -99,12 +141,49 @@ where req.payload_mut().certificate = certificate.take(); req.payload_mut().max_connections = max_connections; req.payload_mut().drop_pending_updates = Some(drop_pending_updates); + req.payload_mut().secret_token = Some(secret); req.send().await?; Ok(()) } +/// Generates a random string consisting of 32 characters (`a-z`, `A-Z`, `0-9`, +/// `_` and `-`). +fn gen_secret_token() -> String { + use rand::{distributions::Uniform, Rng}; + const CHARSET: &[u8] = b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-"; + const SECRET_LENGTH: usize = 32; + + let random = rand::thread_rng() + .sample_iter(Uniform::new(0, CHARSET.len())) + .map(|idx| CHARSET[idx] as char) + .take(SECRET_LENGTH); + + let mut secret = String::with_capacity(SECRET_LENGTH); + secret.extend(random); + + secret +} + +fn check_secret(bytes: &[u8]) -> Result<&[u8], &'static str> { + let len = bytes.len(); + + // Check that length is in bounds + if !(1 <= len && len <= 256) { + return Err("secret token length must be in range 1..=256"); + } + + // Check that all characters of the secret are supported by telegram + let is_not_supported = + |c: &_| !matches!(c, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_' | b'-'); + if bytes.iter().any(is_not_supported) { + return Err("secret token must only contain of `a-z`, `A-Z`, `0-9`, `_` and `-` characters"); + } + + Ok(bytes) +} + /// Returns first (`.0`) field from a tuple as a `&mut` reference. /// /// This hack is needed because there isn't currently a way to easily force a diff --git a/src/dispatching/update_listeners/webhooks/axum.rs b/src/dispatching/update_listeners/webhooks/axum.rs index e145c4b2..ab08cd92 100644 --- a/src/dispatching/update_listeners/webhooks/axum.rs +++ b/src/dispatching/update_listeners/webhooks/axum.rs @@ -1,12 +1,14 @@ -use std::convert::Infallible; +use std::{convert::Infallible, future::Future, pin::Pin}; + +use axum::{ + extract::{FromRequest, RequestParts}, + http::status::StatusCode, +}; use crate::{ dispatching::{ stop_token::{AsyncStopFlag, StopToken}, - update_listeners::{ - webhooks::{setup_webhook, tuple_first_mut, Options}, - UpdateListener, - }, + update_listeners::{webhooks::Options, UpdateListener}, }, requests::Requester, }; @@ -105,15 +107,12 @@ where pub async fn axum_to_router( bot: R, mut options: Options, -) -> Result< - (impl UpdateListener, impl std::future::Future + Send, axum::Router), - R::Err, -> +) -> Result<(impl UpdateListener, impl Future + Send, axum::Router), R::Err> where R: Requester + Send, ::DeleteWebhook: Send, { - use crate::requests::Request; + use crate::{dispatching::update_listeners::webhooks::setup_webhook, requests::Request}; use futures::FutureExt; setup_webhook(&bot, &mut options).await?; @@ -149,12 +148,15 @@ where /// function. pub fn axum_no_setup( options: Options, -) -> (impl UpdateListener, impl std::future::Future, axum::Router) { +) -> (impl UpdateListener, impl Future, axum::Router) { use crate::{ - dispatching::{stop_token::AsyncStopToken, update_listeners}, + dispatching::{ + stop_token::AsyncStopToken, + update_listeners::{self, webhooks::tuple_first_mut}, + }, types::Update, }; - use axum::{extract::Extension, http::StatusCode, response::IntoResponse, routing::post}; + use axum::{extract::Extension, response::IntoResponse, routing::post}; use tokio::sync::mpsc; use tokio_stream::wrappers::UnboundedReceiverStream; use tower::ServiceBuilder; @@ -167,9 +169,16 @@ pub fn axum_no_setup( async fn telegram_request( input: String, + secret_header: XTelegramBotApiSecretToken, + secret: Extension>, tx: Extension, flag: Extension, ) -> impl IntoResponse { + // FIXME: use constant time comparison here + if secret_header.0.as_deref() != secret.as_deref().map(str::as_bytes) { + return StatusCode::UNAUTHORIZED; + } + let tx = match tx.get() { None => return StatusCode::SERVICE_UNAVAILABLE, // Do not process updates after `.stop()` is called even if the server is still @@ -206,6 +215,7 @@ pub fn axum_no_setup( .layer(TraceLayer::new_for_http()) .layer(Extension(ClosableSender::new(tx))) .layer(Extension(stop_flag.clone())) + .layer(Extension(options.secret_token)) .into_inner(), ); @@ -245,3 +255,32 @@ impl ClosableSender { self.origin.write().unwrap().take(); } } + +struct XTelegramBotApiSecretToken(Option>); + +impl FromRequest for XTelegramBotApiSecretToken { + type Rejection = StatusCode; + + fn from_request<'l0, 'at>( + req: &'l0 mut RequestParts, + ) -> Pin> + Send + 'at>> + where + 'l0: 'at, + Self: 'at, + { + use crate::dispatching::update_listeners::webhooks::check_secret; + + let res = req + .headers_mut() + .and_then(|map| map.remove("x-telegram-bot-api-secret-token")) + .map(|header| { + check_secret(header.as_bytes()) + .map(<_>::to_owned) + .map_err(|_| StatusCode::BAD_REQUEST) + }) + .transpose() + .map(Self); + + Box::pin(async { res }) as _ + } +} From 653b2119aaa0d260f4c7c13fe6bdaaeb38afd192 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Fri, 1 Jul 2022 23:27:46 +0400 Subject: [PATCH 22/71] Reformat features file Former-commit-id: 4edd41fd58431cbba20915838ad0aa73d3cbaec2 --- src/features.md | 31 +++++++++++++++++++++++++++++++ src/features.txt | 29 ----------------------------- src/lib.rs | 2 +- 3 files changed, 32 insertions(+), 30 deletions(-) create mode 100644 src/features.md delete mode 100644 src/features.txt diff --git a/src/features.md b/src/features.md new file mode 100644 index 00000000..b7ef133b --- /dev/null +++ b/src/features.md @@ -0,0 +1,31 @@ +## 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. | +| `ctrlc_handler` | Enables the [`Dispatcher::setup_ctrlc_handler`] function (**enabled by default**). | +| `auto-send` | Enables the [`AutoSend`](adaptors::AutoSend) bot adaptor (**enabled by default**). | +| `throttle` | Enables the [`Throttle`](adaptors::Throttle) bot adaptor. | +| `cache-me` | Enables the [`CacheMe`](adaptors::CacheMe) bot adaptor. | +| `trace-adaptor` | Enables the [`Trace`](adaptors::Trace) bot adaptor. | +| `erased` | Enables the [`ErasedRequester`](adaptors::ErasedRequester) bot adaptor. | +| `full` | Enables all the features except `nightly`. | +| `nightly` | Enables nightly-only features (see the [teloxide-core features]). | + +[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 + +[`Dispatcher::setup_ctrlc_handler`]: dispatching::Dispatcher::setup_ctrlc_handler \ No newline at end of file diff --git a/src/features.txt b/src/features.txt deleted file mode 100644 index 32d737b3..00000000 --- a/src/features.txt +++ /dev/null @@ -1,29 +0,0 @@ -## 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. | -| `ctrlc_handler` | Enables the [`Dispatcher::setup_ctrlc_handler`](dispatching::Dispatcher::setup_ctrlc_handler) function. | -| `auto-send` | Enables the [`AutoSend`](adaptors::AutoSend) bot adaptor. | -| `throttle` | Enables the [`Throttle`](adaptors::Throttle) bot adaptor. | -| `cache-me` | Enables the [`CacheMe`](adaptors::CacheMe) bot adaptor. | -| `trace-adaptor` | Enables the [`Trace`](adaptors::Trace) bot adaptor. | -| `erased` | Enables the [`ErasedRequester`](adaptors::ErasedRequester) bot adaptor. | -| `full` | Enables all the features except `nightly`. | -| `nightly` | Enables nightly-only features (see the [teloxide-core features]). | - -[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 e43af26c..2964cd74 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,7 +38,7 @@ // [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")))] +#![cfg_attr(feature = "nightly", cfg_attr(feature = "nightly", doc = include_str!("features.md")))] // 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 aa31cd4cafcbf2fd0b7956935164a026c7726a14 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Fri, 1 Jul 2022 23:29:39 +0400 Subject: [PATCH 23/71] Reorder features in features.md Former-commit-id: dc9dc8cd13f21e22734e8df8daaeabddeed73e3c --- src/features.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/features.md b/src/features.md index b7ef133b..c0e8b4eb 100644 --- a/src/features.md +++ b/src/features.md @@ -2,13 +2,7 @@ | 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. | | `ctrlc_handler` | Enables the [`Dispatcher::setup_ctrlc_handler`] function (**enabled by default**). | | `auto-send` | Enables the [`AutoSend`](adaptors::AutoSend) bot adaptor (**enabled by default**). | | `throttle` | Enables the [`Throttle`](adaptors::Throttle) bot adaptor. | @@ -17,6 +11,13 @@ | `erased` | Enables the [`ErasedRequester`](adaptors::ErasedRequester) bot adaptor. | | `full` | Enables all the features except `nightly`. | | `nightly` | Enables nightly-only features (see the [teloxide-core features]). | +| `native-tls` | Enables the [`native-tls`] TLS implementation (**enabled by default**). | +| `rustls` | Enables the [`rustls`] TLS implementation. | +| `redis-storage` | Enables the [Redis] storage support for dialogues. | +| `sqlite-storage` | Enables the [Sqlite] storage support for dialogues. | +| `cbor-serializer` | Enables the [CBOR] serializer for dialogues. | +| `bincode-serializer` | Enables the [Bincode] serializer for dialogues. | + [Redis]: https://redis.io/ [Sqlite]: https://www.sqlite.org/ From 35b800073bb287f207c9cfea6383b59972685540 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Fri, 1 Jul 2022 23:31:28 +0400 Subject: [PATCH 24/71] Document webhook* features Former-commit-id: 1458e40b45fb64c648b9f457a664a4355af4c7e1 --- src/features.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/features.md b/src/features.md index c0e8b4eb..1d199a80 100644 --- a/src/features.md +++ b/src/features.md @@ -2,6 +2,8 @@ | Feature | Description | |----------------------|------------------------------------------------------------------------------------| +| `webhooks` | Enables general webhook utilities (almost useless on its own) | +| `webhooks-axum` | Enables webhook implementation based on axum framework | | `macros` | Re-exports macros from [`teloxide-macros`]. | | `ctrlc_handler` | Enables the [`Dispatcher::setup_ctrlc_handler`] function (**enabled by default**). | | `auto-send` | Enables the [`AutoSend`](adaptors::AutoSend) bot adaptor (**enabled by default**). | From 4be7e5a8124938151f702fde0f6bb474701f66ff Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Fri, 1 Jul 2022 23:36:39 +0400 Subject: [PATCH 25/71] Update changelog Former-commit-id: 88ddf7f7a296b17e5ee431d44904df01a917e166 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 53ff1e5e..5b0647a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## unreleased +### Added + +- Security checks based on `secret_token` param of `set_webhook` to built-in webhooks + ### Fixed - `Dispatcher` no longer "leaks" memory for every inactive user ([PR 657](https://github.com/teloxide/teloxide/pull/657)). From f46358878b46cbdb59ab308538dea53f206b59be Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Sun, 3 Jul 2022 13:13:34 +0400 Subject: [PATCH 26/71] Clippy :| Former-commit-id: e8ce86df8b206c47ee170e122c900f7d64fbf9ac --- src/dispatching/update_listeners/webhooks.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dispatching/update_listeners/webhooks.rs b/src/dispatching/update_listeners/webhooks.rs index 828f2683..e68cdd90 100644 --- a/src/dispatching/update_listeners/webhooks.rs +++ b/src/dispatching/update_listeners/webhooks.rs @@ -170,7 +170,7 @@ fn check_secret(bytes: &[u8]) -> Result<&[u8], &'static str> { let len = bytes.len(); // Check that length is in bounds - if !(1 <= len && len <= 256) { + if !(1..=256).contains(&len) { return Err("secret token length must be in range 1..=256"); } From 088d5f9912e44efad51fa01c5db2c31314ca01ed Mon Sep 17 00:00:00 2001 From: Waffle Maybe Date: Mon, 4 Jul 2022 19:34:11 +0400 Subject: [PATCH 27/71] **Actually** don't build examples with MSRV on CI :| Former-commit-id: 7a8e1e0e3d8cee9c26dbc39a4f99846b9c195f97 --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 65b4c62f..dba02ce0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -102,11 +102,12 @@ jobs: - name: Cache Dependencies uses: Swatinem/rust-cache@v1 + # NB. Don't test (build) examples so we can use non-msrv features in them (--tests/--doc) - name: Compile uses: actions-rs/cargo@v1 with: command: test - args: --no-run --verbose ${{ matrix.features }} + args: --tests --no-run --verbose ${{ matrix.features }} - name: Setup redis run: | @@ -115,7 +116,6 @@ jobs: redis-server --port 7778 > /dev/null & redis-server --port 7779 > /dev/null & - # NB. Don't test (build) examples so we can use non-msrv features in them - name: Test unit & integration tests uses: actions-rs/cargo@v1 with: From 6331fe50cb3091af71e2267b7e7eec7182c07358 Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Fri, 1 Jul 2022 02:02:45 +0600 Subject: [PATCH 28/71] Use `#[default]` for `State::Start` in dialogues Former-commit-id: 14e9fd21971180c75bf1068904092acfe839c1e9 --- examples/db_remember.rs | 9 ++------- examples/dialogue.rs | 18 +++++++++--------- examples/purchase.rs | 13 +++++-------- src/dispatching/dialogue.rs | 3 ++- 4 files changed, 18 insertions(+), 25 deletions(-) diff --git a/examples/db_remember.rs b/examples/db_remember.rs index dfac7bb0..206768d9 100644 --- a/examples/db_remember.rs +++ b/examples/db_remember.rs @@ -14,18 +14,13 @@ type MyDialogue = Dialogue>; type MyStorage = std::sync::Arc>; type HandlerResult = Result<(), Box>; -#[derive(Clone, serde::Serialize, serde::Deserialize)] +#[derive(Clone, Default, serde::Serialize, serde::Deserialize)] pub enum State { + #[default] Start, GotNumber(i32), } -impl Default for State { - fn default() -> Self { - Self::Start - } -} - #[derive(Clone, BotCommands)] #[command(rename = "lowercase", description = "These commands are supported:")] pub enum Command { diff --git a/examples/dialogue.rs b/examples/dialogue.rs index 3156b924..30636924 100644 --- a/examples/dialogue.rs +++ b/examples/dialogue.rs @@ -18,18 +18,18 @@ use teloxide::{dispatching::dialogue::InMemStorage, prelude::*}; type MyDialogue = Dialogue>; type HandlerResult = Result<(), Box>; -#[derive(Clone)] +#[derive(Clone, Default)] pub enum State { + #[default] Start, ReceiveFullName, - ReceiveAge { full_name: String }, - ReceiveLocation { full_name: String, age: u8 }, -} - -impl Default for State { - fn default() -> Self { - Self::Start - } + ReceiveAge { + full_name: String, + }, + ReceiveLocation { + full_name: String, + age: u8, + }, } #[tokio::main] diff --git a/examples/purchase.rs b/examples/purchase.rs index e8333380..74a0cd6d 100644 --- a/examples/purchase.rs +++ b/examples/purchase.rs @@ -25,17 +25,14 @@ use teloxide::{ type MyDialogue = Dialogue>; type HandlerResult = Result<(), Box>; -#[derive(Clone)] +#[derive(Clone, Default)] pub enum State { + #[default] Start, ReceiveFullName, - ReceiveProductChoice { full_name: String }, -} - -impl Default for State { - fn default() -> Self { - Self::Start - } + ReceiveProductChoice { + full_name: String, + }, } #[derive(BotCommands, Clone)] diff --git a/src/dispatching/dialogue.rs b/src/dispatching/dialogue.rs index a6661f1e..83b0239d 100644 --- a/src/dispatching/dialogue.rs +++ b/src/dispatching/dialogue.rs @@ -13,8 +13,9 @@ //! dialogues. Your dialogue state can be represented as an enumeration: //! //! ```ignore -//! #[derive(Clone)] +//! #[derive(Clone, Default)] //! pub enum State { +//! #[default] //! Start, //! ReceiveFullName, //! ReceiveAge { full_name: String }, From c87b6d4f41aa2c11ea1b56dbfdaee969c4f740cb Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Mon, 4 Jul 2022 23:52:59 +0400 Subject: [PATCH 29/71] Mark `webhooks::Options` as `#[must_use]` Former-commit-id: db59b476749d9a8922c4a0618aa38ffab5f020d9 --- src/dispatching/update_listeners/webhooks.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dispatching/update_listeners/webhooks.rs b/src/dispatching/update_listeners/webhooks.rs index e68cdd90..3cd3d503 100644 --- a/src/dispatching/update_listeners/webhooks.rs +++ b/src/dispatching/update_listeners/webhooks.rs @@ -4,6 +4,7 @@ use std::net::SocketAddr; use crate::{requests::Requester, types::InputFile}; /// Options related to setting up webhooks. +#[must_use] pub struct Options { /// Local address to listen to. pub address: SocketAddr, From 9061d8e347fafa3fd2d7beef35a8b527a0454e52 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Mon, 4 Jul 2022 23:58:46 +0400 Subject: [PATCH 30/71] Remove useless comments Former-commit-id: b849067378853f654fc04bbdc00cb8ed1001a0ae --- src/dispatching/update_listeners/webhooks.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/dispatching/update_listeners/webhooks.rs b/src/dispatching/update_listeners/webhooks.rs index 3cd3d503..ef94fec4 100644 --- a/src/dispatching/update_listeners/webhooks.rs +++ b/src/dispatching/update_listeners/webhooks.rs @@ -170,12 +170,10 @@ fn gen_secret_token() -> String { fn check_secret(bytes: &[u8]) -> Result<&[u8], &'static str> { let len = bytes.len(); - // Check that length is in bounds if !(1..=256).contains(&len) { return Err("secret token length must be in range 1..=256"); } - // Check that all characters of the secret are supported by telegram let is_not_supported = |c: &_| !matches!(c, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_' | b'-'); if bytes.iter().any(is_not_supported) { From a839b47106589464bc88f018ebd196c52cf87729 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Mon, 27 Jun 2022 00:24:54 +0400 Subject: [PATCH 31/71] Add polling builder Former-commit-id: 6729c965fa0a74585cefefdb1d09c1b0b446db4f --- src/dispatching/update_listeners.rs | 2 +- src/dispatching/update_listeners/polling.rs | 84 ++++++++++++++++++--- 2 files changed, 75 insertions(+), 11 deletions(-) diff --git a/src/dispatching/update_listeners.rs b/src/dispatching/update_listeners.rs index f3e358c5..206e90f9 100644 --- a/src/dispatching/update_listeners.rs +++ b/src/dispatching/update_listeners.rs @@ -43,7 +43,7 @@ mod polling; mod stateful_listener; pub use self::{ - polling::{polling, polling_default}, + polling::{polling, polling_builder, polling_default, PollingBuilder}, stateful_listener::StatefulListener, }; diff --git a/src/dispatching/update_listeners/polling.rs b/src/dispatching/update_listeners/polling.rs index 3987779d..e229be98 100644 --- a/src/dispatching/update_listeners/polling.rs +++ b/src/dispatching/update_listeners/polling.rs @@ -15,20 +15,84 @@ use crate::{ types::{AllowedUpdate, 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 +/// Builder for polling update listener. +pub struct PollingBuilder { + bot: R, + timeout: Option, + limit: Option, + allowed_updates: Option>, +} + +impl PollingBuilder where R: Requester + Send + 'static, ::GetUpdates: Send, { - delete_webhook_if_setup(&requester).await; - polling(requester, Some(Duration::from_secs(10)), None, None) + /// Set timeout. + pub fn timeout(self, timeout: Duration) -> Self { + Self { timeout: Some(timeout), ..self } + } + + /// Set limit. + /// + /// ## Panics + /// + /// If `limit` is greater than 100. + #[track_caller] + pub fn limit(self, limit: u8) -> Self { + assert!(limit <= 100, "Maximum limit is 100"); + + Self { limit: Some(limit), ..self } + } + + /// Set allowed updates. + /// + /// ## Note + /// + /// Teloxide normally (when using [`Dispatcher`] or repls) sets this + /// automatically. + /// + /// [`Dispatcher`]: crate::dispatching::Dispatcher + pub fn allowed_updates(self, allowed_updates: Vec) -> Self { + Self { allowed_updates: Some(allowed_updates), ..self } + } + + /// Deletes webhook if it was set up. + pub async fn delete_webhook(self) -> Self { + delete_webhook_if_setup(&self.bot).await; + + self + } + + /// Creates a polling update listener. + pub fn build(self) -> impl UpdateListener { + let Self { bot, timeout, limit, allowed_updates } = self; + polling(bot, timeout, limit, allowed_updates) + } +} + +/// Returns a builder for polling update listener. +pub fn polling_builder(bot: R) -> PollingBuilder +where + R: Requester + Send + 'static, + ::GetUpdates: Send, +{ + PollingBuilder { bot, timeout: None, limit: None, allowed_updates: None } +} + +/// Returns a long polling update listener with `timeout` of 10 seconds. +/// +/// See also: [`polling_builder`]. +/// +/// ## Notes +/// +/// This function will automatically delete a webhook if it was set up. +pub async fn polling_default(bot: R) -> impl UpdateListener +where + R: Requester + Send + 'static, + ::GetUpdates: Send, +{ + polling_builder(bot).timeout(Duration::from_secs(10)).delete_webhook().await.build() } #[cfg_attr(doc, aquamarine::aquamarine)] From 2ceccdf442d4bb38ba0a1027534d4e757ac872dd Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Mon, 27 Jun 2022 02:03:34 +0400 Subject: [PATCH 32/71] Implement polling stream by hand IMO it's actually clearer & nicer than the old impl. +The types are now nameable. Former-commit-id: 82fc756aaba5f5dde03fd186b1fcddd3f938b09e --- src/dispatching/update_listeners/polling.rs | 244 ++++++++++++-------- 1 file changed, 151 insertions(+), 93 deletions(-) diff --git a/src/dispatching/update_listeners/polling.rs b/src/dispatching/update_listeners/polling.rs index e229be98..50f570ee 100644 --- a/src/dispatching/update_listeners/polling.rs +++ b/src/dispatching/update_listeners/polling.rs @@ -1,16 +1,22 @@ -use std::{convert::TryInto, time::Duration}; - -use futures::{ - future::{ready, Either}, - stream::{self, Stream, StreamExt}, +use std::{ + convert::TryInto, + future::Future, + pin::Pin, + task::{ + self, + Poll::{self, Ready}, + }, + time::Duration, + vec, }; +use futures::{ready, stream::Stream}; + use crate::{ dispatching::{ stop_token::{AsyncStopFlag, AsyncStopToken}, - update_listeners::{stateful_listener::StatefulListener, UpdateListener}, + update_listeners::{AsUpdateStream, UpdateListener}, }, - payloads::{GetUpdates, GetUpdatesSetters as _}, requests::{HasPayload, Request, Requester}, types::{AllowedUpdate, Update}, }; @@ -197,93 +203,8 @@ where R: Requester + Send + 'static, ::GetUpdates: Send, { - struct State { - bot: B, - timeout: Option, - limit: Option, - allowed_updates: Option>, - offset: i32, - flag: AsyncStopFlag, - token: AsyncStopToken, - force_stop: bool, - } - - fn stream(st: &mut State) -> impl Stream> + Send + '_ - where - B: Requester + Send, - ::GetUpdates: Send, - { - stream::unfold(st, move |state| async move { - let State { timeout, limit, allowed_updates, bot, offset, flag, force_stop, .. } = - &mut *state; - - if *force_stop { - return None; - } - - if flag.is_stopped() { - let mut req = bot.get_updates().offset(*offset).timeout(0).limit(1); - req.payload_mut().allowed_updates = allowed_updates.take(); - - return match req.send().await { - Ok(_) => None, - Err(err) => { - // Prevents infinite retries, see https://github.com/teloxide/teloxide/issues/496 - *force_stop = true; - - Some((Either::Left(stream::once(ready(Err(err)))), state)) - } - }; - } - - let mut req = bot.get_updates(); - *req.payload_mut() = GetUpdates { - offset: Some(*offset), - timeout: *timeout, - limit: *limit, - allowed_updates: allowed_updates.take(), - }; - - match req.send().await { - Ok(updates) => { - // Set offset to the last update's id + 1 - if let Some(upd) = updates.last() { - *offset = upd.id + 1; - } - - let updates = updates.into_iter().map(Ok); - Some((Either::Right(stream::iter(updates)), state)) - } - Err(err) => Some((Either::Left(stream::once(ready(Err(err)))), state)), - } - }) - .flatten() - } - let (token, flag) = AsyncStopToken::new_pair(); - - let state = State { - bot, - timeout: timeout.map(|t| t.as_secs().try_into().expect("timeout is too big")), - limit, - allowed_updates, - offset: 0, - flag, - token, - force_stop: false, - }; - - 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::new_with_hints(state, stream, stop_token, hint_allowed_updates, timeout_hint) + Polling { bot, timeout, limit, allowed_updates, flag, token } } async fn delete_webhook_if_setup(requester: &R) @@ -307,6 +228,143 @@ where } } +struct Polling { + bot: B, + timeout: Option, + limit: Option, + allowed_updates: Option>, + flag: AsyncStopFlag, + token: AsyncStopToken, +} + +#[pin_project::pin_project] +struct PollingStream<'a, B: Requester> { + /// Parent structure + polling: &'a mut Polling, + + /// Timeout parameter for normal `get_updates()` calls. + timeout: Option, + /// Allowed updates parameter for the first `get_updates()` call. + allowed_updates: Option>, + /// Offset parameter for normal `get_updates()` calls. + offset: i32, + + /// If this is set, return `None` from `poll_next` immediately. + force_stop: bool, + /// If true we've sent last `get_updates()` call for graceful shutdown. + stopping: bool, + + /// Buffer of updates to be yielded. + buffer: vec::IntoIter, + + /// In-flight `get_updates()` call. + #[pin] + in_flight: Option<::Send>, +} + +impl UpdateListener for Polling { + type StopToken = AsyncStopToken; + + fn stop_token(&mut self) -> Self::StopToken { + self.token.clone() + } + + fn hint_allowed_updates(&mut self, hint: &mut dyn Iterator) { + // TODO: we should probably warn if there already were different allowed updates + // before + self.allowed_updates = Some(hint.collect()); + } + + fn timeout_hint(&self) -> Option { + self.timeout + } +} + +impl<'a, B: Requester + Send + 'a> AsUpdateStream<'a, B::Err> for Polling { + type Stream = PollingStream<'a, B>; + + fn as_stream(&'a mut self) -> Self::Stream { + let timeout = self.timeout.map(|t| t.as_secs().try_into().expect("timeout is too big")); + let allowed_updates = self.allowed_updates.clone(); + PollingStream { + polling: self, + timeout, + allowed_updates, + offset: 0, + force_stop: false, + stopping: false, + buffer: Vec::new().into_iter(), + in_flight: None, + } + } +} + +impl Stream for PollingStream<'_, B> { + type Item = Result; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll> { + let mut this = self.as_mut().project(); + + if *this.force_stop { + return Ready(None); + } + + // Poll in-flight future until completion + if let Some(in_flight) = this.in_flight.as_mut().as_pin_mut() { + let res = ready!(in_flight.poll(cx)); + this.in_flight.set(None); + + match res { + Ok(_) if *this.stopping => return Ready(None), + Err(err) if *this.stopping => { + // Prevents infinite retries, see https://github.com/teloxide/teloxide/issues/496 + *this.force_stop = true; + + return Ready(Some(Err(err))); + } + Ok(updates) => { + if let Some(upd) = updates.last() { + *this.offset = upd.id + 1; + } + + *this.buffer = updates.into_iter(); + } + Err(err) => return Ready(Some(Err(err))), + } + } + + // If there are any buffered updates, return one + if let Some(upd) = this.buffer.next() { + return Ready(Some(Ok(upd))); + } + + // When stopping we set `timeout = 0` and `limit = 1` so that `get_updates()` + // set last seen update (offset) and return immediately + let (timeout, limit) = if this.polling.flag.is_stopped() { + *this.stopping = true; + (Some(0), Some(1)) + } else { + (*this.timeout, this.polling.limit) + }; + + let req = this + .polling + .bot + .get_updates() + .with_payload_mut(|pay| { + pay.offset = Some(*this.offset); + pay.timeout = timeout; + pay.limit = limit; + pay.allowed_updates = this.allowed_updates.take(); + }) + .send(); + this.in_flight.set(Some(req)); + + // Recurse to poll `self.in_flight` + self.poll_next(cx) + } +} + #[test] fn polling_is_send() { use crate::dispatching::update_listeners::AsUpdateStream; From e51c4c774c08f2aadebd73b7fdc13986f2c8771d Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Mon, 27 Jun 2022 02:06:29 +0400 Subject: [PATCH 33/71] Return named `Polling<_>` type from polling* functions This replaces the `impl UpdateListener` and makes using polling nicer. Former-commit-id: db417caa528119ba1deff929226960a4f909b94d --- src/dispatching/update_listeners/polling.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/dispatching/update_listeners/polling.rs b/src/dispatching/update_listeners/polling.rs index 50f570ee..d827e0cf 100644 --- a/src/dispatching/update_listeners/polling.rs +++ b/src/dispatching/update_listeners/polling.rs @@ -71,7 +71,7 @@ where } /// Creates a polling update listener. - pub fn build(self) -> impl UpdateListener { + pub fn build(self) -> Polling { let Self { bot, timeout, limit, allowed_updates } = self; polling(bot, timeout, limit, allowed_updates) } @@ -93,7 +93,7 @@ where /// ## Notes /// /// This function will automatically delete a webhook if it was set up. -pub async fn polling_default(bot: R) -> impl UpdateListener +pub async fn polling_default(bot: R) -> Polling where R: Requester + Send + 'static, ::GetUpdates: Send, @@ -198,7 +198,7 @@ pub fn polling( timeout: Option, limit: Option, allowed_updates: Option>, -) -> impl UpdateListener +) -> Polling where R: Requester + Send + 'static, ::GetUpdates: Send, @@ -228,7 +228,7 @@ where } } -struct Polling { +pub struct Polling { bot: B, timeout: Option, limit: Option, @@ -238,7 +238,7 @@ struct Polling { } #[pin_project::pin_project] -struct PollingStream<'a, B: Requester> { +pub struct PollingStream<'a, B: Requester> { /// Parent structure polling: &'a mut Polling, From 060886737fccc02794b66f828700ebabd223c419 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Mon, 27 Jun 2022 02:28:59 +0400 Subject: [PATCH 34/71] Deprecate `polling()`, expose `Polling{,Builder}` and fix docs Former-commit-id: e0e5da30baad1f93e263894f8048f7b1d7b8040e --- src/dispatching/update_listeners.rs | 3 +- src/dispatching/update_listeners/polling.rs | 164 ++++++++++---------- 2 files changed, 84 insertions(+), 83 deletions(-) diff --git a/src/dispatching/update_listeners.rs b/src/dispatching/update_listeners.rs index 206e90f9..677f681e 100644 --- a/src/dispatching/update_listeners.rs +++ b/src/dispatching/update_listeners.rs @@ -42,8 +42,9 @@ use crate::{ mod polling; mod stateful_listener; +#[allow(deprecated)] pub use self::{ - polling::{polling, polling_builder, polling_default, PollingBuilder}, + polling::{polling, polling_builder, polling_default, Polling, PollingBuilder, PollingStream}, stateful_listener::StatefulListener, }; diff --git a/src/dispatching/update_listeners/polling.rs b/src/dispatching/update_listeners/polling.rs index d827e0cf..00a5c013 100644 --- a/src/dispatching/update_listeners/polling.rs +++ b/src/dispatching/update_listeners/polling.rs @@ -34,12 +34,20 @@ where R: Requester + Send + 'static, ::GetUpdates: Send, { - /// Set timeout. + /// A timeout in seconds for polling. + /// + /// ## Note + /// + /// `timeout` should not be bigger than http client timeout, see + /// [`default_reqwest_settings`] for default http client settings. + /// + /// [`default_reqwest_settings`]: crate::net::default_reqwest_settings pub fn timeout(self, timeout: Duration) -> Self { Self { timeout: Some(timeout), ..self } } - /// Set limit. + /// Limit the number of updates to be retrieved at once. Values between + /// 1—100 are accepted. /// /// ## Panics /// @@ -51,14 +59,17 @@ where Self { limit: Some(limit), ..self } } - /// Set allowed updates. + /// A list of the types of updates you want to receive. /// /// ## Note /// - /// Teloxide normally (when using [`Dispatcher`] or repls) sets this - /// automatically. + /// Teloxide normally (when using [`Dispatcher`] or [`repl`]s) sets this + /// automatically via [`hint_allowed_updates`], so you rarely need to use + /// `allowed_updates` explicitly. /// /// [`Dispatcher`]: crate::dispatching::Dispatcher + /// [`repl`]: fn@crate::repl + /// [`hint_allowed_updates`]: crate::dispatching::update_listeners::UpdateListener::hint_allowed_updates pub fn allowed_updates(self, allowed_updates: Vec) -> Self { Self { allowed_updates: Some(allowed_updates), ..self } } @@ -70,10 +81,14 @@ where self } - /// Creates a polling update listener. + /// Returns a long polling update listener with configuration from the + /// builder. + /// + /// See also: [`polling_default`], [`Polling`]. pub fn build(self) -> Polling { let Self { bot, timeout, limit, allowed_updates } = self; - polling(bot, timeout, limit, allowed_updates) + let (token, flag) = AsyncStopToken::new_pair(); + Polling { bot, timeout, limit, allowed_updates, flag, token } } } @@ -101,59 +116,77 @@ where polling_builder(bot).timeout(Duration::from_secs(10)).delete_webhook().await.build() } -#[cfg_attr(doc, aquamarine::aquamarine)] /// Returns a long polling update listener with some additional options. -/// -/// - `bot`: Using this bot, the returned update listener will receive updates. -/// - `timeout`: A timeout in seconds 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). -/// -/// ## Notes -/// -/// - `timeout` should not be bigger than http client timeout, see -/// [`default_reqwest_settings`] for default http client settings. -/// - [`repl`]s and [`Dispatcher`] use [`hint_allowed_updates`] to set -/// `allowed_updates`, so you rarely need to pass `allowed_updates` -/// explicitly. -/// -/// [`default_reqwest_settings`]: teloxide::net::default_reqwest_settings -/// [`repl`]: fn@crate::repl -/// [`Dispatcher`]: crate::dispatching::Dispatcher -/// [`hint_allowed_updates`]: -/// crate::dispatching::update_listeners::UpdateListener::hint_allowed_updates +#[deprecated(since = "0.7.0", note = "use `polling_builder` instead")] +pub fn polling( + bot: R, + timeout: Option, + limit: Option, + allowed_updates: Option>, +) -> Polling +where + R: Requester + Send + 'static, + ::GetUpdates: Send, +{ + let mut builder = polling_builder(bot); + builder.timeout = timeout; + builder.limit = limit; + builder.allowed_updates = allowed_updates; + builder.build() +} + +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_some(); + + if is_webhook_setup { + if let Err(e) = requester.delete_webhook().send().await { + log::error!("Failed to delete a webhook: {:?}", e); + } + } +} + +#[cfg_attr(doc, aquamarine::aquamarine)] +/// A polling update listener. /// /// ## How it works /// -/// Long polling works by repeatedly calling [`Bot::get_updates`][get_updates]. -/// If telegram has any updates, it returns them immediately, otherwise it waits -/// until either it has any updates or `timeout` expires. +/// Long polling works by repeatedly calling +/// [`Bot::get_updates`][get_updates]. If telegram has any updates, it +/// returns them immediately, otherwise it waits until either it has any +/// updates or `timeout` expires. /// -/// Each [`get_updates`][get_updates] call includes an `offset` parameter equal -/// to the latest update id + one, that allows to only receive updates that has -/// not been received before. +/// Each [`get_updates`][get_updates] call includes an `offset` parameter +/// equal to the latest update id + one, that allows to only receive +/// updates that has not been received before. /// -/// When telegram receives a [`get_updates`][get_updates] request with `offset = -/// N` it forgets any updates with id < `N`. When `polling` listener is stopped, -/// it sends [`get_updates`][get_updates] with `timeout = 0, limit = 1` and -/// appropriate `offset`, so future bot restarts won't see updates that were -/// already seen. +/// When telegram receives a [`get_updates`][get_updates] request with +/// `offset = N` it forgets any updates with id < `N`. When `polling` +/// listener is stopped, it sends [`get_updates`][get_updates] with +/// `timeout = 0, limit = 1` and appropriate `offset`, so future bot +/// restarts won't see updates that were already seen. /// -/// Consumers of a `polling` update listener then need to repeatedly call +/// Consumers of a [`Polling`] update listener then need to repeatedly call /// [`futures::StreamExt::next`] to get the updates. /// -/// Here is an example diagram that shows these interactions between consumers -/// like [`Dispatcher`], `polling` update listener and telegram. +/// Here is an example diagram that shows these interactions between +/// consumers like [`Dispatcher`], [`Polling`] update listener and +/// telegram. /// /// ```mermaid /// sequenceDiagram /// participant C as Consumer -/// participant P as polling +/// participant P as Polling /// participant T as Telegram /// /// link C: Dispatcher @ ../struct.Dispatcher.html @@ -193,41 +226,7 @@ where /// ``` /// /// [get_updates]: crate::requests::Requester::get_updates -pub fn polling( - bot: R, - timeout: Option, - limit: Option, - allowed_updates: Option>, -) -> Polling -where - R: Requester + Send + 'static, - ::GetUpdates: Send, -{ - let (token, flag) = AsyncStopToken::new_pair(); - Polling { bot, timeout, limit, allowed_updates, flag, token } -} - -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_some(); - - if is_webhook_setup { - if let Err(e) = requester.delete_webhook().send().await { - log::error!("Failed to delete a webhook: {:?}", e); - } - } -} - +/// [`Dispatcher`]: crate::dispatching::Dispatcher pub struct Polling { bot: B, timeout: Option, @@ -370,6 +369,7 @@ fn polling_is_send() { use crate::dispatching::update_listeners::AsUpdateStream; let bot = crate::Bot::new("TOKEN"); + #[allow(deprecated)] let mut polling = polling(bot, None, None, None); assert_send(&polling); From 60fc833108a41b07016ac12d00f3b2270e115a29 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Mon, 27 Jun 2022 02:36:37 +0400 Subject: [PATCH 35/71] Remove useless use Former-commit-id: 58bbf8e7376625ea70837905411eb6b93270a894 --- src/dispatching/update_listeners/polling.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/dispatching/update_listeners/polling.rs b/src/dispatching/update_listeners/polling.rs index 00a5c013..1fc6273f 100644 --- a/src/dispatching/update_listeners/polling.rs +++ b/src/dispatching/update_listeners/polling.rs @@ -366,8 +366,6 @@ impl Stream for PollingStream<'_, B> { #[test] fn polling_is_send() { - use crate::dispatching::update_listeners::AsUpdateStream; - let bot = crate::Bot::new("TOKEN"); #[allow(deprecated)] let mut polling = polling(bot, None, None, None); From f9da86f88138738c0708cba0b4dd4c0163e294d3 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Mon, 27 Jun 2022 02:58:48 +0400 Subject: [PATCH 36/71] Add option to drop pending updates w/ polling Former-commit-id: 612f47d242f89ffb256ed387ca2253231f8693fc --- src/dispatching/update_listeners/polling.rs | 50 ++++++++++++++++----- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/src/dispatching/update_listeners/polling.rs b/src/dispatching/update_listeners/polling.rs index 1fc6273f..e3ad7ab5 100644 --- a/src/dispatching/update_listeners/polling.rs +++ b/src/dispatching/update_listeners/polling.rs @@ -27,6 +27,7 @@ pub struct PollingBuilder { timeout: Option, limit: Option, allowed_updates: Option>, + drop_pending_updates: bool, } impl PollingBuilder @@ -74,6 +75,11 @@ where Self { allowed_updates: Some(allowed_updates), ..self } } + /// Drops pending updates. + pub fn drop_pending_updates(self) -> Self { + Self { drop_pending_updates: true, ..self } + } + /// Deletes webhook if it was set up. pub async fn delete_webhook(self) -> Self { delete_webhook_if_setup(&self.bot).await; @@ -86,9 +92,9 @@ where /// /// See also: [`polling_default`], [`Polling`]. pub fn build(self) -> Polling { - let Self { bot, timeout, limit, allowed_updates } = self; + let Self { bot, timeout, limit, allowed_updates, drop_pending_updates } = self; let (token, flag) = AsyncStopToken::new_pair(); - Polling { bot, timeout, limit, allowed_updates, flag, token } + Polling { bot, timeout, limit, allowed_updates, drop_pending_updates, flag, token } } } @@ -98,7 +104,13 @@ where R: Requester + Send + 'static, ::GetUpdates: Send, { - PollingBuilder { bot, timeout: None, limit: None, allowed_updates: None } + PollingBuilder { + bot, + timeout: None, + limit: None, + allowed_updates: None, + drop_pending_updates: false, + } } /// Returns a long polling update listener with `timeout` of 10 seconds. @@ -232,6 +244,7 @@ pub struct Polling { timeout: Option, limit: Option, allowed_updates: Option>, + drop_pending_updates: bool, flag: AsyncStopFlag, token: AsyncStopToken, } @@ -241,6 +254,9 @@ pub struct PollingStream<'a, B: Requester> { /// Parent structure polling: &'a mut Polling, + /// Whatever to drop pending updates or not. + drop_pending_updates: bool, + /// Timeout parameter for normal `get_updates()` calls. timeout: Option, /// Allowed updates parameter for the first `get_updates()` call. @@ -285,8 +301,10 @@ impl<'a, B: Requester + Send + 'a> AsUpdateStream<'a, B::Err> for Polling { fn as_stream(&'a mut self) -> Self::Stream { let timeout = self.timeout.map(|t| t.as_secs().try_into().expect("timeout is too big")); let allowed_updates = self.allowed_updates.clone(); + let drop_pending_updates = self.drop_pending_updates; PollingStream { polling: self, + drop_pending_updates, timeout, allowed_updates, offset: 0, @@ -326,7 +344,10 @@ impl Stream for PollingStream<'_, B> { *this.offset = upd.id + 1; } - *this.buffer = updates.into_iter(); + match *this.drop_pending_updates { + false => *this.buffer = updates.into_iter(), + true => *this.drop_pending_updates = false, + } } Err(err) => return Ready(Some(Err(err))), } @@ -337,13 +358,18 @@ impl Stream for PollingStream<'_, B> { return Ready(Some(Ok(upd))); } - // When stopping we set `timeout = 0` and `limit = 1` so that `get_updates()` - // set last seen update (offset) and return immediately - let (timeout, limit) = if this.polling.flag.is_stopped() { - *this.stopping = true; - (Some(0), Some(1)) - } else { - (*this.timeout, this.polling.limit) + *this.stopping = this.polling.flag.is_stopped(); + let (offset, limit, timeout) = match (this.stopping, this.drop_pending_updates) { + // Normal `get_updates()` call + (false, false) => (*this.offset, this.polling.limit, *this.timeout), + // Graceful shutdown `get_updates()` call (shutdown takes priority over dropping pending + // updates) + // + // When stopping we set `timeout = 0` and `limit = 1` so that `get_updates()` + // set last seen update (offset) and return immediately + (true, _) => (*this.offset, Some(1), Some(0)), + // Drop pending updates + (_, true) => (-1, Some(1), Some(0)), }; let req = this @@ -351,7 +377,7 @@ impl Stream for PollingStream<'_, B> { .bot .get_updates() .with_payload_mut(|pay| { - pay.offset = Some(*this.offset); + pay.offset = Some(offset); pay.timeout = timeout; pay.limit = limit; pay.allowed_updates = this.allowed_updates.take(); From 09d189f689da882d273bcfe5fdf5d8279334917b Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Mon, 27 Jun 2022 03:09:19 +0400 Subject: [PATCH 37/71] Panic for limit=0 Former-commit-id: b29d60657fcbca67e5e74520aba5671084d9898b --- src/dispatching/update_listeners/polling.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/dispatching/update_listeners/polling.rs b/src/dispatching/update_listeners/polling.rs index e3ad7ab5..74c99bba 100644 --- a/src/dispatching/update_listeners/polling.rs +++ b/src/dispatching/update_listeners/polling.rs @@ -52,10 +52,11 @@ where /// /// ## Panics /// - /// If `limit` is greater than 100. + /// If `limit` is 0 or greater than 100. #[track_caller] pub fn limit(self, limit: u8) -> Self { - assert!(limit <= 100, "Maximum limit is 100"); + assert_ne!(limit, 0, "limit can't be 0"); + assert!(limit <= 100, "maximum limit is 100, can't set limit to `{limit}`"); Self { limit: Some(limit), ..self } } From a07af45ec7a417f35c6ee7592bf0766c5d395be4 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Mon, 27 Jun 2022 03:15:52 +0400 Subject: [PATCH 38/71] Update changelog Former-commit-id: 4f2e723d56bc3d08b4ad6e280b2893f9a0d6c186 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b0647a8..2baea730 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Security checks based on `secret_token` param of `set_webhook` to built-in webhooks +- `dispatching::update_listeners::{polling_builder, PollingBuilder, Polling, PollingStream}` ### Fixed @@ -17,6 +18,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Add the `Key: Clone` requirement for `impl Dispatcher` [**BC**]. + - `dispatching::update_listeners::{polling_default, polling}` now return a named, `Polling<_>` type + +### Deprecated + +- `dispatching::update_listeners::polling` ## 0.9.2 - 2022-06-07 From 79e393c44526f3417b1028a02de8302b972ac56b Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Mon, 27 Jun 2022 03:23:04 +0400 Subject: [PATCH 39/71] Correct deprecated since version Former-commit-id: 195d34ba0c56fd78422780994cfcacddca33dda8 --- src/dispatching/update_listeners/polling.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dispatching/update_listeners/polling.rs b/src/dispatching/update_listeners/polling.rs index 74c99bba..f71b8d1c 100644 --- a/src/dispatching/update_listeners/polling.rs +++ b/src/dispatching/update_listeners/polling.rs @@ -130,7 +130,7 @@ where } /// Returns a long polling update listener with some additional options. -#[deprecated(since = "0.7.0", note = "use `polling_builder` instead")] +#[deprecated(since = "0.10.0", note = "use `polling_builder` instead")] pub fn polling( bot: R, timeout: Option, From cce0710c8ccc0846df35def421a7aca0f4fe5161 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Mon, 27 Jun 2022 03:28:23 +0400 Subject: [PATCH 40/71] Move `polling_builder` => `Polling::builder` Former-commit-id: 79f6cf4ee9ff0a13613b80cd3f690382916d91ca --- CHANGELOG.md | 2 +- src/dispatching/update_listeners.rs | 2 +- src/dispatching/update_listeners/polling.rs | 38 +++++++++++---------- 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2baea730..67ef18dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Security checks based on `secret_token` param of `set_webhook` to built-in webhooks -- `dispatching::update_listeners::{polling_builder, PollingBuilder, Polling, PollingStream}` +- `dispatching::update_listeners::{PollingBuilder, Polling, PollingStream}` ### Fixed diff --git a/src/dispatching/update_listeners.rs b/src/dispatching/update_listeners.rs index 677f681e..03a9fc5c 100644 --- a/src/dispatching/update_listeners.rs +++ b/src/dispatching/update_listeners.rs @@ -44,7 +44,7 @@ mod stateful_listener; #[allow(deprecated)] pub use self::{ - polling::{polling, polling_builder, polling_default, Polling, PollingBuilder, PollingStream}, + polling::{polling, polling_default, Polling, PollingBuilder, PollingStream}, stateful_listener::StatefulListener, }; diff --git a/src/dispatching/update_listeners/polling.rs b/src/dispatching/update_listeners/polling.rs index f71b8d1c..c41eeabe 100644 --- a/src/dispatching/update_listeners/polling.rs +++ b/src/dispatching/update_listeners/polling.rs @@ -99,21 +99,6 @@ where } } -/// Returns a builder for polling update listener. -pub fn polling_builder(bot: R) -> PollingBuilder -where - R: Requester + Send + 'static, - ::GetUpdates: Send, -{ - PollingBuilder { - bot, - timeout: None, - limit: None, - allowed_updates: None, - drop_pending_updates: false, - } -} - /// Returns a long polling update listener with `timeout` of 10 seconds. /// /// See also: [`polling_builder`]. @@ -126,11 +111,11 @@ where R: Requester + Send + 'static, ::GetUpdates: Send, { - polling_builder(bot).timeout(Duration::from_secs(10)).delete_webhook().await.build() + Polling::builder(bot).timeout(Duration::from_secs(10)).delete_webhook().await.build() } /// Returns a long polling update listener with some additional options. -#[deprecated(since = "0.10.0", note = "use `polling_builder` instead")] +#[deprecated(since = "0.10.0", note = "use `Polling::builder()` instead")] pub fn polling( bot: R, timeout: Option, @@ -141,7 +126,7 @@ where R: Requester + Send + 'static, ::GetUpdates: Send, { - let mut builder = polling_builder(bot); + let mut builder = Polling::builder(bot); builder.timeout = timeout; builder.limit = limit; builder.allowed_updates = allowed_updates; @@ -250,6 +235,23 @@ pub struct Polling { token: AsyncStopToken, } +impl Polling +where + R: Requester + Send + 'static, + ::GetUpdates: Send, +{ + /// Returns a builder for polling update listener. + pub fn builder(bot: R) -> PollingBuilder { + PollingBuilder { + bot, + timeout: None, + limit: None, + allowed_updates: None, + drop_pending_updates: false, + } + } +} + #[pin_project::pin_project] pub struct PollingStream<'a, B: Requester> { /// Parent structure From 4e5e7a145d133516577964c771b2b3aaf76f0cb3 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Mon, 27 Jun 2022 03:32:57 +0400 Subject: [PATCH 41/71] Fix docs Former-commit-id: ee52bb28b4d8c1781743f85bcfe044f1f135ef9d --- src/dispatching/update_listeners/polling.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/dispatching/update_listeners/polling.rs b/src/dispatching/update_listeners/polling.rs index c41eeabe..3eeba061 100644 --- a/src/dispatching/update_listeners/polling.rs +++ b/src/dispatching/update_listeners/polling.rs @@ -22,6 +22,8 @@ use crate::{ }; /// Builder for polling update listener. +/// +/// Can be created by [`Polling::builder`]. pub struct PollingBuilder { bot: R, timeout: Option, @@ -101,7 +103,7 @@ where /// Returns a long polling update listener with `timeout` of 10 seconds. /// -/// See also: [`polling_builder`]. +/// See also: [`Polling::builder`]. /// /// ## Notes /// From f0608da9c3648c6d8a2783e90ab449e798e7c517 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Mon, 4 Jul 2022 23:39:04 +0400 Subject: [PATCH 42/71] When creating `Polling` assert that it's an `UpdateListener` Former-commit-id: 08da55f54f827af1944053a1897918f0d6075ac8 --- src/dispatching/update_listeners.rs | 8 ++++++++ src/dispatching/update_listeners/polling.rs | 14 ++++++++++---- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/dispatching/update_listeners.rs b/src/dispatching/update_listeners.rs index 03a9fc5c..9c4abc2f 100644 --- a/src/dispatching/update_listeners.rs +++ b/src/dispatching/update_listeners.rs @@ -126,3 +126,11 @@ pub trait AsUpdateStream<'a, E> { /// [`Stream`]: AsUpdateStream::Stream fn as_stream(&'a mut self) -> Self::Stream; } + +#[inline(always)] +pub(crate) fn assert_update_listener(listener: L) -> L +where + L: UpdateListener, +{ + listener +} diff --git a/src/dispatching/update_listeners/polling.rs b/src/dispatching/update_listeners/polling.rs index 3eeba061..9a2ee7d6 100644 --- a/src/dispatching/update_listeners/polling.rs +++ b/src/dispatching/update_listeners/polling.rs @@ -15,7 +15,7 @@ use futures::{ready, stream::Stream}; use crate::{ dispatching::{ stop_token::{AsyncStopFlag, AsyncStopToken}, - update_listeners::{AsUpdateStream, UpdateListener}, + update_listeners::{assert_update_listener, AsUpdateStream, UpdateListener}, }, requests::{HasPayload, Request, Requester}, types::{AllowedUpdate, Update}, @@ -97,7 +97,10 @@ where pub fn build(self) -> Polling { let Self { bot, timeout, limit, allowed_updates, drop_pending_updates } = self; let (token, flag) = AsyncStopToken::new_pair(); - Polling { bot, timeout, limit, allowed_updates, drop_pending_updates, flag, token } + let polling = + Polling { bot, timeout, limit, allowed_updates, drop_pending_updates, flag, token }; + + assert_update_listener(polling) } } @@ -113,7 +116,10 @@ where R: Requester + Send + 'static, ::GetUpdates: Send, { - Polling::builder(bot).timeout(Duration::from_secs(10)).delete_webhook().await.build() + let polling = + Polling::builder(bot).timeout(Duration::from_secs(10)).delete_webhook().await.build(); + + assert_update_listener(polling) } /// Returns a long polling update listener with some additional options. @@ -132,7 +138,7 @@ where builder.timeout = timeout; builder.limit = limit; builder.allowed_updates = allowed_updates; - builder.build() + assert_update_listener(builder.build()) } async fn delete_webhook_if_setup(requester: &R) From 1ff0440762431f190468e088ffd3eecd28f23496 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Mon, 4 Jul 2022 23:45:49 +0400 Subject: [PATCH 43/71] Mark `Polling` and `PollingBuilder` as `must_use` Former-commit-id: 430df0cefbdbf442b930701125ba7cf01cfdecc0 --- src/dispatching/update_listeners/polling.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/dispatching/update_listeners/polling.rs b/src/dispatching/update_listeners/polling.rs index 9a2ee7d6..906ec98c 100644 --- a/src/dispatching/update_listeners/polling.rs +++ b/src/dispatching/update_listeners/polling.rs @@ -24,6 +24,7 @@ use crate::{ /// Builder for polling update listener. /// /// Can be created by [`Polling::builder`]. +#[must_use = "`PollingBuilder` is a builder and does nothing unless used"] pub struct PollingBuilder { bot: R, timeout: Option, @@ -233,6 +234,7 @@ where /// /// [get_updates]: crate::requests::Requester::get_updates /// [`Dispatcher`]: crate::dispatching::Dispatcher +#[must_use = "`Polling` is an update listener and does nothing unless used"] pub struct Polling { bot: B, timeout: Option, From 8cecf248b240873c438ea4895e3dcd26c3398440 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Tue, 5 Jul 2022 00:07:15 +0400 Subject: [PATCH 44/71] Make fields of `PollingBuilder` public Former-commit-id: cd63dbf1e7d76bd98bfa239e09c357c76d97cb4e --- src/dispatching/update_listeners/polling.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/dispatching/update_listeners/polling.rs b/src/dispatching/update_listeners/polling.rs index 906ec98c..118fc02e 100644 --- a/src/dispatching/update_listeners/polling.rs +++ b/src/dispatching/update_listeners/polling.rs @@ -24,13 +24,14 @@ use crate::{ /// Builder for polling update listener. /// /// Can be created by [`Polling::builder`]. +#[non_exhaustive] #[must_use = "`PollingBuilder` is a builder and does nothing unless used"] pub struct PollingBuilder { - bot: R, - timeout: Option, - limit: Option, - allowed_updates: Option>, - drop_pending_updates: bool, + pub bot: R, + pub timeout: Option, + pub limit: Option, + pub allowed_updates: Option>, + pub drop_pending_updates: bool, } impl PollingBuilder From 651526f2c0f3ab8593bbf3a117169d19bced6f67 Mon Sep 17 00:00:00 2001 From: Raine Virta Date: Thu, 7 Jul 2022 21:58:17 +0200 Subject: [PATCH 45/71] Add tgreddit to Community Bots Former-commit-id: 9fc68dc73f46d5d5e7fd195dbced3464840b47ec --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 55246a54..8a5e386d 100644 --- a/README.md +++ b/README.md @@ -360,6 +360,8 @@ Feel free to propose your own bot to our collection! - [modos189/tg_blackbox_bot](https://gitlab.com/modos189/tg_blackbox_bot) — Anonymous feedback for your Telegram project. This bot in Docker from scratch container. - [0xNima/spacecraft](https://github.com/0xNima/spacecraft) — Yet another telegram bot to downloading Twitter spaces. - [0xNima/Twideo](https://github.com/0xNima/Twideo) — Telegram Bot for downloading videos from Twitter via their links, as well as converting tweets to telegram messages. + - [raine/tgreddit](https://github.com/raine/tgreddit) — A bot that sends the top posts of your favorite subreddits to Telegram. + ## Contributing See [`CONRIBUTING.md`](CONTRIBUTING.md). From 123c550c6ceec6c2bd5944c7f2f56f27390b051b Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Mon, 18 Jul 2022 15:56:25 +0400 Subject: [PATCH 46/71] Add a test for `discussion_648` Former-commit-id: 56f8ef90ecd9405ab6aa1e900652ec1ad1676137 --- src/dispatching/handler_description.rs | 34 ++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/dispatching/handler_description.rs b/src/dispatching/handler_description.rs index cf3b4e4f..5ee6cffa 100644 --- a/src/dispatching/handler_description.rs +++ b/src/dispatching/handler_description.rs @@ -59,3 +59,37 @@ impl HandlerDescription for DpHandlerDescription { Self { allowed: self.allowed.merge_branch(&other.allowed) } } } +#[cfg(test)] +mod tests { + use crate::{ + dispatching::{HandlerExt, UpdateFilterExt}, + types::{AllowedUpdate::*, Update}, + utils::command::BotCommands, + }; + + use crate as teloxide; // fixup for the `BotCommands` macro + + #[derive(BotCommands, Clone)] + #[command(rename = "lowercase")] + enum Cmd { + B, + } + + // + #[test] + fn discussion_648() { + let h = + dptree::entry().branch(Update::filter_my_chat_member().endpoint(|| async {})).branch( + Update::filter_message() + .branch(dptree::entry().filter_command::().endpoint(|| async {})) + .endpoint(|| async {}), + ); + + let mut v = h.description().allowed_updates(); + + // Hash set randomizes element order, so to compare we need to sort + v.sort_by_key(|&a| a as u8); + + assert_eq!(v, [Message, MyChatMember]) + } +} From f6e7c01a45948c3561d2efc6e2413888c737b214 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Mon, 18 Jul 2022 15:56:45 +0400 Subject: [PATCH 47/71] Update dptree Former-commit-id: eb8b384f464df974c9c9d144c3374d18e2f60c43 --- Cargo.toml | 3 +- src/dispatching/handler_description.rs | 66 ++++++++++++++++---------- 2 files changed, 43 insertions(+), 26 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5dfc708b..d3226640 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,7 +64,8 @@ teloxide-macros = { version = "0.6.2", optional = true } serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } -dptree = "0.2.1" +#dptree = "0.2.1" +dptree = { git = "https://github.com/teloxide/dptree.git", rev = "df578e4" } tokio = { version = "1.8", features = ["fs"] } tokio-util = "0.6" diff --git a/src/dispatching/handler_description.rs b/src/dispatching/handler_description.rs index 5ee6cffa..3ed2f621 100644 --- a/src/dispatching/handler_description.rs +++ b/src/dispatching/handler_description.rs @@ -1,44 +1,27 @@ use std::collections::HashSet; -use dptree::{description::EventKind, HandlerDescription}; +use dptree::{ + description::{EventKind, InterestSet}, + HandlerDescription, +}; use teloxide_core::types::AllowedUpdate; /// Handler description that is used by [`Dispatcher`]. /// /// [`Dispatcher`]: crate::dispatching::Dispatcher pub struct DpHandlerDescription { - allowed: EventKind, + allowed: InterestSet, } impl DpHandlerDescription { pub(crate) fn of(allowed: AllowedUpdate) -> Self { let mut set = HashSet::with_capacity(1); - set.insert(allowed); - Self { allowed: EventKind::InterestList(set) } + set.insert(Kind(allowed)); + Self { allowed: InterestSet::new_filter(set) } } pub(crate) fn allowed_updates(&self) -> Vec { - use AllowedUpdate::*; - - match &self.allowed { - EventKind::InterestList(set) => set.iter().copied().collect(), - EventKind::Entry => panic!("No updates were allowed"), - EventKind::UserDefined => vec![ - Message, - EditedMessage, - ChannelPost, - EditedChannelPost, - InlineQuery, - ChosenInlineResult, - CallbackQuery, - ShippingQuery, - PreCheckoutQuery, - Poll, - PollAnswer, - MyChatMember, - ChatMember, - ], - } + self.allowed.observed.iter().map(|Kind(x)| x).copied().collect() } } @@ -59,6 +42,39 @@ impl HandlerDescription for DpHandlerDescription { Self { allowed: self.allowed.merge_branch(&other.allowed) } } } + +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +struct Kind(AllowedUpdate); + +impl EventKind for Kind { + fn full_set() -> HashSet { + use AllowedUpdate::*; + + [ + Message, + EditedMessage, + ChannelPost, + EditedChannelPost, + InlineQuery, + ChosenInlineResult, + CallbackQuery, + ShippingQuery, + PreCheckoutQuery, + Poll, + PollAnswer, + MyChatMember, + ChatMember, + ] + .into_iter() + .map(Kind) + .collect() + } + + fn empty_set() -> HashSet { + HashSet::new() + } +} + #[cfg(test)] mod tests { use crate::{ From cc74e7517d8ba1f182c7008b0d3be458f08739f9 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Tue, 19 Jul 2022 14:30:18 +0400 Subject: [PATCH 48/71] Update deps Former-commit-id: ed88e43afa87842e52b42f688c8ab94875809494 --- Cargo.toml | 20 +++++++++---------- .../update_listeners/webhooks/axum.rs | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d3226640..d5786657 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,18 +57,18 @@ full = [ ] [dependencies] -#teloxide-core = { version = "0.6.0", default-features = false } -teloxide-core = { git = "https://github.com/teloxide/teloxide-core", rev = "b13393d", default-features = false } +teloxide-core = { version = "0.7.0", default-features = false } +#teloxide-core = { git = "https://github.com/teloxide/teloxide-core", rev = "b13393d", default-features = false } teloxide-macros = { version = "0.6.2", optional = true } serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } -#dptree = "0.2.1" -dptree = { git = "https://github.com/teloxide/dptree.git", rev = "df578e4" } +dptree = "0.3.0" +#dptree = { git = "https://github.com/teloxide/dptree.git", rev = "df578e4" } tokio = { version = "1.8", features = ["fs"] } -tokio-util = "0.6" +tokio-util = "0.7" tokio-stream = "0.1.8" url = "2.2.2" @@ -83,17 +83,17 @@ pin-project = "1.0" serde_with_macros = "1.4" aquamarine = "0.1.11" -sqlx = { version = "0.5", optional = true, default-features = false, features = [ +sqlx = { version = "0.6", optional = true, default-features = false, features = [ "runtime-tokio-native-tls", "macros", "sqlite", ] } -redis = { version = "0.20", features = ["tokio-comp"], optional = true } +redis = { version = "0.21", features = ["tokio-comp"], optional = true } serde_cbor = { version = "0.11", optional = true } bincode = { version = "1.3", optional = true } -axum = { version = "0.4.8", optional = true } +axum = { version = "0.5.13", optional = true } tower = { version = "0.4.12", optional = true } -tower-http = { version = "0.2.5", features = ["trace"], optional = true } +tower-http = { version = "0.3.4", features = ["trace"], optional = true } rand = { version = "0.8.5", optional = true } [dev-dependencies] @@ -102,7 +102,7 @@ pretty_env_logger = "0.4.0" serde = "1" serde_json = "1" tokio = { version = "1.8", features = ["fs", "rt-multi-thread", "macros"] } -reqwest = "0.10.4" +reqwest = "0.11.11" chrono = "0.4" tokio-stream = "0.1" diff --git a/src/dispatching/update_listeners/webhooks/axum.rs b/src/dispatching/update_listeners/webhooks/axum.rs index ab08cd92..6c16bdf0 100644 --- a/src/dispatching/update_listeners/webhooks/axum.rs +++ b/src/dispatching/update_listeners/webhooks/axum.rs @@ -272,7 +272,7 @@ impl FromRequest for XTelegramBotApiSecretToken { let res = req .headers_mut() - .and_then(|map| map.remove("x-telegram-bot-api-secret-token")) + .remove("x-telegram-bot-api-secret-token") .map(|header| { check_secret(header.as_bytes()) .map(<_>::to_owned) From a7eff7192b33dcaf4afb2920022507b7e54d466b Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Tue, 19 Jul 2022 14:49:15 +0400 Subject: [PATCH 49/71] bump version Former-commit-id: 6996c7ff87a964efdfd5ee1649d2f89e535495af --- CHANGELOG.md | 3 +++ Cargo.toml | 2 +- MIGRATION_GUIDE.md | 41 ++++++++++++++++++++++++++++++++++++++++- README.md | 6 ++---- 4 files changed, 46 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67ef18dd..4fe36a67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add the `Key: Clone` requirement for `impl Dispatcher` [**BC**]. - `dispatching::update_listeners::{polling_default, polling}` now return a named, `Polling<_>` type + - Update teloxide-core to v0.7.0 with Bot API 6.1 support, see [its changelog][core07c] for more [**BC**] + +[core07c]: https://github.com/teloxide/teloxide-core/blob/master/CHANGELOG.md#070---2022-07-19 ### Deprecated diff --git a/Cargo.toml b/Cargo.toml index d5786657..aa08091a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "teloxide" -version = "0.9.2" +version = "0.10.0" edition = "2021" description = "An elegant Telegram bots framework for Rust" repository = "https://github.com/teloxide/teloxide" diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md index 315cd9f1..dd1af6eb 100644 --- a/MIGRATION_GUIDE.md +++ b/MIGRATION_GUIDE.md @@ -1,6 +1,45 @@ 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.9 -> 0.10 + +### core + +We've added some convenience functions to `InlineKeyboardButton` so it's easier to construct it. Consider using them instead of variants: +```diff +-InlineKeyboardButton::new("text", InlineKeyboardButtonKind::Url(url)) ++InlineKeyboardButton::url("text", url) +``` + +`file_size` fields are now `u32`, you may need to update your code accordingly: + +```diff +-let file_size: u64 = audio.file_size?; ++let file_size: u32 = audio.file_size; +``` + +Some places now use `FileMeta` instead of `File`, you may need to change types. + +`Sticker` and `StickerSet` now has a `kind` field instead of `is_animated` and `is_video`: + +```diff ++use teloxide::types::StickerKind::*; +-match () { ++match sticker.kind { +- _ if sticker.is_animated => /* handle animated */, ++ Animated => /* handle animated */, +- _ if sticker.is_video => /* handle video */, ++ Video => /* handle video */, +- _ => /* handle normal */, ++ Webp => /* handle normal */, +} +``` + +### teloxide + +Teloxide itself doesn't have any major API changes. +Note however that `dispatching::update_listeners::polling` function was deprecated, use `polling_builder` instead. + ## 0.7 -> 0.8 ### core @@ -8,7 +47,7 @@ Note that the list of required changes is not fully exhaustive and it may lack s `user.id` now uses `UserId` type, `ChatId` now represents only _chat id_, not channel username, all `chat_id` function parameters now accept `Recipient` (if they allow for channel usernames). If you used to work with chat/user ids (for example saving them to a database), you may need to change your code to account for new types. Some examples how that may look like: -```diff, +```diff -let user_id: i64 = user.id; +let UserId(user_id) = user.id; db.save(user_id, ...); diff --git a/README.md b/README.md index 8a5e386d..7e849ec7 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,4 @@ -> [v0.7 -> v0.8 migration guide >>](MIGRATION_GUIDE.md#07---08) - -> `teloxide-core` versions less that `0.4.5` (`teloxide` versions less than 0.7.3) have a low-severity security vulnerability, [learn more >>](https://github.com/teloxide/teloxide/discussions/574) +> [v0.9 -> v0.10 migration guide >>](MIGRATION_GUIDE.md#09---010)
@@ -72,7 +70,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.9", features = ["macros", "auto-send"] } +teloxide = { version = "0.10", features = ["macros", "auto-send"] } log = "0.4" pretty_env_logger = "0.4" tokio = { version = "1.8", features = ["rt-multi-thread", "macros"] } From 1b2b1ee30ceebb599d3c7f8d7c4a9c56e58cc003 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Tue, 19 Jul 2022 14:51:25 +0400 Subject: [PATCH 50/71] update faq Former-commit-id: 4af32d21ebe1198b8481ea64c7274498b3618a2b --- README.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/README.md b/README.md index 7e849ec7..b8bb377c 100644 --- a/README.md +++ b/README.md @@ -319,11 +319,7 @@ A: No, only the bots API. **Q: Can I use webhooks?** -A: teloxide doesn't provide a special API for working with webhooks due to their nature with lots of subtle settings. Instead, you should setup your webhook by yourself, as shown in [`examples/ngrok_ping_pong_bot`](examples/ngrok_ping_pong.rs) and [`examples/heroku_ping_pong_bot`](examples/heroku_ping_pong.rs). - -Associated links: - - [Marvin's Marvellous Guide to All Things Webhook](https://core.telegram.org/bots/webhooks) - - [Using self-signed certificates](https://core.telegram.org/bots/self-signed) +A: You can! Teloxide has a built-in support for webhooks in `dispatching::update_listeners::webhooks` module. See how it's used in [`examples/ngrok_ping_pong_bot`](examples/ngrok_ping_pong.rs) and [`examples/heroku_ping_pong_bot`](examples/heroku_ping_pong.rs) **Q: Can I handle both callback queries and messages within a single dialogue?** From 4262175be24b096c65f1d192cf07e57aa620b0c4 Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Tue, 19 Jul 2022 19:34:14 +0600 Subject: [PATCH 51/71] Remove commented dependencies in `Cargo.toml` Former-commit-id: ce40f04ebd9bc0505e4c7e06cba1e43e39b61f11 --- Cargo.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index aa08091a..9c702774 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,14 +58,12 @@ full = [ [dependencies] teloxide-core = { version = "0.7.0", default-features = false } -#teloxide-core = { git = "https://github.com/teloxide/teloxide-core", rev = "b13393d", default-features = false } teloxide-macros = { version = "0.6.2", optional = true } serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } dptree = "0.3.0" -#dptree = { git = "https://github.com/teloxide/dptree.git", rev = "df578e4" } tokio = { version = "1.8", features = ["fs"] } tokio-util = "0.7" From 411c851a2f2e0ed8a90cbbc055e137831f3fbed5 Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Tue, 19 Jul 2022 19:34:35 +0600 Subject: [PATCH 52/71] Update the dispatching example explanation Former-commit-id: bc8b86181fd6e39f01a66627a767effcbb9b8e8d --- examples/purchase.rs | 9 +- src/dispatching.rs | 213 ++++++++++++++++++++++++++---------- src/dispatching/dialogue.rs | 24 ++-- 3 files changed, 172 insertions(+), 74 deletions(-) diff --git a/examples/purchase.rs b/examples/purchase.rs index 74a0cd6d..cbe4bfe6 100644 --- a/examples/purchase.rs +++ b/examples/purchase.rs @@ -13,10 +13,7 @@ // ``` use teloxide::{ - dispatching::{ - dialogue::{self, InMemStorage}, - UpdateHandler, - }, + dispatching::{dialogue::InMemStorage, UpdateHandler}, prelude::*, types::{InlineKeyboardButton, InlineKeyboardMarkup}, utils::command::BotCommands, @@ -75,12 +72,12 @@ fn schema() -> UpdateHandler> .branch(dptree::case![State::ReceiveFullName].endpoint(receive_full_name)) .branch(dptree::endpoint(invalid_state)); - let callback_query_handler = Update::filter_callback_query().chain( + let callback_query_handler = Update::filter_callback_query().branch( dptree::case![State::ReceiveProductChoice { full_name }] .endpoint(receive_product_selection), ); - dialogue::enter::, State, _>() + teloxide::dispatching::dialogue::enter::, State, _>() .branch(message_handler) .branch(callback_query_handler) } diff --git a/src/dispatching.rs b/src/dispatching.rs index 1c093048..042870e8 100644 --- a/src/dispatching.rs +++ b/src/dispatching.rs @@ -1,76 +1,175 @@ //! An update dispatching model based on [`dptree`]. //! -//! In teloxide, updates are dispatched by a pipeline. The central type is -//! [`dptree::Handler`] -- it represents a handler of an update; since the API -//! is highly declarative, you can combine handlers with each other via such -//! methods as [`dptree::Handler::chain`] and [`dptree::Handler::branch`]. The -//! former method pipes one handler to another one, whilst the latter creates a -//! new node, as communicated by the name. For more information, please refer to -//! the documentation of [`dptree`]. +//! In teloxide, update dispatching is declarative: it takes the form of a +//! [chain of responsibility] pattern enriched with a number of combinator +//! functions, which together form an instance of the [`dptree::Handler`] type. //! -//! The pattern itself is called [chain of responsibility], a well-known design -//! technique across OOP developers. But unlike typical object-oriented design, -//! we employ declarative FP-style functions like [`dptree::filter`], -//! [`dptree::filter_map`], and [`dptree::endpoint`]; these functions create -//! special forms of [`dptree::Handler`]; for more information, please refer to -//! their respective documentation. Each of these higher-order functions accept -//! a closure that is made into a handler -- this closure can take any -//! additional parameters, which must be supplied while creating [`Dispatcher`] -//! (see [`DispatcherBuilder::dependencies`]). -//! -//! The [`Dispatcher`] type puts all these things together: it only provides -//! [`Dispatcher::dispatch`] and a handful of other methods. Once you call -//! `.dispatch()`, it will retrieve updates from the Telegram server and pass -//! them to your handler, which is a parameter of [`Dispatcher::builder`]. -//! -//! Let us look at a simple example: -//! -//! -//! ([Full](https://github.com/teloxide/teloxide/blob/master/examples/shared_state.rs)) +//! Let us look at this simple example: //! +//! [[`examples/purchase.rs`](https://github.com/teloxide/teloxide/blob/master/examples/purchase.rs)] //! ```no_run -//! // TODO: examples/purchase.rs -//! fn main() {} +//! // Imports omitted... +//! # use teloxide::{ +//! # dispatching::{dialogue::InMemStorage, UpdateHandler}, +//! # prelude::*, +//! # types::{InlineKeyboardButton, InlineKeyboardMarkup}, +//! # utils::command::BotCommands, +//! # }; +//! +//! type MyDialogue = Dialogue>; +//! type HandlerResult = Result<(), Box>; +//! +//! #[derive(Clone, Default)] +//! pub enum State { +//! #[default] +//! Start, +//! ReceiveFullName, +//! ReceiveProductChoice { +//! full_name: String, +//! }, +//! } +//! +//! #[derive(BotCommands, Clone)] +//! #[command(rename = "lowercase", description = "These commands are supported:")] +//! enum Command { +//! #[command(description = "display this text.")] +//! Help, +//! #[command(description = "start the purchase procedure.")] +//! Start, +//! #[command(description = "cancel the purchase procedure.")] +//! Cancel, +//! } +//! +//! #[tokio::main] +//! async fn main() { +//! // Setup is omitted... +//! # pretty_env_logger::init(); +//! # log::info!("Starting purchase bot..."); +//! # +//! # let bot = Bot::from_env().auto_send(); +//! # +//! # Dispatcher::builder(bot, schema()) +//! # .dependencies(dptree::deps![InMemStorage::::new()]) +//! # .build() +//! # .setup_ctrlc_handler() +//! # .dispatch() +//! # .await; +//! } +//! +//! fn schema() -> UpdateHandler> { +//! let command_handler = teloxide::filter_command::() +//! .branch( +//! dptree::case![State::Start] +//! .branch(dptree::case![Command::Help].endpoint(help)) +//! .branch(dptree::case![Command::Start].endpoint(start)), +//! ) +//! .branch(dptree::case![Command::Cancel].endpoint(cancel)); +//! +//! let message_handler = Update::filter_message() +//! .branch(command_handler) +//! .branch(dptree::case![State::ReceiveFullName].endpoint(receive_full_name)) +//! .branch(dptree::endpoint(invalid_state)); +//! +//! let callback_query_handler = Update::filter_callback_query().branch( +//! dptree::case![State::ReceiveProductChoice { full_name }] +//! .endpoint(receive_product_selection), +//! ); +//! +//! teloxide::dispatching::dialogue::enter::, State, _>() +//! .branch(message_handler) +//! .branch(callback_query_handler) +//! } +//! +//! // Handler definitions omitted... +//! +//! async fn start(bot: AutoSend, msg: Message, dialogue: MyDialogue) -> HandlerResult { +//! todo!() +//! } +//! +//! async fn help(bot: AutoSend, msg: Message) -> HandlerResult { +//! todo!() +//! } +//! +//! async fn cancel(bot: AutoSend, msg: Message, dialogue: MyDialogue) -> HandlerResult { +//! todo!() +//! } +//! +//! async fn invalid_state(bot: AutoSend, msg: Message) -> HandlerResult { +//! todo!() +//! } +//! +//! async fn receive_full_name( +//! bot: AutoSend, +//! msg: Message, +//! dialogue: MyDialogue, +//! ) -> HandlerResult { +//! todo!() +//! } +//! +//! async fn receive_product_selection( +//! bot: AutoSend, +//! q: CallbackQuery, +//! dialogue: MyDialogue, +//! full_name: String, +//! ) -> HandlerResult { +//! todo!() +//! } //! ``` //! -//! 1. First, we create the bot: `let bot = Bot::from_env().auto_send()`. -//! 2. Then we construct an update handler. While it is possible to handle all -//! kinds of [`crate::types::Update`], here we are only interested in -//! [`crate::types::Message`]: [`UpdateFilterExt::filter_message`] create a -//! handler object which filters all messages out of a generic update. -//! 3. By doing `.endpoint(...)` we set up a custom handling closure that -//! receives `msg: Message` and `bot: AutoSend`. There are -//! called dependencies: `msg` is supplied by -//! [`UpdateFilterExt::filter_message`], while `bot` is supplied by -//! [`Dispatcher`]. +//! The above code shows how to dispatch on different combinations of a state +//! and command _elegantly_. We give a top-bottom explanation of the function +//! `schema`, which constructs the main update handler: //! -//! That being said, if we receive a message, the dispatcher will call our -//! handler, but if we receive something other than a message (e.g., a channel -//! post), you will see an unhandled update notice in your terminal. +//! - We call the [`dialogue::enter`] function to initiate dialogue +//! interaction. Then we call [`dptree::Handler::branch`] two times to form a +//! tree of responsibility of `message_handler` and `callback_query_handler`. +//! - Inside `message_handler`, we use [`Update::filter_message`] as a filter +//! for incoming messages. Then we create a tree of responsibility again, +//! consisting of three branches with a similar structure. +//! - Inside `callback_query_handler`, we use +//! [`Update::filter_callback_query`] as a filter and create one branch for +//! handling product selection. //! -//! This is a very limited example of update pipelining facilities. In more -//! involved scenarios, there are multiple branches and chains; if one element -//! of a chain fails to handle an update, the update will be passed forwards; if -//! no handler succeeds at handling the update, [`Dispatcher`] will invoke a -//! default handler set up via [`DispatcherBuilder::default_handler`]. +//! `a.branch(b)` roughly means "try to handle an update with `a`, then, if it +//! fails, try `b`". We use branching multiple times here, which is a natural +//! pattern for describing dispatching logic. We also use the [`dptree::case!`] +//! macro extensively, which acts as a filter on an enumeration: if it is of a +//! certain variant, it passes the variant's payload down the handler chain; +//! otherwise, it neglects an update. Note how we utilise this macro both for +//! `State` and `Command` in the same way! //! -//! Update pipelining provides several advantages over the typical `match -//! (update.kind) { ... }` approach: +//! Finally, we plug the schema into [`Dispatcher`] like this: //! -//! 1. It supports _extension_: e.g., you -//! can define extension filters or some other handlers and then combine them in -//! a single place, thus facilitating loose coupling. -//! 2. Pipelining exhibits a natural syntax for expressing message processing. -//! 3. Lastly, it provides a primitive form of [dependency injection (DI)], -//! which allows you to deal with such objects as a bot and various update types -//! easily. +//! ```no_run +//! # #[tokio::main] +//! # async fn main() { +//! let bot = Bot::from_env().auto_send(); //! -//! For a more involved example, see [`examples/dispatching_features.rs`](https://github.com/teloxide/teloxide/blob/master/examples/dispatching_features.rs). +//! Dispatcher::builder(bot, schema()) +//! .dependencies(dptree::deps![InMemStorage::::new()]) +//! .build() +//! .setup_ctrlc_handler() +//! .dispatch() +//! .await; +//! # } +//! ``` //! -//! TODO: explain a more involved example with multiple branches. +//! In a call to [`DispatcherBuilder::dependencies`], we specify a list of +//! dependencies that all handlers will receive as parameters. Here, we only +//! specify an in-memory storage of dialogues needed for [`dialogue::enter`]. +//! However, in production bots, you normally also pass a database connection, +//! configuration, and other stuff. //! +//! All in all, [`dptree`] can be seen as an extensible alternative to pattern +//! matching, with support for [dependency injection (DI)] and a few other +//! useful features. See [`examples/dispatching_features.rs`] as a more involved +//! example. +//! +//! [`Update::filter_message`]: crate::types::Update::filter_message +//! [`Update::filter_callback_query`]: crate::types::Update::filter_callback_query //! [chain of responsibility]: https://en.wikipedia.org/wiki/Chain-of-responsibility_pattern //! [dependency injection (DI)]: https://en.wikipedia.org/wiki/Dependency_injection +//! [`examples/dispatching_features.rs`]: https://github.com/teloxide/teloxide/blob/master/examples/dispatching_features.rs #[cfg(all(feature = "ctrlc_handler"))] pub mod repls; diff --git a/src/dispatching/dialogue.rs b/src/dispatching/dialogue.rs index 83b0239d..3af1ecc0 100644 --- a/src/dispatching/dialogue.rs +++ b/src/dispatching/dialogue.rs @@ -4,10 +4,11 @@ //! wrapper over [`Storage`] and a chat ID. All it does is provides convenient //! method for manipulating the dialogue state. [`Storage`] is where all //! dialogue states are stored; it can be either [`InMemStorage`], which is a -//! simple hash map, or database wrappers such as [`SqliteStorage`]. In the -//! latter case, your dialogues are _persistent_, meaning that you can safely -//! restart your bot and all dialogues will remain in the database -- this is a -//! preferred method for production bots. +//! simple hash map from [`std::collections`], or an advanced database wrapper +//! such as [`SqliteStorage`]. In the latter case, your dialogues are +//! _persistent_, meaning that you can safely restart your bot and all ongoing +//! dialogues will remain in the database -- this is a preferred method for +//! production bots. //! //! [`examples/dialogue.rs`] clearly demonstrates the typical usage of //! dialogues. Your dialogue state can be represented as an enumeration: @@ -31,8 +32,8 @@ //! bot: AutoSend, //! msg: Message, //! dialogue: MyDialogue, -//! (full_name,): (String,), // Available from `State::ReceiveAge`. -//! ) -> anyhow::Result<()> { +//! full_name: String, // Available from `State::ReceiveAge`. +//! ) -> HandlerResult { //! match msg.text().map(|text| text.parse::()) { //! Some(Ok(age)) => { //! bot.send_message(msg.chat.id, "What's your location?").await?; @@ -47,11 +48,12 @@ //! } //! ``` //! -//! Variant's fields are passed to state handlers as tuples: `(full_name,): -//! (String,)`. Using [`Dialogue::update`], you can update the dialogue with a -//! new state, in our case -- `State::ReceiveLocation { full_name, age }`. To -//! exit the dialogue, just call [`Dialogue::exit`] and it will be removed from -//! the inner storage: +//! Variant's fields are passed to state handlers as single arguments like +//! `full_name: String` or tuples in case of two or more variant parameters (see +//! below). Using [`Dialogue::update`], you can update the dialogue with a new +//! state, in our case -- `State::ReceiveLocation { full_name, age }`. To exit +//! the dialogue, just call [`Dialogue::exit`] and it will be removed from the +//! underlying storage: //! //! ```ignore //! async fn receive_location( From db5747eb4e2bc99f4cad7078215b5d9fe295e6aa Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Tue, 19 Jul 2022 19:39:25 +0600 Subject: [PATCH 53/71] Ignore one doc example Former-commit-id: b48f87573a6d1e3e943ae0e1005303788b233eae --- src/dispatching.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dispatching.rs b/src/dispatching.rs index 042870e8..e35d6b9d 100644 --- a/src/dispatching.rs +++ b/src/dispatching.rs @@ -140,7 +140,7 @@ //! //! Finally, we plug the schema into [`Dispatcher`] like this: //! -//! ```no_run +//! ```ignore //! # #[tokio::main] //! # async fn main() { //! let bot = Bot::from_env().auto_send(); From 6c7f17b07080f49f699169716411cf76ec440f2c Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Tue, 19 Jul 2022 19:43:02 +0600 Subject: [PATCH 54/71] Retain consistency in `CHANGELOG.md` Former-commit-id: 82c7923dea46061c0cbd691b0ace757ddde879f3 --- CHANGELOG.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fe36a67..92916a97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Security checks based on `secret_token` param of `set_webhook` to built-in webhooks -- `dispatching::update_listeners::{PollingBuilder, Polling, PollingStream}` + - Security checks based on `secret_token` param of `set_webhook` to built-in webhooks. + - `dispatching::update_listeners::{PollingBuilder, Polling, PollingStream}`. ### Fixed @@ -18,14 +18,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Add the `Key: Clone` requirement for `impl Dispatcher` [**BC**]. - - `dispatching::update_listeners::{polling_default, polling}` now return a named, `Polling<_>` type - - Update teloxide-core to v0.7.0 with Bot API 6.1 support, see [its changelog][core07c] for more [**BC**] + - `dispatching::update_listeners::{polling_default, polling}` now return a named, `Polling<_>` type. + - Update teloxide-core to v0.7.0 with Bot API 6.1 support, see [its changelog][core07c] for more information [**BC**]. [core07c]: https://github.com/teloxide/teloxide-core/blob/master/CHANGELOG.md#070---2022-07-19 ### Deprecated -- `dispatching::update_listeners::polling` +- The `dispatching::update_listeners::polling` function. ## 0.9.2 - 2022-06-07 From a27799e098a4ed6e1281c6963af314ef877f7462 Mon Sep 17 00:00:00 2001 From: Waffle Maybe Date: Tue, 19 Jul 2022 18:01:25 +0400 Subject: [PATCH 55/71] attempt to fix ci Former-commit-id: 9f1d0d7dd88b66fe28c76c93d405a475c8c1630d --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dba02ce0..54ae77ee 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -123,6 +123,7 @@ jobs: args: --tests --verbose ${{ matrix.features }} - name: Test documentation tests + if: ${{ matrix.rust != "msrv" }} uses: actions-rs/cargo@v1 with: command: test From 6c0e7f814fdcf7d9f00159dae4abf85d6f836efd Mon Sep 17 00:00:00 2001 From: Waffle Maybe Date: Tue, 19 Jul 2022 18:12:45 +0400 Subject: [PATCH 56/71] use different quotes?... Former-commit-id: d9a7c265f97d734fa50306d5f6682dbeb6db130a --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 54ae77ee..c2ccac7e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -123,7 +123,7 @@ jobs: args: --tests --verbose ${{ matrix.features }} - name: Test documentation tests - if: ${{ matrix.rust != "msrv" }} + if: ${{ matrix.rust != 'msrv' }} uses: actions-rs/cargo@v1 with: command: test From f2cf85d9ce9232603bb80c86e2c50e98c36a3b2e Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Tue, 19 Jul 2022 20:16:08 +0600 Subject: [PATCH 57/71] Leave `teloxide-core` and `dptree` as GitHub deps (dev) Former-commit-id: d6d819afb8f951f21592eb64081743a70cf779c5 --- Cargo.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 9c702774..e5f1364b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,6 +65,10 @@ serde = { version = "1.0", features = ["derive"] } dptree = "0.3.0" +# These lines are used only for development. +# teloxide-core = { git = "https://github.com/teloxide/teloxide-core", rev = "b13393d", default-features = false } +# dptree = { git = "https://github.com/teloxide/dptree", rev = "df578e4" } + tokio = { version = "1.8", features = ["fs"] } tokio-util = "0.7" tokio-stream = "0.1.8" From ad87f6e63c26963cc7b5db8a8d9d2412a8fefa67 Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Tue, 19 Jul 2022 21:47:09 +0600 Subject: [PATCH 58/71] Test teloxide-macros with a `parse_with` bug fix Former-commit-id: 78afa99b794d72405566b4ce120005630a6796f6 --- Cargo.toml | 3 ++- tests/command.rs | 35 +++++++++++++++++++++++++---------- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e5f1364b..d7b160ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,7 +58,7 @@ full = [ [dependencies] teloxide-core = { version = "0.7.0", default-features = false } -teloxide-macros = { version = "0.6.2", optional = true } +# teloxide-macros = { version = "0.6.2", optional = true } serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } @@ -67,6 +67,7 @@ dptree = "0.3.0" # These lines are used only for development. # teloxide-core = { git = "https://github.com/teloxide/teloxide-core", rev = "b13393d", default-features = false } +teloxide-macros = { git = "https://github.com/teloxide/teloxide-macros", rev = "44d91c5", optional = true } # dptree = { git = "https://github.com/teloxide/dptree", rev = "df578e4" } tokio = { version = "1.8", features = ["fs"] } diff --git a/tests/command.rs b/tests/command.rs index b3e6609e..4b59ac48 100644 --- a/tests/command.rs +++ b/tests/command.rs @@ -2,7 +2,7 @@ #![allow(clippy::nonstandard_macro_braces)] #[cfg(feature = "macros")] -use teloxide::utils::command::{BotCommands, ParseError}; +use teloxide::utils::command::BotCommands; // We put tests here because macro expand in unit tests in module // teloxide::utils::command was a failure @@ -141,22 +141,33 @@ fn parse_with_split2() { #[test] #[cfg(feature = "macros")] fn parse_custom_parser() { - fn custom_parse_function(s: String) -> Result<(u8, String), ParseError> { - let vec = s.split_whitespace().collect::>(); - let (left, right) = match vec.as_slice() { - [l, r] => (l, r), - _ => return Err(ParseError::IncorrectFormat("might be 2 arguments!".into())), - }; - left.parse::() - .map(|res| (res, (*right).to_string())) - .map_err(|_| ParseError::Custom("First argument must be a integer!".to_owned().into())) + mod parser { + use teloxide::utils::command::ParseError; + + pub fn custom_parse_function(s: String) -> Result<(u8, String), ParseError> { + let vec = s.split_whitespace().collect::>(); + let (left, right) = match vec.as_slice() { + [l, r] => (l, r), + _ => return Err(ParseError::IncorrectFormat("might be 2 arguments!".into())), + }; + left.parse::().map(|res| (res, (*right).to_string())).map_err(|_| { + ParseError::Custom("First argument must be a integer!".to_owned().into()) + }) + } } + use parser::custom_parse_function; + #[derive(BotCommands, Debug, PartialEq)] #[command(rename = "lowercase")] enum DefaultCommands { #[command(parse_with = "custom_parse_function")] Start(u8, String), + + // Test . + #[command(parse_with = "parser::custom_parse_function")] + TestPath(u8, String), + Help, } @@ -164,6 +175,10 @@ fn parse_custom_parser() { DefaultCommands::Start(10, "hello".to_string()), DefaultCommands::parse("/start 10 hello", "").unwrap() ); + assert_eq!( + DefaultCommands::TestPath(10, "hello".to_string()), + DefaultCommands::parse("/testpath 10 hello", "").unwrap() + ); } #[test] From fb5b64bff96e7b5c2ed626fe0aa00940b1828b64 Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Tue, 19 Jul 2022 22:02:44 +0600 Subject: [PATCH 59/71] Update teloxide-macros to v0.6.3 Former-commit-id: 3d9f4ef2b0b84a517bb77d9a4b5e6093eb86c339 --- CHANGELOG.md | 1 + Cargo.toml | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92916a97..8681abe3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - `Dispatcher` no longer "leaks" memory for every inactive user ([PR 657](https://github.com/teloxide/teloxide/pull/657)). + - Allow specifying a path to a custom command parser in `parse_with` ([issue 668](https://github.com/teloxide/teloxide/issues/668)). ### Changed diff --git a/Cargo.toml b/Cargo.toml index d7b160ca..df04f34e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,7 +58,7 @@ full = [ [dependencies] teloxide-core = { version = "0.7.0", default-features = false } -# teloxide-macros = { version = "0.6.2", optional = true } +teloxide-macros = { version = "0.6.3", optional = true } serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } @@ -67,7 +67,7 @@ dptree = "0.3.0" # These lines are used only for development. # teloxide-core = { git = "https://github.com/teloxide/teloxide-core", rev = "b13393d", default-features = false } -teloxide-macros = { git = "https://github.com/teloxide/teloxide-macros", rev = "44d91c5", optional = true } +# teloxide-macros = { git = "https://github.com/teloxide/teloxide-macros", rev = "44d91c5", optional = true } # dptree = { git = "https://github.com/teloxide/dptree", rev = "df578e4" } tokio = { version = "1.8", features = ["fs"] } From d919c99b69ca6bc5ae2aaa48d02fe226f9b0990d Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Wed, 20 Jul 2022 17:44:39 +0600 Subject: [PATCH 60/71] Improve the dispatching explanation (docs) Former-commit-id: 9ab3b3a1c5ab6ebbfce82b027cf50d2f961d87ba --- src/dispatching.rs | 130 +++++++++++++++++++++------------------------ 1 file changed, 62 insertions(+), 68 deletions(-) diff --git a/src/dispatching.rs b/src/dispatching.rs index e35d6b9d..ce939b28 100644 --- a/src/dispatching.rs +++ b/src/dispatching.rs @@ -4,31 +4,23 @@ //! [chain of responsibility] pattern enriched with a number of combinator //! functions, which together form an instance of the [`dptree::Handler`] type. //! -//! Let us look at this simple example: -//! -//! [[`examples/purchase.rs`](https://github.com/teloxide/teloxide/blob/master/examples/purchase.rs)] -//! ```no_run -//! // Imports omitted... -//! # use teloxide::{ -//! # dispatching::{dialogue::InMemStorage, UpdateHandler}, -//! # prelude::*, -//! # types::{InlineKeyboardButton, InlineKeyboardMarkup}, -//! # utils::command::BotCommands, -//! # }; -//! -//! type MyDialogue = Dialogue>; -//! type HandlerResult = Result<(), Box>; +//! Take [`examples/purchase.rs`] as an example of dispatching logic. First, we +//! define a type named `State` to represent the current state of a dialogue: //! +//! ```ignore //! #[derive(Clone, Default)] //! pub enum State { //! #[default] //! Start, //! ReceiveFullName, -//! ReceiveProductChoice { -//! full_name: String, -//! }, +//! ReceiveProductChoice { full_name: String }, //! } +//! ``` //! +//! Then, we define a type `Command` to represent user commands such as +//! `/start` or `/help`: +//! +//! ```ignore //! #[derive(BotCommands, Clone)] //! #[command(rename = "lowercase", description = "These commands are supported:")] //! enum Command { @@ -39,23 +31,15 @@ //! #[command(description = "cancel the purchase procedure.")] //! Cancel, //! } +//! ``` //! -//! #[tokio::main] -//! async fn main() { -//! // Setup is omitted... -//! # pretty_env_logger::init(); -//! # log::info!("Starting purchase bot..."); -//! # -//! # let bot = Bot::from_env().auto_send(); -//! # -//! # Dispatcher::builder(bot, schema()) -//! # .dependencies(dptree::deps![InMemStorage::::new()]) -//! # .build() -//! # .setup_ctrlc_handler() -//! # .dispatch() -//! # .await; -//! } +//! Now the key question: how to elegantly dispatch on different combinations of +//! `State`, `Command`, and Telegram updates? -- i.e., we may want to execute +//! specific endpoints only in response to specific user commands and while we +//! are in a given dialogue state (and possibly under other circumstances!). The +//! solution is to use [`dptree`]: //! +//! ```ignore //! fn schema() -> UpdateHandler> { //! let command_handler = teloxide::filter_command::() //! .branch( @@ -79,7 +63,30 @@ //! .branch(message_handler) //! .branch(callback_query_handler) //! } +//! ``` //! +//! The overall logic should be clear. Throughout the above example, we use +//! several techniques: +//! +//! - **Branching:** `a.branch(b)` roughly means "try to handle an update with +//! `a`, then, if it +//! neglects the update, try `b`". +//! - **Pattern matching:** We also use the [`dptree::case!`] macro +//! extensively, which acts as a filter on an enumeration: if it is of a +//! certain variant, it passes the variant's payload down the handler chain; +//! otherwise, it neglects an update. +//! - **Endpoints:** To specify the final function to handle an update, we use +//! [`dptree::Handler::endpoint`]. +//! +//! Notice the clear and uniform code structure: regardless of the dispatch +//! criteria, we use the same program constructions. In future, you may want to +//! introduce your application-specific filters or data structures to match upon +//! -- no problem, reuse [`dptree::Handler::filter`], [`dptree::case!`], and +//! other combinators in the same way! +//! +//! Finally, we define our endpoints like this: +//! +//! ```ignore //! // Handler definitions omitted... //! //! async fn start(bot: AutoSend, msg: Message, dialogue: MyDialogue) -> HandlerResult { @@ -116,55 +123,42 @@ //! } //! ``` //! -//! The above code shows how to dispatch on different combinations of a state -//! and command _elegantly_. We give a top-bottom explanation of the function -//! `schema`, which constructs the main update handler: +//! Each parameter is supplied as a dependency by teloxide. In particular: +//! - `bot: AutoSend` comes from the dispatcher (see below); +//! - `msg: Message` comes from [`Update::filter_message`]; +//! - `q: CallbackQuery` comes from [`Update::filter_callback_query`]; +//! - `dialogue: MyDialogue` comes from [`dialogue::enter`]; +//! - `full_name: String` comes from `dptree::case![State::ReceiveProductChoice +//! { full_name }]`. //! -//! - We call the [`dialogue::enter`] function to initiate dialogue -//! interaction. Then we call [`dptree::Handler::branch`] two times to form a -//! tree of responsibility of `message_handler` and `callback_query_handler`. -//! - Inside `message_handler`, we use [`Update::filter_message`] as a filter -//! for incoming messages. Then we create a tree of responsibility again, -//! consisting of three branches with a similar structure. -//! - Inside `callback_query_handler`, we use -//! [`Update::filter_callback_query`] as a filter and create one branch for -//! handling product selection. -//! -//! `a.branch(b)` roughly means "try to handle an update with `a`, then, if it -//! fails, try `b`". We use branching multiple times here, which is a natural -//! pattern for describing dispatching logic. We also use the [`dptree::case!`] -//! macro extensively, which acts as a filter on an enumeration: if it is of a -//! certain variant, it passes the variant's payload down the handler chain; -//! otherwise, it neglects an update. Note how we utilise this macro both for -//! `State` and `Command` in the same way! -//! -//! Finally, we plug the schema into [`Dispatcher`] like this: +//! Inside `main`, we plug the schema into [`Dispatcher`] like this: //! //! ```ignore -//! # #[tokio::main] -//! # async fn main() { -//! let bot = Bot::from_env().auto_send(); +//! #[tokio::main] +//! async fn main() { +//! let bot = Bot::from_env().auto_send(); //! -//! Dispatcher::builder(bot, schema()) -//! .dependencies(dptree::deps![InMemStorage::::new()]) -//! .build() -//! .setup_ctrlc_handler() -//! .dispatch() -//! .await; -//! # } +//! Dispatcher::builder(bot, schema()) +//! .dependencies(dptree::deps![InMemStorage::::new()]) +//! .build() +//! .setup_ctrlc_handler() +//! .dispatch() +//! .await; +//! } //! ``` //! //! In a call to [`DispatcherBuilder::dependencies`], we specify a list of -//! dependencies that all handlers will receive as parameters. Here, we only -//! specify an in-memory storage of dialogues needed for [`dialogue::enter`]. -//! However, in production bots, you normally also pass a database connection, -//! configuration, and other stuff. +//! additional dependencies that all handlers will receive as parameters. Here, +//! we only specify an in-memory storage of dialogues needed for +//! [`dialogue::enter`]. However, in production bots, you normally also pass a +//! database connection, configuration, and other stuff. //! //! All in all, [`dptree`] can be seen as an extensible alternative to pattern //! matching, with support for [dependency injection (DI)] and a few other //! useful features. See [`examples/dispatching_features.rs`] as a more involved //! example. //! +//! [`examples/purchase.rs`]: https://github.com/teloxide/teloxide/blob/master/examples/purchase.rs //! [`Update::filter_message`]: crate::types::Update::filter_message //! [`Update::filter_callback_query`]: crate::types::Update::filter_callback_query //! [chain of responsibility]: https://en.wikipedia.org/wiki/Chain-of-responsibility_pattern From ac3a42ff0a4f07e77526db12b17c6c8240dad0ba Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Wed, 20 Jul 2022 19:40:32 +0600 Subject: [PATCH 61/71] Link `Dispatcher` from the docs of REPLs Former-commit-id: 4fb8a120ea9f23a9774597c08fdb8dc3e054e25f --- src/dispatching/repls/commands_repl.rs | 10 ++++++++++ src/dispatching/repls/repl.rs | 12 +++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/dispatching/repls/commands_repl.rs b/src/dispatching/repls/commands_repl.rs index fd04964a..c5669e29 100644 --- a/src/dispatching/repls/commands_repl.rs +++ b/src/dispatching/repls/commands_repl.rs @@ -14,7 +14,12 @@ use teloxide_core::requests::Requester; /// /// All errors from an update listener and handler will be logged. /// +/// REPLs are meant only for simple bots and rapid prototyping. If you need to +/// supply dependencies or describe more complex dispatch logic, please use +/// [`Dispatcher`]. +/// /// ## Caution +/// /// **DO NOT** use this function together with [`Dispatcher`] and other REPLs, /// because Telegram disallow multiple requests at the same time from the same /// bot. @@ -49,7 +54,12 @@ where /// /// All errors from an update listener and handler will be logged. /// +/// REPLs are meant only for simple bots and rapid prototyping. If you need to +/// supply dependencies or describe more complex dispatch logic, please use +/// [`Dispatcher`]. +/// /// ## Caution +/// /// **DO NOT** use this function together with [`Dispatcher`] and other REPLs, /// because Telegram disallow multiple requests at the same time from the same /// bot. diff --git a/src/dispatching/repls/repl.rs b/src/dispatching/repls/repl.rs index eec73f1f..509525d4 100644 --- a/src/dispatching/repls/repl.rs +++ b/src/dispatching/repls/repl.rs @@ -11,7 +11,12 @@ use teloxide_core::requests::Requester; /// /// All errors from an update listener and a handler will be logged. /// -/// # Caution +/// REPLs are meant only for simple bots and rapid prototyping. If you need to +/// supply dependencies or describe more complex dispatch logic, please use +/// [`Dispatcher`]. +/// +/// ## Caution +/// /// **DO NOT** use this function together with [`Dispatcher`] and other REPLs, /// because Telegram disallow multiple requests at the same time from the same /// bot. @@ -35,7 +40,12 @@ where /// /// All errors from an update listener and handler will be logged. /// +/// REPLs are meant only for simple bots and rapid prototyping. If you need to +/// supply dependencies or describe more complex dispatch logic, please use +/// [`Dispatcher`]. +/// /// # Caution +/// /// **DO NOT** use this function together with [`Dispatcher`] and other REPLs, /// because Telegram disallow multiple requests at the same time from the same /// bot. From e2127658477992167bf9ae4070d6517ae1e3c493 Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Wed, 20 Jul 2022 20:11:55 +0600 Subject: [PATCH 62/71] Drop community bots using old versions of teloxide Also, prettify the list representation a little bit. Former-commit-id: d2cb46382cea83f1aa5d9b9dfd317c47a585ef36 --- README.md | 41 ++++++++++++++--------------------------- 1 file changed, 14 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index b8bb377c..ee55388e 100644 --- a/README.md +++ b/README.md @@ -319,7 +319,7 @@ A: No, only the bots API. **Q: Can I use webhooks?** -A: You can! Teloxide has a built-in support for webhooks in `dispatching::update_listeners::webhooks` module. See how it's used in [`examples/ngrok_ping_pong_bot`](examples/ngrok_ping_pong.rs) and [`examples/heroku_ping_pong_bot`](examples/heroku_ping_pong.rs) +A: You can! Teloxide has a built-in support for webhooks in `dispatching::update_listeners::webhooks` module. See how it's used in [`examples/ngrok_ping_pong_bot`](examples/ngrok_ping_pong.rs) and [`examples/heroku_ping_pong_bot`](examples/heroku_ping_pong.rs). **Q: Can I handle both callback queries and messages within a single dialogue?** @@ -329,32 +329,19 @@ A: Yes, see [`examples/purchase.rs`](examples/purchase.rs). Feel free to propose your own bot to our collection! - - [WaffleLapkin/crate_upd_bot](https://github.com/WaffleLapkin/crate_upd_bot) — A bot that notifies about crate updates. - - [mxseev/logram](https://github.com/mxseev/logram) — Utility that takes logs from anywhere and sends them to Telegram. - - [alexkonovalov/PedigreeBot](https://github.com/alexkonovalov/PedigreeBot) — A Telegram bot for building family trees. - - [Hermitter/tepe](https://github.com/Hermitter/tepe) — A CLI to command a bot to send messages and files over Telegram. - - [mattrighetti/GroupActivityBot](https://github.com/mattrighetti/group-activity-bot-rs) — Telegram bot that keeps track of user activity in groups. - - [mattrighetti/libgen-bot-rs](https://github.com/mattrighetti/libgen-bot-rs) — Telgram bot to interface with libgen - - [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. - - [myblackbeard/basketball-betting-bot](https://github.com/myblackbeard/basketball-betting-bot) — The bot lets you bet on NBA games against your buddies. - - [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. - - [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. - - [slondr/BeerHolderBot](https://gitlab.com/slondr/BeerHolderBot) — A bot that holds your beer. - - [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. - - [magnickolas/remindee-bot](https://github.com/magnickolas/remindee-bot) — Telegram bot for managing reminders. - - [cyberknight777/knight-bot](https://gitlab.com/cyberknight777/knight-bot) — A Telegram bot with variety of fun features. - - [wa7sa34cx/the-black-box-bot](https://github.com/wa7sa34cx/the-black-box-bot) — This is the Black Box Telegram bot. You can hold any items in it. - - [crapstone/hsctt](https://codeberg.org/crapstones-bots/hsctt) — A Telegram bot that searches for HTTP status codes in all messages and replies with the text form. - - [alenpaul2001/AurSearchBot](https://gitlab.com/alenpaul2001/aursearchbot) — Telegram bot for searching AUR in inline mode. - - [studiedlist/EddieBot](https://gitlab.com/studiedlist/eddie-bot) — Chatting bot with several entertainment features. - - [modos189/tg_blackbox_bot](https://gitlab.com/modos189/tg_blackbox_bot) — Anonymous feedback for your Telegram project. This bot in Docker from scratch container. - - [0xNima/spacecraft](https://github.com/0xNima/spacecraft) — Yet another telegram bot to downloading Twitter spaces. - - [0xNima/Twideo](https://github.com/0xNima/Twideo) — Telegram Bot for downloading videos from Twitter via their links, as well as converting tweets to telegram messages. - - [raine/tgreddit](https://github.com/raine/tgreddit) — A bot that sends the top posts of your favorite subreddits to Telegram. + - [`raine/tgreddit`](https://github.com/raine/tgreddit) — A bot that sends the top posts of your favorite subreddits to Telegram. + - [`magnickolas/remindee-bot`](https://github.com/magnickolas/remindee-bot) — Telegram bot for managing reminders. + - [`WaffleLapkin/crate_upd_bot`](https://github.com/WaffleLapkin/crate_upd_bot) — A bot that notifies about crate updates. + - [`mattrighetti/GroupActivityBot`](https://github.com/mattrighetti/group-activity-bot-rs) — Telegram bot that keeps track of user activity in groups. + - [`alenpaul2001/AurSearchBot`](https://gitlab.com/alenpaul2001/aursearchbot) — Telegram bot for searching in Arch User Repository (AUR). + - [`ArtHome12/vzmuinebot`](https://github.com/ArtHome12/vzmuinebot) — Telegram bot for food menu navigate. + - [`studiedlist/EddieBot`](https://gitlab.com/studiedlist/eddie-bot) — Chatting bot with several entertainment features. + - [`modos189/tg_blackbox_bot`](https://gitlab.com/modos189/tg_blackbox_bot) — Anonymous feedback for your Telegram project. + - [`0xNima/spacecraft`](https://github.com/0xNima/spacecraft) — Yet another telegram bot to downloading Twitter spaces. + - [`0xNima/Twideo`](https://github.com/0xNima/Twideo) — Simple Telegram Bot for downloading videos from Twitter via their links. + - [`mattrighetti/libgen-bot-rs`](https://github.com/mattrighetti/libgen-bot-rs) — Telgram bot to interface with libgen. + +(Only bots using teloxide v0.6.0 or higher are listed.) ## Contributing From 31cfca1615451459602a3ada7da1dbef0d329dae Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Wed, 20 Jul 2022 20:13:44 +0600 Subject: [PATCH 63/71] Update the API coverage badge Former-commit-id: 7e6c04643f81df692ddc23bc660f5162bb402f51 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ee55388e..b3fd1bd2 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ - + From b6c6e3e1eea6b95b40eef1651a780e9908a236a4 Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Thu, 21 Jul 2022 14:28:26 +0600 Subject: [PATCH 64/71] Add `zamazan4ik/npaperbot-telegram` to community bots Former-commit-id: ca85dfff166f2bc7acab3bd22990ed2bf2d19e47 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b3fd1bd2..fed1aa26 100644 --- a/README.md +++ b/README.md @@ -340,6 +340,7 @@ Feel free to propose your own bot to our collection! - [`0xNima/spacecraft`](https://github.com/0xNima/spacecraft) — Yet another telegram bot to downloading Twitter spaces. - [`0xNima/Twideo`](https://github.com/0xNima/Twideo) — Simple Telegram Bot for downloading videos from Twitter via their links. - [`mattrighetti/libgen-bot-rs`](https://github.com/mattrighetti/libgen-bot-rs) — Telgram bot to interface with libgen. + - [`zamazan4ik/npaperbot-telegram`](https://github.com/zamazan4ik/npaperbot-telegram) — Telegram bot for searching via C++ proposals. (Only bots using teloxide v0.6.0 or higher are listed.) From 378acfc17a76a4df6939cec0668ec9f6291a8ba0 Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Thu, 21 Jul 2022 14:51:22 +0600 Subject: [PATCH 65/71] Put community bots with old teloxide into a hidden section Former-commit-id: 92abc22613738c995f1d6bcf4f0c6ce3e3d92557 --- README.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fed1aa26..12663bea 100644 --- a/README.md +++ b/README.md @@ -342,7 +342,19 @@ Feel free to propose your own bot to our collection! - [`mattrighetti/libgen-bot-rs`](https://github.com/mattrighetti/libgen-bot-rs) — Telgram bot to interface with libgen. - [`zamazan4ik/npaperbot-telegram`](https://github.com/zamazan4ik/npaperbot-telegram) — Telegram bot for searching via C++ proposals. -(Only bots using teloxide v0.6.0 or higher are listed.) +
+Show bots using teloxide older than v0.6.0 + + - [`mxseev/logram`](https://github.com/mxseev/logram) — Utility that takes logs from anywhere and sends them to Telegram. + - [`alexkonovalov/PedigreeBot`](https://github.com/alexkonovalov/PedigreeBot) — A Telegram bot for building family trees. + - [`Hermitter/tepe`](https://github.com/Hermitter/tepe) — A CLI to command a bot to send messages and files over Telegram. + - [`myblackbeard/basketball-betting-bot`](https://github.com/myblackbeard/basketball-betting-bot) — The bot lets you bet on NBA games against your buddies. + - [`dracarys18/grpmr-rs`](https://github.com/dracarys18/grpmr-rs) — Modular Telegram Group Manager Bot written in Rust. + - [`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. + - [`crapstone/hsctt`](https://codeberg.org/crapstones-bots/hsctt) — A bot that converts HTTP status codes into text. + +
## Contributing From 11231655c222510a21792ccdfa51d36fa4c0f5fe Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Thu, 21 Jul 2022 12:36:57 +0400 Subject: [PATCH 66/71] Move ctrlc handler enable function to `DispatcherBuilder` This helps with consistency -- every setting is changed in builder. Also `Self -> Self` function sometimes plays more nicely with borrowck. Former-commit-id: dd4af30727caa2dfbd81464e0a6f8e87f056a2d3 --- CHANGELOG.md | 2 + MIGRATION_GUIDE.md | 4 +- README.md | 2 +- examples/buttons.rs | 2 +- examples/db_remember.rs | 2 +- examples/dialogue.rs | 2 +- examples/dispatching_features.rs | 2 +- examples/inline.rs | 2 +- examples/purchase.rs | 2 +- examples/shared_state.rs | 2 +- src/dispatching.rs | 2 +- src/dispatching/dispatcher.rs | 51 ++++++++++++++++++++------ src/dispatching/repls/commands_repl.rs | 2 +- src/dispatching/repls/repl.rs | 2 +- src/features.md | 40 ++++++++++---------- 15 files changed, 76 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8681abe3..db915866 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Security checks based on `secret_token` param of `set_webhook` to built-in webhooks. - `dispatching::update_listeners::{PollingBuilder, Polling, PollingStream}`. + - `DispatcherBuilder::enable_ctrlc_handler` method. ### Fixed @@ -27,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Deprecated - The `dispatching::update_listeners::polling` function. +- `Dispatcher::setup_ctrlc_handler` method. ## 0.9.2 - 2022-06-07 diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md index dd1af6eb..78003f48 100644 --- a/MIGRATION_GUIDE.md +++ b/MIGRATION_GUIDE.md @@ -38,7 +38,9 @@ Some places now use `FileMeta` instead of `File`, you may need to change types. ### teloxide Teloxide itself doesn't have any major API changes. -Note however that `dispatching::update_listeners::polling` function was deprecated, use `polling_builder` instead. +Note however that some function were deprecated: +- Instead of `dispatching::update_listeners::polling` use `polling_builder` +- Instead of `Dispatcher::setup_ctrlc_handler` use `DispatcherBuilder::enable_ctrlc_handler` ## 0.7 -> 0.8 diff --git a/README.md b/README.md index 12663bea..b05f48f8 100644 --- a/README.md +++ b/README.md @@ -223,8 +223,8 @@ async fn main() { ), ) .dependencies(dptree::deps![InMemStorage::::new()]) + .enable_ctrlc_handler() .build() - .setup_ctrlc_handler() .dispatch() .await; } diff --git a/examples/buttons.rs b/examples/buttons.rs index 5b16fe35..b510e24a 100644 --- a/examples/buttons.rs +++ b/examples/buttons.rs @@ -30,7 +30,7 @@ async fn main() -> Result<(), Box> { .branch(Update::filter_callback_query().endpoint(callback_handler)) .branch(Update::filter_inline_query().endpoint(inline_query_handler)); - Dispatcher::builder(bot, handler).build().setup_ctrlc_handler().dispatch().await; + Dispatcher::builder(bot, handler).enable_ctrlc_handler().build().dispatch().await; Ok(()) } diff --git a/examples/db_remember.rs b/examples/db_remember.rs index 206768d9..1aed2808 100644 --- a/examples/db_remember.rs +++ b/examples/db_remember.rs @@ -54,8 +54,8 @@ async fn main() { Dispatcher::builder(bot, handler) .dependencies(dptree::deps![storage]) + .enable_ctrlc_handler() .build() - .setup_ctrlc_handler() .dispatch() .await; } diff --git a/examples/dialogue.rs b/examples/dialogue.rs index 30636924..8e30219d 100644 --- a/examples/dialogue.rs +++ b/examples/dialogue.rs @@ -51,8 +51,8 @@ async fn main() { ), ) .dependencies(dptree::deps![InMemStorage::::new()]) + .enable_ctrlc_handler() .build() - .setup_ctrlc_handler() .dispatch() .await; } diff --git a/examples/dispatching_features.rs b/examples/dispatching_features.rs index 91ef0808..dcaf4fcc 100644 --- a/examples/dispatching_features.rs +++ b/examples/dispatching_features.rs @@ -87,8 +87,8 @@ async fn main() { .error_handler(LoggingErrorHandler::with_custom_text( "An error has occurred in the dispatcher", )) + .enable_ctrlc_handler() .build() - .setup_ctrlc_handler() .dispatch() .await; } diff --git a/examples/inline.rs b/examples/inline.rs index a85de2c2..837fa30d 100644 --- a/examples/inline.rs +++ b/examples/inline.rs @@ -60,5 +60,5 @@ async fn main() { }, )); - Dispatcher::builder(bot, handler).build().setup_ctrlc_handler().dispatch().await; + Dispatcher::builder(bot, handler).enable_ctrlc_handler().build().dispatch().await; } diff --git a/examples/purchase.rs b/examples/purchase.rs index cbe4bfe6..f2f68729 100644 --- a/examples/purchase.rs +++ b/examples/purchase.rs @@ -52,8 +52,8 @@ async fn main() { Dispatcher::builder(bot, schema()) .dependencies(dptree::deps![InMemStorage::::new()]) + .enable_ctrlc_handler() .build() - .setup_ctrlc_handler() .dispatch() .await; } diff --git a/examples/shared_state.rs b/examples/shared_state.rs index c09fab93..21a0fcc0 100644 --- a/examples/shared_state.rs +++ b/examples/shared_state.rs @@ -27,8 +27,8 @@ async fn main() { Dispatcher::builder(bot, handler) // Pass the shared state to the handler as a dependency. .dependencies(dptree::deps![messages_total]) + .enable_ctrlc_handler() .build() - .setup_ctrlc_handler() .dispatch() .await; } diff --git a/src/dispatching.rs b/src/dispatching.rs index ce939b28..f8fb3bdf 100644 --- a/src/dispatching.rs +++ b/src/dispatching.rs @@ -140,8 +140,8 @@ //! //! Dispatcher::builder(bot, schema()) //! .dependencies(dptree::deps![InMemStorage::::new()]) +//! .enable_ctrlc_handler() //! .build() -//! .setup_ctrlc_handler() //! .dispatch() //! .await; //! } diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index 74eace82..3926792d 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -33,6 +33,7 @@ pub struct DispatcherBuilder { handler: Arc>, default_handler: DefaultHandler, error_handler: Arc + Send + Sync>, + ctrlc_handler: bool, distribution_f: fn(&Update) -> Option, worker_queue_size: usize, } @@ -78,6 +79,14 @@ where Self { dependencies, ..self } } + /// Enables the `^C` handler that [`shutdown`]s dispatching. + /// + /// [`shutdown`]: ShutdownToken::shutdown + #[cfg(feature = "ctrlc_handler")] + pub fn enable_ctrlc_handler(self) -> Self { + Self { ctrlc_handler: true, ..self } + } + /// Specifies size of the queue for workers. /// /// By default it's 64. @@ -101,6 +110,7 @@ where handler, default_handler, error_handler, + ctrlc_handler, distribution_f: _, worker_queue_size, } = self; @@ -111,6 +121,7 @@ where handler, default_handler, error_handler, + ctrlc_handler, distribution_f: f, worker_queue_size, } @@ -127,9 +138,10 @@ where error_handler, distribution_f, worker_queue_size, + ctrlc_handler, } = self; - Dispatcher { + let dp = Dispatcher { bot, dependencies, handler, @@ -142,7 +154,18 @@ where default_worker: None, current_number_of_active_workers: Default::default(), max_number_of_active_workers: Default::default(), + }; + + #[cfg(feature = "ctrlc_handler")] + { + if ctrlc_handler { + let mut dp = dp; + dp.setup_ctrlc_handler_inner(); + return dp; + } } + + dp } } @@ -212,6 +235,7 @@ where Box::pin(async {}) }), error_handler: LoggingErrorHandler::new(), + ctrlc_handler: false, worker_queue_size: DEFAULT_WORKER_QUEUE_SIZE, distribution_f: default_distribution_function, } @@ -238,7 +262,6 @@ where /// - [`crate::types::Me`] (can be used in [`HandlerExt::filter_command`]). /// /// [`shutdown`]: ShutdownToken::shutdown - /// [a ctrlc signal]: Dispatcher::setup_ctrlc_handler /// [`HandlerExt::filter_command`]: crate::dispatching::HandlerExt::filter_command pub async fn dispatch(&mut self) where @@ -258,7 +281,6 @@ where /// This method adds the same dependencies as [`Dispatcher::dispatch`]. /// /// [`shutdown`]: ShutdownToken::shutdown - /// [a ctrlc signal]: Dispatcher::setup_ctrlc_handler pub async fn dispatch_with_listener<'a, UListener, ListenerE, Eh>( &'a mut self, mut update_listener: UListener, @@ -425,7 +447,22 @@ where /// /// [`shutdown`]: ShutdownToken::shutdown #[cfg(feature = "ctrlc_handler")] + #[deprecated(since = "0.10", note = "use `enable_ctrlc_handler` on builder instead")] pub fn setup_ctrlc_handler(&mut self) -> &mut Self { + self.setup_ctrlc_handler_inner(); + self + } + + /// Returns a shutdown token, which can later be used to shutdown + /// dispatching. + pub fn shutdown_token(&self) -> ShutdownToken { + self.state.clone() + } +} + +impl Dispatcher { + #[cfg(feature = "ctrlc_handler")] + fn setup_ctrlc_handler_inner(&mut self) { let token = self.state.clone(); tokio::spawn(async move { loop { @@ -443,14 +480,6 @@ where } } }); - - self - } - - /// Returns a shutdown token, which can later be used to shutdown - /// dispatching. - pub fn shutdown_token(&self) -> ShutdownToken { - self.state.clone() } } diff --git a/src/dispatching/repls/commands_repl.rs b/src/dispatching/repls/commands_repl.rs index c5669e29..7f7db10f 100644 --- a/src/dispatching/repls/commands_repl.rs +++ b/src/dispatching/repls/commands_repl.rs @@ -96,8 +96,8 @@ pub async fn commands_repl_with_listener<'a, R, Cmd, H, L, ListenerE, E, Args>( Update::filter_message().filter_command::().chain(dptree::endpoint(handler)), ) .default_handler(ignore_update) + .enable_ctrlc_handler() .build() - .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 509525d4..cecf90ad 100644 --- a/src/dispatching/repls/repl.rs +++ b/src/dispatching/repls/repl.rs @@ -71,8 +71,8 @@ where Dispatcher::builder(bot, Update::filter_message().chain(dptree::endpoint(handler))) .default_handler(ignore_update) + .enable_ctrlc_handler() .build() - .setup_ctrlc_handler() .dispatch_with_listener( listener, LoggingErrorHandler::with_custom_text("An error from the update listener"), diff --git a/src/features.md b/src/features.md index 1d199a80..2c4b2951 100644 --- a/src/features.md +++ b/src/features.md @@ -1,24 +1,24 @@ ## Cargo features -| Feature | Description | -|----------------------|------------------------------------------------------------------------------------| -| `webhooks` | Enables general webhook utilities (almost useless on its own) | -| `webhooks-axum` | Enables webhook implementation based on axum framework | -| `macros` | Re-exports macros from [`teloxide-macros`]. | -| `ctrlc_handler` | Enables the [`Dispatcher::setup_ctrlc_handler`] function (**enabled by default**). | -| `auto-send` | Enables the [`AutoSend`](adaptors::AutoSend) bot adaptor (**enabled by default**). | -| `throttle` | Enables the [`Throttle`](adaptors::Throttle) bot adaptor. | -| `cache-me` | Enables the [`CacheMe`](adaptors::CacheMe) bot adaptor. | -| `trace-adaptor` | Enables the [`Trace`](adaptors::Trace) bot adaptor. | -| `erased` | Enables the [`ErasedRequester`](adaptors::ErasedRequester) bot adaptor. | -| `full` | Enables all the features except `nightly`. | -| `nightly` | Enables nightly-only features (see the [teloxide-core features]). | -| `native-tls` | Enables the [`native-tls`] TLS implementation (**enabled by default**). | -| `rustls` | Enables the [`rustls`] TLS implementation. | -| `redis-storage` | Enables the [Redis] storage support for dialogues. | -| `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. | +| Feature | Description | +|----------------------|--------------------------------------------------------------------------------------------| +| `webhooks` | Enables general webhook utilities (almost useless on its own) | +| `webhooks-axum` | Enables webhook implementation based on axum framework | +| `macros` | Re-exports macros from [`teloxide-macros`]. | +| `ctrlc_handler` | Enables the [`DispatcherBuilder::enable_ctrlc_handler`] function (**enabled by default**). | +| `auto-send` | Enables the [`AutoSend`](adaptors::AutoSend) bot adaptor (**enabled by default**). | +| `throttle` | Enables the [`Throttle`](adaptors::Throttle) bot adaptor. | +| `cache-me` | Enables the [`CacheMe`](adaptors::CacheMe) bot adaptor. | +| `trace-adaptor` | Enables the [`Trace`](adaptors::Trace) bot adaptor. | +| `erased` | Enables the [`ErasedRequester`](adaptors::ErasedRequester) bot adaptor. | +| `full` | Enables all the features except `nightly`. | +| `nightly` | Enables nightly-only features (see the [teloxide-core features]). | +| `native-tls` | Enables the [`native-tls`] TLS implementation (**enabled by default**). | +| `rustls` | Enables the [`rustls`] TLS implementation. | +| `redis-storage` | Enables the [Redis] storage support for dialogues. | +| `sqlite-storage` | Enables the [Sqlite] storage support for dialogues. | +| `cbor-serializer` | Enables the [CBOR] serializer for dialogues. | +| `bincode-serializer` | Enables the [Bincode] serializer for dialogues. | [Redis]: https://redis.io/ @@ -31,4 +31,4 @@ [`teloxide::utils::UpState`]: utils::UpState [teloxide-core features]: https://docs.rs/teloxide-core/latest/teloxide_core/#cargo-features -[`Dispatcher::setup_ctrlc_handler`]: dispatching::Dispatcher::setup_ctrlc_handler \ No newline at end of file +[`DispatcherBuilder::enable_ctrlc_handler`]: dispatching::DispatcherBuilder::enable_ctrlc_handler \ No newline at end of file From a4ecaa03af807a502874c7af0cd1c43fc4bc5c8a Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Thu, 21 Jul 2022 13:22:11 +0400 Subject: [PATCH 67/71] Do not `ignore` tests in `dispatching` When test is ignored rustdoc shows a warning sign which is disturbing. Former-commit-id: 627b8624f3cbc0a0dc51ad5d14894c6294fac689 --- src/dispatching.rs | 42 +++++++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/src/dispatching.rs b/src/dispatching.rs index f8fb3bdf..feef5c38 100644 --- a/src/dispatching.rs +++ b/src/dispatching.rs @@ -7,20 +7,23 @@ //! Take [`examples/purchase.rs`] as an example of dispatching logic. First, we //! define a type named `State` to represent the current state of a dialogue: //! -//! ```ignore +//! ```no_run //! #[derive(Clone, Default)] //! pub enum State { //! #[default] //! Start, //! ReceiveFullName, -//! ReceiveProductChoice { full_name: String }, +//! ReceiveProductChoice { +//! full_name: String, +//! }, //! } //! ``` //! //! Then, we define a type `Command` to represent user commands such as //! `/start` or `/help`: //! -//! ```ignore +//! ```no_run +//! # use teloxide::utils::command::BotCommands; //! #[derive(BotCommands, Clone)] //! #[command(rename = "lowercase", description = "These commands are supported:")] //! enum Command { @@ -39,7 +42,21 @@ //! are in a given dialogue state (and possibly under other circumstances!). The //! solution is to use [`dptree`]: //! -//! ```ignore +//! ```no_run +//! # // That's a lot of context needed to compile this, oof +//! # use teloxide::dispatching::{UpdateHandler, UpdateFilterExt, dialogue::InMemStorage}; +//! # use teloxide::utils::command::BotCommands; +//! # use teloxide::types::Update; +//! # #[derive(Clone, Default)] pub enum State { #[default] Start, ReceiveFullName, ReceiveProductChoice { full_name: String } } +//! # #[derive(BotCommands, Clone)] enum Command { Help, Start, Cancel } +//! # type HandlerResult = Result<(), Box>; +//! # async fn help() -> HandlerResult { Ok(()) } +//! # async fn start() -> HandlerResult { Ok(()) } +//! # async fn cancel() -> HandlerResult { Ok(()) } +//! # async fn receive_full_name() -> HandlerResult { Ok(()) } +//! # async fn invalid_state() -> HandlerResult { Ok(()) } +//! # async fn receive_product_selection() -> HandlerResult { Ok(()) } +//! # //! fn schema() -> UpdateHandler> { //! let command_handler = teloxide::filter_command::() //! .branch( @@ -86,8 +103,14 @@ //! //! Finally, we define our endpoints like this: //! -//! ```ignore -//! // Handler definitions omitted... +//! ```no_run +//! # use teloxide::{Bot, adaptors::AutoSend}; +//! # use teloxide::types::{Message, CallbackQuery}; +//! # use teloxide::dispatching::dialogue::{InMemStorage, Dialogue}; +//! # enum State{} +//! # +//! type MyDialogue = Dialogue>; +//! type HandlerResult = Result<(), Box>; //! //! async fn start(bot: AutoSend, msg: Message, dialogue: MyDialogue) -> HandlerResult { //! todo!() @@ -133,7 +156,12 @@ //! //! Inside `main`, we plug the schema into [`Dispatcher`] like this: //! -//! ```ignore +//! ```no_run +//! # use teloxide::Bot; +//! # use teloxide::requests::RequesterExt; +//! # use teloxide::dispatching::{Dispatcher, dialogue::InMemStorage}; +//! # enum State {} +//! # fn schema() -> teloxide::dispatching::UpdateHandler> { teloxide::dptree::entry() } //! #[tokio::main] //! async fn main() { //! let bot = Bot::from_env().auto_send(); From 04bcce77019d8fde55f35ae81b9d89b35cd64c9a Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Thu, 21 Jul 2022 13:31:37 +0400 Subject: [PATCH 68/71] make `schema` in docs nicer Former-commit-id: 84beedfff4864fe2d17b1fc053328b2829f095ec --- src/dispatching.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/dispatching.rs b/src/dispatching.rs index feef5c38..e8a744bb 100644 --- a/src/dispatching.rs +++ b/src/dispatching.rs @@ -58,25 +58,29 @@ //! # async fn receive_product_selection() -> HandlerResult { Ok(()) } //! # //! fn schema() -> UpdateHandler> { -//! let command_handler = teloxide::filter_command::() +//! use teloxide::dispatching::dialogue; +//! use teloxide::filter_command; +//! use dptree::case; +//! +//! let command_handler = filter_command::() //! .branch( -//! dptree::case![State::Start] -//! .branch(dptree::case![Command::Help].endpoint(help)) -//! .branch(dptree::case![Command::Start].endpoint(start)), +//! case![State::Start] +//! .branch(case![Command::Help].endpoint(help)) +//! .branch(case![Command::Start].endpoint(start)), //! ) -//! .branch(dptree::case![Command::Cancel].endpoint(cancel)); +//! .branch(case![Command::Cancel].endpoint(cancel)); //! //! let message_handler = Update::filter_message() //! .branch(command_handler) -//! .branch(dptree::case![State::ReceiveFullName].endpoint(receive_full_name)) +//! .branch(case![State::ReceiveFullName].endpoint(receive_full_name)) //! .branch(dptree::endpoint(invalid_state)); //! //! let callback_query_handler = Update::filter_callback_query().branch( -//! dptree::case![State::ReceiveProductChoice { full_name }] +//! case![State::ReceiveProductChoice { full_name }] //! .endpoint(receive_product_selection), //! ); //! -//! teloxide::dispatching::dialogue::enter::, State, _>() +//! dialogue::enter::, State, _>() //! .branch(message_handler) //! .branch(callback_query_handler) //! } From e1419f578f5eef7864434da71a47a123b6a5176c Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Thu, 21 Jul 2022 13:35:48 +0400 Subject: [PATCH 69/71] Fix deprecation version Former-commit-id: 0cb4bfef36d28628bd12ebc2ea75932c13ad9072 --- src/dispatching/dispatcher.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index 3926792d..6ff568e6 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -447,7 +447,7 @@ where /// /// [`shutdown`]: ShutdownToken::shutdown #[cfg(feature = "ctrlc_handler")] - #[deprecated(since = "0.10", note = "use `enable_ctrlc_handler` on builder instead")] + #[deprecated(since = "0.10.0", note = "use `enable_ctrlc_handler` on builder instead")] pub fn setup_ctrlc_handler(&mut self) -> &mut Self { self.setup_ctrlc_handler_inner(); self From 3dd8787f132d5e0c86714bd98beae72a37e536a5 Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Thu, 21 Jul 2022 16:53:52 +0600 Subject: [PATCH 70/71] Adjust imports a little bit in `examples/purchase.rs` Former-commit-id: a22c91df6ce693b6abbf91a0707e5a7283fb0c6e --- examples/purchase.rs | 19 ++++++++++--------- src/dispatching.rs | 9 +++------ 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/examples/purchase.rs b/examples/purchase.rs index f2f68729..0eac1ad3 100644 --- a/examples/purchase.rs +++ b/examples/purchase.rs @@ -13,7 +13,7 @@ // ``` use teloxide::{ - dispatching::{dialogue::InMemStorage, UpdateHandler}, + dispatching::{dialogue, dialogue::InMemStorage, UpdateHandler}, prelude::*, types::{InlineKeyboardButton, InlineKeyboardMarkup}, utils::command::BotCommands, @@ -59,25 +59,26 @@ async fn main() { } fn schema() -> UpdateHandler> { + use dptree::case; + let command_handler = teloxide::filter_command::() .branch( - dptree::case![State::Start] - .branch(dptree::case![Command::Help].endpoint(help)) - .branch(dptree::case![Command::Start].endpoint(start)), + case![State::Start] + .branch(case![Command::Help].endpoint(help)) + .branch(case![Command::Start].endpoint(start)), ) - .branch(dptree::case![Command::Cancel].endpoint(cancel)); + .branch(case![Command::Cancel].endpoint(cancel)); let message_handler = Update::filter_message() .branch(command_handler) - .branch(dptree::case![State::ReceiveFullName].endpoint(receive_full_name)) + .branch(case![State::ReceiveFullName].endpoint(receive_full_name)) .branch(dptree::endpoint(invalid_state)); let callback_query_handler = Update::filter_callback_query().branch( - dptree::case![State::ReceiveProductChoice { full_name }] - .endpoint(receive_product_selection), + case![State::ReceiveProductChoice { full_name }].endpoint(receive_product_selection), ); - teloxide::dispatching::dialogue::enter::, State, _>() + dialogue::enter::, State, _>() .branch(message_handler) .branch(callback_query_handler) } diff --git a/src/dispatching.rs b/src/dispatching.rs index e8a744bb..e0440705 100644 --- a/src/dispatching.rs +++ b/src/dispatching.rs @@ -44,7 +44,7 @@ //! //! ```no_run //! # // That's a lot of context needed to compile this, oof -//! # use teloxide::dispatching::{UpdateHandler, UpdateFilterExt, dialogue::InMemStorage}; +//! # use teloxide::dispatching::{UpdateHandler, UpdateFilterExt, dialogue, dialogue::InMemStorage}; //! # use teloxide::utils::command::BotCommands; //! # use teloxide::types::Update; //! # #[derive(Clone, Default)] pub enum State { #[default] Start, ReceiveFullName, ReceiveProductChoice { full_name: String } } @@ -58,11 +58,9 @@ //! # async fn receive_product_selection() -> HandlerResult { Ok(()) } //! # //! fn schema() -> UpdateHandler> { -//! use teloxide::dispatching::dialogue; -//! use teloxide::filter_command; //! use dptree::case; //! -//! let command_handler = filter_command::() +//! let command_handler = teloxide::filter_command::() //! .branch( //! case![State::Start] //! .branch(case![Command::Help].endpoint(help)) @@ -76,8 +74,7 @@ //! .branch(dptree::endpoint(invalid_state)); //! //! let callback_query_handler = Update::filter_callback_query().branch( -//! case![State::ReceiveProductChoice { full_name }] -//! .endpoint(receive_product_selection), +//! case![State::ReceiveProductChoice { full_name }].endpoint(receive_product_selection), //! ); //! //! dialogue::enter::, State, _>() From f4d99b87665f640727be66fc30394d3177b378e7 Mon Sep 17 00:00:00 2001 From: Hirrolot Date: Thu, 21 Jul 2022 16:54:37 +0600 Subject: [PATCH 71/71] Update `CHANGELOG.md` to v0.10.0 Former-commit-id: 02ec94a08b5728309d700ca2392544ad98419762 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index db915866..2198111f 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.10.0 - 2022-07-21 + ### Added - Security checks based on `secret_token` param of `set_webhook` to built-in webhooks.