import { SlashCommandBuilder, MessageFlags } from "discord.js" import type { ChatInputCommandInteraction, AutocompleteInteraction, GuildMember } from "discord.js" import { useMainPlayer, useQueue } from "discord-player" import { SpotifyExtractor } from "@discord-player/extractor" import { YoutubeiExtractor } from "discord-player-youtubei" import { startProgressSaving } from "@/utils/player" import type { TrackSearchResult } from "@/types/player" import type { GuildPlayer } from "@/types/schemas" import dbGuild from "@/schemas/guild" import { t } from "@/utils/i18n" export const data = new SlashCommandBuilder() .setName("play") .setDescription("Play a song") .setNameLocalizations({ fr: "jouer" }) .setDescriptionLocalizations({ fr: "Jouer une musique" }) .addStringOption(option => option .setName("search") .setDescription("Music title to search for") .setNameLocalizations({ fr: "recherche" }) .setDescriptionLocalizations({ fr: "Titre de la musique à chercher" }) .setRequired(true) .setAutocomplete(true) ) export async function execute(interaction: ChatInputCommandInteraction) { const member = interaction.member as GuildMember const voiceChannel = member.voice.channel if (!voiceChannel) return interaction.reply({ content: t(interaction.locale, "player.not_in_voice"), flags: MessageFlags.Ephemeral }) const botChannel = interaction.guild?.members.me?.voice.channel if (botChannel && voiceChannel.id !== botChannel.id) return interaction.reply({ content: t(interaction.locale, "player.not_in_same_voice"), flags: MessageFlags.Ephemeral }) await interaction.deferReply() const query = interaction.options.getString("search", true) const player = useMainPlayer() let queue = useQueue(interaction.guild?.id ?? "") if (!queue) { if (interaction.guild) queue = player.nodes.create(interaction.guild, { metadata: { channel: interaction.channel, client: interaction.guild.members.me, requestedBy: interaction.user }, selfDeaf: true, volume: 20, leaveOnEmpty: true, leaveOnEmptyCooldown: 30000, leaveOnEnd: true, leaveOnEndCooldown: 300000 }) else return } try { if (!queue.connection) await queue.connect(voiceChannel) } catch (error) { console.error(error) } const guildProfile = await dbGuild.findOne({ guildId: queue.guild.id }) if (!guildProfile) return interaction.reply({ content: t(interaction.locale, "common.database_not_found"), flags: MessageFlags.Ephemeral }) const botId = interaction.client.user.id const dbData = guildProfile.get("guildPlayer") as GuildPlayer dbData.instances ??= [] const instanceIndex = dbData.instances.findIndex(instance => instance.botId === botId) const instance = { botId, replay: { textChannelId: interaction.channel?.id ?? "", voiceChannelId: voiceChannel.id, trackUrl: "", progress: 0 } } if (instanceIndex === -1) dbData.instances.push(instance) else dbData.instances[instanceIndex] = instance guildProfile.set("guildPlayer", dbData) guildProfile.markModified("guildPlayer") await guildProfile.save().catch(console.error) const result = await player.search(query, { requestedBy: interaction.user }) if (!result.hasTracks()) return interaction.followUp({ content: t(interaction.locale, "player.no_track_found", { query }), flags: MessageFlags.Ephemeral }) const track = result.tracks[0] const entry = queue.tasksQueue.acquire() await entry.getTask() queue.addTrack(track) try { if (!queue.isPlaying()) await queue.node.play() startProgressSaving(queue.guild.id, botId) const track_source = track.source === "spotify" ? t(interaction.locale, "player.sources.spotify") : track.source === "youtube" ? t(interaction.locale, "player.sources.youtube") : t(interaction.locale, "player.sources.unknown") return await interaction.followUp(t(interaction.locale, "player.loading_track", { title: track.title, author: track.author, source: track_source })) } catch (error) { console.error(error) } finally { queue.tasksQueue.release() } } export async function autocompleteRun(interaction: AutocompleteInteraction) { const query = interaction.options.getString("search", true) if (!query) return interaction.respond([]) const player = useMainPlayer() const resultsSpotify = await player.search(query, { searchEngine: `ext:${SpotifyExtractor.identifier}` }) const resultsYouTube = await player.search(query, { searchEngine: `ext:${YoutubeiExtractor.identifier}` }) const tracksSpotify = resultsSpotify.tracks.slice(0, 5).map(t => ({ name: `Spotify: ${`${t.title} - ${t.author} (${t.duration})`.length > 75 ? `${`${t.title} - ${t.author}`.substring(0, 75)}... (${t.duration})` : `${t.title} - ${t.author} (${t.duration})`}`, value: t.url })) const tracksYouTube = resultsYouTube.tracks.slice(0, 5).map(t => ({ name: `YouTube: ${`${t.title} - ${t.author} (${t.duration})`.length > 75 ? `${`${t.title} - ${t.author}`.substring(0, 75)}... (${t.duration})` : `${t.title} - ${t.author} (${t.duration})`}`, value: t.url })) const tracks: TrackSearchResult[] = [] tracksSpotify.forEach((t) => tracks.push({ name: t.name, value: t.value })) tracksYouTube.forEach((t) => tracks.push({ name: t.name, value: t.value })) return interaction.respond(tracks) }