[wip] Create trivia quiz bot.
This commit is contained in:
parent
93661db0d1
commit
9096dd2918
0
zulip_bots/zulip_bots/bots/trivia_quiz/__init__.py
Normal file
0
zulip_bots/zulip_bots/bots/trivia_quiz/__init__.py
Normal file
1
zulip_bots/zulip_bots/bots/trivia_quiz/requirements.txt
Normal file
1
zulip_bots/zulip_bots/bots/trivia_quiz/requirements.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
requests
|
208
zulip_bots/zulip_bots/bots/trivia_quiz/trivia_quiz.py
Normal file
208
zulip_bots/zulip_bots/bots/trivia_quiz/trivia_quiz.py
Normal file
|
@ -0,0 +1,208 @@
|
||||||
|
import html
|
||||||
|
import json
|
||||||
|
import requests
|
||||||
|
import random
|
||||||
|
import re
|
||||||
|
from zulip_bots.lib import Any
|
||||||
|
|
||||||
|
from typing import Optional, Any, Dict
|
||||||
|
|
||||||
|
class NotAvailableException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class InvalidAnswerException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class TriviaQuizHandler:
|
||||||
|
def usage(self) -> str:
|
||||||
|
return '''
|
||||||
|
This plugin will give users a trivia question from
|
||||||
|
the open trivia database at opentdb.com.'''
|
||||||
|
|
||||||
|
def handle_message(self, message: Dict[str, Any], bot_handler: Any) -> None:
|
||||||
|
query = message['content']
|
||||||
|
if query == 'new':
|
||||||
|
try:
|
||||||
|
start_new_quiz(message, bot_handler)
|
||||||
|
return
|
||||||
|
except NotAvailableException:
|
||||||
|
bot_response = 'Uh-Oh! Trivia service is down.'
|
||||||
|
bot_handler.send_reply(message, bot_response)
|
||||||
|
return
|
||||||
|
elif query.startswith('answer'):
|
||||||
|
try:
|
||||||
|
(quiz_id, answer) = parse_answer(query)
|
||||||
|
except InvalidAnswerException:
|
||||||
|
bot_response = 'Invalid answer format'
|
||||||
|
bot_handler.send_reply(message, bot_response)
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
quiz_payload = bot_handler.storage.get(quiz_id)
|
||||||
|
except KeyError:
|
||||||
|
bot_response = 'Invalid quiz id'
|
||||||
|
bot_handler.send_reply(message, bot_response)
|
||||||
|
return
|
||||||
|
quiz = json.loads(quiz_payload)
|
||||||
|
correct, bot_response = grade_question(quiz, answer)
|
||||||
|
bot_handler.send_reply(message, bot_response)
|
||||||
|
if correct:
|
||||||
|
start_new_quiz(message, bot_handler)
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
bot_response = 'type "new" for a new question'
|
||||||
|
bot_handler.send_reply(message, bot_response)
|
||||||
|
|
||||||
|
def start_new_quiz(message: Dict[str, Any], bot_handler: Any) -> None:
|
||||||
|
quiz = get_trivia_quiz()
|
||||||
|
quiz_id = generate_quiz_id(bot_handler.storage)
|
||||||
|
bot_response = format_quiz_for_markdown(quiz_id, quiz)
|
||||||
|
widget_content = format_quiz_for_widget(quiz_id, quiz)
|
||||||
|
bot_handler.storage.put(quiz_id, json.dumps(quiz))
|
||||||
|
bot_handler.send_reply(message, bot_response, widget_content)
|
||||||
|
|
||||||
|
def parse_answer(query):
|
||||||
|
m = re.match('answer\s+(Q...)\s+(.)', query)
|
||||||
|
if not m:
|
||||||
|
raise InvalidAnswerException()
|
||||||
|
|
||||||
|
quiz_id = m.group(1)
|
||||||
|
answer = m.group(2).upper()
|
||||||
|
if answer not in 'ABCD':
|
||||||
|
raise InvalidAnswerException()
|
||||||
|
|
||||||
|
return (quiz_id, answer)
|
||||||
|
|
||||||
|
def get_trivia_quiz() -> str:
|
||||||
|
payload = get_trivia_payload()
|
||||||
|
quiz = get_quiz_from_payload(payload)
|
||||||
|
return quiz
|
||||||
|
|
||||||
|
def get_trivia_payload() -> str:
|
||||||
|
|
||||||
|
url = 'https://opentdb.com/api.php?amount=1&type=multiple'
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = requests.get(url)
|
||||||
|
|
||||||
|
except requests.exceptions.RequestException:
|
||||||
|
raise NotAvailableException()
|
||||||
|
|
||||||
|
if data.status_code != 200:
|
||||||
|
raise NotAvailableException()
|
||||||
|
|
||||||
|
payload = data.json()
|
||||||
|
return payload
|
||||||
|
|
||||||
|
def fix_quotes(s):
|
||||||
|
# opentdb is nice enough to escape HTML for us, but
|
||||||
|
# we are sending this to code that does that already :)
|
||||||
|
#
|
||||||
|
# Meanwhile Python took until version 3.4 to have a
|
||||||
|
# simple html.unescape function.
|
||||||
|
try:
|
||||||
|
return html.unescape(s)
|
||||||
|
except Exception:
|
||||||
|
raise Exception('Please use python3.4 or later for this bot.')
|
||||||
|
|
||||||
|
def get_quiz_from_payload(payload):
|
||||||
|
result = payload['results'][0]
|
||||||
|
question = result['question']
|
||||||
|
letters = ['A', 'B', 'C', 'D']
|
||||||
|
random.shuffle(letters)
|
||||||
|
correct_letter = letters[0]
|
||||||
|
answers = dict()
|
||||||
|
answers[correct_letter] = result['correct_answer']
|
||||||
|
for i in range(3):
|
||||||
|
answers[letters[i+1]] = result['incorrect_answers'][i]
|
||||||
|
answers = {
|
||||||
|
letter: fix_quotes(answer)
|
||||||
|
for letter, answer
|
||||||
|
in answers.items()
|
||||||
|
}
|
||||||
|
quiz = dict(
|
||||||
|
question=fix_quotes(question),
|
||||||
|
answers=answers,
|
||||||
|
correct_letter=correct_letter,
|
||||||
|
)
|
||||||
|
return quiz
|
||||||
|
|
||||||
|
def generate_quiz_id(storage) -> str:
|
||||||
|
try:
|
||||||
|
quiz_num = storage.get('quiz_id')
|
||||||
|
except KeyError:
|
||||||
|
quiz_num = 0
|
||||||
|
quiz_num += 1
|
||||||
|
quiz_num = quiz_num % (1000)
|
||||||
|
storage.put('quiz_id', quiz_num)
|
||||||
|
quiz_id = 'Q%03d' % (quiz_num,)
|
||||||
|
return quiz_id
|
||||||
|
|
||||||
|
def format_quiz_for_widget(quiz_id, quiz):
|
||||||
|
widget_type = 'zform'
|
||||||
|
question = quiz['question']
|
||||||
|
answers = quiz['answers']
|
||||||
|
|
||||||
|
heading = quiz_id + ': ' + question
|
||||||
|
|
||||||
|
def get_choice(letter):
|
||||||
|
answer = answers[letter]
|
||||||
|
reply = 'answer ' + quiz_id + ' ' + letter
|
||||||
|
|
||||||
|
return dict(
|
||||||
|
type='multiple_choice',
|
||||||
|
short_name=letter,
|
||||||
|
long_name=answer,
|
||||||
|
reply=reply,
|
||||||
|
)
|
||||||
|
|
||||||
|
choices = [get_choice(letter) for letter in 'ABCD']
|
||||||
|
|
||||||
|
extra_data = dict(
|
||||||
|
type='choices',
|
||||||
|
heading=heading,
|
||||||
|
choices=choices,
|
||||||
|
)
|
||||||
|
|
||||||
|
widget_content = dict(
|
||||||
|
widget_type=widget_type,
|
||||||
|
extra_data=extra_data,
|
||||||
|
)
|
||||||
|
payload = json.dumps(widget_content)
|
||||||
|
return payload
|
||||||
|
|
||||||
|
def format_quiz_for_markdown(quiz_id, quiz):
|
||||||
|
question = quiz['question']
|
||||||
|
answers = quiz['answers']
|
||||||
|
answer_list = '\n'.join([
|
||||||
|
'* **{letter}** {answer}'.format(
|
||||||
|
letter=letter,
|
||||||
|
answer=answers[letter],
|
||||||
|
)
|
||||||
|
for letter in 'ABCD'
|
||||||
|
])
|
||||||
|
how_to_respond = '''**reply**: answer {quiz_id} <letter>'''.format(quiz_id=quiz_id)
|
||||||
|
|
||||||
|
content = '''
|
||||||
|
Q: {question}
|
||||||
|
|
||||||
|
{answer_list}
|
||||||
|
{how_to_respond}'''.format(
|
||||||
|
question=question,
|
||||||
|
answer_list=answer_list,
|
||||||
|
how_to_respond=how_to_respond,
|
||||||
|
)
|
||||||
|
return content
|
||||||
|
|
||||||
|
def grade_question(quiz, answer):
|
||||||
|
correct = (answer == quiz['correct_letter'])
|
||||||
|
|
||||||
|
if correct:
|
||||||
|
long_answer = quiz['answers'][answer]
|
||||||
|
response = '**CORRECT!** {long_answer} :tada:'.format(long_answer=long_answer)
|
||||||
|
return correct, response
|
||||||
|
|
||||||
|
response = '**WRONG!** {answer} is not correct :disappointed:'.format(answer=answer)
|
||||||
|
return correct, response
|
||||||
|
|
||||||
|
|
||||||
|
handler_class = TriviaQuizHandler
|
Loading…
Reference in a new issue