refactor(send): one payload to InputMedia dispatch instead of three

media_from_file, media_from_url and build_media_group's closure each wrote
the same per-kind match plus the same video-thumbnail attach. media_from
takes the already-selected InputFile; the two builders that differ only in
how that file is chosen are now two-line calls to it. The local-file branch
keeps InputFile::file (no existence probe) and every caller still passes the
item's own has_spoiler / thumbnail_url, so what reaches Telegram is the same.
This commit is contained in:
2026-09-21 16:59:54 +08:00
parent a5187981c7
commit ebe7b8bdd7
2 changed files with 34 additions and 62 deletions
+26 -19
View File
@@ -93,6 +93,31 @@ pub(super) fn animation_media(file: InputFile, caption: Option<&str>, spoiler: b
InputMedia::Animation(animation) InputMedia::Animation(animation)
} }
/// Builds one media-group item around an already-selected file: the per-kind
/// `InputMedia` (same spoiler/caption handling) plus the video's thumbnail,
/// which Telegram takes as a separate upload/URL. The one place that dispatch
/// is written; callers only choose the `InputFile`.
pub(super) fn media_from(
item: &MediaItemPayload,
file: InputFile,
caption: Option<&str>,
thumbnail: Option<&str>,
) -> Result<InputMedia, String> {
let media = match item {
MediaItemPayload::Photo { has_spoiler, .. } => photo_media(file, caption, *has_spoiler),
MediaItemPayload::Video { has_spoiler, .. } => video_media(file, caption, *has_spoiler),
MediaItemPayload::Animation { has_spoiler, .. } => {
animation_media(file, caption, *has_spoiler)
}
};
match (thumbnail, media) {
(Some(thumb), InputMedia::Video(video)) => {
Ok(InputMedia::Video(video.thumbnail(input_file_for(thumb)?)))
}
(_, media) => Ok(media),
}
}
/// Builds a media group from payloads; only the first item of the batch gets /// Builds a media group from payloads; only the first item of the batch gets
/// the caption (Telegram rejects captions on later items). /// the caption (Telegram rejects captions on later items).
pub(super) fn build_media_group( pub(super) fn build_media_group(
@@ -104,25 +129,7 @@ pub(super) fn build_media_group(
.enumerate() .enumerate()
.map(|(i, item)| { .map(|(i, item)| {
let item_caption = if i == 0 { caption } else { None }; let item_caption = if i == 0 { caption } else { None };
Ok(match item { media_from(item, item.input_file()?, item_caption, item.thumbnail_url())
MediaItemPayload::Photo { has_spoiler, .. } => {
photo_media(item.input_file()?, item_caption, *has_spoiler)
}
MediaItemPayload::Video {
has_spoiler,
thumbnail,
..
} => {
let mut video = video_media(item.input_file()?, item_caption, *has_spoiler);
if let (Some(thumb), InputMedia::Video(v)) = (thumbnail, &mut video) {
*v = v.clone().thumbnail(input_file_for(thumb)?);
}
video
}
MediaItemPayload::Animation { has_spoiler, .. } => {
animation_media(item.input_file()?, item_caption, *has_spoiler)
}
})
}) })
.collect() .collect()
} }
+8 -43
View File
@@ -2,7 +2,7 @@
//! itself (hotlink protection), the bot downloads the file, shrinks photos //! itself (hotlink protection), the bot downloads the file, shrinks photos
//! that exceed Telegram's limits and uploads the batch via multipart. //! that exceed Telegram's limits and uploads the batch via multipart.
use super::input_media::{animation_media, input_file_for, item_url, photo_media, video_media}; use super::input_media::{input_file_for, item_url, media_from};
use super::{MediaItemPayload, SendError, Task, classify_to_send_error, retry_delay_seconds}; use super::{MediaItemPayload, SendError, Task, classify_to_send_error, retry_delay_seconds};
use crate::media_sender::MediaSender; use crate::media_sender::MediaSender;
use crate::photo::{self, MAX_UPLOAD_BYTES, PhotoPrep}; use crate::photo::{self, MAX_UPLOAD_BYTES, PhotoPrep};
@@ -133,23 +133,8 @@ fn media_from_file(
item: &MediaItemPayload, item: &MediaItemPayload,
path: std::path::PathBuf, path: std::path::PathBuf,
caption: Option<&str>, caption: Option<&str>,
thumbnail: Option<&str>,
) -> Result<InputMedia, String> { ) -> Result<InputMedia, String> {
let mut media = match item { media_from(item, InputFile::file(path), caption, item.thumbnail_url())
MediaItemPayload::Photo { has_spoiler, .. } => {
photo_media(InputFile::file(path), caption, *has_spoiler)
}
MediaItemPayload::Video { has_spoiler, .. } => {
video_media(InputFile::file(path), caption, *has_spoiler)
}
MediaItemPayload::Animation { has_spoiler, .. } => {
animation_media(InputFile::file(path), caption, *has_spoiler)
}
};
if let (Some(thumb), InputMedia::Video(v)) = (thumbnail, &mut media) {
*v = v.clone().thumbnail(input_file_for(thumb)?);
}
Ok(media)
} }
/// Builds the media group item from a (smaller) URL. /// Builds the media group item from a (smaller) URL.
@@ -157,23 +142,8 @@ fn media_from_url(
item: &MediaItemPayload, item: &MediaItemPayload,
url: &str, url: &str,
caption: Option<&str>, caption: Option<&str>,
thumbnail: Option<&str>,
) -> Result<InputMedia, String> { ) -> Result<InputMedia, String> {
let mut media = match item { media_from(item, input_file_for(url)?, caption, item.thumbnail_url())
MediaItemPayload::Photo { has_spoiler, .. } => {
photo_media(input_file_for(url)?, caption, *has_spoiler)
}
MediaItemPayload::Video { has_spoiler, .. } => {
video_media(input_file_for(url)?, caption, *has_spoiler)
}
MediaItemPayload::Animation { has_spoiler, .. } => {
animation_media(input_file_for(url)?, caption, *has_spoiler)
}
};
if let (Some(thumb), InputMedia::Video(v)) = (thumbnail, &mut media) {
*v = v.clone().thumbnail(input_file_for(thumb)?);
}
Ok(media)
} }
/// One item prepared for the upload fallback: the ready-to-send media plus /// One item prepared for the upload fallback: the ready-to-send media plus
@@ -199,12 +169,7 @@ pub(super) async fn prepare_upload_item(
// permanent (a video cannot be re-encoded here). // permanent (a video cannot be re-encoded here).
let media_url = item_url(&item); let media_url = item_url(&item);
if !media_url.starts_with("http://") && !media_url.starts_with("https://") { if !media_url.starts_with("http://") && !media_url.starts_with("https://") {
let media = media_from_file( let media = media_from_file(&item, std::path::PathBuf::from(media_url), caption)
&item,
std::path::PathBuf::from(media_url),
caption,
item.thumbnail_url(),
)
.map_err(|message| FallbackError::Permanent { message })?; .map_err(|message| FallbackError::Permanent { message })?;
return Ok(PreparedItem { return Ok(PreparedItem {
index, index,
@@ -252,7 +217,7 @@ pub(super) async fn prepare_upload_item(
match prep { match prep {
PhotoPrep::Upload(upload) => { PhotoPrep::Upload(upload) => {
let path = upload.path().to_path_buf(); let path = upload.path().to_path_buf();
let media = media_from_file(&item, path, caption, item.thumbnail_url()) let media = media_from_file(&item, path, caption)
.map_err(|message| FallbackError::Permanent { message })?; .map_err(|message| FallbackError::Permanent { message })?;
Ok(PreparedItem { Ok(PreparedItem {
index, index,
@@ -265,7 +230,7 @@ pub(super) async fn prepare_upload_item(
message: "photo dimensions exceed Telegram limits and no smaller variant is available" message: "photo dimensions exceed Telegram limits and no smaller variant is available"
.into(), .into(),
})?; })?;
let media = media_from_url(&item, url, caption, item.thumbnail_url()) let media = media_from_url(&item, url, caption)
.map_err(|message| FallbackError::Permanent { message })?; .map_err(|message| FallbackError::Permanent { message })?;
Ok(PreparedItem { Ok(PreparedItem {
index, index,
@@ -276,7 +241,7 @@ pub(super) async fn prepare_upload_item(
} }
} else { } else {
let path = file.path().to_path_buf(); let path = file.path().to_path_buf();
let media = media_from_file(&item, path, caption, item.thumbnail_url()) let media = media_from_file(&item, path, caption)
.map_err(|message| FallbackError::Permanent { message })?; .map_err(|message| FallbackError::Permanent { message })?;
Ok(PreparedItem { Ok(PreparedItem {
index, index,
@@ -291,7 +256,7 @@ pub(super) async fn prepare_upload_item(
.ok_or_else(|| FallbackError::Permanent { .ok_or_else(|| FallbackError::Permanent {
message: "media too large".into(), message: "media too large".into(),
})?; })?;
let media = media_from_url(&item, url, caption, item.thumbnail_url()) let media = media_from_url(&item, url, caption)
.map_err(|message| FallbackError::Permanent { message })?; .map_err(|message| FallbackError::Permanent { message })?;
Ok(PreparedItem { Ok(PreparedItem {
index, index,