jmcomic 2.6.5__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.5'
5
+ __version__ = '2.6.7'
6
6
 
7
7
  from .api import *
8
8
  from .jm_plugin import *
jmcomic/jm_client_impl.py CHANGED
@@ -26,6 +26,7 @@ class AbstractJmClient(
26
26
  super().__init__(postman)
27
27
  self.retry_times = retry_times
28
28
  self.domain_list = domain_list
29
+ self.domain_retry_strategy = None
29
30
  self.CLIENT_CACHE = None
30
31
  self._username = None # help for favorite_folder method
31
32
  self.enable_cache()
@@ -44,23 +45,14 @@ class AbstractJmClient(
44
45
  return JmcomicText.format_url(api_path, domain)
45
46
 
46
47
  def get_jm_image(self, img_url) -> JmImageResp:
47
-
48
- def callback(resp):
49
- """
50
- 使用此方法包装 self.get,使得图片数据为空时,判定为请求失败时,走重试逻辑
51
- """
52
- resp = JmImageResp(resp)
53
- resp.require_success()
54
- return resp
55
-
56
- return self.get(img_url, callback=callback, headers=JmModuleConfig.new_html_headers())
48
+ return self.get(img_url, is_image=True, headers=JmModuleConfig.new_html_headers())
57
49
 
58
50
  def request_with_retry(self,
59
51
  request,
60
52
  url,
61
53
  domain_index=0,
62
54
  retry_count=0,
63
- callback=None,
55
+ is_image=False,
64
56
  **kwargs,
65
57
  ):
66
58
  """
@@ -74,11 +66,19 @@ class AbstractJmClient(
74
66
  :param url: 图片url / path (/album/xxx)
75
67
  :param domain_index: 域名下标
76
68
  :param retry_count: 重试次数
77
- :param callback: 回调,可以接收resp返回新的resp,也可以抛出异常强制重试
69
+ :param is_image: 是否是图片请求
78
70
  :param kwargs: 请求方法的kwargs
79
71
  """
72
+ if self.domain_retry_strategy:
73
+ return self.domain_retry_strategy(self,
74
+ request,
75
+ url,
76
+ is_image,
77
+ **kwargs,
78
+ )
79
+
80
80
  if domain_index >= len(self.domain_list):
81
- return self.fallback(request, url, domain_index, retry_count, **kwargs)
81
+ return self.fallback(request, url, domain_index, retry_count, is_image, **kwargs)
82
82
 
83
83
  url_backup = url
84
84
 
@@ -87,12 +87,12 @@ class AbstractJmClient(
87
87
  domain = self.domain_list[domain_index]
88
88
  url = self.of_api_url(url, domain)
89
89
 
90
- self.update_request_with_specify_domain(kwargs, domain)
90
+ self.update_request_with_specify_domain(kwargs, domain, is_image)
91
91
 
92
92
  jm_log(self.log_topic(), self.decode(url))
93
- else:
93
+ elif is_image:
94
94
  # 图片url
95
- self.update_request_with_specify_domain(kwargs, None, True)
95
+ self.update_request_with_specify_domain(kwargs, None, is_image)
96
96
 
97
97
  if domain_index != 0 or retry_count != 0:
98
98
  jm_log(f'req.retry',
@@ -106,14 +106,8 @@ class AbstractJmClient(
106
106
 
107
107
  try:
108
108
  resp = request(url, **kwargs)
109
-
110
- # 回调,可以接收resp返回新的resp,也可以抛出异常强制重试
111
- if callback is not None:
112
- resp = callback(resp)
113
-
114
- # 依然是回调,在最后返回之前,还可以判断resp是否重试
115
- resp = self.raise_if_resp_should_retry(resp)
116
-
109
+ # 在最后返回之前,还可以判断resp是否重试
110
+ resp = self.raise_if_resp_should_retry(resp, is_image)
117
111
  return resp
118
112
  except Exception as e:
119
113
  if self.retry_times == 0:
@@ -122,15 +116,19 @@ class AbstractJmClient(
122
116
  self.before_retry(e, kwargs, retry_count, url)
123
117
 
124
118
  if retry_count < self.retry_times:
125
- return self.request_with_retry(request, url_backup, domain_index, retry_count + 1, callback, **kwargs)
119
+ return self.request_with_retry(request, url_backup, domain_index, retry_count + 1, is_image, **kwargs)
126
120
  else:
127
- return self.request_with_retry(request, url_backup, domain_index + 1, 0, callback, **kwargs)
121
+ return self.request_with_retry(request, url_backup, domain_index + 1, 0, is_image, **kwargs)
128
122
 
129
123
  # noinspection PyMethodMayBeStatic
130
- def raise_if_resp_should_retry(self, resp):
124
+ def raise_if_resp_should_retry(self, resp, is_image):
131
125
  """
132
126
  依然是回调,在最后返回之前,还可以判断resp是否重试
133
127
  """
128
+ if is_image is True:
129
+ resp = JmImageResp(resp)
130
+ resp.require_success()
131
+
134
132
  return resp
135
133
 
136
134
  def update_request_with_specify_domain(self, kwargs: dict, domain: Optional[str], is_image: bool = False):
@@ -208,7 +206,7 @@ class AbstractJmClient(
208
206
  self.domain_list = domain_list
209
207
 
210
208
  # noinspection PyUnusedLocal
211
- def fallback(self, request, url, domain_index, retry_count, **kwargs):
209
+ def fallback(self, request, url, domain_index, retry_count, is_image, **kwargs):
212
210
  msg = f"请求重试全部失败: [{url}], {self.domain_list}"
213
211
  jm_log('req.fallback', msg)
214
212
  ExceptionTool.raises(msg, {}, RequestRetryAllFailException)
@@ -897,7 +895,7 @@ class JmApiClient(AbstractJmClient):
897
895
  检查返回数据中的status字段是否为ok
898
896
  """
899
897
  data = resp.model_data
900
- if data.status == 'ok':
898
+ if data.status != 'ok':
901
899
  ExceptionTool.raises_resp(data.msg, resp)
902
900
 
903
901
  def req_api(self, url, get=True, require_success=True, **kwargs) -> JmApiResp:
@@ -965,17 +963,16 @@ class JmApiClient(AbstractJmClient):
965
963
  # 2. 是否是特殊的内容
966
964
  # 暂无
967
965
 
968
- def raise_if_resp_should_retry(self, resp):
966
+ def raise_if_resp_should_retry(self, resp, is_image):
969
967
  """
970
968
  该方法会判断resp返回值是否是json格式,
971
969
  如果不是,大概率是禁漫内部异常,需要进行重试
972
970
 
973
971
  由于完整的json格式校验会有性能开销,所以只做简单的检查,
974
972
  只校验第一个有效字符是不是 '{',如果不是,就认为异常数据,需要重试
975
-
976
- :param resp: 响应对象
977
- :return: resp
978
973
  """
974
+ resp = super().raise_if_resp_should_retry(resp, is_image)
975
+
979
976
  if isinstance(resp, JmResp):
980
977
  # 不对包装过的resp对象做校验,包装者自行校验
981
978
  # 例如图片请求
@@ -998,7 +995,7 @@ class JmApiClient(AbstractJmClient):
998
995
  # 找到第一个有效字符
999
996
  ExceptionTool.require_true(
1000
997
  char == '{',
1001
- f'请求不是json格式,强制重试!响应文本: [{resp.text}]'
998
+ f'请求不是json格式,强制重试!响应文本: [{JmcomicText.limit_text(text, 200)}]'
1002
999
  )
1003
1000
  return resp
1004
1001
 
@@ -1015,6 +1012,23 @@ class JmApiClient(AbstractJmClient):
1015
1012
 
1016
1013
  client_update_domain_lock = Lock()
1017
1014
 
1015
+ def req_api_domain_server(self, url):
1016
+ resp = self.postman.get(url)
1017
+ text: str = resp.text
1018
+ # 去掉开头非ascii字符
1019
+ while text and not text[0].isascii():
1020
+ text = text[1:]
1021
+ res_json = JmCryptoTool.decode_resp_data(text, '', JmMagicConstants.API_DOMAIN_SERVER_SECRET)
1022
+ res_data = json_loads(res_json)
1023
+
1024
+ # 检查返回值
1025
+ if not res_data.get('Server', None):
1026
+ jm_log('api.update_domain.empty',
1027
+ f'获取禁漫最新API域名失败, 返回值: {res_json}')
1028
+ return None
1029
+ else:
1030
+ return res_data['Server']
1031
+
1018
1032
  def update_api_domain(self):
1019
1033
  if True is JmModuleConfig.FLAG_API_CLIENT_AUTO_UPDATE_DOMAIN_DONE:
1020
1034
  return
@@ -1022,33 +1036,29 @@ class JmApiClient(AbstractJmClient):
1022
1036
  with self.client_update_domain_lock:
1023
1037
  if True is JmModuleConfig.FLAG_API_CLIENT_AUTO_UPDATE_DOMAIN_DONE:
1024
1038
  return
1025
- try:
1026
- # 获取域名列表
1027
- resp = self.postman.get(JmModuleConfig.API_URL_DOMAIN_SERVER)
1028
- res_json = JmCryptoTool.decode_resp_data(resp.text, '', JmMagicConstants.API_DOMAIN_SERVER_SECRET)
1029
- res_data = json_loads(res_json)
1030
-
1031
- # 检查返回值
1032
- if not res_data.get('Server', None):
1033
- jm_log('api.update_domain.empty',
1034
- f'获取禁漫最新API域名失败, 返回值: {res_json}')
1035
- return
1036
- new_server_list: List[str] = res_data['Server']
1037
- old_server_list = JmModuleConfig.DOMAIN_API_LIST
1038
- jm_log('api.update_domain.success',
1039
- f'获取到最新的API域名,替换jmcomic内置域名:(new){new_server_list} ---→ (old){old_server_list}'
1040
- )
1041
- # 更新域名
1042
- if self.domain_list is old_server_list:
1043
- self.domain_list = new_server_list
1044
- JmModuleConfig.DOMAIN_API_LIST = new_server_list
1045
- except Exception as e:
1046
- jm_log('api.update_domain.error',
1047
- f'自动更新API域名失败,仍使用jmcomic内置域名。'
1048
- f'可通过代码[JmModuleConfig.FLAG_API_CLIENT_AUTO_UPDATE_DOMAIN=False]关闭自动更新API域名. 异常: {e}'
1049
- )
1050
- finally:
1051
- JmModuleConfig.FLAG_API_CLIENT_AUTO_UPDATE_DOMAIN_DONE = True
1039
+ # 遍历多个域名服务器
1040
+ for url in JmModuleConfig.API_URL_DOMAIN_SERVER_LIST:
1041
+ try:
1042
+ # 获取域名列表
1043
+ new_server_list = self.req_api_domain_server(url)
1044
+ if new_server_list is None:
1045
+ continue
1046
+ old_server_list = JmModuleConfig.DOMAIN_API_LIST
1047
+ jm_log('api.update_domain.success',
1048
+ f'获取到最新的API域名,替换jmcomic内置域名:(new){new_server_list} ---→ (old){old_server_list}'
1049
+ )
1050
+ # 更新域名
1051
+ if sorted(self.domain_list) == sorted(old_server_list):
1052
+ self.domain_list = new_server_list
1053
+ JmModuleConfig.DOMAIN_API_LIST = new_server_list
1054
+ break
1055
+ except Exception as e:
1056
+ jm_log('api.update_domain.error',
1057
+ f'通过[{url}]自动更新API域名失败,仍使用jmcomic内置域名。'
1058
+ f'可通过代码[JmModuleConfig.FLAG_API_CLIENT_AUTO_UPDATE_DOMAIN=False]关闭自动更新API域名. 异常: {e}'
1059
+ )
1060
+ # set done finally
1061
+ JmModuleConfig.FLAG_API_CLIENT_AUTO_UPDATE_DOMAIN_DONE = True
1052
1062
 
1053
1063
  client_init_cookies_lock = Lock()
1054
1064
 
@@ -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_config.py CHANGED
@@ -77,7 +77,7 @@ class JmMagicConstants:
77
77
  APP_TOKEN_SECRET_2 = '18comicAPPContent'
78
78
  APP_DATA_SECRET = '185Hcomic3PAPP7R'
79
79
  API_DOMAIN_SERVER_SECRET = 'diosfjckwpqpdfjkvnqQjsik'
80
- APP_VERSION = '1.8.0'
80
+ APP_VERSION = '2.0.6'
81
81
 
82
82
 
83
83
  # 模块级别共用配置
@@ -135,7 +135,10 @@ class JmModuleConfig:
135
135
  ''')
136
136
 
137
137
  # 获取最新移动端API域名的地址
138
- API_URL_DOMAIN_SERVER = f'{PROT}jmapp03-1308024008.cos.ap-jakarta.myqcloud.com/server-2024.txt'
138
+ API_URL_DOMAIN_SERVER_LIST = shuffled('''
139
+ https://rup4a04-c01.tos-ap-southeast-1.bytepluses.com/newsvr-2025.txt
140
+ https://rup4a04-c02.tos-cn-hongkong.bytepluses.com/newsvr-2025.txt
141
+ ''')
139
142
 
140
143
  APP_HEADERS_TEMPLATE = {
141
144
  'Accept-Encoding': 'gzip, deflate',
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
@@ -1216,3 +1216,93 @@ class ReplacePathStringPlugin(JmOptionPlugin):
1216
1216
  return original_path
1217
1217
 
1218
1218
  self.option.decide_image_save_dir = new_decide_dir
1219
+
1220
+
1221
+ class AdvancedRetryPlugin(JmOptionPlugin):
1222
+ plugin_key = 'advanced-retry'
1223
+
1224
+ def __init__(self, option: JmOption):
1225
+ super().__init__(option)
1226
+ self.retry_config = None
1227
+
1228
+ def invoke(self,
1229
+ retry_config,
1230
+ **kwargs):
1231
+ self.require_param(isinstance(retry_config, dict), '必须配置retry_config为dict')
1232
+ self.retry_config = retry_config
1233
+
1234
+ new_jm_client: Callable = self.option.new_jm_client
1235
+
1236
+ def hook_new_jm_client(*args, **kwargs):
1237
+ client: JmcomicClient = new_jm_client(*args, **kwargs)
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()
1242
+ return client
1243
+
1244
+ self.option.new_jm_client = hook_new_jm_client
1245
+
1246
+ def request_with_retry(self,
1247
+ client: AbstractJmClient,
1248
+ request: Callable,
1249
+ url: str,
1250
+ is_image: bool,
1251
+ **kwargs,
1252
+ ):
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.5
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=dK6DD9-2IN7Bo5y5pYeX1-LWii9kAjf8spTfaHfEfmw,902
2
- jmcomic/api.py,sha256=ZduhXDmh4lg1dkXHs7UTAaPpYNO7kcdPCDH5JJT9KSI,4253
3
- jmcomic/cl.py,sha256=PBSh0JndNFZw3B7WJPj5Y8SeFdKzHE00jIwYo9An-K0,3475
4
- jmcomic/jm_client_impl.py,sha256=huPWXkEizu8x7J4rAk_iQROnXphPoyadXyvN8n1ENqI,41624
5
- jmcomic/jm_client_interface.py,sha256=Bld80cYwBwIqcMSJK09g5q8U_UX3IWnQJxuRJQwmIOk,19082
6
- jmcomic/jm_config.py,sha256=8oWw7BiQh9Jimhn5Des8Dk0YL4gEnjp31yKBhwiS8sQ,17113
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=x42eqhRcnHElTif8ORXw2jZh33Xnq55-1dpC6tmr_lQ,40894
12
- jmcomic/jm_toolkit.py,sha256=SIYCRZ6P4SPbAQA03U5z2DNiDldY7hWsAvKTdEzyK9U,29683
13
- jmcomic-2.6.5.dist-info/licenses/LICENSE,sha256=kz4coTxZxuGxisK3W00tjK57Zh3RcMGq-EnbXrK7-xA,1064
14
- jmcomic-2.6.5.dist-info/METADATA,sha256=wz0cF1HnDaeaj6YRev35IYubXqSrIXs7WCJf3ZgiICQ,9572
15
- jmcomic-2.6.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
16
- jmcomic-2.6.5.dist-info/entry_points.txt,sha256=tRbQltaGSBjejI0c9jYt-4SXQMd5nSDHcMvHmuTy4ow,44
17
- jmcomic-2.6.5.dist-info/top_level.txt,sha256=puvVMFYJqIbd6NOTMEvOyugMTT8woBfSQyxEBan3zY4,8
18
- jmcomic-2.6.5.dist-info/RECORD,,