diff --git a/assets/icons/announcement-channel.png b/assets/icons/announcement-channel.png new file mode 100644 index 00000000..7fbf8dd3 Binary files /dev/null and b/assets/icons/announcement-channel.png differ diff --git a/assets/icons/announcement-channel.svg b/assets/icons/announcement-channel.svg new file mode 100644 index 00000000..d99d88aa --- /dev/null +++ b/assets/icons/announcement-channel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/text-channel.png b/assets/icons/text-channel.png new file mode 100644 index 00000000..4bfd3079 Binary files /dev/null and b/assets/icons/text-channel.png differ diff --git a/assets/icons/text-channel.svg b/assets/icons/text-channel.svg new file mode 100644 index 00000000..fb328cb6 --- /dev/null +++ b/assets/icons/text-channel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/voice-channel.png b/assets/icons/voice-channel.png new file mode 100644 index 00000000..311ce3c9 Binary files /dev/null and b/assets/icons/voice-channel.png differ diff --git a/assets/icons/voice-channel.svg b/assets/icons/voice-channel.svg new file mode 100644 index 00000000..79f0cf72 --- /dev/null +++ b/assets/icons/voice-channel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/backend/src/plugins/Utility/UtilityPlugin.ts b/backend/src/plugins/Utility/UtilityPlugin.ts index 0e52f5bf..628871d4 100644 --- a/backend/src/plugins/Utility/UtilityPlugin.ts +++ b/backend/src/plugins/Utility/UtilityPlugin.ts @@ -28,6 +28,7 @@ import { AvatarCmd } from "./commands/AvatarCmd"; import { CleanCmd } from "./commands/CleanCmd"; import { Message } from "eris"; import { InviteInfoCmd } from "./commands/InviteInfoCmd"; +import { ChannelInfoCmd } from "./commands/ChannelInfoCmd"; const defaultOptions: PluginOptions = { config: { @@ -38,6 +39,7 @@ const defaultOptions: PluginOptions = { can_info: false, can_server: false, can_invite: false, + can_channel: false, can_reload_guild: false, can_nickname: false, can_ping: false, @@ -62,6 +64,7 @@ const defaultOptions: PluginOptions = { can_info: true, can_server: true, can_invite: true, + can_channel: true, can_nickname: true, can_vcmove: true, can_help: true, @@ -112,6 +115,7 @@ export const UtilityPlugin = zeppelinPlugin()("utility", { AvatarCmd, CleanCmd, InviteInfoCmd, + ChannelInfoCmd, ], onLoad(pluginData) { diff --git a/backend/src/plugins/Utility/commands/ChannelInfoCmd.ts b/backend/src/plugins/Utility/commands/ChannelInfoCmd.ts new file mode 100644 index 00000000..c65ab41d --- /dev/null +++ b/backend/src/plugins/Utility/commands/ChannelInfoCmd.ts @@ -0,0 +1,25 @@ +import { utilityCmd } from "../types"; +import { commandTypeHelpers as ct } from "../../../commandTypes"; +import { sendErrorMessage } from "../../../pluginUtils"; +import { getChannelInfoEmbed } from "../functions/getChannelInfoEmbed"; + +export const ChannelInfoCmd = utilityCmd({ + trigger: ["channel", "channelinfo"], + description: "Show information about a channel", + usage: "!channel 534722016549404673", + permission: "can_channel", + + signature: { + channel: ct.channelId({ required: false }), + }, + + async run({ message, args, pluginData }) { + const embed = await getChannelInfoEmbed(pluginData, args.channel); + if (!embed) { + sendErrorMessage(pluginData, message.channel, "Unknown channel"); + return; + } + + message.channel.createMessage({ embed }); + }, +}); diff --git a/backend/src/plugins/Utility/functions/getChannelInfoEmbed.ts b/backend/src/plugins/Utility/functions/getChannelInfoEmbed.ts new file mode 100644 index 00000000..6206dc5a --- /dev/null +++ b/backend/src/plugins/Utility/functions/getChannelInfoEmbed.ts @@ -0,0 +1,111 @@ +import { PluginData } from "knub"; +import { UtilityPluginType } from "../types"; +import { Constants, EmbedOptions } from "eris"; +import moment from "moment-timezone"; +import humanizeDuration from "humanize-duration"; +import { formatNumber, preEmbedPadding, trimLines } from "../../../utils"; + +const TEXT_CHANNEL_ICON = + "https://cdn.discordapp.com/attachments/740650744830623756/740656843545772062/text-channel.png"; +const VOICE_CHANNEL_ICON = + "https://cdn.discordapp.com/attachments/740650744830623756/740656845982662716/voice-channel.png"; +const ANNOUNCEMENT_CHANNEL_ICON = + "https://cdn.discordapp.com/attachments/740650744830623756/740656841687564348/announcement-channel.png"; + +export async function getChannelInfoEmbed( + pluginData: PluginData, + channelId: string, +): Promise { + const channel = pluginData.guild.channels.get(channelId); + if (!channel) { + return null; + } + + const embed: EmbedOptions = { + fields: [], + }; + + let icon; + if (channel.type === Constants.ChannelTypes.GUILD_VOICE) { + icon = VOICE_CHANNEL_ICON; + } else if (channel.type === Constants.ChannelTypes.GUILD_NEWS) { + icon = ANNOUNCEMENT_CHANNEL_ICON; + } else { + icon = TEXT_CHANNEL_ICON; + } + + const channelType = + { + [Constants.ChannelTypes.GUILD_TEXT]: "Text channel", + [Constants.ChannelTypes.GUILD_VOICE]: "Voice channel", + [Constants.ChannelTypes.GUILD_CATEGORY]: "Category", + [Constants.ChannelTypes.GUILD_NEWS]: "Announcement channel", + [Constants.ChannelTypes.GUILD_STORE]: "Store channel", + }[channel.type] || "Channel"; + + embed.author = { + name: `${channelType}: ${channel.name}`, + icon_url: icon, + }; + + let channelName; + if (channel.type === Constants.ChannelTypes.GUILD_VOICE || channel.type === Constants.ChannelTypes.GUILD_CATEGORY) { + channelName = channel.name; + } else { + channelName = `#${channel.name}`; + } + + const createdAt = moment(channel.createdAt, "x"); + const channelAge = humanizeDuration(Date.now() - channel.createdAt, { + largest: 2, + round: true, + }); + + const showMention = + channel.type !== Constants.ChannelTypes.GUILD_VOICE && channel.type !== Constants.ChannelTypes.GUILD_CATEGORY; + + embed.fields.push({ + name: preEmbedPadding + "Channel information", + value: trimLines(` + Name: **${channelName}** + ID: \`${channel.id}\` + Created: **${channelAge} ago** (\`${createdAt.format("MMM D, YYYY [at] H:mm [UTC]")}\`) + Type: **${channelType}** + ${showMention ? `Mention: <#${channel.id}>` : ""} + `), + }); + + if (channel.type === Constants.ChannelTypes.GUILD_VOICE) { + const voiceMembers = Array.from(channel.voiceMembers.values()); + const muted = voiceMembers.filter(vm => vm.voiceState.mute || vm.voiceState.selfMute); + const deafened = voiceMembers.filter(vm => vm.voiceState.deaf || vm.voiceState.selfDeaf); + + embed.fields.push({ + name: preEmbedPadding + "Voice information", + value: trimLines(` + Users on voice channel: **${formatNumber(voiceMembers.length)}** + Muted: **${formatNumber(muted.length)}** + Deafened: **${formatNumber(deafened.length)}** + `), + }); + } + + if (channel.type === Constants.ChannelTypes.GUILD_CATEGORY) { + const textChannels = pluginData.guild.channels.filter( + ch => ch.parentID === channel.id && ch.type !== Constants.ChannelTypes.GUILD_VOICE, + ); + const voiceChannels = pluginData.guild.channels.filter( + ch => ch.parentID === channel.id && ch.type === Constants.ChannelTypes.GUILD_VOICE, + ); + + embed.fields.push({ + name: preEmbedPadding + "Category information", + value: trimLines(` + Text channels: **${textChannels.length}** + Voice channels: **${voiceChannels.length}** + `), + }); + } + + return embed; +} diff --git a/backend/src/plugins/Utility/types.ts b/backend/src/plugins/Utility/types.ts index 868f917a..5efbadc3 100644 --- a/backend/src/plugins/Utility/types.ts +++ b/backend/src/plugins/Utility/types.ts @@ -14,6 +14,7 @@ export const ConfigSchema = t.type({ can_info: t.boolean, can_server: t.boolean, can_invite: t.boolean, + can_channel: t.boolean, can_reload_guild: t.boolean, can_nickname: t.boolean, can_ping: t.boolean,