Skip to content

Commit

Permalink
General cleanup & refactor (#106)
Browse files Browse the repository at this point in the history
* refactor: avoid high cognitive complexity

* fix(interactionCreate): bug due to already replied interaction

* fix(interactionCreate): sometimes, the reply is not existing so it needs to be sent

* fix(events): apply sonarcloud fixes
  • Loading branch information
HunteRoi authored Sep 19, 2024
1 parent d977863 commit 1b0315f
Show file tree
Hide file tree
Showing 10 changed files with 268 additions and 173 deletions.
2 changes: 1 addition & 1 deletion Database.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ FROM postgres:15.1
ARG POSTGRES_DB

# Install pg_cron extension
RUN apt-get update && apt-get install -y postgresql-15-cron
RUN apt-get update && apt-get install -y postgresql-15-cron && apt-get clean

# Add pg_cron to shared_preload_libraries
RUN echo "shared_preload_libraries='pg_cron'" >> /usr/share/postgresql/postgresql.conf.sample
Expand Down
13 changes: 5 additions & 8 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
FROM node:lts-alpine as BUILD
FROM node:lts-alpine AS BUILD

WORKDIR /app

RUN apk add zip
RUN apk --no-cache add zip

COPY . .
RUN yarn install --frozen-lockfile
Expand All @@ -12,16 +12,13 @@ RUN yarn install --production
RUN zip -r app.zip ./node_modules ./build ./yarn.lock ./.env

# ------------------------------------------------------------
FROM node:lts-alpine as APP
FROM node:lts-alpine AS APP

WORKDIR /app

RUN apk add unzip
RUN apk --no-cache add unzip

COPY --from=BUILD /app/app.zip .
RUN unzip app.zip
RUN rm app.zip
RUN mv ./build/* .
RUN rm -rf ./build
RUN unzip app.zip && rm app.zip && mv ./build/* . && rm -rf ./build

CMD ["sh", "-c", "sleep 2 && node ./index.js"]
2 changes: 1 addition & 1 deletion src/commands/announce.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export default {
max: 1,
time: 30000
});
const confirmation = collectedMessages && collectedMessages.first();
const confirmation = collectedMessages?.first();
if (confirmation && confirmation.content.toLowerCase() === 'yes') {
const annoncesChannel = await message.guild.channels.fetch(announce.channelid);
if (!annoncesChannel) return;
Expand Down
64 changes: 39 additions & 25 deletions src/commands/help.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import { ChannelType, ColorResolvable, EmbedBuilder, Message } from 'discord.js';
import { Collection, ColorResolvable, EmbedBuilder, Message } from 'discord.js';

import { DatadropClient } from '../datadrop.js';

function buildEmbed(title: string, color: ColorResolvable, description: string): EmbedBuilder {
return new EmbedBuilder().setTitle(title).setColor(color).setDescription(description);
}

export default {
name: 'help',
description:
Expand All @@ -16,36 +12,22 @@ export default {
execute: async (client: DatadropClient, message: Message, args: string[]) => {
const { prefix } = client.config;
const { commands } = client;
const data: string[] = [];
let embed: EmbedBuilder;

if (!args.length) {
// lister les commandes
data.push(`Utilisez \`${prefix}${module.exports.name} [commande]\` pour lire le message d'aide sur une commande spécifique.\n`);
data.push('Commandes disponibles :');
data.push(`- ${commands.map((command) => command.name).join('\n- ')}`);
embed = buildEmbed('Liste des commandes', 'Random', data.join('\n'));
}
else {
// afficher le message d'aide de la commande args[0]
embed = listAvailableCommands(prefix, commands);
} else {
const name = args[0].toLowerCase();
const command =
commands.get(name) ||
commands.find((c) => c.aliases && c.aliases.includes(name));
const command = commands.get(name) || commands.find((c) => c.aliases?.includes(name));

if (!command) {
if (message.channel.isSendable())
if (message.channel.isSendable()) {
await message.channel.send("Ce n'est pas une commande valide.");
}
return;
}

data.push(`**Nom:** ${command.name}`);
if (command.aliases) data.push(`**Alias:** ${command.aliases.join(', ')}`);
if (command.description) data.push(`**Description:** ${command.description}`);
if (command.usage) data.push(`**Usage:** \`${prefix}${command.name} ${command.usage}\``);
data.push(`**Cooldown:** ${command.cooldown || 3} seconde(s)`);

embed = buildEmbed(`Aide pour '${command.name}'`, 'Random', data.join('\n'));
embed = buildCommandUsage(prefix, command);
}

try {
Expand All @@ -57,3 +39,35 @@ export default {
}
}
};

function buildEmbed(title: string, color: ColorResolvable, description: string): EmbedBuilder {
return new EmbedBuilder().setTitle(title).setColor(color).setDescription(description);
}

function listAvailableCommands(prefix: string | undefined, commands: Collection<string, any>): EmbedBuilder {
const data: string[] = [];

data.push(`Utilisez \`${prefix}${module.exports.name} [commande]\` pour lire le message d'aide sur une commande spécifique.\n`);
data.push('Commandes disponibles :');
data.push(`- ${commands.map((command) => command.name).join('\n- ')}`);

return buildEmbed('Liste des commandes', 'Random', data.join('\n'));
}

function buildCommandUsage(prefix: string | undefined, command: any): EmbedBuilder {
const data: string[] = [];

data.push(`**Nom:** ${command.name}`);
if (command.aliases) {
data.push(`**Alias:** ${command.aliases.join(', ')}`);
}
if (command.description) {
data.push(`**Description:** ${command.description}`);
}
if (command.usage) {
data.push(`**Usage:** \`${prefix}${command.name} ${command.usage}\``);
}
data.push(`**Cooldown:** ${command.cooldown || 3} seconde(s)`);

return buildEmbed(`Aide pour '${command.name}'`, 'Random', data.join('\n'));
}
87 changes: 54 additions & 33 deletions src/commands/pinmsg.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Message, MessageResolvable } from 'discord.js';
import { Message, MessageReference, MessageResolvable } from 'discord.js';

import { DatadropClient } from '../datadrop.js';

Expand All @@ -12,46 +12,67 @@ export default {
async execute(client: DatadropClient, message: Message, args: string[]) {
if (!message.guild || !message.member || !message.reference) return;

const reference = message.reference;
const { communitymanagerRoleid, adminRoleid, delegatesRoleid, professorRoleid } = client.config;
const verboseIsActive = args && args.length >= 1 && (args[0] === '--verbose' || args[0] === '-v');

const hasAnyRequiredRole = [communitymanagerRoleid, adminRoleid, delegatesRoleid, professorRoleid].some(r => message.member!.roles.cache.has(r));
if (!hasAnyRequiredRole) {
if (verboseIsActive && message.channel.isSendable()) await message.channel.send(`❌ **Oups!** - Tu n'es pas membre d'un des rôles nécessaires et n'es donc pas éligible à cette commande.`);
else await message.react('❌');
const hasRequiredRoles = await isAuthorized(message, [communitymanagerRoleid, adminRoleid, delegatesRoleid, professorRoleid], client, verboseIsActive);
if (!hasRequiredRoles) return;

client.logger.info(`Le membre <${message.member.displayName}> (${message.member.id}) a tenté d'épingler/désépingler le message <${reference.messageId}> mais n'a pas les droits nécessaires.`);
return;
const referencedMessage = await getMessage(message, client, verboseIsActive);
if (!referencedMessage) return;

if (referencedMessage.pinned) {
await referencedMessage.unpin();
await replyOnAction(message, '✅', 'Message désépinglé!', verboseIsActive);
} else {
await referencedMessage.pin();
await replyOnAction(message, '✅', 'Message épinglé!', verboseIsActive);
}
client.logger.info(`Le membre <${message.member.displayName}> (${message.member.id}) a épinglé/désépinglé le message <${referencedMessage.id}>.`);
}
};

if (!reference || reference.channelId != message.channel.id) {
if (verboseIsActive && message.channel.isSendable()) await message.channel.send('❌ **Oups!** - Pas de référence. Peut-être avez-vous oublié de sélectionner le message à (dés)épingler en y répondant? (cfr <https://support.discord.com/hc/fr/articles/360057382374-Replies-FAQ>)');
else await message.react('❌');
async function replyOnAction(message: Message, emoji: string, content: string, verboseIsActive?: boolean): Promise<void> {
if (verboseIsActive && message.channel.isSendable()) {
await message.channel.send(`${emoji} ${content}`);
} else {
await message.react(emoji);
}
}

client.logger.info(`Le membre <${message.member.displayName}> (${message.member.id}) a tenté d'épingler/désépingler un message sans le référencer.`);
return;
}
async function isAuthorized(message: Message, requiredRoles: string[], client: DatadropClient, verboseIsActive?: boolean): Promise<boolean> {
if (!requiredRoles.some(r => message.member!.roles.cache.has(r))) {
await replyOnAction(message, '❌', '**Oups!** - Tu n\'es pas membre d\'un des rôles nécessaires et n\'es donc pas éligible à cette commande.', verboseIsActive);

const channel = message.channel;
const parentMessage = await channel.messages.fetch(reference.messageId as MessageResolvable);
if (!parentMessage) {
if (verboseIsActive && message.channel.isSendable()) await message.channel.send('❌ **Oups!** - Message non trouvé. Peut-être a-t-il été supprimé?');
else await message.react('❌');
client.logger.info(`Le membre <${message.member?.displayName}> (${message.member?.id}) a tenté d'épingler/désépingler un message mais n'a pas les droits nécessaires.`);
return false;
}
return true;
}

client.logger.info(`Le membre <${message.member.displayName}> (${message.member.id}) a tenté d'épingler/désépingler un message non-trouvé.`);
return;
}
async function getReference(message: Message, client: DatadropClient, verboseIsActive?: boolean): Promise<MessageReference | null> {
const reference = message.reference;
if (!reference || reference.channelId != message.channel.id) {
await replyOnAction(message, '❌', '**Oups!** - Pas de référence. Peut-être avez-vous oublié de sélectionner le message à (dés)épingler en y répondant? (cfr <https://support.discord.com/hc/fr/articles/360057382374-Replies-FAQ>)', verboseIsActive);

if (parentMessage.pinned) {
await parentMessage.unpin();
if (verboseIsActive && message.channel.isSendable()) await message.channel.send('✅ Message désépinglé!');
else await message.react('✅');
} else {
await parentMessage.pin();
if (verboseIsActive && message.channel.isSendable()) await message.channel.send('✅ Message épinglé!');
else await message.react('✅');
}
client.logger.info(`Le membre <${message.member.displayName}> (${message.member.id}) a épinglé/désépinglé le message <${parentMessage.id}>.`);
client.logger.info(`Le membre <${message.member?.displayName}> (${message.member?.id}) a tenté d'épingler/désépingler un message sans le référencer.`);
return null;
}
};
return reference;
}

async function getMessage(message: Message, client: DatadropClient, verboseIsActive?: boolean): Promise<Message | null> {
const reference = await getReference(message, client, verboseIsActive);
if (!reference) return null;

const channel = message.channel;
const referencedMessage = await channel.messages.fetch(reference.messageId as MessageResolvable);
if (!referencedMessage) {
await replyOnAction(message, '❌', '**Oups!** - Message non trouvé. Peut-être a-t-il été supprimé?', verboseIsActive);

client.logger.info(`Le membre <${message.member?.displayName}> (${message.member?.id}) a tenté d'épingler/désépingler un message non-trouvé.`);
return null;
}

return referencedMessage;
}
2 changes: 1 addition & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export async function readConfig(): Promise<Configuration> {

const json = JSON.parse(process.env.CONFIG ?? '{}');
for (const prop in json) {
if (prop.match(/regex/i)) {
if (/regex/i.exec(prop)) {
json[prop] = new RegExp(json[prop]);
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/datadrop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,8 @@ export class DatadropClient extends Client {
.map(role => role as Role)
.filter(requiredRole => !!requiredRole);

this.logger.info(`Le rôle ${role.name} (<${role.id}>) n'a pas pu être donné à <${member.user.tag}> parce que tous les rôles requis ne sont pas assignés à ce membre: ${requiredRolesMissing.map(role => `${role.name} (<${role.id}>)`).join(', ')}.`);
const roleNames = requiredRolesMissing.map(role => `${role.name} (<${role.id}>)`).join(', ');
this.logger.info(`Le rôle ${role.name} (<${role.id}>) n'a pas pu être donné à <${member.user.tag}> parce que tous les rôles requis ne sont pas assignés à ce membre: ${roleNames}.`);
await interaction.editReply(`Tu ne peux pas t'assigner le rôle ${role}! Tu dois d'abord avoir les rôles suivants: ${requiredRolesMissing.join(', ')}.`);
});
this.selfRoleManager.on(SelfRoleManagerEvents.maxRolesReach, async (member: GuildMember, interaction: StringSelectMenuInteraction | ButtonInteraction, currentRolesNumber: number, maxRolesNumber: number, role: Role) => {
Expand Down
Loading

0 comments on commit 1b0315f

Please sign in to comment.