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/// Represents a file to be sent.
214#[derive(Debug, Clone)]
215pub enum InputFile {
216    /// Send an existing file by its Telegram `file_id`.
217    FileId(String),
218    /// Send a file from a URL (photo ≤5 MB, others ≤20 MB).
219    Url(String),
220    /// Upload new file bytes.
221    Bytes {
222        /// Original filename sent in the Content-Disposition header.
223        filename: String,
224        /// Raw file bytes.
225        data: Vec<u8>,
226        /// MIME type of the file.
227        mime_type: String,
228    },
229    /// Reference an already-included multipart attachment by `attach://<name>`.
230    Attach(String),
231}
232
233impl InputFile {
234    /// Returns the string representation for JSON/query-string fields.
235    #[must_use]
236    pub fn as_str(&self) -> &str {
237        match self {
238            Self::FileId(id) => id,
239            Self::Url(url) => url,
240            Self::Attach(name) => name,
241            Self::Bytes { filename, .. } => filename,
242        }
243    }
244
245    /// Returns `true` if this variant needs multipart form upload.
246    #[must_use]
247    pub fn requires_multipart(&self) -> bool {
248        matches!(self, Self::Bytes { .. })
249    }
250}
251
252/// Encrypted passport file.
253#[derive(Debug, Clone, Serialize, Deserialize)]
254pub struct PassportFile {
255    /// Telegram file identifier.
256    pub file_id: String,
257    /// Unique file identifier, stable across bots and time.
258    pub file_unique_id: String,
259    /// File size in bytes.
260    pub file_size: u64,
261    /// Unix timestamp when the file was uploaded.
262    pub file_date: i64,
263}
264
265// ─── InputMedia ───────────────────────────────────────────────────────────────
266//
267// Used by `sendMediaGroup` and `editMessageMedia`. The `media` field accepts a
268// `file_id`, HTTP URL, or `"attach://<n>"` for a multipart attachment.
269
270/// A photo to include in a media group or replace an existing media message.
271#[derive(Debug, Clone, Serialize, Deserialize)]
272pub struct InputMediaPhoto {
273    /// File to send: `file_id`, HTTP URL, or `"attach://<n>"`.
274    pub media: String,
275    /// Caption (0–1024 characters after entities parsing).
276    #[serde(skip_serializing_if = "Option::is_none")]
277    pub caption: Option<String>,
278    /// Parse mode for the caption.
279    #[serde(skip_serializing_if = "Option::is_none")]
280    pub parse_mode: Option<crate::message::ParseMode>,
281    /// Special entities in the caption.
282    #[serde(skip_serializing_if = "Option::is_none")]
283    pub caption_entities: Option<Vec<crate::message::MessageEntity>>,
284    /// `true` if the caption must be shown above the media.
285    #[serde(skip_serializing_if = "Option::is_none")]
286    pub show_caption_above_media: Option<bool>,
287    /// `true` if the photo needs a spoiler animation.
288    #[serde(skip_serializing_if = "Option::is_none")]
289    pub has_spoiler: Option<bool>,
290}
291
292/// A video to include in a media group or replace an existing media message.
293#[derive(Debug, Clone, Serialize, Deserialize)]
294pub struct InputMediaVideo {
295    /// File to send: `file_id`, HTTP URL, or `"attach://<n>"`.
296    pub media: String,
297    /// Thumbnail: `file_id` or `"attach://<n>"`.
298    #[serde(skip_serializing_if = "Option::is_none")]
299    pub thumbnail: Option<String>,
300    /// Cover image: `file_id`, HTTP URL, or `"attach://<n>"`.
301    #[serde(skip_serializing_if = "Option::is_none")]
302    pub cover: Option<String>,
303    /// Start timestamp for the video in the message.
304    #[serde(skip_serializing_if = "Option::is_none")]
305    pub start_timestamp: Option<i64>,
306    /// Caption (0–1024 characters after entities parsing).
307    #[serde(skip_serializing_if = "Option::is_none")]
308    pub caption: Option<String>,
309    /// Parse mode for the caption.
310    #[serde(skip_serializing_if = "Option::is_none")]
311    pub parse_mode: Option<crate::message::ParseMode>,
312    /// Special entities in the caption.
313    #[serde(skip_serializing_if = "Option::is_none")]
314    pub caption_entities: Option<Vec<crate::message::MessageEntity>>,
315    /// `true` if the caption must be shown above the media.
316    #[serde(skip_serializing_if = "Option::is_none")]
317    pub show_caption_above_media: Option<bool>,
318    /// Video width in pixels.
319    #[serde(skip_serializing_if = "Option::is_none")]
320    pub width: Option<u32>,
321    /// Video height in pixels.
322    #[serde(skip_serializing_if = "Option::is_none")]
323    pub height: Option<u32>,
324    /// Video duration in seconds.
325    #[serde(skip_serializing_if = "Option::is_none")]
326    pub duration: Option<u32>,
327    /// `true` if the uploaded video is suitable for streaming.
328    #[serde(skip_serializing_if = "Option::is_none")]
329    pub supports_streaming: Option<bool>,
330    /// `true` if the video needs a spoiler animation.
331    #[serde(skip_serializing_if = "Option::is_none")]
332    pub has_spoiler: Option<bool>,
333}
334
335/// An animation (GIF or silent H.264) to include in a media group.
336#[derive(Debug, Clone, Serialize, Deserialize)]
337pub struct InputMediaAnimation {
338    /// File to send: `file_id`, HTTP URL, or `"attach://<n>"`.
339    pub media: String,
340    /// Thumbnail: `file_id` or `"attach://<n>"`.
341    #[serde(skip_serializing_if = "Option::is_none")]
342    pub thumbnail: Option<String>,
343    /// Caption (0–1024 characters after entities parsing).
344    #[serde(skip_serializing_if = "Option::is_none")]
345    pub caption: Option<String>,
346    /// Parse mode for the caption.
347    #[serde(skip_serializing_if = "Option::is_none")]
348    pub parse_mode: Option<crate::message::ParseMode>,
349    /// Special entities in the caption.
350    #[serde(skip_serializing_if = "Option::is_none")]
351    pub caption_entities: Option<Vec<crate::message::MessageEntity>>,
352    /// `true` if the caption must be shown above the media.
353    #[serde(skip_serializing_if = "Option::is_none")]
354    pub show_caption_above_media: Option<bool>,
355    /// Animation width in pixels.
356    #[serde(skip_serializing_if = "Option::is_none")]
357    pub width: Option<u32>,
358    /// Animation height in pixels.
359    #[serde(skip_serializing_if = "Option::is_none")]
360    pub height: Option<u32>,
361    /// Animation duration in seconds.
362    #[serde(skip_serializing_if = "Option::is_none")]
363    pub duration: Option<u32>,
364    /// `true` if the animation needs a spoiler animation.
365    #[serde(skip_serializing_if = "Option::is_none")]
366    pub has_spoiler: Option<bool>,
367}
368
369/// An audio file to include in a media group.
370#[derive(Debug, Clone, Serialize, Deserialize)]
371pub struct InputMediaAudio {
372    /// File to send: `file_id`, HTTP URL, or `"attach://<n>"`.
373    pub media: String,
374    /// Thumbnail: `file_id` or `"attach://<n>"`.
375    #[serde(skip_serializing_if = "Option::is_none")]
376    pub thumbnail: Option<String>,
377    /// Caption (0–1024 characters after entities parsing).
378    #[serde(skip_serializing_if = "Option::is_none")]
379    pub caption: Option<String>,
380    /// Parse mode for the caption.
381    #[serde(skip_serializing_if = "Option::is_none")]
382    pub parse_mode: Option<crate::message::ParseMode>,
383    /// Special entities in the caption.
384    #[serde(skip_serializing_if = "Option::is_none")]
385    pub caption_entities: Option<Vec<crate::message::MessageEntity>>,
386    /// Audio duration in seconds.
387    #[serde(skip_serializing_if = "Option::is_none")]
388    pub duration: Option<u32>,
389    /// Performer of the audio.
390    #[serde(skip_serializing_if = "Option::is_none")]
391    pub performer: Option<String>,
392    /// Title of the audio.
393    #[serde(skip_serializing_if = "Option::is_none")]
394    pub title: Option<String>,
395}
396
397/// A document (general file) to include in a media group.
398#[derive(Debug, Clone, Serialize, Deserialize)]
399pub struct InputMediaDocument {
400    /// File to send: `file_id`, HTTP URL, or `"attach://<n>"`.
401    pub media: String,
402    /// Thumbnail: `file_id` or `"attach://<n>"`.
403    #[serde(skip_serializing_if = "Option::is_none")]
404    pub thumbnail: Option<String>,
405    /// Caption (0–1024 characters after entities parsing).
406    #[serde(skip_serializing_if = "Option::is_none")]
407    pub caption: Option<String>,
408    /// Parse mode for the caption.
409    #[serde(skip_serializing_if = "Option::is_none")]
410    pub parse_mode: Option<crate::message::ParseMode>,
411    /// Special entities in the caption.
412    #[serde(skip_serializing_if = "Option::is_none")]
413    pub caption_entities: Option<Vec<crate::message::MessageEntity>>,
414    /// `true` to disable automatic server-side content type detection.
415    #[serde(skip_serializing_if = "Option::is_none")]
416    pub disable_content_type_detection: Option<bool>,
417}
418
419/// The media content of a message to be sent or edited.
420///
421/// Serialise with `serde_json::to_value(&media)` to pass to `sendMediaGroup`
422/// or `editMessageMedia` until the API methods are updated to accept this type
423/// directly.
424#[derive(Debug, Clone, Serialize, Deserialize)]
425#[serde(tag = "type", rename_all = "snake_case")]
426pub enum InputMedia {
427    /// A photo.
428    Photo(InputMediaPhoto),
429    /// A video.
430    Video(InputMediaVideo),
431    /// An animation (GIF or silent H.264).
432    Animation(InputMediaAnimation),
433    /// An audio file treated as music.
434    Audio(InputMediaAudio),
435    /// A general document.
436    Document(InputMediaDocument),
437}
438
439// ─── InputPaidMedia ───────────────────────────────────────────────────────────
440
441/// A photo to send as paid media.
442#[derive(Debug, Clone, Serialize, Deserialize)]
443pub struct InputPaidMediaPhoto {
444    /// File to send: `file_id`, HTTP URL, or `"attach://<n>"`.
445    pub media: String,
446}
447
448/// A video to send as paid media.
449#[derive(Debug, Clone, Serialize, Deserialize)]
450pub struct InputPaidMediaVideo {
451    /// File to send: `file_id`, HTTP URL, or `"attach://<n>"`.
452    pub media: String,
453    /// Thumbnail: `file_id` or `"attach://<n>"`.
454    #[serde(skip_serializing_if = "Option::is_none")]
455    pub thumbnail: Option<String>,
456    /// Cover image: `file_id`, HTTP URL, or `"attach://<n>"`.
457    #[serde(skip_serializing_if = "Option::is_none")]
458    pub cover: Option<String>,
459    /// Start timestamp for the video in the message.
460    #[serde(skip_serializing_if = "Option::is_none")]
461    pub start_timestamp: Option<i64>,
462    /// Video width in pixels.
463    #[serde(skip_serializing_if = "Option::is_none")]
464    pub width: Option<u32>,
465    /// Video height in pixels.
466    #[serde(skip_serializing_if = "Option::is_none")]
467    pub height: Option<u32>,
468    /// Video duration in seconds.
469    #[serde(skip_serializing_if = "Option::is_none")]
470    pub duration: Option<u32>,
471    /// `true` if the uploaded video is suitable for streaming.
472    #[serde(skip_serializing_if = "Option::is_none")]
473    pub supports_streaming: Option<bool>,
474}
475
476/// Paid media to send via `sendPaidMedia`.
477///
478/// Serialise with `serde_json::to_value(&media)` to pass to `sendPaidMedia`
479/// until the method is updated to accept this type directly.
480#[derive(Debug, Clone, Serialize, Deserialize)]
481#[serde(tag = "type", rename_all = "snake_case")]
482pub enum InputPaidMedia {
483    /// A photo.
484    Photo(InputPaidMediaPhoto),
485    /// A video.
486    Video(InputPaidMediaVideo),
487}
488
489// ─── InputProfilePhoto ────────────────────────────────────────────────────────
490
491/// A static profile photo in JPEG format.
492#[derive(Debug, Clone, Serialize, Deserialize)]
493pub struct InputProfilePhotoStatic {
494    /// The photo file.
495    ///
496    /// Profile photos cannot be reused — upload as a new file using
497    /// `"attach://<n>"` via multipart/form-data.
498    pub photo: String,
499}
500
501/// An animated profile photo in MPEG4 format.
502#[derive(Debug, Clone, Serialize, Deserialize)]
503pub struct InputProfilePhotoAnimated {
504    /// The animation file.
505    ///
506    /// Profile photos cannot be reused — upload as a new file using
507    /// `"attach://<n>"` via multipart/form-data.
508    pub animation: String,
509    /// Timestamp in seconds of the frame to use as the static profile photo.
510    /// Defaults to `0.0`.
511    #[serde(skip_serializing_if = "Option::is_none")]
512    pub main_frame_timestamp: Option<f64>,
513}
514
515/// A profile photo to set via `setMyProfilePhoto` or `setBusinessAccountProfilePhoto`.
516///
517/// Serialise with `serde_json::to_string(&photo)` to pass to the relevant method.
518#[derive(Debug, Clone, Serialize, Deserialize)]
519#[serde(tag = "type", rename_all = "snake_case")]
520pub enum InputProfilePhoto {
521    /// A static JPEG profile photo.
522    Static(InputProfilePhotoStatic),
523    /// An animated MPEG4 profile photo.
524    Animated(InputProfilePhotoAnimated),
525}