diff --git a/.env.example b/.env.example index fe916d5..77e3b12 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/core/cache.ts b/src/core/cache.ts index 329a2fc..e9fe741 100644 --- a/src/core/cache.ts +++ b/src/core/cache.ts @@ -22,6 +22,7 @@ interface Cache> { interface CacheEntries { lobbyId: string; channels: string[]; + quoiFeurChannels: string[]; } class CacheImpl implements Cache { diff --git a/src/helpers/regex.helper.ts b/src/helpers/regex.helper.ts index 2bcbea7..de9c326 100644 --- a/src/helpers/regex.helper.ts +++ b/src/helpers/regex.helper.ts @@ -1,6 +1,12 @@ 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 = /[.,!?]/g; +const emojiRegex = /(\p{Extended_Pictographic}|\p{Emoji_Component})/gu; + 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, ''); 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 new file mode 100644 index 0000000..aef13d1 --- /dev/null +++ b/src/modules/quoiFeur/quoiFeur.helpers.ts @@ -0,0 +1,126 @@ +import { + ChannelType, + type ChatInputCommandInteraction, + Client, + Guild, + type Message, + Role, +} from 'discord.js'; + +import { cache } from '../../core/cache'; +import { removeEmoji, removePunctuation } from '../../helpers/regex.helper'; + +const ONE_MINUTE = 1 * 60 * 1000; +const MUTED_ON_COUBEH = 'Muted on Coubeh'; + +const quoiDetectorRegex = /\bquoi\s*$/i; +const endWithQuoi = (text: string) => quoiDetectorRegex.test(removeEmoji(removePunctuation(text))); + +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('🔇'); + + const mutedRole = message.guild?.roles.cache.find((r) => r.name === MUTED_ON_COUBEH); + + if (!mutedRole?.id) return; + + await message.member?.roles.add(mutedRole.id); + + setTimeout(() => { + message.member?.roles.remove(mutedRole.id).catch(console.error); + }, ONE_MINUTE * 5); +}; + +export const reactOnEndWithQuoi = async (message: Message) => { + if (!endWithQuoi(message.content)) return; + + const channelIds = await cache.get('quoiFeurChannels', []); + const channelHasGame = channelIds.find((channelId) => channelId === message.channelId); + if (!channelHasGame) return; + + const probability = 1 / 20; + + Math.random() <= probability ? await reactWithCoubeh(message) : await reactWithFeur(message); +}; + +export const createRoleMutedOnCoubeh = 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_ON_COUBEH); + + return ( + existingMutedByBot ?? + guild.roles.create({ + name: MUTED_ON_COUBEH, + }) + ); +}; + +export const deleteRoleMutedOnCoubeh = async (client: Client): Promise => { + const guilds = await client.guilds.fetch().then((guilds) => guilds.map((guild) => guild.fetch())); + const roles = await Promise.all(guilds).then((guilds) => + guilds.map((guild) => guild.roles.cache.find((role) => role.name === MUTED_ON_COUBEH)), + ); + + for (const role of roles) { + if (!role) continue; + await role.delete(); + } +}; + +export const addQuoiFeurToChannel = async (interaction: ChatInputCommandInteraction) => { + const channel = interaction.channel; + if (!channel || !channel.isTextBased() || channel.type !== ChannelType.GuildText) 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 = await createRoleMutedOnCoubeh(interaction.guild); + 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'); +}; + +export const removeQuoiFeurFromChannel = async (interaction: ChatInputCommandInteraction) => { + const channel = interaction.channel; + if (!channel || !channel.isTextBased() || channel.type !== ChannelType.GuildText) 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_ON_COUBEH); + if (role) { + await channel.permissionOverwrites.delete(role); + } + await cache.set( + 'quoiFeurChannels', + channels.filter((channelId) => channelId !== channel.id), + ); + await interaction.reply('Quoi-feur disabled in this channel'); +}; diff --git a/src/modules/quoiFeur/quoiFeur.module.ts b/src/modules/quoiFeur/quoiFeur.module.ts new file mode 100644 index 0000000..6e7c638 --- /dev/null +++ b/src/modules/quoiFeur/quoiFeur.module.ts @@ -0,0 +1,35 @@ +import { SlashCommandBuilder } from 'discord.js'; + +import type { BotModule } from '../../types/bot'; +import { + addQuoiFeurToChannel, + deleteRoleMutedOnCoubeh, + reactOnEndWithQuoi, + removeQuoiFeurFromChannel, +} from './quoiFeur.helpers'; + +export const quoiFeur: BotModule = { + slashCommands: [ + { + schema: new SlashCommandBuilder() + .setName('quoi-feur') + .setDescription('Manage quoi-feur game in the channel') + .addSubcommand((subcommand) => + subcommand.setName('add').setDescription('Add the quoi-feur game to the channel'), + ) + .addSubcommand((subcommand) => + subcommand.setName('remove').setDescription('Remove the quoi-feur game from the channel'), + ) + .toJSON(), + handler: { + add: addQuoiFeurToChannel, + remove: removeQuoiFeurFromChannel, + }, + }, + ], + eventHandlers: { + // unmute everyone in every server on bot restart + ready: deleteRoleMutedOnCoubeh, + messageCreate: reactOnEndWithQuoi, + }, +};