diff --git a/royalnet/backpack/commands/summon.py b/royalnet/backpack/commands/summon.py index 97585f19..decfc408 100644 --- a/royalnet/backpack/commands/summon.py +++ b/royalnet/backpack/commands/summon.py @@ -28,7 +28,7 @@ class SummonCommand(Command): # noinspection PyUnresolvedReferences message: discord.Message = data.message member: Union[discord.User, discord.Member] = message.author - serf: DiscordSerf = self.interface.bot + serf: DiscordSerf = self.interface.serf client: discord.Client = serf.client channel_name: Optional[str] = args.joined() diff --git a/royalnet/bard/discordbard.py b/royalnet/bard/discordbard.py new file mode 100644 index 00000000..548f29e0 --- /dev/null +++ b/royalnet/bard/discordbard.py @@ -0,0 +1,76 @@ +from typing import Optional, AsyncGenerator, List, Tuple, Any, Dict +from .ytdldiscord import YtdlDiscord +from .fileaudiosource import FileAudioSource +from .errors import UnsupportedError + + +class DiscordBard: + """An abstract representation of a music sequence. + + Possible implementation may be playlist, song pools, multilayered tracks, and so on.""" + + def __init__(self): + """Create manually a :class:`DiscordBard`. + + Warning: + Avoid calling this method, please use :meth:`create` instead!""" + self.now_playing: Optional[YtdlDiscord] = None + """The :class:`YtdlDiscord` that's currently being played.""" + + self.generator: \ + AsyncGenerator[FileAudioSource, Tuple[Tuple[Any, ...], Dict[str, Any]]] = self._generate_generator() + """The AsyncGenerator responsible for deciding the next song that should be played.""" + + async def _generate_generator(self) -> AsyncGenerator[FileAudioSource, Tuple[Tuple[Any, ...], Dict[str, Any]]]: + """Create an async generator that returns the next source to be played; + it can take a args+kwargs tuple in input to optionally select a different source. + + The generator should ``yield`` once before doing anything else.""" + args, kwargs = yield + raise NotImplementedError() + + @classmethod + async def create(cls) -> "DiscordBard": + """Create an instance of the :class:`DiscordBard`, and initialize its async generator.""" + bard = cls() + # noinspection PyTypeChecker + none = bard.generator.asend(None) + assert none is None + return bard + + async def next(self, *args, **kwargs) -> Optional[FileAudioSource]: + """Get the next :class:`FileAudioSource` that should be played, and change :attr:`.now_playing`. + + Args and kwargs can be passed to the generator to select differently.""" + fas: Optional[FileAudioSource] = await self.generator.asend((args, kwargs)) + self.now_playing = fas + return fas + + async def add(self, ytd: YtdlDiscord): + """Add a new :class:`YtdlDiscord` to the :class:`DiscordBard`, if possible. + + Raises: + UnsupportedError: If it isn't possible to add new :class:`YtdlDiscord` to the :class:`DiscordBard`. + """ + raise UnsupportedError() + + async def peek(self) -> Optional[List[YtdlDiscord]]: + """Return the contents of the :class:`DiscordBard` as a :class:`list`, if possible. + + Raises: + UnsupportedError: If it isn't possible to display the :class:`DiscordBard` state as a :class:`list`. + """ + raise UnsupportedError() + + async def remove(self, ytd: YtdlDiscord): + """Remove a :class:`YtdlDiscord` from the :class:`DiscordBard`, if possible. + + Raises: + UnsupportedError: If it isn't possible to remove the :class:`YtdlDiscord` from the :class:`DiscordBard`. + """ + raise UnsupportedError() + + async def cleanup(self): + """Enqueue the deletion of all :class:`YtdlDiscord` contained in the :class:`DiscordBard`, and return only once + all deletions are complete.""" + raise NotImplementedError() diff --git a/royalnet/bard/errors.py b/royalnet/bard/errors.py index 1368a7cb..60253203 100644 --- a/royalnet/bard/errors.py +++ b/royalnet/bard/errors.py @@ -12,3 +12,7 @@ class NotFoundError(YtdlError): class MultipleFilesError(YtdlError): """The resource contains multiple media files.""" + + +class UnsupportedError(BardError): + """The method you tried to call on a :class:`DiscordBard` is not supported on that particular Bard.""" \ No newline at end of file diff --git a/royalnet/serf/discord/fileaudiosource.py b/royalnet/bard/fileaudiosource.py similarity index 87% rename from royalnet/serf/discord/fileaudiosource.py rename to royalnet/bard/fileaudiosource.py index 6f97746a..c0695761 100644 --- a/royalnet/serf/discord/fileaudiosource.py +++ b/royalnet/bard/fileaudiosource.py @@ -1,3 +1,6 @@ +from typing import Optional +from .ytdlinfo import YtdlInfo + try: import discord except ImportError: @@ -13,6 +16,10 @@ class FileAudioSource(discord.AudioSource): This AudioSource will consume (and close) the passed stream.""" def __init__(self, file): + """Create a FileAudioSource. + + Arguments: + file: the file to be played back.""" self.file = file def __repr__(self):