Skip to main content

rustigram_types/
file.rs

1use serde::{Deserialize, Serialize};
2
3/// One size of a photo or file thumbnail.
4#[derive(Debug, Clone, Serialize, Deserialize)]
5pub struct PhotoSize {
6    /// Telegram file identifier.
7    pub file_id: String,
8    /// Unique file identifier, stable across bots and time.
9    pub file_unique_id: String,
10    /// Photo width in pixels.
11    pub width: u32,
12    /// Photo height in pixels.
13    pub height: u32,
14    /// File size in bytes.
15    #[serde(skip_serializing_if = "Option::is_none")]
16    pub file_size: Option<u64>,
17}
18
19/// A file ready to be downloaded.
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct File {
22    /// Telegram file identifier.
23    pub file_id: String,
24    /// Unique file identifier, stable across bots and time.
25    pub file_unique_id: String,
26    /// File size in bytes.
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub file_size: Option<u64>,
29    /// Relative file path for constructing the download URL.
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub file_path: Option<String>,
32}
33
34impl File {
35    /// Constructs the full download URL for this file.
36    #[must_use]
37    pub fn url(&self, token: &str) -> Option<String> {
38        self.file_path
39            .as_ref()
40            .map(|path| format!("https://api.telegram.org/file/bot{token}/{path}"))
41    }
42}
43
44/// Audio file to be treated as music.
45#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct Audio {
47    /// Telegram file identifier.
48    pub file_id: String,
49    /// Unique file identifier, stable across bots and time.
50    pub file_unique_id: String,
51    /// Duration of the audio in seconds.
52    pub duration: u32,
53    /// Performer of the audio as defined by the sender or audio tags.
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub performer: Option<String>,
56    /// Title of the audio as defined by the sender or audio tags.
57    #[serde(skip_serializing_if = "Option::is_none")]
58    pub title: Option<String>,
59    /// Original filename as defined by the sender.
60    #[serde(skip_serializing_if = "Option::is_none")]
61    pub file_name: Option<String>,
62    /// MIME type of the audio.
63    #[serde(skip_serializing_if = "Option::is_none")]
64    pub mime_type: Option<String>,
65    /// File size in bytes.
66    #[serde(skip_serializing_if = "Option::is_none")]
67    pub file_size: Option<u64>,
68    /// Thumbnail of the album cover.
69    #[serde(skip_serializing_if = "Option::is_none")]
70    pub thumbnail: Option<PhotoSize>,
71}
72
73/// General file (not photo, voice, audio, or video).
74#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct Document {
76    /// Telegram file identifier.
77    pub file_id: String,
78    /// Unique file identifier, stable across bots and time.
79    pub file_unique_id: String,
80    /// Document thumbnail.
81    #[serde(skip_serializing_if = "Option::is_none")]
82    pub thumbnail: Option<PhotoSize>,
83    /// Original filename as defined by the sender.
84    #[serde(skip_serializing_if = "Option::is_none")]
85    pub file_name: Option<String>,
86    /// MIME type of the document.
87    #[serde(skip_serializing_if = "Option::is_none")]
88    pub mime_type: Option<String>,
89    /// File size in bytes.
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub file_size: Option<u64>,
92}
93
94/// A video file of a specific quality.
95///
96/// Returned inside [`Video::qualities`] when multiple quality levels are available.
97#[derive(Debug, Clone, Serialize, Deserialize)]
98pub struct VideoQuality {
99    /// Telegram file identifier.
100    pub file_id: String,
101    /// Unique file identifier, stable across bots and time.
102    pub file_unique_id: String,
103    /// Video width in pixels.
104    pub width: u32,
105    /// Video height in pixels.
106    pub height: u32,
107    /// Codec used to encode the video (e.g. `"h264"`, `"h265"`, `"av01"`).
108    pub codec: String,
109    /// File size in bytes.
110    #[serde(skip_serializing_if = "Option::is_none")]
111    pub file_size: Option<u64>,
112}
113
114/// Video file.
115#[derive(Debug, Clone, Serialize, Deserialize)]
116pub struct Video {
117    /// Telegram file identifier.
118    pub file_id: String,
119    /// Unique file identifier, stable across bots and time.
120    pub file_unique_id: String,
121    /// Video width in pixels.
122    pub width: u32,
123    /// Video height in pixels.
124    pub height: u32,
125    /// Duration of the video in seconds.
126    pub duration: u32,
127    /// Video thumbnail.
128    #[serde(skip_serializing_if = "Option::is_none")]
129    pub thumbnail: Option<PhotoSize>,
130    /// Cover image for the video in the message.
131    #[serde(skip_serializing_if = "Option::is_none")]
132    pub cover: Option<Vec<PhotoSize>>,
133    /// Start timestamp for video chapters.
134    #[serde(skip_serializing_if = "Option::is_none")]
135    pub start_timestamp: Option<u32>,
136    /// Other available quality levels for this video (Bot API 9.4).
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub qualities: Option<Vec<VideoQuality>>,
139    /// Original filename.
140    #[serde(skip_serializing_if = "Option::is_none")]
141    pub file_name: Option<String>,
142    /// MIME type of the video.
143    #[serde(skip_serializing_if = "Option::is_none")]
144    pub mime_type: Option<String>,
145    /// File size in bytes.
146    #[serde(skip_serializing_if = "Option::is_none")]
147    pub file_size: Option<u64>,
148}
149
150/// Animation file (GIF or H.264/MPEG-4 AVC, no sound).
151#[derive(Debug, Clone, Serialize, Deserialize)]
152pub struct Animation {
153    /// Telegram file identifier.
154    pub file_id: String,
155    /// Unique file identifier, stable across bots and time.
156    pub file_unique_id: String,
157    /// Animation width in pixels.
158    pub width: u32,
159    /// Animation height in pixels.
160    pub height: u32,
161    /// Duration of the animation in seconds.
162    pub duration: u32,
163    /// Animation thumbnail.
164    #[serde(skip_serializing_if = "Option::is_none")]
165    pub thumbnail: Option<PhotoSize>,
166    /// Original filename as defined by the sender.
167    #[serde(skip_serializing_if = "Option::is_none")]
168    pub file_name: Option<String>,
169    /// MIME type of the animation.
170    #[serde(skip_serializing_if = "Option::is_none")]
171    pub mime_type: Option<String>,
172    /// File size in bytes.
173    #[serde(skip_serializing_if = "Option::is_none")]
174    pub file_size: Option<u64>,
175}
176
177/// Voice note (OGG/OPUS audio).
178#[derive(Debug, Clone, Serialize, Deserialize)]
179pub struct Voice {
180    /// Telegram file identifier.
181    pub file_id: String,
182    /// Unique file identifier, stable across bots and time.
183    pub file_unique_id: String,
184    /// Duration of the voice note in seconds.
185    pub duration: u32,
186    /// MIME type of the voice note.
187    #[serde(skip_serializing_if = "Option::is_none")]
188    pub mime_type: Option<String>,
189    /// File size in bytes.
190    #[serde(skip_serializing_if = "Option::is_none")]
191    pub file_size: Option<u64>,
192}
193
194/// Rounded-square MPEG4 video note.
195#[derive(Debug, Clone, Serialize, Deserialize)]
196pub struct VideoNote {
197    /// Telegram file identifier.
198    pub file_id: String,
199    /// Unique file identifier, stable across bots and time.
200    pub file_unique_id: String,
201    /// Video width and height (diameter of the circle).
202    pub length: u32,
203    /// Duration of the video in seconds.
204    pub duration: u32,
205    /// Video thumbnail.
206    #[serde(skip_serializing_if = "Option::is_none")]
207    pub thumbnail: Option<PhotoSize>,
208    /// File size in bytes.
209    #[serde(skip_serializing_if = "Option::is_none")]
210    pub file_size: Option<u64>,
211}
212
213/// A photo with a short video (live photo).
214#[derive(Debug, Clone, Serialize, Deserialize)]
215pub struct LivePhoto {
216    /// Available sizes of the corresponding static photo.
217    #[serde(skip_serializing_if = "Option::is_none")]
218    pub photo: Option<Vec<PhotoSize>>,
219    /// Identifier for the video file.
220    pub file_id: String,
221    /// Unique identifier for the video file, stable across bots and time.
222    pub file_unique_id: String,
223    /// Video width as defined by the sender.
224    pub width: u32,
225    /// Video height as defined by the sender.
226    pub height: u32,
227    /// Duration of the video in seconds.
228    pub duration: u32,
229    /// MIME type of the file as defined by the sender.
230    #[serde(skip_serializing_if = "Option::is_none")]
231    pub mime_type: Option<String>,
232    /// File size in bytes.
233    #[serde(skip_serializing_if = "Option::is_none")]
234    pub file_size: Option<u64>,
235}
236
237/// Represents a file to be sent.
238#[derive(Debug, Clone)]
239pub enum InputFile {
240    /// Send an existing file by its Telegram `file_id`.
241    FileId(String),
242    /// Send a file from a URL (photo ≤5 MB, others ≤20 MB).
243    Url(String),
244    /// Upload new file bytes.
245    Bytes {
246        /// Original filename sent in the Content-Disposition header.
247        filename: String,
248        /// Raw file bytes.
249        data: Vec<u8>,
250        /// MIME type of the file.
251        mime_type: String,
252    },
253    /// Reference an already-included multipart attachment by `attach://<name>`.
254    Attach(String),
255}
256
257impl InputFile {
258    /// Returns the string representation for JSON/query-string fields.
259    #[must_use]
260    pub fn as_str(&self) -> &str {
261        match self {
262            Self::FileId(id) => id,
263            Self::Url(url) => url,
264            Self::Attach(name) => name,
265            Self::Bytes { filename, .. } => filename,
266        }
267    }
268
269    /// Returns `true` if this variant needs multipart form upload.
270    #[must_use]
271    pub fn requires_multipart(&self) -> bool {
272        matches!(self, Self::Bytes { .. })
273    }
274}
275
276/// Encrypted passport file.
277#[derive(Debug, Clone, Serialize, Deserialize)]
278pub struct PassportFile {
279    /// Telegram file identifier.
280    pub file_id: String,
281    /// Unique file identifier, stable across bots and time.
282    pub file_unique_id: String,
283    /// File size in bytes.
284    pub file_size: u64,
285    /// Unix timestamp when the file was uploaded.
286    pub file_date: i64,
287}
288
289// ─── InputMedia ───────────────────────────────────────────────────────────────
290//
291// Used by `sendMediaGroup` and `editMessageMedia`. The `media` field accepts a
292// `file_id`, HTTP URL, or `"attach://<n>"` for a multipart attachment.
293
294/// A photo to include in a media group or replace an existing media message.
295#[derive(Debug, Clone, Serialize, Deserialize)]
296pub struct InputMediaPhoto {
297    /// File to send: `file_id`, HTTP URL, or `"attach://<n>"`.
298    pub media: String,
299    /// Caption (0–1024 characters after entities parsing).
300    #[serde(skip_serializing_if = "Option::is_none")]
301    pub caption: Option<String>,
302    /// Parse mode for the caption.
303    #[serde(skip_serializing_if = "Option::is_none")]
304    pub parse_mode: Option<crate::message::ParseMode>,
305    /// Special entities in the caption.
306    #[serde(skip_serializing_if = "Option::is_none")]
307    pub caption_entities: Option<Vec<crate::message::MessageEntity>>,
308    /// `true` if the caption must be shown above the media.
309    #[serde(skip_serializing_if = "Option::is_none")]
310    pub show_caption_above_media: Option<bool>,
311    /// `true` if the photo needs a spoiler animation.
312    #[serde(skip_serializing_if = "Option::is_none")]
313    pub has_spoiler: Option<bool>,
314}
315
316/// A video to include in a media group or replace an existing media message.
317#[derive(Debug, Clone, Serialize, Deserialize)]
318pub struct InputMediaVideo {
319    /// File to send: `file_id`, HTTP URL, or `"attach://<n>"`.
320    pub media: String,
321    /// Thumbnail: `file_id` or `"attach://<n>"`.
322    #[serde(skip_serializing_if = "Option::is_none")]
323    pub thumbnail: Option<String>,
324    /// Cover image: `file_id`, HTTP URL, or `"attach://<n>"`.
325    #[serde(skip_serializing_if = "Option::is_none")]
326    pub cover: Option<String>,
327    /// Start timestamp for the video in the message.
328    #[serde(skip_serializing_if = "Option::is_none")]
329    pub start_timestamp: Option<i64>,
330    /// Caption (0–1024 characters after entities parsing).
331    #[serde(skip_serializing_if = "Option::is_none")]
332    pub caption: Option<String>,
333    /// Parse mode for the caption.
334    #[serde(skip_serializing_if = "Option::is_none")]
335    pub parse_mode: Option<crate::message::ParseMode>,
336    /// Special entities in the caption.
337    #[serde(skip_serializing_if = "Option::is_none")]
338    pub caption_entities: Option<Vec<crate::message::MessageEntity>>,
339    /// `true` if the caption must be shown above the media.
340    #[serde(skip_serializing_if = "Option::is_none")]
341    pub show_caption_above_media: Option<bool>,
342    /// Video width in pixels.
343    #[serde(skip_serializing_if = "Option::is_none")]
344    pub width: Option<u32>,
345    /// Video height in pixels.
346    #[serde(skip_serializing_if = "Option::is_none")]
347    pub height: Option<u32>,
348    /// Video duration in seconds.
349    #[serde(skip_serializing_if = "Option::is_none")]
350    pub duration: Option<u32>,
351    /// `true` if the uploaded video is suitable for streaming.
352    #[serde(skip_serializing_if = "Option::is_none")]
353    pub supports_streaming: Option<bool>,
354    /// `true` if the video needs a spoiler animation.
355    #[serde(skip_serializing_if = "Option::is_none")]
356    pub has_spoiler: Option<bool>,
357}
358
359/// An animation (GIF or silent H.264) to include in a media group.
360#[derive(Debug, Clone, Serialize, Deserialize)]
361pub struct InputMediaAnimation {
362    /// File to send: `file_id`, HTTP URL, or `"attach://<n>"`.
363    pub media: String,
364    /// Thumbnail: `file_id` or `"attach://<n>"`.
365    #[serde(skip_serializing_if = "Option::is_none")]
366    pub thumbnail: Option<String>,
367    /// Caption (0–1024 characters after entities parsing).
368    #[serde(skip_serializing_if = "Option::is_none")]
369    pub caption: Option<String>,
370    /// Parse mode for the caption.
371    #[serde(skip_serializing_if = "Option::is_none")]
372    pub parse_mode: Option<crate::message::ParseMode>,
373    /// Special entities in the caption.
374    #[serde(skip_serializing_if = "Option::is_none")]
375    pub caption_entities: Option<Vec<crate::message::MessageEntity>>,
376    /// `true` if the caption must be shown above the media.
377    #[serde(skip_serializing_if = "Option::is_none")]
378    pub show_caption_above_media: Option<bool>,
379    /// Animation width in pixels.
380    #[serde(skip_serializing_if = "Option::is_none")]
381    pub width: Option<u32>,
382    /// Animation height in pixels.
383    #[serde(skip_serializing_if = "Option::is_none")]
384    pub height: Option<u32>,
385    /// Animation duration in seconds.
386    #[serde(skip_serializing_if = "Option::is_none")]
387    pub duration: Option<u32>,
388    /// `true` if the animation needs a spoiler animation.
389    #[serde(skip_serializing_if = "Option::is_none")]
390    pub has_spoiler: Option<bool>,
391}
392
393/// An audio file to include in a media group.
394#[derive(Debug, Clone, Serialize, Deserialize)]
395pub struct InputMediaAudio {
396    /// File to send: `file_id`, HTTP URL, or `"attach://<n>"`.
397    pub media: String,
398    /// Thumbnail: `file_id` or `"attach://<n>"`.
399    #[serde(skip_serializing_if = "Option::is_none")]
400    pub thumbnail: Option<String>,
401    /// Caption (0–1024 characters after entities parsing).
402    #[serde(skip_serializing_if = "Option::is_none")]
403    pub caption: Option<String>,
404    /// Parse mode for the caption.
405    #[serde(skip_serializing_if = "Option::is_none")]
406    pub parse_mode: Option<crate::message::ParseMode>,
407    /// Special entities in the caption.
408    #[serde(skip_serializing_if = "Option::is_none")]
409    pub caption_entities: Option<Vec<crate::message::MessageEntity>>,
410    /// Audio duration in seconds.
411    #[serde(skip_serializing_if = "Option::is_none")]
412    pub duration: Option<u32>,
413    /// Performer of the audio.
414    #[serde(skip_serializing_if = "Option::is_none")]
415    pub performer: Option<String>,
416    /// Title of the audio.
417    #[serde(skip_serializing_if = "Option::is_none")]
418    pub title: Option<String>,
419}
420
421/// A voice message file to be sent.
422#[derive(Debug, Clone, Serialize, Deserialize)]
423pub struct InputMediaVoiceNote {
424    /// File to send: `file_id`, HTTP URL, or `"attach://<n>"`.
425    pub media: String,
426    /// Caption (0–1024 characters after entities parsing).
427    #[serde(skip_serializing_if = "Option::is_none")]
428    pub caption: Option<String>,
429    /// Parse mode for the caption.
430    #[serde(skip_serializing_if = "Option::is_none")]
431    pub parse_mode: Option<crate::message::ParseMode>,
432    /// Special entities in the caption.
433    #[serde(skip_serializing_if = "Option::is_none")]
434    pub caption_entities: Option<Vec<crate::message::MessageEntity>>,
435    /// Duration of the voice message in seconds.
436    #[serde(skip_serializing_if = "Option::is_none")]
437    pub duration: Option<u32>,
438}
439
440/// A document (general file) to include in a media group.
441#[derive(Debug, Clone, Serialize, Deserialize)]
442pub struct InputMediaDocument {
443    /// File to send: `file_id`, HTTP URL, or `"attach://<n>"`.
444    pub media: String,
445    /// Thumbnail: `file_id` or `"attach://<n>"`.
446    #[serde(skip_serializing_if = "Option::is_none")]
447    pub thumbnail: Option<String>,
448    /// Caption (0–1024 characters after entities parsing).
449    #[serde(skip_serializing_if = "Option::is_none")]
450    pub caption: Option<String>,
451    /// Parse mode for the caption.
452    #[serde(skip_serializing_if = "Option::is_none")]
453    pub parse_mode: Option<crate::message::ParseMode>,
454    /// Special entities in the caption.
455    #[serde(skip_serializing_if = "Option::is_none")]
456    pub caption_entities: Option<Vec<crate::message::MessageEntity>>,
457    /// `true` to disable automatic server-side content type detection.
458    #[serde(skip_serializing_if = "Option::is_none")]
459    pub disable_content_type_detection: Option<bool>,
460}
461
462/// A live photo to be sent as part of a media group or to replace an existing media message.
463///
464/// Sending live photos by URL is currently unsupported — use `file_id` or `attach://<n>`.
465#[derive(Debug, Clone, Serialize, Deserialize)]
466pub struct InputMediaLivePhoto {
467    /// Video of the live photo: `file_id` or `"attach://<n>"`.
468    pub media: String,
469    /// The static photo: `file_id` or `"attach://<n>"`.
470    pub photo: String,
471    /// Caption (0–1024 characters after entities parsing).
472    #[serde(skip_serializing_if = "Option::is_none")]
473    pub caption: Option<String>,
474    /// Parse mode for the caption.
475    #[serde(skip_serializing_if = "Option::is_none")]
476    pub parse_mode: Option<crate::message::ParseMode>,
477    /// Special entities in the caption.
478    #[serde(skip_serializing_if = "Option::is_none")]
479    pub caption_entities: Option<Vec<crate::message::MessageEntity>>,
480    /// `true` if the caption must be shown above the media.
481    #[serde(skip_serializing_if = "Option::is_none")]
482    pub show_caption_above_media: Option<bool>,
483    /// `true` if the live photo needs a spoiler animation.
484    #[serde(skip_serializing_if = "Option::is_none")]
485    pub has_spoiler: Option<bool>,
486}
487
488/// A location to be sent as poll media.
489#[derive(Debug, Clone, Serialize, Deserialize)]
490pub struct InputMediaLocation {
491    /// Latitude of the location.
492    pub latitude: f64,
493    /// Longitude of the location.
494    pub longitude: f64,
495    /// Radius of uncertainty in metres (0–1500).
496    #[serde(skip_serializing_if = "Option::is_none")]
497    pub horizontal_accuracy: Option<f64>,
498}
499
500/// A venue to be sent as poll media.
501#[derive(Debug, Clone, Serialize, Deserialize)]
502pub struct InputMediaVenue {
503    /// Latitude of the venue.
504    pub latitude: f64,
505    /// Longitude of the venue.
506    pub longitude: f64,
507    /// Name of the venue.
508    pub title: String,
509    /// Address of the venue.
510    pub address: String,
511    /// Foursquare identifier of the venue.
512    #[serde(skip_serializing_if = "Option::is_none")]
513    pub foursquare_id: Option<String>,
514    /// Foursquare type of the venue.
515    #[serde(skip_serializing_if = "Option::is_none")]
516    pub foursquare_type: Option<String>,
517    /// Google Places identifier of the venue.
518    #[serde(skip_serializing_if = "Option::is_none")]
519    pub google_place_id: Option<String>,
520    /// Google Places type of the venue.
521    #[serde(skip_serializing_if = "Option::is_none")]
522    pub google_place_type: Option<String>,
523}
524
525/// A sticker file to be sent as poll option media.
526#[derive(Debug, Clone, Serialize, Deserialize)]
527pub struct InputMediaSticker {
528    /// File to send: `file_id`, HTTP URL, or `"attach://<n>"`.
529    pub media: String,
530    /// Emoji associated with the sticker; only for just-uploaded stickers.
531    #[serde(skip_serializing_if = "Option::is_none")]
532    pub emoji: Option<String>,
533}
534
535/// The media content of a message to be sent or edited.
536///
537/// Serialise with `serde_json::to_value(&media)` to pass to `sendMediaGroup`
538/// or `editMessageMedia` until the API methods are updated to accept this type
539/// directly.
540#[derive(Debug, Clone, Serialize, Deserialize)]
541#[serde(tag = "type", rename_all = "snake_case")]
542pub enum InputMedia {
543    /// A photo.
544    Photo(InputMediaPhoto),
545    /// A video.
546    Video(InputMediaVideo),
547    /// An animation (GIF or silent H.264).
548    Animation(InputMediaAnimation),
549    /// An audio file treated as music.
550    Audio(InputMediaAudio),
551    /// A general document.
552    Document(InputMediaDocument),
553    /// A live photo.
554    LivePhoto(InputMediaLivePhoto),
555    /// A voice message.
556    VoiceNote(InputMediaVoiceNote),
557}
558
559// ─── InputPaidMedia ───────────────────────────────────────────────────────────
560
561/// A photo to send as paid media.
562#[derive(Debug, Clone, Serialize, Deserialize)]
563pub struct InputPaidMediaPhoto {
564    /// File to send: `file_id`, HTTP URL, or `"attach://<n>"`.
565    pub media: String,
566}
567
568/// A video to send as paid media.
569#[derive(Debug, Clone, Serialize, Deserialize)]
570pub struct InputPaidMediaVideo {
571    /// File to send: `file_id`, HTTP URL, or `"attach://<n>"`.
572    pub media: String,
573    /// Thumbnail: `file_id` or `"attach://<n>"`.
574    #[serde(skip_serializing_if = "Option::is_none")]
575    pub thumbnail: Option<String>,
576    /// Cover image: `file_id`, HTTP URL, or `"attach://<n>"`.
577    #[serde(skip_serializing_if = "Option::is_none")]
578    pub cover: Option<String>,
579    /// Start timestamp for the video in the message.
580    #[serde(skip_serializing_if = "Option::is_none")]
581    pub start_timestamp: Option<i64>,
582    /// Video width in pixels.
583    #[serde(skip_serializing_if = "Option::is_none")]
584    pub width: Option<u32>,
585    /// Video height in pixels.
586    #[serde(skip_serializing_if = "Option::is_none")]
587    pub height: Option<u32>,
588    /// Video duration in seconds.
589    #[serde(skip_serializing_if = "Option::is_none")]
590    pub duration: Option<u32>,
591    /// `true` if the uploaded video is suitable for streaming.
592    #[serde(skip_serializing_if = "Option::is_none")]
593    pub supports_streaming: Option<bool>,
594}
595
596/// A live photo to send as paid media.
597///
598/// Both video and static photo must be `file_id` or `attach://<n>`.
599/// Sending live photos by URL is currently unsupported.
600#[derive(Debug, Clone, Serialize, Deserialize)]
601pub struct InputPaidMediaLivePhoto {
602    /// Video of the live photo: `file_id` or `"attach://<n>"`.
603    pub media: String,
604    /// The static photo: `file_id` or `"attach://<n>"`.
605    pub photo: String,
606}
607
608/// Paid media to send via `sendPaidMedia`.
609///
610/// Serialise with `serde_json::to_value(&media)` to pass to `sendPaidMedia`
611/// until the method is updated to accept this type directly.
612#[derive(Debug, Clone, Serialize, Deserialize)]
613#[serde(tag = "type", rename_all = "snake_case")]
614pub enum InputPaidMedia {
615    /// A photo.
616    Photo(InputPaidMediaPhoto),
617    /// A video.
618    Video(InputPaidMediaVideo),
619    /// A live photo.
620    LivePhoto(InputPaidMediaLivePhoto),
621}
622
623// ─── InputProfilePhoto ────────────────────────────────────────────────────────
624
625/// A static profile photo in JPEG format.
626#[derive(Debug, Clone, Serialize, Deserialize)]
627pub struct InputProfilePhotoStatic {
628    /// The photo file.
629    ///
630    /// Profile photos cannot be reused — upload as a new file using
631    /// `"attach://<n>"` via multipart/form-data.
632    pub photo: String,
633}
634
635/// An animated profile photo in MPEG4 format.
636#[derive(Debug, Clone, Serialize, Deserialize)]
637pub struct InputProfilePhotoAnimated {
638    /// The animation file.
639    ///
640    /// Profile photos cannot be reused — upload as a new file using
641    /// `"attach://<n>"` via multipart/form-data.
642    pub animation: String,
643    /// Timestamp in seconds of the frame to use as the static profile photo.
644    /// Defaults to `0.0`.
645    #[serde(skip_serializing_if = "Option::is_none")]
646    pub main_frame_timestamp: Option<f64>,
647}
648
649/// A profile photo to set via `setMyProfilePhoto` or `setBusinessAccountProfilePhoto`.
650///
651/// Pass this value directly; both methods serialise it themselves.
652#[derive(Debug, Clone, Serialize, Deserialize)]
653#[serde(tag = "type", rename_all = "snake_case")]
654pub enum InputProfilePhoto {
655    /// A static JPEG profile photo.
656    Static(InputProfilePhotoStatic),
657    /// An animated MPEG4 profile photo.
658    Animated(InputProfilePhotoAnimated),
659}