jmcomic 2.5.8__tar.gz → 2.5.10__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.8/src/jmcomic.egg-info → jmcomic-2.5.10}/PKG-INFO +2 -2
- {jmcomic-2.5.8 → jmcomic-2.5.10}/setup.py +1 -1
- {jmcomic-2.5.8 → jmcomic-2.5.10}/src/jmcomic/__init__.py +1 -1
- {jmcomic-2.5.8 → jmcomic-2.5.10}/src/jmcomic/jm_client_impl.py +4 -4
- {jmcomic-2.5.8 → jmcomic-2.5.10}/src/jmcomic/jm_config.py +16 -12
- {jmcomic-2.5.8 → jmcomic-2.5.10}/src/jmcomic/jm_downloader.py +10 -3
- {jmcomic-2.5.8 → jmcomic-2.5.10}/src/jmcomic/jm_entity.py +16 -9
- {jmcomic-2.5.8 → jmcomic-2.5.10}/src/jmcomic/jm_exception.py +2 -8
- {jmcomic-2.5.8 → jmcomic-2.5.10}/src/jmcomic/jm_option.py +10 -24
- {jmcomic-2.5.8 → jmcomic-2.5.10}/src/jmcomic/jm_plugin.py +73 -44
- {jmcomic-2.5.8 → jmcomic-2.5.10/src/jmcomic.egg-info}/PKG-INFO +2 -2
- {jmcomic-2.5.8 → jmcomic-2.5.10}/src/jmcomic.egg-info/requires.txt +1 -1
- {jmcomic-2.5.8 → jmcomic-2.5.10}/LICENSE +0 -0
- {jmcomic-2.5.8 → jmcomic-2.5.10}/README.md +0 -0
- {jmcomic-2.5.8 → jmcomic-2.5.10}/setup.cfg +0 -0
- {jmcomic-2.5.8 → jmcomic-2.5.10}/src/jmcomic/api.py +0 -0
- {jmcomic-2.5.8 → jmcomic-2.5.10}/src/jmcomic/cl.py +0 -0
- {jmcomic-2.5.8 → jmcomic-2.5.10}/src/jmcomic/jm_client_interface.py +0 -0
- {jmcomic-2.5.8 → jmcomic-2.5.10}/src/jmcomic/jm_toolkit.py +0 -0
- {jmcomic-2.5.8 → jmcomic-2.5.10}/src/jmcomic.egg-info/SOURCES.txt +0 -0
- {jmcomic-2.5.8 → jmcomic-2.5.10}/src/jmcomic.egg-info/dependency_links.txt +0 -0
- {jmcomic-2.5.8 → jmcomic-2.5.10}/src/jmcomic.egg-info/entry_points.txt +0 -0
- {jmcomic-2.5.8 → jmcomic-2.5.10}/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.10
|
|
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()
|
|
@@ -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': {
|
|
@@ -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
|
|
@@ -72,13 +72,6 @@ class ExceptionTool:
|
|
|
72
72
|
CONTEXT_KEY_RE_PATTERN = 'pattern'
|
|
73
73
|
CONTEXT_KEY_MISSING_JM_ID = 'missing_jm_id'
|
|
74
74
|
|
|
75
|
-
# 兼容旧版本
|
|
76
|
-
|
|
77
|
-
EXTRA_KEY_RESP = 'resp'
|
|
78
|
-
EXTRA_KEY_HTML = 'html'
|
|
79
|
-
EXTRA_KEY_RE_PATTERN = 'pattern'
|
|
80
|
-
EXTRA_KEY_MISSING_JM_ID = 'missing_jm_id'
|
|
81
|
-
|
|
82
75
|
@classmethod
|
|
83
76
|
def raises(cls,
|
|
84
77
|
msg: str,
|
|
@@ -144,7 +137,8 @@ class ExceptionTool:
|
|
|
144
137
|
:param resp: 响应对象
|
|
145
138
|
:param jmid: 禁漫本子/章节id
|
|
146
139
|
"""
|
|
147
|
-
|
|
140
|
+
from .jm_toolkit import JmcomicText
|
|
141
|
+
url = JmcomicText.format_album_url(jmid)
|
|
148
142
|
|
|
149
143
|
req_type = "本子" if "album" in url else "章节"
|
|
150
144
|
cls.raises(
|
|
@@ -236,28 +236,6 @@ class JmOption:
|
|
|
236
236
|
def decide_photo_batch_count(self, album: JmAlbumDetail):
|
|
237
237
|
return self.download.threading.photo
|
|
238
238
|
|
|
239
|
-
def decide_album_dir(self, album: JmAlbumDetail) -> str:
|
|
240
|
-
"""
|
|
241
|
-
该方法目前仅在 plugin-zip 中使用,不建议外部调用
|
|
242
|
-
"""
|
|
243
|
-
dir_layer = []
|
|
244
|
-
dir_rule = self.dir_rule
|
|
245
|
-
for rule in dir_rule.rule_dsl.split('_'):
|
|
246
|
-
if rule == 'Bd':
|
|
247
|
-
dir_layer.append(dir_rule.base_dir)
|
|
248
|
-
continue
|
|
249
|
-
|
|
250
|
-
if rule[0] == 'A':
|
|
251
|
-
name = dir_rule.apply_rule_directly(album, None, rule)
|
|
252
|
-
dir_layer.append(name)
|
|
253
|
-
|
|
254
|
-
if rule[0] == 'P':
|
|
255
|
-
break
|
|
256
|
-
|
|
257
|
-
from os.path import join
|
|
258
|
-
# noinspection PyTypeChecker
|
|
259
|
-
return join(*dir_layer)
|
|
260
|
-
|
|
261
239
|
# noinspection PyMethodMayBeStatic
|
|
262
240
|
def decide_image_filename(self, image: JmImageDetail) -> str:
|
|
263
241
|
"""
|
|
@@ -285,7 +263,15 @@ class JmOption:
|
|
|
285
263
|
)
|
|
286
264
|
|
|
287
265
|
if ensure_exists:
|
|
288
|
-
|
|
266
|
+
try:
|
|
267
|
+
mkdir_if_not_exists(save_dir)
|
|
268
|
+
except OSError as e:
|
|
269
|
+
if e.errno == 36:
|
|
270
|
+
# 目录名过长
|
|
271
|
+
limit = JmModuleConfig.VAR_FILE_NAME_LENGTH_LIMIT
|
|
272
|
+
jm_log('error', f'目录名过长,无法创建目录,强制缩短到{limit}个字符并重试')
|
|
273
|
+
save_dir = save_dir[0:limit]
|
|
274
|
+
mkdir_if_not_exists(save_dir)
|
|
289
275
|
|
|
290
276
|
return save_dir
|
|
291
277
|
|
|
@@ -359,7 +345,7 @@ class JmOption:
|
|
|
359
345
|
def deconstruct(self) -> Dict:
|
|
360
346
|
return {
|
|
361
347
|
'version': JmModuleConfig.JM_OPTION_VER,
|
|
362
|
-
'log': JmModuleConfig.
|
|
348
|
+
'log': JmModuleConfig.FLAG_ENABLE_JM_LOG,
|
|
363
349
|
'dir_rule': {
|
|
364
350
|
'rule': self.dir_rule.rule_dsl,
|
|
365
351
|
'base_dir': self.dir_rule.base_dir,
|
|
@@ -302,27 +302,19 @@ class ZipPlugin(JmOptionPlugin):
|
|
|
302
302
|
|
|
303
303
|
if level == 'album':
|
|
304
304
|
zip_path = self.get_zip_path(album, None, filename_rule, suffix, zip_dir)
|
|
305
|
-
|
|
306
|
-
if dir_path is not None:
|
|
307
|
-
# 要删除这个album文件夹
|
|
308
|
-
dir_zip_dict[dir_path] = zip_path
|
|
309
|
-
# 也要删除album下的photo文件夹
|
|
310
|
-
for d in files_of_dir(dir_path):
|
|
311
|
-
dir_zip_dict[d] = None
|
|
305
|
+
self.zip_album(album, photo_dict, zip_path, dir_zip_dict)
|
|
312
306
|
|
|
313
307
|
elif level == 'photo':
|
|
314
308
|
for photo, image_list in photo_dict.items():
|
|
315
309
|
zip_path = self.get_zip_path(None, photo, filename_rule, suffix, zip_dir)
|
|
316
|
-
|
|
317
|
-
if dir_path is not None:
|
|
318
|
-
dir_zip_dict[dir_path] = zip_path
|
|
310
|
+
self.zip_photo(photo, image_list, zip_path, dir_zip_dict)
|
|
319
311
|
|
|
320
312
|
else:
|
|
321
313
|
ExceptionTool.raises(f'Not Implemented Zip Level: {level}')
|
|
322
314
|
|
|
323
315
|
self.after_zip(dir_zip_dict)
|
|
324
316
|
|
|
325
|
-
def zip_photo(self, photo, image_list: list, zip_path: str) -> Optional[str]:
|
|
317
|
+
def zip_photo(self, photo, image_list: list, zip_path: str, dir_zip_dict) -> Optional[str]:
|
|
326
318
|
"""
|
|
327
319
|
压缩photo文件夹
|
|
328
320
|
:returns: photo文件夹路径
|
|
@@ -333,50 +325,58 @@ class ZipPlugin(JmOptionPlugin):
|
|
|
333
325
|
|
|
334
326
|
all_filepath = set(map(lambda t: self.unified_path(t[0]), image_list))
|
|
335
327
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
328
|
+
if len(all_filepath) == 0:
|
|
329
|
+
self.log('无下载文件,无需压缩', 'skip')
|
|
330
|
+
return None
|
|
331
|
+
|
|
332
|
+
from common import backup_dir_to_zip
|
|
333
|
+
backup_dir_to_zip(
|
|
334
|
+
photo_dir,
|
|
335
|
+
zip_path,
|
|
336
|
+
acceptor=lambda f: os.path.isdir(f) or self.unified_path(f) in all_filepath
|
|
337
|
+
).close()
|
|
338
|
+
|
|
339
|
+
self.log(f'压缩章节[{photo.photo_id}]成功 → {zip_path}', 'finish')
|
|
340
|
+
dir_zip_dict[self.unified_path(photo_dir)] = zip_path
|
|
341
341
|
|
|
342
342
|
@staticmethod
|
|
343
343
|
def unified_path(f):
|
|
344
344
|
return fix_filepath(f, os.path.isdir(f))
|
|
345
345
|
|
|
346
|
-
def zip_album(self, album, photo_dict: dict, zip_path) -> Optional[str]:
|
|
346
|
+
def zip_album(self, album, photo_dict: dict, zip_path, dir_zip_dict) -> Optional[str]:
|
|
347
347
|
"""
|
|
348
348
|
压缩album文件夹
|
|
349
349
|
:returns: album文件夹路径
|
|
350
350
|
"""
|
|
351
|
-
all_filepath: Set[str] = set()
|
|
352
|
-
|
|
353
|
-
def addpath(f):
|
|
354
|
-
all_filepath.update(set(f))
|
|
355
|
-
|
|
356
|
-
album_dir = self.option.decide_album_dir(album)
|
|
357
|
-
# addpath(self.option.decide_image_save_dir(photo) for photo in photo_dict.keys())
|
|
358
|
-
addpath(path for ls in photo_dict.values() for path, _ in ls)
|
|
359
351
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
all_filepath,
|
|
363
|
-
msg=f'压缩本子[{album.album_id}]成功 → {zip_path}',
|
|
364
|
-
)
|
|
352
|
+
# 所有下载了的图片文件的路径
|
|
353
|
+
all_filepath: Set[str] = set(path for ls in photo_dict.values() for path, _ in ls)
|
|
365
354
|
|
|
366
|
-
def do_zip(self, source_dir, zip_path, all_filepath, msg):
|
|
367
355
|
if len(all_filepath) == 0:
|
|
368
356
|
self.log('无下载文件,无需压缩', 'skip')
|
|
369
|
-
return
|
|
357
|
+
return
|
|
370
358
|
|
|
359
|
+
# 该本子的所有章节的图片所在文件夹
|
|
360
|
+
photo_dir_list = [self.option.decide_image_save_dir(photo) for photo in photo_dict.keys()]
|
|
361
|
+
|
|
362
|
+
# 压缩文件对象
|
|
371
363
|
from common import backup_dir_to_zip
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
364
|
+
import zipfile
|
|
365
|
+
zfile = zipfile.ZipFile(zip_path, 'w')
|
|
366
|
+
|
|
367
|
+
for photo_dir in photo_dir_list:
|
|
368
|
+
photo_dir = self.unified_path(photo_dir)
|
|
369
|
+
backup_dir_to_zip(
|
|
370
|
+
photo_dir,
|
|
371
|
+
zip_path,
|
|
372
|
+
zfile=zfile,
|
|
373
|
+
prefix=os.path.basename(photo_dir.rstrip('/')),
|
|
374
|
+
acceptor=lambda f: os.path.isdir(f) or self.unified_path(f) in all_filepath
|
|
375
|
+
)
|
|
376
|
+
dir_zip_dict[photo_dir] = zip_path
|
|
377
377
|
|
|
378
|
-
|
|
379
|
-
|
|
378
|
+
zfile.close()
|
|
379
|
+
self.log(f'压缩本子[{album.album_id}]成功 → {zip_path}', 'finish')
|
|
380
380
|
|
|
381
381
|
def after_zip(self, dir_zip_dict: Dict[str, Optional[str]]):
|
|
382
382
|
# 删除所有原文件
|
|
@@ -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):
|
|
@@ -963,3 +961,34 @@ class SubscribeAlbumUpdatePlugin(JmOptionPlugin):
|
|
|
963
961
|
is_new_photo = True
|
|
964
962
|
|
|
965
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)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: jmcomic
|
|
3
|
-
Version: 2.5.
|
|
3
|
+
Version: 2.5.10
|
|
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
|
|
File without changes
|
|
File without changes
|