Skip to content

Engine update, online library, filtering, categories/tags, new sources, mobile UI optimizations - #7

Closed
morpheasgr wants to merge 109 commits into
involvex:mainfrom
morpheasgr:fork
Closed

morpheasgr wants to merge 109 commits into
involvex:mainfrom
morpheasgr:fork

Conversation

@morpheasgr

Copy link
Copy Markdown

Work from the morpheasgr:fork branch, developed and tested on an arm64 Android phone
(release APK) plus the desktop LAN backend. 108 commits, about 158 files.
The signing / quinn-proto commits from #6 are included but are already in main, so they add no diff.

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.
  • Fallbacks when yt-dlp is refused:
    • Android: Cloudflare checks are passed in the in-app browser, and the video URL is read
      from the rendered page (KVS, SpankBang, <video>, og:video, JSON-LD).
    • Desktop: retried with --impersonate, then through a verification window whose cookies
      are saved per site.
    • Native resolvers (KVS in-session, XXXBP, HQPorner) take over downloads that yt-dlp fails.
  • The Desktop settings tab (tray, hotkey) shows only in the desktop shell
    (RuntimeCapabilities.desktopShell), not on phones.

New sources

  • xPomelo aggregator (46 sources): source filter, length buckets, full tag list with
    include/exclude, result counts, per-page completeness checks, request pacing that stays under
    Cloudflare's 1015 rate limit, a site-down message, and unsupported sources hidden by default.
  • Pornwitch: strict tag search, word suggestions, cursor paging, multi-word tag exclusion.
    /go?id= links are resolved to the real source page.
  • XXXBP resolver and a session-aware KVS resolver (ThisVid, ThotHub, other KVS tubes).

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, and a tag list cache kept until refreshed.
  • Filters are edited as a draft and load on Apply.
  • Re-listed videos are skipped across pages, short pages are topped up, and load-more failures
    are reported.
  • Saved searches keep sort and filters, update incrementally newest-first, and have a
    "Full fetch" option (including passes over several sort orders).

Online library (new)

  • Results of saved searches and bookmarks are stored locally (db/online.rs). Recording every
    browsed video is an opt-in setting.
  • Bookmarks scope; bookmark and un-bookmark from the player.
  • Fetch tags for one video, a selection, or everything shown. Tags come from video pages;
    TXXX and beeg use their JSON APIs; PornHub is handled in both its desktop and mobile layouts.
  • Removed videos (site removal notices, or a 404 while the site itself is up) are dropped.
    HTTP 410 / 403 are treated as region or VPN blocks, not removals, and a batch where most
    videos look removed asks before deleting.
  • No tags filter, and selecting with a tap in Select mode.
  • Query builder: OR groups ANDed together, exclusions, "rank higher" terms, whole-word and
    plural matching, term* prefixes, best-match ordering, Clear / Save, and saved filtered views.

Offline library

  • The same query builder in Library → Scenes, saved with Smart Collections.
  • Fix: "Save as Smart Collection" now opens pre-filled (it used to drop the filter).
  • Fix: "Load more" skipped a page of scenes.
  • Fix: deleting library files on Android; default library folder is Download/ArcHive.
  • One video file per download.

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.
  • Many xPomelo sources fixed, with a native download fallback.
  • Connections that fail are retried over IPv4 / HTTP/1.1, and errors show their real cause.
  • A rate-limit guard probes and recovers instead of waiting out a blind cooldown.
  • Requests are paced like a browser; thumbnails go through a proxy.

Cookies, Cloudflare and the in-app browser

  • In-app browser cookie login on desktop and Android.
  • Per-domain cookie vault entries, for example for xPomelo sources.
  • Android: Cloudflare challenges solved in the in-app browser. Desktop: bot checks passed with
    impersonation and a verification window.
  • Android in-app browser for pages (instead of the system browser): popups refused, a prompt
    before navigating to another site, Clear site data, Share, and an opt-in ad blocker using
    EasyList domains.
  • Android share sheet for links and library files (open in any video player).

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.
  • A shared trending row on Browse and Feed.

StashDB

  • The real error is shown, and rejected API keys are detected.
  • Scored metadata matching when fingerprints miss.
  • Text search by performer or studio.

Android build

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

Testing

  • cargo test --lib: 231 passed
  • bun run lint, bun run format:check, tsc --noEmit: clean
  • Manual testing on Android (arm64 release APK) and the desktop LAN backend.

🤖 Generated with Claude Code

claude added 30 commits October 12, 2026 03:18
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 27 commits October 14, 2026 07:54
PornHub serves its mobile layout when a phone-browser login left a
platform=mobile cookie. Its tags are the pill row above the title, which
the scraper didn't read, so Fetch tags fell back to yt-dlp and got only
the channel. Categories and tags are now read by their data-label in
either layout, and the mobile uploader link is read too.

A video page answering 404 / 410 now means the video is gone: Fetch tags
removes it from the Online library and every saved search (it won't come
back on Update) and says how many were removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fetch tags only read videos never fetched before, so videos fetched
while PornHub's mobile layout went unread kept just their channel.
Select mode gets "Fetch tags N" for the selected videos, and "Re-fetch
all shown" fetches every video matching the current filters (all pages,
after a confirmation with a rough duration). Both re-read videos done
before, replace their tags, can be stopped, and drop videos the site
removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SceneCard ignores taps in selection mode (pages own selection, as the
Library Scenes page does with a wrapper), and the Online library had no
wrapper, so only "Select all loaded" could select anything.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PornHub answers taken-down videos with a page whose player is replaced
by a removed-video notice ("This video has been disabled"), with HTTP
200 or 403. Fetch tags now removes those like 404s. Other 403s (bot
checks, rate limits, region blocks) still count as temporary failures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fetch tags already removed videos the site took down; tapping one to
play it only showed the stream error (HTTP 403 for PornHub's "This video
has been disabled" page). When resolving a stream fails with a 403 /
404 / 410-style error, one page load now checks whether the video is
gone; if so it leaves the Online library and saved searches, the player
says so, and the Online list drops it. The Info dialog does the same.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With a platform=mobile cookie (a phone browser login), PornHub's
disabled-video page shows "This video has been disabled." in a
.removedVideoContent box instead of the desktop .video-notice, so the
phone never saw these videos as removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A site in maintenance (or misconfigured) can answer 404 for every page,
and Info, playback or a "Re-fetch all shown" run would then have dropped
all of its videos. A bare 404 / 410 now counts as removed only when the
site's home page loads and isn't a maintenance notice (remembered for 5
minutes per site). The sites' own removed-video notices still count
directly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
quinn-proto 0.11.16 is flagged by five RustSec advisories published in
August and September 2026 (memory-exhaustion and remote-panic issues,
fixed in 0.11.17 / 0.11.18), which fails the cargo audit CI step on
this branch and on main. Lockfile-only update; cargo audit now reports
no vulnerabilities (only the 7 allowed unmaintained/unsound warnings).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A site having trouble could make a whole batch look removed. Fetch tags
now only reports removed videos (fetch_online_details no longer deletes
them); the Online library page removes them, but first asks when more
than half of a batch (and at least 5 videos) came back removed. Cancel
keeps them and stops the run; OK removes them and doesn't ask again for
the rest of the run. The Info dialog and player still remove a single
removed video directly.

Also drops a needless `mut` left in the xPomelo adapter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xPomelo lists PornZog thumbnails on the main domain
(pornzog.com/media/videos/tmb/...), which answers 404; PornZog serves
them from tn1.pornzog.com. xPomelo cards now get the image-host URL,
and Online library entries saved earlier are mapped when read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Browsing no longer puts every result (trending rows included) into
  the Online library. Results are kept for saved searches (browsing one
  adds its results to it) and bookmarks; Settings has a toggle to keep
  everything as before.
- The player has a Bookmark button. Bookmarks live in the Online library
  under "Bookmarks" (a reserved scope, so tags, filters and removal work
  as for saved searches, and clearing history keeps them).
- The player reads tags from the video's page when the listing had none,
  and the Info dialog now does so whenever tags are missing (it skipped
  every aggregator result because they carry a channel). Details fetched
  for a video in the Online library are stored on it.
- Trending on Feed and Browse share one row of compact cards that open
  the player; Feed no longer shows full-width Download buttons.
- Library History, performer, tag and collection pages, Home and Feed
  rows use the same 2-column phone grid as Scenes instead of 3 tiny
  columns.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pornwitch serves PornHub thumbnails from cdn.pornwitch.com, which
answers 403 unless the Referer is pornwitch.com, so they never showed in
the app. Thumbnails on such Referer-gated hosts now load through the
app's media proxy (phone loopback, Remote LAN, desktop with LAN on),
which sends the right Referer and caches them; this also covers videos
already in the Online library.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- External links (source URLs, "Open page" when a video won't play) open
  in an in-app browser on Android instead of the system browser, so
  nothing lands in its history. It keeps the WebView's disk cache and
  cookies, blocks common ad / popunder networks and popup windows, and
  its toolbar can clear the current site's cookies and storage or share
  the page. The system browser is only the fallback.
- A Share button (Android) in the browse player sends the video's URL to
  the share sheet (copy, browsers, other apps); in the library player it
  sends the video file itself (FileProvider) to video players and other
  apps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With multiple windows disabled, the WebView loaded every window.open()
popunder in the current page, so a click anywhere redirected to an ad.
New windows are now accepted and refused in onCreateWindow (counted in
the title bar). Script- or click-driven navigation of the page to
another site asks "Leave for <host>?" first; server redirects and links
within the site load as usual.

Blocking ad requests trips sites' anti-adblock checks, so it is now an
"Ads: on / off" toolbar toggle, off by default and remembered.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With "Ads: off" on, the in-app browser now also blocks the ~51k
ad-server domains from EasyList's `||domain^` rules (site-limited,
path, wildcard and element-hiding rules are skipped). The list is
downloaded in the background, cached in the app's files as a plain
domain list and refreshed every 4 days; the built-in list covers the
time before it loads or when the download fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A "Query" panel builds a search from groups of alternatives (any term
of a group, every group required), an exclusion list and optional
"rank higher" terms. Terms are whole words or phrases in titles, tags
and performers (punctuation and hyphens ignored, so "ass" no longer
matches "glasses"); `term*` matches as a prefix. A "Best match" sort
ranks by how many group and boost terms each video has. The query runs
in the database over all results, is remembered per saved search /
Bookmarks / everything, and also drives "Remove all shown" and
"Re-fetch all shown". The component is reusable for the offline
library.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A live line above the groups spells out the query in parentheses form,
and terms inside a group / the exclusion list are joined with "OR".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Terms match singular and plural forms of their last word (blowjob /
  blowjobs, tit / tits, pussy / pussies); whole-word matching had
  missed every plural.
- Clear and Save buttons at the top of the builder. Save creates a
  "filtered view" (name, scope, query and length filter) listed under
  "Filtered views" in the Online library drop-down; it re-runs live, so
  updates and full fetches of the underlying saved search show up in
  it. Saving while a view is open updates it; views can be deleted.
  Views are kept on the device.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Tags the player reads from a video's page were already stored on the
  Online library entry (only for videos in the library), but the list
  kept showing the old card (often just a channel from yt-dlp). The
  player now hands the details back and the card updates at once.
- Un-bookmarking takes the video out of the Online library too, unless
  a saved search also holds it; the Bookmarks list drops it right away.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Shows only videos with an empty tag list, whether or not they have a
channel (yt-dlp often gives just the channel), so they can be fixed in
one go with Re-fetch all shown.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PornHub answers 410 with "This page is not available" for videos it
won't show in the viewer's region or to their VPN address; the video
still exists. The removed-video check counted 410 as deleted, so
playing such a video could take it out of the Online library, and the
player said it "no longer exists". Only 404 (or a site's own removal
notice) counts as removed now, and a 410 says the video is blocked for
this connection and suggests another VPN location.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sites (PornHub among them) refuse VPN exit addresses with 403 or 410;
the 403 hint blamed only the embedded player / cookies. Both the stream
error and the player hint now suggest another VPN location first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Fetch tags, the Info dialog and the player now follow a Pornwitch
  /go?id= link once and move the Online library entry (with its saved
  searches and bookmarks) to the real source URL, without Pornwitch's
  affiliate parameters. Tags are then read from the source site.
- TXXX-platform sites (TXXX, HClips, HotMovs, UPornia, VXXX,
  TubePornClassic, VoyeurHit) and beeg render their pages with
  JavaScript, so tags are read from their JSON video records instead:
  categories / tags, models, uploader, duration. beeg's brace notation
  ("Big {Tits}") is cleaned and technical tags (1080p, Index, media
  orientation, length) are skipped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keeps both sides' settings (online_record_all + recommendation prefs),
the in-app browser plugin args next to ShareFileArgs, and upstream's
R8-off release build plus package-wide ProGuard keeps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Scenes get the Online library's query builder: groups of alternatives
(AND between groups), exclusions and rank-higher terms, matched as whole
words or phrases in titles, tags and performers, best matches first.
The query is part of the scene filter, so Save as Smart Collection keeps
it; that form now opens pre-filled (it used to drop the filter).
Also fixes Load more skipping a page of scenes.

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

changeset-bot Bot commented Oct 16, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: d40c921

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

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, we are unable to review this pull request

The GitHub API does not allow us to fetch diffs exceeding 20000 lines

@morpheasgr morpheasgr closed this Oct 16, 2026
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