127 lines
5.4 KiB
TypeScript
127 lines
5.4 KiB
TypeScript
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"
|
|
import { logConsoleError } from "@/utils/console"
|
|
|
|
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) { logConsoleError('discord_player', 'play.connect_error', {}, error as 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]
|
|
if (process.env.NODE_ENV === "development") console.log(query, result, track)
|
|
|
|
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) { logConsoleError('discord_player', 'play.execution_error', {}, error as 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}` })
|
|
if (process.env.NODE_ENV === "development") console.log(resultsSpotify, resultsYouTube)
|
|
|
|
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)
|
|
}
|