Skip to content

Commit 06237f8

Browse files
authored
v2.4.13: 更新禁漫网页的正则表达式,完善【多图合并pdf】插件的一些细节。 (#187)
1 parent d0f4082 commit 06237f8

7 files changed

+143
-58
lines changed

src/jmcomic/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# 被依赖方 <--- 使用方
33
# config <--- entity <--- toolkit <--- client <--- option <--- downloader
44

5-
__version__ = '2.4.12'
5+
__version__ = '2.4.13'
66

77
from .api import *
88
from .jm_plugin import *

src/jmcomic/jm_client_interface.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ def transfer_to(self,
7171
JmImageTool.save_resp_img(
7272
self,
7373
path,
74-
need_convert=suffix_not_equal(img_url, path),
74+
need_convert=suffix_not_equal(img_url[:img_url.find("?")], path),
7575
)
7676
else:
7777
# 解密图片并保存文件

src/jmcomic/jm_option.py

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -242,14 +242,16 @@ def decide_image_suffix(self, image: JmImageDetail) -> str:
242242
# 非动图,以配置为先
243243
return self.download.image.suffix or image.img_file_suffix
244244

245-
def decide_image_save_dir(self, photo) -> str:
245+
def decide_image_save_dir(self, photo, ensure_exists=True) -> str:
246246
# 使用 self.dir_rule 决定 save_dir
247247
save_dir = self.dir_rule.decide_image_save_dir(
248248
photo.from_album,
249249
photo
250250
)
251251

252-
mkdir_if_not_exists(save_dir)
252+
if ensure_exists:
253+
mkdir_if_not_exists(save_dir)
254+
253255
return save_dir
254256

255257
def decide_image_filepath(self, image: JmImageDetail, consider_custom_suffix=True) -> str:
@@ -504,7 +506,7 @@ def call_all_plugin(self, group: str, safe=True, **extra):
504506

505507
plugin_registry = JmModuleConfig.REGISTRY_PLUGIN
506508
for pinfo in plugin_list:
507-
key, kwargs = pinfo['plugin'], pinfo['kwargs']
509+
key, kwargs = pinfo['plugin'], pinfo.get('kwargs', None) # kwargs为None
508510
plugin_class: Optional[Type[JmOptionPlugin]] = plugin_registry.get(key, None)
509511

510512
ExceptionTool.require_true(plugin_class is not None, f'[{group}] 未注册的plugin: {key}')
@@ -517,7 +519,7 @@ def call_all_plugin(self, group: str, safe=True, **extra):
517519
else:
518520
raise e
519521

520-
def invoke_plugin(self, plugin_class, kwargs: Any, extra: dict, pinfo: dict):
522+
def invoke_plugin(self, plugin_class, kwargs: Optional[Dict], extra: dict, pinfo: dict):
521523
# 检查插件的参数类型
522524
kwargs = self.fix_kwargs(kwargs)
523525
# 把插件的配置数据kwargs和附加数据extra合并,extra会覆盖kwargs
@@ -591,15 +593,18 @@ def handle_plugin_jmcomic_exception(self, e, pinfo: dict, kwargs: dict, plugin):
591593
raise e
592594

593595
# noinspection PyMethodMayBeStatic
594-
def fix_kwargs(self, kwargs) -> Dict[str, Any]:
596+
def fix_kwargs(self, kwargs: Optional[Dict]) -> Dict[str, Any]:
595597
"""
596598
kwargs将来要传给方法参数,这要求kwargs的key是str类型,
597599
该方法检查kwargs的key的类型,如果不是str,尝试转为str,不行则抛异常。
598600
"""
599-
ExceptionTool.require_true(
600-
isinstance(kwargs, dict),
601-
f'插件的kwargs参数必须为dict类型,而不能是类型: {type(kwargs)}'
602-
)
601+
if kwargs is None:
602+
kwargs = {}
603+
else:
604+
ExceptionTool.require_true(
605+
isinstance(kwargs, dict),
606+
f'插件的kwargs参数必须为dict类型,而不能是类型: {type(kwargs)}'
607+
)
603608

604609
kwargs: dict
605610
new_kwargs: Dict[str, Any] = {}

src/jmcomic/jm_plugin.py

Lines changed: 99 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class JmOptionPlugin:
1818
def __init__(self, option: JmOption):
1919
self.option = option
2020
self.log_enable = True
21+
self.delete_original_file = False
2122

2223
def invoke(self, **kwargs) -> None:
2324
"""
@@ -66,6 +67,33 @@ def warning_lib_not_install(self, lib: str):
6667
import warnings
6768
warnings.warn(msg)
6869

70+
def execute_deletion(self, paths: List[str]):
71+
"""
72+
删除文件和文件夹
73+
:param paths: 路径列表
74+
"""
75+
if self.delete_original_file is not True:
76+
return
77+
78+
for p in paths:
79+
if file_not_exists(p):
80+
continue
81+
82+
if os.path.isdir(p):
83+
os.rmdir(p)
84+
self.log(f'删除文件夹: {p}', 'remove')
85+
else:
86+
os.remove(p)
87+
self.log(f'删除原文件: {p}', 'remove')
88+
89+
# noinspection PyMethodMayBeStatic
90+
def execute_cmd(self, cmd):
91+
"""
92+
执行shell命令,这里采用简单的实现
93+
:param cmd: shell命令
94+
"""
95+
return os.system(cmd)
96+
6997

7098
class JmLoginPlugin(JmOptionPlugin):
7199
"""
@@ -342,12 +370,16 @@ def do_zip(self, source_dir, zip_path, all_filepath, msg):
342370
return self.unified_path(source_dir)
343371

344372
def after_zip(self, dir_zip_dict: Dict[str, Optional[str]]):
345-
# 是否要删除所有原文件
346-
if self.delete_original_file is True:
347-
self.delete_all_files_and_empty_dir(
348-
all_downloaded=self.downloader.all_downloaded,
349-
dir_list=list(dir_zip_dict.keys())
350-
)
373+
# 删除所有原文件
374+
dirs = sorted(dir_zip_dict.keys(), reverse=True)
375+
image_paths = [
376+
path
377+
for photo_dict in self.downloader.all_downloaded.values()
378+
for image_list in photo_dict.values()
379+
for path, image in image_list
380+
]
381+
self.execute_deletion(image_paths)
382+
self.execute_deletion(dirs)
351383

352384
# noinspection PyMethodMayBeStatic
353385
def get_zip_path(self, album, photo, filename_rule, suffix, zip_dir):
@@ -361,28 +393,6 @@ def get_zip_path(self, album, photo, filename_rule, suffix, zip_dir):
361393
filename + fix_suffix(suffix),
362394
)
363395

364-
# noinspection PyMethodMayBeStatic
365-
def delete_all_files_and_empty_dir(self, all_downloaded: dict, dir_list: List[str]):
366-
"""
367-
删除所有文件和文件夹
368-
"""
369-
import os
370-
for photo_dict in all_downloaded.values():
371-
for image_list in photo_dict.values():
372-
for f, _ in image_list:
373-
# check not exist
374-
if file_not_exists(f):
375-
continue
376-
377-
os.remove(f)
378-
self.log(f'删除原文件: {f}', 'remove')
379-
380-
for d in sorted(dir_list, reverse=True):
381-
# check exist
382-
if file_exists(d):
383-
os.rmdir(d)
384-
self.log(f'删除文件夹: {d}', 'remove')
385-
386396

387397
class ClientProxyPlugin(JmOptionPlugin):
388398
plugin_key = 'client_proxy'
@@ -581,10 +591,7 @@ def main(self):
581591
else:
582592
self.zip_with_password()
583593

584-
# 删除导出的原文件
585-
if self.delete_original_file is True:
586-
for f in self.files:
587-
os.remove(f)
594+
self.execute_deletion(self.files)
588595

589596
def handle_folder(self, fid: str, fname: str):
590597
self.log(f'【收藏夹: {fname}, fid: {fid}】开始获取数据')
@@ -646,35 +653,77 @@ def zip_with_password(self):
646653
os.chdir(self.save_dir)
647654
cmd = f'7z a "{self.zip_filepath}" "{self.save_dir}" -p{self.zip_password} -mhe=on'
648655
self.require_true(
649-
0 == os.system(cmd),
656+
0 == self.execute_cmd(cmd),
650657
'加密压缩文件失败'
651658
)
652659

653660

654661
class ConvertJpgToPdfPlugin(JmOptionPlugin):
655662
plugin_key = 'j2p'
656663

664+
def check_image_suffix_is_valid(self, std_suffix):
665+
"""
666+
检查option配置的图片后缀转换,目前限制使用Magick时只能搭配jpg
667+
暂不探究Magick是否支持更多图片格式
668+
"""
669+
cur_suffix: Optional[str] = self.option.download.image.suffix
670+
671+
ExceptionTool.require_true(
672+
cur_suffix is not None and cur_suffix.endswith(std_suffix),
673+
'请把图片的后缀转换配置为jpg,不然无法使用Magick!'
674+
f'(当前配置是[{cur_suffix}])\n'
675+
f'配置模板如下: \n'
676+
f'```\n'
677+
f'download:\n'
678+
f' image:\n'
679+
f' suffix: {std_suffix} # 当前配置是{cur_suffix}\n'
680+
f'```'
681+
)
682+
657683
def invoke(self,
658684
photo: JmPhotoDetail,
685+
downloader=None,
659686
pdf_dir=None,
660687
filename_rule='Pid',
661688
quality=100,
689+
delete_original_file=False,
690+
overwrite_cmd=None,
691+
overwrite_jpg=None,
662692
**kwargs,
663693
):
694+
self.delete_original_file = delete_original_file
695+
696+
# 检查图片后缀配置
697+
suffix = overwrite_jpg or '.jpg'
698+
self.check_image_suffix_is_valid(suffix)
699+
700+
# 处理文件夹配置
664701
filename = DirRule.apply_rule_directly(None, photo, filename_rule)
665702
photo_dir = self.option.decide_image_save_dir(photo)
703+
704+
# 处理生成的pdf文件的路径
666705
if pdf_dir is None:
667706
pdf_dir = photo_dir
668707
else:
708+
pdf_dir = fix_filepath(pdf_dir, True)
669709
mkdir_if_not_exists(pdf_dir)
670710

671-
pdf_filepath = f'{pdf_dir}{filename}.pdf'
672-
673-
def get_cmd(suffix='.jpg'):
674-
return f'magick convert -quality {quality} "{photo_dir}*{suffix}" "{pdf_filepath}"'
711+
pdf_filepath = os.path.join(pdf_dir, f'{filename}.pdf')
712+
713+
# 生成命令
714+
def generate_cmd():
715+
return (
716+
overwrite_cmd or
717+
'magick convert -quality {quality} "{photo_dir}*{suffix}" "{pdf_filepath}"'
718+
).format(
719+
quality=quality,
720+
photo_dir=photo_dir,
721+
suffix=suffix,
722+
pdf_filepath=pdf_filepath,
723+
)
675724

676-
cmd = get_cmd()
677-
self.log(f'execute command: {cmd}')
725+
cmd = generate_cmd()
726+
self.log(f'Execute Command: [{cmd}]')
678727
code = self.execute_cmd(cmd)
679728

680729
self.require_true(
@@ -686,6 +735,14 @@ def get_cmd(suffix='.jpg'):
686735

687736
self.log(f'Convert Successfully: JM{photo.id}{pdf_filepath}')
688737

689-
# noinspection PyMethodMayBeStatic
690-
def execute_cmd(self, cmd):
691-
return os.system(cmd)
738+
if downloader is not None:
739+
from .jm_downloader import JmDownloader
740+
downloader: JmDownloader
741+
742+
paths = [
743+
path
744+
for path, image in downloader.all_downloaded[photo.from_album][photo]
745+
]
746+
747+
paths.append(self.option.decide_image_save_dir(photo, ensure_exists=False))
748+
self.execute_deletion(paths)

src/jmcomic/jm_toolkit.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ class JmcomicText:
5151
# 點擊喜歡
5252
pattern_html_album_likes = compile(r'<span id="albim_likes_\d+">(.*?)</span>')
5353
# 觀看
54-
pattern_html_album_views = compile(r'<span>(.*?)</span> (次觀看|观看次数)')
54+
pattern_html_album_views = compile(r'<span>(.*?)</span>\n<span>(次觀看|观看次数)</span>')
5555
# 評論(div)
5656
pattern_html_album_comment_count = compile(r'<div class="badge"[^>]*?id="total_video_comments">(\d+)</div>'), 0
5757

@@ -266,19 +266,37 @@ def add(w=None):
266266
ret.append(w)
267267
char_list.clear()
268268

269+
def find_right_pair(left_pair, i):
270+
stack = [left_pair]
271+
j = i + 1
272+
273+
while j < length and len(stack) != 0:
274+
c = title[j]
275+
if c in bracket_map:
276+
stack.append(c)
277+
elif c == bracket_map[stack[-1]]:
278+
stack.pop()
279+
280+
j += 1
281+
282+
if len(stack) == 0:
283+
return j
284+
else:
285+
return -1
286+
269287
while i < length:
270288
c = title[i]
271289

272290
if c in bracket_map:
273291
# 上一个单词结束
274292
add()
275293
# 定位右括号
276-
j = title.find(bracket_map[c], i)
294+
j = find_right_pair(c, i)
277295
ExceptionTool.require_true(j != -1, f'未闭合的 {c}{bracket_map[c]}: {title[i:]}')
278296
# 整个括号的单词结束
279-
add(title[i:j + 1])
297+
add(title[i:j])
280298
# 移动指针
281-
i = j + 1
299+
i = j
282300
else:
283301
char_list.append(c)
284302
i += 1

usage/workflow_download.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def main():
5555

5656
def get_option():
5757
# 读取 option 配置文件
58-
option = create_option('../assets/option/option_workflow_download.yml')
58+
option = create_option(os.path.abspath(os.path.join(__file__, '../../assets/option/option_workflow_download.yml')))
5959

6060
# 支持工作流覆盖配置文件的配置
6161
cover_option_config(option)

usage/workflow_export_favorites.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33

44
def prepare_actions_input_and_secrets():
5+
"""
6+
本函数替代对配置文件中的 ${} 的解析函数
7+
目的是为了支持:当没有配置环境变量时,可以找另一个环境变量来用
8+
"""
9+
510
def env(match: Match) -> str:
611
name = match[1]
712
value = os.getenv(name, '')

0 commit comments

Comments
 (0)