All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 3m43s
355 lines
16 KiB
TypeScript
355 lines
16 KiB
TypeScript
import { SlashCommandBuilder, EmbedBuilder, MessageFlags, ButtonBuilder, ButtonStyle, ActionRowBuilder, inlineCode } from "discord.js"
|
|
import type { ChatInputCommandInteraction } from "discord.js"
|
|
import crypto from "crypto"
|
|
import * as Freebox from "@/utils/freebox"
|
|
import type {
|
|
APIResponseData, APIResponseDataError, APIResponseDataVersion,
|
|
ConnectionStatus, GetChallenge, LcdConfig, OpenSession, RequestAuthorization, TrackAuthorizationProgress
|
|
} from "@/types/freebox"
|
|
import type { GuildFbx } from "@/types/schemas"
|
|
import dbGuild from "@/schemas/guild"
|
|
import { t } from "@/utils/i18n"
|
|
import { logConsole } from "@/utils/console"
|
|
|
|
export const data = new SlashCommandBuilder()
|
|
.setName("freebox")
|
|
.setDescription("Access FreeboxOS API")
|
|
.setDescriptionLocalizations({ fr: "Accéder à l'API FreeboxOS" })
|
|
.addSubcommand(subcommand => subcommand
|
|
.setName("status")
|
|
.setDescription("Display Freebox configuration and status")
|
|
.setDescriptionLocalizations({ fr: "Afficher la configuration et l'état de la Freebox" })
|
|
)
|
|
.addSubcommand(subcommand => subcommand
|
|
.setName("setup")
|
|
.setDescription("Configure Freebox settings easily")
|
|
.setDescriptionLocalizations({ fr: "Configurer facilement les paramètres Freebox" })
|
|
.addStringOption(option => option
|
|
.setName("host")
|
|
.setDescription("Freebox host (IP address)")
|
|
.setNameLocalizations({ fr: "hote" })
|
|
.setDescriptionLocalizations({ fr: "Hôte Freebox (adresse IP)" })
|
|
.setRequired(false)
|
|
)
|
|
.addIntegerOption(option => option
|
|
.setName("version")
|
|
.setDescription("Freebox API version")
|
|
.setNameLocalizations({ fr: "version" })
|
|
.setDescriptionLocalizations({ fr: "Version de l'API Freebox" })
|
|
.setRequired(false)
|
|
)
|
|
.addBooleanOption(option => option
|
|
.setName("enabled")
|
|
.setDescription("Enable or disable the Freebox module")
|
|
.setNameLocalizations({ fr: "active" })
|
|
.setDescriptionLocalizations({ fr: "Activer ou désactiver le module Freebox" })
|
|
.setRequired(false)
|
|
)
|
|
)
|
|
.addSubcommand(subcommand => subcommand
|
|
.setName("init")
|
|
.setDescription("Create an app on the Freebox to authenticate")
|
|
.setDescriptionLocalizations({ fr: "Créer une app sur la Freebox pour s'authentifier" })
|
|
.addStringOption(option => option
|
|
.setName("host")
|
|
.setDescription("Freebox host (IP or domain)")
|
|
.setNameLocalizations({ fr: "hote" })
|
|
.setDescriptionLocalizations({ fr: "Hôte Freebox (IP ou domaine)" })
|
|
.setRequired(true)
|
|
)
|
|
)
|
|
.addSubcommandGroup(subcommandGroup => subcommandGroup
|
|
.setName("get")
|
|
.setDescription("Retrieve data")
|
|
.setNameLocalizations({ fr: "recuperer" })
|
|
.setDescriptionLocalizations({ fr: "Récupérer des données" })
|
|
.addSubcommand(subcommand => subcommand
|
|
.setName("version")
|
|
.setDescription("Display API version")
|
|
.setDescriptionLocalizations({ fr: "Afficher la version de l'API" })
|
|
)
|
|
.addSubcommand(subcommand => subcommand
|
|
.setName("connection")
|
|
.setDescription("Retrieve connection information")
|
|
.setNameLocalizations({ fr: "connexion" })
|
|
.setDescriptionLocalizations({ fr: "Récupérer les informations de connexion" })
|
|
)
|
|
.addSubcommand(subcommand => subcommand
|
|
.setName("lcd")
|
|
.setDescription("Retrieve LCD configuration")
|
|
.setDescriptionLocalizations({ fr: "Récupérer la configuration de l'écran LCD" })
|
|
)
|
|
)
|
|
.addSubcommandGroup(subcommandGroup => subcommandGroup
|
|
.setName("lcd")
|
|
.setDescription("Control LCD features")
|
|
.setDescriptionLocalizations({ fr: "Contrôler les fonctionnalités LCD" })
|
|
.addSubcommand(subcommand => subcommand
|
|
.setName("leds")
|
|
.setDescription("Toggle LED strip on/off")
|
|
.setNameLocalizations({ fr: "leds" })
|
|
.setDescriptionLocalizations({ fr: "Allumer/éteindre le bandeau LED" })
|
|
.addBooleanOption(option => option
|
|
.setName("enabled")
|
|
.setDescription("Enable or disable LED strip")
|
|
.setNameLocalizations({ fr: "active" })
|
|
.setDescriptionLocalizations({ fr: "Activer ou désactiver le bandeau LED" })
|
|
.setRequired(true)
|
|
)
|
|
)
|
|
.addSubcommand(subcommand => subcommand
|
|
.setName("timer")
|
|
.setDescription("Setup automatic LED timer")
|
|
.setNameLocalizations({ fr: "minuteur" })
|
|
.setDescriptionLocalizations({ fr: "Configurer le minuteur automatique des LEDs" })
|
|
.addStringOption(option => option
|
|
.setName("action")
|
|
.setDescription("Timer action")
|
|
.setNameLocalizations({ fr: "action" })
|
|
.setDescriptionLocalizations({ fr: "Action du minuteur" })
|
|
.setRequired(true)
|
|
.addChoices(
|
|
{ name: "Enable timer", value: "enable", name_localizations: { fr: "Activer minuteur" } },
|
|
{ name: "Disable timer", value: "disable", name_localizations: { fr: "Désactiver minuteur" } },
|
|
{ name: "Status", value: "status", name_localizations: { fr: "Statut" } }
|
|
)
|
|
)
|
|
.addStringOption(option => option
|
|
.setName("morning_time")
|
|
.setDescription("Morning time (HH:MM format, 24h)")
|
|
.setNameLocalizations({ fr: "heure_matin" })
|
|
.setDescriptionLocalizations({ fr: "Heure du matin (format HH:MM, 24h)" })
|
|
.setRequired(false)
|
|
)
|
|
.addStringOption(option => option
|
|
.setName("night_time")
|
|
.setDescription("Night time (HH:MM format, 24h)")
|
|
.setNameLocalizations({ fr: "heure_nuit" })
|
|
.setDescriptionLocalizations({ fr: "Heure du soir (format HH:MM, 24h)" })
|
|
.setRequired(false)
|
|
)
|
|
)
|
|
)
|
|
|
|
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("guildFbx") as GuildFbx
|
|
let host = dbData.host
|
|
let appToken = dbData.appToken
|
|
|
|
const subcommandGroup = interaction.options.getSubcommandGroup(false)
|
|
const subcommand = interaction.options.getSubcommand(true)
|
|
|
|
if (subcommand === "status") {
|
|
// Construire l'embed de statut
|
|
const embed = new EmbedBuilder()
|
|
.setTitle(t(interaction.locale, "freebox.status.title"))
|
|
.setColor(dbData.enabled ? 0x00ff00 : 0xff0000)
|
|
.addFields({
|
|
name: t(interaction.locale, "freebox.status.config_section"),
|
|
value: [
|
|
t(interaction.locale, "freebox.status.module_field", { status: dbData.enabled ? t(interaction.locale, "freebox.common.enabled") : t(interaction.locale, "freebox.common.disabled") }),
|
|
t(interaction.locale, "freebox.status.host_field", { value: host ? `\`${host}\`` : t(interaction.locale, "freebox.status.host_not_configured") }),
|
|
t(interaction.locale, "freebox.status.token_field", { status: appToken ? t(interaction.locale, "freebox.status.token_configured") : t(interaction.locale, "freebox.status.token_not_configured") })
|
|
].join('\n'),
|
|
inline: false
|
|
})
|
|
|
|
// Informations LCD si disponibles
|
|
if (dbData.lcd) {
|
|
const lcdStatus = dbData.lcd.enabled ? t(interaction.locale, "freebox.status.timer_enabled") : t(interaction.locale, "freebox.status.timer_disabled")
|
|
const botManaged = dbData.lcd.botId ? `<@${dbData.lcd.botId}>` : t(interaction.locale, "freebox.status.timer_no_manager")
|
|
const morningTime = dbData.lcd.morningTime ?? t(interaction.locale, "freebox.status.timer_not_configured")
|
|
const nightTime = dbData.lcd.nightTime ?? t(interaction.locale, "freebox.status.timer_not_configured")
|
|
|
|
embed.addFields({
|
|
name: t(interaction.locale, "freebox.status.timer_section"),
|
|
value: [
|
|
t(interaction.locale, "freebox.timer.status_field", { status: lcdStatus }),
|
|
t(interaction.locale, "freebox.timer.managed_by", { manager: botManaged }),
|
|
t(interaction.locale, "freebox.timer.morning", { time: morningTime }),
|
|
t(interaction.locale, "freebox.timer.night", { time: nightTime })
|
|
].join('\n'),
|
|
inline: false
|
|
})
|
|
}
|
|
|
|
// Boutons d'action
|
|
const buttons = new ActionRowBuilder<ButtonBuilder>().addComponents(
|
|
new ButtonBuilder()
|
|
.setCustomId("freebox_test_connection")
|
|
.setLabel(t(interaction.locale, "freebox.buttons.test_connection"))
|
|
.setEmoji("🔌")
|
|
.setStyle(ButtonStyle.Primary)
|
|
.setDisabled(!appToken),
|
|
new ButtonBuilder()
|
|
.setCustomId("freebox_lcd_status")
|
|
.setLabel(t(interaction.locale, "freebox.buttons.lcd_status"))
|
|
.setEmoji("💡")
|
|
.setStyle(ButtonStyle.Secondary)
|
|
.setDisabled(!appToken),
|
|
new ButtonBuilder()
|
|
.setCustomId("freebox_refresh_status")
|
|
.setLabel(t(interaction.locale, "freebox.buttons.refresh_status"))
|
|
.setEmoji("🔄")
|
|
.setStyle(ButtonStyle.Success)
|
|
)
|
|
|
|
return interaction.reply({ embeds: [embed], components: [buttons], flags: MessageFlags.Ephemeral })
|
|
}
|
|
|
|
if (!dbData.enabled) return interaction.reply({ content: t(interaction.locale, "common.module_disabled"), flags: MessageFlags.Ephemeral })
|
|
|
|
if (subcommand === "init") {
|
|
if (appToken) return interaction.reply({ content: t(interaction.locale, "freebox.auth.app_token_already_set"), flags: MessageFlags.Ephemeral })
|
|
|
|
host = interaction.options.getString("host", true)
|
|
if (host === "mafreebox.freebox.fr") return interaction.reply({ content: t(interaction.locale, "freebox.auth.host_not_allowed"), flags: MessageFlags.Ephemeral })
|
|
|
|
const initData = await Freebox.Core.Init(host) as APIResponseData<RequestAuthorization>
|
|
if (!initData.success) return Freebox.handleError(initData as APIResponseDataError, interaction)
|
|
|
|
appToken = initData.result.app_token
|
|
const trackId = initData.result.track_id
|
|
if (!trackId) return interaction.reply({ content: t(interaction.locale, "freebox.auth.track_id_failed"), flags: MessageFlags.Ephemeral })
|
|
|
|
// Si l'utilisateur n'a pas encore autorisé l'application, on lui demande de le faire
|
|
await interaction.reply({ content: t(interaction.locale, "freebox.auth.init_in_progress", { trackId }), flags: MessageFlags.Ephemeral })
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
const initCheck = setInterval(async () => {
|
|
if (!host || !trackId) { clearInterval(initCheck); return }
|
|
|
|
const trackData = await Freebox.Core.Init(host, trackId) as APIResponseData<TrackAuthorizationProgress>
|
|
if (!trackData.success) return Freebox.handleError(trackData as APIResponseDataError, interaction, false)
|
|
|
|
const status = trackData.result.status
|
|
if (status === "granted") {
|
|
clearInterval(initCheck)
|
|
dbData.appToken = appToken
|
|
|
|
guildProfile.set("guildFbx", dbData)
|
|
guildProfile.markModified("guildFbx")
|
|
await guildProfile.save().catch(console.error)
|
|
|
|
return interaction.followUp({ content: t(interaction.locale, "common.success"), flags: MessageFlags.Ephemeral })
|
|
} else if (status === "denied") {
|
|
clearInterval(initCheck)
|
|
|
|
return interaction.followUp({ content: t(interaction.locale, "freebox.auth.user_denied_access"), flags: MessageFlags.Ephemeral })
|
|
} else if (status === "pending") logConsole('freebox', 'authorization_pending')
|
|
}, 2000)
|
|
}
|
|
else {
|
|
if (!host) return interaction.reply({ content: t(interaction.locale, "freebox.general.host_not_set"), flags: MessageFlags.Ephemeral })
|
|
|
|
if (subcommand === "version") {
|
|
const versionData = await Freebox.Core.Version(host) as APIResponseDataVersion
|
|
|
|
const embed = new EmbedBuilder()
|
|
.setTitle("FreeboxOS API Version")
|
|
.setDescription(`Version: ${versionData.api_version || "Unknown"}`)
|
|
|
|
return interaction.reply({ embeds: [embed] })
|
|
}
|
|
|
|
if (!appToken) return interaction.reply({ content: t(interaction.locale, "freebox.general.app_token_not_set"), flags: MessageFlags.Ephemeral })
|
|
|
|
const challengeData = await Freebox.Login.Challenge(host) as APIResponseData<GetChallenge>
|
|
if (!challengeData.success) return Freebox.handleError(challengeData as APIResponseDataError, interaction)
|
|
|
|
const password = crypto.createHmac("sha1", appToken).update(challengeData.result.challenge).digest("hex")
|
|
const sessionData = await Freebox.Login.Session(host, password) as APIResponseData<OpenSession>
|
|
if (!sessionData.success) return Freebox.handleError(sessionData as APIResponseDataError, interaction)
|
|
|
|
const sessionToken = sessionData.result.session_token
|
|
if (!sessionToken) return interaction.reply({ content: t(interaction.locale, "freebox.auth.session_token_failed"), flags: MessageFlags.Ephemeral })
|
|
|
|
if (subcommandGroup === "get") {
|
|
if (subcommand === "connection") {
|
|
const connectionData = await Freebox.Get.Connection(host, sessionToken) as APIResponseData<ConnectionStatus>
|
|
if (!connectionData.success) return Freebox.handleError(connectionData as APIResponseDataError, interaction)
|
|
|
|
return interaction.reply({ content: t(interaction.locale, "freebox.api.connection_details", { details: inlineCode(JSON.stringify(connectionData.result)) }), flags: MessageFlags.Ephemeral })
|
|
}
|
|
else if (subcommand === "lcd") {
|
|
const lcdData = await Freebox.Get.LcdConfig(host, sessionToken) as APIResponseData<LcdConfig>
|
|
if (!lcdData.success) return Freebox.handleError(lcdData as APIResponseDataError, interaction)
|
|
|
|
return interaction.reply({ content: t(interaction.locale, "freebox.api.lcd_details", { details: inlineCode(JSON.stringify(lcdData.result)) }), flags: MessageFlags.Ephemeral })
|
|
}
|
|
}
|
|
else if (subcommandGroup === "lcd") {
|
|
// Initialiser l'objet LCD s'il n'existe pas
|
|
dbData.lcd ??= { enabled: false }
|
|
|
|
// Vérifier si le bot est autorisé pour ce serveur
|
|
if (dbData.lcd.enabled && dbData.lcd.botId && dbData.lcd.botId !== interaction.client.user.id) {
|
|
return interaction.reply({ content: t(interaction.locale, "freebox.lcd.managed_by_other_bot"), flags: MessageFlags.Ephemeral })
|
|
}
|
|
|
|
if (subcommand === "leds") {
|
|
const enabled = interaction.options.getBoolean("enabled", true)
|
|
const lcdData = await Freebox.Set.LcdConfig(host, sessionToken, { led_strip_enabled: enabled }) as APIResponseData<LcdConfig>
|
|
if (!lcdData.success) return Freebox.handleError(lcdData as APIResponseDataError, interaction)
|
|
|
|
return interaction.reply({ content: t(interaction.locale, "freebox.lcd.leds_success", { status: enabled ? t(interaction.locale, "freebox.common.enabled") : t(interaction.locale, "freebox.common.disabled") }), flags: MessageFlags.Ephemeral })
|
|
}
|
|
else if (subcommand === "timer") {
|
|
const action = interaction.options.getString("action")
|
|
if (!action) return interaction.reply({ content: t(interaction.locale, "freebox.general.invalid_action"), flags: MessageFlags.Ephemeral })
|
|
|
|
if (action === "status") {
|
|
const status = dbData.lcd.enabled ? t(interaction.locale, "freebox.common.enabled") : t(interaction.locale, "freebox.common.disabled")
|
|
const managedBy = dbData.lcd.botId ? `<@${dbData.lcd.botId}>` : t(interaction.locale, "common.none")
|
|
|
|
return interaction.reply({ content: t(interaction.locale, "freebox.timer.status_display", { status, managedBy }), flags: MessageFlags.Ephemeral })
|
|
}
|
|
else if (action === "enable") {
|
|
const morningTime = interaction.options.getString("morning_time")
|
|
const nightTime = interaction.options.getString("night_time")
|
|
|
|
if (!morningTime || !nightTime) return interaction.reply({ content: t(interaction.locale, "freebox.timer.times_required"), flags: MessageFlags.Ephemeral })
|
|
|
|
// Valider le format HH:MM
|
|
const timeRegex = /^([01]?[0-9]|2[0-3]):[0-5][0-9]$/
|
|
if (!timeRegex.test(morningTime) || !timeRegex.test(nightTime)) return interaction.reply({ content: t(interaction.locale, "freebox.general.invalid_time_format"), flags: MessageFlags.Ephemeral })
|
|
|
|
// Activer le timer et enregistrer ce bot comme responsable
|
|
dbData.lcd.enabled = true
|
|
dbData.lcd.botId = interaction.client.user.id
|
|
dbData.lcd.morningTime = morningTime
|
|
dbData.lcd.nightTime = nightTime
|
|
|
|
guildProfile.set("guildFbx", dbData)
|
|
guildProfile.markModified("guildFbx")
|
|
await guildProfile.save().catch(console.error)
|
|
|
|
// Démarrer les timers automatiques
|
|
if (interaction.guildId) Freebox.Timer.schedule(interaction.client, interaction.guildId, dbData)
|
|
|
|
return interaction.reply({ content: t(interaction.locale, "freebox.timer.enabled", { morningTime, nightTime }), flags: MessageFlags.Ephemeral })
|
|
}
|
|
else if (action === "disable") {
|
|
// Arrêter les timers actifs avant de désactiver
|
|
if (interaction.guildId) Freebox.Timer.clear(interaction.guildId)
|
|
|
|
// Désactiver le timer
|
|
dbData.lcd.enabled = false
|
|
dbData.lcd.botId = ""
|
|
dbData.lcd.morningTime = ""
|
|
dbData.lcd.nightTime = ""
|
|
|
|
guildProfile.set("guildFbx", dbData)
|
|
guildProfile.markModified("guildFbx")
|
|
await guildProfile.save().catch(console.error)
|
|
|
|
return interaction.reply({ content: t(interaction.locale, "freebox.timer.disabled"), flags: MessageFlags.Ephemeral })
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|