refactor(send): share one task constructor between fresh sends and startup repair

build_send_task and apply_refresh each wrote the same animation-vs-sequence
branch and the same two 13-field literals; Task::from_items takes the
delivery envelope (chat, reply, forward/edit settings, notify targets) once.
The two callers keep computing that envelope from their own source -- chat
settings vs. the queued row being replaced -- so the repair path's delivery
semantics are unchanged.
This commit is contained in:
2026-09-21 16:58:39 +08:00
parent 10a672787a
commit a5187981c7
2 changed files with 88 additions and 59 deletions
+22 -59
View File
@@ -5,7 +5,7 @@ use super::{log_key, reply};
use crate::ctx::{AppContext, CONTEXT};
use crate::link_cache::{CachedMedia, CachedMediaKind, CachedPost};
use crate::media_sender::MediaSender;
use crate::send::{self, MediaItemPayload, Task};
use crate::send::{self, Delivery, MediaItemPayload, Task};
use crate::state::ChatData;
use std::collections::{HashMap, HashSet};
use std::future::Future;
@@ -433,37 +433,20 @@ fn build_send_task(
PostSend::FromChat => (chat_data.edit_before_forward, chat_data.forward_channel_id),
PostSend::Suppressed => (false, None),
};
if items.len() == 1 && matches!(items[0], MediaItemPayload::Animation { .. }) {
Task::SendAnimation {
Task::from_items(
Delivery {
chat_id,
reply_to_message_id,
caption,
animation: items.into_iter().next().unwrap(),
source_url,
edit_before_forward,
forward_channel_id,
notify_chat_id: Some(chat_id),
notify_message_id: Some(reply_to_message_id),
cache_data,
}
} else {
Task::SendMediaSequence {
chat_id,
reply_to_message_id,
caption,
// Photos first so a mixed photo+video group starts with a photo
// (Telegram's sendMediaGroup rule); order within each kind is kept.
media_batches: send::chunk_media_items(send::photos_first(items)),
batch_index: 0,
sent_message_ids: vec![],
source_url,
edit_before_forward,
forward_channel_id,
notify_chat_id: Some(chat_id),
notify_message_id: Some(reply_to_message_id),
cache_data,
}
}
},
source_url,
caption,
items,
cache_data,
)
}
/// The per-URL pipeline: link cache → fetch → build → send → post-send.
@@ -850,40 +833,20 @@ fn apply_refresh(task: &Task, fresh: &Refetched) -> Option<Task> {
Task::ForwardMessages { .. } => return None,
};
let (notify_chat_id, notify_message_id) = task.notify_target();
let items = fresh.items.clone();
let source_url = task.source_url()?.to_string();
Some(
if items.len() == 1 && matches!(items[0], MediaItemPayload::Animation { .. }) {
Task::SendAnimation {
chat_id,
reply_to_message_id,
caption: fresh.caption.clone(),
animation: items.into_iter().next().expect("checked len"),
source_url,
edit_before_forward,
forward_channel_id,
notify_chat_id,
notify_message_id,
cache_data: fresh.cache_data.clone(),
}
} else {
Task::SendMediaSequence {
chat_id,
reply_to_message_id,
caption: fresh.caption.clone(),
media_batches: send::chunk_media_items(send::photos_first(items)),
// A fresh delivery: nothing of this payload has been sent.
batch_index: 0,
sent_message_ids: vec![],
source_url,
edit_before_forward,
forward_channel_id,
notify_chat_id,
notify_message_id,
cache_data: fresh.cache_data.clone(),
}
Some(Task::from_items(
Delivery {
chat_id,
reply_to_message_id,
edit_before_forward,
forward_channel_id,
notify_chat_id,
notify_message_id,
},
)
task.source_url()?.to_string(),
fresh.caption.clone(),
fresh.items.clone(),
fresh.cache_data.clone(),
))
}
/// Fetches the post again and maps it into [`Refetched`]: the same mapping the
+66
View File
@@ -133,7 +133,73 @@ pub enum Task {
},
}
/// The delivery envelope of a task built from ready-made items: who receives
/// the media, where a failure notice goes, and whether the chat's
/// edit-before-forward / channel-forward settings apply. The fresh-send path
/// fills it from the chat's settings, the startup repair from the queued row it
/// replaces, so the two shapes cannot drift apart.
pub(crate) struct Delivery {
pub chat_id: i64,
pub reply_to_message_id: i64,
pub edit_before_forward: bool,
pub forward_channel_id: Option<i64>,
pub notify_chat_id: Option<i64>,
pub notify_message_id: Option<i64>,
}
impl Task {
/// A send task for ready-made `items`: a lone animation takes the
/// SendAnimation path, everything else the media sequence (photos first,
/// chunked). The one place that shape is written.
pub(crate) fn from_items(
delivery: Delivery,
source_url: String,
caption: String,
items: Vec<MediaItemPayload>,
cache_data: Option<CachedPost>,
) -> Task {
let Delivery {
chat_id,
reply_to_message_id,
edit_before_forward,
forward_channel_id,
notify_chat_id,
notify_message_id,
} = delivery;
if items.len() == 1 && matches!(items[0], MediaItemPayload::Animation { .. }) {
Task::SendAnimation {
chat_id,
reply_to_message_id,
caption,
animation: items.into_iter().next().unwrap(),
source_url,
edit_before_forward,
forward_channel_id,
notify_chat_id,
notify_message_id,
cache_data,
}
} else {
Task::SendMediaSequence {
chat_id,
reply_to_message_id,
caption,
// Photos first so a mixed photo+video group starts with a photo
// (Telegram's sendMediaGroup rule); order within each kind is kept.
media_batches: chunk_media_items(photos_first(items)),
// A fresh delivery: nothing of this payload has been sent.
batch_index: 0,
sent_message_ids: vec![],
source_url,
edit_before_forward,
forward_channel_id,
notify_chat_id,
notify_message_id,
cache_data,
}
}
}
fn cache_data(&self) -> Option<&CachedPost> {
match self {
Task::SendMediaSequence { cache_data, .. } | Task::SendAnimation { cache_data, .. } => {