Skip to content

Commit 0ebca2d

Browse files
committed
refactor(events): improve emoji processing and error handling in message events for better maintainability and clarity
feat(events): enhance message reaction handling to support bulk reactions and improve user experience fix(events): ensure proper handling of guild data retrieval errors and provide fallback mechanisms for missing data
1 parent 10d70c9 commit 0ebca2d

File tree

9 files changed

+211
-177
lines changed

9 files changed

+211
-177
lines changed

src/events/guildCreate.js

Lines changed: 36 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,57 +2,58 @@ import { mediaLinks } from '../helpers/constants.js';
22
import { insertGuild } from '../helpers/mongodbModel.js';
33
import { EmbedBuilder, ChannelType, PermissionsBitField, Events } from 'discord.js';
44

5-
const postToAnyChannel = async (guild, embed) => {
5+
const sendEmbedToChannel = async (channel, embed) => {
66
try {
7-
const channels = await guild.channels.cache;
8-
const foundChannel = await channels.find(
9-
(channel) =>
10-
channel.type === ChannelType.GuildText &&
11-
channel.permissionsFor(guild.members.me).has(PermissionsBitField.Flags.SendMessages) &&
12-
channel.permissionsFor(guild.members.me).has(PermissionsBitField.Flags.ViewChannel),
13-
);
14-
if (foundChannel) {
15-
foundChannel.send({ embeds: [embed] });
16-
}
17-
else {
18-
console.error('No channel access found. Welcome message not sent.');
7+
await channel.send({ embeds: [embed] });
8+
} catch (error) {
9+
console.error(`Failed to send embed to ${channel.name}: ${error.message}`);
10+
}
11+
};
12+
13+
const findFirstAccessibleTextChannel = (guild) => {
14+
return guild.channels.cache.find((channel) =>
15+
channel.type === ChannelType.GuildText &&
16+
channel.permissionsFor(guild.members.me)?.has([
17+
PermissionsBitField.Flags.ViewChannel,
18+
PermissionsBitField.Flags.SendMessages,
19+
])
20+
);
21+
};
22+
23+
const postWelcomeEmbed = async (guild, embed) => {
24+
const publicChannel = guild.publicUpdatesChannel;
25+
if (publicChannel) {
26+
try {
27+
await sendEmbedToChannel(publicChannel, embed);
28+
return;
29+
} catch (error) {
30+
console.error(
31+
`Can't post to public updates channel in ${guild.name}: ${error.message}\nFalling back to another channel.`
32+
);
1933
}
2034
}
21-
catch (e) {
22-
console.error(e);
35+
36+
const fallbackChannel = findFirstAccessibleTextChannel(guild);
37+
if (fallbackChannel) {
38+
await sendEmbedToChannel(fallbackChannel, embed);
39+
} else {
40+
console.error(`No accessible text channels found in ${guild.name}. Welcome message not sent.`);
2341
}
2442
};
2543

2644
export default {
2745
name: Events.GuildCreate,
2846
async execute(guild) {
29-
// console.log(`guildCreate: ${guild.name}, ${guild.id}.`);
30-
31-
const guildsCount = guild.client.guilds.cache.size;
32-
console.log(`Guild Created. Current Count: ${guildsCount}`);
47+
console.log(`Guild Created (${guild.name}). Current Server Count: ${guild.client.guilds.cache.size}`);
3348

34-
// await createDatabase(guild.id);
3549
await insertGuild(guild.client.db, guild);
3650

37-
// Send greeting
3851
const embed = new EmbedBuilder()
3952
.setTitle('Hello! Nice to meet you!')
4053
.setDescription(
41-
mediaLinks +
42-
'\n\nThanks For Adding Me To Your Server!\nDon\'t worry, everything has been setup for you.\nJust make sure I have **View** access to all the channels otherwise I won\'t be able to track emoji usage.\nDo `/help` for a list of commands and if you have any issues or questions, feel free to join our support server.\n\nThanks again and have a nice day! 🙂',
54+
`${mediaLinks}\n\nThanks For Adding Me To Your Server!\nDon't worry, everything has been setup for you.\nJust make sure I have **View** access to all the channels otherwise I won't be able to track emoji usage.\nUse \`/help\` for a list of commands. If you have any issues, feel free to join our support server.\n\nThanks again and have a nice day! 🙂`
4355
);
4456

45-
const publicUpdatesChannel = await guild.publicUpdatesChannel;
46-
if (publicUpdatesChannel) {
47-
publicUpdatesChannel.send({ embeds: [embed] }).catch(async (error) => {
48-
console.error(
49-
`Can't post to public updates channel in ${guild.name}: ${error.message}\nDefaulting to first available text channel.`,
50-
);
51-
await postToAnyChannel(guild, embed);
52-
});
53-
}
54-
else {
55-
await postToAnyChannel(guild, embed);
56-
}
57+
await postWelcomeEmbed(guild, embed);
5758
},
5859
};

src/events/interactionCreate.js

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,33 @@ import { sendErrorFeedback } from '../helpers/utilities.js';
33

44
export default {
55
name: Events.InteractionCreate,
6-
execute(interaction) {
7-
// console.log(`${interaction.user.tag} in #${interaction.channel.name} triggered an interaction.`);
6+
async execute(interaction) {
87
if (!interaction.isCommand()) return;
98

109
const command = interaction.client.commands.get(interaction.commandName);
11-
1210
if (!command) return;
1311

1412
try {
15-
command.execute(interaction);
16-
}
17-
catch (error) {
18-
switch (error.message) {
19-
case 'Cannot read properties of null (reading \'1\')':
20-
interaction.reply({
21-
embeds: [sendErrorFeedback(interaction.commandName, 'No emoji found in `emoji`.')],
22-
});
23-
break;
24-
default:
25-
console.error(`interactionCreate error\n${interaction.commandName}\n${error}`);
26-
return interaction.reply({
27-
embeds: [sendErrorFeedback(interaction.commandName)],
28-
});
13+
await command.execute(interaction);
14+
} catch (error) {
15+
console.error(`Error running command "${interaction.commandName}":\n`, error);
16+
17+
const errorMessage =
18+
error.message === "Cannot read properties of null (reading '1')"
19+
? 'No emoji found in `emoji`.'
20+
: undefined;
21+
22+
// Always check if a reply was already sent
23+
if (interaction.replied || interaction.deferred) {
24+
await interaction.followUp({
25+
embeds: [sendErrorFeedback(interaction.commandName, errorMessage)],
26+
ephemeral: true,
27+
});
28+
} else {
29+
await interaction.reply({
30+
embeds: [sendErrorFeedback(interaction.commandName, errorMessage)],
31+
ephemeral: true,
32+
});
2933
}
3034
}
3135
},

src/events/messageCreate.js

Lines changed: 35 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,61 @@
11
import { Events } from 'discord.js';
22
import { getGuildInfo, addEmojiRecords, insertGuild } from '../helpers/mongodbModel.js';
3-
import {
4-
createEmojiRecord,
5-
extractEmojis,
6-
getUserOpt,
7-
shouldProcessMessage,
8-
} from '../helpers/utilities.js';
9-
10-
async function processEmojis(message) {
3+
import { createEmojiRecord, extractEmojis, getUserOpt, shouldProcessMessage } from '../helpers/utilities.js';
4+
5+
const processEmojis = async (message) => {
116
const emojis = extractEmojis(message);
7+
if (!emojis.length) return false;
8+
129

13-
const emojiRecords = [];
14-
for (const emoji of emojis) {
15-
const guildEmoji = await message.guild.emojis.fetch(emoji[3]).catch(() => null);
16-
if (!guildEmoji) continue;
17-
const emojiRecord = createEmojiRecord(
18-
message.guildId,
19-
message.id,
20-
guildEmoji.id,
21-
message.author.id,
22-
message.createdAt,
23-
'message'
24-
);
25-
26-
emojiRecords.push(emojiRecord);
10+
// Batch-fetch all server emojis once
11+
const emojiCache = await message.guild.emojis.fetch().catch(() => null);
12+
if (!emojiCache) {
13+
console.warn(`Could not fetch emojis for guild ${message.guild?.name}.`);
14+
return false;
2715
}
2816

29-
if (emojiRecords.length === 0) return false;
17+
const emojiRecords = emojis
18+
.map((emoji) => {
19+
const emojiId = emoji[3];
20+
const guildEmoji = emojiCache.get(emojiId);
21+
if (!guildEmoji) return null;
22+
23+
return createEmojiRecord(
24+
message.guildId,
25+
message.id,
26+
guildEmoji.id,
27+
message.author.id,
28+
message.createdAt,
29+
'message'
30+
);
31+
})
32+
.filter(Boolean);
33+
34+
if (!emojiRecords.length) return false;
3035

3136
await addEmojiRecords(message.client.db, emojiRecords);
32-
}
37+
};
3338

34-
async function processMessageCreate(message) {
39+
const processMessageCreate = async (message) => {
3540
const guildInfo = await getGuildInfo(message.client.db, message.guild);
3641
const userOpt = await getUserOpt(guildInfo, message.author.id);
3742

3843
if (shouldProcessMessage(message, guildInfo, userOpt)) {
39-
await processEmojis(message, guildInfo);
44+
await processEmojis(message);
4045
}
41-
}
46+
};
4247

4348
export default {
4449
name: Events.MessageCreate,
4550
async execute(message) {
4651
try {
4752
await processMessageCreate(message);
4853
} catch (error) {
49-
if (error.message == `Cannot read properties of null (reading 'usersOpt')`) {
54+
if (error.message === `Cannot read properties of null (reading 'usersOpt')`) {
55+
console.warn(`Guild data missing for ${message.guild?.name} (${message.guildId}). Reinserting...`);
5056
await insertGuild(message.client.db, message.guild);
5157
} else {
52-
console.error(Events.MessageCreate, error);
58+
console.error(`Error in ${Events.MessageCreate}:`, error);
5359
}
5460
}
5561
},

src/events/messageDelete.js

Lines changed: 38 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,77 +8,84 @@ import {
88
shouldProcessReaction,
99
} from '../helpers/utilities.js';
1010

11-
async function processEmojis(message, guildInfo) {
11+
const processEmojis = async (message, guildInfo) => {
1212
if (isMessagePartial(message)) return false;
13+
1314
const emojis = extractEmojis(message);
15+
if (!emojis.length) return false;
16+
17+
const users = new Set();
18+
const tags = new Set();
1419
let guildEmojiDetected = false;
1520

16-
for (const emoji of emojis) {
17-
const guildEmoji = await message.guild.emojis.fetch(emoji[3]).catch(() => null);
18-
if (!guildEmoji) continue;
19-
guildEmojiDetected = true;
20-
break;
21+
// Check if message itself used guild emojis
22+
for (const [, , , emojiId] of emojis) {
23+
const guildEmoji = await message.guild.emojis.fetch(emojiId).catch(() => null);
24+
if (guildEmoji) {
25+
guildEmojiDetected = true;
26+
break;
27+
}
2128
}
2229

23-
const users = [];
24-
const tags = [];
25-
30+
// Process message author
2631
const messageUserOpt = await getUserOpt(guildInfo, message.author.id);
2732
if (shouldProcessMessage(message, guildInfo, messageUserOpt)) {
28-
users.push(message.author.id);
29-
tags.push('message');
33+
users.add(message.author.id);
34+
tags.add('message');
3035
}
3136

32-
const guildEmojiMessageReactions = message.reactions.cache.filter((reaction) =>
37+
// Process message reactions
38+
const guildReactions = message.reactions.cache.filter((reaction) =>
3339
message.guild.emojis.resolve(reaction.emoji)
3440
);
3541

36-
if (guildEmojiMessageReactions.size > 0) {
42+
if (guildReactions.size > 0) {
3743
guildEmojiDetected = true;
3844

39-
if (shouldProcessReaction(guildEmojiMessageReactions.first(), guildInfo, messageUserOpt)) {
40-
users.push(message.author.id);
41-
tags.push('received-reaction');
45+
if (shouldProcessReaction(guildReactions.first(), guildInfo, messageUserOpt)) {
46+
users.add(message.author.id);
47+
tags.add('received-reaction');
4248
}
4349

44-
for (const messageReaction of guildEmojiMessageReactions.values()) {
45-
for (const reactionUser of messageReaction.users.cache.values()) {
46-
const reactionUserOpt = await getUserOpt(guildInfo, reactionUser.id);
47-
if (shouldProcessReaction(messageReaction, guildInfo, reactionUserOpt)) {
48-
users.push(reactionUser.id);
49-
tags.push('sent-reaction');
50+
for (const reaction of guildReactions.values()) {
51+
for (const user of reaction.users.cache.values()) {
52+
const reactionUserOpt = await getUserOpt(guildInfo, user.id);
53+
if (shouldProcessReaction(reaction, guildInfo, reactionUserOpt)) {
54+
users.add(user.id);
55+
tags.add('sent-reaction');
5056
}
5157
}
5258
}
5359
}
5460

61+
if (!guildEmojiDetected) return false;
62+
5563
const filter = {
5664
guild: message.guildId,
5765
message: message.id,
58-
user: { $in: users },
59-
tag: { $in: tags },
66+
user: { $in: [...users] },
67+
tag: { $in: [...tags] },
6068
};
6169

62-
if (!guildEmojiDetected) return false;
63-
6470
await deleteEmojiRecords(message.client.db, filter);
65-
}
71+
};
6672

67-
async function processMessageDelete(message) {
73+
const processMessageDelete = async (message) => {
6874
const guildInfo = await getGuildInfo(message.client.db, message.guild);
6975
await processEmojis(message, guildInfo);
70-
}
76+
};
7177

7278
export default {
7379
name: Events.MessageDelete,
7480
async execute(message) {
7581
try {
7682
await processMessageDelete(message);
7783
} catch (error) {
78-
if (error.message == `Cannot read properties of null (reading 'usersOpt')`) {
84+
if (error.message === `Cannot read properties of null (reading 'usersOpt')`) {
85+
console.warn(`Guild data missing for ${message.guild?.name} (${message.guildId}). Reinserting...`);
7986
await insertGuild(message.client.db, message.guild);
8087
} else {
81-
console.error(Events.MessageDelete, error);
88+
console.error(`Error in ${Events.MessageDelete}:`, error);
8289
}
8390
}
8491
},

src/events/messageDeleteBulk.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,23 @@ import { Events } from 'discord.js';
22
import { processMessageDelete } from './messageDelete.js';
33
import { insertGuild } from '../helpers/mongodbModel.js';
44

5-
async function processMessageDeleteBulk(messages) {
5+
const processMessageDeleteBulk = async (messages) => {
66
for (const message of messages.values()) {
77
await processMessageDelete(message);
88
}
9-
}
9+
};
1010

1111
export default {
1212
name: Events.MessageBulkDelete,
1313
async execute(messages, channel) {
1414
try {
1515
await processMessageDeleteBulk(messages);
1616
} catch (error) {
17-
if (error.message == `Cannot read properties of null (reading 'usersOpt')`) {
17+
if (error.message === `Cannot read properties of null (reading 'usersOpt')`) {
18+
console.warn(`Guild data missing for ${channel.guild?.name} (${channel.guildId}). Reinserting...`);
1819
await insertGuild(channel.client.db, channel.guild);
1920
} else {
20-
console.error(Events.MessageBulkDelete, error);
21+
console.error(`Error in ${Events.MessageBulkDelete}:`, error);
2122
}
2223
}
2324
},

0 commit comments

Comments
 (0)