Skip to content

Commit

Permalink
Add extras for userData in Lavalink (#261)
Browse files Browse the repository at this point in the history
* Add extras for userData in Lavalink

* Add migrating docs
  • Loading branch information
EvieePy authored Dec 8, 2023
1 parent 2cf793d commit a9ed696
Show file tree
Hide file tree
Showing 8 changed files with 131 additions and 3 deletions.
1 change: 1 addition & 0 deletions docs/migrating.rst
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ Added
- :meth:`wavelink.Node.fetch_version`
- :meth:`wavelink.Node.fetch_player_info`
- :meth:`wavelink.Node.fetch_players`
- :attr:`wavelink.Playable.extras`


Connecting
Expand Down
9 changes: 9 additions & 0 deletions docs/wavelink.rst
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,15 @@ Filters
:members:


Utils
-----

.. attributetable:: ExtrasNamespace

.. autoclass:: ExtrasNamespace



Exceptions
----------

Expand Down
1 change: 1 addition & 0 deletions wavelink/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,4 @@
from .player import Player as Player
from .queue import *
from .tracks import *
from .utils import ExtrasNamespace as ExtrasNamespace
4 changes: 2 additions & 2 deletions wavelink/player.py
Original file line number Diff line number Diff line change
Expand Up @@ -667,7 +667,7 @@ async def play(
self._filters = filters

request: RequestPayload = {
"encodedTrack": track.encoded,
"track": {"encoded": track.encoded, "userData": dict(track.extras)},
"volume": vol,
"position": start,
"endTime": end,
Expand Down Expand Up @@ -850,7 +850,7 @@ async def skip(self, *, force: bool = True) -> Playable | None:
if force:
self.queue._loaded = None

request: RequestPayload = {"encodedTrack": None}
request: RequestPayload = {"track": {"encoded": None}}
await self.node._update_player(self.guild.id, data=request, replace=True)

return old
Expand Down
47 changes: 47 additions & 0 deletions wavelink/tracks.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import wavelink

from .enums import TrackSource
from .utils import ExtrasNamespace

if TYPE_CHECKING:
from .types.tracks import (
Expand Down Expand Up @@ -134,6 +135,8 @@ def __init__(self, data: TrackPayload, *, playlist: PlaylistInfo | None = None)
self._playlist = playlist
self._recommended: bool = False

self._extras: ExtrasNamespace = ExtrasNamespace(data.get("userData", {}))

def __hash__(self) -> int:
return hash(self.encoded)

Expand Down Expand Up @@ -247,6 +250,50 @@ def recommended(self) -> bool:
"""Property returning a bool indicating whether this track was recommended via AutoPlay."""
return self._recommended

@property
def extras(self) -> ExtrasNamespace:
"""Property returning a :class:`~wavelink.ExtrasNamespace` of extras for this :class:`Playable`.
You can set this property with a :class:`dict` of valid :class:`str` keys to any valid ``JSON`` value,
or a :class:`~wavelink.ExtrasNamespace`.
If a dict is passed, it will be converted into an :class:`~wavelink.ExtrasNamespace`,
which can be converted back to a dict with dict(...). Additionally, you can also use list or tuple on
:class:`~wavelink.ExtrasNamespace`.
The extras dict will be sent to Lavalink as the ``userData`` field.
.. warning::
This is only available when using Lavalink 4+ (**Non BETA**) versions.
Examples
--------
.. code:: python
track: wavelink.Playable = wavelink.Playable.search("QUERY")
track.extras = {"requester_id": 1234567890}
# later...
print(track.extras.requester_id)
# or
print(dict(track.extras)["requester_id"])
.. versionadded:: 3.1.0
"""
return self._extras

@extras.setter
def extras(self, __value: ExtrasNamespace | dict[str, Any]) -> None:
if isinstance(__value, ExtrasNamespace):
self._extras = __value
else:
self._extras = ExtrasNamespace(__value)

@classmethod
async def search(cls, query: str, /, *, source: TrackSource | str | None = TrackSource.YouTubeMusic) -> Search:
"""Search for a list of :class:`~wavelink.Playable` or a :class:`~wavelink.Playlist`, with the given query.
Expand Down
9 changes: 8 additions & 1 deletion wavelink/types/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"""
from __future__ import annotations

from typing import TYPE_CHECKING, TypeAlias, TypedDict
from typing import TYPE_CHECKING, Any, TypeAlias, TypedDict

if TYPE_CHECKING:
from typing_extensions import NotRequired
Expand All @@ -37,13 +37,20 @@ class VoiceRequest(TypedDict):
sessionId: str


class TrackRequest(TypedDict, total=False):
encoded: str | None
identifier: str
userData: dict[str, Any]


class _BaseRequest(TypedDict, total=False):
voice: VoiceRequest
position: int
endTime: int | None
volume: int
paused: bool
filters: FilterPayload
track: TrackRequest


class EncodedTrackRequest(_BaseRequest):
Expand Down
1 change: 1 addition & 0 deletions wavelink/types/tracks.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ class TrackPayload(TypedDict):
encoded: str
info: TrackInfoPayload
pluginInfo: dict[Any, Any]
userData: dict[str, Any]


class PlaylistPayload(TypedDict):
Expand Down
62 changes: 62 additions & 0 deletions wavelink/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"""
MIT License
Copyright (c) 2019-Current PythonistaGuild, EvieePy
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.
"""
from types import SimpleNamespace
from typing import Any, Iterator

__all__ = (
"Namespace",
"ExtrasNamespace",
)


class Namespace(SimpleNamespace):
def __iter__(self) -> Iterator[tuple[str, Any]]:
return iter(self.__dict__.items())


class ExtrasNamespace(Namespace):
"""A subclass of :class:`types.SimpleNameSpace`.
You can construct this namespace with a :class:`dict` of `str` keys and `Any` value, or with keyword pairs or
with a mix of both.
You can access a dict version of this namespace by calling `dict()` on an instance.
Examples
--------
.. code:: python
ns: ExtrasNamespace = ExtrasNamespace({"hello": "world!"}, stuff=1)
# Later...
print(ns.hello)
print(ns.stuff)
print(dict(ns))
"""

def __init__(self, __dict: dict[str, Any] = {}, /, **kwargs: Any) -> None:
updated = __dict | kwargs
super().__init__(**updated)

0 comments on commit a9ed696

Please sign in to comment.