import axios from "axios" import type { AxiosError } from "axios" import crypto from "crypto" import { MessageFlags } from "discord.js" import type { ChatInputCommandInteraction, ButtonInteraction, Client } from "discord.js" import type { APIResponseData, APIResponseDataError, APIResponseDataVersion, ConnectionStatus, GetChallenge, LcdConfig, OpenSession, RequestAuthorization, TokenRequest, TrackAuthorizationProgress } from "@/types/freebox" import type { GuildFbx } from "@/types/schemas" import { t } from "@/utils/i18n" import { logConsole } from "@/utils/console" const app: TokenRequest = { app_id: "fr.angels-dev.tamiseur", app_name: "Bot Tamiseur", app_version: process.env.npm_package_version ?? "1.0.0", device_name: "Bot Discord NodeJS", } export const Core = { async Version(host: string) { try { const res = await axios.get(host + `/api_version`) return res.data } catch (error) { return (error as AxiosError).response?.data } }, async Init(host: string, trackId?: string) { let request if (trackId) request = axios.get>(host + `/api/v8/login/authorize/` + trackId) else request = axios.post>(host + `/api/v8/login/authorize/`, app) try { const res = await request return res.data } catch (error) { return (error as AxiosError).response?.data } } } export const Login = { async Challenge(host: string) { try { const res = await axios.get>(host + `/api/v8/login/`) return res.data } catch (error) { return (error as AxiosError).response?.data } }, async Session(host: string, password: string) { try { const res = await axios.post>(host + `/api/v8/login/session/`, { app_id: app.app_id, password }) return res.data } catch (error) { return (error as AxiosError).response?.data } } } export const Get = { async Connection(host: string, sessionToken: string) { try { const res = await axios.get>(host + `/api/v11/connection/`, { headers: { "X-Fbx-App-Auth": sessionToken } }) return res.data } catch (error) { return (error as AxiosError).response?.data } }, async LcdConfig(host: string, sessionToken: string) { try { const res = await axios.get>(host + `/api/v8/lcd/config/`, { headers: { "X-Fbx-App-Auth": sessionToken } }) return res.data } catch (error) { return (error as AxiosError).response?.data } } } export const Set = { async LcdConfig(host: string, sessionToken: string, config: LcdConfig) { try { const res = await axios.put>(host + `/api/v8/lcd/config/`, config, { headers: { "X-Fbx-App-Auth": sessionToken } }) return res.data } catch (error) { return (error as AxiosError).response?.data } } } export async function handleError(error: APIResponseDataError, interaction: ChatInputCommandInteraction | ButtonInteraction, reply = true) { if (reply) return await interaction.reply({ content: t(interaction.locale, `freebox.error.${error.error_code}`), flags: MessageFlags.Ephemeral }) else return await interaction.followUp({ content: t(interaction.locale, `freebox.error.${error.error_code}`), flags: MessageFlags.Ephemeral }) } // Stockage des timers actifs pour chaque guild const activeTimers = new Map() // Gestion du timer LCD export const Timer = { // Fonction pour programmer le timer LCD Freebox schedule(client: Client, guildId: string, dbData: GuildFbx) { const lcd = dbData.lcd if (!lcd?.morningTime || !lcd.nightTime) return // Nettoyer les anciens timers pour cette guild Timer.clear(guildId) const now = new Date() const [morningHour, morningMinute] = lcd.morningTime.split(':').map(Number) const [nightHour, nightMinute] = lcd.nightTime.split(':').map(Number) // Calculer le prochain allumage (matin) const nextMorning = new Date(now.getFullYear(), now.getMonth(), now.getDate(), morningHour, morningMinute, 0, 0) if (nextMorning <= now) nextMorning.setDate(nextMorning.getDate() + 1) // Calculer la prochaine extinction (nuit) const nextNight = new Date(now.getFullYear(), now.getMonth(), now.getDate(), nightHour, nightMinute, 0, 0) if (nextNight <= now) nextNight.setDate(nextNight.getDate() + 1) // Programmer l'allumage le matin const morningDelay = nextMorning.getTime() - now.getTime() const morningTimer = setTimeout(() => { void Timer.controlLeds(guildId, dbData, true) // Reprogrammer pour le cycle suivant Timer.schedule(client, guildId, dbData) }, morningDelay) // Programmer l'extinction le soir const nightDelay = nextNight.getTime() - now.getTime() const nightTimer = setTimeout(() => { void Timer.controlLeds(guildId, dbData, false) // Note: pas besoin de reprogrammer ici car le timer du matin s'en charge }, nightDelay) // Stocker les références des timers activeTimers.set(guildId, { morning: morningTimer, night: nightTimer }) logConsole('freebox', 'timer_scheduled', { guildId, nextMorning: nextMorning.toLocaleString(), nextNight: nextNight.toLocaleString() }) }, // Fonction utilitaire pour calculer la prochaine occurrence d'une heure donnée getNextOccurrence(now: Date, hour: number, minute: number): Date { const target = new Date(now.getFullYear(), now.getMonth(), now.getDate(), hour, minute, 0, 0) // Si l'heure cible est déjà passée aujourd'hui, programmer pour demain if (target <= now) target.setDate(target.getDate() + 1) return target }, // Fonction pour nettoyer les timers d'une guild clear(guildId: string) { const timers = activeTimers.get(guildId) if (timers) { if (timers.morning) clearTimeout(timers.morning) if (timers.night) clearTimeout(timers.night) activeTimers.delete(guildId) logConsole('freebox', 'timers_cleaned', { guildId }) } }, // Fonction pour nettoyer tous les timers (utile lors de l'arrêt du bot) clearAll() { for (const [guildId] of activeTimers) { Timer.clear(guildId) } logConsole('freebox', 'all_timers_cleaned') }, // Fonction pour contrôler les LEDs async controlLeds(guildId: string, dbDataFbx: GuildFbx, enabled: boolean) { if (!dbDataFbx.host || !dbDataFbx.appToken) { logConsole('freebox', 'missing_configuration', { guildId }); return } try { // Obtenir le challenge const challengeData = await Login.Challenge(dbDataFbx.host) as APIResponseData if (!challengeData.success) { logConsole('freebox', 'challenge_error', { guildId }); return } const challenge = challengeData.result.challenge if (!challenge) { logConsole('freebox', 'challenge_not_found', { guildId }); return } // Créer la session const password = crypto.createHmac("sha1", dbDataFbx.appToken).update(challenge).digest("hex") const sessionData = await Login.Session(dbDataFbx.host, password) as APIResponseData if (!sessionData.success) { logConsole('freebox', 'session_error', { guildId }); return } const sessionToken = sessionData.result.session_token if (!sessionToken) { logConsole('freebox', 'session_token_not_found', { guildId }); return } // Contrôler les LEDs const lcdData = await Set.LcdConfig(dbDataFbx.host, sessionToken, { led_strip_enabled: enabled }) as APIResponseData if (!lcdData.success) { logConsole('freebox', 'leds_control_error', { guildId }); return } logConsole('freebox', 'leds_success', { status: enabled ? 'allumées' : 'éteintes', guildId }) } catch { logConsole('freebox', 'leds_error', { guildId }) } } }