diff --git a/requirements-dev.txt b/requirements-dev.txt index f6ed9b825..07ed2aa11 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -6,4 +6,5 @@ psutil pycryptodome requests plugin_jm_server -zhconv \ No newline at end of file +zhconv +img2pdf \ No newline at end of file diff --git a/src/jmcomic/__init__.py b/src/jmcomic/__init__.py index 4c2fce5e4..a052b0943 100644 --- a/src/jmcomic/__init__.py +++ b/src/jmcomic/__init__.py @@ -2,7 +2,7 @@ # 被依赖方 <--- 使用方 # config <--- entity <--- toolkit <--- client <--- option <--- downloader -__version__ = '2.6.16' +__version__ = '2.6.17' from .api import * from .jm_plugin import * diff --git a/src/jmcomic/jm_config.py b/src/jmcomic/jm_config.py index 59ab93ca5..9a1c8c257 100644 --- a/src/jmcomic/jm_config.py +++ b/src/jmcomic/jm_config.py @@ -102,7 +102,7 @@ class JmMagicConstants: APP_TOKEN_SECRET_2 = '18comicAPPContent' APP_DATA_SECRET = '185Hcomic3PAPP7R' API_DOMAIN_SERVER_SECRET = 'diosfjckwpqpdfjkvnqQjsik' - APP_VERSION = '2.0.18' + APP_VERSION = '2.0.19' # 模块级别共用配置 @@ -405,8 +405,8 @@ def get_fix_ts_token_tokenparam(cls): return ts, token, tokenparam @classmethod - def jm_log(cls, topic: str, msg: str, e: Exception = None): - if cls.FLAG_ENABLE_JM_LOG is True: + def jm_log(cls, topic: str, msg, e: Exception = None): + if cls.FLAG_ENABLE_JM_LOG: executor = cls.EXECUTOR_LOG if e is None: executor(topic, msg) @@ -440,7 +440,7 @@ def new_postman(cls, session=False, **kwargs): from common import Postmans - if session is True: + if session: return Postmans.new_session(**kwargs) return Postmans.new_postman(**kwargs) diff --git a/src/jmcomic/jm_option.py b/src/jmcomic/jm_option.py index d1841494a..edff61d55 100644 --- a/src/jmcomic/jm_option.py +++ b/src/jmcomic/jm_option.py @@ -522,7 +522,7 @@ def download_photo(self, # 下面的方法为调用插件提供支持 - def call_all_plugin(self, group: str, safe=True, **extra): + def call_all_plugin(self, group: str, safe=None, **extra): plugin_list: List[dict] = self.plugins.get(group, []) if plugin_list is None or len(plugin_list) == 0: return @@ -540,7 +540,7 @@ def call_all_plugin(self, group: str, safe=True, **extra): try: self.invoke_plugin(pclass, kwargs, extra, pinfo) except BaseException as e: - if safe is True: + if safe is True or pinfo.get('safe', True): jm_log('plugin.exception', e) else: raise e diff --git a/src/jmcomic/jm_plugin.py b/src/jmcomic/jm_plugin.py index 71cc8f83d..8da3e1cbb 100644 --- a/src/jmcomic/jm_plugin.py +++ b/src/jmcomic/jm_plugin.py @@ -36,7 +36,7 @@ def build(cls, option: JmOption) -> 'JmOptionPlugin': return cls(option) def log(self, msg, topic=None): - if self.log_enable is not True: + if self.log_enable: return jm_log( @@ -68,7 +68,7 @@ def execute_deletion(self, paths: List[str]): 删除文件和文件夹 :param paths: 路径列表 """ - if self.delete_original_file is not True: + if not self.delete_original_file: return for p in paths: @@ -120,7 +120,11 @@ def decide_filepath(self, 参数 dir_rule_dict 优先级最高, 如果 dir_rule_dict 不为空,优先用 dir_rule_dict 否则使用 base_dir + filename_rule + suffix + + 当album为空时,自动复制为photo.from_album,防止底层dir_rule的dsl包含Axx报错 """ + if album is None: + album = photo.from_album filepath: str base_dir: str if dir_rule_dict is not None: @@ -248,7 +252,7 @@ def warning(): ]) self.log(msg, topic='log') - if enable_warning is True: + if enable_warning: # 警告 warning() @@ -776,7 +780,10 @@ def invoke(self, pdf_filepath = self.decide_filepath(album, photo, filename_rule, 'pdf', pdf_dir, dir_rule) # 调用 img2pdf 把 photo_dir 下的所有图片转为pdf - img_path_ls, img_dir_ls = self.write_img_2_pdf(pdf_filepath, album, photo, encrypt) + result = self.write_img_2_pdf(pdf_filepath, album, photo, encrypt) + if not result: + return + img_path_ls, img_dir_ls = result self.log(f'Convert Successfully: JM{album or photo} → {pdf_filepath}') # 执行删除 @@ -801,6 +808,7 @@ def write_img_2_pdf(self, pdf_filepath, album: JmAlbumDetail, photo: JmPhotoDeta if len(img_path_ls) == 0: self.log(f'所有文件夹都不存在图片,无法生成pdf:{img_dir_ls}', 'error') + return with open(pdf_filepath, 'wb') as f: f.write(img2pdf.convert(img_path_ls)) @@ -851,12 +859,14 @@ def invoke(self, # 调用 PIL 把 photo_dir 下的所有图片合并为长图 img_path_ls = self.write_img_2_long_img(long_img_path, album, photo) + if not img_path_ls: + return self.log(f'Convert Successfully: JM{album or photo} → {long_img_path}') # 执行删除 self.execute_deletion(img_path_ls) - def write_img_2_long_img(self, long_img_path, album: JmAlbumDetail, photo: JmPhotoDetail) -> List[str]: + def write_img_2_long_img(self, long_img_path, album: JmAlbumDetail, photo: JmPhotoDetail) -> Optional[List[str]]: import itertools from PIL import Image @@ -868,6 +878,10 @@ def write_img_2_long_img(self, long_img_path, album: JmAlbumDetail, photo: JmPho img_paths = itertools.chain(*map(files_of_dir, img_dir_items)) img_paths = list(filter(lambda x: not x.startswith('.'), img_paths)) # 过滤系统文件 + if not img_paths: + self.log(f'所有文件夹都不存在图片,无法生成long_img:{img_paths}', 'error') + return + images = self.open_images(img_paths) try: @@ -954,11 +968,11 @@ def invoke(self, base_run_kwargs.update(run) run = base_run_kwargs - if self.running is True: + if self.running: return with self.run_server_lock: - if self.running is True: + if self.running: return # 服务器的代码位于一个独立库:plugin_jm_server,需要独立安装 @@ -1074,7 +1088,7 @@ def invoke(self, self.log('Exception happened: ' + str(e), 'check_update.error') continue - if has_update is False: + if not has_update: continue self.log(f'album={album_id},发现新章节: {photo_new_list},准备开始下载') diff --git a/tests/test_jmcomic/test_jm_custom.py b/tests/test_jmcomic/test_jm_custom.py index 1bbb222c7..174b119f3 100644 --- a/tests/test_jmcomic/test_jm_custom.py +++ b/tests/test_jmcomic/test_jm_custom.py @@ -36,6 +36,8 @@ def pname(self): os.path.abspath(save_dir), os.path.abspath(base_dir + dic[1] + '/' + dic[2]), ) + JmModuleConfig.CLASS_ALBUM = JmAlbumDetail + JmModuleConfig.CLASS_PHOTO = JmPhotoDetail def test_extends_api_client(self): class MyClient(JmApiClient): diff --git a/tests/test_jmcomic/test_jm_plugin.py b/tests/test_jmcomic/test_jm_plugin.py new file mode 100644 index 000000000..8efa286db --- /dev/null +++ b/tests/test_jmcomic/test_jm_plugin.py @@ -0,0 +1,37 @@ +from test_jmcomic import * + + +class Test_Plugin(JmTestConfigurable): + + def test_plugin_missing_album_context(self): + """ + source: https://github.com/hect0x7/JMComic-Crawler-Python/issues/523 + + 测试当仅下载单章(photo)时,如果上下文中缺少 album 对象, + 各个包含路径生成的插件(如 download_cover, img2pdf, long_img, zip) + 是否能正确从 photo.from_album 中提取专辑属性, + 避免解析需要 {Atitle} 等本子级占位符时报错 KeyError。 + """ + photo_id = '350234' + option = self.new_option() + + flawed_rule = { + 'base_dir': option.dir_rule.base_dir, + 'rule': '{Atitle}/{Aid}_photo.jpg' + } + + from jmcomic.jm_downloader import DoNotDownloadImage + + # 将四个需要校验的插件全部进行孤立测试,避免前一个插件后续报错导致循环终端 + test_plugins = ['download_cover', 'img2pdf', 'long_img', 'zip'] + option.plugins['before_photo'] = [ + { + 'plugin': plugin_key, + 'kwargs': {'dir_rule': flawed_rule}, + 'safe': False # 防止内部catch异常 + } + for plugin_key in test_plugins + ] + + download_photo(photo_id, option, downloader=DoNotDownloadImage) + print('✅ All folder rule plugins assert completed safely without KeyError.')