Skip to content

Commit f4493cb

Browse files
authored
v2.3.12: 增强搜索功能,支持获取搜索结果的总页数,支持基于生成器调用搜索API (#154)
1 parent a0e8af9 commit f4493cb

File tree

7 files changed

+238
-44
lines changed

7 files changed

+238
-44
lines changed

src/jmcomic/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# 被依赖方 <--- 使用方
33
# config <--- entity <--- toolkit <--- client <--- option <--- downloader
44

5-
__version__ = '2.3.11'
5+
__version__ = '2.3.12'
66

77
from .api import *
88
from .jm_plugin import *

src/jmcomic/jm_client_impl.py

Lines changed: 70 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -210,14 +210,14 @@ def search(self,
210210
page: int,
211211
main_tag: int,
212212
order_by: str,
213-
date: str,
213+
time: str,
214214
) -> JmSearchPage:
215215
params = {
216216
'main_tag': main_tag,
217217
'search_query': search_query,
218218
'page': page,
219219
'o': order_by,
220-
't': date,
220+
't': time,
221221
}
222222

223223
resp = self.get_jm_html(
@@ -401,16 +401,15 @@ def search(self,
401401
order_by: str,
402402
time: str,
403403
) -> JmSearchPage:
404-
resp = self.get_decode(
405-
self.API_SEARCH,
406-
params={
407-
'search_query': search_query,
408-
'main_tag': main_tag,
409-
'page': page,
410-
'o': order_by,
411-
't': time,
412-
}
413-
)
404+
params = {
405+
'main_tag': main_tag,
406+
'search_query': search_query,
407+
'page': page,
408+
'o': order_by,
409+
't': time,
410+
}
411+
412+
resp = self.get_decode(self.append_params_to_url(self.API_SEARCH, params))
414413

415414
# 直接搜索禁漫车号,发生重定向的响应数据 resp.model_data
416415
# {
@@ -424,7 +423,7 @@ def search(self,
424423
aid = data.redirect_aid
425424
return JmSearchPage.wrap_single_album(self.get_album_detail(aid))
426425

427-
return JmcomicSearchTool.parse_api_resp_to_page(data)
426+
return JmSearchTool.parse_api_resp_to_page(data)
428427

429428
def get_album_detail(self, album_id) -> JmAlbumDetail:
430429
return self.fetch_detail_entity(album_id,
@@ -517,6 +516,64 @@ def fetch_photo_additional_field(self, photo: JmPhotoDetail, fetch_album: bool,
517516
if fetch_scramble_id:
518517
photo.scramble_id = self.get_scramble_id(photo.album_id)
519518

519+
def setting(self) -> JmApiResp:
520+
"""
521+
禁漫app的setting请求,返回如下内容(resp.res_data)
522+
{
523+
"logo_path": "https://cdn-msp.jmapiproxy1.monster/media/logo/new_logo.png",
524+
"main_web_host": "18-comic.work",
525+
"img_host": "https://cdn-msp.jmapiproxy1.monster",
526+
"base_url": "https://www.jmapinode.biz",
527+
"is_cn": 0,
528+
"cn_base_url": "https://www.jmapinode.biz",
529+
"version": "1.6.0",
530+
"test_version": "1.6.1",
531+
"store_link": "https://play.google.com/store/apps/details?id=com.jiaohua_browser",
532+
"ios_version": "1.6.0",
533+
"ios_test_version": "1.6.1",
534+
"ios_store_link": "https://18comic.vip/stray/",
535+
"ad_cache_version": 1698140798,
536+
"bundle_url": "https://18-comic.work/static/apk/patches1.6.0.zip",
537+
"is_hot_update": true,
538+
"api_banner_path": "https://cdn-msp.jmapiproxy1.monster/media/logo/channel_log.png?v=",
539+
"version_info": "\nAPP & IOS更新\nV1.6.0\n#禁漫 APK 更新拉!!\n更新調整以下項目\n1. 系統優化\n\nV1.5.9\n1. 跳錯誤新增 重試 網頁 按鈕\n2. 圖片讀取優化\n3.
540+
線路調整優化\n\n無法順利更新或是系統題是有風險請使用下方\n下載點2\n有問題可以到DC群反饋\nhttps://discord.gg/V74p7HM\n",
541+
"app_shunts": [
542+
{
543+
"title": "圖源1",
544+
"key": 1
545+
},
546+
{
547+
"title": "圖源2",
548+
"key": 2
549+
},
550+
{
551+
"title": "圖源3",
552+
"key": 3
553+
},
554+
{
555+
"title": "圖源4",
556+
"key": 4
557+
}
558+
],
559+
"download_url": "https://18-comic.work/static/apk/1.6.0.apk",
560+
"app_landing_page": "https://jm365.work/pXYbfA",
561+
"float_ad": true
562+
}
563+
"""
564+
resp = self.get_decode('/setting')
565+
return resp
566+
567+
def login(self,
568+
username,
569+
password,
570+
refresh_client_cookies=True,
571+
id_remember='on',
572+
login_remember='on',
573+
):
574+
jm_debug('api.login', '禁漫移动端无需登录,调用login不会做任何操作')
575+
pass
576+
520577
def get_decode(self, url, **kwargs) -> JmApiResp:
521578
# set headers
522579
headers, key_ts = self.headers_key_ts

src/jmcomic/jm_client_interface.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,69 @@ def search_actor(self,
371371
"""
372372
return self.search(search_query, page, 4, order_by, time)
373373

374+
def search_gen(self,
375+
search_query: str,
376+
main_tag=0,
377+
page: int = 1,
378+
order_by: str = ORDER_BY_LATEST,
379+
time: str = TIME_ALL,
380+
):
381+
"""
382+
搜索结果的生成器,支持下面这种调用方式:
383+
384+
```
385+
for page in self.search_gen('无修正'):
386+
# 每次循环,page为新页的结果
387+
pass
388+
```
389+
390+
同时支持外界send参数,可以改变搜索的设定,例如:
391+
392+
```
393+
gen = client.search_gen('MANA')
394+
for i, page in enumerate(gen):
395+
print(page.page_count)
396+
page = gen.send({
397+
'search_query': '+MANA +无修正',
398+
'page': 1
399+
})
400+
print(page.page_count)
401+
break
402+
```
403+
404+
"""
405+
params = {
406+
'search_query': search_query,
407+
'main_tag': main_tag,
408+
'order_by': order_by,
409+
'time': time,
410+
}
411+
412+
def search(page):
413+
params['page'] = page
414+
return self.search(**params)
415+
416+
from math import inf
417+
418+
def update(value: Union[Dict], page: int, search_page: JmSearchPage):
419+
if value is None:
420+
return page + 1, search_page.page_count
421+
422+
ExceptionTool.require_true(isinstance(value, dict), 'require dict params')
423+
424+
# 根据外界传递的参数,更新params和page
425+
page = value.get('page', page)
426+
params.update(value)
427+
428+
return page, inf
429+
430+
total = inf
431+
432+
while page <= total:
433+
search_page = search(page)
434+
value = yield search_page
435+
page, total = update(value, page, search_page)
436+
374437

375438
# noinspection PyAbstractClass
376439
class JmcomicClient(

src/jmcomic/jm_config.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ def system_proxy():
2626
return ProxyBuilder.system_proxy()
2727

2828

29+
def str_to_list(text):
30+
from common import str_to_list
31+
return str_to_list(text)
32+
33+
2934
class JmcomicException(Exception):
3035
pass
3136

@@ -63,9 +68,25 @@ class JmModuleConfig:
6368

6469
# 域名配置 - 移动端
6570
# 图片域名
66-
DOMAIN_API_IMAGE_LIST = [f"cdn-msp.jmapiproxy{i}.cc" for i in range(1, 4)]
71+
DOMAIN_API_IMAGE_LIST = str_to_list('''
72+
cdn-msp.jmapiproxy1.monster
73+
cdn-msp2.jmapiproxy1.monster
74+
cdn-msp.jmapiproxy1.cc
75+
cdn-msp.jmapiproxy2.cc
76+
cdn-msp.jmapiproxy3.cc
77+
cdn-msp.jmapiproxy4.cc
78+
79+
''')
80+
6781
# API域名
68-
DOMAIN_API_LIST = [f'www.jmapinode{i}.top' for i in range(1, 4)]
82+
DOMAIN_API_LIST = str_to_list('''
83+
www.jmapinode1.top
84+
www.jmapinode2.top
85+
www.jmapinode3.top
86+
www.jmapinode.biz
87+
www.jmapinode.top
88+
89+
''')
6990

7091
# 域名配置 - 网页端
7192
# 无需配置,默认为None,需要的时候会发起请求获得

src/jmcomic/jm_entity.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -425,11 +425,17 @@ def __iter__(self) -> Generator[JmPhotoDetail, None, None]:
425425
class JmSearchPage(JmBaseEntity, IndexedEntity):
426426
ContentItem = Tuple[str, Dict[str, Any]]
427427

428-
def __init__(self, content: List[ContentItem]):
429-
# [
430-
# album_id, {title, tag_list, ...}
431-
# ]
428+
def __init__(self, content: List[ContentItem], page_count):
429+
430+
"""
431+
[
432+
album_id, {title, tag_list, ...}
433+
]
434+
:param content: 搜索结果,移动端和网页端都一次返回80个
435+
:param page_count: 总页数,登录和不登录能看到的总页数不一样
436+
"""
432437
self.content = content
438+
self.page_count = page_count
433439

434440
def iter_id(self) -> Generator[str, None, None]:
435441
"""
@@ -469,7 +475,7 @@ def wrap_single_album(cls, album: JmAlbumDetail) -> 'JmSearchPage':
469475
'name': album.name,
470476
'tag_list': album.tags,
471477
}
472-
)])
478+
)], -1)
473479
setattr(page, 'album', album)
474480
return page
475481

src/jmcomic/jm_toolkit.py

Lines changed: 57 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ def analyse_jm_album_html(cls, html: str) -> JmAlbumDetail:
115115

116116
@classmethod
117117
def analyse_jm_search_html(cls, html: str) -> JmSearchPage:
118-
return JmcomicSearchTool.parse_html_to_page(html)
118+
return JmSearchTool.parse_html_to_page(html)
119119

120120
@classmethod
121121
def reflect_new_instance(cls, html: str, cls_field_prefix: str, clazz: type):
@@ -217,7 +217,39 @@ def parse_to_abspath(cls, dsl_text: str) -> str:
217217
JmcomicText.dsl_replacer.add_dsl_and_replacer(r'\$\{(.*?)\}', JmcomicText.match_os_env)
218218

219219

220-
class JmcomicSearchTool:
220+
class PatternTool:
221+
222+
@classmethod
223+
def match_or_default(cls, html: str, pattern: Pattern, default):
224+
match = pattern.search(html)
225+
return default if match is None else match[1]
226+
227+
@classmethod
228+
def require_match(cls, html: str, pattern: Pattern, msg, rindex=1):
229+
match = pattern.search(html)
230+
if match is not None:
231+
return match[rindex]
232+
233+
ExceptionTool.raises_regex(
234+
msg,
235+
html=html,
236+
pattern=pattern,
237+
)
238+
239+
@classmethod
240+
def require_not_match(cls, html: str, pattern: Pattern, *, msg_func):
241+
match = pattern.search(html)
242+
if match is None:
243+
return
244+
245+
ExceptionTool.raises_regex(
246+
msg_func(match),
247+
html=html,
248+
pattern=pattern,
249+
)
250+
251+
252+
class JmSearchTool:
221253
# 用来缩减html的长度
222254
pattern_html_search_shorten_for = compile(r'<div class="well well-sm">([\s\S]*)<div class="row">')
223255

@@ -238,30 +270,31 @@ class JmcomicSearchTool:
238270
# 查找错误,例如 [错误,關鍵字過短,請至少輸入兩個字以上。]
239271
pattern_html_search_error = compile(r'<fieldset>\n<legend>(.*?)</legend>\n<div class=.*?>\n(.*?)\n</div>\n</fieldset>')
240272

273+
pattern_html_search_total_count = compile(r'<span class="text-white">(\d+)</span> A漫.'), 0
274+
241275
@classmethod
242276
def parse_html_to_page(cls, html: str) -> JmSearchPage:
243-
# 检查是否失败
244-
match = cls.pattern_html_search_error.search(html)
245-
if match is not None:
246-
topic, reason = match[1], match[2]
247-
ExceptionTool.raises_regex(
248-
f'{topic}: {reason}',
249-
html=html,
250-
pattern=cls.pattern_html_search_error,
251-
)
277+
# 1. 检查是否失败
278+
PatternTool.require_not_match(
279+
html,
280+
cls.pattern_html_search_error,
281+
msg_func=lambda match: '{}: {}'.format(match[1], match[2])
282+
)
252283

253-
# 缩小文本范围
254-
match = cls.pattern_html_search_shorten_for.search(html)
255-
if match is None:
256-
ExceptionTool.raises_regex(
257-
'未匹配到搜索结果',
258-
html=html,
259-
pattern=cls.pattern_html_search_shorten_for,
260-
)
261-
html = match[0]
284+
# 2. 缩小文本范围
285+
html = PatternTool.require_match(
286+
html,
287+
cls.pattern_html_search_shorten_for,
288+
msg='未匹配到搜索结果',
289+
)
290+
291+
# 3. 提取结果
292+
import math
262293

263-
# 提取结果
264294
content = [] # content这个名字来源于api版搜索返回值
295+
total_count = PatternTool.match_or_default(html, *cls.pattern_html_search_total_count) # 总结果数
296+
page_count = math.ceil(int(total_count) / 80)
297+
265298
album_info_list = cls.pattern_html_search_album_info_list.findall(html)
266299

267300
for (album_id, title, _, label_category, label_sub, tag_text) in album_info_list:
@@ -273,7 +306,7 @@ def parse_html_to_page(cls, html: str) -> JmSearchPage:
273306
}
274307
))
275308

276-
return JmSearchPage(content)
309+
return JmSearchPage(content, page_count)
277310

278311
@classmethod
279312
def parse_api_resp_to_page(cls, data: DictModel) -> JmSearchPage:
@@ -300,6 +333,7 @@ def parse_api_resp_to_page(cls, data: DictModel) -> JmSearchPage:
300333
]
301334
}
302335
"""
336+
total: int = int(data.total)
303337

304338
def adapt_item(item: DictModel):
305339
item: dict = item.src_dict
@@ -311,7 +345,7 @@ def adapt_item(item: DictModel):
311345
for item in data.content
312346
]
313347

314-
return JmSearchPage(content)
348+
return JmSearchPage(content, total)
315349

316350

317351
class JmApiAdaptTool:

0 commit comments

Comments
 (0)