1use reqwest::{
2 multipart::{Form, Part},
3 Client, Response,
4};
5pub use telbot_types as types;
6use types::{ApiResponse, FileMethod, JsonMethod, TelegramError, TelegramMethod};
7
8#[derive(Clone)]
9pub struct Api {
10 base_url: String,
11 client: Client,
12}
13
14#[derive(Debug)]
15pub enum Error {
16 TelegramError(TelegramError),
17 Reqwest(reqwest::Error),
18}
19
20impl From<reqwest::Error> for Error {
21 fn from(e: reqwest::Error) -> Self {
22 Self::Reqwest(e)
23 }
24}
25
26pub type Result<T> = std::result::Result<T, Error>;
27
28impl Api {
29 pub fn new(token: impl AsRef<str>) -> Self {
30 Self {
31 base_url: format!("https://api.telegram.org/bot{}/", token.as_ref()),
32 client: Client::new(),
33 }
34 }
35
36 pub async fn send_json<Method: JsonMethod>(&self, method: &Method) -> Result<Method::Response> {
37 let url = format!("{}{}", self.base_url, Method::name());
38 let response = self.client.post(url).json(method).send().await?;
39 Self::parse_response::<Method>(response).await
40 }
41
42 pub async fn send_file<Method: FileMethod>(&self, method: &Method) -> Result<Method::Response> {
43 let url = format!("{}{}", self.base_url, Method::name());
44 let files = method.files();
45 let serialized = serde_json::to_value(method).unwrap();
46
47 let mut form = Form::new();
48 for (key, value) in serialized.as_object().unwrap() {
49 if let Some(file) = files.as_ref().and_then(|map| map.get(key.as_str())) {
50 form = form.part(
51 key.to_string(),
52 Part::bytes(file.data.clone())
53 .file_name(file.name.clone())
54 .mime_str(&file.mime)
55 .unwrap(),
56 );
57 } else if let Some(value) = value.as_str() {
58 form = form.text(key.to_string(), value.to_string());
59 } else {
60 form = form.text(key.to_string(), value.to_string());
61 }
62 }
63
64 let response = self.client.post(url).multipart(form).send().await?;
65
66 Self::parse_response::<Method>(response).await
67 }
68
69 async fn parse_response<Method: TelegramMethod>(
70 response: Response,
71 ) -> Result<Method::Response> {
72 let tg_response: ApiResponse<_> = response.json().await?;
73 match tg_response {
74 ApiResponse::Ok { result } => Ok(result),
75 ApiResponse::Err(error) => Err(Error::TelegramError(error)),
76 }
77 }
78}