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

@ -54,37 +54,51 @@ module.exports = {
ENABLED: true,
IDLE_TIME: 60, // Time in seconds before the bot disconnects from an idle voice channel
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
// Refer to https://github.com/freyacodes/Lavalink to host your own lavalink server
LAVALINK_NODES: [
{
host: "lavalink.serenetia.com",
port: 443,
password: "amanechan",
info: {
host: "lavalink.serenetia.com",
port: 443,
auth: "amanechan",
},
id: "Indonesia Node secure",
secure: true,
},
{
host: "lavalink-sg.serenetia.com",
port: 443,
password: "amanechan",
id: "Singapore Node secure",
secure: true,
info: {
host: "lavalinkv4.serenetia.com",
auth: "amanechan",
port: 433,
secure: true,
},
identifier: "Indonesia V4 Secure"
},
{
host: "103.125.38.143",
port: 3556,
password: "amanechan",
id: "Indonesia Node not secure",
secure: false,
info: {
host: "103.125.38.143",
port: 3556,
auth: "amanechan",
},
identifier: "Indonesia Node not secure",
},
{
host: "217.15.162.4",
port: 23333,
password: "amanechan",
id: "Singapore Node not secure",
secure: false,
info: {
host: "103.125.38.143",
port: 3557,
auth: "amanechan",
},
identifier: "Indonesia V4 not secure",
},
],
},

1190
package-lock.json generated

File diff suppressed because it is too large Load Diff

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

@ -70,8 +70,8 @@ module.exports = {
* @param {number} 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] }));
player.setEqualizer(...bands);
player.setFilters(...bands);
return `> Set the bassboost level to \`${level}\``;
}

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

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

@ -30,7 +30,7 @@ module.exports = {
* @param {import("discord.js").CommandInteraction|import("discord.js").Message} arg0
*/
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.";
player.pause(true);

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

@ -27,13 +27,13 @@ module.exports = {
async messageRun(message, args) {
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);
},
async interactionRun(interaction) {
const page = interaction.options.getInteger("page");
const response = getQueue(interaction, page);
const response = await getQueue(interaction, page);
await interaction.followUp(response);
},
};
@ -42,8 +42,8 @@ module.exports = {
* @param {import("discord.js").CommandInteraction|import("discord.js").Message} arg0
* @param {number} pgNo
*/
function getQueue({ client, guild }, pgNo) {
const player = client.musicManager.getPlayer(guild.id);
async function getQueue({ client, guild }, pgNo) {
const player = client.musicManager.players.resolve(guild.id);
if (!player) return "🚫 There is no music playing in this guild.";
const queue = player.queue;
@ -58,9 +58,16 @@ function getQueue({ client, guild }, pgNo) {
const tracks = queue.tracks.slice(start, end);
if (queue.current) embed.addFields({ name: "Current", value: `[${queue.current.title}](${queue.current.uri})` });
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"));
if (queue.current) embed.addFields({ name: "Current", value: `[${queue.current.info.title}](${queue.current.info.url})` });
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);

@ -30,7 +30,7 @@ module.exports = {
* @param {import("discord.js").CommandInteraction|import("discord.js").Message} arg0
*/
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";
player.resume();
return "▶️ Resumed the music player";

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

@ -45,10 +45,10 @@ module.exports = {
* @param {number} time
*/
function seekTo({ client, guildId }, time) {
const player = client.musicManager?.getPlayer(guildId);
const player = client.musicManager?.players.resolve(guildId);
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";
}

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

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

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

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

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

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

@ -1,68 +1,52 @@
const { EmbedBuilder } = require("discord.js");
const { EmbedBuilder, GatewayDispatchEvents } = require("discord.js");
const { Cluster } = require("lavaclient");
const prettyMs = require("pretty-ms");
const { load, SpotifyItemType } = require("@lavaclient/spotify");
require("@lavaclient/queue/register");
require("@lavaclient/plugin-queue");
/**
* @param {import("@structures/BotClient")} 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({
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("VOICE_STATE_UPDATE", (data) => lavaclient.handleVoiceUpdate(data));
client.ws.on(GatewayDispatchEvents.VoiceStateUpdate, (data) => lavaclient.players.handleVoiceUpdate(data));
client.ws.on(GatewayDispatchEvents.VoiceServerUpdate, (data) => lavaclient.players.handleVoiceUpdate(data));
lavaclient.on("nodeConnect", (node, event) => {
client.logger.log(`Node "${node.id}" connected`);
lavaclient.on("nodeConnected", (node, event) => {
client.logger.log(`Node "${node.identifier}" connected`);
});
lavaclient.on("nodeDisconnect", (node, event) => {
client.logger.log(`Node "${node.id}" disconnected`);
const reconnetInterval = 5000;
setTimeout(() => {
node.connect();
}, reconnetInterval);
lavaclient.on("nodeDisconnected", (node, event) => {
client.logger.log(`Node "${node.identifier}" disconnected`);
});
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) => {
client.logger.debug(`Node "${node.id}" debug: ${message}`);
lavaclient.on("nodeDebug", (node, event) => {
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 embed = new EmbedBuilder()
.setAuthor({ name: "Now Playing" })
.setColor(client.config.EMBED_COLORS.BOT_EMBED)
.setDescription(`[${song.title}](${song.uri})`)
.setFooter({ text: `Requested By: ${song.requester}` });
if (song.sourceName === "youtube") {
const identifier = song.identifier;
const thumbnail = `https://img.youtube.com/vi/${identifier}/hqdefault.jpg`;
embed.setThumbnail(thumbnail);
}
.setDescription(`[${track.info.title}](${track.info.uri})`)
.setFooter({ text: `Requested By: ${track.requesterId}` })
.setThumbnail(track.info.artworkUrl);
fields.push({
name: "Song Duration",
value: "`" + prettyMs(song.length, { colonNotation: true }) + "`",
value: "`" + prettyMs(track.info.length, { colonNotation: true }) + "`",
inline: true,
});
@ -80,7 +64,7 @@ module.exports = (client) => {
lavaclient.on("nodeQueueFinish", async (_node, queue) => {
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;

@ -6,7 +6,7 @@ module.exports = class BotUtils {
* Check if the bot is up to date
*/
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.data) {
if (
@ -15,7 +15,7 @@ module.exports = class BotUtils {
success("VersionCheck: Your discord bot is up to date");
} else {
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() {
return [
{
callback: ({ client, guildId }) => client.musicManager.getPlayer(guildId),
callback: ({ client, guildId }) => client.musicManager.players.resolve(guildId),
message: "🚫 No music is being played!",
},
{
@ -72,7 +72,7 @@ module.exports = class BotUtils {
},
{
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.",
},
];

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