1use std::future::{Future, IntoFuture};
2use std::pin::Pin;
3
4use reqwest::multipart::{Form, Part};
5use serde::Serialize;
6
7use rustigram_types::file::InputFile;
8use rustigram_types::keyboard::ReplyMarkup;
9use rustigram_types::message::{LinkPreviewOptions, Message, ParseMode, ReplyParameters};
10use rustigram_types::poll::InputPollOption;
11use rustigram_types::user::ChatId;
12
13use crate::client::BotClient;
14use crate::error::Result;
15
16macro_rules! impl_into_future {
20 ($builder:ident, $return_ty:ty, $method:literal) => {
21 impl IntoFuture for $builder {
22 type Output = Result<$return_ty>;
23 type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
24
25 fn into_future(self) -> Self::IntoFuture {
26 Box::pin(async move { self.client.post_json($method, &self.params).await })
27 }
28 }
29 };
30}
31
32#[derive(Serialize)]
35struct SendMessageParams {
36 chat_id: ChatId,
37 text: String,
38 #[serde(skip_serializing_if = "Option::is_none")]
39 business_connection_id: Option<String>,
40 #[serde(skip_serializing_if = "Option::is_none")]
41 message_thread_id: Option<i64>,
42 #[serde(skip_serializing_if = "Option::is_none")]
43 parse_mode: Option<ParseMode>,
44 #[serde(skip_serializing_if = "Option::is_none")]
45 entities: Option<Vec<rustigram_types::message::MessageEntity>>,
46 #[serde(skip_serializing_if = "Option::is_none")]
47 link_preview_options: Option<LinkPreviewOptions>,
48 #[serde(skip_serializing_if = "Option::is_none")]
49 disable_notification: Option<bool>,
50 #[serde(skip_serializing_if = "Option::is_none")]
51 protect_content: Option<bool>,
52 #[serde(skip_serializing_if = "Option::is_none")]
53 allow_paid_broadcast: Option<bool>,
54 #[serde(skip_serializing_if = "Option::is_none")]
55 message_effect_id: Option<String>,
56 #[serde(skip_serializing_if = "Option::is_none")]
57 reply_parameters: Option<ReplyParameters>,
58 #[serde(skip_serializing_if = "Option::is_none")]
59 reply_markup: Option<ReplyMarkup>,
60}
61
62pub struct SendMessage {
64 client: BotClient,
65 params: SendMessageParams,
66}
67
68impl SendMessage {
69 pub(crate) fn new(
70 client: BotClient,
71 chat_id: impl Into<ChatId>,
72 text: impl Into<String>,
73 ) -> Self {
74 Self {
75 client,
76 params: SendMessageParams {
77 chat_id: chat_id.into(),
78 text: text.into(),
79 business_connection_id: None,
80 message_thread_id: None,
81 parse_mode: None,
82 entities: None,
83 link_preview_options: None,
84 disable_notification: None,
85 protect_content: None,
86 allow_paid_broadcast: None,
87 message_effect_id: None,
88 reply_parameters: None,
89 reply_markup: None,
90 },
91 }
92 }
93 pub fn business_connection_id(mut self, id: impl Into<String>) -> Self {
95 self.params.business_connection_id = Some(id.into());
96 self
97 }
98 pub fn message_thread_id(mut self, id: i64) -> Self {
100 self.params.message_thread_id = Some(id);
101 self
102 }
103 pub fn parse_mode(mut self, mode: ParseMode) -> Self {
105 self.params.parse_mode = Some(mode);
106 self
107 }
108 pub fn entities(mut self, entities: Vec<rustigram_types::message::MessageEntity>) -> Self {
110 self.params.entities = Some(entities);
111 self
112 }
113 pub fn link_preview_options(mut self, opts: LinkPreviewOptions) -> Self {
115 self.params.link_preview_options = Some(opts);
116 self
117 }
118 pub fn disable_notification(mut self, v: bool) -> Self {
120 self.params.disable_notification = Some(v);
121 self
122 }
123 pub fn protect_content(mut self, v: bool) -> Self {
125 self.params.protect_content = Some(v);
126 self
127 }
128 pub fn allow_paid_broadcast(mut self, v: bool) -> Self {
130 self.params.allow_paid_broadcast = Some(v);
131 self
132 }
133 pub fn message_effect_id(mut self, id: impl Into<String>) -> Self {
135 self.params.message_effect_id = Some(id.into());
136 self
137 }
138 pub fn reply_parameters(mut self, rp: ReplyParameters) -> Self {
140 self.params.reply_parameters = Some(rp);
141 self
142 }
143 pub fn reply_to(mut self, message_id: i64) -> Self {
145 self.params.reply_parameters = Some(ReplyParameters {
146 message_id,
147 chat_id: None,
148 allow_sending_without_reply: None,
149 quote: None,
150 quote_parse_mode: None,
151 quote_entities: None,
152 quote_position: None,
153 poll_option_id: None,
154 checklist_task_id: None,
155 });
156 self
157 }
158 pub fn reply_markup(mut self, markup: impl Into<ReplyMarkup>) -> Self {
160 self.params.reply_markup = Some(markup.into());
161 self
162 }
163}
164
165impl_into_future!(SendMessage, Message, "sendMessage");
166
167#[derive(Serialize)]
170struct ForwardMessageParams {
171 chat_id: ChatId,
172 from_chat_id: ChatId,
173 message_id: i64,
174 #[serde(skip_serializing_if = "Option::is_none")]
175 message_thread_id: Option<i64>,
176 #[serde(skip_serializing_if = "Option::is_none")]
177 video_start_timestamp: Option<i64>,
178 #[serde(skip_serializing_if = "Option::is_none")]
179 disable_notification: Option<bool>,
180 #[serde(skip_serializing_if = "Option::is_none")]
181 protect_content: Option<bool>,
182}
183
184pub struct ForwardMessage {
186 client: BotClient,
187 params: ForwardMessageParams,
188}
189
190impl ForwardMessage {
191 pub(crate) fn new(
192 client: BotClient,
193 chat_id: impl Into<ChatId>,
194 from_chat_id: impl Into<ChatId>,
195 message_id: i64,
196 ) -> Self {
197 Self {
198 client,
199 params: ForwardMessageParams {
200 chat_id: chat_id.into(),
201 from_chat_id: from_chat_id.into(),
202 message_id,
203 message_thread_id: None,
204 video_start_timestamp: None,
205 disable_notification: None,
206 protect_content: None,
207 },
208 }
209 }
210 pub fn message_thread_id(mut self, id: i64) -> Self {
212 self.params.message_thread_id = Some(id);
213 self
214 }
215 pub fn disable_notification(mut self, v: bool) -> Self {
217 self.params.disable_notification = Some(v);
218 self
219 }
220 pub fn protect_content(mut self, v: bool) -> Self {
222 self.params.protect_content = Some(v);
223 self
224 }
225}
226
227impl_into_future!(ForwardMessage, Message, "forwardMessage");
228
229#[derive(Serialize)]
232struct CopyMessageParams {
233 chat_id: ChatId,
234 from_chat_id: ChatId,
235 message_id: i64,
236 #[serde(skip_serializing_if = "Option::is_none")]
237 message_thread_id: Option<i64>,
238 #[serde(skip_serializing_if = "Option::is_none")]
239 video_start_timestamp: Option<i64>,
240 #[serde(skip_serializing_if = "Option::is_none")]
241 caption: Option<String>,
242 #[serde(skip_serializing_if = "Option::is_none")]
243 parse_mode: Option<ParseMode>,
244 #[serde(skip_serializing_if = "Option::is_none")]
245 caption_entities: Option<Vec<rustigram_types::message::MessageEntity>>,
246 #[serde(skip_serializing_if = "Option::is_none")]
247 show_caption_above_media: Option<bool>,
248 #[serde(skip_serializing_if = "Option::is_none")]
249 disable_notification: Option<bool>,
250 #[serde(skip_serializing_if = "Option::is_none")]
251 protect_content: Option<bool>,
252 #[serde(skip_serializing_if = "Option::is_none")]
253 reply_parameters: Option<ReplyParameters>,
254 #[serde(skip_serializing_if = "Option::is_none")]
255 reply_markup: Option<ReplyMarkup>,
256}
257
258pub struct CopyMessage {
260 client: BotClient,
261 params: CopyMessageParams,
262}
263
264impl CopyMessage {
265 pub(crate) fn new(
266 client: BotClient,
267 chat_id: impl Into<ChatId>,
268 from_chat_id: impl Into<ChatId>,
269 message_id: i64,
270 ) -> Self {
271 Self {
272 client,
273 params: CopyMessageParams {
274 chat_id: chat_id.into(),
275 from_chat_id: from_chat_id.into(),
276 message_id,
277 message_thread_id: None,
278 video_start_timestamp: None,
279 caption: None,
280 parse_mode: None,
281 caption_entities: None,
282 show_caption_above_media: None,
283 disable_notification: None,
284 protect_content: None,
285 reply_parameters: None,
286 reply_markup: None,
287 },
288 }
289 }
290 pub fn caption(mut self, c: impl Into<String>) -> Self {
292 self.params.caption = Some(c.into());
293 self
294 }
295 pub fn parse_mode(mut self, m: ParseMode) -> Self {
297 self.params.parse_mode = Some(m);
298 self
299 }
300 pub fn disable_notification(mut self, v: bool) -> Self {
302 self.params.disable_notification = Some(v);
303 self
304 }
305 pub fn reply_markup(mut self, m: impl Into<ReplyMarkup>) -> Self {
307 self.params.reply_markup = Some(m.into());
308 self
309 }
310}
311
312impl_into_future!(
313 CopyMessage,
314 rustigram_types::message::MessageId,
315 "copyMessage"
316);
317
318#[derive(Serialize)]
321struct SendChatActionParams {
322 chat_id: ChatId,
323 action: ChatAction,
324 #[serde(skip_serializing_if = "Option::is_none")]
325 business_connection_id: Option<String>,
326 #[serde(skip_serializing_if = "Option::is_none")]
327 message_thread_id: Option<i64>,
328}
329
330#[derive(Serialize, Clone, Copy)]
331#[serde(rename_all = "snake_case")]
333pub enum ChatAction {
334 Typing,
336 UploadPhoto,
338 RecordVideo,
340 UploadVideo,
342 RecordVoice,
344 UploadVoice,
346 UploadDocument,
348 ChooseSticker,
350 FindLocation,
352 RecordVideoNote,
354 UploadVideoNote,
356}
357
358pub struct SendChatAction {
360 client: BotClient,
361 params: SendChatActionParams,
362}
363
364impl SendChatAction {
365 pub(crate) fn new(client: BotClient, chat_id: impl Into<ChatId>, action: ChatAction) -> Self {
366 Self {
367 client,
368 params: SendChatActionParams {
369 chat_id: chat_id.into(),
370 action,
371 business_connection_id: None,
372 message_thread_id: None,
373 },
374 }
375 }
376 pub fn business_connection_id(mut self, id: impl Into<String>) -> Self {
378 self.params.business_connection_id = Some(id.into());
379 self
380 }
381 pub fn message_thread_id(mut self, id: i64) -> Self {
383 self.params.message_thread_id = Some(id);
384 self
385 }
386}
387
388impl_into_future!(SendChatAction, bool, "sendChatAction");
389
390#[derive(Serialize)]
393struct SendDiceParams {
394 chat_id: ChatId,
395 #[serde(skip_serializing_if = "Option::is_none")]
396 emoji: Option<String>,
397 #[serde(skip_serializing_if = "Option::is_none")]
398 message_thread_id: Option<i64>,
399 #[serde(skip_serializing_if = "Option::is_none")]
400 disable_notification: Option<bool>,
401 #[serde(skip_serializing_if = "Option::is_none")]
402 protect_content: Option<bool>,
403 #[serde(skip_serializing_if = "Option::is_none")]
404 reply_parameters: Option<ReplyParameters>,
405 #[serde(skip_serializing_if = "Option::is_none")]
406 reply_markup: Option<ReplyMarkup>,
407}
408
409pub struct SendDice {
411 client: BotClient,
412 params: SendDiceParams,
413}
414
415impl SendDice {
416 pub(crate) fn new(client: BotClient, chat_id: impl Into<ChatId>) -> Self {
417 Self {
418 client,
419 params: SendDiceParams {
420 chat_id: chat_id.into(),
421 emoji: None,
422 message_thread_id: None,
423 disable_notification: None,
424 protect_content: None,
425 reply_parameters: None,
426 reply_markup: None,
427 },
428 }
429 }
430 pub fn emoji(mut self, e: impl Into<String>) -> Self {
432 self.params.emoji = Some(e.into());
433 self
434 }
435 pub fn disable_notification(mut self, v: bool) -> Self {
437 self.params.disable_notification = Some(v);
438 self
439 }
440 pub fn reply_markup(mut self, m: impl Into<ReplyMarkup>) -> Self {
442 self.params.reply_markup = Some(m.into());
443 self
444 }
445}
446
447impl_into_future!(SendDice, Message, "sendDice");
448
449#[derive(Serialize)]
452struct SendLocationParams {
453 chat_id: ChatId,
454 latitude: f64,
455 longitude: f64,
456 #[serde(skip_serializing_if = "Option::is_none")]
457 message_thread_id: Option<i64>,
458 #[serde(skip_serializing_if = "Option::is_none")]
459 horizontal_accuracy: Option<f64>,
460 #[serde(skip_serializing_if = "Option::is_none")]
461 live_period: Option<u32>,
462 #[serde(skip_serializing_if = "Option::is_none")]
463 heading: Option<u16>,
464 #[serde(skip_serializing_if = "Option::is_none")]
465 proximity_alert_radius: Option<u32>,
466 #[serde(skip_serializing_if = "Option::is_none")]
467 disable_notification: Option<bool>,
468 #[serde(skip_serializing_if = "Option::is_none")]
469 protect_content: Option<bool>,
470 #[serde(skip_serializing_if = "Option::is_none")]
471 reply_parameters: Option<ReplyParameters>,
472 #[serde(skip_serializing_if = "Option::is_none")]
473 reply_markup: Option<ReplyMarkup>,
474}
475
476pub struct SendLocation {
478 client: BotClient,
479 params: SendLocationParams,
480}
481
482impl SendLocation {
483 pub(crate) fn new(
484 client: BotClient,
485 chat_id: impl Into<ChatId>,
486 latitude: f64,
487 longitude: f64,
488 ) -> Self {
489 Self {
490 client,
491 params: SendLocationParams {
492 chat_id: chat_id.into(),
493 latitude,
494 longitude,
495 message_thread_id: None,
496 horizontal_accuracy: None,
497 live_period: None,
498 heading: None,
499 proximity_alert_radius: None,
500 disable_notification: None,
501 protect_content: None,
502 reply_parameters: None,
503 reply_markup: None,
504 },
505 }
506 }
507 pub fn horizontal_accuracy(mut self, v: f64) -> Self {
509 self.params.horizontal_accuracy = Some(v);
510 self
511 }
512 pub fn live_period(mut self, v: u32) -> Self {
514 self.params.live_period = Some(v);
515 self
516 }
517 pub fn heading(mut self, v: u16) -> Self {
519 self.params.heading = Some(v);
520 self
521 }
522 pub fn proximity_alert_radius(mut self, v: u32) -> Self {
524 self.params.proximity_alert_radius = Some(v);
525 self
526 }
527 pub fn disable_notification(mut self, v: bool) -> Self {
529 self.params.disable_notification = Some(v);
530 self
531 }
532 pub fn reply_markup(mut self, m: impl Into<ReplyMarkup>) -> Self {
534 self.params.reply_markup = Some(m.into());
535 self
536 }
537}
538
539impl_into_future!(SendLocation, Message, "sendLocation");
540
541#[derive(Serialize)]
544struct SendContactParams {
545 chat_id: ChatId,
546 phone_number: String,
547 first_name: String,
548 #[serde(skip_serializing_if = "Option::is_none")]
549 last_name: Option<String>,
550 #[serde(skip_serializing_if = "Option::is_none")]
551 vcard: Option<String>,
552 #[serde(skip_serializing_if = "Option::is_none")]
553 message_thread_id: Option<i64>,
554 #[serde(skip_serializing_if = "Option::is_none")]
555 disable_notification: Option<bool>,
556 #[serde(skip_serializing_if = "Option::is_none")]
557 protect_content: Option<bool>,
558 #[serde(skip_serializing_if = "Option::is_none")]
559 reply_parameters: Option<ReplyParameters>,
560 #[serde(skip_serializing_if = "Option::is_none")]
561 reply_markup: Option<ReplyMarkup>,
562}
563
564pub struct SendContact {
566 client: BotClient,
567 params: SendContactParams,
568}
569
570impl SendContact {
571 pub(crate) fn new(
572 client: BotClient,
573 chat_id: impl Into<ChatId>,
574 phone_number: impl Into<String>,
575 first_name: impl Into<String>,
576 ) -> Self {
577 Self {
578 client,
579 params: SendContactParams {
580 chat_id: chat_id.into(),
581 phone_number: phone_number.into(),
582 first_name: first_name.into(),
583 last_name: None,
584 vcard: None,
585 message_thread_id: None,
586 disable_notification: None,
587 protect_content: None,
588 reply_parameters: None,
589 reply_markup: None,
590 },
591 }
592 }
593 pub fn last_name(mut self, v: impl Into<String>) -> Self {
595 self.params.last_name = Some(v.into());
596 self
597 }
598 pub fn vcard(mut self, v: impl Into<String>) -> Self {
600 self.params.vcard = Some(v.into());
601 self
602 }
603 pub fn disable_notification(mut self, v: bool) -> Self {
605 self.params.disable_notification = Some(v);
606 self
607 }
608 pub fn reply_markup(mut self, m: impl Into<ReplyMarkup>) -> Self {
610 self.params.reply_markup = Some(m.into());
611 self
612 }
613}
614
615impl_into_future!(SendContact, Message, "sendContact");
616
617#[derive(Serialize)]
620struct SendPollParams {
621 chat_id: ChatId,
622 question: String,
623 options: Vec<InputPollOption>,
624 #[serde(skip_serializing_if = "Option::is_none")]
625 question_parse_mode: Option<ParseMode>,
626 #[serde(skip_serializing_if = "Option::is_none")]
627 question_entities: Option<Vec<rustigram_types::message::MessageEntity>>,
628 #[serde(skip_serializing_if = "Option::is_none")]
629 message_thread_id: Option<i64>,
630 #[serde(skip_serializing_if = "Option::is_none", rename = "type")]
631 poll_type: Option<rustigram_types::poll::PollType>,
632 #[serde(skip_serializing_if = "Option::is_none")]
633 is_anonymous: Option<bool>,
634 #[serde(skip_serializing_if = "Option::is_none")]
635 allows_multiple_answers: Option<bool>,
636 #[serde(skip_serializing_if = "Option::is_none")]
637 allows_revoting: Option<bool>,
638 #[serde(skip_serializing_if = "Option::is_none")]
639 correct_option_ids: Option<Vec<u8>>,
640 #[serde(skip_serializing_if = "Option::is_none")]
641 explanation: Option<String>,
642 #[serde(skip_serializing_if = "Option::is_none")]
643 explanation_parse_mode: Option<ParseMode>,
644 #[serde(skip_serializing_if = "Option::is_none")]
645 open_period: Option<u32>,
646 #[serde(skip_serializing_if = "Option::is_none")]
647 close_date: Option<i64>,
648 #[serde(skip_serializing_if = "Option::is_none")]
649 is_closed: Option<bool>,
650 #[serde(skip_serializing_if = "Option::is_none")]
651 disable_notification: Option<bool>,
652 #[serde(skip_serializing_if = "Option::is_none")]
653 protect_content: Option<bool>,
654 #[serde(skip_serializing_if = "Option::is_none")]
655 reply_parameters: Option<ReplyParameters>,
656 #[serde(skip_serializing_if = "Option::is_none")]
657 reply_markup: Option<ReplyMarkup>,
658}
659
660pub struct SendPoll {
662 client: BotClient,
663 params: SendPollParams,
664}
665
666impl SendPoll {
667 pub(crate) fn new(
668 client: BotClient,
669 chat_id: impl Into<ChatId>,
670 question: impl Into<String>,
671 options: Vec<InputPollOption>,
672 ) -> Self {
673 Self {
674 client,
675 params: SendPollParams {
676 chat_id: chat_id.into(),
677 question: question.into(),
678 options,
679 question_parse_mode: None,
680 question_entities: None,
681 message_thread_id: None,
682 poll_type: None,
683 is_anonymous: None,
684 allows_multiple_answers: None,
685 allows_revoting: None,
686 correct_option_ids: None,
687 explanation: None,
688 explanation_parse_mode: None,
689 open_period: None,
690 close_date: None,
691 is_closed: None,
692 disable_notification: None,
693 protect_content: None,
694 reply_parameters: None,
695 reply_markup: None,
696 },
697 }
698 }
699 pub fn is_anonymous(mut self, v: bool) -> Self {
701 self.params.is_anonymous = Some(v);
702 self
703 }
704 pub fn allows_multiple_answers(mut self, v: bool) -> Self {
706 self.params.allows_multiple_answers = Some(v);
707 self
708 }
709 pub fn allows_revoting(mut self, v: bool) -> Self {
711 self.params.allows_revoting = Some(v);
712 self
713 }
714 pub fn quiz(mut self, correct_option_id: u8) -> Self {
716 self.params.poll_type = Some(rustigram_types::poll::PollType::Quiz);
717 self.params.correct_option_ids = Some(vec![correct_option_id]);
718 self
719 }
720 pub fn explanation(mut self, text: impl Into<String>) -> Self {
722 self.params.explanation = Some(text.into());
723 self
724 }
725 pub fn open_period(mut self, secs: u32) -> Self {
727 self.params.open_period = Some(secs);
728 self
729 }
730 pub fn close_date(mut self, ts: i64) -> Self {
732 self.params.close_date = Some(ts);
733 self
734 }
735 pub fn disable_notification(mut self, v: bool) -> Self {
737 self.params.disable_notification = Some(v);
738 self
739 }
740 pub fn reply_markup(mut self, m: impl Into<ReplyMarkup>) -> Self {
742 self.params.reply_markup = Some(m.into());
743 self
744 }
745}
746
747impl_into_future!(SendPoll, Message, "sendPoll");
748
749#[derive(Serialize)]
752struct SendMessageDraftParams {
753 chat_id: ChatId,
754 draft_id: i64,
755 text: String,
756 #[serde(skip_serializing_if = "Option::is_none")]
757 message_thread_id: Option<i64>,
758 #[serde(skip_serializing_if = "Option::is_none")]
759 parse_mode: Option<ParseMode>,
760 #[serde(skip_serializing_if = "Option::is_none")]
761 entities: Option<Vec<rustigram_types::message::MessageEntity>>,
762}
763
764pub struct SendMessageDraft {
767 client: BotClient,
768 params: SendMessageDraftParams,
769}
770
771impl SendMessageDraft {
772 pub(crate) fn new(
773 client: BotClient,
774 chat_id: impl Into<ChatId>,
775 draft_id: i64,
776 text: impl Into<String>,
777 ) -> Self {
778 Self {
779 client,
780 params: SendMessageDraftParams {
781 chat_id: chat_id.into(),
782 draft_id,
783 text: text.into(),
784 message_thread_id: None,
785 parse_mode: None,
786 entities: None,
787 },
788 }
789 }
790 pub fn parse_mode(mut self, m: ParseMode) -> Self {
792 self.params.parse_mode = Some(m);
793 self
794 }
795 pub fn entities(mut self, e: Vec<rustigram_types::message::MessageEntity>) -> Self {
797 self.params.entities = Some(e);
798 self
799 }
800}
801
802impl_into_future!(SendMessageDraft, bool, "sendMessageDraft");
803
804#[derive(Default)]
813pub struct MediaSendOptions {
814 pub business_connection_id: Option<String>,
816 pub message_thread_id: Option<i64>,
818 pub caption: Option<String>,
820 pub parse_mode: Option<ParseMode>,
822 pub caption_entities: Option<Vec<rustigram_types::message::MessageEntity>>,
824 pub show_caption_above_media: Option<bool>,
826 pub has_spoiler: Option<bool>,
828 pub disable_notification: Option<bool>,
830 pub protect_content: Option<bool>,
832 pub allow_paid_broadcast: Option<bool>,
834 pub reply_parameters: Option<ReplyParameters>,
836 pub reply_markup: Option<ReplyMarkup>,
838}
839
840fn media_json_body(
842 chat_id: &ChatId,
843 media_field: &str,
844 media_value: &str,
845 opts: &MediaSendOptions,
846 extra: serde_json::Value,
847) -> serde_json::Value {
848 let mut map = serde_json::json!({
849 "chat_id": chat_id,
850 media_field: media_value,
851 });
852 let obj = map.as_object_mut().unwrap();
853 if let Some(v) = &opts.business_connection_id {
854 obj.insert("business_connection_id".to_owned(), serde_json::json!(v));
855 }
856 if let Some(v) = &opts.message_thread_id {
857 obj.insert("message_thread_id".to_owned(), serde_json::json!(v));
858 }
859 if let Some(v) = &opts.caption {
860 obj.insert("caption".to_owned(), serde_json::json!(v));
861 }
862 if let Some(v) = &opts.parse_mode {
863 obj.insert("parse_mode".to_owned(), serde_json::json!(v));
864 }
865 if let Some(v) = &opts.caption_entities {
866 obj.insert("caption_entities".to_owned(), serde_json::json!(v));
867 }
868 if let Some(v) = opts.show_caption_above_media {
869 obj.insert("show_caption_above_media".to_owned(), serde_json::json!(v));
870 }
871 if let Some(v) = opts.has_spoiler {
872 obj.insert("has_spoiler".to_owned(), serde_json::json!(v));
873 }
874 if let Some(v) = opts.disable_notification {
875 obj.insert("disable_notification".to_owned(), serde_json::json!(v));
876 }
877 if let Some(v) = opts.protect_content {
878 obj.insert("protect_content".to_owned(), serde_json::json!(v));
879 }
880 if let Some(v) = opts.allow_paid_broadcast {
881 obj.insert("allow_paid_broadcast".to_owned(), serde_json::json!(v));
882 }
883 if let Some(v) = &opts.reply_parameters {
884 obj.insert("reply_parameters".to_owned(), serde_json::json!(v));
885 }
886 if let Some(v) = &opts.reply_markup {
887 obj.insert("reply_markup".to_owned(), serde_json::json!(v));
888 }
889 if let serde_json::Value::Object(extra_obj) = extra {
890 for (k, v) in extra_obj {
891 obj.insert(k, v);
892 }
893 }
894 map
895}
896
897pub struct SendPhoto {
901 client: BotClient,
902 chat_id: ChatId,
903 photo: InputFile,
904 opts: MediaSendOptions,
905}
906
907impl SendPhoto {
908 pub(crate) fn new(client: BotClient, chat_id: impl Into<ChatId>, photo: InputFile) -> Self {
909 Self {
910 client,
911 chat_id: chat_id.into(),
912 photo,
913 opts: MediaSendOptions::default(),
914 }
915 }
916 pub fn caption(mut self, c: impl Into<String>) -> Self {
918 self.opts.caption = Some(c.into());
919 self
920 }
921 pub fn parse_mode(mut self, m: ParseMode) -> Self {
923 self.opts.parse_mode = Some(m);
924 self
925 }
926 pub fn has_spoiler(mut self, v: bool) -> Self {
928 self.opts.has_spoiler = Some(v);
929 self
930 }
931 pub fn show_caption_above_media(mut self, v: bool) -> Self {
933 self.opts.show_caption_above_media = Some(v);
934 self
935 }
936 pub fn disable_notification(mut self, v: bool) -> Self {
938 self.opts.disable_notification = Some(v);
939 self
940 }
941 pub fn protect_content(mut self, v: bool) -> Self {
943 self.opts.protect_content = Some(v);
944 self
945 }
946 pub fn allow_paid_broadcast(mut self, v: bool) -> Self {
948 self.opts.allow_paid_broadcast = Some(v);
949 self
950 }
951 pub fn reply_parameters(mut self, rp: ReplyParameters) -> Self {
953 self.opts.reply_parameters = Some(rp);
954 self
955 }
956 pub fn reply_markup(mut self, m: impl Into<ReplyMarkup>) -> Self {
958 self.opts.reply_markup = Some(m.into());
959 self
960 }
961}
962
963impl IntoFuture for SendPhoto {
964 type Output = Result<Message>;
965 type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
966
967 fn into_future(self) -> Self::IntoFuture {
968 Box::pin(async move {
969 match &self.photo {
970 InputFile::Bytes {
971 filename,
972 data,
973 mime_type,
974 } => {
975 let part = Part::bytes(data.clone())
976 .file_name(filename.clone())
977 .mime_str(mime_type)
978 .map_err(|e| crate::error::Error::Decode(e.to_string()))?;
979 let mut form = Form::new().part("photo", part);
980 form = form.text("chat_id", self.chat_id.to_string());
981 if let Some(c) = &self.opts.caption {
982 form = form.text("caption", c.clone());
983 }
984 if let Some(m) = &self.opts.parse_mode {
985 form = form.text("parse_mode", format!("{m:?}"));
986 }
987 if let Some(v) = self.opts.disable_notification {
988 form = form.text("disable_notification", v.to_string());
989 }
990 if let Some(v) = self.opts.has_spoiler {
991 form = form.text("has_spoiler", v.to_string());
992 }
993 if let Some(v) = &self.opts.reply_markup {
994 form = form.text("reply_markup", serde_json::to_string(v).unwrap());
995 }
996 self.client.post_multipart("sendPhoto", form).await
997 }
998 _ => {
999 let body = media_json_body(
1000 &self.chat_id,
1001 "photo",
1002 self.photo.as_str(),
1003 &self.opts,
1004 serde_json::Value::Null,
1005 );
1006 self.client.post_json("sendPhoto", &body).await
1007 }
1008 }
1009 })
1010 }
1011}
1012
1013macro_rules! media_sender {
1016 ($(#[$doc:meta])* $name:ident, $field:literal, $method:literal, $return_ty:ty, [$($extra_field:ident: $extra_ty:ty),*]) => {
1017 $(#[$doc])*
1018 pub struct $name {
1019 client: BotClient,
1021 chat_id: ChatId,
1023 file: InputFile,
1025 opts: MediaSendOptions,
1027 $($extra_field: Option<$extra_ty>,)*
1029 }
1030
1031 impl $name {
1032 pub(crate) fn new(client: BotClient, chat_id: impl Into<ChatId>, file: InputFile) -> Self {
1033 Self {
1034 client,
1035 chat_id: chat_id.into(),
1036 file,
1037 opts: MediaSendOptions::default(),
1038 $($extra_field: None,)*
1039 }
1040 }
1041 pub fn caption(mut self, c: impl Into<String>) -> Self { self.opts.caption = Some(c.into()); self }
1043 pub fn parse_mode(mut self, m: ParseMode) -> Self { self.opts.parse_mode = Some(m); self }
1045 pub fn disable_notification(mut self, v: bool) -> Self { self.opts.disable_notification = Some(v); self }
1047 pub fn protect_content(mut self, v: bool) -> Self { self.opts.protect_content = Some(v); self }
1049 pub fn allow_paid_broadcast(mut self, v: bool) -> Self { self.opts.allow_paid_broadcast = Some(v); self }
1051 pub fn reply_parameters(mut self, rp: ReplyParameters) -> Self { self.opts.reply_parameters = Some(rp); self }
1053 pub fn reply_markup(mut self, m: impl Into<ReplyMarkup>) -> Self { self.opts.reply_markup = Some(m.into()); self }
1055 }
1056
1057 impl IntoFuture for $name {
1058 type Output = Result<$return_ty>;
1059 type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
1060
1061 fn into_future(self) -> Self::IntoFuture {
1062 Box::pin(async move {
1063 match &self.file {
1064 InputFile::Bytes { filename, data, mime_type } => {
1065 let part = Part::bytes(data.clone())
1066 .file_name(filename.clone())
1067 .mime_str(mime_type)
1068 .map_err(|e| crate::error::Error::Decode(e.to_string()))?;
1069 let mut form = Form::new().part($field, part);
1070 form = form.text("chat_id", self.chat_id.to_string());
1071 if let Some(c) = &self.opts.caption { form = form.text("caption", c.clone()); }
1072 if let Some(v) = self.opts.disable_notification { form = form.text("disable_notification", v.to_string()); }
1073 if let Some(v) = &self.opts.reply_markup { form = form.text("reply_markup", serde_json::to_string(v).unwrap()); }
1074 self.client.post_multipart($method, form).await
1075 }
1076 _ => {
1077 let mut extra = serde_json::json!({});
1078 $(
1079 if let Some(ref v) = self.$extra_field {
1080 extra[stringify!($extra_field)] = serde_json::json!(v);
1081 }
1082 )*
1083 let body = media_json_body(&self.chat_id, $field, self.file.as_str(), &self.opts, extra);
1084 self.client.post_json($method, &body).await
1085 }
1086 }
1087 })
1088 }
1089 }
1090 };
1091}
1092
1093media_sender!(
1094 SendAudio, "audio", "sendAudio", Message, [duration: u32, performer: String, title: String]);
1096media_sender!(
1097 SendDocument, "document", "sendDocument", Message, [disable_content_type_detection: bool]);
1099media_sender!(
1100 SendVideo, "video", "sendVideo", Message, [duration: u32, width: u32, height: u32, supports_streaming: bool]);
1102media_sender!(
1103 SendAnimation, "animation", "sendAnimation", Message, [duration: u32, width: u32, height: u32]);
1105media_sender!(
1106 SendVoice, "voice", "sendVoice", Message, [duration: u32]);
1108media_sender!(
1109 SendVideoNote, "video_note", "sendVideoNote", Message, [duration: u32, length: u32]);
1111media_sender!(
1112 SendSticker, "sticker", "sendSticker", Message, [emoji: String]);
1114
1115#[derive(Serialize)]
1118struct DeleteMessageParams {
1119 chat_id: ChatId,
1120 message_id: i64,
1121}
1122
1123pub struct DeleteMessage {
1125 client: BotClient,
1126 params: DeleteMessageParams,
1127}
1128impl DeleteMessage {
1129 pub(crate) fn new(client: BotClient, chat_id: impl Into<ChatId>, message_id: i64) -> Self {
1130 Self {
1131 client,
1132 params: DeleteMessageParams {
1133 chat_id: chat_id.into(),
1134 message_id,
1135 },
1136 }
1137 }
1138}
1139impl_into_future!(DeleteMessage, bool, "deleteMessage");
1140
1141#[derive(Serialize)]
1142struct DeleteMessagesParams {
1143 chat_id: ChatId,
1144 message_ids: Vec<i64>,
1145}
1146
1147pub struct DeleteMessages {
1149 client: BotClient,
1150 params: DeleteMessagesParams,
1151}
1152impl DeleteMessages {
1153 pub(crate) fn new(
1154 client: BotClient,
1155 chat_id: impl Into<ChatId>,
1156 message_ids: Vec<i64>,
1157 ) -> Self {
1158 Self {
1159 client,
1160 params: DeleteMessagesParams {
1161 chat_id: chat_id.into(),
1162 message_ids,
1163 },
1164 }
1165 }
1166}
1167impl_into_future!(DeleteMessages, bool, "deleteMessages");
1168
1169#[derive(Serialize)]
1172struct StopPollParams {
1173 chat_id: ChatId,
1174 message_id: i64,
1175 #[serde(skip_serializing_if = "Option::is_none")]
1176 reply_markup: Option<rustigram_types::keyboard::InlineKeyboardMarkup>,
1177}
1178
1179pub struct StopPoll {
1181 client: BotClient,
1182 params: StopPollParams,
1183}
1184impl StopPoll {
1185 pub(crate) fn new(client: BotClient, chat_id: impl Into<ChatId>, message_id: i64) -> Self {
1186 Self {
1187 client,
1188 params: StopPollParams {
1189 chat_id: chat_id.into(),
1190 message_id,
1191 reply_markup: None,
1192 },
1193 }
1194 }
1195 pub fn reply_markup(mut self, m: rustigram_types::keyboard::InlineKeyboardMarkup) -> Self {
1197 self.params.reply_markup = Some(m);
1198 self
1199 }
1200}
1201impl_into_future!(StopPoll, rustigram_types::poll::Poll, "stopPoll");
1202
1203#[derive(Serialize)]
1206struct AnswerCallbackQueryParams {
1207 callback_query_id: String,
1208 #[serde(skip_serializing_if = "Option::is_none")]
1209 text: Option<String>,
1210 #[serde(skip_serializing_if = "Option::is_none")]
1211 show_alert: Option<bool>,
1212 #[serde(skip_serializing_if = "Option::is_none")]
1213 url: Option<String>,
1214 #[serde(skip_serializing_if = "Option::is_none")]
1215 cache_time: Option<u32>,
1216}
1217
1218pub struct AnswerCallbackQuery {
1220 client: BotClient,
1221 params: AnswerCallbackQueryParams,
1222}
1223impl AnswerCallbackQuery {
1224 pub(crate) fn new(client: BotClient, callback_query_id: impl Into<String>) -> Self {
1225 Self {
1226 client,
1227 params: AnswerCallbackQueryParams {
1228 callback_query_id: callback_query_id.into(),
1229 text: None,
1230 show_alert: None,
1231 url: None,
1232 cache_time: None,
1233 },
1234 }
1235 }
1236 pub fn text(mut self, t: impl Into<String>) -> Self {
1238 self.params.text = Some(t.into());
1239 self
1240 }
1241 pub fn show_alert(mut self, v: bool) -> Self {
1243 self.params.show_alert = Some(v);
1244 self
1245 }
1246 pub fn url(mut self, u: impl Into<String>) -> Self {
1248 self.params.url = Some(u.into());
1249 self
1250 }
1251 pub fn cache_time(mut self, secs: u32) -> Self {
1253 self.params.cache_time = Some(secs);
1254 self
1255 }
1256 pub fn alert(self, text: impl Into<String>) -> Self {
1258 self.text(text).show_alert(true)
1259 }
1260}
1261impl_into_future!(AnswerCallbackQuery, bool, "answerCallbackQuery");