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
93pub 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#[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#[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#[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#[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
244pub 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#[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
280pub 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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
580#[serde(untagged)]
581pub enum MenuButton {
582 Typed(MenuButtonKind),
583 Other(Value),
584}
585
586#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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}