diff --git a/wastream/debrid/torbox.py b/wastream/debrid/torbox.py index 56dd211..dd07ed2 100644 --- a/wastream/debrid/torbox.py +++ b/wastream/debrid/torbox.py @@ -169,15 +169,19 @@ class TorBoxService(BaseDebridService): if not links or not api_key: return links + links.sort(key=quality_sort_key) + top_links = links[:15] + rest_links = links[15:] + cache_timeout = config.get("stream_request_timeout", settings.STREAM_REQUEST_TIMEOUT) check_endpoint = "usenet/checkcached" if download_type == "usenet" else "webdl/checkcached" - cache_logger.debug(f"[TorBox] Checking {len(links)} links (timeout: {cache_timeout}s)") + cache_logger.debug(f"[TorBox] Checking top {len(top_links)} links (timeout: {cache_timeout}s)") start_time = time.time() link_to_hash = {} hashes = [] - for link_dict in links: + for link_dict in top_links: link = link_dict.get("link") if link: if download_type == "usenet": @@ -193,8 +197,10 @@ class TorBoxService(BaseDebridService): hashes.append(link_hash) if not hashes: - cache_logger.debug("[TorBox] No valid links") - return links + cache_logger.debug("[TorBox] No valid links to hash") + for link_dict in rest_links: + link_dict["cache_status"] = "uncached" + return top_links + rest_links http_error_count = 0 @@ -216,25 +222,29 @@ class TorBoxService(BaseDebridService): if response.status_code != 200: cache_logger.error(f"[TorBox] Batch failed: HTTP {response.status_code}") - for link_dict in links: + for link_dict in top_links: link_dict["cache_status"] = "uncached" - return links + for link_dict in rest_links: + link_dict["cache_status"] = "uncached" + return top_links + rest_links response_json = response.json() error_code = response_json.get("error") cooldown_result, http_error_count = await self._handle_cooldown_limit(error_code, http_error_count) if cooldown_result == "RETRY_ERROR": - for link_dict in links: + for link_dict in top_links: link_dict["cache_status"] = "uncached" - return links + for link_dict in rest_links: + link_dict["cache_status"] = "uncached" + return top_links + rest_links elif cooldown_result == "RETRY": continue http_error_count = 0 cache_data = response_json.get("data", {}) - for link_dict in links: + for link_dict in top_links: link = link_dict.get("link") link_hash = link_to_hash.get(link) @@ -280,15 +290,18 @@ class TorBoxService(BaseDebridService): except Exception as e: cache_logger.error(f"[TorBox] Batch error: {type(e).__name__}: {e}") - for link_dict in links: + for link_dict in top_links: link_dict["cache_status"] = "uncached" break - cached_count = sum(1 for r in links if r.get("cache_status") == "cached") - elapsed = time.time() - start_time - cache_logger.debug(f"[TorBox] Done in {elapsed:.1f}s: {cached_count} cached / {len(links) - cached_count} uncached") + for link_dict in rest_links: + link_dict["cache_status"] = "uncached" - return links + cached_count = sum(1 for r in top_links if r.get("cache_status") == "cached") + elapsed = time.time() - start_time + cache_logger.debug(f"[TorBox] Done in {elapsed:.1f}s: {cached_count} cached / {len(top_links) - cached_count} uncached (skipped {len(rest_links)})") + + return top_links + rest_links async def check_cache_and_enrich(self, results: List[Dict], api_key: str, config: Dict, timeout_remaining: float, user_season: Optional[str] = None, user_episode: Optional[str] = None, user_hosts: Optional[List[str]] = None) -> List[Dict]: start_time = time.time() @@ -484,6 +497,38 @@ class TorBoxService(BaseDebridService): debrid_logger.error(f"[TorBox] Failed after {settings.DEBRID_MAX_RETRIES} attempts") return "FATAL_ERROR", http_error_count + async def trigger_auto_download(self, link: str, api_key: str): + if not api_key or not link: + return + + download_type = "usenet" if "/nzb/" in link else "webdl" + create_endpoint = "usenet/createusenetdownload" if download_type == "usenet" else "webdl/createwebdownload" + + cleaned_link = link + if download_type == "webdl" and "&af=" in cleaned_link: + cleaned_link = cleaned_link.split("&af=")[0] + elif download_type == "usenet": + nzb_id = link.split("/")[-1] + cleaned_link = f"{settings.DARKI_API_URL}/nzb/{nzb_id}/download" + + try: + headers = self._get_headers(api_key) + data_payload = {"link": cleaned_link} + if download_type == "webdl": + data_payload["add_only_if_cached"] = False + + debrid_logger.info(f"[TorBox] Triggering auto-cacher for uncached link: {cleaned_link[:50]}...") + + # Fire and forget without retries for background job + await http_client.post( + f"{self.API_URL}/{create_endpoint}", + data=data_payload, + headers=headers, + timeout=settings.HTTP_TIMEOUT + ) + except Exception as e: + debrid_logger.error(f"[TorBox] Auto-cacher background trigger failed: {type(e).__name__}: {e}") + async def convert_link(self, link: str, api_key: str, season: Optional[str] = None, episode: Optional[str] = None, hoster: Optional[str] = None) -> Optional[str]: if not api_key: debrid_logger.error("[TorBox] Empty API key") diff --git a/wastream/public/translations.js b/wastream/public/translations.js index d39fec4..84a9ab9 100644 --- a/wastream/public/translations.js +++ b/wastream/public/translations.js @@ -47,6 +47,7 @@ const translations = { cachedOnly: 'Cached links only', enableUsenet: 'Enable Usenet (TorBox only)', fullSeasonPacks: 'Include Full Season Packs (Usenet)', + autoCache: 'Auto-cache best link (TorBox)', deduplicateResults: 'Deduplicate results', earlyStop: 'Early Stop', earlyStopIncludeNzb: 'Include NZB in count', @@ -62,6 +63,7 @@ const translations = { tokenLabel: 'Token:', hostsInfo: 'Select specific hosts or leave empty for all', sourcesInfo: 'Select specific sources or leave empty for all', + autoCacheInfo: 'Automatically triggers a background download for the best quality link if no streams are cached.', timeoutInfo: 'Maximum search time (default: 20s)', excludedInfo: 'Streams with these words will be filtered', deduplicateInfo: 'Hides duplicate results from different hosts', @@ -179,6 +181,7 @@ const translations = { cachedOnly: 'Liens en cache uniquement', enableUsenet: 'Activer Usenet (TorBox uniquement)', fullSeasonPacks: 'Inclure les packs de saison (Usenet)', + autoCache: 'Auto-cache le meilleur lien (TorBox)', deduplicateResults: 'Dédupliquer les résultats', earlyStop: 'Arrêt Anticipé', earlyStopIncludeNzb: 'Inclure NZB dans le comptage', @@ -194,6 +197,7 @@ const translations = { tokenLabel: 'Token :', hostsInfo: 'Sélectionner des hébergeurs ou laisser vide pour tous', sourcesInfo: 'Sélectionner des sources ou laisser vide pour toutes', + autoCacheInfo: 'Déclenche automatiquement le téléchargement en arrière-plan du meilleur lien si aucun stream n\'est en cache.', timeoutInfo: 'Temps de recherche maximum (défaut : 20s)', excludedInfo: 'Les streams avec ces mots seront filtrés', deduplicateInfo: 'Masque les résultats identiques provenant de différents hébergeurs', diff --git a/wastream/services/stream.py b/wastream/services/stream.py index 0bc1c8e..94e5fcd 100644 --- a/wastream/services/stream.py +++ b/wastream/services/stream.py @@ -475,6 +475,22 @@ class StreamService: stream_logger.debug(f"Excluded {excluded_count} streams") streams = filtered_streams + torbox_config = next((s for s in get_debrid_services(config) if s.get("service") == "torbox"), None) + if torbox_config and torbox_config.get("auto_cache", False): + cached_count = sum(1 for s in streams if "⚡" in s["name"] and "TB" in s["name"]) + + if cached_count == 0 and len(streams) > 0: + best_torbox_stream = next((s for s in streams if "TB" in s["name"]), None) + if best_torbox_stream: + token_data = decode_playback_token(best_torbox_stream["url"].split("/")[-2]) + link_to_download = token_data.get("l") + + if link_to_download: + asyncio.create_task(torbox_service.trigger_auto_download( + link=link_to_download, + api_key=torbox_config.get("api_key") + )) + return streams async def _get_metadata(self, imdb_id: str, tmdb_api_token: str) -> Optional[Dict]: @@ -1286,7 +1302,23 @@ class StreamService: excluded_count = len(streams) - len(filtered_streams) if excluded_count > 0: stream_logger.debug(f"Excluded {excluded_count} streams") - return filtered_streams + streams = filtered_streams + + torbox_config = next((s for s in get_debrid_services(config) if s.get("service") == "torbox"), None) + if torbox_config and torbox_config.get("auto_cache", False): + cached_count = sum(1 for s in streams if "⚡" in s["name"] and "TB" in s["name"]) + + if cached_count == 0 and len(streams) > 0: + best_torbox_stream = next((s for s in streams if "TB" in s["name"]), None) + if best_torbox_stream: + token_data = decode_playback_token(best_torbox_stream["url"].split("/")[-2]) + link_to_download = token_data.get("l") + + if link_to_download: + asyncio.create_task(torbox_service.trigger_auto_download( + link=link_to_download, + api_key=torbox_config.get("api_key") + )) return streams diff --git a/wastream/templates/index.html b/wastream/templates/index.html index 2e53a0b..584a3cc 100644 --- a/wastream/templates/index.html +++ b/wastream/templates/index.html @@ -1768,6 +1768,22 @@ +