Goon — self-hosted aggregator for adult-content scene metadata. Indexes scenes from TPDB, StashDB, and 30+ public adult tube sites. Cross-source deduplication via perceptual hash + Levenshtein distance. FastAPI backend + APScheduler worker + React Native (Expo) mobile client. FOSS, ad-free, donation-funded. See README for details.
104 lines
3.5 KiB
Python
104 lines
3.5 KiB
Python
"""Probe mangoporn movie embed hosters — wyciąga real stream URLs / patterns
|
|
z każdego hostera, decyduje czy mamy yt-dlp support / dedicated extractor /
|
|
drop.
|
|
|
|
Output: tabela hoster | embed status | stream URL pattern | yt-dlp support |
|
|
recommended action.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import re
|
|
import sys
|
|
|
|
try:
|
|
from curl_cffi import requests as cf
|
|
except ImportError:
|
|
print("Install curl_cffi: pip install curl_cffi")
|
|
sys.exit(1)
|
|
|
|
|
|
HOSTERS = [
|
|
("doply", "https://doply.net/e/wzuz0w61lm7c"),
|
|
("mixdrop", "https://mixdrop.my/e/03m9p1kpi7llwl"),
|
|
("voe", "https://voe.sx/e/k9j1qhxzbeli"),
|
|
("luluvid", "https://luluvid.com/e/67jkt27c33fn"),
|
|
("embedseek", "https://embedseek.com/e/abcdef"),
|
|
("upns", "https://upns.one/e/abcdef"),
|
|
("seekplayer", "https://seekplayer.com/e/abcdef"),
|
|
("rpmplay", "https://rpmplay.com/e/abcdef"),
|
|
("player4me", "https://player4me.com/e/abcdef"),
|
|
("easyvidplayer", "https://easyvidplayer.com/e/abcdef"),
|
|
("frdl", "https://frdl.to/abcdef"),
|
|
("playmogo", "https://playmogo.com/e/wzuz0w61lm7c"),
|
|
]
|
|
|
|
|
|
# Patterns suggesting embedded video
|
|
M3U8_RE = re.compile(r'https?://[^"\'\s<>]+\.m3u8[^"\'\s<>]*')
|
|
MP4_RE = re.compile(r'https?://[^"\'\s<>]+\.mp4[^"\'\s<>]*')
|
|
PACKER_RE = re.compile(r"eval\(function\(p,a,c,k,e,d\)")
|
|
JWPLAYER_RE = re.compile(r"jwplayer\(['\"]\w*['\"]\)\.setup")
|
|
KVS_RE = re.compile(r"flashvars\s*=\s*\{")
|
|
SOURCES_TAG_RE = re.compile(r'<source[^>]+src="[^"]+"')
|
|
|
|
|
|
def probe(name: str, url: str) -> dict:
|
|
out = {"hoster": name, "url": url}
|
|
try:
|
|
r = cf.get(url, impersonate="chrome120", timeout=12, allow_redirects=True)
|
|
out["status"] = r.status_code
|
|
out["final_url"] = r.url
|
|
body = r.text
|
|
out["body_size"] = len(body)
|
|
out["m3u8_hits"] = len(M3U8_RE.findall(body))
|
|
out["mp4_hits"] = len(MP4_RE.findall(body))
|
|
out["has_packer"] = bool(PACKER_RE.search(body))
|
|
out["has_jwplayer"] = bool(JWPLAYER_RE.search(body))
|
|
out["has_kvs"] = bool(KVS_RE.search(body))
|
|
out["has_source_tag"] = bool(SOURCES_TAG_RE.search(body))
|
|
# Sample one m3u8/mp4 URL if found
|
|
m = M3U8_RE.search(body)
|
|
if m:
|
|
out["m3u8_sample"] = m.group(0)[:120]
|
|
m = MP4_RE.search(body)
|
|
if m:
|
|
out["mp4_sample"] = m.group(0)[:120]
|
|
except Exception as e:
|
|
out["err"] = f"{type(e).__name__}: {str(e)[:120]}"
|
|
return out
|
|
|
|
|
|
def main() -> None:
|
|
results = []
|
|
for name, url in HOSTERS:
|
|
r = probe(name, url)
|
|
results.append(r)
|
|
keys_to_show = [k for k in r if k != "url"]
|
|
line = " | ".join(f"{k}={r[k]}" for k in keys_to_show if r[k] not in (None, "", 0, False))
|
|
print(f"\n[{name}] {url}")
|
|
print(f" {line}")
|
|
print("\n=== Summary ===")
|
|
for r in results:
|
|
verdict = "?"
|
|
if r.get("err"):
|
|
verdict = "ERR (dead?)"
|
|
elif r.get("status") and r["status"] >= 400:
|
|
verdict = f"HTTP {r['status']} (dead)"
|
|
elif r.get("m3u8_hits"):
|
|
verdict = f"HLS direct ({r['m3u8_hits']} m3u8)"
|
|
elif r.get("mp4_hits"):
|
|
verdict = f"MP4 direct ({r['mp4_hits']} mp4)"
|
|
elif r.get("has_source_tag"):
|
|
verdict = "KVS-like <source> tags"
|
|
elif r.get("has_packer"):
|
|
verdict = "P.A.C.K.E.R. obfuscated"
|
|
elif r.get("has_jwplayer"):
|
|
verdict = "JWPlayer (needs JS exec)"
|
|
else:
|
|
verdict = "no streams found"
|
|
print(f" {r['hoster']:15s} → {verdict}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|