use std::{collections::HashMap, sync::Mutex};
use actix_multipart::Multipart;
use actix_web::{error::ErrorBadRequest, web, Responder};
use rand::distr::{Alphanumeric, SampleString};
use serde::Deserialize;
use serde_json::Value;
use teloxide::types::{
BusinessConnectionId, EffectId, FileId, FileUniqueId, Me, MediaGroupId, Message, MessageEntity,
MessageId, ParseMode, ReplyParameters, Seconds,
};
use super::{
get_raw_multipart_fields, make_telegram_result, Attachment, BodyChatId, MediaGroupInputMedia,
MediaGroupInputMediaAudio, MediaGroupInputMediaDocument, MediaGroupInputMediaPhoto,
MediaGroupInputMediaVideo,
};
use crate::{
server::{routes::check_if_message_exists, SentMediaGroup},
state::State,
MockMessageAudio, MockMessageDocument, MockMessagePhoto, MockMessageVideo, MockPhotoSize,
MockVideo,
};
pub async fn send_media_group(
mut payload: Multipart,
me: web::Data<Me>,
state: web::Data<Mutex<State>>,
) -> impl Responder {
let (fields, attachments) = get_raw_multipart_fields(&mut payload).await;
let mut lock = state.lock().unwrap();
let body = SendMediaGroupBody::serialize_raw_fields(&fields, &attachments).unwrap();
if body.media.len() > 10 {
return ErrorBadRequest("Too many media items").into();
} else if body.media.len() < 2 {
return ErrorBadRequest("Too few media items").into();
}
let chat = body.chat_id.chat();
let protect_content = body.protect_content;
let message_effect_id = body.message_effect_id.clone();
let business_connection_id = body.business_connection_id.clone();
let mut reply_to_message = None;
if let Some(reply_parameters) = &body.reply_parameters {
check_if_message_exists!(lock, reply_parameters.message_id.0);
reply_to_message = Some(Box::new(
lock.messages
.get_message(reply_parameters.message_id.0)
.unwrap(),
));
}
let media_group_id = MediaGroupId(Alphanumeric.sample_string(&mut rand::rng(), 16));
let mut messages: Vec<Message> = vec![];
for media in &body.media {
let file_id = FileId(Alphanumeric.sample_string(&mut rand::rng(), 16));
let file_unique_id = FileUniqueId(Alphanumeric.sample_string(&mut rand::rng(), 8));
let last_id = lock.messages.max_message_id();
let message: Message;
match media {
MediaGroupInputMedia::InputMediaAudio(audio) => {
let mut mock_message = MockMessageAudio::new();
mock_message.chat = chat.clone();
mock_message.from = Some(me.user.clone());
mock_message.has_protected_content = protect_content.unwrap_or(false);
mock_message.reply_to_message = reply_to_message.clone();
mock_message.caption = audio.caption.clone();
mock_message.caption_entities = audio.caption_entities.clone().unwrap_or_default();
mock_message.media_group_id = Some(media_group_id.clone());
mock_message.performer = audio.performer.clone();
mock_message.title = audio.title.clone();
mock_message.duration = audio.duration.unwrap_or(Seconds::from_seconds(1));
mock_message.effect_id = message_effect_id.clone();
mock_message.business_connection_id = business_connection_id.clone();
mock_message.file_name = Some(audio.file_name.clone());
mock_message.file_id = file_id;
mock_message.file_unique_id = file_unique_id;
mock_message.file_size = audio.file_data.bytes().len() as u32;
mock_message.mime_type = mime_guess::from_path(&audio.file_name).first();
mock_message.id = MessageId(last_id + 1);
message = mock_message.build();
lock.files.push(teloxide::types::File {
meta: message.audio().unwrap().file.clone(),
path: audio.file_name.clone(),
});
}
MediaGroupInputMedia::InputMediaDocument(document) => {
let mut mock_message = MockMessageDocument::new();
mock_message.chat = chat.clone();
mock_message.from = Some(me.user.clone());
mock_message.has_protected_content = protect_content.unwrap_or(false);
mock_message.reply_to_message = reply_to_message.clone();
mock_message.caption = document.caption.clone();
mock_message.caption_entities =
document.caption_entities.clone().unwrap_or_default();
mock_message.media_group_id = Some(media_group_id.clone());
mock_message.effect_id = message_effect_id.clone();
mock_message.business_connection_id = business_connection_id.clone();
mock_message.file_name = Some(document.file_name.clone());
mock_message.file_id = file_id;
mock_message.file_unique_id = file_unique_id;
mock_message.file_size = document.file_data.bytes().len() as u32;
mock_message.mime_type = mime_guess::from_path(&document.file_name).first();
mock_message.id = MessageId(last_id + 1);
message = mock_message.build();
lock.files.push(teloxide::types::File {
meta: message.document().unwrap().file.clone(),
path: document.file_name.clone(),
});
}
MediaGroupInputMedia::InputMediaPhoto(photo) => {
let mut mock_message = MockMessagePhoto::new();
mock_message.chat = chat.clone();
mock_message.from = Some(me.user.clone());
mock_message.has_protected_content = protect_content.unwrap_or(false);
mock_message.reply_to_message = reply_to_message.clone();
mock_message.caption = photo.caption.clone();
mock_message.caption_entities = photo.caption_entities.clone().unwrap_or_default();
mock_message.media_group_id = Some(media_group_id.clone());
mock_message.effect_id = message_effect_id.clone();
mock_message.business_connection_id = business_connection_id.clone();
let mut mock_photo = MockPhotoSize::new();
mock_photo.file_id = file_id;
mock_photo.file_unique_id = file_unique_id;
mock_photo.file_size = photo.file_data.bytes().len() as u32;
mock_message.photo = vec![mock_photo.build()];
mock_message.id = MessageId(last_id + 1);
message = mock_message.build();
lock.files.push(teloxide::types::File {
meta: message.photo().unwrap().first().unwrap().clone().file,
path: photo.file_name.clone(),
});
}
MediaGroupInputMedia::InputMediaVideo(video) => {
let mut mock_message = MockMessageVideo::new();
mock_message.chat = chat.clone();
mock_message.from = Some(me.user.clone());
mock_message.has_protected_content = protect_content.unwrap_or(false);
mock_message.reply_to_message = reply_to_message.clone();
mock_message.caption = video.caption.clone();
mock_message.caption_entities = video.caption_entities.clone().unwrap_or_default();
mock_message.media_group_id = Some(media_group_id.clone());
mock_message.effect_id = message_effect_id.clone();
mock_message.business_connection_id = business_connection_id.clone();
let mut mock_video = MockVideo::new();
mock_video.mime_type = mime_guess::from_path(&video.file_name).first();
mock_video.width = video.width.unwrap_or(100);
mock_video.height = video.height.unwrap_or(100);
mock_video.duration = video.duration.unwrap_or(Seconds::from_seconds(1));
mock_video.file_id = file_id;
mock_video.file_unique_id = file_unique_id;
mock_video.file_size = video.file_data.bytes().len() as u32;
mock_video.file_name = Some(video.file_name.clone());
mock_message.video = mock_video.build();
mock_message.id = MessageId(last_id + 1);
message = mock_message.build();
lock.files.push(teloxide::types::File {
meta: message.video().unwrap().file.clone(),
path: video.file_name.clone(),
});
}
}
messages.push(message.clone());
lock.messages.add_message(message);
}
lock.responses.sent_messages.extend(messages.clone());
lock.responses.sent_media_group.push(SentMediaGroup {
messages: messages.clone(),
bot_request: body,
});
make_telegram_result(messages)
}
#[derive(Debug, Clone, Deserialize)]
pub struct SendMediaGroupBody {
pub chat_id: BodyChatId,
pub message_thread_id: Option<i64>,
pub media: Vec<MediaGroupInputMedia>,
pub disable_notification: Option<bool>,
pub protect_content: Option<bool>,
pub message_effect_id: Option<EffectId>,
pub reply_parameters: Option<ReplyParameters>,
pub business_connection_id: Option<BusinessConnectionId>,
}
impl SendMediaGroupBody {
fn serialize_raw_fields(
fields: &HashMap<String, String>,
attachments: &HashMap<String, Attachment>,
) -> Option<Self> {
let raw_media: Vec<Value> = serde_json::from_str(fields.get("media")?).ok()?;
let mut media: Vec<MediaGroupInputMedia> = vec![];
for raw_media_item in raw_media.iter() {
let raw_media_string = raw_media_item.get("media").unwrap().as_str().unwrap();
let file_name;
let file_data;
if raw_media_string.starts_with("attach://") {
let raw_name = raw_media_string.strip_prefix("attach://").unwrap();
let attachment = attachments
.values()
.find(|a| a.raw_name == raw_name)
.expect("No attachment was found!");
file_name = Some(attachment.file_name.clone());
file_data = attachment.file_data.clone();
} else {
file_name = None;
file_data = raw_media_item.get("media").unwrap().to_string();
}
let media_type = raw_media_item.get("type").unwrap();
let caption = raw_media_item
.get("caption")
.map(|s| serde_json::from_value(s.clone()).unwrap());
let parse_mode: Option<ParseMode> = raw_media_item
.get("parse_mode")
.map(|s| serde_json::from_value(s.clone()).unwrap());
let caption_entities: Option<Vec<MessageEntity>> = raw_media_item
.get("caption_entities")
.map(|s| serde_json::from_value(s.clone()).unwrap());
let duration: Option<Seconds> = raw_media_item
.get("duration")
.map(|s| serde_json::from_value(s.clone()).unwrap());
let performer = raw_media_item
.get("performer")
.map(|s| serde_json::from_value(s.clone()).unwrap());
let title = raw_media_item
.get("title")
.map(|s| serde_json::from_value(s.clone()).unwrap());
let disable_content_type_detection: Option<bool> = raw_media_item
.get("disable_content_type_detection")
.map(|s| serde_json::from_value(s.clone()).unwrap());
let show_caption_above_media: Option<bool> = raw_media_item
.get("show_caption_above_media")
.map(|s| serde_json::from_value(s.clone()).unwrap());
let has_spoiler: Option<bool> = raw_media_item
.get("has_spoiler")
.map(|s| serde_json::from_value(s.clone()).unwrap());
let width: Option<u32> = raw_media_item
.get("width")
.map(|s| serde_json::from_value(s.clone()).unwrap());
let height: Option<u32> = raw_media_item
.get("height")
.map(|s| serde_json::from_value(s.clone()).unwrap());
let supports_streaming: Option<bool> = raw_media_item
.get("supports_streaming")
.map(|s| serde_json::from_value(s.clone()).unwrap());
if media_type == "audio" {
media.push(MediaGroupInputMedia::InputMediaAudio(
MediaGroupInputMediaAudio {
r#type: "audio".to_string(),
file_name: file_name.unwrap_or("no_name.mp3".to_string()),
file_data,
caption,
parse_mode,
caption_entities,
duration,
performer,
title,
},
));
} else if media_type == "document" {
media.push(MediaGroupInputMedia::InputMediaDocument(
MediaGroupInputMediaDocument {
r#type: "document".to_string(),
file_name: file_name.unwrap_or("no_name.txt".to_string()),
file_data,
caption,
parse_mode,
caption_entities,
disable_content_type_detection,
},
));
} else if media_type == "photo" {
media.push(MediaGroupInputMedia::InputMediaPhoto(
MediaGroupInputMediaPhoto {
r#type: "photo".to_string(),
file_name: file_name.unwrap_or("no_name.jpg".to_string()),
file_data,
caption,
parse_mode,
caption_entities,
show_caption_above_media,
has_spoiler,
},
));
} else if media_type == "video" {
media.push(MediaGroupInputMedia::InputMediaVideo(
MediaGroupInputMediaVideo {
r#type: "video".to_string(),
file_name: file_name.unwrap_or("no_name.mp4".to_string()),
file_data,
caption,
parse_mode,
caption_entities,
duration,
supports_streaming,
show_caption_above_media,
width,
height,
has_spoiler,
},
));
} else {
panic!("Unknown media type: {}", media_type);
}
}
Some(Self {
chat_id: serde_json::from_str(&fields.get("chat_id").unwrap().clone()).unwrap(),
message_thread_id: fields.get("message_thread_id").map(|s| s.parse().unwrap()),
media,
disable_notification: fields
.get("disable_notification")
.map(|s| s.parse().unwrap()),
protect_content: fields.get("protect_content").map(|s| s.parse().unwrap()),
message_effect_id: fields
.get("message_effect_id")
.map(|s| s.to_string().into()),
reply_parameters: fields
.get("reply_parameters")
.map(|s| serde_json::from_str(s).unwrap()),
business_connection_id: fields
.get("business_connection_id")
.map(|s| serde_json::from_str(s).unwrap()),
})
}
}