rutebot/
requests.rs

1use std::ops::Not;
2
3use hyper::Body;
4use hyper_multipart_rfc7578::client::{multipart, multipart::Form};
5use serde::{Serialize, Serializer};
6use serde_json::Value;
7
8pub use answer_callback_query::*;
9pub use delete_chat_photo::*;
10pub use delete_chat_sticker_set::*;
11pub use delete_message::*;
12pub use edit_live_location::*;
13pub use edit_message_caption::*;
14pub use edit_message_media::*;
15pub use edit_message_reply_markup::*;
16pub use edit_message_text::*;
17pub use export_chat_invite_link::*;
18pub use forward_message::*;
19pub use get_chat::*;
20pub use get_chat_administrators::*;
21pub use get_chat_member::*;
22pub use get_chat_members_count::*;
23pub use get_file::*;
24pub use get_me::*;
25pub use get_updates::*;
26pub use get_user_profile_photos::*;
27pub use kick_chat_member::*;
28pub use leave_chat::*;
29pub use pin_chat_message::*;
30pub use promote_chat_member::*;
31pub use restrict_chat_member::*;
32pub use send_animation::*;
33pub use send_audio::*;
34pub use send_chat_action::*;
35pub use send_contact::*;
36pub use send_document::*;
37pub use send_location::*;
38pub use send_media_group::*;
39pub use send_message::*;
40pub use send_photo::*;
41pub use send_poll::*;
42pub use send_venue::*;
43pub use send_video::*;
44pub use send_video_note::*;
45pub use send_voice::*;
46pub use set_chat_description::*;
47pub use set_chat_photo::*;
48pub use set_chat_sticker_set::*;
49pub use set_chat_title::*;
50pub use stop_live_location::*;
51pub use stop_poll::*;
52pub use unban_chat_member::*;
53pub use unpin_chat_message::*;
54
55use crate::error::Error;
56use std::io::Cursor;
57
58mod answer_callback_query;
59mod delete_chat_photo;
60mod delete_chat_sticker_set;
61mod delete_message;
62mod edit_live_location;
63mod edit_message_caption;
64mod edit_message_media;
65mod edit_message_reply_markup;
66mod edit_message_text;
67mod export_chat_invite_link;
68mod forward_message;
69mod get_chat;
70mod get_chat_administrators;
71mod get_chat_member;
72mod get_chat_members_count;
73mod get_file;
74mod get_me;
75mod get_updates;
76mod get_user_profile_photos;
77mod kick_chat_member;
78mod leave_chat;
79mod pin_chat_message;
80mod promote_chat_member;
81mod restrict_chat_member;
82mod send_animation;
83mod send_audio;
84mod send_chat_action;
85mod send_contact;
86mod send_document;
87mod send_location;
88mod send_media_group;
89mod send_message;
90mod send_photo;
91mod send_poll;
92mod send_venue;
93mod send_video;
94mod send_video_note;
95mod send_voice;
96mod set_chat_description;
97mod set_chat_photo;
98mod set_chat_sticker_set;
99mod set_chat_title;
100mod stop_live_location;
101mod stop_poll;
102mod unban_chat_member;
103mod unpin_chat_message;
104
105/// Basic request type.
106pub trait Request: Serialize + Sized {
107    type ResponseType;
108
109    fn method(&self) -> &'static str;
110
111    fn set_http_request_body(
112        self,
113        request_builder: hyper::http::request::Builder,
114    ) -> Result<hyper::http::request::Request<Body>, Error> {
115        add_json_body(request_builder, &self)
116    }
117}
118
119pub(crate) fn add_json_body<S: Serialize + Sized>(
120    request_builder: hyper::http::request::Builder,
121    serializable: &S,
122) -> Result<hyper::http::request::Request<Body>, Error> {
123    let json_bytes = serde_json::to_vec(serializable).map_err(Error::Serde)?;
124    request_builder
125        .header("content-type", "application/json")
126        .body(Body::from(json_bytes))
127        .map_err(|x| Error::RequestBuilt(x.to_string()))
128}
129
130pub(crate) fn add_form_body(
131    request_builder: hyper::http::request::Builder,
132    form: Form<'static>,
133) -> Result<hyper::http::request::Request<Body>, Error> {
134    form.set_body_convert::<hyper::Body, multipart::Body>(request_builder)
135        .map_err(|x| Error::RequestBuilt(x.to_string()))
136}
137
138pub(crate) fn add_file_to_form(form: &mut Form, file: FileKind, upload_type: Option<&str>) {
139    if let FileKind::InputFile {
140        name,
141        content,
142        thumb,
143    } = file
144    {
145        form.add_reader_file(upload_type.unwrap_or(name), Cursor::new(content), name);
146        if let Some(thumb) = thumb {
147            let thumb_name = format!("thumb_{}", name);
148            form.add_reader_file(&thumb_name, Cursor::new(thumb), thumb_name.as_str());
149            form.add_text("thumb", format!("attach://{}", &thumb_name));
150        }
151    }
152}
153
154pub(crate) fn add_fields_to_form<S: Serialize + Sized>(
155    form: &mut Form<'static>,
156    serializable: &S,
157) -> Result<(), Error> {
158    let json = serde_json::to_value(serializable).map_err(Error::Serde)?;
159    if let Value::Object(map) = json {
160        for (k, v) in map {
161            match v {
162                Value::String(s) => form.add_text(k, s),
163                other => form.add_text(k, other.to_string()),
164            }
165        }
166    }
167    Ok(())
168}
169
170/// File to send
171#[derive(Serialize, Debug, Clone)]
172#[serde(untagged)]
173pub enum FileKind<'a> {
174    /// Identifier of file on the telegram servers
175    FileId(&'a str),
176
177    /// Http url for the file to be sent. Telegram will download and send the file.
178    /// 5 MB max size for photos and 20 MB max for other types of content
179    Url(&'a str),
180
181    /// Arbitrary file to be uploaded
182    #[serde(serialize_with = "FileKind::serialize_attach")]
183    InputFile {
184        /// Name of the file
185        name: &'a str,
186
187        /// File content
188        content: Vec<u8>,
189
190        /// Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side.
191        /// The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail‘s width and height should not exceed 320
192        thumb: Option<Vec<u8>>,
193    },
194}
195
196impl<'a> FileKind<'a> {
197    pub(crate) fn is_input_file(&self) -> bool {
198        matches!(self, FileKind::InputFile { .. })
199    }
200
201    pub(crate) fn serialize_attach<S: Serializer>(
202        field0: &str,
203        _: &[u8],
204        _: &Option<Vec<u8>>,
205        s: S,
206    ) -> Result<S::Ok, S::Error> {
207        s.serialize_str(&format!("attach://{}", field0))
208    }
209}
210
211/// Unique identifier for the target group or username of the target supergroup or channel (in the format @channelusername)
212#[derive(Serialize, Debug, Clone)]
213#[serde(untagged)]
214pub enum ChatId<'a> {
215    /// Unique identifier for the target group
216    Id(i64),
217    /// Username of the target supergroup or channel (in the format @channelusername)
218    Username(&'a str),
219}
220
221impl<'a> From<i64> for ChatId<'a> {
222    fn from(x: i64) -> Self {
223        ChatId::Id(x)
224    }
225}
226
227impl<'a> From<&'a str> for ChatId<'a> {
228    fn from(x: &'a str) -> Self {
229        ChatId::Username(x)
230    }
231}
232
233/// Represents a photo to be sent.
234#[derive(Serialize, Debug, Clone)]
235pub struct InputMediaPhoto<'a> {
236    /// File to send
237    pub media: FileKind<'a>,
238
239    /// Caption of the photo to be sent, 0-1024 characters
240    #[serde(skip_serializing_if = "Option::is_none")]
241    pub caption: Option<&'a str>,
242
243    /// Send `ParseMode::Markdown` or `ParseMode::Html`,
244    /// if you want Telegram apps to show
245    /// [bold, italic, fixed-width text or inline URLs](https://core.telegram.org/bots/api#formatting-options) in the media caption.
246    #[serde(skip_serializing_if = "Option::is_none")]
247    pub parse_mode: Option<ParseMode>,
248}
249
250impl<'a> InputMediaPhoto<'a> {
251    pub fn new(media: FileKind<'a>) -> Self {
252        Self {
253            media,
254            caption: None,
255            parse_mode: None,
256        }
257    }
258}
259
260/// Represents a video to be sent.
261#[derive(Serialize, Debug, Clone)]
262pub struct InputMediaVideo<'a> {
263    /// File to send
264    pub media: FileKind<'a>,
265
266    /// Caption of the photo to be sent, 0-1024 characters
267    #[serde(skip_serializing_if = "Option::is_none")]
268    pub caption: Option<&'a str>,
269
270    /// Send `ParseMode::Markdown` or `ParseMode::Html`,
271    /// if you want Telegram apps to show
272    /// [bold, italic, fixed-width text or inline URLs](https://core.telegram.org/bots/api#formatting-options) in the media caption.
273    #[serde(skip_serializing_if = "Option::is_none")]
274    pub parse_mode: Option<ParseMode>,
275
276    /// Duration of sent video in seconds
277    #[serde(skip_serializing_if = "Option::is_none")]
278    pub duration: Option<i64>,
279
280    /// Video width
281    #[serde(skip_serializing_if = "Option::is_none")]
282    pub width: Option<i64>,
283
284    /// Video height
285    #[serde(skip_serializing_if = "Option::is_none")]
286    pub height: Option<i64>,
287
288    /// Pass True, if the uploaded video is suitable for streaming.
289    #[serde(skip_serializing_if = "Not::not")]
290    pub supports_streaming: bool,
291}
292
293impl<'a> InputMediaVideo<'a> {
294    pub fn new(media: FileKind<'a>) -> Self {
295        Self {
296            media,
297            caption: None,
298            parse_mode: None,
299            duration: None,
300            width: None,
301            height: None,
302            supports_streaming: false,
303        }
304    }
305}
306
307/// Represents an animation file (GIF or H.264/MPEG-4 AVC video without sound) to be sent.
308#[derive(Serialize, Debug, Clone)]
309pub struct InputMediaAnimation<'a> {
310    /// File to send
311    pub media: FileKind<'a>,
312
313    /// Caption of the animation to be sent, 0-1024 characters
314    #[serde(skip_serializing_if = "Option::is_none")]
315    pub caption: Option<&'a str>,
316
317    /// Send `ParseMode::Markdown` or `ParseMode::Html`,
318    /// if you want Telegram apps to show
319    /// [bold, italic, fixed-width text or inline URLs](https://core.telegram.org/bots/api#formatting-options) in the media caption.
320    #[serde(skip_serializing_if = "Option::is_none")]
321    pub parse_mode: Option<ParseMode>,
322
323    /// Animation duration
324    #[serde(skip_serializing_if = "Option::is_none")]
325    pub duration: Option<i64>,
326
327    /// Animation width
328    #[serde(skip_serializing_if = "Option::is_none")]
329    pub width: Option<i64>,
330
331    /// Animation height
332    #[serde(skip_serializing_if = "Option::is_none")]
333    pub height: Option<i64>,
334}
335
336impl<'a> InputMediaAnimation<'a> {
337    pub fn new(media: FileKind<'a>) -> Self {
338        Self {
339            media,
340            caption: None,
341            parse_mode: None,
342            duration: None,
343            width: None,
344            height: None,
345        }
346    }
347}
348
349/// Represents a general file to be sent.
350#[derive(Serialize, Debug, Clone)]
351pub struct InputMediaDocument<'a> {
352    /// File to send
353    pub media: FileKind<'a>,
354
355    /// Caption of the document to be sent, 0-1024 characters
356    #[serde(skip_serializing_if = "Option::is_none")]
357    pub caption: Option<&'a str>,
358
359    /// Send `ParseMode::Markdown` or `ParseMode::Html`,
360    /// if you want Telegram apps to show
361    /// [bold, italic, fixed-width text or inline URLs](https://core.telegram.org/bots/api#formatting-options) in the media caption.
362    #[serde(skip_serializing_if = "Option::is_none")]
363    pub parse_mode: Option<ParseMode>,
364}
365
366impl<'a> InputMediaDocument<'a> {
367    pub fn new(media: FileKind<'a>) -> Self {
368        Self {
369            media,
370            caption: None,
371            parse_mode: None,
372        }
373    }
374}
375
376/// Represents an audio file to be treated as music to be sent.
377#[derive(Serialize, Debug, Clone)]
378pub struct InputMediaAudio<'a> {
379    /// File to send
380    pub media: FileKind<'a>,
381
382    /// Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side.
383    /// The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail‘s width and height should not exceed 320
384    #[serde(skip_serializing)]
385    pub thumb: Option<Vec<u8>>,
386
387    /// Caption of the audio to be sent, 0-1024 characters
388    #[serde(skip_serializing_if = "Option::is_none")]
389    pub caption: Option<&'a str>,
390
391    /// Send `ParseMode::Markdown` or `ParseMode::Html`,
392    /// if you want Telegram apps to show
393    /// [bold, italic, fixed-width text or inline URLs](https://core.telegram.org/bots/api#formatting-options) in the media caption.
394    #[serde(skip_serializing_if = "Option::is_none")]
395    pub parse_mode: Option<ParseMode>,
396
397    /// Duration of the audio in seconds
398    #[serde(skip_serializing_if = "Option::is_none")]
399    pub duration: Option<i64>,
400
401    /// Performer of the audio
402    #[serde(skip_serializing_if = "Option::is_none")]
403    pub performer: Option<&'a str>,
404
405    /// Title of the audio
406    #[serde(skip_serializing_if = "Option::is_none")]
407    pub title: Option<&'a str>,
408}
409
410impl<'a> InputMediaAudio<'a> {
411    pub fn new(media: FileKind<'a>) -> Self {
412        Self {
413            media,
414            thumb: None,
415            caption: None,
416            parse_mode: None,
417            duration: None,
418            performer: None,
419            title: None,
420        }
421    }
422}
423
424#[derive(Serialize, Debug, Clone)]
425#[serde(tag = "type")]
426pub enum InputMedia<'a> {
427    #[serde(rename = "video")]
428    Video(InputMediaVideo<'a>),
429
430    #[serde(rename = "photo")]
431    Photo(InputMediaPhoto<'a>),
432
433    #[serde(rename = "animation")]
434    Animation(InputMediaAnimation<'a>),
435
436    #[serde(rename = "document")]
437    Document(InputMediaDocument<'a>),
438
439    #[serde(rename = "audio")]
440    Audio(InputMediaAudio<'a>),
441}
442
443impl<'a> InputMedia<'a> {
444    fn get_file(self) -> FileKind<'a> {
445        match self {
446            InputMedia::Photo(x) => x.media,
447            InputMedia::Video(x) => x.media,
448            InputMedia::Animation(x) => x.media,
449            InputMedia::Document(x) => x.media,
450            InputMedia::Audio(x) => x.media,
451        }
452    }
453
454    fn contains_input_file(&self) -> bool {
455        match &self {
456            InputMedia::Video(x) => x.media.is_input_file(),
457            InputMedia::Photo(x) => x.media.is_input_file(),
458            InputMedia::Animation(x) => x.media.is_input_file(),
459            InputMedia::Document(x) => x.media.is_input_file(),
460            InputMedia::Audio(x) => x.media.is_input_file(),
461        }
462    }
463}
464
465/// Additional interface options
466#[derive(Serialize, Debug, Clone)]
467#[serde(untagged)]
468pub enum ReplyMarkup<'a> {
469    InlineKeyboard(InlineKeyboard<'a>),
470    ReplyKeyboardMarkup(ReplyKeyboardMarkup<'a>),
471    ReplyKeyboardRemove(ReplyKeyboardRemove),
472    ForceReply(ForceReply),
473}
474
475/// This object represents an [inline keyboard](https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating)
476/// that appears right next to the message it belongs to
477#[derive(Serialize, Debug, Clone)]
478pub struct InlineKeyboard<'a> {
479    /// Array of button rows, each represented by an Array of `InlineKeyboardButton` objects
480    pub inline_keyboard: &'a [Vec<InlineKeyboardButton<'a>>],
481}
482
483/// This object represents a custom keyboard with reply options.
484#[derive(Serialize, Debug, Clone)]
485pub struct ReplyKeyboardMarkup<'a> {
486    /// Array of button rows, each represented by an Array of `KeyboardButton` objects
487    pub keyboard: &'a [Vec<KeyboardButton<'a>>],
488
489    /// Requests clients to resize the keyboard vertically for optimal fit
490    /// (e.g., make the keyboard smaller if there are just two rows of buttons).
491    /// Defaults to false, in which case the custom keyboard is always of the same height as the app's standard keyboard
492    #[serde(skip_serializing_if = "Not::not")]
493    pub resize_keyboard: bool,
494
495    /// Requests clients to hide the keyboard as soon as it's been used.
496    /// The keyboard will still be available, but clients will automatically display the usual
497    /// letter-keyboard in the chat – the user can press a special button in the input field to
498    /// see the custom keyboard again. Defaults to false
499    #[serde(skip_serializing_if = "Not::not")]
500    pub one_time_keyboard: bool,
501    /// Use this parameter if you want to show the keyboard to specific users only.
502    /// Targets: 1) users that are @mentioned in the text of the Message object; 2)
503    /// if the bot's message is a reply (has `reply_to_message_id`), sender of the original message.
504
505    /// Example: A user requests to change the bot‘s language, bot replies to the request with a
506    /// keyboard to select the new language. Other users in the group don’t see the keyboard
507    #[serde(skip_serializing_if = "Not::not")]
508    pub selective: bool,
509}
510
511/// Upon receiving a message with this object, Telegram clients will remove the current custom keyboard
512/// and display the default letter-keyboard. By default, custom keyboards are displayed until a
513/// new keyboard is sent by a bot. An exception is made for one-time keyboards that are hidden
514/// immediately after the user presses a button (see `ReplyKeyboardMarkup`)
515#[derive(Serialize, Debug, Clone)]
516pub struct ReplyKeyboardRemove {
517    /// Requests clients to remove the custom keyboard
518    /// (user will not be able to summon this keyboard; if you want to hide the keyboard from sight but keep it accessible,
519    /// use `one_time_keyboard` in `ReplyKeyboardMarkup`)
520    #[serde(skip_serializing_if = "Not::not")]
521    pub remove_keyboard: bool,
522
523    /// Use this parameter if you want to remove the keyboard for specific users only.
524    /// Targets: 1) users that are @mentioned in the text of the Message object; 2) if the bot's
525    /// message is a reply (has `reply_to_message_id`), sender of the original message.
526    ///
527    /// Example: A user votes in a poll, bot returns confirmation message in reply
528    /// to the vote and removes the keyboard for that user, while still showing the keyboard
529    /// with poll options to users who haven't voted yet
530    #[serde(skip_serializing_if = "Not::not")]
531    pub selective: bool,
532}
533
534/// Upon receiving a message with this object, Telegram clients will display a reply interface
535/// to the user (act as if the user has selected the bot‘s message and tapped ’Reply').
536/// This can be extremely useful if you want to create user-friendly step-by-step interfaces
537/// without having to sacrifice [privacy mode](https://core.telegram.org/bots#privacy-mode).
538#[derive(Serialize, Debug, Clone)]
539pub struct ForceReply {
540    /// Shows reply interface to the user, as if they manually selected the bot‘s message and tapped ’Reply'
541    #[serde(skip_serializing_if = "Not::not")]
542    pub force_reply: bool,
543    /// Use this parameter if you want to force reply from specific users only.
544    /// Targets: 1) users that are @mentioned in the text of the Message object; 2)
545    /// if the bot's message is a reply (has `reply_to_message_id`), sender of the original message
546    #[serde(skip_serializing_if = "Not::not")]
547    pub selective: bool,
548}
549
550#[derive(Serialize, Debug, Clone)]
551#[serde(untagged)]
552pub enum InlineKeyboardButton<'a> {
553    Url {
554        /// Label text on the button
555        text: &'a str,
556        /// HTTP or tg:// url to be opened when button is pressed
557        url: &'a str,
558    },
559    CallbackData {
560        /// Label text on the button
561        text: &'a str,
562        /// Data to be sent in a [callback query](https://core.telegram.org/bots/api#callbackquery)
563        /// to the bot when button is pressed, 1-64 bytes
564        callback_data: &'a str,
565    },
566}
567
568/// This object represents one button of the reply keyboard
569#[derive(Serialize, Debug, Clone)]
570#[serde(untagged)]
571pub enum KeyboardButton<'a> {
572    /// Text of the button. It will be sent as a message when the button is pressed
573    Text(&'a str),
574}
575
576#[derive(Serialize, Debug, Clone, Copy)]
577pub enum ParseMode {
578    Html,
579    Markdown,
580}
581
582#[derive(Serialize, Debug, Clone)]
583#[serde(untagged)]
584pub enum MessageOrInlineMessageId<'a> {
585    Inline {
586        inline_message_id: &'a str,
587    },
588    Chat {
589        chat_id: ChatId<'a>,
590        message_id: i64,
591    },
592}