chore: remove unused imports and fix function name conflicts (#5764)

- Remove unused imports in anthropic.py, tmdbv3api/__init__.py, tv.py, test files
- Rename conflicting function names in subscribe.py and webhook.py
- Clean up unused re-exports in tmdbv3api/__init__.py (15 unused exports)
- Apply consistent formatting across API endpoints
This commit is contained in:
DDSRem
2026-05-13 18:59:03 +08:00
committed by GitHub
parent fcf6e14ac9
commit 5a585839ba
34 changed files with 2974 additions and 1838 deletions
+294 -160
View File
@@ -47,17 +47,15 @@ def _merge_append_event(pending_event: Optional[dict], event: dict) -> dict:
return merged_event
merged_event = dict(pending_event)
merged_event.update({
key: value
for key, value in event.items()
if key != "items"
})
merged_event.update({key: value for key, value in event.items() if key != "items"})
merged_event["type"] = "append"
merged_event["items"] = [*(pending_event.get("items") or []), *items]
return merged_event
async def _iter_batched_search_events(event_source: AsyncIterator[dict]) -> AsyncIterator[dict]:
async def _iter_batched_search_events(
event_source: AsyncIterator[dict],
) -> AsyncIterator[dict]:
"""
对搜索流事件做轻量批处理,避免站点结果集中返回时产生过密 SSE。
"""
@@ -90,7 +88,10 @@ async def _iter_batched_search_events(event_source: AsyncIterator[dict]) -> Asyn
if event.get("type") == "append":
pending_append_event = _merge_append_event(pending_append_event, event)
if len(pending_append_event.get("items") or []) >= _SSE_APPEND_MAX_ITEMS:
if (
len(pending_append_event.get("items") or [])
>= _SSE_APPEND_MAX_ITEMS
):
yield pending_append_event
pending_append_event = None
continue
@@ -121,20 +122,17 @@ async def _stream_search_events(request: Request, event_source: AsyncIterator[di
# 精确搜索会先发送 replace,再发送 done。done 再带整包 items 只会重复占用带宽和前端内存。
if event.get("type") == "replace" and event.get("items"):
has_sent_final_replace = True
elif event.get("type") == "done" and has_sent_final_replace and event.get("stage") == "done" and event.get("items"):
event = {
key: value
for key, value in event.items()
if key != "items"
}
elif (
event.get("type") == "done"
and has_sent_final_replace
and event.get("stage") == "done"
and event.get("items")
):
event = {key: value for key, value in event.items() if key != "items"}
yield _sse_event(event)
except Exception as err:
logger.error(f"渐进式搜索出错:{err}", exc_info=True)
yield _sse_event({
"type": "error",
"success": False,
"message": str(err)
})
yield _sse_event({"type": "error", "success": False, "message": str(err)})
@router.get("/last", summary="查询搜索结果", response_model=List[schemas.Context])
@@ -147,15 +145,17 @@ async def search_latest(_: schemas.TokenPayload = Depends(verify_token)) -> Any:
@router.get("/media/{mediaid}/stream", summary="渐进式精确搜索资源")
async def search_by_id_stream(request: Request,
mediaid: str,
mtype: Optional[str] = None,
area: Optional[str] = "title",
title: Optional[str] = None,
year: Optional[str] = None,
season: Optional[str] = None,
sites: Optional[str] = None,
_: schemas.TokenPayload = Depends(verify_resource_token)) -> Any:
async def search_by_id_stream(
request: Request,
mediaid: str,
mtype: Optional[str] = None,
area: Optional[str] = "title",
title: Optional[str] = None,
year: Optional[str] = None,
season: Optional[str] = None,
sites: Optional[str] = None,
_: schemas.TokenPayload = Depends(verify_resource_token),
) -> Any:
"""
根据TMDBID/豆瓣ID渐进式搜索站点资源,返回格式为SSE
"""
@@ -172,77 +172,138 @@ async def search_by_id_stream(request: Request,
if mediaid.startswith("tmdb:"):
tmdbid = int(mediaid.replace("tmdb:", ""))
if settings.RECOGNIZE_SOURCE == "douban":
doubaninfo = await media_chain.async_get_doubaninfo_by_tmdbid(tmdbid=tmdbid, mtype=media_type)
doubaninfo = await media_chain.async_get_doubaninfo_by_tmdbid(
tmdbid=tmdbid, mtype=media_type
)
if doubaninfo:
torrents = search_chain.async_search_by_id_stream(doubanid=doubaninfo.get("id"),
mtype=media_type, area=area,
season=media_season, sites=site_list,
cache_local=True)
torrents = search_chain.async_search_by_id_stream(
doubanid=doubaninfo.get("id"),
mtype=media_type,
area=area,
season=media_season,
sites=site_list,
cache_local=True,
)
else:
yield {"type": "error", "success": False, "message": "未识别到豆瓣媒体信息"}
yield {
"type": "error",
"success": False,
"message": "未识别到豆瓣媒体信息",
}
return
else:
torrents = search_chain.async_search_by_id_stream(tmdbid=tmdbid, mtype=media_type, area=area,
season=media_season, sites=site_list,
cache_local=True)
torrents = search_chain.async_search_by_id_stream(
tmdbid=tmdbid,
mtype=media_type,
area=area,
season=media_season,
sites=site_list,
cache_local=True,
)
elif mediaid.startswith("douban:"):
doubanid = mediaid.replace("douban:", "")
if settings.RECOGNIZE_SOURCE == "themoviedb":
tmdbinfo = await media_chain.async_get_tmdbinfo_by_doubanid(doubanid=doubanid, mtype=media_type)
tmdbinfo = await media_chain.async_get_tmdbinfo_by_doubanid(
doubanid=doubanid, mtype=media_type
)
if tmdbinfo:
if tmdbinfo.get('season') and not media_season:
media_season = tmdbinfo.get('season')
torrents = search_chain.async_search_by_id_stream(tmdbid=tmdbinfo.get("id"),
mtype=media_type, area=area,
season=media_season, sites=site_list,
cache_local=True)
if tmdbinfo.get("season") and not media_season:
media_season = tmdbinfo.get("season")
torrents = search_chain.async_search_by_id_stream(
tmdbid=tmdbinfo.get("id"),
mtype=media_type,
area=area,
season=media_season,
sites=site_list,
cache_local=True,
)
else:
yield {"type": "error", "success": False, "message": "未识别到TMDB媒体信息"}
yield {
"type": "error",
"success": False,
"message": "未识别到TMDB媒体信息",
}
return
else:
torrents = search_chain.async_search_by_id_stream(doubanid=doubanid, mtype=media_type, area=area,
season=media_season, sites=site_list,
cache_local=True)
torrents = search_chain.async_search_by_id_stream(
doubanid=doubanid,
mtype=media_type,
area=area,
season=media_season,
sites=site_list,
cache_local=True,
)
elif mediaid.startswith("bangumi:"):
bangumiid = int(mediaid.replace("bangumi:", ""))
if settings.RECOGNIZE_SOURCE == "themoviedb":
tmdbinfo = await media_chain.async_get_tmdbinfo_by_bangumiid(bangumiid=bangumiid)
tmdbinfo = await media_chain.async_get_tmdbinfo_by_bangumiid(
bangumiid=bangumiid
)
if tmdbinfo:
torrents = search_chain.async_search_by_id_stream(tmdbid=tmdbinfo.get("id"),
mtype=media_type, area=area,
season=media_season, sites=site_list,
cache_local=True)
torrents = search_chain.async_search_by_id_stream(
tmdbid=tmdbinfo.get("id"),
mtype=media_type,
area=area,
season=media_season,
sites=site_list,
cache_local=True,
)
else:
yield {"type": "error", "success": False, "message": "未识别到TMDB媒体信息"}
yield {
"type": "error",
"success": False,
"message": "未识别到TMDB媒体信息",
}
return
else:
doubaninfo = await media_chain.async_get_doubaninfo_by_bangumiid(bangumiid=bangumiid)
doubaninfo = await media_chain.async_get_doubaninfo_by_bangumiid(
bangumiid=bangumiid
)
if doubaninfo:
torrents = search_chain.async_search_by_id_stream(doubanid=doubaninfo.get("id"),
mtype=media_type, area=area,
season=media_season, sites=site_list,
cache_local=True)
torrents = search_chain.async_search_by_id_stream(
doubanid=doubaninfo.get("id"),
mtype=media_type,
area=area,
season=media_season,
sites=site_list,
cache_local=True,
)
else:
yield {"type": "error", "success": False, "message": "未识别到豆瓣媒体信息"}
yield {
"type": "error",
"success": False,
"message": "未识别到豆瓣媒体信息",
}
return
else:
event_data = MediaRecognizeConvertEventData(
mediaid=mediaid,
convert_type=settings.RECOGNIZE_SOURCE
mediaid=mediaid, convert_type=settings.RECOGNIZE_SOURCE
)
event = await eventmanager.async_send_event(
ChainEventType.MediaRecognizeConvert, event_data
)
event = await eventmanager.async_send_event(ChainEventType.MediaRecognizeConvert, event_data)
if event and event.event_data:
event_data = event.event_data
if event_data.media_dict:
search_id = event_data.media_dict.get("id")
if event_data.convert_type == "themoviedb":
torrents = search_chain.async_search_by_id_stream(tmdbid=search_id, mtype=media_type,
area=area, season=media_season,
sites=site_list, cache_local=True)
torrents = search_chain.async_search_by_id_stream(
tmdbid=search_id,
mtype=media_type,
area=area,
season=media_season,
sites=site_list,
cache_local=True,
)
elif event_data.convert_type == "douban":
torrents = search_chain.async_search_by_id_stream(doubanid=search_id, mtype=media_type,
area=area, season=media_season,
sites=site_list, cache_local=True)
torrents = search_chain.async_search_by_id_stream(
doubanid=search_id,
mtype=media_type,
area=area,
season=media_season,
sites=site_list,
cache_local=True,
)
else:
if not title:
yield {"type": "error", "success": False, "message": "未知的媒体ID"}
@@ -261,15 +322,23 @@ async def search_by_id_stream(request: Request,
)
if mediainfo:
if settings.RECOGNIZE_SOURCE == "themoviedb":
torrents = search_chain.async_search_by_id_stream(tmdbid=mediainfo.tmdb_id,
mtype=media_type, area=area,
season=media_season, sites=site_list,
cache_local=True)
torrents = search_chain.async_search_by_id_stream(
tmdbid=mediainfo.tmdb_id,
mtype=media_type,
area=area,
season=media_season,
sites=site_list,
cache_local=True,
)
else:
torrents = search_chain.async_search_by_id_stream(doubanid=mediainfo.douban_id,
mtype=media_type, area=area,
season=media_season, sites=site_list,
cache_local=True)
torrents = search_chain.async_search_by_id_stream(
doubanid=mediainfo.douban_id,
mtype=media_type,
area=area,
season=media_season,
sites=site_list,
cache_local=True,
)
if not torrents:
yield {"type": "error", "success": False, "message": "未搜索到任何资源"}
@@ -278,18 +347,22 @@ async def search_by_id_stream(request: Request,
async for event in torrents:
yield event
return StreamingResponse(_stream_search_events(request, event_source()), media_type="text/event-stream")
return StreamingResponse(
_stream_search_events(request, event_source()), media_type="text/event-stream"
)
@router.get("/media/{mediaid}", summary="精确搜索资源", response_model=schemas.Response)
async def search_by_id(mediaid: str,
mtype: Optional[str] = None,
area: Optional[str] = "title",
title: Optional[str] = None,
year: Optional[str] = None,
season: Optional[str] = None,
sites: Optional[str] = None,
_: schemas.TokenPayload = Depends(verify_token)) -> Any:
async def search_by_id(
mediaid: str,
mtype: Optional[str] = None,
area: Optional[str] = "title",
title: Optional[str] = None,
year: Optional[str] = None,
season: Optional[str] = None,
sites: Optional[str] = None,
_: schemas.TokenPayload = Depends(verify_token),
) -> Any:
"""
根据TMDBID/豆瓣ID精确搜索站点资源 tmdb:/douban:/bangumi:
"""
@@ -313,72 +386,121 @@ async def search_by_id(mediaid: str,
tmdbid = int(mediaid.replace("tmdb:", ""))
if settings.RECOGNIZE_SOURCE == "douban":
# 通过TMDBID识别豆瓣ID
doubaninfo = await media_chain.async_get_doubaninfo_by_tmdbid(tmdbid=tmdbid, mtype=media_type)
doubaninfo = await media_chain.async_get_doubaninfo_by_tmdbid(
tmdbid=tmdbid, mtype=media_type
)
if doubaninfo:
torrents = await search_chain.async_search_by_id(doubanid=doubaninfo.get("id"),
mtype=media_type, area=area, season=media_season,
sites=site_list, cache_local=True)
torrents = await search_chain.async_search_by_id(
doubanid=doubaninfo.get("id"),
mtype=media_type,
area=area,
season=media_season,
sites=site_list,
cache_local=True,
)
else:
return schemas.Response(success=False, message="未识别到豆瓣媒体信息")
else:
torrents = await search_chain.async_search_by_id(tmdbid=tmdbid, mtype=media_type, area=area,
season=media_season,
sites=site_list, cache_local=True)
torrents = await search_chain.async_search_by_id(
tmdbid=tmdbid,
mtype=media_type,
area=area,
season=media_season,
sites=site_list,
cache_local=True,
)
elif mediaid.startswith("douban:"):
doubanid = mediaid.replace("douban:", "")
if settings.RECOGNIZE_SOURCE == "themoviedb":
# 通过豆瓣ID识别TMDBID
tmdbinfo = await media_chain.async_get_tmdbinfo_by_doubanid(doubanid=doubanid, mtype=media_type)
tmdbinfo = await media_chain.async_get_tmdbinfo_by_doubanid(
doubanid=doubanid, mtype=media_type
)
if tmdbinfo:
if tmdbinfo.get('season') and not media_season:
media_season = tmdbinfo.get('season')
torrents = await search_chain.async_search_by_id(tmdbid=tmdbinfo.get("id"),
mtype=media_type, area=area, season=media_season,
sites=site_list, cache_local=True)
if tmdbinfo.get("season") and not media_season:
media_season = tmdbinfo.get("season")
torrents = await search_chain.async_search_by_id(
tmdbid=tmdbinfo.get("id"),
mtype=media_type,
area=area,
season=media_season,
sites=site_list,
cache_local=True,
)
else:
return schemas.Response(success=False, message="未识别到TMDB媒体信息")
else:
torrents = await search_chain.async_search_by_id(doubanid=doubanid, mtype=media_type, area=area,
season=media_season,
sites=site_list, cache_local=True)
torrents = await search_chain.async_search_by_id(
doubanid=doubanid,
mtype=media_type,
area=area,
season=media_season,
sites=site_list,
cache_local=True,
)
elif mediaid.startswith("bangumi:"):
bangumiid = int(mediaid.replace("bangumi:", ""))
if settings.RECOGNIZE_SOURCE == "themoviedb":
# 通过BangumiID识别TMDBID
tmdbinfo = await media_chain.async_get_tmdbinfo_by_bangumiid(bangumiid=bangumiid)
tmdbinfo = await media_chain.async_get_tmdbinfo_by_bangumiid(
bangumiid=bangumiid
)
if tmdbinfo:
torrents = await search_chain.async_search_by_id(tmdbid=tmdbinfo.get("id"),
mtype=media_type, area=area, season=media_season,
sites=site_list, cache_local=True)
torrents = await search_chain.async_search_by_id(
tmdbid=tmdbinfo.get("id"),
mtype=media_type,
area=area,
season=media_season,
sites=site_list,
cache_local=True,
)
else:
return schemas.Response(success=False, message="未识别到TMDB媒体信息")
else:
# 通过BangumiID识别豆瓣ID
doubaninfo = await media_chain.async_get_doubaninfo_by_bangumiid(bangumiid=bangumiid)
doubaninfo = await media_chain.async_get_doubaninfo_by_bangumiid(
bangumiid=bangumiid
)
if doubaninfo:
torrents = await search_chain.async_search_by_id(doubanid=doubaninfo.get("id"),
mtype=media_type, area=area, season=media_season,
sites=site_list, cache_local=True)
torrents = await search_chain.async_search_by_id(
doubanid=doubaninfo.get("id"),
mtype=media_type,
area=area,
season=media_season,
sites=site_list,
cache_local=True,
)
else:
return schemas.Response(success=False, message="未识别到豆瓣媒体信息")
else:
# 未知前缀,广播事件解析媒体信息
event_data = MediaRecognizeConvertEventData(
mediaid=mediaid,
convert_type=settings.RECOGNIZE_SOURCE
mediaid=mediaid, convert_type=settings.RECOGNIZE_SOURCE
)
event = await eventmanager.async_send_event(
ChainEventType.MediaRecognizeConvert, event_data
)
event = await eventmanager.async_send_event(ChainEventType.MediaRecognizeConvert, event_data)
# 使用事件返回的上下文数据
if event and event.event_data:
event_data: MediaRecognizeConvertEventData = event.event_data
if event_data.media_dict:
search_id = event_data.media_dict.get("id")
if event_data.convert_type == "themoviedb":
torrents = await search_chain.async_search_by_id(tmdbid=search_id, mtype=media_type, area=area,
season=media_season, cache_local=True)
torrents = await search_chain.async_search_by_id(
tmdbid=search_id,
mtype=media_type,
area=area,
season=media_season,
cache_local=True,
)
elif event_data.convert_type == "douban":
torrents = await search_chain.async_search_by_id(doubanid=search_id, mtype=media_type, area=area,
season=media_season, cache_local=True)
torrents = await search_chain.async_search_by_id(
doubanid=search_id,
mtype=media_type,
area=area,
season=media_season,
cache_local=True,
)
else:
if not title:
return schemas.Response(success=False, message="未知的媒体ID")
@@ -397,73 +519,89 @@ async def search_by_id(mediaid: str,
)
if mediainfo:
if settings.RECOGNIZE_SOURCE == "themoviedb":
torrents = await search_chain.async_search_by_id(tmdbid=mediainfo.tmdb_id, mtype=media_type,
area=area,
season=media_season, cache_local=True)
torrents = await search_chain.async_search_by_id(
tmdbid=mediainfo.tmdb_id,
mtype=media_type,
area=area,
season=media_season,
cache_local=True,
)
else:
torrents = await search_chain.async_search_by_id(doubanid=mediainfo.douban_id, mtype=media_type,
area=area,
season=media_season, cache_local=True)
torrents = await search_chain.async_search_by_id(
doubanid=mediainfo.douban_id,
mtype=media_type,
area=area,
season=media_season,
cache_local=True,
)
# 返回搜索结果
if not torrents:
return schemas.Response(success=False, message="未搜索到任何资源")
else:
return schemas.Response(success=True, data=[torrent.to_dict() for torrent in torrents])
return schemas.Response(
success=True, data=[torrent.to_dict() for torrent in torrents]
)
@router.get("/title/stream", summary="渐进式模糊搜索资源")
async def search_by_title_stream(request: Request,
keyword: Optional[str] = None,
page: Optional[int] = 0,
sites: Optional[str] = None,
_: schemas.TokenPayload = Depends(verify_resource_token)) -> Any:
async def search_by_title_stream(
request: Request,
keyword: Optional[str] = None,
page: Optional[int] = 0,
sites: Optional[str] = None,
_: schemas.TokenPayload = Depends(verify_resource_token),
) -> Any:
"""
根据名称渐进式模糊搜索站点资源,返回格式为SSE
"""
event_source = SearchChain().async_search_by_title_stream(
title=keyword,
page=page,
sites=_parse_site_list(sites),
cache_local=True
title=keyword, page=page, sites=_parse_site_list(sites), cache_local=True
)
return StreamingResponse(
_stream_search_events(request, event_source), media_type="text/event-stream"
)
return StreamingResponse(_stream_search_events(request, event_source), media_type="text/event-stream")
@router.get("/title", summary="模糊搜索资源", response_model=schemas.Response)
async def search_by_title(keyword: Optional[str] = None,
page: Optional[int] = 0,
sites: Optional[str] = None,
_: schemas.TokenPayload = Depends(verify_token)) -> Any:
async def search_by_title(
keyword: Optional[str] = None,
page: Optional[int] = 0,
sites: Optional[str] = None,
_: schemas.TokenPayload = Depends(verify_token),
) -> Any:
"""
根据名称模糊搜索站点资源,支持分页,关键词为空是返回首页资源
"""
torrents = await SearchChain().async_search_by_title(
title=keyword, page=page,
sites=_parse_site_list(sites),
cache_local=True
title=keyword, page=page, sites=_parse_site_list(sites), cache_local=True
)
if not torrents:
return schemas.Response(success=False, message="未搜索到任何资源")
return schemas.Response(success=True, data=[torrent.to_dict() for torrent in torrents])
return schemas.Response(
success=True, data=[torrent.to_dict() for torrent in torrents]
)
@router.post("/recommend", summary="AI推荐资源", response_model=schemas.Response)
async def recommend_search_results(
filtered_indices: Optional[List[int]] = Body(None, embed=True, description="筛选后的索引列表"),
check_only: bool = Body(False, embed=True, description="仅检查状态,不启动新任务"),
force: bool = Body(False, embed=True, description="强制重新推荐,清除旧结果"),
_: schemas.TokenPayload = Depends(verify_token)) -> Any:
filtered_indices: Optional[List[int]] = Body(
None, embed=True, description="筛选后的索引列表"
),
check_only: bool = Body(False, embed=True, description="仅检查状态,不启动新任务"),
force: bool = Body(False, embed=True, description="强制重新推荐,清除旧结果"),
_: schemas.TokenPayload = Depends(verify_token),
) -> Any:
"""
AI推荐资源 - 轮询接口
前端轮询此接口,发送筛选后的索引(如果有筛选)
后端根据请求变化自动取消旧任务并启动新任务
参数:
- filtered_indices: 筛选后的索引列表(可选,为空或不提供时使用所有结果)
- check_only: 仅检查状态(首次打开页面时使用,避免触发不必要的重新推理)
- force: 强制重新推荐(清除旧结果并重新启动)
返回数据结构:
{
"success": bool,
@@ -477,9 +615,9 @@ async def recommend_search_results(
# 从缓存获取上次搜索结果
results = await SearchChain().async_last_search_results() or []
if not results:
return schemas.Response(success=False, message="没有可用的搜索结果", data={
"status": "error"
})
return schemas.Response(
success=False, message="没有可用的搜索结果", data={"status": "error"}
)
recommend_chain = SearchChain()
@@ -487,16 +625,12 @@ async def recommend_search_results(
if force:
# 检查功能是否启用
if not recommend_chain.is_ai_recommend_enabled:
return schemas.Response(success=True, data={
"status": "disabled"
})
return schemas.Response(success=True, data={"status": "disabled"})
logger.info("收到新推荐请求,清除旧结果并启动新任务")
recommend_chain.cancel_ai_recommend()
recommend_chain.start_recommend_task(filtered_indices, len(results), results)
# 直接返回运行中状态
return schemas.Response(success=True, data={
"status": "running"
})
return schemas.Response(success=True, data={"status": "running"})
# 如果是仅检查模式,不传递 filtered_indices(避免触发请求变化检测)
if check_only:
@@ -505,7 +639,9 @@ async def recommend_search_results(
# 如果有错误,将错误信息放到message中
if current_status.get("status") == "error":
error_msg = current_status.pop("error", "未知错误")
return schemas.Response(success=False, message=error_msg, data=current_status)
return schemas.Response(
success=False, message=error_msg, data=current_status
)
return schemas.Response(success=True, data=current_status)
# 获取当前状态(会检测请求是否变化)
@@ -519,9 +655,7 @@ async def recommend_search_results(
if status_data["status"] == "idle":
recommend_chain.start_recommend_task(filtered_indices, len(results), results)
# 立即返回运行中状态
return schemas.Response(success=True, data={
"status": "running"
})
return schemas.Response(success=True, data={"status": "running"})
# 如果有错误,将错误信息放到message中
if status_data.get("status") == "error":