Skip to content

Commit

Permalink
Merge pull request #142 from devoxin/dev
Browse files Browse the repository at this point in the history
5.2.0 changes
  • Loading branch information
devoxin authored Feb 17, 2024
2 parents 7a80dfb + 3e81162 commit c9f80b9
Show file tree
Hide file tree
Showing 19 changed files with 492 additions and 246 deletions.
7 changes: 7 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
# If true, the global TOC tree will also contain hidden entries
globaltoc_includehidden = False

toc_object_entries_show_parents = 'hide'

# -- General configuration ---------------------------------------------------

# Add any Sphinx extension module names here, as strings. They can be
Expand All @@ -39,11 +41,16 @@

extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.intersphinx',
'sphinx.ext.napoleon',
'guzzle_sphinx_theme',
'enum_tools.autoenum'
]

intersphinx_mapping = {
'py': ('https://docs.python.org/3', None)
}

rst_prolog = """
.. |coro| replace:: This function is a |coroutine_link|_.
.. |maybecoro| replace:: This function *could be a* |coroutine_link|_.
Expand Down
8 changes: 3 additions & 5 deletions docs/lavalink.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
Documentation
=============

.. autofunction:: enable_debug_logging

.. autofunction:: listener

ABC
Expand Down Expand Up @@ -39,9 +37,6 @@ Errors
.. autoclass:: RequestError
:members:

.. autoclass:: PlayerErrorEvent
:members:

Events
------
All Events are derived from :class:`Event`
Expand Down Expand Up @@ -88,6 +83,9 @@ All Events are derived from :class:`Event`
.. autoclass:: IncomingWebSocketMessage
:members:

.. autoclass:: PlayerErrorEvent
:members:

Filters
-------
**All** custom filters must derive from :class:`Filter`
Expand Down
10 changes: 5 additions & 5 deletions lavalink/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@
__author__ = 'Devoxin'
__license__ = 'MIT'
__copyright__ = 'Copyright 2017-present Devoxin'
__version__ = '5.1.0'
__version__ = '5.2.0'


from .abc import BasePlayer, DeferredAudioTrack, Source
from .client import Client
from .errors import (AuthenticationError, ClientError, InvalidTrack, LoadError,
PlayerErrorEvent, RequestError)
RequestError)
from .events import (Event, IncomingWebSocketMessage, NodeChangedEvent,
NodeConnectedEvent, NodeDisconnectedEvent, NodeReadyEvent,
PlayerUpdateEvent, QueueEndEvent, TrackEndEvent,
TrackExceptionEvent, TrackLoadFailedEvent,
PlayerErrorEvent, PlayerUpdateEvent, QueueEndEvent,
TrackEndEvent, TrackExceptionEvent, TrackLoadFailedEvent,
TrackStartEvent, TrackStuckEvent, WebSocketClosedEvent)
from .filters import (ChannelMix, Distortion, Equalizer, Filter, Karaoke,
LowPass, Rotation, Timescale, Tremolo, Vibrato, Volume)
Expand Down Expand Up @@ -55,7 +55,7 @@ async def on_track_start(self, event: TrackStartEvent):
Parameters
----------
events: List[:class:`Event`]
events: :class:`Event`
The events to listen for. Leave this empty to listen for all events.
"""
def wrapper(func):
Expand Down
25 changes: 24 additions & 1 deletion lavalink/__main__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,26 @@
"""
MIT License
Copyright (c) 2017-present Devoxin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
import os
import re
import sys
Expand Down Expand Up @@ -129,7 +152,7 @@ def display_help():
""".strip(), file=sys.stdout)


def format_bytes(length: int) -> str:
def format_bytes(length: float) -> str:
sizes = ['B', 'KB', 'MB', 'GB', 'TB']
unit = 0

Expand Down
86 changes: 70 additions & 16 deletions lavalink/abc.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,45 @@
"""
MIT License
Copyright (c) 2017-present Devoxin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
import logging
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
from typing import (TYPE_CHECKING, Any, Dict, Generic, List, Optional, TypeVar,
Union)

from .common import MISSING
from .errors import InvalidTrack, LoadError
from .events import TrackLoadFailedEvent
from .events import Event, TrackLoadFailedEvent
from .server import AudioTrack

if TYPE_CHECKING:
from .client import Client
from .node import Node
from .player import LoadResult
from .server import LoadResult

_log = logging.getLogger(__name__)

FilterValueT = TypeVar('FilterValueT', Dict[str, Any], List[float], List[int], float)


class BasePlayer(ABC):
"""
Expand All @@ -29,7 +55,7 @@ class BasePlayer(ABC):
The node that the player is connected to.
channel_id: Optional[:class:`int`]
The ID of the voice channel the player is connected to.
This could be None if the player isn't connected.
This could be ``None`` if the player isn't connected.
current: Optional[:class:`AudioTrack`]
The currently playing track.
"""
Expand All @@ -46,11 +72,31 @@ def __init__(self, guild_id: int, node: 'Node'):
self._voice_state = {}

@abstractmethod
async def _handle_event(self, event):
async def handle_event(self, event: Event):
"""|coro|
Handles an :class:`Event` received directly from the websocket.
Parameters
----------
event: :class:`Event`
The event that will be handled.
"""
raise NotImplementedError

@abstractmethod
async def _update_state(self, state: dict):
async def update_state(self, state: Dict[str, Any]):
"""|coro|
.. _state object: https://lavalink.dev/api/websocket#player-state
Updates this player's state with the `state object`_ received from the server.
Parameters
----------
state: Dict[:class:`str`, Any]
The player state.
"""
raise NotImplementedError

async def play_track(self,
Expand All @@ -60,9 +106,11 @@ async def play_track(self,
no_replace: bool = MISSING,
volume: int = MISSING,
pause: bool = MISSING,
**kwargs):
**kwargs) -> Optional[Dict[str, Any]]:
"""|coro|
.. _player object: https://lavalink.dev/api/rest.html#Player
Plays the given track.
Parameters
Expand All @@ -80,7 +128,7 @@ async def play_track(self,
no_replace: :class:`bool`
If set to true, operation will be ignored if a track is already playing or paused.
The default behaviour is to always replace.
If left unspecified or None is provided, the default behaviour is exhibited.
If left unspecified or ``None`` is provided, the default behaviour is exhibited.
volume: :class:`int`
The initial volume to set. This is useful for changing the volume between tracks etc.
If left unspecified or ``None`` is provided, the volume will remain at its current setting.
Expand All @@ -91,6 +139,11 @@ async def play_track(self,
**kwargs: Any
The kwargs to use when playing. You can specify any extra parameters that may be
used by plugins, which offer extra features not supported out-of-the-box by Lavalink.py.
Returns
-------
Optional[Dict[:class:`str`, Any]]
The updated `player object`_, or ``None`` if a request wasn't made due to an empty payload.
"""
if track is MISSING or not isinstance(track, AudioTrack):
raise ValueError('track must be an instance of an AudioTrack!')
Expand Down Expand Up @@ -138,13 +191,14 @@ async def play_track(self,
playable_track = await track.load(self.client)
except LoadError as load_error:
await self.client._dispatch_event(TrackLoadFailedEvent(self, track, load_error))
return

if playable_track is None: # This should only fire when a DeferredAudioTrack fails to yield a base64 track string.
await self.client._dispatch_event(TrackLoadFailedEvent(self, track, None))
await self.client._dispatch_event(TrackLoadFailedEvent(self, track, None)) # type: ignore
return

self._next = track
await self.node.update_player(self._internal_id, encoded_track=playable_track, **options)
return await self.node.update_player(guild_id=self._internal_id, encoded_track=playable_track, **options)

def cleanup(self):
pass
Expand Down Expand Up @@ -181,7 +235,7 @@ async def _voice_state_update(self, data):

async def _dispatch_voice_update(self):
if {'sessionId', 'endpoint', 'token'} == self._voice_state.keys():
await self.node.update_player(self._internal_id, voice_state=self._voice_state)
await self.node.update_player(guild_id=self._internal_id, voice_state=self._voice_state)

@abstractmethod
async def node_unavailable(self):
Expand Down Expand Up @@ -270,15 +324,15 @@ async def load_item(self, client: 'Client', query: str) -> Optional['LoadResult'
Returns
-------
Optional[:class:`LoadResult`]
A LoadResult, or None if there were no matches for the provided query.
A LoadResult, or ``None`` if there were no matches for the provided query.
"""
raise NotImplementedError

def __repr__(self):
return f'<Source name={self.name}>'


class Filter:
class Filter(ABC, Generic[FilterValueT]):
"""
A class representing a Lavalink audio filter.
Expand All @@ -297,8 +351,8 @@ class Filter:
plugin_filter: :class:`bool`
Whether this filter is part of a Lavalink plugin.
"""
def __init__(self, values: Union[Dict[str, Any], List[Union[float, int]], float], plugin_filter: bool = False):
self.values = values
def __init__(self, values: FilterValueT, plugin_filter: bool = False):
self.values: FilterValueT = values
self.plugin_filter: bool = plugin_filter

@abstractmethod
Expand All @@ -307,7 +361,7 @@ def update(self, **kwargs):
raise NotImplementedError

@abstractmethod
def serialize(self) -> Dict[str, Any]:
def serialize(self) -> Dict[str, FilterValueT]:
"""
Transforms the internal values into a dict matching the structure Lavalink expects.
Expand Down
45 changes: 40 additions & 5 deletions lavalink/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
import random
from collections import defaultdict
from inspect import getmembers, ismethod
from typing import Any, Dict, List, Optional, Set, Tuple, Type, TypeVar, Union
from typing import (Any, Callable, Dict, List, Optional, Sequence, Set, Tuple,
Type, TypeVar, Union)

import aiohttp

Expand Down Expand Up @@ -98,7 +99,7 @@ def __init__(self, user_id: Union[int, str], player: Type[PlayerT] = DefaultPlay
'the Lavalink client. Alternatively, you can hardcode your user ID.')

self._session: aiohttp.ClientSession = aiohttp.ClientSession()
self._user_id: str = int(user_id)
self._user_id: int = int(user_id)
self._event_hooks = defaultdict(list)
self.node_manager: NodeManager = NodeManager(self, regions, connect_back)
self.player_manager: PlayerManager = PlayerManager(self, player)
Expand Down Expand Up @@ -198,6 +199,40 @@ def add_event_hooks(self, cls: Any): # TODO: I don't think Any is the correct t
else:
self._event_hooks['Generic'].append(listener)

def remove_event_hooks(self, *, events: Optional[Sequence[EventT]] = None, hooks: Sequence[Callable]):
"""
Removes the given hooks from the event hook registry.
Parameters
----------
events: Sequence[:class:`Event`]
The events to remove the hooks from. This parameter can be omitted,
and the events registered on the function via :meth:`listener` will be used instead, if applicable.
Otherwise, a default value of ``Generic`` is used instead.
hooks: Sequence[Callable]
A list of hook methods to remove.
"""
if events is not None:
for event in events:
if Event not in event.__bases__:
raise TypeError(f'{event.__name__} is not of type Event')

for hook in hooks:
if not callable(hook):
raise ValueError(f'Provided hook {hook} is not a callable')

for hook in hooks:
unregister_events = events or getattr(hook, '_lavalink_events', None)

try:
if not unregister_events:
self._event_hooks['Generic'].remove(hook)
else:
for event in unregister_events:
self._event_hooks[event.__name__].remove(hook)
except ValueError:
pass

def register_source(self, source: Source):
"""
Registers a :class:`Source` that Lavalink.py will use for looking up tracks.
Expand Down Expand Up @@ -229,7 +264,7 @@ def get_source(self, source_name: str) -> Optional[Source]:
"""
return next((source for source in self.sources if source.name == source_name), None)

def add_node(self, host: str, port: int, password: str, region: str, name: str = None,
def add_node(self, host: str, port: int, password: str, region: str, name: Optional[str] = None,
ssl: bool = False, session_id: Optional[str] = None) -> Node:
"""
Shortcut for :func:`NodeManager.add_node`.
Expand Down Expand Up @@ -345,7 +380,7 @@ async def decode_track(self, track: str, node: Optional[Node] = None) -> AudioTr
async def decode_tracks(self, tracks: List[str], node: Optional[Node] = None) -> List[AudioTrack]:
"""|coro|
Decodes a list of base64-encoded track strings into ``AudioTrack``s.
Decodes a list of base64-encoded track strings into a list of :class:`AudioTrack`.
Parameters
----------
Expand All @@ -357,7 +392,7 @@ async def decode_tracks(self, tracks: List[str], node: Optional[Node] = None) ->
Returns
-------
List[:class:`AudioTrack`]
A list of decoded ``AudioTrack``s.
A list of decoded :class:`AudioTrack`.
"""
node = node or random.choice(self.node_manager.nodes)
return await node.decode_tracks(tracks)
Expand Down
Loading

0 comments on commit c9f80b9

Please sign in to comment.