Add !deletecase

This commit is contained in:
Dragory 2020-08-09 22:44:07 +03:00
parent 8826b2521d
commit ddbbc543c2
No known key found for this signature in database
GPG key ID: 5F387BA66DF8AAC1
9 changed files with 146 additions and 6 deletions

View file

@ -6,6 +6,6 @@ export enum CaseTypes {
Kick,
Mute,
Unmute,
Expunged,
Deleted,
Softban,
}

View file

@ -66,5 +66,7 @@
"AUTOMOD_ACTION": "\uD83E\uDD16 Automod rule **{rule}** triggered by {userMention(users)}\n{matchSummary}\nActions taken: **{actionsTaken}**",
"SET_ANTIRAID_USER": "⚔ {userMention(user)} set anti-raid to **{level}**",
"SET_ANTIRAID_AUTO": "⚔ Anti-raid automatically set to **{level}**"
"SET_ANTIRAID_AUTO": "⚔ Anti-raid automatically set to **{level}**",
"CASE_DELETE": "✂️ **Case #{case.case_number}** was deleted by {userMention(mod)}"
}

View file

@ -5,6 +5,7 @@ import { getRepository, In, Repository } from "typeorm";
import { disableLinkPreviews } from "../utils";
import { CaseTypes } from "./CaseTypes";
import moment = require("moment-timezone");
import { connection } from "./db";
const CASE_SUMMARY_REASON_MAX_LENGTH = 300;
@ -119,6 +120,37 @@ export class GuildCases extends BaseGuildRepository {
return this.cases.update(id, data);
}
async softDelete(id: number, deletedById: string, deletedByName: string, deletedByText: string) {
return connection.transaction(async entityManager => {
const cases = entityManager.getRepository(Case);
const caseNotes = entityManager.getRepository(CaseNote);
await Promise.all([
caseNotes.delete({
case_id: id,
}),
cases.update(id, {
user_id: "0",
user_name: "Unknown#0000",
mod_id: null,
mod_name: "Unknown#0000",
type: CaseTypes.Deleted,
audit_log_id: null,
is_hidden: false,
pp_id: null,
pp_name: null,
}),
]);
await caseNotes.insert({
case_id: id,
mod_id: deletedById,
mod_name: deletedByName,
body: deletedByText,
});
});
}
async createNote(caseId: number, data: any): Promise<void> {
await this.caseNotes.insert({
...data,

View file

@ -75,4 +75,6 @@ export enum LogType {
MASS_UNASSIGN_ROLES,
MEMBER_NOTE,
CASE_DELETE,
}

View file

@ -0,0 +1,22 @@
import { MigrationInterface, QueryRunner } from "typeorm";
import { TableForeignKey } from "typeorm/index";
export class AddCaseNotesForeignKey1596994103885 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<any> {
await queryRunner.createForeignKey(
"case_notes",
new TableForeignKey({
name: "case_notes_case_id_fk",
columnNames: ["case_id"],
referencedTableName: "cases",
referencedColumnNames: ["id"],
onDelete: "CASCADE",
onUpdate: "CASCADE",
}),
);
}
public async down(queryRunner: QueryRunner): Promise<any> {
await queryRunner.dropForeignKey("case_notes", "case_notes_case_id_fk");
}
}

View file

@ -1,5 +1,5 @@
import { Case } from "../../../data/entities/Case";
import { MessageContent } from "eris";
import { AdvancedMessageContent, MessageContent } from "eris";
import moment from "moment-timezone";
import { CaseTypes } from "../../../data/CaseTypes";
import { PluginData, helpers } from "knub";
@ -11,13 +11,19 @@ import { chunkLines, chunkMessageLines, emptyEmbedValue } from "../../../utils";
export async function getCaseEmbed(
pluginData: PluginData<CasesPluginType>,
caseOrCaseId: Case | number,
): Promise<MessageContent> {
): Promise<AdvancedMessageContent> {
const theCase = await pluginData.state.cases.with("notes").find(resolveCaseId(caseOrCaseId));
if (!theCase) return null;
const createdAt = moment(theCase.created_at);
const actionTypeStr = CaseTypes[theCase.type].toUpperCase();
let userName = theCase.user_name;
if (theCase.user_id && theCase.user_id !== "0") userName += `\n<@!${theCase.user_id}>`;
let modName = theCase.mod_name;
if (theCase.mod_id) modName += `\n<@!${theCase.mod_id}>`;
const embed: any = {
title: `${actionTypeStr} - Case #${theCase.case_number}`,
footer: {
@ -26,12 +32,12 @@ export async function getCaseEmbed(
fields: [
{
name: "User",
value: `${theCase.user_name}\n<@!${theCase.user_id}>`,
value: userName,
inline: true,
},
{
name: "Moderator",
value: `${theCase.mod_name}\n<@!${theCase.mod_id}>`,
value: modName,
inline: true,
},
],

View file

@ -34,6 +34,7 @@ import { kickMember } from "./functions/kickMember";
import { banUserId } from "./functions/banUserId";
import { MassmuteCmd } from "./commands/MassmuteCmd";
import { trimPluginDescription } from "../../utils";
import { DeleteCaseCmd } from "./commands/DeleteCaseCmd";
const defaultOptions = {
config: {
@ -65,6 +66,7 @@ const defaultOptions = {
can_massban: false,
can_massmute: false,
can_hidecase: false,
can_deletecase: false,
can_act_as_other: false,
},
overrides: [
@ -134,6 +136,7 @@ export const ModActionsPlugin = zeppelinPlugin<ModActionsPluginType>()("mod_acti
CasesModCmd,
HideCaseCmd,
UnhideCaseCmd,
DeleteCaseCmd,
],
public: {

View file

@ -0,0 +1,72 @@
import { modActionsCommand } from "../types";
import { commandTypeHelpers as ct } from "../../../commandTypes";
import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils";
import { helpers } from "knub";
import { CasesPlugin } from "../../Cases/CasesPlugin";
import { TextChannel } from "eris";
import { SECONDS, stripObjectToScalars, trimLines } from "../../../utils";
import { LogsPlugin } from "../../Logs/LogsPlugin";
import { LogType } from "../../../data/LogType";
import moment from "moment-timezone";
export const DeleteCaseCmd = modActionsCommand({
trigger: ["delete_case", "deletecase"],
permission: "can_deletecase",
description: trimLines(`
Delete the specified case. This operation can *not* be reversed.
It is generally recommended to use \`!hidecase\` instead when possible.
`),
signature: {
caseNumber: ct.number(),
force: ct.switchOption({ shortcut: "f" }),
},
async run({ pluginData, message, args }) {
const theCase = await pluginData.state.cases.findByCaseNumber(args.caseNumber);
if (!theCase) {
sendErrorMessage(pluginData, message.channel, "Case not found");
return;
}
if (!args.force) {
const cases = pluginData.getPlugin(CasesPlugin);
const embedContent = await cases.getCaseEmbed(theCase);
message.channel.createMessage({
content: "Delete the following case? Answer 'Yes' to continue, 'No' to cancel.",
embed: embedContent.embed,
});
const reply = await helpers.waitForReply(
pluginData.client,
message.channel as TextChannel,
message.author.id,
15 * SECONDS,
);
const normalizedReply = (reply?.content || "").toLowerCase().trim();
if (normalizedReply !== "yes" && normalizedReply !== "y") {
message.channel.createMessage("Cancelled. Case was not deleted.");
return;
}
}
const deletedByName = `${message.author.username}#${message.author.discriminator}`;
const deletedAt = moment().format(`MMM D, YYYY [at] H:mm [UTC]`);
await pluginData.state.cases.softDelete(
theCase.id,
message.author.id,
deletedByName,
`Case deleted by **${deletedByName}** (\`${message.author.id}\`) on ${deletedAt}`,
);
const logs = pluginData.getPlugin(LogsPlugin);
logs.log(LogType.CASE_DELETE, {
mod: stripObjectToScalars(message.member, ["user", "roles"]),
case: stripObjectToScalars(theCase),
});
sendSuccessMessage(pluginData, message.channel, `Case #${theCase.case_number} deleted!`);
},
});

View file

@ -35,6 +35,7 @@ export const ConfigSchema = t.type({
can_massban: t.boolean,
can_massmute: t.boolean,
can_hidecase: t.boolean,
can_deletecase: t.boolean,
can_act_as_other: t.boolean,
});
export type TConfigSchema = t.TypeOf<typeof ConfigSchema>;