add support lavalink v4

This commit is contained in:
Amane Serenetia 2024-06-11 12:18:40 +07:00
parent e1a08f2a46
commit 17b4eb0914
21 changed files with 1081 additions and 544 deletions

View File

@ -54,37 +54,51 @@ module.exports = {
ENABLED: true, ENABLED: true,
IDLE_TIME: 60, // Time in seconds before the bot disconnects from an idle voice channel IDLE_TIME: 60, // Time in seconds before the bot disconnects from an idle voice channel
MAX_SEARCH_RESULTS: 100, MAX_SEARCH_RESULTS: 100,
DEFAULT_SOURCE: "YT", // YT = Youtube, YTM = Youtube Music, SC = SoundCloud DEFAULT_VOLUME: 50, // Default volume for the music player (0-100)
DEFAULT_SOURCE: "ytsearch", // ytsearch = Youtube, ytmsearch = Youtube Music, spsearch = Spotify, scsearch = SoundCloud
// Lavalink Websocket configuration
LAVALINK_WS: {
clientName: "Kiera-Bot", // The name of the lavalink client.
resuming: true, // Whether Lavalink should attempt to resume existing sessions when reconnecting.
reconnecting: {
tries: Infinity, // Number of times to attempt reconnecting.
delay: 20000 // Delay
}
},
// Add any number of lavalink nodes here // Add any number of lavalink nodes here
// Refer to https://github.com/freyacodes/Lavalink to host your own lavalink server
LAVALINK_NODES: [ LAVALINK_NODES: [
{ {
host: "lavalink.serenetia.com", info: {
port: 443, host: "lavalink.serenetia.com",
password: "amanechan", port: 443,
auth: "amanechan",
},
id: "Indonesia Node secure", id: "Indonesia Node secure",
secure: true,
}, },
{ {
host: "lavalink-sg.serenetia.com", info: {
port: 443, host: "lavalinkv4.serenetia.com",
password: "amanechan", auth: "amanechan",
id: "Singapore Node secure", port: 433,
secure: true, secure: true,
},
identifier: "Indonesia V4 Secure"
}, },
{ {
host: "103.125.38.143", info: {
port: 3556, host: "103.125.38.143",
password: "amanechan", port: 3556,
id: "Indonesia Node not secure", auth: "amanechan",
secure: false, },
identifier: "Indonesia Node not secure",
}, },
{ {
host: "217.15.162.4", info: {
port: 23333, host: "103.125.38.143",
password: "amanechan", port: 3557,
id: "Singapore Node not secure", auth: "amanechan",
secure: false, },
identifier: "Indonesia V4 not secure",
}, },
], ],
}, },

1190
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "KieraBot", "name": "KieraBot",
"version": "5.5.0", "version": "5.5.1",
"description": "multipurpose discord bot built using discord-js", "description": "multipurpose discord bot built using discord-js",
"main": "bot.js", "main": "bot.js",
"author": "Amane", "author": "Amane",
@ -24,6 +24,7 @@
"url": "https://github.com/MinazukiAmane/Kiera-Bot/issues" "url": "https://github.com/MinazukiAmane/Kiera-Bot/issues"
}, },
"dependencies": { "dependencies": {
"@lavaclient/plugin-queue": "^0.0.1",
"@lavaclient/queue": "^2.1.1", "@lavaclient/queue": "^2.1.1",
"@lavaclient/spotify": "^3.1.0", "@lavaclient/spotify": "^3.1.0",
"@lavaclient/types": "^2.1.1", "@lavaclient/types": "^2.1.1",
@ -43,7 +44,7 @@
"express-session": "^1.18.0", "express-session": "^1.18.0",
"fixedsize-map": "^1.0.1", "fixedsize-map": "^1.0.1",
"iso-639-1": "^3.1.0", "iso-639-1": "^3.1.0",
"lavaclient": "^4.1.1", "lavaclient": "^5.0.0-rc.3",
"module-alias": "^2.2.3", "module-alias": "^2.2.3",
"moment": "^2.30.1", "moment": "^2.30.1",
"mongoose": "^8.1.1", "mongoose": "^8.1.1",

View File

@ -70,8 +70,8 @@ module.exports = {
* @param {number} level * @param {number} level
*/ */
function setBassBoost({ client, guildId }, level) { function setBassBoost({ client, guildId }, level) {
const player = client.musicManager.getPlayer(guildId); const player = client.musicManager.players.resolve(guildId);
const bands = new Array(3).fill(null).map((_, i) => ({ band: i, gain: levels[level] })); const bands = new Array(3).fill(null).map((_, i) => ({ band: i, gain: levels[level] }));
player.setEqualizer(...bands); player.setFilters(...bands);
return `> Set the bassboost level to \`${level}\``; return `> Set the bassboost level to \`${level}\``;
} }

View File

@ -1,5 +1,5 @@
const { musicValidations } = require("@helpers/BotUtils"); const { musicValidations } = require("@helpers/BotUtils");
const { LoopType } = require("@lavaclient/queue"); const { LoopType } = require("@lavaclient/plugin-queue");
const { ApplicationCommandOptionType } = require("discord.js"); const { ApplicationCommandOptionType } = require("discord.js");
/** /**
@ -56,7 +56,7 @@ module.exports = {
* @param {"queue"|"track"} type * @param {"queue"|"track"} type
*/ */
function toggleLoop({ client, guildId }, type) { function toggleLoop({ client, guildId }, type) {
const player = client.musicManager.getPlayer(guildId); const player = client.musicManager.players.resolve(guildId);
// track // track
if (type === "track") { if (type === "track") {
@ -66,7 +66,7 @@ function toggleLoop({ client, guildId }, type) {
// queue // queue
else if (type === "queue") { else if (type === "queue") {
player.queue.setLoop(1); player.queue.setLoop(LoopType.Queue);
return "Loop mode is set to `queue`"; return "Loop mode is set to `queue`";
} }
} }

View File

@ -33,36 +33,45 @@ module.exports = {
/** /**
* @param {import("discord.js").CommandInteraction|import("discord.js").Message} arg0 * @param {import("discord.js").CommandInteraction|import("discord.js").Message} arg0
*/ */
function nowPlaying({ client, guildId }) { function nowPlaying({ client, guildId, member }) {
const player = client.musicManager.getPlayer(guildId); const player = client.musicManager.players.resolve(guildId);
if (!player || !player.queue.current) return "🚫 No music is being played!"; if (!player || !player.queue.current) return "🚫 No music is being played!";
const track = player.queue.current; const track = player.queue.current;
const end = track.length > 6.048e8 ? "🔴 LIVE" : new Date(track.length).toISOString().slice(11, 19); const trackLength = track.info.isStream ? "🔴 LIVE" : prettyMs(track.info.length, { colonNotation: true });
const trackPosition = track.info.isStream ? "🔴 LIVE" : prettyMs(player.position, { colonNotation: true });
let progressBar = "";
if (!track.info.isStream) {
const totalLength = track.info.length > 6.048e8 ? player.position : track.info.length;
progressBar =
new Date(player.position).toISOString().slice(11, 19) +
" [" +
splitBar(totalLength, player.position, 15)[0] +
"] " +
new Date(track.info.length).toISOString().slice(11, 19);
} else {
progressBar = "🔴 LIVE";
}
const embed = new EmbedBuilder() const embed = new EmbedBuilder()
.setColor(EMBED_COLORS.BOT_EMBED) .setColor(EMBED_COLORS.BOT_EMBED)
.setAuthor({ name: "Now playing" }) .setAuthor({ name: "Now playing" })
.setDescription(`[${track.title}](${track.uri})`) .setDescription(`[${track.info.title}](${track.info.uri})`)
.addFields( .addFields(
{ {
name: "Song Duration", name: "Song Duration",
value: "`" + prettyMs(track.length, { colonNotation: true }) + "`", value: "`" + trackLength + "`",
inline: true, inline: true,
}, },
{ {
name: "Requested By", name: "Requested By",
value: track.requester || "Unknown", value: track.requesterId || member.displayName,
inline: true, inline: true,
}, },
{ {
name: "\u200b", name: "\u200b",
value: value: progressBar,
new Date(player.position).toISOString().slice(11, 19) +
" [" +
splitBar(track.length > 6.048e8 ? player.position : track.length, player.position, 15)[0] +
"] " +
end,
inline: false, inline: false,
} }
); );

View File

@ -30,7 +30,7 @@ module.exports = {
* @param {import("discord.js").CommandInteraction|import("discord.js").Message} arg0 * @param {import("discord.js").CommandInteraction|import("discord.js").Message} arg0
*/ */
function pause({ client, guildId }) { function pause({ client, guildId }) {
const player = client.musicManager.getPlayer(guildId); const player = client.musicManager.players.resolve(guildId);
if (player.paused) return "The player is already paused."; if (player.paused) return "The player is already paused.";
player.pause(true); player.pause(true);

View File

@ -1,13 +1,7 @@
const { EmbedBuilder, ApplicationCommandOptionType } = require("discord.js"); const { EmbedBuilder, ApplicationCommandOptionType } = require("discord.js");
const prettyMs = require("pretty-ms"); const prettyMs = require("pretty-ms");
const { EMBED_COLORS, MUSIC } = require("@root/config"); const { EMBED_COLORS, MUSIC } = require("@root/config");
const { SpotifyItemType } = require("@lavaclient/spotify"); require("@lavaclient/plugin-queue")
const search_prefix = {
YT: "ytsearch",
YTM: "ytmsearch",
SC: "scsearch",
};
/** /**
* @type {import("@structures/Command")} * @type {import("@structures/Command")}
@ -54,10 +48,10 @@ module.exports = {
async function play({ member, guild, channel }, query) { async function play({ member, guild, channel }, query) {
if (!member.voice.channel) return "🚫 You need to join a voice channel first"; if (!member.voice.channel) return "🚫 You need to join a voice channel first";
let player = guild.client.musicManager.getPlayer(guild.id); let player = guild.client.musicManager.players.resolve(guild.id);
if (player && !guild.members.me.voice.channel) { if (player && !guild.members.me.voice.channel) {
player.disconnect(); player.voice.disconnect();
await guild.client.musicManager.destroyPlayer(guild.id); await guild.client.musicManager.players.destroy(guild.id);
} }
if (player && member.voice.channel !== guild.members.me.voice.channel) { if (player && member.voice.channel !== guild.members.me.voice.channel) {
@ -67,73 +61,45 @@ async function play({ member, guild, channel }, query) {
let embed = new EmbedBuilder().setColor(EMBED_COLORS.BOT_EMBED); let embed = new EmbedBuilder().setColor(EMBED_COLORS.BOT_EMBED);
let tracks; let tracks;
let description = ""; let description = "";
let thumbnail;
try { try {
if (guild.client.musicManager.spotify.isSpotifyUrl(query)) { const res = await guild.client.musicManager.api.loadTracks(
if (!process.env.SPOTIFY_CLIENT_ID || !process.env.SPOTIFY_CLIENT_SECRET) { /^https?:\/\//.test(query) ? query : `${MUSIC.DEFAULT_SOURCE}:${query}`
return "🚫 Spotify songs cannot be played. Please contact the bot owner"; );
}
const item = await guild.client.musicManager.spotify.load(query); let track;
switch (item?.type) {
case SpotifyItemType.Track: {
const track = await item.resolveYoutubeTrack();
tracks = [track];
description = `[${track.info.title}](${track.info.uri})`;
break;
}
case SpotifyItemType.Artist: switch (res.loadType) {
tracks = await item.resolveYoutubeTracks(); case "error":
description = `Artist: [**${item.name}**](${query})`; guild.client.logger.error("Search Exception", res.data);
break; return "🚫 There was an error while searching";
case SpotifyItemType.Album: case "empty":
tracks = await item.resolveYoutubeTracks(); return `No results found matching ${query}`;
description = `Album: [**${item.name}**](${query})`;
break;
case SpotifyItemType.Playlist: case "playlist":
tracks = await item.resolveYoutubeTracks(); tracks = res.data.tracks;
description = `Playlist: [**${item.name}**](${query})`; description = res.data.info.name;
break; thumbnail = res.data.pluginInfo.artworkUrl;
break;
default: case "track":
return "🚫 An error occurred while searching for the song"; track = res.data;
} tracks = [track];
break;
if (!tracks) guild.client.logger.debug({ query, item }); case "search":
} else { track = res.data[0];
const res = await guild.client.musicManager.rest.loadTracks( tracks = [track];
/^https?:\/\//.test(query) ? query : `${search_prefix[MUSIC.DEFAULT_SOURCE]}:${query}` break;
);
switch (res.loadType) {
case "LOAD_FAILED":
guild.client.logger.error("Search Exception", res.exception);
return "🚫 There was an error while searching";
case "NO_MATCHES": default:
return `No results found matching ${query}`; guild.client.logger.debug("Unknown loadType", res);
return "🚫 An error occurred while searching for the song";
case "PLAYLIST_LOADED":
tracks = res.tracks;
description = res.playlistInfo.name;
break;
case "TRACK_LOADED":
case "SEARCH_RESULT": {
const [track] = res.tracks;
tracks = [track];
break;
}
default:
guild.client.logger.debug("Unknown loadType", res);
return "🚫 An error occurred while searching for the song";
}
if (!tracks) guild.client.logger.debug({ query, res });
} }
if (!tracks) guild.client.logger.debug({ query, res });
} catch (error) { } catch (error) {
guild.client.logger.error("Search Exception", typeof error === "object" ? JSON.stringify(error) : error); guild.client.logger.error("Search Exception", typeof error === "object" ? JSON.stringify(error) : error);
return "🚫 An error occurred while searching for the song"; return "🚫 An error occurred while searching for the song";
@ -150,6 +116,7 @@ async function play({ member, guild, channel }, query) {
embed embed
.setAuthor({ name: "Added Track to queue" }) .setAuthor({ name: "Added Track to queue" })
.setDescription(`[${track.info.title}](${track.info.uri})`) .setDescription(`[${track.info.title}](${track.info.uri})`)
.setThumbnail(track.info.artworkUrl)
.setFooter({ text: `Requested By: ${member.user.username}` }); .setFooter({ text: `Requested By: ${member.user.username}` });
fields.push({ fields.push({
@ -170,6 +137,7 @@ async function play({ member, guild, channel }, query) {
} else { } else {
embed embed
.setAuthor({ name: "Added Playlist to queue" }) .setAuthor({ name: "Added Playlist to queue" })
.setThumbnail(thumbnail)
.setDescription(description) .setDescription(description)
.addFields( .addFields(
{ {
@ -194,14 +162,15 @@ async function play({ member, guild, channel }, query) {
// create a player and/or join the member's vc // create a player and/or join the member's vc
if (!player?.connected) { if (!player?.connected) {
player = guild.client.musicManager.createPlayer(guild.id); player = guild.client.musicManager.players.create(guild.id);
player.queue.data.channel = channel; player.queue.data.channel = channel;
player.connect(member.voice.channel.id, { deafened: true }); player.voice.connect(member.voice.channel.id, { deafened: true });
player.setVolume(MUSIC.DEFAULT_VOLUME);
} }
// do queue things // do queue things
const started = player.playing || player.paused; const started = player.playing || player.paused;
player.queue.add(tracks, { requester: member.user.username, next: false }); player.queue.add(tracks, { requester: member.user.displayName, next: false });
if (!started) { if (!started) {
await player.queue.start(); await player.queue.start();
} }

View File

@ -27,13 +27,13 @@ module.exports = {
async messageRun(message, args) { async messageRun(message, args) {
const page = args.length && Number(args[0]) ? Number(args[0]) : 1; const page = args.length && Number(args[0]) ? Number(args[0]) : 1;
const response = getQueue(message, page); const response = await getQueue(message, page);
await message.safeReply(response); await message.safeReply(response);
}, },
async interactionRun(interaction) { async interactionRun(interaction) {
const page = interaction.options.getInteger("page"); const page = interaction.options.getInteger("page");
const response = getQueue(interaction, page); const response = await getQueue(interaction, page);
await interaction.followUp(response); await interaction.followUp(response);
}, },
}; };
@ -42,8 +42,8 @@ module.exports = {
* @param {import("discord.js").CommandInteraction|import("discord.js").Message} arg0 * @param {import("discord.js").CommandInteraction|import("discord.js").Message} arg0
* @param {number} pgNo * @param {number} pgNo
*/ */
function getQueue({ client, guild }, pgNo) { async function getQueue({ client, guild }, pgNo) {
const player = client.musicManager.getPlayer(guild.id); const player = client.musicManager.players.resolve(guild.id);
if (!player) return "🚫 There is no music playing in this guild."; if (!player) return "🚫 There is no music playing in this guild.";
const queue = player.queue; const queue = player.queue;
@ -58,9 +58,16 @@ function getQueue({ client, guild }, pgNo) {
const tracks = queue.tracks.slice(start, end); const tracks = queue.tracks.slice(start, end);
if (queue.current) embed.addFields({ name: "Current", value: `[${queue.current.title}](${queue.current.uri})` }); if (queue.current) embed.addFields({ name: "Current", value: `[${queue.current.info.title}](${queue.current.info.url})` });
if (!tracks.length) embed.setDescription(`No tracks in ${page > 1 ? `page ${page}` : "the queue"}.`);
else embed.setDescription(tracks.map((track, i) => `${start + ++i} - [${track.title}](${track.uri})`).join("\n")); const queueList = track.map((track, index) => {
const title = track.info.title;
const uri = track.info.uri;
return `${start + index + 1}. [${title}](${uri})`;
});
if (!queueList.length) embed.setDescription(`No tracks in ${page > 1 ? `page ${page}` : "the queue"}.`);
else embed.setDescription(queueList.join("\n"));
const maxPages = Math.ceil(queue.tracks.length / multiple); const maxPages = Math.ceil(queue.tracks.length / multiple);

View File

@ -30,7 +30,7 @@ module.exports = {
* @param {import("discord.js").CommandInteraction|import("discord.js").Message} arg0 * @param {import("discord.js").CommandInteraction|import("discord.js").Message} arg0
*/ */
function resumePlayer({ client, guildId }) { function resumePlayer({ client, guildId }) {
const player = client.musicManager.getPlayer(guildId); const player = client.musicManager.players.resolve(guildId);
if (!player.paused) return "The player is already resumed"; if (!player.paused) return "The player is already resumed";
player.resume(); player.resume();
return "▶️ Resumed the music player"; return "▶️ Resumed the music player";

View File

@ -6,14 +6,9 @@ const {
ComponentType, ComponentType,
} = require("discord.js"); } = require("discord.js");
const prettyMs = require("pretty-ms"); const prettyMs = require("pretty-ms");
require("@lavaclient/plugin-queue")
const { EMBED_COLORS, MUSIC } = require("@root/config"); const { EMBED_COLORS, MUSIC } = require("@root/config");
const search_prefix = {
YT: "ytsearch",
YTM: "ytmsearch",
SC: "scsearch",
};
/** /**
* @type {import("@structures/Command")} * @type {import("@structures/Command")}
*/ */
@ -60,10 +55,10 @@ module.exports = {
async function search({ member, guild, channel }, query) { async function search({ member, guild, channel }, query) {
if (!member.voice.channel) return "🚫 You need to join a voice channel first"; if (!member.voice.channel) return "🚫 You need to join a voice channel first";
let player = guild.client.musicManager.getPlayer(guild.id); let player = guild.client.musicManager.players.resolve(guild.id);
if (player && !guild.members.me.voice.channel) { if (player && !guild.members.me.voice.channel) {
player.disconnect(); player.voice.disconnect();
await guild.client.musicManager.destroyPlayer(guild.id); await guild.client.musicManager.players.destroy(guild.id);
} }
if (player && member.voice.channel !== guild.members.me.voice.channel) { if (player && member.voice.channel !== guild.members.me.voice.channel) {
return "🚫 You must be in the same voice channel as mine"; return "🚫 You must be in the same voice channel as mine";
@ -71,8 +66,8 @@ async function search({ member, guild, channel }, query) {
let res; let res;
try { try {
res = await guild.client.musicManager.rest.loadTracks( res = await guild.client.musicManager.api.loadTracks(
/^https?:\/\//.test(query) ? query : `${search_prefix[MUSIC.DEFAULT_SOURCE]}:${query}` /^https?:\/\//.test(query) ? query : `${MUSIC.DEFAULT_SOURCE}:${query}`
); );
} catch (err) { } catch (err) {
return "🚫 There was an error while searching"; return "🚫 There was an error while searching";
@ -81,17 +76,16 @@ async function search({ member, guild, channel }, query) {
let embed = new EmbedBuilder().setColor(EMBED_COLORS.BOT_EMBED); let embed = new EmbedBuilder().setColor(EMBED_COLORS.BOT_EMBED);
let tracks; let tracks;
const loadType = res.tracks.length > 0 ? res.loadType : "NO_MATCHES"; switch (res.loadType) {
switch (loadType) { case "error":
case "LOAD_FAILED": guild.client.logger.error("Search Exception", res.data);
guild.client.logger.error("Search Exception", res.exception);
return "🚫 There was an error while searching"; return "🚫 There was an error while searching";
case "NO_MATCHES": case "empty":
return `No results found matching ${query}`; return `No results found matching ${query}`;
case "TRACK_LOADED": { case "track": {
const [track] = res.tracks; const [track] = res.data[0];
tracks = [track]; tracks = [track];
if (!player?.playing && !player?.paused && !player?.queue.tracks.length) { if (!player?.playing && !player?.paused && !player?.queue.tracks.length) {
embed.setAuthor({ name: "Added Song to queue" }); embed.setAuthor({ name: "Added Song to queue" });
@ -110,7 +104,6 @@ async function search({ member, guild, channel }, query) {
inline: true, inline: true,
}); });
// if (typeof track.displayThumbnail === "function") embed.setThumbnail(track.displayThumbnail("hqdefault"));
if (player?.queue?.tracks?.length > 0) { if (player?.queue?.tracks?.length > 0) {
fields.push({ fields.push({
name: "Position in Queue", name: "Position in Queue",
@ -122,15 +115,15 @@ async function search({ member, guild, channel }, query) {
break; break;
} }
case "PLAYLIST_LOADED": case "playlist":
tracks = res.tracks; tracks = res.data.tracks;
embed embed
.setAuthor({ name: "Added Playlist to queue" }) .setAuthor({ name: "Added Playlist to queue" })
.setDescription(res.playlistInfo.name) .setDescription(res.data.info.name)
.addFields( .addFields(
{ {
name: "Enqueued", name: "Enqueued",
value: `${res.tracks.length} songs`, value: `${res.data.tracks.length} songs`,
inline: true, inline: true,
}, },
{ {
@ -138,7 +131,7 @@ async function search({ member, guild, channel }, query) {
value: value:
"`" + "`" +
prettyMs( prettyMs(
res.tracks.map((t) => t.info.length).reduce((a, b) => a + b, 0), res.data.tracks.map((t) => t.info.length).reduce((a, b) => a + b, 0),
{ colonNotation: true } { colonNotation: true }
) + ) +
"`", "`",
@ -148,11 +141,11 @@ async function search({ member, guild, channel }, query) {
.setFooter({ text: `Requested By: ${member.user.username}` }); .setFooter({ text: `Requested By: ${member.user.username}` });
break; break;
case "SEARCH_RESULT": { case "search": {
let max = guild.client.config.MUSIC.MAX_SEARCH_RESULTS; let max = guild.client.config.MUSIC.MAX_SEARCH_RESULTS;
if (res.tracks.length < max) max = res.tracks.length; if (res.data.length < max) max = res.data.length;
const results = res.tracks.slice(0, max); const results = res.data.slice(0, max);
const options = results.map((result, index) => ({ const options = results.map((result, index) => ({
label: result.info.title, label: result.info.title,
value: index.toString(), value: index.toString(),
@ -205,12 +198,13 @@ async function search({ member, guild, channel }, query) {
await sentMsg.delete(); await sentMsg.delete();
return "🚫 Failed to register your response"; return "🚫 Failed to register your response";
} }
break;
} }
} }
// create a player and/or join the member's vc // create a player and/or join the member's vc
if (!player?.connected) { if (!player?.connected) {
player = guild.client.musicManager.createPlayer(guild.id); player = guild.client.musicManager.players.create(guild.id);
player.queue.data.channel = channel; player.queue.data.channel = channel;
player.connect(member.voice.channel.id, { deafened: true }); player.connect(member.voice.channel.id, { deafened: true });
} }

View File

@ -45,10 +45,10 @@ module.exports = {
* @param {number} time * @param {number} time
*/ */
function seekTo({ client, guildId }, time) { function seekTo({ client, guildId }, time) {
const player = client.musicManager?.getPlayer(guildId); const player = client.musicManager?.players.resolve(guildId);
const seekTo = durationToMillis(time); const seekTo = durationToMillis(time);
if (seekTo > player.queue.current.length) { if (seekTo > player.queue.current.info.length) {
return "The duration you provide exceeds the duration of the current track"; return "The duration you provide exceeds the duration of the current track";
} }

View File

@ -30,7 +30,7 @@ module.exports = {
* @param {import("discord.js").CommandInteraction|import("discord.js").Message} arg0 * @param {import("discord.js").CommandInteraction|import("discord.js").Message} arg0
*/ */
function shuffle({ client, guildId }) { function shuffle({ client, guildId }) {
const player = client.musicManager.getPlayer(guildId); const player = client.musicManager.players.resolve(guildId);
player.queue.shuffle(); player.queue.shuffle();
return "🎶 Queue has been shuffled"; return "🎶 Queue has been shuffled";
} }

View File

@ -17,12 +17,12 @@ module.exports = {
}, },
async messageRun(message, args) { async messageRun(message, args) {
const response = skip(message); const response = await skip(message);
await message.safeReply(response); await message.safeReply(response);
}, },
async interactionRun(interaction) { async interactionRun(interaction) {
const response = skip(interaction); const response = await skip(interaction);
await interaction.followUp(response); await interaction.followUp(response);
}, },
}; };
@ -30,12 +30,21 @@ module.exports = {
/** /**
* @param {import("discord.js").CommandInteraction|import("discord.js").Message} arg0 * @param {import("discord.js").CommandInteraction|import("discord.js").Message} arg0
*/ */
function skip({ client, guildId }) { async function skip({ client, guildId }) {
const player = client.musicManager.getPlayer(guildId); const player = client.musicManager.players.resolve(guildId);
// check if current song is playing if (!player || !player.queue.current) {
if (!player.queue.current) return "⏯️ There is no song currently being played"; return "⏯️ There is no song currently being played";
}
const { title } = player.queue.current; const title = player.queue.current.info.title;
return player.queue.next() ? `⏯️ ${title} was skipped.` : "⏯️ There is no song to skip.";
// Check if there is a next song in the queue
if (player.queue.tracks.length === 0) {
return "There is no next song ti skip to";
}
// skip to the next song
player.queue.next();
return `⏯️ ${title} was skipped successfully.`;
} }

View File

@ -31,8 +31,8 @@ module.exports = {
* @param {import("discord.js").CommandInteraction|import("discord.js").Message} arg0 * @param {import("discord.js").CommandInteraction|import("discord.js").Message} arg0
*/ */
async function stop({ client, guildId }) { async function stop({ client, guildId }) {
const player = client.musicManager.getPlayer(guildId); const player = client.musicManager.players.resolve(guildId);
player.disconnect(); player.voice.disconnect();
await client.musicManager.destroyPlayer(guildId); await client.musicManager.players.destroy(guildId);
return "🎶 The music player is stopped and queue has been cleared"; return "🎶 The music player is stopped and queue has been cleared";
} }

View File

@ -6,7 +6,7 @@ const { ApplicationCommandOptionType } = require("discord.js");
*/ */
module.exports = { module.exports = {
name: "volume", name: "volume",
description: "set the music player volume", description: "Set the music player volume",
category: "MUSIC", category: "MUSIC",
validations: musicValidations, validations: musicValidations,
command: { command: {
@ -18,7 +18,7 @@ module.exports = {
options: [ options: [
{ {
name: "amount", name: "amount",
description: "Enter a value to set [0 to 100]", description: "Enter a value to set [1 to 100]",
type: ApplicationCommandOptionType.Integer, type: ApplicationCommandOptionType.Integer,
required: false, required: false,
}, },
@ -26,14 +26,14 @@ module.exports = {
}, },
async messageRun(message, args) { async messageRun(message, args) {
const amount = args[0]; const amount = parseInt(args[0]);
const response = await volume(message, amount); const response = await getVolume(message, amount);
await message.safeReply(response); await message.safeReply(response);
}, },
async interactionRun(interaction) { async interactionRun(interaction) {
const amount = interaction.options.getInteger("amount"); const amount = parseInt(interaction.options.getInteger("amount"));
const response = await volume(interaction, amount); const response = await getVolume(interaction, amount);
await interaction.followUp(response); await interaction.followUp(response);
}, },
}; };
@ -41,12 +41,16 @@ module.exports = {
/** /**
* @param {import("discord.js").CommandInteraction|import("discord.js").Message} arg0 * @param {import("discord.js").CommandInteraction|import("discord.js").Message} arg0
*/ */
async function volume({ client, guildId }, volume) { async function getVolume({ client, guildId }, amount) {
const player = client.musicManager.getPlayer(guildId); const player = client.musicManager.players.resolve(guildId);
if (!volume) return `> The player volume is \`${player.volume}\`.`; if (!amount) return `> The player volume is \`${player.volume}\`.`;
if (volume < 1 || volume > 100) return "you need to give me a volume between 1 and 100.";
await player.setVolume(volume); if (isNaN(amount) || amount < 0 || amount > 100) {
return `🎶 Music player volume is set to \`${volume}\`.`; return "You need to give me a volume between 1 and 100.";
}
// Set the player volume
await player.setVolume(amount);
return `🎶 Music player volume is set to \`${amount}\`.`;
} }

View File

@ -10,7 +10,7 @@ module.exports = async (client) => {
// Initialize Music Manager // Initialize Music Manager
if (client.config.MUSIC.ENABLED) { if (client.config.MUSIC.ENABLED) {
client.musicManager.connect(client.user.id); client.musicManager.connect({ userId: client.user.id });
client.logger.success("Music Manager initialized"); client.logger.success("Music Manager initialized");
} }

View File

@ -21,8 +21,8 @@ module.exports = async (client, oldState, newState) => {
setTimeout(() => { setTimeout(() => {
// if 1 (you), wait 1 minute // if 1 (you), wait 1 minute
if (!oldState.channel.members.size - 1) { if (!oldState.channel.members.size - 1) {
const player = client.musicManager.getPlayer(guild.id); const player = client.musicManager.players.resolve(guild.id);
if (player) client.musicManager.destroyPlayer(guild.id).then(player.disconnect()); // destroy the player if (player) client.musicManager.players.destroy(guild.id).then(player.voice.disconnect()); // destroy the player
} }
}, client.config.MUSIC.IDLE_TIME * 1000); }, client.config.MUSIC.IDLE_TIME * 1000);
} }

View File

@ -1,68 +1,52 @@
const { EmbedBuilder } = require("discord.js"); const { EmbedBuilder, GatewayDispatchEvents } = require("discord.js");
const { Cluster } = require("lavaclient"); const { Cluster } = require("lavaclient");
const prettyMs = require("pretty-ms"); const prettyMs = require("pretty-ms");
const { load, SpotifyItemType } = require("@lavaclient/spotify"); require("@lavaclient/plugin-queue");
require("@lavaclient/queue/register");
/** /**
* @param {import("@structures/BotClient")} client * @param {import("@structures/BotClient")} client
*/ */
module.exports = (client) => { module.exports = (client) => {
load({
client: {
id: process.env.SPOTIFY_CLIENT_ID,
secret: process.env.SPOTIFY_CLIENT_SECRET,
},
autoResolveYoutubeTracks: false,
loaders: [SpotifyItemType.Album, SpotifyItemType.Artist, SpotifyItemType.Playlist, SpotifyItemType.Track],
});
const lavaclient = new Cluster({ const lavaclient = new Cluster({
nodes: client.config.MUSIC.LAVALINK_NODES, nodes: client.config.MUSIC.LAVALINK_NODES,
sendGatewayPayload: (id, payload) => client.guilds.cache.get(id)?.shard?.send(payload), ws: client.config.MUSIC.LAVALINK_WS,
discord: {
sendGatewayCommand: (id, payload) => client.guilds.cache.get(id)?.shard?.send(payload),
},
}); });
client.ws.on("VOICE_SERVER_UPDATE", (data) => lavaclient.handleVoiceUpdate(data)); client.ws.on(GatewayDispatchEvents.VoiceStateUpdate, (data) => lavaclient.players.handleVoiceUpdate(data));
client.ws.on("VOICE_STATE_UPDATE", (data) => lavaclient.handleVoiceUpdate(data)); client.ws.on(GatewayDispatchEvents.VoiceServerUpdate, (data) => lavaclient.players.handleVoiceUpdate(data));
lavaclient.on("nodeConnect", (node, event) => { lavaclient.on("nodeConnected", (node, event) => {
client.logger.log(`Node "${node.id}" connected`); client.logger.log(`Node "${node.identifier}" connected`);
}); });
lavaclient.on("nodeDisconnect", (node, event) => { lavaclient.on("nodeDisconnected", (node, event) => {
client.logger.log(`Node "${node.id}" disconnected`); client.logger.log(`Node "${node.identifier}" disconnected`);
const reconnetInterval = 5000;
setTimeout(() => {
node.connect();
}, reconnetInterval);
}); });
lavaclient.on("nodeError", (node, error) => { lavaclient.on("nodeError", (node, error) => {
client.logger.error(`Node "${node.id}" encountered an error: ${error.message}.`, error); client.logger.error(`Node "${node.identifier}" encountered an error: ${error.message}.`, error);
}); });
lavaclient.on("nodeDebug", (node, message) => { lavaclient.on("nodeDebug", (node, event) => {
client.logger.debug(`Node "${node.id}" debug: ${message}`); client.logger.debug(`Node "${node.identifier}" debug: ${event.message}`);
}); });
lavaclient.on("nodeTrackStart", (_node, queue, song) => { lavaclient.on("nodeTrackStart", async (_node, queue, track) => {
const fields = []; const fields = [];
const embed = new EmbedBuilder() const embed = new EmbedBuilder()
.setAuthor({ name: "Now Playing" }) .setAuthor({ name: "Now Playing" })
.setColor(client.config.EMBED_COLORS.BOT_EMBED) .setColor(client.config.EMBED_COLORS.BOT_EMBED)
.setDescription(`[${song.title}](${song.uri})`) .setDescription(`[${track.info.title}](${track.info.uri})`)
.setFooter({ text: `Requested By: ${song.requester}` }); .setFooter({ text: `Requested By: ${track.requesterId}` })
.setThumbnail(track.info.artworkUrl);
if (song.sourceName === "youtube") {
const identifier = song.identifier;
const thumbnail = `https://img.youtube.com/vi/${identifier}/hqdefault.jpg`;
embed.setThumbnail(thumbnail);
}
fields.push({ fields.push({
name: "Song Duration", name: "Song Duration",
value: "`" + prettyMs(song.length, { colonNotation: true }) + "`", value: "`" + prettyMs(track.info.length, { colonNotation: true }) + "`",
inline: true, inline: true,
}); });
@ -80,7 +64,7 @@ module.exports = (client) => {
lavaclient.on("nodeQueueFinish", async (_node, queue) => { lavaclient.on("nodeQueueFinish", async (_node, queue) => {
queue.data.channel.safeSend("Queue has ended."); queue.data.channel.safeSend("Queue has ended.");
await client.musicManager.destroyPlayer(queue.player.guildId).then(queue.player.disconnect()); await client.musicManager.players.destroy(queue.player.guildId).then(() => queue.player.voice.disconnect());
}); });
return lavaclient; return lavaclient;

View File

@ -6,7 +6,7 @@ module.exports = class BotUtils {
* Check if the bot is up to date * Check if the bot is up to date
*/ */
static async checkForUpdates() { static async checkForUpdates() {
const response = await getJson("https://api.github.com/repos/MinazukiAmane/Kiera-Bot/releases/latest"); const response = await getJson("https://git.serenetia.com/api/v1/repos/Serenetia/Kiera-Bot/releases/latest");
if (!response.success) return error("VersionCheck: Failed to check for bot updates"); if (!response.success) return error("VersionCheck: Failed to check for bot updates");
if (response.data) { if (response.data) {
if ( if (
@ -15,7 +15,7 @@ module.exports = class BotUtils {
success("VersionCheck: Your discord bot is up to date"); success("VersionCheck: Your discord bot is up to date");
} else { } else {
warn(`VersionCheck: ${response.data.tag_name} update is available`); warn(`VersionCheck: ${response.data.tag_name} update is available`);
warn("download: https://github.com/MinazukiAmane/Kiera-Bot/releases/latest"); warn("download: https://git.serenetia.com/Serenetia/Kiera-Bot/releases/latest");
} }
} }
} }
@ -63,7 +63,7 @@ module.exports = class BotUtils {
static get musicValidations() { static get musicValidations() {
return [ return [
{ {
callback: ({ client, guildId }) => client.musicManager.getPlayer(guildId), callback: ({ client, guildId }) => client.musicManager.players.resolve(guildId),
message: "🚫 No music is being played!", message: "🚫 No music is being played!",
}, },
{ {
@ -72,7 +72,7 @@ module.exports = class BotUtils {
}, },
{ {
callback: ({ member, client, guildId }) => callback: ({ member, client, guildId }) =>
member.voice?.channelId === client.musicManager.getPlayer(guildId)?.channelId, member.voice?.channelId === client.musicManager.players.resolve(guildId)?.voice.channelId,
message: "🚫 You're not in the same voice channel.", message: "🚫 You're not in the same voice channel.",
}, },
]; ];

View File

@ -50,8 +50,8 @@ module.exports = class Validator {
if (config.MUSIC.LAVALINK_NODES.length == 0) { if (config.MUSIC.LAVALINK_NODES.length == 0) {
warn("config.js: There must be at least one node for Lavalink"); warn("config.js: There must be at least one node for Lavalink");
} }
if (!["YT", "YTM", "SC"].includes(config.MUSIC.DEFAULT_SOURCE)) { if (!["ytsearch", "ytmsearch", "spsearch", "scsearch"].includes(config.MUSIC.DEFAULT_SOURCE)) {
warn("config.js: MUSIC.DEFAULT_SOURCE must be either YT, YTM or SC"); warn("config.js: MUSIC.DEFAULT_SOURCE must be either ytsearch, ytmsearch, spsearch or scsearch");
} }
} }