more wip work yippeee
This commit is contained in:
parent
72b835f475
commit
ad48cd516e
|
@ -16,6 +16,7 @@
|
||||||
"@discordjs/rest": "^2.2.0",
|
"@discordjs/rest": "^2.2.0",
|
||||||
"chalk": "^4.1.2",
|
"chalk": "^4.1.2",
|
||||||
"d3-array": "^2.12.1",
|
"d3-array": "^2.12.1",
|
||||||
|
"diff": "^5.2.0",
|
||||||
"discord.js": "^14.14.1",
|
"discord.js": "^14.14.1",
|
||||||
"express": "^4.18.3",
|
"express": "^4.18.3",
|
||||||
"express-handlebars": "^7.1.2",
|
"express-handlebars": "^7.1.2",
|
||||||
|
@ -31,6 +32,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/d3-array": "^3.2.1",
|
"@types/d3-array": "^3.2.1",
|
||||||
|
"@types/diff": "^5.0.9",
|
||||||
"@types/express": "^4.17.21",
|
"@types/express": "^4.17.21",
|
||||||
"@types/parse-color": "^1.0.3",
|
"@types/parse-color": "^1.0.3",
|
||||||
"@types/tough-cookie": "^4.0.5",
|
"@types/tough-cookie": "^4.0.5",
|
||||||
|
|
|
@ -17,6 +17,9 @@ dependencies:
|
||||||
d3-array:
|
d3-array:
|
||||||
specifier: ^2.12.1
|
specifier: ^2.12.1
|
||||||
version: 2.12.1
|
version: 2.12.1
|
||||||
|
diff:
|
||||||
|
specifier: ^5.2.0
|
||||||
|
version: 5.2.0
|
||||||
discord.js:
|
discord.js:
|
||||||
specifier: ^14.14.1
|
specifier: ^14.14.1
|
||||||
version: 14.14.1
|
version: 14.14.1
|
||||||
|
@ -58,6 +61,9 @@ devDependencies:
|
||||||
'@types/d3-array':
|
'@types/d3-array':
|
||||||
specifier: ^3.2.1
|
specifier: ^3.2.1
|
||||||
version: 3.2.1
|
version: 3.2.1
|
||||||
|
'@types/diff':
|
||||||
|
specifier: ^5.0.9
|
||||||
|
version: 5.0.9
|
||||||
'@types/express':
|
'@types/express':
|
||||||
specifier: ^4.17.21
|
specifier: ^4.17.21
|
||||||
version: 4.17.21
|
version: 4.17.21
|
||||||
|
@ -372,6 +378,10 @@ packages:
|
||||||
resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==}
|
resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@types/diff@5.0.9:
|
||||||
|
resolution: {integrity: sha512-RWVEhh/zGXpAVF/ZChwNnv7r4rvqzJ7lYNSmZSVTxjV0PBLf6Qu7RNg+SUtkpzxmiNkjCx0Xn2tPp7FIkshJwQ==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@types/express-serve-static-core@4.17.43:
|
/@types/express-serve-static-core@4.17.43:
|
||||||
resolution: {integrity: sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==}
|
resolution: {integrity: sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -1043,6 +1053,11 @@ packages:
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/diff@5.2.0:
|
||||||
|
resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==}
|
||||||
|
engines: {node: '>=0.3.1'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/dir-glob@3.0.1:
|
/dir-glob@3.0.1:
|
||||||
resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
|
resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
|
@ -0,0 +1,123 @@
|
||||||
|
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, CommandInteraction, ComponentType, SlashCommandBuilder, StringSelectMenuBuilder } from 'discord.js';
|
||||||
|
import { Command } from '../types/index';
|
||||||
|
import { AuditLog, db } from '../lib/db';
|
||||||
|
import { EventType } from '../lib/events';
|
||||||
|
import { chunks } from '../lib/util';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('auditlog')
|
||||||
|
.setDescription('[ADMIN] Set up an audit logger, or edit an existing one')
|
||||||
|
.setDefaultMemberPermissions('0'),
|
||||||
|
|
||||||
|
execute: async (interaction: CommandInteraction) => {
|
||||||
|
if (!interaction.isChatInputCommand()) return;
|
||||||
|
|
||||||
|
const channel = interaction.channelId;
|
||||||
|
|
||||||
|
if (!channel) return;
|
||||||
|
|
||||||
|
const log = await db<AuditLog>('auditLogs')
|
||||||
|
.select('eventTypes')
|
||||||
|
.where('channel', channel)
|
||||||
|
.first();
|
||||||
|
|
||||||
|
let types = log ? log.eventTypes.split(',') : [];
|
||||||
|
|
||||||
|
const options = Object.values(EventType)
|
||||||
|
.map(event => ({
|
||||||
|
label: event,
|
||||||
|
value: event,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const chunked = [...chunks(options, 25)];
|
||||||
|
|
||||||
|
const selectRows =
|
||||||
|
chunked.map(
|
||||||
|
(opt, i) => new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(
|
||||||
|
new StringSelectMenuBuilder()
|
||||||
|
.addOptions(
|
||||||
|
...opt
|
||||||
|
)
|
||||||
|
.setMinValues(0)
|
||||||
|
.setMaxValues(opt.length)
|
||||||
|
.setCustomId(`auditlog-select-events-${i}`)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const components = [
|
||||||
|
...selectRows,
|
||||||
|
new ActionRowBuilder<ButtonBuilder>().addComponents(
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId('auditlog-select-events-done')
|
||||||
|
.setLabel('Done')
|
||||||
|
.setStyle(ButtonStyle.Primary)
|
||||||
|
.setDisabled(true)
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
const formatContent = () =>
|
||||||
|
`${log ? `Editing audit log in <#${channel}>` : `Creating audit log in <#${channel}>`}\n` +
|
||||||
|
'Select the types of events to be reported on' + ((types.length > 0) ? `\n**Current events**: ${types.map(s => '`' + s + '`').join(', ')}` : '');
|
||||||
|
|
||||||
|
const msg = await interaction.reply({
|
||||||
|
ephemeral: true,
|
||||||
|
content: formatContent(),
|
||||||
|
components,
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectCollector = msg.createMessageComponentCollector({
|
||||||
|
componentType: ComponentType.StringSelect,
|
||||||
|
time: 60_000 * 5,
|
||||||
|
});
|
||||||
|
|
||||||
|
selectCollector.on('collect', async selectInteraction => {
|
||||||
|
const possibleTypes = selectInteraction.component.options.map(opt => opt.value);
|
||||||
|
const selectedTypes = selectInteraction.values;
|
||||||
|
types = types.filter(t => !possibleTypes.includes(t));
|
||||||
|
types = [...types, ...selectedTypes];
|
||||||
|
|
||||||
|
components[components.length - 1].components[0].setDisabled(types.length === 0);
|
||||||
|
|
||||||
|
await selectInteraction.reply({
|
||||||
|
content: 'Hit "Done" when finished',
|
||||||
|
ephemeral: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
await msg.edit({
|
||||||
|
content: formatContent(),
|
||||||
|
components,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
selectCollector.on('end', async () => {
|
||||||
|
await msg.edit({
|
||||||
|
content: formatContent(),
|
||||||
|
components: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const buttonInteraction = await msg.awaitMessageComponent({ componentType: ComponentType.Button, time: 60_000 * 5 });
|
||||||
|
selectCollector.stop();
|
||||||
|
|
||||||
|
if (log) {
|
||||||
|
await db<AuditLog>('auditLogs')
|
||||||
|
.where('channel', channel)
|
||||||
|
.update({
|
||||||
|
eventTypes: types.join(','),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await db<AuditLog>('auditLogs')
|
||||||
|
.insert({
|
||||||
|
guild: interaction.guildId!,
|
||||||
|
channel: channel,
|
||||||
|
eventTypes: types.join(','),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await buttonInteraction.reply({
|
||||||
|
content: 'Audit log successfully created.',
|
||||||
|
components: [],
|
||||||
|
ephemeral: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} satisfies Command;
|
|
@ -1,12 +1,10 @@
|
||||||
import { Sticker, ThreadChannel, Message, User, Invite, GuildMember, GuildScheduledEvent, GuildEmoji, Channel, Client, Events, GuildAuditLogsEntry, AuditLogEvent } from 'discord.js';
|
import { Sticker, ThreadChannel, Message, User, Invite, GuildMember, GuildScheduledEvent, GuildEmoji, Client, Events, AuditLogEvent, ChannelType, PartialMessage, EmbedBuilder, MessageType } from 'discord.js';
|
||||||
import { AuditLog, db } from './db';
|
import { AuditLog, db } from './db';
|
||||||
import * as log from './log';
|
import * as log from './log';
|
||||||
|
import { formatMessageAsEmbed, getUploadLimitForGuild, shortenStr } from './util';
|
||||||
|
import { Change, diffWords } from 'diff';
|
||||||
|
|
||||||
export enum EventType {
|
export enum EventType {
|
||||||
ChannelCreate = 'CHANNEL_CREATE',
|
|
||||||
ChannelRename = 'CHANNEL_RENAME',
|
|
||||||
ChannelDelete = 'CHANNEL_DELETE',
|
|
||||||
|
|
||||||
EmojiCreate = 'EMOJI_CREATE',
|
EmojiCreate = 'EMOJI_CREATE',
|
||||||
EmojiRename = 'EMOJI_RENAME',
|
EmojiRename = 'EMOJI_RENAME',
|
||||||
EmojiDelete = 'EMOJI_DELETE',
|
EmojiDelete = 'EMOJI_DELETE',
|
||||||
|
@ -16,7 +14,6 @@ export enum EventType {
|
||||||
EventDelete = 'EVENT_DELETE',
|
EventDelete = 'EVENT_DELETE',
|
||||||
|
|
||||||
InviteCreate = 'INVITE_CREATE',
|
InviteCreate = 'INVITE_CREATE',
|
||||||
InviteUpdate = 'INVITE_UPDATE',
|
|
||||||
InviteDelete = 'INVITE_DELETE',
|
InviteDelete = 'INVITE_DELETE',
|
||||||
|
|
||||||
MemberBan = 'MEMBER_BAN',
|
MemberBan = 'MEMBER_BAN',
|
||||||
|
@ -24,7 +21,8 @@ export enum EventType {
|
||||||
MemberKick = 'MEMBER_KICK',
|
MemberKick = 'MEMBER_KICK',
|
||||||
MemberDisconnect = 'MEMBER_DISCONNECT',
|
MemberDisconnect = 'MEMBER_DISCONNECT',
|
||||||
MemberNickname = 'MEMBER_NICKNAME',
|
MemberNickname = 'MEMBER_NICKNAME',
|
||||||
MemberChangeRoles = 'MEMBER_CHANGE_ROLES',
|
MemberJoin = 'MEMBER_JOIN',
|
||||||
|
MemberLeave = 'MEMBER_LEAVE',
|
||||||
|
|
||||||
MessageDelete = 'MESSAGE_DELETE',
|
MessageDelete = 'MESSAGE_DELETE',
|
||||||
MessageEdit = 'MESSAGE_EDIT',
|
MessageEdit = 'MESSAGE_EDIT',
|
||||||
|
@ -39,129 +37,107 @@ export enum EventType {
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Event = {
|
export type Event = {
|
||||||
type: EventType.ChannelCreate,
|
|
||||||
causer: User,
|
|
||||||
channel: Channel,
|
|
||||||
} | {
|
|
||||||
type: EventType.ChannelRename,
|
|
||||||
causer: User,
|
|
||||||
channel: Channel,
|
|
||||||
oldName: string, newName: string,
|
|
||||||
} | {
|
|
||||||
type: EventType.ChannelDelete,
|
|
||||||
causer: User,
|
|
||||||
channel: Channel,
|
|
||||||
} | {
|
|
||||||
type: EventType.EmojiCreate,
|
type: EventType.EmojiCreate,
|
||||||
causer: User,
|
causer: User | null,
|
||||||
emoji: GuildEmoji,
|
emoji: GuildEmoji,
|
||||||
} | {
|
} | {
|
||||||
type: EventType.EmojiRename,
|
type: EventType.EmojiRename,
|
||||||
causer: User,
|
causer: User | null,
|
||||||
emoji: GuildEmoji,
|
emoji: GuildEmoji,
|
||||||
oldName: string, newName: string,
|
oldName: string, newName: string,
|
||||||
} | {
|
} | {
|
||||||
type: EventType.EmojiDelete,
|
type: EventType.EmojiDelete,
|
||||||
causer: User,
|
causer: User | null,
|
||||||
emoji: GuildEmoji,
|
emoji: GuildEmoji,
|
||||||
} | {
|
} | {
|
||||||
type: EventType.EventCreate,
|
type: EventType.EventCreate,
|
||||||
causer: User,
|
causer: User | null,
|
||||||
event: GuildScheduledEvent,
|
event: GuildScheduledEvent,
|
||||||
} | {
|
} | {
|
||||||
type: EventType.EventEdit,
|
type: EventType.EventEdit,
|
||||||
causer: User,
|
causer: User | null,
|
||||||
oldEvent: GuildScheduledEvent,
|
oldEvent: GuildScheduledEvent,
|
||||||
event: GuildScheduledEvent,
|
event: GuildScheduledEvent,
|
||||||
} | {
|
} | {
|
||||||
type: EventType.EventDelete,
|
type: EventType.EventDelete,
|
||||||
causer: User,
|
causer: User | null,
|
||||||
event: GuildScheduledEvent,
|
event: GuildScheduledEvent,
|
||||||
} | {
|
} | {
|
||||||
type: EventType.InviteCreate,
|
type: EventType.InviteCreate,
|
||||||
causer: User,
|
causer: User | null,
|
||||||
invite: Invite,
|
|
||||||
} | {
|
|
||||||
type: EventType.InviteUpdate,
|
|
||||||
causer: User,
|
|
||||||
oldInvite: Invite,
|
|
||||||
invite: Invite,
|
invite: Invite,
|
||||||
} | {
|
} | {
|
||||||
type: EventType.InviteDelete,
|
type: EventType.InviteDelete,
|
||||||
causer: User,
|
causer: User | null,
|
||||||
invite: Invite,
|
invite: Invite,
|
||||||
} | {
|
} | {
|
||||||
type: EventType.MemberBan,
|
type: EventType.MemberBan,
|
||||||
causer: User,
|
causer: User | null,
|
||||||
member: GuildMember,
|
member: GuildMember,
|
||||||
reason: string,
|
reason: string,
|
||||||
} | {
|
} | {
|
||||||
type: EventType.MemberUnban,
|
type: EventType.MemberUnban,
|
||||||
causer: User,
|
causer: User | null,
|
||||||
member: GuildMember,
|
member: GuildMember,
|
||||||
reason: string,
|
reason: string,
|
||||||
} | {
|
} | {
|
||||||
type: EventType.MemberKick,
|
type: EventType.MemberKick,
|
||||||
causer: User,
|
causer: User | null,
|
||||||
member: GuildMember,
|
member: GuildMember,
|
||||||
reason: string,
|
reason: string,
|
||||||
} | {
|
} | {
|
||||||
type: EventType.MemberDisconnect,
|
type: EventType.MemberDisconnect,
|
||||||
causer: User,
|
causer: User | null,
|
||||||
member: GuildMember,
|
member: GuildMember,
|
||||||
} | {
|
} | {
|
||||||
type: EventType.MemberNickname,
|
type: EventType.MemberNickname,
|
||||||
causer: User,
|
causer: User | null,
|
||||||
member: GuildMember,
|
member: GuildMember,
|
||||||
oldNickname: string, newNickname: string,
|
oldNickname: string, newNickname: string,
|
||||||
} | {
|
|
||||||
type: EventType.MemberChangeRoles,
|
|
||||||
causer: User,
|
|
||||||
member: GuildMember,
|
|
||||||
// TODO: huh
|
|
||||||
} | {
|
} | {
|
||||||
type: EventType.MessageDelete,
|
type: EventType.MessageDelete,
|
||||||
causer: User,
|
causer: User | null,
|
||||||
message: Message,
|
message: Message | PartialMessage,
|
||||||
} | {
|
} | {
|
||||||
type: EventType.MessageEdit,
|
type: EventType.MessageEdit,
|
||||||
causer: User,
|
causer: User | null,
|
||||||
oldMessage: Message,
|
oldMessage: Message,
|
||||||
message: Message,
|
message: Message,
|
||||||
} | {
|
} | {
|
||||||
type: EventType.StickerCreate,
|
type: EventType.StickerCreate,
|
||||||
causer: User,
|
causer: User | null,
|
||||||
sticker: Sticker,
|
sticker: Sticker,
|
||||||
} | {
|
} | {
|
||||||
type: EventType.StickerRename,
|
type: EventType.StickerRename,
|
||||||
causer: User,
|
causer: User | null,
|
||||||
oldName: string, newName: string,
|
oldName: string, newName: string,
|
||||||
} | {
|
} | {
|
||||||
type: EventType.StickerDelete,
|
type: EventType.StickerDelete,
|
||||||
causer: User,
|
causer: User | null,
|
||||||
sticker: Sticker,
|
sticker: Sticker,
|
||||||
} | {
|
} | {
|
||||||
type: EventType.ThreadCreate,
|
type: EventType.ThreadCreate,
|
||||||
causer: User,
|
causer: User | null,
|
||||||
thread: ThreadChannel,
|
thread: ThreadChannel,
|
||||||
} | {
|
} | {
|
||||||
type: EventType.ThreadEdit,
|
type: EventType.ThreadEdit,
|
||||||
causer: User,
|
causer: User | null,
|
||||||
oldThread: ThreadChannel,
|
oldThread: ThreadChannel,
|
||||||
thread: ThreadChannel,
|
thread: ThreadChannel,
|
||||||
} | {
|
} | {
|
||||||
type: EventType.ThreadDelete,
|
type: EventType.ThreadDelete,
|
||||||
causer: User,
|
causer: User | null,
|
||||||
thread: ThreadChannel,
|
thread: ThreadChannel,
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function triggerEvent(bot: Client, event: Event) {
|
export async function triggerEvent(bot: Client, guildId: string, event: Event) {
|
||||||
const type = event.type;
|
const type = event.type;
|
||||||
|
|
||||||
log.info(`Got event ${event.type}: ${JSON.stringify(event)}`);
|
log.info(`got event ${event.type}`);
|
||||||
|
|
||||||
const logs = await db<AuditLog>('auditLogs')
|
const logs = await db<AuditLog>('auditLogs')
|
||||||
.select('guild', 'channel')
|
.select('guild', 'channel')
|
||||||
|
.where('guild', guildId)
|
||||||
// @ts-expect-error this LITERALLY works
|
// @ts-expect-error this LITERALLY works
|
||||||
.whereLike(db.raw('UPPER(eventTypes)'), `%${type.toUpperCase()}%`);
|
.whereLike(db.raw('UPPER(eventTypes)'), `%${type.toUpperCase()}%`);
|
||||||
|
|
||||||
|
@ -173,31 +149,144 @@ export async function triggerEvent(bot: Client, event: Event) {
|
||||||
log.warn(err);
|
log.warn(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (channel && channel.isText()) {
|
if (channel && (channel.type === ChannelType.GuildText)) {
|
||||||
channel.send(`${JSON.stringify(event)}`);
|
switch(event.type) {
|
||||||
|
case EventType.MessageDelete: {
|
||||||
|
const limit = getUploadLimitForGuild(channel.guild);
|
||||||
|
const { embed, attach } = formatMessageAsEmbed(event.message, limit);
|
||||||
|
|
||||||
|
const logEmbed = new EmbedBuilder()
|
||||||
|
.setDescription(`Message sent by ${event.message.author} in <#${event.message.channelId}> deleted by **${event.causer}**:`)
|
||||||
|
.setTimestamp();
|
||||||
|
|
||||||
|
channel.send({
|
||||||
|
embeds: [ logEmbed, embed ],
|
||||||
|
files: attach ? [{
|
||||||
|
attachment: attach.url,
|
||||||
|
name: attach.name,
|
||||||
|
description: attach.url,
|
||||||
|
}] : [],
|
||||||
|
});
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EventType.MessageEdit: {
|
||||||
|
const logEmbed = new EmbedBuilder()
|
||||||
|
.setDescription(`Message sent by ${event.message.author} in <#${event.message.channelId}> edited:`)
|
||||||
|
.setTimestamp();
|
||||||
|
|
||||||
|
const diff = diffWords(event.oldMessage.content.replace(/`/g, ''), event.message.content.replace(/`/g, ''));
|
||||||
|
let output = '';
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
diff.forEach((part: Change) => {
|
||||||
|
if (part.added) output += `\x1B[2;32m\x1B[1;32m${part.value}\x1B[0m`;
|
||||||
|
if (part.removed) output += `\x1B[2;41m\x1B[1;2m${part.value}\x1B[0m`;
|
||||||
|
if (!part.added && !part.removed) output += part.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
const diffMsg = `\`\`\`ansi\n${shortenStr(output, 4000)}\`\`\``;
|
||||||
|
|
||||||
|
const editEmbed = new EmbedBuilder()
|
||||||
|
.setAuthor({ name: event.message.author.tag, iconURL: event.message.author.displayAvatarURL() })
|
||||||
|
.setDescription(diffMsg);
|
||||||
|
|
||||||
|
channel.send({
|
||||||
|
embeds: [ logEmbed, editEmbed ],
|
||||||
|
});
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EventType.InviteCreate: {
|
||||||
|
const logEmbed = new EmbedBuilder()
|
||||||
|
.setDescription(`Invite ${event.invite.code} created by ${event.causer} in <#${event.invite.channelId}>`)
|
||||||
|
.setTimestamp();
|
||||||
|
|
||||||
|
channel.send({
|
||||||
|
embeds: [ logEmbed ],
|
||||||
|
});
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EventType.InviteDelete: {
|
||||||
|
const logEmbed = new EmbedBuilder()
|
||||||
|
.setDescription(`Invite ${event.invite.code} made by ${event.invite.inviter} in <#${event.invite.channelId}> deleted by ${event.causer}`)
|
||||||
|
.setTimestamp();
|
||||||
|
|
||||||
|
channel.send({
|
||||||
|
embeds: [ logEmbed ],
|
||||||
|
});
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
log.warn(`Channel ${auditLog.channel} from guild ${auditLog.guild} not found! Deleting audit log`);
|
log.warn(`Channel ${auditLog.channel} from guild ${auditLog.guild} not found! Deleting audit log`);
|
||||||
await db<AuditLog>('auditLogs')
|
await db<AuditLog>('auditLogs')
|
||||||
|
.where('guild', auditLog.guild)
|
||||||
.where('channel', auditLog.channel)
|
.where('channel', auditLog.channel)
|
||||||
.delete();
|
.delete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DELETE_FETCH_TIMEOUT = 2_000;
|
||||||
|
|
||||||
export function setupListeners(bot: Client) {
|
export function setupListeners(bot: Client) {
|
||||||
/*bot.on(Events.GuildAuditLogEntryCreate, async (auditLog: GuildAuditLogsEntry) => {
|
bot.on(Events.MessageDelete, async message => {
|
||||||
const { action, extra: channel, executorId, targetId } = auditLog;
|
if (!message.guild) return;
|
||||||
|
if (!(message.type === MessageType.Default || message.type === MessageType.Reply || message.type === MessageType.ThreadStarterMessage)) return;
|
||||||
|
|
||||||
// Check only for deleted messages.
|
let causer: User | null = message.author;
|
||||||
if (action !== AuditLogEvent.MessageDelete) return;
|
|
||||||
|
|
||||||
// Ensure the executor is cached.
|
const audit = await message.guild.fetchAuditLogs({
|
||||||
const executor = await bot.users.fetch(executorId);
|
type: AuditLogEvent.MessageDelete,
|
||||||
|
limit: 1,
|
||||||
|
});
|
||||||
|
|
||||||
// Ensure the author whose message was deleted is cached.
|
const entry = audit.entries.first();
|
||||||
const target = await bot.users.fetch(targetId);
|
|
||||||
|
|
||||||
// Log the output.
|
if (entry && ((Date.now() - entry.createdTimestamp) < DELETE_FETCH_TIMEOUT || (entry.extra.count > 1)) && entry.targetId === message.author?.id) {
|
||||||
console.log(`A message by ${target.tag} was deleted by ${executor.tag} in ${channel}.`);
|
causer = entry.executor;
|
||||||
});*/
|
}
|
||||||
|
|
||||||
|
triggerEvent(bot, message.guild.id, {
|
||||||
|
type: EventType.MessageDelete,
|
||||||
|
causer, message,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
bot.on(Events.MessageUpdate, async (old, message) => {
|
||||||
|
if (!message.guild) return;
|
||||||
|
if (message.content === old.content) return;
|
||||||
|
if (message.partial || old.partial) return;
|
||||||
|
|
||||||
|
triggerEvent(bot, message.guild.id, {
|
||||||
|
type: EventType.MessageEdit,
|
||||||
|
causer: message.author,
|
||||||
|
oldMessage: old, message: message,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
bot.on(Events.InviteCreate, async invite => {
|
||||||
|
if (!invite.guild || !invite.inviter) return;
|
||||||
|
|
||||||
|
triggerEvent(bot, invite.guild.id, {
|
||||||
|
type: EventType.InviteCreate,
|
||||||
|
causer: invite.inviter, invite,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
bot.on(Events.GuildAuditLogEntryCreate, async (entry, guild) => {
|
||||||
|
if (entry.action === AuditLogEvent.InviteDelete) {
|
||||||
|
if (!entry.target) return;
|
||||||
|
|
||||||
|
triggerEvent(bot, guild.id, {
|
||||||
|
type: EventType.InviteDelete,
|
||||||
|
causer: entry.executor,
|
||||||
|
// @ts-expect-error shut up
|
||||||
|
invite: entry.target
|
||||||
|
});
|
||||||
|
} else if (entry.action === AuditLogEvent.EmojiCreate) {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
import { Interaction, Message, TextBasedChannel, User } from 'discord.js';
|
import { Interaction, Message, TextBasedChannel, User } from 'discord.js';
|
||||||
import * as log from '../lib/log';
|
import * as log from '../lib/log';
|
||||||
|
import { chunks } from './util';
|
||||||
|
|
||||||
export const RANDOM_WORDS = [
|
export const RANDOM_WORDS = [
|
||||||
'tarsorado', 'aboba', 'radiation', 'extreme', 'glogging', 'glogged', 'penis', 'easy', 'glue', 'contaminated water',
|
'tarsorado', 'aboba', 'radiation', 'extreme', 'glogging', 'glogged', 'penis', 'easy', 'glue', 'contaminated water',
|
||||||
|
@ -140,12 +141,6 @@ export async function getTextResponsePrettyPlease(user: User, prompt: string, fi
|
||||||
return randomWord();
|
return randomWord();
|
||||||
}
|
}
|
||||||
|
|
||||||
function* chunks(arr: unknown[], n: number) {
|
|
||||||
for (let i = 0; i < arr.length; i += n) {
|
|
||||||
yield arr.slice(i, i + n);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function sendSegments(segments: string[], channel: TextBasedChannel) {
|
export async function sendSegments(segments: string[], channel: TextBasedChannel) {
|
||||||
const content = [];
|
const content = [];
|
||||||
let contentBuffer = '';
|
let contentBuffer = '';
|
||||||
|
|
|
@ -2,6 +2,7 @@ import * as fsp from 'fs/promises';
|
||||||
import { tmpdir } from 'os';
|
import { tmpdir } from 'os';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { randomBytes } from 'crypto';
|
import { randomBytes } from 'crypto';
|
||||||
|
import { Attachment, EmbedBuilder, Guild, GuildPremiumTier, Message, PartialMessage } from 'discord.js';
|
||||||
|
|
||||||
export async function exists(file: string) {
|
export async function exists(file: string) {
|
||||||
try {
|
try {
|
||||||
|
@ -41,3 +42,91 @@ export class Right<R> {
|
||||||
constructor(private readonly value: R) {}
|
constructor(private readonly value: R) {}
|
||||||
public getValue() { return this.value; }
|
public getValue() { return this.value; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function* chunks<T>(arr: T[], n: number) {
|
||||||
|
for (let i = 0; i < arr.length; i += n) {
|
||||||
|
yield arr.slice(i, i + n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function shortenStr(str: string, chars: number) {
|
||||||
|
if (str.length > chars)
|
||||||
|
return str.slice(0, chars - 1) + '…';
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MAX_MESSAGE_SIZE = 2000;
|
||||||
|
|
||||||
|
export const defaultUploadLimit = 25 * 1024 * 1024;
|
||||||
|
|
||||||
|
export const getUploadLimitForGuild = (guild: Guild) => {
|
||||||
|
switch (guild.premiumTier) {
|
||||||
|
case GuildPremiumTier.Tier3: return 100 * 1024 * 1024;
|
||||||
|
case GuildPremiumTier.Tier2: return 50 * 1024 * 1024;
|
||||||
|
default: return defaultUploadLimit;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export function formatMessageAsEmbed(message: Message | PartialMessage, uploadLimit: number): { embed: EmbedBuilder, attach: Attachment | null } {
|
||||||
|
const embed = new EmbedBuilder()
|
||||||
|
.setTimestamp(message.createdTimestamp);
|
||||||
|
|
||||||
|
if (message.author) {
|
||||||
|
embed.setAuthor({
|
||||||
|
name: message.author.tag,
|
||||||
|
iconURL: message.author.displayAvatarURL(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let attach = null;
|
||||||
|
|
||||||
|
message.embeds.forEach(em => {
|
||||||
|
if (em.image) {
|
||||||
|
embed.setImage(em.image.url);
|
||||||
|
} else if (em.thumbnail) {
|
||||||
|
embed.setImage(em.thumbnail.url);
|
||||||
|
}
|
||||||
|
if (em.provider) {
|
||||||
|
embed.addFields({
|
||||||
|
name: shortenStr(`${em.provider.name}:`, 256),
|
||||||
|
value: shortenStr(`${em.author}: **${em.title}**\n${em.description || ''}`, 2048),
|
||||||
|
});
|
||||||
|
} else if (!em.image) {
|
||||||
|
embed.addFields({
|
||||||
|
name: 'Embed:',
|
||||||
|
value: shortenStr(em.description || em.fields[0]?.value || em.title || 'Unknown content', 2048),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let msgEnd = null;
|
||||||
|
|
||||||
|
const first = message.attachments.first();
|
||||||
|
|
||||||
|
if (
|
||||||
|
message.attachments.size === 1 && first && first.contentType
|
||||||
|
&& !first.spoiler
|
||||||
|
&& (first.contentType?.startsWith('image/')
|
||||||
|
|| first.contentType?.startsWith('video/'))
|
||||||
|
&& (first.size <= uploadLimit)
|
||||||
|
) {
|
||||||
|
attach = first;
|
||||||
|
} else {
|
||||||
|
for (const attach of message.attachments.values()) {
|
||||||
|
msgEnd = (msgEnd || '\n') + `\n[${attach.name}](${attach.url})`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.content) {
|
||||||
|
if (!msgEnd) {
|
||||||
|
embed.setDescription(shortenStr(message.content, 4096));
|
||||||
|
} else {
|
||||||
|
embed.setDescription(shortenStr(message.content, 4096 - msgEnd.length) + msgEnd);
|
||||||
|
}
|
||||||
|
} else if (msgEnd) {
|
||||||
|
embed.setDescription('_Content unknown_' + msgEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { embed, attach };
|
||||||
|
}
|
Loading…
Reference in New Issue