Skip to content

Commit cce59b2

Browse files
luca-montaigutpotbneolectron
committed
feat: quoi feur (#52)
Co-authored-by: Peïo Thibault <[email protected]> Co-authored-by: Manu <[email protected]>
1 parent fbae908 commit cce59b2

File tree

6 files changed

+180
-2
lines changed

6 files changed

+180
-2
lines changed

.env.example

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
1+
# SETUP
12
DISCORD_TOKEN=
23
DISCORD_CLIENT_ID=
34
DISCORD_GUILD_ID=
4-
COOL_LINKS_CHANNEL_ID=
5+
6+
# DB
57
REDIS_URL=
6-
PAGE_SUMMARIZER_BASE_URL=
8+
9+
# CHANNELS
10+
BLABLA_CHANNEL_ID=
11+
COOL_LINKS_CHANNEL_ID=
12+
13+
# API
14+
PAGE_SUMMARIZER_BASE_URL=

src/core/cache.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ interface Cache<Entries extends Record<string, any>> {
2222
interface CacheEntries {
2323
lobbyId: string;
2424
channels: string[];
25+
quoiFeurChannels: string[];
2526
}
2627

2728
class CacheImpl implements Cache<CacheEntries> {

src/helpers/regex.helper.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
const socialNetworksUrlRegex = new RegExp(
22
'^(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.-/?=&#_]+$',
33
);
4+
const punctuationRegex = /[.,!?]/g;
5+
const emojiRegex = /(\p{Extended_Pictographic}|\p{Emoji_Component})/gu;
6+
47
export const isASocialNetworkUrl = (url: string): boolean => {
58
return socialNetworksUrlRegex.test(url);
69
};
10+
11+
export const removePunctuation = (text: string) => text.replaceAll(punctuationRegex, '');
12+
export const removeEmoji = (text: string) => text.replaceAll(emojiRegex, '');

src/modules/modules.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import { coolLinksManagement } from './coolLinksManagement/coolLinksManagement.module';
22
import { fart } from './fart/fart.module';
33
import { patternReplace } from './patternReplace/patternReplace.module';
4+
import { quoiFeur } from './quoiFeur/quoiFeur.module';
45
import { voiceOnDemand } from './voiceOnDemand/voiceOnDemand.module';
56

67
export const modules = {
78
fart,
89
voiceOnDemand,
910
coolLinksManagement,
1011
patternReplace,
12+
quoiFeur,
1113
};
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import {
2+
ChannelType,
3+
type ChatInputCommandInteraction,
4+
Client,
5+
Guild,
6+
type Message,
7+
Role,
8+
} from 'discord.js';
9+
10+
import { cache } from '../../core/cache';
11+
import { removeEmoji, removePunctuation } from '../../helpers/regex.helper';
12+
13+
const ONE_MINUTE = 1 * 60 * 1000;
14+
const MUTED_ON_COUBEH = 'Muted on Coubeh';
15+
16+
const quoiDetectorRegex = /\bquoi\s*$/i;
17+
const endWithQuoi = (text: string) => quoiDetectorRegex.test(removeEmoji(removePunctuation(text)));
18+
19+
const reactWithFeur = async (message: Message) => {
20+
await message.react('🇫');
21+
await message.react('🇪');
22+
await message.react('🇺');
23+
await message.react('🇷');
24+
};
25+
26+
const reactWithCoubeh = async (message: Message) => {
27+
await message.react('🇨');
28+
await message.react('🇴');
29+
await message.react('🇺');
30+
await message.react('🇧');
31+
await message.react('🇪');
32+
await message.react('🇭');
33+
await message.react('🔇');
34+
35+
const mutedRole = message.guild?.roles.cache.find((r) => r.name === MUTED_ON_COUBEH);
36+
37+
if (!mutedRole?.id) return;
38+
39+
await message.member?.roles.add(mutedRole.id);
40+
41+
setTimeout(() => {
42+
message.member?.roles.remove(mutedRole.id).catch(console.error);
43+
}, ONE_MINUTE * 5);
44+
};
45+
46+
export const reactOnEndWithQuoi = async (message: Message) => {
47+
if (!endWithQuoi(message.content)) return;
48+
49+
const channelIds = await cache.get('quoiFeurChannels', []);
50+
const channelHasGame = channelIds.find((channelId) => channelId === message.channelId);
51+
if (!channelHasGame) return;
52+
53+
const probability = 1 / 20;
54+
55+
Math.random() <= probability ? await reactWithCoubeh(message) : await reactWithFeur(message);
56+
};
57+
58+
export const createRoleMutedOnCoubeh = async (guild: Guild | null): Promise<Role> => {
59+
if (!guild) {
60+
throw new Error('Guild is null in createRoleMutedByBot');
61+
}
62+
const existingMutedByBot = guild.roles.cache.find((role) => role.name === MUTED_ON_COUBEH);
63+
64+
return (
65+
existingMutedByBot ??
66+
guild.roles.create({
67+
name: MUTED_ON_COUBEH,
68+
})
69+
);
70+
};
71+
72+
export const deleteRoleMutedOnCoubeh = async (client: Client<true>): Promise<void> => {
73+
const guilds = await client.guilds.fetch().then((guilds) => guilds.map((guild) => guild.fetch()));
74+
const roles = await Promise.all(guilds).then((guilds) =>
75+
guilds.map((guild) => guild.roles.cache.find((role) => role.name === MUTED_ON_COUBEH)),
76+
);
77+
78+
for (const role of roles) {
79+
if (!role) continue;
80+
await role.delete();
81+
}
82+
};
83+
84+
export const addQuoiFeurToChannel = async (interaction: ChatInputCommandInteraction) => {
85+
const channel = interaction.channel;
86+
if (!channel || !channel.isTextBased() || channel.type !== ChannelType.GuildText) return;
87+
88+
const channels = await cache.get('quoiFeurChannels', []);
89+
if (channels.includes(channel.id)) {
90+
await interaction.reply('Quoi-feur is already enabled in this channel');
91+
return;
92+
}
93+
94+
const role = await createRoleMutedOnCoubeh(interaction.guild);
95+
await channel.permissionOverwrites.create(role, {
96+
SendMessages: false,
97+
CreatePublicThreads: false,
98+
CreatePrivateThreads: false,
99+
SendMessagesInThreads: false,
100+
SendTTSMessages: false,
101+
AttachFiles: false,
102+
});
103+
await cache.set('quoiFeurChannels', [...channels, channel.id]);
104+
await interaction.reply('Quoi-feur enabled in this channel');
105+
};
106+
107+
export const removeQuoiFeurFromChannel = async (interaction: ChatInputCommandInteraction) => {
108+
const channel = interaction.channel;
109+
if (!channel || !channel.isTextBased() || channel.type !== ChannelType.GuildText) return;
110+
111+
const channels = await cache.get('quoiFeurChannels', []);
112+
if (!channels.includes(channel.id)) {
113+
await interaction.reply('Quoi-feur is not enabled in this channel');
114+
return;
115+
}
116+
117+
const role = interaction.guild?.roles.cache.find((r) => r.name === MUTED_ON_COUBEH);
118+
if (role) {
119+
await channel.permissionOverwrites.delete(role);
120+
}
121+
await cache.set(
122+
'quoiFeurChannels',
123+
channels.filter((channelId) => channelId !== channel.id),
124+
);
125+
await interaction.reply('Quoi-feur disabled in this channel');
126+
};
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { SlashCommandBuilder } from 'discord.js';
2+
3+
import type { BotModule } from '../../types/bot';
4+
import {
5+
addQuoiFeurToChannel,
6+
deleteRoleMutedOnCoubeh,
7+
reactOnEndWithQuoi,
8+
removeQuoiFeurFromChannel,
9+
} from './quoiFeur.helpers';
10+
11+
export const quoiFeur: BotModule = {
12+
slashCommands: [
13+
{
14+
schema: new SlashCommandBuilder()
15+
.setName('quoi-feur')
16+
.setDescription('Manage quoi-feur game in the channel')
17+
.addSubcommand((subcommand) =>
18+
subcommand.setName('add').setDescription('Add the quoi-feur game to the channel'),
19+
)
20+
.addSubcommand((subcommand) =>
21+
subcommand.setName('remove').setDescription('Remove the quoi-feur game from the channel'),
22+
)
23+
.toJSON(),
24+
handler: {
25+
add: addQuoiFeurToChannel,
26+
remove: removeQuoiFeurFromChannel,
27+
},
28+
},
29+
],
30+
eventHandlers: {
31+
// unmute everyone in every server on bot restart
32+
ready: deleteRoleMutedOnCoubeh,
33+
messageCreate: reactOnEndWithQuoi,
34+
},
35+
};

0 commit comments

Comments
 (0)