diff --git a/base/Client.js b/base/Client.js index 231f535d..4cf33b94 100644 --- a/base/Client.js +++ b/base/Client.js @@ -1,7 +1,7 @@ const { Client, Collection, SlashCommandBuilder, ContextMenuCommandBuilder, EmbedBuilder, PermissionsBitField, ChannelType } = require("discord.js"), { GiveawaysManager } = require("discord-giveaways"), { REST } = require("@discordjs/rest"), - { LavalinkManager } = require("lavalink-client"), + { Player } = require("discord-player"), { Routes } = require("discord-api-types/v10"); const BaseEvent = require("./BaseEvent.js"), @@ -32,61 +32,43 @@ class JaBaClient extends Client { this.databaseCache.members = new Collection(); this.databaseCache.usersReminds = new Collection(); - this.lavalink = new LavalinkManager({ - nodes: [this.config.lavalinkNodes], - sendToShard: (guildId, payload) => this.guilds.cache.get(guildId)?.shard?.send(payload), - client: { - id: this.config.userId, - username: "JaBa", - }, - autoSkip: true, - playerOptions: { - defaultSearchPlatform: "ytmsearch", - volumeDecrementer: 1, - onDisconnect: { - autoReconnect: false, - destroyPlayer: true, - }, - onEmptyQueue: { - destroyAfterMs: 5000, - }, + this.player = new Player(this); + this.player.extractors.loadDefault(null, { + SpotifyExtractor: { + clientId: this.config.spotify.clientId, + clientSecret: this.config.spotify.clientSecret, }, }); - this.lavalink.on("trackStart", async (player, track) => { - const guildData = await this.findOrCreateGuild(player.guildId), - channel = this.channels.cache.get(player.textChannelId); - + this.player.events.on("playerStart", async (queue, track) => { const m = ( - await channel.send({ - content: this.translate("music/play:NOW_PLAYING", { songName: `${track.info.title} - ${track.info.author}` }, guildData.language), + await queue.metadata.channel.send({ + content: this.translate("music/play:NOW_PLAYING", { songName: `${track.title} - ${track.author}` }, queue.metadata.data.guild.language), }) ).id; - if (track.info.duration > 1) + if (track.durationMS > 1) setTimeout(() => { - const message = channel.messages.cache.get(m); + const message = queue.metadata.channel.messages.cache.get(m); if (message && message.deletable) message.delete(); - }, track.info.duration); + }, track.durationMS); else setTimeout(() => { - const message = channel.messages.cache.get(m); + const message = queue.metadata.channel.messages.cache.get(m); if (message && message.deletable) message.delete(); }, 5 * 60 * 1000); }); - this.lavalink.on("queueEnd", async player => { - const guildData = await this.findOrCreateGuild(player.guildId), - channel = this.channels.cache.get(player.textChannelId); - - channel.send(this.translate("music/play:QUEUE_ENDED", null, guildData.language)); + this.player.events.on("emptyQueue", queue => queue.metadata.channel.send(this.translate("music/play:QUEUE_ENDED", null, queue.metadata.data.guild.language))); + this.player.events.on("emptyChannel", queue => queue.metadata.channel.send(this.translate("music/play:STOP_EMPTY", null, queue.metadata.data.guild.language))); + this.player.events.on("playerError", (queue, e) => { + queue.metadata.channel.send({ content: this.translate("music/play:ERR_OCCURRED", { error: e.message }, queue.metadata.data.guild.language) }); + console.log(e); }); - this.lavalink.on("trackError", async player => { - const guildData = await this.findOrCreateGuild(player.guildId), - channel = this.channels.cache.get(player.textChannelId); - - channel.send({ content: this.translate("music/play:ERR_OCCURRED", null, guildData.language) }); + this.player.events.on("error", (queue, e) => { + queue.metadata.channel.send({ content: this.translate("music/play:ERR_OCCURRED", { error: e.message }, queue.metadata.data.guild.language) }); + console.log(e); }); this.giveawaysManager = new GiveawaysManager(this, { diff --git a/commands/!DISABLED/Music/back.js b/commands/Music/back.js similarity index 100% rename from commands/!DISABLED/Music/back.js rename to commands/Music/back.js diff --git a/commands/!DISABLED/Music/clips.js b/commands/Music/clips.js similarity index 100% rename from commands/!DISABLED/Music/clips.js rename to commands/Music/clips.js diff --git a/commands/Music/loop.js b/commands/Music/loop.js index 2203dbbe..00519b71 100644 --- a/commands/Music/loop.js +++ b/commands/Music/loop.js @@ -1,4 +1,5 @@ -const { SlashCommandBuilder } = require("discord.js"); +const { SlashCommandBuilder } = require("discord.js"), + { QueueRepeatMode } = require("discord-player"); const BaseCommand = require("../../base/BaseCommand"); class Loop extends BaseCommand { @@ -26,10 +27,10 @@ class Loop extends BaseCommand { }) .setRequired(true) .setChoices( - // { name: client.translate("music/loop:AUTOPLAY"), value: "3" }, - { name: client.translate("music/loop:QUEUE"), value: "queue" }, - { name: client.translate("music/loop:TRACK"), value: "track" }, - { name: client.translate("music/loop:DISABLE"), value: "off" }, + { name: client.translate("music/loop:AUTOPLAY"), value: "3" }, + { name: client.translate("music/loop:QUEUE"), value: "2" }, + { name: client.translate("music/loop:TRACK"), value: "1" }, + { name: client.translate("music/loop:DISABLE"), value: "0" }, ), ), dirname: __dirname, @@ -43,25 +44,25 @@ class Loop extends BaseCommand { * @param {import("discord.js").ChatInputCommandInteraction} interaction */ async execute(client, interaction) { - await interaction.deferReply(); - const voice = interaction.member.voice.channel; if (!voice) return interaction.error("music/play:NO_VOICE_CHANNEL", null, { edit: true }); - const player = client.lavalink.getPlayer(interaction.guildId); - if (!player) return interaction.error("music/play:NOT_PLAYING", null, { edit: true }); - - const type = interaction.options.getString("option"); + const queue = client.player.nodes.get(interaction.guildId); + if (!queue) return interaction.error("music/play:NOT_PLAYING", null, { edit: true }); const translated = { - // "3": interaction.translate("music/loop:AUTOPLAY_ENABLED"), - "queue": interaction.translate("music/loop:QUEUE_ENABLED"), - "track": interaction.translate("music/loop:TRACK_ENABLED"), - "off": interaction.translate("music/loop:LOOP_DISABLED"), + "3": interaction.translate("music/loop:AUTOPLAY_ENABLED"), + "2": interaction.translate("music/loop:QUEUE_ENABLED"), + "1": interaction.translate("music/loop:TRACK_ENABLED"), + "0": interaction.translate("music/loop:LOOP_DISABLED"), }; - await player.setRepeatMode(type); - interaction.editReply({ content: translated[type] }); + const type = interaction.options.getString("option"), + mode = type === "3" ? QueueRepeatMode.AUTOPLAY : type === "2" ? QueueRepeatMode.QUEUE : type === "1" ? QueueRepeatMode.TRACK : QueueRepeatMode.OFF; + + queue.setRepeatMode(mode); + + interaction.reply({ content: translated[type] }); } } diff --git a/commands/!DISABLED/Music/nowplaying.js b/commands/Music/nowplaying.js similarity index 100% rename from commands/!DISABLED/Music/nowplaying.js rename to commands/Music/nowplaying.js diff --git a/commands/Music/play.c.js b/commands/Music/play.c.js index 1d98b053..2813fcf0 100644 --- a/commands/Music/play.c.js +++ b/commands/Music/play.c.js @@ -29,37 +29,39 @@ class PlayContext extends BaseCommand { if (!links) return interaction.error("music/play:NO_LINK", null, { edit: true }); const query = links[0], - voice = interaction.member.voice; - if (!voice.channel) return interaction.error("music/play:NO_VOICE_CHANNEL", null, { edit: true }); + voice = interaction.member.voice.channel; + if (!voice) return interaction.error("music/play:NO_VOICE_CHANNEL", null, { edit: true }); - const perms = voice.channel.permissionsFor(client.user); + const perms = voice.permissionsFor(client.user); if (!perms.has(PermissionsBitField.Flags.Connect) || !perms.has(PermissionsBitField.Flags.Speak)) return interaction.error("music/play:VOICE_CHANNEL_CONNECT", null, { edit: true }); - const player = await client.lavalink.createPlayer({ - guildId: interaction.guildId, - voiceChannelId: voice.channelId, - textChannelId: interaction.channelId, - selfDeaf: true, - selfMute: false, - volume: 100, + const searchResult = await client.player.search(query, { + requestedBy: interaction.user, }); - await player.connect(); + if (!searchResult.hasTracks()) { + console.log(searchResult); - const res = await player.search({ query }, interaction.member); + return interaction.error("music/play:NO_RESULT", { query }, { edit: true }); + } else { + await client.player.play(voice, searchResult, { + nodeOptions: { + metadata: interaction, + }, + selfDeaf: true, + leaveOnEnd: false, + leaveOnStop: true, + skipOnNoStream: true, + maxSize: 100, + maxHistorySize: 50, + }); - if (res.loadType === "playlist") await player.queue.add(res.tracks); - else if (res.loadType === "search") await player.queue.add(res.tracks[0]); - else if (res.loadType === "track") await player.queue.add(res.tracks[0]); - else console.log(res); - - if (!player.playing) await player.play(); - - interaction.editReply({ - content: interaction.translate("music/play:ADDED_QUEUE", { - songName: res.loadType === "playlist" ? res.playlist.name : `${res.tracks[0].info.title} - ${res.tracks[0].info.author}`, - }), - }); + interaction.editReply({ + content: interaction.translate("music/play:ADDED_QUEUE", { + songName: searchResult.hasPlaylist() ? searchResult.playlist.title : searchResult.tracks[0].title, + }), + }); + } } } diff --git a/commands/Music/play.js b/commands/Music/play.js index d8c5f21a..c0b21ca2 100644 --- a/commands/Music/play.js +++ b/commands/Music/play.js @@ -1,4 +1,5 @@ -const { SlashCommandBuilder, PermissionsBitField } = require("discord.js"); +const { SlashCommandBuilder, PermissionsBitField } = require("discord.js"), + { QueryType } = require("discord-player"); const BaseCommand = require("../../base/BaseCommand"); class Play extends BaseCommand { @@ -24,7 +25,8 @@ class Play extends BaseCommand { uk: client.translate("music/play:QUERY", null, "uk-UA"), ru: client.translate("music/play:QUERY", null, "ru-RU"), }) - .setRequired(true), + .setRequired(true) + .setAutocomplete(true), ), dirname: __dirname, ownerOnly: false, @@ -40,37 +42,39 @@ class Play extends BaseCommand { await interaction.deferReply(); const query = interaction.options.getString("query"), - voice = interaction.member.voice; - if (!voice.channel) return interaction.error("music/play:NO_VOICE_CHANNEL", null, { edit: true }); + voice = interaction.member.voice.channel; + if (!voice) return interaction.error("music/play:NO_VOICE_CHANNEL", null, { edit: true }); - const perms = voice.channel.permissionsFor(client.user); + const perms = voice.permissionsFor(client.user); if (!perms.has(PermissionsBitField.Flags.Connect) || !perms.has(PermissionsBitField.Flags.Speak)) return interaction.error("music/play:VOICE_CHANNEL_CONNECT", null, { edit: true }); - const player = await client.lavalink.createPlayer({ - guildId: interaction.guildId, - voiceChannelId: voice.channelId, - textChannelId: interaction.channelId, - selfDeaf: true, - selfMute: false, - volume: 100, + const searchResult = await client.player.search(query, { + requestedBy: interaction.user, }); - await player.connect(); + if (!searchResult.hasTracks()) { + console.log(searchResult); - const res = await player.search({ query }, interaction.member); + return interaction.error("music/play:NO_RESULT", { query }, { edit: true }); + } else { + await client.player.play(voice, searchResult, { + nodeOptions: { + metadata: interaction, + }, + selfDeaf: true, + leaveOnEnd: false, + leaveOnStop: true, + skipOnNoStream: true, + maxSize: 100, + maxHistorySize: 50, + }); - if (res.loadType === "playlist") await player.queue.add(res.tracks); - else if (res.loadType === "search") await player.queue.add(res.tracks[0]); - else if (res.loadType === "track") await player.queue.add(res.tracks[0]); - else console.log(res); - - if (!player.playing) await player.play(); - - interaction.editReply({ - content: interaction.translate("music/play:ADDED_QUEUE", { - songName: res.loadType === "playlist" ? res.playlist.name : `${res.tracks[0].info.title} - ${res.tracks[0].info.author}`, - }), - }); + interaction.editReply({ + content: interaction.translate("music/play:ADDED_QUEUE", { + songName: searchResult.hasPlaylist() ? searchResult.playlist.title : `${searchResult.tracks[0].title} - ${searchResult.tracks[0].author}`, + }), + }); + } } /** @@ -79,34 +83,32 @@ class Play extends BaseCommand { * @param {import("discord.js").AutocompleteInteraction} interaction * @returns */ - // async autocompleteRun(client, interaction) { // TODO: Works from time to time - // const query = interaction.options.getString("query"); - // if (query === "") return; + async autocompleteRun(client, interaction) { + const query = interaction.options.getString("query"); + if (query === "" || query === null) return; - // if (!client.lavalink.players.get(interaction.guildId)) { - // const player = await client.lavalink.createPlayer({ - // guildId: interaction.guildId, - // voiceChannelId: interaction.member.voice.channelId, - // textChannelId: interaction.channelId, - // selfDeaf: true, - // selfMute: false, - // volume: 100, - // }); + const youtubeResults = await client.player.search(query, { searchEngine: QueryType.YOUTUBE }); + const spotifyResults = await client.player.search(query, { searchEngine: QueryType.SPOTIFY_SEARCH }); + const tracks = []; - // const results = await player.search({ query }, interaction.member); - // if (results.loadType === "empty") return interaction.respond([{ name: "Nothing found", "value": "https://www.youtube.com/watch?v=dQw4w9WgXcQ" }]); - // const tracks = []; + youtubeResults.tracks + .slice(0, 5) + .map(t => ({ + name: `YouTube: ${`${t.title} - ${t.author} (${t.duration})`.length > 75 ? `${`${t.title} - ${t.author}`.substring(0, 75)}... (${t.duration})` : `${t.title} - ${t.author} (${t.duration})`}`, + value: t.url, + })) + .forEach(t => tracks.push({ name: t.name, value: t.value })); - // results.tracks - // .map(t => ({ - // name: `YouTube: ${`${t.info.title} - ${t.info.author} (${client.functions.printDate(client, t.info.duration, null, interaction.data.guild.lanugage)})`.length > 75 ? `${`${t.info.title} - ${t.info.author}`.substring(0, 75)}... (${client.functions.printDate(client, t.info.duration, null, interaction.data.guild.lanugage)})` : `${t.info.title} - ${t.info.author} (${client.functions.printDate(client, t.info.duration, null, interaction.data.guild.lanugage)})`}`, - // value: t.info.uri, - // })) - // .forEach(t => tracks.push({ name: t.name, value: t.value })); + spotifyResults.tracks + .slice(0, 5) + .map(t => ({ + name: `Spotify: ${`${t.title} - ${t.author} (${t.duration})`.length > 75 ? `${`${t.title} - ${t.author}`.substring(0, 75)}... (${t.duration})` : `${t.title} - ${t.author} (${t.duration})`}`, + value: t.url, + })) + .forEach(t => tracks.push({ name: t.name, value: t.value })); - // return interaction.respond(tracks); - // } - // } + return interaction.respond(tracks); + } } module.exports = Play; diff --git a/commands/Music/queue.js b/commands/Music/queue.js index 794220f0..37dad4e1 100644 --- a/commands/Music/queue.js +++ b/commands/Music/queue.js @@ -32,10 +32,10 @@ class Queue extends BaseCommand { if (interaction.customId.startsWith("queue_")) { const locale = (await client.findOrCreateGuild(interaction.guildId)).language; - const player = client.lavalink.getPlayer(interaction.guildId); - if (!player) return interaction.error("music/play:NOT_PLAYING"); + const queue = client.player.nodes.get(interaction.guildId); + if (!queue) return interaction.error("music/play:NOT_PLAYING", null, locale); - const { embeds, size } = generateQueueEmbeds(interaction, player, locale); + const { embeds, size } = generateQueueEmbeds(interaction, queue, locale); let currentPage = Number(interaction.message.content.match(/\d+/g)[0]) - 1 ?? 0; @@ -123,10 +123,10 @@ class Queue extends BaseCommand { * @param {import("discord.js").ChatInputCommandInteraction} interaction */ async execute(client, interaction) { - const player = client.lavalink.getPlayer(interaction.guildId); - if (!player) return interaction.error("music/play:NOT_PLAYING"); + const queue = client.player.nodes.get(interaction.guildId); + if (!queue) return interaction.error("music/play:NOT_PLAYING"); - const { embeds, size } = generateQueueEmbeds(interaction, player), + const { embeds, size } = generateQueueEmbeds(interaction, queue), row = new ActionRowBuilder().addComponents( new ButtonBuilder().setCustomId("queue_prev_page").setStyle(ButtonStyle.Primary).setEmoji("⬅️"), new ButtonBuilder().setCustomId("queue_next_page").setStyle(ButtonStyle.Primary).setEmoji("➡️"), @@ -151,29 +151,29 @@ class Queue extends BaseCommand { /** * * @param {import("discord.js").ChatInputCommandInteraction} interaction - * @param {import("lavalink-client").Player} player + * @param {import("discord-player").GuildQueue} queue * @param {string} locale * @returns */ -function generateQueueEmbeds(interaction, player, locale) { +function generateQueueEmbeds(interaction, queue, locale) { const embeds = [], - currentTrack = player.queue.current, + currentTrack = queue.currentTrack, translated = { - // "3": interaction.translate("music/loop:AUTOPLAY"), - "queue": interaction.translate("music/loop:QUEUE", null, locale), - "track": interaction.translate("music/loop:TRACK", null, locale), - "off": interaction.translate("common:DISABLED", null, locale), + "3": interaction.translate("music/loop:AUTOPLAY", null, locale), + "2": interaction.translate("music/loop:QUEUE", null, locale), + "1": interaction.translate("music/loop:TRACK", null, locale), + "0": interaction.translate("common:DISABLED", null, locale), }; let k = 10; - if (!player.queue.tracks.length) { + if (!queue.tracks.size) { const embed = interaction.client.embed({ title: interaction.translate("music/nowplaying:CURRENTLY_PLAYING", null, locale), - thumbnail: currentTrack.info.artworkUrl || null, - description: `${interaction.translate("music/nowplaying:REPEAT", null, locale)}: \`${translated[player.repeatMode]}\`\n${ - currentTrack.info.uri.startsWith("./clips", null, locale) ? `${currentTrack.info.title} (clips)` : `[${currentTrack.info.title}](${currentTrack.info.uri})` - }\n> ${interaction.translate("music/queue:ADDED", null, locale)} ${currentTrack.requester.toString()}\n\n**${interaction.translate("music/queue:NEXT", null, locale)}**\n${interaction.translate("music/queue:NO_QUEUE", null, locale)}`, + thumbnail: currentTrack.thumbnail || null, + description: `${interaction.translate("music/nowplaying:REPEAT", null, locale)}: \`${translated[queue.repeatMode]}\`\n${ + currentTrack.url.startsWith("./clips") ? `${currentTrack.title} (clips)` : `[${currentTrack.title}](${currentTrack.url})` + }\n> ${interaction.translate("music/queue:ADDED", null, locale)} ${currentTrack.requestedBy}\n\n**${interaction.translate("music/queue:NEXT", null, locale)}**\n${interaction.translate("music/queue:NO_QUEUE", null, locale)}`, }); embeds.push(embed); @@ -181,19 +181,19 @@ function generateQueueEmbeds(interaction, player, locale) { return { embeds: embeds, size: embeds.length }; } - for (let i = 0; i < player.queue.tracks.length; i += 10) { - const current = player.queue.tracks.slice(i, k); + for (let i = 0; i < queue.getSize(); i += 10) { + const current = queue.tracks.toArray().slice(i, k); let j = i; k += 10; - const info = current.map(track => `${++j}. ${track.info.uri.startsWith("./clips") ? `${track.info.title} (clips)` : `[${track.info.title}](${track.info.uri})`}\n> ${interaction.translate("music/queue:ADDED", null, locale)} ${track.requester.toString()}`).join("\n"); + const info = current.map(track => `${++j}. ${track.url.startsWith("./clips") ? `${track.title} (clips)` : `[${track.title}](${track.url})`}\n> ${interaction.translate("music/queue:ADDED", null, locale)} ${track.requestedBy}`).join("\n"); const embed = interaction.client.embed({ title: interaction.translate("music/nowplaying:CURRENTLY_PLAYING", null, locale), - thumbnail: currentTrack.info.artworkUrl || null, - description: `${interaction.translate("music/nowplaying:REPEAT", null, locale)}: \`${translated[player.repeatMode]}\`\n${ - currentTrack.info.uri.startsWith("./clips") ? `${currentTrack.info.title} (clips)` : `[${currentTrack.info.title}](${currentTrack.info.uri})` - }\n * ${interaction.translate("music/queue:ADDED", null, locale)} ${currentTrack.requester.toString()}\n\n**${interaction.translate("music/queue:NEXT", null, locale)}**\n${info}`, + thumbnail: currentTrack.thumbnail || null, + description: `${interaction.translate("music/nowplaying:REPEAT", null, locale)}: \`${translated[queue.repeatMode]}\`\n${ + currentTrack.url.startsWith("./clips") ? `${currentTrack.title} (clips)` : `[${currentTrack.title}](${currentTrack.url})` + }\n * ${interaction.translate("music/queue:ADDED", null, locale)} ${currentTrack.requestedBy}\n\n**${interaction.translate("music/queue:NEXT", null, locale)}**\n${info}`, }); embeds.push(embed); diff --git a/commands/Music/seek.js b/commands/Music/seek.js index 1a58b40d..86b560aa 100644 --- a/commands/Music/seek.js +++ b/commands/Music/seek.js @@ -40,11 +40,10 @@ class Seek extends BaseCommand { voice = interaction.member.voice.channel; if (!voice) return interaction.error("music/play:NO_VOICE_CHANNEL"); - const player = client.lavalink.getPlayer(interaction.guildId); - if (!player) return interaction.error("music/play:NOT_PLAYING"); - - await player.seek(time * 1000); + const queue = client.player.nodes.get(interaction.guildId); + if (!queue) return interaction.error("music/play:NOT_PLAYING"); + queue.node.seek(time * 1000); interaction.success("music/seek:SUCCESS", { time: `**${time}** ${client.functions.getNoun(time, interaction.translate("misc:NOUNS:SECONDS:1"), interaction.translate("misc:NOUNS:SECONDS:2"), interaction.translate("misc:NOUNS:SECONDS:5"))}`, }); diff --git a/commands/Music/shuffle.js b/commands/Music/shuffle.js index f88f2d2d..8d5890c1 100644 --- a/commands/Music/shuffle.js +++ b/commands/Music/shuffle.js @@ -30,10 +30,10 @@ class Shuffle extends BaseCommand { const voice = interaction.member.voice.channel; if (!voice) return interaction.error("music/play:NO_VOICE_CHANNEL", null, { ephemeral: true }); - const player = client.lavalink.getPlayer(interaction.guildId); - if (!player) return interaction.error("music/play:NOT_PLAYING", null, { ephemeral: true }); + const queue = client.player.nodes.get(interaction.guildId); + if (!queue) return interaction.error("music/play:NOT_PLAYING", null, { ephemeral: true }); - await player.queue.shuffle(); + queue.tracks.shuffle(); interaction.success("music/shuffle:SUCCESS"); } } diff --git a/commands/Music/skip.js b/commands/Music/skip.js index 60170f5b..8fcd7a1c 100644 --- a/commands/Music/skip.js +++ b/commands/Music/skip.js @@ -40,26 +40,25 @@ class Skip extends BaseCommand { const voice = interaction.member.voice.channel; if (!voice) return interaction.error("music/play:NO_VOICE_CHANNEL"); - const player = client.lavalink.getPlayer(interaction.guildId); - if (!player) return interaction.error("music/play:NOT_PLAYING"); + const queue = client.player.nodes.get(interaction.guildId); + if (!queue) return interaction.error("music/play:NOT_PLAYING"); const position = interaction.options.getInteger("position"); if (position) { - if (position <= 0) return interaction.error("music/skip:NO_PREV_SONG"); + if (position <= 0) return interaction.error("music/skipto:NO_PREV_SONG"); - if (player.queue.tracks[position]) { - await player.skip(position); + if (queue.tracks.at(position - 1)) { + queue.node.skipTo(queue.tracks.at(position - 1)); - return interaction.success("music/skip:SUCCESS", { - track: player.queue.current.info.title, + interaction.success("music/skipto:SUCCESS", { + track: queue.tracks.at(0).title, }); - } else return interaction.error("music/skip:ERROR", { position }); + } else return interaction.error("music/skipto:ERROR", { position }); } else { - await player.skip(); + queue.node.skip(); interaction.success("music/skip:SUCCESS"); } - } } diff --git a/commands/Music/stop.js b/commands/Music/stop.js index 0e1b6c3a..a5e34364 100644 --- a/commands/Music/stop.js +++ b/commands/Music/stop.js @@ -30,10 +30,10 @@ class Stop extends BaseCommand { const voice = interaction.member.voice.channel; if (!voice) return interaction.error("music/play:NO_VOICE_CHANNEL"); - const player = client.lavalink.getPlayer(interaction.guildId); - if (!player) return interaction.error("music/play:NOT_PLAYING"); + const queue = client.player.nodes.get(interaction.guildId); + if (!queue) return interaction.error("music/play:NOT_PLAYING"); - await player.destroy(); + queue.delete(); interaction.success("music/stop:SUCCESS"); } } diff --git a/commands/Music/volume.js b/commands/Music/volume.js index 04ff38a3..00a72d7d 100644 --- a/commands/Music/volume.js +++ b/commands/Music/volume.js @@ -1,6 +1,5 @@ const { SlashCommandBuilder } = require("discord.js"); const BaseCommand = require("../../base/BaseCommand"); -const numbers = Array.from({ length: 100 }, (_, k) => k + 1); class Volume extends BaseCommand { /** @@ -42,13 +41,13 @@ class Volume extends BaseCommand { const voice = interaction.member.voice.channel; if (!voice) return interaction.error("music/play:NO_VOICE_CHANNEL", null, { ephemeral: true }); - const player = client.lavalink.getPlayer(interaction.guildId); - if (!player) return interaction.error("music/play:NOT_PLAYING", null, { ephemeral: true }); + const queue = client.player.nodes.get(interaction.guildId); + if (!queue) return interaction.error("music/play:NOT_PLAYING", null, { ephemeral: true }); const volume = interaction.options.getInteger("int"); if (volume <= 0 || volume > 100) return interaction.error("misc:INVALID_NUMBER_RANGE", { min: 1, max: 100 }); - await player.setVolume(volume); + queue.node.setVolume(volume); interaction.success("music/volume:SUCCESS", { volume, }); @@ -62,7 +61,7 @@ class Volume extends BaseCommand { */ async autocompleteRun(client, interaction) { const int = interaction.options.getInteger("int"), - results = numbers.filter(i => i.toString().includes(int)); + results = Array.from({ length: 100 }, (_, k) => k + 1).filter(i => i.toString().includes(int)); return await interaction.respond( results.slice(0, 25).map(i => ({ diff --git a/config.sample.js b/config.sample.js index 4a698a95..54d5fd4d 100644 --- a/config.sample.js +++ b/config.sample.js @@ -8,13 +8,10 @@ module.exports = { /* Set to true for production */ /* If set to false, commands only will be registered on the support.id server */ production: true, - /* Lavalink Nodes */ - lavalinkNodes: { - id: "localhost", - host: "localhost", - port: 2333, - authorization: "strongpassword", - secure: true, + /* Spotify */ + spotify: { + clientId: "XXXXXXXXXXXXXXXXXXXXXXXXXXXX", + clientSecret: "XXXXXXXXXXXXXXXXXXXXXXXXXXXX", }, /* Support server */ support: { diff --git a/events/ready.js b/events/ready.js index 06e32a68..96bc66b9 100644 --- a/events/ready.js +++ b/events/ready.js @@ -61,10 +61,6 @@ class Ready extends BaseEvent { if (status[i + 1]) i++; else i = 0; }, 30 * 1000); // Every 30 seconds - - await client.lavalink.init({ ...client.user }); - client.on("raw", d => client.lavalink.sendRawData(d)); - client.logger.ready("Lavalink ready."); } } diff --git a/package.json b/package.json index 1c376c52..16b7a56b 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ }, "author": "@jonny_bro", "dependencies": { + "@discord-player/extractor": "^4.4.7", "@discordjs/opus": "^0.9.0", "@discordjs/rest": "^2.2.0", "@discordjs/voice": "^0.16.1", @@ -18,16 +19,17 @@ "cron": "^2.4.4", "discord-api-types": "^0.37.71", "discord-giveaways": "^6.0.1", + "discord-player": "^6.6.8", "discord.js": "^14.14.1", "gamedig": "^4.1.0", "i18next": "^21.10.0", "i18next-fs-backend": "^1.2.0", - "lavalink-client": "^2.1.7", "md5": "^2.3.0", "moment": "^2.29.4", "mongoose": "^7.6.3", "ms": "^2.1.3", - "node-fetch": "^2.7.0" + "node-fetch": "^2.7.0", + "youtube-ext": "^1.1.23" }, "devDependencies": { "eslint": "^8.56.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 69ffba04..4e44ab5e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,6 +5,9 @@ settings: excludeLinksFromLockfile: false dependencies: + '@discord-player/extractor': + specifier: ^4.4.7 + version: 4.4.7 '@discordjs/opus': specifier: ^0.9.0 version: 0.9.0 @@ -29,6 +32,9 @@ dependencies: discord-giveaways: specifier: ^6.0.1 version: 6.0.1(discord.js@14.14.1) + discord-player: + specifier: ^6.6.8 + version: 6.6.8(@discord-player/extractor@4.4.7)(@discordjs/opus@0.9.0) discord.js: specifier: ^14.14.1 version: 14.14.1 @@ -41,9 +47,6 @@ dependencies: i18next-fs-backend: specifier: ^1.2.0 version: 1.2.0 - lavalink-client: - specifier: ^2.1.7 - version: 2.1.7 md5: specifier: ^2.3.0 version: 2.3.0 @@ -59,6 +62,9 @@ dependencies: node-fetch: specifier: ^2.7.0 version: 2.7.0 + youtube-ext: + specifier: ^1.1.23 + version: 1.1.23 devDependencies: eslint: @@ -79,6 +85,39 @@ packages: regenerator-runtime: 0.14.0 dev: false + /@discord-player/equalizer@0.2.3: + resolution: {integrity: sha512-71UAepYMbHTg2QQLXQAgyuXYHrgAYpJDxjg9dRWfTUNf+zfOAlyJEiRRk/WFhQyGu6m23iLR/H/JxgF4AW8Csg==} + dev: false + + /@discord-player/extractor@4.4.7: + resolution: {integrity: sha512-XHG9Y45rQVWk3quf0IJqAj1ybTqiRgAy6vr5hnlaDZeaxXlsHRlDSzmSYl+teFVw2G9bjzR0jIvm8a4BW9hCBw==} + dependencies: + file-type: 16.5.4 + genius-lyrics: 4.4.7 + isomorphic-unfetch: 4.0.2 + node-html-parser: 6.1.13 + reverbnation-scraper: 2.0.0 + soundcloud.ts: 0.5.2 + spotify-url-info: 3.2.13 + youtube-sr: 4.3.11 + transitivePeerDependencies: + - encoding + dev: false + + /@discord-player/ffmpeg@0.1.0: + resolution: {integrity: sha512-0kW6q4gMQN2B4Z4EzmUgXrKQSXXmyhjdZBBZ/6jSHZ9fh814oOu+JXP01VvtWHwTylI7qJHIctEWtSyjEubCJg==} + dev: false + + /@discord-player/opus@0.1.2: + resolution: {integrity: sha512-yF0m+pW7H9RCbRcgk/i6vv47tlWxaCHjp6/F0W4GXZMGZ0pcvZaxk8ic7aFPc3+IoDvrAHvWNomLq+JeFzdncA==} + dev: false + + /@discord-player/utils@0.2.2: + resolution: {integrity: sha512-UklWUT7BcZEkBgywM9Cmpo2nwj3SQ9Wmhu6ml1uy/YRQnY8IRdZEHD84T2kfjOg4LVZek0ej1VerIqq7a9PAHQ==} + dependencies: + '@discordjs/collection': 1.5.3 + dev: false + /@discordjs/builders@1.7.0: resolution: {integrity: sha512-GDtbKMkg433cOZur8Dv6c25EHxduNIBsxeHrsRoIM8+AwmEZ8r0tEpckx/sHwTLwQPOF3e2JWloZh9ofCaMfAw==} engines: {node: '>=16.11.0'} @@ -334,6 +373,10 @@ packages: defer-to-connect: 2.0.1 dev: false + /@tokenizer/token@0.3.0: + resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} + dev: false + /@types/http-cache-semantics@4.0.3: resolution: {integrity: sha512-V46MYLFp08Wf2mmaBhvgjStM3tPa+2GAdy/iqoX+noX1//zje2x4XmrIU0cAwyClATsTmahbtoQ2EwP7I5WSiA==} dev: false @@ -611,6 +654,11 @@ packages: engines: {node: '>= 6'} dev: false + /data-uri-to-buffer@4.0.1: + resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} + engines: {node: '>= 12'} + dev: false + /debug@4.3.4: resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} engines: {node: '>=6.0'} @@ -678,6 +726,47 @@ packages: serialize-javascript: 6.0.1 dev: false + /discord-player@6.6.8(@discord-player/extractor@4.4.7)(@discordjs/opus@0.9.0): + resolution: {integrity: sha512-aZQxtWbHaQPYs0KWU3XObxQLabWg7RWvLlytEitVsqbyfoBFKv2KMYX4swq0/I6eBbu2755z80pbvwU7ycjmsw==} + peerDependencies: + '@discord-player/extractor': ^4.4.7 + dependencies: + '@discord-player/equalizer': 0.2.3 + '@discord-player/extractor': 4.4.7 + '@discord-player/ffmpeg': 0.1.0 + '@discord-player/utils': 0.2.2 + discord-voip: 0.1.3(@discordjs/opus@0.9.0) + ip: 1.1.9 + libsodium-wrappers: 0.7.13 + transitivePeerDependencies: + - '@discordjs/opus' + - bufferutil + - ffmpeg-static + - node-opus + - opusscript + - utf-8-validate + dev: false + + /discord-voip@0.1.3(@discordjs/opus@0.9.0): + resolution: {integrity: sha512-9DWY5/BLPXeldVwPr8/ggGjggTYOTw77aGQc3+4n5K54bRbbiJ9DUJc+mJzDiSLoHN3f286eRGACJYtrUu27xA==} + engines: {node: '>=16.9.0'} + dependencies: + '@discord-player/ffmpeg': 0.1.0 + '@discord-player/opus': 0.1.2 + '@types/ws': 8.5.10 + discord-api-types: 0.37.71 + prism-media: 1.3.5(@discordjs/opus@0.9.0) + tslib: 2.6.2 + ws: 8.14.2 + transitivePeerDependencies: + - '@discordjs/opus' + - bufferutil + - ffmpeg-static + - node-opus + - opusscript + - utf-8-validate + dev: false + /discord.js@14.14.1: resolution: {integrity: sha512-/hUVzkIerxKHyRKopJy5xejp4MYKDPTszAnpYxzVVv4qJYf+Tkt+jnT2N29PIPschicaEEpXwF2ARrTYHYwQ5w==} engines: {node: '>=16.11.0'} @@ -864,6 +953,14 @@ packages: reusify: 1.0.4 dev: true + /fetch-blob@3.2.0: + resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} + engines: {node: ^12.20 || >= 14.13} + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 3.3.3 + dev: false + /file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -871,6 +968,15 @@ packages: flat-cache: 3.1.1 dev: true + /file-type@16.5.4: + resolution: {integrity: sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==} + engines: {node: '>=10'} + dependencies: + readable-web-to-node-stream: 3.0.2 + strtok3: 6.3.0 + token-types: 4.2.1 + dev: false + /find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} @@ -897,6 +1003,13 @@ packages: engines: {node: '>= 14.17'} dev: false + /formdata-polyfill@4.0.10: + resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} + engines: {node: '>=12.20.0'} + dependencies: + fetch-blob: 3.2.0 + dev: false + /fs-minipass@2.1.0: resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} engines: {node: '>= 8'} @@ -949,6 +1062,13 @@ packages: xmlrpc: 1.3.2 dev: false + /genius-lyrics@4.4.7: + resolution: {integrity: sha512-cgO5nSeFqtLZAUyWB+8XWMRBIRzPUSUC42N3CoDGRgKX1anGAyDUhM6/RVIJXCNnQa6XHZHswKcKgHaRiyl+GQ==} + dependencies: + node-html-parser: 6.1.13 + undici: 6.15.0 + dev: false + /get-stream@6.0.1: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} @@ -1007,6 +1127,15 @@ packages: resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} dev: false + /he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + dev: false + + /himalaya@1.1.0: + resolution: {integrity: sha512-LLase1dHCRMel68/HZTFft0N0wti0epHr3nNY7ynpLbyZpmrKMQ8YIpiOV77TM97cNpC8Wb2n6f66IRggwdWPw==} + dev: false + /htmlparser2@8.0.2: resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==} dependencies: @@ -1055,6 +1184,10 @@ packages: safer-buffer: 2.1.2 dev: false + /ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + dev: false + /ignore@5.2.4: resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} engines: {node: '>= 4'} @@ -1082,6 +1215,10 @@ packages: /inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + /ip@1.1.9: + resolution: {integrity: sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==} + dev: false + /ip@2.0.0: resolution: {integrity: sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==} dev: false @@ -1124,6 +1261,13 @@ packages: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} dev: true + /isomorphic-unfetch@4.0.2: + resolution: {integrity: sha512-1Yd+CF/7al18/N2BDbsLBcp6RO3tucSW+jcLq24dqdX5MNbCNTw1z4BsGsp4zNmjr/Izm2cs/cEqZPp4kvWSCA==} + dependencies: + node-fetch: 3.3.2 + unfetch: 5.0.0 + dev: false + /js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true @@ -1152,17 +1296,6 @@ packages: dependencies: json-buffer: 3.0.1 - /lavalink-client@2.1.7: - resolution: {integrity: sha512-MtXtyTowC+SGmnZRZ7aNuShQ7ADc2ULC8ib31sJTKzYqJ7gvjxfay9P21ewwOHtmnsOvN4999s1bPeP8K6vJOg==} - dependencies: - tslib: 2.6.2 - undici: 5.27.2 - ws: 8.14.2 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - dev: false - /levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -1171,6 +1304,16 @@ packages: type-check: 0.4.0 dev: true + /libsodium-wrappers@0.7.13: + resolution: {integrity: sha512-kasvDsEi/r1fMzKouIDv7B8I6vNmknXwGiYodErGuESoFTohGSKZplFtVxZqHaoQ217AynyIFgnOVRitpHs0Qw==} + dependencies: + libsodium: 0.7.13 + dev: false + + /libsodium@0.7.13: + resolution: {integrity: sha512-mK8ju0fnrKXXfleL53vtp9xiPq5hKM0zbDQtcxQIsSmxNgSxqCj6R7Hl9PkrNe2j29T4yoDaF7DJLK9/i5iWUw==} + dev: false + /locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -1378,6 +1521,11 @@ packages: resolution: {integrity: sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==} dev: false + /node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + dev: false + /node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} @@ -1390,6 +1538,22 @@ packages: whatwg-url: 5.0.0 dev: false + /node-fetch@3.3.2: + resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + data-uri-to-buffer: 4.0.1 + fetch-blob: 3.2.0 + formdata-polyfill: 4.0.10 + dev: false + + /node-html-parser@6.1.13: + resolution: {integrity: sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==} + dependencies: + css-select: 5.1.0 + he: 1.2.0 + dev: false + /nopt@5.0.0: resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==} engines: {node: '>=6'} @@ -1493,6 +1657,11 @@ packages: engines: {node: '>=8'} dev: true + /peek-readable@4.1.0: + resolution: {integrity: sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==} + engines: {node: '>=8'} + dev: false + /prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -1571,6 +1740,13 @@ packages: util-deprecate: 1.0.2 dev: false + /readable-web-to-node-stream@3.0.2: + resolution: {integrity: sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==} + engines: {node: '>=8'} + dependencies: + readable-stream: 3.6.2 + dev: false + /regenerator-runtime@0.14.0: resolution: {integrity: sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==} dev: false @@ -1596,6 +1772,14 @@ packages: engines: {iojs: '>=1.0.0', node: '>=0.10.0'} dev: true + /reverbnation-scraper@2.0.0: + resolution: {integrity: sha512-t1Mew5QC9QEVEry5DXyagvci2O+TgXTGoMHbNoW5NRz6LTOzK/DLHUpnrQwloX8CVX5z1a802vwHM3YgUVOvKg==} + dependencies: + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding + dev: false + /rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} hasBin: true @@ -1699,6 +1883,12 @@ packages: smart-buffer: 4.2.0 dev: false + /soundcloud.ts@0.5.2: + resolution: {integrity: sha512-/pc72HWYJpSpup+mJBE9pT31JsrMcxJGBlip3Vem+0Fsscg98xh1/7I2nCpAKuMAeV6MVyrisI8TfjO6T7qKJg==} + dependencies: + undici: 5.27.2 + dev: false + /sparse-bitfield@3.0.3: resolution: {integrity: sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==} requiresBuild: true @@ -1707,6 +1897,19 @@ packages: dev: false optional: true + /spotify-uri@4.0.1: + resolution: {integrity: sha512-dEt8UN5fSsZpcPk8HOEHkv29U71kefKjcYt2dopsShrkIZhXtDXe9Xse4xq0GW6831LnEZFry5mpzm1HV/TNLw==} + engines: {node: '>= 16'} + dev: false + + /spotify-url-info@3.2.13: + resolution: {integrity: sha512-b1D4n4vnSHf8/HkLT7SIwBsj21t5AV8uhWvzU6c1v8JHS34Ocdb1SsPlannRChCuRAWMKbOEntSn/sP3RhsDfQ==} + engines: {node: '>= 12'} + dependencies: + himalaya: 1.1.0 + spotify-uri: 4.0.1 + dev: false + /string-to-stream@1.1.1: resolution: {integrity: sha512-QySF2+3Rwq0SdO3s7BAp4x+c3qsClpPQ6abAmb0DGViiSBAkT5kL6JT2iyzEVP+T1SmzHrQD1TwlP9QAHCc+Sw==} dependencies: @@ -1750,6 +1953,14 @@ packages: engines: {node: '>=8'} dev: true + /strtok3@6.3.0: + resolution: {integrity: sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==} + engines: {node: '>=10'} + dependencies: + '@tokenizer/token': 0.3.0 + peek-readable: 4.1.0 + dev: false + /supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -1772,6 +1983,14 @@ packages: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} dev: true + /token-types@4.2.1: + resolution: {integrity: sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==} + engines: {node: '>=10'} + dependencies: + '@tokenizer/token': 0.3.0 + ieee754: 1.2.1 + dev: false + /tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} dev: false @@ -1814,6 +2033,15 @@ packages: '@fastify/busboy': 2.0.0 dev: false + /undici@6.15.0: + resolution: {integrity: sha512-VviMt2tlMg1BvQ0FKXxrz1eJuyrcISrL2sPfBf7ZskX/FCEc/7LeThQaoygsMJpNqrATWQIsRVx+1Dpe4jaYuQ==} + engines: {node: '>=18.17'} + dev: false + + /unfetch@5.0.0: + resolution: {integrity: sha512-3xM2c89siXg0nHvlmYsQ2zkLASvVMBisZm5lF3gFDqfF2xonNStDJyMpvaOBe0a1Edxmqrf2E0HBdmy9QyZaeg==} + dev: false + /uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} dependencies: @@ -1828,6 +2056,11 @@ packages: resolution: {integrity: sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==} dev: false + /web-streams-polyfill@3.3.3: + resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} + engines: {node: '>= 8'} + dev: false + /webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} dev: false @@ -1903,3 +2136,13 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} dev: true + + /youtube-ext@1.1.23: + resolution: {integrity: sha512-YqOg3pQONwIZ5OMyLaDzAKmyN4ZsLzhCll20VGL20Z6aakNK6n9hHEdf7Bn3VkGw8/ehub7OpoqepHzBkomk+Q==} + dependencies: + undici: 6.15.0 + dev: false + + /youtube-sr@4.3.11: + resolution: {integrity: sha512-3oHiS2x7PpMiDRW7Cq8nz1bkAIBOJHoOwkPl/oncM/+A9/3xxMDgMLGW2dsBEP1DHFyRXYTVABgfbdwHF8sXXQ==} + dev: false