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 InputMediaPhoto,
14 InputMediaVideo,
15 Integer,
16 Message,
17 ReplyParameters,
18 ReplyParametersError,
19 },
20};
21
22const MIN_GROUP_ATTACHMENTS: usize = 2;
23const MAX_GROUP_ATTACHMENTS: usize = 10;
24
25#[derive(Debug)]
27pub struct MediaGroup {
28 form: Form,
29}
30
31impl MediaGroup {
32 pub fn new<T>(items: T) -> Result<Self, MediaGroupError>
38 where
39 T: IntoIterator<Item = MediaGroupItem>,
40 {
41 let items: Vec<(usize, MediaGroupItem)> = items.into_iter().enumerate().collect();
42
43 let total_items = items.len();
44 if total_items < MIN_GROUP_ATTACHMENTS {
45 return Err(MediaGroupError::NotEnoughAttachments(MIN_GROUP_ATTACHMENTS));
46 }
47 if total_items > MAX_GROUP_ATTACHMENTS {
48 return Err(MediaGroupError::TooManyAttachments(MAX_GROUP_ATTACHMENTS));
49 }
50
51 let mut form = Form::default();
52
53 let mut add_file = |key: String, file: InputFile| -> String {
54 match &file {
55 InputFile::Id(text) | InputFile::Url(text) => text.clone(),
56 _ => {
57 form.insert_field(&key, file);
58 format!("attach://{key}")
59 }
60 }
61 };
62
63 let mut info = Vec::new();
64 for (idx, item) in items {
65 let media = add_file(format!("tgbot_im_file_{idx}"), item.file);
66 let thumbnail = item
67 .thumbnail
68 .map(|thumbnail| add_file(format!("tgbot_im_thumb_{idx}"), thumbnail));
69 let data = match item.item_type {
70 MediaGroupItemType::Audio(info) => MediaGroupItemData::Audio { media, thumbnail, info },
71 MediaGroupItemType::Document(info) => MediaGroupItemData::Document { media, thumbnail, info },
72 MediaGroupItemType::Photo(info) => MediaGroupItemData::Photo { media, info },
73 MediaGroupItemType::Video(info) => MediaGroupItemData::Video {
74 media,
75 cover: item.cover.map(|cover| add_file(format!("tgbot_im_cover_{idx}"), cover)),
76 thumbnail,
77 info,
78 },
79 };
80 info.push(data);
81 }
82
83 form.insert_field(
84 "media",
85 serde_json::to_string(&info).map_err(MediaGroupError::Serialize)?,
86 );
87
88 Ok(Self { form })
89 }
90}
91
92impl From<MediaGroup> for Form {
93 fn from(group: MediaGroup) -> Self {
94 group.form
95 }
96}
97
98#[derive(Debug)]
100pub struct MediaGroupItem {
101 file: InputFile,
102 item_type: MediaGroupItemType,
103 cover: Option<InputFile>,
104 thumbnail: Option<InputFile>,
105}
106
107impl MediaGroupItem {
108 pub fn for_audio<T>(file: T, metadata: InputMediaAudio) -> Self
115 where
116 T: Into<InputFile>,
117 {
118 Self::new(file, MediaGroupItemType::Audio(metadata))
119 }
120
121 pub fn for_document<T>(file: T, metadata: InputMediaDocument) -> Self
128 where
129 T: Into<InputFile>,
130 {
131 Self::new(file, MediaGroupItemType::Document(metadata))
132 }
133
134 pub fn for_photo<T>(file: T, metadata: InputMediaPhoto) -> Self
141 where
142 T: Into<InputFile>,
143 {
144 Self::new(file, MediaGroupItemType::Photo(metadata))
145 }
146
147 pub fn for_video<T>(file: T, metadata: InputMediaVideo) -> Self
154 where
155 T: Into<InputFile>,
156 {
157 Self::new(file, MediaGroupItemType::Video(metadata))
158 }
159
160 pub fn with_cover<T>(mut self, value: T) -> Self
168 where
169 T: Into<InputFile>,
170 {
171 self.cover = Some(value.into());
172 self
173 }
174
175 pub fn with_thumbnail<T>(mut self, value: T) -> Self
183 where
184 T: Into<InputFile>,
185 {
186 self.thumbnail = Some(value.into());
187 self
188 }
189
190 fn new<T>(file: T, item_type: MediaGroupItemType) -> Self
191 where
192 T: Into<InputFile>,
193 {
194 Self {
195 item_type,
196 file: file.into(),
197 cover: None,
198 thumbnail: None,
199 }
200 }
201}
202
203#[derive(Debug)]
204enum MediaGroupItemType {
205 Audio(InputMediaAudio),
206 Document(InputMediaDocument),
207 Photo(InputMediaPhoto),
208 Video(InputMediaVideo),
209}
210
211#[serde_with::skip_serializing_none]
212#[derive(Debug, Serialize)]
213#[serde(tag = "type")]
214#[serde(rename_all = "lowercase")]
215enum MediaGroupItemData {
216 Audio {
217 media: String,
218 thumbnail: Option<String>,
219 #[serde(flatten)]
220 info: InputMediaAudio,
221 },
222 Document {
223 media: String,
224 thumbnail: Option<String>,
225 #[serde(flatten)]
226 info: InputMediaDocument,
227 },
228 Photo {
229 media: String,
230 #[serde(flatten)]
231 info: InputMediaPhoto,
232 },
233 Video {
234 media: String,
235 cover: Option<String>,
236 thumbnail: Option<String>,
237 #[serde(flatten)]
238 info: InputMediaVideo,
239 },
240}
241
242#[derive(Debug)]
244pub enum MediaGroupError {
245 NotEnoughAttachments(usize),
247 TooManyAttachments(usize),
249 Serialize(JsonError),
251}
252
253impl Error for MediaGroupError {
254 fn source(&self) -> Option<&(dyn Error + 'static)> {
255 match self {
256 MediaGroupError::Serialize(err) => Some(err),
257 _ => None,
258 }
259 }
260}
261
262impl fmt::Display for MediaGroupError {
263 fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result {
264 match self {
265 MediaGroupError::NotEnoughAttachments(number) => {
266 write!(out, "media group must contain at least {number} attachments")
267 }
268 MediaGroupError::TooManyAttachments(number) => {
269 write!(out, "media group must contain no more than {number} attachments")
270 }
271 MediaGroupError::Serialize(err) => write!(out, "can not serialize media group items: {err}"),
272 }
273 }
274}
275
276#[derive(Debug)]
278pub struct SendMediaGroup {
279 form: Form,
280}
281
282impl SendMediaGroup {
283 pub fn new<T>(chat_id: T, media: MediaGroup) -> Self
288 where
289 T: Into<ChatId>,
290 {
291 let mut form: Form = media.into();
292 form.insert_field("chat_id", chat_id.into());
293 Self { form }
294 }
295
296 pub fn with_allow_paid_broadcast(mut self, value: bool) -> Self {
304 self.form.insert_field("allow_paid_broadcast", value);
305 self
306 }
307
308 pub fn with_business_connection_id<T>(mut self, value: T) -> Self
314 where
315 T: Into<String>,
316 {
317 self.form.insert_field("business_connection_id", value.into());
318 self
319 }
320
321 pub fn with_direct_messages_topic_id(mut self, value: Integer) -> Self {
327 self.form.insert_field("direct_messages_topic_id", value);
328 self
329 }
330
331 pub fn with_disable_notification(mut self, value: bool) -> Self {
338 self.form.insert_field("disable_notification", value);
339 self
340 }
341
342 pub fn with_message_effect_id<T>(mut self, value: T) -> Self
348 where
349 T: Into<String>,
350 {
351 self.form.insert_field("message_effect_id", value.into());
352 self
353 }
354
355 pub fn with_message_thread_id(mut self, value: Integer) -> Self {
362 self.form.insert_field("message_thread_id", value);
363 self
364 }
365
366 pub fn with_protect_content(mut self, value: bool) -> Self {
373 self.form.insert_field("protect_content", value);
374 self
375 }
376
377 pub fn with_reply_parameters(mut self, value: ReplyParameters) -> Result<Self, ReplyParametersError> {
383 self.form.insert_field("reply_parameters", value.serialize()?);
384 Ok(self)
385 }
386}
387
388impl Method for SendMediaGroup {
389 type Response = Vec<Message>;
390
391 fn into_payload(self) -> Payload {
392 Payload::form("sendMediaGroup", self.form)
393 }
394}