This commit is contained in:
JandereDev 2021-10-14 13:25:13 +02:00
parent 10e0f37a50
commit ca132071d9
Signed by: Lea
GPG key ID: 5D5E18ACB990F57A
5 changed files with 182 additions and 10 deletions

View file

@ -3,7 +3,7 @@ import { Message } from "revolt.js/dist/maps/Messages";
import { client } from "../..";
import Infraction from "../../struct/antispam/Infraction";
import InfractionType from "../../struct/antispam/InfractionType";
import { isModerator, NO_MANAGER_MSG, parseUser } from "../util";
import { isModerator, NO_MANAGER_MSG, parseUser, uploadFile } from "../util";
import Day from 'dayjs';
import RelativeTime from 'dayjs/plugin/relativeTime';
import Xlsx from 'xlsx';
@ -108,17 +108,9 @@ export default {
}
let sheet = Xlsx.utils.aoa_to_sheet(csv_data);
let csv = Xlsx.utils.sheet_to_csv(sheet);
let apiConfig: any = (await axios.get(client.apiURL)).data;
let autumnURL = apiConfig.features.autumn.url;
let data = new FormData();
data.append("file", csv, { filename: `${user._id}.csv` });
let req = await axios.post(autumnURL + '/attachments', data, { headers: data.getHeaders() });
message.reply({ content: msg, attachments: [ (req.data as any)['id'] as string ] });
message.reply({ content: msg, attachments: [ await uploadFile(csv, `${user._id}.csv`) ] });
} catch(e) {
console.error(e);
message.reply(msg);

113
src/bot/modules/mod_logs.ts Normal file
View file

@ -0,0 +1,113 @@
import { client } from "../..";
import ServerConfig from "../../struct/ServerConfig";
import logger from "../logger";
import { getAutumnURL, sanitizeMessageContent, uploadFile } from "../util";
// the `packet` event is emitted before the client's cache
// is updated, which allows us to get the old message content
// if it was cached before
client.on('packet', async (packet) => {
if (packet.type == 'MessageUpdate') {
try {
if (!packet.data.content) return;
logger.debug('Message updated');
let m = client.messages.get(packet.id);
if (m?.author_id == client.user?._id) return;
let oldMsgRaw = String(m?.content ?? '(Unknown)');
let newMsgRaw = String(packet.data.content);
let oldMsg = sanitizeMessageContent(oldMsgRaw) || '(Empty)';
let newMsg = sanitizeMessageContent(newMsgRaw) || '(Empty)';
let channel = client.channels.get(packet.channel);
let server = channel?.server;
if (!server || !channel) return logger.warn('Received message update in unknown channel or server');
let config: ServerConfig = await client.db.get('servers').findOne({ id: server._id }) ?? {};
if (!config?.logs?.messageUpdate) return;
let logChannelID = config.logs.messageUpdate;
let logChannel = client.channels.get(logChannelID);
if (!logChannel) return logger.debug('Log channel deleted or not cached: ' + logChannelID);
let attachFullMessage = oldMsg.length > 800 || newMsg.length > 800;
if (attachFullMessage) {
logChannel.sendMessage({
content: `### Message edited in ${server.name}\n`
+ `[\\[Jump to message\\]](/server/${server._id}/channel/${channel._id}/${packet.id})\n`,
attachments: await Promise.all([
uploadFile(oldMsgRaw, 'Old message'),
uploadFile(newMsgRaw, 'New message'),
]),
});
} else {
let logMsg = `### Message edited in ${server.name}\n`
+ `[\\[Jump to message\\]](/server/${server._id}/channel/${channel._id}/${packet.id}) | `
+ `[\\[Author\\]](/@${m?.author_id})\n`;
logMsg += `#### Old Content\n${oldMsg}\n`;
logMsg += `#### New Content\n${newMsg}`;
logChannel.sendMessage(logMsg)
.catch(() => logger.warn(`Failed to send log message`));
}
} catch(e) {
console.error(e);
}
}
if (packet.type == 'MessageDelete') {
try {
let channel = client.channels.get(packet.channel);
if (!channel) return;
let message = client.messages.get(packet.id);
if (!message) return;
let msgRaw = String(message.content ?? '(Unknown)');
let msg = sanitizeMessageContent(msgRaw);
let config: ServerConfig = await client.db.get('servers').findOne({ id: message.channel?.server?._id }) ?? {};
if (!config?.logs?.messageUpdate) return;
let logChannelID = config.logs.messageUpdate;
let logChannel = client.channels.get(logChannelID);
if (!logChannel) return logger.debug('Log channel deleted or not cached: ' + logChannelID);
if (msg.length > 1000) {
let logMsg = `### Message deleted in ${message.channel?.server?.name}\n`;
if (message.attachments?.length) {
let autumnURL = await getAutumnURL();
logMsg += `\n\u200b\n#### Attachments\n` + message.attachments.map(a =>
`[\\[${a.filename}\\]](<${autumnURL}/${a.tag}/${a._id}/${a.filename}>)`).join(' | ');
}
logChannel.sendMessage({
content: logMsg,
attachments: [ await uploadFile(msgRaw, 'Message content') ],
})
.catch(() => logger.warn(`Failed to send log message`));
} else {
let logMsg = `### Message deleted in ${channel.server?.name}\n`
+ `[\\[Jump to channel\\]](/server/${channel.server?._id}/channel/${channel._id}) | `
+ `[\\[Author\\]](/@${message.author_id})\n`
+ `#### Message content\n`
+ msg;
if (message.attachments?.length) {
let autumnURL = await getAutumnURL();
logMsg += `\n\u200b\n#### Attachments\n` + message.attachments.map(a =>
`[\\[${a.filename}\\]](<${autumnURL}/${a.tag}/${a._id}/${a.filename}>)`).join(' | ');
}
logChannel.sendMessage(logMsg)
.catch(() => logger.warn(`Failed to send log message`));
}
} catch(e) {
console.error(e);
}
}
});

View file

@ -3,6 +3,8 @@ import { User } from "revolt.js/dist/maps/Users";
import { client } from "..";
import Infraction from "../struct/antispam/Infraction";
import ServerConfig from "../struct/ServerConfig";
import FormData from 'form-data';
import axios from 'axios';
let ServerPermissions = {
['View' as string]: 1 << 0,
@ -20,6 +22,14 @@ let ServerPermissions = {
const NO_MANAGER_MSG = '🔒 Missing permission';
const USER_MENTION_REGEX = /^<@[0-9A-HJ-KM-NP-TV-Z]{26}>$/i;
const CHANNEL_MENTION_REGEX = /^<#[0-9A-HJ-KM-NP-TV-Z]{26}>$/i;
let autumn_url: string|null = null;
let apiConfig: any = axios.get(client.apiURL).then(res => {
autumn_url = (res.data as any).features.autumn.url;
});
async function getAutumnURL() {
return autumn_url || ((await axios.get(client.apiURL)).data as any).features.autumn.url;
}
/**
* Parses user input and returns an user object.
@ -96,12 +106,61 @@ async function storeInfraction(infraction: Infraction): Promise<{ userWarnCount:
return { userWarnCount: (r[1].length ?? 0) + 1 }
}
async function uploadFile(file: any, filename: string): Promise<string> {
let data = new FormData();
data.append("file", file, { filename: filename });
let req = await axios.post(await getAutumnURL() + '/attachments', data, { headers: data.getHeaders() });
return (req.data as any)['id'] as string;
}
/**
* Attempts to escape a message's markdown content (qoutes, headers, **bold** / *italic*, etc)
*/
function sanitizeMessageContent(msg: string): string {
let str = '';
for (let line of msg.split('\n')) {
line = line.trim();
if (line.startsWith('#') || // headers
line.startsWith('>') || // quotes
line.startsWith('|') || // tables
line.startsWith('*') || // unordered lists
line.startsWith('-') || // ^
line.startsWith('+') // ^
) {
line = `\\${line}`;
}
// Ordered lists can't be escaped using `\`,
// so we just put an invisible character \u200b
if (/^[0-9]+[)\.].*/gi.test(line)) {
line = `\u200b${line}`;
}
for (const char of ['_', '!!', '~', '`', '*', '^', '$']) {
line = line.replace(new RegExp(`(?<!\\\\)\\${char}`, 'g'), `\\${char}`);
}
// Mentions
line = line.replace(/<@/g, `<\\@`);
str += line + '\n';
}
return str;
}
export {
getAutumnURL,
hasPerm,
isModerator,
isBotManager,
parseUser,
storeInfraction,
uploadFile,
sanitizeMessageContent,
NO_MANAGER_MSG,
USER_MENTION_REGEX,
CHANNEL_MENTION_REGEX,

View file

@ -14,3 +14,4 @@ export { client }
// Load modules
import('./bot/modules/command_handler');
import('./bot/modules/mod_logs');

View file

@ -12,6 +12,13 @@ class ServerConfig {
roles: string[] | undefined,
managers: boolean | undefined,
} | undefined;
logs: {
infractions: string | undefined, // User warned
automod: string | undefined, // automod rule triggered
messageUpdate: string | undefined, // Message edited or deleted
modAction: string | undefined, // User kicked, banned, or roles updated
userUpdate: string | undefined, // Username/nickname/avatar changes
} | undefined;
}
export default ServerConfig;