mirror of
https://github.com/nx-bat/discordbot-ng.git
synced 2026-09-22 02:19:09 -04:00
[Events] Refresh event loading & organisation (#11)
This commit is contained in:
@@ -0,0 +1,98 @@
|
|||||||
|
import { APIEmbedField, APIRole, AuditLogEvent, Guild, GuildAuditLogsEntry, RoleFlags, SnowflakeUtil } from 'discord.js';
|
||||||
|
import { Database } from '../../shared/Database';
|
||||||
|
import { CreateDefaultEmbed, formatChanges, formatExtras, formatSnowflake, getTargetType } from '../../utils';
|
||||||
|
|
||||||
|
const IGNORED_ACTIONS = [
|
||||||
|
AuditLogEvent.MemberMove,
|
||||||
|
AuditLogEvent.AutoModerationFlagToChannel // Handled by automod.
|
||||||
|
];
|
||||||
|
|
||||||
|
async function shouldLog(entry: GuildAuditLogsEntry, guild: Guild): Promise<boolean> {
|
||||||
|
if (!entry.executorId) return true;
|
||||||
|
|
||||||
|
if (IGNORED_ACTIONS.some(a => entry.action == a)) return false;
|
||||||
|
|
||||||
|
if (entry.action == AuditLogEvent.MemberRoleUpdate) {
|
||||||
|
return await shouldLogRoleChanges(entry as GuildAuditLogsEntry<AuditLogEvent.MemberRoleUpdate>, guild);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function shouldLogRoleChanges(entry: GuildAuditLogsEntry<AuditLogEvent.MemberRoleUpdate>, guild: Guild): Promise<boolean> {
|
||||||
|
for (const change of entry.changes) {
|
||||||
|
// Get role changes from the log.
|
||||||
|
const roles = (await Promise.all((change.new! as Pick<APIRole, 'id' | 'name'>[]).map(c => guild.roles.fetch(c.id))));
|
||||||
|
|
||||||
|
// Check if role is part of onboarding.
|
||||||
|
for (const role of roles) {
|
||||||
|
if (role && !role.flags.has(RoleFlags.InPrompt)) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
event: 'guildAuditLogEntryCreate',
|
||||||
|
|
||||||
|
handler: async (entry: GuildAuditLogsEntry, guild: Guild) => {
|
||||||
|
if (!await shouldLog(entry, guild)) return;
|
||||||
|
|
||||||
|
const settings = await Database.getOrCreateSettings(guild.id);
|
||||||
|
if (!settings.audit_logs_channel_id) return;
|
||||||
|
|
||||||
|
const channel = await guild.channels.fetch(settings.audit_logs_channel_id);
|
||||||
|
if (!channel || !channel.isSendable()) return;
|
||||||
|
|
||||||
|
const fields: APIEmbedField[] = [
|
||||||
|
{
|
||||||
|
name: 'Actor',
|
||||||
|
value: `<@${entry.executorId}>`,
|
||||||
|
inline: true
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
if (entry.targetId) {
|
||||||
|
const targetType = getTargetType(entry.action);
|
||||||
|
fields.push({
|
||||||
|
name: 'Target',
|
||||||
|
value: formatSnowflake(entry.targetId, targetType),
|
||||||
|
inline: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.reason) {
|
||||||
|
fields.push({
|
||||||
|
name: 'Reason',
|
||||||
|
value: entry.reason,
|
||||||
|
inline: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.changes && entry.changes.length > 0) {
|
||||||
|
fields.push({
|
||||||
|
name: 'Changes',
|
||||||
|
value: formatChanges(entry),
|
||||||
|
inline: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.extra) {
|
||||||
|
fields.push({
|
||||||
|
name: 'Options',
|
||||||
|
value: formatExtras(entry, guild),
|
||||||
|
inline: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await channel.send({
|
||||||
|
embeds: [{
|
||||||
|
...CreateDefaultEmbed(guild.client),
|
||||||
|
title: Object.keys(AuditLogEvent)[Object.values(AuditLogEvent).indexOf(entry.action)],
|
||||||
|
timestamp: new Date(Number(SnowflakeUtil.decode(entry.id).timestamp)).toISOString(),
|
||||||
|
fields: fields,
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { GuildBan } from 'discord.js';
|
||||||
|
import { Database } from '../../shared/Database';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
event: 'guildBanRemove',
|
||||||
|
|
||||||
|
handler: async (ban: GuildBan) => {
|
||||||
|
await Database.removeBan(ban.user.id);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { Guild } from 'discord.js';
|
||||||
|
import { Database } from '../../shared/Database';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
event: 'guildCreate',
|
||||||
|
|
||||||
|
handler: async (guild: Guild) => {
|
||||||
|
await Database.getOrCreateSettings(guild.id);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { GuildMember, GuildTextBasedChannel } from 'discord.js';
|
||||||
|
import { Database } from '../../shared/Database';
|
||||||
|
import { getE621Alts } from '../../utils';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
event: 'guildMemberAdd',
|
||||||
|
|
||||||
|
handler: async (member: GuildMember) => {
|
||||||
|
const settings = await Database.getOrCreateSettings(member.guild.id);
|
||||||
|
|
||||||
|
if (settings.new_member_channel_id) {
|
||||||
|
const channel = await member.guild.channels.fetch(settings.new_member_channel_id) as GuildTextBasedChannel;
|
||||||
|
|
||||||
|
if (channel) {
|
||||||
|
const content = `${member.toString()}'s (${member.id}) e621 and discord account(s):\n${await getE621Alts(member.id, member.guild)}`;
|
||||||
|
|
||||||
|
channel.send(content).catch(console.error);
|
||||||
|
|
||||||
|
if (settings.moderator_channel_id && content.includes('[BANNED]')) {
|
||||||
|
const modChannel = await member.guild.channels.fetch(settings.moderator_channel_id) as GuildTextBasedChannel;
|
||||||
|
|
||||||
|
if (modChannel) modChannel.send(`Member joined with banned alts:\n${content}`).catch(console.error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
import { APIEmbedField, APIRole, AuditLogEvent, EmbedBuilder, Guild, GuildAuditLogsEntry, RoleFlags, SnowflakeUtil } from 'discord.js';
|
|
||||||
import { Database } from '../shared/Database';
|
|
||||||
import { formatChanges, formatExtras, formatSnowflake, getTargetType } from '../utils';
|
|
||||||
|
|
||||||
const IGNORED_ACTIONS = [
|
|
||||||
AuditLogEvent.MemberMove,
|
|
||||||
// Handled by automod.
|
|
||||||
AuditLogEvent.AutoModerationFlagToChannel
|
|
||||||
];
|
|
||||||
|
|
||||||
export async function handleAuditLogCreate(entry: GuildAuditLogsEntry, guild: Guild) {
|
|
||||||
if (!await shouldLog(entry, guild)) return;
|
|
||||||
|
|
||||||
const settings = await Database.getOrCreateSettings(guild.id);
|
|
||||||
if (!settings.audit_logs_channel_id) return;
|
|
||||||
|
|
||||||
const channel = await guild.channels.fetch(settings.audit_logs_channel_id);
|
|
||||||
if (!channel || !channel.isSendable()) return;
|
|
||||||
|
|
||||||
const fields: APIEmbedField[] = [
|
|
||||||
{
|
|
||||||
name: 'Actor',
|
|
||||||
value: `<@${entry.executorId}>`,
|
|
||||||
inline: true
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
if (entry.targetId) {
|
|
||||||
const targetType = getTargetType(entry.action);
|
|
||||||
fields.push({
|
|
||||||
name: 'Target',
|
|
||||||
value: formatSnowflake(entry.targetId, targetType),
|
|
||||||
inline: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entry.reason) {
|
|
||||||
fields.push({
|
|
||||||
name: 'Reason',
|
|
||||||
value: entry.reason,
|
|
||||||
inline: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entry.changes && entry.changes.length > 0) {
|
|
||||||
fields.push({
|
|
||||||
name: 'Changes',
|
|
||||||
value: formatChanges(entry),
|
|
||||||
inline: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entry.extra) {
|
|
||||||
fields.push({
|
|
||||||
name: 'Options',
|
|
||||||
value: formatExtras(entry, guild),
|
|
||||||
inline: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const embed = new EmbedBuilder()
|
|
||||||
.setTitle(Object.keys(AuditLogEvent)[Object.values(AuditLogEvent).indexOf(entry.action)])
|
|
||||||
.setTimestamp(Number(SnowflakeUtil.decode(entry.id).timestamp))
|
|
||||||
.addFields(...fields);
|
|
||||||
|
|
||||||
channel.send({ embeds: [embed] });
|
|
||||||
}
|
|
||||||
|
|
||||||
async function shouldLog(entry: GuildAuditLogsEntry, guild: Guild): Promise<boolean> {
|
|
||||||
if (!entry.executorId) return true;
|
|
||||||
|
|
||||||
if (IGNORED_ACTIONS.some(a => entry.action == a)) return false;
|
|
||||||
|
|
||||||
if (entry.action == AuditLogEvent.MemberRoleUpdate) {
|
|
||||||
return await shouldLogRoleChanges(entry as GuildAuditLogsEntry<AuditLogEvent.MemberRoleUpdate>, guild);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function shouldLogRoleChanges(entry: GuildAuditLogsEntry<AuditLogEvent.MemberRoleUpdate>, guild: Guild): Promise<boolean> {
|
|
||||||
for (const change of entry.changes) {
|
|
||||||
// Get role changes from the log.
|
|
||||||
const roles = (await Promise.all((change.new! as Pick<APIRole, 'id' | 'name'>[]).map(c => guild.roles.fetch(c.id))));
|
|
||||||
|
|
||||||
// Check if role is part of onboarding.
|
|
||||||
for (const role of roles) {
|
|
||||||
if (role && !role.flags.has(RoleFlags.InPrompt)) return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import { GuildBan } from 'discord.js';
|
|
||||||
import { Database } from '../shared/Database';
|
|
||||||
|
|
||||||
export async function handleBanRemove(ban: GuildBan) {
|
|
||||||
await Database.removeBan(ban.user.id);
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import { Guild } from 'discord.js';
|
|
||||||
import { Database } from '../shared/Database';
|
|
||||||
|
|
||||||
export async function handleGuildCreate(guild: Guild) {
|
|
||||||
await Database.getOrCreateSettings(guild.id);
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import { GuildMember, GuildTextBasedChannel } from 'discord.js';
|
|
||||||
import { Database } from '../shared/Database';
|
|
||||||
import { getE621Alts } from '../utils';
|
|
||||||
|
|
||||||
export async function handleMemberJoin(member: GuildMember) {
|
|
||||||
const guildSettings = await Database.getOrCreateSettings(member.guild.id);
|
|
||||||
|
|
||||||
if (guildSettings.new_member_channel_id) {
|
|
||||||
const channel = await member.guild.channels.fetch(guildSettings.new_member_channel_id) as GuildTextBasedChannel;
|
|
||||||
|
|
||||||
if (channel) {
|
|
||||||
const content = `${member.toString()}'s (${member.id}) e621 and discord account(s):\n${await getE621Alts(member.id, member.guild)}`;
|
|
||||||
|
|
||||||
channel.send(content).catch(console.error);
|
|
||||||
|
|
||||||
if (guildSettings.moderator_channel_id && content.includes('[BANNED]')) {
|
|
||||||
const modChannel = await member.guild.channels.fetch(guildSettings.moderator_channel_id) as GuildTextBasedChannel;
|
|
||||||
|
|
||||||
if (modChannel) modChannel.send(`Member joined with banned alts:\n${content}`).catch(console.error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import { AnyThreadChannel } from 'discord.js';
|
|
||||||
|
|
||||||
export async function handleThreadCreate(thread: AnyThreadChannel, newlyCreated: boolean) {
|
|
||||||
try {
|
|
||||||
await thread.join();
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Failed to join thread:');
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
import { Guild, GuildMember, GuildTextBasedChannel, time, VoiceBasedChannel, VoiceState } from 'discord.js';
|
|
||||||
import { Database } from '../shared/Database';
|
|
||||||
|
|
||||||
export async function handleVoiceStateUpdate(oldState: VoiceState, newState: VoiceState) {
|
|
||||||
// The logChannel declaration being inside is purposeful, as this event is fired a lot for users talking.
|
|
||||||
if (newState.channelId != null && oldState.channelId != null && newState.channelId != oldState.channelId) {
|
|
||||||
const logChannel = await getVoiceLogsChannel(newState.guild);
|
|
||||||
if (!logChannel) return;
|
|
||||||
|
|
||||||
await sendMovedMessage(logChannel, newState.member!, oldState.channel!, newState.channel!);
|
|
||||||
} else if (oldState.channelId == null && newState.channelId != null) {
|
|
||||||
const logChannel = await getVoiceLogsChannel(newState.guild);
|
|
||||||
if (!logChannel) return;
|
|
||||||
|
|
||||||
await sendJoinMessage(logChannel, newState.member!, newState.channel!);
|
|
||||||
} else if (newState.channelId == null && oldState.channelId != null) {
|
|
||||||
const logChannel = await getVoiceLogsChannel(newState.guild);
|
|
||||||
if (!logChannel) return;
|
|
||||||
|
|
||||||
await sendLeftMessage(logChannel, newState.member!, oldState.channel!);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function sendJoinMessage(channel: GuildTextBasedChannel, member: GuildMember, voiceChannel: VoiceBasedChannel) {
|
|
||||||
await channel.send(`${member} joined ${voiceChannel} at ${time()}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function sendLeftMessage(channel: GuildTextBasedChannel, member: GuildMember, voiceChannel: VoiceBasedChannel) {
|
|
||||||
await channel.send(`${member} left ${voiceChannel} at ${time()}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function sendMovedMessage(channel: GuildTextBasedChannel, member: GuildMember, oldVoiceChannel: VoiceBasedChannel, newVoiceChannel: VoiceBasedChannel) {
|
|
||||||
await channel.send(`${member} moved from ${oldVoiceChannel} to ${newVoiceChannel} at ${time()}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getVoiceLogsChannel(guild: Guild): Promise<GuildTextBasedChannel | undefined> {
|
|
||||||
const settings = await Database.getOrCreateSettings(guild.id);
|
|
||||||
|
|
||||||
if (!settings || !settings.voice_logs_channel_id) return;
|
|
||||||
|
|
||||||
const channel = await guild.channels.fetch(settings.voice_logs_channel_id);
|
|
||||||
|
|
||||||
if (!channel || !channel.isSendable()) return;
|
|
||||||
|
|
||||||
return channel as GuildTextBasedChannel;
|
|
||||||
}
|
|
||||||
+23
-7
@@ -1,7 +1,23 @@
|
|||||||
export * from './handle-audit-log-create';
|
import auditLogCreate from './guild/guildAuditLogEntryCreate';
|
||||||
export * from './handle-ban-remove';
|
import guildBanRemove from './guild/guildBanRemove';
|
||||||
export * from './handle-guild-create';
|
import guildCreate from './guild/guildCreate';
|
||||||
export * from './handle-member-join';
|
import guildMemberAdd from './guild/guildMemberAdd';
|
||||||
export * from './handle-message';
|
import messageCreate from './message/messageCreate';
|
||||||
export * from './handle-thread-create';
|
import messageDelete from './message/messageDelete';
|
||||||
export * from './handle-voice-state-update';
|
import messageDeleteBulk from './message/messageDeleteBulk';
|
||||||
|
import messageUpdate from './message/messageUpdate';
|
||||||
|
import threadCreate from './thread/threadCreate';
|
||||||
|
import voiceStateUpdate from './voice/voiceStateUpdate';
|
||||||
|
|
||||||
|
export default [
|
||||||
|
auditLogCreate,
|
||||||
|
guildBanRemove,
|
||||||
|
guildCreate,
|
||||||
|
guildMemberAdd,
|
||||||
|
messageCreate,
|
||||||
|
messageDelete,
|
||||||
|
messageDeleteBulk,
|
||||||
|
messageUpdate,
|
||||||
|
threadCreate,
|
||||||
|
voiceStateUpdate
|
||||||
|
];
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import { config } from '../../config';
|
||||||
|
import { Database } from '../../shared/Database';
|
||||||
|
import { ALLOWED_MIMETYPES, blacklistIfNecessary, calculateMD5FromURL, getE621PostByMd5, getPostUrl, md5Regex, Message, regexTesters, uniqueRegexMatches } from '../../utils';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
event: 'messageCreate',
|
||||||
|
|
||||||
|
handler: async (message: Message) => {
|
||||||
|
if (message.author.bot) return;
|
||||||
|
if (message.inGuild()) await Database.putMessage(message);
|
||||||
|
|
||||||
|
const responses: string[] = [];
|
||||||
|
|
||||||
|
for (const test of regexTesters) {
|
||||||
|
if (config.DEV_MODE && !test.runInDev) continue;
|
||||||
|
|
||||||
|
const hasMatches = test.regex.test(message.content);
|
||||||
|
test.regex.lastIndex = 0;
|
||||||
|
|
||||||
|
if (hasMatches) {
|
||||||
|
const matches: RegExpExecArray[] = [];
|
||||||
|
let match: RegExpExecArray | null;
|
||||||
|
|
||||||
|
while ((match = test.regex.exec(message.content)) != null) {
|
||||||
|
matches.push(match);
|
||||||
|
}
|
||||||
|
test.regex.lastIndex = 0;
|
||||||
|
|
||||||
|
const response = await test.handler(message, matches.filter(uniqueRegexMatches));
|
||||||
|
|
||||||
|
if (response === false) return;
|
||||||
|
|
||||||
|
if (response !== true) responses.push(response as string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const attachment of message.attachments.values()) {
|
||||||
|
const match = md5Regex.exec(attachment.name);
|
||||||
|
md5Regex.lastIndex = 0;
|
||||||
|
|
||||||
|
const md5s: string[] = [];
|
||||||
|
|
||||||
|
if (match) md5s.push(match[1]);
|
||||||
|
else if (ALLOWED_MIMETYPES.includes(attachment.contentType!)) {
|
||||||
|
const md5Data = await calculateMD5FromURL(attachment.url);
|
||||||
|
if (!md5Data) continue;
|
||||||
|
md5s.push(md5Data.correctedFileMD5, md5Data.originalFileMD5);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (md5s.length == 0) continue;
|
||||||
|
|
||||||
|
for (const md5 of md5s) {
|
||||||
|
const post = await getE621PostByMd5(md5, false);
|
||||||
|
|
||||||
|
if (post) {
|
||||||
|
if (await blacklistIfNecessary(message, [post])) return;
|
||||||
|
|
||||||
|
responses.push(`<${getPostUrl(post)}>`);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (responses.length > 0) {
|
||||||
|
await message.reply(responses.join('\n'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { Message, PartialMessage } from 'discord.js';
|
||||||
|
import { Database } from '../../shared/Database';
|
||||||
|
import { logDeletion } from '../../utils';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
event: 'messageDelete',
|
||||||
|
|
||||||
|
handler: async (message: Message | PartialMessage) => {
|
||||||
|
const loggedMessage = await Database.getMessageWithRetry(message.id);
|
||||||
|
if (!loggedMessage) return;
|
||||||
|
|
||||||
|
await Database.removeMessge(message.id);
|
||||||
|
|
||||||
|
if (message.inGuild()) await logDeletion(loggedMessage, message);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { GuildTextBasedChannel, Message, OmitPartialGroupDMChannel, PartialMessage, ReadonlyCollection } from 'discord.js';
|
||||||
|
import { Database } from '../../shared/Database';
|
||||||
|
import { logDeletion } from '../../utils';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
event: 'messageDeleteBulk',
|
||||||
|
|
||||||
|
handler: async (messages: ReadonlyCollection<string, Message | OmitPartialGroupDMChannel<PartialMessage>>, channel: GuildTextBasedChannel) => {
|
||||||
|
for (const message of messages.values()) {
|
||||||
|
const loggedMessage = await Database.getMessageWithRetry(message.id);
|
||||||
|
if (!loggedMessage) continue;
|
||||||
|
|
||||||
|
await Database.removeMessge(message.id);
|
||||||
|
|
||||||
|
if (message.inGuild()) await logDeletion(loggedMessage, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import { PartialMessage } from 'discord.js';
|
||||||
|
import { config } from '../../config';
|
||||||
|
import { Database } from '../../shared/Database';
|
||||||
|
import { isEdited, logEdit, Message, regexTesters, uniqueRegexMatches } from '../../utils';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
event: 'messageUpdate',
|
||||||
|
|
||||||
|
handler: async (oldMessage: Message | PartialMessage, newMessage: Message) => {
|
||||||
|
if (newMessage.author.bot) return;
|
||||||
|
|
||||||
|
const loggedMessage = await Database.getMessageWithRetry(newMessage.id);
|
||||||
|
|
||||||
|
if (!loggedMessage) {
|
||||||
|
if (newMessage.inGuild()) await Database.putMessage(newMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newMessage.inGuild() && isEdited(loggedMessage, newMessage)) {
|
||||||
|
await Database.putMessage(newMessage);
|
||||||
|
await logEdit(loggedMessage, newMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loggedMessage.content == newMessage.content) return;
|
||||||
|
|
||||||
|
const responses: string[] = [];
|
||||||
|
|
||||||
|
for (const test of regexTesters) {
|
||||||
|
if (config.DEV_MODE && !test.runInDev) continue;
|
||||||
|
|
||||||
|
const hasMatches = test.regex.test(newMessage.content);
|
||||||
|
test.regex.lastIndex = 0;
|
||||||
|
|
||||||
|
if (hasMatches) {
|
||||||
|
const oldMatches: RegExpExecArray[] = [];
|
||||||
|
const newMatches: RegExpExecArray[] = [];
|
||||||
|
let match: RegExpExecArray | null;
|
||||||
|
|
||||||
|
while ((match = test.regex.exec(newMessage.content)) != null) {
|
||||||
|
newMatches.push(match);
|
||||||
|
}
|
||||||
|
test.regex.lastIndex = 0;
|
||||||
|
|
||||||
|
while ((match = test.regex.exec(loggedMessage.content)) != null) {
|
||||||
|
oldMatches.push(match);
|
||||||
|
}
|
||||||
|
test.regex.lastIndex = 0;
|
||||||
|
|
||||||
|
const properMatches: RegExpExecArray[] = [];
|
||||||
|
|
||||||
|
for (const newMatch of newMatches) {
|
||||||
|
if (!oldMatches.find(m => m[1] == newMatch[1])) properMatches.push(newMatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (properMatches.length == 0) continue;
|
||||||
|
|
||||||
|
const response = await test.handler(newMessage, properMatches.filter(uniqueRegexMatches));
|
||||||
|
|
||||||
|
if (response === false) return;
|
||||||
|
|
||||||
|
if (response !== true) responses.push(response as string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (responses.length > 0) {
|
||||||
|
await newMessage.reply(responses.join('\n'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { AnyThreadChannel } from 'discord.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
event: 'threadCreate',
|
||||||
|
|
||||||
|
handler: async (thread: AnyThreadChannel, newlyCreated: boolean) => {
|
||||||
|
try {
|
||||||
|
await thread.join();
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Failed to join thread: ${thread.name} (${thread.id})`, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
import { Guild, GuildTextBasedChannel, VoiceState } from 'discord.js';
|
||||||
|
import { Database } from '../../shared/Database';
|
||||||
|
import { CreateDefaultEmbed, SetSeverity } from '../../utils';
|
||||||
|
|
||||||
|
async function getVoiceLogsChannel(guild: Guild): Promise<GuildTextBasedChannel | undefined> {
|
||||||
|
const settings = await Database.getOrCreateSettings(guild.id);
|
||||||
|
if (!settings || !settings.voice_logs_channel_id) return;
|
||||||
|
|
||||||
|
const channel = await guild.channels.fetch(settings.voice_logs_channel_id);
|
||||||
|
if (!channel || !channel.isSendable()) return;
|
||||||
|
|
||||||
|
return channel as GuildTextBasedChannel;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function memberJoinedChannel(_: VoiceState, state: VoiceState) {
|
||||||
|
const channel = await getVoiceLogsChannel(state.guild);
|
||||||
|
if (!channel) return;
|
||||||
|
|
||||||
|
await channel.send({
|
||||||
|
embeds: [{
|
||||||
|
...CreateDefaultEmbed(state.client),
|
||||||
|
...SetSeverity('success'),
|
||||||
|
|
||||||
|
title: 'Joined Voice Channel',
|
||||||
|
|
||||||
|
fields: [
|
||||||
|
{ name: 'Member', value: `<@${state.member?.id}>`, inline: false },
|
||||||
|
{ name: 'Channel', value: `${state.channel?.name} (${state.channelId})`, inline: true },
|
||||||
|
{ name: 'Mention', value: `<#${state.channelId}>`, inline: true },
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function memberLeftChannel(state: VoiceState, _: VoiceState) {
|
||||||
|
const channel = await getVoiceLogsChannel(state.guild);
|
||||||
|
if (!channel) return;
|
||||||
|
|
||||||
|
await channel.send({
|
||||||
|
embeds: [{
|
||||||
|
...CreateDefaultEmbed(state.client),
|
||||||
|
...SetSeverity('error'),
|
||||||
|
|
||||||
|
title: 'Left Voice Channel',
|
||||||
|
fields: [
|
||||||
|
{ name: 'Member', value: `<@${state.member?.id}>`, inline: false },
|
||||||
|
{ name: 'Channel', value: `${state.channel?.name} (${state.channelId})`, inline: true },
|
||||||
|
{ name: 'Mention', value: `<#${state.channelId}>`, inline: true },
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function memberMovedChannel(oldState: VoiceState, newState: VoiceState) {
|
||||||
|
const channel = await getVoiceLogsChannel(newState.guild);
|
||||||
|
if (!channel) return;
|
||||||
|
|
||||||
|
await channel.send({
|
||||||
|
embeds: [{
|
||||||
|
...CreateDefaultEmbed(newState.client),
|
||||||
|
...SetSeverity('warning'),
|
||||||
|
|
||||||
|
title: 'Moved Voice Channel',
|
||||||
|
|
||||||
|
fields: [
|
||||||
|
{ name: 'Member', value: `<@${newState.member?.id}>`, inline: false },
|
||||||
|
{ name: 'Channel', value: `<#${oldState.channelId}> -> <#${newState.channelId}>`, inline: false },
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
event: 'voiceStateUpdate',
|
||||||
|
|
||||||
|
handler: async (oldState: VoiceState, newState: VoiceState) => {
|
||||||
|
// Ignore non-movement voiceStateUpdate events.
|
||||||
|
if (oldState.channelId === newState.channelId) return;
|
||||||
|
|
||||||
|
if (newState.channelId != null && oldState.channelId != null && newState.channelId != oldState.channelId)
|
||||||
|
await memberMovedChannel(oldState, newState);
|
||||||
|
else if (oldState.channelId == null && newState.channelId != null)
|
||||||
|
await memberJoinedChannel(oldState, newState);
|
||||||
|
else if (newState.channelId == null && oldState.channelId != null)
|
||||||
|
await memberLeftChannel(oldState, newState);
|
||||||
|
}
|
||||||
|
};
|
||||||
+8
-11
@@ -1,6 +1,6 @@
|
|||||||
import { Client as DiscordClient, GatewayIntentBits, MessageFlags, Partials } from 'discord.js';
|
import { Client as DiscordClient, GatewayIntentBits, MessageFlags, Partials } from 'discord.js';
|
||||||
import { config } from './config';
|
import { config } from './config';
|
||||||
import { handleAuditLogCreate, handleBanRemove, handleBulkMessageDelete, handleGuildCreate, handleMemberJoin, handleMessageCreate, handleMessageDelete, handleMessageUpdate, handleThreadCreate, handleVoiceStateUpdate } from './events';
|
import events from './events';
|
||||||
import { ScheduledTasks, Scheduler } from './scheduler';
|
import { ScheduledTasks, Scheduler } from './scheduler';
|
||||||
import { Database } from './shared/Database';
|
import { Database } from './shared/Database';
|
||||||
import { openRedisClient } from './shared/RedisClient';
|
import { openRedisClient } from './shared/RedisClient';
|
||||||
@@ -9,6 +9,7 @@ import { initIfNecessary, loadHandlersFrom, refreshCommands } from './utils';
|
|||||||
import { initializeWebserver } from './webserver';
|
import { initializeWebserver } from './webserver';
|
||||||
import { Encrypter } from './shared/Encrypter';
|
import { Encrypter } from './shared/Encrypter';
|
||||||
|
|
||||||
|
|
||||||
let ready = false;
|
let ready = false;
|
||||||
|
|
||||||
console.log('Starting...');
|
console.log('Starting...');
|
||||||
@@ -142,16 +143,12 @@ client.on('clientReady', async () => {
|
|||||||
|
|
||||||
ScheduledTasks.forEach(task => scheduler.add(task));
|
ScheduledTasks.forEach(task => scheduler.add(task));
|
||||||
|
|
||||||
client.on('guildAuditLogEntryCreate', handleAuditLogCreate);
|
events.forEach(event =>
|
||||||
client.on('guildBanRemove', handleBanRemove);
|
client.on(event.event, (...args) =>
|
||||||
client.on('guildCreate', handleGuildCreate);
|
Promise.resolve((event.handler as (...args: unknown[]) => Promise<void>)(...args))
|
||||||
client.on('guildMemberAdd', handleMemberJoin);
|
.catch(e => console.error(`Error in ${event.event} handler:`, e))
|
||||||
client.on('messageCreate', handleMessageCreate);
|
)
|
||||||
client.on('messageDelete', handleMessageDelete);
|
);
|
||||||
client.on('messageDeleteBulk', handleBulkMessageDelete);
|
|
||||||
client.on('messageUpdate', handleMessageUpdate);
|
|
||||||
client.on('threadCreate', handleThreadCreate);
|
|
||||||
client.on('voiceStateUpdate', handleVoiceStateUpdate);
|
|
||||||
|
|
||||||
ready = true;
|
ready = true;
|
||||||
console.log('Ready');
|
console.log('Ready');
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
|
import { readFileSync } from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { open, Database as SqliteDatabase } from 'sqlite';
|
import { open, Database as SqliteDatabase } from 'sqlite';
|
||||||
import sqlite3 from 'sqlite3';
|
import sqlite3 from 'sqlite3';
|
||||||
import { Message } from '../events';
|
|
||||||
import { AppealMessage, Ban, GithubUserMapping, GuildArraySetting, GuildSettings, KnowledgebaseItem, LoggedMessage, Note, PrivateHelpTicket, RoleButton, TicketMessage, TicketPhrase } from '../types';
|
import { AppealMessage, Ban, GithubUserMapping, GuildArraySetting, GuildSettings, KnowledgebaseItem, LoggedMessage, Note, PrivateHelpTicket, RoleButton, TicketMessage, TicketPhrase } from '../types';
|
||||||
import { deserializeMessage, serializeMessage, wait } from '../utils';
|
import { deserializeMessage, Message, serializeMessage, wait } from '../utils';
|
||||||
import { readFileSync } from 'fs';
|
|
||||||
import { Encrypter } from './Encrypter';
|
import { Encrypter } from './Encrypter';
|
||||||
|
|
||||||
export const enum PrivateHelpTicketStatus {
|
export const enum PrivateHelpTicketStatus {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { APIEmbed, Client } from 'discord.js';
|
import { APIEmbed, Client } from 'discord.js';
|
||||||
|
|
||||||
type EmbedSeverity =
|
type EmbedSeverity =
|
||||||
| 'default'
|
|
||||||
| 'info'
|
| 'info'
|
||||||
| 'warning'
|
| 'warning'
|
||||||
| 'error'
|
| 'error'
|
||||||
@@ -11,6 +10,8 @@ export function CreateDefaultEmbed(context: Client): APIEmbed {
|
|||||||
if (!context.user) return {};
|
if (!context.user) return {};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
color: 0x014995,
|
||||||
|
|
||||||
footer: {
|
footer: {
|
||||||
icon_url: context.user.avatarURL()!,
|
icon_url: context.user.avatarURL()!,
|
||||||
text: context.user.username
|
text: context.user.username
|
||||||
@@ -22,7 +23,6 @@ export function CreateDefaultEmbed(context: Client): APIEmbed {
|
|||||||
|
|
||||||
export function SetSeverity(severity: EmbedSeverity): APIEmbed {
|
export function SetSeverity(severity: EmbedSeverity): APIEmbed {
|
||||||
switch (severity) {
|
switch (severity) {
|
||||||
case 'default': return { color: 0x014995 };
|
|
||||||
case 'info': return { color: 0x5865F2 };
|
case 'info': return { color: 0x5865F2 };
|
||||||
case 'warning': return { color: 0xFEE75C };
|
case 'warning': return { color: 0xFEE75C };
|
||||||
case 'error': return { color: 0xED4245 };
|
case 'error': return { color: 0xED4245 };
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { APIEmbedField, AttachmentBuilder, EmbedBuilder, Guild, GuildBasedChannel, GuildTextBasedChannel, MessageCreateOptions } from 'discord.js';
|
import { APIEmbedField, AttachmentBuilder, EmbedBuilder, Guild, GuildBasedChannel, GuildTextBasedChannel, MessageCreateOptions } from 'discord.js';
|
||||||
import { Message } from '../events';
|
|
||||||
import { Database } from '../shared/Database';
|
import { Database } from '../shared/Database';
|
||||||
import { LoggedMessage } from '../types';
|
import { LoggedMessage } from '../types';
|
||||||
|
import { Message } from '../utils';
|
||||||
import { channelIsInStaffCategory } from './channel-utils';
|
import { channelIsInStaffCategory } from './channel-utils';
|
||||||
import { deserializeMessagePart, getModifiedAttachments, getModifiedStickers } from './message-utils';
|
import { deserializeMessagePart, getModifiedAttachments, getModifiedStickers } from './message-utils';
|
||||||
|
|
||||||
@@ -216,4 +216,4 @@ function getEditEmbeds(loggedMessage: LoggedMessage, newMessage: Message<true>,
|
|||||||
}
|
}
|
||||||
|
|
||||||
return fields;
|
return fields;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ export * from './file-utils';
|
|||||||
export * from './github-user-utils';
|
export * from './github-user-utils';
|
||||||
export * from './interaction-utils';
|
export * from './interaction-utils';
|
||||||
export * from './message-matcher-regex';
|
export * from './message-matcher-regex';
|
||||||
|
export * from './message-handler-utils';
|
||||||
export * from './message-utils';
|
export * from './message-utils';
|
||||||
export * from './modal-utils';
|
export * from './modal-utils';
|
||||||
export * from './ms-to-human';
|
export * from './ms-to-human';
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
import { Message as DiscordMessage, GuildBasedChannel, GuildTextBasedChannel, OmitPartialGroupDMChannel, PartialMessage, ReadonlyCollection, spoiler } from 'discord.js';
|
import { Message as DiscordMessage, GuildBasedChannel, GuildTextBasedChannel, spoiler, OmitPartialGroupDMChannel } from 'discord.js';
|
||||||
import { config } from '../config';
|
import { config } from '../config';
|
||||||
import { Database } from '../shared/Database';
|
import { appealIDRegex, artistIDRegex, blipIDRegex, channelIgnoresLinks, channelIsInStaffCategory, channelIsSafe, commentIDRegex, flagIDRegex, forumTopicIDRegex, getE621Pool, getE621Post, getE621PostByMd5, getManyE621Posts, getPoolUrl, getPostUrl, isInSpoilerTags, issueRegex, poolIDRegex, PostAction, postIDRegex, prRegex, recordIDRegex, searchLinkRegex, setIDRegex, spoilerOrBlacklist, takedownIDRegex, ticketIDRegex, userIDRegex, wikiLinkRegex } from '.';
|
||||||
import { E621Pool, E621Post } from '../types';
|
import { E621Post, E621Pool } from '../types';
|
||||||
import { ALLOWED_MIMETYPES, appealIDRegex, artistIDRegex, blipIDRegex, calculateMD5FromURL, channelIgnoresLinks, channelIsInStaffCategory, channelIsSafe, commentIDRegex, flagIDRegex, forumTopicIDRegex, getE621Pool, getE621Post, getE621PostByMd5, getManyE621Posts, getPoolUrl, getPostUrl, isEdited, isInSpoilerTags, issueRegex, logDeletion, logEdit, poolIDRegex, PostAction, postIDRegex, prRegex, recordIDRegex, searchLinkRegex, setIDRegex, spoilerOrBlacklist, takedownIDRegex, ticketIDRegex, userIDRegex, wikiLinkRegex } from '../utils';
|
|
||||||
|
|
||||||
export type Message<InGuild extends boolean = boolean> = OmitPartialGroupDMChannel<DiscordMessage<InGuild>>;
|
export type Message<InGuild extends boolean = boolean> = OmitPartialGroupDMChannel<DiscordMessage<InGuild>>;
|
||||||
export type Partial = OmitPartialGroupDMChannel<PartialMessage>;
|
|
||||||
|
|
||||||
// TODO: I don't know of any good way to not hardcode this regex for e621 links. So I've provided two that may need to have the port altered.
|
|
||||||
const postRegex = new RegExp('!?https?://(?:.*@)?(?:e621|e926)\\.net/+posts/+([0-9]+)', 'gi');
|
const postRegex = new RegExp('!?https?://(?:.*@)?(?:e621|e926)\\.net/+posts/+([0-9]+)', 'gi');
|
||||||
const postShareRegex = new RegExp('!?https?://(?:.*@)?(?:e621|e926)\\.net/+p/+([a-z0-9]+)', 'gi');
|
const postShareRegex = new RegExp('!?https?://(?:.*@)?(?:e621|e926)\\.net/+p/+([a-z0-9]+)', 'gi');
|
||||||
const imageRegex = new RegExp('!?https?://(?:.*@)?static[0-9]*\\.(?:e621|e926)\\.net/+data/+(?:sample/+|preview/+|)[\\da-f]{2}/+[\\da-f]{2}/+([\\da-f]{32})\\.[\\da-z]+', 'gi');
|
const imageRegex = new RegExp('!?https?://(?:.*@)?static[0-9]*\\.(?:e621|e926)\\.net/+data/+(?:sample/+|preview/+|)[\\da-f]{2}/+[\\da-f]{2}/+([\\da-f]{32})\\.[\\da-z]+', 'gi');
|
||||||
@@ -17,9 +14,9 @@ const postRegex_DEV = new RegExp('!?https?://(?:.*@)?localhost:3000/+posts/+([0-
|
|||||||
const imageRegex_DEV = new RegExp('!?https?://(?:.*@)?localhost:3000/+data/+(?:sample/+|preview/+|)[\\da-f]{2}/+[\\da-f]{2}/+([\\da-f]{32})\\.[\\da-z]+', 'gi');
|
const imageRegex_DEV = new RegExp('!?https?://(?:.*@)?localhost:3000/+data/+(?:sample/+|preview/+|)[\\da-f]{2}/+[\\da-f]{2}/+([\\da-f]{32})\\.[\\da-z]+', 'gi');
|
||||||
const poolRegex_DEV = new RegExp('!?https?://(?:.*@)?localhost:3000/+pools/+([0-9]+)', 'gi');
|
const poolRegex_DEV = new RegExp('!?https?://(?:.*@)?localhost:3000/+pools/+([0-9]+)', 'gi');
|
||||||
|
|
||||||
const md5Regex = new RegExp('^([a-f0-9]{32}).(?:png|apng|jpg|jpeg|gif|webm|mp4)$', 'gi');
|
export const md5Regex = new RegExp('^([a-f0-9]{32}).(?:png|apng|jpg|jpeg|gif|webm|mp4)$', 'gi');
|
||||||
|
|
||||||
const regexTesters = [
|
export const regexTesters = [
|
||||||
{ runInDev: false, regex: postRegex, handler: postHandler.bind(null, null) },
|
{ runInDev: false, regex: postRegex, handler: postHandler.bind(null, null) },
|
||||||
{
|
{
|
||||||
runInDev: false, regex: postShareRegex, handler: postHandler.bind(null, (idString: string) => {
|
runInDev: false, regex: postShareRegex, handler: postHandler.bind(null, (idString: string) => {
|
||||||
@@ -51,148 +48,7 @@ const regexTesters = [
|
|||||||
{ runInDev: true, regex: issueRegex, handler: githubIssueHandler },
|
{ runInDev: true, regex: issueRegex, handler: githubIssueHandler },
|
||||||
];
|
];
|
||||||
|
|
||||||
const uniqueRegexMatches = (g, i, a) => a.findIndex(v => v[1] == g[1]) == i;
|
export const uniqueRegexMatches = (g, i, a) => a.findIndex(v => v[1] == g[1]) == i;
|
||||||
|
|
||||||
export async function handleMessageCreate(message: Message) {
|
|
||||||
if (message.author.bot) return;
|
|
||||||
if (message.inGuild()) await Database.putMessage(message);
|
|
||||||
|
|
||||||
const responses: string[] = [];
|
|
||||||
|
|
||||||
for (const test of regexTesters) {
|
|
||||||
if (config.DEV_MODE && !test.runInDev) continue;
|
|
||||||
|
|
||||||
const hasMatches = test.regex.test(message.content);
|
|
||||||
test.regex.lastIndex = 0;
|
|
||||||
|
|
||||||
if (hasMatches) {
|
|
||||||
const matches: RegExpExecArray[] = [];
|
|
||||||
let match: RegExpExecArray | null;
|
|
||||||
|
|
||||||
while ((match = test.regex.exec(message.content)) != null) {
|
|
||||||
matches.push(match);
|
|
||||||
}
|
|
||||||
test.regex.lastIndex = 0;
|
|
||||||
|
|
||||||
const response = await test.handler(message, matches.filter(uniqueRegexMatches));
|
|
||||||
|
|
||||||
if (response === false) return;
|
|
||||||
|
|
||||||
if (response !== true) responses.push(response as string);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const attachment of message.attachments.values()) {
|
|
||||||
const match = md5Regex.exec(attachment.name);
|
|
||||||
md5Regex.lastIndex = 0;
|
|
||||||
|
|
||||||
const md5s: string[] = [];
|
|
||||||
|
|
||||||
if (match) md5s.push(match[1]);
|
|
||||||
else if (ALLOWED_MIMETYPES.includes(attachment.contentType!)) {
|
|
||||||
const md5Data = await calculateMD5FromURL(attachment.url);
|
|
||||||
if (!md5Data) continue;
|
|
||||||
md5s.push(md5Data.correctedFileMD5, md5Data.originalFileMD5);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (md5s.length == 0) continue;
|
|
||||||
|
|
||||||
for (const md5 of md5s) {
|
|
||||||
const post = await getE621PostByMd5(md5, false);
|
|
||||||
|
|
||||||
if (post) {
|
|
||||||
if (await blacklistIfNecessary(message, [post])) return;
|
|
||||||
|
|
||||||
responses.push(`<${getPostUrl(post)}>`);
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (responses.length > 0) {
|
|
||||||
await message.reply(responses.join('\n'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function handleMessageUpdate(oldMessage: Message | PartialMessage, newMessage: Message) {
|
|
||||||
if (newMessage.author.bot) return;
|
|
||||||
|
|
||||||
const loggedMessage = await Database.getMessageWithRetry(newMessage.id);
|
|
||||||
|
|
||||||
if (!loggedMessage) {
|
|
||||||
if (newMessage.inGuild()) await Database.putMessage(newMessage);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newMessage.inGuild() && isEdited(loggedMessage, newMessage)) {
|
|
||||||
await Database.putMessage(newMessage);
|
|
||||||
await logEdit(loggedMessage, newMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loggedMessage.content == newMessage.content) return;
|
|
||||||
|
|
||||||
const responses: string[] = [];
|
|
||||||
|
|
||||||
for (const test of regexTesters) {
|
|
||||||
if (config.DEV_MODE && !test.runInDev) continue;
|
|
||||||
|
|
||||||
const hasMatches = test.regex.test(newMessage.content);
|
|
||||||
test.regex.lastIndex = 0;
|
|
||||||
|
|
||||||
if (hasMatches) {
|
|
||||||
const oldMatches: RegExpExecArray[] = [];
|
|
||||||
const newMatches: RegExpExecArray[] = [];
|
|
||||||
let match: RegExpExecArray | null;
|
|
||||||
|
|
||||||
while ((match = test.regex.exec(newMessage.content)) != null) {
|
|
||||||
newMatches.push(match);
|
|
||||||
}
|
|
||||||
test.regex.lastIndex = 0;
|
|
||||||
|
|
||||||
while ((match = test.regex.exec(loggedMessage.content)) != null) {
|
|
||||||
oldMatches.push(match);
|
|
||||||
}
|
|
||||||
test.regex.lastIndex = 0;
|
|
||||||
|
|
||||||
const properMatches: RegExpExecArray[] = [];
|
|
||||||
|
|
||||||
for (const newMatch of newMatches) {
|
|
||||||
if (!oldMatches.find(m => m[1] == newMatch[1])) properMatches.push(newMatch);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (properMatches.length == 0) continue;
|
|
||||||
|
|
||||||
const response = await test.handler(newMessage, properMatches.filter(uniqueRegexMatches));
|
|
||||||
|
|
||||||
if (response === false) return;
|
|
||||||
|
|
||||||
if (response !== true) responses.push(response as string);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (responses.length > 0) {
|
|
||||||
await newMessage.reply(responses.join('\n'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function handleMessageDelete(message: Message | PartialMessage) {
|
|
||||||
const loggedMessage = await Database.getMessageWithRetry(message.id);
|
|
||||||
|
|
||||||
if (!loggedMessage) return;
|
|
||||||
else await Database.removeMessge(message.id);
|
|
||||||
|
|
||||||
if (message.inGuild()) {
|
|
||||||
await logDeletion(loggedMessage, message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function handleBulkMessageDelete(messages: ReadonlyCollection<string, Message | Partial>, channel: GuildTextBasedChannel) {
|
|
||||||
for (const message of messages.values()) {
|
|
||||||
await handleMessageDelete(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function searchHandler(message: Message, matchedGroups: RegExpExecArray[]): Promise<string | boolean> {
|
async function searchHandler(message: Message, matchedGroups: RegExpExecArray[]): Promise<string | boolean> {
|
||||||
const skip = await channelIgnoresLinks(message.channel as GuildBasedChannel);
|
const skip = await channelIgnoresLinks(message.channel as GuildBasedChannel);
|
||||||
@@ -226,7 +82,7 @@ async function wikiPageHandler(message: Message, matchedGroups: RegExpExecArray[
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function blacklistIfNecessary(message: Message, posts: E621Post[]): Promise<boolean> {
|
export async function blacklistIfNecessary(message: Message, posts: E621Post[]): Promise<boolean> {
|
||||||
const blacklistedIds: number[] = [];
|
const blacklistedIds: number[] = [];
|
||||||
|
|
||||||
const channel = await message.channel.fetch() as GuildTextBasedChannel;
|
const channel = await message.channel.fetch() as GuildTextBasedChannel;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Message } from '../events';
|
|
||||||
import { Encrypter } from '../shared/Encrypter';
|
import { Encrypter } from '../shared/Encrypter';
|
||||||
import { LoggedMessage } from '../types';
|
import { LoggedMessage } from '../types';
|
||||||
|
import { Message } from '../utils';
|
||||||
|
|
||||||
export const ARRAY_SEPARATOR = '$';
|
export const ARRAY_SEPARATOR = '$';
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user