diff --git a/.gitignore b/.gitignore index 5526fd6..eae2719 100644 --- a/.gitignore +++ b/.gitignore @@ -119,6 +119,7 @@ config.yml pagermaid.session pagermaid.session-journal *.pagermaid +pagermaid_backup.tar.gz docker-compose.yml plugins/ data/* diff --git a/languages/built-in/en.yml b/languages/built-in/en.yml index bb1d076..7f6ae04 100644 --- a/languages/built-in/en.yml +++ b/languages/built-in/en.yml @@ -126,6 +126,19 @@ parseqr_nofile: There are no attachments in the message. parseqr_content: Content parseqr_e_noqr: Target is not a QR Code. parseqr_log: Parsed QR Code with content +# backup +# backup +backup_des: Backup data files to local as well as log channels, support full backup of database, safe and reliable. +backup_process: It may take some time to complete the backup. +backup_success_channel: Backup is complete! And it has been packaged and sent to the Log channel! +backup_success: Backup is complete! +# recovery +recovery_des: Restore data from local backup or replied backup file, and support full database recovery easily and quickly. +recovery_file_error: Unknown file that may not be the type of file backed up by pagermaid. +recovery_down: Downloading backup file to local... +recovery_process: It may take some time to complete the recovery. +recovery_file_not_found: No backup file found. +recovery_success: Data file recovery is complete! You may need to restart manually for this to take effect. # captions # convert convert_des: Reply to an attachment message and convert it to image output diff --git a/languages/built-in/zh-cn.yml b/languages/built-in/zh-cn.yml index 6013593..f6f781f 100644 --- a/languages/built-in/zh-cn.yml +++ b/languages/built-in/zh-cn.yml @@ -133,6 +133,20 @@ parseqr_content: 内容 parseqr_e_noqr: 回复的附件不是 QR 码。 parseqr_log: 已解析一张带有 QR 码的消息,内容: +# backup +## backup +backup_des: 备份数据文件到本地以及日志频道,支持数据库的全量备份,安全可靠。 +backup_process: 开始备份,可能需要一定的时间。。。 +backup_success_channel: 数据文件备份完成!并且已经打包发送到 Log 频道! +backup_success: 数据文件备份完成! +## recovery +recovery_des: 从本地备份或者所回复的备份文件中恢复数据,支持数据库的全量恢复,方便快速。 +recovery_file_error: 未知的文件,可能并不是通过 pagermaid 所备份的文件类型。 +recovery_down: 正在下载备份文件到本地。。。 +recovery_process: 开始恢复,可能需要一定的时间。。 +recovery_file_not_found: 没有找到备份文件。 +recovery_success: 数据文件恢复完成!您可能需要手动重新启动才能生效。 + # captions ## convert convert_des: 回复某条附件消息然后转换为图片输出 diff --git a/languages/built-in/zh-tw.yml b/languages/built-in/zh-tw.yml index 9d64e19..871e3a6 100644 --- a/languages/built-in/zh-tw.yml +++ b/languages/built-in/zh-tw.yml @@ -126,6 +126,19 @@ parseqr_nofile: 回覆的訊息裡面沒有附件。 parseqr_content: 內容 parseqr_e_noqr: 回覆的附件錯誤! parseqr_log: 已解析QR 扣的內容,如下: +# backup +# backup +backup_des: 備份數據文件到本地以及日誌頻道,支持數據庫的全量備份,安全可靠。 +backup_process: 開始備份,可能需要一定的時間。。。 +backup_success_channel: 數據文件備份完成!並且已經打包發送到 Log 頻道! +backup_success: 數據文件備份完成! +# recovery +recovery_des: 從本地備份或者所回复的備份文件中恢復數據,支持數據庫的全量恢復,方便快速。 +recovery_file_error: 未知的文件,可能並不是通過 pagermaid 所備份的文件類型。 +recovery_down: 正在下載備份文件到本地。。。 +recovery_process: 開始恢復,可能需要一定的時間。。 +recovery_file_not_found: 沒有找到備份文件。 +recovery_success: 數據文件恢復完成!您可能需要手動重新啟動才能生效。 # captions # convert convert_des: 回覆附件訊息並轉換為圖片 diff --git a/pagermaid/modules/backup.py b/pagermaid/modules/backup.py new file mode 100644 index 0000000..12b98d9 --- /dev/null +++ b/pagermaid/modules/backup.py @@ -0,0 +1,104 @@ +""" Pagermaid backup and recovery plugin. """ +import json +import os +import tarfile +from distutils.util import strtobool +from io import BytesIO + +from pagermaid import config, redis_status, redis, silent +from pagermaid.listener import listener +from pagermaid.utils import alias_command, upload_attachment, lang +from telethon.tl.types import MessageMediaDocument + + +def make_tar_gz(output_filename, source_dirs: list): + """ + 压缩 tar.gz 文件 + :param output_filename: 压缩文件名 + :param source_dirs: 需要压缩的文件列表 + :return: None + """ + with tarfile.open(output_filename, "w:gz") as tar: + for i in source_dirs: + tar.add(i, arcname=os.path.basename(i)) + + +def un_tar_gz(filename, dirs): + """ + 解压 tar.gz 文件 + :param filename: 压缩文件名 + :param dirs: 解压后的存放路径 + :return: bool + """ + try: + t = tarfile.open(filename, "r:gz") + t.extractall(path=dirs) + return True + except Exception as e: + print(e) + return False + + +@listener(is_plugin=True, outgoing=True, command=alias_command("backup"), + description=lang('back_des')) +async def backup(context): + if not silent: + await context.edit(lang('backup_process')) + if os.path.exists("pagermaid_backup.tar.gz"): + os.remove("pagermaid_backup.tar.gz") + # remove mp3 , they are so big ! + for i in os.listdir("data"): + if i.find(".mp3") != -1 or i.find(".jpg") != -1 or i.find(".flac") != -1 or i.find(".ogg") != -1: + os.remove(f"data{os.sep}{i}") + # backup redis + redis_data = {} + if redis_status(): + for k in redis.keys(): + data_type = redis.type(k) + if data_type == b'string': + v = redis.get(k) + redis_data[k.decode()] = v.decode() + with open(f"data{os.sep}redis.json", "w", encoding='utf-8') as f: + json.dump(redis_data, f, indent=4) + # run backup function + make_tar_gz("pagermaid_backup.tar.gz", ["data", "plugins", "config.yml"]) + if strtobool(config['log']): + await upload_attachment("pagermaid_backup.tar.gz", int(config['log_chatid']), None) + await context.edit(lang("backup_success_channel")) + else: + await context.edit(lang("backup_success")) + + +@listener(is_plugin=True, outgoing=True, command=alias_command("recovery"), + description=lang('recovery_des')) +async def recovery(context): + message = await context.get_reply_message() + if message and message.media: + if isinstance(message.media, MessageMediaDocument): + try: + file_name = message.media.document.attributes[0].file_name + except: + return await context.edit(lang('recovery_file_error')) + if file_name.find(".tar.gz") != -1: + await context.edit(lang('recovery_down')) + else: + return await context.edit(lang('recovery_file_error')) + else: + return await context.edit(lang('recovery_file_error')) + _file = BytesIO() + await context.client.download_file(message.media.document, _file) + with open("pagermaid_backup.tar.gz", "wb") as f: + f.write(_file.getvalue()) + if not silent: + await context.edit(lang('recovery_process')) + if not os.path.exists("pagermaid_backup.tar.gz"): + return await context.edit(lang('recovery_file_not_found')) + un_tar_gz("pagermaid_backup.tar.gz", "") + # recovery redis + if redis_status(): + if os.path.exists(f"data{os.sep}redis.json"): + with open(f"data{os.sep}redis.json", "r", encoding='utf-8') as f: + redis_data = json.load(f) + for k, v in redis_data.items(): + redis.set(k, v) + await context.edit(lang('recovery_success'))