jmcomic 2.1.15__py3-none-any.whl → 2.1.17__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,6 +2,6 @@
2
2
  # 被依赖方 <--- 使用方
3
3
  # config <--- entity <--- toolkit <--- client <--- option <--- downloader
4
4
 
5
- __version__ = '2.1.15'
5
+ __version__ = '2.1.17'
6
6
 
7
7
  from .api import *
jmcomic/jm_client_impl.py CHANGED
@@ -143,7 +143,7 @@ class AbstractJmClient(
143
143
  def fallback(self, request, url, domain_index, retry_count, **kwargs):
144
144
  msg = f"请求重试全部失败: [{url}], {self.domain_list}"
145
145
  jm_debug('req.fallback', msg)
146
- raise AssertionError(msg)
146
+ raise JmModuleConfig.exception(msg)
147
147
 
148
148
 
149
149
  # 基于网页实现的JmClient
@@ -216,7 +216,7 @@ class JmHtmlClient(AbstractJmClient):
216
216
  )
217
217
 
218
218
  if resp.status_code != 301:
219
- raise AssertionError(f'登录失败,状态码为{resp.status_code}')
219
+ raise JmModuleConfig.exception(f'登录失败,状态码为{resp.status_code}')
220
220
 
221
221
  if refresh_client_cookies is True:
222
222
  self['cookies'] = resp.cookies
@@ -251,7 +251,7 @@ class JmHtmlClient(AbstractJmClient):
251
251
  + (f"响应文本=[{resp.text}]" if len(resp.text) < 200 else
252
252
  f'响应文本过长(len={len(resp.text)}),不打印'
253
253
  )
254
- raise AssertionError(msg)
254
+ raise JmModuleConfig.exception(msg)
255
255
 
256
256
  def get_jm_image(self, img_url) -> JmImageResp:
257
257
 
@@ -418,6 +418,9 @@ class JmApiClient(AbstractJmClient):
418
418
  "accept-encoding": "gzip",
419
419
  }, key_ts
420
420
 
421
+ def debug_topic_request(self):
422
+ return 'api'
423
+
421
424
 
422
425
  class AsyncSaveImageClient(JmImageClient):
423
426
 
@@ -23,13 +23,13 @@ class JmResp(CommonResp):
23
23
  class JmImageResp(JmResp):
24
24
 
25
25
  def json(self, **kwargs) -> Dict:
26
- raise AssertionError
26
+ raise NotImplementedError
27
27
 
28
28
  def require_success(self):
29
29
  if self.is_success:
30
30
  return
31
31
 
32
- raise AssertionError(self.get_error_msg())
32
+ raise JmModuleConfig.exception(self.get_error_msg())
33
33
 
34
34
  def get_error_msg(self):
35
35
  msg = f'禁漫图片获取失败: [{self.url}]'
@@ -68,7 +68,7 @@ class JmApiResp(JmResp):
68
68
  @classmethod
69
69
  def wrap(cls, resp, key_ts):
70
70
  if isinstance(resp, JmApiResp):
71
- raise AssertionError('重复包装')
71
+ raise JmModuleConfig.exception('重复包装')
72
72
 
73
73
  return cls(resp, key_ts)
74
74
 
jmcomic/jm_config.py CHANGED
@@ -17,11 +17,19 @@ def default_postman_constructor(session, **kwargs):
17
17
  return Postmans.new_postman(**kwargs)
18
18
 
19
19
 
20
+ def default_raise_regex_error(msg, *_args, **_kwargs):
21
+ raise JmModuleConfig.exception(msg)
22
+
23
+
24
+ class JmcomicException(Exception):
25
+ pass
26
+
27
+
20
28
  class JmModuleConfig:
21
29
  # 网站相关
22
30
  PROT = "https://"
23
31
  JM_REDIRECT_URL = f'{PROT}jm365.work/3YeBdF' # 永久網域,怕走失的小伙伴收藏起来
24
- JM_PUB_URL = f'{PROT}jmcomic2.bet'
32
+ JM_PUB_URL = f'{PROT}jmcomic.ltd'
25
33
  JM_CDN_IMAGE_URL_TEMPLATE = PROT + 'cdn-msp.{domain}/media/photos/{photo_id}/{index:05}{suffix}' # index 从1开始
26
34
  JM_IMAGE_SUFFIX = ['.jpg', '.webp', '.png', '.gif']
27
35
 
@@ -56,12 +64,17 @@ class JmModuleConfig:
56
64
  CLASS_OPTION = None
57
65
  CLASS_ALBUM = None
58
66
  CLASS_PHOTO = None
67
+ CLASS_IMAGE = None
59
68
  CLASS_CLIENT_IMPL = {}
69
+ CLASS_EXCEPTION = None
60
70
 
61
71
  # 执行debug的函数
62
72
  debug_executor = default_jm_debug
63
73
  # postman构造函数
64
74
  postman_constructor = default_postman_constructor
75
+ # 网页正则表达式解析失败时,执行抛出异常的函数,可以替换掉用于debug
76
+ raise_regex_error_executor = default_raise_regex_error
77
+
65
78
  # debug开关标记
66
79
  enable_jm_debug = True
67
80
 
@@ -97,6 +110,14 @@ class JmModuleConfig:
97
110
  from .jm_entity import JmPhotoDetail
98
111
  return JmPhotoDetail
99
112
 
113
+ @classmethod
114
+ def image_class(cls):
115
+ if cls.CLASS_IMAGE is not None:
116
+ return cls.CLASS_IMAGE
117
+
118
+ from .jm_entity import JmImageDetail
119
+ return JmImageDetail
120
+
100
121
  @classmethod
101
122
  def client_impl_class(cls, client_key: str):
102
123
  client_impl_dict = cls.CLASS_CLIENT_IMPL
@@ -107,6 +128,13 @@ class JmModuleConfig:
107
128
 
108
129
  return impl_class
109
130
 
131
+ @classmethod
132
+ def exception(cls, msg: str):
133
+ if cls.CLASS_EXCEPTION is not None:
134
+ return cls.CLASS_EXCEPTION(msg)
135
+
136
+ return JmcomicException(msg)
137
+
110
138
  @classmethod
111
139
  @field_cache("DOMAIN")
112
140
  def domain(cls, postman=None):
@@ -163,7 +191,7 @@ class JmModuleConfig:
163
191
  postman = postman or cls.new_postman(session=True)
164
192
 
165
193
  url = postman.with_redirect_catching().get(cls.JM_REDIRECT_URL)
166
- cls.jm_debug('获取禁漫地址', f'[{cls.JM_REDIRECT_URL}] → [{url}]')
194
+ cls.jm_debug('获取禁漫URL', f'[{cls.JM_REDIRECT_URL}] → [{url}]')
167
195
  return url
168
196
 
169
197
  @classmethod
@@ -178,10 +206,13 @@ class JmModuleConfig:
178
206
 
179
207
  resp = postman.get(cls.JM_PUB_URL)
180
208
  if resp.status_code != 200:
181
- raise AssertionError(resp.text)
209
+ raise JmModuleConfig.exception(resp.text)
182
210
 
183
211
  from .jm_toolkit import JmcomicText
184
- return JmcomicText.analyse_jm_pub_html(resp.text)
212
+ domain_list = JmcomicText.analyse_jm_pub_html(resp.text)
213
+
214
+ cls.jm_debug('获取禁漫全部域名', f'[{resp.url}] → {domain_list}')
215
+ return domain_list
185
216
 
186
217
  album_comment_headers = {
187
218
  'authority': '18comic.vip',
jmcomic/jm_entity.py CHANGED
@@ -241,11 +241,11 @@ class JmPhotoDetail(DetailEntity):
241
241
  # 校验参数
242
242
  length = len(self.page_arr)
243
243
  if index >= length:
244
- raise AssertionError(f'创建JmImageDetail失败,{index} >= {length}')
244
+ raise JmModuleConfig.exception(f'创建JmImageDetail失败,{index} >= {length}')
245
245
 
246
246
  data_original = self.get_img_data_original(self.page_arr[index])
247
247
 
248
- return JmImageDetail.of(
248
+ return JmModuleConfig.image_class().of(
249
249
  self.photo_id,
250
250
  self.scramble_id,
251
251
  data_original,
@@ -262,7 +262,7 @@ class JmPhotoDetail(DetailEntity):
262
262
  """
263
263
  data_original_domain = self.data_original_domain
264
264
  if data_original_domain is None:
265
- raise AssertionError(f'图片域名为空: {self.__dict__}')
265
+ raise JmModuleConfig.exception(f'图片域名为空: {self.__dict__}')
266
266
 
267
267
  return f'https://{data_original_domain}/media/photos/{self.photo_id}/{img_name}'
268
268
 
@@ -330,13 +330,13 @@ class JmAlbumDetail(DetailEntity):
330
330
  length = len(self.episode_list)
331
331
 
332
332
  if index >= length:
333
- raise AssertionError(f'创建JmPhotoDetail失败,{index} >= {length}')
333
+ raise JmModuleConfig.exception(f'创建JmPhotoDetail失败,{index} >= {length}')
334
334
 
335
335
  # episode_info: ('212214', '81', '94 突然打來', '2020-08-29')
336
336
  episode_info: tuple = self.episode_list[index]
337
337
  photo_id, photo_index, photo_title, photo_pub_date = episode_info
338
338
 
339
- photo = JmPhotoDetail(
339
+ photo = JmModuleConfig.photo_class()(
340
340
  photo_id=photo_id,
341
341
  scramble_id=self.scramble_id,
342
342
  title=photo_title,
jmcomic/jm_option.py CHANGED
@@ -10,7 +10,8 @@ class DirRule:
10
10
  # 根目录 / Photo-序号&标题 /
11
11
  'Bd_Pindextitle',
12
12
  # 根目录 / Photo-自定义类属性 /
13
- 'Bd_Aauthor_Atitle_Pcustomfield', # 使用自定义类属性前,需替换 JmcomicText的 PhotoClass / AlbumClass
13
+ 'Bd_Aauthor_Atitle_Pcustomfield',
14
+ # 需要替换JmModuleConfig.CLASS_ALBUM / CLASS_PHOTO才能让自定义属性生效
14
15
  ]
15
16
 
16
17
  RuleFunc = Callable[[Union[JmAlbumDetail, JmPhotoDetail, None]], str]
@@ -45,8 +46,10 @@ class DirRule:
45
46
  path_ls.append(str(ret))
46
47
  except BaseException as e:
47
48
  # noinspection PyUnboundLocalVariable
48
- raise AssertionError(f'路径规则"{self.rule_dsl}"的第{i + 1}个解析出错: {e},'
49
- f'param is {param}')
49
+ raise JmModuleConfig.exception(
50
+ f'路径规则"{self.rule_dsl}"的第{i + 1}个解析出错: {e},'
51
+ f'param is {param}'
52
+ )
50
53
 
51
54
  return fix_filepath('/'.join(path_ls), is_dir=True)
52
55
 
@@ -214,7 +217,7 @@ class JmOption:
214
217
  filepath = self.filepath
215
218
 
216
219
  if filepath is None:
217
- raise AssertionError("未指定JmOption的保存路径")
220
+ raise JmModuleConfig.exception("未指定JmOption的保存路径")
218
221
 
219
222
  PackerUtil.pack(self.deconstruct(), filepath)
220
223
 
jmcomic/jm_toolkit.py CHANGED
@@ -52,7 +52,7 @@ class JmcomicText:
52
52
  return str(text)
53
53
 
54
54
  if not isinstance(text, str):
55
- raise AssertionError(f"无法解析jm车号, 参数类型为: {type(text)}")
55
+ raise JmModuleConfig.exception(f"无法解析jm车号, 参数类型为: {type(text)}")
56
56
 
57
57
  # 43210
58
58
  if text.isdigit():
@@ -60,7 +60,7 @@ class JmcomicText:
60
60
 
61
61
  # Jm43210
62
62
  if len(text) <= 2:
63
- raise AssertionError(f"无法解析jm车号, 文本为: {text}")
63
+ raise JmModuleConfig.exception(f"无法解析jm车号, 文本为: {text}")
64
64
 
65
65
  # text: JM12341
66
66
  c0 = text[0]
@@ -72,7 +72,7 @@ class JmcomicText:
72
72
  # https://xxx/photo/412038
73
73
  match = cls.pattern_jm_pa_id.search(text)
74
74
  if match is None:
75
- raise AssertionError(f"无法解析jm车号, 文本为: {text}")
75
+ raise JmModuleConfig.exception(f"无法解析jm车号, 文本为: {text}")
76
76
  return match[2]
77
77
 
78
78
  @classmethod
@@ -141,8 +141,12 @@ class JmcomicText:
141
141
  field_value = match_field(field_name, pattern_value, html)
142
142
 
143
143
  if field_value is None:
144
- write_text('./resp.txt', html) # debug
145
- raise AssertionError(f"文本没有匹配上字段:字段名为'{field_name}',pattern: [{pattern_value.pattern}]")
144
+ JmModuleConfig.raise_regex_error_executor(
145
+ f"文本没有匹配上字段:字段名为'{field_name}',pattern: [{pattern_value.pattern}]",
146
+ html,
147
+ field_name,
148
+ pattern_value
149
+ )
146
150
 
147
151
  # 保存字段
148
152
  field_dict[field_name] = field_value
@@ -167,9 +171,7 @@ class JmcomicText:
167
171
 
168
172
  class JmSearchSupport:
169
173
  # 用来缩减html的长度
170
- pattern_html_search_shorten_for = compile('<div class="well well-sm">([\s\S]*)'
171
- '<div class="row">[\s\S]*'
172
- '<div class="bot-per visible-xs visible-sm">')
174
+ pattern_html_search_shorten_for = compile('<div class="well well-sm">([\s\S]*)<div class="row">')
173
175
 
174
176
  # 用来提取搜索页面的的album的信息
175
177
  pattern_html_search_album_info_list = compile(
@@ -185,9 +187,24 @@ class JmSearchSupport:
185
187
  # 用来查找tag列表
186
188
  pattern_html_search_tag_list = compile('<a href=".*?">(.*?)</a>')
187
189
 
190
+ # 查找错误,例如 [错误,關鍵字過短,請至少輸入兩個字以上。]
191
+ pattern_html_search_error = compile('<fieldset>\n<legend>(.*?)</legend>\n<div class=.*?>\n(.*?)\n</div>\n</fieldset>')
192
+
188
193
  @classmethod
189
194
  def analyse_jm_search_html(cls, html: str) -> JmSearchPage:
190
- html = cls.pattern_html_search_shorten_for.search(html)[0]
195
+ # 检查是否失败
196
+ match = cls.pattern_html_search_error.search(html)
197
+ if match is not None:
198
+ topic, reason = match[1], match[2]
199
+ JmModuleConfig.raise_regex_error_executor(f'{topic}: {reason}', html)
200
+
201
+ # 缩小文本范围
202
+ match = cls.pattern_html_search_shorten_for.search(html)
203
+ if match is None:
204
+ JmModuleConfig.raise_regex_error_executor('未匹配到搜索结果', html)
205
+ html = match[0]
206
+
207
+ # 提取结果
191
208
  album_info_list = cls.pattern_html_search_album_info_list.findall(html)
192
209
 
193
210
  for i, (album_id, title, *args) in enumerate(album_info_list):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: jmcomic
3
- Version: 2.1.15
3
+ Version: 2.1.17
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,14 @@
1
+ jmcomic/__init__.py,sha256=P841NJsb9rk_BcH56LkydBcqTWqLJ_2U5Ruo5tCqd2s,176
2
+ jmcomic/api.py,sha256=s1LTgpKQh5hMnl3Im6qD5nvrnusf6NHawzvJKbZxwCo,1864
3
+ jmcomic/jm_client_impl.py,sha256=CxuJEQnF9b_kfAkHVJZByn6JdxOP-noiflm67sjwCtw,14101
4
+ jmcomic/jm_client_interface.py,sha256=OhS-pC2FXQT4vuu2WejypEGns2fbL0yD8ZEItAvSLE8,8073
5
+ jmcomic/jm_config.py,sha256=g2BXvXpYIEHewaMmh4WeHc8jz3kCJFOijls2bfpgdrA,8086
6
+ jmcomic/jm_downloader.py,sha256=NrDp8q7l1-FChAsEUOnWp9Vcx-C3xn5R2Cq6kG4s2nc,5910
7
+ jmcomic/jm_entity.py,sha256=VEw4vCpsmzCJqlOuzLz7O9aeHIuGpwCLX10yzoX4ZIc,12908
8
+ jmcomic/jm_option.py,sha256=X5fOCFpk4h7wQi5pPBJUzhsEMOKgsIRiZH4cNb7gCR8,9494
9
+ jmcomic/jm_toolkit.py,sha256=p0Z6WNAwil-F9S3BRYRnPA-b8uR9a6TA9yhMWATv2KY,12053
10
+ jmcomic-2.1.17.dist-info/LICENSE,sha256=kz4coTxZxuGxisK3W00tjK57Zh3RcMGq-EnbXrK7-xA,1064
11
+ jmcomic-2.1.17.dist-info/METADATA,sha256=y2hfchFCFgIU2DzvNYd_sWUwRLgUikGbKfSkDSrX5rc,3922
12
+ jmcomic-2.1.17.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
13
+ jmcomic-2.1.17.dist-info/top_level.txt,sha256=puvVMFYJqIbd6NOTMEvOyugMTT8woBfSQyxEBan3zY4,8
14
+ jmcomic-2.1.17.dist-info/RECORD,,
@@ -1,14 +0,0 @@
1
- jmcomic/__init__.py,sha256=GlziFJIG_aXTQ9qAJ-TZ4pwGmNafxiuBJi4BenPQv64,176
2
- jmcomic/api.py,sha256=s1LTgpKQh5hMnl3Im6qD5nvrnusf6NHawzvJKbZxwCo,1864
3
- jmcomic/jm_client_impl.py,sha256=Xaaeh14FtbHIutjh6m2jJT_k7lThCefs4CfQ-YOukzg,14014
4
- jmcomic/jm_client_interface.py,sha256=5cbxTVxkEsTU7O11FN2J34P67_eRbfiFqSKl8wZLim8,8048
5
- jmcomic/jm_config.py,sha256=p1PU-5SgVvssxCqIqIaN2A3mcMQDk2-bhoCQ5Cj_Ung,7234
6
- jmcomic/jm_downloader.py,sha256=NrDp8q7l1-FChAsEUOnWp9Vcx-C3xn5R2Cq6kG4s2nc,5910
7
- jmcomic/jm_entity.py,sha256=Xh7Yl-jOro2oUtkrAcbb6S00MCttveZXhn4ZUs-nDKU,12848
8
- jmcomic/jm_option.py,sha256=1U3_Yubl7uRF31e6HAKXpzg2apCUS2tk2Wm3w7s2pHM,9442
9
- jmcomic/jm_toolkit.py,sha256=5B040C6_dDhaBy5VFqW7sdLokYVfxHiu3hNSDYD7vSw,11423
10
- jmcomic-2.1.15.dist-info/LICENSE,sha256=kz4coTxZxuGxisK3W00tjK57Zh3RcMGq-EnbXrK7-xA,1064
11
- jmcomic-2.1.15.dist-info/METADATA,sha256=Yqg2VUC7zx4t7XvRfBSkyZlz3O8YQnPC9H8Ryv0jR88,3922
12
- jmcomic-2.1.15.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
13
- jmcomic-2.1.15.dist-info/top_level.txt,sha256=puvVMFYJqIbd6NOTMEvOyugMTT8woBfSQyxEBan3zY4,8
14
- jmcomic-2.1.15.dist-info/RECORD,,