fix: Emulate removed Redgifs /info endpoint - #100
Conversation
Redgifs removed the /info endpoint, which was only used to populate the legacy "user-addr" query parameter on gif requests. Since that endpoint now 404s, apps whose legacy Redgifs flow depends on it (e.g. Sync for Reddit) never reach the actual gif-fetch request, surfacing as a connection error instead of playing the video. Emulate the endpoint locally the same way the old OAuth endpoint is already emulated, since current Redgifs endpoints accept but ignore the "user-addr" value populated from it.
|
FYI for whoever picks this up: Root cause: Not including that bump in this PR since it's unrelated to the Redgifs fix, but wanted to flag it since it's currently blocking CI on every PR into In the meantime, I published an unofficial build with this fix + the version bump for anyone who wants to use it before this gets reviewed: https://github.com/KingOfPoptart/patcheddit/releases/tag/redgifs-fix-v1 |
Logs each intercepted request's path, which branch handled it (local emulation vs proxied to the network), and on failure the actual exception (DNS/TLS/timeout/etc.) or HTTP status code -- previously a proceed() failure on the network paths propagated silently with no log line at all, making it hard to tell why a user's Redgifs requests were failing (e.g. reports of failures specifically over VPN). Uses Logger.printInfo/printException, which write to logcat and the extension log buffer unconditionally (unlike printDebug, they aren't gated behind the DEBUG setting).
RedgifsToken.isValid() only checks time-based expiry, never whether the server actually accepted the token. When a request using the app's own Authorization header gets a 401, the existing retry path called refreshToken() again, but since the cached token was still time-valid it just handed back the exact same (already-rejected) token, so the retry always failed identically to the first attempt. Confirmed via a user's logcat output: two consecutive 401s for the same gif request, ~150ms apart, both presumably using the same token, since nothing forced a new one to be minted. Invalidate the cache entry as soon as a 401 is observed on the existing-Authorization path, so the subsequent refreshToken() call mints a genuinely new token instead of resending the rejected one.
|
Update: a user reported still seeing Redgifs failures after this fix, so I added diagnostic logging (now included in this PR) to help track it down. Their Added Updated build for anyone testing: https://github.com/KingOfPoptart/patcheddit/releases/tag/redgifs-fix-v1 |
New setting: Settings > Security > "Open Redgifs links in browser view", off by default. When enabled, Redgifs links open in Sync's existing in-app WebView (the same one used for login) instead of the native player, bypassing the Redgifs API/token flow entirely. Motivated by a user report of persistent "Error connecting to Redgifs" that survived the token-retry fix. A real browser demonstrably works for them; a WebView is a real browser engine (same Chromium as the system browser), so this should behave the same way when the native flow doesn't. Confirmed working live on a Pixel 8 Pro: toggling the setting on/off correctly switches between WebView and native player behavior. Implementation notes: - Reuses Sync's existing, already-manifest-declared WebViewActivity (K0(Context, String) just takes a URL) -- no new Activity or manifest patch needed. - SettingsSingleton$Settings is NOT Gson/reflection backed: SettingsSingleton.p(Context) populates each field individually via SharedPreferences.getBoolean(key, default). A field added without a matching read here silently never populates from the stored preference, regardless of what the UI shows -- this was the actual bug in an earlier version of this patch (checkbox toggled fine, had zero runtime effect). Fixed by inserting a matching read right after the existing "doh" field's read in that same method. - SettingsSingleton is a lazy singleton built once per app process, so toggling the setting requires a full app restart (not just background/resume) to take effect -- true of the existing "doh" setting too, not specific to this one. - Off by default, so default behavior for users who don't opt in is completely unchanged (falls through to the original, unmodified method body when the setting is off).
Emulate the removed /info endpoint, add diagnostic logging, and add an optional Sync WebView fallback. Keep the existing forced token refresh implementation instead of duplicating upstream cache invalidation, and adapt the WebView patch to this branch's Morphe 1.3 compatibility API.
PR wchill#100 already repairs Sync native playback by emulating the removed Redgifs /info endpoint. The additional WebView toggle is therefore a workaround for the old failure mode rather than part of the API repair. Remove its app-version-specific fingerprints, injected Settings field, and preference UI so this branch stays focused on the shared API fix. This reduces the surface area that can break when Sync bytecode or the patcher API changes while retaining native playback for every client covered by the shared interceptor.
PR wchill#100 invalidates the cached token before reading the User-Agent from the response request. Another interceptor can rewrite that header, so the rejected token may be cached under a different key than the default user agent. Invalidating the default key can then leave the rejected token available for immediate reuse. Capture a non-empty effective User-Agent first and pass an explicit force-refresh flag to the token manager. This always mints a replacement for the identity that actually failed, keeps unrelated cached tokens intact, and avoids replacing a usable fallback with a null response header. The existing refreshToken(String) entry point remains for callers that want normal cache behavior.
The original Redgifs work was prepared against an earlier patches-library API and passed a single extension name followed by hook varargs. On the PR wchill#100 dependency baseline, the single-name/list helper is private while the supported public entry point accepts a list of extension names followed by hook varargs. Wrap the extension name in a one-item list and expand the generated hooks into that public overload. This preserves the original per-client hook behavior while compiling against the same API generation used by PR wchill#100.
Redgifs PR #100 Follow-up FixThis update builds on PR #100 and is available from PR #100 restored the removed Redgifs Our update fixes this by:
In short: the original PR repaired the endpoint, but did not resolve this token cache-key mismatch; our update does. The branch was verified with: ./gradlew :patches:buildAndroid --no-daemonResult: Pull the complete updateThe branch is based directly on PR #100's head, so maintainers can pull it as a fast-forward from that commit: git remote add pdscomp https://github.com/pdscomp/patcheddit.git
git fetch pdscomp fix-redgifs
git merge --ff-only pdscomp/fix-redgifsFor me, the original test build still failed with the same error sometimes, these updates made it 100% reliable in my testing. Cheers! -PD |
PR wchill#100 invalidates the cached token before reading the User-Agent from the response request. Another interceptor can rewrite that header, so the rejected token may be cached under a different key than the default user agent. Invalidating the default key can then leave the rejected token available for immediate reuse. Capture a non-empty effective User-Agent first and pass an explicit force-refresh flag to the token manager. This always mints a replacement for the identity that actually failed, keeps unrelated cached tokens intact, and avoids replacing a usable fallback with a null response header. The existing refreshToken(String) entry point remains for callers that want normal cache behavior.
The original Redgifs work was prepared against an earlier patches-library API and passed a single extension name followed by hook varargs. On the PR wchill#100 dependency baseline, the single-name/list helper is private while the supported public entry point accepts a list of extension names followed by hook varargs. Wrap the extension name in a one-item list and expand the generated hooks into that public overload. This preserves the original per-client hook behavior while compiling against the same API generation used by PR wchill#100.
PR wchill#100 invalidates the cached token before reading the User-Agent from the response request. Another interceptor can rewrite that header, so the rejected token may be cached under a different key than the default user agent. Invalidating the default key can then leave the rejected token available for immediate reuse. Capture a non-empty effective User-Agent first and pass an explicit force-refresh flag to the token manager. This always mints a replacement for the identity that actually failed, keeps unrelated cached tokens intact, and avoids replacing a usable fallback with a null response header. The existing refreshToken(String) entry point remains for callers that want normal cache behavior.
The original Redgifs work was prepared against an earlier patches-library API and passed a single extension name followed by hook varargs. On the PR wchill#100 dependency baseline, the single-name/list helper is private while the supported public entry point accepts a list of extension names followed by hook varargs. Wrap the extension name in a one-item list and expand the generated hooks into that public overload. This preserves the original per-client hook behavior while compiling against the same API generation used by PR wchill#100.
|
Thanks for this — reviewed both fixes carefully. The token cache-key fix is a real bug and I've adopted it (cherry-picked The build fix is genuinely better than what I'd proposed. I'd worked around I've kept the WebView fallback, though — didn't adopt the removal commit. The reasoning in that commit is that the Updated build's up now if you want to verify: https://github.com/KingOfPoptart/patcheddit/releases/tag/redgifs-fix-v1 |
|
Awesome, thank you! Makes sense! |
Redgifs follow-up: true fallback + URL canonicalizationFollow-up to my earlier comment here — new work lives on 1. The WebView fallback is now actually a fallback. The old branch short-circuited link handling, so every redgifs link opened in the WebView even when the native player would have worked. The new patch hooks the API error funnel instead — the native player gets first crack, and the WebView only opens when the request genuinely fails. 2. Fallback UX: consent persists. Sync wipes all cookies when opening its in-app WebView, so the cookie-consent wall had to be clicked through on every single fallback. The patch auto-clicks the CookieYes "agree" button and skips the cookie wipe for redgifs URLs, so consent survives between opens. If the consent wall or its markup ever changes upstream, the auto-click simply finds nothing and no-ops — the fallback still works, you'd just tap agree once like before. 3. URL canonicalization at the extraction point. Some links render fine in a browser (or the WebView) but produce mangled IDs for the API: the site's web server tolerates junk in the path, but the API route-matches strictly and 404s. Worse, the app's ID extractor strips the trailing slash before the query string, so a trailing query can wipe the ID entirely and the request goes out with an empty one. The patch replaces extraction with: strip query/fragment from the full URL first, then take the last path segment. Cases this fixes/handles (fabricated IDs, but real shapes seen in the wild):
Verified with Cheers! -PD |
…accepted consent Two related changes to the "Open Redgifs links in browser view" setting: 1. Make it a fallback instead of a default. The setting used to short-circuit LinkHelper at link-open time, so every Redgifs link used the WebView even when the API would have worked. Now hook ImageViewerFragment$g0.onErrorResponse -- the single failure funnel for Sync's Redgifs playback flow (OAuth, /info, and /v2/gifs/<id> requests all deliver errors there, and its 'a' field holds the original redgifs.com URL). The native/API player is always attempted first; the WebView only opens on an actual failure. On fallback the player activity is finished and its error handling is skipped: no error flash before the WebView appears, and Back from the WebView returns to the page that opened the player. Setting off = unchanged behavior. 2. Auto-accept the CookieYes consent banner on redgifs pages and persist it. Sync's WebViewFragment wipes all cookies on every fresh WebView open, so the banner returned every time. WebViewFragment$a .onPageFinished now auto-clicks the CookieYes accept button ([data-cky-tag=accept-button], legacy .cky-btn-accept fallback), and the removeAllCookie wipe in onViewCreated is replaced with a helper call that skips it for redgifs URLs. Host-exact matching (redgifs.com and subdomains only); non-redgifs WebView behavior is unchanged, and if the banner markup changes the click no-ops. Scope: main player flow only; long-press peek preview has its own error listener and is unchanged.
LinkHandler.getGfycatId (y7.a.d) strips the trailing slash before the query/fragment, so URLs like redgifs.com/watch/<id>/?92/ leave ?92 as the last path segment and the query-strip then reduces the ID to an empty string -- the API call goes out as /v2/gifs/ and 404s, showing "Error connecting to Redgifs" for links that work fine on the site. Redirect the method to a corrected extension implementation: strip query/fragment from the full URL first, then take the last segment. Suffix cleanup is parity-identical to the original (verified against a matrix of gfycat/redgifs URL shapes). Notably the ".jpg" suffix is NOT stripped: type-2 image posts resolve to .jpg URLs that Sync feeds to its video player (error 14 "Could not load video"), so keeping the suffix lets the API 404 and the WebView fallback display the image. Video URLs with malformed paths now play in the native player without needing the fallback.
|
Pulled in both commits from your Fallback-only behavior -- confirmed working. With the setting off, nothing changes. With it on, verified via logcat that the native/API path is always attempted first and URL canonicalization -- confirmed working. Fed it Cookie auto-accept -- did not fire in testing. The patch's JS is correctly wired in (confirmed via decompile: Cookie persistence -- confirmed working, once accepted manually. After clearing the age-gate and consent banner by hand once, force-killed the app and re-triggered a fresh WebView fallback: no banner the second time, straight to content. So the "skip the wipe for redgifs URLs" half of the fix genuinely works -- it's specifically the auto-click half that isn't landing on the current live site. One more thing found along the way, not part of your PR: Sync's gif-ID extraction (both the pre-existing code and your corrected version, since you kept it parity-identical there) never lowercases the ID, but Redgifs' Shipped in the unofficial build too: |
…onditional The setting added a second layer of gating on top of the fallback already being fallback-only (native player always tried first, WebView only opens after a real failure) -- users had to know the toggle existed and turn it on before the fallback would ever help them. Removes the SettingsSingleton$Settings field, its SharedPreferences read in the loader method, and the Settings > Security checkbox entirely. The WebView fallback now applies unconditionally whenever this patch is included, matching how every other fix patch in this bundle behaves (opt in/out at patch time via Manager, not via an in-app setting). Verified live: a working link plays natively with nothing touched: a failing link falls through to the WebView with a fresh, never-configured install.
|
One more change on top of the above: removed the in-app "Open Redgifs links in browser view" Settings > Security toggle entirely. The WebView fallback is now unconditional whenever this patch is applied -- native player is always tried first, WebView opens automatically on a real failure, nothing to configure. Reasoning: the fallback was already fallback-only (only fires on genuine failure, never replaces a working native playback), so gating it behind a second, easy-to-miss opt-in setting didn't add safety, just meant most users who'd actually benefit from it never knew to turn it on. Removed the Verified live on a completely fresh install (no settings ever touched): a working Redgifs link plays natively, and a genuinely-failing one falls through to the WebView automatically. Shipped in |
|
I was thinking about suggesting that exact change but it slipped my mind. Sweet! |
Summary
Sync for Reddit currently fails to open Redgifs links, showing "Error connecting to Redgifs" instead of playing the video.
Root cause: Sync's legacy Redgifs flow is a 3-step Volley request chain — get OAuth token → get client IP via
GET /info→ get gif viaGET /v2/gifs/{id}. Redgifs has removed/infoentirely (confirmed 404 as of 2026-08-12); it was only ever used to populate the legacyuser-addrquery parameter. Since step 2 now fails outright, the chain never reaches step 3, so the gif never resolves.The existing
FixRedgifsApiPatchalready emulates Redgifs' old (also-removed) OAuth endpoint locally rather than hitting the dead network endpoint. This PR extends the same technique to/info: emulate a local{"remote-addr": "..."}response instead of proxying to the network. Current Redgifs endpoints accept but ignore theuser-addrvalue derived from this, confirmed via direct testing against the live API (a placeholder value works fine), and cross-checked against how other actively-maintained Redgifs clients (yt-dlp, Voyager, Hydra, RedditRepostSleuth) handle auth — none of them use/infoor any IP-lookup step at all.The fix lives in the shared
BaseFixRedgifsApiPatch/RedgifsTokenManagerextension code, so it applies to Sync, Boost for Reddit, and BaconReader simultaneously, not just Sync.Test plan
com.laurencedawson.reddit_syncv23.06.30-13:39 withmorphe-desktop/infoendpoint 404s, and that the downstream/v2/gifs/{id}fetch succeeds regardless of whatuser-addrvalue it's given