mirror of
https://github.com/RYGhub/royalnet.git
synced 2024-11-23 19:44:20 +00:00
MORE MORE MORE DOCS
This commit is contained in:
parent
7583f1330f
commit
b2c28f1735
14 changed files with 186 additions and 43 deletions
|
@ -7,3 +7,6 @@ royalnet.audio
|
|||
|
||||
.. automodule:: royalnet.audio
|
||||
:members:
|
||||
:private-members:
|
||||
:undoc-members:
|
||||
|
||||
|
|
|
@ -4,5 +4,8 @@ royalnet.bots
|
|||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
|
||||
.. automodule:: royalnet.bots
|
||||
:members:
|
||||
:private-members:
|
||||
:undoc-members:
|
||||
|
|
|
@ -7,3 +7,5 @@ royalnet.commands
|
|||
|
||||
.. automodule:: royalnet.commands
|
||||
:members:
|
||||
:private-members:
|
||||
:special-members:
|
||||
|
|
11
docs/conf.py
11
docs/conf.py
|
@ -32,6 +32,17 @@ extensions = ["sphinx.ext.autodoc", "sphinx.ext.napoleon", "sphinx.ext.intersphi
|
|||
intersphinx_mapping = {'python': ('https://docs.python.org/3.7', None),
|
||||
'discord': ('https://discordpy.readthedocs.io/en/latest/', None)}
|
||||
|
||||
|
||||
def skip(app, what, name, obj, would_skip, options):
|
||||
if name == "__init__":
|
||||
return False
|
||||
return would_skip
|
||||
|
||||
|
||||
def setup(app):
|
||||
app.connect("autodoc-skip-member", skip)
|
||||
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
|
|
|
@ -7,6 +7,8 @@ royalnet.database
|
|||
|
||||
.. automodule:: royalnet.database
|
||||
:members:
|
||||
:private-members:
|
||||
:undoc-members:
|
||||
|
||||
|
||||
Tables
|
||||
|
@ -14,3 +16,5 @@ Tables
|
|||
|
||||
.. automodule:: royalnet.database.tables
|
||||
:members:
|
||||
:private-members:
|
||||
:undoc-members:
|
||||
|
|
|
@ -7,3 +7,6 @@ royalnet.network
|
|||
|
||||
.. automodule:: royalnet.network
|
||||
:members:
|
||||
:private-members:
|
||||
:undoc-members:
|
||||
|
||||
|
|
|
@ -7,3 +7,6 @@ royalnet.utils
|
|||
|
||||
.. automodule:: royalnet.utils
|
||||
:members:
|
||||
:private-members:
|
||||
:undoc-members:
|
||||
|
||||
|
|
|
@ -10,36 +10,52 @@ class PlayMode:
|
|||
def __init__(self):
|
||||
"""Create a new PlayMode and initialize the generator inside."""
|
||||
self.now_playing: typing.Optional[RoyalPCMAudio] = None
|
||||
self.generator: typing.AsyncGenerator = self.generate_generator()
|
||||
self.generator: typing.AsyncGenerator = self._generate_generator()
|
||||
|
||||
async def next(self):
|
||||
async def next(self) -> typing.Optional[RoyalPCMAudio]:
|
||||
"""Get the next :py:class:`royalnet.audio.RoyalPCMAudio` from the list and advance it.
|
||||
|
||||
Returns:
|
||||
The next :py:class:`royalnet.audio.RoyalPCMAudio`."""
|
||||
return await self.generator.__anext__()
|
||||
|
||||
def videos_left(self) -> typing.Union[int, float]:
|
||||
"""Return the number of videos left in the PlayMode.
|
||||
|
||||
Usually returns an :py:class:`int`, but may return :py:obj:`math.inf` if the PlayMode is infinite."""
|
||||
Returns:
|
||||
Usually a :py:class:`int`, but may return also :py:obj:`math.inf` if the PlayMode is infinite."""
|
||||
raise NotImplementedError()
|
||||
|
||||
async def generate_generator(self):
|
||||
"""Get the next :py:class:`royalnet.audio.RoyalPCMAudio` from the list and advance it."""
|
||||
async def _generate_generator(self):
|
||||
"""Factory function for an async generator that changes the ``now_playing`` property either to a :py:class:`discord.audio.RoyalPCMAudio` or to ``None``, then yields the value it changed it to.
|
||||
|
||||
Yields:
|
||||
The :py:class:`royalnet.audio.RoyalPCMAudio` to be played next."""
|
||||
raise NotImplementedError()
|
||||
# This is needed to make the coroutine an async generator
|
||||
# noinspection PyUnreachableCode
|
||||
yield NotImplemented
|
||||
|
||||
def add(self, item):
|
||||
"""Add a new :py:class:`royalnet.audio.RoyalPCMAudio` to the PlayMode."""
|
||||
def add(self, item: RoyalPCMAudio) -> None:
|
||||
"""Add a new :py:class:`royalnet.audio.RoyalPCMAudio` to the PlayMode.
|
||||
|
||||
Args:
|
||||
item: The item to add to the PlayMode."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def delete(self):
|
||||
def delete(self) -> None:
|
||||
"""Delete all :py:class:`royalnet.audio.RoyalPCMAudio` contained inside this PlayMode."""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class Playlist(PlayMode):
|
||||
"""A video list. :py:class:`royalnet.audio.RoyalPCMAudio` played are removed from the list."""
|
||||
|
||||
def __init__(self, starting_list: typing.List[RoyalPCMAudio] = None):
|
||||
"""Create a new Playlist.
|
||||
|
||||
Args:
|
||||
starting_list: A list of items with which the Playlist will be created."""
|
||||
super().__init__()
|
||||
if starting_list is None:
|
||||
starting_list = []
|
||||
|
@ -48,7 +64,7 @@ class Playlist(PlayMode):
|
|||
def videos_left(self) -> typing.Union[int, float]:
|
||||
return len(self.list)
|
||||
|
||||
async def generate_generator(self):
|
||||
async def _generate_generator(self):
|
||||
while True:
|
||||
try:
|
||||
next_video = self.list.pop(0)
|
||||
|
@ -60,10 +76,10 @@ class Playlist(PlayMode):
|
|||
if self.now_playing is not None:
|
||||
self.now_playing.delete()
|
||||
|
||||
def add(self, item):
|
||||
def add(self, item) -> None:
|
||||
self.list.append(item)
|
||||
|
||||
def delete(self):
|
||||
def delete(self) -> None:
|
||||
while self.list:
|
||||
self.list.pop(0).delete()
|
||||
self.now_playing.delete()
|
||||
|
@ -71,7 +87,12 @@ class Playlist(PlayMode):
|
|||
|
||||
class Pool(PlayMode):
|
||||
"""A :py:class:`royalnet.audio.RoyalPCMAudio` pool. :py:class:`royalnet.audio.RoyalPCMAudio` are selected in random order and are not repeated until every song has been played at least once."""
|
||||
|
||||
def __init__(self, starting_pool: typing.List[RoyalPCMAudio] = None):
|
||||
"""Create a new Pool.
|
||||
|
||||
Args:
|
||||
starting_pool: A list of items the Pool will be created from."""
|
||||
super().__init__()
|
||||
if starting_pool is None:
|
||||
starting_pool = []
|
||||
|
@ -81,7 +102,7 @@ class Pool(PlayMode):
|
|||
def videos_left(self) -> typing.Union[int, float]:
|
||||
return math.inf
|
||||
|
||||
async def generate_generator(self):
|
||||
async def _generate_generator(self):
|
||||
while True:
|
||||
if not self.pool:
|
||||
self.now_playing = None
|
||||
|
@ -94,12 +115,12 @@ class Pool(PlayMode):
|
|||
self.now_playing = next_video
|
||||
yield next_video
|
||||
|
||||
def add(self, item):
|
||||
def add(self, item) -> None:
|
||||
self.pool.append(item)
|
||||
self._pool_copy.append(item)
|
||||
random.shuffle(self._pool_copy)
|
||||
|
||||
def delete(self):
|
||||
def delete(self) -> None:
|
||||
for item in self.pool:
|
||||
item.delete()
|
||||
self.pool = None
|
||||
|
|
|
@ -8,26 +8,35 @@ class RoyalPCMAudio(AudioSource):
|
|||
"""A discord-compatible :py:class:`discord.AudioSource` that keeps data in a file instead of in memory."""
|
||||
|
||||
def __init__(self, rpf: "RoyalPCMFile"):
|
||||
"""Create a :py:class:`discord.audio.RoyalPCMAudio` from a :ref:`discord.audio.RoyalPCMFile`. Not recommended, use """
|
||||
"""Create a :py:class:`discord.audio.RoyalPCMAudio` from a :py:class:`royalnet.audio.RoyalPCMFile`.
|
||||
|
||||
Warning:
|
||||
Not recommended, use :py:func:`royalnet.audio.RoyalPCMAudio.create_from_url` or :py:func:`royalnet.audio.RoyalPCMAudio.create_from_ytsearch` instead."""
|
||||
self.rpf: "RoyalPCMFile" = rpf
|
||||
self._file = open(self.rpf.audio_filename, "rb")
|
||||
|
||||
@staticmethod
|
||||
def create_from_url(url: str) -> typing.List["RoyalPCMAudio"]:
|
||||
"""Download a file with youtube_dl and create a list of :py:class:`discord.audio.RoyalPCMAudio`.
|
||||
"""Download a file with youtube_dl and create a list of RoyalPCMAudios.
|
||||
|
||||
Parameters:
|
||||
url: The url of the file to download."""
|
||||
url: The url of the file to download.
|
||||
|
||||
Returns:
|
||||
A :py:class:`list` of RoyalPCMAudios, each corresponding to a downloaded video."""
|
||||
rpf_list = RoyalPCMFile.create_from_url(url)
|
||||
return [RoyalPCMAudio(rpf) for rpf in rpf_list]
|
||||
|
||||
@staticmethod
|
||||
def create_from_ytsearch(search: str, amount: int = 1) -> typing.List["RoyalPCMAudio"]:
|
||||
"""Search a string on YouTube and download the first ``amount`` number of videos, then download those with youtube_dl and create a list of :py:class:`discord.audio.RoyalPCMAudio`.
|
||||
"""Search a string on YouTube and download the first ``amount`` number of videos, then download those with youtube_dl and create a list of RoyalPCMAudios.
|
||||
|
||||
Parameters:
|
||||
search: The string to search on YouTube.
|
||||
amount: The number of videos to download."""
|
||||
amount: The number of videos to download.
|
||||
|
||||
Returns:
|
||||
A :py:class:`list` of RoyalPCMAudios, each corresponding to a downloaded video."""
|
||||
rpf_list = RoyalPCMFile.create_from_ytsearch(search, amount)
|
||||
return [RoyalPCMAudio(rpf) for rpf in rpf_list]
|
||||
|
||||
|
|
|
@ -21,12 +21,12 @@ class RoyalPCMFile(YtdlFile):
|
|||
# Set the time to generate the filename
|
||||
self._time = time.time()
|
||||
# Ensure the file doesn't already exist
|
||||
if os.path.exists(self._ytdl_filename) or os.path.exists(self.audio_filename):
|
||||
if os.path.exists(self.ytdl_filename) or os.path.exists(self.audio_filename):
|
||||
raise FileExistsError("Can't overwrite file")
|
||||
# Overwrite the new ytdl_args
|
||||
self.ytdl_args = {**self.ytdl_args, **ytdl_args}
|
||||
log.info(f"Now downloading {info.webpage_url}")
|
||||
super().__init__(info, outtmpl=self._ytdl_filename, **self.ytdl_args)
|
||||
super().__init__(info, outtmpl=self.ytdl_filename, **self.ytdl_args)
|
||||
# Find the audio_filename with a regex (should be video.opus)
|
||||
log.info(f"Converting {self.video_filename}...")
|
||||
# Convert the video to pcm
|
||||
|
@ -50,25 +50,48 @@ class RoyalPCMFile(YtdlFile):
|
|||
"""Download a file with youtube_dl and create a list of :py:class:`discord.audio.RoyalPCMFile`.
|
||||
|
||||
Parameters:
|
||||
url: The url of the file to download."""
|
||||
url: The url of the file to download.
|
||||
ytdl_args: Extra arguments to be passed to YoutubeDL while downloading.
|
||||
|
||||
Returns:
|
||||
A :py:class:`list` of RoyalPCMAudios, each corresponding to a downloaded video."""
|
||||
info_list = YtdlInfo.create_from_url(url)
|
||||
return [RoyalPCMFile(info, **ytdl_args) for info in info_list]
|
||||
|
||||
@staticmethod
|
||||
def create_from_ytsearch(search: str, amount: int = 1, **ytdl_args) -> typing.List["RoyalPCMFile"]:
|
||||
"""Search a string on YouTube and download the first ``amount`` number of videos, then download those with youtube_dl and create a list of :py:class:`discord.audio.RoyalPCMFile`."""
|
||||
"""Search a string on YouTube and download the first ``amount`` number of videos, then download those with youtube_dl and create a list of :py:class:`discord.audio.RoyalPCMFile`.
|
||||
|
||||
Parameters:
|
||||
search: The string to search on YouTube.
|
||||
amount: The number of videos to download.
|
||||
ytdl_args: Extra arguments to be passed to YoutubeDL while downloading.
|
||||
|
||||
Returns:
|
||||
A :py:class:`list` of RoyalPCMFiles, each corresponding to a downloaded video."""
|
||||
url = f"ytsearch{amount}:{search}"
|
||||
info_list = YtdlInfo.create_from_url(url)
|
||||
return [RoyalPCMFile(info, **ytdl_args) for info in info_list]
|
||||
|
||||
@property
|
||||
def _ytdl_filename(self):
|
||||
def ytdl_filename(self) -> str:
|
||||
"""
|
||||
Returns:
|
||||
The name of the downloaded video file, as a :py:class:`str`.
|
||||
|
||||
Warning:
|
||||
It's going to be deleted as soon as the :py:func:`discord.audio.RoyalPCMFile.__init__` function has completed, so it's probably not going to be very useful...
|
||||
"""
|
||||
return f"./downloads/{safefilename(self.info.title)}-{safefilename(str(int(self._time)))}.ytdl"
|
||||
|
||||
@property
|
||||
def audio_filename(self):
|
||||
def audio_filename(self) -> str:
|
||||
"""
|
||||
Returns:
|
||||
The name of the downloaded and PCM-converted audio file."""
|
||||
return f"./downloads/{safefilename(self.info.title)}-{safefilename(str(int(self._time)))}.pcm"
|
||||
|
||||
def delete_audio_file(self):
|
||||
"""Delete the PCM-converted audio file."""
|
||||
log.info(f"Deleting {self.audio_filename}")
|
||||
os.remove(self.audio_filename)
|
||||
|
|
|
@ -15,6 +15,8 @@ class InterruptDownload(DownloaderError):
|
|||
|
||||
|
||||
class YtdlFile:
|
||||
"""A wrapper around a youtube_dl downloaded file."""
|
||||
|
||||
ytdl_args = {
|
||||
"logger": log, # Log messages to a logging.Logger instance.
|
||||
"quiet": True, # Do not print messages to stdout.
|
||||
|
@ -22,7 +24,6 @@ class YtdlFile:
|
|||
"no_warnings": True, # Do not print out anything for warnings.
|
||||
}
|
||||
|
||||
"""A wrapper around a youtube_dl downloaded file."""
|
||||
def __init__(self, info: "YtdlInfo", outtmpl="%(title)s-%(id)s.%(ext)s", **ytdl_args):
|
||||
self.info: "YtdlInfo" = info
|
||||
self.video_filename: str
|
||||
|
@ -57,7 +58,11 @@ class YtdlFile:
|
|||
class YtdlInfo:
|
||||
"""A wrapper around youtube_dl extracted info."""
|
||||
|
||||
def __init__(self, info):
|
||||
def __init__(self, info: typing.Dict[str, typing.Any]):
|
||||
"""Create a YtdlInfo from the dict returned by the :py:func:`youtube_dl.YoutubeDL.extract_info` function.
|
||||
|
||||
Warning:
|
||||
Does not download the info, for that use :py:func:`royalnet.audio.YtdlInfo.create_from_url`."""
|
||||
self.id: typing.Optional[str] = info.get("id")
|
||||
self.uploader: typing.Optional[str] = info.get("uploader")
|
||||
self.uploader_id: typing.Optional[str] = info.get("uploader_id")
|
||||
|
|
|
@ -2,42 +2,70 @@ from ..error import RoyalnetError
|
|||
|
||||
|
||||
class Message:
|
||||
"""A message sent through the Royalnet."""
|
||||
def __repr__(self):
|
||||
return f"<{self.__class__.__name__}>"
|
||||
|
||||
def raise_on_error(self):
|
||||
pass
|
||||
|
||||
|
||||
class IdentifySuccessfulMessage(Message):
|
||||
pass
|
||||
"""The Royalnet identification step was successful."""
|
||||
|
||||
|
||||
class ServerErrorMessage(Message):
|
||||
"""Something went wrong in the connection to the :py:class:`royalnet.network.RoyalnetServer`."""
|
||||
def __init__(self, reason):
|
||||
super().__init__()
|
||||
self.reason = reason
|
||||
|
||||
|
||||
class InvalidSecretEM(ServerErrorMessage):
|
||||
pass
|
||||
"""The sent secret was incorrect.
|
||||
|
||||
This message terminates connection to the :py:class:`royalnet.network.RoyalnetServer`."""
|
||||
|
||||
|
||||
class InvalidPackageEM(ServerErrorMessage):
|
||||
pass
|
||||
"""The sent :py:class:`royalnet.network.Package` was invalid."""
|
||||
|
||||
|
||||
class InvalidDestinationEM(InvalidPackageEM):
|
||||
pass
|
||||
"""The :py:class:`royalnet.network.Package` destination was invalid or not found."""
|
||||
|
||||
|
||||
class RequestSuccessful(Message):
|
||||
pass
|
||||
class Reply(Message):
|
||||
"""A reply to a request sent through the Royalnet."""
|
||||
|
||||
def raise_on_error(self) -> None:
|
||||
"""If the reply is an error, raise an error, otherwise, do nothing.
|
||||
|
||||
Raises:
|
||||
A :py:exc:`RoyalnetError`, if the Reply is an error, otherwise, nothing."""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class RequestError(Message):
|
||||
class RequestSuccessful(Reply):
|
||||
"""The sent request was successful."""
|
||||
|
||||
def raise_on_error(self) -> None:
|
||||
"""If the reply is an error, raise an error, otherwise, do nothing.
|
||||
|
||||
Does nothing."""
|
||||
pass
|
||||
|
||||
|
||||
class RequestError(Reply):
|
||||
"""The sent request wasn't successful."""
|
||||
|
||||
def __init__(self, exc: Exception):
|
||||
"""Create a RequestError.
|
||||
|
||||
Parameters:
|
||||
exc: The exception that caused the error in the request."""
|
||||
self.exc: Exception = exc
|
||||
|
||||
def raise_on_error(self):
|
||||
def raise_on_error(self) -> None:
|
||||
"""If the reply is an error, raise an error, otherwise, do nothing.
|
||||
|
||||
Raises:
|
||||
Always raises a :py:exc:`royalnet.error.RoyalnetError`, containing the exception that caused the error."""
|
||||
raise RoyalnetError(exc=self.exc)
|
||||
|
|
|
@ -3,20 +3,42 @@ import uuid
|
|||
|
||||
|
||||
class Package:
|
||||
"""A Royalnet package, the data type with which a :py:class:`royalnet.network.RoyalnetLink` communicates with a :py:class:`royalnet.network.RoyalnetServer` or another link. """
|
||||
|
||||
def __init__(self, data, destination: str, source: str, *, source_conv_id: str = None, destination_conv_id: str = None):
|
||||
"""Create a Package.
|
||||
|
||||
Parameters:
|
||||
data: The data that should be sent. Usually a :py:class:`royalnet.network.Message`.
|
||||
destination: The ``link_type`` of the destination node, or alternatively, the ``nid`` of the node. Can also be the ``NULL`` value to send the message to nobody.
|
||||
source: The ``nid`` of the node that created this Package.
|
||||
source_conv_id: The conversation id of the node that created this package. Akin to the sequence number on IP packets.
|
||||
destination_conv_id: The conversation id of the node that this Package is a reply to."""
|
||||
# TODO: something is not right in these type hints. Check them.
|
||||
self.data = data
|
||||
self.destination: str = destination
|
||||
self.source = source
|
||||
self.source_conv_id = source_conv_id or str(uuid.uuid4())
|
||||
self.destination_conv_id = destination_conv_id
|
||||
self.source: str = source
|
||||
self.source_conv_id: str = source_conv_id or str(uuid.uuid4())
|
||||
self.destination_conv_id: str = destination_conv_id
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Package to {self.destination}: {self.data.__class__.__name__}>"
|
||||
|
||||
def reply(self, data) -> "Package":
|
||||
"""Reply to this Package with another Package.
|
||||
|
||||
Parameters:
|
||||
data: The data that should be sent. Usually a :py:class:`royalnet.network.Message`.
|
||||
|
||||
Returns:
|
||||
The reply Package."""
|
||||
return Package(data, self.source, self.destination,
|
||||
source_conv_id=str(uuid.uuid4()),
|
||||
destination_conv_id=self.source_conv_id)
|
||||
|
||||
def pickle(self):
|
||||
def pickle(self) -> bytes:
|
||||
""":py:mod:`pickle` this Package.
|
||||
|
||||
Returns:
|
||||
The pickled package in form of bytes."""
|
||||
return pickle.dumps(self)
|
||||
|
|
|
@ -85,7 +85,13 @@ class RoyalnetServer:
|
|||
self._loop.create_task(self.route_package(package))
|
||||
|
||||
def find_destination(self, package: Package) -> typing.List[ConnectedClient]:
|
||||
"""Find a list of destinations for the sent packages"""
|
||||
"""Find a list of destinations for the package.
|
||||
|
||||
Parameters:
|
||||
package: The package to find the destination of.
|
||||
|
||||
Returns:
|
||||
A :py:class:`list` of :py:class:`ConnectedClients` to send the package to."""
|
||||
# Parse destination
|
||||
# Is it nothing?
|
||||
if package.destination == "NULL":
|
||||
|
|
Loading…
Reference in a new issue