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
use super::{Bot, InnerBot};
use crate::{
    connectors::Client,
    errors,
    methods::{Close, DeleteWebhook, LogOut},
    proxy::Proxy,
    token::Token,
};
use std::sync::Arc;

/// A builder for a [`Bot`] with advanced configuration.
#[derive(Debug)]
#[must_use]
pub struct Builder(InnerBot);

impl Builder {
    /// Starts constructing a `Bot` with the provided token.
    pub fn with_string_token(token: String) -> Self {
        Self(InnerBot::new(Token(token), Client::https()))
    }

    /// Starts constructing a `Bot`, extracting the token from the provided
    /// environment variable.
    ///
    /// # Panics
    ///
    /// Panics if the variable couldn't be found.
    pub fn with_env_token(env_var: &'static str) -> Self {
        let token = std::env::var(env_var).unwrap_or_else(|_| {
            panic!("[tbot] Bot's token in {} was not specified", env_var)
        });

        Self::with_string_token(token)
    }

    /// Configures a proxy through which all the request will go.
    pub fn proxy(mut self, proxy: impl Into<Proxy>) -> Self {
        let proxy: Proxy = proxy.into();
        self.0.set_client(proxy.into());
        self
    }

    // I don't think marking `localhost` as a link is a good idea
    #[allow(clippy::doc_markdown)]
    /// Configures the URI where the bot will make requests.
    ///
    /// You only need to use this if you're going to use a self-hosted Bot API
    /// server. The provided URI may be `http` or `https`, it also may contain
    /// a path (e.g. `http://localhost/self-hosted-bot-api/`), and `tbot` will
    /// append `bot$TOKEN/$METHOD` to it, in case the server is behind a reverse
    /// proxy. The URI may also contain a query (e.g. `https://localhost/?foo`),
    /// in which case `tbot` will move it after the `bot$TOKEN/$METHOD` part.
    /// For example:
    ///
    /// The provided URI        | A URI generated by `tbot`
    /// ------------------------|-----------------------------------------
    /// `http://localhost`      | `http://localhost/bot$TOKEN/$METHOD`
    /// `http://localhost/foo`  | `http://localhost/foo/bot$TOKEN/$METHOD`
    /// `http://localhost/?foo` | `http://localhost/bot$TOKEN/$METHOD?foo`
    ///
    /// Note that `tbot` itself does not use the query part. `tbot` allows you
    /// to set it just in case your self-hosted Bot API server is behind
    /// a reverse proxy and you need to set the query for some reason. In this
    /// case, the query part is supposed to be removed  when it gets to the
    /// Bot API server.
    ///
    /// Do not forget to call [`log_out`] when you're moving from the cloud Bot
    /// API server, or [`close`] when you're moving from one self-hosted server
    /// to another. This method calls neither method and assumes that you've
    /// already migrated to the server you're configuring.
    ///
    /// # Example
    ///
    /// Say that you've started your local Bot API server on
    /// `http://localhost:8081`, and this is the first time you configure your
    /// bot to use your Bot API server. First, you need to call [`log_out`],
    /// and only then you call `server_uri`:
    ///
    /// ```no_run
    /// # use tbot::{errors::MethodCall as C, bot::Builder as B};
    /// # use hyper::http::uri::InvalidUri as I;
    /// # struct Error;
    /// # impl From<I> for Error { fn from(_: I) -> Self { Self } }
    /// # impl From<(C, B)> for Error { fn from(_: (C, B)) -> Self { Self } }
    /// # async fn foo() -> Result<(), Error> {
    /// use tbot::bot;
    ///
    /// let bot = bot::Builder::with_env_token("BOT_TOKEN")
    ///     .log_out().await? // log out from cloud Bot API first
    ///     .server_uri("http://localhost:8081".parse()?)
    ///     .build();
    /// # Ok(())
    /// # }
    /// ```
    ///
    /// Now `tbot` will make requests to your Bot API server. You only need
    /// to call [`log_out`] once (unless you use the cloud Bot API server
    /// again), so after you did it once, you can remove that line:
    ///
    /// ```
    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
    /// use tbot::bot;
    ///
    /// let bot = bot::Builder::with_env_token("BOT_TOKEN")
    ///     .server_uri("http://localhost:8081".parse()?)
    ///     .build();
    /// # Ok(())
    /// # }
    /// ```
    ///
    /// If you're moving from one local server to another, you're going to call
    /// this method twice:
    ///
    /// ```no_run
    /// # use tbot::{errors::MethodCall as C, bot::Builder as B};
    /// # use hyper::http::uri::InvalidUri as I;
    /// # struct Error;
    /// # impl From<I> for Error { fn from(_: I) -> Self { Self } }
    /// # impl From<(C, B)> for Error { fn from(_: (C, B)) -> Self { Self } }
    /// # async fn foo() -> Result<(), Error> {
    /// use tbot::bot;
    ///
    /// let bot = bot::Builder::with_env_token("BOT_TOKEN")
    ///     .server_uri("http://other-server:8081".parse()?)
    ///     .close().await? // close the bot on the old server first
    ///     .server_uri("http://localhost:8081".parse()?)
    ///     .build();
    /// # Ok(())
    /// # }
    /// ```
    ///
    /// Just like with logging out from the cloud Bot API server, you only have
    /// to do it once, and after that you can leave only the last `server_uri`
    /// call. If you use webhooks, do not forget to call [`delete_webhook`]
    /// before calling [`close`] to ensure that your bot isn't launched
    /// on the old server when it restarts.
    ///
    /// [`log_out`]: Self::log_out
    /// [`close`]: Self::close
    /// [`delete_webhook`]: Self::delete_webhook
    pub fn server_uri(mut self, uri: hyper::Uri) -> Self {
        self.0.set_uri(uri);
        self
    }

    /// Logs out from the _cloud_ Bot API server.
    ///
    /// Note that after calling this method you must change the URI where `tbot`
    /// makes requests to your local Bot API server using [`server_uri`]. Once
    /// you log out, you cannot log back in the cloud server for 10 minutes.
    ///
    /// [`server_uri`]: Self::server_uri
    ///
    /// In case of an error, a tuple of `(`[`errors::MethodCall`]`, Self)` is
    /// returned in case you expect an error and can recover from it.
    pub async fn log_out(self) -> Result<Self, (errors::MethodCall, Self)> {
        match LogOut::new(&self.0).call().await {
            Ok(()) => Ok(self),
            Err(error) => Err((error, self)),
        }
    }

    /// Logs out from a _self-hosted_ Bot API server.
    ///
    /// Note that after calling this method you must change the URI where `tbot`
    /// makes requests to your local Bot API server using [`server_uri`]. Once
    /// you log out, you cannot log back in this server for 10 minutes. You may
    /// also need to call [`delete_webhook`] before calling this method
    /// to ensure that the bot isn't launched on the old server if it restarts.
    ///
    /// [`server_uri`]: Self::server_uri
    /// [`delete_webhook`]: Self::delete_webhook
    ///
    /// In case of an error, a tuple of `(`[`errors::MethodCall`]`, Self)` is
    /// returned in case you expect an error and can recover from it.
    pub async fn close(self) -> Result<Self, (errors::MethodCall, Self)> {
        match Close::new(&self.0).call().await {
            Ok(()) => Ok(self),
            Err(error) => Err((error, self)),
        }
    }

    /// Deletes the bot's webhook.
    ///
    /// Before you [`close`] your bot on a self-hosted server, you should
    /// delete the bot's webhook to ensure that, when that server is restarted,
    /// your bot isn't laucnhed there.
    ///
    /// [`close`]: Self::close
    ///
    /// You might want to call this method if you're switching from webhooks
    /// to polling. Though this method can be used this way, this isn't
    /// the intended usecase: starting a polling event loop already does it
    /// automatically before the first call to `getUpdates`.
    ///
    /// In case of an error, a tuple of `(`[`errors::MethodCall`]`, Self)` is
    /// returned in case you expect an error and can recover from it.
    pub async fn delete_webhook(
        self,
    ) -> Result<Self, (errors::MethodCall, Self)> {
        match DeleteWebhook::new(&self.0).call().await {
            Ok(()) => Ok(self),
            Err(error) => Err((error, self)),
        }
    }

    /// Finishes constructing the [`Bot`].
    pub fn build(self) -> Bot {
        Bot {
            inner: Arc::new(self.0),
        }
    }
}