jmcomic 2.3.14__py3-none-any.whl → 2.3.16__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.14'
5
+ __version__ = '2.3.16'
6
6
 
7
7
  from .api import *
8
8
  from .jm_plugin import *
jmcomic/jm_client_impl.py CHANGED
@@ -493,13 +493,13 @@ class JmApiClient(AbstractJmClient):
493
493
  }
494
494
  )
495
495
 
496
- match = JmcomicText.pattern_html_album_scramble_id.search(resp.text)
497
-
498
- if match is not None:
499
- scramble_id = match[1]
500
- else:
501
- jm_debug('api.scramble', '未从响应中匹配到scramble_id,返回默认值220980')
502
- scramble_id = '220980'
496
+ scramble_id = PatternTool.match_or_default(resp.text,
497
+ JmcomicText.pattern_html_album_scramble_id,
498
+ None,
499
+ )
500
+ if scramble_id is None:
501
+ jm_debug('api.scramble', f'未匹配到scramble_id,响应文本:{resp.text}')
502
+ scramble_id = str(JmModuleConfig.SCRAMBLE_220980)
503
503
 
504
504
  return scramble_id
505
505
 
@@ -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,13 @@ class JmApiClient(AbstractJmClient):
610
610
  # 2. 是否是特殊的内容
611
611
  # 暂无
612
612
 
613
+ def after_init(self):
614
+ # cookies = self.__class__.fetch_init_cookies(self)
615
+ # self.get_root_postman().get_meta_data()['cookies'] = cookies
616
+
617
+ self.get_root_postman().get_meta_data()['cookies'] = JmModuleConfig.get_cookies(self)
618
+ pass
619
+
613
620
 
614
621
  class FutureClientProxy(JmcomicClient):
615
622
  """
jmcomic/jm_config.py CHANGED
@@ -3,7 +3,7 @@ def field_cache(*args, **kwargs):
3
3
  return field_cache(*args, **kwargs)
4
4
 
5
5
 
6
- def default_jm_debug(topic: str, msg: str):
6
+ def default_jm_debug_logging(topic: str, msg: str):
7
7
  from common import format_ts
8
8
  print(f'{format_ts()}:【{topic}】{msg}')
9
9
 
@@ -62,13 +62,14 @@ class JmModuleConfig:
62
62
  SCRAMBLE_421926 = 421926 # 2023-02-08后改了图片切割算法
63
63
  SCRAMBLE_CACHE = {}
64
64
 
65
- # 移动端API的相关配置
66
- # API密钥
67
- MAGIC_18COMICAPPCONTENT = '18comicAPPContent'
65
+ # 移动端API密钥
66
+ APP_SECRET = '18comicAPPContent'
68
67
 
69
- # 域名配置 - 移动端
70
- # 图片域名
71
- DOMAIN_API_IMAGE_LIST = str_to_list('''
68
+ # cookies,目前只在移动端使用,因为移动端请求接口须携带,但不会校验cookies的内容。
69
+ APP_COOKIES = None
70
+
71
+ # 移动端图片域名
72
+ DOMAIN_IMAGE_LIST = str_to_list('''
72
73
  cdn-msp.jmapiproxy1.monster
73
74
  cdn-msp2.jmapiproxy1.monster
74
75
  cdn-msp.jmapiproxy1.cc
@@ -78,7 +79,7 @@ class JmModuleConfig:
78
79
 
79
80
  ''')
80
81
 
81
- # API域名
82
+ # 移动端API域名
82
83
  DOMAIN_API_LIST = str_to_list('''
83
84
  www.jmapinode1.top
84
85
  www.jmapinode2.top
@@ -88,8 +89,11 @@ class JmModuleConfig:
88
89
 
89
90
  ''')
90
91
 
91
- # 域名配置 - 网页端
92
+ # 网页端域名配置
92
93
  # 无需配置,默认为None,需要的时候会发起请求获得
94
+ # 使用优先级:
95
+ # 1. DOMAIN_HTML_LIST
96
+ # 2. [DOMAIN_HTML]
93
97
  DOMAIN_HTML = None
94
98
  DOMAIN_HTML_LIST = None
95
99
 
@@ -106,7 +110,7 @@ class JmModuleConfig:
106
110
  REGISTRY_PLUGIN = {}
107
111
 
108
112
  # 执行debug的函数
109
- debug_executor = default_jm_debug
113
+ debug_executor = default_jm_debug_logging
110
114
  # postman构造函数
111
115
  postman_constructor = default_postman_constructor
112
116
  # 网页正则表达式解析失败时,执行抛出异常的函数,可以替换掉用于debug
@@ -190,7 +194,7 @@ class JmModuleConfig:
190
194
  postman = postman or cls.new_postman(session=True)
191
195
 
192
196
  url = postman.with_redirect_catching().get(cls.JM_REDIRECT_URL)
193
- cls.jm_debug('获取禁漫网页URL', f'[{cls.JM_REDIRECT_URL}] → [{url}]')
197
+ cls.jm_debug('module.html_url', f'获取禁漫网页URL: [{cls.JM_REDIRECT_URL}] → [{url}]')
194
198
  return url
195
199
 
196
200
  @classmethod
@@ -211,9 +215,22 @@ class JmModuleConfig:
211
215
  from .jm_toolkit import JmcomicText
212
216
  domain_list = JmcomicText.analyse_jm_pub_html(resp.text)
213
217
 
214
- cls.jm_debug('获取禁漫网页全部域名', f'[{resp.url}] → {domain_list}')
218
+ cls.jm_debug('module.html_domain_all', f'获取禁漫网页全部域名: [{resp.url}] → {domain_list}')
215
219
  return domain_list
216
220
 
221
+ @classmethod
222
+ @field_cache("APP_COOKIES")
223
+ def get_cookies(cls, postman=None):
224
+ from .jm_toolkit import JmcomicText
225
+ url = JmcomicText.format_url('/setting', cls.DOMAIN_API_LIST[0])
226
+ postman = postman or cls.new_postman()
227
+
228
+ resp = postman.get(url)
229
+ cookies = dict(resp.cookies)
230
+
231
+ cls.jm_debug('module.cookies', f'获取cookies: [{url}] → {cookies}')
232
+ return cookies
233
+
217
234
  @classmethod
218
235
  def new_html_headers(cls, domain='18comic.vip'):
219
236
  """
@@ -247,7 +264,7 @@ class JmModuleConfig:
247
264
  key_ts = time_stamp()
248
265
 
249
266
  import hashlib
250
- token = hashlib.md5(f"{key_ts}{cls.MAGIC_18COMICAPPCONTENT}".encode()).hexdigest()
267
+ token = hashlib.md5(f"{key_ts}{cls.APP_SECRET}".encode("utf-8")).hexdigest()
251
268
 
252
269
  return {
253
270
  'token': token,
@@ -331,7 +348,11 @@ class JmModuleConfig:
331
348
  'impl': None,
332
349
  'retry_times': 5
333
350
  },
334
- 'plugins': {},
351
+ 'plugins': {
352
+ # 如果插件抛出参数校验异常,只debug。(全局配置,可以被插件的局部配置覆盖)
353
+ # 可选值:ignore(忽略),debug(打印日志),raise(抛异常)。
354
+ 'valid': 'debug',
355
+ },
335
356
  }
336
357
 
337
358
  @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,13 @@ 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
+ meta_data: dict = postman_conf['meta_data'] # 请求元信息
317
318
  impl: str = impl or self.client.impl # client_key
318
319
  retry_times: int = self.client.retry_times # 重试次数
319
- cache: str = self.client.cache # 启用缓存
320
+ cache: str = cache or self.client.cache # 启用缓存
320
321
 
321
322
  # domain
322
323
  def decide_domain():
@@ -335,10 +336,9 @@ class JmOption:
335
336
 
336
337
  # support kwargs overwrite meta_data
337
338
  if len(kwargs) != 0:
338
- postman_conf['meta_data'].update(kwargs)
339
+ meta_data.update(kwargs)
339
340
 
340
341
  # headers
341
- meta_data = postman_conf['meta_data']
342
342
  if meta_data['headers'] is None:
343
343
  meta_data['headers'] = self.decide_postman_headers(impl, domain[0])
344
344
 
@@ -349,7 +349,8 @@ class JmOption:
349
349
  clazz = JmModuleConfig.client_impl_class(impl)
350
350
  if clazz == AbstractJmClient or not issubclass(clazz, AbstractJmClient):
351
351
  raise NotImplementedError(clazz)
352
- client = clazz(
352
+
353
+ client: AbstractJmClient = clazz(
353
354
  postman,
354
355
  retry_times,
355
356
  fallback_domain_list=decide_domain(),
@@ -371,6 +372,9 @@ class JmOption:
371
372
 
372
373
  if is_client_type(JmHtmlClient):
373
374
  # 网页端
375
+ domain_list = JmModuleConfig.DOMAIN_HTML_LIST
376
+ if domain_list is not None:
377
+ return domain_list
374
378
  return [JmModuleConfig.get_html_domain()]
375
379
 
376
380
  ExceptionTool.raises(f'没有配置域名,且是无法识别的client类型: {client_key}')
@@ -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
  """
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
 
@@ -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)
@@ -233,26 +249,32 @@ class ZipPlugin(JmOptionPlugin):
233
249
  mkdir_if_not_exists(zip_dir)
234
250
 
235
251
  # 原文件夹 -> zip文件
236
- dir_zip_dict = {}
252
+ dir_zip_dict: Dict[str, Optional[str]] = {}
237
253
  photo_dict = downloader.all_downloaded[album]
238
254
 
239
255
  if level == 'album':
240
256
  zip_path = self.get_zip_path(album, None, filename_rule, suffix, zip_dir)
241
257
  dir_path = self.zip_album(album, photo_dict, zip_path)
242
- dir_zip_dict[dir_path] = zip_path
258
+ if dir_path is not None:
259
+ # 要删除这个album文件夹
260
+ dir_zip_dict[dir_path] = zip_path
261
+ # 也要删除album下的photo文件夹
262
+ for d in files_of_dir(dir_path):
263
+ dir_zip_dict[d] = None
243
264
 
244
265
  elif level == 'photo':
245
266
  for photo, image_list in photo_dict.items():
246
267
  zip_path = self.get_zip_path(None, photo, filename_rule, suffix, zip_dir)
247
268
  dir_path = self.zip_photo(photo, image_list, zip_path)
248
- dir_zip_dict[dir_path] = zip_path
269
+ if dir_path is not None:
270
+ dir_zip_dict[dir_path] = zip_path
249
271
 
250
272
  else:
251
273
  ExceptionTool.raises(f'Not Implemented Zip Level: {level}')
252
274
 
253
275
  self.after_zip(dir_zip_dict)
254
276
 
255
- def zip_photo(self, photo, image_list: list, zip_path: str):
277
+ def zip_photo(self, photo, image_list: list, zip_path: str) -> Optional[str]:
256
278
  """
257
279
  压缩photo文件夹
258
280
  :returns: photo文件夹路径
@@ -261,46 +283,54 @@ class ZipPlugin(JmOptionPlugin):
261
283
  if len(image_list) == 0 \
262
284
  else os.path.dirname(image_list[0][0])
263
285
 
264
- all_filepath = set(map(lambda t: t[0], image_list))
286
+ all_filepath = set(map(lambda t: self.unified_path(t[0]), image_list))
265
287
 
266
- if len(all_filepath) == 0:
267
- self.debug('无下载文件,无需压缩', 'skip')
268
- return
288
+ return self.do_zip(photo_dir,
289
+ zip_path,
290
+ all_filepath,
291
+ f'压缩章节[{photo.photo_id}]成功 → {zip_path}',
292
+ )
269
293
 
270
- from common import backup_dir_to_zip
271
- backup_dir_to_zip(photo_dir, zip_path, acceptor=lambda f: f in all_filepath)
272
- self.debug(f'压缩章节[{photo.photo_id}]成功 → {zip_path}', 'finish')
273
-
274
- return photo_dir
294
+ @staticmethod
295
+ def unified_path(f):
296
+ return fix_filepath(f, os.path.isdir(f))
275
297
 
276
- def zip_album(self, album, photo_dict: dict, zip_path):
298
+ def zip_album(self, album, photo_dict: dict, zip_path) -> Optional[str]:
277
299
  """
278
300
  压缩album文件夹
279
301
  :returns: album文件夹路径
280
302
  """
281
- album_dir = self.option.decide_album_dir(album)
282
303
  all_filepath: Set[str] = set()
283
304
 
284
- for image_list in photo_dict.values():
285
- image_list: List[Tuple[str, JmImageDetail]]
286
- for path, _ in image_list:
287
- all_filepath.add(path)
305
+ def addpath(f):
306
+ all_filepath.update(set(f))
307
+
308
+ album_dir = self.option.decide_album_dir(album)
309
+ # addpath(self.option.decide_image_save_dir(photo) for photo in photo_dict.keys())
310
+ addpath(path for ls in photo_dict.values() for path, _ in ls)
311
+
312
+ return self.do_zip(album_dir,
313
+ zip_path,
314
+ all_filepath,
315
+ msg=f'压缩本子[{album.album_id}]成功 → {zip_path}',
316
+ )
288
317
 
318
+ def do_zip(self, source_dir, zip_path, all_filepath, msg):
289
319
  if len(all_filepath) == 0:
290
320
  self.debug('无下载文件,无需压缩', 'skip')
291
- return
321
+ return None
292
322
 
293
323
  from common import backup_dir_to_zip
294
324
  backup_dir_to_zip(
295
- album_dir,
325
+ source_dir,
296
326
  zip_path,
297
- acceptor=lambda f: f in all_filepath
298
- )
327
+ acceptor=lambda f: os.path.isdir(f) or self.unified_path(f) in all_filepath
328
+ ).close()
299
329
 
300
- self.debug(f'压缩本子[{album.album_id}]成功 → {zip_path}', 'finish')
301
- return album_dir
330
+ self.debug(msg, 'finish')
331
+ return self.unified_path(source_dir)
302
332
 
303
- def after_zip(self, dir_zip_dict: Dict[str, str]):
333
+ def after_zip(self, dir_zip_dict: Dict[str, Optional[str]]):
304
334
  # 是否要删除所有原文件
305
335
  if self.delete_original_file is True:
306
336
  self.delete_all_files_and_empty_dir(
@@ -326,15 +356,20 @@ class ZipPlugin(JmOptionPlugin):
326
356
  删除所有文件和文件夹
327
357
  """
328
358
  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:
359
+ for photo_dict in all_downloaded.values():
360
+ for image_list in photo_dict.values():
361
+ for f, _ in image_list:
362
+ # check not exist
363
+ if file_not_exists(f):
364
+ continue
365
+
332
366
  os.remove(f)
333
367
  self.debug(f'删除原文件: {f}', 'remove')
334
368
 
335
- for d in dir_list:
336
- if len(os.listdir(d)) == 0:
337
- os.removedirs(d)
369
+ for d in sorted(dir_list, reverse=True):
370
+ # check exist
371
+ if file_exists(d):
372
+ os.rmdir(d)
338
373
  self.debug(f'删除文件夹: {d}', 'remove')
339
374
 
340
375
 
@@ -403,11 +438,29 @@ class SendQQEmailPlugin(JmOptionPlugin):
403
438
  album=None,
404
439
  downloader=None,
405
440
  ) -> None:
406
- if not (msg_from and msg_to and password):
407
- self.debug('发送邮件的相关参数为空,不处理')
408
- return
441
+ self.require_true(msg_from and msg_to and password, '发件人、收件人、授权码都不能为空')
442
+
409
443
  from common import EmailConfig
410
444
  econfig = EmailConfig(msg_from, msg_to, password)
411
445
  epostman = econfig.create_email_postman()
412
446
  epostman.send(content, title)
447
+
413
448
  self.debug('Email sent successfully')
449
+
450
+
451
+ class DebugTopicFilterPlugin(JmOptionPlugin):
452
+ plugin_key = 'debug_topic_filter'
453
+
454
+ def invoke(self, whitelist) -> None:
455
+ if whitelist is not None:
456
+ whitelist = set(whitelist)
457
+
458
+ old_jm_debug = JmModuleConfig.debug_executor
459
+
460
+ def new_jm_debug(topic, msg):
461
+ if whitelist is not None and topic not in whitelist:
462
+ return
463
+
464
+ old_jm_debug(topic, msg)
465
+
466
+ JmModuleConfig.debug_executor = new_jm_debug
jmcomic/jm_toolkit.py CHANGED
@@ -489,7 +489,7 @@ class JmApiAdaptTool:
489
489
 
490
490
  fields['sort'] = sort
491
491
  import random
492
- fields['data_original_domain'] = random.choice(JmModuleConfig.DOMAIN_API_IMAGE_LIST)
492
+ fields['data_original_domain'] = random.choice(JmModuleConfig.DOMAIN_IMAGE_LIST)
493
493
 
494
494
 
495
495
  class JmImageTool:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: jmcomic
3
- Version: 2.3.14
3
+ Version: 2.3.16
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
  ## 进阶使用
@@ -0,0 +1,17 @@
1
+ jmcomic/__init__.py,sha256=qqA6jypcSuTQ_DN37vmWjpu-jC8pkxB_s-AiFhj2Y6E,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=fDx5VZnlmYpdie5grHfwf8E-zh-QYrbdHzUJK5nqdw4,25161
5
+ jmcomic/jm_client_interface.py,sha256=r-y81BqiQSj74dwjnpa22uHbaYPVAENG6QOegx9g-F0,13087
6
+ jmcomic/jm_config.py,sha256=x8Xi4aTNn8YX32d_f7Wy775Kp9YTlqYJS64Rl3wpOVY,14092
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=YxLO4uqnTVqj5A9Ouv_lbQ7rSBA2zgbWuSW0yKuMxdA,18685
10
+ jmcomic/jm_plugin.py,sha256=nLCyrWTjaSXCRcVHZBwJtgzYeeNEc92vOqw0_8AwJpA,14906
11
+ jmcomic/jm_toolkit.py,sha256=YrOlDp_fYQ5Ggu40Py-jArM2D1GfpQ7VJycfnR6aaLs,21846
12
+ jmcomic-2.3.16.dist-info/LICENSE,sha256=kz4coTxZxuGxisK3W00tjK57Zh3RcMGq-EnbXrK7-xA,1064
13
+ jmcomic-2.3.16.dist-info/METADATA,sha256=s1R-negNRV1gArP_eLwwp45AsD-LTzbY36ddCEo3iXY,4944
14
+ jmcomic-2.3.16.dist-info/WHEEL,sha256=Xo9-1PvkuimrydujYJAjF7pCkriuXBpUPEjma1nZyJ0,92
15
+ jmcomic-2.3.16.dist-info/entry_points.txt,sha256=tRbQltaGSBjejI0c9jYt-4SXQMd5nSDHcMvHmuTy4ow,44
16
+ jmcomic-2.3.16.dist-info/top_level.txt,sha256=puvVMFYJqIbd6NOTMEvOyugMTT8woBfSQyxEBan3zY4,8
17
+ jmcomic-2.3.16.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=cpaa_TivIu_XDHXwKdE7w8-XJz0GNk5ZkNsE4YU-zAs,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=1E9tIePPNW2OguqS8-Jk3uBlsirkEEYqtPzd53oaY5E,24742
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=3nI2e0U9V7w00xpM16358SvoOnKBvZUEfK4uQCBG5d8,16976
10
- jmcomic/jm_plugin.py,sha256=kBE5lrfCkWn5ZXOq2K-w7es24vUM-WGBQC_A3tBl2jk,13194
11
- jmcomic/jm_toolkit.py,sha256=K4ip55Di7ZZDv_Bnj-NvaOSKoRHaCmrMrXliD2vSJhw,21850
12
- jmcomic-2.3.14.dist-info/LICENSE,sha256=kz4coTxZxuGxisK3W00tjK57Zh3RcMGq-EnbXrK7-xA,1064
13
- jmcomic-2.3.14.dist-info/METADATA,sha256=fCkMQgNJo7kFx2nhwz6L2Xk9kUAPJFkuoI4vGOPB_rk,4903
14
- jmcomic-2.3.14.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
15
- jmcomic-2.3.14.dist-info/entry_points.txt,sha256=tRbQltaGSBjejI0c9jYt-4SXQMd5nSDHcMvHmuTy4ow,44
16
- jmcomic-2.3.14.dist-info/top_level.txt,sha256=puvVMFYJqIbd6NOTMEvOyugMTT8woBfSQyxEBan3zY4,8
17
- jmcomic-2.3.14.dist-info/RECORD,,