1use {
2 crate::{Multipart, Result, PAYLOAD_LINK},
3 bytes::{Bytes, BytesMut},
4 reqwest::{multipart::Part, Body},
5 serde::{
6 de::{DeserializeOwned, Error, Unexpected},
7 Deserialize, Deserializer, Serialize, Serializer,
8 },
9 shrimple_telegram_proc_macro::telegram_type,
10 std::{
11 borrow::Cow,
12 fmt::{Debug, Formatter},
13 io,
14 mem::{replace, take},
15 num::NonZero,
16 sync::Mutex,
17 },
18 tokio::io::{AsyncRead, AsyncReadExt},
19 tokio_util::io::ReaderStream,
20};
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
24pub struct True;
25
26impl<'de> Deserialize<'de> for True {
27 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
28 if !bool::deserialize(d)? {
29 return Err(D::Error::invalid_value(Unexpected::Bool(false), &"`true`"));
30 }
31 Ok(Self)
32 }
33}
34
35impl Serialize for True {
36 fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
37 true.serialize(s)
38 }
39}
40
41#[telegram_type]
42pub struct BotCommand<'src> {
43 pub command: Cow<'src, str>,
44 pub description: Cow<'src, str>,
45}
46
47pub type UpdateId = u64;
48
49#[telegram_type(copy, no_doc)]
50pub enum AllowedUpdate {
51 CallbackQuery,
52 ChannelPost,
53 ChatBoost,
54 ChatJoinRequest,
55 ChatMember,
56 ChosenInlineResult,
57 EditedChannelPost,
58 EditedMessage,
59 InlineQuery,
60 Message,
61 MessageReaction,
62 MessageReactionCount,
63 MyChatMember,
64 Poll,
65 PollAnswer,
66 PreCheckoutQuery,
67 RemovedChatBoost,
68 ShippingQuery,
69}
70
71#[telegram_type(no_eq)]
72pub struct Update {
73 #[serde(rename = "update_id")]
74 pub id: u64,
75 #[serde(flatten)]
76 pub kind: UpdateKind,
77}
78
79impl Update {
80 pub fn from(&self) -> Option<&User> {
81 self.kind.from()
82 }
83
84 pub fn chat(&self) -> Option<&Chat> {
85 self.kind.chat()
86 }
87}
88
89#[telegram_type(no_doc, no_eq, common_fields {
90 #[optional] from: User,
91 #[optional] chat: Chat,
92})]
93pub enum UpdateKind {
94 #[telegram_type(nested_fields(from = &field0.from))]
95 CallbackQuery(CallbackQuery),
96 #[telegram_type(nested_fields(from = field0.from.as_ref()?, chat = &field0.chat))]
97 ChannelPost(Message),
98 #[telegram_type(nested_fields(chat = &field0.chat))]
99 ChatBoost(ChatBoostUpdated),
100 #[telegram_type(nested_fields(from = field0.from.as_ref()?, chat = &field0.chat))]
101 Message(Message),
102 #[serde(other)]
103 Other,
104}
105
106pub type MessageId = NonZero<i32>;
107
108#[telegram_type(no_eq)]
109pub struct Message {
110 #[serde(rename = "message_id")]
111 pub id: MessageId,
112 pub from: Option<User>,
113 pub chat: Chat,
114 pub date: Date,
115 #[serde(flatten)]
116 pub kind: MessageKind,
117}
118
119#[telegram_type(untagged, no_eq, no_doc)]
120pub enum MessageKind {
121 Common(MessageCommon),
122}
123
124#[telegram_type(no_eq, no_doc)]
125pub struct MessageCommon {
126 pub from: Option<User>,
127 pub sender_chat: Option<Chat>,
128 pub reply_to_message: Option<Box<Message>>,
129 #[serde(flatten)]
130 pub media: Media,
131}
132
133#[telegram_type(untagged, no_eq, no_doc)]
134pub enum Media {
135 Text {
136 text: Box<str>,
137 #[serde(default)]
138 entities: Box<[MessageEntity]>,
139 },
140 #[telegram_type(name_all(audio))]
141 Audio(File),
142 #[telegram_type(name_all(video))]
143 Video(File),
144 #[telegram_type(name_all(photo))]
145 Photo(Vec<File>),
146 #[telegram_type(name_all(document))]
147 Document(File),
148 #[telegram_type(name_all(location))]
149 Location(Location),
150}
151
152#[telegram_type]
153pub struct MessageEntity {
154 pub length: usize,
155 pub offset: usize,
156 #[serde(flatten)]
157 pub kind: MessageEntityKind,
158}
159
160#[telegram_type(no_doc)]
161#[serde(tag = "type")]
162pub enum MessageEntityKind {
163 BotCommand,
164 #[serde(other)]
165 Other,
166}
167
168#[telegram_type]
169pub struct File {
170 #[serde(rename = "file_id")]
171 pub id: Box<str>,
172}
173
174pub type UserId = i64;
175
176#[telegram_type]
177pub struct User {
178 pub id: UserId,
179 pub is_bot: bool,
180 pub first_name: Box<str>,
181 pub last_name: Option<Box<str>>,
182 pub username: Option<Box<str>>,
183 pub language_code: Option<Box<str>>,
184}
185
186impl User {
187 pub fn full_name(&self) -> String {
188 format!("{} {}", self.first_name, self.last_name.as_deref().unwrap_or_default())
189 }
190
191 pub fn username_or_full_name(&self) -> String {
192 self.username.as_deref().map_or_else(|| self.full_name(), |x| format!("@{x}"))
193 }
194}
195
196pub type ChatId = i64;
197
198#[telegram_type]
199pub struct Chat {
200 pub id: ChatId,
201 pub username: Option<Box<str>>,
202 #[serde(flatten)]
203 pub kind: ChatKind,
204}
205
206#[telegram_type(no_doc)]
207#[serde(tag = "type")]
208pub enum ChatKind {
209 Private {
210 first_name: Box<str>,
211 last_name: Option<Box<str>>,
212 },
213 Group {
214 title: Box<str>,
215 },
216 Supergroup {
217 title: Box<str>,
218 #[serde(default)]
219 is_forum: bool,
220 },
221 Channel {
222 title: Box<str>,
223 },
224}
225
226#[telegram_type(untagged, no_doc)]
227pub enum ReplyMarkup<'src> {
228 Keyboard(KeyboardMarkup<'src>),
229 InlineKeyboard(InlineKeyboardMarkup<'src>),
230 #[telegram_type(phantom_fields { remove_keyboard: True })]
231 Remove,
232 ForceReply(ForceReply<'src>),
233 #[serde(serialize_with = "Serializer::serialize_none")]
234 None,
235}
236
237#[telegram_type(name_all(keyboard))]
238pub struct KeyboardMarkup<'src>(pub Vec<Vec<KeyboardButton<'src>>>);
239
240impl<'src, A: IntoIterator<Item = KeyboardButton<'src>>> FromIterator<A> for KeyboardMarkup<'src> {
241 fn from_iter<T: IntoIterator<Item = A>>(iter: T) -> Self {
242 Self(Vec::from_iter(iter.into_iter().map(Vec::from_iter)))
243 }
244}
245
246impl<'src> KeyboardMarkup<'src> {
247 pub fn from_rows(iter: impl IntoIterator<Item = KeyboardButton<'src>>) -> Self {
249 Self(iter.into_iter().map(|x| vec![x]).collect())
250 }
251}
252
253#[telegram_type(name_all(inline_keyboard))]
254#[derive(Default)]
255pub struct InlineKeyboardMarkup<'src>(pub Vec<Vec<InlineKeyboardButton<'src>>>);
256
257impl<'src, A: IntoIterator<Item = InlineKeyboardButton<'src>>> FromIterator<A> for InlineKeyboardMarkup<'src> {
258 fn from_iter<T: IntoIterator<Item = A>>(iter: T) -> Self {
259 Self(Vec::from_iter(iter.into_iter().map(Vec::from_iter)))
260 }
261}
262
263impl<'src> InlineKeyboardMarkup<'src> {
264 pub fn from_rows(iter: impl IntoIterator<Item = InlineKeyboardButton<'src>>) -> Self {
266 Self(iter.into_iter().map(|x| vec![x]).collect())
267 }
268}
269
270#[telegram_type(phantom_fields { force_reply: True })]
271#[derive(Default)]
272pub struct ForceReply<'src> {
273 pub input_field_placeholder: Option<Cow<'src, str>>,
274}
275
276#[telegram_type]
277pub struct KeyboardButton<'src> {
278 pub text: Cow<'src, str>,
279}
280
281impl<'src, T: Into<Cow<'src, str>>> From<T> for KeyboardButton<'src> {
282 fn from(value: T) -> Self {
283 Self { text: value.into() }
284 }
285}
286
287#[telegram_type]
288pub struct InlineKeyboardButton<'src> {
289 pub text: Cow<'src, str>,
290 #[serde(skip_serializing_if = "str::is_empty")]
291 pub callback_data: Cow<'src, str>,
292 #[serde(skip_serializing_if = "str::is_empty")]
293 pub url: Cow<'src, str>,
294}
295
296#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
297pub enum ParseMode {
298 #[serde(rename = "MarkdownV2")]
299 Markdown,
300 #[serde(rename = "HTML")]
301 Html,
302}
303
304#[telegram_type(no_eq)]
305pub struct CallbackQuery {
306 pub id: Box<str>,
307 pub from: User,
308 pub data: Box<str>,
309 #[serde(flatten)]
310 pub message: Option<CallbackQueryMessage>,
311}
312
313#[telegram_type(no_eq, untagged, no_doc)]
315pub enum CallbackQueryMessage {
316 Message {
317 message: Message,
318 },
319 InlineMessage {
320 #[serde(rename = "inline_message_id")]
321 id: Box<str>,
322 },
323}
324
325#[telegram_type]
326pub struct ChatBoostUpdated {
327 pub chat: Chat,
328 pub boost: ChatBoost,
329}
330
331#[telegram_type]
332pub struct ChatBoost {
333 #[serde(rename = "boost_id")]
334 pub id: Box<str>,
335 pub add_date: Date,
336 pub expiration_date: Date,
337 pub source: ChatBoostSource,
338}
339
340#[telegram_type]
341#[serde(tag = "source")]
342pub enum ChatBoostSource {
343 GiftCode {
344 user: User,
345 },
346 Giveaway {
347 user: User,
348 #[serde(deserialize_with = "deserialize_via_try_into::<i32, _, _>")]
349 giveaway_message_id: Option<MessageId>,
350 #[serde(default)]
351 prize_star_count: u32,
352 #[serde(default)]
353 is_unclaimed: bool,
354 },
355 Premium {
356 user: User,
357 },
358}
359
360#[telegram_type(copy, no_doc)]
361#[serde(transparent)]
362pub struct Date(pub i64);
365
366#[derive(Debug, Clone, PartialEq, Serialize)]
367#[serde(transparent)]
368pub struct InputFile<'src> {
369 kind: InputFileKind<'src>,
370 #[serde(skip_serializing)]
371 file_name: Option<Cow<'src, str>>,
372}
373
374impl Multipart for InputFile<'_> {
375 fn get_payload(&self) -> Option<Part> {
376 match &self.kind {
377 InputFileKind::UrlOrFileId(_) => None,
378 InputFileKind::Payload(payload) => {
379 let bytes = payload.get_shared();
380 let bytes_len = bytes.len();
381 let mut res = Part::stream_with_length(bytes, bytes_len as u64);
382 if let Some(filename) = self.file_name.as_deref() {
383 res = res.file_name(filename.to_owned());
384 }
385 Some(res)
386 }
387 }
388 }
389
390 fn get_payload_mut(&mut self) -> Option<Part> {
391 match &mut self.kind {
392 InputFileKind::UrlOrFileId(_) => None,
393 InputFileKind::Payload(payload) => {
394 let mut res =
395 match replace(payload, Payload(Mutex::new(PayloadRepr::Owned(vec![]))))
396 .into_body()
397 {
398 (Some(len), body) => Part::stream_with_length(body, len as u64),
399 (None, body) => Part::stream(body),
400 };
401 if let Some(filename) = self.file_name.take() {
402 res = res.file_name(filename.into_owned());
403 }
404 Some(res)
405 }
406 }
407 }
408}
409
410impl<'src> InputFile<'src> {
411 pub fn url(url: impl Into<Cow<'src, str>>) -> Self {
413 Self { kind: InputFileKind::UrlOrFileId(url.into()), file_name: None }
414 }
415
416 pub fn file_id(file_id: impl Into<Cow<'src, str>>) -> Self {
418 Self { kind: InputFileKind::UrlOrFileId(file_id.into()), file_name: None }
419 }
420
421 pub fn memory(vec: Vec<u8>) -> Self {
424 Self { kind: InputFileKind::Payload(PayloadRepr::Owned(vec).into()), file_name: None }
425 }
426
427 pub fn shared_memory(bytes: Bytes) -> Self {
429 Self { kind: InputFileKind::Payload(PayloadRepr::Shared(bytes).into()), file_name: None }
430 }
431
432 pub fn reader(reader: impl AsyncRead + Send + Sync + Unpin + 'static) -> Self {
434 Self {
435 kind: InputFileKind::Payload(PayloadRepr::Reader(Box::new(reader)).into()),
436 file_name: None,
437 }
438 }
439
440 pub fn text(text: impl Into<Cow<'static, str>>) -> Self {
442 match text.into() {
443 Cow::Owned(text) => Self::memory(text.into_bytes()),
444 Cow::Borrowed(text) => Self::shared_memory(Bytes::from_static(text.as_bytes())),
445 }
446 }
447
448 pub fn file_name(self, file_name: impl Into<Cow<'src, str>>) -> Self {
450 Self { file_name: Some(file_name.into()), ..self }
451 }
452
453 pub async fn reusable(self) -> Result<Self> {
460 Ok(match self.kind {
461 InputFileKind::UrlOrFileId(_) => self,
462 InputFileKind::Payload(p) => Self {
463 kind: InputFileKind::Payload(p.0.into_inner().unwrap().reusable().await?.into()),
464 ..self
465 },
466 })
467 }
468}
469
470#[derive(Debug, Clone, PartialEq, Serialize)]
471#[serde(untagged)]
472enum InputFileKind<'src> {
473 UrlOrFileId(Cow<'src, str>),
474 #[serde(serialize_with = "serialize_payload")]
475 Payload(Payload),
476}
477
478fn serialize_payload<S: Serializer>(_: &'_ Payload, s: S) -> Result<S::Ok, S::Error> {
479 s.serialize_str(PAYLOAD_LINK)
480}
481
482struct Payload(Mutex<PayloadRepr>);
483
484impl From<PayloadRepr> for Payload {
485 fn from(repr: PayloadRepr) -> Self {
486 Self(Mutex::new(repr))
487 }
488}
489
490impl PartialEq for Payload {
491 fn eq(&self, other: &Self) -> bool {
492 let (Ok(lhs), Ok(rhs)) = (self.0.try_lock(), other.0.try_lock()) else {
493 return false;
494 };
495 match (&*lhs, &*rhs) {
496 (PayloadRepr::Shared(b1), PayloadRepr::Shared(b2)) => b1 == b2,
497 (PayloadRepr::Owned(v1), PayloadRepr::Owned(v2)) => v1 == v2,
498 _ => false,
499 }
500 }
501}
502
503impl Debug for Payload {
504 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
505 f.debug_struct("Payload").finish_non_exhaustive()
506 }
507}
508
509impl Clone for Payload {
510 fn clone(&self) -> Self {
511 Self(Mutex::new(PayloadRepr::Shared(self.0.lock().unwrap().make_shared())))
512 }
513}
514
515impl From<Payload> for Body {
516 fn from(payload: Payload) -> Self {
517 match payload.0.into_inner().unwrap() {
518 PayloadRepr::Shared(bytes) => bytes.into(),
519 PayloadRepr::Owned(vec) => vec.into(),
520 PayloadRepr::Reader(reader) => Body::wrap_stream(ReaderStream::new(reader)),
521 }
522 }
523}
524
525impl Payload {
526 fn get_shared(&self) -> Bytes {
527 self.0.lock().unwrap().make_shared()
528 }
529
530 fn into_body(self) -> (Option<usize>, Body) {
532 match self.0.into_inner().unwrap() {
533 PayloadRepr::Shared(bytes) => (bytes.len().into(), bytes.into()),
534 PayloadRepr::Owned(vec) => (vec.len().into(), vec.into()),
535 PayloadRepr::Reader(reader) => (None, Body::wrap_stream(ReaderStream::new(reader))),
536 }
537 }
538}
539
540enum PayloadRepr {
541 Shared(Bytes),
542 Owned(Vec<u8>),
543 Reader(Box<dyn AsyncRead + Send + Sync + Unpin>),
544}
545
546impl PayloadRepr {
547 fn make_shared(&mut self) -> Bytes {
550 match self {
551 PayloadRepr::Shared(b) => b.clone(),
552 PayloadRepr::Owned(vec) => {
553 let bytes = Bytes::from(take(vec));
554 *self = PayloadRepr::Shared(bytes.clone());
555 bytes
556 }
557 PayloadRepr::Reader(_) => {
558 panic!("PayloadRepr::make_shared: can't extract bytes from an async reader")
559 }
560 }
561 }
562
563 async fn reusable(self) -> io::Result<Self> {
564 Ok(Self::Shared(match self {
565 PayloadRepr::Shared(bytes) => bytes,
566 PayloadRepr::Owned(vec) => vec.into(),
567 PayloadRepr::Reader(mut reader) => {
568 let mut buf = BytesMut::new();
569 reader.read_buf(&mut buf).await?;
570 buf.freeze()
571 }
572 }))
573 }
574}
575
576#[telegram_type(no_eq, copy)]
577pub struct Location {
578 pub latitude: f64,
579 pub longitude: f64,
580 #[serde(default, skip_serializing_if = "Option::is_none")]
581 pub horizontal_accuracy: Option<f64>,
582 #[serde(default, skip_serializing_if = "Option::is_none")]
583 pub live_period: Option<u32>,
584 #[serde(default, skip_serializing_if = "Option::is_none")]
585 pub heading: Option<u16>,
586 #[serde(default, skip_serializing_if = "Option::is_none")]
587 pub proximity_alert_radius: Option<u32>,
588}
589
590fn deserialize_via_try_into<'de, Medium, Out, D>(d: D) -> Result<Option<Out>, D::Error>
591where
592 D: Deserializer<'de>,
593 Medium: DeserializeOwned + TryInto<Out>,
594{
595 Medium::deserialize(d).map(|x| x.try_into().ok())
596}