1use std::sync::Arc;
2use std::time::Duration;
3
4use reqwest::multipart::{Form, Part};
5use serde::de::DeserializeOwned;
6use serde::Serialize;
7use tracing::{debug, warn};
8
9use crate::error::{Error, Result};
10use crate::methods::bot_settings::*;
11use crate::methods::business::*;
12use crate::methods::chat_management::*;
13use crate::methods::editing::*;
14use crate::methods::forum::*;
15use crate::methods::getters::*;
16use crate::methods::inline::*;
17use crate::methods::payments::*;
18use crate::methods::reactions::*;
19use crate::methods::sending::*;
20use crate::methods::stickers::*;
21use crate::methods::updates::*;
22use crate::methods::verification::*;
23
24#[derive(serde::Deserialize)]
29#[serde(bound(deserialize = "T: serde::de::DeserializeOwned"))]
30struct ApiResponse<T> {
31 ok: bool,
32 #[serde(default)]
33 result: Option<T>,
34 description: Option<String>,
35 error_code: Option<u16>,
36 parameters: Option<ResponseParameters>,
37}
38
39#[derive(serde::Deserialize)]
40struct ResponseParameters {
41 migrate_to_chat_id: Option<i64>,
42 retry_after: Option<u32>,
43}
44
45#[derive(Debug, Clone)]
48pub struct ClientConfig {
64 pub token: String,
66 pub api_base_url: String,
68 pub timeout: Duration,
70 pub max_retries: u8,
72}
73
74impl ClientConfig {
75 pub fn new(token: impl Into<String>) -> Result<Self> {
77 let token = token.into();
78 validate_token(&token)?;
79 Ok(Self {
80 token,
81 api_base_url: "https://api.telegram.org".to_owned(),
82 timeout: Duration::from_secs(30),
83 max_retries: 3,
84 })
85 }
86
87 #[must_use]
89 pub fn api_base_url(mut self, url: impl Into<String>) -> Self {
90 self.api_base_url = url.into();
91 self
92 }
93
94 #[must_use]
96 pub fn timeout(mut self, timeout: Duration) -> Self {
97 self.timeout = timeout;
98 self
99 }
100
101 #[must_use]
103 pub fn max_retries(mut self, n: u8) -> Self {
104 self.max_retries = n;
105 self
106 }
107}
108
109struct Inner {
112 http: reqwest::Client,
113 config: ClientConfig,
114}
115
116#[derive(Clone)]
117pub struct BotClient {
150 inner: Arc<Inner>,
151}
152
153impl BotClient {
154 pub fn new(config: ClientConfig) -> Result<Self> {
160 let http = reqwest::Client::builder()
161 .timeout(config.timeout)
162 .build()
163 .map_err(Error::Http)?;
164 Ok(Self {
165 inner: Arc::new(Inner { http, config }),
166 })
167 }
168
169 pub fn from_token(token: impl Into<String>) -> Result<Self> {
177 Self::new(ClientConfig::new(token)?)
178 }
179
180 #[must_use]
182 pub fn token(&self) -> &str {
183 &self.inner.config.token
184 }
185
186 #[must_use]
188 pub fn api_base_url(&self) -> &str {
189 &self.inner.config.api_base_url
190 }
191
192 #[must_use]
193 fn method_url(&self, method: &str) -> String {
194 format!(
195 "{}/bot{}/{}",
196 self.inner.config.api_base_url, self.inner.config.token, method
197 )
198 }
199
200 pub async fn post_json<P, R>(&self, method: &str, params: &P) -> Result<R>
210 where
211 P: Serialize + ?Sized,
212 R: DeserializeOwned,
213 {
214 let url = self.method_url(method);
215 let body = serde_json::to_vec(params).map_err(Error::Serialization)?;
216 let max_retries = self.inner.config.max_retries;
217
218 for attempt in 0..=max_retries {
219 debug!("POST {} (attempt {})", method, attempt + 1);
220
221 let resp = self
222 .inner
223 .http
224 .post(&url)
225 .header("Content-Type", "application/json")
226 .body(body.clone())
227 .send()
228 .await
229 .map_err(Error::Http)?;
230
231 let api_resp: ApiResponse<R> = resp
232 .json()
233 .await
234 .map_err(|e| Error::Decode(e.to_string()))?;
235
236 if api_resp.ok {
237 return api_resp
238 .result
239 .ok_or_else(|| Error::Decode("ok=true but result is null".to_owned()));
240 }
241
242 let error_code = api_resp.error_code.unwrap_or(0);
243 let description = api_resp
244 .description
245 .unwrap_or_else(|| "Unknown error".to_owned());
246 let retry_after = api_resp.parameters.as_ref().and_then(|p| p.retry_after);
247 let migrate_to_chat_id = api_resp
248 .parameters
249 .as_ref()
250 .and_then(|p| p.migrate_to_chat_id);
251
252 if error_code == 429 {
253 let wait = retry_after.unwrap_or(1);
254 if attempt < max_retries {
255 warn!(
256 "Flood control on {}: waiting {}s (attempt {}/{})",
257 method,
258 wait,
259 attempt + 1,
260 max_retries
261 );
262 tokio::time::sleep(Duration::from_secs(u64::from(wait))).await;
263 continue;
264 }
265 return Err(Error::RateLimit { retry_after: wait });
266 }
267
268 return Err(Error::Api {
269 error_code,
270 description,
271 migrate_to_chat_id,
272 retry_after,
273 });
274 }
275
276 unreachable!()
277 }
278
279 pub async fn post_multipart<R>(&self, method: &str, form: Form) -> Result<R>
281 where
282 R: DeserializeOwned,
283 {
284 let url = self.method_url(method);
285 debug!("POST multipart {}", method);
286
287 let resp = self
288 .inner
289 .http
290 .post(&url)
291 .multipart(form)
292 .send()
293 .await
294 .map_err(Error::Http)?;
295
296 let api_resp: ApiResponse<R> = resp
297 .json()
298 .await
299 .map_err(|e| Error::Decode(e.to_string()))?;
300
301 if api_resp.ok {
302 return api_resp
303 .result
304 .ok_or_else(|| Error::Decode("ok=true but result is null".to_owned()));
305 }
306
307 let error_code = api_resp.error_code.unwrap_or(0);
308 let description = api_resp
309 .description
310 .unwrap_or_else(|| "Unknown error".to_owned());
311 let retry_after = api_resp.parameters.as_ref().and_then(|p| p.retry_after);
312 let migrate_to_chat_id = api_resp
313 .parameters
314 .as_ref()
315 .and_then(|p| p.migrate_to_chat_id);
316
317 if error_code == 429 {
318 return Err(Error::RateLimit {
319 retry_after: retry_after.unwrap_or(1),
320 });
321 }
322
323 Err(Error::Api {
324 error_code,
325 description,
326 migrate_to_chat_id,
327 retry_after,
328 })
329 }
330
331 pub async fn download_file(&self, file_path: &str) -> Result<bytes::Bytes> {
344 let url = format!(
345 "{}/file/bot{}/{}",
346 self.inner.config.api_base_url, self.inner.config.token, file_path
347 );
348 self.inner
349 .http
350 .get(&url)
351 .send()
352 .await
353 .map_err(Error::Http)?
354 .bytes()
355 .await
356 .map_err(Error::Http)
357 }
358
359 pub fn get_updates(&self) -> GetUpdates {
363 GetUpdates::new(self.clone())
364 }
365 pub fn set_webhook(&self, url: impl Into<String>) -> SetWebhook {
367 SetWebhook::new(self.clone(), url)
368 }
369 pub fn delete_webhook(&self) -> DeleteWebhook {
371 DeleteWebhook::new(self.clone())
372 }
373 pub fn get_webhook_info(&self) -> GetWebhookInfo {
375 GetWebhookInfo::new(self.clone())
376 }
377
378 pub fn get_me(&self) -> GetMe {
382 GetMe::new(self.clone())
383 }
384 pub fn get_chat(&self, chat_id: impl Into<rustigram_types::user::ChatId>) -> GetChat {
386 GetChat::new(self.clone(), chat_id)
387 }
388 pub fn get_chat_administrators(
390 &self,
391 chat_id: impl Into<rustigram_types::user::ChatId>,
392 ) -> GetChatAdministrators {
393 GetChatAdministrators::new(self.clone(), chat_id)
394 }
395 pub fn get_chat_member_count(
397 &self,
398 chat_id: impl Into<rustigram_types::user::ChatId>,
399 ) -> GetChatMemberCount {
400 GetChatMemberCount::new(self.clone(), chat_id)
401 }
402 pub fn get_chat_member(
404 &self,
405 chat_id: impl Into<rustigram_types::user::ChatId>,
406 user_id: i64,
407 ) -> GetChatMember {
408 GetChatMember::new(self.clone(), chat_id, user_id)
409 }
410 pub fn get_file(&self, file_id: impl Into<String>) -> GetFile {
412 GetFile::new(self.clone(), file_id)
413 }
414 pub fn get_user_profile_photos(&self, user_id: i64) -> GetUserProfilePhotos {
416 GetUserProfilePhotos::new(self.clone(), user_id)
417 }
418
419 pub fn send_message(
423 &self,
424 chat_id: impl Into<rustigram_types::user::ChatId>,
425 text: impl Into<String>,
426 ) -> SendMessage {
427 SendMessage::new(self.clone(), chat_id, text)
428 }
429 pub fn forward_message(
431 &self,
432 chat_id: impl Into<rustigram_types::user::ChatId>,
433 from_chat_id: impl Into<rustigram_types::user::ChatId>,
434 message_id: i64,
435 ) -> ForwardMessage {
436 ForwardMessage::new(self.clone(), chat_id, from_chat_id, message_id)
437 }
438 pub fn copy_message(
440 &self,
441 chat_id: impl Into<rustigram_types::user::ChatId>,
442 from_chat_id: impl Into<rustigram_types::user::ChatId>,
443 message_id: i64,
444 ) -> CopyMessage {
445 CopyMessage::new(self.clone(), chat_id, from_chat_id, message_id)
446 }
447 pub fn send_chat_action(
449 &self,
450 chat_id: impl Into<rustigram_types::user::ChatId>,
451 action: ChatAction,
452 ) -> SendChatAction {
453 SendChatAction::new(self.clone(), chat_id, action)
454 }
455 pub fn send_photo(
457 &self,
458 chat_id: impl Into<rustigram_types::user::ChatId>,
459 photo: rustigram_types::file::InputFile,
460 ) -> SendPhoto {
461 SendPhoto::new(self.clone(), chat_id, photo)
462 }
463 pub fn send_audio(
465 &self,
466 chat_id: impl Into<rustigram_types::user::ChatId>,
467 audio: rustigram_types::file::InputFile,
468 ) -> SendAudio {
469 SendAudio::new(self.clone(), chat_id, audio)
470 }
471 pub fn send_document(
473 &self,
474 chat_id: impl Into<rustigram_types::user::ChatId>,
475 document: rustigram_types::file::InputFile,
476 ) -> SendDocument {
477 SendDocument::new(self.clone(), chat_id, document)
478 }
479 pub fn send_video(
481 &self,
482 chat_id: impl Into<rustigram_types::user::ChatId>,
483 video: rustigram_types::file::InputFile,
484 ) -> SendVideo {
485 SendVideo::new(self.clone(), chat_id, video)
486 }
487 pub fn send_animation(
489 &self,
490 chat_id: impl Into<rustigram_types::user::ChatId>,
491 animation: rustigram_types::file::InputFile,
492 ) -> SendAnimation {
493 SendAnimation::new(self.clone(), chat_id, animation)
494 }
495 pub fn send_voice(
497 &self,
498 chat_id: impl Into<rustigram_types::user::ChatId>,
499 voice: rustigram_types::file::InputFile,
500 ) -> SendVoice {
501 SendVoice::new(self.clone(), chat_id, voice)
502 }
503 pub fn send_video_note(
505 &self,
506 chat_id: impl Into<rustigram_types::user::ChatId>,
507 video_note: rustigram_types::file::InputFile,
508 ) -> SendVideoNote {
509 SendVideoNote::new(self.clone(), chat_id, video_note)
510 }
511 pub fn send_sticker(
513 &self,
514 chat_id: impl Into<rustigram_types::user::ChatId>,
515 sticker: rustigram_types::file::InputFile,
516 ) -> SendSticker {
517 SendSticker::new(self.clone(), chat_id, sticker)
518 }
519 pub fn send_location(
521 &self,
522 chat_id: impl Into<rustigram_types::user::ChatId>,
523 latitude: f64,
524 longitude: f64,
525 ) -> SendLocation {
526 SendLocation::new(self.clone(), chat_id, latitude, longitude)
527 }
528 pub fn send_contact(
530 &self,
531 chat_id: impl Into<rustigram_types::user::ChatId>,
532 phone_number: impl Into<String>,
533 first_name: impl Into<String>,
534 ) -> SendContact {
535 SendContact::new(self.clone(), chat_id, phone_number, first_name)
536 }
537 pub fn send_poll(
539 &self,
540 chat_id: impl Into<rustigram_types::user::ChatId>,
541 question: impl Into<String>,
542 options: Vec<rustigram_types::poll::InputPollOption>,
543 ) -> SendPoll {
544 SendPoll::new(self.clone(), chat_id, question, options)
545 }
546 pub fn send_dice(&self, chat_id: impl Into<rustigram_types::user::ChatId>) -> SendDice {
548 SendDice::new(self.clone(), chat_id)
549 }
550 pub fn send_message_draft(
552 &self,
553 chat_id: impl Into<rustigram_types::user::ChatId>,
554 draft_id: i64,
555 text: impl Into<String>,
556 ) -> SendMessageDraft {
557 SendMessageDraft::new(self.clone(), chat_id, draft_id, text)
558 }
559 pub fn delete_message(
561 &self,
562 chat_id: impl Into<rustigram_types::user::ChatId>,
563 message_id: i64,
564 ) -> DeleteMessage {
565 DeleteMessage::new(self.clone(), chat_id, message_id)
566 }
567 pub fn delete_messages(
569 &self,
570 chat_id: impl Into<rustigram_types::user::ChatId>,
571 message_ids: Vec<i64>,
572 ) -> DeleteMessages {
573 DeleteMessages::new(self.clone(), chat_id, message_ids)
574 }
575 pub fn stop_poll(
577 &self,
578 chat_id: impl Into<rustigram_types::user::ChatId>,
579 message_id: i64,
580 ) -> StopPoll {
581 StopPoll::new(self.clone(), chat_id, message_id)
582 }
583 pub fn answer_callback_query(
585 &self,
586 callback_query_id: impl Into<String>,
587 ) -> AnswerCallbackQuery {
588 AnswerCallbackQuery::new(self.clone(), callback_query_id)
589 }
590
591 pub fn edit_message_text(
595 &self,
596 chat_id: impl Into<rustigram_types::user::ChatId>,
597 message_id: i64,
598 text: impl Into<String>,
599 ) -> EditMessageText {
600 EditMessageText::in_chat(self.clone(), chat_id, message_id, text)
601 }
602 pub fn edit_inline_message_text(
604 &self,
605 inline_message_id: impl Into<String>,
606 text: impl Into<String>,
607 ) -> EditMessageText {
608 EditMessageText::inline(self.clone(), inline_message_id, text)
609 }
610 pub fn edit_message_caption(
612 &self,
613 chat_id: impl Into<rustigram_types::user::ChatId>,
614 message_id: i64,
615 ) -> EditMessageCaption {
616 EditMessageCaption::in_chat(self.clone(), chat_id, message_id)
617 }
618 pub fn edit_message_reply_markup(
620 &self,
621 chat_id: impl Into<rustigram_types::user::ChatId>,
622 message_id: i64,
623 ) -> EditMessageReplyMarkup {
624 EditMessageReplyMarkup::in_chat(self.clone(), chat_id, message_id)
625 }
626 pub fn edit_message_live_location(
628 &self,
629 chat_id: impl Into<rustigram_types::user::ChatId>,
630 message_id: i64,
631 latitude: f64,
632 longitude: f64,
633 ) -> EditMessageLiveLocation {
634 EditMessageLiveLocation::in_chat(self.clone(), chat_id, message_id, latitude, longitude)
635 }
636 pub fn stop_message_live_location(
638 &self,
639 chat_id: impl Into<rustigram_types::user::ChatId>,
640 message_id: i64,
641 ) -> StopMessageLiveLocation {
642 StopMessageLiveLocation::in_chat(self.clone(), chat_id, message_id)
643 }
644
645 pub fn ban_chat_member(
649 &self,
650 chat_id: impl Into<rustigram_types::user::ChatId>,
651 user_id: i64,
652 ) -> BanChatMember {
653 BanChatMember::new(self.clone(), chat_id, user_id)
654 }
655 pub fn unban_chat_member(
657 &self,
658 chat_id: impl Into<rustigram_types::user::ChatId>,
659 user_id: i64,
660 ) -> UnbanChatMember {
661 UnbanChatMember::new(self.clone(), chat_id, user_id)
662 }
663 pub fn restrict_chat_member(
665 &self,
666 chat_id: impl Into<rustigram_types::user::ChatId>,
667 user_id: i64,
668 permissions: rustigram_types::chat::ChatPermissions,
669 ) -> RestrictChatMember {
670 RestrictChatMember::new(self.clone(), chat_id, user_id, permissions)
671 }
672 pub fn promote_chat_member(
674 &self,
675 chat_id: impl Into<rustigram_types::user::ChatId>,
676 user_id: i64,
677 ) -> PromoteChatMember {
678 PromoteChatMember::new(self.clone(), chat_id, user_id)
679 }
680 pub fn create_chat_invite_link(
682 &self,
683 chat_id: impl Into<rustigram_types::user::ChatId>,
684 ) -> CreateChatInviteLink {
685 CreateChatInviteLink::new(self.clone(), chat_id)
686 }
687 pub fn pin_chat_message(
689 &self,
690 chat_id: impl Into<rustigram_types::user::ChatId>,
691 message_id: i64,
692 ) -> PinChatMessage {
693 PinChatMessage::new(self.clone(), chat_id, message_id)
694 }
695 pub fn unpin_chat_message(
697 &self,
698 chat_id: impl Into<rustigram_types::user::ChatId>,
699 ) -> UnpinChatMessage {
700 UnpinChatMessage::new(self.clone(), chat_id)
701 }
702
703 pub fn set_my_commands(
707 &self,
708 commands: Vec<rustigram_types::user::BotCommand>,
709 ) -> SetMyCommands {
710 SetMyCommands::new(self.clone(), commands)
711 }
712 pub fn get_my_commands(&self) -> GetMyCommands {
714 GetMyCommands::new(self.clone())
715 }
716 pub fn set_my_name(&self) -> SetMyName {
718 SetMyName::new(self.clone())
719 }
720 pub fn set_my_description(&self) -> SetMyDescription {
722 SetMyDescription::new(self.clone())
723 }
724 pub fn get_chat_menu_button(&self) -> GetChatMenuButton {
726 GetChatMenuButton::new(self.clone())
727 }
728 pub fn get_managed_bot_token(&self, user_id: i64) -> GetManagedBotToken {
730 GetManagedBotToken::new(self.clone(), user_id)
731 }
732
733 pub fn set_message_reaction(
737 &self,
738 chat_id: impl Into<rustigram_types::user::ChatId>,
739 message_id: i64,
740 ) -> SetMessageReaction {
741 SetMessageReaction::new(self.clone(), chat_id, message_id)
742 }
743
744 pub fn answer_inline_query(
748 &self,
749 inline_query_id: impl Into<String>,
750 results: Vec<rustigram_types::inline::InlineQueryResult>,
751 ) -> AnswerInlineQuery {
752 AnswerInlineQuery::new(self.clone(), inline_query_id, results)
753 }
754
755 pub fn send_invoice(
759 &self,
760 chat_id: impl Into<rustigram_types::user::ChatId>,
761 title: impl Into<String>,
762 description: impl Into<String>,
763 payload: impl Into<String>,
764 currency: impl Into<String>,
765 prices: Vec<rustigram_types::payments::LabeledPrice>,
766 ) -> SendInvoice {
767 SendInvoice::new(
768 self.clone(),
769 chat_id,
770 title,
771 description,
772 payload,
773 currency,
774 prices,
775 )
776 }
777 pub fn get_my_star_balance(&self) -> GetMyStarBalance {
779 GetMyStarBalance::new(self.clone())
780 }
781 pub fn get_star_transactions(&self) -> GetStarTransactions {
783 GetStarTransactions::new(self.clone())
784 }
785
786 pub fn get_sticker_set(&self, name: impl Into<String>) -> GetStickerSet {
790 GetStickerSet::new(self.clone(), name)
791 }
792 pub fn get_custom_emoji_stickers(&self, ids: Vec<impl Into<String>>) -> GetCustomEmojiStickers {
794 GetCustomEmojiStickers::new(self.clone(), ids)
795 }
796 pub fn upload_sticker_file(
798 &self,
799 user_id: i64,
800 sticker: rustigram_types::file::InputFile,
801 format: rustigram_types::sticker::StickerFormat,
802 ) -> UploadStickerFile {
803 UploadStickerFile::new(self.clone(), user_id, sticker, format)
804 }
805 pub fn create_new_sticker_set(
807 &self,
808 user_id: i64,
809 name: impl Into<String>,
810 title: impl Into<String>,
811 stickers: Vec<rustigram_types::sticker::InputSticker>,
812 ) -> CreateNewStickerSet {
813 CreateNewStickerSet::new(self.clone(), user_id, name, title, stickers)
814 }
815 pub fn add_sticker_to_set(
817 &self,
818 user_id: i64,
819 name: impl Into<String>,
820 sticker: rustigram_types::sticker::InputSticker,
821 ) -> AddStickerToSet {
822 AddStickerToSet::new(self.clone(), user_id, name, sticker)
823 }
824 pub fn set_sticker_position_in_set(
826 &self,
827 sticker: impl Into<String>,
828 position: u32,
829 ) -> SetStickerPositionInSet {
830 SetStickerPositionInSet::new(self.clone(), sticker, position)
831 }
832 pub fn delete_sticker_from_set(&self, sticker: impl Into<String>) -> DeleteStickerFromSet {
834 DeleteStickerFromSet::new(self.clone(), sticker)
835 }
836 pub fn set_sticker_emoji_list(
838 &self,
839 sticker: impl Into<String>,
840 emoji_list: Vec<impl Into<String>>,
841 ) -> SetStickerEmojiList {
842 SetStickerEmojiList::new(self.clone(), sticker, emoji_list)
843 }
844 pub fn set_sticker_keywords(&self, sticker: impl Into<String>) -> SetStickerKeywords {
846 SetStickerKeywords::new(self.clone(), sticker)
847 }
848 pub fn set_sticker_mask_position(&self, sticker: impl Into<String>) -> SetStickerMaskPosition {
850 SetStickerMaskPosition::new(self.clone(), sticker)
851 }
852 pub fn set_sticker_set_title(
854 &self,
855 name: impl Into<String>,
856 title: impl Into<String>,
857 ) -> SetStickerSetTitle {
858 SetStickerSetTitle::new(self.clone(), name, title)
859 }
860 pub fn delete_sticker_set(&self, name: impl Into<String>) -> DeleteStickerSet {
862 DeleteStickerSet::new(self.clone(), name)
863 }
864 pub fn get_forum_topic_icon_stickers(&self) -> GetForumTopicIconStickers {
866 GetForumTopicIconStickers::new(self.clone())
867 }
868
869 pub fn create_forum_topic(
873 &self,
874 chat_id: impl Into<rustigram_types::user::ChatId>,
875 name: impl Into<String>,
876 ) -> CreateForumTopic {
877 CreateForumTopic::new(self.clone(), chat_id, name)
878 }
879 pub fn edit_forum_topic(
881 &self,
882 chat_id: impl Into<rustigram_types::user::ChatId>,
883 thread_id: i64,
884 ) -> EditForumTopic {
885 EditForumTopic::new(self.clone(), chat_id, thread_id)
886 }
887 pub fn close_forum_topic(
889 &self,
890 chat_id: impl Into<rustigram_types::user::ChatId>,
891 thread_id: i64,
892 ) -> CloseForumTopic {
893 CloseForumTopic::new(self.clone(), chat_id, thread_id)
894 }
895 pub fn reopen_forum_topic(
897 &self,
898 chat_id: impl Into<rustigram_types::user::ChatId>,
899 thread_id: i64,
900 ) -> ReopenForumTopic {
901 ReopenForumTopic::new(self.clone(), chat_id, thread_id)
902 }
903 pub fn delete_forum_topic(
905 &self,
906 chat_id: impl Into<rustigram_types::user::ChatId>,
907 thread_id: i64,
908 ) -> DeleteForumTopic {
909 DeleteForumTopic::new(self.clone(), chat_id, thread_id)
910 }
911 pub fn edit_general_forum_topic(
913 &self,
914 chat_id: impl Into<rustigram_types::user::ChatId>,
915 name: impl Into<String>,
916 ) -> EditGeneralForumTopic {
917 EditGeneralForumTopic::new(self.clone(), chat_id, name)
918 }
919 pub fn close_general_forum_topic(
921 &self,
922 chat_id: impl Into<rustigram_types::user::ChatId>,
923 ) -> CloseGeneralForumTopic {
924 CloseGeneralForumTopic::new(self.clone(), chat_id)
925 }
926 pub fn reopen_general_forum_topic(
928 &self,
929 chat_id: impl Into<rustigram_types::user::ChatId>,
930 ) -> ReopenGeneralForumTopic {
931 ReopenGeneralForumTopic::new(self.clone(), chat_id)
932 }
933 pub fn hide_general_forum_topic(
935 &self,
936 chat_id: impl Into<rustigram_types::user::ChatId>,
937 ) -> HideGeneralForumTopic {
938 HideGeneralForumTopic::new(self.clone(), chat_id)
939 }
940 pub fn unhide_general_forum_topic(
942 &self,
943 chat_id: impl Into<rustigram_types::user::ChatId>,
944 ) -> UnhideGeneralForumTopic {
945 UnhideGeneralForumTopic::new(self.clone(), chat_id)
946 }
947
948 pub fn verify_user(&self, user_id: i64) -> VerifyUser {
952 VerifyUser::new(self.clone(), user_id)
953 }
954 pub fn verify_chat(&self, chat_id: impl Into<rustigram_types::user::ChatId>) -> VerifyChat {
956 VerifyChat::new(self.clone(), chat_id)
957 }
958 pub fn remove_user_verification(&self, user_id: i64) -> RemoveUserVerification {
960 RemoveUserVerification::new(self.clone(), user_id)
961 }
962 pub fn remove_chat_verification(
964 &self,
965 chat_id: impl Into<rustigram_types::user::ChatId>,
966 ) -> RemoveChatVerification {
967 RemoveChatVerification::new(self.clone(), chat_id)
968 }
969
970 pub fn get_business_connection(&self, id: impl Into<String>) -> GetBusinessConnection {
974 GetBusinessConnection::new(self.clone(), id)
975 }
976 pub fn read_business_message(
978 &self,
979 business_connection_id: impl Into<String>,
980 chat_id: impl Into<rustigram_types::user::ChatId>,
981 message_id: i64,
982 ) -> ReadBusinessMessage {
983 ReadBusinessMessage::new(self.clone(), business_connection_id, chat_id, message_id)
984 }
985 pub fn delete_business_messages(
987 &self,
988 business_connection_id: impl Into<String>,
989 message_ids: Vec<i64>,
990 ) -> DeleteBusinessMessages {
991 DeleteBusinessMessages::new(self.clone(), business_connection_id, message_ids)
992 }
993 pub fn set_business_account_name(
995 &self,
996 business_connection_id: impl Into<String>,
997 first_name: impl Into<String>,
998 last_name: Option<String>,
999 ) -> SetBusinessAccountName {
1000 SetBusinessAccountName::new(
1001 self.clone(),
1002 business_connection_id,
1003 first_name.into(),
1004 last_name,
1005 )
1006 }
1007 pub fn set_business_account_username(
1009 &self,
1010 business_connection_id: impl Into<String>,
1011 username: Option<String>,
1012 ) -> SetBusinessAccountUsername {
1013 SetBusinessAccountUsername::new(self.clone(), business_connection_id, username)
1014 }
1015 pub fn set_business_account_bio(
1017 &self,
1018 business_connection_id: impl Into<String>,
1019 bio: Option<String>,
1020 ) -> SetBusinessAccountBio {
1021 SetBusinessAccountBio::new(self.clone(), business_connection_id, bio)
1022 }
1023 pub fn get_business_account_star_balance(
1025 &self,
1026 business_connection_id: impl Into<String>,
1027 ) -> GetBusinessAccountStarBalance {
1028 GetBusinessAccountStarBalance::new(self.clone(), business_connection_id)
1029 }
1030 pub fn transfer_business_account_stars(
1032 &self,
1033 business_connection_id: impl Into<String>,
1034 star_count: u64,
1035 ) -> TransferBusinessAccountStars {
1036 TransferBusinessAccountStars::new(self.clone(), business_connection_id, star_count)
1037 }
1038}
1039
1040#[allow(dead_code)]
1043pub(crate) fn input_file_to_part(file: rustigram_types::file::InputFile) -> Option<(String, Part)> {
1045 use rustigram_types::file::InputFile;
1046 match file {
1047 InputFile::Bytes {
1048 filename,
1049 data,
1050 mime_type,
1051 } => {
1052 let part = Part::bytes(data)
1053 .file_name(filename.clone())
1054 .mime_str(&mime_type)
1055 .ok()?;
1056 Some((filename, part))
1057 }
1058 _ => None,
1059 }
1060}
1061
1062fn validate_token(token: &str) -> Result<()> {
1063 let colon = token.find(':').ok_or(Error::InvalidToken)?;
1064 let id_part = &token[..colon];
1065 if id_part.is_empty() || !id_part.chars().all(|c| c.is_ascii_digit()) {
1066 return Err(Error::InvalidToken);
1067 }
1068 if token[colon + 1..].is_empty() {
1069 return Err(Error::InvalidToken);
1070 }
1071 Ok(())
1072}