jmcomic 2.5.5__tar.gz → 2.5.7__tar.gz
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-2.5.5/src/jmcomic.egg-info → jmcomic-2.5.7}/PKG-INFO +31 -18
- {jmcomic-2.5.5 → jmcomic-2.5.7}/README.md +30 -17
- {jmcomic-2.5.5 → jmcomic-2.5.7}/src/jmcomic/__init__.py +1 -1
- {jmcomic-2.5.5 → jmcomic-2.5.7}/src/jmcomic/api.py +15 -4
- {jmcomic-2.5.5 → jmcomic-2.5.7}/src/jmcomic/jm_client_impl.py +18 -16
- {jmcomic-2.5.5 → jmcomic-2.5.7}/src/jmcomic/jm_client_interface.py +13 -12
- {jmcomic-2.5.5 → jmcomic-2.5.7}/src/jmcomic/jm_config.py +52 -16
- {jmcomic-2.5.5 → jmcomic-2.5.7}/src/jmcomic/jm_downloader.py +75 -23
- {jmcomic-2.5.5 → jmcomic-2.5.7}/src/jmcomic/jm_entity.py +11 -7
- {jmcomic-2.5.5 → jmcomic-2.5.7}/src/jmcomic/jm_option.py +31 -5
- {jmcomic-2.5.5 → jmcomic-2.5.7}/src/jmcomic/jm_plugin.py +5 -5
- {jmcomic-2.5.5 → jmcomic-2.5.7}/src/jmcomic/jm_toolkit.py +31 -21
- {jmcomic-2.5.5 → jmcomic-2.5.7/src/jmcomic.egg-info}/PKG-INFO +31 -18
- {jmcomic-2.5.5 → jmcomic-2.5.7}/LICENSE +0 -0
- {jmcomic-2.5.5 → jmcomic-2.5.7}/setup.cfg +0 -0
- {jmcomic-2.5.5 → jmcomic-2.5.7}/setup.py +0 -0
- {jmcomic-2.5.5 → jmcomic-2.5.7}/src/jmcomic/cl.py +0 -0
- {jmcomic-2.5.5 → jmcomic-2.5.7}/src/jmcomic/jm_exception.py +0 -0
- {jmcomic-2.5.5 → jmcomic-2.5.7}/src/jmcomic.egg-info/SOURCES.txt +0 -0
- {jmcomic-2.5.5 → jmcomic-2.5.7}/src/jmcomic.egg-info/dependency_links.txt +0 -0
- {jmcomic-2.5.5 → jmcomic-2.5.7}/src/jmcomic.egg-info/entry_points.txt +0 -0
- {jmcomic-2.5.5 → jmcomic-2.5.7}/src/jmcomic.egg-info/requires.txt +0 -0
- {jmcomic-2.5.5 → jmcomic-2.5.7}/src/jmcomic.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: jmcomic
|
|
3
|
-
Version: 2.5.
|
|
3
|
+
Version: 2.5.7
|
|
4
4
|
Summary: Python API For JMComic (禁漫天堂)
|
|
5
5
|
Home-page: https://github.com/hect0x7/JMComic-Crawler-Python
|
|
6
6
|
Author: hect0x7
|
|
@@ -42,16 +42,26 @@ Requires-Dist: pycryptodome
|
|
|
42
42
|
|
|
43
43
|
本项目的核心功能是下载本子,基于此,设计了一套方便使用、便于扩展,能满足一些特殊下载需求的框架。
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
目前核心功能实现较为稳定,项目也处于维护阶段。
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
除了下载功能以外,也实现了其他的一些禁漫接口,按需实现,具体如下。
|
|
48
|
+
|
|
49
|
+
### 已实现的禁漫API:
|
|
50
|
+
|
|
51
|
+
- 登录
|
|
52
|
+
- 搜本
|
|
53
|
+
- 分类 (排行榜)
|
|
54
|
+
- 本子章节详情
|
|
55
|
+
- 图片下载解码
|
|
56
|
+
- 收藏夹
|
|
57
|
+
- 移动端接口加解密
|
|
48
58
|
|
|
49
59
|
## 安装教程
|
|
50
60
|
|
|
51
61
|
* 通过pip官方源安装(推荐,并且更新也是这个命令)
|
|
52
62
|
|
|
53
63
|
```shell
|
|
54
|
-
pip install jmcomic -i https://pypi.org/project
|
|
64
|
+
pip install jmcomic -i https://pypi.org/project -U
|
|
55
65
|
```
|
|
56
66
|
* 通过源代码安装
|
|
57
67
|
|
|
@@ -75,6 +85,21 @@ jmcomic.download_album('422866') # 传入要下载的album的id,即可下载
|
|
|
75
85
|
$ jmcomic 422866
|
|
76
86
|
```
|
|
77
87
|
|
|
88
|
+
## 进阶使用
|
|
89
|
+
|
|
90
|
+
文档网站:[jmcomic.readthedocs.io](https://jmcomic.readthedocs.io/en/latest)
|
|
91
|
+
|
|
92
|
+
进阶使用可以参考:[jmcomic常用类和方法演示](assets/docs/sources/tutorial/0_demo.md)
|
|
93
|
+
|
|
94
|
+
下面列出的是一些常用的文档:
|
|
95
|
+
|
|
96
|
+
* [jmcomic常用类和方法演示](assets/docs/sources/tutorial/0_demo.md)
|
|
97
|
+
* [option配置文件语法(包含插件配置)](./assets/docs/sources/option_file_syntax.md)
|
|
98
|
+
* [GitHub Actions使用教程](./assets/docs/sources/tutorial/1_github_actions.md)
|
|
99
|
+
* [命令行使用教程](assets/docs/sources/tutorial/2_command_line.md)
|
|
100
|
+
* [插件机制](assets/docs/sources/tutorial/6_plugin.md)
|
|
101
|
+
* [下载过滤器机制](assets/docs/sources/tutorial/5_filter.md)
|
|
102
|
+
|
|
78
103
|
## 项目特点
|
|
79
104
|
|
|
80
105
|
- **绕过Cloudflare的反爬虫**
|
|
@@ -111,19 +136,6 @@ $ jmcomic 422866
|
|
|
111
136
|
- `jpg图片合成为一个pdf插件`
|
|
112
137
|
- `导出收藏夹为csv文件插件`
|
|
113
138
|
|
|
114
|
-
## 进阶使用
|
|
115
|
-
|
|
116
|
-
进阶使用请查阅文档:[文档](https://jmcomic.readthedocs.io/en/latest)
|
|
117
|
-
|
|
118
|
-
下面列出一些常用的文档链接:
|
|
119
|
-
|
|
120
|
-
* [option配置文件语法(包含插件配置)](./assets/docs/sources/option_file_syntax.md)
|
|
121
|
-
* [常用类和方法演示(下载本子、获取实体类、搜索本子)](assets/docs/sources/tutorial/3_demo.md)
|
|
122
|
-
* [命令行使用教程](assets/docs/sources/tutorial/2_command_line.md)
|
|
123
|
-
* [GitHub Actions使用教程](./assets/docs/sources/tutorial/1_github_actions.md)
|
|
124
|
-
* [插件机制](assets/docs/sources/tutorial/6_plugin.md)
|
|
125
|
-
* [下载过滤器机制](assets/docs/sources/tutorial/5_filter.md)
|
|
126
|
-
|
|
127
139
|
## 使用小说明
|
|
128
140
|
|
|
129
141
|
* Python >= 3.7
|
|
@@ -131,10 +143,11 @@ $ jmcomic 422866
|
|
|
131
143
|
|
|
132
144
|
## 项目文件夹介绍
|
|
133
145
|
|
|
146
|
+
* .github:GitHub Actions配置文件
|
|
134
147
|
* assets:存放一些非代码的资源文件
|
|
135
148
|
|
|
136
|
-
* config:存放配置文件
|
|
137
149
|
* docs:项目文档
|
|
150
|
+
* option:存放配置文件
|
|
138
151
|
|
|
139
152
|
* src:存放源代码
|
|
140
153
|
|
|
@@ -14,16 +14,26 @@
|
|
|
14
14
|
|
|
15
15
|
本项目的核心功能是下载本子,基于此,设计了一套方便使用、便于扩展,能满足一些特殊下载需求的框架。
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
目前核心功能实现较为稳定,项目也处于维护阶段。
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
除了下载功能以外,也实现了其他的一些禁漫接口,按需实现,具体如下。
|
|
20
|
+
|
|
21
|
+
### 已实现的禁漫API:
|
|
22
|
+
|
|
23
|
+
- 登录
|
|
24
|
+
- 搜本
|
|
25
|
+
- 分类 (排行榜)
|
|
26
|
+
- 本子章节详情
|
|
27
|
+
- 图片下载解码
|
|
28
|
+
- 收藏夹
|
|
29
|
+
- 移动端接口加解密
|
|
20
30
|
|
|
21
31
|
## 安装教程
|
|
22
32
|
|
|
23
33
|
* 通过pip官方源安装(推荐,并且更新也是这个命令)
|
|
24
34
|
|
|
25
35
|
```shell
|
|
26
|
-
pip install jmcomic -i https://pypi.org/project
|
|
36
|
+
pip install jmcomic -i https://pypi.org/project -U
|
|
27
37
|
```
|
|
28
38
|
* 通过源代码安装
|
|
29
39
|
|
|
@@ -47,6 +57,21 @@ jmcomic.download_album('422866') # 传入要下载的album的id,即可下载
|
|
|
47
57
|
$ jmcomic 422866
|
|
48
58
|
```
|
|
49
59
|
|
|
60
|
+
## 进阶使用
|
|
61
|
+
|
|
62
|
+
文档网站:[jmcomic.readthedocs.io](https://jmcomic.readthedocs.io/en/latest)
|
|
63
|
+
|
|
64
|
+
进阶使用可以参考:[jmcomic常用类和方法演示](assets/docs/sources/tutorial/0_demo.md)
|
|
65
|
+
|
|
66
|
+
下面列出的是一些常用的文档:
|
|
67
|
+
|
|
68
|
+
* [jmcomic常用类和方法演示](assets/docs/sources/tutorial/0_demo.md)
|
|
69
|
+
* [option配置文件语法(包含插件配置)](./assets/docs/sources/option_file_syntax.md)
|
|
70
|
+
* [GitHub Actions使用教程](./assets/docs/sources/tutorial/1_github_actions.md)
|
|
71
|
+
* [命令行使用教程](assets/docs/sources/tutorial/2_command_line.md)
|
|
72
|
+
* [插件机制](assets/docs/sources/tutorial/6_plugin.md)
|
|
73
|
+
* [下载过滤器机制](assets/docs/sources/tutorial/5_filter.md)
|
|
74
|
+
|
|
50
75
|
## 项目特点
|
|
51
76
|
|
|
52
77
|
- **绕过Cloudflare的反爬虫**
|
|
@@ -83,19 +108,6 @@ $ jmcomic 422866
|
|
|
83
108
|
- `jpg图片合成为一个pdf插件`
|
|
84
109
|
- `导出收藏夹为csv文件插件`
|
|
85
110
|
|
|
86
|
-
## 进阶使用
|
|
87
|
-
|
|
88
|
-
进阶使用请查阅文档:[文档](https://jmcomic.readthedocs.io/en/latest)
|
|
89
|
-
|
|
90
|
-
下面列出一些常用的文档链接:
|
|
91
|
-
|
|
92
|
-
* [option配置文件语法(包含插件配置)](./assets/docs/sources/option_file_syntax.md)
|
|
93
|
-
* [常用类和方法演示(下载本子、获取实体类、搜索本子)](assets/docs/sources/tutorial/3_demo.md)
|
|
94
|
-
* [命令行使用教程](assets/docs/sources/tutorial/2_command_line.md)
|
|
95
|
-
* [GitHub Actions使用教程](./assets/docs/sources/tutorial/1_github_actions.md)
|
|
96
|
-
* [插件机制](assets/docs/sources/tutorial/6_plugin.md)
|
|
97
|
-
* [下载过滤器机制](assets/docs/sources/tutorial/5_filter.md)
|
|
98
|
-
|
|
99
111
|
## 使用小说明
|
|
100
112
|
|
|
101
113
|
* Python >= 3.7
|
|
@@ -103,10 +115,11 @@ $ jmcomic 422866
|
|
|
103
115
|
|
|
104
116
|
## 项目文件夹介绍
|
|
105
117
|
|
|
118
|
+
* .github:GitHub Actions配置文件
|
|
106
119
|
* assets:存放一些非代码的资源文件
|
|
107
120
|
|
|
108
|
-
* config:存放配置文件
|
|
109
121
|
* docs:项目文档
|
|
122
|
+
* option:存放配置文件
|
|
110
123
|
|
|
111
124
|
* src:存放源代码
|
|
112
125
|
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
from .jm_downloader import *
|
|
2
2
|
|
|
3
|
+
__DOWNLOAD_API_RET = Tuple[JmAlbumDetail, JmDownloader]
|
|
3
4
|
|
|
4
5
|
def download_batch(download_api,
|
|
5
6
|
jm_id_iter: Union[Iterable, Generator],
|
|
6
7
|
option=None,
|
|
7
8
|
downloader=None,
|
|
8
|
-
) -> Set[
|
|
9
|
+
) -> Set[__DOWNLOAD_API_RET]:
|
|
9
10
|
"""
|
|
10
11
|
批量下载 album / photo
|
|
11
12
|
|
|
@@ -46,7 +47,7 @@ def download_album(jm_album_id,
|
|
|
46
47
|
option=None,
|
|
47
48
|
downloader=None,
|
|
48
49
|
callback=None,
|
|
49
|
-
):
|
|
50
|
+
) -> Union[__DOWNLOAD_API_RET, Set[__DOWNLOAD_API_RET]]:
|
|
50
51
|
"""
|
|
51
52
|
下载一个本子(album),包含其所有的章节(photo)
|
|
52
53
|
|
|
@@ -100,7 +101,7 @@ def new_downloader(option=None, downloader=None) -> JmDownloader:
|
|
|
100
101
|
return downloader(option)
|
|
101
102
|
|
|
102
103
|
|
|
103
|
-
def
|
|
104
|
+
def create_option_by_file(filepath):
|
|
104
105
|
return JmModuleConfig.option_class().from_file(filepath)
|
|
105
106
|
|
|
106
107
|
|
|
@@ -110,4 +111,14 @@ def create_option_by_env(env_name='JM_OPTION_PATH'):
|
|
|
110
111
|
filepath = get_env(env_name, None)
|
|
111
112
|
ExceptionTool.require_true(filepath is not None,
|
|
112
113
|
f'未配置环境变量: {env_name},请配置为option的文件路径')
|
|
113
|
-
return
|
|
114
|
+
return create_option_by_file(filepath)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def create_option_by_str(text: str, mode=None):
|
|
118
|
+
if mode is None:
|
|
119
|
+
mode = PackerUtil.mode_yml
|
|
120
|
+
data = PackerUtil.unpack_by_str(text, mode)[0]
|
|
121
|
+
return JmModuleConfig.option_class().construct(data)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
create_option = create_option_by_file
|
|
@@ -79,9 +79,9 @@ class AbstractJmClient(
|
|
|
79
79
|
"""
|
|
80
80
|
if domain_index >= len(self.domain_list):
|
|
81
81
|
return self.fallback(request, url, domain_index, retry_count, **kwargs)
|
|
82
|
-
|
|
82
|
+
|
|
83
83
|
url_backup = url
|
|
84
|
-
|
|
84
|
+
|
|
85
85
|
if url.startswith('/'):
|
|
86
86
|
# path → url
|
|
87
87
|
domain = self.domain_list[domain_index]
|
|
@@ -976,10 +976,14 @@ class JmApiClient(AbstractJmClient):
|
|
|
976
976
|
return cookies
|
|
977
977
|
|
|
978
978
|
|
|
979
|
-
class
|
|
979
|
+
class PhotoConcurrentFetcherProxy(JmcomicClient):
|
|
980
980
|
"""
|
|
981
|
-
|
|
982
|
-
|
|
981
|
+
为了解决 JmApiClient.get_photo_detail 方法的排队调用问题,
|
|
982
|
+
即在访问完photo的接口后,需要另外排队访问获取album和scramble_id的接口。
|
|
983
|
+
|
|
984
|
+
这三个接口可以并发请求,这样可以提高效率。
|
|
985
|
+
|
|
986
|
+
此Proxy代理了get_photo_detail,实现了并发请求这三个接口,然后组装返回值返回photo。
|
|
983
987
|
|
|
984
988
|
可通过插件 ClientProxyPlugin 启用本类,配置如下:
|
|
985
989
|
```yml
|
|
@@ -987,10 +991,10 @@ class FutureClientProxy(JmcomicClient):
|
|
|
987
991
|
after_init:
|
|
988
992
|
- plugin: client_proxy
|
|
989
993
|
kwargs:
|
|
990
|
-
proxy_client_key:
|
|
994
|
+
proxy_client_key: photo_concurrent_fetcher_proxy
|
|
991
995
|
```
|
|
992
996
|
"""
|
|
993
|
-
client_key = '
|
|
997
|
+
client_key = 'photo_concurrent_fetcher_proxy'
|
|
994
998
|
|
|
995
999
|
class FutureWrapper:
|
|
996
1000
|
def __init__(self, future, after_done_callback):
|
|
@@ -1024,16 +1028,15 @@ class FutureClientProxy(JmcomicClient):
|
|
|
1024
1028
|
executors = ThreadPoolExecutor(max_workers)
|
|
1025
1029
|
|
|
1026
1030
|
self.executors = executors
|
|
1027
|
-
self.future_dict: Dict[str,
|
|
1031
|
+
self.future_dict: Dict[str, PhotoConcurrentFetcherProxy.FutureWrapper] = {}
|
|
1028
1032
|
from threading import Lock
|
|
1029
1033
|
self.lock = Lock()
|
|
1030
1034
|
|
|
1031
1035
|
def route_notimpl_method_to_internal_client(self, client):
|
|
1032
1036
|
|
|
1033
|
-
|
|
1037
|
+
proxy_methods = str_to_set('''
|
|
1034
1038
|
get_album_detail
|
|
1035
1039
|
get_photo_detail
|
|
1036
|
-
search
|
|
1037
1040
|
''')
|
|
1038
1041
|
|
|
1039
1042
|
# 获取对象的所有属性和方法的名称列表
|
|
@@ -1043,7 +1046,7 @@ class FutureClientProxy(JmcomicClient):
|
|
|
1043
1046
|
# 判断是否为方法(可调用对象)
|
|
1044
1047
|
if (not method.startswith('_')
|
|
1045
1048
|
and callable(getattr(client, method))
|
|
1046
|
-
and method not in
|
|
1049
|
+
and method not in proxy_methods
|
|
1047
1050
|
):
|
|
1048
1051
|
setattr(self, method, getattr(client, method))
|
|
1049
1052
|
|
|
@@ -1055,15 +1058,19 @@ class FutureClientProxy(JmcomicClient):
|
|
|
1055
1058
|
|
|
1056
1059
|
def get_future(self, cache_key, task):
|
|
1057
1060
|
if cache_key in self.future_dict:
|
|
1061
|
+
# cache hit, means that a same task is running
|
|
1058
1062
|
return self.future_dict[cache_key]
|
|
1059
1063
|
|
|
1060
1064
|
with self.lock:
|
|
1061
1065
|
if cache_key in self.future_dict:
|
|
1062
1066
|
return self.future_dict[cache_key]
|
|
1063
1067
|
|
|
1068
|
+
# after future done, remove it from future_dict.
|
|
1069
|
+
# cache depends on self.client instead of self.future_dict
|
|
1064
1070
|
future = self.FutureWrapper(self.executors.submit(task),
|
|
1065
1071
|
after_done_callback=lambda: self.future_dict.pop(cache_key, None)
|
|
1066
1072
|
)
|
|
1073
|
+
|
|
1067
1074
|
self.future_dict[cache_key] = future
|
|
1068
1075
|
return future
|
|
1069
1076
|
|
|
@@ -1115,8 +1122,3 @@ class FutureClientProxy(JmcomicClient):
|
|
|
1115
1122
|
photo.scramble_id = scramble_id
|
|
1116
1123
|
|
|
1117
1124
|
return photo
|
|
1118
|
-
|
|
1119
|
-
def search(self, search_query: str, page: int, main_tag: int, order_by: str, time: str) -> JmSearchPage:
|
|
1120
|
-
cache_key = f'search_query_{search_query}_page_{page}_main_tag_{main_tag}_order_by_{order_by}_time_{time}'
|
|
1121
|
-
future = self.get_future(cache_key, task=lambda: self.client.search(search_query, page, main_tag, order_by, time))
|
|
1122
|
-
return future.result()
|
|
@@ -6,8 +6,6 @@ Response Entity
|
|
|
6
6
|
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
DictModel = AdvancedEasyAccessDict
|
|
10
|
-
|
|
11
9
|
|
|
12
10
|
class JmResp:
|
|
13
11
|
|
|
@@ -87,11 +85,11 @@ class JmJsonResp(JmResp):
|
|
|
87
85
|
def json(self) -> Dict:
|
|
88
86
|
try:
|
|
89
87
|
return self.resp.json()
|
|
90
|
-
except Exception:
|
|
91
|
-
ExceptionTool.raises_resp('json
|
|
88
|
+
except Exception as e:
|
|
89
|
+
ExceptionTool.raises_resp(f'json解析失败: {e}', self, JsonResolveFailException)
|
|
92
90
|
|
|
93
|
-
def model(self) ->
|
|
94
|
-
return
|
|
91
|
+
def model(self) -> AdvancedEasyAccessDict:
|
|
92
|
+
return AdvancedEasyAccessDict(self.json())
|
|
95
93
|
|
|
96
94
|
|
|
97
95
|
class JmApiResp(JmJsonResp):
|
|
@@ -120,9 +118,9 @@ class JmApiResp(JmJsonResp):
|
|
|
120
118
|
return loads(self.decoded_data)
|
|
121
119
|
|
|
122
120
|
@property
|
|
123
|
-
def model_data(self) ->
|
|
121
|
+
def model_data(self) -> AdvancedEasyAccessDict:
|
|
124
122
|
self.require_success()
|
|
125
|
-
return
|
|
123
|
+
return AdvancedEasyAccessDict(self.res_data)
|
|
126
124
|
|
|
127
125
|
|
|
128
126
|
# album-comment
|
|
@@ -469,11 +467,14 @@ class JmcomicClient(
|
|
|
469
467
|
def of_api_url(self, api_path, domain):
|
|
470
468
|
raise NotImplementedError
|
|
471
469
|
|
|
472
|
-
def get_html_domain(self
|
|
473
|
-
return JmModuleConfig.get_html_domain(
|
|
470
|
+
def get_html_domain(self):
|
|
471
|
+
return JmModuleConfig.get_html_domain(self.get_root_postman())
|
|
472
|
+
|
|
473
|
+
def get_html_domain_all(self):
|
|
474
|
+
return JmModuleConfig.get_html_domain_all(self.get_root_postman())
|
|
474
475
|
|
|
475
|
-
def
|
|
476
|
-
return JmModuleConfig.
|
|
476
|
+
def get_html_domain_all_via_github(self):
|
|
477
|
+
return JmModuleConfig.get_html_domain_all_via_github(self.get_root_postman())
|
|
477
478
|
|
|
478
479
|
# noinspection PyMethodMayBeStatic
|
|
479
480
|
def do_page_iter(self, params: dict, page: int, get_page_method):
|
|
@@ -2,8 +2,8 @@ from common import time_stamp, str_to_list, field_cache, ProxyBuilder
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
def default_jm_logging(topic: str, msg: str):
|
|
5
|
-
from common import format_ts
|
|
6
|
-
print(
|
|
5
|
+
from common import format_ts, current_thread
|
|
6
|
+
print('[{}] [{}]:【{}】{}'.format(format_ts(), current_thread().name, topic, msg))
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
# 禁漫常量
|
|
@@ -24,17 +24,18 @@ class JmMagicConstants:
|
|
|
24
24
|
TIME_MONTH = 'm'
|
|
25
25
|
TIME_ALL = 'a'
|
|
26
26
|
|
|
27
|
-
#
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
27
|
+
# 分类参数API接口的category
|
|
28
|
+
CATEGORY_ALL = '0' # 全部
|
|
29
|
+
CATEGORY_DOUJIN = 'doujin' # 同人
|
|
30
|
+
CATEGORY_SINGLE = 'single' # 单本
|
|
31
|
+
CATEGORY_SHORT = 'short' # 短篇
|
|
32
|
+
CATEGORY_ANOTHER = 'another' # 其他
|
|
33
|
+
CATEGORY_HANMAN = 'hanman' # 韩漫
|
|
34
|
+
CATEGORY_MEIMAN = 'meiman' # 美漫
|
|
35
|
+
CATEGORY_DOUJIN_COSPLAY = 'doujin_cosplay' # cosplay
|
|
36
|
+
CATEGORY_3D = '3D' # 3D
|
|
37
|
+
CATEGORY_ENGLISH_SITE = 'english_site' # 英文站
|
|
38
|
+
CATEGORY_JM_TEAM = '禁漫漢化組'
|
|
38
39
|
|
|
39
40
|
# 分页大小
|
|
40
41
|
PAGE_SIZE_SEARCH = 80
|
|
@@ -52,10 +53,10 @@ class JmMagicConstants:
|
|
|
52
53
|
APP_TOKEN_SECRET = '18comicAPP'
|
|
53
54
|
APP_TOKEN_SECRET_2 = '18comicAPPContent'
|
|
54
55
|
APP_DATA_SECRET = '185Hcomic3PAPP7R'
|
|
55
|
-
APP_VERSION = '1.6.
|
|
56
|
+
APP_VERSION = '1.6.7'
|
|
56
57
|
APP_HEADERS_TEMPLATE = {
|
|
57
58
|
'Accept-Encoding': 'gzip',
|
|
58
|
-
'user-agent': 'Mozilla/5.0 (Linux; Android 9; V1938CT Build/PQ3A.190705.
|
|
59
|
+
'user-agent': 'Mozilla/5.0 (Linux; Android 9; V1938CT Build/PQ3A.190705.11211812; wv) AppleWebKit/537.36 (KHTML, '
|
|
59
60
|
'like Gecko) Version/4.0 Chrome/91.0.4472.114 Safari/537.36',
|
|
60
61
|
}
|
|
61
62
|
|
|
@@ -81,7 +82,7 @@ class JmModuleConfig:
|
|
|
81
82
|
# 网站相关
|
|
82
83
|
PROT = "https://"
|
|
83
84
|
JM_REDIRECT_URL = f'{PROT}jm365.work/3YeBdF' # 永久網域,怕走失的小伙伴收藏起来
|
|
84
|
-
JM_PUB_URL = f'{PROT}jmcomic.
|
|
85
|
+
JM_PUB_URL = f'{PROT}jmcomic-fb.vip'
|
|
85
86
|
JM_CDN_IMAGE_URL_TEMPLATE = PROT + 'cdn-msp.{domain}/media/photos/{photo_id}/{index:05}{suffix}' # index 从1开始
|
|
86
87
|
JM_IMAGE_SUFFIX = ['.jpg', '.webp', '.png', '.gif']
|
|
87
88
|
|
|
@@ -259,6 +260,41 @@ class JmModuleConfig:
|
|
|
259
260
|
cls.jm_log('module.html_domain_all', f'获取禁漫网页全部域名: [{resp.url}] → {domain_list}')
|
|
260
261
|
return domain_list
|
|
261
262
|
|
|
263
|
+
@classmethod
|
|
264
|
+
def get_html_domain_all_via_github(cls,
|
|
265
|
+
postman=None,
|
|
266
|
+
template='https://jmcmomic.github.io/go/{}.html',
|
|
267
|
+
index_range=(300, 309)
|
|
268
|
+
):
|
|
269
|
+
"""
|
|
270
|
+
通过禁漫官方的github号的repo获取最新的禁漫域名
|
|
271
|
+
https://github.com/jmcmomic/jmcmomic.github.io
|
|
272
|
+
"""
|
|
273
|
+
postman = postman or cls.new_postman(headers={
|
|
274
|
+
'authority': 'github.com',
|
|
275
|
+
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 '
|
|
276
|
+
'Safari/537.36'
|
|
277
|
+
})
|
|
278
|
+
domain_set = set()
|
|
279
|
+
|
|
280
|
+
def fetch_domain(url):
|
|
281
|
+
resp = postman.get(url, allow_redirects=False)
|
|
282
|
+
text = resp.text
|
|
283
|
+
from .jm_toolkit import JmcomicText
|
|
284
|
+
for domain in JmcomicText.analyse_jm_pub_html(text):
|
|
285
|
+
if domain.startswith('jm365'):
|
|
286
|
+
continue
|
|
287
|
+
domain_set.add(domain)
|
|
288
|
+
|
|
289
|
+
from common import multi_thread_launcher
|
|
290
|
+
|
|
291
|
+
multi_thread_launcher(
|
|
292
|
+
iter_objs=[template.format(i) for i in range(*index_range)],
|
|
293
|
+
apply_each_obj_func=fetch_domain,
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
return domain_set
|
|
297
|
+
|
|
262
298
|
@classmethod
|
|
263
299
|
def new_html_headers(cls, domain='18comic.vip'):
|
|
264
300
|
"""
|
|
@@ -50,8 +50,10 @@ class JmDownloader(DownloadCallback):
|
|
|
50
50
|
|
|
51
51
|
def __init__(self, option: JmOption) -> None:
|
|
52
52
|
self.option = option
|
|
53
|
-
#
|
|
54
|
-
self.
|
|
53
|
+
# 下载成功的记录dict
|
|
54
|
+
self.download_success_dict: Dict[JmAlbumDetail, Dict[JmPhotoDetail, List[Tuple[str, JmImageDetail]]]] = {}
|
|
55
|
+
# 下载失败的记录list
|
|
56
|
+
self.download_failed_list: List[Tuple[JmImageDetail, BaseException]] = []
|
|
55
57
|
|
|
56
58
|
def download_album(self, album_id):
|
|
57
59
|
client = self.client_for_album(album_id)
|
|
@@ -101,11 +103,21 @@ class JmDownloader(DownloadCallback):
|
|
|
101
103
|
if use_cache is True and image.is_exists:
|
|
102
104
|
return
|
|
103
105
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
106
|
+
e = None
|
|
107
|
+
try:
|
|
108
|
+
client.download_by_image_detail(
|
|
109
|
+
image,
|
|
110
|
+
img_save_path,
|
|
111
|
+
decode_image=decode_image,
|
|
112
|
+
)
|
|
113
|
+
except BaseException as e:
|
|
114
|
+
jm_log('image.failed', f'图片下载失败: [{image.download_url}], 异常: {e}')
|
|
115
|
+
# 保存失败记录
|
|
116
|
+
self.download_failed_list.append((image, e))
|
|
117
|
+
|
|
118
|
+
if e is not None:
|
|
119
|
+
raise e
|
|
120
|
+
|
|
109
121
|
self.after_image(image, img_save_path)
|
|
110
122
|
|
|
111
123
|
# noinspection PyMethodMayBeStatic
|
|
@@ -164,16 +176,38 @@ class JmDownloader(DownloadCallback):
|
|
|
164
176
|
"""
|
|
165
177
|
return self.option.build_jm_client()
|
|
166
178
|
|
|
179
|
+
@property
|
|
180
|
+
def all_success(self) -> bool:
|
|
181
|
+
"""
|
|
182
|
+
是否成功下载了全部图片
|
|
183
|
+
|
|
184
|
+
该属性需要等到downloader的全部download_xxx方法完成后才有意义。
|
|
185
|
+
|
|
186
|
+
注意!如果使用了filter机制,例如通过filter只下载3张图片,那么all_success也会为False
|
|
187
|
+
"""
|
|
188
|
+
if len(self.download_failed_list) != 0:
|
|
189
|
+
return False
|
|
190
|
+
|
|
191
|
+
for album, photo_dict in self.download_success_dict.items():
|
|
192
|
+
if len(album) != len(photo_dict):
|
|
193
|
+
return False
|
|
194
|
+
|
|
195
|
+
for photo, image_list in photo_dict.items():
|
|
196
|
+
if len(photo) != len(image_list):
|
|
197
|
+
return False
|
|
198
|
+
|
|
199
|
+
return True
|
|
200
|
+
|
|
167
201
|
# 下面是回调方法
|
|
168
202
|
|
|
169
203
|
def before_album(self, album: JmAlbumDetail):
|
|
170
204
|
super().before_album(album)
|
|
171
|
-
self.
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
205
|
+
self.download_success_dict.setdefault(album, {})
|
|
206
|
+
self.option.call_all_plugin(
|
|
207
|
+
'before_album',
|
|
208
|
+
album=album,
|
|
209
|
+
downloader=self,
|
|
210
|
+
)
|
|
177
211
|
|
|
178
212
|
def after_album(self, album: JmAlbumDetail):
|
|
179
213
|
super().after_album(album)
|
|
@@ -183,6 +217,16 @@ class JmDownloader(DownloadCallback):
|
|
|
183
217
|
downloader=self,
|
|
184
218
|
)
|
|
185
219
|
|
|
220
|
+
def before_photo(self, photo: JmPhotoDetail):
|
|
221
|
+
super().before_photo(photo)
|
|
222
|
+
self.download_success_dict.setdefault(photo.from_album, {})
|
|
223
|
+
self.download_success_dict[photo.from_album].setdefault(photo, [])
|
|
224
|
+
self.option.call_all_plugin(
|
|
225
|
+
'before_photo',
|
|
226
|
+
photo=photo,
|
|
227
|
+
downloader=self,
|
|
228
|
+
)
|
|
229
|
+
|
|
186
230
|
def after_photo(self, photo: JmPhotoDetail):
|
|
187
231
|
super().after_photo(photo)
|
|
188
232
|
self.option.call_all_plugin(
|
|
@@ -191,12 +235,25 @@ class JmDownloader(DownloadCallback):
|
|
|
191
235
|
downloader=self,
|
|
192
236
|
)
|
|
193
237
|
|
|
238
|
+
def before_image(self, image: JmImageDetail, img_save_path):
|
|
239
|
+
super().before_image(image, img_save_path)
|
|
240
|
+
self.option.call_all_plugin(
|
|
241
|
+
'before_image',
|
|
242
|
+
image=image,
|
|
243
|
+
downloader=self,
|
|
244
|
+
)
|
|
245
|
+
|
|
194
246
|
def after_image(self, image: JmImageDetail, img_save_path):
|
|
195
247
|
super().after_image(image, img_save_path)
|
|
196
248
|
photo = image.from_photo
|
|
197
249
|
album = photo.from_album
|
|
198
250
|
|
|
199
|
-
self.
|
|
251
|
+
self.download_success_dict.get(album).get(photo).append((img_save_path, image))
|
|
252
|
+
self.option.call_all_plugin(
|
|
253
|
+
'after_image',
|
|
254
|
+
image=image,
|
|
255
|
+
downloader=self,
|
|
256
|
+
)
|
|
200
257
|
|
|
201
258
|
# 下面是对with语法的支持
|
|
202
259
|
|
|
@@ -219,28 +276,23 @@ class JmDownloader(DownloadCallback):
|
|
|
219
276
|
|
|
220
277
|
class DoNotDownloadImage(JmDownloader):
|
|
221
278
|
"""
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
用法:
|
|
225
|
-
|
|
226
|
-
JmModuleConfig.CLASS_DOWNLOADER = DoNotDownloadImage
|
|
279
|
+
不会下载任何图片的Downloader,用作测试
|
|
227
280
|
"""
|
|
228
281
|
|
|
229
282
|
def download_by_image_detail(self, image: JmImageDetail, client: JmcomicClient):
|
|
230
283
|
# ensure make dir
|
|
231
284
|
self.option.decide_image_filepath(image)
|
|
232
|
-
pass
|
|
233
285
|
|
|
234
286
|
|
|
235
287
|
class JustDownloadSpecificCountImage(JmDownloader):
|
|
288
|
+
"""
|
|
289
|
+
只下载特定数量图片的Downloader,用作测试
|
|
290
|
+
"""
|
|
236
291
|
from threading import Lock
|
|
237
292
|
|
|
238
293
|
count_lock = Lock()
|
|
239
294
|
count = 0
|
|
240
295
|
|
|
241
|
-
def __init__(self, option: JmOption) -> None:
|
|
242
|
-
super().__init__(option)
|
|
243
|
-
|
|
244
296
|
def download_by_image_detail(self, image: JmImageDetail, client: JmcomicClient):
|
|
245
297
|
# ensure make dir
|
|
246
298
|
self.option.decide_image_filepath(image)
|
|
@@ -91,17 +91,17 @@ class DetailEntity(JmBaseEntity, IndexedEntity):
|
|
|
91
91
|
"""
|
|
92
92
|
authoroname = author + oname
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
个人认为识别度比较高的本子名称,一眼看去就能获取到本子的关键信息
|
|
95
95
|
|
|
96
|
-
具体格式:
|
|
96
|
+
具体格式: '【author】oname'
|
|
97
97
|
|
|
98
98
|
示例:
|
|
99
99
|
|
|
100
|
-
|
|
100
|
+
Pname:喂我吃吧 老師! [欶瀾漢化組] [BLVEFO9] たべさせて、せんせい! (ブルーアーカイブ) [中國翻譯] [無修正]
|
|
101
101
|
|
|
102
|
-
|
|
102
|
+
Pauthoroname:【BLVEFO9】喂我吃吧 老師!
|
|
103
103
|
|
|
104
|
-
:return:
|
|
104
|
+
:return: 返回作者名+本子原始名称,格式为: '【author】oname'
|
|
105
105
|
"""
|
|
106
106
|
return f'【{self.author}】{self.oname}'
|
|
107
107
|
|
|
@@ -109,12 +109,16 @@ class DetailEntity(JmBaseEntity, IndexedEntity):
|
|
|
109
109
|
def idoname(self):
|
|
110
110
|
"""
|
|
111
111
|
类似 authoroname
|
|
112
|
-
|
|
112
|
+
|
|
113
|
+
:return: '[id] oname'
|
|
113
114
|
"""
|
|
114
115
|
return f'[{self.id}] {self.oname}'
|
|
115
116
|
|
|
116
117
|
def __str__(self):
|
|
117
|
-
return f'{self.__class__.__name__}
|
|
118
|
+
return f'{self.__class__.__name__}' \
|
|
119
|
+
'{' \
|
|
120
|
+
f'{self.id}: {self.title}'\
|
|
121
|
+
'}'
|
|
118
122
|
|
|
119
123
|
@classmethod
|
|
120
124
|
def __alias__(cls):
|
|
@@ -105,13 +105,11 @@ class DirRule:
|
|
|
105
105
|
解析下载路径dsl,得到一个路径规则解析列表
|
|
106
106
|
"""
|
|
107
107
|
|
|
108
|
-
|
|
109
|
-
ExceptionTool.raises(f'不支持的dsl: "{rule_dsl}"')
|
|
110
|
-
|
|
111
|
-
rule_list = rule_dsl.split('_')
|
|
108
|
+
rule_list = self.split_rule_dsl(rule_dsl)
|
|
112
109
|
solver_ls: List[DirRule.RuleSolver] = []
|
|
113
110
|
|
|
114
111
|
for rule in rule_list:
|
|
112
|
+
rule = rule.strip()
|
|
115
113
|
if rule == 'Bd':
|
|
116
114
|
solver_ls.append((0, lambda _: base_dir, 'Bd'))
|
|
117
115
|
continue
|
|
@@ -124,6 +122,19 @@ class DirRule:
|
|
|
124
122
|
|
|
125
123
|
return solver_ls
|
|
126
124
|
|
|
125
|
+
# noinspection PyMethodMayBeStatic
|
|
126
|
+
def split_rule_dsl(self, rule_dsl: str) -> List[str]:
|
|
127
|
+
if rule_dsl == 'Bd':
|
|
128
|
+
return [rule_dsl]
|
|
129
|
+
|
|
130
|
+
if '/' in rule_dsl:
|
|
131
|
+
return rule_dsl.split('/')
|
|
132
|
+
|
|
133
|
+
if '_' in rule_dsl:
|
|
134
|
+
return rule_dsl.split('_')
|
|
135
|
+
|
|
136
|
+
ExceptionTool.raises(f'不支持的rule配置: "{rule_dsl}"')
|
|
137
|
+
|
|
127
138
|
@classmethod
|
|
128
139
|
def get_rule_solver(cls, rule: str) -> Optional[RuleSolver]:
|
|
129
140
|
# 查找缓存
|
|
@@ -181,6 +192,7 @@ class JmOption:
|
|
|
181
192
|
client: Dict,
|
|
182
193
|
plugins: Dict,
|
|
183
194
|
filepath=None,
|
|
195
|
+
call_after_init_plugin=True,
|
|
184
196
|
):
|
|
185
197
|
# 路径规则配置
|
|
186
198
|
self.dir_rule = DirRule(**dir_rule)
|
|
@@ -196,7 +208,21 @@ class JmOption:
|
|
|
196
208
|
# 需要主线程等待完成的插件
|
|
197
209
|
self.need_wait_plugins = []
|
|
198
210
|
|
|
199
|
-
|
|
211
|
+
if call_after_init_plugin:
|
|
212
|
+
self.call_all_plugin('after_init', safe=True)
|
|
213
|
+
|
|
214
|
+
def copy_option(self):
|
|
215
|
+
return self.__class__(
|
|
216
|
+
dir_rule={
|
|
217
|
+
'rule': self.dir_rule.rule_dsl,
|
|
218
|
+
'base_dir': self.dir_rule.base_dir,
|
|
219
|
+
},
|
|
220
|
+
download=self.download.src_dict,
|
|
221
|
+
client=self.client.src_dict,
|
|
222
|
+
plugins=self.plugins.src_dict,
|
|
223
|
+
filepath=self.filepath,
|
|
224
|
+
call_after_init_plugin=False
|
|
225
|
+
)
|
|
200
226
|
|
|
201
227
|
"""
|
|
202
228
|
下面是decide系列方法,为了支持重写和增加程序动态性。
|
|
@@ -298,7 +298,7 @@ class ZipPlugin(JmOptionPlugin):
|
|
|
298
298
|
|
|
299
299
|
# 原文件夹 -> zip文件
|
|
300
300
|
dir_zip_dict: Dict[str, Optional[str]] = {}
|
|
301
|
-
photo_dict = downloader.
|
|
301
|
+
photo_dict = downloader.download_success_dict[album]
|
|
302
302
|
|
|
303
303
|
if level == 'album':
|
|
304
304
|
zip_path = self.get_zip_path(album, None, filename_rule, suffix, zip_dir)
|
|
@@ -383,7 +383,7 @@ class ZipPlugin(JmOptionPlugin):
|
|
|
383
383
|
dirs = sorted(dir_zip_dict.keys(), reverse=True)
|
|
384
384
|
image_paths = [
|
|
385
385
|
path
|
|
386
|
-
for photo_dict in self.downloader.
|
|
386
|
+
for photo_dict in self.downloader.download_success_dict.values()
|
|
387
387
|
for image_list in photo_dict.values()
|
|
388
388
|
for path, image in image_list
|
|
389
389
|
]
|
|
@@ -752,7 +752,7 @@ class ConvertJpgToPdfPlugin(JmOptionPlugin):
|
|
|
752
752
|
|
|
753
753
|
paths = [
|
|
754
754
|
path
|
|
755
|
-
for path, image in downloader.
|
|
755
|
+
for path, image in downloader.download_success_dict[photo.from_album][photo]
|
|
756
756
|
]
|
|
757
757
|
|
|
758
758
|
paths.append(self.option.decide_image_save_dir(photo, ensure_exists=False))
|
|
@@ -814,8 +814,6 @@ class JmServerPlugin(JmOptionPlugin):
|
|
|
814
814
|
if self.running is True:
|
|
815
815
|
return
|
|
816
816
|
|
|
817
|
-
self.running = True
|
|
818
|
-
|
|
819
817
|
# 服务器的代码位于一个独立库:plugin_jm_server,需要独立安装
|
|
820
818
|
# 源代码仓库:https://github.com/hect0x7/plugin-jm-server
|
|
821
819
|
try:
|
|
@@ -842,6 +840,7 @@ class JmServerPlugin(JmOptionPlugin):
|
|
|
842
840
|
# 不是主线程,return
|
|
843
841
|
return self.warning_wrong_usage_of_debug()
|
|
844
842
|
else:
|
|
843
|
+
self.running = True
|
|
845
844
|
# 是主线程,启动服务器
|
|
846
845
|
blocking_run_server()
|
|
847
846
|
|
|
@@ -849,6 +848,7 @@ class JmServerPlugin(JmOptionPlugin):
|
|
|
849
848
|
# 非debug模式,开新线程启动
|
|
850
849
|
threading.Thread(target=blocking_run_server, daemon=True).start()
|
|
851
850
|
atexit_register(self.wait_server_stop)
|
|
851
|
+
self.running = True
|
|
852
852
|
|
|
853
853
|
def warning_wrong_usage_of_debug(self):
|
|
854
854
|
self.log('注意!当配置debug=True时,请确保当前插件是在主线程中被调用。\n'
|
|
@@ -357,17 +357,16 @@ class JmPageTool:
|
|
|
357
357
|
# 用来缩减html的长度
|
|
358
358
|
pattern_html_search_shorten_for = compile(r'<div class="well well-sm">([\s\S]*)<div class="row">')
|
|
359
359
|
|
|
360
|
-
#
|
|
360
|
+
# 用来提取搜索页面的album的信息
|
|
361
361
|
pattern_html_search_album_info_list = compile(
|
|
362
362
|
r'<a href="/album/(\d+)/[\s\S]*?title="(.*?)"([\s\S]*?)<div class="title-truncate tags .*>([\s\S]*?)</div>'
|
|
363
363
|
)
|
|
364
364
|
|
|
365
|
-
#
|
|
365
|
+
# 用来提取分类页面的album的信息
|
|
366
366
|
pattern_html_category_album_info_list = compile(
|
|
367
|
-
r'<a href="/album/(\d+)/[^>]*>[
|
|
368
|
-
r'\n
|
|
369
|
-
r'<div class="label-loveicon">'
|
|
370
|
-
r'([\s\S]*?)'
|
|
367
|
+
r'<a href="/album/(\d+)/[^>]*>[^>]*?'
|
|
368
|
+
r'title="(.*?)"[^>]*>[ \n]*</a>[ \n]*'
|
|
369
|
+
r'<div class="label-loveicon">([\s\S]*?)'
|
|
371
370
|
r'<div class="clearfix">'
|
|
372
371
|
)
|
|
373
372
|
|
|
@@ -473,7 +472,7 @@ class JmPageTool:
|
|
|
473
472
|
return JmFavoritePage(content, folder_list, total)
|
|
474
473
|
|
|
475
474
|
@classmethod
|
|
476
|
-
def parse_api_to_search_page(cls, data:
|
|
475
|
+
def parse_api_to_search_page(cls, data: AdvancedEasyAccessDict) -> JmSearchPage:
|
|
477
476
|
"""
|
|
478
477
|
model_data: {
|
|
479
478
|
"search_query": "MANA",
|
|
@@ -502,7 +501,7 @@ class JmPageTool:
|
|
|
502
501
|
return JmSearchPage(content, total)
|
|
503
502
|
|
|
504
503
|
@classmethod
|
|
505
|
-
def parse_api_to_favorite_page(cls, data:
|
|
504
|
+
def parse_api_to_favorite_page(cls, data: AdvancedEasyAccessDict) -> JmFavoritePage:
|
|
506
505
|
"""
|
|
507
506
|
{
|
|
508
507
|
"list": [
|
|
@@ -547,7 +546,7 @@ class JmPageTool:
|
|
|
547
546
|
|
|
548
547
|
@classmethod
|
|
549
548
|
def adapt_content(cls, content):
|
|
550
|
-
def adapt_item(item:
|
|
549
|
+
def adapt_item(item: AdvancedEasyAccessDict):
|
|
551
550
|
item: dict = item.src_dict
|
|
552
551
|
item.setdefault('tags', [])
|
|
553
552
|
return item
|
|
@@ -674,7 +673,7 @@ class JmApiAdaptTool:
|
|
|
674
673
|
series = data['series']
|
|
675
674
|
episode_list = []
|
|
676
675
|
for chapter in series:
|
|
677
|
-
chapter =
|
|
676
|
+
chapter = AdvancedEasyAccessDict(chapter)
|
|
678
677
|
# photo_id, photo_index, photo_title, photo_pub_date
|
|
679
678
|
episode_list.append(
|
|
680
679
|
(chapter.id, chapter.sort, chapter.name, None)
|
|
@@ -689,7 +688,7 @@ class JmApiAdaptTool:
|
|
|
689
688
|
sort = 1
|
|
690
689
|
series: list = data['series'] # series中的sort从1开始
|
|
691
690
|
for chapter in series:
|
|
692
|
-
chapter =
|
|
691
|
+
chapter = AdvancedEasyAccessDict(chapter)
|
|
693
692
|
if int(chapter.id) == int(data['id']):
|
|
694
693
|
sort = chapter.sort
|
|
695
694
|
break
|
|
@@ -755,23 +754,34 @@ class JmImageTool:
|
|
|
755
754
|
|
|
756
755
|
# 创建新的解密图片
|
|
757
756
|
img_decode = Image.new("RGB", (w, h))
|
|
758
|
-
|
|
759
|
-
copyW = w
|
|
757
|
+
over = h % num
|
|
760
758
|
for i in range(num):
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
759
|
+
move = math.floor(h / num)
|
|
760
|
+
y_src = h - (move * (i + 1)) - over
|
|
761
|
+
y_dst = move * i
|
|
764
762
|
|
|
765
763
|
if i == 0:
|
|
766
|
-
|
|
764
|
+
move += over
|
|
767
765
|
else:
|
|
768
|
-
|
|
766
|
+
y_dst += over
|
|
769
767
|
|
|
770
768
|
img_decode.paste(
|
|
771
|
-
img_src.crop((
|
|
772
|
-
|
|
769
|
+
img_src.crop((
|
|
770
|
+
0, y_src,
|
|
771
|
+
w, y_src + move
|
|
772
|
+
)),
|
|
773
|
+
(
|
|
774
|
+
0, y_dst,
|
|
775
|
+
w, y_dst + move
|
|
776
|
+
)
|
|
773
777
|
)
|
|
774
778
|
|
|
779
|
+
# save every step result
|
|
780
|
+
# cls.save_image(img_decode, change_file_name(
|
|
781
|
+
# decoded_save_path,
|
|
782
|
+
# f'{of_file_name(decoded_save_path, trim_suffix=True)}_{i}{of_file_suffix(decoded_save_path)}'
|
|
783
|
+
# ))
|
|
784
|
+
|
|
775
785
|
# 保存到新的解密文件
|
|
776
786
|
cls.save_image(img_decode, decoded_save_path)
|
|
777
787
|
|
|
@@ -867,7 +877,7 @@ class JmCryptoTool:
|
|
|
867
877
|
"""
|
|
868
878
|
解密接口返回值
|
|
869
879
|
|
|
870
|
-
:param data:
|
|
880
|
+
:param data: resp.json()['data']
|
|
871
881
|
:param ts: 时间戳
|
|
872
882
|
:param secret: 密钥
|
|
873
883
|
:return: json格式的字符串
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: jmcomic
|
|
3
|
-
Version: 2.5.
|
|
3
|
+
Version: 2.5.7
|
|
4
4
|
Summary: Python API For JMComic (禁漫天堂)
|
|
5
5
|
Home-page: https://github.com/hect0x7/JMComic-Crawler-Python
|
|
6
6
|
Author: hect0x7
|
|
@@ -42,16 +42,26 @@ Requires-Dist: pycryptodome
|
|
|
42
42
|
|
|
43
43
|
本项目的核心功能是下载本子,基于此,设计了一套方便使用、便于扩展,能满足一些特殊下载需求的框架。
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
目前核心功能实现较为稳定,项目也处于维护阶段。
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
除了下载功能以外,也实现了其他的一些禁漫接口,按需实现,具体如下。
|
|
48
|
+
|
|
49
|
+
### 已实现的禁漫API:
|
|
50
|
+
|
|
51
|
+
- 登录
|
|
52
|
+
- 搜本
|
|
53
|
+
- 分类 (排行榜)
|
|
54
|
+
- 本子章节详情
|
|
55
|
+
- 图片下载解码
|
|
56
|
+
- 收藏夹
|
|
57
|
+
- 移动端接口加解密
|
|
48
58
|
|
|
49
59
|
## 安装教程
|
|
50
60
|
|
|
51
61
|
* 通过pip官方源安装(推荐,并且更新也是这个命令)
|
|
52
62
|
|
|
53
63
|
```shell
|
|
54
|
-
pip install jmcomic -i https://pypi.org/project
|
|
64
|
+
pip install jmcomic -i https://pypi.org/project -U
|
|
55
65
|
```
|
|
56
66
|
* 通过源代码安装
|
|
57
67
|
|
|
@@ -75,6 +85,21 @@ jmcomic.download_album('422866') # 传入要下载的album的id,即可下载
|
|
|
75
85
|
$ jmcomic 422866
|
|
76
86
|
```
|
|
77
87
|
|
|
88
|
+
## 进阶使用
|
|
89
|
+
|
|
90
|
+
文档网站:[jmcomic.readthedocs.io](https://jmcomic.readthedocs.io/en/latest)
|
|
91
|
+
|
|
92
|
+
进阶使用可以参考:[jmcomic常用类和方法演示](assets/docs/sources/tutorial/0_demo.md)
|
|
93
|
+
|
|
94
|
+
下面列出的是一些常用的文档:
|
|
95
|
+
|
|
96
|
+
* [jmcomic常用类和方法演示](assets/docs/sources/tutorial/0_demo.md)
|
|
97
|
+
* [option配置文件语法(包含插件配置)](./assets/docs/sources/option_file_syntax.md)
|
|
98
|
+
* [GitHub Actions使用教程](./assets/docs/sources/tutorial/1_github_actions.md)
|
|
99
|
+
* [命令行使用教程](assets/docs/sources/tutorial/2_command_line.md)
|
|
100
|
+
* [插件机制](assets/docs/sources/tutorial/6_plugin.md)
|
|
101
|
+
* [下载过滤器机制](assets/docs/sources/tutorial/5_filter.md)
|
|
102
|
+
|
|
78
103
|
## 项目特点
|
|
79
104
|
|
|
80
105
|
- **绕过Cloudflare的反爬虫**
|
|
@@ -111,19 +136,6 @@ $ jmcomic 422866
|
|
|
111
136
|
- `jpg图片合成为一个pdf插件`
|
|
112
137
|
- `导出收藏夹为csv文件插件`
|
|
113
138
|
|
|
114
|
-
## 进阶使用
|
|
115
|
-
|
|
116
|
-
进阶使用请查阅文档:[文档](https://jmcomic.readthedocs.io/en/latest)
|
|
117
|
-
|
|
118
|
-
下面列出一些常用的文档链接:
|
|
119
|
-
|
|
120
|
-
* [option配置文件语法(包含插件配置)](./assets/docs/sources/option_file_syntax.md)
|
|
121
|
-
* [常用类和方法演示(下载本子、获取实体类、搜索本子)](assets/docs/sources/tutorial/3_demo.md)
|
|
122
|
-
* [命令行使用教程](assets/docs/sources/tutorial/2_command_line.md)
|
|
123
|
-
* [GitHub Actions使用教程](./assets/docs/sources/tutorial/1_github_actions.md)
|
|
124
|
-
* [插件机制](assets/docs/sources/tutorial/6_plugin.md)
|
|
125
|
-
* [下载过滤器机制](assets/docs/sources/tutorial/5_filter.md)
|
|
126
|
-
|
|
127
139
|
## 使用小说明
|
|
128
140
|
|
|
129
141
|
* Python >= 3.7
|
|
@@ -131,10 +143,11 @@ $ jmcomic 422866
|
|
|
131
143
|
|
|
132
144
|
## 项目文件夹介绍
|
|
133
145
|
|
|
146
|
+
* .github:GitHub Actions配置文件
|
|
134
147
|
* assets:存放一些非代码的资源文件
|
|
135
148
|
|
|
136
|
-
* config:存放配置文件
|
|
137
149
|
* docs:项目文档
|
|
150
|
+
* option:存放配置文件
|
|
138
151
|
|
|
139
152
|
* src:存放源代码
|
|
140
153
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|