Skip to content

Part 1 engine browsing UI - #8

Open
morpheasgr wants to merge 51 commits into
involvex:mainfrom
morpheasgr:part-1-engine-browsing-ui
Open

morpheasgr wants to merge 51 commits into
involvex:mainfrom
morpheasgr:part-1-engine-browsing-ui

Conversation

@morpheasgr

Copy link
Copy Markdown

Multi-part PR series: this is part 1 of 4.
The full change set (108 commits) was split into four stacked PRs because GitHub can't fetch
diffs over 20,000 lines for review. Each part contains the parts before it, so please merge
them in order: 1 → 2 → 3 → 4.
Until the earlier parts are merged, this PR also shows their
changes; after that, only its own.

Part Branch Topic
1 part-1-engine-browsing-ui On-device engine for phones, browsing fixes and mobile UI
2 part-2-sources New sources, Cloudflare handling and cookies for any site
3 part-3-online-library Online library, Pornwitch and saved-search Full fetch
4 part-4-browser-query In-app browser, share sheet and query builders

Developed and tested on an arm64 Android phone (release APK) and the desktop LAN backend.

About 3,000 of this diff's lines are line-ending churn (CRLF → LF) in models.rs, migrations.rs,
error.rs and sites/adapters/mod.rs; viewing the diff with whitespace hidden removes them.

Engine: phones run their own engine

  • On-device engine is the phone default and works end to end. youtubedl-android now
    initializes in release APKs: native libs are extracted (useLegacyPackaging), and
    commons-compress, commons-io and Jackson are kept from R8. Init failures are reported instead
    of crashing the app.
  • Standalone mode retired. It was YouTube + direct links only, a subset of the on-device
    engine. Saved "standalone" settings migrate to Local (settings store, device sync, mobile
    bootstrap); the enum value stays for old settings.
  • On phones the engine is labelled "On-device (this phone)", and the dashboard no longer asks
    for a desktop host or shows all zeros without one. Remote LAN remains optional.
  • Phones never host LAN. The LAN server refuses to start on mobile (it would bind 0.0.0.0);
    the on-device server is loopback-only and no longer advertises the phone's Wi-Fi IP. Library
    playback uses that loopback server even while lan_enabled still reads false.
  • The Desktop settings tab (tray, hotkey) shows only in the desktop shell
    (RuntimeCapabilities.desktopShell), not on phones.

Browsing

  • Fixed listing parsers for XVIDEOS, XNXX, xHamster and YouPorn; xHamster lazy cards filled from
    window.initials; RedGifs on the v2 API.
  • Per-site sorting, filters and category/tag indexes. Full A-Z tag indexes for XVIDEOS, XNXX,
    xHamster, Eporner and ThisVid, plus the live PornHub category catalog (trans/gay pairs,
    "Language in videos").
  • Category/tag include and exclude where a site supports it strictly. docs/tag-combining.md
    records what each site really combines (verified live).
  • Tag autocomplete from those indexes.
  • Saved searches keep sort and filters, reopen correctly and show their errors.
  • Info dialog: video-page details (tags, models, channel) for tube and KVS sites, where yt-dlp
    returns none.
  • In-app browser cookie login on desktop and Android.

Playback and network

  • Working fullscreen on Android.
  • YouPorn and Eporner play through the media proxy with the right Referer; proxy cookies are kept
    per site.
  • PornHub plays via yt-dlp.
  • ThisVid / ThotHub play through a session-aware KVS resolver; private ThotHub videos are flagged.
  • Connections that fail are retried over IPv4 / HTTP/1.1, and errors show their real cause.
  • A per-host request throttle and a rate-limit guard that probes and recovers instead of waiting
    out a blind cooldown.

Mobile UI

  • Compact browse results, a site picker with real site icons, and in-page tag suggestions.
  • Categories, Library Scenes, Downloads and Live Streams reworked for phones.
  • Bottom-sheet context menu.
  • Tabs remember their last location and scroll position.
  • Saved searches fit on phones, recent searches can be cleared, and keyboard hints are hidden on
    touch.

StashDB

  • The real error is shown, and rejected API keys are detected.

Android build

  • Plugin Args classes are kept from R8.
  • Cleartext is allowed for LAN in release builds.
  • FileProvider is used for opening files, and renames are safe.

Testing

  • cargo test --lib: 178 passed
  • bun run lint, bun run format:check, tsc --noEmit: clean

🤖 Generated with Claude Code

claude added 30 commits October 16, 2026 16:41
Browsing:
- Add shared listing parser (sites/listing.rs) that merges every anchor
  linking to the same video, so titles come from title/aria-label/alt
  instead of quality badges ("1080p"), noscript markup or nothing.
  Fixes XVIDEOS/xHamster titles and XNXX/YouPorn cards being dropped.
- RedGifs: browse via the v2 API with a cached temporary token (the
  site is a JS app, so HTML scraping never returned results); gif
  lookups are now authenticated and return direct MP4 URLs.
- ThisVid: scrape search/tag listings directly (yt-dlp has no search
  extractor for it).
- Chaturbate: use the username as the card title and strip tag markup
  from room subjects.
- Surface yt-dlp browse errors and Reddit login requirement instead of
  silently returning empty pages.

Cookies:
- New in-app login browser (cookie_login.rs): desktop opens a separate
  webview window and reads cookies via cookies_for_url; Android uses a
  new CookieBrowserPlugin Kotlin overlay (full-screen WebView +
  CookieManager). Captured cookies (incl. HttpOnly) are saved as
  Netscape through the existing vault path.
- "Log in with browser" button in Settings -> Cookies and on
  cookie-required site pages; cookie site picker lists all sites.
- Vault: treat #HttpOnly_ lines as cookies, not comments.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The APK stored .so files uncompressed with extractNativeLibs=false (AGP
default), so YoutubeDL.init / FFmpeg.init could not unpack
libpython.zip.so / libffmpeg.zip.so and every yt-dlp/ffmpeg call failed
with "not initialized".

- patch-android-ytdlp (.ps1/.sh): set jniLibs.useLegacyPackaging = true
- validate-android-build: fail when it is missing
- YtDlpPlugin: include the init exception in "not initialized" errors
- Settings hint + mobile-android troubleshooting entry

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n init

With native libs now extracted, YoutubeDL.init / FFmpeg.init get past
the early failure and run commons-compress / commons-io / Jackson code
that R8 had renamed in release builds, crashing the app at launch.

- proguard overlay + patch scripts: keep org.apache.commons.compress,
  org.apache.commons.io and com.fasterxml.jackson (added to existing
  projects too)
- YtDlpPlugin: catch Throwable (LinkageError etc.) and give the plugin
  scope a CoroutineExceptionHandler so init failures are reported
  instead of killing the process

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Backend
- BrowseQuery.filters (sort / period / duration / quality option ids)
  threaded through the browse command, LAN API query params and the
  browse cache key; SiteInfo.browse_options advertises what each site
  supports. Unknown ids are dropped before they reach a URL.
- SiteAdapter::browse_options / list_categories, list_site_categories
  command + GET /api/sites/{id}/categories (6 h in-memory cache).
- Shared category-index parser (sites/listing.rs) and with_query helper.
- Per-site support, verified against live filter menus:
  PornHub (o/t/min_duration/max_duration/hd), XVIDEOS (sort/datef/durf/
  quality), XNXX (path segments), xHamster (search query params,
  category path sorts), YouPorn (/search/<sort>/), Eporner (API order),
  ThotHub/ThisVid (KVS sort_by), Reddit (hot/new/top/rising + t),
  RedGifs (order). Categories for PornHub, XVIDEOS, XNXX, xHamster,
  YouPorn, Eporner, ThisVid and Chaturbate (top hashtags).
- Fixes: ThotHub page 2+ repeated page 1 (from_videos / from paging),
  XNXX pages were off by one, xHamster category paging, PornHub
  category names were scraped as video counts (now parsed from the
  embedded JSON catalog).

Frontend
- BrowseFilterBar: site sort/filter selects (reload from page 1) plus
  on-device text (-exclude) and length filters over loaded results.
- SiteCategoryBrowser on site hubs; useSiteInfo hook for live options.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The site hub (/browse/$site) holding the category browser had no entry
point. Selecting a site chip now shows a "<Site> categories" button.
siteHasCategories() falls back to a static id list when the backend
site list (with browse_options) is unavailable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
R8 stripped YtDlpPlugin$Args / $MediaArgs / $KeepAliveArgs /
$ResumeArgs to empty shells (no fields, no constructors), so Tauri's
Jackson parseArgs failed with "Cannot construct instance of
YtDlpPlugin$Args (no Creators ...)" and every on-device download and
stream resolve broke. Keep com.archhive.app.**Args, and add the rule
to already-patched projects in patch-android-ytdlp.ps1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- PornHub retired /categories/<slug> (404 -> "browse failed"). Resolve
  slugs to numeric c= ids from the live catalog (straight / gay /
  lesbian, 12 h cache; gay slugs carry a -gay suffix) and fall back to
  search. Trans uses the main index: /transgender/video* now 404s.
- PornHub listings scope to the result grid (#videoSearchResult,
  #videoCategory, ul.search-video-thumbs) so header "hottest" /
  recommended dropdowns no longer lead results; skip playlist
  "Play All" links (pkey=).
- PornhubCategoryBrowser is now a picker: a category opens the browse
  page (sort / filters / infinite scroll) with ?orientation= in the
  URL; the browse cache key includes orientation.
- Browse page loads page 1 on open instead of waiting for "Browse".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Phones never host LAN: ensure_lan_server / regenerate_lan_server
  refuse on mobile (they would bind 0.0.0.0). The on-device loopback
  server reports "loopback": true / "lan": false in /api/health and no
  longer advertises the phone's Wi-Fi IP; the status chip says
  "(on-device)" instead of "(open)".
- sites::throttle: >=1 s between requests to the same host, and after a
  429 a cooldown (Retry-After or 60 s) that fails fast with a clear
  "rate limiting" error instead of hammering the site.
- Trending: 10-minute session cache shared by /browse and /feed with
  in-flight de-duplication (Feed scraped every trending site twice per
  visit); pull-to-refresh clears it.
- PornHub playback tries yt-dlp first (what works for downloads
  on-device), then the page extractor on mobile; both via the Referer
  loopback proxy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- IdentifyDialog / EnrichDialog / performer StashDB actions showed a
  generic "Query failed": Tauri rejects with a string, not an Error, so
  the backend message was dropped. Fall back to String(e).
- StashDB answers a missing/invalid ApiKey with HTTP 200 + GraphQL
  "not authorized", not 401; map it to a clear API-key message.
- Select Scene.release_date (Scene.date is deprecated on StashDB) and
  retry with `date` on boxes that do not know release_date.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…essage

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- sceneMediaUrl / sceneThumbUrl / fileStreamUrl no longer require
  settings.lan_enabled on the phone: the 127.0.0.1 loopback server
  always runs there, but the flag can still read false while it starts
  (e.g. first launch after reinstall), leaving library files with no
  playable URL.
- Scene player shows why playback failed (probes the stream: missing
  file, outside the library folder, server unreachable, unsupported
  format) with an "Open with system player" button, instead of a silent
  black box.
- SceneContextMenu: bottom sheet on phones (large targets, padded above
  the system navigation bar); the desktop popup is clamped inside the
  window. "Open in Explorer" is hidden on phones.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…fe rename

- Release APKs shipped usesCleartextTraffic=false: patch-android-lan.ps1
  tested for any "true" (only the debug build type sets it) instead of a
  remaining "false". The WebView could not reach the 127.0.0.1 media
  server, so library playback, thumbnails and the PornHub Referer proxy
  all failed. The validator now fails on a remaining "false".
- "Open with default app" / "Open with system player" on Android go
  through a new YtDlpPlugin.openFile (ACTION_VIEW + FileProvider
  content:// URI); the opener plugin's open_path did nothing there.
  FileProvider roots overlay adds app-private dirs.
- Rename file to match title: error when the file is missing, never
  overwrite an existing file, move the <stem>.jpg thumbnail sidecar too.
- Library rename / regenerate thumbnail / open actions show their
  result or error as a toast instead of failing silently.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Tag/category indexes are no longer capped (XVIDEOS ~2,000 tags, XNXX
  2,000, xHamster 655 were cut to the 300-400 most popular, hiding most
  rare / late-alphabet tags).
- Combined tags: a Tag/Category slug of `a,b` intersects tags where the
  site supports it, verified live: XVIDEOS /tags/a+b (up to 5; tag pages
  ignore sort/filters, so filtered queries use the word search) and
  PornHub /video/incategories/a/b (exactly 2, straight only). Other sites
  and larger selections run as a word search; BrowseOptions.combine_tags
  advertises the exact limit.
- Tag browser: A–Z / most-videos order and a Combine mode (select tags,
  "Browse N together"), also in the PornHub category picker.
- Browse page: tag autocomplete from the site's tag index, and a note
  when a combination falls back to word search.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
XVIDEOS' /tags page only lists the overall top ~2,000 tags; the full
index is split by first character (/tags/09, /tags/a … /tags/z, up to
2,000 each, ~50k tags). BrowseOptions.category_groups exposes those
sections; SiteAdapter::list_category_group fetches one on demand, and
list_site_categories(site, group) caches it in memory (6 h) and on disk
(<data>/tag-index, 7 days).

- Tag browser: "Popular · 0-9 · A … Z" tabs.
- Browse page autocomplete loads only the group for the tag being typed
  and suggests the 50 most-used prefix matches, including after a comma
  for combined tags ("milf, zo" -> "milf,zombie-sex").

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
XVIDEOS has ~38 categories (/c/<Name>-<id>, listed on the home page)
separate from its tags. They are now a "Categories" group in the tag
browser. Category pages take sort / length / quality as path segments
(/c/s:uploaddate/d:10-20min/q:hd/Femdom-235) and page as /<n-1>; the
filter bar applies to them. Verified live that categories cannot be
combined with tags or searches in one URL (c= and tag= are ignored,
/c/X/tags/Y 404s), so Combine is hidden on the Categories tab and a
combined category slug falls back to a word search on the names.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Eporner's HTML listings take category filters in the URL, verified live:
/tag/<query>/[<page>/][<sort>/]?include=a,b&exclude=c (page must come
before the sort segment; the reverse redirects to page 1), also on
/cat/<slug>/. The API used for plain browsing has no such filter, so
requests with include/exclude use the HTML listing (new parser; slugs
validated to [a-z0-9-]).

- BrowseFilters gains include / exclude (comma-separated category
  slugs; part of the cache key and LAN query params);
  BrowseOptions.include_exclude advertises support.
- Browse page: "Categories" panel for such sites — tap a category to
  require it, again to exclude, again to clear; taps are committed after
  a short pause so several taps trigger one reload.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
/cats/ lists the 86 categories plus the top 500 tags; /cats/A/ … /cats/Z/
list 500 tags each (no further paging), almost none of them on /cats/.
They are now category groups ("Tags", A … Z) in the tag browser and feed
autocomplete; the main tab is labelled "Categories" for Eporner.

Tags open their exact /tag/<slug>/ page (HTML listing, with sort, paging
and category include/exclude) instead of an API word search. Tag slugs
are case-sensitive (czech-ouQ9EM84 works, lowercased 404s), so they are
not lowercased. Verified that ?include= only accepts categories, not tags.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The categories page only lists 655 categories and ~200 tags; its page
data links an A–Z tag index at /categories-0, /categories-a … -z (up to
2,000 tags each, no further paging), ~30k tags in total. These are now
category groups ("0-9", A … Z) in the tag browser and feed autocomplete.

Single tags open their /tags/<slug> listing (sort/HD as path segments
and /<page>, like categories) instead of a word search; combined tags
search with xHamster's own '+' form.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
XNXX tags are search terms (/tags/<t> redirects to /search/<t>), but it
has the same per-letter index as XVIDEOS (/tags/09, /tags/a … /tags/z,
up to 2,000 each). Added as category groups ("0-9", A … Z) with their
own parser; tag browsing keeps the hyphenated /search/<tag> form XNXX's
own tag links use (it ranks differently from /search/a+b).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs/tag-combining.md records the live-verified URL schemes: XVIDEOS tag
intersection (/tags/a+b, up to 5, no filters), PornHub two-category
intersection (/video/incategories/a/b, straight only), Eporner search/tag
+ category include/exclude, and the sites that only support word search
(xHamster, XNXX, others), with how the app maps each one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ThisVid: one category + one tag is a strict intersection
(/categories/X/?tag=Y or /tags/Y/?category=X; verified on result pages),
only one tag, search + category is not strict. YouPorn: no strict
combination; porntags are search aliases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
/categories/<c>/?tag=<t> is a strict category ∩ tag listing (verified on
result pages); it pages and sorts. BrowseFilters.tag carries the tag
(validated to [a-z0-9-]; part of the cache key and LAN params) and
BrowseOptions.category_tag_filter enables a "Narrow by tag" box on
category pages, with autocomplete from ThisVid's tag index
(/tags/?section=a … z, now also A–Z tabs in its tag browser).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Checked against result videos' own tag lists: XVIDEOS tag pages are a
keyword search over titles and tags. /tags/petite-brunette lists more
videos than petite or brunette alone and none of the sampled results
carried that tag; /tags/milf+anal and /tags/a+b+c results were missing
tags. combine_tags is now 0 for XVIDEOS (combined tags run /?k=...), the
/tags/a+b URL path is removed, and the docs are corrected.

Also re-verified the remaining strict claims on result pages: PornHub
/video/incategories/a/b (10/10) and Eporner include/exclude (13/13).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PornHub searches take one required category (filter_category=<id>) and up
to 10 excluded ones (exclude_category=<id>-<id>, dash-separated as the
site's own UI sends them); category listings take only the excludes.
Verified strict on result video pages.

BrowseOptions gains max_include / max_exclude / exclude_only_kinds so the
Categories panel enforces the per-site limits; docs updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The picker showed a 55-entry built-in list unless "Refresh counts" was
tapped; it now loads PornHub's live catalog per orientation (111 straight,
53 gay, 73 lesbian), cached for the session, with the built-in list only as
an offline fallback.

Trans has no index of its own (/transgender/categories is a 404) and trans
categories used to open the straight /video?c= listing. The Trans tab now
lists the straight catalog, each entry browsing <category> ∩ Transgender
(/video/incategories/<x>/transgender), with "All trans" (c=83) on top.
Gay pairs intersect too (/gay/video/incategories/a/b). Category pairs
without a page (404) fall back to a word search.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The categories page also links 17 spoken-language listings
(/language/<name>, same grid, paging and sort as categories). The picker's
Straight tab shows them as a Languages row; `language-<name>` slugs browse
/language/<name>.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Saved searches store the listing's sort / filters (new saved_searches.filters
  JSON column, MIGRATION_020); checks run the saved listing instead of the
  site defaults, and a filtered page is a different saved search from the
  unfiltered one.
- Opening a saved search passes its orientation and filters (browse route
  search params now carry the filter keys) and no longer pre-encodes the
  slug, which double-encoded combined slugs ("milf,anal").
- Save / remove failures show a toast instead of failing silently; browse
  errors show Tauri's string rejections instead of "Browse failed".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Replace <datalist> autocomplete (browse tag box, ThisVid "Narrow by tag")
  with SuggestInput: an in-page list anchored under the input, ~6 rows then
  scrolling, closed on pick / blur / Escape. Android WebView drew datalist
  as a native popup that could cover the screen and overlap the keyboard.
- Replace the sideways-scrolling site chips and the separate site dropdown
  with SitePicker: one large button opening a grid of big site tiles (bottom
  sheet on phones, dialog on desktop) with search, Auto-detect and a
  "Needs cookies" hint.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bundle each site's best published icon (apple-touch / largest favicon,
normalised to 128×128 PNG, 144 KB total) in public/site-icons and show it
in the site picker. ThotHub and ThisVid only publish 16–32px icons, so they
and plugin sites keep the coloured initial badge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude added 18 commits October 16, 2026 16:41
- Phones (<768px): one-row header (back, site icon + name, kind, save),
  full-width search, and a Filters button with an active-filter count and
  summary chips. Orientation, sort/date/length/quality, the text filter,
  category include/exclude, tag narrowing and "hide watched" (now a switch)
  move into a bottom sheet. Desktop keeps the controls inline.
- Results use two columns on phones with larger thumbnails; Info/Download
  become small icon buttons on the thumbnail (SceneCard compactActions)
  instead of a full-width button row, and the duration badge sits in the
  corner.
- Queuing a download shows a toast.
- Extract BottomSheet (ui/bottom-sheet) and reuse it in SitePicker.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Categories (site hub):
- Compact header (back, site icon, name); the "Advanced browse" and
  "Browse by" cards merge into one chip row.
- SiteCategoryBrowser / PornhubCategoryBrowser drop the nested card and
  inner scroll box; sections become full-width tabs and the A–Z pages a
  letter grid instead of a sideways strip; PornHub rows are 48px tall with
  checkboxes in Combine mode.
- Combine mode shows a floating CombineBar above the bottom navigation
  with the picks and "Browse", so it never scrolls out of reach.

Library → Scenes:
- Smaller header with icon buttons; search sits next to a Filters button
  that opens a "Sort & filter" sheet (sort, quick filters, duration/size)
  on phones; 2-column grid in portrait.

Downloads:
- Bulk import moves behind an "Add URLs" button (bottom sheet, opened
  automatically for shared links) on phones; bulk actions scroll in one
  row; rows show the site icon, a two-line title and a percentage next to
  a thicker progress bar, plus a swipe hint. "Clear completed" is renamed
  "Clear finished" (it also removes failed/cancelled jobs).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ource URLs

- Fullscreen: Tauri's Android WebView (wry) cancels HTML5 fullscreen in
  onShowCustomView, so the native button never worked. New
  usePlayerFullscreen hook: HTML Fullscreen API where it works; in the
  Android app the player container covers the screen and a new setImmersive
  plugin command (set_player_fullscreen) hides the system bars and locks
  landscape for wide video. The back button / Escape exit. HlsVideoPlayer
  gets its own fullscreen button and hides the dead native one;
  ScenePlayerDialog's button and double-tap use the same hook.
- YouPorn: stream URLs go through the loopback media proxy on the phone
  (site Referer + browser UA, like the downloader), as PornHub's already do.
- Source URLs in the browse player and the browse Info dialog are links
  that open in the system browser; ExtLink only links http(s) URLs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Eporner's CDN redirects requests with a foreign Referer (the WebView's
  http://tauri.localhost/) to a placeholder clip ("only available on
  eporner.com", static.eporner.com/na.mp4). Stream URLs now go through the
  loopback media proxy with https://www.eporner.com/ as Referer (verified:
  real video instead of the 642 KB placeholder).
- The proxy attached PornHub cookies for any Referer it didn't recognise;
  it now matches the Referer host exactly and attaches no cookies for
  unknown sites. Eporner added to the known sites. Unit-tested.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nails

KVS get_file links only work inside the session that loaded the video page;
from yt-dlp's session or the WebView they redirect to placeholder clips
(ThisVid processing.mp4, ThotHub fu.mp4).

- New extractors::thothub::resolve_kvs_stream: loads the page in a fresh
  cookie session (plus imported cookies), decodes the best get_file URL and
  follows it in that session to the signed CDN URL. Placeholders, bot-check
  pages (Cloudflare) and pages without a player now give specific errors
  instead of "could not extract".
- ThisVid uses it (the CDN URL plays directly); ThotHub uses it and plays
  through the loopback proxy, since its mirror refuses the WebView Referer.
- ThotHub listing thumbnails are hotlink-protected (403 unless the Referer is
  ThotHub): rewritten through the loopback proxy on the phone.
- KVS quality pick: an unlabelled main video_url ranks as standard quality,
  so ThisVid no longer plays its 240p alternate.
- Proxy cookie mapping knows thethothub.com and thisvid.com.

Verified live: ThisVid resolves to the real CDN video; ThotHub stream and
thumbnail return video/mp4 and image/jpeg through the proxy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xHamster renders only the first ~7 result cards with <img> and duration
markup; the rest are filled in client-side from the window.initials JSON,
so most results had no thumbnail or duration. The tube parser now walks
that JSON (any object with a /videos/ pageURL) and fills missing
thumbnails, durations and titles by page URL.

Verified live: search (pages 1 and 2), category and tag listings return
46/46 items with thumbnails and durations (was 7/46). Unit-tested.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
About half of ThotHub's results are private videos (<div class="item
private">): their pages have no player unless you are a logged-in member,
which looked like playback "working once and then failing". Listing cards
now get a 🔒 title prefix and a "private" tag ("-private" in Filter results
hides them), and the resolver reports "This is a private ThotHub video —
only logged-in members can watch it" instead of a generic failure.

Verified live: of 24 search results the 12 flagged private are exactly the
ones whose pages have no player; the public ones resolve and play.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…uses

ThotHub failed on the phone with a bare "error sending request for url".
ThotHub (Cloudflare) publishes IPv6 addresses (ThisVid doesn't); networks
that resolve but cannot route IPv6, or break HTTP/2, fail this way while
browsers and yt-dlp fall back silently.

- SiteContext::fetch_with_headers and the KVS resolver retry a transport
  failure (connect / timeout / request) once on a fallback client bound to
  IPv4 (0.0.0.0) and limited to HTTP/1.1; the KVS resolver then keeps using
  the client that worked for get_file.
- AppError::Http now appends reqwest's source chain, e.g. "…: client error
  (Connect): tcp connect error: Connection refused", so reports say whether
  DNS, connect, TLS or a timeout failed. Unit-tested.

Verified: the fallback client fetches thethothub.com over IPv4/HTTP/1.1;
ThotHub and ThisVid still resolve through the backend.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The dashboard treated every phone without a Remote LAN host as "needs
setup": it showed "Connect to your desktop ArcHive" and skipped loading
scenes, downloads and stats entirely (all zeros), although phones run
their own on-device engine by default. Setup is now only required in
browser mode or when Remote LAN is selected without a host. The engine
banner says "On-device engine" on phones instead of "Local desktop engine".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It read "Local (Desktop)" on Android although the engine runs on the phone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Standalone (YouTube + direct links only) predates the on-device yt-dlp
  engine and is a subset of Local now. It is no longer offered in Settings
  → Engine or the first-run wizard; saved "standalone" values migrate to
  "local" (settings store rehydration, device settings sync, and the
  mobile backend bootstrap). The enum value stays for old settings.
- The Desktop settings tab (tray, hotkey, window behaviour) was gated on
  having a local backend, which phones have too; it now requires the
  desktop app shell (new RuntimeCapabilities.desktopShell). Unit-tested.
- Wizard copy: Remote LAN no longer claims to be "best for phones".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ints on touch

- Saved searches overflowed the right edge on phones: grid items default
  to their content width, so a long name plus the "checked …" date pushed
  the auto-queue / check / delete buttons off-screen. Rows can now shrink
  (min-w-0, grid-cols-1); the name truncates with the status on a second
  line. Recent-search rows truncate long queries the same way.
- Recent searches get a "✕ Clear" button (same style as the filter bar's
  Reset) that wipes the list.
- Keyboard-shortcut badges ("Ctrl 1" …) are hidden on touch-only devices
  (pointer: coarse); phones in landscape get the desktop sidebar and
  showed them.

Verified at 393×852 and 852×393 with touch emulation: no horizontal
overflow, no visible shortcut badges.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
yt-dlp -J returns no tags, models or channel for XVIDEOS / XNXX, fails on
some PornHub pages and hands back a hotlink-protected ThotHub thumbnail.
Read those pages directly (sites/details.rs), falling back to yt-dlp when
nothing useful is found. The dialog now keeps the listing thumbnail, hides
a broken image, shows string errors, and says when a site lists nothing;
the LAN endpoint returns the error text instead of an empty 500.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two-column grid on phones with compact card actions, site tiles with
icons, a search field with clear button, and a LIVE badge with viewer
count on cam cards. Cards no longer repeat the title as the channel line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Switching to Downloads (or any tab) and back returned Browse to its
start page. The shell now remembers each tab's last URL and scroll
position for the session: a tab button, nav swipe or shortcut returns
there (the browse results cache restores the loaded pages), tapping the
active tab goes to its start page, and tapping it there scrolls to top.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After a 429 the app refused every request to the host for the whole
Retry-After (up to 15 minutes, or until restart) without asking the site
again, so it kept saying "rate limited" while the site already worked in
a browser. Now one probe request goes through every 15 seconds, a
successful response ends the cooldown, Retry-After is capped at 5
minutes, and the message says the pause is ArcHive's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Oct 16, 2026

Copy link
Copy Markdown

Sorry @morpheasgr, your pull request is larger than the review limit of 150,000 diff characters

@changeset-bot

changeset-bot Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 79e2719

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@kilo-code-bot

kilo-code-bot Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Files Reviewed (1 file)
  • src-tauri/src/sites/extractors/thothub.rs
Previous Review Summaries (2 snapshots, latest commit b45ef0b)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit b45ef0b)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (27 files)
  • docs/cookie-import.md
  • docs/mobile-android.md
  • docs/sites.md
  • package.json
  • scripts/apply-android-patches.ps1
  • scripts/patch-android-cookie.ps1
  • scripts/validate-android-build.ps1
  • src-tauri/Cargo.lock
  • src-tauri/Cargo.toml
  • src-tauri/android-overlays/CookieHarvestPlugin.kt
  • src-tauri/android-overlays/TermuxBridgePlugin.kt
  • src-tauri/src/commands.rs
  • src-tauri/src/lib.rs
  • src-tauri/src/mobile/cookie_harvest.rs
  • src-tauri/src/mobile/mod.rs
  • src-tauri/src/mobile/termux_bridge.rs
  • src-tauri/src/sites/adapters/thothub.rs
  • src-tauri/src/sites/extractors/thothub.rs
  • src-tauri/tauri.conf.json
  • src/components/AppShell.tsx
  • src/components/CookieShareImportDialog.tsx
  • src/lib/api/client.ts
  • src/lib/cookies/import.test.ts
  • src/lib/cookies/import.ts
  • src/lib/stores/share.ts
  • src/lib/types.ts
  • src/routes/settings/index.tsx

Previous review (commit 5602c4c)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (changed files)
  • All files in PR Part 1 engine browsing UI #8 were reviewed for type errors, security/module concerns, and leaked secrets. No blocking issues identified.

Fix these issues in Kilo Cloud


Reviewed by free · Input: 91.2K · Output: 4K · Cached: 568.2K

@morpheasgr

Copy link
Copy Markdown
Author

If you decide to merge this I'll open a pull request for the part2 branch :) you can take a look at my fork.

morpheasgr and others added 3 commits October 17, 2026 17:42
The web conflict editor kept only this branch's side of each conflict,
which dropped main's new CookieHarvestPoll / TermuxStatus types (CI
typecheck failure), its Termux and cookie-capture plugin registration
and its ThotHub login-required handling. This restores main's side:

- lib.rs registers both the cookie login and main's cookie harvest /
  Termux bridge plugins; types.ts has both sets of types.
- ThotHub keeps the session-aware KVS resolver, now also reporting
  login-required pages, and falls back to yt-dlp on Android too (as
  main does) unless the page is gated, private or bot-checked.
- docs keep both cookie flows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
"No player on this page - the video may be private" contains the
word private but is not a verdict; match the resolver's whole verdict
phrases instead, so such pages still fall back to yt-dlp.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants