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 +1 -1
- jmcomic/jm_client_impl.py +21 -4
- jmcomic/jm_config.py +7 -3
- jmcomic/jm_downloader.py +0 -4
- jmcomic/jm_option.py +71 -22
- jmcomic/jm_plugin.py +93 -22
- jmcomic/jm_toolkit.py +6 -3
- {jmcomic-2.3.12.dist-info → jmcomic-2.3.15.dist-info}/METADATA +5 -5
- jmcomic-2.3.15.dist-info/RECORD +17 -0
- {jmcomic-2.3.12.dist-info → jmcomic-2.3.15.dist-info}/WHEEL +1 -1
- jmcomic-2.3.12.dist-info/RECORD +0 -17
- {jmcomic-2.3.12.dist-info → jmcomic-2.3.15.dist-info}/LICENSE +0 -0
- {jmcomic-2.3.12.dist-info → jmcomic-2.3.15.dist-info}/entry_points.txt +0 -0
- {jmcomic-2.3.12.dist-info → jmcomic-2.3.15.dist-info}/top_level.txt +0 -0
jmcomic/__init__.py
CHANGED
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.
|
|
520
|
+
photo.from_album = self.get_album_detail(photo.album_id)
|
|
515
521
|
|
|
516
522
|
if fetch_scramble_id:
|
|
517
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
468
|
-
|
|
469
|
-
|
|
477
|
+
# 模块内部异常,通过不是插件抛出的,而是插件调用了例如Client,Client请求失败抛出的
|
|
478
|
+
self.handle_plugin_exception(e, pinfo, kwargs, plugin)
|
|
479
|
+
|
|
470
480
|
except BaseException as e:
|
|
471
|
-
|
|
472
|
-
|
|
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,
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
319
|
-
for
|
|
320
|
-
for f,
|
|
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
|
-
|
|
353
|
+
self.debug(f'删除原文件: {f}', 'remove')
|
|
323
354
|
|
|
324
355
|
for d in dir_list:
|
|
325
|
-
|
|
356
|
+
# check exist
|
|
357
|
+
if file_exists(d) and len(os.listdir(d)) == 0:
|
|
326
358
|
os.removedirs(d)
|
|
327
|
-
|
|
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
|
-
|
|
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
|
-
|
|
351
|
-
return
|
|
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
|
-
|
|
372
|
-
|
|
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
|
|
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
|
|
213
|
-
|
|
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.
|
|
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
|
|
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
|
|
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,,
|
jmcomic-2.3.12.dist-info/RECORD
DELETED
|
@@ -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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|