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.
Files changed (23) hide show
  1. {jmcomic-2.5.8/src/jmcomic.egg-info → jmcomic-2.5.10}/PKG-INFO +2 -2
  2. {jmcomic-2.5.8 → jmcomic-2.5.10}/setup.py +1 -1
  3. {jmcomic-2.5.8 → jmcomic-2.5.10}/src/jmcomic/__init__.py +1 -1
  4. {jmcomic-2.5.8 → jmcomic-2.5.10}/src/jmcomic/jm_client_impl.py +4 -4
  5. {jmcomic-2.5.8 → jmcomic-2.5.10}/src/jmcomic/jm_config.py +16 -12
  6. {jmcomic-2.5.8 → jmcomic-2.5.10}/src/jmcomic/jm_downloader.py +10 -3
  7. {jmcomic-2.5.8 → jmcomic-2.5.10}/src/jmcomic/jm_entity.py +16 -9
  8. {jmcomic-2.5.8 → jmcomic-2.5.10}/src/jmcomic/jm_exception.py +2 -8
  9. {jmcomic-2.5.8 → jmcomic-2.5.10}/src/jmcomic/jm_option.py +10 -24
  10. {jmcomic-2.5.8 → jmcomic-2.5.10}/src/jmcomic/jm_plugin.py +73 -44
  11. {jmcomic-2.5.8 → jmcomic-2.5.10/src/jmcomic.egg-info}/PKG-INFO +2 -2
  12. {jmcomic-2.5.8 → jmcomic-2.5.10}/src/jmcomic.egg-info/requires.txt +1 -1
  13. {jmcomic-2.5.8 → jmcomic-2.5.10}/LICENSE +0 -0
  14. {jmcomic-2.5.8 → jmcomic-2.5.10}/README.md +0 -0
  15. {jmcomic-2.5.8 → jmcomic-2.5.10}/setup.cfg +0 -0
  16. {jmcomic-2.5.8 → jmcomic-2.5.10}/src/jmcomic/api.py +0 -0
  17. {jmcomic-2.5.8 → jmcomic-2.5.10}/src/jmcomic/cl.py +0 -0
  18. {jmcomic-2.5.8 → jmcomic-2.5.10}/src/jmcomic/jm_client_interface.py +0 -0
  19. {jmcomic-2.5.8 → jmcomic-2.5.10}/src/jmcomic/jm_toolkit.py +0 -0
  20. {jmcomic-2.5.8 → jmcomic-2.5.10}/src/jmcomic.egg-info/SOURCES.txt +0 -0
  21. {jmcomic-2.5.8 → jmcomic-2.5.10}/src/jmcomic.egg-info/dependency_links.txt +0 -0
  22. {jmcomic-2.5.8 → jmcomic-2.5.10}/src/jmcomic.egg-info/entry_points.txt +0 -0
  23. {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.8
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
@@ -27,8 +27,8 @@ setup(
27
27
  package_dir={"": "src"},
28
28
  python_requires=">=3.7",
29
29
  install_requires=[
30
- 'commonX>=0.6.4',
31
30
  'curl_cffi',
31
+ 'commonX',
32
32
  'PyYAML',
33
33
  'Pillow',
34
34
  'pycryptodome',
@@ -2,7 +2,7 @@
2
2
  # 被依赖方 <--- 使用方
3
3
  # config <--- entity <--- toolkit <--- client <--- option <--- downloader
4
4
 
5
- __version__ = '2.5.8'
5
+ __version__ = '2.5.10'
6
6
 
7
7
  from .api import *
8
8
  from .jm_plugin import *
@@ -224,7 +224,7 @@ class AbstractJmClient(
224
224
 
225
225
  # noinspection PyMethodMayBeStatic
226
226
  def decode(self, url: str):
227
- if not JmModuleConfig.flag_decode_url_when_logging or '/search/' not in url:
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.flag_use_version_newer_if_behind:
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.flag_use_fix_timestamp:
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.flag_api_client_require_cookies:
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
- executor_log = default_jm_logging
149
+ EXECUTOR_LOG = default_jm_logging
150
150
 
151
151
  # 使用固定时间戳
152
- flag_use_fix_timestamp = True
152
+ FLAG_USE_FIX_TIMESTAMP = True
153
153
  # 移动端Client初始化cookies
154
- flag_api_client_require_cookies = True
154
+ FLAG_API_CLIENT_REQUIRE_COOKIES = True
155
155
  # log开关标记
156
- flag_enable_jm_log = True
156
+ FLAG_ENABLE_JM_LOG = True
157
157
  # log时解码url
158
- flag_decode_url_when_logging = True
158
+ FLAG_DECODE_URL_WHEN_LOGGING = True
159
159
  # 当内置的版本号落后时,使用最新的禁漫app版本号
160
- flag_use_version_newer_if_behind = True
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.flag_enable_jm_log is True:
323
- cls.executor_log(topic, msg)
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.flag_enable_jm_log = False
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
- default_option_dict: dict = {
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.default_option_dict)
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.flag_enable_jm_log
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.is_exists:
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.is_exists = file_exists(img_save_path)
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.is_exists:
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
- ) -> None:
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
- url = resp.url
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
- mkdir_if_not_exists(save_dir)
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.flag_enable_jm_log,
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
- dir_path = self.zip_album(album, photo_dict, zip_path)
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
- dir_path = self.zip_photo(photo, image_list, zip_path)
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
- return self.do_zip(photo_dir,
337
- zip_path,
338
- all_filepath,
339
- f'压缩章节[{photo.photo_id}]成功 → {zip_path}',
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
- return self.do_zip(album_dir,
361
- zip_path,
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 None
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
- backup_dir_to_zip(
373
- source_dir,
374
- zip_path,
375
- acceptor=lambda f: os.path.isdir(f) or self.unified_path(f) in all_filepath
376
- ).close()
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
- self.log(msg, 'finish')
379
- return self.unified_path(source_dir)
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
- # hook is_exists True to skip download
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.executor_log
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.executor_log = new_jm_log
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.8
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
@@ -1,5 +1,5 @@
1
- commonX>=0.6.4
2
1
  curl_cffi
2
+ commonX
3
3
  PyYAML
4
4
  Pillow
5
5
  pycryptodome
File without changes
File without changes
File without changes
File without changes
File without changes