From d895606c73c033c088cec976e3bc9b580299af56 Mon Sep 17 00:00:00 2001 From: EvieePy Date: Sat, 3 Feb 2024 23:15:03 +1000 Subject: [PATCH 1/7] New Queue implementation. --- wavelink/player.py | 2 +- wavelink/queue.py | 566 +++++++++++++++++++++++++++++++++------------ 2 files changed, 419 insertions(+), 149 deletions(-) diff --git a/wavelink/player.py b/wavelink/player.py index 3c602425..65381568 100644 --- a/wavelink/player.py +++ b/wavelink/player.py @@ -369,7 +369,7 @@ async def _search(query: str | None) -> T_a: track._recommended = True added += await self.auto_queue.put_wait(track) - random.shuffle(self.auto_queue._queue) + random.shuffle(self.auto_queue._items) logger.debug(f'Player "{self.guild.id}" added "{added}" tracks to the auto_queue via AutoPlay.') # Probably don't need this here as it's likely to be cancelled instantly... diff --git a/wavelink/queue.py b/wavelink/queue.py index 3c04307f..9089c4ca 100644 --- a/wavelink/queue.py +++ b/wavelink/queue.py @@ -26,100 +26,24 @@ import asyncio import random from collections import deque -from collections.abc import Iterator -from typing import overload +from collections.abc import Iterable, Iterator +from typing import SupportsIndex, TypeGuard, overload from .enums import QueueMode from .exceptions import QueueEmpty from .tracks import Playable, Playlist -__all__ = ("Queue",) - - -class _Queue: - def __init__(self) -> None: - self._queue: deque[Playable] = deque() - - def __str__(self) -> str: - return ", ".join([f'"{p}"' for p in self]) - - def __repr__(self) -> str: - return f"BaseQueue(items={len(self._queue)})" - - def __bool__(self) -> bool: - return bool(self._queue) - - def __call__(self, item: Playable | Playlist) -> None: - self.put(item) - - def __len__(self) -> int: - return len(self._queue) - - @overload - def __getitem__(self, index: int) -> Playable: - ... - - @overload - def __getitem__(self, index: slice) -> list[Playable]: - ... - def __getitem__(self, index: int | slice) -> Playable | list[Playable]: - if isinstance(index, slice): - return list(self._queue)[index] - - return self._queue[index] - - def __iter__(self) -> Iterator[Playable]: - return self._queue.__iter__() - - def __contains__(self, item: object) -> bool: - return item in self._queue - - @staticmethod - def _check_compatability(item: object) -> None: - if not isinstance(item, Playable): - raise TypeError("This queue is restricted to Playable objects.") - - def _get(self) -> Playable: - if not self: - raise QueueEmpty("There are no items currently in this queue.") - - return self._queue.popleft() - - def get(self) -> Playable: - return self._get() - - def _check_atomic(self, item: list[Playable] | Playlist) -> None: - for track in item: - self._check_compatability(track) - - def _put(self, item: Playable) -> None: - self._check_compatability(item) - self._queue.append(item) - - def put(self, item: Playable | Playlist, /, *, atomic: bool = True) -> int: - added: int = 0 - - if isinstance(item, Playlist): - if atomic: - self._check_atomic(item) +__all__ = ("Queue",) - for track in item: - try: - self._put(track) - added += 1 - except TypeError: - pass - else: - self._put(item) - added += 1 +class Queue: + """The default custom wavelink Queue designed specifically for :class:`wavelink.Player`. - return added + .. note:: - -class Queue(_Queue): - """The default custom wavelink Queue designed specifically for :class:`wavelink.Player`. + :class:`~wavelink.Player` implements this queue by default. + You can access it via :attr:`~wavelink.Player.queue`. .. container:: operations @@ -155,37 +79,121 @@ class Queue(_Queue): Check whether a specific track is in the queue. + .. describe:: queue[1] = track + + Set a track in the queue at a specific index. + + .. describe:: del queue[1] + + Delete a track from the queue at a specific index. + + .. describe:: reversed(queue) + + Return a reversed iterator of the queue. Attributes ---------- history: :class:`wavelink.Queue` - A queue of tracks that have been added to history. - - Even though the history queue is the same class as this Queue some differences apply. - Mainly you can not set the ``mode``. + A queue of tracks that have been added to history. Tracks are added to history when they are played. """ - def __init__(self, history: bool = True) -> None: - super().__init__() - self.history: Queue | None = None + def __init__(self, *, history: bool = True) -> None: + self._items: list[Playable] = [] - if history: - self.history = Queue(history=False) - - self._loaded: Playable | None = None + self._history: Queue | None = None if not history else Queue(history=False) self._mode: QueueMode = QueueMode.normal + self._loaded: Playable | None = None + self._waiters: deque[asyncio.Future[None]] = deque() - self._finished: asyncio.Event = asyncio.Event() - self._finished.set() + self._lock = asyncio.Lock() + + @property + def mode(self) -> QueueMode: + """Property which returns a :class:`~wavelink.QueueMode` indicating which mode the + :class:`~wavelink.Queue` is in. + + This property can be set with any :class:`~wavelink.QueueMode`. + + .. versionadded:: 3.0.0 + """ + return self._mode + + @mode.setter + def mode(self, value: QueueMode) -> None: + self._mode = value + + @property + def history(self) -> Queue | None: + return self._history + + @property + def count(self) -> int: + """The queue member count. + + Returns + ------- + int + The amount of tracks in the queue. + """ - self._lock: asyncio.Lock = asyncio.Lock() + return len(self) + + @property + def is_empty(self) -> bool: + """Whether the queue has no members. + + Returns + ------- + bool + Whether the queue is empty. + """ + + return not bool(self) def __str__(self) -> str: - return ", ".join([f'"{p}"' for p in self]) + joined: str = ", ".join([f'"{p}"' for p in self]) + return f"Queue([{joined}])" def __repr__(self) -> str: return f"Queue(items={len(self)}, history={self.history!r})" + def __call__(self, item: Playable) -> None: + self.put(item) + + def __bool__(self) -> bool: + return bool(self._items) + + @overload + def __getitem__(self, __index: SupportsIndex, /) -> Playable: + ... + + @overload + def __getitem__(self, __index: slice, /) -> list[Playable]: + ... + + def __getitem__(self, __index: SupportsIndex | slice, /) -> Playable | list[Playable]: + return self._items[__index] + + def __setitem__(self, __index: SupportsIndex, __value: Playable, /) -> None: + self._check_compatability(__value) + self._items[__index] = __value + self._wakeup_next() + + def __delitem__(self, __index: int | slice, /) -> None: + del self._items[__index] + + def __contains__(self, __other: Playable) -> bool: + return __other in self._items + + def __len__(self) -> int: + return len(self._items) + + def __reversed__(self) -> Iterator[Playable]: + return reversed(self._items) + + def __iter__(self) -> Iterator[Playable]: + return iter(self._items) + def _wakeup_next(self) -> None: while self._waiters: waiter = self._waiters.popleft() @@ -194,38 +202,124 @@ def _wakeup_next(self) -> None: waiter.set_result(None) break - def _get(self) -> Playable: + @staticmethod + def _check_compatability(item: object) -> TypeGuard[Playable]: + if not isinstance(item, Playable): + raise TypeError("This queue is restricted to Playable objects.") + return True + + @classmethod + def _check_atomic(cls, item: Iterable[object]) -> TypeGuard[Iterable[Playable]]: + for track in item: + cls._check_compatability(track) + return True + + def get(self) -> Playable: + """Retrieve a track from the left side of the queue. E.g. the first. + + This method does not block. + + .. warning:: + + Due to the way the queue loop works, this method will return the same track if the queue is in loop mode. + You can use :meth:`wavelink.Player.skip` with ``force=True`` to skip the current track. + + Do **NOT** use this method to remove tracks from the queue, use either: + + - ``del queue[index]`` + - :meth:`wavelink.Queue.remove` + - :meth:`wavelink.Queue.delete` + + + Returns + ------- + :class:`wavelink.Playable` + The track retrieved from the queue. + + Raises + ------ + QueueEmpty + The queue was empty when retrieving a track. + """ + if self.mode is QueueMode.loop and self._loaded: return self._loaded if self.mode is QueueMode.loop_all and not self: assert self.history is not None - self._queue.extend(self.history._queue) + self._items.extend(self.history._items) self.history.clear() - track: Playable = super()._get() + if not self: + raise QueueEmpty("There are no items currently in this queue.") + + track: Playable = self._items.pop(0) self._loaded = track return track - def get(self) -> Playable: - """Retrieve a track from the left side of the queue. E.g. the first. + def get_at(self, index: int, /) -> Playable: + """Retrieve a track from the queue at a given index. - This method does not block. + .. warning:: + + Due to the way the queue loop works, this method will load the retrieved track for looping. + + Do **NOT** use this method to remove tracks from the queue, use either: + + - ``del queue[index]`` + - :meth:`wavelink.Queue.remove` + - :meth:`wavelink.Queue.delete` + + Parameters + ---------- + index: int + The index of the track to get. Returns ------- :class:`wavelink.Playable` The track retrieved from the queue. - Raises ------ QueueEmpty The queue was empty when retrieving a track. + IndexError + The index was out of range for the current queue. + """ + + if not self: + raise QueueEmpty("There are no items currently in this queue.") + + track: Playable = self._items.pop(index) + self._loaded = track + + return track + + def put_at(self, index: int, value: Playable, /) -> None: + """Put a track into the queue at a given index. + + .. note:: + + This method doesn't replace the track at the index but rather inserts one there, similar to a list. + + Parameters + ---------- + index: int + The index to put the track at. + value: :class:`wavelink.Playable` + The track to put. + + Raises + ------ + TypeError + The track was not a :class:`wavelink.Playable`. """ - return self._get() + self._check_compatability(value) + self._items.insert(index, value) + self._wakeup_next() async def get_wait(self) -> Playable: """This method returns the first :class:`wavelink.Playable` if one is present or @@ -237,8 +331,8 @@ async def get_wait(self) -> Playable: ------- :class:`wavelink.Playable` The track retrieved from the queue. - """ + while not self: loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() waiter: asyncio.Future[None] = loop.create_future() @@ -262,26 +356,47 @@ async def get_wait(self) -> Playable: return self.get() - def put(self, item: Playable | Playlist, /, *, atomic: bool = True) -> int: + def put(self, item: list[Playable] | Playable | Playlist, /, *, atomic: bool = True) -> int: """Put an item into the end of the queue. - Accepts a :class:`wavelink.Playable` or :class:`wavelink.Playlist` + Accepts a :class:`wavelink.Playable`, :class:`wavelink.Playlist` or list[:class:`wavelink.Playble`]. Parameters ---------- - item: :class:`wavelink.Playable` | :class:`wavelink.Playlist` + item: :class:`wavelink.Playable` | :class:`wavelink.Playlist` | list[:class:`wavelink.Playble`] The item to enter into the queue. atomic: bool Whether the items should be inserted atomically. If set to ``True`` this method won't enter any tracks if - it encounters an error. Defaults to ``True`` - + it encounters an error. Defaults to ``True``. Returns ------- int - The amount of tracks added to the queue. + The number of tracks added to the queue. """ - added: int = super().put(item, atomic=atomic) + + added = 0 + + if isinstance(item, Iterable): + if atomic: + self._check_atomic(item) + self._items.extend(item) + added = len(item) + else: + + def try_compatibility(track: object) -> bool: + try: + return self._check_compatability(track) + except TypeError: + return False + + passing_items = [track for track in item if try_compatibility(track)] + self._items.extend(passing_items) + added = len(passing_items) + else: + self._check_compatability(item) + self._items.append(item) + added = 1 self._wakeup_next() return added @@ -289,7 +404,7 @@ def put(self, item: Playable | Playlist, /, *, atomic: bool = True) -> int: async def put_wait(self, item: list[Playable] | Playable | Playlist, /, *, atomic: bool = True) -> int: """Put an item or items into the end of the queue asynchronously. - Accepts a :class:`wavelink.Playable` or :class:`wavelink.Playlist` or list[:class:`wavelink.Playable`] + Accepts a :class:`wavelink.Playable` or :class:`wavelink.Playlist` or list[:class:`wavelink.Playable`]. .. note:: @@ -301,59 +416,145 @@ async def put_wait(self, item: list[Playable] | Playable | Playlist, /, *, atomi The item or items to enter into the queue. atomic: bool Whether the items should be inserted atomically. If set to ``True`` this method won't enter any tracks if - it encounters an error. Defaults to ``True`` - + it encounters an error. Defaults to ``True``. Returns ------- int - The amount of tracks added to the queue. + The number of tracks added to the queue. """ + added: int = 0 async with self._lock: - if isinstance(item, list | Playlist): + if isinstance(item, Iterable): if atomic: - super()._check_atomic(item) + self._check_atomic(item) + self._items.extend(item) + return len(item) for track in item: try: - super()._put(track) - added += 1 + self._check_compatability(track) except TypeError: pass + else: + self._items.append(track) + added += 1 await asyncio.sleep(0) else: - super()._put(item) + self._check_compatability(item) + self._items.append(item) added += 1 await asyncio.sleep(0) self._wakeup_next() return added - async def delete(self, index: int, /) -> None: + def delete(self, index: int, /) -> None: """Method to delete an item in the queue by index. - This method is asynchronous and implements/waits for a lock. - Raises ------ IndexError No track exists at this index. - Examples -------- .. code:: python3 - await queue.delete(1) # Deletes the track at index 1 (The second track). + queue.delete(1) """ - async with self._lock: - self._queue.__delitem__(index) + + del self._items[index] + + def peek(self, index: int = 0, /) -> Playable: + """Method to peek at an item in the queue by index. + + .. note:: + + This does not change the queue or remove the item. + + Parameters + ---------- + index: int + The index to peek at. Defaults to ``0`` which is the next item in the queue. + + Returns + ------- + :class:`wavelink.Playable` + The track at the given index. + + Raises + ------ + QueueEmpty + There are no items currently in this queue. + IndexError + No track exists at the given index. + + ..versionadded:: 3.2.0 + """ + if not self: + raise QueueEmpty("There are no items currently in this queue.") + + return self[index] + + def swap(self, first: int, second: int, /) -> None: + """Swap two items in the queue by index. + + Parameters + ---------- + first: int + The first index to swap with. + second: int + The second index to swap with. + + Returns + ------- + None + + Raises + ------ + IndexError + No track exists at the given index. + + Example + ------- + + .. code:: python3 + + # Swap the first and second tracks in the queue. + queue.swap(0, 1) + + .. versionadded:: 3.2.0 + """ + self[first], self[second] = self[second], self[first] + + def index(self, item: Playable, /) -> int: + """Return the index of the first occurence of a :class:`wavelink.Playable` in the queue. + + Parameters + ---------- + item: :class:`wavelink.Playable` + The item to search the index for. + + Returns + ------- + int + The index of the item in the queue. + + Raises + ------ + ValueError + The item was not found in the queue. + + .. versionadded:: 3.2.0 + """ + return self._items.index(item) def shuffle(self) -> None: """Shuffles the queue in place. This does **not** return anything. @@ -365,41 +566,110 @@ def shuffle(self) -> None: player.queue.shuffle() # Your queue has now been shuffled... + + Returns + ------- + None """ - random.shuffle(self._queue) + + random.shuffle(self._items) def clear(self) -> None: """Remove all items from the queue. - .. note:: - This does not reset the queue. + This does not reset the queue or clear history. Use this method on queue.history to clear history. Example ------- - .. code:: python3 player.queue.clear() # Your queue is now empty... + + Returns + ------- + None """ - self._queue.clear() - @property - def mode(self) -> QueueMode: - """Property which returns a :class:`~wavelink.QueueMode` indicating which mode the - :class:`~wavelink.Queue` is in. + self._items.clear() - This property can be set with any :class:`~wavelink.QueueMode`. + def copy(self) -> Queue: + """Create a shallow copy of the queue. - .. versionadded:: 3.0.0 + Returns + ------- + :class:`wavelink.Queue` + A shallow copy of the queue. """ - return self._mode - @mode.setter - def mode(self, value: QueueMode) -> None: - if not hasattr(self, "_mode"): - raise AttributeError("This queues mode can not be set.") + copy_queue = Queue(history=self.history is not None) + copy_queue._items = self._items.copy() + return copy_queue - self._mode = value + def reset(self) -> None: + """Reset the queue to its default state. This will clear the queue and history. + + .. note:: + + This will cancel any waiting futures on the queue. E.g. :meth:`wavelink.Queue.get_wait`. + + Returns + ------- + None + """ + self.clear() + if self.history is not None: + self.history.clear() + + for waiter in self._waiters: + waiter.cancel() + + self._waiters.clear() + + self._mode: QueueMode = QueueMode.normal + self._loaded = None + + def remove(self, item: Playable, /, count: int | None = 1) -> int: + """Remove a specific track from the queue up to a given count or all instances. + + .. note:: + + This method starts from the left hand side of the queue E.g. the beginning. + + .. warning:: + + Setting count to ``<= 0`` is equivalent to setting it to ``1``. + + Parameters + ---------- + item: :class:`wavelink.Playable` + The item to remove from the queue. + count: int + The amount of times to remove the item from the queue. Defaults to ``1``. + If set to ``None`` this will remove all instances of the item. + + Returns + ------- + int + The amount of times the item was removed from the queue. + + Raises + ------ + ValueError + The item was not found in the queue. + + .. versionadded:: 3.2.0 + """ + deleted_count: int = 0 + + for track in self._items.copy(): + if track == item: + self._items.remove(track) + deleted_count += 1 + + if count is not None and deleted_count >= count: + break + + return deleted_count From badf8789915eb0eed000f0cd26b82c237fc4cae7 Mon Sep 17 00:00:00 2001 From: EvieePy Date: Sat, 3 Feb 2024 23:15:37 +1000 Subject: [PATCH 2/7] Add test.py to gitignore. --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 5e8f6be6..c7bf8404 100644 --- a/.gitignore +++ b/.gitignore @@ -159,4 +159,7 @@ cython_debug/ # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ -.vscode/ \ No newline at end of file +.vscode/ + +# Test +test.py \ No newline at end of file From 196bdce7f9fe69c909b6b5f48405428314bb0b8e Mon Sep 17 00:00:00 2001 From: EvieePy Date: Sat, 3 Feb 2024 23:22:04 +1000 Subject: [PATCH 3/7] Run isort. --- wavelink/queue.py | 1 - 1 file changed, 1 deletion(-) diff --git a/wavelink/queue.py b/wavelink/queue.py index 9089c4ca..3cab83b3 100644 --- a/wavelink/queue.py +++ b/wavelink/queue.py @@ -33,7 +33,6 @@ from .exceptions import QueueEmpty from .tracks import Playable, Playlist - __all__ = ("Queue",) From 26ec22a93c7a7a6dbfeb736c3dc38b2263d87b29 Mon Sep 17 00:00:00 2001 From: EvieePy Date: Sat, 3 Feb 2024 23:26:34 +1000 Subject: [PATCH 4/7] Add documentation for queue to Player --- wavelink/player.py | 7 +++++++ wavelink/queue.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/wavelink/player.py b/wavelink/player.py index 65381568..ffbcf134 100644 --- a/wavelink/player.py +++ b/wavelink/player.py @@ -83,6 +83,13 @@ class Player(discord.VoiceProtocol): Since the Player is a :class:`discord.VoiceProtocol`, it is attached to the various ``voice_client`` attributes in discord.py, including ``guild.voice_client``, ``ctx.voice_client`` and ``interaction.voice_client``. + + Attributes + ---------- + queue: :class:`~wavelink.Queue` + The queue associated with this player. + auto_queue: :class:`~wavelink.Queue` + The auto_queue associated with this player. This queue holds tracks that are recommended by the AutoPlay feature. """ channel: VocalGuildChannel diff --git a/wavelink/queue.py b/wavelink/queue.py index 3cab83b3..a767b71b 100644 --- a/wavelink/queue.py +++ b/wavelink/queue.py @@ -42,7 +42,7 @@ class Queue: .. note:: :class:`~wavelink.Player` implements this queue by default. - You can access it via :attr:`~wavelink.Player.queue`. + You can access it via :attr:`wavelink.Player.queue`. .. container:: operations From 7c4850cb58e7d980dc84d2ef02072271dddad844 Mon Sep 17 00:00:00 2001 From: EvieePy Date: Sat, 3 Feb 2024 23:32:09 +1000 Subject: [PATCH 5/7] Fix some documentation. --- wavelink/queue.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/wavelink/queue.py b/wavelink/queue.py index a767b71b..5a61078c 100644 --- a/wavelink/queue.py +++ b/wavelink/queue.py @@ -113,6 +113,7 @@ def mode(self) -> QueueMode: This property can be set with any :class:`~wavelink.QueueMode`. + .. versionadded:: 3.0.0 """ return self._mode @@ -495,6 +496,7 @@ def peek(self, index: int = 0, /) -> Playable: IndexError No track exists at the given index. + ..versionadded:: 3.2.0 """ if not self: @@ -529,6 +531,7 @@ def swap(self, first: int, second: int, /) -> None: # Swap the first and second tracks in the queue. queue.swap(0, 1) + .. versionadded:: 3.2.0 """ self[first], self[second] = self[second], self[first] @@ -551,6 +554,7 @@ def index(self, item: Playable, /) -> int: ValueError The item was not found in the queue. + .. versionadded:: 3.2.0 """ return self._items.index(item) @@ -659,6 +663,7 @@ def remove(self, item: Playable, /, count: int | None = 1) -> int: ValueError The item was not found in the queue. + .. versionadded:: 3.2.0 """ deleted_count: int = 0 From ba6e0eec55e8b07a3879549f0b1981fdc2e51ef9 Mon Sep 17 00:00:00 2001 From: EvieePy Date: Sun, 4 Feb 2024 00:44:38 +1000 Subject: [PATCH 6/7] Fix spelling issue. --- wavelink/player.py | 2 +- wavelink/queue.py | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/wavelink/player.py b/wavelink/player.py index ffbcf134..39d04aad 100644 --- a/wavelink/player.py +++ b/wavelink/player.py @@ -950,7 +950,7 @@ async def stop(self, *, force: bool = True) -> Playable | None: .. versionchanged:: 3.0.0 - This method is now known as ``skip``, but the alias ``stop`` has been kept for backwards compatability. + This method is now known as ``skip``, but the alias ``stop`` has been kept for backwards compatibility. """ return await self.skip(force=force) diff --git a/wavelink/queue.py b/wavelink/queue.py index 5a61078c..fb7d127f 100644 --- a/wavelink/queue.py +++ b/wavelink/queue.py @@ -175,7 +175,7 @@ def __getitem__(self, __index: SupportsIndex | slice, /) -> Playable | list[Play return self._items[__index] def __setitem__(self, __index: SupportsIndex, __value: Playable, /) -> None: - self._check_compatability(__value) + self._check_compatibility(__value) self._items[__index] = __value self._wakeup_next() @@ -203,7 +203,7 @@ def _wakeup_next(self) -> None: break @staticmethod - def _check_compatability(item: object) -> TypeGuard[Playable]: + def _check_compatibility(item: object) -> TypeGuard[Playable]: if not isinstance(item, Playable): raise TypeError("This queue is restricted to Playable objects.") return True @@ -211,7 +211,7 @@ def _check_compatability(item: object) -> TypeGuard[Playable]: @classmethod def _check_atomic(cls, item: Iterable[object]) -> TypeGuard[Iterable[Playable]]: for track in item: - cls._check_compatability(track) + cls._check_compatibility(track) return True def get(self) -> Playable: @@ -317,7 +317,7 @@ def put_at(self, index: int, value: Playable, /) -> None: TypeError The track was not a :class:`wavelink.Playable`. """ - self._check_compatability(value) + self._check_compatibility(value) self._items.insert(index, value) self._wakeup_next() @@ -386,7 +386,7 @@ def put(self, item: list[Playable] | Playable | Playlist, /, *, atomic: bool = T def try_compatibility(track: object) -> bool: try: - return self._check_compatability(track) + return self._check_compatibility(track) except TypeError: return False @@ -394,7 +394,7 @@ def try_compatibility(track: object) -> bool: self._items.extend(passing_items) added = len(passing_items) else: - self._check_compatability(item) + self._check_compatibility(item) self._items.append(item) added = 1 @@ -435,7 +435,7 @@ async def put_wait(self, item: list[Playable] | Playable | Playlist, /, *, atomi for track in item: try: - self._check_compatability(track) + self._check_compatibility(track) except TypeError: pass else: @@ -445,7 +445,7 @@ async def put_wait(self, item: list[Playable] | Playable | Playlist, /, *, atomi await asyncio.sleep(0) else: - self._check_compatability(item) + self._check_compatibility(item) self._items.append(item) added += 1 await asyncio.sleep(0) From 87edcd5dacd9b98a34552a5cadb7c78907a7ba51 Mon Sep 17 00:00:00 2001 From: EvieePy Date: Sun, 4 Feb 2024 01:02:43 +1000 Subject: [PATCH 7/7] Add loaded propery to Queue and other review changes. --- wavelink/queue.py | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/wavelink/queue.py b/wavelink/queue.py index fb7d127f..c3f43c61 100644 --- a/wavelink/queue.py +++ b/wavelink/queue.py @@ -99,7 +99,7 @@ class Queue: def __init__(self, *, history: bool = True) -> None: self._items: list[Playable] = [] - self._history: Queue | None = None if not history else Queue(history=False) + self._history: Queue | None = Queue(history=False) if history else None self._mode: QueueMode = QueueMode.normal self._loaded: Playable | None = None @@ -677,3 +677,33 @@ def remove(self, item: Playable, /, count: int | None = 1) -> int: break return deleted_count + + @property + def loaded(self) -> Playable | None: + """The currently loaded track that will repeat when the queue is set to :attr:`wavelink.QueueMode.loop`. + + This track will be retrieved when using :meth:`wavelink.Queue.get` if the queue is in loop mode. + You can unload the track by setting this property to ``None`` or by using :meth:`wavelink.Player.skip` with + ``force=True``. + + Setting this property to a new :class:`wavelink.Playable` will replace the currently loaded track, but will not + add it to the queue; or history until the track is played. + + Returns + ------- + :class:`wavelink.Playable` | None + The currently loaded track or ``None`` if there is no track ready to repeat. + + Raises + ------ + TypeError + The track was not a :class:`wavelink.Playable` or ``None``. + """ + return self._loaded + + @loaded.setter + def loaded(self, value: Playable | None) -> None: + if value is not None: + self._check_compatibility(value) + + self._loaded = value