import subprocess import json from pathlib import Path from services.base import BaseService class TenService(BaseService): def name(self): return "Ten" def slugify(self, text): # Ten slugs are usually just lowercase with hyphens import re text = text.lower().replace("&", "and") text = re.sub(r"[^a-z0-t0-9\s]", "", text) return re.sub(r"\s+", "-", text).strip("-") def run_ytdlp_json(self, url): result = subprocess.run([ "yt-dlp", "-J", "--netrc", # Using .netrc for headless auth "--no-flat-playlist", url ], capture_output=True, text=True) try: return json.loads(result.stdout) except: return None def discover_seasons(self, show_title, source_season=None, source_url=None): # 1. Enforce source_season requirement for Ten if not source_season: print(f"❌ Error: Ten requires 'source_season' in series.json for '{show_title}'") return None if config.get("source_url"): url = config["source_url"] else: slug = self.slugify(show_title) # Ten URL structure: /v/show-name/season-YYYY or /v/show-name/season-X url = f"https://10play.com.au/{slug}/episodes/season-{source_season}" print(f"🔎 Querying Ten: {url}") data = self.run_ytdlp_json(url) if not data or "entries" not in data: print(f"⚠️ Could not find episodes for {show_title} Season {source_season}") return None return [{ "season": source_season, "data": data }] def normalize_episode(self, output_title, entry, season_num=None): # Ten uses 'episode_number' in its JSON episode_idx = entry.get("episode_number") or 0 episode_id = entry.get("id") # Clean title (Ten often includes show name in the episode title) raw_title = entry.get("title") or "" clean_title = raw_title.replace(output_title, "").strip(": ").strip() # Use the provided season_num (which we resolved in autograbber.py) # Convert to int to handle potential string inputs from JSON for formatting try: s_val = int(season_num) except (ValueError, TypeError): s_val = 1 show_clean = output_title.replace(" ", ".") title_clean = clean_title.replace(" ", ".") filename = f"{show_clean}.S{s_val:02d}E{episode_idx:02d}.{title_clean}".strip(".") return { "show": show_clean, "episode_id": episode_id, "filename": filename } def download_episode(self, episode, entry, download_dir): # You can reuse the logic from your iViewService here # Just ensure --netrc is included in the final subprocess call pass