Skip to content

Commit

Permalink
feat: Add a rule parameter to the /warn command.
Browse files Browse the repository at this point in the history
  • Loading branch information
vxern committed Dec 10, 2023
1 parent 573a106 commit a96e0af
Show file tree
Hide file tree
Showing 8 changed files with 129 additions and 28 deletions.
5 changes: 5 additions & 0 deletions assets/localisations/commands/eng-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,11 @@
"timeout.strings.timeoutCleared.description": "User {user_mention} is no longer timed out.",
"warn.name": "warn",
"warn.description": "Warns a user.",
"warn.options.rule.name": "rule",
"warn.options.rule.description": "The rule to cite as having been broken.",
"warn.options.rule.strings.other": "Other (justified in reason)",
"warn.strings.invalidRule.title": "Invalid rule",
"warn.strings.invalidRule.description": "The rule you specified is invalid.",
"warn.strings.failed.title": "Failed to warn user",
"warn.strings.failed.description": "Due to unknown reasons, warning the specified user failed.",
"warn.strings.warned.title": "User warned",
Expand Down
5 changes: 4 additions & 1 deletion assets/localisations/rules/eng-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,8 @@

"rules.adherence.title": "Adherence",
"rules.adherence.summary": "Respect the rules.",
"rules.adherence.content": "For members who show no regard for the server rules, and are not interested in making useful contributions, a permanent ban may be issued."
"rules.adherence.content": "For members who show no regard for the server rules, and are not interested in making useful contributions, a permanent ban may be issued.",

"rules.other.title": "Other",
"rules.other.summary": "Justified in the reason."
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"name": "logos",
"description": "A multi-purpose community bot built to cater to language-learning communities on Discord.",
"license": "ISC",
"version": "3.36.0",
"version": "3.37.0",
"type": "module",
"keywords": [
"discord",
Expand Down
6 changes: 5 additions & 1 deletion src/lib/commands/information/commands/list/warnings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Client, autocompleteMembers, localise, resolveInteractionToMember } fro
import { User } from "../../../../database/user";
import { Warning } from "../../../../database/warning";
import { parseArguments, reply } from "../../../../interactions";
import { getRuleTitleFormatted, rules } from "../../../moderation/commands/rule";

async function handleDisplayWarningsAutocomplete(
[client, bot]: [Client, Discord.Bot],
Expand Down Expand Up @@ -184,7 +185,10 @@ function getWarningPage(
relative_timestamp: timestamp(warning.createdAt),
});

return { name: `${constants.symbols.warn} ${warningString}`, value: `*${warning.reason}*` };
const ruleIndex = rules.findIndex((rule) => rule === warning.rule);
const ruleTitle = getRuleTitleFormatted(client, warning.rule ?? "other", ruleIndex, "option", { locale });

return { name: warningString, value: `${ruleTitle}\n> *${warning.reason}*` };
}),
color: constants.colors.blue,
};
Expand Down
17 changes: 9 additions & 8 deletions src/lib/commands/moderation/commands/rule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import constants from "../../../../constants/constants";
import { Locale } from "../../../../constants/languages";
import * as Logos from "../../../../types";
import { Client, localise } from "../../../client";
import { Guild } from "../../../database/guild";
import { getShowButton, parseArguments, reply, respond } from "../../../interactions";
import { CommandTemplate } from "../../command";
import { show } from "../../parameters";
import { Rule } from "../../../database/warning";
import { Guild } from "../../../database/guild";

const command: CommandTemplate = {
name: "rule",
Expand All @@ -26,14 +27,12 @@ const command: CommandTemplate = {
],
};

const ruleIds = ["behaviour", "quality", "relevance", "suitability", "exclusivity", "adherence"] as const;
const rules: Rule[] = ["behaviour", "quality", "relevance", "suitability", "exclusivity", "adherence"];

async function handleCiteRuleAutocomplete(
[client, bot]: [Client, Discord.Bot],
interaction: Logos.Interaction,
): Promise<void> {
const locale = interaction.locale;

const guildId = interaction.guildId;
if (guildId === undefined) {
return;
Expand All @@ -51,17 +50,19 @@ async function handleCiteRuleAutocomplete(
return;
}

const configuration = guildDocument.features.moderation.features?.rules;
const configuration = guildDocument.features.moderation.features?.warns;
if (configuration === undefined || !configuration.enabled) {
return;
}

const locale = interaction.locale;

const [{ rule: ruleOrUndefined }] = parseArguments(interaction.data?.options, {});
const ruleQueryRaw = ruleOrUndefined ?? "";

const ruleQueryTrimmed = ruleQueryRaw.trim();
const ruleQueryLowercase = ruleQueryTrimmed.toLowerCase();
const choices = ruleIds
const choices = rules
.map((ruleId, index) => {
return {
name: getRuleTitleFormatted(client, ruleId, index, "option", { locale }),
Expand All @@ -86,7 +87,7 @@ async function handleCiteRule([client, bot]: [Client, Discord.Bot], interaction:
const show = interaction.show ?? showParameter;
const locale = show ? interaction.guildLocale : interaction.locale;

const ruleId = ruleIds.at(ruleIndex);
const ruleId = rules.at(ruleIndex);
if (ruleId === undefined) {
displayError([client, bot], interaction, { locale: interaction.locale });
return;
Expand Down Expand Up @@ -175,4 +176,4 @@ async function displayError(
}

export default command;
export { ruleIds };
export { rules, getRuleTitleFormatted };
106 changes: 95 additions & 11 deletions src/lib/commands/moderation/commands/warn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,35 +8,105 @@ import { Client, autocompleteMembers, localise, pluralise, resolveInteractionToM
import { timeStructToMilliseconds } from "../../../database/guild";
import { Guild } from "../../../database/guild";
import { User } from "../../../database/user";
import { Warning } from "../../../database/warning";
import { Rule, Warning } from "../../../database/warning";
import diagnostics from "../../../diagnostics";
import { parseArguments, reply } from "../../../interactions";
import { parseArguments, reply, respond } from "../../../interactions";
import { CommandTemplate } from "../../command";
import { reason, user } from "../../parameters";
import { getActiveWarnings } from "../module";
import { getRuleTitleFormatted, rules } from "./rule";
import components from "../../../../constants/types/components";

const command: CommandTemplate = {
name: "warn",
type: Discord.ApplicationCommandTypes.ChatInput,
defaultMemberPermissions: ["MODERATE_MEMBERS"],
handle: handleWarnUser,
handleAutocomplete: handleWarnUserAutocomplete,
options: [user, reason],
options: [
user,
{
name: "rule",
type: Discord.ApplicationCommandOptionTypes.String,
required: true,
autocomplete: true,
},
reason,
],
};

async function handleWarnUserAutocomplete(
[client, bot]: [Client, Discord.Bot],
interaction: Logos.Interaction,
): Promise<void> {
const [{ user }] = parseArguments(interaction.data?.options, {});
if (user === undefined) {
const guildId = interaction.guildId;
if (guildId === undefined) {
return;
}

autocompleteMembers([client, bot], interaction, user, {
restrictToNonSelf: true,
excludeModerators: true,
});
const session = client.database.openSession();

const guildDocument =
client.cache.documents.guilds.get(guildId.toString()) ??
(await session.load<Guild>(`guilds/${guildId}`).then((value) => value ?? undefined));

session.dispose();

if (guildDocument === undefined) {
return;
}

const configuration = guildDocument.features.moderation.features?.warns;
if (configuration === undefined || !configuration.enabled) {
return;
}

const [{ user, rule: ruleOrUndefined }, focused] = parseArguments(interaction.data?.options, {});

if (focused?.name === "user") {
if (user === undefined) {
return;
}

autocompleteMembers([client, bot], interaction, user, {
restrictToNonSelf: true,
excludeModerators: true,
});

return;
}

if (focused?.name === "rule") {
if (ruleOrUndefined === undefined) {
return;
}

const locale = interaction.locale;

const strings = {
other: localise(client, "warn.options.rule.strings.other", locale)(),
};

const ruleQueryRaw = ruleOrUndefined ?? "";

const ruleQueryTrimmed = ruleQueryRaw.trim();
const ruleQueryLowercase = ruleQueryTrimmed.toLowerCase();
const choices = [
...rules
.map((rule, index) => {
return {
name: getRuleTitleFormatted(client, rule, index, "option", { locale }),
value: rule,
};
})
.filter((choice) => choice.name.toLowerCase().includes(ruleQueryLowercase)),
{ name: strings.other, value: components.none },
];

respond([client, bot], interaction, choices);

return;
}
}

async function handleWarnUser([client, bot]: [Client, Discord.Bot], interaction: Logos.Interaction): Promise<void> {
Expand Down Expand Up @@ -65,8 +135,21 @@ async function handleWarnUser([client, bot]: [Client, Discord.Bot], interaction:
return;
}

const [{ user: userSearchQuery, reason }] = parseArguments(interaction.data?.options, {});
if (userSearchQuery === undefined || reason === undefined) {
const [{ user: userSearchQuery, rule, reason }] = parseArguments(interaction.data?.options, {});
if (userSearchQuery === undefined || rule === undefined || reason === undefined) {
return;
}

if (rule !== components.none && !(rules as string[]).includes(rule)) {
const strings = {
title: localise(client, "warn.strings.invalidRule.title", locale)(),
description: localise(client, "warn.strings.invalidRule.description", locale)(),
};

reply([client, bot], interaction, {
embeds: [{ title: strings.title, description: strings.description, color: constants.colors.red }],
});

return;
}

Expand Down Expand Up @@ -160,6 +243,7 @@ async function handleWarnUser([client, bot]: [Client, Discord.Bot], interaction:
id: `warnings/${targetDocument.account.id}/${authorDocument.account.id}/${createdAt}`,
authorId: authorDocument.account.id,
targetId: targetDocument.account.id,
rule: rule === components.none ? undefined : (rule as Rule),
reason,
createdAt,
"@metadata": { "@collection": "Warnings" },
Expand Down
6 changes: 5 additions & 1 deletion src/lib/database/warning.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
type Rule = "behaviour" | "quality" | "relevance" | "suitability" | "exclusivity" | "adherence";

interface Warning {
id: string;
authorId: string;
targetId: string;
/** @since v3.37.0 */
rule?: Rule;
reason: string;
createdAt: number;
}

export type { Warning };
export type { Warning, Rule };
10 changes: 5 additions & 5 deletions src/lib/services/notices/types/information.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as Discord from "@discordeno/bot";
import constants from "../../../../constants/constants";
import { Client, localise } from "../../../client";
import { ruleIds } from "../../../commands/moderation/commands/rule";
import { rules } from "../../../commands/moderation/commands/rule";
import { HashableMessageContents, NoticeService } from "../service";

class InformationNoticeService extends NoticeService<"information"> {
Expand All @@ -20,12 +20,12 @@ class InformationNoticeService extends NoticeService<"information"> {
}

const guildLocale = this.guildLocale;
const informationFields = ruleIds.map((ruleId, index) => {
const informationFields = rules.map((rule, index) => {
const strings = {
title: localise(this.client, `rules.${ruleId}.title`, guildLocale)(),
title: localise(this.client, `rules.${rule}.title`, guildLocale)(),
tldr: localise(this.client, "rules.tldr", guildLocale)(),
summary: localise(this.client, `rules.${ruleId}.summary`, guildLocale)(),
content: localise(this.client, `rules.${ruleId}.content`, guildLocale)(),
summary: localise(this.client, `rules.${rule}.summary`, guildLocale)(),
content: localise(this.client, `rules.${rule}.content`, guildLocale)(),
};

return {
Expand Down

0 comments on commit a96e0af

Please sign in to comment.