Merge pull request #15 from mltobi/fix-delete-recording-magenta2

merci!
This commit is contained in:
nirvana-7777
2026-10-06 10:06:48 +02:00
committed by GitHub
6 changed files with 72 additions and 11 deletions
+2
View File
@@ -1 +1,3 @@
.aider*
*.pyc
__pycache__/
@@ -43,9 +43,10 @@ def generate_qr_code_png(data: str, size: int = 512) -> Optional[bytes]:
# Generate image using pure Python PNG backend
img = qr.make_image(image_factory=PyPNGImage, fill_color="black", back_color="white")
# Convert to PNG bytes
# Convert to PNG bytes. PyPNGImage.save() always writes PNG and does
# not accept a `format` kwarg (unlike PIL), so it must be omitted here.
buffer = io.BytesIO()
img.save(buffer, format="PNG")
img.save(buffer)
png_data = buffer.getvalue()
return png_data
@@ -373,6 +373,10 @@ VOD_PREFIX_MOVIE_SH = "GN_SH"
PVR_GET_RECORDINGS_PATH = "/get-recordings"
PVR_RECORDINGS_PATH = "/recordings"
# recordings are deleted with HTTP DELETE on this listing-GUID-based path (returns 202 Accepted),
# NOT via DELETE /recordings/{id} (which 404s for every identifier).
PVR_DELETE_RECORDING_FOR_LISTING_PATH = "/delete-recording-for-listing"
# Default and maximum page sizes for the nPVR get-recordings endpoint
PVR_DEFAULT_PAGE_LIMIT = 500
PVR_MAX_PAGE_LIMIT = 500
@@ -44,6 +44,7 @@ from ...base.utils.logger import logger
from .constants import (
PVR_DEFAULT_PAGE_LIMIT,
PVR_DELETE_RECORDING_FOR_LISTING_PATH,
PVR_GET_RECORDINGS_PATH,
PVR_MAX_PAGE_LIMIT,
PVR_RECORDINGS_PATH,
@@ -144,23 +145,69 @@ class RecordingsManager(PvrHttpMixin):
"""
Permanently delete a recording on the nPVR backend.
The recording_id received from the PVR client is the MPX playback GUID
(content_id). Deletion is performed with HTTP DELETE on
``/delete-recording-for-listing/{listingGuid}`` (confirmed to return
202 Accepted) - the documented ``/recordings/{id}`` path 404s for every
known identifier.
Args:
recording_id: The recording's ``id`` field (not externalRecordingId).
recording_id: The recording's content_id (MPX GUID).
Raises:
RuntimeError: When the API returns a non-200/204 status or the
``auth_headers_callback`` is not configured.
KeyError: When the recording does not exist (404).
RuntimeError: When the recording cannot be resolved or the API
returns a non-success status.
"""
pvr_base_url = self._get_pvr_base_url()
url = f"{pvr_base_url}{PVR_RECORDINGS_PATH}/{recording_id}"
listing_guid = self._resolve_listing_guid(recording_id)
if not listing_guid:
raise RuntimeError(
f"{self._provider}: cannot delete recording '{recording_id}' - "
"no matching listing GUID found"
)
url = f"{pvr_base_url}{PVR_DELETE_RECORDING_FOR_LISTING_PATH}/{listing_guid}"
status_code = self._delete(url)
logger.info(
f"{self._provider}: Deleted recording '{recording_id}' "
f"[HTTP {status_code}]"
f"(listing '{listing_guid}') [HTTP {status_code}]"
)
def _resolve_listing_guid(self, recording_id: str) -> str:
"""
Resolve the listing GUID for the recording the PVR client refers to.
The client sends content_id (MPX playback GUID); the delete endpoint
needs the recording's ``listing.guid``. Fetch the raw recordings and
match on the playback GUID (or the internal/external recording ids).
"""
try:
pvr_base_url = self._get_pvr_base_url()
url = f"{pvr_base_url}{PVR_GET_RECORDINGS_PATH}"
params = {
"limit": PVR_MAX_PAGE_LIMIT,
"offset": 1,
"byRecordingStatus": "|".join(PVR_RECORDING_STATUSES_ALL),
}
data = self._get(url, params) or {}
for raw in data.get("recordings", []):
if not raw:
continue
internal_id = raw.get("id", "")
external_id = raw.get("externalRecordingId", "")
mpx_guid = PvrHelpers.extract_mpx_guid(raw.get("playbackUrl"))
if recording_id in (internal_id, external_id, mpx_guid):
listing = raw.get("listing") or {}
if isinstance(listing, dict):
return listing.get("guid") or ""
except Exception as exc:
logger.warning(
f"{self._provider}: could not resolve listing guid for "
f"'{recording_id}': {exc}"
)
return ""
def get_recording_manifest(self, recording_id: str) -> Optional[str]:
"""
Return the playback URL for a recording.
@@ -320,7 +367,6 @@ class RecordingsManager(PvrHttpMixin):
content_id=effective_content_id,
name=title,
provider=self._provider,
# descriptions
description=description,
plot=plot,
plot_outline=plot_outline,
@@ -14,7 +14,7 @@ from urllib.parse import parse_qs, unquote, urlparse
from ...base.network import HTTPManager
from ...base.ui import NotificationFactory, NotificationInterface, NotificationResult
from ...base.utils.logger import logger
from .constants import DEFAULT_PLATFORM, DEFAULT_REQUEST_TIMEOUT, GRANT_TYPES, MAGENTA2_PLATFORMS
from .constants import DEFAULT_PLATFORM, DEFAULT_REQUEST_TIMEOUT, GRANT_TYPES, MAGENTA2_PLATFORMS, render_user_agent
@dataclass
@@ -73,7 +73,9 @@ class RemoteLoginHandler:
# FIX: Get platform config correctly
self.platform_config = MAGENTA2_PLATFORMS[DEFAULT_PLATFORM]
self.user_agent = self.platform_config["user_agent"]
# The platform config has no static "user_agent" key - the UA is
# rendered from the platform's templates via render_user_agent().
self.user_agent = render_user_agent(DEFAULT_PLATFORM)
# Get or create notifier with http_manager
if notifier:
+6
View File
@@ -17,6 +17,12 @@ requests~=2.32.5
Unidecode~=1.3.7
PyYAML~=6.0.3
# QR-code login (magenta2 SAM3 remote login). pypng provides qrcode's pure
# Python PNG backend so no Pillow/system image libs are required in the
# standalone/Docker runtime.
qrcode>=7.4
pypng>=0.0.21
# Optional but recommended
aiohttp>=3.8.5
cryptography>=41.0.4