mirror of
https://github.com/RYGhub/royalnet.git
synced 2024-11-23 19:44:20 +00:00
Improve mm code
This commit is contained in:
parent
3fb7dec8fe
commit
6583823c23
4 changed files with 367 additions and 295 deletions
|
@ -29,7 +29,7 @@ class TelegramBot(GenericBot):
|
||||||
def _init_client(self):
|
def _init_client(self):
|
||||||
"""Create the :py:class:`telegram.Bot`, and set the starting offset."""
|
"""Create the :py:class:`telegram.Bot`, and set the starting offset."""
|
||||||
# https://github.com/python-telegram-bot/python-telegram-bot/issues/341
|
# https://github.com/python-telegram-bot/python-telegram-bot/issues/341
|
||||||
request = telegram.utils.request.Request(5)
|
request = telegram.utils.request.Request(20, read_timeout=15)
|
||||||
self.client = telegram.Bot(self._telegram_config.token, request=request)
|
self.client = telegram.Bot(self._telegram_config.token, request=request)
|
||||||
self._offset: int = -100
|
self._offset: int = -100
|
||||||
|
|
||||||
|
@ -62,7 +62,8 @@ class TelegramBot(GenericBot):
|
||||||
data.update = update
|
data.update = update
|
||||||
|
|
||||||
async def reply(data, text: str):
|
async def reply(data, text: str):
|
||||||
await asyncify(data.update.effective_chat.send_message, telegram_escape(text),
|
await TelegramBot.safe_api_call(data.update.effective_chat.send_message,
|
||||||
|
telegram_escape(text),
|
||||||
parse_mode="HTML",
|
parse_mode="HTML",
|
||||||
disable_web_page_preview=True)
|
disable_web_page_preview=True)
|
||||||
|
|
||||||
|
@ -92,7 +93,7 @@ class TelegramBot(GenericBot):
|
||||||
press_id = uuid.uuid4()
|
press_id = uuid.uuid4()
|
||||||
tg_keyboard.append([telegram.InlineKeyboardButton(key, callback_data=str(press_id))])
|
tg_keyboard.append([telegram.InlineKeyboardButton(key, callback_data=str(press_id))])
|
||||||
data.interface.register_keyboard_key(key_name=str(press_id), callback=keyboard[key])
|
data.interface.register_keyboard_key(key_name=str(press_id), callback=keyboard[key])
|
||||||
await asyncify(data.update.effective_chat.send_message,
|
await TelegramBot.safe_api_call(data.update.effective_chat.send_message,
|
||||||
telegram_escape(text),
|
telegram_escape(text),
|
||||||
reply_markup=telegram.InlineKeyboardMarkup(tg_keyboard),
|
reply_markup=telegram.InlineKeyboardMarkup(tg_keyboard),
|
||||||
parse_mode="HTML",
|
parse_mode="HTML",
|
||||||
|
@ -113,6 +114,31 @@ class TelegramBot(GenericBot):
|
||||||
self._telegram_config = telegram_config
|
self._telegram_config = telegram_config
|
||||||
self._init_client()
|
self._init_client()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def safe_api_call(f: typing.Callable, *args, **kwargs) -> typing.Optional:
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
return await asyncify(f, *args, **kwargs)
|
||||||
|
except telegram.error.TimedOut as error:
|
||||||
|
log.debug(f"Timed out during {f.__qualname__} (retrying in 15s): {error}")
|
||||||
|
await asyncio.sleep(15)
|
||||||
|
continue
|
||||||
|
except telegram.error.NetworkError as error:
|
||||||
|
log.warning(f"Network error during {f.__qualname__} (retrying in 15s): {error}")
|
||||||
|
await asyncio.sleep(15)
|
||||||
|
continue
|
||||||
|
except telegram.error.Unauthorized as error:
|
||||||
|
log.info(f"Unauthorized to run {f.__qualname__} (skipping): {error}")
|
||||||
|
break
|
||||||
|
except telegram.error.RetryAfter as error:
|
||||||
|
log.warning(f"Rate limited during {f.__qualname__} (retrying in 15s): {error}")
|
||||||
|
await asyncio.sleep(15)
|
||||||
|
except telegram.error.TelegramError as error:
|
||||||
|
log.error(f"{error.__class__.__qualname__} during {f} (skipping): {error}")
|
||||||
|
sentry_sdk.capture_exception(error)
|
||||||
|
break
|
||||||
|
return None
|
||||||
|
|
||||||
async def _handle_update(self, update: telegram.Update):
|
async def _handle_update(self, update: telegram.Update):
|
||||||
# Skip non-message updates
|
# Skip non-message updates
|
||||||
if update.message is not None:
|
if update.message is not None:
|
||||||
|
@ -136,7 +162,7 @@ class TelegramBot(GenericBot):
|
||||||
command_text, *parameters = text.split(" ")
|
command_text, *parameters = text.split(" ")
|
||||||
command_name = command_text.replace(f"@{self.client.username}", "").lower()
|
command_name = command_text.replace(f"@{self.client.username}", "").lower()
|
||||||
# Send a typing notification
|
# Send a typing notification
|
||||||
update.message.chat.send_action(telegram.ChatAction.TYPING)
|
await self.safe_api_call(update.message.chat.send_action, telegram.ChatAction.TYPING)
|
||||||
# Find the command
|
# Find the command
|
||||||
try:
|
try:
|
||||||
command = self.commands[command_name]
|
command = self.commands[command_name]
|
||||||
|
@ -166,40 +192,31 @@ class TelegramBot(GenericBot):
|
||||||
callback = command.interface.keys_callbacks[query.data]
|
callback = command.interface.keys_callbacks[query.data]
|
||||||
break
|
break
|
||||||
if callback is None:
|
if callback is None:
|
||||||
await asyncify(source.edit_reply_markup, reply_markup=None)
|
await self.safe_api_call(source.edit_reply_markup, reply_markup=None)
|
||||||
await asyncify(query.answer, text="⛔️ This keyboard has expired.")
|
await self.safe_api_call(query.answer, text="⛔️ This keyboard has expired.")
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
response = await callback(data=self._Data(interface=command.interface, update=update))
|
response = await callback(data=self._Data(interface=command.interface, update=update))
|
||||||
except KeyboardExpiredError:
|
except KeyboardExpiredError:
|
||||||
# FIXME: May cause a memory leak, as keys are not deleted after use
|
# FIXME: May cause a memory leak, as keys are not deleted after use
|
||||||
await asyncify(source.edit_reply_markup, reply_markup=None)
|
await self.safe_api_call(source.edit_reply_markup, reply_markup=None)
|
||||||
await asyncify(query.answer, text="⛔️ This keyboard has expired.")
|
await self.safe_api_call(query.answer, text="⛔️ This keyboard has expired.")
|
||||||
return
|
return
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_text = f"⛔️ {e.__class__.__name__}\n"
|
error_text = f"⛔️ {e.__class__.__name__}\n"
|
||||||
error_text += '\n'.join(e.args)
|
error_text += '\n'.join(e.args)
|
||||||
await asyncify(query.answer, text=error_text)
|
await self.safe_api_call(query.answer, text=error_text)
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
await asyncify(query.answer, text=response)
|
await self.safe_api_call(query.answer, text=response)
|
||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
while True:
|
while True:
|
||||||
# Get the latest 100 updates
|
# Get the latest 100 updates
|
||||||
try:
|
last_updates: typing.List[telegram.Update] = await self.safe_api_call(self.client.get_updates,
|
||||||
last_updates: typing.List[telegram.Update] = await asyncify(self.client.get_updates,
|
|
||||||
offset=self._offset,
|
offset=self._offset,
|
||||||
timeout=30,
|
timeout=30,
|
||||||
read_latency=5.0)
|
read_latency=5.0)
|
||||||
except telegram.error.TimedOut as error:
|
|
||||||
log.debug("getUpdates timed out")
|
|
||||||
continue
|
|
||||||
except Exception as error:
|
|
||||||
log.error(f"Error while getting updates: {error.__class__.__name__} {error.args}")
|
|
||||||
sentry_sdk.capture_exception(error)
|
|
||||||
await asyncio.sleep(5)
|
|
||||||
continue
|
|
||||||
# Handle updates
|
# Handle updates
|
||||||
for update in last_updates:
|
for update in last_updates:
|
||||||
# noinspection PyAsyncCall
|
# noinspection PyAsyncCall
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import typing
|
|
||||||
import datetime
|
import datetime
|
||||||
import dateparser
|
import dateparser
|
||||||
import os
|
import os
|
||||||
|
@ -24,59 +23,322 @@ class MmCommand(Command):
|
||||||
|
|
||||||
require_alchemy_tables = {MMEvent, MMDecision, MMResponse}
|
require_alchemy_tables = {MMEvent, MMDecision, MMResponse}
|
||||||
|
|
||||||
@staticmethod
|
_cycle_duration = 5
|
||||||
def _decision_string(mmevent: MMEvent) -> str:
|
|
||||||
return f"⚫️ Hai detto che forse parteciperai a [b]{mmevent.title}[/b] alle [b]{mmevent.datetime.strftime('%H:%M')}[/b].\n" \
|
|
||||||
f"Confermi di volerci essere?"
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _decision_keyboard(mmevent: MMEvent) -> telegram.InlineKeyboardMarkup:
|
def _main_keyboard(mmevent: MMEvent) -> typing.Optional[telegram.InlineKeyboardMarkup]:
|
||||||
|
if mmevent.state == "WAITING":
|
||||||
|
return telegram.InlineKeyboardMarkup([
|
||||||
|
[telegram.InlineKeyboardButton("🔵 Ci sarò!", callback_data=f"mm_{mmevent.mmid}_d_YES")],
|
||||||
|
[telegram.InlineKeyboardButton("⚫️ Forse...", callback_data=f"mm_{mmevent.mmid}_d_MAYBE")],
|
||||||
|
[telegram.InlineKeyboardButton("🔴 Non mi interessa.", callback_data=f"mm_{mmevent.mmid}_d_NO")]
|
||||||
|
])
|
||||||
|
elif mmevent.state == "DECISION":
|
||||||
return telegram.InlineKeyboardMarkup([
|
return telegram.InlineKeyboardMarkup([
|
||||||
[telegram.InlineKeyboardButton("🔵 Ci sarò!", callback_data=f"mm_{mmevent.mmid}_d_YES"),
|
[telegram.InlineKeyboardButton("🔵 Ci sarò!", callback_data=f"mm_{mmevent.mmid}_d_YES"),
|
||||||
telegram.InlineKeyboardButton("🔴 Non mi interessa...", callback_data=f"mm_{mmevent.mmid}_d_NO")]
|
telegram.InlineKeyboardButton("🔴 Non mi interessa...", callback_data=f"mm_{mmevent.mmid}_d_NO")]
|
||||||
])
|
])
|
||||||
|
elif mmevent.state == "READY_CHECK":
|
||||||
@staticmethod
|
|
||||||
def _response_string(mmevent: MMEvent, count: int = 0) -> str:
|
|
||||||
if count == 0:
|
|
||||||
return f"🚩 E' ora di [b]{mmevent.title}[/b]!\n" \
|
|
||||||
f"Sei pronto?"
|
|
||||||
else:
|
|
||||||
return f"🕒 Sei in ritardo di [b]{count * 5}[/b] minuti per [b]{mmevent.title}[/b]...\n" \
|
|
||||||
f"Sei pronto?"
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _response_keyboard(mmevent: MMEvent) -> telegram.InlineKeyboardMarkup:
|
|
||||||
return telegram.InlineKeyboardMarkup([
|
return telegram.InlineKeyboardMarkup([
|
||||||
[telegram.InlineKeyboardButton("✅ Ci sono!", callback_data=f"mm_{mmevent.mmid}_r_YES")],
|
[telegram.InlineKeyboardButton("🚩 Avvia la partita", callback_data=f"mm_{mmevent.mmid}_start")]
|
||||||
[telegram.InlineKeyboardButton("🕒 Aspettatemi 5 minuti!", callback_data=f"mm_{mmevent.mmid}_r_LATER")],
|
|
||||||
[telegram.InlineKeyboardButton("❌ Non ci sono più, mi spiace.", callback_data=f"mm_{mmevent.mmid}_r_NO")]
|
|
||||||
])
|
])
|
||||||
|
elif mmevent.state == "STARTED":
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
raise ValueError(f"state is of an unknown value ({mmevent.state})")
|
||||||
|
|
||||||
async def _update_message(self, mmevent: MMEvent) -> None:
|
@staticmethod
|
||||||
|
def _main_text(mmevent: MMEvent):
|
||||||
|
text = f"🌐 [b]{mmevent.title}[/b] - [b]{mmevent.datetime.strftime('%Y-%m-%d %H:%M')}[/b]\n"
|
||||||
|
if mmevent.description:
|
||||||
|
text += f"{mmevent.description}\n"
|
||||||
|
text += "\n"
|
||||||
|
if mmevent.state == "WAITING" or mmevent.state == "DECISION":
|
||||||
|
for mmdecision in sorted(mmevent.decisions, key=lambda mmd: mmd.royal.username):
|
||||||
|
mmdecision: "MMDecision"
|
||||||
|
if mmdecision.decision == "YES":
|
||||||
|
text += "🔵 "
|
||||||
|
elif mmdecision.decision == "MAYBE":
|
||||||
|
text += "⚫️ "
|
||||||
|
elif mmdecision.decision == "NO":
|
||||||
|
text += "🔴 "
|
||||||
|
else:
|
||||||
|
raise ValueError(f"decision is of an unknown value ({mmdecision.decision})")
|
||||||
|
text += f"{mmdecision.royal}\n"
|
||||||
|
elif mmevent.state == "READY_CHECK":
|
||||||
|
for mmresponse in sorted(mmevent.responses, key=lambda mmr: mmr.royal.username):
|
||||||
|
mmresponse: "MMResponse"
|
||||||
|
if mmresponse.response is None:
|
||||||
|
text += "❔ "
|
||||||
|
elif mmresponse.response == "YES":
|
||||||
|
text += "✅ "
|
||||||
|
elif mmresponse.response == "LATER":
|
||||||
|
text += "🕒 "
|
||||||
|
elif mmresponse.response == "NO":
|
||||||
|
text += "❌ "
|
||||||
|
else:
|
||||||
|
raise ValueError(f"response is of an unknown value ({mmresponse.response})")
|
||||||
|
text += f"{mmresponse.royal}\n"
|
||||||
|
elif mmevent.state == "STARTED":
|
||||||
|
for mmresponse in sorted(mmevent.responses, key=lambda mmr: mmr.response, reverse=True):
|
||||||
|
if mmresponse.response == "YES":
|
||||||
|
text += f"✅ {mmresponse.royal}\n"
|
||||||
|
elif mmresponse.response == "NO":
|
||||||
|
text += "❌ {mmresponse.royal}\n"
|
||||||
|
return text
|
||||||
|
|
||||||
|
async def _run_mm(self, mmevent: MMEvent) -> None:
|
||||||
client: telegram.Bot = self.interface.bot.client
|
client: telegram.Bot = self.interface.bot.client
|
||||||
|
|
||||||
|
async def update_message() -> None:
|
||||||
try:
|
try:
|
||||||
await asyncify(client.edit_message_text,
|
await self.interface.bot.safe_api_call(client.edit_message_text,
|
||||||
text=telegram_escape(str(mmevent)),
|
text=telegram_escape(self._main_text(mmevent)),
|
||||||
chat_id=os.environ["MM_CHANNEL_ID"],
|
chat_id=os.environ["MM_CHANNEL_ID"],
|
||||||
message_id=mmevent.message_id,
|
message_id=mmevent.message_id,
|
||||||
parse_mode="HTML",
|
parse_mode="HTML",
|
||||||
disable_web_page_preview=True,
|
disable_web_page_preview=True,
|
||||||
reply_markup=mmevent.main_keyboard())
|
reply_markup=self._main_keyboard(mmevent))
|
||||||
except telegram.error.BadRequest:
|
except telegram.error.BadRequest:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
decision_string = f"⚫️ Hai detto che forse parteciperai a [b]{mmevent.title}[/b]" \
|
||||||
|
f" alle [b]{mmevent.datetime.strftime('%H:%M')}[/b].\n" \
|
||||||
|
f"Confermi di volerci essere? (Metti sì anche se arrivi un po' in ritardo!)"
|
||||||
|
|
||||||
|
decision_keyboard = telegram.InlineKeyboardMarkup([
|
||||||
|
[telegram.InlineKeyboardButton("🔵 Ci sarò!", callback_data=f"mm_{mmevent.mmid}_d_YES"),
|
||||||
|
telegram.InlineKeyboardButton("🔴 Non mi interessa più.", callback_data=f"mm_{mmevent.mmid}_d_NO")]
|
||||||
|
])
|
||||||
|
|
||||||
|
async def decision_yes(data: CommandData):
|
||||||
|
royal = await data.get_author()
|
||||||
|
mmdecision: MMDecision = await asyncify(
|
||||||
|
self.interface.session.query(self.interface.alchemy.MMDecision).filter_by(mmevent=mmevent,
|
||||||
|
royal=royal).one_or_none)
|
||||||
|
if mmdecision is None:
|
||||||
|
mmdecision: MMDecision = self.interface.alchemy.MMDecision(royal=royal,
|
||||||
|
mmevent=mmevent,
|
||||||
|
decision="YES")
|
||||||
|
self.interface.session.add(mmdecision)
|
||||||
|
else:
|
||||||
|
mmdecision.decision = "YES"
|
||||||
|
await asyncify(self.interface.session.commit)
|
||||||
|
await update_message()
|
||||||
|
return "🔵 Hai detto che ci sarai!"
|
||||||
|
|
||||||
|
async def decision_maybe(data: CommandData):
|
||||||
|
royal = await data.get_author()
|
||||||
|
mmdecision: MMDecision = await asyncify(
|
||||||
|
self.interface.session.query(self.interface.alchemy.MMDecision).filter_by(mmevent=mmevent,
|
||||||
|
royal=royal).one_or_none)
|
||||||
|
if mmdecision is None:
|
||||||
|
mmdecision: MMDecision = self.interface.alchemy.MMDecision(royal=royal,
|
||||||
|
mmevent=mmevent,
|
||||||
|
decision="MAYBE")
|
||||||
|
self.interface.session.add(mmdecision)
|
||||||
|
else:
|
||||||
|
mmdecision.decision = "MAYBE"
|
||||||
|
# Can't asyncify this
|
||||||
|
self.interface.session.commit()
|
||||||
|
await update_message()
|
||||||
|
return "⚫️ Hai detto che forse ci sarai. Rispondi al messaggio di conferma 5 minuti prima dell'inizio!"
|
||||||
|
|
||||||
|
async def decision_no(data: CommandData):
|
||||||
|
royal = await data.get_author()
|
||||||
|
mmdecision: MMDecision = await asyncify(
|
||||||
|
self.interface.session.query(self.interface.alchemy.MMDecision).filter_by(mmevent=mmevent,
|
||||||
|
royal=royal).one_or_none)
|
||||||
|
if mmdecision is None:
|
||||||
|
mmdecision: MMDecision = self.interface.alchemy.MMDecision(royal=royal,
|
||||||
|
mmevent=mmevent,
|
||||||
|
decision="NO")
|
||||||
|
self.interface.session.add(mmdecision)
|
||||||
|
else:
|
||||||
|
mmdecision.decision = "NO"
|
||||||
|
# Can't asyncify this
|
||||||
|
self.interface.session.commit()
|
||||||
|
await update_message()
|
||||||
|
return "🔴 Hai detto che non ti interessa."
|
||||||
|
|
||||||
|
def response_string() -> str:
|
||||||
|
delay = (datetime.datetime.now() - mmevent.datetime).total_seconds()
|
||||||
|
if delay > 60:
|
||||||
|
return f"🚩 E' ora di [b]{mmevent.title}[/b]!\n" \
|
||||||
|
f"Sei pronto?"
|
||||||
|
return f"🕒 Sei in ritardo di [b]{int(delay / 60)} minuti[/b] per [b]{mmevent.title}[/b]...\n" \
|
||||||
|
f"Sei pronto?"
|
||||||
|
|
||||||
|
response_keyboard = telegram.InlineKeyboardMarkup([
|
||||||
|
[telegram.InlineKeyboardButton("✅ Ci sono!",
|
||||||
|
callback_data=f"mm_{mmevent.mmid}_r_YES")],
|
||||||
|
[telegram.InlineKeyboardButton("🕒 Aspettatemi ancora un po'!",
|
||||||
|
callback_data=f"mm_{mmevent.mmid}_r_LATER")],
|
||||||
|
[telegram.InlineKeyboardButton("❌ Non vengo più, mi spiace.",
|
||||||
|
callback_data=f"mm_{mmevent.mmid}_r_NO")]
|
||||||
|
])
|
||||||
|
|
||||||
|
async def response_yes(data: CommandData):
|
||||||
|
royal = await data.get_author()
|
||||||
|
mmresponse: MMResponse = await asyncify(
|
||||||
|
self.interface.session.query(self.interface.alchemy.MMResponse).filter_by(mmevent=mmevent,
|
||||||
|
royal=royal).one_or_none)
|
||||||
|
mmresponse.response = "YES"
|
||||||
|
# Can't asyncify this
|
||||||
|
self.interface.session.commit()
|
||||||
|
await update_message()
|
||||||
|
return "✅ Sei pronto!"
|
||||||
|
|
||||||
|
async def response_later(data: CommandData):
|
||||||
|
royal = await data.get_author()
|
||||||
|
mmresponse: MMResponse = await asyncify(
|
||||||
|
self.interface.session.query(self.interface.alchemy.MMResponse).filter_by(mmevent=mmevent,
|
||||||
|
royal=royal).one_or_none)
|
||||||
|
mmresponse.response = "LATER"
|
||||||
|
# Can't asyncify this
|
||||||
|
self.interface.session.commit()
|
||||||
|
await update_message()
|
||||||
|
return f"🕒 Hai chiesto agli altri di aspettarti {self._cycle_duration} minuti."
|
||||||
|
|
||||||
|
async def response_no(data: CommandData):
|
||||||
|
royal = await data.get_author()
|
||||||
|
mmresponse: MMResponse = await asyncify(
|
||||||
|
self.interface.session.query(self.interface.alchemy.MMResponse).filter_by(mmevent=mmevent,
|
||||||
|
royal=royal).one_or_none)
|
||||||
|
mmresponse.response = "NO"
|
||||||
|
# Can't asyncify this
|
||||||
|
self.interface.session.commit()
|
||||||
|
await update_message()
|
||||||
|
return "❌ Hai detto che non ci sarai."
|
||||||
|
|
||||||
|
def started_string():
|
||||||
|
text = f"🚩 L'evento [b]{mmevent.title}[/b] è iniziato!\n\n" \
|
||||||
|
f"Parteciperanno:"
|
||||||
|
for mmresponse in sorted(mmevent.responses, key=lambda mmr: mmr.response, reverse=True):
|
||||||
|
if mmresponse.response == "YES":
|
||||||
|
text += f"✅ {mmresponse.royal}\n"
|
||||||
|
elif mmresponse.response == "NO":
|
||||||
|
text += "❌ {mmresponse.royal}\n"
|
||||||
|
return text
|
||||||
|
|
||||||
|
started_without_you_string = f"🚩 Non hai confermato la tua presenza in tempo e [b]{mmevent.title}[/b] è" \
|
||||||
|
f" iniziato senza di te.\n" \
|
||||||
|
f"Mi dispiace!"
|
||||||
|
|
||||||
|
async def start_event():
|
||||||
|
mmevent.state = "STARTED"
|
||||||
|
for mmresponse in mmevent.responses:
|
||||||
|
if mmresponse.response is None:
|
||||||
|
mmresponse.response = "NO"
|
||||||
|
if mmresponse.response == "LATER":
|
||||||
|
mmresponse.response = "NO"
|
||||||
|
|
||||||
|
if mmresponse.response == "YES":
|
||||||
|
await self.interface.bot.safe_api_call(client.send_message,
|
||||||
|
chat_id=mmresponse.royal.telegram[0].tg_id,
|
||||||
|
text=telegram_escape(started_string()),
|
||||||
|
parse_mode="HTML",
|
||||||
|
disable_webpage_preview=True,
|
||||||
|
reply_markup=response_keyboard)
|
||||||
|
else:
|
||||||
|
await self.interface.bot.safe_api_call(client.send_message,
|
||||||
|
chat_id=mmresponse.royal.telegram[0].tg_id,
|
||||||
|
text=telegram_escape(started_without_you_string),
|
||||||
|
parse_mode="HTML",
|
||||||
|
disable_webpage_preview=True,
|
||||||
|
reply_markup=response_keyboard)
|
||||||
|
await asyncify(self.interface.session.commit)
|
||||||
|
await update_message()
|
||||||
|
|
||||||
|
async def start_key(data: CommandData):
|
||||||
|
royal = await data.get_author()
|
||||||
|
if royal == mmevent.creator:
|
||||||
|
await start_event()
|
||||||
|
|
||||||
|
if mmevent.state == "WAITING":
|
||||||
|
self.interface.register_keyboard_key(f"mm_{mmevent.mmid}_d_YES", decision_yes)
|
||||||
|
self.interface.register_keyboard_key(f"mm_{mmevent.mmid}_d_MAYBE", decision_maybe)
|
||||||
|
self.interface.register_keyboard_key(f"mm_{mmevent.mmid}_d_NO", decision_no)
|
||||||
|
await sleep_until(mmevent.datetime - datetime.timedelta(minutes=10))
|
||||||
|
self.interface.unregister_keyboard_key(f"mm_{mmevent.mmid}_d_YES")
|
||||||
|
self.interface.unregister_keyboard_key(f"mm_{mmevent.mmid}_d_MAYBE")
|
||||||
|
self.interface.unregister_keyboard_key(f"mm_{mmevent.mmid}_d_NO")
|
||||||
|
mmevent.state = "DECISION"
|
||||||
|
for mmdecision in mmevent.decisions:
|
||||||
|
mmdecision: MMDecision
|
||||||
|
if mmdecision.decision == "MAYBE":
|
||||||
|
await self.interface.bot.safe_api_call(client.send_message,
|
||||||
|
chat_id=mmdecision.royal.telegram[0].tg_id,
|
||||||
|
text=telegram_escape(decision_string),
|
||||||
|
parse_mode="HTML",
|
||||||
|
disable_webpage_preview=True,
|
||||||
|
reply_markup=decision_keyboard)
|
||||||
|
await asyncify(self.interface.session.commit)
|
||||||
|
await update_message()
|
||||||
|
|
||||||
|
if mmevent.state == "DECISION":
|
||||||
|
self.interface.register_keyboard_key(f"mm_{mmevent.mmid}_d_YES", decision_yes)
|
||||||
|
self.interface.register_keyboard_key(f"mm_{mmevent.mmid}_d_NO", decision_no)
|
||||||
|
await sleep_until(mmevent.datetime)
|
||||||
|
self.interface.unregister_keyboard_key(f"mm_{mmevent.mmid}_d_YES")
|
||||||
|
self.interface.unregister_keyboard_key(f"mm_{mmevent.mmid}_d_NO")
|
||||||
|
mmevent.state = "READY_CHECK"
|
||||||
|
for mmdecision in mmevent.decisions:
|
||||||
|
if mmdecision.decision == "MAYBE":
|
||||||
|
mmdecision.decision = "NO"
|
||||||
|
elif mmdecision.decision == "YES":
|
||||||
|
mmresponse: MMResponse = self.interface.alchemy.MMResponse(royal=mmdecision.royal, mmevent=mmevent)
|
||||||
|
self.interface.session.add(mmresponse)
|
||||||
|
await asyncify(self.interface.session.commit)
|
||||||
|
await update_message()
|
||||||
|
|
||||||
|
if mmevent.state == "READY_CHECK":
|
||||||
|
self.interface.register_keyboard_key(f"mm_{mmevent.mmid}_r_YES", response_yes)
|
||||||
|
self.interface.register_keyboard_key(f"mm_{mmevent.mmid}_r_LATER", response_later)
|
||||||
|
self.interface.register_keyboard_key(f"mm_{mmevent.mmid}_r_NO", response_no)
|
||||||
|
self.interface.register_keyboard_key(f"mm_{mmevent.mmid}_forcestart", start_key)
|
||||||
|
cycle = 0
|
||||||
|
while True:
|
||||||
|
for mmresponse in mmevent.responses:
|
||||||
|
# Send messages
|
||||||
|
if mmresponse.response is None:
|
||||||
|
await self.interface.bot.safe_api_call(client.send_message,
|
||||||
|
chat_id=mmresponse.royal.telegram[0].tg_id,
|
||||||
|
text=telegram_escape(response_string()),
|
||||||
|
parse_mode="HTML",
|
||||||
|
disable_webpage_preview=True,
|
||||||
|
reply_markup=response_keyboard)
|
||||||
|
# Wait
|
||||||
|
await asyncio.sleep(60 * self._cycle_duration)
|
||||||
|
# Advance cycle
|
||||||
|
for mmresponse in mmevent.responses:
|
||||||
|
if mmresponse.response is None:
|
||||||
|
mmresponse.response = "NO"
|
||||||
|
if mmresponse.response == "LATER":
|
||||||
|
mmresponse.response = None
|
||||||
|
# Check if the event can start
|
||||||
|
for mmresponse in mmevent.responses:
|
||||||
|
if mmresponse.response is None:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
cycle += 1
|
||||||
|
|
||||||
|
await start_event()
|
||||||
|
self.interface.unregister_keyboard_key(f"mm_{mmevent.mmid}_r_YES")
|
||||||
|
self.interface.unregister_keyboard_key(f"mm_{mmevent.mmid}_r_LATER")
|
||||||
|
self.interface.unregister_keyboard_key(f"mm_{mmevent.mmid}_r_NO")
|
||||||
|
self.interface.unregister_keyboard_key(f"mm_{mmevent.mmid}_start")
|
||||||
|
|
||||||
async def run(self, args: CommandArgs, data: CommandData) -> None:
|
async def run(self, args: CommandArgs, data: CommandData) -> None:
|
||||||
if self.interface.name != "telegram":
|
if self.interface.name != "telegram":
|
||||||
raise UnsupportedError("mm is supported only on Telegram")
|
raise UnsupportedError("mm is supported only on Telegram")
|
||||||
client: telegram.Bot = self.interface.bot.client
|
client: telegram.Bot = self.interface.bot.client
|
||||||
creator = await data.get_author(error_if_none=True)
|
creator = await data.get_author(error_if_none=True)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
timestring, title, description = args.match(r"\[\s*([^]]+)\s*]\s*([^\n]+)\s*\n?\s*(.+)?\s*")
|
timestring, title, description = args.match(r"\[\s*([^]]+)\s*]\s*([^\n]+)\s*\n?\s*(.+)?\s*")
|
||||||
except InvalidInputError:
|
except InvalidInputError:
|
||||||
timestring, title, description = args.match(r"\s*(.+?)\s*\n\s*([^\n]+)\s*\n?\s*(.+)?\s*")
|
timestring, title, description = args.match(r"\s*(.+?)\s*\n\s*([^\n]+)\s*\n?\s*(.+)?\s*")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
dt: typing.Optional[datetime.datetime] = dateparser.parse(timestring)
|
dt: typing.Optional[datetime.datetime] = dateparser.parse(timestring)
|
||||||
except OverflowError:
|
except OverflowError:
|
||||||
|
@ -87,7 +349,6 @@ class MmCommand(Command):
|
||||||
if dt <= datetime.datetime.now():
|
if dt <= datetime.datetime.now():
|
||||||
await data.reply("⚠️ La data che hai specificato è nel passato.")
|
await data.reply("⚠️ La data che hai specificato è nel passato.")
|
||||||
return
|
return
|
||||||
|
|
||||||
mmevent: MMEvent = self.interface.alchemy.MMEvent(creator=creator,
|
mmevent: MMEvent = self.interface.alchemy.MMEvent(creator=creator,
|
||||||
datetime=dt,
|
datetime=dt,
|
||||||
title=title,
|
title=title,
|
||||||
|
@ -96,183 +357,15 @@ class MmCommand(Command):
|
||||||
self.interface.session.add(mmevent)
|
self.interface.session.add(mmevent)
|
||||||
await asyncify(self.interface.session.commit)
|
await asyncify(self.interface.session.commit)
|
||||||
|
|
||||||
async def decision_yes(data: CommandData):
|
|
||||||
royal = await data.get_author()
|
|
||||||
mmdecision: MMDecision = await asyncify(self.interface.session.query(self.interface.alchemy.MMDecision).filter_by(mmevent=mmevent, royal=royal).one_or_none)
|
|
||||||
if mmdecision is None:
|
|
||||||
mmdecision: MMDecision = self.interface.alchemy.MMDecision(royal=royal,
|
|
||||||
mmevent=mmevent,
|
|
||||||
decision="YES")
|
|
||||||
self.interface.session.add(mmdecision)
|
|
||||||
else:
|
|
||||||
mmdecision.decision = "YES"
|
|
||||||
# Can't asyncify this
|
|
||||||
self.interface.session.commit()
|
|
||||||
await self._update_message(mmevent)
|
|
||||||
return "🔵 Hai detto che ci sarai!"
|
|
||||||
|
|
||||||
async def decision_maybe(data: CommandData):
|
|
||||||
royal = await data.get_author()
|
|
||||||
mmdecision: MMDecision = await asyncify(self.interface.session.query(self.interface.alchemy.MMDecision).filter_by(mmevent=mmevent, royal=royal).one_or_none)
|
|
||||||
if mmdecision is None:
|
|
||||||
mmdecision: MMDecision = self.interface.alchemy.MMDecision(royal=royal,
|
|
||||||
mmevent=mmevent,
|
|
||||||
decision="MAYBE")
|
|
||||||
self.interface.session.add(mmdecision)
|
|
||||||
else:
|
|
||||||
mmdecision.decision = "MAYBE"
|
|
||||||
# Can't asyncify this
|
|
||||||
self.interface.session.commit()
|
|
||||||
await self._update_message(mmevent)
|
|
||||||
return "⚫️ Hai detto che forse ci sarai. Rispondi al messaggio di conferma 5 minuti prima dell'inizio!"
|
|
||||||
|
|
||||||
async def decision_no(data: CommandData):
|
|
||||||
royal = await data.get_author()
|
|
||||||
mmdecision: MMDecision = await asyncify(self.interface.session.query(self.interface.alchemy.MMDecision).filter_by(mmevent=mmevent, royal=royal).one_or_none)
|
|
||||||
if mmdecision is None:
|
|
||||||
mmdecision: MMDecision = self.interface.alchemy.MMDecision(royal=royal,
|
|
||||||
mmevent=mmevent,
|
|
||||||
decision="NO")
|
|
||||||
self.interface.session.add(mmdecision)
|
|
||||||
else:
|
|
||||||
mmdecision.decision = "NO"
|
|
||||||
# Can't asyncify this
|
|
||||||
self.interface.session.commit()
|
|
||||||
await self._update_message(mmevent)
|
|
||||||
return "🔴 Hai detto che non ti interessa."
|
|
||||||
|
|
||||||
self.interface.register_keyboard_key(f"mm_{mmevent.mmid}_d_YES", decision_yes)
|
|
||||||
self.interface.register_keyboard_key(f"mm_{mmevent.mmid}_d_MAYBE", decision_maybe)
|
|
||||||
self.interface.register_keyboard_key(f"mm_{mmevent.mmid}_d_NO", decision_no)
|
|
||||||
|
|
||||||
message: telegram.Message = await asyncify(client.send_message,
|
message: telegram.Message = await asyncify(client.send_message,
|
||||||
chat_id=os.environ["MM_CHANNEL_ID"],
|
chat_id=os.environ["MM_CHANNEL_ID"],
|
||||||
text=telegram_escape(str(mmevent)),
|
text=telegram_escape(self._main_text(mmevent)),
|
||||||
parse_mode="HTML",
|
parse_mode="HTML",
|
||||||
disable_webpage_preview=True,
|
disable_webpage_preview=True,
|
||||||
reply_markup=mmevent.main_keyboard())
|
reply_markup=self._main_keyboard(mmevent))
|
||||||
|
|
||||||
mmevent.message_id = message.message_id
|
mmevent.message_id = message.message_id
|
||||||
# Can't asyncify this
|
# Can't asyncify this
|
||||||
self.interface.session.commit()
|
|
||||||
|
|
||||||
await sleep_until(dt - datetime.timedelta(minutes=10))
|
|
||||||
|
|
||||||
mmevent.state = "DECISION"
|
|
||||||
await asyncify(self.interface.session.commit)
|
|
||||||
for mmdecision in mmevent.decisions:
|
|
||||||
mmdecision: MMDecision
|
|
||||||
if mmdecision.decision == "MAYBE":
|
|
||||||
await asyncify(client.send_message,
|
|
||||||
chat_id=mmdecision.royal.telegram[0].tg_id,
|
|
||||||
text=telegram_escape(self._decision_string(mmevent)),
|
|
||||||
parse_mode="HTML",
|
|
||||||
disable_webpage_preview=True,
|
|
||||||
reply_markup=self._decision_keyboard(mmevent))
|
|
||||||
await self._update_message(mmevent)
|
|
||||||
|
|
||||||
await sleep_until(dt)
|
|
||||||
|
|
||||||
async def response_yes(data: CommandData):
|
|
||||||
royal = await data.get_author()
|
|
||||||
mmresponse: MMResponse = await asyncify(
|
|
||||||
self.interface.session.query(self.interface.alchemy.MMResponse).filter_by(mmevent=mmevent, royal=royal).one_or_none)
|
|
||||||
mmresponse.response = "YES"
|
|
||||||
# Can't asyncify this
|
|
||||||
self.interface.session.commit()
|
|
||||||
await self._update_message(mmevent)
|
|
||||||
return "✅ Sei pronto!"
|
|
||||||
|
|
||||||
async def response_later(data: CommandData):
|
|
||||||
royal = await data.get_author()
|
|
||||||
mmresponse: MMResponse = await asyncify(
|
|
||||||
self.interface.session.query(self.interface.alchemy.MMResponse).filter_by(mmevent=mmevent, royal=royal).one_or_none)
|
|
||||||
mmresponse.response = "LATER"
|
|
||||||
# Can't asyncify this
|
|
||||||
self.interface.session.commit()
|
|
||||||
await self._update_message(mmevent)
|
|
||||||
return "🕒 Hai chiesto agli altri di aspettarti 5 minuti."
|
|
||||||
|
|
||||||
async def response_no(data: CommandData):
|
|
||||||
royal = await data.get_author()
|
|
||||||
mmresponse: MMResponse = await asyncify(
|
|
||||||
self.interface.session.query(self.interface.alchemy.MMResponse).filter_by(mmevent=mmevent, royal=royal).one_or_none)
|
|
||||||
mmresponse.response = "NO"
|
|
||||||
# Can't asyncify this
|
|
||||||
self.interface.session.commit()
|
|
||||||
await self._update_message(mmevent)
|
|
||||||
return "❌ Hai detto che non ci sarai."
|
|
||||||
|
|
||||||
async def start_now():
|
|
||||||
mmevent.state = "STARTED"
|
|
||||||
for mmresponse in mmevent.responses:
|
|
||||||
if mmresponse.response is None:
|
|
||||||
mmresponse.response = "NO"
|
|
||||||
if mmresponse.response == "LATER":
|
|
||||||
mmresponse.response = "NO"
|
|
||||||
await self._update_message(mmevent)
|
|
||||||
await asyncify(self.interface.session.commit)
|
|
||||||
self.interface.unregister_keyboard_key(f"mm_{mmevent.mmid}_r_YES")
|
|
||||||
self.interface.unregister_keyboard_key(f"mm_{mmevent.mmid}_r_LATER")
|
|
||||||
self.interface.unregister_keyboard_key(f"mm_{mmevent.mmid}_r_NO")
|
|
||||||
self.interface.unregister_keyboard_key(f"mm_{mmevent.mmid}_start")
|
|
||||||
|
|
||||||
async def start_key(data: CommandData):
|
|
||||||
royal = await data.get_author()
|
|
||||||
if royal == creator:
|
|
||||||
await start_now()
|
|
||||||
|
|
||||||
mmevent.state = "READY_CHECK"
|
|
||||||
|
|
||||||
self.interface.unregister_keyboard_key(f"mm_{mmevent.mmid}_d_YES")
|
|
||||||
self.interface.unregister_keyboard_key(f"mm_{mmevent.mmid}_d_MAYBE")
|
|
||||||
self.interface.unregister_keyboard_key(f"mm_{mmevent.mmid}_d_NO")
|
|
||||||
|
|
||||||
for mmdecision in mmevent.decisions:
|
|
||||||
if mmdecision.decision == "MAYBE":
|
|
||||||
mmdecision.decision = "NO"
|
|
||||||
elif mmdecision.decision == "YES":
|
|
||||||
mmresponse: MMResponse = self.interface.alchemy.MMResponse(royal=mmdecision.royal, mmevent=mmevent)
|
|
||||||
self.interface.session.add(mmresponse)
|
|
||||||
await asyncify(self.interface.session.commit)
|
await asyncify(self.interface.session.commit)
|
||||||
|
|
||||||
self.interface.register_keyboard_key(f"mm_{mmevent.mmid}_r_YES", response_yes)
|
await self._run_mm(mmevent)
|
||||||
self.interface.register_keyboard_key(f"mm_{mmevent.mmid}_r_LATER", response_later)
|
|
||||||
self.interface.register_keyboard_key(f"mm_{mmevent.mmid}_r_NO", response_no)
|
|
||||||
self.interface.register_keyboard_key(f"mm_{mmevent.mmid}_start", start_key)
|
|
||||||
|
|
||||||
count = 0
|
|
||||||
while True:
|
|
||||||
for mmresponse in mmevent.responses:
|
|
||||||
# Send messages
|
|
||||||
if mmresponse.response is None:
|
|
||||||
await asyncify(client.send_message,
|
|
||||||
chat_id=mmresponse.royal.telegram[0].tg_id,
|
|
||||||
text=telegram_escape(self._response_string(mmevent, count=count)),
|
|
||||||
parse_mode="HTML",
|
|
||||||
disable_webpage_preview=True,
|
|
||||||
reply_markup=self._response_keyboard(mmevent))
|
|
||||||
await self._update_message(mmevent)
|
|
||||||
# Wait
|
|
||||||
await asyncio.sleep(300)
|
|
||||||
|
|
||||||
if mmevent.state == "STARTED":
|
|
||||||
return
|
|
||||||
|
|
||||||
# Advance cycle
|
|
||||||
for mmresponse in mmevent.responses:
|
|
||||||
if mmresponse.response is None:
|
|
||||||
mmresponse.response = "NO"
|
|
||||||
if mmresponse.response == "LATER":
|
|
||||||
mmresponse.response = None
|
|
||||||
|
|
||||||
# Check if the event can start
|
|
||||||
for mmresponse in mmevent.responses:
|
|
||||||
if mmresponse.response is None:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
|
|
||||||
count += 1
|
|
||||||
|
|
||||||
await start_now()
|
|
||||||
|
|
|
@ -54,60 +54,3 @@ class MMEvent:
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<MMEvent {self.mmid}: {self.title}>"
|
return f"<MMEvent {self.mmid}: {self.title}>"
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
text = f"🌐 [b]{self.title}[/b] - [b]{self.datetime.strftime('%Y-%m-%d %H:%M')}[/b]\n"
|
|
||||||
if self.description:
|
|
||||||
text += f"{self.description}\n"
|
|
||||||
text += "\n"
|
|
||||||
if self.state == "WAITING" or self.state == "DECISION":
|
|
||||||
for mmdecision in self.decisions:
|
|
||||||
mmdecision: "MMDecision"
|
|
||||||
if mmdecision.decision == "YES":
|
|
||||||
text += "🔵 "
|
|
||||||
elif mmdecision.decision == "MAYBE":
|
|
||||||
text += "⚫️ "
|
|
||||||
elif mmdecision.decision == "NO":
|
|
||||||
text += "🔴 "
|
|
||||||
else:
|
|
||||||
raise ValueError(f"decision is of an unknown value ({mmdecision.decision})")
|
|
||||||
text += f"{mmdecision.royal}\n"
|
|
||||||
elif self.state == "READY_CHECK":
|
|
||||||
for mmresponse in self.responses:
|
|
||||||
mmresponse: "MMResponse"
|
|
||||||
if mmresponse.response is None:
|
|
||||||
text += "❔ "
|
|
||||||
elif mmresponse.response == "YES":
|
|
||||||
text += "✅ "
|
|
||||||
elif mmresponse.response == "LATER":
|
|
||||||
text += "🕒 "
|
|
||||||
elif mmresponse.response == "NO":
|
|
||||||
text += "❌ "
|
|
||||||
else:
|
|
||||||
raise ValueError(f"response is of an unknown value ({mmresponse.response})")
|
|
||||||
text += f"{mmresponse.royal}\n"
|
|
||||||
elif self.state == "STARTED":
|
|
||||||
for mmresponse in self.responses:
|
|
||||||
if mmresponse.response == "YES":
|
|
||||||
text += f"✅ {mmresponse.royal}\n"
|
|
||||||
return text
|
|
||||||
|
|
||||||
def main_keyboard(self) -> typing.Optional[telegram.InlineKeyboardMarkup]:
|
|
||||||
if self.state == "WAITING":
|
|
||||||
return telegram.InlineKeyboardMarkup([
|
|
||||||
[telegram.InlineKeyboardButton("🔵 Ci sarò!", callback_data=f"mm_{self.mmid}_d_YES")],
|
|
||||||
[telegram.InlineKeyboardButton("⚫️ Forse...", callback_data=f"mm_{self.mmid}_d_MAYBE")],
|
|
||||||
[telegram.InlineKeyboardButton("🔴 Non mi interessa.", callback_data=f"mm_{self.mmid}_d_NO")]
|
|
||||||
])
|
|
||||||
elif self.state == "DECISION":
|
|
||||||
return telegram.InlineKeyboardMarkup([
|
|
||||||
[telegram.InlineKeyboardButton("🔵 Ci sarò!", callback_data=f"mm_{self.mmid}_d_YES"),
|
|
||||||
telegram.InlineKeyboardButton("🔴 Non mi interessa...", callback_data=f"mm_{self.mmid}_d_NO")]
|
|
||||||
])
|
|
||||||
elif self.state == "READY_CHECK":
|
|
||||||
return telegram.InlineKeyboardMarkup([
|
|
||||||
[telegram.InlineKeyboardButton("🚩 Avvia la partita", callback_data=f"mm_{self.mmid}_start")]
|
|
||||||
])
|
|
||||||
elif self.state == "STARTED":
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
raise ValueError(f"state is of an unknown value ({self.state})")
|
|
||||||
|
|
|
@ -24,6 +24,25 @@ sentry_dsn = os.environ.get("SENTRY_DSN")
|
||||||
# noinspection PyUnreachableCode
|
# noinspection PyUnreachableCode
|
||||||
if __debug__:
|
if __debug__:
|
||||||
commands = [
|
commands = [
|
||||||
|
CiaoruoziCommand,
|
||||||
|
ColorCommand,
|
||||||
|
CvCommand,
|
||||||
|
DiarioCommand,
|
||||||
|
Mp3Command,
|
||||||
|
PauseCommand,
|
||||||
|
PingCommand,
|
||||||
|
PlayCommand,
|
||||||
|
PlaymodeCommand,
|
||||||
|
QueueCommand,
|
||||||
|
RageCommand,
|
||||||
|
ReminderCommand,
|
||||||
|
ShipCommand,
|
||||||
|
SkipCommand,
|
||||||
|
SmecdsCommand,
|
||||||
|
SummonCommand,
|
||||||
|
VideochannelCommand,
|
||||||
|
TriviaCommand,
|
||||||
|
MmCommand
|
||||||
]
|
]
|
||||||
log.setLevel(logging.DEBUG)
|
log.setLevel(logging.DEBUG)
|
||||||
else:
|
else:
|
||||||
|
|
Loading…
Reference in a new issue