feat: add PDF viewer command with page navigation and multi-project support improvements
This commit is contained in:
@@ -7,6 +7,7 @@ GITLAB_URL=https://gitlab.example.com
|
|||||||
GITLAB_TOKEN=your_gitlab_private_token_here
|
GITLAB_TOKEN=your_gitlab_private_token_here
|
||||||
|
|
||||||
# Configuration Bot
|
# Configuration Bot
|
||||||
|
# ALLOW_NO_PROJECTS=true
|
||||||
SYNC_INTERVAL_MINUTES=5
|
SYNC_INTERVAL_MINUTES=5
|
||||||
LOG_LEVEL=info
|
LOG_LEVEL=info
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,10 @@ WORKDIR /app
|
|||||||
# Copie les fichiers de dépendances
|
# Copie les fichiers de dépendances
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
|
|
||||||
# Installe les dépendances
|
# Dépendances natives pour sharp/canvas/pdfjs sur Alpine
|
||||||
RUN npm ci --only=production && npm cache clean --force
|
RUN apk add --no-cache python3 make g++ cairo-dev pango-dev libjpeg-turbo-dev giflib-dev freetype-dev pkgconfig \
|
||||||
|
&& npm ci --only=production \
|
||||||
|
&& npm cache clean --force
|
||||||
|
|
||||||
# Copie le code source
|
# Copie le code source
|
||||||
COPY src/ ./src/
|
COPY src/ ./src/
|
||||||
@@ -37,4 +39,4 @@ CMD ["node", "src/index.js"]
|
|||||||
# Labels pour la documentation
|
# Labels pour la documentation
|
||||||
LABEL maintainer="paulinsegura79@gmail.com"
|
LABEL maintainer="paulinsegura79@gmail.com"
|
||||||
LABEL description="Bot Discord pour synchroniser les issue boards GitLab"
|
LABEL description="Bot Discord pour synchroniser les issue boards GitLab"
|
||||||
LABEL version="1.0.0"
|
LABEL version="1.1.0"
|
||||||
|
|||||||
938
package-lock.json
generated
938
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
21
package.json
21
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "gitlab-issue-discord-bot",
|
"name": "gitlab-issue-discord-bot",
|
||||||
"version": "1.0.0",
|
"version": "1.1.0",
|
||||||
"description": "Bot Discord pour synchroniser les issue boards GitLab avec Discord",
|
"description": "Bot Discord pour synchroniser les issue boards GitLab avec Discord",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -10,19 +10,28 @@
|
|||||||
"lint": "eslint src/",
|
"lint": "eslint src/",
|
||||||
"lint:fix": "eslint src/ --fix"
|
"lint:fix": "eslint src/ --fix"
|
||||||
},
|
},
|
||||||
"keywords": ["discord", "gitlab", "kanban", "bot", "issue-board"],
|
"keywords": [
|
||||||
|
"discord",
|
||||||
|
"gitlab",
|
||||||
|
"kanban",
|
||||||
|
"bot",
|
||||||
|
"issue-board"
|
||||||
|
],
|
||||||
"author": "Holo795",
|
"author": "Holo795",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"discord.js": "^14.14.1",
|
|
||||||
"axios": "^1.6.2",
|
"axios": "^1.6.2",
|
||||||
|
"canvas": "^2.11.2",
|
||||||
|
"discord.js": "^14.14.1",
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
"node-cron": "^3.0.3"
|
"node-cron": "^3.0.3",
|
||||||
|
"pdfjs-dist": "^3.11.174",
|
||||||
|
"sharp": "^0.33.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"nodemon": "^3.0.2",
|
|
||||||
"eslint": "^8.56.0",
|
"eslint": "^8.56.0",
|
||||||
"jest": "^29.7.0"
|
"jest": "^29.7.0",
|
||||||
|
"nodemon": "^3.0.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0"
|
"node": ">=18.0.0"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ const CommandDefinitions = require('./definitions/CommandDefinitions');
|
|||||||
const IssueInfoHandler = require('./handlers/IssueInfoHandler');
|
const IssueInfoHandler = require('./handlers/IssueInfoHandler');
|
||||||
const SyncHandler = require('./handlers/SyncHandler');
|
const SyncHandler = require('./handlers/SyncHandler');
|
||||||
const StatsHandler = require('./handlers/StatsHandler');
|
const StatsHandler = require('./handlers/StatsHandler');
|
||||||
|
const PdfHandler = require('./handlers/PdfHandler');
|
||||||
const AutocompleteService = require('./autocomplete/AutocompleteService');
|
const AutocompleteService = require('./autocomplete/AutocompleteService');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -16,6 +17,7 @@ class Commands {
|
|||||||
this.issueInfoHandler = new IssueInfoHandler(gitlabClient, syncService);
|
this.issueInfoHandler = new IssueInfoHandler(gitlabClient, syncService);
|
||||||
this.syncHandler = new SyncHandler(syncService);
|
this.syncHandler = new SyncHandler(syncService);
|
||||||
this.statsHandler = new StatsHandler(syncService);
|
this.statsHandler = new StatsHandler(syncService);
|
||||||
|
this.pdfHandler = new PdfHandler();
|
||||||
this.autocompleteService = new AutocompleteService(gitlabClient, syncService);
|
this.autocompleteService = new AutocompleteService(gitlabClient, syncService);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,6 +49,20 @@ class Commands {
|
|||||||
return await this.statsHandler.handle(interaction);
|
return await this.statsHandler.handle(interaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gère la commande de visualisation PDF
|
||||||
|
*/
|
||||||
|
async handlePdf(interaction) {
|
||||||
|
return await this.pdfHandler.handle(interaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gère les interactions bouton pour la navigation PDF
|
||||||
|
*/
|
||||||
|
async handlePdfButton(interaction) {
|
||||||
|
return await this.pdfHandler.handleButton(interaction);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gère l'auto-complétion pour les commandes
|
* Gère l'auto-complétion pour les commandes
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ class CommandDefinitions {
|
|||||||
return [
|
return [
|
||||||
this.getIssueInfoCommand(),
|
this.getIssueInfoCommand(),
|
||||||
this.getSyncCommand(),
|
this.getSyncCommand(),
|
||||||
this.getBotStatsCommand()
|
this.getBotStatsCommand(),
|
||||||
|
this.getPdfCommand()
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,6 +57,27 @@ class CommandDefinitions {
|
|||||||
.setName('bot-stats')
|
.setName('bot-stats')
|
||||||
.setDescription('Affiche les statistiques du bot');
|
.setDescription('Affiche les statistiques du bot');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Commande pour visualiser un PDF page par page
|
||||||
|
*/
|
||||||
|
static getPdfCommand() {
|
||||||
|
return new SlashCommandBuilder()
|
||||||
|
.setName('pdf')
|
||||||
|
.setDescription('Visualise un PDF et navigue page par page')
|
||||||
|
.addAttachmentOption(option =>
|
||||||
|
option.setName('file')
|
||||||
|
.setDescription('PDF à afficher')
|
||||||
|
.setRequired(false))
|
||||||
|
.addStringOption(option =>
|
||||||
|
option.setName('url')
|
||||||
|
.setDescription('URL HTTP(S) vers un PDF')
|
||||||
|
.setRequired(false))
|
||||||
|
.addBooleanOption(option =>
|
||||||
|
option.setName('public')
|
||||||
|
.setDescription('Afficher pour tout le monde (sinon seulement pour toi)')
|
||||||
|
.setRequired(false));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = CommandDefinitions;
|
module.exports = CommandDefinitions;
|
||||||
|
|||||||
227
src/commands/handlers/PdfHandler.js
Normal file
227
src/commands/handlers/PdfHandler.js
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const os = require('os');
|
||||||
|
const path = require('path');
|
||||||
|
const axios = require('axios');
|
||||||
|
const sharp = require('sharp');
|
||||||
|
const { createCanvas } = require('canvas');
|
||||||
|
const { EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle, AttachmentBuilder } = require('discord.js');
|
||||||
|
const pdfjsLib = require('pdfjs-dist/legacy/build/pdf.js');
|
||||||
|
const logger = require('../../utils/logger');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler pour la commande /pdf
|
||||||
|
*/
|
||||||
|
class PdfHandler {
|
||||||
|
constructor() {
|
||||||
|
this.sessions = new Map(); // messageId -> { pdfPath, totalPages, ownerId, createdAt, downloadUrl, isPublic }
|
||||||
|
pdfjsLib.GlobalWorkerOptions.workerSrc = require('pdfjs-dist/build/pdf.worker.min.js');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Commande principale
|
||||||
|
*/
|
||||||
|
async handle(interaction) {
|
||||||
|
try {
|
||||||
|
const attachment = interaction.options.getAttachment('file');
|
||||||
|
const urlOption = interaction.options.getString('url');
|
||||||
|
const isPublic = interaction.options.getBoolean('public') === true;
|
||||||
|
|
||||||
|
const sourceUrl = attachment ? attachment.url : urlOption;
|
||||||
|
if (!attachment && !urlOption) {
|
||||||
|
await interaction.editReply({ content: '❌ Fournis un PDF (fichier) ou une URL https:// vers un PDF.' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (urlOption && !urlOption.toLowerCase().startsWith('http')) {
|
||||||
|
await interaction.editReply({ content: '❌ URL invalide. Utilise http(s)://...' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attachment && attachment.contentType && !attachment.contentType.includes('pdf')) {
|
||||||
|
await interaction.editReply({ content: '❌ Le fichier doit être un PDF.' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attachment && attachment.size > 15 * 1024 * 1024) {
|
||||||
|
await interaction.editReply({ content: '❌ PDF trop lourd (max 15 Mo).' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tmpPath = await this.downloadPdf(sourceUrl);
|
||||||
|
const { pdf, totalPages } = await this.loadPdf(tmpPath);
|
||||||
|
|
||||||
|
if (totalPages > 100) {
|
||||||
|
await interaction.editReply({ content: '❌ PDF trop long (max 100 pages).' });
|
||||||
|
fs.unlink(tmpPath, () => {});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pageBuffer = await this.renderPage(pdf, 1);
|
||||||
|
const title = this.getPdfTitle(sourceUrl);
|
||||||
|
const messageOptions = this.buildMessageOptions(pageBuffer, 1, totalPages, interaction.user.id, sourceUrl, title);
|
||||||
|
|
||||||
|
// Visibilité du message (ephemeral si non public)
|
||||||
|
if (!isPublic) {
|
||||||
|
messageOptions.flags = require('discord.js').MessageFlags.Ephemeral;
|
||||||
|
}
|
||||||
|
|
||||||
|
const reply = await interaction.editReply(messageOptions);
|
||||||
|
|
||||||
|
// Sauvegarde de la session pour la navigation
|
||||||
|
this.sessions.set(reply.id, {
|
||||||
|
pdfPath: tmpPath,
|
||||||
|
totalPages,
|
||||||
|
ownerId: interaction.user.id,
|
||||||
|
createdAt: Date.now(),
|
||||||
|
downloadUrl: sourceUrl,
|
||||||
|
isPublic
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info(`PDF affiché (${totalPages} pages) par ${interaction.user.tag}`);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Erreur lors du traitement du PDF:', error);
|
||||||
|
await interaction.editReply({ content: '❌ Erreur lors du traitement du PDF.' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gestion des boutons
|
||||||
|
*/
|
||||||
|
async handleButton(interaction) {
|
||||||
|
const parts = (interaction.customId || '').split(':'); // pdf-nav:{userId}:{page}:{total}
|
||||||
|
if (parts.length !== 4) return;
|
||||||
|
const [, ownerId, pageStr, totalStr] = parts;
|
||||||
|
const requestedPage = parseInt(pageStr, 10);
|
||||||
|
const totalPages = parseInt(totalStr, 10);
|
||||||
|
|
||||||
|
const session = this.sessions.get(interaction.message.id);
|
||||||
|
if (!session) {
|
||||||
|
await interaction.reply({
|
||||||
|
content: '❌ Session expirée ou introuvable.',
|
||||||
|
flags: require('discord.js').MessageFlags.Ephemeral
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!session.isPublic && interaction.user.id !== ownerId) {
|
||||||
|
await interaction.reply({
|
||||||
|
content: '❌ Seul l’auteur de la commande peut naviguer.',
|
||||||
|
flags: require('discord.js').MessageFlags.Ephemeral
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const clampedPage = Math.min(Math.max(1, requestedPage), totalPages);
|
||||||
|
if (clampedPage === requestedPage && interaction.isRepliable()) {
|
||||||
|
await interaction.deferUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { pdf } = await this.loadPdf(session.pdfPath);
|
||||||
|
const pageBuffer = await this.renderPage(pdf, clampedPage);
|
||||||
|
const title = this.getPdfTitle(session.downloadUrl);
|
||||||
|
const messageOptions = this.buildMessageOptions(pageBuffer, clampedPage, totalPages, ownerId, session.downloadUrl, title);
|
||||||
|
|
||||||
|
await interaction.editReply(messageOptions);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Erreur lors de la navigation PDF:', error);
|
||||||
|
await interaction.editReply({ content: '❌ Erreur lors du rendu de la page.' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Télécharge le PDF dans un fichier temporaire
|
||||||
|
*/
|
||||||
|
async downloadPdf(url) {
|
||||||
|
const response = await axios.get(url, { responseType: 'arraybuffer' });
|
||||||
|
const tmpPath = path.join(os.tmpdir(), `pdf-${Date.now()}-${Math.random().toString(36).slice(2)}.pdf`);
|
||||||
|
fs.writeFileSync(tmpPath, response.data);
|
||||||
|
return tmpPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Charge le PDF avec pdfjs
|
||||||
|
*/
|
||||||
|
async loadPdf(pdfPath) {
|
||||||
|
const data = new Uint8Array(fs.readFileSync(pdfPath));
|
||||||
|
const loadingTask = pdfjsLib.getDocument({ data });
|
||||||
|
const pdf = await loadingTask.promise;
|
||||||
|
return { pdf, totalPages: pdf.numPages };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rendu d'une page en buffer PNG
|
||||||
|
*/
|
||||||
|
async renderPage(pdf, pageNumber) {
|
||||||
|
const page = await pdf.getPage(pageNumber);
|
||||||
|
const viewport = page.getViewport({ scale: 1.5 }); // Ajuster si besoin
|
||||||
|
const canvas = createCanvas(viewport.width, viewport.height);
|
||||||
|
const context = canvas.getContext('2d');
|
||||||
|
|
||||||
|
await page.render({
|
||||||
|
canvasContext: context,
|
||||||
|
viewport,
|
||||||
|
}).promise;
|
||||||
|
|
||||||
|
const buffer = canvas.toBuffer('image/png');
|
||||||
|
// Recompression légère pour réduire la taille
|
||||||
|
return sharp(buffer).png({ quality: 90 }).toBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construit le message Discord avec image + boutons
|
||||||
|
*/
|
||||||
|
buildMessageOptions(pageBuffer, pageIndex, totalPages, ownerId, downloadUrl, title) {
|
||||||
|
const file = new AttachmentBuilder(pageBuffer, { name: `page-${pageIndex}.png` });
|
||||||
|
const embed = new EmbedBuilder()
|
||||||
|
.setTitle(title || 'Visualisation PDF')
|
||||||
|
.setDescription(`Page ${pageIndex} / ${totalPages}`)
|
||||||
|
.setImage(`attachment://page-${pageIndex}.png`)
|
||||||
|
.setColor(0x5865F2);
|
||||||
|
|
||||||
|
const prevDisabled = pageIndex <= 1;
|
||||||
|
const nextDisabled = pageIndex >= totalPages;
|
||||||
|
|
||||||
|
const row = new ActionRowBuilder().addComponents(
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId(`pdf-nav:${ownerId}:${pageIndex - 1}:${totalPages}`)
|
||||||
|
.setLabel('◀️ Précédent')
|
||||||
|
.setStyle(ButtonStyle.Primary)
|
||||||
|
.setDisabled(prevDisabled),
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId(`pdf-nav:${ownerId}:${pageIndex + 1}:${totalPages}`)
|
||||||
|
.setLabel('Suivant ▶️')
|
||||||
|
.setStyle(ButtonStyle.Primary)
|
||||||
|
.setDisabled(nextDisabled),
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setLabel('Télécharger PDF')
|
||||||
|
.setStyle(ButtonStyle.Link)
|
||||||
|
.setURL(downloadUrl || 'https://')
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
embeds: [embed],
|
||||||
|
files: [file],
|
||||||
|
components: [row]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Déduit un titre à partir de l'URL du PDF
|
||||||
|
*/
|
||||||
|
getPdfTitle(downloadUrl) {
|
||||||
|
try {
|
||||||
|
if (!downloadUrl) return 'Visualisation PDF';
|
||||||
|
const url = new URL(downloadUrl);
|
||||||
|
const pathname = url.pathname || '';
|
||||||
|
const name = pathname.split('/').filter(Boolean).pop();
|
||||||
|
if (name) return name;
|
||||||
|
return 'Visualisation PDF';
|
||||||
|
} catch (e) {
|
||||||
|
return 'Visualisation PDF';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = PdfHandler;
|
||||||
@@ -51,11 +51,18 @@ const config = {
|
|||||||
}
|
}
|
||||||
// Aucune configuration trouvée
|
// Aucune configuration trouvée
|
||||||
else {
|
else {
|
||||||
throw new Error('Aucune configuration trouvée. Créez le fichier config/projects.json ou définissez les variables legacy (GITLAB_PROJECT_ID + DISCORD_CATEGORY_ID)');
|
if (process.env.ALLOW_NO_PROJECTS === 'true') {
|
||||||
|
console.log('Aucun projet configuré, mais ALLOW_NO_PROJECTS=true -> démarrage sans projets.');
|
||||||
|
this.projects = [];
|
||||||
|
} else {
|
||||||
|
throw new Error('Aucune configuration trouvée. Créez le fichier config/projects.json ou définissez les variables legacy (GITLAB_PROJECT_ID + DISCORD_CATEGORY_ID)');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validation des projets avant encoding
|
// Validation des projets avant encoding
|
||||||
if (!this.projects || this.projects.length === 0) {
|
if ((!this.projects || this.projects.length === 0) && process.env.ALLOW_NO_PROJECTS === 'true') {
|
||||||
|
console.log('Avertissement: démarrage sans projet (ALLOW_NO_PROJECTS=true). Certaines fonctionnalités seront désactivées.');
|
||||||
|
} else if (!this.projects || this.projects.length === 0) {
|
||||||
throw new Error('Aucun projet configuré');
|
throw new Error('Aucun projet configuré');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,6 +94,9 @@ const config = {
|
|||||||
|
|
||||||
// Validation des projets
|
// Validation des projets
|
||||||
if (this.projects.length === 0) {
|
if (this.projects.length === 0) {
|
||||||
|
if (process.env.ALLOW_NO_PROJECTS === 'true') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
throw new Error('Aucun projet configuré. Définissez PROJECTS_CONFIG ou les variables legacy.');
|
throw new Error('Aucun projet configuré. Définissez PROJECTS_CONFIG ou les variables legacy.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
16
src/index.js
16
src/index.js
@@ -1,3 +1,4 @@
|
|||||||
|
const { Client, GatewayIntentBits } = require('discord.js');
|
||||||
const config = require('./config/config');
|
const config = require('./config/config');
|
||||||
const logger = require('./utils/logger');
|
const logger = require('./utils/logger');
|
||||||
const SyncService = require('./services/SyncService');
|
const SyncService = require('./services/SyncService');
|
||||||
@@ -39,10 +40,23 @@ class KanbanBot {
|
|||||||
// Démarrage du service de synchronisation
|
// Démarrage du service de synchronisation
|
||||||
await this.syncService.initialize();
|
await this.syncService.initialize();
|
||||||
|
|
||||||
// Initialisation des commandes Discord (récupère le client du premier projet)
|
// Initialisation des commandes Discord
|
||||||
const firstProjectSync = Array.from(this.syncService.projectSyncs.values())[0];
|
const firstProjectSync = Array.from(this.syncService.projectSyncs.values())[0];
|
||||||
if (firstProjectSync && firstProjectSync.discordService.client) {
|
if (firstProjectSync && firstProjectSync.discordService.client) {
|
||||||
await this.commandService.initialize(firstProjectSync.discordService.client);
|
await this.commandService.initialize(firstProjectSync.discordService.client);
|
||||||
|
} else if (process.env.ALLOW_NO_PROJECTS === 'true') {
|
||||||
|
logger.info('Aucun projet configuré : initialisation d\'un client Discord minimal pour les commandes.');
|
||||||
|
const commandOnlyClient = new Client({
|
||||||
|
intents: [GatewayIntentBits.Guilds]
|
||||||
|
});
|
||||||
|
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
commandOnlyClient.once('ready', () => resolve());
|
||||||
|
commandOnlyClient.on('error', reject);
|
||||||
|
commandOnlyClient.login(config.discord.token).catch(reject);
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.commandService.initialize(commandOnlyClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.isRunning = true;
|
this.isRunning = true;
|
||||||
|
|||||||
@@ -69,6 +69,8 @@ class CommandService {
|
|||||||
await this.handleSlashCommand(interaction);
|
await this.handleSlashCommand(interaction);
|
||||||
} else if (interaction.isAutocomplete()) {
|
} else if (interaction.isAutocomplete()) {
|
||||||
await this.handleAutocomplete(interaction);
|
await this.handleAutocomplete(interaction);
|
||||||
|
} else if (interaction.isButton()) {
|
||||||
|
await this.handleButton(interaction);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -98,6 +100,10 @@ class CommandService {
|
|||||||
await this.commands.handleBotStats(interaction);
|
await this.commands.handleBotStats(interaction);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'pdf':
|
||||||
|
await this.commands.handlePdf(interaction);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
await interaction.editReply({
|
await interaction.editReply({
|
||||||
content: '❌ Commande non reconnue'
|
content: '❌ Commande non reconnue'
|
||||||
@@ -155,6 +161,28 @@ class CommandService {
|
|||||||
logger.error('Erreur lors de la suppression des commandes:', error);
|
logger.error('Erreur lors de la suppression des commandes:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gère les interactions bouton
|
||||||
|
*/
|
||||||
|
async handleButton(interaction) {
|
||||||
|
try {
|
||||||
|
const customId = interaction.customId || '';
|
||||||
|
if (customId.startsWith('pdf-nav:')) {
|
||||||
|
await this.commands.handlePdfButton(interaction);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Erreur lors du traitement du bouton:', error);
|
||||||
|
try {
|
||||||
|
await interaction.reply({
|
||||||
|
content: '❌ Erreur lors du traitement du bouton',
|
||||||
|
flags: require('discord.js').MessageFlags.Ephemeral
|
||||||
|
});
|
||||||
|
} catch (replyError) {
|
||||||
|
logger.error('Erreur lors de la réponse à l\'interaction bouton:', replyError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = CommandService;
|
module.exports = CommandService;
|
||||||
|
|||||||
Reference in New Issue
Block a user