From ebe7b8bdd77782cd90abc24053308c34917bcd2f Mon Sep 17 00:00:00 2001 From: YoursFunny Date: Mon, 21 Sep 2026 16:59:54 +0800 Subject: [PATCH] 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. --- crates/xmedia-bot/src/send/input_media.rs | 45 +++++++++++--------- crates/xmedia-bot/src/send/upload.rs | 51 ++++------------------- 2 files changed, 34 insertions(+), 62 deletions(-) diff --git a/crates/xmedia-bot/src/send/input_media.rs b/crates/xmedia-bot/src/send/input_media.rs index 1b65735..59981ff 100644 --- a/crates/xmedia-bot/src/send/input_media.rs +++ b/crates/xmedia-bot/src/send/input_media.rs @@ -93,6 +93,31 @@ pub(super) fn animation_media(file: InputFile, caption: Option<&str>, spoiler: b 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 { + 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 /// the caption (Telegram rejects captions on later items). pub(super) fn build_media_group( @@ -104,25 +129,7 @@ pub(super) fn build_media_group( .enumerate() .map(|(i, item)| { let item_caption = if i == 0 { caption } else { None }; - Ok(match item { - 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) - } - }) + media_from(item, item.input_file()?, item_caption, item.thumbnail_url()) }) .collect() } diff --git a/crates/xmedia-bot/src/send/upload.rs b/crates/xmedia-bot/src/send/upload.rs index a0f091b..1f2e039 100644 --- a/crates/xmedia-bot/src/send/upload.rs +++ b/crates/xmedia-bot/src/send/upload.rs @@ -2,7 +2,7 @@ //! itself (hotlink protection), the bot downloads the file, shrinks photos //! 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 crate::media_sender::MediaSender; use crate::photo::{self, MAX_UPLOAD_BYTES, PhotoPrep}; @@ -133,23 +133,8 @@ fn media_from_file( item: &MediaItemPayload, path: std::path::PathBuf, caption: Option<&str>, - thumbnail: Option<&str>, ) -> Result { - let mut media = match item { - 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) + media_from(item, InputFile::file(path), caption, item.thumbnail_url()) } /// Builds the media group item from a (smaller) URL. @@ -157,23 +142,8 @@ fn media_from_url( item: &MediaItemPayload, url: &str, caption: Option<&str>, - thumbnail: Option<&str>, ) -> Result { - let mut media = match item { - 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) + media_from(item, input_file_for(url)?, caption, item.thumbnail_url()) } /// 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). let media_url = item_url(&item); if !media_url.starts_with("http://") && !media_url.starts_with("https://") { - let media = media_from_file( - &item, - std::path::PathBuf::from(media_url), - caption, - item.thumbnail_url(), - ) + let media = media_from_file(&item, std::path::PathBuf::from(media_url), caption) .map_err(|message| FallbackError::Permanent { message })?; return Ok(PreparedItem { index, @@ -252,7 +217,7 @@ pub(super) async fn prepare_upload_item( match prep { PhotoPrep::Upload(upload) => { 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 })?; Ok(PreparedItem { index, @@ -265,7 +230,7 @@ pub(super) async fn prepare_upload_item( message: "photo dimensions exceed Telegram limits and no smaller variant is available" .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 })?; Ok(PreparedItem { index, @@ -276,7 +241,7 @@ pub(super) async fn prepare_upload_item( } } else { 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 })?; Ok(PreparedItem { index, @@ -291,7 +256,7 @@ pub(super) async fn prepare_upload_item( .ok_or_else(|| FallbackError::Permanent { 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 })?; Ok(PreparedItem { index,