11"""
2- 该文件存放的是 Feature(下载特性) 机制
2+ 该文件存放的是 Feature 机制
33
4- Feature 用于在下载生命周期中挂载上下文相关的动态附加行为,
5- 例如下载完成后自适应导出为 PDF、ZIP 或长图等。
6- 它不仅是插件的封装,更能根据调用来源(整本/单章)智能调整执行策略。
4+ Feature 用于封装复杂、高级的功能特性,例如pdf导出插件,以前用户需要知道插件名称,调用时机,option插件参数等等,使用feature相当于包办了这些。
75
86用法:
97 from jmcomic import download_album, Feature
108
119 # 最简单
1210 download_album(id, option, extra=Feature.export_pdf)
1311
14- # 带参数
12+ # 带自定义参数
1513 download_album(id, option, extra=Feature.export_pdf(pdf_dir='./output'))
1614
1715 # 多个 Feature(列表 / 运算符均可)
1816 download_album(id, option, extra=[Feature.export_pdf, Feature.export_zip])
1917 download_album(id, option, extra=Feature.export_pdf + Feature.export_zip)
2018"""
19+ from typing import LiteralString
2120
2221from .jm_plugin import *
2322
@@ -36,23 +35,25 @@ class Feature:
3635 export_zip : 'PluginFeature'
3736 export_long_img : 'PluginFeature'
3837
39- def should_invoke (self , when : str , feature_from : str ) -> bool :
38+ def should_invoke (self , feature_from : str , when : str ) -> bool :
4039 """
4140 判断在当前钩子(when)下,根据来源(feature_from),是否应该执行。
4241 默认返回 True(任何钩子都执行)。子类可覆写来限制执行时机。
4342
44- :param when: 当前触发的钩子名称,如 'after_album', 'after_photo'
4543 :param feature_from: Feature 的注册来源,如 'download_album', 'download_photo'
44+ :param when: 当前触发的钩子名称,如 'after_album', 'after_photo'
4645 :returns: 是否应该执行
4746 """
4847 return True
4948
50- def invoke (self , option , ** context ):
49+ def invoke (self , option : JmOption , feature_from : str , when : str , ** kwargs ):
5150 """
5251 执行此 Feature。子类需实现该方法。
5352
5453 :param option: 当前的 JmOption
55- :param context: album, photo, downloader, feature_from 等上下文
54+ :param feature_from 注册来源,如 'download_album', 'download_photo'
55+ :param when: 钩子回调时机,如 'after_album', 'after_photo'
56+ :param kwargs: album, photo, downloader 等回调参数
5657 """
5758 raise NotImplementedError
5859
@@ -83,9 +84,9 @@ def __init__(self, plugin_key, **kwargs):
8384 # 用户通过 __call__ 显式传入的参数名,这些参数不会被 _adapt_kwargs 动态适配
8485 self ._user_keys : set = set ()
8586
86- def should_invoke (self , when : str , feature_from : str ) -> bool :
87+ def should_invoke (self , feature_from : str , when : str ) -> bool :
8788 """
88- 根据注册来源推导执行时机 :
89+ 默认根据注册来源推导执行时机 :
8990 download_album → after_album, download_photo → after_photo
9091 """
9192 if feature_from == 'download_album' :
@@ -100,52 +101,34 @@ def __call__(self, **kwargs):
100101 new_kwargs .update (kwargs )
101102 new_instance = PluginFeature (self .plugin_key , ** new_kwargs )
102103 # 记录用户显式传入的参数名,这些参数不被动态适配
103- new_instance ._user_keys = set (kwargs .keys ())
104+ new_instance ._user_0keys = set (kwargs .keys ())
104105 return new_instance
105106
106- def invoke (self , option , feature_from = None , ** context ):
107+ def invoke (self , option : JmOption , feature_from : str , when : str , ** extra ):
107108 """
108109 执行此 Feature 对应的插件。
109110 根据 feature_from 动态适配 filename_rule 等参数。
110111 """
111- pclass = JmModuleConfig .REGISTRY_PLUGIN .get (self .plugin_key )
112- if pclass is None :
113- ExceptionTool .raises (f'PluginFeature 引用了未注册的插件: { self .plugin_key } ' )
112+ pclass : type = JmModuleConfig .REGISTRY_PLUGIN .get (self .plugin_key )
113+ ExceptionTool .require_true (pclass is not None , f'PluginFeature 引用了未注册的插件: { self .plugin_key } , from { feature_from } , when { when } ' )
114114
115115 # 根据 feature_from 动态适配参数
116- adapted = self ._adapt_kwargs (feature_from )
117- merged_kwargs = {** adapted , ** context }
116+ plugin_kwargs : dict = self ._adapt_plugin_kwargs (feature_from , when )
118117
119118 option .invoke_plugin (
120119 pclass = pclass ,
121- kwargs = merged_kwargs ,
122- extra = {} ,
123- pinfo = {'plugin' : self .plugin_key , 'kwargs' : adapted },
120+ kwargs = plugin_kwargs ,
121+ extra = extra ,
122+ pinfo = {'plugin' : self .plugin_key , 'kwargs' : plugin_kwargs },
124123 )
125124
126- def _adapt_kwargs (self , feature_from ) :
125+ def _adapt_plugin_kwargs (self , feature_from : str , when : str ) -> dict :
127126 """
128- 根据 feature_from 动态适配参数:
129- - filename_rule 前缀:download_album → A前缀,download_photo → P前缀
130-
131- 注意:用户通过 __call__ 显式传入的参数(记录在 _user_keys 中)不会被适配。
127+ 根据feature_from和when动态确定以下插件参数:
128+ filename_rule
132129 """
133130 kwargs = self .kwargs .copy ()
134-
135- if feature_from == 'download_album' :
136- # album 模式:P前缀规则 → A前缀规则
137- if 'filename_rule' not in self ._user_keys and 'filename_rule' in kwargs :
138- rule = kwargs ['filename_rule' ]
139- if rule and rule [0 ] == 'P' :
140- kwargs ['filename_rule' ] = 'A' + rule [1 :]
141-
142- elif feature_from == 'download_photo' :
143- # photo 模式:A前缀规则 → P前缀规则
144- if 'filename_rule' not in self ._user_keys and 'filename_rule' in kwargs :
145- rule = kwargs ['filename_rule' ]
146- if rule and rule [0 ] == 'A' :
147- kwargs ['filename_rule' ] = 'P' + rule [1 :]
148-
131+ kwargs .setdefault ('filename_rule' , '[JM{Aid}]{Atitle}' if feature_from == 'download_album' else '[JM{Pid}]{Ptitle}' )
149132 return kwargs
150133
151134 def __repr__ (self ):
@@ -184,6 +167,6 @@ def __repr__(self):
184167# 预定义特性(用插件类的 plugin_key 引用,附带默认参数)
185168# filename_rule 会根据 feature_from 在 invoke 时动态适配 A/P 前缀
186169# zip 的打包粒度由插件根据上下文(album/photo)自动推导,无需 level 参数
187- Feature .export_pdf = PluginFeature (Img2pdfPlugin .plugin_key , pdf_dir = './' , filename_rule = 'Atitle' )
188- Feature .export_zip = PluginFeature (ZipPlugin .plugin_key , zip_dir = './' , filename_rule = 'Ptitle' )
189- Feature .export_long_img = PluginFeature (LongImgPlugin .plugin_key , img_dir = './' , filename_rule = 'Pid' )
170+ Feature .export_pdf = PluginFeature (Img2pdfPlugin .plugin_key , pdf_dir = './' )
171+ Feature .export_zip = PluginFeature (ZipPlugin .plugin_key , zip_dir = './' )
172+ Feature .export_long_img = PluginFeature (LongImgPlugin .plugin_key , img_dir = './' )
0 commit comments