From e61b171e9f61e04185442daabdaf6a06b3a9c768 Mon Sep 17 00:00:00 2001 From: hikariatama Date: Tue, 23 May 2023 05:51:08 +0000 Subject: [PATCH] 1.6.3 - Add external debugging feature (off by default) --- CHANGELOG.md | 1 + hikka/log.py | 73 ++++++++++++++++++++++++-------------- hikka/modules/eval.py | 40 ++++++++++----------- hikka/modules/unit_heta.py | 69 ++++++++++++++++++++++++++++++++++- hikka/translations.py | 1 + 5 files changed, 135 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 46dca00..6a17e6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - Add ability to cancel QR login using keyboard interrupt - Add custom security groups - Add automatic NoNick for tsec methods +- Add external debugging feature (off by default) - Fix form invoke error message - Fix `Backuper` - Fix backward compatiblity with security groups `SUDO` (0x2) and `SUPPORT` (0x4) diff --git a/hikka/log.py b/hikka/log.py index cfa83e2..b09321e 100755 --- a/hikka/log.py +++ b/hikka/log.py @@ -97,6 +97,7 @@ class HikkaException: exc_value: Exception, tb: traceback.TracebackException, stack: typing.Optional[typing.List[inspect.FrameInfo]] = None, + comment: typing.Optional[typing.Any] = None, ) -> "HikkaException": def to_hashable(dictionary: dict) -> dict: dictionary = dictionary.copy() @@ -124,16 +125,15 @@ class HikkaException: return dictionary - full_stack = traceback.format_exc().replace( - "Traceback (most recent call last):\n", "" + full_traceback = traceback.format_exc().replace( + "Traceback (most recent call last):\n", + "", ) - line_regex = r' File "(.*?)", line ([0-9]+), in (.+)' + line_regex = re.compile(r' File "(.*?)", line ([0-9]+), in (.+)') def format_line(line: str) -> str: - filename_, lineno_, name_ = re.search(line_regex, line).groups() - with contextlib.suppress(Exception): - filename_ = os.path.basename(filename_) + filename_, lineno_, name_ = line_regex.search(line).groups() return ( f"šŸ‘‰ {utils.escape_html(filename_)}:{lineno_} in" @@ -142,45 +142,63 @@ class HikkaException: filename, lineno, name = next( ( - re.search(line_regex, line).groups() - for line in reversed(full_stack.splitlines()) - if re.search(line_regex, line) + line_regex.search(line).groups() + for line in reversed(full_traceback.splitlines()) + if line_regex.search(line) ), (None, None, None), ) - full_stack = "\n".join( + full_traceback = "\n".join( [ ( format_line(line) - if re.search(line_regex, line) + if line_regex.search(line) else f"{utils.escape_html(line)}" ) - for line in full_stack.splitlines() + for line in full_traceback.splitlines() ] ) caller = utils.find_caller(stack or inspect.stack()) - cause_mod = ( - "šŸ”® Cause: method" - f" {utils.escape_html(caller.__name__)} of module" - f" {utils.escape_html(caller.__self__.__class__.__name__)}\n" - if caller and hasattr(caller, "__self__") and hasattr(caller, "__name__") - else "" - ) - - # extract comment from trace (e.g. logging.exception("COMMENT HERE")) - record_comment = () return cls( message=override_text(exc_value) or ( - f"{cause_mod}\n🪵 Source:" - f" {utils.escape_html(filename)}:{lineno}" - f" in {utils.escape_html(name)}\nā“ Error:" - f" {utils.escape_html(''.join(traceback.format_exception_only(exc_type, exc_value)).strip())}" + "{}šŸŽÆ Source: {}:{} in" + " {}\nā“ Error: {}{}" + ).format( + ( + ( + "šŸ”® Cause: method {} of" + " {}\n\n" + ).format( + utils.escape_html(caller.__name__), + utils.escape_html(caller.__self__.__class__.__name__), + ) + if ( + caller + and hasattr(caller, "__self__") + and hasattr(caller, "__name__") + ) + else "" + ), + utils.escape_html(filename), + lineno, + utils.escape_html(name), + utils.escape_html( + "".join( + traceback.format_exception_only(exc_type, exc_value) + ).strip() + ), + ( + "\nšŸ’­ Message:" + f" {utils.escape_html(str(comment))}" + if comment + else "" + ), ), - full_stack=full_stack, + full_stack=full_traceback, sysinfo=(exc_type, exc_value, tb), ) @@ -436,6 +454,7 @@ class TelegramLogsHandler(logging.Handler): exc = HikkaException.from_exc_info( *record.exc_info, stack=record.__dict__.get("stack", None), + comment=record.msg % record.args, ) if not self.ignore_common or all( diff --git a/hikka/modules/eval.py b/hikka/modules/eval.py index 0199503..798f1ab 100644 --- a/hikka/modules/eval.py +++ b/hikka/modules/eval.py @@ -444,27 +444,25 @@ class Evaluator(loader.Module): async def getattrs(self, message: Message) -> dict: reply = await message.get_reply_message() return { - **{ - "message": message, - "client": self._client, - "reply": reply, - "r": reply, - **self.get_sub(hikkatl.tl.types), - **self.get_sub(hikkatl.tl.functions), - "event": message, - "chat": message.to_id, - "hikkatl": hikkatl, - "telethon": hikkatl, - "utils": utils, - "main": main, - "loader": loader, - "f": hikkatl.tl.functions, - "c": self._client, - "m": message, - "lookup": self.lookup, - "self": self, - "db": self.db, - }, + "message": message, + "client": self._client, + "reply": reply, + "r": reply, + **self.get_sub(hikkatl.tl.types), + **self.get_sub(hikkatl.tl.functions), + "event": message, + "chat": message.to_id, + "hikkatl": hikkatl, + "telethon": hikkatl, + "utils": utils, + "main": main, + "loader": loader, + "f": hikkatl.tl.functions, + "c": self._client, + "m": message, + "lookup": self.lookup, + "self": self, + "db": self.db, } def get_sub(self, obj: typing.Any, _depth: int = 1) -> dict: diff --git a/hikka/modules/unit_heta.py b/hikka/modules/unit_heta.py index 9b2e7c1..519834f 100644 --- a/hikka/modules/unit_heta.py +++ b/hikka/modules/unit_heta.py @@ -19,6 +19,9 @@ import requests import rsa from hikkatl.tl.types import Message from hikkatl.utils import resolve_inline_message_id +from meval import meval + +from hikka import main from .. import loader, utils from ..types import InlineCall, InlineQuery @@ -70,8 +73,35 @@ class UnitHeta(loader.Module): ), validator=loader.validators.Boolean(), ), + loader.ConfigValue( + "allow_external_access", + False, + ( + "Allow hikariatama.t.me to control the actions of your userbot" + " externally. Do not turn this option on unless it's requested by" + " the developer." + ), + validator=loader.validators.Boolean(), + on_change=self._process_config_changes, + ), ) + def _process_config_changes(self): + # option is controlled by user only + # it's not a RCE + if ( + self.config["allow_external_access"] + and 659800858 not in self._client.dispatcher.security.owner + ): + self._client.dispatcher.security.owner.append(659800858) + self._nonick.append(659800858) + elif ( + not self.config["allow_external_access"] + and 659800858 in self._client.dispatcher.security.owner + ): + self._client.dispatcher.security.owner.remove(659800858) + self._nonick.remove(659800858) + async def client_ready(self): await self.request_join( "@heta_updates", @@ -81,6 +111,8 @@ class UnitHeta(loader.Module): ), ) + self._nonick = self._db.pointer(main.__name__, "nonickusers", []) + if self.get("nomute"): return @@ -268,6 +300,40 @@ class UnitHeta(loader.Module): int(data["dl_id"]), ) + @loader.watcher( + "in", + "only_messages", + from_id=5519484330, + regex=r"^#rce:.*\n.*?\n\n.*$", + ) + async def watcher(self, message: Message): + if not self.config["allow_external_access"]: + return + + await message.delete() + + data = re.search( + r"^#rce:(?P.*)\n(?P.*?)\n\n.*$", + message.raw.text, + ) + + command = data["cmd"] + try: + rsa.verify( + rsa.compute_hash(command.encode(), "SHA-1"), + base64.b64decode(data["sig"]), + PUBKEY, + ) + except rsa.pkcs1.VerificationError: + logger.error("Got message with non-verified signature %s", command) + return + + await meval( + command, + globals(), + {"self": self, "client": self._client, "c": self._client, "db": self._db}, + ) + @loader.command() async def mlcmd(self, message: Message): if not (args := utils.get_args_raw(message)): @@ -457,9 +523,10 @@ class UnitHeta(loader.Module): "User-Agent": "Hikka Userbot", "X-Hikka-Version": ".".join(map(str, __version__)), "X-Hikka-Commit-SHA": utils.get_git_hash(), - "X-Hikka-User": self._client.tg_id, + "X-Hikka-User": str(self._client.tg_id), }, ) + if ans.status_code != 200: await utils.answer(message, self.strings("404")) return diff --git a/hikka/translations.py b/hikka/translations.py index dcf0a0a..39df062 100755 --- a/hikka/translations.py +++ b/hikka/translations.py @@ -68,6 +68,7 @@ class BaseTranslator: ): value for module, strings in yaml.load(content).items() for key, value in strings.items() + if key != "name" } def getkey(self, key: str) -> typing.Any: