Skip to content

feat: add quoi-feur game with react #31

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
# SETUP
DISCORD_TOKEN=
DISCORD_CLIENT_ID=
DISCORD_GUILD_ID=
COOL_LINKS_CHANNEL_ID=

# DB
REDIS_URL=
PAGE_SUMMARIZER_BASE_URL=

# CHANNELS
BLABLA_CHANNEL_ID=
COOL_LINKS_CHANNEL_ID=

# API
PAGE_SUMMARIZER_BASE_URL=
13 changes: 12 additions & 1 deletion src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,16 @@ 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('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();
1 change: 1 addition & 0 deletions src/constants/roles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const MUTED_BY_BOT = 'Muted by bot';
3 changes: 3 additions & 0 deletions src/handlers/handle-guild-message-creation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -14,6 +15,8 @@ export const handleGuildMessageCreation = async (message: Message) => {
return;
}

await quoiFeurReact(message);

if (message.channelId === config.discord.coolLinksChannelId) {
await coolLinksManagement(message);
return;
Expand Down
14 changes: 13 additions & 1 deletion src/handlers/handle-interaction-creation.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import type { Interaction } from 'discord.js';

import { createLobby } from '../create-lobby';
import { addQuoiFeurChannel, removeQuoiFeurChannel } from '../quoi-feur';

export const handleInteractionCreation = async (interaction: Interaction): Promise<void> => {
if (
!interaction.isCommand() ||
!interaction.inGuild() ||
!interaction.isChatInputCommand() ||
!['voice-on-demand', 'fart'].includes(interaction.commandName)
!['voice-on-demand', 'fart', 'quoi-feur'].includes(interaction.commandName)
) {
return;
}
Expand All @@ -23,5 +24,16 @@ export const handleInteractionCreation = async (interaction: Interaction): Promi
case 'fart':
await interaction.reply('https://prout.dev');
break;
case 'quoi-feur':
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;
}
};
14 changes: 14 additions & 0 deletions src/handlers/handle-role-creation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { type Guild } from 'discord.js';

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);
if (hasMutedByBot) {
// delete to unmute all members and re-create it
await hasMutedByBot.delete();
}
await guild.roles.create({
name: MUTED_BY_BOT,
});
};
1 change: 1 addition & 0 deletions src/helpers/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ interface Cache<Entries extends Record<string, any>> {
interface CacheEntries {
lobbyId: string;
channels: string[];
quoiFeurChannels: string[];
}

class CacheImpl implements Cache<CacheEntries> {
Expand Down
8 changes: 6 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
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';
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';

Expand Down Expand Up @@ -51,7 +52,10 @@ 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);
await handleRoleCreation(guild);

console.log('Bot started.');
100 changes: 100 additions & 0 deletions src/quoi-feur.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { type ChatInputCommandInteraction, type Message, TextChannel } from 'discord.js';

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 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_BY_BOT);

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 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;

try {
Math.random() <= probability ? await reactWithCoubeh(message) : await reactWithFeur(message);
} catch (error) {
console.error(error);
}
};

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');
};

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');
};