1import {
2 ChatInputCommandInteraction,
3 SlashCommandBuilder,
4 GuildMember,
5 ChannelType,
6} from 'discord.js'
7import { Command } from '@/types/bot'
8import {
9 joinVoiceChannel,
10 createAudioPlayer,
11 createAudioResource,
12 AudioPlayerStatus,
13 VoiceConnectionStatus,
14 entersState,
15} from '@discordjs/voice'
16
17export default {
18 data: new SlashCommandBuilder()
19 .setName('play-sound')
20 .setDescription('Play a sound file in your voice channel')
21 .addAttachmentOption((option) =>
22 option
23 .setName('sound')
24 .setDescription('The sound file to play (MP3, WAV, OGG, etc.)')
25 .setRequired(true),
26 )
27 .addStringOption((option) =>
28 option
29 .setName('volume')
30 .setDescription('The volume of the sound file (0-100)')
31 .setRequired(false),
32 )
33 .addChannelOption((option) =>
34 option
35 .setName('channel')
36 .setDescription('The channel to play the sound in')
37 .addChannelTypes(ChannelType.GuildVoice)
38 .setRequired(false),
39 ),
40 async execute(interaction: ChatInputCommandInteraction) {
41 // Get the target voice channel (either specified or user's current channel)
42 const targetChannel =
43 interaction.options.getChannel('channel') ||
44 (interaction.member instanceof GuildMember ?
45 interaction.member.voice.channel
46 : null)
47
48 if (!targetChannel) {
49 return interaction.reply({
50 content:
51 'You need to be in a voice channel or specify a voice channel!',
52 ephemeral: true,
53 })
54 }
55
56 const attachment = interaction.options.getAttachment('sound', true)
57 const volumeOption = interaction.options.getString('volume')
58 const volume =
59 volumeOption ? Math.min(Math.max(parseInt(volumeOption) / 100, 0), 1) : 1
60
61 // Validate file type
62 if (!attachment.contentType?.includes('audio')) {
63 return interaction.reply({
64 content: 'Please upload a valid audio file!',
65 ephemeral: true,
66 })
67 }
68
69 try {
70 // Join the voice channel
71 const connection = joinVoiceChannel({
72 channelId: targetChannel.id,
73 guildId: interaction.guildId!,
74 adapterCreator: interaction.guild!.voiceAdapterCreator as any,
75 })
76
77 // Create audio player and resource with volume
78 const player = createAudioPlayer()
79 const resource = createAudioResource(attachment.url, {
80 inlineVolume: true,
81 })
82
83 if (resource.volume) {
84 resource.volume.setVolume(volume)
85 }
86
87 // Handle connection ready
88 connection.on(VoiceConnectionStatus.Ready, () => {
89 player.play(resource)
90 connection.subscribe(player)
91 })
92
93 // Handle when audio finishes playing
94 player.on(AudioPlayerStatus.Idle, () => {
95 connection.destroy()
96 })
97
98 // Handle errors
99 connection.on('error', (error) => {
100 console.error('Voice connection error:', error)
101 connection.destroy()
102 })
103
104 player.on('error', (error) => {
105 console.error('Audio player error:', error)
106 connection.destroy()
107 })
108
109 // Wait for connection to be ready
110 await entersState(connection, VoiceConnectionStatus.Ready, 5000)
111
112 await interaction.reply({
113 content: '🎵 Playing your sound!',
114 ephemeral: true,
115 })
116 } catch (error) {
117 console.error('Error playing sound:', error)
118 await interaction.reply({
119 content: 'Failed to play the sound file!',
120 ephemeral: true,
121 })
122 }
123 },
124} as Command
125