Audiotrivia updates from lessons learned attempting core

pull/149/head
bobloy 4 years ago
parent b210f4a9ff
commit 3fceea634b

@ -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!"
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")
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 # Skip tracks with error
continue
tracks = load_result.tracks
track = tracks[0]
seconds = track.length / 1000
if self.settings["repeat"] and seconds < delay:
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 < delay:
self.player.add(self.ctx.author, track)
while tot_length < audio_delay:
player.add(self.ctx.author, track)
tot_length += seconds
else:
self.player.add(self.ctx.author, track)
player.add(self.ctx.author, track)
if not self.player.current:
log.debug("Pressing play")
await self.player.play()
if not player.current:
await player.play()
await self.ctx.maybe_send_embed(msg)
log.debug(f"Audio question: {question}")
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

@ -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,24 +156,32 @@ 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))))
"""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)
return
else:
await ctx.send(msg)
def get_audio_list(self, category: str) -> dict:
@ -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"))

@ -1,4 +1,5 @@
AUTHOR: Plab
AUDIO: "[Audio] Identify this Anime!"
https://www.youtube.com/watch?v=2uq34TeWEdQ:
- 'Hagane no Renkinjutsushi (2009)'
- '(2009) الخيميائي المعدني الكامل'

@ -1,4 +1,5 @@
AUTHOR: Lazar
AUDIO: "[Audio] Identify this NHL Team by their goal horn"
https://youtu.be/6OejNXrGkK0:
- Anaheim Ducks
- Anaheim

@ -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:
Loading…
Cancel
Save