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 .ping import PingCommand
from .version import VersionCommand from .version import VersionCommand
from .summon import SummonCommand from .summon import SummonCommand
from .play import PlayCommand
# Enter the commands of your Pack here! # Enter the commands of your Pack here!
available_commands = [ available_commands = [
PingCommand, PingCommand,
VersionCommand, VersionCommand,
SummonCommand, SummonCommand,
PlayCommand,
] ]
# Don't change this, it should automatically generate __all__ # 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! # Imports go here!
from .summon import SummonEvent from .summon import SummonEvent
from .play import PlayEvent
# Enter the commands of your Pack here! # Enter the commands of your Pack here!
available_events = [ available_events = [
SummonEvent, SummonEvent,
PlayEvent
] ]
# Don't change this, it should automatically generate __all__ # 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): class SummonEvent(Event):
name = "summon" 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): if not isinstance(self.serf, DiscordSerf):
raise UnsupportedError("Summon can't be called on interfaces other than Discord.") raise UnsupportedError("Summon can't be called on interfaces other than Discord.")
if discord is None: if discord is None:

View file

@ -1,7 +1,6 @@
from .ytdlinfo import YtdlInfo from .ytdlinfo import YtdlInfo
from .ytdlfile import YtdlFile from .ytdlfile import YtdlFile
from .ytdlmp3 import YtdlMp3 from .ytdlmp3 import YtdlMp3
from .ytdldiscord import YtdlDiscord
from .errors import * from .errors import *
try: try:
@ -19,4 +18,5 @@ __all__ = [
"NotFoundError", "NotFoundError",
"MultipleFilesError", "MultipleFilesError",
"FileAudioSource", "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 .createrichembed import create_rich_embed
from .escape import escape from .escape import escape
from .discordserf import DiscordSerf from .discordserf import DiscordSerf
from .fileaudiosource import FileAudioSource
from . import discordbard from . import discordbard
__all__ = [ __all__ = [
@ -9,5 +12,4 @@ __all__ = [
"escape", "escape",
"DiscordSerf", "DiscordSerf",
"discordbard", "discordbard",
"FileAudioSource",
] ]

View file

@ -1,9 +1,11 @@
from .discordbard import DiscordBard from .discordbard import DiscordBard
from .dbstack import DBStack
from .dbqueue import DBQueue from .dbqueue import DBQueue
from .fileaudiosource import FileAudioSource
from .ytdldiscord import YtdlDiscord
__all__ = [ __all__ = [
"DBStack",
"DBQueue", "DBQueue",
"DiscordBard", "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 typing import List, AsyncGenerator, Tuple, Any, Dict, Optional
from .discordbard import DiscordBard from .discordbard import DiscordBard
from .ytdldiscord import YtdlDiscord
try: try:
import discord 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 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: try:
import discord import discord
@ -20,7 +22,7 @@ class DiscordBard:
self.voice_client: "discord.VoiceClient" = voice_client self.voice_client: "discord.VoiceClient" = voice_client
"""The voice client that this :class:`DiscordBard` refers to.""" """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.""" """The :class:`YtdlDiscord` that's currently being played."""
self.generator: \ self.generator: \
@ -40,7 +42,7 @@ class DiscordBard:
"""Create an instance of the :class:`DiscordBard`, and initialize its async generator.""" """Create an instance of the :class:`DiscordBard`, and initialize its async generator."""
bard = cls(voice_client=voice_client) bard = cls(voice_client=voice_client)
# noinspection PyTypeChecker # noinspection PyTypeChecker
none = bard.generator.asend(None) none = await bard.generator.asend(None)
assert none is None assert none is None
return bard return bard
@ -90,3 +92,8 @@ class DiscordBard:
Raises: Raises:
UnsupportedError: If :meth:`.peek` is unsupported.""" UnsupportedError: If :meth:`.peek` is unsupported."""
return len(await self.peek()) 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 from royalnet.bard import YtdlInfo, YtdlFile
try: try:
from royalnet.serf.discord.fileaudiosource import FileAudioSource from .fileaudiosource import FileAudioSource
except ImportError: except ImportError:
FileAudioSource = None FileAudioSource = None
@ -72,5 +72,7 @@ class YtdlDiscord:
if FileAudioSource is None: if FileAudioSource is None:
raise ImportError("'discord' extra is not installed") raise ImportError("'discord' extra is not installed")
await self.convert_to_pcm() await self.convert_to_pcm()
with open(self.pcm_filename, "rb") as stream: async with self.lock.normal():
yield FileAudioSource(stream) 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 # TODO: safely move the bot somewhere else
raise CommandError("The bot is already connected in another channel.\n" raise CommandError("The bot is already connected in another channel.\n"
" Please disconnect it before resummoning!") " 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]): async def voice_run(self, guild: "discord.Guild"):
"""Safely change the :class:`DiscordBard` for a 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)