1use 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 InputMediaPhoto,
14 InputMediaVideo,
15 Integer,
16 Message,
17 ReplyParameters,
18 ReplyParametersError,
19 },
20};
21
22#[cfg(test)]
23mod tests;
24
25const MIN_GROUP_ATTACHMENTS: usize = 2;
26const MAX_GROUP_ATTACHMENTS: usize = 10;
27
28#[derive(Debug)]
30pub struct MediaGroup {
31 form: Form,
32}
33
34impl MediaGroup {
35 pub fn new<T>(items: T) -> Result<Self, MediaGroupError>
41 where
42 T: IntoIterator<Item = MediaGroupItem>,
43 {
44 let items: Vec<(usize, MediaGroupItem)> = items.into_iter().enumerate().collect();
45
46 let total_items = items.len();
47 if total_items < MIN_GROUP_ATTACHMENTS {
48 return Err(MediaGroupError::NotEnoughAttachments(MIN_GROUP_ATTACHMENTS));
49 }
50 if total_items > MAX_GROUP_ATTACHMENTS {
51 return Err(MediaGroupError::TooManyAttachments(MAX_GROUP_ATTACHMENTS));
52 }
53
54 let mut form = Form::default();
55
56 let mut add_file = |key: String, file: InputFile| -> String {
57 match &file {
58 InputFile::Id(text) | InputFile::Url(text) => text.clone(),
59 _ => {
60 form.insert_field(&key, file);
61 format!("attach://{}", key)
62 }
63 }
64 };
65
66 let mut info = Vec::new();
67 for (idx, item) in items {
68 let media = add_file(format!("tgbot_im_file_{}", idx), item.file);
69 let thumbnail = item
70 .thumbnail
71 .map(|thumbnail| add_file(format!("tgbot_im_thumb_{}", idx), thumbnail));
72 let data = match item.item_type {
73 MediaGroupItemType::Audio(info) => MediaGroupItemData::Audio { media, thumbnail, info },
74 MediaGroupItemType::Document(info) => MediaGroupItemData::Document { media, thumbnail, info },
75 MediaGroupItemType::Photo(info) => MediaGroupItemData::Photo { media, info },
76 MediaGroupItemType::Video(info) => MediaGroupItemData::Video {
77 media,
78 cover: item
79 .cover
80 .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_photo<T>(file: T, metadata: InputMediaPhoto) -> Self
146 where
147 T: Into<InputFile>,
148 {
149 Self::new(file, MediaGroupItemType::Photo(metadata))
150 }
151
152 pub fn for_video<T>(file: T, metadata: InputMediaVideo) -> Self
159 where
160 T: Into<InputFile>,
161 {
162 Self::new(file, MediaGroupItemType::Video(metadata))
163 }
164
165 pub fn with_cover<T>(mut self, value: T) -> Self
173 where
174 T: Into<InputFile>,
175 {
176 self.cover = Some(value.into());
177 self
178 }
179
180 pub fn with_thumbnail<T>(mut self, value: T) -> Self
188 where
189 T: Into<InputFile>,
190 {
191 self.thumbnail = Some(value.into());
192 self
193 }
194
195 fn new<T>(file: T, item_type: MediaGroupItemType) -> Self
196 where
197 T: Into<InputFile>,
198 {
199 Self {
200 item_type,
201 file: file.into(),
202 cover: None,
203 thumbnail: None,
204 }
205 }
206}
207
208#[derive(Debug)]
209enum MediaGroupItemType {
210 Audio(InputMediaAudio),
211 Document(InputMediaDocument),
212 Photo(InputMediaPhoto),
213 Video(InputMediaVideo),
214}
215
216#[serde_with::skip_serializing_none]
217#[derive(Debug, Serialize)]
218#[serde(tag = "type")]
219#[serde(rename_all = "lowercase")]
220enum MediaGroupItemData {
221 Audio {
222 media: String,
223 thumbnail: Option<String>,
224 #[serde(flatten)]
225 info: InputMediaAudio,
226 },
227 Document {
228 media: String,
229 thumbnail: Option<String>,
230 #[serde(flatten)]
231 info: InputMediaDocument,
232 },
233 Photo {
234 media: String,
235 #[serde(flatten)]
236 info: InputMediaPhoto,
237 },
238 Video {
239 media: String,
240 cover: Option<String>,
241 thumbnail: Option<String>,
242 #[serde(flatten)]
243 info: InputMediaVideo,
244 },
245}
246
247#[derive(Debug)]
249pub enum MediaGroupError {
250 NotEnoughAttachments(usize),
252 TooManyAttachments(usize),
254 Serialize(JsonError),
256}
257
258impl Error for MediaGroupError {
259 fn source(&self) -> Option<&(dyn Error + 'static)> {
260 match self {
261 MediaGroupError::Serialize(err) => Some(err),
262 _ => None,
263 }
264 }
265}
266
267impl fmt::Display for MediaGroupError {
268 fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result {
269 match self {
270 MediaGroupError::NotEnoughAttachments(number) => {
271 write!(out, "media group must contain at least {} attachments", number)
272 }
273 MediaGroupError::TooManyAttachments(number) => {
274 write!(out, "media group must contain no more than {} attachments", number)
275 }
276 MediaGroupError::Serialize(err) => write!(out, "can not serialize media group items: {}", err),
277 }
278 }
279}
280
281#[derive(Debug)]
283pub struct SendMediaGroup {
284 form: Form,
285}
286
287impl SendMediaGroup {
288 pub fn new<T>(chat_id: T, media: MediaGroup) -> Self
293 where
294 T: Into<ChatId>,
295 {
296 let mut form: Form = media.into();
297 form.insert_field("chat_id", chat_id.into());
298 Self { form }
299 }
300
301 pub fn with_allow_paid_broadcast(mut self, value: bool) -> Self {
309 self.form.insert_field("allow_paid_broadcast", value);
310 self
311 }
312
313 pub fn with_business_connection_id<T>(mut self, value: T) -> Self
319 where
320 T: Into<String>,
321 {
322 self.form.insert_field("business_connection_id", value.into());
323 self
324 }
325
326 pub fn with_disable_notification(mut self, value: bool) -> Self {
333 self.form.insert_field("disable_notification", value);
334 self
335 }
336
337 pub fn with_message_effect_id<T>(mut self, value: T) -> Self
343 where
344 T: Into<String>,
345 {
346 self.form.insert_field("message_effect_id", value.into());
347 self
348 }
349
350 pub fn with_message_thread_id(mut self, value: Integer) -> Self {
357 self.form.insert_field("message_thread_id", value);
358 self
359 }
360
361 pub fn with_protect_content(mut self, value: bool) -> Self {
368 self.form.insert_field("protect_content", value);
369 self
370 }
371
372 pub fn with_reply_parameters(mut self, value: ReplyParameters) -> Result<Self, ReplyParametersError> {
378 self.form.insert_field("reply_parameters", value.serialize()?);
379 Ok(self)
380 }
381}
382
383impl Method for SendMediaGroup {
384 type Response = Vec<Message>;
385
386 fn into_payload(self) -> Payload {
387 Payload::form("sendMediaGroup", self.form)
388 }
389}