- Converted all .js files to .ts - Added TypeScript configuration (tsconfig.json) - Added ESLint and Prettier configuration - Updated package.json dependencies - Modified Docker and application configurations
123 lines
6.6 KiB
TypeScript
123 lines
6.6 KiB
TypeScript
import {
|
|
SlashCommandBuilder,
|
|
PermissionFlagsBits,
|
|
ChannelType,
|
|
ChatInputCommandInteraction, // Import the specific interaction type
|
|
GuildMember, // Import GuildMember type
|
|
VoiceBasedChannel // Import VoiceBasedChannel type
|
|
} from 'discord.js';
|
|
import logger from '../utils/logger'; // Use default import
|
|
import { BotClient } from '../index'; // Import the BotClient interface
|
|
import { Player } from 'shoukaku'; // Import the Player type explicitly
|
|
|
|
export default { // Use export default for ES Modules
|
|
data: new SlashCommandBuilder()
|
|
.setName('join')
|
|
.setDescription('Joins your current voice channel'),
|
|
async execute(interaction: ChatInputCommandInteraction, client: BotClient) { // Add types
|
|
// Ensure command is run in a guild
|
|
if (!interaction.guildId || !interaction.guild || !interaction.channelId) {
|
|
// Reply might fail if interaction is already replied/deferred, use editReply if needed
|
|
return interaction.reply({ content: 'This command can only be used in a server.', ephemeral: true }).catch(() => {});
|
|
}
|
|
// Ensure interaction.member is a GuildMember
|
|
if (!(interaction.member instanceof GuildMember)) {
|
|
return interaction.reply({ content: 'Could not determine your voice channel.', ephemeral: true }).catch(() => {});
|
|
}
|
|
|
|
// Use ephemeral deferral
|
|
await interaction.deferReply({ ephemeral: true });
|
|
|
|
const member = interaction.member; // Already checked it's GuildMember
|
|
const voiceChannel = member?.voice?.channel;
|
|
|
|
// 1. Check if user is in a voice channel
|
|
if (!voiceChannel) {
|
|
return interaction.editReply('You need to be in a voice channel to use this command!');
|
|
}
|
|
|
|
// Type assertion for voiceChannel after check
|
|
const currentVoiceChannel = voiceChannel as VoiceBasedChannel;
|
|
|
|
// 2. Check bot permissions
|
|
const permissions = currentVoiceChannel.permissionsFor(client.user!); // Use non-null assertion for client.user
|
|
if (!permissions?.has(PermissionFlagsBits.Connect)) { // Optional chaining for permissions
|
|
return interaction.editReply('I need permission to **connect** to your voice channel!');
|
|
}
|
|
if (!permissions?.has(PermissionFlagsBits.Speak)) {
|
|
return interaction.editReply('I need permission to **speak** in your voice channel!');
|
|
}
|
|
// Ensure it's a voice channel (not stage, etc.)
|
|
if (currentVoiceChannel.type !== ChannelType.GuildVoice) {
|
|
return interaction.editReply('I can only join standard voice channels.');
|
|
}
|
|
|
|
// Get the initialized Shoukaku instance from the client object
|
|
const shoukaku = client.shoukaku;
|
|
if (!shoukaku) {
|
|
logger.error('Shoukaku instance not found on client object!');
|
|
return interaction.editReply('The music player is not ready yet. Please try again shortly.');
|
|
}
|
|
|
|
// 3. Get or create the player and connect using Shoukaku
|
|
// Correctly get player from the players map and type it
|
|
let player: Player | undefined = shoukaku.players.get(interaction.guildId);
|
|
|
|
if (!player) {
|
|
try {
|
|
// Create player using the Shoukaku manager
|
|
player = await shoukaku.joinVoiceChannel({
|
|
guildId: interaction.guildId,
|
|
channelId: currentVoiceChannel.id,
|
|
shardId: interaction.guild.shardId, // Get shardId from guild
|
|
});
|
|
|
|
logger.info(`Created player and connected to voice channel ${currentVoiceChannel.name} (${currentVoiceChannel.id}) in guild ${interaction.guild.name} (${interaction.guildId})`);
|
|
await interaction.editReply(`Joined ${currentVoiceChannel.name}! Ready to play music.`);
|
|
|
|
} catch (error: unknown) { // Type error as unknown
|
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
logger.error(`Failed to create/connect player for guild ${interaction.guildId}: ${errorMessage}`, error);
|
|
// Attempt to leave voice channel if connection failed partially
|
|
shoukaku.leaveVoiceChannel(interaction.guildId).catch((e: unknown) => { // Type catch error
|
|
const leaveErrorMsg = e instanceof Error ? e.message : String(e);
|
|
logger.error(`Error leaving VC after failed join: ${leaveErrorMsg}`);
|
|
});
|
|
return interaction.editReply('An error occurred while trying to join the voice channel.');
|
|
}
|
|
} else {
|
|
// If player exists, get the corresponding connection
|
|
const connection = shoukaku.connections.get(interaction.guildId);
|
|
|
|
// Check if connection exists and if it's in a different channel
|
|
if (!connection || connection.channelId !== currentVoiceChannel.id) {
|
|
try {
|
|
// Rejoining should handle moving the bot
|
|
// Note: joinVoiceChannel might implicitly destroy the old player/connection if one exists for the guild.
|
|
// If issues arise, explicitly call leaveVoiceChannel first.
|
|
player = await shoukaku.joinVoiceChannel({
|
|
guildId: interaction.guildId,
|
|
channelId: currentVoiceChannel.id,
|
|
shardId: interaction.guild.shardId,
|
|
});
|
|
|
|
logger.info(`Moved player to voice channel ${currentVoiceChannel.name} (${currentVoiceChannel.id}) in guild ${interaction.guildId}`);
|
|
await interaction.editReply(`Moved to ${currentVoiceChannel.name}!`);
|
|
} catch (error: unknown) { // Type error as unknown
|
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
logger.error(`Failed to move player for guild ${interaction.guildId}: ${errorMessage}`, error);
|
|
return interaction.editReply('An error occurred while trying to move to the voice channel.');
|
|
}
|
|
} else {
|
|
// Already in the correct channel
|
|
await interaction.editReply(`I'm already in ${currentVoiceChannel.name}!`);
|
|
}
|
|
// Example of updating a manually managed text channel context (if needed)
|
|
// if (player.textChannelId !== interaction.channelId) {
|
|
// player.textChannelId = interaction.channelId;
|
|
// logger.debug(`Updated player text channel context to ${interaction.channel?.name} (${interaction.channelId}) in guild ${interaction.guildId}`);
|
|
// }
|
|
}
|
|
},
|
|
};
|