diff --git a/README.md b/README.md
index ce3bc62..098cd6d 100644
--- a/README.md
+++ b/README.md
@@ -5,6 +5,7 @@ Cog Function
| Name | Status | Description (Click to see full status)
| --- | --- | --- |
| announcedaily | **Alpha** | Send daily announcements to all servers at a specified times
Commissioned release, so suggestions will not be accepted |
+| audiotrivia | **Alpha** | Guess the audio using the core trivia cog
Replaces the core Trivia cog. Needs help adding audio trivia lists, please submit a PR to contribute |
| ccrole | **Beta** | Create custom commands that also assign roles
May have some bugs, please create an issue if you find any |
| chatter | **Alpha** | Chat-bot trained to talk like your guild
Missing some key features, but currently functional |
| coglint | **Alpha** | Error check code in python syntax posted to discord
Works, but probably needs more turning to work for cogs |
diff --git a/audiotrivia/__init__.py b/audiotrivia/__init__.py
new file mode 100644
index 0000000..327e34e
--- /dev/null
+++ b/audiotrivia/__init__.py
@@ -0,0 +1,5 @@
+from .audiotrivia import AudioTrivia
+
+
+def setup(bot):
+ bot.add_cog(AudioTrivia(bot))
diff --git a/audiotrivia/audiosession.py b/audiotrivia/audiosession.py
new file mode 100644
index 0000000..780d4b9
--- /dev/null
+++ b/audiotrivia/audiosession.py
@@ -0,0 +1,74 @@
+"""Module to manage audio trivia sessions."""
+import asyncio
+
+import lavalink
+from redbot.cogs.trivia import TriviaSession
+
+
+class AudioSession(TriviaSession):
+ """Class to run a session of audio trivia"""
+
+ def __init__(self, ctx, question_list: dict, settings: dict, player: lavalink.Player):
+ super().__init__(ctx, question_list, settings)
+
+ self.player = player
+
+ @classmethod
+ def start(cls, ctx, question_list, settings, player: lavalink.Player = None):
+ session = cls(ctx, question_list, settings, player)
+ loop = ctx.bot.loop
+ session._task = loop.create_task(session.run())
+ return session
+
+ async def run(self):
+ """Run the audio trivia session.
+
+ In order for the trivia session to be stopped correctly, this should
+ only be called internally by `TriviaSession.start`.
+ """
+ await self._send_startup_msg()
+ max_score = self.settings["max_score"]
+ delay = self.settings["delay"]
+ timeout = self.settings["timeout"]
+ for question, answers in self._iter_questions():
+ async with self.ctx.typing():
+ await asyncio.sleep(3)
+ self.count += 1
+ await self.player.stop()
+
+ msg = "**Question number {}!**\n\nName this audio!".format(self.count)
+ await self.ctx.send(msg)
+ # 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("<>")
+ tracks = await self.player.get_tracks(query)
+ seconds = tracks[0].length / 1000
+
+ if self.settings["repeat"] and seconds < delay:
+ tot_length = seconds + 0
+ while tot_length < delay:
+ self.player.add(self.ctx.author, tracks[0])
+ tot_length += seconds
+ else:
+ self.player.add(self.ctx.author, tracks[0])
+
+ if not self.player.current:
+ await self.player.play()
+
+ continue_ = await self.wait_for_answer(answers, 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.end_game()
+
+ async def end_game(self):
+ await super().end_game()
+ await self.player.disconnect()
diff --git a/audiotrivia/audiotrivia.py b/audiotrivia/audiotrivia.py
new file mode 100644
index 0000000..6328e37
--- /dev/null
+++ b/audiotrivia/audiotrivia.py
@@ -0,0 +1,205 @@
+import datetime
+import pathlib
+from typing import List
+
+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.core import commands, Config, checks
+from redbot.core.bot import Red
+from redbot.core.data_manager import cog_data_path
+from redbot.core.utils.chat_formatting import box
+
+from .audiosession import AudioSession
+
+
+class AudioTrivia(Trivia):
+ """
+ Custom commands
+ Creates commands used to display text and adjust roles
+ """
+
+ 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
+ )
+
+ @commands.group()
+ @commands.guild_only()
+ @checks.mod_or_permissions(administrator=True)
+ async def atriviaset(self, ctx: commands.Context):
+ """Manage Audio Trivia settings."""
+ audioset = self.audioconf.guild(ctx.guild)
+ settings_dict = await audioset.all()
+ msg = box(
+ "**Audio settings**\n"
+ "Answer time limit: {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):
+ """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))
+
+ @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))
+
+ @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.
+
+ 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 = self.bot.get_cog("Audio")
+
+ if self.audio is None:
+ await ctx.send("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:
+ await ctx.send("There is already an ongoing trivia session in this channel.")
+ return
+
+ if not Audio._player_check(ctx):
+ try:
+ if not ctx.author.voice.channel.permissions_for(ctx.me).connect or Audio._userlimit(
+ ctx.author.voice.channel
+ ):
+ return await ctx.send("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.send("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
+ lavaplayer.store("guild", ctx.guild.id)
+
+ if (
+ not ctx.author.voice or ctx.author.voice.channel != lavaplayer.channel
+ ):
+ return await ctx.send("You must be in the voice channel to use the audiotrivia command.")
+
+ trivia_dict = {}
+ authors = []
+ for category in reversed(categories):
+ # We reverse the categories so that the first list's config takes
+ # priority over the others.
+ try:
+ dict_ = self.get_audio_list(category)
+ except FileNotFoundError:
+ await ctx.send(
+ "Invalid category `{0}`. See `{1}audiotrivia list`"
+ " for a list of trivia categories."
+ "".format(category, ctx.prefix)
+ )
+ except InvalidListError:
+ await ctx.send(
+ "There was an error parsing the trivia list for"
+ " the `{}` category. It may be formatted"
+ " incorrectly.".format(category)
+ )
+ else:
+ trivia_dict.update(dict_)
+ authors.append(trivia_dict.pop("AUTHOR", None))
+ continue
+ return
+ if not trivia_dict:
+ await ctx.send(
+ "The trivia list was parsed successfully, however it appears to be empty!"
+ )
+ return
+ settings = await self.conf.guild(ctx.guild).all()
+ audiosettings = await self.audioconf.guild(ctx.guild).all()
+ config = trivia_dict.pop("CONFIG", None)
+ if config and settings["allow_override"]:
+ settings.update(config)
+ settings["lists"] = dict(zip(categories, reversed(authors)))
+
+ # 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)
+ self.trivia_sessions.append(session)
+ 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)
+
+ def get_audio_list(self, category: str) -> dict:
+ """Get the audiotrivia list corresponding to the given category.
+
+ Parameters
+ ----------
+ category : str
+ The desired category. Case sensitive.
+
+ Returns
+ -------
+ `dict`
+ A dict mapping questions (`str`) to answers (`list` of `str`).
+
+ """
+ try:
+ path = next(p for p in self._audio_lists() if p.stem == category)
+ except StopIteration:
+ raise FileNotFoundError("Could not find the `{}` category.".format(category))
+
+ with path.open(encoding="utf-8") as file:
+ try:
+ dict_ = yaml.load(file)
+ except yaml.error.YAMLError as exc:
+ raise InvalidListError("YAML parsing failed.") from exc
+ else:
+ return dict_
+
+ def _audio_lists(self) -> List[pathlib.Path]:
+ personal_lists = [p.resolve() for p in cog_data_path(self).glob("*.yaml")]
+
+ return personal_lists + get_core_lists()
+
+
+def get_core_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/csgo.yaml b/audiotrivia/data/lists/csgo.yaml
new file mode 100644
index 0000000..ab726bb
--- /dev/null
+++ b/audiotrivia/data/lists/csgo.yaml
@@ -0,0 +1,5 @@
+https://www.youtube.com/watch?v=DYWi8qdvWCk:
+- AK47
+- AK 47
+https://www.youtube.com/watch?v=DmuK9Wml88E:
+- P90
\ No newline at end of file
diff --git a/audiotrivia/data/lists/games.yaml b/audiotrivia/data/lists/games.yaml
new file mode 100644
index 0000000..eacde4b
--- /dev/null
+++ b/audiotrivia/data/lists/games.yaml
@@ -0,0 +1,4 @@
+https://www.youtube.com/watch?v=FrceWR4XnVU:
+- shovel knight
+https://www.youtube.com/watch?v=Fn0khIn2wfc:
+- super mario world
\ No newline at end of file
diff --git a/audiotrivia/data/lists/guitar.yaml b/audiotrivia/data/lists/guitar.yaml
new file mode 100644
index 0000000..1c0d07e
--- /dev/null
+++ b/audiotrivia/data/lists/guitar.yaml
@@ -0,0 +1,4 @@
+https://www.youtube.com/watch?v=hfyE220BsD0:
+- holiday
+https://www.youtube.com/watch?v=Hh3U9iPKeXQ:
+- sultans of swing
\ No newline at end of file
diff --git a/audiotrivia/data/lists/league.yaml b/audiotrivia/data/lists/league.yaml
new file mode 100644
index 0000000..323aadd
--- /dev/null
+++ b/audiotrivia/data/lists/league.yaml
@@ -0,0 +1,4 @@
+https://www.youtube.com/watch?v=Hi1kUdreiWk:
+- Jinx
+https://www.youtube.com/watch?v=PNYHFluhOGI:
+- Teemo
\ No newline at end of file
diff --git a/audiotrivia/info.json b/audiotrivia/info.json
new file mode 100644
index 0000000..323a69d
--- /dev/null
+++ b/audiotrivia/info.json
@@ -0,0 +1,20 @@
+{
+ "author": [
+ "Bobloy"
+ ],
+ "bot_version": [
+ 3,
+ 0,
+ 0
+ ],
+ "description": "Start an Audio Trivia game",
+ "hidden": false,
+ "install_msg": "Thank you for installing Audio trivia! Get started with `[p]help AudioTrivia`",
+ "requirements": [],
+ "short": "Start an Audio Trivia game",
+ "tags": [
+ "fox",
+ "bobloy",
+ "games"
+ ]
+}
\ No newline at end of file