Add Logs plugin and GuildServerLogs class to communicate with it
This commit is contained in:
parent
8234f67b0f
commit
c087654979
5 changed files with 188 additions and 3 deletions
26
src/data/GuildServerLogs.ts
Normal file
26
src/data/GuildServerLogs.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
import * as EventEmitter from "events";
|
||||
import { LogType } from "./LogType";
|
||||
|
||||
// Use the same instance for the same guild, even if a new instance is created
|
||||
const guildInstances: Map<string, GuildServerLogs> = new Map();
|
||||
|
||||
export class GuildServerLogs extends EventEmitter {
|
||||
protected guildId: string;
|
||||
|
||||
constructor(guildId) {
|
||||
if (guildInstances.has(guildId)) {
|
||||
// Return existing instance for this guild if one exists
|
||||
return guildInstances.get(guildId);
|
||||
}
|
||||
|
||||
super();
|
||||
this.guildId = guildId;
|
||||
|
||||
// Store the instance for this guild so it can be returned later if a new instance for this guild is requested
|
||||
guildInstances.set(guildId, this);
|
||||
}
|
||||
|
||||
log(type: LogType, data: any) {
|
||||
this.emit("log", { type, data });
|
||||
}
|
||||
}
|
34
src/data/LogType.ts
Normal file
34
src/data/LogType.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
export enum LogType {
|
||||
MEMBER_WARN = 1,
|
||||
MEMBER_MUTE,
|
||||
MEMBER_UNMUTE,
|
||||
MEMBER_KICK,
|
||||
MEMBER_BAN,
|
||||
MEMBER_JOIN,
|
||||
MEMBER_LEAVE,
|
||||
MEMBER_ROLE_ADD,
|
||||
MEMBER_ROLE_REMOVE,
|
||||
MEMBER_NICK_CHANGE,
|
||||
MEMBER_USERNAME_CHANGE,
|
||||
MEMBER_ROLES_RESTORE,
|
||||
|
||||
CHANNEL_CREATE,
|
||||
CHANNEL_DELETE,
|
||||
CHANNEL_EDIT,
|
||||
|
||||
ROLE_CREATE,
|
||||
ROLE_DELETE,
|
||||
|
||||
MESSAGE_EDIT,
|
||||
MESSAGE_DELETE,
|
||||
MESSAGE_DELETE_BULK,
|
||||
|
||||
VOICE_CHANNEL_JOIN,
|
||||
VOICE_CHANNEL_LEAVE,
|
||||
VOICE_CHANNEL_MOVE,
|
||||
|
||||
COMMAND,
|
||||
|
||||
SPAM_DELETE,
|
||||
CENSOR
|
||||
}
|
|
@ -16,6 +16,7 @@ import { Knub, logger } from "knub";
|
|||
import { BotControlPlugin } from "./plugins/BotControl";
|
||||
import { ModActionsPlugin } from "./plugins/ModActions";
|
||||
import { UtilityPlugin } from "./plugins/Utility";
|
||||
import { LogsPlugin } from "./plugins/Logs";
|
||||
import knex from "./knex";
|
||||
|
||||
// Run latest database migrations
|
||||
|
@ -26,7 +27,8 @@ knex.migrate.latest().then(() => {
|
|||
const bot = new Knub(client, {
|
||||
plugins: {
|
||||
utility: UtilityPlugin,
|
||||
mod_actions: ModActionsPlugin
|
||||
mod_actions: ModActionsPlugin,
|
||||
logs: LogsPlugin
|
||||
},
|
||||
globalPlugins: {
|
||||
bot_control: BotControlPlugin
|
||||
|
|
91
src/plugins/Logs.ts
Normal file
91
src/plugins/Logs.ts
Normal file
|
@ -0,0 +1,91 @@
|
|||
import { Plugin } from "knub";
|
||||
import { GuildServerLogs } from "../data/GuildServerLogs";
|
||||
import { LogType } from "../data/LogType";
|
||||
import { TextChannel } from "eris";
|
||||
import { formatTemplateString } from "../utils";
|
||||
import * as moment from "moment-timezone";
|
||||
|
||||
interface ILogChannel {
|
||||
include?: LogType[];
|
||||
exclude?: LogType[];
|
||||
}
|
||||
|
||||
interface ILogChannelMap {
|
||||
[channelId: string]: ILogChannel;
|
||||
}
|
||||
|
||||
export class LogsPlugin extends Plugin {
|
||||
protected serverLogs: GuildServerLogs;
|
||||
protected logListener;
|
||||
|
||||
getDefaultOptions() {
|
||||
return {
|
||||
config: {
|
||||
channels: {},
|
||||
format: {
|
||||
timestamp: "HH:mm:ss",
|
||||
MEMBER_WARN:
|
||||
"⚠️ **{member.user.username}#{member.user.discriminator}** (`{member.id}`) was warned by {mod.user.username}#{mod.user.discriminator}",
|
||||
MEMBER_MUTE:
|
||||
"🔇 **{member.user.username}#{member.user.discriminator}** (`{member.id}`) was muted by {mod.user.username}#{mod.user.discriminator}",
|
||||
MEMBER_UNMUTE:
|
||||
"🔉 **{member.user.username}#{member.user.discriminator}** was unmuted",
|
||||
MEMBER_KICK:
|
||||
"👢 **{member.user.username}#{member.user.discriminator}** (`{member.id}`) was kicked by {mod.user.username}#{mod.user.discriminator}",
|
||||
MEMBER_BAN:
|
||||
"🔨 **{member.user.username}#{member.user.discriminator}** (`{member.id}`) was banned by {mod.user.username}#{mod.user.discriminator}",
|
||||
MEMBER_JOIN:
|
||||
"📥 **{member.user.username}#{member.user.discriminator}** (`{member.id}`) joined{new} (created {account_age})",
|
||||
MEMBER_LEAVE:
|
||||
"📤 **{member.user.username}#{member.user.discriminator}** left the server",
|
||||
MEMBER_ROLE_ADD:
|
||||
"🔑 **{member.user.username}#{member.user.discriminator}** role added **{role.name}** by {mod.user.username}#{mod.user.discriminator}",
|
||||
MEMBER_ROLE_REMOVE:
|
||||
"🔑 **{member.user.username}#{member.user.discriminator}** role removed **{role.name}** by {mod.user.username}#{mod.user.discriminator}"
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
onLoad() {
|
||||
this.serverLogs = new GuildServerLogs(this.guildId);
|
||||
|
||||
this.logListener = ({ type, data }) => this.log(type, data);
|
||||
this.serverLogs.on("log", this.logListener);
|
||||
}
|
||||
|
||||
onUnload() {
|
||||
this.serverLogs.removeListener("log", this.logListener);
|
||||
}
|
||||
|
||||
log(type, data) {
|
||||
const logChannels: ILogChannelMap = this.configValue("channels");
|
||||
for (const [channelId, opts] of Object.entries(logChannels)) {
|
||||
const channel = this.guild.channels.get(channelId);
|
||||
if (!channel || !(channel instanceof TextChannel)) continue;
|
||||
|
||||
if (
|
||||
(opts.include && opts.include.includes(type)) ||
|
||||
(opts.exclude && !opts.exclude.includes(type))
|
||||
) {
|
||||
const message = this.getLogMessage(type, data);
|
||||
if (message) channel.createMessage(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getLogMessage(type, data): string {
|
||||
const format = this.configValue(`format.${LogType[type]}`, "");
|
||||
if (format === "") return;
|
||||
|
||||
const formatted = formatTemplateString(format, data);
|
||||
|
||||
const timestampFormat = this.configValue("format.timestamp");
|
||||
if (timestampFormat) {
|
||||
const timestamp = moment().format(timestampFormat);
|
||||
return `\`[${timestamp}]\` ${formatted}`;
|
||||
} else {
|
||||
return formatted;
|
||||
}
|
||||
}
|
||||
}
|
36
src/utils.ts
36
src/utils.ts
|
@ -1,5 +1,4 @@
|
|||
import * as moment from "moment-timezone";
|
||||
import { ModActionType } from "./data/ModActionType";
|
||||
import at = require("lodash.at");
|
||||
|
||||
/**
|
||||
* Turns a "delay string" such as "1h30m" to milliseconds
|
||||
|
@ -42,3 +41,36 @@ export function errorMessage(str) {
|
|||
export function uclower(str) {
|
||||
return str[0].toLowerCase() + str.slice(1);
|
||||
}
|
||||
|
||||
export function stripObjectToScalars(obj, includedNested: string[]) {
|
||||
const result = {};
|
||||
|
||||
for (const key in obj) {
|
||||
if (
|
||||
obj[key] == null ||
|
||||
typeof obj[key] === "string" ||
|
||||
typeof obj[key] === "number" ||
|
||||
typeof obj[key] === "boolean"
|
||||
) {
|
||||
result[key] = obj[key];
|
||||
} else if (typeof obj[key] === "object") {
|
||||
const prefix = `${key}.`;
|
||||
const nestedNested = includedNested
|
||||
.filter(p => p === key || p.startsWith(prefix))
|
||||
.map(p => (p === key ? p : p.slice(prefix.length)));
|
||||
|
||||
if (nestedNested.length) {
|
||||
result[key] = stripObjectToScalars(obj[key], nestedNested);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const stringFormatRegex = /{([^{}]+?)}/g;
|
||||
export function formatTemplateString(str: string, values) {
|
||||
return str.replace(stringFormatRegex, (match, val) => {
|
||||
return (at(values, val)[0] as string) || "";
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue