1
Fork 0
mirror of https://github.com/RYGhub/royalnet.git synced 2024-11-23 19:44:20 +00:00
This commit is contained in:
Steffo 2019-11-21 02:01:33 +01:00
parent e11fd430d1
commit a86f2edd0e
14 changed files with 132 additions and 57 deletions

View file

@ -2,12 +2,14 @@
from .ping import PingCommand
from .version import VersionCommand
from .summon import SummonCommand
from .play import PlayCommand
# Enter the commands of your Pack here!
available_commands = [
PingCommand,
VersionCommand,
SummonCommand,
PlayCommand,
]
# Don't change this, it should automatically generate __all__

View file

@ -0,0 +1,28 @@
from royalnet.commands import *
try:
import discord
except ImportError:
discord = None
class PlayCommand(Command):
# TODO: possibly move this in another pack
name: str = "play"
description = "Download a file located at an URL and play it on Discord."
syntax = "[url]"
async def run(self, args: CommandArgs, data: CommandData) -> None:
if self.interface.name != "discord":
raise UnsupportedError()
msg: "discord.Message" = data.message
guild: "discord.Guild" = msg.guild
url: str = args.joined()
response: dict = await self.interface.call_herald_event("discord", "play", {
"guild_id": guild.id,
"url": url,
})
await data.reply(f"✅ !")

View file

@ -1,9 +1,11 @@
# Imports go here!
from .summon import SummonEvent
from .play import PlayEvent
# Enter the commands of your Pack here!
available_events = [
SummonEvent,
PlayEvent
]
# Don't change this, it should automatically generate __all__

View file

@ -0,0 +1,54 @@
from typing import Optional
from royalnet.commands import *
from royalnet.serf.discord import DiscordSerf
from royalnet.serf.discord.discordbard import YtdlDiscord, DiscordBard
from royalnet.utils import asyncify
import logging
log = logging.getLogger(__name__)
try:
import discord
except ImportError:
discord = None
class PlayEvent(Event):
name = "play"
async def run(self, *, url: str, guild_id: Optional[int] = None, guild_name: Optional[str] = None, **kwargs):
if not isinstance(self.serf, DiscordSerf):
raise UnsupportedError("Play can't be called on interfaces other than Discord.")
if discord is None:
raise UnsupportedError("'discord' extra is not installed.")
# Variables
client = self.serf.client
# Find the guild
guild: Optional["discord.Guild"] = None
if guild_id is not None:
guild = client.get_guild(guild_id)
elif guild_name is not None:
for g in client.guilds:
if g.name == guild_name:
guild = g
break
if guild is None:
raise InvalidInputError("No guild_id or guild_name specified.")
# Find the bard
bard: Optional[DiscordBard] = self.serf.bards.get(guild)
if bard is None:
raise CommandError("Bot is not connected to voice chat.")
# Create the YtdlDiscords
log.debug(f"Downloading: {url}")
try:
ytdl = await YtdlDiscord.from_url(url)
except Exception as exc:
breakpoint()
return
# Add the YtdlDiscords to the queue
log.debug(f"Adding to bard: {ytdl}")
for ytd in ytdl:
await bard.add(ytd)
# Run the bard
log.debug(f"Running voice for: {guild}")
await self.serf.voice_run(guild)

View file

@ -11,7 +11,7 @@ except ImportError:
class SummonEvent(Event):
name = "summon"
async def run(self, *, channel_name: str, guild_id: Optional[int], user_id: Optional[int], **kwargs):
async def run(self, *, channel_name: str, guild_id: Optional[int] = None, user_id: Optional[int] = None, **kwargs):
if not isinstance(self.serf, DiscordSerf):
raise UnsupportedError("Summon can't be called on interfaces other than Discord.")
if discord is None:

View file

@ -1,7 +1,6 @@
from .ytdlinfo import YtdlInfo
from .ytdlfile import YtdlFile
from .ytdlmp3 import YtdlMp3
from .ytdldiscord import YtdlDiscord
from .errors import *
try:
@ -19,4 +18,5 @@ __all__ = [
"NotFoundError",
"MultipleFilesError",
"FileAudioSource",
"UnsupportedError",
]

View file

@ -1,7 +1,10 @@
"""A :class:`Serf` implementation for Discord.
It is pretty unstable, compared to the rest of the bot, but it *should* work."""
from .createrichembed import create_rich_embed
from .escape import escape
from .discordserf import DiscordSerf
from .fileaudiosource import FileAudioSource
from . import discordbard
__all__ = [
@ -9,5 +12,4 @@ __all__ = [
"escape",
"DiscordSerf",
"discordbard",
"FileAudioSource",
]

View file

@ -1,9 +1,11 @@
from .discordbard import DiscordBard
from .dbstack import DBStack
from .dbqueue import DBQueue
from .fileaudiosource import FileAudioSource
from .ytdldiscord import YtdlDiscord
__all__ = [
"DBStack",
"DBQueue",
"DiscordBard",
"FileAudioSource",
"YtdlDiscord",
]

View file

@ -1,6 +1,7 @@
from royalnet.bard import FileAudioSource, YtdlDiscord
from royalnet.bard import FileAudioSource
from typing import List, AsyncGenerator, Tuple, Any, Dict, Optional
from .discordbard import DiscordBard
from .ytdldiscord import YtdlDiscord
try:
import discord

View file

@ -1,41 +0,0 @@
from typing import List, AsyncGenerator, Tuple, Any, Dict, Optional
from royalnet.bard import FileAudioSource, YtdlDiscord
from .discordbard import DiscordBard
try:
import discord
except ImportError:
discord = None
class DBStack(DiscordBard):
"""A First-In-Last-Out music queue.
Not really sure if it is going to be useful..."""
def __init__(self, voice_client: "discord.VoiceClient"):
super().__init__(voice_client)
self.list: List[YtdlDiscord] = []
async def _generator(self) -> AsyncGenerator[Optional[FileAudioSource], Tuple[Tuple[Any, ...], Dict[str, Any]]]:
yield
while True:
try:
ytd = self.list.pop()
except IndexError:
yield None
else:
async with ytd.spawn_audiosource() as fas:
yield fas
async def add(self, ytd: YtdlDiscord):
self.list.append(ytd)
async def peek(self) -> List[YtdlDiscord]:
return self.list
async def remove(self, ytd: YtdlDiscord):
self.list.remove(ytd)
async def cleanup(self) -> None:
for ytd in self.list:
await ytd.delete_asap()

View file

@ -1,5 +1,7 @@
from typing import Optional, AsyncGenerator, List, Tuple, Any, Dict
from royalnet.bard import YtdlDiscord, FileAudioSource, UnsupportedError
from royalnet.bard import UnsupportedError
from .fileaudiosource import FileAudioSource
from .ytdldiscord import YtdlDiscord
try:
import discord
@ -20,7 +22,7 @@ class DiscordBard:
self.voice_client: "discord.VoiceClient" = voice_client
"""The voice client that this :class:`DiscordBard` refers to."""
self.now_playing: Optional[YtdlDiscord] = None
self.now_playing: Optional[FileAudioSource] = None
"""The :class:`YtdlDiscord` that's currently being played."""
self.generator: \
@ -40,7 +42,7 @@ class DiscordBard:
"""Create an instance of the :class:`DiscordBard`, and initialize its async generator."""
bard = cls(voice_client=voice_client)
# noinspection PyTypeChecker
none = bard.generator.asend(None)
none = await bard.generator.asend(None)
assert none is None
return bard
@ -90,3 +92,8 @@ class DiscordBard:
Raises:
UnsupportedError: If :meth:`.peek` is unsupported."""
return len(await self.peek())
async def stop(self):
"""Stop the playback of the current song."""
if self.now_playing is not None:
self.now_playing.stop()

View file

@ -6,7 +6,7 @@ from royalnet.utils import asyncify, MultiLock
from royalnet.bard import YtdlInfo, YtdlFile
try:
from royalnet.serf.discord.fileaudiosource import FileAudioSource
from .fileaudiosource import FileAudioSource
except ImportError:
FileAudioSource = None
@ -72,5 +72,7 @@ class YtdlDiscord:
if FileAudioSource is None:
raise ImportError("'discord' extra is not installed")
await self.convert_to_pcm()
with open(self.pcm_filename, "rb") as stream:
yield FileAudioSource(stream)
async with self.lock.normal():
with open(self.pcm_filename, "rb") as stream:
fas = FileAudioSource(stream)
yield fas

View file

@ -247,7 +247,23 @@ class DiscordSerf(Serf):
# TODO: safely move the bot somewhere else
raise CommandError("The bot is already connected in another channel.\n"
" Please disconnect it before resummoning!")
self.bards[channel.guild] = DBQueue(voice_client=voice_client)
self.bards[channel.guild] = await DBQueue.create(voice_client=voice_client)
async def voice_change(self, guild: "discord.Guild", bard: Type[DiscordBard]):
"""Safely change the :class:`DiscordBard` for a guild."""
async def voice_run(self, guild: "discord.Guild"):
"""Send the data from the bard to the voice websocket for a specific client."""
bard: Optional[DiscordBard] = self.bards.get(guild)
if bard is None:
return
def finished_playing(error=None):
if error:
log.error(f"Finished playing with error: {error}")
return
self.loop.create_task(self.voice_run(guild))
if bard.now_playing is None:
fas = await bard.next()
# FIXME: possible race condition here, pls check
bard = self.bards.get(guild)
if bard.voice_client is not None and bard.voice_client.is_connected():
bard.voice_client.play(fas, after=finished_playing)