jmcomic 2.5.15__tar.gz → 2.5.17__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.15/src/jmcomic.egg-info → jmcomic-2.5.17}/PKG-INFO +1 -1
- {jmcomic-2.5.15 → jmcomic-2.5.17}/src/jmcomic/__init__.py +1 -1
- {jmcomic-2.5.15 → jmcomic-2.5.17}/src/jmcomic/api.py +1 -0
- {jmcomic-2.5.15 → jmcomic-2.5.17}/src/jmcomic/jm_client_impl.py +20 -10
- {jmcomic-2.5.15 → jmcomic-2.5.17}/src/jmcomic/jm_config.py +44 -36
- {jmcomic-2.5.15 → jmcomic-2.5.17}/src/jmcomic/jm_entity.py +10 -5
- {jmcomic-2.5.15 → jmcomic-2.5.17}/src/jmcomic/jm_option.py +13 -13
- {jmcomic-2.5.15 → jmcomic-2.5.17}/src/jmcomic/jm_plugin.py +67 -17
- {jmcomic-2.5.15 → jmcomic-2.5.17}/src/jmcomic/jm_toolkit.py +13 -0
- {jmcomic-2.5.15 → jmcomic-2.5.17/src/jmcomic.egg-info}/PKG-INFO +1 -1
- {jmcomic-2.5.15 → jmcomic-2.5.17}/LICENSE +0 -0
- {jmcomic-2.5.15 → jmcomic-2.5.17}/README.md +0 -0
- {jmcomic-2.5.15 → jmcomic-2.5.17}/setup.cfg +0 -0
- {jmcomic-2.5.15 → jmcomic-2.5.17}/setup.py +0 -0
- {jmcomic-2.5.15 → jmcomic-2.5.17}/src/jmcomic/cl.py +0 -0
- {jmcomic-2.5.15 → jmcomic-2.5.17}/src/jmcomic/jm_client_interface.py +0 -0
- {jmcomic-2.5.15 → jmcomic-2.5.17}/src/jmcomic/jm_downloader.py +0 -0
- {jmcomic-2.5.15 → jmcomic-2.5.17}/src/jmcomic/jm_exception.py +0 -0
- {jmcomic-2.5.15 → jmcomic-2.5.17}/src/jmcomic.egg-info/SOURCES.txt +0 -0
- {jmcomic-2.5.15 → jmcomic-2.5.17}/src/jmcomic.egg-info/dependency_links.txt +0 -0
- {jmcomic-2.5.15 → jmcomic-2.5.17}/src/jmcomic.egg-info/entry_points.txt +0 -0
- {jmcomic-2.5.15 → jmcomic-2.5.17}/src/jmcomic.egg-info/requires.txt +0 -0
- {jmcomic-2.5.15 → jmcomic-2.5.17}/src/jmcomic.egg-info/top_level.txt +0 -0
|
@@ -27,7 +27,7 @@ class AbstractJmClient(
|
|
|
27
27
|
self.retry_times = retry_times
|
|
28
28
|
self.domain_list = domain_list
|
|
29
29
|
self.CLIENT_CACHE = None
|
|
30
|
-
self.
|
|
30
|
+
self._username = None # help for favorite_folder method
|
|
31
31
|
self.enable_cache()
|
|
32
32
|
self.after_init()
|
|
33
33
|
|
|
@@ -92,7 +92,7 @@ class AbstractJmClient(
|
|
|
92
92
|
jm_log(self.log_topic(), self.decode(url))
|
|
93
93
|
else:
|
|
94
94
|
# 图片url
|
|
95
|
-
|
|
95
|
+
self.update_request_with_specify_domain(kwargs, None, True)
|
|
96
96
|
|
|
97
97
|
if domain_index != 0 or retry_count != 0:
|
|
98
98
|
jm_log(f'req.retry',
|
|
@@ -133,7 +133,7 @@ class AbstractJmClient(
|
|
|
133
133
|
"""
|
|
134
134
|
return resp
|
|
135
135
|
|
|
136
|
-
def update_request_with_specify_domain(self, kwargs: dict, domain: str):
|
|
136
|
+
def update_request_with_specify_domain(self, kwargs: dict, domain: Optional[str], is_image: bool = False):
|
|
137
137
|
"""
|
|
138
138
|
域名自动切换时,用于更新请求参数的回调
|
|
139
139
|
"""
|
|
@@ -412,7 +412,7 @@ class JmHtmlClient(AbstractJmClient):
|
|
|
412
412
|
return resp
|
|
413
413
|
|
|
414
414
|
self['cookies'] = new_cookies
|
|
415
|
-
self.
|
|
415
|
+
self._username = username
|
|
416
416
|
|
|
417
417
|
return resp
|
|
418
418
|
|
|
@@ -423,8 +423,8 @@ class JmHtmlClient(AbstractJmClient):
|
|
|
423
423
|
username='',
|
|
424
424
|
) -> JmFavoritePage:
|
|
425
425
|
if username == '':
|
|
426
|
-
ExceptionTool.require_true(self.
|
|
427
|
-
username = self.
|
|
426
|
+
ExceptionTool.require_true(self._username is not None, 'favorite_folder方法需要传username参数')
|
|
427
|
+
username = self._username
|
|
428
428
|
|
|
429
429
|
resp = self.get_jm_html(
|
|
430
430
|
f'/user/{username}/favorite/albums',
|
|
@@ -463,7 +463,10 @@ class JmHtmlClient(AbstractJmClient):
|
|
|
463
463
|
|
|
464
464
|
return resp
|
|
465
465
|
|
|
466
|
-
def update_request_with_specify_domain(self, kwargs: dict, domain: Optional[str]):
|
|
466
|
+
def update_request_with_specify_domain(self, kwargs: dict, domain: Optional[str], is_image=False):
|
|
467
|
+
if is_image:
|
|
468
|
+
return
|
|
469
|
+
|
|
467
470
|
latest_headers = kwargs.get('headers', None)
|
|
468
471
|
base_headers = self.get_meta_data('headers', None) or JmModuleConfig.new_html_headers(domain)
|
|
469
472
|
base_headers.update(latest_headers or {})
|
|
@@ -909,8 +912,10 @@ class JmApiClient(AbstractJmClient):
|
|
|
909
912
|
|
|
910
913
|
return resp
|
|
911
914
|
|
|
912
|
-
def update_request_with_specify_domain(self, kwargs: dict, domain: str):
|
|
913
|
-
|
|
915
|
+
def update_request_with_specify_domain(self, kwargs: dict, domain: Optional[str], is_image=False):
|
|
916
|
+
if is_image:
|
|
917
|
+
# 设置APP端的图片请求headers
|
|
918
|
+
kwargs['headers'] = {**JmModuleConfig.APP_HEADERS_TEMPLATE, **JmModuleConfig.APP_HEADERS_IMAGE}
|
|
914
919
|
|
|
915
920
|
# noinspection PyMethodMayBeStatic
|
|
916
921
|
def decide_headers_and_ts(self, kwargs, url):
|
|
@@ -930,7 +935,7 @@ class JmApiClient(AbstractJmClient):
|
|
|
930
935
|
token, tokenparam = JmCryptoTool.token_and_tokenparam(ts)
|
|
931
936
|
|
|
932
937
|
# 设置headers
|
|
933
|
-
headers = kwargs.get('headers', None) or
|
|
938
|
+
headers = kwargs.get('headers', None) or JmModuleConfig.APP_HEADERS_TEMPLATE.copy()
|
|
934
939
|
headers.update({
|
|
935
940
|
'token': token,
|
|
936
941
|
'tokenparam': tokenparam,
|
|
@@ -973,6 +978,11 @@ class JmApiClient(AbstractJmClient):
|
|
|
973
978
|
# 例如图片请求
|
|
974
979
|
return resp
|
|
975
980
|
|
|
981
|
+
code = resp.status_code
|
|
982
|
+
if code >= 500:
|
|
983
|
+
msg = JmModuleConfig.JM_ERROR_STATUS_CODE.get(code, f'HTTP状态码: {code}')
|
|
984
|
+
ExceptionTool.raises_resp(f"禁漫API异常响应, {msg}", resp)
|
|
985
|
+
|
|
976
986
|
url = resp.request.url
|
|
977
987
|
|
|
978
988
|
if self.API_SCRAMBLE in url:
|
|
@@ -59,50 +59,16 @@ class JmMagicConstants:
|
|
|
59
59
|
SUB_SINGLE_JAPANESE = SUB_JAPANESE
|
|
60
60
|
SUB_SINGLE_YOUTH = 'youth'
|
|
61
61
|
|
|
62
|
-
# 分页大小
|
|
63
|
-
PAGE_SIZE_SEARCH = 80
|
|
64
|
-
PAGE_SIZE_FAVORITE = 20
|
|
65
|
-
|
|
66
62
|
# 图片分割参数
|
|
67
63
|
SCRAMBLE_220980 = 220980
|
|
68
64
|
SCRAMBLE_268850 = 268850
|
|
69
65
|
SCRAMBLE_421926 = 421926 # 2023-02-08后改了图片切割算法
|
|
70
66
|
|
|
71
|
-
# 当本子没有作者名字时,顶替作者名字
|
|
72
|
-
DEFAULT_AUTHOR = 'default_author'
|
|
73
|
-
|
|
74
67
|
# 移动端API密钥
|
|
75
68
|
APP_TOKEN_SECRET = '18comicAPP'
|
|
76
69
|
APP_TOKEN_SECRET_2 = '18comicAPPContent'
|
|
77
70
|
APP_DATA_SECRET = '185Hcomic3PAPP7R'
|
|
78
|
-
APP_VERSION = '1.7.
|
|
79
|
-
APP_HEADERS_TEMPLATE = {
|
|
80
|
-
'Accept-Encoding': 'gzip',
|
|
81
|
-
'user-agent': 'Mozilla/5.0 (Linux; Android 9; V1938CT Build/PQ3A.190705.11211812; wv) AppleWebKit/537.36 (KHTML, '
|
|
82
|
-
'like Gecko) Version/4.0 Chrome/91.0.4472.114 Safari/537.36',
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
# 网页端headers
|
|
86
|
-
HTML_HEADERS_TEMPLATE = {
|
|
87
|
-
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,'
|
|
88
|
-
'application/signed-exchange;v=b3;q=0.7',
|
|
89
|
-
'accept-language': 'zh-CN,zh;q=0.9',
|
|
90
|
-
'cache-control': 'no-cache',
|
|
91
|
-
'dnt': '1',
|
|
92
|
-
'pragma': 'no-cache',
|
|
93
|
-
'priority': 'u=0, i',
|
|
94
|
-
'referer': 'https://18comic.vip/',
|
|
95
|
-
'sec-ch-ua': '"Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"',
|
|
96
|
-
'sec-ch-ua-mobile': '?0',
|
|
97
|
-
'sec-ch-ua-platform': '"Windows"',
|
|
98
|
-
'sec-fetch-dest': 'document',
|
|
99
|
-
'sec-fetch-mode': 'navigate',
|
|
100
|
-
'sec-fetch-site': 'none',
|
|
101
|
-
'sec-fetch-user': '?1',
|
|
102
|
-
'upgrade-insecure-requests': '1',
|
|
103
|
-
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 '
|
|
104
|
-
'Safari/537.36',
|
|
105
|
-
}
|
|
71
|
+
APP_VERSION = '1.7.1'
|
|
106
72
|
|
|
107
73
|
|
|
108
74
|
# 模块级别共用配置
|
|
@@ -123,13 +89,55 @@ class JmModuleConfig:
|
|
|
123
89
|
# JM的异常网页code
|
|
124
90
|
JM_ERROR_STATUS_CODE = {
|
|
125
91
|
403: 'ip地区禁止访问/爬虫被识别',
|
|
92
|
+
500: '500: 禁漫服务器内部异常(可能是服务器过载,可以切换ip或稍后重试)',
|
|
126
93
|
520: '520: Web server is returning an unknown error (禁漫服务器内部报错)',
|
|
127
94
|
524: '524: The origin web server timed out responding to this request. (禁漫服务器处理超时)',
|
|
128
95
|
}
|
|
129
96
|
|
|
97
|
+
# 分页大小
|
|
98
|
+
PAGE_SIZE_SEARCH = 80
|
|
99
|
+
PAGE_SIZE_FAVORITE = 20
|
|
100
|
+
|
|
130
101
|
# 图片分隔相关
|
|
131
102
|
SCRAMBLE_CACHE = {}
|
|
132
103
|
|
|
104
|
+
# 当本子没有作者名字时,顶替作者名字
|
|
105
|
+
DEFAULT_AUTHOR = 'default_author'
|
|
106
|
+
|
|
107
|
+
APP_HEADERS_TEMPLATE = {
|
|
108
|
+
'Accept-Encoding': 'gzip, deflate',
|
|
109
|
+
'user-agent': 'Mozilla/5.0 (Linux; Android 9; V1938CT Build/PQ3A.190705.11211812; wv) AppleWebKit/537.36 (KHTML, '
|
|
110
|
+
'like Gecko) Version/4.0 Chrome/91.0.4472.114 Safari/537.36',
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
APP_HEADERS_IMAGE = {
|
|
114
|
+
'Accept': 'image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8',
|
|
115
|
+
'X-Requested-With': 'com.jiaohua_browser',
|
|
116
|
+
'Referer': 'https://www.jmfreedomproxy.xyz/',
|
|
117
|
+
'Accept-Language': 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7',
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
# 网页端headers
|
|
121
|
+
HTML_HEADERS_TEMPLATE = {
|
|
122
|
+
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,'
|
|
123
|
+
'application/signed-exchange;v=b3;q=0.7',
|
|
124
|
+
'accept-language': 'zh-CN,zh;q=0.9',
|
|
125
|
+
'cache-control': 'no-cache',
|
|
126
|
+
'dnt': '1',
|
|
127
|
+
'pragma': 'no-cache',
|
|
128
|
+
'priority': 'u=0, i',
|
|
129
|
+
'referer': 'https://18comic.vip/',
|
|
130
|
+
'sec-ch-ua': '"Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"',
|
|
131
|
+
'sec-ch-ua-mobile': '?0',
|
|
132
|
+
'sec-ch-ua-platform': '"Windows"',
|
|
133
|
+
'sec-fetch-dest': 'document',
|
|
134
|
+
'sec-fetch-mode': 'navigate',
|
|
135
|
+
'sec-fetch-site': 'none',
|
|
136
|
+
'sec-fetch-user': '?1',
|
|
137
|
+
'upgrade-insecure-requests': '1',
|
|
138
|
+
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 '
|
|
139
|
+
'Safari/537.36',
|
|
140
|
+
}
|
|
133
141
|
# cookies,目前只在移动端使用,因为移动端请求接口须携带,但不会校验cookies的内容。
|
|
134
142
|
APP_COOKIES = None
|
|
135
143
|
|
|
@@ -335,7 +343,7 @@ class JmModuleConfig:
|
|
|
335
343
|
"""
|
|
336
344
|
网页端的headers
|
|
337
345
|
"""
|
|
338
|
-
headers =
|
|
346
|
+
headers = cls.HTML_HEADERS_TEMPLATE.copy()
|
|
339
347
|
headers.update({
|
|
340
348
|
'authority': domain,
|
|
341
349
|
'origin': f'https://{domain}',
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from functools import lru_cache
|
|
2
|
+
|
|
1
3
|
from common import *
|
|
2
4
|
|
|
3
5
|
from .jm_config import *
|
|
@@ -303,7 +305,8 @@ class JmPhotoDetail(DetailEntity, Downloadable):
|
|
|
303
305
|
# 2. 值目前在网页端只在photo页面的图片标签的data-original属性出现
|
|
304
306
|
# 这里的模拟思路是,获取到第一个图片标签的data-original,
|
|
305
307
|
# 取出其query参数 → self.data_original_query_params, 该值未来会传递给 JmImageDetail
|
|
306
|
-
self.data_original_query_params = self.get_data_original_query_params(data_original_0)
|
|
308
|
+
# self.data_original_query_params = self.get_data_original_query_params(data_original_0)
|
|
309
|
+
self.data_original_query_params = None
|
|
307
310
|
|
|
308
311
|
@property
|
|
309
312
|
def is_single_album(self) -> bool:
|
|
@@ -353,7 +356,7 @@ class JmPhotoDetail(DetailEntity, Downloadable):
|
|
|
353
356
|
return self._author.strip()
|
|
354
357
|
|
|
355
358
|
# 使用默认
|
|
356
|
-
return
|
|
359
|
+
return JmModuleConfig.DEFAULT_AUTHOR
|
|
357
360
|
|
|
358
361
|
def create_image_detail(self, index) -> JmImageDetail:
|
|
359
362
|
# 校验参数
|
|
@@ -400,6 +403,7 @@ class JmPhotoDetail(DetailEntity, Downloadable):
|
|
|
400
403
|
def id(self):
|
|
401
404
|
return self.photo_id
|
|
402
405
|
|
|
406
|
+
@lru_cache(None)
|
|
403
407
|
def getindex(self, index) -> JmImageDetail:
|
|
404
408
|
return self.create_image_detail(index)
|
|
405
409
|
|
|
@@ -472,7 +476,7 @@ class JmAlbumDetail(DetailEntity, Downloadable):
|
|
|
472
476
|
if len(self.authors) >= 1:
|
|
473
477
|
return self.authors[0]
|
|
474
478
|
|
|
475
|
-
return
|
|
479
|
+
return JmModuleConfig.DEFAULT_AUTHOR
|
|
476
480
|
|
|
477
481
|
@property
|
|
478
482
|
def id(self):
|
|
@@ -514,6 +518,7 @@ class JmAlbumDetail(DetailEntity, Downloadable):
|
|
|
514
518
|
|
|
515
519
|
return photo
|
|
516
520
|
|
|
521
|
+
@lru_cache(None)
|
|
517
522
|
def getindex(self, item) -> JmPhotoDetail:
|
|
518
523
|
return self.create_photo_detail(item)
|
|
519
524
|
|
|
@@ -608,7 +613,7 @@ class JmSearchPage(JmPageContent):
|
|
|
608
613
|
|
|
609
614
|
@property
|
|
610
615
|
def page_size(self) -> int:
|
|
611
|
-
return
|
|
616
|
+
return JmModuleConfig.PAGE_SIZE_SEARCH
|
|
612
617
|
|
|
613
618
|
# 下面的方法是对单个album的包装
|
|
614
619
|
|
|
@@ -649,7 +654,7 @@ class JmFavoritePage(JmPageContent):
|
|
|
649
654
|
|
|
650
655
|
@property
|
|
651
656
|
def page_size(self) -> int:
|
|
652
|
-
return
|
|
657
|
+
return JmModuleConfig.PAGE_SIZE_FAVORITE
|
|
653
658
|
|
|
654
659
|
def iter_folder_id_name(self) -> Generator[Tuple[str, str], None, None]:
|
|
655
660
|
"""
|
|
@@ -270,15 +270,7 @@ class JmOption:
|
|
|
270
270
|
)
|
|
271
271
|
|
|
272
272
|
if ensure_exists:
|
|
273
|
-
|
|
274
|
-
mkdir_if_not_exists(save_dir)
|
|
275
|
-
except OSError as e:
|
|
276
|
-
if e.errno == 36:
|
|
277
|
-
# 目录名过长
|
|
278
|
-
limit = JmModuleConfig.VAR_FILE_NAME_LENGTH_LIMIT
|
|
279
|
-
jm_log('error', f'目录名过长,无法创建目录,强制缩短到{limit}个字符并重试')
|
|
280
|
-
save_dir = save_dir[0:limit]
|
|
281
|
-
mkdir_if_not_exists(save_dir)
|
|
273
|
+
save_dir = JmcomicText.try_mkdir(save_dir)
|
|
282
274
|
|
|
283
275
|
return save_dir
|
|
284
276
|
|
|
@@ -517,13 +509,21 @@ class JmOption:
|
|
|
517
509
|
|
|
518
510
|
# 下面的方法提供面向对象的调用风格
|
|
519
511
|
|
|
520
|
-
def download_album(self,
|
|
512
|
+
def download_album(self,
|
|
513
|
+
album_id,
|
|
514
|
+
downloader=None,
|
|
515
|
+
callback=None,
|
|
516
|
+
):
|
|
521
517
|
from .api import download_album
|
|
522
|
-
download_album(album_id, self)
|
|
518
|
+
download_album(album_id, self, downloader, callback)
|
|
523
519
|
|
|
524
|
-
def download_photo(self,
|
|
520
|
+
def download_photo(self,
|
|
521
|
+
photo_id,
|
|
522
|
+
downloader=None,
|
|
523
|
+
callback=None
|
|
524
|
+
):
|
|
525
525
|
from .api import download_photo
|
|
526
|
-
download_photo(photo_id, self)
|
|
526
|
+
download_photo(photo_id, self, downloader, callback)
|
|
527
527
|
|
|
528
528
|
# 下面的方法为调用插件提供支持
|
|
529
529
|
|
|
@@ -601,7 +601,7 @@ class FavoriteFolderExportPlugin(JmOptionPlugin):
|
|
|
601
601
|
for page in page_data:
|
|
602
602
|
for aid, extra in page.content:
|
|
603
603
|
data.append(
|
|
604
|
-
(aid, extra.get('author', '') or
|
|
604
|
+
(aid, extra.get('author', '') or JmModuleConfig.DEFAULT_AUTHOR, extra['name'])
|
|
605
605
|
)
|
|
606
606
|
|
|
607
607
|
if len(data) == 0:
|
|
@@ -734,13 +734,17 @@ class Img2pdfPlugin(JmOptionPlugin):
|
|
|
734
734
|
plugin_key = 'img2pdf'
|
|
735
735
|
|
|
736
736
|
def invoke(self,
|
|
737
|
-
photo: JmPhotoDetail,
|
|
737
|
+
photo: JmPhotoDetail = None,
|
|
738
|
+
album: JmAlbumDetail = None,
|
|
738
739
|
downloader=None,
|
|
739
740
|
pdf_dir=None,
|
|
740
741
|
filename_rule='Pid',
|
|
741
742
|
delete_original_file=False,
|
|
742
743
|
**kwargs,
|
|
743
744
|
):
|
|
745
|
+
if photo is None and album is None:
|
|
746
|
+
jm_log('wrong_usage', 'img2pdf必须运行在after_photo或after_album时')
|
|
747
|
+
|
|
744
748
|
try:
|
|
745
749
|
import img2pdf
|
|
746
750
|
except ImportError:
|
|
@@ -749,28 +753,50 @@ class Img2pdfPlugin(JmOptionPlugin):
|
|
|
749
753
|
|
|
750
754
|
self.delete_original_file = delete_original_file
|
|
751
755
|
|
|
752
|
-
# 处理文件夹配置
|
|
753
|
-
filename = DirRule.apply_rule_directly(None, photo, filename_rule)
|
|
754
|
-
photo_dir = self.option.decide_image_save_dir(photo)
|
|
755
|
-
|
|
756
756
|
# 处理生成的pdf文件的路径
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
mkdir_if_not_exists(pdf_dir)
|
|
757
|
+
pdf_dir = self.ensure_make_pdf_dir(pdf_dir)
|
|
758
|
+
|
|
759
|
+
# 处理pdf文件名
|
|
760
|
+
filename = DirRule.apply_rule_directly(album, photo, filename_rule)
|
|
762
761
|
|
|
762
|
+
# pdf路径
|
|
763
763
|
pdf_filepath = os.path.join(pdf_dir, f'{filename}.pdf')
|
|
764
764
|
|
|
765
765
|
# 调用 img2pdf 把 photo_dir 下的所有图片转为pdf
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
f.write(img2pdf.convert(all_img))
|
|
766
|
+
img_path_ls, img_dir_ls = self.write_img_2_pdf(pdf_filepath, album, photo)
|
|
767
|
+
self.log(f'Convert Successfully: JM{album or photo} → {pdf_filepath}')
|
|
769
768
|
|
|
770
769
|
# 执行删除
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
770
|
+
img_path_ls += img_dir_ls
|
|
771
|
+
self.execute_deletion(img_path_ls)
|
|
772
|
+
|
|
773
|
+
def write_img_2_pdf(self, pdf_filepath, album: JmAlbumDetail, photo: JmPhotoDetail):
|
|
774
|
+
import img2pdf
|
|
775
|
+
|
|
776
|
+
if album is None:
|
|
777
|
+
img_dir_ls = [self.option.decide_image_save_dir(photo)]
|
|
778
|
+
else:
|
|
779
|
+
img_dir_ls = [self.option.decide_image_save_dir(photo) for photo in album]
|
|
780
|
+
|
|
781
|
+
img_path_ls = []
|
|
782
|
+
|
|
783
|
+
for img_dir in img_dir_ls:
|
|
784
|
+
imgs = files_of_dir(img_dir)
|
|
785
|
+
if not imgs:
|
|
786
|
+
continue
|
|
787
|
+
img_path_ls += imgs
|
|
788
|
+
|
|
789
|
+
with open(pdf_filepath, 'wb') as f:
|
|
790
|
+
f.write(img2pdf.convert(img_path_ls))
|
|
791
|
+
|
|
792
|
+
return img_path_ls, img_dir_ls
|
|
793
|
+
|
|
794
|
+
@staticmethod
|
|
795
|
+
def ensure_make_pdf_dir(pdf_dir: str):
|
|
796
|
+
pdf_dir = pdf_dir or os.getcwd()
|
|
797
|
+
pdf_dir = fix_filepath(pdf_dir, True)
|
|
798
|
+
mkdir_if_not_exists(pdf_dir)
|
|
799
|
+
return pdf_dir
|
|
774
800
|
|
|
775
801
|
|
|
776
802
|
class JmServerPlugin(JmOptionPlugin):
|
|
@@ -1067,3 +1093,27 @@ class DeleteDuplicatedFilesPlugin(JmOptionPlugin):
|
|
|
1067
1093
|
[f' {path}' for path in paths]
|
|
1068
1094
|
self.log('\n'.join(message))
|
|
1069
1095
|
self.execute_deletion(paths)
|
|
1096
|
+
|
|
1097
|
+
|
|
1098
|
+
class ReplacePathStringPlugin(JmOptionPlugin):
|
|
1099
|
+
plugin_key = 'replace_path_string'
|
|
1100
|
+
|
|
1101
|
+
def invoke(self,
|
|
1102
|
+
replace: Dict[str, str],
|
|
1103
|
+
):
|
|
1104
|
+
if not replace:
|
|
1105
|
+
return
|
|
1106
|
+
|
|
1107
|
+
old_decide_dir = self.option.decide_image_save_dir
|
|
1108
|
+
|
|
1109
|
+
def new_decide_dir(photo, ensure_exists=True) -> str:
|
|
1110
|
+
original_path: str = old_decide_dir(photo, False)
|
|
1111
|
+
for k, v in replace.items():
|
|
1112
|
+
original_path = original_path.replace(k, v)
|
|
1113
|
+
|
|
1114
|
+
if ensure_exists:
|
|
1115
|
+
JmcomicText.try_mkdir(original_path)
|
|
1116
|
+
|
|
1117
|
+
return original_path
|
|
1118
|
+
|
|
1119
|
+
self.option.decide_image_save_dir = new_decide_dir
|
|
@@ -316,6 +316,19 @@ class JmcomicText:
|
|
|
316
316
|
import zhconv
|
|
317
317
|
return zhconv.convert(s, 'zh_cn')
|
|
318
318
|
|
|
319
|
+
@classmethod
|
|
320
|
+
def try_mkdir(cls, save_dir: str):
|
|
321
|
+
try:
|
|
322
|
+
mkdir_if_not_exists(save_dir)
|
|
323
|
+
except OSError as e:
|
|
324
|
+
if e.errno == 36:
|
|
325
|
+
# 目录名过长
|
|
326
|
+
limit = JmModuleConfig.VAR_FILE_NAME_LENGTH_LIMIT
|
|
327
|
+
jm_log('error', f'目录名过长,无法创建目录,强制缩短到{limit}个字符并重试')
|
|
328
|
+
save_dir = save_dir[0:limit]
|
|
329
|
+
mkdir_if_not_exists(save_dir)
|
|
330
|
+
return save_dir
|
|
331
|
+
|
|
319
332
|
|
|
320
333
|
# 支持dsl: #{???} -> os.getenv(???)
|
|
321
334
|
JmcomicText.dsl_replacer.add_dsl_and_replacer(r'\$\{(.*?)\}', JmcomicText.match_os_env)
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|