blob: 41f34b981f2cbe2ce399d96616b54e9f89f308eb [file] [log] [blame]
Skyler Greyda16adf2023-03-05 10:22:12 +00001import { LoadingEmbed } from "./../../utils/defaults.js";
Skyler Grey75ea9172022-08-06 10:22:23 +01002import Discord, {
Skyler Grey75ea9172022-08-06 10:22:23 +01003 CommandInteraction,
4 GuildMember,
TheCodedProf21c08592022-09-13 14:14:43 -04005 ActionRowBuilder,
6 ButtonBuilder,
PineaFan1dee28f2023-01-16 22:09:07 +00007 ButtonStyle,
PineaFan0d06edc2023-01-17 22:10:31 +00008 NonThreadGuildBasedChannel,
9 StringSelectMenuOptionBuilder,
TheCodedProf4a6d5712023-01-19 15:54:40 -050010 StringSelectMenuBuilder,
11 APIMessageComponentEmoji
Skyler Grey75ea9172022-08-06 10:22:23 +010012} from "discord.js";
TheCodedProff86ba092023-01-27 17:10:07 -050013import type { SlashCommandSubcommandBuilder } from "discord.js";
PineaFan1dee28f2023-01-16 22:09:07 +000014import type { GuildBasedChannel } from "discord.js";
pineafan4edb7762022-06-26 19:21:04 +010015import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
pineafanc6158ab2022-06-17 16:34:07 +010016import getEmojiByName from "../../utils/getEmojiByName.js";
pineafan4edb7762022-06-26 19:21:04 +010017import pageIndicator from "../../utils/createPageIndicator.js";
pineafan4f164f32022-02-26 22:07:12 +000018
19const command = (builder: SlashCommandSubcommandBuilder) =>
20 builder
pineafan63fc5e22022-08-04 22:04:10 +010021 .setName("viewas")
22 .setDescription("View the server as a specific member")
Skyler Grey11236ba2022-08-08 21:13:33 +010023 .addUserOption((option) => option.setName("member").setDescription("The member to view as").setRequired(true));
pineafan4f164f32022-02-26 22:07:12 +000024
pineafanbd02b4a2022-08-05 22:01:38 +010025const callback = async (interaction: CommandInteraction): Promise<void> => {
Skyler Greyda16adf2023-03-05 10:22:12 +000026 const m = await interaction.reply({ embeds: LoadingEmbed, ephemeral: true, fetchReply: true });
TheCodedProf51296102023-01-18 22:35:02 -050027
Skyler Greyda16adf2023-03-05 10:22:12 +000028 let channels: Record<string, GuildBasedChannel[]> = { "": [] };
PineaFan1dee28f2023-01-16 22:09:07 +000029
PineaFan0d06edc2023-01-17 22:10:31 +000030 const channelCollection = await interaction.guild!.channels.fetch();
31
Skyler Greyda16adf2023-03-05 10:22:12 +000032 channelCollection.forEach((channel) => {
PineaFan0d06edc2023-01-17 22:10:31 +000033 if (!channel) return; // if no channel
34 if (channel.type === Discord.ChannelType.GuildCategory) {
Skyler Greyda16adf2023-03-05 10:22:12 +000035 if (!channels[channel!.id]) channels[channel!.id] = [];
PineaFan0d06edc2023-01-17 22:10:31 +000036 } else if (channel.parent) {
37 if (!channels[channel.parent.id]) channels[channel.parent.id] = [channel];
Skyler Greyda16adf2023-03-05 10:22:12 +000038 else channels[channel.parent.id as string]!.push(channel);
PineaFan0d06edc2023-01-17 22:10:31 +000039 } else {
40 channels[""]!.push(channel);
41 }
pineafan63fc5e22022-08-04 22:04:10 +010042 });
PineaFan1dee28f2023-01-16 22:09:07 +000043
44 const member = interaction.options.getMember("member") as Discord.GuildMember;
45 const autoSortBelow = [Discord.ChannelType.GuildVoice, Discord.ChannelType.GuildStageVoice];
PineaFan0d06edc2023-01-17 22:10:31 +000046
47 for (const category in channels) {
48 channels[category] = channels[category]!.sort((a: GuildBasedChannel, b: GuildBasedChannel) => {
Skyler Greyda16adf2023-03-05 10:22:12 +000049 const disallowedTypes = [
50 Discord.ChannelType.PublicThread,
51 Discord.ChannelType.PrivateThread,
52 Discord.ChannelType.AnnouncementThread
53 ];
PineaFan0d06edc2023-01-17 22:10:31 +000054 if (disallowedTypes.includes(a.type) || disallowedTypes.includes(b.type)) return 0;
55 a = a as NonThreadGuildBasedChannel;
56 b = b as NonThreadGuildBasedChannel;
57 if (autoSortBelow.includes(a.type) && autoSortBelow.includes(b.type)) return a.position - b.position;
Skyler Grey75ea9172022-08-06 10:22:23 +010058 if (autoSortBelow.includes(a.type)) return 1;
59 if (autoSortBelow.includes(b.type)) return -1;
PineaFan0d06edc2023-01-17 22:10:31 +000060 return a.position - b.position;
Skyler Grey75ea9172022-08-06 10:22:23 +010061 });
pineafanc6158ab2022-06-17 16:34:07 +010062 }
PineaFan0d06edc2023-01-17 22:10:31 +000063 for (const category in channels) {
64 channels[category] = channels[category]!.filter((c) => {
65 return c.permissionsFor(member).has("ViewChannel");
66 });
67 }
68 for (const category in channels) {
69 channels[category] = channels[category]!.filter((c) => {
Skyler Greyda16adf2023-03-05 10:22:12 +000070 return !(
71 c.type === Discord.ChannelType.PublicThread ||
72 c.type === Discord.ChannelType.PrivateThread ||
73 c.type === Discord.ChannelType.AnnouncementThread
74 );
PineaFan0d06edc2023-01-17 22:10:31 +000075 });
76 }
77 channels = Object.fromEntries(Object.entries(channels).filter(([_, v]) => v.length > 0));
78 let page = 0;
79 let closed = false;
80 const categoryIDs = Object.keys(channels);
81 const categoryNames = Object.values(channels).map((c) => {
82 return c[0]!.parent?.name ?? "Uncategorised";
83 });
84 // Split the category names into the first and last 25, ignoring the last 25 if there are 25 or less
85 const first25 = categoryNames.slice(0, 25);
86 const last25 = categoryNames.slice(25);
87 const categoryNames25: string[][] = [first25];
88 if (last25.length > 0) categoryNames25.push(last25);
PineaFan1dee28f2023-01-16 22:09:07 +000089
PineaFan0d06edc2023-01-17 22:10:31 +000090 const channelTypeEmoji: Record<number, string> = {
Skyler Greyda16adf2023-03-05 10:22:12 +000091 0: "GUILD_TEXT", // Text channel
92 2: "GUILD_VOICE", // Voice channel
93 5: "GUILD_NEWS", // Announcement channel
94 13: "GUILD_STAGE_VOICE", // Stage channel
95 15: "FORUM", // Forum channel
96 99: "RULES" // Rules channel
PineaFan0d06edc2023-01-17 22:10:31 +000097 };
98 const NSFWAvailable: number[] = [0, 2, 5, 13];
99 const rulesChannel = interaction.guild!.rulesChannel?.id;
100
101 async function nameFromChannel(channel: GuildBasedChannel): Promise<string> {
PineaFan638eb132023-01-19 10:41:22 +0000102 let channelType: Discord.ChannelType | 99 = channel.type;
PineaFan0d06edc2023-01-17 22:10:31 +0000103 if (channelType === Discord.ChannelType.GuildCategory) return "";
Skyler Greyda16adf2023-03-05 10:22:12 +0000104 if (channel.id === rulesChannel) channelType = 99;
PineaFan0d06edc2023-01-17 22:10:31 +0000105 let threads: Discord.ThreadChannel[] = [];
106 if ("threads" in channel) {
107 threads = channel.threads.cache.toJSON().map((t) => t as Discord.ThreadChannel);
108 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000109 const nsfw = ("nsfw" in channel ? channel.nsfw : false) && NSFWAvailable.includes(channelType);
TheCodedProf51296102023-01-18 22:35:02 -0500110 const emojiName = channelTypeEmoji[channelType.valueOf()] + (nsfw ? "_NSFW" : "");
PineaFan0d06edc2023-01-17 22:10:31 +0000111 const emoji = getEmojiByName("ICONS.CHANNEL." + (threads.length ? "THREAD_CHANNEL" : emojiName));
112 let current = `${emoji} ${channel.name}`;
113 if (threads.length) {
114 for (const thread of threads) {
115 current += `\n${getEmojiByName("ICONS.CHANNEL.THREAD_PIPE")} ${thread.name}`;
116 }
117 }
118 return current;
119 }
120
121 while (!closed) {
122 const category = categoryIDs[page]!;
123 let description = "";
124 for (const channel of channels[category]!) {
125 description += `${await nameFromChannel(channel)}\n`;
126 }
127
Skyler Greyda16adf2023-03-05 10:22:12 +0000128 const parsedCategorySelectMenu: ActionRowBuilder<StringSelectMenuBuilder | ButtonBuilder>[] =
129 categoryNames25.map((categories, set) => {
130 return new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(
131 new StringSelectMenuBuilder()
132 .setCustomId("category")
133 .setMinValues(1)
134 .setMaxValues(1)
135 .setOptions(
136 categories.map((c, i) => {
137 return new StringSelectMenuOptionBuilder()
138 .setLabel(c)
139 .setValue((set * 25 + i).toString())
140 .setEmoji(
141 getEmojiByName("ICONS.CHANNEL.CATEGORY", "id") as APIMessageComponentEmoji
142 ) // Again, this is valid but TS doesn't think so
143 .setDefault(set * 25 + i === page);
144 })
145 )
146 );
147 });
148
149 const components: ActionRowBuilder<ButtonBuilder | StringSelectMenuBuilder>[] = parsedCategorySelectMenu;
150 components.push(
151 new ActionRowBuilder<ButtonBuilder>().addComponents(
152 new ButtonBuilder()
153 .setCustomId("back")
154 .setStyle(ButtonStyle.Secondary)
155 .setDisabled(page === 0)
156 .setEmoji(getEmojiByName("CONTROL.LEFT", "id")),
157 new ButtonBuilder()
158 .setCustomId("right")
159 .setStyle(ButtonStyle.Secondary)
160 .setDisabled(page === categoryIDs.length - 1)
161 .setEmoji(getEmojiByName("CONTROL.RIGHT", "id"))
162 )
PineaFan0d06edc2023-01-17 22:10:31 +0000163 );
164
PineaFan0d06edc2023-01-17 22:10:31 +0000165 await interaction.editReply({
Skyler Greyda16adf2023-03-05 10:22:12 +0000166 embeds: [
167 new EmojiEmbed()
168 .setEmoji("MEMBER.JOIN")
169 .setTitle("Viewing as " + member.displayName)
170 .setStatus("Success")
171 .setDescription(description + "\n" + pageIndicator(categoryIDs.length, page))
172 ],
173 components: components
PineaFan0d06edc2023-01-17 22:10:31 +0000174 });
175 let i;
176 try {
Skyler Greyda16adf2023-03-05 10:22:12 +0000177 i = await m.awaitMessageComponent({
178 filter: (i) => i.user.id === interaction.user.id && i.message.id === m.id,
179 time: 30000
180 });
PineaFan0d06edc2023-01-17 22:10:31 +0000181 } catch (e) {
182 closed = true;
183 continue;
184 }
TheCodedProf267563a2023-01-21 17:00:57 -0500185 await i.deferUpdate();
PineaFan0d06edc2023-01-17 22:10:31 +0000186 if (i.customId === "back") page--;
187 else if (i.customId === "right") page++;
188 else if (i.customId === "category" && i.isStringSelectMenu()) page = parseInt(i.values[0]!);
189 }
pineafan63fc5e22022-08-04 22:04:10 +0100190};
pineafan4f164f32022-02-26 22:07:12 +0000191
TheCodedProff86ba092023-01-27 17:10:07 -0500192const check = (interaction: CommandInteraction, _partial: boolean = false) => {
Skyler Grey75ea9172022-08-06 10:22:23 +0100193 const member = interaction.member as GuildMember;
PineaFan0d06edc2023-01-17 22:10:31 +0000194 if (!member.permissions.has("ManageRoles")) return "You do not have the *Manage Roles* permission";
pineafan63fc5e22022-08-04 22:04:10 +0100195 return true;
196};
pineafan4f164f32022-02-26 22:07:12 +0000197
Skyler Grey75ea9172022-08-06 10:22:23 +0100198export { command, callback, check };