Skip to content

Commit

Permalink
refactor(decorators): recreate using better code
Browse files Browse the repository at this point in the history
  • Loading branch information
NedcloarBR committed Sep 1, 2024
1 parent d9db85f commit af331dd
Show file tree
Hide file tree
Showing 17 changed files with 363 additions and 107 deletions.
1 change: 0 additions & 1 deletion .vscode/Workspace.code-snippets
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@
"\t\ttestOnly: true,",
"\t\townerOnly: true,",
"\t})",
"\t@UseGuards(CommandConfigGuard, CommandPermissionsGuard)",
"\tpublic async onCommandRun(@Ctx() [interaction]: SlashCommandContext, @CurrentTranslate() t: TranslationFn) {",
"\t\t",
"\t}",
Expand Down
12 changes: 10 additions & 2 deletions src/common/decorators/CommandConfig.decorator.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import { Reflector } from "@nestjs/core";
import { SetMetadata, UseGuards, applyDecorators } from "@nestjs/common";
import { CommandConfigGuard } from "../guards";

export interface CommandConfigOptions {
category: string;
disable: boolean;
}

export const CommandConfig = Reflector.createDecorator<CommandConfigOptions>();
export const CommandConfigKey = "discord::command::__config__";

export const CommandConfig = (options: CommandConfigOptions) => {
return applyDecorators(
SetMetadata(CommandConfigKey, options),
UseGuards(CommandConfigGuard),
);
};
16 changes: 12 additions & 4 deletions src/common/decorators/CommandPermissions.decorator.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
import { Reflector } from "@nestjs/core";
import { PermissionResolvable } from "discord.js";
import { SetMetadata, UseGuards, applyDecorators } from "@nestjs/common";
import type { PermissionResolvable } from "discord.js";
import { CommandPermissionsGuard } from "../guards";

export interface CommandPermissionsOptions {
user: PermissionResolvable[];
bot: PermissionResolvable[];
guildOnly: boolean;
testOnly: boolean;
ownerOnly: boolean;
guilds?: string[];
guilds?: string[];
}

export const CommandPermissions = Reflector.createDecorator<CommandPermissionsOptions>();
export const CommandPermissionsKey = "discord::command::__permissions__";

export const CommandPermissions = (options: CommandPermissionsOptions) => {
return applyDecorators(
SetMetadata(CommandPermissionsKey, options),
UseGuards(CommandPermissionsGuard),
);
};
7 changes: 6 additions & 1 deletion src/common/decorators/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
export {
CommandPermissions,
CommandPermissionsOptions,
CommandPermissionsKey,
} from "./CommandPermissions.decorator";
export { CommandConfig, CommandConfigOptions } from "./CommandConfig.decorator";
export {
CommandConfig,
CommandConfigOptions,
CommandConfigKey,
} from "./CommandConfig.decorator";
export { ValidatedOptions } from "./ValidatedOptions.decorator";
33 changes: 26 additions & 7 deletions src/common/guards/CommandConfig.guard.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,44 @@
import { LOCALIZATION_ADAPTER, type NestedLocalizationAdapter } from "@necord/localization";
import { type CanActivate, type ExecutionContext, Inject, Injectable } from "@nestjs/common";
import {
LOCALIZATION_ADAPTER,
type NestedLocalizationAdapter,
} from "@necord/localization";
import {
type CanActivate,
type ExecutionContext,
Inject,
Injectable,
} from "@nestjs/common";
import { Reflector } from "@nestjs/core";
import type { ChatInputCommandInteraction } from "discord.js";
import { NecordExecutionContext } from "necord";
import { CommandConfig } from "../decorators";
import { CommandConfigKey } from "../decorators";

@Injectable()
export class CommandConfigGuard implements CanActivate {
public constructor(
@Inject(LOCALIZATION_ADAPTER) private readonly translate: NestedLocalizationAdapter,
@Inject(LOCALIZATION_ADAPTER)
private readonly translate: NestedLocalizationAdapter,
private readonly reflector: Reflector,
) {}

public async canActivate(executionContext: ExecutionContext): Promise<boolean> {
const commandConfig = this.reflector.get(CommandConfig.KEY, executionContext.getHandler());
public async canActivate(
executionContext: ExecutionContext,
): Promise<boolean> {
const commandConfig = this.reflector.get(
CommandConfigKey,
executionContext.getHandler(),
);
const context = NecordExecutionContext.create(executionContext);
const args = context.getArgByIndex(0);
const interaction = args[0] as ChatInputCommandInteraction;

if (commandConfig.disable) {
interaction.reply(this.translate.getTranslation("Tools.Command.Checker.Disable", interaction.guildLocale));
interaction.reply(
this.translate.getTranslation(
"Tools.Command.Checker.Disable",
interaction.guildLocale,
),
);
return false;
}

Expand Down
76 changes: 58 additions & 18 deletions src/common/guards/CommandPermissions.guard.ts
Original file line number Diff line number Diff line change
@@ -1,55 +1,91 @@
import { CommandPermissions } from "@/common/decorators";
import type { Config } from "@/modules/config/types";
import { formatArray } from "@/utils/Tools";
import { LOCALIZATION_ADAPTER, NestedLocalizationAdapter } from "@necord/localization";
import { type CanActivate, type ExecutionContext, Inject, Injectable } from "@nestjs/common";
import {
LOCALIZATION_ADAPTER,
NestedLocalizationAdapter,
} from "@necord/localization";
import {
type CanActivate,
type ExecutionContext,
Inject,
Injectable,
} from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { Reflector } from "@nestjs/core";
import type { ChatInputCommandInteraction } from "discord.js";
import { NecordExecutionContext } from "necord";
import { CommandPermissionsKey } from "../decorators";

@Injectable()
export class CommandPermissionsGuard implements CanActivate {
public constructor(
@Inject(LOCALIZATION_ADAPTER) private readonly translate: NestedLocalizationAdapter,
@Inject(LOCALIZATION_ADAPTER)
private readonly translate: NestedLocalizationAdapter,
private readonly reflector: Reflector,
private readonly config: ConfigService,
) {}

public async canActivate(executionContext: ExecutionContext): Promise<boolean> {
const permissions = this.reflector.get(CommandPermissions.KEY, executionContext.getHandler());
public async canActivate(
executionContext: ExecutionContext,
): Promise<boolean> {
const permissions = this.reflector.get(
CommandPermissionsKey,
executionContext.getHandler(),
);
const context = NecordExecutionContext.create(executionContext);
const args = context.getArgByIndex(0);
const interaction = args[0] as ChatInputCommandInteraction;

if (permissions.bot) {
if (!interaction.guild.members.me.permissions.has(permissions.bot)) {
interaction.reply(
this.translate.getTranslation("Tools.Commands.Permission.Bot", interaction.guildLocale, {
PERMS: formatArray(permissions.bot as string[]),
}),
this.translate.getTranslation(
"Tools.Commands.Permission.Bot",
interaction.guildLocale,
{
PERMS: formatArray(permissions.bot as string[]),
},
),
);
return false;
}
}

if (permissions.user) {
if (!(await interaction.guild.members.fetch(interaction.user.id)).permissions.has(permissions.user)) {
if (
!(
await interaction.guild.members.fetch(interaction.user.id)
).permissions.has(permissions.user)
) {
interaction.reply(
this.translate.getTranslation("Tools.Commands.Permission.User", interaction.guildLocale, {
PERMS: formatArray(permissions.user as string[]),
}),
this.translate.getTranslation(
"Tools.Commands.Permission.User",
interaction.guildLocale,
{
PERMS: formatArray(permissions.user as string[]),
},
),
);
}
}

if (permissions.guildOnly && !this.checkGuild(interaction.guildId)) {
interaction.reply(this.translate.getTranslation("Tools.Command.Checker.GuildOnly", interaction.guildLocale));
interaction.reply(
this.translate.getTranslation(
"Tools.Command.Checker.GuildOnly",
interaction.guildLocale,
),
);
return false;
}

if (permissions.ownerOnly && !this.checkOwner(interaction.user.id)) {
interaction.reply(this.translate.getTranslation("Tools.Command.Checker.OwnerOnly", interaction.guildLocale));
interaction.reply(
this.translate.getTranslation(
"Tools.Command.Checker.OwnerOnly",
interaction.guildLocale,
),
);
return false;
}

Expand All @@ -58,12 +94,16 @@ export class CommandPermissionsGuard implements CanActivate {

private checkGuild(target: string): boolean {
return (
this.config.getOrThrow<Config["Discord"]>("Discord").Servers.NDCommunity === target ||
this.config.getOrThrow<Config["Discord"]>("Discord").Servers.TestGuild === target
this.config.getOrThrow<Config["Discord"]>("Discord").Servers
.NDCommunity === target ||
this.config.getOrThrow<Config["Discord"]>("Discord").Servers.TestGuild ===
target
);
}

private checkOwner(target: string) {
return this.config.getOrThrow<Config["Discord"]>("Discord").Client.Owners.includes(target);
return this.config
.getOrThrow<Config["Discord"]>("Discord")
.Client.Owners.includes(target);
}
}
6 changes: 3 additions & 3 deletions src/modules/commands/Commands.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {
CommandPermissions,
CommandPermissionsKey,
type CommandPermissionsOptions,
} from "@/common/decorators";
import {
Expand Down Expand Up @@ -50,7 +50,7 @@ export class CommandsService implements OnApplicationBootstrap {
this.slashCommandService.remove(command.getName());

const perms: CommandPermissionsOptions = this.reflector.get(
CommandPermissions.KEY,
CommandPermissionsKey,
command.getHandler(),
);
const guilds = [];
Expand Down Expand Up @@ -86,7 +86,7 @@ export class CommandsService implements OnApplicationBootstrap {
this.slashCommandService.remove(command.getName());

const perms: CommandPermissionsOptions = this.reflector.get(
CommandPermissions.KEY,
CommandPermissionsKey,
command.getHandler(),
);
const guilds = [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,14 @@ export class HelloCommand {
name: "hello_world",
description: "a simple hello",
})
@CommandConfig({ category: "🛠️ Developer Tools", disable: false })
@CommandConfig({ category: "🛠️ Developer Tools", disable: true })
@CommandPermissions({
user: [],
bot: [],
guildOnly: false,
testOnly: false,
ownerOnly: true,
})
@UseGuards(CommandConfigGuard, CommandPermissionsGuard)
public async onCommandRun(
@Ctx() [interaction]: SlashCommandContext,
@CurrentTranslate() t: TranslationFn,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import { inspect } from "node:util";
import { CommandConfig, CommandPermissions } from "@/common/decorators";
import { CommandConfigGuard, CommandPermissionsGuard } from "@/common/guards";
import type { Config } from "@/modules/config/types";
import { CurrentTranslate, type TranslationFn, localizationMapByKey } from "@necord/localization";
import {
CurrentTranslate,
type TranslationFn,
localizationMapByKey,
} from "@necord/localization";
import { UseGuards } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { EmbedBuilder, codeBlock } from "discord.js";
Expand All @@ -17,9 +21,12 @@ export class EvalCommand {

@Subcommand({
name: "evaluate",
description: "Evaluate some codes to test it without restart the bot every time",
description:
"Evaluate some codes to test it without restart the bot every time",
nameLocalizations: localizationMapByKey("DeveloperTools.eval.name"),
descriptionLocalizations: localizationMapByKey("DeveloperTools.eval.description"),
descriptionLocalizations: localizationMapByKey(
"DeveloperTools.eval.description",
),
})
@CommandConfig({
category: "🛠️ Developer Tools",
Expand All @@ -32,14 +39,17 @@ export class EvalCommand {
testOnly: true,
ownerOnly: true,
})
@UseGuards(CommandConfigGuard, CommandPermissionsGuard)
public async onCommandRun(
@Ctx() [interaction]: SlashCommandContext,
@Options() { code }: EvalDTO,
@CurrentTranslate() t: TranslationFn,
) {
try {
if (this.config.getOrThrow<Config["EvalBadKeys"]>("EvalBadKeys").some((key) => code.includes(key))) {
if (
this.config
.getOrThrow<Config["EvalBadKeys"]>("EvalBadKeys")
.some((key) => code.includes(key))
) {
return await interaction.reply({
content: t("DeveloperTools.eval.BadKey"),
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ export class TestCommand {
testOnly: true,
ownerOnly: true,
})
@UseGuards(CommandConfigGuard, CommandPermissionsGuard)
public async onCommandRun(
@Ctx() [interaction]: SlashCommandContext,
@CurrentTranslate() t: TranslationFn,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { CommandConfig, CommandPermissions } from "@/common/decorators";
import { CommandConfigGuard, CommandPermissionsGuard } from "@/common/guards";
import { WAIT } from "@/utils/Tools";
import { CurrentTranslate, TranslationFn, localizationMapByKey } from "@necord/localization";
import {
CurrentTranslate,
TranslationFn,
localizationMapByKey,
} from "@necord/localization";
import { Logger, UseGuards } from "@nestjs/common";
import { channelMention } from "discord.js";
import { Ctx, Options, SlashCommandContext, Subcommand } from "necord";
Expand All @@ -16,7 +20,9 @@ export class ClearCommand {
name: "clear",
description: "Clear a number of messages in the selected channel",
nameLocalizations: localizationMapByKey("Moderation.clear.name"),
descriptionLocalizations: localizationMapByKey("Moderation.clear.description"),
descriptionLocalizations: localizationMapByKey(
"Moderation.clear.description",
),
})
@CommandConfig({ category: "🛡️ Moderation", disable: false })
@CommandPermissions({
Expand All @@ -26,7 +32,6 @@ export class ClearCommand {
testOnly: true,
ownerOnly: false,
})
@UseGuards(CommandConfigGuard, CommandPermissionsGuard)
public async OnCommandRun(
@Ctx() [interaction]: SlashCommandContext,
@Options() { amount, channel }: ClearDTO,
Expand All @@ -41,7 +46,10 @@ export class ClearCommand {
try {
channel.bulkDelete(fetched);
const res = await interaction.reply({
content: t("Moderation.clear.response.success", { amount, channel: channelMention(channel.id) }),
content: t("Moderation.clear.response.success", {
amount,
channel: channelMention(channel.id),
}),
ephemeral: false,
});
await WAIT(4000);
Expand Down
Loading

0 comments on commit af331dd

Please sign in to comment.