Add case_id to mutes. Add !mutes command to list mutes.
This commit is contained in:
parent
60c434999e
commit
7a372533ec
9 changed files with 131 additions and 3 deletions
12
migrations/20180801235500_add_case_id_to_mutes.js
Normal file
12
migrations/20180801235500_add_case_id_to_mutes.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
exports.up = async function(knex, Promise) {
|
||||
await knex.schema.table('mutes', table => {
|
||||
table.integer('case_id').unsigned().nullable().defaultTo(null).after('user_id').references('id').inTable('cases').onDelete('SET NULL');
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = async function(knex, Promise) {
|
||||
await knex.schema.table('mutes', table => {
|
||||
table.dropForeign('case_id');
|
||||
table.dropColumn('case_id');
|
||||
});
|
||||
};
|
5
package-lock.json
generated
5
package-lock.json
generated
|
@ -2824,6 +2824,11 @@
|
|||
"resolved": "https://registry.npmjs.org/lodash.at/-/lodash.at-4.6.0.tgz",
|
||||
"integrity": "sha1-k83OZk8KGZTqM9181A4jr9EbD/g="
|
||||
},
|
||||
"lodash.chunk": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.chunk/-/lodash.chunk-4.2.0.tgz",
|
||||
"integrity": "sha1-ZuXOH3btJ7QwPYxlEujRIW6BBrw="
|
||||
},
|
||||
"lodash.debounce": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
"knex": "0.12.6",
|
||||
"knub": "^9.6.4",
|
||||
"lodash.at": "^4.6.0",
|
||||
"lodash.chunk": "^4.2.0",
|
||||
"lodash.difference": "^4.5.0",
|
||||
"lodash.intersection": "^4.4.0",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
|
|
|
@ -9,6 +9,14 @@ export class GuildCases {
|
|||
this.guildId = guildId;
|
||||
}
|
||||
|
||||
async get(ids: number[]): Promise<Case[]> {
|
||||
const result = await knex("cases")
|
||||
.whereIn("id", ids)
|
||||
.select();
|
||||
|
||||
return result.map(r => new Case(r));
|
||||
}
|
||||
|
||||
async find(id: number): Promise<Case> {
|
||||
const result = await knex("cases")
|
||||
.where("guild_id", this.guildId)
|
||||
|
|
|
@ -69,6 +69,22 @@ export class GuildMutes {
|
|||
}
|
||||
}
|
||||
|
||||
async getActiveMutes(): Promise<Mute[]> {
|
||||
const result = await knex("mutes")
|
||||
.where("guild_id", this.guildId)
|
||||
.where(q => q.whereRaw("expires_at > NOW()").orWhereNull("expires_at"))
|
||||
.select();
|
||||
|
||||
return result.map(r => new Mute(r));
|
||||
}
|
||||
|
||||
async setCaseId(userId, caseId) {
|
||||
await knex("mutes")
|
||||
.where("guild_id", this.guildId)
|
||||
.where("user_id", userId)
|
||||
.update({ case_id: caseId });
|
||||
}
|
||||
|
||||
async clear(userId) {
|
||||
return knex("mutes")
|
||||
.where("guild_id", this.guildId)
|
||||
|
|
|
@ -3,6 +3,7 @@ import Model from "./Model";
|
|||
export default class Mute extends Model {
|
||||
public guild_id: string;
|
||||
public user_id: string;
|
||||
public case_id: number;
|
||||
public created_at: string;
|
||||
public expires_at: string;
|
||||
}
|
||||
|
|
|
@ -2,9 +2,11 @@ import { decorators as d, Plugin, waitForReaction } from "knub";
|
|||
import { Constants as ErisConstants, Guild, Member, Message, TextChannel, User } from "eris";
|
||||
import moment from "moment-timezone";
|
||||
import humanizeDuration from "humanize-duration";
|
||||
import chunk from "lodash.chunk";
|
||||
import { GuildCases } from "../data/GuildCases";
|
||||
import {
|
||||
convertDelayStringToMS,
|
||||
DBDateFormat,
|
||||
disableLinkPreviews,
|
||||
errorMessage,
|
||||
findRelevantAuditLogEntry,
|
||||
|
@ -36,8 +38,8 @@ interface IIgnoredEvent {
|
|||
const CASE_LIST_REASON_MAX_LENGTH = 80;
|
||||
|
||||
export class ModActionsPlugin extends Plugin {
|
||||
public mutes: GuildMutes;
|
||||
protected cases: GuildCases;
|
||||
protected mutes: GuildMutes;
|
||||
protected serverLogs: GuildLogs;
|
||||
|
||||
protected muteClearIntervalId: Timer;
|
||||
|
@ -355,7 +357,14 @@ export class ModActionsPlugin extends Plugin {
|
|||
this.muteMember(args.member, muteTime, args.reason);
|
||||
|
||||
// Create a case
|
||||
await this.createCase(args.member.id, msg.author.id, CaseType.Mute, null, args.reason);
|
||||
const caseId = await this.createCase(
|
||||
args.member.id,
|
||||
msg.author.id,
|
||||
CaseType.Mute,
|
||||
null,
|
||||
args.reason
|
||||
);
|
||||
await this.mutes.setCaseId(args.member.id, caseId);
|
||||
|
||||
// Message the user informing them of the mute
|
||||
let messageSent = true;
|
||||
|
@ -441,6 +450,78 @@ export class ModActionsPlugin extends Plugin {
|
|||
});
|
||||
}
|
||||
|
||||
@d.command("mutes")
|
||||
@d.permission("view")
|
||||
async mutesCmd(msg: Message) {
|
||||
const lines = [];
|
||||
|
||||
// Active, logged mutes
|
||||
const activeMutes = await this.mutes.getActiveMutes();
|
||||
activeMutes.sort((a, b) => {
|
||||
if (a.expires_at == null && b.expires_at != null) return 1;
|
||||
if (b.expires_at == null && a.expires_at != null) return -1;
|
||||
if (a.expires_at == null && b.expires_at == null) {
|
||||
return a.created_at > b.created_at ? 1 : -1;
|
||||
}
|
||||
return a.expires_at > b.expires_at ? 1 : -1;
|
||||
});
|
||||
|
||||
const caseIds = activeMutes.map(m => m.case_id).filter(v => !!v);
|
||||
const cases = caseIds ? await this.cases.get(caseIds) : [];
|
||||
const casesById = cases.reduce((map, c) => map.set(c.id, c), new Map());
|
||||
|
||||
lines.push(
|
||||
...activeMutes.map(mute => {
|
||||
const user = this.bot.users.get(mute.user_id);
|
||||
const username = user ? `${user.username}#${user.discriminator}` : "Unknown#0000";
|
||||
const theCase = casesById[mute.case_id] || null;
|
||||
const caseName = theCase ? `Case #${theCase.case_number}` : "No case";
|
||||
|
||||
let line = `\`${caseName}\` **${username}** (\`${mute.user_id}\`)`;
|
||||
|
||||
if (mute.expires_at) {
|
||||
const timeUntilExpiry = moment().diff(moment(mute.expires_at, DBDateFormat));
|
||||
const humanizedTime = humanizeDuration(timeUntilExpiry, { largest: 2, round: true });
|
||||
line += ` (expires in ${humanizedTime})`;
|
||||
} else {
|
||||
line += ` (doesn't expire)`;
|
||||
}
|
||||
|
||||
const mutedAt = moment(mute.created_at, DBDateFormat);
|
||||
line += ` (muted at ${mutedAt.format("YYYY-MM-DD HH:mm:ss")})`;
|
||||
|
||||
return line;
|
||||
})
|
||||
);
|
||||
|
||||
// Manually added mute roles
|
||||
const muteUserIds = activeMutes.reduce((set, m) => set.add(m.user_id), new Set());
|
||||
const manuallyMutedMembers = [];
|
||||
const muteRole = this.configValue("mute_role");
|
||||
|
||||
if (muteRole) {
|
||||
this.guild.members.forEach(member => {
|
||||
if (muteUserIds.has(member.id)) return;
|
||||
if (member.roles.includes(muteRole)) manuallyMutedMembers.push(member);
|
||||
});
|
||||
}
|
||||
|
||||
lines.push(
|
||||
...manuallyMutedMembers.map(member => {
|
||||
return `\`Manual mute\` **${member.user.username}#${member.user.discriminator}** (\`${
|
||||
member.id
|
||||
}\`)`;
|
||||
})
|
||||
);
|
||||
|
||||
const chunks = chunk(lines, 15);
|
||||
for (const [i, chunkLines] of chunks.entries()) {
|
||||
let body = chunkLines.join("\n");
|
||||
if (i === 0) body = `Active mutes:\n\n${body}`;
|
||||
msg.channel.createMessage(body);
|
||||
}
|
||||
}
|
||||
|
||||
@d.command("kick", "<member:Member> [reason:string$]")
|
||||
@d.permission("kick")
|
||||
async kickCmd(msg, args) {
|
||||
|
|
|
@ -192,7 +192,7 @@ export class SpamPlugin extends Plugin {
|
|||
spamConfig.mute_time ? spamConfig.mute_time * 60 * 1000 : 120 * 1000,
|
||||
"Automatic spam detection"
|
||||
);
|
||||
await modActionsPlugin.createCase(
|
||||
const caseId = await modActionsPlugin.createCase(
|
||||
msg.member.id,
|
||||
this.bot.user.id,
|
||||
CaseType.Mute,
|
||||
|
@ -205,6 +205,8 @@ export class SpamPlugin extends Plugin {
|
|||
`),
|
||||
true
|
||||
);
|
||||
await modActionsPlugin.mutes.setCaseId(msg.member.id, caseId);
|
||||
|
||||
this.logs.log(LogType.MEMBER_MUTE_SPAM, {
|
||||
member: stripObjectToScalars(msg.member, ["user"]),
|
||||
channel: stripObjectToScalars(msg.channel),
|
||||
|
|
|
@ -274,3 +274,5 @@ export function getRoleMentions(str: string) {
|
|||
export function disableLinkPreviews(str: string): string {
|
||||
return str.replace(/(?<!\<)(https?:\/\/\S+)/gi, "<$1>");
|
||||
}
|
||||
|
||||
export const DBDateFormat = "YYYY-MM-DD HH:mm:ss";
|
||||
|
|
Loading…
Add table
Reference in a new issue