main: consolidate integrated changes after v0.1.54
This commit is contained in:
@@ -11,7 +11,7 @@ from __future__ import annotations
|
||||
import json
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Dict, List, Optional, Set
|
||||
from typing import Any, Callable, Dict, List, Optional, Set
|
||||
from urllib.parse import urlencode, urljoin, urlsplit
|
||||
|
||||
try: # pragma: no cover - optional dependency (Kodi dependency)
|
||||
@@ -43,7 +43,7 @@ SETTING_DUMP_HTML = "dump_html_einschalten"
|
||||
SETTING_SHOW_URL_INFO = "show_url_info_einschalten"
|
||||
SETTING_LOG_ERRORS = "log_errors_einschalten"
|
||||
|
||||
DEFAULT_BASE_URL = ""
|
||||
DEFAULT_BASE_URL = "https://einschalten.in"
|
||||
DEFAULT_INDEX_PATH = "/"
|
||||
DEFAULT_NEW_TITLES_PATH = "/movies/new"
|
||||
DEFAULT_SEARCH_PATH = "/search"
|
||||
@@ -56,6 +56,16 @@ HEADERS = {
|
||||
"Accept-Language": "de-DE,de;q=0.9,en;q=0.8",
|
||||
"Connection": "keep-alive",
|
||||
}
|
||||
ProgressCallback = Optional[Callable[[str, Optional[int]], Any]]
|
||||
|
||||
|
||||
def _emit_progress(callback: ProgressCallback, message: str, percent: Optional[int] = None) -> None:
|
||||
if not callable(callback):
|
||||
return
|
||||
try:
|
||||
callback(str(message or ""), None if percent is None else int(percent))
|
||||
except Exception:
|
||||
return
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -526,6 +536,34 @@ class EinschaltenPlugin(BasisPlugin):
|
||||
self._session = requests.Session()
|
||||
return self._session
|
||||
|
||||
def _http_get_text(self, url: str, *, timeout: int = 20) -> tuple[str, str]:
|
||||
_log_url(url, kind="GET")
|
||||
_notify_url(url)
|
||||
sess = self._get_session()
|
||||
response = None
|
||||
try:
|
||||
response = sess.get(url, headers=HEADERS, timeout=timeout)
|
||||
response.raise_for_status()
|
||||
final_url = (response.url or url) if response is not None else url
|
||||
body = (response.text or "") if response is not None else ""
|
||||
_log_url(final_url, kind="OK")
|
||||
_log_response_html(final_url, body)
|
||||
return final_url, body
|
||||
finally:
|
||||
if response is not None:
|
||||
try:
|
||||
response.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _http_get_json(self, url: str, *, timeout: int = 20) -> tuple[str, Any]:
|
||||
final_url, body = self._http_get_text(url, timeout=timeout)
|
||||
try:
|
||||
payload = json.loads(body or "{}")
|
||||
except Exception:
|
||||
payload = {}
|
||||
return final_url, payload
|
||||
|
||||
def _get_base_url(self) -> str:
|
||||
base = _get_setting_text(SETTING_BASE_URL, default=DEFAULT_BASE_URL).strip()
|
||||
return base.rstrip("/")
|
||||
@@ -646,15 +684,9 @@ class EinschaltenPlugin(BasisPlugin):
|
||||
if not url:
|
||||
return ""
|
||||
try:
|
||||
_log_url(url, kind="GET")
|
||||
_notify_url(url)
|
||||
sess = self._get_session()
|
||||
resp = sess.get(url, headers=HEADERS, timeout=20)
|
||||
resp.raise_for_status()
|
||||
_log_url(resp.url or url, kind="OK")
|
||||
_log_response_html(resp.url or url, resp.text)
|
||||
self._detail_html_by_id[movie_id] = resp.text or ""
|
||||
return resp.text or ""
|
||||
_, body = self._http_get_text(url, timeout=20)
|
||||
self._detail_html_by_id[movie_id] = body
|
||||
return body
|
||||
except Exception as exc:
|
||||
_log_error(f"GET {url} failed: {exc}")
|
||||
return ""
|
||||
@@ -667,16 +699,8 @@ class EinschaltenPlugin(BasisPlugin):
|
||||
if not url:
|
||||
return {}
|
||||
try:
|
||||
_log_url(url, kind="GET")
|
||||
_notify_url(url)
|
||||
sess = self._get_session()
|
||||
resp = sess.get(url, headers=HEADERS, timeout=20)
|
||||
resp.raise_for_status()
|
||||
_log_url(resp.url or url, kind="OK")
|
||||
# Some backends may return JSON with a JSON content-type; for debugging we still dump text.
|
||||
_log_response_html(resp.url or url, resp.text)
|
||||
data = resp.json()
|
||||
return dict(data) if isinstance(data, dict) else {}
|
||||
_, data = self._http_get_json(url, timeout=20)
|
||||
return data
|
||||
except Exception as exc:
|
||||
_log_error(f"GET {url} failed: {exc}")
|
||||
return {}
|
||||
@@ -741,14 +765,8 @@ class EinschaltenPlugin(BasisPlugin):
|
||||
if not url:
|
||||
return []
|
||||
try:
|
||||
_log_url(url, kind="GET")
|
||||
_notify_url(url)
|
||||
sess = self._get_session()
|
||||
resp = sess.get(url, headers=HEADERS, timeout=20)
|
||||
resp.raise_for_status()
|
||||
_log_url(resp.url or url, kind="OK")
|
||||
_log_response_html(resp.url or url, resp.text)
|
||||
payload = _extract_ng_state_payload(resp.text)
|
||||
_, body = self._http_get_text(url, timeout=20)
|
||||
payload = _extract_ng_state_payload(body)
|
||||
return _parse_ng_state_movies(payload)
|
||||
except Exception:
|
||||
return []
|
||||
@@ -759,14 +777,8 @@ class EinschaltenPlugin(BasisPlugin):
|
||||
if not url:
|
||||
return []
|
||||
try:
|
||||
_log_url(url, kind="GET")
|
||||
_notify_url(url)
|
||||
sess = self._get_session()
|
||||
resp = sess.get(url, headers=HEADERS, timeout=20)
|
||||
resp.raise_for_status()
|
||||
_log_url(resp.url or url, kind="OK")
|
||||
_log_response_html(resp.url or url, resp.text)
|
||||
payload = _extract_ng_state_payload(resp.text)
|
||||
_, body = self._http_get_text(url, timeout=20)
|
||||
payload = _extract_ng_state_payload(body)
|
||||
movies = _parse_ng_state_movies(payload)
|
||||
_log_debug_line(f"parse_ng_state_movies:count={len(movies)}")
|
||||
if movies:
|
||||
@@ -784,14 +796,8 @@ class EinschaltenPlugin(BasisPlugin):
|
||||
if page > 1:
|
||||
url = f"{url}?{urlencode({'page': str(page)})}"
|
||||
try:
|
||||
_log_url(url, kind="GET")
|
||||
_notify_url(url)
|
||||
sess = self._get_session()
|
||||
resp = sess.get(url, headers=HEADERS, timeout=20)
|
||||
resp.raise_for_status()
|
||||
_log_url(resp.url or url, kind="OK")
|
||||
_log_response_html(resp.url or url, resp.text)
|
||||
payload = _extract_ng_state_payload(resp.text)
|
||||
_, body = self._http_get_text(url, timeout=20)
|
||||
payload = _extract_ng_state_payload(body)
|
||||
movies, has_more, current_page = _parse_ng_state_movies_with_pagination(payload)
|
||||
_log_debug_line(f"parse_ng_state_movies_page:page={page} count={len(movies)}")
|
||||
if has_more is not None:
|
||||
@@ -844,14 +850,8 @@ class EinschaltenPlugin(BasisPlugin):
|
||||
if not url:
|
||||
return []
|
||||
try:
|
||||
_log_url(url, kind="GET")
|
||||
_notify_url(url)
|
||||
sess = self._get_session()
|
||||
resp = sess.get(url, headers=HEADERS, timeout=20)
|
||||
resp.raise_for_status()
|
||||
_log_url(resp.url or url, kind="OK")
|
||||
_log_response_html(resp.url or url, resp.text)
|
||||
payload = _extract_ng_state_payload(resp.text)
|
||||
_, body = self._http_get_text(url, timeout=20)
|
||||
payload = _extract_ng_state_payload(body)
|
||||
results = _parse_ng_state_search_results(payload)
|
||||
return _filter_movies_by_title(query, results)
|
||||
except Exception:
|
||||
@@ -867,13 +867,7 @@ class EinschaltenPlugin(BasisPlugin):
|
||||
api_url = self._api_genres_url()
|
||||
if api_url:
|
||||
try:
|
||||
_log_url(api_url, kind="GET")
|
||||
_notify_url(api_url)
|
||||
sess = self._get_session()
|
||||
resp = sess.get(api_url, headers=HEADERS, timeout=20)
|
||||
resp.raise_for_status()
|
||||
_log_url(resp.url or api_url, kind="OK")
|
||||
payload = resp.json()
|
||||
_, payload = self._http_get_json(api_url, timeout=20)
|
||||
if isinstance(payload, list):
|
||||
parsed: Dict[str, int] = {}
|
||||
for item in payload:
|
||||
@@ -900,14 +894,8 @@ class EinschaltenPlugin(BasisPlugin):
|
||||
if not url:
|
||||
return
|
||||
try:
|
||||
_log_url(url, kind="GET")
|
||||
_notify_url(url)
|
||||
sess = self._get_session()
|
||||
resp = sess.get(url, headers=HEADERS, timeout=20)
|
||||
resp.raise_for_status()
|
||||
_log_url(resp.url or url, kind="OK")
|
||||
_log_response_html(resp.url or url, resp.text)
|
||||
payload = _extract_ng_state_payload(resp.text)
|
||||
_, body = self._http_get_text(url, timeout=20)
|
||||
payload = _extract_ng_state_payload(body)
|
||||
parsed = _parse_ng_state_genres(payload)
|
||||
if parsed:
|
||||
self._genre_id_by_name.clear()
|
||||
@@ -915,7 +903,7 @@ class EinschaltenPlugin(BasisPlugin):
|
||||
except Exception:
|
||||
return
|
||||
|
||||
async def search_titles(self, query: str) -> List[str]:
|
||||
async def search_titles(self, query: str, progress_callback: ProgressCallback = None) -> List[str]:
|
||||
if not REQUESTS_AVAILABLE:
|
||||
return []
|
||||
query = (query or "").strip()
|
||||
@@ -924,9 +912,12 @@ class EinschaltenPlugin(BasisPlugin):
|
||||
if not self._get_base_url():
|
||||
return []
|
||||
|
||||
_emit_progress(progress_callback, "Einschalten Suche", 15)
|
||||
movies = self._fetch_search_movies(query)
|
||||
if not movies:
|
||||
_emit_progress(progress_callback, "Fallback: Index filtern", 45)
|
||||
movies = _filter_movies_by_title(query, self._load_movies())
|
||||
_emit_progress(progress_callback, f"Treffer verarbeiten ({len(movies)})", 75)
|
||||
titles: List[str] = []
|
||||
seen: set[str] = set()
|
||||
for movie in movies:
|
||||
@@ -936,6 +927,7 @@ class EinschaltenPlugin(BasisPlugin):
|
||||
self._id_by_title[movie.title] = movie.id
|
||||
titles.append(movie.title)
|
||||
titles.sort(key=lambda value: value.casefold())
|
||||
_emit_progress(progress_callback, f"Fertig: {len(titles)} Treffer", 95)
|
||||
return titles
|
||||
|
||||
def genres(self) -> List[str]:
|
||||
@@ -971,14 +963,8 @@ class EinschaltenPlugin(BasisPlugin):
|
||||
if not url:
|
||||
return []
|
||||
try:
|
||||
_log_url(url, kind="GET")
|
||||
_notify_url(url)
|
||||
sess = self._get_session()
|
||||
resp = sess.get(url, headers=HEADERS, timeout=20)
|
||||
resp.raise_for_status()
|
||||
_log_url(resp.url or url, kind="OK")
|
||||
_log_response_html(resp.url or url, resp.text)
|
||||
payload = _extract_ng_state_payload(resp.text)
|
||||
_, body = self._http_get_text(url, timeout=20)
|
||||
payload = _extract_ng_state_payload(body)
|
||||
except Exception:
|
||||
return []
|
||||
if not isinstance(payload, dict):
|
||||
@@ -1079,3 +1065,7 @@ class EinschaltenPlugin(BasisPlugin):
|
||||
return []
|
||||
# Backwards compatible: first page only. UI uses paging via `new_titles_page`.
|
||||
return self.new_titles_page(1)
|
||||
|
||||
|
||||
# Alias für die automatische Plugin-Erkennung.
|
||||
Plugin = EinschaltenPlugin
|
||||
|
||||
Reference in New Issue
Block a user