Skip to content
This repository has been archived by the owner on Oct 12, 2024. It is now read-only.

Commit

Permalink
Int-CI-Moderation: Add warn, mute, kick, and ban commands
Browse files Browse the repository at this point in the history
  • Loading branch information
RadiatedExodus committed Apr 24, 2024
1 parent 36a48da commit 054aacd
Show file tree
Hide file tree
Showing 8 changed files with 687 additions and 2 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,7 @@ yarn start
## Configuration file

See the `.env.example`

## Special thanks

- [@Abdelrahmanwalidhassan's `ms` fork](https://github.com/Abdelrahmanwalidhassan/ms)
86 changes: 84 additions & 2 deletions src/classes/dbUtils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { MessageCreateOptions, MessagePayload, User, time, userMention } from "discord.js";
import { ModerationAction } from "@prisma/client";
import { EmbedBuilder, MessageCreateOptions, MessagePayload, User, time, userMention } from "discord.js";
import { ModerationAction, ModerationCase } from "@prisma/client";
import MeteoriumClient from "./client.js";
import MeteoriumEmbedBuilder from "./embedBuilder.js";

Expand All @@ -23,6 +23,20 @@ export type CaseData = {
Removed: boolean;
};

export type NewCaseData = {
Action: ModerationAction;
GuildId: string;
TargetUserId: string;
ModeratorUserId: string;
RelatedCaseId?: number;
Reason: string;
AttachmentProof?: string;
Duration?: string;
ModeratorNote?: string;
ModeratorAttachment?: string;
NotAppealable?: boolean;
};

export default class MeteoriumDatabaseUtilities {
public client: MeteoriumClient;

Expand Down Expand Up @@ -209,4 +223,72 @@ export default class MeteoriumDatabaseUtilities {

return await channel.send(reply);
}

public async createModerationCase(
data: NewCaseData,
afterDbCreateCallback?: (caseDb: ModerationCase) => Promise<void>,
) {
// Create case
const caseDb = await this.client.db.moderationCase.create({
data: {
GuildId: data.GuildId,
CaseId: (await this.client.db.moderationCase.count({ where: { GuildId: data.GuildId } })) + 1,
Action: data.Action,
TargetUserId: data.TargetUserId,
ModeratorUserId: data.ModeratorUserId,
Reason: data.Reason,
AttachmentProof: data.AttachmentProof,
Duration: data.Duration,
ModeratorNote: data.ModeratorNote,
ModeratorAttachment: data.ModeratorAttachment,
NotAppealable: data.NotAppealable,
RelatedCaseId: data.RelatedCaseId,
},
});

// Do callback
if (afterDbCreateCallback) await afterDbCreateCallback(caseDb);

// Generate embed
const embed = await this.generateCaseEmbedFromData(
{
...caseDb,
Removed: false,
PublicLogMsgId: "",
},
undefined,
false,
false,
);

// Send in public log
const pubLog = await this.sendGuildPubLog(data.GuildId, { embeds: [embed] });
if (pubLog)
await this.client.db.moderationCase.update({
where: { GlobalCaseId: caseDb.GlobalCaseId },
data: { PublicModLogMsgId: pubLog.id },
});

// Generate full embed
const fullEmbed = await this.generateCaseEmbedFromData(
{
...caseDb,
Removed: false,
PublicLogMsgId: pubLog ? pubLog.id : "",
},
await this.client.users.fetch(data.ModeratorUserId).catch(() => undefined),
true,
true,
);

// Send in private log
await this.sendGuildLog(data.GuildId, { embeds: [fullEmbed] });

return {
globalCaseId: caseDb.GlobalCaseId,
caseId: caseDb.CaseId,
embed: embed,
fullEmbed: fullEmbed,
};
}
}
241 changes: 241 additions & 0 deletions src/classes/ms.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
// https://github.com/vercel/ms
// https://github.com/Abdelrahmanwalidhassan/ms

// Helpers.
const s = 1000;
const m = s * 60;
const h = m * 60;
const d = h * 24;
const w = d * 7;
const mo = d * 30.4375;
const y = d * 365.25;

type Unit =
| "Years"
| "Year"
| "Yrs"
| "Yr"
| "Y"
| "Months"
| "Month"
| "Mo"
| "Weeks"
| "Week"
| "W"
| "Days"
| "Day"
| "D"
| "Hours"
| "Hour"
| "Hrs"
| "Hr"
| "H"
| "Minutes"
| "Minute"
| "Mins"
| "Min"
| "M"
| "Seconds"
| "Second"
| "Secs"
| "Sec"
| "s"
| "Milliseconds"
| "Millisecond"
| "Msecs"
| "Msec"
| "Ms";

type UnitAnyCase = Unit | Uppercase<Unit> | Lowercase<Unit>;

export type StringValue = `${number}` | `${number}${UnitAnyCase}` | `${number} ${UnitAnyCase}` | string;

interface Options {
/**
* Set to `true` to use verbose formatting. Defaults to `false`.
*/
long?: boolean;
}

/**
* Parse or format the given value.
*
* @param value - The string or number to convert
* @param options - Options for the conversion
* @throws Error if `value` is not a non-empty string or a number
*/
function msFn(value: StringValue, options?: Options): number;
function msFn(value: number, options?: Options): string;
function msFn(value: StringValue | number, options?: Options): number | string {
try {
if (typeof value === "string" && value.length > 0) {
return parse(value);
} else if (typeof value === "number" && isFinite(value)) {
return options?.long ? fmtLong(value) : fmtShort(value);
}
throw new Error("Value is not a string or number.");
} catch (error) {
const message = isError(error)
? `${error.message}. value=${JSON.stringify(value)}`
: "An unknown error has occurred.";
throw new Error(message);
}
}

/**
* Parse the given string and return milliseconds.
*
* @param str - A string to parse to milliseconds
* @returns The parsed value in milliseconds, or `NaN` if the string can't be
* parsed
*/
function parse(str: string): number {
if (str.length > 100) {
throw new Error("Value exceeds the maximum length of 100 characters.");
}
const match =
/^(?<value>-?(?:\d+)?\.?\d+) *(?<type>milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|months?|mo?|years?|yrs?|y)?$/i.exec(
str,
);
// Named capture groups need to be manually typed today.
// https://github.com/microsoft/TypeScript/issues/32098
const groups = match?.groups as { value: string; type?: string } | undefined;
if (!groups) {
return NaN;
}
const n = parseFloat(groups.value);
const type = (groups.type || "ms").toLowerCase() as Lowercase<Unit>;
switch (type) {
case "years":
case "year":
case "yrs":
case "yr":
case "y":
return n * y;
case "months":
case "month":
case "mo":
return n * mo;
case "weeks":
case "week":
case "w":
return n * w;
case "days":
case "day":
case "d":
return n * d;
case "hours":
case "hour":
case "hrs":
case "hr":
case "h":
return n * h;
case "minutes":
case "minute":
case "mins":
case "min":
case "m":
return n * m;
case "seconds":
case "second":
case "secs":
case "sec":
case "s":
return n * s;
case "milliseconds":
case "millisecond":
case "msecs":
case "msec":
case "ms":
return n;
default:
// This should never occur.
throw new Error(`The unit ${type as string} was matched, but no matching case exists.`);
}
}

/**
* Parse the given StringValue and return milliseconds.
*
* @param value - A typesafe StringValue to parse to milliseconds
* @returns The parsed value in milliseconds, or `NaN` if the string can't be
* parsed
*/
export function parseStrict(value: StringValue): number {
return parse(value);
}

// eslint-disable-next-line import/no-default-export
export default msFn;

/**
* Short format for `ms`.
*/
function fmtShort(ms: number): StringValue {
const msAbs = Math.abs(ms);
if (msAbs >= d) {
return `${Math.round(ms / d)}d`;
}
if (msAbs >= h) {
return `${Math.round(ms / h)}h`;
}
if (msAbs >= m) {
return `${Math.round(ms / m)}m`;
}
if (msAbs >= s) {
return `${Math.round(ms / s)}s`;
}
return `${ms}ms`;
}

/**
* Long format for `ms`.
*/
function fmtLong(ms: number): StringValue {
const msAbs = Math.abs(ms);
if (msAbs >= d) {
return plural(ms, msAbs, d, "day");
}
if (msAbs >= h) {
return plural(ms, msAbs, h, "hour");
}
if (msAbs >= m) {
return plural(ms, msAbs, m, "minute");
}
if (msAbs >= s) {
return plural(ms, msAbs, s, "second");
}
return `${ms} ms`;
}

/**
* Format the given integer as a string.
*
* @param ms - milliseconds
* @param options - Options for the conversion
* @returns The formatted string
*/
export function format(ms: number, options?: Options): string {
if (typeof ms !== "number" || !isFinite(ms)) {
throw new Error("Value provided to ms.format() must be of type number.");
}
return options?.long ? fmtLong(ms) : fmtShort(ms);
}

/**
* Pluralization helper.
*/
function plural(ms: number, msAbs: number, n: number, name: string): StringValue {
const isPlural = msAbs >= n * 1.5;
return `${Math.round(ms / n)} ${name}${isPlural ? "s" : ""}` as StringValue;
}

/**
* A type guard for errors.
*
* @param value - The value to test
* @returns A boolean `true` if the provided value is an Error-like object
*/
function isError(value: unknown): value is Error {
return typeof value === "object" && value !== null && "message" in value;
}
4 changes: 4 additions & 0 deletions src/interactions/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@ export * as Cases from "./moderation/cases.js";
export * as RemoveCase from "./moderation/removeCase.js";
export * as EditCase from "./moderation/editCase.js";
export * as CreateCase from "./moderation/createCase.js";
export * as Warn from "./moderation/warn.js";
export * as Mute from "./moderation/mute.js";
export * as Kick from "./moderation/kick.js";
export * as Ban from "./moderation/ban.js";
Loading

0 comments on commit 054aacd

Please sign in to comment.