Skip to content

Commit de0e0ba

Browse files
authored
v2.3.14: 插件支持调用环境变量,新增插件【发送QQ邮件】,优化移动端缓存scramble_id减少请求。 (#157)
1 parent f2c255e commit de0e0ba

File tree

12 files changed

+140
-71
lines changed

12 files changed

+140
-71
lines changed

.github/workflows/download.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,18 @@ jobs:
1212
crawler:
1313
runs-on: ubuntu-latest
1414
env:
15+
# 登录相关secrets
1516
JM_USERNAME: ${{ secrets.JM_USERNAME }}
1617
JM_PASSWORD: ${{ secrets.JM_PASSWORD }}
18+
19+
# 邮件相关secrets
20+
EMAIL_FROM: ${{ secrets.EMAIL_FROM }}
21+
EMAIL_TO: ${{ secrets.EMAIL_TO }}
22+
EMAIL_PASS: ${{ secrets.EMAIL_PASS }}
23+
EMAIL_TITLE: ${{ secrets.EMAIL_TITLE }}
24+
EMAIL_CONTENT: ${{ secrets.EMAIL_CONTENT }}
25+
26+
# 固定值
1727
JM_DOWNLOAD_DIR: /home/runner/work/jmcomic/download/
1828
ZIP_NAME: '本子.tar.gz'
1929
UPLOAD_NAME: '下载完成的本子'

.github/workflows/download_dispatch.yml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,18 @@ jobs:
6868
ZIP_NAME: ${{ github.event.inputs.ZIP_NAME }}
6969
UPLOAD_NAME: ${{ github.event.inputs.UPLOAD_NAME }}
7070
IMAGE_SUFFIX: ${{ github.event.inputs.IMAGE_SUFFIX }}
71-
# secrets
71+
72+
# 登录相关secrets
7273
JM_USERNAME: ${{ secrets.JM_USERNAME }}
7374
JM_PASSWORD: ${{ secrets.JM_PASSWORD }}
75+
76+
# 邮件相关secrets
77+
EMAIL_FROM: ${{ secrets.EMAIL_FROM }}
78+
EMAIL_TO: ${{ secrets.EMAIL_TO }}
79+
EMAIL_PASS: ${{ secrets.EMAIL_PASS }}
80+
EMAIL_TITLE: ${{ secrets.EMAIL_TITLE }}
81+
EMAIL_CONTENT: ${{ secrets.EMAIL_CONTENT }}
82+
7483
# 固定值
7584
JM_DOWNLOAD_DIR: /home/runner/work/jmcomic/download/
7685

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ $ jmcomic 422866
5454
- **可扩展性强**
5555

5656
- **支持Plugin插件,可以方便地扩展功能,以及使用别人的插件**
57-
- 目前内置支持的插件有:`登录插件` `硬件占用监控插件` `只下载新章插件` `压缩文件插件` `下载特定后缀图片插件`
57+
- 目前内置支持的插件有:`登录插件` `硬件占用监控插件` `只下载新章插件` `压缩文件插件` `下载特定后缀图片插件` `发送QQ邮件插件`
5858
- 支持自定义本子/章节/图片下载前后的回调函数
5959
- 支持自定义debug日志
6060
- 支持自定义类:`Downloader(负责调度)` `Option(负责配置)` `Client(负责请求)` `实体类`
@@ -65,7 +65,7 @@ $ jmcomic 422866
6565

6666
下面列出一些常用的文档链接:
6767

68-
* [option配置文件语法](./assets/docs/sources/option_file_syntax.md)
68+
* [option配置文件语法(包含插件配置)](./assets/docs/sources/option_file_syntax.md)
6969
* [常用类和方法演示(下载本子、获取实体类、搜索本子)](assets/docs/sources/tutorial/3_demo.md)
7070
* [命令行使用教程](assets/docs/sources/tutorial/2_command_line.md)
7171
* [GitHub Actions使用教程](./assets/docs/sources/tutorial/1_github_actions.md)

assets/docs/sources/index.md

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,34 +7,31 @@ Python API for JMComic(禁漫天堂)
77
- Bypasses Cloudflare anti-bot measures.
88
- Multiple usage ways:
99

10-
- GitHub Actions: Requires only a GitHub account. (See
11-
tutorial → [Tutorial - Download Album via GitHub Actions](./tutorial/1_github_actions.md))
12-
13-
- Command line: No need to write Python code, simple and easy to use. (See tutorial → [Tutorial - Download Album via Command Line](./tutorial/2_command_line.md))
14-
- Python code: The most flexible and powerful way, requires some basic knowledge of Python programming.
15-
10+
- GitHub Actions: Requires only a GitHub account. (See
11+
tutorial → [Tutorial - Download Album via GitHub Actions](./tutorial/1_github_actions.md))
12+
- Command line: No need to write Python code, simple and easy to use. (See tutorial → [Tutorial - Download Album via Command Line](./tutorial/2_command_line.md))
13+
- Python code: The most flexible and powerful way, requires some basic knowledge of Python programming.
1614
- Supports two client implementations: web-based and mobile-based. Switchable through configuration (mobile-based has
1715
better IP compatibility, web-based has higher efficiency).
1816
- Supports automatic request retry and domain switching mechanism.
1917
- Multi-threaded downloading (can be fine-tuned to one thread per image, highly efficient).
2018
- Highly configurable:
2119

22-
- Can be used without configuration, very convenient.
23-
- Configuration can be generated from a configuration file, supports multiple file formats.
24-
- Configuration options
25-
include: `request domain`, `client implementation`, `number of chapters/images downloaded simultaneously`, `image format conversion`, `download path rules`, `request metadata (headers, cookies, proxies)`,
26-
and more.
27-
20+
- Can be used without configuration, very convenient.
21+
- Configuration can be generated from a configuration file, supports multiple file formats.
22+
- Configuration options
23+
include: `request domain`, `client implementation`, `number of chapters/images downloaded simultaneously`, `image format conversion`, `download path rules`, `request metadata (headers, cookies, proxies)`,
24+
and more.
2825
- Highly extensible:
2926

30-
- Supports Plugin plugins for easy functionality extension and use of other plugins.
31-
- Currently built-in
32-
plugins: `login plugin`, `hardware usage monitoring plugin`, `only download new chapters plugin`, `zip compression plugin`.
33-
- Supports custom callback functions before and after downloading album/chapter/images.
34-
- Supports custom debug logging.
35-
- Supports custom core
36-
classes: `Downloader (responsible for scheduling)`, `Option (responsible for configuration)`, `Client (responsible for requests)`, `entity classes`,
37-
and more.
27+
- Supports Plugin plugins for easy functionality extension and use of other plugins.
28+
- Currently built-in
29+
plugins: `login plugin`, `hardware usage monitoring plugin`, `only download new chapters plugin`, `zip compression plugin`, `image suffix filter plugin` `send qq email plugin`.
30+
- Supports custom callback functions before and after downloading album/chapter/images.
31+
- Supports custom debug logging.
32+
- Supports custom core
33+
classes: `Downloader (responsible for scheduling)`, `Option (responsible for configuration)`, `Client (responsible for requests)`, `entity classes`,
34+
and more.
3835

3936
## Install
4037

assets/docs/sources/option_file_syntax.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ dir_rule:
9393
## 3. option插件配置项
9494
```yml
9595
# 插件的配置示例
96+
# 当kwargs的值为字符串类型时,支持使用环境变量,语法为 ${环境变量名}
9697
plugins:
9798
after_init:
9899
- plugin: usage_log # 实时打印硬件占用率的插件
@@ -127,4 +128,12 @@ plugins:
127128
zip_dir: D:/jmcomic/zip/ # 压缩文件存放的文件夹
128129
delete_original_file: true # 压缩成功后,删除所有原文件和文件夹
129130

131+
- plugin: send_qq_email # 发送qq邮件插件
132+
kwargs:
133+
msg_from: ${EMAIL} # 发件人
134+
msg_to: [email protected] # 收件人
135+
password: dkjlakdjlkas # 发件人的授权码
136+
title: jmcomic # 标题
137+
content: jmcomic finished !!! # 内容
138+
130139
```

assets/option/option_workflow_download.yml

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ dir_rule:
55

66
client:
77
domain:
8-
html: [jmcomic1.me, jmcomic.me]
8+
html: [ jmcomic1.me, jmcomic.me ]
99

1010
# 插件配置
1111
plugins:
@@ -15,7 +15,21 @@ plugins:
1515
interval: 0.5 # 间隔时间
1616
enable_warning: false # 不告警
1717

18-
- plugin: client_proxy
18+
- plugin: client_proxy # 提高移动端的请求效率的插件
1919
kwargs:
2020
proxy_client_key: cl_proxy_future
21-
whitelist: [ api, ]
21+
whitelist: [ api, ]
22+
23+
- plugin: login # 登录插件
24+
kwargs:
25+
username: ${JM_USERNAME}
26+
password: ${JM_PASSWORD}
27+
28+
after_download: # 全部下载完成以后
29+
- plugin: send_qq_email # 发送邮件,如果未配置下面的前3个环境变量则不会发送。
30+
kwargs:
31+
msg_from: ${EMAIL_FROM}
32+
msg_to: ${EMAIL_TO}
33+
password: ${EMAIL_PASS}
34+
title: ${EMAIL_TITLE}
35+
content: ${EMAIL_CONTENT}

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.3.12'
5+
__version__ = '2.3.14'
66

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

src/jmcomic/jm_client_impl.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -443,16 +443,22 @@ def get_photo_detail(self,
443443

444444
return photo
445445

446-
def get_scramble_id(self, photo_id):
446+
def get_scramble_id(self, photo_id, album_id=None):
447447
"""
448448
带有缓存的fetch_scramble_id,缓存位于JmModuleConfig.SCRAMBLE_CACHE
449449
"""
450450
cache = JmModuleConfig.SCRAMBLE_CACHE
451451
if photo_id in cache:
452452
return cache[photo_id]
453453

454+
if album_id is not None and album_id in cache:
455+
return cache[album_id]
456+
454457
scramble_id = self.fetch_scramble_id(photo_id)
455458
cache[photo_id] = scramble_id
459+
if album_id is not None:
460+
cache[album_id] = scramble_id
461+
456462
return scramble_id
457463

458464
def fetch_detail_entity(self, apid, clazz):
@@ -511,10 +517,11 @@ def fetch_photo_additional_field(self, photo: JmPhotoDetail, fetch_album: bool,
511517
23做法要改不止一处地方
512518
"""
513519
if fetch_album:
514-
photo.from_album = self.get_album_detail(photo.photo_id)
520+
photo.from_album = self.get_album_detail(photo.album_id)
515521

516522
if fetch_scramble_id:
517-
photo.scramble_id = self.get_scramble_id(photo.album_id)
523+
# 同album的scramble_id相同
524+
photo.scramble_id = self.get_scramble_id(photo.photo_id, photo.album_id)
518525

519526
def setting(self) -> JmApiResp:
520527
"""

src/jmcomic/jm_option.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,7 @@ def decide_postman_headers(self, client_key, domain):
381381
if is_client_type(JmApiClient):
382382
# 移动端
383383
# 不配置headers,由client每次请求前创建headers
384-
return {}
384+
return None
385385

386386
if is_client_type(JmHtmlClient):
387387
# 网页端
@@ -487,6 +487,10 @@ def fix_kwargs(self, kwargs) -> Dict[str, Any]:
487487
new_kwargs: Dict[str, Any] = {}
488488

489489
for k, v in kwargs.items():
490+
if isinstance(v, str):
491+
newv = JmcomicText.parse_dsl_text(v)
492+
v = newv
493+
490494
if isinstance(k, str):
491495
new_kwargs[k] = v
492496
continue

src/jmcomic/jm_plugin.py

Lines changed: 50 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,26 @@ def build(cls, option: JmOption) -> 'JmOptionPlugin':
2626
"""
2727
return cls(option)
2828

29+
@classmethod
30+
def debug(cls, msg, topic=None):
31+
jm_debug(
32+
topic=f'plugin.{cls.plugin_key}' + (f'.{topic}' if topic is not None else ''),
33+
msg=msg
34+
)
35+
2936

3037
class JmLoginPlugin(JmOptionPlugin):
3138
"""
3239
功能:登录禁漫,并保存登录后的cookies,让所有client都带上此cookies
3340
"""
3441
plugin_key = 'login'
3542

36-
def invoke(self, username, password) -> None:
37-
assert isinstance(username, str), '用户名必须是str'
38-
assert isinstance(password, str), '密码必须是str'
43+
def invoke(self,
44+
username: str,
45+
password: str,
46+
) -> None:
47+
if not (username and password):
48+
return
3949

4050
client = self.option.new_jm_client()
4151
client.login(username, password)
@@ -45,7 +55,7 @@ def invoke(self, username, password) -> None:
4555
meta_data = postman.get('meta_data', {})
4656
meta_data['cookies'] = cookies
4757
postman['meta_data'] = meta_data
48-
jm_debug('plugin.login', '登录成功')
58+
self.debug('登录成功')
4959

5060

5161
class UsageLogPlugin(JmOptionPlugin):
@@ -119,7 +129,7 @@ def warning():
119129
if len(warning_msg_list) != 0:
120130
warning_msg_list.insert(0, '硬件占用告警,占用过高可能导致系统卡死!')
121131
warning_msg_list.append('')
122-
jm_debug('plugin.psutil.warning', '\n'.join(warning_msg_list))
132+
self.debug('\n'.join(warning_msg_list), topic='warning')
123133

124134
while True:
125135
# 获取CPU占用率(0~100)
@@ -140,7 +150,7 @@ def warning():
140150
# f"发送的字节数: {network_bytes_sent}",
141151
# f"接收的字节数: {network_bytes_received}",
142152
])
143-
jm_debug('plugin.psutil.log', msg)
153+
self.debug(msg, topic='log')
144154

145155
if enable_warning is True:
146156
# 警告
@@ -254,12 +264,13 @@ def zip_photo(self, photo, image_list: list, zip_path: str):
254264
all_filepath = set(map(lambda t: t[0], image_list))
255265

256266
if len(all_filepath) == 0:
257-
jm_debug('plugin.zip.skip', '无下载文件,无需压缩')
267+
self.debug('无下载文件,无需压缩', 'skip')
258268
return
259269

260270
from common import backup_dir_to_zip
261271
backup_dir_to_zip(photo_dir, zip_path, acceptor=lambda f: f in all_filepath)
262-
jm_debug('plugin.zip.finish', f'压缩章节[{photo.photo_id}]成功 → {zip_path}')
272+
self.debug(f'压缩章节[{photo.photo_id}]成功 → {zip_path}', 'finish')
273+
263274
return photo_dir
264275

265276
def zip_album(self, album, photo_dict: dict, zip_path):
@@ -276,7 +287,7 @@ def zip_album(self, album, photo_dict: dict, zip_path):
276287
all_filepath.add(path)
277288

278289
if len(all_filepath) == 0:
279-
jm_debug('plugin.zip.skip', '无下载文件,无需压缩')
290+
self.debug('无下载文件,无需压缩', 'skip')
280291
return
281292

282293
from common import backup_dir_to_zip
@@ -286,7 +297,7 @@ def zip_album(self, album, photo_dict: dict, zip_path):
286297
acceptor=lambda f: f in all_filepath
287298
)
288299

289-
jm_debug('plugin.zip.finish', f'压缩本子[{album.album_id}]成功 → {zip_path}')
300+
self.debug(f'压缩本子[{album.album_id}]成功 → {zip_path}', 'finish')
290301
return album_dir
291302

292303
def after_zip(self, dir_zip_dict: Dict[str, str]):
@@ -319,12 +330,12 @@ def delete_all_files_and_empty_dir(self, all_downloaded: dict, dir_list: List[st
319330
for photo, image_list in photo_dict.items():
320331
for f, image in image_list:
321332
os.remove(f)
322-
jm_debug('plugin.zip.remove', f'删除原文件: {f}')
333+
self.debug(f'删除原文件: {f}', 'remove')
323334

324335
for d in dir_list:
325336
if len(os.listdir(d)) == 0:
326337
os.removedirs(d)
327-
jm_debug('plugin.zip.remove', f'删除文件夹: {d}')
338+
self.debug(f'删除文件夹: {d}', 'remove')
328339

329340

330341
class ClientProxyPlugin(JmOptionPlugin):
@@ -338,7 +349,7 @@ def invoke(self,
338349
if whitelist is not None:
339350
whitelist = set(whitelist)
340351

341-
clazz = JmModuleConfig.client_impl_class(proxy_client_key)
352+
proxy_clazz = JmModuleConfig.client_impl_class(proxy_client_key)
342353
clazz_init_kwargs = kwargs
343354
new_jm_client = self.option.new_jm_client
344355

@@ -347,8 +358,8 @@ def hook_new_jm_client(*args, **kwargs):
347358
if whitelist is not None and client.client_key not in whitelist:
348359
return client
349360

350-
jm_debug('plugin.client_proxy', f'proxy client {client} with {proxy_client_key}')
351-
return clazz(client, **clazz_init_kwargs)
361+
self.debug(f'proxy client {client} with {proxy_clazz}')
362+
return proxy_clazz(client, **clazz_init_kwargs)
352363

353364
self.option.new_jm_client = hook_new_jm_client
354365

@@ -368,9 +379,8 @@ def invoke(self,
368379

369380
def apply_filter_then_decide_cache(image: JmImageDetail):
370381
if image.img_file_suffix not in allowed_suffix_set:
371-
jm_debug('image.filter.skip',
372-
f'跳过下载图片: {image.tag},'
373-
f'因为其后缀\'{image.img_file_suffix}\'不在允许的后缀集合{allowed_suffix_set}内')
382+
self.debug(f'跳过下载图片: {image.tag},'
383+
f'因为其后缀\'{image.img_file_suffix}\'不在允许的后缀集合{allowed_suffix_set}内')
374384
# hook is_exists True to skip download
375385
image.is_exists = True
376386
return True
@@ -379,3 +389,25 @@ def apply_filter_then_decide_cache(image: JmImageDetail):
379389
return option_decide_cache(image)
380390

381391
self.option.decide_download_cache = apply_filter_then_decide_cache
392+
393+
394+
class SendQQEmailPlugin(JmOptionPlugin):
395+
plugin_key = 'send_qq_email'
396+
397+
def invoke(self,
398+
msg_from,
399+
msg_to,
400+
password,
401+
title,
402+
content,
403+
album=None,
404+
downloader=None,
405+
) -> None:
406+
if not (msg_from and msg_to and password):
407+
self.debug('发送邮件的相关参数为空,不处理')
408+
return
409+
from common import EmailConfig
410+
econfig = EmailConfig(msg_from, msg_to, password)
411+
epostman = econfig.create_email_postman()
412+
epostman.send(content, title)
413+
self.debug('Email sent successfully')

0 commit comments

Comments
 (0)