# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
# █▀█ █ █ █ █▀█ █▀▄ █
# © 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")
)