summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/bot.js4
-rw-r--r--src/handlers/controlCommands.js4
-rw-r--r--src/handlers/playCommand.js55
-rw-r--r--src/utils/helpers.js28
-rw-r--r--src/utils/player.js8
5 files changed, 74 insertions, 25 deletions
diff --git a/src/bot.js b/src/bot.js
index cd7c2db..3ec9aa1 100644
--- a/src/bot.js
+++ b/src/bot.js
@@ -1,4 +1,4 @@
-const { Client, GatewayIntentBits } = require('discord.js');
+const { Client, GatewayIntentBits, Events } = require('discord.js');
require('dotenv').config();
const commands = require('./commands/definitions');
@@ -18,7 +18,7 @@ const client = new Client({
const queues = new Map();
-client.once('ready', async () => {
+client.once(Events.ClientReady, async () => {
console.log(`Bot ready: ${client.user.tag}`);
console.log(`Ready at: ${new Date().toISOString()}`);
await registerCommands(commands, process.env.DISCORD_TOKEN, process.env.CLIENT_ID);
diff --git a/src/handlers/controlCommands.js b/src/handlers/controlCommands.js
index 14c73f7..42de36e 100644
--- a/src/handlers/controlCommands.js
+++ b/src/handlers/controlCommands.js
@@ -1,4 +1,4 @@
-const { playSong, safeCleanup, clearSongBuffer } = require('../utils/player');
+const { safeCleanup, clearSongBuffer } = require('../utils/player');
const { getQueueOrReply } = require('../utils/helpers');
function handlePause(interaction, queues) {
@@ -80,7 +80,7 @@ function handleQuit(interaction, queues) {
const queue = getQueueOrReply(interaction, queues);
if (!queue) return;
- try { queue.player.stop(); } catch (e) {}
+ try { queue.player.stop(); } catch (_e) {}
safeCleanup(queue, 'Quit');
queue.connection.destroy();
queues.delete(interaction.guild.id);
diff --git a/src/handlers/playCommand.js b/src/handlers/playCommand.js
index 5551d28..c61e009 100644
--- a/src/handlers/playCommand.js
+++ b/src/handlers/playCommand.js
@@ -2,6 +2,7 @@ const { EmbedBuilder } = require('discord.js');
const {
createAudioPlayer,
joinVoiceChannel,
+ getVoiceConnection,
AudioPlayerStatus,
VoiceConnectionStatus,
entersState,
@@ -87,7 +88,7 @@ async function onConnectionDisconnected(connection, queues, guildId) {
entersState(connection, VoiceConnectionStatus.Signalling, 5000),
entersState(connection, VoiceConnectionStatus.Connecting, 5000),
]);
- } catch (error) {
+ } catch (_error) {
connection.destroy();
queues.delete(guildId);
}
@@ -114,20 +115,46 @@ function setupConnectionEventListeners(connection, queues, guildId, queue) {
}
async function createVoiceConnection(voiceChannel, interaction) {
- const connection = joinVoiceChannel({
- channelId: voiceChannel.id,
- guildId: interaction.guild.id,
- adapterCreator: voiceChannel.guild.voiceAdapterCreator,
- });
+ const guildId = interaction.guild.id;
+ const existingConnection = getVoiceConnection(guildId);
+ if (existingConnection) {
+ try {
+ existingConnection.destroy();
+ } catch (e) {
+ console.warn('Failed to destroy existing voice connection before reconnect:', e);
+ }
+ }
- try {
- await entersState(connection, VoiceConnectionStatus.Ready, 30000);
- return connection;
- } catch (error) {
- console.error('Failed to join voice channel:', error);
- connection.destroy();
- throw error;
+ let lastError;
+ const maxAttempts = 2;
+
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
+ const connection = joinVoiceChannel({
+ channelId: voiceChannel.id,
+ guildId,
+ adapterCreator: voiceChannel.guild.voiceAdapterCreator,
+ selfDeaf: true,
+ });
+
+ try {
+ await entersState(connection, VoiceConnectionStatus.Ready, 30000);
+ return connection;
+ } catch (error) {
+ lastError = error;
+ console.error(`Failed to join voice channel (attempt ${attempt}/${maxAttempts}):`, error);
+ try {
+ connection.destroy();
+ } catch (e) {
+ console.warn('Failed to destroy connection after join failure:', e);
+ }
+
+ if (attempt < maxAttempts) {
+ await new Promise((resolve) => setTimeout(resolve, 1000));
+ }
+ }
}
+
+ throw lastError;
}
async function initializeQueue(voiceChannel, interaction, queues, songs) {
@@ -136,7 +163,7 @@ async function initializeQueue(voiceChannel, interaction, queues, songs) {
try {
connection = await createVoiceConnection(voiceChannel, interaction);
- } catch (error) {
+ } catch (_error) {
return null;
}
diff --git a/src/utils/helpers.js b/src/utils/helpers.js
index f18408f..0362528 100644
--- a/src/utils/helpers.js
+++ b/src/utils/helpers.js
@@ -1,3 +1,5 @@
+const { PermissionsBitField } = require('discord.js');
+
function getQueueOrReply(interaction, queues, message = 'Not in a voice channel!') {
const queue = queues.get(interaction.guild.id);
@@ -10,12 +12,32 @@ function getQueueOrReply(interaction, queues, message = 'Not in a voice channel!
}
function requireVoiceChannel(interaction) {
- if (!interaction.member?.voice?.channel) {
+ const voiceChannel = interaction.member?.voice?.channel;
+
+ if (!voiceChannel) {
interaction.editReply('You need to be in a voice channel!');
return null;
}
-
- return interaction.member.voice.channel;
+
+ const botMember = interaction.guild?.members?.me;
+ if (!botMember) {
+ interaction.editReply('Could not validate bot permissions in your voice channel. Try again in a moment.');
+ return null;
+ }
+
+ const perms = voiceChannel.permissionsFor(botMember);
+ const missing = [];
+
+ if (!perms?.has(PermissionsBitField.Flags.ViewChannel)) missing.push('View Channel');
+ if (!perms?.has(PermissionsBitField.Flags.Connect)) missing.push('Connect');
+ if (!perms?.has(PermissionsBitField.Flags.Speak)) missing.push('Speak');
+
+ if (missing.length > 0) {
+ interaction.editReply(`I am missing permissions in that voice channel: ${missing.join(', ')}`);
+ return null;
+ }
+
+ return voiceChannel;
}
module.exports = {
diff --git a/src/utils/player.js b/src/utils/player.js
index 7085dae..c17ba56 100644
--- a/src/utils/player.js
+++ b/src/utils/player.js
@@ -100,7 +100,7 @@ async function preloadSong(song) {
}
async function getVideoInfo(query) {
- return new Promise((resolve, reject) => {
+ return new Promise((resolve) => {
const isUrl = query.startsWith('http://') || query.startsWith('https://');
const isPlaylist = isUrl && query.includes('list=');
@@ -153,7 +153,7 @@ async function getVideoInfo(query) {
duration: info.duration || 0,
thumbnail: info.thumbnail || info.thumbnails?.[0]?.url,
};
- } catch (e) {
+ } catch (_e) {
return null;
}
}).filter(v => v !== null);
@@ -221,7 +221,7 @@ function setupFFmpegMonitoring(ffmpeg, song, guildId, queue, seekSeconds, onRetr
processesKilled = true;
try {
ffmpeg.kill('SIGKILL');
- } catch (e) {}
+ } catch (_e) {}
}
};
@@ -497,7 +497,7 @@ function getCurrentProgress(queue) {
return null;
}
- let elapsed = 0;
+ let elapsed;
if (queue.resource && queue.resource.playbackDuration !== undefined) {
elapsed = Math.floor(queue.resource.playbackDuration / 1000) + (queue.seekOffset || 0);