General fixes. Update Knub to 9.6.1. Add info and server commands.

This commit is contained in:
Dragory 2018-07-31 04:02:45 +03:00
parent 7ded84b924
commit 5359d0d5fe
7 changed files with 245 additions and 24 deletions

6
package-lock.json generated
View file

@ -2181,9 +2181,9 @@
}
},
"knub": {
"version": "9.6.0",
"resolved": "https://registry.npmjs.org/knub/-/knub-9.6.0.tgz",
"integrity": "sha512-+a/woh8WnSxBkflNjCjvfGASadz80o/0Mot81K9sr8BvcITzeDtoOBaxzeiwCb5NWNtYz/Qp9M7ZZ6Jr5U45bg==",
"version": "9.6.1",
"resolved": "https://registry.npmjs.org/knub/-/knub-9.6.1.tgz",
"integrity": "sha512-Usydud/TYz8Il/8DLpMSTnAup3nespr3DLlPRnFzmO9mL0hQnCKtqAdp3WoDCmPb6uhqJ3zCe6AH66zRS7VC/w==",
"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.0",
"knub": "^9.6.1",
"lodash.at": "^4.6.0",
"lodash.difference": "^4.5.0",
"lodash.intersection": "^4.4.0",

View file

@ -6,8 +6,8 @@
"MEMBER_MUTE_EXPIRED": "🔉 **{member.user.username}#{member.user.discriminator}**'s mute expired",
"MEMBER_KICK": "👢 **{user.username}#{user.discriminator}** (`{user.id}`) was kicked by {mod.username}#{mod.discriminator}",
"MEMBER_BAN": "🔨 **{user.username}#{user.discriminator}** (`{user.id}`) was banned by {mod.username}#{mod.discriminator}",
"MEMBER_UNBAN": "🔓 **{user.username}#{user.discriminator}** (`{user.id}`) was unbanned by {mod.username}#{mod.discriminator}",
"MEMBER_FORCEBAN": "🔨 User `{userId}` was forcebanned by {mod.username}#{mod.discriminator}",
"MEMBER_UNBAN": "🔓 User (`{userId}`) was unbanned by {mod.username}#{mod.discriminator}",
"MEMBER_FORCEBAN": "🔨 User (`{userId}`) was forcebanned by {mod.username}#{mod.discriminator}",
"MEMBER_JOIN": "📥 **{member.user.username}#{member.user.discriminator}** (`{member.id}`) joined{new} (created {account_age} ago)",
"MEMBER_LEAVE": "📤 **{member.user.username}#{member.user.discriminator}** (`{member.id}`) left the server",
"MEMBER_ROLE_ADD": "🔑 **{member.user.username}#{member.user.discriminator}** (`{member.id}`) role added **{role.name}** by {mod.username}#{mod.discriminator}",

View file

@ -113,10 +113,14 @@ export class LogsPlugin extends Plugin {
);
const mod = relevantAuditLogEntry ? relevantAuditLogEntry.user : unknownUser;
this.serverLogs.log(LogType.MEMBER_BAN, {
this.serverLogs.log(
LogType.MEMBER_BAN,
{
user: stripObjectToScalars(user),
mod: stripObjectToScalars(mod)
});
},
user.id
);
}
@d.event("guildBanRemove")
@ -128,10 +132,14 @@ export class LogsPlugin extends Plugin {
);
const mod = relevantAuditLogEntry ? relevantAuditLogEntry.user : unknownUser;
this.serverLogs.log(LogType.MEMBER_UNBAN, {
user: stripObjectToScalars(user),
mod: stripObjectToScalars(mod)
});
this.serverLogs.log(
LogType.MEMBER_UNBAN,
{
mod: stripObjectToScalars(mod),
userId: user.id
},
user.id
);
}
@d.event("guildMemberUpdate")

View file

@ -19,6 +19,17 @@ import { GuildLogs } from "../data/GuildLogs";
import { LogType } from "../data/LogType";
import Timer = NodeJS.Timer;
enum IgnoredEventType {
Ban = 1,
Unban,
Kick
}
interface IIgnoredEvent {
type: IgnoredEventType;
userId: string;
}
export class ModActionsPlugin extends Plugin {
protected cases: GuildCases;
protected mutes: GuildMutes;
@ -26,11 +37,15 @@ export class ModActionsPlugin extends Plugin {
protected muteClearIntervalId: Timer;
protected ignoredEvents: IIgnoredEvent[];
async onLoad() {
this.cases = new GuildCases(this.guildId);
this.mutes = new GuildMutes(this.guildId);
this.serverLogs = new GuildLogs(this.guildId);
this.ignoredEvents = [];
// Check for expired mutes every 5s
this.clearExpiredMutes();
this.muteClearIntervalId = setInterval(() => this.clearExpiredMutes(), 5000);
@ -89,13 +104,37 @@ export class ModActionsPlugin extends Plugin {
};
}
ignoreEvent(type: IgnoredEventType, userId: any) {
this.ignoredEvents.push({ type, userId });
// Clear after expiry (15sec by default)
setTimeout(() => {
this.clearIgnoredEvent(type, userId);
}, 1000 * 15);
}
isEventIgnored(type: IgnoredEventType, userId: any) {
return this.ignoredEvents.some(info => type === info.type && userId === info.userId);
}
clearIgnoredEvent(type: IgnoredEventType, userId: any) {
this.ignoredEvents.splice(
this.ignoredEvents.findIndex(info => type === info.type && userId === info.userId),
1
);
}
/**
* Add a BAN action automatically when a user is banned.
* Attempts to find the ban's details in the audit log.
*/
@d.event("guildBanAdd")
async onGuildBanAdd(guild: Guild, user: User) {
await sleep(1000); // Wait a moment for the audit log to update
if (this.isEventIgnored(IgnoredEventType.Ban, user.id)) {
this.clearIgnoredEvent(IgnoredEventType.Ban, user.id);
return;
}
const relevantAuditLogEntry = await findRelevantAuditLogEntry(
this.guild,
ErisConstants.AuditLogActions.MEMBER_BAN_ADD,
@ -125,6 +164,11 @@ export class ModActionsPlugin extends Plugin {
*/
@d.event("guildBanRemove")
async onGuildBanRemove(guild: Guild, user: User) {
if (this.isEventIgnored(IgnoredEventType.Unban, user.id)) {
this.clearIgnoredEvent(IgnoredEventType.Unban, user.id);
return;
}
const relevantAuditLogEntry = await findRelevantAuditLogEntry(
this.guild,
ErisConstants.AuditLogActions.MEMBER_BAN_REMOVE,
@ -165,6 +209,11 @@ export class ModActionsPlugin extends Plugin {
@d.event("guildMemberRemove")
async onGuildMemberRemove(_, member: Member) {
if (this.isEventIgnored(IgnoredEventType.Kick, member.id)) {
this.clearIgnoredEvent(IgnoredEventType.Kick, member.id);
return;
}
const kickAuditLogEntry = await findRelevantAuditLogEntry(
this.guild,
ErisConstants.AuditLogActions.MEMBER_KICK,
@ -398,6 +447,7 @@ export class ModActionsPlugin extends Plugin {
// Kick the user
this.serverLogs.ignoreLog(LogType.MEMBER_KICK, args.member.id);
this.ignoreEvent(IgnoredEventType.Kick, args.member.id);
args.member.kick(args.reason);
// Create a case for this action
@ -442,6 +492,7 @@ export class ModActionsPlugin extends Plugin {
// Ban the user
this.serverLogs.ignoreLog(LogType.MEMBER_BAN, args.member.id);
this.ignoreEvent(IgnoredEventType.Ban, args.member.id);
args.member.ban(1, args.reason);
// Create a case for this action
@ -462,9 +513,10 @@ export class ModActionsPlugin extends Plugin {
@d.command("unban", "<userId:string> [reason:string$]")
@d.permission("ban")
async unbanCmd(msg: Message, args: any) {
this.serverLogs.ignoreLog(LogType.MEMBER_UNBAN, args.member.id);
this.serverLogs.ignoreLog(LogType.MEMBER_UNBAN, args.userId);
try {
this.ignoreEvent(IgnoredEventType.Unban, args.userId);
await this.guild.unbanMember(args.userId, args.reason);
} catch (e) {
msg.channel.createMessage(errorMessage("Failed to unban member"));
@ -496,7 +548,8 @@ export class ModActionsPlugin extends Plugin {
return;
}
this.serverLogs.ignoreLog(LogType.MEMBER_FORCEBAN, args.member.id);
this.ignoreEvent(IgnoredEventType.Ban, args.userId);
this.serverLogs.ignoreLog(LogType.MEMBER_BAN, args.userId);
try {
await this.guild.banMember(args.userId, 1, args.reason);

View file

@ -1,14 +1,26 @@
import { Plugin, decorators as d, reply } from "knub";
import { Channel, Message, TextChannel, User } from "eris";
import { errorMessage, getMessages, stripObjectToScalars, successMessage } from "../utils";
import { Channel, Embed, EmbedOptions, Message, TextChannel, User, VoiceChannel } from "eris";
import {
embedPadding,
errorMessage,
getMessages,
stripObjectToScalars,
successMessage,
trimLines
} from "../utils";
import { GuildLogs } from "../data/GuildLogs";
import { LogType } from "../data/LogType";
import moment from "moment-timezone";
import humanizeDuration from "humanize-duration";
import { GuildCases } from "../data/GuildCases";
import { CaseType } from "../data/CaseType";
const MAX_SEARCH_RESULTS = 15;
const MAX_CLEAN_COUNT = 50;
export class UtilityPlugin extends Plugin {
protected logs: GuildLogs;
protected cases: GuildCases;
getDefaultOptions() {
return {
@ -17,7 +29,8 @@ export class UtilityPlugin extends Plugin {
level: false,
search: false,
clean: false,
info: false
info: false,
server: true
},
overrides: [
{
@ -27,7 +40,8 @@ export class UtilityPlugin extends Plugin {
level: true,
search: true,
clean: true,
info: true
info: true,
server: true
}
}
]
@ -36,6 +50,7 @@ export class UtilityPlugin extends Plugin {
onLoad() {
this.logs = new GuildLogs(this.guildId);
this.cases = new GuildCases(this.guildId);
}
@d.command("roles")
@ -122,8 +137,9 @@ export class UtilityPlugin extends Plugin {
m => m.id !== msg.id,
args.count
);
if (messagesToClean.length > 0)
if (messagesToClean.length > 0) {
await this.cleanMessages(msg.channel, messagesToClean.map(m => m.id), msg.author);
}
msg.channel.createMessage(
successMessage(
@ -147,8 +163,9 @@ export class UtilityPlugin extends Plugin {
m => m.id !== msg.id && m.author.id === args.userId,
args.count
);
if (messagesToClean.length > 0)
if (messagesToClean.length > 0) {
await this.cleanMessages(msg.channel, messagesToClean.map(m => m.id), msg.author);
}
msg.channel.createMessage(
successMessage(
@ -172,8 +189,9 @@ export class UtilityPlugin extends Plugin {
m => m.id !== msg.id && m.author.bot,
args.count
);
if (messagesToClean.length > 0)
if (messagesToClean.length > 0) {
await this.cleanMessages(msg.channel, messagesToClean.map(m => m.id), msg.author);
}
msg.channel.createMessage(
successMessage(
@ -181,4 +199,134 @@ export class UtilityPlugin extends Plugin {
)
);
}
@d.command("info", "<userId:userId>")
@d.permission("info")
async infoCmd(msg: Message, args: { userId: string }) {
const embed: EmbedOptions = {
fields: []
};
const user = this.bot.users.get(args.userId);
if (user) {
const createdAt = moment(user.createdAt);
const accountAge = humanizeDuration(moment().valueOf() - user.createdAt, {
largest: 2,
round: true
});
embed.title = `${user.username}#${user.discriminator}`;
embed.thumbnail = { url: user.avatarURL };
embed.fields.push({
name: "User information",
value:
trimLines(`
ID: ${user.id}
Profile: <@!${user.id}>
Created: ${accountAge} ago (${createdAt.format("YYYY-MM-DD[T]HH:mm:ss")})
`) + embedPadding
});
} else {
embed.title = `Unknown user`;
}
const member = this.guild.members.get(args.userId);
if (member) {
const joinedAt = moment(member.joinedAt);
const joinAge = humanizeDuration(moment().valueOf() - member.joinedAt, {
largest: 2,
round: true
});
const roles = member.roles.map(id => this.guild.roles.get(id));
embed.fields.push({
name: "Member information",
value:
trimLines(`
Joined: ${joinAge} ago (${joinedAt.format("YYYY-MM-DD[T]HH:mm:ss")})
${roles.length > 0 ? "Roles: " + roles.map(r => r.name).join(", ") : ""}
`) + embedPadding
});
}
const cases = await this.cases.getByUserId(args.userId);
if (cases.length > 0) {
cases.sort((a, b) => {
return a.created_at < b.created_at ? -1 : 1;
});
const caseSummaries = cases.map(c => {
return `${CaseType[c.type]} (#${c.case_number})`;
});
embed.fields.push({
name: "Cases",
value: trimLines(`
Total cases: ${cases.length}
Summary: ${caseSummaries.join(", ")}
`)
});
}
msg.channel.createMessage({ embed });
}
@d.command("server")
@d.permission("server")
async serverCmd(msg: Message) {
await this.guild.fetchAllMembers();
const embed: EmbedOptions = {
fields: []
};
embed.thumbnail = { url: this.guild.iconURL };
const createdAt = moment(this.guild.createdAt);
const serverAge = humanizeDuration(moment().valueOf() - this.guild.createdAt, {
largest: 2,
round: true
});
embed.fields.push({
name: "Server information",
value:
trimLines(`
Created: ${serverAge} ago (${createdAt.format("YYYY-MM-DD[T]HH:mm:ss")})
Members: ${this.guild.memberCount}
${this.guild.features.length > 0 ? "Features: " + this.guild.features.join(", ") : ""}
`) + embedPadding
});
const textChannels = this.guild.channels.filter(channel => channel instanceof TextChannel);
const voiceChannels = this.guild.channels.filter(channel => channel instanceof VoiceChannel);
embed.fields.push({
name: "Counts",
value:
trimLines(`
Roles: ${this.guild.roles.size}
Text channels: ${textChannels.length}
Voice channels: ${voiceChannels.length}
`) + embedPadding
});
const onlineMembers = this.guild.members.filter(m => m.status === "online");
const dndMembers = this.guild.members.filter(m => m.status === "dnd");
const idleMembers = this.guild.members.filter(m => m.status === "idle");
const offlineMembers = this.guild.members.filter(m => m.status === "offline");
embed.fields.push({
name: "Members",
value: trimLines(`
Online: **${onlineMembers.length}**
Idle: **${idleMembers.length}**
DND: **${dndMembers.length}**
Offline: **${offlineMembers.length}**
`)
});
msg.channel.createMessage({ embed });
}
}

View file

@ -226,3 +226,15 @@ export async function cleanMessagesInChannel(
await bot.deleteMessages(channel.id, ids, reason);
}
}
export function trimLines(str: string) {
return str
.trim()
.split("\n")
.map(l => l.trim())
.join("\n")
.trim();
}
export const emptyEmbedValue = "\u200b";
export const embedPadding = "\n" + emptyEmbedValue;