Skip to content
This repository was archived by the owner on Jun 14, 2026. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions src/trackers/SPD.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 5 additions & 1 deletion src/trackersetup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment on lines +430 to +433

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Don't treat “claims check failed” as “not claimed”.

This branch returns SPD's raw boolean straight into the normal claims flow. In src/trackerstatus.py:94-105, that result is collapsed into local_tracker_status['skipped'] = bool(claimed), so the False returned by SPD.get_claims() on missing API keys, 403s, and transport errors becomes “safe to upload”. That makes the new staff-reservation guard fail open whenever the API cannot be verified.

🛡️ Suggested contract change
         if tracker.upper() == "SPD":
             if hasattr(tracker_instance, 'get_claims'):
-                return await tracker_instance.get_claims(meta)
+                claimed = await tracker_instance.get_claims(meta)
+                if claimed is None:
+                    meta['tracker_status'].setdefault(tracker, {})['skip_upload'] = True
+                    return True
+                return claimed
             return None

And have SPD.get_claims() return None for configuration/API failures instead of False.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/trackersetup.py` around lines 430 - 433, The SPD tracker must not treat
verification failures as "not claimed": change the SPD implementation (the
SPD.get_claims method) to return None for configuration/API/transport failures
(only True for claimed, False for explicitly not claimed), and keep
trackersetup.py's branch (where tracker_instance.get_claims is awaited)
returning the raw result (None/True/False) unchanged so the later logic that
builds local_tracker_status can distinguish None (verification failure) from
False (explicitly not claimed) when computing local_tracker_status['skipped'].

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
Expand Down