diff --git a/.gitignore b/.gitignore index e2855ed..27b07e9 100755 --- a/.gitignore +++ b/.gitignore @@ -154,4 +154,8 @@ config.json *.jpg *.jpeg *.webp -*.webm \ No newline at end of file +*.webm +*.tgs +*.mp4 +*.mp3 +*.ogg \ No newline at end of file diff --git a/hikka/_types.py b/hikka/_types.py index cc6ea20..9e9fbcf 100644 --- a/hikka/_types.py +++ b/hikka/_types.py @@ -8,20 +8,35 @@ class Module: """There is no help for this module""" def config_complete(self): - """Will be called when module.config is populated""" + """Called when module.config is populated""" async def client_ready(self, client, db): - """Will be called after client is ready (after config_loaded)""" + """Called after client is ready (after config_loaded)""" async def on_unload(self): - """Will be called after unloading / reloading module""" + """Called after unloading / reloading module""" - # Called after client_ready, for internal use only. Must not be used by non-core modules async def _client_ready2(self, client, db): - pass + """Called after client_ready, for internal use only. Must not be used by non-core modules""" + + async def on_dlmod(self, client, db): + """ + Called after the module is first time loaded with .dlmod or .loadmod + + Possible use-cases: + - Send reaction to author's channel message + - Join author's channel + - Create asset folder + - ... + + ⚠️ Note, that any error there will not interrupt module load, and will just + send a message to logs with verbosity INFO and exception traceback + """ class LoadError(Exception): + """Tells user, why your module can't be loaded, if rased in `client_ready`""" + def __init__(self, error_message: str): # skipcq: PYL-W0231 self._error = error_message @@ -29,6 +44,16 @@ class LoadError(Exception): return self._error +class SelfUnload(Exception): + """Silently unloads module, if raised in `client_ready`""" + + def __init__(self, error_message: str = ""): # skipcq: PYL-W0231 + self._error = error_message + + def __str__(self) -> str: + return self._error + + class StopLoop(Exception): """Stops the loop, in which is raised""" diff --git a/hikka/langpacks/ru.json b/hikka/langpacks/ru.json index 4bb34ff..44dba47 100644 --- a/hikka/langpacks/ru.json +++ b/hikka/langpacks/ru.json @@ -379,15 +379,15 @@ "hikka.modules.animefy._cmd_doc_animefy": " - Нарисовать фото", "hikka.modules.animefy._cls_doc": "Делает из реальных людей мультяшек", "hikka.modules.temp_chat.chat_is_being_removed": "🚫 Чат удаляется...", - "hikka.modules.temp_chat.args": "Капец с аргументами: .help TempChat", - "hikka.modules.temp_chat.chat_not_found": "Чат не найден", - "hikka.modules.temp_chat.tmp_cancelled": "Чат {} будет жить вечно!", - "hikka.modules.temp_chat.delete_error": "Произошла ошибка удаления чата. Сделай это вручную.", + "hikka.modules.temp_chat.args": "🚫 Капец с аргументами: .help TempChat", + "hikka.modules.temp_chat.chat_not_found": "🚫 Чат не найден", + "hikka.modules.temp_chat.tmp_cancelled": "🚫 Чат {} будет жить вечно!", + "hikka.modules.temp_chat.delete_error": "🚫 Произошла ошибка удаления чата. Сделай это вручную.", "hikka.modules.temp_chat.temp_chat_header": "⚠️ Этот чат ({}) является временным и будет удален {}.", - "hikka.modules.temp_chat.chat_created": "Чат создан", - "hikka.modules.temp_chat.delete_error_me": "Ошибка удаления чата {}", - "hikka.modules.temp_chat._cmd_doc_tmpchat": "<время> <название> - Создать новый временный чат\nВремя можно указать в таком формате: 30s, 30min, 1h, 1d, 1w, 1m", - "hikka.modules.temp_chat._cmd_doc_tmpcurrent": "<время> - Создать новый временный чат\nВремя можно указать в таком формате: 30s, 30min, 1h, 1d, 1w, 1m", + "hikka.modules.temp_chat.chat_created": "✅ Чат создан", + "hikka.modules.temp_chat.delete_error_me": "🚫 Ошибка удаления чата {}", + "hikka.modules.temp_chat._cmd_doc_tmpchat": "<время> <название> - Создать новый временный чат", + "hikka.modules.temp_chat._cmd_doc_tmpcurrent": "<время> - Создать новый временный чат", "hikka.modules.temp_chat._cmd_doc_tmpchats": "Показать временные чаты", "hikka.modules.temp_chat._cmd_doc_tmpcancel": "[chat-id] - Отменить удаление чата.", "hikka.modules.temp_chat._cmd_doc_tmpctime": " <новое время> - Изменить время жизни чата", @@ -809,5 +809,12 @@ "hikka.modules.hikka_backup.period": "⌚️ Приветики! Я Асуна - твой менеджер резервного копирования. Пожалуйста, выбери периодичность резервных копий базы данных Hikka", "hikka.modules.hikka_backup.saved": "✅ Периодичность сохранена! Ее можно изменить с помощью .set_backup_period", "hikka.modules.hikka_backup.never": "✅ Я не буду делать автоматические резервные копии. Можно отменить используя .set_backup_period", - "hikka.modules.hikka_backup.invalid_args": "🚫 Укажи правильную периодичность в часах, или `0` для отключения" + "hikka.modules.hikka_backup.invalid_args": "🚫 Укажи правильную периодичность в часах, или `0` для отключения", + "hikka.modules.hikka_info.owner": "Владелец", + "hikka.modules.hikka_info.version": "Версия", + "hikka.modules.hikka_info.build": "Сборка", + "hikka.modules.hikka_info.prefix": "Префикс команд", + "hikka.modules.hikka_info.send_info": "Отправить информацию о юзерботе", + "hikka.modules.hikka_info.description": "ℹ Это не раскроет никакой личной информации", + "hikka.modules.hikka_info._ihandle_doc_info": "Отправить информацию о юзерботе" } \ No newline at end of file diff --git a/hikka/loader.py b/hikka/loader.py index b3c293e..fc87647 100755 --- a/hikka/loader.py +++ b/hikka/loader.py @@ -39,7 +39,7 @@ from importlib.abc import SourceLoader from . import utils, security from .translations import Strings from .inline.core import InlineManager -from ._types import Module, LoadError, ModuleConfig, StopLoop # noqa: F401 +from ._types import Module, LoadError, ModuleConfig, StopLoop, SelfUnload # noqa: F401 from importlib.machinery import ModuleSpec from types import FunctionType @@ -303,7 +303,6 @@ class Modules: len(x) > 3 and x[-3:] == ".py" and x[0] != "_" - and ("OKTETO" in os.environ or x != "okteto.py") and ( not db.get("hikka", "disable_quickstart", False) or x != "quickstart.py" @@ -489,6 +488,10 @@ class Modules: mod=instance.strings["name"], ) + instance.get_prefix = lambda: ( + self._db.get("hikka.main", "command_prefix", False) or "." + ) + for module in self.modules: if module.__class__.__name__ == instance.__class__.__name__: if getattr(module, "__origin__", "") == "": @@ -612,12 +615,32 @@ class Modules: if self.added_modules: await self.added_modules(self) - async def send_ready_one(self, mod, client, db, allclients): + async def send_ready_one( + self, + mod: Module, + client: "TelegramClient", # noqa: F821 + db: "Database", # noqa: F821 + allclients: list, + no_self_unload: bool = False, + from_dlmod: bool = False, + ): mod.allclients = allclients mod.inline = self.inline + if from_dlmod: + try: + await mod.on_dlmod(client, db) + except Exception: + logging.info("Can't process `on_dlmod` hook", exc_info=True) + try: await mod.client_ready(client, db) + except SelfUnload as e: + if no_self_unload: + raise e + + logging.debug(f"Unloading {mod}, because it raised SelfUnload") + self.modules.remove(mod) except Exception as e: logging.exception(f"Failed to send mod init complete signal for {mod} due to {e}, attempting unload") # fmt: skip self.modules.remove(mod) diff --git a/hikka/modules/hikka_info.py b/hikka/modules/hikka_info.py index ab9e03b..259222e 100755 --- a/hikka/modules/hikka_info.py +++ b/hikka/modules/hikka_info.py @@ -25,7 +25,15 @@ logger = logging.getLogger(__name__) class HikkaInfoMod(loader.Module): """Show userbot info""" - strings = {"name": "HikkaInfo"} + strings = { + "name": "HikkaInfo", + "owner": "Owner", + "version": "Version", + "build": "Build", + "prefix": "Command prefix", + "send_info": "Send userbot info", + "description": "ℹ This will not compromise any sensitive info", + } async def client_ready(self, client, db): self._db = db @@ -34,13 +42,10 @@ class HikkaInfoMod(loader.Module): self.markup = {"text": "🌘 Support chat", "url": "https://t.me/hikka_talks"} def _render_info(self) -> str: - try: - repo = git.Repo() - ver = repo.heads[0].commit.hexsha - except Exception: - ver = "unknown" + ver = utils.get_git_hash() or "Unknown" try: + repo = git.Repo() diff = repo.git.log(["HEAD..origin/master", "--oneline"]) upd = ( "⚠️ Update required .update" @@ -52,10 +57,10 @@ class HikkaInfoMod(loader.Module): return ( "🌘 Hikka Userbot\n" - f'🤴 Owner: {utils.escape_html(get_display_name(self._me))}\n\n' - f"🔮 Version: {'.'.join(list(map(str, list(main.__version__))))}\n" - f"🧱 Build: {ver[:8] or 'Unknown'}\n" - f"📼 Command prefix: «{utils.escape_html((self._db.get(main.__name__, 'command_prefix', False) or '.')[0])}»\n" + f'🤴 {self.strings("owner")}: {utils.escape_html(get_display_name(self._me))}\n\n' + f"🔮 {self.strings('version')}: {'.'.join(list(map(str, list(main.__version__))))}\n" + f"🧱 {self.strings('build')}: {ver[:8]}\n\n" + f"📼 {self.strings('prefix')}: «{utils.escape_html(self.get_prefix())}»\n" f"{upd}\n" f"{utils.get_named_platform()}\n" ) @@ -65,8 +70,8 @@ class HikkaInfoMod(loader.Module): """Send userbot info""" return { - "title": "Send userbot info", - "description": "ℹ This will not compromise any sensitive data", + "title": self.strings("send_info"), + "description": self.strings("description"), "message": self._render_info(), "thumb": "https://github.com/hikariatama/Hikka/raw/master/assets/hikka_pfp.png", "reply_markup": self.markup, diff --git a/hikka/modules/loader.py b/hikka/modules/loader.py index 228888c..c515826 100755 --- a/hikka/modules/loader.py +++ b/hikka/modules/loader.py @@ -523,6 +523,7 @@ class LoaderMod(loader.Module): save_fs, ) # Try again except loader.LoadError as e: + self.allmodules.modules.remove(instance) if message: await utils.answer(message, f"🚫 {utils.escape_html(str(e))}") return @@ -535,15 +536,6 @@ class LoaderMod(loader.Module): return instance.inline = self.inline - instance.get = functools.partial( - self._mod_get, - mod=instance.strings["name"], - ) - instance.set = functools.partial( - self._mod_set, - mod=instance.strings["name"], - ) - instance.animate = self._animate for method in dir(instance): @@ -564,8 +556,17 @@ class LoaderMod(loader.Module): self._client, self._db, self.allclients, + no_self_unload=True, + from_dlmod=bool(message), ) except loader.LoadError as e: + self.allmodules.modules.remove(instance) + if message: + await utils.answer(message, f"🚫 {utils.escape_html(str(e))}") + return + except loader.SelfUnload as e: + logging.debug(f"Unloading {instance}, because it raised `SelfUnload`") + self.allmodules.modules.remove(instance) if message: await utils.answer(message, f"🚫 {utils.escape_html(str(e))}") return @@ -584,9 +585,6 @@ class LoaderMod(loader.Module): modname = getattr(instance, "name", "ERROR") modhelp = "" - prefix = utils.escape_html( - (self._db.get(main.__name__, "command_prefix", False) or ".") - ) if instance.__doc__: modhelp += ( @@ -612,7 +610,7 @@ class LoaderMod(loader.Module): instance.commands.items(), key=lambda x: x[0], ): - modhelp += self.strings("single_cmd").format(prefix, _name) + modhelp += self.strings("single_cmd").format(self.get_prefix(), _name) if fun.__doc__: modhelp += utils.escape_html(inspect.getdoc(fun)) @@ -690,12 +688,6 @@ class LoaderMod(loader.Module): self.strings("unloaded" if worked else "not_unloaded"), ) - def _mod_get(self, *args, mod: str = None) -> Any: - return self._db.get(mod, *args) - - def _mod_set(self, *args, mod: str = None) -> bool: - return self._db.set(mod, *args) - async def _animate( self, message: Union[Message, InlineMessage], @@ -726,7 +718,7 @@ class LoaderMod(loader.Module): message = await self.inline.form( message=message, text=frame, - reply_markup={"text": ".", "data": "empty"}, + reply_markup={"text": "\u0020\u2800", "data": "empty"}, ) else: message = await utils.answer(message, frame) diff --git a/hikka/modules/okteto.py b/hikka/modules/okteto.py index 44ea4d2..22d9117 100644 --- a/hikka/modules/okteto.py +++ b/hikka/modules/okteto.py @@ -8,7 +8,7 @@ # 🔒 Licensed under the GNU GPLv3 # 🌐 https://www.gnu.org/licenses/agpl-3.0.html -from .. import loader, utils +from .. import loader, utils, main import logging import asyncio import os @@ -16,6 +16,8 @@ import time from telethon.tl.functions.messages import GetScheduledHistoryRequest from telethon.tl.types import Message +from .._types import SelfUnload + logger = logging.getLogger(__name__) @@ -26,8 +28,6 @@ class OktetoMod(loader.Module): strings = {"name": "Okteto"} async def client_ready(self, client, db): - if "OKTETO" not in os.environ: - raise loader.LoadError("This module can be loaded only if userbot is installed to ☁️ Okteto") # fmt: skip self._db = db self._client = client @@ -38,21 +38,40 @@ class OktetoMod(loader.Module): self._exception_timeout = 10 self._send_interval = 5 self._bot = "@WebpageBot" + + if "OKTETO" not in os.environ: + messages = ( + await client( + GetScheduledHistoryRequest( + peer=self._bot, + hash=0, + ), + ) + ).messages + + if messages: + logger.info("Deleting previously scheduled Okteto pinger messages") + + for message in messages: + await message.delete() + + raise SelfUnload + await utils.dnd(client, await client.get_entity(self._bot), True) - self._task = asyncio.ensure_future(self._okteto_poller()) + self._task = asyncio.ensure_future(self._okteto_pinger()) async def on_unload(self): self._task.cancel() - async def _okteto_poller(self): + async def _okteto_pinger(self): """Creates queue to Webpage bot to reset Okteto polling after app goes to sleep""" while True: try: - if not self._db.get("hikka", "okteto_uri", False): + if not main.get_config_key("okteto_uri"): await asyncio.sleep(self._env_wait_interval) continue - uri = self._db.get("hikka", "okteto_uri") + uri = main.get_config_key("okteto_uri") current_queue = ( await self._client( GetScheduledHistoryRequest( @@ -87,8 +106,8 @@ class OktetoMod(loader.Module): async def watcher(self, message: Message): if ( not getattr(message, "raw_text", False) - or not self._db.get("hikka", "okteto_uri", False) - or self._db.get("hikka", "okteto_uri") not in message.raw_text + or not main.get_config_key("okteto_uri") + or main.get_config_key("okteto_uri") not in message.raw_text and "Link previews was updated successfully" not in message.raw_text or utils.get_chat_id(message) != 169642392 ): diff --git a/hikka/modules/quickstart.py b/hikka/modules/quickstart.py index 80a34e7..858dadd 100644 --- a/hikka/modules/quickstart.py +++ b/hikka/modules/quickstart.py @@ -8,7 +8,7 @@ # 🔒 Licensed under the GNU GPLv3 # 🌐 https://www.gnu.org/licenses/agpl-3.0.html -from .. import loader +from .. import loader, translations import logging from aiogram.types import CallbackQuery from random import choice @@ -29,11 +29,15 @@ TEXT = """🌘🇬🇧 Hello. You've just installed Hikka userbot. ❓ Need help? Feel free to join our support chat. We help everyone. -📼 Official modules sources: +📼 Official modules sources: ▫️ @hikarimods ▫️ @hikarimods_database ▫️ .dlmod +✅ Trusted modules' developers: +▫️ @morisummermods +▫️ @cakestwix_mods + """ @@ -41,11 +45,14 @@ TEXT_RU = """🌘🇷🇺 Привет. Твой юзербот Hikka< ❓ Нужна помощь? Вступай в наш чат поддержки. Мы помогаем всем. -📼 Официальные источники модулей: +📼 Официальные источники модулей: ▫️ @hikarimods ▫️ @hikarimods_database ▫️ .dlmod +✅ Доверенные разработчики модулей: +▫️ @morisummermods +▫️ @cakestwix_mods """ if "OKTETO" in os.environ: @@ -61,6 +68,7 @@ class QuickstartMod(loader.Module): async def client_ready(self, client, db): self._me = (await client.get_me()).id + self._db = db mark = self.inline._generate_markup( [ @@ -92,6 +100,11 @@ class QuickstartMod(loader.Module): ] ) + self._db.set(translations.__name__, "lang", "ru") + self._db.set(translations.__name__, "pack", "ru") + await self.translator.init() + await call.answer("🇷🇺 Язык сохранен!") + await self.inline.bot.edit_message_caption( chat_id=call.message.chat.id, message_id=call.message.message_id, @@ -107,6 +120,11 @@ class QuickstartMod(loader.Module): ] ) + self._db.set(translations.__name__, "lang", "en") + self._db.set(translations.__name__, "pack", None) + await self.translator.init() + await call.answer("🇬🇧 Language saved!") + await self.inline.bot.edit_message_caption( chat_id=call.message.chat.id, message_id=call.message.message_id, diff --git a/hikka/version.py b/hikka/version.py index 6d14177..fc7f1e9 100644 --- a/hikka/version.py +++ b/hikka/version.py @@ -1 +1 @@ -__version__ = (1, 1, 7) +__version__ = (1, 1, 8)