diff --git a/backend/src/plugins/Utility/commands/ServerCmd.ts b/backend/src/plugins/Utility/commands/ServerCmd.ts index f29ce5b0..0cc0e734 100644 --- a/backend/src/plugins/Utility/commands/ServerCmd.ts +++ b/backend/src/plugins/Utility/commands/ServerCmd.ts @@ -1,125 +1,26 @@ -import { CategoryChannel, EmbedOptions, RESTChannelInvite, TextChannel, VoiceChannel } from "eris"; -import moment from "moment-timezone"; -import { embedPadding, formatNumber, memoize, MINUTES, trimLines } from "../../../utils"; import { utilityCmd } from "../types"; -import humanizeDuration from "humanize-duration"; +import { commandTypeHelpers as ct } from "../../../commandTypes"; +import { sendErrorMessage } from "../../../pluginUtils"; +import { serverInfo } from "../functions/serverInfo"; export const ServerCmd = utilityCmd({ - trigger: "server", - description: "Show information about the server", + trigger: ["server", "serverinfo"], + description: "Show server information", usage: "!server", permission: "can_server", - async run({ message, pluginData }) { - const { guild } = pluginData; + signature: { + serverId: ct.string({ required: false }), + }, - const embed: EmbedOptions = { - fields: [], - color: parseInt("6b80cf", 16), - }; - - embed.thumbnail = { url: guild.iconURL }; - - const createdAt = moment(guild.createdAt); - const serverAge = humanizeDuration(moment().valueOf() - guild.createdAt, { - largest: 2, - round: true, - }); - - const owner = pluginData.client.users.get(guild.ownerID); - const ownerName = owner ? `${owner.username}#${owner.discriminator}` : "Unknown#0000"; - - embed.fields.push({ - name: `Server information - ${guild.name}`, - value: - trimLines(` - Created: **${serverAge} ago** (${createdAt.format("YYYY-MM-DD[T]HH:mm:ss")}) - Owner: **${ownerName}** (${guild.ownerID}) - Voice region: **${guild.region}** - ${guild.features.length > 0 ? "Features: " + guild.features.join(", ") : ""} - `) + embedPadding, - }); - - const restGuild = await memoize( - () => pluginData.client.getRESTGuild(guild.id), - `getRESTGuild_${guild.id}`, - 10 * MINUTES, - ); - - const totalMembers = restGuild.memberCount ?? restGuild.approximateMemberCount; - let onlineMemberCount = restGuild.approximatePresenceCount; - - if (totalMembers == null || onlineMemberCount == null) { - // For servers with a vanity URL, we can also use the numbers from the invite for online count - const invite = guild.vanityURL - ? ((await memoize( - () => pluginData.client.getInvite(guild.vanityURL, true), - `getInvite_${guild.vanityURL}`, - 10 * MINUTES, - )) as RESTChannelInvite) - : null; - - onlineMemberCount = invite ? invite.presenceCount : guild.members.filter(m => m.status !== "offline").length; + async run({ message, pluginData, args }) { + const serverId = args.serverId || pluginData.guild.id; + const serverInfoEmbed = await serverInfo(pluginData, serverId); + if (!serverInfoEmbed) { + sendErrorMessage(pluginData, message.channel, "Could not find information for that server"); + return; } - const offlineMemberCount = totalMembers - onlineMemberCount; - - let memberCountTotalLines = `Total: **${formatNumber(totalMembers)}**`; - if (restGuild.maxMembers) { - memberCountTotalLines += `\nMax: **${formatNumber(restGuild.maxMembers)}**`; - } - - let memberCountOnlineLines = `Online: **${formatNumber(onlineMemberCount)}**`; - if (restGuild.maxPresences) { - memberCountOnlineLines += `\nMax online: **${formatNumber(restGuild.maxPresences)}**`; - } - - embed.fields.push({ - name: "Members", - inline: true, - value: trimLines(` - ${memberCountTotalLines} - ${memberCountOnlineLines} - Offline: **${formatNumber(offlineMemberCount)}** - `), - }); - - const totalChannels = guild.channels.size; - const categories = guild.channels.filter(channel => channel instanceof CategoryChannel); - const textChannels = guild.channels.filter(channel => channel instanceof TextChannel); - const voiceChannels = guild.channels.filter(channel => channel instanceof VoiceChannel); - - embed.fields.push({ - name: "Channels", - inline: true, - value: - trimLines(` - Total: **${totalChannels}** / 500 - Categories: **${categories.length}** - Text: **${textChannels.length}** - Voice: **${voiceChannels.length}** - `) + embedPadding, - }); - - const maxEmojis = - { - 0: 50, - 1: 100, - 2: 150, - 3: 250, - }[guild.premiumTier] || 50; - - embed.fields.push({ - name: "Other stats", - inline: true, - value: - trimLines(` - Roles: **${guild.roles.size}** / 250 - Emojis: **${guild.emojis.length}** / ${maxEmojis} - Boosts: **${guild.premiumSubscriptionCount ?? 0}** (level ${guild.premiumTier}) - `) + embedPadding, - }); - - message.channel.createMessage({ embed }); + message.channel.createMessage({ embed: serverInfoEmbed }); }, }); diff --git a/backend/src/plugins/Utility/functions/serverInfo.ts b/backend/src/plugins/Utility/functions/serverInfo.ts new file mode 100644 index 00000000..05b91c52 --- /dev/null +++ b/backend/src/plugins/Utility/functions/serverInfo.ts @@ -0,0 +1,161 @@ +import { PluginData } from "knub"; +import { UtilityPluginType } from "../types"; +import { embedPadding, formatNumber, memoize, MINUTES, resolveUser, trimLines } from "../../../utils"; +import { CategoryChannel, EmbedOptions, Guild, RESTChannelInvite, TextChannel, VoiceChannel } from "eris"; +import moment from "moment-timezone"; +import humanizeDuration from "humanize-duration"; + +export async function serverInfo(pluginData: PluginData, serverId: string): Promise { + const thisServer = serverId === pluginData.guild.id ? pluginData.guild : null; + const [restGuild, guildPreview] = await Promise.all([ + thisServer + ? memoize(() => pluginData.client.getRESTGuild(serverId), `getRESTGuild_${serverId}`, 10 * MINUTES) + : null, + memoize( + () => pluginData.client.getGuildPreview(serverId).catch(() => null), + `getGuildPreview_${serverId}`, + 10 * MINUTES, + ), + ]); + + if (!restGuild && !guildPreview) { + return null; + } + + const embed: EmbedOptions = { + fields: [], + color: parseInt("6b80cf", 16), + }; + + embed.thumbnail = { url: (guildPreview || restGuild).iconURL }; + + // BASIC INFORMATION + const createdAt = moment((guildPreview || restGuild).createdAt); + const serverAge = humanizeDuration(moment().valueOf() - createdAt.valueOf(), { + largest: 2, + round: true, + }); + + const basicInformation = []; + basicInformation.push(`Created: **${serverAge} ago** (${createdAt.format("YYYY-MM-DD[T]HH:mm:ss")})`); + + if (thisServer) { + const owner = await resolveUser(pluginData.client, thisServer.ownerID); + const ownerName = `${owner.username}#${owner.discriminator}`; + + basicInformation.push(`Owner: **${ownerName}** (${thisServer.ownerID})`); + basicInformation.push(`Voice region: **${thisServer.region}**`); + } + + const features = (guildPreview || restGuild).features; + if (features.length > 0) { + basicInformation.push(`Features: ${features.join(", ")}`); + } + + embed.fields.push({ + name: `Server information - ${(guildPreview || restGuild).name}`, + value: basicInformation.join("\n") + embedPadding, + }); + + // MEMBER COUNTS + const totalMembers = + guildPreview?.approximateMemberCount || + restGuild?.approximateMemberCount || + restGuild?.memberCount || + thisServer?.memberCount || + thisServer?.members.size || + 0; + + let onlineMemberCount = guildPreview?.approximatePresenceCount || restGuild?.approximatePresenceCount; + + if (onlineMemberCount == null && restGuild?.vanityURL) { + // For servers with a vanity URL, we can also use the numbers from the invite for online count + const invite = (await memoize( + () => pluginData.client.getInvite(restGuild.vanityURL, true), + `getInvite_${restGuild.vanityURL}`, + 10 * MINUTES, + )) as RESTChannelInvite; + + if (invite) { + onlineMemberCount = invite.presenceCount; + } + } + + if (!onlineMemberCount && thisServer) { + onlineMemberCount = thisServer.members.filter(m => m.status !== "offline").length; // Extremely inaccurate fallback + } + + const offlineMemberCount = totalMembers - onlineMemberCount; + + let memberCountTotalLines = `Total: **${formatNumber(totalMembers)}**`; + if (restGuild?.maxMembers) { + memberCountTotalLines += `\nMax: **${formatNumber(restGuild.maxMembers)}**`; + } + + let memberCountOnlineLines = `Online: **${formatNumber(onlineMemberCount)}**`; + if (restGuild?.maxPresences) { + memberCountOnlineLines += `\nMax online: **${formatNumber(restGuild.maxPresences)}**`; + } + + embed.fields.push({ + name: "Members", + inline: true, + value: trimLines(` + ${memberCountTotalLines} + ${memberCountOnlineLines} + Offline: **${formatNumber(offlineMemberCount)}** + `), + }); + + // CHANNEL COUNTS + if (thisServer) { + const totalChannels = thisServer.channels.size; + const categories = thisServer.channels.filter(channel => channel instanceof CategoryChannel); + const textChannels = thisServer.channels.filter(channel => channel instanceof TextChannel); + const voiceChannels = thisServer.channels.filter(channel => channel instanceof VoiceChannel); + + embed.fields.push({ + name: "Channels", + inline: true, + value: + trimLines(` + Total: **${totalChannels}** / 500 + Categories: **${categories.length}** + Text: **${textChannels.length}** + Voice: **${voiceChannels.length}** + `) + embedPadding, + }); + } + + // OTHER STATS + const otherStats = []; + + if (thisServer) { + otherStats.push(`Roles: **${thisServer.roles.size}** / 250`); + } + + if (restGuild) { + const maxEmojis = + { + 0: 50, + 1: 100, + 2: 150, + 3: 250, + }[restGuild.premiumTier] || 50; + otherStats.push(`Emojis: **${restGuild.emojis.length}** / ${maxEmojis}`); + } else { + otherStats.push(`Emojis: **${guildPreview.emojis.length}**`); + } + + if (thisServer) { + otherStats.push(`Boosts: **${thisServer.premiumSubscriptionCount ?? 0}** (level ${thisServer.premiumTier})`); + } + + embed.fields.push({ + name: "Other stats", + inline: true, + value: otherStats.join("\n") + embedPadding, + }); + + return embed; +}