Skip to content
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

Separate moderator logs from event logs #36

Open
wants to merge 4 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
4 changes: 2 additions & 2 deletions src/commands/ban.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { EmbedBuilder } from 'discord.js';
import { SlashCommandBuilder } from '@discordjs/builders';
import { Ban } from '@/models/bans';
import { banMessageDeleteChoices, sendEventLogMessage, ordinal } from '@/util';
import { banMessageDeleteChoices, sendModLogMessage, ordinal } from '@/util';
import { untrustUser } from '@/leveling';
import { notifyUser } from '@/notifications';
import type { ChatInputCommandInteraction, CommandInteraction, ModalSubmitInteraction } from 'discord.js';
Expand Down Expand Up @@ -84,7 +84,7 @@ export async function banHandler(interaction: CommandInteraction | ModalSubmitIn
iconURL: guild.iconURL()!
});

await sendEventLogMessage(guild, null, eventLogEmbed);
await sendModLogMessage(guild, null, eventLogEmbed);

const { count, rows } = await Ban.findAndCountAll({
where: {
Expand Down
4 changes: 2 additions & 2 deletions src/commands/kick.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { EmbedBuilder } from 'discord.js';
import { SlashCommandBuilder } from '@discordjs/builders';
import { Kick } from '@/models/kicks';
import { Ban } from '@/models/bans';
import { banMessageDeleteChoices, ordinal, sendEventLogMessage } from '@/util';
import { banMessageDeleteChoices, ordinal, sendModLogMessage } from '@/util';
import { untrustUser } from '@/leveling';
import { notifyUser } from '@/notifications';
import type { ChatInputCommandInteraction, CommandInteraction, ModalSubmitInteraction } from 'discord.js';
Expand Down Expand Up @@ -154,7 +154,7 @@ export async function kickHandler(interaction: CommandInteraction | ModalSubmitI
sendMemberEmbeds.push(kickEmbed);
}

await sendEventLogMessage(guild, null, eventLogEmbed);
await sendModLogMessage(guild, null, eventLogEmbed);

if (count > 0) {
const pastKicksEmbed = new EmbedBuilder();
Expand Down
1 change: 1 addition & 0 deletions src/commands/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const editableOptions = [
'channels.nsfw-logs',
'channels.event-logs',
'channels.event-logs.blacklist',
'channels.mod-logs',
'channels.matchmaking',
'channels.notifications',
'matchmaking.lock-timeout-seconds',
Expand Down
16 changes: 8 additions & 8 deletions src/commands/slow-mode.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { SlowMode, SlowModeStage } from '@/models/slow-mode';
import handleSlowMode from '@/slow-mode';
import { SlashCommandBuilder } from '@discordjs/builders';
import { sendEventLogMessage } from '@/util';
import { sendModLogMessage } from '@/util';
import { ChannelType, EmbedBuilder } from 'discord.js';
import type { GuildTextBasedChannel, ChatInputCommandInteraction } from 'discord.js';

Expand Down Expand Up @@ -116,8 +116,8 @@ async function setStageHandler(interaction: ChatInputCommandInteraction): Promis
}
]);
}
await sendEventLogMessage(interaction.guild!, channel.id, auditLogEmbed);

await sendModLogMessage(interaction.guild!, channel.id, auditLogEmbed);

await interaction.followUp({ content: `Set a limit of 1 message every ${limit} seconds above ${threshold} messages per minute on <#${channel.id}>` });
}
Expand Down Expand Up @@ -190,8 +190,8 @@ async function unsetStageHandler(interaction: ChatInputCommandInteraction): Prom
}
]);
}
await sendEventLogMessage(interaction.guild!, channel.id, auditLogEmbed);

await sendModLogMessage(interaction.guild!, channel.id, auditLogEmbed);

await interaction.followUp({ content: `Unset the limit at ${threshold} messages per minute on <#${channel.id}>` });
}
Expand Down Expand Up @@ -270,7 +270,7 @@ async function enableAutoSlowModeHandler(interaction: ChatInputCommandInteractio
]);
}

await sendEventLogMessage(interaction.guild!, channel.id, auditLogEmbed);
await sendModLogMessage(interaction.guild!, channel.id, auditLogEmbed);

await interaction.followUp({ content: `Auto slow mode enabled for <#${channel.id}>` });
}
Expand Down Expand Up @@ -324,7 +324,7 @@ async function enableStaticSlowModeHandler(interaction: ChatInputCommandInteract
iconURL: interaction.guild!.iconURL()!
});

await sendEventLogMessage(interaction.guild!, channel.id, auditLogEmbed);
await sendModLogMessage(interaction.guild!, channel.id, auditLogEmbed);

await interaction.followUp({ content: `Static slow mode enabled for <#${channel.id}>` });
}
Expand Down Expand Up @@ -376,7 +376,7 @@ async function disableSlowModeHandler(interaction: ChatInputCommandInteraction):
iconURL: interaction.guild!.iconURL()!
});

await sendEventLogMessage(interaction.guild!, channel.id, auditLogEmbed);
await sendModLogMessage(interaction.guild!, channel.id, auditLogEmbed);

await interaction.followUp({ content: `Slow mode disabled for <#${channel.id}>` });
}
Expand Down
4 changes: 2 additions & 2 deletions src/commands/warn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { SlashCommandBuilder } from '@discordjs/builders';
import { Warning } from '@/models/warnings';
import { Kick } from '@/models/kicks';
import { Ban } from '@/models/bans';
import { ordinal, sendEventLogMessage } from '@/util';
import { ordinal, sendModLogMessage } from '@/util';
import { untrustUser } from '@/leveling';
import { notifyUser } from '@/notifications';
import type { ChatInputCommandInteraction, CommandInteraction, ModalSubmitInteraction } from 'discord.js';
Expand Down Expand Up @@ -156,7 +156,7 @@ export async function warnHandler(interaction: CommandInteraction | ModalSubmitI
isBan = true;
}

await sendEventLogMessage(guild, null, eventLogEmbed);
await sendModLogMessage(guild, null, eventLogEmbed);

if (punishmentEmbed) {
const pastWarningsEmbed = new EmbedBuilder();
Expand Down
8 changes: 4 additions & 4 deletions src/events/guildAuditLogEntryCreate.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AuditLogEvent, EmbedBuilder } from 'discord.js';
import { sendEventLogMessage } from '@/util';
import { sendEventLogMessage, sendModLogMessage } from '@/util';
import type { Guild, User, GuildAuditLogsEntry } from 'discord.js';

export default async function guildAuditLogEntryCreateHandler(auditLogEntry: GuildAuditLogsEntry, guild: Guild): Promise<void> {
Expand Down Expand Up @@ -76,7 +76,7 @@ async function handleMemberTimedOut(guild: Guild, user: User, executor: User, re
}
);

await sendEventLogMessage(guild, null, embed);
await sendModLogMessage(guild, null, embed);
}

async function handleMemberNicknameChange(guild: Guild, user: User, oldName?: string, newName?: string): Promise<void> {
Expand Down Expand Up @@ -160,7 +160,7 @@ async function handleMemberKick(auditLogEntry: GuildAuditLogsEntry<AuditLogEvent
iconURL: guild.iconURL()!
});

await sendEventLogMessage(guild, null, embed);
await sendModLogMessage(guild, null, embed);
}

async function handleMemberBanAdd(auditLogEntry: GuildAuditLogsEntry<AuditLogEvent.MemberBanAdd>, guild: Guild): Promise<void> {
Expand Down Expand Up @@ -208,7 +208,7 @@ async function handleMemberBanAdd(auditLogEntry: GuildAuditLogsEntry<AuditLogEve
iconURL: guild.iconURL()!
});

await sendEventLogMessage(guild, null, embed);
await sendModLogMessage(guild, null, embed);
}

function logIsForEvent<EventType extends AuditLogEvent>(log: GuildAuditLogsEntry, eventType: EventType): log is GuildAuditLogsEntry<EventType> {
Expand Down
80 changes: 54 additions & 26 deletions src/events/messageDelete.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BaseGuildTextChannel, EmbedBuilder } from 'discord.js';
import { sendEventLogMessage } from '@/util';
import { AuditLogEvent, BaseGuildTextChannel, EmbedBuilder } from 'discord.js';
import { sendEventLogMessage, sendModLogMessage } from '@/util';
import type { Message, PartialMessage } from 'discord.js';

export default async function messageDeleteHandler(message: Message | PartialMessage): Promise<void> {
Expand All @@ -12,8 +12,6 @@ export default async function messageDeleteHandler(message: Message | PartialMes
}

const guild = await message.guild!.fetch();
const member = await message.member.fetch();
const user = member.user;

let messageContent = message.content.length > 1024 ? message.content.substring(0, 1023) + '…' : message.content;
if (messageContent === '') {
Expand All @@ -25,46 +23,76 @@ export default async function messageDeleteHandler(message: Message | PartialMes
channelName = message.channel.name;
}

// * Audit logs will not show up at the same time the message delete event
await new Promise(resolve => setTimeout(resolve, 1000));

const auditLogs = await guild.fetchAuditLogs({
type: AuditLogEvent.MessageDelete,
limit: 5
});
const auditLogEntry = auditLogs.entries.find(entry =>
entry.target.id === message.author.id &&
entry.extra.channel.id === message.channelId &&
Date.now() - entry.createdTimestamp < 5 * 60 * 1000
);
// * This large time range is needed because message delete audit log entries are combined if multiple deletions are
// * performed with the same target user, executor, and channel within 5 minutes. Decreasing this time range would
// * create a risk of false negatives because the combined entry keeps the timestamp of the first deletion.
// ! Edge case: If a user deletes their own message after a moderator deletes one of theirs within 5 minutes and in
// ! the same channel, there will be a false positive mod log entry.

const isDeletedByModerator = auditLogEntry !== undefined;
Comment on lines +26 to +44
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do agree with your original message about this not being super great. This looks like it would be very error prone in large servers like ours where tons of messages are deleted all the time. I'm not sure this is worth keeping

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've gone back and forth on this. I think that it is worth trying out, but if the edge case is hit often enough that it causes a significant amount of confusion, it should be removed. I don't know how common it is for users to delete their own messages within 5 minutes of one of theirs getting deleted by a mod.


const eventLogEmbed = new EmbedBuilder();

eventLogEmbed.setColor(0xC0C0C0);
eventLogEmbed.setTitle('Event Type: _Message Delete_');
eventLogEmbed.setDescription('――――――――――――――――――――――――――――――――――');
eventLogEmbed.setTimestamp(Date.now());
eventLogEmbed.setFields(
{
name: 'User',
value: `<@${user.id}>`
},
{
name: 'User ID',
value: user.id
},
eventLogEmbed.addFields(
{
name: 'Author',
value: `<@${message.author.id}>`
},
{
name: 'Author ID',
value: message.author.id
});

if (isDeletedByModerator) {
const executor = auditLogEntry.executor;

eventLogEmbed.addFields({
name: 'Executor',
value: executor ? `<@${executor.id}>` : 'Unknown'
},
{
name: 'Channel Tag',
value: `<#${message.channelId}>`
},
{
name: 'Channel Name',
value: channelName
},
{
name: 'Message',
value: messageContent
}
);
name: 'Executor ID',
value: executor?.id ?? 'Unknown'
});
}

eventLogEmbed.addFields({
name: 'Channel Tag',
value: `<#${message.channelId}>`
},
{
name: 'Channel Name',
value: channelName
},
{
name: 'Message',
value: messageContent
});

eventLogEmbed.setFooter({
text: 'Pretendo Network',
iconURL: guild.iconURL()!
});

await sendEventLogMessage(guild, message.channelId, eventLogEmbed);
if (isDeletedByModerator) {
await sendModLogMessage(guild, message.channelId, eventLogEmbed);
} else {
await sendEventLogMessage(guild, message.channelId, eventLogEmbed);
}
}
4 changes: 2 additions & 2 deletions src/leveling.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { EmbedBuilder } from 'discord.js';
import { getDB, getDBList } from '@/db';
import { User } from '@/models/users';
import { getRoleFromSettings, sendEventLogMessage } from '@/util';
import { getRoleFromSettings, sendEventLogMessage, sendModLogMessage } from '@/util';
import { sequelize } from '@/sequelize-instance';
import { notifyUser } from '@/notifications';
import type { GuildMember, Message } from 'discord.js';
Expand Down Expand Up @@ -214,5 +214,5 @@ export async function untrustUser(member: GuildMember, newStartDate: Date): Prom
text: 'Pretendo Network',
iconURL: member.guild.iconURL()!
});
await sendEventLogMessage(member.guild, null, eventLogEmbed);
await sendModLogMessage(member.guild, null, eventLogEmbed);
}
21 changes: 18 additions & 3 deletions src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ export function ordinal(number: number): string {
return (number + suffix);
}

export async function sendEventLogMessage(guild: Guild, originId: string | null, embed: EmbedBuilder, content?: string): Promise<Message | null> {
const blacklistedIds = getDBList('channels.event-logs.blacklist');
if (originId && blacklistedIds.includes(originId)) {
export async function sendEventLogMessage(guild: Guild, originID: string | null, embed: EmbedBuilder, content?: string): Promise<Message | null> {
const blacklistedIDs = getDBList('channels.event-logs.blacklist');
if (originID && blacklistedIDs.includes(originID)) {
return null;
}

Expand All @@ -45,6 +45,21 @@ export async function sendEventLogMessage(guild: Guild, originId: string | null,
return logChannel.send({ content, embeds: [embed] });
}

export async function sendModLogMessage(guild: Guild, originID: string | null, embed: EmbedBuilder, content?: string): Promise<Message | null> {
const blacklistedIDs = getDBList('channels.event-logs.blacklist');
if (originID && blacklistedIDs.includes(originID)) {
return null;
}

const logChannel = await getChannelFromSettings(guild, 'channels.mod-logs');
if (!logChannel || logChannel.type !== ChannelType.GuildText) {
console.log('Missing mod log channel!');
return null;
}

return logChannel.send({ content, embeds: [embed] });
}

export async function getChannelFromSettings(guild: Guild, channelName: string): Promise<Channel | null> {
const channelID = getDB().get(channelName);
if (!channelID) {
Expand Down