1use crate::types::photo_sizes::{PhotoSize, VecExt};
9use chrono::{DateTime, Utc};
10use grammers_tl_types as tl;
11use std::fmt::Debug;
12
13use super::Downloadable;
14
15#[derive(Clone, Debug, PartialEq)]
16pub struct Photo {
17 pub raw: tl::types::MessageMediaPhoto,
18}
19
20#[derive(Clone, Debug, PartialEq)]
21pub struct Document {
22 pub raw: tl::types::MessageMediaDocument,
23}
24
25#[derive(Clone, Debug, PartialEq)]
26pub struct Sticker {
27 pub document: Document,
28 pub raw_attrs: tl::types::DocumentAttributeSticker,
29 animated: bool,
30}
31
32#[derive(Clone, Debug, PartialEq)]
33pub struct Uploaded {
34 pub raw: tl::enums::InputFile,
35}
36
37#[derive(Clone, Debug, PartialEq)]
38pub struct Contact {
39 pub raw: tl::types::MessageMediaContact,
40}
41
42#[derive(Clone, Debug, PartialEq)]
43pub struct Poll {
44 pub raw: tl::types::Poll,
45 pub raw_results: tl::types::PollResults,
46}
47
48#[derive(Clone, Debug, PartialEq)]
49pub struct Geo {
50 pub raw: tl::types::GeoPoint,
51}
52
53#[derive(Clone, Debug, PartialEq)]
54pub struct Dice {
55 pub raw: tl::types::MessageMediaDice,
56}
57
58#[derive(Clone, Debug, PartialEq)]
59pub struct Venue {
60 pub geo: Option<Geo>,
61 pub raw_venue: tl::types::MessageMediaVenue,
62}
63
64#[derive(Clone, Debug, PartialEq)]
65pub struct GeoLive {
66 pub geo: Option<Geo>,
67 pub raw_geolive: tl::types::MessageMediaGeoLive,
68}
69
70#[derive(Clone, Debug, PartialEq)]
71pub struct WebPage {
72 pub raw: tl::types::MessageMediaWebPage,
73}
74
75#[derive(Clone, Debug, PartialEq)]
77pub struct ChatPhoto {
78 pub raw: tl::enums::InputFileLocation,
79}
80
81#[derive(Clone, Debug, PartialEq)]
82#[non_exhaustive]
83pub enum Media {
84 Photo(Photo),
85 Document(Document),
86 Sticker(Sticker),
87 Contact(Contact),
88 Poll(Poll),
89 Geo(Geo),
90 Dice(Dice),
91 Venue(Venue),
92 GeoLive(GeoLive),
93 WebPage(WebPage),
94}
95
96impl Photo {
97 pub fn from_raw(photo: tl::enums::Photo) -> Self {
98 Self {
99 raw: tl::types::MessageMediaPhoto {
100 spoiler: false,
101 photo: Some(photo),
102 ttl_seconds: None,
103 },
104 }
105 }
106
107 pub fn from_raw_media(photo: tl::types::MessageMediaPhoto) -> Self {
108 Self { raw: photo }
109 }
110
111 pub fn to_raw_input_media(&self) -> tl::types::InputMediaPhoto {
112 use tl::{
113 enums::{InputPhoto as eInputPhoto, Photo},
114 types::InputPhoto,
115 };
116
117 tl::types::InputMediaPhoto {
118 spoiler: false,
119 id: match self.raw.photo {
120 Some(Photo::Photo(ref photo)) => InputPhoto {
121 id: photo.id,
122 access_hash: photo.access_hash,
123 file_reference: photo.file_reference.clone(),
124 }
125 .into(),
126 _ => eInputPhoto::Empty,
127 },
128 ttl_seconds: self.raw.ttl_seconds,
129 }
130 }
131
132 pub fn id(&self) -> i64 {
133 use tl::enums::Photo as P;
134
135 match self.raw.photo.as_ref().unwrap() {
136 P::Empty(photo) => photo.id,
137 P::Photo(photo) => photo.id,
138 }
139 }
140
141 pub fn size(&self) -> i64 {
144 match self.thumbs().largest() {
145 Some(thumb) => thumb.size() as i64,
146 None => 0,
147 }
148 }
149
150 pub fn thumbs(&self) -> Vec<PhotoSize> {
161 use tl::enums::Photo as P;
162
163 let photo = match self.raw.photo.as_ref() {
164 Some(photo) => photo,
165 None => return vec![],
166 };
167
168 match photo {
169 P::Empty(_) => vec![],
170 P::Photo(photo) => photo
171 .sizes
172 .iter()
173 .map(|x| PhotoSize::make_from(x, photo))
174 .collect(),
175 }
176 }
177
178 pub fn is_spoiler(&self) -> bool {
180 self.raw.spoiler
181 }
182
183 pub fn ttl_seconds(&self) -> Option<i32> {
185 self.raw.ttl_seconds
186 }
187}
188
189impl Downloadable for Photo {
190 fn to_raw_input_location(&self) -> Option<tl::enums::InputFileLocation> {
191 use tl::enums::Photo as P;
192
193 self.raw.photo.as_ref().and_then(|p| match p {
194 P::Empty(_) => None,
195 P::Photo(photo) => Some(
196 tl::types::InputPhotoFileLocation {
197 id: photo.id,
198 access_hash: photo.access_hash,
199 file_reference: photo.file_reference.clone(),
200 thumb_size: self
201 .thumbs()
202 .largest()
203 .map(|ps| ps.photo_type())
204 .unwrap_or(String::from("w")),
205 }
206 .into(),
207 ),
208 })
209 }
210}
211
212impl Document {
213 pub fn from_raw_media(document: tl::types::MessageMediaDocument) -> Self {
214 Self { raw: document }
215 }
216
217 pub fn to_raw_input_media(&self) -> tl::types::InputMediaDocument {
218 use tl::{
219 enums::{Document, InputDocument as eInputDocument},
220 types::InputDocument,
221 };
222
223 tl::types::InputMediaDocument {
224 spoiler: false,
225 id: match self.raw.document {
226 Some(Document::Document(ref document)) => InputDocument {
227 id: document.id,
228 access_hash: document.access_hash,
229 file_reference: document.file_reference.clone(),
230 }
231 .into(),
232 _ => eInputDocument::Empty,
233 },
234 ttl_seconds: self.raw.ttl_seconds,
235 query: None,
236 video_cover: None,
237 video_timestamp: None,
238 }
239 }
240
241 pub fn id(&self) -> i64 {
242 use tl::enums::Document as D;
243
244 match self.raw.document.as_ref().unwrap() {
245 D::Empty(document) => document.id,
246 D::Document(document) => document.id,
247 }
248 }
249
250 pub fn name(&self) -> &str {
254 use tl::enums::Document as D;
255
256 match self.raw.document.as_ref().unwrap() {
257 D::Empty(_) => "",
258 D::Document(document) => document
259 .attributes
260 .iter()
261 .find_map(|attr| match attr {
262 tl::enums::DocumentAttribute::Filename(attr) => Some(attr.file_name.as_ref()),
263 _ => None,
264 })
265 .unwrap_or(""),
266 }
267 }
268
269 pub fn mime_type(&self) -> Option<&str> {
271 match self.raw.document.as_ref() {
272 Some(tl::enums::Document::Document(d)) => Some(d.mime_type.as_str()),
273 _ => None,
274 }
275 }
276
277 pub fn creation_date(&self) -> Option<DateTime<Utc>> {
279 match self.raw.document.as_ref() {
280 Some(tl::enums::Document::Document(d)) => {
281 Some(DateTime::<Utc>::from_timestamp(d.date as i64, 0).expect("date out of range"))
282 }
283 _ => None,
284 }
285 }
286
287 pub fn size(&self) -> i64 {
290 match self.raw.document.as_ref() {
291 Some(tl::enums::Document::Document(d)) => d.size,
292 _ => 0,
293 }
294 }
295
296 pub fn thumbs(&self) -> Vec<PhotoSize> {
299 use tl::enums::Document as D;
300
301 let document = match self.raw.document.as_ref() {
302 Some(document) => document,
303 None => return vec![],
304 };
305
306 match document {
307 D::Empty(_) => vec![],
308 D::Document(document) => match &document.thumbs {
309 Some(thumbs) => thumbs
310 .iter()
311 .map(|x| PhotoSize::make_from_document(x, document))
312 .collect(),
313 None => vec![],
314 },
315 }
316 }
317
318 pub fn duration(&self) -> Option<f64> {
320 match self.raw.document.as_ref() {
321 Some(tl::enums::Document::Document(d)) => {
322 for attr in &d.attributes {
323 match attr {
324 tl::enums::DocumentAttribute::Video(v) => return Some(v.duration),
325 tl::enums::DocumentAttribute::Audio(a) => return Some(a.duration as _),
326 _ => {}
327 }
328 }
329 None
330 }
331 _ => None,
332 }
333 }
334
335 pub fn resolution(&self) -> Option<(i32, i32)> {
337 match self.raw.document.as_ref() {
338 Some(tl::enums::Document::Document(d)) => {
339 for attr in &d.attributes {
340 match attr {
341 tl::enums::DocumentAttribute::Video(v) => return Some((v.w, v.h)),
342 tl::enums::DocumentAttribute::ImageSize(i) => return Some((i.w, i.h)),
343 _ => {}
344 }
345 }
346 None
347 }
348 _ => None,
349 }
350 }
351
352 pub fn audio_title(&self) -> Option<String> {
354 match self.raw.document.as_ref() {
355 Some(tl::enums::Document::Document(d)) => {
356 for attr in &d.attributes {
357 #[allow(clippy::single_match)]
358 match attr {
359 tl::enums::DocumentAttribute::Audio(a) => return a.title.clone(),
360 _ => {}
361 }
362 }
363 None
364 }
365 _ => None,
366 }
367 }
368
369 pub fn performer(&self) -> Option<String> {
371 match self.raw.document.as_ref() {
372 Some(tl::enums::Document::Document(d)) => {
373 for attr in &d.attributes {
374 #[allow(clippy::single_match)]
375 match attr {
376 tl::enums::DocumentAttribute::Audio(a) => return a.performer.clone(),
377 _ => {}
378 }
379 }
380 None
381 }
382 _ => None,
383 }
384 }
385
386 pub fn is_animated(&self) -> bool {
388 match self.raw.document.as_ref() {
389 Some(tl::enums::Document::Document(d)) => {
390 for attr in &d.attributes {
391 #[allow(clippy::single_match)]
392 match attr {
393 tl::enums::DocumentAttribute::Animated => return true,
394 _ => {}
395 }
396 }
397 false
398 }
399 _ => false,
400 }
401 }
402
403 pub fn is_spoiler(&self) -> bool {
405 self.raw.spoiler
406 }
407}
408
409impl Downloadable for Document {
410 fn to_raw_input_location(&self) -> Option<tl::enums::InputFileLocation> {
411 use tl::enums::Document as D;
412
413 self.raw.document.as_ref().and_then(|p| match p {
414 D::Empty(_) => None,
415 D::Document(document) => Some(
416 tl::types::InputDocumentFileLocation {
417 id: document.id,
418 access_hash: document.access_hash,
419 file_reference: document.file_reference.clone(),
420 thumb_size: String::new(),
421 }
422 .into(),
423 ),
424 })
425 }
426
427 fn size(&self) -> Option<usize> {
428 Some(self.size() as usize)
429 }
430}
431
432impl Sticker {
433 pub fn from_document(document: &Document) -> Option<Self> {
434 match document.raw.document {
435 Some(tl::enums::Document::Document(ref doc)) => {
436 let mut animated = false;
437 let mut sticker_attrs: Option<tl::types::DocumentAttributeSticker> = None;
438 for attr in &doc.attributes {
439 match attr {
440 tl::enums::DocumentAttribute::Sticker(s) => sticker_attrs = Some(s.clone()),
441 tl::enums::DocumentAttribute::Animated => animated = true,
442 _ => (),
443 }
444 }
445 Some(Self {
446 document: document.clone(),
447 raw_attrs: sticker_attrs?,
448 animated,
449 })
450 }
451 _ => None,
452 }
453 }
454
455 pub fn emoji(&self) -> &str {
457 self.raw_attrs.alt.as_str()
458 }
459
460 pub fn is_animated(&self) -> bool {
462 self.animated
463 }
464}
465
466impl Contact {
467 pub fn from_raw_media(contact: tl::types::MessageMediaContact) -> Self {
468 Self { raw: contact }
469 }
470
471 pub fn to_raw_input_media(&self) -> tl::types::InputMediaContact {
472 tl::types::InputMediaContact {
473 phone_number: self.raw.phone_number.clone(),
474 first_name: self.raw.first_name.clone(),
475 last_name: self.raw.last_name.clone(),
476 vcard: self.raw.vcard.clone(),
477 }
478 }
479
480 pub fn phone_number(&self) -> &str {
483 self.raw.phone_number.as_str()
484 }
485
486 pub fn first_name(&self) -> &str {
489 self.raw.first_name.as_str()
490 }
491
492 pub fn last_name(&self) -> &str {
494 self.raw.last_name.as_str()
495 }
496
497 pub fn vcard(&self) -> &str {
503 self.raw.vcard.as_str()
504 }
505}
506
507impl Poll {
508 pub fn from_raw_media(poll: tl::types::MessageMediaPoll) -> Self {
509 Self {
510 raw: match poll.poll {
511 tl::enums::Poll::Poll(poll) => poll,
512 },
513 raw_results: match poll.results {
514 tl::enums::PollResults::Results(results) => results,
515 },
516 }
517 }
518
519 pub fn to_raw_input_media(&self) -> tl::types::InputMediaPoll {
520 tl::types::InputMediaPoll {
521 poll: grammers_tl_types::enums::Poll::Poll(self.raw.clone()),
522 correct_answers: None,
523 solution: None,
524 solution_entities: None,
525 }
526 }
527
528 pub fn question(&self) -> &grammers_tl_types::enums::TextWithEntities {
530 &self.raw.question
531 }
532
533 pub fn is_quiz(&self) -> bool {
535 self.raw.quiz
536 }
537
538 pub fn closed(&self) -> bool {
540 self.raw.closed
541 }
542
543 pub fn iter_answers(&self) -> impl Iterator<Item = &tl::types::PollAnswer> {
545 self.raw.answers.iter().map(|answer| match answer {
546 tl::enums::PollAnswer::Answer(answer) => answer,
547 })
548 }
549
550 pub fn total_voters(&self) -> Option<i32> {
554 self.raw_results.total_voters
555 }
556
557 pub fn iter_voters_summary(
560 &self,
561 ) -> Option<impl Iterator<Item = &tl::types::PollAnswerVoters>> {
562 self.raw_results.results.as_ref().map(|results| {
563 results.iter().map(|result| match result {
564 tl::enums::PollAnswerVoters::Voters(voters) => voters,
565 })
566 })
567 }
568}
569
570impl Geo {
571 pub fn from_raw_media(geo: tl::types::MessageMediaGeo) -> Option<Self> {
572 use tl::enums::GeoPoint as eGeoPoint;
573
574 match &geo.geo {
575 eGeoPoint::Empty => None,
576 eGeoPoint::Point(point) => Some(Self { raw: point.clone() }),
577 }
578 }
579
580 pub fn to_raw_input_media(&self) -> tl::types::InputMediaGeoPoint {
581 use tl::types::InputGeoPoint;
582
583 tl::types::InputMediaGeoPoint {
584 geo_point: InputGeoPoint {
585 lat: self.raw.lat,
586 long: self.raw.long,
587 accuracy_radius: self.raw.accuracy_radius,
588 }
589 .into(),
590 }
591 }
592
593 pub fn to_raw_input_geo_point(&self) -> tl::enums::InputGeoPoint {
594 use tl::{enums::InputGeoPoint as eInputGeoPoint, types::InputGeoPoint};
595
596 eInputGeoPoint::Point(InputGeoPoint {
597 lat: self.raw.lat,
598 long: self.raw.long,
599 accuracy_radius: self.raw.accuracy_radius,
600 })
601 }
602
603 pub fn latitue(&self) -> f64 {
605 self.raw.lat
606 }
607
608 pub fn longitude(&self) -> f64 {
610 self.raw.long
611 }
612
613 pub fn accuracy_radius(&self) -> Option<i32> {
615 self.raw.accuracy_radius
616 }
617}
618
619impl Dice {
620 pub fn from_raw_media(dice: tl::types::MessageMediaDice) -> Self {
621 Self { raw: dice }
622 }
623
624 pub fn to_raw_input_media(&self) -> tl::types::InputMediaDice {
625 tl::types::InputMediaDice {
626 emoticon: self.raw.emoticon.clone(),
627 }
628 }
629
630 pub fn emoji(&self) -> &str {
632 &self.raw.emoticon
633 }
634
635 pub fn value(&self) -> i32 {
637 self.raw.value
638 }
639}
640
641impl Venue {
642 pub fn from_raw_media(venue: tl::types::MessageMediaVenue) -> Self {
643 use tl::types::MessageMediaGeo;
644 Self {
645 geo: Geo::from_raw_media(MessageMediaGeo {
646 geo: venue.geo.clone(),
647 }),
648 raw_venue: venue,
649 }
650 }
651
652 pub fn to_raw_input_media(&self) -> tl::types::InputMediaVenue {
653 tl::types::InputMediaVenue {
654 geo_point: match self.geo {
655 Some(ref geo) => geo.to_raw_input_geo_point(),
656 None => tl::enums::InputGeoPoint::Empty,
657 },
658 title: self.raw_venue.title.clone(),
659 address: self.raw_venue.address.clone(),
660 provider: self.raw_venue.provider.clone(),
661 venue_id: self.raw_venue.venue_id.clone(),
662 venue_type: self.raw_venue.venue_type.clone(),
663 }
664 }
665
666 pub fn title(&self) -> &str {
668 &self.raw_venue.title
669 }
670
671 pub fn address(&self) -> &str {
673 &self.raw_venue.address
674 }
675
676 pub fn provider(&self) -> &str {
678 &self.raw_venue.provider
679 }
680
681 pub fn venue_id(&self) -> &str {
683 &self.raw_venue.venue_id
684 }
685
686 pub fn venue_type(&self) -> &str {
688 &self.raw_venue.venue_type
689 }
690}
691
692impl GeoLive {
693 pub fn from_raw_media(geolive: tl::types::MessageMediaGeoLive) -> Self {
694 use tl::types::MessageMediaGeo;
695 Self {
696 geo: Geo::from_raw_media(MessageMediaGeo {
697 geo: geolive.geo.clone(),
698 }),
699 raw_geolive: geolive,
700 }
701 }
702
703 pub fn to_raw_input_media(&self) -> tl::types::InputMediaGeoLive {
704 tl::types::InputMediaGeoLive {
705 geo_point: match self.geo {
706 Some(ref geo) => geo.to_raw_input_geo_point(),
707 None => tl::enums::InputGeoPoint::Empty,
708 },
709 heading: self.raw_geolive.heading,
710 period: Some(self.raw_geolive.period),
711 proximity_notification_radius: self.raw_geolive.proximity_notification_radius,
712 stopped: false,
713 }
714 }
715
716 pub fn heading(&self) -> Option<i32> {
718 self.raw_geolive.heading
719 }
720
721 pub fn period(&self) -> i32 {
723 self.raw_geolive.period
724 }
725
726 pub fn proximity_notification_radius(&self) -> Option<i32> {
728 self.raw_geolive.proximity_notification_radius
729 }
730}
731
732impl WebPage {
733 pub fn from_raw_media(webpage: tl::types::MessageMediaWebPage) -> Self {
734 Self { raw: webpage }
735 }
736}
737
738impl Uploaded {
739 pub fn from_raw(input_file: tl::enums::InputFile) -> Self {
740 Self { raw: input_file }
741 }
742
743 pub(crate) fn name(&self) -> &str {
744 match &self.raw {
745 tl::enums::InputFile::File(f) => f.name.as_ref(),
746 tl::enums::InputFile::Big(f) => f.name.as_ref(),
747 tl::enums::InputFile::StoryDocument(_) => "",
748 }
749 }
750}
751
752impl Media {
753 pub fn from_raw(media: tl::enums::MessageMedia) -> Option<Self> {
754 use tl::enums::MessageMedia as M;
755
756 match media {
758 M::Empty => None,
759 M::Photo(photo) => Some(Self::Photo(Photo::from_raw_media(photo))),
760 M::Geo(geo) => Geo::from_raw_media(geo).map(Self::Geo),
761 M::Contact(contact) => Some(Self::Contact(Contact::from_raw_media(contact))),
762 M::Unsupported => None,
763 M::Document(document) => {
764 let document = Document::from_raw_media(document);
765 Some(if let Some(sticker) = Sticker::from_document(&document) {
766 Self::Sticker(sticker)
767 } else {
768 Self::Document(document)
769 })
770 }
771 M::WebPage(webpage) => Some(Self::WebPage(WebPage::from_raw_media(webpage))),
772 M::Venue(venue) => Some(Self::Venue(Venue::from_raw_media(venue))),
773 M::Game(_) => None,
774 M::Invoice(_) => None,
775 M::GeoLive(geolive) => Some(Self::GeoLive(GeoLive::from_raw_media(geolive))),
776 M::Poll(poll) => Some(Self::Poll(Poll::from_raw_media(poll))),
777 M::Dice(dice) => Some(Self::Dice(Dice::from_raw_media(dice))),
778 M::Story(_) => None,
779 M::Giveaway(_) => None,
780 M::GiveawayResults(_) => None,
781 M::PaidMedia(_) => None,
782 M::ToDo(_) => None,
783 }
784 }
785
786 pub fn to_raw_input_media(&self) -> Option<tl::enums::InputMedia> {
787 match self {
788 Media::Photo(photo) => Some(photo.to_raw_input_media().into()),
789 Media::Document(document) => Some(document.to_raw_input_media().into()),
790 Media::Sticker(sticker) => Some(sticker.document.to_raw_input_media().into()),
791 Media::Contact(contact) => Some(contact.to_raw_input_media().into()),
792 Media::Poll(poll) => Some(poll.to_raw_input_media().into()),
793 Media::Geo(geo) => Some(geo.to_raw_input_media().into()),
794 Media::Dice(dice) => Some(dice.to_raw_input_media().into()),
795 Media::Venue(venue) => Some(venue.to_raw_input_media().into()),
796 Media::GeoLive(geolive) => Some(geolive.to_raw_input_media().into()),
797 Media::WebPage(_) => None,
798 }
799 }
800}
801
802impl Downloadable for Media {
803 fn to_raw_input_location(&self) -> Option<tl::enums::InputFileLocation> {
804 match self {
805 Media::Photo(photo) => photo.to_raw_input_location(),
806 Media::Document(document) => document.to_raw_input_location(),
807 Media::Sticker(sticker) => sticker.document.to_raw_input_location(),
808 Media::Contact(_) => None,
809 Media::Poll(_) => None,
810 Media::Geo(_) => None,
811 Media::Dice(_) => None,
812 Media::Venue(_) => None,
813 Media::GeoLive(_) => None,
814 Media::WebPage(_) => None,
815 }
816 }
817}
818
819impl From<Photo> for Media {
820 fn from(photo: Photo) -> Self {
821 Self::Photo(photo)
822 }
823}
824
825impl Downloadable for ChatPhoto {
826 fn to_raw_input_location(&self) -> Option<tl::enums::InputFileLocation> {
827 Some(self.raw.clone())
828 }
829}