Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2fd0f622f7 |
@@ -23,7 +23,7 @@ A discord music bot that seamlessly streams music from your personal music serve
|
||||
| `/queue` | View the current queue |
|
||||
| `/clear` | Clear the current queue |
|
||||
| `/shuffle` | Shuffles the current queue |
|
||||
| `/skip` | Skip the current track |
|
||||
| `/skip [count]` | Skip the current track or `count` tracks |
|
||||
| `/stop` | Stop playing the current track |
|
||||
| `/autoplay` | Toggle autoplay |
|
||||
| `/playlists` | List available playlists |
|
||||
|
||||
@@ -76,8 +76,7 @@ class DiscodromeClient(commands.Bot):
|
||||
async def on_ready(self) -> None:
|
||||
''' Event called when the client is done preparing. '''
|
||||
|
||||
activity = discord.Activity(type=discord.ActivityType.playing, name=env.BOT_STATUS)
|
||||
await self.change_presence(activity=activity)
|
||||
await self.set_default_presence()
|
||||
|
||||
logger.info(
|
||||
"Logged as: %s | Connected Guilds: %s | Loaded Extensions: %s",
|
||||
@@ -87,6 +86,22 @@ class DiscodromeClient(commands.Bot):
|
||||
)
|
||||
logger.info("Bot status set to: '%s'", env.BOT_STATUS)
|
||||
|
||||
async def set_default_presence(self) -> None:
|
||||
''' Sets the default bot presence (idle status). '''
|
||||
activity = discord.Activity(type=discord.ActivityType.playing, name=env.BOT_STATUS)
|
||||
await self.change_presence(activity=activity)
|
||||
|
||||
async def set_now_playing(self, song_title: str, artist: str, cover_url: str = None) -> None:
|
||||
''' Sets the bot presence to show the currently playing song. '''
|
||||
activity = discord.Activity(
|
||||
type=discord.ActivityType.listening,
|
||||
name=f"{song_title} - {artist}",
|
||||
large_image=cover_url,
|
||||
large_text=f"{song_title} - {artist}"
|
||||
)
|
||||
await self.change_presence(activity=activity)
|
||||
logger.debug("Bot presence updated: Listening to '%s - %s'", song_title, artist)
|
||||
|
||||
if __name__ == "__main__":
|
||||
logs.setup_logging()
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -209,6 +209,12 @@ class MusicCog(commands.Cog):
|
||||
# Add current song back to the queue if exists
|
||||
player.queue.insert(0, player.current_song)
|
||||
|
||||
# Reset bot presence to default
|
||||
try:
|
||||
await self.bot.set_default_presence()
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to reset bot presence: {e}")
|
||||
|
||||
# Display disconnect confirmation
|
||||
await ui.SysMsg.stopping_queue_playback(interaction)
|
||||
|
||||
@@ -269,8 +275,9 @@ class MusicCog(commands.Cog):
|
||||
await ui.ErrMsg.msg(ctx, f"An unknown error has occurred and has been logged to console. Please contact an administrator. {error}")
|
||||
|
||||
|
||||
@app_commands.command(name="skip", description="Skip the current track")
|
||||
async def skip(self, interaction: discord.Interaction) -> None:
|
||||
@app_commands.command(name="skip", description="Skip one or more tracks")
|
||||
@app_commands.describe(count="Optional number of tracks to skip (>= 1)")
|
||||
async def skip(self, interaction: discord.Interaction, count: int=None) -> None:
|
||||
''' Skip the current track '''
|
||||
|
||||
# Get the voice client instance
|
||||
@@ -281,12 +288,17 @@ class MusicCog(commands.Cog):
|
||||
await ui.ErrMsg.bot_not_in_voice_channel(interaction)
|
||||
return
|
||||
|
||||
# Validate count if provided
|
||||
if count is not None and count < 1:
|
||||
await ui.ErrMsg.msg(interaction, "Skip count must be at least 1.")
|
||||
return
|
||||
|
||||
# Check if the bot is playing music
|
||||
if not voice_client.is_playing():
|
||||
await ui.ErrMsg.not_playing(interaction)
|
||||
return
|
||||
|
||||
await data.guild_data(interaction.guild_id).player.skip_track(interaction, voice_client)
|
||||
await data.guild_data(interaction.guild_id).player.skip_track(interaction, voice_client, count=count)
|
||||
|
||||
@skip.error
|
||||
async def skip_error(self, ctx, error):
|
||||
@@ -463,6 +475,11 @@ class MusicCog(commands.Cog):
|
||||
player = data.guild_data(member.guild.id).player
|
||||
player.queue.clear()
|
||||
player.current_song = None
|
||||
# Reset bot presence to default
|
||||
try:
|
||||
await self.bot.set_default_presence()
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to reset bot presence: {e}")
|
||||
logger.info("The bot has disconnected and cleared the queue as there are no users in the voice channel.")
|
||||
else:
|
||||
logger.debug("Bot is no longer alone in voice channel, aborting disconnect...")
|
||||
|
||||
34
player.py
34
player.py
@@ -8,7 +8,7 @@ import data
|
||||
import ui
|
||||
import logging
|
||||
|
||||
from subsonic import Song, APIError, get_random_songs, get_similar_songs, stream, scrobble
|
||||
from subsonic import Song, APIError, get_random_songs, get_similar_songs, stream, scrobble, get_album_art_url
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -263,6 +263,12 @@ class Player():
|
||||
song = self.queue.pop(0)
|
||||
self.current_song = song
|
||||
await ui.SysMsg.now_playing(interaction, song)
|
||||
# Update bot presence with now playing info
|
||||
try:
|
||||
cover_url = get_album_art_url(song.cover_id)
|
||||
await interaction.client.set_now_playing(song.title, song.artist, cover_url)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to update bot presence: {e}")
|
||||
await self.stream_track(interaction, song, voice_client)
|
||||
else:
|
||||
logger.debug("Queue is empty.")
|
||||
@@ -278,10 +284,15 @@ class Player():
|
||||
return
|
||||
# If the queue is empty, playback has ended; we should let the user know
|
||||
await ui.SysMsg.playback_ended(interaction)
|
||||
# Reset bot presence to default
|
||||
try:
|
||||
await interaction.client.set_default_presence()
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to reset bot presence: {e}")
|
||||
|
||||
|
||||
async def skip_track(self, interaction: discord.Interaction, voice_client: discord.VoiceClient) -> None:
|
||||
''' Skips the current track and plays the next one in the queue '''
|
||||
async def skip_track(self, interaction: discord.Interaction, voice_client: discord.VoiceClient, count: int=None) -> None:
|
||||
''' Skips the current track and optionally additional tracks from the queue '''
|
||||
|
||||
# Check if the bot is connected to a voice channel; it's the caller's responsibility to open a voice channel
|
||||
if voice_client is None:
|
||||
@@ -290,8 +301,25 @@ class Player():
|
||||
logger.debug("Skipping track...")
|
||||
# Check if the bot is already playing something
|
||||
if voice_client.is_playing():
|
||||
# Determine how many tracks to skip in total (current + additional)
|
||||
total_skips = 1 if count is None or count < 1 else count
|
||||
|
||||
# Stop current playback (this counts as 1 skip)
|
||||
voice_client.stop()
|
||||
|
||||
# Remove additional tracks from the front of the queue, if any
|
||||
additional_to_skip = max(0, total_skips - 1)
|
||||
if additional_to_skip > 0 and self.queue:
|
||||
removed = min(additional_to_skip, len(self.queue))
|
||||
# Slice off the skipped items
|
||||
self.queue = self.queue[removed:]
|
||||
logger.debug(f"Skipped {removed} additional track(s) from queue")
|
||||
|
||||
# Notify user
|
||||
if total_skips <= 1:
|
||||
await ui.SysMsg.skipping(interaction)
|
||||
else:
|
||||
await ui.SysMsg.msg(interaction, f"Skipped {total_skips} track{'s' if total_skips != 1 else ''}", ephemeral=True)
|
||||
else:
|
||||
await ui.ErrMsg.not_playing(interaction)
|
||||
|
||||
|
||||
13
subsonic.py
13
subsonic.py
@@ -449,6 +449,19 @@ async def get_artist_discography(query: str) -> Album:
|
||||
return album_list
|
||||
|
||||
|
||||
def get_album_art_url(cover_id: str, size: int=300) -> str:
|
||||
''' Get the URL for album art from the subsonic API '''
|
||||
if not cover_id:
|
||||
return None
|
||||
params = {
|
||||
**SUBSONIC_REQUEST_PARAMS,
|
||||
"id": cover_id,
|
||||
"size": str(size)
|
||||
}
|
||||
query_string = "&".join(f"{k}={v}" for k, v in params.items())
|
||||
return f"{env.SUBSONIC_SERVER}/rest/getCoverArt?{query_string}"
|
||||
|
||||
|
||||
async def get_album_art_file(cover_id: str, size: int=300) -> str:
|
||||
''' Request album art from the subsonic API '''
|
||||
target_path = f"cache/{cover_id}.jpg"
|
||||
|
||||
Reference in New Issue
Block a user