rust_tdlib/client/
mod.rs

1//! Module contains structs and traits, required for proper interaction with Telegram server.
2#[doc(hidden)]
3mod observer;
4
5/// Handlers for all incoming data
6pub mod worker;
7
8#[doc(hidden)]
9mod api;
10/// Authorization state handlers.
11pub mod auth_handler;
12
13#[doc(hidden)]
14pub mod tdlib_client;
15
16pub use auth_handler::{
17    AuthStateHandler, AuthStateHandlerProxy, ClientIdentifier, ConsoleAuthStateHandler,
18    SignalAuthStateHandler,
19};
20use observer::OBSERVER;
21use serde::de::DeserializeOwned;
22pub use worker::{Worker, WorkerBuilder};
23
24use crate::client::auth_handler::ClientAuthStateHandler;
25use crate::types::{
26    AuthorizationStateWaitCode, AuthorizationStateWaitEncryptionKey,
27    AuthorizationStateWaitPassword, AuthorizationStateWaitPhoneNumber,
28    AuthorizationStateWaitRegistration, Close, Ok, RFunction, TdlibParameters, Update,
29};
30use crate::{
31    errors::{Error, Result},
32    types::Error as TDLibError,
33    utils,
34};
35use async_trait::async_trait;
36use tdlib_client::{TdJson, TdLibClient};
37use tokio::sync::mpsc;
38
39const CLIENT_NOT_AUTHORIZED: Error = Error::Internal("client not authorized yet");
40const CLOSED_RECEIVER_ERROR: Error = Error::Internal("receiver already closed");
41const INVALID_RESPONSE_ERROR: Error = Error::Internal("receive invalid response");
42const NO_EXTRA: Error = Error::Internal("invalid tdlib response type, not have `extra` field");
43
44/// Represents state of particular client instance.
45#[derive(Debug, Clone, PartialEq)]
46pub enum ClientState {
47    /// Client opened. You can start interaction
48    Opened,
49    /// Client closed. You must reopen it if you want to interact with TDLib
50    Closed,
51    /// Client not authorizde yet
52    Authorizing,
53}
54
55#[derive(Debug, Clone)]
56pub struct ConsoleClientStateHandler;
57
58#[async_trait]
59impl ClientAuthStateHandler for ConsoleClientStateHandler {
60    async fn handle_wait_code(&self, _wait_code: &AuthorizationStateWaitCode) -> String {
61        println!("waiting for auth code");
62        utils::wait_input_sync()
63    }
64
65    async fn handle_encryption_key(
66        &self,
67        _wait_encryption_key: &AuthorizationStateWaitEncryptionKey,
68    ) -> String {
69        println!("waiting for encryption key");
70        utils::wait_input_sync()
71    }
72
73    async fn handle_wait_password(
74        &self,
75        _wait_password: &AuthorizationStateWaitPassword,
76    ) -> String {
77        println!("waiting for password");
78        utils::wait_input_sync()
79    }
80
81    async fn handle_wait_client_identifier(
82        &self,
83        _: &AuthorizationStateWaitPhoneNumber,
84    ) -> ClientIdentifier {
85        loop {
86            println!("ConsoleClientStateHandler: choose one of phone number (p) or bot token (b)");
87            let inp = utils::wait_input_sync();
88            match inp.to_lowercase().trim() {
89                "b" => {
90                    println!("enter bot token");
91                    return ClientIdentifier::BotToken(utils::wait_input_sync());
92                }
93                "p" => {
94                    println!("ConsoleClientStateHandler: enter phone number");
95                    return ClientIdentifier::PhoneNumber(utils::wait_input_sync());
96                }
97                _ => {
98                    // invalid input, next iteration}
99                    continue;
100                }
101            }
102        }
103    }
104
105    async fn handle_wait_registration(
106        &self,
107        _wait_registration: &AuthorizationStateWaitRegistration,
108    ) -> (String, String) {
109        loop {
110            println!("waiting for first_name and second_name separated by comma");
111            let inp: String = utils::wait_input_sync();
112            if let Some((f, l)) = utils::split_string(inp, ',') {
113                return (f, l);
114            }
115        }
116    }
117}
118
119#[derive(Debug, Clone)]
120pub struct ConsoleClientStateHandlerIdentified(ClientIdentifier);
121
122impl ConsoleClientStateHandlerIdentified {
123    pub fn new(ident: ClientIdentifier) -> Self {
124        Self(ident)
125    }
126}
127
128#[async_trait]
129impl ClientAuthStateHandler for ConsoleClientStateHandlerIdentified {
130    async fn handle_wait_code(&self, _wait_code: &AuthorizationStateWaitCode) -> String {
131        println!("waiting for auth code");
132        utils::wait_input_sync()
133    }
134
135    async fn handle_encryption_key(
136        &self,
137        _wait_encryption_key: &AuthorizationStateWaitEncryptionKey,
138    ) -> String {
139        println!("waiting for encryption key");
140        utils::wait_input_sync()
141    }
142
143    async fn handle_wait_password(
144        &self,
145        _wait_password: &AuthorizationStateWaitPassword,
146    ) -> String {
147        println!("waiting for password");
148        utils::wait_input_sync()
149    }
150
151    async fn handle_wait_client_identifier(
152        &self,
153        _: &AuthorizationStateWaitPhoneNumber,
154    ) -> ClientIdentifier {
155        self.0.clone()
156    }
157
158    async fn handle_wait_registration(
159        &self,
160        _wait_registration: &AuthorizationStateWaitRegistration,
161    ) -> (String, String) {
162        loop {
163            println!("waiting for first_name and second_name separated by comma");
164            let inp: String = utils::wait_input_sync();
165            if let Some((f, l)) = utils::split_string(inp, ',') {
166                return (f, l);
167            }
168        }
169    }
170}
171
172/// Struct stores all methods which you can call to interact with Telegram, such as:
173/// [send_message](Api::send_message), [download_file](Api::download_file), [search_chats](Api::search_chats) and so on.
174#[derive(Clone, Debug)]
175pub struct Client<S>
176where
177    S: TdLibClient + Clone,
178{
179    tdlib_client: S,
180    client_id: Option<i32>,
181    is_started: bool,
182    updates_sender: Option<mpsc::Sender<Box<Update>>>,
183    tdlib_parameters: TdlibParameters,
184    auth_state_channel_size: Option<usize>,
185    auth_handler: Box<dyn ClientAuthStateHandler>,
186}
187
188impl<S> Client<S>
189where
190    S: TdLibClient + Clone,
191{
192    pub(crate) fn get_auth_state_channel_size(&self) -> Option<usize> {
193        self.auth_state_channel_size
194    }
195
196    pub(crate) fn tdlib_parameters(&self) -> &TdlibParameters {
197        &self.tdlib_parameters
198    }
199
200    pub(crate) fn get_auth_handler(&self) -> Box<dyn ClientAuthStateHandler> {
201        dyn_clone::clone_box(&*self.auth_handler)
202    }
203
204    pub fn get_tdlib_client(&self) -> S {
205        self.tdlib_client.clone()
206    }
207
208    pub(crate) fn get_client_id(&self) -> Option<i32> {
209        self.client_id
210    }
211
212    pub(crate) fn take_client_id(&mut self) -> Result<i32> {
213        match self.client_id.take() {
214            Some(client_id) => Ok(client_id),
215            None => Err(CLIENT_NOT_AUTHORIZED),
216        }
217    }
218
219    pub(crate) fn set_client_id(&mut self, client_id: i32) -> Result<()> {
220        match self.client_id {
221            Some(_) => Err(Error::BadRequest("client already authorized")),
222            None => {
223                self.client_id = Some(client_id);
224                self.is_started = true;
225                Ok(())
226            }
227        }
228    }
229
230    pub(crate) fn updates_sender(&self) -> &Option<mpsc::Sender<Box<Update>>> {
231        &self.updates_sender
232    }
233}
234
235#[derive(Debug)]
236pub struct ClientBuilder<R, A>
237where
238    R: TdLibClient + Clone,
239    A: ClientAuthStateHandler + Clone + 'static,
240{
241    updates_sender: Option<mpsc::Sender<Box<Update>>>,
242    tdlib_parameters: Option<TdlibParameters>,
243    tdlib_client: R,
244    auth_state_channel_size: Option<usize>,
245    auth_handler: A,
246}
247
248impl Default for ClientBuilder<TdJson, ConsoleClientStateHandler> {
249    fn default() -> Self {
250        Self {
251            updates_sender: None,
252            tdlib_parameters: None,
253            auth_state_channel_size: None,
254            tdlib_client: TdJson::new(),
255            auth_handler: ConsoleClientStateHandler,
256        }
257    }
258}
259
260impl<R, A> ClientBuilder<R, A>
261where
262    R: TdLibClient + Clone,
263    A: ClientAuthStateHandler + Clone,
264{
265    /// If you want to receive Telegram updates (messages, channels, etc; see `crate::types::Update`),
266    /// you must set mpsc::Sender here.
267    pub fn with_updates_sender(mut self, updates_sender: mpsc::Sender<Box<Update>>) -> Self {
268        self.updates_sender = Some(updates_sender);
269        self
270    }
271
272    /// If you want to receive all (AuthorizationState)[crate::types::authorization_state::AuthorizationState] changes
273    /// you have to specify positive number of (channel)[tokio::sync::mpsc::channel] size.
274    /// Channel will be used to send state changes.
275    pub fn with_auth_state_channel(mut self, channel_size: usize) -> Self {
276        self.auth_state_channel_size = Some(channel_size);
277        self
278    }
279
280    /// Base parameters for your TDlib instance.
281    pub fn with_tdlib_parameters(mut self, tdlib_parameters: TdlibParameters) -> Self {
282        self.tdlib_parameters = Some(tdlib_parameters);
283        self
284    }
285
286    /// Use it to bound specific auth handler with a client.
287    pub fn with_client_auth_state_handler<NA: ClientAuthStateHandler + Clone>(
288        self,
289        auth_handler: NA,
290    ) -> ClientBuilder<R, NA> {
291        ClientBuilder {
292            auth_handler,
293            tdlib_client: self.tdlib_client,
294            updates_sender: self.updates_sender,
295            tdlib_parameters: self.tdlib_parameters,
296            auth_state_channel_size: self.auth_state_channel_size,
297        }
298    }
299
300    #[doc(hidden)]
301    pub fn with_tdlib_client<T: TdLibClient + Clone>(self, tdlib_client: T) -> ClientBuilder<T, A> {
302        ClientBuilder {
303            tdlib_client,
304            updates_sender: self.updates_sender,
305            tdlib_parameters: self.tdlib_parameters,
306            auth_state_channel_size: self.auth_state_channel_size,
307            auth_handler: self.auth_handler,
308        }
309    }
310
311    pub fn build(self) -> Result<Client<R>> {
312        if self.tdlib_parameters.is_none() {
313            return Err(Error::BadRequest("tdlib_parameters not set"));
314        };
315
316        let client = Client::new(
317            self.tdlib_client,
318            self.auth_handler,
319            self.updates_sender,
320            self.tdlib_parameters.unwrap(),
321            self.auth_state_channel_size,
322        );
323        Ok(client)
324    }
325}
326
327impl Client<TdJson> {
328    pub fn builder() -> ClientBuilder<TdJson, ConsoleClientStateHandler> {
329        ClientBuilder::default()
330    }
331}
332/// TDLib high-level API methods.
333/// Methods documentation can be found in https://core.telegram.org/tdlib/docs/td__api_8h.html
334impl<R> Client<R>
335where
336    R: TdLibClient + Clone,
337{
338    #[doc(hidden)]
339    pub fn new<A: ClientAuthStateHandler + 'static>(
340        tdlib_client: R,
341        auth_handler: A,
342        updates_sender: Option<mpsc::Sender<Box<Update>>>,
343        tdlib_parameters: TdlibParameters,
344        auth_state_channel_size: Option<usize>,
345    ) -> Self {
346        Self {
347            tdlib_client,
348            updates_sender,
349            tdlib_parameters,
350            auth_handler: Box::new(auth_handler),
351            auth_state_channel_size,
352            is_started: false,
353            client_id: None,
354        }
355    }
356
357    pub fn set_updates_sender(&mut self, updates_sender: mpsc::Sender<Box<Update>>) -> Result<()> {
358        match self.is_started {
359            true => Err(Error::BadRequest(
360                "can't set updates sender when client already started",
361            )),
362            false => {
363                self.updates_sender = Some(updates_sender);
364                Ok(())
365            }
366        }
367    }
368
369    /// Just a shortcut for `crate::client::client::Client::close`, allows you to stop the client.
370    pub async fn stop(&self) -> Result<Ok> {
371        self.close(Close::builder().build()).await
372    }
373
374    pub(crate) async fn reload(&mut self, client_id: i32) -> Result<i32> {
375        self.stop().await?;
376        Ok(self.client_id.replace(client_id).unwrap_or_default())
377    }
378
379    async fn make_request<T: RFunction, P: AsRef<T>, Q: DeserializeOwned>(
380        &self,
381        param: P,
382    ) -> Result<Q> {
383        let extra = param.as_ref().extra().ok_or(NO_EXTRA)?;
384        let signal = OBSERVER.subscribe(extra);
385        log::trace!("sending request: {:?}", param.as_ref());
386        self.tdlib_client.send(
387            self.get_client_id().ok_or(CLIENT_NOT_AUTHORIZED)?,
388            param.as_ref(),
389        )?;
390        let received = signal.await;
391        OBSERVER.unsubscribe(extra);
392        match received {
393            Err(_) => Err(CLOSED_RECEIVER_ERROR),
394            Ok(v) => {
395                if error_received(&v) {
396                    match serde_json::from_value::<TDLibError>(v) {
397                        Ok(v) => Err(Error::TDLibError(v)),
398                        Err(e) => {
399                            log::error!("cannot deserialize error response: {:?}", e);
400                            Err(INVALID_RESPONSE_ERROR)
401                        }
402                    }
403                } else {
404                    match serde_json::from_value::<Q>(v) {
405                        Ok(v) => Ok(v),
406                        Err(e) => {
407                            log::error!("response serialization error: {:?}", e);
408                            Err(INVALID_RESPONSE_ERROR)
409                        }
410                    }
411                }
412            }
413        }
414    }
415}
416
417fn error_received(value: &serde_json::Value) -> bool {
418    value.get("@type") == Some(&serde_json::Value::String("error".to_string()))
419}