1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
use std::{future::Future, sync::Arc};

use reqwest::Client;
use serde::{de::DeserializeOwned, Serialize};

use crate::{
    net,
    requests::{MultipartPayload, Payload, ResponseResult},
    serde_multipart,
};

mod api;
mod download;

const TELOXIDE_TOKEN: &str = "TELOXIDE_TOKEN";

/// A requests sender.
///
/// This is the main type of the library, it allows to send requests to the
/// [Telegram Bot API] and download files.
///
/// ## TBA methods
///
/// All TBA methods are located in the [`Requester`] [`impl for Bot`]. This
/// allows for opt-in behaviours using requester [adaptors].
///
/// ```
/// # async {
/// use teloxide_core::prelude::*;
///
/// let bot = Bot::new("TOKEN");
/// dbg!(bot.get_me().await?);
/// # Ok::<_, teloxide_core::RequestError>(()) };
/// ```
///
/// [`Requester`]: crate::requests::Requester
/// [`impl for Bot`]: Bot#impl-Requester
/// [adaptors]: crate::adaptors
///
/// ## File download
///
/// In the similar way as with TBA methods, file downloading methods are located
/// in a trait — [`Download<'_>`]. See its documentation for more.
///
/// [`Download<'_>`]: crate::net::Download
///
/// ## Clone cost
///
/// `Bot::clone` is relatively cheap, so if you need to share `Bot`, it's
/// recommended to clone it, instead of wrapping it in [`Arc<_>`].
///
/// [`Arc`]: std::sync::Arc
/// [Telegram Bot API]: https://core.telegram.org/bots/api
#[must_use]
#[derive(Debug, Clone)]
pub struct Bot {
    token: Arc<str>,
    api_url: Arc<reqwest::Url>,
    client: Client,
}

/// Constructors
impl Bot {
    /// Creates a new `Bot` with the specified token and the default
    /// [http-client](reqwest::Client).
    ///
    /// # Panics
    ///
    /// If it cannot create [`reqwest::Client`].
    pub fn new<S>(token: S) -> Self
    where
        S: Into<String>,
    {
        let client = net::default_reqwest_settings().build().expect("Client creation failed");

        Self::with_client(token, client)
    }

    /// Creates a new `Bot` with the specified token and your
    /// [`reqwest::Client`].
    ///
    /// # Caution
    ///
    /// Your custom client might not be configured correctly to be able to work
    /// in long time durations, see [issue 223].
    ///
    /// [`reqwest::Client`]: https://docs.rs/reqwest/latest/reqwest/struct.Client.html
    /// [issue 223]: https://github.com/teloxide/teloxide/issues/223
    pub fn with_client<S>(token: S, client: Client) -> Self
    where
        S: Into<String>,
    {
        let token = Into::<String>::into(token).into();
        let api_url = Arc::new(
            reqwest::Url::parse(net::TELEGRAM_API_URL)
                .expect("Failed to parse default Telegram bot API url"),
        );

        Self { token, api_url, client }
    }

    /// Creates a new `Bot` with the `TELOXIDE_TOKEN` & `TELOXIDE_PROXY`
    /// environmental variables (a bot's token & a proxy) and the default
    /// [`reqwest::Client`].
    ///
    /// This function passes the value of `TELOXIDE_PROXY` into
    /// [`reqwest::Proxy::all`], if it exists, otherwise returns the default
    /// client.
    ///
    /// # Panics
    ///  - If cannot get the `TELOXIDE_TOKEN`  environmental variable.
    ///  - If it cannot create [`reqwest::Client`].
    ///
    /// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html
    /// [`reqwest::Proxy::all`]: https://docs.rs/reqwest/latest/reqwest/struct.Proxy.html#method.all
    pub fn from_env() -> Self {
        Self::from_env_with_client(crate::net::client_from_env())
    }

    /// Creates a new `Bot` with the `TELOXIDE_TOKEN` environmental variable (a
    /// bot's token) and your [`reqwest::Client`].
    ///
    /// # Panics
    /// If cannot get the `TELOXIDE_TOKEN` environmental variable.
    ///
    /// # Caution
    /// Your custom client might not be configured correctly to be able to work
    /// in long time durations, see [issue 223].
    ///
    /// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html
    /// [issue 223]: https://github.com/teloxide/teloxide/issues/223
    pub fn from_env_with_client(client: Client) -> Self {
        Self::with_client(get_env(TELOXIDE_TOKEN), client)
    }

    /// Sets a custom API URL.
    ///
    /// For example, you can run your own [Telegram bot API server][tbas] and
    /// set its URL using this method.
    ///
    /// [tbas]: https://github.com/tdlib/telegram-bot-api
    ///
    /// ## Examples
    ///
    /// ```
    /// use teloxide_core::{
    ///     requests::{Request, Requester},
    ///     Bot,
    /// };
    ///
    /// # async {
    /// let url = reqwest::Url::parse("https://localhost/tbas").unwrap();
    /// let bot = Bot::new("TOKEN").set_api_url(url);
    /// // From now all methods will use "https://localhost/tbas" as an API URL.
    /// bot.get_me().await
    /// # };
    /// ```
    ///
    /// ## Multi-instance behaviour
    ///
    /// This method only sets the url for one bot instace, older clones are
    /// unaffected.
    ///
    /// ```
    /// use teloxide_core::Bot;
    ///
    /// let bot = Bot::new("TOKEN");
    /// let bot2 = bot.clone();
    /// let bot = bot.set_api_url(reqwest::Url::parse("https://example.com/").unwrap());
    ///
    /// assert_eq!(bot.api_url().as_str(), "https://example.com/");
    /// assert_eq!(bot.clone().api_url().as_str(), "https://example.com/");
    /// assert_ne!(bot2.api_url().as_str(), "https://example.com/");
    /// ```
    pub fn set_api_url(mut self, url: reqwest::Url) -> Self {
        self.api_url = Arc::new(url);
        self
    }
}

/// Getters
impl Bot {
    /// Returns currently used token.
    #[must_use]
    pub fn token(&self) -> &str {
        &self.token
    }

    /// Returns currently used http-client.
    #[must_use]
    pub fn client(&self) -> &Client {
        &self.client
    }

    /// Returns currently used token API url.
    #[must_use]
    pub fn api_url(&self) -> reqwest::Url {
        reqwest::Url::clone(&*self.api_url)
    }
}

impl Bot {
    pub(crate) fn execute_json<P>(
        &self,
        payload: &P,
    ) -> impl Future<Output = ResponseResult<P::Output>> + 'static
    where
        P: Payload + Serialize,
        P::Output: DeserializeOwned + 'static,
    {
        let client = self.client.clone();
        let token = Arc::clone(&self.token);
        let api_url = Arc::clone(&self.api_url);

        let timeout_hint = payload.timeout_hint();
        let params = serde_json::to_vec(payload)
            // this `expect` should be ok since we don't write request those may trigger error here
            .expect("serialization of request to be infallible");

        // async move to capture client&token&api_url&params
        async move {
            net::request_json(
                &client,
                token.as_ref(),
                reqwest::Url::clone(&*api_url),
                P::NAME,
                params,
                timeout_hint,
            )
            .await
        }
    }

    pub(crate) fn execute_multipart<P>(
        &self,
        payload: &mut P,
    ) -> impl Future<Output = ResponseResult<P::Output>>
    where
        P: MultipartPayload + Serialize,
        P::Output: DeserializeOwned + 'static,
    {
        let client = self.client.clone();
        let token = Arc::clone(&self.token);
        let api_url = Arc::clone(&self.api_url);

        let timeout_hint = payload.timeout_hint();
        let params = serde_multipart::to_form(payload);

        // async move to capture client&token&api_url&params
        async move {
            let params = params?.await;
            net::request_multipart(
                &client,
                token.as_ref(),
                reqwest::Url::clone(&*api_url),
                P::NAME,
                params,
                timeout_hint,
            )
            .await
        }
    }

    pub(crate) fn execute_multipart_ref<P>(
        &self,
        payload: &P,
    ) -> impl Future<Output = ResponseResult<P::Output>>
    where
        P: MultipartPayload + Serialize,
        P::Output: DeserializeOwned + 'static,
    {
        let client = self.client.clone();
        let token = Arc::clone(&self.token);
        let api_url = self.api_url.clone();

        let timeout_hint = payload.timeout_hint();
        let params = serde_multipart::to_form_ref(payload);

        // async move to capture client&token&api_url&params
        async move {
            let params = params?.await;
            net::request_multipart(
                &client,
                token.as_ref(),
                reqwest::Url::clone(&*api_url),
                P::NAME,
                params,
                timeout_hint,
            )
            .await
        }
    }
}

fn get_env(env: &'static str) -> String {
    std::env::var(env).unwrap_or_else(|_| panic!("Cannot get the {env} env variable"))
}