Skip to content

Commit e89f0e1

Browse files
feat: message reporting system (1/3)
1 parent 37b072a commit e89f0e1

File tree

9 files changed

+473
-27
lines changed

9 files changed

+473
-27
lines changed

src/framework/typescript/arguments/ArgumentParser.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ class ArgumentParser extends HasClient {
100100
argv,
101101
argTypes,
102102
subcommand,
103-
commandManager.commands.get(argv[0])?.subcommands ?? [],
103+
commandManager.getCommand(argv[0])?.subcommands ?? [],
104104
command.onSubcommandNotFound?.bind(command) ??
105105
Reflect.getMetadata("command:subcommand_not_found_error", command.constructor),
106106
context
@@ -180,12 +180,12 @@ class ArgumentParser extends HasClient {
180180
if (subcommand) {
181181
const commandManager = Application.current().service("commandManager");
182182
const canonicalName = commandManager.getCommand(argv[0])?.name ?? argv[0];
183-
const baseCommand = commandManager.commands.get(canonicalName);
183+
const baseCommand = commandManager.getCommand(canonicalName);
184184

185185
const command =
186186
baseCommand && baseCommand.isolatedSubcommands === false
187187
? baseCommand
188-
: commandManager.commands.get(`${canonicalName}::${args[0]}`);
188+
: commandManager.getCommand(`${canonicalName}::${args[0]}`);
189189

190190
if (!command) {
191191
if (typeof onSubcommandNotFound === "function" && context) {

src/framework/typescript/commands/Command.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ abstract class Command<T extends ContextType = ContextType.ChatInput | ContextTy
115115
/**
116116
* The supported contexts of the command.
117117
*/
118-
public readonly supportedContexts: T[] = [ContextType.Legacy, ContextType.ChatInput] as T[];
118+
public readonly supportedContexts: readonly T[] = [ContextType.Legacy, ContextType.ChatInput] as T[];
119119

120120
/**
121121
* Whether the command should be deferred.

src/main/typescript/automod/TriggerService.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,13 @@ class TriggerService extends Service implements HasEventListeners {
7575

7676
public onMessageCreate(message: Message<boolean>) {
7777
if (message.author.bot) {
78-
return false;
78+
return true;
7979
}
8080

8181
const config = this.config(message.guildId!);
8282

8383
if (!config?.enabled || config?.global_disabled_channels?.includes(message.channelId!)) {
84-
return false;
84+
return true;
8585
}
8686

8787
this.processMessageTriggers(message, config.triggers);

src/main/typescript/cache/CommandPermissionOverwriteCacheStore.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ class CommandPermissionOverwriteCacheStore extends GuildStore<
7373
const cached = this.makeCache(overwrite);
7474

7575
for (const command of overwrite.commands) {
76-
const actualName = this.commandManager.commands.get(command)?.name;
76+
const actualName = this.commandManager.getCommand(command)?.name;
7777

7878
if (!actualName) {
7979
continue;
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import type { Buildable } from "@framework/commands/Command";
2+
import { Command } from "@framework/commands/Command";
3+
import { ContextType } from "@framework/commands/ContextType";
4+
import type InteractionContext from "@framework/commands/InteractionContext";
5+
import { Inject } from "@framework/container/Inject";
6+
import { GatewayEventListener } from "@framework/events/GatewayEventListener";
7+
import { fetchMessage } from "@framework/utils/entities";
8+
import MessageReportingService from "@main/services/MessageReportingService";
9+
import type {
10+
GuildMember,
11+
GuildTextBasedChannel,
12+
Interaction,
13+
MessageContextMenuCommandInteraction
14+
} from "discord.js";
15+
import {
16+
ActionRowBuilder,
17+
ApplicationCommandType,
18+
ModalBuilder,
19+
TextInputBuilder,
20+
TextInputStyle
21+
} from "discord.js";
22+
23+
class ReportMessageCommand extends Command<ContextType.MessageContextMenu> {
24+
public override readonly name = "Report Message";
25+
public override readonly description: string = "Reports a message.";
26+
public override readonly usage = [""];
27+
public override readonly systemPermissions = [];
28+
public override readonly supportedContexts = [ContextType.MessageContextMenu] as const;
29+
30+
@Inject()
31+
private readonly messageReportingService!: MessageReportingService;
32+
33+
public override build(): Buildable[] {
34+
return [this.buildContextMenu().setType(ApplicationCommandType.Message)];
35+
}
36+
37+
public override async execute(
38+
context: InteractionContext<MessageContextMenuCommandInteraction>
39+
): Promise<void> {
40+
const reporter = context.member;
41+
42+
if (!reporter) {
43+
await context.error("Unable to compute permissions");
44+
return;
45+
}
46+
47+
if (!(await this.messageReportingService.canReport(reporter))) {
48+
await context.error({
49+
content: "You do not have permission to report messages.",
50+
ephemeral: true
51+
});
52+
53+
return;
54+
}
55+
56+
const modal = new ModalBuilder()
57+
.setCustomId(`report_message_${context.commandMessage.targetId}`)
58+
.setTitle("Report Message")
59+
.addComponents(
60+
new ActionRowBuilder<TextInputBuilder>().addComponents(
61+
new TextInputBuilder()
62+
.setCustomId("reason")
63+
.setLabel("Reason")
64+
.setPlaceholder("Reason")
65+
.setRequired(false)
66+
.setStyle(TextInputStyle.Paragraph)
67+
)
68+
);
69+
70+
await context.commandMessage.showModal(modal);
71+
}
72+
73+
@GatewayEventListener("interactionCreate")
74+
public async onInteractionCreate(interaction: Interaction) {
75+
if (!interaction.isModalSubmit() || !interaction.customId.startsWith("report_message_")) {
76+
return;
77+
}
78+
79+
const reporter = interaction.member;
80+
81+
if (!reporter) {
82+
await interaction.reply({
83+
content: "Unable to compute permissions",
84+
ephemeral: true
85+
});
86+
87+
return;
88+
}
89+
90+
await interaction.deferReply({
91+
ephemeral: true
92+
});
93+
94+
const [, , messageId] = interaction.customId.split("_");
95+
const message = await fetchMessage(interaction.channel as GuildTextBasedChannel, messageId);
96+
97+
if (!message?.inGuild()) {
98+
await interaction.editReply({
99+
content: "Unable to retrieve message"
100+
});
101+
102+
return;
103+
}
104+
105+
const reason = interaction.fields.fields.some(f => f.customId === "reason")
106+
? interaction.fields.getTextInputValue("reason") || undefined
107+
: undefined;
108+
const result = await this.messageReportingService.report(
109+
message,
110+
reporter as GuildMember,
111+
reason
112+
);
113+
114+
if (result === false) {
115+
await interaction.editReply({
116+
content: "You do not have permission to report messages."
117+
});
118+
119+
return;
120+
}
121+
122+
if (result === null) {
123+
await interaction.editReply({
124+
content: "Unable to report message"
125+
});
126+
127+
return;
128+
}
129+
130+
await interaction.editReply({
131+
content: "Message reported successfully."
132+
});
133+
}
134+
}
135+
136+
export default ReportMessageCommand;

src/main/typescript/core/DiscordKernel.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ class DiscordKernel extends Kernel {
8787
"@services/AutoRoleService",
8888
"@automod/TriggerService",
8989
"@services/DirectiveParsingService",
90+
"@services/MessageReportingService",
9091
"@services/WelcomerService",
9192
"@services/AuthService",
9293
"@services/SnippetManagerService",

src/main/typescript/schemas/GuildConfigSchema.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -274,14 +274,13 @@ export const GuildConfigSchema = z.object({
274274
forced_embed_color: z.number().int().optional()
275275
})
276276
.optional(),
277-
auto_triggers: z
278-
.object({
279-
enabled: z.boolean().default(false),
280-
triggers: z.array(TriggerSchema).default([]),
281-
global_disabled_channels: z.array(zSnowflake).default([])
282-
})
283-
.optional(),
284-
/*
277+
auto_triggers: z
278+
.object({
279+
enabled: z.boolean().default(false),
280+
triggers: z.array(TriggerSchema).default([]),
281+
global_disabled_channels: z.array(zSnowflake).default([])
282+
})
283+
.optional(),
285284
message_reporting: z
286285
.object({
287286
enabled: z.boolean().default(false),
@@ -308,7 +307,9 @@ export const GuildConfigSchema = z.object({
308307
})
309308
.default({})
310309
})
311-
.optional(),
310+
.optional()
311+
/*
312+
312313
bump_reminder: z
313314
.object({
314315
enabled: z.boolean().optional(),

src/main/typescript/services/CommandManager.ts

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -162,26 +162,28 @@ class CommandManager extends Service implements CommandManagerServiceInterface {
162162
groups: Record<string, string> | null = null,
163163
defaultGroup = "default"
164164
) {
165-
const previousCommand = this.commands.get(command.name);
165+
const previousCommand = this.getCommand(command.name);
166166
let aliasGroupSet = false;
167167

168168
if (loadMetadata && previousCommand) {
169169
await this.application.classLoader.unloadEventsFromMetadata(previousCommand);
170170
}
171171

172-
this.commands.set(command.name, command);
172+
const loweredName = command.name.toLowerCase();
173+
this.commands.set(loweredName, command);
173174

174175
for (const alias of command.aliases) {
175-
this.commands.set(alias, command);
176+
const loweredAlias = alias.toLowerCase();
177+
this.commands.set(loweredAlias, command);
176178

177-
if (groups?.[alias] && !aliasGroupSet) {
178-
command.group = groups?.[alias];
179+
if (groups?.[loweredAlias] && !aliasGroupSet) {
180+
command.group = groups?.[loweredAlias];
179181
aliasGroupSet = true;
180182
}
181183
}
182184

183-
if (!aliasGroupSet || groups?.[command.name]) {
184-
command.group = groups?.[command.name] ?? defaultGroup;
185+
if (!aliasGroupSet || groups?.[loweredName]) {
186+
command.group = groups?.[loweredName] ?? defaultGroup;
185187
}
186188

187189
if (loadMetadata) {
@@ -233,7 +235,7 @@ class CommandManager extends Service implements CommandManagerServiceInterface {
233235
const argv = content.split(/ +/);
234236
const [rawCommandName, ...args] = argv;
235237
const commandName = rawCommandName.toLowerCase();
236-
const command = this.commands.get(commandName);
238+
const command = this.getCommand(commandName);
237239

238240
if (!command) {
239241
this.application.logger.debug(
@@ -274,7 +276,7 @@ class CommandManager extends Service implements CommandManagerServiceInterface {
274276
const key = command.isolatedSubcommands
275277
? `${this.getCanonicalName(commandName)}::${subcommandName}`
276278
: this.getCanonicalName(commandName);
277-
const subcommand = this.commands.get(key);
279+
const subcommand = this.getCommand(key);
278280

279281
if (subcommand && subcommand.isDisabled(message.guildId!)) {
280282
respondOnFail && (await context.error("This command is disabled."));
@@ -342,7 +344,7 @@ class CommandManager extends Service implements CommandManagerServiceInterface {
342344
}
343345

344346
const { commandName } = interaction;
345-
const baseCommand = this.commands.get(commandName);
347+
const baseCommand = this.getCommand(commandName);
346348

347349
if (!baseCommand || !baseCommand.supportsInteraction()) {
348350
return false;
@@ -352,7 +354,7 @@ class CommandManager extends Service implements CommandManagerServiceInterface {
352354
e => e.type === ApplicationCommandOptionType.Subcommand
353355
)?.name;
354356

355-
const command = this.commands.get(
357+
const command = this.getCommand(
356358
subcommand && baseCommand.isolatedSubcommands && baseCommand.hasSubcommands
357359
? `${commandName}::${subcommand}`
358360
: commandName

0 commit comments

Comments
 (0)