@@ -415,6 +415,58 @@ def _cleanup_plugin_state(self, dir_name: str) -> None:
415415 llm_tools .func_list .remove (tool )
416416 logger .info (f"清理工具: { tool .name } " )
417417
418+ def _build_failed_plugin_record (
419+ self ,
420+ * ,
421+ root_dir_name : str ,
422+ plugin_dir_path : str ,
423+ reserved : bool ,
424+ error : Exception | str ,
425+ error_trace : str ,
426+ ) -> dict :
427+ record : dict = {
428+ "name" : root_dir_name ,
429+ "error" : str (error ),
430+ "traceback" : error_trace ,
431+ "reserved" : reserved ,
432+ }
433+ try :
434+ metadata = self ._load_plugin_metadata (plugin_path = plugin_dir_path )
435+ if metadata :
436+ record .update (
437+ {
438+ "name" : metadata .name ,
439+ "author" : metadata .author ,
440+ "desc" : metadata .desc ,
441+ "version" : metadata .version ,
442+ "repo" : metadata .repo ,
443+ "display_name" : metadata .display_name ,
444+ "support_platforms" : metadata .support_platforms ,
445+ "astrbot_version" : metadata .astrbot_version ,
446+ }
447+ )
448+ except Exception as metadata_error :
449+ logger .debug (
450+ f"读取失败插件 { root_dir_name } 元数据失败: { metadata_error !s} " ,
451+ )
452+
453+ return record
454+
455+ def _rebuild_failed_plugin_info (self ) -> None :
456+ if not self .failed_plugin_dict :
457+ self .failed_plugin_info = ""
458+ return
459+
460+ lines = []
461+ for dir_name , info in self .failed_plugin_dict .items ():
462+ if isinstance (info , dict ):
463+ error = info .get ("error" , "未知错误" )
464+ else :
465+ error = str (info )
466+ lines .append (f"加载 { dir_name } 插件时出现问题,原因 { error } 。" )
467+
468+ self .failed_plugin_info = "\n " .join (lines ) + "\n "
469+
418470 async def reload_failed_plugin (self , dir_name ):
419471 """
420472 重新加载未注册(加载失败)的插件
@@ -435,8 +487,7 @@ async def reload_failed_plugin(self, dir_name):
435487 success , error = await self .load (specified_dir_name = dir_name )
436488 if success :
437489 self .failed_plugin_dict .pop (dir_name , None )
438- if not self .failed_plugin_dict :
439- self .failed_plugin_info = ""
490+ self ._rebuild_failed_plugin_info ()
440491 return success , None
441492 else :
442493 return False , error
@@ -567,10 +618,15 @@ async def load(
567618 logger .error (error_trace )
568619 logger .error (f"插件 { root_dir_name } 导入失败。原因:{ e !s} " )
569620 fail_rec += f"加载 { root_dir_name } 插件时出现问题,原因 { e !s} 。\n "
570- self .failed_plugin_dict [root_dir_name ] = {
571- "error" : str (e ),
572- "traceback" : error_trace ,
573- }
621+ self .failed_plugin_dict [root_dir_name ] = (
622+ self ._build_failed_plugin_record (
623+ root_dir_name = root_dir_name ,
624+ plugin_dir_path = plugin_dir_path ,
625+ reserved = reserved ,
626+ error = e ,
627+ error_trace = error_trace ,
628+ )
629+ )
574630 if path in star_map :
575631 logger .info ("失败插件依旧在插件列表中,正在清理..." )
576632 metadata = star_map .pop (path )
@@ -837,10 +893,15 @@ async def load(
837893 logger .error (f"| { line } " )
838894 logger .error ("----------------------------------" )
839895 fail_rec += f"加载 { root_dir_name } 插件时出现问题,原因 { e !s} 。\n "
840- self .failed_plugin_dict [root_dir_name ] = {
841- "error" : str (e ),
842- "traceback" : errors ,
843- }
896+ self .failed_plugin_dict [root_dir_name ] = (
897+ self ._build_failed_plugin_record (
898+ root_dir_name = root_dir_name ,
899+ plugin_dir_path = plugin_dir_path ,
900+ reserved = reserved ,
901+ error = e ,
902+ error_trace = errors ,
903+ )
904+ )
844905 # 记录注册失败的插件名称,以便后续重载插件
845906 if path in star_map :
846907 logger .info ("失败插件依旧在插件列表中,正在清理..." )
@@ -857,10 +918,10 @@ async def load(
857918 logger .error (f"同步指令配置失败: { e !s} " )
858919 logger .error (traceback .format_exc ())
859920
921+ self ._rebuild_failed_plugin_info ()
860922 if not fail_rec :
861923 return True , None
862- self .failed_plugin_info = fail_rec
863- return False , fail_rec
924+ return False , self .failed_plugin_info
864925
865926 async def _cleanup_failed_plugin_install (
866927 self ,
@@ -934,10 +995,8 @@ async def install_plugin(
934995 async with self ._pm_lock :
935996 plugin_path = ""
936997 dir_name = ""
937- cleanup_required = False
938998 try :
939999 plugin_path = await self .updator .install (repo_url , proxy )
940- cleanup_required = True
9411000
9421001 # reload the plugin
9431002 dir_name = os .path .basename (plugin_path )
@@ -985,10 +1044,9 @@ async def install_plugin(
9851044
9861045 return plugin_info
9871046 except Exception :
988- if cleanup_required and dir_name and plugin_path :
989- await self ._cleanup_failed_plugin_install (
990- dir_name = dir_name ,
991- plugin_path = plugin_path ,
1047+ if dir_name and plugin_path :
1048+ logger .warning (
1049+ f"安装插件 { dir_name } 失败,插件安装目录:{ plugin_path } " ,
9921050 )
9931051 raise
9941052
@@ -1086,6 +1144,80 @@ async def uninstall_plugin(
10861144 except Exception as e :
10871145 logger .warning (f"删除插件持久化数据失败 (plugins_data): { e !s} " )
10881146
1147+ async def uninstall_failed_plugin (
1148+ self ,
1149+ dir_name : str ,
1150+ delete_config : bool = False ,
1151+ delete_data : bool = False ,
1152+ ) -> None :
1153+ """卸载加载失败的插件(按目录名)。"""
1154+ async with self ._pm_lock :
1155+ failed_info = self .failed_plugin_dict .get (dir_name )
1156+ if not failed_info :
1157+ raise Exception ("插件不存在于失败列表中。" )
1158+
1159+ if isinstance (failed_info , dict ) and failed_info .get ("reserved" ):
1160+ raise Exception ("该插件是 AstrBot 保留插件,无法卸载。" )
1161+
1162+ plugin_path = os .path .join (self .plugin_store_path , dir_name )
1163+ if not os .path .exists (plugin_path ):
1164+ raise Exception ("插件目录不存在。" )
1165+
1166+ self ._cleanup_plugin_state (dir_name )
1167+
1168+ try :
1169+ remove_dir (plugin_path )
1170+ except Exception as e :
1171+ raise Exception (
1172+ f"移除失败插件成功,但是删除插件文件夹失败: { e !s} 。您可以手动删除该文件夹,位于 addons/plugins/ 下。" ,
1173+ )
1174+
1175+ if delete_config :
1176+ config_file = os .path .join (
1177+ self .plugin_config_path ,
1178+ f"{ dir_name } _config.json" ,
1179+ )
1180+ if os .path .exists (config_file ):
1181+ try :
1182+ os .remove (config_file )
1183+ logger .info (f"已删除失败插件 { dir_name } 的配置文件" )
1184+ except Exception as e :
1185+ logger .warning (f"删除失败插件配置文件失败: { e !s} " )
1186+
1187+ if delete_data :
1188+ data_base_dir = os .path .dirname (self .plugin_store_path )
1189+
1190+ plugin_data_dir = os .path .join (data_base_dir , "plugin_data" , dir_name )
1191+ if os .path .exists (plugin_data_dir ):
1192+ try :
1193+ remove_dir (plugin_data_dir )
1194+ logger .info (
1195+ f"已删除失败插件 { dir_name } 的持久化数据 (plugin_data)" ,
1196+ )
1197+ except Exception as e :
1198+ logger .warning (
1199+ f"删除失败插件持久化数据失败 (plugin_data): { e !s} " ,
1200+ )
1201+
1202+ plugins_data_dir = os .path .join (
1203+ data_base_dir ,
1204+ "plugins_data" ,
1205+ dir_name ,
1206+ )
1207+ if os .path .exists (plugins_data_dir ):
1208+ try :
1209+ remove_dir (plugins_data_dir )
1210+ logger .info (
1211+ f"已删除失败插件 { dir_name } 的持久化数据 (plugins_data)" ,
1212+ )
1213+ except Exception as e :
1214+ logger .warning (
1215+ f"删除失败插件持久化数据失败 (plugins_data): { e !s} " ,
1216+ )
1217+
1218+ self .failed_plugin_dict .pop (dir_name , None )
1219+ self ._rebuild_failed_plugin_info ()
1220+
10891221 async def _unbind_plugin (self , plugin_name : str , plugin_module_path : str ) -> None :
10901222 """解绑并移除一个插件。
10911223
@@ -1267,7 +1399,6 @@ async def install_plugin_from_file(
12671399 dir_name = os .path .basename (zip_file_path ).replace (".zip" , "" )
12681400 dir_name = dir_name .removesuffix ("-master" ).removesuffix ("-main" ).lower ()
12691401 desti_dir = os .path .join (self .plugin_store_path , dir_name )
1270- cleanup_required = False
12711402
12721403 # 第一步:检查是否已安装同目录名的插件,先终止旧插件
12731404 existing_plugin = None
@@ -1289,7 +1420,6 @@ async def install_plugin_from_file(
12891420
12901421 try :
12911422 self .updator .unzip_file (zip_file_path , desti_dir )
1292- cleanup_required = True
12931423
12941424 # 第二步:解压后,读取新插件的 metadata.yaml,检查是否存在同名但不同目录的插件
12951425 try :
@@ -1369,9 +1499,7 @@ async def install_plugin_from_file(
13691499
13701500 return plugin_info
13711501 except Exception :
1372- if cleanup_required :
1373- await self ._cleanup_failed_plugin_install (
1374- dir_name = dir_name ,
1375- plugin_path = desti_dir ,
1376- )
1502+ logger .warning (
1503+ f"安装插件 { dir_name } 失败,插件安装目录:{ desti_dir } " ,
1504+ )
13771505 raise
0 commit comments