diff --git a/cogs/trivia.py b/cogs/trivia.py index e5e8e7b64..944014a1a 100644 --- a/cogs/trivia.py +++ b/cogs/trivia.py @@ -3,7 +3,8 @@ from random import choice from .utils.dataIO import dataIO from .utils import checks from .utils.chat_formatting import box -from collections import Counter, defaultdict +from collections import Counter, defaultdict, namedtuple +import discord import time import os import asyncio @@ -15,6 +16,8 @@ DEFAULTS = {"MAX_SCORE" : 10, "BOT_PLAYS" : False, "REVEAL_ANSWER": True} +TriviaLine = namedtuple("Question", "question answers") + class Trivia: """General commands.""" @@ -86,26 +89,48 @@ class Trivia: await self.bot.say("I'll reveal the answer if no one knows it.") self.save_settings() - @commands.group(pass_context=True, invoke_without_command=True) + @commands.group(pass_context=True, invoke_without_command=True, no_pm=True) async def trivia(self, ctx, list_name: str): """Start a trivia session with the specified list""" message = ctx.message server = message.server session = self.get_trivia_by_channel(message.channel) if not session: - t = TriviaSession(self.bot, message.channel, self.settings[server.id]) - self.trivia_sessions.append(t) - await t.load_questions(list_name) + try: + trivia_list = self.parse_trivia_list(list_name) + except FileNotFoundError: + await self.bot.say("That trivia list doesn't exist.") + except Exception as e: + print(e) + await self.bot.say("Error loading the trivia list.") + else: + settings = self.settings[server.id] + t = TriviaSession(self.bot, trivia_list, message, settings) + self.trivia_sessions.append(t) + await t.new_question() else: await self.bot.say("A trivia session is already ongoing in this channel.") - @trivia.group(name="stop", pass_context=True) + @trivia.group(name="stop", pass_context=True, no_pm=True) async def trivia_stop(self, ctx): """Stops an ongoing trivia session""" + author = ctx.message.author + server = author.server + admin_role = self.bot.settings.get_server_admin(server) + mod_role = self.bot.settings.get_server_mod(server) + is_admin = discord.utils.get(author.roles, name=admin_role) + is_mod = discord.utils.get(author.roles, name=mod_role) + is_owner = author.id == self.bot.settings.owner + is_server_owner = author == server.owner + is_authorized = is_admin or is_mod or is_owner or is_server_owner + session = self.get_trivia_by_channel(ctx.message.channel) if session: - await session.end_game() - await self.bot.say("Trivia stopped.") + if author == session.starter or is_authorized: + await session.end_game() + await self.bot.say("Trivia stopped.") + else: + await self.bot.say("You are not allowed to do that.") else: await self.bot.say("There's no trivia session ongoing in this channel.") @@ -126,6 +151,37 @@ class Trivia: else: await self.bot.say("There are no trivia lists available.") + def parse_trivia_list(self, filename): + path = "data/trivia/{}.txt".format(filename) + parsed_list = [] + + with open(path, "rb") as f: + try: + encoding = chardet.detect(f.read())["encoding"] + except: + encoding = "ISO-8859-1" + + with open(path, "r", encoding=encoding) as f: + trivia_list = f.readlines() + + for line in trivia_list: + if "`" not in line: + continue + line = line.replace("\n", "") + line = line.split("`") + question = line[0] + answers = [] + for l in line[1:]: + answers.append(l.strip()) + if len(line) >= 2 and question and answers: + line = TriviaLine(question=question, answers=answers) + parsed_list.append(line) + + if not parsed_list: + raise ValueError("Empty trivia list") + + return parsed_list + def get_trivia_by_channel(self, channel): for t in self.trivia_sessions: if t.channel == channel: @@ -147,7 +203,7 @@ class Trivia: class TriviaSession(): - def __init__(self, bot, channel, settings): + def __init__(self, bot, trivia_list, message, settings): self.bot = bot self.reveal_messages = ("I know this one! {}!", "Easy: {}.", @@ -156,27 +212,17 @@ class TriviaSession(): "Moving on...", "I'm sure you'll know the answer of the next one.", "\N{PENSIVE FACE} Next one.") - self.current_q = None # {"QUESTION" : "String", "ANSWERS" : []} - self.question_list = "" - self.channel = channel + self.current_line = None # {"QUESTION" : "String", "ANSWERS" : []} + self.question_list = trivia_list + self.channel = message.channel + self.starter = message.author self.scores = Counter() - self.status = None + self.status = "new question" self.timer = None + self.timeout = time.perf_counter() self.count = 0 self.settings = settings - async def load_questions(self, filename): - path = "data/trivia/{}.txt".format(filename) - if os.path.isfile(path): - self.question_list = await self.load_list(path) - self.status = "new question" - self.timeout = time.perf_counter() - if self.question_list: - await self.new_question() - else: - await self.bot.say("There is no list with that name.") - await self.stop_trivia() - async def stop_trivia(self): self.status = "stop" self.bot.dispatch("trivia_end", self) @@ -187,35 +233,6 @@ class TriviaSession(): await self.send_table() self.bot.dispatch("trivia_end", self) - def guess_encoding(self, trivia_list): - with open(trivia_list, "rb") as f: - try: - return chardet.detect(f.read())["encoding"] - except: - return "ISO-8859-1" - - async def load_list(self, qlist): - encoding = self.guess_encoding(qlist) - with open(qlist, "r", encoding=encoding) as f: - qlist = f.readlines() - parsed_list = [] - for line in qlist: - if "`" in line and len(line) > 4: - line = line.replace("\n", "") - line = line.split("`") - question = line[0] - answers = [] - for l in line[1:]: - answers.append(l.lower().strip()) - if len(line) >= 2: - line = {"QUESTION" : question, "ANSWERS": answers} #string, list - parsed_list.append(line) - if parsed_list != []: - return parsed_list - else: - await self.stop_trivia() - return None - async def new_question(self): for score in self.scores.values(): if score == self.settings["MAX_SCORE"]: @@ -224,12 +241,12 @@ class TriviaSession(): if self.question_list == []: await self.end_game() return True - self.current_q = choice(self.question_list) - self.question_list.remove(self.current_q) + self.current_line = choice(self.question_list) + self.question_list.remove(self.current_line) self.status = "waiting for answer" self.count += 1 self.timer = int(time.perf_counter()) - msg = "**Question number {}!**\n\n{}".format(self.count, self.current_q["QUESTION"]) + msg = "**Question number {}!**\n\n{}".format(self.count, self.current_line.question) await self.bot.say(msg) while self.status != "correct answer" and abs(self.timer - int(time.perf_counter())) <= self.settings["DELAY"]: @@ -247,13 +264,13 @@ class TriviaSession(): return True else: if self.settings["REVEAL_ANSWER"]: - msg = choice(self.reveal_messages).format(self.current_q["ANSWERS"][0]) + msg = choice(self.reveal_messages).format(self.current_line.answers[0]) else: msg = choice(self.fail_messages) if self.settings["BOT_PLAYS"]: msg += " **+1** for me!" self.scores[self.bot.user] += 1 - self.current_q["ANSWERS"] = [] + self.current_line = None await self.bot.say(msg) await self.bot.type() await asyncio.sleep(3) @@ -269,10 +286,10 @@ class TriviaSession(): async def check_answer(self, message): if message.author.id != self.bot.user.id: self.timeout = time.perf_counter() - if self.current_q is not None: - for answer in self.current_q["ANSWERS"]: - if answer in message.content.lower(): - self.current_q["ANSWERS"] = [] + if self.current_line is not None: + for answer in self.current_line.answers: + if answer.lower() in message.content.lower(): + self.current_line = None self.status = "correct answer" self.scores[message.author] += 1 msg = "You got it {}! **+1** to you!".format(message.author.name)