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 (<img class="rmbd">); 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) <noreply@anthropic.com>
This commit is contained in:
goon-foss 2026-07-03 13:02:47 +02:00
parent 393dbe548a
commit 0c18472a15
5 changed files with 38 additions and 6 deletions

View file

@ -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: <img class="... rmbd ..." src="..."> (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'<img[^>]+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},

View file

@ -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"

View file

@ -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',

View file

@ -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';

View file

@ -33,12 +33,12 @@ export function DonateScreen() {
<Text style={styles.footnoteTitle}>Why crypto?</Text>
<Text style={styles.footnoteBody}>
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.
</Text>
<Text style={[styles.footnoteBody, { marginTop: 10 }]}>
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.
</Text>
@ -83,7 +83,7 @@ function CoinCard({ coin }: { coin: DonateCoin }) {
{placeholder ? (
<View style={styles.placeholderBox}>
<Text style={styles.placeholderText}>
Address not configured yet see{' '}
Address not configured yet, see{' '}
<Text style={{ color: theme.muted, fontWeight: '700' }}>mobile/src/lib/donate.ts</Text>
</Text>
</View>