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