Skip to main content

Fetching Lyrics

This section explains how to use Salada’s built-in lyrics feature to get synced or plain lyrics for any track, enhanced with metadata from Spotify.

Prerequisites

To use the lyrics feature, you must provide Spotify API credentials in your node configuration. This is required to fetch song metadata like album art, which enriches the lyrics display.
nodes = [{
    'host': 'your_lavalink_host',
    'port': 2333,
    'auth': 'your_lavalink_password',
    'ssl': False,
    'spotify_client_id': 'YOUR_SPOTIFY_CLIENT_ID',
    'spotify_client_secret': 'YOUR_SPOTIFY_CLIENT_SECRET'
}]

# Make sure to install the required library
# pip install syncedlyrics

Complete Lyrics Command Example

Here is a complete example of a /lyrics command for a Discord bot. It fetches lyrics for the currently playing song and displays them in a paginated embed.
from discord import app_commands
import discord
import re

# You'll need a way to handle pagination. This is a simplified example.
# For a real implementation, consider a pagination file from discord.py or any other library you are using!
class LyricsView(discord.ui.View):
    def __init__(self, embeds):
        super().__init__(timeout=120)
        self.embeds = embeds
        self.current_page = 0

    @discord.ui.button(label="◀", style=discord.ButtonStyle.primary)
    async def previous_button(self, interaction: discord.Interaction, button: discord.ui.Button):
        self.current_page = (self.current_page - 1) % len(self.embeds)
        await interaction.response.edit_message(embed=self.embeds[self.current_page])

    @discord.ui.button(label="▶", style=discord.ButtonStyle.primary)
    async def next_button(self, interaction: discord.Interaction, button: discord.ui.Button):
        self.current_page = (self.current_page + 1) % len(self.embeds)
        await interaction.response.edit_message(embed=self.embeds[self.current_page])

def format_lyrics_for_embed(lyrics_text: str, is_synced: bool):
    """Splits lyrics into chunks for Discord embeds."""
    if not lyrics_text:
        return ["No lyrics available."]
    
    chunks = []
    current_chunk = ""
    lines = lyrics_text.split('\\n')
    
    for line in lines:
        # For synced lyrics remove the timestamp
        if is_synced and line.strip().startswith('['):
            if ']' in line:
                line = line[line.index(']') + 1:].strip()
        
        if not current_chunk and not line.strip():
            continue
        
        if len(current_chunk) + len(line) + 1 > 1024: # Embed field value limit
            chunks.append(current_chunk.strip())
            current_chunk = line + '\\n'
        else:
            current_chunk += line + '\\n'
    
    if current_chunk.strip():
        chunks.append(current_chunk.strip())
    
    return chunks

@bot.tree.command(name='lyrics')
@app_commands.describe(song='Song name (optional, defaults to current track)')
async def lyrics_command(interaction: discord.Interaction, song: str = None):
    await interaction.response.defer()

    player = bot.salad.getPlayer(interaction.guild.id)
    
    track = None
    artist_name = None

    if song:
        # If a song is provided, we need to search for it first
        result = await bot.salad.resolve(song, requester=interaction.user)
        if result and result.get('tracks'):
            track = result['tracks'][0]
    elif player and player.currentTrackObj:
        track = player.currentTrackObj
    else:
        await interaction.followup.send('❌ No song is playing and no song was specified.')
        return

    # Clean up artist name for better search results
    artist_name = getattr(track, 'author', None)
    if artist_name:
        artist_name = re.sub(r'\\s*-\\s*Topic', '', artist_name)
        artist_name = re.sub(r'\\s*VEVO', '', artist_name, flags=re.IGNORECASE)

    # Fetch lyrics
    lyrics_text, is_synced = await track.fetch_lyrics(player)

    if not lyrics_text:
        await interaction.followup.send(f"❌ No lyrics found for **{track.title}**.")
        return

    # Get song info from Spotify
    lyrics_handler = player.get_lyrics_handler()
    song_info = {}
    if lyrics_handler:
        song_info = await lyrics_handler.get_song_info(track.title, artist_name)

    # Create paginated embeds
    chunks = format_lyrics_for_embed(lyrics_text, is_synced)
    embeds = []
    
    song_display = f"**[{track.title}]({song_info.get('song_url')})**" if song_info.get('song_url') else f"**{track.title}**"

    for i, chunk in enumerate(chunks):
        embed = discord.Embed(
            title=f"Lyrics for {track.title}",
            description=f"{song_display} by **{track.author}**\n\n{chunk}",
            color=discord.Color.blue()
        )
        if song_info.get("thumbnail_url"):
            embed.set_thumbnail(url=song_info["thumbnail_url"])
        
        embed.set_footer(text=f"Page {i + 1}/{len(chunks)} | {'Synced' if is_synced else 'Plain Text'}")
        embeds.append(embed)

    if len(embeds) == 1:
        await interaction.followup.send(embed=embeds[0])
    else:
        view = LyricsView(embeds)
        await interaction.followup.send(embed=embeds[0], view=view)

Core Concepts

Fetching Lyrics from a Track

The primary method for getting lyrics is track.fetch_lyrics(player). It automatically handles fetching and caching.
player = bot.salad.getPlayer(guild_id)
current_track = player.currentTrackObj

if current_track:
    # Returns a tuple: (lyrics_string, is_synced_bool)
    lyrics, is_synced = await current_track.fetch_lyrics(player)
    
    if lyrics:
        print("Lyrics found!")
    else:
        print("Lyrics not found.")

Accessing the Lyrics Handler

If you need to access the Spotify metadata functions directly, you can get the lyrics handler from the player.
player = bot.salad.getPlayer(guild_id)
lyrics_handler = player.get_lyrics_handler()

if lyrics_handler:
    # Fetch Spotify info for a song
    song_info = await lyrics_handler.get_song_info("Bohemian Rhapsody", "Queen")
    
    if song_info and song_info.get("thumbnail_url"):
        print("Album Art URL:", song_info["thumbnail_url"])
The syncedlyrics library performs web scraping and can sometimes be slow or fail. Always wrap your lyrics-fetching calls in try...except blocks and handle cases where no lyrics are found gracefully.
Since lyrics can be very long, always paginate them for a better user experience in Discord. Splitting the text into multiple embeds or using Discord’s Paginator is highly recommended.