jmcomic 2.5.7__tar.gz → 2.5.9__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.
- {jmcomic-2.5.7/src/jmcomic.egg-info → jmcomic-2.5.9}/PKG-INFO +2 -2
- {jmcomic-2.5.7 → jmcomic-2.5.9}/setup.py +1 -1
- {jmcomic-2.5.7 → jmcomic-2.5.9}/src/jmcomic/__init__.py +1 -1
- {jmcomic-2.5.7 → jmcomic-2.5.9}/src/jmcomic/jm_client_impl.py +4 -4
- {jmcomic-2.5.7 → jmcomic-2.5.9}/src/jmcomic/jm_client_interface.py +4 -4
- {jmcomic-2.5.7 → jmcomic-2.5.9}/src/jmcomic/jm_config.py +17 -13
- {jmcomic-2.5.7 → jmcomic-2.5.9}/src/jmcomic/jm_downloader.py +10 -3
- {jmcomic-2.5.7 → jmcomic-2.5.9}/src/jmcomic/jm_entity.py +16 -9
- {jmcomic-2.5.7 → jmcomic-2.5.9}/src/jmcomic/jm_exception.py +4 -8
- {jmcomic-2.5.7 → jmcomic-2.5.9}/src/jmcomic/jm_option.py +14 -5
- {jmcomic-2.5.7 → jmcomic-2.5.9}/src/jmcomic/jm_plugin.py +94 -5
- {jmcomic-2.5.7 → jmcomic-2.5.9}/src/jmcomic/jm_toolkit.py +5 -5
- {jmcomic-2.5.7 → jmcomic-2.5.9/src/jmcomic.egg-info}/PKG-INFO +2 -2
- {jmcomic-2.5.7 → jmcomic-2.5.9}/src/jmcomic.egg-info/requires.txt +1 -1
- {jmcomic-2.5.7 → jmcomic-2.5.9}/LICENSE +0 -0
- {jmcomic-2.5.7 → jmcomic-2.5.9}/README.md +0 -0
- {jmcomic-2.5.7 → jmcomic-2.5.9}/setup.cfg +0 -0
- {jmcomic-2.5.7 → jmcomic-2.5.9}/src/jmcomic/api.py +0 -0
- {jmcomic-2.5.7 → jmcomic-2.5.9}/src/jmcomic/cl.py +0 -0
- {jmcomic-2.5.7 → jmcomic-2.5.9}/src/jmcomic.egg-info/SOURCES.txt +0 -0
- {jmcomic-2.5.7 → jmcomic-2.5.9}/src/jmcomic.egg-info/dependency_links.txt +0 -0
- {jmcomic-2.5.7 → jmcomic-2.5.9}/src/jmcomic.egg-info/entry_points.txt +0 -0
- {jmcomic-2.5.7 → jmcomic-2.5.9}/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.5.
|
|
3
|
+
Version: 2.5.9
|
|
4
4
|
Summary: Python API For JMComic (禁漫天堂)
|
|
5
5
|
Home-page: https://github.com/hect0x7/JMComic-Crawler-Python
|
|
6
6
|
Author: hect0x7
|
|
@@ -20,8 +20,8 @@ Classifier: Operating System :: Microsoft :: Windows
|
|
|
20
20
|
Requires-Python: >=3.7
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
License-File: LICENSE
|
|
23
|
-
Requires-Dist: commonX>=0.6.4
|
|
24
23
|
Requires-Dist: curl_cffi
|
|
24
|
+
Requires-Dist: commonX
|
|
25
25
|
Requires-Dist: PyYAML
|
|
26
26
|
Requires-Dist: Pillow
|
|
27
27
|
Requires-Dist: pycryptodome
|
|
@@ -224,7 +224,7 @@ class AbstractJmClient(
|
|
|
224
224
|
|
|
225
225
|
# noinspection PyMethodMayBeStatic
|
|
226
226
|
def decode(self, url: str):
|
|
227
|
-
if not JmModuleConfig.
|
|
227
|
+
if not JmModuleConfig.FLAG_DECODE_URL_WHEN_LOGGING or '/search/' not in url:
|
|
228
228
|
return url
|
|
229
229
|
|
|
230
230
|
from urllib.parse import unquote
|
|
@@ -767,7 +767,7 @@ class JmApiClient(AbstractJmClient):
|
|
|
767
767
|
# 检查禁漫最新的版本号
|
|
768
768
|
setting_ver = str(resp.model_data.version)
|
|
769
769
|
# 禁漫接口的版本 > jmcomic库内置版本
|
|
770
|
-
if setting_ver > JmMagicConstants.APP_VERSION and JmModuleConfig.
|
|
770
|
+
if setting_ver > JmMagicConstants.APP_VERSION and JmModuleConfig.FLAG_USE_VERSION_NEWER_IF_BEHIND:
|
|
771
771
|
jm_log('api.setting', f'change APP_VERSION from [{JmMagicConstants.APP_VERSION}] to [{setting_ver}]')
|
|
772
772
|
JmMagicConstants.APP_VERSION = setting_ver
|
|
773
773
|
|
|
@@ -883,7 +883,7 @@ class JmApiClient(AbstractJmClient):
|
|
|
883
883
|
ts = time_stamp()
|
|
884
884
|
token, tokenparam = JmCryptoTool.token_and_tokenparam(ts, secret=JmMagicConstants.APP_TOKEN_SECRET_2)
|
|
885
885
|
|
|
886
|
-
elif JmModuleConfig.
|
|
886
|
+
elif JmModuleConfig.FLAG_USE_FIX_TIMESTAMP:
|
|
887
887
|
ts, token, tokenparam = JmModuleConfig.get_fix_ts_token_tokenparam()
|
|
888
888
|
|
|
889
889
|
else:
|
|
@@ -954,7 +954,7 @@ class JmApiClient(AbstractJmClient):
|
|
|
954
954
|
|
|
955
955
|
def after_init(self):
|
|
956
956
|
# 保证拥有cookies,因为移动端要求必须携带cookies,否则会直接跳转同一本子【禁漫娘】
|
|
957
|
-
if JmModuleConfig.
|
|
957
|
+
if JmModuleConfig.FLAG_API_CLIENT_REQUIRE_COOKIES:
|
|
958
958
|
self.ensure_have_cookies()
|
|
959
959
|
|
|
960
960
|
client_init_cookies_lock = Lock()
|
|
@@ -88,8 +88,8 @@ class JmJsonResp(JmResp):
|
|
|
88
88
|
except Exception as e:
|
|
89
89
|
ExceptionTool.raises_resp(f'json解析失败: {e}', self, JsonResolveFailException)
|
|
90
90
|
|
|
91
|
-
def model(self) ->
|
|
92
|
-
return
|
|
91
|
+
def model(self) -> AdvancedDict:
|
|
92
|
+
return AdvancedDict(self.json())
|
|
93
93
|
|
|
94
94
|
|
|
95
95
|
class JmApiResp(JmJsonResp):
|
|
@@ -118,9 +118,9 @@ class JmApiResp(JmJsonResp):
|
|
|
118
118
|
return loads(self.decoded_data)
|
|
119
119
|
|
|
120
120
|
@property
|
|
121
|
-
def model_data(self) ->
|
|
121
|
+
def model_data(self) -> AdvancedDict:
|
|
122
122
|
self.require_success()
|
|
123
|
-
return
|
|
123
|
+
return AdvancedDict(self.res_data)
|
|
124
124
|
|
|
125
125
|
|
|
126
126
|
# album-comment
|
|
@@ -146,18 +146,18 @@ class JmModuleConfig:
|
|
|
146
146
|
REGISTRY_EXCEPTION_LISTENER = {}
|
|
147
147
|
|
|
148
148
|
# 执行log的函数
|
|
149
|
-
|
|
149
|
+
EXECUTOR_LOG = default_jm_logging
|
|
150
150
|
|
|
151
151
|
# 使用固定时间戳
|
|
152
|
-
|
|
152
|
+
FLAG_USE_FIX_TIMESTAMP = True
|
|
153
153
|
# 移动端Client初始化cookies
|
|
154
|
-
|
|
154
|
+
FLAG_API_CLIENT_REQUIRE_COOKIES = True
|
|
155
155
|
# log开关标记
|
|
156
|
-
|
|
156
|
+
FLAG_ENABLE_JM_LOG = True
|
|
157
157
|
# log时解码url
|
|
158
|
-
|
|
158
|
+
FLAG_DECODE_URL_WHEN_LOGGING = True
|
|
159
159
|
# 当内置的版本号落后时,使用最新的禁漫app版本号
|
|
160
|
-
|
|
160
|
+
FLAG_USE_VERSION_NEWER_IF_BEHIND = True
|
|
161
161
|
|
|
162
162
|
# 关联dir_rule的自定义字段与对应的处理函数
|
|
163
163
|
# 例如:
|
|
@@ -165,6 +165,10 @@ class JmModuleConfig:
|
|
|
165
165
|
AFIELD_ADVICE = dict()
|
|
166
166
|
PFIELD_ADVICE = dict()
|
|
167
167
|
|
|
168
|
+
# 当发生 oserror: [Errno 36] File name too long 时,
|
|
169
|
+
# 把文件名限制在指定个字符以内
|
|
170
|
+
VAR_FILE_NAME_LENGTH_LIMIT = 100
|
|
171
|
+
|
|
168
172
|
@classmethod
|
|
169
173
|
def downloader_class(cls):
|
|
170
174
|
if cls.CLASS_DOWNLOADER is not None:
|
|
@@ -319,12 +323,12 @@ class JmModuleConfig:
|
|
|
319
323
|
# noinspection PyUnusedLocal
|
|
320
324
|
@classmethod
|
|
321
325
|
def jm_log(cls, topic: str, msg: str):
|
|
322
|
-
if cls.
|
|
323
|
-
cls.
|
|
326
|
+
if cls.FLAG_ENABLE_JM_LOG is True:
|
|
327
|
+
cls.EXECUTOR_LOG(topic, msg)
|
|
324
328
|
|
|
325
329
|
@classmethod
|
|
326
330
|
def disable_jm_log(cls):
|
|
327
|
-
cls.
|
|
331
|
+
cls.FLAG_ENABLE_JM_LOG = False
|
|
328
332
|
|
|
329
333
|
@classmethod
|
|
330
334
|
def new_postman(cls, session=False, **kwargs):
|
|
@@ -347,7 +351,7 @@ class JmModuleConfig:
|
|
|
347
351
|
DEFAULT_CLIENT_CACHE = None # 默认关闭Client缓存。缓存的配置详见 CacheRegistry
|
|
348
352
|
DEFAULT_PROXIES = ProxyBuilder.system_proxy() # 默认使用系统代理
|
|
349
353
|
|
|
350
|
-
|
|
354
|
+
DEFAULT_OPTION_DICT: dict = {
|
|
351
355
|
'log': None,
|
|
352
356
|
'dir_rule': {'rule': 'Bd_Pname', 'base_dir': None},
|
|
353
357
|
'download': {
|
|
@@ -364,7 +368,7 @@ class JmModuleConfig:
|
|
|
364
368
|
'postman': {
|
|
365
369
|
'type': 'cffi',
|
|
366
370
|
'meta_data': {
|
|
367
|
-
'impersonate': '
|
|
371
|
+
'impersonate': 'chrome',
|
|
368
372
|
'headers': None,
|
|
369
373
|
'proxies': None,
|
|
370
374
|
}
|
|
@@ -387,11 +391,11 @@ class JmModuleConfig:
|
|
|
387
391
|
"""
|
|
388
392
|
from copy import deepcopy
|
|
389
393
|
|
|
390
|
-
option_dict = deepcopy(cls.
|
|
394
|
+
option_dict = deepcopy(cls.DEFAULT_OPTION_DICT)
|
|
391
395
|
|
|
392
396
|
# log
|
|
393
397
|
if option_dict['log'] is None:
|
|
394
|
-
option_dict['log'] = cls.
|
|
398
|
+
option_dict['log'] = cls.FLAG_ENABLE_JM_LOG
|
|
395
399
|
|
|
396
400
|
# dir_rule.base_dir
|
|
397
401
|
dir_rule = option_dict['dir_rule']
|
|
@@ -29,7 +29,7 @@ class DownloadCallback:
|
|
|
29
29
|
f'章节下载完成: [{photo.id}] ({photo.album_id}[{photo.index}/{len(photo.from_album)}])')
|
|
30
30
|
|
|
31
31
|
def before_image(self, image: JmImageDetail, img_save_path):
|
|
32
|
-
if image.
|
|
32
|
+
if image.exists:
|
|
33
33
|
jm_log('image.before',
|
|
34
34
|
f'图片已存在: {image.tag} ← [{img_save_path}]'
|
|
35
35
|
)
|
|
@@ -63,6 +63,8 @@ class JmDownloader(DownloadCallback):
|
|
|
63
63
|
|
|
64
64
|
def download_by_album_detail(self, album: JmAlbumDetail, client: JmcomicClient):
|
|
65
65
|
self.before_album(album)
|
|
66
|
+
if album.skip:
|
|
67
|
+
return
|
|
66
68
|
self.execute_by_condition(
|
|
67
69
|
iter_objs=album,
|
|
68
70
|
apply=lambda photo: self.download_by_photo_detail(photo, client),
|
|
@@ -80,6 +82,8 @@ class JmDownloader(DownloadCallback):
|
|
|
80
82
|
client.check_photo(photo)
|
|
81
83
|
|
|
82
84
|
self.before_photo(photo)
|
|
85
|
+
if photo.skip:
|
|
86
|
+
return
|
|
83
87
|
self.execute_by_condition(
|
|
84
88
|
iter_objs=photo,
|
|
85
89
|
apply=lambda image: self.download_by_image_detail(image, client),
|
|
@@ -91,16 +95,19 @@ class JmDownloader(DownloadCallback):
|
|
|
91
95
|
img_save_path = self.option.decide_image_filepath(image)
|
|
92
96
|
|
|
93
97
|
image.save_path = img_save_path
|
|
94
|
-
image.
|
|
98
|
+
image.exists = file_exists(img_save_path)
|
|
95
99
|
|
|
96
100
|
self.before_image(image, img_save_path)
|
|
97
101
|
|
|
102
|
+
if image.skip:
|
|
103
|
+
return
|
|
104
|
+
|
|
98
105
|
# let option decide use_cache and decode_image
|
|
99
106
|
use_cache = self.option.decide_download_cache(image)
|
|
100
107
|
decode_image = self.option.decide_download_image_decode(image)
|
|
101
108
|
|
|
102
109
|
# skip download
|
|
103
|
-
if use_cache is True and image.
|
|
110
|
+
if use_cache is True and image.exists:
|
|
104
111
|
return
|
|
105
112
|
|
|
106
113
|
e = None
|
|
@@ -3,6 +3,14 @@ from common import *
|
|
|
3
3
|
from .jm_config import *
|
|
4
4
|
|
|
5
5
|
|
|
6
|
+
class Downloadable:
|
|
7
|
+
|
|
8
|
+
def __init__(self):
|
|
9
|
+
self.save_path: str = ''
|
|
10
|
+
self.exists: bool = False
|
|
11
|
+
self.skip = False
|
|
12
|
+
|
|
13
|
+
|
|
6
14
|
class JmBaseEntity:
|
|
7
15
|
|
|
8
16
|
def to_file(self, filepath):
|
|
@@ -117,7 +125,7 @@ class DetailEntity(JmBaseEntity, IndexedEntity):
|
|
|
117
125
|
def __str__(self):
|
|
118
126
|
return f'{self.__class__.__name__}' \
|
|
119
127
|
'{' \
|
|
120
|
-
f'{self.id}: {self.title}'\
|
|
128
|
+
f'{self.id}: {self.title}' \
|
|
121
129
|
'}'
|
|
122
130
|
|
|
123
131
|
@classmethod
|
|
@@ -156,7 +164,7 @@ class DetailEntity(JmBaseEntity, IndexedEntity):
|
|
|
156
164
|
return getattr(detail, ref)
|
|
157
165
|
|
|
158
166
|
|
|
159
|
-
class JmImageDetail(JmBaseEntity):
|
|
167
|
+
class JmImageDetail(JmBaseEntity, Downloadable):
|
|
160
168
|
|
|
161
169
|
def __init__(self,
|
|
162
170
|
aid,
|
|
@@ -167,7 +175,8 @@ class JmImageDetail(JmBaseEntity):
|
|
|
167
175
|
from_photo=None,
|
|
168
176
|
query_params=None,
|
|
169
177
|
index=-1,
|
|
170
|
-
)
|
|
178
|
+
):
|
|
179
|
+
super().__init__()
|
|
171
180
|
if scramble_id is None or (isinstance(scramble_id, str) and scramble_id == ''):
|
|
172
181
|
from .jm_toolkit import ExceptionTool
|
|
173
182
|
ExceptionTool.raises(f'图片的scramble_id不能为空')
|
|
@@ -182,10 +191,6 @@ class JmImageDetail(JmBaseEntity):
|
|
|
182
191
|
self.query_params: Optional[str] = query_params
|
|
183
192
|
self.index = index # 从1开始
|
|
184
193
|
|
|
185
|
-
# temp fields, in order to simplify passing parameter
|
|
186
|
-
self.save_path: str = ''
|
|
187
|
-
self.is_exists: bool = False
|
|
188
|
-
|
|
189
194
|
@property
|
|
190
195
|
def filename_without_suffix(self):
|
|
191
196
|
return self.img_file_name
|
|
@@ -252,7 +257,7 @@ class JmImageDetail(JmBaseEntity):
|
|
|
252
257
|
return True
|
|
253
258
|
|
|
254
259
|
|
|
255
|
-
class JmPhotoDetail(DetailEntity):
|
|
260
|
+
class JmPhotoDetail(DetailEntity, Downloadable):
|
|
256
261
|
|
|
257
262
|
def __init__(self,
|
|
258
263
|
photo_id,
|
|
@@ -267,6 +272,7 @@ class JmPhotoDetail(DetailEntity):
|
|
|
267
272
|
author=None,
|
|
268
273
|
from_album=None,
|
|
269
274
|
):
|
|
275
|
+
super().__init__()
|
|
270
276
|
self.photo_id: str = str(photo_id)
|
|
271
277
|
self.scramble_id: str = str(scramble_id)
|
|
272
278
|
self.name: str = str(name).strip()
|
|
@@ -411,7 +417,7 @@ class JmPhotoDetail(DetailEntity):
|
|
|
411
417
|
return True
|
|
412
418
|
|
|
413
419
|
|
|
414
|
-
class JmAlbumDetail(DetailEntity):
|
|
420
|
+
class JmAlbumDetail(DetailEntity, Downloadable):
|
|
415
421
|
|
|
416
422
|
def __init__(self,
|
|
417
423
|
album_id,
|
|
@@ -430,6 +436,7 @@ class JmAlbumDetail(DetailEntity):
|
|
|
430
436
|
tags,
|
|
431
437
|
related_list=None,
|
|
432
438
|
):
|
|
439
|
+
super().__init__()
|
|
433
440
|
self.album_id: str = str(album_id)
|
|
434
441
|
self.scramble_id: str = str(scramble_id)
|
|
435
442
|
self.name: str = name
|
|
@@ -12,6 +12,8 @@ class JmcomicException(Exception):
|
|
|
12
12
|
def from_context(self, key):
|
|
13
13
|
return self.context[key]
|
|
14
14
|
|
|
15
|
+
def __str__(self):
|
|
16
|
+
return self.msg
|
|
15
17
|
|
|
16
18
|
class ResponseUnexpectedException(JmcomicException):
|
|
17
19
|
description = '响应不符合预期异常'
|
|
@@ -70,13 +72,6 @@ class ExceptionTool:
|
|
|
70
72
|
CONTEXT_KEY_RE_PATTERN = 'pattern'
|
|
71
73
|
CONTEXT_KEY_MISSING_JM_ID = 'missing_jm_id'
|
|
72
74
|
|
|
73
|
-
# 兼容旧版本
|
|
74
|
-
|
|
75
|
-
EXTRA_KEY_RESP = 'resp'
|
|
76
|
-
EXTRA_KEY_HTML = 'html'
|
|
77
|
-
EXTRA_KEY_RE_PATTERN = 'pattern'
|
|
78
|
-
EXTRA_KEY_MISSING_JM_ID = 'missing_jm_id'
|
|
79
|
-
|
|
80
75
|
@classmethod
|
|
81
76
|
def raises(cls,
|
|
82
77
|
msg: str,
|
|
@@ -142,7 +137,8 @@ class ExceptionTool:
|
|
|
142
137
|
:param resp: 响应对象
|
|
143
138
|
:param jmid: 禁漫本子/章节id
|
|
144
139
|
"""
|
|
145
|
-
|
|
140
|
+
from .jm_toolkit import JmcomicText
|
|
141
|
+
url = JmcomicText.format_album_url(jmid)
|
|
146
142
|
|
|
147
143
|
req_type = "本子" if "album" in url else "章节"
|
|
148
144
|
cls.raises(
|
|
@@ -197,11 +197,11 @@ class JmOption:
|
|
|
197
197
|
# 路径规则配置
|
|
198
198
|
self.dir_rule = DirRule(**dir_rule)
|
|
199
199
|
# 客户端配置
|
|
200
|
-
self.client =
|
|
200
|
+
self.client = AdvancedDict(client)
|
|
201
201
|
# 下载配置
|
|
202
|
-
self.download =
|
|
202
|
+
self.download = AdvancedDict(download)
|
|
203
203
|
# 插件配置
|
|
204
|
-
self.plugins =
|
|
204
|
+
self.plugins = AdvancedDict(plugins)
|
|
205
205
|
# 其他配置
|
|
206
206
|
self.filepath = filepath
|
|
207
207
|
|
|
@@ -285,7 +285,15 @@ class JmOption:
|
|
|
285
285
|
)
|
|
286
286
|
|
|
287
287
|
if ensure_exists:
|
|
288
|
-
|
|
288
|
+
try:
|
|
289
|
+
mkdir_if_not_exists(save_dir)
|
|
290
|
+
except OSError as e:
|
|
291
|
+
if e.errno == 36:
|
|
292
|
+
# 目录名过长
|
|
293
|
+
limit = JmModuleConfig.VAR_FILE_NAME_LENGTH_LIMIT
|
|
294
|
+
jm_log('error', f'目录名过长,无法创建目录,强制缩短到{limit}个字符并重试')
|
|
295
|
+
save_dir = save_dir[0:limit]
|
|
296
|
+
mkdir_if_not_exists(save_dir)
|
|
289
297
|
|
|
290
298
|
return save_dir
|
|
291
299
|
|
|
@@ -359,7 +367,7 @@ class JmOption:
|
|
|
359
367
|
def deconstruct(self) -> Dict:
|
|
360
368
|
return {
|
|
361
369
|
'version': JmModuleConfig.JM_OPTION_VER,
|
|
362
|
-
'log': JmModuleConfig.
|
|
370
|
+
'log': JmModuleConfig.FLAG_ENABLE_JM_LOG,
|
|
363
371
|
'dir_rule': {
|
|
364
372
|
'rule': self.dir_rule.rule_dsl,
|
|
365
373
|
'base_dir': self.dir_rule.base_dir,
|
|
@@ -376,6 +384,7 @@ class JmOption:
|
|
|
376
384
|
@classmethod
|
|
377
385
|
def from_file(cls, filepath: str) -> 'JmOption':
|
|
378
386
|
dic: dict = PackerUtil.unpack(filepath)[0]
|
|
387
|
+
dic.setdefault('filepath', filepath)
|
|
379
388
|
return cls.construct(dic)
|
|
380
389
|
|
|
381
390
|
def to_file(self, filepath=None):
|
|
@@ -445,9 +445,7 @@ class ImageSuffixFilterPlugin(JmOptionPlugin):
|
|
|
445
445
|
if image.img_file_suffix not in allowed_suffix_set:
|
|
446
446
|
self.log(f'跳过下载图片: {image.tag},'
|
|
447
447
|
f'因为其后缀\'{image.img_file_suffix}\'不在允许的后缀集合{allowed_suffix_set}内')
|
|
448
|
-
|
|
449
|
-
image.is_exists = True
|
|
450
|
-
return True
|
|
448
|
+
image.skip = True
|
|
451
449
|
|
|
452
450
|
# let option decide
|
|
453
451
|
return option_decide_cache(image)
|
|
@@ -484,7 +482,7 @@ class LogTopicFilterPlugin(JmOptionPlugin):
|
|
|
484
482
|
if whitelist is not None:
|
|
485
483
|
whitelist = set(whitelist)
|
|
486
484
|
|
|
487
|
-
old_jm_log = JmModuleConfig.
|
|
485
|
+
old_jm_log = JmModuleConfig.EXECUTOR_LOG
|
|
488
486
|
|
|
489
487
|
def new_jm_log(topic, msg):
|
|
490
488
|
if whitelist is not None and topic not in whitelist:
|
|
@@ -492,7 +490,7 @@ class LogTopicFilterPlugin(JmOptionPlugin):
|
|
|
492
490
|
|
|
493
491
|
old_jm_log(topic, msg)
|
|
494
492
|
|
|
495
|
-
JmModuleConfig.
|
|
493
|
+
JmModuleConfig.EXECUTOR_LOG = new_jm_log
|
|
496
494
|
|
|
497
495
|
|
|
498
496
|
class AutoSetBrowserCookiesPlugin(JmOptionPlugin):
|
|
@@ -903,3 +901,94 @@ class JmServerPlugin(JmOptionPlugin):
|
|
|
903
901
|
instance = JmServerPlugin(option)
|
|
904
902
|
setattr(cls, field_name, instance)
|
|
905
903
|
return instance
|
|
904
|
+
|
|
905
|
+
|
|
906
|
+
class SubscribeAlbumUpdatePlugin(JmOptionPlugin):
|
|
907
|
+
plugin_key = 'subscribe_album_update'
|
|
908
|
+
|
|
909
|
+
def invoke(self,
|
|
910
|
+
album_photo_dict=None,
|
|
911
|
+
email_notify=None,
|
|
912
|
+
download_if_has_update=True,
|
|
913
|
+
auto_update_after_download=True,
|
|
914
|
+
) -> None:
|
|
915
|
+
if album_photo_dict is None:
|
|
916
|
+
return
|
|
917
|
+
|
|
918
|
+
album_photo_dict: Dict
|
|
919
|
+
for album_id, photo_id in album_photo_dict.copy().items():
|
|
920
|
+
# check update
|
|
921
|
+
try:
|
|
922
|
+
has_update, photo_new_list = self.check_photo_update(album_id, photo_id)
|
|
923
|
+
except JmcomicException as e:
|
|
924
|
+
self.log('Exception happened: ' + str(e), 'check_update.error')
|
|
925
|
+
continue
|
|
926
|
+
|
|
927
|
+
if has_update is False:
|
|
928
|
+
continue
|
|
929
|
+
|
|
930
|
+
self.log(f'album={album_id},发现新章节: {photo_new_list},准备开始下载')
|
|
931
|
+
|
|
932
|
+
# send email
|
|
933
|
+
try:
|
|
934
|
+
if email_notify:
|
|
935
|
+
SendQQEmailPlugin.build(self.option).invoke(**email_notify)
|
|
936
|
+
except PluginValidationException:
|
|
937
|
+
# ignore
|
|
938
|
+
pass
|
|
939
|
+
|
|
940
|
+
# download new photo
|
|
941
|
+
if has_update and download_if_has_update:
|
|
942
|
+
self.option.download_photo(photo_new_list)
|
|
943
|
+
|
|
944
|
+
if auto_update_after_download:
|
|
945
|
+
album_photo_dict[album_id] = photo_new_list[-1]
|
|
946
|
+
self.option.to_file()
|
|
947
|
+
|
|
948
|
+
def check_photo_update(self, album_id: str, photo_id: str):
|
|
949
|
+
client = self.option.new_jm_client()
|
|
950
|
+
album = client.get_album_detail(album_id)
|
|
951
|
+
|
|
952
|
+
photo_new_list = []
|
|
953
|
+
is_new_photo = False
|
|
954
|
+
sentinel = int(photo_id)
|
|
955
|
+
|
|
956
|
+
for photo in album:
|
|
957
|
+
if is_new_photo:
|
|
958
|
+
photo_new_list.append(photo.photo_id)
|
|
959
|
+
|
|
960
|
+
if int(photo.photo_id) == sentinel:
|
|
961
|
+
is_new_photo = True
|
|
962
|
+
|
|
963
|
+
return len(photo_new_list) != 0, photo_new_list
|
|
964
|
+
|
|
965
|
+
|
|
966
|
+
class SkipPhotoWithFewImagesPlugin(JmOptionPlugin):
|
|
967
|
+
plugin_key = 'skip_photo_with_few_images'
|
|
968
|
+
|
|
969
|
+
def invoke(self,
|
|
970
|
+
at_least_image_count: int,
|
|
971
|
+
photo: Optional[JmPhotoDetail] = None,
|
|
972
|
+
image: Optional[JmImageDetail] = None,
|
|
973
|
+
album: Optional[JmAlbumDetail] = None,
|
|
974
|
+
**kwargs
|
|
975
|
+
):
|
|
976
|
+
self.try_mark_photo_skip_and_log(photo, at_least_image_count)
|
|
977
|
+
if image is not None:
|
|
978
|
+
self.try_mark_photo_skip_and_log(image.from_photo, at_least_image_count)
|
|
979
|
+
|
|
980
|
+
def try_mark_photo_skip_and_log(self, photo: JmPhotoDetail, at_least_image_count: int):
|
|
981
|
+
if photo is None:
|
|
982
|
+
return
|
|
983
|
+
|
|
984
|
+
if len(photo) >= at_least_image_count:
|
|
985
|
+
return
|
|
986
|
+
|
|
987
|
+
self.log(f'跳过下载章节: {photo.id} ({photo.album_id}[{photo.index}/{len(photo.from_album)}]),'
|
|
988
|
+
f'因为其图片数: {len(photo)} < {at_least_image_count} (at_least_image_count)')
|
|
989
|
+
photo.skip = True
|
|
990
|
+
|
|
991
|
+
@classmethod
|
|
992
|
+
@field_cache() # 单例
|
|
993
|
+
def build(cls, option: JmOption) -> 'JmOptionPlugin':
|
|
994
|
+
return super().build(option)
|
|
@@ -472,7 +472,7 @@ class JmPageTool:
|
|
|
472
472
|
return JmFavoritePage(content, folder_list, total)
|
|
473
473
|
|
|
474
474
|
@classmethod
|
|
475
|
-
def parse_api_to_search_page(cls, data:
|
|
475
|
+
def parse_api_to_search_page(cls, data: AdvancedDict) -> JmSearchPage:
|
|
476
476
|
"""
|
|
477
477
|
model_data: {
|
|
478
478
|
"search_query": "MANA",
|
|
@@ -501,7 +501,7 @@ class JmPageTool:
|
|
|
501
501
|
return JmSearchPage(content, total)
|
|
502
502
|
|
|
503
503
|
@classmethod
|
|
504
|
-
def parse_api_to_favorite_page(cls, data:
|
|
504
|
+
def parse_api_to_favorite_page(cls, data: AdvancedDict) -> JmFavoritePage:
|
|
505
505
|
"""
|
|
506
506
|
{
|
|
507
507
|
"list": [
|
|
@@ -546,7 +546,7 @@ class JmPageTool:
|
|
|
546
546
|
|
|
547
547
|
@classmethod
|
|
548
548
|
def adapt_content(cls, content):
|
|
549
|
-
def adapt_item(item:
|
|
549
|
+
def adapt_item(item: AdvancedDict):
|
|
550
550
|
item: dict = item.src_dict
|
|
551
551
|
item.setdefault('tags', [])
|
|
552
552
|
return item
|
|
@@ -673,7 +673,7 @@ class JmApiAdaptTool:
|
|
|
673
673
|
series = data['series']
|
|
674
674
|
episode_list = []
|
|
675
675
|
for chapter in series:
|
|
676
|
-
chapter =
|
|
676
|
+
chapter = AdvancedDict(chapter)
|
|
677
677
|
# photo_id, photo_index, photo_title, photo_pub_date
|
|
678
678
|
episode_list.append(
|
|
679
679
|
(chapter.id, chapter.sort, chapter.name, None)
|
|
@@ -688,7 +688,7 @@ class JmApiAdaptTool:
|
|
|
688
688
|
sort = 1
|
|
689
689
|
series: list = data['series'] # series中的sort从1开始
|
|
690
690
|
for chapter in series:
|
|
691
|
-
chapter =
|
|
691
|
+
chapter = AdvancedDict(chapter)
|
|
692
692
|
if int(chapter.id) == int(data['id']):
|
|
693
693
|
sort = chapter.sort
|
|
694
694
|
break
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: jmcomic
|
|
3
|
-
Version: 2.5.
|
|
3
|
+
Version: 2.5.9
|
|
4
4
|
Summary: Python API For JMComic (禁漫天堂)
|
|
5
5
|
Home-page: https://github.com/hect0x7/JMComic-Crawler-Python
|
|
6
6
|
Author: hect0x7
|
|
@@ -20,8 +20,8 @@ Classifier: Operating System :: Microsoft :: Windows
|
|
|
20
20
|
Requires-Python: >=3.7
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
License-File: LICENSE
|
|
23
|
-
Requires-Dist: commonX>=0.6.4
|
|
24
23
|
Requires-Dist: curl_cffi
|
|
24
|
+
Requires-Dist: commonX
|
|
25
25
|
Requires-Dist: PyYAML
|
|
26
26
|
Requires-Dist: Pillow
|
|
27
27
|
Requires-Dist: pycryptodome
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|