Heroku/hikka/inline/utils.py

165 lines
5.7 KiB
Python

from .types import InlineUnit
from .. import utils
from aiogram.types import (
InlineKeyboardMarkup,
InlineKeyboardButton,
)
import logging
from typing import Union
from types import FunctionType
from .._types import Module
import inspect
import re
URL_REGEX = re.compile(
r"^(?:http|ftp)s?://" # http:// or https://
r"(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|" # domain...
r"localhost|" # localhost...
r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})" # ...or ip
r"(?::\d+)?" # optional port
r"(?:/?|[/?]\S+)$",
re.IGNORECASE,
)
logger = logging.getLogger(__name__)
class Utils(InlineUnit):
def _generate_markup(
self,
form_uid: Union[str, list],
/,
) -> Union[None, InlineKeyboardMarkup]:
"""Generate markup for form or list of `dict`s"""
if not form_uid:
return None
markup = InlineKeyboardMarkup()
map_ = (
self._forms[form_uid]["buttons"] if isinstance(form_uid, str) else form_uid
)
map_ = self._normalize_markup(map_)
for row in map_:
for button in row:
if not isinstance(button, dict):
logger.error(f"Button {button} is not a `dict`, but `{type(button)}` in {map_}") # fmt: skip
return None
if "callback" in button and "_callback_data" not in button:
button["_callback_data"] = utils.rand(30)
if "input" in button and "_switch_query" not in button:
button["_switch_query"] = utils.rand(10)
for row in map_:
line = []
for button in row:
try:
if "url" in button:
if not re.match(URL_REGEX, button["url"]):
logger.warning(
"Button have not been added to form, "
"because its url is invalid"
)
continue
line += [
InlineKeyboardButton(
button["text"],
url=button["url"],
)
]
elif "callback" in button:
line += [
InlineKeyboardButton(
button["text"],
callback_data=button["_callback_data"],
)
]
elif "input" in button:
line += [
InlineKeyboardButton(
button["text"],
switch_inline_query_current_chat=button["_switch_query"] + " ", # fmt: skip
)
]
elif "data" in button:
line += [
InlineKeyboardButton(
button["text"],
callback_data=button["data"],
)
]
else:
logger.warning(
"Button have not been added to "
"form, because it is not structured "
f"properly. {button}"
)
except KeyError:
logger.exception(
"Error while forming markup! Probably, you "
"passed wrong type combination for button. "
"Contact developer of module."
)
return False
markup.row(*line)
return markup
async def check_inline_security(
self,
*,
func: FunctionType,
user: int,
) -> bool:
"""Checks if user with id `user` is allowed to run function `func`"""
return await self._client.dispatcher.security.check(
func=func,
user=user,
message=None,
)
def _find_caller_sec_map(self) -> Union[FunctionType, None]:
try:
for stack_entry in inspect.stack():
if (
hasattr(stack_entry, "function")
and (
stack_entry.function.endswith("cmd")
or stack_entry.function.endswith("_inline_handler")
)
):
logger.debug(f"Found caller: {stack_entry.function}")
return next(
lambda: self._client.dispatcher.security.get_flags(
getattr(
cls_,
stack_entry.function,
),
)
for name, cls_ in stack_entry.frame.f_globals.items()
if name.endswith("Mod") and issubclass(cls_, Module)
)
except Exception:
logger.debug("Can't parse security mask in form", exc_info=True)
return None
def _normalize_markup(self, reply_markup: Union[dict, list]) -> list:
if isinstance(reply_markup, dict):
return [[reply_markup]]
if isinstance(reply_markup, list) and any(
isinstance(i, dict) for i in reply_markup
):
return [reply_markup]
return reply_markup