twitter: request original image via name=orig

This commit is contained in:
2026-08-04 01:28:21 +08:00
parent 3dff9a2473
commit b6ae88e869
+46 -1
View File
@@ -127,7 +127,7 @@ impl Tweet {
match item.media_type.as_str() { match item.media_type.as_str() {
"photo" => media.push(Media::Illustration { "photo" => media.push(Media::Illustration {
title: None, title: None,
url: item.media_url_https, url: original_twimg_url(&item.media_url_https),
thumbnail_url: None, thumbnail_url: None,
fallback_url: None, fallback_url: None,
}), }),
@@ -198,6 +198,20 @@ fn expand_links(text: &str, urls: &[model::SyndicationEntityUrl]) -> String {
out 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 { fn mp4_variant(item: &model::SyndicationMedia) -> String {
item.video_info item.video_info
.as_ref() .as_ref()
@@ -302,6 +316,16 @@ mod tests {
assert_eq!(fetched.title, "a & b <c>"); assert_eq!(fetched.title, "a & b <c>");
assert!(fetched.sensitive); assert!(fetched.sensitive);
assert_eq!(fetched.media.len(), 2); 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] { match &fetched.media[1] {
Media::Video { url, thumbnail_url, .. } => { Media::Video { url, thumbnail_url, .. } => {
assert_eq!(url, "https://video.twimg.com/v.mp4"); assert_eq!(url, "https://video.twimg.com/v.mp4");
@@ -435,6 +459,27 @@ mod tests {
assert_eq!(tweet.text, text, "full text kept intact"); 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] #[test]
fn syndication_token_matches_js_formula() { fn syndication_token_matches_js_formula() {
// JS: ((861627479294746624 / 1e15) * PI).toString(36) == "236.vrsocvda" // JS: ((861627479294746624 / 1e15) * PI).toString(36) == "236.vrsocvda"