From 5215dd0738a37f0dd5706d7febeb412e766fd5c5 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sun, 9 Aug 2020 17:28:21 +0300 Subject: [PATCH] Add !slowmode command --- assets/icons/snowflake.png | Bin 0 -> 6591 bytes assets/icons/snowflake.svg | 1 + backend/src/commandTypes.ts | 22 +++++++++ backend/src/plugins/Utility/UtilityPlugin.ts | 4 ++ .../src/plugins/Utility/commands/InfoCmd.ts | 10 +++- .../Utility/commands/SnowflakeInfoCmd.ts | 21 +++++++++ .../functions/getSnowflakeInfoEmbed.ts | 44 ++++++++++++++++++ backend/src/plugins/Utility/types.ts | 1 + backend/src/utils.ts | 13 ++++++ backend/src/utils/snowflakeToTimestamp.ts | 8 +++- 10 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 assets/icons/snowflake.png create mode 100644 assets/icons/snowflake.svg create mode 100644 backend/src/plugins/Utility/commands/SnowflakeInfoCmd.ts create mode 100644 backend/src/plugins/Utility/functions/getSnowflakeInfoEmbed.ts diff --git a/assets/icons/snowflake.png b/assets/icons/snowflake.png new file mode 100644 index 0000000000000000000000000000000000000000..20240f053e9c3c034e197553db9a9bbcad794409 GIT binary patch literal 6591 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 \ No newline at end of file diff --git a/backend/src/commandTypes.ts b/backend/src/commandTypes.ts index 9298a292..74919110 100644 --- a/backend/src/commandTypes.ts +++ b/backend/src/commandTypes.ts @@ -1,10 +1,14 @@ import { + channelMentionRegex, convertDelayStringToMS, disableCodeBlocks, disableInlineCode, isSnowflake, + isValidSnowflake, resolveMember, resolveUser, + resolveUserId, + roleMentionRegex, UnknownUser, } from "./utils"; import { GuildChannel, Member, TextChannel, User } from "eris"; @@ -63,6 +67,23 @@ export const commandTypes = { return result; }, + + async anyId(value: string, context: CommandContext) { + const userId = resolveUserId(context.pluginData.client, value); + if (userId) return userId; + + const channelIdMatch = value.match(channelMentionRegex); + if (channelIdMatch) return channelIdMatch[1]; + + const roleIdMatch = value.match(roleMentionRegex); + if (roleIdMatch) return roleIdMatch[1]; + + if (isValidSnowflake(value)) { + return value; + } + + throw new TypeConversionError(`Could not parse ID: \`${disableInlineCode(value)}\``); + }, }; export const commandTypeHelpers = { @@ -73,4 +94,5 @@ export const commandTypeHelpers = { resolvedUserLoose: createTypeHelper>(commandTypes.resolvedUserLoose), resolvedMember: createTypeHelper>(commandTypes.resolvedMember), messageTarget: createTypeHelper>(commandTypes.messageTarget), + anyId: createTypeHelper>(commandTypes.anyId), }; diff --git a/backend/src/plugins/Utility/UtilityPlugin.ts b/backend/src/plugins/Utility/UtilityPlugin.ts index 5e31bc34..1300e8d1 100644 --- a/backend/src/plugins/Utility/UtilityPlugin.ts +++ b/backend/src/plugins/Utility/UtilityPlugin.ts @@ -31,6 +31,7 @@ import { InviteInfoCmd } from "./commands/InviteInfoCmd"; import { ChannelInfoCmd } from "./commands/ChannelInfoCmd"; import { MessageInfoCmd } from "./commands/MessageInfoCmd"; import { InfoCmd } from "./commands/InfoCmd"; +import { SnowflakeInfoCmd } from "./commands/SnowflakeInfoCmd"; const defaultOptions: PluginOptions = { config: { @@ -44,6 +45,7 @@ const defaultOptions: PluginOptions = { can_channelinfo: false, can_messageinfo: false, can_userinfo: false, + can_snowflake: false, can_reload_guild: false, can_nickname: false, can_ping: false, @@ -71,6 +73,7 @@ const defaultOptions: PluginOptions = { can_channelinfo: true, can_messageinfo: true, can_userinfo: true, + can_snowflake: true, can_nickname: true, can_vcmove: true, can_help: true, @@ -124,6 +127,7 @@ export const UtilityPlugin = zeppelinPlugin()("utility", { ChannelInfoCmd, MessageInfoCmd, InfoCmd, + SnowflakeInfoCmd, ], onLoad(pluginData) { diff --git a/backend/src/plugins/Utility/commands/InfoCmd.ts b/backend/src/plugins/Utility/commands/InfoCmd.ts index 37bf2968..bc2b1e77 100644 --- a/backend/src/plugins/Utility/commands/InfoCmd.ts +++ b/backend/src/plugins/Utility/commands/InfoCmd.ts @@ -2,7 +2,7 @@ import { utilityCmd } from "../types"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { sendErrorMessage } from "../../../pluginUtils"; import { getInviteInfoEmbed } from "../functions/getInviteInfoEmbed"; -import { parseInviteCodeInput, resolveInvite, resolveUser } from "../../../utils"; +import { isValidSnowflake, parseInviteCodeInput, resolveInvite, resolveUser } from "../../../utils"; import { getUserInfoEmbed } from "../functions/getUserInfoEmbed"; import { resolveMessageTarget } from "../../../utils/resolveMessageTarget"; import { canReadChannel } from "../../../utils/canReadChannel"; @@ -11,6 +11,7 @@ import { getChannelInfoEmbed } from "../functions/getChannelInfoEmbed"; import { getServerInfoEmbed } from "../functions/getServerInfoEmbed"; import { getChannelId } from "knub/dist/utils"; import { getGuildPreview } from "../functions/getGuildPreview"; +import { getSnowflakeInfoEmbed } from "../functions/getSnowflakeInfoEmbed"; export const InfoCmd = utilityCmd({ trigger: "info", @@ -93,6 +94,13 @@ export const InfoCmd = utilityCmd({ } } + // 7. Arbitrary ID + if (isValidSnowflake(value)) { + const embed = getSnowflakeInfoEmbed(pluginData, value, true); + message.channel.createMessage({ embed }); + return; + } + // 7. No can do sendErrorMessage(pluginData, message.channel, "Could not find anything with that value"); }, diff --git a/backend/src/plugins/Utility/commands/SnowflakeInfoCmd.ts b/backend/src/plugins/Utility/commands/SnowflakeInfoCmd.ts new file mode 100644 index 00000000..b327dfc0 --- /dev/null +++ b/backend/src/plugins/Utility/commands/SnowflakeInfoCmd.ts @@ -0,0 +1,21 @@ +import { utilityCmd } from "../types"; +import { commandTypeHelpers as ct } from "../../../commandTypes"; +import { sendErrorMessage } from "../../../pluginUtils"; +import { getChannelInfoEmbed } from "../functions/getChannelInfoEmbed"; +import { getSnowflakeInfoEmbed } from "../functions/getSnowflakeInfoEmbed"; + +export const SnowflakeInfoCmd = utilityCmd({ + trigger: ["snowflake", "snowflakeinfo"], + description: "Show information about a snowflake ID", + usage: "!snowflake 534722016549404673", + permission: "can_snowflake", + + signature: { + id: ct.anyId(), + }, + + run({ message, args, pluginData }) { + const embed = getSnowflakeInfoEmbed(pluginData, args.id); + message.channel.createMessage({ embed }); + }, +}); diff --git a/backend/src/plugins/Utility/functions/getSnowflakeInfoEmbed.ts b/backend/src/plugins/Utility/functions/getSnowflakeInfoEmbed.ts new file mode 100644 index 00000000..68b4ce82 --- /dev/null +++ b/backend/src/plugins/Utility/functions/getSnowflakeInfoEmbed.ts @@ -0,0 +1,44 @@ +import { Message, GuildTextableChannel, EmbedOptions } from "eris"; +import { PluginData } from "knub"; +import { UtilityPluginType } from "../types"; +import { UnknownUser, trimLines, embedPadding, resolveMember, resolveUser, preEmbedPadding } from "src/utils"; +import moment from "moment-timezone"; +import { CaseTypes } from "src/data/CaseTypes"; +import humanizeDuration from "humanize-duration"; +import { snowflakeToTimestamp } from "../../../utils/snowflakeToTimestamp"; + +const SNOWFLAKE_ICON = "https://cdn.discordapp.com/attachments/740650744830623756/742020790471491668/snowflake.png"; + +export function getSnowflakeInfoEmbed( + pluginData: PluginData, + snowflake: string, + showUnknownWarning = false, +): EmbedOptions { + const embed: EmbedOptions = { + fields: [], + }; + + embed.author = { + name: `Snowflake: ${snowflake}`, + icon_url: SNOWFLAKE_ICON, + }; + + if (showUnknownWarning) { + embed.description = + "This is a valid [snowflake ID](https://discord.com/developers/docs/reference#snowflakes), but I don't know what it's for."; + } + + const createdAtMS = snowflakeToTimestamp(snowflake); + const createdAt = moment(createdAtMS, "x"); + const snowflakeAge = humanizeDuration(Date.now() - createdAtMS, { + largest: 2, + round: true, + }); + + embed.fields.push({ + name: preEmbedPadding + "Basic information", + value: `Created: **${snowflakeAge} ago** (\`${createdAt.format("MMM D, YYYY [at] H:mm [UTC]")}\`)`, + }); + + return embed; +} diff --git a/backend/src/plugins/Utility/types.ts b/backend/src/plugins/Utility/types.ts index b1ccbd7e..c75cd9bb 100644 --- a/backend/src/plugins/Utility/types.ts +++ b/backend/src/plugins/Utility/types.ts @@ -17,6 +17,7 @@ export const ConfigSchema = t.type({ can_channelinfo: t.boolean, can_messageinfo: t.boolean, can_userinfo: t.boolean, + can_snowflake: 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 8bda4bda..3f6c5ef3 100644 --- a/backend/src/utils.ts +++ b/backend/src/utils.ts @@ -58,6 +58,19 @@ export const WEEKS = 7 * 24 * HOURS; export const EMPTY_CHAR = "\u200b"; +// https://discord.com/developers/docs/reference#snowflakes +export const MIN_SNOWFLAKE = 0b000000000000000000000000000000000000000000_00001_00001_000000000001; +// 0b111111111111111111111111111111111111111111_11111_11111_111111111111 without _ which BigInt doesn't support +export const MAX_SNOWFLAKE = BigInt("0b1111111111111111111111111111111111111111111111111111111111111111"); + +const snowflakePattern = /^[1-9]\d+$/; +export function isValidSnowflake(str: string) { + if (!str.match(snowflakePattern)) return false; + if (parseInt(str, 10) < MIN_SNOWFLAKE) return false; + if (BigInt(str) > MAX_SNOWFLAKE) return false; + return true; +} + export const DISCORD_HTTP_ERROR_NAME = "DiscordHTTPError"; export const DISCORD_REST_ERROR_NAME = "DiscordRESTError"; diff --git a/backend/src/utils/snowflakeToTimestamp.ts b/backend/src/utils/snowflakeToTimestamp.ts index d06f0018..09494eed 100644 --- a/backend/src/utils/snowflakeToTimestamp.ts +++ b/backend/src/utils/snowflakeToTimestamp.ts @@ -1,7 +1,13 @@ +import { isValidSnowflake } from "../utils"; + /** * @return Unix timestamp in milliseconds */ export function snowflakeToTimestamp(snowflake: string) { + if (!isValidSnowflake(snowflake)) { + throw new Error(`Invalid snowflake: ${snowflake}`); + } + // https://discord.com/developers/docs/reference#snowflakes-snowflake-id-format-structure-left-to-right - return Number(BigInt(snowflake) >> 22n) + 1420070400000; + return Number(BigInt(snowflake) >> 22n) + 1_420_070_400_000; }