mirror of
https://github.com/TheFunny/TelegramTwitterMediaBot.git
synced 2026-10-03 00:52:10 +00:00
fix(send): refuse a file id at the upload fallback's door
download_to_temp guarded against a file id, but prepare_upload_item answered a non-http media_url first — and item_url maps FileId to the id itself, so the id was presented as a local *path* and died later with a confusing file-open error instead of a classification. The guard now sits at the entry, download_to_temp takes the already-narrowed URL (the inner guard deleted as dead), and the test pins the refusal as Permanent before any I/O. The file also carries item10's download-classification arm (RateLimited is retryable there too) — same file, landed here to keep each tree compiling.
This commit is contained in:
@@ -89,17 +89,10 @@ pub(super) enum FallbackError {
|
|||||||
/// errors are not.
|
/// errors are not.
|
||||||
async fn download_to_temp(
|
async fn download_to_temp(
|
||||||
item: &MediaItemPayload,
|
item: &MediaItemPayload,
|
||||||
|
media_url: &str,
|
||||||
) -> Result<(NamedTempFile, bytes::Bytes), FallbackError> {
|
) -> Result<(NamedTempFile, bytes::Bytes), FallbackError> {
|
||||||
// Only a URL/path item is ever downloaded: a file id is sent as-is (see
|
// The caller narrows the media to a source URL before calling (its entry
|
||||||
// `MediaItemPayload::input_file`), so this path cannot see one.
|
// guard rejects a file id), so there is nothing to match on here.
|
||||||
let media_url = match item.media_ref() {
|
|
||||||
MediaRef::Source(media) => media,
|
|
||||||
MediaRef::FileId(id) => {
|
|
||||||
return Err(FallbackError::Permanent {
|
|
||||||
message: format!("file id reached the download path: {id}"),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// Photos are downloaded even over the upload cap so `prepare_photo` can
|
// Photos are downloaded even over the upload cap so `prepare_photo` can
|
||||||
// downscale / transcode them, up to their own download cap; videos and
|
// downscale / transcode them, up to their own download cap; videos and
|
||||||
// animations are refused as soon as the declared size crosses their own
|
// animations are refused as soon as the declared size crosses their own
|
||||||
@@ -177,9 +170,11 @@ async fn download_to_temp(
|
|||||||
/// retry could only ask the same URL again.
|
/// retry could only ask the same URL again.
|
||||||
fn classify_download_error(err: FetchError) -> FallbackError {
|
fn classify_download_error(err: FetchError) -> FallbackError {
|
||||||
match err {
|
match err {
|
||||||
FetchError::Http(_) | FetchError::Transient(_) => FallbackError::Retryable {
|
FetchError::Http(_) | FetchError::Transient(_) | FetchError::RateLimited { .. } => {
|
||||||
delay_seconds: retry_delay_seconds(0),
|
FallbackError::Retryable {
|
||||||
},
|
delay_seconds: retry_delay_seconds(0),
|
||||||
|
}
|
||||||
|
}
|
||||||
FetchError::TooLarge => FallbackError::MediaTooLarge,
|
FetchError::TooLarge => FallbackError::MediaTooLarge,
|
||||||
e => FallbackError::Permanent {
|
e => FallbackError::Permanent {
|
||||||
message: format!("download failed: {e}"),
|
message: format!("download failed: {e}"),
|
||||||
@@ -222,6 +217,15 @@ pub(super) async fn prepare_upload_item(
|
|||||||
index: usize,
|
index: usize,
|
||||||
caption: Option<&str>,
|
caption: Option<&str>,
|
||||||
) -> Result<PreparedItem, FallbackError> {
|
) -> Result<PreparedItem, FallbackError> {
|
||||||
|
// A file id is already Telegram's copy of an uploaded file: there is no
|
||||||
|
// URL to re-fetch, and without this guard `item_url` presents the id as
|
||||||
|
// a *path*, which fails at upload time with a confusing open error
|
||||||
|
// instead of a classification. Re-upload cannot apply to it.
|
||||||
|
if matches!(item.media_ref(), MediaRef::FileId(_)) {
|
||||||
|
return Err(FallbackError::Permanent {
|
||||||
|
message: "file id reached the upload fallback".into(),
|
||||||
|
});
|
||||||
|
}
|
||||||
// Locally produced files (ugoira / bsky remux MP4): nothing to download
|
// Locally produced files (ugoira / bsky remux MP4): nothing to download
|
||||||
// or shrink — upload the file directly. The send is a multipart upload,
|
// or shrink — upload the file directly. The send is a multipart upload,
|
||||||
// so the only remaining failure is an upload-cap error, which is
|
// so the only remaining failure is an upload-cap error, which is
|
||||||
@@ -243,7 +247,7 @@ pub(super) async fn prepare_upload_item(
|
|||||||
// the match below. A separate size probe used to issue a second GET of the
|
// the match below. A separate size probe used to issue a second GET of the
|
||||||
// same URL for an answer this path already has (and issued it for photos,
|
// same URL for an answer this path already has (and issued it for photos,
|
||||||
// whose answer was discarded one line later).
|
// whose answer was discarded one line later).
|
||||||
match download_to_temp(&item).await {
|
match download_to_temp(&item, media_url).await {
|
||||||
Ok((file, bytes)) => {
|
Ok((file, bytes)) => {
|
||||||
if matches!(item, MediaItemPayload::Photo { .. }) {
|
if matches!(item, MediaItemPayload::Photo { .. }) {
|
||||||
// Telegram rejects photos wider+taller than 10000 px combined
|
// Telegram rejects photos wider+taller than 10000 px combined
|
||||||
@@ -422,4 +426,18 @@ mod download_class_tests {
|
|||||||
FallbackError::MediaTooLarge
|
FallbackError::MediaTooLarge
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn a_file_id_item_is_refused_before_any_download() {
|
||||||
|
let item = MediaItemPayload::Photo {
|
||||||
|
media: MediaRef::FileId("AgACAgIAAx".into()),
|
||||||
|
has_spoiler: false,
|
||||||
|
fallback_url: None,
|
||||||
|
};
|
||||||
|
match prepare_upload_item(item, 0, None).await {
|
||||||
|
Err(FallbackError::Permanent { .. }) => {}
|
||||||
|
Err(_) => panic!("expected a permanent classification, got a different error"),
|
||||||
|
Ok(_) => panic!("a file id must be refused, not prepared"),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user