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,