aboutsummaryrefslogtreecommitdiff
path: root/src/commands/utilities/whoHasRole.ts
blob: 851411af56bdd8f847e329f3462df89838292035 (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
import {
	BushCommand,
	ButtonPaginator,
	chunk,
	clientSendAndPermCheck,
	colors,
	emojis,
	OptArgType,
	oxford,
	type CommandMessage,
	type SlashMessage
} from '#lib';
import assert from 'assert';
import { ApplicationCommandOptionType, escapeMarkdown, type CommandInteraction, type Role } from 'discord.js';

export default class WhoHasRoleCommand extends BushCommand {
	public constructor() {
		super('whoHasRole', {
			aliases: ['who-has-role', 'whr', 'dump'],
			category: 'utilities',
			description: 'Allows you to view what users have a certain role.',
			usage: ['who-has-role <...roles>'],
			examples: ['who-has-role admin'],
			args: new Array(25).fill(0).map(
				(_, i) =>
					({
						id: `role${i + 1}`,
						description: i === 0 ? 'The role to find the users of.' : 'Another role that the user must have.',
						type: 'role',
						prompt: i === 0 ? 'What role would you like to find the users of?' : 'What other role should the user also have?',
						retry: '{error} Choose a valid role.',
						slashType: ApplicationCommandOptionType.Role,
						optional: i !== 0
					} as const)
			),
			slash: true,
			channel: 'guild',
			clientPermissions: (m) => clientSendAndPermCheck(m),
			userPermissions: [],
			typing: true
		});
	}

	public override async exec(
		message: CommandMessage | SlashMessage,
		args: {
			[K in `role${NumberRange}`]: OptArgType<'role'>;
		}
	) {
		assert(message.inGuild());
		if (message.util.isSlash) await (message.interaction as CommandInteraction).deferReply();

		const rawRoles = Object.values(args).filter((v) => v !== null) as Role[];
		const roles = rawRoles.map((v) => v.id);

		const members = message.guild.members.cache.filter((m) => roles.every((r) => m.roles.cache.has(r)));

		const roleMembers = members.map((member) => `${member.user} (${escapeMarkdown(member.user.tag)})`);
		const chunkedRoleMembers = chunk(roleMembers, 30);

		const title = `Members with ${
			roles.length < 4
				? oxford(
						rawRoles.map((r) => r.name),
						'and'
				  )
				: `${rawRoles.length} Roles`
		} [\`${members.size.toLocaleString()}\`]`;
		const color = colors.default;
		const embedPages = chunkedRoleMembers.map((chunk) => ({
			title,
			description: chunk.join('\n'),
			color
		}));

		if (embedPages.length === 0) {
			return await message.util.reply(`${emojis.error} No members found matching the given roles.`);
		}

		return await ButtonPaginator.send(message, embedPages, null, true);
	}
}

type Mapped<N extends number, Result extends Array<unknown> = []> = Result['length'] extends N
	? Result
	: Mapped<N, [...Result, Result['length']]>;

type NumberRange = Exclude<Mapped<25>[number], 0 | 1>;