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