diff --git a/src/discord/MusicPlayer/Join.ts b/src/discord/MusicPlayer/Join.ts index e5ff954..f614015 100644 --- a/src/discord/MusicPlayer/Join.ts +++ b/src/discord/MusicPlayer/Join.ts @@ -32,11 +32,13 @@ import DiscordMusicPlayer, { VoiceDisconnectedEvent, ValidTracks, DiscordMusicPlayerInstance, - DiscordMusicPlayerLoopMode + DiscordMusicPlayerLoopMode, + TrackUtils } from '../../providers/DiscordMusicPlayer'; import DiscordModule, { HybridInteractionMessage } from '../../utils/DiscordModule'; import Locale from '../../services/Locale'; +import { SpotifyTrack, YouTubeVideo } from 'play-dl'; const EMBEDS = { VOICECHANNEL_JOINED: (data: HybridInteractionMessage, locale: I18n) => { @@ -81,35 +83,37 @@ const EMBEDS = { user: data.getUser() }); }, - NOW_PLAYING: (data: HybridInteractionMessage, locale: I18n, track: ValidTracks) => { + NOW_PLAYING: async (data: HybridInteractionMessage, locale: I18n, track: ValidTracks) => { + + const title = TrackUtils.getTitle(track); + const thumbnails = await TrackUtils.getThumbnails(track); + const embed = makeInfoEmbed({ title: locale.__('musicplayer.now_playing'), icon: '🎵 ', - description: track.title, + description: title, user: DiscordProvider.client.user }); - const highestResolutionThumbnail = track.thumbnails.reduce((prev, current) => - prev.height * prev.width > current.height * current.width ? prev : current - ); - - if (highestResolutionThumbnail) embed.setImage(highestResolutionThumbnail.url); + if (TrackUtils.getHighestResolutionThumbnail(thumbnails)) + embed.setImage(TrackUtils.getHighestResolutionThumbnail(thumbnails).url); return embed; }, - NOW_REPEATING: (data: HybridInteractionMessage, locale: I18n, track: ValidTracks) => { + NOW_REPEATING: async (data: HybridInteractionMessage, locale: I18n, track: ValidTracks) => { + + const title = TrackUtils.getTitle(track); + const thumbnails = await TrackUtils.getThumbnails(track); + const embed = makeInfoEmbed({ title: locale.__('musicplayer.now_playing_repeating'), icon: '🎵 ', - description: `${track.title}`, + description: title, user: DiscordProvider.client.user }); - const highestResolutionThumbnail = track.thumbnails.reduce((prev, current) => - prev.height * prev.width > current.height * current.width ? prev : current - ); - - if (highestResolutionThumbnail) embed.setImage(highestResolutionThumbnail.url); + if (TrackUtils.getHighestResolutionThumbnail(thumbnails)) + embed.setImage(TrackUtils.getHighestResolutionThumbnail(thumbnails).url); return embed; } @@ -227,24 +231,37 @@ export async function joinVoiceChannelProcedure( if (event.instance.queue.track[0] !== previousTrack) isLoopMessageSent = false; else if (event.instance.queue.track[0] === previousTrack && isLoopMessageSent) return; - const row = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setEmoji('▶️') - .setLabel(' Open on YouTube') - .setURL(encodeURI(`https://www.youtube.com/watch?v=${event.instance.queue.track[0].id}`)) - .setStyle(ButtonStyle.Link) - ); + const row = new ActionRowBuilder(); + if(event.instance.queue.track[0] instanceof SpotifyTrack) + row.addComponents( + new ButtonBuilder() + .setEmoji('🟢') + .setLabel(' Open on Spotify') + .setURL(encodeURI(`https://open.spotify.com/track/${event.instance.queue.track[0].id}`)) + .setStyle(ButtonStyle.Link), + ) + if(event.instance.queue.track[0] instanceof YouTubeVideo || event.instance.queue.track[0] instanceof SpotifyTrack) { + const actualPlaybackURL = event.instance.getActualPlaybackURL(); + if(event.instance.queue.track[0] instanceof SpotifyTrack && !actualPlaybackURL) return; + row.addComponents( + new ButtonBuilder() + .setEmoji('🔴') + .setLabel(' Open on YouTube') + .setURL(event.instance.queue.track[0] instanceof YouTubeVideo ? encodeURI(`https://www.youtube.com/watch?v=${event.instance.queue.track[0].id}`) : encodeURI(event.instance.getActualPlaybackURL()!)) + .setStyle(ButtonStyle.Link), + ) + } if (event.instance.textChannel) { if (event.instance.getLoopMode() === DiscordMusicPlayerLoopMode.Current) { isLoopMessageSent = true; await sendMessage(event.instance.textChannel, undefined, { - embeds: [EMBEDS.NOW_REPEATING(data, locale, event.instance.queue.track[0])], + embeds: [await EMBEDS.NOW_REPEATING(data, locale, event.instance.queue.track[0])], components: [row] }); } else { await sendMessage(event.instance.textChannel, undefined, { - embeds: [EMBEDS.NOW_PLAYING(data, locale, event.instance.queue.track[0])], + embeds: [await EMBEDS.NOW_PLAYING(data, locale, event.instance.queue.track[0])], components: [row] }); } diff --git a/src/discord/MusicPlayer/NowPlaying.ts b/src/discord/MusicPlayer/NowPlaying.ts index bae412f..b30ed2b 100644 --- a/src/discord/MusicPlayer/NowPlaying.ts +++ b/src/discord/MusicPlayer/NowPlaying.ts @@ -1,26 +1,29 @@ import { Message, CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js"; import { I18n } from "i18n"; +import { SpotifyTrack, YouTubeVideo } from "play-dl"; import DiscordProvider from "../../providers/Discord"; -import DiscordMusicPlayer, { ValidTracks } from "../../providers/DiscordMusicPlayer"; +import DiscordMusicPlayer, { TrackUtils, ValidTracks } from "../../providers/DiscordMusicPlayer"; import Locale from "../../services/Locale"; import { makeErrorEmbed, makeInfoEmbed, sendHybridInteractionMessageResponse } from "../../utils/DiscordMessage"; import DiscordModule, { HybridInteractionMessage } from "../../utils/DiscordModule"; const EMBEDS = { - NOW_PLAYING: (data: HybridInteractionMessage, locale: I18n, track: ValidTracks) => { + NOW_PLAYING: async (data: HybridInteractionMessage, locale: I18n, track: ValidTracks) => { + + const title = TrackUtils.getTitle(track); + const thumbnails = await TrackUtils.getThumbnails(track); + const embed = makeInfoEmbed({ title: locale.__('musicplayer.now_playing'), icon: '🎵 ', - description: track.title, + description: title, user: DiscordProvider.client.user }); - const highestResolutionThumbnail = track.thumbnails.reduce((prev, current) => (prev.height * prev.width > current.height * current.width) ? prev : current) - - if(highestResolutionThumbnail) - embed.setImage(highestResolutionThumbnail.url); + if (TrackUtils.getHighestResolutionThumbnail(thumbnails)) + embed.setImage(TrackUtils.getHighestResolutionThumbnail(thumbnails).url); return embed; }, @@ -55,15 +58,27 @@ export default class NowPlaying extends DiscordModule { const instance = DiscordMusicPlayer.getGuildInstance(guild.id); if(!instance || !instance.queue.track[0]) return await sendHybridInteractionMessageResponse(data, { embeds: [EMBEDS.NO_MUSIC_PLAYING(data, locale)] }); - const row = new ActionRowBuilder() - .addComponents( + const row = new ActionRowBuilder(); + + if(instance.queue.track[0] instanceof SpotifyTrack) + row.addComponents( new ButtonBuilder() - .setEmoji('▶️') - .setLabel(' Open on YouTube') - .setURL(encodeURI(`https://www.youtube.com/watch?v=${instance.queue.track[0].id}`)) + .setEmoji('🟢') + .setLabel(' Open on Spotify') + .setURL(encodeURI(`https://open.spotify.com/track/${instance.queue.track[0].id}`)) .setStyle(ButtonStyle.Link), ) - - return await sendHybridInteractionMessageResponse(data, { embeds: [EMBEDS.NOW_PLAYING(data, locale, instance.queue.track[0])], components: [row] }); + if(instance.queue.track[0] instanceof YouTubeVideo || instance.queue.track[0] instanceof SpotifyTrack) { + const actualPlaybackURL = instance.getActualPlaybackURL(); + if(instance.queue.track[0] instanceof SpotifyTrack && !actualPlaybackURL) return; + row.addComponents( + new ButtonBuilder() + .setEmoji('🔴') + .setLabel(' Open on YouTube') + .setURL(instance.queue.track[0] instanceof YouTubeVideo ? encodeURI(`https://www.youtube.com/watch?v=${instance.queue.track[0].id}`) : encodeURI(instance.getActualPlaybackURL()!)) + .setStyle(ButtonStyle.Link), + ) + } + return await sendHybridInteractionMessageResponse(data, { embeds: [await EMBEDS.NOW_PLAYING(data, locale, instance.queue.track[0])], components: [row] }); } } \ No newline at end of file diff --git a/src/discord/MusicPlayer/Play.ts b/src/discord/MusicPlayer/Play.ts index d5d7f1a..9ce5ef6 100644 --- a/src/discord/MusicPlayer/Play.ts +++ b/src/discord/MusicPlayer/Play.ts @@ -14,7 +14,7 @@ import { I18n } from 'i18n'; import { joinVoiceChannelProcedure } from './Join'; import DiscordProvider from '../../providers/Discord'; -import DiscordMusicPlayer, { ValidTracks } from '../../providers/DiscordMusicPlayer'; +import DiscordMusicPlayer, { TrackUtils, ValidTracks } from '../../providers/DiscordMusicPlayer'; import Locale from '../../services/Locale'; import DiscordModule, { HybridInteractionMessage } from '../../utils/DiscordModule'; @@ -24,6 +24,7 @@ import { sendHybridInteractionMessageResponse, makeInfoEmbed } from '../../utils/DiscordMessage'; +import { SpotifyTrack, YouTubeVideo } from 'play-dl'; const EMBEDS = { PLAY_INFO: (data: HybridInteractionMessage, locale: I18n) => { @@ -33,24 +34,25 @@ const EMBEDS = { fields: [ { name: locale.__('common.available_args'), - value: locale.__('musicplayer_play.valid_args'), + value: locale.__('musicplayer_play.valid_args') } ], user: data.getUser() }); }, - ADDED_QUEUE: (data: HybridInteractionMessage, locale: I18n, track: ValidTracks) => { + ADDED_QUEUE: async (data: HybridInteractionMessage, locale: I18n, track: ValidTracks) => { + + const title = TrackUtils.getTitle(track); + const thumbnails = await TrackUtils.getThumbnails(track); + let embed = makeSuccessEmbed({ title: locale.__('musicplayer_play.queue_added_song'), - description: track.title, + description: title, user: data.getUser() }); - const highestResolutionThumbnail = track.thumbnails.reduce((prev, current) => - prev.height * prev.width > current.height * current.width ? prev : current - ); - - if (highestResolutionThumbnail) embed.setImage(highestResolutionThumbnail.url); + if (TrackUtils.getHighestResolutionThumbnail(thumbnails)) + embed.setImage(TrackUtils.getHighestResolutionThumbnail(thumbnails).url); return embed; }, @@ -158,7 +160,7 @@ export default class Play extends DiscordModule { await joinVoiceChannelProcedure(new HybridInteractionMessage(interaction), null, voiceChannel); let instance = DiscordMusicPlayer.getGuildInstance(guild.id); - if(!instance) return; + if (!instance) return; if (instance!.voiceChannel.id !== member.voice.channel.id) return await sendHybridInteractionMessageResponse( @@ -223,7 +225,8 @@ export default class Play extends DiscordModule { const voiceChannel = DiscordProvider.client.guilds.cache.get(guild.id)?.channels.cache.get(payload.d.v); //let member = DiscordProvider.client.guilds.cache.get(guild.id)?.members.cache.get(user.id); - if (!voiceChannel || !(voiceChannel instanceof VoiceChannel || voiceChannel instanceof StageChannel)) return; + if (!voiceChannel || !(voiceChannel instanceof VoiceChannel || voiceChannel instanceof StageChannel)) + return; if (!member.voice.channel) return await sendHybridInteractionMessageResponse( @@ -236,12 +239,16 @@ export default class Play extends DiscordModule { await joinVoiceChannelProcedure(new HybridInteractionMessage(interaction), null, voiceChannel); let instance = DiscordMusicPlayer.getGuildInstance(guild.id); - if(!instance) return; + if (!instance) return; if (instance!.voiceChannel.id !== member.voice.channel.id) return await sendHybridInteractionMessageResponse( hybridData, - { embeds: [EMBEDS.USER_NOT_IN_SAME_VOICECHANNEL(new HybridInteractionMessage(interaction), locale)] }, + { + embeds: [ + EMBEDS.USER_NOT_IN_SAME_VOICECHANNEL(new HybridInteractionMessage(interaction), locale) + ] + }, true ); @@ -267,7 +274,7 @@ export default class Play extends DiscordModule { instance.addTrackToQueue(result); return await sendHybridInteractionMessageResponse( hybridData, - { embeds: [EMBEDS.ADDED_QUEUE(new HybridInteractionMessage(interaction), locale, result)] }, + { embeds: [await EMBEDS.ADDED_QUEUE(new HybridInteractionMessage(interaction), locale, result)] }, true ); } @@ -306,7 +313,7 @@ export default class Play extends DiscordModule { } let instance = DiscordMusicPlayer.getGuildInstance(guild.id); - if(!instance) return; + if (!instance) return; if (instance!.voiceChannel.id !== member.voice.channel.id) return await sendHybridInteractionMessageResponse( @@ -368,13 +375,55 @@ export default class Play extends DiscordModule { ); return await sendHybridInteractionMessageResponse(data, { - embeds: [EMBEDS.ADDED_QUEUE(data, locale, result)], + embeds: [await EMBEDS.ADDED_QUEUE(data, locale, result)], components: [row] }); } else return await sendHybridInteractionMessageResponse(data, { - embeds: [EMBEDS.ADDED_QUEUE(data, locale, result)] + embeds: [await EMBEDS.ADDED_QUEUE(data, locale, result)] }); + } else if (DiscordMusicPlayer.isSpotifyLink(query)) { + let linkData = DiscordMusicPlayer.parseSpotifyLink(query); + + if (linkData.type === 'playlist' || linkData.type === 'album') { + let playlist = await DiscordMusicPlayer.getSpotifySongsInPlayList(query); + const songs = await playlist.all_tracks(); + + for (const song of songs) { + instance.addTrackToQueue(song); + } + + return await sendHybridInteractionMessageResponse( + data, + { embeds: [EMBEDS.ADDED_SONGS_QUEUE(data, locale, songs)] }, + true + ); + } else if (false) { + sendHybridInteractionMessageResponse( + data, + { embeds: [EMBEDS.LOOKUP_ERROR(data, locale, new Error('Album not supported for now'))] }, + true + ); + return null; + } + + let result = await DiscordMusicPlayer.searchSpotifyBySpotifyLink(linkData).catch((err) => { + sendHybridInteractionMessageResponse(data, { embeds: [EMBEDS.LOOKUP_ERROR(data, locale, err)] }, true); + return null; + }); + + if (!result) return; + + if (data.isMessage() && data.getMessage().embeds.length > 0) { + data.getMessage().suppressEmbeds(true); + } + + instance.addTrackToQueue(result); + + return await sendHybridInteractionMessageResponse(data, { + embeds: [await EMBEDS.ADDED_QUEUE(data, locale, result)] + }); + } else { let result = await DiscordMusicPlayer.searchYouTubeByQuery(query); @@ -407,13 +456,13 @@ export default class Play extends DiscordModule { ); return await sendHybridInteractionMessageResponse(data, { - embeds: [EMBEDS.ADDED_QUEUE(data, locale, result[0])], + embeds: [await EMBEDS.ADDED_QUEUE(data, locale, result[0])], components: [row] }); } return await sendHybridInteractionMessageResponse(data, { - embeds: [EMBEDS.ADDED_QUEUE(data, locale, result[0])] + embeds: [await EMBEDS.ADDED_QUEUE(data, locale, result[0])] }); } } diff --git a/src/discord/MusicPlayer/Queue.ts b/src/discord/MusicPlayer/Queue.ts index d818b7f..9f68fc5 100644 --- a/src/discord/MusicPlayer/Queue.ts +++ b/src/discord/MusicPlayer/Queue.ts @@ -3,7 +3,8 @@ import { I18n } from 'i18n'; import DiscordMusicPlayer, { DiscordMusicPlayerInstance, - DiscordMusicPlayerLoopMode + DiscordMusicPlayerLoopMode, + TrackUtils } from '../../providers/DiscordMusicPlayer'; import Locale from '../../services/Locale'; @@ -21,14 +22,15 @@ const EMBEDS = { user: data.getUser() }); else { + const nowPlayingText = `${ instance.getLoopMode() === DiscordMusicPlayerLoopMode.Current ? ` ${locale.__('musicplayer_queue.looping_current')}` : '' - }: [${queue.track[0].title}](${queue.track[0].url})`; + }: [${TrackUtils.getTitle(queue.track[0])}](${queue.track[0].url})`; if (queue.track.length == 1) { - let queueString = `1. [${queue.track[0].title}](${queue.track[0].url})`; + let queueString = `1. [${TrackUtils.getTitle(queue.track[0])}](${queue.track[0].url})`; return makeInfoEmbed({ title: locale.__('musicplayer_queue.title'), description: `${locale.__('musicplayer_queue.now_playing')}${nowPlayingText}\n @@ -36,10 +38,10 @@ const EMBEDS = { user: data.getUser() }); } else if (queue.track.length >= 10) { - const upcomingText = `[${queue.track[1].title}](${queue.track[1].url})`; + const upcomingText = `[${TrackUtils.getTitle(queue.track[1])}](${queue.track[1].url})`; const first10 = queue.track.slice(0, 10); let queueString = first10 - .map((track, index) => `${index + 1}. [${track.title}](${track.url})`) + .map((track, index) => `${index + 1}. [${TrackUtils.getTitle(track)}](${track.url})`) .join('\n'); return makeInfoEmbed({ title: locale.__('musicplayer_queue.title'), @@ -50,9 +52,9 @@ const EMBEDS = { user: data.getUser() }); } else { - const upcomingText = `[${queue.track[1].title}](${queue.track[1].url})`; + const upcomingText = `[${TrackUtils.getTitle(queue.track[1])}](${queue.track[1].url})`; const queueString = queue.track - .map((track, index) => `${index + 1}. [${track.title}](${track.url})`) + .map((track, index) => `${index + 1}. [${TrackUtils.getTitle(track)}](${track.url})`) .join('\n'); return makeInfoEmbed({ title: locale.__('musicplayer_queue.title'), diff --git a/src/discord/MusicPlayer/Search.ts b/src/discord/MusicPlayer/Search.ts index 5aadebd..a8f018f 100644 --- a/src/discord/MusicPlayer/Search.ts +++ b/src/discord/MusicPlayer/Search.ts @@ -3,7 +3,7 @@ import { I18n } from "i18n"; import { joinVoiceChannelProcedure } from "./Join"; -import DiscordMusicPlayer, { ValidTracks } from "../../providers/DiscordMusicPlayer"; +import DiscordMusicPlayer, { TrackUtils, ValidTracks } from "../../providers/DiscordMusicPlayer"; import Locale from "../../services/Locale"; import DiscordModule, { HybridInteractionMessage } from "../../utils/DiscordModule"; @@ -28,7 +28,7 @@ const EMBEDS = { title: locale.__('musicplayer_search.title_result'), description: `${locale.__('musicplayer_search.x_results_found', { COUNT: result.length.toString() - })}\n\n${result.map(track => `- [${track.title}](${track.url})`).join('\n')}`, + })}\n\n${result.map(track => `- [${TrackUtils.getTitle(track)}](${track.url})`).join('\n')}`, user: data.getUser() }); },