diff --git a/.env.example b/.env.example index 23e3a6021..307dd77ef 100644 --- a/.env.example +++ b/.env.example @@ -16,9 +16,10 @@ AUTO_NODE=" false" # true for auto node. It is given from lavainfo-api (https:// SEARCH_ENGINE= "YouTubeMusic" # Search engine to be used when playing the song. You can use: YouTube, YouTubeMusic, SoundCloud, Spotify, Apple, Deezer, Yandex and JioSaavn MAX_PLAYLIST_SIZE= "100" # Max playlist size. MAX_QUEUE_SIZE= "100" # Max queue size. +GENIUS_API= "" # Sign up and get your own api at (https://genius.com/) to fetch your lyrics (CLIENT TOKEN) # Configuration for multiple Lavalink servers LAVALINK_SERVERS = '[ {"url":"localhost:2333","auth":"youshallnotpass","name":"Local Node","secure":false}, {"url":"localhost:2333","auth":"youshallnotpass2","name":"Another Node","secure":false} -]' \ No newline at end of file +]' diff --git a/locales/EnglishUS.json b/locales/EnglishUS.json index 51b11138d..1260ad343 100644 --- a/locales/EnglishUS.json +++ b/locales/EnglishUS.json @@ -263,6 +263,16 @@ "looping_queue": "**Looping the queue.**", "looping_off": "**Looping is now off.**" }, + "lyrics": { + "description": "Get's the lyrics of the currently playing track.", + "lyrics_track": "### Lyrics for: [{trackTitle}]({trackUrl})\n**`{lyrics}`**", + "searching": "`🔍` Searching for **{trackTitle}** lyrics...", + "errors": { + "no_results": "No lyrics found for the current track.", + "no_playing": "No track is currently playing.", + "lyrics_error": "An error occurred while getting the lyrics." + } + }, "nowplaying": { "description": "Shows the currently playing song", "now_playing": "Now Playing", @@ -631,4 +641,4 @@ "Leave a guild": "Leave a guild", "List all guilds the bot is in": "List all guilds the bot is in", "Restart the bot": "Restart the bot" -} \ No newline at end of file +} diff --git a/package.json b/package.json index 784554098..e51fe0ee8 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "@top-gg/sdk": "^3.1.6", "discord.js": "^14.16.1", "dotenv": "^16.4.5", + "genius-lyrics-api": "^3.2.1", "i18n": "^0.15.1", "node-system-stats": "^1.3.0", "shoukaku": "github:shipgirlproject/Shoukaku#master", diff --git a/src/commands/music/Lyrics.ts b/src/commands/music/Lyrics.ts new file mode 100644 index 000000000..d24c2dc5e --- /dev/null +++ b/src/commands/music/Lyrics.ts @@ -0,0 +1,183 @@ +import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import { getLyrics } from "genius-lyrics-api"; +import { ActionRowBuilder, ButtonBuilder, ButtonStyle, ComponentType } from "discord.js"; + +export default class Lyrics extends Command { + constructor(client: Lavamusic) { + super(client, { + name: "lyrics", + description: { + content: "cmd.lyrics.description", + examples: ["lyrics"], + usage: "lyrics", + }, + category: "music", + aliases: ["ly"], + cooldown: 3, + args: false, + vote: false, + player: { + voice: true, + dj: false, + active: true, + djPerm: null, + }, + permissions: { + dev: false, + client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], + user: [], + }, + slashCommand: true, + options: [], + }); + } + + public async run(client: Lavamusic, ctx: Context): Promise { + const player = client.queue.get(ctx.guild!.id); + const embed = this.client.embed(); + + if (!(player && !player.isPlaying)) { + return await ctx.sendMessage({ + embeds: [embed.setColor(client.color.red).setDescription(ctx.locale("cmd.lyrics.errors.no_playing"))], + }); + } + + const currentTrack = player.current; + const trackTitle = currentTrack.info.title.replace(/\[.*?\]/g, "").trim(); + const artistName = currentTrack.info.author.replace(/\[.*?\]/g, "").trim(); + const trackUrl = currentTrack.info.uri; + const artworkUrl = currentTrack.info.artworkUrl; + + await ctx.sendDeferMessage(ctx.locale("cmd.lyrics.searching", { trackTitle })); + + const options = { + apiKey: client.config.lyricsApi, + title: trackTitle, + artist: artistName, + optimizeQuery: true, + }; + + try { + const lyrics = await getLyrics(options); + if (lyrics) { + const lyricsPages = this.paginateLyrics(lyrics); + let currentPage = 0; + + const row = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId("prev") + .setEmoji(this.client.emoji.page.back) + .setStyle(ButtonStyle.Secondary) + .setDisabled(true), + new ButtonBuilder().setCustomId("stop").setEmoji(this.client.emoji.page.cancel).setStyle(ButtonStyle.Danger), + new ButtonBuilder() + .setCustomId("next") + .setEmoji(this.client.emoji.page.next) + .setStyle(ButtonStyle.Secondary) + .setDisabled(lyricsPages.length <= 1), + ); + + await ctx.editMessage({ + embeds: [ + embed + .setColor(client.color.main) + .setDescription( + ctx.locale("cmd.lyrics.lyrics_track", { trackTitle, trackUrl, lyrics: lyricsPages[currentPage] }), + ) + .setThumbnail(artworkUrl) + .setTimestamp(), + ], + components: [row], + }); + + const filter = (interaction) => interaction.user.id === ctx.author.id; + const collector = ctx.channel.createMessageComponentCollector({ + filter, + componentType: ComponentType.Button, + time: 60000, + }); + + collector.on("collect", async (interaction) => { + if (interaction.customId === "prev") { + currentPage--; + } else if (interaction.customId === "next") { + currentPage++; + } else if (interaction.customId === "stop") { + collector.stop(); + return interaction.update({ components: [] }); + } + + await interaction.update({ + embeds: [ + embed + .setDescription( + ctx.locale("cmd.lyrics.lyrics_track", { trackTitle, trackUrl, lyrics: lyricsPages[currentPage] }), + ) + .setThumbnail(artworkUrl) + .setTimestamp(), + ], + components: [ + new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId("prev") + .setEmoji(this.client.emoji.page.back) + .setStyle(ButtonStyle.Secondary) + .setDisabled(currentPage === 0), + new ButtonBuilder() + .setCustomId("stop") + .setEmoji(this.client.emoji.page.cancel) + .setStyle(ButtonStyle.Danger), + new ButtonBuilder() + .setCustomId("next") + .setEmoji(this.client.emoji.page.next) + .setStyle(ButtonStyle.Secondary) + .setDisabled(currentPage === lyricsPages.length - 1), + ), + ], + }); + }); + + collector.on("end", () => { + ctx.editMessage({ components: [] }); + }); + } else { + await ctx.editMessage({ + embeds: [embed.setColor(client.color.red).setDescription(ctx.locale("cmd.lyrics.errors.no_results"))], + }); + } + } catch (error) { + console.error(error); + await ctx.editMessage({ + embeds: [embed.setColor(client.color.red).setDescription(ctx.locale("cmd.lyrics.errors.lyrics_error"))], + }); + } + } + + paginateLyrics(lyrics) { + const lines = lyrics.split("\n"); + const pages = []; + let page = ""; + + for (const line of lines) { + if (page.length + line.length > 2048) { + pages.push(page); + page = ""; + } + page += `${line}\n`; + } + + if (page) pages.push(page); + return pages; + } +} + +/** + * Project: lavamusic + * Author: Appu + * Main Contributor: LucasB25 + * Company: Coders + * Copyright (c) 2024. All rights reserved. + * This code is the property of Coder and may not be reproduced or + * modified without permission. For more information, contact us at + * https://discord.gg/ns8CTk9J3e + */ diff --git a/src/config.ts b/src/config.ts index 34257ddd3..61da8f1db 100644 --- a/src/config.ts +++ b/src/config.ts @@ -55,6 +55,7 @@ export default { guildId: process.env.GUILD_ID, logChannelId: process.env.LOG_CHANNEL_ID, commandLogs: process.env.LOG_COMMANDS_ID, + lyricsApi: process.env.GENIUS_API, links: { img: process.env.IMG_LINK || "https://i.imgur.com/ud3EWNh.jpg", },