Pushing changes

This commit is contained in:
2017-03-23 23:52:08 -05:00
parent 6075860b82
commit ac667ec74f
1465 changed files with 345149 additions and 3 deletions

873
node_modules/discordie/lib/Constants.js generated vendored Normal file
View File

@@ -0,0 +1,873 @@
"use strict";
const Permissions = {
General: {
CREATE_INSTANT_INVITE: 1 << 0,
KICK_MEMBERS: 1 << 1,
BAN_MEMBERS: 1 << 2,
ADMINISTRATOR: 1 << 3,
MANAGE_CHANNELS: 1 << 4,
MANAGE_GUILD: 1 << 5,
CHANGE_NICKNAME: 1 << 26,
MANAGE_NICKNAMES: 1 << 27,
MANAGE_ROLES: 1 << 28,
MANAGE_WEBHOOKS: 1 << 29,
MANAGE_EMOJIS: 1 << 30,
},
Text: {
READ_MESSAGES: 1 << 10,
SEND_MESSAGES: 1 << 11,
SEND_TTS_MESSAGES: 1 << 12,
MANAGE_MESSAGES: 1 << 13,
EMBED_LINKS: 1 << 14,
ATTACH_FILES: 1 << 15,
READ_MESSAGE_HISTORY: 1 << 16,
MENTION_EVERYONE: 1 << 17,
EXTERNAL_EMOTES: 1 << 18,
ADD_REACTIONS: 1 << 6,
},
Voice: {
CONNECT: 1 << 20,
SPEAK: 1 << 21,
MUTE_MEMBERS: 1 << 22,
DEAFEN_MEMBERS: 1 << 23,
MOVE_MEMBERS: 1 << 24,
USE_VAD: 1 << 25,
}
};
const ChannelGeneral = {
CREATE_INSTANT_INVITE: Permissions.General.CREATE_INSTANT_INVITE,
MANAGE_CHANNEL: Permissions.General.MANAGE_CHANNELS,
MANAGE_PERMISSIONS: Permissions.General.MANAGE_ROLES
};
const PermissionSpecs = {
Role: Permissions,
TextChannel: {
General: ChannelGeneral,
Text: Permissions.Text
},
VoiceChannel: {
General: ChannelGeneral,
Voice: Permissions.Voice
}
};
const PermissionsDefault = [
Permissions.General.CREATE_INSTANT_INVITE,
Permissions.General.CHANGE_NICKNAME,
Permissions.Text.READ_MESSAGES,
Permissions.Text.SEND_MESSAGES,
Permissions.Text.SEND_TTS_MESSAGES,
Permissions.Text.EMBED_LINKS,
Permissions.Text.ATTACH_FILES,
Permissions.Text.READ_MESSAGE_HISTORY,
Permissions.Text.MENTION_EVERYONE,
Permissions.Text.EXTERNAL_EMOTES,
Permissions.Text.ADD_REACTIONS,
Permissions.Voice.CONNECT,
Permissions.Voice.SPEAK,
Permissions.Voice.USE_VAD
].reduce((perms, perm) => perms | perm, 0);
const Constants = {
ReadyState: {
CONNECTING: 0,
OPEN: 1,
CLOSING: 2,
CLOSED: 3,
},
DiscordieState: {
DISCONNECTED: 0,
LOGGING_IN: 0,
LOGGED_IN: 0,
CONNECTING: 0,
CONNECTED: 0,
},
Errors: {
VOICE_DISCONNECTED_FROM_GATEWAY:
error => `Disconnected from gateway socket: ${error}`,
VOICE_SESSION_INVALIDATED:
`Session has been invalidated`,
VOICE_CHANGING_SERVER:
"Changing server",
VOICE_SESSION_DESCRIPTION_TIMEOUT:
"Failed to connect to voice: VOICE_SESSION_DESCRIPTION timed out",
VOICE_TRANSPORT_TIMEOUT:
"Voice transport timed out",
VOICE_KICKED_FROM_CHANNEL:
"Kicked from the voice channel",
VOICE_CONNECTED_FROM_ANOTHER_LOCATION:
"Connected from another location",
VOICE_GUILD_UNAVAILABLE:
"Guild unavailable",
VOICE_CALL_UNAVAILABLE:
"Call unavailable",
VOICE_MANUAL_DISCONNECT:
"Manual disconnect"
},
Events: {
// =============================== INTERNAL ===============================
GATEWAY_OPEN: 0,
GATEWAY_HELLO: 0,
GATEWAY_DISPATCH: 0,
GATEWAY_DISCONNECT: 0,
GATEWAY_UNHANDLED_MESSAGE: 0,
VOICESOCKET_OPEN: 0,
VOICESOCKET_DISCONNECT: 0,
VOICESOCKET_UNHANDLED_MESSAGE: 0,
VOICE_READY: 0,
VOICE_SESSION_DESCRIPTION: 0,
VOICE_SPEAKING: 0,
COLLECTION_READY: 0,
READY_TASK_FINISHED: 0,
LOADED_MORE_MESSAGES: 0,
LOADED_PINNED_MESSAGES: 0,
LOADED_GUILD_BANS: 0,
ANY_GATEWAY_READY: 0, // fires on every gateway (primary and secondary)
// No secondary gateways as of botapi multiserver voice release @2016-03-11
// ================================= REST =================================
REQUEST_AUTH_LOGIN_ERROR: 0, REQUEST_AUTH_LOGIN_SUCCESS: 0,
REQUEST_GATEWAY_ERROR: 0, REQUEST_GATEWAY_SUCCESS: 0,
// ================================ CUSTOM ================================
/**
* Emitted when login or gateway auth failed,
* or primary gateway socket disconnects, closing all open sockets.
*
* Not emitted if disconnected using `client.disconnect()`.
*
* Property `error` can contain following `Error` messages:
*
* - ** `"Login failed"` **
*
* Set if REST `/login` endpoint returned an error.
*
* - ** `"Could not get gateway"` **
*
* Set if REST `/gateway` endpoint returned an error.
*
* - ** `"No token specified"` **
*
* Should never fire under normal circumstances.
* Set if attempted to open gateway socket without token.
*
* - ** `"Heartbeat ACK did not arrive in time"` **
*
* - ** `"Failed to connect to gateway: READY timed out"` **
*
* Set if server did not return a `READY` (initialization) packet
* within 5 minutes.
*
* - ** `"Disconnected from primary gateway"` **
*
* Emitted for any other errors. Property `error.exception` will contain
* the `Number` error code from websocket.
*
* Original `Error` object or `Number` error code is stored
* within `DiscordieError` and can be inspected using `error.exception`.
* @event DISCONNECTED
* @property {DiscordieError} error
* @property {Boolean} [autoReconnect]
* Only present (and set to true) when auto-reconnect is enabled.
* @property {Number} [delay]
* Delay in milliseconds until next reconnect attempt.
* Only present when auto-reconnect is enabled.
*/
DISCONNECTED: 0,
/**
* Emitted when the `Discordie` instance is ready to use.
*
* All objects except unavailable guilds and offline members of large guilds
* (250+ members) will be in cache when this event fires.
*
* You can request offline members using `client.Users.fetchMembers()`.
* See documentation for `IUserCollection.fetchMembers`.
*
* > **Note: Any other events may fire before `GATEWAY_READY`.**
* @event GATEWAY_READY
* @property {GatewaySocket} socket
* @property {Object} data - Raw event data
* @example
* var client = new Discordie();
* client.connect({ token: "bot_token" });
* client.Dispatcher.on("GATEWAY_READY", e => {
* // all objects except offline members of large guilds
* // have been cached at this point
* });
*/
GATEWAY_READY: 0, // fires only on primary gateway
/**
* Emitted after gateway connection is resumed after a disconnect.
*
* Connections can be resumable if disconnected for short period of time.
*
* Does not clear cache unlike `GATEWAY_READY`.
* @event GATEWAY_RESUMED
* @property {GatewaySocket} socket
* @property {Object} data - Raw event data
*/
GATEWAY_RESUMED: 0,
/**
* Emitted when a new voice connection is fully initialized.
* @event VOICE_CONNECTED
* @property {VoiceSocket} socket
* @property {IVoiceConnection} voiceConnection
*/
VOICE_CONNECTED: 0,
/**
* Emitted when a voice socket disconnects.
*
* Property `error` can contain `null` or following `Error` messages:
*
* - ** `` `Disconnected from gateway socket: ${errorCode}` `` **
*
* Set when an error occured on gateway socket.
*
* - ** `"Failed to connect to voice: VOICE_SESSION_DESCRIPTION timed out"` **
*
* - ** `"Voice transport timed out"` **
*
* - ** `"Connected from another location"` **
*
* - ** `"Kicked from the voice channel"` **
*
* Set when server kicks the user from a voice channel,
* user accounts do not support botapi multiserver voice.
*
* - ** `"Changing server"` **
*
* - ** `"Guild unavailable"` **
*
* This event fires before `GUILD_UNAVAILABLE`.
*
* - ** `"Call unavailable"` **
*
* - ** `"Session has been invalidated"` **
*
* Set when the gateway connection could not be resumed and new
* session has been started. Fires for all previously active voice
* connections.
*
* - ** `"Manual disconnect"` **
*
* Set if `channel.leave()` or `client.disconnect()` was called.
* Will also be set if channel or guild gets deleted (`VOICE_DISCONNECTED`
* emits before `GUILD_DELETE` or `CHANNEL_DELETE` respectively).
*
* If Discord decides to change voice servers - check if `endpointAwait`
* is not null and contains a `Promise` which will resolve when a new
* connection is fully initialized (`VOICE_CONNECTED` will also fire)
* and reject if reconnect fails.
*
* Calls on `.join()` for channels of the same guild will return the same
* promise.
*
* Call `.leave()` on pending connection's channel to cancel a reconnect.
* Pending promise (`endpointAwait`) will be rejected with
* `Error` message "Cancelled".
*
* If a gateway disconnects, pending connections that belong to the gateway
* will be rejected with `Error` message `"Gateway disconnected"`.
*
* @event VOICE_DISCONNECTED
* @property {VoiceSocket} socket
* @property {IVoiceConnection} voiceConnection
* @property {Error|null} error
* @property {boolean} manual
* Indicating whether was caused by `IVoiceChannel.leave()` or
* `Discordie.disconnect()`, also true if channel/guild has been deleted
* @property {Promise<VoiceConnectionInfo, Error|Number>} endpointAwait
* Indicates whether there is a reconnect pending, reconnects can occur when
* Discord decides to move users to another voice server
* @example
* client.Dispatcher.on("VOICE_DISCONNECTED", e => {
* const channel = e.voiceConnection.channel;
* if (!channel) return console.log("Channel has been deleted");
*
* if (e.endpointAwait) {
* // handle reconnect instantly if it's a server-switch disconnect
* // transparently creates same promise as `oldChannel.join()`
* // see the `reconnect` function below
*
* // Note: During Discord outages it will act like the official client
* // and wait for an endpoint. Sometimes this can take a very
* // long time. To cancel pending reconnect just call leave on
* // the voice channel. Pending promise will reject with
* // `Error` message "Cancelled".
*
*
* e.endpointAwait
* .then(info => onConnected(info))
* .catch(err => {
* // server switching failed, do a regular backoff
* setTimeout(() => reconnect(channel), 5000);
* });
* return;
* }
*
* // normal disconnect
* setTimeout(() => reconnect(channel), 5000);
* });
* function reconnect(channel) {
* var channelName = channel.name;
* channel.join()
* .then(info => onConnected(info))
* .catch(err => console.log("Failed to connect to " + channelName));
* // this example will stop reconnecting after 1 attempt
* // you can continue trying to reconnect
* }
* function onConnected(info) {
* console.log("Connected to " + info.voiceConnection.channel.name);
* }
*/
VOICE_DISCONNECTED: 0,
/**
* Emitted when guild becomes unavailable.
* Guild is deleted from cache until another `GUILD_CREATE`.
* @event GUILD_UNAVAILABLE
* @property {GatewaySocket} socket
* @property {String} guildId
*/
GUILD_UNAVAILABLE: 0,
/**
* Emitted when call becomes unavailable.
* @event CALL_UNAVAILABLE
* @property {GatewaySocket} socket
* @property {String} channelId
*/
CALL_UNAVAILABLE: 0,
/**
* Emitted when current user is being rung in a call.
* @event CALL_RING
* @property {GatewaySocket} socket
* @property {IDirectMessageChannel} channel
*/
CALL_RING: 0,
/**
* Emitted when `username`, `avatar` or `discriminator` difference detected
* in an incoming `PRESENCE_UPDATE` event.
* @event PRESENCE_MEMBER_INFO_UPDATE
* @property {GatewaySocket} socket
* @property {Object} old - Old instance of internal User model (immutable)
* @property {Object} new - New instance of internal User model (immutable)
*/
PRESENCE_MEMBER_INFO_UPDATE: 0,
/**
* Emitted when user leaves voice channel.
* Fields `newChannelId`/`newGuildId` contain ids that will appear in
* `VOICE_CHANNEL_JOIN` event that will follow if user has moved to
* another channel, otherwise null.
* @event VOICE_CHANNEL_LEAVE
* @property {GatewaySocket} socket
* @property {IUser} user
* @property {IChannel|null} channel
* @property {String} channelId
* @property {String} guildId
* @property {String|null} newChannelId -
* Next channel id if user moved to another channel
* @property {String|null} newGuildId -
* Next guild id if user moved to another channel
*/
VOICE_CHANNEL_LEAVE: 0,
/**
* Emitted when user joins voice channel.
* @event VOICE_CHANNEL_JOIN
* @property {GatewaySocket} socket
* @property {IUser} user
* @property {IChannel} channel
* @property {String} channelId
* @property {String} guildId
*/
VOICE_CHANNEL_JOIN: 0,
/**
* Emitted when user self mute change is detected.
* Manual client-side mute.
* @event VOICE_USER_SELF_MUTE
* @property {GatewaySocket} socket
* @property {IUser} user
* @property {IChannel} channel
* @property {String} channelId
* @property {String} guildId
* @property {boolean} state - Current state (is self muted)
*/
VOICE_USER_SELF_MUTE: 0,
/**
* Emitted when user self deaf change is detected.
* Manual client-side deafen.
* @event VOICE_USER_SELF_DEAF
* @property {GatewaySocket} socket
* @property {IUser} user
* @property {IChannel} channel
* @property {String} channelId
* @property {String} guildId
* @property {boolean} state - Current state (is self deafened)
*/
VOICE_USER_SELF_DEAF: 0,
/**
* Emitted when user mute change is detected.
* Global server-side mute.
* @event VOICE_USER_MUTE
* @property {GatewaySocket} socket
* @property {IUser} user
* @property {IChannel} channel
* @property {String} channelId
* @property {String} guildId
* @property {boolean} state - Current state (is muted globally)
*/
VOICE_USER_MUTE: 0,
/**
* Emitted when user deaf change is detected.
* Global server-side deafen.
* @event VOICE_USER_DEAF
* @property {GatewaySocket} socket
* @property {IUser} user
* @property {IChannel} channel
* @property {String} channelId
* @property {String} guildId
* @property {boolean} state - Current state (is deafened globally)
*/
VOICE_USER_DEAF: 0,
// ============================= PROXY EVENTS =============================
/**
* @event MESSAGE_CREATE
* @property {GatewaySocket} socket
* @property {IMessage} message
*/
MESSAGE_CREATE: 0,
/**
* Emitted when user deletes their message.
* Contains null `message` if not cached.
* @event MESSAGE_DELETE
* @property {GatewaySocket} socket
* @property {String} channelId
* @property {String} messageId
* @property {IMessage|null} message
*/
MESSAGE_DELETE: 0,
/**
* Emitted when a bot deletes more than 1 message at once.
* @event MESSAGE_DELETE_BULK
* @property {GatewaySocket} socket
* @property {String} channelId
* @property {Array<String>} messageIds
* @property {Array<IMessage>} messages
* Array of known deleted messages, can be empty
*/
MESSAGE_DELETE_BULK: 0,
/**
* Emitted when user updates their message.
* Contains null `message` if not cached.
* @event MESSAGE_UPDATE
* @property {GatewaySocket} socket
* @property {IMessage|null} message
* @property {Object} data - Raw message object received from server
*/
MESSAGE_UPDATE: 0,
/**
* Emitted when on changes for username, avatar, status or game.
*
* Emitted multiple times for each shared guild with the local user and the
* user presence is for.
*
* Compare `user.status` and `user.previousStatus` to detect status changes.
*
* Games can be checked with `user.game` and `user.previousGame`
* (and helpers for names `user.gameName` and `user.previousGameName`)
* respectively.
*
* > **Note:** Property `member` will contain `IUser` instance if user
* > has left the `guild`.
*
* @event PRESENCE_UPDATE
* @property {GatewaySocket} socket
* @property {IGuild} guild
* @property {IUser} user
* @property {IGuildMember|IUser} member
*/
PRESENCE_UPDATE: 0,
/**
* @event TYPING_START
* @property {GatewaySocket} socket
* @property {IUser} user
* @property {Number} timestamp - Unix timestamp
* @property {IChannel} channel
*/
TYPING_START: 0,
/**
* @event CHANNEL_CREATE
* @property {GatewaySocket} socket
* @property {IChannel} channel
*/
CHANNEL_CREATE: 0,
/**
* @event CHANNEL_DELETE
* @property {GatewaySocket} socket
* @property {String} channelId
* @property {Object} data - Raw channel object received from server
*/
CHANNEL_DELETE: 0,
/**
* @event CHANNEL_UPDATE
* @property {GatewaySocket} socket
* @property {IChannel} channel
* @property {Function} getChanges
* Function returning an object `{before: ..., after: ...}` containing two
* raw channel objects.
*/
CHANNEL_UPDATE: 0,
/**
* Emitted when a user has been added to a group dm.
* @event CHANNEL_RECIPIENT_ADD
* @property {GatewaySocket} socket
* @property {IDirectMessageChannel} channel
* @property {IUser} user
*/
CHANNEL_RECIPIENT_ADD: 0,
/**
* Emitted when a user has been removed or left from a group dm.
* @event CHANNEL_RECIPIENT_REMOVE
* @property {GatewaySocket} socket
* @property {IDirectMessageChannel} channel
* @property {IUser} user
*/
CHANNEL_RECIPIENT_REMOVE: 0,
/**
* @event GUILD_CREATE
* @property {GatewaySocket} socket
* @property {IGuild} guild
* @property {boolean} becameAvailable
* Indicates whether the guild has recovered from unavailable state
*/
GUILD_CREATE: 0,
/**
* @event GUILD_DELETE
* @property {GatewaySocket} socket
* @property {String} guildId
* @property {Object} data - Raw guild object received from server
* @property {Function} getCachedData
* Function returning a raw guild object or null.
*/
GUILD_DELETE: 0,
/**
* @event GUILD_UPDATE
* @property {GatewaySocket} socket
* @property {IGuild} guild
* @property {Function} getChanges
* Function returning an object `{before: ..., after: ...}` containing two
* raw guild objects.
*/
GUILD_UPDATE: 0,
/**
* @event GUILD_MEMBER_ADD
* @property {GatewaySocket} socket
* @property {IGuild} guild
* @property {IGuildMember} member
*/
GUILD_MEMBER_ADD: 0,
/**
* Emitted when any other member of a joined guild leaves,
* events for self are blocked internally due to race condition between
* `GUILD_DELETE` and `GUILD_MEMBER_REMOVE`.
* @event GUILD_MEMBER_REMOVE
* @property {GatewaySocket} socket
* @property {IGuild} guild
* @property {IUser} user
* @property {Object} data - Raw data received from server
* @property {Function} getCachedData
* Function returning a raw member object or null.
*/
GUILD_MEMBER_REMOVE: 0,
/**
* @event GUILD_MEMBER_UPDATE
* @property {GatewaySocket} socket
* @property {IGuild} guild
* @property {IGuildMember} member
* @property {Array<IRole>} rolesAdded
* @property {Array<IRole>} rolesRemoved
* @property {String|null} previousNick
* @property {Function} getChanges
* Function returning an object `{before: ..., after: ...}` containing two
* raw member objects.
*/
GUILD_MEMBER_UPDATE: 0,
/**
* @event GUILD_BAN_ADD
* @property {GatewaySocket} socket
* @property {IGuild} guild
* @property {IUser} user
*/
GUILD_BAN_ADD: 0,
/**
* @event GUILD_BAN_REMOVE
* @property {GatewaySocket} socket
* @property {IGuild} guild
* @property {IUser} user
*/
GUILD_BAN_REMOVE: 0,
/**
* @event GUILD_ROLE_CREATE
* @property {GatewaySocket} socket
* @property {IGuild} guild
* @property {IRole} role
*/
GUILD_ROLE_CREATE: 0,
/**
* @event GUILD_ROLE_UPDATE
* @property {GatewaySocket} socket
* @property {IGuild} guild
* @property {IRole} role
* @property {Function} getChanges
* Function returning an object `{before: ..., after: ...}` containing two
* raw role objects.
*/
GUILD_ROLE_UPDATE: 0,
/**
* @event GUILD_ROLE_DELETE
* @property {GatewaySocket} socket
* @property {IGuild} guild
* @property {String} roleId
* @property {Function} getCachedData
* Function returning a raw role object or null.
*/
GUILD_ROLE_DELETE: 0,
/**
* @event GUILD_EMOJIS_UPDATE
* @property {GatewaySocket} socket
* @property {IGuild} guild
* @property {Function} getChanges
* Function returning an object `{before: ..., after: ...}` containing two
* full emoji arrays in format provided by Discord.
*/
GUILD_EMOJIS_UPDATE: 0,
/**
* @event CALL_CREATE
* @property {GatewaySocket} socket
* @property {IDirectMessageChannel} channel
* @property {ICall} call
*/
CALL_CREATE: 0,
/**
* @event CALL_DELETE
* @property {GatewaySocket} socket
* @property {String} channelId
* @property {Object} data - Raw object received from server
*/
CALL_DELETE: 0,
/**
* @event CALL_UPDATE
* @property {GatewaySocket} socket
* @property {IDirectMessageChannel} channel
* @property {ICall} call
*/
CALL_UPDATE: 0,
/**
* Emitted when a webhook is updated.
* @event WEBHOOKS_UPDATE
* @property {GatewaySocket} socket
* @property {IGuild} guild
* @property {IChannel} channel
* @property {Object} data - Raw object received from server
*/
WEBHOOKS_UPDATE: 0,
/**
* Emitted when a reaction is added to a message.
* @event MESSAGE_REACTION_ADD
* @property {GatewaySocket} socket
* @property {IUser|null} user
* @property {IChannel|null} channel
* @property {IMessage|null} message
* @property {Object} emoji
* Partial emoji `{id: String|null, name: String}`
* @property {Object} data - Raw object received from server
*/
MESSAGE_REACTION_ADD: 0,
/**
* Emitted when a reaction is removed from a message.
* @event MESSAGE_REACTION_REMOVE
* @property {GatewaySocket} socket
* @property {IUser|null} user
* @property {IChannel|null} channel
* @property {IMessage|null} message
* @property {Object} emoji
* Partial emoji `{id: String|null, name: String}`
* @property {Object} data - Raw object received from server
*/
MESSAGE_REACTION_REMOVE: 0,
/**
* Emitted when message reactions are cleared.
* @event MESSAGE_REACTION_REMOVE_ALL
* @property {GatewaySocket} socket
* @property {IChannel|null} channel
* @property {IMessage|null} message
* @property {Object} data - Raw object received from server
* @property {Function} getCachedData
* Function returning an array of removed reactions or null.
*/
MESSAGE_REACTION_REMOVE_ALL: 0,
},
ChannelTypes: {
GUILD_TEXT: 0,
DM: 1,
GUILD_VOICE: 2,
GROUP_DM: 3
},
MessageTypes: {
DEFAULT: 0,
RECIPIENT_ADD: 1,
RECIPIENT_REMOVE: 2,
CALL: 3,
CHANNEL_NAME_CHANGE: 4,
CHANNEL_ICON_CHANGE: 5,
CHANNEL_PINNED_MESSAGE: 6
},
EncryptionModes: {
plain: 0,
xsalsa20_poly1305: 0,
},
ME: "@me",
Endpoints: {
CDN_AVATAR: (userId, hash, format) => `/avatars/${userId}/${hash}.${format || "jpg"}`,
CDN_DM_ICON: (channelId, hash) => `/channel-icons/${channelId}/${hash}.jpg`,
CDN_GUILD_ICON: (guildId, hash) => `/icons/${guildId}/${hash}.jpg`,
CDN_GUILD_SPLASH: (guildId, hash) => `/splashes/${guildId}/${hash}.jpg`,
CDN_EMOJI: emojiId => `/emojis/${emojiId}.png`,
LOGIN: "/auth/login",
ME: "/users/@me",
TYPING: channelId => `/channels/${channelId}/typing`,
CHANNEL_PERMISSIONS: channelId => `/channels/${channelId}/permissions`,
CHANNEL_RECIPIENTS: channelId => `/channels/${channelId}/recipients`,
CHANNEL_WEBHOOKS: channelId => `/channels/${channelId}/webhooks`,
MESSAGES: channelId => `/channels/${channelId}/messages`,
PINS: channelId => `/channels/${channelId}/pins`,
CHANNELS: "/channels",
SETTINGS: "/users/@me/settings",
GUILD_CHANNELS: guildId => `/guilds/${guildId}/channels`,
GUILDS: "/guilds",
INSTANT_INVITES: channelId => `/channels/${channelId}/invites`,
CALL: channelId => `/channels/${channelId}/call`,
CALL_RING: channelId => `/channels/${channelId}/call/ring`,
CALL_STOP_RINGING: channelId => `/channels/${channelId}/call/stop-ringing`,
REACTIONS: (channelId, messageId) =>
`/channels/${channelId}/messages/${messageId}/reactions`,
REACTIONS_EMOJI: (channelId, messageId, emoji) =>
`/channels/${channelId}/messages/${messageId}/reactions/${emoji}`,
REACTION: (channelId, messageId, emoji, userId) =>
`/channels/${channelId}/messages/${messageId}/reactions/${emoji}/${userId}`,
USER_CHANNELS: userId => `/users/${userId}/channels`,
GUILD_MEMBERS: guildId => `/guilds/${guildId}/members`,
GUILD_BANS: guildId => `/guilds/${guildId}/bans`,
GUILD_ROLES: guildId => `/guilds/${guildId}/roles`,
GUILD_INSTANT_INVITES: guildId => `/guilds/${guildId}/invites`,
GUILD_EMBED: guildId => `/guilds/${guildId}/embed`,
GUILD_PRUNE: guildId => `/guilds/${guildId}/prune`,
GUILD_REGIONS: guildId => `/guilds/${guildId}/regions`,
GUILD_EMOJIS: guildId => `/guilds/${guildId}/emojis`,
GUILD_EMOJI: (guildId, emojiId) => `/guilds/${guildId}/emojis/${emojiId}`,
GUILD_WEBHOOKS: guildId => `/guilds/${guildId}/webhooks`,
WEBHOOK: webhookId => `/webhooks/${webhookId}`,
USERS: "/users",
LOGOUT: "/auth/logout",
REGISTER: "/auth/register",
INVITE: "/invite",
REGIONS: "/voice/regions",
ICE: "/voice/ice",
GATEWAY: "/gateway",
OAUTH2_APPLICATION: id => `/oauth2/applications/${id}`
},
API_VERSION: 6,
get API_ENDPOINT() {
return "https://discordapp.com/api/v" + this.API_VERSION;
},
CDN_ENDPOINT: "https://cdn.discordapp.com",
Permissions: Permissions,
PermissionsDefault: PermissionsDefault,
PermissionSpecs: PermissionSpecs,
StatusTypes: {
ONLINE: "online",
OFFLINE: "offline",
IDLE: "idle",
DND: "dnd",
INVISIBLE: "invisible"
},
ActivityTypes: {
PLAYING: 0,
STREAMING: 1
},
VerificationLevel: {
NONE: 0,
LOW: 1,
MEDIUM: 2,
HIGH: 3
},
MFALevels: {
NONE: 0,
ELEVATED: 1
},
UserNotificationSettings: {
ALL_MESSAGES: 0,
ONLY_MENTIONS: 1,
NO_MESSAGES: 2,
NULL: 3
},
TYPING_TIMEOUT: 10000,
BITRATE_MIN: 8000,
BITRATE_DEFAULT: 64000,
BITRATE_MAX: 96000,
BITRATE_MAX_VIP: 128000,
DISCORD_SAMPLE_RATE: 48000,
NON_USER_BOT_DISCRIMINATOR: "0000"
};
function mirror(d) { Object.keys(d).forEach((k) => d[k] = k); }
function enumerate(d) { let c = 0; Object.keys(d).forEach((k) => d[k] = c++); }
mirror(Constants.Events);
mirror(Constants.EncryptionModes);
mirror(Constants.DiscordieState);
module.exports = Constants;

View File

@@ -0,0 +1,12 @@
"use strict";
class BaseArrayCollection extends Array {
// avoid constructor, calling array mutation methods will call it (ES2015)
static create() {
const instance = new this();
this._constructor.apply(instance, arguments);
return instance;
}
}
module.exports = BaseArrayCollection;

View File

@@ -0,0 +1,19 @@
"use strict";
const BaseModel = require("../models/BaseModel");
class BaseCollection extends Map {
constructor() {
super();
}
mergeOrSet(key, value) {
const old = this.get(key);
let merged = value;
if (old && old instanceof BaseModel) {
merged = old.merge(value);
}
this.set(key, merged);
}
}
module.exports = BaseCollection;

View File

@@ -0,0 +1,97 @@
"use strict";
const Constants = require("../Constants");
const Events = Constants.Events;
const Utils = require("../core/Utils");
const BaseCollection = require("./BaseCollection");
const Call = require("../models/Call");
function emitRing(gw, channelId) {
const channel = this._discordie.DirectMessageChannels.get(channelId);
if (!channel) return;
this._discordie.Dispatcher.emit(Events.CALL_RING, {
socket: gw,
channel: channel
});
}
function checkRing(gw, prev, next) {
const channelId = next.channel_id;
const userId = this._discordie._user && this._discordie._user.id;
if (!channelId || !userId) return;
if (!next || !next.ringing) return;
const hasPrev = prev ? prev.ringing.indexOf(userId) >= 0 : false;
const hasNext = next.ringing.indexOf(userId) >= 0;
if (!hasPrev && hasNext) emitRing.call(this, gw, channelId);
}
function handleConnectionOpen(data) {
this.clear();
return true;
}
function handleCallCreate(call, e) {
this.set(call.channel_id, new Call(call));
checkRing.call(this, e.socket, null, call);
return true;
}
function handleCallUpdate(call, e) {
const prev = this.get(call.channel_id);
this.mergeOrSet(call.channel_id, new Call(call));
checkRing.call(this, e.socket, prev, call);
return true;
}
function handleCallDelete(call) {
const _call = this.get(call.channel_id);
if (!_call) return true;
if (call.unavailable === true) {
this.mergeOrSet(call.channel_id, {unavailable: true});
} else {
this.delete(call.channel_id);
}
return true;
}
class CallCollection extends BaseCollection {
constructor(discordie, gateway) {
super();
if (typeof gateway !== "function")
throw new Error("Gateway parameter must be a function");
discordie.Dispatcher.on(Events.GATEWAY_DISPATCH, e => {
if (e.socket != gateway()) return;
Utils.bindGatewayEventHandlers(this, e, {
READY: handleConnectionOpen,
CALL_CREATE: handleCallCreate,
CALL_UPDATE: handleCallUpdate,
CALL_DELETE: handleCallDelete
});
});
this._discordie = discordie;
Utils.privatify(this);
}
isActive(channelId, messageId) {
const call = this.get(channelId);
if (messageId) {
return call && !call.unavailable && call.message_id == messageId;
}
return call && !call.unavailable;
}
isUnavailable(channelId) {
const call = this.get(channelId);
return call && call.unavailable;
}
}
module.exports = CallCollection;

View File

@@ -0,0 +1,193 @@
"use strict";
const Constants = require("../Constants");
const Events = Constants.Events;
const ChannelTypes = Constants.ChannelTypes;
const Utils = require("../core/Utils");
const BaseCollection = require("./BaseCollection");
const Channel = require("../models/Channel");
const PermissionOverwrite = require("../models/PermissionOverwrite");
const IPermissions = require("../interfaces/IPermissions");
function convertOverwrites(channel) {
const overwrites = channel.permission_overwrites || [];
// note: @everyone overwrite does not exist by default
// this will add one locally
if (channel.guild_id) {
const everyone = overwrites.find(o => o.id == channel.guild_id);
if (!everyone) {
overwrites.push({
id: channel.guild_id,
type: "role",
allow: IPermissions.NONE,
deny: IPermissions.NONE
});
}
}
return overwrites.map(o => new PermissionOverwrite(o));
}
function createChannel(channel) {
const channelRecipients =
Array.isArray(channel.recipients) ? channel.recipients.map(r => r.id) : [];
return new Channel({
id: channel.id,
type: channel.type || ChannelTypes.GUILD_TEXT,
name: channel.name || "",
topic: channel.topic || "",
position: channel.position || 0,
recipients: new Set(channelRecipients),
guild_id: channel.guild_id || null,
permission_overwrites: convertOverwrites(channel),
bitrate: channel.bitrate || Constants.BITRATE_DEFAULT,
user_limit: channel.user_limit || 0,
owner_id: channel.owner_id || null,
icon: channel.icon || null
});
}
function handleConnectionOpen(data) {
this.clear();
data.guilds.forEach(guild => handleGuildCreate.call(this, guild));
data.private_channels.forEach(channel => {
this.set(channel.id, createChannel(channel));
});
return true;
}
function handleCreateOrUpdateChannel(channel, e) {
const prev = this.get(channel.id);
const next = createChannel(channel);
this.mergeOrSet(channel.id, next);
if (e.type === Events.CHANNEL_UPDATE) {
e.data._prev = prev;
e.data._next = next;
}
return true;
}
function handleChannelDelete(channel) {
this.delete(channel.id);
return true;
}
function handleGuildCreate(guild) {
if (!guild || guild.unavailable) return true;
guild.channels.forEach(channel => {
channel.guild_id = guild.id;
this.set(channel.id, createChannel(channel));
});
return true;
}
function handleGuildDelete(guild) {
this.forEach((channel, id) => {
if (channel.guild_id == guild.id)
this.delete(id);
});
return true;
}
function processRecipientAddOrRemove(data, handler) {
const user = data.user;
const channel = this.get(data.channel_id);
if (!channel) return;
if (!this._discordie._user || this._discordie._user.id == user.id) return;
handler(channel, user);
}
function handleRecipientAdd(data) {
const handler = (channel, user) => channel.recipients.add(user.id);
processRecipientAddOrRemove.call(this, data, handler);
return true;
}
function handleRecipientRemove(data) {
const handler = (channel, user) => channel.recipients.delete(user.id);
processRecipientAddOrRemove.call(this, data, handler);
return true;
}
class ChannelCollection extends BaseCollection {
constructor(discordie, gateway) {
super();
if (typeof gateway !== "function")
throw new Error("Gateway parameter must be a function");
discordie.Dispatcher.on(Events.GATEWAY_DISPATCH, e => {
if (e.socket != gateway()) return;
Utils.bindGatewayEventHandlers(this, e, {
READY: handleConnectionOpen,
GUILD_CREATE: handleGuildCreate,
GUILD_DELETE: handleGuildDelete,
CHANNEL_CREATE: handleCreateOrUpdateChannel,
CHANNEL_UPDATE: handleCreateOrUpdateChannel,
CHANNEL_DELETE: handleChannelDelete,
CHANNEL_RECIPIENT_ADD: handleRecipientAdd,
CHANNEL_RECIPIENT_REMOVE: handleRecipientRemove
});
});
this._discordie = discordie;
Utils.privatify(this);
}
*getPrivateChannelIterator() {
for (let channel of this.values()) {
if (this._isPrivate(channel))
yield channel;
}
}
*getGuildChannelIterator() {
for (let channel of this.values()) {
if (!this._isPrivate(channel))
yield channel;
}
}
getPrivateChannel(channelId) {
var channel = this.get(channelId);
if (!channel) return null;
return this._isPrivate(channel) ? channel : null;
}
getGuildChannel(channelId) {
var channel = this.get(channelId);
if (!channel) return null;
return !this._isPrivate(channel) ? channel : null;
}
isPrivate(channelId) {
const channel = this.get(channelId);
if (channel)
return this._isPrivate(channel);
return null;
}
_isPrivate(channel) {
const type = channel.type;
return (type === ChannelTypes.DM || type === ChannelTypes.GROUP_DM);
}
getChannelType(channelId) {
const channel = this.get(channelId);
if (channel) return channel.type;
return null;
}
update(channel) {
handleCreateOrUpdateChannel.call(this, channel, {});
}
updatePermissionOverwrite(channelId, overwrite) {
const channel = this.get(channelId);
if (!channel) return;
const newOverwrites = channel.permission_overwrites
.filter(o => o.id != overwrite.id && o.type != overwrite.type);
newOverwrites.push(new PermissionOverwrite(overwrite));
this.set(channelId, channel.merge({
permission_overwrites: newOverwrites
}));
}
}
module.exports = ChannelCollection;

View File

@@ -0,0 +1,183 @@
"use strict";
const Constants = require("../Constants");
const Events = Constants.Events;
const Utils = require("../core/Utils");
const BaseCollection = require("./BaseCollection");
const MFALevels = Constants.MFALevels;
const VerificationLevel = Constants.VerificationLevel;
const UserNotificationSettings = Constants.UserNotificationSettings;
const Guild = require("../models/Guild");
const Role = require("../models/Role");
function convertRoles(roles) {
const map = new Map();
roles.forEach(role => {
map.set(role.id, new Role(role));
});
return map;
}
function createGuild(guild, old) {
return new Guild({
id: guild.id,
name: guild.name,
region: guild.region,
icon: guild.icon,
splash: guild.splash,
features: new Set(guild.features),
emojis: (guild.emojis != null ? guild.emojis : old.emojis) || [],
default_message_notifications:
guild.default_message_notifications ||
UserNotificationSettings.ALL_MESSAGES,
owner_id: guild.owner_id,
roles: convertRoles(guild.roles),
afk_channel_id: guild.afk_channel_id,
afk_timeout: guild.afk_timeout,
verification_level: guild.verification_level || VerificationLevel.NONE,
member_count:
(guild.member_count != null ? guild.member_count : old.member_count),
large: (guild.large != null ? guild.large : old.large) || false,
mfa_level: guild.mfa_level || MFALevels.NONE,
joined_at: guild.joined_at || old.joined_at
});
}
function handleConnectionOpen(data) {
this.clear();
data.guilds.forEach(guild => {
if (guild.unavailable) return;
this.set(guild.id, createGuild(guild, {}));
});
return true;
}
function handleCreateOrUpdateGuild(guild, e) {
if (!guild || guild.unavailable) return true;
const prev = this.get(guild.id) || {};
const next = createGuild(guild, prev);
this.mergeOrSet(guild.id, next);
if (e.type === Events.GUILD_UPDATE) {
e.data._prev = prev;
e.data._next = next;
}
return true;
}
function handleDeleteGuild(guild, e) {
const oldGuild = this.get(guild.id);
if (oldGuild) e.data._cached = oldGuild;
this.delete(guild.id);
return true;
}
function handleGuildSync(guild) {
let oldGuild = this.get(guild.id);
if (!oldGuild) return true;
this.mergeOrSet(guild.id, {
large: (guild.large != null ? guild.large : oldGuild.large)
});
return true;
}
function handleGuildRoleCreateOrUpdate(data, e) {
let guild = this.get(data.guild_id);
if (guild) {
const prev = guild.roles.get(data.role.id);
const next = new Role(data.role);
guild.roles.set(data.role.id, next);
if (e.type === Events.GUILD_ROLE_UPDATE) {
e.data._prev = prev;
e.data._next = next;
}
}
return true;
}
function handleGuildRoleDelete(data, e) {
let guild = this.get(data.guild_id);
if (guild) {
const oldRole = guild.roles.get(data.role_id);
if (oldRole) e.data._cached = oldRole;
guild.roles.delete(data.role_id);
}
return true;
}
function handleGuildMemberAdd(member) {
updateMemberCount.call(this, member.guild_id, +1);
return true;
}
function handleGuildMemberRemove(member) {
updateMemberCount.call(this, member.guild_id, -1);
return true;
}
function updateMemberCount(guildId, delta) {
let guild = this.get(guildId);
if (!guild) return true;
this.mergeOrSet(guildId, { member_count: guild.member_count + delta });
return true;
}
function handleGuildEmojisUpdate(data, e) {
if (!data || data.emojis == null) return true;
let guild = this.get(data.guild_id);
if (!guild) return true;
const prev = guild.emojis;
const next = data.emojis;
this.mergeOrSet(data.guild_id, { emojis: data.emojis });
if (e.type === Events.GUILD_EMOJIS_UPDATE) {
e.data._prev = prev;
e.data._next = next;
}
return true;
}
class GuildCollection extends BaseCollection {
constructor(discordie, gateway) {
super();
if (typeof gateway !== "function")
throw new Error("Gateway parameter must be a function");
discordie.Dispatcher.on(Events.GATEWAY_DISPATCH, e => {
if (e.socket != gateway()) return;
Utils.bindGatewayEventHandlers(this, e, {
READY: handleConnectionOpen,
GUILD_SYNC: handleGuildSync,
GUILD_CREATE: handleCreateOrUpdateGuild,
GUILD_UPDATE: handleCreateOrUpdateGuild,
GUILD_DELETE: handleDeleteGuild,
GUILD_ROLE_CREATE: handleGuildRoleCreateOrUpdate,
GUILD_ROLE_UPDATE: handleGuildRoleCreateOrUpdate,
GUILD_ROLE_DELETE: handleGuildRoleDelete,
GUILD_MEMBER_ADD: handleGuildMemberAdd,
GUILD_MEMBER_REMOVE: handleGuildMemberRemove,
GUILD_EMOJIS_UPDATE: handleGuildEmojisUpdate,
});
});
this._discordie = discordie;
Utils.privatify(this);
}
update(guild) {
handleCreateOrUpdateGuild.call(this, guild, {});
}
updateRole(guildId, role) {
const guild = this.get(guildId);
if (!guild || !guild.roles) return;
guild.roles.set(role.id, new Role(role));
}
}
module.exports = GuildCollection;

View File

@@ -0,0 +1,239 @@
"use strict";
const Constants = require("../Constants");
const StatusTypes = Constants.StatusTypes;
const Events = Constants.Events;
const Utils = require("../core/Utils");
const BaseCollection = require("./BaseCollection");
const GuildMember = require("../models/GuildMember");
function createGuildMember(member) {
if (member.user) member.id = member.user.id;
return new GuildMember({
id: member.id,
guild_id: member.guild_id,
nick: member.nick || null,
roles: member.roles || [],
mute: member.mute || false,
deaf: member.deaf || false,
self_mute: member.self_mute || false,
self_deaf: member.self_deaf || false,
joined_at: member.joined_at
});
}
function handleConnectionOpen(data) {
this.clear();
data.guilds.forEach(guild => handleCreateGuild.call(this, guild));
return true;
}
function handleGuildMemberCreateOrUpdate(member, e) {
const memberCollection = this.get(member.guild_id);
if (!memberCollection) return true;
const prev = memberCollection.get(member.user.id);
const next = createGuildMember(member);
memberCollection.mergeOrSet(member.user.id, next);
if (e.type === Events.GUILD_MEMBER_UPDATE) {
e.data._prev = prev;
e.data._next = next;
}
return true;
}
function handleGuildMemberRemove(member, e) {
const memberCollection = this.get(member.guild_id);
if (!memberCollection) return true;
const oldMember = memberCollection.get(member.user.id);
if (oldMember) e.data._cached = oldMember;
memberCollection.delete(member.user.id);
return true;
}
function handleCreateGuild(guild) {
if (!guild || guild.unavailable) return true;
if (!this._discordie._guilds.get(guild.id)) return true; // GUILD_SYNC case
const memberCollection = new BaseCollection();
this.set(guild.id, memberCollection);
guild.members.forEach(member => {
member.guild_id = guild.id;
memberCollection.set(member.user.id, createGuildMember(member));
});
return true;
}
function handleDeleteGuild(guild) {
this.delete(guild.id);
return true;
}
function handleVoiceStateUpdate(data) {
const memberCollection = this.get(data.guild_id);
if (!memberCollection) return true;
const member = memberCollection.get(data.user_id);
if (!member) return true;
memberCollection.set(data.user_id,
member.merge({
mute: data.mute,
deaf: data.deaf,
self_mute: data.self_mute,
self_deaf: data.self_deaf
})
);
return true;
}
function handlePresenceUpdate(presence) {
if (!presence.user || !presence.user.id) return true;
// add members only for online users and if presence is not partial
if (presence.status == StatusTypes.OFFLINE || !presence.user.username)
return true;
const memberCollection = this.get(presence.guild_id);
if (!memberCollection) return true;
const cachedMember = memberCollection.get(presence.user.id);
if (!cachedMember) {
// note: presences only contain roles
memberCollection.set(presence.user.id, createGuildMember(presence));
}
return true;
}
function handleGuildMembersChunk(chunk) {
var guildId = chunk.guild_id;
if (!guildId || !chunk.members) return true;
var state = this._guildMemberChunks[guildId];
if (!state) return true;
var guild = this._discordie._guilds.get(guildId);
if (!guild) return true;
const memberCollection = this.get(guildId);
if (!memberCollection) return true;
chunk.members.forEach(member => {
member.guild_id = guildId;
memberCollection.mergeOrSet(member.user.id, createGuildMember(member))
});
if (memberCollection.size >= guild.member_count) state.resolve();
return true;
}
class GuildMemberCollection extends BaseCollection {
constructor(discordie, gateway) {
super();
if (typeof gateway !== "function")
throw new Error("Gateway parameter must be a function");
discordie.Dispatcher.on(Events.GATEWAY_DISPATCH, e => {
if (e.socket != gateway()) return;
Utils.bindGatewayEventHandlers(this, e, {
READY: handleConnectionOpen,
GUILD_SYNC: handleCreateGuild,
GUILD_CREATE: handleCreateGuild,
GUILD_DELETE: handleDeleteGuild,
GUILD_MEMBER_ADD: handleGuildMemberCreateOrUpdate,
GUILD_MEMBER_UPDATE: handleGuildMemberCreateOrUpdate,
GUILD_MEMBER_REMOVE: handleGuildMemberRemove,
VOICE_STATE_UPDATE: handleVoiceStateUpdate,
PRESENCE_UPDATE: handlePresenceUpdate,
GUILD_MEMBERS_CHUNK: handleGuildMembersChunk
});
});
discordie.Dispatcher.on(Events.GATEWAY_DISCONNECT, e => {
if (e.socket != gateway()) return;
for (var guildId in this._guildMemberChunks) {
var state = this._guildMemberChunks[guildId];
if (!state) continue;
state.reject(new Error("Gateway disconnect"));
}
this._guildMemberChunks = {};
});
this._guildMemberChunks = {};
this._discordie = discordie;
Utils.privatify(this);
}
fetchMembers(guilds){
const gateway = this._discordie.gatewaySocket;
if (!gateway || !gateway.connected)
return Promise.reject(new Error("No gateway socket (not connected)"));
const largeGuilds =
Array.from(this._discordie._guilds.values())
.filter(guild => {
if (!guild.large) return false;
var cachedMembers = this._discordie._members.get(guild.id);
if (!cachedMembers) return false;
return guild.member_count > cachedMembers.size;
})
.map(guild => guild.id);
// filter out only requested guilds (if specified) from large ones
// return a resolved promise if no large guilds in the list
let targetGuilds =
!guilds ?
largeGuilds :
largeGuilds.filter(guild => guilds.indexOf(guild) >= 0);
if (!targetGuilds.length) return Promise.resolve();
targetGuilds.forEach(guildId => {
if (this._guildMemberChunks[guildId]) return;
var state = {promise: null, resolve: null, reject: null, timer: null};
state.promise = new Promise((rs, rj) => {
const destroyState = () => {
if (!state.timer) return;
clearTimeout(state.timer);
state.timer = null;
delete this._guildMemberChunks[guildId];
};
state.resolve = result => { destroyState(); return rs(result); };
state.reject = reason => { destroyState(); return rj(reason); };
});
state.timer = setTimeout(() => {
if (!this._guildMemberChunks[guildId]) return;
state.reject(new Error(
"Guild member request timed out (" + guildId + ")"
));
delete this._guildMemberChunks[guildId];
}, 60000);
this._guildMemberChunks[guildId] = state;
});
gateway.requestGuildMembers(targetGuilds);
var targetPromises =
targetGuilds.map(guildId => this._guildMemberChunks[guildId].promise);
return Promise.all(targetPromises);
}
getMember(guildId, userId) {
const memberCollection = this.get(guildId);
if (!memberCollection) return null;
return memberCollection.get(userId);
}
}
module.exports = GuildMemberCollection;

View File

@@ -0,0 +1,174 @@
"use strict";
const Constants = require("../Constants");
const Events = Constants.Events;
const Utils = require("../core/Utils");
const GUILD_SYNC_TIMEOUT = 3000;
function emitTaskFinished(gw) {
this._syncingGuilds.clear();
this._discordie.Dispatcher.emit(Events.READY_TASK_FINISHED, {
socket: gw,
name: this.constructor.name,
handler: this
});
}
function scheduleReadyTimeout(gw) {
this._syncingGuildsTimeout = setTimeout(() => {
this._syncingGuildsTimeout = null;
if (!this._discordie.connected) return;
setImmediate(() => emitTaskFinished.call(this, gw));
}, GUILD_SYNC_TIMEOUT);
}
function maybeReady(gw) {
if (this._syncingGuildsTimeout) {
clearTimeout(this._syncingGuildsTimeout);
}
if (this._syncingGuilds.size) {
scheduleReadyTimeout.call(this, gw);
} else {
setImmediate(() => emitTaskFinished.call(this, gw));
}
}
function handleExecuteReadyTask(data, e) {
clearAll.call(this);
if (this.isSupported(e.socket)) {
data.guilds.forEach(guild => {
this.add(guild.id);
this._syncingGuilds.add(guild.id);
});
commit.call(this);
}
maybeReady.call(this, e.socket);
return true;
}
function handleGuildCreate(guild, e) {
if (!this.isSupported(e.socket)) return true;
// ignore if became available
if (this._discordie.UnavailableGuilds.isGuildAvailable(guild)) return true;
this.sync(guild);
return true;
}
function handleGuildSync(guild, e) {
if (this._syncingGuilds.has(guild.id)) {
this._syncingGuilds.delete(guild.id);
maybeReady.call(this, e.socket);
}
return true;
}
function handleGuildDelete(guild, e) {
if (!this.isSupported(e.socket)) return true;
if (guild.unavailable) return true;
this.unsync(guild);
return true;
}
function commit() {
const gateway = this._gateway();
if (!gateway || !gateway.connected) return;
gateway.syncGuilds(Array.from(this));
}
function clearAll() {
this.clear();
this._syncingGuilds.clear();
}
class GuildSyncCollection extends Set {
constructor(discordie, gateway) {
super();
if (typeof gateway !== "function")
throw new Error("Gateway parameter must be a function");
discordie.Dispatcher.on(Events.GATEWAY_DISPATCH, e => {
if (e.socket != gateway()) return;
Utils.bindGatewayEventHandlers(this, e, {
GUILD_SYNC: handleGuildSync,
GUILD_CREATE: handleGuildCreate,
GUILD_DELETE: handleGuildDelete,
});
});
this._discordie = discordie;
this._gateway = gateway;
this._syncingGuilds = new Set();
this._syncingGuildsTimeout = null;
Utils.privatify(this);
}
_executeReadyTask(data, socket) {
handleExecuteReadyTask.call(this, data, {socket});
}
isSupported(socket) {
if (!socket) socket = this._gateway();
if (!socket) return false;
return socket.remoteGatewayVersion >= 5 && !this._discordie._user.bot;
}
sync(guild) {
if (!this.isSupported()) return false;
if (!guild) return false;
if (Array.isArray(guild)) {
const guilds = guild
.map(g => g.id || g.valueOf())
.filter(id => !this.has(id));
if (!guilds.length) return false;
guilds.forEach(id => this.add(id));
} else {
if (this.has(guild.id)) return false;
this.add(guild.id);
}
commit.call(this);
return true;
}
unsync(guild) {
if (!this.isSupported()) return false;
if (!guild) return false;
if (Array.isArray(guild)) {
const guilds = guild
.map(g => g.id || g.valueOf())
.filter(id => this.has(id));
if (!guilds.length) return false;
guilds.forEach(id => this.delete(id));
} else {
if (!this.delete(guild.id)) return false;
}
commit.call(this);
return true;
}
syncAll() {
const available = this._discordie.Guilds.map(g => g.valueOf());
const unavailable = this._discordie.UnavailableGuilds;
const all = available.concat(unavailable);
return this.sync(all);
}
unsyncAll() {
if (!this.isSupported()) return false;
if (!this.size) return false;
this.clear();
commit.call(this);
return true;
}
}
module.exports = GuildSyncCollection;

View File

@@ -0,0 +1,393 @@
"use strict";
const Constants = require("../Constants");
const Events = Constants.Events;
const Utils = require("../core/Utils");
const BaseCollection = require("./BaseCollection");
const LimitedCache = require("../core/LimitedCache");
const Message = require("../models/Message");
function getOrCreateChannelCache(channelId) {
return (
this._messagesByChannel[channelId] =
this._messagesByChannel[channelId] ||
new LimitedCache(this._messageLimit)
);
}
function updatePinnedMessages(channelId, messageId, message) {
if (message && message.pinned && !message.deleted) {
var channelPinned =
this._pinnedMessagesByChannel[channelId] =
this._pinnedMessagesByChannel[channelId] || [];
// insert or replace
const idx = channelPinned.findIndex(msg => msg.id === messageId);
if (idx < 0) channelPinned.unshift(message);
else channelPinned[idx] = message;
} else {
var channelPinned = this._pinnedMessagesByChannel[channelId];
if (channelPinned) {
// delete if exists
const idx = channelPinned.findIndex(msg => msg.id === messageId);
if (idx >= 0) channelPinned.splice(idx, 1);
}
}
}
function updateMessages(channelId, messageId, msg) {
const messages = getOrCreateChannelCache.call(this, channelId);
if (msg.nonce && messages.has(msg.nonce)) {
messages.rename(msg.nonce, messageId);
}
var edited = true;
var message = null;
if (messages.has(messageId)) {
edited = !!msg.edited_timestamp;
messages.set(messageId, message = messages.get(messageId).merge(msg));
} else {
messages.set(messageId, message = new Message(msg));
}
if (edited) {
const edits = this._messageEdits[messageId] || [];
this._messageEdits[messageId] = edits;
var edit = message.merge({embeds: [], reactions: []});
edits.push(edit);
trimEdits.call(this, messageId);
}
}
function trimEdits(messageId) {
if (!messageId) {
for (var id in this._messageEdits)
if (id) trimEdits.call(this, id);
}
const edits = this._messageEdits[messageId];
if (!edits) return;
if (edits.length > this._editsLimit)
edits.splice(0, edits.length - this._editsLimit);
}
function handleMessageCreate(msg) {
msg.deleted = false;
updateMessages.call(this, msg.channel_id, msg.id, msg);
return true;
}
function handleMessageUpdate(msg) {
msg.deleted = false;
// update pinned cache only if pinned messages have been fetched
var channelPinned = this._pinnedMessagesByChannel[msg.channel_id];
if (channelPinned) {
updatePinnedMessages.call(this, msg.channel_id, msg.id, msg);
}
const channelCache = this._messagesByChannel[msg.channel_id];
if (!channelCache || !channelCache.has(msg.id)) return true;
updateMessages.call(this, msg.channel_id, msg.id, msg);
return true;
}
function handleMessageDelete(msg) {
msg.deleted = true;
updatePinnedMessages.call(this, msg.channel_id, msg.id, msg);
const channelCache = this._messagesByChannel[msg.channel_id];
if (!channelCache || !channelCache.has(msg.id)) return true;
updateMessages.call(this, msg.channel_id, msg.id, msg);
return true;
}
function handleMessageDeleteBulk(msg) {
const channelCache = this._messagesByChannel[msg.channel_id];
if (!channelCache) return true;
const update = { deleted: true };
msg.ids.forEach(id => {
updatePinnedMessages.call(this, msg.channel_id, id, update);
if (!channelCache.has(id)) return;
updateMessages.call(this, msg.channel_id, id, update);
});
return true;
}
function handleReaction(reaction, e) {
const channelCache = this._messagesByChannel[reaction.channel_id];
if (!channelCache) return true;
const localUser = this._discordie._user || {};
const me = (localUser.id == reaction.user_id);
var message = channelCache.get(reaction.message_id);
if (!message) return true;
const emoji = reaction.emoji;
const idx = message.reactions
.findIndex(r => r.emoji.id === emoji.id && r.emoji.name == emoji.name);
const old = message.reactions[idx];
if (e.type === Events.MESSAGE_REACTION_ADD) {
if (!message.reactions) {
message = message.merge({reactions: []});
channelCache.set(reaction.message_id, message);
}
if (old) {
old.count += 1;
if (me) old.me = true;
} else {
message.reactions.push({emoji, me, count: 1});
}
}
if (e.type === Events.MESSAGE_REACTION_REMOVE) {
if (old) {
old.count -= 1;
if (me) old.me = false;
if (old.count <= 0) message.reactions.splice(idx, 1);
}
}
return true;
}
function handleReactionRemoveAll(reaction, e) {
const channelCache = this._messagesByChannel[reaction.channel_id];
if (!channelCache) return true;
const message = channelCache.get(reaction.message_id);
if (!message) return true;
if (!message.reactions || !message.reactions.length) return true;
e.data._cached = message.reactions;
channelCache.set(reaction.message_id, message.merge({reactions: []}));
}
function handleConnectionOpen(data) {
this.purgeAllCache();
}
function handleCleanup() {
for (let channelId in this._messagesByChannel) {
if (!this._messagesByChannel.hasOwnProperty(channelId)) continue;
if (this._discordie._channels.get(channelId)) continue;
var messageIds = this._messagesByChannel[channelId]._keys;
for (var i = 0, len = messageIds.length; i < len; i++)
delete this._messageEdits[messageIds[i]];
delete this._pinnedMessagesByChannel[channelId];
delete this._messagesByChannel[channelId];
delete this._hasMoreByChannel[channelId];
}
}
function handleLoadedMoreMessages(e) {
var messagesLength = e.messages.length;
if (!messagesLength) return;
const channelId = e.messages[0].channel_id;
if (!e.before && !e.after) {
this._hasMoreByChannel[channelId] = (e.limit == messagesLength);
}
const messages = getOrCreateChannelCache.call(this, channelId);
const limit = messages.limit;
messages.setLimit(limit + messagesLength);
var i = messagesLength;
while (i--) {
var msg = e.messages[i];
updateMessages.call(this, msg.channel_id, msg.id, msg);
}
messages.setLimit(Math.max(messages.size + 500, limit));
// increase channel cache limits
// in case new messages arrive and invalidate old message references
}
function handleLoadedPinnedMessages(e) {
const channelId = e.channelId;
const channelPinned =
this._pinnedMessagesByChannel[channelId] =
this._pinnedMessagesByChannel[channelId] || [];
var messagesLength = e.messages.length;
if (!messagesLength) return;
const channelCache = this._messagesByChannel[channelId];
e.messages.forEach(msg => {
const cached = channelCache ? channelCache.get(msg.id) : null;
channelPinned.push(cached || new Message(msg));
});
}
class MessageCollection {
constructor(discordie, gateway) {
if (typeof gateway !== "function")
throw new Error("Gateway parameter must be a function");
discordie.Dispatcher.on(Events.GATEWAY_DISPATCH, e => {
if (e.socket != gateway()) return;
Utils.bindGatewayEventHandlers(this, e, {
READY: handleConnectionOpen,
MESSAGE_CREATE: handleMessageCreate,
MESSAGE_UPDATE: handleMessageUpdate,
MESSAGE_DELETE: handleMessageDelete,
MESSAGE_DELETE_BULK: handleMessageDeleteBulk,
CHANNEL_DELETE: handleCleanup,
GUILD_DELETE: handleCleanup,
MESSAGE_REACTION_ADD: handleReaction,
MESSAGE_REACTION_REMOVE: handleReaction,
MESSAGE_REACTION_REMOVE_ALL: handleReactionRemoveAll
});
});
discordie.Dispatcher.on(Events.LOADED_MORE_MESSAGES,
handleLoadedMoreMessages.bind(this));
discordie.Dispatcher.on(Events.LOADED_PINNED_MESSAGES,
handleLoadedPinnedMessages.bind(this));
this.purgeAllCache();
this._messageLimit = 1000;
this._editsLimit = 50;
this._discordie = discordie;
Utils.privatify(this);
}
getChannelMessageLimit(channelId) {
if (!this._discordie._channels.get(channelId)) return -1;
if (!this._messagesByChannel.hasOwnProperty(channelId)) return -1;
return this._messagesByChannel[channelId].limit;
}
setChannelMessageLimit(channelId, limit) {
if (!limit) return false;
if (!this._discordie._channels.get(channelId)) return false;
const messages = getOrCreateChannelCache.call(this, channelId);
messages.setLimit(limit);
return true;
}
getMessageLimit() { return this._messageLimit; }
setMessageLimit(limit) {
if (!limit) return;
if (!(limit > 0)) limit = 1;
var keys = Object.keys(this._messagesByChannel);
for (var i = 0, len = keys.length; i < len; i++) {
var cache = this._messagesByChannel[keys[i]];
// decrease only for channels with default limit
if (!cache) continue;
if (cache.limit != this._messageLimit && limit < cache.limit)
continue;
var removed = cache.setLimit(limit);
if (!removed) continue;
for (var messageId of removed)
delete this._messageEdits[messageId];
}
this._messageLimit = limit;
}
getEditsLimit() { return this._editsLimit; }
setEditsLimit(limit) {
if (!limit) return;
if (!(limit > 0)) limit = 1;
this._editsLimit = limit;
trimEdits.call(this);
}
*getIterator() {
for (var channelId in this._messagesByChannel) {
if (!this._messagesByChannel.hasOwnProperty(channelId)) continue;
if (!this._discordie._channels.get(channelId)) continue;
var channelMessages = this._messagesByChannel[channelId];
var keys = channelMessages._keys;
for (var i = 0, len = keys.length; i < len; i++) {
var message = channelMessages.get(keys[i]);
if (message) yield message;
}
}
}
get(messageId) {
for (var channelId in this._messagesByChannel) {
if (!this._messagesByChannel.hasOwnProperty(channelId)) continue;
if (!this._discordie._channels.get(channelId)) continue;
var channelMessages = this._messagesByChannel[channelId];
var message = channelMessages.get(messageId);
if (message) return message;
}
for (var channelId in this._pinnedMessagesByChannel) {
if (!this._pinnedMessagesByChannel.hasOwnProperty(channelId)) continue;
if (!this._discordie._channels.get(channelId)) continue;
var channelPinned = this._pinnedMessagesByChannel[channelId];
var message = channelPinned.find(msg => msg.id === messageId);
if (message) return message;
}
}
getChannelCache(channelId) {
if (!this._discordie._channels.get(channelId)) return null;
if (!this._messagesByChannel.hasOwnProperty(channelId)) return null;
return this._messagesByChannel[channelId];
}
purgeChannelCache(channelId) {
delete this._hasMoreByChannel[channelId];
delete this._messagesByChannel[channelId];
}
getChannelPinned(channelId) {
if (!this._discordie._channels.get(channelId)) return null;
if (!this._pinnedMessagesByChannel.hasOwnProperty(channelId)) return null;
return this._pinnedMessagesByChannel[channelId];
}
purgeChannelPinned(channelId) {
delete this._pinnedMessagesByChannel[channelId];
}
purgePinned() {
this._pinnedMessagesByChannel = {};
}
getEdits(messageId) {
var edits = this._messageEdits[messageId];
return edits ? edits.slice().reverse() : [];
}
purgeEdits() {
this._messageEdits = {};
}
purgeAllCache() {
this._messagesByChannel = {};
this._hasMoreByChannel = {};
this.purgeEdits();
this.purgePinned();
}
create(message) {
handleMessageCreate.call(this, message);
}
update(message) {
handleMessageUpdate.call(this, message);
}
channelHasMore(channelId) {
if (this._hasMoreByChannel[channelId] === undefined)
return true;
return this._hasMoreByChannel[channelId];
}
}
module.exports = MessageCollection;

View File

@@ -0,0 +1,162 @@
"use strict";
const Constants = require("../Constants");
const Events = Constants.Events;
const StatusTypes = Constants.StatusTypes;
const Utils = require("../core/Utils");
const BaseCollection = require("./BaseCollection");
const User = require("../models/User");
function updatePresence(guildId, userId, status, game) {
if (userId === this._discordie._user.id) return;
// ignore friend list presences (without `guild_id`)
if (!guildId) return;
// get presences in all guilds
var presencesForUser =
(this._presencesForGuilds[userId] =
this._presencesForGuilds[userId] || {});
var previousPresencesForUser =
(this._previousPresencesForGuilds[userId] =
this._previousPresencesForGuilds[userId] || {});
var previousPresence = presencesForUser[guildId];
// copy current to previous
previousPresencesForUser[guildId] =
Object.assign(previousPresencesForUser[guildId] || {}, previousPresence);
if (!previousPresence)
delete previousPresencesForUser[guildId];
if (status === StatusTypes.OFFLINE) {
// delete current presence cache if user is not online anymore
delete presencesForUser[guildId];
if (!Object.keys(presencesForUser).length) {
delete this._presencesForGuilds[userId];
}
delete this._statuses[userId];
delete this._games[userId];
} else {
// set current presence
presencesForUser[guildId] = {status, game};
// update global status and game (for user statuses in DM list)
this._statuses[userId] = status;
this._games[userId] = game || null;
}
}
function initializeCache() {
this._presencesForGuilds = {};
this._previousPresencesForGuilds = {};
this._statuses = {};
this._games = {};
}
function handleConnectionOpen(data) {
initializeCache.call(this);
data.guilds.forEach(guild => handleGuildCreate.call(this, guild));
return true;
}
function handleGuildCreate(guild) {
if (!guild || guild.unavailable) return true;
guild.presences.forEach(presence => {
updatePresence.call(this,
guild.id,
presence.user.id,
presence.status,
presence.game
);
});
return true;
}
function handleGuildDelete(guild) {
for (let userId of Object.keys(this._presencesForGuilds)) {
if (!this._presencesForGuilds[userId][guild.id]) continue;
updatePresence.call(this, guild.id, userId, StatusTypes.OFFLINE, null);
}
return true;
}
function handlePresenceUpdate(presence) {
updatePresence.call(this,
presence.guild_id,
presence.user.id,
presence.status,
presence.game
);
return true;
}
function getPresence(collection, userId, guildId) {
if (collection.hasOwnProperty(userId)) {
const presencesForUser = collection[userId];
guildId = guildId || Object.keys(presencesForUser)[0];
if (presencesForUser.hasOwnProperty(guildId)) {
return presencesForUser[guildId];
}
}
return null;
}
class PresenceCollection {
constructor(discordie, gateway) {
if (typeof gateway !== "function")
throw new Error("Gateway parameter must be a function");
discordie.Dispatcher.on(Events.GATEWAY_DISPATCH, e => {
if (e.socket != gateway()) return;
Utils.bindGatewayEventHandlers(this, e, {
READY: handleConnectionOpen,
GUILD_SYNC: handleGuildCreate,
GUILD_CREATE: handleGuildCreate,
GUILD_DELETE: handleGuildDelete,
PRESENCE_UPDATE: handlePresenceUpdate
});
});
initializeCache.call(this);
this._discordie = discordie;
Utils.privatify(this);
}
getStatus(userId, guildId) {
if (this._discordie._user && this._discordie._user.id == userId) {
return this._discordie._user.status;
}
const presence = getPresence.call(this,
this._presencesForGuilds, userId, guildId
) || {};
return presence.status || StatusTypes.OFFLINE;
}
getPreviousStatus(userId, guildId) {
const presence = getPresence.call(this,
this._previousPresencesForGuilds, userId, guildId
) || {};
return presence.status || StatusTypes.OFFLINE;
}
getGame(userId, guildId) {
if (this._discordie._user && this._discordie._user.id == userId) {
return this._discordie._user.game;
}
const presence = getPresence.call(this,
this._presencesForGuilds, userId, guildId
) || {};
return presence.game || null;
}
getPreviousGame(userId, guildId) {
const presence = getPresence.call(this,
this._previousPresencesForGuilds, userId, guildId
) || {};
return presence.game || null;
}
}
module.exports = PresenceCollection;

View File

@@ -0,0 +1,135 @@
"use strict";
const Constants = require("../Constants");
const Events = Constants.Events;
const Utils = require("../core/Utils");
const BaseArrayCollection = require("./BaseArrayCollection");
const GUILD_STREAMING_TIMEOUT = 3000;
function emitReady(gw) {
const timedOut = Array.from(this._streamingGuilds);
this._streamingGuilds.clear();
this._discordie.Dispatcher.emit(Events.COLLECTION_READY, {
socket: gw,
name: this.constructor.name,
collection: this
});
if (!timedOut.length) return;
timedOut.forEach(id => {
this._discordie.Dispatcher.emit(Events.GUILD_UNAVAILABLE, {
socket: gw,
guildId: id
});
});
}
function scheduleReadyTimeout(gw) {
this._streamingGuildsTimeout = setTimeout(() => {
this._streamingGuildsTimeout = null;
if (!this._discordie.connected) return;
setImmediate(() => emitReady.call(this, gw));
}, GUILD_STREAMING_TIMEOUT);
}
function maybeReady(gw) {
if (this._streamingGuildsTimeout) {
clearTimeout(this._streamingGuildsTimeout);
}
if (this._streamingGuilds.size) {
scheduleReadyTimeout.call(this, gw);
} else {
setImmediate(() => emitReady.call(this, gw));
}
}
function handleConnectionOpen(data, e) {
clearCollections.call(this);
data.guilds.forEach(guild => {
if (!guild.unavailable) return;
addUnavailable.call(this, guild.id);
this._streamingGuilds.add(guild.id);
});
maybeReady.call(this, e.socket);
return true;
}
function handleGuildCreate(guild, e) {
handleUnavailable.call(this, guild);
if (this.isGuildAvailable(guild) && this._streamingGuilds.has(guild.id)) {
e.suppress = true;
this._streamingGuilds.delete(guild.id);
maybeReady.call(this, e.socket);
}
return true;
}
function handleGuildDelete(guild) {
handleUnavailable.call(this, guild);
return true;
}
function handleUnavailable(guild) {
if (guild.unavailable) {
addUnavailable.call(this, guild.id)
} else {
removeUnavailable.call(this, guild.id);
}
}
function addUnavailable(id) {
if (this._set.has(id)) return;
this._set.add(id);
this.push(id);
}
function removeUnavailable(id) {
if (!this._set.has(id)) return;
this._set.delete(id);
var idx = this.indexOf(id);
this.splice(idx, 1);
}
function clearCollections() {
this._streamingGuilds.clear();
this._set.clear();
this.length = 0;
}
class UnavailableGuildCollection extends BaseArrayCollection {
static _constructor(discordie, gateway) {
if (typeof gateway !== "function")
throw new Error("Gateway parameter must be a function");
discordie.Dispatcher.on(Events.GATEWAY_DISPATCH, e => {
if (e.socket != gateway()) return;
Utils.bindGatewayEventHandlers(this, e, {
READY: handleConnectionOpen,
GUILD_CREATE: handleGuildCreate,
GUILD_DELETE: handleGuildDelete,
});
});
this._discordie = discordie;
this._set = new Set();
this._streamingGuilds = new Set();
this._streamingGuildsTimeout = null;
Utils.privatify(this);
}
isGuildAvailable(guild) {
// unavailable guilds that became available have key `unavailable`
return guild.unavailable === false;
}
}
module.exports = UnavailableGuildCollection;

View File

@@ -0,0 +1,191 @@
"use strict";
const Constants = require("../Constants");
const Events = Constants.Events;
const Utils = require("../core/Utils");
const BaseCollection = require("./BaseCollection");
const User = require("../models/User");
const AuthenticatedUser = require("../models/AuthenticatedUser");
function handleConnectionOpen(data) {
this.clear();
this.set(data.user.id, new AuthenticatedUser(data.user));
data.guilds.forEach(guild => {
if (guild.unavailable) return;
guild.members.forEach(member => {
if (member.user.id == data.user.id) return;
this.set(member.user.id, new User(member.user));
});
});
data.private_channels.forEach(channel => {
if (!channel.recipients) return;
channel.recipients.forEach(u => {
if (u.id === data.user.id) return;
this.set(u.id, new User(u));
});
});
return true;
}
function handleUpdateUser(user) {
const cachedUser = this._discordie._user;
delete user.token;
this._discordie._user = new AuthenticatedUser(
cachedUser ? cachedUser.merge(user) : user
);
this.mergeOrSet(user.id, this._discordie._user);
return true;
}
function handleLoadedMoreOrPinnedMessages(e) {
e.messages.forEach(message => {
this.mergeOrSet(message.author.id, new User(message.author));
message.mentions.forEach(mention => {
this.mergeOrSet(mention.id, new User(mention));
});
});
return true;
}
function handleIncomingMessage(message) {
if (message.author) {
this.mergeOrSet(message.author.id, new User(message.author));
}
if (message.mentions) {
message.mentions.forEach(mention => {
this.mergeOrSet(mention.id, new User(mention));
});
}
return true;
}
function handleCreateOrUpdateChannel(channel) {
if (channel.recipient) {
this.mergeOrSet(channel.recipient.id, new User(channel.recipient));
}
if (channel.recipients) {
channel.recipients.forEach(u => this.mergeOrSet(u.id, new User(u)));
}
return true;
}
function handlePresenceUpdate(presence, e) {
if (!presence.user || !presence.user.id) return true;
const cachedUser = this.get(presence.user.id);
if (!cachedUser) {
// update 2015-10-22:
// Discord client now creates local GUILD_MEMBER_ADD event
// update 2015-12-01:
// Discord client creates local GUILD_MEMBER_ADD event only for
// online users with `user.username != null`
this.set(presence.user.id, new User(presence.user));
return true;
}
const replacer = (hasChanges, key) => {
if (presence.user.hasOwnProperty(key)) {
hasChanges = hasChanges ||
(cachedUser[key] != presence.user[key]);
}
return hasChanges;
};
const hasChanges =
["username", "avatar", "discriminator"].reduce(replacer, false);
if (hasChanges) {
const oldUser = this.get(cachedUser.id);
this.mergeOrSet(cachedUser.id, new User(presence.user));
const newUser = this.get(cachedUser.id);
this._discordie.Dispatcher.emit(Events.PRESENCE_MEMBER_INFO_UPDATE, {
socket: e.socket,
old: oldUser,
new: newUser
});
}
return true;
}
function handleLoadedGuildBans(bans) {
bans.forEach(ban => {
this.mergeOrSet(ban.user.id, new User(ban.user));
});
}
function handleBanOrMember(member) {
this.mergeOrSet(member.user.id, new User(member.user));
return true;
}
function handleGuildCreate(guild) {
if (!guild || guild.unavailable) return true;
guild.members.forEach(member => {
if (this._discordie._user.id == member.user.id) return;
this.mergeOrSet(member.user.id, new User(member.user));
});
return true;
}
function handleGuildMembersChunk(chunk) {
if (!chunk.members) return true;
handleGuildCreate.call(this, chunk);
return true;
}
class UserCollection extends BaseCollection {
constructor(discordie, gateway) {
super();
if (typeof gateway !== "function")
throw new Error("Gateway parameter must be a function");
discordie.Dispatcher.on(Events.GATEWAY_DISPATCH, e => {
if (e.socket != gateway()) return;
Utils.bindGatewayEventHandlers(this, e, {
READY: handleConnectionOpen,
USER_UPDATE: handleUpdateUser,
PRESENCE_UPDATE: handlePresenceUpdate,
MESSAGE_CREATE: handleIncomingMessage,
MESSAGE_UPDATE: handleIncomingMessage,
GUILD_SYNC: handleGuildCreate,
GUILD_CREATE: handleGuildCreate,
GUILD_BAN_ADD: handleBanOrMember,
GUILD_BAN_REMOVE: handleBanOrMember,
GUILD_MEMBER_ADD: handleBanOrMember,
GUILD_MEMBER_REMOVE: handleBanOrMember,
CHANNEL_RECIPIENT_ADD: handleBanOrMember,
CHANNEL_RECIPIENT_REMOVE: handleBanOrMember,
CHANNEL_CREATE: handleCreateOrUpdateChannel,
CHANNEL_UPDATE: handleCreateOrUpdateChannel,
GUILD_MEMBERS_CHUNK: handleGuildMembersChunk
});
});
discordie.Dispatcher.on(Events.LOADED_MORE_MESSAGES,
handleLoadedMoreOrPinnedMessages.bind(this));
discordie.Dispatcher.on(Events.LOADED_PINNED_MESSAGES,
handleLoadedMoreOrPinnedMessages.bind(this));
discordie.Dispatcher.on(Events.LOADED_GUILD_BANS,
handleLoadedGuildBans.bind(this));
this._discordie = discordie;
Utils.privatify(this);
}
updateAuthenticatedUser(user) {
handleUpdateUser.call(this, user);
}
update(user) {
this.mergeOrSet(user.id, new User(user));
}
}
module.exports = UserCollection;

View File

@@ -0,0 +1,273 @@
"use strict";
const Constants = require("../Constants");
const Errors = Constants.Errors;
const Events = Constants.Events;
const Utils = require("../core/Utils");
const BaseArrayCollection = require("./BaseArrayCollection");
const IVoiceConnection = require("../interfaces/IVoiceConnection");
/**
* @class
*/
class VoiceConnectionInfo {
constructor(gatewaySocket, voiceSocket, voiceConnection) {
/**
* @instance
* @memberOf VoiceConnectionInfo
* @name gatewaySocket
* @returns {GatewaySocket}
*/
this.gatewaySocket = gatewaySocket;
/**
* @instance
* @memberOf VoiceConnectionInfo
* @name voiceSocket
* @returns {VoiceSocket}
*/
this.voiceSocket = voiceSocket;
/**
* @instance
* @memberOf VoiceConnectionInfo
* @name voiceConnection
* @returns {IVoiceConnection}
*/
this.voiceConnection = voiceConnection;
Object.freeze(this);
}
}
class VoiceConnectionCollection extends BaseArrayCollection {
static _constructor(discordie, primaryGateway) {
this._gateways = new Set();
this._pendingConnections = new Map();
// handle pending disconnects first to avoid firing a rejected pending
discordie.Dispatcher.on(Events.VOICESOCKET_DISCONNECT, e => {
var voiceSocket = e.socket;
var gatewaySocket = e.socket.gatewaySocket;
var guildId = voiceSocket.guildId;
const awaitingEndpoint =
e.error && e.error.message == Errors.VOICE_CHANGING_SERVER;
if (awaitingEndpoint) {
this._createPending(guildId);
} else {
var pending = this._pendingConnections.get(guildId);
if (!pending) return;
gatewaySocket.disconnectVoice(guildId);
this._pendingConnections.delete(guildId);
e ? pending.reject(e.error) : pending.reject();
}
});
// process voice connections
discordie.Dispatcher.on(Events.VOICE_SESSION_DESCRIPTION, e => {
const gw = e.socket.gatewaySocket;
const voicews = e.socket;
const voiceConnection = new IVoiceConnection(discordie, gw, voicews);
this.push(new VoiceConnectionInfo(gw, voicews, voiceConnection));
discordie.Dispatcher.emit(Events.VOICE_CONNECTED, {
socket: voicews,
voiceConnection: voiceConnection
});
});
discordie.Dispatcher.on(Events.VOICESOCKET_DISCONNECT, e => {
const idx = this.findIndex(c => c.voiceSocket == e.socket);
if (idx < 0) return;
var info = this[idx];
// delete from this array
this.splice(idx, 1);
var guildId = info.voiceSocket.guildId;
const awaitingEndpoint =
e.error && e.error.message == Errors.VOICE_CHANGING_SERVER;
const manual =
e.error && e.error.message == Errors.VOICE_MANUAL_DISCONNECT;
const endpointAwait =
awaitingEndpoint ? this._createPending(guildId) : null;
discordie.Dispatcher.emit(Events.VOICE_DISCONNECTED, {
socket: info.voiceSocket,
voiceConnection: info.voiceConnection,
error: (e.error instanceof Error) ? e.error : null,
manual, endpointAwait
});
if (guildId && !endpointAwait)
info.gatewaySocket.disconnectVoice(guildId);
info.voiceConnection.dispose();
});
// resolve promise when we have a voice connection created
discordie.Dispatcher.on(Events.VOICE_SESSION_DESCRIPTION, e => {
var voiceSocket = e.socket;
var guildId = voiceSocket.guildId;
var pending = this._pendingConnections.get(guildId);
if (!pending) return;
this._pendingConnections.delete(guildId);
pending.resolve(this.getForVoiceSocket(voiceSocket));
});
discordie.Dispatcher.on(Events.GATEWAY_DISPATCH, e => {
if (e.type === "READY" || e.type === "RESUMED") {
this._gateways.add(e.socket);
}
// process edge cases
if (e.type === "CALL_DELETE") {
const info = this.getForChannel(e.data.channel_id);
if (!info) return;
if (e.data.unavailable) {
info.voiceConnection._disconnect(Errors.VOICE_CALL_UNAVAILABLE);
} else {
info.voiceConnection._disconnect();
}
}
if (e.type === "GUILD_DELETE") {
const info = this.getForGuild(e.data.id);
if (!info) return;
if (e.data.unavailable) {
info.voiceConnection._disconnect(Errors.VOICE_GUILD_UNAVAILABLE);
} else {
info.voiceConnection._disconnect();
}
}
if (e.type === "CHANNEL_DELETE") {
const info = this.getForChannel(e.data.id);
if (info) info.voiceConnection._disconnect();
}
});
discordie.Dispatcher.on(Events.GATEWAY_DISCONNECT, e => {
Array.from(this._pendingConnections.keys()).forEach(guildId => {
var pending = this._pendingConnections.get(guildId);
if (!pending) return;
this._pendingConnections.delete(guildId);
pending.reject(new Error("Gateway disconnected"));
});
this._gateways.delete(e.socket);
});
this._discordie = discordie;
Utils.privatify(this);
}
_createPending(guildId) {
return this._getOrCreate(guildId, null, null, null, true);
}
_cancelPending(guildId) {
let pending = this._pendingConnections.get(guildId);
if (!pending) return;
this._pendingConnections.delete(guildId);
pending.reject(new Error("Cancelled"));
}
_isPending(guildId) {
return this._pendingConnections.get(guildId);
}
_getOrCreate(guildId, channelId, selfMute, selfDeaf, silent) {
const gateway = this._discordie.gatewaySocket;
if (!gateway || !gateway.connected)
return Promise.reject(new Error("No gateway socket (not connected)"));
// voiceStateUpdate triggers some events and can update pending connections
var newState = {guildId, channelId, selfMute, selfDeaf};
if (!silent && this.shouldUpdateVoiceState(newState)) {
if (!channelId && this.shouldCancelPending(guildId)) {
this._cancelPending(guildId);
}
gateway.voiceStateUpdate(guildId, channelId, selfMute, selfDeaf);
}
if (!silent && !channelId) return;
let pending = this._pendingConnections.get(guildId);
if (pending) return pending.promise;
const existing = this.getForGuild(guildId);
if (existing) return Promise.resolve(existing);
pending = {gateway, promise: null, resolve: null, reject: null};
this._pendingConnections.set(guildId, pending);
return (pending.promise = new Promise((rs, rj) => {
pending.resolve = rs;
pending.reject = rj;
}));
}
getForGuild(guildId) {
guildId = guildId ? guildId.valueOf() : null;
return this.find(c => c.voiceSocket.guildId == guildId) || null;
}
getForChannel(channelId) {
channelId = channelId.valueOf();
for (var gatewaySocket of this._gateways.values()) {
for (var voiceState of gatewaySocket.voiceStates.values()) {
if (!voiceState || voiceState.channelId !== channelId) continue;
return this.getForGuild(voiceState.guildId);
}
}
return null;
}
getForVoiceSocket(voiceSocket) {
return this.find(c => c.voiceSocket == voiceSocket) || null;
}
getForGatewaySocket(gatewaySocket) {
return this.find(c => c.gatewaySocket == gatewaySocket) || null;
}
isLocalSession(sessionId) {
return !!Array.from(this._gateways).find(gw => gw.sessionId == sessionId);
}
shouldUpdateVoiceState(newState) {
var guildId = newState.guildId;
for (var gatewaySocket of this._gateways.values()) {
var state = gatewaySocket.voiceStates.get(guildId);
if (!state) continue;
var hasChanges = Object.keys(state).reduce((r, key) => {
return r ||
(newState[key] !== undefined && newState[key] != state[key]);
}, false);
return hasChanges;
}
return true;
}
shouldCancelPending(guildId) {
// cancel pending connection only if there are no sockets open
// otherwise disconnect existing socket and fire VOICESOCKET_DISCONNECT
for (var gatewaySocket of this._gateways.values()) {
var socket = gatewaySocket.voiceSockets.get(guildId);
if (!socket) continue;
return false;
}
return true;
}
getPendingChannel(guildId) {
for (var gatewaySocket of this._gateways.values()) {
var state = gatewaySocket.voiceStates.get(guildId);
if (!state) continue;
return state.channelId;
}
return null;
}
cancelIfPending(guildId, channelId) {
const pendingChannelId = this.getPendingChannel(guildId);
if (pendingChannelId && pendingChannelId === channelId)
this._getOrCreate(guildId, null);
}
}
module.exports = VoiceConnectionCollection;

View File

@@ -0,0 +1,352 @@
"use strict";
const Constants = require("../Constants");
const StatusTypes = Constants.StatusTypes;
const Events = Constants.Events;
const Utils = require("../core/Utils");
const BaseCollection = require("./BaseCollection");
const User = require("../models/User");
const AuthenticatedUser = require("../models/AuthenticatedUser");
function getUserStates(userId) {
var states = [];
var channels = this._channelsForUser.get(userId);
if (!channels) return states;
Array.from(channels).forEach(channelId => {
var userMap = this._usersForChannel.get(channelId);
if (!userMap || !userMap.has(userId)) return;
states.push(userMap.get(userId));
});
return states;
}
function createChangeEvent(state, e) {
return {
socket: e.socket,
user: this._discordie.Users.get(state.user_id),
channel:
this._discordie.Channels.get(state.channel_id) ||
this._discordie.DirectMessageChannels.get(state.channel_id),
channelId: state.channel_id,
guildId: state.guild_id
};
}
function emitMuteDeafUpdate(type, state, key, e) {
var e = createChangeEvent.call(this, state, e);
e.state = state[key];
this._discordie.Dispatcher.emit(type, e);
}
function emitChanges(before, after, e) {
if (!before.length && !after.length) return;
var leave =
before.filter(b => !after.find(a => a.channel_id == b.channel_id));
var join =
after.filter(a => !before.find(b => a.channel_id == b.channel_id));
var moved = leave.length === 1 && join.length === 1;
leave.forEach(state => {
var event = createChangeEvent.call(this, state, e);
event.newChannelId = moved ? join[0].channel_id : null;
event.newGuildId = moved ? join[0].guild_id : null;
this._discordie.Dispatcher.emit(
Events.VOICE_CHANNEL_LEAVE,
event
);
});
join.forEach(state => {
this._discordie.Dispatcher.emit(
Events.VOICE_CHANNEL_JOIN,
createChangeEvent.call(this, state, e)
);
});
if (!leave.length && !join.length) {
var sm = after.find(b => before.find(a => a.self_mute != b.self_mute));
var sd = after.find(b => before.find(a => a.self_deaf != b.self_deaf));
var m = after.find(b => before.find(a => a.mute != b.mute));
var d = after.find(b => before.find(a => a.deaf != b.deaf));
var _emitMuteDeafUpdate = emitMuteDeafUpdate.bind(this);
if (sm)_emitMuteDeafUpdate(Events.VOICE_USER_SELF_MUTE, sm, "self_mute", e);
if (sd)_emitMuteDeafUpdate(Events.VOICE_USER_SELF_DEAF, sd, "self_deaf", e);
if (m) _emitMuteDeafUpdate(Events.VOICE_USER_MUTE, m, "mute", e);
if (d) _emitMuteDeafUpdate(Events.VOICE_USER_DEAF, d, "deaf", e);
}
}
function shouldCalculateChanges() {
var Dispatcher = this._discordie.Dispatcher;
return Dispatcher.hasListeners(Events.VOICE_CHANNEL_JOIN) ||
Dispatcher.hasListeners(Events.VOICE_CHANNEL_LEAVE) ||
Dispatcher.hasListeners(Events.VOICE_USER_SELF_MUTE) ||
Dispatcher.hasListeners(Events.VOICE_USER_SELF_DEAF) ||
Dispatcher.hasListeners(Events.VOICE_USER_MUTE) ||
Dispatcher.hasListeners(Events.VOICE_USER_DEAF);
}
function getOrCreate(type, target, key) {
const _T = target.get(key);
const got = _T || new type();
if (!_T) target.set(key, got);
return got;
}
function speakingDelete(userId, guildId) {
if (guildId) {
const info = this._discordie.VoiceConnections.getForGuild(guildId);
if (!info) return;
const speakingSet = this._speakingForVC.get(info.voiceConnection);
if (speakingSet) speakingSet.delete(userId);
return;
}
for (var speakingSet of this._speakingForVC.values()) {
speakingSet.delete(userId);
}
}
function ssrcDelete(userId, guildId) {
function removeFromMap(ssrcMap) {
for (var pair of ssrcMap.entries()) {
var ssrc = pair[0];
var user = pair[1];
if (user == userId) ssrcMap.delete(ssrc);
}
}
if (guildId) {
const info = this._discordie.VoiceConnections.getForGuild(guildId);
if (!info) return;
const ssrcMap = this._ssrcForVC.get(info.voiceConnection);
if (ssrcMap) removeFromMap(ssrcMap);
return;
}
for (var ssrcMap of this._ssrcForVC.values()) {
removeFromMap(ssrcMap);
}
}
function userDelete(userId, guildId) {
var channels = this._channelsForUser.get(userId);
if (!channels) return;
for (var channelId of channels.values()) {
var userMap = this._usersForChannel.get(channelId);
if (!userMap) continue;
if (guildId) {
var state = userMap.get(userId);
if (state.guild_id && state.guild_id !== guildId) continue;
}
userMap.delete(userId);
channels.delete(channelId);
if (!userMap.size) this._usersForChannel.delete(channelId);
if (!channels.size) this._channelsForUser.delete(userId);
}
}
function channelDelete(channelId) {
var userMap = this._usersForChannel.get(channelId);
this._usersForChannel.delete(channelId);
if (!userMap) return;
for (var state of userMap.values()) {
var channels = this._channelsForUser.get(state.user_id);
channels.delete(state.channel_id);
if (!channels.size) this._channelsForUser.delete(state.user_id);
}
}
function initializeCache() {
this._speakingForVC = new Map(); // Map<IVoiceConnection, Map<userId, bool>>
this._ssrcForVC = new Map(); // Map<IVoiceConnection, Map<userId, ssrc>>
this._usersForChannel = new Map(); // Map<channelId, Map<userId, voiceState>>
this._channelsForUser = new Map(); // Map<userId, Set<channelId>>
}
function handleConnectionOpen(data) {
initializeCache.call(this);
data.guilds.forEach(guild => handleGuildCreate.call(this, guild));
return true;
}
function handleGuildCreate(guild) {
if (guild.unavailable) return;
guild.voice_states.forEach(state => {
// states in READY don't contain `guild_id`
state.guild_id = guild.id;
insertVoiceState.call(this, state);
});
}
function handleVoiceStateUpdateChanges(data, e) {
// process only if we have event listeners for those events
if (!shouldCalculateChanges.call(this)) {
handleVoiceStateUpdate.call(this, data);
return true;
}
var before = getUserStates.call(this, data.user_id);
handleVoiceStateUpdate.call(this, data);
var after = getUserStates.call(this, data.user_id);
process.nextTick(() => emitChanges.call(this, before, after, e));
return true;
}
function handleVoiceStateUpdate(data) {
userDelete.call(this, data.user_id, data.guild_id);
if (!data.channel_id) {
speakingDelete.call(this, data.user_id, data.guild_id);
ssrcDelete.call(this, data.user_id, data.guild_id);
return true;
}
insertVoiceState.call(this, data);
return true;
}
function insertVoiceState(data) {
getOrCreate(Map, this._usersForChannel, data.channel_id)
.set(data.user_id, data);
getOrCreate(Set, this._channelsForUser, data.user_id)
.add(data.channel_id);
}
function handleVoiceSpeaking(data, voiceSocket) {
const info = this._discordie.VoiceConnections.getForVoiceSocket(voiceSocket);
if (!info) return true;
const vc = info.voiceConnection;
const speakingSet = getOrCreate(Set, this._speakingForVC, vc);
data.speaking ?
speakingSet.add(data.user_id) :
speakingSet.delete(data.user_id);
getOrCreate(Map, this._ssrcForVC, vc)
.set(data.ssrc, data.user_id);
return true;
}
function handleChannelDelete(channel, e) {
// just silently delete voice states as clients still stay connected to
// deleted channels
//var userMap = this._usersForChannel.get(channel.id);
//for (var userId of userMap.keys()) {
// var event = createChangeEvent.call(this, {
// user_id: userId,
// channel_id: channel.id,
// guild_id: channel.guild_id
// }, e);
// event.newChannelId = event.newGuildId = null;
//
// this._discordie.Dispatcher.emit(Events.VOICE_CHANNEL_LEAVE, event);
//}
channelDelete.call(this, channel.id);
return true;
}
function handleGuildDelete(guild) {
handleCleanup.call(this);
return true;
}
function handleCleanup() {
for (var channelId of this._usersForChannel.keys()) {
// delete all channel states if channel is no longer in cache
if (!this._discordie._channels.get(channelId))
channelDelete.call(this, channelId);
}
}
function handleCallCreate(call) {
if (!call || !call.voice_states) return true;
call.voice_states.forEach(state => {
state.guild_id = null;
insertVoiceState.call(this, state);
})
}
function handleCallDelete(call) {
if (!call || !call.channel_id) return true;
channelDelete.call(this, call.channel_id);
}
class VoiceStateCollection {
constructor(discordie, gateway) {
if (typeof gateway !== "function")
throw new Error("Gateway parameter must be a function");
discordie.Dispatcher.on(Events.GATEWAY_DISPATCH, e => {
if (e.socket != gateway()) return;
Utils.bindGatewayEventHandlers(this, e, {
READY: handleConnectionOpen,
GUILD_CREATE: handleGuildCreate,
GUILD_DELETE: handleGuildDelete,
CHANNEL_DELETE: handleChannelDelete,
VOICE_STATE_UPDATE: handleVoiceStateUpdateChanges,
CALL_CREATE: handleCallCreate,
CALL_DELETE: handleCallDelete
});
});
discordie.Dispatcher.on(Events.VOICE_SPEAKING, e => {
if (handleVoiceSpeaking.call(this, e.data, e.socket))
e.handled = true;
if (e.data && e.data.user_id) {
const user = this._discordie.Users.get(e.data.user_id);
if (user) e.user = user;
const info = discordie.VoiceConnections.getForVoiceSocket(e.socket);
if (info) e.voiceConnection = info.voiceConnection;
}
});
discordie.Dispatcher.on(Events.VOICE_DISCONNECTED, e => {
this._speakingForVC.delete(e.voiceConnection);
this._ssrcForVC.delete(e.voiceConnection);
});
initializeCache.call(this);
this._discordie = discordie;
Utils.privatify(this);
}
getStatesInChannel(channelId) {
channelId = channelId.valueOf();
const userMap = this._usersForChannel.get(channelId);
if (!userMap) return new Map();
return userMap;
}
getUserStateInGuild(guildId, userId) {
// note: there can be more than 1 voice member with same user id in guild
// this will return only the first voice state registered
var channels = this._channelsForUser.get(userId);
if (!channels) return null;
for (var channelId of channels.values()) {
const userMap = this._usersForChannel.get(channelId);
if (!userMap) continue;
var state = userMap.get(userId);
if (!state) continue;
if (state.guild_id == guildId) return state;
}
return null;
}
ssrcToUserId(voiceConnection, ssrc) {
const ssrcMap = this._ssrcForVC.get(voiceConnection);
if (ssrcMap) return ssrcMap.get(ssrc) || null;
return null;
}
}
module.exports = VoiceStateCollection;

168
node_modules/discordie/lib/core/ApiRequest.js generated vendored Normal file
View File

@@ -0,0 +1,168 @@
"use strict";
const superagent = require("superagent");
const Constants = require("../Constants");
const Backoff = require("./Backoff");
const os = require("os");
const version = require('../../package.json').version;
const useragent = process.browser ? "" : [
"DiscordBot",
`(https://github.com/qeled/discordie, v${version})`,
`(${os.type()} ${os.release()}; ${os.arch()})`,
process.version.replace(/^v/, (process.release.name || "node") + "/"),
process.versions.openssl ? `openssl/${process.versions.openssl}` : null,
].filter(e => e).join(" ");
function isDiscordAPI(url) {
if (!url.startsWith("/")) return false;
for (const k in Constants.Endpoints) {
if (url.startsWith(Constants.Endpoints[k]))
return true;
}
return false;
}
const BAD_GATEWAY_MIN_DELAY_MS = 100;
const BAD_GATEWAY_MAX_RETRIES = 10;
const HTTP_BAD_GATEWAY = 502;
class ApiRequest {
constructor(method, discordie, options) {
this._discordie = discordie;
options = options || {};
if (typeof options === "string") {
options = {url: options};
}
options.method = method;
if (!options.url) {
throw new TypeError(`ApiRequest ${method} failed: Invalid URL`);
}
this._badGatewayBackoff = new Backoff(
BAD_GATEWAY_MIN_DELAY_MS,
BAD_GATEWAY_MIN_DELAY_MS * 100
);
this._badGatewayRetries = 0;
this._options = options;
this._request = null;
this._promise = null;
this._resolve = null;
this._reject = null;
this._callback = null;
}
get method() {
return this._options.method;
}
get path() {
return this._options.url;
}
send(callback) {
if (this._promise) {
if (callback) {
console.warn(
"Called ApiRequest.send with callback more than 1 time: "
+ this._options.url
);
}
return this._promise;
}
this._callback = callback || null;
this._promise = new Promise((rs, rj) => {
this._resolve = rs;
this._reject = rj;
this._send();
});
// no need to fire unhandled rejection for this one if using callback
if (callback) {
this._promise.catch(this._void);
}
return this._promise;
}
_void() {}
_safeCallback(err, res) {
if (typeof this._callback !== "function") return;
this._callback(err, res);
this._callback = null;
}
_send() {
const token = this._discordie.token;
const method = this._options.method;
const query = this._options.query;
const body = this._options.body;
const attachDelegate = this._options.attachDelegate;
const rs = this._resolve, rj = this._reject;
var req = superagent[method](Constants.API_ENDPOINT + this._options.url);
this._request = req;
const botPrefix = this._discordie.bot ? "Bot " : "";
if (useragent) req.set("User-Agent", useragent);
if (token) req.set("Authorization", botPrefix + token);
if (query) req.query(query);
if (body) req.send(body);
if (typeof attachDelegate === "function") attachDelegate(req);
// warning: don't call requests with attachDelegate more than once because
// it can attach form data with streams
req.end((err, res) => {
this._request = null;
if (res && !res.ok && res.status === HTTP_BAD_GATEWAY) {
this._badGatewayRetries++;
if (this._badGatewayRetries <= BAD_GATEWAY_MAX_RETRIES) {
return this._badGatewayBackoff.fail(() => this._send());
}
}
if (err || !res.ok) {
if (res && res.body && res.body.code && res.body.message) {
err.code = res.body.code;
err.message = err.message + ` (${res.body.message})`;
}
rj(err);
} else {
rs(res);
}
this._promise = null;
this._safeCallback(err, res);
});
req.on("abort", () => {
this._request = null;
rj("Cancelled");
});
}
cancel() {
if (!this._request) return;
this._request.abort();
}
}
function createRequest(_method, discordie, options) {
return new ApiRequest(_method, discordie, options);
}
module.exports = {
get: createRequest.bind(null, "get"),
post: createRequest.bind(null, "post"),
patch: createRequest.bind(null, "patch"),
put: createRequest.bind(null, "put"),
del: createRequest.bind(null, "del"),
delete: createRequest.bind(null, "del")
};

56
node_modules/discordie/lib/core/Backoff.js generated vendored Normal file
View File

@@ -0,0 +1,56 @@
"use strict";
class Backoff {
constructor(min, max, jitter) {
this._min = (min != null && min > 0) ? min : 500;
this._max = (max != null && max > this._min) ? max : (this._min * 30);
this.jitter = (jitter != null) ? jitter : true;
this.fails = 0;
this.delay = this._min;
this._timer = null;
}
get min() { return this._min; }
set min(value) {
if (value < 0) throw new TypeError("Param 'value' must be >= 0");
this._min = value;
if (!this.fails) this.delay = this._min;
}
get max() { return this._max; }
set max(value) {
if (value < 5000) throw new TypeError("Param 'value' must be >= 5000");
this._max = value;
}
reset() {
this.cancel();
this.fails = 0;
this.delay = this._min;
}
fail(cb) {
this.fails++;
const delay = this.delay * 2 * (this.jitter ? Math.random() : 1);
this.delay = Math.min(this.delay + delay, this._max);
if (typeof cb !== "function") return this.delay;
this.cancel();
this._timer = setTimeout(() => {
try { cb(); }
catch (e) { console.log(e instanceof Error ? e.stack : e); }
finally { this._timer = null; }
}, this.delay);
return this.delay;
}
cancel() {
if (!this._timer) return;
clearTimeout(this._timer);
this._timer = null;
}
}
module.exports = Backoff;

52
node_modules/discordie/lib/core/DiscordieDispatcher.js generated vendored Normal file
View File

@@ -0,0 +1,52 @@
"use strict";
const DiscordieError = require("./DiscordieError");
const Constants = require("../Constants");
const EventTypes = Object.keys(Constants.Events);
const events = require("events");
let lastEvent = null;
function validateEvent(eventType) {
if (EventTypes.indexOf(eventType) < 0)
throw new DiscordieError(`Invalid event '${eventType}'`);
}
class DiscordieDispatcher extends events.EventEmitter {
constructor() {
super();
this._anyeventlisteners = [];
this.setMaxListeners(14);
}
static _getLastEvent() { return lastEvent; }
on(eventType, listener) {
validateEvent(eventType);
return super.on.apply(this, arguments);
}
onAny(fn) {
if (typeof fn !== "function")
return this;
this._anyeventlisteners.push(fn);
return this;
}
emit(eventType) {
validateEvent(eventType);
lastEvent = [].slice.call(arguments);
super.emit.apply(this, arguments);
const _arguments = arguments;
this._anyeventlisteners.forEach((fn) => {
fn.apply(this, _arguments);
});
}
hasListeners(eventType) {
validateEvent(eventType);
if (this._anyeventlisteners.length || this.listenerCount(eventType))
return true;
return false;
}
}
module.exports = DiscordieDispatcher;

13
node_modules/discordie/lib/core/DiscordieError.js generated vendored Normal file
View File

@@ -0,0 +1,13 @@
"use strict";
class DiscordieError extends Error {
constructor(message, exception) {
super(message);
if (exception) this.exception = exception;
}
toJSON() {
return this.message;
}
}
module.exports = DiscordieError;

24
node_modules/discordie/lib/core/DiscordieProfiler.js generated vendored Normal file
View File

@@ -0,0 +1,24 @@
"use strict";
const state = new Map();
const stats = new Map();
class DiscordieProfiler {
static hrtime() {
const t = process.hrtime();
return t[0] * 1000 + t[1] / 1000000;
}
static start(n) {
state.set(n, this.hrtime());
}
static stop(n) {
const d = this.hrtime() - state.get(n);
stats.set(n, d);
return d;
}
static get(n) {
return stats.get(n);
}
}
module.exports = DiscordieProfiler;

View File

@@ -0,0 +1,71 @@
"use strict";
const Constants = require("../Constants");
const Events = Constants.Events;
const Backoff = require("./Backoff");
/**
* @class
*/
class GatewayReconnectHandler {
constructor(discordie) {
this._discordie = discordie;
this._backoff = new Backoff(1000, 60000);
this._enabled = false;
}
_disconnected(e) {
if (!this._enabled) return;
e.autoReconnect = true;
e.delay = this._backoff.fail(() => this._discordie.connect());
}
_reset() {
this._backoff.reset();
}
/**
* Enables auto-reconnect.
*/
enable() { this._enabled = true; }
/**
* Disables auto-reconnect.
*/
disable() { this._enabled = false; }
/**
* Boolean indicating whether auto-reconnect is enabled.
* @returns {boolean}
* @readonly
*/
get enabled() { return this._enabled; }
/**
* Gets/sets minimum delay in milliseconds. Must be >= 0.
*
* Default is 1000.
* @returns {Number}
*/
get min() { return this._backoff.min; }
/**
* @ignore
* @type {Number}
**/
set min(value) { this._backoff.min = value; }
/**
* Gets/sets maximum delay in milliseconds. Must be >= 5000.
*
* Default is 60000.
* @returns {Number}
*/
get max() { return this._backoff.max; }
/**
* @ignore
* @type {Number}
**/
set max(value) { this._backoff.max = value; }
}
module.exports = GatewayReconnectHandler;

93
node_modules/discordie/lib/core/LimitedCache.js generated vendored Normal file
View File

@@ -0,0 +1,93 @@
"use strict";
function findKeyInsertionPoint(array, element, start, end) {
if (start === undefined) start = 0;
if (end === undefined) end = array.length;
var min = start, max = end;
while (true) {
var k = ((min + max) / 2) | 0;
if (k > end || min === max) break;
var result = (+element) - (+array[k]);
if (result === 0) break;
else if (result > 0) min = k + 1;
else if (result < 0) max = k;
}
return k;
}
class LimitedCache {
constructor(limit) {
this._keys = [];
Object.defineProperty(this, "_keys", { enumerable: false });
this._map = new Map;
this.setLimit(limit);
}
setLimit(limit) {
this.limit = limit || 1000;
if (!(this.limit > 0)) this.limit = 1;
return this.trim();
}
trim() {
var keys = this._keys;
if (keys.length <= this.limit) return null;
var removed = keys.splice(0, keys.length - this.limit);
for (var i = 0; i < removed.length; i++)
this._map.delete(removed[i]);
return removed;
}
set(k, v) {
if (!this._map.has(k)) {
this._keys.splice(findKeyInsertionPoint(this._keys, k), 0, k);
this.trim();
}
return this._map.set(k, v);
}
rename(from, to) {
var i = this._keys.indexOf(from);
if (i >= 0) {
this._keys.splice(i, 1);
this._keys.splice(findKeyInsertionPoint(this._keys, to), 0, to);
}
if (this._map.has(from)) {
this._map.set(to, this._map.get(from));
this._map.delete(from);
}
}
delete(k) {
if (this._map.delete(k)) {
var i = this._keys.indexOf(k);
if (i >= 0) this._keys.splice(i, 1);
return true;
}
return false;
}
clear() {
this._keys.length = 0;
return this._map.clear();
}
forEach(fn) { return this._map.forEach(fn); }
values() { return this._map.values(); }
entries() { return this._map.entries(); }
keys() { return this._map.keys(); }
get(k) { return this._map.get(k); }
has(k) { return this._map.has(k); }
get size() { return this._map.size; }
map(fn) {
if (typeof fn !== "function") {
throw new TypeError("fn is not a function");
}
if (!this._keys.length) return [];
var items = [];
for (var i = 0, len = this._keys.length; i < len; i++) {
var item = this.get(this._keys[i]);
items.push(fn(item));
}
return items;
}
}
module.exports = LimitedCache;

59
node_modules/discordie/lib/core/MessageHandlerCache.js generated vendored Normal file
View File

@@ -0,0 +1,59 @@
"use strict";
const Constants = require("../Constants");
const discordie = new WeakMap();
const messageHandlerCache = {};
function processMessage(source, socket, type, data) {
if (messageHandlerCache[`${source}/${type}`]) {
if (typeof messageHandlerCache[`${source}/${type}`] === "function")
return messageHandlerCache[`${source}/${type}`].apply(this, [data, socket]);
return false;
}
let messageHandler = null;
if (process.browser) {
const ctx = require.context("../networking/messages/", true, /\.js$/);
const modulePath = `./${source}/${type.toLowerCase()}.js`;
try {
messageHandler = ctx(modulePath);
} catch (e) { } //eslint-disable-line no-empty
} else {
const modulePath =
`../networking/messages/${source}/${type.toLowerCase()}`;
try {
messageHandler = require(modulePath);
} catch (e) {
if (e.code != "MODULE_NOT_FOUND")
throw e;
}
}
if (messageHandler) {
messageHandlerCache[`${source}/${type}`] = messageHandler;
return processMessage.apply(this, arguments);
}
}
class MessageHandlerCache {
constructor(_discordie) {
discordie.set(this, _discordie);
}
processVoiceMessage(socket, type, data) {
return processMessage.call(discordie.get(this),
"voice", socket, type, data
);
}
processGatewayMessage(socket, type, data) {
return processMessage.call(discordie.get(this),
"gateway", socket, type, data
);
}
}
module.exports = MessageHandlerCache;

106
node_modules/discordie/lib/core/ReadyEventScheduler.js generated vendored Normal file
View File

@@ -0,0 +1,106 @@
"use strict";
const Constants = require("../Constants");
const Events = Constants.Events;
const Utils = require("../core/Utils");
function emitReady(gw) {
const ready = this._readyPayload;
if (!this._readyPayload) return;
this._readyPayload = null;
if (!this._discordie.connected) return;
if (gw.isPrimary) {
this._discordie.Dispatcher.emit(Events.GATEWAY_READY, {
socket: gw,
data: ready
});
}
this._discordie.Dispatcher.emit(Events.ANY_GATEWAY_READY, {
socket: gw,
data: ready
});
}
function handleConnectionOpen(data, e) {
this._readyPayload = data;
this._collections = new Set(this._registeredCollections);
this._tasks = Array.from(this._registeredTasks);
return true;
}
function executeNextTask(gw) {
if (!this._readyPayload || !this._tasks.length) return;
const nextTask = this._tasks.shift();
nextTask.handler._executeReadyTask(this._readyPayload, gw);
}
class ReadyEventScheduler {
constructor(discordie, gateway) {
this._discordie = discordie;
this._readyPayload = null;
discordie.Dispatcher.on(Events.GATEWAY_DISPATCH, e => {
if (e.socket != gateway()) return;
Utils.bindGatewayEventHandlers(this, e, {
READY: handleConnectionOpen
});
});
discordie.Dispatcher.on(Events.GATEWAY_DISCONNECT, e => {
if (e.socket != gateway()) return;
this._readyPayload = null;
});
// parallel
this._registeredCollections = new Set();
this._collections = new Set();
discordie.Dispatcher.on(Events.COLLECTION_READY, e => {
this._collections.delete(e.collection);
if (this._collections.size) return;
executeNextTask.call(this, gateway());
});
// sequential
this._registeredTasks = [];
this._tasks = [];
discordie.Dispatcher.on(Events.READY_TASK_FINISHED, lastTask => {
const idx = this._tasks.findIndex(t => t.handler === lastTask.handler);
this._tasks.splice(idx, 1);
if (!this._tasks.length) {
return setImmediate(() => emitReady.call(this, gateway()));
}
executeNextTask.call(this, gateway());
});
Utils.privatify(this);
}
_waitFor(collection) {
this._registeredCollections.add(collection);
}
_addTask(name, handler) {
if (typeof name !== "string") {
throw new TypeError(
"ReadyEventScheduler: Invalid task name " + name
);
}
if (typeof handler._executeReadyTask !== "function") {
throw new TypeError(
"ReadyEventScheduler: Invalid handler for task " + name
);
}
this._registeredTasks.push({name, handler});
}
}
module.exports = ReadyEventScheduler;

154
node_modules/discordie/lib/core/Utils.js generated vendored Normal file
View File

@@ -0,0 +1,154 @@
"use strict";
const Stream = require("stream").Stream;
const Writable = require("stream").Writable;
class CachingSink extends Writable {
constructor() {
super();
this.buffer = [];
}
_write(chunk, encoding, done) {
this.buffer.push(chunk);
done();
}
getData() { return Buffer.concat(this.buffer); }
}
module.exports = {
allocBuffer(size) {
return Buffer.alloc ? Buffer.alloc(size) : new Buffer(size);
},
createArrayBuffer(source) {
var view = new Uint8Array(new ArrayBuffer(source.length));
for (var i = 0; i < view.length; i++)
view[i] = source[i];
return view.buffer;
},
createBuffer(source) {
if (source instanceof Float32Array) {
var buf = this.allocBuffer(source.length * 4);
for (var i = 0; i < source.length; i++) {
buf.writeFloatLE(source[i], i * 4);
}
return buf;
}
var view = new Uint8Array(source);
var buf = this.allocBuffer(view.length);
for (var i = 0; i < view.length; i++)
buf[i] = view[i];
return buf;
},
bindGatewayEventHandlers(source, event, map) {
if (!event.type)
throw new Error(`Invalid event '${event.type}'`);
let handler = map[event.type];
if (!handler) return;
if (typeof handler !== "function")
throw new Error(`Invalid handler ${handler} for event ${event.type}`);
if (handler.call(source, event.data, event))
event.handled = true;
},
privatify(target) {
for (var k in target) {
if (k.charCodeAt(0) != 95) // "_"
continue;
if (!target.hasOwnProperty(k)) continue;
Object.defineProperty(target, k, {enumerable: false});
}
},
definePrivate(target, kv) {
for (var k in kv) {
Object.defineProperty(target, k, {
enumberable: false,
writable: true,
value: kv[k]
});
}
},
reorderObjects(array, target, position) {
array = array.slice();
array.sort((a, b) => (a.position - b.position));
const from = Math.max(array.findIndex(c => (c.id == target.id)), 0);
const to = Math.min(Math.max(position, 0), array.length - 1);
if (from == to) return;
const remove = (i) => array.splice(i, 1)[0];
const insert = (i, v) => array.splice(i, 0, v);
insert(to, remove(from));
const updated = array.map((c, i) => ({id: c.valueOf(), position: i}));
const changes = to > from ?
updated.slice(from, to + 1) :
updated.slice(to, from + 1);
return changes;
},
imageToDataURL(buffer) {
if (!buffer || !(buffer instanceof Buffer)) return null;
const types = {
0xFFD8FF: "image/jpg",
0x89504E: "image/png"
};
const magic = buffer.readUIntBE(0, 3);
const type = types[magic];
if (!type) return null;
return `data:${type};base64,` + buffer.toString("base64");
},
timestampFromSnowflake(id) {
return id ? (+id / 4194304) + 1420070400000 : 0;
},
convertMentions(mentions) {
if (!mentions) return mentions;
if (mentions.map) return mentions.map(m => m.valueOf());
return [mentions.valueOf()];
},
fileExists(path) {
try {
return require("fs").statSync(path).isFile();
} catch (e) { return false; }
},
modelToObject(model) {
if (typeof model !== "object") return model;
if (Array.isArray(model)) {
return Array.from(model).map(i => this.modelToObject(i));
}
const copy = Object.assign({}, model);
Object.keys(copy).forEach(k => {
var v = copy[k];
const type = v && v.constructor && v.constructor.name;
if (type === "Object") copy[k] = this.modelToObject(v);
if (type === "Array") v = copy[k] = Array.from(v);
if (type === "Set") v = copy[k] = Array.from(v);
if (type === "Map") v = copy[k] = Array.from(v.values());
if (Array.isArray(v)) copy[k] = v.map(i => this.modelToObject(i));
});
return copy;
},
cacheStream(stream, cb) {
if (stream instanceof Stream &&
typeof stream._read === "function" &&
typeof stream.pipe === "function") {
const sink = new CachingSink();
sink.on("finish", () => cb(sink.getData()));
stream.pipe(sink);
return;
}
cb(stream);
},
emojiToCode(emoji) {
if (typeof emoji === "string") return emoji;
return emoji.id != null ? `${emoji.name}:${emoji.id}` : emoji.name;
}
};

54
node_modules/discordie/lib/core/ratelimiting/Bucket.js generated vendored Normal file
View File

@@ -0,0 +1,54 @@
"use strict";
const Profiler = require("../DiscordieProfiler");
class Bucket {
constructor(size, duration) {
this.size = size;
this.duration = duration;
if (typeof size !== "number")
throw new TypeError("Param 'size' is not a number");
if (typeof duration !== "number")
throw new TypeError("Param 'duration' is not a number");
this.refill();
}
resize(newSize) {
if (newSize > 0) {
if (this.size === this.dropsLeft) this.dropsLeft = newSize;
if ((this.size - 1) === this.dropsLeft) this.dropsLeft = newSize - 1;
}
this.size = newSize;
}
rescheduleRefill(timeFromNow) {
this.lastRefill = (Profiler.hrtime() - this.duration) + timeFromNow;
}
wait(time) {
this.rescheduleRefill(time);
this.dropsLeft = 0;
}
refill() {
this.lastRefill = Profiler.hrtime();
this.dropsLeft = this.size;
}
get waitTime() {
var now = Profiler.hrtime();
return (this.lastRefill + this.duration) - now;
}
get available() {
if (this.waitTime < 0)
this.refill();
return this.dropsLeft;
}
consume(n) {
if (!n) n = 1;
if (this.available < n)
return false;
this.dropsLeft -= n;
return true;
}
}
module.exports = Bucket;

View File

@@ -0,0 +1,39 @@
"use strict";
const Bucket = require("./Bucket");
class ChainedBucket extends Bucket {
constructor(size, duration, name, parent) {
super(size, duration);
this.parent = parent || null;
this.name = name || null;
}
refillByName(name) {
if (this.parent) this.parent.refillByName(name);
if (this.name && this.name.indexOf(name) === 0) this.refill();
}
get waitingBucket() {
if (this.parent && this.parent.available <= 0)
return this.parent;
if (this.available <= 0)
return this;
return null;
}
get available() {
// this getter triggers refill
const availableParent = this.parent && this.parent.available;
const availableSelf = super.available;
if (this.parent && availableParent <= 0)
return availableParent;
return availableSelf;
}
consume(n) {
// this triggers 'available' getter which triggers refill
if (this.parent) {
return super.consume(n) && this.parent.consume(n);
}
return super.consume(n);
}
}
module.exports = ChainedBucket;

View File

@@ -0,0 +1,128 @@
"use strict";
const Deque = require("double-ended-queue");
const DEFAULT_RETRY_AFTER = 100;
const POST_RATELIMIT_DELAY = 1000;
class RequestQueue {
constructor(bucket) {
this.bucket = bucket || null;
this.queue = new Deque();
this.timeout = null;
this.draining = false;
this.lastReset = 0;
this.disabled = false;
}
enqueue(request, sendCallback) {
this.queue.push({request, sendCallback});
this._drain();
}
_drain() {
if (this.disabled) {
const entry = this.queue.shift();
entry.request.send(entry.sendCallback);
if (this.queue.length) {
setImmediate(() => this._drain());
}
return;
}
if (!this.queue.length) return;
if (this.timeout !== null || this.draining) return;
if (this.bucket && !this.bucket.consume()) {
this._scheduleDrain(this.bucket.waitTime);
return;
}
this.draining = true;
const entry = this.queue.shift();
entry.request.send((err, res) => {
this.draining = false;
this._updateBucket(res);
if (err && res && res.status === 429) {
this.queue.unshift(entry);
const retryAfter =
res.body["retry_after"] ||
(+res.headers["retry-after"]) ||
DEFAULT_RETRY_AFTER;
const isGlobal =
res.body["global"] ||
(res.headers["x-ratelimit-global"] === "true") ||
false;
if (isGlobal) {
const manager = entry.request._discordie._queueManager;
if (manager && manager.globalBucket) {
manager.globalBucket.wait(retryAfter);
}
} else if (this.bucket) {
this.bucket.wait(retryAfter);
}
this._scheduleDrain(retryAfter);
return;
}
setImmediate(() => this._drain());
if (typeof entry.sendCallback === "function") {
entry.sendCallback(err, res);
}
});
}
_updateBucket(res) {
if (!res || !res.headers) return;
if (!this.bucket) return;
// update limits and timing based on server info if available,
// otherwise fallback to hardcoded buckets
// each window is delayed by POST_RATELIMIT_DELAY = 1s:
// this makes it slower, but ensures not hitting 429 unless:
// - some other client has drained all tokens already,
// - server time is out of sync by more than 1 second,
// - or headers X-RateLimit-Reset/Date do not contain usable time
if (res.headers["x-ratelimit-limit"]) {
const limit = +res.headers["x-ratelimit-limit"];
if (limit > 0) this.bucket.resize(limit);
}
if (res.headers["x-ratelimit-remaining"]) {
const remaining = +res.headers["x-ratelimit-remaining"];
if (!isNaN(remaining)) this.bucket.dropsLeft = remaining;
}
if (res.headers["x-ratelimit-reset"] && res.headers["date"]) {
const resetSeconds = (+res.headers["x-ratelimit-reset"]) || 0;
if (resetSeconds > 0) {
const date = new Date(res.headers["date"]).getTime();
const reset = new Date(resetSeconds * 1000).getTime();
const now = date > 0 ? date : Date.now();
if (!this.lastReset || reset > this.lastReset) {
this.lastReset = reset;
this.bucket.rescheduleRefill(reset - now);
}
}
}
}
_scheduleDrain(time) {
this.timeout = setTimeout(() => {
this.timeout = null;
this._drain();
}, time + POST_RATELIMIT_DELAY);
}
}
module.exports = RequestQueue;

View File

@@ -0,0 +1,256 @@
"use strict";
const Utils = require("../../core/Utils");
const RequestQueue = require("./RequestQueue");
const ChainedBucket = require("./ChainedBucket");
class RequestQueueGroup {
constructor(bucketFactory) {
this.queues = {};
this.bucketFactory = bucketFactory || null;
if (!(bucketFactory instanceof BucketFactory)) {
throw new TypeError(
"Param 'bucketFactory' is not an instance of BucketFactory"
);
}
}
get(id) {
if (!this.queues[id]) {
const bucket = (this.bucketFactory && this.bucketFactory.get(id));
this.queues[id] = new RequestQueue(bucket);
}
this.queues[id].id = id;
return this.queues[id];
}
delete(id) {
if (this.bucketFactory)
this.bucketFactory.delete(id);
delete this.queues[id];
}
deleteContaining(id) {
var keys = Object.keys(this.queues);
for (var i = 0, len = keys.length; i < len; i++) {
var key = keys[i];
if (!key || key.indexOf(id) < 0) continue;
this.delete(key);
}
}
}
class BucketFactory {
constructor(manager, size, duration, name, parent) {
this.manager = manager;
this.size = size;
this.duration = duration;
this.name = name;
this.parent = parent || null;
if (!(manager instanceof RequestQueueManager))
throw new TypeError("Param 'manager' is invalid");
if (typeof size !== "number")
throw new TypeError("Param 'size' is not a number");
if (typeof duration !== "number")
throw new TypeError("Param 'duration' is not a number");
if (typeof name !== "string")
throw new TypeError("Param 'name' is not a string");
}
makeName(id) {
return this.name + ":" + id;
}
get(id) {
const parent =
this.parent instanceof BucketFactory ?
this.parent.get(id) :
this.parent;
return this.manager._createBucket(
this.size, this.duration, this.makeName(id), parent
);
}
delete(id) {
delete this.manager.buckets[this.makeName(id)];
}
}
class RequestQueueManager {
constructor(discordie) {
this._discordie = discordie;
this.isBot = true;
this.disabled = false;
this.buckets = {};
this.bucketFactories = {};
// whole API, bucket blocks when client gets a HTTP 429 with global flag
const _bot_global =
this._createBucket(Infinity, 1000, "bot:global");
this.globalBucket = _bot_global;
// msg 10/10s
const _msg =
this._createBucket(10, 10000, "msg", _bot_global);
this.userMessageQueue = new RequestQueue(_msg);
// per-channel bot:msg:dm 10/10s
const _bot_msg_dm =
this._createBucketFactory(10, 10000, "bot:msg:dm", _bot_global);
this.botDirectMessageQueues = new RequestQueueGroup(_bot_msg_dm);
// per-guild bot:msg:server 10/10s
const _bot_msg_server =
this._createBucketFactory(10, 10000, "bot:msg:server", _bot_global);
this.botMessageQueues = new RequestQueueGroup(_bot_msg_server);
// per-guild dmsg 5/1s
const _dmsg =
this._createBucketFactory(5, 1000, "dmsg", _bot_global);
this.messageDeleteQueues = new RequestQueueGroup(_dmsg);
// per-guild bdmsg 1/1s
const _bdmsg =
this._createBucketFactory(1, 1000, "bdmsg", _bot_global);
this.messageBulkDeleteQueues = new RequestQueueGroup(_bdmsg);
// per-guild guild_member 10/10s
const _guild_member =
this._createBucketFactory(10, 10000, "guild_member", _bot_global);
this.guildMemberPatchQueues = new RequestQueueGroup(_guild_member);
// per-guild guild_member_nick 1/1s
const _guild_member_nick =
this._createBucketFactory(1, 1000, "guild_member_nick", _bot_global);
this.guildMemberNickQueues = new RequestQueueGroup(_guild_member_nick);
// all other requests go here with route as key
// bucket size should be set by HTTP headers
const _bot_generic =
this._createBucketFactory(Infinity, 5000, "bot:generic", _bot_global);
this.genericRequestQueues = new RequestQueueGroup(_bot_generic);
discordie.Dispatcher.on("GATEWAY_DISPATCH", e => {
if (!e.data) return;
if (e.type === "READY") {
if (!e.data.user) return;
this.isBot = e.data.user.bot || false;
}
if (e.type === "GUILD_DELETE") {
this._deleteGuildQueues(e.data.id);
}
if (e.type === "CHANNEL_DELETE") {
this._deleteChannelQueues(e.data.id);
}
});
Utils.privatify(this);
}
_reset() {
Object.keys(this.buckets).forEach(k => this.buckets[k].refill());
}
_createBucket(size, duration, name, parent) {
if (!this.buckets[name]) {
this.buckets[name] = new ChainedBucket(size, duration, name, parent);
} else {
this.buckets[name].refill();
}
return this.buckets[name];
}
_createBucketFactory(size, duration, name, parent) {
if (!this.bucketFactories[name]) {
this.bucketFactories[name] =
new BucketFactory(this, size, duration, name, parent);
}
return this.bucketFactories[name];
}
put(request, sendCallback) {
const route = request.path
.replace(/\/\d+$/g, "")
.replace(/\d+/g, ":id");
// convert to route: <- /api/guilds/:guild_id/bans/:user_id
// -> /api/guilds/:id/bans
// <- /api/channels/:channel_id
// -> /api/channels/
const queue = this.genericRequestQueues.get(route);
this._enqueueTo(queue, request, sendCallback);
}
putToRoute(request, route, sendCallback) {
const queue = this.genericRequestQueues.get(route);
this._enqueueTo(queue, request, sendCallback);
}
putMessage(request, channelId, sendCallback) {
const channel = this._discordie._channels.get(channelId);
var queue = this.userMessageQueue;
if (this.isBot && channel) {
if (channel.is_private || !channel.guild_id) {
queue = this.botDirectMessageQueues.get(channelId);
} else {
queue = this.botMessageQueues.get(channel.guild_id);
}
}
this._enqueueTo(queue, request, sendCallback);
}
putDeleteMessage(request, channelId, sendCallback) {
const group = this.messageDeleteQueues;
this._enqueueToGroup(group, request, channelId, sendCallback);
}
putBulkDeleteMessage(request, channelId, sendCallback) {
const group = this.messageBulkDeleteQueues;
this._enqueueToGroup(group, request, channelId, sendCallback);
}
putGuildMemberPatch(request, guildId, sendCallback) {
const queue = this.guildMemberPatchQueues.get(guildId);
this._enqueueTo(queue, request, sendCallback);
}
putGuildMemberNick(request, guildId, sendCallback) {
const queue = this.guildMemberNickQueues.get(guildId);
this._enqueueTo(queue, request, sendCallback);
}
_enqueueToGroup(group, request, channelId, sendCallback) {
const channel = this._discordie._channels.get(channelId);
const guildId = (channel && channel.guild_id) || null;
this._enqueueTo(group.get(guildId), request, sendCallback);
}
_enqueueTo(queue, request, sendCallback) {
if (this.disabled) {
return request.send(sendCallback);
}
queue.enqueue(request, sendCallback);
}
_deleteGuildQueues(guildId) {
const groups = [
this.botMessageQueues,
this.messageDeleteQueues,
this.messageBulkDeleteQueues,
this.guildMemberPatchQueues
];
groups.forEach(g => g.delete(guildId));
this.genericRequestQueues.deleteContaining(guildId);
}
_deleteChannelQueues(channelId) {
this.botDirectMessageQueues.delete(channelId);
this.genericRequestQueues.deleteContaining(channelId);
}
}
module.exports = RequestQueueManager;

414
node_modules/discordie/lib/index.js generated vendored Normal file
View File

@@ -0,0 +1,414 @@
"use strict";
const DiscordieDispatcher = require("./core/DiscordieDispatcher");
const events = require("events");
const request = require("./core/ApiRequest");
const DiscordieError = require("./core/DiscordieError");
const Constants = require("./Constants");
const Events = Constants.Events;
const GatewaySocket = require("./networking/ws/GatewaySocket");
const Utils = require("./core/Utils");
const GuildCollection = require("./collections/GuildCollection");
const ChannelCollection = require("./collections/ChannelCollection");
const UserCollection = require("./collections/UserCollection");
const GuildMemberCollection = require("./collections/GuildMemberCollection");
const MessageCollection = require("./collections/MessageCollection");
const PresenceCollection = require("./collections/PresenceCollection");
const VoiceStateCollection = require("./collections/VoiceStateCollection");
const VoiceConnectionCollection = require("./collections/VoiceConnectionCollection");
const UnavailableGuildCollection = require("./collections/UnavailableGuildCollection");
const GuildSyncCollection = require("./collections/GuildSyncCollection");
const CallCollection = require("./collections/CallCollection");
const User = require("./models/User");
const AuthenticatedUser = require("./models/AuthenticatedUser");
const IGuildCollection = require("./interfaces/IGuildCollection");
const IChannelCollection = require("./interfaces/IChannelCollection");
const IAuthenticatedUser = require("./interfaces/IAuthenticatedUser");
const IUserCollection = require("./interfaces/IUserCollection");
const IMessageCollection = require("./interfaces/IMessageCollection");
const IDirectMessageChannelCollection = require("./interfaces/IDirectMessageChannelCollection");
const IInviteManager = require("./interfaces/IInviteManager");
const IWebhookManager = require("./interfaces/IWebhookManager");
const RequestQueueManager = require("./core/ratelimiting/RequestQueueManager");
const GatewayReconnectHandler = require("./core/GatewayReconnectHandler");
const ReadyEventScheduler = require("./core/ReadyEventScheduler");
const MessageHandlerCache = require("./core/MessageHandlerCache");
const messageHandlerCache = new WeakMap();
const rest = require("./networking/rest");
function handleAuthLoginError(e) {
this.pendingLogin = false;
this.Dispatcher.emit(
Events.DISCONNECTED,
{error: new DiscordieError("Login failed", e.error)}
);
}
function handleAuthLoginSuccess(e) {
this.pendingLogin = false;
this.token = e.token;
delete e.token;
delete e.password;
rest(this).gateway();
}
function handleGatewayError(e) {
this.pendingLogin = false;
var ev = {error: new DiscordieError("Could not get gateway", e.error)};
if (this.autoReconnect) this.autoReconnect._disconnected(ev);
this.Dispatcher.emit(Events.DISCONNECTED, ev);
}
function handleGatewaySuccess(e) {
this.pendingLogin = false;
this.gatewayEndpoint = e.gateway;
this._createPrimaryGateway();
}
function registerGatewayHandlers() {
this.Dispatcher.on(Events.GATEWAY_DISPATCH, e => {
if (e.suppress) { delete e.suppress; delete e.handled; return; }
const handlers = messageHandlerCache.get(this);
if (!handlers.processGatewayMessage(e.socket, e.type, e.data) && !e.handled) {
if (!e.socket.isPrimary) return;
return this.Dispatcher.emit(
Events.GATEWAY_UNHANDLED_MESSAGE,
{type: e.type, data: e.data}
);
}
if (e.handled) delete e.handled;
});
const onVoiceMessage = (type, e) => {
if (e.suppress) { delete e.suppress; delete e.handled; return; }
const handlers = messageHandlerCache.get(this);
if (!handlers.processVoiceMessage(e.socket, type, e.data) && !e.handled) {
return this.Dispatcher.emit(
Events.VOICESOCKET_UNHANDLED_MESSAGE,
{type: type, data: e.data}
);
}
if (e.handled) delete e.handled;
};
this.Dispatcher.on(Events.VOICE_READY, e => {
onVoiceMessage("READY", e);
});
this.Dispatcher.on(Events.VOICE_SESSION_DESCRIPTION, e => {
onVoiceMessage("SESSION_DESCRIPTION", e);
});
this.Dispatcher.on(Events.VOICE_SPEAKING, e => {
onVoiceMessage("SPEAKING", e);
});
}
const defaultOptions = {
compressMessages: true,
messageQueue: true,
};
/**
* @class
* @classdesc
* Additional constructor options:
*
* ```js
* var client = new Discordie({
* // Maximum amount of messages to store in channel cache.
* // Decreasing this will reduce memory usage over time.
* // With low values (below 50) chances of message invalidation increase:
* // accessing message properties (ex. `e.message.channel`) in callbacks
* // of long running tasks (ex. HTTP requests) becomes unsafe.
* messageCacheLimit: 1000, // < default
*
* // Guild sharding:
* // (Note that this is only intended to be used by large bots.)
* // 'shardId' is a number starting at 0 and less than 'shardCount'
* // 'shardCount' must be a number greater than 1
* shardId: 0, shardCount: 2, // sharding is disabled by default
*
* // Gateway auto-reconnect:
* // If enabled, 'DISCONNECTED' event will also contain properties
* // 'autoReconnect' boolean, set to true
* // 'delay' delay in milliseconds until next connect attempt
* autoReconnect: false, // < default
* });
* ```
*/
class Discordie {
constructor(options) {
messageHandlerCache.set(this, new MessageHandlerCache(this));
if (!options) options = defaultOptions;
const _defaultOptions = Object.assign({}, defaultOptions);
this.options = Object.assign(_defaultOptions, options);
Object.defineProperty(this, "options", {writable: false});
this.token = null;
this.bot = false;
/**
* Primary event bus.
* @returns {EventEmitter}
* @example
* client.Dispatcher.on("GATEWAY_READY", e => {
* console.log("Connected as: " + client.User.username);
* });
*/
this.Dispatcher = new DiscordieDispatcher();
this.gatewaySocket = null;
const gw = () => this.gatewaySocket;
this._readyScheduler = new ReadyEventScheduler(this, gw);
this._user = new AuthenticatedUser();
this._guilds = new GuildCollection(this, gw);
this._channels = new ChannelCollection(this, gw);
this._users = new UserCollection(this, gw);
this._members = new GuildMemberCollection(this, gw);
this._presences = new PresenceCollection(this, gw);
this._messages = new MessageCollection(this, gw);
this._voicestates = new VoiceStateCollection(this, gw);
this._calls = new CallCollection(this, gw);
if (options.messageCacheLimit) {
this._messages.setMessageLimit(options.messageCacheLimit);
}
this._queueManager = new RequestQueueManager(this);
if (!this.options.messageQueue) {
this._queueManager.disabled = true;
}
// == PUBLIC == //
/**
* Represents current user.
* @returns {IAuthenticatedUser}
*/
this.User = new IAuthenticatedUser(this);
/**
* Interface to a collection containing all "Discord Servers"
* (internally called guilds) current session is connected
* to. Does not contain unavailable guilds.
* @returns {IGuildCollection}
*/
this.Guilds = new IGuildCollection(this,
() => this._guilds.values(),
(key) => this._guilds.get(key));
/**
* Interface to a collection containing all public channels current session
* is connected to.
* @returns {IChannelCollection}
*/
this.Channels = new IChannelCollection(this,
() => this._channels.getGuildChannelIterator(),
(key) => this._channels.getGuildChannel(key));
/**
* Interface to a collection containing all users current session has
* been exposed to.
*
* Contains only online users after `READY`.
* See documentation for `IUserCollection.fetchMembers(guilds)` if you
* want to load offline members too.
* @returns {IUserCollection}
*/
this.Users = new IUserCollection(this,
() => this._users.values(),
(key) => this._users.get(key));
/**
* Interface to a collection containing all private (direct message)
* channels current session is connected to.
* @returns {IDirectMessageChannelCollection}
*/
this.DirectMessageChannels = new IDirectMessageChannelCollection(this,
() => this._channels.getPrivateChannelIterator(),
(key) => this._channels.getPrivateChannel(key));
/**
* Interface to a collection containing all cached messages.
* @returns {IMessageCollection}
*/
this.Messages = new IMessageCollection(this,
() => this._messages.getIterator(),
(key) => this._messages.get(key));
/**
* An instance of IInviteManager.
* @returns {IInviteManager}
*/
this.Invites = new IInviteManager(this);
/**
* An instance of IWebhookManager.
* @returns {IWebhookManager}
*/
this.Webhooks = new IWebhookManager(this);
/**
* An array of VoiceConnectionInfo.
* @returns {Array<VoiceConnectionInfo>}
*/
this.VoiceConnections = VoiceConnectionCollection.create(this, gw);
/**
* An array of unavailable guild's ids.
* @returns {Array<String>}
*/
this.UnavailableGuilds = UnavailableGuildCollection.create(this, gw);
this.SyncedGuilds = new GuildSyncCollection(this, gw);
this._readyScheduler._waitFor(this.UnavailableGuilds);
this._readyScheduler._addTask("GuildSync", this.SyncedGuilds);
// == EVENTS == //
this.Dispatcher.on(Events.REQUEST_AUTH_LOGIN_ERROR, handleAuthLoginError.bind(this));
this.Dispatcher.on(Events.REQUEST_AUTH_LOGIN_SUCCESS, handleAuthLoginSuccess.bind(this));
this.Dispatcher.on(Events.REQUEST_GATEWAY_ERROR, handleGatewayError.bind(this));
this.Dispatcher.on(Events.REQUEST_GATEWAY_SUCCESS, handleGatewaySuccess.bind(this));
/**
* Auto-reconnect handler.
* @returns {GatewayReconnectHandler}
*/
this.autoReconnect = new GatewayReconnectHandler(this);
if (options.autoReconnect) this.autoReconnect.enable();
// must register our gateway/voice events last to check e.handled
registerGatewayHandlers.call(this);
Utils.privatify(this);
}
/**
* Current state.
*
* Possible return values:
*
* - **`DISCONNECTED`**: Initial state. Returned if token is not available.
* - **`LOGGING_IN`**
* - **`LOGGED_IN`**: Returned if token is set, but gateway websocket is
* not connected.
* - **`CONNECTING`**
* - **`CONNECTED`**
*
* State `CONNECTED` only indicates state of gateway websocket connection,
* returns both before and after `READY`.
* @returns {String}
* @readonly
*/
get state() {
if (this.pendingLogin)
return Constants.DiscordieState.LOGGING_IN;
if (this.gatewaySocket) {
if (this.gatewaySocket.connected)
return Constants.DiscordieState.CONNECTED;
if (this.gatewaySocket.connecting)
return Constants.DiscordieState.CONNECTING;
}
if (this.token)
return Constants.DiscordieState.LOGGED_IN;
return Constants.DiscordieState.DISCONNECTED;
}
/**
* Gets a value indicating whether the gateway websocket connection is
* established.
* @returns {boolean}
* @readonly
*/
get connected() {
return this.state == Constants.DiscordieState.CONNECTED;
}
/**
* @param {Object} credentials - Can contain `email`, `password` or `token`
*/
connect(credentials) {
if (this.state == Constants.DiscordieState.CONNECTED
|| this.state == Constants.DiscordieState.CONNECTING
|| this.pendingLogin)
return;
this.pendingLogin = true;
if (credentials && credentials.hasOwnProperty("bot")) {
this.bot = credentials.bot;
}
if (credentials && credentials.token) {
this.token = credentials.token;
credentials = null;
}
if (!credentials && this.token) {
rest(this).gateway();
return;
}
rest(this).auth.login(credentials);
}
/**
* Disconnects primary gateway websocket.
*/
disconnect() {
if (this.gatewaySocket) {
this.gatewaySocket.disconnect();
this.gatewaySocket = null;
}
}
_createPrimaryGateway() {
if (!this.gatewaySocket) {
const compressMessages = this.options.compressMessages;
const shardId = this.options.shardId;
const shardCount = this.options.shardCount;
const gatewayOptions = {compressMessages, shardId, shardCount};
this.gatewaySocket = new GatewaySocket(this, gatewayOptions);
}
this.gatewaySocket.connect(this.gatewayEndpoint);
}
}
Discordie.Events = Constants.Events;
Discordie.StatusTypes = Constants.StatusTypes;
Discordie.ActivityTypes = Constants.ActivityTypes;
Discordie.States = Constants.DiscordieState;
Discordie.Permissions = Constants.Permissions;
Discordie.VerificationLevel = Constants.VerificationLevel;
Discordie.MFALevels = Constants.MFALevels;
Discordie.UserNotificationSettings = Constants.UserNotificationSettings;
Discordie.Errors = Constants.Errors;
Discordie.ChannelTypes = Constants.ChannelTypes;
Discordie.MessageTypes = Constants.MessageTypes;
module.exports = Discordie;

View File

@@ -0,0 +1,327 @@
"use strict";
const IBase = require("./IBase");
const IUser = require("./IUser");
const Utils = require("../core/Utils");
const AuthenticatedUser = require("../models/AuthenticatedUser");
const Constants = require("../Constants");
const StatusTypes = Constants.StatusTypes;
const rest = require("../networking/rest");
/**
* @interface
* @model AuthenticatedUser
* @extends IBase
*/
class IAuthenticatedUser extends IBase {
constructor(discordie) {
super();
Utils.definePrivate(this, {_discordie: discordie});
Object.freeze(this);
}
/**
* Gets date and time the account was registered (created) at.
* @returns {Date}
* @readonly
*/
get registeredAt() {
return new Date(Utils.timestampFromSnowflake(this.id));
}
/**
* Gets current JPG or GIF avatar URL.
* @returns {String|null}
* @readonly
*/
get avatarURL() {
return Object.getOwnPropertyDescriptor(IUser.prototype, "avatarURL")
.get.apply(this, arguments);
}
/**
* Gets current JPG avatar URL.
* @returns {String|null}
* @readonly
*/
get staticAvatarURL() {
return Object.getOwnPropertyDescriptor(IUser.prototype, "staticAvatarURL")
.get.apply(this, arguments);
}
/**
* Gets a value indicating whether the account is claimed.
* @returns {boolean}
* @readonly
*/
get isClaimedAccount() {
return this.email != null;
}
/**
* Checks whether the user is mentioned in a `message`.
* @param {IMessage} message
* @param {boolean} ignoreImplicitMentions
* @returns {boolean}
*/
isMentioned(message, ignoreImplicitMentions) {
return IUser.prototype.isMentioned.apply(this, arguments);
}
/**
* Makes a request to get the bot's OAuth2 application info.
*
* Works only with bot accounts.
*
* Example response object:
* ```js
* {
* "description": "Test",
* "icon": null,
* "id": "179527948411052118",
* "name": "app name or something",
* "rpc_origins": [],
* "flags": 0,
* "owner": {
* "username": "bot owner",
* "discriminator": "4937",
* "id": "169454786183781631",
* "avatar": null
* }
* }
* ```
* @returns {Promise<Object, Error>}
*/
getApplication() {
return rest(this._discordie).oauth2.getApplication(Constants.ME);
}
/**
* Makes a request to edit user profile,
* substituting `undefined` or `null` properties with current values.
*
* Passing `null` in `avatar` will remove current avatar. Use `undefined`
* instead of `null` in this case.
* @param {String} currentPassword - Use null as password on bot accounts
* @param {String} [username]
* @param {String|Buffer|null} [avatar] - Buffer or base64 data URL
* @param {String} [email]
* @param {String} [newPassword]
* @returns {Promise<IUser, Error>}
* @example
* // avatar from file
* client.User.edit(currentPassword, null, fs.readFileSync("test.png"));
* client.User.edit(currentPassword, null, fs.readFileSync("test.jpg"));
* // avatar unchanged
* client.User.edit(currentPassword, "test", undefined, "new@example.com");
* client.User.edit(currentPassword, "test", client.User.avatar);
* // no avatar / default avatar
* client.User.edit(currentPassword, "test", null);
*/
edit(currentPassword, username, avatar, email, newPassword) {
const user = this._discordie._user;
username = username || user.username;
email = email || user.email;
newPassword = newPassword || null;
if (avatar instanceof Buffer) {
avatar = Utils.imageToDataURL(avatar);
} else if (avatar === undefined) {
avatar = user.avatar;
}
return new Promise((rs, rj) => {
rest(this._discordie)
.users.me(currentPassword, username, avatar, email, newPassword)
.then(() => rs(this._discordie.User))
.catch(rj);
});
}
/**
* Makes a request to edit avatar.
*
* Setting avatar to `null` will remove current avatar.
* @param {String|Buffer|null} avatar - Buffer or base64 data URL
* @param {String} [currentPassword] - Not applicable for bot accounts
* @returns {Promise<IUser, Error>}
* @example
* // avatar from file
* client.User.setAvatar(fs.readFileSync("test.png"));
* // remove avatar
* client.User.setAvatar(null);
*/
setAvatar(avatar, currentPassword) {
return this.edit(currentPassword, null, avatar);
}
/**
* Makes a request to edit username.
* @param {String} username
* @param {String} [currentPassword] - Not applicable for bot accounts
* @returns {Promise<IUser, Error>}
*/
setUsername(username, currentPassword) {
return this.edit(currentPassword, username);
}
/**
* Sets current user status via websocket.
*
* With multiple sessions status from last connected will override statuses
* from previous sessions.
*
* > Note: By default Discord client does not display game/status updates for
* > the user it's logged in into. It will be visible for other users.
* @param {Object|String} status
* Object `{status: String, afk: boolean}` or string.
* Field `afk` changes how Discord handles push notifications.
* @param {Object|String|null} [game] - Object `{name: String}` or string
* @example
* var game = {name: "with code"}; // sets status as "Playing with code"
* var streamingGame = {type: 1, name: "something", url: ""}; // "Streaming"
* // note: streaming status requires a valid twitch url:
* // ex. "http://twitch.tv/channel"
* client.User.setStatus("idle", game); // idle, playing
* client.User.setStatus(null, game); // no status change, playing
* client.User.setStatus(null, "with code"); // no status change, playing
* client.User.setStatus(null, streamingGame); // no status change, streaming
* client.User.setStatus("online", game); // online, playing
* client.User.setStatus("idle", null); // idle, not playing
* client.User.setStatus("dnd"); // "do not disturb"
* client.User.setStatus("invisible");
* client.User.setStatus({status: "idle", afk: true});
*/
setStatus(status, game) {
if (arguments.length == 0) return;
var afk = this.afk;
if (!status) status = this.status;
if (typeof status === "object") {
afk = status.afk != null ? !!status.afk : this.afk;
status = status.status != null ? status.status : this.status;
}
if (game === undefined) game = this.game;
if (typeof game === "string") game = {name: game};
status = Object.keys(StatusTypes).map(v => StatusTypes[v])
.find(v => v === status.toLowerCase());
status = status || StatusTypes.ONLINE;
if (this._discordie._user) {
this._discordie._user = this._discordie._user.merge({status, game, afk});
}
if (!this._discordie.gatewaySocket) return;
this._discordie.gatewaySocket.statusUpdate(
status,
status === StatusTypes.IDLE ? Date.now() : null,
game,
afk
);
}
/**
* Sets playing game for current user via websocket.
*
* With multiple sessions status from last connected will override statuses
* from previous sessions.
*
* > Note: By default Discord client does not display game/status updates for
* > the user it's logged in into. It will be visible for other users.
* @param {Object|String|null} game - Object `{name: String}` or string
* @example
* var game = {name: "with code"}; // sets game as "Playing with code"
* var streamingGame = {type: 1, name: "something", url: ""}; // "Streaming"
* // note: streaming status requires a valid twitch url:
* // ex. "http://twitch.tv/channel"
* client.User.setGame(game); // playing
* client.User.setGame("with code"); // playing
* client.User.setGame(streamingGame); // streaming
* client.User.setGame(null); // not playing
*/
setGame(game) {
if (arguments.length == 0) return;
this.setStatus(null, game);
}
/**
* Name of the game current user is playing.
* @returns {String|null}
* @readonly
*/
get gameName() {
return this.game ? this.game.name : null;
}
/**
* Attempts to get a guild member interface, returns null if this user is not
* a member of the `guild` or `guild` is not in cache.
* @param {IGuild|String} guild
* @returns {IGuildMember|null}
*/
memberOf(guild) {
return this._discordie.Users.getMember(guild.valueOf(), this.id) || null;
}
/**
* See `IUser.permissionsFor`.
* @see IUser.permissionsFor
* @param {IChannel|IGuild} context
* @returns {IPermissions}
*/
permissionsFor(context) {
return IUser.prototype.permissionsFor.apply(this, arguments);
}
/**
* See `IUser.can`.
* @see IUser.can
* @param {Number} permission - One or multiple permission bits
* @param {IChannel|IGuild} context
* @returns {boolean}
*/
can(permission, context) {
return IUser.prototype.can.apply(this, arguments);
}
/**
* See `IUser.getVoiceChannel`.
* @see IUser.getVoiceChannel
* @param {IGuild|String} guild - Guild or an id string
* @returns {IVoiceChannel|null}
*/
getVoiceChannel(guild) {
return IUser.prototype.getVoiceChannel.apply(this, arguments);
}
/**
* Creates a mention from this user's id.
* @returns {String}
* @readonly
* @example
* channel.sendMessage(user.mention + ", example mention");
*/
get mention() {
return `<@${this.id}>`;
}
/**
* Creates a nickname mention from this user's id.
* @returns {String}
* @readonly
*/
get nickMention() {
return `<@!${this.id}>`;
}
}
IAuthenticatedUser._inherit(AuthenticatedUser, function(key) {
return this._discordie._user[key];
});
module.exports = IAuthenticatedUser;

157
node_modules/discordie/lib/interfaces/IBase.js generated vendored Normal file
View File

@@ -0,0 +1,157 @@
"use strict";
const Utils = require("../core/Utils");
class IBase {
static _inherit(proto, get) {
var thisProto = this.prototype;
thisProto._valueOverrides = thisProto._valueOverrides || {};
thisProto._gettersByProperty = thisProto._gettersByProperty || {};
thisProto._suppressErrors = false;
Object.defineProperties(thisProto, {
_valueOverrides: {enumerable: false},
_gettersByProperty: {enumerable: false},
_suppressErrors: {enumerable: false}
});
thisProto._gettersByProperty[this.name] =
thisProto._gettersByProperty[this.name] || {};
const interfacingProperties = new proto();
for (let key in interfacingProperties) {
thisProto._gettersByProperty[this.name][key] = get;
Object.defineProperty(thisProto, key, {
enumerable: true,
configurable: true,
get: function() {
const __thisproto__ = Object.getPrototypeOf(this);
try {
const value = get.call(this, key);
if (__thisproto__._valueOverrides[key]) {
return __thisproto__._valueOverrides[key].call(this, value);
}
return value;
} catch (e) {
const __ctrproto__ = Object.getPrototypeOf(this.constructor);
if (!__ctrproto__._suppressErrors) console.error(e.stack);
return null;
}
}
});
}
}
static _setValueOverride(k, fn) {
if (!this.prototype.hasOwnProperty(k)) {
throw new Error(
`Property '${k}' is not defined for ${this.constructor.name}`
);
}
if (typeof fn !== "function") {
return delete this._valueOverrides[k];
}
this.prototype._valueOverrides[k] = fn;
}
static _setSuppressErrors(value) {
Object.getPrototypeOf(this)._suppressErrors = value;
}
// Get a copy of raw model data or property
getRaw(property) {
var allGetters = Object.getPrototypeOf(this)._gettersByProperty;
var getters = allGetters[this.constructor.name];
if (!getters) {
// no own properties, lookup last inherited class
var lastClass = Object.keys(allGetters).pop();
getters = allGetters[lastClass];
}
getters = getters || {};
if (property) {
if (!getters.hasOwnProperty(property)) return;
return getters[property].call(this, property);
}
var copy = {};
for (var key in this) {
try {
if (getters.hasOwnProperty(key))
copy[key] = getters[key].call(this, key);
} catch (e) {
copy[key] = null;
console.error("Could not get key", key);
console.error(e.stack);
}
}
return copy;
}
// Get a copy of internal model data, including inherited properties
toJSON() {
var copy = {};
const __thisproto__ = Object.getPrototypeOf(this);
for (var classname in __thisproto__._gettersByProperty) {
var getters = __thisproto__._gettersByProperty[classname];
for (var key in this) {
try {
if (getters.hasOwnProperty(key)) {
copy[key] = getters[key].call(this, key);
const v = copy[key];
const type = v && v.constructor && v.constructor.name;
if (type === "Set") copy[key] = Array.from(v);
if (type === "Map") copy[key] = Array.from(v.values());
}
} catch (e) {
copy[key] = null;
console.error("Could not get key ", key);
console.error(e.stack);
}
}
if (classname === this.constructor.name) break;
}
return copy;
}
// Custom inspect output (console.log, util.format, util.inspect)
inspect() {
var copy = new (
// create fake object to preserve class name
new Function("return function " + this.constructor.name + "(){}")()
);
for (var key in this) { copy[key] = this[key]; }
return copy;
}
get _valid() {
try {
var allGetters = Object.getPrototypeOf(this)._gettersByProperty;
for (var classname in allGetters) {
if (allGetters[classname].hasOwnProperty("id"))
return allGetters[classname]["id"].call(this, "id");
}
} catch (e) {
return false;
}
}
valueOf() {
if (!this.id) return null;
return this.id;
}
equals(b) {
return this.valueOf() === b.valueOf();
}
/**
* Gets date and time this object was created at.
* @returns {Date}
* @readonly
*/
get createdAt() {
return new Date(Utils.timestampFromSnowflake(this.id));
}
}
module.exports = IBase;

75
node_modules/discordie/lib/interfaces/ICall.js generated vendored Normal file
View File

@@ -0,0 +1,75 @@
"use strict";
const Constants = require("../Constants");
const IBase = require("./IBase");
const Utils = require("../core/Utils");
const Call = require("../models/Call");
/**
* @interface
* @model Call
* @extends IBase
*/
class ICall extends IBase {
constructor(discordie, directMessageChannelId) {
super();
Utils.definePrivate(this, {
_discordie: discordie,
_directMessageChannelId: directMessageChannelId
});
Object.freeze(this);
}
/**
* Gets date and time this call was created at.
* @returns {Date}
* @readonly
*/
get createdAt() {
const call = this._discordie._calls.get(this._directMessageChannelId);
if (!call) return new Date(null);
return new Date(Utils.timestampFromSnowflake(call.message_id));
}
/**
* Checks if the call is ringing for current user.
* @return {boolean}
* @readonly
*/
get isRinging() {
const call = this._discordie._calls.get(this._directMessageChannelId);
if (!call) return false;
const userId = this._discordie._user && this._discordie._user.id;
if (!userId) return false;
return call.ringing ? call.ringing.indexOf(userId) >= 0 : false;
}
}
ICall._inherit(Call, function modelPropertyGetter(key) {
return this._discordie._calls.get(this._directMessageChannelId)[key];
});
/**
* @readonly
* @instance
* @memberOf ICall
* @name ringing
* @returns {Array<IUser>|null}
*/
ICall._setValueOverride("ringing", function(ringing) {
const users = [];
if (!ringing) return users;
for (let id of ringing) {
const user = this._discordie.Users.get(id);
if (user) users.push(user);
}
return users;
});
module.exports = ICall;

307
node_modules/discordie/lib/interfaces/IChannel.js generated vendored Normal file
View File

@@ -0,0 +1,307 @@
"use strict";
const Constants = require("../Constants");
const ChannelTypes = Constants.ChannelTypes;
const IBase = require("./IBase");
const Utils = require("../core/Utils");
const Channel = require("../models/Channel");
const IUser = require("./IUser");
const IRole = require("./IRole");
const IGuildMember = require("./IGuildMember");
const IAuthenticatedUser = require("./IAuthenticatedUser");
const IPermissions = require("./IPermissions");
const IPermissionOverwrite = require("./IPermissionOverwrite");
const rest = require("../networking/rest");
/**
* @interface
* @model Channel
* @extends IBase
* @description
* Base channel class.
*
* Use the `type` property to determine whether it is a
* `ITextChannel`, `IDirectMessageChannel` or `IVoiceChannel`:
*
* ```js
* Discordie.ChannelTypes: {
* GUILD_TEXT: 0, // ITextChannel
* DM: 1, // IDirectMessageChannel
* GUILD_VOICE: 2, // IVoiceChannel
* GROUP_DM: 3 // IDirectMessageChannel
* }
* ```
*/
class IChannel extends IBase {
constructor(discordie, channelId) {
super();
Utils.definePrivate(this, {
_discordie: discordie,
_channelId: channelId
});
Object.freeze(this);
}
/**
* **Deprecated**: Removed in API v6. Use `isPrivate` instead.
* @return {boolean|null}
* @readonly
*/
get is_private() {
return this.isPrivate;
}
/**
* Checks whether this channel is a direct message channel or a group.
* @return {boolean|null}
* @readonly
*/
get isPrivate() {
if (!this._valid) return null;
return this._discordie._channels._isPrivate(this);
}
/**
* Checks whether this channel is a voice channel in a guild.
* @return {boolean}
* @readonly
*/
get isGuildVoice() {
return this.type === ChannelTypes.GUILD_VOICE;
}
/**
* Checks whether this channel is a text channel in a guild.
* @return {boolean}
* @readonly
*/
get isGuildText() {
return this.type === ChannelTypes.GUILD_TEXT;
}
/**
* Checks whether this channel is a direct message channel (non-group).
* @return {boolean}
* @readonly
*/
get isDM() {
return this.type === ChannelTypes.DM;
}
/**
* Checks whether this channel is a group direct message channel.
* @return {boolean}
* @readonly
*/
get isGroupDM() {
return this.type === ChannelTypes.GROUP_DM;
}
/**
* Gets guild of this channel.
* @returns {IGuild|null}
* @readonly
*/
get guild() {
return this._discordie.Guilds.get(this.guild_id);
}
/**
* Makes a request to create an invite for this channel.
* @param {Object} options
* @returns {Promise<Object, Error>}
* @example
* channel.createInvite({
* max_age: 60 * 60 * 24, // value in seconds
* max_uses: 0, // pretty obvious
* temporary: false
* // temporary membership, kicks members without roles on disconnect
* });
* // Example response:
* {
* "max_age": 86400,
* "code": "AAAAAAAAAAAAAAAA",
* "guild": {
* "splash_hash": null,
* "id": "00000000000000000",
* "name": "test"
* },
* "revoked": false,
* "created_at": "2015-10-16T10:45:38.566978+00:00",
* "temporary": false,
* "uses": 0,
* "max_uses": 0,
* "inviter": {
* "username": "testuser",
* "discriminator": "3273",
* "bot": true,
* "id": "00000000000000000",
* "avatar": null
* },
* "channel": {
* "type": "text",
* "id": "000000000000000000",
* "name": "testchannel"
* }
* }
*/
createInvite(options) {
return this._discordie.Invites.create(this, options);
}
/**
* Makes a request to create a permission overwrite for this channel.
* @param {IAuthenticatedUser|IRole|IGuildMember} roleOrMember
* @param {IPermissions|Number} [allow]
* @param {IPermissions|Number} [deny]
* @returns {Promise<IPermissionOverwrite, Error>}
* @example
* channel.createPermissionOverwrite(this.User);
* channel.createPermissionOverwrite(
* channel.guild.members.find(m => m.username == "testuser")
* );
* channel.createPermissionOverwrite(
* channel.guild.roles.find(r => r.name == "new role")
* )
* .then(overwrite => console.log(overwrite))
* .catch(error => console.log(error));
*/
createPermissionOverwrite(roleOrMember, allow, deny) {
if (![IAuthenticatedUser, IRole, IGuildMember]
.some(t => roleOrMember instanceof t))
throw new TypeError(
"roleOrMember must be an instance of IRole or IGuildMember"
);
if (allow === undefined) allow = 0;
if (deny === undefined) deny = 0;
if (allow instanceof IPermissions) allow = allow.raw;
if (deny instanceof IPermissions) deny = deny.raw;
const types = [
{t: IRole, s: "role"},
{t: IGuildMember, s: "member"},
{t: IAuthenticatedUser, s: "member"}
];
const type = types.find(spec => roleOrMember instanceof spec.t).s;
return new Promise((rs, rj) => {
const raw = {
id: roleOrMember.valueOf(),
type: type,
allow: allow,
deny: deny
};
rest(this._discordie)
.channels.putPermissionOverwrite(this._channelId, raw)
.then(o =>
rs(new IPermissionOverwrite(this._discordie, o.id, this._channelId))
)
.catch(rj);
});
}
/**
* Makes a request to update this channel.
* @param {String} [name]
* @param {String} [topic]
* @param {Number} [bitrate] - Only for voice channels
* @param {Number} [userLimit] - Only for voice channels
* @returns {Promise<IChannel, Error>}
*/
update(name, topic, bitrate, userLimit) {
if (typeof name !== "string") name = this.name;
if (typeof topic !== "string") topic = this.topic;
if (typeof bitrate !== "number") bitrate = this.bitrate;
if (typeof userLimit !== "number") userLimit = this.user_limit;
return new Promise((rs, rj) => {
rest(this._discordie)
.channels.patchChannel(this.id,
name, topic, bitrate, userLimit, this.position
)
.then((channel) => rs(this._discordie.Channels.get(channel.id)))
.catch(rj);
});
}
/**
* Makes a request to create a new channel with permission overwrites of
* this channel.
* @param {String} name
* @param {Number} [type] - See IChannel / Discordie.ChannelTypes
* @param {Number} [bitrate] - Only for voice channels
* @param {Number} [userLimit] - Only for voice channels
*/
clone(name, type, bitrate, userLimit) {
if (!this._valid) return Promise.reject(new Error("Invalid channel"));
if (!this.guild) return Promise.reject(new Error("Invalid guild"));
name = name != null ? name : this.name;
type = type != null ? type : this.type;
if (type === ChannelTypes.GUILD_VOICE || type === "voice") {
if (bitrate == null) bitrate = this.bitrate;
if (userLimit == null) userLimit = this.user_limit;
}
const permissionOverwrites = this.permission_overwrites;
return this.guild
.createChannel(type, name, permissionOverwrites, bitrate, userLimit);
}
/**
* Moves this channel to `position` and makes a batch channel update request.
* @param {Number} position
* @returns {Promise}
*/
setPosition(position) {
const channels = (this.type == ChannelTypes.GUILD_VOICE) ?
this.guild.voiceChannels :
this.guild.textChannels;
const changes = Utils.reorderObjects(channels, this, position);
if (!changes) return Promise.resolve();
return rest(this._discordie)
.channels.batchPatchChannels(this.guild_id, changes);
}
/**
* Makes a request to delete this channel.
* @returns {Promise}
*/
delete() {
return rest(this._discordie).channels.deleteChannel(this.id);
}
/**
* Makes a request to get a list of invites for this channel.
* @returns {Promise<Array<Object>, Error>}
*/
getInvites() {
return rest(this._discordie).channels.getInvites(this.id);
}
}
IChannel._inherit(Channel, function modelPropertyGetter(key) {
return this._discordie._channels.get(this._channelId)[key];
});
/**
* @readonly
* @instance
* @memberOf IChannel
* @name permission_overwrites
* @returns {Array<IPermissionOverwrite>}
*/
IChannel._setValueOverride("permission_overwrites", function(overwritesRaw) {
const overwrites = [];
if (!overwritesRaw) return overwrites;
for (let overwrite of overwritesRaw) {
overwrites.push(new IPermissionOverwrite(
this._discordie, overwrite.id, this._channelId
));
}
return overwrites;
});
module.exports = IChannel;

View File

@@ -0,0 +1,65 @@
"use strict";
const ICollectionBase = require("./ICollectionBase");
const IChannel = require("./IChannel");
const ITextChannel = require("./ITextChannel");
const IVoiceChannel = require("./IVoiceChannel");
const IGuild = require("./IGuild");
const Utils = require("../core/Utils");
const Constants = require("../Constants");
const ChannelTypes = Constants.ChannelTypes;
/**
* @interface
* @extends ICollectionBase
*/
class IChannelCollection extends ICollectionBase {
constructor(discordie, valuesGetter, valueGetter) {
super({
valuesGetter: valuesGetter,
valueGetter: valueGetter,
itemFactory: (id) => {
const type = this._discordie._channels.getChannelType(id);
if (type && type === ChannelTypes.GUILD_VOICE) {
return new IVoiceChannel(this._discordie, id);
}
return new ITextChannel(this._discordie, id);
}
});
Utils.definePrivate(this, {_discordie: discordie});
}
/**
* Creates an array of `IChannel` (`ITextChannel` and `IVoiceChannel`)
* for `guild`.
* @param {IGuild|String} guild
* @returns {Array<IChannel>}
*/
forGuild(guild) {
return this.filter(channel => channel.guild_id == guild.valueOf());
}
/**
* Creates an array of `ITextChannel` for `guild`.
* @param {IGuild|String} guild
* @returns {Array<ITextChannel>}
*/
textForGuild(guild) {
return this.filter(channel =>
channel.guild_id == guild && channel.type == ChannelTypes.GUILD_TEXT
);
}
/**
* Creates an array of `IVoiceChannel` for `guild`.
* @param {IGuild|String} guild
* @returns {Array<IVoiceChannel>}
*/
voiceForGuild(guild) {
return this.filter(channel =>
channel.guild_id == guild && channel.type == ChannelTypes.GUILD_VOICE
);
}
}
module.exports = IChannelCollection;

View File

@@ -0,0 +1,192 @@
"use strict";
const Utils = require("../core/Utils");
class ICollectionBase {
constructor(descriptor) {
if (!descriptor.valuesGetter)
throw new Error("valuesGetter is not defined");
if (!descriptor.itemFactory)
throw new Error("itemFactory is not defined");
this._valuesGetter = descriptor.valuesGetter;
this._valueGetter = descriptor.valueGetter;
this._itemFactory = descriptor.itemFactory;
this._cache = new WeakMap();
Utils.privatify(this);
}
_getOrCreateInterface(item, customItemFactory) {
var factory = customItemFactory || this._itemFactory;
var cache = this._cache;
if (!cache.get(item))
cache.set(item, factory(item.id));
return cache.get(item);
}
/**
* Returns an an element, if `key` of an element in the collection
* with exact `value` can be found.
* Otherwise null is returned.
* @param key
* @param value
* @returns {*}
*/
getBy(key, value) {
if (key === "id" && this._valueGetter) {
var item = this._valueGetter(value);
if (!item) return null;
return this._getOrCreateInterface(item);
}
for (var item of this._valuesGetter()) {
if (item[key] != value) continue;
return this._getOrCreateInterface(item);
}
return null;
}
/**
* Returns an element with requested `id`, if exists in the collection.
* Otherwise null is returned.
* @param {String} id
* @returns {*}
*/
get(id) { return this.getBy("id", id); }
*[Symbol.iterator]() {
for (var item of this._valuesGetter()) {
yield this._getOrCreateInterface(item);
}
}
_getRawItemBy(key, value) {
for (var item of this._valuesGetter()) {
if (item[key] != value) continue;
return item;
}
}
*_getRawIterator() {
for (var item of this._valuesGetter()) {
yield item;
}
}
_getRaw(id) { return this._getRawItemBy("id", id); }
/**
* Creates a new array with all elements that pass the test implemented
* by the provided function.
* @param {Function} fn - Function with signature fn(item)
* @returns {Array}
*/
filter(condition) {
if (typeof condition !== "function") {
throw new TypeError("condition is not a function");
}
const items = [];
for (var item of this) {
if (condition(item))
items.push(item);
}
return items;
}
concat(collection) {
if (collection == null) {
throw new TypeError("collection is null or not defined");
}
if (typeof collection.filter !== "function") {
throw new TypeError();
}
collection = collection.filter(() => true);
return this.filter(() => true).concat(collection);
}
/**
* Returns a value in the collection, if an element in the collection
* satisfies the provided testing function. Otherwise null is returned.
* @param {Function} fn - Function with signature fn(item)
* @returns {Object|null}
*/
find(condition) {
if (typeof condition !== "function") {
throw new TypeError("condition is not a function");
}
for (var item of this) {
if (condition(item))
return item;
}
return null;
}
/**
* Executes a provided function once per element.
* @param {Function} fn - Function with signature fn(item)
*/
forEach(fn) {
if (typeof fn !== "function") {
throw new TypeError("fn is not a function");
}
for (var item of this) {
fn(item);
}
}
/**
* Creates a new array with the results of calling a provided function on
* every element in this collection.
* @param {Function} fn - Function with signature fn(item)
* @returns {Array}
*/
map(fn) {
if (typeof fn !== "function") {
throw new TypeError("fn is not a function");
}
const items = [];
for (var item of this) {
items.push(fn(item));
}
return items;
}
inspect() {
var copy = new (
// create fake object to preserve class name
new Function("return function " + this.constructor.name + "(){}")()
);
copy.length = this.length;
return copy;
}
/**
* Creates a new array with elements of this collection.
* @returns {Array}
*/
toArray() {
const array = [];
for (var item of this) {
array.push(item);
}
return array;
}
toJSON() { return this.toArray(); }
/**
* Number of elements in this collection.
* @returns {Number}
* @readonly
*/
get length() {
var i = 0;
for (var item of this._valuesGetter()) i++;
return i;
}
/**
* Number of elements in this collection. Alias for `.length`.
* @returns {Number}
* @readonly
*/
get size() { return this.length; }
}
module.exports = ICollectionBase;

View File

@@ -0,0 +1,589 @@
"use strict";
const Constants = require("../Constants");
const Endpoints = Constants.Endpoints;
const ChannelTypes = Constants.ChannelTypes;
const IBase = require("./IBase");
const ITextChannel = require("./ITextChannel");
const ICall = require("./ICall");
const Utils = require("../core/Utils");
const Channel = require("../models/Channel");
const rest = require("../networking/rest");
/**
* @interface
* @model Channel
* @extends IBase
*/
class IDirectMessageChannel extends IBase {
constructor(discordie, directMessageChannelId) {
super();
Utils.definePrivate(this, {
_discordie: discordie,
_directMessageChannelId: directMessageChannelId,
_call: new ICall(discordie, directMessageChannelId)
});
Object.freeze(this);
}
/**
* **Deprecated**: Removed in API v6. Use `isPrivate` instead.
* @return {boolean|null}
* @readonly
*/
get is_private() {
return this.isPrivate;
}
/**
* Checks whether this channel is a direct message channel or a group.
* @return {boolean|null}
* @readonly
*/
get isPrivate() {
if (!this._valid) return null;
return this._discordie._channels._isPrivate(this);
}
/**
* Checks whether this channel is a voice channel in a guild.
* @return {boolean}
* @readonly
*/
get isGuildVoice() {
return this.type === ChannelTypes.GUILD_VOICE;
}
/**
* Checks whether this channel is a text channel in a guild.
* @return {boolean}
* @readonly
*/
get isGuildText() {
return this.type === ChannelTypes.GUILD_TEXT;
}
/**
* Checks whether this channel is a direct message channel (non-group).
* @return {boolean}
* @readonly
*/
get isDM() {
return this.type === ChannelTypes.DM;
}
/**
* Checks whether this channel is a group direct message channel.
* @return {boolean}
* @readonly
*/
get isGroupDM() {
return this.type === ChannelTypes.GROUP_DM;
}
/**
* Returns the owner of the private channel.
*
* Returns null if the owner user is not in cache or there is no owner.
* @returns {IAuthenticatedUser|IUser|null}
* @readonly
*/
get owner() {
if (!this.owner_id) return null;
const owner = this._discordie.Users.get(this.owner_id);
if (!owner) return null;
if (this._discordie.User.equals(owner))
return this._discordie.User;
return owner;
}
/**
* Checks whether the `user` is the owner of the private channel.
* @param {IGuildMember|IUser|IAuthenticatedUser|String} user
* @returns {boolean}
*/
isOwner(user) {
if (!user) return false;
if (!this.owner_id) return false;
return this.owner_id === user.valueOf();
}
/**
* Creates a string URL of image icon of this channel.
* @returns {String|null}
* @readonly
*/
get iconURL() {
if (!this.icon) return null;
return Constants.CDN_ENDPOINT + Endpoints.CDN_DM_ICON(this.id, this.icon);
}
/**
* Gets first recipient of this channel.
*
* Returns null if this channel is invalid or has no recipients.
*
* **Deprecated**: Use `recipients` instead.
* @returns {IUser|null}
* @readonly
*/
get recipient() {
if (!this._valid) return null;
return this.recipients[0] || null;
}
/**
* Gets a value indicating whether all messages were loaded.
* @returns {boolean}
* @readonly
*/
get allMessagesLoaded() {
return !this._discordie._messages.channelHasMore(this.id);
}
/**
* Creates an array of cached messages in this channel, sorted in order of
* arrival (message cache is sorted on message insertion, not when this
* getter is invoked).
*
* Returns an empty array if channel no longer exists.
* @returns {Array<IMessage>}
* @readonly
*/
get messages() {
return this._discordie.Messages.forChannel(this.id);
}
/**
* Makes a request to fetch messages for this channel.
*
* Discord API does not allow fetching more than 100 messages at once.
*
* Promise resolves with an Object with following structure:
* ```js
* {
* messages: Array<IMessage>,
* limit: Number, // same as parameter passed or default value
* before: String | null, // message id
* after: String | null // message id
* }
* ```
* @param {Number|null} [limit] - Default is 100
* @param {IMessage|String|null} [before] - Message or message id
* @param {IMessage|String|null} [after] - Message or message id
* @returns {Promise<Object, Error>}
* @example
* var guild = client.Guilds.find(g => g.name == "test");
* var channel = guild.generalChannel;
*
* // simple fetch:
* channel.fetchMessages().then(() => {
* console.log("[simple] messages in cache: " + channel.messages.length);
* });
*
* // fetching more than 100 messages into cache sequentially:
* fetchMessagesEx(channel, 420).then(() => {
* console.log("[extended] messages in cache: " + channel.messages.length);
* });
*
* // fetch more messages just like Discord client does
* function fetchMessagesEx(channel, left) {
* // message cache is sorted on insertion
* // channel.messages[0] will get oldest message
* var before = channel.messages[0];
* return channel.fetchMessages(Math.min(left, 100), before)
* .then(e => onFetch(e, channel, left));
* }
* function onFetch(e, channel, left) {
* if (!e.messages.length) return Promise.resolve();
* left -= e.messages.length;
* console.log(`Received ${e.messages.length}, left: ${left}`);
* if (left <= 0) return Promise.resolve();
* return fetchMessagesEx(channel, left);
* }
*/
fetchMessages(limit, before, after) {
return ITextChannel.prototype.fetchMessages.apply(this, arguments);
}
/**
* Creates an array of cached pinned messages in this channel.
*
* Pinned message cache is updated only if all pinned messages have been
* loaded with `IDirectMessageChannel.fetchPinned()`.
*
* Returns an empty array if channel no longer exists or if pinned messages
* have not been fetched yet.
* @returns {Array<IMessage>}
* @readonly
*/
get pinnedMessages() {
return this._discordie.Messages.forChannelPinned(this.id);
}
/**
* Makes a request to fetch pinned messages for this channel.
*
* Promise resolves with an Object with following structure:
* ```js
* {
* channelId: String,
* messages: Array<IMessage>
* }
* ```
* @returns {Promise<Object, Error>}
*/
fetchPinned() {
return new Promise((rs, rj) => {
rest(this._discordie).channels.getPinnedMessages(this.id)
.then(e => {
e.messages = e.messages
.map(msg => this._discordie.Messages.get(msg.id));
return rs(e);
})
.catch(rj);
});
}
/**
* Makes a request to send a message to this channel. Messages over 2000
* characters will be rejected by the server.
*
* Use `uploadFile` if you want to send a message with an attachment.
* @param {String|Array<String>} content
* Strings will be sent as is, arrays - joined with a newline character.
* @param {IUser|IGuildMember|Array<IUser>|Array<IGuildMember>} [mentions]
* Deprecated: left for backward compatibility.
* @param {boolean} [tts]
* @param {Object} [embed]
* Refer to [official API documentation](https://discordapp.com/developers/docs/resources/channel#embed-object)
* for embed structure description.
* @returns {Promise<IMessage, Error>}
* @example
* var guild = client.Guilds.find(g => g.name == "test");
* if (!guild) return console.log("invalid guild");
*
* var channel = guild.generalChannel;
*
* channel.sendMessage("regular message");
* channel.sendMessage("test with tts", true);
*
* var user = client.Users.find(u => u.username == "test");
* if (!user) return console.log("invalid user");
*
* channel.sendMessage("mentioning user " + user.mention);
* channel.sendMessage("@everyone or @here mention if you have permissions");
*
* channel.sendMessage("message with an embed", false, {
* color: 0x3498db,
* author: {name: "author name"},
* title: "This is an embed",
* url: "http://google.com",
* timestamp: "2016-11-13T03:43:32.127Z",
* fields: [{name: "some field", value: "some value"}],
* footer: {text: "footer text"}
* });
*/
sendMessage(content, mentions, tts, embed) {
return ITextChannel.prototype.sendMessage.apply(this, arguments);
}
/**
* Makes a request to upload data to this channel.
* Images require a `filename` with a valid extension to actually be uploaded.
* @param {Buffer|ReadableStream|String} readableStream
* Data to upload or filename as a string
* @param {String} filename
* Actual filename to show, required for non-string `readableStream`
* @param {String} [content] - Additional comment message for attachment
* @param {boolean} [tts]
* @returns {Promise<IMessage, Error>}
* @example
* channel.uploadFile(fs.readFileSync("test.png"), "test.png"); // Buffer
* channel.uploadFile(fs.createReadStream("test.png"), "test.png"); // Stream
* channel.uploadFile("test.png"); // File
* channel.uploadFile("test.png", null, "file with message");
* channel.uploadFile("test.png", null, "file with message and tts", true);
*/
uploadFile(readableStream, filename, content, mentions, tts) {
return ITextChannel.prototype.uploadFile.apply(this, arguments);
}
/**
* Makes a request to send typing status for this channel.
*
* Discord client displays it for 10 seconds, sends every 5 seconds.
* Stops showing typing status if receives a message from the user.
* @returns {Promise}
*/
sendTyping() {
return ITextChannel.prototype.sendTyping.apply(this, arguments);
}
/**
* Makes a request to close this channel (direct message channels only).
* @returns {Promise}
*/
close() {
return rest(this._discordie).channels.deleteChannel(this.id);
}
/**
* Makes a request to ring specified recipients.
* Has no effect if call has not started yet.
*
* Bot accounts cannot use this endpoint.
* @param {Array<IUser|String>} [recipients]
* @return {Promise}
*/
ring(recipients) {
if (recipients && !Array.isArray(recipients))
throw new TypeError("Param 'recipients' is not an array");
if (recipients) recipients = recipients.map(u => u.valueOf());
return rest(this._discordie).channels.calls
.ring(this.id, recipients);
}
/**
* Makes a request to decline an incoming call (if no arguments passed) or
* stop ringing for specified recipients.
*
* Bot accounts cannot use this endpoint.
* @param {Array<IUser|String>} [recipients]
* @return {Promise}
*/
stopRinging(recipients) {
if (recipients && !Array.isArray(recipients))
throw new TypeError("Param 'recipients' is not an array");
if (recipients) recipients = recipients.map(u => u.valueOf());
return rest(this._discordie).channels.calls
.stopRinging(this.id, recipients);
}
/**
* Makes a request to change the server region hosting the call.
*
* Bot accounts cannot use this endpoint.
* @param {String} region
* @return {Promise}
*/
changeCallRegion(region) {
return rest(this._discordie).channels.calls.changeRegion(this.id, region);
}
/**
* Makes a request to add a user to this private channel.
*
* Bot accounts cannot use this endpoint.
* @param {IUser|IGuildMember} user
* @return {Promise}
*/
addRecipient(user) {
user = user.valueOf();
return rest(this._discordie).channels.dm.addRecipient(this.id, user);
}
/**
* Makes a request to remove a user from this private channel.
*
* Bot accounts cannot use this endpoint.
* @param {IUser|IGuildMember} user
* @return {Promise}
*/
removeRecipient(user) {
user = user.valueOf();
return rest(this._discordie).channels.dm.removeRecipient(this.id, user);
}
/**
* Makes a request to set a name for this private channel.
* @param {String} name
* @return {Promise<IDirectMessageChannel, Error>}
*/
setName(name) {
return new Promise((rs, rj) => {
rest(this._discordie).channels.dm.setName(this.id, name)
.then(() => rs(this))
.catch(rj);
});
}
/**
* Makes a request to set an icon for this private channel.
* @param {String|Buffer|null} icon
* @return {Promise<IDirectMessageChannel, Error>}
*/
setIcon(icon) {
if (icon instanceof Buffer) {
icon = Utils.imageToDataURL(icon);
} else if (icon === undefined) {
icon = this.icon;
}
return new Promise((rs, rj) => {
rest(this._discordie).channels.dm.setIcon(this.id, icon)
.then(() => rs(this))
.catch(rj);
});
}
/**
* Creates or joins a call.
* Only one call can be connected at the same time.
*
* Joining calls with bot accounts is not supported.
* @param {boolean} [selfMute]
* @param {boolean} [selfDeaf]
* @returns {Promise<VoiceConnectionInfo, Error|Number>}
*/
joinCall(selfMute, selfDeaf) {
selfMute = !!selfMute;
selfDeaf = !!selfDeaf;
if (!this._valid)
return Promise.reject(new Error("Channel does not exist"));
const call = this._discordie._calls.get(this._directMessageChannelId);
if (call && call.unavailable)
return Promise.reject(new Error("Call is unavailable"));
if (this._discordie._user && this._discordie._user.bot)
throw new Error("Joining calls with bot accounts is not supported");
const vc = this._discordie.VoiceConnections;
return vc._getOrCreate(null, this.id, selfMute, selfDeaf);
}
/**
* Leaves call if joined.
*/
leaveCall() {
const info = this.getVoiceConnectionInfo();
if (info) return info.voiceConnection.disconnect();
this._discordie.VoiceConnections
.cancelIfPending(null, this._directMessageChannelId);
}
/**
* Retrieves `VoiceConnectionInfo` for the call of this channel.
* @returns {VoiceConnectionInfo|null}
*/
getVoiceConnectionInfo() {
return this._discordie.VoiceConnections
.getForChannel(this._directMessageChannelId);
}
/**
* Checks whether current user is in the call.
* @returns {boolean}
* @readonly
*/
get joinedCall() {
const vc = this._discordie.VoiceConnections;
const pendingChannel = vc.getPendingChannel(null);
const channelId = this._directMessageChannelId;
return !!(pendingChannel && pendingChannel === channelId);
}
/**
* Creates an array of users in the call.
*
* Returns null if call does not exist in cache or has not started yet.
* @returns {Array<IUser>|null}
* @readonly
*/
get usersInCall() {
const call = this._discordie._calls.get(this._directMessageChannelId);
return this._discordie.Users.usersInCall(this);
}
/**
* Gets call from cache.
*
* Returns null if call does not exist in cache or has not started yet.
* @returns {ICall|null}
* @readonly
*/
get call() {
const call = this._discordie._calls.get(this._directMessageChannelId);
return call ? this._call : null;
}
/**
* Fetches call info through gateway socket.
*
* Currently there are no ways to fetch call info for all channels at once.
* @return {Promise<ICall|null, Error>}
*/
fetchCall() {
const gateway = this._discordie.gatewaySocket;
if (!gateway || !gateway.connected)
return Promise.reject(new Error("No gateway socket (not connected)"));
return new Promise((rs, rj) => {
const call = this._discordie._calls.get(this._directMessageChannelId);
if (call) return rs(this._call);
gateway.callConnect(this._directMessageChannelId);
setTimeout(() => {
const call = this._discordie._calls.get(this._directMessageChannelId);
rs(call ? this._call : null);
}, 1000);
});
}
}
IDirectMessageChannel._inherit(Channel, function modelPropertyGetter(key) {
return this._discordie._channels.get(this._directMessageChannelId)[key];
});
/**
* @readonly
* @instance
* @memberOf IDirectMessageChannel
* @name name
* @returns {String}
*/
IDirectMessageChannel._setValueOverride("name", function(name) {
const UNNAMED = "Unnamed";
if (!this._valid) return UNNAMED;
const type = this.type;
if (type === ChannelTypes.DM) {
const recipient = this.recipients[0];
return recipient ? recipient.username : name;
}
if (type === ChannelTypes.GROUP_DM) {
return name || this.recipients.map(u => u.username).join(", ") || UNNAMED;
}
return name;
});
/**
* Gets recipients of this channel.
* @readonly
* @instance
* @memberOf IDirectMessageChannel
* @name recipients
* @returns {Array<IUser>|null}
*/
IDirectMessageChannel._setValueOverride("recipients", function(recipients) {
const users = [];
if (!recipients) return users;
for (let id of recipients.values()) {
const user = this._discordie.Users.get(id);
if (user) users.push(user);
}
return users;
});
module.exports = IDirectMessageChannel;

View File

@@ -0,0 +1,72 @@
"use strict";
const ICollectionBase = require("./ICollectionBase");
const IDirectMessageChannel = require("./IDirectMessageChannel");
const Utils = require("../core/Utils");
const Constants = require("../Constants");
const ChannelTypes = Constants.ChannelTypes;
const rest = require("../networking/rest");
/**
* @interface
* @extends ICollectionBase
*/
class IDirectMessageChannelCollection extends ICollectionBase {
constructor(discordie, valuesGetter, valueGetter) {
super({
valuesGetter: valuesGetter,
valueGetter: valueGetter,
itemFactory: (id) => new IDirectMessageChannel(this._discordie, id)
});
this._discordie = discordie;
Utils.privatify(this);
}
/**
* Gets a DM channel from cache or makes a request to create one.
* @param {IUser|IGuildMember|String} recipient
* @returns {Promise<IDirectMessageChannel, Error>}
*/
getOrOpen(recipient) {
const existing = this.find(c =>
c.type === ChannelTypes.DM &&
c.recipients.length === 1 &&
c.recipients[0].equals(recipient)
);
if (existing)
return Promise.resolve(existing);
return this.open(recipient);
}
/**
* Makes a request to create a DM channel.
* @param {IUser|IGuildMember|String} recipient
* @returns {Promise<IDirectMessageChannel, Error>}
*/
open(recipient) {
recipient = recipient.valueOf();
return this.createGroupDM([recipient]);
}
/**
* Makes a request to create a group DM channel.
*
* Bot accounts cannot use this endpoint.
* @param {Array<IUser|IGuildMember|String>} [recipients]
* @returns {Promise<IDirectMessageChannel, Error>}
*/
createGroupDM(recipients) {
recipients = recipients || [];
recipients = recipients.filter(u => u).map(u => u.valueOf());
const userId = this._discordie.User.id;
return new Promise((rs, rj) => {
rest(this._discordie)
.users.createDirectMessageChannel(userId, recipients)
.then(c => rs(this._discordie.DirectMessageChannels.get(c.id)))
.catch(rj);
});
}
}
module.exports = IDirectMessageChannelCollection;

542
node_modules/discordie/lib/interfaces/IGuild.js generated vendored Normal file
View File

@@ -0,0 +1,542 @@
"use strict";
const Constants = require("../Constants");
const Endpoints = Constants.Endpoints;
const IBase = require("./IBase");
const IChannel = require("./IChannel");
const IGuildMember = require("./IGuildMember");
const IUser = require("./IUser");
const IRole = require("./IRole");
const Utils = require("../core/Utils");
const Guild = require("../models/Guild");
const rest = require("../networking/rest");
/**
* @interface
* @model Guild
* @extends IBase
*/
class IGuild extends IBase {
constructor(discordie, guildId) {
super();
Utils.definePrivate(this, {
_discordie: discordie,
_guildId: guildId
});
Object.freeze(this);
}
/**
* Creates an acronym string for this guild.
* (Text that shows up as guild icon in the client if there is no image icon.)
* @returns {String}
* @readonly
*/
get acronym() {
if (!this.name) return "";
return this.name.replace(/\w+/g, match => match[0]).replace(/\s/g, "");
}
/**
* Creates a string URL of image icon of this guild.
* @returns {String|null}
* @readonly
*/
get iconURL() {
if (!this.icon) return null;
return Constants.CDN_ENDPOINT +
Endpoints.CDN_GUILD_ICON(this.id, this.icon);
}
/**
* Creates a string URL of invite splash image of this guild.
* @returns {String|null}
* @readonly
*/
get splashURL() {
if (!this.splash) return null;
return Constants.CDN_ENDPOINT +
Endpoints.CDN_GUILD_SPLASH(this.id, this.splash);
}
/**
* Checks whether the `user` is the owner of this guild.
* @param {IGuildMember|IUser|IAuthenticatedUser|String} user
* @returns {boolean}
*/
isOwner(user) {
if (!user) return false;
if (!this.owner_id) return false;
return this.owner_id === user.valueOf();
}
/**
* Returns afk channel of this guild.
* @returns {IChannel|null}
* @readonly
*/
get afk_channel() {
if (!this.afk_channel_id) return null;
const afk_channel = this._discordie.Channels.get(this.afk_channel_id);
return afk_channel ? afk_channel : null;
}
/**
* Returns the owner of this guild.
*
* Returns null if the owner user is not in cache.
*
* See `.isOwner(user)` if you want to safely check if the `user` is owner.
* @returns {IAuthenticatedUser|IUser|null}
* @readonly
*/
get owner() {
if (!this.owner_id) return null;
const owner = this._discordie.Users.get(this.owner_id);
if (!owner) return null;
if (this._discordie.User.equals(owner))
return this._discordie.User;
return owner;
}
/**
* Creates an array of text and voice channels of this guild.
* @returns {Array<IChannel>}
* @readonly
*/
get channels() {
return this._discordie.Channels.forGuild(this.id);
}
/**
* Creates an array of text channels of this guild.
* @returns {Array<ITextChannel>}
* @readonly
*/
get textChannels() {
return this._discordie.Channels.textForGuild(this.id);
}
/**
* Creates an array of voice channels of this guild.
* @returns {Array<IVoiceChannel>}
* @readonly
*/
get voiceChannels() {
return this._discordie.Channels.voiceForGuild(this.id);
}
/**
* Returns general channel of this guild.
* @returns {ITextChannel}
* @readonly
*/
get generalChannel() {
return this._discordie.Channels.get(this.id);
}
/**
* Makes a request to edit this guild,
* substituting `undefined` or `null` properties with current values.
*
* Passing `null` in `icon` or `afkChannelId` will remove current
* icon/channel. Use `undefined` instead of `null` in this case.
* @param {String} [name]
* @param {String|Buffer|null} [icon]
* @param {String} [region]
* @param {IChannel|String|null} [afkChannelId] - Channel or an id string
* @param {Number} [afkTimeout] - 60, 300, 900, 1800 or 3600 seconds
* @param {Number} [verificationLevel]
* See Discordie.VerificationLevel
* @param {Number} [defaultMessageNotifications]
* See Discordie.UserNotificationSettings
* @returns {Promise<IGuild, Error>}
*/
edit(name, icon, region, afkChannelId, afkTimeout, verificationLevel,
defaultMessageNotifications) {
const guild = this._discordie._guilds.get(this._guildId);
name = name || guild.name;
region = region || guild.region;
afkTimeout = afkTimeout || guild.afk_timeout;
const opt = (value, _) => value != null ? value : _;
verificationLevel =
opt(verificationLevel, guild.verification_level);
defaultMessageNotifications =
opt(defaultMessageNotifications, guild.default_message_notifications);
if (icon instanceof Buffer) {
icon = Utils.imageToDataURL(icon);
} else if (icon === undefined) {
icon = guild.icon;
}
if (afkChannelId === undefined) {
afkChannelId = guild.afk_channel_id;
} else if (afkChannelId != null) {
afkChannelId = afkChannelId.valueOf();
}
return new Promise((rs, rj) => {
rest(this._discordie)
.guilds.patchGuild(
guild.id,
name, icon, region,
afkChannelId, afkTimeout,
verificationLevel,
defaultMessageNotifications
)
.then(() => rs(this))
.catch(rj);
});
}
/**
* Makes a request to create a channel in this guild.
* @param {Number} type - See IChannel / Discordie.ChannelTypes
* @param {String} name
* @param {Array<IPermissionOverwrite>} [permissionOverwrites]
* @param {Number} [bitrate] - Only for voice channels
* @param {Number} [userLimit] - Only for voice channels
* @returns {Promise<ITextChannel|IVoiceChannel, Error>}
*/
createChannel(type, name, permissionOverwrites, bitrate, userLimit) {
permissionOverwrites = permissionOverwrites || [];
if (!Array.isArray(permissionOverwrites))
throw TypeError("Param 'permissionOverwrites' must be an array");
permissionOverwrites = permissionOverwrites.map(v => {
return typeof v.getRaw === "function" ? v.getRaw() : null;
}).filter(v => v);
return new Promise((rs, rj) => {
rest(this._discordie)
.channels.createChannel(this.id,
type, name, permissionOverwrites, bitrate, userLimit
)
.then(channel => rs(this._discordie.Channels.get(channel.id)))
.catch(rj);
});
}
/**
* Makes a request to create a role in this guild.
* @returns {Promise<IRole, Error>}
*/
createRole() {
return new Promise((rs, rj) => {
rest(this._discordie).guilds.roles.createRole(this.id)
.then(role => rs(new IRole(this._discordie, role.id, this.id)))
.catch(rj);
});
}
/**
* Makes a request to create an invite for general channel in this guild.
*
* See `IChannel.createInvite` for more info.
* @see IChannel.createInvite
* @param {Object} options
* @returns {Promise<Object, Error>}
*/
createInvite(options) {
return this._discordie.Invites.create(this.generalChannel, options);
}
/**
* Creates an array containing members of this guild.
* @returns {Array<IGuildMember>}
* @readonly
*/
get members() {
return this._discordie.Users.membersForGuild(this.id);
}
/**
* Makes a request to delete this guild.
*
* Returns a rejected promise if the user **is not** owner.
* @returns {Promise}
*/
delete() {
if (!this.owner.equals(this._discordie.User))
return Promise.reject();
return rest(this._discordie).guilds.deleteGuild(this.id);
}
/**
* Makes a request to delete this guild.
*
* Returns a rejected promise if the user **is** owner.
* @returns {Promise}
*/
leave() {
if (this.owner.equals(this._discordie.User))
return Promise.reject();
return rest(this._discordie).guilds.leaveGuild(this.id);
}
/**
* Makes a request to ban a user (does not have to be a member of the guild).
*
* Additionally delete `deleteMessageForDays` number of days worth of their
* messages from all channels of the guild.
* @param {IUser|String} user - User or an id string
* @param deleteMessageForDays - Number of days worth of messages to delete
* (min 0, max 7)
* @returns {Promise}
*/
ban(user, deleteMessageForDays) {
user = user.valueOf();
return rest(this._discordie)
.guilds.bans.banMember(this.id, user, deleteMessageForDays);
}
/**
* Makes a request to unban a user.
* @param {IUser|String} user - User or an id string
* @returns {Promise}
*/
unban(user) {
user = user.valueOf();
return rest(this._discordie).guilds.bans.unbanMember(this.id, user);
}
/**
* Makes a request to get ban list of this guild.
* @returns {Promise<Array<IUser>, Error>}
*/
getBans() {
return new Promise((rs, rj) => {
rest(this._discordie).guilds.bans.getBans(this.id)
.then(bans => {
const bannedUsers = bans.map(ban => {
return new IUser(this._discordie, ban.user.id);
});
return rs(bannedUsers);
})
.catch(rj);
});
}
/**
* Makes a request to get estimate of members affected by prune request.
*
* Promise resolves with an Object with following structure:
* ```js
* {
* guildId: String,
* days: Number, // same as parameter passed
* estimate: Number
* }
* ```
* @param {Number} days - Number of days from 1 to 7, default 1
* @returns {Promise<Object, Error>}
*/
getPruneEstimate(days) {
return rest(this._discordie).guilds.prune.getPrune(this.id, days);
}
/**
* Makes a request to prune members.
*
* Promise resolves with an Object with following structure:
* ```js
* {
* guildId: String,
* days: Number, // same as parameter passed
* pruned: Number
* }
* ```
* @param {Number} days - Number of days from 1 to 7, default 1
* @returns {Promise<Object, Error>}
*/
pruneMembers(days) {
// make it also accept object from .getPruneEstimate(days)
if (days && days.hasOwnProperty("days")) {
days = days.days;
}
return rest(this._discordie).guilds.prune.postPrune(this.id, days);
}
/**
* Makes a request to get a list of invites of this guild.
* @returns {Promise<Array<Object>, Error>}
*/
getInvites() {
return rest(this._discordie).guilds.getInvites(this.id);
}
/**
* Makes a request to get a list of voice regions available for this guild.
* @returns {Promise<Array<Object>, Error>}
*/
fetchRegions() {
return rest(this._discordie).voice.getRegions(this.id);
}
/**
* Makes a request to transfer ownership of this guild to `user`.
* @param {IGuildMember|IUser|String} user - User or an id string
* @returns {Promise<IGuild, Error>}
*/
transferOwnership(user) {
return new Promise((rs, rj) => {
rest(this._discordie)
.guilds.transferOwnership(this.id, user.valueOf())
.then(() => rs(this))
.catch(rj);
});
}
/**
* Makes a request to get widget (external embed) settings.
*
* Promise resolves with an Object with following structure:
* ```js
* {
* enabled: boolean,
* channel_id: String|null
* }
* ```
* @return {Promise<Object, Error>}
*/
getWidget() {
return rest(this._discordie).guilds.getEmbed(this.id);
}
/**
* Makes a request to set widget (external embed) settings.
* @param {Object} options
* Object `{enabled: boolean, channelId or channel_id: String}`.
* Both options are optional.
* @return {Promise<Object, Error>}
*/
editWidget(options) {
return rest(this._discordie).guilds.patchEmbed(this.id, options || {});
}
/**
* Makes a request to fetch emojis for this guild.
*
* Only user and whitelisted bot accounts can use this endpoint.
*
* Promise resolves with an array of Objects with following structure:
* ```js
* {
* "managed": false,
* "name": 'abc',
* "roles": [],
* "user": {
* "username": "testuser",
* "discriminator": "3273",
* "id": "000000000000000000",
* "avatar": null
* },
* "require_colons": true,
* "id": "000000000000000000"
* }
* ```
* @return {Promise<Array<Object>, Error>}
*/
fetchEmoji() {
return rest(this._discordie).guilds.emoji.getEmoji(this.id);
}
/**
* Makes a request to create an emoji.
*
* Returned object does not contain `user` property.
*
* Only user and whitelisted bot accounts can use this endpoint.
* @param {Buffer|String} image - Buffer or base64 image string
* @param {String} name
* @return {Promise<Object, Error>}
*/
uploadEmoji(image, name) {
if (image instanceof Buffer) {
image = Utils.imageToDataURL(image);
}
return rest(this._discordie).guilds.emoji.postEmoji(this.id, image, name);
}
/**
* Makes a request to delete the specified emoji.
*
* Only user and whitelisted bot accounts can use this endpoint.
* @param {Object|String} emoji - Emoji object or an id string
* @return {Promise<Object, Error>}
*/
deleteEmoji(emoji) {
if (emoji && emoji.id) emoji = emoji.id;
return rest(this._discordie).guilds.emoji.deleteEmoji(this.id, emoji);
}
/**
* Makes a request to edit the specified emoji.
*
* Returned object does not contain `user` property.
*
* Only user and whitelisted bot accounts can use this endpoint.
* @param {Object|String} emoji - Emoji object or an id string
* @param {Object} options
* Optional properties to edit:
* `{name: String, roles: Array<IRole>|Array<String>}`
* @return {Promise<Object, Error>}
*/
editEmoji(emoji, options) {
if (emoji && emoji.id) emoji = emoji.id;
if (!options) return Promise.resolve();
options = Object.assign({}, options);
const roles = options.roles;
if (roles && !Array.isArray(roles)) {
throw new TypeError("Param 'roles' must be an array");
}
if (Array.isArray(roles)) {
options.roles = roles.filter(role => role).map(role => role.valueOf());
}
return rest(this._discordie)
.guilds.emoji.patchEmoji(this.id, emoji, options);
}
/**
* Creates a string URL of an emoji.
* @returns {String|null}
*/
getEmojiURL(emoji) {
if (emoji && emoji.id) emoji = emoji.id;
if (!emoji) return null;
return Constants.CDN_ENDPOINT + Endpoints.CDN_EMOJI(emoji);
}
}
IGuild._inherit(Guild, function modelPropertyGetter(key) {
return this._discordie._guilds.get(this._guildId)[key];
});
/**
* Creates an array of roles of this guild.
* @readonly
* @instance
* @memberOf IGuild
* @name roles
* @returns {Array<IRole>}
*/
IGuild._setValueOverride("roles", function(rolesRaw) {
const roles = [];
if (!rolesRaw) return roles;
for (let role of rolesRaw.values()) {
roles.push(new IRole(this._discordie, role.id, this.id));
}
return roles;
});
module.exports = IGuild;

View File

@@ -0,0 +1,78 @@
"use strict";
const ICollectionBase = require("./ICollectionBase");
const IGuild = require("./IGuild");
const Utils = require("../core/Utils");
const rest = require("../networking/rest");
/**
* @interface
* @extends ICollectionBase
*/
class IGuildCollection extends ICollectionBase {
constructor(discordie, valuesGetter, valueGetter) {
super({
valuesGetter: valuesGetter,
valueGetter: valueGetter,
itemFactory: (id) => new IGuild(this._discordie, id)
});
this._discordie = discordie;
Utils.privatify(this);
}
/**
* Makes a request to create a guild.
* @param {String} name
* @param {String} region
* @param {Buffer|null} [icon]
* @param {Array<IRole|Object>} [roles]
* @param {Array<IChannel|Object>} [channels]
* @param {Number} [verificationLevel]
* See Discordie.VerificationLevel
* @param {Number} [defaultMessageNotifications]
* See Discordie.UserNotificationSettings
* @returns {Promise<IGuild, Error>}
*/
create(name, region, icon,
roles, channels,
verificationLevel, defaultMessageNotifications) {
if (icon instanceof Buffer) {
icon = Utils.imageToDataURL(icon);
}
const toRaw = (data, param) => {
data = data || [];
if (!Array.isArray(data))
throw TypeError("Param '" + param + "' must be an array");
return data.map(v => {
return typeof v.getRaw === "function" ? v.getRaw() : null;
}).filter(v => v);
};
roles = toRaw(roles, "roles");
channels = toRaw(channels, "channels");
return new Promise((rs, rj) => {
rest(this._discordie).guilds.createGuild(
name, region, icon,
roles, channels,
verificationLevel, defaultMessageNotifications
)
.then(guild => rs(this._discordie.Guilds.get(guild.id)))
.catch(rj);
});
}
/**
* Makes a request to get a default list of voice regions.
* Use IGuild.fetchRegions for getting guild-specific list.
* @returns {Promise<Array<Object>, Error>}
*/
fetchRegions() {
return rest(this._discordie).voice.getRegions();
}
}
module.exports = IGuildCollection;

291
node_modules/discordie/lib/interfaces/IGuildMember.js generated vendored Normal file
View File

@@ -0,0 +1,291 @@
"use strict";
const IBase = require("./IBase");
const IUser = require("./IUser");
const IRole = require("./IRole");
const GuildMember = require("../models/GuildMember");
const Utils = require("../core/Utils");
const rest = require("../networking/rest");
/**
* @interface
* @model GuildMember
* @extends IUser
*/
class IGuildMember extends IUser {
constructor(discordie, userId, guildId) {
super(discordie, userId);
Utils.definePrivate(this, {_guildId: guildId});
Object.freeze(this);
}
/**
* Current status of the member.
* @returns {String}
* @readonly
*/
get status() {
return this._discordie._presences.getStatus(this.id, this._guildId);
}
/**
* Current game the member is playing.
* @returns {Object|null}
* @readonly
*/
get game() {
return this._discordie._presences.getGame(this.id, this._guildId);
}
/**
* Name of the current game the member is playing.
* @returns {String|null}
* @readonly
*/
get gameName() {
return this.game ? this.game.name : null;
}
/**
* Previous status of the member.
* @returns {String}
* @readonly
*/
get previousStatus() {
return this._discordie._presences.getPreviousStatus(this.id, this._guildId);
}
/**
* Previous game the member was playing.
* @returns {Object|null}
* @readonly
*/
get previousGame() {
return this._discordie._presences.getPreviousGame(this.id, this._guildId);
}
/**
* Name of the previous game the member was playing.
* @returns {String|null}
* @readonly
*/
get previousGameName() {
return this.previousGame ? this.previousGame.name : null;
}
/**
* Gets guild of this member.
* @returns {IGuild|null}
* @readonly
*/
get guild() {
return this._discordie.Guilds.get(this._guildId);
}
/**
* Gets `nick` of this member if set, otherwise returns `username`.
* @returns {String|null}
* @readonly
*/
get name() {
return this.nick ? this.nick : this.username;
}
/**
* Gets the first voice channel that this member is currently in.
* @returns {IVoiceChannel|null}
*/
getVoiceChannel() {
return super.getVoiceChannel(this._guildId);
}
/**
* Makes a request to kick this member (from the guild they belong to).
* @returns {Promise}
*/
kick() {
return rest(this._discordie)
.guilds.members.kickMember(this._guildId, this.id);
}
/**
* Makes a request to ban this member (from the guild they belong to).
*
* Additionally delete `deleteMessageForDays` number of days worth of their
* messages from all channels of the guild.
* @param deleteMessageForDays - Number of days worth of messages to delete
* (min 0, max 7)
* @returns {Promise}
*/
ban(deleteMessageForDays) {
return rest(this._discordie)
.guilds.bans.banMember(this._guildId, this._userId, deleteMessageForDays);
}
/**
* Makes a request to unban this member (from the guild they belonged to).
* @returns {Promise}
*/
unban() {
return rest(this._discordie)
.guilds.bans.unbanMember(this._guildId, this._userId);
}
/**
* Makes a request to mute this member globally in the guild.
*
* Returns a resolved promise if the member is already muted.
* @returns {Promise}
*/
serverMute(mute) {
if (mute === undefined) mute = true;
if (mute == this.mute) return Promise.resolve();
return rest(this._discordie)
.guilds.members.setMute(this._guildId, this.id, mute);
}
/**
* Makes a request to unmute this member globally in the guild.
*
* Returns a resolved promise if the member is already unmuted.
* @returns {Promise}
*/
serverUnmute() {
return this.serverMute(false);
}
/**
* Makes a request to deafen this member globally in the guild.
*
* Returns a resolved promise if the member is already deafened.
* @returns {Promise}
*/
serverDeafen(deaf) {
if (deaf === undefined) deaf = true;
if (deaf == this.deaf) return Promise.resolve();
return rest(this._discordie)
.guilds.members.setDeaf(this._guildId, this.id, deaf);
}
/**
* Makes a request to undeafen this member globally in the guild.
*
* Returns a resolved promise if the member is already undeafened.
* @returns {Promise}
*/
serverUndeafen() {
return this.serverDeafen(false);
}
/**
* Checks if this member has the specified role.
* @param {IRole|String} role - Role or an id string
* @returns {Promise}
*/
hasRole(role) {
role = role.valueOf();
return this.getRaw().roles.indexOf(role) >= 0;
}
/**
* Assigns (adds) the specified role to this member.
* @param {IRole|String} role - Role or an id string
* @returns {Promise}
*/
assignRole(role) {
role = role.valueOf();
const rawMember = this.getRaw();
if (!rawMember || !rawMember.roles)
return Promise.reject(new Error("Member does not exist"));
// raw roles are mutable, copying with .slice()
const roleIds = rawMember.roles.slice();
const roleIndex = roleIds.indexOf(role);
if (roleIndex >= 0) return Promise.resolve();
roleIds.push(role);
return rest(this._discordie)
.guilds.members.setRoles(this._guildId, this.id, roleIds);
}
/**
* Unassigns (removes) the specified role from this member.
* @param {IRole|String} role - Role or an id string
* @returns {Promise}
*/
unassignRole(role) {
role = role.valueOf();
const rawMember = this.getRaw();
if (!rawMember || !rawMember.roles)
return Promise.reject(new Error("Member does not exist"));
// raw roles are mutable, copying with .slice()
const roleIds = rawMember.roles.slice();
const roleIndex = roleIds.indexOf(role);
if (roleIndex < 0) return Promise.resolve();
roleIds.splice(roleIndex, 1);
return rest(this._discordie)
.guilds.members.setRoles(this._guildId, this.id, roleIds);
}
/**
* Sets specified roles for this member: overwrites all existing roles
* with a new set of roles.
* @param {Array<IRole|String>} roles - Array of roles or id strings
* @returns {Promise}
*/
setRoles(roles) {
const roleIds = roles.map(role => role.valueOf());
return rest(this._discordie)
.guilds.members.setRoles(this._guildId, this.id, roleIds);
}
/**
* Moves this member to the specified voice channel.
* @param {IChannel|String} channel - Channel or an id string
* @returns {Promise}
*/
setChannel(channel) {
channel = channel.valueOf();
return rest(this._discordie)
.guilds.members.setChannel(this._guildId, this.id, channel);
}
/**
* Makes a request to set a nickname for this member.
*
* Requires permission `MANAGE_NICKNAMES`.
* @param {String} nick
* @returns {Promise}
*/
setNickname(nick) {
return rest(this._discordie)
.guilds.members.setNickname(this._guildId, this.id, nick);
}
}
IGuildMember._inherit(GuildMember, function modelPropertyGetter(key) {
return this._discordie._members.getMember(this._guildId, this._userId)[key];
});
/**
* Creates an array of roles assigned to this member.
* @readonly
* @instance
* @memberOf IGuildMember
* @name roles
* @returns {Array<IRole>}
*/
IGuildMember._setValueOverride("roles", function(roleIds) {
const roles = [];
if (!roleIds) return roles;
for (let roleId of roleIds) {
roles.push(new IRole(this._discordie, roleId, this._guildId));
}
return roles;
});
module.exports = IGuildMember;

101
node_modules/discordie/lib/interfaces/IInviteManager.js generated vendored Normal file
View File

@@ -0,0 +1,101 @@
"use strict";
const Utils = require("../core/Utils");
const rest = require("../networking/rest");
/**
* @interface
*/
class IInviteManager {
constructor(discordie) {
this._discordie = discordie;
Utils.privatify(this);
Object.freeze(this);
}
/**
* Makes a request to create an invite.
* See `IChannel.createInvite` for more info.
* @see IChannel.createInvite
* @param {IChannel|String} channel
* @param {Object} options
* @returns {Promise<Object, Error>}
*/
create(channel, options) {
options = options || {
max_age: 60 * 30,
// value in seconds
max_uses: 0,
// pretty obvious
temporary: false
// temporary membership, kicks members without roles on disconnect
};
channel = channel.valueOf();
return rest(this._discordie).invites.createInvite(channel, options);
}
/**
* Makes a request to regenerate existing invite.
* @param {Object|String} code
* @returns {Promise<Object, Error>}
*/
regenerate(code) {
if (code && code.code) code = code.code;
const options = {regenerate: code};
return rest(this._discordie).invites.createInvite(channel, options);
}
/**
* Makes a request to revoke existing invite.
* @param {Object|String} code
* @returns {Promise<Object, Error>}
*/
revoke(code) {
if (code && code.code) code = code.code;
return rest(this._discordie).invites.deleteInvite(code);
}
/**
* Makes a request to resolve existing invite.
* @param {Object|String} code
* @returns {Promise<Object, Error>}
* @example
* client.Invites.resolve("Zt5yW").then(console.log).catch(console.log);
* // Example response:
* {
* "code": "Zt5yW",
* "guild": {
* "splash_hash": null,
* "id": "00000000000000000",
* "name": "test"
* },
* "channel": {
* "type": "text",
* "id": "000000000000000000",
* "name": "testchannel"
* }
* }
*/
resolve(code) {
if (code && code.code) code = code.code;
return rest(this._discordie).invites.getInvite(code);
}
/**
* **Deprecated**: Only works with user accounts.
* Bot accounts can be invited by users with Manage Server permission using
* the `https://discordapp.com/oauth2/authorize?client_id=%APP_ID%&scope=bot`
* page. See official Discord API documentation for more info.
*
* Makes a request to accept existing invite.
* @param {Object|String} code
* @returns {Promise<Object, Error>}
*/
accept(code) {
if (code && code.code) code = code.code;
return rest(this._discordie).invites.postInvite(code);
}
}
module.exports = IInviteManager;

430
node_modules/discordie/lib/interfaces/IMessage.js generated vendored Normal file
View File

@@ -0,0 +1,430 @@
"use strict";
const Constants = require("../Constants");
const MessageTypes = Constants.MessageTypes;
const IBase = require("./IBase");
const IRole = require("./IRole");
const IUser = require("./IUser");
const Utils = require("../core/Utils");
const Message = require("../models/Message");
const rest = require("../networking/rest");
/**
* @interface
* @model Message
* @extends IBase
* @description
* Chat message. Can be a system message depending on type:
*
* ```js
* Discordie.MessageTypes: {
* DEFAULT: 0,
* RECIPIENT_ADD: 1,
* RECIPIENT_REMOVE: 2,
* CALL: 3,
* CHANNEL_NAME_CHANGE: 4,
* CHANNEL_ICON_CHANGE: 5,
* CHANNEL_PINNED_MESSAGE: 6
* }
* ```
*/
class IMessage extends IBase {
constructor(discordie, messageId) {
super();
Utils.definePrivate(this, {
_discordie: discordie,
_messageId: messageId
});
Object.freeze(this);
}
/**
* Checks whether this message is cached.
* @param {IMessage} message
* @returns {boolean}
* @readonly
*/
get isCached() {
return !!this._valid;
}
/**
* Checks whether this message was edited by the author.
*
* Returns null if message does not exist in cache.
* @returns {boolean|null}
* @readonly
*/
get isEdited() {
return this.isCached ? this.edited_timestamp != null : null;
}
/**
* Checks whether this message is from a private channel (direct message).
*
* Returns null if message/channel does not exist in cache.
* @returns {boolean|null}
* @readonly
*/
get isPrivate() {
return this._discordie._channels.isPrivate(this.channel_id);
}
/**
* Checks whether the message is a system message.
* @returns {boolean|null}
* @readonly
*/
get isSystem() {
return this.type !== MessageTypes.DEFAULT;
}
/**
* Generates a system message string depending on message `type`.
* @returns {String|null}
* @readonly
*/
get systemMessage() {
if (!this._valid) return null;
const type = this.type;
const user = this.author || {};
const target = this.mentions[0] || {};
if (type === MessageTypes.RECIPIENT_ADD) {
return user.username + " added " + target.username + " to the group.";
}
if (type === MessageTypes.RECIPIENT_REMOVE) {
if (user.id !== target.id) {
return user.username + " added " + target.username + " to the group.";
} else {
return user.username + " left the group.";
}
}
if (type === MessageTypes.CALL) {
const localUserId = this._discordie._user && this._discordie._user.id;
const isActive =
this._discordie._calls.isActive(this.channel_id, this.id);
const isMissed =
!isActive &&
this.call && this.call.participants &&
this.call.participants.indexOf(localUserId) < 0;
if (isMissed) {
return "You missed a call from " + user.username + ".";
}
if (isActive) {
return user.username + " started a call. Join the call.";
} else {
return user.username + " started a call.";
}
}
if (type === MessageTypes.CHANNEL_NAME_CHANGE) {
return user.username + " changed the channel name: " + this.content;
}
if (type === MessageTypes.CHANNEL_ICON_CHANGE) {
return user.username + " changed the channel icon.";
}
if (type === MessageTypes.CHANNEL_PINNED_MESSAGE) {
return user.username +
" pinned a message to this channel. See all the pins.";
}
}
/**
* Resolves username that should be displayed with this message.
* @return {String|null}
* @readonly
*/
get displayUsername() {
if (!this._valid) return null;
const member = this.member;
const author = this.author;
const nick = member ? member.nick : null;
const username = author ? author.username : null;
return nick || username;
}
/**
* Gets channel of this message.
*
* Returns null if message does not exist in cache.
* @returns {ITextChannel|IDirectMessageChannel|null}
* @readonly
*/
get channel() {
if (this.isPrivate) {
return this._discordie.DirectMessageChannels.get(this.channel_id);
}
return this._discordie.Channels.get(this.channel_id);
}
/**
* Gets guild of this message.
*
* Returns null if message does not exist in cache or from a private
* channel (direct message).
* @returns {IGuild|null}
* @readonly
*/
get guild() {
if (this.isPrivate || !this.isCached) return null;
return this.channel ? this.channel.guild : null;
}
/**
* Gets member instance of author.
*
* Returns null for private channels, if message does not exist in cache,
* the author is no longer a member of the guild, or it is a webhook message.
* @returns {IGuildMember|null}
* @readonly
*/
get member() {
if (this.isPrivate || !this.isCached) return null;
return this._discordie.Users.getMember(this.guild.id, this.author.id);
}
/**
* Creates an array of all known (cached) versions of this message (including
* the latest).
* Sorted from latest (first) to oldest (last).
* Does not include embeds.
* @returns {Array<Object>}
* @readonly
*/
get edits() {
return this._discordie._messages.getEdits(this.id);
}
/**
* Resolves user and channel references in `content` property to proper names.
* References that are not found in cache will be left as is and not resolved.
*
* Returns null if this message is not cached.
* @returns {String|null}
* @example
* var content = message.content;
* // 'just a message for <@157838423632817262> in <#78826383786329613>'
* var resolvedContent = message.resolveContent();
* // 'just a message for @testie in #general'
* var resolvedContent = client.Messages.resolveContent(message.content);
* // 'just a message for @testie in #general'
*/
resolveContent() {
if (!this.isCached) return null;
return this._discordie.Messages.resolveContent(this.content, this.guild);
}
/**
* Makes a request to edit this message.
*
* Editing of other users' messages is not allowed, server will send an
* `Error` `Forbidden` and returned promise will be rejected if you attempt
* to do so.
*
* See `IMessageCollection.editMessage` if you are looking for a method
* that can operate on JSON or raw message id.
* @param {String} [content]
* @param {Object} [embed]
* Refer to [official API documentation](https://discordapp.com/developers/docs/resources/channel#embed-object)
* for embed structure description.
* @returns {Promise<IMessage, Error>}
*/
edit(content, embed) {
if (Array.isArray(content)) content = content.join("\n");
if (content && typeof content !== "string") content = String(content);
return new Promise((rs, rj) => {
rest(this._discordie)
.channels.patchMessage(this.channel_id, this.id, content, embed)
.then(() => rs(this))
.catch(rj);
});
}
/**
* Makes a request to delete this message.
*
* See `IMessageCollection.deleteMessage` if you are looking for a method
* that can operate on JSON or raw message id.
* @returns {Promise}
*/
delete() {
return rest(this._discordie)
.channels.deleteMessage(this.channel_id, this.id);
}
/**
* Makes a request to pin this message.
*
* See `IMessageCollection.pinMessage` if you are looking for a method
* that can operate on JSON or raw message id.
* @returns {Promise<IMessage, Error>}
*/
pin() {
return new Promise((rs, rj) => {
rest(this._discordie)
.channels.pinMessage(this.channel_id, this.id)
.then(() => rs(this))
.catch(rj);
});
}
/**
* Makes a request to unpin this message.
*
* See `IMessageCollection.unpinMessage` if you are looking for a method
* that can operate on JSON or raw message id.
* @returns {Promise<IMessage, Error>}
*/
unpin() {
return new Promise((rs, rj) => {
rest(this._discordie)
.channels.unpinMessage(this.channel_id, this.id)
.then(() => rs(this))
.catch(rj);
});
}
/**
* Makes a request to fetch users who reacted to this message with the
* specified emoji.
* @param {Object|String} emoji
* Partial emoji `{id: String|null, name: String}` or a unicode emoji
* @param {Number} [limit] - Max 100 users per fetch
* @param {IUser|String} [after] - Start list from specified user id
* @return {Promise<Array<IUser>, Error>}
*/
fetchReactions(emoji, limit, after) {
emoji = Utils.emojiToCode(emoji);
limit = limit || 100;
after = after ? after.valueOf() : after;
return new Promise((rs, rj) => {
rest(this._discordie)
.channels.getReactions(this.channel_id, this.id, emoji, limit, after)
.then(users => rs(users.map(user => client.Users.get(user.id))))
.catch(rj);
});
}
/**
* Makes a request to add a reaction to this message with the specified
* emoji.
* @param {Object|String} emoji
* Partial emoji `{id: String|null, name: String}` or a unicode emoji
* @return {Promise}
* @example
* message.addReaction(message.reactions[0].emoji);
* message.addReaction("\uD83D\uDE2C");
*/
addReaction(emoji) {
emoji = Utils.emojiToCode(emoji);
return rest(this._discordie)
.channels.addReaction(this.channel_id, this.id, emoji);
}
/**
* Makes a request to remove a reaction to this message with the specified
* emoji.
* @param {Object|String} emoji
* Partial emoji `{id: String|null, name: String}` or a unicode emoji
* @param {IUser|String} [user] - Remove reaction of another user
* @return {Promise}
*/
removeReaction(emoji, user) {
emoji = Utils.emojiToCode(emoji);
user = user ? user.valueOf() : user;
return rest(this._discordie)
.channels.removeReaction(this.channel_id, this.id, emoji, user);
}
/**
* Makes a request to remove all reactions from this message.
* @return {Promise}
*/
clearReactions() {
return rest(this._discordie)
.channels.deleteReactions(this.channel_id, this.id);
}
/**
* Makes a request to send a reply to channel the message was from, prefixing
* content with author's mention in non-private channels.
* @param {String|Array<String>} content
* Strings will be sent as is, arrays - joined with a newline character.
* @param {IUser|IGuildMember|Array<IUser>|Array<IGuildMember>} [mentions]
* Deprecated: left for backward compatibility.
* @param {boolean} [tts]
* @param {Object} [embed]
* Refer to [official API documentation](https://discordapp.com/developers/docs/resources/channel#embed-object)
* for embed structure description.
* @returns {Promise<IMessage, Error>}
*/
reply(content, mentions, tts, embed) {
if (this.isPrivate) {
return this.channel.sendMessage(content, mentions, tts, embed);
}
return this.channel
.sendMessage(`${this.author.mention}, ${content}`, mentions, tts, embed);
}
}
IMessage._inherit(Message, function modelPropertyGetter(key) {
return this._discordie._messages.get(this._messageId)[key];
});
IMessage._setSuppressErrors(true);
/**
* @readonly
* @instance
* @memberOf IMessage
* @name author
* @returns {IUser}
*/
IMessage._setValueOverride("author", function(user) {
if (user && this.webhook_id) {
const factory = () => new IUser(this._discordie, user.id, user);
return this._discordie.Users._getOrCreateInterface(user, factory);
}
return user ? this._discordie.Users.get(user.id) : null;
});
/**
* @readonly
* @instance
* @memberOf IMessage
* @name mentions
* @returns {Array<IUser>}
*/
IMessage._setValueOverride("mentions", function(v) {
return v ? v.map(u => this._discordie.Users.get(u.id)) : [];
});
/**
* @readonly
* @instance
* @memberOf IMessage
* @name mention_roles
* @returns {Array<IRole>}
*/
IMessage._setValueOverride("mention_roles", function(rolesRaw) {
if (!rolesRaw || !this.guild) return [];
var guildId = this.guild.id;
return rolesRaw.map(id => new IRole(this._discordie, id, guildId));
});
module.exports = IMessage;

View File

@@ -0,0 +1,374 @@
"use strict";
const ICollectionBase = require("./ICollectionBase");
const IMessage = require("./IMessage");
const IChannel = require("./IChannel");
const Utils = require("../core/Utils");
const rest = require("../networking/rest");
/**
* @interface
* @extends ICollectionBase
* @description
* Collection with all cached messages.
*
* **Includes deleted messages** - you can filter them by checking
* `IMessage.deleted` boolean.
*/
class IMessageCollection extends ICollectionBase {
constructor(discordie, valuesGetter, valueGetter) {
super({
valuesGetter: valuesGetter,
valueGetter: valueGetter,
itemFactory: (id) => new IMessage(this._discordie, id)
});
this._discordie = discordie;
Utils.privatify(this);
}
/**
* Creates an array of cached messages in `channel`, sorted in order of
* arrival (message cache is sorted on message insertion, not when this
* getter is invoked).
*
* Returns an empty array if channel no longer exists.
*
* > **Note:** Message cache also includes deleted messages.
* > You can filter them by checking `IMessage.deleted` boolean.
* @param {IChannel|String} channel
* @returns {Array<IMessage>}
*/
forChannel(channel) {
var cache = this._discordie._messages.getChannelCache(channel.valueOf());
if (!cache || !cache.size) return [];
return cache.map(message => this._getOrCreateInterface(message));
}
/**
* Purges channel cache.
* @param {IChannel|String} channel
*/
purgeChannelCache(channel) {
channel = channel.valueOf();
return this._discordie._messages.purgeChannelCache(channel);
}
/**
* Creates an array of cached pinned messages in `channel`.
*
* Pinned message cache is updated only if all pinned messages have been
* loaded with `ITextChannel.fetchPinned()`.
*
* Returns an empty array if channel no longer exists or if pinned messages
* have not been fetched yet.
* @param {IChannel|String} channel
* @returns {Array<IMessage>}
*/
forChannelPinned(channel) {
var cache = this._discordie._messages.getChannelPinned(channel.valueOf());
if (!cache) return [];
return cache.map(msg => this._getOrCreateInterface(msg));
}
/**
* Purges pinned message cache for `channel`.
* @param {IChannel|String} channel
*/
purgeChannelPinned(channel) {
channel = channel.valueOf();
return this._discordie._messages.purgeChannelPinned(channel);
}
/**
* Purges pinned message cache globally.
*/
purgePinned() {
return this._discordie._messages.purgePinned();
}
/**
* Purges edits cache globally.
*/
purgeEdits() {
return this._discordie._messages.purgeEdits();
}
/**
* Purges message cache globally.
*/
purgeAllCache() {
return this._discordie._messages.purgeAllCache();
}
/**
* Gets channel message cache limit.
* @param {IChannel|String} channel
* @returns {Number}
*/
getChannelMessageLimit(channel) {
channel = channel.valueOf();
return this._discordie._messages.getChannelMessageLimit(channel);
}
/**
* Sets channel message cache limit (with a minimum of 1).
* Limit reverts to default when channel (or cache) is destroyed.
* Returns false if limit is invalid or channel does not exist.
* @param {IChannel|String} channel
* @param {Number} limit
* @returns {Boolean}
*/
setChannelMessageLimit(channel, limit) {
channel = channel.valueOf();
return this._discordie._messages.setChannelMessageLimit(channel, limit);
}
/**
* Gets global message cache limit per channel.
* @returns {Number}
*/
getMessageLimit() {
return this._discordie._messages.getMessageLimit();
}
/**
* Sets global message cache limit per channel (with a minimum of 1).
* Does not affect channels with custom limits if new is lower than current.
* @param {Number} limit
*/
setMessageLimit(limit) {
return this._discordie._messages.setMessageLimit(limit);
}
/**
* Gets global edits cache limit per message.
* @returns {Number}
*/
getEditsLimit() {
return this._discordie._messages.getEditsLimit();
}
/**
* Sets global edits cache limit per message.
* @param {Number} limit
*/
setEditsLimit(limit) {
return this._discordie._messages.setEditsLimit(limit);
}
/**
* Makes a request to edit a message.
* Alternative method for editing messages that are not in cache.
*
* Editing of other users' messages is not allowed, server will send an
* `Error` `Forbidden` and returned promise will be rejected if you attempt
* to do so.
*
* Parameter `messageId` can be an object with fields `{channel_id, id}`,
* where `id` is a String message id, `channel_id` is a String channel id.
*
* Parameter `channelId` is ignored when `messageId` is an object or
* an instance of `IMessage`.
*
* Returns a promise that resolves to a JSON object of the edited message.
*
* @param {String|Object} [content]
* @param {IMessage|Object|String} messageId
* @param {String} channelId - Ignored if `messageId` is an object with `id`
* @param {Object} [embed]
* Refer to [official API documentation](https://discordapp.com/developers/docs/resources/channel#embed-object)
* for embed structure description.
* @returns {Promise<Object, Error>}
* @example
* var message = client.Messages.find(m => true); // get any message
* client.Messages.editMessage("new content", message);
*
* var jsonMessage = {id: message.id, channel_id: message.channel_id};
* client.Messages.editMessage("new content", jsonMessage);
*
* client.Messages.editMessage("new content", message.id, message.channel_id);
*/
editMessage(content, messageId, channelId, embed) {
if (Array.isArray(content)) content = content.join("\n");
if (content && typeof content !== "string") content = String(content);
var message = {id: messageId, channel_id: channelId};
if (messageId && messageId.id) {
message.id = messageId.id;
message.channel_id = messageId.channel_id || messageId.channelId;
}
return rest(this._discordie)
.channels.patchMessage(message.channel_id, message.id, content, embed);
}
/**
* Makes a request to delete a message.
* Alternative method for deleting messages that are not in cache.
*
* Parameter `messageId` can be an object with fields `{channel_id, id}`,
* where `id` is a String message id, `channel_id` is a String channel id.
*
* Parameter `channelId` is ignored when `messageId` is an object or
* an instance of `IMessage`.
*
* @param {IMessage|Object|String} messageId
* @param {String} channelId - Ignored if `messageId` is an object with `id`
* @returns {Promise}
* @example
* var message = client.Messages.find(m => true); // get any message
* client.Messages.deleteMessage(message);
*
* var jsonMessage = {id: message.id, channel_id: message.channel_id};
* client.Messages.deleteMessage(jsonMessage);
*
* client.Messages.deleteMessage(message.id, message.channel_id);
*/
deleteMessage(messageId, channelId) {
var message = {id: messageId, channel_id: channelId};
if (messageId && messageId.id) {
message.id = messageId.id;
message.channel_id = messageId.channel_id || messageId.channelId;
}
return rest(this._discordie)
.channels.deleteMessage(message.channel_id, message.id);
}
/**
* Makes a request to pin a message.
* Alternative method for pinning messages that are not in cache.
*
* Accepts same parameters as `IMessageCollection.deleteMessage`.
*
* @param {IMessage|Object|String} messageId
* @param {String} channelId - Ignored if `messageId` is an object with `id`
* @returns {Promise}
*/
pinMessage(messageId, channelId) {
var message = {id: messageId, channel_id: channelId};
if (messageId && messageId.id) {
message.id = messageId.id;
message.channel_id = messageId.channel_id || messageId.channelId;
}
return rest(this._discordie)
.channels.pinMessage(message.channel_id, message.id);
}
/**
* Makes a request to unpin a message.
* Alternative method for unpinning messages that are not in cache.
*
* Accepts same parameters as `IMessageCollection.deleteMessage`.
*
* @param {IMessage|Object|String} messageId
* @param {String} channelId - Ignored if `messageId` is an object with `id`
* @returns {Promise}
*/
unpinMessage(messageId, channelId) {
var message = {id: messageId, channel_id: channelId};
if (messageId && messageId.id) {
message.id = messageId.id;
message.channel_id = messageId.channel_id || messageId.channelId;
}
return rest(this._discordie)
.channels.unpinMessage(message.channel_id, message.id);
}
/**
* Makes a request to delete multiple messages.
*
* If `messages` array contains instances of `IMessage`, parameter `channel`
* is not required as it will be determined from the first message instance.
* Also deleted messages will be omitted from the request.
*
* If `messages` array is empty, returned promise resolves instantly
* without sending a request.
* @param {Array<IMessage|String>} messages
* @param {IChannel|String} [channel]
* Channel or channel id, required is `messages` is an array of string ids
* @returns {Promise}
*/
deleteMessages(messages, channel) {
if (!Array.isArray(messages))
throw new TypeError("Param 'messages' must be an array");
messages = messages.filter(m => {
if (m instanceof IMessage)
return !m.deleted;
return true;
});
if (!messages.length) return Promise.resolve();
var internalMessage = messages.find(v => v.channel_id);
if (!internalMessage && !channel)
throw new TypeError("Param 'channel' must be defined for arrays of ids");
channel = (internalMessage && !channel) ?
internalMessage.channel_id :
channel.valueOf();
// bulk-delete returns 'Bad Request' in this case
if (messages.length === 1) {
return rest(this._discordie)
.channels.deleteMessage(channel, messages[0].valueOf());
}
return rest(this._discordie)
.channels.deleteMessages(channel, messages.map(v => v.valueOf()));
}
/**
* Resolves user and channel references to proper names.
* References that are not found in cache will be left as is and not resolved.
* @param {String} content
* @param {IGuild|String} [guild]
* Optional guild to resolve roles and nicknames from
* @returns {String}
* @example
* var content = message.content;
* // 'just a message for <@157838423632817262> in <#78826383786329613>'
* var resolvedContent = message.resolveContent();
* // 'just a message for @testie in #general'
* var resolvedContent = client.Messages.resolveContent(message.content);
* // 'just a message for @testie in #general'
*/
resolveContent(content, guild) {
if (typeof content !== "string")
throw new TypeError("Param 'content' is not a string");
if (guild) {
var guildId = guild.valueOf();
guild = this._discordie.Guilds.get(guildId);
}
return content.replace(/<(@!?|#|@&)([0-9]+)>/g, (match, type, id) => {
if (type === "@" || type === "@!") { // user
var user = this._discordie.Users.get(id);
if (!user) return match;
if (guild && type === "@!") {
var member = user.memberOf(guild);
return (member && ("@" + member.name)) || match;
}
return (user && ("@" + user.username)) || match;
}
else if (type === "#") { // channel
var channel = this._discordie.Channels.get(id);
return (channel && ("#" + channel.name)) || match;
}
else if (type === "@&") { // role
if (!guild || !guild.roles) return match;
var role = guild.roles.find(r => r.id === id);
return (role && ("@" + role.name)) || match;
}
});
}
}
module.exports = IMessageCollection;

View File

@@ -0,0 +1,121 @@
"use strict";
const IBase = require("./IBase");
const IPermissions = require("./IPermissions");
const Utils = require("../core/Utils");
const PermissionOverwrite = require("../models/PermissionOverwrite");
const Constants = require("../Constants");
const ChannelTypes = Constants.ChannelTypes;
const PermissionSpecs = Constants.PermissionSpecs;
const rest = require("../networking/rest");
/**
* @interface
* @model PermissionOverwrite
* @extends IBase
*/
class IPermissionOverwrite extends IBase {
constructor(discordie, overwriteId, channelId) {
super();
this._discordie = discordie;
this._overwriteId = overwriteId;
this._channelId = channelId;
const channel = discordie._channels.get(channelId) || {};
const spec = (channel.type == ChannelTypes.GUILD_VOICE ?
PermissionSpecs.VoiceChannel :
PermissionSpecs.TextChannel
);
this._allow = new IPermissions(this.getRaw().allow, spec);
this._deny = new IPermissions(this.getRaw().deny, spec);
Utils.privatify(this);
Object.freeze(this);
}
/**
* Gets date and time the member or role of this overwrite was created at.
* @returns {Date}
* @readonly
* @ignore
*/
get createdAt() {
return new Date(Utils.timestampFromSnowflake(this.id));
}
/**
* Loads original permissions from cache and updates this object.
*/
reload() {
const raw = this.getRaw();
if (!raw) return;
this._allow.raw = raw.allow;
this._deny.raw = raw.deny;
}
/**
* Makes a request to commit changes made to this permission overwrite object.
* @returns {Promise<IPermissionOverwrite, Error>}
*/
commit() {
return new Promise((rs, rj) => {
const raw = this.getRaw();
raw.allow = this._allow.raw;
raw.deny = this._deny.raw;
rest(this._discordie)
.channels.putPermissionOverwrite(this._channelId, raw)
.then(() => rs(this))
.catch(e => {
this.reload();
return rj(e);
});
});
}
/**
* Makes a request to delete this permission overwrite.
* @returns {Promise}
*/
delete() {
return rest(this._discordie)
.channels.deletePermissionOverwrite(this._channelId, this.id);
}
}
IPermissionOverwrite._inherit(PermissionOverwrite, function(key) {
const channel = this._discordie._channels.get(this._channelId);
if (!channel) return null;
const overwrite = channel.permission_overwrites
.find(o => o.id == this._overwriteId);
if (!overwrite) return null;
return overwrite[key];
});
/**
* @readonly
* @instance
* @memberOf IPermissionOverwrite
* @name allow
* @returns {IPermissions}
*/
IPermissionOverwrite._setValueOverride("allow", function(v) {
return this._allow;
});
/**
* @readonly
* @instance
* @memberOf IPermissionOverwrite
* @name deny
* @returns {IPermissions}
*/
IPermissionOverwrite._setValueOverride("deny", function(v) {
return this._deny;
});
module.exports = IPermissionOverwrite;

202
node_modules/discordie/lib/interfaces/IPermissions.js generated vendored Normal file
View File

@@ -0,0 +1,202 @@
"use strict";
const Constants = require("../Constants");
const Permissions = Constants.Permissions;
const PermissionsDefault = Constants.PermissionsDefault;
/**
* @interface
* @description
* Wrapper for numeric permission values.
*
* Contains boolean getters/setters (different for roles and channels).
*
* - `.Text` section only exists for text channels;
* - `.Voice` section only exists for voice channels;
* - `.General` section exists for both;
* - Roles contain all properties - both `.Text` and `.Voice` sections.
*
* Example of role permission properties:
* ```
* General: {
* CREATE_INSTANT_INVITE,
* KICK_MEMBERS,
* BAN_MEMBERS,
* ADMINISTRATOR,
* MANAGE_CHANNELS,
* MANAGE_GUILD,
* CHANGE_NICKNAME,
* MANAGE_NICKNAMES,
* MANAGE_ROLES,
* MANAGE_WEBHOOKS,
* MANAGE_EMOJIS,
* },
* Text: {
* READ_MESSAGES,
* SEND_MESSAGES,
* SEND_TTS_MESSAGES,
* MANAGE_MESSAGES,
* EMBED_LINKS,
* ATTACH_FILES,
* READ_MESSAGE_HISTORY,
* MENTION_EVERYONE,
* EXTERNAL_EMOTES,
* ADD_REACTIONS,
* },
* Voice: {
* CONNECT,
* SPEAK,
* MUTE_MEMBERS,
* DEAFEN_MEMBERS,
* MOVE_MEMBERS,
* USE_VAD,
* }
* ```
*
* Example of text channel permission properties:
* ```
* General: {
* CREATE_INSTANT_INVITE,
* MANAGE_CHANNEL,
* MANAGE_PERMISSIONS
* },
* Text: {
* READ_MESSAGES,
* SEND_MESSAGES,
* SEND_TTS_MESSAGES,
* MANAGE_MESSAGES,
* EMBED_LINKS,
* ATTACH_FILES,
* READ_MESSAGE_HISTORY,
* MENTION_EVERYONE,
* EXTERNAL_EMOTES,
* ADD_REACTIONS,
* }
* ```
*
* @example
* var guild = client.Guilds.find(g => g.name == "test");
* guild.createRole().then(role => {
* var perms = role.permissions;
* perms.General.KICK_MEMBERS = true;
* perms.General.BAN_MEMBERS = true;
* perms.Text.MENTION_EVERYONE = true;
*
* var newRoleName = "Testing";
* var color = 0xE74C3C; // red
* var hoist = true; // display as separate group
*
* role.commit(newRoleName, color, hoist);
* }).catch(err => console.log("Failed to create role:", err));
*/
class IPermissions {
constructor(raw, permissionSpec) {
this.raw = raw || 0;
for (let type in permissionSpec) {
this[type] = {};
for (let permission in permissionSpec[type]) {
const bit = permissionSpec[type][permission];
Object.defineProperty(this[type], permission, {
enumerable: true,
get: () => (this.raw & bit) === bit,
set: (v) => v ? (this.raw |= bit) : (this.raw &= ~bit)
});
}
Object.seal(this[type]);
}
Object.seal(this);
}
inspect() { return JSON.parse(JSON.stringify(this)); }
setAll() { this.raw = IPermissions.ALL; }
unsetAll() { this.raw = IPermissions.NONE; }
static get ALL() { return (~0 >>> 0); }
static get DEFAULT() { return PermissionsDefault; }
static get NONE() { return 0; }
static resolveRaw(user, context) {
// referencing here to avoid circular require()
const IUser = require("./IUser");
const IAuthenticatedUser = require("./IAuthenticatedUser");
const IChannel = require("./IChannel");
const IGuild = require("./IGuild");
const IGuildMember = require("./IGuildMember");
if (!(user instanceof IUser) && !(user instanceof IAuthenticatedUser))
throw new TypeError("user must be an instance of IUser");
if (!(context instanceof IChannel) && !(context instanceof IGuild))
throw new TypeError("context must be an instance of IChannel or IGuild");
if (!context._valid) throw new Error("Invalid context");
let overwrites = null;
if (context instanceof IChannel) {
overwrites = context.getRaw().permission_overwrites;
context = context.guild;
}
if (context.isOwner(user))
return IPermissions.ALL;
const member = user instanceof IGuildMember ?
user : context._discordie.Users.getMember(context.id, user.id);
if (!member) throw new Error("User is not a member of the context");
const contextRaw = context.getRaw();
const roleEveryone = contextRaw ? contextRaw.roles.get(context.id) : null;
// apply default permissions
let permissions = roleEveryone ?
roleEveryone.permissions : IPermissions.DEFAULT;
// then roles assigned for member
const memberRoles = member ? member.roles : null;
if (memberRoles) {
permissions = memberRoles.reduce(
(ps, role) => ps | role.permissions.raw,
permissions
);
}
if (permissions & Permissions.General.ADMINISTRATOR)
return IPermissions.ALL;
if (overwrites) {
const applyOverwrite = (overwrite) => {
if (!overwrite) return;
permissions &= ~overwrite.deny;
permissions |= overwrite.allow;
};
// then channel specific @everyone role
const overwriteEveryone = overwrites.find(o => o.id == context.id);
applyOverwrite(overwriteEveryone);
if (member) {
// then member roles for channel
if (memberRoles)
memberRoles.forEach(role =>
applyOverwrite(overwrites.find(o => o.id == role.id))
);
// then member specific permissions for channel
const overwriteMember = overwrites.find(o => o.id == member.id);
applyOverwrite(overwriteMember);
}
}
return permissions;
}
static resolve(user, context) {
return new IPermissions(
IPermissions.resolveRaw(user, context),
Permissions
);
}
}
module.exports = IPermissions;

132
node_modules/discordie/lib/interfaces/IRole.js generated vendored Normal file
View File

@@ -0,0 +1,132 @@
"use strict";
const IBase = require("./IBase");
const IPermissions = require("./IPermissions");
const Utils = require("../core/Utils");
const Role = require("../models/Role");
const Constants = require("../Constants");
const PermissionSpecs = Constants.PermissionSpecs;
const rest = require("../networking/rest");
/**
* @interface
* @extends IBase
* @model Role
*/
class IRole extends IBase {
constructor(discordie, roleId, guildId) {
super();
Utils.definePrivate(this, {
_discordie: discordie,
_roleId: roleId,
_guildId: guildId
});
Utils.definePrivate(this, {
_permissions: new IPermissions(
this.getRaw("permissions"),
PermissionSpecs.Role
)
});
Object.freeze(this);
}
/**
* Creates a mention from this role's id.
* @returns {String}
* @readonly
* @example
* channel.sendMessage(role.mention + ", example mention");
*/
get mention() {
return `<@&${this.id}>`;
}
/**
* Loads original permissions from cache and updates this object.
*/
reload() {
const rawPermissions = this.getRaw("permissions");
if (!rawPermissions) return;
this._permissions.raw = rawPermissions;
}
/**
* Makes a request to commit changes made to this role object.
* @param {String} [name]
* @param {Number} [color] - RGB color number
* @param {boolean} [hoist] - true if role should be displayed separately
* @param {boolean} [mentionable]
* @returns {Promise}
*/
commit(name, color, hoist, mentionable) {
const everyone = (this.id == this._guildId);
if (!name || everyone) name = this.name;
if (hoist === undefined || hoist === null || everyone) hoist = this.hoist;
if (color === undefined || color === null || everyone) color = this.color;
if (mentionable === undefined || mentionable === null || everyone) {
mentionable = this.mentionable;
}
return new Promise((rs, rj) => {
rest(this._discordie).guilds.roles.patchRole(
this._guildId, this.id,
name, this.permissions.raw, color, hoist, mentionable
)
.then(() => rs(this))
.catch(err => {
this.reload();
return rj(err);
});
});
}
/**
* Moves this role to `position` and makes a batch role update request.
* @param {Number} position
* @returns {Promise}
*/
setPosition(position) {
const guild = this._discordie.Guilds.get(this._guildId);
if (!guild) return Promise.reject(new Error("Guild does not exist"));
// maybe todo: disallow assigning position 0 (role @everyone)
const changes = Utils.reorderObjects(guild.roles, this, position);
if (!changes) return Promise.resolve();
return rest(this._discordie)
.guilds.roles.batchPatchRoles(this._guildId, changes);
}
/**
* Makes a request to delete this role.
* @returns {Promise}
*/
delete() {
return rest(this._discordie)
.guilds.roles.deleteRole(this._guildId, this.id);
}
}
IRole._inherit(Role, function modelPropertyGetter(key) {
const guild = this._discordie._guilds.get(this._guildId);
if (!guild) return null;
const role = guild.roles.get(this._roleId);
if (!role) return null;
return role[key];
});
/**
* @readonly
* @instance
* @memberOf IRole
* @name permissions
* @returns {IPermissions}
*/
IRole._setValueOverride("permissions", function(v) {
return this._permissions;
});
module.exports = IRole;

283
node_modules/discordie/lib/interfaces/ITextChannel.js generated vendored Normal file
View File

@@ -0,0 +1,283 @@
"use strict";
const Utils = require("../core/Utils");
const User = require("../models/User");
const IChannel = require("./IChannel");
const IUser = require("./IUser");
const Constants = require("../Constants");
const Permissions = Constants.Permissions;
const rest = require("../networking/rest");
/**
* @interface
* @model Channel
* @extends IChannel
*/
class ITextChannel extends IChannel {
constructor(discordie, channelId) {
super(discordie, channelId);
}
/**
* Creates a mention from this channel's id.
* @returns {String}
* @readonly
* @example
* channel.sendMessage(channel.mention + ", example mention");
*/
get mention() {
return `<#${this.id}>`;
}
/**
* Creates an array of IGuildMember that
* have permissions to read this channel.
* @returns {Array<IGuildMember>}
* @readonly
*/
get members() {
return this._discordie.Users.membersForChannel(this);
}
/**
* Gets a value indicating whether it is a default (general) channel.
* @returns {boolean}
* @readonly
*/
get isDefaultChannel() {
return this.guild_id === this.id;
}
/**
* Gets a value indicating whether all messages were loaded.
* @returns {boolean}
* @readonly
*/
get allMessagesLoaded() {
return !this._discordie._messages.channelHasMore(this.id);
}
/**
* Creates an array of cached messages in this channel, sorted in order of
* arrival (message cache is sorted on message insertion, not when this
* getter is invoked).
*
* Returns an empty array if channel no longer exists.
*
* > **Note:** Message cache also includes deleted messages.
* > You can filter them by checking `IMessage.deleted` boolean.
* @returns {Array<IMessage>}
* @readonly
*/
get messages() {
return this._discordie.Messages.forChannel(this.id);
}
/**
* Makes a request to fetch messages for this channel.
*
* Discord API does not allow fetching more than 100 messages at once.
*
* Promise resolves with an Object with following structure:
* ```js
* {
* messages: Array<IMessage>,
* limit: Number, // same as parameter passed or default value
* before: String | null, // message id
* after: String | null // message id
* }
* ```
* @param {Number|null} [limit] - Default is 100
* @param {IMessage|String|null} [before] - Message or message id
* @param {IMessage|String|null} [after] - Message or message id
* @returns {Promise<Object, Error>}
* @example
* var guild = client.Guilds.find(g => g.name == "test");
* var channel = guild.generalChannel;
*
* // simple fetch:
* channel.fetchMessages().then(() => {
* console.log("[simple] messages in cache: " + channel.messages.length);
* });
*
* // fetching more than 100 messages into cache sequentially:
* fetchMessagesEx(channel, 420).then(() => {
* console.log("[extended] messages in cache: " + channel.messages.length);
* });
*
* // fetch more messages just like Discord client does
* function fetchMessagesEx(channel, left) {
* // message cache is sorted on insertion
* // channel.messages[0] will get oldest message
* var before = channel.messages[0];
* return channel.fetchMessages(Math.min(left, 100), before)
* .then(e => onFetch(e, channel, left));
* }
* function onFetch(e, channel, left) {
* if (!e.messages.length) return Promise.resolve();
* left -= e.messages.length;
* console.log(`Received ${e.messages.length}, left: ${left}`);
* if (left <= 0) return Promise.resolve();
* return fetchMessagesEx(channel, left);
* }
*/
fetchMessages(limit, before, after) {
if (!limit) limit = 100;
if (before) before = before.valueOf();
if (after) after = after.valueOf();
return new Promise((rs, rj) => {
rest(this._discordie).channels.getMessages(this.id, limit, before, after)
.then(e => {
e.messages = e.messages
.map(msg => this._discordie.Messages.get(msg.id));
return rs(e);
})
.catch(rj);
});
}
/**
* Creates an array of cached pinned messages in this channel.
*
* Pinned message cache is updated only if all pinned messages have been
* loaded with `ITextChannel.fetchPinned()`.
*
* Returns an empty array if channel no longer exists or if pinned messages
* have not been fetched yet.
* @returns {Array<IMessage>}
* @readonly
*/
get pinnedMessages() {
return this._discordie.Messages.forChannelPinned(this.id);
}
/**
* Makes a request to fetch pinned messages for this channel.
*
* Promise resolves with an Object with following structure:
* ```js
* {
* channelId: String,
* messages: Array<IMessage>
* }
* ```
* @returns {Promise<Object, Error>}
*/
fetchPinned() {
return new Promise((rs, rj) => {
rest(this._discordie).channels.getPinnedMessages(this.id)
.then(e => {
e.messages = e.messages
.map(msg => this._discordie.Messages.get(msg.id));
return rs(e);
})
.catch(rj);
});
}
/**
* Makes a request to send a message to this channel. Messages over 2000
* characters will be rejected by the server.
*
* Use `uploadFile` if you want to send a message with an attachment.
* @param {String|Array<String>} content
* Strings will be sent as is, arrays - joined with a newline character.
* @param {IUser|IGuildMember|Array<IUser>|Array<IGuildMember>} [mentions]
* Deprecated: left for backward compatibility.
* @param {boolean} [tts]
* @param {Object} [embed]
* Refer to [official API documentation](https://discordapp.com/developers/docs/resources/channel#embed-object)
* for embed structure description.
* @returns {Promise<IMessage, Error>}
* @example
* var guild = client.Guilds.find(g => g.name == "test");
* if (!guild) return console.log("invalid guild");
*
* var channel = guild.generalChannel;
*
* channel.sendMessage("regular message");
* channel.sendMessage("test with tts", true);
*
* var user = client.Users.find(u => u.username == "test");
* if (!user) return console.log("invalid user");
*
* channel.sendMessage("mentioning user " + user.mention);
* channel.sendMessage("@everyone or @here mention if you have permissions");
*
* channel.sendMessage("message with an embed", false, {
* color: 0x3498db,
* author: {name: "author name"},
* title: "This is an embed",
* url: "http://google.com",
* timestamp: "2016-11-13T03:43:32.127Z",
* fields: [{name: "some field", value: "some value"}],
* footer: {text: "footer text"}
* });
*/
sendMessage(content, mentions, tts, embed) {
if (Array.isArray(content)) content = content.join("\n");
if (typeof content !== "string") content = String(content);
if (!Array.isArray(mentions)) {
embed = tts;
tts = mentions;
mentions = [];
}
mentions = Utils.convertMentions(mentions);
return new Promise((rs, rj) => {
rest(this._discordie)
.channels.createMessage(this.id, content, mentions, tts, embed)
.then(msg => rs(this._discordie.Messages.get(msg.id)))
.catch(rj);
});
}
/**
* Makes a request to send typing status for this channel.
*
* Discord client displays it for 10 seconds, sends every 5 seconds.
* Stops showing typing status if receives a message from the user.
* @returns {Promise}
*/
sendTyping() {
return rest(this._discordie).channels.postTyping(this.id);
}
/**
* Makes a request to upload data to this channel.
* Images require a `filename` with a valid extension to actually be uploaded.
* @param {Buffer|ReadableStream|String} readableStream
* Data to upload or filename as a string
* @param {String} filename
* Actual filename to show, required for non-string `readableStream`
* @param {String} [content] - Additional comment message for attachment
* @param {boolean} [tts]
* @returns {Promise<IMessage, Error>}
* @example
* channel.uploadFile(fs.readFileSync("test.png"), "test.png"); // Buffer
* channel.uploadFile(fs.createReadStream("test.png"), "test.png"); // Stream
* channel.uploadFile("test.png"); // File
* channel.uploadFile("test.png", null, "file with message");
* channel.uploadFile("test.png", null, "file with message and tts", true);
*/
uploadFile(readableStream, filename, content, mentions, tts) {
if (mentions === true) {
tts = mentions;
mentions = [];
}
mentions = Utils.convertMentions(mentions);
return new Promise((rs, rj) => {
rest(this._discordie)
.channels.uploadFile(
this.id, readableStream, filename,
content, mentions, tts
)
.then(msg => rs(this._discordie.Messages.get(msg.id)))
.catch(rj);
});
}
}
module.exports = ITextChannel;

285
node_modules/discordie/lib/interfaces/IUser.js generated vendored Normal file
View File

@@ -0,0 +1,285 @@
"use strict";
const IBase = require("./IBase");
const IPermissions = require("./IPermissions");
const Utils = require("../core/Utils");
const User = require("../models/User");
const Constants = require("../Constants");
const Endpoints = Constants.Endpoints;
/**
* @interface
* @model User
* @extends IBase
* @description
* Object representing a user, user-bot, or a webhook-bot.
*
* Both bots have the `bot` property set to true.
*
* Each webhook-bot object is unique to a message and may have different
* username and avatar, the user collection only contains last seen data.
* Use `IMessage.author` to get a message-specific object.
*/
class IUser extends IBase {
constructor(discordie, userId, webhookUser) {
super();
Utils.definePrivate(this, {
_discordie: discordie,
_userId: userId,
_webhookUser: webhookUser
});
if (this.constructor == IUser) {
Object.freeze(this);
}
}
/**
* Gets date and time the account was registered (created) at.
* @returns {Date}
* @readonly
*/
get registeredAt() {
return new Date(Utils.timestampFromSnowflake(this.id));
}
/**
* Gets current JPG or GIF avatar URL.
* @returns {String|null}
* @readonly
*/
get avatarURL() {
const avatar = this.avatar;
if (!avatar) return null;
var fmt = "jpg";
if (avatar.length >= 2 && avatar[0] === "a" && avatar[1] === "_") {
fmt = "gif";
}
return Constants.CDN_ENDPOINT + Endpoints.CDN_AVATAR(this.id, avatar, fmt);
}
/**
* Gets current JPG avatar URL.
* @returns {String|null}
* @readonly
*/
get staticAvatarURL() {
const avatar = this.avatar;
if (!avatar) return null;
return Constants.CDN_ENDPOINT + Endpoints.CDN_AVATAR(this.id, avatar);
}
/**
* Current status of the user.
* @returns {String}
* @readonly
*/
get status() {
return this._discordie._presences.getStatus(this.id);
}
/**
* Current game the user is playing.
* @returns {Object|null}
* @readonly
*/
get game() {
return this._discordie._presences.getGame(this.id);
}
/**
* Name of the current game the user is playing.
* @returns {String|null}
* @readonly
*/
get gameName() {
return this.game ? this.game.name : null;
}
/**
* Previous status of the user.
* @returns {String}
* @readonly
*/
get previousStatus() {
return this._discordie._presences.getPreviousStatus(this.id);
}
/**
* Previous game the user was playing.
* @returns {Object|null}
* @readonly
*/
get previousGame() {
return this._discordie._presences.getPreviousGame(this.id);
}
/**
* Name of the previous game the user was playing.
* @returns {String|null}
* @readonly
*/
get previousGameName() {
return this.previousGame ? this.previousGame.name : null;
}
/**
* Checks whether the user is mentioned in a `message`.
* @param {IMessage} message
* @param {boolean} ignoreImplicitMentions
* @returns {boolean}
*/
isMentioned(message, ignoreImplicitMentions) {
const IMessage = require("./IMessage");
if (!message) return false;
if (!(message instanceof IMessage)) {
if (message.id) message = message.id;
message = this._discordie.Messages.get(message);
if (!message) return false;
}
return message.mentions.some(mention => mention.id === this.id) ||
(ignoreImplicitMentions === true ? false : message.mention_everyone);
}
/**
* Opens or gets existing Direct Message channel.
* @returns {Promise<IDirectMessageChannel, Error>}
*/
openDM() {
return this._discordie.DirectMessageChannels.getOrOpen(this);
}
/**
* Attempts to get a guild member interface, returns null if this user is not
* a member of the `guild` or `guild` is not in cache.
* @param {IGuild|String} guild
* @returns {IGuildMember|null}
*/
memberOf(guild) {
return this._discordie.Users.getMember(guild.valueOf(), this.id) || null;
}
/**
* Resolves permissions for user in `context`.
*
* Returns a helper object with getter boolean properties.
*
* Throws:
*
* - ** `"user must be an instance of IUser"` **
*
* If the method was called without binding to a `IUser` object
* (as a function).
*
* - ** `"context must be an instance of IChannel or IGuild"` **
*
* If context type is invalid.
*
* - ** `"Invalid context"` **
*
* If context object no longer exists in cache.
*
* - ** `"User is not a member of the context"` **
*
* If this user is not a member of the guild or
* guild the channel belongs to.
*
* See documentation of `IPermissions` for list of possible permissions.
* @param {IChannel|IGuild} context
* @returns {IPermissions}
* @example
* const guild = client.Guilds.find(g => g.name == "test");
* const channel = guild.channels.find(c => c.name == "restricted");
* const user = guild.members.find(m => m.username == "testuser");
*
* const guildPerms = user.permissionsFor(guild); // resolves to role permissions
* const channelPerms = user.permissionsFor(channel); // resolves to channel permissions
*
* console.log(guildPerms.General.MANAGE_ROLES); // false
* console.log(guildPerms.Text.READ_MESSAGES); // true
* // The `restricted` channel has `READ_MESSAGES` denied
* console.log(channelPerms.Text.READ_MESSAGES); // false
*/
permissionsFor(context) {
return IPermissions.resolve(this, context);
}
/**
* Resolves permissions for user in `context` and checks if
* user has `permission`.
*
* See `IUser.permissionsFor` method for list of throwable errors.
*
* See documentation of `IPermissions` for full list of possible permissions.
* @param {Number} permission - One or multiple permission bits
* @param {IChannel|IGuild} context
* @returns {boolean}
* @example
* const guild = client.Guilds.find(g => g.name == "test");
* const channel = guild.channels.find(c => c.name == "node_discordie");
* const user = guild.members.find(m => m.username == "testuser");
* user.can(Discordie.Permissions.General.KICK_MEMBERS, guild); // resolves to role permissions
* user.can(Discordie.Permissions.Text.READ_MESSAGES, channel); // resolves to channel permissions
*/
can(permission, context) {
return (IPermissions.resolveRaw(this, context) & permission) != 0;
}
/**
* Gets the first voice channel that member of `guild` currently in.
* @param {IGuild|String|null} guild
* Guild or an id string, null for private call
* (call channel is only available if recipient is in call with current user)
* @returns {IVoiceChannel|null}
*/
getVoiceChannel(guild) {
const state = this._discordie._voicestates.getUserStateInGuild(
(guild ? guild.valueOf() : null), this.id
);
if (!state) return null;
return this._discordie.Channels.get(state.channel_id);
}
/**
* Creates a mention from this user's id.
* @returns {String}
* @readonly
* @example
* channel.sendMessage(user.mention + ", example mention");
*/
get mention() {
return `<@${this.id}>`;
}
/**
* Creates a nickname mention from this user's id.
* @returns {String}
* @readonly
*/
get nickMention() {
return `<@!${this.id}>`;
}
/**
* Returns true if this is a non-user bot object such as webhook-bot.
* @return {boolean}
*/
get isWebhook() {
const nonUserBot =
this.bot && this.discriminator === Constants.NON_USER_BOT_DISCRIMINATOR;
return !!this._webhookUser || nonUserBot;
}
}
IUser._inherit(User, function modelPropertyGetter(key) {
if (this._webhookUser) return this._webhookUser[key];
return this._discordie._users.get(this._userId)[key];
});
module.exports = IUser;

View File

@@ -0,0 +1,256 @@
"use strict";
const Constants = require("../Constants");
const StatusTypes = Constants.StatusTypes;
const Permissions = Constants.Permissions;
const ICollectionBase = require("./ICollectionBase");
const IGuild = require("./IGuild");
const IGuildMember = require("./IGuildMember");
const IChannel = require("./IChannel");
const IUser = require("./IUser");
const IDirectMessageChannel = require("./IDirectMessageChannel");
const Utils = require("../core/Utils");
/**
* @interface
* @extends ICollectionBase
*/
class IUserCollection extends ICollectionBase {
constructor(discordie, valuesGetter, valueGetter) {
super({
valuesGetter: valuesGetter,
valueGetter: valueGetter,
itemFactory: (id) => new IUser(this._discordie, id)
});
Utils.definePrivate(this, {_discordie: discordie});
}
/**
* Request members and wait until cache populates for all guilds or
* an array of guilds.
* Request is made over gateway websocket.
*
* Will request members for all guilds if no arguments passed.
*
* By default Discord sends only online members if there are more than 250
* (offline and online total) joined in a guild.
*
* Returned promise will resolve when all members have been fetched.
* Returned promise will reject if all or some members have not been received
* within 60 seconds or primary gateway websocket disconnected.
*
* If all members for chosen guilds are already in cache - returns a
* resolved promise.
*
* > **Note:** When guilds become unavailable or deleted
* > (events `GUILD_UNAVAILABLE` and `GUILD_DELETE`)
* > all members will also be deleted from cache.
* @param {IGuild|String|Array<IGuild|String>} [guilds]
* @returns {Promise}
* @example
* client.Users.fetchMembers()
* .then(() => console.log("Received members for all guilds"));
*
* client.Users.fetchMembers([guild1, guild2, guild3])
* .then(() => console.log("Received members for 3 guilds"));
*
* var p1 = client.Users.fetchMembers(guild1);
* p1.catch(err => console.log("Failed to receive guild1 members: " + err));
* var p2 = client.Users.fetchMembers(guild2);
* var p3 = client.Users.fetchMembers(guild3);
* Promise.all([p1, p2, p3])
* .then(() => console.log("Received members for 3 guilds"));
* .catch(err => console.log("Failed to receive some members: " + err));
*/
fetchMembers(guilds) {
if (guilds) {
if (guilds.map) guilds = guilds.map(guild => guild.valueOf());
else guilds = [guilds.valueOf()];
}
return this._discordie._members.fetchMembers(guilds);
}
/**
* Gets a `IGuildMember` for specified `user` of `guild`.
*
* Returns null if the user is not a member of the guild.
* @param {IGuild|String} guild - Guild or an id string
* @param {IUser|String} user - User or an id string
* @returns {IGuildMember|null}
*/
getMember(guild, user) {
guild = guild.valueOf();
user = user.valueOf();
var member = this._discordie._members.getMember(guild, user);
if (!member) return null;
return this._getOrCreateInterface(member,
() => new IGuildMember(this._discordie, user, guild)
);
}
/**
* Creates an array of `IGuildMember` for `guild`.
* @param {IGuild|String} guild - Guild or an id string
* @returns {Array<IGuildMember>}
*/
membersForGuild(guild) {
guild = guild.valueOf();
const members = [];
const guildMembers = this._discordie._members.get(guild);
if (!guildMembers) return members;
for (var member of guildMembers.values()) {
members.push(this._getOrCreateInterface(member,
() => new IGuildMember(this._discordie, member.id, guild)
));
}
return members;
}
/**
* Creates an array of `IGuildMember` that have permissions to read `channel`.
*
* > **Note:** This method computes permissions for all members and may be CPU
* > intensive for large guilds.
* @param {ITextChannel|String} channel - Channel or an id string
* @returns {Array<IGuildMember>}
*/
membersForChannel(channel) {
if (!(channel instanceof IChannel))
channel = this._discordie.Channels.get(channel);
if (!channel) return [];
const guild = channel.guild;
if (!guild) throw new Error("Channel does not exist");
const guildMembers = channel.guild.members;
if (!guildMembers) return [];
return guildMembers.filter(m =>
m.can(Permissions.Text.READ_MESSAGES, channel)
);
}
/**
* Creates an array of `IGuildMember` containing
* active members in a voice `channel`.
* @param {IVoiceChannel|String} channel - Channel or an id string
* @returns {Array<IGuildMember>}
*/
membersInVoiceChannel(channel) {
if (!(channel instanceof IChannel))
channel = this._discordie.Channels.get(channel);
if (!channel) return [];
const members = [];
const guildMembers = this._discordie._members.get(channel.guild_id);
if (!guildMembers) return members;
const userMap = this._discordie._voicestates.getStatesInChannel(channel.id);
for (var id of userMap.keys()) {
const member = guildMembers.get(id);
if (!member) continue;
members.push(this._getOrCreateInterface(member,
() => new IGuildMember(this._discordie, id, channel.guild_id)
));
}
return members;
}
/**
* Creates an array of `IUser` containing users in a private voice channel.
* @param {IDirectMessageChannel|String} channel - Channel or an id string
* @returns {Array<IUser>}
*/
usersInCall(channel) {
if (!(channel instanceof IDirectMessageChannel))
channel = this._discordie.DirectMessageChannels.get(channel);
if (!channel) return [];
const users = [];
const userMap = this._discordie._voicestates.getStatesInChannel(channel.id);
for (var id of userMap.keys()) {
const user = this.get(id);
if (user) users.push(user);
}
return users;
}
/**
* Creates an array of `IGuildMember` for `guild` that are currently online.
* @param {IGuild|String} guild - Guild or an id string
* @returns {Array<IGuildMember>}
*/
onlineMembersForGuild(guild) {
guild = guild.valueOf();
const members = [];
const presences = this._discordie._presences;
const guildMembers = this._discordie._members.get(guild);
if (!guildMembers) return members;
for (var member of guildMembers.values()) {
if (presences.getStatus(member.id) != StatusTypes.OFFLINE) {
members.push(this._getOrCreateInterface(member,
() => new IGuildMember(this._discordie, member.id, guild)
));
}
}
return members;
}
/**
* Creates an array of `IGuildMember` that
* have permissions to read `channel` and currently online.
*
* > **Note:** This method computes permissions for all members and may be CPU
* > intensive for large guilds.
* @param {ITextChannel|String} channel - Channel or an id string
* @returns {Array<IGuildMember>}
*/
onlineMembersForChannel(channel) {
return this.membersForChannel(channel).filter(
m => m.status != StatusTypes.OFFLINE
);
}
/**
* Creates an array of `IGuildMember` for `guild` that are currently offline.
*
* Does not guarantee every offline member unless
* `IUserCollection.fetchMembers` has been called for the `guild`.
*
* @param {IGuild|String} guild - Guild or an id string
* @returns {Array<IGuildMember>}
*/
offlineMembersForGuild(guild) {
guild = guild.valueOf();
const members = [];
const presences = this._discordie._presences;
const guildMembers = this._discordie._members.get(guild);
if (!guildMembers) return members;
for (var member of guildMembers.values()) {
if (presences.getStatus(member.id) == StatusTypes.OFFLINE) {
members.push(this._getOrCreateInterface(member,
() => new IGuildMember(this._discordie, member.id, guild)
));
}
}
return members;
}
/**
* Creates an array of `IGuildMember` that
* have permissions to read `channel` and currently offline.
*
* Does not guarantee every offline member unless
* `IUserCollection.fetchMembers` has been called for the guild the `channel`
* belongs to.
*
* > **Note:** This method computes permissions for all members and may be CPU
* > intensive for large guilds.
* @param {ITextChannel|String} channel - Channel or an id string
* @returns {Array<IGuildMember>}
*/
offlineMembersForChannel(channel) {
return this.membersForChannel(channel).filter(
m => m.status == StatusTypes.OFFLINE
);
}
}
module.exports = IUserCollection;

113
node_modules/discordie/lib/interfaces/IVoiceChannel.js generated vendored Normal file
View File

@@ -0,0 +1,113 @@
"use strict";
const Utils = require("../core/Utils");
const User = require("../models/User");
const IChannel = require("./IChannel");
/**
* @interface
* @model Channel
* @extends IChannel
*/
class IVoiceChannel extends IChannel {
constructor(discordie, channelId) {
super(discordie, channelId);
}
/**
* Creates an array of members joined in this voice channels.
* @returns {Array<IGuildMember>}
* @readonly
*/
get members() {
return this._discordie.Users.membersInVoiceChannel(this);
}
/**
* Checks whether current user is in this voice channel.
* @returns {boolean}
* @readonly
*/
get joined() {
const vc = this._discordie.VoiceConnections;
const pendingChannel = vc.getPendingChannel(this.guild_id);
return !!(pendingChannel && pendingChannel === this._channelId);
}
/**
* Joins this voice channel.
* Creates a new voice connection if there are no active connections for
* this channels' guild.
*
* > **Note:** One account can be only in one channel per guild.
* > Promise will resolve instantly and contain the same instance
* > if connection to the server is already established.
*
* If there is a pending connection for the guild this channel belongs to,
* it will return the same promise.
*
* Checks permissions locally and returns a rejected promise with:
*
* - **`Error "Missing permission"`** if `Voice.CONNECT` permission is denied.
*
* - **`Error "Channel is full"`** if `Voice.MOVE_MEMBERS` permission is
* denied and channel is full.
*
* Returns a rejected promise with **`Error "Channel does not exist"`** if
* guild is unavailable or channel does not exist in cache.
*
* Returned promise can be cancelled or rejected: see `VOICE_DISCONNECTED`
* event for more info.
*
* @param {boolean} [selfMute]
* @param {boolean} [selfDeaf]
* @returns {Promise<VoiceConnectionInfo, Error|Number>}
*/
join(selfMute, selfDeaf) {
selfMute = !!selfMute;
selfDeaf = !!selfDeaf;
if (!this._valid)
return Promise.reject(new Error("Channel does not exist"));
// check permissions locally
// since server silently drops invalid voice state updates
if (!this.joined) {
const permissions = this._discordie.User.permissionsFor(this);
if (!permissions.Voice.CONNECT)
return Promise.reject(new Error("Missing permission"));
if (this.user_limit > 0) {
const states = this._discordie._voicestates.getStatesInChannel(this.id);
if (states.size >= this.user_limit) {
if (!permissions.Voice.MOVE_MEMBERS)
return Promise.reject(new Error("Channel is full"));
}
}
}
const vc = this._discordie.VoiceConnections;
return vc._getOrCreate(this.guild_id, this._channelId, selfMute, selfDeaf);
}
/**
* Leaves this voice channel if joined.
*/
leave() {
const info = this.getVoiceConnectionInfo();
if (info) return info.voiceConnection.disconnect();
this._discordie.VoiceConnections
.cancelIfPending(this.guild_id, this._channelId);
}
/**
* Retrieves `VoiceConnectionInfo` for this voice channel.
* @returns {VoiceConnectionInfo|null}
*/
getVoiceConnectionInfo() {
return this._discordie.VoiceConnections.getForChannel(this._channelId);
}
}
module.exports = IVoiceChannel;

View File

@@ -0,0 +1,215 @@
"use strict";
const Utils = require("../core/Utils");
const ExternalEncoderFactory = require("../voice/players/ExternalEncoderFactory");
function initInterface(_interface, _options) {
if (!_interface)
throw new TypeError("Invalid interface");
if (_options) _interface.initialize(_options);
return _interface;
}
/**
* @interface
*/
class IVoiceConnection {
constructor(discordie, gatewaySocket, voiceSocket) {
// todo: ssrc to user
//discordie.Dispatcher.on(Events.VOICE_SPEAKING, e => {
// if (e.socket == voicews) this.emit("speaking", - bla - resolve ssrc)
//});
this._gatewaySocket = gatewaySocket;
this._voiceSocket = voiceSocket;
this._lastChannelId = null;
this._discordie = discordie;
Utils.privatify(this);
if (!this.canStream)
throw new Error("Failed to create IVoiceConnection");
}
dispose() {
this._lastChannelId = this.channelId;
this._gatewaySocket = null;
this._voiceSocket = null;
}
/**
* Checks whether this voice connection is no longer valid.
* @returns {boolean}
* @readonly
*/
get disposed() {
return !this._voiceSocket;
}
/**
* Checks whether this voice connection is fully initialized.
* @returns {boolean}
* @readonly
*/
get canStream() {
return this._voiceSocket && this._voiceSocket.canStream;
}
/**
* Gets channel of this voice connection.
*
* Returns last channel it was connected to if voice connection has been
* disposed.
* Returns null if guild became unavailable or channel doesn't exist in cache.
* @returns {IChannel|null}
* @readonly
*/
get channel() {
const channelId = this.channelId;
if (!channelId) return null;
return this._discordie.Channels.get(channelId);
}
/**
* Gets channel id of this voice connection.
*
* Returns last channel id it was connected to if voice connection has been
* disposed.
* @returns {String|null}
* @readonly
*/
get channelId() {
if (this._lastChannelId) return this._lastChannelId;
const gw = this._gatewaySocket;
if (!gw) return null;
const voiceState = gw.voiceStates.get(this.guildId);
if (!voiceState) return null;
return voiceState.channelId;
}
/**
* Gets guild of this voice connection.
*
* Returns null if this is a private call, or guild became unavailable or
* doesn't exist in cache.
* @returns {IGuild|null}
* @readonly
*/
get guild() {
const gw = this._gatewaySocket;
if (!gw) return null;
return this._discordie.Guilds.get(this.guildId);
}
/**
* Gets guild id of this voice connection.
*
* Returns null if this is a private call.
* @returns {String|null}
* @readonly
*/
get guildId() {
return this._voiceSocket ? this._voiceSocket.guildId : null;
}
/**
* Resolves a user object from source id assigned to this voice connection.
* @param {Number} ssrc
* @returns {IUser}
*/
ssrcToUser(ssrc) {
if (this.disposed) return null;
const userid = this._discordie._voicestates.ssrcToUserId(this, ssrc);
if (!userid) return null;
return this._discordie.Users.get(userid) || null;
}
/**
* Resolves a member object from source id assigned to this voice connection.
* @param {Number} ssrc
* @returns {IGuildMember}
*/
ssrcToMember(ssrc) {
if (this.disposed) return null;
const userid = this._discordie._voicestates.ssrcToUserId(this, ssrc);
if (!userid) return null;
return this._discordie.Users.getMember(this.guild, userid);
}
/**
* Initializes encoder and gets stream for this voice connection.
*
* Calls without arguments return existing encoder without reinitialization.
*
* See `AudioEncoder.initialize()` method for list of options.
* @param [options]
* @returns {AudioEncoderStream}
*/
getEncoderStream(options) {
const encoder = this.getEncoder(options);
if (!encoder) return null;
return encoder._stream;
}
createDecoderStream(options) {
}
/**
* Creates an external encoder.
*
* Accepts options object with `type` property (default `{type: "ffmpeg"}`).
* Each type supports additional options.
* See docs for returned classes for usage info.
* @param {Object} [options]
* @returns {FFmpegEncoder|
* OggOpusPlayer|
* WebmOpusPlayer}
*/
createExternalEncoder(options) {
if (!this._voiceSocket) return null;
return ExternalEncoderFactory.create(this, options);
}
/**
* Initializes encoder instance for this voice connection.
*
* Calls without arguments return existing encoder without reinitialization.
*
* See `AudioEncoder.initialize()` method for list of options.
* @param {Object} [options]
* @returns {AudioEncoder}
*/
getEncoder(options) {
if (!this._voiceSocket) return null;
return initInterface(this._voiceSocket.audioEncoder, options);
}
/**
* Initializes decoder instance for this voice connection.
*
* Calls without arguments return existing decoder without reinitialization.
* @param {Object} [options]
* @returns {AudioDecoder}
*/
getDecoder(options) {
if (!this._voiceSocket) return null;
return initInterface(this._voiceSocket.audioDecoder, options);
}
/**
* Disconnects this voice connection.
*/
disconnect() {
this._disconnect();
}
_disconnect(error) {
if (!this._gatewaySocket) return;
if (this.guildId || this.channelId) {
this._gatewaySocket.disconnectVoice(this.guildId, false, error);
}
}
}
module.exports = IVoiceConnection;

View File

@@ -0,0 +1,230 @@
"use strict";
const Utils = require("../core/Utils");
const rest = require("../networking/rest");
function webhookToId(webhook) {
if (!webhook) throw new TypeError("Param 'webhook' is invalid");
if (typeof webhook === "string") return webhook;
return webhook.id;
}
/**
* @interface
* @description
* Wrapper for webhook methods.
*
* Example webhook object:
* ```js
* {
* "name": "abc",
* "channel_id": "78231142373424474",
* "token": "EtuNYHGkElBlE7BE266Jk...NzHvccXaUCQUOY64NbFWz9zbQ",
* "avatar": null,
* "guild_id": "78231498370026660",
* "id": "232330225768333313",
* "user": {
* "username": "testuser",
* "discriminator": "3273",
* "id": "000000000000000000",
* "avatar": null
* }
* }
* ```
*/
class IWebhookManager {
constructor(discordie) {
this._discordie = discordie;
Utils.privatify(this);
Object.freeze(this);
}
/**
* **Requires logging in with an API token.**
*
* Makes a request to get webhook objects for the specified guild.
* @param {IGuild|String} guild
* @return {Promise<Array<Object>, Error>}
*/
fetchForGuild(guild) {
guild = guild.valueOf();
return rest(this._discordie).webhooks.getGuildWebhooks(guild);
}
/**
* **Requires logging in with an API token.**
*
* Makes a request to get webhook objects for the specified channel.
* @param {IChannel|String} channel
* @return {Promise<Array<Object>, Error>}
*/
fetchForChannel(channel) {
channel = channel.valueOf();
return rest(this._discordie).webhooks.getChannelWebhooks(channel);
}
/**
* **Requires logging in with an API token.**
*
* Makes a request to create a webhook for the channel.
*
* Promise resolves with a webhook object.
* @param {IChannel} channel
* @param {Object} options
* Object with properties `{name: String, avatar: Buffer|String|null}`.
*
* String avatars must be base64 data-url encoded.
* @return {Promise<Object, Error>}
*/
create(channel, options) {
channel = channel.valueOf();
options = options || {};
if (options.avatar instanceof Buffer) {
options.avatar = Utils.imageToDataURL(options.avatar);
}
return rest(this._discordie).webhooks.createWebhook(channel, options);
}
/**
* Makes a request to fetch a webhook object.
*
* Promise resolves with a webhook object (does not contain a `user` object
* if fetched with `token` param).
* @param {Object|String} webhook - Webhook object or id.
* @param {String} token
* Webhook token, not required if currently logged in
* with an account that has access to the webhook.
* @return {Promise<Object, Error>}
*/
fetch(webhook, token) {
const webhookId = webhookToId(webhook);
return rest(this._discordie).webhooks.getWebhook(webhookId, token);
}
/**
* Makes a request to edit the specified webhook.
*
* Promise resolves with a webhook object (does not contain a `user` object
* if edited with `token` param).
* @param {Object|String} webhook - Webhook object or id.
* @param {String} token
* Webhook token, not required and can be set to null if currently logged in
* with an account that has access to the webhook.
* @param {Object} options
* Object with properties `{name: String, avatar: Buffer|String|null}`.
*
* String avatars must be base64 data-url encoded.
* @return {Promise<Object, Error>}
*/
edit(webhook, token, options) {
const webhookId = webhookToId(webhook);
options = options || {};
if (options.avatar instanceof Buffer) {
options.avatar = Utils.imageToDataURL(options.avatar);
}
return rest(this._discordie)
.webhooks.patchWebhook(webhookId, token, options);
}
/**
* Makes a request to delete the specified webhook.
* @param {Object|String} webhook - Webhook object or id.
* @param {String} token
* Webhook token, not required and can be set to null if currently logged in
* with an account that has access to the webhook.
* @return {Promise}
*/
delete(webhook, token) {
const webhookId = webhookToId(webhook);
return rest(this._discordie)
.webhooks.deleteWebhook(webhookId, token);
}
/**
* Makes a request to execute the specified webhook.
*
* > **Note:** Embeds in file uploads are not supported.
* @param {Object|String} webhook - Webhook object or id.
* @param {String} token - Required unless webhook object contains token.
* @param {Object} options
* Refer to [official API documentation](https://discordapp.com/developers/docs/)
* for more information.
* @param {boolean} [wait]
* Wait for server confirmation of message delivery,
* returned promise will contain a raw message object or an error if message
* creation failed.
* @return {Promise}
* @example
* const webhookId = "232330225768333313";
* const token = "EtuNYHGkElBlE7BE266Jk...NzHvccXaUCQUOY64NbFWz9zbQ";
* client.Webhooks.execute(webhookId, token, {content: "text message"});
* client.Webhooks.execute(webhookId, token, {
* // text message
* content: "text message",
* username: "Different Username",
* avatar_url: "https://localhost/test.png",
* tts: false,
* embeds: [{
* color: 0x3498db,
* author: {name: "who dis"},
* title: "This is an embed",
* description: "Nobody will read this anyway",
* url: "http://google.com",
* timestamp: "2016-10-03T03:32:31.205Z",
* fields: [{name: "some field", value: "some value"}],
* footer: {text: "footer text"}
* }]
* });
*
* client.Webhooks.execute(webhookId, token, {
* // file upload
* content: "text message",
* username: "Different Username",
* file: fs.readFileSync("test.png"),
* filename: "test.png"
* });
*/
execute(webhook, token, options, wait) {
const webhookId = webhookToId(webhook);
token = token || webhook.token;
return rest(this._discordie)
.webhooks.executeWebhook(webhookId, token, options, wait);
}
/**
* Makes a request to execute the specified webhook with slack-compatible
* options.
* @param {Object|String} webhook - Webhook object or id.
* @param {String} token - Required unless webhook object contains token.
* @param {Object} options
* Refer to [Slack's documentation](https://api.slack.com/incoming-webhooks)
* for more information.
*
* Discord does not support Slack's `channel`, `icon_emoji`, `mrkdwn`,
* or `mrkdwn_in` properties.
* @param {boolean} [wait]
* Wait for server confirmation of message delivery, defaults to true.
* When set to false, a message that is not saved does not return an error.
* @return {Promise}
* @example
* const webhookId = "232330225768333313";
* const token = "EtuNYHGkElBlE7BE266Jk...NzHvccXaUCQUOY64NbFWz9zbQ";
* client.Webhooks.executeSlack(webhookId, token, {
* text: "text message",
* username: "Different Username"
* });
*/
executeSlack(webhook, token, options, wait) {
const webhookId = webhookToId(webhook);
token = token || webhook.token;
return rest(this._discordie)
.webhooks.executeSlackWebhook(webhookId, token, options, wait);
}
}
module.exports = IWebhookManager;

44
node_modules/discordie/lib/models/AuthenticatedUser.js generated vendored Normal file
View File

@@ -0,0 +1,44 @@
"use strict";
const Constants = require("../Constants");
const StatusTypes = Constants.StatusTypes;
const BaseModel = require("./BaseModel");
/**
* @kind model
* @alias AuthenticatedUser
*/
const BaseAuthenticatedUser = {
/** @returns {String|null} */
id: null,
/** @returns {String|null} */
username: "",
/** @returns {String|null} */
discriminator: null,
/** @returns {String|null} */
email: null,
/** @returns {boolean|null} */
verified: false,
/** @returns {String|null} */
status: StatusTypes.ONLINE,
/** @returns {String|null} */
avatar: null,
/** @returns {String|null} */
token: null,
/** @returns {boolean|null} */
bot: false,
/** @returns {boolean|null} */
mfa_enabled: false,
/** @returns {Object|null} */
game: null, // clientside cache
/** @returns {boolean|null} */
afk: false // clientside cache
};
class AuthenticatedUser extends BaseModel {
constructor(def) {
super(BaseAuthenticatedUser, def);
}
}
module.exports = AuthenticatedUser;

27
node_modules/discordie/lib/models/BaseModel.js generated vendored Normal file
View File

@@ -0,0 +1,27 @@
"use strict";
class BaseModel {
constructor(base, def) {
for (let k in base) {
this[k] = ((def && def.hasOwnProperty(k)) ? def[k] : base[k]);
}
Object.freeze(this);
}
merge(def) {
if (!def) {
return this;
}
let merged = {};
for (let k in this) {
merged[k] = this[k];
}
for (let k in def) {
if (merged.hasOwnProperty(k)) {
merged[k] = def[k];
}
}
return new this.constructor(merged);
}
}
module.exports = BaseModel;

29
node_modules/discordie/lib/models/Call.js generated vendored Normal file
View File

@@ -0,0 +1,29 @@
"use strict";
const Constants = require("../Constants");
const BaseModel = require("./BaseModel");
/**
* @kind model
* @alias Call
*/
const BaseCall = {
/** @returns {String|null} */
channel_id: null,
/** @returns {String|null} */
message_id: null,
/** @returns {String|null} */
region: null,
/** @returns {Array<String>|null} */
ringing: [],
/** @returns {boolean|null} */
unavailable: false
};
class Call extends BaseModel {
constructor(def) {
super(BaseCall, def);
}
}
module.exports = Call;

44
node_modules/discordie/lib/models/Channel.js generated vendored Normal file
View File

@@ -0,0 +1,44 @@
"use strict";
const Constants = require("../Constants");
const ChannelTypes = Constants.ChannelTypes;
const BaseModel = require("./BaseModel");
/**
* @kind model
* @alias Channel
*/
const BaseChannel = {
/** @returns {String|null} */
id: null,
/** @returns {String|null} */
name: "<not initialized>",
/** @returns {String|null} */
topic: "",
/** @returns {Number|null} */
position: 0,
/** @returns {Number|null} */
type: ChannelTypes.GUILD_TEXT,
/** @returns {String|null} */
guild_id: null,
/** @returns {Set|null} */
recipients: new Set(),
/** @returns {Array|null} */
permission_overwrites: [],
/** @returns {Number|null} */
bitrate: 0,
/** @returns {Number|null} */
user_limit: 0,
/** @returns {String|null} */
owner_id: null,
/** @returns {String|null} */
icon: null
};
class Channel extends BaseModel {
constructor(def) {
super(BaseChannel, def);
}
}
module.exports = Channel;

53
node_modules/discordie/lib/models/Guild.js generated vendored Normal file
View File

@@ -0,0 +1,53 @@
"use strict";
const Constants = require("../Constants");
const BaseModel = require("./BaseModel");
/**
* @kind model
* @alias Guild
*/
const BaseGuild = {
/** @returns {String|null} */
id: null,
/** @returns {String|null} */
name: null,
/** @returns {String|null} */
owner_id: null,
/** @returns {String|null} */
icon: null,
/** @returns {String|null} */
splash: null,
/** @returns {Set|null} */
features: new Set(),
/** @returns {Array<Object>|null} */
emojis: [],
/** @returns {Number|null} */
default_message_notifications: 0,
/** @returns {Map|null} */
roles: new Map(),
/** @returns {String|null} */
afk_channel_id: null,
/** @returns {Number|null} */
afk_timeout: null,
/** @returns {Number|null} */
verification_level: 0,
/** @returns {String|null} */
region: null,
/** @returns {Number|null} */
member_count: 0,
/** @returns {Boolean|null} */
large: false,
/** @returns {Number|null} */
mfa_level: 0,
/** @returns {String|null} */
joined_at: ""
};
class Guild extends BaseModel {
constructor(def) {
super(BaseGuild, def);
}
}
module.exports = Guild;

37
node_modules/discordie/lib/models/GuildMember.js generated vendored Normal file
View File

@@ -0,0 +1,37 @@
"use strict";
const Constants = require("../Constants");
const BaseModel = require("./BaseModel");
/**
* @kind model
* @alias GuildMember
*/
const BaseGuildMember = {
/** @returns {String|null} */
id: null,
/** @returns {String|null} */
guild_id: null,
/** @returns {String|null} */
nick: null,
/** @returns {Array|null} */
roles: [],
/** @returns {boolean|null} */
mute: false,
/** @returns {boolean|null} */
deaf: false,
/** @returns {boolean|null} */
self_mute: false,
/** @returns {boolean|null} */
self_deaf: false,
/** @returns {String|null} */
joined_at: ""
};
class GuildMember extends BaseModel {
constructor(def) {
super(BaseGuildMember, def);
}
}
module.exports = GuildMember;

75
node_modules/discordie/lib/models/Message.js generated vendored Normal file
View File

@@ -0,0 +1,75 @@
"use strict";
const Constants = require("../Constants");
const MessageTypes = Constants.MessageTypes;
const BaseModel = require("./BaseModel");
/**
* @kind model
* @alias Message
*/
const BaseMessage = {
/** @returns {String|null} */
id: null,
/** @returns {Number|null} */
type: MessageTypes.DEFAULT,
/** @returns {String|null} */
channel_id: null,
/** @returns {User|null} */
author: null,
/** @returns {String|null} */
content: "",
/** @returns {Array|null} */
attachments: [],
/** @returns {Array|null} */
embeds: [],
/** @returns {Array|null} */
mentions: [],
/** @returns {Array|null} */
mention_roles: [],
/** @returns {boolean|null} */
mention_everyone: false,
/** @returns {boolean|null} */
tts: false,
/** @returns {String|null} */
timestamp: "",
/** @returns {String|null} */
edited_timestamp: null,
/** @returns {String|null} */
nonce: null,
/** @returns {String|null} */
webhook_id: null,
/** @returns {Array|null} */
reactions: [],
/** @returns {boolean|null} */
pinned: false,
/**
* Raw MessageCall object:
*
* ```js
* {
* // Array of user ids participating in this call,
* // only used to check if the call was missed
* participants: [ "108721394061205504" ], // Array<String>
*
* // Timestamp when call ended, null if call is in progress
* ended_timestamp: "2016-07-24T06:52:11.860000+00:00" // String | null
* }
* ```
* @returns {Object|null}
* @memberOf IMessage
* @readonly
* */
call: null,
/** @returns {boolean|null} */
deleted: false // for clientside cache
};
class Message extends BaseModel {
constructor(def) {
super(BaseMessage, def);
}
}
module.exports = Message;

View File

@@ -0,0 +1,27 @@
"use strict";
const Constants = require("../Constants");
const BaseModel = require("./BaseModel");
/**
* @kind model
* @alias PermissionOverwrite
*/
const BasePermissionOverwrite = {
/** @returns {String|null} */
id: null,
/** @returns {String|null} */
type: null,
/** @returns {Number|null} */
allow: 0,
/** @returns {Number|null} */
deny: 0
};
class PermissionOverwrite extends BaseModel {
constructor(def) {
super(BasePermissionOverwrite, def);
}
}
module.exports = PermissionOverwrite;

35
node_modules/discordie/lib/models/Role.js generated vendored Normal file
View File

@@ -0,0 +1,35 @@
"use strict";
const Constants = require("../Constants");
const BaseModel = require("./BaseModel");
/**
* @kind model
* @alias Role
*/
const BaseRole = {
/** @returns {String|null} */
id: null,
/** @returns {String|null} */
name: null,
/** @returns {Number|null} */
permissions: 0,
/** @returns {boolean|null} */
mentionable: false,
/** @returns {Number|null} */
position: -1,
/** @returns {boolean|null} */
hoist: false,
/** @returns {Number|null} */
color: 0,
/** @returns {boolean|null} */
managed: false
};
class Role extends BaseModel {
constructor(def) {
super(BaseRole, def);
}
}
module.exports = Role;

29
node_modules/discordie/lib/models/User.js generated vendored Normal file
View File

@@ -0,0 +1,29 @@
"use strict";
const Constants = require("../Constants");
const BaseModel = require("./BaseModel");
/**
* @kind model
* @alias User
*/
const BaseUser = {
/** @returns {String|null} */
id: null,
/** @returns {String|null} */
username: "",
/** @returns {String|null} */
discriminator: null,
/** @returns {String|null} */
avatar: null,
/** @returns {boolean|null} */
bot: false,
};
class User extends BaseModel {
constructor(def) {
super(BaseUser, def);
}
}
module.exports = User;

View File

@@ -0,0 +1,5 @@
"use strict";
module.exports = function(schema, data) {
};

View File

@@ -0,0 +1,18 @@
"use strict";
const Constants = require("../../../Constants");
const Events = Constants.Events;
module.exports = function handler(data, gw) {
if (!gw.isPrimary) return true;
const channel = this.DirectMessageChannels.get(data.channel_id);
if (!channel) return true;
this.Dispatcher.emit(Events.CALL_CREATE, {
socket: gw,
channel: channel,
call: channel.call
});
return true;
};

View File

@@ -0,0 +1,27 @@
"use strict";
const Constants = require("../../../Constants");
const Events = Constants.Events;
module.exports = function handler(data, gw) {
if (!gw.isPrimary) return true;
const channel = this.DirectMessageChannels.get(data.channel_id);
if (!channel) return true;
if (data.unavailable) {
this.Dispatcher.emit(Events.CALL_UNAVAILABLE, {
socket: gw,
channelId: data.channel_id,
data: data
});
return true;
}
this.Dispatcher.emit(Events.CALL_DELETE, {
socket: gw,
channelId: data.channel_id,
data: data
});
return true;
};

View File

@@ -0,0 +1,18 @@
"use strict";
const Constants = require("../../../Constants");
const Events = Constants.Events;
module.exports = function handler(data, gw) {
if (!gw.isPrimary) return true;
const channel = this.DirectMessageChannels.get(data.channel_id);
if (!channel) return true;
this.Dispatcher.emit(Events.CALL_UPDATE, {
socket: gw,
channel: channel,
call: channel.call
});
return true;
};

View File

@@ -0,0 +1,14 @@
"use strict";
const Constants = require("../../../Constants");
const Events = Constants.Events;
module.exports = function handler(data, gw) {
if (!gw.isPrimary) return true;
this.Dispatcher.emit(Events.CHANNEL_CREATE, {
socket: gw,
channel: this.Channels.get(data.id) ||
this.DirectMessageChannels.get(data.id)
});
return true;
};

View File

@@ -0,0 +1,14 @@
"use strict";
const Constants = require("../../../Constants");
const Events = Constants.Events;
module.exports = function handler(data, gw) {
if (!gw.isPrimary) return true;
this.Dispatcher.emit(Events.CHANNEL_DELETE, {
socket: gw,
channelId: data.id,
data: data
});
return true;
};

View File

@@ -0,0 +1,19 @@
"use strict";
const Constants = require("../../../Constants");
const Events = Constants.Events;
module.exports = function handler(data, gw) {
if (!gw.isPrimary) return true;
const channel = this.DirectMessageChannels.get(data.channel_id);
const user = this.Users.get(data.user && data.user.id);
if (!channel || !user) return true;
this.Dispatcher.emit(Events.CHANNEL_RECIPIENT_ADD, {
socket: gw,
channel: channel,
user: user
});
return true;
};

View File

@@ -0,0 +1,19 @@
"use strict";
const Constants = require("../../../Constants");
const Events = Constants.Events;
module.exports = function handler(data, gw) {
if (!gw.isPrimary) return true;
const channel = this.DirectMessageChannels.get(data.channel_id);
const user = this.Users.get(data.user && data.user.id);
if (!channel || !user) return true;
this.Dispatcher.emit(Events.CHANNEL_RECIPIENT_REMOVE, {
socket: gw,
channel: channel,
user: user
});
return true;
};

View File

@@ -0,0 +1,25 @@
"use strict";
const Constants = require("../../../Constants");
const Events = Constants.Events;
const Utils = require("../../../core/Utils");
module.exports = function handler(data, gw) {
const prev = data._prev; delete data._prev;
const next = data._next; delete data._next;
if (!gw.isPrimary) return true;
if (!this.Dispatcher.hasListeners(Events.CHANNEL_UPDATE)) return true;
this.Dispatcher.emit(Events.CHANNEL_UPDATE, {
socket: gw,
channel: this.Channels.get(data.id) ||
this.DirectMessageChannels.get(data.id),
getChanges: () => ({
before: Utils.modelToObject(prev),
after: Utils.modelToObject(next)
})
});
return true;
};

View File

@@ -0,0 +1,14 @@
"use strict";
const Constants = require("../../../Constants");
const Events = Constants.Events;
module.exports = function handler(data, gw) {
if (!gw.isPrimary) return true;
this.Dispatcher.emit(Events.GUILD_BAN_ADD, {
socket: gw,
guild: this.Guilds.get(data.guild_id),
user: this.Users.get(data.user.id)
});
return true;
};

View File

@@ -0,0 +1,14 @@
"use strict";
const Constants = require("../../../Constants");
const Events = Constants.Events;
module.exports = function handler(data, gw) {
if (!gw.isPrimary) return true;
this.Dispatcher.emit(Events.GUILD_BAN_REMOVE, {
socket: gw,
guild: this.Guilds.get(data.guild_id),
user: this.Users.get(data.user.id)
});
return true;
};

View File

@@ -0,0 +1,22 @@
"use strict";
const Constants = require("../../../Constants");
const Events = Constants.Events;
module.exports = function handler(data, gw) {
if (!gw.isPrimary) return true;
if (data.unavailable) {
this.Dispatcher.emit(Events.GUILD_UNAVAILABLE, {
socket: gw,
guildId: data.id
});
return true;
}
this.Dispatcher.emit(Events.GUILD_CREATE, {
socket: gw,
guild: this.Guilds.get(data.id),
becameAvailable: this.UnavailableGuilds.isGuildAvailable(data)
});
return true;
};

View File

@@ -0,0 +1,29 @@
"use strict";
const Constants = require("../../../Constants");
const Events = Constants.Events;
const Utils = require("../../../core/Utils");
module.exports = function handler(data, gw) {
if (!gw.isPrimary) return true;
if (data.unavailable) {
this.Dispatcher.emit(Events.GUILD_UNAVAILABLE, {
socket: gw,
guildId: data.id,
data: data
});
return true;
}
const cached = data._cached; delete data._cached;
if (!this.Dispatcher.hasListeners(Events.GUILD_DELETE)) return true;
this.Dispatcher.emit(Events.GUILD_DELETE, {
socket: gw,
guildId: data.id,
data: data,
getCachedData: () => Utils.modelToObject(cached || null)
});
return true;
};

View File

@@ -0,0 +1,27 @@
"use strict";
const Constants = require("../../../Constants");
const Events = Constants.Events;
const Utils = require("../../../core/Utils");
module.exports = function handler(data, gw) {
const prev = data._prev; delete data._prev;
const next = data._next; delete data._next;
if (!gw.isPrimary) return true;
if (!this.Dispatcher.hasListeners(Events.GUILD_EMOJIS_UPDATE)) return true;
const guild = this.Guilds.get(data.guild_id);
if (!guild) return;
this.Dispatcher.emit(Events.GUILD_EMOJIS_UPDATE, {
socket: gw,
guild: guild,
getChanges: () => ({
before: Utils.modelToObject(prev),
after: Utils.modelToObject(next)
})
});
return true;
};

View File

@@ -0,0 +1,14 @@
"use strict";
const Constants = require("../../../Constants");
const Events = Constants.Events;
module.exports = function handler(data, gw) {
if (!gw.isPrimary) return true;
this.Dispatcher.emit(Events.GUILD_MEMBER_ADD, {
socket: gw,
guild: this.Guilds.get(data.guild_id),
member: this.Users.getMember(data.guild_id, data.user.id)
});
return true;
};

View File

@@ -0,0 +1,24 @@
"use strict";
const Constants = require("../../../Constants");
const Events = Constants.Events;
const Utils = require("../../../core/Utils");
module.exports = function handler(data, gw) {
if (!gw.isPrimary) return true;
const cached = data._cached; delete data._cached;
if (!this.Dispatcher.hasListeners(Events.GUILD_MEMBER_REMOVE)) return true;
if (data.user && data.user.id === this._user.id) return true;
this.Dispatcher.emit(Events.GUILD_MEMBER_REMOVE, {
socket: gw,
guild: this.Guilds.get(data.guild_id),
user: this.Users.get(data.user.id),
data: data,
getCachedData: () => Utils.modelToObject(cached || null)
});
return true;
};

View File

@@ -0,0 +1,59 @@
"use strict";
const Constants = require("../../../Constants");
const Events = Constants.Events;
const Utils = require("../../../core/Utils");
const IRole = require("../../../interfaces/IRole");
function diffArray(a, b) {
return a.filter(id => b.indexOf(id) < 0);
}
function filterExistingRoles(roleIds, guildId) {
const guild = this._guilds.get(guildId);
if (!guild) return [];
return roleIds.filter(id => guild.roles.get(id));
}
function mapRoles(ids, guildId) {
return filterExistingRoles.call(this, ids, guildId)
.map(id => new IRole(this, id, guildId));
}
module.exports = function handler(data, gw) {
const prev = data._prev; delete data._prev;
const next = data._next; delete data._next;
if (!gw.isPrimary) return true;
if (!this.Dispatcher.hasListeners(Events.GUILD_MEMBER_UPDATE)) return true;
const event = {
socket: gw,
guild: this.Guilds.get(data.guild_id),
member: this.Users.getMember(data.guild_id, data.user.id),
rolesAdded: [],
rolesRemoved: [],
previousNick: null,
getChanges: () => ({
before: Utils.modelToObject(prev),
after: Utils.modelToObject(next)
})
};
if (prev && next) {
if (Array.isArray(prev.roles) && Array.isArray(next.roles)) {
const rolesAdded = diffArray(next.roles, prev.roles);
const rolesRemoved = diffArray(prev.roles, next.roles);
const rolesChanged = rolesAdded.length || rolesRemoved.length;
if (rolesChanged) {
event.rolesAdded = mapRoles.call(this, rolesAdded, data.guild_id);
event.rolesRemoved = mapRoles.call(this, rolesRemoved, data.guild_id);
}
}
event.previousNick = prev.nick;
}
this.Dispatcher.emit(Events.GUILD_MEMBER_UPDATE, event);
return true;
};

View File

@@ -0,0 +1,15 @@
"use strict";
const Constants = require("../../../Constants");
const Events = Constants.Events;
const IRole = require("../../../interfaces/IRole");
module.exports = function handler(data, gw) {
if (!gw.isPrimary) return true;
this.Dispatcher.emit(Events.GUILD_ROLE_CREATE, {
socket: gw,
guild: this.Guilds.get(data.guild_id),
role: new IRole(this, data.role.id, data.guild_id)
});
return true;
};

View File

@@ -0,0 +1,21 @@
"use strict";
const Constants = require("../../../Constants");
const Events = Constants.Events;
const Utils = require("../../../core/Utils");
module.exports = function handler(data, gw) {
if (!gw.isPrimary) return true;
const cached = data._cached; delete data._cached;
if (!this.Dispatcher.hasListeners(Events.GUILD_ROLE_DELETE)) return true;
this.Dispatcher.emit(Events.GUILD_ROLE_DELETE, {
socket: gw,
guild: this.Guilds.get(data.guild_id),
roleId: data.role_id,
getCachedData: () => Utils.modelToObject(cached || null)
});
return true;
};

View File

@@ -0,0 +1,26 @@
"use strict";
const Constants = require("../../../Constants");
const Events = Constants.Events;
const Utils = require("../../../core/Utils");
const IRole = require("../../../interfaces/IRole");
module.exports = function handler(data, gw) {
const prev = data._prev; delete data._prev;
const next = data._next; delete data._next;
if (!gw.isPrimary) return true;
if (!this.Dispatcher.hasListeners(Events.GUILD_ROLE_UPDATE)) return true;
this.Dispatcher.emit(Events.GUILD_ROLE_UPDATE, {
socket: gw,
guild: this.Guilds.get(data.guild_id),
role: new IRole(this, data.role.id, data.guild_id),
getChanges: () => ({
before: Utils.modelToObject(prev),
after: Utils.modelToObject(next)
})
});
return true;
};

View File

@@ -0,0 +1,24 @@
"use strict";
const Constants = require("../../../Constants");
const Events = Constants.Events;
const Utils = require("../../../core/Utils");
module.exports = function handler(data, gw) {
const prev = data._prev; delete data._prev;
const next = data._next; delete data._next;
if (!gw.isPrimary) return true;
if (!this.Dispatcher.hasListeners(Events.GUILD_UPDATE)) return true;
this.Dispatcher.emit(Events.GUILD_UPDATE, {
socket: gw,
guild: this.Guilds.get(data.id),
getChanges: () => ({
before: Utils.modelToObject(prev),
after: Utils.modelToObject(next)
})
});
return true;
};

View File

@@ -0,0 +1,17 @@
"use strict";
const Constants = require("../../../Constants");
const Events = Constants.Events;
module.exports = function handler(data, gw) {
if (!gw.isPrimary) return true;
const message = this.Messages.get(data.id);
if (!message) return true;
this.Dispatcher.emit(Events.MESSAGE_CREATE, {
socket: gw,
message: message
});
return true;
};

View File

@@ -0,0 +1,15 @@
"use strict";
const Constants = require("../../../Constants");
const Events = Constants.Events;
module.exports = function handler(data, gw) {
if (!gw.isPrimary) return true;
this.Dispatcher.emit(Events.MESSAGE_DELETE, {
socket: gw,
channelId: data.channel_id,
messageId: data.id,
message: this.Messages.get(data.id)
});
return true;
};

View File

@@ -0,0 +1,15 @@
"use strict";
const Constants = require("../../../Constants");
const Events = Constants.Events;
module.exports = function handler(data, gw) {
if (!gw.isPrimary) return true;
this.Dispatcher.emit(Events.MESSAGE_DELETE_BULK, {
socket: gw,
channelId: data.channel_id,
messageIds: data.ids,
messages: data.ids.map(id => this.Messages.get(id)).filter(e => e)
});
return true;
};

View File

@@ -0,0 +1,21 @@
"use strict";
const Constants = require("../../../Constants");
const Events = Constants.Events;
module.exports = function handler(data, gw) {
if (!gw.isPrimary) return true;
const user = this.Users.get(data.user_id);
const message = this.Messages.get(data.message_id);
const channel = this.Channels.get(data.channel_id) ||
this.DirectMessageChannels.get(data.channel_id);
this.Dispatcher.emit(Events.MESSAGE_REACTION_ADD, {
socket: gw,
user, channel, message,
emoji: data.emoji,
data
});
return true;
};

View File

@@ -0,0 +1,21 @@
"use strict";
const Constants = require("../../../Constants");
const Events = Constants.Events;
module.exports = function handler(data, gw) {
if (!gw.isPrimary) return true;
const user = this.Users.get(data.user_id);
const message = this.Messages.get(data.message_id);
const channel = this.Channels.get(data.channel_id) ||
this.DirectMessageChannels.get(data.channel_id);
this.Dispatcher.emit(Events.MESSAGE_REACTION_REMOVE, {
socket: gw,
user, channel, message,
emoji: data.emoji,
data
});
return true;
};

View File

@@ -0,0 +1,23 @@
"use strict";
const Constants = require("../../../Constants");
const Events = Constants.Events;
const Utils = require("../../../core/Utils");
module.exports = function handler(data, gw) {
if (!gw.isPrimary) return true;
const cached = data._cached; delete data._cached;
const message = this.Messages.get(data.message_id);
const channel = this.Channels.get(data.channel_id) ||
this.DirectMessageChannels.get(data.channel_id);
this.Dispatcher.emit(Events.MESSAGE_REACTION_REMOVE_ALL, {
socket: gw,
channel, message,
data,
getCachedData: () => Utils.modelToObject(cached || null)
});
return true;
};

View File

@@ -0,0 +1,14 @@
"use strict";
const Constants = require("../../../Constants");
const Events = Constants.Events;
module.exports = function handler(data, gw) {
if (!gw.isPrimary) return true;
this.Dispatcher.emit(Events.MESSAGE_UPDATE, {
socket: gw,
message: this.Messages.get(data.id),
data: data
});
return true;
};

View File

@@ -0,0 +1,25 @@
"use strict";
const Constants = require("../../../Constants");
const Events = Constants.Events;
module.exports = function handler(data, gw) {
if (!gw.isPrimary) return true;
if (!this.Dispatcher.hasListeners(Events.PRESENCE_UPDATE)) return true;
if (this.User.id != data.user.id) {
// Presences for friends are emitted without `guild_id`
if (!data.guild_id) return true;
var user = this.Users.get(data.user.id);
var member = this.Users.getMember(data.guild_id, data.user.id) || user;
var guild = this.Guilds.get(data.guild_id);
this.Dispatcher.emit(Events.PRESENCE_UPDATE, {
socket: gw,
guild, user, member
});
}
return true;
};

View File

@@ -0,0 +1,19 @@
"use strict";
const Constants = require("../../../Constants");
const Events = Constants.Events;
const User = require("../../../models/User");
const AuthenticatedUser = require("../../../models/AuthenticatedUser");
module.exports = function handler(data, gw) {
if (gw.isPrimary) {
this._user = new AuthenticatedUser(data.user);
this.bot = this._user.bot;
// GW V4 READY emits all guilds as unavailable and
// implements data streaming with GUILD_CREATE
// GATEWAY_READY event has been moved to ReadyEventScheduler
}
return true;
};

View File

@@ -0,0 +1,13 @@
"use strict";
const Constants = require("../../../Constants");
const Events = Constants.Events;
module.exports = function handler(data, gw) {
if (!gw.isPrimary) return true;
this.Dispatcher.emit(Events.GATEWAY_RESUMED, {
socket: gw,
data: data
});
return true;
};

View File

@@ -0,0 +1,25 @@
"use strict";
const Constants = require("../../../Constants");
const Events = Constants.Events;
module.exports = function handler(data, gw) {
if (!gw.isPrimary) return true;
if (!this.Dispatcher.hasListeners(Events.TYPING_START)) return true;
const channel = this.Channels.get(data.channel_id) ||
this.DirectMessageChannels.get(data.channel_id);
if (!channel) return true;
const user = this.Users.get(data.user_id);
if (!user) return true;
this.Dispatcher.emit(Events.TYPING_START, {
socket: gw,
user: user,
timestamp: data.timestamp,
channel: channel
});
return true;
};

View File

@@ -0,0 +1,8 @@
"use strict";
const Constants = require("../../../Constants");
const User = require("../../../models/User");
module.exports = function handler(data, gw) {
return true;
};

View File

@@ -0,0 +1,12 @@
"use strict";
module.exports = function handler(data, gw) {
data.guild_id = data.guild_id || null;
gw.createVoiceSocket(
data.endpoint,
data.guild_id, data.channel_id, gw.userId,
gw.sessionId, data.token
);
return true;
};

View File

@@ -0,0 +1,46 @@
"use strict";
const Constants = require("../../../Constants");
const Errors = Constants.Errors;
const DiscordieError = require("../../../core/DiscordieError");
function disconnectVoice(gw, data, errorDescription) {
return gw.disconnectVoice(
data.guild_id,
true, /* noStateUpdate=true */
new DiscordieError(errorDescription)
);
}
module.exports = function handler(data, gw) {
data.guild_id = data.guild_id || null;
const user = this.User;
const isLocal = this.VoiceConnections.isLocalSession(data.session_id);
const localState = gw.voiceStates.get(data.guild_id);
if (user.id == data.user_id && localState) {
gw.voiceStateUpdate(
data.guild_id, data.channel_id,
data.self_mute, data.self_deaf,
true /* external=true */
);
if (!isLocal) {
disconnectVoice(gw, data, Errors.VOICE_CONNECTED_FROM_ANOTHER_LOCATION);
}
if (!data.channel_id) {
disconnectVoice(gw, data, Errors.VOICE_KICKED_FROM_CHANNEL);
}
}
if (user.id != data.user_id && !data.channel_id) {
var voiceSocket = gw.voiceSockets.get(data.guild_id);
if (voiceSocket && voiceSocket.audioDecoder) {
voiceSocket.audioDecoder.destroyUser(data.user_id);
}
}
return true;
};

View File

@@ -0,0 +1,18 @@
"use strict";
const Constants = require("../../../Constants");
const Events = Constants.Events;
module.exports = function handler(data, gw) {
if (!gw.isPrimary) return true;
const guild = this.Guilds.get(data.guild_id);
const channel = this.Channels.get(data.channel_id);
if (!guild || !channel) return true;
this.Dispatcher.emit(Events.WEBHOOKS_UPDATE, {
socket: gw,
guild, channel, data
});
return true;
};

View File

@@ -0,0 +1,9 @@
"use strict";
const Constants = require("../../../Constants");
const VoiceUDP = require("../../voicetransports/VoiceUDP");
module.exports = function handler(data, voicews) {
voicews.connectAudioTransport(data.ssrc, data.port, data.modes);
return true;
};

View File

@@ -0,0 +1,6 @@
"use strict";
module.exports = function handler(data, voicews) {
voicews.speaking(true, 0);
return true;
};

Some files were not shown because too many files have changed in this diff Show More