1.1.8: Add `on_dlmod` hook, fix translations, fix okteto pinger and some minor stuff

More info in t.me/hikka_ub
pull/1/head
Hikari 2022-04-25 13:10:34 +00:00
parent 185457140d
commit f56a380fc6
No known key found for this signature in database
GPG Key ID: 5FA52ACBB2AD964D
9 changed files with 156 additions and 63 deletions

6
.gitignore vendored
View File

@ -154,4 +154,8 @@ config.json
*.jpg
*.jpeg
*.webp
*.webm
*.webm
*.tgs
*.mp4
*.mp3
*.ogg

View File

@ -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"""

View File

@ -379,15 +379,15 @@
"hikka.modules.animefy._cmd_doc_animefy": "<reply> - Нарисовать фото",
"hikka.modules.animefy._cls_doc": "Делает из реальных людей мультяшек",
"hikka.modules.temp_chat.chat_is_being_removed": "<b>🚫 Чат удаляется...</b>",
"hikka.modules.temp_chat.args": "<b>Капец с аргументами: </b><code>.help TempChat</code>",
"hikka.modules.temp_chat.chat_not_found": "<b>Чат не найден</b>",
"hikka.modules.temp_chat.tmp_cancelled": "<b>Чат </b><code>{}</code><b> будет жить вечно!</b>",
"hikka.modules.temp_chat.delete_error": "<b>Произошла ошибка удаления чата. Сделай это вручную.</b>",
"hikka.modules.temp_chat.args": "🚫 <b>Капец с аргументами: </b><code>.help TempChat</code>",
"hikka.modules.temp_chat.chat_not_found": "🚫 <b>Чат не найден</b>",
"hikka.modules.temp_chat.tmp_cancelled": "🚫 <b>Чат </b><code>{}</code><b> будет жить вечно!</b>",
"hikka.modules.temp_chat.delete_error": "🚫 <b>Произошла ошибка удаления чата. Сделай это вручную.</b>",
"hikka.modules.temp_chat.temp_chat_header": "<b>⚠️ Этот чат</b> (<code>{}</code>)<b> является временным и будет удален {}.</b>",
"hikka.modules.temp_chat.chat_created": "<b>Чат создан</b>",
"hikka.modules.temp_chat.delete_error_me": "<b>Ошибка удаления чата {}</b>",
"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": "<b><a href=\"{}\">Чат</a> создан</b>",
"hikka.modules.temp_chat.delete_error_me": "🚫 <b>Ошибка удаления чата {}</b>",
"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": "<chat_id> <новое время> - Изменить время жизни чата",
@ -809,5 +809,12 @@
"hikka.modules.hikka_backup.period": "⌚️ <b>Приветики! Я Асуна</b> - твой менеджер резервного копирования. Пожалуйста, выбери периодичность резервных копий базы данных Hikka",
"hikka.modules.hikka_backup.saved": "✅ Периодичность сохранена! Ее можно изменить с помощью .set_backup_period",
"hikka.modules.hikka_backup.never": "✅ Я не буду делать автоматические резервные копии. Можно отменить используя .set_backup_period",
"hikka.modules.hikka_backup.invalid_args": "🚫 <b>Укажи правильную периодичность в часах, или `0` для отключения</b>"
"hikka.modules.hikka_backup.invalid_args": "🚫 <b>Укажи правильную периодичность в часах, или `0` для отключения</b>",
"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": "Отправить информацию о юзерботе"
}

View File

@ -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__", "") == "<core>":
@ -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)

View File

@ -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 </b><code>.update</code><b>"
@ -52,10 +57,10 @@ class HikkaInfoMod(loader.Module):
return (
"<b>🌘 Hikka Userbot</b>\n"
f'<b>🤴 Owner: <a href="tg://user?id={self._me.id}">{utils.escape_html(get_display_name(self._me))}</a></b>\n\n'
f"<b>🔮 Version: </b><i>{'.'.join(list(map(str, list(main.__version__))))}</i>\n"
f"<b>🧱 Build: </b><a href=\"https://github.com/hikariatama/Hikka/commit/{ver}\">{ver[:8] or 'Unknown'}</a>\n"
f"<b>📼 Command prefix: </b>«<code>{utils.escape_html((self._db.get(main.__name__, 'command_prefix', False) or '.')[0])}</code>»\n"
f'<b>🤴 {self.strings("owner")}: <a href="tg://user?id={self._me.id}">{utils.escape_html(get_display_name(self._me))}</a></b>\n\n'
f"<b>🔮 {self.strings('version')}: </b><i>{'.'.join(list(map(str, list(main.__version__))))}</i>\n"
f"<b>🧱 {self.strings('build')}: </b><a href=\"https://github.com/hikariatama/Hikka/commit/{ver}\">{ver[:8]}</a>\n\n"
f"<b>📼 {self.strings('prefix')}: </b>«<code>{utils.escape_html(self.get_prefix())}</code>»\n"
f"<b>{upd}</b>\n"
f"<b>{utils.get_named_platform()}</b>\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,

View File

@ -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"🚫 <b>{utils.escape_html(str(e))}</b>")
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"🚫 <b>{utils.escape_html(str(e))}</b>")
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"🚫 <b>{utils.escape_html(str(e))}</b>")
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)

View File

@ -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
):

View File

@ -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 = """🌘🇬🇧 <b>Hello.</b> You've just installed <b>Hikka</b> userbot.
<b>Need help?</b> Feel free to join our support chat. We help <b>everyone</b>.
📼 <b>Official modules sources: </b>
📼 <b>Official modules sources:</b>
@hikarimods
@hikarimods_database
<code>.dlmod</code>
<b>Trusted modules' developers:</b>
@morisummermods
@cakestwix_mods
"""
@ -41,11 +45,14 @@ TEXT_RU = """🌘🇷🇺 <b>Привет.</b> Твой юзербот <b>Hikka<
<b>Нужна помощь?</b> Вступай в наш чат поддержки. Мы помогаем <b>всем</b>.
📼 <b>Официальные источники модулей: </b>
📼 <b>Официальные источники модулей:</b>
@hikarimods
@hikarimods_database
<code>.dlmod</code>
<b>Доверенные разработчики модулей:</b>
@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,

View File

@ -1 +1 @@
__version__ = (1, 1, 7)
__version__ = (1, 1, 8)