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:
@@ -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 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):
|
def _extract_hdhive_items(hdhive_result):
|
||||||
@@ -15,14 +22,59 @@ def _extract_hdhive_items(hdhive_result):
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
def _extract_hdhive_unlock_data(unlock_result):
|
def _resource_cache_key(media_type, tmdb_id):
|
||||||
payload = (unlock_result or {}).get("data")
|
return f"{media_type}:{tmdb_id}"
|
||||||
# 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 {}
|
def _get_cached_resources(media_type, tmdb_id):
|
||||||
if isinstance(payload, dict):
|
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 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):
|
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):
|
def get_media_resources(media_type, tmdb_id):
|
||||||
detail = get_media_detail(tmdb_id, media_type)
|
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)
|
search_data = _extract_hdhive_items(hdhive)
|
||||||
|
|
||||||
resources = []
|
resources = []
|
||||||
for item in search_data:
|
for item in search_data:
|
||||||
slug = (item or {}).get("slug")
|
normalized = normalize_resource(item, {})
|
||||||
unlock_data = {}
|
normalized["unlockError"] = "详情页不自动解锁,点击“使用该资源入库”时才会请求解锁并扣积分"
|
||||||
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
|
|
||||||
resources.append(normalized)
|
resources.append(normalized)
|
||||||
|
_set_cached_resources(media_type, tmdb_id, resources)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"media": detail.get("normalized"),
|
"media": detail.get("normalized"),
|
||||||
|
|||||||
Reference in New Issue
Block a user