Skip to main content

tele/types/
telegram.rs

1use std::collections::BTreeMap;
2use std::fmt::{Display, Write as _};
3use std::str::FromStr;
4
5use serde::de::DeserializeOwned;
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8
9use crate::types::common::{ChatId, MessageId, ParseMode};
10use crate::types::message::MessageEntity;
11use crate::{Error, Result};
12
13pub const MAX_CALLBACK_DATA_BYTES: usize = 64;
14
15fn invalid_request(reason: impl Into<String>) -> Error {
16    Error::InvalidRequest {
17        reason: reason.into(),
18    }
19}
20
21fn validate_callback_data(data: impl Into<String>) -> Result<String> {
22    let data = data.into();
23    if data.trim().is_empty() {
24        return Err(invalid_request("callback_data cannot be empty"));
25    }
26    if data.len() > MAX_CALLBACK_DATA_BYTES {
27        return Err(invalid_request(format!(
28            "callback_data exceeds Telegram's 64-byte limit ({})",
29            data.len()
30        )));
31    }
32    Ok(data)
33}
34
35fn is_compact_callback_safe(byte: u8) -> bool {
36    matches!(
37        byte,
38        b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~'
39    )
40}
41
42fn encode_compact_callback_segment(segment: &str) -> String {
43    let mut encoded = String::with_capacity(segment.len());
44    for byte in segment.as_bytes() {
45        if is_compact_callback_safe(*byte) {
46            encoded.push(*byte as char);
47        } else {
48            let _ = write!(&mut encoded, "%{byte:02X}");
49        }
50    }
51    encoded
52}
53
54fn decode_hex_digit(byte: u8) -> Option<u8> {
55    match byte {
56        b'0'..=b'9' => Some(byte - b'0'),
57        b'a'..=b'f' => Some(byte - b'a' + 10),
58        b'A'..=b'F' => Some(byte - b'A' + 10),
59        _ => None,
60    }
61}
62
63fn decode_compact_callback_segment(segment: &str) -> Result<String> {
64    let mut bytes = Vec::with_capacity(segment.len());
65    let raw = segment.as_bytes();
66    let mut index = 0;
67    while index < raw.len() {
68        if raw[index] == b'%' {
69            let hi = *raw
70                .get(index + 1)
71                .ok_or_else(|| invalid_request("compact callback segment has truncated escape"))?;
72            let lo = *raw
73                .get(index + 2)
74                .ok_or_else(|| invalid_request("compact callback segment has truncated escape"))?;
75            let hi = decode_hex_digit(hi).ok_or_else(|| {
76                invalid_request("compact callback segment contains invalid escape")
77            })?;
78            let lo = decode_hex_digit(lo).ok_or_else(|| {
79                invalid_request("compact callback segment contains invalid escape")
80            })?;
81            bytes.push((hi << 4) | lo);
82            index += 3;
83        } else {
84            bytes.push(raw[index]);
85            index += 1;
86        }
87    }
88
89    String::from_utf8(bytes)
90        .map_err(|_| invalid_request("compact callback segment is not valid UTF-8"))
91}
92
93/// Pluggable callback payload codec for inline keyboard buttons and callback routers.
94pub trait CallbackCodec<T>: Send + Sync + 'static {
95    fn encode_callback_data(payload: &T) -> Result<String>;
96    fn decode_callback_data(data: &str) -> Result<T>;
97}
98
99/// Adapter codec for payload types that implement [`CallbackPayload`].
100#[derive(Clone, Copy, Debug, Default)]
101pub struct CallbackPayloadCodec;
102
103impl<T> CallbackCodec<T> for CallbackPayloadCodec
104where
105    T: CallbackPayload,
106{
107    fn encode_callback_data(payload: &T) -> Result<String> {
108        payload.encode_callback_data()
109    }
110
111    fn decode_callback_data(data: &str) -> Result<T> {
112        T::decode_callback_data(data)
113    }
114}
115
116/// JSON callback codec for serde-serializable payloads.
117#[derive(Clone, Copy, Debug, Default)]
118pub struct JsonCallbackCodec;
119
120impl<T> CallbackCodec<T> for JsonCallbackCodec
121where
122    T: Serialize + DeserializeOwned,
123{
124    fn encode_callback_data(payload: &T) -> Result<String> {
125        let encoded =
126            serde_json::to_string(payload).map_err(|source| Error::SerializeRequest { source })?;
127        validate_callback_data(encoded)
128    }
129
130    fn decode_callback_data(data: &str) -> Result<T> {
131        serde_json::from_str(data).map_err(|source| {
132            invalid_request(format!("failed to decode callback payload: {source}"))
133        })
134    }
135}
136
137/// Builder for compact callback payload strings.
138#[derive(Clone, Debug, Default)]
139pub struct CompactCallbackEncoder {
140    segments: Vec<String>,
141}
142
143impl CompactCallbackEncoder {
144    pub fn new() -> Self {
145        Self::default()
146    }
147
148    pub fn tag(&mut self, tag: impl AsRef<str>) -> Result<&mut Self> {
149        let tag = tag.as_ref().trim();
150        if tag.is_empty() {
151            return Err(invalid_request("compact callback tag cannot be empty"));
152        }
153        self.segments.push(encode_compact_callback_segment(tag));
154        Ok(self)
155    }
156
157    pub fn push(&mut self, value: impl AsRef<str>) -> Result<&mut Self> {
158        self.segments
159            .push(encode_compact_callback_segment(value.as_ref()));
160        Ok(self)
161    }
162
163    pub fn push_display(&mut self, value: impl Display) -> Result<&mut Self> {
164        self.push(value.to_string())
165    }
166
167    pub fn finish(self) -> Result<String> {
168        if self.segments.is_empty() {
169            return Err(invalid_request("compact callback payload cannot be empty"));
170        }
171        validate_callback_data(self.segments.join(":"))
172    }
173}
174
175/// Decoder for compact callback payload strings.
176#[derive(Clone, Debug)]
177pub struct CompactCallbackDecoder {
178    segments: Vec<String>,
179    index: usize,
180}
181
182impl CompactCallbackDecoder {
183    pub fn new(data: &str) -> Result<Self> {
184        if data.is_empty() {
185            return Err(invalid_request("compact callback payload cannot be empty"));
186        }
187        let segments = data
188            .split(':')
189            .map(decode_compact_callback_segment)
190            .collect::<Result<Vec<_>>>()?;
191        Ok(Self { segments, index: 0 })
192    }
193
194    pub fn expect_tag(&mut self, expected: &str) -> Result<&mut Self> {
195        let actual = self.next_string("callback tag")?;
196        if actual == expected {
197            Ok(self)
198        } else {
199            Err(invalid_request(format!(
200                "unexpected compact callback tag `{actual}`, expected `{expected}`"
201            )))
202        }
203    }
204
205    pub fn next_string(&mut self, field: &str) -> Result<String> {
206        let value = self.segments.get(self.index).cloned().ok_or_else(|| {
207            invalid_request(format!(
208                "compact callback payload is missing required field `{field}`"
209            ))
210        })?;
211        self.index += 1;
212        Ok(value)
213    }
214
215    pub fn next_parse<T>(&mut self, field: &str) -> Result<T>
216    where
217        T: FromStr,
218        T::Err: Display,
219    {
220        let raw = self.next_string(field)?;
221        raw.parse().map_err(|source| {
222            invalid_request(format!(
223                "failed to parse compact callback field `{field}`: {source}"
224            ))
225        })
226    }
227
228    pub fn remaining(&self) -> usize {
229        self.segments.len().saturating_sub(self.index)
230    }
231
232    pub fn finish(self) -> Result<()> {
233        if self.remaining() == 0 {
234            Ok(())
235        } else {
236            Err(invalid_request(format!(
237                "compact callback payload has {} unexpected trailing field(s)",
238                self.remaining()
239            )))
240        }
241    }
242}
243
244/// Manual compact callback payload contract for 64-byte-friendly callback data.
245pub trait CompactCallbackPayload: Sized {
246    fn encode_compact(&self, encoder: &mut CompactCallbackEncoder) -> Result<()>;
247    fn decode_compact(decoder: &mut CompactCallbackDecoder) -> Result<Self>;
248
249    fn encode_compact_data(&self) -> Result<String> {
250        let mut encoder = CompactCallbackEncoder::new();
251        self.encode_compact(&mut encoder)?;
252        encoder.finish()
253    }
254
255    fn decode_compact_data(data: &str) -> Result<Self> {
256        let mut decoder = CompactCallbackDecoder::new(data)?;
257        let payload = Self::decode_compact(&mut decoder)?;
258        decoder.finish()?;
259        Ok(payload)
260    }
261}
262
263/// Compact callback codec backed by [`CompactCallbackPayload`].
264#[derive(Clone, Copy, Debug, Default)]
265pub struct CompactCallbackCodec;
266
267impl<T> CallbackCodec<T> for CompactCallbackCodec
268where
269    T: CompactCallbackPayload,
270{
271    fn encode_callback_data(payload: &T) -> Result<String> {
272        payload.encode_compact_data()
273    }
274
275    fn decode_callback_data(data: &str) -> Result<T> {
276        T::decode_compact_data(data)
277    }
278}
279
280/// Strongly-typed callback payload codec for inline keyboard buttons.
281pub trait CallbackPayload: Sized {
282    fn encode_callback_data(&self) -> Result<String>;
283    fn decode_callback_data(data: &str) -> Result<Self>;
284}
285
286impl<T> CallbackPayload for T
287where
288    T: Serialize + DeserializeOwned,
289{
290    fn encode_callback_data(&self) -> Result<String> {
291        JsonCallbackCodec::encode_callback_data(self)
292    }
293
294    fn decode_callback_data(data: &str) -> Result<Self> {
295        JsonCallbackCodec::decode_callback_data(data)
296    }
297}
298
299/// Generic inline query result payload.
300#[derive(Clone, Debug, Default, Serialize, Deserialize)]
301#[serde(transparent)]
302pub struct InlineQueryResult(pub Value);
303
304impl InlineQueryResult {
305    pub fn new(value: Value) -> Self {
306        Self(value)
307    }
308
309    pub fn try_from_typed<T>(value: T) -> std::result::Result<Self, serde_json::Error>
310    where
311        T: Serialize,
312    {
313        serde_json::to_value(value).map(Self)
314    }
315
316    pub fn from_typed<T>(value: T) -> std::result::Result<Self, serde_json::Error>
317    where
318        T: Serialize,
319    {
320        Self::try_from_typed(value)
321    }
322
323    pub fn article(
324        id: impl Into<String>,
325        title: impl Into<String>,
326        message_text: impl Into<String>,
327    ) -> std::result::Result<Self, serde_json::Error> {
328        InlineQueryResult::try_from(InlineQueryResultArticle::new(id, title, message_text))
329    }
330
331    pub fn as_value(&self) -> &Value {
332        &self.0
333    }
334
335    pub fn into_value(self) -> Value {
336        self.0
337    }
338}
339
340impl From<Value> for InlineQueryResult {
341    fn from(value: Value) -> Self {
342        Self(value)
343    }
344}
345
346impl From<InlineQueryResult> for Value {
347    fn from(value: InlineQueryResult) -> Self {
348        value.0
349    }
350}
351
352/// Input text content for inline query article results.
353#[derive(Clone, Debug, Serialize, Deserialize)]
354#[non_exhaustive]
355pub struct InputTextMessageContent {
356    pub message_text: String,
357    #[serde(default, skip_serializing_if = "Option::is_none")]
358    pub parse_mode: Option<ParseMode>,
359    #[serde(default, skip_serializing_if = "Option::is_none")]
360    pub entities: Option<Vec<MessageEntity>>,
361    #[serde(default, skip_serializing_if = "Option::is_none")]
362    pub link_preview_options: Option<LinkPreviewOptions>,
363    #[serde(default, skip_serializing_if = "Option::is_none")]
364    pub disable_web_page_preview: Option<bool>,
365    #[serde(flatten)]
366    pub extra: BTreeMap<String, Value>,
367}
368
369impl InputTextMessageContent {
370    pub fn new(message_text: impl Into<String>) -> Self {
371        Self {
372            message_text: message_text.into(),
373            parse_mode: None,
374            entities: None,
375            link_preview_options: None,
376            disable_web_page_preview: None,
377            extra: BTreeMap::new(),
378        }
379    }
380}
381
382/// Typed inline query article result.
383#[derive(Clone, Debug, Serialize, Deserialize)]
384#[non_exhaustive]
385pub struct InlineQueryResultArticle {
386    #[serde(rename = "type")]
387    pub kind: String,
388    pub id: String,
389    pub title: String,
390    pub input_message_content: InputTextMessageContent,
391    #[serde(default, skip_serializing_if = "Option::is_none")]
392    pub reply_markup: Option<InlineKeyboardMarkup>,
393    #[serde(default, skip_serializing_if = "Option::is_none")]
394    pub url: Option<String>,
395    #[serde(default, skip_serializing_if = "Option::is_none")]
396    pub hide_url: Option<bool>,
397    #[serde(default, skip_serializing_if = "Option::is_none")]
398    pub description: Option<String>,
399    #[serde(default, skip_serializing_if = "Option::is_none")]
400    pub thumbnail_url: Option<String>,
401    #[serde(default, skip_serializing_if = "Option::is_none")]
402    pub thumbnail_width: Option<u32>,
403    #[serde(default, skip_serializing_if = "Option::is_none")]
404    pub thumbnail_height: Option<u32>,
405    #[serde(flatten)]
406    pub extra: BTreeMap<String, Value>,
407}
408
409impl InlineQueryResultArticle {
410    pub fn new(
411        id: impl Into<String>,
412        title: impl Into<String>,
413        message_text: impl Into<String>,
414    ) -> Self {
415        Self {
416            kind: "article".to_owned(),
417            id: id.into(),
418            title: title.into(),
419            input_message_content: InputTextMessageContent::new(message_text),
420            reply_markup: None,
421            url: None,
422            hide_url: None,
423            description: None,
424            thumbnail_url: None,
425            thumbnail_width: None,
426            thumbnail_height: None,
427            extra: BTreeMap::new(),
428        }
429    }
430}
431
432impl TryFrom<InlineQueryResultArticle> for InlineQueryResult {
433    type Error = serde_json::Error;
434
435    fn try_from(value: InlineQueryResultArticle) -> std::result::Result<Self, Self::Error> {
436        Self::try_from_typed(value)
437    }
438}
439
440/// Generic checklist input payload.
441#[derive(Clone, Debug, Default, Serialize, Deserialize)]
442#[serde(transparent)]
443pub struct InputChecklist(pub Value);
444
445impl InputChecklist {
446    pub fn new(value: Value) -> Self {
447        Self(value)
448    }
449}
450
451impl From<Value> for InputChecklist {
452    fn from(value: Value) -> Self {
453        Self(value)
454    }
455}
456
457impl From<InputChecklist> for Value {
458    fn from(value: InputChecklist) -> Self {
459        value.0
460    }
461}
462
463/// Generic story content payload.
464#[derive(Clone, Debug, Default, Serialize, Deserialize)]
465#[serde(transparent)]
466pub struct InputStoryContent(pub Value);
467
468impl InputStoryContent {
469    pub fn new(value: Value) -> Self {
470        Self(value)
471    }
472}
473
474impl From<Value> for InputStoryContent {
475    fn from(value: Value) -> Self {
476        Self(value)
477    }
478}
479
480impl From<InputStoryContent> for Value {
481    fn from(value: InputStoryContent) -> Self {
482        value.0
483    }
484}
485
486/// Generic story area payload.
487#[derive(Clone, Debug, Default, Serialize, Deserialize)]
488#[serde(transparent)]
489pub struct StoryArea(pub Value);
490
491impl StoryArea {
492    pub fn new(value: Value) -> Self {
493        Self(value)
494    }
495}
496
497impl From<Value> for StoryArea {
498    fn from(value: Value) -> Self {
499        Self(value)
500    }
501}
502
503impl From<StoryArea> for Value {
504    fn from(value: StoryArea) -> Self {
505        value.0
506    }
507}
508
509/// Generic paid media item payload.
510#[derive(Clone, Debug, Default, Serialize, Deserialize)]
511#[serde(transparent)]
512pub struct InputPaidMedia(pub Value);
513
514impl InputPaidMedia {
515    pub fn new(value: Value) -> Self {
516        Self(value)
517    }
518}
519
520impl From<Value> for InputPaidMedia {
521    fn from(value: Value) -> Self {
522        Self(value)
523    }
524}
525
526impl From<InputPaidMedia> for Value {
527    fn from(value: InputPaidMedia) -> Self {
528        value.0
529    }
530}
531
532/// Generic suggested-post payload.
533#[derive(Clone, Debug, Default, Serialize, Deserialize)]
534#[serde(transparent)]
535pub struct SuggestedPostParameters(pub Value);
536
537impl SuggestedPostParameters {
538    pub fn new(value: Value) -> Self {
539        Self(value)
540    }
541}
542
543impl From<Value> for SuggestedPostParameters {
544    fn from(value: Value) -> Self {
545        Self(value)
546    }
547}
548
549impl From<SuggestedPostParameters> for Value {
550    fn from(value: SuggestedPostParameters) -> Self {
551        value.0
552    }
553}
554
555/// Generic accepted-gift-types payload.
556#[derive(Clone, Debug, Default, Serialize, Deserialize)]
557#[serde(transparent)]
558pub struct AcceptedGiftTypes(pub Value);
559
560impl AcceptedGiftTypes {
561    pub fn new(value: Value) -> Self {
562        Self(value)
563    }
564}
565
566impl From<Value> for AcceptedGiftTypes {
567    fn from(value: Value) -> Self {
568        Self(value)
569    }
570}
571
572impl From<AcceptedGiftTypes> for Value {
573    fn from(value: AcceptedGiftTypes) -> Self {
574        value.0
575    }
576}
577
578/// Typed menu button union.
579#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
580#[serde(untagged)]
581pub enum MenuButton {
582    Typed(MenuButtonKind),
583    Other(Value),
584}
585
586/// Known menu button variants.
587#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
588#[serde(tag = "type", rename_all = "snake_case")]
589pub enum MenuButtonKind {
590    Commands,
591    Default,
592    WebApp(MenuButtonWebApp),
593}
594
595impl MenuButton {
596    pub fn new(value: Value) -> Self {
597        Self::from(value)
598    }
599
600    pub fn commands() -> Self {
601        Self::Typed(MenuButtonKind::Commands)
602    }
603
604    pub fn default_button() -> Self {
605        Self::Typed(MenuButtonKind::Default)
606    }
607
608    pub fn web_app(text: impl Into<String>, web_app: impl Into<WebAppInfo>) -> Self {
609        Self::Typed(MenuButtonKind::WebApp(MenuButtonWebApp::new(text, web_app)))
610    }
611
612    pub fn as_web_app(&self) -> Option<&MenuButtonWebApp> {
613        match self {
614            Self::Typed(MenuButtonKind::WebApp(value)) => Some(value),
615            Self::Typed(_) | Self::Other(_) => None,
616        }
617    }
618}
619
620impl Default for MenuButton {
621    fn default() -> Self {
622        Self::default_button()
623    }
624}
625
626impl From<Value> for MenuButton {
627    fn from(value: Value) -> Self {
628        match serde_json::from_value::<MenuButtonKind>(value.clone()) {
629            Ok(known) => Self::Typed(known),
630            Err(_error) => Self::Other(value),
631        }
632    }
633}
634
635impl From<MenuButtonKind> for MenuButton {
636    fn from(value: MenuButtonKind) -> Self {
637        Self::Typed(value)
638    }
639}
640
641impl From<MenuButton> for Value {
642    fn from(value: MenuButton) -> Self {
643        match value {
644            MenuButton::Typed(known) => match known {
645                MenuButtonKind::Commands => serde_json::json!({"type": "commands"}),
646                MenuButtonKind::Default => serde_json::json!({"type": "default"}),
647                MenuButtonKind::WebApp(mut value) => {
648                    let mut object = serde_json::Map::new();
649                    let mut web_app = serde_json::Map::new();
650                    web_app.insert("url".to_owned(), Value::String(value.web_app.url));
651                    object.insert("type".to_owned(), Value::String("web_app".to_owned()));
652                    object.insert("text".to_owned(), Value::String(value.text));
653                    object.insert("web_app".to_owned(), Value::Object(web_app));
654                    for (key, extra_value) in std::mem::take(&mut value.extra) {
655                        object.insert(key, extra_value);
656                    }
657                    Value::Object(object)
658                }
659            },
660            MenuButton::Other(value) => value,
661        }
662    }
663}
664
665/// Mini App Web App descriptor.
666#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
667#[non_exhaustive]
668pub struct WebAppInfo {
669    pub url: String,
670}
671
672impl WebAppInfo {
673    pub fn new(url: impl Into<String>) -> Self {
674        Self { url: url.into() }
675    }
676}
677
678impl From<String> for WebAppInfo {
679    fn from(value: String) -> Self {
680        Self::new(value)
681    }
682}
683
684impl From<&str> for WebAppInfo {
685    fn from(value: &str) -> Self {
686        Self::new(value)
687    }
688}
689
690/// Button shown above inline query results.
691#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
692#[non_exhaustive]
693pub struct InlineQueryResultsButton {
694    pub text: String,
695    #[serde(default, skip_serializing_if = "Option::is_none")]
696    pub web_app: Option<WebAppInfo>,
697    #[serde(default, skip_serializing_if = "Option::is_none")]
698    pub start_parameter: Option<String>,
699    #[serde(flatten)]
700    pub extra: BTreeMap<String, Value>,
701}
702
703impl InlineQueryResultsButton {
704    pub fn new(text: impl Into<String>) -> Self {
705        Self {
706            text: text.into(),
707            web_app: None,
708            start_parameter: None,
709            extra: BTreeMap::new(),
710        }
711    }
712
713    pub fn web_app(text: impl Into<String>, web_app: impl Into<WebAppInfo>) -> Self {
714        Self::new(text).with_web_app(web_app)
715    }
716
717    pub fn start_parameter(text: impl Into<String>, start_parameter: impl Into<String>) -> Self {
718        Self::new(text).with_start_parameter(start_parameter)
719    }
720
721    pub fn with_web_app(mut self, web_app: impl Into<WebAppInfo>) -> Self {
722        self.web_app = Some(web_app.into());
723        self.start_parameter = None;
724        self
725    }
726
727    pub fn with_start_parameter(mut self, start_parameter: impl Into<String>) -> Self {
728        self.start_parameter = Some(start_parameter.into());
729        self.web_app = None;
730        self
731    }
732}
733
734/// Menu button launching a Mini App.
735#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
736#[non_exhaustive]
737pub struct MenuButtonWebApp {
738    pub text: String,
739    pub web_app: WebAppInfo,
740    #[serde(flatten)]
741    pub extra: BTreeMap<String, Value>,
742}
743
744impl MenuButtonWebApp {
745    pub fn new(text: impl Into<String>, web_app: impl Into<WebAppInfo>) -> Self {
746        Self {
747            text: text.into(),
748            web_app: web_app.into(),
749            extra: BTreeMap::new(),
750        }
751    }
752}
753
754impl From<MenuButtonWebApp> for MenuButton {
755    fn from(value: MenuButtonWebApp) -> Self {
756        Self::Typed(MenuButtonKind::WebApp(value))
757    }
758}
759
760/// Data sent from Mini App via `Telegram.WebApp.sendData`.
761#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
762#[non_exhaustive]
763pub struct WebAppData {
764    pub data: String,
765    pub button_text: String,
766    #[serde(flatten)]
767    pub extra: BTreeMap<String, Value>,
768}
769
770impl WebAppData {
771    pub fn new(data: impl Into<String>, button_text: impl Into<String>) -> Self {
772        Self {
773            data: data.into(),
774            button_text: button_text.into(),
775            extra: BTreeMap::new(),
776        }
777    }
778}
779
780impl crate::types::advanced::AdvancedSetChatMenuButtonRequest {
781    pub fn chat_id(mut self, chat_id: i64) -> Self {
782        self.chat_id = Some(chat_id);
783        self
784    }
785
786    pub fn menu_button(mut self, menu_button: impl Into<MenuButton>) -> Self {
787        self.menu_button = Some(menu_button.into());
788        self
789    }
790
791    pub fn menu_button_default(mut self) -> Self {
792        self.menu_button = Some(MenuButton::default_button());
793        self
794    }
795
796    pub fn menu_button_commands(mut self) -> Self {
797        self.menu_button = Some(MenuButton::commands());
798        self
799    }
800
801    pub fn menu_button_web_app(
802        mut self,
803        text: impl Into<String>,
804        web_app: impl Into<WebAppInfo>,
805    ) -> Self {
806        self.menu_button = Some(MenuButton::web_app(text, web_app));
807        self
808    }
809}
810
811/// Generic reaction type payload.
812#[derive(Clone, Debug, Default, Serialize, Deserialize)]
813#[serde(transparent)]
814pub struct ReactionType(pub Value);
815
816impl ReactionType {
817    pub fn new(value: Value) -> Self {
818        Self(value)
819    }
820}
821
822impl From<Value> for ReactionType {
823    fn from(value: Value) -> Self {
824        Self(value)
825    }
826}
827
828impl From<ReactionType> for Value {
829    fn from(value: ReactionType) -> Self {
830        value.0
831    }
832}
833
834/// Generic passport element error payload.
835#[derive(Clone, Debug, Default, Serialize, Deserialize)]
836#[serde(transparent)]
837pub struct PassportElementError(pub Value);
838
839impl PassportElementError {
840    pub fn new(value: Value) -> Self {
841        Self(value)
842    }
843}
844
845impl From<Value> for PassportElementError {
846    fn from(value: Value) -> Self {
847        Self(value)
848    }
849}
850
851impl From<PassportElementError> for Value {
852    fn from(value: PassportElementError) -> Self {
853        value.0
854    }
855}
856
857/// Inline keyboard button.
858#[derive(Clone, Debug, Serialize, Deserialize)]
859#[non_exhaustive]
860pub struct InlineKeyboardButton {
861    pub text: String,
862    #[serde(default, skip_serializing_if = "Option::is_none")]
863    pub web_app: Option<WebAppInfo>,
864    #[serde(flatten)]
865    pub extra: BTreeMap<String, Value>,
866}
867
868impl InlineKeyboardButton {
869    pub fn new(text: impl Into<String>) -> Self {
870        Self {
871            text: text.into(),
872            web_app: None,
873            extra: BTreeMap::new(),
874        }
875    }
876
877    pub fn callback(text: impl Into<String>, data: impl Into<String>) -> Result<Self> {
878        Self::new(text).with_callback_data(data)
879    }
880
881    pub fn typed_callback<T>(text: impl Into<String>, payload: &T) -> Result<Self>
882    where
883        T: CallbackPayload,
884    {
885        Self::new(text).with_typed_callback(payload)
886    }
887
888    pub fn typed_callback_with_codec<T, C>(text: impl Into<String>, payload: &T) -> Result<Self>
889    where
890        C: CallbackCodec<T>,
891    {
892        Self::new(text).with_typed_callback_with_codec::<T, C>(payload)
893    }
894
895    pub fn compact_callback<T>(text: impl Into<String>, payload: &T) -> Result<Self>
896    where
897        T: CompactCallbackPayload,
898    {
899        Self::typed_callback_with_codec::<T, CompactCallbackCodec>(text, payload)
900    }
901
902    pub fn web_app(mut self, web_app: impl Into<WebAppInfo>) -> Self {
903        self.web_app = Some(web_app.into());
904        self
905    }
906
907    pub fn with_callback_data(mut self, data: impl Into<String>) -> Result<Self> {
908        self.extra.insert(
909            "callback_data".to_owned(),
910            Value::String(validate_callback_data(data)?),
911        );
912        Ok(self)
913    }
914
915    pub fn with_typed_callback<T>(self, payload: &T) -> Result<Self>
916    where
917        T: CallbackPayload,
918    {
919        self.with_callback_data(payload.encode_callback_data()?)
920    }
921
922    pub fn with_typed_callback_with_codec<T, C>(self, payload: &T) -> Result<Self>
923    where
924        C: CallbackCodec<T>,
925    {
926        self.with_callback_data(C::encode_callback_data(payload)?)
927    }
928
929    pub fn with_compact_callback<T>(self, payload: &T) -> Result<Self>
930    where
931        T: CompactCallbackPayload,
932    {
933        self.with_typed_callback_with_codec::<T, CompactCallbackCodec>(payload)
934    }
935
936    pub fn callback_data(&self) -> Option<&str> {
937        self.extra.get("callback_data").and_then(Value::as_str)
938    }
939
940    pub fn decode_callback<T>(&self) -> Result<Option<T>>
941    where
942        T: CallbackPayload,
943    {
944        self.callback_data()
945            .map(T::decode_callback_data)
946            .transpose()
947    }
948
949    pub fn decode_callback_with_codec<T, C>(&self) -> Result<Option<T>>
950    where
951        C: CallbackCodec<T>,
952    {
953        self.callback_data()
954            .map(C::decode_callback_data)
955            .transpose()
956    }
957
958    pub fn decode_compact_callback<T>(&self) -> Result<Option<T>>
959    where
960        T: CompactCallbackPayload,
961    {
962        self.decode_callback_with_codec::<T, CompactCallbackCodec>()
963    }
964}
965
966/// Inline keyboard markup.
967#[derive(Clone, Debug, Serialize, Deserialize)]
968#[non_exhaustive]
969pub struct InlineKeyboardMarkup {
970    pub inline_keyboard: Vec<Vec<InlineKeyboardButton>>,
971    #[serde(flatten)]
972    pub extra: BTreeMap<String, Value>,
973}
974
975impl InlineKeyboardMarkup {
976    pub fn new(inline_keyboard: Vec<Vec<InlineKeyboardButton>>) -> Self {
977        Self {
978            inline_keyboard,
979            extra: BTreeMap::new(),
980        }
981    }
982
983    pub fn single_row(row: Vec<InlineKeyboardButton>) -> Self {
984        Self::new(vec![row])
985    }
986
987    pub fn push_row(mut self, row: Vec<InlineKeyboardButton>) -> Self {
988        self.inline_keyboard.push(row);
989        self
990    }
991}
992
993/// Reply keyboard button.
994#[derive(Clone, Debug, Serialize, Deserialize)]
995#[non_exhaustive]
996pub struct KeyboardButton {
997    pub text: String,
998    #[serde(default, skip_serializing_if = "Option::is_none")]
999    pub web_app: Option<WebAppInfo>,
1000    #[serde(flatten)]
1001    pub extra: BTreeMap<String, Value>,
1002}
1003
1004impl KeyboardButton {
1005    pub fn new(text: impl Into<String>) -> Self {
1006        Self {
1007            text: text.into(),
1008            web_app: None,
1009            extra: BTreeMap::new(),
1010        }
1011    }
1012
1013    pub fn web_app(mut self, web_app: impl Into<WebAppInfo>) -> Self {
1014        self.web_app = Some(web_app.into());
1015        self
1016    }
1017}
1018
1019/// Reply keyboard markup.
1020#[derive(Clone, Debug, Serialize, Deserialize)]
1021#[non_exhaustive]
1022pub struct ReplyKeyboardMarkup {
1023    pub keyboard: Vec<Vec<KeyboardButton>>,
1024    #[serde(default, skip_serializing_if = "Option::is_none")]
1025    pub is_persistent: Option<bool>,
1026    #[serde(default, skip_serializing_if = "Option::is_none")]
1027    pub resize_keyboard: Option<bool>,
1028    #[serde(default, skip_serializing_if = "Option::is_none")]
1029    pub one_time_keyboard: Option<bool>,
1030    #[serde(default, skip_serializing_if = "Option::is_none")]
1031    pub input_field_placeholder: Option<String>,
1032    #[serde(default, skip_serializing_if = "Option::is_none")]
1033    pub selective: Option<bool>,
1034    #[serde(flatten)]
1035    pub extra: BTreeMap<String, Value>,
1036}
1037
1038impl ReplyKeyboardMarkup {
1039    pub fn new(keyboard: Vec<Vec<KeyboardButton>>) -> Self {
1040        Self {
1041            keyboard,
1042            is_persistent: None,
1043            resize_keyboard: None,
1044            one_time_keyboard: None,
1045            input_field_placeholder: None,
1046            selective: None,
1047            extra: BTreeMap::new(),
1048        }
1049    }
1050}
1051
1052/// Remove reply keyboard marker.
1053#[derive(Clone, Debug, Serialize, Deserialize)]
1054#[non_exhaustive]
1055pub struct ReplyKeyboardRemove {
1056    pub remove_keyboard: bool,
1057    #[serde(default, skip_serializing_if = "Option::is_none")]
1058    pub selective: Option<bool>,
1059    #[serde(flatten)]
1060    pub extra: BTreeMap<String, Value>,
1061}
1062
1063impl Default for ReplyKeyboardRemove {
1064    fn default() -> Self {
1065        Self {
1066            remove_keyboard: true,
1067            selective: None,
1068            extra: BTreeMap::new(),
1069        }
1070    }
1071}
1072
1073/// Force reply marker.
1074#[derive(Clone, Debug, Serialize, Deserialize)]
1075#[non_exhaustive]
1076pub struct ForceReply {
1077    pub force_reply: bool,
1078    #[serde(default, skip_serializing_if = "Option::is_none")]
1079    pub input_field_placeholder: Option<String>,
1080    #[serde(default, skip_serializing_if = "Option::is_none")]
1081    pub selective: Option<bool>,
1082    #[serde(flatten)]
1083    pub extra: BTreeMap<String, Value>,
1084}
1085
1086impl Default for ForceReply {
1087    fn default() -> Self {
1088        Self {
1089            force_reply: true,
1090            input_field_placeholder: None,
1091            selective: None,
1092            extra: BTreeMap::new(),
1093        }
1094    }
1095}
1096
1097/// Reply markup union accepted by Telegram send/edit methods.
1098#[derive(Clone, Debug, Serialize, Deserialize)]
1099#[serde(untagged)]
1100pub enum ReplyMarkup {
1101    InlineKeyboardMarkup(InlineKeyboardMarkup),
1102    ReplyKeyboardMarkup(ReplyKeyboardMarkup),
1103    ReplyKeyboardRemove(ReplyKeyboardRemove),
1104    ForceReply(ForceReply),
1105}
1106
1107impl From<InlineKeyboardMarkup> for ReplyMarkup {
1108    fn from(value: InlineKeyboardMarkup) -> Self {
1109        Self::InlineKeyboardMarkup(value)
1110    }
1111}
1112
1113impl From<ReplyKeyboardMarkup> for ReplyMarkup {
1114    fn from(value: ReplyKeyboardMarkup) -> Self {
1115        Self::ReplyKeyboardMarkup(value)
1116    }
1117}
1118
1119impl From<ReplyKeyboardRemove> for ReplyMarkup {
1120    fn from(value: ReplyKeyboardRemove) -> Self {
1121        Self::ReplyKeyboardRemove(value)
1122    }
1123}
1124
1125impl From<ForceReply> for ReplyMarkup {
1126    fn from(value: ForceReply) -> Self {
1127        Self::ForceReply(value)
1128    }
1129}
1130
1131/// Reply-to reference parameters.
1132#[derive(Clone, Debug, Serialize, Deserialize)]
1133#[non_exhaustive]
1134pub struct ReplyParameters {
1135    pub message_id: MessageId,
1136    #[serde(default, skip_serializing_if = "Option::is_none")]
1137    pub chat_id: Option<ChatId>,
1138    #[serde(default, skip_serializing_if = "Option::is_none")]
1139    pub allow_sending_without_reply: Option<bool>,
1140    #[serde(default, skip_serializing_if = "Option::is_none")]
1141    pub quote: Option<String>,
1142    #[serde(default, skip_serializing_if = "Option::is_none")]
1143    pub quote_parse_mode: Option<ParseMode>,
1144    #[serde(default, skip_serializing_if = "Option::is_none")]
1145    pub quote_entities: Option<Vec<MessageEntity>>,
1146    #[serde(default, skip_serializing_if = "Option::is_none")]
1147    pub quote_position: Option<u32>,
1148    #[serde(flatten)]
1149    pub extra: BTreeMap<String, Value>,
1150}
1151
1152impl ReplyParameters {
1153    pub fn new(message_id: MessageId) -> Self {
1154        Self {
1155            message_id,
1156            chat_id: None,
1157            allow_sending_without_reply: None,
1158            quote: None,
1159            quote_parse_mode: None,
1160            quote_entities: None,
1161            quote_position: None,
1162            extra: BTreeMap::new(),
1163        }
1164    }
1165}
1166
1167/// Link preview options for text messages.
1168#[derive(Clone, Debug, Serialize, Deserialize)]
1169#[non_exhaustive]
1170pub struct LinkPreviewOptions {
1171    #[serde(default, skip_serializing_if = "Option::is_none")]
1172    pub is_disabled: Option<bool>,
1173    #[serde(default, skip_serializing_if = "Option::is_none")]
1174    pub url: Option<String>,
1175    #[serde(default, skip_serializing_if = "Option::is_none")]
1176    pub prefer_small_media: Option<bool>,
1177    #[serde(default, skip_serializing_if = "Option::is_none")]
1178    pub prefer_large_media: Option<bool>,
1179    #[serde(default, skip_serializing_if = "Option::is_none")]
1180    pub show_above_text: Option<bool>,
1181    #[serde(flatten)]
1182    pub extra: BTreeMap<String, Value>,
1183}
1184
1185impl LinkPreviewOptions {
1186    pub fn new() -> Self {
1187        Self {
1188            is_disabled: None,
1189            url: None,
1190            prefer_small_media: None,
1191            prefer_large_media: None,
1192            show_above_text: None,
1193            extra: BTreeMap::new(),
1194        }
1195    }
1196
1197    pub fn disabled() -> Self {
1198        let mut options = Self::new();
1199        options.is_disabled = Some(true);
1200        options
1201    }
1202}
1203
1204impl Default for LinkPreviewOptions {
1205    fn default() -> Self {
1206        Self::new()
1207    }
1208}