tgbot/types/definitions/media/
group.rs1use std::{error::Error, fmt};
2
3use serde::Serialize;
4use serde_json::Error as JsonError;
5
6use crate::{
7 api::{Form, Method, Payload},
8 types::{
9 ChatId,
10 InputFile,
11 InputMediaAudio,
12 InputMediaDocument,
13 InputMediaLivePhoto,
14 InputMediaPhoto,
15 InputMediaVideo,
16 Integer,
17 Message,
18 ReplyParameters,
19 ReplyParametersError,
20 },
21};
22
23const MIN_GROUP_ATTACHMENTS: usize = 2;
24const MAX_GROUP_ATTACHMENTS: usize = 10;
25
26#[derive(Debug)]
28pub struct MediaGroup {
29 form: Form,
30}
31
32impl MediaGroup {
33 pub fn new<T>(items: T) -> Result<Self, MediaGroupError>
39 where
40 T: IntoIterator<Item = MediaGroupItem>,
41 {
42 let items: Vec<(usize, MediaGroupItem)> = items.into_iter().enumerate().collect();
43
44 let total_items = items.len();
45 if total_items < MIN_GROUP_ATTACHMENTS {
46 return Err(MediaGroupError::NotEnoughAttachments(MIN_GROUP_ATTACHMENTS));
47 }
48 if total_items > MAX_GROUP_ATTACHMENTS {
49 return Err(MediaGroupError::TooManyAttachments(MAX_GROUP_ATTACHMENTS));
50 }
51
52 let mut form = Form::default();
53
54 let mut add_file = |key: String, file: InputFile| -> String {
55 match &file {
56 InputFile::Id(text) | InputFile::Url(text) => text.clone(),
57 _ => {
58 form.insert_field(&key, file);
59 format!("attach://{key}")
60 }
61 }
62 };
63
64 let mut info = Vec::new();
65 for (idx, item) in items {
66 let media = add_file(format!("tgbot_im_file_{idx}"), item.file);
67 let thumbnail = item
68 .thumbnail
69 .map(|thumbnail| add_file(format!("tgbot_im_thumb_{idx}"), thumbnail));
70 let data = match item.item_type {
71 MediaGroupItemType::Audio(info) => MediaGroupItemData::Audio { media, thumbnail, info },
72 MediaGroupItemType::Document(info) => MediaGroupItemData::Document { media, thumbnail, info },
73 MediaGroupItemType::LivePhoto(photo, info) => {
74 let photo = add_file(format!("tgbot_im_live_photo_{idx}"), photo);
75 MediaGroupItemData::LivePhoto { media, photo, info }
76 }
77 MediaGroupItemType::Photo(info) => MediaGroupItemData::Photo { media, info },
78 MediaGroupItemType::Video(info) => MediaGroupItemData::Video {
79 media,
80 cover: item.cover.map(|cover| add_file(format!("tgbot_im_cover_{idx}"), cover)),
81 thumbnail,
82 info,
83 },
84 };
85 info.push(data);
86 }
87
88 form.insert_field(
89 "media",
90 serde_json::to_string(&info).map_err(MediaGroupError::Serialize)?,
91 );
92
93 Ok(Self { form })
94 }
95}
96
97impl From<MediaGroup> for Form {
98 fn from(group: MediaGroup) -> Self {
99 group.form
100 }
101}
102
103#[derive(Debug)]
105pub struct MediaGroupItem {
106 file: InputFile,
107 item_type: MediaGroupItemType,
108 cover: Option<InputFile>,
109 thumbnail: Option<InputFile>,
110}
111
112impl MediaGroupItem {
113 pub fn for_audio<T>(file: T, metadata: InputMediaAudio) -> Self
120 where
121 T: Into<InputFile>,
122 {
123 Self::new(file, MediaGroupItemType::Audio(metadata))
124 }
125
126 pub fn for_document<T>(file: T, metadata: InputMediaDocument) -> Self
133 where
134 T: Into<InputFile>,
135 {
136 Self::new(file, MediaGroupItemType::Document(metadata))
137 }
138
139 pub fn for_live_photo<A, B>(file: A, photo: B, metadata: InputMediaLivePhoto) -> Self
146 where
147 A: Into<InputFile>,
148 B: Into<InputFile>,
149 {
150 Self::new(file, MediaGroupItemType::LivePhoto(photo.into(), metadata))
151 }
152
153 pub fn for_photo<T>(file: T, metadata: InputMediaPhoto) -> Self
160 where
161 T: Into<InputFile>,
162 {
163 Self::new(file, MediaGroupItemType::Photo(metadata))
164 }
165
166 pub fn for_video<T>(file: T, metadata: InputMediaVideo) -> Self
173 where
174 T: Into<InputFile>,
175 {
176 Self::new(file, MediaGroupItemType::Video(metadata))
177 }
178
179 pub fn with_cover<T>(mut self, value: T) -> Self
187 where
188 T: Into<InputFile>,
189 {
190 self.cover = Some(value.into());
191 self
192 }
193
194 pub fn with_thumbnail<T>(mut self, value: T) -> Self
202 where
203 T: Into<InputFile>,
204 {
205 self.thumbnail = Some(value.into());
206 self
207 }
208
209 fn new<T>(file: T, item_type: MediaGroupItemType) -> Self
210 where
211 T: Into<InputFile>,
212 {
213 Self {
214 item_type,
215 file: file.into(),
216 cover: None,
217 thumbnail: None,
218 }
219 }
220}
221
222#[derive(Debug)]
223enum MediaGroupItemType {
224 Audio(InputMediaAudio),
225 Document(InputMediaDocument),
226 LivePhoto(InputFile, InputMediaLivePhoto),
227 Photo(InputMediaPhoto),
228 Video(InputMediaVideo),
229}
230
231#[serde_with::skip_serializing_none]
232#[derive(Debug, Serialize)]
233#[serde(tag = "type")]
234#[serde(rename_all = "lowercase")]
235enum MediaGroupItemData {
236 Audio {
237 media: String,
238 thumbnail: Option<String>,
239 #[serde(flatten)]
240 info: InputMediaAudio,
241 },
242 Document {
243 media: String,
244 thumbnail: Option<String>,
245 #[serde(flatten)]
246 info: InputMediaDocument,
247 },
248 LivePhoto {
249 media: String,
250 photo: String,
251 #[serde(flatten)]
252 info: InputMediaLivePhoto,
253 },
254 Photo {
255 media: String,
256 #[serde(flatten)]
257 info: InputMediaPhoto,
258 },
259 Video {
260 media: String,
261 cover: Option<String>,
262 thumbnail: Option<String>,
263 #[serde(flatten)]
264 info: InputMediaVideo,
265 },
266}
267
268#[derive(Debug)]
270pub enum MediaGroupError {
271 NotEnoughAttachments(usize),
273 TooManyAttachments(usize),
275 Serialize(JsonError),
277}
278
279impl Error for MediaGroupError {
280 fn source(&self) -> Option<&(dyn Error + 'static)> {
281 match self {
282 MediaGroupError::Serialize(err) => Some(err),
283 _ => None,
284 }
285 }
286}
287
288impl fmt::Display for MediaGroupError {
289 fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result {
290 match self {
291 MediaGroupError::NotEnoughAttachments(number) => {
292 write!(out, "media group must contain at least {number} attachments")
293 }
294 MediaGroupError::TooManyAttachments(number) => {
295 write!(out, "media group must contain no more than {number} attachments")
296 }
297 MediaGroupError::Serialize(err) => write!(out, "can not serialize media group items: {err}"),
298 }
299 }
300}
301
302#[derive(Debug)]
304pub struct SendMediaGroup {
305 form: Form,
306}
307
308impl SendMediaGroup {
309 pub fn new<T>(chat_id: T, media: MediaGroup) -> Self
314 where
315 T: Into<ChatId>,
316 {
317 let mut form: Form = media.into();
318 form.insert_field("chat_id", chat_id.into());
319 Self { form }
320 }
321
322 pub fn with_allow_paid_broadcast(mut self, value: bool) -> Self {
330 self.form.insert_field("allow_paid_broadcast", value);
331 self
332 }
333
334 pub fn with_business_connection_id<T>(mut self, value: T) -> Self
340 where
341 T: Into<String>,
342 {
343 self.form.insert_field("business_connection_id", value.into());
344 self
345 }
346
347 pub fn with_direct_messages_topic_id(mut self, value: Integer) -> Self {
353 self.form.insert_field("direct_messages_topic_id", value);
354 self
355 }
356
357 pub fn with_disable_notification(mut self, value: bool) -> Self {
364 self.form.insert_field("disable_notification", value);
365 self
366 }
367
368 pub fn with_message_effect_id<T>(mut self, value: T) -> Self
374 where
375 T: Into<String>,
376 {
377 self.form.insert_field("message_effect_id", value.into());
378 self
379 }
380
381 pub fn with_message_thread_id(mut self, value: Integer) -> Self {
388 self.form.insert_field("message_thread_id", value);
389 self
390 }
391
392 pub fn with_protect_content(mut self, value: bool) -> Self {
399 self.form.insert_field("protect_content", value);
400 self
401 }
402
403 pub fn with_reply_parameters(mut self, value: ReplyParameters) -> Result<Self, ReplyParametersError> {
409 self.form.insert_field("reply_parameters", value.serialize()?);
410 Ok(self)
411 }
412}
413
414impl Method for SendMediaGroup {
415 type Response = Vec<Message>;
416
417 fn into_payload(self) -> Payload {
418 Payload::form("sendMediaGroup", self.form)
419 }
420}