diff --git a/src/client/ClientDataResolver.js b/src/client/ClientDataResolver.js
index c17d4944e..c6bf38717 100644
--- a/src/client/ClientDataResolver.js
+++ b/src/client/ClientDataResolver.js
@@ -32,7 +32,7 @@ class ClientDataResolver {
* * A Message object (resolves to the message author)
* * A Guild object (owner of the guild)
* * A GuildMember object
- * @typedef {User|string|Message|Guild|GuildMember} UserResolvable
+ * @typedef {User|Snowflake|Message|Guild|GuildMember} UserResolvable
*/
/**
@@ -52,7 +52,7 @@ class ClientDataResolver {
/**
* Resolves a UserResolvable to a user ID string
* @param {UserResolvable} user The UserResolvable to identify
- * @returns {?string}
+ * @returns {?Snowflake}
*/
resolveUserID(user) {
if (user instanceof User || user instanceof GuildMember) return user.id;
@@ -66,7 +66,7 @@ class ClientDataResolver {
* Data that resolves to give a Guild object. This can be:
* * A Guild object
* * A Guild ID
- * @typedef {Guild|string} GuildResolvable
+ * @typedef {Guild|Snowflake} GuildResolvable
*/
/**
@@ -107,7 +107,7 @@ class ClientDataResolver {
* * A Message object (the channel the message was sent in)
* * A Guild object (the #general channel)
* * A channel ID
- * @typedef {Channel|Guild|Message|string} ChannelResolvable
+ * @typedef {Channel|Guild|Message|Snowflake} ChannelResolvable
*/
/**
@@ -126,7 +126,7 @@ class ClientDataResolver {
/**
* Resolves a ChannelResolvable to a Channel object
* @param {ChannelResolvable} channel The channel resolvable to resolve
- * @returns {?string}
+ * @returns {?Snowflake}
*/
resolveChannelID(channel) {
if (channel instanceof Channel) return channel.id;
diff --git a/src/index.js b/src/index.js
index 85326bfc1..07cfbf39b 100644
--- a/src/index.js
+++ b/src/index.js
@@ -9,6 +9,7 @@ module.exports = {
splitMessage: require('./util/SplitMessage'),
escapeMarkdown: require('./util/EscapeMarkdown'),
fetchRecommendedShards: require('./util/FetchRecommendedShards'),
+ Snowflake: require('./util/Snowflake'),
Channel: require('./structures/Channel'),
ClientOAuth2Application: require('./structures/ClientOAuth2Application'),
diff --git a/src/structures/Channel.js b/src/structures/Channel.js
index b37b14b41..6b42b2cd6 100644
--- a/src/structures/Channel.js
+++ b/src/structures/Channel.js
@@ -27,7 +27,7 @@ class Channel {
setup(data) {
/**
* The unique ID of the channel
- * @type {string}
+ * @type {Snowflake}
*/
this.id = data.id;
}
diff --git a/src/structures/ClientUser.js b/src/structures/ClientUser.js
index 67e19f9c2..22140bee8 100644
--- a/src/structures/ClientUser.js
+++ b/src/structures/ClientUser.js
@@ -26,21 +26,21 @@ class ClientUser extends User {
/**
* A Collection of friends for the logged in user.
* This is only filled when using a user account.
- * @type {Collection}
+ * @type {Collection}
*/
this.friends = new Collection();
/**
* A Collection of blocked users for the logged in user.
* This is only filled when using a user account.
- * @type {Collection}
+ * @type {Collection}
*/
this.blocked = new Collection();
/**
* A Collection of notes for the logged in user.
* This is only filled when using a user account.
- * @type {Collection}
+ * @type {Collection}
*/
this.notes = new Collection();
@@ -229,7 +229,7 @@ class ClientUser extends User {
* @param {number} [options.limit=25] Maximum number of mentions to retrieve
* @param {boolean} [options.roles=true] Whether to include role mentions
* @param {boolean} [options.everyone=true] Whether to include everyone/here mentions
- * @param {Guild|string} [options.guild] Limit the search to a specific guild
+ * @param {Guild|Snowflake} [options.guild] Limit the search to a specific guild
* @returns {Promise}
*/
fetchMentions(options = { limit: 25, roles: true, everyone: true, guild: null }) {
diff --git a/src/structures/Emoji.js b/src/structures/Emoji.js
index 6cd39f661..519092c28 100644
--- a/src/structures/Emoji.js
+++ b/src/structures/Emoji.js
@@ -26,7 +26,7 @@ class Emoji {
setup(data) {
/**
* The ID of the emoji
- * @type {string}
+ * @type {Snowflake}
*/
this.id = data.id;
@@ -71,7 +71,7 @@ class Emoji {
/**
* A collection of roles this emoji is active for (empty if all), mapped by role ID.
- * @type {Collection}
+ * @type {Collection}
* @readonly
*/
get roles() {
diff --git a/src/structures/GroupDMChannel.js b/src/structures/GroupDMChannel.js
index d6b5338d4..cf6739fd3 100644
--- a/src/structures/GroupDMChannel.js
+++ b/src/structures/GroupDMChannel.js
@@ -61,7 +61,7 @@ class GroupDMChannel extends Channel {
if (!this.recipients) {
/**
* A collection of the recipients of this DM, mapped by their ID.
- * @type {Collection}
+ * @type {Collection}
*/
this.recipients = new Collection();
}
diff --git a/src/structures/Guild.js b/src/structures/Guild.js
index e3c50b00e..8b5cb2d3c 100644
--- a/src/structures/Guild.js
+++ b/src/structures/Guild.js
@@ -25,25 +25,25 @@ class Guild {
/**
* A collection of members that are in this guild. The key is the member's ID, the value is the member.
- * @type {Collection}
+ * @type {Collection}
*/
this.members = new Collection();
/**
* A collection of channels that are in this guild. The key is the channel's ID, the value is the channel.
- * @type {Collection}
+ * @type {Collection}
*/
this.channels = new Collection();
/**
* A collection of roles that are in this guild. The key is the role's ID, the value is the role.
- * @type {Collection}
+ * @type {Collection}
*/
this.roles = new Collection();
/**
* A collection of presences in this guild
- * @type {Collection}
+ * @type {Collection}
*/
this.presences = new Collection();
@@ -57,7 +57,7 @@ class Guild {
/**
* The Unique ID of the Guild, useful for comparisons.
- * @type {string}
+ * @type {Snowflake}
*/
this.id = data.id;
} else {
@@ -116,13 +116,13 @@ class Guild {
/**
* The ID of the application that created this guild (if applicable)
- * @type {?string}
+ * @type {?Snowflake}
*/
this.applicationID = data.application_id;
/**
* A collection of emojis that are in this guild. The key is the emoji's ID, the value is the emoji.
- * @type {Collection}
+ * @type {Collection}
*/
this.emojis = new Collection();
for (const emoji of data.emojis) this.emojis.set(emoji.id, new Emoji(this, emoji));
@@ -169,7 +169,7 @@ class Guild {
if (data.owner_id) {
/**
* The user ID of this guild's owner.
- * @type {string}
+ * @type {Snowflake}
*/
this.ownerID = data.owner_id;
}
diff --git a/src/structures/GuildChannel.js b/src/structures/GuildChannel.js
index a93078972..ef5687c43 100644
--- a/src/structures/GuildChannel.js
+++ b/src/structures/GuildChannel.js
@@ -37,7 +37,7 @@ class GuildChannel extends Channel {
/**
* A map of permission overwrites in this channel for roles and users.
- * @type {Collection}
+ * @type {Collection}
*/
this.permissionOverwrites = new Collection();
if (data.permission_overwrites) {
diff --git a/src/structures/GuildMember.js b/src/structures/GuildMember.js
index 60a498a3e..c3cb86cdb 100644
--- a/src/structures/GuildMember.js
+++ b/src/structures/GuildMember.js
@@ -36,7 +36,7 @@ class GuildMember {
/**
* The ID of the last message sent by the member in their guild, if one was sent.
- * @type {?string}
+ * @type {?Snowflake}
*/
this.lastMessageID = null;
}
@@ -68,13 +68,13 @@ class GuildMember {
/**
* The voice session ID of this member, if any
- * @type {?string}
+ * @type {?Snowflake}
*/
this.voiceSessionID = data.session_id;
/**
* The voice channel ID of this member, if any
- * @type {?string}
+ * @type {?Snowflake}
*/
this.voiceChannelID = data.channel_id;
@@ -120,7 +120,7 @@ class GuildMember {
/**
* A list of roles that are applied to this GuildMember, mapped by the role ID.
- * @type {Collection}
+ * @type {Collection}
* @readonly
*/
get roles() {
@@ -175,7 +175,7 @@ class GuildMember {
/**
* The ID of this user
- * @type {string}
+ * @type {Snowflake}
* @readonly
*/
get id() {
diff --git a/src/structures/Message.js b/src/structures/Message.js
index cf4c481f0..7d6708f10 100644
--- a/src/structures/Message.js
+++ b/src/structures/Message.js
@@ -33,7 +33,7 @@ class Message {
setup(data) { // eslint-disable-line complexity
/**
* The ID of the message (unique in the channel it was sent)
- * @type {string}
+ * @type {Snowflake}
*/
this.id = data.id;
@@ -75,7 +75,7 @@ class Message {
this.tts = data.tts;
/**
- * A random number used for checking message delivery
+ * A random number or string used for checking message delivery
* @type {string}
*/
this.nonce = data.nonce;
@@ -94,7 +94,7 @@ class Message {
/**
* A collection of attachments in the message - e.g. Pictures - mapped by their ID.
- * @type {Collection}
+ * @type {Collection}
*/
this.attachments = new Collection();
for (const attachment of data.attachments) this.attachments.set(attachment.id, new Attachment(this, attachment));
@@ -114,9 +114,9 @@ class Message {
/**
* An object containing a further users, roles or channels collections
* @type {Object}
- * @property {Collection} mentions.users Mentioned users, maps their ID to the user object.
- * @property {Collection} mentions.roles Mentioned roles, maps their ID to the role object.
- * @property {Collection} mentions.channels Mentioned channels,
+ * @property {Collection} mentions.users Mentioned users, maps their ID to the user object.
+ * @property {Collection} mentions.roles Mentioned roles, maps their ID to the role object.
+ * @property {Collection} mentions.channels Mentioned channels,
* maps their ID to the channel object.
* @property {boolean} mentions.everyone Whether or not @everyone was mentioned.
*/
@@ -156,7 +156,7 @@ class Message {
/**
* A collection of reactions to this message, mapped by the reaction "id".
- * @type {Collection}
+ * @type {Collection}
*/
this.reactions = new Collection();
@@ -169,7 +169,7 @@ class Message {
/**
* ID of the webhook that sent the message, if applicable
- * @type {?string}
+ * @type {?Snowflake}
*/
this.webhookID = data.webhook_id || null;
diff --git a/src/structures/MessageAttachment.js b/src/structures/MessageAttachment.js
index 29dfb524e..6bd1030f9 100644
--- a/src/structures/MessageAttachment.js
+++ b/src/structures/MessageAttachment.js
@@ -23,7 +23,7 @@ class MessageAttachment {
setup(data) {
/**
* The ID of this attachment
- * @type {string}
+ * @type {Snowflake}
*/
this.id = data.id;
diff --git a/src/structures/MessageCollector.js b/src/structures/MessageCollector.js
index f84ecbda3..54e9b1539 100644
--- a/src/structures/MessageCollector.js
+++ b/src/structures/MessageCollector.js
@@ -61,7 +61,7 @@ class MessageCollector extends EventEmitter {
/**
* A collection of collected messages, mapped by message ID.
- * @type {Collection}
+ * @type {Collection}
*/
this.collected = new Collection();
diff --git a/src/structures/MessageReaction.js b/src/structures/MessageReaction.js
index 30c555f9d..eada0d7a5 100644
--- a/src/structures/MessageReaction.js
+++ b/src/structures/MessageReaction.js
@@ -27,7 +27,7 @@ class MessageReaction {
/**
* The users that have given this reaction, mapped by their ID.
- * @type {Collection}
+ * @type {Collection}
*/
this.users = new Collection();
diff --git a/src/structures/OAuth2Application.js b/src/structures/OAuth2Application.js
index b7c728581..cbe162466 100644
--- a/src/structures/OAuth2Application.js
+++ b/src/structures/OAuth2Application.js
@@ -17,7 +17,7 @@ class OAuth2Application {
setup(data) {
/**
* The ID of the app
- * @type {string}
+ * @type {Snowflake}
*/
this.id = data.id;
diff --git a/src/structures/PartialGuild.js b/src/structures/PartialGuild.js
index 407212e2f..e16f67bee 100644
--- a/src/structures/PartialGuild.js
+++ b/src/structures/PartialGuild.js
@@ -24,7 +24,7 @@ class PartialGuild {
setup(data) {
/**
* The ID of this guild
- * @type {string}
+ * @type {Snowflake}
*/
this.id = data.id;
diff --git a/src/structures/PartialGuildChannel.js b/src/structures/PartialGuildChannel.js
index e58a6bb91..13cf4c2f9 100644
--- a/src/structures/PartialGuildChannel.js
+++ b/src/structures/PartialGuildChannel.js
@@ -23,7 +23,7 @@ class PartialGuildChannel {
setup(data) {
/**
* The ID of this guild channel
- * @type {string}
+ * @type {Snowflake}
*/
this.id = data.id;
diff --git a/src/structures/PermissionOverwrites.js b/src/structures/PermissionOverwrites.js
index 9b2f536f5..8044be45a 100644
--- a/src/structures/PermissionOverwrites.js
+++ b/src/structures/PermissionOverwrites.js
@@ -17,7 +17,7 @@ class PermissionOverwrites {
setup(data) {
/**
* The ID of this overwrite, either a user ID or a role ID
- * @type {string}
+ * @type {Snowflake}
*/
this.id = data.id;
diff --git a/src/structures/ReactionEmoji.js b/src/structures/ReactionEmoji.js
index 58d00b9b8..b504a3635 100644
--- a/src/structures/ReactionEmoji.js
+++ b/src/structures/ReactionEmoji.js
@@ -19,7 +19,7 @@ class ReactionEmoji {
/**
* The ID of this reaction emoji.
- * @type {string}
+ * @type {?Snowflake}
*/
this.id = id;
}
diff --git a/src/structures/Role.js b/src/structures/Role.js
index c15ff4be0..329a2264c 100644
--- a/src/structures/Role.js
+++ b/src/structures/Role.js
@@ -25,7 +25,7 @@ class Role {
setup(data) {
/**
* The ID of the role (unique to the guild it is part of)
- * @type {string}
+ * @type {Snowflake}
*/
this.id = data.id;
@@ -103,7 +103,7 @@ class Role {
/**
* The cached guild members that have this role.
- * @type {Collection}
+ * @type {Collection}
* @readonly
*/
get members() {
diff --git a/src/structures/TextChannel.js b/src/structures/TextChannel.js
index 6e98e9e89..38e6a56be 100644
--- a/src/structures/TextChannel.js
+++ b/src/structures/TextChannel.js
@@ -29,7 +29,7 @@ class TextChannel extends GuildChannel {
/**
* A collection of members that can see this channel, mapped by their ID.
- * @type {Collection}
+ * @type {Collection}
* @readonly
*/
get members() {
diff --git a/src/structures/User.js b/src/structures/User.js
index fd0a8d305..6beb3f352 100644
--- a/src/structures/User.js
+++ b/src/structures/User.js
@@ -22,7 +22,7 @@ class User {
setup(data) {
/**
* The ID of the user
- * @type {string}
+ * @type {Snowflake}
*/
this.id = data.id;
@@ -52,7 +52,7 @@ class User {
/**
* The ID of the last message sent by the user, if one was sent.
- * @type {?string}
+ * @type {?Snowflake}
*/
this.lastMessageID = null;
}
diff --git a/src/structures/VoiceChannel.js b/src/structures/VoiceChannel.js
index 848a6d513..ad18a94b0 100644
--- a/src/structures/VoiceChannel.js
+++ b/src/structures/VoiceChannel.js
@@ -11,7 +11,7 @@ class VoiceChannel extends GuildChannel {
/**
* The members in this voice channel.
- * @type {Collection}
+ * @type {Collection}
*/
this.members = new Collection();
diff --git a/src/structures/Webhook.js b/src/structures/Webhook.js
index 96984ffe0..153d8f3a5 100644
--- a/src/structures/Webhook.js
+++ b/src/structures/Webhook.js
@@ -43,19 +43,19 @@ class Webhook {
/**
* The ID of the webhook
- * @type {string}
+ * @type {Snowflake}
*/
this.id = data.id;
/**
* The guild the webhook belongs to
- * @type {string}
+ * @type {Snowflake}
*/
this.guildID = data.guild_id;
/**
* The channel the webhook belongs to
- * @type {string}
+ * @type {Snowflake}
*/
this.channelID = data.channel_id;
diff --git a/src/structures/interface/TextBasedChannel.js b/src/structures/interface/TextBasedChannel.js
index 0daf8600a..5e06cb543 100644
--- a/src/structures/interface/TextBasedChannel.js
+++ b/src/structures/interface/TextBasedChannel.js
@@ -11,13 +11,13 @@ class TextBasedChannel {
constructor() {
/**
* A collection containing the messages sent to this channel.
- * @type {Collection}
+ * @type {Collection}
*/
this.messages = new Collection();
/**
* The ID of the last message in the channel, if one was sent.
- * @type {?string}
+ * @type {?Snowflake}
*/
this.lastMessageID = null;
}
diff --git a/src/util/Snowflake.js b/src/util/Snowflake.js
new file mode 100644
index 000000000..ad8259977
--- /dev/null
+++ b/src/util/Snowflake.js
@@ -0,0 +1,62 @@
+const Long = require('long');
+
+// Discord epoch (2015-01-01T00:00:00.000Z)
+const EPOCH = 1420070400000;
+let INCREMENT = 0;
+
+function pad(v, n, c = '0') {
+ return String(v).length >= n ? String(v) : (String(c).repeat(n) + v).slice(-n);
+}
+
+ /**
+ * A deconstructed snowflake
+ * @typedef {Object} DeconstructedSnowflake
+ * @property {Date} date Date in the snowflake
+ * @property {number} workerID Worker id in the snowflake
+ * @property {number} processID Process id in the snowflake
+ * @property {number} increment Increment in the snowflake
+ * @property {string} binary Binary representation of the snowflake
+ */
+
+ /**
+ * A Twitter snowflake, except the epoch is 2015-01-01T00:00:00.000Z
+ * ```
+ * If we have a snowflake '266241948824764416' we can represent it as binary:
+ *
+ * 64 22 17 12 0
+ * 000000111011000111100001101001000101000000 00001 00000 000000000000
+ * number of ms since discord epoch worker pid increment
+ * ```
+ * Note: this generator hardcodes the worker id as 1 and the process id as 0
+ * @typedef {string} Snowflake
+ * @class Snowflake
+ */
+class Snowflake {
+ /**
+ * Generate a Discord snowflake
+ * @returns {Snowflake} The generated snowflake
+ */
+ static generate() {
+ if (INCREMENT >= 4095) INCREMENT = 0;
+ const BINARY = `${pad((Date.now() - EPOCH).toString(2), 42)}0000100000${pad((INCREMENT++).toString(2), 12)}`;
+ return Long.fromString(BINARY, 2).toString();
+ }
+
+ /**
+ * Deconstruct a Discord snowflake
+ * @param {Snowflake} snowflake Snowflake to deconstruct
+ * @returns {DeconstructedSnowflake} Deconstructed snowflake
+ */
+ static deconstruct(snowflake) {
+ const BINARY = pad(Long.fromString(snowflake).toString(2), 64);
+ return {
+ date: new Date(parseInt(BINARY.substring(0, 42), 2) + EPOCH),
+ workerID: parseInt(BINARY.substring(42, 47), 2),
+ processID: parseInt(BINARY.substring(47, 52), 2),
+ increment: parseInt(BINARY.substring(52, 64), 2),
+ binary: BINARY,
+ };
+ }
+}
+
+module.exports = Snowflake;