jmcomic 2.3.12__py3-none-any.whl → 2.3.15__py3-none-any.whl

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.
jmcomic/__init__.py CHANGED
@@ -2,7 +2,7 @@
2
2
  # 被依赖方 <--- 使用方
3
3
  # config <--- entity <--- toolkit <--- client <--- option <--- downloader
4
4
 
5
- __version__ = '2.3.12'
5
+ __version__ = '2.3.15'
6
6
 
7
7
  from .api import *
8
8
  from .jm_plugin import *
jmcomic/jm_client_impl.py CHANGED
@@ -443,7 +443,7 @@ class JmApiClient(AbstractJmClient):
443
443
 
444
444
  return photo
445
445
 
446
- def get_scramble_id(self, photo_id):
446
+ def get_scramble_id(self, photo_id, album_id=None):
447
447
  """
448
448
  带有缓存的fetch_scramble_id,缓存位于JmModuleConfig.SCRAMBLE_CACHE
449
449
  """
@@ -451,8 +451,14 @@ class JmApiClient(AbstractJmClient):
451
451
  if photo_id in cache:
452
452
  return cache[photo_id]
453
453
 
454
+ if album_id is not None and album_id in cache:
455
+ return cache[album_id]
456
+
454
457
  scramble_id = self.fetch_scramble_id(photo_id)
455
458
  cache[photo_id] = scramble_id
459
+ if album_id is not None:
460
+ cache[album_id] = scramble_id
461
+
456
462
  return scramble_id
457
463
 
458
464
  def fetch_detail_entity(self, apid, clazz):
@@ -511,10 +517,11 @@ class JmApiClient(AbstractJmClient):
511
517
  23做法要改不止一处地方
512
518
  """
513
519
  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)
515
521
 
516
522
  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)
518
525
 
519
526
  def setting(self) -> JmApiResp:
520
527
  """
@@ -577,7 +584,7 @@ class JmApiClient(AbstractJmClient):
577
584
  def get_decode(self, url, **kwargs) -> JmApiResp:
578
585
  # set headers
579
586
  headers, key_ts = self.headers_key_ts
580
- kwargs.setdefault('headers', headers)
587
+ kwargs['headers'] = headers
581
588
 
582
589
  resp = self.get(url, **kwargs)
583
590
  return JmApiResp.wrap(resp, key_ts)
@@ -603,6 +610,16 @@ class JmApiClient(AbstractJmClient):
603
610
  # 2. 是否是特殊的内容
604
611
  # 暂无
605
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
+
606
623
 
607
624
  class FutureClientProxy(JmcomicClient):
608
625
  """
jmcomic/jm_config.py CHANGED
@@ -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
jmcomic/jm_downloader.py CHANGED
@@ -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
 
jmcomic/jm_option.py CHANGED
@@ -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(),
@@ -381,7 +385,7 @@ class JmOption:
381
385
  if is_client_type(JmApiClient):
382
386
  # 移动端
383
387
  # 不配置headers,由client每次请求前创建headers
384
- return {}
388
+ return None
385
389
 
386
390
  if is_client_type(JmHtmlClient):
387
391
  # 网页端
@@ -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
  """
@@ -487,6 +532,10 @@ class JmOption:
487
532
  new_kwargs: Dict[str, Any] = {}
488
533
 
489
534
  for k, v in kwargs.items():
535
+ if isinstance(v, str):
536
+ newv = JmcomicText.parse_dsl_text(v)
537
+ v = newv
538
+
490
539
  if isinstance(k, str):
491
540
  new_kwargs[k] = v
492
541
  continue
jmcomic/jm_plugin.py CHANGED
@@ -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
 
@@ -26,6 +33,22 @@ class JmOptionPlugin:
26
33
  """
27
34
  return cls(option)
28
35
 
36
+ @classmethod
37
+ def debug(cls, msg, topic=None):
38
+ jm_debug(
39
+ topic=f'plugin.{cls.plugin_key}' + (f'.{topic}' if topic is not None else ''),
40
+ msg=msg
41
+ )
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
+
29
52
 
30
53
  class JmLoginPlugin(JmOptionPlugin):
31
54
  """
@@ -33,9 +56,12 @@ class JmLoginPlugin(JmOptionPlugin):
33
56
  """
34
57
  plugin_key = 'login'
35
58
 
36
- def invoke(self, username, password) -> None:
37
- assert isinstance(username, str), '用户名必须是str'
38
- assert isinstance(password, str), '密码必须是str'
59
+ def invoke(self,
60
+ username: str,
61
+ password: str,
62
+ ) -> None:
63
+ self.require_true(username, '用户名不能为空')
64
+ self.require_true(password, '密码不能为空')
39
65
 
40
66
  client = self.option.new_jm_client()
41
67
  client.login(username, password)
@@ -45,7 +71,7 @@ class JmLoginPlugin(JmOptionPlugin):
45
71
  meta_data = postman.get('meta_data', {})
46
72
  meta_data['cookies'] = cookies
47
73
  postman['meta_data'] = meta_data
48
- jm_debug('plugin.login', '登录成功')
74
+ self.debug('登录成功')
49
75
 
50
76
 
51
77
  class UsageLogPlugin(JmOptionPlugin):
@@ -119,7 +145,7 @@ class UsageLogPlugin(JmOptionPlugin):
119
145
  if len(warning_msg_list) != 0:
120
146
  warning_msg_list.insert(0, '硬件占用告警,占用过高可能导致系统卡死!')
121
147
  warning_msg_list.append('')
122
- jm_debug('plugin.psutil.warning', '\n'.join(warning_msg_list))
148
+ self.debug('\n'.join(warning_msg_list), topic='warning')
123
149
 
124
150
  while True:
125
151
  # 获取CPU占用率(0~100)
@@ -140,7 +166,7 @@ class UsageLogPlugin(JmOptionPlugin):
140
166
  # f"发送的字节数: {network_bytes_sent}",
141
167
  # f"接收的字节数: {network_bytes_received}",
142
168
  ])
143
- jm_debug('plugin.psutil.log', msg)
169
+ self.debug(msg, topic='log')
144
170
 
145
171
  if enable_warning is True:
146
172
  # 警告
@@ -254,12 +280,13 @@ class ZipPlugin(JmOptionPlugin):
254
280
  all_filepath = set(map(lambda t: t[0], image_list))
255
281
 
256
282
  if len(all_filepath) == 0:
257
- jm_debug('plugin.zip.skip', '无下载文件,无需压缩')
283
+ self.debug('无下载文件,无需压缩', 'skip')
258
284
  return
259
285
 
260
286
  from common import backup_dir_to_zip
261
287
  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}')
288
+ self.debug(f'压缩章节[{photo.photo_id}]成功 → {zip_path}', 'finish')
289
+
263
290
  return photo_dir
264
291
 
265
292
  def zip_album(self, album, photo_dict: dict, zip_path):
@@ -276,7 +303,7 @@ class ZipPlugin(JmOptionPlugin):
276
303
  all_filepath.add(path)
277
304
 
278
305
  if len(all_filepath) == 0:
279
- jm_debug('plugin.zip.skip', '无下载文件,无需压缩')
306
+ self.debug('无下载文件,无需压缩', 'skip')
280
307
  return
281
308
 
282
309
  from common import backup_dir_to_zip
@@ -286,7 +313,7 @@ class ZipPlugin(JmOptionPlugin):
286
313
  acceptor=lambda f: f in all_filepath
287
314
  )
288
315
 
289
- jm_debug('plugin.zip.finish', f'压缩本子[{album.album_id}]成功 → {zip_path}')
316
+ self.debug(f'压缩本子[{album.album_id}]成功 → {zip_path}', 'finish')
290
317
  return album_dir
291
318
 
292
319
  def after_zip(self, dir_zip_dict: Dict[str, str]):
@@ -315,16 +342,21 @@ class ZipPlugin(JmOptionPlugin):
315
342
  删除所有文件和文件夹
316
343
  """
317
344
  import os
318
- for album, photo_dict in all_downloaded.items():
319
- for photo, image_list in photo_dict.items():
320
- 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
+
321
352
  os.remove(f)
322
- jm_debug('plugin.zip.remove', f'删除原文件: {f}')
353
+ self.debug(f'删除原文件: {f}', 'remove')
323
354
 
324
355
  for d in dir_list:
325
- if len(os.listdir(d)) == 0:
356
+ # check exist
357
+ if file_exists(d) and len(os.listdir(d)) == 0:
326
358
  os.removedirs(d)
327
- jm_debug('plugin.zip.remove', f'删除文件夹: {d}')
359
+ self.debug(f'删除文件夹: {d}', 'remove')
328
360
 
329
361
 
330
362
  class ClientProxyPlugin(JmOptionPlugin):
@@ -338,7 +370,7 @@ class ClientProxyPlugin(JmOptionPlugin):
338
370
  if whitelist is not None:
339
371
  whitelist = set(whitelist)
340
372
 
341
- clazz = JmModuleConfig.client_impl_class(proxy_client_key)
373
+ proxy_clazz = JmModuleConfig.client_impl_class(proxy_client_key)
342
374
  clazz_init_kwargs = kwargs
343
375
  new_jm_client = self.option.new_jm_client
344
376
 
@@ -347,8 +379,8 @@ class ClientProxyPlugin(JmOptionPlugin):
347
379
  if whitelist is not None and client.client_key not in whitelist:
348
380
  return client
349
381
 
350
- jm_debug('plugin.client_proxy', f'proxy client {client} with {proxy_client_key}')
351
- return clazz(client, **clazz_init_kwargs)
382
+ self.debug(f'proxy client {client} with {proxy_clazz}')
383
+ return proxy_clazz(client, **clazz_init_kwargs)
352
384
 
353
385
  self.option.new_jm_client = hook_new_jm_client
354
386
 
@@ -368,9 +400,8 @@ class ImageSuffixFilterPlugin(JmOptionPlugin):
368
400
 
369
401
  def apply_filter_then_decide_cache(image: JmImageDetail):
370
402
  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}内')
403
+ self.debug(f'跳过下载图片: {image.tag},'
404
+ f'因为其后缀\'{image.img_file_suffix}\'不在允许的后缀集合{allowed_suffix_set}内')
374
405
  # hook is_exists True to skip download
375
406
  image.is_exists = True
376
407
  return True
@@ -379,3 +410,43 @@ class ImageSuffixFilterPlugin(JmOptionPlugin):
379
410
  return option_decide_cache(image)
380
411
 
381
412
  self.option.decide_download_cache = apply_filter_then_decide_cache
413
+
414
+
415
+ class SendQQEmailPlugin(JmOptionPlugin):
416
+ plugin_key = 'send_qq_email'
417
+
418
+ def invoke(self,
419
+ msg_from,
420
+ msg_to,
421
+ password,
422
+ title,
423
+ content,
424
+ album=None,
425
+ downloader=None,
426
+ ) -> None:
427
+ self.require_true(msg_from and msg_to and password, '发件人、收件人、授权码都不能为空')
428
+
429
+ from common import EmailConfig
430
+ econfig = EmailConfig(msg_from, msg_to, password)
431
+ epostman = econfig.create_email_postman()
432
+ epostman.send(content, title)
433
+
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
jmcomic/jm_toolkit.py CHANGED
@@ -203,14 +203,17 @@ class JmcomicText:
203
203
  name = match[1]
204
204
  value = os.getenv(name, None)
205
205
  assert value is not None, f"未配置环境变量: {name}"
206
- return os.path.abspath(value)
206
+ return value
207
207
 
208
208
  dsl_replacer = DSLReplacer()
209
209
 
210
210
  @classmethod
211
211
  def parse_to_abspath(cls, dsl_text: str) -> str:
212
- path = cls.dsl_replacer.parse_dsl_text(dsl_text)
213
- return os.path.abspath(path)
212
+ return os.path.abspath(cls.parse_dsl_text(dsl_text))
213
+
214
+ @classmethod
215
+ def parse_dsl_text(cls, dsl_text: str) -> str:
216
+ return cls.dsl_replacer.parse_dsl_text(dsl_text)
214
217
 
215
218
 
216
219
  # 支持dsl: #{???} -> os.getenv(???)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: jmcomic
3
- Version: 2.3.12
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
- - 目前内置支持的插件有:`登录插件` `硬件占用监控插件` `只下载新章插件` `压缩文件插件` `下载特定后缀图片插件`
83
+ - 目前内置支持的插件有:`登录插件` `硬件占用监控插件` `只下载新章插件` `压缩文件插件` `下载特定后缀图片插件` `发送QQ邮件插件` `日志主题过滤插件`
84
84
  - 支持自定义本子/章节/图片下载前后的回调函数
85
- - 支持自定义debug日志
85
+ - 支持自定义debug/logging
86
86
  - 支持自定义类:`Downloader(负责调度)` `Option(负责配置)` `Client(负责请求)` `实体类`等
87
87
 
88
88
  ## 进阶使用
@@ -91,7 +91,7 @@ $ jmcomic 422866
91
91
 
92
92
  下面列出一些常用的文档链接:
93
93
 
94
- * [option配置文件语法](./assets/docs/sources/option_file_syntax.md)
94
+ * [option配置文件语法(包含插件配置)](./assets/docs/sources/option_file_syntax.md)
95
95
  * [常用类和方法演示(下载本子、获取实体类、搜索本子)](assets/docs/sources/tutorial/3_demo.md)
96
96
  * [命令行使用教程](assets/docs/sources/tutorial/2_command_line.md)
97
97
  * [GitHub Actions使用教程](./assets/docs/sources/tutorial/1_github_actions.md)
@@ -0,0 +1,17 @@
1
+ jmcomic/__init__.py,sha256=us33H3aqW0Hlu7igzVxvRBtQ2S_8fwX92WfPTPnVUQ4,879
2
+ jmcomic/api.py,sha256=wzHtdI3AoB1NOKbx7hlcRD_KSUu_CexHKXS0pOLTAsI,2497
3
+ jmcomic/cl.py,sha256=ArDxQvt4G2YungPUqYHG5zWkUxQh71nY-T_IS2jzIV8,3489
4
+ jmcomic/jm_client_impl.py,sha256=ojNeh4BKRfrxxkWS1_VQr8ZpaknWRADGXPGWyQM2O1s,25068
5
+ jmcomic/jm_client_interface.py,sha256=r-y81BqiQSj74dwjnpa22uHbaYPVAENG6QOegx9g-F0,13087
6
+ jmcomic/jm_config.py,sha256=qL12dheI3iraHpw-SejweqTk7q5zpp-y0btOKXvA9I8,13422
7
+ jmcomic/jm_downloader.py,sha256=QcvBzTWAwGuZ9b10R5qYnXE_rjEIJvobsvNF5G5ZqXU,7207
8
+ jmcomic/jm_entity.py,sha256=6N5EBAWIl-rbVAak8HuGeyNB6KPM_kQ6ekPKfZht4c0,15082
9
+ jmcomic/jm_option.py,sha256=uNq6cjUkPXiAViEbdBW9XmvrFkNuE8X3bhpUJ-dVx70,18659
10
+ jmcomic/jm_plugin.py,sha256=VEfGu3xyj415dHR4VUVf-43O24S42Ki33dtdSzWGuvY,14223
11
+ jmcomic/jm_toolkit.py,sha256=K4ip55Di7ZZDv_Bnj-NvaOSKoRHaCmrMrXliD2vSJhw,21850
12
+ jmcomic-2.3.15.dist-info/LICENSE,sha256=kz4coTxZxuGxisK3W00tjK57Zh3RcMGq-EnbXrK7-xA,1064
13
+ jmcomic-2.3.15.dist-info/METADATA,sha256=R3FlUuJYhRF87TxmDMwkqAWgV5N_K363zBX9N_KQEE8,4944
14
+ jmcomic-2.3.15.dist-info/WHEEL,sha256=Xo9-1PvkuimrydujYJAjF7pCkriuXBpUPEjma1nZyJ0,92
15
+ jmcomic-2.3.15.dist-info/entry_points.txt,sha256=tRbQltaGSBjejI0c9jYt-4SXQMd5nSDHcMvHmuTy4ow,44
16
+ jmcomic-2.3.15.dist-info/top_level.txt,sha256=puvVMFYJqIbd6NOTMEvOyugMTT8woBfSQyxEBan3zY4,8
17
+ jmcomic-2.3.15.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.41.2)
2
+ Generator: bdist_wheel (0.41.3)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,17 +0,0 @@
1
- jmcomic/__init__.py,sha256=RqP2tbOGOniJQ-4D6kKbReCJLIrQJP8FKlNxbs-6Ffo,879
2
- jmcomic/api.py,sha256=wzHtdI3AoB1NOKbx7hlcRD_KSUu_CexHKXS0pOLTAsI,2497
3
- jmcomic/cl.py,sha256=ArDxQvt4G2YungPUqYHG5zWkUxQh71nY-T_IS2jzIV8,3489
4
- jmcomic/jm_client_impl.py,sha256=b6B_3ZLOC2Z1aLsKlYnDsjlID2vtX2YY3IPOTRB_oxE,24501
5
- jmcomic/jm_client_interface.py,sha256=r-y81BqiQSj74dwjnpa22uHbaYPVAENG6QOegx9g-F0,13087
6
- jmcomic/jm_config.py,sha256=Wl9cfMiEoJszVd_SEIaxlux9N8gHfYQt1V0jHq6cJy8,13197
7
- jmcomic/jm_downloader.py,sha256=TV_NhM4JEe210rAGypvoL181O4y5ejiiFSg-q38HBO0,7256
8
- jmcomic/jm_entity.py,sha256=6N5EBAWIl-rbVAak8HuGeyNB6KPM_kQ6ekPKfZht4c0,15082
9
- jmcomic/jm_option.py,sha256=Q2lG45kMMFzprsOfPUGjt_87mU42NFRQ0w_EEJaVd8c,16860
10
- jmcomic/jm_plugin.py,sha256=yi272Buhz-9T9HLwQ4LRXfXvFHDzjgVdFHS0mIXl-ss,12473
11
- jmcomic/jm_toolkit.py,sha256=tNSwKEMb3eAP__c3ZDCuj3CENF7ma-l99_PePidutdE,21774
12
- jmcomic-2.3.12.dist-info/LICENSE,sha256=kz4coTxZxuGxisK3W00tjK57Zh3RcMGq-EnbXrK7-xA,1064
13
- jmcomic-2.3.12.dist-info/METADATA,sha256=hx4M2tw8XV8jtjG2TalzQlMBZR1LOKr6e3vzbEyNULg,4856
14
- jmcomic-2.3.12.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
15
- jmcomic-2.3.12.dist-info/entry_points.txt,sha256=tRbQltaGSBjejI0c9jYt-4SXQMd5nSDHcMvHmuTy4ow,44
16
- jmcomic-2.3.12.dist-info/top_level.txt,sha256=puvVMFYJqIbd6NOTMEvOyugMTT8woBfSQyxEBan3zY4,8
17
- jmcomic-2.3.12.dist-info/RECORD,,