@@ -383,6 +383,168 @@ def get_zip_path(self, album, photo, filename_rule, suffix, zip_dir):
383383 filename + fix_suffix (suffix ),
384384 )
385385
386+ # Plugin: EncryptedZipPlugin
387+ # Author: AXIS5 a.k.a AXIS5Hacker
388+ class EncryptedZipPlugin (JmOptionPlugin ):
389+ plugin_key = 'encryptzip'
390+
391+ # noinspection PyAttributeOutsideInit
392+ def invoke (self ,
393+ downloader ,
394+ album : JmAlbumDetail = None ,
395+ photo : JmPhotoDetail = None ,
396+ delete_original_file = False ,
397+ level = 'photo' ,
398+ filename_rule = 'Ptitle' ,
399+ suffix = 'zip' ,
400+ zip_dir = './' ,
401+ default_password = None
402+ ) -> None :
403+ # default_password为指定的固定密码,在yml中配置,为空则为随机
404+
405+ downloader : JmDownloader
406+ self .downloader = downloader
407+ self .level = level
408+ self .delete_original_file = delete_original_file
409+ self .default_password = default_password
410+
411+ # 使用的库
412+ import random
413+ import pyzipper
414+ import zipfile
415+
416+ # 确保压缩文件所在文件夹存在
417+ zip_dir = JmcomicText .parse_to_abspath (zip_dir )
418+ mkdir_if_not_exists (zip_dir )
419+
420+ path_to_delete = []
421+ photo_dict = self .get_downloaded_photo (downloader , album , photo )
422+
423+ if level == 'album' :
424+ zip_path = self .get_zip_path (album , None , filename_rule , suffix , zip_dir )
425+ self .zip_album (album , photo_dict , zip_path , path_to_delete )
426+
427+ elif level == 'photo' :
428+ for photo , image_list in photo_dict .items ():
429+ zip_path = self .get_zip_path (None , photo , filename_rule , suffix , zip_dir )
430+ self .zip_photo (photo , image_list , zip_path , path_to_delete )
431+
432+ else :
433+ ExceptionTool .raises (f'Not Implemented Zip Level: { level } ' )
434+
435+ self .after_zip (path_to_delete )
436+
437+ def generate_random_str (self , randomlength = 32 ):
438+ """
439+ 自动生成随机字符密码,长度由randomlength指定
440+ """
441+ random_str = ''
442+ base_str = r'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='
443+ length = len (base_str ) - 1
444+ for i in range (randomlength ):
445+ random_str += base_str [random .randint (0 , length )]
446+ return random_str
447+
448+ def get_downloaded_photo (self , downloader , album , photo ):
449+ return (
450+ downloader .download_success_dict [album ]
451+ if album is not None # after_album
452+ else downloader .download_success_dict [photo .from_album ] # after_photo
453+ )
454+
455+ def zip_photo (self , photo , image_list : list , zip_path : str , path_to_delete ):
456+ """
457+ 压缩photo文件夹
458+ """
459+ photo_dir = self .option .decide_image_save_dir (photo ) \
460+ if len (image_list ) == 0 \
461+ else os .path .dirname (image_list [0 ][0 ])
462+
463+ #from common import backup_dir_to_zip
464+ #backup_dir_to_zip(photo_dir, zip_path)
465+ if self .default_password is None :
466+ #generate random password
467+ crypt = self .generate_random_str (48 )
468+ else :
469+ #fixed password
470+ crypt = self .default_password
471+ zip_file = pyzipper .AESZipFile (zip_path , "w" ,pyzipper .ZIP_DEFLATED )
472+
473+ if self .default_password is None :
474+ #attach the random password to the comment of the zip file
475+ zip_file .comment = str .encode (crypt )
476+ zip_file .setpassword (str .encode (crypt ))
477+ zip_file .setencryption (pyzipper .WZ_AES , nbits = 128 )
478+ for file in files_of_dir (photo_dir ):
479+ abspath = os .path .join (photo_dir , file )
480+ relpath = os .path .relpath (abspath , photo_dir )
481+ zip_file .write (abspath , relpath )
482+ if self .default_password is not None :
483+ self .log (f'指定固定密码[{ self .default_password } ]' )
484+ self .log (f'加密压缩章节[{ photo .photo_id } ]成功 → { zip_path } ' , 'finish' )
485+ path_to_delete .append (self .unified_path (photo_dir ))
486+
487+ @staticmethod
488+ def unified_path (f ):
489+ return fix_filepath (f , os .path .isdir (f ))
490+
491+ def zip_album (self , album , photo_dict : dict , zip_path , path_to_delete ):
492+ """
493+ 压缩album文件夹
494+ """
495+
496+ album_dir = self .option .dir_rule .decide_album_root_dir (album )
497+
498+ with pyzipper .AESZipFile (zip_path , 'w' , pyzipper .ZIP_DEFLATED ) as f :
499+ if self .default_password is None :
500+ #generate random password
501+ crypt = self .generate_random_str (48 )
502+ else :
503+ #fixed password
504+ crypt = self .default_password
505+ if self .default_password is None :
506+ #attach the random password to the comment of the zip file
507+ zip_file .comment = str .encode (crypt )
508+ f .setpassword (str .encode (crypt ))
509+ f .setencryption (pyzipper .WZ_AES , nbits = 128 )
510+ for photo in photo_dict .keys ():
511+ # 定位到章节所在文件夹
512+ photo_dir = self .unified_path (self .option .decide_image_save_dir (photo ))
513+ # 章节文件夹标记为删除
514+ path_to_delete .append (photo_dir )
515+ for file in files_of_dir (photo_dir ):
516+ abspath = os .path .join (photo_dir , file )
517+ relpath = os .path .relpath (abspath , album_dir )
518+ f .write (abspath , relpath )
519+ if self .default_password is not None :
520+ self .log (f'指定固定密码[{ self .default_password } ]' )
521+ self .log (f'加密压缩本子[{ album .album_id } ]成功 → { zip_path } ' , 'finish' )
522+
523+ def after_zip (self , path_to_delete : List [str ]):
524+ # 删除所有原文件
525+ dirs = sorted (path_to_delete , reverse = True )
526+
527+ image_paths = [
528+ path
529+ for photo_dict in self .downloader .download_success_dict .values ()
530+ for image_list in photo_dict .values ()
531+ for path , image in image_list
532+ ]
533+ #print(image_paths)
534+ self .execute_deletion (image_paths )
535+ self .execute_deletion (dirs )
536+
537+ # noinspection PyMethodMayBeStatic
538+ def get_zip_path (self , album , photo , filename_rule , suffix , zip_dir ):
539+ """
540+ 计算zip文件的路径
541+ """
542+ filename = DirRule .apply_rule_directly (album , photo , filename_rule )
543+ from os .path import join
544+ return join (
545+ zip_dir ,
546+ filename + fix_suffix (suffix ),
547+ )
386548
387549class ClientProxyPlugin (JmOptionPlugin ):
388550 plugin_key = 'client_proxy'
0 commit comments