Skip to content
This repository was archived by the owner on Apr 5, 2025. It is now read-only.

Commit a9ed696

Browse files
authored
Add extras for userData in Lavalink (#261)
* Add extras for userData in Lavalink * Add migrating docs
1 parent 2cf793d commit a9ed696

File tree

8 files changed

+131
-3
lines changed

8 files changed

+131
-3
lines changed

docs/migrating.rst

+1
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ Added
8585
- :meth:`wavelink.Node.fetch_version`
8686
- :meth:`wavelink.Node.fetch_player_info`
8787
- :meth:`wavelink.Node.fetch_players`
88+
- :attr:`wavelink.Playable.extras`
8889

8990

9091
Connecting

docs/wavelink.rst

+9
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,15 @@ Filters
347347
:members:
348348

349349

350+
Utils
351+
-----
352+
353+
.. attributetable:: ExtrasNamespace
354+
355+
.. autoclass:: ExtrasNamespace
356+
357+
358+
350359
Exceptions
351360
----------
352361

wavelink/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,4 @@
3838
from .player import Player as Player
3939
from .queue import *
4040
from .tracks import *
41+
from .utils import ExtrasNamespace as ExtrasNamespace

wavelink/player.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -667,7 +667,7 @@ async def play(
667667
self._filters = filters
668668

669669
request: RequestPayload = {
670-
"encodedTrack": track.encoded,
670+
"track": {"encoded": track.encoded, "userData": dict(track.extras)},
671671
"volume": vol,
672672
"position": start,
673673
"endTime": end,
@@ -850,7 +850,7 @@ async def skip(self, *, force: bool = True) -> Playable | None:
850850
if force:
851851
self.queue._loaded = None
852852

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

856856
return old

wavelink/tracks.py

+47
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import wavelink
3232

3333
from .enums import TrackSource
34+
from .utils import ExtrasNamespace
3435

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

138+
self._extras: ExtrasNamespace = ExtrasNamespace(data.get("userData", {}))
139+
137140
def __hash__(self) -> int:
138141
return hash(self.encoded)
139142

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

253+
@property
254+
def extras(self) -> ExtrasNamespace:
255+
"""Property returning a :class:`~wavelink.ExtrasNamespace` of extras for this :class:`Playable`.
256+
257+
You can set this property with a :class:`dict` of valid :class:`str` keys to any valid ``JSON`` value,
258+
or a :class:`~wavelink.ExtrasNamespace`.
259+
260+
If a dict is passed, it will be converted into an :class:`~wavelink.ExtrasNamespace`,
261+
which can be converted back to a dict with dict(...). Additionally, you can also use list or tuple on
262+
:class:`~wavelink.ExtrasNamespace`.
263+
264+
The extras dict will be sent to Lavalink as the ``userData`` field.
265+
266+
267+
.. warning::
268+
269+
This is only available when using Lavalink 4+ (**Non BETA**) versions.
270+
271+
272+
Examples
273+
--------
274+
275+
.. code:: python
276+
277+
track: wavelink.Playable = wavelink.Playable.search("QUERY")
278+
track.extras = {"requester_id": 1234567890}
279+
280+
# later...
281+
print(track.extras.requester_id)
282+
# or
283+
print(dict(track.extras)["requester_id"])
284+
285+
286+
.. versionadded:: 3.1.0
287+
"""
288+
return self._extras
289+
290+
@extras.setter
291+
def extras(self, __value: ExtrasNamespace | dict[str, Any]) -> None:
292+
if isinstance(__value, ExtrasNamespace):
293+
self._extras = __value
294+
else:
295+
self._extras = ExtrasNamespace(__value)
296+
250297
@classmethod
251298
async def search(cls, query: str, /, *, source: TrackSource | str | None = TrackSource.YouTubeMusic) -> Search:
252299
"""Search for a list of :class:`~wavelink.Playable` or a :class:`~wavelink.Playlist`, with the given query.

wavelink/types/request.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"""
2424
from __future__ import annotations
2525

26-
from typing import TYPE_CHECKING, TypeAlias, TypedDict
26+
from typing import TYPE_CHECKING, Any, TypeAlias, TypedDict
2727

2828
if TYPE_CHECKING:
2929
from typing_extensions import NotRequired
@@ -37,13 +37,20 @@ class VoiceRequest(TypedDict):
3737
sessionId: str
3838

3939

40+
class TrackRequest(TypedDict, total=False):
41+
encoded: str | None
42+
identifier: str
43+
userData: dict[str, Any]
44+
45+
4046
class _BaseRequest(TypedDict, total=False):
4147
voice: VoiceRequest
4248
position: int
4349
endTime: int | None
4450
volume: int
4551
paused: bool
4652
filters: FilterPayload
53+
track: TrackRequest
4754

4855

4956
class EncodedTrackRequest(_BaseRequest):

wavelink/types/tracks.py

+1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ class TrackPayload(TypedDict):
5050
encoded: str
5151
info: TrackInfoPayload
5252
pluginInfo: dict[Any, Any]
53+
userData: dict[str, Any]
5354

5455

5556
class PlaylistPayload(TypedDict):

wavelink/utils.py

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
"""
2+
MIT License
3+
4+
Copyright (c) 2019-Current PythonistaGuild, EvieePy
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 types import SimpleNamespace
25+
from typing import Any, Iterator
26+
27+
__all__ = (
28+
"Namespace",
29+
"ExtrasNamespace",
30+
)
31+
32+
33+
class Namespace(SimpleNamespace):
34+
def __iter__(self) -> Iterator[tuple[str, Any]]:
35+
return iter(self.__dict__.items())
36+
37+
38+
class ExtrasNamespace(Namespace):
39+
"""A subclass of :class:`types.SimpleNameSpace`.
40+
41+
You can construct this namespace with a :class:`dict` of `str` keys and `Any` value, or with keyword pairs or
42+
with a mix of both.
43+
44+
You can access a dict version of this namespace by calling `dict()` on an instance.
45+
46+
47+
Examples
48+
--------
49+
50+
.. code:: python
51+
52+
ns: ExtrasNamespace = ExtrasNamespace({"hello": "world!"}, stuff=1)
53+
54+
# Later...
55+
print(ns.hello)
56+
print(ns.stuff)
57+
print(dict(ns))
58+
"""
59+
60+
def __init__(self, __dict: dict[str, Any] = {}, /, **kwargs: Any) -> None:
61+
updated = __dict | kwargs
62+
super().__init__(**updated)

0 commit comments

Comments
 (0)