refactor(send): model a payload's media as MediaRef, not media + file_id

MediaItemPayload carried one String field with two meanings and a file_id
bool beside it to say which, in all three variants; every reader re-checked
the flag (input_file had a three-arm pattern just to find the file-id case).
MediaRef::Source/FileId says it once, and the readers now match on it —
local_media_paths, item_url, input_file and the download path each shrank to
the one branch they care about.

Fixes a real failure that the flag was hiding: send_animation built its
InputFile with input_file_for(media_url), which read a cached file id as a
local path and answered "local media file missing" — permanent. So the
second request for a single-gif post always failed (the third worked, from
the degraded entry). It now uses the payload's own input_file, and
a_cached_animation_sends_by_file_id fails without that line.

Wire shape: a queued row from before this change no longer parses, and the
queue already handles that shape (handle_task dead-letters it as an invalid
payload, and dead_letter_notify still names the post and drops the stale
cache entry).
This commit is contained in:
2026-09-21 18:44:26 +08:00
parent 70d4f2ec88
commit aa70b45ae6
7 changed files with 164 additions and 110 deletions
+6 -7
View File
@@ -190,6 +190,7 @@ mod tests {
use super::*;
use crate::ctx::test_support::{TestStores, permanent_error, photo_item};
use crate::media_sender::test_support::MockSender;
use crate::send::MediaRef;
fn queued_task(media: &str, batch_index: usize, sent: Vec<i64>) -> Task {
Task::SendMediaSequence {
@@ -281,8 +282,8 @@ mod tests {
assert_eq!(caption, "fresh caption");
assert!(
matches!(
&media_batches[0][0],
MediaItemPayload::Photo { media, .. } if media == "https://cdn/fresh.jpg"
media_batches[0][0].media_ref(),
MediaRef::Source(media) if media == "https://cdn/fresh.jpg"
),
"fresh media must replace the lost local file"
);
@@ -330,13 +331,11 @@ mod tests {
caption,
..
} => {
let media: Vec<String> = media_batches
let media: Vec<&str> = media_batches
.iter()
.flatten()
.map(|item| match item {
MediaItemPayload::Photo { media, .. }
| MediaItemPayload::Video { media, .. }
| MediaItemPayload::Animation { media, .. } => media.clone(),
.map(|item| match item.media_ref() {
MediaRef::Source(media) | MediaRef::FileId(media) => media.as_str(),
})
.collect();
assert!(!media.is_empty(), "the fresh fetch yielded no media");