# ______ ___ ___ _ _ # ____ | ___ \ | \/ | | | | | # / __ \| |_/ / _| . . | ___ __| |_ _| | ___ # / / _` | __/ | | | |\/| |/ _ \ / _` | | | | |/ _ \ # | | (_| | | | |_| | | | | (_) | (_| | |_| | | __/ # \ \__,_\_| \__, \_| |_/\___/ \__,_|\__,_|_|\___| # \____/ __/ | # |___/ # На модуль распространяется лицензия "GNU General Public License v3.0" # https://github.com/all-licenses/GNU-General-Public-License-v3.0 # meta developer: @pymodule import asyncio import os import toml from .. import loader, utils from herokutl.tl.types import Message @loader.tds class IwaAnimation(loader.Module): """Frame-by-frame text animations loaded from .anim TOML files""" strings = { "name": "IwaAnimation", "err_no_reply": "{e} Reply to a .anim file.", "err_not_anim": "{e} File must have .anim extension.", "err_bad_format": "{e} Invalid file format (missing name or cmd).", "err_no_frames": "{e} No frames found in the file.", "err_not_found": "{e} Animation not found.", "err_no_cmd": "{e} Specify a command name.", "err_generic": "{e} Error:\n\n{exc}", "ok_loaded": "{s} Loaded: {name}\nCommand: .anim {cmd}", "ok_deleted": "{s} Deleted.", "list_header": "
Animations:
\n\n
", "list_row": "• {cmd} — {name} ({n} frames)\n", "list_footer": "
", "list_empty": "{e} No animations.", } strings_ru = { "name": "IwaAnimation", "err_no_reply": "{e} Ответьте на .anim файл.", "err_not_anim": "{e} Файл должен быть формата .anim", "err_bad_format": "{e} Неверный формат файла (нет name или cmd).", "err_no_frames": "{e} В файле нет кадров.", "err_not_found": "{e} Анимация не найдена.", "err_no_cmd": "{e} Укажи команду.", "err_generic": "{e} Ошибка:\n\n{exc}", "ok_loaded": "{s} Загружено: {name}\nКоманда: .anim {cmd}", "ok_deleted": "{s} Удалено.", "list_header": "
Анимации:
\n\n
", "list_row": "• {cmd} — {name} ({n} кадров)\n", "list_footer": "
", "list_empty": "{e} Нет анимаций.", } _E = "" _S = "" async def client_ready(self): if not self.db.get("IwaAnimations", "anims", False): self.db.set("IwaAnimations", "anims", {}) @loader.command(ru_doc="- Загрузить анимацию из полученного .anim файла") async def lanimcmd(self, message: Message): """- Load animation from a replied .anim file""" reply = await message.get_reply_message() if not reply or not reply.document: return await utils.answer( message, self.strings["err_no_reply"].format(e=self._E) ) filename = reply.file.name or "" if not filename.endswith(".anim"): return await utils.answer( message, self.strings["err_not_anim"].format(e=self._E) ) tmp = "anim_load.anim" await reply.download_media(tmp) try: data = toml.load(tmp) name = data.get("name") cmd = data.get("cmd") delay = float(data.get("time", 0.5)) if not name or not cmd: return await utils.answer( message, self.strings["err_bad_format"].format(e=self._E) ) frames = [] for key in sorted( (k for k in data if str(k).isdigit()), key=lambda x: int(x) ): frame = data[key] frames.append("\n".join(frame) if isinstance(frame, list) else str(frame)) if not frames: return await utils.answer( message, self.strings["err_no_frames"].format(e=self._E) ) anims = self.db.get("IwaAnimations", "anims", {}) anims[cmd] = {"name": name, "frames": frames, "delay": delay} self.db.set("IwaAnimations", "anims", anims) await utils.answer( message, self.strings["ok_loaded"].format(s=self._S, name=name, cmd=cmd), ) except Exception as exc: await utils.answer( message, self.strings["err_generic"].format(e=self._E, exc=exc) ) finally: if os.path.exists(tmp): os.remove(tmp) @loader.command(ru_doc=" - Воспроизвести загруженную анимацию") async def animcmd(self, message: Message): """ - Play a loaded animation""" cmd = utils.get_args_raw(message) if not cmd: return await utils.answer( message, self.strings["err_no_cmd"].format(e=self._E) ) anims = self.db.get("IwaAnimations", "anims", {}) if cmd not in anims: return await utils.answer( message, self.strings["err_not_found"].format(e=self._E) ) anim = anims[cmd] msg = await utils.answer(message, anim["frames"][0]) try: for frame in anim["frames"][1:]: await asyncio.sleep(anim["delay"]) await msg.edit(frame) except Exception: pass @loader.command(ru_doc="- Отобразить список всех загруженных анимаций") async def animscmd(self, message: Message): """- List all loaded animations""" anims = self.db.get("IwaAnimations", "anims", {}) if not anims: return await utils.answer( message, self.strings["list_empty"].format(e=self._E) ) text = self.strings["list_header"] for cmd, data in anims.items(): text += self.strings["list_row"].format( cmd=cmd, name=data["name"], n=len(data["frames"]) ) text += self.strings["list_footer"] await utils.answer(message, text) @loader.command(ru_doc=" - Удалить анимацию") async def delanimcmd(self, message: Message): """ - Delete an animation""" cmd = utils.get_args_raw(message) anims = self.db.get("IwaAnimations", "anims", {}) if cmd not in anims: return await utils.answer( message, self.strings["err_not_found"].format(e=self._E) ) anims.pop(cmd) self.db.set("IwaAnimations", "anims", anims) await utils.answer(message, self.strings["ok_deleted"].format(s=self._S)) @loader.command(ru_doc=" - Экспорт анимации в файл .anim") async def dumpanimcmd(self, message: Message): """ - Export an animation to a .anim file""" cmd = utils.get_args_raw(message) anims = self.db.get("IwaAnimations", "anims", {}) if cmd not in anims: return await utils.answer( message, self.strings["err_not_found"].format(e=self._E) ) anim = anims[cmd] data = {"name": anim["name"], "cmd": cmd, "time": str(anim["delay"])} for i, frame in enumerate(anim["frames"], start=1): data[str(i)] = frame.split("\n") file = f"{cmd}.anim" try: with open(file, "w", encoding="utf-8") as f: toml.dump(data, f) await message.delete() await self._client.send_file(message.to_id, file) finally: if os.path.exists(file): os.remove(file)