11# clients/bangumi_client.py
22# 该模块用于与 Bangumi API 交互,获取游戏和角色信息
33import asyncio
4+ import logging
45from rapidfuzz import fuzz
56
67import json
1819from core .interaction import InteractionProvider
1920from core .mapping_manager import BangumiMappingManager
2021from core .schema_manager import NotionSchemaManager
21- from utils import logger
2222
2323API_TOKEN = BANGUMI_TOKEN
2424HEADERS_API = {
@@ -83,11 +83,11 @@ async def _search(self, keyword: str):
8383 try :
8484 resp = await self .client .post (url , headers = self .headers , json = payload , timeout = 15 )
8585 if resp .status_code != 200 :
86- logger . warn (f"[Bangumi] API搜索失败: { resp .status_code } " )
86+ logging . warning (f"⚠️ [Bangumi] API搜索失败: { resp .status_code } " )
8787 return []
8888 return resp .json ().get ("data" , [])
8989 except httpx .RequestError as e :
90- logger .error (f"[Bangumi] API请求异常: { e } " )
90+ logging .error (f"❌ [Bangumi] API请求异常: { e } " )
9191 return []
9292
9393 async def search_and_select_bangumi_id (self , keyword : str ) -> str | None :
@@ -119,23 +119,23 @@ async def search_and_select_bangumi_id(self, keyword: str) -> str | None:
119119 if clean_title (item .get ("name" , "" )) and (
120120 clean_title (keyword ) in clean_title (item .get ("name" , "" ))
121121 ):
122- logger .info (f"[Bangumi] 子串匹配成功: { item ['name' ]} ,视为同一作品" )
122+ logging .info (f"🔍 [Bangumi] 子串匹配成功: { item ['name' ]} ,视为同一作品" )
123123 return str (item ["id" ])
124124 if candidates and candidates [0 ][0 ] >= self .similarity_threshold :
125125 best = candidates [0 ][1 ]
126- logger .info (f"[Bangumi] 自动匹配成功: { best ['name' ]} (相似度 { candidates [0 ][0 ]:.2f} )" )
126+ logging .info (f"🔍 [Bangumi] 自动匹配成功: { best ['name' ]} (相似度 { candidates [0 ][0 ]:.2f} )" )
127127 return str (best ["id" ])
128128 if candidates and candidates [0 ][0 ] >= 0.7 :
129129 best = candidates [0 ][1 ]
130130 if clean_title (best ["name" ]) in clean_title (keyword ) or clean_title (
131131 keyword
132132 ) in clean_title (best ["name" ]):
133- logger .info (
134- f"[Bangumi] 模糊匹配成功(放宽判定): { best ['name' ]} (相似度 { candidates [0 ][0 ]:.2f} )"
133+ logging .info (
134+ f"🔍 [Bangumi] 模糊匹配成功(放宽判定): { best ['name' ]} (相似度 { candidates [0 ][0 ]:.2f} )"
135135 )
136136 return str (best ["id" ])
137137
138- logger . warn ( " Bangumi自动匹配相似度不足,请手动选择:" )
138+ logging . warning ( "⚠️ Bangumi自动匹配相似度不足,请手动选择:" )
139139
140140 # Format candidates for display in GUI
141141 gui_candidates = []
@@ -335,7 +335,7 @@ async def create_or_update_character(self, char: dict, warned_keys: Set[str]) ->
335335 prop_type = self .schema .get_property_type (CHARACTER_DB_ID , notion_prop_name )
336336 if not prop_type :
337337 if notion_prop_name not in warned_keys :
338- logger . warn (f"角色属性 '{ notion_prop_name } ' 在 Notion 角色库中不存在,已跳过。" )
338+ logging . warning (f"⚠️ 角色属性 '{ notion_prop_name } ' 在 Notion 角色库中不存在,已跳过。" )
339339 warned_keys .add (notion_prop_name )
340340 continue
341341 if prop_type == "title" :
@@ -361,19 +361,19 @@ async def create_or_update_character(self, char: dict, warned_keys: Set[str]) ->
361361 "PATCH" , f"https://api.notion.com/v1/pages/{ existing_id } " , {"properties" : props }
362362 )
363363 if resp :
364- logger .info (f"角色已存在,已更新:{ char ['name' ]} " )
364+ logging .info (f"🔍 角色已存在,已更新:{ char ['name' ]} " )
365365 return existing_id if resp else None
366366 else :
367367 payload = {"parent" : {"database_id" : CHARACTER_DB_ID }, "properties" : props }
368368 resp = await self .notion ._request ("POST" , "https://api.notion.com/v1/pages" , payload )
369369 if resp :
370- logger . success (f"新角色已创建:{ char ['name' ]} " )
370+ logging . info (f"✅ 新角色已创建:{ char ['name' ]} " )
371371 return resp .get ("id" ) if resp else None
372372
373373 async def create_or_link_characters (self , game_page_id : str , subject_id : str ):
374374 characters = await self .fetch_characters (subject_id )
375375 if not characters :
376- logger .info ("未找到任何 Bangumi 角色信息,跳过角色关联。" )
376+ logging .info ("🔍 未找到任何 Bangumi 角色信息,跳过角色关联。" )
377377 patch = {
378378 "properties" : {
379379 FIELDS ["bangumi_url" ]: {"url" : f"https://bangumi.tv/subject/{ subject_id } " }
@@ -391,7 +391,7 @@ async def create_or_link_characters(self, game_page_id: str, subject_id: str):
391391 character_relations = [{"id" : cid } for cid in char_ids if cid ]
392392 page_data = await self .notion .get_page (game_page_id )
393393 if not page_data :
394- logger .error (f"无法获取游戏页面 { game_page_id } 的当前状态,跳过声优补充。" )
394+ logging .error (f"❌ 无法获取游戏页面 { game_page_id } 的当前状态,跳过声优补充。" )
395395 return
396396 patch_props = {
397397 FIELDS ["bangumi_url" ]: {"url" : f"https://bangumi.tv/subject/{ subject_id } " },
@@ -401,32 +401,32 @@ async def create_or_link_characters(self, game_page_id: str, subject_id: str):
401401 page_data .get ("properties" , {}).get (FIELDS ["voice_actor" ], {}).get ("multi_select" , [])
402402 )
403403 if not existing_vcs :
404- logger .info ("游戏页面声优信息为空,尝试从 Bangumi 角色数据中补充..." )
404+ logging .info ("🔍 游戏页面声优信息为空,尝试从 Bangumi 角色数据中补充..." )
405405 all_cvs = {ch ["声优" ].strip () for ch in characters if ch .get ("声优" )}
406406 if all_cvs :
407- logger . success (f"已为【游戏页面】补充 { len (all_cvs )} 位声优。" )
407+ logging . info (f"✅ 已为【游戏页面】补充 { len (all_cvs )} 位声优。" )
408408 patch_props [FIELDS ["voice_actor" ]] = {
409409 "multi_select" : [{"name" : name } for name in sorted (all_cvs )]
410410 }
411411 else :
412- logger .info ("Bangumi 角色数据中也未找到声优信息以供补充。" )
412+ logging .info ("🔍 Bangumi 角色数据中也未找到声优信息以供补充。" )
413413 else :
414- logger .info ("游戏页面已存在声优信息,跳过补充。" )
414+ logging .info ("🔍 游戏页面已存在声优信息,跳过补充。" )
415415 await self .notion ._request (
416416 "PATCH" , f"https://api.notion.com/v1/pages/{ game_page_id } " , {"properties" : patch_props }
417417 )
418- logger . success ( " Bangumi 角色信息同步与关联完成。" )
418+ logging . info ( "✅ Bangumi 角色信息同步与关联完成。" )
419419
420420 async def fetch_brand_info_from_bangumi (self , brand_name : str ) -> dict | None :
421421 """[已重构] 搜索品牌,找到ID后调用 fetch_person_by_id 获取完整信息。"""
422422
423423 async def search_brand (keyword : str ):
424- logger .info (f"[Bangumi] 正在搜索品牌关键词: { keyword } " )
424+ logging .info (f"🔍 [Bangumi] 正在搜索品牌关键词: { keyword } " )
425425 url = "https://api.bgm.tv/v0/search/persons"
426426 data = {"keyword" : keyword , "filter" : {"career" : ["artist" , "director" , "producer" ]}}
427427 resp = await self .client .post (url , headers = self .headers , json = data )
428428 if resp .status_code != 200 :
429- logger .error (f"[Bangumi] 品牌搜索失败,状态码: { resp .status_code } " )
429+ logging .error (f"❌ [Bangumi] 品牌搜索失败,状态码: { resp .status_code } " )
430430 return []
431431 return resp .json ().get ("data" , [])
432432
@@ -458,28 +458,28 @@ async def search_brand(keyword: str):
458458 best_score , best_match = candidates [0 ] if candidates else (0 , None )
459459
460460 if not best_match or best_score < 0.7 :
461- logger . warn (f"未找到相似度高于阈值的品牌(最高: { best_score :.2f} )" )
461+ logging . warning (f"⚠️ 未找到相似度高于阈值的品牌(最高: { best_score :.2f} )" )
462462 return None
463463
464464 person_id = best_match .get ("id" )
465465 if not person_id :
466- logger . warn ( " 最佳匹配项缺少ID,无法获取详细信息。" )
466+ logging . warning ( "⚠️ 最佳匹配项缺少ID,无法获取详细信息。" )
467467 return None
468468
469- logger . success (
470- f"[Bangumi] 搜索匹配成功: { best_match .get ('name' )} (ID: { person_id } , 相似度: { best_score :.2f} )"
469+ logging . info (
470+ f"✅ [Bangumi] 搜索匹配成功: { best_match .get ('name' )} (ID: { person_id } , 相似度: { best_score :.2f} )"
471471 )
472472 return await self .fetch_person_by_id (str (person_id ))
473473
474474 async def fetch_person_by_id (self , person_id : str ) -> dict | None :
475475 """[已重构] 通过 Person ID 直接获取并处理厂商/个人信息,作为唯一的数据处理源。"""
476476 url = f"https://api.bgm.tv/v0/persons/{ person_id } "
477- logger .info (f"[Bangumi] 正在通过 ID 直接获取品牌信息: { person_id } " )
477+ logging .info (f"🔍 [Bangumi] 正在通过 ID 直接获取品牌信息: { person_id } " )
478478 try :
479479 resp = await self .client .get (url , headers = self .headers )
480480 if resp .status_code != 200 :
481- logger .error (
482- f"[Bangumi] 品牌信息获取失败,ID: { person_id } , 状态码: { resp .status_code } "
481+ logging .error (
482+ f"❌ [Bangumi] 品牌信息获取失败,ID: { person_id } , 状态码: { resp .status_code } "
483483 )
484484 return None
485485
@@ -499,11 +499,11 @@ async def fetch_person_by_id(self, person_id: str) -> dict | None:
499499 }
500500 brand_info .update (infobox_data )
501501
502- logger . success (f"[Bangumi] 已成功获取并处理品牌: { person_data .get ('name' )} " )
502+ logging . info (f"✅ [Bangumi] 已成功获取并处理品牌: { person_data .get ('name' )} " )
503503 return brand_info
504504
505505 except Exception as e :
506- logger .error (f"[Bangumi] 通过ID获取品牌信息时发生异常: { e } " )
506+ logging .error (f"❌ [Bangumi] 通过ID获取品牌信息时发生异常: { e } " )
507507 return None
508508
509509 async def fetch_and_prepare_character_data (self , character_id : str ) -> dict | None :
@@ -512,7 +512,7 @@ async def fetch_and_prepare_character_data(self, character_id: str) -> dict | No
512512 char_detail_url = f"https://api.bgm.tv/v0/characters/{ character_id } "
513513 resp = await self .client .get (char_detail_url , headers = self .headers )
514514 if resp .status_code != 200 :
515- logger .error (f"获取角色 { character_id } 详情失败: 状态码 { resp .status_code } " )
515+ logging .error (f"❌ 获取角色 { character_id } 详情失败: 状态码 { resp .status_code } " )
516516 return None
517517
518518 detail = resp .json ()
@@ -536,5 +536,5 @@ async def fetch_and_prepare_character_data(self, character_id: str) -> dict | No
536536
537537 return char_data_to_update
538538 except Exception as e :
539- logger .error (f"处理角色 { character_id } 数据时出错: { e } " )
539+ logging .error (f"❌ 处理角色 { character_id } 数据时出错: { e } " )
540540 return None
0 commit comments