**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 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
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 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,
|
||||
|
|
|
@ -14,3 +14,4 @@ export { client }
|
|||
|
||||
// Load modules
|
||||
import('./bot/modules/command_handler');
|
||||
import('./bot/modules/mod_logs');
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue