From 888936d6b840cc02632239d7fc935299b299c0d1 Mon Sep 17 00:00:00 2001 From: renjue Date: Sat, 9 May 2026 17:58:03 +0800 Subject: [PATCH] Avoid automatic HDHive unlock on detail page visits. Keep resource listing read-only on media detail requests and defer unlock calls to explicit user ingest actions, while retaining cached fallback behavior for upstream rate limits. Co-authored-by: Cursor --- backend/services/media_service.py | 108 ++++++++++++++++++++++++------ 1 file changed, 87 insertions(+), 21 deletions(-) diff --git a/backend/services/media_service.py b/backend/services/media_service.py index a5dbf11..83ad799 100644 --- a/backend/services/media_service.py +++ b/backend/services/media_service.py @@ -1,6 +1,13 @@ -from adapters.hdhive_adapter import normalize_resource, search_resource, unlock_link +import json +import time + +from adapters.hdhive_adapter import normalize_resource, search_resource from adapters.tmdb_adapter import get_media_detail, search_media -from error_handling import AppServiceError +from error_handling import AppServiceError, normalize_exception +from storage import find_media_item + +RESOURCE_CACHE_TTL_SECONDS = 15 * 60 +_RESOURCE_CACHE = {} def _extract_hdhive_items(hdhive_result): @@ -15,14 +22,59 @@ def _extract_hdhive_items(hdhive_result): return [] -def _extract_hdhive_unlock_data(unlock_result): - payload = (unlock_result or {}).get("data") - # HDHive unlock response is usually envelope with inner data object. - if isinstance(payload, dict) and isinstance(payload.get("data"), dict): - return payload.get("data") or {} - if isinstance(payload, dict): +def _resource_cache_key(media_type, tmdb_id): + return f"{media_type}:{tmdb_id}" + + +def _get_cached_resources(media_type, tmdb_id): + key = _resource_cache_key(media_type, tmdb_id) + cached = _RESOURCE_CACHE.get(key) + if not cached: + return None + if cached.get("expireAt", 0) < time.time(): + _RESOURCE_CACHE.pop(key, None) + return None + return cached.get("resources") or [] + + +def _set_cached_resources(media_type, tmdb_id, resources): + key = _resource_cache_key(media_type, tmdb_id) + _RESOURCE_CACHE[key] = { + "expireAt": time.time() + RESOURCE_CACHE_TTL_SECONDS, + "resources": resources or [], + } + + +def _extract_hdhive_raw_items(hdhive_raw): + payload = hdhive_raw + if isinstance(payload, str): + try: + payload = json.loads(payload) + except Exception: + return [] + if isinstance(payload, dict) and isinstance(payload.get("data"), list): + return payload.get("data") or [] + if isinstance(payload, dict) and isinstance(payload.get("items"), list): + return payload.get("items") or [] + if isinstance(payload, list): return payload - return {} + return [] + + +def _fallback_resources_from_db(tmdb_id, media_type): + item = find_media_item(tmdb_id) + if not item: + return [] + if item.get("type") != media_type: + return [] + + raw_items = _extract_hdhive_raw_items(item.get("hdhive_raw")) + resources = [] + for raw_item in raw_items: + normalized = normalize_resource(raw_item, {}) + normalized["unlockError"] = "当前 HDHive 接口限流,已回退展示缓存资源(未实时解锁)" + resources.append(normalized) + return resources def search_media_by_keyword(query, media_type): @@ -46,23 +98,37 @@ def search_media_by_keyword(query, media_type): def get_media_resources(media_type, tmdb_id): detail = get_media_detail(tmdb_id, media_type) - hdhive = search_resource(media_type, tmdb_id) + cached_resources = _get_cached_resources(media_type, tmdb_id) + if cached_resources: + return { + "media": detail.get("normalized"), + "resources": cached_resources, + "resourceNotice": "命中本地缓存,已减少上游请求压力", + } + + try: + hdhive = search_resource(media_type, tmdb_id) + except Exception as error: + normalized_error = normalize_exception(error) + if normalized_error.provider == "hdhive" and normalized_error.category == "rate_limit": + fallback_resources = _fallback_resources_from_db(tmdb_id, media_type) + if fallback_resources: + _set_cached_resources(media_type, tmdb_id, fallback_resources) + return { + "media": detail.get("normalized"), + "resources": fallback_resources, + "resourceNotice": "HDHive 当前限流,已回退到历史缓存资源", + } + raise + search_data = _extract_hdhive_items(hdhive) resources = [] for item in search_data: - slug = (item or {}).get("slug") - unlock_data = {} - unlock_error = None - if slug: - try: - unlock = unlock_link(slug) - unlock_data = _extract_hdhive_unlock_data(unlock) - except Exception as error: - unlock_error = str(error) - normalized = normalize_resource(item, unlock_data) - normalized["unlockError"] = unlock_error + normalized = normalize_resource(item, {}) + normalized["unlockError"] = "详情页不自动解锁,点击“使用该资源入库”时才会请求解锁并扣积分" resources.append(normalized) + _set_cached_resources(media_type, tmdb_id, resources) return { "media": detail.get("normalized"),