From 3fceea634bb6a9e6e590a013cda8819d653f1532 Mon Sep 17 00:00:00 2001 From: bobloy Date: Thu, 1 Oct 2020 09:10:02 -0400 Subject: [PATCH] Audiotrivia updates from lessons learned attempting core --- audiotrivia/audiosession.py | 117 ++++++++----- audiotrivia/audiotrivia.py | 159 +++++++++--------- .../lists/{anime.yaml => audioanime.yaml} | 1 + ...lgoalhorns.yaml => audionhlgoalhorns.yaml} | 1 + .../{videogames.yaml => audiovideogames.yaml} | 1 + 5 files changed, 154 insertions(+), 125 deletions(-) rename audiotrivia/data/lists/{anime.yaml => audioanime.yaml} (99%) rename audiotrivia/data/lists/{nhlgoalhorns.yaml => audionhlgoalhorns.yaml} (97%) rename audiotrivia/data/lists/{videogames.yaml => audiovideogames.yaml} (99%) diff --git a/audiotrivia/audiosession.py b/audiotrivia/audiosession.py index 1bdff02..17fc998 100644 --- a/audiotrivia/audiosession.py +++ b/audiotrivia/audiosession.py @@ -2,9 +2,8 @@ import asyncio import logging -import lavalink -from lavalink.enums import LoadType from redbot.cogs.trivia import TriviaSession +from redbot.cogs.trivia.session import _parse_answers from redbot.core.utils.chat_formatting import bold log = logging.getLogger("red.fox_v3.audiotrivia.audiosession") @@ -13,14 +12,14 @@ log = logging.getLogger("red.fox_v3.audiotrivia.audiosession") class AudioSession(TriviaSession): """Class to run a session of audio trivia""" - def __init__(self, ctx, question_list: dict, settings: dict, player: lavalink.Player): + def __init__(self, ctx, question_list: dict, settings: dict, audio = None): super().__init__(ctx, question_list, settings) - self.player = player + self.audio = audio @classmethod - def start(cls, ctx, question_list, settings, player: lavalink.Player = None): - session = cls(ctx, question_list, settings, player) + def start(cls, ctx, question_list, settings, audio = None): + session = cls(ctx, question_list, settings, audio) loop = ctx.bot.loop session._task = loop.create_task(session.run()) return session @@ -34,57 +33,89 @@ class AudioSession(TriviaSession): await self._send_startup_msg() max_score = self.settings["max_score"] delay = self.settings["delay"] + audio_delay = self.settings["audio_delay"] timeout = self.settings["timeout"] - for question, answers in self._iter_questions(): + if self.audio is not None: + import lavalink + + player = lavalink.get_player(self.ctx.guild.id) + player.store("channel", self.ctx.channel.id) # What's this for? I dunno + await self.audio.set_player_settings(self.ctx) + else: + lavalink = None + player = False + + for question, answers, audio_url in self._iter_questions(): async with self.ctx.typing(): await asyncio.sleep(3) self.count += 1 - await self.player.stop() - - msg = bold(f"Question number {self.count}!") + "\n\nName this audio!" + msg = bold(f"Question number {self.count}!") + f"\n\n{question}" + if player: + await player.stop() + if audio_url: + if not player: + log.debug("Got an audio question in a non-audio trivia session") + continue + + load_result = await player.load_tracks(audio_url) + if ( + load_result.has_error + or load_result.load_type != lavalink.enums.LoadType.TRACK_LOADED + ): + await self.ctx.maybe_send_embed( + "Audio Track has an error, skipping. See logs for details" + ) + log.info(f"Track has error: {load_result.exception_message}") + continue + tracks = load_result.tracks + track = tracks[0] + seconds = track.length / 1000 + track.uri = "" # Hide the info from `now` + if self.settings["repeat"] and seconds < audio_delay: + # Append it until it's longer than the delay + tot_length = seconds + 0 + while tot_length < audio_delay: + player.add(self.ctx.author, track) + tot_length += seconds + else: + player.add(self.ctx.author, track) + + if not player.current: + await player.play() await self.ctx.maybe_send_embed(msg) log.debug(f"Audio question: {question}") - # print("Audio question: {}".format(question)) - - # await self.ctx.invoke(self.audio.play(ctx=self.ctx, query=question)) - # ctx_copy = copy(self.ctx) - - # await self.ctx.invoke(self.player.play, query=question) - query = question.strip("<>") - load_result = await self.player.load_tracks(query) - log.debug(f"{load_result.load_type=}") - if load_result.has_error or load_result.load_type != LoadType.TRACK_LOADED: - await self.ctx.maybe_send_embed(f"Track has error, skipping. See logs for details") - log.info(f"Track has error: {load_result.exception_message}") - continue # Skip tracks with error - tracks = load_result.tracks - - track = tracks[0] - seconds = track.length / 1000 - - if self.settings["repeat"] and seconds < delay: - # Append it until it's longer than the delay - tot_length = seconds + 0 - while tot_length < delay: - self.player.add(self.ctx.author, track) - tot_length += seconds - else: - self.player.add(self.ctx.author, track) - - if not self.player.current: - log.debug("Pressing play") - await self.player.play() - continue_ = await self.wait_for_answer(answers, delay, timeout) + continue_ = await self.wait_for_answer( + answers, audio_delay if audio_url else delay, timeout + ) if continue_ is False: break if any(score >= max_score for score in self.scores.values()): await self.end_game() break else: - await self.ctx.send("There are no more questions!") + await self.ctx.maybe_send_embed("There are no more questions!") await self.end_game() async def end_game(self): await super().end_game() - await self.player.disconnect() + if self.audio is not None: + await self.ctx.invoke(self.audio.command_disconnect) + + def _iter_questions(self): + """Iterate over questions and answers for this session. + + Yields + ------ + `tuple` + A tuple containing the question (`str`) and the answers (`tuple` of + `str`). + + """ + for question, q_data in self.question_list: + answers = _parse_answers(q_data["answers"]) + _audio = q_data["audio"] + if _audio: + yield _audio, answers, question.strip("<>") + else: + yield question, answers, _audio \ No newline at end of file diff --git a/audiotrivia/audiotrivia.py b/audiotrivia/audiotrivia.py index c3ac1e9..9617f32 100644 --- a/audiotrivia/audiotrivia.py +++ b/audiotrivia/audiotrivia.py @@ -1,17 +1,17 @@ import datetime import logging import pathlib -from typing import List +from typing import List, Optional +import discord import lavalink import yaml from redbot.cogs.audio import Audio -from redbot.cogs.trivia import LOG -from redbot.cogs.trivia.trivia import InvalidListError, Trivia +from redbot.cogs.trivia.trivia import InvalidListError, Trivia, get_core_lists from redbot.core import Config, checks, commands from redbot.core.bot import Red from redbot.core.data_manager import cog_data_path -from redbot.core.utils.chat_formatting import box +from redbot.core.utils.chat_formatting import bold, box from .audiosession import AudioSession @@ -28,12 +28,11 @@ class AudioTrivia(Trivia): def __init__(self, bot: Red): super().__init__() self.bot = bot - self.audio = None self.audioconf = Config.get_conf( self, identifier=651171001051118411410511810597, force_registration=True ) - self.audioconf.register_guild(delay=30.0, repeat=True) + self.audioconf.register_guild(audio_delay=30.0, repeat=True) @commands.group() @commands.guild_only() @@ -44,49 +43,42 @@ class AudioTrivia(Trivia): settings_dict = await audioset.all() msg = box( "**Audio settings**\n" - "Answer time limit: {delay} seconds\n" + "Answer time limit: {audio_delay} seconds\n" "Repeat Short Audio: {repeat}" "".format(**settings_dict), lang="py", ) await ctx.send(msg) - @atriviaset.command(name="delay") - async def atriviaset_delay(self, ctx: commands.Context, seconds: float): + @atriviaset.command(name="timelimit") + async def atriviaset_timelimit(self, ctx: commands.Context, seconds: float): """Set the maximum seconds permitted to answer a question.""" if seconds < 4.0: await ctx.send("Must be at least 4 seconds.") return settings = self.audioconf.guild(ctx.guild) - await settings.delay.set(seconds) - await ctx.send("Done. Maximum seconds to answer set to {}.".format(seconds)) + await settings.audo_delay.set(seconds) + await ctx.maybe_send_embed(f"Done. Maximum seconds to answer set to {seconds}.") @atriviaset.command(name="repeat") async def atriviaset_repeat(self, ctx: commands.Context, true_or_false: bool): """Set whether or not short audio will be repeated""" settings = self.audioconf.guild(ctx.guild) await settings.repeat.set(true_or_false) - await ctx.send("Done. Repeating short audio is now set to {}.".format(true_or_false)) + await ctx.maybe_send_embed(f"Done. Repeating short audio is now set to {true_or_false}.") @commands.group(invoke_without_command=True) @commands.guild_only() async def audiotrivia(self, ctx: commands.Context, *categories: str): - """Start trivia session on the specified category. + """Start trivia session on the specified category or categories. + Includes Audio categories. You may list multiple categories, in which case the trivia will involve questions from all of them. """ if not categories and ctx.invoked_subcommand is None: await ctx.send_help() return - - if self.audio is None: - self.audio: Audio = self.bot.get_cog("Audio") - - if self.audio is None: - await ctx.maybe_send_embed("Audio is not loaded. Load it and try again") - return - categories = [c.lower() for c in categories] session = self._get_trivia_session(ctx.channel) if session is not None: @@ -94,45 +86,9 @@ class AudioTrivia(Trivia): "There is already an ongoing trivia session in this channel." ) return - status = await self.audio.config.status() - notify = await self.audio.config.guild(ctx.guild).notify() - - if status: - await ctx.maybe_send_embed( - f"It is recommended to disable audio status with `{ctx.prefix}audioset status`" - ) - - if notify: - await ctx.maybe_send_embed( - f"It is recommended to disable audio notify with `{ctx.prefix}audioset notify`" - ) - - if not self.audio._player_check(ctx): - try: - if not ctx.author.voice.channel.permissions_for( - ctx.me - ).connect or self.audio.is_vc_full(ctx.author.voice.channel): - return await ctx.maybe_send_embed( - "I don't have permission to connect to your channel." - ) - await lavalink.connect(ctx.author.voice.channel) - lavaplayer = lavalink.get_player(ctx.guild.id) - lavaplayer.store("connect", datetime.datetime.utcnow()) - except AttributeError: - return await ctx.maybe_send_embed("Connect to a voice channel first.") - - lavaplayer = lavalink.get_player(ctx.guild.id) - lavaplayer.store("channel", ctx.channel.id) # What's this for? I dunno - - await self.audio.set_player_settings(ctx) - - if not ctx.author.voice or ctx.author.voice.channel != lavaplayer.channel: - return await ctx.maybe_send_embed( - "You must be in the voice channel to use the audiotrivia command." - ) - trivia_dict = {} authors = [] + any_audio = False for category in reversed(categories): # We reverse the categories so that the first list's config takes # priority over the others. @@ -140,19 +96,22 @@ class AudioTrivia(Trivia): dict_ = self.get_audio_list(category) except FileNotFoundError: await ctx.maybe_send_embed( - "Invalid category `{0}`. See `{1}audiotrivia list`" + f"Invalid category `{category}`. See `{ctx.prefix}audiotrivia list`" " for a list of trivia categories." - "".format(category, ctx.prefix) ) except InvalidListError: await ctx.maybe_send_embed( "There was an error parsing the trivia list for" - " the `{}` category. It may be formatted" - " incorrectly.".format(category) + f" the `{category}` category. It may be formatted" + " incorrectly." ) else: - trivia_dict.update(dict_) - authors.append(trivia_dict.pop("AUTHOR", None)) + is_audio = dict_.pop("AUDIO", False) + authors.append(dict_.pop("AUTHOR", None)) + trivia_dict.update( + {_q: {"audio": is_audio, "answers": _a} for _q, _a in dict_.items()} + ) + any_audio = any_audio or is_audio continue return if not trivia_dict: @@ -161,9 +120,35 @@ class AudioTrivia(Trivia): ) return + if not any_audio: + audio = None + else: + audio: Optional["Audio"] = self.bot.get_cog("Audio") + if audio is None: + await ctx.send("Audio lists were parsed but Audio is not loaded!") + return + status = await audio.config.status() + notify = await audio.config.guild(ctx.guild).notify() + + if status: + await ctx.maybe_send_embed( + f"It is recommended to disable audio status with `{ctx.prefix}audioset status`" + ) + + if notify: + await ctx.maybe_send_embed( + f"It is recommended to disable audio notify with `{ctx.prefix}audioset notify`" + ) + + failed = await ctx.invoke(audio.command_summon) + if failed: + return + lavaplayer = lavalink.get_player(ctx.guild.id) + lavaplayer.store("channel", ctx.channel.id) # What's this for? I dunno + settings = await self.config.guild(ctx.guild).all() audiosettings = await self.audioconf.guild(ctx.guild).all() - config = trivia_dict.pop("CONFIG", None) + config = trivia_dict.pop("CONFIG", {"answer": None})["answer"] if config and settings["allow_override"]: settings.update(config) settings["lists"] = dict(zip(categories, reversed(authors))) @@ -171,25 +156,33 @@ class AudioTrivia(Trivia): # Delay in audiosettings overwrites delay in settings combined_settings = {**settings, **audiosettings} session = AudioSession.start( - ctx=ctx, - question_list=trivia_dict, - settings=combined_settings, - player=lavaplayer, + ctx, + trivia_dict, + combined_settings, + audio, ) self.trivia_sessions.append(session) - LOG.debug("New audio trivia session; #%s in %d", ctx.channel, ctx.guild.id) + log.debug("New audio trivia session; #%s in %d", ctx.channel, ctx.guild.id) @audiotrivia.command(name="list") @commands.guild_only() async def audiotrivia_list(self, ctx: commands.Context): - """List available trivia categories.""" - lists = set(p.stem for p in self._audio_lists()) - - msg = box("**Available trivia lists**\n\n{}".format(", ".join(sorted(lists)))) - if len(msg) > 1000: - await ctx.author.send(msg) - return - await ctx.send(msg) + """List available trivia including audio categories.""" + lists = set(p.stem for p in self._all_audio_lists()) + if await ctx.embed_requested(): + await ctx.send( + embed=discord.Embed( + title="Available trivia lists", + colour=await ctx.embed_colour(), + description=", ".join(sorted(lists)), + ) + ) + else: + msg = box(bold("Available trivia lists") + "\n\n" + ", ".join(sorted(lists))) + if len(msg) > 1000: + await ctx.author.send(msg) + else: + await ctx.send(msg) def get_audio_list(self, category: str) -> dict: """Get the audiotrivia list corresponding to the given category. @@ -206,7 +199,7 @@ class AudioTrivia(Trivia): """ try: - path = next(p for p in self._audio_lists() if p.stem == category) + path = next(p for p in self._all_audio_lists() if p.stem == category) except StopIteration: raise FileNotFoundError("Could not find the `{}` category.".format(category)) @@ -218,13 +211,15 @@ class AudioTrivia(Trivia): else: return dict_ - def _audio_lists(self) -> List[pathlib.Path]: + def _all_audio_lists(self) -> List[pathlib.Path]: + # Custom trivia lists uploaded with audiotrivia. Not necessarily audio lists personal_lists = [p.resolve() for p in cog_data_path(self).glob("*.yaml")] - return personal_lists + get_core_lists() + # Add to that custom lists uploaded with trivia and core lists + return personal_lists + get_core_audio_lists() + self._all_lists() -def get_core_lists() -> List[pathlib.Path]: +def get_core_audio_lists() -> List[pathlib.Path]: """Return a list of paths for all trivia lists packaged with the bot.""" core_lists_path = pathlib.Path(__file__).parent.resolve() / "data/lists" return list(core_lists_path.glob("*.yaml")) diff --git a/audiotrivia/data/lists/anime.yaml b/audiotrivia/data/lists/audioanime.yaml similarity index 99% rename from audiotrivia/data/lists/anime.yaml rename to audiotrivia/data/lists/audioanime.yaml index 7a27a0e..8aa518d 100644 --- a/audiotrivia/data/lists/anime.yaml +++ b/audiotrivia/data/lists/audioanime.yaml @@ -1,4 +1,5 @@ AUTHOR: Plab +AUDIO: "[Audio] Identify this Anime!" https://www.youtube.com/watch?v=2uq34TeWEdQ: - 'Hagane no Renkinjutsushi (2009)' - '(2009) الخيميائي المعدني الكامل' diff --git a/audiotrivia/data/lists/nhlgoalhorns.yaml b/audiotrivia/data/lists/audionhlgoalhorns.yaml similarity index 97% rename from audiotrivia/data/lists/nhlgoalhorns.yaml rename to audiotrivia/data/lists/audionhlgoalhorns.yaml index 689f478..9e86313 100644 --- a/audiotrivia/data/lists/nhlgoalhorns.yaml +++ b/audiotrivia/data/lists/audionhlgoalhorns.yaml @@ -1,4 +1,5 @@ AUTHOR: Lazar +AUDIO: "[Audio] Identify this NHL Team by their goal horn" https://youtu.be/6OejNXrGkK0: - Anaheim Ducks - Anaheim diff --git a/audiotrivia/data/lists/videogames.yaml b/audiotrivia/data/lists/audiovideogames.yaml similarity index 99% rename from audiotrivia/data/lists/videogames.yaml rename to audiotrivia/data/lists/audiovideogames.yaml index 5798f0c..d7f594c 100644 --- a/audiotrivia/data/lists/videogames.yaml +++ b/audiotrivia/data/lists/audiovideogames.yaml @@ -1,4 +1,5 @@ AUTHOR: Bobloy +AUDIO: "[Audio] Identify this video game" https://www.youtube.com/watch?v=GBPbJyxqHV0: - Super Mario 64 https://www.youtube.com/watch?v=0jXTBAGv9ZQ: