1
Fork 0
mirror of https://github.com/RYGhub/royalnet.git synced 2024-11-23 19:44:20 +00:00

Add trivia command (closes #78)

This commit is contained in:
Steffo 2019-09-03 15:16:12 +02:00
parent 9b5e06a46c
commit ea939289b6
6 changed files with 125 additions and 12 deletions

View file

@ -27,6 +27,7 @@ class GenericBot:
self.commands = {} self.commands = {}
self.network_handlers: typing.Dict[str, typing.Type[NetworkHandler]] = {} self.network_handlers: typing.Dict[str, typing.Type[NetworkHandler]] = {}
for SelectedCommand in commands: for SelectedCommand in commands:
log.debug(f"Binding {SelectedCommand.name}...")
interface = self._Interface() interface = self._Interface()
self.commands[f"{interface.prefix}{SelectedCommand.name}"] = SelectedCommand(interface) self.commands[f"{interface.prefix}{SelectedCommand.name}"] = SelectedCommand(interface)
log.debug(f"Successfully bound commands") log.debug(f"Successfully bound commands")

View file

@ -152,17 +152,30 @@ class TelegramBot(GenericBot):
async def _handle_callback_query(self, update: telegram.Update): async def _handle_callback_query(self, update: telegram.Update):
query: telegram.CallbackQuery = update.callback_query query: telegram.CallbackQuery = update.callback_query
source: telegram.Message = query.message source: telegram.Message = query.message
callback: typing.Optional[typing.Callable] = None
command: typing.Optional[Command] = None
for command in self.commands.values(): for command in self.commands.values():
try: if query.data in command.interface.keys_callbacks:
callback = command.interface.keys_callbacks[query.data] callback = command.interface.keys_callbacks[query.data]
except KeyError: break
continue if callback is None:
await callback(data=self._Data(interface=command.interface, update=update))
await asyncify(query.answer)
break
else:
await asyncify(source.edit_reply_markup, reply_markup=None) await asyncify(source.edit_reply_markup, reply_markup=None)
await asyncify(query.answer, text="⛔️ This keyboard has expired.") await asyncify(query.answer, text="⛔️ This keyboard has expired.")
return
try:
response = await callback(data=self._Data(interface=command.interface, update=update))
except KeyboardExpiredError:
# FIXME: May cause a memory leak, as keys are not deleted after use
await asyncify(source.edit_reply_markup, reply_markup=None)
await asyncify(query.answer, text="⛔️ This keyboard has expired.")
return
except Exception as e:
error_text = f"⛔️ {e.__class__.__name__}\n"
error_text += '\n'.join(e.args)
await asyncify(query.answer, text=error_text)
return
else:
await asyncify(query.answer, text=response)
async def run(self): async def run(self):
while True: while True:

View file

@ -22,6 +22,7 @@ from .summon import SummonCommand
from .videochannel import VideochannelCommand from .videochannel import VideochannelCommand
from .dnditem import DnditemCommand from .dnditem import DnditemCommand
from .dndspell import DndspellCommand from .dndspell import DndspellCommand
from .trivia import TriviaCommand
__all__ = [ __all__ = [
"CiaoruoziCommand", "CiaoruoziCommand",
@ -42,5 +43,6 @@ __all__ = [
"SummonCommand", "SummonCommand",
"VideochannelCommand", "VideochannelCommand",
"DnditemCommand", "DnditemCommand",
"DndspellCommand" "DndspellCommand",
"TriviaCommand"
] ]

View file

@ -0,0 +1,94 @@
import typing
import asyncio
import aiohttp
import random
import uuid
from ..command import Command
from ..commandargs import CommandArgs
from ..commanddata import CommandData
from ..commandinterface import CommandInterface
from ...error import *
class TriviaCommand(Command):
name: str = "trivia"
description: str = "Manda una domanda dell'OpenTDB in chat."
_letter_emojis = ["🇦", "🇧", "🇨", "🇩"]
_correct_emoji = ""
_wrong_emoji = ""
_answer_time = 15
def __init__(self, interface: CommandInterface):
super().__init__(interface)
self.answerers: typing.Dict[uuid.UUID, typing.Dict[..., bool]] = {}
async def run(self, args: CommandArgs, data: CommandData) -> None:
# Fetch the question
async with aiohttp.ClientSession() as session:
async with session.get("https://opentdb.com/api.php?amount=1") as response:
j = await response.json()
# Parse the question
if j["response_code"] != 0:
raise ExternalError(f"OpenTDB returned {j['response_code']} response_code")
question = j["results"][0]
text = f'❓ [b]{question["category"]} - {question["difficulty"].capitalize()}[/b]\n' \
f'{question["question"]}'
# Prepare answers
correct_answer: str = question["correct_answer"]
wrong_answers: typing.List[str] = question["incorrect_answers"]
answers = [correct_answer, *wrong_answers]
random.shuffle(answers)
# Find the correct index
for index, answer in enumerate(answers):
if answer == correct_answer:
correct_index = index
break
else:
raise ValueError("correct_index not found")
# Add emojis
for index, answer in enumerate(answers):
answers[index] = f"{self._letter_emojis[index]} {answers[index]}"
# Create the question id
question_id = uuid.uuid4()
self.answerers[question_id] = {}
# Create the correct and wrong functions
async def correct(data: CommandData):
answerer_ = await data.get_author(error_if_none=True)
try:
self.answerers[question_id][answerer_] = True
except KeyError:
raise KeyboardExpiredError("Question time ran out.")
return "🆗 Hai risposto alla domanda. Ora aspetta un attimo per i risultati!"
async def wrong(data: CommandData):
answerer_ = await data.get_author(error_if_none=True)
try:
self.answerers[question_id][answerer_] = False
except KeyError:
raise KeyboardExpiredError("Question time ran out.")
return "🆗 Hai risposto alla domanda. Ora aspetta un attimo per i risultati!"
# Add question
keyboard = {}
for index, answer in enumerate(answers):
if index == correct_index:
keyboard[answer] = correct
else:
keyboard[answer] = wrong
await data.keyboard(text, keyboard)
await asyncio.sleep(self._answer_time)
results = f"❗️ Tempo scaduto!\n" \
f"La risposta corretta era [b]{answers[correct_index]}[/b]!\n\n"
for answerer in self.answerers[question_id]:
if self.answerers[question_id][answerer]:
results += self._correct_emoji
else:
results += self._wrong_emoji
results += f" {answerer}"
await data.reply(results)

View file

@ -49,3 +49,7 @@ class FileTooBigError(Exception):
class CurrentlyDisabledError(Exception): class CurrentlyDisabledError(Exception):
"""This feature is temporarely disabled and is not available right now.""" """This feature is temporarely disabled and is not available right now."""
class KeyboardExpiredError(Exception):
"""A special type of exception that can be raised in keyboard handlers to mark a specific keyboard as expired."""

View file

@ -19,14 +19,12 @@ stream_handler.formatter = logging.Formatter("{asctime}\t{name}\t{levelname}\t{m
log.addHandler(stream_handler) log.addHandler(stream_handler)
sentry_dsn = os.environ.get("SENTRY_DSN") sentry_dsn = os.environ.get("SENTRY_DSN")
# noinspection PyUnreachableCode # noinspection PyUnreachableCode
if __debug__: if __debug__:
commands = [ commands = [
DebugErrorCommand,
DebugKeyboardCommand
] ]
log.setLevel(logging.DEBUG) log.setLevel(logging.DEBUG)
else: else:
@ -49,7 +47,8 @@ else:
SummonCommand, SummonCommand,
VideochannelCommand, VideochannelCommand,
DnditemCommand, DnditemCommand,
DndspellCommand DndspellCommand,
TriviaCommand
] ]
log.setLevel(logging.INFO) log.setLevel(logging.INFO)