From 15c1e4e5e1cc82528cf3e76a0cc8dc8d6e562ffb Mon Sep 17 00:00:00 2001 From: jtrzupek Date: Fri, 3 Jul 2026 13:02:47 +0200 Subject: [PATCH] fix(feedback): freeomovie posters + dead hoster + myvidplay; watchporn _embed_iframe guard From user bug reports (8f8c10c0 freeomovie, c156e4b7/8db71220 watchporn scene): freeomovie (app/connectors/freeomovie.py): - Parse the detail-page poster (); RawMovie was built with no poster_url, so title-only orphans (no canonical match) rendered blank cards. Re-ingest backfilled existing blanks 11 -> 1. - _host_label now returns the registrable domain (parts[-2]) not the subdomain, so video.player4me.xyz labels as 'player4me' (was garbage 'freeomovie:video'). - Skip player4me (JS-SPA, no known resolver); pruned 28 dead freeomovie:video sources. - myvidplay.com added to mobile DOOD_HOSTS (doodstream.ts): it 301s to playmogo.com (doodcdn clone), so it now routes to the native dood resolver instead of a dead WebView. watchporn: the reported 4 duplicate/broken quality rows were the API process serving a STALE extractor registry (never restarted after the re-enable deploy) -> _embed_iframe scraped 2 tokenless get_file (403) + 2 preview trailers (404). The running api now serves the native extractor (2 clean playing links, verified). Hardened _embed_iframe to skip preview/videos_screenshots clips and tokenless get_file so this class can't recur. Also: DonateScreen em-dashes -> commas/semicolon (no-em-dash rule). Co-Authored-By: Claude Opus 4.8 (1M context) --- app/connectors/freeomovie.py | 19 ++++++++++++++++--- app/extractors/tubes/_embed_iframe.py | 9 +++++++++ mobile/src/changelog.ts | 7 +++++++ mobile/src/lib/doodstream.ts | 3 +++ mobile/src/screens/DonateScreen.tsx | 6 +++--- 5 files changed, 38 insertions(+), 6 deletions(-) diff --git a/app/connectors/freeomovie.py b/app/connectors/freeomovie.py index 7bdf6fa..57ebb89 100644 --- a/app/connectors/freeomovie.py +++ b/app/connectors/freeomovie.py @@ -50,9 +50,16 @@ _LIST_ITEM_RE = re.compile( ) _TABS_RE = re.compile(r"var\s+TABS\s*=\s*(\[.*?\])\s*;", re.DOTALL) _ARTICLE_SECTION_RE = re.compile(r'"articleSection"\s*:\s*"([^"]+)"') +# Poster: (fastpic.org itp.). Bez tego film-orphan +# (tytuł bez matchu do canonical) miał poster_url=None → pusty kafelek (report 8f8c10c0). +_POSTER_RE = re.compile( + r']+class="[^"]*\brmbd\b[^"]*"[^>]+src="(https?://[^"]+)"', + re.IGNORECASE, +) -# Hostery pomijane (martwe/flaky). -_SKIP_HOSTS = ("streamtape", "mxdrop", "mixdrop", "streamsb") +# Hostery pomijane (martwe/flaky). player4me = JS-SPA bez znanego resolvera (28 martwych +# źródeł freeomovie:video, report 8f8c10c0). +_SKIP_HOSTS = ("streamtape", "mxdrop", "mixdrop", "streamsb", "player4me") def _host_label(embed_url: str) -> str | None: @@ -60,7 +67,9 @@ def _host_label(embed_url: str) -> str | None: if not host: return None parts = host.replace("www.", "").split(".") - return parts[0] if parts else None + # Rejestrowalna domena (SLD), nie subdomena: video.player4me.xyz → 'player4me' (nie + # 'video'), myvidplay.com → 'myvidplay'. Inaczej origin był śmieciowy (freeomovie:video). + return parts[-2] if len(parts) >= 2 else parts[0] class FreeoMovieConnector(BaseMovieConnector): @@ -177,11 +186,15 @@ class FreeoMovieConnector(BaseMovieConnector): if not playback: return None # bez playbacku film jest bezużyteczny (nie tworzymy orphana) + pm = _POSTER_RE.search(detail) + poster_url = html.unescape(pm.group(1)) if pm else None + slug = urlparse(url).path.strip("/").split("/")[-1] return RawMovie( external_id=slug, title=title, url=url, + poster_url=poster_url, tags=tags, playback_sources=playback, raw={"source": "freeomovie", "url": url}, diff --git a/app/extractors/tubes/_embed_iframe.py b/app/extractors/tubes/_embed_iframe.py index 32bbc26..393fabf 100644 --- a/app/extractors/tubes/_embed_iframe.py +++ b/app/extractors/tubes/_embed_iframe.py @@ -283,6 +283,15 @@ def _extract_direct_stream_urls(page_html: str, page_url: str) -> list[StreamSou # i embed pages z .mp4 suffix (playmogo/dood /d/ — to HTML, nie video). if _NOT_DIRECT_STREAM_RE.search(url): continue + # KVS false-positives (report c156e4b7/8db71220, watchporn przez _embed_iframe): + # preview_/videos_screenshots to krótkie trailery (404), a surowy get_file/ bez + # podpisanego query-tokena daje 403 — kt_player dokłada token z license_code (patrz + # natywny watchporn.extract). Żadne z nich to pełne wideo → pomijamy. + low = url.lower() + if "videos_screenshots/" in low or "/preview_" in low: + continue + if "/get_file/" in low and "?" not in url: + continue # Quality wykrycie z nazwy pliku q_int = 0 q_label = "mp4" diff --git a/mobile/src/changelog.ts b/mobile/src/changelog.ts index 8c2b1c1..5215ae7 100644 --- a/mobile/src/changelog.ts +++ b/mobile/src/changelog.ts @@ -16,6 +16,13 @@ export type ChangelogEntry = { }; export const CHANGELOG: ChangelogEntry[] = [ + { + id: '2026-07-03', + date: 'July 2026', + items: [ + 'Movie posters show up again (freeomovie), and more of its sources actually play (myvidplay).', + ], + }, { id: '2026-07-02b', date: 'July 2026', diff --git a/mobile/src/lib/doodstream.ts b/mobile/src/lib/doodstream.ts index e92ec3e..cf87dbc 100644 --- a/mobile/src/lib/doodstream.ts +++ b/mobile/src/lib/doodstream.ts @@ -38,6 +38,9 @@ const DOOD_HOSTS = new Set([ 'ds2play.com', 'ds2video.com', 'd000d.com', 'd0o0d.com', 'do0od.com', 'do7go.com', 'dooood.com', 'd0000d.com', + // myvidplay.com: 301 -> playmogo.com (doodcdn clone). Najczęstszy hoster freeomovie + // (63 filmy); bez tego isDoodStream() false -> WebView -> nie gra (report 8f8c10c0). + 'myvidplay.com', ]); const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36'; diff --git a/mobile/src/screens/DonateScreen.tsx b/mobile/src/screens/DonateScreen.tsx index 5cf9b45..034f1e7 100644 --- a/mobile/src/screens/DonateScreen.tsx +++ b/mobile/src/screens/DonateScreen.tsx @@ -33,12 +33,12 @@ export function DonateScreen() { Why crypto? Mainstream payment processors (Stripe, PayPal, Patreon) refuse adult - projects — even open-source tooling like this one. Crypto is the only + projects, even open-source tooling like this one. Crypto is the only channel that does not require us to identify our donors and does not let third parties freeze the project mid-month. - Monero is privacy-preserving by design — if you care about not being + Monero is privacy-preserving by design; if you care about not being linked to this app on-chain, send XMR. Bitcoin and USDT are public ledgers; use a fresh wallet if anonymity matters to you. @@ -83,7 +83,7 @@ function CoinCard({ coin }: { coin: DonateCoin }) { {placeholder ? ( - Address not configured yet — see{' '} + Address not configured yet, see{' '} mobile/src/lib/donate.ts