mirror of
https://github.com/hect0x7/JMComic-Crawler-Python.git
synced 2025-09-26 22:31:30 +08:00
v2.6.0: 实现API域名可自动更新; 压缩插件支持zip加密、7z加密; 增强文件路径的插件配置,支持配置项dir_rule。(#437) (#438)
Some checks failed
Auto Release & Publish / release (push) Has been cancelled
Some checks failed
Auto Release & Publish / release (push) Has been cancelled
This commit is contained in:
parent
905f72c690
commit
1679f009b9
@ -157,10 +157,11 @@ jmcomic 123
|
||||
- `下载特定后缀图片插件`
|
||||
- `发送QQ邮件插件`
|
||||
- `自动使用浏览器cookies插件`
|
||||
- `jpg图片合成为一个pdf插件`
|
||||
- `导出收藏夹为csv文件插件`
|
||||
- `合并所有图片为pdf文件插件`
|
||||
- `合并所有图片为长图插件`
|
||||
- `合并所有图片为长图png插件`
|
||||
- `重复文件检测删除插件`
|
||||
- `网页观看本地章节插件`
|
||||
|
||||
## 使用小说明
|
||||
|
||||
|
@ -264,19 +264,4 @@ plugins:
|
||||
img_dir: D:/pdf/ # 长图存放文件夹
|
||||
filename_rule: Aname # 长图命名规则,同上
|
||||
|
||||
# 请注意⚠
|
||||
# 下方的j2p插件的功能不如img2pdf插件,不建议使用。
|
||||
# 如有图片转pdf的需求,直接使用img2pdf即可,下面的内容请忽略。
|
||||
|
||||
- plugin: j2p # 图片合并插件,可以将下载下来的jpg图片合成为一个pdf插件
|
||||
# 请注意⚠ 该插件的使用前提是,下载下来的图片是jpg图片
|
||||
# 因此,使用该插件前,需要有如下配置:(下载图片格式转为jpg,上文有解释过此配置)
|
||||
# download:
|
||||
# image:
|
||||
# suffix: .jpg
|
||||
kwargs:
|
||||
pdf_dir: D:/pdf/ # pdf存放文件夹
|
||||
filename_rule: Pid # pdf命名规则
|
||||
quality: 100 # pdf质量,0 - 100
|
||||
|
||||
```
|
||||
|
@ -2,7 +2,7 @@
|
||||
# 被依赖方 <--- 使用方
|
||||
# config <--- entity <--- toolkit <--- client <--- option <--- downloader
|
||||
|
||||
__version__ = '2.5.39'
|
||||
__version__ = '2.6.0'
|
||||
|
||||
from .api import *
|
||||
from .jm_plugin import *
|
||||
|
@ -1005,10 +1005,51 @@ class JmApiClient(AbstractJmClient):
|
||||
ExceptionTool.raises_resp(f'响应无数据!request_url=[{url}]', resp)
|
||||
|
||||
def after_init(self):
|
||||
# 自动更新禁漫API域名
|
||||
if JmModuleConfig.FLAG_API_CLIENT_AUTO_UPDATE_DOMAIN:
|
||||
self.update_api_domain()
|
||||
|
||||
# 保证拥有cookies,因为移动端要求必须携带cookies,否则会直接跳转同一本子【禁漫娘】
|
||||
if JmModuleConfig.FLAG_API_CLIENT_REQUIRE_COOKIES:
|
||||
self.ensure_have_cookies()
|
||||
|
||||
client_update_domain_lock = Lock()
|
||||
|
||||
def update_api_domain(self):
|
||||
if True is JmModuleConfig.FLAG_API_CLIENT_AUTO_UPDATE_DOMAIN_DONE:
|
||||
return
|
||||
|
||||
with self.client_update_domain_lock:
|
||||
if True is JmModuleConfig.FLAG_API_CLIENT_AUTO_UPDATE_DOMAIN_DONE:
|
||||
return
|
||||
try:
|
||||
# 获取域名列表
|
||||
resp = self.postman.get(JmModuleConfig.API_URL_DOMAIN_SERVER)
|
||||
res_json = JmCryptoTool.decode_resp_data(resp.text, '', JmMagicConstants.API_DOMAIN_SERVER_SECRET)
|
||||
res_data = json_loads(res_json)
|
||||
|
||||
# 检查返回值
|
||||
if not res_data.get('Server', None):
|
||||
jm_log('api.update_domain.empty',
|
||||
f'获取禁漫最新API域名失败, 返回值: {res_json}')
|
||||
return
|
||||
new_server_list: list[str] = res_data['Server']
|
||||
old_server_list = JmModuleConfig.DOMAIN_API_LIST
|
||||
jm_log('api.update_domain.success',
|
||||
f'获取到最新的API域名,替换jmcomic内置域名:(new){new_server_list} ---→ (old){old_server_list}'
|
||||
)
|
||||
# 更新域名
|
||||
if self.domain_list is old_server_list:
|
||||
self.domain_list = new_server_list
|
||||
JmModuleConfig.DOMAIN_API_LIST = new_server_list
|
||||
except Exception as e:
|
||||
jm_log('api.update_domain.error',
|
||||
f'自动更新API域名失败,仍使用jmcomic内置域名。'
|
||||
f'可通过代码[JmModuleConfig.FLAG_API_CLIENT_AUTO_UPDATE_DOMAIN=False]关闭自动更新API域名. 异常: {e}'
|
||||
)
|
||||
finally:
|
||||
JmModuleConfig.FLAG_API_CLIENT_AUTO_UPDATE_DOMAIN_DONE = True
|
||||
|
||||
client_init_cookies_lock = Lock()
|
||||
|
||||
def ensure_have_cookies(self):
|
||||
|
@ -76,7 +76,8 @@ class JmMagicConstants:
|
||||
APP_TOKEN_SECRET = '18comicAPP'
|
||||
APP_TOKEN_SECRET_2 = '18comicAPPContent'
|
||||
APP_DATA_SECRET = '185Hcomic3PAPP7R'
|
||||
APP_VERSION = '1.7.9'
|
||||
API_DOMAIN_SERVER_SECRET = 'diosfjckwpqpdfjkvnqQjsik'
|
||||
APP_VERSION = '1.8.0'
|
||||
|
||||
|
||||
# 模块级别共用配置
|
||||
@ -128,11 +129,15 @@ class JmModuleConfig:
|
||||
# 移动端API域名
|
||||
DOMAIN_API_LIST = shuffled('''
|
||||
www.cdnmhwscc.vip
|
||||
www.cdnblackmyth.club
|
||||
www.cdnmhws.cc
|
||||
www.cdnplaystation6.club
|
||||
www.cdnplaystation6.org
|
||||
www.cdnuc.vip
|
||||
www.cdn-mspjmapiproxy.xyz
|
||||
''')
|
||||
|
||||
# 获取最新移动端API域名的地址
|
||||
API_URL_DOMAIN_SERVER = f'{PROT}jmappc01-1308024008.cos.ap-guangzhou.myqcloud.com/server-2024.txt'
|
||||
|
||||
APP_HEADERS_TEMPLATE = {
|
||||
'Accept-Encoding': 'gzip, deflate',
|
||||
'user-agent': 'Mozilla/5.0 (Linux; Android 9; V1938CT Build/PQ3A.190705.11211812; wv) AppleWebKit/537.36 (KHTML, '
|
||||
@ -200,6 +205,9 @@ class JmModuleConfig:
|
||||
FLAG_USE_FIX_TIMESTAMP = True
|
||||
# 移动端Client初始化cookies
|
||||
FLAG_API_CLIENT_REQUIRE_COOKIES = True
|
||||
# 自动更新禁漫API域名
|
||||
FLAG_API_CLIENT_AUTO_UPDATE_DOMAIN = True
|
||||
FLAG_API_CLIENT_AUTO_UPDATE_DOMAIN_DONE = None
|
||||
# log开关标记
|
||||
FLAG_ENABLE_JM_LOG = True
|
||||
# log时解码url
|
||||
@ -380,7 +388,7 @@ class JmModuleConfig:
|
||||
|
||||
@classmethod
|
||||
def new_postman(cls, session=False, **kwargs):
|
||||
kwargs.setdefault('impersonate', 'chrome110')
|
||||
kwargs.setdefault('impersonate', 'chrome')
|
||||
kwargs.setdefault('headers', JmModuleConfig.new_html_headers())
|
||||
kwargs.setdefault('proxies', JmModuleConfig.DEFAULT_PROXIES)
|
||||
|
||||
@ -416,7 +424,7 @@ class JmModuleConfig:
|
||||
'postman': {
|
||||
'type': 'curl_cffi',
|
||||
'meta_data': {
|
||||
'impersonate': 'chrome110',
|
||||
'impersonate': 'chrome',
|
||||
'headers': None,
|
||||
'proxies': None,
|
||||
}
|
||||
|
@ -70,12 +70,12 @@ class DirRule:
|
||||
album: JmAlbumDetail,
|
||||
photo: JmPhotoDetail,
|
||||
) -> str:
|
||||
return self._build_path_from_rules(album, photo)
|
||||
return self.apply_rule_to_path(album, photo)
|
||||
|
||||
def decide_album_root_dir(self, album: JmAlbumDetail) -> str:
|
||||
return self._build_path_from_rules(album, None, True)
|
||||
return self.apply_rule_to_path(album, None, True)
|
||||
|
||||
def _build_path_from_rules(self, album, photo, only_album_rules=False) -> str:
|
||||
def apply_rule_to_path(self, album, photo, only_album_rules=False) -> str:
|
||||
path_ls = []
|
||||
for rule, parser in self.parser_list:
|
||||
if only_album_rules and not (rule == self.RULE_BASE_DIR or rule.startswith('A')):
|
||||
@ -92,7 +92,7 @@ class DirRule:
|
||||
|
||||
path_ls.append(path)
|
||||
|
||||
return fix_filepath('/'.join(path_ls), is_dir=True)
|
||||
return fix_filepath('/'.join(path_ls))
|
||||
|
||||
def get_rule_parser_list(self, rule_dsl: str):
|
||||
"""
|
||||
@ -103,7 +103,6 @@ class DirRule:
|
||||
parser_list: list = []
|
||||
|
||||
for rule in rule_list:
|
||||
rule = rule.strip()
|
||||
if rule == self.RULE_BASE_DIR:
|
||||
parser_list.append((rule, self.parse_bd_rule))
|
||||
continue
|
||||
@ -135,17 +134,21 @@ class DirRule:
|
||||
return str(DetailEntity.get_dirname(detail, rule[1:]))
|
||||
|
||||
# noinspection PyMethodMayBeStatic
|
||||
def split_rule_dsl(self, rule_dsl: str):
|
||||
if rule_dsl == self.RULE_BASE_DIR:
|
||||
return [rule_dsl]
|
||||
|
||||
def split_rule_dsl(self, rule_dsl: str) -> list[str]:
|
||||
if '/' in rule_dsl:
|
||||
return rule_dsl.split('/')
|
||||
rule_list = rule_dsl.split('/')
|
||||
elif '_' in rule_dsl:
|
||||
rule_list = rule_dsl.split('_')
|
||||
else:
|
||||
rule_list = [rule_dsl]
|
||||
|
||||
if '_' in rule_dsl:
|
||||
return rule_dsl.split('_')
|
||||
for i, e in enumerate(rule_list):
|
||||
rule_list[i] = e.strip()
|
||||
|
||||
ExceptionTool.raises(f'不支持的rule配置: "{rule_dsl}"')
|
||||
if rule_list[0] != self.RULE_BASE_DIR:
|
||||
rule_list.insert(0, self.RULE_BASE_DIR)
|
||||
|
||||
return rule_list
|
||||
|
||||
@classmethod
|
||||
def get_rule_parser(cls, rule: str):
|
||||
@ -158,7 +161,7 @@ class DirRule:
|
||||
ExceptionTool.raises(f'不支持的rule配置: "{rule}"')
|
||||
|
||||
@classmethod
|
||||
def apply_rule_directly(cls, album, photo, rule: str) -> str:
|
||||
def apply_rule_to_filename(cls, album, photo, rule: str) -> str:
|
||||
if album is None:
|
||||
album = photo.from_album
|
||||
# noinspection PyArgumentList
|
||||
|
@ -1,7 +1,6 @@
|
||||
"""
|
||||
该文件存放的是option插件
|
||||
"""
|
||||
import os.path
|
||||
|
||||
from .jm_option import *
|
||||
|
||||
@ -57,11 +56,12 @@ class JmOptionPlugin:
|
||||
|
||||
raise PluginValidationException(self, msg)
|
||||
|
||||
def warning_lib_not_install(self, lib: str):
|
||||
def warning_lib_not_install(self, lib: str, throw=False):
|
||||
msg = (f'插件`{self.plugin_key}`依赖库: {lib},请先安装{lib}再使用。'
|
||||
f'安装命令: [pip install {lib}]')
|
||||
import warnings
|
||||
warnings.warn(msg)
|
||||
self.require_param(throw, msg)
|
||||
|
||||
def execute_deletion(self, paths: List[str]):
|
||||
"""
|
||||
@ -76,6 +76,9 @@ class JmOptionPlugin:
|
||||
continue
|
||||
|
||||
if os.path.isdir(p):
|
||||
if os.listdir(p):
|
||||
self.log(f'文件夹中存在非本次下载的文件,请手动删除文件夹内的文件: {p}', 'remove.ignore')
|
||||
continue
|
||||
os.rmdir(p)
|
||||
self.log(f'删除文件夹: {p}', 'remove')
|
||||
else:
|
||||
@ -104,6 +107,33 @@ class JmOptionPlugin:
|
||||
def wait_until_finish(self):
|
||||
pass
|
||||
|
||||
# noinspection PyMethodMayBeStatic
|
||||
def decide_filepath(self,
|
||||
album: Optional[JmAlbumDetail],
|
||||
photo: Optional[JmPhotoDetail],
|
||||
filename_rule: str, suffix: str, base_dir: Optional[str],
|
||||
dir_rule_dict: Optional[dict]
|
||||
):
|
||||
"""
|
||||
根据规则计算一个文件的全路径
|
||||
|
||||
参数 dir_rule_dict 优先级最高,
|
||||
如果 dir_rule_dict 不为空,优先用 dir_rule_dict
|
||||
否则使用 base_dir + filename_rule + suffix
|
||||
"""
|
||||
filepath: str
|
||||
base_dir: str
|
||||
if dir_rule_dict is not None:
|
||||
dir_rule = DirRule(**dir_rule_dict)
|
||||
filepath = dir_rule.apply_rule_to_path(album, photo)
|
||||
base_dir = os.path.dirname(filepath)
|
||||
else:
|
||||
base_dir = base_dir or os.getcwd()
|
||||
filepath = os.path.join(base_dir, DirRule.apply_rule_to_filename(album, photo, filename_rule) + fix_suffix(suffix))
|
||||
|
||||
mkdir_if_not_exists(base_dir)
|
||||
return filepath
|
||||
|
||||
|
||||
class JmLoginPlugin(JmOptionPlugin):
|
||||
"""
|
||||
@ -274,6 +304,11 @@ class FindUpdatePlugin(JmOptionPlugin):
|
||||
|
||||
|
||||
class ZipPlugin(JmOptionPlugin):
|
||||
"""
|
||||
感谢zip加密功能的贡献者:
|
||||
- AXIS5 a.k.a AXIS5Hacker (https://github.com/hect0x7/JMComic-Crawler-Python/pull/375)
|
||||
"""
|
||||
|
||||
plugin_key = 'zip'
|
||||
|
||||
# noinspection PyAttributeOutsideInit
|
||||
@ -285,7 +320,9 @@ class ZipPlugin(JmOptionPlugin):
|
||||
level='photo',
|
||||
filename_rule='Ptitle',
|
||||
suffix='zip',
|
||||
zip_dir='./'
|
||||
zip_dir='./',
|
||||
dir_rule=None,
|
||||
encrypt=None,
|
||||
) -> None:
|
||||
|
||||
from .jm_downloader import JmDownloader
|
||||
@ -302,19 +339,20 @@ class ZipPlugin(JmOptionPlugin):
|
||||
photo_dict = self.get_downloaded_photo(downloader, album, photo)
|
||||
|
||||
if level == 'album':
|
||||
zip_path = self.get_zip_path(album, None, filename_rule, suffix, zip_dir)
|
||||
self.zip_album(album, photo_dict, zip_path, path_to_delete)
|
||||
zip_path = self.decide_filepath(album, None, filename_rule, suffix, zip_dir, dir_rule)
|
||||
self.zip_album(album, photo_dict, zip_path, path_to_delete, encrypt)
|
||||
|
||||
elif level == 'photo':
|
||||
for photo, image_list in photo_dict.items():
|
||||
zip_path = self.get_zip_path(photo.from_album, photo, filename_rule, suffix, zip_dir)
|
||||
self.zip_photo(photo, image_list, zip_path, path_to_delete)
|
||||
zip_path = self.decide_filepath(photo.from_album, photo, filename_rule, suffix, zip_dir, dir_rule)
|
||||
self.zip_photo(photo, image_list, zip_path, path_to_delete, encrypt)
|
||||
|
||||
else:
|
||||
ExceptionTool.raises(f'Not Implemented Zip Level: {level}')
|
||||
|
||||
self.after_zip(path_to_delete)
|
||||
|
||||
# noinspection PyMethodMayBeStatic
|
||||
def get_downloaded_photo(self, downloader, album, photo):
|
||||
return (
|
||||
downloader.download_success_dict[album]
|
||||
@ -322,7 +360,7 @@ class ZipPlugin(JmOptionPlugin):
|
||||
else downloader.download_success_dict[photo.from_album] # after_photo
|
||||
)
|
||||
|
||||
def zip_photo(self, photo, image_list: list, zip_path: str, path_to_delete):
|
||||
def zip_photo(self, photo, image_list: list, zip_path: str, path_to_delete, encrypt_dict):
|
||||
"""
|
||||
压缩photo文件夹
|
||||
"""
|
||||
@ -330,8 +368,11 @@ class ZipPlugin(JmOptionPlugin):
|
||||
if len(image_list) == 0 \
|
||||
else os.path.dirname(image_list[0][0])
|
||||
|
||||
from common import backup_dir_to_zip
|
||||
backup_dir_to_zip(photo_dir, zip_path)
|
||||
with self.open_zip_file(zip_path, encrypt_dict) as f:
|
||||
for file in files_of_dir(photo_dir):
|
||||
abspath = os.path.join(photo_dir, file)
|
||||
relpath = os.path.relpath(abspath, photo_dir)
|
||||
f.write(abspath, relpath)
|
||||
|
||||
self.log(f'压缩章节[{photo.photo_id}]成功 → {zip_path}', 'finish')
|
||||
path_to_delete.append(self.unified_path(photo_dir))
|
||||
@ -340,14 +381,13 @@ class ZipPlugin(JmOptionPlugin):
|
||||
def unified_path(f):
|
||||
return fix_filepath(f, os.path.isdir(f))
|
||||
|
||||
def zip_album(self, album, photo_dict: dict, zip_path, path_to_delete):
|
||||
def zip_album(self, album, photo_dict: dict, zip_path, path_to_delete, encrypt_dict):
|
||||
"""
|
||||
压缩album文件夹
|
||||
"""
|
||||
|
||||
album_dir = self.option.dir_rule.decide_album_root_dir(album)
|
||||
import zipfile
|
||||
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as f:
|
||||
with self.open_zip_file(zip_path, encrypt_dict) as f:
|
||||
for photo in photo_dict.keys():
|
||||
# 定位到章节所在文件夹
|
||||
photo_dir = self.unified_path(self.option.decide_image_save_dir(photo))
|
||||
@ -372,16 +412,65 @@ class ZipPlugin(JmOptionPlugin):
|
||||
self.execute_deletion(dirs)
|
||||
|
||||
# noinspection PyMethodMayBeStatic
|
||||
def get_zip_path(self, album, photo, filename_rule, suffix, zip_dir):
|
||||
@classmethod
|
||||
def generate_random_str(cls, random_length) -> str:
|
||||
"""
|
||||
计算zip文件的路径
|
||||
自动生成随机字符密码,长度由randomlength指定
|
||||
"""
|
||||
filename = DirRule.apply_rule_directly(album, photo, filename_rule)
|
||||
from os.path import join
|
||||
return join(
|
||||
zip_dir,
|
||||
filename + fix_suffix(suffix),
|
||||
)
|
||||
import random
|
||||
|
||||
random_str = ''
|
||||
base_str = r'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='
|
||||
base_length = len(base_str) - 1
|
||||
for _ in range(random_length):
|
||||
random_str += base_str[random.randint(0, base_length)]
|
||||
return random_str
|
||||
|
||||
def open_zip_file(self, zip_path: str, encrypt_dict: Optional[dict]):
|
||||
if encrypt_dict is None:
|
||||
import zipfile
|
||||
return zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED)
|
||||
|
||||
password, is_random = self.decide_password(encrypt_dict, zip_path)
|
||||
if encrypt_dict.get('impl', '') == '7z':
|
||||
try:
|
||||
# noinspection PyUnresolvedReferences
|
||||
import py7zr
|
||||
except ImportError:
|
||||
self.warning_lib_not_install('py7zr', True)
|
||||
|
||||
# noinspection PyUnboundLocalVariable
|
||||
filters = [{'id': py7zr.FILTER_COPY}]
|
||||
return py7zr.SevenZipFile(zip_path, mode='w', password=password, filters=filters, header_encryption=True)
|
||||
else:
|
||||
try:
|
||||
# noinspection PyUnresolvedReferences
|
||||
import pyzipper
|
||||
except ImportError:
|
||||
self.warning_lib_not_install('pyzipper', True)
|
||||
|
||||
# noinspection PyUnboundLocalVariable
|
||||
aes_zip_file = pyzipper.AESZipFile(zip_path, "w", pyzipper.ZIP_DEFLATED)
|
||||
aes_zip_file.setencryption(pyzipper.WZ_AES, nbits=128)
|
||||
password_bytes = str.encode(password)
|
||||
aes_zip_file.setpassword(password_bytes)
|
||||
if is_random:
|
||||
aes_zip_file.comment = password_bytes
|
||||
return aes_zip_file
|
||||
|
||||
def decide_password(self, encrypt_dict: dict, zip_path: str):
|
||||
encrypt_type = encrypt_dict.get('type', '')
|
||||
is_random = False
|
||||
|
||||
if encrypt_type == 'random':
|
||||
is_random = True
|
||||
password = self.generate_random_str(48)
|
||||
self.log(f'生成随机密码: [{password}] → [{zip_path}]', 'encrypt')
|
||||
else:
|
||||
password = str(encrypt_dict['password'])
|
||||
self.log(f'使用指定密码: [{password}] → [{zip_path}]', 'encrypt')
|
||||
|
||||
return password, is_random
|
||||
|
||||
|
||||
class ClientProxyPlugin(JmOptionPlugin):
|
||||
@ -649,95 +738,6 @@ class FavoriteFolderExportPlugin(JmOptionPlugin):
|
||||
self.execute_multi_line_cmd(cmd_list)
|
||||
|
||||
|
||||
class ConvertJpgToPdfPlugin(JmOptionPlugin):
|
||||
plugin_key = 'j2p'
|
||||
|
||||
def check_image_suffix_is_valid(self, std_suffix):
|
||||
"""
|
||||
检查option配置的图片后缀转换,目前限制使用Magick时只能搭配jpg
|
||||
暂不探究Magick是否支持更多图片格式
|
||||
"""
|
||||
cur_suffix: Optional[str] = self.option.download.image.suffix
|
||||
|
||||
ExceptionTool.require_true(
|
||||
cur_suffix is not None and cur_suffix.endswith(std_suffix),
|
||||
'请把图片的后缀转换配置为jpg,不然无法使用Magick!'
|
||||
f'(当前配置是[{cur_suffix}])\n'
|
||||
f'配置模板如下: \n'
|
||||
f'```\n'
|
||||
f'download:\n'
|
||||
f' image:\n'
|
||||
f' suffix: {std_suffix} # 当前配置是{cur_suffix}\n'
|
||||
f'```'
|
||||
)
|
||||
|
||||
def invoke(self,
|
||||
photo: JmPhotoDetail,
|
||||
downloader=None,
|
||||
pdf_dir=None,
|
||||
filename_rule='Pid',
|
||||
quality=100,
|
||||
delete_original_file=False,
|
||||
override_cmd=None,
|
||||
override_jpg=None,
|
||||
**kwargs,
|
||||
):
|
||||
self.delete_original_file = delete_original_file
|
||||
|
||||
# 检查图片后缀配置
|
||||
suffix = override_jpg or '.jpg'
|
||||
self.check_image_suffix_is_valid(suffix)
|
||||
|
||||
# 处理文件夹配置
|
||||
filename = DirRule.apply_rule_directly(None, photo, filename_rule)
|
||||
photo_dir = self.option.decide_image_save_dir(photo)
|
||||
|
||||
# 处理生成的pdf文件的路径
|
||||
if pdf_dir is None:
|
||||
pdf_dir = photo_dir
|
||||
else:
|
||||
pdf_dir = fix_filepath(pdf_dir, True)
|
||||
mkdir_if_not_exists(pdf_dir)
|
||||
|
||||
pdf_filepath = os.path.join(pdf_dir, f'{filename}.pdf')
|
||||
|
||||
# 生成命令
|
||||
def generate_cmd():
|
||||
return (
|
||||
override_cmd or
|
||||
'magick convert -quality {quality} "{photo_dir}*{suffix}" "{pdf_filepath}"'
|
||||
).format(
|
||||
quality=quality,
|
||||
photo_dir=photo_dir,
|
||||
suffix=suffix,
|
||||
pdf_filepath=pdf_filepath,
|
||||
)
|
||||
|
||||
cmd = generate_cmd()
|
||||
self.log(f'Execute Command: [{cmd}]')
|
||||
code = self.execute_cmd(cmd)
|
||||
|
||||
ExceptionTool.require_true(
|
||||
code == 0,
|
||||
'jpg图片合并为pdf失败!'
|
||||
'请确认你是否安装了magick,安装网站: [https://www.imagemagick.org/]',
|
||||
)
|
||||
|
||||
self.log(f'Convert Successfully: JM{photo.id} → {pdf_filepath}')
|
||||
|
||||
if downloader is not None:
|
||||
from .jm_downloader import JmDownloader
|
||||
downloader: JmDownloader
|
||||
|
||||
paths = [
|
||||
path
|
||||
for path, image in downloader.download_success_dict[photo.from_album][photo]
|
||||
]
|
||||
|
||||
paths.append(self.option.decide_image_save_dir(photo, ensure_exists=False))
|
||||
self.execute_deletion(paths)
|
||||
|
||||
|
||||
class Img2pdfPlugin(JmOptionPlugin):
|
||||
plugin_key = 'img2pdf'
|
||||
|
||||
@ -747,6 +747,7 @@ class Img2pdfPlugin(JmOptionPlugin):
|
||||
downloader=None,
|
||||
pdf_dir=None,
|
||||
filename_rule='Pid',
|
||||
dir_rule=None,
|
||||
delete_original_file=False,
|
||||
**kwargs,
|
||||
):
|
||||
@ -762,13 +763,7 @@ class Img2pdfPlugin(JmOptionPlugin):
|
||||
self.delete_original_file = delete_original_file
|
||||
|
||||
# 处理生成的pdf文件的路径
|
||||
pdf_dir = self.ensure_make_pdf_dir(pdf_dir)
|
||||
|
||||
# 处理pdf文件名
|
||||
filename = DirRule.apply_rule_directly(album, photo, filename_rule)
|
||||
|
||||
# pdf路径
|
||||
pdf_filepath = os.path.join(pdf_dir, f'{filename}.pdf')
|
||||
pdf_filepath = self.decide_filepath(album, photo, filename_rule, 'pdf', pdf_dir, dir_rule)
|
||||
|
||||
# 调用 img2pdf 把 photo_dir 下的所有图片转为pdf
|
||||
img_path_ls, img_dir_ls = self.write_img_2_pdf(pdf_filepath, album, photo)
|
||||
@ -794,18 +789,14 @@ class Img2pdfPlugin(JmOptionPlugin):
|
||||
continue
|
||||
img_path_ls += imgs
|
||||
|
||||
if len(img_path_ls) == 0:
|
||||
self.log(f'所有文件夹都不存在图片,无法生成pdf:{img_dir_ls}', 'error')
|
||||
|
||||
with open(pdf_filepath, 'wb') as f:
|
||||
f.write(img2pdf.convert(img_path_ls))
|
||||
|
||||
return img_path_ls, img_dir_ls
|
||||
|
||||
@staticmethod
|
||||
def ensure_make_pdf_dir(pdf_dir: str):
|
||||
pdf_dir = pdf_dir or os.getcwd()
|
||||
pdf_dir = fix_filepath(pdf_dir, True)
|
||||
mkdir_if_not_exists(pdf_dir)
|
||||
return pdf_dir
|
||||
|
||||
|
||||
class LongImgPlugin(JmOptionPlugin):
|
||||
plugin_key = 'long_img'
|
||||
@ -817,6 +808,7 @@ class LongImgPlugin(JmOptionPlugin):
|
||||
img_dir=None,
|
||||
filename_rule='Pid',
|
||||
delete_original_file=False,
|
||||
dir_rule=None,
|
||||
**kwargs,
|
||||
):
|
||||
if photo is None and album is None:
|
||||
@ -830,14 +822,8 @@ class LongImgPlugin(JmOptionPlugin):
|
||||
|
||||
self.delete_original_file = delete_original_file
|
||||
|
||||
# 处理文件夹配置
|
||||
img_dir = self.get_img_dir(img_dir)
|
||||
|
||||
# 处理生成的长图文件的路径
|
||||
filename = DirRule.apply_rule_directly(album, photo, filename_rule)
|
||||
|
||||
# 长图路径
|
||||
long_img_path = os.path.join(img_dir, f'{filename}.png')
|
||||
long_img_path = self.decide_filepath(album, photo, filename_rule, 'png', img_dir, dir_rule)
|
||||
|
||||
# 调用 PIL 把 photo_dir 下的所有图片合并为长图
|
||||
img_path_ls = self.write_img_2_long_img(long_img_path, album, photo)
|
||||
@ -856,7 +842,7 @@ class LongImgPlugin(JmOptionPlugin):
|
||||
img_dir_items = [self.option.decide_image_save_dir(photo) for photo in album]
|
||||
|
||||
img_paths = itertools.chain(*map(files_of_dir, img_dir_items))
|
||||
img_paths = filter(lambda x: not x.startswith('.'), img_paths) # 过滤系统文件
|
||||
img_paths = list(filter(lambda x: not x.startswith('.'), img_paths)) # 过滤系统文件
|
||||
|
||||
images = self.open_images(img_paths)
|
||||
|
||||
@ -895,12 +881,6 @@ class LongImgPlugin(JmOptionPlugin):
|
||||
self.log(f"Failed to open image {img_path}: {e}", 'error')
|
||||
return images
|
||||
|
||||
@staticmethod
|
||||
def get_img_dir(img_dir: Optional[str]) -> str:
|
||||
img_dir = fix_filepath(img_dir or os.getcwd())
|
||||
mkdir_if_not_exists(img_dir)
|
||||
return img_dir
|
||||
|
||||
|
||||
class JmServerPlugin(JmOptionPlugin):
|
||||
plugin_key = 'jm_server'
|
||||
@ -960,6 +940,7 @@ class JmServerPlugin(JmOptionPlugin):
|
||||
# 服务器的代码位于一个独立库:plugin_jm_server,需要独立安装
|
||||
# 源代码仓库:https://github.com/hect0x7/plugin-jm-server
|
||||
try:
|
||||
# noinspection PyUnresolvedReferences
|
||||
import plugin_jm_server
|
||||
self.log(f'当前使用plugin_jm_server版本: {plugin_jm_server.__version__}')
|
||||
except ImportError:
|
||||
|
Loading…
Reference in New Issue
Block a user