From b6ae88e869c0352f9f7b78b3689b060406cef98f Mon Sep 17 00:00:00 2001 From: YoursFunny Date: Tue, 4 Aug 2026 01:28:21 +0800 Subject: [PATCH] twitter: request original image via name=orig --- crates/x-media/src/site/twitter/interface.rs | 47 +++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/crates/x-media/src/site/twitter/interface.rs b/crates/x-media/src/site/twitter/interface.rs index d2e9c59..479599f 100644 --- a/crates/x-media/src/site/twitter/interface.rs +++ b/crates/x-media/src/site/twitter/interface.rs @@ -127,7 +127,7 @@ impl Tweet { match item.media_type.as_str() { "photo" => media.push(Media::Illustration { title: None, - url: item.media_url_https, + url: original_twimg_url(&item.media_url_https), thumbnail_url: None, fallback_url: None, }), @@ -198,6 +198,20 @@ fn expand_links(text: &str, urls: &[model::SyndicationEntityUrl]) -> String { out } +/// pbs.twimg.com serves a reduced default size without size params; `name=orig` +/// returns the original file (fxtwitter used to hand out the original +/// directly, the syndication API does not). Non-twimg URLs pass through +/// unchanged. +fn original_twimg_url(url: &str) -> String { + if url.starts_with("https://pbs.twimg.com/") + && (url.ends_with(".jpg") || url.ends_with(".png")) + { + format!("{url}?name=orig") + } else { + url.to_string() + } +} + fn mp4_variant(item: &model::SyndicationMedia) -> String { item.video_info .as_ref() @@ -302,6 +316,16 @@ mod tests { assert_eq!(fetched.title, "a & b "); assert!(fetched.sensitive); assert_eq!(fetched.media.len(), 2); + match &fetched.media[0] { + Media::Illustration { url, .. } => { + // Photo URL is rewritten to request the original file. + assert_eq!( + url, + "https://pbs.twimg.com/media/photo.jpg?name=orig" + ); + } + other => panic!("expected illustration, got {other:?}"), + } match &fetched.media[1] { Media::Video { url, thumbnail_url, .. } => { assert_eq!(url, "https://video.twimg.com/v.mp4"); @@ -435,6 +459,27 @@ mod tests { assert_eq!(tweet.text, text, "full text kept intact"); } + #[test] + fn original_twimg_url_rewrites_photo_urls() { + assert_eq!( + original_twimg_url("https://pbs.twimg.com/media/C_UdnvPUwAE3Dnn.jpg"), + "https://pbs.twimg.com/media/C_UdnvPUwAE3Dnn.jpg?name=orig" + ); + assert_eq!( + original_twimg_url("https://pbs.twimg.com/media/abc.png"), + "https://pbs.twimg.com/media/abc.png?name=orig" + ); + // Non-twimg URLs (videos, animated gifs) pass through unchanged. + assert_eq!( + original_twimg_url("https://video.twimg.com/v.mp4"), + "https://video.twimg.com/v.mp4" + ); + assert_eq!( + original_twimg_url("https://pbs.twimg.com/media/abc.webp"), + "https://pbs.twimg.com/media/abc.webp" + ); + } + #[test] fn syndication_token_matches_js_formula() { // JS: ((861627479294746624 / 1e15) * PI).toString(36) == "236.vrsocvda"