diff --git a/jsconfig.json b/jsconfig.json index a7062f3..d5bdae2 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -12,5 +12,6 @@ "@structures/*": ["./src/structures/*"] } }, + "include": ["src"], "exclude": ["node_modules", "**/node_modules/*"] } \ No newline at end of file diff --git a/src/commands/music/lyric.js b/src/commands/music/lyric.js index 606ba66..4bdc508 100644 --- a/src/commands/music/lyric.js +++ b/src/commands/music/lyric.js @@ -1,5 +1,5 @@ const { EmbedBuilder, ApplicationCommandOptionType } = require("discord.js"); -const { MESSAGES, EMBED_COLORS } = require("@root/config"); +const { EMBED_COLORS } = require("@root/config"); /** * @type {import("@structures/Command")} @@ -39,86 +39,93 @@ module.exports = { }; async function getLyric({ client, guild, member }, query) { - const player = client.musicManager.getPlayer(guild.id); - - if (!player) { - return "🚫 There's no active music player in this server."; - } + /** @type {import('../../handlers/manager')} */ + const manager = client.musicManager + const player = manager.getPlayer(guild.id) + let node = player?.node + if (!node) [node] = manager.nodeManager.nodes.values() let track; - let lyrics; + if (!query) { + // query not specified - use currently playing music + track = player?.queue.current; + if (!track) { + return "🚫 No music is currently playing!"; + } + } else { + // query specified -- search + const result = await node.search({ query }, member.user); + if (!result || result.loadType === "error" || result.loadType === "empty") { + return "Failed to find any tracks for the query."; + } + track = result.tracks[0]; + } + + // lavalyrics try { + const lyrics = await node.request(`/lyrics?track=${encodeURIComponent(track.encoded)}&skipTrackSource=${true}`) + if (lyrics && 'text' in lyrics) return createLyricsEmbed(lyrics, member, track) + else console.log(`Failed search LavaLyrics track ${track.info.title}`, lyrics) + } catch (e) { + console.error(`Error search LavaLyrics track ${track.info.title}`, e) + } + + // javatimed + if (node.info.plugins.includes('java-timed-lyrics')) { + // java-timed (current) if (!query) { - if (!player.queue.current) { - return "🚫 No music is currently playing!"; + try { + const lyrics = await getCurrentLyricJavaTimed(node, guild.id) + if (lyrics && 'text' in lyrics) return createLyricsEmbed(lyrics, member, track) + else console.log(`Failed search JavaTimed (current) track ${track.info.title}`, lyrics) + } catch (e) { + console.error(`Error search JavaTimed (current) track ${track.info.title}`, e) } - track = player.queue.current; - } else { - const result = await player.search({ query }, member.user); - if (!result || result.loadType === "error" || result.loadType === "empty") { - return "Failed to find any tracks for the query."; - } - track = result.tracks[0]; } - const node = player.node; - const baseUrl = (node.options.port !== 80 && node.options.secure) - ? `https://${node.options.host}:${node.options.port}` - : `http://${node.options.host}:${node.options.port}`; - - // Try default lyrics endpoint first + // java-timed (search) try { - const defaultLyricsUrl = `${baseUrl}/v4/sessions/${node.sessionId}/players/${guild.id}/track/lyrics?skipTrackSource=true`; - const defaultRes = await fetch(defaultLyricsUrl, { - headers: { Authorization: node.options.authorization } - }); - - if (defaultRes.ok) { - lyrics = await defaultRes.json(); - if (lyrics) { - return createLyricsEmbed(lyrics, member, track); + const lyrics = await searchLyricJavaTimed(node, guild.id) + if (lyrics.lines) { + for (const line of lyrics.lines) { + line.timestamp = line.range.start + line.duration = line.range.end - line.range.start } } - } catch (err) { - client.logger.debug("Default lyrics endpoint failed:", err); + if (lyrics && 'text' in lyrics) return createLyricsEmbed(lyrics, member, track) + else console.log(`Failed search JavaTimed (search) track ${track.info.title}`, lyrics) + } catch (e) { + console.error(`Error search JavaTimed (search) track ${track.info.title}`, e) } - - // Fallback to genius search if default endpoint fails - const geniusUrl = `${baseUrl}/v4/lyrics/search?query=${encodeURIComponent(track.info.title + " " + track.info.author)}&source=genius`; - const geniusRes = await fetch(geniusUrl, { - headers: { Authorization: node.options.authorization } - }); - - if (!geniusRes.ok) { - if (geniusRes.status === 404) return "No lyrics found for this song."; - throw new Error(`Failed to fetch lyrics: ${geniusRes.status} ${geniusRes.statusText}`); - } - - lyrics = await geniusRes.json(); - return createLyricsEmbed(lyrics, member, track); - - } catch (error) { - client.logger.error("Lyric Command Error:", error); - return "An error occurred while fetching the lyrics. Please try again later."; } + + return "No lyrics found" } function createLyricsEmbed(lyrics, member, track) { - if (!lyrics) { - return "No lyrics found for this song."; - } - const embed = new EmbedBuilder() .setColor(EMBED_COLORS.BOT_EMBED) .setTitle(`${track.info.author} - ${track.info.title}`) .setThumbnail(track.info.artworkUrl) - .setFooter({ text: `Requested by: ${member.user.displayName} | Source: ${lyrics.source || 'Unknown'}` }); + .setFooter({ text: `Requested by: ${member.user.displayName} | Source: ${lyrics.provider || lyrics.source || lyrics.sourceName || '-'}` }); + + const lines = !lyrics.lines ? lyrics.text ?? 'No lyrics found' : lyrics.lines + .filter(Boolean) + .map(line => line.line) + .join("\n"); - const lines = lyrics.lines.map(line => line.line).filter(Boolean).join("\n"); const truncatedLyrics = lines.length > 4096 ? `${lines.slice(0, 4093)}...` : lines; embed.setDescription(truncatedLyrics); return { embeds: [embed] }; } + +function getCurrentLyricJavaTimed(node, guildId) { + return node.request(`/sessions/${node.sessionId}/players/${guildId}/lyrics`) +} + +function searchLyricJavaTimed(node, track, source = "genius") { + return node.request(`/lyrics/search?query=${encodeURIComponent(track.info.title + " " + track.info.author)}&source=${source}`) +} diff --git a/src/commands/owner/eval.js b/src/commands/owner/eval.js index 47b8c0f..99fac35 100644 --- a/src/commands/owner/eval.js +++ b/src/commands/owner/eval.js @@ -1,9 +1,13 @@ -const { EmbedBuilder, ApplicationCommandOptionType } = require("discord.js"); +const { EmbedBuilder, ApplicationCommandOptionType, AttachmentBuilder } = require("discord.js"); const { EMBED_COLORS } = require("@root/config"); // This dummy token will be replaced by the actual token const DUMMY_TOKEN = "MY_TOKEN_IS_SECRET"; +const AsyncFunctionConstructor = (async() => {}).constructor +let prev = null +let store = Object.create(null) + /** * @type {import("@structures/Command")} */ @@ -13,71 +17,113 @@ module.exports = { category: "OWNER", botPermissions: ["EmbedLinks"], command: { - enabled: true, + enabled: process.env.DEV === 'true', usage: "