import logging
from re import search, findall
from typing import Dict, Callable, Union
from telegram import InlineKeyboardMarkup, ParseMode
from MateWrapper.context import MATEVarGetter
from MateWrapper.generics import TelegramFunctionBlueprint, TelegramEvent
logging.basicConfig(
level=logging.ERROR,
format='%(asctime)s - %(levelname)s - %(message)s'
)
log = logging.getLogger()
log.setLevel(logging.INFO)
FORMATTING_REGEX = r"\{([a-zA-Z_:.]+)\}"
# Formatting ----
def _format_message(message_text: str, variables: Dict[str, MATEVarGetter], event: TelegramEvent) -> str:
result_message = message_text
for var in variables:
getter = variables[var]
result_message = result_message.replace(f"{{{var}}}", str(getter.logic(getter, event)))
return result_message
def _has_formatting(message_text: str) -> bool:
return search(FORMATTING_REGEX, message_text) is not None
def _get_variable_getters(message_text: str) -> Dict[str, MATEVarGetter]:
result: Dict[str, MATEVarGetter] = {}
matches = findall(FORMATTING_REGEX, message_text)
for variable in matches:
if variable not in result:
result[variable] = MATEVarGetter(variable)
return result
def _autoformat_text(text_to_send: str, event: TelegramEvent):
if _has_formatting(text_to_send):
return _format_message(text_to_send, _get_variable_getters(text_to_send), event)
return text_to_send
[docs]class Prompt(TelegramFunctionBlueprint):
"""
Class to handle sending prompts via Telegram.
Automatically optimizes itself depending on the text & keyboard you pass in.
"""
[docs] def __init__(
self,
text: Union[str, Callable[[TelegramEvent], str]],
keyboard: Union[InlineKeyboardMarkup, Callable[[TelegramEvent], InlineKeyboardMarkup]] = None,
format_text: bool or None = None,
next_state: object or None = None,
delete_last_message: bool = False,
use_markdown: bool = False,
use_web_preview: bool = False
):
"""
:param Union[str, Callable[[TelegramEvent], str]] text:
text of the message to send or the function used to generate it.
If '{something}' is found in the text then the TelegramWrapper will try to format the text replacing all
'{something}' instances with whatever context.chat_data['something'] contains
see MATEVarHandler for more info on special characters in format string
Can be callable, the logic defined above still applies, see the format_text parameter for more info.
:param Union[InlineKeyboardMarkup, Callable[[TelegramEvent], InlineKeyboardMarkup]] keyboard:
the inline keyboard to send or the function that will generate the inline keyboard to send.
Leave as None to not send any keyboard.
:param bool or None format_text:
manually set if the text should be formatted, by default the wrapper will automatically determine if
the prompt needs to be formatted.
It is only taken into account if the text is of type 'callable', that is to say if the text is
programmatically generated by a function and is not known beforehand by the program.
:param object or None next_state:
the return value of the function, used to change state in conversation handlers.
Leave at None to not change state.
:param bool delete_last_message:
if true it will delete either the last message sent by the user or the keyboard that generated the update.
:param bool use_markdown:
tells telegram to format the message using markdown V2. By default it's false.
:param bool use_web_preview:
tells telegram to render the web preview of the first link found in the message if true.
By default it's false.
"""
self.text = text
self.keyboard = keyboard
self.next_state = next_state
self.delete_last_message = delete_last_message
self.parse_mode = ParseMode.MARKDOWN_V2 if use_markdown else None
self.web_preview = not use_web_preview
if callable(text):
if format_text is None:
if callable(keyboard):
self.behaviour = self._call_and_call_autoformat
else:
self.behaviour = self._call_and_send_autoformat
else:
if format_text:
if callable(keyboard):
self.behaviour = self._call_and_call_format
else:
self.behaviour = self._call_and_send_format
else:
if callable(keyboard):
self.behaviour = self._call_and_call_noformat
else:
self.behaviour = self._call_and_send_noformat
else:
if _has_formatting(text):
self.variables = _get_variable_getters(text)
if callable(keyboard):
self.behaviour = self._format_and_call
else:
self.behaviour = self._format_and_send
else:
if callable(keyboard):
self.behaviour = self._send_and_call
else:
self.behaviour = self._send_and_send
# print(self.behaviour.__name__, type(keyboard))
def _format_and_call(self, text: str, keyboard_func: Callable[[TelegramEvent], InlineKeyboardMarkup], event: TelegramEvent):
event.context.bot.send_message(
chat_id=event.chat_id,
text=_format_message(text, self.variables, event),
reply_markup=keyboard_func(event),
parse_mode=self.parse_mode,
disable_web_page_preview=self.web_preview
)
def _format_and_send(self, text: str, keyboard: InlineKeyboardMarkup, event: TelegramEvent):
event.context.bot.send_message(
chat_id=event.chat_id,
text=_format_message(text, self.variables, event),
reply_markup=keyboard,
parse_mode=self.parse_mode,
disable_web_page_preview=self.web_preview
)
# No formatting behaviours ----
def _send_and_call(self, text: str, keyboard_func: Callable[[TelegramEvent], InlineKeyboardMarkup], event: TelegramEvent):
event.context.bot.send_message(
chat_id=event.chat_id,
text=text,
reply_markup=keyboard_func(event),
parse_mode=self.parse_mode,
disable_web_page_preview=self.web_preview
)
def _send_and_send(self, text: str, keyboard: InlineKeyboardMarkup, event: TelegramEvent):
event.context.bot.send_message(
chat_id=event.chat_id,
text=text,
reply_markup=keyboard,
parse_mode=self.parse_mode,
disable_web_page_preview=self.web_preview
)
# text is callable
def _call_and_call_autoformat(self, text_func: Callable[[TelegramEvent], str], keyboard_func: Callable[[TelegramEvent], InlineKeyboardMarkup], event: TelegramEvent):
event.context.bot.send_message(
chat_id=event.chat_id,
text=_autoformat_text(text_func(event), event),
reply_markup=keyboard_func(event),
parse_mode=self.parse_mode,
disable_web_page_preview=self.web_preview
)
def _call_and_call_noformat(self, text_func: Callable[[TelegramEvent], str], keyboard_func: Callable[[TelegramEvent], InlineKeyboardMarkup], event: TelegramEvent):
event.context.bot.send_message(
chat_id=event.chat_id,
text=text_func(event),
reply_markup=keyboard_func(event),
parse_mode=self.parse_mode,
disable_web_page_preview=self.web_preview
)
def _call_and_call_format(self, text_func: Callable[[TelegramEvent], str], keyboard_func: Callable[[TelegramEvent], InlineKeyboardMarkup], event: TelegramEvent):
text_to_send = text_func(event)
event.context.bot.send_message(
chat_id=event.chat_id,
text=_format_message(text_to_send, _get_variable_getters(text_to_send), event),
reply_markup=keyboard_func(event),
parse_mode=self.parse_mode,
disable_web_page_preview=self.web_preview
)
def _call_and_send_autoformat(self, text_func: Callable[[TelegramEvent], str], keyboard: InlineKeyboardMarkup, event: TelegramEvent):
event.context.bot.send_message(
chat_id=event.chat_id,
text=_autoformat_text(text_func(event), event),
reply_markup=keyboard,
parse_mode=self.parse_mode,
disable_web_page_preview=self.web_preview
)
def _call_and_send_noformat(self, text_func: Callable[[TelegramEvent], str], keyboard: InlineKeyboardMarkup, event: TelegramEvent):
event.context.bot.send_message(
chat_id=event.chat_id,
text=text_func(event),
reply_markup=keyboard,
parse_mode=self.parse_mode,
disable_web_page_preview=self.web_preview
)
def _call_and_send_format(self, text_func: Callable[[TelegramEvent], str], keyboard: InlineKeyboardMarkup, event: TelegramEvent):
text_to_send = text_func(event)
event.context.bot.send_message(
chat_id=event.chat_id,
text=_format_message(text_to_send, _get_variable_getters(text_to_send), event),
reply_markup=keyboard,
parse_mode=self.parse_mode,
disable_web_page_preview=self.web_preview
)
[docs] def logic(self, event: TelegramEvent):
if self.delete_last_message:
try:
try:
last_message_id = event.update.message.message_id
except AttributeError:
last_message_id = event.update.callback_query.message.message_id
event.context.bot.delete_message(
event.chat_id,
last_message_id
)
except Exception as e:
log.error(f"error while trying to delete message: {e}, update: {event.update.to_dict()}")
self.behaviour(self.text, self.keyboard, event)
return self.next_state