jmcomic 2.3.14__tar.gz → 2.3.15__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (22) hide show
  1. {jmcomic-2.3.14/src/jmcomic.egg-info → jmcomic-2.3.15}/PKG-INFO +4 -4
  2. {jmcomic-2.3.14 → jmcomic-2.3.15}/README.md +3 -3
  3. {jmcomic-2.3.14 → jmcomic-2.3.15}/src/jmcomic/__init__.py +1 -1
  4. {jmcomic-2.3.14 → jmcomic-2.3.15}/src/jmcomic/jm_client_impl.py +11 -1
  5. {jmcomic-2.3.14 → jmcomic-2.3.15}/src/jmcomic/jm_config.py +7 -3
  6. {jmcomic-2.3.14 → jmcomic-2.3.15}/src/jmcomic/jm_downloader.py +0 -4
  7. {jmcomic-2.3.14 → jmcomic-2.3.15}/src/jmcomic/jm_option.py +66 -21
  8. {jmcomic-2.3.14 → jmcomic-2.3.15}/src/jmcomic/jm_plugin.py +48 -9
  9. {jmcomic-2.3.14 → jmcomic-2.3.15/src/jmcomic.egg-info}/PKG-INFO +4 -4
  10. {jmcomic-2.3.14 → jmcomic-2.3.15}/LICENSE +0 -0
  11. {jmcomic-2.3.14 → jmcomic-2.3.15}/setup.cfg +0 -0
  12. {jmcomic-2.3.14 → jmcomic-2.3.15}/setup.py +0 -0
  13. {jmcomic-2.3.14 → jmcomic-2.3.15}/src/jmcomic/api.py +0 -0
  14. {jmcomic-2.3.14 → jmcomic-2.3.15}/src/jmcomic/cl.py +0 -0
  15. {jmcomic-2.3.14 → jmcomic-2.3.15}/src/jmcomic/jm_client_interface.py +0 -0
  16. {jmcomic-2.3.14 → jmcomic-2.3.15}/src/jmcomic/jm_entity.py +0 -0
  17. {jmcomic-2.3.14 → jmcomic-2.3.15}/src/jmcomic/jm_toolkit.py +0 -0
  18. {jmcomic-2.3.14 → jmcomic-2.3.15}/src/jmcomic.egg-info/SOURCES.txt +0 -0
  19. {jmcomic-2.3.14 → jmcomic-2.3.15}/src/jmcomic.egg-info/dependency_links.txt +0 -0
  20. {jmcomic-2.3.14 → jmcomic-2.3.15}/src/jmcomic.egg-info/entry_points.txt +0 -0
  21. {jmcomic-2.3.14 → jmcomic-2.3.15}/src/jmcomic.egg-info/requires.txt +0 -0
  22. {jmcomic-2.3.14 → jmcomic-2.3.15}/src/jmcomic.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: jmcomic
3
- Version: 2.3.14
3
+ Version: 2.3.15
4
4
  Summary: Python API For JMComic (禁漫天堂)
5
5
  Home-page: https://github.com/hect0x7/JMComic-Crawler-Python
6
6
  Author: hect0x7
@@ -68,7 +68,7 @@ $ jmcomic 422866
68
68
 
69
69
  - GitHub Actions:网页上直接输入本子id就能下载([教程:使用GitHub Actions下载禁漫本子](./assets/docs/sources/tutorial/1_github_actions.md))
70
70
  - 命令行:无需写Python代码,简单易用([教程:使用命令行下载禁漫本子](./assets/docs/sources/tutorial/2_command_line.md))
71
- - Python代码:最直接的使用方式,需要你有一定的python编程基础
71
+ - Python代码:最本质、最强大的使用方式,需要你有一定的python编程基础
72
72
  - 支持**网页端**和**移动端**两种客户端实现,可通过配置切换(**移动端不限ip兼容性好,网页端限制ip地区但效率高**)
73
73
  - 支持**自动重试和域名切换**机制
74
74
  - **多线程下载**(可细化到一图一线程,效率极高)
@@ -80,9 +80,9 @@ $ jmcomic 422866
80
80
  - **可扩展性强**
81
81
 
82
82
  - **支持Plugin插件,可以方便地扩展功能,以及使用别人的插件**
83
- - 目前内置支持的插件有:`登录插件` `硬件占用监控插件` `只下载新章插件` `压缩文件插件` `下载特定后缀图片插件` `发送QQ邮件插件`
83
+ - 目前内置支持的插件有:`登录插件` `硬件占用监控插件` `只下载新章插件` `压缩文件插件` `下载特定后缀图片插件` `发送QQ邮件插件` `日志主题过滤插件`
84
84
  - 支持自定义本子/章节/图片下载前后的回调函数
85
- - 支持自定义debug日志
85
+ - 支持自定义debug/logging
86
86
  - 支持自定义类:`Downloader(负责调度)` `Option(负责配置)` `Client(负责请求)` `实体类`等
87
87
 
88
88
  ## 进阶使用
@@ -42,7 +42,7 @@ $ jmcomic 422866
42
42
 
43
43
  - GitHub Actions:网页上直接输入本子id就能下载([教程:使用GitHub Actions下载禁漫本子](./assets/docs/sources/tutorial/1_github_actions.md))
44
44
  - 命令行:无需写Python代码,简单易用([教程:使用命令行下载禁漫本子](./assets/docs/sources/tutorial/2_command_line.md))
45
- - Python代码:最直接的使用方式,需要你有一定的python编程基础
45
+ - Python代码:最本质、最强大的使用方式,需要你有一定的python编程基础
46
46
  - 支持**网页端**和**移动端**两种客户端实现,可通过配置切换(**移动端不限ip兼容性好,网页端限制ip地区但效率高**)
47
47
  - 支持**自动重试和域名切换**机制
48
48
  - **多线程下载**(可细化到一图一线程,效率极高)
@@ -54,9 +54,9 @@ $ jmcomic 422866
54
54
  - **可扩展性强**
55
55
 
56
56
  - **支持Plugin插件,可以方便地扩展功能,以及使用别人的插件**
57
- - 目前内置支持的插件有:`登录插件` `硬件占用监控插件` `只下载新章插件` `压缩文件插件` `下载特定后缀图片插件` `发送QQ邮件插件`
57
+ - 目前内置支持的插件有:`登录插件` `硬件占用监控插件` `只下载新章插件` `压缩文件插件` `下载特定后缀图片插件` `发送QQ邮件插件` `日志主题过滤插件`
58
58
  - 支持自定义本子/章节/图片下载前后的回调函数
59
- - 支持自定义debug日志
59
+ - 支持自定义debug/logging
60
60
  - 支持自定义类:`Downloader(负责调度)` `Option(负责配置)` `Client(负责请求)` `实体类`等
61
61
 
62
62
  ## 进阶使用
@@ -2,7 +2,7 @@
2
2
  # 被依赖方 <--- 使用方
3
3
  # config <--- entity <--- toolkit <--- client <--- option <--- downloader
4
4
 
5
- __version__ = '2.3.14'
5
+ __version__ = '2.3.15'
6
6
 
7
7
  from .api import *
8
8
  from .jm_plugin import *
@@ -584,7 +584,7 @@ class JmApiClient(AbstractJmClient):
584
584
  def get_decode(self, url, **kwargs) -> JmApiResp:
585
585
  # set headers
586
586
  headers, key_ts = self.headers_key_ts
587
- kwargs.setdefault('headers', headers)
587
+ kwargs['headers'] = headers
588
588
 
589
589
  resp = self.get(url, **kwargs)
590
590
  return JmApiResp.wrap(resp, key_ts)
@@ -610,6 +610,16 @@ class JmApiClient(AbstractJmClient):
610
610
  # 2. 是否是特殊的内容
611
611
  # 暂无
612
612
 
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
+
613
623
 
614
624
  class FutureClientProxy(JmcomicClient):
615
625
  """
@@ -64,7 +64,7 @@ class JmModuleConfig:
64
64
 
65
65
  # 移动端API的相关配置
66
66
  # API密钥
67
- MAGIC_18COMICAPPCONTENT = '18comicAPPContent'
67
+ APP_SECRET = '18comicAPP'
68
68
 
69
69
  # 域名配置 - 移动端
70
70
  # 图片域名
@@ -247,7 +247,7 @@ class JmModuleConfig:
247
247
  key_ts = time_stamp()
248
248
 
249
249
  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()
251
251
 
252
252
  return {
253
253
  'token': token,
@@ -331,7 +331,11 @@ class JmModuleConfig:
331
331
  'impl': None,
332
332
  'retry_times': 5
333
333
  },
334
- 'plugins': {},
334
+ 'plugins': {
335
+ # 如果插件抛出参数校验异常,只debug。(全局配置,可以被插件的局部配置覆盖)
336
+ # 可选值:ignore(忽略),debug(打印日志),raise(抛异常)。
337
+ 'valid': 'debug',
338
+ },
335
339
  }
336
340
 
337
341
  @classmethod
@@ -9,10 +9,6 @@ DownloadIterObjs = Union[
9
9
  ]
10
10
 
11
11
 
12
- class JmDownloadException(Exception):
13
- pass
14
-
15
-
16
12
  # noinspection PyMethodMayBeStatic
17
13
  class DownloadCallback:
18
14
 
@@ -311,12 +311,12 @@ class JmOption:
311
311
  """
312
312
  return self.new_jm_client(**kwargs)
313
313
 
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:
315
315
  # 所有需要用到的 self.client 配置项如下
316
316
  postman_conf: dict = self.client.postman.src_dict # postman dsl 配置
317
317
  impl: str = impl or self.client.impl # client_key
318
318
  retry_times: int = self.client.retry_times # 重试次数
319
- cache: str = self.client.cache # 启用缓存
319
+ cache: str = cache or self.client.cache # 启用缓存
320
320
 
321
321
  # domain
322
322
  def decide_domain():
@@ -340,7 +340,10 @@ class JmOption:
340
340
  # headers
341
341
  meta_data = postman_conf['meta_data']
342
342
  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
344
347
 
345
348
  # postman
346
349
  postman = Postmans.create(data=postman_conf)
@@ -349,7 +352,8 @@ class JmOption:
349
352
  clazz = JmModuleConfig.client_impl_class(impl)
350
353
  if clazz == AbstractJmClient or not issubclass(clazz, AbstractJmClient):
351
354
  raise NotImplementedError(clazz)
352
- client = clazz(
355
+
356
+ client: AbstractJmClient = clazz(
353
357
  postman,
354
358
  retry_times,
355
359
  fallback_domain_list=decide_domain(),
@@ -442,36 +446,77 @@ class JmOption:
442
446
 
443
447
  ExceptionTool.require_true(plugin_class is not None, f'[{group}] 未注册的plugin: {key}')
444
448
 
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)
446
457
 
447
- def invoke_plugin(self, plugin_class, kwargs: Any, extra: dict):
448
458
  # 保证 jm_plugin.py 被加载
449
- from .jm_plugin import JmOptionPlugin
459
+ from .jm_plugin import JmOptionPlugin, PluginValidationException
450
460
 
461
+ plugin = plugin_class
451
462
  plugin_class: Type[JmOptionPlugin]
452
- pkey = plugin_class.plugin_key
453
463
 
454
464
  try:
455
- # 检查插件的参数类型
456
- kwargs = self.fix_kwargs(kwargs)
457
- # 把插件的配置数据kwargs和附加数据extra合并
458
- # extra会覆盖kwargs
459
- if len(extra) != 0:
460
- kwargs.update(extra)
461
465
  # 构建插件对象
462
- plugin = plugin_class.build(self)
466
+ plugin: JmOptionPlugin = plugin_class.build(self)
467
+
468
+ jm_debug('plugin.invoke', f'调用插件: [{plugin_class.plugin_key}]')
463
469
  # 调用插件功能
464
- jm_debug('plugin.invoke', f'调用插件: [{pkey}]')
465
470
  plugin.invoke(**kwargs)
471
+
472
+ except PluginValidationException as e:
473
+ # 插件抛出的参数校验异常
474
+ self.handle_plugin_valid_exception(e, pinfo, kwargs, plugin)
475
+
466
476
  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
+
470
480
  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
473
504
  raise e
474
505
 
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
+
475
520
  # noinspection PyMethodMayBeStatic
476
521
  def fix_kwargs(self, kwargs) -> Dict[str, Any]:
477
522
  """
@@ -5,6 +5,13 @@
5
5
  from .jm_option import *
6
6
 
7
7
 
8
+ class PluginValidationException(Exception):
9
+
10
+ def __init__(self, plugin: 'JmOptionPlugin', msg: str):
11
+ self.plugin = plugin
12
+ self.msg = msg
13
+
14
+
8
15
  class JmOptionPlugin:
9
16
  plugin_key: str
10
17
 
@@ -33,6 +40,15 @@ class JmOptionPlugin:
33
40
  msg=msg
34
41
  )
35
42
 
43
+ def require_true(self, case: Any, msg: str):
44
+ """
45
+ 独立于ExceptionTool的一套异常抛出体系
46
+ """
47
+ if case:
48
+ return
49
+
50
+ raise PluginValidationException(self, msg)
51
+
36
52
 
37
53
  class JmLoginPlugin(JmOptionPlugin):
38
54
  """
@@ -44,8 +60,8 @@ class JmLoginPlugin(JmOptionPlugin):
44
60
  username: str,
45
61
  password: str,
46
62
  ) -> None:
47
- if not (username and password):
48
- return
63
+ self.require_true(username, '用户名不能为空')
64
+ self.require_true(password, '密码不能为空')
49
65
 
50
66
  client = self.option.new_jm_client()
51
67
  client.login(username, password)
@@ -326,14 +342,19 @@ class ZipPlugin(JmOptionPlugin):
326
342
  删除所有文件和文件夹
327
343
  """
328
344
  import os
329
- for album, photo_dict in all_downloaded.items():
330
- for photo, image_list in photo_dict.items():
331
- for f, image in image_list:
345
+ for photo_dict in all_downloaded.values():
346
+ for image_list in photo_dict.values():
347
+ for f, _ in image_list:
348
+ # check not exist
349
+ if file_not_exists(f):
350
+ continue
351
+
332
352
  os.remove(f)
333
353
  self.debug(f'删除原文件: {f}', 'remove')
334
354
 
335
355
  for d in dir_list:
336
- if len(os.listdir(d)) == 0:
356
+ # check exist
357
+ if file_exists(d) and len(os.listdir(d)) == 0:
337
358
  os.removedirs(d)
338
359
  self.debug(f'删除文件夹: {d}', 'remove')
339
360
 
@@ -403,11 +424,29 @@ class SendQQEmailPlugin(JmOptionPlugin):
403
424
  album=None,
404
425
  downloader=None,
405
426
  ) -> None:
406
- if not (msg_from and msg_to and password):
407
- self.debug('发送邮件的相关参数为空,不处理')
408
- return
427
+ self.require_true(msg_from and msg_to and password, '发件人、收件人、授权码都不能为空')
428
+
409
429
  from common import EmailConfig
410
430
  econfig = EmailConfig(msg_from, msg_to, password)
411
431
  epostman = econfig.create_email_postman()
412
432
  epostman.send(content, title)
433
+
413
434
  self.debug('Email sent successfully')
435
+
436
+
437
+ class DebugTopicFilterPlugin(JmOptionPlugin):
438
+ plugin_key = 'debug_topic_filter'
439
+
440
+ def invoke(self, whitelist) -> None:
441
+ if whitelist is not None:
442
+ whitelist = set(whitelist)
443
+
444
+ old_jm_debug = JmModuleConfig.debug_executor
445
+
446
+ def new_jm_debug(topic, msg):
447
+ if whitelist is not None and topic not in whitelist:
448
+ return
449
+
450
+ old_jm_debug(topic, msg)
451
+
452
+ JmModuleConfig.debug_executor = new_jm_debug
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: jmcomic
3
- Version: 2.3.14
3
+ Version: 2.3.15
4
4
  Summary: Python API For JMComic (禁漫天堂)
5
5
  Home-page: https://github.com/hect0x7/JMComic-Crawler-Python
6
6
  Author: hect0x7
@@ -68,7 +68,7 @@ $ jmcomic 422866
68
68
 
69
69
  - GitHub Actions:网页上直接输入本子id就能下载([教程:使用GitHub Actions下载禁漫本子](./assets/docs/sources/tutorial/1_github_actions.md))
70
70
  - 命令行:无需写Python代码,简单易用([教程:使用命令行下载禁漫本子](./assets/docs/sources/tutorial/2_command_line.md))
71
- - Python代码:最直接的使用方式,需要你有一定的python编程基础
71
+ - Python代码:最本质、最强大的使用方式,需要你有一定的python编程基础
72
72
  - 支持**网页端**和**移动端**两种客户端实现,可通过配置切换(**移动端不限ip兼容性好,网页端限制ip地区但效率高**)
73
73
  - 支持**自动重试和域名切换**机制
74
74
  - **多线程下载**(可细化到一图一线程,效率极高)
@@ -80,9 +80,9 @@ $ jmcomic 422866
80
80
  - **可扩展性强**
81
81
 
82
82
  - **支持Plugin插件,可以方便地扩展功能,以及使用别人的插件**
83
- - 目前内置支持的插件有:`登录插件` `硬件占用监控插件` `只下载新章插件` `压缩文件插件` `下载特定后缀图片插件` `发送QQ邮件插件`
83
+ - 目前内置支持的插件有:`登录插件` `硬件占用监控插件` `只下载新章插件` `压缩文件插件` `下载特定后缀图片插件` `发送QQ邮件插件` `日志主题过滤插件`
84
84
  - 支持自定义本子/章节/图片下载前后的回调函数
85
- - 支持自定义debug日志
85
+ - 支持自定义debug/logging
86
86
  - 支持自定义类:`Downloader(负责调度)` `Option(负责配置)` `Client(负责请求)` `实体类`等
87
87
 
88
88
  ## 进阶使用
File without changes
File without changes
File without changes
File without changes
File without changes