From 90d421c4fb754445eae4172481ffb6529a3fd649 Mon Sep 17 00:00:00 2001 From: wolfmanster <68727977+wolfmanster@users.noreply.github.com> Date: Thu, 20 Aug 2026 14:04:52 +0800 Subject: [PATCH] fix(android): download original X images --- .../com/xmedia/archive/resolver/XPostResolver.kt | 11 ++++++++++- .../xmedia/archive/resolver/XPostResolverTest.kt | 13 +++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/Android/project/app/src/main/kotlin/com/xmedia/archive/resolver/XPostResolver.kt b/Android/project/app/src/main/kotlin/com/xmedia/archive/resolver/XPostResolver.kt index b704ac76..68127d5f 100644 --- a/Android/project/app/src/main/kotlin/com/xmedia/archive/resolver/XPostResolver.kt +++ b/Android/project/app/src/main/kotlin/com/xmedia/archive/resolver/XPostResolver.kt @@ -317,7 +317,7 @@ class XPostResolver( val item = items.optJSONObject(index) ?: continue val type = item.optString("type") val source = when (type) { - "photo" -> item.optString("media_url_https") + "photo" -> originalPhotoUrl(item.optString("media_url_https")) "video", "animated_gif" -> videoCandidates(item.optJSONObject("video_info")?.optJSONArray("variants")).firstOrNull().orEmpty() else -> "" } @@ -331,6 +331,15 @@ class XPostResolver( } } + /** Requests the original X image instead of the default 1200-pixel rendition. */ + internal fun originalPhotoUrl(rawUrl: String): String { + if (rawUrl.isBlank()) return "" + return rawUrl.toHttpUrl().newBuilder() + .setQueryParameter("name", "orig") + .build() + .toString() + } + /** * Returns only directly downloadable MP4 variants. X also exposes HLS manifests, * which cannot be saved as a video file by the foreground downloader. diff --git a/Android/project/app/src/test/kotlin/com/xmedia/archive/resolver/XPostResolverTest.kt b/Android/project/app/src/test/kotlin/com/xmedia/archive/resolver/XPostResolverTest.kt index e9f99249..a93e7761 100644 --- a/Android/project/app/src/test/kotlin/com/xmedia/archive/resolver/XPostResolverTest.kt +++ b/Android/project/app/src/test/kotlin/com/xmedia/archive/resolver/XPostResolverTest.kt @@ -34,6 +34,18 @@ class XPostResolverTest { } } + @Test + fun requestsOriginalPhotosLikeCobalt() { + assertEquals( + "https://pbs.twimg.com/media/example.jpg?name=orig", + resolver.originalPhotoUrl("https://pbs.twimg.com/media/example.jpg"), + ) + assertEquals( + "https://pbs.twimg.com/media/example?format=jpg&name=orig", + resolver.originalPhotoUrl("https://pbs.twimg.com/media/example?format=jpg&name=small"), + ) + } + @Test fun ranksDirectVariantsByResolutionThenMuxedAudioVideoBitrate() { val variants = listOf( @@ -123,6 +135,7 @@ class XPostResolverTest { val post = resolver.resolve("123") assertEquals("example", post.metadata.username) + assertEquals("https://pbs.twimg.com/media/example.jpg?name=orig", post.media.single().sourceUrl) assertEquals(0, nonSyndicationRequests) }