jmcomic 2.6.6__py3-none-any.whl → 2.6.7__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.6.6'
5
+ __version__ = '2.6.7'
6
6
 
7
7
  from .api import *
8
8
  from .jm_plugin import *
jmcomic/jm_client_impl.py CHANGED
@@ -895,7 +895,7 @@ class JmApiClient(AbstractJmClient):
895
895
  检查返回数据中的status字段是否为ok
896
896
  """
897
897
  data = resp.model_data
898
- if data.status == 'ok':
898
+ if data.status != 'ok':
899
899
  ExceptionTool.raises_resp(data.msg, resp)
900
900
 
901
901
  def req_api(self, url, get=True, require_success=True, **kwargs) -> JmApiResp:
@@ -995,7 +995,7 @@ class JmApiClient(AbstractJmClient):
995
995
  # 找到第一个有效字符
996
996
  ExceptionTool.require_true(
997
997
  char == '{',
998
- f'请求不是json格式,强制重试!响应文本: [{resp.text}]'
998
+ f'请求不是json格式,强制重试!响应文本: [{JmcomicText.limit_text(text, 200)}]'
999
999
  )
1000
1000
  return resp
1001
1001
 
@@ -101,6 +101,14 @@ class JmApiResp(JmJsonResp):
101
101
  super().__init__(resp)
102
102
  self.ts = ts
103
103
 
104
+ # 重写json()方法,可以忽略一些非json格式的脏数据
105
+ @field_cache()
106
+ def json(self) -> Dict:
107
+ try:
108
+ return JmcomicText.try_parse_json_object(self.resp.text)
109
+ except Exception as e:
110
+ ExceptionTool.raises_resp(f'json解析失败: {e}', self, JsonResolveFailException)
111
+
104
112
  @property
105
113
  def is_success(self) -> bool:
106
114
  return super().is_success and self.json()['code'] == 200
jmcomic/jm_option.py CHANGED
@@ -419,7 +419,7 @@ class JmOption:
419
419
  if clazz == AbstractJmClient or not issubclass(clazz, AbstractJmClient):
420
420
  raise NotImplementedError(clazz)
421
421
 
422
- client: AbstractJmClient = clazz(
422
+ client: JmcomicClient = clazz(
423
423
  postman=postman,
424
424
  domain_list=decide_domain_list(),
425
425
  retry_times=retry_times,
jmcomic/jm_plugin.py CHANGED
@@ -1221,23 +1221,88 @@ class ReplacePathStringPlugin(JmOptionPlugin):
1221
1221
  class AdvancedRetryPlugin(JmOptionPlugin):
1222
1222
  plugin_key = 'advanced-retry'
1223
1223
 
1224
+ def __init__(self, option: JmOption):
1225
+ super().__init__(option)
1226
+ self.retry_config = None
1227
+
1224
1228
  def invoke(self,
1225
1229
  retry_config,
1226
1230
  **kwargs):
1231
+ self.require_param(isinstance(retry_config, dict), '必须配置retry_config为dict')
1232
+ self.retry_config = retry_config
1233
+
1227
1234
  new_jm_client: Callable = self.option.new_jm_client
1228
1235
 
1229
1236
  def hook_new_jm_client(*args, **kwargs):
1230
- client: AbstractJmClient = new_jm_client(*args, **kwargs)
1237
+ client: JmcomicClient = new_jm_client(*args, **kwargs)
1231
1238
  client.domain_retry_strategy = self.request_with_retry
1239
+ client.domain_req_failed_counter = {}
1240
+ from threading import Lock
1241
+ client.domain_counter_lock = Lock()
1232
1242
  return client
1233
1243
 
1234
1244
  self.option.new_jm_client = hook_new_jm_client
1235
1245
 
1236
1246
  def request_with_retry(self,
1237
- client,
1238
- request,
1239
- url,
1240
- is_image,
1247
+ client: AbstractJmClient,
1248
+ request: Callable,
1249
+ url: str,
1250
+ is_image: bool,
1241
1251
  **kwargs,
1242
1252
  ):
1243
- pass
1253
+ """
1254
+ 实现如下域名重试机制:
1255
+ - 对域名列表轮询请求,配置:retry_rounds
1256
+ - 限制单个域名最大失败次数,配置:retry_domain_max_times
1257
+ - 轮询域名列表前,根据历史失败次数对域名列表排序,失败多的后置
1258
+ """
1259
+
1260
+ def do_request(domain):
1261
+ url_to_use = url
1262
+ if url_to_use.startswith('/'):
1263
+ # path → url
1264
+ url_to_use = client.of_api_url(url, domain)
1265
+ client.update_request_with_specify_domain(kwargs, domain, is_image)
1266
+ jm_log(client.log_topic(), client.decode(url_to_use))
1267
+ elif is_image:
1268
+ # 图片url
1269
+ client.update_request_with_specify_domain(kwargs, None, is_image)
1270
+
1271
+ resp = request(url_to_use, **kwargs)
1272
+ resp = client.raise_if_resp_should_retry(resp, is_image)
1273
+ return resp
1274
+
1275
+ retry_domain_max_times: int = self.retry_config['retry_domain_max_times']
1276
+ retry_rounds: int = self.retry_config['retry_rounds']
1277
+ for rindex in range(retry_rounds):
1278
+ domain_list = self.get_sorted_domain(client, retry_domain_max_times)
1279
+ for i, domain in enumerate(domain_list):
1280
+ if self.failed_count(client, domain) >= retry_domain_max_times:
1281
+ continue
1282
+
1283
+ try:
1284
+ return do_request(domain)
1285
+ except Exception as e:
1286
+ from common import traceback_print_exec
1287
+ traceback_print_exec()
1288
+ jm_log('req.error', str(e))
1289
+ self.update_failed_count(client, domain)
1290
+
1291
+ return client.fallback(request, url, 0, 0, is_image, **kwargs)
1292
+
1293
+ def get_sorted_domain(self, client: JmcomicClient, times):
1294
+ domain_list = client.get_domain_list()
1295
+ return sorted(
1296
+ filter(lambda d: self.failed_count(client, d) < times, domain_list),
1297
+ key=lambda d: self.failed_count(client, d)
1298
+ )
1299
+
1300
+ # noinspection PyUnresolvedReferences
1301
+ def update_failed_count(self, client: AbstractJmClient, domain: str):
1302
+ with client.domain_counter_lock:
1303
+ client.domain_req_failed_counter[domain] = self.failed_count(client, domain) + 1
1304
+
1305
+ @staticmethod
1306
+ def failed_count(client: JmcomicClient, domain: str) -> int:
1307
+ # noinspection PyUnresolvedReferences
1308
+ return client.domain_req_failed_counter.get(domain, 0)
jmcomic/jm_toolkit.py CHANGED
@@ -61,6 +61,8 @@ class JmcomicText:
61
61
 
62
62
  # 提取接口返回值信息
63
63
  pattern_ajax_favorite_msg = compile(r'</button>(.*?)</div>')
64
+ # 提取api接口返回值里的json,防止返回值里有无关日志导致json解析报错
65
+ pattern_api_response_json_object = compile(r'\{[\s\S]*?}')
64
66
 
65
67
  @classmethod
66
68
  def parse_to_jm_domain(cls, text: str):
@@ -344,6 +346,28 @@ class JmcomicText:
344
346
  raise e
345
347
  return save_dir
346
348
 
349
+ # noinspection PyTypeChecker
350
+ @classmethod
351
+ def try_parse_json_object(cls, resp_text: str) -> dict:
352
+ import json
353
+ text = resp_text.strip()
354
+ if text.startswith('{') and text.endswith('}'):
355
+ # fast case
356
+ return json.loads(text)
357
+
358
+ for match in cls.pattern_api_response_json_object.finditer(text):
359
+ try:
360
+ return json.loads(match.group(0))
361
+ except Exception as e:
362
+ jm_log('parse_json_object.error', e)
363
+
364
+ raise AssertionError(f'未解析出json数据: {cls.limit_text(resp_text, 200)}')
365
+
366
+ @classmethod
367
+ def limit_text(cls, text: str, limit: int) -> str:
368
+ length = len(text)
369
+ return text if length <= limit else (text[:limit] + f'...({length - limit}')
370
+
347
371
 
348
372
  # 支持dsl: #{???} -> os.getenv(???)
349
373
  JmcomicText.dsl_replacer.add_dsl_and_replacer(r'\$\{(.*?)\}', JmcomicText.match_os_env)
@@ -450,10 +474,7 @@ class JmPageTool:
450
474
  # 这里不作解析,因为没什么用...
451
475
  tags = cls.pattern_html_search_tags.findall(tag_text)
452
476
  content.append((
453
- album_id, {
454
- 'name': title, # 改成name是为了兼容 parse_api_resp_to_page
455
- 'tags': tags
456
- }
477
+ album_id, dict(name=title, tags=tags) # 改成name是为了兼容 parse_api_resp_to_page
457
478
  ))
458
479
 
459
480
  return JmSearchPage(content, total)
@@ -468,10 +489,7 @@ class JmPageTool:
468
489
  for (album_id, title, tag_text) in album_info_list:
469
490
  tags = cls.pattern_html_search_tags.findall(tag_text)
470
491
  content.append((
471
- album_id, {
472
- 'name': title, # 改成name是为了兼容 parse_api_resp_to_page
473
- 'tags': tags
474
- }
492
+ album_id, dict(name=title, tags=tags) # 改成name是为了兼容 parse_api_resp_to_page
475
493
  ))
476
494
 
477
495
  return JmSearchPage(content, total)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jmcomic
3
- Version: 2.6.6
3
+ Version: 2.6.7
4
4
  Summary: Python API For JMComic (禁漫天堂)
5
5
  Home-page: https://github.com/hect0x7/JMComic-Crawler-Python
6
6
  Author: hect0x7
@@ -0,0 +1,18 @@
1
+ jmcomic/__init__.py,sha256=Rg9DJ3YptC-BImHcbNB4cqM6-ZZMMtPGy2hw3-G7hZY,902
2
+ jmcomic/api.py,sha256=ZduhXDmh4lg1dkXHs7UTAaPpYNO7kcdPCDH5JJT9KSI,4253
3
+ jmcomic/cl.py,sha256=PBSh0JndNFZw3B7WJPj5Y8SeFdKzHE00jIwYo9An-K0,3475
4
+ jmcomic/jm_client_impl.py,sha256=sDrhqlO6zHAR0vlGFoKXmQ85l89BwksfG8BHGk7eXJ4,42165
5
+ jmcomic/jm_client_interface.py,sha256=4TDz-V8XLWPPxogLI-xiiHug4n6iir6UNeiKx9-KTXM,19410
6
+ jmcomic/jm_config.py,sha256=8GDETnzQObZabd441Boc--Mb--J2kr7HLxxViKB0DvI,17213
7
+ jmcomic/jm_downloader.py,sha256=azYkIN-1OuEivZ1TGGDaYo1FkcO5KatSb0g4GVdKSPY,11041
8
+ jmcomic/jm_entity.py,sha256=ZyXVy3mOl9qkxlw6hyshVS3RR-yKfO5BkJHtyBAoKQE,20053
9
+ jmcomic/jm_exception.py,sha256=x3KGMLlQS2zi1GX7z5G58zJN2EwLkI4mAURkxZYjEvA,5055
10
+ jmcomic/jm_option.py,sha256=T__PXJgzITdjyr2PMdk98jYMIKkUVGUA30p5lsMOTvQ,21244
11
+ jmcomic/jm_plugin.py,sha256=OekMjPVS25PMLcHV0r434BBvy0vbszlNiU5wODCZ3js,44524
12
+ jmcomic/jm_toolkit.py,sha256=9vwk4Q2gfh_04BhJxEoSsZcDT6ICh-wsN70WR4LSD9I,30511
13
+ jmcomic-2.6.7.dist-info/licenses/LICENSE,sha256=kz4coTxZxuGxisK3W00tjK57Zh3RcMGq-EnbXrK7-xA,1064
14
+ jmcomic-2.6.7.dist-info/METADATA,sha256=cuqBqo5Bp2GHItulju3coNt5dU9_UMtY1vcN0G0m0jk,9572
15
+ jmcomic-2.6.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
16
+ jmcomic-2.6.7.dist-info/entry_points.txt,sha256=tRbQltaGSBjejI0c9jYt-4SXQMd5nSDHcMvHmuTy4ow,44
17
+ jmcomic-2.6.7.dist-info/top_level.txt,sha256=puvVMFYJqIbd6NOTMEvOyugMTT8woBfSQyxEBan3zY4,8
18
+ jmcomic-2.6.7.dist-info/RECORD,,
@@ -1,18 +0,0 @@
1
- jmcomic/__init__.py,sha256=jl9p808Fwi2NHktr1RmupkJuZ4GbRqTzExT2CouapZY,902
2
- jmcomic/api.py,sha256=ZduhXDmh4lg1dkXHs7UTAaPpYNO7kcdPCDH5JJT9KSI,4253
3
- jmcomic/cl.py,sha256=PBSh0JndNFZw3B7WJPj5Y8SeFdKzHE00jIwYo9An-K0,3475
4
- jmcomic/jm_client_impl.py,sha256=atj557YHa0LDOezL9TSZrRA5lV9fs7CJzFLD1_MrONU,42141
5
- jmcomic/jm_client_interface.py,sha256=Bld80cYwBwIqcMSJK09g5q8U_UX3IWnQJxuRJQwmIOk,19082
6
- jmcomic/jm_config.py,sha256=8GDETnzQObZabd441Boc--Mb--J2kr7HLxxViKB0DvI,17213
7
- jmcomic/jm_downloader.py,sha256=azYkIN-1OuEivZ1TGGDaYo1FkcO5KatSb0g4GVdKSPY,11041
8
- jmcomic/jm_entity.py,sha256=ZyXVy3mOl9qkxlw6hyshVS3RR-yKfO5BkJHtyBAoKQE,20053
9
- jmcomic/jm_exception.py,sha256=x3KGMLlQS2zi1GX7z5G58zJN2EwLkI4mAURkxZYjEvA,5055
10
- jmcomic/jm_option.py,sha256=kErkn9-tXYp4h4lDQvc8jDYG1vqOMLIdIb3fbHvSsbA,21247
11
- jmcomic/jm_plugin.py,sha256=0SnL6bc5kUeDoculz_YxlOdxFFg1B0EkAcDV1K0GIAk,41633
12
- jmcomic/jm_toolkit.py,sha256=SIYCRZ6P4SPbAQA03U5z2DNiDldY7hWsAvKTdEzyK9U,29683
13
- jmcomic-2.6.6.dist-info/licenses/LICENSE,sha256=kz4coTxZxuGxisK3W00tjK57Zh3RcMGq-EnbXrK7-xA,1064
14
- jmcomic-2.6.6.dist-info/METADATA,sha256=bTugkFXmdS6nzfEk7hL6jCcweUBg0ntz3ksRaHJtcrM,9572
15
- jmcomic-2.6.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
16
- jmcomic-2.6.6.dist-info/entry_points.txt,sha256=tRbQltaGSBjejI0c9jYt-4SXQMd5nSDHcMvHmuTy4ow,44
17
- jmcomic-2.6.6.dist-info/top_level.txt,sha256=puvVMFYJqIbd6NOTMEvOyugMTT8woBfSQyxEBan3zY4,8
18
- jmcomic-2.6.6.dist-info/RECORD,,