update V2 (cleanunp, basic multiple choice, embeds)
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
|||||||
/.env
|
/.env
|
||||||
/__pycache__/
|
/__pycache__/
|
||||||
|
/.idea/
|
||||||
|
287
bot.py
287
bot.py
@ -1,11 +1,12 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
import asyncio
|
||||||
import os
|
import os
|
||||||
|
import random
|
||||||
import re
|
import re
|
||||||
from discord.message import Message
|
|
||||||
from dotenv import load_dotenv
|
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
|
from discord.message import Message
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
from quiz import Quiz
|
from quiz import Quiz
|
||||||
|
|
||||||
@ -33,16 +34,34 @@ class QuizClient(discord.Client):
|
|||||||
self.triggers = {} # automatic actions
|
self.triggers = {} # automatic actions
|
||||||
# Example: self.triggers[lambda m: "jeremy" in m.author.nick.lower()] = self.autoreact_to_jeremy
|
# Example: self.triggers[lambda m: "jeremy" in m.author.nick.lower()] = self.autoreact_to_jeremy
|
||||||
|
|
||||||
self.matchers = {} # react to messages
|
self.matchers = {"hilfe$": self.help,
|
||||||
self.matchers["hilfe$"] = self.help
|
"init: .*": self.init_quiz,
|
||||||
self.matchers["init: .*"] = self.init_quiz
|
"start$": self.run_quiz,
|
||||||
self.matchers["start$"] = self.run_quiz
|
"reset$": self.reset_quiz,
|
||||||
self.matchers["reset$"] = self.reset_quiz
|
"scores$": self.show_scores,
|
||||||
self.matchers["scores$"] = self.show_scores
|
"players$": self.show_players}
|
||||||
self.matchers["players$"] = self.show_players
|
|
||||||
|
|
||||||
# Voicelines
|
# Voicelines
|
||||||
|
|
||||||
|
# Events -------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
async def on_ready(self):
|
||||||
|
print(f"{self.user} (id: {self.user.id}) has connected to Discord!")
|
||||||
|
|
||||||
|
async def on_message(self, message):
|
||||||
|
if message.author == client.user:
|
||||||
|
return
|
||||||
|
|
||||||
|
for trigger in self.triggers:
|
||||||
|
if trigger(message):
|
||||||
|
await self.triggers[trigger](message)
|
||||||
|
break
|
||||||
|
|
||||||
|
for matcher in self.matchers:
|
||||||
|
if self._match(matcher, message):
|
||||||
|
await self.matchers[matcher](message)
|
||||||
|
break
|
||||||
|
|
||||||
# Helpers ------------------------------------------------------------------------------------
|
# Helpers ------------------------------------------------------------------------------------
|
||||||
|
|
||||||
def _help_text(self):
|
def _help_text(self):
|
||||||
@ -73,24 +92,129 @@ class QuizClient(discord.Client):
|
|||||||
"""
|
"""
|
||||||
return re.match(self.prefix_regex + matcher, message.content, re.IGNORECASE)
|
return re.match(self.prefix_regex + matcher, message.content, re.IGNORECASE)
|
||||||
|
|
||||||
# Events -------------------------------------------------------------------------------------
|
def _reset(self):
|
||||||
|
"""
|
||||||
|
Set the saved state to None
|
||||||
|
"""
|
||||||
|
self.quiz = None
|
||||||
|
self.channel = None
|
||||||
|
self.quizmaster = None
|
||||||
|
self.players = dict()
|
||||||
|
self.scores = list()
|
||||||
|
|
||||||
async def on_ready(self):
|
def _is_init(self):
|
||||||
print(f"{self.user} (id: {self.user.id}) has connected to Discord!")
|
"""
|
||||||
|
Check if required state is present to start quiz/get scores etc
|
||||||
|
"""
|
||||||
|
return not (self.quiz is None or
|
||||||
|
self.channel is None or
|
||||||
|
self.quizmaster is None or
|
||||||
|
self.players == dict()) # TODO: Include players here?
|
||||||
|
|
||||||
async def on_message(self, message):
|
async def _quizmaster_confirm(self, message):
|
||||||
if message.author == client.user:
|
"""
|
||||||
|
Adds checkmark to message and waits for quizmaster reaction.
|
||||||
|
Returns the newly fetched message
|
||||||
|
"""
|
||||||
|
await message.add_reaction("✅")
|
||||||
|
|
||||||
|
def check_confirm_players(reaction, user):
|
||||||
|
return reaction.message == message and str(reaction.emoji) == "✅" and user == self.quizmaster
|
||||||
|
|
||||||
|
await self.wait_for("reaction_add", check=check_confirm_players)
|
||||||
|
react_message = discord.utils.get(client.cached_messages, id=message.id)
|
||||||
|
assert isinstance(react_message, Message), "This should be a Message!" # silence pyright
|
||||||
|
return react_message
|
||||||
|
|
||||||
|
async def _determine_players(self):
|
||||||
|
# Players react to this message
|
||||||
|
react_message = await self.channel.send(
|
||||||
|
"Hier mit individuellem Emoji reagieren, am Ende mit dem Haken bestätigen!")
|
||||||
|
react_message = await self._quizmaster_confirm(react_message)
|
||||||
|
|
||||||
|
# Get players from emojis
|
||||||
|
players = {}
|
||||||
|
for reaction in react_message.reactions:
|
||||||
|
if reaction.emoji == "✅":
|
||||||
|
continue
|
||||||
|
|
||||||
|
async for user in reaction.users():
|
||||||
|
if user == self.quizmaster:
|
||||||
|
continue
|
||||||
|
|
||||||
|
players[reaction.emoji] = user # TODO: key value which order?
|
||||||
|
|
||||||
|
return players
|
||||||
|
|
||||||
|
async def _wait_for_players(self):
|
||||||
|
def make_player_check(player):
|
||||||
|
return lambda message: message.author == player
|
||||||
|
|
||||||
|
await asyncio.wait(
|
||||||
|
[asyncio.create_task(self.wait_for("message", check=make_player_check(p))) for p in self.players.values()])
|
||||||
|
|
||||||
|
async def _message_players(self, message):
|
||||||
|
await asyncio.wait([asyncio.create_task(p.send(message)) for p in self.players.values()])
|
||||||
|
|
||||||
|
async def _ask_question(self, question):
|
||||||
|
question_text, answer, embed = question
|
||||||
|
await self._post_question_text(question_text, answer)
|
||||||
|
await self._post_embed(embed)
|
||||||
|
if isinstance(answer, tuple):
|
||||||
|
await self._post_answer_choices(answer)
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def _post_question_text(self, question, answer):
|
||||||
|
await self.channel.send("**Frage:** " + question)
|
||||||
|
|
||||||
|
if not isinstance(answer, tuple):
|
||||||
|
await self._message_players("**Frage:** " + question)
|
||||||
|
|
||||||
|
async def _post_embed(self, embed):
|
||||||
|
if len(embed) < 2:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if embed[0] == "Image":
|
||||||
|
picture = discord.Embed()
|
||||||
|
picture.set_image(url=embed[1])
|
||||||
|
await self.channel.send(embed=picture)
|
||||||
|
elif embed[0] == "Video":
|
||||||
|
await self.channel.send(embed[1])
|
||||||
|
elif embed[0] == "Audio":
|
||||||
|
await self.channel.send("Audio not implemented!")
|
||||||
|
|
||||||
for trigger in self.triggers:
|
async def _post_answer_choices(self, choices):
|
||||||
if trigger(message):
|
"""
|
||||||
await self.triggers[trigger](message)
|
Posts the answers in random order, returns the correct message
|
||||||
break
|
"""
|
||||||
|
choices_rand = list(choices)
|
||||||
|
await self.channel.send("Antwortmöglichkeiten:")
|
||||||
|
while len(choices_rand) > 0:
|
||||||
|
choice = random.choice(choices_rand)
|
||||||
|
choices_rand.remove(choice)
|
||||||
|
await self.channel.send("- " + choice)
|
||||||
|
|
||||||
for matcher in self.matchers:
|
async def _post_answer(self, question):
|
||||||
if self._match(matcher, message):
|
question, answer, embed = question
|
||||||
await self.matchers[matcher](message)
|
|
||||||
break
|
if isinstance(answer, tuple):
|
||||||
|
return await self.channel.send("**Korrekte Antwort:** " + answer[0])
|
||||||
|
|
||||||
|
return await self.channel.send("**Korrekte Antwort:** " + answer)
|
||||||
|
|
||||||
|
async def _fetch_reactants(self, message):
|
||||||
|
turn_scores = list()
|
||||||
|
for reaction in message.reactions:
|
||||||
|
if reaction.emoji == "✅":
|
||||||
|
continue
|
||||||
|
|
||||||
|
async for user in reaction.users():
|
||||||
|
if user != self.quizmaster:
|
||||||
|
continue
|
||||||
|
|
||||||
|
turn_scores.append(reaction.emoji)
|
||||||
|
|
||||||
|
return turn_scores
|
||||||
|
|
||||||
# Commands -----------------------------------------------------------------------------------
|
# Commands -----------------------------------------------------------------------------------
|
||||||
|
|
||||||
@ -105,28 +229,14 @@ class QuizClient(discord.Client):
|
|||||||
Quiz, reset - Gesetzte Werte zurücksetzen (zum neu Initialisieren)
|
Quiz, reset - Gesetzte Werte zurücksetzen (zum neu Initialisieren)
|
||||||
"""
|
"""
|
||||||
await message.channel.send("Resetting...")
|
await message.channel.send("Resetting...")
|
||||||
|
self._reset()
|
||||||
self.quiz = None
|
|
||||||
self.channel = None
|
|
||||||
self.quizmaster = None
|
|
||||||
self.players = dict()
|
|
||||||
self.scores = list()
|
|
||||||
|
|
||||||
await message.channel.send("Finished.")
|
await message.channel.send("Finished.")
|
||||||
|
|
||||||
def is_init(self):
|
|
||||||
return not (self.quiz == None or
|
|
||||||
self.channel == None or
|
|
||||||
self.quizmaster == None or
|
|
||||||
self.players == dict())
|
|
||||||
|
|
||||||
async def init_quiz(self, message):
|
async def init_quiz(self, message):
|
||||||
"""
|
"""
|
||||||
Quiz, init: [NAME] - Initialisiere ein neues Quiz.
|
Quiz, init: [NAME] - Initialisiere ein neues Quiz.
|
||||||
"""
|
"""
|
||||||
|
self._reset() # Reset to enable multiple inits
|
||||||
# Reset to enable multiple inits
|
|
||||||
await self.reset_quiz(message)
|
|
||||||
|
|
||||||
# Set self.channel
|
# Set self.channel
|
||||||
if "quiz" not in message.channel.name.lower():
|
if "quiz" not in message.channel.name.lower():
|
||||||
@ -149,27 +259,7 @@ class QuizClient(discord.Client):
|
|||||||
|
|
||||||
# Set self.players
|
# Set self.players
|
||||||
await self.channel.send("Determining players:")
|
await self.channel.send("Determining players:")
|
||||||
react_message = await self.channel.send(
|
self.players = await self._determine_players()
|
||||||
"Hier mit individuellem Emoji reagieren, am Ende mit dem Haken bestätigen!")
|
|
||||||
await react_message.add_reaction("✅")
|
|
||||||
|
|
||||||
def check_confirm_players(reaction, user):
|
|
||||||
return reaction.message == react_message and str(reaction.emoji) == "✅" and user == self.quizmaster
|
|
||||||
|
|
||||||
await self.wait_for("reaction_add", check=check_confirm_players)
|
|
||||||
react_message = discord.utils.get(client.cached_messages, id=react_message.id)
|
|
||||||
assert isinstance(react_message, Message), "This should be a Message!" # silence pyright
|
|
||||||
|
|
||||||
# Get players from emojis
|
|
||||||
for reaction in react_message.reactions:
|
|
||||||
if reaction.emoji == "✅":
|
|
||||||
continue
|
|
||||||
|
|
||||||
async for user in reaction.users():
|
|
||||||
if user == self.quizmaster:
|
|
||||||
continue
|
|
||||||
|
|
||||||
self.players[reaction.emoji] = user # TODO: key value which order?
|
|
||||||
|
|
||||||
# Send starting message
|
# Send starting message
|
||||||
await self.channel.send("Quiz will start in channel \"" + self.channel.name + "\"")
|
await self.channel.send("Quiz will start in channel \"" + self.channel.name + "\"")
|
||||||
@ -182,70 +272,39 @@ class QuizClient(discord.Client):
|
|||||||
"""
|
"""
|
||||||
Quiz, run - Starte das Quiz
|
Quiz, run - Starte das Quiz
|
||||||
"""
|
"""
|
||||||
if not self.is_init():
|
if not self._is_init():
|
||||||
await message.channel.send("Vorher init du kek")
|
await message.channel.send("Vorher init du kek")
|
||||||
return
|
return
|
||||||
|
|
||||||
if not message.author == self.quizmaster:
|
if not message.author == self.quizmaster:
|
||||||
await self.channel.send("Kein QuizMaster kein Quiz!")
|
await self.channel.send("Kein QuizMaster, kein Quiz!")
|
||||||
return
|
return
|
||||||
|
|
||||||
for question, answer in self.quiz:
|
# Run questions
|
||||||
|
for question in self.quiz:
|
||||||
# post question to players
|
multiple_choice = await self._ask_question(question)
|
||||||
for player in self.players.values():
|
if not multiple_choice:
|
||||||
await player.send("**Frage:** " + question)
|
await self._wait_for_players() # TODO: If this makes problems replace this with manual quizmaster emoji
|
||||||
|
cmsg = await self.channel.send("Alle Spieler haben geantwortet, fortfahren?")
|
||||||
# post question to channel for confirmation
|
else:
|
||||||
await self.channel.send("**Frage:** " + question)
|
cmsg = await self.channel.send("Fortfahren?")
|
||||||
|
await self._quizmaster_confirm(cmsg)
|
||||||
# wait for answers from all players
|
|
||||||
for player in self.players.values():
|
|
||||||
def check_answers_given(message):
|
|
||||||
return message.author == player
|
|
||||||
|
|
||||||
await self.wait_for("message", check=check_answers_given)
|
|
||||||
|
|
||||||
# wait for confirmation
|
|
||||||
cmsg = await self.channel.send("Alle Spieler haben geantwortet, fortfahren?")
|
|
||||||
await cmsg.add_reaction("✅")
|
|
||||||
|
|
||||||
def check_question_finished(reaction, user):
|
|
||||||
return reaction.message == cmsg and str(reaction.emoji) == "✅" and user == self.quizmaster
|
|
||||||
|
|
||||||
await self.wait_for("reaction_add", check=check_question_finished)
|
|
||||||
await self.channel.send("- " * 40)
|
await self.channel.send("- " * 40)
|
||||||
|
|
||||||
# Antworten
|
# Antworten
|
||||||
await self.channel.send("**Antworten:**")
|
if not multiple_choice:
|
||||||
for emoji, player in self.players.items():
|
await self.channel.send("**Antworten:**")
|
||||||
await self.channel.send(
|
for emoji, player in self.players.items():
|
||||||
str(emoji) + ": " + str((await player.dm_channel.history(limit=1).flatten())[0].content))
|
await self.channel.send(
|
||||||
|
str(emoji) + ": " + str((await player.dm_channel.history(limit=1).flatten())[0].content))
|
||||||
|
await asyncio.sleep(0.5) # TODO: Keep this?
|
||||||
|
|
||||||
amsg = await self.channel.send("**Korrekte Antwort:** " + answer)
|
# Correct answer and scores
|
||||||
await amsg.add_reaction("✅")
|
amsg = await self._post_answer(question)
|
||||||
for emoji, player in self.players.items():
|
for emoji, player in self.players.items():
|
||||||
await amsg.add_reaction(emoji)
|
await amsg.add_reaction(emoji)
|
||||||
|
amsg = await self._quizmaster_confirm(amsg)
|
||||||
# Set Points
|
turn_scores = await self._fetch_reactants(amsg)
|
||||||
def check_confirm_points(reaction, user):
|
|
||||||
return reaction.message == amsg and str(reaction.emoji) == "✅" and user == self.quizmaster
|
|
||||||
|
|
||||||
await self.wait_for("reaction_add", check=check_confirm_points)
|
|
||||||
amsg = discord.utils.get(client.cached_messages, id=amsg.id)
|
|
||||||
assert isinstance(amsg, Message), "This should be a Message!" # silence pyright
|
|
||||||
|
|
||||||
turn_scores = list()
|
|
||||||
for reaction in amsg.reactions:
|
|
||||||
if reaction.emoji == "✅":
|
|
||||||
continue
|
|
||||||
|
|
||||||
async for user in reaction.users():
|
|
||||||
if user != self.quizmaster:
|
|
||||||
continue
|
|
||||||
|
|
||||||
turn_scores.append(reaction.emoji)
|
|
||||||
|
|
||||||
self.scores.append(turn_scores)
|
self.scores.append(turn_scores)
|
||||||
|
|
||||||
# Separators at the end
|
# Separators at the end
|
||||||
@ -259,7 +318,7 @@ class QuizClient(discord.Client):
|
|||||||
"""
|
"""
|
||||||
Quiz, scores - Zeigt den aktuellen Punktestand
|
Quiz, scores - Zeigt den aktuellen Punktestand
|
||||||
"""
|
"""
|
||||||
if not self.is_init():
|
if not self._is_init():
|
||||||
await message.channel.send("Vorher init du kek")
|
await message.channel.send("Vorher init du kek")
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -281,7 +340,7 @@ class QuizClient(discord.Client):
|
|||||||
"""
|
"""
|
||||||
Quiz, players - Zeigt die Spielerliste
|
Quiz, players - Zeigt die Spielerliste
|
||||||
"""
|
"""
|
||||||
if not self.is_init():
|
if not self._is_init():
|
||||||
await message.channel.send("Vorher init du kek")
|
await message.channel.send("Vorher init du kek")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
7
quiz.py
7
quiz.py
@ -3,11 +3,18 @@
|
|||||||
class Quiz(object):
|
class Quiz(object):
|
||||||
|
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
|
self.name = name
|
||||||
self.questions = []
|
self.questions = []
|
||||||
|
|
||||||
with open(name) as file:
|
with open(name) as file:
|
||||||
for line in file:
|
for line in file:
|
||||||
|
if len(line.strip()) == 0:
|
||||||
|
continue
|
||||||
|
|
||||||
question = tuple(string.strip() for string in tuple(line.split(" ")))
|
question = tuple(string.strip() for string in tuple(line.split(" ")))
|
||||||
|
if question[1].startswith("{"): # handle multiple choice
|
||||||
|
question = (question[0], tuple(((question[1])[1:-1]).split(", ")), question[2])
|
||||||
|
question = (question[0], question[1], tuple(((question[2])[1:-1]).split(": "))) # unpack embed
|
||||||
self.questions.append(question)
|
self.questions.append(question)
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
|
Reference in New Issue
Block a user