From 1e03d823c86809e59e0b5213f5d91580d44f946f Mon Sep 17 00:00:00 2001 From: HichamLL04 Date: Sun, 7 Jun 2026 01:09:12 +0200 Subject: [PATCH 1/2] feat(trackers/spd): handle staff reservations --- src/trackers/SPD.py | 45 +++++++++++++++++++++++++++++++++++++++++++++ src/trackersetup.py | 6 +++++- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/trackers/SPD.py b/src/trackers/SPD.py index 5e22d7c3a..341f44b80 100644 --- a/src/trackers/SPD.py +++ b/src/trackers/SPD.py @@ -38,6 +38,51 @@ def __init__(self, config: Config) -> None: 'Authorization': api_key, }, timeout=30.0) + async def get_claims(self, meta: Meta) -> bool: + if not self.config['TRACKERS'][self.tracker].get('api_key'): + return False + + url = f"{self.url}/api/reservation" + try: + response = await self.session.get(url=url) + if response.status_code == 200: + reservations = response.json() + if not isinstance(reservations, list): + if meta.get('debug'): + console.print(f"[{self.tracker}]: Unexpected reservations response format: {reservations}") + return False + + spd_name = await self.edit_name(meta) + normalized_spd_name = re.sub(r'[^a-zA-Z0-9]', '', spd_name).lower() + + for reservation in reservations: + if not isinstance(reservation, dict): + continue + if reservation.get('is_uploaded') is True: + continue + + res_name = reservation.get('name') + if not res_name: + continue + + normalized_res_name = re.sub(r'[^a-zA-Z0-9]', '', str(res_name)).lower() + + if normalized_spd_name == normalized_res_name: + console.print( + f"[green]Staff reservation match found at [cyan]{self.tracker}: [yellow]{res_name}[/green]" + ) + return True + elif response.status_code == 403: + if meta.get('debug'): + console.print(f"[{self.tracker}]: Access denied (403) to reservations API. Skipping check.") + else: + if meta.get('debug'): + console.print(f"[{self.tracker}]: Failed to fetch reservations. HTTP Status: {response.status_code}") + except Exception as e: + if meta.get('debug'): + console.print(f"[{self.tracker}]: Error fetching reservations: {e}") + return False + async def get_cat_id(self, meta: Meta) -> Optional[str]: if not meta.get('language_checked', False): await languages_manager.process_desc_language(meta, tracker=self.tracker) diff --git a/src/trackersetup.py b/src/trackersetup.py index ae2f13493..9452399d6 100644 --- a/src/trackersetup.py +++ b/src/trackersetup.py @@ -424,10 +424,14 @@ async def write_internal_claims_to_file(self, file_path: str, data: list[JsonDic console.print(f"An error occurred: {e}") async def get_torrent_claims(self, meta: Meta, tracker: str) -> Optional[bool]: - file_path = os.path.join(meta['base_dir'], 'data', 'banned', f'{tracker}_claimed_releases.json') tracker_instance = self._create_tracker_instance(tracker) if tracker_instance is None: return None + if tracker.upper() == "SPD": + if hasattr(tracker_instance, 'get_claims'): + return await tracker_instance.get_claims(meta) + return None + file_path = os.path.join(meta['base_dir'], 'data', 'banned', f'{tracker}_claimed_releases.json') claims_url = getattr(tracker_instance, 'claims_url', None) if not isinstance(claims_url, str): return None From 5497ad01314cdb5517b4b2872be409e8ceb59dd3 Mon Sep 17 00:00:00 2001 From: HichamLL04 Date: Sun, 7 Jun 2026 01:09:12 +0200 Subject: [PATCH 2/2] feat(trackers/spd): handle staff reservations --- src/trackers/SPD.py | 55 +++++++++++++++++++++++++++++++++++++++++++++ src/trackersetup.py | 6 ++++- 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/src/trackers/SPD.py b/src/trackers/SPD.py index 5e22d7c3a..c205773dc 100644 --- a/src/trackers/SPD.py +++ b/src/trackers/SPD.py @@ -38,6 +38,61 @@ def __init__(self, config: Config) -> None: 'Authorization': api_key, }, timeout=30.0) + async def get_claims(self, meta: Meta) -> Optional[bool]: + if not self.config['TRACKERS'][self.tracker].get('api_key'): + return None + + url = f"{self.url}/api/reservation" + try: + response = await self.session.get(url=url) + if response.status_code == 200: + reservations = response.json() + if not isinstance(reservations, list): + if meta.get('debug'): + console.print(f"[{self.tracker}]: Unexpected reservations response format: {reservations}") + return None + + spd_name = await self.edit_name(meta) + normalized_spd_name = re.sub(r'[^a-zA-Z0-9]', '', spd_name).lower() + + for reservation in reservations: + if not isinstance(reservation, dict): + continue + if reservation.get('is_uploaded') is True: + continue + + res_name = reservation.get('name') + if not res_name: + continue + + # Apply the same folding routine as edit_name() + folded_res_name = str(res_name).replace(':', ' -') + folded_res_name = unicodedata.normalize("NFKD", folded_res_name) + folded_res_name = folded_res_name.encode("ascii", "ignore").decode("ascii") + folded_res_name = re.sub(r'[\\/*?"<>|]', '', folded_res_name) + folded_res_name = re.sub(r"\s{2,}", " ", folded_res_name) + + normalized_res_name = re.sub(r'[^a-zA-Z0-9]', '', folded_res_name).lower() + + if normalized_spd_name == normalized_res_name: + console.print( + f"[green]Staff reservation match found at [cyan]{self.tracker}: [yellow]{res_name}[/green]" + ) + return True + return False + elif response.status_code == 403: + if meta.get('debug'): + console.print(f"[{self.tracker}]: Access denied (403) to reservations API. Skipping check.") + return None + else: + if meta.get('debug'): + console.print(f"[{self.tracker}]: Failed to fetch reservations. HTTP Status: {response.status_code}") + return None + except Exception as e: + if meta.get('debug'): + console.print(f"[{self.tracker}]: Error fetching reservations: {e}") + return None + async def get_cat_id(self, meta: Meta) -> Optional[str]: if not meta.get('language_checked', False): await languages_manager.process_desc_language(meta, tracker=self.tracker) diff --git a/src/trackersetup.py b/src/trackersetup.py index ae2f13493..9452399d6 100644 --- a/src/trackersetup.py +++ b/src/trackersetup.py @@ -424,10 +424,14 @@ async def write_internal_claims_to_file(self, file_path: str, data: list[JsonDic console.print(f"An error occurred: {e}") async def get_torrent_claims(self, meta: Meta, tracker: str) -> Optional[bool]: - file_path = os.path.join(meta['base_dir'], 'data', 'banned', f'{tracker}_claimed_releases.json') tracker_instance = self._create_tracker_instance(tracker) if tracker_instance is None: return None + if tracker.upper() == "SPD": + if hasattr(tracker_instance, 'get_claims'): + return await tracker_instance.get_claims(meta) + return None + file_path = os.path.join(meta['base_dir'], 'data', 'banned', f'{tracker}_claimed_releases.json') claims_url = getattr(tracker_instance, 'claims_url', None) if not isinstance(claims_url, str): return None