tbot 0.6.7

Make cool Telegram bots with Rust easily.
Documentation
use crate::event_loop::{EventLoop, Polling, Webhook};
use crate::{contexts, errors};
use std::{future::Future, sync::Arc};

macro_rules! handler {
    (
        $context:path,
        $(#[doc = $doc:literal])+
        $name:ident,
        $(#[doc = $doc_if:literal])+
        $name_if:ident,
    ) => {
        $(#[doc = $doc])+
        pub fn $name<H, F>(&mut self, handler: H)
        where
            H: (Fn(Arc<$context>, Arc<S>) -> F)
                + Send
                + Sync
                + 'static,
            F: Future<Output = ()> + Send + 'static,
        {
            let state = Arc::clone(&self.state);
            self.inner
                .$name(move |context| handler(context, Arc::clone(&state)));
        }

        $(#[doc = $doc_if])+
        pub fn $name_if<H, HF, P, PF>(
            &mut self,
            predicate: P,
            handler: H,
        ) where
            H: (Fn(Arc<$context>, Arc<S>) -> HF)
                + Send
                + Sync
                + 'static,
            HF: Future<Output = ()> + Send + 'static,
            P: (Fn(Arc<$context>, Arc<S>) -> PF)
                + Send
                + Sync
                + 'static,
            PF: Future<Output = bool> + Send + 'static,
        {
            let predicate = Arc::new(predicate);
            let handler = Arc::new(handler);
            self.$name(move |context, state| {
                let predicate = Arc::clone(&predicate);
                let handler = Arc::clone(&handler);
                async move {
                    if predicate(Arc::clone(&context), Arc::clone(&state)).await {
                        handler(context, state).await
                    }
                }
            });
        }
    };
}

/// A stateful event loop.
#[allow(clippy::module_name_repetitions)]
#[must_use]
pub struct StatefulEventLoop<S> {
    inner: EventLoop,
    state: Arc<S>,
}

#[allow(clippy::use_self)] // https://github.com/rust-lang/rust-clippy/issues/4143
impl<S> StatefulEventLoop<S> {
    pub(crate) fn new(inner: EventLoop, state: S) -> Self {
        Self {
            inner,
            state: Arc::new(state),
        }
    }

    /// Returns an `Arc` to the state.
    #[must_use]
    pub fn get_state(&self) -> Arc<S> {
        Arc::clone(&self.state)
    }

    /// Turns this event loop into a stateless one. Handlers added on this event
    /// loop are still kept.
    #[allow(clippy::missing_const_for_fn)] // https://github.com/rust-lang/rust-clippy/issues/4979
    pub fn into_stateless(self) -> EventLoop {
        self.inner
    }

    /// Turns this event loop into another with other state. Handlers added on
    /// this event loop are still kept and will receive the previous state.
    pub fn with_other_state<T>(self, other_state: T) -> StatefulEventLoop<T>
    where
        T: Send + Sync + 'static,
    {
        StatefulEventLoop {
            inner: self.inner,
            state: Arc::new(other_state),
        }
    }

    /// Sets the bot's username.
    ///
    /// The username is used when checking if a command such as
    /// `/command@username` was directed to the bot.
    pub fn username(&mut self, username: String) {
        self.inner.username(username);
    }

    /// Starts polling configuration.
    pub fn polling(self) -> Polling {
        self.inner.polling()
    }

    /// Starts webhook configuration.
    ///
    /// See our [wiki] to learn how to use webhook with `tbot`.
    ///
    /// [wiki]: https://gitlab.com/SnejUgal/tbot/wikis/How-to/How-to-use-webhooks
    pub fn webhook(self, url: &str, port: u16) -> Webhook<'_> {
        self.inner.webhook(url, port)
    }
}

impl<S> StatefulEventLoop<S>
where
    S: Send + Sync + 'static,
{
    /// Adds a new handler for a command.
    ///
    /// Note that commands such as `/command@username` will be completely
    /// ignored unless you configure the event loop with your bot's username
    /// with either [`username`] or [`fetch_username`].
    ///
    /// [`username`]: #method.username
    /// [`fetch_username`]: #method.fetch_username
    pub fn command<H, F>(&mut self, command: &'static str, handler: H)
    where
        H: (Fn(Arc<contexts::Command<contexts::Text>>, Arc<S>) -> F)
            + Send
            + Sync
            + 'static,
        F: Future<Output = ()> + Send + 'static,
    {
        let state = Arc::clone(&self.state);
        self.inner.command(command, move |context| {
            handler(context, Arc::clone(&state))
        });
    }

    /// Adds a new handler for a command which is run if the predicate
    /// returns true.
    ///
    /// Note that commands such as `/command@username` will be completely
    /// ignored unless you configure the event loop with your bot's username
    /// with either [`username`] or [`fetch_username`].
    ///
    /// [`username`]: #method.username
    /// [`fetch_username`]: #method.fetch_username
    pub fn command_if<H, HF, P, PF>(
        &mut self,
        command: &'static str,
        predicate: P,
        handler: H,
    ) where
        H: (Fn(Arc<contexts::Command<contexts::Text>>, Arc<S>) -> HF)
            + Send
            + Sync
            + 'static,
        HF: Future<Output = ()> + Send + 'static,
        P: (Fn(Arc<contexts::Command<contexts::Text>>, Arc<S>) -> PF)
            + Send
            + Sync
            + 'static,
        PF: Future<Output = bool> + Send + 'static,
    {
        let predicate = Arc::new(predicate);
        let handler = Arc::new(handler);
        self.command(command, move |context, state| {
            let predicate = Arc::clone(&predicate);
            let handler = Arc::clone(&handler);
            async move {
                if predicate(Arc::clone(&context), Arc::clone(&state)).await {
                    handler(context, state).await
                }
            }
        });
    }

    /// Adds a new handler for a sequence of commands.
    ///
    /// Note that commands such as `/command@username` will be completely
    /// ignored unless you configure the event loop with your bot's username
    /// with either [`username`] or [`fetch_username`].
    ///
    /// [`username`]: #method.username
    /// [`fetch_username`]: #method.fetch_username
    pub fn commands<Cm, H, F>(&mut self, commands: Cm, handler: H)
    where
        Cm: IntoIterator<Item = &'static str>,
        H: (Fn(Arc<contexts::Command<contexts::Text>>, Arc<S>) -> F)
            + Send
            + Sync
            + 'static,
        F: Future<Output = ()> + Send + 'static,
    {
        let state = Arc::clone(&self.state);
        self.inner.commands(commands, move |context| {
            handler(context, Arc::clone(&state))
        });
    }

    /// Adds a new handler for a sequence of commands which is run
    /// if the predicate returns true.
    ///
    /// Note that commands such as `/command@username` will be completely
    /// ignored unless you configure the event loop with your bot's username
    /// with either [`username`] or [`fetch_username`].
    ///
    /// [`username`]: #method.username
    /// [`fetch_username`]: #method.fetch_username
    pub fn commands_if<Cm, H, HF, P, PF>(
        &mut self,
        commands: Cm,
        predicate: P,
        handler: H,
    ) where
        Cm: IntoIterator<Item = &'static str>,
        H: (Fn(Arc<contexts::Command<contexts::Text>>, Arc<S>) -> HF)
            + Send
            + Sync
            + 'static,
        HF: Future<Output = ()> + Send + 'static,
        P: (Fn(Arc<contexts::Command<contexts::Text>>, Arc<S>) -> PF)
            + Send
            + Sync
            + 'static,
        PF: Future<Output = bool> + Send + 'static,
    {
        let predicate = Arc::new(predicate);
        let handler = Arc::new(handler);
        self.commands(commands, move |context, state| {
            let predicate = Arc::clone(&predicate);
            let handler = Arc::clone(&handler);
            async move {
                if predicate(Arc::clone(&context), Arc::clone(&state)).await {
                    handler(context, state).await
                }
            }
        });
    }

    /// Adds a new handler for the `/start` command.
    pub fn start<H, F>(&mut self, handler: H)
    where
        H: (Fn(Arc<contexts::Command<contexts::Text>>, Arc<S>) -> F)
            + Send
            + Sync
            + 'static,
        F: Future<Output = ()> + Send + 'static,
    {
        self.command("start", handler);
    }

    /// Adds a new handler for the `/start` command which is run
    /// if the predicate returns true.
    pub fn start_if<H, HF, P, PF>(&mut self, predicate: P, handler: H)
    where
        H: (Fn(Arc<contexts::Command<contexts::Text>>, Arc<S>) -> HF)
            + Send
            + Sync
            + 'static,
        HF: Future<Output = ()> + Send + 'static,
        P: (Fn(Arc<contexts::Command<contexts::Text>>, Arc<S>) -> PF)
            + Send
            + Sync
            + 'static,
        PF: Future<Output = bool> + Send + 'static,
    {
        let predicate = Arc::new(predicate);
        let handler = Arc::new(handler);
        self.command("start", move |context, state| {
            let predicate = Arc::clone(&predicate);
            let handler = Arc::clone(&handler);
            async move {
                if predicate(Arc::clone(&context), Arc::clone(&state)).await {
                    handler(context, state).await
                }
            }
        });
    }

    /// Adds a new handler for the `/help` command.
    pub fn help<H, F>(&mut self, handler: H)
    where
        H: (Fn(Arc<contexts::Command<contexts::Text>>, Arc<S>) -> F)
            + Send
            + Sync
            + 'static,
        F: Future<Output = ()> + Send + 'static,
    {
        self.command("help", handler);
    }

    /// Adds a new handler for the `/help` command which is run if the predicate
    /// returns true.
    pub fn help_if<H, HF, P, PF>(&mut self, predicate: P, handler: H)
    where
        H: (Fn(Arc<contexts::Command<contexts::Text>>, Arc<S>) -> HF)
            + Send
            + Sync
            + 'static,
        HF: Future<Output = ()> + Send + 'static,
        P: (Fn(Arc<contexts::Command<contexts::Text>>, Arc<S>) -> PF)
            + Send
            + Sync
            + 'static,
        PF: Future<Output = bool> + Send + 'static,
    {
        let predicate = Arc::new(predicate);
        let handler = Arc::new(handler);
        self.command("help", move |context, state| {
            let predicate = Arc::clone(&predicate);
            let handler = Arc::clone(&handler);
            async move {
                if predicate(Arc::clone(&context), Arc::clone(&state)).await {
                    handler(context, state).await
                }
            }
        });
    }

    /// Adds a new handler for the `/settings` command.
    pub fn settings<H, F>(&mut self, handler: H)
    where
        H: (Fn(Arc<contexts::Command<contexts::Text>>, Arc<S>) -> F)
            + Send
            + Sync
            + 'static,
        F: Future<Output = ()> + Send + 'static,
    {
        self.command("settings", handler);
    }

    /// Adds a new handler for the `/settings` command which is run
    /// if the predicate returns true.
    pub fn settings_if<H, HF, P, PF>(&mut self, predicate: P, handler: H)
    where
        H: (Fn(Arc<contexts::Command<contexts::Text>>, Arc<S>) -> HF)
            + Send
            + Sync
            + 'static,
        HF: Future<Output = ()> + Send + 'static,
        P: (Fn(Arc<contexts::Command<contexts::Text>>, Arc<S>) -> PF)
            + Send
            + Sync
            + 'static,
        PF: Future<Output = bool> + Send + 'static,
    {
        let predicate = Arc::new(predicate);
        let handler = Arc::new(handler);
        self.command("settings", move |context, state| {
            let predicate = Arc::clone(&predicate);
            let handler = Arc::clone(&handler);
            async move {
                if predicate(Arc::clone(&context), Arc::clone(&state)).await {
                    handler(context, state).await
                }
            }
        });
    }

    /// Adds a new handler for an edited command.
    pub fn edited_command<H, F>(&mut self, command: &'static str, handler: H)
    where
        H: (Fn(Arc<contexts::Command<contexts::EditedText>>, Arc<S>) -> F)
            + Send
            + Sync
            + 'static,
        F: Future<Output = ()> + Send + 'static,
    {
        let state = Arc::clone(&self.state);
        self.inner.edited_command(command, move |context| {
            handler(context, Arc::clone(&state))
        });
    }

    /// Adds a new handler for an edited command which is run if the predicate
    /// returns true.
    pub fn edited_command_if<H, HF, P, PF>(
        &mut self,
        command: &'static str,
        predicate: P,
        handler: H,
    ) where
        H: (Fn(Arc<contexts::Command<contexts::EditedText>>, Arc<S>) -> HF)
            + Send
            + Sync
            + 'static,
        HF: Future<Output = ()> + Send + 'static,
        P: (Fn(Arc<contexts::Command<contexts::EditedText>>, Arc<S>) -> PF)
            + Send
            + Sync
            + 'static,
        PF: Future<Output = bool> + Send + 'static,
    {
        let predicate = Arc::new(predicate);
        let handler = Arc::new(handler);
        self.edited_command(command, move |context, state| {
            let predicate = Arc::clone(&predicate);
            let handler = Arc::clone(&handler);
            async move {
                if predicate(Arc::clone(&context), Arc::clone(&state)).await {
                    handler(context, state).await
                }
            }
        });
    }

    /// Adds a new handler for a sequence of edited commands.
    pub fn edited_commands<Cm, H, F>(&mut self, commands: Cm, handler: H)
    where
        Cm: IntoIterator<Item = &'static str>,
        H: (Fn(Arc<contexts::Command<contexts::EditedText>>, Arc<S>) -> F)
            + Send
            + Sync
            + 'static,
        F: Future<Output = ()> + Send + 'static,
    {
        let state = Arc::clone(&self.state);
        self.inner.edited_commands(commands, move |context| {
            handler(context, Arc::clone(&state))
        });
    }

    /// Adds a new handler for a sequence of edited commands which is run
    /// if the predicate returns true.
    pub fn edited_commands_if<Cm, H, HF, P, PF>(
        &mut self,
        commands: Cm,
        predicate: P,
        handler: H,
    ) where
        Cm: IntoIterator<Item = &'static str>,
        H: (Fn(Arc<contexts::Command<contexts::EditedText>>, Arc<S>) -> HF)
            + Send
            + Sync
            + 'static,
        HF: Future<Output = ()> + Send + 'static,
        P: (Fn(Arc<contexts::Command<contexts::EditedText>>, Arc<S>) -> PF)
            + Send
            + Sync
            + 'static,
        PF: Future<Output = bool> + Send + 'static,
    {
        let predicate = Arc::new(predicate);
        let handler = Arc::new(handler);
        self.edited_commands(commands, move |context, state| {
            let predicate = Arc::clone(&predicate);
            let handler = Arc::clone(&handler);
            async move {
                if predicate(Arc::clone(&context), Arc::clone(&state)).await {
                    handler(context, state).await
                }
            }
        });
    }

    handler! {
        contexts::Update,
        /// Adds a new handler which is run after handling an update.
        after_update,
        /// Adds a new handler which is run after handling an update and
        /// if the predicate returns true.
        after_update_if,
    }

    handler! {
        contexts::Animation,
        /// Adds a new handler for animations.
        animation,
        /// Adds a new handler for animations which is run if the predicate
        /// returns true.
        animation_if,
    }

    handler! {
        contexts::Audio,
        /// Adds a new handler for audio.
        audio,
        /// Adds a new handler for audio which is run if the predicate
        /// returns true.
        audio_if,
    }

    handler! {
        contexts::Update,
        /// Adds a new handler which is run before handling an update.
        before_update,
        /// Adds a new handler which is run before handling an update and
        /// if the predicate returns true.
        before_update_if,
    }

    handler! {
        contexts::ChosenInline,
        /// Adds a new handler for chosen inline results.
        chosen_inline,
        /// Adds a new handler for chosen inline results which is run
        /// if the predicate returns true.
        chosen_inline_if,
    }

    handler! {
        contexts::Contact,
        /// Adds a new handler for contacts.
        contact,
        /// Adds a new handler for contacts which is run if the predicate
        /// returns true.
        contact_if,
    }

    handler! {
        contexts::ConnectedWebsite,
        /// Adds a new handler for connected websites.
        connected_website,
        /// Adds a new handler for connected websites which is run
        /// if the predicate returns true.
        connected_website_if,
    }

    handler! {
        contexts::CreatedGroup,
        /// Adds a new handler for created groups.
        created_group,
        /// Adds a new handler for created groups which is run if the
        /// predicate returns true.
        created_group_if,
    }

    handler! {
        contexts::DataCallback,
        /// Adds a new handler for data callbacks.
        data_callback,
        /// Adds a new handler for data callbacks which is run if the
        /// predicate returns true.
        data_callback_if,
    }

    handler! {
        contexts::DeletedChatPhoto,
        /// Adds a new handler for deleted chat photos.
        deleted_chat_photo,
        /// Adds a new handler for deleted chat photos which is run
        /// if the predicate returns true.
        deleted_chat_photo_if,
    }

    handler! {
        contexts::Dice,
        /// Adds a new handler for dice.
        dice,
        /// Adds a new handler for dice which is run if the predicate
        /// returns true.
        dice_if,
    }

    handler! {
        contexts::Document,
        /// Adds a new handler for documents.
        document,
        /// Adds a new handler for documents which is run if the predicate
        /// returns true.
        document_if,
    }

    handler! {
        contexts::EditedAnimation,
        /// Adds a new handler for edited animations.
        edited_animation,
        /// Adds a new handler for edited animations which is run
        /// if the predicate returns true.
        edited_animation_if,
    }

    handler! {
        contexts::EditedAudio,
        /// Adds a new handler for edited audio.
        edited_audio,
        /// Adds a new handler for edited audio which is run if the
        /// predicate returns true.
        edited_audio_if,
    }

    handler! {
        contexts::EditedDocument,
        /// Adds a new handler for edited documents.
        edited_document,
        /// Adds a new handler for edited documents which is run
        /// if the predicate returns true.
        edited_document_if,
    }

    handler! {
        contexts::EditedLocation,
        /// Adds a new handler for edited locations.
        edited_location,
        /// Adds a new handler for edited locations which is run
        /// if the predicate returns true.
        edited_location_if,
    }

    handler! {
        contexts::EditedPhoto,
        /// Adds a new handler for edited photos.
        edited_photo,
        /// Adds a new handler for edited photos which is run if the
        /// predicate returns true.
        edited_photo_if,
    }

    handler! {
        contexts::EditedText,
        /// Adds a new handler for edited text messages.
        edited_text,
        /// Adds a new handler for edited text messages which is run
        /// if the predicate returns true.
        edited_text_if,
    }

    handler! {
        contexts::EditedVideo,
        /// Adds a new handler for edited videos.
        edited_video,
        /// Adds a new handler for edited videos which is run if the
        /// predicate returns true.
        edited_video_if,
    }

    handler! {
        contexts::GameCallback,
        /// Adds a new handler for game callbacks.
        game_callback,
        /// Adds a new handler for game callbacks which is run if the
        /// predicate returns true.
        game_callback_if,
    }

    handler! {
        contexts::Game,
        /// Adds a new handler for game messages.
        game,
        /// Adds a new handler for game messages which is run if the
        /// predicate returns true.
        game_if,
    }

    handler! {
        contexts::Inline,
        /// Adds a new handler for inline queries.
        inline,
        /// Adds a new handler for inline queries which is run if the
        /// predicate returns true.
        inline_if,
    }

    handler! {
        contexts::Invoice,
        /// Adds a new handler for invoices.
        invoice,
        /// Adds a new handler for invoices which is run if the predicate
        /// returns true.
        invoice_if,
    }

    handler! {
        contexts::LeftMember,
        /// Adds a new handler for left members.
        left_member,
        /// Adds a new handler for left members which is run if the
        /// predicate returns true.
        left_member_if,
    }

    handler! {
        contexts::Location,
        /// Adds a new handler for locations.
        location,
        /// Adds a new handler for locations which is run if the predicate
        /// returns true.
        location_if,
    }

    handler! {
        contexts::Migration,
        /// Adds a new handler for migrations.
        migration,
        /// Adds a new handler for migrations which is run if the predicate
        /// returns true.
        migration_if,
    }

    handler! {
        contexts::NewChatPhoto,
        /// Adds a new handler for new chat photos.
        new_chat_photo,
        /// Adds a new handler for new chat photos which is run if the
        /// predicate returns true.
        new_chat_photo_if,
    }

    handler! {
        contexts::NewChatTitle,
        /// Adds a new handler for new chat titles.
        new_chat_title,
        /// Adds a new handler for new chat titles which is run if the
        /// predicate returns true.
        new_chat_title_if,
    }

    handler! {
        contexts::NewMembers,
        /// Adds a new handler for new members.
        new_members,
        /// Adds a new handler for new members which is run if the predicate
        /// returns true.
        new_members_if,
    }

    handler! {
        contexts::Passport,
        /// Adds a new handler for passport data.
        passport,
        /// Adds a new handler for passport data which is run if the
        /// predicate returns true.
        passport_if,
    }

    handler! {
        contexts::Payment,
        /// Adds a new handler for successful payments.
        payment,
        /// Adds a new handler for successful payments which is run
        /// if the predicate returns true.
        payment_if,
    }

    handler! {
        contexts::Photo,
        /// Adds a new handler for photos.
        photo,
        /// Adds a new handler for photos which is run if the predicate
        /// returns true.
        photo_if,
    }

    handler! {
        contexts::PinnedMessage,
        /// Adds a new handler for pinned messages.
        pinned_message,
        /// Adds a new handler for pinned messages which is run if the
        /// predicate returns true.
        pinned_message_if,
    }

    handler! {
        contexts::Poll,
        /// Adds a new handler for poll messages.
        poll,
        /// Adds a new handler for poll messages which is run if the
        /// predicate returns true.
        poll_if,
    }

    handler! {
        contexts::PreCheckout,
        /// Adds a new handler for pre-checkout queries.
        pre_checkout,
        /// Adds a new handler for pre-checkout queries which is run
        /// if the predicate returns true.
        pre_checkout_if,
    }

    handler! {
        contexts::Shipping,
        /// Adds a new handler for shipping queries.
        shipping,
        /// Adds a new handler for shipping queries which is run
        /// if the predicate returns true.
        shipping_if,
    }

    handler! {
        contexts::Sticker,
        /// Adds a new handler for stickers.
        sticker,
        /// Adds a new handler for stickers which is run if the predicate
        /// returns true.
        sticker_if,
    }

    handler! {
        contexts::Text,
        /// Adds a new handler for text messages.
        text,
        /// Adds a new handler for text messages which is run if the
        /// predicate returns true.
        text_if,
    }

    handler! {
        contexts::Unhandled,
        /// Adds a new handler for unhandled updates.
        unhandled,
        /// Adds a new handler for unhandled updates which is run
        /// if the predicate returns true.
        unhandled_if,
    }

    handler! {
        contexts::UpdatedPoll,
        /// Adds a new handler for new states of polls.
        updated_poll,
        /// Adds a new handler for new states of polls which is run
        /// if the predicate returns true.
        updated_poll_if,
    }

    handler! {
        contexts::PollAnswer,
        /// Adds a new handler for new answers in the poll.
        poll_answer,
        /// Adds a new handler for new answers in the poll which is run
        /// if the predicate returns true.
        poll_answer_if,
    }

    handler! {
        contexts::Venue,
        /// Adds a new handler for venues.
        venue,
        /// Adds a new handler for venues which is run if the predicate
        /// returns true.
        venue_if,
    }

    handler! {
        contexts::Video,
        /// Adds a new handler for videos.
        video,
        /// Adds a new handler for videos which is run if the predicate
        /// returns true.
        video_if,
    }

    handler! {
        contexts::VideoNote,
        /// Adds a new handler for video notes.
        video_note,
        /// Adds a new handler for video notes which is run if the predicate
        /// returns true.
        video_note_if,
    }

    handler! {
        contexts::Voice,
        /// Adds a new handler for voice messages.
        voice,
        /// Adds a new handler for voice messages which is run if the
        /// predicate returns true.
        voice_if,
    }
}

impl<S> StatefulEventLoop<S> {
    /// Fetches the bot's username.
    ///
    /// The username is used when checking if a command such as
    /// `/command@username` was directed to the bot.
    // `StatefulEventLoop` can be constructed only if `S: Send + Sync`
    #[allow(clippy::future_not_send)]
    pub async fn fetch_username(&mut self) -> Result<(), errors::MethodCall> {
        self.inner.fetch_username().await
    }
}