# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀ # █▀█ █ █ █ █▀█ █▀▄ █ # © Copyright 2022 # https://t.me/hikariatama # # 🔒 Licensed under the GNU AGPLv3 # 🌐 https://www.gnu.org/licenses/agpl-3.0.html # meta pic: https://static.dan.tatar/backuper_icon.png # meta banner: https://mods.hikariatama.ru/badges/backuper.jpg # meta developer: @hikarimods # scope: hikka_only # scope: hikka_min 1.2.10 import datetime import io import json import logging import os import zipfile from telethon.tl.types import Message from .. import loader, utils logger = logging.getLogger(__name__) DATA_DIR = ( os.path.normpath(os.path.join(utils.get_base_dir(), "..")) if "OKTETO" not in os.environ and "DOCKER" not in os.environ else "/data" ) LOADED_MODULES_DIR = os.path.join(DATA_DIR, "loaded_modules") @loader.tds class BackuperMod(loader.Module): """Create the backup of all modules or the whole database""" strings = { "name": "Backuper", "backup_caption": ( "👆 This is your database" " backup. Do not give it to anyone, it contains personal info." ), "reply_to_file": ( "🚫 Reply to .json or" " .zip" " file" ), "db_restored": ( "🔄 Database updated," " restarting..." ), "modules_backup": ( "🗃 Backup mods ({})" ), "mods_restored": ( " Mods restored," " restarting" ), } strings_ru = { "backup_caption": ( "👆 Это - бекап базы" " данных. Никому его не передавай, он содержит личную информацию." ), "reply_to_file": ( "🚫 Ответь на .json или" " .zip файл" ), "db_restored": ( "🔄 База обновлена," " перезагружаюсь..." ), "modules_backup": ( "🗃 Бекап модулей ({})" ), "mods_restored": ( " Модули восстановлены," " перезагружаюсь" ), "_cmd_doc_backupdb": "Создать бекап базы данных [будет отправлен в Избранное]", "_cmd_doc_restoredb": "Восстановить базу данных из файла", "_cmd_doc_backupmods": "Создать бекап модулей", "_cmd_doc_restoremods": " - Восстановить модули из файла", "_cls_doc": "Создает резервные копии", } strings_de = { "backup_caption": ( "👆 Dies ist ein" " Datenbank-Backup. Gib es niemandem, es enthält persönliche" " Informationen." ), "reply_to_file": ( "🚫 Antworte auf .json" " oder .zip Datei" ), "db_restored": ( "🔄 Datenbank" " aktualisiert, starte neu..." ), "modules_backup": ( "🗃 Backup-Module ({})" ), "mods_restored": ( " Module" " wiederhergestellt, starte neu" ), "_cmd_doc_backupdb": ( "Datenbank-Backup erstellen [wird in den Favoriten gesendet]" ), "_cmd_doc_restoredb": "Datenbank aus Datei wiederherstellen", "_cmd_doc_backupmods": "Backup-Module erstellen", "_cmd_doc_restoremods": " - Module aus Datei wiederherstellen", "_cls_doc": "Erstellt Sicherungskopien", } strings_hi = { "backup_caption": ( "👆 यह आपका डेटाबेस बैकअप" " है। किसी को भी न दें, यह व्यक्तिगत जानकारी सामग्री में है।" ), "reply_to_file": ( "🚫 .json या .zip फ़ाइल पर" " जवाब दें" ), "db_restored": ( "🔄 डेटाबेस अपडेट कर रहा" " है, पुनः आरंभ कर रहा है..." ), "modules_backup": ( "🗃 मॉड्स बैकअप ({})" ), "mods_restored": ( " मॉड्स पुनः स्थापित कर" " रहे हैं, पुनः आरंभ कर रहे हैं" ), "_cmd_doc_backupdb": "डेटाबेस बैकअप बनाएं [फ़ेवरिट्स में भेजा जाएगा]", "_cmd_doc_restoredb": "फ़ाइल से डेटाबेस पुनः स्थापित करें", "_cmd_doc_backupmods": "मॉड्स बैकअप बनाएं", "_cmd_doc_restoremods": " - फ़ाइल से मॉड्स पुनः स्थापित करें", "_cls_doc": "बैकअप बनाता है", } strings_uz = { "backup_caption": ( "👆 Bu sizning" " ma'lumotlar" " bazangizning e'loni. Kimga ko'rsatmasangiz, shu shaxsiy ma'lumotlarni o'z" " ichiga oladi." ), "reply_to_file": ( "🚫 .json yoki .zip" " faylga" " javob bering" ), "db_restored": ( "🔄 Ma'lumotlar bazasi" " yangilandi, qayta ishga tushirilmoqda..." ), "modules_backup": ( "🗃 Modullar e'loni" " ({})" ), "mods_restored": ( " Modullar qayta" " tiklandi, qayta ishga tushirilmoqda" ), "_cmd_doc_backupdb": ( "Ma'lumotlar bazasini e'lon qiling [Favoritlarga jo'natiladi]" ), "_cmd_doc_restoredb": "Fayldan ma'lumotlar bazasini tiklash", "_cmd_doc_backupmods": "Modullarni e'lon qiling", "_cmd_doc_restoremods": " - Fayldan modullarni tiklash", "_cls_doc": "E'lon qiladi", } strings_tr = { "backup_caption": ( "👆 Bu veritabanı" " yedeğinizdir. Kimseye verin, kişisel bilgiler içerir." ), "reply_to_file": ( "🚫 .json veya .zip" " dosyasına yanıt verin" ), "db_restored": ( "🔄 Veritabanı" " güncellendi, yeniden başlatılıyor..." ), "modules_backup": ( "🗃 Modüller yedeği" " ({})" ), "mods_restored": ( " Modüller geri" " yüklendi, yeniden başlatılıyor" ), "_cmd_doc_backupdb": "Veritabanı yedeği oluştur [favorilere gönderilecek]", "_cmd_doc_restoredb": "Dosyadan veritabanını geri yükle", "_cmd_doc_backupmods": "Modüller yedeği oluştur", "_cmd_doc_restoremods": " - Modülleri dosyadan geri yükle", "_cls_doc": "Yedek oluşturur", } async def backupdbcmd(self, message: Message): """Create database backup [will be sent in pm]""" txt = io.BytesIO(json.dumps(self._db).encode("utf-8")) txt.name = ( f"db-backup-{getattr(datetime, 'datetime', datetime).now().strftime('%d-%m-%Y-%H-%M')}.json" ) await self._client.send_file("me", txt, caption=self.strings("backup_caption")) await message.delete() async def restoredbcmd(self, message: Message): """Restore database from file""" reply = await message.get_reply_message() if not reply or not reply.media: await utils.answer( message, self.strings("reply_to_file"), ) return file = await self._client.download_file(reply.media, bytes) decoded_text = json.loads(file.decode("utf-8")) if not self._db.process_db_autofix(decoded_text): raise RuntimeError("Attempted to restore broken database") self._db.clear() self._db.update(**decoded_text) self._db.save() await utils.answer(message, self.strings("db_restored")) await self.allmodules.commands["restart"]( await message.respond(f"{self.get_prefix()}restart --force") ) async def backupmodscmd(self, message: Message): """Create backup of mods""" mods_quantity = len(self.lookup("Loader").get("loaded_modules", {})) result = io.BytesIO() result.name = "mods.zip" db_mods = json.dumps(self.lookup("Loader").get("loaded_modules", {})).encode() with zipfile.ZipFile(result, "w", zipfile.ZIP_DEFLATED) as zipf: if "DYNO" not in os.environ: for root, _, files in os.walk(LOADED_MODULES_DIR): for file in files: with open(os.path.join(root, file), "rb") as f: zipf.writestr(file, f.read()) mods_quantity += 1 zipf.writestr("db_mods.json", db_mods) archive = io.BytesIO(result.getvalue()) archive.name = ( f"mods-{getattr(datetime, 'datetime', datetime).now().strftime('%d-%m-%Y-%H-%M')}.zip" ) await self._client.send_file( utils.get_chat_id(message), archive, caption=self.strings("modules_backup").format(mods_quantity), ) await message.delete() async def restoremodscmd(self, message: Message): """ - Restore mods from backup""" reply = await message.get_reply_message() if not reply or not reply.media: await utils.answer(message, self.strings("reply_to_file")) return file = await self._client.download_file(reply.media, bytes) try: decoded_text = json.loads(file.decode("utf-8")) except Exception: try: file = io.BytesIO(file) file.name = "mods.zip" with zipfile.ZipFile(file) as zf: for name in zf.namelist(): with zf.open(name, "r") as module: if name == "db_mods.json": db_mods = json.loads(module.read().decode()) if isinstance(db_mods, dict) and all( isinstance(key, str) and isinstance(value, str) for key, value in db_mods.items() ): self.lookup("Loader").set("loaded_modules", db_mods) continue if "DYNO" not in os.environ: with open( os.path.join(LOADED_MODULES_DIR, name), "wb" ) as f: f.write(module.read()) except Exception: logger.exception("Can't restore mods") await utils.answer(message, self.strings("reply_to_file")) return else: if not isinstance(decoded_text, dict) or not all( isinstance(key, str) and isinstance(value, str) for key, value in decoded_text.items() ): raise RuntimeError("Invalid backup") self.lookup("Loader").set("loaded_modules", decoded_text) await utils.answer(message, self.strings("mods_restored")) await self.allmodules.commands["restart"]( await message.respond(f"{self.get_prefix()}restart --force") )