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),
}
}
}