**H**
This commit is contained in:
parent
10e0f37a50
commit
ca132071d9
5 changed files with 182 additions and 10 deletions
|
@ -3,7 +3,7 @@ import { Message } from "revolt.js/dist/maps/Messages";
|
||||||
import { client } from "../..";
|
import { client } from "../..";
|
||||||
import Infraction from "../../struct/antispam/Infraction";
|
import Infraction from "../../struct/antispam/Infraction";
|
||||||
import InfractionType from "../../struct/antispam/InfractionType";
|
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 Day from 'dayjs';
|
||||||
import RelativeTime from 'dayjs/plugin/relativeTime';
|
import RelativeTime from 'dayjs/plugin/relativeTime';
|
||||||
import Xlsx from 'xlsx';
|
import Xlsx from 'xlsx';
|
||||||
|
@ -108,17 +108,9 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
let sheet = Xlsx.utils.aoa_to_sheet(csv_data);
|
let sheet = Xlsx.utils.aoa_to_sheet(csv_data);
|
||||||
|
|
||||||
let csv = Xlsx.utils.sheet_to_csv(sheet);
|
let csv = Xlsx.utils.sheet_to_csv(sheet);
|
||||||
|
|
||||||
let apiConfig: any = (await axios.get(client.apiURL)).data;
|
message.reply({ content: msg, attachments: [ await uploadFile(csv, `${user._id}.csv`) ] });
|
||||||
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 ] });
|
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
message.reply(msg);
|
message.reply(msg);
|
||||||
|
|
113
src/bot/modules/mod_logs.ts
Normal file
113
src/bot/modules/mod_logs.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
|
@ -3,6 +3,8 @@ import { User } from "revolt.js/dist/maps/Users";
|
||||||
import { client } from "..";
|
import { client } from "..";
|
||||||
import Infraction from "../struct/antispam/Infraction";
|
import Infraction from "../struct/antispam/Infraction";
|
||||||
import ServerConfig from "../struct/ServerConfig";
|
import ServerConfig from "../struct/ServerConfig";
|
||||||
|
import FormData from 'form-data';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
let ServerPermissions = {
|
let ServerPermissions = {
|
||||||
['View' as string]: 1 << 0,
|
['View' as string]: 1 << 0,
|
||||||
|
@ -20,6 +22,14 @@ let ServerPermissions = {
|
||||||
const NO_MANAGER_MSG = '🔒 Missing permission';
|
const NO_MANAGER_MSG = '🔒 Missing permission';
|
||||||
const USER_MENTION_REGEX = /^<@[0-9A-HJ-KM-NP-TV-Z]{26}>$/i;
|
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;
|
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.
|
* 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 }
|
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 {
|
export {
|
||||||
|
getAutumnURL,
|
||||||
hasPerm,
|
hasPerm,
|
||||||
isModerator,
|
isModerator,
|
||||||
isBotManager,
|
isBotManager,
|
||||||
parseUser,
|
parseUser,
|
||||||
storeInfraction,
|
storeInfraction,
|
||||||
|
uploadFile,
|
||||||
|
sanitizeMessageContent,
|
||||||
NO_MANAGER_MSG,
|
NO_MANAGER_MSG,
|
||||||
USER_MENTION_REGEX,
|
USER_MENTION_REGEX,
|
||||||
CHANNEL_MENTION_REGEX,
|
CHANNEL_MENTION_REGEX,
|
||||||
|
|
|
@ -14,3 +14,4 @@ export { client }
|
||||||
|
|
||||||
// Load modules
|
// Load modules
|
||||||
import('./bot/modules/command_handler');
|
import('./bot/modules/command_handler');
|
||||||
|
import('./bot/modules/mod_logs');
|
||||||
|
|
|
@ -12,6 +12,13 @@ class ServerConfig {
|
||||||
roles: string[] | undefined,
|
roles: string[] | undefined,
|
||||||
managers: boolean | undefined,
|
managers: boolean | undefined,
|
||||||
} | 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;
|
export default ServerConfig;
|
||||||
|
|
Loading…
Reference in a new issue