Skip to content

Commit

Permalink
Merge pull request #145 from devoxin/dev
Browse files Browse the repository at this point in the history
5.4.0
  • Loading branch information
devoxin authored May 10, 2024
2 parents 4ea2dc7 + 8adadab commit 69dca8a
Show file tree
Hide file tree
Showing 12 changed files with 278 additions and 47 deletions.
2 changes: 1 addition & 1 deletion lavalink/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
__author__ = 'Devoxin'
__license__ = 'MIT'
__copyright__ = 'Copyright 2017-present Devoxin'
__version__ = '5.3.0'
__version__ = '5.4.0'


from typing import Type
Expand Down
5 changes: 5 additions & 0 deletions lavalink/abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ async def play_track(self,
Plays the given track.
Warning
-------
Multiple calls to this method within a short timeframe could cause issues with the player's
internal state, which can cause errors when processing a :class:`TrackStartEvent`.
Parameters
----------
track: Union[:class:`AudioTrack`, :class:`DeferredAudioTrack`]
Expand Down
10 changes: 7 additions & 3 deletions lavalink/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,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: Optional[str] = None,
ssl: bool = False, session_id: Optional[str] = None) -> Node:
ssl: bool = False, session_id: Optional[str] = None, connect: bool = True) -> Node:
"""
Shortcut for :func:`NodeManager.add_node`.
Expand All @@ -283,20 +283,24 @@ def add_node(self, host: str, port: int, password: str, region: str, name: Optio
The region to assign this node to.
name: Optional[:class:`str`]
An identifier for the node that will show in logs. Defaults to ``None``.
ssl: Optional[:class:`bool`]
ssl: :class:`bool`
Whether to use SSL for the node. SSL will use ``wss`` and ``https``, instead of ``ws`` and ``http``,
respectively. Your node should support SSL if you intend to enable this, either via reverse proxy or
other methods. Only enable this if you know what you're doing.
session_id: Optional[:class:`str`]
The ID of the session to resume. Defaults to ``None``.
Only specify this if you have the ID of the session you want to resume.
connect: :class:`bool`
Whether to immediately connect to the node after creating it.
If ``False``, you must call :func:`Node.connect` if you require WebSocket functionality.
Returns
-------
:class:`Node`
The created Node instance.
"""
return self.node_manager.add_node(host, port, password, region, name, ssl, session_id)
return self.node_manager.add_node(host, port, password, region, name, ssl,
session_id, connect)

async def get_local_tracks(self, query: str) -> LoadResult:
"""|coro|
Expand Down
147 changes: 141 additions & 6 deletions lavalink/dataio.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,25 +42,76 @@ def _read(self, count: int):
return self._buf.read(count)

def read_byte(self) -> bytes:
"""
Reads a single byte from the stream.
Returns
-------
:class:`bytes`
"""
return self._read(1)

def read_boolean(self) -> bool:
"""
Reads a bool from the stream.
Returns
-------
:class:`bool`
"""
result, = struct.unpack('B', self.read_byte())
return result != 0

def read_unsigned_short(self) -> int:
"""
Reads an unsigned short from the stream.
Returns
-------
:class:`int`
"""
result, = struct.unpack('>H', self._read(2))
return result

def read_int(self) -> int:
"""
Reads an int from the stream.
Returns
-------
:class:`int`
"""
result, = struct.unpack('>i', self._read(4))
return result

def read_long(self) -> int:
"""
Reads a long from the stream.
Returns
-------
:class:`int`
"""
result, = struct.unpack('>Q', self._read(8))
return result

def read_nullable_utf(self, utfm: bool = False) -> Optional[str]:
"""
.. _modified UTF: https://en.wikipedia.org/wiki/UTF-8#Modified_UTF-8
Reads an optional UTF string from the stream.
Internally, this just reads a bool and then a string if the bool is ``True``.
Parameters
----------
utfm: :class:`bool`
Whether to read the string as `modified UTF`_.
Returns
-------
Optional[:class:`str`]
"""
exists = self.read_boolean()

if not exists:
Expand All @@ -69,10 +120,30 @@ def read_nullable_utf(self, utfm: bool = False) -> Optional[str]:
return self.read_utfm() if utfm else self.read_utf().decode()

def read_utf(self) -> bytes:
"""
Reads a UTF string from the stream.
Returns
-------
:class:`bytes`
"""
text_length = self.read_unsigned_short()
return self._read(text_length)

def read_utfm(self) -> str:
"""
.. _modified UTF: https://en.wikipedia.org/wiki/UTF-8#Modified_UTF-8
Reads a UTF string from the stream.
This method is different to :func:`read_utf` as it accounts for
different encoding methods utilised by Java's streams, which uses `modified UTF`_
for character encoding.
Returns
-------
:class:`str`
"""
text_length = self.read_unsigned_short()
utf_string = self._read(text_length)
return read_utfm(text_length, utf_string)
Expand All @@ -86,31 +157,87 @@ def _write(self, data):
self._buf.write(data)

def write_byte(self, byte):
"""
Writes a single byte to the stream.
Parameters
----------
byte: Any
This can be anything ``BytesIO.write()`` accepts.
"""
self._buf.write(byte)

def write_boolean(self, boolean):
def write_boolean(self, boolean: bool):
"""
Writes a bool to the stream.
Parameters
----------
boolean: :class:`bool`
The bool to write.
"""
enc = struct.pack('B', 1 if boolean else 0)
self.write_byte(enc)

def write_unsigned_short(self, short):
def write_unsigned_short(self, short: int):
"""
Writes an unsigned short to the stream.
Parameters
----------
short: :class:`int`
The unsigned short to write.
"""
enc = struct.pack('>H', short)
self._write(enc)

def write_int(self, integer):
def write_int(self, integer: int):
"""
Writes an int to the stream.
Parameters
----------
integer: :class:`int`
The integer to write.
"""
enc = struct.pack('>i', integer)
self._write(enc)

def write_long(self, long_value):
def write_long(self, long_value: int):
"""
Writes a long to the stream.
Parameters
----------
long_value: :class:`int`
The long to write.
"""
enc = struct.pack('>Q', long_value)
self._write(enc)

def write_nullable_utf(self, utf_string):
def write_nullable_utf(self, utf_string: Optional[str]):
"""
Writes an optional string to the stream.
Parameters
----------
utf_string: Optional[:class:`str`]
The optional string to write.
"""
self.write_boolean(bool(utf_string))

if utf_string:
self.write_utf(utf_string)

def write_utf(self, utf_string):
def write_utf(self, utf_string: str):
"""
Writes a utf string to the stream.
Parameters
----------
utf_string: :class:`str`
The string to write.
"""
utf = utf_string.encode('utf8')
byte_len = len(utf)

Expand All @@ -121,6 +248,14 @@ def write_utf(self, utf_string):
self._write(utf)

def finish(self) -> bytes:
"""
Finalizes the stream by writing the necessary flags, byte length etc.
Returns
----------
:class:`bytes`
The finalized stream.
"""
with BytesIO() as track_buf:
byte_len = self._buf.getbuffer().nbytes
flags = byte_len | (1 << 30)
Expand Down
20 changes: 20 additions & 0 deletions lavalink/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ class TrackStuckEvent(Event):
This event is emitted when the currently playing track is stuck (i.e. has not provided any audio).
This is typically a fault of the track's underlying audio stream, and not Lavalink itself.
Note
----
You do not need to manually trigger the start of the next track in the queue within
this event when using the :class:`DefaultPlayer`. This is handled for you.
Attributes
----------
player: :class:`BasePlayer`
Expand All @@ -80,6 +85,11 @@ class TrackExceptionEvent(Event):
"""
This event is emitted when a track encounters an exception during playback.
Note
----
You do not need to manually trigger the start of the next track in the queue within
this event when using the :class:`DefaultPlayer`. This is handled for you.
Attributes
----------
player: :class:`BasePlayer`
Expand Down Expand Up @@ -108,6 +118,11 @@ class TrackEndEvent(Event):
"""
This event is emitted when the player finished playing a track.
Note
----
You do not need to manually trigger the start of the next track in the queue within
this event when using the :class:`DefaultPlayer`. This is handled for you.
Attributes
----------
player: :class:`BasePlayer`
Expand All @@ -132,6 +147,11 @@ class TrackLoadFailedEvent(Event):
produce a playable track. The player will not do anything by itself,
so it is up to you to skip the broken track.
Note
----
This event will not automatically trigger the start of the next track in the queue,
so you must ensure that you do this if you want the player to continue playing from the queue.
Attributes
----------
player: :class:`BasePlayer`
Expand Down
35 changes: 31 additions & 4 deletions lavalink/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
from asyncio import Task
from collections import defaultdict
from time import time
from typing import (TYPE_CHECKING, Any, Dict, List, Optional, Type, TypeVar,
Expand Down Expand Up @@ -62,10 +63,10 @@ class Node:
__slots__ = ('client', 'manager', '_transport', 'region', 'name', 'stats')

def __init__(self, manager, host: str, port: int, password: str, region: str, name: Optional[str] = None,
ssl: bool = False, session_id: Optional[str] = None):
ssl: bool = False, session_id: Optional[str] = None, connect: bool = True):
self.client: 'Client' = manager.client
self.manager: 'NodeManager' = manager
self._transport = Transport(self, host, port, password, ssl, session_id)
self._transport = Transport(self, host, port, password, ssl, session_id, connect)

self.region: str = region
self.name: str = name or f'{region}-{host}:{port}'
Expand Down Expand Up @@ -139,6 +140,32 @@ async def get_rest_latency(self) -> float:

return (time() - start) * 1000

async def connect(self, force: bool = False) -> Optional[Task]:
"""|coro|
Initiates a WebSocket connection to this node.
If a connection already exists, and ``force`` is ``False``, this will not do anything.
Parameters
----------
force: :class:`bool`
Whether to close any existing WebSocket connections and re-establish a connection to
the node.
Returns
-------
Optional[:class:`asyncio.Task`]
The WebSocket connection task, or ``None`` if a WebSocket connection already exists and force
is ``False``.
"""
if self._transport.ws_connected:
if not force:
return None

await self._transport.close()

return self._transport.connect()

async def destroy(self):
"""|coro|
Expand Down Expand Up @@ -576,11 +603,11 @@ async def update_session(self, resuming: bool = MISSING, timeout: int = MISSING)
return await self.request('PATCH', f'sessions/{session_id}', json=json) # type: ignore

@overload
async def request(self, method: str, path: str, *, to: Type[T], trace: bool = ..., versioned: bool = ..., **kwargs) -> T:
async def request(self, method: str, path: str, *, to: Type[str], trace: bool = ..., versioned: bool = ..., **kwargs) -> str:
...

@overload
async def request(self, method: str, path: str, *, to: str, trace: bool = ..., versioned: bool = ..., **kwargs) -> str:
async def request(self, method: str, path: str, *, to: Type[T], trace: bool = ..., versioned: bool = ..., **kwargs) -> T:
...

@overload
Expand Down
Loading

0 comments on commit 69dca8a

Please sign in to comment.