diff --git a/assets/icons/LICENSE b/assets/icons/LICENSE new file mode 100644 index 00000000..8518a347 --- /dev/null +++ b/assets/icons/LICENSE @@ -0,0 +1,4 @@ +# TWEMOJI +Copyright 2020 Twitter, Inc and other contributors +Code licensed under the MIT License: http://opensource.org/licenses/MIT +Graphics licensed under CC-BY 4.0: https://creativecommons.org/licenses/by/4.0/ diff --git a/assets/icons/memo.svg b/assets/icons/memo.svg new file mode 100644 index 00000000..1697ffbe --- /dev/null +++ b/assets/icons/memo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/message.png b/assets/icons/message.png new file mode 100644 index 00000000..e10d5af8 Binary files /dev/null and b/assets/icons/message.png differ diff --git a/assets/icons/message.svg b/assets/icons/message.svg new file mode 100644 index 00000000..62cf2b78 --- /dev/null +++ b/assets/icons/message.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/backend/src/plugins/Utility/UtilityPlugin.ts b/backend/src/plugins/Utility/UtilityPlugin.ts index 3b909040..ad0fbd37 100644 --- a/backend/src/plugins/Utility/UtilityPlugin.ts +++ b/backend/src/plugins/Utility/UtilityPlugin.ts @@ -29,6 +29,7 @@ import { CleanCmd } from "./commands/CleanCmd"; import { Message } from "eris"; import { InviteInfoCmd } from "./commands/InviteInfoCmd"; import { ChannelInfoCmd } from "./commands/ChannelInfoCmd"; +import { MessageInfoCmd } from "./commands/MessageInfoCmd"; const defaultOptions: PluginOptions = { config: { @@ -40,6 +41,7 @@ const defaultOptions: PluginOptions = { can_server: false, can_invite: false, can_channel: false, + can_message: false, can_reload_guild: false, can_nickname: false, can_ping: false, @@ -65,6 +67,7 @@ const defaultOptions: PluginOptions = { can_server: true, can_invite: true, can_channel: true, + can_message: true, can_nickname: true, can_vcmove: true, can_help: true, @@ -116,6 +119,7 @@ export const UtilityPlugin = zeppelinPlugin()("utility", { CleanCmd, InviteInfoCmd, ChannelInfoCmd, + MessageInfoCmd, ], onLoad(pluginData) { diff --git a/backend/src/plugins/Utility/commands/MessageInfoCmd.ts b/backend/src/plugins/Utility/commands/MessageInfoCmd.ts new file mode 100644 index 00000000..7072c72c --- /dev/null +++ b/backend/src/plugins/Utility/commands/MessageInfoCmd.ts @@ -0,0 +1,31 @@ +import { utilityCmd } from "../types"; +import { commandTypeHelpers as ct } from "../../../commandTypes"; +import { sendErrorMessage } from "../../../pluginUtils"; +import { getMessageInfoEmbed } from "../functions/getMessageInfoEmbed"; +import { canReadChannel } from "../../../utils/canReadChannel"; + +export const MessageInfoCmd = utilityCmd({ + trigger: ["message", "messageinfo"], + description: "Show information about a message", + usage: "!message 534722016549404673-534722219696455701", + permission: "can_message", + + signature: { + message: ct.messageTarget(), + }, + + async run({ message, args, pluginData }) { + if (!canReadChannel(args.message.channel, message.author.id)) { + sendErrorMessage(pluginData, message.channel, "Unknown message"); + return; + } + + const embed = await getMessageInfoEmbed(pluginData, args.message.channel.id, args.message.messageId); + if (!embed) { + sendErrorMessage(pluginData, message.channel, "Unknown message"); + return; + } + + message.channel.createMessage({ embed }); + }, +}); diff --git a/backend/src/plugins/Utility/functions/getMessageInfoEmbed.ts b/backend/src/plugins/Utility/functions/getMessageInfoEmbed.ts new file mode 100644 index 00000000..083f6cfd --- /dev/null +++ b/backend/src/plugins/Utility/functions/getMessageInfoEmbed.ts @@ -0,0 +1,129 @@ +import { PluginData } from "knub"; +import { UtilityPluginType } from "../types"; +import { Constants, EmbedOptions } from "eris"; +import moment from "moment-timezone"; +import humanizeDuration from "humanize-duration"; +import { chunkMessageLines, preEmbedPadding, trimEmptyLines, trimLines } from "../../../utils"; +import { getDefaultPrefix } from "knub/dist/commands/commandUtils"; + +const MESSAGE_ICON = "https://cdn.discordapp.com/attachments/740650744830623756/740685652152025088/message.png"; + +export async function getMessageInfoEmbed( + pluginData: PluginData, + channelId: string, + messageId: string, +): Promise { + const message = await pluginData.client.getMessage(channelId, messageId).catch(() => null); + if (!message) { + return null; + } + + const embed: EmbedOptions = { + fields: [], + }; + + embed.author = { + name: `Message: ${message.id}`, + icon_url: MESSAGE_ICON, + }; + + const createdAt = moment(message.createdAt, "x"); + const messageAge = humanizeDuration(Date.now() - message.createdAt, { + largest: 2, + round: true, + }); + + const editedAt = message.editedTimestamp && moment(message.editedTimestamp, "x"); + const editAge = + message.editedTimestamp && + humanizeDuration(Date.now() - message.editedTimestamp, { + largest: 2, + round: true, + }); + + const type = + { + [Constants.MessageTypes.DEFAULT]: "Regular message", + [Constants.MessageTypes.CHANNEL_PINNED_MESSAGE]: "System message", + [Constants.MessageTypes.GUILD_MEMBER_JOIN]: "System message", + [Constants.MessageTypes.USER_PREMIUM_GUILD_SUBSCRIPTION]: "System message", + [Constants.MessageTypes.USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1]: "System message", + [Constants.MessageTypes.USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2]: "System message", + [Constants.MessageTypes.USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3]: "System message", + [Constants.MessageTypes.CHANNEL_FOLLOW_ADD]: "System message", + [Constants.MessageTypes.GUILD_DISCOVERY_DISQUALIFIED]: "System message", + [Constants.MessageTypes.GUILD_DISCOVERY_REQUALIFIED]: "System message", + }[message.type] || "Unknown"; + + embed.fields.push({ + name: preEmbedPadding + "Message information", + value: trimEmptyLines( + trimLines(` + ID: \`${message.id}\` + Channel: <#${message.channel.id}> + Channel ID: \`${message.channel.id}\` + Created: **${messageAge} ago** (\`${createdAt.format("MMM D, YYYY [at] H:mm [UTC]")}\`) + ${editedAt ? `Edited at: **${editAge} ago** (\`${editedAt.format("MMM D, YYYY [at] H:mm [UTC]")}\`)` : ""} + Type: **${type}** + Link: [**Go to message ➔**](https://discord.com/channels/${pluginData.guild.id}/${message.channel.id}/${ + message.id + }) + `), + ), + }); + + const authorCreatedAt = moment(message.author.createdAt); + const authorAccountAge = humanizeDuration(Date.now() - message.author.createdAt, { + largest: 2, + round: true, + }); + + const authorJoinedAt = message.member && moment(message.member.joinedAt); + const authorServerAge = + message.member && + humanizeDuration(Date.now() - message.member.joinedAt, { + largest: 2, + round: true, + }); + + embed.fields.push({ + name: preEmbedPadding + "Author information", + value: trimLines(` + Name: **${message.author.username}#${message.author.discriminator}** + ID: \`${message.author.id}\` + Created: **${authorAccountAge} ago** (\`${authorCreatedAt.format("MMM D, YYYY [at] H:mm [UTC]")}\`) + ${ + authorJoinedAt + ? `Joined: **${authorServerAge} ago** (\`${authorJoinedAt.format("MMM D, YYYY [at] H:mm [UTC]")}\`)` + : "" + } + Mention: <@!${message.author.id}> + `), + }); + + const textContent = message.content || ""; + const chunked = chunkMessageLines(textContent, 1014); + for (const [i, chunk] of chunked.entries()) { + embed.fields.push({ + name: i === 0 ? preEmbedPadding + "Text content" : "[...]", + value: chunk, + }); + } + + if (message.attachments.length) { + embed.fields.push({ + name: preEmbedPadding + "Attachments", + value: message.attachments[0].url, + }); + } + + if (message.embeds.length) { + const prefix = pluginData.guildConfig.prefix || getDefaultPrefix(pluginData.client); + embed.fields.push({ + name: preEmbedPadding + "Embeds", + value: `Message contains an embed, use \`${prefix}source\` to see the embed source`, + }); + } + + return embed; +} diff --git a/backend/src/plugins/Utility/types.ts b/backend/src/plugins/Utility/types.ts index 5efbadc3..e085e2da 100644 --- a/backend/src/plugins/Utility/types.ts +++ b/backend/src/plugins/Utility/types.ts @@ -15,6 +15,7 @@ export const ConfigSchema = t.type({ can_server: t.boolean, can_invite: t.boolean, can_channel: t.boolean, + can_message: t.boolean, can_reload_guild: t.boolean, can_nickname: t.boolean, can_ping: t.boolean, diff --git a/backend/src/utils.ts b/backend/src/utils.ts index c6c91546..8bda4bda 100644 --- a/backend/src/utils.ts +++ b/backend/src/utils.ts @@ -492,6 +492,13 @@ export function trimLines(str: string) { .trim(); } +export function trimEmptyLines(str: string) { + return str + .split("\n") + .filter(l => l.trim() !== "") + .join("\n"); +} + export function asSingleLine(str: string) { return trimLines(str).replace(/\n/g, " "); }