3
0
Fork 0
mirror of https://github.com/ZeppelinBot/Zeppelin.git synced 2025-03-14 21:31:50 +00:00

feat: save deleted spam logs; server spam logs from a web server; update Knub to 9.6.4

This commit is contained in:
Dragory 2018-08-01 20:09:51 +03:00
parent 847ee11195
commit 16be52a5e7
10 changed files with 167 additions and 10 deletions

View file

@ -0,0 +1,15 @@
exports.up = async function(knex, Promise) {
if (! await knex.schema.hasTable('spam_logs')) {
await knex.schema.createTable('spam_logs', table => {
table.string('id', 36).notNullable().primary();
table.string('guild_id', 20).notNullable();
table.text('body', 'mediumtext').notNullable();
table.dateTime('created_at').defaultTo(knex.raw('NOW()')).notNullable();
table.dateTime('expires_at').nullable();
});
}
};
exports.down = async function(knex, Promise) {
await knex.schema.dropTableIfExists('spam_logs');
};

6
package-lock.json generated
View file

@ -2181,9 +2181,9 @@
}
},
"knub": {
"version": "9.6.2",
"resolved": "https://registry.npmjs.org/knub/-/knub-9.6.2.tgz",
"integrity": "sha512-4Hz6xTrY8srq+tT5h1uxWIGZrb0iIvRkCP2l5OKINKfFHzm0Xn6IvkWYW6DUTQg4m37KgySGdWDpPhLSZUqVmg==",
"version": "9.6.4",
"resolved": "https://registry.npmjs.org/knub/-/knub-9.6.4.tgz",
"integrity": "sha512-srwdWu/XPciBQQP3phuavJgQd4XDixccSgjl/vcvQu2kjWgH5Cgnprkfl5JuzhMum/7pVnch4YLEfZDrFzjxAw==",
"requires": {
"escape-string-regexp": "^1.0.5",
"js-yaml": "^3.9.1",

View file

@ -33,7 +33,7 @@
"escape-string-regexp": "^1.0.5",
"humanize-duration": "^3.15.0",
"knex": "^0.14.6",
"knub": "^9.6.2",
"knub": "^9.6.4",
"lodash.at": "^4.6.0",
"lodash.difference": "^4.5.0",
"lodash.intersection": "^4.4.0",
@ -42,7 +42,8 @@
"moment-timezone": "^0.5.21",
"tlds": "^1.203.1",
"ts-node": "^3.3.0",
"typescript": "^2.9.2"
"typescript": "^2.9.2",
"uuid": "^3.3.2"
},
"devDependencies": {
"nodemon": "^1.17.5",

View file

@ -35,7 +35,7 @@
"COMMAND": "🤖 **{member.user.username}#{member.user.discriminator}** (`{member.id}`) used command in **#{channel.name}**:\n`{command}`",
"SPAM_DELETE": "🛑 **{member.user.username}#{member.user.discriminator}** (`{member.id}`) spam deleted in **#{channel.name}**: {description} (more than {limit} in {interval}s)",
"SPAM_DELETE": "🛑 **{member.user.username}#{member.user.discriminator}** (`{member.id}`) spam deleted in **#{channel.name}**: {description} (more than {limit} in {interval}s) {logUrl}",
"CENSOR": "🛑 **{member.user.username}#{member.user.discriminator}** (`{member.id}`) censored message in **#{channel.name}** (`{channel.id}`) {reason}:\n```{messageText}```",
"CLEAN": "🚿 **{mod.username}#{mod.discriminator}** (`{mod.id}`) cleaned **{count}** message(s) in **#{channel.name}**",

72
src/data/GuildSpamLogs.ts Normal file
View file

@ -0,0 +1,72 @@
import uuid from "uuid/v4"; // tslint:disable-line
import moment from "moment-timezone";
import knex from "../knex";
import SpamLog from "../models/SpamLog";
import { Message } from "eris";
import { formatTemplateString, stripObjectToScalars, trimLines } from "../utils";
const EXPIRY_DAYS = 7;
const MESSAGE_FORMAT =
"[{timestamp}] [{message.id}] {user.username}#{user.discriminator}: {message.content}{attachments}";
function cleanExpiredLogs() {
knex("spam_logs")
.where("expires_at", "<=", knex.raw("NOW()"))
.delete();
}
cleanExpiredLogs();
setInterval(cleanExpiredLogs, 1000 * 60 * 60); // Clean expired logs every hour
export class GuildSpamLogs {
protected guildId: string;
constructor(guildId) {
this.guildId = guildId;
}
async find(id: string): Promise<SpamLog> {
const result = await knex("spam_logs")
.where("id", id)
.first();
return result ? new SpamLog(result) : null;
}
/**
* @return ID of the created spam log entry
*/
async createFromMessages(messages: Message[], header: string = null) {
const lines = messages.map(msg => {
return formatTemplateString(MESSAGE_FORMAT, {
user: stripObjectToScalars(msg.author),
message: stripObjectToScalars(msg),
timestamp: moment(msg.timestamp).format("YYYY-MM-DD HH:mm:ss zz"),
attachments: msg.attachments.length
? ` (message contained ${msg.attachments.length} attachment(s))`
: ""
});
});
const id = uuid();
const now = moment().format("YYYY-MM-DD HH:mm:ss zz");
const expiresAt = moment().add(EXPIRY_DAYS, "days");
const body = trimLines(`
Log file generated on ${now}. Expires at ${expiresAt.format("YYYY-MM-DD HH:mm:ss zz")}.${
header ? "\n" + header : ""
}
${lines.join("\n")}
`);
await knex("spam_logs").insert({
id,
guild_id: this.guildId,
body,
expires_at: expiresAt.format("YYYY-MM-DD HH:mm:ss")
});
return id;
}
}

View file

@ -22,6 +22,7 @@ import { ReactionRolesPlugin } from "./plugins/ReactionRoles";
import { CensorPlugin } from "./plugins/Censor";
import { PersistPlugin } from "./plugins/Persist";
import { SpamPlugin } from "./plugins/Spam";
import { LogServerPlugin } from "./plugins/LogServer";
import knex from "./knex";
// Run latest database migrations
@ -44,7 +45,8 @@ knex.migrate.latest().then(() => {
spam: SpamPlugin
},
globalPlugins: {
bot_control: BotControlPlugin
bot_control: BotControlPlugin,
log_server: LogServerPlugin
},
options: {

9
src/models/SpamLog.ts Normal file
View file

@ -0,0 +1,9 @@
import Model from "./Model";
export default class SpamLog extends Model {
public id: string;
public guild_id: string;
public body: string;
public created_at: string;
public expires_at: string;
}

44
src/plugins/LogServer.ts Normal file
View file

@ -0,0 +1,44 @@
import http, { ServerResponse } from "http";
import { GlobalPlugin } from "knub";
import { GuildSpamLogs } from "../data/GuildSpamLogs";
const DEFAULT_PORT = 9920;
const logUrlRegex = /^\/spam-logs\/([a-z0-9\-]+)\/?$/i;
function notFound(res: ServerResponse) {
res.statusCode = 404;
res.end("Not Found");
}
/**
* A global plugin that allows bot owners to control the bot
*/
export class LogServerPlugin extends GlobalPlugin {
protected spamLogs: GuildSpamLogs;
protected server: http.Server;
onLoad() {
this.spamLogs = new GuildSpamLogs(null);
this.server = http.createServer(async (req, res) => {
const logId = req.url.match(logUrlRegex);
if (!logId) return notFound(res);
if (logId) {
const log = await this.spamLogs.find(logId[1]);
if (!log) return notFound(res);
res.setHeader("Content-Type", "text/plain; charset=UTF-8");
res.end(log.body);
}
});
this.server.listen(this.configValue("port", DEFAULT_PORT));
}
async onUnload() {
return new Promise(resolve => {
this.server.close(() => resolve());
});
}
}

View file

@ -1,7 +1,6 @@
import { decorators as d, Plugin } from "knub";
import { Message, TextChannel } from "eris";
import {
cleanMessagesInChannel,
getEmojiInString,
getRoleMentions,
getUrlsInString,
@ -12,6 +11,7 @@ import { LogType } from "../data/LogType";
import { GuildLogs } from "../data/GuildLogs";
import { ModActionsPlugin } from "./ModActions";
import { CaseType } from "../data/CaseType";
import { GuildSpamLogs } from "../data/GuildSpamLogs";
enum RecentActionType {
Message = 1,
@ -35,6 +35,7 @@ const MAX_INTERVAL = 300;
export class SpamPlugin extends Plugin {
protected logs: GuildLogs;
protected spamLogs: GuildSpamLogs;
protected recentActions: IRecentAction[];
@ -56,6 +57,7 @@ export class SpamPlugin extends Plugin {
onLoad() {
this.logs = new GuildLogs(this.guildId);
this.spamLogs = new GuildSpamLogs(this.guildId);
this.expiryInterval = setInterval(() => this.clearOldRecentActions(), 1000 * 60);
this.recentActions = [];
}
@ -112,6 +114,17 @@ export class SpamPlugin extends Plugin {
this.recentActions = this.recentActions.filter(action => action.timestamp >= expiryTimestamp);
}
async saveSpamLogs(messages: Message[]) {
const channel = messages[0].channel as TextChannel;
const header = `Server: ${this.guild.name} (${this.guild.id}), channel: #${channel.name} (${
channel.id
})`;
const logId = await this.spamLogs.createFromMessages(messages, header);
const url = this.knub.getGlobalConfig().url;
return url ? `${url}/spam-logs/${logId}` : `Log ID: ${logId}`;
}
async detectSpam(
msg: Message,
type: RecentActionType,
@ -137,13 +150,15 @@ export class SpamPlugin extends Plugin {
const msgIds = recentActions.map(a => a.msg.id);
await this.bot.deleteMessages(msg.channel.id, msgIds);
const logUrl = await this.saveSpamLogs(recentActions.map(a => a.msg));
this.logs.log(LogType.SPAM_DELETE, {
member: stripObjectToScalars(msg.member, ["user"]),
channel: stripObjectToScalars(msg.channel),
description,
limit: spamConfig.count,
interval: spamConfig.interval
interval: spamConfig.interval,
logUrl
});
}

View file

@ -249,7 +249,6 @@ export function getUserMentions(str: string) {
// tslint:disable-next-line
while ((match = regex.exec(str)) !== null) {
console.log("m", match);
userIds.push(match[1]);
}