From 012896d6c37ee2f44364457d81939dd48e77fcda Mon Sep 17 00:00:00 2001 From: Luca Montaigut Date: Thu, 3 Aug 2023 01:31:48 +0200 Subject: [PATCH 1/8] feat: add quoi-feur game with react --- .env.example | 12 +++++- src/config.ts | 2 + src/handlers/handle-guild-message-creation.ts | 5 +++ src/quoi-feur.ts | 38 +++++++++++++++++++ 4 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 src/quoi-feur.ts diff --git a/.env.example b/.env.example index fe916d5..a13135d 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,14 @@ +# SETUP DISCORD_TOKEN= DISCORD_CLIENT_ID= DISCORD_GUILD_ID= -COOL_LINKS_CHANNEL_ID= + +# DB REDIS_URL= -PAGE_SUMMARIZER_BASE_URL= \ No newline at end of file + +# CHANNELS +BLABLA_CHANNEL_ID= +COOL_LINKS_CHANNEL_ID= + +# API +PAGE_SUMMARIZER_BASE_URL== diff --git a/src/config.ts b/src/config.ts index ecdbeb8..74f2448 100644 --- a/src/config.ts +++ b/src/config.ts @@ -5,7 +5,9 @@ export const config = { token: env.get('DISCORD_TOKEN').required().asString(), clientId: env.get('DISCORD_CLIENT_ID').required().asString(), guildId: env.get('DISCORD_GUILD_ID').required().asString(), + blablaChannelId: env.get('BLABLA_CHANNEL_ID').required().asString(), coolLinksChannelId: env.get('COOL_LINKS_CHANNEL_ID').required().asString(), + mutedRoleId: env.get('MUTED_ROLE_ID').required().asString(), }, redis: { url: env.get('REDIS_URL').required().asString(), diff --git a/src/handlers/handle-guild-message-creation.ts b/src/handlers/handle-guild-message-creation.ts index 253ec93..97d42ff 100644 --- a/src/handlers/handle-guild-message-creation.ts +++ b/src/handlers/handle-guild-message-creation.ts @@ -4,6 +4,7 @@ import { MessageType } from 'discord.js'; import { config } from '../config'; import { coolLinksManagement } from '../cool-links-management'; import { patternReplacement } from '../pattern-replacement'; +import { quoiFeurReact } from '../quoi-feur'; export const handleGuildMessageCreation = async (message: Message) => { if (message.author.bot) { @@ -14,6 +15,10 @@ export const handleGuildMessageCreation = async (message: Message) => { return; } + if (message.channelId === config.discord.blablaChannelId) { + await quoiFeurReact(message); + } + if (message.channelId === config.discord.coolLinksChannelId) { await coolLinksManagement(message); return; diff --git a/src/quoi-feur.ts b/src/quoi-feur.ts new file mode 100644 index 0000000..2877db1 --- /dev/null +++ b/src/quoi-feur.ts @@ -0,0 +1,38 @@ +import type { Message } from 'discord.js'; +import { config } from './config'; + +const quoiDetector = new RegExp(/\b\s*[qQ][uU][oO][iI]\s*[.,!?]*\s*$/i); +const ONE_MINUTE = 60000; + +const reactWithFeur = async (message: Message) => { + await message.react('🇫'); + await message.react('🇪'); + await message.react('🇺'); + await message.react('🇷'); +}; + +const reactWithCoubeh = async (message: Message) => { + await message.react('🇨'); + await message.react('🇴'); + await message.react('🇺'); + await message.react('🇧'); + await message.react('🇪'); + await message.react('🇭'); + await message.react('🔇'); + + message.member?.roles.add(config.discord.mutedRoleId); + setTimeout(() => { + message.member?.roles.remove(config.discord.mutedRoleId); + }, ONE_MINUTE * 5); +}; + +export const quoiFeurReact = async (message: Message) => { + if (!quoiDetector.test(message.content)) return; + + const probability = 1 / 20; + if (Math.random() <= probability) { + await reactWithCoubeh(message); + } else { + await reactWithFeur(message); + } +}; From b4ac595590d2a447d060689fa6b912d411eb8750 Mon Sep 17 00:00:00 2001 From: Luca Montaigut Date: Fri, 1 Sep 2023 09:05:47 +0200 Subject: [PATCH 2/8] feat: remove role env var --- .env.example | 2 +- src/config.ts | 1 - src/constants.ts/roles.ts | 1 + src/handlers/handle-role-creation.ts | 31 ++++++++++++++++++++++++++++ src/main.ts | 4 ++++ src/quoi-feur.ts | 12 ++++++++--- 6 files changed, 46 insertions(+), 5 deletions(-) create mode 100644 src/constants.ts/roles.ts create mode 100644 src/handlers/handle-role-creation.ts diff --git a/.env.example b/.env.example index a13135d..77e3b12 100644 --- a/.env.example +++ b/.env.example @@ -11,4 +11,4 @@ BLABLA_CHANNEL_ID= COOL_LINKS_CHANNEL_ID= # API -PAGE_SUMMARIZER_BASE_URL== +PAGE_SUMMARIZER_BASE_URL= diff --git a/src/config.ts b/src/config.ts index 74f2448..173d112 100644 --- a/src/config.ts +++ b/src/config.ts @@ -7,7 +7,6 @@ export const config = { guildId: env.get('DISCORD_GUILD_ID').required().asString(), blablaChannelId: env.get('BLABLA_CHANNEL_ID').required().asString(), coolLinksChannelId: env.get('COOL_LINKS_CHANNEL_ID').required().asString(), - mutedRoleId: env.get('MUTED_ROLE_ID').required().asString(), }, redis: { url: env.get('REDIS_URL').required().asString(), diff --git a/src/constants.ts/roles.ts b/src/constants.ts/roles.ts new file mode 100644 index 0000000..99abcf0 --- /dev/null +++ b/src/constants.ts/roles.ts @@ -0,0 +1 @@ +export const MUTED_BY_BOT = 'Muted by bot'; diff --git a/src/handlers/handle-role-creation.ts b/src/handlers/handle-role-creation.ts new file mode 100644 index 0000000..078dcc9 --- /dev/null +++ b/src/handlers/handle-role-creation.ts @@ -0,0 +1,31 @@ +import { type Guild, TextChannel } from 'discord.js'; + +import { MUTED_BY_BOT } from '../constants.ts/roles'; + +const createMutedByBotRole = async (guild: Guild) => { + const role = await guild.roles.create({ + name: MUTED_BY_BOT, + }); + guild.channels.cache.forEach((channel) => { + if (!(channel instanceof TextChannel)) return; + channel.permissionOverwrites + .create(role, { + SendMessages: false, + CreatePublicThreads: false, + CreatePrivateThreads: false, + SendMessagesInThreads: false, + SendTTSMessages: false, + AttachFiles: false, + }) + .catch(console.error); + }); +}; + +export const handleRoleCreation = async (guild: Guild) => { + const hasMutedByBot = guild.roles.cache.find((role) => role.name === MUTED_BY_BOT); + if (hasMutedByBot) { + // delete to unmute all members and re-create it + await hasMutedByBot.delete(); + } + await createMutedByBotRole(guild); +}; diff --git a/src/main.ts b/src/main.ts index 15b29a0..7012281 100644 --- a/src/main.ts +++ b/src/main.ts @@ -5,6 +5,7 @@ import { config } from './config'; import { deleteExistingCommands } from './delete-existing-commands'; import { handleGuildMessageCreation } from './handlers/handle-guild-message-creation'; import { handleInteractionCreation } from './handlers/handle-interaction-creation'; +import { handleRoleCreation } from './handlers/handle-role-creation'; import { handleVoiceChannelDeletion } from './handlers/handle-voice-channel-deletion'; import { handleVoiceStateUpdate } from './handlers/handle-voice-state-update'; @@ -54,4 +55,7 @@ await rest.put(Routes.applicationGuildCommands(discord.clientId, discord.guildId body: [voiceOnDemandCommand, fartCommand], }); +const guild = await client.guilds.fetch(discord.guildId); +await handleRoleCreation(guild); + console.log('Bot started.'); diff --git a/src/quoi-feur.ts b/src/quoi-feur.ts index 2877db1..acfb6cb 100644 --- a/src/quoi-feur.ts +++ b/src/quoi-feur.ts @@ -1,5 +1,6 @@ import type { Message } from 'discord.js'; -import { config } from './config'; + +import { MUTED_BY_BOT } from './constants.ts/roles'; const quoiDetector = new RegExp(/\b\s*[qQ][uU][oO][iI]\s*[.,!?]*\s*$/i); const ONE_MINUTE = 60000; @@ -20,9 +21,14 @@ const reactWithCoubeh = async (message: Message) => { await message.react('🇭'); await message.react('🔇'); - message.member?.roles.add(config.discord.mutedRoleId); + const mutedRole = message.guild?.roles.cache.find((r) => r.name === MUTED_BY_BOT); + + if (!mutedRole?.id) return; + + await message.member?.roles.add(mutedRole.id); + setTimeout(() => { - message.member?.roles.remove(config.discord.mutedRoleId); + message.member?.roles.remove(mutedRole.id).catch(console.error); }, ONE_MINUTE * 5); }; From 6879106f2ba88d990662056cd3a071101dfeac35 Mon Sep 17 00:00:00 2001 From: Luca Montaigut Date: Fri, 1 Sep 2023 15:51:55 +0200 Subject: [PATCH 3/8] feat: make quoi-feur stateless --- src/commands.ts | 7 +++- src/config.ts | 1 - src/handlers/handle-guild-message-creation.ts | 4 +-- src/handlers/handle-interaction-creation.ts | 6 +++- src/handlers/handle-role-creation.ts | 25 +++---------- src/helpers/cache.ts | 1 + src/main.ts | 4 +-- src/quoi-feur.ts | 35 ++++++++++++++++++- 8 files changed, 53 insertions(+), 30 deletions(-) diff --git a/src/commands.ts b/src/commands.ts index f0bbcc1..becf394 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -10,5 +10,10 @@ export const voiceOnDemandCommand = new SlashCommandBuilder() export const fartCommand = new SlashCommandBuilder() .setName('fart') - .setDescription("Replies with https://prout.dev") + .setDescription('Replies with https://prout.dev') + .toJSON(); + +export const quoiFeurCommand = new SlashCommandBuilder() + .setName('quoi-feur') + .setDescription('Add quoi-feur game to the channel') .toJSON(); diff --git a/src/config.ts b/src/config.ts index 173d112..ecdbeb8 100644 --- a/src/config.ts +++ b/src/config.ts @@ -5,7 +5,6 @@ export const config = { token: env.get('DISCORD_TOKEN').required().asString(), clientId: env.get('DISCORD_CLIENT_ID').required().asString(), guildId: env.get('DISCORD_GUILD_ID').required().asString(), - blablaChannelId: env.get('BLABLA_CHANNEL_ID').required().asString(), coolLinksChannelId: env.get('COOL_LINKS_CHANNEL_ID').required().asString(), }, redis: { diff --git a/src/handlers/handle-guild-message-creation.ts b/src/handlers/handle-guild-message-creation.ts index 97d42ff..e1d08cb 100644 --- a/src/handlers/handle-guild-message-creation.ts +++ b/src/handlers/handle-guild-message-creation.ts @@ -15,9 +15,7 @@ export const handleGuildMessageCreation = async (message: Message) => { return; } - if (message.channelId === config.discord.blablaChannelId) { - await quoiFeurReact(message); - } + await quoiFeurReact(message); if (message.channelId === config.discord.coolLinksChannelId) { await coolLinksManagement(message); diff --git a/src/handlers/handle-interaction-creation.ts b/src/handlers/handle-interaction-creation.ts index 21ac505..9f32cee 100644 --- a/src/handlers/handle-interaction-creation.ts +++ b/src/handlers/handle-interaction-creation.ts @@ -1,13 +1,14 @@ import type { Interaction } from 'discord.js'; import { createLobby } from '../create-lobby'; +import { addQuoiFeurChannel } from '../quoi-feur'; export const handleInteractionCreation = async (interaction: Interaction): Promise => { if ( !interaction.isCommand() || !interaction.inGuild() || !interaction.isChatInputCommand() || - !['voice-on-demand', 'fart'].includes(interaction.commandName) + !['voice-on-demand', 'fart', 'quoi-feur'].includes(interaction.commandName) ) { return; } @@ -23,5 +24,8 @@ export const handleInteractionCreation = async (interaction: Interaction): Promi case 'fart': await interaction.reply('https://prout.dev'); break; + case 'quoi-feur': + await addQuoiFeurChannel(interaction); + break; } }; diff --git a/src/handlers/handle-role-creation.ts b/src/handlers/handle-role-creation.ts index 078dcc9..2fe9260 100644 --- a/src/handlers/handle-role-creation.ts +++ b/src/handlers/handle-role-creation.ts @@ -1,31 +1,14 @@ -import { type Guild, TextChannel } from 'discord.js'; +import { type Guild } from 'discord.js'; import { MUTED_BY_BOT } from '../constants.ts/roles'; -const createMutedByBotRole = async (guild: Guild) => { - const role = await guild.roles.create({ - name: MUTED_BY_BOT, - }); - guild.channels.cache.forEach((channel) => { - if (!(channel instanceof TextChannel)) return; - channel.permissionOverwrites - .create(role, { - SendMessages: false, - CreatePublicThreads: false, - CreatePrivateThreads: false, - SendMessagesInThreads: false, - SendTTSMessages: false, - AttachFiles: false, - }) - .catch(console.error); - }); -}; - export const handleRoleCreation = async (guild: Guild) => { const hasMutedByBot = guild.roles.cache.find((role) => role.name === MUTED_BY_BOT); if (hasMutedByBot) { // delete to unmute all members and re-create it await hasMutedByBot.delete(); } - await createMutedByBotRole(guild); + await guild.roles.create({ + name: MUTED_BY_BOT, + }); }; diff --git a/src/helpers/cache.ts b/src/helpers/cache.ts index 329a2fc..e9fe741 100644 --- a/src/helpers/cache.ts +++ b/src/helpers/cache.ts @@ -22,6 +22,7 @@ interface Cache> { interface CacheEntries { lobbyId: string; channels: string[]; + quoiFeurChannels: string[]; } class CacheImpl implements Cache { diff --git a/src/main.ts b/src/main.ts index 7012281..8ffbfde 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,6 +1,6 @@ import { Client, REST, Routes } from 'discord.js'; -import { fartCommand, voiceOnDemandCommand } from './commands'; +import { fartCommand, quoiFeurCommand, voiceOnDemandCommand } from './commands'; import { config } from './config'; import { deleteExistingCommands } from './delete-existing-commands'; import { handleGuildMessageCreation } from './handlers/handle-guild-message-creation'; @@ -52,7 +52,7 @@ const rest = new REST({ version: '10' }).setToken(discord.token); await deleteExistingCommands(rest, discord); await rest.put(Routes.applicationGuildCommands(discord.clientId, discord.guildId), { - body: [voiceOnDemandCommand, fartCommand], + body: [voiceOnDemandCommand, fartCommand, quoiFeurCommand], }); const guild = await client.guilds.fetch(discord.guildId); diff --git a/src/quoi-feur.ts b/src/quoi-feur.ts index acfb6cb..3a86e2d 100644 --- a/src/quoi-feur.ts +++ b/src/quoi-feur.ts @@ -1,6 +1,7 @@ -import type { Message } from 'discord.js'; +import { type ChatInputCommandInteraction, type Message, TextChannel } from 'discord.js'; import { MUTED_BY_BOT } from './constants.ts/roles'; +import { cache } from './helpers/cache'; const quoiDetector = new RegExp(/\b\s*[qQ][uU][oO][iI]\s*[.,!?]*\s*$/i); const ONE_MINUTE = 60000; @@ -33,6 +34,10 @@ const reactWithCoubeh = async (message: Message) => { }; export const quoiFeurReact = async (message: Message) => { + const channelIds = await cache.get('quoiFeurChannels', []); + const channelHasGame = channelIds.find((channelId) => channelId === message.channelId); + if (!channelHasGame) return; + if (!quoiDetector.test(message.content)) return; const probability = 1 / 20; @@ -42,3 +47,31 @@ export const quoiFeurReact = async (message: Message) => { await reactWithFeur(message); } }; + +export const addQuoiFeurChannel = async (interaction: ChatInputCommandInteraction) => { + const channel = interaction.channel; + if (!channel || !channel.isTextBased()) return; + + const channels = await cache.get('quoiFeurChannels', []); + if (channels.includes(channel.id)) { + await interaction.reply('Quoi-feur is already enabled in this channel'); + return; + } + + const role = interaction.guild?.roles.cache.find((r) => r.name === MUTED_BY_BOT); + if (!role) { + throw new Error(`Role ${MUTED_BY_BOT} is missing`); + } + + if (!(channel instanceof TextChannel)) return; + await channel.permissionOverwrites.create(role, { + SendMessages: false, + CreatePublicThreads: false, + CreatePrivateThreads: false, + SendMessagesInThreads: false, + SendTTSMessages: false, + AttachFiles: false, + }); + await cache.set('quoiFeurChannels', [...channels, channel.id]); + await interaction.reply('Quoi-feur enabled in this channel'); +}; From cbffe777d025f1fd4e5bf98394769671dc952f37 Mon Sep 17 00:00:00 2001 From: Luca Montaigut Date: Fri, 1 Sep 2023 15:58:38 +0200 Subject: [PATCH 4/8] feat: add and remove game from channel --- src/commands.ts | 8 +++++++- src/handlers/handle-interaction-creation.ts | 12 +++++++++-- src/quoi-feur.ts | 22 +++++++++++++++++++++ 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/commands.ts b/src/commands.ts index becf394..386efb4 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -15,5 +15,11 @@ export const fartCommand = new SlashCommandBuilder() export const quoiFeurCommand = new SlashCommandBuilder() .setName('quoi-feur') - .setDescription('Add quoi-feur game to the channel') + .setDescription('Manage quoi-feur game in the channel') + .addSubcommand((subcommand) => + subcommand.setName('add').setDescription('Add the quoi-feur game in the channel'), + ) + .addSubcommand((subcommand) => + subcommand.setName('remove').setDescription('Remove the quoi-feur game in the channel'), + ) .toJSON(); diff --git a/src/handlers/handle-interaction-creation.ts b/src/handlers/handle-interaction-creation.ts index 9f32cee..4205758 100644 --- a/src/handlers/handle-interaction-creation.ts +++ b/src/handlers/handle-interaction-creation.ts @@ -1,7 +1,7 @@ import type { Interaction } from 'discord.js'; import { createLobby } from '../create-lobby'; -import { addQuoiFeurChannel } from '../quoi-feur'; +import { addQuoiFeurChannel, removeQuoiFeurChannel } from '../quoi-feur'; export const handleInteractionCreation = async (interaction: Interaction): Promise => { if ( @@ -25,7 +25,15 @@ export const handleInteractionCreation = async (interaction: Interaction): Promi await interaction.reply('https://prout.dev'); break; case 'quoi-feur': - await addQuoiFeurChannel(interaction); + if (interaction.options.getSubcommand(true) === 'add') { + await addQuoiFeurChannel(interaction); + return; + } + if (interaction.options.getSubcommand(true) === 'remove') { + await removeQuoiFeurChannel(interaction); + return; + } + await interaction.reply('Unknown subcommand'); break; } }; diff --git a/src/quoi-feur.ts b/src/quoi-feur.ts index 3a86e2d..f7ade44 100644 --- a/src/quoi-feur.ts +++ b/src/quoi-feur.ts @@ -75,3 +75,25 @@ export const addQuoiFeurChannel = async (interaction: ChatInputCommandInteractio await cache.set('quoiFeurChannels', [...channels, channel.id]); await interaction.reply('Quoi-feur enabled in this channel'); }; + +export const removeQuoiFeurChannel = async (interaction: ChatInputCommandInteraction) => { + const channel = interaction.channel; + if (!channel || !channel.isTextBased()) return; + + const channels = await cache.get('quoiFeurChannels', []); + if (!channels.includes(channel.id)) { + await interaction.reply('Quoi-feur is not enabled in this channel'); + return; + } + + const role = interaction.guild?.roles.cache.find((r) => r.name === MUTED_BY_BOT); + if (!role) return; + if (!(channel instanceof TextChannel)) return; + + await channel.permissionOverwrites.delete(role); + await cache.set( + 'quoiFeurChannels', + channels.filter((channelId) => channelId !== channel.id), + ); + await interaction.reply('Quoi-feur disabled in this channel'); +}; From 7486ef943c882e00480cd075ffd5ce795a1132b6 Mon Sep 17 00:00:00 2001 From: Luca Montaigut Date: Fri, 1 Sep 2023 16:51:16 +0200 Subject: [PATCH 5/8] fix: avoid error if user delete message before react end --- src/{constants.ts => constants}/roles.ts | 0 src/handlers/handle-role-creation.ts | 2 +- src/quoi-feur.ts | 11 ++++++----- 3 files changed, 7 insertions(+), 6 deletions(-) rename src/{constants.ts => constants}/roles.ts (100%) diff --git a/src/constants.ts/roles.ts b/src/constants/roles.ts similarity index 100% rename from src/constants.ts/roles.ts rename to src/constants/roles.ts diff --git a/src/handlers/handle-role-creation.ts b/src/handlers/handle-role-creation.ts index 2fe9260..7c00731 100644 --- a/src/handlers/handle-role-creation.ts +++ b/src/handlers/handle-role-creation.ts @@ -1,6 +1,6 @@ import { type Guild } from 'discord.js'; -import { MUTED_BY_BOT } from '../constants.ts/roles'; +import { MUTED_BY_BOT } from '../constants/roles'; export const handleRoleCreation = async (guild: Guild) => { const hasMutedByBot = guild.roles.cache.find((role) => role.name === MUTED_BY_BOT); diff --git a/src/quoi-feur.ts b/src/quoi-feur.ts index f7ade44..f3578e9 100644 --- a/src/quoi-feur.ts +++ b/src/quoi-feur.ts @@ -1,6 +1,6 @@ import { type ChatInputCommandInteraction, type Message, TextChannel } from 'discord.js'; -import { MUTED_BY_BOT } from './constants.ts/roles'; +import { MUTED_BY_BOT } from './constants/roles'; import { cache } from './helpers/cache'; const quoiDetector = new RegExp(/\b\s*[qQ][uU][oO][iI]\s*[.,!?]*\s*$/i); @@ -41,10 +41,11 @@ export const quoiFeurReact = async (message: Message) => { if (!quoiDetector.test(message.content)) return; const probability = 1 / 20; - if (Math.random() <= probability) { - await reactWithCoubeh(message); - } else { - await reactWithFeur(message); + + try { + Math.random() <= probability ? await reactWithCoubeh(message) : await reactWithFeur(message); + } catch (error) { + console.error(error); } }; From e065591307723e70e748af8514c1d70bcc1802f3 Mon Sep 17 00:00:00 2001 From: LazyRabbit Date: Fri, 1 Sep 2023 21:30:52 +0200 Subject: [PATCH 6/8] fix: detail one minute constant MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Peïo Thibault --- src/quoi-feur.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/quoi-feur.ts b/src/quoi-feur.ts index f3578e9..72047d2 100644 --- a/src/quoi-feur.ts +++ b/src/quoi-feur.ts @@ -4,7 +4,7 @@ import { MUTED_BY_BOT } from './constants/roles'; import { cache } from './helpers/cache'; const quoiDetector = new RegExp(/\b\s*[qQ][uU][oO][iI]\s*[.,!?]*\s*$/i); -const ONE_MINUTE = 60000; +const ONE_MINUTE = 1 * 60 * 1000; const reactWithFeur = async (message: Message) => { await message.react('🇫'); From 97a90fd3fe7e8cb52c2715772dca3088f7f5add3 Mon Sep 17 00:00:00 2001 From: Luca Montaigut Date: Fri, 1 Sep 2023 23:26:18 +0200 Subject: [PATCH 7/8] feat: improve quoi detection --- src/helpers/regex.helper.ts | 9 +++++++++ src/quoi-feur.ts | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/helpers/regex.helper.ts b/src/helpers/regex.helper.ts index 2bcbea7..bea7d2c 100644 --- a/src/helpers/regex.helper.ts +++ b/src/helpers/regex.helper.ts @@ -1,6 +1,15 @@ const socialNetworksUrlRegex = new RegExp( '^(https?://)?(www.)?(facebook.com|fb.me|twitter.com|vxtwitter.com|instagram.com|linkedin.com|youtube.com|youtu.be|pinterest.com|snapchat.com|tiktok.com)/[a-zA-Z0-9.-/?=&#_]+$', ); +const punctuationRegex = new RegExp(/[.,!?]/g); +const emojiRegex = new RegExp(/(\p{Extended_Pictographic}|\p{Emoji_Component})/gu); +const quoiDetectorRegex = new RegExp(/\b\s*[q][u][o][i]\s*$/i); + export const isASocialNetworkUrl = (url: string): boolean => { return socialNetworksUrlRegex.test(url); }; + +export const removePunctuation = (text: string) => text.replaceAll(punctuationRegex, ''); +export const removeEmoji = (text: string) => text.replaceAll(emojiRegex, ''); +export const endWithQuoi = (text: string) => + quoiDetectorRegex.test(removeEmoji(removePunctuation(text))); diff --git a/src/quoi-feur.ts b/src/quoi-feur.ts index 72047d2..9444b76 100644 --- a/src/quoi-feur.ts +++ b/src/quoi-feur.ts @@ -2,8 +2,8 @@ import { type ChatInputCommandInteraction, type Message, TextChannel } from 'dis import { MUTED_BY_BOT } from './constants/roles'; import { cache } from './helpers/cache'; +import { endWithQuoi } from './helpers/regex.helper'; -const quoiDetector = new RegExp(/\b\s*[qQ][uU][oO][iI]\s*[.,!?]*\s*$/i); const ONE_MINUTE = 1 * 60 * 1000; const reactWithFeur = async (message: Message) => { @@ -38,7 +38,7 @@ export const quoiFeurReact = async (message: Message) => { const channelHasGame = channelIds.find((channelId) => channelId === message.channelId); if (!channelHasGame) return; - if (!quoiDetector.test(message.content)) return; + if (!endWithQuoi(message.content)) return; const probability = 1 / 20; From 6b7d6b08139b3355061e7e93b7496a082ee070ae Mon Sep 17 00:00:00 2001 From: Luca Montaigut Date: Sun, 3 Sep 2023 12:09:48 +0200 Subject: [PATCH 8/8] refactor: match new architecture --- src/constants/roles.ts | 1 - src/modules/modules.ts | 2 + src/modules/quoiFeur/quoiFeur.helpers.ts | 59 +++++++++++++++++------- src/modules/quoiFeur/quoiFeur.module.ts | 30 ++++++------ 4 files changed, 58 insertions(+), 34 deletions(-) delete mode 100644 src/constants/roles.ts diff --git a/src/constants/roles.ts b/src/constants/roles.ts deleted file mode 100644 index 99abcf0..0000000 --- a/src/constants/roles.ts +++ /dev/null @@ -1 +0,0 @@ -export const MUTED_BY_BOT = 'Muted by bot'; diff --git a/src/modules/modules.ts b/src/modules/modules.ts index 4e6bdc8..ad9cf90 100644 --- a/src/modules/modules.ts +++ b/src/modules/modules.ts @@ -1,6 +1,7 @@ import { coolLinksManagement } from './coolLinksManagement/coolLinksManagement.module'; import { fart } from './fart/fart.module'; import { patternReplace } from './patternReplace/patternReplace.module'; +import { quoiFeur } from './quoiFeur/quoiFeur.module'; import { voiceOnDemand } from './voiceOnDemand/voiceOnDemand.module'; export const modules = { @@ -8,4 +9,5 @@ export const modules = { voiceOnDemand, coolLinksManagement, patternReplace, + quoiFeur, }; diff --git a/src/modules/quoiFeur/quoiFeur.helpers.ts b/src/modules/quoiFeur/quoiFeur.helpers.ts index 27b4217..136968d 100644 --- a/src/modules/quoiFeur/quoiFeur.helpers.ts +++ b/src/modules/quoiFeur/quoiFeur.helpers.ts @@ -1,10 +1,16 @@ -import { type ChatInputCommandInteraction, type Message, TextChannel } from 'discord.js'; +import { + ChannelType, + type ChatInputCommandInteraction, + Guild, + type Message, + Role, +} from 'discord.js'; -import { MUTED_BY_BOT } from '../../constants/roles'; import { cache } from '../../core/cache'; import { endWithQuoi } from '../../helpers/regex.helper'; const ONE_MINUTE = 1 * 60 * 1000; +const MUTED_BY_BOT = 'Muted by bot'; const reactWithFeur = async (message: Message) => { await message.react('🇫'); @@ -33,7 +39,7 @@ const reactWithCoubeh = async (message: Message) => { }, ONE_MINUTE * 5); }; -export const quoiFeurReact = async (message: Message) => { +export const reactOnEndWithQuoi = async (message: Message) => { const channelIds = await cache.get('quoiFeurChannels', []); const channelHasGame = channelIds.find((channelId) => channelId === message.channelId); if (!channelHasGame) return; @@ -49,9 +55,34 @@ export const quoiFeurReact = async (message: Message) => { } }; -export const addQuoiFeurChannel = async (interaction: ChatInputCommandInteraction) => { +export const createRoleMutedByBot = async (guild: Guild | null): Promise => { + if (!guild) { + throw new Error('Guild is null in createRoleMutedByBot'); + } + const existingMutedByBot = guild.roles.cache.find((role) => role.name === MUTED_BY_BOT); + + return ( + existingMutedByBot ?? + guild.roles.create({ + name: MUTED_BY_BOT, + }) + ); +}; + +export const deleteRoleMutedByBot = async (guild: Guild | null): Promise => { + if (!guild) { + throw new Error('Guild is null in removeRoleMutedByBot'); + } + const existingMutedByBot = guild.roles.cache.find((role) => role.name === MUTED_BY_BOT); + + if (existingMutedByBot) { + await existingMutedByBot.delete(); + } +}; + +export const addQuoiFeurToChannel = async (interaction: ChatInputCommandInteraction) => { const channel = interaction.channel; - if (!channel || !channel.isTextBased()) return; + if (!channel || !channel.isTextBased() || channel.type !== ChannelType.GuildText) return; const channels = await cache.get('quoiFeurChannels', []); if (channels.includes(channel.id)) { @@ -59,12 +90,7 @@ export const addQuoiFeurChannel = async (interaction: ChatInputCommandInteractio return; } - const role = interaction.guild?.roles.cache.find((r) => r.name === MUTED_BY_BOT); - if (!role) { - throw new Error(`Role ${MUTED_BY_BOT} is missing`); - } - - if (!(channel instanceof TextChannel)) return; + const role = await createRoleMutedByBot(interaction.guild); await channel.permissionOverwrites.create(role, { SendMessages: false, CreatePublicThreads: false, @@ -77,9 +103,9 @@ export const addQuoiFeurChannel = async (interaction: ChatInputCommandInteractio await interaction.reply('Quoi-feur enabled in this channel'); }; -export const removeQuoiFeurChannel = async (interaction: ChatInputCommandInteraction) => { +export const removeQuoiFeurFromChannel = async (interaction: ChatInputCommandInteraction) => { const channel = interaction.channel; - if (!channel || !channel.isTextBased()) return; + if (!channel || !channel.isTextBased() || channel.type !== ChannelType.GuildText) return; const channels = await cache.get('quoiFeurChannels', []); if (!channels.includes(channel.id)) { @@ -88,10 +114,9 @@ export const removeQuoiFeurChannel = async (interaction: ChatInputCommandInterac } const role = interaction.guild?.roles.cache.find((r) => r.name === MUTED_BY_BOT); - if (!role) return; - if (!(channel instanceof TextChannel)) return; - - await channel.permissionOverwrites.delete(role); + if (role) { + await channel.permissionOverwrites.delete(role); + } await cache.set( 'quoiFeurChannels', channels.filter((channelId) => channelId !== channel.id), diff --git a/src/modules/quoiFeur/quoiFeur.module.ts b/src/modules/quoiFeur/quoiFeur.module.ts index fc9bbf6..3557dac 100644 --- a/src/modules/quoiFeur/quoiFeur.module.ts +++ b/src/modules/quoiFeur/quoiFeur.module.ts @@ -1,9 +1,13 @@ import { SlashCommandBuilder } from 'discord.js'; import { config } from '../../config'; -import { MUTED_BY_BOT } from '../../constants/roles'; import type { BotModule } from '../../types/bot'; -import { addQuoiFeurChannel, quoiFeurReact, removeQuoiFeurChannel } from './quoiFeur.helpers'; +import { + addQuoiFeurToChannel, + deleteRoleMutedByBot, + reactOnEndWithQuoi, + removeQuoiFeurFromChannel, +} from './quoiFeur.helpers'; export const quoiFeur: BotModule = { slashCommands: [ @@ -12,36 +16,30 @@ export const quoiFeur: BotModule = { .setName('quoi-feur') .setDescription('Manage quoi-feur game in the channel') .addSubcommand((subcommand) => - subcommand.setName('add').setDescription('Add the quoi-feur game in the channel'), + subcommand.setName('add').setDescription('Add the quoi-feur game to the channel'), ) .addSubcommand((subcommand) => - subcommand.setName('remove').setDescription('Remove the quoi-feur game in the channel'), + subcommand.setName('remove').setDescription('Remove the quoi-feur game from the channel'), ) .toJSON(), handler: { add: async (interaction) => { - await addQuoiFeurChannel(interaction); + await addQuoiFeurToChannel(interaction).catch(console.error); }, remove: async (interaction) => { - await removeQuoiFeurChannel(interaction); + await removeQuoiFeurFromChannel(interaction).catch(console.error); }, }, }, ], eventHandlers: { ready: async (client) => { - const guild = await client.guilds.fetch(config.discord.guildId); - const hasMutedByBot = guild.roles.cache.find((role) => role.name === MUTED_BY_BOT); - if (hasMutedByBot) { - // delete to unmute all members and re-create it - await hasMutedByBot.delete(); - } - await guild.roles.create({ - name: MUTED_BY_BOT, - }); + const guild = client.guilds.cache.get(config.discord.guildId) ?? null; + // unmute everyone on bot restart + await deleteRoleMutedByBot(guild).catch(console.error); }, messageCreate: async (message) => { - await quoiFeurReact(message); + await reactOnEndWithQuoi(message).catch(console.error); }, }, };