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 <cursoragent@cursor.com>
This commit is contained in:
renjue
2026-05-09 17:58:03 +08:00
parent 9368b5d80c
commit 888936d6b8

View File

@@ -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)
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"),