mirror of
https://github.com/RYGhub/royalnet.git
synced 2024-11-23 19:44:20 +00:00
Idk?
This commit is contained in:
parent
e11fd430d1
commit
a86f2edd0e
14 changed files with 132 additions and 57 deletions
|
@ -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__
|
||||||
|
|
28
royalnet/backpack/commands/play.py
Normal file
28
royalnet/backpack/commands/play.py
Normal 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"✅ !")
|
|
@ -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__
|
||||||
|
|
54
royalnet/backpack/events/play.py
Normal file
54
royalnet/backpack/events/play.py
Normal 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)
|
|
@ -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:
|
||||||
|
|
|
@ -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",
|
||||||
]
|
]
|
||||||
|
|
|
@ -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",
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -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",
|
||||||
]
|
]
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue