Compare commits
10 Commits
f1a488d362
...
f1b5592045
| Author | SHA1 | Date | |
|---|---|---|---|
| f1b5592045 | |||
| af4e6e2e69 | |||
|
6d0c0145ee
|
|||
|
e714e94f85
|
|||
|
0cc81d6430
|
|||
|
1dcb8c6826
|
|||
|
2b6870b861
|
|||
|
ceb7a74b11
|
|||
|
fd4e17a754
|
|||
|
4ed73f7c72
|
@@ -1,22 +1,34 @@
|
||||
# Starting from node
|
||||
FROM node:22-alpine
|
||||
FROM node:22-slim
|
||||
|
||||
ENV NODE_ENV=production
|
||||
# Install build dependencies
|
||||
RUN apt-get update && \
|
||||
apt-get install -y ffmpeg python3 make g++
|
||||
|
||||
# Set the working directory
|
||||
WORKDIR /app
|
||||
RUN chown node:node ./
|
||||
USER node
|
||||
|
||||
RUN apk add --no-cache ffmpeg python3 make g++
|
||||
# Copy package files first
|
||||
COPY --chown=node:node package.json package-lock.json* .
|
||||
|
||||
# Copy package files and install only production dependencies
|
||||
COPY package.json package-lock.json* .
|
||||
# Install app dependencies
|
||||
ENV NODE_ENV=production
|
||||
RUN npm ci --only=production --ignore-scripts && \
|
||||
npm install bufferutil zlib-sync
|
||||
npm install bufferutil zlib-sync && \
|
||||
npm cache clean --force
|
||||
|
||||
# Copy the builded files and the charts
|
||||
COPY ./dist/* .
|
||||
# Copy the builded files
|
||||
COPY --chown=node:node ./dist/* .
|
||||
|
||||
# Set the permissions
|
||||
RUN chown -R node:node /app
|
||||
# Return to root user to remove build dependencies
|
||||
USER root
|
||||
RUN apt-get remove -y python3 make g++ && \
|
||||
apt-get autoremove -y && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Go back to node user
|
||||
USER node
|
||||
|
||||
# Start the application
|
||||
|
||||
32
deploy/templates/ingress.yaml
Normal file
32
deploy/templates/ingress.yaml
Normal file
@@ -0,0 +1,32 @@
|
||||
{{- if .Values.ingress.enabled }}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ .Release.Name }}
|
||||
annotations:
|
||||
external-dns.alpha.kubernetes.io/target: omegamaestro.{{ .Values.ingress.domain }}
|
||||
cert-manager.io/cluster-issuer: {{ .Values.ingress.issuer }}
|
||||
nginx.ingress.kubernetes.io/backend-protocol: "HTTP"
|
||||
{{- if .Values.ingress.geoip }}
|
||||
nginx.ingress.kubernetes.io/server-snippet: |
|
||||
if ($lan = yes) { set $allowed_country yes; }
|
||||
if ($allowed_country = no) { return 451; }
|
||||
{{- end }}
|
||||
spec:
|
||||
ingressClassName: {{ .Values.ingress.class }}
|
||||
tls:
|
||||
- hosts:
|
||||
- {{ .Values.ingress.subdomain }}.{{ .Values.ingress.domain }}
|
||||
secretName: {{ .Release.Name }}-tls
|
||||
rules:
|
||||
- host: "{{ .Values.ingress.subdomain }}.{{ .Values.ingress.domain }}"
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: "{{ .Release.Name }}-{{ .Values.service.name }}"
|
||||
port:
|
||||
name: {{ .Values.service.name }}
|
||||
{{- end }}
|
||||
15
deploy/templates/service.yaml
Normal file
15
deploy/templates/service.yaml
Normal file
@@ -0,0 +1,15 @@
|
||||
{{- if .Values.service.enabled }}
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: "{{ .Release.Name }}-{{ .Values.service.name }}"
|
||||
spec:
|
||||
selector:
|
||||
pod: {{ .Release.Name }}
|
||||
ports:
|
||||
- name: {{ .Values.service.name }}
|
||||
port: {{ .Values.deployment.env.TWURPLE_PORT | default .Values.service.port }}
|
||||
targetPort: {{ .Values.deployment.env.TWURPLE_PORT | default .Values.service.port }}
|
||||
protocol: TCP
|
||||
type: {{ .Values.service.type }}
|
||||
{{- end }}
|
||||
@@ -15,4 +15,17 @@ deployment:
|
||||
# Memory: "500Mi"
|
||||
requests:
|
||||
Cpu: "0.1"
|
||||
Memory: "50Mi"
|
||||
Memory: "50Mi"
|
||||
|
||||
service:
|
||||
enabled: true
|
||||
type: ClusterIP
|
||||
name: twurple
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
class: nginx
|
||||
subdomain: dcb-chantier.prd
|
||||
domain: angels-dev.fr
|
||||
issuer: letsencrypt-prod
|
||||
geoip: false
|
||||
@@ -1,103 +0,0 @@
|
||||
# Système de Contrôle des LEDs Freebox
|
||||
|
||||
Ce module permet au bot de contrôler automatiquement les LEDs de l'écran LCD de la Freebox avec des timers programmables.
|
||||
|
||||
## Fonctionnalités
|
||||
|
||||
### Contrôle Manuel des LEDs
|
||||
- Allumer/éteindre les LEDs instantanément via commande Discord
|
||||
- Récupérer la configuration actuelle de l'écran LCD
|
||||
|
||||
### Timer Automatique
|
||||
- Programmation d'extinction automatique la nuit
|
||||
- Programmation d'allumage automatique le matin
|
||||
- Gestion par bot unique par serveur (évite les conflits)
|
||||
- Persistance des paramètres en base de données
|
||||
|
||||
## Configuration Prérequise
|
||||
|
||||
1. **Module Freebox activé** : `/database edit guildFbx.enabled true`
|
||||
2. **Hôte configuré** : `/database edit guildFbx.host <ip_freebox>`
|
||||
3. **Version API configurée** : `/database edit guildFbx.version <version>`
|
||||
4. **Authentification** : `/freebox init` (suivre le processus d'autorisation)
|
||||
|
||||
## Commandes Disponibles
|
||||
|
||||
### Récupération de configuration
|
||||
```
|
||||
/freebox get lcd
|
||||
```
|
||||
Récupère et affiche la configuration actuelle de l'écran LCD.
|
||||
|
||||
### Contrôle manuel des LEDs
|
||||
```
|
||||
/freebox lcd leds enabled:true # Allumer les LEDs
|
||||
/freebox lcd leds enabled:false # Éteindre les LEDs
|
||||
```
|
||||
|
||||
### Gestion du timer
|
||||
```
|
||||
# Activer le timer avec horaires
|
||||
/freebox lcd timer action:enable morning_time:08:00 night_time:22:30
|
||||
|
||||
# Vérifier le statut du timer
|
||||
/freebox lcd timer action:status
|
||||
|
||||
# Désactiver le timer
|
||||
/freebox lcd timer action:disable
|
||||
```
|
||||
|
||||
## Fonctionnement du Timer
|
||||
|
||||
### Programmation
|
||||
- Le timer est programmé automatiquement au démarrage du bot
|
||||
- Seul le bot configuré comme "gestionnaire" peut contrôler les LEDs d'un serveur
|
||||
- Les horaires sont vérifiés et formatés (HH:MM, 24h)
|
||||
|
||||
### Exécution
|
||||
- **Matin** : Les LEDs s'allument à l'heure programmée
|
||||
- **Soir** : Les LEDs s'éteignent à l'heure programmée
|
||||
- **Reprogrammation** : Le cycle se répète automatiquement chaque jour
|
||||
|
||||
### Logs
|
||||
Les opérations sont loggées dans la console :
|
||||
- Programmation des timers
|
||||
- Exécution des commandes d'allumage/extinction
|
||||
- Erreurs de connexion ou d'authentification
|
||||
|
||||
## Gestion Multi-Bot
|
||||
|
||||
### Système de Verrouillage
|
||||
- Un seul bot peut gérer les LEDs par serveur Discord
|
||||
- L'ID du bot gestionnaire est stocké en base de données
|
||||
- Les autres bots reçoivent un message d'erreur s'ils tentent d'utiliser les commandes
|
||||
|
||||
### Changement de Bot Gestionnaire
|
||||
Pour changer de bot gestionnaire :
|
||||
1. Désactiver le timer sur le bot actuel : `/freebox lcd timer action:disable`
|
||||
2. Activer le timer sur le nouveau bot : `/freebox lcd timer action:enable`
|
||||
|
||||
## Dépannage
|
||||
|
||||
### Erreurs Communes
|
||||
- **"Module Freebox désactivé"** : Activer avec `/database edit guildFbx.enabled true`
|
||||
- **"Hôte non configuré"** : Définir avec `/database edit guildFbx.host <ip>`
|
||||
- **"Token d'app manquant"** : Refaire l'initialisation avec `/freebox init`
|
||||
- **"Géré par un autre bot"** : Désactiver sur l'autre bot d'abord
|
||||
|
||||
### Vérification de Configuration
|
||||
1. Vérifier que la Freebox est accessible sur le réseau
|
||||
2. S'assurer que l'application est autorisée dans l'interface Freebox
|
||||
3. Vérifier les logs de la console pour les erreurs détaillées
|
||||
|
||||
## API Freebox Utilisée
|
||||
|
||||
- `GET /api/v8/lcd/config/` : Récupération de la configuration LCD
|
||||
- `PUT /api/v8/lcd/config/` : Modification de la configuration LCD
|
||||
- Propriété `led_strip_enabled` : Contrôle de l'état des LEDs
|
||||
|
||||
## Sécurité
|
||||
|
||||
- Les tokens d'authentification sont gérés automatiquement
|
||||
- Les sessions sont créées à la demande
|
||||
- Les erreurs d'authentification sont loggées mais les tokens ne sont pas exposés
|
||||
@@ -6,6 +6,7 @@ import type { APIResponseData, APIResponseDataError, GetChallenge, LcdConfig, Op
|
||||
import type { GuildFbx } from "@/types/schemas"
|
||||
import dbGuild from "@/schemas/guild"
|
||||
import { t } from "@/utils/i18n"
|
||||
import { logConsoleError } from "@/utils/console"
|
||||
|
||||
export const id = "freebox_lcd_status"
|
||||
export async function execute(interaction: ButtonInteraction) {
|
||||
@@ -82,7 +83,7 @@ export async function execute(interaction: ButtonInteraction) {
|
||||
|
||||
return await interaction.followUp({ embeds: [embed], flags: MessageFlags.Ephemeral })
|
||||
} catch (error) {
|
||||
console.error("Erreur lors de la récupération de l'état LCD:", error)
|
||||
logConsoleError('freebox', 'lcd_status_error', undefined, error as Error)
|
||||
return interaction.followUp({ content: t(interaction.locale, "freebox.lcd.unexpected_error"), flags: MessageFlags.Ephemeral })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { APIResponseData, APIResponseDataError, APIResponseDataVersion, Con
|
||||
import type { GuildFbx } from "@/types/schemas"
|
||||
import dbGuild from "@/schemas/guild"
|
||||
import { t } from "@/utils/i18n"
|
||||
import { logConsoleError } from "@/utils/console"
|
||||
|
||||
export const id = "freebox_test_connection"
|
||||
export async function execute(interaction: ButtonInteraction) {
|
||||
@@ -65,7 +66,7 @@ export async function execute(interaction: ButtonInteraction) {
|
||||
|
||||
return await interaction.followUp({ embeds: [embed], flags: MessageFlags.Ephemeral })
|
||||
} catch (error) {
|
||||
console.error("Erreur lors du test de connexion Freebox:", error)
|
||||
logConsoleError('freebox', 'test_connection_error', undefined, error as Error)
|
||||
return interaction.followUp({ content: t(interaction.locale, "freebox.test.connection_error"), flags: MessageFlags.Ephemeral })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { GuildTwitch } from "@/types/schemas"
|
||||
import dbGuild from "@/schemas/guild"
|
||||
import { twitchClient } from "@/utils/twitch"
|
||||
import { t } from "@/utils/i18n"
|
||||
import { logConsole } from "@/utils/console"
|
||||
import { logConsoleError } from "@/utils/console"
|
||||
|
||||
export const id = "twitch_streamer_list"
|
||||
export async function execute(interaction: ButtonInteraction) {
|
||||
@@ -34,8 +34,7 @@ export async function execute(interaction: ButtonInteraction) {
|
||||
streamers.push(`**${index + 1}.** ${t(interaction.locale, "twitch.list.user_not_found")}\n└ ID: \`${streamer.twitchUserId}\``)
|
||||
}
|
||||
} catch (error) {
|
||||
logConsole('twitch', 'user_fetch_error_buttons', { id: streamer.twitchUserId })
|
||||
console.error(error)
|
||||
logConsoleError('twitch', 'user_fetch_error', { id: streamer.twitchUserId }, error as Error)
|
||||
streamers.push(`**${index + 1}.** ${t(interaction.locale, "twitch.list.fetch_error")}\n└ ID: \`${streamer.twitchUserId}\``)
|
||||
}
|
||||
}))
|
||||
|
||||
@@ -4,7 +4,7 @@ import { twitchClient } from "@/utils/twitch"
|
||||
import type { GuildTwitch } from "@/types/schemas"
|
||||
import dbGuild from "@/schemas/guild"
|
||||
import { t } from "@/utils/i18n"
|
||||
import { logConsole } from "@/utils/console"
|
||||
import { logConsoleError } from "@/utils/console"
|
||||
|
||||
export const id = "twitch_streamer_remove"
|
||||
export async function execute(interaction: ButtonInteraction) {
|
||||
@@ -25,8 +25,7 @@ export async function execute(interaction: ButtonInteraction) {
|
||||
description: user ? `@${user.name}` : t(interaction.locale, "twitch.user_not_found")
|
||||
}
|
||||
} catch (error) {
|
||||
logConsole('twitch', 'user_fetch_error_buttons', { id: streamer.twitchUserId })
|
||||
console.error(error)
|
||||
logConsoleError('twitch', 'user_fetch_error', { id: streamer.twitchUserId }, error as Error)
|
||||
return {
|
||||
label: `ID: ${streamer.twitchUserId}`,
|
||||
value: streamer.twitchUserId,
|
||||
|
||||
@@ -9,6 +9,7 @@ import type {
|
||||
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")
|
||||
@@ -238,7 +239,7 @@ export async function execute(interaction: ChatInputCommandInteraction) {
|
||||
clearInterval(initCheck)
|
||||
|
||||
return interaction.followUp({ content: t(interaction.locale, "freebox.auth.user_denied_access"), flags: MessageFlags.Ephemeral })
|
||||
} else if (status === "pending") { console.log("Freebox authorization pending...") }
|
||||
} else if (status === "pending") logConsole('freebox', 'authorization_pending')
|
||||
}, 2000)
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -2,6 +2,7 @@ import * as amp from "./amp"
|
||||
import * as boost from "./boost"
|
||||
import * as database from "./database"
|
||||
import * as freebox from "./freebox"
|
||||
import * as locale from "./locale"
|
||||
import * as ping from "./ping"
|
||||
import * as twitch from "./twitch"
|
||||
|
||||
@@ -12,6 +13,7 @@ export default [
|
||||
boost,
|
||||
database,
|
||||
freebox,
|
||||
locale,
|
||||
ping,
|
||||
twitch
|
||||
] as Command[]
|
||||
|
||||
56
src/commands/global/locale.ts
Normal file
56
src/commands/global/locale.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { SlashCommandBuilder, MessageFlags } from "discord.js"
|
||||
import type { ChatInputCommandInteraction } from "discord.js"
|
||||
import dbGuild from "@/schemas/guild"
|
||||
import { t } from "@/utils/i18n"
|
||||
|
||||
export const data = new SlashCommandBuilder()
|
||||
.setName("locale")
|
||||
.setDescription("Manage server language")
|
||||
.setDescriptionLocalizations({ fr: "Gérer la langue du serveur" })
|
||||
.addStringOption(option => option
|
||||
.setName("language")
|
||||
.setDescription("Select the server language")
|
||||
.setNameLocalizations({ fr: "langue" })
|
||||
.setDescriptionLocalizations({ fr: "Sélectionner la langue du serveur" })
|
||||
.setRequired(true)
|
||||
.addChoices(
|
||||
{ name: "Français", value: "fr" },
|
||||
{ name: "English", value: "en-US" }
|
||||
)
|
||||
)
|
||||
|
||||
export async function execute(interaction: ChatInputCommandInteraction) {
|
||||
const guild = interaction.guild
|
||||
if (!guild) return interaction.reply({ content: t(interaction.locale, "common.command_server_only"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
const language = interaction.options.getString("language", true)
|
||||
|
||||
// Récupération du profil du serveur
|
||||
const guildProfile = await dbGuild.findOne({ guildId: guild.id })
|
||||
if (!guildProfile) return interaction.reply({ content: t(interaction.locale, "common.database_not_found"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
// Sauvegarde de l'ancienne langue pour le message de confirmation
|
||||
const oldLocale = guildProfile.guildLocale
|
||||
|
||||
// Mise à jour de la langue
|
||||
guildProfile.guildLocale = language
|
||||
guildProfile.markModified("guildLocale")
|
||||
await guildProfile.save().catch(console.error)
|
||||
|
||||
// Utilisation de la nouvelle langue pour la réponse
|
||||
const languageNames = {
|
||||
'fr': 'Français',
|
||||
'en-US': 'English'
|
||||
}
|
||||
|
||||
const oldLanguageName = languageNames[oldLocale as keyof typeof languageNames] || oldLocale
|
||||
const newLanguageName = languageNames[language as keyof typeof languageNames] || language
|
||||
|
||||
return interaction.reply({
|
||||
content: t(language, "locale.updated", {
|
||||
oldLanguage: oldLanguageName,
|
||||
newLanguage: newLanguageName
|
||||
}),
|
||||
flags: MessageFlags.Ephemeral
|
||||
})
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import { SlashCommandBuilder, ChannelType, MessageFlags, PermissionFlagsBits } from "discord.js"
|
||||
import type { ChatInputCommandInteraction, AutocompleteInteraction, ApplicationCommandOptionChoiceData } from "discord.js"
|
||||
import chalk from "chalk"
|
||||
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")
|
||||
@@ -120,8 +120,7 @@ export async function execute(interaction: ChatInputCommandInteraction) {
|
||||
if (user) streamers.push(`- ${user.displayName} (${streamer.twitchUserId})`)
|
||||
else streamers.push(`- ${t(interaction.locale, "twitch.user_not_found_id", { id: streamer.twitchUserId })}`)
|
||||
} catch (error) {
|
||||
console.log(chalk.magenta(`[Twitch] Error fetching user for ID ${streamer.twitchUserId}`))
|
||||
console.error(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")
|
||||
@@ -167,7 +166,7 @@ export async function execute(interaction: ChatInputCommandInteraction) {
|
||||
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() }))
|
||||
console.log(chalk.magenta(`[Twitch] Listener removed for ${user.displayName} (ID ${user.id})`))
|
||||
logConsole('twitch', 'listener_removed', { name: user.displayName, id: user.id })
|
||||
}
|
||||
|
||||
return interaction.reply({ content: t(interaction.locale, "twitch.streamer_removed", { username }), flags: MessageFlags.Ephemeral })
|
||||
|
||||
@@ -8,6 +8,7 @@ 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")
|
||||
@@ -55,7 +56,7 @@ export async function execute(interaction: ChatInputCommandInteraction) {
|
||||
}
|
||||
|
||||
try { if (!queue.connection) await queue.connect(voiceChannel) }
|
||||
catch (error) { console.error(error) }
|
||||
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 })
|
||||
@@ -82,6 +83,7 @@ export async function execute(interaction: ChatInputCommandInteraction) {
|
||||
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()
|
||||
@@ -93,7 +95,7 @@ export async function execute(interaction: ChatInputCommandInteraction) {
|
||||
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) { console.error(error) }
|
||||
catch (error) { logConsoleError('discord_player', 'play.execution_error', {}, error as Error) }
|
||||
finally { queue.tasksQueue.release() }
|
||||
}
|
||||
|
||||
@@ -105,6 +107,7 @@ export async function autocompleteRun(interaction: AutocompleteInteraction) {
|
||||
|
||||
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})`}`,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Events, EmbedBuilder, ChannelType } from "discord.js"
|
||||
import type { GuildMember } from "discord.js"
|
||||
import { t } from "@/utils/i18n"
|
||||
import { t, getGuildLocale } from "@/utils/i18n"
|
||||
import { logConsole } from "@/utils/console"
|
||||
|
||||
export const name = Events.GuildMemberAdd
|
||||
@@ -30,10 +30,11 @@ export async function execute(member: GuildMember) {
|
||||
return
|
||||
}
|
||||
|
||||
const guildLocale = await getGuildLocale(guild.id)
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor(guild.members.me.displayHexColor)
|
||||
.setTitle(t(guild.preferredLocale, "welcome.title", { username: member.user.username }))
|
||||
.setDescription(t(guild.preferredLocale, "welcome.description", { memberCount: guild.memberCount.toString() }))
|
||||
.setTitle(t(guildLocale, "welcome.title", { username: member.user.username }))
|
||||
.setDescription(t(guildLocale, "welcome.description", { memberCount: guild.memberCount.toString() }))
|
||||
.setThumbnail(member.user.avatarURL())
|
||||
.setTimestamp(new Date())
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Events } from "discord.js"
|
||||
import type { GuildMember } from "discord.js"
|
||||
import { t } from "@/utils/i18n"
|
||||
import { t, getGuildLocale } from "@/utils/i18n"
|
||||
|
||||
export const name = Events.GuildMemberRemove
|
||||
export function execute(member: GuildMember) {
|
||||
@@ -15,8 +15,9 @@ export function execute(member: GuildMember) {
|
||||
const channel = guild.channels.cache.get("1091140609139560508")
|
||||
if (!channel) return
|
||||
|
||||
await channel.setName(t(guild.preferredLocale, "salonpostam.update.loading"))
|
||||
await channel.setName(t(guild.preferredLocale, "salonpostam.update.members_updated", { count: i.toString() }))
|
||||
const guildLocale = await getGuildLocale(guild.id)
|
||||
await channel.setName(t(guildLocale, "salonpostam.update.loading"))
|
||||
await channel.setName(t(guildLocale, "salonpostam.update.members_updated", { count: i.toString() }))
|
||||
}).catch(console.error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Events, EmbedBuilder, ChannelType } from "discord.js"
|
||||
import type { GuildMember } from "discord.js"
|
||||
import { t } from "@/utils/i18n"
|
||||
import { t, getGuildLocale } from "@/utils/i18n"
|
||||
import { logConsole } from "@/utils/console"
|
||||
|
||||
export const name = Events.GuildMemberUpdate
|
||||
@@ -24,10 +24,11 @@ export async function execute(oldMember: GuildMember, newMember: GuildMember) {
|
||||
if (!hadRole && hasRole) {
|
||||
if (!guild.members.me) { logConsole('discordjs', 'boost.not_in_guild'); return }
|
||||
|
||||
const guildLocale = await getGuildLocale(guild.id)
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor(guild.members.me.displayHexColor)
|
||||
.setTitle(t(guild.preferredLocale, "boost.new_boost_title", { username: newMember.user.username }))
|
||||
.setDescription(t(guild.preferredLocale, "boost.new_boost_description", { count: guild.premiumSubscriptionCount?.toString() ?? "0" }))
|
||||
.setTitle(t(guildLocale, "boost.new_boost_title", { username: newMember.user.username }))
|
||||
.setDescription(t(guildLocale, "boost.new_boost_description", { count: guild.premiumSubscriptionCount?.toString() ?? "0" }))
|
||||
.setThumbnail(newMember.user.avatarURL())
|
||||
.setTimestamp(new Date())
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { connect } from "mongoose"
|
||||
import type { Document } from "mongoose"
|
||||
import { playerDisco, playerReplay } from "@/utils/player"
|
||||
import { twitchClient, listener, onlineSub, offlineSub, startStreamWatching } from "@/utils/twitch"
|
||||
import { logConsole } from "@/utils/console"
|
||||
import { logConsole, logConsoleError } from "@/utils/console"
|
||||
import type { GuildPlayer, Disco, GuildTwitch, GuildFbx } from "@/types/schemas"
|
||||
import * as Freebox from "@/utils/freebox"
|
||||
import dbGuildInit from "@/utils/dbGuildInit"
|
||||
@@ -19,8 +19,10 @@ export async function execute(client: Client) {
|
||||
logConsole('discordjs', 'ready', { tag: client.user?.tag ?? "unknown" })
|
||||
client.user?.setActivity("some bangers...", { type: ActivityType.Listening })
|
||||
|
||||
await useMainPlayer().extractors.register(SpotifyExtractor, {}).then(() => { logConsole('discord_player', 'extractor_loaded', { extractor: 'Spotify' }) }).catch(console.error)
|
||||
await useMainPlayer().extractors.register(YoutubeiExtractor, {}).then(() => { logConsole('discord_player', 'extractor_loaded', { extractor: 'Youtube' }) }).catch(console.error)
|
||||
const player = useMainPlayer()
|
||||
await player.extractors.register(SpotifyExtractor, {}).then(() => { logConsole('discord_player', 'extractor_loaded', { extractor: 'Spotify' }) }).catch(console.error)
|
||||
await player.extractors.register(YoutubeiExtractor, {}).then(() => { logConsole('discord_player', 'extractor_loaded', { extractor: 'Youtube' }) }).catch(console.error)
|
||||
if (process.env.NODE_ENV === "development") console.log(player.scanDeps())
|
||||
|
||||
const mongo_url = `mongodb://${process.env.MONGOOSE_USER}:${process.env.MONGOOSE_PASSWORD}@${process.env.MONGOOSE_HOST}/${process.env.MONGOOSE_DATABASE}`
|
||||
await connect(mongo_url).catch(console.error)
|
||||
@@ -95,8 +97,7 @@ export async function execute(client: Client) {
|
||||
startStreamWatching(guild.id, streamer.twitchUserId, user.name, streamer.messageId)
|
||||
logConsole('twitch', 'ready.monitoring_restored', { guild: guild.name, userName: user.name })
|
||||
} catch (error) {
|
||||
logConsole('twitch', 'ready.message_not_found', { guild: guild.name, userName: user.name })
|
||||
console.error(error)
|
||||
logConsoleError('twitch', 'ready.message_not_found', { guild: guild.name, userName: user.name }, error as Error)
|
||||
await cleanupMessageId(guildProfile, streamer.twitchUserId)
|
||||
}
|
||||
}
|
||||
@@ -131,9 +132,8 @@ async function cleanupMessageId(guildProfile: Document, twitchUserId: string) {
|
||||
|
||||
guildProfile.set("guildTwitch", dbData)
|
||||
guildProfile.markModified("guildTwitch")
|
||||
await guildProfile.save()
|
||||
await guildProfile.save().catch(console.error)
|
||||
} catch (error) {
|
||||
logConsole('twitch', 'ready.cleanup_error', { userId: twitchUserId })
|
||||
console.error(error)
|
||||
logConsoleError('twitch', 'ready.cleanup_error', { userId: twitchUserId }, error as Error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import type { GuildQueue, Track } from "discord-player"
|
||||
import type { PlayerMetadata } from "@/types/player"
|
||||
import { t } from "@/utils/i18n"
|
||||
import { t, getGuildLocale } from "@/utils/i18n"
|
||||
|
||||
export const name = "audioTrackAdd"
|
||||
export async function execute(queue: GuildQueue<PlayerMetadata>, track: Track) {
|
||||
// Emitted when the player adds a single song to its queue
|
||||
if (!queue.metadata.channel) return
|
||||
|
||||
if ("send" in queue.metadata.channel) return queue.metadata.channel.send({ content: t(queue.guild.preferredLocale, "player.track_added", { title: track.title }) })
|
||||
if ("send" in queue.metadata.channel) {
|
||||
const guildLocale = await getGuildLocale(queue.guild.id)
|
||||
return queue.metadata.channel.send({ content: t(guildLocale, "player.track_added", { title: track.title }) })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import type { GuildQueue, Track } from "discord-player"
|
||||
import type { PlayerMetadata } from "@/types/player"
|
||||
import { t } from "@/utils/i18n"
|
||||
import { t, getGuildLocale } from "@/utils/i18n"
|
||||
|
||||
export const name = "audioTracksAdd"
|
||||
export async function execute(queue: GuildQueue<PlayerMetadata>, track: Track[]) {
|
||||
// Emitted when the player adds multiple songs to its queue
|
||||
if (!queue.metadata.channel) return
|
||||
if ("send" in queue.metadata.channel) return queue.metadata.channel.send({ content: t(queue.guild.preferredLocale, "player.track_added_playlist", { count: track.length.toString() }) })
|
||||
|
||||
if ("send" in queue.metadata.channel) {
|
||||
const guildLocale = await getGuildLocale(queue.guild.id)
|
||||
return queue.metadata.channel.send({ content: t(guildLocale, "player.track_added_playlist", { count: track.length.toString() }) })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { GuildQueue } from "discord-player"
|
||||
import type { PlayerMetadata } from "@/types/player"
|
||||
import { stopProgressSaving } from "@/utils/player"
|
||||
import { t } from "@/utils/i18n"
|
||||
import { t, getGuildLocale } from "@/utils/i18n"
|
||||
|
||||
export const name = "disconnect"
|
||||
export async function execute(queue: GuildQueue<PlayerMetadata>) {
|
||||
@@ -9,5 +9,9 @@ export async function execute(queue: GuildQueue<PlayerMetadata>) {
|
||||
await stopProgressSaving(queue.guild.id, queue.player.client.user?.id ?? "")
|
||||
|
||||
if (!queue.metadata.channel) return
|
||||
if ("send" in queue.metadata.channel) return queue.metadata.channel.send({ content: t(queue.guild.preferredLocale, "player.disconnect") })
|
||||
|
||||
if ("send" in queue.metadata.channel) {
|
||||
const guildLocale = await getGuildLocale(queue.guild.id)
|
||||
return queue.metadata.channel.send({ content: t(guildLocale, "player.disconnect") })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { GuildQueue } from "discord-player"
|
||||
import type { PlayerMetadata } from "@/types/player"
|
||||
import { stopProgressSaving } from "@/utils/player"
|
||||
import { t } from "@/utils/i18n"
|
||||
import { t, getGuildLocale } from "@/utils/i18n"
|
||||
|
||||
export const name = "emptyChannel"
|
||||
export async function execute(queue: GuildQueue<PlayerMetadata>) {
|
||||
@@ -10,5 +10,9 @@ export async function execute(queue: GuildQueue<PlayerMetadata>) {
|
||||
await stopProgressSaving(queue.guild.id, queue.player.client.user?.id ?? "")
|
||||
|
||||
if (!queue.metadata.channel) return
|
||||
if ("send" in queue.metadata.channel) return queue.metadata.channel.send({ content: t(queue.guild.preferredLocale, "player.leaving_empty_channel") })
|
||||
|
||||
if ("send" in queue.metadata.channel) {
|
||||
const guildLocale = await getGuildLocale(queue.guild.id)
|
||||
return queue.metadata.channel.send({ content: t(guildLocale, "player.leaving_empty_channel") })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { GuildQueue } from "discord-player"
|
||||
import type { PlayerMetadata } from "@/types/player"
|
||||
import { stopProgressSaving } from "@/utils/player"
|
||||
import { t } from "@/utils/i18n"
|
||||
import { t, getGuildLocale } from "@/utils/i18n"
|
||||
|
||||
export const name = "emptyQueue"
|
||||
export async function execute(queue: GuildQueue<PlayerMetadata>) {
|
||||
@@ -9,5 +9,9 @@ export async function execute(queue: GuildQueue<PlayerMetadata>) {
|
||||
await stopProgressSaving(queue.guild.id, queue.player.client.user?.id ?? "")
|
||||
|
||||
if (!queue.metadata.channel) return
|
||||
if ("send" in queue.metadata.channel) return queue.metadata.channel.send({ content: t(queue.guild.preferredLocale, "player.queue_empty") })
|
||||
|
||||
if ("send" in queue.metadata.channel) {
|
||||
const guildLocale = await getGuildLocale(queue.guild.id)
|
||||
return queue.metadata.channel.send({ content: t(guildLocale, "player.queue_empty") })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import type { GuildQueue, Track } from "discord-player"
|
||||
import type { PlayerMetadata } from "@/types/player"
|
||||
import { t } from "@/utils/i18n"
|
||||
import { t, getGuildLocale } from "@/utils/i18n"
|
||||
|
||||
export const name = "playerSkip"
|
||||
export async function execute(queue: GuildQueue<PlayerMetadata>, track: Track) {
|
||||
// Emitted when the audio player fails to load the stream for a song
|
||||
if (!queue.metadata.channel) return
|
||||
if ("send" in queue.metadata.channel) return queue.metadata.channel.send({
|
||||
content: t(queue.guild.preferredLocale, "player.track_skipped", { title: track.title, author: track.author })
|
||||
})
|
||||
|
||||
if ("send" in queue.metadata.channel) {
|
||||
const guildLocale = await getGuildLocale(queue.guild.id)
|
||||
return queue.metadata.channel.send({ content: t(guildLocale, "player.track_skipped", { title: track.title, author: track.author }) })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import type { GuildQueue, Track } from "discord-player"
|
||||
import type { PlayerMetadata } from "@/types/player"
|
||||
import { t } from "@/utils/i18n"
|
||||
import { t, getGuildLocale } from "@/utils/i18n"
|
||||
|
||||
export const name = "playerStart"
|
||||
export async function execute(queue: GuildQueue<PlayerMetadata>, track: Track) {
|
||||
// Emitted when the player starts to play a song
|
||||
if (!queue.metadata.channel) return
|
||||
if ("send" in queue.metadata.channel) await queue.metadata.channel.send({ content: t(queue.guild.preferredLocale, "player.now_playing", { title: track.title, author: track.author }) })
|
||||
|
||||
if ("send" in queue.metadata.channel) {
|
||||
const guildLocale = await getGuildLocale(queue.guild.id)
|
||||
await queue.metadata.channel.send({ content: t(guildLocale, "player.now_playing", { title: track.title, author: track.author }) })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,10 +103,6 @@
|
||||
"description_enabled": "Disco mode is enabled! Visual and audio effects will be applied during music playback.",
|
||||
"description_disabled": "Disco mode is disabled. Enable it to enjoy visual and audio effects during music playback.",
|
||||
"channel_not_configured": "No channel configured",
|
||||
"channel_not_found": "Channel not found",
|
||||
"enabled": "✅ Enabled",
|
||||
"disabled": "❌ Disabled",
|
||||
"configure_channel": "Configure Channel",
|
||||
"configure_channel_first": "❌ Cannot enable Disco mode! Please first configure a channel with the **Configure Channel** button.",
|
||||
"effects_applied": "Disco effects will be applied in {channel}.",
|
||||
"select_channel": "Please select the channel where to apply Disco effects:",
|
||||
@@ -187,11 +183,6 @@
|
||||
"message_not_found": "Message not found for {userName}, cleaning up messageId",
|
||||
"stream_offline_cleanup": "Offline stream detected for {userName}, cleaning up messageId",
|
||||
"cleanup_error": "Error while cleaning up messageId for {userId}"
|
||||
},
|
||||
"logs": {
|
||||
"user_fetch_error": "Error while fetching user for ID {userId}",
|
||||
"listener_removed": "Listener removed for {streamerName} (ID {userId})",
|
||||
"listener_removal_error": "Error while removing listener for {streamerName}"
|
||||
}
|
||||
},
|
||||
"amp": {
|
||||
@@ -422,6 +413,9 @@
|
||||
"select_streamer_remove": "Select a streamer to remove"
|
||||
}
|
||||
},
|
||||
"locale": {
|
||||
"updated": "✅ Server language updated from **{oldLanguage}** to **{newLanguage}**!"
|
||||
},
|
||||
"database": {
|
||||
"owner_only": "This command can only be used by the bot owner!",
|
||||
"server_only": "This command must be used in a server!",
|
||||
@@ -477,7 +471,8 @@
|
||||
"debug": "[Discord-Player] Debug - Player debug event: {message}",
|
||||
"disco": {
|
||||
"channel_not_configured": "[Discord-Player] PlayerDisco - {guild} Channel is not configured!",
|
||||
"channel_not_found": "[Discord-Player] PlayerDisco - {guild} No channel found with id {channelId}"
|
||||
"channel_not_found": "[Discord-Player] PlayerDisco - {guild} No channel found with id {channelId}",
|
||||
"general_error": "[Discord-Player] Disco - General disco module error"
|
||||
},
|
||||
"progress_saving": {
|
||||
"missing_ids": "[Discord-Player] ProgressSaving - GuildId or BotId is missing!",
|
||||
@@ -485,6 +480,14 @@
|
||||
"stop": "[Discord-Player] ProgressSaving - Stopping save for server {guildId} (bot {botId})",
|
||||
"error": "[Discord-Player] ProgressSaving - Error saving progress for guild {guildId} (bot {botId})",
|
||||
"database_not_exist": "[Discord-Player] ProgressSaving - Database data does not exist!"
|
||||
},
|
||||
"replay": {
|
||||
"connect_error": "[Discord-Player] Replay - Error connecting to voice channel",
|
||||
"play_error": "[Discord-Player] Replay - Error playing track"
|
||||
},
|
||||
"play": {
|
||||
"connect_error": "[Discord-Player] Play - Error connecting to voice channel",
|
||||
"execution_error": "[Discord-Player] Play - Error executing track"
|
||||
}
|
||||
},
|
||||
"mongoose": {
|
||||
@@ -494,7 +497,8 @@
|
||||
"error": "[Mongoose] An error occurred with the database connection: {message}",
|
||||
"event_triggered": "[Mongoose] Event {event} triggered",
|
||||
"guild_init": "[Mongoose] Initializing guild profile for {name} ({id})",
|
||||
"guild_create": "[Mongoose] GuildCreate - Database data for new guild \"{name}\" successfully initialized!"
|
||||
"guild_create": "[Mongoose] GuildCreate - Database data for new guild \"{name}\" successfully initialized!",
|
||||
"locale_fetch_error": "[Mongoose] Error fetching guild locale for {guildId}"
|
||||
},
|
||||
"twitch": {
|
||||
"starting_listener": "[Twitch] Starting listener with {adapter}...",
|
||||
@@ -525,13 +529,29 @@
|
||||
"stream_data_not_found": "[Twitch] StreamWatching - {guild} Stream data not found for {streamer} (ID {id})",
|
||||
"message_id_not_found": "[Twitch] StreamWatching - {guild} Message ID not found for {streamer} (ID {id})",
|
||||
"user_fetch_error": "[Twitch] Error fetching user for ID {id}",
|
||||
"user_fetch_error_detailed": "[Twitch] Error while fetching user for ID {id}",
|
||||
"starting_listener_ngrok": "[Twitch] Starting listener with ngrok...",
|
||||
"user_fetch_error_buttons": "[Twitch] Error fetching user for ID {id} in buttons/selectmenu",
|
||||
"listener_removal_error": "[Twitch] Error removing listener for {streamerName}"
|
||||
"listener_removal_error": "[Twitch] Error removing listener for {streamerName}",
|
||||
"missing_credentials": "[Twitch] Missing TWITCH_APP_ID or TWITCH_APP_SECRET in environment variables!",
|
||||
"starting_listener_port": "[Twitch] Starting listener with port {port}...",
|
||||
"streamer_already_processing": "[Twitch] StreamWatching - {{{guildName}}} Streamer {broadcasterName} already being processed, skipping",
|
||||
"stop_watching_error": "[Twitch] Error stopping watching for {streamer} (ID {id}) on {guildId}"
|
||||
},
|
||||
"freebox": {
|
||||
"lcd_timer_restored": "Timers restored successfully for {guild}!"
|
||||
"lcd_timer_restored": "Timers restored successfully for {guild}!",
|
||||
"authorization_pending": "[Freebox] Authorization pending...",
|
||||
"timer_scheduled": "[Freebox] Timer scheduled for {guildId} - Turn on: {nextMorning}, Turn off: {nextNight}",
|
||||
"timers_cleaned": "[Freebox] Timers cleaned for {guildId}",
|
||||
"all_timers_cleaned": "[Freebox] All timers have been cleaned",
|
||||
"missing_configuration": "[Freebox] Missing configuration for server {guildId}",
|
||||
"challenge_error": "[Freebox] Error retrieving challenge for {guildId}",
|
||||
"challenge_not_found": "[Freebox] Challenge not found for {guildId}",
|
||||
"session_error": "[Freebox] Error creating session for {guildId}",
|
||||
"session_token_not_found": "[Freebox] Session token not found for {guildId}",
|
||||
"leds_control_error": "[Freebox] Error controlling LEDs for {guildId}",
|
||||
"leds_success": "[Freebox] LEDs {status} successfully for {guildId}",
|
||||
"leds_error": "[Freebox] Error controlling LEDs for {guildId}",
|
||||
"lcd_status_error": "[Freebox] Error retrieving LCD status",
|
||||
"test_connection_error": "[Freebox] Error testing connection"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,11 +183,6 @@
|
||||
"message_not_found": "Message introuvable pour {userName}, nettoyage du messageId",
|
||||
"stream_offline_cleanup": "Stream hors ligne détecté pour {userName}, nettoyage du messageId",
|
||||
"cleanup_error": "Erreur lors du nettoyage du messageId pour {userId}"
|
||||
},
|
||||
"logs": {
|
||||
"user_fetch_error": "Erreur lors de la récupération de l'utilisateur pour l'ID {userId}",
|
||||
"listener_removed": "Listener supprimé pour {streamerName} (ID {userId})",
|
||||
"listener_removal_error": "Erreur lors de la suppression du listener pour {streamerName}"
|
||||
}
|
||||
},
|
||||
"amp": {
|
||||
@@ -418,6 +413,9 @@
|
||||
"select_streamer_remove": "Sélectionner un streamer à supprimer"
|
||||
}
|
||||
},
|
||||
"locale": {
|
||||
"updated": "✅ Langue du serveur mise à jour de **{oldLanguage}** vers **{newLanguage}** !"
|
||||
},
|
||||
"database": {
|
||||
"owner_only": "Cette commande ne peut être utilisée que par le propriétaire du bot !",
|
||||
"server_only": "Cette commande doit être utilisée sur un serveur !",
|
||||
@@ -448,8 +446,7 @@
|
||||
"button_error": "[DiscordJS] InteractionCreate - Erreur lors du clic sur {id}",
|
||||
"selectmenu_not_found": "[DiscordJS] InteractionCreate - Aucun SelectMenu avec l'id {id} trouvé.",
|
||||
"selectmenu_used": "[DiscordJS] InteractionCreate - SelectMenu '{id}' utilisé par {user}",
|
||||
"selectmenu_error": "[DiscordJS] InteractionCreate - Erreur lors de l'utilisation de {id}",
|
||||
"selectmenu_invalid_type": "[DiscordJS] InteractionCreate - Type de SelectMenu invalide pour {id} reçu '{type}'"
|
||||
"selectmenu_error": "[DiscordJS] InteractionCreate - Erreur lors de l'utilisation de {id}"
|
||||
},
|
||||
"error": "[DiscordJS] Error - Une erreur s'est produite : {message}",
|
||||
"boost": {
|
||||
@@ -474,7 +471,8 @@
|
||||
"debug": "[Discord-Player] Debug - Événement de débogage du lecteur : {message}",
|
||||
"disco": {
|
||||
"channel_not_configured": "[Discord-Player] PlayerDisco - {guild} Le canal n'est pas configuré !",
|
||||
"channel_not_found": "[Discord-Player] PlayerDisco - {guild} Aucun canal trouvé avec l'id {channelId}"
|
||||
"channel_not_found": "[Discord-Player] PlayerDisco - {guild} Aucun canal trouvé avec l'id {channelId}",
|
||||
"general_error": "[Discord-Player] Disco - Erreur générale du module Disco"
|
||||
},
|
||||
"progress_saving": {
|
||||
"missing_ids": "[Discord-Player] ProgressSaving - GuildId ou BotId manquant !",
|
||||
@@ -482,6 +480,14 @@
|
||||
"stop": "[Discord-Player] ProgressSaving - Arrêt de la sauvegarde pour le serveur {guildId} (bot {botId})",
|
||||
"error": "[Discord-Player] ProgressSaving - Erreur lors de la sauvegarde pour le serveur {guildId} (bot {botId})",
|
||||
"database_not_exist": "[Discord-Player] ProgressSaving - Les données de base n'existent pas !"
|
||||
},
|
||||
"replay": {
|
||||
"connect_error": "[Discord-Player] Replay - Erreur lors de la connexion au canal vocal",
|
||||
"play_error": "[Discord-Player] Replay - Erreur lors de la lecture de la piste"
|
||||
},
|
||||
"play": {
|
||||
"connect_error": "[Discord-Player] Play - Erreur lors de la connexion au canal vocal",
|
||||
"execution_error": "[Discord-Player] Play - Erreur lors de l'exécution de la piste"
|
||||
}
|
||||
},
|
||||
"mongoose": {
|
||||
@@ -491,7 +497,8 @@
|
||||
"error": "[Mongoose] Une erreur s'est produite avec la connexion à la base de données : {message}",
|
||||
"event_triggered": "[Mongoose] Événement {event} déclenché",
|
||||
"guild_init": "[Mongoose] Initialisation du profil de serveur pour {name} ({id})",
|
||||
"guild_create": "[Mongoose] GuildCreate - Données de base pour le nouveau serveur \"{name}\" initialisées avec succès !"
|
||||
"guild_create": "[Mongoose] GuildCreate - Données de base pour le nouveau serveur \"{name}\" initialisées avec succès !",
|
||||
"locale_fetch_error": "[Mongoose] Erreur lors de la récupération de la locale du serveur {guildId}"
|
||||
},
|
||||
"twitch": {
|
||||
"starting_listener": "[Twitch] Démarrage du listener avec {adapter}...",
|
||||
@@ -522,13 +529,29 @@
|
||||
"stream_data_not_found": "[Twitch] StreamWatching - {guild} Données de stream non trouvées pour {streamer} (ID {id})",
|
||||
"message_id_not_found": "[Twitch] StreamWatching - {guild} ID de message non trouvé pour {streamer} (ID {id})",
|
||||
"user_fetch_error": "[Twitch] Erreur lors de la récupération de l'utilisateur pour l'ID {id}",
|
||||
"user_fetch_error_detailed": "[Twitch] Erreur lors de la récupération de l'utilisateur pour l'ID {id}",
|
||||
"starting_listener_ngrok": "[Twitch] Démarrage du listener avec ngrok...",
|
||||
"user_fetch_error_buttons": "[Twitch] Erreur lors de la récupération de l'utilisateur pour l'ID {id} dans buttons/selectmenu",
|
||||
"listener_removal_error": "[Twitch] Erreur lors de la suppression du listener pour {streamerName}"
|
||||
"listener_removal_error": "[Twitch] Erreur lors de la suppression du listener pour {streamerName}",
|
||||
"missing_credentials": "[Twitch] TWITCH_APP_ID ou TWITCH_APP_SECRET manquant dans les variables d'environnement !",
|
||||
"starting_listener_port": "[Twitch] Démarrage du listener avec le port {port}...",
|
||||
"streamer_already_processing": "[Twitch] StreamWatching - {{{guildName}}} Streamer {broadcasterName} déjà en cours de traitement, ignoré",
|
||||
"stop_watching_error": "[Twitch] Erreur lors de l'arrêt du watching pour {streamer} (ID {id}) sur {guildId}"
|
||||
},
|
||||
"freebox": {
|
||||
"lcd_timer_restored": "Minuteurs restaurés avec succès pour {guild} !"
|
||||
"lcd_timer_restored": "Minuteurs restaurés avec succès pour {guild} !",
|
||||
"authorization_pending": "[Freebox] Autorisation en attente...",
|
||||
"timer_scheduled": "[Freebox] Timer programmé pour {guildId} - Allumage: {nextMorning}, Extinction: {nextNight}",
|
||||
"timers_cleaned": "[Freebox] Timers nettoyés pour {guildId}",
|
||||
"all_timers_cleaned": "[Freebox] Tous les timers ont été nettoyés",
|
||||
"missing_configuration": "[Freebox] Configuration manquante pour le serveur {guildId}",
|
||||
"challenge_error": "[Freebox] Erreur lors de la récupération du challenge pour {guildId}",
|
||||
"challenge_not_found": "[Freebox] Challenge introuvable pour {guildId}",
|
||||
"session_error": "[Freebox] Erreur lors de la création de la session pour {guildId}",
|
||||
"session_token_not_found": "[Freebox] Token de session introuvable pour {guildId}",
|
||||
"leds_control_error": "[Freebox] Erreur lors du contrôle des LEDs pour {guildId}",
|
||||
"leds_success": "[Freebox] LEDs {status} avec succès pour {guildId}",
|
||||
"leds_error": "[Freebox] Erreur lors du contrôle des LEDs pour {guildId}",
|
||||
"lcd_status_error": "[Freebox] Erreur lors de la récupération de l'état LCD",
|
||||
"test_connection_error": "[Freebox] Erreur lors du test de connexion"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ const guildSchema = new Schema({
|
||||
guildId: { type: String, required: true },
|
||||
guildName: { type: String, required: true },
|
||||
guildIcon: { type: String, required: true },
|
||||
guildLocale: { type: String, required: true },
|
||||
guildPlayer: {
|
||||
instances: [{
|
||||
botId: { type: String, required: true },
|
||||
|
||||
@@ -26,7 +26,7 @@ export async function execute(interaction: StringSelectMenuInteraction) {
|
||||
const user = await twitchClient.users.getUserById(twitchUserId)
|
||||
if (user) streamerName = user.displayName
|
||||
} catch {
|
||||
logConsole('twitch', 'user_fetch_error_buttons', { id: twitchUserId })
|
||||
logConsole('twitch', 'user_fetch_error', { id: twitchUserId })
|
||||
}
|
||||
|
||||
// Supprimer le streamer
|
||||
|
||||
@@ -5,6 +5,7 @@ export interface GuildSchema {
|
||||
guildId: string
|
||||
guildName: string
|
||||
guildIcon: string
|
||||
guildLocale: string
|
||||
guildPlayer: GuildPlayer
|
||||
guildAmp: GuildAmp
|
||||
guildFbx: GuildFbx
|
||||
|
||||
@@ -11,6 +11,7 @@ export default async (guild: Guild) => {
|
||||
guildId: guild.id,
|
||||
guildName: guild.name,
|
||||
guildIcon: guild.iconURL() ?? "None",
|
||||
guildLocale: 'fr',
|
||||
guildPlayer: {
|
||||
disco: { enabled: false }
|
||||
},
|
||||
|
||||
@@ -9,6 +9,7 @@ import type {
|
||||
} 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",
|
||||
@@ -123,7 +124,7 @@ export const Timer = {
|
||||
// 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()}`)
|
||||
logConsole('freebox', 'timer_scheduled', { guildId, nextMorning: nextMorning.toLocaleString(), nextNight: nextNight.toLocaleString() })
|
||||
},
|
||||
|
||||
// Fonction utilitaire pour calculer la prochaine occurrence d'une heure donnée
|
||||
@@ -131,10 +132,7 @@ export const Timer = {
|
||||
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)
|
||||
}
|
||||
|
||||
if (target <= now) target.setDate(target.getDate() + 1)
|
||||
return target
|
||||
},
|
||||
|
||||
@@ -145,7 +143,7 @@ export const Timer = {
|
||||
if (timers.morning) clearTimeout(timers.morning)
|
||||
if (timers.night) clearTimeout(timers.night)
|
||||
activeTimers.delete(guildId)
|
||||
console.log(`[Freebox LCD] Timers nettoyés pour ${guildId}`)
|
||||
logConsole('freebox', 'timers_cleaned', { guildId })
|
||||
}
|
||||
},
|
||||
|
||||
@@ -154,39 +152,36 @@ export const Timer = {
|
||||
for (const [guildId] of activeTimers) {
|
||||
Timer.clear(guildId)
|
||||
}
|
||||
console.log(`[Freebox LCD] Tous les timers ont été nettoyés`)
|
||||
logConsole('freebox', 'all_timers_cleaned')
|
||||
},
|
||||
|
||||
// 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
|
||||
}
|
||||
if (!dbDataFbx.host || !dbDataFbx.appToken) { logConsole('freebox', 'missing_configuration', { 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 }
|
||||
if (!challengeData.success) { logConsole('freebox', 'challenge_error', { guildId }); return }
|
||||
|
||||
const challenge = challengeData.result.challenge
|
||||
if (!challenge) { console.error(`[Freebox LCD] Challenge introuvable pour ${guildId}`); return }
|
||||
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<OpenSession>
|
||||
if (!sessionData.success) { console.error(`[Freebox LCD] Erreur lors de la création de la session pour ${guildId}`); return }
|
||||
if (!sessionData.success) { logConsole('freebox', 'session_error', { guildId }); return }
|
||||
|
||||
const sessionToken = sessionData.result.session_token
|
||||
if (!sessionToken) { console.error(`[Freebox LCD] Token de session introuvable pour ${guildId}`); return }
|
||||
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<LcdConfig>
|
||||
if (!lcdData.success) { console.error(`[Freebox LCD] Erreur lors du contrôle des LEDs pour ${guildId}:`, lcdData); return }
|
||||
if (!lcdData.success) { logConsole('freebox', 'leds_control_error', { guildId }); 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)
|
||||
logConsole('freebox', 'leds_success', { status: enabled ? 'allumées' : 'éteintes', guildId })
|
||||
} catch {
|
||||
logConsole('freebox', 'leds_error', { guildId })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { Locale } from "discord.js"
|
||||
import frLocale from "@/locales/fr.json"
|
||||
import enLocale from "@/locales/en.json"
|
||||
import dbGuild from "@/schemas/guild"
|
||||
import { logConsoleError } from "./console"
|
||||
|
||||
// Variables d'environnement pour les locales avec valeurs par défaut
|
||||
const DEFAULT_LOCALE = process.env.DEFAULT_LOCALE ?? 'fr'
|
||||
@@ -11,6 +13,21 @@ type LocaleData = Record<string, unknown>
|
||||
type ReplacementParams = Record<string, string | number>
|
||||
type TranslationKey = string
|
||||
|
||||
/**
|
||||
* Récupère la locale configurée pour un serveur
|
||||
* @param guildId - L'ID du serveur Discord
|
||||
* @returns La locale configurée ou 'fr' par défaut
|
||||
*/
|
||||
export async function getGuildLocale(guildId: string): Promise<string> {
|
||||
try {
|
||||
const guildProfile = await dbGuild.findOne({ guildId })
|
||||
return guildProfile?.guildLocale ?? 'fr'
|
||||
} catch (error) {
|
||||
logConsoleError('mongoose', 'locale.fetch_error', { guildId }, error as Error)
|
||||
return 'fr'
|
||||
}
|
||||
}
|
||||
|
||||
// Conversion des imports en type LocaleData
|
||||
const frLocaleData = frLocale as unknown as LocaleData
|
||||
const enLocaleData = enLocale as unknown as LocaleData
|
||||
@@ -104,6 +121,40 @@ export function getCommandLocalizations(baseKey: string) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fonction de localisation utilisant la locale du serveur
|
||||
* @param guildLocale - La locale configurée du serveur
|
||||
* @param key - La clé de traduction (ex: "player.not_in_voice")
|
||||
* @param params - Les paramètres à remplacer dans la traduction
|
||||
* @returns La chaîne traduite
|
||||
*/
|
||||
export function tGuild(guildLocale: string, key: TranslationKey, params: ReplacementParams = {}): string {
|
||||
return t(guildLocale, key, params)
|
||||
}
|
||||
|
||||
/**
|
||||
* Fonction helper pour obtenir la locale appropriée (serveur ou utilisateur)
|
||||
* @param guildId - L'ID du serveur (optionnel)
|
||||
* @param userLocale - La locale de l'utilisateur
|
||||
* @returns La locale à utiliser
|
||||
*/
|
||||
export async function getLocaleForContext(guildId: string | null, userLocale: string): Promise<string> {
|
||||
if (guildId) return await getGuildLocale(guildId)
|
||||
return userLocale
|
||||
}
|
||||
|
||||
/**
|
||||
* Fonction de traduction intelligente qui utilise automatiquement la locale du serveur
|
||||
* @param interaction - L'interaction Discord
|
||||
* @param key - La clé de traduction
|
||||
* @param params - Les paramètres de remplacement
|
||||
* @returns La chaîne traduite
|
||||
*/
|
||||
export async function tSmart(interaction: { guild: { id: string } | null; locale: string }, key: TranslationKey, params: ReplacementParams = {}): Promise<string> {
|
||||
const locale = await getLocaleForContext(interaction.guild?.id ?? null, interaction.locale)
|
||||
return t(locale, key, params)
|
||||
}
|
||||
|
||||
// Export des constantes de locale
|
||||
export { DEFAULT_LOCALE, CONSOLE_LOCALE }
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ import type { GuildPlayer, Disco } from "@/types/schemas"
|
||||
import type { PlayerMetadata } from "@/types/player"
|
||||
import uptime from "./uptime"
|
||||
import dbGuild from "@/schemas/guild"
|
||||
import { t } from "./i18n"
|
||||
import { logConsole } from "./console"
|
||||
import { t, getGuildLocale } from "./i18n"
|
||||
import { logConsole, logConsoleError } from "./console"
|
||||
|
||||
const progressIntervals = new Map<string, NodeJS.Timeout>()
|
||||
|
||||
@@ -25,7 +25,7 @@ export function startProgressSaving(guildId: string, botId: string) {
|
||||
const interval = setInterval(async () => {
|
||||
try {
|
||||
const queue = useQueue(guildId)
|
||||
if (!queue || !queue.isPlaying() || !queue.currentTrack) { startProgressSaving(guildId, botId); return }
|
||||
if (!queue || !queue.isPlaying() || !queue.currentTrack) { await stopProgressSaving(guildId, botId); return }
|
||||
|
||||
const guildProfile = await dbGuild.findOne({ guildId })
|
||||
if (!guildProfile) { await stopProgressSaving(guildId, botId); return }
|
||||
@@ -50,8 +50,7 @@ export function startProgressSaving(guildId: string, botId: string) {
|
||||
guildProfile.markModified("guildPlayer")
|
||||
await guildProfile.save().catch(console.error)
|
||||
} catch (error) {
|
||||
logConsole('discord_player', 'progress_saving.error', { guildId, botId })
|
||||
console.error(error)
|
||||
logConsoleError('discord_player', 'progress_saving.error', { guildId, botId }, error as Error)
|
||||
await stopProgressSaving(guildId, botId)
|
||||
}
|
||||
}, 3000)
|
||||
@@ -127,12 +126,13 @@ export async function playerReplay(client: Client, dbData: GuildPlayer) {
|
||||
})
|
||||
|
||||
try { if (!queue.connection) await queue.connect(voiceChannel) }
|
||||
catch (error) { console.error(error) }
|
||||
catch (error) { logConsoleError('discord_player', 'replay.connect_error', {}, error as Error) }
|
||||
|
||||
if (!instance.replay.trackUrl) return
|
||||
|
||||
const guildLocale = await getGuildLocale(queue.guild.id)
|
||||
const result = await player.search(instance.replay.trackUrl, { requestedBy: client.user ?? undefined })
|
||||
if (!result.hasTracks()) await textChannel.send(t(queue.guild.preferredLocale, "player.no_track_found", { url: instance.replay.trackUrl }))
|
||||
if (!result.hasTracks()) await textChannel.send(t(guildLocale, "player.no_track_found", { url: instance.replay.trackUrl }))
|
||||
const track = result.tracks[0]
|
||||
|
||||
const entry = queue.tasksQueue.acquire()
|
||||
@@ -143,9 +143,9 @@ export async function playerReplay(client: Client, dbData: GuildPlayer) {
|
||||
if (!queue.isPlaying()) await queue.node.play()
|
||||
if (instance.replay.progress) await queue.node.seek(instance.replay.progress)
|
||||
startProgressSaving(queue.guild.id, botId)
|
||||
await textChannel.send(t(queue.guild.preferredLocale, "player.music_restarted"))
|
||||
await textChannel.send(t(guildLocale, "player.music_restarted"))
|
||||
}
|
||||
catch (error) { console.error(error) }
|
||||
catch (error) { logConsoleError('discord_player', 'replay.play_error', {}, error as Error) }
|
||||
finally { queue.tasksQueue.release() }
|
||||
}
|
||||
|
||||
@@ -164,7 +164,8 @@ export async function playerDisco(client: Client, guild: Guild, dbData: Disco) {
|
||||
return "clear"
|
||||
}
|
||||
|
||||
const { embed, components } = generatePlayerEmbed(guild, guild.preferredLocale)
|
||||
const guildLocale = await getGuildLocale(guild.id)
|
||||
const { embed, components } = generatePlayerEmbed(guild, guildLocale)
|
||||
if (components && embed.data.footer) embed.setFooter({ text: `Uptime: ${uptime(client.uptime)} \n ${embed.data.footer.text}` })
|
||||
else embed.setFooter({ text: `Uptime: ${uptime(client.uptime)}` })
|
||||
|
||||
@@ -183,7 +184,7 @@ export async function playerDisco(client: Client, guild: Guild, dbData: Disco) {
|
||||
}
|
||||
else return await channel.send({ embeds: [embed] })
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
logConsoleError('discord_player', 'disco.general_error', {}, error as Error)
|
||||
return "clear"
|
||||
}
|
||||
}
|
||||
@@ -199,7 +200,7 @@ export async function playerEdit(interaction: ButtonInteraction) {
|
||||
await interaction.update({ components })
|
||||
}
|
||||
|
||||
export function generatePlayerEmbed(guild: Guild, locale: Locale) {
|
||||
export function generatePlayerEmbed(guild: Guild, locale: Locale | string) {
|
||||
const embed = new EmbedBuilder().setColor("#ffc370")
|
||||
|
||||
const queue = useQueue(guild.id)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
const clientId = process.env.TWITCH_APP_ID
|
||||
const clientSecret = process.env.TWITCH_APP_SECRET
|
||||
if (!clientId || !clientSecret) {
|
||||
console.warn(chalk.red("[Twitch] Missing TWITCH_APP_ID or TWITCH_APP_SECRET in environment variables!"))
|
||||
logConsole('twitch', 'missing_credentials')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
@@ -14,12 +14,11 @@ import { NgrokAdapter } from "@twurple/eventsub-ngrok"
|
||||
import type { EventSubStreamOnlineEvent, EventSubStreamOfflineEvent } from "@twurple/eventsub-base"
|
||||
import { EmbedBuilder, ChannelType, ComponentType, ButtonBuilder, ButtonStyle, Locale } from "discord.js"
|
||||
import type { Client, Guild } from "discord.js"
|
||||
import chalk from "chalk"
|
||||
import discordClient from "@/index"
|
||||
import type { GuildTwitch } from "@/types/schemas"
|
||||
import dbGuild from "@/schemas/guild"
|
||||
import { t } from "@/utils/i18n"
|
||||
import { logConsole } from "@/utils/console"
|
||||
import { t, getGuildLocale } from "@/utils/i18n"
|
||||
import { logConsole, logConsoleError } from "@/utils/console"
|
||||
|
||||
// Twurple API client setup
|
||||
const authProvider = new AppTokenAuthProvider(clientId, clientSecret)
|
||||
@@ -35,7 +34,7 @@ if (process.env.NODE_ENV === "development") {
|
||||
const hostName = process.env.TWURPLE_HOSTNAME ?? "localhost"
|
||||
const port = process.env.TWURPLE_PORT ?? "3000"
|
||||
|
||||
console.log(chalk.magenta(`[Twitch] Starting listener with port ${port}...`))
|
||||
logConsole('twitch', 'starting_listener_port', { port })
|
||||
adapter = new ReverseProxyAdapter({ hostName, port: parseInt(port) })
|
||||
}
|
||||
|
||||
@@ -45,26 +44,30 @@ listener.start()
|
||||
|
||||
// Twurple subscriptions callback functions
|
||||
export const onlineSub = async (event: EventSubStreamOnlineEvent) => {
|
||||
console.log(chalk.magenta(`[Twitch] Stream from ${event.broadcasterName} (ID ${event.broadcasterId}) is now online, sending Discord messages...`))
|
||||
logConsole('twitch', 'stream_online', { streamer: event.broadcasterName, id: event.broadcasterId })
|
||||
|
||||
const results = await Promise.allSettled(discordClient.guilds.cache.map(async guild => {
|
||||
const processingKey = `${guild.id}-${event.broadcasterId}`
|
||||
try {
|
||||
console.log(chalk.magenta(`[Twitch] Processing guild: ${guild.name} (ID: ${guild.id}) for streamer ${event.broadcasterName}`))
|
||||
if (processingStreamers.has(processingKey)) { logConsole('twitch', 'streamer_already_processing', { guildName: guild.name, broadcasterName: event.broadcasterName }); return }
|
||||
processingStreamers.add(processingKey)
|
||||
|
||||
logConsole('twitch', 'processing_guild', { name: guild.name, id: guild.id, streamer: event.broadcasterName })
|
||||
|
||||
const notification = await generateNotification(guild, event.broadcasterId, event.broadcasterName)
|
||||
if (notification.status !== "ok") { console.log(chalk.magenta(`[Twitch] StreamWatching - {${guild.name}} Notification generation failed with status: ${notification.status}`)); return }
|
||||
if (notification.status !== "ok") { logConsole('twitch', 'notification_failed', { guild: guild.name, status: notification.status }); return }
|
||||
|
||||
const { guildProfile, dbData, channel, content, embed } = notification
|
||||
if (!dbData) { console.log(chalk.magenta(`[Twitch] StreamWatching - {${guild.name}} No dbData found`)); return }
|
||||
if (!dbData) { logConsole('twitch', 'no_db_data', { guild: guild.name }); return }
|
||||
|
||||
const streamerIndex = dbData.streamers.findIndex(s => s.twitchUserId === event.broadcasterId)
|
||||
if (streamerIndex === -1) { console.log(chalk.magenta(`[Twitch] StreamWatching - {${guild.name}} Streamer ${event.broadcasterName} not found in this guild`)); return }
|
||||
if (streamerIndex === -1) { logConsole('twitch', 'streamer_not_found', { guild: guild.name, streamer: event.broadcasterName }); return }
|
||||
|
||||
if (dbData.streamers[streamerIndex].messageId) { console.log(chalk.magenta(`[Twitch] StreamWatching - {${guild.name}} Message already exists for ${event.broadcasterName}, skipping`)); return }
|
||||
if (dbData.streamers[streamerIndex].messageId) { logConsole('twitch', 'message_exists', { guild: guild.name, streamer: event.broadcasterName }); return }
|
||||
|
||||
console.log(chalk.magenta(`[Twitch] StreamWatching - {${guild.name}} Sending notification for ${event.broadcasterName}`))
|
||||
logConsole('twitch', 'sending_notification', { guild: guild.name, streamer: event.broadcasterName })
|
||||
const message = await channel.send({ content, embeds: [embed] })
|
||||
console.log(chalk.magenta(`[Twitch] StreamWatching - {${guild.name}} Message sent with ID: ${message.id}`))
|
||||
logConsole('twitch', 'message_sent', { guild: guild.name, id: message.id })
|
||||
|
||||
dbData.streamers[streamerIndex].messageId = message.id
|
||||
|
||||
@@ -74,18 +77,20 @@ export const onlineSub = async (event: EventSubStreamOnlineEvent) => {
|
||||
|
||||
startStreamWatching(guild.id, event.broadcasterId, event.broadcasterName, message.id)
|
||||
} catch (error) {
|
||||
console.log(chalk.magenta(`[Twitch] Error processing guild ${guild.name}`))
|
||||
console.error(error)
|
||||
processingStreamers.delete(processingKey)
|
||||
logConsoleError('twitch', 'error_processing_guild', { name: guild.name }, error as Error)
|
||||
} finally {
|
||||
processingStreamers.delete(processingKey)
|
||||
}
|
||||
}))
|
||||
|
||||
results.forEach((result, index) => {
|
||||
if (result.status === "rejected") console.log(chalk.magenta(`[Twitch] Guild ${index} failed:`), result.reason)
|
||||
if (result.status === "rejected") logConsoleError('twitch', 'guild_failed', { index: index.toString() }, result.reason instanceof Error ? result.reason : new Error(String(result.reason)))
|
||||
})
|
||||
}
|
||||
|
||||
export const offlineSub = async (event: EventSubStreamOfflineEvent) => {
|
||||
console.log(chalk.magenta(`[Twitch] Stream from ${event.broadcasterName} (ID ${event.broadcasterId}) is now offline, editing Discord messages...`))
|
||||
logConsole('twitch', 'stream_offline', { streamer: event.broadcasterName, id: event.broadcasterId })
|
||||
|
||||
await Promise.all(discordClient.guilds.cache.map(async guild => {
|
||||
await stopStreamWatching(guild.id, event.broadcasterId, event.broadcasterName)
|
||||
@@ -95,8 +100,11 @@ export const offlineSub = async (event: EventSubStreamOfflineEvent) => {
|
||||
// Stream upadting intervals
|
||||
const streamIntervals = new Map<string, NodeJS.Timeout>()
|
||||
|
||||
// Tracking des streamers en cours de traitement pour éviter les doublons
|
||||
const processingStreamers = new Set<string>()
|
||||
|
||||
export function startStreamWatching(guildId: string, streamerId: string, streamerName: string, messageId: string) {
|
||||
console.log(chalk.magenta(`[Twitch] StreamWatching - Démarrage du visionnage de ${streamerName} (ID ${streamerId}) sur ${guildId}`))
|
||||
logConsole('twitch', 'start_watching', { streamer: streamerName, id: streamerId, guildId })
|
||||
|
||||
const key = `${guildId}-${streamerId}`
|
||||
if (streamIntervals.has(key)) {
|
||||
@@ -109,21 +117,19 @@ export function startStreamWatching(guildId: string, streamerId: string, streame
|
||||
try {
|
||||
const guild = await discordClient.guilds.fetch(guildId)
|
||||
const notification = await generateNotification(guild, streamerId, streamerName)
|
||||
if (notification.status !== "ok") { console.log(chalk.magenta(`[Twitch] StreamWatching - {${guild.name}} Notification generation failed with status: ${notification.status}`)); return }
|
||||
if (notification.status !== "ok") { logConsole('twitch', 'notification_failed', { guild: guild.name, status: notification.status }); return }
|
||||
|
||||
const { channel, content, embed } = notification
|
||||
if (!embed) { console.log(chalk.magenta(`[Twitch] StreamWatching - {${guild.name}} Embed is missing`)); return }
|
||||
if (!embed) { logConsole('twitch', 'embed_missing', { guild: guild.name }); return }
|
||||
|
||||
try {
|
||||
const message = await channel.messages.fetch(messageId)
|
||||
await message.edit({ content, embeds: [embed] })
|
||||
} catch (error) {
|
||||
console.log(chalk.magenta(`[Twitch] StreamWatching - {${guild.name}} Error editing message for ${streamerName} (ID ${streamerId})`))
|
||||
console.error(error)
|
||||
logConsoleError('twitch', 'error_editing_message', { guild: guild.name, streamer: streamerName, id: streamerId }, error as Error)
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(chalk.magenta(`[Twitch] StreamWatching - Erreur lors du visionnage de ${streamerName} (ID ${streamerId}) sur ${guildId}`))
|
||||
console.error(error)
|
||||
logConsoleError('twitch', 'error_watching', { streamer: streamerName, id: streamerId, guildId }, error as Error)
|
||||
await stopStreamWatching(guildId, streamerId, streamerName)
|
||||
}
|
||||
}, 60000)
|
||||
@@ -132,7 +138,7 @@ export function startStreamWatching(guildId: string, streamerId: string, streame
|
||||
}
|
||||
|
||||
export async function stopStreamWatching(guildId: string, streamerId: string, streamerName: string) {
|
||||
console.log(chalk.magenta(`[Twitch] StreamWatching - Arrêt du visionnage de ${streamerName} (ID ${streamerId})`))
|
||||
logConsole('twitch', 'stop_watching', { streamer: streamerName, id: streamerId })
|
||||
|
||||
const key = `${guildId}-${streamerId}`
|
||||
if (streamIntervals.has(key)) {
|
||||
@@ -140,116 +146,121 @@ export async function stopStreamWatching(guildId: string, streamerId: string, st
|
||||
streamIntervals.delete(key)
|
||||
}
|
||||
|
||||
const guild = await discordClient.guilds.fetch(guildId)
|
||||
const guildProfile = await dbGuild.findOne({ guildId: guild.id })
|
||||
if (!guildProfile) { console.log(chalk.magenta(`[Twitch] StreamWatching - {${guild.name}} Database data does not exist !`)); return }
|
||||
|
||||
const dbData = guildProfile.get("guildTwitch") as GuildTwitch
|
||||
if (!dbData.enabled || !dbData.channelId) { console.log(chalk.magenta(`[Twitch] StreamWatching - {${guild.name}} Database data does not exist !`)); return }
|
||||
|
||||
const channel = await guild.channels.fetch(dbData.channelId)
|
||||
if (!channel || (channel.type !== ChannelType.GuildText && channel.type !== ChannelType.GuildAnnouncement)) {
|
||||
console.log(chalk.magenta(`[Twitch] StreamWatching - {${guild.name}} Channel with ID ${dbData.channelId} not found for Twitch notifications`))
|
||||
return
|
||||
}
|
||||
|
||||
const streamer = dbData.streamers.find(s => s.twitchUserId === streamerId)
|
||||
if (!streamer) { console.log(chalk.magenta(`[Twitch] StreamWatching - {${guild.name}} Streamer not found in guild for ${streamerName} (ID ${streamerId})`)); return }
|
||||
|
||||
const messageId = streamer.messageId
|
||||
if (!messageId) { console.log(chalk.magenta(`[Twitch] StreamWatching - {${guild.name}} Message ID not found for ${streamerName} (ID ${streamerId})`)); return }
|
||||
|
||||
const user = await twitchClient.users.getUserById(streamerId)
|
||||
if (!user) console.log(chalk.magenta(`[Twitch] StreamWatching - {${guild.name}} User data not found for ${streamerName} (ID ${streamerId})`))
|
||||
|
||||
let duration_string = ""
|
||||
const stream = await user?.getStream()
|
||||
if (!stream) {
|
||||
console.log(chalk.magenta(`[Twitch] StreamWatching - {${guild.name}} Stream data not found for ${streamerName} (ID ${streamerId})`))
|
||||
duration_string = t(guild.preferredLocale, "twitch.notification.offline.duration_unknown")
|
||||
} else {
|
||||
const duration = new Date().getTime() - stream.startDate.getTime()
|
||||
const seconds = Math.floor(duration / 1000)
|
||||
const minutes = Math.floor(seconds / 60)
|
||||
const hours = Math.floor(minutes / 60)
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
|
||||
duration_string = `${hours ? hours + "H " : ""}${minutes % 60 ? (minutes % 60) + "M " : ""}${seconds % 60 ? (seconds % 60) + "S" : ""}`
|
||||
}
|
||||
|
||||
let content = ""
|
||||
if (!streamer.discordUserId) content = t(guild.preferredLocale, "twitch.notification.offline.everyone", { streamer: streamerName })
|
||||
else content = t(guild.preferredLocale, "twitch.notification.offline.everyone_with_mention", { discordId: streamer.discordUserId })
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor("#6441a5")
|
||||
.setAuthor({
|
||||
name: t(guild.preferredLocale, "twitch.notification.offline.author", { duration: duration_string }),
|
||||
iconURL: user?.profilePictureUrl ?? "https://static-cdn.jtvnw.net/emoticons/v2/58765/static/light/3.0"
|
||||
})
|
||||
.setTimestamp()
|
||||
|
||||
try {
|
||||
const message = await channel.messages.fetch(messageId)
|
||||
await message.edit({ content, embeds: [embed] })
|
||||
const guild = await discordClient.guilds.fetch(guildId)
|
||||
const guildProfile = await dbGuild.findOne({ guildId: guild.id })
|
||||
if (!guildProfile) { logConsole('twitch', 'database_not_exist', { guild: guild.name }); return }
|
||||
|
||||
const dbData = guildProfile.get("guildTwitch") as GuildTwitch
|
||||
if (!dbData.enabled || !dbData.channelId) { logConsole('twitch', 'database_not_exist', { guild: guild.name }); return }
|
||||
|
||||
const channel = await guild.channels.fetch(dbData.channelId)
|
||||
if (!channel || (channel.type !== ChannelType.GuildText && channel.type !== ChannelType.GuildAnnouncement)) {
|
||||
logConsole('twitch', 'channel_not_found', { guild: guild.name, channelId: dbData.channelId })
|
||||
return
|
||||
}
|
||||
|
||||
const streamer = dbData.streamers.find(s => s.twitchUserId === streamerId)
|
||||
if (!streamer) { logConsole('twitch', 'streamer_not_found', { guild: guild.name, streamer: streamerName, id: streamerId }); return }
|
||||
|
||||
const messageId = streamer.messageId
|
||||
if (!messageId) { logConsole('twitch', 'message_id_not_found', { guild: guild.name, streamer: streamerName, id: streamerId }); return }
|
||||
|
||||
const user = await twitchClient.users.getUserById(streamerId)
|
||||
if (!user) logConsole('twitch', 'user_data_not_found', { guild: guild.name, streamer: streamerName, id: streamerId })
|
||||
|
||||
const guildLocale = await getGuildLocale(guild.id)
|
||||
let duration_string = ""
|
||||
const stream = await user?.getStream()
|
||||
if (!stream) {
|
||||
logConsole('twitch', 'stream_data_not_found', { guild: guild.name, streamer: streamerName, id: streamerId })
|
||||
duration_string = t(guildLocale, "twitch.notification.offline.duration_unknown")
|
||||
} else {
|
||||
const duration = new Date().getTime() - stream.startDate.getTime()
|
||||
const seconds = Math.floor(duration / 1000)
|
||||
const minutes = Math.floor(seconds / 60)
|
||||
const hours = Math.floor(minutes / 60)
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
|
||||
duration_string = `${hours ? hours + "H " : ""}${minutes % 60 ? (minutes % 60) + "M " : ""}${seconds % 60 ? (seconds % 60) + "S" : ""}`
|
||||
}
|
||||
|
||||
let content = ""
|
||||
if (!streamer.discordUserId) content = t(guildLocale, "twitch.notification.offline.everyone", { streamer: streamerName })
|
||||
else content = t(guildLocale, "twitch.notification.offline.everyone_with_mention", { discordId: streamer.discordUserId })
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor("#6441a5")
|
||||
.setAuthor({
|
||||
name: t(guildLocale, "twitch.notification.offline.author", { duration: duration_string }),
|
||||
iconURL: user?.profilePictureUrl ?? "https://static-cdn.jtvnw.net/emoticons/v2/58765/static/light/3.0"
|
||||
})
|
||||
.setTimestamp()
|
||||
|
||||
try {
|
||||
const message = await channel.messages.fetch(messageId)
|
||||
await message.edit({ content, embeds: [embed] })
|
||||
} catch (error) {
|
||||
logConsoleError('twitch', 'error_editing_message', { guild: guild.name, streamer: streamerName, id: streamerId }, error as Error)
|
||||
}
|
||||
|
||||
const streamerIndex = dbData.streamers.findIndex(s => s.twitchUserId === streamerId)
|
||||
if (streamerIndex === -1) return
|
||||
|
||||
dbData.streamers[streamerIndex].messageId = ""
|
||||
|
||||
guildProfile.set("guildTwitch", dbData)
|
||||
guildProfile.markModified("guildTwitch")
|
||||
await guildProfile.save().catch(console.error)
|
||||
} catch (error) {
|
||||
console.log(chalk.magenta(`[Twitch] StreamWatching - {${guild.name}} Error editing message for ${streamerName} (ID ${streamerId})`))
|
||||
console.error(error)
|
||||
logConsoleError('twitch', 'stop_watching_error', { streamer: streamerName, id: streamerId, guildId }, error as Error)
|
||||
}
|
||||
|
||||
const streamerIndex = dbData.streamers.findIndex(s => s.twitchUserId === streamerId)
|
||||
if (streamerIndex === -1) return
|
||||
|
||||
dbData.streamers[streamerIndex].messageId = ""
|
||||
|
||||
guildProfile.set("guildTwitch", dbData)
|
||||
guildProfile.markModified("guildTwitch")
|
||||
await guildProfile.save().catch(console.error)
|
||||
}
|
||||
|
||||
async function generateNotification(guild: Guild, streamerId: string, streamerName: string) {
|
||||
const guildProfile = await dbGuild.findOne({ guildId: guild.id })
|
||||
if (!guildProfile) {
|
||||
console.log(chalk.magenta(`[Twitch] StreamWatching - {${guild.name}} Database data does not exist !`))
|
||||
logConsole('twitch', 'database_not_exist', { guild: guild.name })
|
||||
return { status: "noProfile" }
|
||||
}
|
||||
|
||||
const dbData = guildProfile.get("guildTwitch") as GuildTwitch
|
||||
if (!dbData.enabled || !dbData.channelId) {
|
||||
console.log(chalk.magenta(`[Twitch] StreamWatching - {${guild.name}} Twitch module is not enabled or channel ID is missing`))
|
||||
logConsole('twitch', 'module_disabled', { guild: guild.name })
|
||||
return { status: "disabled" }
|
||||
}
|
||||
|
||||
const channel = await guild.channels.fetch(dbData.channelId)
|
||||
if ((channel?.type !== ChannelType.GuildText && channel?.type !== ChannelType.GuildAnnouncement)) {
|
||||
console.log(chalk.magenta(`[Twitch] StreamWatching - {${guild.name}} Channel with ID ${dbData.channelId} not found for Twitch notifications`))
|
||||
logConsole('twitch', 'channel_not_found', { guild: guild.name, channelId: dbData.channelId })
|
||||
return { status: "noChannel" }
|
||||
}
|
||||
|
||||
const streamer = dbData.streamers.find(s => s.twitchUserId === streamerId)
|
||||
if (!streamer) {
|
||||
console.log(chalk.magenta(`[Twitch] StreamWatching - {${guild.name}} Streamer not found in guild for ${streamerName} (ID ${streamerId})`))
|
||||
logConsole('twitch', 'streamer_not_found', { guild: guild.name, streamer: streamerName, id: streamerId })
|
||||
return { status: "noStreamer" }
|
||||
}
|
||||
|
||||
const user = await twitchClient.users.getUserById(streamerId)
|
||||
if (!user) console.log(chalk.magenta(`[Twitch] StreamWatching - {${guild.name}} User data not found for ${streamerName} (ID ${streamerId})`))
|
||||
if (!user) logConsole('twitch', 'user_data_not_found', { guild: guild.name, streamer: streamerName, id: streamerId })
|
||||
|
||||
const stream = await user?.getStream()
|
||||
if (!stream) console.log(chalk.magenta(`[Twitch] StreamWatching - {${guild.name}} Stream data not found for ${streamerName} (ID ${streamerId})`))
|
||||
if (!stream) logConsole('twitch', 'stream_data_not_found', { guild: guild.name, streamer: streamerName, id: streamerId })
|
||||
|
||||
const guildLocale = await getGuildLocale(guild.id)
|
||||
let content = ""
|
||||
if (!streamer.discordUserId) content = t(guild.preferredLocale, "twitch.notification.online.everyone", { streamer: user?.displayName ?? streamerName })
|
||||
else content = t(guild.preferredLocale, "twitch.notification.online.everyone_with_mention", { discordId: streamer.discordUserId })
|
||||
if (!streamer.discordUserId) content = t(guildLocale, "twitch.notification.online.everyone", { streamer: user?.displayName ?? streamerName })
|
||||
else content = t(guildLocale, "twitch.notification.online.everyone_with_mention", { discordId: streamer.discordUserId })
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor("#6441a5")
|
||||
.setTitle(stream?.title ?? t(guild.preferredLocale, "twitch.notification.online.title_unknown"))
|
||||
.setTitle(stream?.title ?? t(guildLocale, "twitch.notification.online.title_unknown"))
|
||||
.setURL(`https://twitch.tv/${streamerName}`)
|
||||
.setAuthor({
|
||||
name: t(guild.preferredLocale, "twitch.notification.online.author", { streamer: (user?.displayName ?? streamerName).toUpperCase() }),
|
||||
name: t(guildLocale, "twitch.notification.online.author", { streamer: (user?.displayName ?? streamerName).toUpperCase() }),
|
||||
iconURL: user?.profilePictureUrl ?? "https://static-cdn.jtvnw.net/emoticons/v2/58765/static/light/3.0"
|
||||
})
|
||||
.setDescription(t(guild.preferredLocale, "twitch.notification.online.description", {
|
||||
.setDescription(t(guildLocale, "twitch.notification.online.description", {
|
||||
game: stream?.gameName ?? "?",
|
||||
viewers: stream?.viewers.toString() ?? "?"
|
||||
}))
|
||||
|
||||
Reference in New Issue
Block a user