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 { BotControlPlugin } from "./plugins/BotControl";
|
||||||
import { ModActionsPlugin } from "./plugins/ModActions";
|
import { ModActionsPlugin } from "./plugins/ModActions";
|
||||||
import { UtilityPlugin } from "./plugins/Utility";
|
import { UtilityPlugin } from "./plugins/Utility";
|
||||||
|
import { LogsPlugin } from "./plugins/Logs";
|
||||||
import knex from "./knex";
|
import knex from "./knex";
|
||||||
|
|
||||||
// Run latest database migrations
|
// Run latest database migrations
|
||||||
|
@ -26,7 +27,8 @@ knex.migrate.latest().then(() => {
|
||||||
const bot = new Knub(client, {
|
const bot = new Knub(client, {
|
||||||
plugins: {
|
plugins: {
|
||||||
utility: UtilityPlugin,
|
utility: UtilityPlugin,
|
||||||
mod_actions: ModActionsPlugin
|
mod_actions: ModActionsPlugin,
|
||||||
|
logs: LogsPlugin
|
||||||
},
|
},
|
||||||
globalPlugins: {
|
globalPlugins: {
|
||||||
bot_control: BotControlPlugin
|
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 at = require("lodash.at");
|
||||||
import { ModActionType } from "./data/ModActionType";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Turns a "delay string" such as "1h30m" to milliseconds
|
* Turns a "delay string" such as "1h30m" to milliseconds
|
||||||
|
@ -42,3 +41,36 @@ export function errorMessage(str) {
|
||||||
export function uclower(str) {
|
export function uclower(str) {
|
||||||
return str[0].toLowerCase() + str.slice(1);
|
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