diff --git a/Cargo.toml b/Cargo.toml index 24f295da..720ae832 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -87,6 +87,7 @@ redis = { version = "0.20", features = ["tokio-comp"], optional = true } serde_cbor = { version = "0.11", optional = true } bincode = { version = "1.3", optional = true } frunk = { version = "0.4", optional = true } +aquamarine = "0.1.11" [dev-dependencies] smart-default = "0.6.0" diff --git a/src/dispatching/update_listeners.rs b/src/dispatching/update_listeners.rs index 078f52a2..18d294d1 100644 --- a/src/dispatching/update_listeners.rs +++ b/src/dispatching/update_listeners.rs @@ -1,107 +1,27 @@ //! Receiving updates from Telegram. //! -//! The key trait here is [`UpdateListener`]. You can get it by these functions: +//! The key trait here is [`UpdateListener`]. You can get its implementation +//! using one these functions: //! -//! - [`polling_default`], which returns a default long polling listener. -//! - [`polling`], which returns a long/short polling listener with your -//! configuration. +//! - [`polling_default`], which returns a default long polling listener. +//! - [`polling`], which returns a long polling listener with your +//! configuration. //! -//! And then you can extract updates from it and pass them directly to a -//! dispatcher. +//! And then you can extract updates from it or pass them directly to a +//! [`Dispatcher`]. //! -//! Telegram supports two ways of [getting updates]: [long]/[short] polling and -//! [webhook]. -//! -//! # Long Polling -//! -//! In long polling, you just call [`Box::get_updates`] every N seconds. -//! -//! ## Example -//! -//!
-//!     tg                           bot
-//!      |                            |
-//!      |<---------------------------| Updates? (Bot::get_updates call)
-//!      ↑                            ↑
-//!      |          timeout^1         |
-//!      ↓                            ↓
-//! Nope |--------------------------->|
-//!      ↑                            ↑
-//!      | delay between Bot::get_updates^2 |
-//!      ↓                            ↓
-//!      |<---------------------------| Updates?
-//!      ↑                            ↑
-//!      |          timeout^3         |
-//!      ↓                            ↓
-//! Yes  |-------[updates 0, 1]------>|
-//!      ↑                            ↑
-//!      |           delay            |
-//!      ↓                            ↓
-//!      |<-------[offset = 1]--------| Updates?^4
-//!      ↑                            ↑
-//!      |           timeout          |
-//!      ↓                            ↓
-//! Yes  |---------[update 2]-------->|
-//!      ↑                            ↑
-//!      |           delay            |
-//!      ↓                            ↓
-//!      |<-------[offset = 2]--------| Updates?
-//!      ↑                            ↑
-//!      |           timeout          |
-//!      ↓                            ↓
-//! Nope |--------------------------->|
-//!      ↑                            ↑
-//!      |           delay            |
-//!      ↓                            ↓
-//!      |<-------[offset = 2]--------| Updates?
-//!      ↑                            ↑
-//!      |           timeout          |
-//!      ↓                            ↓
-//! Nope |--------------------------->|
-//!      ↑                            ↑
-//!      |           delay            |
-//!      ↓                            ↓
-//!      |<-------[offset = 2]--------| Updates?
-//!      ↑                            ↑
-//!      |           timeout          |
-//!      ↓                            ↓
-//! Yes  |-------[updates 2..5]------>|
-//!      ↑                            ↑
-//!      |           delay            |
-//!      ↓                            ↓
-//!      |<-------[offset = 5]--------| Updates?
-//!      ↑                            ↑
-//!      |           timeout          |
-//!      ↓                            ↓
-//! Nope |--------------------------->|
-//!      |                            |
-//!      ~    and so on, and so on    ~
-//! 
-//! -//! ^1 A timeout can be even 0 -//! (this is also called short polling), -//! but you should use it **only** for testing purposes. -//! -//! ^2 Large delays will cause in bot lags, -//! so delay shouldn't exceed second. -//! -//! ^3 Note that if Telegram already have updates for -//! you it will answer you **without** waiting for a timeout. -//! -//! ^4 `offset = N` means that we've already received -//! updates `0..=N`. -//! -//! # Webhooks -//! See the [README FAQ about webhooks](https://github.com/teloxide/teloxide/blob/master/README.md#faq). +//! Telegram supports two ways of [getting updates]: [long polling] and +//! [webhooks]. Currently, only the former one is implemented (see [`polling()`] +//! and [`polling_default`]). See also [README FAQ about webhooks](https://github.com/teloxide/teloxide/blob/master/README.md#faq). //! //! [`UpdateListener`]: UpdateListener //! [`polling_default`]: polling_default //! [`polling`]: polling() +//! [`Dispatcher`]: crate::dispatching::Dispatcher //! [`Box::get_updates`]: crate::requests::Requester::get_updates //! [getting updates]: https://core.telegram.org/bots/api#getting-updates -//! [long]: https://en.wikipedia.org/wiki/Push_technology#Long_polling -//! [short]: https://en.wikipedia.org/wiki/Polling_(computer_science) -//! [webhook]: https://en.wikipedia.org/wiki/Webhook +//! [long polling]: https://en.wikipedia.org/wiki/Push_technology#Long_polling +//! [webhooks]: https://en.wikipedia.org/wiki/Webhook use futures::Stream; @@ -122,19 +42,15 @@ pub use self::{ /// An update listener. /// -/// Implementors of this trait allow getting updates from Telegram. -/// -/// Currently Telegram has 2 ways of getting updates -- [polling] and -/// [webhooks]. Currently, only the former one is implemented (see [`polling()`] -/// and [`polling_default`]) +/// Implementors of this trait allow getting updates from Telegram. See +/// [module-level documentation] for more. /// /// Some functions of this trait are located in the supertrait /// ([`AsUpdateStream`]), see also: /// - [`AsUpdateStream::Stream`] /// - [`AsUpdateStream::as_stream`] /// -/// [polling]: self#long-polling -/// [webhooks]: self#webhooks +/// [module-level documentation]: mod@self pub trait UpdateListener: for<'a> AsUpdateStream<'a, E> { /// The type of token which allows to stop this listener. type StopToken: StopToken; @@ -150,8 +66,8 @@ pub trait UpdateListener: for<'a> AsUpdateStream<'a, E> { /// Implementors of this function are encouraged to stop listening for /// updates as soon as possible and return `None` from the update stream as /// soon as all cached updates are returned. - #[must_use = "This function doesn't stop listening, to stop listening you need to call stop on \ - the returned token"] + #[must_use = "This function doesn't stop listening, to stop listening you need to call `stop` \ + on the returned token"] fn stop_token(&mut self) -> Self::StopToken; /// Hint which updates should the listener listen for. diff --git a/src/dispatching/update_listeners/polling.rs b/src/dispatching/update_listeners/polling.rs index 9a0bf8f1..27e850fe 100644 --- a/src/dispatching/update_listeners/polling.rs +++ b/src/dispatching/update_listeners/polling.rs @@ -31,20 +31,99 @@ where polling(requester, Some(Duration::from_secs(10)), None, None) } -/// Returns a long/short polling update listener with some additional options. +#[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 for polling. +/// - `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). /// -/// [`GetUpdates`]: crate::payloads::GetUpdates +/// ## 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 +/// +/// ## 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. +/// +/// 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. +/// +/// 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. +/// +/// ```mermaid +/// sequenceDiagram +/// participant C as Consumer +/// participant P as polling +/// participant T as Telegram +/// +/// link C: Dispatcher @ ../struct.Dispatcher.html +/// link C: repl @ ../../fn.repl.html +/// +/// C->>P: next +/// +/// P->>+T: Updates? (offset = 0) +/// Note right of T: timeout +/// T->>-P: None +/// +/// P->>+T: Updates? (offset = 0) +/// Note right of T: <= timeout +/// T->>-P: updates with ids [3, 4] +/// +/// P->>C: update(3) +/// +/// C->>P: next +/// P->>C: update(4) +/// +/// C->>P: next +/// +/// P->>+T: Updates? (offset = 5) +/// Note right of T: <= timeout +/// T->>-P: updates with ids [5] +/// +/// C->>P: stop signal +/// +/// P->>C: update(5) +/// +/// C->>P: next +/// +/// P->>T: *Acknolegment of update(5)* +/// T->>P: ok +/// +/// P->>C: None +/// ``` +/// +/// [get_updates]: crate::requests::Requester::get_updates pub fn polling( - requester: R, + bot: R, timeout: Option, limit: Option, allowed_updates: Option>, @@ -119,7 +198,7 @@ where let (token, flag) = AsyncStopToken::new_pair(); let state = State { - bot: requester, + bot, timeout: timeout.map(|t| t.as_secs().try_into().expect("timeout is too big")), limit, allowed_updates, diff --git a/src/lib.rs b/src/lib.rs index 9114d239..0c73df12 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,8 +44,6 @@ html_logo_url = "https://github.com/teloxide/teloxide/raw/master/ICON.png", html_favicon_url = "https://github.com/teloxide/teloxide/raw/master/ICON.png" )] -#![allow(clippy::match_bool)] -#![forbid(unsafe_code)] // We pass "--cfg docsrs" when building docs to add `This is supported on // feature="..." only.` // @@ -56,6 +54,9 @@ // $ RUSTFLAGS="--cfg dep_docsrs" RUSTDOCFLAGS="--cfg docsrs -Znormalize-docs" cargo +nightly doc --open --all-features // ``` #![cfg_attr(all(docsrs, feature = "nightly"), feature(doc_cfg))] +#![forbid(unsafe_code)] +#![warn(rustdoc::broken_intra_doc_links)] +#![allow(clippy::match_bool)] #![allow(clippy::redundant_pattern_matching)] // https://github.com/rust-lang/rust-clippy/issues/7422 #![allow(clippy::nonstandard_macro_braces)]