"""xtapes.la, konektor filmow (kategoria "Full Movies"). Dodany 2026-10-15. WordPress z otwartym REST API, wiec liste bierzemy z `/wp-json/wp/v2/posts` z filtrem kategorii, a nie z HTML-a. Ocena przed dodaniem (2026-10-15): 1846 filmow, 3 do 5 nowych dziennie, czesto w dniu premiery; z 30 najnowszych 10 nie mielismy wcale, reszte z yourdailypornmovies/paradisehill/mangoporn (xtapes dokleja im kolejny link). Metadane z REST (`_embed=wp:term`): - tytul "TYTUL (RRRR) - Full Movie" -> tytul i rok, - studio to kategoria w nawiasie kwadratowym ("[EvilAngel]", "[Brazzers Movies]"), - pozostale kategorie to gatunki (bez "Full Movies"), - obsada to tagi (`post_tag`), - opis z `content` (czesto pusty). Ze strony filmu: odtwarzacze (iframe), dlugosc (`itemprop="duration"`), okladka (og:image). Odtwarzacze: sextapes.to (P.A.C.K.E.R. + JWPlayer, HLS typu StreamWish) i maxcloud.fit. maxcloud to posrednik (lista serwerow w base64, za nia kolejny serwis z blokada devtools), wiec go pomijamy. Link sextapes jest zwiazany z IP rozwiazujacego (z VPS 403 z innego IP), dlatego rozwiazuje go telefon (packerHoster.ts), a backend go nie ekstrahuje (playback.py). Dostep: VPS dostaje od Cloudflare 403 na xtapes.la (REST i strony), przez Bright Data 200. Proxy tylko do HTML/JSON, nie do wideo. sextapes.to z VPS odpowiada bez proxy. """ from __future__ import annotations import html as html_mod import json import logging import re import time from collections.abc import Iterator from datetime import datetime from urllib.parse import urlparse from app.config import get_settings from app.connectors.base import ( BaseMovieConnector, RawMovie, RawPerformer, RawPlaybackSource, RawStudio, RawTag, ) from app.extractors import browser_get from app.models.source import SourceKind from app.normalize.text import slugify log = logging.getLogger(__name__) _BASE = "https://xtapes.la" #: Id kategorii "Full Movies" (sprawdzone 2026-10-15; slugi kategorii sa zaciemnione liczbami). _MOVIES_CATEGORY = 1766 _PER_PAGE = 30 _TITLE_RE = re.compile( r"^(?P