jmcomic 2.4.6__py3-none-any.whl → 2.4.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 +1 -1
- jmcomic/jm_client_impl.py +124 -48
- jmcomic/jm_client_interface.py +9 -0
- jmcomic/jm_config.py +7 -3
- jmcomic/jm_downloader.py +18 -3
- jmcomic/jm_entity.py +11 -5
- jmcomic/jm_option.py +20 -39
- jmcomic/jm_plugin.py +1 -1
- jmcomic/jm_toolkit.py +4 -1
- {jmcomic-2.4.6.dist-info → jmcomic-2.4.7.dist-info}/METADATA +1 -1
- jmcomic-2.4.7.dist-info/RECORD +17 -0
- jmcomic-2.4.6.dist-info/RECORD +0 -17
- {jmcomic-2.4.6.dist-info → jmcomic-2.4.7.dist-info}/LICENSE +0 -0
- {jmcomic-2.4.6.dist-info → jmcomic-2.4.7.dist-info}/WHEEL +0 -0
- {jmcomic-2.4.6.dist-info → jmcomic-2.4.7.dist-info}/entry_points.txt +0 -0
- {jmcomic-2.4.6.dist-info → jmcomic-2.4.7.dist-info}/top_level.txt +0 -0
jmcomic/__init__.py
CHANGED
jmcomic/jm_client_impl.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from threading import Lock
|
|
2
|
+
|
|
1
3
|
from .jm_client_interface import *
|
|
2
4
|
|
|
3
5
|
|
|
@@ -25,6 +27,7 @@ class AbstractJmClient(
|
|
|
25
27
|
self.retry_times = retry_times
|
|
26
28
|
self.domain_list = domain_list
|
|
27
29
|
self.CLIENT_CACHE = None
|
|
30
|
+
self.__username = None # help for favorite_folder method
|
|
28
31
|
self.enable_cache()
|
|
29
32
|
self.after_init()
|
|
30
33
|
|
|
@@ -50,7 +53,7 @@ class AbstractJmClient(
|
|
|
50
53
|
resp.require_success()
|
|
51
54
|
return resp
|
|
52
55
|
|
|
53
|
-
return self.get(img_url, judge=judge)
|
|
56
|
+
return self.get(img_url, judge=judge, headers=JmModuleConfig.new_html_headers())
|
|
54
57
|
|
|
55
58
|
def request_with_retry(self,
|
|
56
59
|
request,
|
|
@@ -61,7 +64,12 @@ class AbstractJmClient(
|
|
|
61
64
|
**kwargs,
|
|
62
65
|
):
|
|
63
66
|
"""
|
|
64
|
-
|
|
67
|
+
支持重试和切换域名的机制
|
|
68
|
+
|
|
69
|
+
如果url包含了指定域名,则不会切换域名,例如图片URL。
|
|
70
|
+
|
|
71
|
+
如果需要拿到域名进行回调处理,可以重写 self.update_request_with_specify_domain 方法,例如更新headers
|
|
72
|
+
|
|
65
73
|
:param request: 请求方法
|
|
66
74
|
:param url: 图片url / path (/album/xxx)
|
|
67
75
|
:param domain_index: 域名下标
|
|
@@ -74,10 +82,11 @@ class AbstractJmClient(
|
|
|
74
82
|
|
|
75
83
|
if url.startswith('/'):
|
|
76
84
|
# path → url
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
)
|
|
85
|
+
domain = self.domain_list[domain_index]
|
|
86
|
+
url = self.of_api_url(url, domain)
|
|
87
|
+
|
|
88
|
+
self.update_request_with_specify_domain(kwargs, domain)
|
|
89
|
+
|
|
81
90
|
jm_log(self.log_topic(), self.decode(url))
|
|
82
91
|
else:
|
|
83
92
|
# 图片url
|
|
@@ -96,6 +105,8 @@ class AbstractJmClient(
|
|
|
96
105
|
try:
|
|
97
106
|
resp = request(url, **kwargs)
|
|
98
107
|
return judge(resp)
|
|
108
|
+
except KeyboardInterrupt as e:
|
|
109
|
+
raise e
|
|
99
110
|
except Exception as e:
|
|
100
111
|
if self.retry_times == 0:
|
|
101
112
|
raise e
|
|
@@ -107,6 +118,12 @@ class AbstractJmClient(
|
|
|
107
118
|
else:
|
|
108
119
|
return self.request_with_retry(request, url, domain_index + 1, 0, judge, **kwargs)
|
|
109
120
|
|
|
121
|
+
def update_request_with_specify_domain(self, kwargs: dict, domain: str):
|
|
122
|
+
"""
|
|
123
|
+
域名自动切换时,用于更新请求参数的回调
|
|
124
|
+
"""
|
|
125
|
+
pass
|
|
126
|
+
|
|
110
127
|
# noinspection PyMethodMayBeStatic
|
|
111
128
|
def log_topic(self):
|
|
112
129
|
return self.client_key
|
|
@@ -205,6 +222,34 @@ class JmHtmlClient(AbstractJmClient):
|
|
|
205
222
|
|
|
206
223
|
func_to_cache = ['search', 'fetch_detail_entity']
|
|
207
224
|
|
|
225
|
+
def add_favorite_album(self,
|
|
226
|
+
album_id,
|
|
227
|
+
folder_id='0',
|
|
228
|
+
):
|
|
229
|
+
data = {
|
|
230
|
+
'album_id': album_id,
|
|
231
|
+
'fid': folder_id,
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
resp = self.get_jm_html(
|
|
235
|
+
'/ajax/favorite_album',
|
|
236
|
+
data=data,
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
res = resp.json()
|
|
240
|
+
|
|
241
|
+
if res['status'] != 1:
|
|
242
|
+
msg = parse_unicode_escape_text(res['msg'])
|
|
243
|
+
error_msg = PatternTool.match_or_default(msg, JmcomicText.pattern_ajax_favorite_msg, msg)
|
|
244
|
+
# 此圖片已經在您最喜愛的清單!
|
|
245
|
+
|
|
246
|
+
self.raise_request_error(
|
|
247
|
+
resp,
|
|
248
|
+
error_msg
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
return resp
|
|
252
|
+
|
|
208
253
|
def get_album_detail(self, album_id) -> JmAlbumDetail:
|
|
209
254
|
return self.fetch_detail_entity(album_id, 'album')
|
|
210
255
|
|
|
@@ -301,6 +346,7 @@ class JmHtmlClient(AbstractJmClient):
|
|
|
301
346
|
return resp
|
|
302
347
|
|
|
303
348
|
self['cookies'] = new_cookies
|
|
349
|
+
self.__username = username
|
|
304
350
|
|
|
305
351
|
return resp
|
|
306
352
|
|
|
@@ -311,7 +357,8 @@ class JmHtmlClient(AbstractJmClient):
|
|
|
311
357
|
username='',
|
|
312
358
|
) -> JmFavoritePage:
|
|
313
359
|
if username == '':
|
|
314
|
-
|
|
360
|
+
ExceptionTool.require_true(self.__username is not None, 'favorite_folder方法需要传username参数')
|
|
361
|
+
username = self.__username
|
|
315
362
|
|
|
316
363
|
resp = self.get_jm_html(
|
|
317
364
|
f'/user/{username}/favorite/albums',
|
|
@@ -325,13 +372,12 @@ class JmHtmlClient(AbstractJmClient):
|
|
|
325
372
|
return JmPageTool.parse_html_to_favorite_page(resp.text)
|
|
326
373
|
|
|
327
374
|
# noinspection PyTypeChecker
|
|
328
|
-
def
|
|
329
|
-
cookies = self.get_meta_data('cookies', None)
|
|
330
|
-
if not cookies:
|
|
331
|
-
|
|
332
|
-
|
|
375
|
+
def get_username_from_cookies(self) -> str:
|
|
376
|
+
# cookies = self.get_meta_data('cookies', None)
|
|
377
|
+
# if not cookies:
|
|
378
|
+
# ExceptionTool.raises('未登录,无法获取到对应的用户名,请给favorite方法传入username参数')
|
|
333
379
|
# 解析cookies,可能需要用到 phpserialize,比较麻烦,暂不实现
|
|
334
|
-
|
|
380
|
+
pass
|
|
335
381
|
|
|
336
382
|
def get_jm_html(self, url, require_200=True, **kwargs):
|
|
337
383
|
"""
|
|
@@ -351,6 +397,12 @@ class JmHtmlClient(AbstractJmClient):
|
|
|
351
397
|
|
|
352
398
|
return resp
|
|
353
399
|
|
|
400
|
+
def update_request_with_specify_domain(self, kwargs: dict, domain: Optional[str]):
|
|
401
|
+
latest_headers = kwargs.get('headers', None)
|
|
402
|
+
base_headers = self.get_meta_data('headers', None) or JmModuleConfig.new_html_headers(domain)
|
|
403
|
+
base_headers.update(latest_headers or {})
|
|
404
|
+
kwargs['headers'] = base_headers
|
|
405
|
+
|
|
354
406
|
@classmethod
|
|
355
407
|
def raise_request_error(cls, resp, msg: Optional[str] = None):
|
|
356
408
|
"""
|
|
@@ -393,10 +445,7 @@ class JmHtmlClient(AbstractJmClient):
|
|
|
393
445
|
(f' to ({comment_id})' if comment_id is not None else '')
|
|
394
446
|
)
|
|
395
447
|
|
|
396
|
-
resp = self.post('/ajax/album_comment',
|
|
397
|
-
headers=self.album_comment_headers,
|
|
398
|
-
data=data,
|
|
399
|
-
)
|
|
448
|
+
resp = self.post('/ajax/album_comment', data=data)
|
|
400
449
|
|
|
401
450
|
ret = JmAlbumCommentResp(resp)
|
|
402
451
|
jm_log('album.comment', f'{video_id}: [{comment}] ← ({ret.model().cid})')
|
|
@@ -469,26 +518,6 @@ class JmHtmlClient(AbstractJmClient):
|
|
|
469
518
|
+ (f'URL=[{url}]' if url is not None else '')
|
|
470
519
|
)
|
|
471
520
|
|
|
472
|
-
album_comment_headers = {
|
|
473
|
-
'authority': '18comic.vip',
|
|
474
|
-
'accept': 'application/json, text/javascript, */*; q=0.01',
|
|
475
|
-
'accept-language': 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7',
|
|
476
|
-
'cache-control': 'no-cache',
|
|
477
|
-
'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
|
478
|
-
'origin': 'https://18comic.vip',
|
|
479
|
-
'pragma': 'no-cache',
|
|
480
|
-
'referer': 'https://18comic.vip/album/248965/',
|
|
481
|
-
'sec-ch-ua': '"Not.A/Brand";v="8", "Chromium";v="114", "Google Chrome";v="114"',
|
|
482
|
-
'sec-ch-ua-mobile': '?0',
|
|
483
|
-
'sec-ch-ua-platform': '"Windows"',
|
|
484
|
-
'sec-fetch-dest': 'empty',
|
|
485
|
-
'sec-fetch-mode': 'cors',
|
|
486
|
-
'sec-fetch-site': 'same-origin',
|
|
487
|
-
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
|
|
488
|
-
'Chrome/114.0.0.0 Safari/537.36',
|
|
489
|
-
'x-requested-with': 'XMLHttpRequest',
|
|
490
|
-
}
|
|
491
|
-
|
|
492
521
|
|
|
493
522
|
# 基于禁漫移动端(APP)实现的JmClient
|
|
494
523
|
class JmApiClient(AbstractJmClient):
|
|
@@ -581,8 +610,6 @@ class JmApiClient(AbstractJmClient):
|
|
|
581
610
|
},
|
|
582
611
|
)
|
|
583
612
|
|
|
584
|
-
self.require_resp_success(resp, url)
|
|
585
|
-
|
|
586
613
|
return JmApiAdaptTool.parse_entity(resp.res_data, clazz)
|
|
587
614
|
|
|
588
615
|
def fetch_scramble_id(self, photo_id):
|
|
@@ -600,6 +627,7 @@ class JmApiClient(AbstractJmClient):
|
|
|
600
627
|
'express': 'off',
|
|
601
628
|
'v': time_stamp(),
|
|
602
629
|
},
|
|
630
|
+
require_success=False,
|
|
603
631
|
)
|
|
604
632
|
|
|
605
633
|
scramble_id = PatternTool.match_or_default(resp.text,
|
|
@@ -712,7 +740,6 @@ class JmApiClient(AbstractJmClient):
|
|
|
712
740
|
'password': password,
|
|
713
741
|
})
|
|
714
742
|
|
|
715
|
-
resp.require_success()
|
|
716
743
|
cookies = dict(resp.resp.cookies)
|
|
717
744
|
cookies.update({'AVS': resp.res_data['s']})
|
|
718
745
|
self['cookies'] = cookies
|
|
@@ -736,7 +763,34 @@ class JmApiClient(AbstractJmClient):
|
|
|
736
763
|
|
|
737
764
|
return JmPageTool.parse_api_to_favorite_page(resp.model_data)
|
|
738
765
|
|
|
739
|
-
def
|
|
766
|
+
def add_favorite_album(self,
|
|
767
|
+
album_id,
|
|
768
|
+
folder_id='0',
|
|
769
|
+
):
|
|
770
|
+
"""
|
|
771
|
+
移动端没有提供folder_id参数
|
|
772
|
+
"""
|
|
773
|
+
resp = self.req_api(
|
|
774
|
+
'/favorite',
|
|
775
|
+
data={
|
|
776
|
+
'aid': album_id,
|
|
777
|
+
},
|
|
778
|
+
)
|
|
779
|
+
|
|
780
|
+
self.require_resp_status_ok(resp)
|
|
781
|
+
|
|
782
|
+
return resp
|
|
783
|
+
|
|
784
|
+
# noinspection PyMethodMayBeStatic
|
|
785
|
+
def require_resp_status_ok(self, resp: JmApiResp):
|
|
786
|
+
"""
|
|
787
|
+
检查返回数据中的status字段是否为ok
|
|
788
|
+
"""
|
|
789
|
+
data = resp.model_data
|
|
790
|
+
if data.status == 'ok':
|
|
791
|
+
ExceptionTool.raises_resp(data.msg, resp)
|
|
792
|
+
|
|
793
|
+
def req_api(self, url, get=True, require_success=True, **kwargs) -> JmApiResp:
|
|
740
794
|
ts = self.decide_headers_and_ts(kwargs, url)
|
|
741
795
|
|
|
742
796
|
if get:
|
|
@@ -744,7 +798,15 @@ class JmApiClient(AbstractJmClient):
|
|
|
744
798
|
else:
|
|
745
799
|
resp = self.post(url, **kwargs)
|
|
746
800
|
|
|
747
|
-
|
|
801
|
+
resp = JmApiResp(resp, ts)
|
|
802
|
+
|
|
803
|
+
if require_success:
|
|
804
|
+
self.require_resp_success(resp, url)
|
|
805
|
+
|
|
806
|
+
return resp
|
|
807
|
+
|
|
808
|
+
def update_request_with_specify_domain(self, kwargs: dict, domain: str):
|
|
809
|
+
pass
|
|
748
810
|
|
|
749
811
|
# noinspection PyMethodMayBeStatic
|
|
750
812
|
def decide_headers_and_ts(self, kwargs, url):
|
|
@@ -791,7 +853,6 @@ class JmApiClient(AbstractJmClient):
|
|
|
791
853
|
if JmModuleConfig.flag_api_client_require_cookies:
|
|
792
854
|
self.ensure_have_cookies()
|
|
793
855
|
|
|
794
|
-
from threading import Lock
|
|
795
856
|
client_init_cookies_lock = Lock()
|
|
796
857
|
|
|
797
858
|
def ensure_have_cookies(self):
|
|
@@ -826,9 +887,6 @@ class FutureClientProxy(JmcomicClient):
|
|
|
826
887
|
```
|
|
827
888
|
"""
|
|
828
889
|
client_key = 'cl_proxy_future'
|
|
829
|
-
proxy_methods = ['album_comment', 'enable_cache', 'get_domain_list',
|
|
830
|
-
'get_html_domain', 'get_html_domain_all', 'get_jm_image',
|
|
831
|
-
'set_cache_dict', 'get_cache_dict', 'set_domain_list', ]
|
|
832
890
|
|
|
833
891
|
class FutureWrapper:
|
|
834
892
|
def __init__(self, future, after_done_callback):
|
|
@@ -855,8 +913,7 @@ class FutureClientProxy(JmcomicClient):
|
|
|
855
913
|
executors=None,
|
|
856
914
|
):
|
|
857
915
|
self.client = client
|
|
858
|
-
|
|
859
|
-
setattr(self, method, getattr(client, method))
|
|
916
|
+
self.route_notimpl_method_to_internal_client(client)
|
|
860
917
|
|
|
861
918
|
if executors is None:
|
|
862
919
|
from concurrent.futures import ThreadPoolExecutor
|
|
@@ -867,6 +924,25 @@ class FutureClientProxy(JmcomicClient):
|
|
|
867
924
|
from threading import Lock
|
|
868
925
|
self.lock = Lock()
|
|
869
926
|
|
|
927
|
+
def route_notimpl_method_to_internal_client(self, client):
|
|
928
|
+
|
|
929
|
+
impl_methods = str_to_set('''
|
|
930
|
+
get_album_detail
|
|
931
|
+
get_photo_detail
|
|
932
|
+
search
|
|
933
|
+
''')
|
|
934
|
+
|
|
935
|
+
# 获取对象的所有属性和方法的名称列表
|
|
936
|
+
attributes_and_methods = dir(client)
|
|
937
|
+
# 遍历属性和方法列表,并访问每个方法
|
|
938
|
+
for method in attributes_and_methods:
|
|
939
|
+
# 判断是否为方法(可调用对象)
|
|
940
|
+
if (not method.startswith('_')
|
|
941
|
+
and callable(getattr(client, method))
|
|
942
|
+
and method not in impl_methods
|
|
943
|
+
):
|
|
944
|
+
setattr(self, method, getattr(client, method))
|
|
945
|
+
|
|
870
946
|
def get_album_detail(self, album_id) -> JmAlbumDetail:
|
|
871
947
|
album_id = JmcomicText.parse_to_jm_id(album_id)
|
|
872
948
|
cache_key = f'album_{album_id}'
|
jmcomic/jm_client_interface.py
CHANGED
jmcomic/jm_config.py
CHANGED
|
@@ -260,6 +260,7 @@ class JmModuleConfig:
|
|
|
260
260
|
headers = JmMagicConstants.HTML_HEADERS_TEMPLATE.copy()
|
|
261
261
|
headers.update({
|
|
262
262
|
'authority': domain,
|
|
263
|
+
'origin': f'https://{domain}',
|
|
263
264
|
'referer': f'https://{domain}',
|
|
264
265
|
})
|
|
265
266
|
return headers
|
|
@@ -296,9 +297,12 @@ class JmModuleConfig:
|
|
|
296
297
|
return Postmans.new_postman(**kwargs)
|
|
297
298
|
|
|
298
299
|
# option 相关的默认配置
|
|
300
|
+
# 一般情况下,建议使用option配置文件来定制配置
|
|
301
|
+
# 而如果只想修改几个简单常用的配置,也可以下方的DEFAULT_XXX属性
|
|
299
302
|
JM_OPTION_VER = '2.1'
|
|
300
|
-
DEFAULT_CLIENT_IMPL = 'html'
|
|
301
|
-
|
|
303
|
+
DEFAULT_CLIENT_IMPL = 'html' # 默认Client实现类型为网页端
|
|
304
|
+
DEFAULT_CLIENT_CACHE = True # 默认开启Client缓存,缓存级别是level_option,详见CacheRegistry
|
|
305
|
+
DEFAULT_PROXIES = ProxyBuilder.system_proxy() # 默认使用系统代理
|
|
302
306
|
|
|
303
307
|
default_option_dict: dict = {
|
|
304
308
|
'log': None,
|
|
@@ -355,7 +359,7 @@ class JmModuleConfig:
|
|
|
355
359
|
# client cache
|
|
356
360
|
client = option_dict['client']
|
|
357
361
|
if client['cache'] is None:
|
|
358
|
-
client['cache'] =
|
|
362
|
+
client['cache'] = cls.DEFAULT_CLIENT_CACHE
|
|
359
363
|
|
|
360
364
|
# client impl
|
|
361
365
|
if client['impl'] is None:
|
jmcomic/jm_downloader.py
CHANGED
|
@@ -115,7 +115,7 @@ class JmDownloader(DownloadCallback):
|
|
|
115
115
|
"""
|
|
116
116
|
调度本子/章节的下载
|
|
117
117
|
"""
|
|
118
|
-
iter_objs = self.
|
|
118
|
+
iter_objs = self.do_filter(iter_objs)
|
|
119
119
|
count_real = len(iter_objs)
|
|
120
120
|
|
|
121
121
|
if count_real == 0:
|
|
@@ -136,14 +136,14 @@ class JmDownloader(DownloadCallback):
|
|
|
136
136
|
)
|
|
137
137
|
|
|
138
138
|
# noinspection PyMethodMayBeStatic
|
|
139
|
-
def
|
|
139
|
+
def do_filter(self, detail: DetailEntity):
|
|
140
140
|
"""
|
|
141
141
|
该方法可用于过滤本子/章节,默认不会做过滤。
|
|
142
142
|
例如:
|
|
143
143
|
只想下载 本子的最新一章,返回 [album[-1]]
|
|
144
144
|
只想下载 章节的前10张图片,返回 [photo[:10]]
|
|
145
145
|
|
|
146
|
-
:param detail: 可能是本子或者章节,需要自行使用 isinstance / is_xxx 判断
|
|
146
|
+
:param detail: 可能是本子或者章节,需要自行使用 isinstance / detail.is_xxx 判断
|
|
147
147
|
:returns: 只想要下载的 本子的章节 或 章节的图片
|
|
148
148
|
"""
|
|
149
149
|
return detail
|
|
@@ -198,3 +198,18 @@ class JmDownloader(DownloadCallback):
|
|
|
198
198
|
jm_log('dler.exception',
|
|
199
199
|
f'{self.__class__.__name__} Exit with exception: {exc_type, exc_val}'
|
|
200
200
|
)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class DoNotDownloadImage(JmDownloader):
|
|
204
|
+
"""
|
|
205
|
+
本类仅用于测试
|
|
206
|
+
|
|
207
|
+
用法:
|
|
208
|
+
|
|
209
|
+
JmModuleConfig.CLASS_DOWNLOADER = DoNotDownloadImage
|
|
210
|
+
"""
|
|
211
|
+
|
|
212
|
+
def download_by_image_detail(self, image: JmImageDetail, client: JmcomicClient):
|
|
213
|
+
# ensure make dir
|
|
214
|
+
self.option.decide_image_filepath(image)
|
|
215
|
+
pass
|
jmcomic/jm_entity.py
CHANGED
|
@@ -175,8 +175,8 @@ class JmImageDetail(JmBaseEntity):
|
|
|
175
175
|
self.img_file_suffix: str = img_file_suffix
|
|
176
176
|
|
|
177
177
|
self.from_photo: Optional[JmPhotoDetail] = from_photo
|
|
178
|
-
self.query_params:
|
|
179
|
-
self.index = index
|
|
178
|
+
self.query_params: Optional[str] = query_params
|
|
179
|
+
self.index = index # 从1开始
|
|
180
180
|
|
|
181
181
|
# temp fields, in order to simplify passing parameter
|
|
182
182
|
self.save_path: str = ''
|
|
@@ -266,7 +266,7 @@ class JmPhotoDetail(DetailEntity):
|
|
|
266
266
|
self._tags: str = tags
|
|
267
267
|
self._series_id: int = int(series_id)
|
|
268
268
|
|
|
269
|
-
self._author:
|
|
269
|
+
self._author: Optional[str] = author
|
|
270
270
|
self.from_album: Optional[JmAlbumDetail] = from_album
|
|
271
271
|
self.index = self.album_index
|
|
272
272
|
|
|
@@ -278,7 +278,7 @@ class JmPhotoDetail(DetailEntity):
|
|
|
278
278
|
# page_arr存放了该photo的所有图片文件名 img_name
|
|
279
279
|
self.page_arr: List[str] = page_arr
|
|
280
280
|
# 图片的cdn域名
|
|
281
|
-
self.data_original_domain:
|
|
281
|
+
self.data_original_domain: Optional[str] = data_original_domain
|
|
282
282
|
# 第一张图的URL
|
|
283
283
|
self.data_original_0 = data_original_0
|
|
284
284
|
|
|
@@ -372,7 +372,7 @@ class JmPhotoDetail(DetailEntity):
|
|
|
372
372
|
return f'{JmModuleConfig.PROT}{domain}/media/photos/{self.photo_id}/{img_name}'
|
|
373
373
|
|
|
374
374
|
# noinspection PyMethodMayBeStatic
|
|
375
|
-
def get_data_original_query_params(self, data_original_0:
|
|
375
|
+
def get_data_original_query_params(self, data_original_0: Optional[str]) -> str:
|
|
376
376
|
if data_original_0 is None:
|
|
377
377
|
return f'v={time_stamp()}'
|
|
378
378
|
|
|
@@ -534,12 +534,18 @@ class JmPageContent(JmBaseEntity, IndexedEntity):
|
|
|
534
534
|
|
|
535
535
|
@property
|
|
536
536
|
def page_count(self) -> int:
|
|
537
|
+
"""
|
|
538
|
+
页数
|
|
539
|
+
"""
|
|
537
540
|
page_size = self.page_size
|
|
538
541
|
import math
|
|
539
542
|
return math.ceil(int(self.total) / page_size)
|
|
540
543
|
|
|
541
544
|
@property
|
|
542
545
|
def page_size(self) -> int:
|
|
546
|
+
"""
|
|
547
|
+
页大小
|
|
548
|
+
"""
|
|
543
549
|
raise NotImplementedError
|
|
544
550
|
|
|
545
551
|
def iter_id(self) -> Generator[str, None, None]:
|
jmcomic/jm_option.py
CHANGED
|
@@ -276,22 +276,10 @@ class JmOption:
|
|
|
276
276
|
return JmModuleConfig.option_default_dict()
|
|
277
277
|
|
|
278
278
|
@classmethod
|
|
279
|
-
def default(cls
|
|
279
|
+
def default(cls) -> 'JmOption':
|
|
280
280
|
"""
|
|
281
281
|
使用默认的 JmOption
|
|
282
|
-
proxies, domain 为常用配置项,为了方便起见直接支持参数配置。
|
|
283
|
-
其他配置项建议还是使用配置文件
|
|
284
|
-
:param proxies: clash; 127.0.0.1:7890; v2ray
|
|
285
|
-
:param domain: 18comic.vip; ["18comic.vip"]
|
|
286
282
|
"""
|
|
287
|
-
if proxies is not None or domain is not None:
|
|
288
|
-
return cls.construct({
|
|
289
|
-
'client': {
|
|
290
|
-
'domain': [domain] if isinstance(domain, str) else domain,
|
|
291
|
-
'postman': {'meta_data': {'proxies': ProxyBuilder.build_by_str(proxies)}},
|
|
292
|
-
},
|
|
293
|
-
})
|
|
294
|
-
|
|
295
283
|
return cls.construct({})
|
|
296
284
|
|
|
297
285
|
@classmethod
|
|
@@ -372,7 +360,7 @@ class JmOption:
|
|
|
372
360
|
"""
|
|
373
361
|
return self.new_jm_client(**kwargs)
|
|
374
362
|
|
|
375
|
-
def new_jm_client(self,
|
|
363
|
+
def new_jm_client(self, domain_list=None, impl=None, cache=None, **kwargs) -> JmcomicClient:
|
|
376
364
|
"""
|
|
377
365
|
创建新的Client(客户端),不同Client之间的元数据不共享
|
|
378
366
|
"""
|
|
@@ -380,10 +368,15 @@ class JmOption:
|
|
|
380
368
|
|
|
381
369
|
# 所有需要用到的 self.client 配置项如下
|
|
382
370
|
postman_conf: dict = deepcopy(self.client.postman.src_dict) # postman dsl 配置
|
|
371
|
+
|
|
383
372
|
meta_data: dict = postman_conf['meta_data'] # 元数据
|
|
373
|
+
|
|
384
374
|
retry_times: int = self.client.retry_times # 重试次数
|
|
375
|
+
|
|
385
376
|
cache: str = cache if cache is not None else self.client.cache # 启用缓存
|
|
377
|
+
|
|
386
378
|
impl: str = impl or self.client.impl # client_key
|
|
379
|
+
|
|
387
380
|
if isinstance(impl, type):
|
|
388
381
|
# eg: impl = JmHtmlClient
|
|
389
382
|
# noinspection PyUnresolvedReferences
|
|
@@ -392,28 +385,30 @@ class JmOption:
|
|
|
392
385
|
# start construct client
|
|
393
386
|
|
|
394
387
|
# domain
|
|
395
|
-
def
|
|
396
|
-
domain_list
|
|
397
|
-
else self.client.domain # 域名
|
|
388
|
+
def decide_domain_list():
|
|
389
|
+
nonlocal domain_list
|
|
398
390
|
|
|
399
|
-
if
|
|
391
|
+
if domain_list is None:
|
|
392
|
+
domain_list = self.client.domain
|
|
393
|
+
|
|
394
|
+
if not isinstance(domain_list, (list, str)):
|
|
395
|
+
# dict
|
|
400
396
|
domain_list = domain_list.get(impl, [])
|
|
401
397
|
|
|
398
|
+
if isinstance(domain_list, str):
|
|
399
|
+
# multi-lines text
|
|
400
|
+
domain_list = str_to_list(domain_list)
|
|
401
|
+
|
|
402
|
+
# list or str
|
|
402
403
|
if len(domain_list) == 0:
|
|
403
404
|
domain_list = self.decide_client_domain(impl)
|
|
404
405
|
|
|
405
406
|
return domain_list
|
|
406
407
|
|
|
407
|
-
domain: List[str] = decide_domain()
|
|
408
|
-
|
|
409
408
|
# support kwargs overwrite meta_data
|
|
410
409
|
if len(kwargs) != 0:
|
|
411
410
|
meta_data.update(kwargs)
|
|
412
411
|
|
|
413
|
-
# headers
|
|
414
|
-
if meta_data['headers'] is None:
|
|
415
|
-
meta_data['headers'] = self.decide_postman_headers(impl, domain[0])
|
|
416
|
-
|
|
417
412
|
# postman
|
|
418
413
|
postman = Postmans.create(data=postman_conf)
|
|
419
414
|
|
|
@@ -424,7 +419,7 @@ class JmOption:
|
|
|
424
419
|
|
|
425
420
|
client: AbstractJmClient = clazz(
|
|
426
421
|
postman=postman,
|
|
427
|
-
domain_list=
|
|
422
|
+
domain_list=decide_domain_list(),
|
|
428
423
|
retry_times=retry_times,
|
|
429
424
|
)
|
|
430
425
|
|
|
@@ -459,20 +454,6 @@ class JmOption:
|
|
|
459
454
|
|
|
460
455
|
ExceptionTool.raises(f'没有配置域名,且是无法识别的client类型: {client_key}')
|
|
461
456
|
|
|
462
|
-
def decide_postman_headers(self, client_key, domain):
|
|
463
|
-
is_client_type = lambda ctype: self.client_key_is_given_type(client_key, ctype)
|
|
464
|
-
|
|
465
|
-
if is_client_type(JmApiClient):
|
|
466
|
-
# 移动端
|
|
467
|
-
# 不配置headers,由client每次请求前创建headers
|
|
468
|
-
return None
|
|
469
|
-
|
|
470
|
-
if is_client_type(JmHtmlClient):
|
|
471
|
-
# 网页端
|
|
472
|
-
return JmModuleConfig.new_html_headers(domain)
|
|
473
|
-
|
|
474
|
-
ExceptionTool.raises(f'没有配置域名,且是无法识别的client类型: {client_key}')
|
|
475
|
-
|
|
476
457
|
@classmethod
|
|
477
458
|
def client_key_is_given_type(cls, client_key, ctype: Type[JmcomicClient]):
|
|
478
459
|
if client_key == ctype.client_key:
|
jmcomic/jm_plugin.py
CHANGED
jmcomic/jm_toolkit.py
CHANGED
|
@@ -55,6 +55,9 @@ class JmcomicText:
|
|
|
55
55
|
# 評論(div)
|
|
56
56
|
pattern_html_album_comment_count = compile(r'<div class="badge"[^>]*?id="total_video_comments">(\d+)</div>'), 0
|
|
57
57
|
|
|
58
|
+
# 提取接口返回值信息
|
|
59
|
+
pattern_ajax_favorite_msg = compile(r'</button>(.*?)</div>')
|
|
60
|
+
|
|
58
61
|
@classmethod
|
|
59
62
|
def parse_to_jm_domain(cls, text: str):
|
|
60
63
|
if text.startswith(JmModuleConfig.PROT):
|
|
@@ -308,7 +311,7 @@ class PatternTool:
|
|
|
308
311
|
def require_match(cls, html: str, pattern: Pattern, msg, rindex=1):
|
|
309
312
|
match = pattern.search(html)
|
|
310
313
|
if match is not None:
|
|
311
|
-
return match[rindex]
|
|
314
|
+
return match[rindex] if rindex is not None else match
|
|
312
315
|
|
|
313
316
|
ExceptionTool.raises_regex(
|
|
314
317
|
msg,
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
jmcomic/__init__.py,sha256=BYuf4ruBex9ljlTlGN0xXdUcus_eKTXS7ZLOqkgKJXM,878
|
|
2
|
+
jmcomic/api.py,sha256=yukYd5NCYYxP8K2Gj72iXu7vg9PHgaOvakQzQxEOcO8,2518
|
|
3
|
+
jmcomic/cl.py,sha256=PBSh0JndNFZw3B7WJPj5Y8SeFdKzHE00jIwYo9An-K0,3475
|
|
4
|
+
jmcomic/jm_client_impl.py,sha256=uG4LIZZdz6zAePkgTq80n5pNV-G069cXiSgBRg_dAO8,34106
|
|
5
|
+
jmcomic/jm_client_interface.py,sha256=xaPtHxdzGYmzsnRfq-fkH__hiARNUtxtjtEzD-SwbPg,14120
|
|
6
|
+
jmcomic/jm_config.py,sha256=88YEZyzxBWJcKmbS6sYhfQ4ZoO6kb7ghMYOgUYBfuWg,13110
|
|
7
|
+
jmcomic/jm_downloader.py,sha256=E1M9CS9bYEHWe8SEsdaq_0c3zxQghpkN_4QLZiZ-o1g,7324
|
|
8
|
+
jmcomic/jm_entity.py,sha256=u5aJhIt2Q21re6moXfS0drVBmIJKjNQnRI2lVvbZ-nY,18671
|
|
9
|
+
jmcomic/jm_option.py,sha256=-_Obz2m_roZOAkb1hw62TKDeKIy_iow99z65fXdvX7I,19945
|
|
10
|
+
jmcomic/jm_plugin.py,sha256=ftL0iZbc1GWk75n6ZuP1j-7MaOTqK1mQA04E-rkNEPs,16198
|
|
11
|
+
jmcomic/jm_toolkit.py,sha256=I67hbdHBLVsmi8RFanKy2sOIaMDU5GlOzi7AxfK_8vA,28892
|
|
12
|
+
jmcomic-2.4.7.dist-info/LICENSE,sha256=kz4coTxZxuGxisK3W00tjK57Zh3RcMGq-EnbXrK7-xA,1064
|
|
13
|
+
jmcomic-2.4.7.dist-info/METADATA,sha256=fqfirQgCGD85-12Vhf5hSo71eG3A3DSSSOjzzbi96Nc,5470
|
|
14
|
+
jmcomic-2.4.7.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
|
15
|
+
jmcomic-2.4.7.dist-info/entry_points.txt,sha256=tRbQltaGSBjejI0c9jYt-4SXQMd5nSDHcMvHmuTy4ow,44
|
|
16
|
+
jmcomic-2.4.7.dist-info/top_level.txt,sha256=puvVMFYJqIbd6NOTMEvOyugMTT8woBfSQyxEBan3zY4,8
|
|
17
|
+
jmcomic-2.4.7.dist-info/RECORD,,
|
jmcomic-2.4.6.dist-info/RECORD
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
jmcomic/__init__.py,sha256=Nt5kEmfeZBoVkg2wCrGNHoXCaB0PFmZMHZyiEg1rqsY,878
|
|
2
|
-
jmcomic/api.py,sha256=yukYd5NCYYxP8K2Gj72iXu7vg9PHgaOvakQzQxEOcO8,2518
|
|
3
|
-
jmcomic/cl.py,sha256=PBSh0JndNFZw3B7WJPj5Y8SeFdKzHE00jIwYo9An-K0,3475
|
|
4
|
-
jmcomic/jm_client_impl.py,sha256=SoblBkIaXI-TVeQCpNmovgvlN9sknjV0xD_svN5vH8s,32067
|
|
5
|
-
jmcomic/jm_client_interface.py,sha256=PaMxxsJvxtUkpq7pRavftMlas3gfC6BQ0ZnFaYiUarU,13886
|
|
6
|
-
jmcomic/jm_config.py,sha256=SJmkJTeQe7PvNbZyq-D3HBHWuNTULcJH0p_jTCka8QI,12735
|
|
7
|
-
jmcomic/jm_downloader.py,sha256=GjC3JU8-UPvnKd_XlAoW0X-Wt2zGaU5K2bm3PE5R3as,7000
|
|
8
|
-
jmcomic/jm_entity.py,sha256=zHI5N3MPA93fbdq3Lv94NVYZOx9mEwjsxshvh-w90jQ,18565
|
|
9
|
-
jmcomic/jm_option.py,sha256=l6YtLqL6Q90o5kI18Cmt5kGbfg9tl-pvFXcowDkc6dg,21070
|
|
10
|
-
jmcomic/jm_plugin.py,sha256=Z-cVc9H3s0Pj91I5ngjx-LdeX2PqYYloOcvogXs4glc,16205
|
|
11
|
-
jmcomic/jm_toolkit.py,sha256=TrddmLTH44iiHv9nwe0l7y14vAs23tDO2QfE6BkLSfc,28759
|
|
12
|
-
jmcomic-2.4.6.dist-info/LICENSE,sha256=kz4coTxZxuGxisK3W00tjK57Zh3RcMGq-EnbXrK7-xA,1064
|
|
13
|
-
jmcomic-2.4.6.dist-info/METADATA,sha256=NrHLNgvjgafu3geU7DXY9wFZ-a6W5eJfIJHoAXSKVyg,5470
|
|
14
|
-
jmcomic-2.4.6.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
|
15
|
-
jmcomic-2.4.6.dist-info/entry_points.txt,sha256=tRbQltaGSBjejI0c9jYt-4SXQMd5nSDHcMvHmuTy4ow,44
|
|
16
|
-
jmcomic-2.4.6.dist-info/top_level.txt,sha256=puvVMFYJqIbd6NOTMEvOyugMTT8woBfSQyxEBan3zY4,8
|
|
17
|
-
jmcomic-2.4.6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|