grammers_client/types/
media.rs

1// Copyright 2020 - developers of the `grammers` project.
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8use 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// Not `MessageMedia`, but media nonetheless.
76#[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    /// The size of the photo.
142    /// returns 0 if unable to get the size.
143    pub fn size(&self) -> i64 {
144        match self.thumbs().largest() {
145            Some(thumb) => thumb.size() as i64,
146            None => 0,
147        }
148    }
149
150    /// Get photo thumbs.
151    ///
152    /// Since Telegram doesn't store the original photo, it can be presented in different sizes
153    /// and quality, a.k.a. thumbnails. Each photo preview has a specific type, indicating
154    /// the resolution and image transform that was applied server-side. Some low-resolution
155    /// thumbnails already contain all necessary information that can be shown to the user, but
156    /// for other types an additional request to the Telegram should be performed.
157    /// Check the description of [PhotoSize] to get an information about each particular thumbnail.
158    ///
159    /// <https://core.telegram.org/api/files#image-thumbnail-types>
160    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    /// Returns true if the photo is a spoiler.
179    pub fn is_spoiler(&self) -> bool {
180        self.raw.spoiler
181    }
182
183    /// Returns TTL seconds if the photo is self-destructive, None otherwise
184    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    /// Return the file's name.
251    ///
252    /// If the file was uploaded with no file name, the returned string will be empty.
253    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    /// Get the file's MIME type, if any.
270    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    /// The date on which the file was created, if any.
278    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    /// The size of the file.
288    /// returns 0 if the document is empty.
289    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    /// Get document thumbs.
297    /// <https://core.telegram.org/api/files#image-thumbnail-types>
298    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    /// Duration of video/audio, in seconds
319    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    /// Width & height of video/image
336    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    /// Title of audio
353    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    /// Performer (artist) of audio
370    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    /// Returns true if the document is an animated sticker
387    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    /// Returns true if the document is a spoiler
404    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    /// Get the emoji associated with the sticker.
456    pub fn emoji(&self) -> &str {
457        self.raw_attrs.alt.as_str()
458    }
459
460    /// Is this sticker an animated sticker?
461    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    /// The contact's phone number, in international format. This field will always be a non-empty
481    /// string of digits, although there's no guarantee that the number actually exists.
482    pub fn phone_number(&self) -> &str {
483        self.raw.phone_number.as_str()
484    }
485
486    /// The contact's first name. Although official clients will always send a non-empty string,
487    /// it is possible for this field to be empty when sent via different means.
488    pub fn first_name(&self) -> &str {
489        self.raw.first_name.as_str()
490    }
491
492    /// The contact's last name. May be empty if it's not set by sender.
493    pub fn last_name(&self) -> &str {
494        self.raw.last_name.as_str()
495    }
496
497    /// Contact information in [vCard format][1]. Applications such as Telegram Desktop leave this
498    /// field empty. The vCard version used in this field could be any. The field may also contain
499    /// arbitrary text when sent by non-official clients.
500    ///
501    /// [1]: https://en.wikipedia.org/wiki/VCard
502    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    /// Return question of the poll
529    pub fn question(&self) -> &grammers_tl_types::enums::TextWithEntities {
530        &self.raw.question
531    }
532
533    /// Return if current poll is quiz
534    pub fn is_quiz(&self) -> bool {
535        self.raw.quiz
536    }
537
538    /// Indicator that poll is closed
539    pub fn closed(&self) -> bool {
540        self.raw.closed
541    }
542
543    /// Iterator over poll answer options
544    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    /// Total voters that took part in the vote
551    ///
552    /// May be None if poll isn't started
553    pub fn total_voters(&self) -> Option<i32> {
554        self.raw_results.total_voters
555    }
556
557    /// Return details of the voters choices:
558    /// how much voters chose each answer and wether current option
559    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    /// Get the latitude of the location.
604    pub fn latitue(&self) -> f64 {
605        self.raw.lat
606    }
607
608    /// Get the latitude of the location.
609    pub fn longitude(&self) -> f64 {
610        self.raw.long
611    }
612
613    /// Get the accuracy of the geo location in meters.
614    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    /// Get the emoji of the dice.
631    pub fn emoji(&self) -> &str {
632        &self.raw.emoticon
633    }
634
635    /// Get the value of the dice.
636    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    /// Get the title of the venue.
667    pub fn title(&self) -> &str {
668        &self.raw_venue.title
669    }
670
671    /// Get the address of the venue.
672    pub fn address(&self) -> &str {
673        &self.raw_venue.address
674    }
675
676    /// Get the provider of the venue location.
677    pub fn provider(&self) -> &str {
678        &self.raw_venue.provider
679    }
680
681    /// Get the id of the venue.
682    pub fn venue_id(&self) -> &str {
683        &self.raw_venue.venue_id
684    }
685
686    /// Get the type of the venue.
687    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    /// Get the heading of the live location in degress (1-360).
717    pub fn heading(&self) -> Option<i32> {
718        self.raw_geolive.heading
719    }
720
721    /// Get the validity period of the live location.
722    pub fn period(&self) -> i32 {
723        self.raw_geolive.period
724    }
725
726    /// Get the radius of the proximity alert.
727    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        // TODO implement the rest
757        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}