import { SlashCommandBuilder, ChannelType, MessageFlags, PermissionFlagsBits } from "discord.js" import type { ChatInputCommandInteraction, AutocompleteInteraction, ApplicationCommandOptionChoiceData } from "discord.js" import { twitchClient, listener, onlineSub, offlineSub, generateTwitchEmbed } from "@/utils/twitch" import type { GuildTwitch } from "@/types/schemas" import dbGuild from "@/schemas/guild" import { t } from "@/utils/i18n" import { logConsole, logConsoleError } from "@/utils/console" export const data = new SlashCommandBuilder() .setName("twitch") .setDescription("Manage streamers notifications") .setDescriptionLocalizations({ fr: "Gérer les notifications des streameurs" }) .setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild) .addSubcommand(subcommand => subcommand .setName("status") .setDescription("Display Twitch module status") .setNameLocalizations({ fr: "statut" }) .setDescriptionLocalizations({ fr: "Afficher le statut du module Twitch" }) ) .addSubcommand(subcommand => subcommand .setName("channel") .setDescription("Set the channel to send notifications") .setNameLocalizations({ fr: "canal" }) .setDescriptionLocalizations({ fr: "Définir le canal pour envoyer les notifications" }) .addChannelOption(option => option .setName("channel") .setDescription("The channel to send notifications") .setNameLocalizations({ fr: "canal" }) .setDescriptionLocalizations({ fr: "Le canal pour envoyer les notifications" }) .setRequired(true) ) ) .addSubcommandGroup(subcommandgroup => subcommandgroup .setName("streamer") .setDescription("Manage streamers") .setNameLocalizations({ fr: "streameur" }) .setDescriptionLocalizations({ fr: "Gérer les streameurs" }) .addSubcommand(subcommand => subcommand .setName("list") .setDescription("List all streamers") .setNameLocalizations({ fr: "liste" }) .setDescriptionLocalizations({ fr: "Lister tous les streameurs" }) ) .addSubcommand(subcommand => subcommand .setName("add") .setDescription("Add a streamer") .setNameLocalizations({ fr: "ajouter" }) .setDescriptionLocalizations({ fr: "Ajouter un streameur" }) .addStringOption(option => option .setName("username") .setDescription("The username of the streamer to add") .setNameLocalizations({ fr: "nom_utilisateur" }) .setDescriptionLocalizations({ fr: "Le nom d'utilisateur du streameur à ajouter" }) .setRequired(true) .setAutocomplete(true) ) .addUserOption(option => option .setName("member") .setDescription("The member on the guild to mention") .setNameLocalizations({ fr: "membre" }) .setDescriptionLocalizations({ fr: "Le membre sur le serveur à mentionner" }) .setRequired(false) ) ) .addSubcommand(subcommand => subcommand .setName("remove") .setDescription("Remove a streamer") .setNameLocalizations({ fr: "supprimer" }) .setDescriptionLocalizations({ fr: "Supprimer un streameur" }) .addStringOption(option => option .setName("username") .setDescription("The username of the streamer to remove") .setNameLocalizations({ fr: "nom_utilisateur" }) .setDescriptionLocalizations({ fr: "Le nom d'utilisateur du streameur à supprimer" }) .setRequired(true) .setAutocomplete(true) ) ) ) export async function execute(interaction: ChatInputCommandInteraction) { const guildProfile = await dbGuild.findOne({ guildId: interaction.guild?.id }) if (!guildProfile) return interaction.reply({ content: t(interaction.locale, "common.database_not_found"), flags: MessageFlags.Ephemeral }) const dbData = guildProfile.get("guildTwitch") as GuildTwitch const subcommandGroup = interaction.options.getSubcommandGroup(false) const subcommand = interaction.options.getSubcommand(true) if (subcommand == "status") { // Utiliser la fonction utilitaire pour générer l'embed et les composants const { embed, components } = generateTwitchEmbed(dbData, interaction.client, interaction.guild?.id ?? "", interaction.locale) return interaction.reply({ embeds: [embed], components: components, flags: MessageFlags.Ephemeral }) } if (!dbData.enabled) return interaction.reply({ content: t(interaction.locale, "twitch.module_disabled_activate"), flags: MessageFlags.Ephemeral }) if (subcommand == "channel") { const channel = interaction.options.getChannel("channel", true) if (channel.type !== ChannelType.GuildText && channel.type !== ChannelType.GuildAnnouncement) return interaction.reply({ content: t(interaction.locale, "common.invalid_text_channel"), flags: MessageFlags.Ephemeral }) dbData.channelId = channel.id guildProfile.set("guildTwitch", dbData) guildProfile.markModified("guildTwitch") await guildProfile.save().catch(console.error) return interaction.reply({ content: t(interaction.locale, "twitch.notifications_channel_set", { channel: channel.name ?? channel.id }), flags: MessageFlags.Ephemeral }) } else if (subcommandGroup == "streamer") { if (!dbData.channelId) return interaction.reply({ content: t(interaction.locale, "twitch.configure_channel_first"), flags: MessageFlags.Ephemeral }) if (subcommand == "list") { if (!dbData.streamers.length) return interaction.reply({ content: t(interaction.locale, "twitch.no_streamers_list"), flags: MessageFlags.Ephemeral }) const streamers = [] as string[] await Promise.all(dbData.streamers.map(async streamer => { try { const user = await twitchClient.users.getUserById(streamer.twitchUserId) if (user) streamers.push(`- ${user.displayName} (${streamer.twitchUserId})`) else streamers.push(`- ${t(interaction.locale, "twitch.user_not_found_id", { id: streamer.twitchUserId })}`) } catch (error) { logConsoleError('twitch', 'user_fetch_error', { id: streamer.twitchUserId }, error as Error) } })) const streamerList = streamers.length > 0 ? streamers.join("\n") : t(interaction.locale, "twitch.no_streamers") return interaction.reply({ content: `${t(interaction.locale, "twitch.list.title")}:\n${streamerList}`, flags: MessageFlags.Ephemeral }) } else if (subcommand == "add") { const username = interaction.options.getString("username", true) const member = interaction.options.getUser("member", false) const user = await twitchClient.users.getUserByName(username) if (!user) return interaction.reply({ content: t(interaction.locale, "twitch.streamer_not_found", { username }), flags: MessageFlags.Ephemeral }) if (dbData.streamers.some(s => s.twitchUserId === user.id)) return interaction.reply({ content: t(interaction.locale, "twitch.streamer_already_added", { username }), flags: MessageFlags.Ephemeral }) dbData.streamers.push({ twitchUserId: user.id, discordUserId: member?.id ?? "", messageId: "" }) guildProfile.set("guildTwitch", dbData) guildProfile.markModified("guildTwitch") await guildProfile.save().catch(console.error) const userSubs = await twitchClient.eventSub.getSubscriptionsForUser(user.id) // eslint-disable-next-line @typescript-eslint/no-misused-promises if (!userSubs.data.find(sub => sub.transportMethod === "webhook" && sub.type === "stream.online")) listener.onStreamOnline(user.id, onlineSub) // eslint-disable-next-line @typescript-eslint/no-misused-promises if (!userSubs.data.find(sub => sub.transportMethod === "webhook" && sub.type === "stream.offline")) listener.onStreamOffline(user.id, offlineSub) return interaction.reply({ content: t(interaction.locale, "twitch.streamer_added", { username, id: user.id }), flags: MessageFlags.Ephemeral }) } else if (subcommand == "remove") { const username = interaction.options.getString("username", true) const user = await twitchClient.users.getUserByName(username) if (!user) return interaction.reply({ content: t(interaction.locale, "twitch.streamer_not_found", { username }), flags: MessageFlags.Ephemeral }) const streamerIndex = dbData.streamers.findIndex(s => s.twitchUserId === user.id) if (streamerIndex === -1)return interaction.reply({ content: t(interaction.locale, "twitch.streamer_not_in_list", { username }), flags: MessageFlags.Ephemeral }) dbData.streamers.splice(streamerIndex, 1) guildProfile.set("guildTwitch", dbData) guildProfile.markModified("guildTwitch") await guildProfile.save().catch(console.error) if (!await dbGuild.exists({ "guildTwitch.streamers.twitchUserId": user.id })) { const userSubs = await twitchClient.eventSub.getSubscriptionsForUser(user.id) await Promise.all(userSubs.data.map(async sub => { if (sub.transportMethod === "webhook" && (sub.type === "stream.online" || sub.type === "stream.offline")) await sub.unsubscribe() })) logConsole('twitch', 'listener_removed', { name: user.displayName, id: user.id }) } return interaction.reply({ content: t(interaction.locale, "twitch.streamer_removed", { username }), flags: MessageFlags.Ephemeral }) } } } export async function autocompleteRun(interaction: AutocompleteInteraction) { const query = interaction.options.getString("username", true) if (!query || query.length < 3) return interaction.respond([]) const guildProfile = await dbGuild.findOne({ guildId: interaction.guild?.id }) if (!guildProfile) return interaction.respond([]) const dbData = guildProfile.get("guildTwitch") as GuildTwitch if (!dbData.enabled) return interaction.respond([]) const choices: ApplicationCommandOptionChoiceData[] = [] const searchResult = await twitchClient.search.searchChannels(query) if (searchResult.data.length === 0) return interaction.respond([]) searchResult.data.forEach(streamerResult => { if (dbData.streamers.some(s => s.twitchUserId === streamerResult.id)) return choices.push({ name: streamerResult.displayName, value: streamerResult.name }) }) return interaction.respond(choices) }