"Scans all text channels in the current guild and returns the number of times each emoji specific to the guild has been used. Has a cooldown of 24 hours per guild.",
// Test if the command is on cooldown. This isn't the strictest cooldown possible, because in the event that the bot crashes, the cooldown will be reset. But for all intends and purposes, it's a good enough cooldown. It's a per-server cooldown.
// If it's been less than an hour since the command was last used, prevent it from executing.
if(difference<cooldown)
return$.channel.send(
`This command requires a day to cooldown. You'll be able to activate this command ${howLong}.`
);
elselastUsedTimestamps[$.guild.id]=startTime;
conststats:{
[id: string]:{
name: string;
formatted: string;
users: number;
bots: number;
};
}={};
lettotalUserEmoteUsage=0;
// IMPORTANT: You MUST check if the bot actually has access to the channel in the first place. It will get the list of all channels, but that doesn't mean it has access to every channel. Without this, it'll require admin access and throw an annoying unhelpful DiscordAPIError: Missing Access otherwise.
// Initialize the emote stats object with every emote in the current guild.
// The goal here is to cut the need to access guild.emojis.get() which'll make it faster and easier to work with.
for(letemoteof$.guild.emojis.cache.values()){
// If you don't include the "a" for animated emotes, it'll not only not show up, but also cause all other emotes in the same message to not show up. The emote name is self-correcting but it's better to keep the right value since it'll be used to calculate message lengths that fit.
// It's very important to not capture an array of matches then do \d+ on each item because emote names can have numbers in them, causing the bot to not count them correctly.
constsearch=/<a?:.+?:(\d+?)>/g;
consttext=msg.content;
letmatch: RegExpExecArray|null;
while((match=search.exec(text))){
constemoteID=match[1];
if(emoteIDinstats){
if(msg.author.bot)stats[emoteID].bots++;
else{
stats[emoteID].users++;
totalUserEmoteUsage++;
}
}
}
for(constreactionofmsg.reactions.cache.values()){
constemoteID=reaction.emoji.id;
letcontinueReactionLoop=true;
letlastUserID: string|undefined;
letuserReactions=0;
letbotReactions=0;
// An emote's ID will be null if it's a unicode emote.
if(emoteID&&emoteIDinstats){
// There is a simple count property on a reaction, but that doesn't separate users from bots.
// So instead, I'll use that property to check for inconsistencies.
while(continueReactionLoop){
// After logging users, it seems like the order is strictly numerical. As long as that stays consistent, this should work fine.
constusers=awaitreaction.users.fetch({
limit: 100,
after: lastUserID
});
if(users.size>0){
for(constuserofusers.values()){
if(user.bot){
stats[emoteID].bots++;
botReactions++;
}else{
stats[emoteID].users++;
totalUserEmoteUsage++;
userReactions++;
}
lastUserID=user.id;
}
}else{
// Then halt the loop and send warnings of any inconsistencies.
`[Channel: ${channel.id}, Message: ${msg.id}] A reaction count of ${reaction.count} was expected but was given ${userReactions} user reactions and ${botReactions} bot reactions.`
// It's better to send all the lines at once rather than paginate the data because it's quite a memory-intensive task to search all the messages in a server for it, and I wouldn't want to activate the command again just to get to another page.