frankenstein/
trait_sync.rs

1use std::path::PathBuf;
2
3use crate::games::GameHighScore;
4use crate::gifts::{Gifts, OwnedGifts};
5use crate::inline_mode::{PreparedInlineMessage, SentWebAppMessage};
6use crate::input_file::HasInputFile;
7use crate::input_media::{InputMedia, InputProfilePhoto, InputStoryContent, MediaGroupInputMedia};
8use crate::payments::{StarAmount, StarTransactions};
9use crate::response::{MessageOrBool, MethodResponse};
10use crate::stickers::{Sticker, StickerSet};
11use crate::types::{
12    BotCommand, BotDescription, BotName, BotShortDescription, BusinessConnection,
13    ChatAdministratorRights, ChatFullInfo, ChatInviteLink, ChatMember, File, ForumTopic,
14    MenuButton, Message, MessageId, Poll, Story, User, UserChatBoosts, UserProfilePhotos,
15};
16use crate::updates::{Update, WebhookInfo};
17
18macro_rules! request {
19    ($name:ident, $return:ty) => {
20        paste::paste! {
21            #[doc = "Call the `" $name "` method.\n\nSee <https://core.telegram.org/bots/api#" $name:lower ">."]
22            #[inline(always)]
23            fn [<$name:snake>] (
24                &self,
25                params: &crate::methods::[<$name:camel Params>],
26            ) -> Result<MethodResponse<$return>, Self::Error> {
27                self.request(stringify!($name), Some(params))
28            }
29        }
30    }
31}
32
33/// request no body
34macro_rules! request_nb {
35    ($name:ident, $return:ty) => {
36        paste::paste! {
37            #[doc = "Call the `" $name "` method.\n\nSee <https://core.telegram.org/bots/api#" $name:lower ">."]
38            #[inline(always)]
39            fn [<$name:snake>] (
40                &self,
41            ) -> Result<MethodResponse<$return>, Self::Error> {
42                let params: Option<()> = None;
43                self.request(stringify!($name), params)
44            }
45        }
46    }
47}
48
49/// request with some properties utilizing [`HasInputFile`]
50macro_rules! request_f {
51    ($name:ident, $return:ty, $($fileproperty:ident),+) => {
52        paste::paste! {
53            #[doc = "Call the `" $name "` method.\n\nSee <https://core.telegram.org/bots/api#" $name:lower ">."]
54            fn [<$name:snake>] (
55                &self,
56                params: &crate::methods::[<$name:camel Params>],
57            ) -> Result<MethodResponse<$return>, Self::Error> {
58                let mut files = Vec::new();
59                $(
60                    if let Some(path) = params.$fileproperty.clone_path() {
61                        files.push((stringify!($fileproperty), path));
62                    }
63                )+
64                self.request_with_possible_form_data(stringify!($name), params, files)
65            }
66        }
67    }
68}
69
70pub trait TelegramApi {
71    type Error;
72
73    request!(getUpdates, Vec<Update>);
74    request!(sendMessage, Message);
75    request!(setWebhook, bool);
76    request!(deleteWebhook, bool);
77    request_nb!(getWebhookInfo, WebhookInfo);
78    request_nb!(getMe, User);
79    request_nb!(logOut, bool);
80    request_nb!(close, bool);
81    request!(forwardMessage, Message);
82    request!(forwardMessages, Vec<MessageId>);
83    request!(copyMessage, MessageId);
84    request!(copyMessages, Vec<MessageId>);
85    request_f!(sendPhoto, Message, photo);
86    request_f!(sendAudio, Message, audio, thumbnail);
87
88    fn send_media_group(
89        &self,
90        params: &crate::methods::SendMediaGroupParams,
91    ) -> Result<MethodResponse<Vec<Message>>, Self::Error> {
92        let mut files = Vec::new();
93
94        macro_rules! replace_attach {
95            ($base:ident. $property:ident) => {
96                if let Some(file) = $base.$property.replace_attach_dyn(|| files.len()) {
97                    files.push(file);
98                }
99            };
100        }
101
102        let mut params = params.clone();
103        for media in &mut params.media {
104            match media {
105                MediaGroupInputMedia::Audio(audio) => {
106                    replace_attach!(audio.media);
107                    replace_attach!(audio.thumbnail);
108                }
109                MediaGroupInputMedia::Document(document) => {
110                    replace_attach!(document.media);
111                }
112                MediaGroupInputMedia::Photo(photo) => {
113                    replace_attach!(photo.media);
114                }
115                MediaGroupInputMedia::Video(video) => {
116                    replace_attach!(video.media);
117                    replace_attach!(video.cover);
118                    replace_attach!(video.thumbnail);
119                }
120            }
121        }
122
123        let files_with_str_names = files
124            .iter()
125            .map(|(key, path)| (key.as_str(), path.clone()))
126            .collect();
127
128        self.request_with_possible_form_data("sendMediaGroup", &params, files_with_str_names)
129    }
130
131    request_f!(sendDocument, Message, document, thumbnail);
132    request_f!(sendVideo, Message, video, cover, thumbnail);
133    request_f!(sendAnimation, Message, animation, thumbnail);
134    request_f!(sendVoice, Message, voice);
135    request_f!(sendVideoNote, Message, video_note, thumbnail);
136    request!(sendPaidMedia, Message);
137    request!(sendLocation, Message);
138    request!(editMessageLiveLocation, MessageOrBool);
139    request!(stopMessageLiveLocation, MessageOrBool);
140    request!(sendChecklist, MessageOrBool);
141    request!(editMessageChecklist, MessageOrBool);
142    request!(sendVenue, Message);
143    request!(sendContact, Message);
144    request!(sendPoll, Message);
145    request!(sendDice, Message);
146    request!(sendChatAction, bool);
147    request!(setMessageReaction, bool);
148    request!(getUserProfilePhotos, UserProfilePhotos);
149    request!(setUserEmojiStatus, bool);
150    request!(getFile, File);
151    request!(banChatMember, bool);
152    request!(unbanChatMember, bool);
153    request!(restrictChatMember, bool);
154    request!(promoteChatMember, bool);
155    request!(setChatAdministratorCustomTitle, bool);
156    request!(banChatSenderChat, bool);
157    request!(unbanChatSenderChat, bool);
158    request!(setChatPermissions, bool);
159    request!(exportChatInviteLink, String);
160    request!(createChatInviteLink, ChatInviteLink);
161    request!(editChatInviteLink, ChatInviteLink);
162    request!(createChatSubscriptionInviteLink, ChatInviteLink);
163    request!(editChatSubscriptionInviteLink, ChatInviteLink);
164    request!(revokeChatInviteLink, ChatInviteLink);
165    request!(approveChatJoinRequest, bool);
166    request!(declineChatJoinRequest, bool);
167
168    fn set_chat_photo(
169        &self,
170        params: &crate::methods::SetChatPhotoParams,
171    ) -> Result<MethodResponse<bool>, Self::Error> {
172        let photo = &params.photo;
173        self.request_with_form_data("setChatPhoto", params, vec![("photo", photo.path.clone())])
174    }
175
176    request!(deleteChatPhoto, bool);
177    request!(setChatTitle, bool);
178    request!(setChatDescription, bool);
179    request!(pinChatMessage, bool);
180    request!(unpinChatMessage, bool);
181    request!(unpinAllChatMessages, bool);
182    request!(leaveChat, bool);
183    request!(getChat, ChatFullInfo);
184    request!(getChatAdministrators, Vec<ChatMember>);
185    request!(getChatMemberCount, u32);
186    request!(getChatMember, ChatMember);
187    request!(setChatStickerSet, bool);
188    request!(deleteChatStickerSet, bool);
189    request_nb!(getForumTopicIconStickers, Vec<Sticker>);
190    request!(createForumTopic, ForumTopic);
191    request!(editForumTopic, bool);
192    request!(closeForumTopic, bool);
193    request!(reopenForumTopic, bool);
194    request!(deleteForumTopic, bool);
195    request!(unpinAllForumTopicMessages, bool);
196    request!(editGeneralForumTopic, bool);
197    request!(closeGeneralForumTopic, bool);
198    request!(reopenGeneralForumTopic, bool);
199    request!(hideGeneralForumTopic, bool);
200    request!(unhideGeneralForumTopic, bool);
201    request!(answerCallbackQuery, bool);
202    request!(getUserChatBoosts, UserChatBoosts);
203    request!(getBusinessConnection, BusinessConnection);
204    request!(getMyCommands, Vec<BotCommand>);
205    request!(setMyCommands, bool);
206    request!(deleteMyCommands, bool);
207    request!(setMyName, bool);
208    request!(getMyName, BotName);
209    request!(setMyDescription, bool);
210    request!(getMyDescription, BotDescription);
211    request!(setMyShortDescription, bool);
212    request!(getMyShortDescription, BotShortDescription);
213    request!(answerInlineQuery, bool);
214    request!(editMessageText, MessageOrBool);
215    request!(editMessageCaption, MessageOrBool);
216
217    fn edit_message_media(
218        &self,
219        params: &crate::methods::EditMessageMediaParams,
220    ) -> Result<MethodResponse<MessageOrBool>, Self::Error> {
221        let mut files = Vec::new();
222
223        macro_rules! replace_attach {
224            ($base:ident. $property:ident) => {{
225                const NAME: &str = concat!(stringify!($base), "_", stringify!($property));
226                if let Some(file) = $base.$property.replace_attach(NAME) {
227                    files.push((NAME, file));
228                }
229            }};
230        }
231
232        let mut params = params.clone();
233        match &mut params.media {
234            InputMedia::Animation(animation) => {
235                replace_attach!(animation.media);
236                replace_attach!(animation.thumbnail);
237            }
238            InputMedia::Document(document) => {
239                replace_attach!(document.media);
240                replace_attach!(document.thumbnail);
241            }
242            InputMedia::Audio(audio) => {
243                replace_attach!(audio.media);
244                replace_attach!(audio.thumbnail);
245            }
246            InputMedia::Photo(photo) => {
247                replace_attach!(photo.media);
248            }
249            InputMedia::Video(video) => {
250                replace_attach!(video.media);
251                replace_attach!(video.cover);
252                replace_attach!(video.thumbnail);
253            }
254        }
255
256        self.request_with_possible_form_data("editMessageMedia", &params, files)
257    }
258
259    request!(editMessageReplyMarkup, MessageOrBool);
260    request!(stopPoll, Poll);
261    request!(deleteMessage, bool);
262    request!(deleteMessages, bool);
263    request_f!(sendSticker, Message, sticker);
264    request!(getStickerSet, StickerSet);
265
266    fn upload_sticker_file(
267        &self,
268        params: &crate::methods::UploadStickerFileParams,
269    ) -> Result<MethodResponse<File>, Self::Error> {
270        let sticker = &params.sticker;
271        self.request_with_form_data(
272            "uploadStickerFile",
273            params,
274            vec![("sticker", sticker.path.clone())],
275        )
276    }
277
278    fn create_new_sticker_set(
279        &self,
280        params: &crate::methods::CreateNewStickerSetParams,
281    ) -> Result<MethodResponse<bool>, Self::Error> {
282        let mut files = Vec::new();
283
284        let mut params = params.clone();
285        for (index, sticker) in params.stickers.iter_mut().enumerate() {
286            if let Some(file) = sticker.sticker.replace_attach_dyn(|| index) {
287                files.push(file);
288            }
289        }
290
291        let files_with_str_names = files
292            .iter()
293            .map(|(key, path)| (key.as_str(), path.clone()))
294            .collect();
295
296        self.request_with_possible_form_data("createNewStickerSet", &params, files_with_str_names)
297    }
298
299    request!(getCustomEmojiStickers, Vec<Sticker>);
300
301    fn add_sticker_to_set(
302        &self,
303        params: &crate::methods::AddStickerToSetParams,
304    ) -> Result<MethodResponse<bool>, Self::Error> {
305        let mut files = Vec::new();
306        let mut params = params.clone();
307        if let Some(file) = params.sticker.sticker.replace_attach("sticker_upload") {
308            files.push(("sticker_upload", file));
309        }
310        self.request_with_possible_form_data("addStickerToSet", params, files)
311    }
312
313    request!(setStickerPositionInSet, bool);
314    request!(deleteStickerFromSet, bool);
315    request!(replaceStickerInSet, bool);
316    request!(setStickerEmojiList, bool);
317    request!(setStickerKeywords, bool);
318    request!(setStickerMaskPosition, bool);
319    request!(setStickerSetTitle, bool);
320    request_f!(setStickerSetThumbnail, bool, thumbnail);
321    request!(setCustomEmojiStickerSetThumbnail, bool);
322    request!(deleteStickerSet, bool);
323    request_nb!(getAvailableGifts, Gifts);
324    request!(sendGift, bool);
325    request!(giftPremiumSubscription, bool);
326    request!(verifyUser, bool);
327    request!(verifyChat, bool);
328    request!(removeUserVerification, bool);
329    request!(removeChatVerification, bool);
330    request!(readBusinessMessage, bool);
331    request!(deleteBusinessMessages, bool);
332    request!(setBusinessAccountName, bool);
333    request!(setBusinessAccountUsername, bool);
334    request!(setBusinessAccountBio, bool);
335
336    fn set_business_account_profile_photo(
337        &self,
338        params: &crate::methods::SetBusinessAccountProfilePhotoParams,
339    ) -> Result<MethodResponse<bool>, Self::Error> {
340        let mut files = Vec::new();
341
342        let mut params = params.clone();
343        match &mut params.photo {
344            InputProfilePhoto::Static(photo_static) => {
345                if let Some(file) = photo_static.photo.replace_attach("photo_static") {
346                    files.push(("photo_static", file));
347                }
348            }
349            InputProfilePhoto::Animated(photo_animated) => {
350                if let Some(file) = photo_animated.animation.replace_attach("photo_animated") {
351                    files.push(("photo_animated", file));
352                }
353            }
354        }
355
356        self.request_with_possible_form_data("setBusinessAccountProfilePhoto", params, files)
357    }
358
359    request!(removeBusinessAccountProfilePhoto, bool);
360    request!(setBusinessAccountGiftSettings, bool);
361    request!(getBusinessAccountStarBalance, StarAmount);
362    request!(transferBusinessAccountStars, bool);
363    request!(getBusinessAccountGifts, OwnedGifts);
364    request!(convertGiftToStars, bool);
365    request!(upgradeGift, bool);
366    request!(transferGift, bool);
367
368    fn post_story(
369        &self,
370        params: &crate::methods::PostStoryParams,
371    ) -> Result<MethodResponse<Story>, Self::Error> {
372        let mut files = Vec::new();
373
374        let mut params = params.clone();
375
376        match &mut params.content {
377            InputStoryContent::Photo(photo_content) => {
378                if let Some(file) = photo_content.photo.replace_attach("photo_content") {
379                    files.push(("photo_content", file));
380                }
381            }
382            InputStoryContent::Video(video_content) => {
383                if let Some(file) = video_content.video.replace_attach("video_content") {
384                    files.push(("video_content", file));
385                }
386            }
387        }
388
389        self.request_with_possible_form_data("postStory", params, files)
390    }
391
392    fn edit_story(
393        &self,
394        params: &crate::methods::EditStoryParams,
395    ) -> Result<MethodResponse<Story>, Self::Error> {
396        let mut files = Vec::new();
397
398        let mut params = params.clone();
399
400        match &mut params.content {
401            InputStoryContent::Photo(photo_content) => {
402                if let Some(file) = photo_content.photo.replace_attach("photo_content") {
403                    files.push(("photo_content", file));
404                }
405            }
406            InputStoryContent::Video(video_content) => {
407                if let Some(file) = video_content.video.replace_attach("video_content") {
408                    files.push(("video_content", file));
409                }
410            }
411        }
412
413        self.request_with_possible_form_data("editStory", params, files)
414    }
415
416    request!(deleteStory, bool);
417    request!(sendInvoice, Message);
418    request!(createInvoiceLink, String);
419    request!(answerShippingQuery, bool);
420    request!(answerPreCheckoutQuery, bool);
421    request_nb!(getMyStarBalance, u32);
422    request!(getStarTransactions, StarTransactions);
423    request!(refundStarPayment, bool);
424    request!(editUserStarSubscription, bool);
425    request!(sendGame, Message);
426    request!(setGameScore, MessageOrBool);
427    request!(getGameHighScores, Vec<GameHighScore>);
428    request!(setMyDefaultAdministratorRights, bool);
429    request!(getMyDefaultAdministratorRights, ChatAdministratorRights);
430    request!(answerWebAppQuery, SentWebAppMessage);
431    request!(savePreparedInlineMessage, PreparedInlineMessage);
432    request!(setChatMenuButton, bool);
433    request!(getChatMenuButton, MenuButton);
434    request!(unpinAllGeneralForumTopicMessages, bool);
435    request!(setPassportDataErrors, bool);
436
437    fn request_with_possible_form_data<Params, Output>(
438        &self,
439        method_name: &str,
440        params: Params,
441        files: Vec<(&str, PathBuf)>,
442    ) -> Result<Output, Self::Error>
443    where
444        Params: serde::ser::Serialize + std::fmt::Debug,
445        Output: serde::de::DeserializeOwned,
446    {
447        if files.is_empty() {
448            self.request(method_name, Some(params))
449        } else {
450            self.request_with_form_data(method_name, params, files)
451        }
452    }
453
454    fn request<Params, Output>(
455        &self,
456        method: &str,
457        params: Option<Params>,
458    ) -> Result<Output, Self::Error>
459    where
460        Params: serde::ser::Serialize + std::fmt::Debug,
461        Output: serde::de::DeserializeOwned;
462
463    fn request_with_form_data<Params, Output>(
464        &self,
465        method: &str,
466        params: Params,
467        files: Vec<(&str, PathBuf)>,
468    ) -> Result<Output, Self::Error>
469    where
470        Params: serde::ser::Serialize + std::fmt::Debug,
471        Output: serde::de::DeserializeOwned;
472}