diff --git a/src/structure/components/commands/information/Info.js b/src/structure/components/commands/information/Info.js new file mode 100644 index 0000000..1b8a65a --- /dev/null +++ b/src/structure/components/commands/information/Info.js @@ -0,0 +1,205 @@ +const { MessageEmbed } = require("discord.js"); +const { Emojis } = require("../../../../constants"); +const { Util } = require("../../../../utilities"); +const { SlashCommand, CommandError } = require("../../../interfaces"); + +const Constants = { + Badges: { + DISCORD_EMPLOYEE: Emojis['discord-staff'], + DISCORD_PARTNER: Emojis['discord-partner'], + HYPESQUAD_EVENTS: Emojis['hypesquad-events'], + BUGHUNTER_LEVEL_1: Emojis['bughunter'], //eslint-disable-line dot-notation + BUGHUNTER_LEVEL_2: Emojis['bughunter-gold'], + HOUSE_BRAVERY: Emojis['hypesquad-bravery'], + HOUSE_BRILLIANCE: Emojis['hypesquad-brilliance'], + HOUSE_BALANCE: Emojis['hypesquad-balance'], + EARLY_SUPPORTER: Emojis['early-supporter'], + EARLY_VERIFIED_BOT_DEVELOPER: Emojis['bot-developer'], + DISCORD_CERTIFIED_MODERATOR: Emojis['certified-moderator'] + } +}; + +class InformationCommand extends SlashCommand { + + constructor(client) { + super(client, { + name: 'info', + description: 'Display information about a discord structure', + module: 'information', + options: [ + { + name: ['user', 'role', 'channel'], + type: ['USER', 'ROLE', 'CHANNEL'], + description: '' + } + ] + }); + } + + async execute(interaction, { user, role, channel }) { + + if (user || !(user || role || channel)) return this._user(interaction, user?.value || interaction.user); + if (role) return this._role(interaction, role.value); + if (channel) return this._channel(interaction, channel.value); + + } + + async _user(interaction, user) { + + const member = await interaction.guild.members.fetch(user.id).catch(() => null); + const activities = member?.presence?.activities || []; + + const flags = user.flags || await user.fetchFlags(); + const badges = flags.toArray().filter((f) => Constants.Badges[f]) + .map((f) => Constants.Badges[f]); + + const response = { + author: { + name: Util.escapeMarkdown(user.tag) + }, + thumbnail: { + url: user.displayAvatarURL({ dynamic: true }) || user.defaultAvatarURL + }, + fields: [], + footer: { + text: interaction.format('COMMAND_INFO_USER_ID', { id: user.id }) + } + }; + + const activity = activities.reduce((acc, curr) => { + + if (acc.length) acc += `\n`; + if (curr.emoji) acc += ` `; + if (curr.type !== 'CUSTOM_STATUS') acc += `__${curr.name}__\n`; + if (curr.state) acc += curr.state; + if (curr.type === 'LISTENING') acc += ': ' + curr.details; + + return acc; + + }, ''); + + const userField = { + name: interaction.format('COMMAND_INFO_USER_DATA_NAME'), + value: interaction.format('COMMAND_INFO_USER_DATA', { + id: user.id, + bot: user.bot ? ` ${Emojis.bot}` : '', + created: `${user.createdAt.toDateString()} (${Util.timeAgo(user.createdTimestamp / 1000)})`, + status: member?.presence?.status || 'N/A', + globalActivity: user.lastMessage ? user.lastMessage.createdAt.toDateString() : 'N/A' + }), + inline: true + }; + + if (badges.length > 0) { + userField.value += `\n${interaction.format('COMMAND_INFO_USER_BADGES', { + badges: badges.join(' ') + })}`; + } + + response.fields.push(userField); + + if (activities.length) response.fields.push({ + name: interaction.format('COMMAND_INFO_USER_ACTIVITIES'), + value: activity, + inline: true + }); + + if (member) { + const memberField = { + name: interaction.format('COMMAND_INFO_MEMBER_NAME'), + value: interaction.format('COMMAND_INFO_MEMBER', { + nickname: member.nickname ? member.nickname : 'N/A', + joined: member.joinedAt ? `${member.joinedAt.toDateString()} (${Util.timeAgo(member.joinedTimestamp / 1000)})` : 'N/A', + serverActivity: member.lastMessage ? member.lastMessage.createdAt.toDateString() : 'N/A' + }) + }; + + const roles = member.roles.cache.filter((r) => r.name !== '@everyone').sort((a, b) => b.rawPosition - a.rawPosition); + const maxRoles = 30; + + if (roles.size > 0) { + memberField.value += `\n${interaction.format('COMMAND_INFO_MEMBER_ROLES', { + roles: roles.size > maxRoles ? `${roles.slice(0, maxRoles).map((r) => `<@&${r.id}>`).join(' ')} \`...${maxRoles - roles.size} more roles\`` : roles.map((r) => `<@&${r.id}>`).join(' ') + })}`; + } + + response.fields.push(memberField); + + const colour = member.highestRoleColor; + if (colour) response.color = colour; + + } + + return new MessageEmbed(response); + + } + + async _role(interaction, role) { + + const { guild } = interaction; + const _members = await guild.members.fetch(); + const members = _members.filter((member) => member.roles.cache.has(role.id)); + const embed = { + title: role.name, + description: interaction.format('COMMAND_INFO_ROLE', { + created: `${role.createdAt.toDateString()} (${Util.timeAgo(role.createdTimestamp / 1000)})`, + members: members.size, + colour: role.hexColor + }), + footer: { + text: interaction.format('COMMAND_INFO_ROLE_ID', { id: role.id }) + } + }; + + if (role.icon) { + const url = role.iconURL(); + embed.thumbnail = { url }; + embed.description += interaction.format('COMMAND_INFO_ROLE_ICON', { url }); + } + + return new MessageEmbed(embed); + + } + + async _channel(interaction, channel) { + + const perms = channel.permissionsFor(interaction.member); + if (perms.missing('VIEW_CHANNEL').length) throw new CommandError(interaction, { index: 'ERR_MISSING_PERMISSIONS' }); + + const embed = { + title: channel.name, + description: interaction.format('COMMAND_INFO_CHANNEL_BASE', { + created: `${channel.createdAt.toDateString()} (${Util.timeAgo(channel.createdTimestamp / 1000)})`, + type: channel.type + }), + footer: { + text: interaction.format('COMMAND_INFO_CHANNEL_ID', { id: channel.id }) + } + }; + + if (channel.type === 'GUILD_TEXT') { + const messages = channel.messages.cache; + const now = Date.now(); + const rates = { + '5': messages.filter((msg) => msg.createdTimestamp > now - 5 * 60 * 1000).size, + '10': messages.filter((msg) => msg.createdTimestamp > now - 10 * 60 * 1000).size, + '30': messages.filter((msg) => msg.createdTimestamp > now - 30 * 60 * 1000).size + }; + const { threads } = await channel.threads.fetch({ archived: { fetchAll: true }, active: true }); + const activeThreads = threads.filter((thread) => !thread.archived).size, + archivedThreads = threads.filter((thread) => thread.archived).size; + embed.description += '\n' + interaction.format('COMMAND_INFO_CHANNEL_ADDON', { + ...rates, + activeThreads, + archivedThreads, + totalThreads: activeThreads + archivedThreads + }); + } + + return new MessageEmbed(embed); + + } + +} + +module.exports = InformationCommand; \ No newline at end of file