diff options
| author | Your Name <you@example.com> | 2026-03-21 01:41:17 +0100 |
|---|---|---|
| committer | Your Name <you@example.com> | 2026-03-21 01:41:17 +0100 |
| commit | 768b537946da0abd89949648b803d625542c8a7d (patch) | |
| tree | 6fbce39026bf008d3c05fc58cfd0b97cc84ab7e4 /src | |
| parent | 09e546c4a7e1bf69784be5e8fe1200a7d5a8c955 (diff) | |
Diffstat (limited to 'src')
| -rw-r--r-- | src/bot.js | 4 | ||||
| -rw-r--r-- | src/handlers/controlCommands.js | 4 | ||||
| -rw-r--r-- | src/handlers/playCommand.js | 55 | ||||
| -rw-r--r-- | src/utils/helpers.js | 28 | ||||
| -rw-r--r-- | src/utils/player.js | 8 |
5 files changed, 74 insertions, 25 deletions
@@ -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); |
