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",
|
"resolved": "https://registry.npmjs.org/lodash.at/-/lodash.at-4.6.0.tgz",
|
||||||
"integrity": "sha1-k83OZk8KGZTqM9181A4jr9EbD/g="
|
"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": {
|
"lodash.debounce": {
|
||||||
"version": "4.0.8",
|
"version": "4.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
||||||
|
|
|
@ -35,6 +35,7 @@
|
||||||
"knex": "0.12.6",
|
"knex": "0.12.6",
|
||||||
"knub": "^9.6.4",
|
"knub": "^9.6.4",
|
||||||
"lodash.at": "^4.6.0",
|
"lodash.at": "^4.6.0",
|
||||||
|
"lodash.chunk": "^4.2.0",
|
||||||
"lodash.difference": "^4.5.0",
|
"lodash.difference": "^4.5.0",
|
||||||
"lodash.intersection": "^4.4.0",
|
"lodash.intersection": "^4.4.0",
|
||||||
"lodash.isequal": "^4.5.0",
|
"lodash.isequal": "^4.5.0",
|
||||||
|
|
|
@ -9,6 +9,14 @@ export class GuildCases {
|
||||||
this.guildId = guildId;
|
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> {
|
async find(id: number): Promise<Case> {
|
||||||
const result = await knex("cases")
|
const result = await knex("cases")
|
||||||
.where("guild_id", this.guildId)
|
.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) {
|
async clear(userId) {
|
||||||
return knex("mutes")
|
return knex("mutes")
|
||||||
.where("guild_id", this.guildId)
|
.where("guild_id", this.guildId)
|
||||||
|
|
|
@ -3,6 +3,7 @@ import Model from "./Model";
|
||||||
export default class Mute extends Model {
|
export default class Mute extends Model {
|
||||||
public guild_id: string;
|
public guild_id: string;
|
||||||
public user_id: string;
|
public user_id: string;
|
||||||
|
public case_id: number;
|
||||||
public created_at: string;
|
public created_at: string;
|
||||||
public expires_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 { Constants as ErisConstants, Guild, Member, Message, TextChannel, User } from "eris";
|
||||||
import moment from "moment-timezone";
|
import moment from "moment-timezone";
|
||||||
import humanizeDuration from "humanize-duration";
|
import humanizeDuration from "humanize-duration";
|
||||||
|
import chunk from "lodash.chunk";
|
||||||
import { GuildCases } from "../data/GuildCases";
|
import { GuildCases } from "../data/GuildCases";
|
||||||
import {
|
import {
|
||||||
convertDelayStringToMS,
|
convertDelayStringToMS,
|
||||||
|
DBDateFormat,
|
||||||
disableLinkPreviews,
|
disableLinkPreviews,
|
||||||
errorMessage,
|
errorMessage,
|
||||||
findRelevantAuditLogEntry,
|
findRelevantAuditLogEntry,
|
||||||
|
@ -36,8 +38,8 @@ interface IIgnoredEvent {
|
||||||
const CASE_LIST_REASON_MAX_LENGTH = 80;
|
const CASE_LIST_REASON_MAX_LENGTH = 80;
|
||||||
|
|
||||||
export class ModActionsPlugin extends Plugin {
|
export class ModActionsPlugin extends Plugin {
|
||||||
|
public mutes: GuildMutes;
|
||||||
protected cases: GuildCases;
|
protected cases: GuildCases;
|
||||||
protected mutes: GuildMutes;
|
|
||||||
protected serverLogs: GuildLogs;
|
protected serverLogs: GuildLogs;
|
||||||
|
|
||||||
protected muteClearIntervalId: Timer;
|
protected muteClearIntervalId: Timer;
|
||||||
|
@ -355,7 +357,14 @@ export class ModActionsPlugin extends Plugin {
|
||||||
this.muteMember(args.member, muteTime, args.reason);
|
this.muteMember(args.member, muteTime, args.reason);
|
||||||
|
|
||||||
// Create a case
|
// 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
|
// Message the user informing them of the mute
|
||||||
let messageSent = true;
|
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.command("kick", "<member:Member> [reason:string$]")
|
||||||
@d.permission("kick")
|
@d.permission("kick")
|
||||||
async kickCmd(msg, args) {
|
async kickCmd(msg, args) {
|
||||||
|
|
|
@ -192,7 +192,7 @@ export class SpamPlugin extends Plugin {
|
||||||
spamConfig.mute_time ? spamConfig.mute_time * 60 * 1000 : 120 * 1000,
|
spamConfig.mute_time ? spamConfig.mute_time * 60 * 1000 : 120 * 1000,
|
||||||
"Automatic spam detection"
|
"Automatic spam detection"
|
||||||
);
|
);
|
||||||
await modActionsPlugin.createCase(
|
const caseId = await modActionsPlugin.createCase(
|
||||||
msg.member.id,
|
msg.member.id,
|
||||||
this.bot.user.id,
|
this.bot.user.id,
|
||||||
CaseType.Mute,
|
CaseType.Mute,
|
||||||
|
@ -205,6 +205,8 @@ export class SpamPlugin extends Plugin {
|
||||||
`),
|
`),
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
await modActionsPlugin.mutes.setCaseId(msg.member.id, caseId);
|
||||||
|
|
||||||
this.logs.log(LogType.MEMBER_MUTE_SPAM, {
|
this.logs.log(LogType.MEMBER_MUTE_SPAM, {
|
||||||
member: stripObjectToScalars(msg.member, ["user"]),
|
member: stripObjectToScalars(msg.member, ["user"]),
|
||||||
channel: stripObjectToScalars(msg.channel),
|
channel: stripObjectToScalars(msg.channel),
|
||||||
|
|
|
@ -274,3 +274,5 @@ export function getRoleMentions(str: string) {
|
||||||
export function disableLinkPreviews(str: string): string {
|
export function disableLinkPreviews(str: string): string {
|
||||||
return str.replace(/(?<!\<)(https?:\/\/\S+)/gi, "<$1>");
|
return str.replace(/(?<!\<)(https?:\/\/\S+)/gi, "<$1>");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const DBDateFormat = "YYYY-MM-DD HH:mm:ss";
|
||||||
|
|
Loading…
Add table
Reference in a new issue