Skip to content

Commit 8a8944b

Browse files
committed
zip加密
1 parent a975fe8 commit 8a8944b

1 file changed

Lines changed: 81 additions & 9 deletions

File tree

src/jmcomic/jm_plugin.py

Lines changed: 81 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,12 @@ def require_param(self, case: Any, msg: str):
5656

5757
raise PluginValidationException(self, msg)
5858

59-
def warning_lib_not_install(self, lib: str):
59+
def warning_lib_not_install(self, lib: str, throw=False):
6060
msg = (f'插件`{self.plugin_key}`依赖库: {lib},请先安装{lib}再使用。'
6161
f'安装命令: [pip install {lib}]')
6262
import warnings
6363
warnings.warn(msg)
64+
self.require_param(throw, msg)
6465

6566
def execute_deletion(self, paths: List[str]):
6667
"""
@@ -303,6 +304,11 @@ def do_filter(self, detail):
303304

304305

305306
class ZipPlugin(JmOptionPlugin):
307+
"""
308+
感谢zip加密功能的贡献者:
309+
- AXIS5 a.k.a AXIS5Hacker (https://github.com/hect0x7/JMComic-Crawler-Python/pull/375)
310+
"""
311+
306312
plugin_key = 'zip'
307313

308314
# noinspection PyAttributeOutsideInit
@@ -316,6 +322,7 @@ def invoke(self,
316322
suffix='zip',
317323
zip_dir='./',
318324
dir_rule=None,
325+
encrypt=None,
319326
) -> None:
320327

321328
from .jm_downloader import JmDownloader
@@ -333,35 +340,39 @@ def invoke(self,
333340

334341
if level == 'album':
335342
zip_path = self.decide_filepath(album, None, filename_rule, suffix, zip_dir, dir_rule)
336-
self.zip_album(album, photo_dict, zip_path, path_to_delete)
343+
self.zip_album(album, photo_dict, zip_path, path_to_delete, encrypt)
337344

338345
elif level == 'photo':
339346
for photo, image_list in photo_dict.items():
340347
zip_path = self.decide_filepath(photo.from_album, photo, filename_rule, suffix, zip_dir, dir_rule)
341-
self.zip_photo(photo, image_list, zip_path, path_to_delete)
348+
self.zip_photo(photo, image_list, zip_path, path_to_delete, encrypt)
342349

343350
else:
344351
ExceptionTool.raises(f'Not Implemented Zip Level: {level}')
345352

346353
self.after_zip(path_to_delete)
347354

355+
# noinspection PyMethodMayBeStatic
348356
def get_downloaded_photo(self, downloader, album, photo):
349357
return (
350358
downloader.download_success_dict[album]
351359
if album is not None # after_album
352360
else downloader.download_success_dict[photo.from_album] # after_photo
353361
)
354362

355-
def zip_photo(self, photo, image_list: list, zip_path: str, path_to_delete):
363+
def zip_photo(self, photo, image_list: list, zip_path: str, path_to_delete, encrypt_dict):
356364
"""
357365
压缩photo文件夹
358366
"""
359367
photo_dir = self.option.decide_image_save_dir(photo) \
360368
if len(image_list) == 0 \
361369
else os.path.dirname(image_list[0][0])
362370

363-
from common import backup_dir_to_zip
364-
backup_dir_to_zip(photo_dir, zip_path)
371+
with self.open_zip_file(zip_path, encrypt_dict) as f:
372+
for file in files_of_dir(photo_dir):
373+
abspath = os.path.join(photo_dir, file)
374+
relpath = os.path.relpath(abspath, photo_dir)
375+
f.write(abspath, relpath)
365376

366377
self.log(f'压缩章节[{photo.photo_id}]成功 → {zip_path}', 'finish')
367378
path_to_delete.append(self.unified_path(photo_dir))
@@ -370,14 +381,13 @@ def zip_photo(self, photo, image_list: list, zip_path: str, path_to_delete):
370381
def unified_path(f):
371382
return fix_filepath(f, os.path.isdir(f))
372383

373-
def zip_album(self, album, photo_dict: dict, zip_path, path_to_delete):
384+
def zip_album(self, album, photo_dict: dict, zip_path, path_to_delete, encrypt_dict):
374385
"""
375386
压缩album文件夹
376387
"""
377388

378389
album_dir = self.option.dir_rule.decide_album_root_dir(album)
379-
import zipfile
380-
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as f:
390+
with self.open_zip_file(zip_path, encrypt_dict) as f:
381391
for photo in photo_dict.keys():
382392
# 定位到章节所在文件夹
383393
photo_dir = self.unified_path(self.option.decide_image_save_dir(photo))
@@ -401,6 +411,67 @@ def after_zip(self, path_to_delete: List[str]):
401411
self.execute_deletion(image_paths)
402412
self.execute_deletion(dirs)
403413

414+
# noinspection PyMethodMayBeStatic
415+
@classmethod
416+
def generate_random_str(cls, random_length) -> str:
417+
"""
418+
自动生成随机字符密码,长度由randomlength指定
419+
"""
420+
import random
421+
422+
random_str = ''
423+
base_str = r'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='
424+
base_length = len(base_str) - 1
425+
for _ in range(random_length):
426+
random_str += base_str[random.randint(0, base_length)]
427+
return random_str
428+
429+
def open_zip_file(self, zip_path: str, encrypt_dict: Optional[dict]):
430+
if encrypt_dict is None:
431+
import zipfile
432+
return zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED)
433+
434+
password, is_random = self.decide_password(encrypt_dict, zip_path)
435+
if encrypt_dict.get('impl', '') == '7z':
436+
try:
437+
# noinspection PyUnresolvedReferences
438+
import py7zr
439+
except ImportError:
440+
self.warning_lib_not_install('py7zr')
441+
442+
# noinspection PyUnboundLocalVariable
443+
filters = [{'id': py7zr.FILTER_COPY}]
444+
return py7zr.SevenZipFile(zip_path, mode='w', password=password, filters=filters, header_encryption=True)
445+
else:
446+
try:
447+
# noinspection PyUnresolvedReferences
448+
import pyzipper
449+
except ImportError:
450+
self.warning_lib_not_install('pyzipper')
451+
452+
# noinspection PyUnboundLocalVariable
453+
aes_zip_file = pyzipper.AESZipFile(zip_path, "w", pyzipper.ZIP_DEFLATED)
454+
aes_zip_file.setencryption(pyzipper.WZ_AES, nbits=128)
455+
password_bytes = str.encode(password)
456+
aes_zip_file.setpassword(password_bytes)
457+
if is_random:
458+
aes_zip_file.comment = password_bytes
459+
return aes_zip_file
460+
461+
def decide_password(self, encrypt_dict: dict, zip_path: str):
462+
encrypt_type = encrypt_dict.get('type', '')
463+
is_random = False
464+
465+
if encrypt_type == 'random':
466+
is_random = True
467+
password = self.generate_random_str(48)
468+
self.log(f'生成随机密码: [{password}] → [{zip_path}]', 'encrypt')
469+
else:
470+
password = str(encrypt_dict['password'])
471+
self.log(f'使用指定密码: [{password}] → [{zip_path}]', 'encrypt')
472+
473+
return password, is_random
474+
404475

405476
class ClientProxyPlugin(JmOptionPlugin):
406477
plugin_key = 'client_proxy'
@@ -869,6 +940,7 @@ def invoke(self,
869940
# 服务器的代码位于一个独立库:plugin_jm_server,需要独立安装
870941
# 源代码仓库:https://github.com/hect0x7/plugin-jm-server
871942
try:
943+
# noinspection PyUnresolvedReferences
872944
import plugin_jm_server
873945
self.log(f'当前使用plugin_jm_server版本: {plugin_jm_server.__version__}')
874946
except ImportError:

0 commit comments

Comments
 (0)