Skip to content

Commit f2adb33

Browse files
authored
v2.6.9: 增加下载本子封面图的函数以及download_cover插件; 修复client.after_init时advance_retry插件没有生效的问题(#472) (#482)
1 parent 7943b9b commit f2adb33

File tree

10 files changed

+100
-19
lines changed

10 files changed

+100
-19
lines changed

assets/docs/sources/option_file_syntax.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,12 +109,12 @@ dir_rule:
109109
# 规则: 根目录 / 本子id / 章节序号 / 图片文件
110110
# rule: 'Bd / Aid / Pindex'
111111
# rule: 'Bd_Aid_Pindex'
112-
113112
# 默认规则是: 根目录 / 章节标题 / 图片文件
114-
rule: Bd_Ptitle
113+
rule: Bd / Ptitle
115114
# jmcomic v2.5.36 以后,支持使用python的f-string的语法组合文件夹名,下为示例
116115
# rule: Bd / Aauthor / (JM{Aid}-{Pindex})-{Pname}
117116
# {}大括号里的内容同样是写 Axxx 或 Pxxx,其他语法自行参考python f-string的语法
117+
# 另外,rule开头的Bd可忽略不写,因为程序会自动插入Bd
118118
```
119119

120120
## 3. option插件配置项
@@ -194,6 +194,15 @@ plugins:
194194
album_photo_dict:
195195
324930: 424507
196196

197+
before_album:
198+
- plugin: download_cover # 额外下载本子封面的插件
199+
kwargs:
200+
size: '_3x4' # 可选项,禁漫搜索页的封面图尺寸是 4x3,和详情页不一样,想下搜索页的封面就设置此项
201+
dir_rule: # 封面图存放路径规则,写法同上
202+
base_dir: D:/a/b/c/
203+
rule: '{Atitle}/{Aid}_cover.jpg'
204+
205+
197206
after_album:
198207
- plugin: zip # 压缩文件插件
199208
kwargs:

assets/docs/sources/tutorial/0_common_usage.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ download_album(123, option)
3838
option.download_album(123)
3939
```
4040

41-
## 获取本子/章节/图片的实体类,下载图片
41+
## 获取本子/章节/图片的实体类,下载图片/封面图
4242

4343
```python
4444
from jmcomic import *
@@ -49,23 +49,26 @@ client = JmOption.default().new_jm_client()
4949
# 本子实体类
5050
album: JmAlbumDetail = client.get_album_detail('427413')
5151

52+
# 下载本子封面图,保存为 cover.png (图片后缀可指定为jpg、webp等)
53+
client.download_album_cover('427413', './cover.png')
54+
5255

5356
def fetch(photo: JmPhotoDetail):
5457
# 章节实体类
5558
photo = client.get_photo_detail(photo.photo_id, False)
5659
print(f'章节id: {photo.photo_id}')
57-
60+
5861
# 图片实体类
5962
image: JmImageDetail
6063
for image in photo:
6164
print(f'图片url: {image.img_url}')
62-
65+
6366
# 下载单个图片
6467
client.download_by_image_detail(image, './a.jpg')
6568
# 如果是已知未混淆的图片,也可以直接使用url来下载
6669
random_image_domain = JmModuleConfig.DOMAIN_IMAGE_LIST[0]
6770
client.download_image(f'https://{random_image_domain}/media/albums/416130.jpg', './a.jpg')
68-
71+
6972

7073
# 多线程发起请求
7174
multi_thread_launcher(

assets/option/option_test_api.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ plugins:
2727
proxy_client_key: photo_concurrent_fetcher_proxy
2828
whitelist: [ api, ]
2929

30-
- plugin: advanced-retry
30+
- plugin: advanced_retry
3131
kwargs:
3232
retry_config:
3333
retry_rounds: 3 # 一共对域名列表重试3轮

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.6.7'
5+
__version__ = '2.6.9'
66

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

src/jmcomic/jm_client_impl.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ def __init__(self,
1515
postman: Postman,
1616
domain_list: List[str],
1717
retry_times=0,
18+
domain_retry_strategy=None,
1819
):
1920
"""
2021
创建JM客户端
@@ -26,9 +27,11 @@ def __init__(self,
2627
super().__init__(postman)
2728
self.retry_times = retry_times
2829
self.domain_list = domain_list
29-
self.domain_retry_strategy = None
30+
self.domain_retry_strategy = domain_retry_strategy
3031
self.CLIENT_CACHE = None
3132
self._username = None # help for favorite_folder method
33+
if domain_retry_strategy:
34+
domain_retry_strategy(self)
3235
self.enable_cache()
3336
self.after_init()
3437

src/jmcomic/jm_client_interface.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,14 @@ def img_is_not_need_to_decode(cls, data_original: str, _resp) -> bool:
293293
# https://cdn-msp2.18comic.vip/media/photos/498976/00027.gif
294294
return data_original.endswith('.gif')
295295

296+
def download_album_cover(self, album_id, save_path: str, size: str = ''):
297+
self.download_image(
298+
img_url=JmcomicText.get_album_cover_url(album_id, size=size),
299+
img_save_path=save_path,
300+
scramble_id=None,
301+
decode_image=False,
302+
)
303+
296304

297305
class JmSearchAlbumClient:
298306
"""

src/jmcomic/jm_option.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,8 @@ def get_rule_parser(cls, rule: str):
158158
if rule.startswith(('A', 'P')):
159159
return cls.parse_detail_rule
160160

161-
ExceptionTool.raises(f'不支持的rule配置: "{rule}"')
161+
return cls.parse_f_string_rule
162+
# ExceptionTool.raises(f'不支持的rule配置: "{rule}"')
162163

163164
@classmethod
164165
def apply_rule_to_filename(cls, album, photo, rule: str) -> str:
@@ -362,7 +363,13 @@ def build_jm_client(self, **kwargs):
362363
"""
363364
return self.new_jm_client(**kwargs)
364365

365-
def new_jm_client(self, domain_list=None, impl=None, cache=None, **kwargs) -> Union[JmHtmlClient, JmApiClient]:
366+
def new_jm_client(self,
367+
domain_list=None,
368+
impl=None,
369+
cache=None,
370+
domain_retry_strategy=None,
371+
**kwargs
372+
) -> Union[JmHtmlClient, JmApiClient]:
366373
"""
367374
创建新的Client(客户端),不同Client之间的元数据不共享
368375
"""
@@ -423,6 +430,7 @@ def decide_domain_list():
423430
postman=postman,
424431
domain_list=decide_domain_list(),
425432
retry_times=retry_times,
433+
domain_retry_strategy=domain_retry_strategy,
426434
)
427435

428436
# enable cache

src/jmcomic/jm_plugin.py

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ def wait_until_finish(self):
111111
def decide_filepath(self,
112112
album: Optional[JmAlbumDetail],
113113
photo: Optional[JmPhotoDetail],
114-
filename_rule: str, suffix: str, base_dir: Optional[str],
114+
filename_rule: Optional[str], suffix: Optional[str], base_dir: Optional[str],
115115
dir_rule_dict: Optional[dict]
116116
):
117117
"""
@@ -1219,7 +1219,7 @@ def new_decide_dir(photo, ensure_exists=True) -> str:
12191219

12201220

12211221
class AdvancedRetryPlugin(JmOptionPlugin):
1222-
plugin_key = 'advanced-retry'
1222+
plugin_key = 'advanced_retry'
12231223

12241224
def __init__(self, option: JmOption):
12251225
super().__init__(option)
@@ -1234,15 +1234,18 @@ def invoke(self,
12341234
new_jm_client: Callable = self.option.new_jm_client
12351235

12361236
def hook_new_jm_client(*args, **kwargs):
1237-
client: JmcomicClient = new_jm_client(*args, **kwargs)
1238-
client.domain_retry_strategy = self.request_with_retry
1239-
client.domain_req_failed_counter = {}
1240-
from threading import Lock
1241-
client.domain_counter_lock = Lock()
1242-
return client
1237+
return new_jm_client(*args, **kwargs, domain_retry_strategy=self)
12431238

12441239
self.option.new_jm_client = hook_new_jm_client
12451240

1241+
def __call__(self, client: AbstractJmClient, *args, **kwargs):
1242+
if args:
1243+
return self.request_with_retry(client, *args, **kwargs)
1244+
# init
1245+
from threading import Lock
1246+
client.domain_req_failed_counter = {}
1247+
client.domain_counter_lock = Lock()
1248+
12461249
def request_with_retry(self,
12471250
client: AbstractJmClient,
12481251
request: Callable,
@@ -1306,3 +1309,25 @@ def update_failed_count(self, client: AbstractJmClient, domain: str):
13061309
def failed_count(client: JmcomicClient, domain: str) -> int:
13071310
# noinspection PyUnresolvedReferences
13081311
return client.domain_req_failed_counter.get(domain, 0)
1312+
1313+
1314+
class DownloadCoverPlugin(JmOptionPlugin):
1315+
plugin_key = 'download_cover'
1316+
1317+
def invoke(self,
1318+
dir_rule: dict,
1319+
size='',
1320+
photo: JmPhotoDetail = None,
1321+
album: JmAlbumDetail = None,
1322+
downloader=None,
1323+
**kwargs) -> None:
1324+
album_id = album.id if album else photo.album_id
1325+
save_path = self.decide_filepath(
1326+
album, photo,
1327+
None, None, None,
1328+
dir_rule
1329+
)
1330+
if self.option.download.cache and os.path.exists(save_path):
1331+
self.log(f'album-{album_id}的封面已存在,跳过下载: [{save_path}]', 'skip')
1332+
return
1333+
downloader.client.download_album_cover(album_id, save_path, size)

src/jmcomic/jm_toolkit.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,26 @@ def limit_text(cls, text: str, limit: int) -> str:
368368
length = len(text)
369369
return text if length <= limit else (text[:limit] + f'...({length - limit}')
370370

371+
@classmethod
372+
def get_album_cover_url(cls,
373+
album_id: Union[str, int],
374+
image_domain: Optional[str] = None,
375+
size: str = '',
376+
) -> str:
377+
"""
378+
根据本子id生成封面url
379+
380+
:param album_id: 本子id
381+
:param image_domain: 图片cdn域名(可传入裸域或含协议的域名)
382+
:param size: 尺寸后缀,例如搜索列表页会使用 size="_3x4" 的封面图
383+
"""
384+
if image_domain is None:
385+
import random
386+
image_domain = random.choice(JmModuleConfig.DOMAIN_IMAGE_LIST)
387+
388+
path = f'/media/albums/{cls.parse_to_jm_id(album_id)}{size}.jpg'
389+
return cls.format_url(path, image_domain)
390+
371391

372392
# 支持dsl: #{???} -> os.getenv(???)
373393
JmcomicText.dsl_replacer.add_dsl_and_replacer(r'\$\{(.*?)\}', JmcomicText.match_os_env)

tests/test_jmcomic/test_jm_client.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,3 +332,8 @@ def print_page(page):
332332
# 打印page内容
333333
for aid, atitle in page:
334334
print(aid, atitle)
335+
336+
def test_download_cover(self):
337+
album_id = 123
338+
self.client.download_album_cover(album_id, f'{self.option.dir_rule.base_dir}/{album_id}.webp')
339+
self.client.download_album_cover(album_id, f'{self.option.dir_rule.base_dir}/{album_id}_3x4.webp', '_3x4')

0 commit comments

Comments
 (0)