1#![allow(clippy::too_many_arguments)]
2
3use crate::error::Result;
4use crate::request::base::{BaseRequest, TimeoutOverride};
5use crate::request::request_data::RequestData;
6use crate::request::request_parameter::{InputFileRef, RequestParameter};
7use crate::types::files;
8use crate::types::link_preview_options;
9use crate::types::message;
10use crate::types::update;
11use crate::types::user;
12use crate::types::webhook_info;
13use serde::{Deserialize, Serialize};
14use std::collections::HashMap;
15use std::sync::Arc;
16use std::time::Duration;
17use tokio::sync::OnceCell;
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
25#[serde(untagged)]
26#[non_exhaustive]
27pub enum ChatId {
28 Id(i64),
30 Username(String),
32}
33
34impl std::fmt::Display for ChatId {
35 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36 match self {
37 ChatId::Id(id) => write!(f, "{id}"),
38 ChatId::Username(u) => write!(f, "{u}"),
39 }
40 }
41}
42
43impl From<i64> for ChatId {
44 fn from(id: i64) -> Self {
45 ChatId::Id(id)
46 }
47}
48
49impl From<String> for ChatId {
50 fn from(username: String) -> Self {
51 ChatId::Username(username)
52 }
53}
54
55impl From<&str> for ChatId {
56 fn from(username: &str) -> Self {
57 ChatId::Username(username.to_owned())
58 }
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
66#[serde(untagged)]
67#[non_exhaustive]
68pub enum MessageOrBool {
69 Message(Box<message::Message>),
71 Bool(bool),
73}
74
75#[derive(Debug, Clone, Default)]
82#[non_exhaustive]
83pub struct Defaults {
84 pub parse_mode: Option<String>,
86 pub disable_notification: Option<bool>,
88 pub protect_content: Option<bool>,
90 pub allow_sending_without_reply: Option<bool>,
92 pub link_preview_options: Option<link_preview_options::LinkPreviewOptions>,
94 pub quote: Option<bool>,
96}
97
98pub struct Bot {
116 token: Arc<str>,
117 base_url: Arc<str>,
118 base_file_url: Arc<str>,
119 request: Arc<dyn BaseRequest>,
120 defaults: Option<Defaults>,
122 cached_bot_data: Arc<OnceCell<user::User>>,
124 local_mode: bool,
128}
129
130impl std::fmt::Debug for Bot {
131 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132 f.debug_struct("Bot")
133 .field("token", &"[REDACTED]")
134 .field("base_url", &self.base_url)
135 .field("base_file_url", &self.base_file_url)
136 .field("defaults", &self.defaults)
137 .field("local_mode", &self.local_mode)
138 .finish()
139 }
140}
141
142fn input_file_param(name: &'static str, file: files::input_file::InputFile) -> RequestParameter {
148 match file {
149 files::input_file::InputFile::FileId(id) => {
150 RequestParameter::new(name, serde_json::Value::String(id))
151 }
152 files::input_file::InputFile::Url(url) => {
153 RequestParameter::new(name, serde_json::Value::String(url))
154 }
155 files::input_file::InputFile::Bytes { filename, data } => {
156 let file_ref = InputFileRef {
157 attach_name: None,
158 bytes: data,
159 mime_type: None,
160 file_name: Some(filename),
161 };
162 RequestParameter::file_only(name, file_ref)
163 }
164 files::input_file::InputFile::Path(path) => {
165 let filename = path
166 .file_name()
167 .unwrap_or_default()
168 .to_string_lossy()
169 .to_string();
170 let path_str = path.to_string_lossy().to_string();
171 let file_ref = InputFileRef {
172 attach_name: None,
173 bytes: Vec::new(),
174 mime_type: None,
175 file_name: Some(filename),
176 };
177 RequestParameter {
178 name: std::borrow::Cow::Borrowed(name),
179 value: Some(serde_json::Value::String(format!(
180 "__filepath__:{path_str}"
181 ))),
182 input_files: Some(vec![file_ref]),
183 }
184 }
185 }
186}
187fn push_opt<T: Serialize>(
189 params: &mut Vec<RequestParameter>,
190 name: &'static str,
191 val: &Option<T>,
192) -> std::result::Result<(), serde_json::Error> {
193 if let Some(v) = val {
194 params.push(RequestParameter::new(name, serde_json::to_value(v)?));
195 }
196 Ok(())
197}
198
199fn push_opt_str(params: &mut Vec<RequestParameter>, name: &'static str, val: Option<&str>) {
201 if let Some(v) = val {
202 params.push(RequestParameter::new(
203 name,
204 serde_json::Value::String(v.to_owned()),
205 ));
206 }
207}
208
209fn push_opt_file(
211 params: &mut Vec<RequestParameter>,
212 name: &'static str,
213 val: Option<files::input_file::InputFile>,
214) {
215 if let Some(f) = val {
216 params.push(input_file_param(name, f));
217 }
218}
219
220#[allow(dead_code)]
225impl Bot {
226 pub fn new(token: impl Into<String>, request: Arc<dyn BaseRequest>) -> Self {
231 let token = token.into();
232 let base_url: Arc<str> = format!("https://api.telegram.org/bot{token}").into();
233 let base_file_url: Arc<str> = format!("https://api.telegram.org/file/bot{token}").into();
234 let token: Arc<str> = token.into();
235 Self {
236 token,
237 base_url,
238 base_file_url,
239 request,
240 defaults: None,
241 cached_bot_data: Arc::new(OnceCell::new()),
242 local_mode: false,
243 }
244 }
245
246 pub fn with_options(
250 token: impl Into<String>,
251 request: Arc<dyn BaseRequest>,
252 defaults: Option<Defaults>,
253 ) -> Self {
254 let token = token.into();
255 let base_url: Arc<str> = format!("https://api.telegram.org/bot{token}").into();
256 let base_file_url: Arc<str> = format!("https://api.telegram.org/file/bot{token}").into();
257 let token: Arc<str> = token.into();
258 Self {
259 token,
260 base_url,
261 base_file_url,
262 request,
263 defaults,
264 cached_bot_data: Arc::new(OnceCell::new()),
265 local_mode: false,
266 }
267 }
268
269 pub fn token(&self) -> &str {
271 &self.token
272 }
273 pub fn base_url(&self) -> &str {
275 &self.base_url
276 }
277 pub fn base_file_url(&self) -> &str {
279 &self.base_file_url
280 }
281 pub fn defaults(&self) -> Option<&Defaults> {
283 self.defaults.as_ref()
284 }
285 pub fn bot_data(&self) -> Option<&user::User> {
287 self.cached_bot_data.get()
288 }
289
290 pub fn local_mode(&self) -> bool {
292 self.local_mode
293 }
294
295 #[must_use]
300 pub fn with_local_mode(mut self) -> Self {
301 self.local_mode = true;
302 self
303 }
304
305 fn api_url(&self, method: &str) -> String {
306 format!("{}/{method}", self.base_url)
307 }
308
309 async fn resolve_file_paths(&self, params: &mut [RequestParameter]) -> Result<()> {
310 for param in params.iter_mut() {
311 let path_str = param
312 .value
313 .as_ref()
314 .and_then(|v| v.as_str())
315 .and_then(|s| s.strip_prefix("__filepath__:"))
316 .map(str::to_owned);
317 if let Some(path_str) = path_str {
318 if self.local_mode {
319 param.value = Some(serde_json::Value::String(format!("file://{path_str}")));
321 param.input_files = None;
322 } else {
323 let data = tokio::fs::read(&path_str).await?;
324 param.value = None;
325 if let Some(ref mut files) = param.input_files {
326 for f in files.iter_mut() {
327 if f.bytes.is_empty() {
328 f.bytes = data.clone();
329 }
330 }
331 }
332 }
333 }
334 }
335 Ok(())
336 }
337
338 fn apply_defaults(&self, params: &mut Vec<RequestParameter>) {
339 let defaults = match &self.defaults {
340 Some(d) => d,
341 None => return,
342 };
343 let existing: std::collections::HashSet<String> =
344 params.iter().map(|p| p.name.as_ref().to_owned()).collect();
345 if let Some(ref pm) = defaults.parse_mode {
346 if !existing.contains("parse_mode") {
347 params.push(RequestParameter::new(
348 "parse_mode",
349 serde_json::Value::String(pm.clone()),
350 ));
351 }
352 }
353 if let Some(dn) = defaults.disable_notification {
354 if !existing.contains("disable_notification") {
355 params.push(RequestParameter::new(
356 "disable_notification",
357 serde_json::Value::Bool(dn),
358 ));
359 }
360 }
361 if let Some(pc) = defaults.protect_content {
362 if !existing.contains("protect_content") {
363 params.push(RequestParameter::new(
364 "protect_content",
365 serde_json::Value::Bool(pc),
366 ));
367 }
368 }
369 if let Some(aswr) = defaults.allow_sending_without_reply {
370 if !existing.contains("allow_sending_without_reply") {
371 params.push(RequestParameter::new(
372 "allow_sending_without_reply",
373 serde_json::Value::Bool(aswr),
374 ));
375 }
376 }
377 if let Some(ref lpo) = defaults.link_preview_options {
378 if !existing.contains("link_preview_options") {
379 if let Ok(v) = serde_json::to_value(lpo) {
380 params.push(RequestParameter::new("link_preview_options", v));
381 }
382 }
383 }
384 }
385
386 async fn do_post<T: serde::de::DeserializeOwned>(
387 &self,
388 method: &str,
389 params: Vec<RequestParameter>,
390 ) -> Result<T> {
391 self.do_post_inner(method, params, TimeoutOverride::default_none(), None)
392 .await
393 }
394
395 #[allow(dead_code)]
396 async fn do_post_with_timeouts<T: serde::de::DeserializeOwned>(
397 &self,
398 method: &str,
399 params: Vec<RequestParameter>,
400 timeouts: TimeoutOverride,
401 ) -> Result<T> {
402 self.do_post_inner(method, params, timeouts, None).await
403 }
404
405 fn do_post_inner<'a, T: serde::de::DeserializeOwned + 'a>(
408 &'a self,
409 method: &'a str,
410 mut params: Vec<RequestParameter>,
411 timeouts: TimeoutOverride,
412 api_kwargs: Option<HashMap<String, serde_json::Value>>,
413 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<T>> + Send + 'a>> {
414 Box::pin(async move {
415 self.apply_defaults(&mut params);
416 if let Some(kwargs) = api_kwargs {
417 let existing: std::collections::HashSet<String> =
418 params.iter().map(|p| p.name.as_ref().to_owned()).collect();
419 for (key, value) in kwargs {
420 if !existing.contains(key.as_str()) {
421 params.push(RequestParameter::new(key, value));
422 }
423 }
424 }
425 self.resolve_file_paths(&mut params).await?;
426 let url = self.api_url(method);
427 let data = RequestData::from_parameters(params);
428 let result = self.request.post(&url, Some(&data), timeouts).await?;
429 serde_json::from_value(result).map_err(Into::into)
430 })
431 }
432
433 pub(crate) async fn do_post_json<T: serde::de::DeserializeOwned>(
439 &self,
440 method: &str,
441 payload: &[u8],
442 ) -> Result<T> {
443 let url = self.api_url(method);
444 let result = self
445 .request
446 .post_json(&url, payload, TimeoutOverride::default_none())
447 .await?;
448 serde_json::from_value(result).map_err(Into::into)
449 }
450
451 pub async fn download_file_raw(&self, file_path: &str) -> Result<Vec<u8>> {
453 let url = format!("{}/{file_path}", self.base_file_url);
454 let bytes = self
455 .request
456 .retrieve(&url, TimeoutOverride::default_none())
457 .await?;
458 Ok(bytes.to_vec())
459 }
460
461 pub async fn initialize(&mut self) -> Result<()> {
463 self.request.initialize().await?;
464 let me = self.get_me_raw().await?;
465 let _ = self.cached_bot_data.set(me);
466 Ok(())
467 }
468
469 pub async fn shutdown(&self) -> Result<()> {
471 self.request.shutdown().await?;
472 Ok(())
473 }
474
475 pub async fn do_api_request<T: serde::de::DeserializeOwned>(
477 &self,
478 method: &str,
479 params: Vec<RequestParameter>,
480 ) -> Result<T> {
481 self.do_post(method, params).await
482 }
483
484 pub async fn do_api_request_with_kwargs<T: serde::de::DeserializeOwned>(
486 &self,
487 method: &str,
488 params: Vec<RequestParameter>,
489 api_kwargs: Option<HashMap<String, serde_json::Value>>,
490 ) -> Result<T> {
491 self.do_post_inner(method, params, TimeoutOverride::default_none(), api_kwargs)
492 .await
493 }
494
495 pub async fn get_updates_raw(
501 &self,
502 offset: Option<i64>,
503 limit: Option<i32>,
504 timeout: Option<i32>,
505 allowed_updates: Option<Vec<String>>,
506 ) -> Result<Vec<update::Update>> {
507 let mut params = Vec::new();
508 push_opt(&mut params, "offset", &offset)?;
509 push_opt(&mut params, "limit", &limit)?;
510 push_opt(&mut params, "timeout", &timeout)?;
511 push_opt(&mut params, "allowed_updates", &allowed_updates)?;
512 self.apply_defaults(&mut params);
513 let timeouts = if let Some(t) = timeout {
514 let effective = Duration::from_secs(t as u64 + 2);
515 TimeoutOverride {
516 read: Some(Some(effective)),
517 ..TimeoutOverride::default_none()
518 }
519 } else {
520 TimeoutOverride::default_none()
521 };
522 let url = self.api_url("getUpdates");
523 let data = RequestData::from_parameters(params);
524 let result = self.request.post(&url, Some(&data), timeouts).await?;
525 serde_json::from_value(result).map_err(Into::into)
526 }
527
528 pub async fn set_webhook_raw(
532 &self,
533 url: &str,
534 certificate: Option<files::input_file::InputFile>,
535 ip_address: Option<&str>,
536 max_connections: Option<i32>,
537 allowed_updates: Option<Vec<String>>,
538 drop_pending_updates: Option<bool>,
539 secret_token: Option<&str>,
540 ) -> Result<bool> {
541 let mut params = vec![RequestParameter::new(
542 "url",
543 serde_json::Value::String(url.to_owned()),
544 )];
545 push_opt_file(&mut params, "certificate", certificate);
546 push_opt_str(&mut params, "ip_address", ip_address);
547 push_opt(&mut params, "max_connections", &max_connections)?;
548 push_opt(&mut params, "allowed_updates", &allowed_updates)?;
549 push_opt(&mut params, "drop_pending_updates", &drop_pending_updates)?;
550 push_opt_str(&mut params, "secret_token", secret_token);
551 self.do_post("setWebhook", params).await
552 }
553
554 pub async fn delete_webhook_raw(&self, drop_pending_updates: Option<bool>) -> Result<bool> {
558 let mut params = Vec::new();
559 push_opt(&mut params, "drop_pending_updates", &drop_pending_updates)?;
560 self.do_post("deleteWebhook", params).await
561 }
562
563 pub async fn get_webhook_info_raw(&self) -> Result<webhook_info::WebhookInfo> {
567 self.do_post("getWebhookInfo", Vec::new()).await
568 }
569
570 pub async fn get_me_raw(&self) -> Result<user::User> {
576 self.do_post("getMe", Vec::new()).await
577 }
578
579 pub async fn log_out_raw(&self) -> Result<bool> {
583 self.do_post("logOut", Vec::new()).await
584 }
585
586 pub async fn close_raw(&self) -> Result<bool> {
590 self.do_post("close", Vec::new()).await
591 }
592}
593
594mod admin;
599mod business_methods;
600mod chat;
601mod editing;
602mod forum;
603mod games_methods;
604mod gifts_methods;
605mod inline_methods;
606mod keyboard_methods;
607mod managed_bots;
608mod media;
609mod messages;
610mod other_content;
611mod passport;
612mod payments;
613mod reactions;
614mod stickers;
615mod stories;
616mod suggested_posts;
617mod user_profile;
618mod verification;