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

Commit f548a3b

Browse files
authored
Feature/playable node and Inactive Channel Check (#303)
* Add inactive_channel check. * Remove discord badge from README * Allow a Node to be passed to Playable or fetch_tracks.
1 parent faee7c8 commit f548a3b

File tree

3 files changed

+92
-15
lines changed

3 files changed

+92
-15
lines changed

wavelink/node.py

+17-3
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,9 @@ class Node:
126126
inactive_player_timeout: int | None
127127
Set the default for :attr:`wavelink.Player.inactive_timeout` on every player that connects to this node.
128128
Defaults to ``300``.
129+
inactive_channel_tokens: int | None
130+
Sets the default for :attr:`wavelink.Player.inactive_channel_tokens` on every player that connects to this node.
131+
Defaults to ``3``.
129132
130133
See also: :func:`on_wavelink_inactive_player`.
131134
"""
@@ -142,6 +145,7 @@ def __init__(
142145
client: discord.Client | None = None,
143146
resume_timeout: int = 60,
144147
inactive_player_timeout: int | None = 300,
148+
inactive_channel_tokens: int | None = 3,
145149
) -> None:
146150
self._identifier = identifier or secrets.token_urlsafe(12)
147151
self._uri = uri.removesuffix("/")
@@ -170,6 +174,8 @@ def __init__(
170174
inactive_player_timeout if inactive_player_timeout and inactive_player_timeout > 0 else None
171175
)
172176

177+
self._inactive_channel_tokens = inactive_channel_tokens
178+
173179
def __repr__(self) -> str:
174180
return f"Node(identifier={self.identifier}, uri={self.uri}, status={self.status}, players={len(self.players)})"
175181

@@ -895,14 +901,17 @@ def get_node(cls, identifier: str | None = None, /) -> Node:
895901
return sorted(nodes, key=lambda n: n._total_player_count or len(n.players))[0]
896902

897903
@classmethod
898-
async def fetch_tracks(cls, query: str, /) -> list[Playable] | Playlist:
904+
async def fetch_tracks(cls, query: str, /, *, node: Node | None = None) -> list[Playable] | Playlist:
899905
"""Search for a list of :class:`~wavelink.Playable` or a :class:`~wavelink.Playlist`, with the given query.
900906
901907
Parameters
902908
----------
903909
query: str
904910
The query to search tracks for. If this is not a URL based search you should provide the appropriate search
905911
prefix, e.g. "ytsearch:Rick Roll"
912+
node: :class:`~wavelink.Node` | None
913+
An optional :class:`~wavelink.Node` to use when fetching tracks. Defaults to ``None``, which selects the
914+
most appropriate :class:`~wavelink.Node` automatically.
906915
907916
Returns
908917
-------
@@ -923,6 +932,11 @@ async def fetch_tracks(cls, query: str, /) -> list[Playable] | Playlist:
923932
or an empty list if no results were found.
924933
925934
This method no longer accepts the ``cls`` parameter.
935+
936+
937+
.. versionadded:: 3.4.0
938+
939+
Added the ``node`` Keyword-Only argument.
926940
"""
927941

928942
# TODO: Documentation Extension for `.. positional-only::` marker.
@@ -934,8 +948,8 @@ async def fetch_tracks(cls, query: str, /) -> list[Playable] | Playlist:
934948
if potential:
935949
return potential
936950

937-
node: Node = cls.get_node()
938-
resp: LoadedResponse = await node._fetch_tracks(encoded_query)
951+
node_: Node = node or cls.get_node()
952+
resp: LoadedResponse = await node_._fetch_tracks(encoded_query)
939953

940954
if resp["loadType"] == "track":
941955
track = Playable(data=resp["data"])

wavelink/player.py

+66-9
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,9 @@ def __init__(
159159
self._auto_lock: asyncio.Lock = asyncio.Lock()
160160
self._error_count: int = 0
161161

162+
self._inactive_channel_limit: int | None = self._node._inactive_channel_tokens
163+
self._inactive_channel_count: int = self._inactive_channel_limit if self._inactive_channel_limit else 0
164+
162165
self._filters: Filters = Filters()
163166

164167
# Needed for the inactivity checks...
@@ -216,7 +219,21 @@ async def _track_start(self, payload: TrackStartEventPayload) -> None:
216219
self._inactivity_cancel()
217220

218221
async def _auto_play_event(self, payload: TrackEndEventPayload) -> None:
219-
if self._autoplay is AutoPlayMode.disabled:
222+
if not self.channel:
223+
return
224+
225+
members: int = len([m for m in self.channel.members if not m.bot])
226+
self._inactive_channel_count = (
227+
self._inactive_channel_count - 1 if not members else self._inactive_channel_limit or 0
228+
)
229+
230+
if self._inactive_channel_limit and self._inactive_channel_count <= 0:
231+
self._inactive_channel_count = self._inactive_channel_limit # Reset...
232+
233+
self._inactivity_cancel()
234+
self.client.dispatch("wavelink_inactive_player", self)
235+
236+
elif self._autoplay is AutoPlayMode.disabled:
220237
self._inactivity_start()
221238
return
222239

@@ -353,7 +370,7 @@ async def _search(query: str | None) -> T_a:
353370
return []
354371

355372
try:
356-
search: wavelink.Search = await Pool.fetch_tracks(query)
373+
search: wavelink.Search = await Pool.fetch_tracks(query, node=self._node)
357374
except (LavalinkLoadException, LavalinkException):
358375
return []
359376

@@ -403,6 +420,49 @@ async def _search(query: str | None) -> T_a:
403420
logger.info('Player "%s" could not load any songs via AutoPlay.', self.guild.id)
404421
self._inactivity_start()
405422

423+
@property
424+
def inactive_channel_tokens(self) -> int | None:
425+
"""A settable property which returns the token limit as an ``int`` of the amount of tracks to play before firing
426+
the :func:`on_wavelink_inactive_player` event when a channel is inactive.
427+
428+
This property could return ``None`` if the check has been disabled.
429+
430+
A channel is considered inactive when no real members (Members other than bots) are in the connected voice
431+
channel. On each consecutive track played without a real member in the channel, this token bucket will reduce
432+
by ``1``. After hitting ``0``, the :func:`on_wavelink_inactive_player` event will be fired and the token bucket
433+
will reset to the set value. The default value for this property is ``3``.
434+
435+
This property can be set with any valid ``int`` or ``None``. If this property is set to ``<= 0`` or ``None``,
436+
the check will be disabled.
437+
438+
Setting this property to ``1`` will fire the :func:`on_wavelink_inactive_player` event at the end of every track
439+
if no real members are in the channel and you have not disconnected the player.
440+
441+
If this check successfully fires the :func:`on_wavelink_inactive_player` event, it will cancel any waiting
442+
:attr:`inactive_timeout` checks until a new track is played.
443+
444+
The default for every player can be set on :class:`~wavelink.Node`.
445+
446+
- See: :class:`~wavelink.Node`
447+
- See: :func:`on_wavelink_inactive_player`
448+
449+
.. warning::
450+
451+
Setting this property will reset the bucket.
452+
453+
.. versionadded:: 3.4.0
454+
"""
455+
return self._inactive_channel_limit
456+
457+
@inactive_channel_tokens.setter
458+
def inactive_channel_tokens(self, value: int | None) -> None:
459+
if not value or value <= 0:
460+
self._inactive_channel_limit = None
461+
return
462+
463+
self._inactive_channel_limit = value
464+
self._inactive_channel_count = value
465+
406466
@property
407467
def inactive_timeout(self) -> int | None:
408468
"""A property which returns the time as an ``int`` of seconds to wait before this player dispatches the
@@ -616,14 +676,11 @@ async def _dispatch_voice_update(self) -> None:
616676
assert self.guild is not None
617677
data: VoiceState = self._voice_state["voice"]
618678

619-
try:
620-
session_id: str = data["session_id"]
621-
token: str = data["token"]
622-
except KeyError:
623-
return
624-
679+
session_id: str | None = data.get("session_id", None)
680+
token: str | None = data.get("token", None)
625681
endpoint: str | None = data.get("endpoint", None)
626-
if not endpoint:
682+
683+
if not session_id or not token or not endpoint:
627684
return
628685

629686
request: RequestPayload = {"voice": {"sessionId": session_id, "token": token, "endpoint": endpoint}}

wavelink/tracks.py

+9-3
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
if TYPE_CHECKING:
3838
from collections.abc import Iterator
3939

40+
from .node import Node
4041
from .types.tracks import (
4142
PlaylistInfoPayload,
4243
PlaylistPayload,
@@ -323,7 +324,9 @@ def raw_data(self) -> TrackPayload:
323324
return self._raw_data
324325

325326
@classmethod
326-
async def search(cls, query: str, /, *, source: TrackSource | str | None = TrackSource.YouTubeMusic) -> Search:
327+
async def search(
328+
cls, query: str, /, *, source: TrackSource | str | None = TrackSource.YouTubeMusic, node: Node | None = None
329+
) -> Search:
327330
"""Search for a list of :class:`~wavelink.Playable` or a :class:`~wavelink.Playlist`, with the given query.
328331
329332
.. note::
@@ -355,6 +358,9 @@ async def search(cls, query: str, /, *, source: TrackSource | str | None = Track
355358
LavaSrc Spotify based search.
356359
357360
Defaults to :attr:`wavelink.TrackSource.YouTubeMusic` which is equivalent to "ytmsearch:".
361+
node: :class:`~wavelink.Node` | None
362+
An optional :class:`~wavelink.Node` to use when searching for tracks. Defaults to ``None``, which uses
363+
the :class:`~wavelink.Pool`'s automatic node selection.
358364
359365
360366
Returns
@@ -410,7 +416,7 @@ async def search(cls, query: str, /, *, source: TrackSource | str | None = Track
410416
check = yarl.URL(query)
411417

412418
if check.host:
413-
tracks: Search = await wavelink.Pool.fetch_tracks(query)
419+
tracks: Search = await wavelink.Pool.fetch_tracks(query, node=node)
414420
return tracks
415421

416422
if not prefix:
@@ -419,7 +425,7 @@ async def search(cls, query: str, /, *, source: TrackSource | str | None = Track
419425
assert not isinstance(prefix, TrackSource)
420426
term: str = f"{prefix.removesuffix(':')}:{query}"
421427

422-
tracks: Search = await wavelink.Pool.fetch_tracks(term)
428+
tracks: Search = await wavelink.Pool.fetch_tracks(term, node=node)
423429
return tracks
424430

425431

0 commit comments

Comments
 (0)