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,12 +0,0 @@
|
||||
.git
|
||||
.vscode
|
||||
node_modules
|
||||
public/cracks/*
|
||||
.dockerignore
|
||||
.env
|
||||
.gitignore
|
||||
.ncurc.json
|
||||
Dockerfile
|
||||
eslint.config.mjs
|
||||
README.md
|
||||
tsconfig.json
|
||||
29
.env.example
Normal file
29
.env.example
Normal file
@@ -0,0 +1,29 @@
|
||||
# Configuration du bot Discord
|
||||
DISCORD_APP_ID=votre_app_id_discord
|
||||
DISCORD_TOKEN=votre_token_discord
|
||||
DISCORD_SPT_GUILD_ID=id_guild_salonpostam
|
||||
|
||||
# Configuration Twitch
|
||||
TWITCH_APP_ID=votre_app_id_twitch
|
||||
TWITCH_APP_SECRET=votre_secret_twitch
|
||||
|
||||
# Configuration Twurple (pour les webhooks)
|
||||
TWURPLE_HOSTNAME=localhost
|
||||
TWURPLE_PORT=3000
|
||||
TWURPLE_SECRET=VeryUnsecureSecretPleaseChangeMe
|
||||
|
||||
# Configuration Ngrok (pour le développement)
|
||||
NGROK_AUTHTOKEN=votre_token_ngrok
|
||||
|
||||
# Configuration MongoDB
|
||||
MONGOOSE_USER=utilisateur_mongodb
|
||||
MONGOOSE_PASSWORD=mot_de_passe_mongodb
|
||||
MONGOOSE_HOST=localhost:27017
|
||||
MONGOOSE_DATABASE=nom_base_de_donnees
|
||||
|
||||
# Configuration d'environnement
|
||||
NODE_ENV=development
|
||||
|
||||
# Configuration des locales
|
||||
DEFAULT_LOCALE=fr
|
||||
CONSOLE_LOCALE=en
|
||||
@@ -26,6 +26,16 @@ jobs:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22'
|
||||
cache: 'npm'
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Build app
|
||||
run: npm run build --if-present
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
@@ -36,20 +46,14 @@ jobs:
|
||||
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
|
||||
- name: Extract metadata
|
||||
- name: Extract metadata for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_PATH }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
# Tag avec le nom de la branche
|
||||
type=ref,event=branch
|
||||
# Tag avec le nom du tag Git
|
||||
type=ref,event=tag
|
||||
# Tag avec le SHA du commit
|
||||
type=sha,prefix={{branch}}-
|
||||
# Tag latest pour la branche master
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
labels: |
|
||||
org.opencontainers.image.title=${{ env.IMAGE_NAME }}
|
||||
org.opencontainers.image.description=Bot Discord
|
||||
|
||||
22
.github/copilot-instructions.md
vendored
Normal file
22
.github/copilot-instructions.md
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
# Instructions pour GitHub Copilot
|
||||
|
||||
## Contexte du projet
|
||||
Ce projet est un bot appelé "bot_Tamiseur" développé en TypeScript.
|
||||
Il s'agit d'un bot Discord pour des tâches de musique ou de notifications Twitch.
|
||||
Des fonctionnalités spécifiques telles que amp, freebox... ont été implémentées par moi-même pour intérager avec mon infrastructure.
|
||||
|
||||
## Conventions de code
|
||||
- Utiliser des noms de variables en anglais
|
||||
- Suivre les conventions de nommage du langage utilisé
|
||||
- Commenter le code en français
|
||||
- Utiliser des messages de commit en français
|
||||
- Priviléger l'écriture sur une seule ligne pour les fonctions simples si la longueur n'est pas excessive
|
||||
|
||||
## Préférences de développement
|
||||
- Suivre les pratiques de développement du projet, telles que du code compressé en lisibilité et optimisé par exemple
|
||||
- Comprendre les fonctionnalités du bot et les interactions avec Discord
|
||||
- Éviter le spaghetti code et ne pas ajouter de code inutile
|
||||
- Ne pas écrire énormément de code pour une seule demande, favoriser une approche simple et efficace
|
||||
- Si demandé, proposer une solution plus développée
|
||||
- Toujours repasser sur ce que tu as modifié pour retirer les erreurs potentielles
|
||||
- Ne pas proposer une solution juste pour avoir une solution
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,5 +1,4 @@
|
||||
.env
|
||||
dist/
|
||||
node_modules/
|
||||
public/cracks/*
|
||||
.env*
|
||||
.ncurc.json
|
||||
public/cracks/
|
||||
18
.vscode/launch.json
vendored
18
.vscode/launch.json
vendored
@@ -1,18 +0,0 @@
|
||||
{
|
||||
// Utilisez IntelliSense pour en savoir plus sur les attributs possibles.
|
||||
// Pointez pour afficher la description des attributs existants.
|
||||
// Pour plus d'informations, visitez : https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Nodemon",
|
||||
"skipFiles": ["<node_internals>/**"],
|
||||
"runtimeExecutable": "nodemon",
|
||||
"console": "integratedTerminal",
|
||||
"internalConsoleOptions": "neverOpen",
|
||||
"restart": true
|
||||
}
|
||||
]
|
||||
}
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"docker.commands.build": "docker build --rm -f \"${dockerfile}\" -t bot_tamiseur:latest -t localhost:5000/bot_tamiseur:latest \"${context}\" && docker push localhost:5000/bot_tamiseur:latest"
|
||||
}
|
||||
19
Dockerfile
19
Dockerfile
@@ -1,19 +0,0 @@
|
||||
FROM node:lts-alpine
|
||||
|
||||
RUN apk add --no-cache ffmpeg python3 make g++
|
||||
RUN npm install -g ts-node
|
||||
|
||||
RUN mkdir -p /usr/src/app/node_modules
|
||||
WORKDIR /usr/src/app
|
||||
COPY package*.json ./
|
||||
RUN chown -R node:node /usr/src/app
|
||||
|
||||
USER node
|
||||
#ENV NODE_ENV=production
|
||||
#RUN npm install --production --verbose
|
||||
RUN npm install --verbose
|
||||
|
||||
COPY --chown=node:node . .
|
||||
|
||||
CMD ["npm", "start"]
|
||||
#CMD ["npm", "run", "prod"]
|
||||
1
Makefile
1
Makefile
@@ -2,7 +2,6 @@
|
||||
# Build new application
|
||||
# =====================
|
||||
|
||||
|
||||
.PHONY: tag-build
|
||||
tag-build: ## DEV : Build a prod version with a timestamped tag
|
||||
@export TIMESTAMP=build_`date +"%G-%m-%d_%Hh%M"`; \
|
||||
|
||||
128
README.md
128
README.md
@@ -1,3 +1,129 @@
|
||||
# Discord
|
||||
# Bot Tamiseur
|
||||
|
||||
Bot Discord multifonction développé en TypeScript pour la musique, les notifications Twitch et l'intégration avec diverses infrastructures.
|
||||
|
||||
## Fonctionnalités
|
||||
|
||||
### 🎵 Lecteur de musique
|
||||
- Lecture de musique depuis diverses sources (YouTube, Spotify, etc.)
|
||||
- Gestion de file d'attente
|
||||
- Mode disco avec sauvegarde automatique
|
||||
- Contrôles avancés (boucle, mélange, volume)
|
||||
|
||||
### 📺 Notifications Twitch
|
||||
- Notifications automatiques de streams en direct
|
||||
- Intégration EventSub avec Twurple
|
||||
- Gestion multi-streamers
|
||||
|
||||
### 🔧 Intégrations infrastructure
|
||||
- **AMP**: Gestion des instances de serveurs de jeu
|
||||
- **Freebox**: Intégration avec l'API Freebox
|
||||
- **Crack**: Fonctionnalités spécifiques pour SalonPostam
|
||||
|
||||
## Installation
|
||||
|
||||
### Prérequis
|
||||
- Node.js 22+
|
||||
- MongoDB
|
||||
- Compte Discord Developer
|
||||
- Compte Twitch Developer (optionnel)
|
||||
|
||||
### Configuration
|
||||
|
||||
1. Cloner le repository :
|
||||
```bash
|
||||
git clone <url_repository>
|
||||
cd bot_Tamiseur
|
||||
```
|
||||
|
||||
2. Installer les dépendances :
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
3. Configurer les variables d'environnement :
|
||||
```bash
|
||||
cp .env.example .env
|
||||
# Éditer le fichier .env avec vos valeurs
|
||||
```
|
||||
|
||||
### Variables d'environnement requises
|
||||
|
||||
Voir le fichier `.env.example` pour la liste complète des variables.
|
||||
|
||||
**Variables essentielles :**
|
||||
- `DISCORD_APP_ID` et `DISCORD_TOKEN` : Configuration Discord
|
||||
- `MONGOOSE_*` : Configuration base de données MongoDB
|
||||
|
||||
**Variables optionnelles :**
|
||||
- `TWITCH_*` : Pour les notifications Twitch
|
||||
- `NGROK_AUTHTOKEN` : Pour le développement avec webhooks Twitch
|
||||
- `DEFAULT_LOCALE` : Locale par défaut (défaut: `fr`)
|
||||
- `CONSOLE_LOCALE` : Locale pour les logs console (défaut: `en`)
|
||||
|
||||
## Utilisation
|
||||
|
||||
### Développement
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Production
|
||||
```bash
|
||||
npm run build
|
||||
npm run start:prod
|
||||
```
|
||||
|
||||
### Scripts disponibles
|
||||
- `npm run dev` : Démarrage en mode développement avec watch
|
||||
- `npm run build` : Build de production
|
||||
- `npm run start:prod` : Démarrage en production
|
||||
- `npm run lint` : Vérification du code
|
||||
- `npm run lint:fix` : Correction automatique des erreurs de linting
|
||||
|
||||
## Architecture
|
||||
|
||||
Le projet suit une architecture modulaire TypeScript :
|
||||
|
||||
- `/src/commands/` : Commandes slash Discord organisées par catégories
|
||||
- `/src/events/` : Gestionnaires d'événements (Discord, MongoDB, Player)
|
||||
- `/src/buttons/` et `/src/selectmenus/` : Interactions utilisateur
|
||||
- `/src/utils/` : Modules utilitaires (player, twitch, amp, freebox)
|
||||
- `/src/types/` : Définitions TypeScript
|
||||
- `/src/schemas/` : Schémas MongoDB
|
||||
|
||||
## Déploiement
|
||||
|
||||
Le projet inclut des configurations Docker et Helm pour le déploiement en Kubernetes :
|
||||
|
||||
```bash
|
||||
# Build et tag pour déploiement
|
||||
make tag-build
|
||||
make tag-deploy
|
||||
```
|
||||
|
||||
## Développement
|
||||
|
||||
Le code suit les conventions définies dans `.github/copilot-instructions.md` :
|
||||
- Variables en anglais, commentaires en français
|
||||
- Code optimisé et lisible
|
||||
- Architecture modulaire stricte
|
||||
|
||||
### Internationalisation
|
||||
|
||||
Le bot supporte plusieurs langues via le système i18n :
|
||||
|
||||
**Fonctions de traduction :**
|
||||
- `t(locale, key, params)` : Traduction basée sur la locale utilisateur
|
||||
- `fr(key, params)` : Traduction en français
|
||||
- `en(key, params)` : Traduction en anglais
|
||||
|
||||
**Configuration des locales :**
|
||||
- `DEFAULT_LOCALE` : Locale par défaut pour les utilisateurs
|
||||
- `CONSOLE_LOCALE` : Locale pour les messages de logs console
|
||||
|
||||
**Fichiers de traduction :**
|
||||
- `/src/locales/fr.json` : Traductions françaises
|
||||
- `/src/locales/en.json` : Traductions anglaises
|
||||
|
||||
|
||||
|
||||
24
build/node.dockerfile
Normal file
24
build/node.dockerfile
Normal file
@@ -0,0 +1,24 @@
|
||||
# Starting from node
|
||||
FROM node:22-alpine
|
||||
|
||||
ENV NODE_ENV=production
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apk add --no-cache python3 make g++
|
||||
|
||||
# Copy package files and install only production dependencies
|
||||
COPY package.json package-lock.json* .
|
||||
RUN npm ci --only=production --ignore-scripts && \
|
||||
npm install bufferutil zlib-sync
|
||||
|
||||
|
||||
# Copy the builded files and the charts
|
||||
COPY ./dist/* .
|
||||
|
||||
# Set the permissions
|
||||
RUN chown -R node:node /app
|
||||
USER node
|
||||
|
||||
# Start the application
|
||||
CMD ["npm", "start"]
|
||||
@@ -3,7 +3,7 @@ deployment:
|
||||
strategy: RollingUpdate
|
||||
image:
|
||||
repository: "rgy.angels-dev.fr/prod/bot_tamiseur"
|
||||
tag: "3.0.4"
|
||||
tag: "4.0.0"
|
||||
pullPolicy: IfNotPresent
|
||||
env:
|
||||
NODE_ENV: "production"
|
||||
|
||||
103
docs/FREEBOX_LCD.md
Normal file
103
docs/FREEBOX_LCD.md
Normal file
@@ -0,0 +1,103 @@
|
||||
# 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
|
||||
@@ -1,23 +1,22 @@
|
||||
import typescriptEslint from "@typescript-eslint/eslint-plugin"
|
||||
import tsParser from "@typescript-eslint/parser"
|
||||
import { FlatCompat } from "@eslint/eslintrc"
|
||||
import { fileURLToPath } from "node:url"
|
||||
import path from "node:path"
|
||||
import js from "@eslint/js"
|
||||
import eslint from "@eslint/js"
|
||||
import tseslint from "typescript-eslint"
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = path.dirname(__filename)
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
recommendedConfig: js.configs.recommended,
|
||||
allConfig: js.configs.all
|
||||
})
|
||||
|
||||
export default [
|
||||
...compat.extends("eslint:recommended", "plugin:@typescript-eslint/recommended"),
|
||||
export default tseslint.config(
|
||||
eslint.configs.recommended,
|
||||
tseslint.configs.recommendedTypeChecked,
|
||||
{
|
||||
plugins: { "@typescript-eslint": typescriptEslint },
|
||||
languageOptions: { parser: tsParser },
|
||||
rules: { "prefer-const": "off" }
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
projectService: true,
|
||||
tsconfigRootDir: import.meta.dirname
|
||||
}
|
||||
}
|
||||
},
|
||||
tseslint.configs.strictTypeChecked,
|
||||
tseslint.configs.stylisticTypeChecked,
|
||||
{ ignores: ["dist/**", "eslint.config.mjs", "tsup.config.ts"] },
|
||||
{
|
||||
rules: { "@typescript-eslint/restrict-template-expressions": "off" },
|
||||
files: ["**/*.ts", "**/*.tsx", "**/*.mts", "**/*.cts"]
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
3664
package-lock.json
generated
3664
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
65
package.json
65
package.json
@@ -1,57 +1,52 @@
|
||||
{
|
||||
"name": "bot_tamiseur",
|
||||
"description": "Listen to music and use fun commands with your friends!",
|
||||
"version": "3.0.4",
|
||||
"version": "4.0.0",
|
||||
"author": {
|
||||
"name": "Zachary Guénot"
|
||||
},
|
||||
"main": "src/index.ts",
|
||||
"scripts": {
|
||||
"format": "prettier --write .",
|
||||
"start": "npx tsx src/index.ts",
|
||||
"dev": "nodemon -e ts src/index.ts",
|
||||
"build": "tsc",
|
||||
"lint": "eslint src/**/*.ts",
|
||||
"prod": "node dist/index.js"
|
||||
"start": "node index.js",
|
||||
"start:prod": "NODE_ENV=production node dist/index.js",
|
||||
"start:dev": "NODE_ENV=development tsx src/index.ts",
|
||||
"dev": "NODE_ENV=development tsx watch src/index.ts",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix",
|
||||
"build": "tsup",
|
||||
"updateall": "ncu -u && npm i"
|
||||
},
|
||||
"//": [
|
||||
"Garder chalk à la version 4.1.2 pour éviter un bug ESM avec la version >=5.0.0"
|
||||
"Garder parse-torrent à la version 9.1.5 pour éviter un bug exports avec la version >=10.0.0"
|
||||
],
|
||||
"dependencies": {
|
||||
"@discord-player/equalizer": "^7.0.0",
|
||||
"@discord-player/extractor": "^7.0.0",
|
||||
"@discord-player/extractor": "^7.1.0",
|
||||
"@discordjs/voice": "^0.18.0",
|
||||
"@evan/opus": "^1.0.3",
|
||||
"axios": "^1.7.9",
|
||||
"@twurple/api": "^7.3.0",
|
||||
"@twurple/auth": "^7.3.0",
|
||||
"@twurple/eventsub-http": "^7.3.0",
|
||||
"@twurple/eventsub-ngrok": "^7.3.0",
|
||||
"axios": "^1.9.0",
|
||||
"bufferutil": "^4.0.9",
|
||||
"chalk": "^4.1.2",
|
||||
"discord-player": "^7.0.0",
|
||||
"discord-player-youtubei": "^1.3.7",
|
||||
"discord.js": "^14.17.2",
|
||||
"dotenv": "^16.4.7",
|
||||
"chalk": "^5.4.1",
|
||||
"discord-player": "^7.1.0",
|
||||
"discord-player-youtubei": "^1.4.6",
|
||||
"discord.js": "^14.19.3",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"jsdom": "^25.0.1",
|
||||
"libsodium-wrappers": "^0.7.15",
|
||||
"mediaplex": "^1.0.0",
|
||||
"mongoose": "^8.9.3",
|
||||
"mongoose": "^8.15.1",
|
||||
"parse-torrent": "^9.1.5",
|
||||
"require-all": "^3.0.0",
|
||||
"rss-parser": "^3.13.0",
|
||||
"utf-8-validate": "^6.0.5",
|
||||
"websocket": "^1.0.35"
|
||||
"zlib-sync": "^0.1.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3.2.0",
|
||||
"@eslint/js": "^9.17.0",
|
||||
"@swc/core": "^1.10.4",
|
||||
"@types/node": "^22.10.5",
|
||||
"@eslint/js": "^9.28.0",
|
||||
"@types/node": "^22.15.30",
|
||||
"@types/parse-torrent": "^5.8.7",
|
||||
"@types/websocket": "^1.0.10",
|
||||
"@typescript-eslint/eslint-plugin": "^8.19.0",
|
||||
"@typescript-eslint/parser": "^8.19.0",
|
||||
"eslint": "^9.17.0",
|
||||
"nodemon": "^3.1.9",
|
||||
"prettier": "^3.4.2",
|
||||
"tsx": "^4.19.2"
|
||||
"dotenv": "^16.5.0",
|
||||
"eslint": "^9.28.0",
|
||||
"tsup": "^8.5.0",
|
||||
"tsx": "^4.19.4",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript-eslint": "^8.33.1"
|
||||
}
|
||||
}
|
||||
|
||||
11
src/buttons/freebox/index.ts
Normal file
11
src/buttons/freebox/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import * as lcd_status from "./lcd_status"
|
||||
import * as refresh_status from "./refresh_status"
|
||||
import * as test_connection from "./test_connection"
|
||||
|
||||
import type { Button } from "@/types"
|
||||
|
||||
export default [
|
||||
lcd_status,
|
||||
refresh_status,
|
||||
test_connection
|
||||
] as Button[]
|
||||
88
src/buttons/freebox/lcd_status.ts
Normal file
88
src/buttons/freebox/lcd_status.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { EmbedBuilder, MessageFlags } from "discord.js"
|
||||
import type { ButtonInteraction } from "discord.js"
|
||||
import crypto from "crypto"
|
||||
import * as Freebox from "@/utils/freebox"
|
||||
import type { APIResponseData, APIResponseDataError, GetChallenge, LcdConfig, OpenSession } from "@/types/freebox"
|
||||
import type { GuildFbx } from "@/types/schemas"
|
||||
import dbGuild from "@/schemas/guild"
|
||||
import { t } from "@/utils/i18n"
|
||||
|
||||
export const id = "freebox_lcd_status"
|
||||
export async function execute(interaction: ButtonInteraction) {
|
||||
await interaction.deferReply({ flags: MessageFlags.Ephemeral })
|
||||
|
||||
const guildProfile = await dbGuild.findOne({ guildId: interaction.guild?.id })
|
||||
if (!guildProfile) return interaction.followUp({ content: t(interaction.locale, "common.database_not_found"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
const dbData = guildProfile.get("guildFbx") as GuildFbx
|
||||
if (!dbData.enabled || !dbData.host || !dbData.appToken) {
|
||||
return interaction.followUp({ content: t(interaction.locale, "freebox.general.incomplete_configuration"), flags: MessageFlags.Ephemeral })
|
||||
}
|
||||
|
||||
try {
|
||||
// Connexion à l'API Freebox
|
||||
const challengeData = await Freebox.Login.Challenge(dbData.host) as APIResponseData<GetChallenge>
|
||||
if (!challengeData.success) return await Freebox.handleError(challengeData as APIResponseDataError, interaction, false)
|
||||
|
||||
const password = crypto.createHmac("sha1", dbData.appToken).update(challengeData.result.challenge).digest("hex")
|
||||
const sessionData = await Freebox.Login.Session(dbData.host, password) as APIResponseData<OpenSession>
|
||||
if (!sessionData.success) return await Freebox.handleError(sessionData as APIResponseDataError, interaction, false)
|
||||
|
||||
const sessionToken = sessionData.result.session_token
|
||||
if (!sessionToken) return await interaction.followUp({ content: t(interaction.locale, "freebox.auth.session_token_failed"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
// Récupération de la configuration LCD
|
||||
const lcdData = await Freebox.Get.LcdConfig(dbData.host, sessionToken) as APIResponseData<LcdConfig>
|
||||
if (!lcdData.success) return await Freebox.handleError(lcdData as APIResponseDataError, interaction, false)
|
||||
|
||||
const lcdConfig = lcdData.result
|
||||
|
||||
// Création de l'embed avec les informations LCD
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle(t(interaction.locale, "freebox.lcd.config_title"))
|
||||
.setColor(lcdConfig.led_strip_enabled ? 0x00ff00 : 0xff0000)
|
||||
.addFields(
|
||||
{
|
||||
name: t(interaction.locale, "freebox.lcd.led_strip"),
|
||||
value: lcdConfig.led_strip_enabled ? t(interaction.locale, "freebox.lcd.led_strip_on") : t(interaction.locale, "freebox.lcd.led_strip_off"),
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: t(interaction.locale, "freebox.lcd.brightness"),
|
||||
value: `${lcdConfig.brightness}%`,
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: t(interaction.locale, "freebox.lcd.orientation"),
|
||||
value: lcdConfig.orientation === 0 ? t(interaction.locale, "freebox.lcd.orientation_portrait") : t(interaction.locale, "freebox.lcd.orientation_landscape"),
|
||||
inline: true
|
||||
}
|
||||
)
|
||||
|
||||
// Informations du timer si configuré
|
||||
if (dbData.lcd) {
|
||||
const timerStatus = dbData.lcd.enabled ? t(interaction.locale, "freebox.status.timer_enabled") : t(interaction.locale, "freebox.status.timer_disabled")
|
||||
const botManaged = dbData.lcd.botId ? `<@${dbData.lcd.botId}>` : t(interaction.locale, "freebox.status.timer_no_manager")
|
||||
const morningTime = dbData.lcd.morningTime ?? t(interaction.locale, "freebox.status.timer_not_configured")
|
||||
const nightTime = dbData.lcd.nightTime ?? t(interaction.locale, "freebox.status.timer_not_configured")
|
||||
|
||||
embed.addFields({
|
||||
name: t(interaction.locale, "freebox.lcd.timer_auto"),
|
||||
value: [
|
||||
t(interaction.locale, "freebox.timer.status_field", { status: timerStatus }),
|
||||
t(interaction.locale, "freebox.timer.managed_by", { manager: botManaged }),
|
||||
t(interaction.locale, "freebox.timer.morning", { time: morningTime }),
|
||||
t(interaction.locale, "freebox.timer.night", { time: nightTime })
|
||||
].join('\n'),
|
||||
inline: false
|
||||
})
|
||||
}
|
||||
|
||||
embed.setTimestamp()
|
||||
|
||||
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)
|
||||
return interaction.followUp({ content: t(interaction.locale, "freebox.lcd.unexpected_error"), flags: MessageFlags.Ephemeral })
|
||||
}
|
||||
}
|
||||
76
src/buttons/freebox/refresh_status.ts
Normal file
76
src/buttons/freebox/refresh_status.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { EmbedBuilder, MessageFlags, ButtonBuilder, ButtonStyle, ActionRowBuilder } from "discord.js"
|
||||
import type { ButtonInteraction } from "discord.js"
|
||||
import type { GuildFbx } from "@/types/schemas"
|
||||
import dbGuild from "@/schemas/guild"
|
||||
import { t } from "@/utils/i18n"
|
||||
|
||||
export const id = "freebox_refresh_status"
|
||||
export async function execute(interaction: ButtonInteraction) {
|
||||
await interaction.deferUpdate()
|
||||
|
||||
const guildProfile = await dbGuild.findOne({ guildId: interaction.guild?.id })
|
||||
if (!guildProfile) return interaction.followUp({ content: t(interaction.locale, "common.database_not_found"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
const dbData = guildProfile.get("guildFbx") as GuildFbx
|
||||
|
||||
// Reconstruire l'embed de statut actualisé
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle(t(interaction.locale, "freebox.status.title"))
|
||||
.setColor(dbData.enabled ? 0x00ff00 : 0xff0000)
|
||||
.addFields(
|
||||
{
|
||||
name: t(interaction.locale, "freebox.status.config_section"),
|
||||
value: [
|
||||
t(interaction.locale, "freebox.status.module_field", { status: dbData.enabled ? t(interaction.locale, "freebox.common.enabled") : t(interaction.locale, "freebox.common.disabled") }),
|
||||
t(interaction.locale, "freebox.status.host_field", { value: dbData.host ? `\`${dbData.host}\`` : t(interaction.locale, "freebox.status.host_not_configured") }),
|
||||
t(interaction.locale, "freebox.status.token_field", { status: dbData.appToken ? t(interaction.locale, "freebox.status.token_configured") : t(interaction.locale, "freebox.status.token_not_configured") })
|
||||
].join('\n'),
|
||||
inline: false
|
||||
}
|
||||
)
|
||||
|
||||
// Informations LCD si disponibles
|
||||
if (dbData.lcd) {
|
||||
const lcdStatus = dbData.lcd.enabled ? t(interaction.locale, "freebox.common.enabled") : t(interaction.locale, "freebox.common.disabled")
|
||||
const botManaged = dbData.lcd.botId ? `<@${dbData.lcd.botId}>` : t(interaction.locale, "freebox.status.timer_no_manager")
|
||||
const morningTime = dbData.lcd.morningTime ?? t(interaction.locale, "freebox.status.timer_not_configured")
|
||||
const nightTime = dbData.lcd.nightTime ?? t(interaction.locale, "freebox.status.timer_not_configured")
|
||||
|
||||
embed.addFields({
|
||||
name: t(interaction.locale, "freebox.status.timer_section"),
|
||||
value: [
|
||||
t(interaction.locale, "freebox.timer.status_field", { status: lcdStatus }),
|
||||
t(interaction.locale, "freebox.timer.managed_by", { manager: botManaged }),
|
||||
t(interaction.locale, "freebox.timer.morning", { time: morningTime }),
|
||||
t(interaction.locale, "freebox.timer.night", { time: nightTime })
|
||||
].join('\n'),
|
||||
inline: false
|
||||
})
|
||||
}
|
||||
|
||||
embed.setTimestamp()
|
||||
|
||||
// Reconstruire les boutons
|
||||
const buttons = new ActionRowBuilder<ButtonBuilder>()
|
||||
.addComponents(
|
||||
new ButtonBuilder()
|
||||
.setCustomId("freebox_test_connection")
|
||||
.setLabel(t(interaction.locale, "freebox.buttons.test_connection"))
|
||||
.setEmoji("🔌")
|
||||
.setStyle(ButtonStyle.Primary)
|
||||
.setDisabled(!dbData.appToken),
|
||||
new ButtonBuilder()
|
||||
.setCustomId("freebox_lcd_status")
|
||||
.setLabel(t(interaction.locale, "freebox.buttons.lcd_status"))
|
||||
.setEmoji("💡")
|
||||
.setStyle(ButtonStyle.Secondary)
|
||||
.setDisabled(!dbData.appToken),
|
||||
new ButtonBuilder()
|
||||
.setCustomId("freebox_refresh_status")
|
||||
.setLabel(t(interaction.locale, "freebox.buttons.refresh_status"))
|
||||
.setEmoji("🔄")
|
||||
.setStyle(ButtonStyle.Success)
|
||||
)
|
||||
|
||||
return interaction.editReply({ embeds: [embed], components: [buttons] })
|
||||
}
|
||||
71
src/buttons/freebox/test_connection.ts
Normal file
71
src/buttons/freebox/test_connection.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { EmbedBuilder, MessageFlags } from "discord.js"
|
||||
import type { ButtonInteraction } from "discord.js"
|
||||
import crypto from "crypto"
|
||||
import * as Freebox from "@/utils/freebox"
|
||||
import type { APIResponseData, APIResponseDataError, APIResponseDataVersion, ConnectionStatus, GetChallenge, OpenSession } from "@/types/freebox"
|
||||
import type { GuildFbx } from "@/types/schemas"
|
||||
import dbGuild from "@/schemas/guild"
|
||||
import { t } from "@/utils/i18n"
|
||||
|
||||
export const id = "freebox_test_connection"
|
||||
export async function execute(interaction: ButtonInteraction) {
|
||||
await interaction.deferReply({ flags: MessageFlags.Ephemeral })
|
||||
|
||||
const guildProfile = await dbGuild.findOne({ guildId: interaction.guild?.id })
|
||||
if (!guildProfile) return interaction.followUp({ content: t(interaction.locale, "common.database_not_found"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
const dbData = guildProfile.get("guildFbx") as GuildFbx
|
||||
if (!dbData.enabled || !dbData.host || !dbData.appToken) {
|
||||
return interaction.followUp({ content: t(interaction.locale, "freebox.general.incomplete_configuration"), flags: MessageFlags.Ephemeral })
|
||||
}
|
||||
|
||||
try {
|
||||
// Test de la version API
|
||||
const versionData = await Freebox.Core.Version(dbData.host) as APIResponseDataVersion
|
||||
|
||||
// Test d'authentification
|
||||
const challengeData = await Freebox.Login.Challenge(dbData.host) as APIResponseData<GetChallenge>
|
||||
if (!challengeData.success) return await Freebox.handleError(challengeData as APIResponseDataError, interaction, false)
|
||||
|
||||
const password = crypto.createHmac("sha1", dbData.appToken).update(challengeData.result.challenge).digest("hex")
|
||||
const sessionData = await Freebox.Login.Session(dbData.host, password) as APIResponseData<OpenSession>
|
||||
if (!sessionData.success) return await Freebox.handleError(sessionData as APIResponseDataError, interaction, false)
|
||||
|
||||
const sessionToken = sessionData.result.session_token
|
||||
if (!sessionToken) return await interaction.followUp({ content: t(interaction.locale, "freebox.auth.session_token_failed"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
// Test de la connexion
|
||||
const connectionData = await Freebox.Get.Connection(dbData.host, sessionToken) as APIResponseData<ConnectionStatus>
|
||||
if (!connectionData.success) return await Freebox.handleError(connectionData as APIResponseDataError, interaction, false)
|
||||
|
||||
// Création de l'embed de succès
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle(t(interaction.locale, "freebox.test.connection_success_title"))
|
||||
.setColor(0x00ff00)
|
||||
.addFields(
|
||||
{
|
||||
name: t(interaction.locale, "freebox.test.api_field"),
|
||||
value: t(interaction.locale, "freebox.test.api_version", { version: versionData.api_version }),
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: t(interaction.locale, "freebox.test.auth_field"),
|
||||
value: t(interaction.locale, "freebox.test.token_valid"),
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: t(interaction.locale, "freebox.test.connection_field"),
|
||||
value: connectionData.result.state === "up" ?
|
||||
t(interaction.locale, "freebox.test.connection_active") :
|
||||
t(interaction.locale, "freebox.test.connection_inactive"),
|
||||
inline: true
|
||||
}
|
||||
)
|
||||
.setTimestamp()
|
||||
|
||||
return await interaction.followUp({ embeds: [embed], flags: MessageFlags.Ephemeral })
|
||||
} catch (error) {
|
||||
console.error("Erreur lors du test de connexion Freebox:", error)
|
||||
return interaction.followUp({ content: t(interaction.locale, "freebox.test.connection_error"), flags: MessageFlags.Ephemeral })
|
||||
}
|
||||
}
|
||||
26
src/buttons/index.ts
Normal file
26
src/buttons/index.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import freebox from "./freebox"
|
||||
import player from "./player"
|
||||
import twitch from "./twitch"
|
||||
|
||||
import type { Button, ButtonFolder } from "@/types"
|
||||
|
||||
export const buttonFolders = [
|
||||
{
|
||||
name: "freebox",
|
||||
commands: freebox
|
||||
},
|
||||
{
|
||||
name: "player",
|
||||
commands: player
|
||||
},
|
||||
{
|
||||
name: "twitch",
|
||||
commands: twitch
|
||||
}
|
||||
] as ButtonFolder[]
|
||||
|
||||
export default [
|
||||
...freebox,
|
||||
...player,
|
||||
...twitch
|
||||
] as Button[]
|
||||
@@ -1,16 +0,0 @@
|
||||
import { ButtonInteraction } from 'discord.js'
|
||||
import { useQueue } from 'discord-player'
|
||||
|
||||
export default {
|
||||
id: 'loop',
|
||||
async execute(interaction: ButtonInteraction) {
|
||||
let guild = interaction.guild
|
||||
if (!guild) return
|
||||
let queue = useQueue(guild.id)
|
||||
if (!queue) return
|
||||
|
||||
let loop = queue.repeatMode === 0 ? 1 : queue.repeatMode === 1 ? 2 : queue.repeatMode === 2 ? 3 : 0
|
||||
await queue.setRepeatMode(loop)
|
||||
await interaction.followUp({ content:`Boucle ${loop === 0 ? 'désactivée' : loop === 1 ? 'en mode Titre' : loop === 2 ? 'en mode File d\'Attente' : 'en autoplay'}.`, ephemeral: true })
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import { ButtonInteraction } from 'discord.js'
|
||||
import { useQueue } from 'discord-player'
|
||||
|
||||
export default {
|
||||
id: 'pause',
|
||||
async execute(interaction: ButtonInteraction) {
|
||||
let guild = interaction.guild
|
||||
if (!guild) return
|
||||
let queue = useQueue(guild.id)
|
||||
if (!queue) return
|
||||
|
||||
await queue.node.setPaused(!queue.node.isPaused())
|
||||
return interaction.followUp({ content: 'Musique mise en pause !', ephemeral: true })
|
||||
}
|
||||
}
|
||||
15
src/buttons/player/disco_channel.ts
Normal file
15
src/buttons/player/disco_channel.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { MessageFlags, ChannelType, ActionRowBuilder, ChannelSelectMenuBuilder } from "discord.js"
|
||||
import type { ButtonInteraction } from "discord.js"
|
||||
import { t } from "@/utils/i18n"
|
||||
|
||||
export const id = "player_disco_channel"
|
||||
export async function execute(interaction: ButtonInteraction) {
|
||||
const channelSelect = new ChannelSelectMenuBuilder()
|
||||
.setCustomId("player_disco_channel")
|
||||
.setPlaceholder(t(interaction.locale, "selectmenus.placeholders.select_channel_disco"))
|
||||
.addChannelTypes(ChannelType.GuildText, ChannelType.GuildAnnouncement)
|
||||
|
||||
const row = new ActionRowBuilder<ChannelSelectMenuBuilder>().addComponents(channelSelect)
|
||||
|
||||
return interaction.reply({ content: t(interaction.locale, "player.disco.select_channel"), components: [row], flags: MessageFlags.Ephemeral })
|
||||
}
|
||||
24
src/buttons/player/disco_disable.ts
Normal file
24
src/buttons/player/disco_disable.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { MessageFlags } from "discord.js"
|
||||
import type { ButtonInteraction } from "discord.js"
|
||||
import { generateDiscoEmbed } from "@/utils/player"
|
||||
import type { Disco } from "@/types/schemas"
|
||||
import dbGuild from "@/schemas/guild"
|
||||
import { t } from "@/utils/i18n"
|
||||
|
||||
export const id = "player_disco_disable"
|
||||
export async function execute(interaction: ButtonInteraction) {
|
||||
const guildProfile = await dbGuild.findOne({ guildId: interaction.guild?.id })
|
||||
if (!guildProfile) return interaction.reply({ content: t(interaction.locale, "common.database_not_found"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
const dbData = guildProfile.get("guildPlayer.disco") as Disco
|
||||
dbData.enabled = false
|
||||
|
||||
guildProfile.set("guildPlayer.disco", dbData)
|
||||
guildProfile.markModified("guildPlayer.disco")
|
||||
await guildProfile.save().catch(console.error)
|
||||
|
||||
// Utiliser la fonction utilitaire pour générer l'embed et les composants mis à jour
|
||||
const { embed, components } = generateDiscoEmbed(dbData, interaction.client, interaction.guild?.id ?? "", interaction.locale)
|
||||
|
||||
return interaction.update({ embeds: [embed], components: components })
|
||||
}
|
||||
31
src/buttons/player/disco_enable.ts
Normal file
31
src/buttons/player/disco_enable.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { MessageFlags } from "discord.js"
|
||||
import type { ButtonInteraction } from "discord.js"
|
||||
import { generateDiscoEmbed } from "@/utils/player"
|
||||
import type { Disco } from "@/types/schemas"
|
||||
import dbGuild from "@/schemas/guild"
|
||||
import { t } from "@/utils/i18n"
|
||||
|
||||
export const id = "player_disco_enable"
|
||||
export async function execute(interaction: ButtonInteraction) {
|
||||
const guildProfile = await dbGuild.findOne({ guildId: interaction.guild?.id })
|
||||
if (!guildProfile) return interaction.reply({ content: t(interaction.locale, "common.database_not_found"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
const dbData = guildProfile.get("guildPlayer.disco") as Disco
|
||||
|
||||
// Vérifier qu'un canal est configuré avant d'activer
|
||||
if (!dbData.channelId) return interaction.reply({
|
||||
content: t(interaction.locale, "player.disco.configure_channel_first"),
|
||||
flags: MessageFlags.Ephemeral
|
||||
})
|
||||
|
||||
dbData.enabled = true
|
||||
|
||||
guildProfile.set("guildPlayer.disco", dbData)
|
||||
guildProfile.markModified("guildPlayer.disco")
|
||||
await guildProfile.save().catch(console.error)
|
||||
|
||||
// Utiliser la fonction utilitaire pour générer l'embed et les composants mis à jour
|
||||
const { embed, components } = generateDiscoEmbed(dbData, interaction.client, interaction.guild?.id ?? "", interaction.locale)
|
||||
|
||||
return interaction.update({ embeds: [embed], components: components })
|
||||
}
|
||||
29
src/buttons/player/index.ts
Normal file
29
src/buttons/player/index.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import * as disco_disable from "./disco_disable"
|
||||
import * as disco_enable from "./disco_enable"
|
||||
import * as disco_channel from "./disco_channel"
|
||||
import * as loop from "./loop"
|
||||
import * as pause from "./pause"
|
||||
import * as previous from "./previous"
|
||||
import * as resume from "./resume"
|
||||
import * as shuffle from "./shuffle"
|
||||
import * as skip from "./skip"
|
||||
import * as stop from "./stop"
|
||||
import * as volume_down from "./volume_down"
|
||||
import * as volume_up from "./volume_up"
|
||||
|
||||
import type { Button } from "@/types"
|
||||
|
||||
export default [
|
||||
disco_channel,
|
||||
disco_disable,
|
||||
disco_enable,
|
||||
loop,
|
||||
pause,
|
||||
previous,
|
||||
resume,
|
||||
shuffle,
|
||||
skip,
|
||||
stop,
|
||||
volume_down,
|
||||
volume_up
|
||||
] as Button[]
|
||||
22
src/buttons/player/loop.ts
Normal file
22
src/buttons/player/loop.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { MessageFlags } from "discord.js"
|
||||
import type { ButtonInteraction } from "discord.js"
|
||||
import { useQueue } from "discord-player"
|
||||
import { t } from "@/utils/i18n"
|
||||
|
||||
export const id = "player_loop"
|
||||
export async function execute(interaction: ButtonInteraction) {
|
||||
const queue = useQueue(interaction.guild?.id ?? "")
|
||||
if (!queue) return
|
||||
|
||||
const loop = queue.repeatMode === 0 ? 1 : queue.repeatMode === 1 ? 2 : queue.repeatMode === 2 ? 3 : 0
|
||||
queue.setRepeatMode(loop)
|
||||
|
||||
const loopModes = {
|
||||
0: t(interaction.locale, "player.loop_off"),
|
||||
1: t(interaction.locale, "player.loop_track"),
|
||||
2: t(interaction.locale, "player.loop_queue"),
|
||||
3: t(interaction.locale, "player.loop_autoplay")
|
||||
}
|
||||
|
||||
return interaction.followUp({ content: loopModes[loop], flags: MessageFlags.Ephemeral })
|
||||
}
|
||||
13
src/buttons/player/pause.ts
Normal file
13
src/buttons/player/pause.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { MessageFlags } from "discord.js"
|
||||
import type { ButtonInteraction } from "discord.js"
|
||||
import { useQueue } from "discord-player"
|
||||
import { t } from "@/utils/i18n"
|
||||
|
||||
export const id = "player_pause"
|
||||
export async function execute(interaction: ButtonInteraction) {
|
||||
const queue = useQueue(interaction.guild?.id ?? "")
|
||||
if (!queue) return
|
||||
|
||||
queue.node.setPaused(!queue.node.isPaused())
|
||||
return interaction.followUp({ content: t(interaction.locale, "player.paused"), flags: MessageFlags.Ephemeral })
|
||||
}
|
||||
13
src/buttons/player/previous.ts
Normal file
13
src/buttons/player/previous.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { MessageFlags } from "discord.js"
|
||||
import type { ButtonInteraction } from "discord.js"
|
||||
import { useHistory } from "discord-player"
|
||||
import { t } from "@/utils/i18n"
|
||||
|
||||
export const id = "player_previous"
|
||||
export async function execute(interaction: ButtonInteraction) {
|
||||
const history = useHistory(interaction.guild?.id ?? "")
|
||||
if (!history) return
|
||||
|
||||
await history.previous()
|
||||
return interaction.followUp({ content: t(interaction.locale, "player.previous_played"), flags: MessageFlags.Ephemeral })
|
||||
}
|
||||
13
src/buttons/player/resume.ts
Normal file
13
src/buttons/player/resume.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { MessageFlags } from "discord.js"
|
||||
import type { ButtonInteraction } from "discord.js"
|
||||
import { useQueue } from "discord-player"
|
||||
import { t } from "@/utils/i18n"
|
||||
|
||||
export const id = "player_resume"
|
||||
export async function execute(interaction: ButtonInteraction) {
|
||||
const queue = useQueue(interaction.guild?.id ?? "")
|
||||
if (!queue) return
|
||||
|
||||
queue.node.setPaused(!queue.node.isPaused())
|
||||
return interaction.followUp({ content: t(interaction.locale, "player.resumed"), flags: MessageFlags.Ephemeral })
|
||||
}
|
||||
13
src/buttons/player/shuffle.ts
Normal file
13
src/buttons/player/shuffle.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { MessageFlags } from "discord.js"
|
||||
import type { ButtonInteraction } from "discord.js"
|
||||
import { useQueue } from "discord-player"
|
||||
import { t } from "@/utils/i18n"
|
||||
|
||||
export const id = "player_shuffle"
|
||||
export async function execute(interaction: ButtonInteraction) {
|
||||
const queue = useQueue(interaction.guild?.id ?? "")
|
||||
if (!queue) return
|
||||
|
||||
queue.tracks.shuffle()
|
||||
return interaction.followUp({ content: t(interaction.locale, "player.shuffled"), flags: MessageFlags.Ephemeral })
|
||||
}
|
||||
13
src/buttons/player/skip.ts
Normal file
13
src/buttons/player/skip.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { MessageFlags } from "discord.js"
|
||||
import type { ButtonInteraction } from "discord.js"
|
||||
import { useQueue } from "discord-player"
|
||||
import { t } from "@/utils/i18n"
|
||||
|
||||
export const id = "player_skip"
|
||||
export async function execute(interaction: ButtonInteraction) {
|
||||
const queue = useQueue(interaction.guild?.id ?? "")
|
||||
if (!queue) return
|
||||
|
||||
queue.node.skip()
|
||||
return interaction.followUp({ content: t(interaction.locale, "player.skipped"), flags: MessageFlags.Ephemeral })
|
||||
}
|
||||
16
src/buttons/player/stop.ts
Normal file
16
src/buttons/player/stop.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { MessageFlags } from "discord.js"
|
||||
import type { ButtonInteraction } from "discord.js"
|
||||
import { useQueue } from "discord-player"
|
||||
import { stopProgressSaving } from "@/utils/player"
|
||||
import { t } from "@/utils/i18n"
|
||||
|
||||
export const id = "player_stop"
|
||||
export async function execute(interaction: ButtonInteraction) {
|
||||
await stopProgressSaving(interaction.guild?.id ?? "", interaction.client.user.id)
|
||||
|
||||
const queue = useQueue(interaction.guild?.id ?? "")
|
||||
if (!queue) return
|
||||
|
||||
queue.delete()
|
||||
return interaction.followUp({ content: t(interaction.locale, "player.stopped"), flags: MessageFlags.Ephemeral })
|
||||
}
|
||||
14
src/buttons/player/volume_down.ts
Normal file
14
src/buttons/player/volume_down.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { MessageFlags } from "discord.js"
|
||||
import type { ButtonInteraction } from "discord.js"
|
||||
import { useQueue } from "discord-player"
|
||||
import { t } from "@/utils/i18n"
|
||||
|
||||
export const id = "player_volume_down"
|
||||
export async function execute(interaction: ButtonInteraction) {
|
||||
const queue = useQueue(interaction.guild?.id ?? "")
|
||||
if (!queue) return
|
||||
|
||||
const volume = queue.node.volume - 10
|
||||
queue.node.setVolume(volume)
|
||||
return interaction.followUp({ content: t(interaction.locale, "player.volume_changed_down", { volume: volume.toString() }), flags: MessageFlags.Ephemeral })
|
||||
}
|
||||
14
src/buttons/player/volume_up.ts
Normal file
14
src/buttons/player/volume_up.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { MessageFlags } from "discord.js"
|
||||
import type { ButtonInteraction } from "discord.js"
|
||||
import { useQueue } from "discord-player"
|
||||
import { t } from "@/utils/i18n"
|
||||
|
||||
export const id = "player_volume_up"
|
||||
export async function execute(interaction: ButtonInteraction) {
|
||||
const queue = useQueue(interaction.guild?.id ?? "")
|
||||
if (!queue) return
|
||||
|
||||
const volume = queue.node.volume + 10
|
||||
queue.node.setVolume(volume)
|
||||
return interaction.followUp({ content: t(interaction.locale, "player.volume_changed", { volume: volume.toString() }), flags: MessageFlags.Ephemeral })
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import { ButtonInteraction } from 'discord.js'
|
||||
import { useHistory } from 'discord-player'
|
||||
|
||||
export default {
|
||||
id: 'previous',
|
||||
async execute(interaction: ButtonInteraction) {
|
||||
let guild = interaction.guild
|
||||
if (!guild) return
|
||||
let history = useHistory(guild.id)
|
||||
if (!history) return
|
||||
|
||||
await history.previous()
|
||||
return interaction.followUp({ content: 'Musique précédente jouée !', ephemeral: true })
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import { ButtonInteraction } from 'discord.js'
|
||||
import { useQueue } from 'discord-player'
|
||||
|
||||
export default {
|
||||
id: 'resume',
|
||||
async execute(interaction: ButtonInteraction) {
|
||||
let guild = interaction.guild
|
||||
if (!guild) return
|
||||
let queue = useQueue(guild.id)
|
||||
if (!queue) return
|
||||
|
||||
await queue.node.setPaused(!queue.node.isPaused())
|
||||
return interaction.followUp({ content: 'Musique reprise !', ephemeral: true })
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import { ButtonInteraction } from 'discord.js'
|
||||
import { useQueue } from 'discord-player'
|
||||
|
||||
export default {
|
||||
id: 'shuffle',
|
||||
async execute(interaction: ButtonInteraction) {
|
||||
let guild = interaction.guild
|
||||
if (!guild) return
|
||||
let queue = useQueue(guild.id)
|
||||
if (!queue) return
|
||||
|
||||
await queue.tracks.shuffle()
|
||||
return interaction.followUp({ content: 'File d\'attente mélangée !', ephemeral: true })
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import { ButtonInteraction } from 'discord.js'
|
||||
import { useQueue } from 'discord-player'
|
||||
|
||||
export default {
|
||||
id: 'skip',
|
||||
async execute(interaction: ButtonInteraction) {
|
||||
let guild = interaction.guild
|
||||
if (!guild) return
|
||||
let queue = useQueue(guild.id)
|
||||
if (!queue) return
|
||||
|
||||
await queue.node.skip()
|
||||
return interaction.followUp({ content: 'Musique passée !', ephemeral: true })
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import { ButtonInteraction } from 'discord.js'
|
||||
import { useQueue } from 'discord-player'
|
||||
|
||||
export default {
|
||||
id: 'stop',
|
||||
async execute(interaction: ButtonInteraction) {
|
||||
let guild = interaction.guild
|
||||
if (!guild) return
|
||||
let queue = useQueue(guild.id)
|
||||
if (!queue) return
|
||||
|
||||
await queue.delete()
|
||||
return interaction.followUp({ content: 'Musique arrêtée !', ephemeral: true })
|
||||
}
|
||||
}
|
||||
19
src/buttons/twitch/channel.ts
Normal file
19
src/buttons/twitch/channel.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { MessageFlags, ChannelType, ActionRowBuilder, ChannelSelectMenuBuilder } from "discord.js"
|
||||
import type { ButtonInteraction } from "discord.js"
|
||||
import { t } from "@/utils/i18n"
|
||||
|
||||
export const id = "twitch_channel"
|
||||
export async function execute(interaction: ButtonInteraction) {
|
||||
const channelSelect = new ChannelSelectMenuBuilder()
|
||||
.setCustomId("twitch_channel")
|
||||
.setPlaceholder(t(interaction.locale, "twitch.select_notification_channel_placeholder"))
|
||||
.addChannelTypes(ChannelType.GuildText, ChannelType.GuildAnnouncement)
|
||||
|
||||
const row = new ActionRowBuilder<ChannelSelectMenuBuilder>().addComponents(channelSelect)
|
||||
|
||||
return interaction.reply({
|
||||
content: t(interaction.locale, "twitch.select_notification_channel"),
|
||||
components: [row],
|
||||
flags: MessageFlags.Ephemeral
|
||||
})
|
||||
}
|
||||
28
src/buttons/twitch/disable.ts
Normal file
28
src/buttons/twitch/disable.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { MessageFlags } from "discord.js"
|
||||
import type { ButtonInteraction } from "discord.js"
|
||||
import { generateTwitchEmbed } from "@/utils/twitch"
|
||||
import type { GuildTwitch } from "@/types/schemas"
|
||||
import dbGuild from "@/schemas/guild"
|
||||
import { t } from "@/utils/i18n"
|
||||
|
||||
export const id = "twitch_disable"
|
||||
export async function execute(interaction: ButtonInteraction) {
|
||||
const guildProfile = await dbGuild.findOne({ guildId: interaction.guild?.id })
|
||||
if (!guildProfile) return interaction.reply({ content: t(interaction.locale, "common.database_not_found"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
const dbData = guildProfile.get("guildTwitch") as GuildTwitch
|
||||
dbData.enabled = false
|
||||
dbData.botId = ""
|
||||
|
||||
guildProfile.set("guildTwitch", dbData)
|
||||
guildProfile.markModified("guildTwitch")
|
||||
await guildProfile.save().catch(console.error)
|
||||
|
||||
// Utiliser la fonction utilitaire pour générer l'embed et les composants mis à jour
|
||||
const { embed, components } = generateTwitchEmbed(dbData, interaction.client, interaction.guild?.id ?? "", interaction.locale)
|
||||
|
||||
return interaction.update({
|
||||
embeds: [embed],
|
||||
components: components
|
||||
})
|
||||
}
|
||||
28
src/buttons/twitch/enable.ts
Normal file
28
src/buttons/twitch/enable.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { MessageFlags } from "discord.js"
|
||||
import type { ButtonInteraction } from "discord.js"
|
||||
import { generateTwitchEmbed } from "@/utils/twitch"
|
||||
import type { GuildTwitch } from "@/types/schemas"
|
||||
import dbGuild from "@/schemas/guild"
|
||||
import { t } from "@/utils/i18n"
|
||||
|
||||
export const id = "twitch_enable"
|
||||
export async function execute(interaction: ButtonInteraction) {
|
||||
const guildProfile = await dbGuild.findOne({ guildId: interaction.guild?.id })
|
||||
if (!guildProfile) return interaction.reply({ content: t(interaction.locale, "common.database_not_found"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
const dbData = guildProfile.get("guildTwitch") as GuildTwitch
|
||||
dbData.enabled = true
|
||||
dbData.botId = interaction.client.user.id
|
||||
|
||||
guildProfile.set("guildTwitch", dbData)
|
||||
guildProfile.markModified("guildTwitch")
|
||||
await guildProfile.save().catch(console.error)
|
||||
|
||||
// Utiliser la fonction utilitaire pour générer l'embed et les composants mis à jour
|
||||
const { embed, components } = generateTwitchEmbed(dbData, interaction.client, interaction.guild?.id ?? "", interaction.locale)
|
||||
|
||||
return interaction.update({
|
||||
embeds: [embed],
|
||||
components: components
|
||||
})
|
||||
}
|
||||
17
src/buttons/twitch/index.ts
Normal file
17
src/buttons/twitch/index.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import * as twitch_disable from "./disable"
|
||||
import * as twitch_enable from "./enable"
|
||||
import * as twitch_set_channel from "./channel"
|
||||
import * as twitch_list_streamers from "./streamer_list"
|
||||
import * as twitch_add_streamer from "./streamer_add"
|
||||
import * as twitch_remove_streamer from "./streamer_remove"
|
||||
|
||||
import type { Button } from "@/types"
|
||||
|
||||
export default [
|
||||
twitch_disable,
|
||||
twitch_enable,
|
||||
twitch_set_channel,
|
||||
twitch_list_streamers,
|
||||
twitch_add_streamer,
|
||||
twitch_remove_streamer
|
||||
] as Button[]
|
||||
20
src/buttons/twitch/streamer_add.ts
Normal file
20
src/buttons/twitch/streamer_add.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { MessageFlags } from "discord.js"
|
||||
import type { ButtonInteraction } from "discord.js"
|
||||
import type { GuildTwitch } from "@/types/schemas"
|
||||
import dbGuild from "@/schemas/guild"
|
||||
import { t } from "@/utils/i18n"
|
||||
|
||||
export const id = "twitch_streamer_add"
|
||||
export async function execute(interaction: ButtonInteraction) {
|
||||
const guildProfile = await dbGuild.findOne({ guildId: interaction.guild?.id })
|
||||
if (!guildProfile) return interaction.reply({ content: t(interaction.locale, "common.database_not_found"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
const dbData = guildProfile.get("guildTwitch") as GuildTwitch
|
||||
if (!dbData.enabled) return interaction.reply({ content: t(interaction.locale, "twitch.module_disabled"), flags: MessageFlags.Ephemeral })
|
||||
if (!dbData.channelId) return interaction.reply({ content: t(interaction.locale, "twitch.configure_channel_first"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
return interaction.reply({
|
||||
content: t(interaction.locale, "twitch.add_streamer_command"),
|
||||
flags: MessageFlags.Ephemeral
|
||||
})
|
||||
}
|
||||
51
src/buttons/twitch/streamer_list.ts
Normal file
51
src/buttons/twitch/streamer_list.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { MessageFlags, EmbedBuilder } from "discord.js"
|
||||
import type { ButtonInteraction } from "discord.js"
|
||||
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"
|
||||
|
||||
export const id = "twitch_streamer_list"
|
||||
export async function execute(interaction: ButtonInteraction) {
|
||||
const guildProfile = await dbGuild.findOne({ guildId: interaction.guild?.id })
|
||||
if (!guildProfile) return interaction.reply({ content: t(interaction.locale, "common.database_not_found"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
const dbData = guildProfile.get("guildTwitch") as GuildTwitch
|
||||
if (!dbData.enabled) return interaction.reply({ content: t(interaction.locale, "twitch.module_disabled"), flags: MessageFlags.Ephemeral })
|
||||
if (!dbData.streamers.length) {
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle(t(interaction.locale, "twitch.list.title"))
|
||||
.setDescription(t(interaction.locale, "twitch.list.empty_description"))
|
||||
.setColor(0x808080)
|
||||
.setTimestamp()
|
||||
|
||||
return interaction.reply({ embeds: [embed], flags: MessageFlags.Ephemeral })
|
||||
}
|
||||
|
||||
const streamers = [] as string[]
|
||||
await Promise.all(dbData.streamers.map(async (streamer, index) => {
|
||||
try {
|
||||
const user = await twitchClient.users.getUserById(streamer.twitchUserId)
|
||||
if (user) {
|
||||
const discordUser = streamer.discordUserId ? `<@${streamer.discordUserId}>` : t(interaction.locale, "twitch.list.discord_not_associated")
|
||||
streamers.push(`**${index + 1}.** ${user.displayName}\n└ Discord: ${discordUser}\n└ ID: \`${streamer.twitchUserId}\``)
|
||||
} else {
|
||||
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)
|
||||
streamers.push(`**${index + 1}.** ${t(interaction.locale, "twitch.list.fetch_error")}\n└ ID: \`${streamer.twitchUserId}\``)
|
||||
}
|
||||
}))
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle(t(interaction.locale, "twitch.list.title"))
|
||||
.setDescription(streamers.join("\n\n"))
|
||||
.setColor(0x9146FF)
|
||||
.setFooter({ text: t(interaction.locale, "twitch.list.footer", { count: streamers.length.toString() }) })
|
||||
.setTimestamp()
|
||||
|
||||
return interaction.reply({ embeds: [embed], flags: MessageFlags.Ephemeral })
|
||||
}
|
||||
46
src/buttons/twitch/streamer_remove.ts
Normal file
46
src/buttons/twitch/streamer_remove.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { MessageFlags, ActionRowBuilder, StringSelectMenuBuilder } from "discord.js"
|
||||
import type { ButtonInteraction } from "discord.js"
|
||||
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"
|
||||
|
||||
export const id = "twitch_streamer_remove"
|
||||
export async function execute(interaction: ButtonInteraction) {
|
||||
const guildProfile = await dbGuild.findOne({ guildId: interaction.guild?.id })
|
||||
if (!guildProfile) return interaction.reply({ content: t(interaction.locale, "common.database_not_found"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
const dbData = guildProfile.get("guildTwitch") as GuildTwitch
|
||||
if (!dbData.enabled) return interaction.reply({ content: t(interaction.locale, "twitch.module_disabled"), flags: MessageFlags.Ephemeral })
|
||||
if (!dbData.streamers.length) return interaction.reply({ content: t(interaction.locale, "twitch.no_streamers_list"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
// Créer la liste des streamers dans un menu de sélection
|
||||
const options = await Promise.all(dbData.streamers.map(async (streamer) => {
|
||||
try {
|
||||
const user = await twitchClient.users.getUserById(streamer.twitchUserId)
|
||||
return {
|
||||
label: user ? user.displayName : `ID: ${streamer.twitchUserId}`,
|
||||
value: streamer.twitchUserId,
|
||||
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)
|
||||
return {
|
||||
label: `ID: ${streamer.twitchUserId}`,
|
||||
value: streamer.twitchUserId,
|
||||
description: t(interaction.locale, "twitch.fetch_error")
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
const selectMenu = new StringSelectMenuBuilder()
|
||||
.setCustomId("twitch_streamer_remove")
|
||||
.setPlaceholder(t(interaction.locale, "twitch.select_streamer_to_remove"))
|
||||
.addOptions(options.slice(0, 25)) // Discord limite à 25 options
|
||||
|
||||
const row = new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(selectMenu)
|
||||
|
||||
return interaction.reply({ content: t(interaction.locale, "twitch.select_streamer_prompt"), components: [row], flags: MessageFlags.Ephemeral })
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import { ButtonInteraction } from 'discord.js'
|
||||
import { useQueue } from 'discord-player'
|
||||
|
||||
export default {
|
||||
id: 'volume_down',
|
||||
async execute(interaction: ButtonInteraction) {
|
||||
let guild = interaction.guild
|
||||
if (!guild) return
|
||||
let queue = useQueue(guild.id)
|
||||
if (!queue) return
|
||||
|
||||
let volume = queue.node.volume - 10
|
||||
await queue.node.setVolume(volume)
|
||||
return interaction.followUp({ content: `🔉 | Volume modifié à ${volume}% !`, ephemeral: true })
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import { ButtonInteraction } from 'discord.js'
|
||||
import { useQueue } from 'discord-player'
|
||||
|
||||
export default {
|
||||
id: 'volume_up',
|
||||
async execute(interaction: ButtonInteraction) {
|
||||
let guild = interaction.guild
|
||||
if (!guild) return
|
||||
let queue = useQueue(guild.id)
|
||||
if (!queue) return
|
||||
|
||||
let volume = queue.node.volume + 10
|
||||
await queue.node.setVolume(volume)
|
||||
return interaction.followUp({ content: `🔊 | Volume modifié à ${volume}% !`, ephemeral: true })
|
||||
}
|
||||
}
|
||||
@@ -1,210 +1,241 @@
|
||||
import { SlashCommandBuilder, ChatInputCommandInteraction, AutocompleteInteraction, ApplicationCommandOptionChoiceData, EmbedBuilder, inlineCode, PermissionFlagsBits } from 'discord.js'
|
||||
import dbGuild from '../../schemas/guild'
|
||||
import * as AMP from '../../utils/amp'
|
||||
|
||||
interface InstanceFields {
|
||||
name: string
|
||||
value: string
|
||||
inline: boolean
|
||||
}
|
||||
interface InstanceResult {
|
||||
status: string
|
||||
data: [
|
||||
Host
|
||||
]
|
||||
}
|
||||
interface Host {
|
||||
AvailableInstances: Instance[]
|
||||
FriendlyName: string
|
||||
}
|
||||
interface Instance {
|
||||
InstanceID: string
|
||||
FriendlyName: string
|
||||
Running: boolean
|
||||
Module: string
|
||||
Port: number
|
||||
}
|
||||
interface FailMsgData {
|
||||
Title: string
|
||||
Message: string
|
||||
}
|
||||
interface ErrorMsgData {
|
||||
error_code: string
|
||||
}
|
||||
|
||||
function failMsg(data: FailMsgData) { return `La commande a échouée !\n${inlineCode(`${data.Title}: ${data.Message}`)}` }
|
||||
function errorMsg(data: ErrorMsgData) { return `Y'a eu une erreur !\n${inlineCode(`${data.error_code}`)}` }
|
||||
|
||||
export default {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('amp')
|
||||
.setDescription('Accède à mon panel de jeu AMP !')
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator)
|
||||
.addSubcommand(subcommand => subcommand.setName('login').setDescription("Connectez-vous avant d'effectuer une autre commande !")
|
||||
.addStringOption(option => option.setName('username').setDescription("Nom d'Utilisateur").setRequired(true))
|
||||
.addStringOption(option => option.setName('password').setDescription('Mot de Passe').setRequired(true))
|
||||
.addBooleanOption(option => option.setName('remember').setDescription('Mémoriser les identifiants').setRequired(true))
|
||||
.addStringOption(option => option.setName('otp').setDescription('Code de double authentification')))
|
||||
.addSubcommandGroup(subcommandgroup => subcommandgroup.setName('instances').setDescription('Intéragir avec les instances AMP.')
|
||||
.addSubcommand(subcommand => subcommand.setName('list').setDescription('Liste toutes les instances disponibles.'))
|
||||
.addSubcommand(subcommand => subcommand.setName('manage').setDescription('Gérer une instance.')
|
||||
.addStringOption(option => option.setName('instance').setDescription("Nom de l'instance").setRequired(true).setAutocomplete(true)))
|
||||
.addSubcommand(subcommand => subcommand.setName('restart').setDescription('Redémarre une instance.')
|
||||
.addStringOption(option => option.setName('name').setDescription("Nom de l'instance").setRequired(true)))
|
||||
),
|
||||
async autocompleteRun(interaction: AutocompleteInteraction) {
|
||||
let query = interaction.options.getString('instance', true)
|
||||
|
||||
let guildProfile = await dbGuild.findOne({ guildId: interaction?.guild?.id })
|
||||
if (!guildProfile) return await interaction.respond([])
|
||||
|
||||
let dbData = guildProfile.get('guildAmp')
|
||||
if (!dbData?.enabled) return await interaction.respond([])
|
||||
|
||||
let host = dbData.host as string
|
||||
let username = dbData.username as string
|
||||
let sessionID = dbData.sessionID as string
|
||||
let rememberMeToken = dbData.rememberMeToken as string
|
||||
|
||||
// Check if the SessionID is still valid
|
||||
let session = await AMP.CheckSession(host, sessionID)
|
||||
if (session.status === 'fail') {
|
||||
if (rememberMeToken) {
|
||||
// Refresh the SessionID if the RememberMeToken is available
|
||||
let details = { username, password: '', token: rememberMeToken, rememberMe: true }
|
||||
let result = await AMP.Core.Login(host, details)
|
||||
|
||||
if (result.status === 'success') sessionID = result.data.sessionID
|
||||
else if (result.status === 'fail') return interaction.respond([])
|
||||
else if (result.status === 'error') return interaction.respond([])
|
||||
} else return await interaction.respond([])
|
||||
}
|
||||
else if (session.status === 'error') return interaction.respond([])
|
||||
|
||||
let choices: ApplicationCommandOptionChoiceData[] = []
|
||||
let result = await AMP.ADSModule.GetInstances(host, sessionID)
|
||||
if (result.status === 'success') {
|
||||
let hosts = result.data.result as Host[]
|
||||
hosts.forEach(host => {
|
||||
let instances = host.AvailableInstances as Instance[]
|
||||
instances.forEach(instance => {
|
||||
if (instance.FriendlyName.includes(query)) choices.push({ name: `${host.FriendlyName} - ${instance.FriendlyName}`, value: instance.InstanceID })
|
||||
})
|
||||
})
|
||||
}
|
||||
else if (result.status === 'fail') return interaction.respond([])
|
||||
else if (result.status === 'error') return interaction.respond([])
|
||||
|
||||
return interaction.respond(choices)
|
||||
},
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
let guildProfile = await dbGuild.findOne({ guildId: interaction?.guild?.id })
|
||||
if (!guildProfile) return interaction.reply({ content: `Database data for **${interaction.guild?.name}** does not exist, please initialize with \`/database init\` !` })
|
||||
|
||||
let dbData = guildProfile.get('guildAmp')
|
||||
if (!dbData?.enabled) return interaction.reply({ content: `AMP module is disabled for **${interaction.guild?.name}**, please activate with \`/database edit guildAmp.enabled True\` !` })
|
||||
|
||||
let host = dbData.host as string
|
||||
let username = dbData.username as string
|
||||
let sessionID = dbData.sessionID as string
|
||||
let rememberMeToken = dbData.rememberMeToken as string
|
||||
|
||||
// Let the user login
|
||||
if (interaction.options.getSubcommand() == 'login') {
|
||||
// Get a SessionID and a RememberMeToken if wanted
|
||||
await interaction.deferReply({ ephemeral: true })
|
||||
|
||||
let details = {
|
||||
username: interaction.options.getString('username') || '',
|
||||
password: interaction.options.getString('password') || '',
|
||||
rememberMe: interaction.options.getBoolean('remember') || '',
|
||||
token: interaction.options.getString('otp') || ''
|
||||
}
|
||||
|
||||
let result = await AMP.Core.Login(host, details)
|
||||
if (result.status === 'success') {
|
||||
username = dbData['username'] = result.data.userInfo.Username
|
||||
sessionID = dbData['sessionID'] = result.data.sessionID
|
||||
rememberMeToken = dbData['rememberMeToken'] = result.data.rememberMeToken
|
||||
|
||||
guildProfile.set('guildAmp', dbData)
|
||||
guildProfile.markModified('guildAmp')
|
||||
await guildProfile.save().catch(console.error)
|
||||
|
||||
return await interaction.followUp(`Tu es connecté au panel sous **${username}** !`)
|
||||
}
|
||||
else if (result.status === 'fail') return await interaction.followUp(failMsg(result.data))
|
||||
else if (result.status === 'error') return await interaction.followUp(errorMsg(result.data))
|
||||
}
|
||||
await interaction.deferReply()
|
||||
|
||||
// Check if the SessionID is still valid
|
||||
let session = await AMP.CheckSession(host, sessionID)
|
||||
if (session.status === 'fail') {
|
||||
if (rememberMeToken) {
|
||||
// Refresh the SessionID if the RememberMeToken is available
|
||||
let details = { username, password: '', token: rememberMeToken, rememberMe: true }
|
||||
let result = await AMP.Core.Login(host, details)
|
||||
|
||||
if (result.status === 'success') sessionID = result.data.sessionID
|
||||
else if (result.status === 'fail') return await interaction.followUp(failMsg(result.data))
|
||||
else if (result.status === 'error') return await interaction.followUp(errorMsg(result.data))
|
||||
}
|
||||
else return await interaction.followUp(`Tu dois te connecter avant d'effectuer une autre commande !`)
|
||||
}
|
||||
else if (session.status === 'error') return await interaction.followUp(errorMsg(session.data))
|
||||
|
||||
if (interaction.options.getSubcommandGroup() == 'instances') {
|
||||
if (interaction.options.getSubcommand() == 'list') {
|
||||
let result = await AMP.ADSModule.GetInstances(host, sessionID) as InstanceResult
|
||||
|
||||
if (result.status === 'success') {
|
||||
await interaction.followUp({ content: `${result.data.length} hôte(s) trouvé(s) !` })
|
||||
result.data.forEach(async host => {
|
||||
let fields = [] as InstanceFields[]
|
||||
host.AvailableInstances.forEach((instance: Instance) => {
|
||||
fields.push({
|
||||
name: instance.FriendlyName,
|
||||
value: `**Running:** ${instance.Running}\n**Port:** ${instance.Port}\n**Module:** ${instance.Module}`,
|
||||
inline: true
|
||||
})
|
||||
})
|
||||
let embed = new EmbedBuilder()
|
||||
.setTitle(host.FriendlyName)
|
||||
.setDescription(`Liste des ${host.AvailableInstances.length} instances :`)
|
||||
.setColor(interaction.guild?.members.me?.displayColor || '#ffc370')
|
||||
.setTimestamp()
|
||||
.setFields(fields)
|
||||
return await interaction.followUp({ embeds: [embed] })
|
||||
})
|
||||
}
|
||||
else if (result.status === 'fail') return await interaction.followUp(failMsg(result.data as unknown as FailMsgData))
|
||||
else if (result.status === 'error') return await interaction.followUp(errorMsg(result.data as unknown as ErrorMsgData))
|
||||
}
|
||||
else if (interaction.options.getSubcommand() == 'manage') {
|
||||
let instanceID = interaction.options.getString('instance', true)
|
||||
let result = await AMP.ADSModule.ManageInstance(host, sessionID, instanceID)
|
||||
|
||||
if (result.status === 'success') {
|
||||
let server = await AMP.ADSModule.Servers(host, sessionID, instanceID)
|
||||
|
||||
if (server.status === 'success') return await interaction.followUp(`Ok !`)
|
||||
else if (server.status === 'fail') return await interaction.followUp(failMsg(server.data))
|
||||
else if (server.status === 'error') return await interaction.followUp(errorMsg(server.data))
|
||||
}
|
||||
else if (result.status === 'fail') return await interaction.followUp(failMsg(result.data))
|
||||
else if (result.status === 'error') return await interaction.followUp(errorMsg(result.data))
|
||||
}
|
||||
else if (interaction.options.getSubcommand() == 'restart') {
|
||||
let query = interaction.options.getString('name')
|
||||
if (!query) return
|
||||
|
||||
let result = await AMP.ADSModule.RestartInstance(host, sessionID, query)
|
||||
|
||||
if (result.status === 'success') return await interaction.followUp(`Ok !`)
|
||||
else if (result.status === 'fail') return await interaction.followUp(failMsg(result.data))
|
||||
else if (result.status === 'error') return await interaction.followUp(errorMsg(result.data))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
import { SlashCommandBuilder, EmbedBuilder, inlineCode, PermissionFlagsBits, MessageFlags } from "discord.js"
|
||||
import type { ChatInputCommandInteraction, AutocompleteInteraction, ApplicationCommandOptionChoiceData, Locale } from "discord.js"
|
||||
import * as AMP from "@/utils/amp"
|
||||
import type { Host, Instance, InstanceFields, InstanceResult, LoginSuccessData } from "@/types/amp"
|
||||
import type { ReturnMsgData } from "@/types"
|
||||
import type { GuildAmp } from "@/types/schemas"
|
||||
import dbGuild from "@/schemas/guild"
|
||||
import { t } from "@/utils/i18n"
|
||||
|
||||
function returnMsg(result: ReturnMsgData, locale: Locale) {
|
||||
if (result.status === "fail") return `${t(locale, "common.failed")}\n${inlineCode(`${result.Title}: ${result.Message}`)}`
|
||||
if (result.status === "error") return `${t(locale, "common.error_occurred")}\n${inlineCode(`${result.error_code}`)}`
|
||||
}
|
||||
|
||||
export const data = new SlashCommandBuilder()
|
||||
.setName("amp")
|
||||
.setDescription("Access my AMP gaming panel")
|
||||
.setDescriptionLocalizations({ fr: "Accède à mon panel de jeu AMP" })
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator)
|
||||
.addSubcommand(subcommand => subcommand
|
||||
.setName("login")
|
||||
.setDescription("Log in before performing another command")
|
||||
.setNameLocalizations({ fr: "connexion" })
|
||||
.setDescriptionLocalizations({ fr: "Connectez-vous avant d'effectuer une autre commande" })
|
||||
.addStringOption(option => option
|
||||
.setName("username")
|
||||
.setDescription("Username")
|
||||
.setNameLocalizations({ fr: "nom_utilisateur" })
|
||||
.setDescriptionLocalizations({ fr: "Nom d'utilisateur" })
|
||||
.setRequired(true)
|
||||
)
|
||||
.addStringOption(option => option
|
||||
.setName("password")
|
||||
.setDescription("Password")
|
||||
.setNameLocalizations({ fr: "mot_de_passe" })
|
||||
.setDescriptionLocalizations({ fr: "Mot de passe" })
|
||||
.setRequired(true)
|
||||
)
|
||||
.addBooleanOption(option => option
|
||||
.setName("remember")
|
||||
.setDescription("Remember credentials")
|
||||
.setNameLocalizations({ fr: "memoriser" })
|
||||
.setDescriptionLocalizations({ fr: "Mémoriser les identifiants" })
|
||||
.setRequired(true)
|
||||
)
|
||||
.addStringOption(option => option
|
||||
.setName("otp")
|
||||
.setDescription("Two-factor authentication code")
|
||||
.setNameLocalizations({ fr: "otp" })
|
||||
.setDescriptionLocalizations({ fr: "Code d'authentification à 2 facteurs" })
|
||||
)
|
||||
)
|
||||
.addSubcommandGroup(subcommandgroup => subcommandgroup
|
||||
.setName("instances")
|
||||
.setDescription("Interact with AMP instances")
|
||||
.setNameLocalizations({ fr: "instances" })
|
||||
.setDescriptionLocalizations({ fr: "Intéragir avec les instances AMP" })
|
||||
.addSubcommand(subcommand => subcommand
|
||||
.setName("list")
|
||||
.setDescription("List all available instances")
|
||||
.setNameLocalizations({ fr: "liste" })
|
||||
.setDescriptionLocalizations({ fr: "Lister toutes les instances disponibles" })
|
||||
)
|
||||
.addSubcommand(subcommand => subcommand
|
||||
.setName("manage")
|
||||
.setDescription("Manage an instance")
|
||||
.setNameLocalizations({ fr: "gerer" })
|
||||
.setDescriptionLocalizations({ fr: "Gérer une instance" })
|
||||
.addStringOption(option => option
|
||||
.setName("instance")
|
||||
.setDescription("Instance name")
|
||||
.setNameLocalizations({ fr: "instance" })
|
||||
.setDescriptionLocalizations({ fr: "Nom de l'instance" })
|
||||
.setRequired(true)
|
||||
.setAutocomplete(true)
|
||||
)
|
||||
)
|
||||
.addSubcommand(subcommand => subcommand
|
||||
.setName("restart")
|
||||
.setDescription("Restart an instance")
|
||||
.setNameLocalizations({ fr: "redemarrer" })
|
||||
.setDescriptionLocalizations({ fr: "Redémarrer une instance" })
|
||||
.addStringOption(option => option
|
||||
.setName("name")
|
||||
.setDescription("Instance name")
|
||||
.setNameLocalizations({ fr: "nom" })
|
||||
.setDescriptionLocalizations({ fr: "Nom de l'instance" })
|
||||
.setRequired(true)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
export async function execute(interaction: ChatInputCommandInteraction) {
|
||||
const guildProfile = await dbGuild.findOne({ guildId: interaction.guild?.id })
|
||||
if (!guildProfile) return interaction.reply({ content: t(interaction.locale, "common.database_not_found"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
const dbData = guildProfile.get("guildAmp") as GuildAmp
|
||||
if (!dbData.enabled) return interaction.reply({ content: t(interaction.locale, "amp.module_disabled"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
const host = dbData.host
|
||||
if (!host) return interaction.reply({ content: t(interaction.locale, "amp.host_not_configured"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
let username = dbData.username
|
||||
let sessionID = dbData.sessionID
|
||||
let rememberMeToken = dbData.rememberMeToken
|
||||
|
||||
const subcommandGroup = interaction.options.getSubcommandGroup(false)
|
||||
const subcommand = interaction.options.getSubcommand(true)
|
||||
if (subcommand == "login") {
|
||||
// Get a SessionID and a RememberMeToken if wanted
|
||||
await interaction.deferReply({ flags: MessageFlags.Ephemeral })
|
||||
|
||||
const details = {
|
||||
username: interaction.options.getString("username", true),
|
||||
password: interaction.options.getString("password", true),
|
||||
token: interaction.options.getString("otp") ?? "",
|
||||
rememberMe: interaction.options.getBoolean("remember", true)
|
||||
}
|
||||
|
||||
const result = await AMP.Core.Login(host, details)
|
||||
if (result.status !== "success") return interaction.followUp({ content: returnMsg(result, interaction.locale), flags: MessageFlags.Ephemeral })
|
||||
|
||||
const loginData = result.data as LoginSuccessData
|
||||
username = dbData.username = loginData.userInfo.Username
|
||||
sessionID = dbData.sessionID = loginData.sessionID
|
||||
rememberMeToken = dbData.rememberMeToken = loginData.rememberMeToken
|
||||
|
||||
guildProfile.set("guildAmp", dbData)
|
||||
guildProfile.markModified("guildAmp")
|
||||
await guildProfile.save().catch(console.error)
|
||||
|
||||
return interaction.followUp({ content: t(interaction.locale, "amp.logged_in", { username }), flags: MessageFlags.Ephemeral })
|
||||
}
|
||||
await interaction.deferReply()
|
||||
|
||||
// Check if the SessionID is still valid
|
||||
if (!sessionID) return interaction.followUp({ content: t(interaction.locale, "amp.login_required"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
const checkResult = await AMP.CheckSession(host, sessionID)
|
||||
if (checkResult.status === "fail") {
|
||||
if (rememberMeToken && username) {
|
||||
// Refresh the SessionID if the RememberMeToken is available
|
||||
const details = { username, password: "", token: rememberMeToken, rememberMe: true }
|
||||
const loginResult = await AMP.Core.Login(host, details)
|
||||
if (loginResult.status !== "success") return interaction.followUp({ content: returnMsg(loginResult, interaction.locale), flags: MessageFlags.Ephemeral })
|
||||
|
||||
const loginData = loginResult.data as LoginSuccessData
|
||||
sessionID = loginData.sessionID
|
||||
}
|
||||
else return interaction.followUp({ content: t(interaction.locale, "amp.login_required"), flags: MessageFlags.Ephemeral })
|
||||
}
|
||||
else if (checkResult.status === "error") return interaction.followUp({ content: returnMsg(checkResult, interaction.locale), flags: MessageFlags.Ephemeral })
|
||||
|
||||
if (subcommandGroup == "instances") {
|
||||
if (subcommand == "list") {
|
||||
const result = (await AMP.ADSModule.GetInstances(host, sessionID)) as InstanceResult
|
||||
if (result.status !== "success") return interaction.followUp({ content: returnMsg(result, interaction.locale), flags: MessageFlags.Ephemeral })
|
||||
|
||||
await interaction.followUp({ content: t(interaction.locale, "amp.hosts_found", { count: result.data.length }) })
|
||||
await Promise.all(result.data.map(async host => {
|
||||
const fields = [] as InstanceFields[]
|
||||
host.AvailableInstances.forEach((instance: Instance) => {
|
||||
fields.push({ name: instance.FriendlyName, value: `**${t(interaction.locale, "amp.running")}:** ${instance.Running}\n**${t(interaction.locale, "amp.port")}:** ${instance.Port}\n**${t(interaction.locale, "amp.module")}:** ${instance.Module}`, inline: true })
|
||||
})
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle(host.FriendlyName)
|
||||
.setDescription(t(interaction.locale, "amp.instance_list", { count: host.AvailableInstances.length }))
|
||||
.setColor(interaction.guild?.members.me?.displayColor ?? "#ffc370")
|
||||
.setTimestamp()
|
||||
.setFields(fields)
|
||||
return interaction.followUp({ embeds: [embed] })
|
||||
}))
|
||||
}
|
||||
else if (subcommand == "manage") {
|
||||
const instanceID = interaction.options.getString("instance", true)
|
||||
|
||||
const manageResult = await AMP.ADSModule.ManageInstance(host, sessionID, instanceID)
|
||||
if (manageResult.status !== "success") return interaction.followUp({ content: returnMsg(manageResult, interaction.locale), flags: MessageFlags.Ephemeral })
|
||||
|
||||
const serversResult = await AMP.ADSModule.Servers(host, sessionID, instanceID)
|
||||
if (serversResult.status !== "success") return interaction.followUp({ content: returnMsg(serversResult, interaction.locale), flags: MessageFlags.Ephemeral })
|
||||
|
||||
return interaction.followUp({ content: t(interaction.locale, "amp.manage_success") })
|
||||
}
|
||||
else if (subcommand == "restart") {
|
||||
const query = interaction.options.getString("name", true)
|
||||
|
||||
const restartResult = await AMP.ADSModule.RestartInstance(host, sessionID, query)
|
||||
if (restartResult.status !== "success") return interaction.followUp({ content: returnMsg(restartResult, interaction.locale), flags: MessageFlags.Ephemeral })
|
||||
|
||||
return interaction.followUp({ content: t(interaction.locale, "amp.restart_success") })
|
||||
}
|
||||
}
|
||||
}
|
||||
export async function autocompleteRun(interaction: AutocompleteInteraction) {
|
||||
const query = interaction.options.getString("instance", true)
|
||||
|
||||
const guildProfile = await dbGuild.findOne({ guildId: interaction.guild?.id })
|
||||
if (!guildProfile) return interaction.respond([])
|
||||
|
||||
const dbData = guildProfile.get("guildAmp") as GuildAmp
|
||||
if (!dbData.enabled) return interaction.respond([])
|
||||
|
||||
const host = dbData.host
|
||||
if (!host) return interaction.respond([])
|
||||
|
||||
let sessionID = dbData.sessionID
|
||||
if (!sessionID) return interaction.respond([])
|
||||
|
||||
const username = dbData.username
|
||||
const rememberMeToken = dbData.rememberMeToken
|
||||
|
||||
const checkResult = await AMP.CheckSession(host, sessionID)
|
||||
if (checkResult.status === "fail") {
|
||||
if (rememberMeToken && username) {
|
||||
// Refresh the SessionID if the RememberMeToken is available
|
||||
const details = { username, password: "", token: rememberMeToken, rememberMe: true }
|
||||
const loginResult = await AMP.Core.Login(host, details)
|
||||
if (loginResult.status !== "success") return interaction.respond([])
|
||||
|
||||
const loginData = loginResult.data as LoginSuccessData
|
||||
sessionID = loginData.sessionID
|
||||
}
|
||||
else return interaction.respond([])
|
||||
}
|
||||
else if (checkResult.status === "error") return interaction.respond([])
|
||||
|
||||
const instancesResult = (await AMP.ADSModule.GetInstances(host, sessionID)) as InstanceResult
|
||||
if (instancesResult.status !== "success") return interaction.respond([])
|
||||
|
||||
const choices: ApplicationCommandOptionChoiceData[] = []
|
||||
const hosts = instancesResult.data as Host[]
|
||||
hosts.forEach(host => {
|
||||
const instances = host.AvailableInstances
|
||||
instances.forEach(instance => {
|
||||
if (instance.FriendlyName.includes(query)) choices.push({ name: `${host.FriendlyName} - ${instance.FriendlyName}`, value: instance.InstanceID })
|
||||
})
|
||||
})
|
||||
|
||||
return interaction.respond(choices)
|
||||
}
|
||||
|
||||
@@ -1,37 +1,39 @@
|
||||
import { SlashCommandBuilder, EmbedBuilder, ChatInputCommandInteraction, TextChannel, PermissionFlagsBits } from 'discord.js'
|
||||
import { SlashCommandBuilder, EmbedBuilder, PermissionFlagsBits, ChannelType } from "discord.js"
|
||||
import type { ChatInputCommandInteraction } from "discord.js"
|
||||
import { t } from "@/utils/i18n"
|
||||
import { logConsole } from "@/utils/console"
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('boost')
|
||||
.setDescription('Tester le boost du serveur !')
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator),
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
if (interaction.guild?.id !== '796327643783626782') return interaction.reply({ content: 'Non !' })// Jujul Community
|
||||
let member = interaction.member
|
||||
if (!member) return console.log(`\u001b[1;31m Aucun membre trouvé !`)
|
||||
export const data = new SlashCommandBuilder()
|
||||
.setName("boost")
|
||||
.setDescription("Test the server boost")
|
||||
.setDescriptionLocalizations({ fr: "Tester le boost du serveur" })
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator)
|
||||
|
||||
let guild = interaction.guild
|
||||
if (!guild) return console.log(`\u001b[1;31m Aucun serveur trouvé !`)
|
||||
export async function execute(interaction: ChatInputCommandInteraction) {
|
||||
if (interaction.guild?.id !== "796327643783626782") return interaction.reply({ content: t(interaction.locale, "boost.not_authorized") }) // Jujul Community
|
||||
|
||||
let channel = guild.channels.cache.get('924353449930412153') as TextChannel
|
||||
if (!channel) return console.log(`\u001b[1;31m Aucun channel trouvé avec l'id "924353449930412153" !`)
|
||||
const member = interaction.member
|
||||
if (!member) { logConsole('discordjs', 'boost.no_member'); return }
|
||||
|
||||
let boostRole = guild.roles.premiumSubscriberRole
|
||||
if (!boostRole) return console.log(`\u001b[1;31m Aucun rôle de boost trouvé !`)
|
||||
const guild = interaction.guild
|
||||
if (!guild.members.me) { logConsole('discordjs', 'boost.not_in_guild'); return }
|
||||
|
||||
if (!guild.members.me) return console.log(`\u001b[1;31m Je ne suis pas sur le serveur !`)
|
||||
|
||||
let embed = new EmbedBuilder()
|
||||
.setColor(guild.members.me.displayHexColor)
|
||||
.setTitle(`Nouveau boost de ${member.user.username} !`)
|
||||
.setDescription(`
|
||||
Merci à toi pour ce boost.\n
|
||||
Grâce à toi, on a atteint ${guild.premiumSubscriptionCount} boosts !
|
||||
`)
|
||||
.setThumbnail(member.user.avatar)
|
||||
.setTimestamp(new Date())
|
||||
|
||||
await channel.send({ embeds: [embed] })
|
||||
await interaction.reply({ content: 'Va voir dans <#924353449930412153> !' })
|
||||
const channel = await guild.channels.fetch("924353449930412153")
|
||||
if (!channel || (channel.type !== ChannelType.GuildText && channel.type !== ChannelType.GuildAnnouncement)) {
|
||||
logConsole('discordjs', 'boost.no_channel', { channelId: "924353449930412153" })
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const boostRole = guild.roles.premiumSubscriberRole
|
||||
if (!boostRole) { logConsole('discordjs', 'boost.no_boost_role'); return }
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor(guild.members.me.displayHexColor)
|
||||
.setTitle(t(interaction.locale, "boost.new_boost_title", { username: member.user.username }))
|
||||
.setDescription(t(interaction.locale, "boost.new_boost_description", { count: (guild.premiumSubscriptionCount ?? 0).toString() }))
|
||||
.setThumbnail(member.user.avatar)
|
||||
.setTimestamp(new Date())
|
||||
|
||||
await channel.send({ embeds: [embed] })
|
||||
return interaction.reply({ content: t(interaction.locale, "boost.check_channel", { channelId: "924353449930412153" }) })
|
||||
}
|
||||
|
||||
@@ -1,76 +1,108 @@
|
||||
import { SlashCommandBuilder, ChatInputCommandInteraction, EmbedBuilder, APIEmbedField, PermissionFlagsBits } from 'discord.js'
|
||||
|
||||
import dbGuildInit from '../../utils/dbGuildInit'
|
||||
import dbGuild from '../../schemas/guild'
|
||||
|
||||
const parseObject = (obj: object, prefix = ''): { name: string, value: object | string | boolean }[] => {
|
||||
let fields: { name: string, value: object | string | boolean }[] = []
|
||||
|
||||
for (let [key, value] of Object.entries(obj)) {
|
||||
if (typeof value === 'object') fields.push(...parseObject(value, `${prefix}${key}.`))
|
||||
else {
|
||||
if (typeof value === 'boolean') value = value ? 'True' : 'False'
|
||||
else if (!value) value = 'None'
|
||||
else value = value.toString()
|
||||
|
||||
fields.push({ name: `${prefix}${key}`, value })
|
||||
}
|
||||
}
|
||||
return fields
|
||||
}
|
||||
|
||||
export default {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('database')
|
||||
.setDescription('Communicate with the database')
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator)
|
||||
.addSubcommand(subcommand => subcommand.setName('info').setDescription('Returns information about the current guild'))
|
||||
.addSubcommand(subcommand => subcommand.setName('init').setDescription('Force initialize an entry for the current guild in the database'))
|
||||
.addSubcommand(subcommand => subcommand.setName('edit').setDescription('Modify parameters for the current guild')
|
||||
.addStringOption(option => option.setName('key').setDescription('Key to modify').setRequired(true))
|
||||
.addStringOption(option => option.setName('value').setDescription('Value to set').setRequired(true))),
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
let guild = interaction.guild
|
||||
if (!guild) return await interaction.reply({ content: 'This command must be used in a server.', ephemeral: true })
|
||||
|
||||
let guildProfile = await dbGuild.findOne({ guildId: guild.id })
|
||||
|
||||
if (interaction.options.getSubcommand() === 'info') {
|
||||
if (!guildProfile) return await interaction.reply({ content: `Database data for **${guild.name}** does not exist !` })
|
||||
|
||||
let fields = parseObject(guildProfile.toObject())
|
||||
|
||||
let embed = new EmbedBuilder()
|
||||
.setTitle('Database Information')
|
||||
.setDescription(`Guild **${guildProfile.guildName}** (ID: ${guildProfile.guildId})`)
|
||||
.setThumbnail(guildProfile.guildIcon as string)
|
||||
.setTimestamp()
|
||||
//.addFields(fields as APIEmbedField[])
|
||||
// Limit the number of fields to 25
|
||||
.addFields(fields.slice(0, 25) as APIEmbedField[])
|
||||
return await interaction.reply({ embeds: [embed] })
|
||||
|
||||
} else if (interaction.options.getSubcommand() === 'init') {
|
||||
if (guildProfile) return await interaction.reply({ content: `Database data for **${guildProfile.guildName}** already exists !` })
|
||||
|
||||
guildProfile = await dbGuildInit(guild)
|
||||
if (!guildProfile) return await interaction.reply({ content: `An error occured while initializing database data for **${guild.name}** !` })
|
||||
|
||||
return await interaction.reply({ content: `Database data for **${guildProfile.guildName}** successfully initialized !` })
|
||||
|
||||
} else if (interaction.options.getSubcommand() === 'edit') {
|
||||
if (!guildProfile) return await interaction.reply({ content: `Database data for **${guild.name}** does not exist, please init with \`/database init\` !` })
|
||||
|
||||
let key = interaction.options.getString('key', true)
|
||||
let value = interaction.options.getString('value', true)
|
||||
|
||||
let oldValue = guildProfile.get(key)
|
||||
if (!oldValue) oldValue = 'None'
|
||||
|
||||
guildProfile.set(key, value)
|
||||
await guildProfile.save().catch(console.error)
|
||||
|
||||
return await interaction.reply({ content: `Database data for **${guildProfile.guildName}** successfully updated !\n**${key}**: ${oldValue} -> ${value}` })
|
||||
}
|
||||
}
|
||||
}
|
||||
import { SlashCommandBuilder, EmbedBuilder, PermissionFlagsBits, MessageFlags } from "discord.js"
|
||||
import type { ChatInputCommandInteraction, APIEmbedField } from "discord.js"
|
||||
import dbGuildInit from "@/utils/dbGuildInit"
|
||||
import dbGuild from "@/schemas/guild"
|
||||
import { t } from "@/utils/i18n"
|
||||
|
||||
const parseObject = (obj: object, prefix = ""): { name: string, value: object | string | boolean }[] => {
|
||||
const fields: { name: string, value: object | string | boolean }[] = []
|
||||
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
if (value !== null && typeof value === "object") fields.push(...parseObject(value as object, `${prefix}${key}.`))
|
||||
else {
|
||||
let newValue: string
|
||||
if (typeof value === "boolean") newValue = value ? "True" : "False"
|
||||
else if (value === null || value === undefined) newValue = "None"
|
||||
else newValue = String(value)
|
||||
|
||||
fields.push({ name: `${prefix}${key}`, value: newValue })
|
||||
}
|
||||
}
|
||||
return fields
|
||||
}
|
||||
|
||||
export const data = new SlashCommandBuilder()
|
||||
.setName("database")
|
||||
.setDescription("Communicate with the database")
|
||||
.setDescriptionLocalizations({ fr: "Communiquer avec la base de données" })
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator)
|
||||
.addSubcommand(subcommand => subcommand
|
||||
.setName("info")
|
||||
.setDescription("Returns information about the current guild")
|
||||
.setNameLocalizations({ fr: "info" })
|
||||
.setDescriptionLocalizations({ fr: "Retourne les informations sur le serveur actuel" })
|
||||
)
|
||||
.addSubcommand(subcommand => subcommand
|
||||
.setName("init")
|
||||
.setDescription("Force initialize an entry for the current guild in the database")
|
||||
.setNameLocalizations({ fr: "init" })
|
||||
.setDescriptionLocalizations({ fr: "Initialiser de force une entrée pour le serveur actuel dans la base de données" })
|
||||
)
|
||||
.addSubcommand(subcommand => subcommand
|
||||
.setName("edit")
|
||||
.setDescription("Modify parameters for the current guild")
|
||||
.setNameLocalizations({ fr: "modifier" })
|
||||
.setDescriptionLocalizations({ fr: "Modifier les paramètres pour le serveur actuel" })
|
||||
.addStringOption(option => option
|
||||
.setName("key")
|
||||
.setDescription("Key to modify")
|
||||
.setNameLocalizations({ fr: "cle" })
|
||||
.setDescriptionLocalizations({ fr: "Clé à modifier" })
|
||||
.setRequired(true)
|
||||
)
|
||||
.addStringOption(option => option
|
||||
.setName("value")
|
||||
.setDescription("Value to set")
|
||||
.setNameLocalizations({ fr: "valeur" })
|
||||
.setDescriptionLocalizations({ fr: "Valeur à définir" })
|
||||
.setRequired(true)
|
||||
)
|
||||
)
|
||||
|
||||
export async function execute(interaction: ChatInputCommandInteraction) {
|
||||
if (interaction.user !== interaction.client.application.owner) return interaction.reply({ content: t(interaction.locale, "database.owner_only"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
const guild = interaction.guild
|
||||
if (!guild) return interaction.reply({ content: t(interaction.locale, "database.server_only"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
let guildProfile = await dbGuild.findOne({ guildId: guild.id })
|
||||
|
||||
const subcommand = interaction.options.getSubcommand(true)
|
||||
if (subcommand === "info") {
|
||||
if (!guildProfile) return interaction.reply({ content: t(interaction.locale, "common.database_not_found"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
const fields = parseObject(guildProfile.toObject())
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle(t(interaction.locale, "database.info_title"))
|
||||
.setDescription(t(interaction.locale, "database.guild_info", { name: guildProfile.guildName, id: guildProfile.guildId }))
|
||||
.setThumbnail(guildProfile.guildIcon)
|
||||
.setTimestamp()
|
||||
//.addFields(fields as APIEmbedField[])
|
||||
// Limit the number of fields to 25
|
||||
.addFields(fields.slice(0, 25) as APIEmbedField[])
|
||||
|
||||
return interaction.reply({ embeds: [embed] })
|
||||
}
|
||||
else if (subcommand === "init") {
|
||||
if (guildProfile) return interaction.reply({ content: t(interaction.locale, "database.already_exists", { name: guildProfile.guildName }), flags: MessageFlags.Ephemeral })
|
||||
|
||||
guildProfile = await dbGuildInit(guild)
|
||||
|
||||
return interaction.reply({ content: t(interaction.locale, "database.initialized", { name: guildProfile.guildName }), flags: MessageFlags.Ephemeral })
|
||||
}
|
||||
else if (subcommand === "edit") {
|
||||
if (!guildProfile) return interaction.reply({ content: t(interaction.locale, "common.database_not_found"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
const key = interaction.options.getString("key", true)
|
||||
const value = interaction.options.getString("value", true)
|
||||
|
||||
let oldValue: string = guildProfile.get(key) as string
|
||||
if (!oldValue) oldValue = t(interaction.locale, "common.none")
|
||||
|
||||
guildProfile.set(key, value)
|
||||
guildProfile.markModified(key)
|
||||
await guildProfile.save().catch(console.error)
|
||||
|
||||
return interaction.reply({ content: t(interaction.locale, "database.updated", { name: guildProfile.guildName, key, oldValue, value }), flags: MessageFlags.Ephemeral })
|
||||
}
|
||||
}
|
||||
|
||||
353
src/commands/global/freebox.ts
Normal file
353
src/commands/global/freebox.ts
Normal file
@@ -0,0 +1,353 @@
|
||||
import { SlashCommandBuilder, EmbedBuilder, MessageFlags, ButtonBuilder, ButtonStyle, ActionRowBuilder, inlineCode } from "discord.js"
|
||||
import type { ChatInputCommandInteraction } from "discord.js"
|
||||
import crypto from "crypto"
|
||||
import * as Freebox from "@/utils/freebox"
|
||||
import type {
|
||||
APIResponseData, APIResponseDataError, APIResponseDataVersion,
|
||||
ConnectionStatus, GetChallenge, LcdConfig, OpenSession, RequestAuthorization, TrackAuthorizationProgress
|
||||
} from "@/types/freebox"
|
||||
import type { GuildFbx } from "@/types/schemas"
|
||||
import dbGuild from "@/schemas/guild"
|
||||
import { t } from "@/utils/i18n"
|
||||
|
||||
export const data = new SlashCommandBuilder()
|
||||
.setName("freebox")
|
||||
.setDescription("Access FreeboxOS API")
|
||||
.setDescriptionLocalizations({ fr: "Accéder à l'API FreeboxOS" })
|
||||
.addSubcommand(subcommand => subcommand
|
||||
.setName("status")
|
||||
.setDescription("Display Freebox configuration and status")
|
||||
.setDescriptionLocalizations({ fr: "Afficher la configuration et l'état de la Freebox" })
|
||||
)
|
||||
.addSubcommand(subcommand => subcommand
|
||||
.setName("setup")
|
||||
.setDescription("Configure Freebox settings easily")
|
||||
.setDescriptionLocalizations({ fr: "Configurer facilement les paramètres Freebox" })
|
||||
.addStringOption(option => option
|
||||
.setName("host")
|
||||
.setDescription("Freebox host (IP address)")
|
||||
.setNameLocalizations({ fr: "hote" })
|
||||
.setDescriptionLocalizations({ fr: "Hôte Freebox (adresse IP)" })
|
||||
.setRequired(false)
|
||||
)
|
||||
.addIntegerOption(option => option
|
||||
.setName("version")
|
||||
.setDescription("Freebox API version")
|
||||
.setNameLocalizations({ fr: "version" })
|
||||
.setDescriptionLocalizations({ fr: "Version de l'API Freebox" })
|
||||
.setRequired(false)
|
||||
)
|
||||
.addBooleanOption(option => option
|
||||
.setName("enabled")
|
||||
.setDescription("Enable or disable the Freebox module")
|
||||
.setNameLocalizations({ fr: "active" })
|
||||
.setDescriptionLocalizations({ fr: "Activer ou désactiver le module Freebox" })
|
||||
.setRequired(false)
|
||||
)
|
||||
)
|
||||
.addSubcommand(subcommand => subcommand
|
||||
.setName("init")
|
||||
.setDescription("Create an app on the Freebox to authenticate")
|
||||
.setDescriptionLocalizations({ fr: "Créer une app sur la Freebox pour s'authentifier" })
|
||||
.addStringOption(option => option
|
||||
.setName("host")
|
||||
.setDescription("Freebox host (IP or domain)")
|
||||
.setNameLocalizations({ fr: "hote" })
|
||||
.setDescriptionLocalizations({ fr: "Hôte Freebox (IP ou domaine)" })
|
||||
.setRequired(true)
|
||||
)
|
||||
)
|
||||
.addSubcommandGroup(subcommandGroup => subcommandGroup
|
||||
.setName("get")
|
||||
.setDescription("Retrieve data")
|
||||
.setNameLocalizations({ fr: "recuperer" })
|
||||
.setDescriptionLocalizations({ fr: "Récupérer des données" })
|
||||
.addSubcommand(subcommand => subcommand
|
||||
.setName("version")
|
||||
.setDescription("Display API version")
|
||||
.setDescriptionLocalizations({ fr: "Afficher la version de l'API" })
|
||||
)
|
||||
.addSubcommand(subcommand => subcommand
|
||||
.setName("connection")
|
||||
.setDescription("Retrieve connection information")
|
||||
.setNameLocalizations({ fr: "connexion" })
|
||||
.setDescriptionLocalizations({ fr: "Récupérer les informations de connexion" })
|
||||
)
|
||||
.addSubcommand(subcommand => subcommand
|
||||
.setName("lcd")
|
||||
.setDescription("Retrieve LCD configuration")
|
||||
.setDescriptionLocalizations({ fr: "Récupérer la configuration de l'écran LCD" })
|
||||
)
|
||||
)
|
||||
.addSubcommandGroup(subcommandGroup => subcommandGroup
|
||||
.setName("lcd")
|
||||
.setDescription("Control LCD features")
|
||||
.setDescriptionLocalizations({ fr: "Contrôler les fonctionnalités LCD" })
|
||||
.addSubcommand(subcommand => subcommand
|
||||
.setName("leds")
|
||||
.setDescription("Toggle LED strip on/off")
|
||||
.setNameLocalizations({ fr: "leds" })
|
||||
.setDescriptionLocalizations({ fr: "Allumer/éteindre le bandeau LED" })
|
||||
.addBooleanOption(option => option
|
||||
.setName("enabled")
|
||||
.setDescription("Enable or disable LED strip")
|
||||
.setNameLocalizations({ fr: "active" })
|
||||
.setDescriptionLocalizations({ fr: "Activer ou désactiver le bandeau LED" })
|
||||
.setRequired(true)
|
||||
)
|
||||
)
|
||||
.addSubcommand(subcommand => subcommand
|
||||
.setName("timer")
|
||||
.setDescription("Setup automatic LED timer")
|
||||
.setNameLocalizations({ fr: "minuteur" })
|
||||
.setDescriptionLocalizations({ fr: "Configurer le minuteur automatique des LEDs" })
|
||||
.addStringOption(option => option
|
||||
.setName("action")
|
||||
.setDescription("Timer action")
|
||||
.setNameLocalizations({ fr: "action" })
|
||||
.setDescriptionLocalizations({ fr: "Action du minuteur" })
|
||||
.setRequired(true)
|
||||
.addChoices(
|
||||
{ name: "Enable timer", value: "enable", name_localizations: { fr: "Activer minuteur" } },
|
||||
{ name: "Disable timer", value: "disable", name_localizations: { fr: "Désactiver minuteur" } },
|
||||
{ name: "Status", value: "status", name_localizations: { fr: "Statut" } }
|
||||
)
|
||||
)
|
||||
.addStringOption(option => option
|
||||
.setName("morning_time")
|
||||
.setDescription("Morning time (HH:MM format, 24h)")
|
||||
.setNameLocalizations({ fr: "heure_matin" })
|
||||
.setDescriptionLocalizations({ fr: "Heure du matin (format HH:MM, 24h)" })
|
||||
.setRequired(false)
|
||||
)
|
||||
.addStringOption(option => option
|
||||
.setName("night_time")
|
||||
.setDescription("Night time (HH:MM format, 24h)")
|
||||
.setNameLocalizations({ fr: "heure_nuit" })
|
||||
.setDescriptionLocalizations({ fr: "Heure du soir (format HH:MM, 24h)" })
|
||||
.setRequired(false)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
export async function execute(interaction: ChatInputCommandInteraction) {
|
||||
const guildProfile = await dbGuild.findOne({ guildId: interaction.guild?.id })
|
||||
if (!guildProfile) return interaction.reply({ content: t(interaction.locale, "common.database_not_found"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
const dbData = guildProfile.get("guildFbx") as GuildFbx
|
||||
let host = dbData.host
|
||||
let appToken = dbData.appToken
|
||||
|
||||
const subcommandGroup = interaction.options.getSubcommandGroup(false)
|
||||
const subcommand = interaction.options.getSubcommand(true)
|
||||
|
||||
if (subcommand === "status") {
|
||||
// Construire l'embed de statut
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle(t(interaction.locale, "freebox.status.title"))
|
||||
.setColor(dbData.enabled ? 0x00ff00 : 0xff0000)
|
||||
.addFields({
|
||||
name: t(interaction.locale, "freebox.status.config_section"),
|
||||
value: [
|
||||
t(interaction.locale, "freebox.status.module_field", { status: dbData.enabled ? t(interaction.locale, "freebox.common.enabled") : t(interaction.locale, "freebox.common.disabled") }),
|
||||
t(interaction.locale, "freebox.status.host_field", { value: host ? `\`${host}\`` : t(interaction.locale, "freebox.status.host_not_configured") }),
|
||||
t(interaction.locale, "freebox.status.token_field", { status: appToken ? t(interaction.locale, "freebox.status.token_configured") : t(interaction.locale, "freebox.status.token_not_configured") })
|
||||
].join('\n'),
|
||||
inline: false
|
||||
})
|
||||
|
||||
// Informations LCD si disponibles
|
||||
if (dbData.lcd) {
|
||||
const lcdStatus = dbData.lcd.enabled ? t(interaction.locale, "freebox.status.timer_enabled") : t(interaction.locale, "freebox.status.timer_disabled")
|
||||
const botManaged = dbData.lcd.botId ? `<@${dbData.lcd.botId}>` : t(interaction.locale, "freebox.status.timer_no_manager")
|
||||
const morningTime = dbData.lcd.morningTime ?? t(interaction.locale, "freebox.status.timer_not_configured")
|
||||
const nightTime = dbData.lcd.nightTime ?? t(interaction.locale, "freebox.status.timer_not_configured")
|
||||
|
||||
embed.addFields({
|
||||
name: t(interaction.locale, "freebox.status.timer_section"),
|
||||
value: [
|
||||
t(interaction.locale, "freebox.timer.status_field", { status: lcdStatus }),
|
||||
t(interaction.locale, "freebox.timer.managed_by", { manager: botManaged }),
|
||||
t(interaction.locale, "freebox.timer.morning", { time: morningTime }),
|
||||
t(interaction.locale, "freebox.timer.night", { time: nightTime })
|
||||
].join('\n'),
|
||||
inline: false
|
||||
})
|
||||
}
|
||||
|
||||
// Boutons d'action
|
||||
const buttons = new ActionRowBuilder<ButtonBuilder>().addComponents(
|
||||
new ButtonBuilder()
|
||||
.setCustomId("freebox_test_connection")
|
||||
.setLabel(t(interaction.locale, "freebox.buttons.test_connection"))
|
||||
.setEmoji("🔌")
|
||||
.setStyle(ButtonStyle.Primary)
|
||||
.setDisabled(!appToken),
|
||||
new ButtonBuilder()
|
||||
.setCustomId("freebox_lcd_status")
|
||||
.setLabel(t(interaction.locale, "freebox.buttons.lcd_status"))
|
||||
.setEmoji("💡")
|
||||
.setStyle(ButtonStyle.Secondary)
|
||||
.setDisabled(!appToken),
|
||||
new ButtonBuilder()
|
||||
.setCustomId("freebox_refresh_status")
|
||||
.setLabel(t(interaction.locale, "freebox.buttons.refresh_status"))
|
||||
.setEmoji("🔄")
|
||||
.setStyle(ButtonStyle.Success)
|
||||
)
|
||||
|
||||
return interaction.reply({ embeds: [embed], components: [buttons], flags: MessageFlags.Ephemeral })
|
||||
}
|
||||
|
||||
if (!dbData.enabled) return interaction.reply({ content: t(interaction.locale, "common.module_disabled"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
if (subcommand === "init") {
|
||||
if (appToken) return interaction.reply({ content: t(interaction.locale, "freebox.auth.app_token_already_set"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
host = interaction.options.getString("host", true)
|
||||
if (host === "mafreebox.freebox.fr") return interaction.reply({ content: t(interaction.locale, "freebox.auth.host_not_allowed"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
const initData = await Freebox.Core.Init(host) as APIResponseData<RequestAuthorization>
|
||||
if (!initData.success) return Freebox.handleError(initData as APIResponseDataError, interaction)
|
||||
|
||||
appToken = initData.result.app_token
|
||||
const trackId = initData.result.track_id
|
||||
if (!trackId) return interaction.reply({ content: t(interaction.locale, "freebox.auth.track_id_failed"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
// Si l'utilisateur n'a pas encore autorisé l'application, on lui demande de le faire
|
||||
await interaction.reply({ content: t(interaction.locale, "freebox.auth.init_in_progress", { trackId }), flags: MessageFlags.Ephemeral })
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
const initCheck = setInterval(async () => {
|
||||
if (!host || !trackId) { clearInterval(initCheck); return }
|
||||
|
||||
const trackData = await Freebox.Core.Init(host, trackId) as APIResponseData<TrackAuthorizationProgress>
|
||||
if (!trackData.success) return Freebox.handleError(trackData as APIResponseDataError, interaction, false)
|
||||
|
||||
const status = trackData.result.status
|
||||
if (status === "granted") {
|
||||
clearInterval(initCheck)
|
||||
dbData.appToken = appToken
|
||||
|
||||
guildProfile.set("guildFbx", dbData)
|
||||
guildProfile.markModified("guildFbx")
|
||||
await guildProfile.save().catch(console.error)
|
||||
|
||||
return interaction.followUp({ content: t(interaction.locale, "common.success"), flags: MessageFlags.Ephemeral })
|
||||
} else if (status === "denied") {
|
||||
clearInterval(initCheck)
|
||||
|
||||
return interaction.followUp({ content: t(interaction.locale, "freebox.auth.user_denied_access"), flags: MessageFlags.Ephemeral })
|
||||
} else if (status === "pending") { console.log("Freebox authorization pending...") }
|
||||
}, 2000)
|
||||
}
|
||||
else {
|
||||
if (!host) return interaction.reply({ content: t(interaction.locale, "freebox.general.host_not_set"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
if (subcommand === "version") {
|
||||
const versionData = await Freebox.Core.Version(host) as APIResponseDataVersion
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle("FreeboxOS API Version")
|
||||
.setDescription(`Version: ${versionData.api_version || "Unknown"}`)
|
||||
|
||||
return interaction.reply({ embeds: [embed] })
|
||||
}
|
||||
|
||||
if (!appToken) return interaction.reply({ content: t(interaction.locale, "freebox.general.app_token_not_set"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
const challengeData = await Freebox.Login.Challenge(host) as APIResponseData<GetChallenge>
|
||||
if (!challengeData.success) return Freebox.handleError(challengeData as APIResponseDataError, interaction)
|
||||
|
||||
const password = crypto.createHmac("sha1", appToken).update(challengeData.result.challenge).digest("hex")
|
||||
const sessionData = await Freebox.Login.Session(host, password) as APIResponseData<OpenSession>
|
||||
if (!sessionData.success) return Freebox.handleError(sessionData as APIResponseDataError, interaction)
|
||||
|
||||
const sessionToken = sessionData.result.session_token
|
||||
if (!sessionToken) return interaction.reply({ content: t(interaction.locale, "freebox.auth.session_token_failed"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
if (subcommandGroup === "get") {
|
||||
if (subcommand === "connection") {
|
||||
const connectionData = await Freebox.Get.Connection(host, sessionToken) as APIResponseData<ConnectionStatus>
|
||||
if (!connectionData.success) return Freebox.handleError(connectionData as APIResponseDataError, interaction)
|
||||
|
||||
return interaction.reply({ content: t(interaction.locale, "freebox.api.connection_details", { details: inlineCode(JSON.stringify(connectionData.result)) }), flags: MessageFlags.Ephemeral })
|
||||
}
|
||||
else if (subcommand === "lcd") {
|
||||
const lcdData = await Freebox.Get.LcdConfig(host, sessionToken) as APIResponseData<LcdConfig>
|
||||
if (!lcdData.success) return Freebox.handleError(lcdData as APIResponseDataError, interaction)
|
||||
|
||||
return interaction.reply({ content: t(interaction.locale, "freebox.api.lcd_details", { details: inlineCode(JSON.stringify(lcdData.result)) }), flags: MessageFlags.Ephemeral })
|
||||
}
|
||||
}
|
||||
else if (subcommandGroup === "lcd") {
|
||||
// Initialiser l'objet LCD s'il n'existe pas
|
||||
dbData.lcd ??= { enabled: false }
|
||||
|
||||
// Vérifier si le bot est autorisé pour ce serveur
|
||||
if (dbData.lcd.enabled && dbData.lcd.botId && dbData.lcd.botId !== interaction.client.user.id) {
|
||||
return interaction.reply({ content: t(interaction.locale, "freebox.lcd.managed_by_other_bot"), flags: MessageFlags.Ephemeral })
|
||||
}
|
||||
|
||||
if (subcommand === "leds") {
|
||||
const enabled = interaction.options.getBoolean("enabled", true)
|
||||
const lcdData = await Freebox.Set.LcdConfig(host, sessionToken, { led_strip_enabled: enabled }) as APIResponseData<LcdConfig>
|
||||
if (!lcdData.success) return Freebox.handleError(lcdData as APIResponseDataError, interaction)
|
||||
|
||||
return interaction.reply({ content: t(interaction.locale, "freebox.lcd.leds_success", { status: enabled ? t(interaction.locale, "freebox.common.enabled") : t(interaction.locale, "freebox.common.disabled") }), flags: MessageFlags.Ephemeral })
|
||||
}
|
||||
else if (subcommand === "timer") {
|
||||
const action = interaction.options.getString("action")
|
||||
if (!action) return interaction.reply({ content: t(interaction.locale, "freebox.general.invalid_action"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
if (action === "status") {
|
||||
const status = dbData.lcd.enabled ? t(interaction.locale, "freebox.common.enabled") : t(interaction.locale, "freebox.common.disabled")
|
||||
const managedBy = dbData.lcd.botId ? `<@${dbData.lcd.botId}>` : t(interaction.locale, "common.none")
|
||||
|
||||
return interaction.reply({ content: t(interaction.locale, "freebox.timer.status_display", { status, managedBy }), flags: MessageFlags.Ephemeral })
|
||||
}
|
||||
else if (action === "enable") {
|
||||
const morningTime = interaction.options.getString("morning_time")
|
||||
const nightTime = interaction.options.getString("night_time")
|
||||
|
||||
if (!morningTime || !nightTime) return interaction.reply({ content: t(interaction.locale, "freebox.timer.times_required"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
// Valider le format HH:MM
|
||||
const timeRegex = /^([01]?[0-9]|2[0-3]):[0-5][0-9]$/
|
||||
if (!timeRegex.test(morningTime) || !timeRegex.test(nightTime)) return interaction.reply({ content: t(interaction.locale, "freebox.general.invalid_time_format"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
// Activer le timer et enregistrer ce bot comme responsable
|
||||
dbData.lcd.enabled = true
|
||||
dbData.lcd.botId = interaction.client.user.id
|
||||
dbData.lcd.morningTime = morningTime
|
||||
dbData.lcd.nightTime = nightTime
|
||||
|
||||
guildProfile.set("guildFbx", dbData)
|
||||
guildProfile.markModified("guildFbx")
|
||||
await guildProfile.save().catch(console.error)
|
||||
|
||||
// Démarrer les timers automatiques
|
||||
if (interaction.guildId) Freebox.Timer.schedule(interaction.client, interaction.guildId, dbData)
|
||||
|
||||
return interaction.reply({ content: t(interaction.locale, "freebox.timer.enabled", { morningTime, nightTime }), flags: MessageFlags.Ephemeral })
|
||||
}
|
||||
else if (action === "disable") {
|
||||
// Arrêter les timers actifs avant de désactiver
|
||||
if (interaction.guildId) Freebox.Timer.clear(interaction.guildId)
|
||||
|
||||
// Désactiver le timer
|
||||
dbData.lcd.enabled = false
|
||||
dbData.lcd.botId = ""
|
||||
dbData.lcd.morningTime = ""
|
||||
dbData.lcd.nightTime = ""
|
||||
|
||||
guildProfile.set("guildFbx", dbData)
|
||||
guildProfile.markModified("guildFbx")
|
||||
await guildProfile.save().catch(console.error)
|
||||
|
||||
return interaction.reply({ content: t(interaction.locale, "freebox.timer.disabled"), flags: MessageFlags.Ephemeral })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
17
src/commands/global/index.ts
Normal file
17
src/commands/global/index.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import * as amp from "./amp"
|
||||
import * as boost from "./boost"
|
||||
import * as database from "./database"
|
||||
import * as freebox from "./freebox"
|
||||
import * as ping from "./ping"
|
||||
import * as twitch from "./twitch"
|
||||
|
||||
import type { Command } from "@/types"
|
||||
|
||||
export default [
|
||||
amp,
|
||||
boost,
|
||||
database,
|
||||
freebox,
|
||||
ping,
|
||||
twitch
|
||||
] as Command[]
|
||||
@@ -1,11 +1,17 @@
|
||||
import { SlashCommandBuilder, ChatInputCommandInteraction } from 'discord.js'
|
||||
|
||||
export default {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('ping')
|
||||
.setDescription('Check the latency of the bot'),
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
let sent = await interaction.reply({ content: 'Pinging...', fetchReply: true })
|
||||
interaction.editReply(`Websocket heartbeat: ${interaction.client.ws.ping}ms.\nRoundtrip latency: ${sent.createdTimestamp - interaction.createdTimestamp}ms`)
|
||||
}
|
||||
}
|
||||
import { SlashCommandBuilder } from "discord.js"
|
||||
import type { ChatInputCommandInteraction } from "discord.js"
|
||||
import { t } from "@/utils/i18n"
|
||||
|
||||
export const data = new SlashCommandBuilder()
|
||||
.setName("ping")
|
||||
.setDescription("Check the latency of the bot")
|
||||
.setDescriptionLocalizations({ fr: "Vérifier la latence du bot" })
|
||||
|
||||
export async function execute(interaction: ChatInputCommandInteraction) {
|
||||
await interaction.reply({ content: t(interaction.locale, "ping.pinging") })
|
||||
const sent = await interaction.fetchReply()
|
||||
return interaction.editReply(t(interaction.locale, "ping.response", {
|
||||
heartbeat: interaction.client.ws.ping.toString(),
|
||||
latency: (sent.createdTimestamp - interaction.createdTimestamp).toString()
|
||||
}))
|
||||
}
|
||||
|
||||
198
src/commands/global/twitch.ts
Normal file
198
src/commands/global/twitch.ts
Normal file
@@ -0,0 +1,198 @@
|
||||
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"
|
||||
|
||||
export const data = new SlashCommandBuilder()
|
||||
.setName("twitch")
|
||||
.setDescription("Manage streamers notifications")
|
||||
.setDescriptionLocalizations({ fr: "Gérer les notifications des streameurs" })
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild)
|
||||
.addSubcommand(subcommand => subcommand
|
||||
.setName("status")
|
||||
.setDescription("Display Twitch module status")
|
||||
.setNameLocalizations({ fr: "statut" })
|
||||
.setDescriptionLocalizations({ fr: "Afficher le statut du module Twitch" })
|
||||
)
|
||||
.addSubcommand(subcommand => subcommand
|
||||
.setName("channel")
|
||||
.setDescription("Set the channel to send notifications")
|
||||
.setNameLocalizations({ fr: "canal" })
|
||||
.setDescriptionLocalizations({ fr: "Définir le canal pour envoyer les notifications" })
|
||||
.addChannelOption(option => option
|
||||
.setName("channel")
|
||||
.setDescription("The channel to send notifications")
|
||||
.setNameLocalizations({ fr: "canal" })
|
||||
.setDescriptionLocalizations({ fr: "Le canal pour envoyer les notifications" })
|
||||
.setRequired(true)
|
||||
)
|
||||
)
|
||||
.addSubcommandGroup(subcommandgroup => subcommandgroup
|
||||
.setName("streamer")
|
||||
.setDescription("Manage streamers")
|
||||
.setNameLocalizations({ fr: "streameur" })
|
||||
.setDescriptionLocalizations({ fr: "Gérer les streameurs" })
|
||||
.addSubcommand(subcommand => subcommand
|
||||
.setName("list")
|
||||
.setDescription("List all streamers")
|
||||
.setNameLocalizations({ fr: "liste" })
|
||||
.setDescriptionLocalizations({ fr: "Lister tous les streameurs" })
|
||||
)
|
||||
.addSubcommand(subcommand => subcommand
|
||||
.setName("add")
|
||||
.setDescription("Add a streamer")
|
||||
.setNameLocalizations({ fr: "ajouter" })
|
||||
.setDescriptionLocalizations({ fr: "Ajouter un streameur" })
|
||||
.addStringOption(option => option
|
||||
.setName("username")
|
||||
.setDescription("The username of the streamer to add")
|
||||
.setNameLocalizations({ fr: "nom_utilisateur" })
|
||||
.setDescriptionLocalizations({ fr: "Le nom d'utilisateur du streameur à ajouter" })
|
||||
.setRequired(true)
|
||||
.setAutocomplete(true)
|
||||
)
|
||||
.addUserOption(option => option
|
||||
.setName("member")
|
||||
.setDescription("The member on the guild to mention")
|
||||
.setNameLocalizations({ fr: "membre" })
|
||||
.setDescriptionLocalizations({ fr: "Le membre sur le serveur à mentionner" })
|
||||
.setRequired(false)
|
||||
)
|
||||
)
|
||||
.addSubcommand(subcommand => subcommand
|
||||
.setName("remove")
|
||||
.setDescription("Remove a streamer")
|
||||
.setNameLocalizations({ fr: "supprimer" })
|
||||
.setDescriptionLocalizations({ fr: "Supprimer un streameur" })
|
||||
.addStringOption(option => option
|
||||
.setName("username")
|
||||
.setDescription("The username of the streamer to remove")
|
||||
.setNameLocalizations({ fr: "nom_utilisateur" })
|
||||
.setDescriptionLocalizations({ fr: "Le nom d'utilisateur du streameur à supprimer" })
|
||||
.setRequired(true)
|
||||
.setAutocomplete(true)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
export async function execute(interaction: ChatInputCommandInteraction) {
|
||||
const guildProfile = await dbGuild.findOne({ guildId: interaction.guild?.id })
|
||||
if (!guildProfile) return interaction.reply({ content: t(interaction.locale, "common.database_not_found"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
const dbData = guildProfile.get("guildTwitch") as GuildTwitch
|
||||
|
||||
const subcommandGroup = interaction.options.getSubcommandGroup(false)
|
||||
const subcommand = interaction.options.getSubcommand(true)
|
||||
if (subcommand == "status") {
|
||||
// Utiliser la fonction utilitaire pour générer l'embed et les composants
|
||||
const { embed, components } = generateTwitchEmbed(dbData, interaction.client, interaction.guild?.id ?? "", interaction.locale)
|
||||
|
||||
return interaction.reply({ embeds: [embed], components: components, flags: MessageFlags.Ephemeral })
|
||||
}
|
||||
|
||||
if (!dbData.enabled) return interaction.reply({ content: t(interaction.locale, "twitch.module_disabled_activate"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
if (subcommand == "channel") {
|
||||
const channel = interaction.options.getChannel("channel", true)
|
||||
if (channel.type !== ChannelType.GuildText && channel.type !== ChannelType.GuildAnnouncement)
|
||||
return interaction.reply({ content: t(interaction.locale, "common.invalid_text_channel"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
dbData.channelId = channel.id
|
||||
guildProfile.set("guildTwitch", dbData)
|
||||
guildProfile.markModified("guildTwitch")
|
||||
await guildProfile.save().catch(console.error)
|
||||
|
||||
return interaction.reply({ content: t(interaction.locale, "twitch.notifications_channel_set", { channel: channel.name ?? channel.id }), flags: MessageFlags.Ephemeral })
|
||||
}
|
||||
else if (subcommandGroup == "streamer") {
|
||||
if (!dbData.channelId) return interaction.reply({ content: t(interaction.locale, "twitch.configure_channel_first"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
if (subcommand == "list") {
|
||||
if (!dbData.streamers.length) return interaction.reply({ content: t(interaction.locale, "twitch.no_streamers_list"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
const streamers = [] as string[]
|
||||
await Promise.all(dbData.streamers.map(async streamer => {
|
||||
try {
|
||||
const user = await twitchClient.users.getUserById(streamer.twitchUserId)
|
||||
if (user) streamers.push(`- ${user.displayName} (${streamer.twitchUserId})`)
|
||||
else streamers.push(`- ${t(interaction.locale, "twitch.user_not_found_id", { id: streamer.twitchUserId })}`)
|
||||
} catch (error) {
|
||||
console.log(chalk.magenta(`[Twitch] Error fetching user for ID ${streamer.twitchUserId}`))
|
||||
console.error(error)
|
||||
}
|
||||
}))
|
||||
const streamerList = streamers.length > 0 ? streamers.join("\n") : t(interaction.locale, "twitch.no_streamers")
|
||||
|
||||
return interaction.reply({ content: `${t(interaction.locale, "twitch.list.title")}:\n${streamerList}`, flags: MessageFlags.Ephemeral })
|
||||
}
|
||||
else if (subcommand == "add") {
|
||||
const username = interaction.options.getString("username", true)
|
||||
const member = interaction.options.getUser("member", false)
|
||||
|
||||
const user = await twitchClient.users.getUserByName(username)
|
||||
if (!user) return interaction.reply({ content: t(interaction.locale, "twitch.streamer_not_found", { username }), flags: MessageFlags.Ephemeral })
|
||||
|
||||
if (dbData.streamers.some(s => s.twitchUserId === user.id)) return interaction.reply({ content: t(interaction.locale, "twitch.streamer_already_added", { username }), flags: MessageFlags.Ephemeral })
|
||||
dbData.streamers.push({ twitchUserId: user.id, discordUserId: member?.id ?? "", messageId: "" })
|
||||
|
||||
guildProfile.set("guildTwitch", dbData)
|
||||
guildProfile.markModified("guildTwitch")
|
||||
await guildProfile.save().catch(console.error)
|
||||
|
||||
const userSubs = await twitchClient.eventSub.getSubscriptionsForUser(user.id)
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
if (!userSubs.data.find(sub => sub.transportMethod === "webhook" && sub.type === "stream.online")) listener.onStreamOnline(user.id, onlineSub)
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
if (!userSubs.data.find(sub => sub.transportMethod === "webhook" && sub.type === "stream.offline")) listener.onStreamOffline(user.id, offlineSub)
|
||||
|
||||
return interaction.reply({ content: t(interaction.locale, "twitch.streamer_added", { username, id: user.id }), flags: MessageFlags.Ephemeral })
|
||||
}
|
||||
else if (subcommand == "remove") {
|
||||
const username = interaction.options.getString("username", true)
|
||||
|
||||
const user = await twitchClient.users.getUserByName(username)
|
||||
if (!user) return interaction.reply({ content: t(interaction.locale, "twitch.streamer_not_found", { username }), flags: MessageFlags.Ephemeral })
|
||||
|
||||
const streamerIndex = dbData.streamers.findIndex(s => s.twitchUserId === user.id)
|
||||
if (streamerIndex === -1)return interaction.reply({ content: t(interaction.locale, "twitch.streamer_not_in_list", { username }), flags: MessageFlags.Ephemeral })
|
||||
|
||||
dbData.streamers.splice(streamerIndex, 1)
|
||||
guildProfile.set("guildTwitch", dbData)
|
||||
guildProfile.markModified("guildTwitch")
|
||||
await guildProfile.save().catch(console.error)
|
||||
|
||||
if (!await dbGuild.exists({ "guildTwitch.streamers.twitchUserId": user.id })) {
|
||||
const userSubs = await twitchClient.eventSub.getSubscriptionsForUser(user.id)
|
||||
await Promise.all(userSubs.data.map(async sub => { if (sub.transportMethod === "webhook" && (sub.type === "stream.online" || sub.type === "stream.offline")) await sub.unsubscribe() }))
|
||||
console.log(chalk.magenta(`[Twitch] Listener removed for ${user.displayName} (ID ${user.id})`))
|
||||
}
|
||||
|
||||
return interaction.reply({ content: t(interaction.locale, "twitch.streamer_removed", { username }), flags: MessageFlags.Ephemeral })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function autocompleteRun(interaction: AutocompleteInteraction) {
|
||||
const query = interaction.options.getString("username", true)
|
||||
if (!query || query.length < 3) return interaction.respond([])
|
||||
|
||||
const guildProfile = await dbGuild.findOne({ guildId: interaction.guild?.id })
|
||||
if (!guildProfile) return interaction.respond([])
|
||||
|
||||
const dbData = guildProfile.get("guildTwitch") as GuildTwitch
|
||||
if (!dbData.enabled) return interaction.respond([])
|
||||
|
||||
const choices: ApplicationCommandOptionChoiceData[] = []
|
||||
const searchResult = await twitchClient.search.searchChannels(query)
|
||||
if (searchResult.data.length === 0) return interaction.respond([])
|
||||
|
||||
searchResult.data.forEach(streamerResult => {
|
||||
if (dbData.streamers.some(s => s.twitchUserId === streamerResult.id)) return
|
||||
choices.push({ name: streamerResult.displayName, value: streamerResult.name })
|
||||
})
|
||||
|
||||
return interaction.respond(choices)
|
||||
}
|
||||
26
src/commands/index.ts
Normal file
26
src/commands/index.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import global from "./global"
|
||||
import player from "./player"
|
||||
import salonpostam from "./salonpostam"
|
||||
|
||||
import { Command, CommandFolder } from "@/types"
|
||||
|
||||
export const commandFolders = [
|
||||
{
|
||||
name: "global",
|
||||
commands: global
|
||||
},
|
||||
{
|
||||
name: "player",
|
||||
commands: player
|
||||
},
|
||||
{
|
||||
name: "salonpostam",
|
||||
commands: salonpostam
|
||||
}
|
||||
] as CommandFolder[]
|
||||
|
||||
export default [
|
||||
...global,
|
||||
...player,
|
||||
...salonpostam
|
||||
] as Command[]
|
||||
60
src/commands/player/disco.ts
Normal file
60
src/commands/player/disco.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { SlashCommandBuilder, PermissionFlagsBits, MessageFlags, ChannelType } from "discord.js"
|
||||
import type { ChatInputCommandInteraction } from "discord.js"
|
||||
import { generateDiscoEmbed } from "@/utils/player"
|
||||
import type { Disco } from "@/types/schemas"
|
||||
import dbGuild from "@/schemas/guild"
|
||||
import { t } from "@/utils/i18n"
|
||||
|
||||
export const data = new SlashCommandBuilder()
|
||||
.setName("disco")
|
||||
.setDescription("Manage the Disco module")
|
||||
.setDescriptionLocalizations({ fr: "Gérer le module Disco" })
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild)
|
||||
.addSubcommand(subcommand => subcommand
|
||||
.setName("status")
|
||||
.setDescription("Display Disco mode status")
|
||||
.setNameLocalizations({ fr: "statut" })
|
||||
.setDescriptionLocalizations({ fr: "Afficher le statut du mode Disco" })
|
||||
)
|
||||
.addSubcommand(subcommand => subcommand
|
||||
.setName("channel")
|
||||
.setDescription("Configure the channel for Disco effects")
|
||||
.setNameLocalizations({ fr: "canal" })
|
||||
.setDescriptionLocalizations({ fr: "Configurer le canal pour les effets Disco" })
|
||||
.addChannelOption(option => option
|
||||
.setName("channel")
|
||||
.setDescription("The channel where to apply Disco effects")
|
||||
.setNameLocalizations({ fr: "canal" })
|
||||
.setDescriptionLocalizations({ fr: "Le canal où appliquer les effets Disco" })
|
||||
.setRequired(true)
|
||||
)
|
||||
)
|
||||
|
||||
export async function execute(interaction: ChatInputCommandInteraction) {
|
||||
const guildProfile = await dbGuild.findOne({ guildId: interaction.guild?.id })
|
||||
if (!guildProfile) return interaction.reply({ content: t(interaction.locale, "common.database_not_found"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
const dbData = guildProfile.get("guildPlayer.disco") as Disco
|
||||
|
||||
const subcommand = interaction.options.getSubcommand(true)
|
||||
if (subcommand === "status") {
|
||||
const { embed, components } = generateDiscoEmbed(dbData, interaction.client, interaction.guild?.id ?? "", interaction.locale)
|
||||
|
||||
return interaction.reply({ embeds: [embed], components: components, flags: MessageFlags.Ephemeral })
|
||||
}
|
||||
else if (subcommand === "channel") {
|
||||
const channel = interaction.options.getChannel("channel", true)
|
||||
if (channel.type !== ChannelType.GuildText && channel.type !== ChannelType.GuildAnnouncement) return interaction.reply({
|
||||
content: t(interaction.locale, "common.invalid_channel_type"),
|
||||
flags: MessageFlags.Ephemeral
|
||||
})
|
||||
|
||||
dbData.channelId = channel.id
|
||||
|
||||
guildProfile.set("guildPlayer.disco", dbData)
|
||||
guildProfile.markModified("guildPlayer.disco")
|
||||
await guildProfile.save().catch(console.error)
|
||||
|
||||
return interaction.reply({ content: t(interaction.locale, "player.disco.channel_configured_success", { channel: channel.name ?? "Inconnu" }), flags: MessageFlags.Ephemeral })
|
||||
}
|
||||
}
|
||||
31
src/commands/player/index.ts
Normal file
31
src/commands/player/index.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import * as disco from "./disco"
|
||||
import * as loop from "./loop"
|
||||
import * as lyrics from "./lyrics"
|
||||
import * as panel from "./panel"
|
||||
import * as pause from "./pause"
|
||||
import * as play from "./play"
|
||||
import * as previous from "./previous"
|
||||
import * as queue from "./queue"
|
||||
import * as resume from "./resume"
|
||||
import * as shuffle from "./shuffle"
|
||||
import * as skip from "./skip"
|
||||
import * as stop from "./stop"
|
||||
import * as volume from "./volume"
|
||||
|
||||
import type { Command } from "@/types"
|
||||
|
||||
export default [
|
||||
disco,
|
||||
loop,
|
||||
lyrics,
|
||||
panel,
|
||||
pause,
|
||||
play,
|
||||
previous,
|
||||
queue,
|
||||
resume,
|
||||
shuffle,
|
||||
skip,
|
||||
stop,
|
||||
volume
|
||||
] as Command[]
|
||||
@@ -1,21 +1,29 @@
|
||||
import { ChatInputCommandInteraction, SlashCommandBuilder } from 'discord.js'
|
||||
import { useQueue } from'discord-player'
|
||||
|
||||
export default {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('loop')
|
||||
.setDescription('Boucler la musique en cours de lecture.')
|
||||
.addIntegerOption(option => option.setName('loop')
|
||||
.setDescription('Mode de boucle (0 = Off, 1 = Titre, 2 = File d\'Attente; 3 = Autoplay)')
|
||||
.setRequired(true)
|
||||
.setMinValue(0)
|
||||
.setMaxValue(3)),
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
let loop = interaction.options.getInteger('loop')
|
||||
let queue = useQueue(interaction.guild?.id ?? '')
|
||||
if (!queue) return interaction.followUp({ content: 'Aucune file d\'attente en cours, recherche une musique plutôt !' })
|
||||
|
||||
queue.setRepeatMode(loop as number)
|
||||
return await interaction.reply(`Boucle ${loop === 0 ? 'désactivée' : loop === 1 ? 'en mode Titre' : loop === 2 ? 'en mode File d\'Attente' : 'en autoplay'}.`)
|
||||
}
|
||||
}
|
||||
import { SlashCommandBuilder } from "discord.js"
|
||||
import type { ChatInputCommandInteraction } from "discord.js"
|
||||
import { useQueue } from "discord-player"
|
||||
import type { QueueRepeatMode } from "discord-player"
|
||||
import { t } from "@/utils/i18n"
|
||||
|
||||
export const data = new SlashCommandBuilder()
|
||||
.setName("loop")
|
||||
.setDescription("Loop the current music")
|
||||
.setNameLocalizations({ fr: "boucle" })
|
||||
.setDescriptionLocalizations({ fr: "Boucler la musique en cours de lecture" })
|
||||
.addIntegerOption(option => option
|
||||
.setName("mode")
|
||||
.setDescription("Loop mode (0 = Off | 1 = Track | 2 = Queue | 3 = Autoplay)")
|
||||
.setDescriptionLocalizations({ fr: "Mode de boucle (0 = Arrêt | 1 = Titre | 2 = File d'Attente | 3 = Autoplay)" })
|
||||
.setRequired(true)
|
||||
.setMinValue(0)
|
||||
.setMaxValue(3)
|
||||
)
|
||||
|
||||
export async function execute(interaction: ChatInputCommandInteraction) {
|
||||
const mode = interaction.options.getInteger("mode", true)
|
||||
const queue = useQueue(interaction.guild?.id ?? "")
|
||||
if (!queue) return interaction.followUp({ content: t(interaction.locale, "player.no_queue") })
|
||||
|
||||
queue.setRepeatMode(mode as QueueRepeatMode)
|
||||
|
||||
return interaction.reply(t(interaction.locale, mode === 0 ? "player.loop_off" : mode === 1 ? "player.loop_track" : mode === 2 ? "player.loop_queue" : "player.loop_autoplay"))
|
||||
}
|
||||
|
||||
@@ -1,45 +1,61 @@
|
||||
import { ChatInputCommandInteraction, SlashCommandBuilder, EmbedBuilder } from 'discord.js'
|
||||
import { useQueue } from 'discord-player'
|
||||
import { lyricsExtractor } from '@discord-player/extractor'
|
||||
|
||||
export default {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('lyrics')
|
||||
.setDescription('Rechercher les paroles d\'une musique.')
|
||||
.addStringOption(option => option.setName('recherche').setDescription('Chercher une musique spécifique')),
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
await interaction.deferReply()
|
||||
|
||||
let query = interaction.options.getString('recherche', false)
|
||||
if (!query) {
|
||||
let queue = useQueue(interaction.guild?.id ?? '')
|
||||
if (!queue) return interaction.followUp({ content: 'Aucune file d\'attente en cours, recherche une musique plutôt !' })
|
||||
let track = queue.currentTrack
|
||||
if (!track) return interaction.followUp({ content: 'Aucune musique en cours, recherche en une plutôt !' })
|
||||
|
||||
if (track.raw.source === 'spotify') query = `${track.author} ${track.title}`
|
||||
else query = track.title
|
||||
}
|
||||
|
||||
let lyricsFinder = lyricsExtractor()
|
||||
|
||||
let lyrics = await lyricsFinder.search(query).catch(() => null)
|
||||
if (!lyrics) return interaction.followUp({ content: 'Pas de paroles trouvées !' })
|
||||
|
||||
let trimmedLyrics = lyrics.lyrics.substring(0, 1997)
|
||||
|
||||
let embed = new EmbedBuilder()
|
||||
.setColor('#ffc370')
|
||||
.setTitle(lyrics.title)
|
||||
.setURL(lyrics.url)
|
||||
.setThumbnail(lyrics.thumbnail)
|
||||
.setAuthor({
|
||||
name: lyrics.artist.name,
|
||||
iconURL: lyrics.artist.image,
|
||||
url: lyrics.artist.url
|
||||
})
|
||||
.setDescription(trimmedLyrics.length === 1997 ? `${trimmedLyrics}...` : trimmedLyrics)
|
||||
|
||||
return interaction.followUp({ embeds: [embed] })
|
||||
}
|
||||
}
|
||||
import { SlashCommandBuilder, EmbedBuilder, MessageFlags } from "discord.js"
|
||||
import type { ChatInputCommandInteraction } from "discord.js"
|
||||
import { useQueue, useMainPlayer } from "discord-player"
|
||||
import type { LrcSearchResult } from "discord-player"
|
||||
import { t } from "@/utils/i18n"
|
||||
|
||||
export const data = new SlashCommandBuilder()
|
||||
.setName("lyrics")
|
||||
.setDescription("Search for song lyrics")
|
||||
.setNameLocalizations({ fr: "paroles" })
|
||||
.setDescriptionLocalizations({ fr: "Rechercher les paroles d'une musique" })
|
||||
.addStringOption(option => option
|
||||
.setName("search")
|
||||
.setDescription("Search for a specific song")
|
||||
.setNameLocalizations({ fr: "recherche" })
|
||||
.setDescriptionLocalizations({ fr: "Chercher une musique spécifique" })
|
||||
)
|
||||
|
||||
export async function execute(interaction: ChatInputCommandInteraction) {
|
||||
await interaction.deferReply()
|
||||
|
||||
const player = useMainPlayer()
|
||||
const embed = new EmbedBuilder().setColor("#ffff64").setFooter({ text: "Powered by Genius" })
|
||||
let lyrics = [] as LrcSearchResult[]
|
||||
|
||||
const query = interaction.options.getString("search", false)
|
||||
if (!query) {
|
||||
const queue = useQueue(interaction.guild?.id ?? "")
|
||||
if (!queue) return interaction.followUp({ content: t(interaction.locale, "player.no_queue"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
const track = queue.currentTrack
|
||||
if (!track) return interaction.followUp({ content: t(interaction.locale, "player.no_current_track"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
lyrics = await player.lyrics.search({ trackName: track.title, artistName: track.author })
|
||||
|
||||
if (!lyrics.length) return interaction.followUp({ content: t(interaction.locale, "player.no_lyrics_found"), flags: MessageFlags.Ephemeral })
|
||||
const trimmedLyrics = lyrics[0].plainLyrics.substring(0, 1997)
|
||||
|
||||
embed
|
||||
.setTitle(track.title)
|
||||
.setURL(track.url)
|
||||
.setDescription(trimmedLyrics.length === 1997 ? `${trimmedLyrics}...` : trimmedLyrics)
|
||||
.setThumbnail(track.thumbnail)
|
||||
.setAuthor({ name: track.author, url: `https://genius.com/search?q=${track.author.replace(/ /g, "-")}` })
|
||||
}
|
||||
else {
|
||||
lyrics = await player.lyrics.search({ q: query })
|
||||
|
||||
if (!lyrics.length) return interaction.followUp({ content: t(interaction.locale, "player.no_lyrics_found"), flags: MessageFlags.Ephemeral })
|
||||
const trimmedLyrics = lyrics[0].plainLyrics.substring(0, 1997)
|
||||
|
||||
embed
|
||||
.setTitle(lyrics[0].name)
|
||||
.setURL(`https://genius.com/search?q=${query.replace(/ /g, "%20")}`)
|
||||
.setDescription(trimmedLyrics.length === 1997 ? `${trimmedLyrics}...` : trimmedLyrics)
|
||||
.setThumbnail("https://play-lh.googleusercontent.com/e6-dZlTM-gJ2sFxFFs3X15O84HEv6jc9PQGgHtVTn7FP6lUXeEAkDl9v4RfVOwbSuQ")
|
||||
.setAuthor({ name: lyrics[0].artistName, url: `https://genius.com/search?q=${lyrics[0].artistName.replace(/ /g, "-")}` })
|
||||
}
|
||||
|
||||
return interaction.followUp({ embeds: [embed] })
|
||||
}
|
||||
|
||||
@@ -1,25 +1,26 @@
|
||||
import { SlashCommandBuilder, ChatInputCommandInteraction } from 'discord.js'
|
||||
import { playerGenerate } from '../../utils/player'
|
||||
import getUptime from '../../utils/getUptime'
|
||||
import { useQueue } from 'discord-player'
|
||||
|
||||
export default {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('panel')
|
||||
.setDescription('Générer les infos de la lecture en cours.'),
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
let queue = useQueue(interaction.guild?.id ?? '')
|
||||
if (!queue) return interaction.followUp({ content: 'Aucune file d\'attente en cours, recherche une musique plutôt !' })
|
||||
|
||||
let guild = interaction.guild
|
||||
if (!guild) return await interaction.reply({ content: 'Cette commande n\'est pas disponible en message privé.', ephemeral: true })
|
||||
|
||||
let client = guild.client
|
||||
|
||||
let { embed, components } = await playerGenerate(guild)
|
||||
if (components && embed.data.footer) embed.setFooter({ text: `Uptime: ${getUptime(client.uptime)} \n ${embed.data.footer.text}` })
|
||||
else embed.setFooter({ text: `Uptime: ${getUptime(client.uptime)}` })
|
||||
|
||||
return interaction.reply({ embeds: [embed] })
|
||||
}
|
||||
}
|
||||
import { SlashCommandBuilder, MessageFlags } from "discord.js"
|
||||
import type { ChatInputCommandInteraction } from "discord.js"
|
||||
import { useQueue } from "discord-player"
|
||||
import { generatePlayerEmbed } from "@/utils/player"
|
||||
import uptime from "@/utils/uptime"
|
||||
import { t } from "@/utils/i18n"
|
||||
|
||||
export const data = new SlashCommandBuilder()
|
||||
.setName("panel")
|
||||
.setDescription("Generate current playback info")
|
||||
.setNameLocalizations({ fr: "panneau" })
|
||||
.setDescriptionLocalizations({ fr: "Générer les infos de la lecture en cours" })
|
||||
|
||||
export async function execute(interaction: ChatInputCommandInteraction) {
|
||||
const queue = useQueue(interaction.guild?.id ?? "")
|
||||
if (!queue) return interaction.followUp({ content: t(interaction.locale, "player.no_queue"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
const guild = interaction.guild
|
||||
if (!guild) return interaction.reply({ content: t(interaction.locale, "common.private_message_not_available"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
const { embed, components } = generatePlayerEmbed(guild, interaction.locale)
|
||||
if (components && embed.data.footer) embed.setFooter({ text: `${t(interaction.locale, "player.uptime")}: ${uptime(guild.client.uptime)} \n ${embed.data.footer.text}` })
|
||||
else embed.setFooter({ text: `${t(interaction.locale, "player.uptime")}: ${uptime(guild.client.uptime)}` })
|
||||
|
||||
return interaction.reply({ embeds: [embed] })
|
||||
}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import { SlashCommandBuilder, ChatInputCommandInteraction } from 'discord.js'
|
||||
import { useQueue } from 'discord-player'
|
||||
|
||||
export default {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('pause')
|
||||
.setDescription('Met en pause la musique.'),
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
let queue = useQueue(interaction.guild?.id ?? '')
|
||||
if (!queue) return interaction.followUp({ content: 'Aucune file d\'attente en cours, recherche une musique plutôt !' })
|
||||
|
||||
queue.node.setPaused(!queue.node.isPaused())
|
||||
return await interaction.reply('Musique mise en pause !')
|
||||
}
|
||||
import { SlashCommandBuilder, MessageFlags } from "discord.js"
|
||||
import type { ChatInputCommandInteraction } from "discord.js"
|
||||
import { useQueue } from "discord-player"
|
||||
import { t } from "@/utils/i18n"
|
||||
|
||||
export const data = new SlashCommandBuilder()
|
||||
.setName("pause")
|
||||
.setDescription("Pause the music")
|
||||
.setDescriptionLocalizations({ fr: "Met en pause la musique" })
|
||||
|
||||
export const execute = async (interaction: ChatInputCommandInteraction) => {
|
||||
const queue = useQueue(interaction.guild?.id ?? "")
|
||||
if (!queue) return interaction.followUp({ content: t(interaction.locale, "player.no_queue_search_instead"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
queue.node.setPaused(!queue.node.isPaused())
|
||||
return interaction.reply(t(interaction.locale, "player.paused"))
|
||||
}
|
||||
@@ -1,96 +1,123 @@
|
||||
import { SlashCommandBuilder, ChatInputCommandInteraction, AutocompleteInteraction, GuildMember } from 'discord.js'
|
||||
import { useMainPlayer, useQueue, QueryType } from 'discord-player'
|
||||
import dbGuild from '../../schemas/guild'
|
||||
|
||||
interface TrackSearchResult { name: string, value: string }
|
||||
|
||||
export default {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('play')
|
||||
.setDescription('Jouer une musique.')
|
||||
.addStringOption(option => option.setName('recherche').setDescription('Titre de la musique à chercher').setRequired(true).setAutocomplete(true)),
|
||||
async autocompleteRun(interaction: AutocompleteInteraction) {
|
||||
let query = interaction.options.getString('recherche', true)
|
||||
if (!query) return interaction.respond([])
|
||||
|
||||
let player = useMainPlayer()
|
||||
|
||||
const resultsYouTube = await player.search(query, { searchEngine: QueryType.YOUTUBE })
|
||||
const resultsSpotify = await player.search(query, { searchEngine: QueryType.SPOTIFY_SEARCH })
|
||||
|
||||
const tracksYouTube = resultsYouTube.tracks.slice(0, 5).map((t) => ({
|
||||
name: `YouTube: ${`${t.title} - ${t.author} (${t.duration})`.length > 75 ? `${`${t.title} - ${t.author}`.substring(0, 75)}... (${t.duration})` : `${t.title} - ${t.author} (${t.duration})`}`,
|
||||
value: t.url
|
||||
}))
|
||||
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})`}`,
|
||||
value: t.url
|
||||
}))
|
||||
|
||||
const tracks: TrackSearchResult[] = []
|
||||
tracksYouTube.forEach((t) => tracks.push({ name: t.name, value: t.value }))
|
||||
tracksSpotify.forEach((t) => tracks.push({ name: t.name, value: t.value }))
|
||||
|
||||
return interaction.respond(tracks)
|
||||
},
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
let member = interaction.member as GuildMember
|
||||
let voiceChannel = member.voice.channel
|
||||
if (!voiceChannel) return await interaction.reply({ content: 'T\'es pas dans un vocal, idiot !', ephemeral: true })
|
||||
|
||||
let botChannel = interaction.guild?.members.me?.voice.channel
|
||||
if (botChannel && voiceChannel.id !== botChannel.id) return await interaction.reply({ content: 'T\'es pas dans mon vocal !', ephemeral: true })
|
||||
|
||||
await interaction.deferReply()
|
||||
|
||||
let query = interaction.options.getString('recherche', true)
|
||||
let player = useMainPlayer()
|
||||
let queue = useQueue(interaction.guild?.id ?? '')
|
||||
|
||||
if (!queue) {
|
||||
if (interaction.guild) queue = player.nodes.create(interaction.guild, {
|
||||
metadata: {
|
||||
channel: interaction.channel,
|
||||
client: interaction.guild.members.me,
|
||||
requestedBy: interaction.user
|
||||
},
|
||||
selfDeaf: true,
|
||||
volume: 20,
|
||||
leaveOnEmpty: true,
|
||||
leaveOnEmptyCooldown: 30000,
|
||||
leaveOnEnd: true,
|
||||
leaveOnEndCooldown: 300000
|
||||
})
|
||||
else return
|
||||
}
|
||||
try { if (!queue.connection) await queue.connect(voiceChannel) }
|
||||
catch (error: unknown) { console.error(error) }
|
||||
|
||||
|
||||
let guildProfile = await dbGuild.findOne({ guildId: queue.guild.id })
|
||||
if (!guildProfile) return console.log(`Database data for **${queue.guild.name}** does not exist !`)
|
||||
|
||||
let dbData = guildProfile.get('guildPlayer.replay')
|
||||
dbData['textChannelId'] = interaction.channel?.id
|
||||
dbData['voiceChannelId'] = voiceChannel.id
|
||||
|
||||
guildProfile.set('guildPlayer.replay', dbData)
|
||||
await guildProfile.save().catch(console.error)
|
||||
|
||||
|
||||
let result = await player.search(query, { requestedBy: interaction.user })
|
||||
if (!result.hasTracks()) return interaction.followUp(`Aucune musique trouvée pour **${query}** !`)
|
||||
let track = result.tracks[0]
|
||||
|
||||
let entry = queue.tasksQueue.acquire()
|
||||
await entry.getTask()
|
||||
queue.addTrack(track)
|
||||
|
||||
try {
|
||||
if (!queue.isPlaying()) await queue.node.play()
|
||||
let track_source = track.source === 'youtube' ? 'Youtube' : track.source === 'spotify' ? 'Spotify' : 'Inconnu'
|
||||
return interaction.followUp(`Chargement de la musique **${track.title}** de **${track.author}** sur **${track_source}**...`)
|
||||
} catch (error: unknown) { console.error(error) }
|
||||
finally { queue.tasksQueue.release() }
|
||||
}
|
||||
}
|
||||
import { SlashCommandBuilder, MessageFlags } from "discord.js"
|
||||
import type { ChatInputCommandInteraction, AutocompleteInteraction, GuildMember } from "discord.js"
|
||||
import { useMainPlayer, useQueue } from "discord-player"
|
||||
import { SpotifyExtractor } from "@discord-player/extractor"
|
||||
import { YoutubeiExtractor } from "discord-player-youtubei"
|
||||
import { startProgressSaving } from "@/utils/player"
|
||||
import type { TrackSearchResult } from "@/types/player"
|
||||
import type { GuildPlayer } from "@/types/schemas"
|
||||
import dbGuild from "@/schemas/guild"
|
||||
import { t } from "@/utils/i18n"
|
||||
|
||||
export const data = new SlashCommandBuilder()
|
||||
.setName("play")
|
||||
.setDescription("Play a song")
|
||||
.setNameLocalizations({ fr: "jouer" })
|
||||
.setDescriptionLocalizations({ fr: "Jouer une musique" })
|
||||
.addStringOption(option => option
|
||||
.setName("search")
|
||||
.setDescription("Music title to search for")
|
||||
.setNameLocalizations({ fr: "recherche" })
|
||||
.setDescriptionLocalizations({ fr: "Titre de la musique à chercher" })
|
||||
.setRequired(true)
|
||||
.setAutocomplete(true)
|
||||
)
|
||||
|
||||
export async function execute(interaction: ChatInputCommandInteraction) {
|
||||
const member = interaction.member as GuildMember
|
||||
const voiceChannel = member.voice.channel
|
||||
if (!voiceChannel) return interaction.reply({ content: t(interaction.locale, "player.not_in_voice"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
const botChannel = interaction.guild?.members.me?.voice.channel
|
||||
if (botChannel && voiceChannel.id !== botChannel.id) return interaction.reply({ content: t(interaction.locale, "player.not_in_same_voice"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
await interaction.deferReply()
|
||||
|
||||
const query = interaction.options.getString("search", true)
|
||||
const player = useMainPlayer()
|
||||
let queue = useQueue(interaction.guild?.id ?? "")
|
||||
|
||||
if (!queue) {
|
||||
if (interaction.guild) queue = player.nodes.create(interaction.guild, {
|
||||
metadata: {
|
||||
channel: interaction.channel,
|
||||
client: interaction.guild.members.me,
|
||||
requestedBy: interaction.user
|
||||
},
|
||||
selfDeaf: true,
|
||||
volume: 20,
|
||||
leaveOnEmpty: true,
|
||||
leaveOnEmptyCooldown: 30000,
|
||||
leaveOnEnd: true,
|
||||
leaveOnEndCooldown: 300000
|
||||
})
|
||||
else return
|
||||
}
|
||||
|
||||
try { if (!queue.connection) await queue.connect(voiceChannel) }
|
||||
catch (error) { console.error(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 })
|
||||
|
||||
const botId = interaction.client.user.id
|
||||
const dbData = guildProfile.get("guildPlayer") as GuildPlayer
|
||||
dbData.instances ??= []
|
||||
|
||||
const instanceIndex = dbData.instances.findIndex(instance => instance.botId === botId)
|
||||
const instance = { botId, replay: {
|
||||
textChannelId: interaction.channel?.id ?? "",
|
||||
voiceChannelId: voiceChannel.id,
|
||||
trackUrl: "",
|
||||
progress: 0
|
||||
} }
|
||||
|
||||
if (instanceIndex === -1) dbData.instances.push(instance)
|
||||
else dbData.instances[instanceIndex] = instance
|
||||
|
||||
guildProfile.set("guildPlayer", dbData)
|
||||
guildProfile.markModified("guildPlayer")
|
||||
await guildProfile.save().catch(console.error)
|
||||
|
||||
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]
|
||||
|
||||
const entry = queue.tasksQueue.acquire()
|
||||
await entry.getTask()
|
||||
queue.addTrack(track)
|
||||
|
||||
try {
|
||||
if (!queue.isPlaying()) await queue.node.play()
|
||||
startProgressSaving(queue.guild.id, botId)
|
||||
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) }
|
||||
finally { queue.tasksQueue.release() }
|
||||
}
|
||||
|
||||
export async function autocompleteRun(interaction: AutocompleteInteraction) {
|
||||
const query = interaction.options.getString("search", true)
|
||||
if (!query) return interaction.respond([])
|
||||
|
||||
const player = useMainPlayer()
|
||||
|
||||
const resultsSpotify = await player.search(query, { searchEngine: `ext:${SpotifyExtractor.identifier}` })
|
||||
const resultsYouTube = await player.search(query, { searchEngine: `ext:${YoutubeiExtractor.identifier}` })
|
||||
|
||||
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})`}`,
|
||||
value: t.url
|
||||
}))
|
||||
const tracksYouTube = resultsYouTube.tracks.slice(0, 5).map(t => ({
|
||||
name: `YouTube: ${`${t.title} - ${t.author} (${t.duration})`.length > 75 ? `${`${t.title} - ${t.author}`.substring(0, 75)}... (${t.duration})` : `${t.title} - ${t.author} (${t.duration})`}`,
|
||||
value: t.url
|
||||
}))
|
||||
|
||||
const tracks: TrackSearchResult[] = []
|
||||
tracksSpotify.forEach((t) => tracks.push({ name: t.name, value: t.value }))
|
||||
tracksYouTube.forEach((t) => tracks.push({ name: t.name, value: t.value }))
|
||||
|
||||
return interaction.respond(tracks)
|
||||
}
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import { SlashCommandBuilder, ChatInputCommandInteraction } from 'discord.js'
|
||||
import { useHistory } from 'discord-player'
|
||||
|
||||
export default {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('previous')
|
||||
.setDescription('Joue la musique précédente.'),
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
let history = useHistory(interaction.guild?.id ?? '')
|
||||
if (!history) return await interaction.reply('Il n\'y a pas d\'historique de musique !')
|
||||
|
||||
await history.previous()
|
||||
return await interaction.reply('Musique précédente jouée !')
|
||||
}
|
||||
}
|
||||
import { SlashCommandBuilder, MessageFlags } from "discord.js"
|
||||
import type { ChatInputCommandInteraction } from "discord.js"
|
||||
import { useHistory } from "discord-player"
|
||||
import { t } from "@/utils/i18n"
|
||||
|
||||
export const data = new SlashCommandBuilder()
|
||||
.setName("previous")
|
||||
.setDescription("Play the previous song")
|
||||
.setNameLocalizations({ fr: "precedent" })
|
||||
.setDescriptionLocalizations({ fr: "Joue la musique précédente" })
|
||||
|
||||
export async function execute(interaction: ChatInputCommandInteraction) {
|
||||
const history = useHistory(interaction.guild?.id ?? "")
|
||||
if (!history) return interaction.reply({ content: t(interaction.locale, "player.no_session"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
await history.previous()
|
||||
return interaction.reply({ content: t(interaction.locale, "player.previous_played"), flags: MessageFlags.Ephemeral })
|
||||
}
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
import { SlashCommandBuilder, ChatInputCommandInteraction } from 'discord.js'
|
||||
import { useQueue } from 'discord-player'
|
||||
|
||||
export default {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('queue')
|
||||
.setDescription("Récupérer la file d'attente."),
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
let queue = useQueue(interaction.guild?.id ?? '')
|
||||
if (!queue) return interaction.reply({ content: 'Aucune file d\'attente en cours, recherche une musique plutôt !' })
|
||||
if (!queue.currentTrack) return interaction.reply({ content: 'Aucune musique en cours de lecture.' })
|
||||
|
||||
let track = `[${queue.currentTrack.title}](${queue.currentTrack.url})`
|
||||
let tracks = queue.tracks.map((track, index) => { return `${index + 1}. [${track.title}](${track.url})` })
|
||||
if (tracks.length === 0) return interaction.reply({ content: `Lecture en cours : ${track} \nAucune musique dans la file d'attente.` })
|
||||
|
||||
return interaction.reply({ content: `Lecture en cours : ${track} \nFile d'attente actuelle : \n${tracks.join('\n')}` })
|
||||
}
|
||||
}
|
||||
import { SlashCommandBuilder, MessageFlags } from "discord.js"
|
||||
import type { ChatInputCommandInteraction } from "discord.js"
|
||||
import { useQueue } from "discord-player"
|
||||
import { t } from "@/utils/i18n"
|
||||
|
||||
export const data = new SlashCommandBuilder()
|
||||
.setName("queue")
|
||||
.setDescription("Get the queue")
|
||||
.setNameLocalizations({ fr: "file" })
|
||||
.setDescriptionLocalizations({ fr: "Récupérer la file d'attente." })
|
||||
|
||||
export async function execute(interaction: ChatInputCommandInteraction) {
|
||||
const queue = useQueue(interaction.guild?.id ?? "")
|
||||
if (!queue) return interaction.reply({ content: t(interaction.locale, "player.no_queue_search_instead"), flags: MessageFlags.Ephemeral})
|
||||
if (!queue.currentTrack) return interaction.reply({ content: t(interaction.locale, "player.no_track_playing"), flags: MessageFlags.Ephemeral})
|
||||
|
||||
const track = `[${queue.currentTrack.title}](${queue.currentTrack.url})`
|
||||
const tracks = queue.tracks.map((track, index) => { return `${index + 1}. [${track.title}](${track.url})` })
|
||||
if (tracks.length === 0) return interaction.reply({ content: t(interaction.locale, "player.now_playing_no_queue", { track }) })
|
||||
|
||||
return interaction.reply({ content: t(interaction.locale, "player.now_playing_with_queue", { track, tracks: tracks.join("\n") }) })
|
||||
}
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import { SlashCommandBuilder, ChatInputCommandInteraction } from 'discord.js'
|
||||
import { useQueue } from 'discord-player'
|
||||
|
||||
export default {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('resume')
|
||||
.setDescription('Reprendre la musique.'),
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
let queue = useQueue(interaction.guild?.id ?? '')
|
||||
if (!queue) return interaction.followUp({ content: 'Aucune file d\'attente en cours, recherche une musique plutôt !' })
|
||||
|
||||
queue.node.setPaused(!queue.node.isPaused())
|
||||
return await interaction.reply('Musique reprise !')
|
||||
}
|
||||
}
|
||||
import { SlashCommandBuilder, MessageFlags } from "discord.js"
|
||||
import type { ChatInputCommandInteraction } from "discord.js"
|
||||
import { useQueue } from "discord-player"
|
||||
import { t } from "@/utils/i18n"
|
||||
|
||||
export const data = new SlashCommandBuilder()
|
||||
.setName("resume")
|
||||
.setDescription("Resume the music")
|
||||
.setNameLocalizations({ fr: "reprendre" })
|
||||
.setDescriptionLocalizations({ fr: "Reprendre la musique" })
|
||||
|
||||
export async function execute(interaction: ChatInputCommandInteraction) {
|
||||
const queue = useQueue(interaction.guild?.id ?? "")
|
||||
if (!queue) return interaction.followUp({ content: t(interaction.locale, "player.no_queue_search_instead"), flags: MessageFlags.Ephemeral})
|
||||
|
||||
queue.node.setPaused(!queue.node.isPaused())
|
||||
return interaction.reply(t(interaction.locale, "player.resumed"))
|
||||
}
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import { SlashCommandBuilder, ChatInputCommandInteraction } from 'discord.js'
|
||||
import { useQueue } from 'discord-player'
|
||||
|
||||
export default {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('shuffle')
|
||||
.setDescription('Mélange la file d\'attente.'),
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
let queue = useQueue(interaction.guild?.id ?? '')
|
||||
if (!queue) return interaction.followUp({ content: 'Aucune file d\'attente en cours, recherche une musique plutôt !' })
|
||||
|
||||
queue.tracks.shuffle()
|
||||
return await interaction.reply('File d\'attente mélangée !')
|
||||
}
|
||||
}
|
||||
import { SlashCommandBuilder, MessageFlags } from "discord.js"
|
||||
import type { ChatInputCommandInteraction } from "discord.js"
|
||||
import { useQueue } from "discord-player"
|
||||
import { t } from "@/utils/i18n"
|
||||
|
||||
export const data = new SlashCommandBuilder()
|
||||
.setName("shuffle")
|
||||
.setDescription("Shuffle the queue")
|
||||
.setNameLocalizations({ fr: "melanger" })
|
||||
.setDescriptionLocalizations({ fr: "Mélange la file d'attente" })
|
||||
|
||||
export async function execute(interaction: ChatInputCommandInteraction) {
|
||||
const queue = useQueue(interaction.guild?.id ?? "")
|
||||
if (!queue) return interaction.followUp({ content: t(interaction.locale, "player.no_queue_search_instead"), flags: MessageFlags.Ephemeral})
|
||||
|
||||
queue.tracks.shuffle()
|
||||
return interaction.reply(t(interaction.locale, "player.shuffled"))
|
||||
}
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import { SlashCommandBuilder, ChatInputCommandInteraction } from 'discord.js'
|
||||
import { useQueue } from 'discord-player'
|
||||
|
||||
export default {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('skip')
|
||||
.setDescription('Passer la musique en cours.'),
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
let queue = useQueue(interaction.guild?.id ?? '')
|
||||
if (!queue) return interaction.followUp({ content: 'Aucune file d\'attente en cours, recherche une musique plutôt !' })
|
||||
|
||||
queue.node.skip()
|
||||
return await interaction.reply('Musique passée !')
|
||||
}
|
||||
}
|
||||
import { SlashCommandBuilder, MessageFlags } from "discord.js"
|
||||
import type { ChatInputCommandInteraction } from "discord.js"
|
||||
import { useQueue } from "discord-player"
|
||||
import { t } from "@/utils/i18n"
|
||||
|
||||
export const data = new SlashCommandBuilder()
|
||||
.setName("skip")
|
||||
.setDescription("Skip the current song")
|
||||
.setNameLocalizations({ fr: "passer" })
|
||||
.setDescriptionLocalizations({ fr: "Passer la musique en cours" })
|
||||
|
||||
export async function execute(interaction: ChatInputCommandInteraction) {
|
||||
const queue = useQueue(interaction.guild?.id ?? "")
|
||||
if (!queue) return interaction.followUp({ content: t(interaction.locale, "player.no_queue_search_instead"), flags: MessageFlags.Ephemeral})
|
||||
|
||||
queue.node.skip()
|
||||
return interaction.reply(t(interaction.locale, "player.skipped"))
|
||||
}
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
import { SlashCommandBuilder, ChatInputCommandInteraction } from 'discord.js'
|
||||
import { useQueue } from 'discord-player'
|
||||
|
||||
export default {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('stop')
|
||||
.setDescription('Arrêter la musique.'),
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
let queue = useQueue(interaction.guild?.id ?? '')
|
||||
if (!queue) return interaction.followUp({ content: 'Aucune file d\'attente en cours, recherche une musique plutôt !' })
|
||||
|
||||
queue.delete()
|
||||
return await interaction.reply('Musique arrêtée !')
|
||||
}
|
||||
}
|
||||
import { SlashCommandBuilder, MessageFlags } from "discord.js"
|
||||
import type { ChatInputCommandInteraction } from "discord.js"
|
||||
import { useQueue } from "discord-player"
|
||||
import { stopProgressSaving } from "@/utils/player"
|
||||
import { t } from "@/utils/i18n"
|
||||
|
||||
export const data = new SlashCommandBuilder()
|
||||
.setName("stop")
|
||||
.setDescription("Stop the music")
|
||||
.setNameLocalizations({ fr: "arreter" })
|
||||
.setDescriptionLocalizations({ fr: "Arrêter la musique" })
|
||||
|
||||
export async function execute(interaction: ChatInputCommandInteraction) {
|
||||
await stopProgressSaving(interaction.guild?.id ?? "", interaction.client.user.id)
|
||||
|
||||
const queue = useQueue(interaction.guild?.id ?? "")
|
||||
if (!queue) return interaction.followUp({ content: t(interaction.locale, "player.no_queue_search_instead"), flags: MessageFlags.Ephemeral})
|
||||
|
||||
queue.delete()
|
||||
return interaction.reply(t(interaction.locale, "player.stopped"))
|
||||
}
|
||||
|
||||
@@ -1,21 +1,26 @@
|
||||
import { SlashCommandBuilder, ChatInputCommandInteraction } from 'discord.js'
|
||||
import { useQueue } from 'discord-player'
|
||||
|
||||
export default {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('volume')
|
||||
.setDescription('Modifie le volume de la musique.')
|
||||
.addIntegerOption(option => option.setName('volume')
|
||||
.setDescription('Le volume à mettre (%)')
|
||||
.setRequired(true)
|
||||
.setMinValue(1)
|
||||
.setMaxValue(100)),
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
let volume = interaction.options.getInteger('volume')
|
||||
let queue = useQueue(interaction.guild?.id ?? '')
|
||||
if (!queue) return interaction.followUp({ content: 'Aucune file d\'attente en cours, recherche une musique plutôt !' })
|
||||
|
||||
queue.node.setVolume(volume as number)
|
||||
return await interaction.reply(`Volume modifié à ${volume}% !`)
|
||||
}
|
||||
}
|
||||
import { SlashCommandBuilder, MessageFlags } from "discord.js"
|
||||
import type { ChatInputCommandInteraction } from "discord.js"
|
||||
import { useQueue } from "discord-player"
|
||||
import { t } from "@/utils/i18n"
|
||||
|
||||
export const data = new SlashCommandBuilder()
|
||||
.setName("volume")
|
||||
.setDescription("Change the music volume")
|
||||
.setDescriptionLocalizations({ fr: "Modifie le volume de la musique" })
|
||||
.addIntegerOption(option => option
|
||||
.setName("volume")
|
||||
.setDescription("The volume to set (%)")
|
||||
.setDescriptionLocalizations({ fr: "Le volume à mettre (%)" })
|
||||
.setRequired(true)
|
||||
.setMinValue(0)
|
||||
.setMaxValue(100)
|
||||
)
|
||||
|
||||
export async function execute(interaction: ChatInputCommandInteraction) {
|
||||
const volume = interaction.options.getInteger("volume", true)
|
||||
const queue = useQueue(interaction.guild?.id ?? "")
|
||||
if (!queue) return interaction.followUp({ content: t(interaction.locale, "player.no_queue_search_instead"), flags: MessageFlags.Ephemeral})
|
||||
|
||||
queue.node.setVolume(volume)
|
||||
return interaction.reply(t(interaction.locale, "player.volume_changed", { volume: volume.toString() }))
|
||||
}
|
||||
|
||||
@@ -1,58 +1,70 @@
|
||||
import { SlashCommandBuilder, EmbedBuilder, ChatInputCommandInteraction, MessageReaction, User }from 'discord.js'
|
||||
import * as crack from '../../utils/crack'
|
||||
|
||||
export default {
|
||||
data: new SlashCommandBuilder().setName('crack').setDescription('Télécharge un crack sur le site online-fix.me !')
|
||||
.addStringOption(option => option.setName('jeu').setDescription('Quel jeu tu veux DL ?').setRequired(true)),
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
await interaction.deferReply()
|
||||
|
||||
let query = interaction.options.getString('jeu')
|
||||
if (!query) return
|
||||
|
||||
let games = await crack.search(query) as crack.Game[]
|
||||
if (!Array.isArray(games)) {
|
||||
//if (games.toString() == "TypeError: Cannot read properties of undefined (reading 'split')") return interaction.followUp({ content: `J'ai rien trouvé pour "${query}" !` })
|
||||
//else return interaction.followUp({ content: "Une erreur s'est produite ! ```" + games + "```" })
|
||||
return interaction.followUp({ content: `J'ai rien trouvé pour "${query}" !` })
|
||||
}
|
||||
|
||||
let game = {} as crack.Game
|
||||
if (games.length > 1) {
|
||||
games = games.slice(0, 9)
|
||||
let list = ''
|
||||
for (let i = 0; i < games.length; i++) list += `\n${i + 1}. ${games[i].name} (${games[i].link})`
|
||||
let message = await interaction.followUp({ content: `J'ai trouvé plusieurs jeux pour "${query}" ! ${list}` })
|
||||
|
||||
let emojis = ['1️⃣', '2️⃣', '3️⃣', '4️⃣', '5️⃣', '6️⃣', '7️⃣', '8️⃣', '9️⃣']
|
||||
for (let i = 0; i < games.length; i++) await message.react(emojis[i])
|
||||
|
||||
// Wait for a reaction to be added by the interaction author.
|
||||
const filter = (reaction: MessageReaction, user: User) => { if (reaction.emoji.name) { return emojis.includes(reaction.emoji.name) && user.id === interaction.user.id } return false }
|
||||
await message.awaitReactions({ filter, max: 1, time: 5000, errors: ['time'] }).then(collected => {
|
||||
console.log(collected)
|
||||
if (!collected.first) return
|
||||
let reaction = collected.first()
|
||||
let index = emojis.indexOf(reaction?.emoji.name ?? '')
|
||||
game = games[index]
|
||||
}).catch(() => { return interaction.followUp({ content: "T'as mis trop de temps à choisir !" }) })
|
||||
}
|
||||
else game = games[0]
|
||||
|
||||
let url = await crack.repo(game)
|
||||
if (!url) return
|
||||
let file = await crack.torrent(url)
|
||||
if (!file) return
|
||||
let filePath = await crack.download(url, file)
|
||||
if (!filePath) return
|
||||
let link = await crack.magnet(filePath)
|
||||
|
||||
let embed = new EmbedBuilder()
|
||||
.setColor('#ffc370')
|
||||
.setTitle(game.name)
|
||||
.setURL(game.link)
|
||||
.setDescription(`Voici ce que j'ai trouvé pour "${query}".\nTu peux aussi cliquer sur [ce lien](https://angels-dev.fr/magnet/${link}) pour pouvoir télécharger le jeu direct !`)
|
||||
|
||||
await interaction.followUp({ embeds: [embed], files: [filePath] })
|
||||
}
|
||||
}
|
||||
import { SlashCommandBuilder, EmbedBuilder, MessageFlags } from "discord.js"
|
||||
import type { ChatInputCommandInteraction, MessageReaction, User } from "discord.js"
|
||||
import { search, repo, torrent, download, magnet } from "@/utils/crack"
|
||||
import type { CrackGame } from "@/types"
|
||||
import { t } from "@/utils/i18n"
|
||||
|
||||
export const data = new SlashCommandBuilder()
|
||||
.setName("crack")
|
||||
.setDescription("Download a crack from online-fix.me")
|
||||
.setDescriptionLocalizations({ fr: "Télécharge un crack sur online-fix.me" })
|
||||
.addStringOption(option => option
|
||||
.setName("game")
|
||||
.setDescription("What game do you want to download?")
|
||||
.setNameLocalizations({ fr: "jeu" })
|
||||
.setDescriptionLocalizations({ fr: "Quel jeu tu veux télécharger ?" })
|
||||
.setRequired(true)
|
||||
)
|
||||
|
||||
export async function execute(interaction: ChatInputCommandInteraction) {
|
||||
await interaction.deferReply()
|
||||
|
||||
const query = interaction.options.getString("game", true)
|
||||
let games = await search(query)
|
||||
if (!Array.isArray(games)) return interaction.followUp({ content: t(interaction.locale, "salonpostam.crack.no_games_found", { query }), flags: MessageFlags.Ephemeral })
|
||||
|
||||
let game = {} as CrackGame
|
||||
if (games.length > 1) {
|
||||
games = games.slice(0, 9)
|
||||
|
||||
let list = ""
|
||||
for (let i = 0; i < games.length; i++) list += `\n${i + 1}. ${games[i].name} (${games[i].link})`
|
||||
const message = await interaction.followUp({ content: t(interaction.locale, "salonpostam.crack.multiple_games_found", { query, list }) })
|
||||
|
||||
const emojis = ["1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣", "9️⃣"]
|
||||
for (let i = 0; i < games.length; i++) await message.react(emojis[i])
|
||||
|
||||
// Wait for a reaction to be added by the interaction author.
|
||||
const filter = (reaction: MessageReaction, user: User) => {
|
||||
if (reaction.emoji.name) return (emojis.includes(reaction.emoji.name) && user.id === interaction.user.id)
|
||||
return false
|
||||
}
|
||||
await message.awaitReactions({ filter, max: 1, time: 5000, errors: ["time"] }).then(collected => {
|
||||
console.log(collected)
|
||||
const reaction = collected.first()
|
||||
const index = emojis.indexOf(reaction?.emoji.name ?? "")
|
||||
|
||||
if (!games) return
|
||||
game = games[index]
|
||||
})
|
||||
.catch(() => { return interaction.followUp({ content: t(interaction.locale, "salonpostam.crack.selection_timeout"), flags: MessageFlags.Ephemeral }) })
|
||||
} else game = games[0]
|
||||
|
||||
const url = await repo(game)
|
||||
if (!url) return
|
||||
|
||||
const file = await torrent(url)
|
||||
if (!file) return
|
||||
|
||||
const filePath = await download(url, file)
|
||||
if (!filePath) return
|
||||
|
||||
const link = magnet(filePath)
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor("#ffc370")
|
||||
.setTitle(game.name)
|
||||
.setURL(game.link)
|
||||
.setDescription(t(interaction.locale, "salonpostam.crack.game_found", { query, link: `https://angels-dev.fr/magnet/${link}` }))
|
||||
|
||||
await interaction.followUp({ embeds: [embed], files: [filePath] })
|
||||
}
|
||||
|
||||
@@ -1,151 +0,0 @@
|
||||
import { SlashCommandBuilder, ChatInputCommandInteraction, EmbedBuilder, Message, inlineCode } from 'discord.js'
|
||||
import * as Freebox from '../../utils/freebox'
|
||||
import dbGuild from '../../schemas/guild'
|
||||
import crypto from 'crypto'
|
||||
import https from 'https'
|
||||
//import path from 'path'
|
||||
//import fs from 'fs'
|
||||
|
||||
interface ReturnMsgData {
|
||||
status: string
|
||||
error_code?: string
|
||||
Title?: string
|
||||
Message?: string
|
||||
}
|
||||
|
||||
function returnMsg(result: ReturnMsgData) {
|
||||
if (result.status === 'fail') return `La commande a échouée !\n${inlineCode(`${result.Title}: ${result.Message}`)}`
|
||||
if (result.status === 'error') return `Y'a eu une erreur !\n${inlineCode(`${result.error_code}`)}`
|
||||
}
|
||||
|
||||
export default {
|
||||
data: new SlashCommandBuilder().setName('freebox').setDescription("Accéder à l'API FreeboxOS !")
|
||||
.addSubcommand(subcommand => subcommand.setName('import').setDescription("Envoyer un fichier d'autorité de certification."))
|
||||
.addSubcommand(subcommand => subcommand.setName('version').setDescription("Afficher la version de l'API."))
|
||||
.addSubcommand(subcommand => subcommand.setName('init').setDescription("Créer une app sur la Freebox pour s'authentifier."))
|
||||
.addSubcommandGroup(subcommandGroup => subcommandGroup.setName('get').setDescription('Récupérer des données.')
|
||||
.addSubcommand(subcommand => subcommand.setName('connection').setDescription('Récupérer les informations de connexion.'))
|
||||
),
|
||||
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
let guildProfile = await dbGuild.findOne({ guildId: interaction?.guild?.id })
|
||||
if (!guildProfile) return interaction.reply({ content: `Database data for **${interaction.guild?.name}** does not exist, please initialize with \`/database init\` !` })
|
||||
|
||||
let dbData = guildProfile.get('guildFbx')
|
||||
if (!dbData?.enabled) return interaction.reply({ content: `Freebox module is disabled for **${interaction.guild?.name}**, please activate with \`/database edit guildFbx.enabled True\` !` })
|
||||
|
||||
let host = dbData.host as string
|
||||
if (!host) return interaction.reply({ content: `Freebox host is not set for **${interaction.guild?.name}**, please set with \`/database edit guildFbx.host <host>\` !` })
|
||||
let version = dbData.version as number
|
||||
if (!version) return interaction.reply({ content: `Freebox API version is not set for **${interaction.guild?.name}**, please set with \`/database edit guildFbx.version <version>\` !` })
|
||||
|
||||
let httpsOptions = {}
|
||||
//let caCrt = fs.readFileSync(path.resolve(__dirname, '../../static/freebox-ecc-root-ca.crt'))
|
||||
// MIME Type : application/x-x509-ca-cert
|
||||
//if (caCrt) httpsOptions = { ca: caCrt }
|
||||
let httpsAgent = new https.Agent(httpsOptions)
|
||||
|
||||
|
||||
if (interaction.options.getSubcommand() === 'import') {
|
||||
let filter = (m: Message) => m.author.id === interaction.user.id
|
||||
|
||||
await interaction.reply({ content: 'Please send another message with the CA file attached, you have one minute.', fetchReply: true }).then(async () => {
|
||||
console.log('waiting for message')
|
||||
await interaction.channel?.awaitMessages({ filter, time: 60_000, errors: ['time'] }).then(async collected => {
|
||||
console.log(collected)
|
||||
let message = collected.first()
|
||||
if (!message?.attachments.size) return interaction.followUp('No file was sent in your message!')
|
||||
|
||||
let attachment = message.attachments.first()
|
||||
console.log(attachment)
|
||||
|
||||
// Save the file to the database // TODO
|
||||
|
||||
interaction.followUp(`File saved, you can now interact with your Freebox!`)
|
||||
}).catch(() => interaction.followUp('No message was sent before the time limit!'))
|
||||
})
|
||||
}
|
||||
else if (interaction.options.getSubcommand() === 'version') {
|
||||
let result = await Freebox.Core.Version(host, httpsAgent)
|
||||
if (result.status === 'success') {
|
||||
let embed = new EmbedBuilder()
|
||||
embed.setTitle('FreeboxOS API Version')
|
||||
embed.setDescription(`Version: ${result.data.api_version}`)
|
||||
return await interaction.reply({ embeds: [embed] })
|
||||
}
|
||||
else if (result.status === 'fail') return await interaction.reply({ content: `Failed to retrieve the API version: ${result.data}`, ephemeral: true })
|
||||
else if (result.status === 'error') return await interaction.reply({ content: `An error occurred while retrieving the API version: ${result.data}`, ephemeral: true })
|
||||
}
|
||||
else if (interaction.options.getSubcommand() === 'init') {
|
||||
await interaction.deferReply({ ephemeral: true })
|
||||
|
||||
let app = {
|
||||
app_id: 'fr.angels.bot_tamiseur',
|
||||
app_name: 'Bot Tamiseur',
|
||||
app_version: '2.3.0',
|
||||
device_name: 'Bot Discord NodeJS'
|
||||
}
|
||||
let result = await Freebox.Core.Init(host, version, httpsAgent, app, '')
|
||||
if (result.status === 'success') {
|
||||
let appToken = result.data.app_token
|
||||
let trackId = result.data.track_id
|
||||
|
||||
let initCheck = setInterval(async () => {
|
||||
let result = await Freebox.Core.Init(host, version, httpsAgent, app, trackId)
|
||||
if (result.status !== 'success') return await interaction.followUp(returnMsg(result) as string)
|
||||
|
||||
let status = result.data.status
|
||||
if (status === 'granted') {
|
||||
clearInterval(initCheck)
|
||||
let password_salt = result.data.password_salt
|
||||
|
||||
if (!dbData) return
|
||||
dbData['appToken'] = appToken
|
||||
dbData['password_salt'] = password_salt
|
||||
|
||||
if (!guildProfile) return
|
||||
guildProfile.set('guildFbx', dbData)
|
||||
guildProfile.markModified('guildFbx')
|
||||
await guildProfile.save().catch(console.error)
|
||||
|
||||
return await interaction.followUp('Done !')
|
||||
}
|
||||
else if (status === 'denied') {
|
||||
clearInterval(initCheck)
|
||||
return await interaction.followUp('The user denied the app access to the Freebox.')
|
||||
}
|
||||
else if (status === 'pending') return
|
||||
}, 2000)
|
||||
} else return await interaction.followUp({ content: returnMsg(result) as string, ephemeral: true })
|
||||
}
|
||||
else if (interaction.options.getSubcommandGroup() === 'get') {
|
||||
let appToken = dbData.appToken as string
|
||||
if (!appToken) return await interaction.reply({ content: `Freebox appToken is not set for **${interaction.guild?.name}**, please init the app with \`/freebox init\` !` })
|
||||
console.log(appToken)
|
||||
|
||||
let challengeData = await Freebox.Login.Challenge(host, version, httpsAgent)
|
||||
if (!challengeData) return await interaction.reply({ content: `Failed to retrieve the challenge for **${interaction.guild?.name}** !` })
|
||||
let challenge = challengeData.data.challenge
|
||||
console.log(challenge)
|
||||
|
||||
let password = crypto.createHmac('sha1', appToken).update(challenge).digest('hex')
|
||||
console.log(password)
|
||||
|
||||
let session = await Freebox.Login.Session(host, version, httpsAgent, 'fr.angels.bot_tamiseur', password)
|
||||
if (!session) return await interaction.reply({ content: `Failed to retrieve the session for **${interaction.guild?.name}** !` })
|
||||
|
||||
let sessionToken = dbData['sessionToken'] = session.data.session_token
|
||||
|
||||
guildProfile.set('guildFbx', dbData)
|
||||
guildProfile.markModified('guildFbx')
|
||||
await guildProfile.save().catch(console.error)
|
||||
|
||||
if (interaction.options.getSubcommand() === 'connection') {
|
||||
let connection = await Freebox.Get.Connection(host, version, httpsAgent, sessionToken)
|
||||
if (!connection) return await interaction.reply({ content: `Failed to retrieve the connection details for **${interaction.guild?.name}** !` })
|
||||
|
||||
return await interaction.reply({ content: `Connection details for **${interaction.guild?.name}**:\n${inlineCode(JSON.stringify(connection))}` })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
15
src/commands/salonpostam/index.ts
Normal file
15
src/commands/salonpostam/index.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import * as crack from "./crack"
|
||||
import * as papa from "./papa"
|
||||
import * as parle from "./parle"
|
||||
import * as spam from "./spam"
|
||||
import * as update from "./update"
|
||||
|
||||
import type { Command } from "@/types"
|
||||
|
||||
export default [
|
||||
crack,
|
||||
papa,
|
||||
parle,
|
||||
spam,
|
||||
update
|
||||
] as Command[]
|
||||
@@ -1,34 +1,34 @@
|
||||
import { SlashCommandBuilder, ChatInputCommandInteraction, GuildMember } from 'discord.js'
|
||||
import { getVoiceConnection, joinVoiceChannel } from '@discordjs/voice'
|
||||
|
||||
export default {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('papa')
|
||||
.setDescription('Si papa m\'appelle, je le rejoins !'),
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
if (interaction.user.id !== '223831938346123275') return interaction.reply({ content: 'T\'es pas mon père, dégage !' })
|
||||
|
||||
let guild = interaction.guild
|
||||
if (!guild) return interaction.reply({ content: 'Je ne peux pas rejoindre ton vocal en message privé, papa !' })
|
||||
|
||||
let member = interaction.member as GuildMember
|
||||
|
||||
let botChannel = guild.members.me?.voice.channel
|
||||
let papaChannel = member.voice.channel
|
||||
|
||||
if (!papaChannel && botChannel) {
|
||||
const voiceConnection = getVoiceConnection(guild.id);
|
||||
if (voiceConnection) voiceConnection.destroy()
|
||||
return interaction.reply({ content: 'Je quitte le vocal, papa !' })
|
||||
}
|
||||
else if (papaChannel && (!botChannel || botChannel.id !== papaChannel.id)) {
|
||||
joinVoiceChannel({
|
||||
channelId: papaChannel.id,
|
||||
guildId: papaChannel.guild.id,
|
||||
adapterCreator: papaChannel.guild.voiceAdapterCreator,
|
||||
})
|
||||
return interaction.reply({ content: 'Je rejoins ton vocal, papa !' })
|
||||
}
|
||||
else return interaction.reply({ content: 'Je suis déjà dans ton vocal, papa !' })
|
||||
}
|
||||
}
|
||||
import { SlashCommandBuilder } from "discord.js"
|
||||
import type { ChatInputCommandInteraction, GuildMember } from "discord.js"
|
||||
import { getVoiceConnection, joinVoiceChannel } from "@discordjs/voice"
|
||||
import { t } from "@/utils/i18n"
|
||||
|
||||
export const data = new SlashCommandBuilder()
|
||||
.setName("papa")
|
||||
.setDescription("If daddy calls me, I join him")
|
||||
.setDescriptionLocalizations({ fr: "Si papa m'appelle, je le rejoins" })
|
||||
|
||||
export async function execute(interaction: ChatInputCommandInteraction) {
|
||||
if (interaction.user.id !== "223831938346123275") return interaction.reply({ content: t(interaction.locale, "salonpostam.papa.not_your_father") })
|
||||
|
||||
const guild = interaction.guild
|
||||
if (!guild) return interaction.reply({ content: t(interaction.locale, "salonpostam.papa.no_dm") })
|
||||
|
||||
const member = interaction.member as GuildMember
|
||||
|
||||
const botChannel = guild.members.me?.voice.channel
|
||||
const papaChannel = member.voice.channel
|
||||
|
||||
if (!papaChannel && botChannel) {
|
||||
const voiceConnection = getVoiceConnection(guild.id)
|
||||
if (voiceConnection) voiceConnection.destroy()
|
||||
return interaction.reply({ content: t(interaction.locale, "salonpostam.papa.leaving_voice") })
|
||||
} else if (papaChannel && (!botChannel || botChannel.id !== papaChannel.id)) {
|
||||
joinVoiceChannel({
|
||||
channelId: papaChannel.id,
|
||||
guildId: papaChannel.guild.id,
|
||||
adapterCreator: papaChannel.guild.voiceAdapterCreator,
|
||||
})
|
||||
return interaction.reply({ content: t(interaction.locale, "salonpostam.papa.joining_voice") })
|
||||
} else return interaction.reply({ content: t(interaction.locale, "salonpostam.papa.already_connected") })
|
||||
}
|
||||
|
||||
@@ -1,79 +1,84 @@
|
||||
import { SlashCommandBuilder, ChatInputCommandInteraction, GuildMember } from 'discord.js'
|
||||
import { joinVoiceChannel, createAudioPlayer, createAudioResource, AudioPlayerStatus, EndBehaviorType } from '@discordjs/voice'
|
||||
|
||||
export default {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('parle')
|
||||
.setDescription('Fais moi parler par dessus quelqu\'un de chiant dans le vocal')
|
||||
.addUserOption(option => option.setName('user').setDescription('La personne en question').setRequired(true)),
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
if (interaction.user.id !== '223831938346123275') return await interaction.reply({ content: 'Tu n\'as pas le droit d\'utiliser cette commande !', ephemeral: true })
|
||||
|
||||
let user = interaction.options.getUser('user')
|
||||
if (!user) return
|
||||
let guild = interaction.guild
|
||||
if (!guild) return
|
||||
let member = guild.members.cache.get(user.id) as GuildMember
|
||||
if (!member) return
|
||||
let caller = interaction.member as GuildMember
|
||||
if (!caller) return
|
||||
|
||||
if (!caller.voice.channel) return await interaction.reply({ content: 'You must be in a voice channel to use this command.', ephemeral: true })
|
||||
if (!member.voice.channel) return await interaction.reply({ content: 'The member must be in a voice channel to use this command.', ephemeral: true })
|
||||
if (caller.voice.channelId !== member.voice.channelId) return await interaction.reply({ content: 'You must be in the same voice channel than the member to use this command.', ephemeral: true })
|
||||
|
||||
await interaction.reply({ content: 'Je vais parler par dessus cette personne !', ephemeral: true })
|
||||
|
||||
/*
|
||||
// Searches for audio files uploaded in the channel
|
||||
let messages = await interaction.channel.messages.fetch({ limit: 10, cache: false })
|
||||
messages = messages.filter(m => m.attachments.size > 0)
|
||||
|
||||
let files = []
|
||||
await messages.forEach(m => m.attachments.forEach(a => {
|
||||
if (a.contentType === 'audio/mpeg') files.push(a)
|
||||
}))
|
||||
if (files.size === 0) return await interaction.editReply({ content: 'Aucun fichier audio trouvé dans ce channel.', ephemeral: true })
|
||||
|
||||
// Limit the number of files to the last 10
|
||||
//files = files.sort((a, b) => b.createdTimestamp - a.createdTimestamp).first(10)
|
||||
|
||||
// Ask the user to choose a file
|
||||
let file = await interaction.channel.send({ content: 'Choisissez un fichier audio :', files: files })
|
||||
let filter = m => m.author.id === interaction.user.id && !isNaN(m.content) && parseInt(m.content) > 0 && parseInt(m.content) <= files.size
|
||||
let response = await interaction.channel.awaitMessages({ filter, max: 1, time: 30000, errors: ['time'] })
|
||||
file = files.get(files.keyArray()[response.first().content - 1])
|
||||
*/
|
||||
|
||||
let playing = false
|
||||
let player = createAudioPlayer()
|
||||
player.on(AudioPlayerStatus.Idle, () => { playing = false })
|
||||
|
||||
let connection = joinVoiceChannel({
|
||||
channelId: caller.voice.channelId as string,
|
||||
guildId: interaction.guildId as string,
|
||||
adapterCreator: guild.voiceAdapterCreator,
|
||||
selfDeaf: false
|
||||
})
|
||||
connection.subscribe(player)
|
||||
|
||||
let stream = connection.receiver.subscribe(user.id, { end: { behavior: EndBehaviorType.Manual } })
|
||||
stream.on('data', () => {
|
||||
if (!user) return
|
||||
if (connection.receiver.speaking.users.has(user.id) && !playing) {
|
||||
playing = true
|
||||
let resource = createAudioResource('../../static/parle.mp3', { inlineVolume: true })
|
||||
//let resource = createAudioResource(file.attachments.first().url, { inlineVolume: true })
|
||||
if (resource.volume) resource.volume.setVolume(0.2)
|
||||
player.play(resource)
|
||||
}
|
||||
})
|
||||
|
||||
interaction.client.on('voiceStateUpdate', (oldState, newState) => {
|
||||
if (oldState.id === member.id && newState.channelId !== caller.voice.channelId) {
|
||||
stream.destroy()
|
||||
connection.disconnect()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
import { SlashCommandBuilder, MessageFlags } from "discord.js"
|
||||
import type { ChatInputCommandInteraction, GuildMember } from "discord.js"
|
||||
import { joinVoiceChannel, createAudioPlayer, createAudioResource, AudioPlayerStatus, EndBehaviorType } from "@discordjs/voice"
|
||||
import { t } from "@/utils/i18n"
|
||||
|
||||
export const data = new SlashCommandBuilder()
|
||||
.setName("speak")
|
||||
.setDescription("Make me talk over someone annoying in voice chat")
|
||||
.setNameLocalizations({ fr: "parle" })
|
||||
.setDescriptionLocalizations({ fr: "Fais moi parler par dessus quelqu'un d'ennuyant dans le vocal" })
|
||||
.addUserOption(option => option
|
||||
.setName("user")
|
||||
.setDescription("The person in question")
|
||||
.setNameLocalizations({ fr: "utilisateur" })
|
||||
.setDescriptionLocalizations({ fr: "La personne en question" })
|
||||
.setRequired(true)
|
||||
)
|
||||
|
||||
export async function execute(interaction: ChatInputCommandInteraction) {
|
||||
const guild = interaction.guild
|
||||
if (!guild) return
|
||||
|
||||
const user = interaction.options.getUser("user", true)
|
||||
const member = await guild.members.fetch(user.id)
|
||||
const caller = interaction.member as GuildMember
|
||||
|
||||
if (!caller.voice.channel) return interaction.reply({ content: t(interaction.locale, "salonpostam.parle.not_in_voice"), flags: MessageFlags.Ephemeral })
|
||||
if (!member.voice.channel) return interaction.reply({ content: t(interaction.locale, "salonpostam.parle.member_not_in_voice"), flags: MessageFlags.Ephemeral })
|
||||
if (caller.voice.channelId !== member.voice.channelId) return interaction.reply({ content: t(interaction.locale, "salonpostam.parle.not_same_channel"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
await interaction.reply({ content: t(interaction.locale, "salonpostam.parle.will_speak_over"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
/*
|
||||
// Searches for audio files uploaded in the channel
|
||||
const messages = await interaction.channel.messages.fetch({ limit: 10, cache: false }).filter(m => m.attachments.size > 0)
|
||||
|
||||
const files = []
|
||||
await messages.forEach(m => m.attachments.forEach(a => {
|
||||
if (a.contentType === 'audio/mpeg') files.push(a)
|
||||
}))
|
||||
if (files.size === 0) return interaction.editReply({ content: t(interaction.locale, "player.no_audio_found"), flags: MessageFlags.Ephemeral })
|
||||
|
||||
// Limit the number of files to the last 10
|
||||
//files = files.sort((a, b) => b.createdTimestamp - a.createdTimestamp).first(10)
|
||||
|
||||
// Ask the user to choose a file
|
||||
let file = await interaction.channel.send({ content: 'Choisissez un fichier audio :', files })
|
||||
const filter = m => m.author.id === interaction.user.id && !isNaN(m.content) && parseInt(m.content) > 0 && parseInt(m.content) <= files.size
|
||||
const response = await interaction.channel.awaitMessages({ filter, max: 1, time: 30000, errors: ['time'] })
|
||||
file = files.get(files.keyArray()[response.first().content - 1])
|
||||
*/
|
||||
|
||||
let playing = false
|
||||
const player = createAudioPlayer()
|
||||
player.on(AudioPlayerStatus.Idle, () => { playing = false })
|
||||
|
||||
const connection = joinVoiceChannel({
|
||||
channelId: caller.voice.channelId ?? "",
|
||||
guildId: interaction.guildId ?? "",
|
||||
adapterCreator: guild.voiceAdapterCreator,
|
||||
selfDeaf: false
|
||||
})
|
||||
connection.subscribe(player)
|
||||
|
||||
const stream = connection.receiver.subscribe(user.id, {
|
||||
end: { behavior: EndBehaviorType.Manual }
|
||||
})
|
||||
stream.on("data", () => {
|
||||
if (connection.receiver.speaking.users.has(user.id) && !playing) {
|
||||
playing = true
|
||||
const resource = createAudioResource("@/static/parle.mp3", { inlineVolume: true })
|
||||
//const resource = createAudioResource(file.attachments.first().url, { inlineVolume: true })
|
||||
if (resource.volume) resource.volume.setVolume(0.2)
|
||||
player.play(resource)
|
||||
}
|
||||
})
|
||||
|
||||
interaction.client.on("voiceStateUpdate", (oldState, newState) => {
|
||||
if (oldState.id === member.id && newState.channelId !== caller.voice.channelId ) {
|
||||
stream.destroy()
|
||||
connection.disconnect()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,29 +1,47 @@
|
||||
import { SlashCommandBuilder, ChatInputCommandInteraction } from 'discord.js'
|
||||
|
||||
export default {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('spam')
|
||||
.setDescription('Spam')
|
||||
.addUserOption(option => option.setName('user').setDescription('Spam').setRequired(true))
|
||||
.addStringOption(option => option.setName('string').setDescription('Spam').setRequired(true))
|
||||
.addIntegerOption(option => option.setName('integer').setDescription('Spam').setRequired(true)),
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
let user = interaction.options.getUser('user')
|
||||
let string = interaction.options.getString('string')
|
||||
let integer = interaction.options.getInteger('integer')
|
||||
|
||||
await interaction.reply({ content: 'Spam', ephemeral: true })
|
||||
let i = 0
|
||||
function myLoop() {
|
||||
setTimeout(function () {
|
||||
if (!user) return
|
||||
if (!string) return
|
||||
if (!integer) return
|
||||
user.send(string).catch(error => console.error(error))
|
||||
i++
|
||||
if (i < integer) myLoop()
|
||||
}, 1000)
|
||||
}
|
||||
myLoop()
|
||||
}
|
||||
}
|
||||
import { SlashCommandBuilder, MessageFlags } from "discord.js"
|
||||
import type { ChatInputCommandInteraction } from "discord.js"
|
||||
import { t } from "@/utils/i18n"
|
||||
|
||||
export const data = new SlashCommandBuilder()
|
||||
.setName("spam")
|
||||
.setDescription("Spam a user with a message")
|
||||
.setDescriptionLocalizations({ fr: "Spammer un utilisateur avec un message" })
|
||||
.addUserOption(option => option
|
||||
.setName("user")
|
||||
.setDescription("Target user")
|
||||
.setNameLocalizations({ fr: "utilisateur" })
|
||||
.setDescriptionLocalizations({ fr: "Utilisateur cible" })
|
||||
.setRequired(true)
|
||||
)
|
||||
.addStringOption(option => option
|
||||
.setName("message")
|
||||
.setDescription("Message to spam")
|
||||
.setDescriptionLocalizations({ fr: "Message à spammer" })
|
||||
.setRequired(true)
|
||||
)
|
||||
.addIntegerOption(option => option
|
||||
.setName("count")
|
||||
.setDescription("Number of times to spam")
|
||||
.setNameLocalizations({ fr: "nombre" })
|
||||
.setDescriptionLocalizations({ fr: "Nombre de fois à spammer" })
|
||||
.setRequired(true)
|
||||
.setMinValue(1)
|
||||
.setMaxValue(100)
|
||||
)
|
||||
|
||||
export async function execute(interaction: ChatInputCommandInteraction) {
|
||||
const user = interaction.options.getUser("user", true)
|
||||
const string = interaction.options.getString("message", true)
|
||||
const integer = interaction.options.getInteger("count", true)
|
||||
|
||||
await interaction.reply({ content: t(interaction.locale, "salonpostam.spam.started"), flags: MessageFlags.Ephemeral })
|
||||
let i = 0
|
||||
function myLoop() {
|
||||
setTimeout(() => {
|
||||
user.send(string).catch(console.error)
|
||||
i++
|
||||
if (i < integer) myLoop()
|
||||
}, 1000)
|
||||
}
|
||||
myLoop()
|
||||
}
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
import { SlashCommandBuilder, ChatInputCommandInteraction, Guild } from 'discord.js'
|
||||
|
||||
export default {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('update')
|
||||
.setDescription('Update the member count channel.'),
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
let guild = interaction.guild as Guild
|
||||
|
||||
guild.members.fetch().then(() => {
|
||||
let i = 0
|
||||
guild.members.cache.forEach(async member => { if (!member.user.bot) i++ })
|
||||
let channel = guild.channels.cache.get('1091140609139560508')
|
||||
if (!channel) return
|
||||
channel.setName(`${i} Gens Posés`)
|
||||
interaction.reply(`${i} Gens Posés !`)
|
||||
}).catch(console.error)
|
||||
}
|
||||
}
|
||||
import { SlashCommandBuilder, MessageFlags } from "discord.js"
|
||||
import type { ChatInputCommandInteraction } from "discord.js"
|
||||
import { t } from "@/utils/i18n"
|
||||
|
||||
export const data = new SlashCommandBuilder()
|
||||
.setName("update")
|
||||
.setDescription("Update the member count channel")
|
||||
.setDescriptionLocalizations({ fr: "Mettre à jour le canal de nombre de membres" })
|
||||
|
||||
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 })
|
||||
|
||||
guild.members.fetch().then(async () => {
|
||||
let i = 0
|
||||
guild.members.cache.forEach(member => { if (!member.user.bot) i++ })
|
||||
const channel = guild.channels.cache.get("1091140609139560508")
|
||||
if (!channel) return
|
||||
await channel.setName(`${i} Gens Posés`)
|
||||
return interaction.reply(t(interaction.locale, "salonpostam.update.members_updated", { count: i }))
|
||||
}).catch(console.error)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { Events } from 'discord.js'
|
||||
import { Events } from "discord.js"
|
||||
import { logConsoleError } from "@/utils/console"
|
||||
|
||||
export default {
|
||||
name: Events.Error,
|
||||
execute(error: Error) {
|
||||
console.error(error)
|
||||
}
|
||||
export const name = Events.Error
|
||||
export function execute(error: Error) {
|
||||
logConsoleError('discordjs', 'error', { message: error.message }, error)
|
||||
}
|
||||
@@ -1,14 +1,12 @@
|
||||
import { Events, Guild } from 'discord.js'
|
||||
import dbGuildInit from '../../utils/dbGuildInit'
|
||||
|
||||
export default {
|
||||
name: Events.GuildCreate,
|
||||
async execute(guild: Guild) {
|
||||
console.log(`Joined "${guild.name}" with ${guild.memberCount} members`)
|
||||
|
||||
let guildProfile = await dbGuildInit(guild)
|
||||
if (!guildProfile) return console.log(`An error occured while initializing database data for "${guild.name}" !`)
|
||||
|
||||
console.log(`Database data for new guild "${guildProfile.guildName}" successfully initialized !`)
|
||||
}
|
||||
}
|
||||
import { Events, Guild } from "discord.js"
|
||||
import dbGuildInit from "@/utils/dbGuildInit"
|
||||
import { logConsole } from "@/utils/console"
|
||||
|
||||
export const name = Events.GuildCreate
|
||||
export async function execute(guild: Guild) {
|
||||
logConsole('discordjs', 'guild_create', { name: guild.name, count: guild.memberCount.toString() })
|
||||
|
||||
const guildProfile = await dbGuildInit(guild)
|
||||
|
||||
logConsole('mongoose', 'guild_create', { name: guildProfile.guildName })
|
||||
}
|
||||
|
||||
@@ -1,44 +1,42 @@
|
||||
import { Events, GuildMember, EmbedBuilder, TextChannel } from 'discord.js'
|
||||
|
||||
export default {
|
||||
name: Events.GuildMemberAdd,
|
||||
async execute(member: GuildMember) {
|
||||
if (member.guild.id === '1086577543651524699') { // Salon posé tamisé
|
||||
let guild = member.guild
|
||||
|
||||
guild.members.fetch().then(() => {
|
||||
let i = 0
|
||||
guild.members.cache.forEach(async member => { if (!member.user.bot) i++ })
|
||||
|
||||
let channel = guild.channels.cache.get('1091140609139560508')
|
||||
if (!channel) return
|
||||
|
||||
channel.setName('Changement...')
|
||||
channel.setName(`${i} Gens Posés`)
|
||||
}).catch(console.error)
|
||||
} else if (member.guild.id === '796327643783626782') { // Jujul Community
|
||||
let guild = member.guild
|
||||
|
||||
let channel = guild.channels.cache.get('837248593609097237') as TextChannel
|
||||
if (!channel) return console.log(`\u001b[1;31m Aucun channel trouvé avec l'id "837248593609097237" !`)
|
||||
|
||||
if (!guild.members.me) return console.log(`\u001b[1;31m Je ne suis pas sur le serveur !`)
|
||||
|
||||
let embed = new EmbedBuilder()
|
||||
.setColor(guild.members.me.displayHexColor)
|
||||
.setTitle(`Salut ${member.user.username} !`)
|
||||
.setDescription(`
|
||||
Bienvenue sur le serveur de **Jujul** !
|
||||
Nous sommes actuellement ${guild.memberCount} membres !\n
|
||||
N'hésite pas à aller lire le <#797471924367786004> et à aller te présenter dans <#837138238417141791> !\n
|
||||
Si tu as des questions,
|
||||
n'hésite pas à les poser dans le <#837110617315344444> !\n
|
||||
Bon séjour parmi nous !
|
||||
`)
|
||||
.setThumbnail(member.user.avatarURL())
|
||||
.setTimestamp(new Date())
|
||||
|
||||
await channel.send({ embeds: [embed] })
|
||||
}
|
||||
}
|
||||
}
|
||||
import { Events, EmbedBuilder, ChannelType } from "discord.js"
|
||||
import type { GuildMember } from "discord.js"
|
||||
import { t } from "@/utils/i18n"
|
||||
import { logConsole } from "@/utils/console"
|
||||
|
||||
export const name = Events.GuildMemberAdd
|
||||
export async function execute(member: GuildMember) {
|
||||
if (member.guild.id === "1086577543651524699") {
|
||||
// Salon posé tamisé
|
||||
const guild = member.guild
|
||||
|
||||
guild.members.fetch().then(async () => {
|
||||
let i = 0
|
||||
guild.members.cache.forEach(member => { if (!member.user.bot) i++ })
|
||||
|
||||
const channel = guild.channels.cache.get("1091140609139560508")
|
||||
if (!channel) return
|
||||
|
||||
await channel.setName("Changement...")
|
||||
await channel.setName(`${i} Gens Posés`)
|
||||
}).catch(console.error)
|
||||
} else if (member.guild.id === "796327643783626782") {
|
||||
// Jujul Community
|
||||
const guild = member.guild
|
||||
if (!guild.members.me) return
|
||||
|
||||
const channel = guild.channels.cache.get("837248593609097237")
|
||||
if (!channel || (channel.type !== ChannelType.GuildText && channel.type !== ChannelType.GuildAnnouncement)) {
|
||||
logConsole('discordjs', 'guild_member_add', { channelId: '837248593609097237' })
|
||||
return
|
||||
}
|
||||
|
||||
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() }))
|
||||
.setThumbnail(member.user.avatarURL())
|
||||
.setTimestamp(new Date())
|
||||
|
||||
return channel.send({ embeds: [embed] })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
import { Events, GuildMember } from 'discord.js'
|
||||
|
||||
export default {
|
||||
name: Events.GuildMemberRemove,
|
||||
async execute(member: GuildMember) {
|
||||
if (member.guild.id === '1086577543651524699') { // Salon posé tamisé
|
||||
let guild = member.guild
|
||||
|
||||
guild.members.fetch().then(() => {
|
||||
let i = 0
|
||||
guild.members.cache.forEach(async member => { if (!member.user.bot) i++ })
|
||||
|
||||
let channel = guild.channels.cache.get('1091140609139560508')
|
||||
if (!channel) return
|
||||
|
||||
channel.setName('Changement...')
|
||||
channel.setName(`${i} Gens Posés`)
|
||||
}).catch(console.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
import { Events } from "discord.js"
|
||||
import type { GuildMember } from "discord.js"
|
||||
import { t } from "@/utils/i18n"
|
||||
|
||||
export const name = Events.GuildMemberRemove
|
||||
export function execute(member: GuildMember) {
|
||||
if (member.guild.id === "1086577543651524699") {
|
||||
// Salon posé tamisé
|
||||
const guild = member.guild
|
||||
|
||||
guild.members.fetch().then(async () => {
|
||||
let i = 0
|
||||
guild.members.cache.forEach(member => { if (!member.user.bot) i++ })
|
||||
|
||||
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() }))
|
||||
}).catch(console.error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +1,37 @@
|
||||
import { Events, GuildMember, EmbedBuilder, TextChannel } from 'discord.js'
|
||||
|
||||
export default {
|
||||
name: Events.GuildMemberUpdate,
|
||||
async execute(oldMember: GuildMember, newMember: GuildMember) {
|
||||
if (newMember.guild.id === '796327643783626782') { // Jujul Community
|
||||
let guild = newMember.guild
|
||||
|
||||
let channel = guild.channels.cache.get('924353449930412153') as TextChannel
|
||||
if (!channel) return console.log(`\u001b[1;31m Aucun channel trouvé avec l'id "924353449930412153" !`)
|
||||
|
||||
let boostRole = guild.roles.premiumSubscriberRole
|
||||
if (!boostRole) return console.log(`\u001b[1;31m Aucun rôle de boost trouvé !`)
|
||||
|
||||
const hadRole = oldMember.roles.cache.find(role => role.id === boostRole.id)
|
||||
const hasRole = newMember.roles.cache.find(role => role.id === boostRole.id)
|
||||
|
||||
if (!hadRole && hasRole) {
|
||||
if (!guild.members.me) return console.log(`\u001b[1;31m Je ne suis pas sur le serveur !`)
|
||||
|
||||
let embed = new EmbedBuilder()
|
||||
.setColor(guild.members.me.displayHexColor)
|
||||
.setTitle(`Nouveau boost de ${newMember.user.username} !`)
|
||||
.setDescription(`
|
||||
Merci à toi pour ce boost.\n
|
||||
Grâce à toi, on a atteint ${guild.premiumSubscriptionCount} boosts !
|
||||
`)
|
||||
.setThumbnail(newMember.user.avatarURL())
|
||||
.setTimestamp(new Date())
|
||||
|
||||
await channel.send({ embeds: [embed] })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
import { Events, EmbedBuilder, ChannelType } from "discord.js"
|
||||
import type { GuildMember } from "discord.js"
|
||||
import { t } from "@/utils/i18n"
|
||||
import { logConsole } from "@/utils/console"
|
||||
|
||||
export const name = Events.GuildMemberUpdate
|
||||
export async function execute(oldMember: GuildMember, newMember: GuildMember) {
|
||||
if (newMember.guild.id === "796327643783626782") {
|
||||
// Jujul Community
|
||||
const guild = newMember.guild
|
||||
|
||||
const channel = await guild.channels.fetch("924353449930412153")
|
||||
if (!channel || (channel.type !== ChannelType.GuildText && channel.type !== ChannelType.GuildAnnouncement)) {
|
||||
logConsole('discordjs', 'boost.no_channel', { channelId: "924353449930412153" })
|
||||
return
|
||||
}
|
||||
|
||||
const boostRole = guild.roles.premiumSubscriberRole
|
||||
if (!boostRole) { logConsole('discordjs', 'boost.no_boost_role'); return }
|
||||
|
||||
const hadRole = oldMember.roles.cache.find(role => role.id === boostRole.id)
|
||||
const hasRole = newMember.roles.cache.find(role => role.id === boostRole.id)
|
||||
|
||||
if (!hadRole && hasRole) {
|
||||
if (!guild.members.me) { logConsole('discordjs', 'boost.not_in_guild'); return }
|
||||
|
||||
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" }))
|
||||
.setThumbnail(newMember.user.avatarURL())
|
||||
.setTimestamp(new Date())
|
||||
|
||||
return channel.send({ embeds: [embed] })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { Events, Guild } from 'discord.js'
|
||||
import dbGuildInit from '../../utils/dbGuildInit'
|
||||
import dbGuild from '../../schemas/guild'
|
||||
|
||||
export default {
|
||||
name: Events.GuildUpdate,
|
||||
async execute(oldGuild: Guild, newGuild: Guild) {
|
||||
console.log(`Guild ${oldGuild.name} updated`)
|
||||
|
||||
let guildProfile = await dbGuild.findOne({ guildId: newGuild.id })
|
||||
if (!guildProfile) guildProfile = await dbGuildInit(newGuild)
|
||||
else {
|
||||
guildProfile.guildName = newGuild.name
|
||||
guildProfile.guildIcon = newGuild.iconURL() ?? 'None'
|
||||
await guildProfile.save().catch(console.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
import { Events } from "discord.js"
|
||||
import type { Guild } from "discord.js"
|
||||
import dbGuildInit from "@/utils/dbGuildInit"
|
||||
import dbGuild from "@/schemas/guild"
|
||||
import { logConsole } from "@/utils/console"
|
||||
|
||||
export const name = Events.GuildUpdate
|
||||
export async function execute(oldGuild: Guild, newGuild: Guild) {
|
||||
logConsole('discordjs', 'guild_update', { name: oldGuild.name })
|
||||
|
||||
let guildProfile = await dbGuild.findOne({ guildId: newGuild.id })
|
||||
if (!guildProfile) guildProfile = await dbGuildInit(newGuild)
|
||||
else {
|
||||
guildProfile.guildName = newGuild.name
|
||||
guildProfile.guildIcon = newGuild.iconURL() ?? "None"
|
||||
await guildProfile.save().catch(console.error)
|
||||
}
|
||||
}
|
||||
|
||||
21
src/events/client/index.ts
Normal file
21
src/events/client/index.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import * as error from "./error"
|
||||
import * as guildCreate from "./guildCreate"
|
||||
import * as guildMemberAdd from "./guildMemberAdd"
|
||||
import * as guildMemberRemove from "./guildMemberRemove"
|
||||
import * as guildMemberUpdate from "./guildMemberUpdate"
|
||||
import * as guildUpdate from "./guildUpdate"
|
||||
import * as interactionCreate from "./interactionCreate"
|
||||
import * as ready from "./ready"
|
||||
|
||||
import type { Event } from "@/types"
|
||||
|
||||
export default [
|
||||
error,
|
||||
guildCreate,
|
||||
guildMemberAdd,
|
||||
guildMemberRemove,
|
||||
guildMemberUpdate,
|
||||
guildUpdate,
|
||||
interactionCreate,
|
||||
ready
|
||||
] as Event[]
|
||||
@@ -1,45 +1,49 @@
|
||||
import { Events, Interaction, ChatInputCommandInteraction, AutocompleteInteraction, ButtonInteraction } from 'discord.js'
|
||||
import { playerButtons, playerEdit } from '../../utils/player'
|
||||
|
||||
export default {
|
||||
name: Events.InteractionCreate,
|
||||
async execute(interaction: Interaction) {
|
||||
//if (!interaction.isAutocomplete() && !interaction.isChatInputCommand() && !interaction.isButton()) return console.error(`Interaction ${interaction.commandName} is not a command.`)
|
||||
|
||||
if (interaction.isChatInputCommand()) {
|
||||
interaction = interaction as ChatInputCommandInteraction
|
||||
|
||||
let chatInputCommand = interaction.client.commands.get(interaction.commandName)
|
||||
if (!chatInputCommand) return console.error(`No chat input command matching ${interaction.commandName} was found.`)
|
||||
|
||||
console.log(`Command '${interaction.commandName}' launched by ${interaction.user.tag}`)
|
||||
|
||||
try { await chatInputCommand.execute(interaction) }
|
||||
catch (error) { console.error(`Error executing ${interaction.commandName}:`, error) }
|
||||
}
|
||||
else if (interaction.isAutocomplete()) {
|
||||
interaction = interaction as AutocompleteInteraction
|
||||
|
||||
let autoCompleteRun = interaction.client.commands.get(interaction.commandName)
|
||||
if (!autoCompleteRun) return console.error(`No autoCompleteRun matching ${interaction.commandName} was found.`)
|
||||
|
||||
console.log(`AutoCompleteRun '${interaction.commandName}' launched by ${interaction.user.tag}`)
|
||||
|
||||
try { await autoCompleteRun.autocompleteRun(interaction) }
|
||||
catch (error) { console.error(`Error autocompleting ${interaction.commandName}:`, error) }
|
||||
}
|
||||
else if (interaction.isButton()) {
|
||||
interaction = interaction as ButtonInteraction
|
||||
|
||||
let button = interaction.client.buttons.get(interaction.customId)
|
||||
if (!button) return console.error(`No button id matching ${interaction.customId} was found.`)
|
||||
|
||||
console.log(`Button '${interaction.customId}' clicked by ${interaction.user.tag}`)
|
||||
|
||||
if (playerButtons.includes(interaction.customId)) { await playerEdit(interaction) }
|
||||
|
||||
try { await button.execute(interaction) }
|
||||
catch (error) { console.error(`Error clicking ${interaction.customId}:`, error) }
|
||||
}
|
||||
}
|
||||
}
|
||||
import { Events } from "discord.js"
|
||||
import type { Interaction } from "discord.js"
|
||||
import commands from "@/commands"
|
||||
import buttons, { buttonFolders } from "@/buttons"
|
||||
import selectMenus from "@/selectmenus"
|
||||
import { playerEdit } from "@/utils/player"
|
||||
import { logConsole, logConsoleError } from "@/utils/console"
|
||||
|
||||
export const name = Events.InteractionCreate
|
||||
export async function execute(interaction: Interaction) {
|
||||
if (interaction.isChatInputCommand()) {
|
||||
const chatInputCommand = commands.find(cmd => cmd.data.name == interaction.commandName)
|
||||
if (!chatInputCommand) { logConsole('discordjs', 'interaction_create.command_not_found', { command: interaction.commandName }); return }
|
||||
|
||||
logConsole('discordjs', 'interaction_create.command_launched', { command: interaction.commandName, user: interaction.user.tag })
|
||||
|
||||
try { await chatInputCommand.execute(interaction) }
|
||||
catch (error) { logConsoleError('discordjs', 'interaction_create.command_error', { command: interaction.commandName }, error as Error) }
|
||||
}
|
||||
else if (interaction.isAutocomplete()) {
|
||||
const autocompleteRun = commands.find(cmd => cmd.data.name == interaction.commandName)
|
||||
if (!autocompleteRun?.autocompleteRun) { logConsole('discordjs', 'interaction_create.autocomplete_not_found', { command: interaction.commandName }); return }
|
||||
|
||||
logConsole('discordjs', 'interaction_create.autocomplete_launched', { command: interaction.commandName, user: interaction.user.tag })
|
||||
|
||||
try { await autocompleteRun.autocompleteRun(interaction) }
|
||||
catch (error) { logConsoleError('discordjs', 'interaction_create.autocomplete_error', { command: interaction.commandName }, error as Error) }
|
||||
}
|
||||
else if (interaction.isButton()) {
|
||||
const button = buttons.find(btn => btn.id === interaction.customId)
|
||||
if (!button) { logConsole('discordjs', 'interaction_create.button_not_found', { id: interaction.customId }); return }
|
||||
|
||||
logConsole('discordjs', 'interaction_create.button_clicked', { id: interaction.customId, user: interaction.user.tag })
|
||||
|
||||
try { await button.execute(interaction) }
|
||||
catch (error) { logConsoleError('discordjs', 'interaction_create.button_error', { id: interaction.customId }, error as Error) }
|
||||
|
||||
if (buttonFolders.find(folder => folder.name === "player" ? folder.commands.some(cmd => cmd.id === interaction.customId) : false)) await playerEdit(interaction)
|
||||
}
|
||||
else if (interaction.isAnySelectMenu()) {
|
||||
const selectMenu = selectMenus.find(menu => menu.id === interaction.customId)
|
||||
if (!selectMenu) { logConsole('discordjs', 'interaction_create.selectmenu_not_found', { id: interaction.customId }); return }
|
||||
|
||||
logConsole('discordjs', 'interaction_create.selectmenu_used', { id: interaction.customId, user: interaction.user.tag })
|
||||
|
||||
try { await selectMenu.execute(interaction) }
|
||||
catch (error) { logConsoleError('discordjs', 'interaction_create.selectmenu_error', { id: interaction.customId }, error as Error) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,117 +1,139 @@
|
||||
import { Events, Client, ActivityType } from 'discord.js'
|
||||
import { SpotifyExtractor } from '@discord-player/extractor'
|
||||
import { YoutubeiExtractor } from 'discord-player-youtubei'
|
||||
import { useMainPlayer } from 'discord-player'
|
||||
import { connect } from 'mongoose'
|
||||
import WebSocket from 'websocket'
|
||||
import chalk from 'chalk'
|
||||
import 'dotenv/config'
|
||||
|
||||
import dbGuildInit from '../../utils/dbGuildInit'
|
||||
import dbGuild from '../../schemas/guild'
|
||||
import { playerDisco, playerReplay } from '../../utils/player'
|
||||
import * as Twitch from '../../utils/twitch'
|
||||
import rss from '../../utils/rss'
|
||||
|
||||
export default {
|
||||
name: Events.ClientReady,
|
||||
once: true,
|
||||
async execute(client: Client) {
|
||||
console.log(chalk.blue(`[DiscordJS] Connected to Discord ! Logged in as ${client.user?.tag ?? 'unknown'}`))
|
||||
client.user?.setActivity('some bangers...', { type: ActivityType.Listening })
|
||||
|
||||
await useMainPlayer().extractors.register(SpotifyExtractor, {}).then(() => console.log(chalk.blue('[Discord-Player] Spotify extractor loaded.'))).catch(console.error)
|
||||
await useMainPlayer().extractors.register(YoutubeiExtractor, {}).then(() => console.log(chalk.blue('[Discord-Player] Youtube extractor loaded.'))).catch(console.error)
|
||||
|
||||
let 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)
|
||||
|
||||
|
||||
let guilds = client.guilds.cache
|
||||
guilds.forEach(async guild => {
|
||||
let guildProfile = await dbGuild.findOne({ guildId: guild.id })
|
||||
|
||||
if (!guildProfile) guildProfile = await dbGuildInit(guild)
|
||||
if (guildProfile.guildPlayer?.replay?.enabled && guildProfile.guildPlayer?.replay?.textChannelId) await playerReplay(client, guildProfile)
|
||||
|
||||
client.disco = { interval: {} as NodeJS.Timeout }
|
||||
client.disco.interval = setInterval(async () => {
|
||||
let guildProfile = await dbGuild.findOne({ guildId: guild.id })
|
||||
|
||||
if (guildProfile?.guildPlayer?.disco?.enabled) {
|
||||
let state = await playerDisco(client, guildProfile)
|
||||
if (state === 'clear') clearInterval(client.disco.interval)
|
||||
}
|
||||
}, 3000)
|
||||
|
||||
client.rss = { interval: {} as NodeJS.Timeout }
|
||||
client.rss.interval = setInterval(async () => {
|
||||
let guildProfile = await dbGuild.findOne({ guildId: guild.id })
|
||||
|
||||
if (guildProfile?.guildRss?.enabled) {
|
||||
let state = await rss(client, guildProfile)
|
||||
if (state === 'clear') clearInterval(client.rss.interval)
|
||||
}
|
||||
}, 30000)
|
||||
|
||||
// TWITCH EVENTSUB
|
||||
if (process.env['TWITCH_RUNNING_' + guild.id]) return console.log(chalk.magenta(`[Twitch] {${guild.name}} Already running...`))
|
||||
console.log(chalk.magenta(`[Twitch] {${guild.name}} Not running, starting...`))
|
||||
process.env['TWITCH_RUNNING_' + guild.id] = 'true'
|
||||
|
||||
let client_id = process.env.TWITCH_APP_ID as string
|
||||
let client_secret = process.env.TWITCH_APP_SECRET as string
|
||||
if (!client_id || !client_secret) return console.log(chalk.magenta(`[Twitch] {${guild.name}} App ID or Secret is not defined !`))
|
||||
|
||||
let dbData = guildProfile.get('guildTwitch')
|
||||
if (!dbData?.enabled) return console.log(chalk.magenta(`[Twitch] {${guild.name}} Module is disabled, please activate with \`/database edit guildTwitch.enabled True\` !`))
|
||||
|
||||
let twitch = new WebSocket.client().on('connect', async connection => {
|
||||
console.log(chalk.magenta(`[Twitch] {${guild.name}} EventSub WebSocket Connected !`))
|
||||
|
||||
connection.on('message', async message => { if (message.type === 'utf8') { try {
|
||||
let data = JSON.parse(message.utf8Data)
|
||||
let channel_access_token = guildProfile.get('guildTwitch')?.channelAccessToken as string
|
||||
|
||||
// Check when Twitch asks to login
|
||||
if (data.metadata.message_type === 'session_welcome') {
|
||||
|
||||
// Check if the channel access token is still valid before connecting
|
||||
channel_access_token = await Twitch.checkChannel(client_id, client_secret, channel_access_token, guild) as string
|
||||
if (!channel_access_token) return console.log(chalk.magenta(`[Twitch] {${guild.name}} Can't refresh channel access token !`))
|
||||
|
||||
// Get broadcaster user id and reward id
|
||||
let broadcaster_user_id = await Twitch.getUserInfo(client_id, channel_access_token, 'id') as string
|
||||
|
||||
let topics: { [key: string]: { version: string; condition: { broadcaster_user_id: string } } } = {
|
||||
'stream.online': { version: '1', condition: { broadcaster_user_id } },
|
||||
'stream.offline': { version: '1', condition: { broadcaster_user_id } }
|
||||
}
|
||||
|
||||
// Subscribe to all events required
|
||||
for (let type in topics) {
|
||||
console.log(chalk.magenta(`[Twitch] {${guild.name}} Creating ${type}...`))
|
||||
let { version, condition } = topics[type]
|
||||
|
||||
let status = await Twitch.subscribeToEvents(client_id, channel_access_token, data.payload.session.id, type, version, condition)
|
||||
if (!status) return console.error(chalk.magenta(`[Twitch] {${guild.name}} Failed to create ${type}`))
|
||||
else if (status.error) return console.log(chalk.magenta(`[Twitch] {${guild.name}} Erreur de connexion EventSub, veuillez vous reconnecter !`))
|
||||
else console.log(chalk.magenta(`[Twitch] {${guild.name}} Successfully created ${type}`))
|
||||
}
|
||||
}
|
||||
|
||||
// Handle notification messages
|
||||
else if (data.metadata.message_type === 'notification') Twitch.notification(client_id, client_secret, channel_access_token, data, guild)
|
||||
|
||||
} catch (error) { console.error(chalk.magenta(`[Twitch] {${guild.name}} ` + error)) } } })
|
||||
.on('error', error => console.error(chalk.magenta(`[Twitch] {${guild.name}} ` + error)))
|
||||
.on('close', () => {
|
||||
console.log(chalk.magenta(`[Twitch] {${guild.name}} EventSub Connection Closed !`))
|
||||
twitch.connect('wss://eventsub.wss.twitch.tv/ws')
|
||||
})
|
||||
}).on('connectFailed', error => console.error(chalk.magenta(`[Twitch] {${guild.name}} ` + error)))
|
||||
|
||||
twitch.connect('wss://eventsub.wss.twitch.tv/ws')
|
||||
})
|
||||
}
|
||||
}
|
||||
import { Events, ActivityType, ChannelType } from "discord.js"
|
||||
import type { Client } from "discord.js"
|
||||
import { useMainPlayer } from "discord-player"
|
||||
import { SpotifyExtractor } from "@discord-player/extractor"
|
||||
import { YoutubeiExtractor } from "discord-player-youtubei"
|
||||
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 type { GuildPlayer, Disco, GuildTwitch, GuildFbx } from "@/types/schemas"
|
||||
import * as Freebox from "@/utils/freebox"
|
||||
import dbGuildInit from "@/utils/dbGuildInit"
|
||||
import dbGuild from "@/schemas/guild"
|
||||
|
||||
export const name = Events.ClientReady
|
||||
export const once = true
|
||||
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 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)
|
||||
|
||||
if (process.env.NODE_ENV === "development") await twitchClient.eventSub.deleteAllSubscriptions()
|
||||
const streamerIds: string[] = []
|
||||
|
||||
await Promise.all(client.guilds.cache.map(async guild => {
|
||||
let guildProfile = await dbGuild.findOne({ guildId: guild.id })
|
||||
guildProfile ??= await dbGuildInit(guild)
|
||||
|
||||
const dbDataPlayer = guildProfile.get("guildPlayer") as GuildPlayer
|
||||
const botInstance = dbDataPlayer.instances?.find(instance => instance.botId === client.user?.id)
|
||||
if (botInstance?.replay.trackUrl) await playerReplay(client, dbDataPlayer)
|
||||
|
||||
client.disco = { interval: {} as NodeJS.Timeout }
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
client.disco.interval = setInterval(async () => {
|
||||
const guildProfile = await dbGuild.findOne({ guildId: guild.id })
|
||||
const dbDataDisco = guildProfile?.get("guildPlayer.disco") as Disco
|
||||
|
||||
if (dbDataDisco.enabled) {
|
||||
const state = await playerDisco(client, guild, dbDataDisco)
|
||||
if (state === "clear") clearInterval(client.disco.interval)
|
||||
}
|
||||
}, 3000)
|
||||
|
||||
// Gestion du timer LCD Freebox
|
||||
const dbDataFbx = guildProfile.get("guildFbx") as GuildFbx
|
||||
if (dbDataFbx.enabled && dbDataFbx.lcd) {
|
||||
if (dbDataFbx.lcd.enabled && dbDataFbx.lcd.botId === client.user?.id) {
|
||||
logConsole('freebox', 'lcd_timer_restored', { guild: guild.name })
|
||||
Freebox.Timer.schedule(client, guild.id, dbDataFbx)
|
||||
}
|
||||
}
|
||||
|
||||
const dbDataTwitch = guildProfile.get("guildTwitch") as GuildTwitch
|
||||
if (!dbDataTwitch.enabled) return
|
||||
if (!dbDataTwitch.streamers.length) { logConsole('twitch', 'ready.no_streamers_configured', { guild: guild.name }); return }
|
||||
|
||||
await Promise.all(dbDataTwitch.streamers.map(async streamer => {
|
||||
if (streamerIds.includes(streamer.twitchUserId)) return
|
||||
streamerIds.push(streamer.twitchUserId)
|
||||
|
||||
const user = await twitchClient.users.getUserById(streamer.twitchUserId)
|
||||
if (!user) { logConsole('twitch', 'ready.user_not_found', { guild: guild.name, userId: streamer.twitchUserId }); return }
|
||||
|
||||
const userSubs = await twitchClient.eventSub.getSubscriptionsForUser(streamer.twitchUserId)
|
||||
if (!userSubs.data.find(sub => sub.transportMethod === "webhook" && sub.type === "stream.online")) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
listener.onStreamOnline(streamer.twitchUserId, onlineSub)
|
||||
logConsole('twitch', 'listener_registered', { type: 'stream.online', name: user.name, id: streamer.twitchUserId })
|
||||
}
|
||||
if (!userSubs.data.find(sub => sub.transportMethod === "webhook" && sub.type === "stream.offline")) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
listener.onStreamOffline(streamer.twitchUserId, offlineSub)
|
||||
logConsole('twitch', 'listener_registered', { type: 'stream.offline', name: user.name, id: streamer.twitchUserId })
|
||||
}
|
||||
|
||||
logConsole('twitch', 'user_operational', { name: user.name, id: streamer.twitchUserId })
|
||||
|
||||
const stream = await user.getStream()
|
||||
if (stream && streamer.messageId) {
|
||||
logConsole('twitch', 'ready.stream_restoration', { guild: guild.name, userName: user.name, userId: streamer.twitchUserId })
|
||||
|
||||
// Vérifier que le message existe encore
|
||||
if (!dbDataTwitch.channelId) return
|
||||
const channel = await guild.channels.fetch(dbDataTwitch.channelId)
|
||||
if (channel && (channel.type === ChannelType.GuildText || channel.type === ChannelType.GuildAnnouncement)) {
|
||||
try {
|
||||
await channel.messages.fetch(streamer.messageId)
|
||||
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)
|
||||
await cleanupMessageId(guildProfile, streamer.twitchUserId)
|
||||
}
|
||||
}
|
||||
} else if (streamer.messageId) {
|
||||
// Il y a un messageId mais le stream n'est plus en ligne, nettoyer
|
||||
logConsole('twitch', 'ready.stream_offline_cleanup', { guild: guild.name, userName: user.name })
|
||||
await cleanupMessageId(guildProfile, streamer.twitchUserId)
|
||||
}
|
||||
|
||||
logConsole('twitch', 'user_operational', { name: user.name, id: streamer.twitchUserId })
|
||||
}))
|
||||
}))
|
||||
|
||||
const subs = await twitchClient.eventSub.getSubscriptions()
|
||||
await Promise.all(subs.data.map(async sub => {
|
||||
if (streamerIds.includes(sub.condition.broadcaster_user_id as string)) return
|
||||
if (sub.type !== "stream.online" && sub.type !== "stream.offline") return
|
||||
|
||||
await sub.unsubscribe().catch(console.error)
|
||||
logConsole('twitch', 'unsubscribed', { type: sub.type, id: sub.condition.broadcaster_user_id as string })
|
||||
}))
|
||||
}
|
||||
|
||||
async function cleanupMessageId(guildProfile: Document, twitchUserId: string) {
|
||||
try {
|
||||
const dbData = guildProfile.get("guildTwitch") as GuildTwitch
|
||||
|
||||
const streamerIndex = dbData.streamers.findIndex(s => s.twitchUserId === twitchUserId)
|
||||
if (streamerIndex === -1) return
|
||||
|
||||
dbData.streamers[streamerIndex].messageId = ""
|
||||
|
||||
guildProfile.set("guildTwitch", dbData)
|
||||
guildProfile.markModified("guildTwitch")
|
||||
await guildProfile.save()
|
||||
} catch (error) {
|
||||
logConsole('twitch', 'ready.cleanup_error', { userId: twitchUserId })
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
//import { Events, VoiceState } from 'discord.js'
|
||||
import { Events } from 'discord.js'
|
||||
|
||||
export default {
|
||||
name: Events.VoiceStateUpdate,
|
||||
async execute() {
|
||||
//async execute(oldState: VoiceState, newState: VoiceState) {
|
||||
/*
|
||||
let oldMute = oldState.serverMute
|
||||
let newMute = newState.serverMute
|
||||
let oldDeaf = oldState.serverDeaf
|
||||
let newDeaf = newState.serverDeaf
|
||||
let oldChannel = oldState.channelId
|
||||
let newChannel = newState.channelId
|
||||
console.log(oldChannel)
|
||||
console.log(newChannel)
|
||||
let guild = newState.guild
|
||||
let member = newState.member
|
||||
let channel = guild.channels.cache.get('1076215868863819848')
|
||||
let angels = guild.members.cache.get('223831938346123275')
|
||||
if (oldChannel !== newChannel) {
|
||||
let executor = await logMoveOrKick('channel_id')
|
||||
//if (!executor) channel.send(`Impossible de savoir qui a déplacé <@${member.id}> !`)
|
||||
//else if (member.id === executor.id) channel.send(`<@${member.id}> s'est déplacé lui-même le con...`)
|
||||
//else {
|
||||
// channel.send(`<@${member.id}> a été mis en sourdine par <@${executor.id}> !`)
|
||||
//}
|
||||
} else if (!oldMute && newMute) {
|
||||
let executor = await logMuteOrDeaf('mute')
|
||||
if (!executor) channel.send(`Impossible de savoir qui a muté <@${member.id}> !`)
|
||||
else if (member.id === executor.id) channel.send(`<@${member.id}> s'est muté lui-même le con...`)
|
||||
else {
|
||||
channel.send(`<@${member.id}> a été muté par <@${executor.id}> !`)
|
||||
}
|
||||
} else if (!oldDeaf && newDeaf) {
|
||||
let executor = await logMuteOrDeaf('deaf')
|
||||
if (!executor) channel.send(`Impossible de savoir qui a mis en sourdine <@${member.id}> !`)
|
||||
else if (member.id === executor.id) channel.send(`<@${member.id}> s'est mis en sourdine lui-même le con...`)
|
||||
else {
|
||||
channel.send(`<@${member.id}> a été mis en sourdine par <@${executor.id}> !`)
|
||||
}
|
||||
}
|
||||
async function logMoveOrKick() {
|
||||
let auditLogs = await guild.fetchAuditLogs({ limit: 1, type: AuditLogEvent.MemberMove })
|
||||
console.log(auditLogs.entries.find(entry => { return entry }))
|
||||
let log = await auditLogs.entries.find(entry => { return entry.extra.channel.id === newChannel })
|
||||
console.log(log)
|
||||
if (!log) return undefined
|
||||
let executor = await guild.members.cache.get(log.executor.id)
|
||||
return executor
|
||||
}
|
||||
async function logMuteOrDeaf(type) {
|
||||
let auditLogs = await guild.fetchAuditLogs({ limit: 1, type: AuditLogEvent.MemberUpdate })
|
||||
let log = await auditLogs.entries.find(entry => { return entry.target.id === member.id && entry.changes[0].key === type && entry.changes[0].new === true })
|
||||
if (!log) return undefined
|
||||
let executor = await guild.members.cache.get(log.executor.id)
|
||||
return executor
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
import chalk from 'chalk'
|
||||
import { logConsole } from "@/utils/console"
|
||||
|
||||
export default {
|
||||
name: 'connected',
|
||||
async execute() {
|
||||
console.log(chalk.green('[Mongoose] Connected to MongoDB !'))
|
||||
}
|
||||
}
|
||||
export const name = "connected"
|
||||
export function execute() {
|
||||
logConsole('mongoose', 'connected')
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import chalk from 'chalk'
|
||||
import { logConsole } from "@/utils/console"
|
||||
|
||||
export default {
|
||||
name: 'connecting',
|
||||
async execute() {
|
||||
console.log(chalk.green('[Mongoose] Connecting to MongoDB...'))
|
||||
}
|
||||
}
|
||||
export const name = "connecting"
|
||||
export function execute() {
|
||||
logConsole('mongoose', 'connecting')
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import chalk from 'chalk'
|
||||
import { logConsole } from "@/utils/console"
|
||||
|
||||
export default {
|
||||
name: 'disconnected',
|
||||
async execute() {
|
||||
console.log(chalk.green('[Mongoose] Disconnected from MongoDB !'))
|
||||
}
|
||||
}
|
||||
export const name = "disconnected"
|
||||
export function execute() {
|
||||
logConsole('mongoose', 'disconnected')
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import chalk from 'chalk'
|
||||
import { logConsoleError } from "@/utils/console"
|
||||
|
||||
export default {
|
||||
name: 'error',
|
||||
async execute(error: Error) {
|
||||
console.log(chalk.red('[Mongoose] An error occured with the database conenction :\n' + error))
|
||||
}
|
||||
}
|
||||
export const name = "error"
|
||||
export function execute(error: Error) {
|
||||
logConsoleError('mongoose', 'error', { message: error.message }, error)
|
||||
}
|
||||
|
||||
13
src/events/mongo/index.ts
Normal file
13
src/events/mongo/index.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import * as connected from "./connected"
|
||||
import * as connecting from "./connecting"
|
||||
import * as disconnected from "./disconnected"
|
||||
import * as error from "./error"
|
||||
|
||||
import type { Event } from "@/types"
|
||||
|
||||
export default [
|
||||
connected,
|
||||
connecting,
|
||||
disconnected,
|
||||
error
|
||||
] as Event[]
|
||||
@@ -1,10 +1,11 @@
|
||||
import { GuildQueue, Track } from 'discord-player'
|
||||
import { PlayerMetadata } from '../../utils/player'
|
||||
|
||||
export default {
|
||||
name: 'audioTrackAdd',
|
||||
async execute(queue: GuildQueue<PlayerMetadata>, track: Track) {
|
||||
// Emitted when the player adds a single song to its queue
|
||||
queue.metadata.channel.send(`Musique **${track.title}** de **${track.author}** ajoutée à la file d'attente !`)
|
||||
}
|
||||
}
|
||||
import type { GuildQueue, Track } from "discord-player"
|
||||
import type { PlayerMetadata } from "@/types/player"
|
||||
import { t } 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 }) })
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { GuildQueue, Track } from 'discord-player'
|
||||
import { PlayerMetadata } from '../../utils/player'
|
||||
|
||||
export default {
|
||||
name: 'audioTracksAdd',
|
||||
async execute(queue: GuildQueue<PlayerMetadata>, track: Array<Track>) {
|
||||
// Emitted when the player adds multiple songs to its queue
|
||||
queue.metadata.channel.send(`Ajout de ${track.length} musiques à la file d'attente !`)
|
||||
}
|
||||
}
|
||||
import type { GuildQueue, Track } from "discord-player"
|
||||
import type { PlayerMetadata } from "@/types/player"
|
||||
import { t } 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() }) })
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { GuildQueue } from 'discord-player'
|
||||
|
||||
export default {
|
||||
name: 'debug',
|
||||
async execute(queue: GuildQueue, message: string) {
|
||||
// Emitted when the player queue sends debug info
|
||||
// Useful for seeing what state the current queue is at
|
||||
console.log(`Player debug event: ${message}`)
|
||||
}
|
||||
}
|
||||
import type { GuildQueue } from "discord-player"
|
||||
import { logConsoleDev } from "@/utils/console"
|
||||
|
||||
export const name = "debug"
|
||||
export function execute(queue: GuildQueue, message: string) {
|
||||
// Emitted when the player queue sends debug info
|
||||
// Useful for seeing what state the current queue is at
|
||||
logConsoleDev('discord_player', 'debug', { message })
|
||||
}
|
||||
|
||||
@@ -1,24 +1,13 @@
|
||||
import { GuildQueue } from 'discord-player'
|
||||
import { PlayerMetadata } from '../../utils/player'
|
||||
import dbGuild from '../../schemas/guild'
|
||||
|
||||
export default {
|
||||
name: 'disconnect',
|
||||
async execute(queue: GuildQueue<PlayerMetadata>) {
|
||||
// Emitted when the bot leaves the voice channel
|
||||
queue.metadata.channel.send("J'ai quitté le vocal !")
|
||||
|
||||
let guildProfile = await dbGuild.findOne({ guildId: queue.guild.id })
|
||||
if (!guildProfile) return console.log(`Database data for **${queue.guild.name}** does not exist !`)
|
||||
|
||||
let dbData = guildProfile.get('guildPlayer.replay')
|
||||
dbData['textChannelId'] = ''
|
||||
dbData['voiceChannelId'] = ''
|
||||
dbData['trackUrl'] = ''
|
||||
dbData['progress'] = ''
|
||||
|
||||
guildProfile.set('guildPlayer.replay', dbData)
|
||||
guildProfile.markModified('guildPlayer.replay')
|
||||
return await guildProfile.save().catch(console.error)
|
||||
}
|
||||
}
|
||||
import type { GuildQueue } from "discord-player"
|
||||
import type { PlayerMetadata } from "@/types/player"
|
||||
import { stopProgressSaving } from "@/utils/player"
|
||||
import { t } from "@/utils/i18n"
|
||||
|
||||
export const name = "disconnect"
|
||||
export async function execute(queue: GuildQueue<PlayerMetadata>) {
|
||||
// Emitted when the bot leaves the voice channel
|
||||
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") })
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { GuildQueue } from 'discord-player'
|
||||
import { PlayerMetadata } from '../../utils/player'
|
||||
|
||||
export default {
|
||||
name: 'emptyChannel',
|
||||
async execute(queue: GuildQueue<PlayerMetadata>) {
|
||||
// Emitted when the voice channel has been empty for the set threshold
|
||||
// Bot will automatically leave the voice channel with this event
|
||||
queue.metadata.channel.send(`Je quitte le vocal car il est vide depuis trop longtemps.`)
|
||||
}
|
||||
}
|
||||
import type { GuildQueue } from "discord-player"
|
||||
import type { PlayerMetadata } from "@/types/player"
|
||||
import { stopProgressSaving } from "@/utils/player"
|
||||
import { t } from "@/utils/i18n"
|
||||
|
||||
export const name = "emptyChannel"
|
||||
export async function execute(queue: GuildQueue<PlayerMetadata>) {
|
||||
// Emitted when the voice channel has been empty for the set threshold
|
||||
// Bot will automatically leave the voice channel with this event
|
||||
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") })
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { GuildQueue } from 'discord-player'
|
||||
import { PlayerMetadata } from '../../utils/player'
|
||||
|
||||
export default {
|
||||
name: 'emptyQueue',
|
||||
async execute(queue: GuildQueue<PlayerMetadata>) {
|
||||
// Emitted when the player queue has finished
|
||||
queue.metadata.channel.send("File d'attente vide !")
|
||||
}
|
||||
}
|
||||
import type { GuildQueue } from "discord-player"
|
||||
import type { PlayerMetadata } from "@/types/player"
|
||||
import { stopProgressSaving } from "@/utils/player"
|
||||
import { t } from "@/utils/i18n"
|
||||
|
||||
export const name = "emptyQueue"
|
||||
export async function execute(queue: GuildQueue<PlayerMetadata>) {
|
||||
// Emitted when the player queue has finished
|
||||
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") })
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user