Skip to content

Commit 87ed6bc

Browse files
committed
v2.3.15: 修复移动端未携带cookies导致跳转同本问题; 新增插件【debug日志主题过滤器】; 新增插件参数校验异常类及其相应的异常处理策略 (ignore, debug, raise)。
1 parent de0e0ba commit 87ed6bc

File tree

11 files changed

+174
-54
lines changed

11 files changed

+174
-54
lines changed

.github/release.py

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,42 @@
44

55

66
def add_output(k, v):
7-
cmd = f'echo {k}="{v}" >> $GITHUB_OUTPUT'
7+
cmd = f'echo "{k}={v}" >> $GITHUB_OUTPUT'
88
print(cmd, os.system(cmd))
99

1010

11-
msg = sys.argv[1]
12-
print(f'msg: {msg}')
13-
p = re.compile('(.*?): ?(.*)')
14-
match = p.search(msg)
15-
assert match is not None, f'commit message format is wrong: {msg}'
11+
def parse_body(body):
12+
if ';' not in body:
13+
return body
1614

17-
tag, body = match[1], match[2]
15+
parts = body.split(";")
16+
points = []
17+
for i, e in enumerate(parts):
18+
e: str = e.strip()
19+
if e == '':
20+
continue
21+
points.append(f'{i}. {e}')
1822

19-
add_output('tag', tag)
20-
add_output('body', body)
23+
return '\n'.join(points)
24+
25+
26+
def get_tag_and_body():
27+
msg = sys.argv[1]
28+
print(f'msg: {msg}')
29+
p = re.compile('(.*?): ?(.*)')
30+
match = p.search(msg)
31+
assert match is not None, f'commit message format is wrong: {msg}'
32+
tag, body = match[1], match[2]
33+
return body, tag
34+
35+
36+
def main():
37+
body, tag = get_tag_and_body()
38+
39+
add_output('tag', tag)
40+
41+
with open('release_body.txt', 'w', encoding='utf-8') as f:
42+
f.write(parse_body(body))
43+
44+
45+
main()

.github/workflows/release_auto.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ jobs:
3131
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
3232
with:
3333
tag_name: ${{ steps.tb.outputs.tag }}
34-
body: ${{ steps.tb.outputs.body }}
34+
body_path: release_body.txt
3535

3636
- name: Build
3737
run: |

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ $ jmcomic 422866
4242

4343
- GitHub Actions:网页上直接输入本子id就能下载([教程:使用GitHub Actions下载禁漫本子](./assets/docs/sources/tutorial/1_github_actions.md)
4444
- 命令行:无需写Python代码,简单易用([教程:使用命令行下载禁漫本子](./assets/docs/sources/tutorial/2_command_line.md)
45-
- Python代码:最直接的使用方式,需要你有一定的python编程基础
45+
- Python代码:最本质、最强大的使用方式,需要你有一定的python编程基础
4646
- 支持**网页端****移动端**两种客户端实现,可通过配置切换(**移动端不限ip兼容性好,网页端限制ip地区但效率高**
4747
- 支持**自动重试和域名切换**机制
4848
- **多线程下载**(可细化到一图一线程,效率极高)
@@ -54,9 +54,9 @@ $ jmcomic 422866
5454
- **可扩展性强**
5555

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

6262
## 进阶使用

assets/docs/sources/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Python API for JMComic(禁漫天堂)
2626

2727
- Supports Plugin plugins for easy functionality extension and use of other plugins.
2828
- 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`.
29+
plugins: `login plugin`, `hardware usage monitoring plugin`, `only download new chapters plugin`, `zip compression plugin`, `image suffix filter plugin` `send qq email plugin` `debug logging topic filter plugin`.
3030
- Supports custom callback functions before and after downloading album/chapter/images.
3131
- Supports custom debug logging.
3232
- Supports custom core

requirements-dev.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ curl_cffi
33
PyYAML
44
Pillow
55
psutil
6-
pycryptodome
6+
pycryptodome
7+
requests

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.14'
5+
__version__ = '2.3.15'
66

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

src/jmcomic/jm_client_impl.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -584,7 +584,7 @@ def login(self,
584584
def get_decode(self, url, **kwargs) -> JmApiResp:
585585
# set headers
586586
headers, key_ts = self.headers_key_ts
587-
kwargs.setdefault('headers', headers)
587+
kwargs['headers'] = headers
588588

589589
resp = self.get(url, **kwargs)
590590
return JmApiResp.wrap(resp, key_ts)
@@ -610,6 +610,16 @@ def require_resp_success(cls, resp: JmApiResp, orig_req_url: str):
610610
# 2. 是否是特殊的内容
611611
# 暂无
612612

613+
@classmethod
614+
@field_cache('__init_cookies__')
615+
def fetch_init_cookies(cls, client: 'JmApiClient'):
616+
resp = client.setting()
617+
return dict(resp.resp.cookies)
618+
619+
def after_init(self):
620+
cookies = self.__class__.fetch_init_cookies(self)
621+
self.get_root_postman().get_meta_data()['cookies'] = cookies
622+
613623

614624
class FutureClientProxy(JmcomicClient):
615625
"""

src/jmcomic/jm_config.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ class JmModuleConfig:
6464

6565
# 移动端API的相关配置
6666
# API密钥
67-
MAGIC_18COMICAPPCONTENT = '18comicAPPContent'
67+
APP_SECRET = '18comicAPP'
6868

6969
# 域名配置 - 移动端
7070
# 图片域名
@@ -247,7 +247,7 @@ def new_api_headers(cls, key_ts):
247247
key_ts = time_stamp()
248248

249249
import hashlib
250-
token = hashlib.md5(f"{key_ts}{cls.MAGIC_18COMICAPPCONTENT}".encode()).hexdigest()
250+
token = hashlib.md5(f"{key_ts}{cls.APP_SECRET}".encode()).hexdigest()
251251

252252
return {
253253
'token': token,
@@ -331,7 +331,11 @@ def new_postman(cls, session=False, **kwargs):
331331
'impl': None,
332332
'retry_times': 5
333333
},
334-
'plugins': {},
334+
'plugins': {
335+
# 如果插件抛出参数校验异常,只debug。(全局配置,可以被插件的局部配置覆盖)
336+
# 可选值:ignore(忽略),debug(打印日志),raise(抛异常)。
337+
'valid': 'debug',
338+
},
335339
}
336340

337341
@classmethod

src/jmcomic/jm_downloader.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,6 @@
99
]
1010

1111

12-
class JmDownloadException(Exception):
13-
pass
14-
15-
1612
# noinspection PyMethodMayBeStatic
1713
class DownloadCallback:
1814

src/jmcomic/jm_option.py

Lines changed: 66 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -311,12 +311,12 @@ def build_jm_client(self, **kwargs):
311311
"""
312312
return self.new_jm_client(**kwargs)
313313

314-
def new_jm_client(self, domain=None, impl=None, **kwargs) -> JmcomicClient:
314+
def new_jm_client(self, domain=None, impl=None, cache=None, **kwargs) -> JmcomicClient:
315315
# 所有需要用到的 self.client 配置项如下
316316
postman_conf: dict = self.client.postman.src_dict # postman dsl 配置
317317
impl: str = impl or self.client.impl # client_key
318318
retry_times: int = self.client.retry_times # 重试次数
319-
cache: str = self.client.cache # 启用缓存
319+
cache: str = cache or self.client.cache # 启用缓存
320320

321321
# domain
322322
def decide_domain():
@@ -340,7 +340,10 @@ def decide_domain():
340340
# headers
341341
meta_data = postman_conf['meta_data']
342342
if meta_data['headers'] is None:
343-
meta_data['headers'] = self.decide_postman_headers(impl, domain[0])
343+
headers = self.decide_postman_headers(impl, domain[0])
344+
# if headers is None:
345+
# postman_conf['type'] = 'requests'
346+
meta_data['headers'] = headers
344347

345348
# postman
346349
postman = Postmans.create(data=postman_conf)
@@ -349,7 +352,8 @@ def decide_domain():
349352
clazz = JmModuleConfig.client_impl_class(impl)
350353
if clazz == AbstractJmClient or not issubclass(clazz, AbstractJmClient):
351354
raise NotImplementedError(clazz)
352-
client = clazz(
355+
356+
client: AbstractJmClient = clazz(
353357
postman,
354358
retry_times,
355359
fallback_domain_list=decide_domain(),
@@ -442,36 +446,77 @@ def call_all_plugin(self, group: str, **extra):
442446

443447
ExceptionTool.require_true(plugin_class is not None, f'[{group}] 未注册的plugin: {key}')
444448

445-
self.invoke_plugin(plugin_class, kwargs, extra)
449+
self.invoke_plugin(plugin_class, kwargs, extra, pinfo)
450+
451+
def invoke_plugin(self, plugin_class, kwargs: Any, extra: dict, pinfo: dict):
452+
# 检查插件的参数类型
453+
kwargs = self.fix_kwargs(kwargs)
454+
# 把插件的配置数据kwargs和附加数据extra合并,extra会覆盖kwargs
455+
if len(extra) != 0:
456+
kwargs.update(extra)
446457

447-
def invoke_plugin(self, plugin_class, kwargs: Any, extra: dict):
448458
# 保证 jm_plugin.py 被加载
449-
from .jm_plugin import JmOptionPlugin
459+
from .jm_plugin import JmOptionPlugin, PluginValidationException
450460

461+
plugin = plugin_class
451462
plugin_class: Type[JmOptionPlugin]
452-
pkey = plugin_class.plugin_key
453463

454464
try:
455-
# 检查插件的参数类型
456-
kwargs = self.fix_kwargs(kwargs)
457-
# 把插件的配置数据kwargs和附加数据extra合并
458-
# extra会覆盖kwargs
459-
if len(extra) != 0:
460-
kwargs.update(extra)
461465
# 构建插件对象
462-
plugin = plugin_class.build(self)
466+
plugin: JmOptionPlugin = plugin_class.build(self)
467+
468+
jm_debug('plugin.invoke', f'调用插件: [{plugin_class.plugin_key}]')
463469
# 调用插件功能
464-
jm_debug('plugin.invoke', f'调用插件: [{pkey}]')
465470
plugin.invoke(**kwargs)
471+
472+
except PluginValidationException as e:
473+
# 插件抛出的参数校验异常
474+
self.handle_plugin_valid_exception(e, pinfo, kwargs, plugin)
475+
466476
except JmcomicException as e:
467-
msg = str(e)
468-
jm_debug('plugin.exception', f'插件[{pkey}]调用失败,异常信息: {msg}')
469-
raise e
477+
# 模块内部异常,通过不是插件抛出的,而是插件调用了例如Client,Client请求失败抛出的
478+
self.handle_plugin_exception(e, pinfo, kwargs, plugin)
479+
470480
except BaseException as e:
471-
msg = str(e)
472-
jm_debug('plugin.error', f'插件[{pkey}]运行遇到未捕获异常,异常信息: {msg}')
481+
# 为插件兜底,捕获其他所有异常
482+
self.handle_plugin_unexpected_error(e, pinfo, kwargs, plugin)
483+
484+
# noinspection PyMethodMayBeStatic,PyUnusedLocal
485+
def handle_plugin_valid_exception(self, e, pinfo: dict, kwargs: dict, plugin):
486+
from .jm_plugin import PluginValidationException
487+
e: PluginValidationException
488+
489+
mode = pinfo.get('valid', self.plugins.valid)
490+
491+
if mode == 'ignore':
492+
# ignore
493+
return
494+
495+
if mode == 'debug':
496+
# debug
497+
jm_debug('plugin.validation',
498+
f'插件 [{e.plugin.plugin_key}] 参数校验异常:{e.msg}'
499+
)
500+
return
501+
502+
if mode == 'raise':
503+
# raise
473504
raise e
474505

506+
# 其他的mode可以通过继承+方法重写来扩展
507+
508+
# noinspection PyMethodMayBeStatic,PyUnusedLocal
509+
def handle_plugin_unexpected_error(self, e, pinfo: dict, kwargs: dict, plugin):
510+
msg = str(e)
511+
jm_debug('plugin.error', f'插件 [{plugin.plugin_key}],运行遇到未捕获异常,异常信息: {msg}')
512+
raise e
513+
514+
# noinspection PyMethodMayBeStatic,PyUnusedLocal
515+
def handle_plugin_exception(self, e, pinfo: dict, kwargs: dict, plugin):
516+
msg = str(e)
517+
jm_debug('plugin.exception', f'插件 [{plugin.plugin_key}],调用失败,异常信息: {msg}')
518+
raise e
519+
475520
# noinspection PyMethodMayBeStatic
476521
def fix_kwargs(self, kwargs) -> Dict[str, Any]:
477522
"""

0 commit comments

Comments
 (0)