telexide_fork/client/
client.rs

1use super::{
2    APIConnector, ClientBuilder, Context, EventHandlerFunc, RawEventHandlerFunc, UpdatesStream,
3    Webhook, WebhookOptions,
4};
5use crate::{
6    api::{
7        types::{SetWebhook, UpdateType},
8        APIClient,
9    },
10    framework::Framework,
11    model::Update,
12    Result,
13};
14use futures::StreamExt;
15use parking_lot::RwLock;
16use std::sync::Arc;
17use typemap::ShareMap;
18
19/// The Client is the main object to manage your interaction with telegram.
20///
21/// It handles the incoming update objects from telegram and dispatches them to
22/// your event handlers and commands, providing those with access to shared data
23/// and easy access to the telegram API itself.
24///
25/// # Event Handlers
26///
27/// Event handlers can be configured to be called upon every update that is
28/// received. (Later on support will be added for subscribing to more specific
29/// update events)
30///
31/// Note that you do not need to manually handle retrieving updates,
32/// as they are handled internally and then dispatched to your event handlers.
33///
34/// # Examples
35/// ```rust,no_run
36/// use telexide_fork::prelude::*;
37///
38/// #[prepare_listener]
39/// async fn event_listener(ctx: Context, update: Update) {
40///     println!("received an update!")
41/// }
42///
43/// #[tokio::main]
44/// async fn main() -> telexide_fork::Result<()> {
45///     let token = String::from("test token");
46///     let mut client = Client::new(&token);
47///     client.subscribe_handler_func(event_listener);
48///
49///     client.start().await
50/// }
51/// ```
52#[derive(Clone)]
53pub struct Client {
54    /// The API client, it contains all the methods to talk to the telegram api,
55    /// more documentation can be found over at the [`API`] docs
56    ///
57    /// [`API`]: ../api/trait.API.html
58    pub api_client: Arc<Box<APIConnector>>,
59    /// Your custom data that you want to be shared amongst event handlers and
60    /// commands.
61    ///
62    /// The purpose of the data field is to be accessible and persistent across
63    /// contexts; that is, data can be modified by one context, and will persist
64    /// through the future and be accessible through other contexts. This is
65    /// useful for anything that should "live" through the program: counters,
66    /// database connections, custom user caches, etc.
67    /// Therefore this ShareMap requires all types it will contain to be Send +
68    /// Sync.
69    ///
70    /// When using a [`Context`], this data will be available as
71    /// [`Context::data`].
72    ///
73    /// Refer to the [repeat_image_bot] example for an example on using the
74    /// `data` field
75    ///
76    /// [repeat_image_bot]: https://github.com/callieve/telexide/tree/master/examples/repeat_image_bot.rs
77    pub data: Arc<RwLock<ShareMap>>,
78    pub(super) event_handlers: Vec<EventHandlerFunc>,
79    pub(super) raw_event_handlers: Vec<RawEventHandlerFunc>,
80    pub(super) framework: Option<Arc<Framework>>,
81    pub(super) webhook_opts: Option<WebhookOptions>,
82    /// The update types that you want to receive, see the documentation of
83    /// [`UpdateType`] for more information
84    pub allowed_updates: Vec<UpdateType>,
85}
86
87impl Client {
88    /// Creates a Client object with default values and no framework
89    pub fn new<T: ToString>(token: &T) -> Self {
90        Self {
91            api_client: Arc::new(Box::new(APIClient::new(None, token))),
92            event_handlers: Vec::new(),
93            raw_event_handlers: Vec::new(),
94            data: Arc::new(RwLock::new(ShareMap::custom())),
95            framework: None,
96            webhook_opts: None,
97            allowed_updates: Vec::new(),
98        }
99    }
100
101    /// Creates a Client object with default values, but with a [`Framework`]
102    pub fn with_framework<T: ToString>(fr: Arc<Framework>, token: &T) -> Self {
103        Self {
104            api_client: Arc::new(Box::new(APIClient::new(None, token))),
105            event_handlers: Vec::new(),
106            raw_event_handlers: Vec::new(),
107            data: Arc::new(RwLock::new(ShareMap::custom())),
108            webhook_opts: None,
109            framework: Some(fr),
110            allowed_updates: Vec::new(),
111        }
112    }
113
114    /// Returns a new `ClientBuilder`
115    pub fn builder() -> ClientBuilder {
116        ClientBuilder::new()
117    }
118
119    /// Starts the client and blocks until an error happens in the updates
120    /// stream or the program exits (for example due to a panic).
121    /// If using the framework, it will update your commands in telegram.
122    /// If using a webhook, it will handle it, else it will use polling using a
123    /// default [`UpdatesStream`] object
124    pub async fn start(&self) -> Result<()> {
125        if let Some(opts) = &self.webhook_opts {
126            self.start_with_webhook(opts).await
127        } else {
128            let mut stream = UpdatesStream::new(self.api_client.clone());
129            stream.set_allowed_updates(self.allowed_updates.clone());
130
131            self.start_with_stream(&mut stream).await
132        }
133    }
134
135    /// Starts the client and blocks until an error happens in the updates
136    /// stream or the program exits (for example due to a panic).
137    /// If using the framework, it will update your commands in telegram
138    /// You have to provide your own [`UpdatesStream`] object
139    pub async fn start_with_stream(&self, stream: &mut UpdatesStream) -> Result<()> {
140        if let Some(fr) = self.framework.clone() {
141            self.api_client
142                .set_my_commands(fr.get_commands().into())
143                .await?;
144        }
145
146        log::info!("starting long polling to listen for updates from telegram api");
147        while let Some(poll) = stream.next().await {
148            match poll {
149                Ok(update) => {
150                    self.fire_handlers(update);
151                }
152                Err(err) => return Err(err),
153            }
154        }
155
156        Ok(())
157    }
158
159    /// Starts the client and blocks until an error happens in the webhook
160    /// handling or the program exits (for example due to a panic).
161    /// If using the framework, it will update your commands in telegram
162    /// You have to provide your own [`WebhookOptions`] object
163    pub async fn start_with_webhook(&self, opts: &WebhookOptions) -> Result<()> {
164        if let Some(fr) = self.framework.clone() {
165            self.api_client
166                .set_my_commands(fr.get_commands().into())
167                .await?;
168        }
169
170        if let Some(webhook_url) = &opts.url {
171            self.api_client
172                .set_webhook(SetWebhook {
173                    url: webhook_url.to_string(),
174                    certificate: None,
175                    max_connections: None,
176                    allowed_updates: Some(self.allowed_updates.clone()),
177                    drop_pending_updates: None,
178                    ip_address: None, // TODO: add opts for these
179                })
180                .await?;
181        }
182
183        log::info!("starting to listen on the webhook");
184        let mut receiver = Webhook::new(opts).start();
185        while let Some(u) = receiver.recv().await {
186            match u {
187                Ok(update) => {
188                    self.fire_handlers(update);
189                }
190                Err(err) => return Err(err),
191            }
192        }
193
194        Ok(())
195    }
196
197    /// Subscribes an update event handler function ([`EventHandlerFunc`]) to
198    /// the client and will be ran whenever a new update is received
199    pub fn subscribe_handler_func(&mut self, handler: EventHandlerFunc) {
200        self.event_handlers.push(handler);
201    }
202
203    /// Subscribes a raw update event handler function ([`RawEventHandlerFunc`])
204    /// to the client and will be ran whenever a new update is received
205    pub fn subscribe_raw_handler(&mut self, handler: RawEventHandlerFunc) {
206        self.raw_event_handlers.push(handler);
207    }
208
209    // public only for testing purposes
210    #[doc(hidden)]
211    pub fn fire_handlers(&self, update: Update) {
212        for h in self.raw_event_handlers.clone() {
213            let ctx = Context::new(self.api_client.clone(), self.data.clone());
214            let u = update.clone();
215            tokio::spawn(async move {
216                h(ctx, u.into()).await;
217            });
218        }
219
220        for h in self.event_handlers.clone() {
221            let ctx = Context::new(self.api_client.clone(), self.data.clone());
222            let u = update.clone();
223            tokio::spawn(async move {
224                h(ctx, u).await;
225            });
226        }
227
228        if self.framework.is_some() {
229            let ctx = Context::new(self.api_client.clone(), self.data.clone());
230            let fr = self.framework.clone();
231            fr.as_ref()
232                .expect("Framework needs to be set before trying to fire commands")
233                .fire_commands(ctx, update);
234        }
235    }
236}
237
238impl From<Box<APIConnector>> for Client {
239    fn from(api: Box<APIConnector>) -> Self {
240        Self {
241            api_client: Arc::new(api),
242            event_handlers: Vec::new(),
243            raw_event_handlers: Vec::new(),
244            data: Arc::new(RwLock::new(ShareMap::custom())),
245            framework: None,
246            webhook_opts: None,
247            allowed_updates: Vec::new(),
248        }
249    }
250}