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}