aboutsummaryrefslogtreecommitdiff
path: root/lib/extensions/discord-akairo/BotCommandHandler.ts
blob: e9b509f97d7789ff9c14b5c8f395005e9e40481a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import type { BotCommand, CommandMessage, SlashMessage } from '#lib';
import { CommandHandler, CommandHandlerEvents, type Category, type CommandHandlerOptions } from 'discord-akairo';
import { GuildMember, PermissionResolvable, type Collection, type Message, type PermissionsString } from 'discord.js';
import { CommandHandlerEvent } from '../../utils/Constants.js';

export type CustomCommandHandlerOptions = CommandHandlerOptions;

export interface BotCommandHandlerEvents extends CommandHandlerEvents {
	commandBlocked: [message: CommandMessage, command: BotCommand, reason: string];
	commandBreakout: [message: CommandMessage, command: BotCommand, /* no util */ breakMessage: Message];
	commandCancelled: [message: CommandMessage, command: BotCommand, /* no util */ retryMessage?: Message];
	commandFinished: [message: CommandMessage, command: BotCommand, args: any, returnValue: any];
	commandInvalid: [message: CommandMessage, command: BotCommand];
	commandLocked: [message: CommandMessage, command: BotCommand];
	commandStarted: [message: CommandMessage, command: BotCommand, args: any];
	cooldown: [message: CommandMessage | SlashMessage, command: BotCommand, remaining: number];
	error: [error: Error, message: /* no util */ Message, command?: BotCommand];
	inPrompt: [message: /* no util */ Message];
	load: [command: BotCommand, isReload: boolean];
	messageBlocked: [message: /* no util */ Message | CommandMessage | SlashMessage, reason: string];
	messageInvalid: [message: CommandMessage];
	missingPermissions: [
		message: CommandMessage,
		command: BotCommand,
		type: 'client' | 'user',
		// fix: this is jank
		missing: (PermissionsString | '[[UnsupportedChannel]]')[]
	];
	remove: [command: BotCommand];
	slashBlocked: [message: SlashMessage, command: BotCommand, reason: string];
	slashError: [error: Error, message: SlashMessage, command: BotCommand];
	slashFinished: [message: SlashMessage, command: BotCommand, args: any, returnValue: any];
	slashMissingPermissions: [
		message: SlashMessage,
		command: BotCommand,
		type: 'client' | 'user',
		// fix: this is jank
		missing: (PermissionsString | '[[UnsupportedChannel]]')[]
	];
	slashStarted: [message: SlashMessage, command: BotCommand, args: any];
}

export class BotCommandHandler extends CommandHandler {
	public declare modules: Collection<string, BotCommand>;
	public declare categories: Collection<string, Category<string, BotCommand>>;

	//! this is a simplified version of the original
	public override async runPermissionChecks(
		message: Message | SlashMessage,
		command: BotCommand,
		slash: boolean = false
	): Promise<boolean> {
		const event = slash ? CommandHandlerEvent.SlashMissingPermissions : CommandHandlerEvent.MissingPermissions;

		const appSlashPerms = slash ? (message as SlashMessage).interaction.appPermissions : null;
		const userSlashPerms = slash ? (message as SlashMessage).interaction.memberPermissions : null;

		const noPerms = message.channel == null || (message.channel.isThread() && message.channel.parent == null);

		if (message.inGuild()) {
			if (noPerms && command.clientCheckChannel && appSlashPerms == null) {
				this.emit(event, message, command, 'client', ['[[UnsupportedChannel]]']);
				return true;
			}
			if (message.channel?.isDMBased()) return false;

			const missing = command.clientCheckChannel
				? (appSlashPerms ?? message.channel?.permissionsFor(message.guild.members.me!))?.missing(command.clientPermissions)
				: message.guild?.members.me?.permissions.missing(command.clientPermissions);

			if (missing?.length) {
				this.emit(event, message, command, 'client', missing);
				return true;
			}
		}

		if (command.userPermissions) {
			// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
			const ignorer = command.ignorePermissions || this.ignorePermissions;
			const isIgnored = Array.isArray(ignorer)
				? ignorer.includes(message.author.id)
				: typeof ignorer === 'function'
				? ignorer(message, command)
				: message.author.id === ignorer;

			if (!isIgnored) {
				if (message.inGuild()) {
					if (noPerms && command.userCheckChannel && userSlashPerms == null) {
						this.emit(event, message, command, 'user', ['[[UnsupportedChannel]]']);
						return true;
					}
					if (message.channel?.isDMBased()) return false;

					const missing = command.userCheckChannel
						? (userSlashPerms ?? message.channel?.permissionsFor(message.author))?.missing(command.userPermissions)
						: message.member?.permissions.missing(command.userPermissions);

					if (missing?.length) {
						this.emit(event, message, command, 'user', missing);
						return true;
					}
				}
			}
		}

		return false;
	}
}

export interface BotCommandHandler extends CommandHandler {
	findCommand(name: string): BotCommand;
}

export function permissionCheck(
	message: CommandMessage | SlashMessage,
	check: GuildMember,
	perms: PermissionResolvable,
	useChannel: boolean
): boolean {
	if (message.inGuild()) {
		if (!message.channel || message.channel.isDMBased()) return true;

		const missing = useChannel ? message.channel.permissionsFor(check)?.missing(perms) : check.permissions.missing(perms);

		if (missing?.length) return false;
	}
	return true;
}