Réécriture complète 4.0
Some checks failed
Build and Push Docker Image / build-and-push (push) Failing after 6m16s
Some checks failed
Build and Push Docker Image / build-and-push (push) Failing after 6m16s
This commit is contained in:
@@ -1,79 +1,192 @@
|
||||
import axios from 'axios'
|
||||
import https from 'https'
|
||||
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"
|
||||
|
||||
export interface App {
|
||||
app_id: string
|
||||
app_name: string
|
||||
app_version: string
|
||||
device_name: string
|
||||
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, httpsAgent: https.Agent) {
|
||||
let request = axios.get(host + '/api_version', { httpsAgent })
|
||||
|
||||
return await request.then(response => {
|
||||
if (response.status !== 200) return { status: 'fail', data: response.data }
|
||||
return { status: 'success', data: response.data }
|
||||
}).catch(error => {
|
||||
console.error(error)
|
||||
return { status: 'error', data: error }
|
||||
})
|
||||
async Version(host: string) {
|
||||
try {
|
||||
const res = await axios.get<APIResponseDataVersion>(host + `/api_version`)
|
||||
return res.data
|
||||
} catch (error) { return (error as AxiosError<APIResponseDataError>).response?.data }
|
||||
},
|
||||
async Init(host: string, version: number, httpsAgent: https.Agent, app: App, trackId: string) {
|
||||
async Init(host: string, trackId?: string) {
|
||||
let request
|
||||
|
||||
if (trackId) request = axios.get(host + `/api/v${version}/login/authorize/` + trackId, { httpsAgent })
|
||||
else request = axios.post(host + `/api/v${version}/login/authorize/`, app, { httpsAgent })
|
||||
|
||||
return await request.then(response => {
|
||||
if (!response.data.success) return { status: 'fail', data: response.data }
|
||||
return { status: 'success', data: response.data.result }
|
||||
}).catch(error => {
|
||||
console.error(error)
|
||||
return { status: 'error', data: error }
|
||||
})
|
||||
if (trackId) request = axios.get<APIResponseData<TrackAuthorizationProgress>>(host + `/api/v8/login/authorize/` + trackId)
|
||||
else request = axios.post<APIResponseData<RequestAuthorization>>(host + `/api/v8/login/authorize/`, app)
|
||||
|
||||
try {
|
||||
const res = await request
|
||||
return res.data
|
||||
} catch (error) { return (error as AxiosError<APIResponseDataError>).response?.data }
|
||||
}
|
||||
}
|
||||
|
||||
export const Login = {
|
||||
async Challenge(host: string, version: number, httpsAgent: https.Agent) {
|
||||
let request = axios.get(host + `/api/v${version}/login/`, { httpsAgent })
|
||||
|
||||
return await request.then(response => {
|
||||
console.log(response.data)
|
||||
if (response.status !== 200) return { status: 'fail', data: response.data }
|
||||
return { status: 'success', data: response.data.result }
|
||||
}).catch(error => {
|
||||
console.error(error)
|
||||
return { status: 'error', data: error }
|
||||
})
|
||||
async Challenge(host: string) {
|
||||
try {
|
||||
const res = await axios.get<APIResponseData<GetChallenge>>(host + `/api/v8/login/`)
|
||||
return res.data
|
||||
} catch (error) { return (error as AxiosError<APIResponseDataError>).response?.data }
|
||||
},
|
||||
async Session(host: string, version: number, httpsAgent: https.Agent, app_id: string, password: string) {
|
||||
let request = axios.post(host + `/api/v${version}/login/session/`, { app_id, password }, { httpsAgent })
|
||||
|
||||
return await request.then(response => {
|
||||
console.log(response.data)
|
||||
if (response.status !== 200) return { status: 'fail', data: response.data }
|
||||
return { status: 'success', data: response.data.result }
|
||||
}).catch(error => {
|
||||
console.error(error)
|
||||
return { status: 'error', data: error }
|
||||
})
|
||||
async Session(host: string, password: string) {
|
||||
try {
|
||||
const res = await axios.post<APIResponseData<OpenSession>>(host + `/api/v8/login/session/`, { app_id: app.app_id, password })
|
||||
return res.data
|
||||
} catch (error) { return (error as AxiosError<APIResponseDataError>).response?.data }
|
||||
}
|
||||
}
|
||||
|
||||
export const Get = {
|
||||
async Connection(host: string, version: number, httpsAgent: https.Agent, sessionToken: string) {
|
||||
let request = axios.get(host + `/api/v${version}/connection/`, { httpsAgent, headers: { 'X-Fbx-App-Auth': sessionToken } })
|
||||
|
||||
return await request.then(response => {
|
||||
console.log(response.data)
|
||||
if (!response.data.success) return { status: 'fail', data: response.data }
|
||||
return { status: 'success', data: response.data.result }
|
||||
}).catch(error => {
|
||||
console.error(error)
|
||||
return { status: 'error', data: error }
|
||||
})
|
||||
async Connection(host: string, sessionToken: string) {
|
||||
try {
|
||||
const res = await axios.get<APIResponseData<ConnectionStatus>>(host + `/api/v11/connection/`, { headers: { "X-Fbx-App-Auth": sessionToken } })
|
||||
return res.data
|
||||
} catch (error) { return (error as AxiosError<APIResponseDataError>).response?.data }
|
||||
},
|
||||
async LcdConfig(host: string, sessionToken: string) {
|
||||
try {
|
||||
const res = await axios.get<APIResponseData<LcdConfig>>(host + `/api/v8/lcd/config/`, { headers: { "X-Fbx-App-Auth": sessionToken } })
|
||||
return res.data
|
||||
} catch (error) { return (error as AxiosError<APIResponseDataError>).response?.data }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const Set = {
|
||||
async LcdConfig(host: string, sessionToken: string, config: LcdConfig) {
|
||||
try {
|
||||
const res = await axios.put<APIResponseData<LcdConfig>>(host + `/api/v8/lcd/config/`, config, { headers: { "X-Fbx-App-Auth": sessionToken } })
|
||||
return res.data
|
||||
} catch (error) { return (error as AxiosError<APIResponseDataError>).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<string, { morning: NodeJS.Timeout | null, night: NodeJS.Timeout | null }>()
|
||||
|
||||
// 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 })
|
||||
|
||||
console.log(`[Freebox LCD] Timer programmé pour ${guildId} - Allumage: ${nextMorning.toLocaleString()}, Extinction: ${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)
|
||||
console.log(`[Freebox LCD] Timers nettoyés pour ${guildId}`)
|
||||
}
|
||||
},
|
||||
|
||||
// Fonction pour nettoyer tous les timers (utile lors de l'arrêt du bot)
|
||||
clearAll() {
|
||||
for (const [guildId] of activeTimers) {
|
||||
Timer.clear(guildId)
|
||||
}
|
||||
console.log(`[Freebox LCD] Tous les timers ont été nettoyés`)
|
||||
},
|
||||
|
||||
// Fonction pour contrôler les LEDs
|
||||
async controlLeds(guildId: string, dbDataFbx: GuildFbx, enabled: boolean) {
|
||||
if (!dbDataFbx.host || !dbDataFbx.appToken) {
|
||||
console.error(`[Freebox LCD] Configuration manquante pour le serveur ${guildId}`)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// Obtenir le challenge
|
||||
const challengeData = await Login.Challenge(dbDataFbx.host) as APIResponseData<GetChallenge>
|
||||
if (!challengeData.success) { console.error(`[Freebox LCD] Erreur lors de la récupération du challenge pour ${guildId}`); return }
|
||||
|
||||
const challenge = challengeData.result.challenge
|
||||
if (!challenge) { console.error(`[Freebox LCD] Challenge introuvable pour ${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<OpenSession>
|
||||
if (!sessionData.success) { console.error(`[Freebox LCD] Erreur lors de la création de la session pour ${guildId}`); return }
|
||||
|
||||
const sessionToken = sessionData.result.session_token
|
||||
if (!sessionToken) { console.error(`[Freebox LCD] Token de session introuvable pour ${guildId}`); return }
|
||||
|
||||
// Contrôler les LEDs
|
||||
const lcdData = await Set.LcdConfig(dbDataFbx.host, sessionToken, { led_strip_enabled: enabled }) as APIResponseData<LcdConfig>
|
||||
if (!lcdData.success) { console.error(`[Freebox LCD] Erreur lors du contrôle des LEDs pour ${guildId}:`, lcdData); return }
|
||||
|
||||
console.log(`[Freebox LCD] LEDs ${enabled ? 'allumées' : 'éteintes'} avec succès pour ${guildId}`)
|
||||
} catch (error) {
|
||||
console.error(`[Freebox LCD] Erreur lors du contrôle des LEDs pour ${guildId}:`, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user