Refactor library and examples for improved usability and features

Key changes:
- Integrated autoplay support into the Player class.
- Added new equalizer presets for Pop, Soft, and Light Bass.
- Enhanced Queue with move and remove_duplicates functionality.
- Updated exception messages and docstrings for better clarity.
- Refreshed advanced example with interaction buttons and progress bars.
This commit is contained in:
wizardoesmagic 2025-12-28 16:10:11 +00:00
parent 77d1e3fcbc
commit 590f292275
5 changed files with 171 additions and 21 deletions

View File

@ -301,6 +301,85 @@ class Music(commands.Cog):
delete_after=15,
)
@commands.command()
async def loop(self, ctx: commands.Context, mode: str = "off"):
"""Sets the loop mode: off, track, queue."""
player: Player = ctx.voice_client
if not player:
return
mode = mode.lower()
if mode == "track":
player.loop_mode = pomice.LoopMode.TRACK
elif mode == "queue":
player.loop_mode = pomice.LoopMode.QUEUE
else:
player.loop_mode = None
await ctx.send(f"Loop mode set to **{mode}**")
@commands.command()
async def autoplay(self, ctx: commands.Context):
"""Toggles autoplay to keep the music going with recommendations when the queue is empty."""
player: Player = ctx.voice_client
if not player:
return
player.autoplay = not player.autoplay
await ctx.send(f"Autoplay is now **{'on' if player.autoplay else 'off'}**")
@commands.command()
async def move(self, ctx: commands.Context, from_index: int, to_index: int):
"""Moves a track's position in the queue (e.g., !move 5 1)."""
player: Player = ctx.voice_client
if not player or player.queue.is_empty:
return await ctx.send("The queue is empty.")
try:
player.queue.move(from_index - 1, to_index - 1)
await ctx.send(f"Moved track from #{from_index} to #{to_index}.")
except IndexError:
await ctx.send("Sorry, I couldn't find a track at that position.")
@commands.command(aliases=["clean"])
async def deduplicate(self, ctx: commands.Context):
"""Removes any double-posted songs from your queue."""
player: Player = ctx.voice_client
if not player:
return
removed = player.queue.remove_duplicates()
await ctx.send(f"All cleaned up! Removed **{removed}** duplicate tracks.")
@commands.command()
async def filter(self, ctx: commands.Context, preset: str = "off"):
"""Apply a sound preset: pop, soft, metal, boost, nightcore, vaporwave, off."""
player: Player = ctx.voice_client
if not player:
return
preset = preset.lower()
await player.reset_filters()
if preset == "off":
return await ctx.send("Filters cleared.")
presets = {
"pop": pomice.Equalizer.pop(),
"soft": pomice.Equalizer.soft(),
"metal": pomice.Equalizer.metal(),
"boost": pomice.Equalizer.boost(),
"nightcore": pomice.Timescale.nightcore(),
"vaporwave": pomice.Timescale.vaporwave(),
"bass": pomice.Equalizer.bass_boost_light()
}
if preset not in presets:
return await ctx.send(f"Available presets: {', '.join(presets.keys())}")
await player.add_filter(presets[preset])
await ctx.send(f"Applied the **{preset}** sound preset!")
@commands.command()
async def stop(self, ctx: commands.Context):
"""Stop the player and clear all internal states."""

View File

@ -69,8 +69,8 @@ class TrackInvalidPosition(PomiceException):
class TrackLoadError(PomiceException):
"""There was an error while loading a track."""
pass
def __init__(self, message: str = "Sorry, I ran into trouble trying to load that track."):
super().__init__(message)
class FilterInvalidArgument(PomiceException):
@ -111,14 +111,14 @@ class QueueException(Exception):
class QueueFull(QueueException):
"""Exception raised when attempting to add to a full Queue."""
pass
def __init__(self, message: str = "Whoops! The queue is completely full right now."):
super().__init__(message)
class QueueEmpty(QueueException):
"""Exception raised when attempting to retrieve from an empty Queue."""
pass
def __init__(self, message: str = "It looks like the queue is empty. There's no more music to play!"):
super().__init__(message)
class LavalinkVersionIncompatible(PomiceException):

View File

@ -110,10 +110,7 @@ class Equalizer(Filter):
@classmethod
def boost(cls) -> "Equalizer":
"""Equalizer preset which boosts the sound of a track,
making it sound fun and energetic by increasing the bass
and the highs.
"""
"""A lively preset that boosts both bass and highs, making the music feel more energetic and fun."""
levels = [
(0, -0.075),
@ -134,11 +131,16 @@ class Equalizer(Filter):
]
return cls(tag="boost", levels=levels)
@classmethod
def bass_boost_light(cls) -> "Equalizer":
"""A light touch for people who want a bit more bass without it becoming overwhelming."""
levels = [(0, 0.15), (1, 0.1), (2, 0.05)]
return cls(tag="bass_boost_light", levels=levels)
@classmethod
def metal(cls) -> "Equalizer":
"""Equalizer preset which increases the mids of a track,
preferably one of the metal genre, to make it sound
more full and concert-like.
"""A heavy preset designed to bring out the intensity of metal and rock.
It boosts the mids and highs to create a fuller, stage-like sound experience.
"""
levels = [
@ -161,6 +163,30 @@ class Equalizer(Filter):
return cls(tag="metal", levels=levels)
@classmethod
def pop(cls) -> "Equalizer":
"""A balanced preset that enhances vocals and adds a bit of 'pop' to the rhythm.
Perfect for mainstream hits.
"""
levels = [
(0, -0.02), (1, -0.01), (2, 0.08), (3, 0.1), (4, 0.15),
(5, 0.1), (6, 0.05), (7, 0.0), (8, 0.0), (9, 0.0),
(10, 0.05), (11, 0.1), (12, 0.15), (13, 0.1), (14, 0.05)
]
return cls(tag="pop", levels=levels)
@classmethod
def soft(cls) -> "Equalizer":
"""A gentle preset that smooths out harsh frequencies.
Ideal for acoustic tracks or when you just want a more relaxed listening experience.
"""
levels = [
(0, 0.0), (1, 0.0), (2, 0.0), (3, -0.05), (4, -0.1),
(5, -0.1), (6, -0.05), (7, 0.0), (8, 0.05), (9, 0.1),
(10, 0.1), (11, 0.05), (12, 0.0), (13, 0.0), (14, 0.0)
]
return cls(tag="soft", levels=levels)
@classmethod
def piano(cls) -> "Equalizer":
"""Equalizer preset which increases the mids and highs

View File

@ -156,6 +156,7 @@ class Player(VoiceProtocol):
"_player_endpoint_uri",
"queue",
"history",
"autoplay",
)
def __call__(self, client: Client, channel: VoiceChannel) -> Player:
@ -195,6 +196,7 @@ class Player(VoiceProtocol):
self.queue: Queue = Queue()
self.history: TrackHistory = TrackHistory()
self.autoplay: bool = False
def __repr__(self) -> str:
return (
@ -252,7 +254,7 @@ class Player(VoiceProtocol):
@property
def is_paused(self) -> bool:
"""Property which returns whether or not the player has a track which is paused or not."""
"""Returns True if the music is currently paused."""
return self._is_connected and self._paused
@property
@ -772,14 +774,25 @@ class Player(VoiceProtocol):
await self.seek(self.position)
async def do_next(self) -> Optional[Track]:
"""Automatically plays the next track from the queue.
"""Automatically picks the next track from the queue and plays it.
If the queue is empty and autoplay is on, it will search for recommended tracks.
Returns
-------
Optional[Track]
The track that is now playing, or None if the queue is empty.
The track that's now playing, or None if we've run out of music.
"""
if self.queue.is_empty:
if self.autoplay and self._current:
recommendations = await self.get_recommendations(track=self._current)
if recommendations:
if isinstance(recommendations, Playlist):
track = recommendations.tracks[0]
else:
track = recommendations[0]
await self.play(track)
return track
return None
track = self.queue.get()

View File

@ -310,8 +310,8 @@ class Queue(Iterable[Track]):
return new_queue
def clear(self) -> None:
"""Remove all items from the queue."""
self._queue.clear()
"""Wipes the entire queue clean, removing all tracks."""
self._queue = []
def set_loop_mode(self, mode: LoopMode) -> None:
"""
@ -343,8 +343,40 @@ class Queue(Iterable[Track]):
self._loop_mode = None
def shuffle(self) -> None:
"""Shuffles the queue."""
return random.shuffle(self._queue)
"""Mixes up the entire queue in a random order."""
random.shuffle(self._queue)
def move(self, from_index: int, to_index: int) -> None:
"""Moves a track from one spot in the queue to another.
Parameters
----------
from_index: int
The current position of the track (0-indexed).
to_index: int
Where you want to put the track.
"""
if from_index == to_index:
return
track = self._queue.pop(from_index)
self._queue.insert(to_index, track)
def remove_duplicates(self) -> int:
"""Looks through the queue and removes any tracks that appear more than once.
Returns the number of duplicate tracks removed.
"""
initial_count = len(self._queue)
seen_ids = set()
unique_queue = []
for track in self._queue:
if track.track_id not in seen_ids:
unique_queue.append(track)
seen_ids.add(track.track_id)
self._queue = unique_queue
return initial_count - len(self._queue)
def clear_track_filters(self) -> None:
"""Clears all filters applied to tracks"""