8000 Merge pull request #143 from devoxin/dev · devoxin/Lavalink.py@4ea2dc7 · GitHub
[go: up one dir, main page]

Skip to content

Commit 4ea2dc7

Browse files
authored
Merge pull request #143 from devoxin/dev
5.3.0
2 parents 210dd6a + 2bf388a commit 4ea2dc7

12 files changed

+183
-31
lines changed

docs/lavalink.rst

+8
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ Client
2424
.. autoclass:: Client
2525
:members:
2626

27+
DataIO
28+
------
29+
.. autoclass:: DataReader
30+
:members:
31+
32+
.. autoclass:: DataWriter
33+
:members:
34+
2735
Errors
2836
------
2937
.. autoclass:: ClientError

lavalink/__init__.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@
44
__author__ = 'Devoxin'
55
__license__ = 'MIT'
66
__copyright__ = 'Copyright 2017-present Devoxin'
7-
__version__ = '5.2.0'
7+
__version__ = '5.3.0'
88

99

10+
from typing import Type
11+
1012
from .abc import BasePlayer, DeferredAudioTrack, Source
1113
from .client import Client
14+
from .dataio import DataReader, DataWriter
1215
from .errors import (AuthenticationError, ClientError, InvalidTrack, LoadError,
1316
RequestError)
1417
from .events import (Event, IncomingWebSocketMessage, NodeChangedEvent,
@@ -24,12 +27,13 @@
2427
from .playermanager import PlayerManager
2528
from .server import (AudioTrack, EndReason, LoadResult, LoadResultError,
2629
LoadType, PlaylistInfo, Plugin, Severity)
30+
from .source_decoders import DEFAULT_DECODER_MAPPING
2731
from .stats import Penalty, Stats
2832
from .utils import (decode_track, encode_track, format_time, parse_time,
2933
timestamp_to_millis)
3034

3135

32-
def listener(*events: Event):
36+
def listener(*events: Type[Event]):
3337
"""
3438
Marks this function as an event listener for Lavalink.py.
3539
This **must** be used on class methods, and you must ensure that you register

lavalink/abc.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ class DeferredAudioTrack(ABC, AudioTrack):
274274
for example.
275275
"""
276276
@abstractmethod
277-
async def load(self, client: 'Client'):
277+
async def load(self, client: 'Client') -> Optional[str]:
278278
"""|coro|
279279
280280
Retrieves a base64 string that's playable by Lavalink.
@@ -288,8 +288,9 @@ async def load(self, client: 'Client'):
288288
289289
Returns
290290
-------
291-
:class:`str`
291+
Optional[:class:`str`]
292292
A Lavalink-compatible base64-encoded string containing track metadata.
293+
If a track string cannot be returned, you may return ``None`` or throw a :class:`LoadError`.
293294
"""
294295
raise NotImplementedError
295296

lavalink/client.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
import random
2929
from collections import defaultdict
3030
from inspect import getmembers, ismethod
31-
from typing import (Any, Callable, Dict, List, Optional, Sequence, Set, Tuple,
31+
from typing import (Any, Callable, Dict, Generic, List, Optional, Sequence, Set, Tuple,
3232
Type, TypeVar, Union)
3333

3434
import aiohttp
@@ -47,7 +47,7 @@
4747
EventT = TypeVar('EventT', bound=Event)
4848

4949

50-
class Client:
50+
class Client(Generic[PlayerT]):
5151
"""
5252
Represents a Lavalink client used to manage nodes and connections.
5353
@@ -102,7 +102,7 @@ def __init__(self, user_id: Union[int, str], player: Type[PlayerT] = DefaultPlay
102102
self._user_id: int = int(user_id)
103103
self._event_hooks = defaultdict(list)
104104
self.node_manager: NodeManager = NodeManager(self, regions, connect_back)
105-
self.player_manager: PlayerManager = PlayerManager(self, player)
105+
self.player_manager: PlayerManager[PlayerT] = PlayerManager(self, player)
106106
self.sources: Set[Source] = set()
107107

108108
@property
@@ -113,7 +113,7 @@ def nodes(self) -> List[Node]:
113113
return self.node_manager.nodes
114114

115115
@property
116-
def players(self) -> Dict[int, BasePlayer]:
116+
def players(self) -> Dict[int, PlayerT]:
117117
"""
118118
Convenience shortcut for :attr:`PlayerManager.players`.
119119
"""
@@ -207,7 +207,7 @@ def remove_event_hooks(self, *, events: Optional[Sequence[EventT]] = None, hooks
207207
----------
208208
events: Sequence[:class:`Event`]
209209
The events to remove the hooks from. This parameter can be omitted,
210-
and the events registered on the function via :meth:`listener` will be used instead, if applicable.
210+
and the events registered on the function via :func:`listener` will be used instead, if applicable.
211211
Otherwise, a default value of ``Generic`` is used instead.
212212
hooks: Sequence[Callable]
213213
A list of hook methods to remove.

lavalink/dataio.py

+15-5
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,14 @@
3131

3232
class DataReader:
3333
def __init__(self, base64_str: str):
34-
self._buf: BytesIO = BytesIO(b64decode(base64_str))
34+
self._buf = BytesIO(b64decode(base64_str))
3535

36-
def _read(self, count):
36+
@property
37+
def remaining(self) -> int:
38+
""" The amount of bytes left to be read. """
39+
return self._buf.getbuffer().nbytes - self._buf.tell()
40+
41+
def _read(self, count: int):
3742
return self._buf.read(count)
3843

3944
def read_byte(self) -> bytes:
@@ -55,8 +60,13 @@ def read_long(self) -> int:
5560
result, = struct.unpack('>Q', self._read(8))
5661
return result
5762

58-
def read_nullable_utf(self) -> Optional[str]:
59-
return self.read_utf().decode() if self.read_boolean() else None
63+
def read_nullable_utf(self, utfm: bool = False) -> Optional[str]:
64+
exists = self.read_boolean()
65+
66+
if not exists:
67+
return None
68+
69+
return self.read_utfm() if utfm else self.read_utf().decode()
6070

6171
def read_utf(self) -> bytes:
6272
text_length = self.read_unsigned_short()
@@ -110,7 +120,7 @@ def write_utf(self, utf_string):
110120
self.write_unsigned_short(byte_len)
111121
self._write(utf)
112122

113-
def finish(self):
123+
def finish(self) -> bytes:
114124
with BytesIO() as track_buf:
115125
byte_len = self._buf.getbuffer().nbytes
116126
flags = byte_len | (1 << 30)

lavalink/filters.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,7 @@ def update(self, *, smoothing: float):
485485
------
486486
:class:`ValueError`
487487
"""
488-
smoothing = float('smoothing')
488+
smoothing = float(smoothing)
489489

490490
if smoothing <= 1:
491491
raise ValueError('smoothing must be bigger than 1')

lavalink/player.py

+14
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,20 @@ def get_filter(self, _filter: Union[Type[FilterT], str]):
533533

534534
return self.filters.get(filter_name.lower(), None)
535535

536+
async def remove_filters(self, *filters: Union[Type[FilterT], str]):
537+
"""|coro|
538+
539+
Removes multiple filters from the player, undoing any effects applied to the audio.
540+
This is similar to :func:`remove_filter` but instead allows you to remove multiple filters with one call.
541+
542+
Parameters
543+
----------
544+
filters: Union[Type[:class:`Filter`], :class:`str`]
545+
The filters to remove. Can be filter name, or filter class (**not** an instance of).
546+
"""
547+
for fltr in filters:
548+
await self.remove_filter(fltr)
549+
536550
async def remove_filter(self, _filter: Union[Type[FilterT], str]):
537551
"""|coro|
538552

lavalink/playermanager.py

+29-9
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@
2222
SOFTWARE.
2323
"""
2424
import logging
25-
from typing import (TYPE_CHECKING, Callable, Dict, Iterator, Optional, Tuple,
26-
Type, TypeVar)
25+
from typing import (TYPE_CHECKING, Callable, Dict, Generic, Iterator, Optional,
26+
Tuple, Type, TypeVar, Union, overload)
2727

2828
from .errors import ClientError
2929
from .node import Node
@@ -35,9 +35,10 @@
3535
_log = logging.getLogger(__name__)
3636

3737
PlayerT = TypeVar('PlayerT', bound=BasePlayer)
38+
CustomPlayerT = TypeVar('CustomPlayerT', bound=BasePlayer)
3839

3940

40-
class PlayerManager:
41+
class PlayerManager(Generic[PlayerT]):
4142
"""
4243
Represents the player manager that contains all the players.
4344
@@ -61,22 +62,22 @@ def __init__(self, client, player: Type[PlayerT]):
6162

6263
self.client: 'Client' = client
6364
self._player_cls: Type[PlayerT] = player
64-
self.players: Dict[int, BasePlayer] = {}
65+
self.players: Dict[int, PlayerT] = {}
6566

6667
def __len__(self) -> int:
6768
return len(self.players)
6869

69-
def __iter__(self) -> Iterator[Tuple[int, BasePlayer]]:
70+
def __iter__(self) -> Iterator[Tuple[int, PlayerT]]:
7071
""" Returns an iterator that yields a tuple of (guild_id, player). """
7172
for guild_id, player in self.players.items():
7273
yield guild_id, player
7374

74-
def values(self) -> Iterator[BasePlayer]:
75+
def values(self) -> Iterator[PlayerT]:
7576
""" Returns an iterator that yields only values. """
7677
for player in self.players.values():
7778
yield player
7879

79-
def find_all(self, predicate: Optional[Callable[[BasePlayer], bool]] = None):
80+
def find_all(self, predicate: Optional[Callable[[PlayerT], bool]] = None):
8081
"""
8182
Returns a list of players that match the given predicate.
8283
@@ -96,7 +97,7 @@ def find_all(self, predicate: Optional[Callable[[BasePlayer], bool]] = None):
9697

9798
return [p for p in self.players.values() if bool(predicate(p))]
9899

99-
def get(self, guild_id: int) -> Optional[BasePlayer]:
100+
def get(self, guild_id: int) -> Optional[PlayerT]:
100101
"""
101102
Gets a player from cache.
102103
@@ -126,13 +127,32 @@ def remove(self, guild_id: int):
126127
player = self.players.pop(guild_id)
127128
player.cleanup()
128129

130+
@overload
131+
def create(self,
132+
guild_id: int,
133+
*,
134+
region: Optional[str] = ...,
135+
endpoint: Optional[str] = ...,
136+
node: Optional[Node] = ...) -> PlayerT:
137+
...
138+
139+
@overload
140+
def create(self,
141+
guild_id: int,
142+
*,
143+
region: Optional[str] = ...,
144+
endpoint: Optional[str] = ...,
145+
node: Optional[Node] = ...,
146+
cls: Type[CustomPlayerT]) -> CustomPlayerT:
147+
...
148+
129149
def create(self,
130150
guild_id: int,
131151
*,
132152
region: Optional[str] = None,
133153
endpoint: Optional[str] = None,
134154
node: Optional[Node] = None,
135-
cls: Optional[Type[PlayerT]] = None) -> BasePlayer:
155+
cls: Optional[Type[CustomPlayerT]] = None) -> Union[CustomPlayerT, PlayerT]:
136156
"""
137157
Creates a player if one doesn't exist with the given information.
138158

lavalink/server.py

+14-3
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ class AudioTrack:
8686
The track's uploader.
8787
duration: :class:`int`
8888
The duration of the track, in milliseconds.
89-
stream: :class:`bool`
89+
is_stream: :class:`bool`
9090
Whether the track is a live-stream.
9191
title: :class:`str`
9292
The title of the track.
@@ -110,7 +110,7 @@ class AudioTrack:
110110
extra: Dict[str, Any]
111111
Any extra properties given to this AudioTrack will be stored here.
112112
"""
113-
__slots__ = ('raw', 'track', 'identifier', 'is_seekable', 'author', 'duration', 'stream', 'title', 'uri',
113+
__slots__ = ('raw', 'track', 'identifier', 'is_seekable', 'author', 'duration', 'is_stream', 'title', 'uri',
114114
'artwork_url', 'isrc', 'position', 'source_name', 'plugin_info', 'user_data', 'extra')
115115

116116
def __init__(self, data: dict, requester: int = 0, **extra):
@@ -127,7 +127,7 @@ def __init__(self, data: dict, requester: int = 0, **extra):
127127
self.is_seekable: bool = info['isSeekable']
128128
self.author: str = info['author']
129129
self.duration: int = info['length']
130-
self.stream: bool = info['isStream']
130+
self.is_stream: bool = info['isStream']
131131
self.title: str = info['title']
132132
self.uri: str = info['uri']
133133
self.artwork_url: Optional[str] = info.get('artworkUrl')
@@ -150,6 +150,17 @@ def __getitem__(self, name):
150150
def from_dict(cls, mapping: dict):
151151
return cls(mapping)
152152

153+
@property
154+
def stream(self) -> bool:
155+
"""
156+
Property indicating whether this track is a stream.
157+
158+
.. deprecated:: 5.3.0
159+
To be consistent with attribute naming, this property has been deprecated
160+
in favour of ``is_stream``.
161+
"""
162+
return self.is_stream
163+
153164
@property
154165
def requester(self) -> int:
155166
return self.extra['requester']

lavalink/source_decoders.py

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"""
2+
MIT License
3+
4+
Copyright (c) 2017-present Devoxin
5+
6+
Permission is hereby granted, free of charge, to any person obtaining a copy
7+
of this software and associated documentation files (the "Software"), to deal
8+
in the Software without restriction, including without limitation the rights
9+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
copies of the Software, and to permit persons to whom the Software is
11+
furnished to do so, subject to the following conditions:
12+
13+
The above copyright notice and this permission notice shall be included in all
14+
copies or substantial portions of the Software.
15+
16+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
SOFTWARE.
23+
"""
24+
from typing import Any, Callable, Dict, Mapping
25+
26+
from .dataio import DataReader
27+
28+
29+
def decode_probe_info(reader: DataReader) -> Mapping[str, Any]:
30+
probe_info = reader.read_utf().decode()
31+
return {'probe_info': probe_info}
32+
33+
34+
def decode_lavasrc_fields(reader: DataReader) -> Mapping[str, Any]:
35+
if reader.remaining <= 8: # 8 bytes (long) = position field
36+
return {}
37+
38+
album_name = reader.read_nullable_utf()
39+
album_url = reader.read_nullable_utf()
40+
artist_url = reader.read_nullable_utf()
41+
artist_artwork_url = reader.read_nullable_utf()
42+
preview_url = reader.read_nullable_utf()
43+
is_preview = reader.read_boolean()
44+
45+
return {
46+
'album_name': album_name,
47+
'album_url': album_url,
48+
'artist_url': artist_url,
49+
'artist_artwork_url': artist_artwork_url,
50+
'preview_url': preview_url,
51+
'is_preview': is_preview
52+
}
53+
54+
55+
DEFAULT_DECODER_MAPPING: Dict[str, Callable[[DataReader], Mapping[str, Any]]] = {
56+
'http': decode_probe_info,
57+
'local': decode_probe_info,
58+
'deezer': decode_lavasrc_fields,
59+
'spotify': decode_lavasrc_fields,
60+
'applemusic': decode_lavasrc_fields
61+
}

lavalink/transport.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ async def _listen(self):
181181
try:
182182
await self._handle_message(msg.json())
183183
except Exception: # pylint: disable=W0718
184-
_log.error('[Node:%s] Unexpected error occurred whilst processing websocket message', self._node.name)
184+
_log.exception('[Node:%s] Unexpected error occurred whilst processing websocket message', self._node.name)
185185
elif msg.type == aiohttp.WSMsgType.ERROR:
186186
exc = self._ws.exception()
187187
_log.error('[Node:%s] Exception in WebSocket!', self._node.name, exc_info=exc)

0 commit comments

Comments
 (0)
0