Add files via upload

This commit is contained in:
Murat
2023-11-29 18:23:54 +03:00
committed by GitHub
parent bdb4e9d949
commit 119d0275bd
79 changed files with 23303 additions and 0 deletions

3731
node_modules/eris/lib/Client.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

568
node_modules/eris/lib/Constants.js generated vendored Normal file
View File

@ -0,0 +1,568 @@
"use strict";
module.exports.GATEWAY_VERSION = 9;
module.exports.REST_VERSION = 9;
module.exports.ActivityTypes = {
GAME: 0,
STREAMING: 1,
LISTENING: 2,
WATCHING: 3,
CUSTOM: 4,
COMPETING: 5
};
module.exports.ApplicationCommandOptionTypes = {
SUB_COMMAND: 1,
SUB_COMMAND_GROUP: 2,
STRING: 3,
INTEGER: 4,
BOOLEAN: 5,
USER: 6,
CHANNEL: 7,
ROLE: 8,
MENTIONABLE: 9,
NUMBER: 10
};
module.exports.ApplicationCommandPermissionTypes = {
ROLE: 1,
USER: 2
};
module.exports.ApplicationCommandTypes = {
CHAT_INPUT: 1,
USER: 2,
MESSAGE: 3
};
module.exports.AuditLogActions = {
GUILD_UPDATE: 1,
CHANNEL_CREATE: 10,
CHANNEL_UPDATE: 11,
CHANNEL_DELETE: 12,
CHANNEL_OVERWRITE_CREATE: 13,
CHANNEL_OVERWRITE_UPDATE: 14,
CHANNEL_OVERWRITE_DELETE: 15,
MEMBER_KICK: 20,
MEMBER_PRUNE: 21,
MEMBER_BAN_ADD: 22,
MEMBER_BAN_REMOVE: 23,
MEMBER_UPDATE: 24,
MEMBER_ROLE_UPDATE: 25,
MEMBER_MOVE: 26,
MEMBER_DISCONNECT: 27,
BOT_ADD: 28,
ROLE_CREATE: 30,
ROLE_UPDATE: 31,
ROLE_DELETE: 32,
INVITE_CREATE: 40,
INVITE_UPDATE: 41,
INVITE_DELETE: 42,
WEBHOOK_CREATE: 50,
WEBHOOK_UPDATE: 51,
WEBHOOK_DELETE: 52,
EMOJI_CREATE: 60,
EMOJI_UPDATE: 61,
EMOJI_DELETE: 62,
MESSAGE_DELETE: 72,
MESSAGE_BULK_DELETE: 73,
MESSAGE_PIN: 74,
MESSAGE_UNPIN: 75,
INTEGRATION_CREATE: 80,
INTEGRATION_UPDATE: 81,
INTEGRATION_DELETE: 82,
STAGE_INSTANCE_CREATE: 83,
STAGE_INSTANCE_UPDATE: 84,
STAGE_INSTANCE_DELETE: 85,
STICKER_CREATE: 90,
STICKER_UPDATE: 91,
STICKER_DELETE: 92,
GUILD_SCHEDULED_EVENT_CREATE: 100,
GUILD_SCHEDULED_EVENT_UPDATE: 101,
GUILD_SCHEDULED_EVENT_DELETE: 102,
THREAD_CREATE: 110,
THREAD_UPDATE: 111,
THREAD_DELETE: 112,
APPLICATION_COMMAND_PERMISSION_UPDATE: 121
};
module.exports.ButtonStyles = {
PRIMARY: 1,
SECONDARY: 2,
SUCCESS: 3,
DANGER: 4,
LINK: 5
};
module.exports.ChannelTypes = {
GUILD_TEXT: 0,
DM: 1,
GUILD_VOICE: 2,
GROUP_DM: 3,
GUILD_CATEGORY: 4,
GUILD_NEWS: 5,
GUILD_STORE: 6,
GUILD_NEWS_THREAD: 10,
GUILD_PUBLIC_THREAD: 11,
GUILD_PRIVATE_THREAD: 12,
GUILD_STAGE_VOICE: 13, GUILD_STAGE: 13 // [DEPRECATED]
};
module.exports.ComponentTypes = {
ACTION_ROW: 1,
BUTTON: 2,
SELECT_MENU: 3
};
module.exports.ConnectionVisibilityTypes = {
NONE: 0,
EVERYONE: 1
};
module.exports.DefaultMessageNotificationLevels = {
ALL_MESSAGES: 0,
ONLY_MENTIONS: 1
};
module.exports.ExplicitContentFilterLevels = {
DISABLED: 0,
MEMBERS_WITHOUT_ROLES: 1,
ALL_MEMBERS: 2
};
module.exports.GatewayOPCodes = {
DISPATCH: 0, EVENT: 0, // [DEPRECATED]
HEARTBEAT: 1,
IDENTIFY: 2,
PRESENCE_UPDATE: 3, STATUS_UPDATE: 3, // [DEPRECATED]
VOICE_STATE_UPDATE: 4,
VOICE_SERVER_PING: 5,
RESUME: 6,
RECONNECT: 7,
REQUEST_GUILD_MEMBERS: 8, GET_GUILD_MEMBERS: 8, // [DEPRECATED]
INVALID_SESSION: 9,
HELLO: 10,
HEARTBEAT_ACK: 11,
SYNC_GUILD: 12,
SYNC_CALL: 13
};
module.exports.GuildFeatures = [
"ANIMATED_ICON",
"BANNER",
"COMMERCE",
"COMMUNITY",
"DISCOVERABLE",
"FEATURABLE",
"INVITE_SPLASH",
"MEMBER_VERIFICATION_GATE_ENABLED",
"MONETIZATION_ENABLED",
"MORE_STICKERS",
"NEWS",
"PARTNERED",
"PREVIEW_ENABLED",
"PRIVATE_THREADS",
"ROLE_ICONS",
"ROLE_SUBSCRIPTIONS_ENABLED",
"SEVEN_DAY_THREAD_ARCHIVE",
"THREE_DAY_THREAD_ARCHIVE",
"TICKETED_EVENTS_ENABLED",
"VANITY_URL",
"VERIFIED",
"VIP_REGIONS",
"WELCOME_SCREEN_ENABLED"
];
module.exports.GuildIntegrationExpireBehavior = {
REMOVE_ROLE: 0,
KICK: 1
};
module.exports.GuildIntegrationTypes = [
"twitch",
"youtube",
"discord"
];
module.exports.GuildNSFWLevels = {
DEFAULT: 0,
EXPLICIT: 1,
SAFE: 2,
AGE_RESTRICTED: 3
};
module.exports.ImageFormats = [
"jpg",
"jpeg",
"png",
"webp",
"gif"
];
module.exports.ImageSizeBoundaries = {
MINIMUM: 16,
MAXIMUM: 4096
};
const Intents = {
guilds: 1 << 0,
guildMembers: 1 << 1,
guildBans: 1 << 2,
guildEmojisAndStickers: 1 << 3, guildEmojis: 1 << 3, // [DEPRECATED]
guildIntegrations: 1 << 4,
guildWebhooks: 1 << 5,
guildInvites: 1 << 6,
guildVoiceStates: 1 << 7,
guildPresences: 1 << 8,
guildMessages: 1 << 9,
guildMessageReactions: 1 << 10,
guildMessageTyping: 1 << 11,
directMessages: 1 << 12,
directMessageReactions: 1 << 13,
directMessageTyping: 1 << 14,
guildScheduledEvents: 1 << 16
};
Intents.allNonPrivileged = Intents.guilds
| Intents.guildBans
| Intents.guildEmojisAndStickers
| Intents.guildIntegrations
| Intents.guildWebhooks
| Intents.guildInvites
| Intents.guildVoiceStates
| Intents.guildMessages
| Intents.guildMessageReactions
| Intents.guildMessageTyping
| Intents.directMessages
| Intents.directMessageReactions
| Intents.directMessageTyping
| Intents.guildScheduledEvents;
Intents.allPrivileged = Intents.guildMembers
| Intents.guildPresences;
Intents.all = Intents.allNonPrivileged | Intents.allPrivileged;
module.exports.Intents = Intents;
module.exports.InteractionResponseTypes = {
PONG: 1,
CHANNEL_MESSAGE_WITH_SOURCE: 4,
DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE: 5,
DEFERRED_UPDATE_MESSAGE: 6,
UPDATE_MESSAGE: 7,
APPLICATION_COMMAND_AUTOCOMPLETE_RESULT: 8
};
module.exports.InteractionTypes = {
PING: 1,
APPLICATION_COMMAND: 2,
MESSAGE_COMPONENT: 3,
APPLICATION_COMMAND_AUTOCOMPLETE: 4
};
module.exports.InviteTargetTypes = {
STREAM: 1,
EMBEDDED_APPLICATION: 2
};
module.exports.MFALevels = {
NONE: 0,
ELEVATED: 1
};
module.exports.MessageActivityFlags = {
INSTANCE: 1 << 0,
JOIN: 1 << 1,
SPECTATE: 1 << 2,
JOIN_REQUEST: 1 << 3,
SYNC: 1 << 4,
PLAY: 1 << 5,
PARTY_PRIVACY_FRIENDS: 1 << 6,
PARTY_PRIVACY_VOICE_CHANNEL: 1 << 7,
EMBEDDED: 1 << 8
};
module.exports.MessageActivityTypes = {
JOIN: 1,
SPECTATE: 2,
LISTEN: 3,
JOIN_REQUEST: 5
};
module.exports.MessageFlags = {
CROSSPOSTED: 1 << 0,
IS_CROSSPOST: 1 << 1,
SUPPRESS_EMBEDS: 1 << 2,
SOURCE_MESSAGE_DELETED: 1 << 3,
URGENT: 1 << 4,
HAS_THREAD: 1 << 5,
EPHEMERAL: 1 << 6,
LOADING: 1 << 7
};
module.exports.MessageTypes = {
DEFAULT: 0,
RECIPIENT_ADD: 1,
RECIPIENT_REMOVE: 2,
CALL: 3,
CHANNEL_NAME_CHANGE: 4,
CHANNEL_ICON_CHANGE: 5,
CHANNEL_PINNED_MESSAGE: 6,
GUILD_MEMBER_JOIN: 7,
USER_PREMIUM_GUILD_SUBSCRIPTION: 8,
USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1: 9,
USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2: 10,
USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3: 11,
CHANNEL_FOLLOW_ADD: 12,
GUILD_DISCOVERY_DISQUALIFIED: 14,
GUILD_DISCOVERY_REQUALIFIED: 15,
GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING: 16,
GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING: 17,
THREAD_CREATED: 18,
REPLY: 19,
CHAT_INPUT_COMMAND: 20,
THREAD_STARTER_MESSAGE: 21,
GUILD_INVITE_REMINDER: 22,
CONTEXT_MENU_COMMAND: 23
};
module.exports.PermissionOverwriteTypes = {
ROLE: 0,
USER: 1
};
const Permissions = {
createInstantInvite: 1n << 0n,
kickMembers: 1n << 1n,
banMembers: 1n << 2n,
administrator: 1n << 3n,
manageChannels: 1n << 4n,
manageGuild: 1n << 5n,
addReactions: 1n << 6n,
viewAuditLog: 1n << 7n, viewAuditLogs: 1n << 7n, // [DEPRECATED]
voicePrioritySpeaker: 1n << 8n,
voiceStream: 1n << 9n, stream: 1n << 9n, // [DEPRECATED]
viewChannel: 1n << 10n, readMessages: 1n << 10n, // [DEPRECATED]
sendMessages: 1n << 11n,
sendTTSMessages: 1n << 12n,
manageMessages: 1n << 13n,
embedLinks: 1n << 14n,
attachFiles: 1n << 15n,
readMessageHistory: 1n << 16n,
mentionEveryone: 1n << 17n,
useExternalEmojis: 1n << 18n, externalEmojis: 1n << 18n, // [DEPRECATED]
viewGuildInsights: 1n << 19n,
voiceConnect: 1n << 20n,
voiceSpeak: 1n << 21n,
voiceMuteMembers: 1n << 22n,
voiceDeafenMembers: 1n << 23n,
voiceMoveMembers: 1n << 24n,
voiceUseVAD: 1n << 25n,
changeNickname: 1n << 26n,
manageNicknames: 1n << 27n,
manageRoles: 1n << 28n,
manageWebhooks: 1n << 29n,
manageEmojisAndStickers: 1n << 30n, manageEmojis: 1n << 30n, // [DEPRECATED]
useApplicationCommands: 1n << 31n, useSlashCommands: 1n << 31n, // [DEPRECATED]
voiceRequestToSpeak: 1n << 32n,
manageEvents: 1n << 33n,
manageThreads: 1n << 34n,
createPublicThreads: 1n << 35n,
createPrivateThreads: 1n << 36n,
useExternalStickers: 1n << 37n,
sendMessagesInThreads: 1n << 38n,
startEmbeddedActivities: 1n << 39n,
moderateMembers: 1n << 40n
};
Permissions.allGuild = Permissions.kickMembers
| Permissions.banMembers
| Permissions.administrator
| Permissions.manageChannels
| Permissions.manageGuild
| Permissions.viewAuditLog
| Permissions.viewGuildInsights
| Permissions.changeNickname
| Permissions.manageNicknames
| Permissions.manageRoles
| Permissions.manageWebhooks
| Permissions.manageEmojisAndStickers
| Permissions.manageEvents
| Permissions.moderateMembers;
Permissions.allText = Permissions.createInstantInvite
| Permissions.manageChannels
| Permissions.addReactions
| Permissions.viewChannel
| Permissions.sendMessages
| Permissions.sendTTSMessages
| Permissions.manageMessages
| Permissions.embedLinks
| Permissions.attachFiles
| Permissions.readMessageHistory
| Permissions.mentionEveryone
| Permissions.useExternalEmojis
| Permissions.manageRoles
| Permissions.manageWebhooks
| Permissions.useApplicationCommands
| Permissions.manageThreads
| Permissions.createPublicThreads
| Permissions.createPrivateThreads
| Permissions.useExternalStickers
| Permissions.sendMessagesInThreads;
Permissions.allVoice = Permissions.createInstantInvite
| Permissions.manageChannels
| Permissions.voicePrioritySpeaker
| Permissions.voiceStream
| Permissions.viewChannel
| Permissions.voiceConnect
| Permissions.voiceSpeak
| Permissions.voiceMuteMembers
| Permissions.voiceDeafenMembers
| Permissions.voiceMoveMembers
| Permissions.voiceUseVAD
| Permissions.manageRoles
| Permissions.voiceRequestToSpeak
| Permissions.startEmbeddedActivities;
Permissions.all = Permissions.allGuild | Permissions.allText | Permissions.allVoice;
module.exports.Permissions = Permissions;
module.exports.PremiumTiers = {
NONE: 0,
TIER_1: 1,
TIER_2: 2,
TIER_3: 3
};
module.exports.GuildScheduledEventStatus = {
SCHEDULED: 1,
ACTIVE: 2,
COMPLETED: 3,
CANCELED: 4
};
module.exports.GuildScheduledEventEntityTypes = {
STAGE_INSTANCE: 1,
VOICE: 2,
EXTERNAL: 3
};
module.exports.GuildScheduledEventPrivacyLevel = {
PUBLIC: 1,
GUILD_ONLY: 2
};
module.exports.PremiumTypes = {
NONE: 0,
NITRO_CLASSIC: 1,
NITRO: 2
};
module.exports.StageInstancePrivacyLevel = {
PUBLIC: 1,
GUILD_ONLY: 2
};
module.exports.StickerFormats = {
PNG: 1,
APNG: 2,
LOTTIE: 3
};
module.exports.StickerTypes = {
STANDARD: 1,
GUILD: 2
};
module.exports.SystemChannelFlags = {
SUPPRESS_JOIN_NOTIFICATIONS: 1 << 0,
SUPPRESS_PREMIUM_SUBSCRIPTIONS: 1 << 1,
SUPPRESS_GUILD_REMINDER_NOTIFICATIONS: 1 << 2,
SUPPRESS_JOIN_NOTIFICATION_REPLIES : 1 << 3
};
module.exports.SystemJoinMessages = [
"%user% joined the party.",
"%user% is here.",
"Welcome, %user%. We hope you brought pizza.",
"A wild %user% appeared.",
"%user% just landed.",
"%user% just slid into the server.",
"%user% just showed up!",
"Welcome %user%. Say hi!",
"%user% hopped into the server.",
"Everyone welcome %user%!",
"Glad you're here, %user%.",
"Good to see you, %user%.",
"Yay you made it, %user%!"
];
module.exports.ThreadMemberFlags = {
HAS_INTERACTED: 1 << 0,
ALL_MESSAGES: 1 << 1,
ONLY_MENTIONS: 1 << 2,
NO_MESSAGES: 1 << 3
};
module.exports.UserFlags = {
NONE: 0,
DISCORD_STAFF: 1 << 0, DISCORD_EMPLOYEE: 1 << 0,
PARTNER: 1 << 1, PARTNERED_SERVER_OWNER: 1 << 1, DISCORD_PARTNER: 1 << 1, // [DEPRECATED]
HYPESQUAD: 1 << 2, HYPESQUAD_EVENTS: 1 << 2,
BUG_HUNTER_LEVEL_1: 1 << 3,
HOUSE_BRAVERY: 1 << 6, HYPESQUAD_ONLINE_HOUSE_1: 1 << 6,
HOUSE_BRILLIANCE: 1 << 7, HYPESQUAD_ONLINE_HOUSE_2: 1 << 7,
HOUSE_BALANCE: 1 << 8, HYPESQUAD_ONLINE_HOUSE_3: 1 << 8,
PREMIUM_EARLY_SUPPORTER: 1 << 9, EARLY_SUPPORTER: 1 << 9,
TEAM_PSEUDO_USER: 1 << 10, TEAM_USER: 1 << 10,
SYSTEM: 1 << 12,
BUG_HUNTER_LEVEL_2: 1 << 14,
VERIFIED_BOT: 1 << 16,
VERIFIED_DEVELOPER: 1 << 17, EARLY_VERIFIED_BOT_DEVELOPER: 1 << 17, VERIFIED_BOT_DEVELOPER: 1 << 17,
CERTIFIED_MODERATOR: 1 << 18, DISCORD_CERTIFIED_MODERATOR: 1 << 18,
BOT_HTTP_INTERACTIONS: 1 << 19
};
module.exports.VerificationLevels = {
NONE: 0,
LOW: 1,
MEDIUM: 2,
HIGH: 3,
VERY_HIGH: 4
};
module.exports.VideoQualityModes = {
AUTO: 1,
FULL: 2
};
module.exports.VoiceOPCodes = {
IDENTIFY: 0,
SELECT_PROTOCOL: 1,
READY: 2,
HEARTBEAT: 3,
SESSION_DESCRIPTION: 4,
SPEAKING: 5,
HEARTBEAT_ACK: 6,
RESUME: 7,
HELLO: 8,
RESUMED: 9,
CLIENT_DISCONNECT: 13, DISCONNECT: 13 // [DEPRECATED]
};
module.exports.WebhookTypes = {
INCOMING: 1,
CHANNEL_FOLLOWER: 2,
APPLICATION: 3
};

550
node_modules/eris/lib/command/Command.js generated vendored Normal file
View File

@ -0,0 +1,550 @@
"use strict";
const Base = require("../structures/Base");
/**
* Represents an command framework command
* @prop {Array<String>} aliases An array of command aliases
* @prop {Boolean} argsRequired If arguments are required or not
* @prop {Boolean} caseInsensitive Whether the command label (and aliases) is case insensitive or not
* @prop {Number} cooldown The cooldown between command usage in milliseconds
* @prop {Object} cooldownExclusions A set of factors that limit where cooldowns are active
* @prop {Function | String} cooldownMessage A string or a function that returns a string to show when the command is on cooldown.
* @prop {Number} cooldownReturns Number of times to return a message when the command is used during it's cooldown.
* @prop {Object} defaultSubcommandOptions Default subcommand options.
* @prop {Boolean} deleteCommand Whether to delete the user command message or not
* @prop {String} description A short description of the command
* @prop {Boolean} dmOnly Whether the command is prevented from being used in guilds or not
* @prop {Function | String} errorMessage A string or a function that returns a string to show if the execution of the command handler somehow fails. The function is passed the Message object as a parameter.
* @prop {String} fullDescription A detailed description of the command
* @prop {String} fullLabel The full command label
* @prop {Boolean} guildOnly Whether the command is prevented from being used in Direct Messages or not
* @prop {Boolean} hidden Whether or not the command is hidden from the default help command list.
* @prop {Object} hooks A set of functions to be executed at different times throughout the command's processing
* @prop {Function | String} invalidUsageMessage A string or a function that returns a string to show when a command was improperly used.
* @prop {String} label The command label
* @prop {Command?} parentCommand If this command is also a subcommand, this will refer to its parent Command
* @prop {Function | String} permissionMessage A string or a function that returns a string to show when the user doesn't have permissions to use the command.
* @prop {Object?} reactionButtons An array of objects specifying reaction buttons, or null if no reaction buttons.
* @prop {Number} reactionButtonTimeout Time (in milliseconds) to wait before invalidating the command's reaction buttons
* @prop {Object} requirements A set of factors that limit who can call the command
* @prop {Boolean} restartCooldown Whether or not to restart a command's cooldown every time it's used
* @prop {Object} subcommands Object mapping subcommand labels to Command objects
* @prop {Object} subcommandAliases Object mapping subcommand aliases to their respective subcommand label
* @prop {String} usage Details on how to call the command to show in the default help command
*/
class Command {
/**
* Register a command
* @arg {String} label The command label
* @arg {Function | String | Array<Function | String>} generator A response string, array of functions or strings, or function that generates a string or array of strings when called.
* If a function is passed, the function will be passed a Message object and an array of command arguments. The Message object will have an additional property `prefix`, which is the prefix used in the command.
* `generator(msg, args)`
* @arg {Object} [options] Command options
* @arg {Array<String>} [options.aliases] An array of command aliases
* @arg {Boolean} [options.argsRequired=false] If arguments are required or not
* @arg {Boolean} [options.caseInsensitive=false] Whether the command label (and aliases) is case insensitive or not
* @arg {Number} [options.cooldown] The cooldown between command usage in milliseconds
* @arg {Object} [options.cooldownExclusions={}] A set of factors that limit where cooldowns are active
* @arg {Array<String>} [options.cooldownExclusions.userIDs] An array of user IDs representing users that are not affected by cooldowns.
* @arg {Array<String>} [options.cooldownExclusions.guildIDs] An array of guild IDs representing guilds that are not affected by cooldowns.
* @arg {Array<String>} [options.cooldownExclusions.channelIDs] An array of channel IDs representing channels that are not affected by cooldowns.
* @arg {Function | String | Object} [options.cooldownMessage] A string or `createMessage()` content object, or a function that returns either of those, to show when the command is on cooldown. The function is passed the Message object as a parameter.
* @arg {Number} [option.cooldownReturns=0] Number of times to return a message when the command is used during it's cooldown. Once the cooldown expires this is reset. Set this to 0 to always return a message.
* @arg {Object} [options.defaultSubcommandOptions={}] Default subcommand options. This object takes the same options as a normal Command
* @arg {Boolean} [options.deleteCommand=false] Whether to delete the user command message or not
* @arg {String} [options.description="No description"] A short description of the command to show in the default help command
* @arg {Boolean} [options.dmOnly=false] Whether to prevent the command from being used in guilds or not
* @arg {Function | String | Object} [options.errorMessage] A string or `createMessage()` content object, or a function that returns either of those, to show if the execution of the command handler somehow fails. The function is passed the Message object as a parameter.
* @arg {String} [options.fullDescription="No full description"] A detailed description of the command to show in the default help command
* @arg {Boolean} [options.guildOnly=false] Whether to prevent the command from being used in Direct Messages or not
* @arg {Boolean} [options.hidden=false] Whether or not the command should be hidden from the default help command list.
* @arg {Object} [options.hooks] A set of functions to be executed at different times throughout the command's processing
* @arg {Function} [options.hooks.preCommand] A function that is executed before any permission or cooldown checks is made. The function is passed the command message and arguments as parameters.
* @arg {Function} [options.hooks.postCheck] A function that is executed after all checks have cleared, but before the command is executed. The function is passed the command message, arguments, and if command checks were passed as parameters.
* @arg {Function} [options.hooks.postExecution] A function that is executed after the command is executed, regardless of the final failed state of the command. The function is passed the command message, arguments, and if execution succeeded as parameters.
* @arg {Function} [options.hooks.postCommand] A function that is executed after a response has been posted, and the command has finished processing. The function is passed the command message, arguments, and the response message (if applicable) as parameters.
* @arg {Function | String | Object} [options.invalidUsageMessage] A string or `createMessage()` content object, or a function that returns either of those, to show when a command was improperly used. The function is passed the Message object as a parameter.
* @arg {Function | String | Object} [options.permissionMessage] A string or `createMessage()` content object, or a function that returns either of those, to show when the user doesn't have permissions to use the command. The function is passed the Message object as a parameter.
* @arg {Array<{emoji: String, type: String, response: (Function | String | Array<Function | String>), filter: Function}>} [options.reactionButtons] An array of objects specifying reaction buttons
* `emoji` specifies the button emoji. Custom emojis should be in format `emojiName:emojiID`
* `type` specifies the type of the reaction button, either "edit" or "cancel"
* `response` specifies the content to edit the message to when the reaction button is pressed. This accepts the same arguments as the `generator` parameter of this function, but with an extra userID parameter for generator functions (`function(msg, args, userID)`) describing the user that made the reaction
* `filter` specifies a function (`function(msg, emoji, userID)`) that filters message reactions. If the function returns false, the reaction is not treated as a valid reaction button response
* @arg {Number} [options.reactionButtonTimeout=60000] Time (in milliseconds) to wait before invalidating the command's reaction buttons
* @arg {Object} [options.requirements] A set of factors that limit who can call the command
* @arg {Function | Array<String>} [options.requirements.userIDs] An array or a function that returns an array of user IDs representing users that can call the command. The function is passed the Message object as a parameter.
* @arg {Function | Object} [options.requirements.permissions] An object or a function that returns an object containing permission keys the user must match to use the command. The function is passed the Message object as a parameter.
* i.e.:
* ```
* {
* "administrator": false,
* "manageMessages": true
* }
* ```
* In the above example, the user must not have administrator permissions, but must have manageMessages to use the command
* @arg {Function | Array<String>} [options.requirements.roleIDs] An array or a function that returns an array of role IDs that would allow a user to use the command. The function is passed the Message object as a parameter.
* @arg {Function | Array<String>} [options.requirements.roleNames] An array or a function that returns an array of role names that would allow a user to use the command. The function is passed the Message object as a parameter.
* @arg {Function} [options.requirements.custom] A function that accepts a message and returns true if the command should be run
* @arg {Boolean} [option.restartCooldown=false] Whether or not to restart a command's cooldown every time it's used.
* @arg {String} [options.usage] Details on how to call the command to show in the default help command
*/
constructor(label, generator, options, parentCommand) {
this.parentCommand = parentCommand;
this.label = label;
this.description = options.description || "No description";
this.fullDescription = options.fullDescription || "No full description";
this.usage = options.usage || "";
this.aliases = options.aliases || [];
this.caseInsensitive = !!options.caseInsensitive;
this.hooks = options.hooks || {};
this.requirements = options.requirements || {};
if(!this.requirements.userIDs) {
this.requirements.userIDs = [];
}
if(!this.requirements.permissions) {
this.requirements.permissions = {};
}
this.deleteCommand = !!options.deleteCommand;
this.argsRequired = !!options.argsRequired;
this.guildOnly = !!options.guildOnly;
this.dmOnly = !!options.dmOnly;
this.cooldown = options.cooldown || 0;
this.cooldownExclusions = options.cooldownExclusions || {};
if(!this.cooldownExclusions.userIDs) {
this.cooldownExclusions.userIDs = [];
}
if(!this.cooldownExclusions.guildIDs) {
this.cooldownExclusions.guildIDs = [];
}
if(!this.cooldownExclusions.channelIDs) {
this.cooldownExclusions.channelIDs = [];
}
this.restartCooldown = !!options.restartCooldown;
this.cooldownReturns = options.cooldownReturns || 0;
this.cooldownMessage = options.cooldownMessage || false;
this.invalidUsageMessage = options.invalidUsageMessage || false;
this.permissionMessage = options.permissionMessage || false;
this.errorMessage = options.errorMessage || "";
this.reactionButtons = options.reactionButtons ? options.reactionButtons.map((button, index) => {
if(typeof button.response === "string") {
button.execute = () => button.response;
return button;
} else if(Array.isArray(button.response)) {
button.responses = button.response.map((item, otherIndex) => {
if(typeof item === "string") {
return () => item;
} else if(typeof item === "function") {
return item;
} else {
throw new Error(`Invalid reaction button response generator (index ${index}:${otherIndex})`);
}
});
button.execute = () => button.responses[Math.floor(Math.random() * button.responses.length)]();
return button;
} else if(typeof button.response === "function") {
button.execute = button.response;
return button;
} else if(button.type === "cancel") {
return button;
} else {
throw new Error(`Invalid reaction button response generator (index ${index})`);
}
}) : null;
this.reactionButtonTimeout = options.reactionButtonTimeout || 60000;
if(this.cooldown !== 0) {
this.usersOnCooldown = new Set();
if(this.restartCooldown) {
this.cooldownTimeouts = {};
}
if(this.cooldownReturns) {
this.cooldownAmounts = {};
}
}
if(typeof generator === "string") {
this.response = generator;
this.execute = () => this.response;
} else if(Array.isArray(generator)) {
this.responses = generator.map((item, index) => {
if(typeof item === "string") {
return () => item;
} else if(typeof item === "function") {
return item;
} else {
throw new Error(`Invalid command response generator (index ${index})`);
}
});
this.execute = () => this.responses[Math.floor(Math.random() * this.responses.length)]();
} else if(typeof generator === "function") {
this.execute = generator;
} else {
throw new Error("Invalid command response generator");
}
this.defaultSubcommandOptions = options.defaultSubcommandOptions || {};
this.subcommands = {};
this.subcommandAliases = {};
this.hidden = !!options.hidden;
}
get fullLabel() {
return `${this.parentCommand ? this.parentCommand.fullLabel + " " : ""}${this.label}`;
}
cooldownCheck(msg) {
// Verify the msg isn't excluded from cooldown checks
if(this.cooldownExclusionCheck(msg)) {
return true;
}
const userID = msg.author.id;
if(this.usersOnCooldown.has(userID)) {
if(this.cooldownReturns) {
this.cooldownAmounts[userID]++;
}
if(this.restartCooldown) {
clearTimeout(this.cooldownTimeouts[userID]);
this.cooldownTimeouts[userID] = setTimeout(() => {
this.usersOnCooldown.delete(userID);
}, this.cooldown);
}
return false;
}
if(this.cooldownReturns) {
this.cooldownAmounts[userID] = 0;
}
this.usersOnCooldown.add(userID);
if(this.restartCooldown) {
this.cooldownTimeouts[userID] = setTimeout(() => {
this.usersOnCooldown.delete(userID);
}, this.cooldown);
} else {
setTimeout(() => {
this.usersOnCooldown.delete(userID);
}, this.cooldown);
}
return true;
}
cooldownExclusionCheck(msg) {
return this.cooldownExclusions.channelIDs.includes(msg.channel.id) || this.cooldownExclusions.userIDs.includes(msg.author.id) || (msg.channel.guild && this.cooldownExclusions.guildIDs.includes(msg.channel.guild.id));
}
async executeCommand(msg, args) {
if(this.hooks.postCheck) {
const response = await this.hooks.postCheck(msg, args, true);
if(response) {
msg = response.msg || msg;
args = response.args || args;
}
}
const ret = this.execute(msg, args);
if(this.hooks.postExecution) {
this.hooks.postExecution(msg, args, true);
}
return ret;
}
async permissionCheck(msg) {
if(this.requirements.custom) {
if(typeof this.requirements.custom !== "function") {
throw new Error("Custom requirement is not a function");
}
if(!(await this.requirements.custom(msg))) {
return false;
}
}
if(this.requirements.userIDs) {
const userIDs = typeof this.requirements.userIDs === "function" ? await this.requirements.userIDs(msg) : this.requirements.userIDs;
if(!Array.isArray(userIDs)) {
throw new Error("User IDs requirement is not an array");
}
if(userIDs.length > 0 && !userIDs.includes(msg.author.id)) {
return false;
}
}
if(msg.channel.guild) {
if(this.dmOnly) {
return false;
}
if(this.requirements.permissions) {
const requiredPermissions = Object.keys(typeof this.requirements.permissions === "function" ? await this.requirements.permissions(msg) : this.requirements.permissions);
if(requiredPermissions.length > 0) {
const permissions = msg.channel.permissionsOf(msg.author.id);
for(const permission of requiredPermissions) {
if(!permissions.has(permission)) {
return false;
}
}
}
}
const roleIDs = msg.member.roles || [];
if(this.requirements.roleIDs) {
const requiredRoleIDs = typeof this.requirements.roleIDs === "function" ? await this.requirements.roleIDs(msg) : this.requirements.roleIDs;
if(!Array.isArray(requiredRoleIDs)) {
throw new Error("Role IDs requirement is not an array");
}
for(const roleID of requiredRoleIDs) {
if(!roleIDs.includes(roleID)) {
return false;
}
}
}
if(this.requirements.roleNames) {
const roleNames = roleIDs.map((roleID) => msg.channel.guild.roles.get(roleID).name);
const requiredRoleNames = typeof this.requirements.roleNames === "function" ? await this.requirements.roleNames(msg) : this.requirements.roleNames;
if(!Array.isArray(roleNames)) {
throw new Error("Role names requirement is not an array");
}
for(const roleName of requiredRoleNames) {
if(!roleNames.includes(roleName)) {
return false;
}
}
}
} else if(this.guildOnly) {
return false;
}
return true;
}
async process(args, msg) {
const shouldDelete = this.deleteCommand && msg.channel.guild && msg.channel.permissionsOf(msg._client.user.id).has("manageMessages");
if(this.hooks.preCommand) {
const response = await this.hooks.preCommand(msg, args);
if(response) {
msg = response.msg || msg;
args = response.args || args;
}
}
let reply;
if(this.cooldown !== 0 && !this.cooldownCheck(msg)) {
if(this.hooks.postCheck) {
const response = await this.hooks.postCheck(msg, args, true);
if(response) {
msg = response.msg || msg;
args = response.args || args;
}
}
if(this.cooldownMessage && (!this.cooldownReturns || this.cooldownAmounts[msg.author.id] <= this.cooldownReturns)) {
reply = typeof this.cooldownMessage === "function" ? await this.cooldownMessage(msg) : this.cooldownMessage;
if(reply) {
msg.channel.createMessage(reply);
}
}
return;
}
if(!await this.permissionCheck(msg)) {
if(this.hooks.postCheck) {
const response = await this.hooks.postCheck(msg, args, false);
if(response) {
msg = response.msg || msg;
args = response.args || args;
}
}
if(shouldDelete) {
msg.delete();
}
reply = typeof this.permissionMessage === "function" ? await this.permissionMessage(msg) : this.permissionMessage;
if(reply) {
msg.channel.createMessage(reply);
}
return;
}
if(args.length === 0) {
if(shouldDelete) {
msg.delete();
}
if(this.argsRequired) {
if(this.hooks.postCheck) {
const response = await this.hooks.postCheck(msg, args, true);
if(response) {
msg = response.msg || msg;
args = response.args || args;
}
}
reply = typeof this.invalidUsageMessage === "function" ? await this.invalidUsageMessage(msg) : this.invalidUsageMessage;
if(reply) {
if(typeof reply === "string") {
reply = {
content: reply
};
}
if(reply.content) {
reply.content = reply.content.replace(/%prefix%/g, msg.prefix).replace(/%label%/g, this.fullLabel);
}
msg.channel.createMessage(reply);
}
return;
}
return this.executeCommand(msg, args);
}
const label = this.subcommandAliases[args[0]] || args[0];
let subcommand;
if((subcommand = this.subcommands[label]) !== undefined || ((subcommand = this.subcommands[label.toLowerCase()]) !== undefined && subcommand.caseInsensitive)) {
msg.command = subcommand; // eslint-disable-line require-atomic-updates
return subcommand.process(args.slice(1), msg);
} else {
if(shouldDelete) {
msg.delete();
}
return this.executeCommand(msg, args);
}
}
/**
* Register a subcommand
* @arg {String} label The subcommand label
* @arg {Function | String | Array<Function | String>} generator A response string, array of functions or strings, or function that generates a string or array of strings when called.
* If a function is passed, the function will be passed a Message object and an array of subcommand arguments. The Message object will have an additional property `prefix`, which is the prefix used in the subcommand.
* `generator(msg, args)`
* @arg {Object} [options] Command options
* @arg {Array<String>} [options.aliases] An array of command aliases
* @arg {Boolean} [options.argsRequired=false] If arguments are required or not
* @arg {Boolean} [options.caseInsensitive=false] Whether the command label (and aliases) is case insensitive or not
* @arg {Number} [options.cooldown] The cooldown between command usage in milliseconds
* @arg {Object} [options.cooldownExclusions={}] A set of factors that limit where cooldowns are active
* @arg {Array<String>} [options.cooldownExclusions.userIDs] An array of user IDs representing users that are not affected by cooldowns.
* @arg {Array<String>} [options.cooldownExclusions.guildIDs] An array of guild IDs representing guilds that are not affected by cooldowns.
* @arg {Array<String>} [options.cooldownExclusions.channelIDs] An array of channel IDs representing channels that are not affected by cooldowns.
* @arg {Function | String | Object} [options.cooldownMessage] A string or `createMessage()` content object, or a function that returns either of those, to show when the command is on cooldown
* @arg {Number} [option.cooldownReturns=0] Number of times to return a message when the command is used during it's cooldown. Once the cooldown expires this is reset. Set this to 0 to always return a message.
* @arg {Object} [options.defaultSubcommandOptions={}] Default subcommand options. This object takes the same options as a normal Command
* @arg {Boolean} [options.deleteCommand=false] Whether to delete the user command message or not
* @arg {String} [options.description="No description"] A short description of the command to show in the default help command
* @arg {Boolean} [options.dmOnly=false] Whether to prevent the command from being used in guilds or not
* @arg {Function | String | Object} [options.errorMessage] A string or `createMessage()` content object, or a function that returns either of those, to show if the execution of the command handler somehow fails.
* @arg {String} [options.fullDescription="No full description"] A detailed description of the command to show in the default help command
* @arg {Boolean} [options.guildOnly=false] Whether to prevent the command from being used in Direct Messages or not
* @arg {Boolean} [options.hidden=false] Whether or not the command should be hidden from the default help command list
* @arg {Object} [options.hooks] A set of functions to be executed at different times throughout the command's processing
* @arg {Function} [options.hooks.preCommand] A function that is executed before any permission or cooldown checks is made. The function is passed the command message and arguments as parameters.
* @arg {Function} [options.hooks.postCheck] A function that is executed after all checks have cleared, but before the command is executed. The function is passed the command message, arguments, and if command checks were passed as parameters.
* @arg {Function} [options.hooks.postExecution] A function that is executed after the command is executed, regardless of the final failed state of the command. The function is passed the command message, arguments, and if execution succeeded as parameters.
* @arg {Function} [options.hooks.postCommand] A function that is executed after a response has been posted, and the command has finished processing. The function is passed the command message, arguments, and the response message (if applicable) as parameters.
* @arg {Function | String | Object} [options.invalidUsageMessage] A string or `createMessage()` content object, or a function that returns either of those, to show when a command was improperly used
* @arg {Function | String | Object} [options.permissionMessage] A string or `createMessage()` content object, or a function that returns either of those, to show when the user doesn't have permissions to use the command
* @arg {Array<{emoji: String, type: String, response: (Function | String | Array<Function | String>)}>} [options.reactionButtons] An array of objects specifying reaction buttons
* `emoji` specifies the button emoji. Custom emojis should be in format `emojiName:emojiID`
* `type` specifies the type of the reaction button, either "edit" or "cancel"
* `response` specifies the content to edit the message to when the reaction button is pressed. This accepts the same arguments as the `generator` parameter of this function, but with an extra userID parameter for generator functions (`function(msg, args, userID)`) describing the user that made the reaction
* `filter` specifies a function (`function(msg, emoji, userID)`) that filters message reactions. If the function returns false, the reaction is not treated as a valid reaction button response
* @arg {Number} [options.reactionButtonTimeout=60000] Time (in milliseconds) to wait before invalidating the command's reaction buttons
* @arg {Object} [options.requirements] A set of factors that limit who can call the command
* @arg {Function | Array<String>} [options.requirements.userIDs] An array or a function that returns an array of user IDs representing users that can call the command
* @arg {Function | Object} [options.requirements.permissions] An object or a function that returns an object containing permission keys the user must match to use the command
* i.e.:
* ```
* {
* "administrator": false,
* "manageMessages": true
* }
* ```
* In the above example, the user must not have administrator permissions, but must have manageMessages to use the command
* @arg {Function | Array<String>} [options.requirements.roleIDs] An array or a function that returns an array of role IDs that would allow a user to use the command
* @arg {Function | Array<String>} [options.requirements.roleNames] An array or a function that returns an array of role names that would allow a user to use the command
* @arg {Function} [options.requirements.custom] A function that accepts a message and returns true if the command should be run
* @arg {Boolean} [option.restartCooldown=false] Whether or not to restart a command's cooldown every time it's used.
* @arg {String} [options.usage] Details on how to call the command to show in the default help command
* @returns {Command}
*/
registerSubcommand(label, generator, options = {}) {
if(label.includes(" ")) {
throw new Error("Subcommand label may not have spaces");
}
if(this.subcommands[label]) {
throw new Error("You have already registered a subcommand for " + label);
}
options.defaultSubcommandOptions = options.defaultSubcommandOptions || {};
for(const key in this.defaultSubcommandOptions) {
if(this.defaultSubcommandOptions.hasOwnProperty(key) && options[key] === undefined) {
options[key] = this.defaultSubcommandOptions[key];
options.defaultSubcommandOptions[key] = this.defaultSubcommandOptions[key];
}
}
label = options.caseInsensitive === true ? label.toLowerCase() : label;
this.subcommands[label] = new Command(label, generator, options, this);
if(options.aliases) {
options.aliases.forEach((alias) => {
this.subcommandAliases[alias] = label;
});
}
return this.subcommands[label];
}
/**
* Register an alias for a subcommand
* @arg {String} alias The alias
* @arg {String} label The original subcommand label
*/
registerSubcommandAlias(alias, label) {
if(!this.subcommands[label]) {
throw new Error("No subcommand registered for " + label);
}
if(this.subcommandAliases[alias]) {
throw new Error(`Alias ${label} already registered`);
}
this.subcommandAliases[alias] = label;
this.subcommands[label].aliases.push(alias);
}
/**
* Unregister a subcommand
* @arg {String} label The subcommand label
*/
unregisterSubcommand(label) {
const original = this.subcommandAliases[label];
if(original) {
this.subcommands[original].aliases.splice(this.subcommands[original].aliases.indexOf(label), 1);
delete this.subcommandAliases[label];
} else {
delete this.subcommands[label];
}
}
toString() {
return `[Command ${this.label}]`;
}
toJSON(props = []) {
return Base.prototype.toJSON.call(this, [
"parentCommand",
"label",
"description",
"fullDescription",
"usage",
"aliases",
"caseInsensitive",
"hooks",
"requirements",
"deleteCommand",
"argsRequired",
"guildOnly",
"dmOnly",
"cooldown",
"cooldownExclusions",
"restartCooldown",
"cooldownReturns",
"cooldownMessage",
"invalidUsageMessage",
"permissionMessage",
"errorMessage",
"reactionButtons",
"reactionButtonTimeout",
"execute",
"defaultSubcommandOptions",
"subcommands",
"subcommandAliases",
"hidden",
...props
]);
}
}
module.exports = Command;

455
node_modules/eris/lib/command/CommandClient.js generated vendored Normal file
View File

@ -0,0 +1,455 @@
"use strict";
const Client = require("../Client");
const Command = require("./Command");
const Message = require("../structures/Message");
/**
* Represents an Eris client with the command framework
* @extends Client
* @prop {Object} commands Object mapping command labels to Command objects
* @prop {Object} commandAliases Object mapping command label aliases to command labels
* @prop {Object} commandOptions Command options
* @prop {Object} guildPrefixes Object mapping guild IDs to guild specific prefix or arrays of guild-specific prefixes
*/
class CommandClient extends Client {
/**
* Create a CommandClient
* @arg {String} token Bot token
* @arg {Object} options Eris options (same as Client)
* @arg {Object} [commandOptions] Command options
* @arg {Function} [argsSplitter] The function used to split args. The function is given a string with the contents of the command message (without the prefix) and should return an array of strings. By default, args are split by consecutive whitespace
* @arg {Boolean} [commandOptions.defaultHelpCommand=true] Whether to register the default help command or not
* @arg {String} [commandOptions.description="An Eris-based Discord bot"] The description to show in the default help command
* @arg {Boolean} [commandOptions.ignoreBots=true] Whether to ignore bot accounts or not
* @arg {Boolean} [commandOptions.ignoreSelf=true] Whether to ignore the bot's own account or not
* @arg {String} [commandOptions.name="<Bot username>"] The bot name to show in the default help command
* @arg {String} [commandOptions.owner="an unknown user"] The owner to show in the default help command
* @arg {String | Array<String>} [commandOptions.prefix="@mention "] The bot prefix. Can be either an array of prefixes or a single prefix. "@mention" will be automatically replaced with the bot's actual mention
* @arg {Object} [commandOptions.defaultCommandOptions={}] Default command options. This object takes the same options as a normal Command
*/
constructor(token, options, commandOptions) {
super(token, options);
this.commandOptions = Object.assign({
argsSplitter: (str) => str.split(/\s+/g),
defaultHelpCommand: true,
description: "An Eris-based Discord bot",
ignoreBots: true,
ignoreSelf: true,
name: null,
owner: "an unknown user",
prefix: "@mention ",
defaultCommandOptions: {}
}, commandOptions);
this.guildPrefixes = {};
this.commands = {};
this.commandAliases = {};
this.activeMessages = {};
this.once("shardPreReady", () => {
this.preReady = true;
if(!this.commandOptions.name) {
this.commandOptions.name = `**${this.user.username}**`;
}
if(Array.isArray(this.commandOptions.prefix)) {
for(let i = 0; i < this.commandOptions.prefix.length; ++i) {
this.commandOptions.prefix[i] = this.commandOptions.prefix[i].replace(/@mention/g, this.user.mention);
}
} else {
this.commandOptions.prefix = this.commandOptions.prefix.replace(/@mention/g, this.user.mention);
}
for(const key of Object.keys(this.guildPrefixes)) {
if(Array.isArray(this.guildPrefixes[key])) {
for(let i = 0; i < this.guildPrefixes[key].length; ++i) {
this.guildPrefixes[key][i] = this.guildPrefixes[key][i].replace(/@mention/g, this.user.mention);
}
} else {
this.guildPrefixes[key] = this.guildPrefixes[key].replace(/@mention/g, this.user.mention);
}
}
});
this.on("messageCreate", this.onMessageCreate);
this.on("messageReactionAdd", this.onMessageReactionEvent);
this.on("messageReactionRemove", this.onMessageReactionEvent);
if(this.commandOptions.defaultHelpCommand) {
this.registerCommand("help", async (msg, args) => {
let result = "";
if(args.length > 0) {
let cur = this.commands[this.commandAliases[args[0]] || args[0]];
if(!cur) {
return "Command not found";
}
let {label} = cur;
for(let i = 1; i < args.length; ++i) {
cur = cur.subcommands[cur.subcommandAliases[args[i]] || args[i]];
if(!cur) {
return "Command not found";
}
label += ` ${cur.label}`;
}
result += `**${msg.prefix}${label}** ${cur.usage}\n${cur.fullDescription}`;
if(cur.aliases.length > 0) {
result += `\n\n**Aliases:** ${cur.aliases.join(", ")}`;
}
const subcommands = Object.keys(cur.subcommands);
if(subcommands.length > 0) {
result += "\n\n**Subcommands:**";
for(const subLabel of subcommands) {
if(cur.subcommands.hasOwnProperty(subLabel) && await cur.subcommands[subLabel].permissionCheck(msg)) {
result += `\n **${subLabel}** - ${cur.subcommands[subLabel].description}`;
}
}
}
} else {
result += `${this.commandOptions.name} - ${this.commandOptions.description}\n`;
if(this.commandOptions.owner) {
result += `by ${this.commandOptions.owner}\n`;
}
result += "\n**Commands:**\n";
for(const label in this.commands) {
if(this.commands.hasOwnProperty(label) && this.commands[label] && await this.commands[label].permissionCheck(msg) && !this.commands[label].hidden) {
result += ` **${msg.prefix}${label}** - ${this.commands[label].description}\n`;
}
}
result += `\nType ${msg.prefix}help <command> for more info on a command.`;
}
return result;
}, {
description: "This help text",
fullDescription: "This command is used to view information of different bot commands, including this one."
});
if(!this.commandOptions.defaultCommandOptions.invalidUsageMessage) {
this.commandOptions.defaultCommandOptions.invalidUsageMessage = "Invalid usage. Do `%prefix%help %label%` to view proper usage.";
}
} else if(!this.commandOptions.defaultCommandOptions.invalidUsageMessage) {
this.commandOptions.defaultCommandOptions.invalidUsageMessage = "Invalid usage.";
}
}
checkPrefix(msg) {
let prefixes = this.commandOptions.prefix;
if(msg.channel.guild !== undefined && this.guildPrefixes[msg.channel.guild.id] !== undefined) {
prefixes = this.guildPrefixes[msg.channel.guild.id];
}
if(typeof prefixes === "string") {
return msg.content.replace(/<@!/g, "<@").startsWith(prefixes) && prefixes;
} else if(Array.isArray(prefixes)) {
return prefixes.find((prefix) => msg.content.replace(/<@!/g, "<@").startsWith(prefix));
}
throw new Error(`Unsupported prefix format | ${prefixes}`);
}
/**
* Checks the command client for a command based on the provided message
* @arg {Message} msg The message object from the message create event
*/
async onMessageCreate(msg) {
if(!this.ready) {
return;
}
if(!msg.author) {
this.emit("warn", `Message ${msg.id} has author=${msg.author} | Channel ${msg.channel.id}, timestamp ${Date.now()}`);
return;
}
msg.command = false;
if((!this.commandOptions.ignoreSelf || msg.author.id !== this.user.id) && (!this.commandOptions.ignoreBots || !msg.author.bot) && (msg.prefix = this.checkPrefix(msg))) {
const args = this.commandOptions.argsSplitter(msg.content.replace(/<@!/g, "<@").substring(msg.prefix.length).trim());
const label = args.shift();
const command = this.resolveCommand(label);
if(command !== undefined) {
msg.command = command;
try {
let resp = await msg.command.process(args, msg);
if(resp != null) {
if(!(resp instanceof Message)) {
resp = await this.createMessage(msg.channel.id, resp); // eslint-disable-line require-atomic-updates
}
if(msg.command.reactionButtons) {
msg.command.reactionButtons.forEach((button) => resp.addReaction(button.emoji));
this.activeMessages[resp.id] = {
args: args,
command: msg.command,
timeout: setTimeout(() => {
this.unwatchMessage(resp.id, resp.channel.id);
}, msg.command.reactionButtonTimeout)
};
}
}
if(msg.command.hooks.postCommand) {
msg.command.hooks.postCommand(msg, args, resp);
}
} catch(err) {
this.emit("error", err);
if(msg.command.hooks.postExecution) {
msg.command.hooks.postExecution(msg, args, false);
}
let newMsg;
if(msg.command.errorMessage) {
try {
if(typeof msg.command.errorMessage === "function") {
const reply = await msg.command.errorMessage(msg, err);
if(reply !== undefined) {
newMsg = await this.createMessage(msg.channel.id, reply);
}
} else {
newMsg = await this.createMessage(msg.channel.id, msg.command.errorMessage);
}
} catch(err) {
this.emit("error", err);
}
}
if(msg.command.hooks.postCommand) {
msg.command.hooks.postCommand(msg, args, newMsg);
}
}
}
}
}
async onMessageReactionEvent(msg, emoji, reactor) {
const userID = typeof reactor === "object" ? reactor.id : reactor;
if(!this.ready || userID === this.user.id || !(msg.content || msg.embeds || msg.attachments)) {
return;
}
emoji = emoji.id ? `${emoji.name}:${emoji.id}` : emoji.name;
const activeMessage = this.activeMessages[msg.id];
if(activeMessage && activeMessage.command.reactionButtons) {
const action = activeMessage.command.reactionButtons.find((button) => button.emoji === emoji);
if(!action) {
return;
}
if(action.filter && !action.filter(msg, emoji, userID)) {
return;
}
switch(action.type) {
case "cancel": {
this.unwatchMessage(msg.id, msg.channel.guild && msg.channel.id);
try {
const resp = await action.execute(msg, activeMessage.args, userID);
if(resp != null) {
await this.editMessage(msg.channel.id, msg.id, resp);
}
} catch(err) {} // eslint-disable-line no-empty
break;
}
case "edit":
default: {
try {
const resp = await action.execute(msg, activeMessage.args, userID);
if(resp != null) {
await this.editMessage(msg.channel.id, msg.id, resp);
}
} catch(err) {} // eslint-disable-line no-empty
break;
}
}
}
}
/**
* Register a command
* @arg {String} label The command label
* @arg {Function | String | Array<Function | String>} generator A response string, array of functions or strings, or function that generates a string or array of strings when called.
* If a function is passed, the function will be passed a Message object and an array of command arguments. The Message object will have an additional property `prefix`, which is the prefix used in the command.
* `generator(msg, args)`
* @arg {Object} [options] Command options
* @arg {Array<String>} [options.aliases] An array of command aliases
* @arg {Boolean} [options.argsRequired=false] If arguments are required or not
* @arg {Boolean} [options.caseInsensitive=false] Whether the command label (and aliases) is case insensitive or not
* @arg {Number} [options.cooldown] The cooldown between command usage in milliseconds
* @arg {Object} [options.cooldownExclusions={}] A set of factors that limit where cooldowns are active
* @arg {Array<String>} [options.cooldownExclusions.userIDs] An array of user IDs representing users that are not affected by cooldowns.
* @arg {Array<String>} [options.cooldownExclusions.guildIDs] An array of guild IDs representing guilds that are not affected by cooldowns.
* @arg {Array<String>} [options.cooldownExclusions.channelIDs] An array of channel IDs representing channels that are not affected by cooldowns.
* @arg {Function | String} [options.cooldownMessage] A string or a function that returns a string to show when the command is on cooldown
* @arg {Number} [option.cooldownReturns=0] Number of times to return a message when the command is used during it's cooldown. Once the cooldown expires this is reset. Set this to 0 to always return a message.
* @arg {Object} [options.defaultSubcommandOptions={}] Default subcommand options. This object takes the same options as a normal Command
* @arg {Boolean} [options.deleteCommand=false] Whether to delete the user command message or not
* @arg {String} [options.description="No description"] A short description of the command to show in the default help command
* @arg {Boolean} [options.dmOnly=false] Whether to prevent the command from being used in guilds or not
* @arg {Function | String} [options.errorMessage] A string or a function that returns a string to show if the execution of the command handler somehow fails. The function is passed the command message and the error as parameters.
* @arg {String} [options.fullDescription="No full description"] A detailed description of the command to show in the default help command
* @arg {Boolean} [options.guildOnly=false] Whether to prevent the command from being used in Direct Messages or not
* @arg {Boolean} [options.hidden=false] Whether or not the command should be hidden from the default help command list
* @arg {Object} [options.hooks] A set of functions to be executed at different times throughout the command's processing
* @arg {Function} [options.hooks.preCommand] A function that is executed before any permission or cooldown checks is made. The function is passed the command message and arguments as parameters.
* @arg {Function} [options.hooks.postCheck] A function that is executed after all checks have cleared, but before the command is executed. The function is passed the command message, arguments, and if command checks were passed as parameters.
* @arg {Function} [options.hooks.postExecution] A function that is executed after the command is executed, regardless of the final failed state of the command. The function is passed the command message, arguments, and if execution succeeded as parameters.
* @arg {Function} [options.hooks.postCommand] A function that is executed after a response has been posted, and the command has finished processing. The function is passed the command message, arguments, and the response message (if applicable) as parameters.
* @arg {Function | String} [options.invalidUsageMessage] A string or a function that returns a string to show when a command was improperly used
* @arg {Function | String} [options.permissionMessage] A string or a function that returns a string to show when the user doesn't have permissions to use the command
* @arg {Array<{emoji: String, type: String, response: (Function | String | Array<Function | String>)}>} [options.reactionButtons] An array of objects specifying reaction buttons
* `emoji` specifies the button emoji. Custom emojis should be in format `emojiName:emojiID`
* `type` specifies the type of the reaction button, either "edit" or "cancel"
* `response` specifies the content to edit the message to when the reaction button is pressed. This accepts the same arguments as the `generator` parameter of this function, but with an extra userID parameter for generator functions (`function(msg, args, userID)`) describing the user that made the reaction
* `filter` specifies a function (`function(msg, emoji, userID)`) that filters message reactions. If the function returns false, the reaction is not treated as a valid reaction button response
* @arg {Number} [options.reactionButtonTimeout=60000] Time (in milliseconds) to wait before invalidating the command's reaction buttons
* @arg {Object} [options.requirements] A set of factors that limit who can call the command
* @arg {Function | Array<String>} [options.requirements.userIDs] An array or a function that returns an array of user IDs representing users that can call the command
* @arg {Function | Object} [options.requirements.permissions] An object or a function that returns an object containing permission keys the user must match to use the command
* i.e.:
* ```
* {
* "administrator": false,
* "manageMessages": true
* }
* ```
* In the above example, the user must not have administrator permissions, but must have manageMessages to use the command
* @arg {Function | Array<String>} [options.requirements.roleIDs] An array or a function that returns an array of role IDs that would allow a user to use the command
* @arg {Function | Array<String>} [options.requirements.roleNames] An array or a function that returns an array of role names that would allow a user to use the command
* @arg {Function} [options.requirements.custom] A function that accepts a message and returns true if the command should be run
* @arg {Boolean} [option.restartCooldown=false] Whether or not to restart a command's cooldown every time it's used.
* @arg {String} [options.usage] Details on how to call the command to show in the default help command
* @returns {Command}
*/
registerCommand(label, generator, options = {}) {
if(label.includes(" ")) {
throw new Error("Command label may not have spaces");
}
let lowercaseCommand = label.toLowerCase();
if(this.commands[label] || (this.commands[lowercaseCommand] && this.commands[lowercaseCommand].caseInsensitive)) {
throw new Error(`You have already registered a command for ${label}`);
}
// Aliases are not deleted when deleting commands
let command = this.commandAliases[label]; // Just to make the following if statement less messy
lowercaseCommand = this.commandAliases[label.toLowerCase()];
if(this.commands[command] || (this.commands[lowercaseCommand] && this.commands[lowercaseCommand].caseInsensitive)) {
throw new Error(`Alias ${label} already registered`);
}
options.defaultSubcommandOptions = options.defaultSubcommandOptions || {};
for(const key in this.commandOptions.defaultCommandOptions) {
if(this.commandOptions.defaultCommandOptions.hasOwnProperty(key) && options[key] === undefined) {
options[key] = this.commandOptions.defaultCommandOptions[key];
options.defaultSubcommandOptions[key] = this.commandOptions.defaultCommandOptions[key];
}
}
label = options.caseInsensitive === true ? label.toLowerCase() : label;
if(this.commands[label]) {
throw new Error(`You have already registered a command for ${label}`);
}
command = this.commandAliases[label];
if(this.commands[command]) {
throw new Error(`Alias ${command} already registered`);
}
if(options.aliases) {
options.aliases.forEach((alias) => {
lowercaseCommand = alias.toLowerCase();
if(this.commands[alias] || (this.commands[lowercaseCommand] && this.commands[lowercaseCommand].caseInsensitive)) {
throw new Error(`You have already registered a command for alias ${alias}`);
}
command = this.commandAliases[alias];
lowercaseCommand = this.commandAliases[alias.toLowerCase()];
if(this.commands[command] || (this.commands[lowercaseCommand] && this.commands[lowercaseCommand].caseInsensitive)) {
throw new Error(`Alias ${alias} already registered`);
}
alias = options.caseInsensitive === true ? alias.toLowerCase() : alias;
if(this.commands[alias]) {
throw new Error(`You have already registered a command for alias ${alias}`);
}
command = this.commandAliases[alias];
if(this.commands[command]) {
throw new Error(`Alias ${alias} already registered`);
}
this.commandAliases[alias] = label;
});
}
this.commands[label] = new Command(label, generator, options);
return this.commands[label];
}
/**
* Register an alias for a command
* @arg {String} alias The alias
* @arg {String} label The original command label
*/
registerCommandAlias(alias, label) {
let caseInsensitiveLabel = false;
if(!this.commands[label] && !(this.commands[(label = label.toLowerCase())] && (caseInsensitiveLabel = this.commands[label.toLowerCase()].caseInsensitive))) {
throw new Error(`No command registered for ${label}`);
}
alias = caseInsensitiveLabel === true ? alias.toLowerCase() : alias;
if(this.commandAliases[alias]) {
throw new Error(`Alias ${alias} already registered`);
}
this.commandAliases[alias] = label;
this.commands[label].aliases.push(alias);
}
/**
* Register a prefix override for a specific guild
* @arg {String} guildID The ID of the guild to override prefixes for
* @arg {String | Array} prefix The bot prefix. Can be either an array of prefixes or a single prefix. "@mention" will be automatically replaced with the bot's actual mention
*/
registerGuildPrefix(guildID, prefix) {
if(!this.preReady) {
this.guildPrefixes[guildID] = prefix;
} else if(Array.isArray(prefix)) {
for(let i = 0; i < prefix.length; ++i) {
prefix[i] = prefix[i].replace(/@mention/g, this.user.mention);
}
this.guildPrefixes[guildID] = prefix;
} else {
this.guildPrefixes[guildID] = prefix.replace(/@mention/g, this.user.mention);
}
}
resolveCommand(label) {
label = this.commandAliases[label] || label;
let command = this.commands[label];
if(command) {
return command;
}
label = label.toLowerCase();
label = this.commandAliases[label] || label;
command = this.commands[label];
if(command && command.caseInsensitive) {
return command;
}
}
/**
* Unregister a command
* @arg {String} label The command label
*/
unregisterCommand(label) {
const original = this.commandAliases[label];
if(original) {
this.commands[original].aliases.splice(this.commands[original].aliases.indexOf(label), 1);
delete this.commandAliases[label];
} else {
delete this.commands[label];
}
}
unwatchMessage(id, channelID) {
delete this.activeMessages[id];
if(channelID) {
this.removeMessageReactions(channelID, id).catch(() => {});
}
}
toString() {
return `[CommandClient ${this.user.id}]`;
}
toJSON(props = []) {
return super.toJSON([
"commandOptions",
"guildPrefixes",
"commands",
"commandAliases",
"activeMessages",
...props
]);
}
}
module.exports = CommandClient;

63
node_modules/eris/lib/errors/DiscordHTTPError.js generated vendored Normal file
View File

@ -0,0 +1,63 @@
"use strict";
class DiscordHTTPError extends Error {
constructor(req, res, response, stack) {
super();
Object.defineProperty(this, "req", {
enumerable: false,
value: req
});
Object.defineProperty(this, "res", {
enumerable: false,
value: res
});
Object.defineProperty(this, "response", {
enumerable: false,
value: response
});
Object.defineProperty(this, "code", {
enumerable: false,
value: res.statusCode
});
let message = `${res.statusCode} ${res.statusMessage} on ${req.method} ${req.path}`;
const errors = this.flattenErrors(response);
if(errors.length > 0) {
message += "\n " + errors.join("\n ");
}
Object.defineProperty(this, "message", {
enumerable: false,
value: message
});
if(stack) {
this.stack = this.name + ": " + this.message + "\n" + stack;
} else {
Error.captureStackTrace(this, DiscordHTTPError);
}
}
get headers() {
return this.response.headers;
}
get name() {
return this.constructor.name;
}
flattenErrors(errors, keyPrefix = "") {
let messages = [];
for(const fieldName in errors) {
if(!errors.hasOwnProperty(fieldName) || fieldName === "message" || fieldName === "code") {
continue;
}
if(Array.isArray(errors[fieldName])) {
messages = messages.concat(errors[fieldName].map((str) => `${keyPrefix + fieldName}: ${str}`));
}
}
return messages;
}
}
module.exports = DiscordHTTPError;

71
node_modules/eris/lib/errors/DiscordRESTError.js generated vendored Normal file
View File

@ -0,0 +1,71 @@
"use strict";
class DiscordRESTError extends Error {
constructor(req, res, response, stack) {
super();
Object.defineProperty(this, "req", {
enumerable: false,
value: req
});
Object.defineProperty(this, "res", {
enumerable: false,
value: res
});
Object.defineProperty(this, "response", {
enumerable: false,
value: response
});
Object.defineProperty(this, "code", {
enumerable: false,
value: +response.code || -1
});
let message = response.message || "Unknown error";
if(response.errors) {
message += "\n " + this.flattenErrors(response.errors).join("\n ");
} else {
const errors = this.flattenErrors(response);
if(errors.length > 0) {
message += "\n " + errors.join("\n ");
}
}
Object.defineProperty(this, "message", {
enumerable: false,
value: message
});
if(stack) {
this.stack = this.name + ": " + this.message + "\n" + stack;
} else {
Error.captureStackTrace(this, DiscordRESTError);
}
}
get headers() {
return this.response.headers;
}
get name() {
return `${this.constructor.name} [${this.code}]`;
}
flattenErrors(errors, keyPrefix = "") {
let messages = [];
for(const fieldName in errors) {
if(!errors.hasOwnProperty(fieldName) || fieldName === "message" || fieldName === "code") {
continue;
}
if(errors[fieldName]._errors) {
messages = messages.concat(errors[fieldName]._errors.map((obj) => `${keyPrefix + fieldName}: ${obj.message}`));
} else if(Array.isArray(errors[fieldName])) {
messages = messages.concat(errors[fieldName].map((str) => `${keyPrefix + fieldName}: ${str}`));
} else if(typeof errors[fieldName] === "object") {
messages = messages.concat(this.flattenErrors(errors[fieldName], keyPrefix + fieldName + "."));
}
}
return messages;
}
}
module.exports = DiscordRESTError;

2673
node_modules/eris/lib/gateway/Shard.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

164
node_modules/eris/lib/gateway/ShardManager.js generated vendored Normal file
View File

@ -0,0 +1,164 @@
"use strict";
const Base = require("../structures/Base");
const Collection = require("../util/Collection");
const Shard = require("./Shard");
class ShardManager extends Collection {
constructor(client, options = {}) {
super(Shard);
this._client = client;
this.options = Object.assign({
concurrency: 1
}, options);
this.buckets = new Map();
this.connectQueue = [];
this.connectTimeout = null;
}
connect(shard) {
this.connectQueue.push(shard);
this.tryConnect();
}
setConcurrency(concurrency) {
this.options.concurrency = concurrency;
}
spawn(id) {
let shard = this.get(id);
if(!shard) {
shard = this.add(new Shard(id, this._client));
shard.on("ready", () => {
/**
* Fired when a shard turns ready
* @event Client#shardReady
* @prop {Number} id The ID of the shard
*/
this._client.emit("shardReady", shard.id);
if(this._client.ready) {
return;
}
for(const other of this.values()) {
if(!other.ready) {
return;
}
}
this._client.ready = true;
this._client.startTime = Date.now();
/**
* Fired when all shards turn ready
* @event Client#ready
*/
this._client.emit("ready");
}).on("resume", () => {
/**
* Fired when a shard resumes
* @event Client#shardResume
* @prop {Number} id The ID of the shard
*/
this._client.emit("shardResume", shard.id);
if(this._client.ready) {
return;
}
for(const other of this.values()) {
if(!other.ready) {
return;
}
}
this._client.ready = true;
this._client.startTime = Date.now();
this._client.emit("ready");
}).on("disconnect", (error) => {
/**
* Fired when a shard disconnects
* @event Client#shardDisconnect
* @prop {Error?} error The error, if any
* @prop {Number} id The ID of the shard
*/
this._client.emit("shardDisconnect", error, shard.id);
for(const other of this.values()) {
if(other.ready) {
return;
}
}
this._client.ready = false;
this._client.startTime = 0;
/**
* Fired when all shards disconnect
* @event Client#disconnect
*/
this._client.emit("disconnect");
});
}
if(shard.status === "disconnected") {
return this.connect(shard);
}
}
tryConnect() {
// nothing in queue
if(this.connectQueue.length === 0) {
return;
}
// loop over the connectQueue
for(const shard of this.connectQueue) {
// find the bucket for our shard
const rateLimitKey = (shard.id % this.options.concurrency) || 0;
const lastConnect = this.buckets.get(rateLimitKey) || 0;
// has enough time passed since the last connect for this bucket (5s/bucket)?
// alternatively if we have a sessionID, we can skip this check
if(!shard.sessionID && Date.now() - lastConnect < 5000) {
continue;
}
// Are there any connecting shards in the same bucket we should wait on?
if(this.some((s) => s.connecting && ((s.id % this.options.concurrency) || 0) === rateLimitKey)) {
continue;
}
// connect the shard
shard.connect();
this.buckets.set(rateLimitKey, Date.now());
// remove the shard from the queue
const index = this.connectQueue.findIndex((s) => s.id === shard.id);
this.connectQueue.splice(index, 1);
}
// set the next timeout if we have more shards to connect
if(!this.connectTimeout && this.connectQueue.length > 0) {
this.connectTimeout = setTimeout(() => {
this.connectTimeout = null;
this.tryConnect();
}, 500);
}
}
_readyPacketCB(shardID) {
const rateLimitKey = (shardID % this.options.concurrency) || 0;
this.buckets.set(rateLimitKey, Date.now());
this.tryConnect();
}
toString() {
return `[ShardManager ${this.size}]`;
}
toJSON(props = []) {
return Base.prototype.toJSON.call(this, [
"buckets",
"connectQueue",
"connectTimeout",
"options",
...props
]);
}
}
module.exports = ShardManager;

137
node_modules/eris/lib/rest/Endpoints.js generated vendored Normal file
View File

@ -0,0 +1,137 @@
"use strict";
const {REST_VERSION} = require("../Constants");
module.exports.BASE_URL = "/api/v" + REST_VERSION;
module.exports.CDN_URL = "https://cdn.discordapp.com";
module.exports.CLIENT_URL = "https://discord.com";
module.exports.ORIGINAL_INTERACTION_RESPONSE = (appID, interactToken) => `/webhooks/${appID}/${interactToken}`;
module.exports.COMMAND = (applicationID, commandID) => `/applications/${applicationID}/commands/${commandID}`;
module.exports.COMMANDS = (applicationID) => `/applications/${applicationID}/commands`;
module.exports.COMMAND_PERMISSIONS = (applicationID, guildID, commandID) => `/applications/${applicationID}/guilds/${guildID}/commands/${commandID}/permissions`;
module.exports.CHANNEL = (chanID) => `/channels/${chanID}`;
module.exports.CHANNEL_BULK_DELETE = (chanID) => `/channels/${chanID}/messages/bulk-delete`;
module.exports.CHANNEL_CALL_RING = (chanID) => `/channels/${chanID}/call/ring`;
module.exports.CHANNEL_CROSSPOST = (chanID, msgID) => `/channels/${chanID}/messages/${msgID}/crosspost`;
module.exports.CHANNEL_FOLLOW = (chanID) => `/channels/${chanID}/followers`;
module.exports.CHANNEL_INVITES = (chanID) => `/channels/${chanID}/invites`;
module.exports.CHANNEL_MESSAGE_REACTION = (chanID, msgID, reaction) => `/channels/${chanID}/messages/${msgID}/reactions/${reaction}`;
module.exports.CHANNEL_MESSAGE_REACTION_USER = (chanID, msgID, reaction, userID) => `/channels/${chanID}/messages/${msgID}/reactions/${reaction}/${userID}`;
module.exports.CHANNEL_MESSAGE_REACTIONS = (chanID, msgID) => `/channels/${chanID}/messages/${msgID}/reactions`;
module.exports.CHANNEL_MESSAGE = (chanID, msgID) => `/channels/${chanID}/messages/${msgID}`;
module.exports.CHANNEL_MESSAGES = (chanID) => `/channels/${chanID}/messages`;
module.exports.CHANNEL_MESSAGES_SEARCH = (chanID) => `/channels/${chanID}/messages/search`;
module.exports.CHANNEL_PERMISSION = (chanID, overID) => `/channels/${chanID}/permissions/${overID}`;
module.exports.CHANNEL_PERMISSIONS = (chanID) => `/channels/${chanID}/permissions`;
module.exports.CHANNEL_PIN = (chanID, msgID) => `/channels/${chanID}/pins/${msgID}`;
module.exports.CHANNEL_PINS = (chanID) => `/channels/${chanID}/pins`;
module.exports.CHANNEL_RECIPIENT = (groupID, userID) => `/channels/${groupID}/recipients/${userID}`;
module.exports.CHANNEL_TYPING = (chanID) => `/channels/${chanID}/typing`;
module.exports.CHANNEL_WEBHOOKS = (chanID) => `/channels/${chanID}/webhooks`;
module.exports.CHANNELS = "/channels";
module.exports.CUSTOM_EMOJI_GUILD = (emojiID) => `/emojis/${emojiID}/guild`;
module.exports.DISCOVERY_CATEGORIES = "/discovery/categories";
module.exports.DISCOVERY_VALIDATION = "/discovery/valid-term";
module.exports.GATEWAY = "/gateway";
module.exports.GATEWAY_BOT = "/gateway/bot";
module.exports.GUILD = (guildID) => `/guilds/${guildID}`;
module.exports.GUILD_AUDIT_LOGS = (guildID) => `/guilds/${guildID}/audit-logs`;
module.exports.GUILD_BAN = (guildID, memberID) => `/guilds/${guildID}/bans/${memberID}`;
module.exports.GUILD_BANS = (guildID) => `/guilds/${guildID}/bans`;
module.exports.GUILD_CHANNELS = (guildID) => `/guilds/${guildID}/channels`;
module.exports.GUILD_COMMAND = (applicationID, guildID, commandID) => `/applications/${applicationID}/guilds/${guildID}/commands/${commandID}`;
module.exports.GUILD_COMMAND_PERMISSIONS = (applicationID, guildID) => `/applications/${applicationID}/guilds/${guildID}/commands/permissions`;
module.exports.GUILD_COMMANDS = (applicationID, guildID) => `/applications/${applicationID}/guilds/${guildID}/commands`;
module.exports.GUILD_DISCOVERY = (guildID) => `/guilds/${guildID}/discovery-metadata`;
module.exports.GUILD_DISCOVERY_CATEGORY = (guildID, categoryID) => `/guilds/${guildID}/discovery-categories/${categoryID}`;
module.exports.GUILD_EMOJI = (guildID, emojiID) => `/guilds/${guildID}/emojis/${emojiID}`;
module.exports.GUILD_EMOJIS = (guildID) => `/guilds/${guildID}/emojis`;
module.exports.GUILD_INTEGRATION = (guildID, inteID) => `/guilds/${guildID}/integrations/${inteID}`;
module.exports.GUILD_INTEGRATION_SYNC = (guildID, inteID) => `/guilds/${guildID}/integrations/${inteID}/sync`;
module.exports.GUILD_INTEGRATIONS = (guildID) => `/guilds/${guildID}/integrations`;
module.exports.GUILD_INVITES = (guildID) => `/guilds/${guildID}/invites`;
module.exports.GUILD_VANITY_URL = (guildID) => `/guilds/${guildID}/vanity-url`;
module.exports.GUILD_MEMBER = (guildID, memberID) => `/guilds/${guildID}/members/${memberID}`;
module.exports.GUILD_MEMBER_NICK = (guildID, memberID) => `/guilds/${guildID}/members/${memberID}/nick`;
module.exports.GUILD_MEMBER_ROLE = (guildID, memberID, roleID) => `/guilds/${guildID}/members/${memberID}/roles/${roleID}`;
module.exports.GUILD_MEMBERS = (guildID) => `/guilds/${guildID}/members`;
module.exports.GUILD_MEMBERS_SEARCH = (guildID) => `/guilds/${guildID}/members/search`;
module.exports.GUILD_MESSAGES_SEARCH = (guildID) => `/guilds/${guildID}/messages/search`;
module.exports.GUILD_PREVIEW = (guildID) => `/guilds/${guildID}/preview`;
module.exports.GUILD_PRUNE = (guildID) => `/guilds/${guildID}/prune`;
module.exports.GUILD_ROLE = (guildID, roleID) => `/guilds/${guildID}/roles/${roleID}`;
module.exports.GUILD_ROLES = (guildID) => `/guilds/${guildID}/roles`;
module.exports.GUILD_SCHEDULED_EVENT = (guildID, scheduledEventID) => `/guilds/${guildID}/scheduled-events/${scheduledEventID}`;
module.exports.GUILD_SCHEDULED_EVENT_USERS = (guildID, scheduledEventID) => `/guilds/${guildID}/scheduled-events/${scheduledEventID}/users`;
module.exports.GUILD_SCHEDULED_EVENTS = (guildID) => `/guilds/${guildID}/scheduled-events`;
module.exports.GUILD_STICKER = (guildID, stickerID) => `/guilds/${guildID}/stickers/${stickerID}`;
module.exports.GUILD_STICKERS = (guildID) => `/guilds/${guildID}/stickers`;
module.exports.GUILD_TEMPLATE = (code) => `/guilds/templates/${code}`;
module.exports.GUILD_TEMPLATES = (guildID) => `/guilds/${guildID}/templates`;
module.exports.GUILD_TEMPLATE_GUILD = (guildID, code) => `/guilds/${guildID}/templates/${code}`;
module.exports.GUILD_VOICE_REGIONS = (guildID) => `/guilds/${guildID}/regions`;
module.exports.GUILD_WEBHOOKS = (guildID) => `/guilds/${guildID}/webhooks`;
module.exports.GUILD_WELCOME_SCREEN = (guildID) => `/guilds/${guildID}/welcome-screen`;
module.exports.GUILD_WIDGET = (guildID) => `/guilds/${guildID}/widget.json`;
module.exports.GUILD_WIDGET_SETTINGS = (guildID) => `/guilds/${guildID}/widget`;
module.exports.GUILD_VOICE_STATE = (guildID, user) => `/guilds/${guildID}/voice-states/${user}`;
module.exports.GUILDS = "/guilds";
module.exports.INTERACTION_RESPOND = (interactID, interactToken) => `/interactions/${interactID}/${interactToken}/callback`;
module.exports.INVITE = (inviteID) => `/invites/${inviteID}`;
module.exports.OAUTH2_APPLICATION = (appID) => `/oauth2/applications/${appID}`;
module.exports.STAGE_INSTANCE = (channelID) => `/stage-instances/${channelID}`;
module.exports.STAGE_INSTANCES = "/stage-instances";
module.exports.STICKER = (stickerID) => `/stickers/${stickerID}`;
module.exports.STICKER_PACKS = "/sticker-packs";
module.exports.THREAD_MEMBER = (channelID, userID) => `/channels/${channelID}/thread-members/${userID}`;
module.exports.THREAD_MEMBERS = (channelID) => `/channels/${channelID}/thread-members`;
module.exports.THREAD_WITH_MESSAGE = (channelID, msgID) => `/channels/${channelID}/messages/${msgID}/threads`;
module.exports.THREAD_WITHOUT_MESSAGE = (channelID) => `/channels/${channelID}/threads`;
module.exports.THREADS_ACTIVE = (channelID) => `/channels/${channelID}/threads/active`;
module.exports.THREADS_ARCHIVED = (channelID, type) => `/channels/${channelID}/threads/archived/${type}`;
module.exports.THREADS_ARCHIVED_JOINED = (channelID) => `/channels/${channelID}/users/@me/threads/archived/private`;
module.exports.THREADS_GUILD_ACTIVE = (guildID) => `/guilds/${guildID}/threads/active`;
module.exports.USER = (userID) => `/users/${userID}`;
module.exports.USER_BILLING = (userID) => `/users/${userID}/billing`;
module.exports.USER_BILLING_PAYMENTS = (userID) => `/users/${userID}/billing/payments`;
module.exports.USER_BILLING_PREMIUM_SUBSCRIPTION = (userID) => `/users/${userID}/billing/premium-subscription`;
module.exports.USER_CHANNELS = (userID) => `/users/${userID}/channels`;
module.exports.USER_CONNECTIONS = (userID) => `/users/${userID}/connections`;
module.exports.USER_CONNECTION_PLATFORM = (userID, platform, id) => `/users/${userID}/connections/${platform}/${id}`;
module.exports.USER_GUILD = (userID, guildID) => `/users/${userID}/guilds/${guildID}`;
module.exports.USER_GUILDS = (userID) => `/users/${userID}/guilds`;
module.exports.USER_MFA_CODES = (userID) => `/users/${userID}/mfa/codes`;
module.exports.USER_MFA_TOTP_DISABLE = (userID) => `/users/${userID}/mfa/totp/disable`;
module.exports.USER_MFA_TOTP_ENABLE = (userID) => `/users/${userID}/mfa/totp/enable`;
module.exports.USER_NOTE = (userID, targetID) => `/users/${userID}/note/${targetID}`;
module.exports.USER_PROFILE = (userID) => `/users/${userID}/profile`;
module.exports.USER_RELATIONSHIP = (userID, relID) => `/users/${userID}/relationships/${relID}`;
module.exports.USER_SETTINGS = (userID) => `/users/${userID}/settings`;
module.exports.USERS = "/users";
module.exports.VOICE_REGIONS = "/voice/regions";
module.exports.WEBHOOK = (hookID) => `/webhooks/${hookID}`;
module.exports.WEBHOOK_MESSAGE = (hookID, token, msgID) => `/webhooks/${hookID}/${token}/messages/${msgID}`;
module.exports.WEBHOOK_SLACK = (hookID) => `/webhooks/${hookID}/slack`;
module.exports.WEBHOOK_TOKEN = (hookID, token) => `/webhooks/${hookID}/${token}`;
module.exports.WEBHOOK_TOKEN_SLACK = (hookID, token) => `/webhooks/${hookID}/${token}/slack`;
// CDN Endpoints
module.exports.ACHIEVEMENT_ICON = (applicationID, achievementID, icon) => `/app-assets/${applicationID}/achievements/${achievementID}/icons/${icon}`;
module.exports.APPLICATION_ASSET = (applicationID, asset) => `/app-assets/${applicationID}/${asset}`;
module.exports.APPLICATION_ICON = (applicationID, icon) => `/app-icons/${applicationID}/${icon}`;
module.exports.BANNER = (guildOrUserID, hash) => `/banners/${guildOrUserID}/${hash}`;
module.exports.CHANNEL_ICON = (chanID, chanIcon) => `/channel-icons/${chanID}/${chanIcon}`;
module.exports.CUSTOM_EMOJI = (emojiID) => `/emojis/${emojiID}`;
module.exports.DEFAULT_USER_AVATAR = (userDiscriminator) => `/embed/avatars/${userDiscriminator}`;
module.exports.GUILD_AVATAR = (guildID, userID, guildAvatar) => `/guilds/${guildID}/users/${userID}/avatars/${guildAvatar}`;
module.exports.GUILD_DISCOVERY_SPLASH = (guildID, guildDiscoverySplash) => `/discovery-splashes/${guildID}/${guildDiscoverySplash}`;
module.exports.GUILD_ICON = (guildID, guildIcon) => `/icons/${guildID}/${guildIcon}`;
module.exports.GUILD_SCHEDULED_EVENT_COVER = (eventID, eventIcon) => `/guild-events/${eventID}/${eventIcon}`;
module.exports.GUILD_SPLASH = (guildID, guildSplash) => `/splashes/${guildID}/${guildSplash}`;
module.exports.ROLE_ICON = (roleID, roleIcon) => `/role-icons/${roleID}/${roleIcon}`;
module.exports.TEAM_ICON = (teamID, teamIcon) => `/team-icons/${teamID}/${teamIcon}`;
module.exports.USER_AVATAR = (userID, userAvatar) => `/avatars/${userID}/${userAvatar}`;
// Client Endpoints
module.exports.MESSAGE_LINK = (guildID, channelID, messageID) => `/channels/${guildID}/${channelID}/${messageID}`;

451
node_modules/eris/lib/rest/RequestHandler.js generated vendored Normal file
View File

@ -0,0 +1,451 @@
"use strict";
const util = require("util");
const Base = require("../structures/Base");
const DiscordHTTPError = require("../errors/DiscordHTTPError");
const DiscordRESTError = require("../errors/DiscordRESTError");
const Endpoints = require("./Endpoints");
const HTTPS = require("https");
const MultipartData = require("../util/MultipartData");
const SequentialBucket = require("../util/SequentialBucket");
const Zlib = require("zlib");
/**
* Handles API requests
*/
class RequestHandler {
constructor(client, options) {
// [DEPRECATED] Previously forceQueueing
if(typeof options === "boolean") {
options = {
forceQueueing: options
};
}
this.options = options = Object.assign({
agent: client.options.agent || null,
baseURL: Endpoints.BASE_URL,
decodeReasons: true,
disableLatencyCompensation: false,
domain: "discord.com",
latencyThreshold: client.options.latencyThreshold || 30000,
ratelimiterOffset: client.options.ratelimiterOffset || 0,
requestTimeout: client.options.requestTimeout || 15000
}, options);
this._client = client;
this.userAgent = `DiscordBot (https://github.com/abalabahaha/eris, ${require("../../package.json").version})`;
this.ratelimits = {};
this.latencyRef = {
latency: this.options.ratelimiterOffset,
raw: new Array(10).fill(this.options.ratelimiterOffset),
timeOffset: 0,
timeOffsets: new Array(10).fill(0),
lastTimeOffsetCheck: 0
};
this.globalBlock = false;
this.readyQueue = [];
if(this.options.forceQueueing) {
this.globalBlock = true;
this._client.once("shardPreReady", () => this.globalUnblock());
}
}
globalUnblock() {
this.globalBlock = false;
while(this.readyQueue.length > 0) {
this.readyQueue.shift()();
}
}
/**
* Make an API request
* @arg {String} method Uppercase HTTP method
* @arg {String} url URL of the endpoint
* @arg {Boolean} [auth] Whether to add the Authorization header and token or not
* @arg {Object} [body] Request payload
* @arg {Object} [file] File object
* @arg {Buffer} file.file A buffer containing file data
* @arg {String} file.name What to name the file
* @returns {Promise<Object>} Resolves with the returned JSON data
*/
request(method, url, auth, body, file, _route, short) {
const route = _route || this.routefy(url, method);
const _stackHolder = {}; // Preserve async stack
Error.captureStackTrace(_stackHolder);
return new Promise((resolve, reject) => {
let attempts = 0;
const actualCall = (cb) => {
const headers = {
"User-Agent": this.userAgent,
"Accept-Encoding": "gzip,deflate"
};
let data;
let finalURL = url;
try {
if(auth) {
headers.Authorization = this._client._token;
}
if(body && body.reason) { // Audit log reason sniping
let unencodedReason = body.reason;
if(this.options.decodeReasons) {
try {
if(unencodedReason.includes("%") && !unencodedReason.includes(" ")) {
unencodedReason = decodeURIComponent(unencodedReason);
}
} catch(err) {
this._client.emit("error", err);
}
}
headers["X-Audit-Log-Reason"] = encodeURIComponent(unencodedReason);
if((method !== "PUT" || !url.includes("/bans")) && (method !== "POST" || !url.includes("/prune"))) {
delete body.reason;
} else {
body.reason = unencodedReason;
}
}
if(file) {
if(Array.isArray(file)) {
data = new MultipartData();
headers["Content-Type"] = "multipart/form-data; boundary=" + data.boundary;
file.forEach(function(f) {
if(!f.file) {
return;
}
data.attach(f.name, f.file, f.name);
});
if(body) {
data.attach("payload_json", body);
}
data = data.finish();
} else if(file.file) {
data = new MultipartData();
headers["Content-Type"] = "multipart/form-data; boundary=" + data.boundary;
data.attach("file", file.file, file.name);
if(body) {
if(method === "POST" && url.endsWith("/stickers")) {
for(const key in body) {
data.attach(key, body[key]);
}
} else {
data.attach("payload_json", body);
}
}
data = data.finish();
} else {
throw new Error("Invalid file object");
}
} else if(body) {
if(method === "GET" || method === "DELETE") {
let qs = "";
Object.keys(body).forEach(function(key) {
if(body[key] != undefined) {
if(Array.isArray(body[key])) {
body[key].forEach(function(val) {
qs += `&${encodeURIComponent(key)}=${encodeURIComponent(val)}`;
});
} else {
qs += `&${encodeURIComponent(key)}=${encodeURIComponent(body[key])}`;
}
}
});
finalURL += "?" + qs.substring(1);
} else {
// Replacer function serializes bigints to strings, the format Discord uses
data = JSON.stringify(body, (k, v) => typeof v === "bigint" ? v.toString() : v);
headers["Content-Type"] = "application/json";
}
}
} catch(err) {
cb();
reject(err);
return;
}
let req;
try {
req = HTTPS.request({
method: method,
host: this.options.domain,
path: this.options.baseURL + finalURL,
headers: headers,
agent: this.options.agent
});
} catch(err) {
cb();
reject(err);
return;
}
let reqError;
req.once("abort", () => {
cb();
reqError = reqError || new Error(`Request aborted by client on ${method} ${url}`);
reqError.req = req;
reject(reqError);
}).once("error", (err) => {
reqError = err;
req.abort();
});
let latency = Date.now();
req.once("response", (resp) => {
latency = Date.now() - latency;
if(!this.options.disableLatencyCompensation) {
this.latencyRef.raw.push(latency);
this.latencyRef.latency = this.latencyRef.latency - ~~(this.latencyRef.raw.shift() / 10) + ~~(latency / 10);
}
if(this._client.listeners("rawREST").length) {
/**
* Fired when the Client's RequestHandler receives a response
* @event Client#rawREST
* @prop {Object} [request] The data for the request.
* @prop {Boolean} request.auth True if the request required an authorization token
* @prop {Object} [request.body] The request payload
* @prop {Object} [request.file] The file object sent in the request
* @prop {Buffer} request.file.file A buffer containing file data
* @prop {String} request.file.name The name of the file
* @prop {Number} request.latency The HTTP response latency
* @prop {String} request.method Uppercase HTTP method
* @prop {IncomingMessage} request.resp The HTTP response to the request
* @prop {String} request.route The calculated ratelimiting route for the request
* @prop {Boolean} request.short Whether or not the request was prioritized in its ratelimiting queue
* @prop {String} request.url URL of the endpoint
*/
this._client.emit("rawREST", {method, url, auth, body, file, route, short, resp, latency});
}
const headerNow = Date.parse(resp.headers["date"]);
if(this.latencyRef.lastTimeOffsetCheck < Date.now() - 5000) {
const timeOffset = headerNow + 500 - (this.latencyRef.lastTimeOffsetCheck = Date.now());
if(this.latencyRef.timeOffset - this.latencyRef.latency >= this.options.latencyThreshold && timeOffset - this.latencyRef.latency >= this.options.latencyThreshold) {
this._client.emit("warn", new Error(`Your clock is ${this.latencyRef.timeOffset}ms behind Discord's server clock. Please check your connection and system time.`));
}
this.latencyRef.timeOffset = this.latencyRef.timeOffset - ~~(this.latencyRef.timeOffsets.shift() / 10) + ~~(timeOffset / 10);
this.latencyRef.timeOffsets.push(timeOffset);
}
resp.once("aborted", () => {
cb();
reqError = reqError || new Error(`Request aborted by server on ${method} ${url}`);
reqError.req = req;
reject(reqError);
});
let response = "";
let _respStream = resp;
if(resp.headers["content-encoding"]) {
if(resp.headers["content-encoding"].includes("gzip")) {
_respStream = resp.pipe(Zlib.createGunzip());
} else if(resp.headers["content-encoding"].includes("deflate")) {
_respStream = resp.pipe(Zlib.createInflate());
}
}
_respStream.on("data", (str) => {
response += str;
}).on("error", (err) => {
reqError = err;
req.abort();
}).once("end", () => {
const now = Date.now();
if(resp.headers["x-ratelimit-limit"]) {
this.ratelimits[route].limit = +resp.headers["x-ratelimit-limit"];
}
if(method !== "GET" && (resp.headers["x-ratelimit-remaining"] == undefined || resp.headers["x-ratelimit-limit"] == undefined) && this.ratelimits[route].limit !== 1) {
this._client.emit("debug", `Missing ratelimit headers for SequentialBucket(${this.ratelimits[route].remaining}/${this.ratelimits[route].limit}) with non-default limit\n`
+ `${resp.statusCode} ${resp.headers["content-type"]}: ${method} ${route} | ${resp.headers["cf-ray"]}\n`
+ "content-type = " + + "\n"
+ "x-ratelimit-remaining = " + resp.headers["x-ratelimit-remaining"] + "\n"
+ "x-ratelimit-limit = " + resp.headers["x-ratelimit-limit"] + "\n"
+ "x-ratelimit-reset = " + resp.headers["x-ratelimit-reset"] + "\n"
+ "x-ratelimit-global = " + resp.headers["x-ratelimit-global"]);
}
this.ratelimits[route].remaining = resp.headers["x-ratelimit-remaining"] === undefined ? 1 : +resp.headers["x-ratelimit-remaining"] || 0;
const retryAfter = Number(resp.headers["x-ratelimit-reset-after"] || resp.headers["retry-after"]) * 1000;
if(retryAfter >= 0) {
if(resp.headers["x-ratelimit-global"]) {
this.globalBlock = true;
setTimeout(() => this.globalUnblock(), retryAfter || 1);
} else {
this.ratelimits[route].reset = (retryAfter || 1) + now;
}
} else if(resp.headers["x-ratelimit-reset"]) {
let resetTime = +resp.headers["x-ratelimit-reset"] * 1000;
if(route.endsWith("/reactions/:id") && (+resp.headers["x-ratelimit-reset"] * 1000 - headerNow) === 1000) {
resetTime = now + 250;
}
this.ratelimits[route].reset = Math.max(resetTime - this.latencyRef.latency, now);
} else {
this.ratelimits[route].reset = now;
}
if(resp.statusCode !== 429) {
const content = typeof body === "object" ? `${body.content} ` : "";
this._client.emit("debug", `${content}${now} ${route} ${resp.statusCode}: ${latency}ms (${this.latencyRef.latency}ms avg) | ${this.ratelimits[route].remaining}/${this.ratelimits[route].limit} left | Reset ${this.ratelimits[route].reset} (${this.ratelimits[route].reset - now}ms left)`);
}
if(resp.statusCode >= 300) {
if(resp.statusCode === 429) {
const content = typeof body === "object" ? `${body.content} ` : "";
let delay = retryAfter;
if(resp.headers["x-ratelimit-scope"] === "shared") {
try {
delay = JSON.parse(response).retry_after * 1000;
} catch(err) {
reject(err);
return;
}
}
this._client.emit("debug", `${resp.headers["x-ratelimit-global"] ? "Global" : "Unexpected"} 429 (╯°□°)╯︵ ┻━┻: ${response}\n${content} ${now} ${route} ${resp.statusCode}: ${latency}ms (${this.latencyRef.latency}ms avg) | ${this.ratelimits[route].remaining}/${this.ratelimits[route].limit} left | Reset ${delay} (${this.ratelimits[route].reset - now}ms left) | Scope ${resp.headers["x-ratelimit-scope"]}`);
if(delay) {
setTimeout(() => {
cb();
this.request(method, url, auth, body, file, route, true).then(resolve).catch(reject);
}, delay);
return;
} else {
cb();
this.request(method, url, auth, body, file, route, true).then(resolve).catch(reject);
return;
}
} else if(resp.statusCode === 502 && ++attempts < 4) {
this._client.emit("debug", "A wild 502 appeared! Thanks CloudFlare!");
setTimeout(() => {
this.request(method, url, auth, body, file, route, true).then(resolve).catch(reject);
}, Math.floor(Math.random() * 1900 + 100));
return cb();
}
cb();
if(response.length > 0) {
if(resp.headers["content-type"] === "application/json") {
try {
response = JSON.parse(response);
} catch(err) {
reject(err);
return;
}
}
}
let {stack} = _stackHolder;
if(stack.startsWith("Error\n")) {
stack = stack.substring(6);
}
let err;
if(response.code) {
err = new DiscordRESTError(req, resp, response, stack);
} else {
err = new DiscordHTTPError(req, resp, response, stack);
}
reject(err);
return;
}
if(response.length > 0) {
if(resp.headers["content-type"] === "application/json") {
try {
response = JSON.parse(response);
} catch(err) {
cb();
reject(err);
return;
}
}
}
cb();
resolve(response);
});
});
req.setTimeout(this.options.requestTimeout, () => {
reqError = new Error(`Request timed out (>${this.options.requestTimeout}ms) on ${method} ${url}`);
req.abort();
});
if(Array.isArray(data)) {
for(const chunk of data) {
req.write(chunk);
}
req.end();
} else {
req.end(data);
}
};
if(this.globalBlock && auth) {
this.readyQueue.push(() => {
if(!this.ratelimits[route]) {
this.ratelimits[route] = new SequentialBucket(1, this.latencyRef);
}
this.ratelimits[route].queue(actualCall, short);
});
} else {
if(!this.ratelimits[route]) {
this.ratelimits[route] = new SequentialBucket(1, this.latencyRef);
}
this.ratelimits[route].queue(actualCall, short);
}
});
}
routefy(url, method) {
let route = url.replace(/\/([a-z-]+)\/(?:[0-9]{17,19})/g, function(match, p) {
return p === "channels" || p === "guilds" || p === "webhooks" ? match : `/${p}/:id`;
}).replace(/\/reactions\/[^/]+/g, "/reactions/:id").replace(/\/reactions\/:id\/[^/]+/g, "/reactions/:id/:userID").replace(/^\/webhooks\/(\d+)\/[A-Za-z0-9-_]{64,}/, "/webhooks/$1/:token");
if(method === "DELETE" && route.endsWith("/messages/:id")) {
const messageID = url.slice(url.lastIndexOf("/") + 1);
const createdAt = Base.getCreatedAt(messageID);
if(Date.now() - this.latencyRef.latency - createdAt >= 1000 * 60 * 60 * 24 * 14) {
method += "_OLD";
} else if(Date.now() - this.latencyRef.latency - createdAt <= 1000 * 10) {
method += "_NEW";
}
route = method + route;
} else if(method === "GET" && /\/guilds\/[0-9]+\/channels$/.test(route)) {
route = "/guilds/:id/channels";
}
if(method === "PUT" || method === "DELETE") {
const index = route.indexOf("/reactions");
if(index !== -1) {
route = "MODIFY" + route.slice(0, index + 10);
}
}
return route;
}
[util.inspect.custom]() {
return Base.prototype[util.inspect.custom].call(this);
}
toString() {
return "[RequestHandler]";
}
toJSON(props = []) {
return Base.prototype.toJSON.call(this, [
"globalBlock",
"latencyRef",
"options",
"ratelimits",
"readyQueue",
"userAgent",
...props
]);
}
}
module.exports = RequestHandler;

View File

@ -0,0 +1,93 @@
"use strict";
const Interaction = require("./Interaction");
const Member = require("./Member");
const Permission = require("./Permission");
const {InteractionResponseTypes} = require("../Constants");
/**
* Represents an application command autocomplete interaction. See Interaction for more properties.
* @extends Interaction
* @prop {Permission?} appPermissions The permissions the app or bot has within the channel the interaction was sent from
* @prop {PrivateChannel | TextChannel | NewsChannel} channel The channel the interaction was created in. Can be partial with only the id if the channel is not cached.
* @prop {Object} data The data attached to the interaction
* @prop {String} data.id The ID of the Application Command
* @prop {String} data.name The command name
* @prop {Number} data.type The [command type](https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-types)
* @prop {String?} data.target_id The id the of user or message targetted by a context menu command
* @prop {Array<Object>?} data.options The run Application Command options
* @prop {String} data.options[].name The name of the Application Command option
* @prop {Number} data.options[].type Command option type, 1-10
* @prop {(String | Number | Boolean)?} data.options[].value The value of the run Application Command (Mutually exclusive with options)
* @prop {Boolean?} data.options[].focused Whether or not the option is focused
* @prop {Array<Object>?} data.options[].options The run Application Command options (Mutually exclusive with value)
* @prop {String?} guildID The ID of the guild in which the interaction was created
* @prop {Member?} member The member who triggered the interaction (This is only sent when the interaction is invoked within a guild)
* @prop {User?} user The user who triggered the interaction (This is only sent when the interaction is invoked within a dm)
*/
class AutocompleteInteraction extends Interaction {
constructor(info, client) {
super(info, client);
this.channel = this._client.getChannel(info.channel_id) || {
id: info.channel_id
};
this.data = info.data;
if(info.guild_id !== undefined) {
this.guildID = info.guild_id;
}
if(info.member !== undefined) {
if(this.channel.guild) {
info.member.id = info.member.user.id;
this.member = this.channel.guild.members.update(info.member, this.channel.guild);
} else {
const guild = this._client.guilds.get(info.guild_id);
this.member = new Member(info.member, guild, this._client);
}
}
if(info.user !== undefined) {
this.user = this._client.users.update(info.user, client);
}
if(info.app_permissions !== undefined) {
this.appPermissions = new Permission(info.app_permissions);
}
}
/**
* Acknowledges the autocomplete interaction with a result of choices.
* Note: You can **not** use more than 1 initial interaction response per interaction.
* @arg {Array<Object>} choices The autocomplete choices to return to the user
* @arg {String | Number} choices[].name The choice display name
* @arg {String} choices[].value The choice value to return to the bot
* @returns {Promise}
*/
async acknowledge(choices) {
return this.result(choices);
}
/**
* Acknowledges the autocomplete interaction with a result of choices.
* Note: You can **not** use more than 1 initial interaction response per interaction.
* @arg {Array<Object>} choices The autocomplete choices to return to the user
* @arg {String | Number} choices[].name The choice display name
* @arg {String} choices[].value The choice value to return to the bot
* @returns {Promise}
*/
async result(choices) {
if(this.acknowledged === true) {
throw new Error("You have already acknowledged this interaction.");
}
return this._client.createInteractionResponse.call(this._client, this.id, this.token, {
type: InteractionResponseTypes.APPLICATION_COMMAND_AUTOCOMPLETE_RESULT,
data: {choices}
}).then(() => this.update());
}
}
module.exports = AutocompleteInteraction;

81
node_modules/eris/lib/structures/Base.js generated vendored Normal file
View File

@ -0,0 +1,81 @@
"use strict";
const util = require("util");
/**
* Provides utilities for working with many Discord structures
* @prop {string} id A Discord snowflake identifying the object
* @prop {Number} createdAt Timestamp of structure creation
*/
class Base {
constructor(id) {
if(id) {
this.id = id;
}
}
get createdAt() {
return Base.getCreatedAt(this.id);
}
/**
* Calculates the timestamp in milliseconds associated with a Discord ID/snowflake
* @param {String} id The ID of a structure
* @returns {Number}
*/
static getCreatedAt(id) {
return Base.getDiscordEpoch(id) + 1420070400000;
}
/**
* Gets the number of milliseconds since epoch represented by an ID/snowflake
* @param {string} id The ID of a structure
* @returns {number}
*/
static getDiscordEpoch(id) {
return Math.floor(id / 4194304);
}
[util.inspect.custom]() {
// http://stackoverflow.com/questions/5905492/dynamic-function-name-in-javascript
const copy = new {[this.constructor.name]: class {}}[this.constructor.name]();
for(const key in this) {
if(this.hasOwnProperty(key) && !key.startsWith("_") && this[key] !== undefined) {
copy[key] = this[key];
}
}
return copy;
}
toString() {
return `[${this.constructor.name} ${this.id}]`;
}
toJSON(props = []) {
const json = {};
if(this.id) {
json.id = this.id;
json.createdAt = this.createdAt;
}
for(const prop of props) {
const value = this[prop];
const type = typeof value;
if(value === undefined) {
continue;
} else if(type !== "object" && type !== "function" && type !== "bigint" || value === null) {
json[prop] = value;
} else if(value.toJSON !== undefined) {
json[prop] = value.toJSON();
} else if(value.values !== undefined) {
json[prop] = [...value.values()];
} else if(type === "bigint") {
json[prop] = value.toString();
} else if(type === "object") {
json[prop] = value;
}
}
return json;
}
}
module.exports = Base;

76
node_modules/eris/lib/structures/Call.js generated vendored Normal file
View File

@ -0,0 +1,76 @@
"use strict";
const Base = require("./Base");
const Collection = require("../util/Collection");
const VoiceState = require("./VoiceState");
/**
* Represents a call
* @prop {GroupChannel} channel The call channel
* @prop {Number} createdAt Timestamp of the call's creation
* @prop {Number?} endedTimestamp The timestamp of the call end
* @prop {String} id The ID of the call
* @prop {Array<String>} participants The IDs of the call participants
* @prop {String?} region The region of the call server
* @prop {Array<String>?} ringing The IDs of people that still have not responded to the call request
* @prop {Boolean} unavailable Whether the call is unavailable or not
* @prop {Collection<VoiceState>} voiceStates The voice states of the call participants
*/
class Call extends Base {
constructor(data, channel) {
super(data.id);
this.channel = channel;
this.voiceStates = new Collection(VoiceState);
this.ringing = [];
this.participants = [];
this.region = null;
this.endedTimestamp = null;
this.unavailable = true;
this.update(data);
}
update(data) {
if(data.participants !== undefined) {
this.participants = data.participants;
}
if(data.ringing !== undefined) {
if(!this.ringing.includes(this.channel._client.user.id) && (this.ringing = data.ringing).includes(this.channel._client.user.id)) {
/**
* Fired when the bot user is rung in a call
* @event Client#callRing
* @prop {Call} call The call
*/
this.channel._client.emit("callRing", this);
}
}
if(data.region !== undefined) {
this.region = data.region;
}
if(data.ended_timestamp !== undefined) {
this.endedTimestamp = Date.parse(data.ended_timestamp);
}
if(data.unavailable !== undefined) {
this.unavailable = data.unavailable;
}
if(data.voice_states) {
data.voice_states.forEach((voiceState) => {
voiceState.id = voiceState.user_id;
this.voiceStates.add(voiceState);
});
}
}
toJSON(props = []) {
return super.toJSON([
"endedTimestamp",
"participants",
"region",
"ringing",
"unavailable",
"voiceStates",
...props
]);
}
}
module.exports = Call;

25
node_modules/eris/lib/structures/CategoryChannel.js generated vendored Normal file
View File

@ -0,0 +1,25 @@
"use strict";
const Collection = require("../util/Collection");
const GuildChannel = require("./GuildChannel");
/**
* Represents a guild category channel. See GuildChannel for more properties and methods.
* @extends GuildChannel
* @prop {Collection<GuildChannel>} channels A collection of guild channels that are part of this category
*/
class CategoryChannel extends GuildChannel {
get channels() {
const channels = new Collection(GuildChannel);
if(this.guild && this.guild.channels) {
for(const channel of this.guild.channels.values()) {
if(channel.parentID === this.id) {
channels.add(channel);
}
}
}
return channels;
}
}
module.exports = CategoryChannel;

95
node_modules/eris/lib/structures/Channel.js generated vendored Normal file
View File

@ -0,0 +1,95 @@
"use strict";
const Base = require("./Base");
const {ChannelTypes} = require("../Constants");
/**
* Represents a channel. You also probably want to look at CategoryChannel, GroupChannel, NewsChannel, PrivateChannel, TextChannel, and TextVoiceChannel.
* @prop {Client} client The client that initialized the channel
* @prop {Number} createdAt Timestamp of the channel's creation
* @prop {String} id The ID of the channel
* @prop {String} mention A string that mentions the channel
* @prop {Number} type The type of the channel
*/
class Channel extends Base {
constructor(data, client) {
super(data.id);
this.type = data.type;
this.client = client;
}
get mention() {
return `<#${this.id}>`;
}
static from(data, client) {
switch(data.type) {
case ChannelTypes.GUILD_TEXT: {
return new TextChannel(data, client);
}
case ChannelTypes.DM: {
return new PrivateChannel(data, client);
}
case ChannelTypes.GUILD_VOICE: {
return new TextVoiceChannel(data, client);
}
case ChannelTypes.GROUP_DM: {
return new GroupChannel(data, client);
}
case ChannelTypes.GUILD_CATEGORY: {
return new CategoryChannel(data, client);
}
case ChannelTypes.GUILD_NEWS: {
return new NewsChannel(data, client);
}
case ChannelTypes.GUILD_STORE: {
return new StoreChannel(data, client);
}
case ChannelTypes.GUILD_NEWS_THREAD: {
return new NewsThreadChannel(data, client);
}
case ChannelTypes.GUILD_PUBLIC_THREAD: {
return new PublicThreadChannel(data, client);
}
case ChannelTypes.GUILD_PRIVATE_THREAD: {
return new PrivateThreadChannel(data, client);
}
case ChannelTypes.GUILD_STAGE_VOICE: {
return new StageChannel(data, client);
}
}
if(data.guild_id) {
if(data.last_message_id !== undefined) {
client.emit("warn", new Error(`Unknown guild text channel type: ${data.type}\n${JSON.stringify(data)}`));
return new TextChannel(data, client);
}
client.emit("warn", new Error(`Unknown guild channel type: ${data.type}\n${JSON.stringify(data)}`));
return new GuildChannel(data, client);
}
client.emit("warn", new Error(`Unknown channel type: ${data.type}\n${JSON.stringify(data)}`));
return new Channel(data, client);
}
toJSON(props = []) {
return super.toJSON([
"type",
...props
]);
}
}
module.exports = Channel;
// Circular import
const CategoryChannel = require("./CategoryChannel");
const GuildChannel = require("./GuildChannel");
const GroupChannel = require("./GroupChannel");
const NewsChannel = require("./NewsChannel");
const NewsThreadChannel = require("./NewsThreadChannel");
const PrivateChannel = require("./PrivateChannel");
const PrivateThreadChannel = require("./PrivateThreadChannel");
const PublicThreadChannel = require("./PublicThreadChannel");
const StageChannel = require("./StageChannel");
const StoreChannel = require("./StoreChannel");
const TextChannel = require("./TextChannel");
const TextVoiceChannel = require("./TextVoiceChannel");

406
node_modules/eris/lib/structures/CommandInteraction.js generated vendored Normal file
View File

@ -0,0 +1,406 @@
"use strict";
const Interaction = require("./Interaction");
const Member = require("./Member");
const User = require("./User");
const Role = require("./Role");
const Channel = require("./Channel");
const Message = require("./Message");
const Collection = require("../util/Collection");
const Permission = require("./Permission");
const {InteractionResponseTypes} = require("../Constants");
/**
* Represents an application command interaction. See Interaction for more properties.
* @extends Interaction
* @prop {Permission?} appPermissions The permissions the app or bot has within the channel the interaction was sent from
* @prop {PrivateChannel | TextChannel | NewsChannel} channel The channel the interaction was created in. Can be partial with only the id if the channel is not cached.
* @prop {Object} data The data attached to the interaction
* @prop {String} data.id The ID of the Application Command
* @prop {String} data.name The command name
* @prop {Number} data.type The [command type](https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-types)
* @prop {String?} data.target_id The id the of user or message targetted by a context menu command
* @prop {Array<Object>?} data.options The run Application Command options
* @prop {String} data.options[].name The name of the Application Command option
* @prop {Number} data.options[].type Command option type, 1-10
* @prop {(String | Number | Boolean)?} data.options[].value The value of the run Application Command (Mutually exclusive with options)
* @prop {Array<Object>?} data.options[].options The run Application Command options (Mutually exclusive with value)
* @prop {Object?} data.resolved converted users + roles + channels
* @prop {Collection<User>?} data.resolved.users converted users
* @prop {Collection<Member>?} data.resolved.members converted members
* @prop {Collection<Role>?} data.resolved.roles converted roles
* @prop {Collection<Channel>?} data.resolved.channels converted channels
* @prop {String?} guildID The ID of the guild in which the interaction was created
* @prop {Member?} member The member who triggered the interaction (This is only sent when the interaction is invoked within a guild)
* @prop {User?} user The user who triggered the interaction (This is only sent when the interaction is invoked within a dm)
*/
class CommandInteraction extends Interaction {
constructor(info, client) {
super(info, client);
this.channel = this._client.getChannel(info.channel_id) || {
id: info.channel_id
};
this.data = JSON.parse(JSON.stringify(info.data));
if(info.data.resolved !== undefined) {
//Users
if(info.data.resolved.users !== undefined) {
const usermap = new Collection(User);
Object.entries(info.data.resolved.users).forEach(([id, user]) => {
usermap.set(id, this._client.users.update(user, client));
});
this.data.resolved.users = usermap;
}
//Members
if(info.data.resolved.members !== undefined) {
const membermap = new Collection(Member);
Object.entries(info.data.resolved.members).forEach(([id, member]) => {
member.id = id;
member.user = {id};
if(this.channel.guild) {
membermap.set(id, this.channel.guild.members.update(member, this.channel.guild));
} else {
const guild = this._client.guilds.get(info.guild_id);
if(guild) {
membermap.set(id, guild.members.update(member, guild));
} else {
membermap.set(id, new Member(member, guild, this._client));
}
}
});
this.data.resolved.members = membermap;
}
//Roles
if(info.data.resolved.roles !== undefined) {
const rolemap = new Collection(Role);
Object.entries(info.data.resolved.roles).forEach(([id, role]) => {
rolemap.set(id, new Role(role, this._client));
});
this.data.resolved.roles = rolemap;
}
//Channels
if(info.data.resolved.channels !== undefined) {
const channelmap = new Collection(Channel);
Object.entries(info.data.resolved.channels).forEach(([id, channel]) => {
channelmap.set(id, new Channel(channel, this._client));
});
this.data.resolved.channels = channelmap;
}
//Messages
if(info.data.resolved.messages !== undefined) {
const messagemap = new Collection(Message);
Object.entries(info.data.resolved.messages).forEach(([id, message]) => {
messagemap.set(id, new Message(message, this._client));
});
this.data.resolved.messages = messagemap;
}
}
if(info.guild_id !== undefined) {
this.guildID = info.guild_id;
}
if(info.member !== undefined) {
if(this.channel.guild) {
info.member.id = info.member.user.id;
this.member = this.channel.guild.members.update(info.member, this.channel.guild);
} else {
const guild = this._client.guilds.get(info.guild_id);
this.member = new Member(info.member, guild, this._client);
}
}
if(info.user !== undefined) {
this.user = this._client.users.update(info.user, client);
}
if(info.app_permissions !== undefined) {
this.appPermissions = new Permission(info.app_permissions);
}
}
/**
* Acknowledges the interaction with a defer response
* Note: You can **not** use more than 1 initial interaction response per interaction.
* @arg {Number} [flags] 64 for Ephemeral
* @returns {Promise}
*/
async acknowledge(flags) {
return this.defer(flags);
}
/**
* Respond to the interaction with a followup message
* @arg {String | Object} content A string or object. If an object is passed:
* @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default)
* @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here.
* @arg {Boolean | Array<String>} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow.
* @arg {Boolean | Array<String>} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow.
* @arg {Array<Object>} [content.components] An array of component objects
* @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only)
* @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only)
* @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2)
* @arg {String} [content.components[].label] The label to be displayed in the component (type 2)
* @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1)
* @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1)
* @arg {Array<Object>} [content.components[].options] The options for this component (type 3 only)
* @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected
* @arg {String} [content.components[].options[].description] The description for this option
* @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option
* @arg {String} content.components[].options[].label The label for this option
* @arg {Number | String} content.components[].options[].value The value for this option
* @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only)
* @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required
* @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu
* @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only)
* @arg {String} [content.content] A content string
* @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Array<Object>} [options.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Number} [content.flags] 64 for Ephemeral
* @arg {Boolean} [content.tts] Set the message TTS flag
* @arg {Object | Array<Object>} [file] A file object (or an Array of them)
* @arg {Buffer} file.file A buffer containing file data
* @arg {String} file.name What to name the file
* @returns {Promise<Message?>}
*/
async createFollowup(content, file) {
if(this.acknowledged === false) {
throw new Error("createFollowup cannot be used to acknowledge an interaction, please use acknowledge, createMessage, or defer first.");
}
if(content !== undefined) {
if(typeof content !== "object" || content === null) {
content = {
content: "" + content
};
} else if(content.content !== undefined && typeof content.content !== "string") {
content.content = "" + content.content;
}
}
if(file) {
content.file = file;
}
return this._client.executeWebhook.call(this._client, this.applicationID, this.token, Object.assign({wait: true}, content));
}
/**
* Acknowledges the interaction with a message. If already acknowledged runs createFollowup
* Note: You can **not** use more than 1 initial interaction response per interaction, use createFollowup if you have already responded with a different interaction response.
* @arg {String | Object} content A string or object. If an object is passed:
* @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default)
* @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here.
* @arg {Boolean | Array<String>} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow.
* @arg {Boolean | Array<String>} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow.
* @arg {Array<Object>} [content.components] An array of component objects
* @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only)
* @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only)
* @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2)
* @arg {String} [content.components[].label] The label to be displayed in the component (type 2)
* @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1)
* @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1)
* @arg {Array<Object>} [content.components[].options] The options for this component (type 3 only)
* @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected
* @arg {String} [content.components[].options[].description] The description for this option
* @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option
* @arg {String} content.components[].options[].label The label for this option
* @arg {Number | String} content.components[].options[].value The value for this option
* @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only)
* @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required
* @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu
* @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only)
* @arg {String} [content.content] A content string
* @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Array<Object>} [content.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Boolean} [content.flags] 64 for Ephemeral
* @arg {Boolean} [content.tts] Set the message TTS flag
* @arg {Object | Array<Object>} [file] A file object (or an Array of them)
* @arg {Buffer} file.file A buffer containing file data
* @arg {String} file.name What to name the file
* @returns {Promise}
*/
async createMessage(content, file) {
if(this.acknowledged === true) {
return this.createFollowup(content, file);
}
if(content !== undefined) {
if(typeof content !== "object" || content === null) {
content = {
content: "" + content
};
} else if(content.content !== undefined && typeof content.content !== "string") {
content.content = "" + content.content;
}
if(content.content !== undefined || content.embeds || content.allowedMentions) {
content.allowed_mentions = this._client._formatAllowedMentions(content.allowedMentions);
}
}
return this._client.createInteractionResponse.call(this._client, this.id, this.token, {
type: InteractionResponseTypes.CHANNEL_MESSAGE_WITH_SOURCE,
data: content
}, file).then(() => this.update());
}
/**
* Acknowledges the interaction with a defer response
* Note: You can **not** use more than 1 initial interaction response per interaction.
* @arg {Number} [flags] 64 for Ephemeral
* @returns {Promise}
*/
async defer(flags) {
if(this.acknowledged === true) {
throw new Error("You have already acknowledged this interaction.");
}
return this._client.createInteractionResponse.call(this._client, this.id, this.token, {
type: InteractionResponseTypes.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE,
data: {
flags: flags || 0
}
}).then(() => this.update());
}
/**
* Delete a message
* @arg {String} messageID the id of the message to delete, or "@original" for the original response.
* @returns {Promise}
*/
async deleteMessage(messageID) {
if(this.acknowledged === false) {
throw new Error("deleteMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, or defer first.");
}
return this._client.deleteWebhookMessage.call(this._client, this.applicationID, this.token, messageID);
}
/**
* Delete the Original message
* Warning: Will error with ephemeral messages.
* @returns {Promise}
*/
async deleteOriginalMessage() {
if(this.acknowledged === false) {
throw new Error("deleteOriginalMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, or defer first.");
}
return this._client.deleteWebhookMessage.call(this._client, this.applicationID, this.token, "@original");
}
/**
* Edit a message
* @arg {String} messageID the id of the message to edit, or "@original" for the original response.
* @arg {Object} content Interaction message edit options
* @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default)
* @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here.
* @arg {Boolean} [content.allowedMentions.repliedUser] Whether or not to mention the author of the message being replied to.
* @arg {Boolean | Array<String>} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow.
* @arg {Boolean | Array<String>} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow.
* @arg {Array<Object>} [content.components] An array of component objects
* @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only)
* @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only)
* @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2)
* @arg {String} [content.components[].label] The label to be displayed in the component (type 2)
* @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1)
* @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1)
* @arg {Array<Object>} [content.components[].options] The options for this component (type 3 only)
* @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected
* @arg {String} [content.components[].options[].description] The description for this option
* @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option
* @arg {String} content.components[].options[].label The label for this option
* @arg {Number | String} content.components[].options[].value The value for this option
* @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only)
* @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required
* @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu
* @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only)
* @arg {String} [content.content] A content string
* @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Array<Object>} [content.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Object | Array<Object>} [file] A file object (or an Array of them)
* @arg {Buffer} file.file A buffer containing file data
* @arg {String} file.name What to name the file
* @returns {Promise<Message>}
*/
async editMessage(messageID, content, file) {
if(this.acknowledged === false) {
throw new Error("editMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, or defer first.");
}
if(content !== undefined) {
if(typeof content !== "object" || content === null) {
content = {
content: "" + content
};
} else if(content.content !== undefined && typeof content.content !== "string") {
content.content = "" + content.content;
}
}
if(file) {
content.file = file;
}
return this._client.editWebhookMessage.call(this._client, this.applicationID, this.token, messageID, content);
}
/**
* Edit the Original response message
* @arg {Object} content Interaction message edit options
* @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default)
* @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here.
* @arg {Boolean} [content.allowedMentions.repliedUser] Whether or not to mention the author of the message being replied to.
* @arg {Boolean | Array<String>} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow.
* @arg {Boolean | Array<String>} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow.
* @arg {Array<Object>} [content.components] An array of component objects
* @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only)
* @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only)
* @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2)
* @arg {String} [content.components[].label] The label to be displayed in the component (type 2)
* @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1)
* @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1)
* @arg {Array<Object>} [content.components[].options] The options for this component (type 3 only)
* @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected
* @arg {String} [content.components[].options[].description] The description for this option
* @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option
* @arg {String} content.components[].options[].label The label for this option
* @arg {Number | String} content.components[].options[].value The value for this option
* @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only)
* @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required
* @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu
* @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only)
* @arg {String} [content.content] A content string
* @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Array<Object>} [content.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Object | Array<Object>} [file] A file object (or an Array of them)
* @arg {Buffer} file.file A buffer containing file data
* @arg {String} file.name What to name the file
* @returns {Promise<Message>}
*/
async editOriginalMessage(content, file) {
if(this.acknowledged === false) {
throw new Error("editOriginalMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, or defer first.");
}
if(content !== undefined) {
if(typeof content !== "object" || content === null) {
content = {
content: "" + content
};
} else if(content.content !== undefined && typeof content.content !== "string") {
content.content = "" + content.content;
}
}
if(file) {
content.file = file;
}
return this._client.editWebhookMessage.call(this._client, this.applicationID, this.token, "@original", content);
}
/**
* Get the Original response message
* Warning: Will error with ephemeral messages.
* @returns {Promise<Message>}
*/
async getOriginalMessage() {
if(this.acknowledged === false) {
throw new Error("getOriginalMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, or defer first.");
}
return this._client.getWebhookMessage.call(this._client, this.applicationID, this.token, "@original");
}
}
module.exports = CommandInteraction;

View File

@ -0,0 +1,412 @@
"use strict";
const Interaction = require("./Interaction");
const Message = require("./Message");
const Member = require("./Member");
const Permission = require("./Permission");
const {InteractionResponseTypes} = require("../Constants");
/**
* Represents a message component interaction. See Interaction for more properties.
* @extends Interaction
* @prop {Permission?} appPermissions The permissions the app or bot has within the channel the interaction was sent from
* @prop {PrivateChannel | TextChannel | NewsChannel} channel The channel the interaction was created in. Can be partial with only the id if the channel is not cached.
* @prop {Object} data The data attached to the interaction
* @prop {Number} data.component_type The type of Message Component
* @prop {String} data.custom_id The ID of the Message Component
* @prop {Array<String>?} data.values The value of the run selected options (Select Menus Only)
* @prop {String?} guildID The ID of the guild in which the interaction was created
* @prop {Member?} member The member who triggered the interaction (This is only sent when the interaction is invoked within a guild)
* @prop {Message?} message The message the interaction came from.
* @prop {User?} user The user who triggered the interaction (This is only sent when the interaction is invoked within a dm)
*/
class ComponentInteraction extends Interaction {
constructor(info, client) {
super(info, client);
this.channel = this._client.getChannel(info.channel_id) || {
id: info.channel_id
};
this.data = info.data;
if(info.guild_id !== undefined) {
this.guildID = info.guild_id;
}
if(info.member !== undefined) {
if(this.channel.guild) {
info.member.id = info.member.user.id;
this.member = this.channel.guild.members.update(info.member, this.channel.guild);
} else {
const guild = this._client.guilds.get(info.guild_id);
this.member = new Member(info.member, guild, this._client);
}
}
if(info.message !== undefined) {
this.message = new Message(info.message, this._client);
}
if(info.user !== undefined) {
this.user = this._client.users.update(info.user, client);
}
if(info.app_permissions !== undefined) {
this.appPermissions = new Permission(info.app_permissions);
}
}
/**
* Acknowledges the interaction with a defer message update response
* @returns {Promise}
*/
async acknowledge() {
return this.deferUpdate();
}
/**
* Respond to the interaction with a followup message
* @arg {String | Object} content A string or object. If an object is passed:
* @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default)
* @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here.
* @arg {Boolean | Array<String>} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow.
* @arg {Boolean | Array<String>} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow.
* @arg {Array<Object>} [content.components] An array of component objects
* @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only)
* @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only)
* @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2)
* @arg {String} [content.components[].label] The label to be displayed in the component (type 2)
* @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1)
* @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1)
* @arg {Array<Object>} [content.components[].options] The options for this component (type 3 only)
* @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected
* @arg {String} [content.components[].options[].description] The description for this option
* @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option
* @arg {String} content.components[].options[].label The label for this option
* @arg {Number | String} content.components[].options[].value The value for this option
* @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only)
* @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required
* @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu
* @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only)
* @arg {String} [content.content] A content string
* @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Array<Object>} [options.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Number} [content.flags] 64 for Ephemeral
* @arg {Boolean} [content.tts] Set the message TTS flag
* @arg {Object | Array<Object>} [file] A file object (or an Array of them)
* @arg {Buffer} file.file A buffer containing file data
* @arg {String} file.name What to name the file
* @returns {Promise<Message?>}
*/
async createFollowup(content, file) {
if(this.acknowledged === false) {
throw new Error("createFollowup cannot be used to acknowledge an interaction, please use acknowledge, createMessage, defer, deferUpdate, or editParent first.");
}
if(content !== undefined) {
if(typeof content !== "object" || content === null) {
content = {
content: "" + content
};
} else if(content.content !== undefined && typeof content.content !== "string") {
content.content = "" + content.content;
}
}
if(file) {
content.file = file;
}
return this._client.executeWebhook.call(this._client, this.applicationID, this.token, Object.assign({wait: true}, content));
}
/**
* Acknowledges the interaction with a message. If already acknowledged runs createFollowup
* Note: You can **not** use more than 1 initial interaction response per interaction, use createFollowup if you have already responded with a different interaction response.
* @arg {String | Object} content A string or object. If an object is passed:
* @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default)
* @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here.
* @arg {Boolean | Array<String>} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow.
* @arg {Boolean | Array<String>} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow.
* @arg {Array<Object>} [content.components] An array of component objects
* @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only)
* @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only)
* @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2)
* @arg {String} [content.components[].label] The label to be displayed in the component (type 2)
* @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1)
* @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1)
* @arg {Array<Object>} [content.components[].options] The options for this component (type 3 only)
* @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected
* @arg {String} [content.components[].options[].description] The description for this option
* @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option
* @arg {String} content.components[].options[].label The label for this option
* @arg {Number | String} content.components[].options[].value The value for this option
* @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only)
* @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required
* @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu
* @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only)
* @arg {String} [content.content] A content string
* @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Array<Object>} [content.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Boolean} [content.flags] 64 for Ephemeral
* @arg {Boolean} [content.tts] Set the message TTS flag
* @arg {Object | Array<Object>} [file] A file object (or an Array of them)
* @arg {Buffer} file.file A buffer containing file data
* @arg {String} file.name What to name the file
* @returns {Promise}
*/
async createMessage(content, file) {
if(this.acknowledged === true) {
return this.createFollowup(content, file);
}
if(content !== undefined) {
if(typeof content !== "object" || content === null) {
content = {
content: "" + content
};
} else if(content.content !== undefined && typeof content.content !== "string") {
content.content = "" + content.content;
}
if(content.content !== undefined || content.embeds || content.allowedMentions) {
content.allowed_mentions = this._client._formatAllowedMentions(content.allowedMentions);
}
}
return this._client.createInteractionResponse.call(this._client, this.id, this.token, {
type: InteractionResponseTypes.CHANNEL_MESSAGE_WITH_SOURCE,
data: content
}, file).then(() => this.update());
}
/**
* Acknowledges the interaction with a defer response
* Note: You can **not** use more than 1 initial interaction response per interaction.
* @arg {Number} [flags] 64 for Ephemeral
* @returns {Promise}
*/
async defer(flags) {
if(this.acknowledged === true) {
throw new Error("You have already acknowledged this interaction.");
}
return this._client.createInteractionResponse.call(this._client, this.id, this.token, {
type: InteractionResponseTypes.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE,
data: {
flags: flags || 0
}
}).then(() => this.update());
}
/**
* Acknowledges the interaction with a defer message update response
* Note: You can **not** use more than 1 initial interaction response per interaction.
* @returns {Promise}
*/
async deferUpdate() {
if(this.acknowledged === true) {
throw new Error("You have already acknowledged this interaction.");
}
return this._client.createInteractionResponse.call(this._client, this.id, this.token, {
type: InteractionResponseTypes.DEFERRED_UPDATE_MESSAGE
}).then(() => this.update());
}
/**
* Delete a message
* @arg {String} messageID the id of the message to delete, or "@original" for the original response.
* @returns {Promise}
*/
async deleteMessage(messageID) {
if(this.acknowledged === false) {
throw new Error("deleteMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, defer, deferUpdate, or editParent first.");
}
return this._client.deleteWebhookMessage.call(this._client, this.applicationID, this.token, messageID);
}
/**
* Delete the parent message
* Warning: Will error with ephemeral messages.
* @returns {Promise}
*/
async deleteOriginalMessage() {
if(this.acknowledged === false) {
throw new Error("deleteOriginalMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, defer, deferUpdate, or editParent first.");
}
return this._client.deleteWebhookMessage.call(this._client, this.applicationID, this.token, "@original");
}
/**
* Edit a message
* @arg {String} messageID the id of the message to edit, or "@original" for the original response.
* @arg {Object} content Interaction message edit options
* @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default)
* @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here.
* @arg {Boolean} [content.allowedMentions.repliedUser] Whether or not to mention the author of the message being replied to.
* @arg {Boolean | Array<String>} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow.
* @arg {Boolean | Array<String>} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow.
* @arg {Array<Object>} [content.components] An array of component objects
* @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only)
* @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only)
* @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2)
* @arg {String} [content.components[].label] The label to be displayed in the component (type 2)
* @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1)
* @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1)
* @arg {Array<Object>} [content.components[].options] The options for this component (type 3 only)
* @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected
* @arg {String} [content.components[].options[].description] The description for this option
* @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option
* @arg {String} content.components[].options[].label The label for this option
* @arg {Number | String} content.components[].options[].value The value for this option
* @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only)
* @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required
* @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu
* @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only)
* @arg {String} [content.content] A content string
* @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Array<Object>} [content.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Object | Array<Object>} [file] A file object (or an Array of them)
* @arg {Buffer} file.file A buffer containing file data
* @arg {String} file.name What to name the file
* @returns {Promise<Message>}
*/
async editMessage(messageID, content, file) {
if(this.acknowledged === false) {
throw new Error("editMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, defer, deferUpdate, or editParent first.");
}
if(content !== undefined) {
if(typeof content !== "object" || content === null) {
content = {
content: "" + content
};
} else if(content.content !== undefined && typeof content.content !== "string") {
content.content = "" + content.content;
}
}
if(file) {
content.file = file;
}
return this._client.editWebhookMessage.call(this._client, this.applicationID, this.token, messageID, content);
}
/**
* Edit the parent message
* @arg {Object} content Interaction message edit options
* @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default)
* @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here.
* @arg {Boolean} [content.allowedMentions.repliedUser] Whether or not to mention the author of the message being replied to.
* @arg {Boolean | Array<String>} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow.
* @arg {Boolean | Array<String>} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow.
* @arg {Array<Object>} [content.components] An array of component objects
* @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only)
* @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only)
* @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2)
* @arg {String} [content.components[].label] The label to be displayed in the component (type 2)
* @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1)
* @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1)
* @arg {Array<Object>} [content.components[].options] The options for this component (type 3 only)
* @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected
* @arg {String} [content.components[].options[].description] The description for this option
* @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option
* @arg {String} content.components[].options[].label The label for this option
* @arg {Number | String} content.components[].options[].value The value for this option
* @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only)
* @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required
* @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu
* @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only)
* @arg {String} [content.content] A content string
* @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Array<Object>} [content.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Object | Array<Object>} [file] A file object (or an Array of them)
* @arg {Buffer} file.file A buffer containing file data
* @arg {String} file.name What to name the file
* @returns {Promise<Message>}
*/
async editOriginalMessage(content, file) {
if(this.acknowledged === false) {
throw new Error("editOriginalMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, defer, deferUpdate, or editParent first.");
}
if(content !== undefined) {
if(typeof content !== "object" || content === null) {
content = {
content: "" + content
};
} else if(content.content !== undefined && typeof content.content !== "string") {
content.content = "" + content.content;
}
}
if(file) {
content.file = file;
}
return this._client.editWebhookMessage.call(this._client, this.applicationID, this.token, "@original", content);
}
/**
* Acknowledges the interaction by editing the parent message. If already acknowledged runs editOriginalMessage
* Note: You can **not** use more than 1 initial interaction response per interaction, use edit if you have already responded with a different interaction response.
* Warning: Will error with ephemeral messages.
* @arg {String | Object} content What to edit the message with
* @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default)
* @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here.
* @arg {Boolean} [content.allowedMentions.repliedUser] Whether or not to mention the author of the message being replied to.
* @arg {Boolean | Array<String>} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow.
* @arg {Boolean | Array<String>} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow.
* @arg {Array<Object>} [content.components] An array of component objects
* @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only)
* @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only)
* @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2)
* @arg {String} [content.components[].label] The label to be displayed in the component (type 2)
* @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1)
* @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1)
* @arg {Array<Object>} [content.components[].options] The options for this component (type 3 only)
* @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected
* @arg {String} [content.components[].options[].description] The description for this option
* @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option
* @arg {String} content.components[].options[].label The label for this option
* @arg {Number | String} content.components[].options[].value The value for this option
* @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only)
* @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required
* @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu
* @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only)
* @arg {String} [content.content] A content string
* @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Array<Object>} [content.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Boolean} [content.flags] 64 for Ephemeral
* @arg {Boolean} [content.tts] Set the message TTS flag
* @arg {Object | Array<Object>} [file] A file object (or an Array of them)
* @arg {Buffer} file.file A buffer containing file data
* @arg {String} file.name What to name the file
* @returns {Promise}
*/
async editParent(content, file) {
if(this.acknowledged === true) {
return this.editOriginalMessage(content);
}
if(content !== undefined) {
if(typeof content !== "object" || content === null) {
content = {
content: "" + content
};
} else if(content.content !== undefined && typeof content.content !== "string") {
content.content = "" + content.content;
}
if(content.content !== undefined || content.embeds || content.allowedMentions) {
content.allowed_mentions = this._client._formatAllowedMentions(content.allowedMentions);
}
}
return this._client.createInteractionResponse.call(this._client, this.id, this.token, {
type: InteractionResponseTypes.UPDATE_MESSAGE,
data: content
}, file).then(() => this.update());
}
/**
* Get the parent message
* Warning: Will error with ephemeral messages.
* @returns {Promise<Message>}
*/
async getOriginalMessage() {
if(this.acknowledged === false) {
throw new Error("getOriginalMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, defer, deferUpdate, or editParent first.");
}
return this._client.getWebhookMessage.call(this._client, this.applicationID, this.token, "@original");
}
}
module.exports = ComponentInteraction;

46
node_modules/eris/lib/structures/ExtendedUser.js generated vendored Normal file
View File

@ -0,0 +1,46 @@
"use strict";
const User = require("./User");
/**
* Represents an extended user
* @extends User
* @prop {String} email The email of the user
* @prop {Boolean} mfaEnabled Whether the user has enabled two-factor authentication
* @prop {Number} premiumType The type of Nitro subscription on the user's account
* @prop {Boolean} verified Whether the account email has been verified
*/
class ExtendedUser extends User {
constructor(data, client) {
super(data, client);
this.update(data);
}
update(data) {
super.update(data);
if(data.email !== undefined) {
this.email = data.email;
}
if(data.verified !== undefined) {
this.verified = data.verified;
}
if(data.mfa_enabled !== undefined) {
this.mfaEnabled = data.mfa_enabled;
}
if(data.premium_type !== undefined) {
this.premiumType = data.premium_type;
}
}
toJSON(props = []) {
return super.toJSON([
"email",
"mfaEnabled",
"premium",
"verified",
...props
]);
}
}
module.exports = ExtendedUser;

97
node_modules/eris/lib/structures/GroupChannel.js generated vendored Normal file
View File

@ -0,0 +1,97 @@
"use strict";
const Collection = require("../util/Collection");
const Endpoints = require("../rest/Endpoints");
const PrivateChannel = require("./PrivateChannel");
const User = require("./User");
/**
* [USER ACCOUNT] Represents a group channel. See PrivateChannel docs for additional properties.
* @extends PrivateChannel
* @prop {Call?} call The current group call, if any
* @prop {String?} icon The hash of the group channel icon
* @prop {String?} iconURL The URL of the group channel icon
* @prop {Call?} lastCall The previous group call, if any
* @prop {String} mention A string that mentions the channel
* @prop {String} name The name of the group channel
* @prop {String} ownerID The ID of the user that is the group owner
* @prop {Collection<User>} recipients The recipients in this private channel
*/
class GroupChannel extends PrivateChannel { // (╯°□°)╯︵ ┻━┻
constructor(data, client) {
super(data, client);
this.recipients = new Collection(User);
data.recipients.forEach((recipient) => {
this.recipients.add(client.options.restMode ? new User(recipient, client) : client.users.add(recipient, client));
});
this.update(data);
}
update(data) {
if(data.name !== undefined) {
this.name = data.name;
}
if(data.owner_id !== undefined) {
this.ownerID = data.owner_id;
}
if(data.icon !== undefined) {
this.icon = data.icon;
}
}
get iconURL() {
return this.icon ? this.client._formatImage(Endpoints.CHANNEL_ICON(this.id, this.icon)) : null;
}
/**
* [USER ACCOUNT] Add a user to the group
* @arg {String} userID The ID of the target user
* @returns {Promise}
*/
addRecipient(userID) {
return this.client.addGroupRecipient.call(this.client, this.id, userID);
}
/**
* Get the group's icon with the given format and size
* @arg {String} [format] The filetype of the icon ("jpg", "jpeg", "png", "gif", or "webp")
* @arg {Number} [size] The size of the icon (any power of two between 16 and 4096)
* @returns {String?}
*/
dynamicIconURL(format, size) {
return this.icon ? this.client._formatImage(Endpoints.CHANNEL_ICON(this.id, this.icon), format, size) : null;
}
/**
* [USER ACCOUNT] Edit the channel's properties
* @arg {Object} options The properties to edit
* @arg {String} [options.name] The name of the channel
* @arg {String} [options.icon] The icon of the channel as a base64 data URI (group channels only). Note: base64 strings alone are not base64 data URI strings
* @arg {String} [options.ownerID] The ID of the channel owner (group channels only)
* @returns {Promise<GroupChannel>}
*/
edit(options) {
return this.client.editChannel.call(this.client, this.id, options);
}
/**
* [USER ACCOUNT] Remove a user from the group
* @arg {String} userID The ID of the target user
* @returns {Promise}
*/
removeRecipient(userID) {
return this.client.removeGroupRecipient.call(this.client, this.id, userID);
}
toJSON(props = []) {
return super.toJSON([
"icon",
"name",
"ownerID",
"recipients",
...props
]);
}
}
module.exports = GroupChannel;

1366
node_modules/eris/lib/structures/Guild.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

157
node_modules/eris/lib/structures/GuildAuditLogEntry.js generated vendored Normal file
View File

@ -0,0 +1,157 @@
"use strict";
const Base = require("./Base");
const Invite = require("./Invite");
const {AuditLogActions} = require("../Constants");
/**
* Represents a guild audit log entry describing a moderation action
* @prop {Number} actionType The action type of the entry. See Constants.AuditLogActions for more details
* @prop {Object?} after The properties of the targeted object after the action was taken
* For example, if a channel was renamed from #general to #potato, this would be `{name: "potato"}``
* @prop {Object?} before The properties of the targeted object before the action was taken
* For example, if a channel was renamed from #general to #potato, this would be `{name: "general"}``
* @prop {(CategoryChannel | TextChannel | TextVoiceChannel | NewsThreadChannel | PrivateThreadChannel | PublicThreadChannel | StageChannel)?} channel The channel targeted in the entry, action types 26 (MEMBER_MOVE), 72/74/75 (MESSAGE_DELETE/PIN/UNPIN) and 83/84/85 (STAGE_INSTANCE_CREATE/UPDATE/DELETE) only
* @prop {Number?} count The number of entities targeted
* For example, for action type 26 (MEMBER_MOVE), this is the number of members that were moved/disconnected from the voice channel
* @prop {Number?} deleteMemberDays The number of days of inactivity to prune for, action type 21 (MEMBER_PRUNE) only
* @prop {Guild} guild The guild containing the entry
* @prop {String} id The ID of the entry
* @prop {(Member | Object)?} member The member described by the permission overwrite, action types 13-15 (CHANNEL\_OVERWRITE\_CREATE/UPDATE/DELETE) only. If the member is not cached, this could be {id: String}
* @prop {Number?} membersRemoved The number of members pruned from the server, action type 21 (MEMBER_PRUNE) only
* @prop {(Message | Object)?} message The message that was (un)pinned, action types 74/75 (MESSAGE_PIN/UNPIN) only. If the message is not cached, this will be an object with an `id` key. No other property is guaranteed.
* @prop {String?} reason The reason for the action
* @prop {(Role | Object)?} role The role described by the permission overwrite, action types 13-15 (CHANNEL\_OVERWRITE\_CREATE/UPDATE/DELETE) only. If the role is not cached, this could be {id: String, name: String}
* @prop {(CategoryChannel | Guild | Member | Invite | Role | Object | TextChannel | TextVoiceChannel | NewsChannel)?} target The object of the action target
* If the item is not cached, this property will be null
* If the action targets a guild, this could be a Guild object
* If the action targets a guild channel, this could be a CategoryChannel, TextChannel, or TextVoiceChannel object
* If the action targets a member, this could be a Member object
* If the action targets a role, this could be a Role object
* If the action targets an invite, this is an Invite object
* If the action targets a webhook, this is null
* If the action targets a emoji, this could be an emoji object
* If the action targets a sticker, this could be a sticker object
* If the action targets a message, this is a User object
* @prop {String} targetID The ID of the action target
* @prop {User} user The user that performed the action
*/
class GuildAuditLogEntry extends Base {
constructor(data, guild) {
super(data.id);
this.guild = guild;
this.actionType = data.action_type;
this.reason = data.reason || null;
this.user = guild.shard.client.users.get(data.user_id);
this.before = null;
this.after = null;
if(data.changes) {
this.before = {};
this.after = {};
data.changes.forEach((change) => {
if(change.old_value != undefined) {
this.before[change.key] = change.old_value;
}
if(change.new_value != undefined) {
this.after[change.key] = change.new_value;
}
});
}
if(data.target_id) {
this.targetID = data.target_id;
}
if(data.options) {
if(data.options.count) {
this.count = +data.options.count;
}
if(data.options.channel_id) {
if(this.actionType >= 83) {
this.channel = guild.threads.get(data.options.channel_id);
} else {
this.channel = guild.channels.get(data.options.channel_id);
}
if(data.options.message_id) {
this.message = this.channel && this.channel.messages.get(data.options.message_id) || {id: data.options.message_id};
}
}
if(data.options.delete_member_days) {
this.deleteMemberDays = +data.options.delete_member_days;
this.membersRemoved = +data.options.members_removed;
}
if(data.options.type) {
if(data.options.type === "1") {
this.member = guild.members.get(data.options.id) || {
id: data.options.id
};
} else if(data.options.type === "0") {
this.role = guild.roles.get(data.options.id) || {
id: data.options.id,
name: data.options.role_name
};
}
}
}
}
get target() { // pay more, get less
if(this.actionType < 10) { // Guild
return this.guild;
} else if(this.actionType < 20) { // Channel
return this.guild && this.guild.channels.get(this.targetID);
} else if(this.actionType < 30) { // Member
if(this.actionType === AuditLogActions.MEMBER_MOVE || this.actionType === AuditLogActions.MEMBER_DISCONNECT) { // MEMBER_MOVE / MEMBER_DISCONNECT
return null;
}
return this.guild && this.guild.members.get(this.targetID);
} else if(this.actionType < 40) { // Role
return this.guild && this.guild.roles.get(this.targetID);
} else if(this.actionType < 50) { // Invite
const changes = this.actionType === 42 ? this.before : this.after; // Apparently the meaning of life is a deleted invite
return new Invite({
code: changes.code,
channel: changes.channel,
guild: this.guild,
uses: changes.uses,
max_uses: changes.max_uses,
max_age: changes.max_age,
temporary: changes.temporary
}, this.guild && this.guild.shard.client);
} else if(this.actionType < 60) { // Webhook
return null; // Go get the webhook yourself
} else if(this.actionType < 70) { // Emoji
return this.guild && this.guild.emojis.find((emoji) => emoji.id === this.targetID);
} else if(this.actionType < 80) { // Message
return this.guild && this.guild.shard.client.users.get(this.targetID);
} else if(this.actionType < 83) { // Integrations
return null;
} else if(this.actionType < 90) { // Stage Instances
return this.guild && this.guild.threads.get(this.targetID);
} else if(this.actionType < 100) { // Sticker
return this.guild && this.guild.stickers.find((sticker) => sticker.id === this.targetID);
} else {
throw new Error("Unrecognized action type: " + this.actionType);
}
}
toJSON(props = []) {
return super.toJSON([
"actionType",
"after",
"before",
"channel",
"count",
"deleteMemberDays",
"member",
"membersRemoved",
"reason",
"role",
"targetID",
"user",
...props
]);
}
}
module.exports = GuildAuditLogEntry;

168
node_modules/eris/lib/structures/GuildChannel.js generated vendored Normal file
View File

@ -0,0 +1,168 @@
"use strict";
const Channel = require("./Channel");
const Collection = require("../util/Collection");
const Permission = require("./Permission");
const {Permissions} = require("../Constants");
const PermissionOverwrite = require("./PermissionOverwrite");
/**
* Represents a guild channel. You also probably want to look at CategoryChannel, NewsChannel, StoreChannel, TextChannel, and TextVoiceChannel. See Channel for extra properties.
* @extends Channel
* @prop {Guild} guild The guild that owns the channel
* @prop {String} id The ID of the channel
* @prop {String} name The name of the channel
* @prop {Boolean} nsfw Whether the channel is an NSFW channel or not
* @prop {String?} parentID The ID of the category this channel belongs to or the channel ID where the thread originated from (thread channels only)
* @prop {Collection<PermissionOverwrite>} permissionOverwrites Collection of PermissionOverwrites in this channel
* @prop {Number} position The position of the channel
*/
class GuildChannel extends Channel {
constructor(data, client) {
super(data, client);
this.guild = client.guilds.get(data.guild_id) || {
id: data.guild_id
};
this.update(data);
}
update(data) {
if(data.type !== undefined) {
this.type = data.type;
}
if(data.name !== undefined) {
this.name = data.name;
}
if(data.position !== undefined) {
this.position = data.position;
}
if(data.parent_id !== undefined) {
this.parentID = data.parent_id;
}
this.nsfw = data.nsfw;
if(data.permission_overwrites) {
this.permissionOverwrites = new Collection(PermissionOverwrite);
data.permission_overwrites.forEach((overwrite) => {
this.permissionOverwrites.add(overwrite);
});
}
}
/**
* Delete the channel
* @arg {String} [reason] The reason to be displayed in audit logs
* @returns {Promise}
*/
delete(reason) {
return this.client.deleteChannel.call(this.client, this.id, reason);
}
/**
* Delete a channel permission overwrite
* @arg {String} overwriteID The ID of the overwritten user or role
* @arg {String} [reason] The reason to be displayed in audit logs
* @returns {Promise}
*/
deletePermission(overwriteID, reason) {
return this.client.deleteChannelPermission.call(this.client, this.id, overwriteID, reason);
}
/**
* Edit the channel's properties
* @arg {Object} options The properties to edit
* @arg {Boolean} [options.archived] The archive status of the channel (thread channels only)
* @arg {Number} [options.autoArchiveDuration] The duration in minutes to automatically archive the thread after recent activity, either 60, 1440, 4320 or 10080 (thread channels only)
* @arg {Number} [options.bitrate] The bitrate of the channel (guild voice channels only)
* @arg {Number?} [options.defaultAutoArchiveDuration] The default duration of newly created threads in minutes to automatically archive the thread after inactivity (60, 1440, 4320, 10080) (guild text/news channels only)
* @arg {Boolean} [options.invitable] Whether non-moderators can add other non-moderators to the channel (private thread channels only)
* @arg {Boolean} [options.locked] The lock status of the channel (thread channels only)
* @arg {String} [options.name] The name of the channel
* @arg {Boolean} [options.nsfw] The nsfw status of the channel
* @arg {Number?} [options.parentID] The ID of the parent channel category for this channel (guild text/voice channels only) or the channel ID where the thread originated from (thread channels only)
* @arg {Array<Object>} [options.permissionOverwrites] An array containing permission overwrite objects
* @arg {Number} [options.position] The sorting position of the channel
* @arg {Number} [options.rateLimitPerUser] The time in seconds a user has to wait before sending another message (does not affect bots or users with manageMessages/manageChannel permissions) (guild text and thread channels only)
* @arg {String?} [options.rtcRegion] The RTC region ID of the channel (automatic if `null`) (guild voice channels only)
* @arg {String} [options.topic] The topic of the channel (guild text channels only)
* @arg {Number} [options.userLimit] The channel user limit (guild voice channels only)
* @arg {Number} [options.videoQualityMode] The camera video quality mode of the channel (guild voice channels only). `1` is auto, `2` is 720p
* @arg {String} [reason] The reason to be displayed in audit logs
* @returns {Promise<CategoryChannel | GroupChannel | TextChannel | TextVoiceChannel | NewsChannel | NewsThreadChannel | PrivateThreadChannel | PublicThreadChannel>}
*/
edit(options, reason) {
return this.client.editChannel.call(this.client, this.id, options, reason);
}
/**
* Create a channel permission overwrite
* @arg {String} overwriteID The ID of the overwritten user or role
* @arg {BigInt | Number} allow The permissions number for allowed permissions
* @arg {BigInt | Number} deny The permissions number for denied permissions
* @arg {Number} type The object type of the overwrite, either 1 for "member" or 0 for "role"
* @arg {String} [reason] The reason to be displayed in audit logs
* @returns {Promise<PermissionOverwrite>}
*/
editPermission(overwriteID, allow, deny, type, reason) {
return this.client.editChannelPermission.call(this.client, this.id, overwriteID, allow, deny, type, reason);
}
/**
* Edit the channel's position. Note that channel position numbers are lowest on top and highest at the bottom.
* @arg {Number} position The new position of the channel
* @arg {Object} [options] Additional options when editing position
* @arg {Boolean} [options.lockPermissions] Whether to sync the permissions with the new parent if moving to a new category
* @arg {String} [options.parentID] The new parent ID (category channel) for the channel that is moved
* @returns {Promise}
*/
editPosition(position, options) {
return this.client.editChannelPosition.call(this.client, this.id, position, options);
}
/**
* Get the channel-specific permissions of a member
* @arg {String | Member | Object} memberID The ID of the member or a Member object
* @returns {Permission}
*/
permissionsOf(memberID) {
const member = typeof memberID === "string" ? this.guild.members.get(memberID) : memberID;
let permission = this.guild.permissionsOf(member).allow;
if(permission & Permissions.administrator) {
return new Permission(Permissions.all);
}
const channel = this instanceof ThreadChannel ? this.guild.channels.get(this.parentID) : this;
let overwrite = channel && channel.permissionOverwrites.get(this.guild.id);
if(overwrite) {
permission = (permission & ~overwrite.deny) | overwrite.allow;
}
let deny = 0n;
let allow = 0n;
for(const roleID of member.roles) {
if((overwrite = channel && channel.permissionOverwrites.get(roleID))) {
deny |= overwrite.deny;
allow |= overwrite.allow;
}
}
permission = (permission & ~deny) | allow;
overwrite = channel && channel.permissionOverwrites.get(member.id);
if(overwrite) {
permission = (permission & ~overwrite.deny) | overwrite.allow;
}
return new Permission(permission);
}
toJSON(props = []) {
return super.toJSON([
"name",
"nsfw",
"parentID",
"permissionOverwrites",
"position",
...props
]);
}
}
module.exports = GuildChannel;
const ThreadChannel = require("./ThreadChannel");

119
node_modules/eris/lib/structures/GuildIntegration.js generated vendored Normal file
View File

@ -0,0 +1,119 @@
"use strict";
const Base = require("./Base");
/**
* Represents a guild integration
* @prop {Object} account Info on the integration account
* @prop {String} account.id The ID of the integration account
* @prop {String} account.name The name of the integration account
* @prop {Object?} application The bot/OAuth2 application for Discord integrations. See [the Discord docs](https://discord.com/developers/docs/resources/guild#integration-application-object)
* @prop {Number} createdAt Timestamp of the guild integration's creation
* @prop {Boolean} enabled Whether the integration is enabled or not
* @prop {Boolean?} enableEmoticons Whether integration emoticons are enabled or not
* @prop {Number?} expireBehavior behavior of expired subscriptions
* @prop {Number?} expireGracePeriod grace period for expired subscriptions
* @prop {String} id The ID of the integration
* @prop {String} name The name of the integration
* @prop {Boolean?} revoked Whether or not the application was revoked
* @prop {String?} roleID The ID of the role connected to the integration
* @prop {Number?} subscriberCount number of subscribers
* @prop {Number?} syncedAt Unix timestamp of last integration sync
* @prop {Boolean?} syncing Whether the integration is syncing or not
* @prop {String} type The type of the integration
* @prop {User?} user The user connected to the integration
*/
class GuildIntegration extends Base {
constructor(data, guild) {
super(data.id);
this.guild = guild;
this.name = data.name;
this.type = data.type;
if(data.role_id !== undefined) {
this.roleID = data.role_id;
}
if(data.user) {
this.user = guild.shard.client.users.add(data.user, guild.shard.client);
}
this.account = data.account; // not worth making a class for
this.update(data);
}
update(data) {
this.enabled = data.enabled;
if(data.syncing !== undefined) {
this.syncing = data.syncing;
}
if(data.expire_behavior !== undefined) {
this.expireBehavior = data.expire_behavior;
}
if(data.expire_behavior !== undefined) {
this.expireGracePeriod = data.expire_grace_period;
}
if(data.enable_emoticons) {
this.enableEmoticons = data.enable_emoticons;
}
if(data.subscriber_count !== undefined) {
this.subscriberCount = data.subscriber_count;
}
if(data.synced_at !== undefined) {
this.syncedAt = data.synced_at;
}
if(data.revoked !== undefined) {
this.revoked = data.revoked;
}
if(data.application !== undefined) {
this.application = data.application;
}
}
/**
* Delete the guild integration
* @returns {Promise}
*/
delete() {
return this.guild.shard.client.deleteGuildIntegration.call(this.guild.shard.client, this.guild.id, this.id);
}
/**
* Edit the guild integration
* @arg {Object} options The properties to edit
* @arg {String} [options.expireBehavior] What to do when a user's subscription runs out
* @arg {String} [options.expireGracePeriod] How long before the integration's role is removed from an unsubscribed user
* @arg {String} [options.enableEmoticons] Whether to enable integration emoticons or not
* @returns {Promise}
*/
edit(options) {
return this.guild.shard.client.editGuildIntegration.call(this.guild.shard.client, this.guild.id, this.id, options);
}
/**
* Force the guild integration to sync
* @returns {Promise}
*/
sync() {
return this.guild.shard.client.syncGuildIntegration.call(this.guild.shard.client, this.guild.id, this.id);
}
toJSON(props = []) {
return super.toJSON([
"account",
"application",
"enabled",
"enableEmoticons",
"expireBehavior",
"expireGracePeriod",
"name",
"revoked",
"roleID",
"subscriberCount",
"syncedAt",
"syncing",
"type",
"user",
...props
]);
}
}
module.exports = GuildIntegration;

97
node_modules/eris/lib/structures/GuildPreview.js generated vendored Normal file
View File

@ -0,0 +1,97 @@
"use strict";
const Base = require("./Base");
const Endpoints = require("../rest/Endpoints.js");
/**
* Represents a GuildPreview structure
* @extends Base
* @prop {Number} approximateMemberCount The **approximate** number of members in the guild
* @prop {Number} approximatePresenceCount The **approximate** number of presences in the guild
* @prop {String?} description The description for the guild (VIP only)
* @prop {String?} discoverySplash The hash of the guild discovery splash image, or null if no splash
* @prop {String?} discoverySplashURL The URL of the guild's discovery splash image
* @prop {Array<Object>} emojis An array of guild emoji objects
* @prop {Array<String>} features An array of guild feature strings
* @prop {String?} icon The hash of the guild icon, or null if no icon
* @prop {String?} iconURL The URL of the guild's icon
* @prop {String} id The ID of the guild
* @prop {String} name The name of the guild
* @prop {String?} splash The hash of the guild splash image, or null if no splash (VIP only)
* @prop {String?} splashURL The URL of the guild's splash image
*/
class GuildPreview extends Base {
constructor(data, client) {
super(data.id);
this._client = client;
this.name = data.name;
this.icon = data.icon;
this.description = data.description;
this.splash = data.splash;
this.discoverySplash = data.discovery_splash;
this.features = data.features;
this.approximateMemberCount = data.approximate_member_count;
this.approximatePresenceCount = data.approximate_presence_count;
this.emojis = data.emojis;
}
get iconURL() {
return this.icon ? this._client._formatImage(Endpoints.GUILD_ICON(this.id, this.icon)) : null;
}
get splashURL() {
return this.splash ? this._client._formatImage(Endpoints.GUILD_SPLASH(this.id, this.splash)) : null;
}
get discoverySplashURL() {
return this.discoverySplash ? this._client._formatImage(Endpoints.GUILD_DISCOVERY_SPLASH(this.id, this.discoverySplash)) : null;
}
/**
* Get the guild's splash with the given format and size
* @arg {String} [format] The filetype of the icon ("jpg", "jpeg", "png", "gif", or "webp")
* @arg {Number} [size] The size of the icon (any power of two between 16 and 4096)
* @returns {String?}
*/
dynamicDiscoverySplashURL(format, size) {
return this.discoverySplash ? this._client._formatImage(Endpoints.GUILD_DISCOVERY_SPLASH(this.id, this.discoverySplash), format, size) : null;
}
/**
* Get the guild's icon with the given format and size
* @arg {String} [format] The filetype of the icon ("jpg", "jpeg", "png", "gif", or "webp")
* @arg {Number} [size] The size of the icon (any power of two between 16 and 4096)
* @returns {String?}
*/
dynamicIconURL(format, size) {
return this.icon ? this._client._formatImage(Endpoints.GUILD_ICON(this.id, this.icon), format, size) : null;
}
/**
* Get the guild's splash with the given format and size
* @arg {String} [format] The filetype of the icon ("jpg", "jpeg", "png", "gif", or "webp")
* @arg {Number} [size] The size of the icon (any power of two between 16 and 4096)
* @returns {String?}
*/
dynamicSplashURL(format, size) {
return this.splash ? this._client._formatImage(Endpoints.GUILD_SPLASH(this.id, this.splash), format, size) : null;
}
toJSON(props = []) {
return super.toJSON([
"approximateMemberCount",
"approximatePresenceCount",
"description",
"discoverySplash",
"emojis",
"features",
"icon",
"name",
"splash",
...props
]);
}
}
module.exports = GuildPreview;

156
node_modules/eris/lib/structures/GuildScheduledEvent.js generated vendored Normal file
View File

@ -0,0 +1,156 @@
"use strict";
const Base = require("./Base");
const Endpoints = require("../rest/Endpoints");
/**
* Represents a guild scheduled event
* @prop {(VoiceChannel | StageChannel | Object)?} channel The channel where the event will be held. This will be null if the event is external (`entityType` is `3`). Can be partial with only `id` if the channel or guild is not cached
* @prop {User?} creator The user that created the scheduled event. For events created before October 25 2021, this will be null. Please see the relevant Discord documentation for more details
* @prop {String?} description The description of the event
* @prop {String?} entityID The entity ID associated to the event
* @prop {Object?} entityMetadata Metadata for the event. This will be null if the event is not external (`entityType` is not `3`)
* @prop {String?} entityMetadata.location Location of the event
* @prop {Number} entityType The [entity type](https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-entity-types) of the scheduled event
* @prop {Guild | Object} guild The guild which the event belongs to. Can be partial with only `id` if not cached
* @prop {String} id The id of the guild event
* @prop {String?} image The hash of the event's image, or null if no image
* @prop {String?} imageURL The URL of the event's image, or null if no image
* @prop {String} name The name of the event
* @prop {Number} privacyLevel Event privacy level
* @prop {Number} scheduledStartTime The time the event will start
* @prop {Number?} scheduledEndTime The time the event will end, or null if the event does not have a scheduled time to end
* @prop {Number} status The [status](https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-status) of the scheduled event
* @prop {Number?} userCount The number of users subscribed to the event
*/
class GuildScheduledEvent extends Base {
constructor(data, client) {
super(data.id);
this._client = client;
if(data.creator !== undefined) {
this.creator = client.users.update(data.creator, this.client);
} else {
this.creator = null;
}
this.guild = client.guilds.get(data.guild_id) || {
id: data.guild_id
};
this.scheduledEndTime = null;
this.update(data);
}
update(data) {
if(data.channel_id !== undefined) {
if(data.channel_id !== null) {
if(this._client.guilds.get(data.guild_id)) {
this.channel = this._client.guilds.get(data.guild_id).channels.get(data.channel_id) || {id: data.channel_id};
} else {
this.channel = {id: data.channel_id};
}
} else {
this.channel = null;
}
}
if(data.name !== undefined) {
this.name = data.name;
}
if(data.description !== undefined) {
this.description = data.description;
}
if(data.scheduled_start_time !== undefined) {
this.scheduledStartTime = Date.parse(data.scheduled_start_time);
}
if(data.scheduled_end_time !== undefined) {
this.scheduledEndTime = Date.parse(data.scheduled_end_time);
}
if(data.privacy_level !== undefined) {
this.privacyLevel = data.privacy_level;
}
if(data.status !== undefined) {
this.status = data.status;
}
if(data.entity_type !== undefined) {
this.entityType = data.entity_type;
}
if(data.entity_id !== undefined) {
this.entityID = data.entity_id;
}
if(data.entity_metadata !== undefined) {
this.entityMetadata = data.entity_metadata;
}
if(data.user_count !== undefined) {
this.userCount = data.user_count;
}
if(data.image !== undefined) {
this.image = data.image;
}
}
get imageURL() {
return this.image ? this._client._formatImage(Endpoints.GUILD_SCHEDULED_EVENT_COVER(this.id, this.image)) : null;
}
/**
* Delete this scheduled event
* @returns {Promise}
*/
delete() {
return this._client.deleteGuildScheduledEvent.call(this._client, this.guildID, this.id);
}
/**
* Edit this scheduled event
* @arg {Object} event The new guild scheduled event object
* @arg {String} [event.channelID] The channel ID of the event. If updating `entityType` to `3` (external), this **must** be set to `null`
* @arg {String} [event.description] The description of the event
* @arg {Object} [event.entityMetadata] The entity metadata for the scheduled event. This is required if updating `entityType` to `3` (external)
* @arg {String} [event.entityMetadata.location] Location of the event. This is required if updating `entityType` to `3` (external)
* @arg {Number} [event.entityType] The [entity type](https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-entity-types) of the scheduled event
* @arg {String} [event.image] Base 64 encoded image for the event
* @arg {String} [event.name] The name of the event
* @arg {String} [event.privacyLevel] The privacy level of the event
* @arg {Date} [event.scheduledEndTime] The time when the scheduled event is scheduled to end. This is required if updating `entityType` to `3` (external)
* @arg {Date} [event.scheduledStartTime] The time the event will start
* @arg {Number} [event.status] The [status](https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-status) of the scheduled event
* @arg {String} [reason] The reason to be displayed in audit logs
* @returns {Promise<GuildScheduledEvent>}
*/
edit(event, reason) {
return this._client.editGuildScheduledEvent.call(this._client, this.guildID, this.id, event, reason);
}
/**
* Get a list of users subscribed to the guild scheduled event
* @arg {Object} [options] Options for the request
* @arg {String} [options.after] Get users after this user ID. If `options.before` is provided, this will be ignored. Fetching users in between `before` and `after` is not supported
* @arg {String} [options.before] Get users before this user ID
* @arg {Number} [options.limit=100] The number of users to get (max 100). Pagination will only work if one of `options.after` or `options.after` is also provided
* @arg {Boolean} [options.withMember] Include guild member data
* @returns {Promise<Array<{guildScheduledEventID: String, member?: Member, user: User}>>}
*/
getUsers(options) {
return this._client.getGuildScheduledEventUsers.call(this._client, this.guild.id, this.id, options);
}
toJSON(props = []) {
return super.toJSON([
"channel",
"creator",
"description",
"entityID",
"entityMetadata",
"entityType",
"guild",
"name",
"privacyLevel",
"scheduledEndTime",
"scheduledStartTime",
"status",
"userCount",
...props
]);
}
}
module.exports = GuildScheduledEvent;

86
node_modules/eris/lib/structures/GuildTemplate.js generated vendored Normal file
View File

@ -0,0 +1,86 @@
const Base = require("./Base");
const Guild = require("./Guild");
/**
* Represents a guild template
* @prop {String} code The template code
* @prop {Number} createdAt Timestamp of template creation
* @prop {User} creator User that created the template
* @prop {String?} description The template description
* @prop {Boolean?} isDirty Whether the template has unsynced changes
* @prop {String} name The template name
* @prop {Guild} serializedSourceGuild The guild snapshot this template contains
* @prop {Guild | Object} sourceGuild The guild this template is based on. If the guild is not cached, this will be an object with `id` key. No other property is guaranteed
* @prop {Number} updatedAt Timestamp of template update
* @prop {Number} usageCount Number of times this template has been used
*/
class GuildTemplate {
constructor(data, client) {
this._client = client;
this.code = data.code;
this.createdAt = Date.parse(data.created_at);
this.creator = client.users.update(data.creator, client);
this.description = data.description;
this.isDirty = data.is_dirty;
this.name = data.name;
this.serializedSourceGuild = new Guild(data.serialized_source_guild, client);
this.sourceGuild = client.guilds.get(data.source_guild_id) || {id: data.source_guild_id};
this.updatedAt = Date.parse(data.updated_at);
this.usageCount = data.usage_count;
}
/**
* Create a guild based on this template. This can only be used with bots in less than 10 guilds
* @arg {String} name The name of the guild
* @arg {String} [icon] The 128x128 icon as a base64 data URI
* @returns {Promise<Guild>}
*/
createGuild(name, icon) {
return this._client.createGuildFromTemplate.call(this._client, this.code, name, icon);
}
/**
* Delete this template
* @returns {Promise<GuildTemplate>}
*/
delete() {
return this._client.deleteGuildTemplate.call(this._client, this.sourceGuild.id, this.code);
}
/**
* Edit this template
* @arg {Object} options The properties to edit
* @arg {String} [options.name] The name of the template
* @arg {String?} [options.description] The description for the template. Set to `null` to remove the description
* @returns {Promise<GuildTemplate>}
*/
edit(options) {
return this._client.editGuildTemplate.call(this._client, this.sourceGuild.id, this.code, options);
}
/**
* Force this template to sync to the guild's current state
* @returns {Promise<GuildTemplate>}
*/
sync() {
return this._client.syncGuildTemplate.call(this._client, this.sourceGuild.id, this.code);
}
toJSON(props = []) {
return Base.prototype.toJSON.call(this, [
"code",
"createdAt",
"creator",
"description",
"isDirty",
"name",
"serializedSourceGuild",
"sourceGuild",
"updatedAt",
"usageCount",
...props
]);
}
}
module.exports = GuildTemplate;

58
node_modules/eris/lib/structures/Interaction.js generated vendored Normal file
View File

@ -0,0 +1,58 @@
"use strict";
const Base = require("./Base");
const {InteractionTypes} = require("../Constants");
/**
* Represents an interaction. You also probably want to look at PingInteraction, CommandInteraction, ComponentInteraction, AutocompleteInteraction, and UnknownInteraction.
* @prop {Boolean} acknowledged Whether or not the interaction has been acknowledged
* @prop {String} applicationID The ID of the interaction's application
* @prop {String} id The ID of the interaction
* @prop {String} token The interaction token (Interaction tokens are valid for 15 minutes after initial response and can be used to send followup messages but you must send an initial response within 3 seconds of receiving the event. If the 3 second deadline is exceeded, the token will be invalidated.)
* @prop {Number} type 1 is a Ping, 2 is an Application Command, 3 is a Message Component
* @prop {Number} version The interaction version
*/
class Interaction extends Base {
constructor(data, client) {
super(data.id);
this._client = client;
this.applicationID = data.application_id;
this.token = data.token;
this.type = data.type;
this.version = data.version;
this.acknowledged = false;
}
update() {
this.acknowledged = true;
}
static from(data, client) {
switch(data.type) {
case InteractionTypes.PING: {
return new PingInteraction(data, client);
}
case InteractionTypes.APPLICATION_COMMAND: {
return new CommandInteraction(data, client);
}
case InteractionTypes.MESSAGE_COMPONENT: {
return new ComponentInteraction(data, client);
}
case InteractionTypes.APPLICATION_COMMAND_AUTOCOMPLETE: {
return new AutocompleteInteraction(data, client);
}
}
client.emit("warn", new Error(`Unknown interaction type: ${data.type}\n${JSON.stringify(data)}`));
return new UnknownInteraction(data, client);
}
}
module.exports = Interaction;
// Circular import
const PingInteraction = require("./PingInteraction");
const CommandInteraction = require("./CommandInteraction");
const ComponentInteraction = require("./ComponentInteraction");
const AutocompleteInteraction = require("./AutocompleteInteraction");
const UnknownInteraction = require("./UnknownInteraction");

109
node_modules/eris/lib/structures/Invite.js generated vendored Normal file
View File

@ -0,0 +1,109 @@
"use strict";
const Base = require("./Base");
const Guild = require("./Guild");
/**
* Represents an invite. Some properties are only available when fetching invites from channels, which requires the Manage Channel permission.
* @prop {TextChannel | NewsChannel | TextVoiceChannel | GroupChannel | StageChannel | Object} channel Info on the invite channel
* @prop {String} channel.id The ID of the invite's channel
* @prop {String?} channel.name The name of the invite's channel
* @prop {Number} channel.type The type of the invite's channel
* @prop {String?} channel.icon The icon of a channel (group dm)
* @prop {String} code The invite code
* @prop {Number?} createdAt Timestamp of invite creation
* @prop {Guild?} guild Info on the invite guild
* @prop {User?} inviter The invite creator
* @prop {Number?} maxAge How long the invite lasts in seconds
* @prop {Number?} maxUses The max number of invite uses
* @prop {Number?} memberCount The **approximate** member count for the guild
* @prop {Number?} presenceCount The **approximate** presence count for the guild
* @prop {Object?} stageInstance The active public stage instance data for the stage channel this invite is for
* @prop {String?} targetApplicationID The target application id
* @prop {Number?} targetType The type of the target application
* @prop {User?} targetUser The user whose stream is displayed for the invite (voice channel only)
* @prop {Boolean?} temporary Whether the invite grants temporary membership or not
* @prop {Number?} uses The number of invite uses
*/
class Invite extends Base {
constructor(data, client) {
super();
this._client = client;
this.code = data.code;
if(data.guild && client.guilds.has(data.guild.id)) {
this.channel = client.guilds.get(data.guild.id).channels.update(data.channel, client);
} else {
this.channel = data.channel;
}
if(data.guild) {
if(client.guilds.has(data.guild.id)) {
this.guild = client.guilds.update(data.guild, client);
} else {
this.guild = new Guild(data.guild, client);
}
}
if(data.inviter) {
this.inviter = client.users.add(data.inviter, client);
}
this.uses = data.uses !== undefined ? data.uses : null;
this.maxUses = data.max_uses !== undefined ? data.max_uses : null;
this.maxAge = data.max_age !== undefined ? data.max_age : null;
this.temporary = data.temporary !== undefined ? data.temporary : null;
this._createdAt = data.created_at !== undefined ? data.created_at : null;
this.presenceCount = data.approximate_presence_count !== undefined ? data.approximate_presence_count : null;
this.memberCount = data.approximate_member_count !== undefined ? data.approximate_member_count : null;
if(data.stage_instance !== undefined) {
data.stage_instance.members = data.stage_instance.members.map((m) => {
m.id = m.user.id;
return m;
});
this.stageInstance = {
members: data.stage_instance.members.map((m) => this.guild.members.update(m, this.guild)),
participantCount: data.stage_instance.participant_count,
speakerCount: data.stage_instance.speaker_count,
topic: data.stage_instance.topic
};
} else {
this.stageInstance = null;
}
this.targetApplicationID = data.target_application !== undefined ? data.target_application.id : null;
this.targetType = data.target_type !== undefined ? data.target_type : null;
this.targetUser = data.target_user !== undefined ? this._client.users.update(data.target_user, this._client) : null;
}
get createdAt() {
return Date.parse(this._createdAt);
}
/**
* Delete the invite
* @arg {String} [reason] The reason to be displayed in audit logs
* @returns {Promise}
*/
delete(reason) {
return this._client.deleteInvite.call(this._client, this.code, reason);
}
toString() {
return `[Invite ${this.code}]`;
}
toJSON(props = []) {
return super.toJSON([
"channel",
"code",
"createdAt",
"guild",
"maxAge",
"maxUses",
"memberCount",
"presenceCount",
"revoked",
"temporary",
"uses",
...props
]);
}
}
module.exports = Invite;

287
node_modules/eris/lib/structures/Member.js generated vendored Normal file
View File

@ -0,0 +1,287 @@
"use strict";
const Base = require("./Base");
const Endpoints = require("../rest/Endpoints");
const User = require("./User");
const VoiceState = require("./VoiceState");
/**
* Represents a server member
* @prop {Number?} accentColor The user's banner color, or null if no banner color (REST only)
* @prop {Array<Object>?} activities The member's current activities
* @prop {String?} avatar The hash of the member's guild avatar, or null if no guild avatar
* @prop {String} avatarURL The URL of the user's avatar which can be either a JPG or GIF
* @prop {String?} banner The hash of the user's banner, or null if no banner (REST only)
* @prop {String?} bannerURL The URL of the user's banner
* @prop {Boolean} bot Whether the user is an OAuth bot or not
* @prop {Object?} clientStatus The member's per-client status
* @prop {String} clientStatus.web The member's status on web. Either "online", "idle", "dnd", or "offline". Will be "online" for bots
* @prop {String} clientStatus.desktop The member's status on desktop. Either "online", "idle", "dnd", or "offline". Will be "offline" for bots
* @prop {String} clientStatus.mobile The member's status on mobile. Either "online", "idle", "dnd", or "offline". Will be "offline" for bots
* @prop {Number?} communicationDisabledUntil Timestamp of timeout expiry. If `null`, the member is not timed out
* @prop {Number} createdAt Timestamp of user creation
* @prop {String} defaultAvatar The hash for the default avatar of a user if there is no avatar set
* @prop {String} defaultAvatarURL The URL of the user's default avatar
* @prop {String} discriminator The discriminator of the user
* @prop {Object?} game The active game the member is playing
* @prop {String} game.name The name of the active game
* @prop {Number} game.type The type of the active game (0 is default, 1 is Twitch, 2 is YouTube)
* @prop {String?} game.url The url of the active game
* @prop {Guild} guild The guild the member is in
* @prop {String} id The ID of the member
* @prop {Number?} joinedAt Timestamp of when the member joined the guild
* @prop {String} mention A string that mentions the member
* @prop {String?} nick The server nickname of the member
* @prop {Boolean?} pending Whether the member has passed the guild's Membership Screening requirements
* @prop {Permission} permission [DEPRECATED] The guild-wide permissions of the member. Use Member#permissions instead
* @prop {Permission} permissions The guild-wide permissions of the member
* @prop {Number?} premiumSince Timestamp of when the member boosted the guild
* @prop {Array<String>} roles An array of role IDs this member is a part of
* @prop {String} staticAvatarURL The URL of the user's avatar (always a JPG)
* @prop {String} status The member's status. Either "online", "idle", "dnd", or "offline"
* @prop {User} user The user object of the member
* @prop {String} username The username of the user
* @prop {VoiceState} voiceState The voice state of the member
*/
class Member extends Base {
constructor(data, guild, client) {
super(data.id || data.user.id);
if(!data.id && data.user) {
data.id = data.user.id;
}
if((this.guild = guild)) {
this.user = guild.shard.client.users.get(data.id);
if(!this.user && data.user) {
this.user = guild.shard.client.users.add(data.user, guild.shard.client);
}
if(!this.user) {
throw new Error("User associated with Member not found: " + data.id);
}
} else if(data.user) {
if(!client) {
this.user = new User(data.user);
} else {
this.user = client.users.update(data.user, client);
}
} else {
this.user = null;
}
this.nick = null;
this.roles = [];
this.update(data);
}
update(data) {
if(data.status !== undefined) {
this.status = data.status;
}
if(data.joined_at !== undefined) {
this.joinedAt = data.joined_at ? Date.parse(data.joined_at) : null;
}
if(data.client_status !== undefined) {
this.clientStatus = Object.assign({web: "offline", desktop: "offline", mobile: "offline"}, data.client_status);
}
if(data.activities !== undefined) {
this.activities = data.activities;
}
if(data.premium_since !== undefined) {
this.premiumSince = data.premium_since === null ? null : Date.parse(data.premium_since);
}
if(data.hasOwnProperty("mute") && this.guild) {
const state = this.guild.voiceStates.get(this.id);
if(data.channel_id === null && !data.mute && !data.deaf && !data.suppress) {
this.guild.voiceStates.delete(this.id);
} else if(state) {
state.update(data);
} else if(data.channel_id || data.mute || data.deaf || data.suppress) {
this.guild.voiceStates.update(data);
}
}
if(data.nick !== undefined) {
this.nick = data.nick;
}
if(data.roles !== undefined) {
this.roles = data.roles;
}
if(data.pending !== undefined) {
this.pending = data.pending;
}
if(data.avatar !== undefined) {
this.avatar = data.avatar;
}
if(data.communication_disabled_until !== undefined) {
if(data.communication_disabled_until !== null) {
this.communicationDisabledUntil = Date.parse(data.communication_disabled_until);
} else {
this.communicationDisabledUntil = data.communication_disabled_until;
}
}
}
get accentColor() {
return this.user.accentColor;
}
get avatarURL() {
return this.avatar ? this.guild.shard.client._formatImage(Endpoints.GUILD_AVATAR(this.guild.id, this.id, this.avatar)) : this.user.avatarURL;
}
get banner() {
return this.user.banner;
}
get bannerURL() {
return this.user.bannerURL;
}
get bot() {
return this.user.bot;
}
get createdAt() {
return this.user.createdAt;
}
get defaultAvatar() {
return this.user.defaultAvatar;
}
get defaultAvatarURL() {
return this.user.defaultAvatarURL;
}
get discriminator() {
return this.user.discriminator;
}
get mention() {
return `<@!${this.id}>`;
}
get permission() {
this.guild.shard.client.emit("warn", "[DEPRECATED] Member#permission is deprecated. Use Member#permissions instead");
return this.permissions;
}
get permissions() {
return this.guild.permissionsOf(this);
}
get staticAvatarURL(){
return this.user.staticAvatarURL;
}
get username() {
return this.user.username;
}
get voiceState() {
if(this.guild && this.guild.voiceStates.has(this.id)) {
return this.guild.voiceStates.get(this.id);
} else {
return new VoiceState({
id: this.id
});
}
}
get game() {
return this.activities && this.activities.length > 0 ? this.activities[0] : null;
}
/**
* Add a role to the guild member
* @arg {String} roleID The ID of the role
* @arg {String} [reason] The reason to be displayed in audit logs
* @returns {Promise}
*/
addRole(roleID, reason) {
return this.guild.shard.client.addGuildMemberRole.call(this.guild.shard.client, this.guild.id, this.id, roleID, reason);
}
/**
* Ban the user from the guild
* @arg {Number} [deleteMessageDays=0] Number of days to delete messages for, between 0-7 inclusive
* @arg {String} [reason] The reason to be displayed in audit logs
* @returns {Promise}
*/
ban(deleteMessageDays, reason) {
return this.guild.shard.client.banGuildMember.call(this.guild.shard.client, this.guild.id, this.id, deleteMessageDays, reason);
}
/**
* Get the member's avatar with the given format and size
* @arg {String} [format] The filetype of the avatar ("jpg", "jpeg", "png", "gif", or "webp")
* @arg {Number} [size] The size of the avatar (any power of two between 16 and 4096)
* @returns {String}
*/
dynamicAvatarURL(format, size) {
if(!this.avatar) {
return this.user.dynamicAvatarURL(format, size);
}
return this.guild.shard.client._formatImage(Endpoints.GUILD_AVATAR(this.guild.id, this.id, this.avatar), format, size);
}
/**
* Edit the guild member
* @arg {Object} options The properties to edit
* @arg {String?} [options.channelID] The ID of the voice channel to move the member to (must be in voice). Set to `null` to disconnect the member
* @arg {Date?} [options.communicationDisabledUntil] When the user's timeout should expire. Set to `null` to instantly remove timeout
* @arg {Boolean} [options.deaf] Server deafen the user
* @arg {Boolean} [options.mute] Server mute the user
* @arg {String} [options.nick] Set the user's server nickname, "" to remove
* @arg {Array<String>} [options.roles] The array of role IDs the user should have
* @arg {String} [reason] The reason to be displayed in audit logs
* @returns {Promise}
*/
edit(options, reason) {
return this.guild.shard.client.editGuildMember.call(this.guild.shard.client, this.guild.id, this.id, options, reason);
}
/**
* Kick the member from the guild
* @arg {String} [reason] The reason to be displayed in audit logs
* @returns {Promise}
*/
kick(reason) {
return this.guild.shard.client.kickGuildMember.call(this.guild.shard.client, this.guild.id, this.id, reason);
}
/**
* Remove a role from the guild member
* @arg {String} roleID The ID of the role
* @arg {String} [reason] The reason to be displayed in audit logs
* @returns {Promise}
*/
removeRole(roleID, reason) {
return this.guild.shard.client.removeGuildMemberRole.call(this.guild.shard.client, this.guild.id, this.id, roleID, reason);
}
/**
* Unban the user from the guild
* @arg {String} [reason] The reason to be displayed in audit logs
* @returns {Promise}
*/
unban(reason) {
return this.guild.shard.client.unbanGuildMember.call(this.guild.shard.client, this.guild.id, this.id, reason);
}
toJSON(props = []) {
return super.toJSON([
"activities",
"communicationDisabledUntil",
"joinedAt",
"nick",
"pending",
"premiumSince",
"roles",
"status",
"user",
"voiceState",
...props
]);
}
}
module.exports = Member;

632
node_modules/eris/lib/structures/Message.js generated vendored Normal file
View File

@ -0,0 +1,632 @@
"use strict";
const Base = require("./Base");
const Endpoints = require("../rest/Endpoints");
const Call = require("./Call");
const {SystemJoinMessages, MessageTypes, MessageFlags} = require("../Constants");
const User = require("./User");
/**
* Represents a message
* @prop {Object?} activity The activity specified in the message
* @prop {Object?} application The application of the activity in the message
* @prop {String?} applicationID The ID of the interaction's application
* @prop {Array<Object>} attachments Array of attachments
* @prop {User} author The message author
* @prop {PrivateChannel | TextChannel | NewsChannel} channel The channel the message is in. Can be partial with only the id if the channel is not cached.
* @prop {Array<String>} channelMentions Array of mentions channels' ids
* @prop {String?} cleanContent Message content with mentions replaced by names. Mentions are currently escaped, but this behavior is [DEPRECATED] and will be removed soon. Use allowed mentions, the official way of avoiding unintended mentions, when creating messages.
* @prop {Command?} command The Command used in the Message, if any (CommandClient only)
* @prop {Array<Object>} components An array of component objects
* @prop {String} content Message content
* @prop {Number} createdAt Timestamp of message creation
* @prop {Number?} editedTimestamp Timestamp of latest message edit
* @prop {Array<Object>} embeds Array of embeds
* @prop {Number} flags Message flags (see constants)
* @prop {String} [guildID] The ID of the guild this message is in (undefined if in DMs)
* @prop {String} id The ID of the message
* @prop {String} jumpLink The url used by Discord clients to jump to this message
* @prop {Member?} member The message author with server-specific data
* @prop {Boolean} mentionEveryone Whether the message mentions everyone/here or not
* @prop {Array<User>} mentions Array of mentioned users
* @prop {Object?} messageReference An object containing the reference to the original message if it is a crossposted message or reply
* @prop {String?} messageReference.messageID The id of the original message this message was crossposted from
* @prop {String} messageReference.channelID The id of the channel this message was crossposted from
* @prop {String?} messageReference.guildID The id of the guild this message was crossposted from
* @prop {Object?} interaction An object containing info about the interaction the message is responding to, if applicable
* @prop {String} interaction.id The id of the interaction
* @prop {Number} interaction.type The type of interaction
* @prop {String} interaction.name The name of the command
* @prop {User} interaction.user The user who invoked the interaction
* @prop {Member?} interaction.member The member who invoked the interaction
* @prop {Boolean} pinned Whether the message is pinned or not
* @prop {String?} prefix The prefix used in the Message, if any (CommandClient only)
* @prop {Object} reactions An object containing the reactions on the message. Each key is a reaction emoji and each value is an object with properties `me` (Boolean) and `count` (Number) for that specific reaction emoji.
* @prop {Message?} referencedMessage The message that was replied to. If undefined, message data was not received. If null, the message was deleted.
* @prop {Array<String>} roleMentions Array of mentioned roles' ids
* @prop {Array<Object>?} stickers [DEPRECATED] The stickers sent with the message
* @prop {Array<Object>?} stickerItems The stickers sent with the message
* @prop {Number} timestamp Timestamp of message creation
* @prop {Boolean} tts Whether to play the message using TTS or not
* @prop {Number} type The type of the message
* @prop {String?} webhookID ID of the webhook that sent the message
*/
class Message extends Base {
constructor(data, client) {
super(data.id);
this._client = client;
this.type = data.type || 0;
this.timestamp = Date.parse(data.timestamp);
this.channel = this._client.getChannel(data.channel_id) || {
id: data.channel_id
};
this.content = "";
this.hit = !!data.hit;
this.reactions = {};
this.guildID = data.guild_id;
this.webhookID = data.webhook_id;
if(data.message_reference) {
this.messageReference = {
messageID: data.message_reference.message_id,
channelID: data.message_reference.channel_id,
guildID: data.message_reference.guild_id
};
} else {
this.messageReference = null;
}
this.flags = data.flags || 0;
if(data.author) {
if(data.author.discriminator !== "0000") {
this.author = this._client.users.update(data.author, client);
} else {
this.author = new User(data.author, client);
}
} else {
this._client.emit("error", new Error("MESSAGE_CREATE but no message author:\n" + JSON.stringify(data, null, 2)));
}
if(data.referenced_message) {
const channel = this._client.getChannel(data.referenced_message.channel_id);
if(channel) {
this.referencedMessage = channel.messages.update(data.referenced_message, this._client);
} else {
this.referencedMessage = new Message(data.referenced_message, this._client);
}
} else {
this.referencedMessage = data.referenced_message;
}
if(data.interaction) {
this.interaction = data.interaction;
let interactionMember;
const interactionUser = this._client.users.update(data.interaction.user, client);
if(data.interaction.member) {
data.interaction.member.id = data.interaction.user.id;
if(this.channel.guild) {
interactionMember = this.channel.guild.members.update(data.interaction.member, this.channel.guild);
} else {
interactionMember = data.interaction.member;
}
} else if(this.channel.guild && this.channel.guild.members.has(data.interaction.user.id)) {
interactionMember = this.channel.guild.members.get(data.interaction.user.id);
} else {
interactionMember = null;
}
this.interaction.user = interactionUser;
this.interaction.member = interactionMember;
} else {
this.interaction = null;
}
if(this.channel.guild) {
if(data.member) {
data.member.id = this.author.id;
if(data.author) {
data.member.user = data.author;
}
this.member = this.channel.guild.members.update(data.member, this.channel.guild);
} else if(this.channel.guild.members.has(this.author.id)) {
this.member = this.channel.guild.members.get(this.author.id);
} else {
this.member = null;
}
if(!this.guildID) {
this.guildID = this.channel.guild.id;
}
} else {
this.member = null;
}
switch(this.type) {
case MessageTypes.DEFAULT: {
break;
}
case MessageTypes.RECIPIENT_ADD: {
data.content = `${this.author.mention} added <@${data.mentions[0].id}>.`;
break;
}
case MessageTypes.RECIPIENT_REMOVE: {
if(this.author.id === data.mentions[0].id) {
data.content = `@${this.author.username} left the group.`;
} else {
data.content = `${this.author.mention} removed @${data.mentions[0].username}.`;
}
break;
}
case MessageTypes.CALL: {
if(data.call.ended_timestamp) {
if((!this.channel.lastCall || this.channel.lastCall.endedTimestamp < Date.parse(data.call.ended_timestamp))) {
data.call.id = this.id;
this.channel.lastCall = new Call(data.call, this.channel);
}
if(data.call.participants.includes(this._client.user.id)) {
data.content = `You missed a call from ${this.author.mention}.`;
} else {
data.content = `${this.author.mention} started a call.`;
}
} else {
if(!this.channel.call) {
data.call.id = this.id;
this.channel.call = new Call(data.call, this.channel);
}
data.content = `${this.author.mention} started a call. — Join the call.`;
}
break;
}
case MessageTypes.CHANNEL_NAME_CHANGE: {
data.content = `${this.author.mention} changed the channel name: ${data.content}`;
break;
}
case MessageTypes.CHANNEL_ICON_CHANGE: {
data.content = `${this.author.mention} changed the channel icon.`;
break;
}
case MessageTypes.CHANNEL_PINNED_MESSAGE: {
data.content = `${this.author.mention} pinned a message to this channel. See all the pins.`;
break;
}
case MessageTypes.GUILD_MEMBER_JOIN: {
data.content = SystemJoinMessages[~~(this.createdAt % SystemJoinMessages.length)].replace(/%user%/g, this.author.mention);
break;
}
case MessageTypes.USER_PREMIUM_GUILD_SUBSCRIPTION: {
data.content = `${this.author.mention} just boosted the server!`;
break;
}
case MessageTypes.USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1:
case MessageTypes.USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2:
case MessageTypes.USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3: {
data.content = `${this.author.mention} just boosted the server! ${this.channel.guild ? this.channel.guild.name : data.guild_id} has achieved **Level ${this.type - 8}!**`;
break;
}
case MessageTypes.CHANNEL_FOLLOW_ADD: {
data.content = `${this.author.mention} has added ${data.content} to this channel`;
break;
}
case MessageTypes.GUILD_DISCOVERY_DISQUALIFIED: {
data.content = "This server has been removed from Server Discovery because it no longer passes all the requirements. Check `Server Settings` for more details.";
break;
}
case MessageTypes.GUILD_DISCOVERY_REQUALIFIED: {
data.content = "This server is eligible for Server Discovery again and has been automatically relisted!";
break;
}
case MessageTypes.GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING: {
data.content = "This server has failed Discovery activity requirements for 1 week. If this server fails for 4 weeks in a row, it will be automatically removed from Discovery.";
break;
}
case MessageTypes.GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING: {
data.content = "This server has failed Discovery activity requirements for 3 weeks in a row. If this server fails for 1 more week, it will be removed from Discovery.";
break;
}
case MessageTypes.THREAD_CREATED: {
break;
}
case MessageTypes.REPLY: {
break;
}
case MessageTypes.CHAT_INPUT_COMMAND: {
break;
}
case MessageTypes.CONTEXT_MENU_COMMAND: {
break;
}
case MessageTypes.THREAD_STARTER_MESSAGE: {
break;
}
case MessageTypes.GUILD_INVITE_REMINDER: {
data.content = "Wondering who to invite?\nStart by inviting anyone who can help you build the server!";
break;
}
default: {
this._client.emit("warn", `Unhandled MESSAGE_CREATE type: ${JSON.stringify(data, null, 2)}`);
break;
}
}
this.update(data, client);
}
update(data, client) {
if(this.type === 3) { // (╯°□°)╯︵ ┻━┻
(this.channel.call || this.channel.lastCall).update(data.call);
}
if(data.content !== undefined) {
this.content = data.content || "";
this.mentionEveryone = !!data.mention_everyone;
this.mentions = data.mentions.map((mention) => {
const user = this._client.users.add(mention, client);
if(mention.member && this.channel.guild) {
mention.member.id = mention.id;
this.channel.guild.members.update(mention.member, this.channel.guild);
}
return user;
});
this.roleMentions = data.mention_roles;
}
if(data.pinned !== undefined) {
this.pinned = !!data.pinned;
}
if(data.edited_timestamp != undefined) {
this.editedTimestamp = Date.parse(data.edited_timestamp);
}
if(data.tts !== undefined) {
this.tts = data.tts;
}
if(data.attachments !== undefined) {
this.attachments = data.attachments;
}
if(data.embeds !== undefined) {
this.embeds = data.embeds;
}
if(data.flags !== undefined) {
this.flags = data.flags;
}
if(data.activity !== undefined) {
this.activity = data.activity;
}
if(data.application !== undefined) {
this.application = data.application;
}
if(data.application_id !== undefined) {
this.applicationID = data.application_id;
}
if(data.reactions) {
data.reactions.forEach((reaction) => {
this.reactions[reaction.emoji.id ? `${reaction.emoji.name}:${reaction.emoji.id}` : reaction.emoji.name] = {
count: reaction.count,
me: reaction.me
};
});
}
if(data.stickers !== undefined) {
this.stickers = data.stickers;
}
if(data.sticker_items !== undefined) {
this.stickerItems = data.sticker_items.map((sticker) => {
if(sticker.user) {
sticker.user = this._client.users.update(sticker.user, client);
}
return sticker;
});
}
if(data.components !== undefined) {
this.components = data.components;
}
}
get channelMentions() {
if(this._channelMentions) {
return this._channelMentions;
}
return (this._channelMentions = (this.content && this.content.match(/<#[0-9]+>/g) || []).map((mention) => mention.substring(2, mention.length - 1)));
}
get cleanContent() {
let cleanContent = this.content && this.content.replace(/<a?(:\w+:)[0-9]+>/g, "$1") || "";
let authorName = this.author.username;
if(this.channel.guild) {
const member = this.channel.guild.members.get(this.author.id);
if(member && member.nick) {
authorName = member.nick;
}
}
cleanContent = cleanContent.replace(new RegExp(`<@!?${this.author.id}>`, "g"), "@\u200b" + authorName);
if(this.mentions) {
this.mentions.forEach((mention) => {
if(this.channel.guild) {
const member = this.channel.guild.members.get(mention.id);
if(member && member.nick) {
cleanContent = cleanContent.replace(new RegExp(`<@!?${mention.id}>`, "g"), "@\u200b" + member.nick);
}
}
cleanContent = cleanContent.replace(new RegExp(`<@!?${mention.id}>`, "g"), "@\u200b" + mention.username);
});
}
if(this.channel.guild && this.roleMentions) {
for(const roleID of this.roleMentions) {
const role = this.channel.guild.roles.get(roleID);
const roleName = role ? role.name : "deleted-role";
cleanContent = cleanContent.replace(new RegExp(`<@&${roleID}>`, "g"), "@\u200b" + roleName);
}
}
this.channelMentions.forEach((id) => {
const channel = this._client.getChannel(id);
if(channel && channel.name && channel.mention) {
cleanContent = cleanContent.replace(channel.mention, "#" + channel.name);
}
});
return cleanContent.replace(/@everyone/g, "@\u200beveryone").replace(/@here/g, "@\u200bhere");
}
get jumpLink() {
return `${Endpoints.CLIENT_URL}${Endpoints.MESSAGE_LINK(this.guildID || "@me", this.channel.id, this.id)}`; // Messages outside of guilds (DMs) will never have a guildID property assigned
}
/**
* Add a reaction to a message
* @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji)
* @arg {String} [userID="@me"] The ID of the user to react as. Passing this parameter is deprecated and will not be supported in future versions.
* @returns {Promise}
*/
addReaction(reaction, userID) {
if(this.flags & MessageFlags.EPHEMERAL) {
throw new Error("Ephemeral messages cannot have reactions");
}
return this._client.addMessageReaction.call(this._client, this.channel.id, this.id, reaction, userID);
}
/**
* Create a thread with this message
* @arg {Object} options The thread options
* @arg {Number} options.autoArchiveDuration Duration in minutes to automatically archive the thread after recent activity, either 60, 1440, 4320 or 10080
* @arg {String} options.name The thread channel name
* @returns {Promise<NewsThreadChannel | PublicThreadChannel>}
*/
createThreadWithMessage(options) {
return this._client.createThreadWithMessage.call(this._client, this.channel.id, this.id, options);
}
/**
* Crosspost (publish) a message to subscribed channels (NewsChannel only)
* @returns {Promise<Message>}
*/
crosspost() {
if(this.flags & MessageFlags.EPHEMERAL) {
throw new Error("Ephemeral messages cannot be crossposted");
}
return this._client.crosspostMessage.call(this._client, this.channel.id, this.id);
}
/**
* Delete the message
* @arg {String} [reason] The reason to be displayed in audit logs
* @returns {Promise}
*/
delete(reason) {
if(this.flags & MessageFlags.EPHEMERAL) {
throw new Error("Ephemeral messages cannot be deleted");
}
return this._client.deleteMessage.call(this._client, this.channel.id, this.id, reason);
}
/**
* Delete the message as a webhook
* @arg {String} token The token of the webhook
* @returns {Promise}
*/
deleteWebhook(token) {
if(!this.webhookID) {
throw new Error("Message is not a webhook");
}
if(this.flags & MessageFlags.EPHEMERAL) {
throw new Error("Ephemeral messages cannot be deleted");
}
return this._client.deleteWebhookMessage.call(this._client, this.webhookID, token, this.id);
}
/**
* Edit the message
* @arg {String | Array | Object} content A string, array of strings, or object. If an object is passed:
* @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default)
* @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here.
* @arg {Boolean | Array<String>} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow.
* @arg {Boolean | Array<String>} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow.
* @arg {Array<Object>} [content.components] An array of component objects
* @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only)
* @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only)
* @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2)
* @arg {String} [content.components[].label] The label to be displayed in the component (type 2)
* @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1)
* @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1)
* @arg {Array<Object>} [content.components[].options] The options for this component (type 3 only)
* @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected
* @arg {String} [content.components[].options[].description] The description for this option
* @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option
* @arg {String} content.components[].options[].label The label for this option
* @arg {Number | String} content.components[].options[].value The value for this option
* @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only)
* @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required
* @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu
* @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only)
* @arg {String} [content.content] A content string
* @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Array<Object>} [content.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Object | Array<Object>} [content.file] A file object (or an Array of them)
* @arg {Buffer} content.file[].file A buffer containing file data
* @arg {String} content.file[].name What to name the file
* @arg {Number} [content.flags] A number representing the flags to apply to the message. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#message-object-message-flags) for flags reference
* @returns {Promise<Message>}
*/
edit(content) {
if(this.flags & MessageFlags.EPHEMERAL) {
throw new Error("Ephemeral messages cannot be edited via this method");
}
return this._client.editMessage.call(this._client, this.channel.id, this.id, content);
}
/**
* Edit the message as a webhook
* @arg {String} token The token of the webhook
* @arg {Object} options Webhook message edit options
* @arg {Object} [options.allowedMentions] A list of mentions to allow (overrides default)
* @arg {Boolean} [options.allowedMentions.everyone] Whether or not to allow @everyone/@here.
* @arg {Boolean} [options.allowedMentions.repliedUser] Whether or not to mention the author of the message being replied to.
* @arg {Boolean | Array<String>} [options.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow.
* @arg {Boolean | Array<String>} [options.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow.
* @arg {Array<Object>} [options.components] An array of component objects
* @arg {String} [options.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only)
* @arg {Boolean} [options.components[].disabled] Whether the component is disabled (type 2 only)
* @arg {Object} [options.components[].emoji] The emoji to be displayed in the component (type 2)
* @arg {String} [options.components[].label] The label to be displayed in the component (type 2)
* @arg {Number} [options.components[].max_values] The maximum number of items that can be chosen (1-25, default 1)
* @arg {Number} [options.components[].min_values] The minimum number of items that must be chosen (0-25, default 1)
* @arg {Array<Object>} [options.components[].options] The options for this component (type 3 only)
* @arg {Boolean} [options.components[].options[].default] Whether this option should be the default value selected
* @arg {String} [options.components[].options[].description] The description for this option
* @arg {Object} [options.components[].options[].emoji] The emoji to be displayed in this option
* @arg {String} options.components[].options[].label The label for this option
* @arg {Number | String} options.components[].options[].value The value for this option
* @arg {String} [options.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only)
* @arg {Number} [options.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required
* @arg {Number} options.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu
* @arg {String} [options.components[].url] The URL that the component should open for users (type 2 style 5 only)
* @arg {String} [options.content] A content string
* @arg {Object} [options.embed] An embed object. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Array<Object>} [options.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Object | Array<Object>} [options.file] A file object (or an Array of them)
* @arg {Buffer} options.file.file A buffer containing file data
* @arg {String} options.file.name What to name the file
* @returns {Promise<Message>}
*/
editWebhook(token, options) {
if(!this.webhookID) {
throw new Error("Message is not a webhook");
}
return this._client.editWebhookMessage.call(this._client, this.webhookID, token, this.id, options);
}
/**
* Get a list of users who reacted with a specific reaction
* @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji)
* @arg {Object} [options] Options for the request. If this is a number, it is treated as `options.limit` ([DEPRECATED] behavior)
* @arg {Number} [options.limit=100] The maximum number of users to get
* @arg {String} [options.after] Get users after this user ID
* @arg {String} [before] [DEPRECATED] Get users before this user ID. Discord no longer supports this parameter
* @arg {String} [after] [DEPRECATED] Get users after this user ID
* @returns {Promise<Array<User>>}
*/
getReaction(reaction, options, before, after) {
if(this.flags & MessageFlags.EPHEMERAL) {
throw new Error("Ephemeral messages cannot have reactions");
}
return this._client.getMessageReaction.call(this._client, this.channel.id, this.id, reaction, options, before, after);
}
/**
* Pin the message
* @returns {Promise}
*/
pin() {
if(this.flags & MessageFlags.EPHEMERAL) {
throw new Error("Ephemeral messages cannot be pinned");
}
return this._client.pinMessage.call(this._client, this.channel.id, this.id);
}
/**
* Remove a reaction from a message
* @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji)
* @arg {String} [userID="@me"] The ID of the user to remove the reaction for.
* @returns {Promise}
*/
removeReaction(reaction, userID) {
if(this.flags & MessageFlags.EPHEMERAL) {
throw new Error("Ephemeral messages cannot have reactions");
}
return this._client.removeMessageReaction.call(this._client, this.channel.id, this.id, reaction, userID);
}
/**
* Remove all reactions from a message for a single emoji
* @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji)
* @returns {Promise}
*/
removeReactionEmoji(reaction) {
if(this.flags & MessageFlags.EPHEMERAL) {
throw new Error("Ephemeral messages cannot have reactions");
}
return this._client.removeMessageReactionEmoji.call(this._client, this.channel.id, this.id, reaction);
}
/**
* Remove all reactions from a message
* @returns {Promise}
*/
removeReactions() {
if(this.flags & MessageFlags.EPHEMERAL) {
throw new Error("Ephemeral messages cannot have reactions");
}
return this._client.removeMessageReactions.call(this._client, this.channel.id, this.id);
}
/**
* Unpin the message
* @returns {Promise}
*/
unpin() {
if(this.flags & MessageFlags.EPHEMERAL) {
throw new Error("Ephemeral messages cannot be pinned");
}
return this._client.unpinMessage.call(this._client, this.channel.id, this.id);
}
toJSON(props = []) {
return super.toJSON([
"activity",
"application",
"attachments",
"author",
"content",
"editedTimestamp",
"embeds",
"flags",
"guildID",
"hit",
"member",
"mentionEveryone",
"mentions",
"messageReference",
"pinned",
"reactions",
"referencedMesssage",
"roleMentions",
"stickers",
"stickerItems",
"timestamp",
"tts",
"type",
"webhookID",
...props
]);
}
}
module.exports = Message;

35
node_modules/eris/lib/structures/NewsChannel.js generated vendored Normal file
View File

@ -0,0 +1,35 @@
"use strict";
const TextChannel = require("./TextChannel");
/**
* Represents a guild news channel. See TextChannel for more properties and methods.
* @extends TextChannel
*/
class NewsChannel extends TextChannel {
constructor(data, guild, messageLimit) {
super(data, guild, messageLimit);
this.rateLimitPerUser = 0;
this.update(data);
}
/**
* Crosspost (publish) a message to subscribed channels
* @arg {String} messageID The ID of the message
* @returns {Promise<Message>}
*/
crosspostMessage(messageID) {
return this.client.crosspostMessage.call(this.client, this.id, messageID);
}
/**
* Follow this channel in another channel. This creates a webhook in the target channel
* @arg {String} webhookChannelID The ID of the target channel
* @returns {Object} An object containing this channel's ID and the new webhook's ID
*/
follow(webhookChannelID) {
return this.client.followChannel.call(this.client, this.id, webhookChannelID);
}
}
module.exports = NewsChannel;

15
node_modules/eris/lib/structures/NewsThreadChannel.js generated vendored Normal file
View File

@ -0,0 +1,15 @@
"use strict";
const ThreadChannel = require("./ThreadChannel");
/**
* Represents a news thread channel. See ThreadChannel for extra properties.
* @extends ThreadChannel
*/
class NewsThreadChannel extends ThreadChannel {
constructor(data, client, messageLimit) {
super(data, client, messageLimit);
}
}
module.exports = NewsThreadChannel;

71
node_modules/eris/lib/structures/Permission.js generated vendored Normal file
View File

@ -0,0 +1,71 @@
"use strict";
const Base = require("./Base");
const {Permissions} = require("../Constants");
/**
* Represents a calculated permissions number
* @prop {BigInt} allow The allowed permissions number
* @prop {BigInt} deny The denied permissions number
* @prop {Object} json A JSON representation of the permissions number.
* If a permission key isn't there, it is not set by this permission.
* If a permission key is false, it is denied by the permission.
* If a permission key is true, it is allowed by the permission.
* i.e.:
* {
* "readMessages": true,
* "sendMessages": true,
* "manageMessages": false
* }
* In the above example, readMessages and sendMessages are allowed permissions, and manageMessages is denied. Everything else is not explicitly set.
* [A full list of permission nodes can be found on the docs reference page](/Eris/docs/reference)
*/
class Permission extends Base {
constructor(allow, deny = 0) {
super();
this.allow = BigInt(allow);
this.deny = BigInt(deny);
}
get json() {
if(!this._json) {
this._json = {};
for(const perm of Object.keys(Permissions)) {
if(!perm.startsWith("all")) {
if(this.allow & Permissions[perm]) {
this._json[perm] = true;
} else if(this.deny & Permissions[perm]) {
this._json[perm] = false;
}
}
}
}
return this._json;
}
/**
* Check if this permission allows a specific permission
* @arg {String | BigInt} permission The name of the permission, or bit of permissions. [A full list of permission nodes can be found on the docs reference page](/Eris/docs/reference). Pass a BigInt if you want to check multiple permissions.
* @returns {Boolean} Whether the permission allows the specified permission
*/
has(permission) {
if(typeof permission === "bigint") {
return (this.allow & permission) === permission;
}
return !!(this.allow & Permissions[permission]);
}
toString() {
return `[${this.constructor.name} +${this.allow} -${this.deny}]`;
}
toJSON(props = []) {
return super.toJSON([
"allow",
"deny",
...props
]);
}
}
module.exports = Permission;

View File

@ -0,0 +1,26 @@
"use strict";
const Permission = require("./Permission");
/**
* Represents a permission overwrite
* @extends Permission
* @prop {String} id The ID of the overwrite
* @prop {Number} type The type of the overwrite, either 1 for "member" or 0 for "role"
*/
class PermissionOverwrite extends Permission {
constructor(data) {
super(data.allow, data.deny);
this.id = data.id;
this.type = data.type;
}
toJSON(props = []) {
return super.toJSON([
"type",
...props
]);
}
}
module.exports = PermissionOverwrite;

40
node_modules/eris/lib/structures/PingInteraction.js generated vendored Normal file
View File

@ -0,0 +1,40 @@
"use strict";
const Interaction = require("./Interaction");
const {InteractionResponseTypes} = require("../Constants");
/**
* Represents a ping interaction. See Interaction for more properties.
* @extends Interaction
*/
class PingInteraction extends Interaction {
constructor(info, client) {
super(info, client);
}
/**
* Acknowledges the ping interaction with a pong response.
* Note: You can **not** use more than 1 initial interaction response per interaction.
* @returns {Promise}
*/
async acknowledge() {
return this.pong();
}
/**
* Acknowledges the ping interaction with a pong response.
* Note: You can **not** use more than 1 initial interaction response per interaction.
* @returns {Promise}
*/
async pong() {
if(this.acknowledged === true) {
throw new Error("You have already acknowledged this interaction.");
}
return this._client.createInteractionResponse.call(this._client, this.id, this.token, {
type: InteractionResponseTypes.PONG
}).then(() => this.update());
}
}
module.exports = PingInteraction;

269
node_modules/eris/lib/structures/PrivateChannel.js generated vendored Normal file
View File

@ -0,0 +1,269 @@
"use strict";
const Channel = require("./Channel");
const Collection = require("../util/Collection");
const Endpoints = require("../rest/Endpoints");
const Message = require("./Message");
const {GatewayOPCodes, ChannelTypes} = require("../Constants");
const User = require("./User");
/**
* Represents a private channel. See Channel for more properties and methods.
* @extends Channel
* @prop {String} lastMessageID The ID of the last message in this channel
* @prop {Collection<Message>} messages Collection of Messages in this channel
* @prop {User} recipient The recipient in this private channel (private channels only)
*/
class PrivateChannel extends Channel {
constructor(data, client) {
super(data, client);
this.lastMessageID = data.last_message_id;
this.rateLimitPerUser = data.rate_limit_per_user;
this.call = this.lastCall = null;
if(this.type === ChannelTypes.DM || this.type === undefined) {
this.recipient = new User(data.recipients[0], client);
}
this.messages = new Collection(Message, client.options.messageLimit);
}
/**
* Add a reaction to a message
* @arg {String} messageID The ID of the message
* @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji)
* @arg {String} [userID="@me"] The ID of the user to react as. Passing this parameter is deprecated and will not be supported in future versions.
* @returns {Promise}
*/
addMessageReaction(messageID, reaction, userID) {
return this.client.addMessageReaction.call(this.client, this.id, messageID, reaction, userID);
}
/**
* Create a message in a text channel
* @arg {String | Object} content A string or object. If an object is passed:
* @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default)
* @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here.
* @arg {Boolean | Array<String>} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow.
* @arg {Boolean | Array<String>} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow.
* @arg {Boolean} [content.allowedMentions.repliedUser] Whether or not to mention the author of the message being replied to.
* @arg {Array<Object>} [content.components] An array of component objects
* @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only)
* @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only)
* @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2)
* @arg {String} [content.components[].label] The label to be displayed in the component (type 2)
* @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1)
* @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1)
* @arg {Array<Object>} [content.components[].options] The options for this component (type 3 only)
* @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected
* @arg {String} [content.components[].options[].description] The description for this option
* @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option
* @arg {String} content.components[].options[].label The label for this option
* @arg {Number | String} content.components[].options[].value The value for this option
* @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only)
* @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required
* @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu
* @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only)
* @arg {String} [content.content] A content string
* @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Array<Object>} [content.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Object} [content.messageReference] The message reference, used when replying to messages
* @arg {String} [content.messageReference.channelID] The channel ID of the referenced message
* @arg {Boolean} [content.messageReference.failIfNotExists=true] Whether to throw an error if the message reference doesn't exist. If false, and the referenced message doesn't exist, the message is created without a referenced message
* @arg {String} [content.messageReference.guildID] The guild ID of the referenced message
* @arg {String} content.messageReference.messageID The message ID of the referenced message. This cannot reference a system message
* @arg {String} [content.messageReferenceID] [DEPRECATED] The ID of the message should be replied to. Use `messageReference` instead
* @arg {Array<String>} [content.stickerIDs] An array of IDs corresponding to stickers to send
* @arg {Boolean} [content.tts] Set the message TTS flag
* @arg {Object | Array<Object>} [file] A file object (or an Array of them)
* @arg {Buffer} file.file A buffer containing file data
* @arg {String} file.name What to name the file
* @returns {Promise<Message>}
*/
createMessage(content, file) {
return this.client.createMessage.call(this.client, this.id, content, file);
}
/**
* Delete a message
* @arg {String} messageID The ID of the message
* @arg {String} [reason] The reason to be displayed in audit logs
* @returns {Promise}
*/
deleteMessage(messageID, reason) {
return this.client.deleteMessage.call(this.client, this.id, messageID, reason);
}
/**
* Edit a message
* @arg {String} messageID The ID of the message
* @arg {String | Array | Object} content A string, array of strings, or object. If an object is passed:
* @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default)
* @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here.
* @arg {Boolean | Array<String>} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow.
* @arg {Boolean | Array<String>} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow.
* @arg {Array<Object>} [content.components] An array of component objects
* @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only)
* @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only)
* @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2)
* @arg {String} [content.components[].label] The label to be displayed in the component (type 2)
* @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1)
* @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1)
* @arg {Array<Object>} [content.components[].options] The options for this component (type 3 only)
* @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected
* @arg {String} [content.components[].options[].description] The description for this option
* @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option
* @arg {String} content.components[].options[].label The label for this option
* @arg {Number | String} content.components[].options[].value The value for this option
* @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only)
* @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required
* @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu
* @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only)
* @arg {String} [content.content] A content string
* @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Array<Object>} [content.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Object | Array<Object>} [content.file] A file object (or an Array of them)
* @arg {Buffer} content.file[].file A buffer containing file data
* @arg {String} content.file[].name What to name the file
* @arg {Number} [content.flags] A number representing the flags to apply to the message. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#message-object-message-flags) for flags reference
* @returns {Promise<Message>}
*/
editMessage(messageID, content) {
return this.client.editMessage.call(this.client, this.id, messageID, content);
}
/**
* Get a previous message in a text channel
* @arg {String} messageID The ID of the message
* @returns {Promise<Message>}
*/
getMessage(messageID) {
return this.client.getMessage.call(this.client, this.id, messageID);
}
/**
* Get a list of users who reacted with a specific reaction
* @arg {String} messageID The ID of the message
* @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji)
* @arg {Object} [options] Options for the request. If this is a number, it is treated as `options.limit` ([DEPRECATED] behavior)
* @arg {Number} [options.limit=100] The maximum number of users to get
* @arg {String} [options.after] Get users after this user ID
* @arg {String} [before] [DEPRECATED] Get users before this user ID. Discord no longer supports this parameter
* @arg {String} [after] [DEPRECATED] Get users after this user ID
* @returns {Promise<Array<User>>}
*/
getMessageReaction(messageID, reaction, options, before, after) {
return this.client.getMessageReaction.call(this.client, this.id, messageID, reaction, options, before, after);
}
/**
* Get a previous message in a text channel
* @arg {Object} [options] Options for the request. If this is a number ([DEPRECATED] behavior), it is treated as `options.limit`
* @arg {String} [options.after] Get messages after this message ID
* @arg {String} [options.around] Get messages around this message ID (does not work with limit > 100)
* @arg {String} [options.before] Get messages before this message ID
* @arg {Number} [options.limit=50] The max number of messages to get
* @arg {String} [before] [DEPRECATED] Get messages before this message ID
* @arg {String} [after] [DEPRECATED] Get messages after this message ID
* @arg {String} [around] [DEPRECATED] Get messages around this message ID (does not work with limit > 100)
* @returns {Promise<Array<Message>>}
*/
getMessages(options, before, after, around) {
return this.client.getMessages.call(this.client, this.id, options, before, after, around);
}
/**
* Get all the pins in a text channel
* @returns {Promise<Array<Message>>}
*/
getPins() {
return this.client.getPins.call(this.client, this.id);
}
/**
* Leave the channel
* @returns {Promise}
*/
leave() {
return this.client.deleteChannel.call(this.client, this.id);
}
/**
* Pin a message
* @arg {String} messageID The ID of the message
* @returns {Promise}
*/
pinMessage(messageID) {
return this.client.pinMessage.call(this.client, this.id, messageID);
}
/**
* Remove a reaction from a message
* @arg {String} messageID The ID of the message
* @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji)
* @arg {String} [userID="@me"] The ID of the user to remove the reaction for. Passing this parameter is deprecated and will not be supported in future versions.
* @returns {Promise}
*/
removeMessageReaction(messageID, reaction, userID) {
if(userID !== undefined) {
this.emit("warn", "[DEPRECATED] removeMessageReaction() was called on a PrivateChannel with a `userID` argument");
}
return this.client.removeMessageReaction.call(this.client, this.id, messageID, reaction, userID);
}
/**
* [USER ACCOUNT] Ring fellow group channel recipient(s)
* @arg {Array<String>} recipients The IDs of the recipients to ring
*/
ring(recipients) {
this.client.requestHandler.request("POST", Endpoints.CHANNEL_CALL_RING(this.id), true, {
recipients
});
}
/**
* Send typing status in a text channel
* @returns {Promise}
*/
sendTyping() {
return this.client.sendChannelTyping.call(this.client, this.id);
}
/**
* Check if the channel has an existing call
*/
syncCall() {
this.client.shards.values().next().value.sendWS(GatewayOPCodes.SYNC_CALL, {
channel_id: this.id
});
}
/**
* Unpin a message
* @arg {String} messageID The ID of the message
* @returns {Promise}
*/
unpinMessage(messageID) {
return this.client.unpinMessage.call(this.client, this.id, messageID);
}
/**
* Un-send a message. You're welcome Programmix
* @arg {String} messageID The ID of the message
* @returns {Promise}
*/
unsendMessage(messageID) {
return this.client.deleteMessage.call(this.client, this.id, messageID);
}
toJSON(props = []) {
return super.toJSON([
"call",
"lastCall",
"lastMessageID",
"messages",
"recipient",
...props
]);
}
}
module.exports = PrivateChannel;

View File

@ -0,0 +1,34 @@
"use strict";
const ThreadChannel = require("./ThreadChannel");
/**
* Represents a private thread channel. See ThreadChannel for extra properties.
* @extends ThreadChannel
* @prop {Object} threadMetadata Metadata for the thread
* @prop {Number} threadMetadata.archiveTimestamp Timestamp when the thread's archive status was last changed, used for calculating recent activity
* @prop {Boolean} threadMetadata.archived Whether the thread is archived
* @prop {Number} threadMetadata.autoArchiveDuration Duration in minutes to automatically archive the thread after recent activity, either 60, 1440, 4320 or 10080
* @prop {Boolean} threadMetadata.invitable Whether non-moderators can add other non-moderators to the thread
* @prop {Boolean} threadMetadata.locked Whether the thread is locked
*/
class PrivateThreadChannel extends ThreadChannel {
constructor(data, client, messageLimit) {
super(data, client, messageLimit);
this.update(data);
}
update(data) {
if(data.thread_metadata !== undefined) {
this.threadMetadata = {
archiveTimestamp: Date.parse(data.thread_metadata.archive_timestamp),
archived: data.thread_metadata.archived,
autoArchiveDuration: data.thread_metadata.auto_archive_duration,
invitable: data.thread_metadata.invitable,
locked: data.thread_metadata.locked
};
}
}
}
module.exports = PrivateThreadChannel;

View File

@ -0,0 +1,15 @@
"use strict";
const ThreadChannel = require("./ThreadChannel");
/**
* Represents a public thread channel. See ThreadChannel for extra properties.
* @extends ThreadChannel
*/
class PublicThreadChannel extends ThreadChannel {
constructor(data, client, messageLimit) {
super(data, client, messageLimit);
}
}
module.exports = PublicThreadChannel;

48
node_modules/eris/lib/structures/Relationship.js generated vendored Normal file
View File

@ -0,0 +1,48 @@
"use strict";
const Base = require("./Base");
/**
* [USER ACCOUNT] Represents a Relationship
* @prop {User} user The other user in the relationship
* @prop {Number} type The type of relationship. 1 is friend, 2 is block, 3 is incoming request, 4 is outgoing request
* @prop {String} status The other user's status. Either "online", "idle", or "offline"
* @prop {Object?} game The active game the other user is playing
* @prop {String} game.name The name of the active game
* @prop {Number} game.type The type of the active game (0 is default, 1 is Twitch, 2 is YouTube)
* @prop {String?} game.url The url of the active game
*/
class Relationship extends Base {
constructor(data, client) {
super(data.id);
this.user = client.users.add(data.user, client);
this.type = 0;
this.status = "offline";
this.activities = null;
this.update(data);
}
update(data) {
if(data.type !== undefined) {
this.type = data.type;
}
if(data.status !== undefined) {
this.status = data.status;
}
if(data.activities !== undefined) {
this.activities = data.activities;
}
}
toJSON(props = []) {
return super.toJSON([
"activities",
"status",
"type",
"user",
...props
]);
}
}
module.exports = Relationship;

136
node_modules/eris/lib/structures/Role.js generated vendored Normal file
View File

@ -0,0 +1,136 @@
"use strict";
const Base = require("./Base");
const Endpoints = require("../rest/Endpoints");
const Permission = require("./Permission");
/**
* Represents a role
* @prop {Number} color The hex color of the role in base 10
* @prop {Number} createdAt Timestamp of the role's creation
* @prop {Boolean} hoist Whether users with this role are hoisted in the user list or not
* @prop {String?} icon The hash of the role's icon, or null if no icon
* @prop {String?} iconURL The URL of the role's icon
* @prop {String} id The ID of the role
* @prop {Object} json Generates a JSON representation of the role permissions
* @prop {Guild} guild The guild that owns the role
* @prop {Boolean} managed Whether a guild integration manages this role or not
* @prop {String} mention A string that mentions the role
* @prop {Boolean} mentionable Whether the role is mentionable or not
* @prop {String} name The name of the role
* @prop {Permission} permissions The permissions representation of the role
* @prop {Number} position The position of the role
* @prop {Object?} tags The tags of the role
* @prop {String?} tags.bot_id The ID of the bot associated with the role
* @prop {String?} tags.integration_id The ID of the integration associated with the role
* @prop {Boolean?} tags.premium_subscriber Whether the role is the guild's premium subscriber role
* @prop {String?} unicodeEmoji Unicode emoji for the role
*/
class Role extends Base {
constructor(data, guild) {
super(data.id);
this.guild = guild;
this.update(data);
}
update(data) {
if(data.name !== undefined) {
this.name = data.name;
}
if(data.mentionable !== undefined) {
this.mentionable = data.mentionable;
}
if(data.managed !== undefined) {
this.managed = data.managed;
}
if(data.hoist !== undefined) {
this.hoist = data.hoist;
}
if(data.color !== undefined) {
this.color = data.color;
}
if(data.position !== undefined) {
this.position = data.position;
}
if(data.permissions !== undefined) {
this.permissions = new Permission(data.permissions);
}
if(data.tags !== undefined) {
this.tags = data.tags;
if(this.tags.premium_subscriber === null) {
this.tags.premium_subscriber = true;
}
}
if(data.icon !== undefined) {
this.icon = data.icon;
}
if(data.unicode_emoji !== undefined) {
this.unicodeEmoji = data.unicode_emoji;
}
}
get iconURL() {
return this.icon ? this.guild.shard.client._formatImage(Endpoints.ROLE_ICON(this.id, this.icon)) : null;
}
get json() {
return this.permissions.json;
}
get mention() {
return `<@&${this.id}>`;
}
/**
* Delete the role
* @arg {String} [reason] The reason to be displayed in audit logs
* @returns {Promise}
*/
delete(reason) {
return this.guild.shard.client.deleteRole.call(this.guild.shard.client, this.guild.id, this.id, reason);
}
/**
* Edit the guild role
* @arg {Object} options The properties to edit
* @arg {Number} [options.color] The hex color of the role, in number form (ex: 0x3da5b3 or 4040115)
* @arg {Boolean} [options.hoist] Whether to hoist the role in the user list or not
* @arg {String} [options.icon] The role icon as a base64 data URI
* @arg {Boolean} [options.mentionable] Whether the role is mentionable or not
* @arg {String} [options.name] The name of the role
* @arg {BigInt | Number} [options.permissions] The role permissions number
* @arg {String?} [options.unicodeEmoji] The role's unicode emoji
* @arg {String} [reason] The reason to be displayed in audit logs
* @returns {Promise<Role>}
*/
edit(options, reason) {
return this.guild.shard.client.editRole.call(this.guild.shard.client, this.guild.id, this.id, options, reason);
}
/**
* Edit the role's position. Note that role position numbers are highest on top and lowest at the bottom.
* @arg {Number} position The new position of the role
* @returns {Promise}
*/
editPosition(position) {
return this.guild.shard.client.editRolePosition.call(this.guild.shard.client, this.guild.id, this.id, position);
}
toJSON(props = []) {
return super.toJSON([
"color",
"hoist",
"icon",
"managed",
"mentionable",
"name",
"permissions",
"position",
"tags",
"unicodeEmoji",
...props
]);
}
}
module.exports = Role;

64
node_modules/eris/lib/structures/StageChannel.js generated vendored Normal file
View File

@ -0,0 +1,64 @@
"use strict";
const VoiceChannel = require("./VoiceChannel");
/**
* Represents a guild stage channel. See VoiceChannel for more properties and methods.
* @extends VoiceChannel
* @prop {String?} topic The topic of the channel
*/
class StageChannel extends VoiceChannel {
update(data) {
super.update(data);
if(data.topic !== undefined) {
this.topic = data.topic;
}
}
/**
* Create a stage instance
* @arg {Object} options The stage instance options
* @arg {Number} [options.privacyLevel] The privacy level of the stage instance. 1 is public, 2 is guild only
* @arg {String} options.topic The stage instance topic
* @returns {Promise<StageInstance>}
*/
createInstance(options) {
return this.client.createStageInstance.call(this.client, this.id, options);
}
/**
* Delete the stage instance for this channel
* @returns {Promise}
*/
deleteInstance() {
return this.client.deleteStageInstance.call(this.client, this.id);
}
/**
* Update the stage instance for this channel
* @arg {Object} options The properties to edit
* @arg {Number} [options.privacyLevel] The privacy level of the stage instance. 1 is public, 2 is guild only
* @arg {String} [options.topic] The stage instance topic
* @returns {Promise<StageInstance>}
*/
editInstance(options) {
return this.client.editStageInstance.call(this.client, this.id, options);
}
/**
* Get the stage instance for this channel
* @returns {Promise<StageInstance>}
*/
getInstance() {
return this.client.getStageInstance.call(this.client, this.id);
}
toJSON(props = []) {
return super.toJSON([
"topic",
...props
]);
}
}
module.exports = StageChannel;

55
node_modules/eris/lib/structures/StageInstance.js generated vendored Normal file
View File

@ -0,0 +1,55 @@
"use strict";
const Base = require("./Base");
/**
* Represents a stage instance
* @prop {StageChannel} channel The associated stage channel
* @prop {Boolean} discoverableDisabled Whether or not stage discovery is disabled
* @prop {Guild} guild The guild of the associated stage channel
* @prop {String} id The ID of the stage instance
* @prop {Number} privacyLevel The privacy level of the stage instance. 1 is public, 2 is guild only
* @prop {String} topic The stage instance topic
*/
class StageInstance extends Base {
constructor(data, client) {
super(data.id);
this._client = client;
this.channel = client.getChannel(data.channel_id) || {id: data.channel_id};
this.guild = client.guilds.get(data.guild_id) || {id: data.guild_id};
this.update(data);
}
update(data) {
if(data.discoverable_disabled !== undefined) {
this.discoverableDisabled = data.discoverable_disabled;
}
if(data.privacy_level !== undefined) {
this.privacyLevel = data.privacy_level;
}
if(data.topic !== undefined) {
this.topic = data.topic;
}
}
/**
* Delete this stage instance
* @returns {Promise}
*/
delete() {
return this._client.deleteStageInstance.call(this._client, this.channel.id);
}
/**
* Update this stage instance
* @arg {Object} options The properties to edit
* @arg {Number} [options.privacyLevel] The privacy level of the stage instance. 1 is public, 2 is guild only
* @arg {String} [options.topic] The stage instance topic
* @returns {Promise<StageInstance>}
*/
edit(options) {
return this._client.editStageInstance.call(this._client, this.channel.id, options);
}
}
module.exports = StageInstance;

12
node_modules/eris/lib/structures/StoreChannel.js generated vendored Normal file
View File

@ -0,0 +1,12 @@
"use strict";
const GuildChannel = require("./GuildChannel");
/**
* Represents a store channel. See GuildChannel for more properties and methods. Bots cannot read or send messages in a store channel.
* @extends GuildChannel
*/
class StoreChannel extends GuildChannel {
}
module.exports = StoreChannel;

395
node_modules/eris/lib/structures/TextChannel.js generated vendored Normal file
View File

@ -0,0 +1,395 @@
"use strict";
const Collection = require("../util/Collection");
const GuildChannel = require("./GuildChannel");
const Message = require("./Message");
/**
* Represents a guild text channel. See GuildChannel for more properties and methods.
* @extends GuildChannel
* @prop {Number} defaultAutoArchiveDuration The default duration of newly created threads in minutes to automatically archive the thread after inactivity (60, 1440, 4320, 10080)
* @prop {String} lastMessageID The ID of the last message in this channel
* @prop {Number} lastPinTimestamp The timestamp of the last pinned message
* @prop {Collection<Message>} messages Collection of Messages in this channel
* @prop {Number} rateLimitPerUser The ratelimit of the channel, in seconds. 0 means no ratelimit is enabled
* @prop {String?} topic The topic of the channel
*/
class TextChannel extends GuildChannel {
constructor(data, client, messageLimit) {
super(data, client);
this.messages = new Collection(Message, messageLimit == null ? client.options.messageLimit : messageLimit);
this.lastMessageID = data.last_message_id || null;
this.rateLimitPerUser = data.rate_limit_per_user == null ? null : data.rate_limit_per_user;
this.lastPinTimestamp = data.last_pin_timestamp ? Date.parse(data.last_pin_timestamp) : null;
this.update(data);
}
update(data) {
super.update(data);
if(data.rate_limit_per_user !== undefined) {
this.rateLimitPerUser = data.rate_limit_per_user;
}
if(data.topic !== undefined) {
this.topic = data.topic;
}
if(data.default_auto_archive_duration !== undefined) {
this.defaultAutoArchiveDuration = data.default_auto_archive_duration;
}
}
/**
* Add a reaction to a message
* @arg {String} messageID The ID of the message
* @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji)
* @arg {String} [userID="@me"] The ID of the user to react as. Passing this parameter is deprecated and will not be supported in future versions.
* @returns {Promise}
*/
addMessageReaction(messageID, reaction, userID) {
return this.client.addMessageReaction.call(this.client, this.id, messageID, reaction, userID);
}
/**
* Create an invite for the channel
* @arg {Object} [options] Invite generation options
* @arg {Number} [options.maxAge] How long the invite should last in seconds
* @arg {Number} [options.maxUses] How many uses the invite should last for
* @arg {Boolean} [options.temporary] Whether the invite grants temporary membership or not
* @arg {Boolean} [options.unique] Whether the invite is unique or not
* @arg {String} [reason] The reason to be displayed in audit logs
* @returns {Promise<Invite>}
*/
createInvite(options, reason) {
return this.client.createChannelInvite.call(this.client, this.id, options, reason);
}
/**
* Create a message in the channel
* @arg {String | Object} content A string or object. If an object is passed:
* @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default)
* @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here.
* @arg {Boolean | Array<String>} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow.
* @arg {Boolean | Array<String>} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow.
* @arg {Boolean} [content.allowedMentions.repliedUser] Whether or not to mention the author of the message being replied to.
* @arg {Array<Object>} [content.components] An array of component objects
* @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only)
* @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only)
* @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2)
* @arg {String} [content.components[].label] The label to be displayed in the component (type 2)
* @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1)
* @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1)
* @arg {Array<Object>} [content.components[].options] The options for this component (type 3 only)
* @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected
* @arg {String} [content.components[].options[].description] The description for this option
* @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option
* @arg {String} content.components[].options[].label The label for this option
* @arg {Number | String} content.components[].options[].value The value for this option
* @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only)
* @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required
* @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu
* @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only)
* @arg {String} [content.content] A content string
* @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Array<Object>} [content.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Object} [content.messageReference] The message reference, used when replying to messages
* @arg {String} [content.messageReference.channelID] The channel ID of the referenced message
* @arg {Boolean} [content.messageReference.failIfNotExists=true] Whether to throw an error if the message reference doesn't exist. If false, and the referenced message doesn't exist, the message is created without a referenced message
* @arg {String} [content.messageReference.guildID] The guild ID of the referenced message
* @arg {String} content.messageReference.messageID The message ID of the referenced message. This cannot reference a system message
* @arg {String} [content.messageReferenceID] [DEPRECATED] The ID of the message should be replied to. Use `messageReference` instead
* @arg {Array<String>} [content.stickerIDs] An array of IDs corresponding to stickers to send
* @arg {Boolean} [content.tts] Set the message TTS flag
* @arg {Object | Array<Object>} [file] A file object (or an Array of them)
* @arg {Buffer} file.file A buffer containing file data
* @arg {String} file.name What to name the file
* @returns {Promise<Message>}
*/
createMessage(content, file) {
return this.client.createMessage.call(this.client, this.id, content, file);
}
/**
* Create a thread with an existing message
* @arg {String} messageID The ID of the message to create the thread from
* @arg {Object} options The thread options
* @arg {Number} options.autoArchiveDuration Duration in minutes to automatically archive the thread after recent activity, either 60, 1440, 4320 or 10080
* @arg {String} options.name The thread channel name
* @returns {Promise<NewsThreadChannel | PublicThreadChannel>}
*/
createThreadWithMessage(messageID, options) {
return this.client.createThreadWithMessage.call(this.client, this.id, messageID, options);
}
/**
* Create a thread without an existing message
* @arg {Object} options The thread options
* @arg {Number} options.autoArchiveDuration Duration in minutes to automatically archive the thread after recent activity, either 60, 1440, 4320 or 10080
* @arg {boolean} [options.invitable] Whether non-moderators can add other non-moderators to the thread (private threads only)
* @arg {String} options.name The thread channel name
* @arg {Number} [options.type] The channel type of the thread to create. It is recommended to explicitly set this property as this will be a required property in API v10
* @returns {Promise<PrivateThreadChannel>}
*/
createThreadWithoutMessage(options) {
return this.client.createThreadWithoutMessage.call(this.client, this.id, options);
}
/**
* Create a channel webhook
* @arg {Object} options Webhook options
* @arg {String} [options.avatar] The default avatar as a base64 data URI. Note: base64 strings alone are not base64 data URI strings
* @arg {String} options.name The default name
* @arg {String} [reason] The reason to be displayed in audit logs
* @returns {Promise<Object>} Resolves with a webhook object
*/
createWebhook(options, reason) {
return this.client.createChannelWebhook.call(this.client, this.id, options, reason);
}
/**
* Delete a message
* @arg {String} messageID The ID of the message
* @arg {String} [reason] The reason to be displayed in audit logs
* @returns {Promise}
*/
deleteMessage(messageID, reason) {
return this.client.deleteMessage.call(this.client, this.id, messageID, reason);
}
/**
* Bulk delete messages (bot accounts only)
* @arg {Array<String>} messageIDs Array of message IDs to delete
* @arg {String} [reason] The reason to be displayed in audit logs
* @returns {Promise}
*/
deleteMessages(messageIDs, reason) {
return this.client.deleteMessages.call(this.client, this.id, messageIDs, reason);
}
/**
* Edit a message
* @arg {String} messageID The ID of the message
* @arg {String | Array | Object} content A string, array of strings, or object. If an object is passed:
* @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default)
* @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here.
* @arg {Boolean | Array<String>} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow.
* @arg {Boolean | Array<String>} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow.
* @arg {Array<Object>} [content.components] An array of component objects
* @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only)
* @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only)
* @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2)
* @arg {String} [content.components[].label] The label to be displayed in the component (type 2)
* @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1)
* @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1)
* @arg {Array<Object>} [content.components[].options] The options for this component (type 3 only)
* @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected
* @arg {String} [content.components[].options[].description] The description for this option
* @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option
* @arg {String} content.components[].options[].label The label for this option
* @arg {Number | String} content.components[].options[].value The value for this option
* @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only)
* @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required
* @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu
* @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only)
* @arg {String} [content.content] A content string
* @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Array<Object>} [content.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Object | Array<Object>} [content.file] A file object (or an Array of them)
* @arg {Buffer} content.file[].file A buffer containing file data
* @arg {String} content.file[].name What to name the file
* @arg {Number} [content.flags] A number representing the flags to apply to the message. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#message-object-message-flags) for flags reference
* @returns {Promise<Message>}
*/
editMessage(messageID, content) {
return this.client.editMessage.call(this.client, this.id, messageID, content);
}
/**
* [DEPRECATED] Get all active threads in this channel. Use guild.getActiveThreads instead
* @returns {Promise<Object>} An object containing an array of `threads`, an array of `members` and whether the response `hasMore` threads that could be returned in a subsequent call
*/
getActiveThreads() {
return this.client.getActiveThreads.call(this.client, this.id);
}
/**
* Get all archived threads in this channel
* @arg {String} type The type of thread channel, either "public" or "private"
* @arg {Object} [options] Additional options when requesting archived threads
* @arg {Date} [options.before] List of threads to return before the timestamp
* @arg {Number} [options.limit] Maximum number of threads to return
* @returns {Promise<Object>} An object containing an array of `threads`, an array of `members` and whether the response `hasMore` threads that could be returned in a subsequent call
*/
getArchivedThreads(type, options) {
return this.client.getArchivedThreads.call(this.client, this.id, type, options);
}
/**
* Get all invites in the channel
* @returns {Promise<Array<Invite>>}
*/
getInvites() {
return this.client.getChannelInvites.call(this.client, this.id);
}
/**
* Get joined private archived threads in this channel
* @arg {Object} [options] Additional options when requesting archived threads
* @arg {Date} [options.before] List of threads to return before the timestamp
* @arg {Number} [options.limit] Maximum number of threads to return
* @returns {Promise<Object>} An object containing an array of `threads`, an array of `members` and whether the response `hasMore` threads that could be returned in a subsequent call
*/
getJoinedPrivateArchivedThreads(options) {
return this.client.getJoinedPrivateArchivedThreads.call(this.client, this.id, options);
}
/**
* Get a previous message in the channel
* @arg {String} messageID The ID of the message
* @returns {Promise<Message>}
*/
getMessage(messageID) {
return this.client.getMessage.call(this.client, this.id, messageID);
}
/**
* Get a list of users who reacted with a specific reaction
* @arg {String} messageID The ID of the message
* @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji)
* @arg {Object} [options] Options for the request. If this is a number, it is treated as `options.limit` ([DEPRECATED] behavior)
* @arg {Number} [options.limit=100] The maximum number of users to get
* @arg {String} [options.after] Get users after this user ID
* @arg {String} [before] [DEPRECATED] Get users before this user ID. Discord no longer supports this parameter
* @arg {String} [after] [DEPRECATED] Get users after this user ID
* @returns {Promise<Array<User>>}
*/
getMessageReaction(messageID, reaction, options, before, after) {
return this.client.getMessageReaction.call(this.client, this.id, messageID, reaction, options, before, after);
}
/**
* Get previous messages in the channel
* @arg {Object} [options] Options for the request. If this is a number ([DEPRECATED] behavior), it is treated as `options.limit`
* @arg {String} [options.after] Get messages after this message ID
* @arg {String} [options.around] Get messages around this message ID (does not work with limit > 100)
* @arg {String} [options.before] Get messages before this message ID
* @arg {Number} [options.limit=50] The max number of messages to get
* @arg {String} [before] [DEPRECATED] Get messages before this message ID
* @arg {String} [after] [DEPRECATED] Get messages after this message ID
* @arg {String} [around] [DEPRECATED] Get messages around this message ID (does not work with limit > 100)
* @returns {Promise<Array<Message>>}
*/
getMessages(options, before, after, around) {
return this.client.getMessages.call(this.client, this.id, options, before, after, around);
}
/**
* Get all the pins in the channel
* @returns {Promise<Array<Message>>}
*/
getPins() {
return this.client.getPins.call(this.client, this.id);
}
/**
* Get all the webhooks in the channel
* @returns {Promise<Array<Object>>} Resolves with an array of webhook objects
*/
getWebhooks() {
return this.client.getChannelWebhooks.call(this.client, this.id);
}
/**
* Pin a message
* @arg {String} messageID The ID of the message
* @returns {Promise}
*/
pinMessage(messageID) {
return this.client.pinMessage.call(this.client, this.id, messageID);
}
/**
* Purge previous messages in the channel with an optional filter (bot accounts only)
* @arg {Object} options Options for the request. If this is a number ([DEPRECATED] behavior), it is treated as `options.limit`
* @arg {String} [options.after] Get messages after this message ID
* @arg {String} [options.before] Get messages before this message ID
* @arg {Function} [options.filter] Optional filter function that returns a boolean when passed a Message object
* @arg {Number} options.limit The max number of messages to search through, -1 for no limit
* @arg {String} [options.reason] The reason to be displayed in audit logs
* @arg {Function} [filter] [DEPRECATED] Optional filter function that returns a boolean when passed a Message object
* @arg {String} [before] [DEPRECATED] Get messages before this message ID
* @arg {String} [after] [DEPRECATED] Get messages after this message ID
* @arg {String} [reason] [DEPRECATED] The reason to be displayed in audit logs
* @returns {Promise<Number>} Resolves with the number of messages deleted
*/
purge(limit, filter, before, after, reason) {
return this.client.purgeChannel.call(this.client, this.id, limit, filter, before, after, reason);
}
/**
* Remove a reaction from a message
* @arg {String} messageID The ID of the message
* @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji)
* @arg {String} [userID="@me"] The ID of the user to remove the reaction for
* @returns {Promise}
*/
removeMessageReaction(messageID, reaction, userID) {
return this.client.removeMessageReaction.call(this.client, this.id, messageID, reaction, userID);
}
/**
* Remove all reactions from a message for a single emoji
* @arg {String} messageID The ID of the message
* @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji)
* @returns {Promise}
*/
removeMessageReactionEmoji(messageID, reaction) {
return this.client.removeMessageReactionEmoji.call(this.client, this.id, messageID, reaction);
}
/**
* Remove all reactions from a message
* @arg {String} messageID The ID of the message
* @returns {Promise}
*/
removeMessageReactions(messageID) {
return this.client.removeMessageReactions.call(this.client, this.id, messageID);
}
/**
* Send typing status in the channel
* @returns {Promise}
*/
sendTyping() {
return this.client.sendChannelTyping.call(this.client, this.id);
}
/**
* Unpin a message
* @arg {String} messageID The ID of the message
* @returns {Promise}
*/
unpinMessage(messageID) {
return this.client.unpinMessage.call(this.client, this.id, messageID);
}
/**
* Un-send a message. You're welcome Programmix
* @arg {String} messageID The ID of the message
* @returns {Promise}
*/
unsendMessage(messageID) {
return this.client.deleteMessage.call(this.client, this.id, messageID);
}
toJSON(props = []) {
return super.toJSON([
"lastMessageID",
"lastPinTimestamp",
"messages",
"rateLimitPerUser",
"topic",
...props
]);
}
}
module.exports = TextChannel;

271
node_modules/eris/lib/structures/TextVoiceChannel.js generated vendored Normal file
View File

@ -0,0 +1,271 @@
"use strict";
const VoiceChannel = require("./VoiceChannel");
const Collection = require("../util/Collection");
const Message = require("./Message");
/**
* Represents a Text-in-Voice channel. See VoiceChannel for more properties and methods.
* @extends VoiceChannel
* @prop {String} lastMessageID The ID of the last message in this channel
* @prop {Collection<Message>} messages Collection of Messages in this channel
* @prop {Number} rateLimitPerUser The ratelimit of the channel, in seconds. 0 means no ratelimit is enabled
*/
class TextVoiceChannel extends VoiceChannel {
constructor(data, client, messageLimit) {
super(data, client);
this.messages = new Collection(Message, messageLimit == null ? client.options.messageLimit : messageLimit);
this.lastMessageID = data.last_message_id || null;
this.rateLimitPerUser = data.rate_limit_per_user == null ? null : data.rate_limit_per_user;
}
update(data) {
super.update(data);
// "not yet, possibly TBD"
if(data.rate_limit_per_user !== undefined) {
this.rateLimitPerUser = data.rate_limit_per_user;
}
}
/**
* Add a reaction to a message
* @arg {String} messageID The ID of the message
* @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji)
* @arg {String} [userID="@me"] The ID of the user to react as. Passing this parameter is deprecated and will not be supported in future versions.
* @returns {Promise}
*/
addMessageReaction(messageID, reaction, userID) {
return this.client.addMessageReaction.call(this.client, this.id, messageID, reaction, userID);
}
/**
* Create an invite for the channel
* @arg {Object} [options] Invite generation options
* @arg {Number} [options.maxAge] How long the invite should last in seconds
* @arg {Number} [options.maxUses] How many uses the invite should last for
* @arg {Boolean} [options.temporary] Whether the invite grants temporary membership or not
* @arg {Boolean} [options.unique] Whether the invite is unique or not
* @arg {String} [reason] The reason to be displayed in audit logs
* @returns {Promise<Invite>}
*/
createInvite(options, reason) {
return this.client.createChannelInvite.call(this.client, this.id, options, reason);
}
/**
* Create a message in the channel
* Note: If you want to DM someone, the user ID is **not** the DM channel ID. use Client.getDMChannel() to get the DM channel ID for a user
* @arg {String | Object} content A string or object. If an object is passed:
* @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default)
* @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here.
* @arg {Boolean | Array<String>} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow.
* @arg {Boolean | Array<String>} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow.
* @arg {Boolean} [options.allowedMentions.repliedUser] Whether or not to mention the author of the message being replied to
* @arg {Array<Object>} [content.components] An array of component objects
* @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only)
* @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only)
* @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2)
* @arg {String} [content.components[].label] The label to be displayed in the component (type 2)
* @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1)
* @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1)
* @arg {Array<Object>} [content.components[].options] The options for this component (type 3 only)
* @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected
* @arg {String} [content.components[].options[].description] The description for this option
* @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option
* @arg {String} content.components[].options[].label The label for this option
* @arg {Number | String} content.components[].options[].value The value for this option
* @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only)
* @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required
* @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu
* @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only)
* @arg {String} content.content A content string
* @arg {Object} [content.embed] [DEPRECATED] An embed object. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure. Use `embeds` instead
* @arg {Array<Object>} [content.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Object} [content.messageReference] The message reference, used when replying to messages
* @arg {String} [content.messageReference.channelID] The channel ID of the referenced message
* @arg {Boolean} [content.messageReference.failIfNotExists=true] Whether to throw an error if the message reference doesn't exist. If false, and the referenced message doesn't exist, the message is created without a referenced message
* @arg {String} [content.messageReference.guildID] The guild ID of the referenced message
* @arg {String} content.messageReference.messageID The message ID of the referenced message. This cannot reference a system message
* @arg {String} [content.messageReferenceID] [DEPRECATED] The ID of the message should be replied to. Use `messageReference` instead
* @arg {Array<String>} [content.stickerIDs] An array of IDs corresponding to the stickers to send
* @arg {Boolean} [content.tts] Set the message TTS flag
* @arg {Object} [file] A file object
* @arg {Buffer} file.file A buffer containing file data
* @arg {String} file.name What to name the file
* @returns {Promise<Message>}
*/
createMessage(content, file) {
return this.client.createMessage.call(this.client, this.id, content, file);
}
/**
* Delete a message
* @arg {String} messageID The ID of the message
* @arg {String} [reason] The reason to be displayed in audit logs
* @returns {Promise}
*/
deleteMessage(messageID, reason) {
return this.client.deleteMessage.call(this.client, this.id, messageID, reason);
}
/**
* Bulk delete messages (bot accounts only)
* @arg {Array<String>} messageIDs Array of message IDs to delete
* @arg {String} [reason] The reason to be displayed in audit logs
* @returns {Promise}
*/
deleteMessages(messageIDs, reason) {
return this.client.deleteMessages.call(this.client, this.id, messageIDs, reason);
}
/**
* Edit a message
* @arg {String} messageID The ID of the message
* @arg {String | Array | Object} content A string, array of strings, or object. If an object is passed:
* @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default)
* @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here.
* @arg {Boolean | Array<String>} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow.
* @arg {Boolean | Array<String>} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow.
* @arg {Array<Object>} [content.components] An array of component objects
* @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only)
* @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only)
* @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2)
* @arg {String} [content.components[].label] The label to be displayed in the component (type 2)
* @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1)
* @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1)
* @arg {Array<Object>} [content.components[].options] The options for this component (type 3 only)
* @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected
* @arg {String} [content.components[].options[].description] The description for this option
* @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option
* @arg {String} content.components[].options[].label The label for this option
* @arg {Number | String} content.components[].options[].value The value for this option
* @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only)
* @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required
* @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu
* @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only)
* @arg {String} content.content A content string
* @arg {Boolean} [content.disableEveryone] Whether to filter @everyone/@here or not (overrides default)
* @arg {Object} [content.embed] [DEPRECATED] An embed object. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure. Use `embeds` instead
* @arg {Array<Object>} [content.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Number} [content.flags] A number representing the flags to apply to the message. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#message-object-message-flags) for flags reference
* @returns {Promise<Message>}
*/
editMessage(messageID, content) {
return this.client.editMessage.call(this.client, this.id, messageID, content);
}
/**
* Get all invites in the channel
* @returns {Promise<Array<Invite>>}
*/
getInvites() {
return this.client.getChannelInvites.call(this.client, this.id);
}
/**
* Get a previous message in the channel
* @arg {String} messageID The ID of the message
* @returns {Promise<Message>}
*/
getMessage(messageID) {
return this.client.getMessage.call(this.client, this.id, messageID);
}
/**
* Get a list of users who reacted with a specific reaction
* @arg {String} messageID The ID of the message
* @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji)
* @arg {Object} [options] Options for the request. If this is a number, it is treated as `options.limit` ([DEPRECATED] behavior)
* @arg {Number} [options.limit=100] The maximum number of users to get
* @arg {String} [options.after] Get users after this user ID
* @arg {String} [before] [DEPRECATED] Get users before this user ID. Discord no longer supports this parameter
* @arg {String} [after] [DEPRECATED] Get users after this user ID
* @returns {Promise<Array<User>>}
*/
getMessageReaction(messageID, reaction, options, before, after) {
return this.client.getMessageReaction.call(this.client, this.id, messageID, reaction, options, before, after);
}
/**
* Get previous messages in the channel
* @arg {Object} [options] Options for the request. If this is a number ([DEPRECATED] behavior), it is treated as `options.limit`
* @arg {String} [options.after] Get messages after this message ID
* @arg {String} [options.around] Get messages around this message ID (does not work with limit > 100)
* @arg {String} [options.before] Get messages before this message ID
* @arg {Number} [options.limit=50] The max number of messages to get
* @arg {String} [before] [DEPRECATED] Get messages before this message ID
* @arg {String} [after] [DEPRECATED] Get messages after this message ID
* @arg {String} [around] [DEPRECATED] Get messages around this message ID (does not work with limit > 100)
* @returns {Promise<Array<Message>>}
*/
getMessages(options, before, after, around) {
return this.client.getMessages.call(this.client, this.id, options, before, after, around);
}
/**
* Purge previous messages in the channel with an optional filter (bot accounts only)
* @arg {Object} options Options for the request. If this is a number ([DEPRECATED] behavior), it is treated as `options.limit`
* @arg {String} [options.after] Get messages after this message ID
* @arg {String} [options.before] Get messages before this message ID
* @arg {Function} [options.filter] Optional filter function that returns a boolean when passed a Message object
* @arg {Number} options.limit The max number of messages to search through, -1 for no limit
* @arg {String} [options.reason] The reason to be displayed in audit logs
* @arg {Function} [filter] [DEPRECATED] Optional filter function that returns a boolean when passed a Message object
* @arg {String} [before] [DEPRECATED] Get messages before this message ID
* @arg {String} [after] [DEPRECATED] Get messages after this message ID
* @arg {String} [reason] [DEPRECATED] The reason to be displayed in audit logs
* @returns {Promise<Number>} Resolves with the number of messages deleted
*/
purge(limit, filter, before, after, reason) {
return this.client.purgeChannel.call(this.client, this.id, limit, filter, before, after, reason);
}
/**
* Remove a reaction from a message
* @arg {String} messageID The ID of the message
* @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji)
* @arg {String} [userID="@me"] The ID of the user to remove the reaction for
* @returns {Promise}
*/
removeMessageReaction(messageID, reaction, userID) {
return this.client.removeMessageReaction.call(this.client, this.id, messageID, reaction, userID);
}
/**
* Remove all reactions from a message for a single emoji
* @arg {String} messageID The ID of the message
* @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji)
* @returns {Promise}
*/
removeMessageReactionEmoji(messageID, reaction) {
return this.client.removeMessageReactionEmoji.call(this.client, this.id, messageID, reaction);
}
/**
* Remove all reactions from a message
* @arg {String} messageID The ID of the message
* @returns {Promise}
*/
removeMessageReactions(messageID) {
return this.client.removeMessageReactions.call(this.client, this.id, messageID);
}
/**
* Send typing status in the channel
* @returns {Promise}
*/
sendTyping() {
return this.client.sendChannelTyping.call(this.client, this.id);
}
toJSON(props = []) {
return super.toJSON([
"lastMessageID",
"messages",
"rateLimitPerUser",
...props
]);
}
}
module.exports = TextVoiceChannel;

344
node_modules/eris/lib/structures/ThreadChannel.js generated vendored Normal file
View File

@ -0,0 +1,344 @@
"use strict";
const Collection = require("../util/Collection");
const GuildChannel = require("./GuildChannel");
const Message = require("./Message");
const ThreadMember = require("./ThreadMember");
/**
* Represents a thread channel. You also probably want to look at NewsThreadChannel, PublicThreadChannel, and PrivateThreadChannel. See GuildChannel for extra properties.
* @extends GuildChannel
* @prop {String} lastMessageID The ID of the last message in this channel
* @prop {Object?} member Thread member for the current user, if they have joined the thread
* @prop {Number} member.flags The user's thread settings
* @prop {String} member.id The ID of the thread
* @prop {Number} member.joinTimestamp The time the user last joined the thread
* @prop {String} member.userID The ID of the user
* @prop {Number} memberCount An approximate number of users in the thread (stops at 50)
* @prop {Collection<ThreadMember>} members Collection of members in this channel
* @prop {Number} messageCount An approximate number of messages in the thread (stops at 50)
* @prop {Collection<Message>} messages Collection of Messages in this channel
* @prop {String} ownerID The ID of the user that created the thread
* @prop {Number} rateLimitPerUser The ratelimit of the channel, in seconds. 0 means no ratelimit is enabled
* @prop {Object} threadMetadata Metadata for the thread
* @prop {Number} threadMetadata.archiveTimestamp Timestamp when the thread's archive status was last changed, used for calculating recent activity
* @prop {Boolean} threadMetadata.archived Whether the thread is archived
* @prop {Number} threadMetadata.autoArchiveDuration Duration in minutes to automatically archive the thread after recent activity, either 60, 1440, 4320 or 10080
* @prop {Boolean} threadMetadata.locked Whether the thread is locked
*/
class ThreadChannel extends GuildChannel {
constructor(data, client, messageLimit) {
super(data, client);
this.messages = new Collection(Message, messageLimit == null ? client.options.messageLimit : messageLimit);
this.members = new Collection(ThreadMember);
this.lastMessageID = data.last_message_id || null;
this.ownerID = data.owner_id;
this.update(data);
}
update(data) {
super.update(data);
if(data.member_count !== undefined) {
this.memberCount = data.member_count;
}
if(data.message_count !== undefined) {
this.messageCount = data.message_count;
}
if(data.rate_limit_per_user !== undefined) {
this.rateLimitPerUser = data.rate_limit_per_user;
}
if(data.thread_metadata !== undefined) {
this.threadMetadata = {
archiveTimestamp: Date.parse(data.thread_metadata.archive_timestamp),
archived: data.thread_metadata.archived,
autoArchiveDuration: data.thread_metadata.auto_archive_duration,
locked: data.thread_metadata.locked
};
}
if(data.member !== undefined) {
this.member = new ThreadMember(data.member, this.client);
}
}
/**
* Add a reaction to a message
* @arg {String} messageID The ID of the message
* @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji)
* @returns {Promise}
*/
addMessageReaction(messageID, reaction) {
return this.client.addMessageReaction.call(this.client, this.id, messageID, reaction);
}
/**
* Create a message in the channel
* @arg {String | Object} content A string or object. If an object is passed:
* @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default)
* @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here.
* @arg {Boolean | Array<String>} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow.
* @arg {Boolean | Array<String>} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow.
* @arg {Boolean} [content.allowedMentions.repliedUser] Whether or not to mention the author of the message being replied to.
* @arg {Array<Object>} [content.components] An array of component objects
* @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only)
* @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only)
* @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2)
* @arg {String} [content.components[].label] The label to be displayed in the component (type 2)
* @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1)
* @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1)
* @arg {Array<Object>} [content.components[].options] The options for this component (type 3 only)
* @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected
* @arg {String} [content.components[].options[].description] The description for this option
* @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option
* @arg {String} content.components[].options[].label The label for this option
* @arg {Number | String} content.components[].options[].value The value for this option
* @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only)
* @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required
* @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu
* @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only)
* @arg {String} [content.content] A content string
* @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Array<Object>} [content.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Object} [content.messageReference] The message reference, used when replying to messages
* @arg {String} [content.messageReference.channelID] The channel ID of the referenced message
* @arg {Boolean} [content.messageReference.failIfNotExists=true] Whether to throw an error if the message reference doesn't exist. If false, and the referenced message doesn't exist, the message is created without a referenced message
* @arg {String} [content.messageReference.guildID] The guild ID of the referenced message
* @arg {String} content.messageReference.messageID The message ID of the referenced message. This cannot reference a system message
* @arg {String} [content.messageReferenceID] [DEPRECATED] The ID of the message should be replied to. Use `messageReference` instead
* @arg {Array<String>} [content.stickerIDs] An array of IDs corresponding to stickers to send
* @arg {Boolean} [content.tts] Set the message TTS flag
* @arg {Object | Array<Object>} [file] A file object (or an Array of them)
* @arg {Buffer} file.file A buffer containing file data
* @arg {String} file.name What to name the file
* @returns {Promise<Message>}
*/
createMessage(content, file) {
return this.client.createMessage.call(this.client, this.id, content, file);
}
/**
* Delete a message
* @arg {String} messageID The ID of the message
* @arg {String} [reason] The reason to be displayed in audit logs
* @returns {Promise}
*/
deleteMessage(messageID, reason) {
return this.client.deleteMessage.call(this.client, this.id, messageID, reason);
}
/**
* Bulk delete messages (bot accounts only)
* @arg {Array<String>} messageIDs Array of message IDs to delete
* @arg {String} [reason] The reason to be displayed in audit logs
* @returns {Promise}
*/
deleteMessages(messageIDs, reason) {
return this.client.deleteMessages.call(this.client, this.id, messageIDs, reason);
}
/**
* Edit a message
* @arg {String} messageID The ID of the message
* @arg {String | Array | Object} content A string, array of strings, or object. If an object is passed:
* @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default)
* @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here.
* @arg {Boolean | Array<String>} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow.
* @arg {Boolean | Array<String>} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow.
* @arg {Array<Object>} [content.components] An array of component objects
* @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only)
* @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only)
* @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2)
* @arg {String} [content.components[].label] The label to be displayed in the component (type 2)
* @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1)
* @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1)
* @arg {Array<Object>} [content.components[].options] The options for this component (type 3 only)
* @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected
* @arg {String} [content.components[].options[].description] The description for this option
* @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option
* @arg {String} content.components[].options[].label The label for this option
* @arg {Number | String} content.components[].options[].value The value for this option
* @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only)
* @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required
* @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu
* @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only)
* @arg {String} [content.content] A content string
* @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Array<Object>} [content.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Object | Array<Object>} [content.file] A file object (or an Array of them)
* @arg {Buffer} content.file[].file A buffer containing file data
* @arg {String} content.file[].name What to name the file
* @arg {Number} [content.flags] A number representing the flags to apply to the message. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#message-object-message-flags) for flags reference
* @returns {Promise<Message>}
*/
editMessage(messageID, content) {
return this.client.editMessage.call(this.client, this.id, messageID, content);
}
/**
* Get a list of members that are part of this thread channel
* @returns {Promise<Array<ThreadMember>>}
*/
getMembers() {
return this.client.getThreadMembers.call(this.client, this.id);
}
/**
* Get a previous message in the channel
* @arg {String} messageID The ID of the message
* @returns {Promise<Message>}
*/
getMessage(messageID) {
return this.client.getMessage.call(this.client, this.id, messageID);
}
/**
* Get a list of users who reacted with a specific reaction
* @arg {String} messageID The ID of the message
* @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji)
* @arg {Object} [options] Options for the request. If this is a number, it is treated as `options.limit` ([DEPRECATED] behavior)
* @arg {Number} [options.limit=100] The maximum number of users to get
* @arg {String} [options.after] Get users after this user ID
* @arg {String} [before] [DEPRECATED] Get users before this user ID. Discord no longer supports this parameter
* @arg {String} [after] [DEPRECATED] Get users after this user ID
* @returns {Promise<Array<User>>}
*/
getMessageReaction(messageID, reaction, options, before, after) {
return this.client.getMessageReaction.call(this.client, this.id, messageID, reaction, options, before, after);
}
/**
* Get previous messages in the channel
* @arg {Object} [options] Options for the request. If this is a number ([DEPRECATED] behavior), it is treated as `options.limit`
* @arg {String} [options.after] Get messages after this message ID
* @arg {String} [options.around] Get messages around this message ID (does not work with limit > 100)
* @arg {String} [options.before] Get messages before this message ID
* @arg {Number} [options.limit=50] The max number of messages to get
* @arg {String} [before] [DEPRECATED] Get messages before this message ID
* @arg {String} [after] [DEPRECATED] Get messages after this message ID
* @arg {String} [around] [DEPRECATED] Get messages around this message ID (does not work with limit > 100)
* @returns {Promise<Array<Message>>}
*/
getMessages(options, before, after, around) {
return this.client.getMessages.call(this.client, this.id, options, before, after, around);
}
/**
* Get all the pins in the channel
* @returns {Promise<Array<Message>>}
*/
getPins() {
return this.client.getPins.call(this.client, this.id);
}
/**
* Join a thread
* @arg {String} [userID="@me"] The user ID of the user joining
* @returns {Promise}
*/
join(userID) {
return this.client.joinThread.call(this.client, this.id, userID);
}
/**
* Leave a thread
* @arg {String} [userID="@me"] The user ID of the user leaving
* @returns {Promise}
*/
leave(userID) {
return this.client.leaveThread.call(this.client, this.id, userID);
}
/**
* Pin a message
* @arg {String} messageID The ID of the message
* @returns {Promise}
*/
pinMessage(messageID) {
return this.client.pinMessage.call(this.client, this.id, messageID);
}
/**
* Purge previous messages in the channel with an optional filter (bot accounts only)
* @arg {Object} options Options for the request. If this is a number
* @arg {String} [options.after] Get messages after this message ID
* @arg {String} [options.before] Get messages before this message ID
* @arg {Function} [options.filter] Optional filter function that returns a boolean when passed a Message object
* @arg {Number} options.limit The max number of messages to search through, -1 for no limit
* @arg {String} [options.reason] The reason to be displayed in audit logs
* @returns {Promise<Number>} Resolves with the number of messages deleted
*/
purge(options) {
return this.client.purgeChannel.call(this.client, this.id, options);
}
/**
* Remove a reaction from a message
* @arg {String} messageID The ID of the message
* @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji)
* @arg {String} [userID="@me"] The ID of the user to remove the reaction for
* @returns {Promise}
*/
removeMessageReaction(messageID, reaction, userID) {
return this.client.removeMessageReaction.call(this.client, this.id, messageID, reaction, userID);
}
/**
* Remove all reactions from a message for a single emoji
* @arg {String} messageID The ID of the message
* @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji)
* @returns {Promise}
*/
removeMessageReactionEmoji(messageID, reaction) {
return this.client.removeMessageReactionEmoji.call(this.client, this.id, messageID, reaction);
}
/**
* Remove all reactions from a message
* @arg {String} messageID The ID of the message
* @returns {Promise}
*/
removeMessageReactions(messageID) {
return this.client.removeMessageReactions.call(this.client, this.id, messageID);
}
/**
* Send typing status in the channel
* @returns {Promise}
*/
sendTyping() {
return this.client.sendChannelTyping.call(this.client, this.id);
}
/**
* Unpin a message
* @arg {String} messageID The ID of the message
* @returns {Promise}
*/
unpinMessage(messageID) {
return this.client.unpinMessage.call(this.client, this.id, messageID);
}
/**
* Un-send a message. You're welcome Programmix
* @arg {String} messageID The ID of the message
* @returns {Promise}
*/
unsendMessage(messageID) {
return this.client.deleteMessage.call(this.client, this.id, messageID);
}
toJSON(props = []) {
return super.toJSON([
"lastMessageID",
"memberCount",
"messageCount",
"messages",
"ownerID",
"rateLimitPerUser",
"threadMetadata",
"member",
...props
]);
}
}
module.exports = ThreadChannel;

55
node_modules/eris/lib/structures/ThreadMember.js generated vendored Normal file
View File

@ -0,0 +1,55 @@
"use strict";
const Base = require("./Base");
/**
* Represents a thread member
* @prop {Number} flags The user-thread settings of this member
* @prop {Member?} guildMember The guild member that this thread member belongs to. This will never be present when fetching over REST
* @prop {String} id The ID of the thread member
* @prop {Number} joinTimestamp Timestamp of when the member joined the thread
* @prop {String} threadID The ID of the thread this member is a part of
*/
class ThreadMember extends Base {
constructor(data, client) {
super(data.user_id);
this._client = client;
this.flags = data.flags;
this.threadID = data.thread_id || data.id; // Thanks Discord
this.joinTimestamp = Date.parse(data.join_timestamp);
if(data.guild_member !== undefined) {
const guild = this._client.guilds.get(this._client.threadGuildMap[this.threadID]);
this.guildMember = guild.members.update(data.guild_member, guild);
if(data.presence !== undefined) {
this.guildMember.update(data.presence);
}
}
this.update(data);
}
update(data) {
if(data.flags !== undefined) {
this.flags = data.flags;
}
}
/**
* Remove the member from the thread
* @returns {Promise}
*/
leave() {
return this._client.leaveThread.call(this._client, this.threadID, this.id);
}
toJSON(props = []) {
return super.toJSON([
"threadID",
"joinTimestamp",
...props
]);
}
}
module.exports = ThreadMember;

26
node_modules/eris/lib/structures/UnavailableGuild.js generated vendored Normal file
View File

@ -0,0 +1,26 @@
"use strict";
const Base = require("./Base");
/**
* Represents a guild
* @prop {String} id The ID of the guild
* @prop {Boolean} unavailable Whether the guild is unavailable or not
* @prop {Shard} shard The Shard that owns the guild
*/
class UnavailableGuild extends Base {
constructor(data, client) {
super(data.id);
this.shard = client.shards.get(client.guildShardMap[this.id]);
this.unavailable = !!data.unavailable;
}
toJSON(props = []) {
return super.toJSON([
"unavailable",
...props
]);
}
}
module.exports = UnavailableGuild;

453
node_modules/eris/lib/structures/UnknownInteraction.js generated vendored Normal file
View File

@ -0,0 +1,453 @@
"use strict";
const Interaction = require("./Interaction");
const Message = require("./Message");
const Member = require("./Member");
const Permission = require("./Permission");
const {InteractionResponseTypes} = require("../Constants");
/**
* Represents an unknown interaction. See Interaction for more properties.
* Note: Methods are not guaranteed to work properly, they are all added just in case you know which to use.
* @extends Interaction
* @prop {Permission?} appPermissions The permissions the app or bot has within the channel the interaction was sent from
* @prop {(PrivateChannel | TextChannel | NewsChannel)?} channel The channel the interaction was created in. Can be partial with only the id if the channel is not cached.
* @prop {Object?} data The data attached to the interaction
* @prop {String?} guildID The ID of the guild in which the interaction was created
* @prop {Member?} member The member who triggered the interaction (This is only sent when the interaction is invoked within a guild)
* @prop {Message?} message The message the interaction came from (Message Component only). If the message is ephemeral, this will be an object with `id` and `flags` keys.
* @prop {User?} user The user who triggered the interaction (This is only sent when the interaction is invoked within a dm)
*/
class UnknownInteraction extends Interaction {
constructor(info, client) {
super(info, client);
if(info.channel_id !== undefined) {
this.channel = this._client.getChannel(info.channel_id) || {
id: info.channel_id
};
}
if(info.data !== undefined) {
this.data = info.data;
}
if(info.guild_id !== undefined) {
this.guildID = info.guild_id;
}
if(info.member !== undefined) {
if(this.channel.guild) {
info.member.id = info.member.user.id;
this.member = this.channel.guild.members.update(info.member, this.channel.guild);
} else {
const guild = this._client.guilds.get(info.guild_id);
this.member = new Member(info.member, guild, this._client);
}
}
if(info.message !== undefined) {
this.message = new Message(info.message, this._client);
}
if(info.user !== undefined) {
this.user = this._client.users.update(info.user, client);
}
if(info.app_permissions !== undefined) {
this.appPermissions = new Permission(info.app_permissions);
}
}
/**
* Acknowledges the autocomplete interaction with a result of choices.
* Note: You can **not** use more than 1 initial interaction response per interaction.
* @arg {Object} data The data object
* @arg {Number} data.type The type of [interaction response](https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-response-object-interaction-callback-type) to send
* @arg {Object} data.data The data to return to discord
* @returns {Promise}
*/
async acknowledge(data) {
if(this.acknowledged === true) {
throw new Error("You have already acknowledged this interaction.");
}
return this._client.createInteractionResponse.call(this._client, this.id, this.token, data).then(() => this.update());
}
/**
* Respond to the interaction with a followup message
* @arg {String | Object} content A string or object. If an object is passed:
* @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default)
* @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here.
* @arg {Boolean | Array<String>} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow.
* @arg {Boolean | Array<String>} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow.
* @arg {Array<Object>} [content.components] An array of component objects
* @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only)
* @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only)
* @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2)
* @arg {String} [content.components[].label] The label to be displayed in the component (type 2)
* @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1)
* @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1)
* @arg {Array<Object>} [content.components[].options] The options for this component (type 3 only)
* @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected
* @arg {String} [content.components[].options[].description] The description for this option
* @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option
* @arg {String} content.components[].options[].label The label for this option
* @arg {Number | String} content.components[].options[].value The value for this option
* @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only)
* @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required
* @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu
* @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only)
* @arg {String} [content.content] A content string
* @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Array<Object>} [options.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Number} [content.flags] 64 for Ephemeral
* @arg {Boolean} [content.tts] Set the message TTS flag
* @arg {Object | Array<Object>} [file] A file object (or an Array of them)
* @arg {Buffer} file.file A buffer containing file data
* @arg {String} file.name What to name the file
* @returns {Promise<Message?>}
*/
async createFollowup(content, file) {
if(this.acknowledged === false) {
throw new Error("createFollowup cannot be used to acknowledge an interaction, please use acknowledge, createMessage, defer, deferUpdate, editParent, pong, or result first.");
}
if(content !== undefined) {
if(typeof content !== "object" || content === null) {
content = {
content: "" + content
};
} else if(content.content !== undefined && typeof content.content !== "string") {
content.content = "" + content.content;
}
}
if(file) {
content.file = file;
}
return this._client.executeWebhook.call(this._client, this.applicationID, this.token, Object.assign({wait: true}, content));
}
/**
* Acknowledges the interaction with a message. If already acknowledged runs createFollowup
* Note: You can **not** use more than 1 initial interaction response per interaction, use createFollowup if you have already responded with a different interaction response.
* @arg {String | Object} content A string or object. If an object is passed:
* @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default)
* @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here.
* @arg {Boolean | Array<String>} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow.
* @arg {Boolean | Array<String>} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow.
* @arg {Array<Object>} [content.components] An array of component objects
* @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only)
* @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only)
* @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2)
* @arg {String} [content.components[].label] The label to be displayed in the component (type 2)
* @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1)
* @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1)
* @arg {Array<Object>} [content.components[].options] The options for this component (type 3 only)
* @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected
* @arg {String} [content.components[].options[].description] The description for this option
* @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option
* @arg {String} content.components[].options[].label The label for this option
* @arg {Number | String} content.components[].options[].value The value for this option
* @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only)
* @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required
* @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu
* @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only)
* @arg {String} [content.content] A content string
* @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Array<Object>} [content.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Boolean} [content.flags] 64 for Ephemeral
* @arg {Boolean} [content.tts] Set the message TTS flag
* @arg {Object | Array<Object>} [file] A file object (or an Array of them)
* @arg {Buffer} file.file A buffer containing file data
* @arg {String} file.name What to name the file
* @returns {Promise}
*/
async createMessage(content, file) {
if(this.acknowledged === true) {
return this.createFollowup(content, file);
}
if(content !== undefined) {
if(typeof content !== "object" || content === null) {
content = {
content: "" + content
};
} else if(content.content !== undefined && typeof content.content !== "string") {
content.content = "" + content.content;
}
if(content.content !== undefined || content.embeds || content.allowedMentions) {
content.allowed_mentions = this._client._formatAllowedMentions(content.allowedMentions);
}
}
return this._client.createInteractionResponse.call(this._client, this.id, this.token, {
type: InteractionResponseTypes.CHANNEL_MESSAGE_WITH_SOURCE,
data: content
}, file).then(() => this.update());
}
/**
* Acknowledges the interaction with a defer response
* Note: You can **not** use more than 1 initial interaction response per interaction.
* @arg {Number} [flags] 64 for Ephemeral
* @returns {Promise}
*/
async defer(flags) {
if(this.acknowledged === true) {
throw new Error("You have already acknowledged this interaction.");
}
return this._client.createInteractionResponse.call(this._client, this.id, this.token, {
type: InteractionResponseTypes.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE,
data: {
flags: flags || 0
}
}).then(() => this.update());
}
/**
* Acknowledges the interaction with a defer message update response (Message Component only)
* Note: You can **not** use more than 1 initial interaction response per interaction.
* @returns {Promise}
*/
async deferUpdate() {
if(this.acknowledged === true) {
throw new Error("You have already acknowledged this interaction.");
}
return this._client.createInteractionResponse.call(this._client, this.id, this.token, {
type: InteractionResponseTypes.DEFERRED_UPDATE_MESSAGE
}).then(() => this.update());
}
/**
* Delete a message
* @arg {String} messageID the id of the message to delete, or "@original" for the original response.
* @returns {Promise}
*/
async deleteMessage(messageID) {
if(this.acknowledged === false) {
throw new Error("deleteMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, defer, deferUpdate, editParent, or pong first.");
}
return this._client.deleteWebhookMessage.call(this._client, this.applicationID, this.token, messageID);
}
/**
* Delete the Original message (or the parent message for components)
* Warning: Will error with ephemeral messages.
* @returns {Promise}
*/
async deleteOriginalMessage() {
if(this.acknowledged === false) {
throw new Error("deleteOriginalMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, defer, deferUpdate, editParent, or pong first.");
}
return this._client.deleteWebhookMessage.call(this._client, this.applicationID, this.token, "@original");
}
/**
* Edit a message
* @arg {String} messageID the id of the message to edit, or "@original" for the original response.
* @arg {Object} content Interaction message edit options
* @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default)
* @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here.
* @arg {Boolean} [content.allowedMentions.repliedUser] Whether or not to mention the author of the message being replied to.
* @arg {Boolean | Array<String>} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow.
* @arg {Boolean | Array<String>} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow.
* @arg {Array<Object>} [content.components] An array of component objects
* @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only)
* @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only)
* @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2)
* @arg {String} [content.components[].label] The label to be displayed in the component (type 2)
* @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1)
* @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1)
* @arg {Array<Object>} [content.components[].options] The options for this component (type 3 only)
* @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected
* @arg {String} [content.components[].options[].description] The description for this option
* @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option
* @arg {String} content.components[].options[].label The label for this option
* @arg {Number | String} content.components[].options[].value The value for this option
* @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only)
* @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required
* @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu
* @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only)
* @arg {String} [content.content] A content string
* @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Array<Object>} [content.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Object | Array<Object>} [file] A file object (or an Array of them)
* @arg {Buffer} file.file A buffer containing file data
* @arg {String} file.name What to name the file
* @returns {Promise<Message>}
*/
async editMessage(messageID, content, file) {
if(this.acknowledged === false) {
throw new Error("editMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, defer, deferUpdate, editParent, pong, or result first.");
}
if(content !== undefined) {
if(typeof content !== "object" || content === null) {
content = {
content: "" + content
};
} else if(content.content !== undefined && typeof content.content !== "string") {
content.content = "" + content.content;
}
}
if(file) {
content.file = file;
}
return this._client.editWebhookMessage.call(this._client, this.applicationID, this.token, messageID, content);
}
/**
* Edit the Original response message
* @arg {Object} content Interaction message edit options (or the parent message for components)
* @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default)
* @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here.
* @arg {Boolean} [content.allowedMentions.repliedUser] Whether or not to mention the author of the message being replied to.
* @arg {Boolean | Array<String>} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow.
* @arg {Boolean | Array<String>} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow.
* @arg {Array<Object>} [content.components] An array of component objects
* @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only)
* @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only)
* @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2)
* @arg {String} [content.components[].label] The label to be displayed in the component (type 2)
* @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1)
* @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1)
* @arg {Array<Object>} [content.components[].options] The options for this component (type 3 only)
* @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected
* @arg {String} [content.components[].options[].description] The description for this option
* @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option
* @arg {String} content.components[].options[].label The label for this option
* @arg {Number | String} content.components[].options[].value The value for this option
* @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only)
* @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required
* @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu
* @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only)
* @arg {String} [content.content] A content string
* @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Array<Object>} [content.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Object | Array<Object>} [file] A file object (or an Array of them)
* @arg {Buffer} file.file A buffer containing file data
* @arg {String} file.name What to name the file
* @returns {Promise<Message>}
*/
async editOriginalMessage(content, file) {
if(this.acknowledged === false) {
throw new Error("editOriginalMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, defer, deferUpdate, editParent, pong, or result first.");
}
if(content !== undefined) {
if(typeof content !== "object" || content === null) {
content = {
content: "" + content
};
} else if(content.content !== undefined && typeof content.content !== "string") {
content.content = "" + content.content;
}
}
if(file) {
content.file = file;
}
return this._client.editWebhookMessage.call(this._client, this.applicationID, this.token, "@original", content);
}
/**
* Acknowledges the interaction by editing the parent message. If already acknowledged runs editOriginalMessage (Message Component only)
* Note: You can **not** use more than 1 initial interaction response per interaction, use edit if you have already responded with a different interaction response.
* Warning: Will error with ephemeral messages.
* @arg {String | Object} content What to edit the message with
* @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default)
* @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here.
* @arg {Boolean} [content.allowedMentions.repliedUser] Whether or not to mention the author of the message being replied to.
* @arg {Boolean | Array<String>} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow.
* @arg {Boolean | Array<String>} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow.
* @arg {Array<Object>} [content.components] An array of component objects
* @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only)
* @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only)
* @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2)
* @arg {String} [content.components[].label] The label to be displayed in the component (type 2)
* @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1)
* @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1)
* @arg {Array<Object>} [content.components[].options] The options for this component (type 3 only)
* @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected
* @arg {String} [content.components[].options[].description] The description for this option
* @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option
* @arg {String} content.components[].options[].label The label for this option
* @arg {Number | String} content.components[].options[].value The value for this option
* @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only)
* @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required
* @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu
* @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only)
* @arg {String} [content.content] A content string
* @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Array<Object>} [content.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Boolean} [content.flags] 64 for Ephemeral
* @arg {Boolean} [content.tts] Set the message TTS flag
* @arg {Object | Array<Object>} [file] A file object (or an Array of them)
* @arg {Buffer} file.file A buffer containing file data
* @arg {String} file.name What to name the file
* @returns {Promise}
*/
async editParent(content, file) {
if(this.acknowledged === true) {
return this.editOriginalMessage(content);
}
if(content !== undefined) {
if(typeof content !== "object" || content === null) {
content = {
content: "" + content
};
} else if(content.content !== undefined && typeof content.content !== "string") {
content.content = "" + content.content;
}
if(content.content !== undefined || content.embeds || content.allowedMentions) {
content.allowed_mentions = this._client._formatAllowedMentions(content.allowedMentions);
}
}
return this._client.createInteractionResponse.call(this._client, this.id, this.token, {
type: InteractionResponseTypes.UPDATE_MESSAGE,
data: content
}, file).then(() => this.update());
}
/**
* Get the Original response message (or the parent message for components)
* Warning: Will error with ephemeral messages.
* @returns {Promise<Message>}
*/
async getOriginalMessage() {
if(this.acknowledged === false) {
throw new Error("getOriginalMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, defer, deferUpdate, editParent, or pong first.");
}
return this._client.getWebhookMessage.call(this._client, this.applicationID, this.token, "@original");
}
/**
* Acknowledges the ping interaction with a pong response (Ping Only)
* Note: You can **not** use more than 1 initial interaction response per interaction.
* @returns {Promise}
*/
async pong() {
if(this.acknowledged === true) {
throw new Error("You have already acknowledged this interaction.");
}
return this._client.createInteractionResponse.call(this._client, this.id, this.token, {
type: InteractionResponseTypes.PONG
}).then(() => this.update());
}
/**
* Acknowledges the autocomplete interaction with a result of choices.
* Note: You can **not** use more than 1 initial interaction response per interaction.
* @arg {Array<Object>} choices The autocomplete choices to return to the user
* @arg {String | Number} choices[].name The choice display name
* @arg {String} choices[].value The choice value to return to the bot
* @returns {Promise}
*/
async result(choices) {
if(this.acknowledged === true) {
throw new Error("You have already acknowledged this interaction.");
}
return this._client.createInteractionResponse.call(this._client, this.id, this.token, {
type: InteractionResponseTypes.APPLICATION_COMMAND_AUTOCOMPLETE_RESULT,
data: {choices}
}).then(() => this.update());
}
}
module.exports = UnknownInteraction;

190
node_modules/eris/lib/structures/User.js generated vendored Normal file
View File

@ -0,0 +1,190 @@
"use strict";
const Base = require("./Base");
const Endpoints = require("../rest/Endpoints");
/**
* Represents a user
* @prop {Number?} accentColor The user's banner color, or null if no banner color (REST only)
* @prop {String?} avatar The hash of the user's avatar, or null if no avatar
* @prop {String} avatarURL The URL of the user's avatar which can be either a JPG or GIF
* @prop {String?} banner The hash of the user's banner, or null if no banner (REST only)
* @prop {String?} bannerURL The URL of the user's banner
* @prop {Boolean} bot Whether the user is an OAuth bot or not
* @prop {Number} createdAt Timestamp of the user's creation
* @prop {String} defaultAvatar The hash for the default avatar of a user if there is no avatar set
* @prop {String} defaultAvatarURL The URL of the user's default avatar
* @prop {String} discriminator The discriminator of the user
* @prop {String} id The ID of the user
* @prop {String} mention A string that mentions the user
* @prop {Number?} publicFlags Publicly visible flags for this user
* @prop {String} staticAvatarURL The URL of the user's avatar (always a JPG)
* @prop {Boolean} system Whether the user is an official Discord system user (e.g. urgent messages)
* @prop {String} username The username of the user
*/
class User extends Base {
constructor(data, client) {
super(data.id);
if(!client) {
this._missingClientError = new Error("Missing client in constructor"); // Preserve constructor callstack
}
this._client = client;
this.bot = !!data.bot;
this.system = !!data.system;
this.update(data);
}
update(data) {
if(data.avatar !== undefined) {
this.avatar = data.avatar;
}
if(data.username !== undefined) {
this.username = data.username;
}
if(data.discriminator !== undefined) {
this.discriminator = data.discriminator;
}
if(data.public_flags !== undefined) {
this.publicFlags = data.public_flags;
}
if(data.banner !== undefined) {
this.banner = data.banner;
}
if(data.accent_color !== undefined) {
this.accentColor = data.accent_color;
}
}
get avatarURL() {
if(this._missingClientError) {
throw this._missingClientError;
}
return this.avatar ? this._client._formatImage(Endpoints.USER_AVATAR(this.id, this.avatar)) : this.defaultAvatarURL;
}
get bannerURL() {
if(!this.banner) {
return null;
}
if(this._missingClientError) {
throw this._missingClientError;
}
return this._client._formatImage(Endpoints.BANNER(this.id, this.banner));
}
get defaultAvatar() {
return this.discriminator % 5;
}
get defaultAvatarURL() {
return `${Endpoints.CDN_URL}${Endpoints.DEFAULT_USER_AVATAR(this.defaultAvatar)}.png`;
}
get mention() {
return `<@${this.id}>`;
}
get staticAvatarURL() {
if(this._missingClientError) {
throw this._missingClientError;
}
return this.avatar ? this._client._formatImage(Endpoints.USER_AVATAR(this.id, this.avatar), "jpg") : this.defaultAvatarURL;
}
/**
* [USER ACCOUNT] Create a relationship with the user
* @arg {Boolean} [block=false] If true, block the user. Otherwise, add the user as a friend
* @returns {Promise}
*/
addRelationship(block) {
return this._client.addRelationship.call(this._client, this.id, block);
}
/**
* [USER ACCOUNT] Delete the current user's note for another user
*/
deleteNote() {
return this._client.deleteUserNote.call(this._client, this.id);
}
/**
* Get the user's avatar with the given format and size
* @arg {String} [format] The filetype of the avatar ("jpg", "jpeg", "png", "gif", or "webp")
* @arg {Number} [size] The size of the avatar (any power of two between 16 and 4096)
* @returns {String}
*/
dynamicAvatarURL(format, size) {
if(!this.avatar) {
return this.defaultAvatarURL;
}
if(this._missingClientError) {
throw this._missingClientError;
}
return this._client._formatImage(Endpoints.USER_AVATAR(this.id, this.avatar), format, size);
}
/**
* Get the user's banner with the given format and size
* @arg {String} [format] The filetype of the banner ("jpg", "jpeg", "png", "gif", or "webp")
* @arg {Number} [size] The size of the banner (any power of two between 16 and 4096)
* @returns {String?}
*/
dynamicBannerURL(format, size) {
if(!this.banner) {
return null;
}
if(this._missingClientError) {
throw this._missingClientError;
}
return this._client._formatImage(Endpoints.BANNER(this.id, this.banner), format, size);
}
/**
* [USER ACCOUNT] Edit the current user's note for the user
* @arg {String} note The note
* @returns {Promise}
*/
editNote(note) {
return this._client.editUserNote.call(this._client, this.id, note);
}
/**
* Get a DM channel with the user, or create one if it does not exist
* @returns {Promise<PrivateChannel>}
*/
getDMChannel() {
return this._client.getDMChannel.call(this._client, this.id);
}
/**
* [USER ACCOUNT] Get profile data for the user
* @returns {Promise<Object>} The user's profile data.
*/
getProfile() {
return this._client.getUserProfile.call(this._client, this.id);
}
/**
* [USER ACCOUNT] Remove a relationship with the user
* @returns {Promise}
*/
removeRelationship() {
return this._client.removeRelationship.call(this._client, this.id);
}
toJSON(props = []) {
return super.toJSON([
"accentColor",
"avatar",
"banner",
"bot",
"discriminator",
"publicFlags",
"system",
"username",
...props
]);
}
}
module.exports = User;

95
node_modules/eris/lib/structures/VoiceChannel.js generated vendored Normal file
View File

@ -0,0 +1,95 @@
"use strict";
const Collection = require("../util/Collection");
const GuildChannel = require("./GuildChannel");
const Member = require("./Member");
/**
* Represents a guild voice channel. See GuildChannel for more properties and methods.
* @extends GuildChannel
* @prop {Number?} bitrate The bitrate of the channel
* @prop {String?} rtcRegion The RTC region ID of the channel (automatic when `null`)
* @prop {Number} type The type of the channel
* @prop {Number?} userLimit The max number of users that can join the channel
* @prop {Number?} videoQualityMode The camera video quality mode of the voice channel. `1` is auto, `2` is 720p
* @prop {Collection<Member>} voiceMembers Collection of Members in this channel
*/
class VoiceChannel extends GuildChannel {
constructor(data, client) {
super(data, client);
this.voiceMembers = new Collection(Member);
this.update(data);
}
update(data) {
super.update(data);
if(data.bitrate !== undefined) {
this.bitrate = data.bitrate;
}
if(data.rtc_region !== undefined) {
this.rtcRegion = data.rtc_region;
}
if(data.user_limit !== undefined) {
this.userLimit = data.user_limit;
}
if(data.video_quality_mode !== undefined) {
this.videoQualityMode = data.video_quality_mode;
}
}
/**
* Create an invite for the channel
* @arg {Object} [options] Invite generation options
* @arg {Number} [options.maxAge] How long the invite should last in seconds
* @arg {Number} [options.maxUses] How many uses the invite should last for
* @arg {Boolean} [options.temporary] Whether the invite grants temporary membership or not
* @arg {Boolean} [options.unique] Whether the invite is unique or not
* @arg {String} [reason] The reason to be displayed in audit logs
* @returns {Promise<Invite>}
*/
createInvite(options, reason) {
return this.client.createChannelInvite.call(this.client, this.id, options, reason);
}
/**
* Get all invites in the channel
* @returns {Promise<Array<Invite>>}
*/
getInvites() {
return this.client.getChannelInvites.call(this.client, this.id);
}
/**
* Joins the channel.
* @arg {Object} [options] VoiceConnection constructor options
* @arg {Object} [options.opusOnly] Skip opus encoder initialization. You should not enable this unless you know what you are doing
* @arg {Object} [options.shared] Whether the VoiceConnection will be part of a SharedStream or not
* @arg {Boolean} [options.selfMute] Whether the bot joins the channel muted or not
* @arg {Boolean} [options.selfDeaf] Whether the bot joins the channel deafened or not
* @returns {Promise<VoiceConnection>} Resolves with a VoiceConnection
*/
join(options) {
return this.client.joinVoiceChannel.call(this.client, this.id, options);
}
/**
* Leaves the channel.
*/
leave() {
return this.client.leaveVoiceChannel.call(this.client, this.id);
}
toJSON(props = []) {
return super.toJSON([
"bitrate",
"rtcRegion",
"userLimit",
"videoQualityMode",
"voiceMembers",
...props
]);
}
}
module.exports = VoiceChannel;

83
node_modules/eris/lib/structures/VoiceState.js generated vendored Normal file
View File

@ -0,0 +1,83 @@
"use strict";
const Base = require("./Base");
/**
* Represents a member's voice state in a call/guild
* @prop {String?} channelID The ID of the member's current voice channel
* @prop {Boolean} deaf Whether the member is server deafened or not
* @prop {String} id The ID of the member
* @prop {Boolean} mute Whether the member is server muted or not
* @prop {Number?} requestToSpeakTimestamp Timestamp of the member's latest request to speak
* @prop {Boolean} selfDeaf Whether the member is self deafened or not
* @prop {Boolean} selfMute Whether the member is self muted or not
* @prop {Boolean} selfStream Whether the member is streaming using "Go Live"
* @prop {Boolean} selfVideo Whether the member's camera is enabled
* @prop {Boolean} suppress Whether the member is suppressed or not
* @prop {String?} sessionID The ID of the member's current voice session
*/
class VoiceState extends Base {
constructor(data) {
super(data.id);
this.mute = false;
this.deaf = false;
this.requestToSpeakTimestamp = null;
this.selfMute = false;
this.selfDeaf = false;
this.selfStream = false;
this.selfVideo = false;
this.suppress = false;
this.update(data);
}
update(data) {
if(data.channel_id !== undefined) {
this.channelID = data.channel_id;
this.sessionID = data.channel_id === null ? null : data.session_id;
} else if(this.channelID === undefined) {
this.channelID = this.sessionID = null;
}
if(data.mute !== undefined) {
this.mute = data.mute;
}
if(data.deaf !== undefined) {
this.deaf = data.deaf;
}
if(data.request_to_speak_timestamp !== undefined) {
this.requestToSpeakTimestamp = Date.parse(data.request_to_speak_timestamp);
}
if(data.self_mute !== undefined) {
this.selfMute = data.self_mute;
}
if(data.self_deaf !== undefined) {
this.selfDeaf = data.self_deaf;
}
if(data.self_video !== undefined) {
this.selfVideo = data.self_video;
}
if(data.self_stream !== undefined) {
this.selfStream = data.self_stream;
}
if(data.suppress !== undefined) { // Bots ignore this
this.suppress = data.suppress;
}
}
toJSON(props = []) {
return super.toJSON([
"channelID",
"deaf",
"mute",
"requestToSpeakTimestamp",
"selfDeaf",
"selfMute",
"selfStream",
"selfVideo",
"sessionID",
"suppress",
...props
]);
}
}
module.exports = VoiceState;

76
node_modules/eris/lib/util/BrowserWebSocket.js generated vendored Normal file
View File

@ -0,0 +1,76 @@
const util = require("util");
const Base = require("../structures/Base");
let EventEmitter;
try {
EventEmitter = require("eventemitter3");
} catch(err) {
EventEmitter = require("events").EventEmitter;
}
class BrowserWebSocketError extends Error {
constructor(message, event) {
super(message);
this.event = event;
}
}
/**
* Represents a browser's websocket usable by Eris
* @extends EventEmitter
* @prop {String} url The URL to connect to
*/
class BrowserWebSocket extends EventEmitter {
constructor(url) {
super();
if(typeof window === "undefined") {
throw new Error("BrowserWebSocket cannot be used outside of a browser environment");
}
this._ws = new window.WebSocket(url);
this._ws.onopen = () => this.emit("open");
this._ws.onmessage = this._onMessage.bind(this);
this._ws.onerror = (event) => this.emit("error", new BrowserWebSocketError("Unknown error", event));
this._ws.onclose = (event) => this.emit("close", event.code, event.reason);
}
get readyState() {
return this._ws.readyState;
}
close(code, reason) {
return this._ws.close(code, reason);
}
removeEventListener(type, listener) {
return this.removeListener(type, listener);
}
send(data) {
return this._ws.send(data);
}
terminate() {
return this._ws.close();
}
async _onMessage(event) {
if(event.data instanceof window.Blob) {
this.emit("message", await event.data.arrayBuffer());
} else {
this.emit("message", event.data);
}
}
[util.inspect.custom]() {
return Base.prototype[util.inspect.custom].call(this);
}
}
BrowserWebSocket.CONNECTING = 0;
BrowserWebSocket.OPEN = 1;
BrowserWebSocket.CLOSING = 2;
BrowserWebSocket.CLOSED = 3;
module.exports = BrowserWebSocket;

89
node_modules/eris/lib/util/Bucket.js generated vendored Normal file
View File

@ -0,0 +1,89 @@
"use strict";
const util = require("util");
const Base = require("../structures/Base");
/**
* Handle ratelimiting something
* @prop {Number} interval How long (in ms) to wait between clearing used tokens
* @prop {Number} lastReset Timestamp of last token clearing
* @prop {Number} lastSend Timestamp of last token consumption
* @prop {Number} tokenLimit The max number tokens the bucket can consume per interval
* @prop {Number} tokens How many tokens the bucket has consumed in this interval
*/
class Bucket {
/**
* Construct a Bucket
* @arg {Number} tokenLimit The max number of tokens the bucket can consume per interval
* @arg {Number} interval How long (in ms) to wait between clearing used tokens
* @arg {Object} [options] Optional parameters
* @arg {Object} options.latencyRef A latency reference object
* @arg {Number} options.latencyRef.latency Interval between consuming tokens
* @arg {Number} options.reservedTokens How many tokens to reserve for priority operations
*/
constructor(tokenLimit, interval, options = {}) {
this.tokenLimit = tokenLimit;
this.interval = interval;
this.latencyRef = options.latencyRef || {latency: 0};
this.lastReset = this.tokens = this.lastSend = 0;
this.reservedTokens = options.reservedTokens || 0;
this._queue = [];
}
check() {
if(this.timeout || this._queue.length === 0) {
return;
}
if(this.lastReset + this.interval + this.tokenLimit * this.latencyRef.latency < Date.now()) {
this.lastReset = Date.now();
this.tokens = Math.max(0, this.tokens - this.tokenLimit);
}
let val;
let tokensAvailable = this.tokens < this.tokenLimit;
let unreservedTokensAvailable = this.tokens < (this.tokenLimit - this.reservedTokens);
while(this._queue.length > 0 && (unreservedTokensAvailable || (tokensAvailable && this._queue[0].priority))) {
this.tokens++;
tokensAvailable = this.tokens < this.tokenLimit;
unreservedTokensAvailable = this.tokens < (this.tokenLimit - this.reservedTokens);
const item = this._queue.shift();
val = this.latencyRef.latency - Date.now() + this.lastSend;
if(this.latencyRef.latency === 0 || val <= 0) {
item.func();
this.lastSend = Date.now();
} else {
setTimeout(() => {
item.func();
}, val);
this.lastSend = Date.now() + val;
}
}
if(this._queue.length > 0 && !this.timeout) {
this.timeout = setTimeout(() => {
this.timeout = null;
this.check();
}, this.tokens < this.tokenLimit ? this.latencyRef.latency : Math.max(0, this.lastReset + this.interval + this.tokenLimit * this.latencyRef.latency - Date.now()));
}
}
/**
* Queue something in the Bucket
* @arg {Function} func A callback to call when a token can be consumed
* @arg {Boolean} [priority=false] Whether or not the callback should use reserved tokens
*/
queue(func, priority=false) {
if(priority) {
this._queue.unshift({func, priority});
} else {
this._queue.push({func, priority});
}
this.check();
}
[util.inspect.custom]() {
return Base.prototype[util.inspect.custom].call(this);
}
}
module.exports = Bucket;

202
node_modules/eris/lib/util/Collection.js generated vendored Normal file
View File

@ -0,0 +1,202 @@
"use strict";
/**
* Hold a bunch of something
* @extends Map
* @prop {Class} baseObject The base class for all items
* @prop {Number?} limit Max number of items to hold
*/
class Collection extends Map {
/**
* Construct a Collection
* @arg {Class} baseObject The base class for all items
* @arg {Number} [limit] Max number of items to hold
*/
constructor(baseObject, limit) {
super();
this.baseObject = baseObject;
this.limit = limit;
}
/**
* Update an object
* @arg {Object} obj The updated object data
* @arg {String} obj.id The ID of the object
* @arg {Class} [extra] An extra parameter the constructor may need
* @arg {Boolean} [replace] Whether to replace an existing object with the same ID
* @returns {Class} The updated object
*/
update(obj, extra, replace) {
if(!obj.id && obj.id !== 0) {
throw new Error("Missing object id");
}
const item = this.get(obj.id);
if(!item) {
return this.add(obj, extra, replace);
}
item.update(obj, extra);
return item;
}
/**
* Add an object
* @arg {Object} obj The object data
* @arg {String} obj.id The ID of the object
* @arg {Class} [extra] An extra parameter the constructor may need
* @arg {Boolean} [replace] Whether to replace an existing object with the same ID
* @returns {Class} The existing or newly created object
*/
add(obj, extra, replace) {
if(this.limit === 0) {
return (obj instanceof this.baseObject || obj.constructor.name === this.baseObject.name) ? obj : new this.baseObject(obj, extra);
}
if(obj.id == null) {
throw new Error("Missing object id");
}
const existing = this.get(obj.id);
if(existing && !replace) {
return existing;
}
if(!(obj instanceof this.baseObject || obj.constructor.name === this.baseObject.name)) {
obj = new this.baseObject(obj, extra);
}
this.set(obj.id, obj);
if(this.limit && this.size > this.limit) {
const iter = this.keys();
while(this.size > this.limit) {
this.delete(iter.next().value);
}
}
return obj;
}
/**
* Returns true if all elements satisfy the condition
* @arg {Function} func A function that takes an object and returns true or false
* @returns {Boolean} Whether or not all elements satisfied the condition
*/
every(func) {
for(const item of this.values()) {
if(!func(item)) {
return false;
}
}
return true;
}
/**
* Return all the objects that make the function evaluate true
* @arg {Function} func A function that takes an object and returns true if it matches
* @returns {Array<Class>} An array containing all the objects that matched
*/
filter(func) {
const arr = [];
for(const item of this.values()) {
if(func(item)) {
arr.push(item);
}
}
return arr;
}
/**
* Return the first object to make the function evaluate true
* @arg {Function} func A function that takes an object and returns true if it matches
* @returns {Class?} The first matching object, or undefined if no match
*/
find(func) {
for(const item of this.values()) {
if(func(item)) {
return item;
}
}
return undefined;
}
/**
* Return an array with the results of applying the given function to each element
* @arg {Function} func A function that takes an object and returns something
* @returns {Array} An array containing the results
*/
map(func) {
const arr = [];
for(const item of this.values()) {
arr.push(func(item));
}
return arr;
}
/**
* Get a random object from the Collection
* @returns {Class?} The random object, or undefined if there is no match
*/
random() {
const index = Math.floor(Math.random() * this.size);
const iter = this.values();
for(let i = 0; i < index; ++i) {
iter.next();
}
return iter.next().value;
}
/**
* Returns a value resulting from applying a function to every element of the collection
* @arg {Function} func A function that takes the previous value and the next item and returns a new value
* @arg {any} [initialValue] The initial value passed to the function
* @returns {any} The final result
*/
reduce(func, initialValue) {
const iter = this.values();
let val;
let result = initialValue === undefined ? iter.next().value : initialValue;
while((val = iter.next().value) !== undefined) {
result = func(result, val);
}
return result;
}
/**
* Remove an object
* @arg {Object} obj The object
* @arg {String} obj.id The ID of the object
* @returns {Class?} The removed object, or null if nothing was removed
*/
remove(obj) {
const item = this.get(obj.id);
if(!item) {
return null;
}
this.delete(obj.id);
return item;
}
/**
* Returns true if at least one element satisfies the condition
* @arg {Function} func A function that takes an object and returns true or false
* @returns {Boolean} Whether or not at least one element satisfied the condition
*/
some(func) {
for(const item of this.values()) {
if(func(item)) {
return true;
}
}
return false;
}
toString() {
return `[Collection<${this.baseObject.name}>]`;
}
toJSON() {
const json = {};
for(const item of this.values()) {
json[item.id] = item;
}
return json;
}
}
module.exports = Collection;

66
node_modules/eris/lib/util/MultipartData.js generated vendored Normal file
View File

@ -0,0 +1,66 @@
"use strict";
class MultipartData {
constructor() {
this.boundary = "----------------Eris";
this.bufs = [];
}
attach(fieldName, data, filename) {
if(data === undefined) {
return;
}
let str = "\r\n--" + this.boundary + "\r\nContent-Disposition: form-data; name=\"" + fieldName + "\"";
let contentType;
if(filename) {
str += "; filename=\"" + filename + "\"";
const extension = filename.match(/\.(png|apng|gif|jpg|jpeg|webp|svg|json)$/i);
if(extension) {
let ext = extension[1].toLowerCase();
switch(ext) {
case "png":
case "apng":
case "gif":
case "jpg":
case "jpeg":
case "webp":
case "svg": {
if(ext === "svg") {
ext = "svg+xml";
}
contentType = "image/";
break;
}
case "json": {
contentType = "application/";
break;
}
}
contentType += ext;
}
}
if(contentType) {
str += `\r\nContent-Type: ${contentType}`;
} else if(ArrayBuffer.isView(data)) {
str +="\r\nContent-Type: application/octet-stream";
if(!(data instanceof Uint8Array)) {
data = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
}
} else if(typeof data === "object") {
str +="\r\nContent-Type: application/json";
data = Buffer.from(JSON.stringify(data));
} else {
data = Buffer.from("" + data);
}
this.bufs.push(Buffer.from(str + "\r\n\r\n"));
this.bufs.push(data);
}
finish() {
this.bufs.push(Buffer.from("\r\n--" + this.boundary + "--"));
return this.bufs;
}
}
module.exports = MultipartData;

34
node_modules/eris/lib/util/Opus.js generated vendored Normal file
View File

@ -0,0 +1,34 @@
"use strict";
let NativeOpus;
let OpusScript;
module.exports.createOpus = function createOpus(samplingRate, channels, bitrate) {
if(!NativeOpus && !OpusScript) {
try {
NativeOpus = require("@discordjs/opus");
} catch(err) {
try {
OpusScript = require("opusscript");
} catch(err) { // eslint-disable no-empty
}
}
}
let opus;
if(NativeOpus) {
opus = new NativeOpus.OpusEncoder(samplingRate, channels);
} else if(OpusScript) {
opus = new OpusScript(samplingRate, channels, OpusScript.Application.AUDIO);
} else {
throw new Error("No opus encoder found, playing non-opus audio will not work.");
}
if(opus.setBitrate) {
opus.setBitrate(bitrate);
} else if(opus.encoderCTL) {
opus.encoderCTL(4002, bitrate);
}
return opus;
};

83
node_modules/eris/lib/util/SequentialBucket.js generated vendored Normal file
View File

@ -0,0 +1,83 @@
"use strict";
const util = require("util");
const Base = require("../structures/Base");
/**
* Ratelimit requests and release in sequence
* TODO: add latencyref
* @prop {Number} limit How many tokens the bucket can consume in the current interval
* @prop {Boolean} processing Whether the queue is being processed
* @prop {Number} remaining How many tokens the bucket has left in the current interval
* @prop {Number} reset Timestamp of next reset
*/
class SequentialBucket {
/**
* Construct a SequentialBucket
* @arg {Number} limit The max number of tokens the bucket can consume per interval
* @arg {Object} [latencyRef] An object
* @arg {Number} latencyRef.latency Interval between consuming tokens
*/
constructor(limit, latencyRef = {latency: 0}) {
this.limit = this.remaining = limit;
this.reset = 0;
this.processing = false;
this.latencyRef = latencyRef;
this._queue = [];
}
check(override) {
if(this._queue.length === 0) {
if(this.processing) {
clearTimeout(this.processing);
this.processing = false;
}
return;
}
if(this.processing && !override) {
return;
}
const now = Date.now();
const offset = this.latencyRef.latency;
if(!this.reset || this.reset < now - offset) {
this.reset = now - offset;
this.remaining = this.limit;
}
this.last = now;
if(this.remaining <= 0) {
this.processing = setTimeout(() => {
this.processing = false;
this.check(true);
}, Math.max(0, (this.reset || 0) - now + offset) + 1);
return;
}
--this.remaining;
this.processing = true;
this._queue.shift()(() => {
if(this._queue.length > 0) {
this.check(true);
} else {
this.processing = false;
}
});
}
/**
* Queue something in the SequentialBucket
* @arg {Function} func A function to call when a token can be consumed. The function will be passed a callback argument, which must be called to allow the bucket to continue to work
*/
queue(func, short) {
if(short) {
this._queue.unshift(func);
} else {
this._queue.push(func);
}
this.check();
}
[util.inspect.custom]() {
return Base.prototype[util.inspect.custom].call(this);
}
}
module.exports = SequentialBucket;

297
node_modules/eris/lib/voice/Piper.js generated vendored Normal file
View File

@ -0,0 +1,297 @@
"use strict";
const util = require("util");
const Base = require("../structures/Base");
const DCAOpusTransformer = require("./streams/DCAOpusTransformer");
const FFmpegOggTransformer = require("./streams/FFmpegOggTransformer");
const FFmpegPCMTransformer = require("./streams/FFmpegPCMTransformer");
const FS = require("fs");
const HTTP = require("http");
const HTTPS = require("https");
const OggOpusTransformer = require("./streams/OggOpusTransformer");
const PassThroughStream = require("stream").PassThrough;
const PCMOpusTransformer = require("./streams/PCMOpusTransformer");
const Stream = require("stream").Stream;
const VolumeTransformer = require("./streams/VolumeTransformer");
const WebmOpusTransformer = require("./streams/WebmOpusTransformer");
let EventEmitter;
try {
EventEmitter = require("eventemitter3");
} catch(err) {
EventEmitter = require("events").EventEmitter;
}
class Piper extends EventEmitter {
constructor(converterCommand, opusFactory) {
super();
this.reset();
this.converterCommand = converterCommand;
this._dataPackets = [];
this._dataPacketMax = 30;
this._dataPacketMin = 15;
this.encoding = false;
this.libopus = true;
this.opusFactory = opusFactory;
this.opus = null;
this.volumeLevel = 1;
this._retransformer = [];
this.addDataPacket = this.addDataPacket.bind(this);
}
get dataPacketCount() {
return this._dataPackets.length;
}
addDataPacket(packet) {
if(!this.encoding) {
return;
}
if(this._dataPackets.push(packet) < this._dataPacketMax && this._endStream && this._endStream.manualCB) {
process.nextTick(() => {
if(this._endStream && this._endStream.manualCB) {
this._endStream.transformCB();
}
});
}
}
encode(source, options) {
if(this.encoding || this.streams.length) {
this.emit("error", new Error("Already encoding"));
return false;
}
if(typeof source === "string") {
if(options.format === "dca" || options.format === "ogg" || options.format === "webm" || options.format === "pcm") {
if(source.startsWith("http://") || source.startsWith("https://")) {
const passThrough = new PassThroughStream();
if(source.startsWith("http://")) {
HTTP.get(source, (res) => res.pipe(passThrough)).once("error", (e) => this.stop(e));
} else {
HTTPS.get(source, (res) => res.pipe(passThrough)).once("error", (e) => this.stop(e));
}
source = passThrough;
} else {
try {
FS.statSync(source);
} catch(err) {
if(err.code === "ENOENT") {
this.emit("error", new Error("That file does not exist."));
} else {
this.emit("error", new Error("An error occured trying to access that file."));
}
this.reset();
return false;
}
source = FS.createReadStream(source);
}
}
} else if(!(source instanceof Stream) || !source.pipe) {
this.emit("error", new Error("Invalid source type"));
return false;
}
this._dataPacketMax = 30;
this._dataPacketMin = 15;
if(typeof source !== "string") {
this.streams.push(source.once("error", (e) => this.stop(e)));
}
if(options.format === "opusPackets") { // eslint-disable no-empty
} else if(options.format === "dca") {
this.streams.push(source.pipe(new DCAOpusTransformer()).once("error", (e) => this.stop(e)));
} else if(options.format === "ogg") {
this.streams.push(source.pipe(new OggOpusTransformer()).once("error", (e) => this.stop(e)));
} else if(options.format === "webm") {
this.streams.push(source.pipe(new WebmOpusTransformer()).once("error", (e) => this.stop(e)));
} else if(!options.format || options.format === "pcm") {
if(options.inlineVolume) {
if(!options.format) {
if(!this.converterCommand) {
this.emit("error", new Error("FFmpeg/avconv was not found on this system. Playback of this audio format is impossible"));
this.reset();
return false;
}
if(typeof source === "string") {
this.streams.push(source = new FFmpegPCMTransformer({
command: this.converterCommand,
input: source,
encoderArgs: options.encoderArgs,
inputArgs: options.inputArgs
}).once("error", (e) => this.stop(e)));
} else {
this.streams.push(source = source.pipe(new FFmpegPCMTransformer({
command: this.converterCommand,
encoderArgs: options.encoderArgs,
inputArgs: options.inputArgs
})).once("error", (e) => this.stop(e)));
}
}
this.streams.push(this.volume = source = source.pipe(new VolumeTransformer()).once("error", (e) => this.stop(e)));
this.volume.setVolume(this.volumeLevel);
this.streams.push(this.volume.pipe(new PCMOpusTransformer({
opusFactory: this.opusFactory,
frameSize: options.frameSize,
pcmSize: options.pcmSize
})).once("error", (e) => this.stop(e)));
this._dataPacketMax = 1; // Live volume updating
this._dataPacketMin = 4;
} else {
if(this.libopus) {
if(typeof source === "string") {
this.streams.push(source = new FFmpegOggTransformer({
command: this.converterCommand,
input: source,
encoderArgs: options.encoderArgs,
inputArgs: options.inputArgs,
format: options.format,
frameDuration: options.frameDuration
}).once("error", (e) => this.stop(e)));
} else {
this.streams.push(source = source.pipe(new FFmpegOggTransformer({
command: this.converterCommand,
encoderArgs: options.encoderArgs,
inputArgs: options.inputArgs,
format: options.format,
frameDuration: options.frameDuration
})).once("error", (e) => this.stop(e)));
}
this.streams.push(source.pipe(new OggOpusTransformer()).once("error", (e) => this.stop(e)));
} else {
if(typeof source === "string") {
this.streams.push(source = new FFmpegPCMTransformer({
command: this.converterCommand,
input: source,
encoderArgs: options.encoderArgs,
inputArgs: options.inputArgs
}).once("error", (e) => this.stop(e)));
} else {
this.streams.push(source = source.pipe(new FFmpegPCMTransformer({
command: this.converterCommand,
encoderArgs: options.encoderArgs,
inputArgs: options.inputArgs
})).once("error", (e) => this.stop(e)));
}
this.streams.push(source.pipe(new PCMOpusTransformer({
opusFactory: this.opusFactory,
frameSize: options.frameSize,
pcmSize: options.pcmSize
})).once("error", (e) => this.stop(e)));
}
}
} else {
this.emit("error", new Error("Unrecognized format"));
this.reset();
return false;
}
this._endStream = this.streams[this.streams.length - 1];
if(this._endStream.hasOwnProperty("manualCB")) {
this._endStream.manualCB = true;
}
this._endStream.on("data", this.addDataPacket);
this._endStream.once("end", () => this.stop(null, source));
this.emit("start");
return (this.encoding = true);
}
getDataPacket() {
if(this._dataPackets.length < this._dataPacketMin && this._endStream && this._endStream.manualCB) {
this._endStream.transformCB();
}
if(this._retransformer.length === 0) {
return this._dataPackets.shift();
} else {
// If we don't have an opus instance yet, create one.
if(!this.opus) {
this.opus = this.opusFactory();
}
const packet = this.opus.decode(this._dataPackets.shift());
for(let i = 0, num; i < packet.length - 1; i += 2) {
num = ~~(this._retransformer.shift() * packet.readInt16LE(i));
packet.writeInt16LE(num >= 32767 ? 32767 : num <= -32767 ? -32767 : num, i);
}
return this.opus.encode(packet, 3840 / 2 / 2);
}
}
reset() {
if(this.streams) {
for(const stream of this.streams) {
if(typeof stream.destroy === "function") {
stream.destroy();
} else {
stream.unpipe();
}
}
}
this.streams = [];
this._endStream = null;
this.volume = null;
}
resetPackets() {
// We no longer need this to convert inline volume, so... let it go.
if(this.opus) {
this.opus.delete && this.opus.delete();
this.opus = null;
}
this._dataPackets = [];
}
setVolume(volume) {
this.volumeLevel = volume;
if(!this.volume) {
return;
}
this.volume.setVolume(volume);
}
stop(e, source) {
if(source && !this.streams.includes(source)) {
return;
}
if(e) {
this.emit("error", e);
}
if(this.throttleTimeout) {
clearTimeout(this.throttleTimeout);
this.throttleTimeout = null;
}
if(this.streams.length === 0) {
return;
}
if(this._endStream) {
this._endStream.removeAllListeners("data");
}
this.reset();
if(this.encoding) {
this.encoding = false;
this.emit("stop");
}
}
[util.inspect.custom]() {
return Base.prototype[util.inspect.custom].call(this);
}
}
module.exports = Piper;

238
node_modules/eris/lib/voice/SharedStream.js generated vendored Normal file
View File

@ -0,0 +1,238 @@
"use strict";
const util = require("util");
const Base = require("../structures/Base");
const Piper = require("./Piper");
const VoiceConnection = require("./VoiceConnection");
const Collection = require("../util/Collection");
const {createOpus} = require("../util/Opus");
let EventEmitter;
try {
EventEmitter = require("eventemitter3");
} catch(err) {
EventEmitter = require("events").EventEmitter;
}
/**
* Represents a collection of VoiceConnections sharing an input stream
* @extends EventEmitter
* @prop {Object?} current The current stream
* @prop {Boolean} ended Whether the stream ended
* @prop {Boolean} playing Whether the voice connection is playing something
* @prop {Boolean} speaking Whether someone is speaking
* @prop {Number} volume The current volume level of the connection
*/
class SharedStream extends EventEmitter {
constructor() {
super();
this.samplingRate = 48000;
this.frameDuration = 20;
this.channels = 2;
this.bitrate = 64000;
this.voiceConnections = new Collection(VoiceConnection);
if(!VoiceConnection._converterCommand.cmd) {
VoiceConnection._converterCommand.pickCommand();
}
this.piper = new Piper(VoiceConnection._converterCommand.cmd, () => createOpus(this.samplingRate, this.channels, this.bitrate));
/**
* Fired when the shared stream encounters an error
* @event SharedStream#error
* @prop {Error} e The error
*/
this.piper.on("error", (e) => this.emit("error", e));
if(!VoiceConnection._converterCommand.libopus) {
this.piper.libopus = false;
}
this.ended = true;
this.playing = false;
this.speaking = false;
this._send = this._send.bind(this);
}
get volume() {
return this.piper.volumeLevel;
}
/**
* Add a voice connection to the shared stream
* @arg {VoiceConnection} connection The voice connection to add
*/
add(connection) {
const _connection = this.voiceConnections.add(connection);
if(_connection.ready) {
_connection.setSpeaking(this.speaking);
} else {
_connection.once("ready", () => {
_connection.setSpeaking(this.speaking);
});
}
return _connection;
}
/**
* Play an audio or video resource. If playing from a non-opus resource, FFMPEG should be compiled with --enable-libopus for best performance. If playing from HTTPS, FFMPEG must be compiled with --enable-openssl
* @arg {ReadableStream | String} resource The audio or video resource, either a ReadableStream, URL, or file path
* @arg {Object} [options] Music options
* @arg {Array<String>} [options.encoderArgs] Additional encoder parameters to pass to ffmpeg/avconv (after -i)
* @arg {String} [options.format] The format of the resource. If null, FFmpeg will attempt to guess and play the format. Available options: "dca", "ogg", "webm", "pcm", null
* @arg {Number} [options.frameDuration=60] The resource opus frame duration (required for DCA/Ogg)
* @arg {Number} [options.frameSize=2880] The resource opus frame size
* @arg {Boolean} [options.inlineVolume=false] Whether to enable on-the-fly volume changing. Note that enabling this leads to increased CPU usage
* @arg {Array<String>} [options.inputArgs] Additional input parameters to pass to ffmpeg/avconv (before -i)
* @arg {Number} [options.sampleRate=48000] The resource audio sampling rate
* @arg {Number} [options.voiceDataTimeout=2000] Timeout when waiting for voice data (-1 for no timeout)
*/
play(source, options = {}) {
options.format = options.format || null;
options.voiceDataTimeout = !isNaN(options.voiceDataTimeout) ? options.voiceDataTimeout : 2000;
options.inlineVolume = !!options.inlineVolume;
options.inputArgs = options.inputArgs || [];
options.encoderArgs = options.encoderArgs || [];
options.samplingRate = options.samplingRate || this.samplingRate;
options.frameDuration = options.frameDuration || this.frameDuration;
options.frameSize = options.frameSize || options.samplingRate * options.frameDuration / 1000;
options.pcmSize = options.pcmSize || options.frameSize * 2 * this.channels;
if(!this.piper.encode(source, options)) {
this.emit("error", new Error("Unable to encode source"));
return;
}
this.ended = false;
this.current = {
startTime: 0, // later
playTime: 0,
pausedTimestamp: 0,
pausedTime: 0,
bufferingTicks: 0,
options: options,
timeout: null,
buffer: null
};
this.playing = true;
/**
* Fired when the shared stream starts playing a stream
* @event SharedStream#start
*/
this.emit("start");
this._send();
}
/**
* Remove a voice connection from the shared stream
* @arg {VoiceConnection} connection The voice connection to remove
*/
remove(connection) {
return this.voiceConnections.remove(connection);
}
setSpeaking(value) {
if((value = !!value) != this.speaking) {
this.speaking = value;
for(const vc of this.voiceConnections.values()) {
vc.setSpeaking(value);
}
}
}
/**
* Sets the volume of this shared stream if inline volume is enabled
* @arg {Number} volume The volume as a value from 0 (min) to 1 (max)
*/
setVolume(volume) {
this.piper.setVolume(volume);
}
/**
* Stop the bot from sending audio
*/
stopPlaying() {
if(this.ended) {
return;
}
this.ended = true;
if(this.current && this.current.timeout) {
clearTimeout(this.current.timeout);
this.current.timeout = null;
}
this.current = null;
this.piper.stop();
this.piper.resetPackets();
this.setSpeaking(this.playing = false);
/**
* Fired when the shared stream finishes playing a stream
* @event SharedStream#end
*/
this.emit("end");
}
_incrementSequences() {
for(const vc of this.voiceConnections.values()) {
vc.sequence = (vc.sequence + 1) & 0xFFFF;
}
}
_incrementTimestamps(val) {
for(const vc of this.voiceConnections.values()) {
vc.timestamp = (vc.timestamp + val) >>> 0;
}
}
_send() {
if(!this.piper.encoding && this.piper.dataPacketCount === 0) {
return this.stopPlaying();
}
this._incrementTimestamps(this.current.options.frameSize);
this._incrementSequences();
if((this.current.buffer = this.piper.getDataPacket())) {
if(this.current.startTime === 0) {
this.current.startTime = Date.now();
}
if(this.current.bufferingTicks > 0) {
this.current.bufferingTicks = 0;
this.setSpeaking(true);
}
} else if(this.current.options.voiceDataTimeout === -1 || this.current.bufferingTicks < this.current.options.voiceDataTimeout / (4 * this.current.options.frameDuration)) { // wait for data
if(++this.current.bufferingTicks === 1) {
this.setSpeaking(false);
} else {
this.current.pausedTime += 4 * this.current.options.frameDuration;
this._incrementTimestamps(3 * this.current.options.frameSize);
this.current.timeout = setTimeout(this._send, 4 * this.current.options.frameDuration);
return;
}
} else {
return this.stopPlaying();
}
this.voiceConnections.forEach((connection) => {
if(connection.ready && this.current.buffer) {
connection._sendAudioFrame(this.current.buffer);
}
});
this.current.playTime += this.current.options.frameDuration;
this.current.timeout = setTimeout(this._send, this.current.startTime + this.current.pausedTime + this.current.playTime - Date.now());
}
[util.inspect.custom]() {
return Base.prototype[util.inspect.custom].call(this);
}
}
module.exports = SharedStream;

862
node_modules/eris/lib/voice/VoiceConnection.js generated vendored Normal file
View File

@ -0,0 +1,862 @@
"use strict";
const util = require("util");
const Base = require("../structures/Base");
const ChildProcess = require("child_process");
const {VoiceOPCodes, GatewayOPCodes} = require("../Constants");
const Dgram = require("dgram");
const Net = require("net");
const Piper = require("./Piper");
const VoiceDataStream = require("./VoiceDataStream");
const {createOpus} = require("../util/Opus");
const WebSocket = typeof window !== "undefined" ? require("../util/BrowserWebSocket") : require("ws");
let EventEmitter;
try {
EventEmitter = require("eventemitter3");
} catch(err) {
EventEmitter = require("events").EventEmitter;
}
let Sodium = null;
let NaCl = null;
const ENCRYPTION_MODE = "xsalsa20_poly1305";
const MAX_FRAME_SIZE = 1276 * 3;
const SILENCE_FRAME = Buffer.from([0xF8, 0xFF, 0xFE]);
const converterCommand = {
cmd: null,
libopus: false
};
converterCommand.pickCommand = function pickCommand() {
let tenative;
for(const command of ["./ffmpeg", "./avconv", "ffmpeg", "avconv"]) {
const res = ChildProcess.spawnSync(command, ["-encoders"]);
if(!res.error) {
if(!res.stdout.toString().includes("libopus")) {
tenative = command;
continue;
}
converterCommand.cmd = command;
converterCommand.libopus = true;
return;
}
}
if(tenative) {
converterCommand.cmd = tenative;
return;
}
};
/**
* Represents a voice connection
* @extends EventEmitter
* @prop {String} channelID The ID of the voice connection's current channel
* @prop {Boolean} connecting Whether the voice connection is connecting
* @prop {Object?} current The state of the currently playing stream
* @prop {Object} current.options The custom options for the current stream
* @prop {Array<String>?} current.options.encoderArgs Additional encoder parameters to pass to ffmpeg/avconv (after -i)
* @prop {String?} current.options.format The format of the resource. If null, FFmpeg will attempt to guess and play the format. Available options: "dca", "ogg", "webm", "pcm", null
* @prop {Number?} current.options.frameDuration The resource opus frame duration (required for DCA/Ogg)
* @prop {Number?} current.options.frameSize The resource opus frame size
* @prop {Boolean?} current.options.inlineVolume Whether to enable on-the-fly volume changing. Note that enabling this leads to increased CPU usage
* @prop {Array<String>?} current.options.inputArgs Additional input parameters to pass to ffmpeg/avconv (before -i)
* @prop {Number?} current.options.sampleRate The resource audio sampling rate
* @prop {Number?} current.options.voiceDataTimeout Timeout when waiting for voice data (-1 for no timeout)
* @prop {Number} current.pausedTime How long the current stream has been paused for, in milliseconds
* @prop {Number} current.pausedTimestamp The timestamp of the most recent pause
* @prop {Number} current.playTime How long the current stream has been playing for, in milliseconds
* @prop {Number} current.startTime The timestamp of the start of the current stream
* @prop {String} id The ID of the voice connection (guild ID)
* @prop {Boolean} paused Whether the voice connection is paused
* @prop {Boolean} playing Whether the voice connection is playing something
* @prop {Boolean} ready Whether the voice connection is ready
* @prop {Number} volume The current volume level of the connection
*/
class VoiceConnection extends EventEmitter {
constructor(id, options = {}) {
super();
if(typeof window !== "undefined") {
throw new Error("Voice is not supported in browsers at this time");
}
if(!Sodium && !NaCl) {
try {
Sodium = require("sodium-native");
} catch(err) {
try {
NaCl = require("tweetnacl");
} catch(err) { // eslint-disable no-empty
throw new Error("Error loading tweetnacl/libsodium, voice not available");
}
}
}
this.id = id;
this.samplingRate = 48000;
this.channels = 2;
this.frameDuration = 20;
this.frameSize = this.samplingRate * this.frameDuration / 1000;
this.pcmSize = this.frameSize * this.channels * 2;
this.bitrate = 64000;
this.shared = !!options.shared;
this.shard = options.shard || {};
this.opusOnly = !!options.opusOnly;
if(!this.opusOnly && !this.shared) {
this.opus = {};
}
this.channelID = null;
this.paused = true;
this.speaking = false;
this.sequence = 0;
this.timestamp = 0;
this.ssrcUserMap = {};
this.connectionTimeout = null;
this.connecting = false;
this.reconnecting = false;
this.ready = false;
this.sendBuffer = Buffer.allocUnsafe(16 + 32 + MAX_FRAME_SIZE);
this.sendNonce = Buffer.alloc(24);
this.sendNonce[0] = 0x80;
this.sendNonce[1] = 0x78;
if(!options.shared) {
if(!converterCommand.cmd) {
converterCommand.pickCommand();
}
this.piper = new Piper(converterCommand.cmd, () => createOpus(this.samplingRate, this.channels, this.bitrate));
/**
* Fired when the voice connection encounters an error. This event should be handled by users
* @event VoiceConnection#error
* @prop {Error} err The error object
*/
this.piper.on("error", (e) => this.emit("error", e));
if(!converterCommand.libopus) {
this.piper.libopus = false;
}
}
this._send = this._send.bind(this);
}
get volume() {
return this.piper.volumeLevel;
}
connect(data) {
this.connecting = true;
if(this.ws && this.ws.readyState !== WebSocket.CLOSED) {
this.disconnect(undefined, true);
setTimeout(() => {
if(!this.connecting && !this.ready) {
this.connect(data);
}
}, 500).unref();
return;
}
clearTimeout(this.connectionTimeout);
this.connectionTimeout = setTimeout(() => {
if(this.connecting) {
this.disconnect(new Error("Voice connection timeout"));
}
this.connectionTimeout = null;
}, this.shard.client ? this.shard.client.options.connectionTimeout : 30000).unref();
if(!data.endpoint) {
return; // Endpoint null, wait next update.
}
if(!data.token || !data.session_id || !data.user_id) {
this.disconnect(new Error("Malformed voice server update: " + JSON.stringify(data)));
return;
}
this.channelID = data.channel_id;
this.endpoint = new URL(`wss://${data.endpoint}`);
if(this.endpoint.port === "80") {
this.endpoint.port = "";
}
this.endpoint.searchParams.set("v", 4);
this.ws = new WebSocket(this.endpoint.href);
/**
* Fired when stuff happens and gives more info
* @event VoiceConnection#debug
* @prop {String} message The debug message
*/
this.emit("debug", "Connection: " + JSON.stringify(data));
this.ws.on("open", () => {
/**
* Fired when the voice connection connects
* @event VoiceConnection#connect
*/
this.emit("connect");
if(this.connectionTimeout) {
clearTimeout(this.connectionTimeout);
this.connectionTimeout = null;
}
this.sendWS(VoiceOPCodes.IDENTIFY, {
server_id: this.id === "call" ? data.channel_id : this.id,
user_id: data.user_id,
session_id: data.session_id,
token: data.token
});
});
this.ws.on("message", (m) => {
const packet = JSON.parse(m);
if(this.listeners("debug").length > 0) {
this.emit("debug", "Rec: " + JSON.stringify(packet));
}
switch(packet.op) {
case VoiceOPCodes.READY: {
this.ssrc = packet.d.ssrc;
this.sendNonce.writeUInt32BE(this.ssrc, 8);
if(!packet.d.modes.includes(ENCRYPTION_MODE)) {
throw new Error("No supported voice mode found");
}
this.modes = packet.d.modes;
this.udpIP = packet.d.ip;
this.udpPort = packet.d.port;
this.emit("debug", "Connecting to UDP: " + this.udpIP + ":" + this.udpPort);
this.udpSocket = Dgram.createSocket(Net.isIPv6(this.udpIP) ? "udp6" : "udp4");
this.udpSocket.on("error", (err, msg) => {
this.emit("error", err);
if(msg) {
this.emit("debug", "Voice UDP error: " + msg);
}
if(this.ready || this.connecting) {
this.disconnect(err);
}
});
this.udpSocket.once("message", (packet) => {
let i = 8;
while(packet[i] !== 0) {
i++;
}
const localIP = packet.toString("ascii", 8, i);
const localPort = packet.readUInt16BE(packet.length - 2);
this.emit("debug", `Discovered IP: ${localIP}:${localPort} (${packet.toString("hex")})`);
this.sendWS(VoiceOPCodes.SELECT_PROTOCOL, {
protocol: "udp",
data: {
address: localIP,
port: localPort,
mode: ENCRYPTION_MODE
}
});
});
this.udpSocket.on("close", (err) => {
if(err) {
this.emit("warn", "Voice UDP close: " + err);
}
if(this.ready || this.connecting) {
this.disconnect(err);
}
});
const udpMessage = Buffer.allocUnsafe(74);
udpMessage.writeUInt16BE(0x1, 0);
udpMessage.writeUInt16BE(70, 2);
udpMessage.writeUInt32BE(this.ssrc, 4);
this.sendUDPPacket(udpMessage);
break;
}
case VoiceOPCodes.SESSION_DESCRIPTION: {
this.mode = packet.d.mode;
this.secret = Buffer.from(packet.d.secret_key);
this.connecting = false;
this.reconnecting = false;
this.ready = true;
// Send audio to properly establish the socket (e.g. for voice receive)
this.sendAudioFrame(SILENCE_FRAME, this.frameSize);
/**
* Fired when the voice connection turns ready
* @event VoiceConnection#ready
*/
this.emit("ready");
this.resume();
if(this.receiveStreamOpus || this.receiveStreamPCM) {
this.registerReceiveEventHandler();
}
break;
}
case VoiceOPCodes.HEARTBEAT_ACK: {
/**
* Fired when the voice connection receives a pong
* @event VoiceConnection#pong
* @prop {Number} latency The current latency in milliseconds
*/
this.emit("pong", Date.now() - packet.d);
break;
}
case VoiceOPCodes.SPEAKING: {
this.ssrcUserMap[packet.d.ssrc] = packet.d.user_id;
/**
* Fired when a user begins speaking
* @event VoiceConnection#speakingStart
* @prop {String} userID The ID of the user that began speaking
*/
/**
* Fired when a user stops speaking
* @event VoiceConnection#speakingStop
* @prop {String} userID The ID of the user that stopped speaking
*/
this.emit(packet.d.speaking ? "speakingStart" : "speakingStop", packet.d.user_id);
break;
}
case VoiceOPCodes.HELLO: {
if(this.heartbeatInterval) {
clearInterval(this.heartbeatInterval);
}
this.heartbeatInterval = setInterval(() => {
this.heartbeat();
}, packet.d.heartbeat_interval);
this.heartbeat();
break;
}
case VoiceOPCodes.CLIENT_DISCONNECT: {
if(this.opus) {
// opusscript requires manual cleanup
if(this.opus[packet.d.user_id] && this.opus[packet.d.user_id].delete) {
this.opus[packet.d.user_id].delete();
}
delete this.opus[packet.d.user_id];
}
/**
* Fired when a user disconnects from the voice server
* @event VoiceConnection#userDisconnect
* @prop {String} userID The ID of the user that disconnected
*/
this.emit("userDisconnect", packet.d.user_id);
break;
}
default: {
this.emit("unknown", packet);
break;
}
}
});
this.ws.on("error", (err) => {
this.emit("error", err);
});
this.ws.on("close", (code, reason) => {
let err = !code || code === 1000 ? null : new Error(code + ": " + reason);
this.emit("warn", `Voice WS close ${code}: ${reason}`);
if(this.connecting || this.ready) {
let reconnecting = true;
if(code === 4006) {
reconnecting = false;
} else if(code === 4014) {
if(this.channelID) {
data.endpoint = null;
reconnecting = true;
err = null;
} else {
reconnecting = false;
}
} else if(code === 1000) {
reconnecting = false;
}
this.disconnect(err, reconnecting);
if(reconnecting) {
setTimeout(() => {
if(!this.connecting && !this.ready) {
this.connect(data);
}
}, 500).unref();
}
}
});
}
disconnect(error, reconnecting) {
this.connecting = false;
this.reconnecting = reconnecting;
this.ready = false;
this.speaking = false;
this.timestamp = 0;
this.sequence = 0;
if(this.connectionTimeout) {
clearTimeout(this.connectionTimeout);
this.connectionTimeout = null;
}
try {
if(reconnecting) {
this.pause();
} else {
this.stopPlaying();
}
} catch(err) {
this.emit("error", err);
}
if(this.heartbeatInterval) {
clearInterval(this.heartbeatInterval);
this.heartbeatInterval = null;
}
if(this.udpSocket) {
try {
this.udpSocket.close();
} catch(err) {
if(err.message !== "Not running") {
this.emit("error", err);
}
}
this.udpSocket = null;
}
if(this.ws) {
try {
if(reconnecting) {
if(this.ws.readyState === WebSocket.OPEN) {
this.ws.close(4901, "Eris: reconnect");
} else {
this.emit("debug", `Terminating websocket (state: ${this.ws.readyState})`);
this.ws.terminate();
}
} else {
this.ws.close(1000, "Eris: normal");
}
} catch(err) {
this.emit("error", err);
}
this.ws = null;
}
if(reconnecting) {
if(error) {
this.emit("error", error);
}
} else {
this.channelID = null;
this.updateVoiceState();
/**
* Fired when the voice connection disconnects
* @event VoiceConnection#disconnect
* @prop {Error?} error The error, if any
*/
this.emit("disconnect", error);
}
}
heartbeat() {
this.sendWS(VoiceOPCodes.HEARTBEAT, Date.now());
if(this.udpSocket) {
// NAT/connection table keep-alive
const udpMessage = Buffer.from([0x80, 0xC8, 0x0, 0x0]);
this.sendUDPPacket(udpMessage);
}
}
/**
* Pause sending audio (if playing)
*/
pause() {
this.paused = true;
this.setSpeaking(0);
if(this.current) {
if(!this.current.pausedTimestamp) {
this.current.pausedTimestamp = Date.now();
}
if(this.current.timeout) {
clearTimeout(this.current.timeout);
this.current.timeout = null;
}
}
}
/**
* Play an audio or video resource. If playing from a non-opus resource, FFMPEG should be compiled with --enable-libopus for best performance. If playing from HTTPS, FFMPEG must be compiled with --enable-openssl
* @arg {ReadableStream | String} resource The audio or video resource, either a ReadableStream, URL, or file path
* @arg {Object} [options] Music options
* @arg {Array<String>} [options.encoderArgs] Additional encoder parameters to pass to ffmpeg/avconv (after -i)
* @arg {String} [options.format] The format of the resource. If null, FFmpeg will attempt to guess and play the format. Available options: "dca", "ogg", "webm", "pcm", null
* @arg {Number} [options.frameDuration=20] The resource opus frame duration (required for DCA/Ogg)
* @arg {Number} [options.frameSize=2880] The resource opus frame size
* @arg {Boolean} [options.inlineVolume=false] Whether to enable on-the-fly volume changing. Note that enabling this leads to increased CPU usage
* @arg {Array<String>} [options.inputArgs] Additional input parameters to pass to ffmpeg/avconv (before -i)
* @arg {Number} [options.pcmSize=options.frameSize*2*this.channels] The PCM size if the "pcm" format is used
* @arg {Number} [options.samplingRate=48000] The resource audio sampling rate
* @arg {Number} [options.voiceDataTimeout=2000] Timeout when waiting for voice data (-1 for no timeout)
*/
play(source, options = {}) {
if(this.shared) {
throw new Error("Cannot play stream on shared voice connection");
}
if(!this.ready) {
throw new Error("Not ready yet");
}
options.format = options.format || null;
options.voiceDataTimeout = !isNaN(options.voiceDataTimeout) ? options.voiceDataTimeout : 2000;
options.inlineVolume = !!options.inlineVolume;
options.inputArgs = options.inputArgs || [];
options.encoderArgs = options.encoderArgs || [];
options.samplingRate = options.samplingRate || this.samplingRate;
options.frameDuration = options.frameDuration || this.frameDuration;
options.frameSize = options.frameSize || options.samplingRate * options.frameDuration / 1000;
options.pcmSize = options.pcmSize || options.frameSize * 2 * this.channels;
if(!this.piper.encode(source, options)) {
this.emit("error", new Error("Unable to encode source"));
return;
}
this.ended = false;
this.current = {
startTime: 0, // later
playTime: 0,
pausedTimestamp: 0,
pausedTime: 0,
bufferingTicks: 0,
options: options,
timeout: null,
buffer: null
};
this.playing = true;
/**
* Fired when the voice connection starts playing a stream
* @event VoiceConnection#start
*/
this.emit("start");
this._send();
}
/**
* Generate a receive stream for the voice connection.
* @arg {String} [type="pcm"] The desired voice data type, either "opus" or "pcm"
* @returns {VoiceDataStream}
*/
receive(type) {
if(type === "pcm") {
if(!this.receiveStreamPCM) {
this.receiveStreamPCM = new VoiceDataStream(type);
if(!this.receiveStreamOpus) {
this.registerReceiveEventHandler();
}
}
} else if(type === "opus") {
if(!this.receiveStreamOpus) {
this.receiveStreamOpus = new VoiceDataStream(type);
if(!this.receiveStreamPCM) {
this.registerReceiveEventHandler();
}
}
} else {
throw new Error(`Unsupported voice data type: ${type}`);
}
return type === "pcm" ? this.receiveStreamPCM : this.receiveStreamOpus;
}
registerReceiveEventHandler() {
this.udpSocket.on("message", (msg) => {
if(msg[1] !== 0x78) { // unknown payload type, ignore
return;
}
const nonce = Buffer.alloc(24);
msg.copy(nonce, 0, 0, 12);
let data;
if(Sodium) {
data = Buffer.alloc(msg.length - 12 - Sodium.crypto_secretbox_MACBYTES);
Sodium.crypto_secretbox_open_easy(data, msg.subarray(12), nonce, this.secret);
} else {
if(!(data = NaCl.secretbox.open(msg.subarray(12), nonce, this.secret))) {
/**
* Fired to warn of something weird but non-breaking happening
* @event VoiceConnection#warn
* @prop {String} message The warning message
*/
this.emit("warn", "Failed to decrypt received packet");
return;
}
}
const hasExtension = !!(msg[0] & 0b10000);
const cc = msg[0] & 0b1111;
if(cc > 0) {
data = data.subarray(cc * 4);
}
// Not a RFC5285 One Byte Header Extension (not negotiated)
if(hasExtension) { // RFC3550 5.3.1: RTP Header Extension
const l = data[2] << 8 | data[3];
data = data.subarray(4 + l * 4);
}
if(this.receiveStreamOpus) {
/**
* Fired when a voice data packet is received
* @event VoiceDataStream#data
* @prop {Buffer} data The voice data
* @prop {String} userID The user who sent the voice packet
* @prop {Number} timestamp The intended timestamp of the packet
* @prop {Number} sequence The intended sequence number of the packet
*/
this.receiveStreamOpus.emit("data", data, this.ssrcUserMap[nonce.readUIntBE(8, 4)], nonce.readUIntBE(4, 4), nonce.readUIntBE(2, 2));
}
if(this.receiveStreamPCM) {
const userID = this.ssrcUserMap[nonce.readUIntBE(8, 4)];
if(!this.opus[userID]) {
this.opus[userID] = createOpus(this.samplingRate, this.channels, this.bitrate);
}
data = this.opus[userID].decode(data, this.frameSize);
if(!data) {
return this.emit("warn", "Failed to decode received packet");
}
this.receiveStreamPCM.emit("data", data, userID, nonce.readUIntBE(4, 4), nonce.readUIntBE(2, 2));
}
});
}
/**
* Resume sending audio (if paused)
*/
resume() {
this.paused = false;
if(this.current) {
this.setSpeaking(1);
if(this.current.pausedTimestamp) {
this.current.pausedTime += Date.now() - this.current.pausedTimestamp;
this.current.pausedTimestamp = 0;
}
this._send();
} else {
this.setSpeaking(0);
}
}
/**
* Send a packet containing an Opus audio frame
* @arg {Buffer} frame The Opus audio frame
* @arg {Number} [frameSize] The size (in samples) of the Opus audio frame
*/
sendAudioFrame(frame, frameSize = this.frameSize) {
this.timestamp = (this.timestamp + frameSize) >>> 0;
this.sequence = (this.sequence + 1) & 0xFFFF;
return this._sendAudioFrame(frame);
}
/**
* Send a packet through the connection's UDP socket. The packet is dropped if the socket isn't established
* @arg {Buffer} packet The packet data
*/
sendUDPPacket(packet) {
if(this.udpSocket) {
try {
this.udpSocket.send(packet, 0, packet.length, this.udpPort, this.udpIP);
} catch(e) {
this.emit("error", e);
}
}
}
sendWS(op, data) {
if(this.ws && this.ws.readyState === WebSocket.OPEN) {
data = JSON.stringify({op: op, d: data});
this.ws.send(data);
this.emit("debug", data);
}
}
setSpeaking(value, delay = 0) {
this.speaking = value === true ? 1 : value === false ? 0 : value;
this.sendWS(VoiceOPCodes.SPEAKING, {
speaking: value,
delay: delay,
ssrc: this.ssrc
});
}
/**
* Modify the output volume of the current stream (if inlineVolume is enabled for the current stream)
* @arg {Number} [volume=1.0] The desired volume. 0.0 is 0%, 1.0 is 100%, 2.0 is 200%, etc. It is not recommended to go above 2.0
*/
setVolume(volume) {
this.piper.setVolume(volume);
}
/**
* Stop the bot from sending audio
*/
stopPlaying() {
if(this.ended) {
return;
}
this.ended = true;
if(this.current && this.current.timeout) {
clearTimeout(this.current.timeout);
this.current.timeout = null;
}
this.current = null;
if(this.piper) {
this.piper.stop();
this.piper.resetPackets();
}
if(this.secret) {
for(let i = 0; i < 5; i++) {
this.sendAudioFrame(SILENCE_FRAME, this.frameSize);
}
}
this.playing = false;
this.setSpeaking(0);
/**
* Fired when the voice connection finishes playing a stream
* @event VoiceConnection#end
*/
this.emit("end");
}
/**
* Switch the voice channel the bot is in. The channel to switch to must be in the same guild as the current voice channel
* @arg {String} channelID The ID of the voice channel
*/
switchChannel(channelID, reactive) {
if(this.channelID === channelID) {
return;
}
this.channelID = channelID;
if(reactive) {
if(this.reconnecting && !channelID) {
this.disconnect();
}
} else {
this.updateVoiceState();
}
}
/**
* Update the bot's voice state
* @arg {Boolean} selfMute Whether the bot muted itself or not (audio receiving is unaffected)
* @arg {Boolean} selfDeaf Whether the bot deafened itself or not (audio sending is unaffected)
*/
updateVoiceState(selfMute, selfDeaf) {
if(this.shard.sendWS) {
this.shard.sendWS(GatewayOPCodes.VOICE_STATE_UPDATE, {
guild_id: this.id === "call" ? null : this.id,
channel_id: this.channelID || null,
self_mute: !!selfMute,
self_deaf: !!selfDeaf
});
}
}
_destroy() {
if(this.opus) {
for(const key in this.opus) {
this.opus[key].delete && this.opus[key].delete();
delete this.opus[key];
}
}
delete this.piper;
if(this.receiveStreamOpus) {
this.receiveStreamOpus.removeAllListeners();
this.receiveStreamOpus = null;
}
if(this.receiveStreamPCM) {
this.receiveStreamPCM.removeAllListeners();
this.receiveStreamPCM = null;
}
}
_send() {
if(!this.piper.encoding && this.piper.dataPacketCount === 0) {
return this.stopPlaying();
}
if((this.current.buffer = this.piper.getDataPacket())) {
if(this.current.startTime === 0) {
this.current.startTime = Date.now();
}
if(this.current.bufferingTicks > 0) {
this.current.bufferingTicks = 0;
this.setSpeaking(1);
}
} else if(this.current.options.voiceDataTimeout === -1 || this.current.bufferingTicks < this.current.options.voiceDataTimeout / (4 * this.current.options.frameDuration)) { // wait for data
if(++this.current.bufferingTicks === 1) {
this.setSpeaking(0);
}
this.current.pausedTime += 4 * this.current.options.frameDuration;
this.timestamp = (this.timestamp + 3 * this.current.options.frameSize) >>> 0;
this.current.timeout = setTimeout(this._send, 4 * this.current.options.frameDuration);
return;
} else {
return this.stopPlaying();
}
this.sendAudioFrame(this.current.buffer, this.current.options.frameSize);
this.current.playTime += this.current.options.frameDuration;
this.current.timeout = setTimeout(this._send, this.current.startTime + this.current.pausedTime + this.current.playTime - Date.now());
}
_sendAudioFrame(frame) {
this.sendNonce.writeUInt16BE(this.sequence, 2);
this.sendNonce.writeUInt32BE(this.timestamp, 4);
if(Sodium) {
const MACBYTES = Sodium.crypto_secretbox_MACBYTES;
const length = frame.length + MACBYTES;
this.sendBuffer.fill(0, 12, 12 + MACBYTES);
frame.copy(this.sendBuffer, 12 + MACBYTES);
Sodium.crypto_secretbox_easy(this.sendBuffer.subarray(12, 12 + length), this.sendBuffer.subarray(12 + MACBYTES, 12 + length), this.sendNonce, this.secret);
this.sendNonce.copy(this.sendBuffer, 0, 0, 12);
return this.sendUDPPacket(this.sendBuffer.subarray(0, 12 + length));
} else {
const BOXZEROBYTES = NaCl.lowlevel.crypto_secretbox_BOXZEROBYTES;
const ZEROBYTES = NaCl.lowlevel.crypto_secretbox_ZEROBYTES;
const length = frame.length + BOXZEROBYTES;
this.sendBuffer.fill(0, BOXZEROBYTES, BOXZEROBYTES + ZEROBYTES);
frame.copy(this.sendBuffer, BOXZEROBYTES + ZEROBYTES);
NaCl.lowlevel.crypto_secretbox(this.sendBuffer, this.sendBuffer.subarray(BOXZEROBYTES), ZEROBYTES + frame.length, this.sendNonce, this.secret);
this.sendNonce.copy(this.sendBuffer, BOXZEROBYTES - 12, 0, 12);
return this.sendUDPPacket(this.sendBuffer.subarray(BOXZEROBYTES - 12, BOXZEROBYTES + length));
}
}
// [DEPRECATED]
_sendAudioPacket(audio) {
return this._sendAudioFrame(audio);
}
[util.inspect.custom]() {
return Base.prototype[util.inspect.custom].call(this);
}
toString() {
return `[VoiceConnection ${this.channelID}]`;
}
toJSON(props = []) {
return Base.prototype.toJSON.call(this, [
"channelID",
"connecting",
"current",
"id",
"paused",
"playing",
"ready",
"volume",
...props
]);
}
}
VoiceConnection._converterCommand = converterCommand;
module.exports = VoiceConnection;

148
node_modules/eris/lib/voice/VoiceConnectionManager.js generated vendored Normal file
View File

@ -0,0 +1,148 @@
"use strict";
const Base = require("../structures/Base");
const Collection = require("../util/Collection");
class VoiceConnectionManager extends Collection {
constructor(vcObject) {
super(vcObject || require("./VoiceConnection"));
this.pendingGuilds = {};
}
join(guildID, channelID, options) {
const connection = this.get(guildID);
if(connection && connection.ws) {
connection.switchChannel(channelID);
if(connection.ready) {
return Promise.resolve(connection);
} else {
return new Promise((res, rej) => {
const disconnectHandler = () => {
connection.removeListener("ready", readyHandler);
connection.removeListener("error", errorHandler);
rej(new Error("Disconnected"));
};
const readyHandler = () => {
connection.removeListener("disconnect", disconnectHandler);
connection.removeListener("error", errorHandler);
res(connection);
};
const errorHandler = (err) => {
connection.removeListener("disconnect", disconnectHandler);
connection.removeListener("ready", readyHandler);
connection.disconnect();
rej(err);
};
connection.once("ready", readyHandler).once("disconnect", disconnectHandler).once("error", errorHandler);
});
}
}
return new Promise((res, rej) => {
this.pendingGuilds[guildID] = {
channelID: channelID,
options: options || {},
res: res,
rej: rej,
timeout: setTimeout(() => {
delete this.pendingGuilds[guildID];
rej(new Error("Voice connection timeout"));
}, 10000)
};
});
}
leave(guildID) {
const connection = this.get(guildID);
if(!connection) {
return;
}
connection.disconnect();
connection._destroy();
this.remove(connection);
}
switch(guildID, channelID) {
const connection = this.get(guildID);
if(!connection) {
return;
}
connection.switch(channelID);
}
voiceServerUpdate(data) {
if(this.pendingGuilds[data.guild_id] && this.pendingGuilds[data.guild_id].timeout) {
clearTimeout(this.pendingGuilds[data.guild_id].timeout);
this.pendingGuilds[data.guild_id].timeout = null;
}
let connection = this.get(data.guild_id);
if(!connection) {
if(!this.pendingGuilds[data.guild_id]) {
return;
}
connection = this.add(new this.baseObject(data.guild_id, {
shard: data.shard,
opusOnly: this.pendingGuilds[data.guild_id].options.opusOnly,
shared: this.pendingGuilds[data.guild_id].options.shared
}));
}
connection.connect({
channel_id: (this.pendingGuilds[data.guild_id] || connection).channelID,
endpoint: data.endpoint,
token: data.token,
session_id: data.session_id,
user_id: data.user_id
});
if(!this.pendingGuilds[data.guild_id] || this.pendingGuilds[data.guild_id].waiting) {
return;
}
this.pendingGuilds[data.guild_id].waiting = true;
const disconnectHandler = () => {
connection = this.get(data.guild_id);
if(connection) {
connection.removeListener("ready", readyHandler);
connection.removeListener("error", errorHandler);
}
if(this.pendingGuilds[data.guild_id]) {
this.pendingGuilds[data.guild_id].rej(new Error("Disconnected"));
delete this.pendingGuilds[data.guild_id];
}
};
const readyHandler = () => {
connection = this.get(data.guild_id);
if(connection) {
connection.removeListener("disconnect", disconnectHandler);
connection.removeListener("error", errorHandler);
}
if(this.pendingGuilds[data.guild_id]) {
this.pendingGuilds[data.guild_id].res(connection);
delete this.pendingGuilds[data.guild_id];
}
};
const errorHandler = (err) => {
connection = this.get(data.guild_id);
if(connection) {
connection.removeListener("disconnect", disconnectHandler);
connection.removeListener("ready", readyHandler);
connection.disconnect();
}
if(this.pendingGuilds[data.guild_id]) {
this.pendingGuilds[data.guild_id].rej(err);
delete this.pendingGuilds[data.guild_id];
}
};
connection.once("ready", readyHandler).once("disconnect", disconnectHandler).once("error", errorHandler);
}
toString() {
return "[VoiceConnectionManager]";
}
toJSON(props = []) {
return Base.prototype.toJSON.call(this, [
"pendingGuilds",
...props
]);
}
}
module.exports = VoiceConnectionManager;

22
node_modules/eris/lib/voice/VoiceDataStream.js generated vendored Normal file
View File

@ -0,0 +1,22 @@
"use strict";
let EventEmitter;
try {
EventEmitter = require("eventemitter3");
} catch(err) {
EventEmitter = require("events").EventEmitter;
}
/**
* Represents a voice data stream
* @extends EventEmitter
* @prop {String} type The targeted voice data type for the stream, either "opus" or "pcm"
*/
class VoiceDataStream extends EventEmitter {
constructor(type) {
super();
this.type = type;
}
}
module.exports = VoiceDataStream;

40
node_modules/eris/lib/voice/streams/BaseTransformer.js generated vendored Normal file
View File

@ -0,0 +1,40 @@
"use strict";
const util = require("util");
const Base = require("../../structures/Base");
const TransformStream = require("stream").Transform;
class BaseTransformer extends TransformStream {
constructor(options = {}) {
if(options.allowHalfOpen === undefined) {
options.allowHalfOpen = true;
}
if(options.highWaterMark === undefined) {
options.highWaterMark = 0;
}
super(options);
this.manualCB = false;
}
setTransformCB(cb) {
if(this.manualCB) {
this.transformCB();
this._transformCB = cb;
} else {
cb();
}
}
transformCB() {
if(this._transformCB) {
this._transformCB();
this._transformCB = null;
}
}
[util.inspect.custom]() {
return Base.prototype[util.inspect.custom].call(this);
}
}
module.exports = BaseTransformer;

View File

@ -0,0 +1,78 @@
"use strict";
const BaseTransformer = require("./BaseTransformer");
class DCAOpusTransformer extends BaseTransformer {
constructor(options = {}) {
super(options);
this._remainder = null;
}
process(buffer) {
if(buffer.length - buffer._index < 2) {
return true;
}
const opusLen = buffer.readInt16LE(buffer._index);
buffer._index += 2;
if(buffer.length - buffer._index < opusLen) {
return true;
}
buffer._index += opusLen;
this.push(buffer.subarray(buffer._index - opusLen, buffer._index));
}
_transform(chunk, enc, cb) {
if(this._remainder) {
chunk = Buffer.concat([this._remainder, chunk]);
this._remainder = null;
}
if(!this.head) {
if(chunk.length < 4) {
this._remainder = chunk;
return cb();
} else {
const dcaVersion = chunk.subarray(0, 4);
if(dcaVersion[0] !== 68 || dcaVersion[1] !== 67 || dcaVersion[2] !== 65) { // DCA0 or invalid
this.head = true; // Attempt to play as if it were a DCA0 file
} else if(dcaVersion[3] === 49) { // DCA1
if(chunk.length < 8) {
this._remainder = chunk;
return cb();
}
const jsonLength = chunk.subarray(4, 8).readInt32LE(0);
if(chunk.length < 8 + jsonLength) {
this._remainder = chunk;
return cb();
}
const jsonMetadata = chunk.subarray(8, 8 + jsonLength);
this.emit("debug", jsonMetadata);
chunk = chunk.subarray(8 + jsonLength);
this.head = true;
} else {
this.emit("error", new Error("Unsupported DCA version: " + dcaVersion.toString()));
}
}
}
chunk._index = 0;
while(chunk._index < chunk.length) {
const offset = chunk._index;
const ret = this.process(chunk);
if(ret) {
this._remainder = chunk.subarray(offset);
cb();
return;
}
}
this.setTransformCB(cb);
}
}
module.exports = DCAOpusTransformer;

199
node_modules/eris/lib/voice/streams/FFmpegDuplex.js generated vendored Normal file
View File

@ -0,0 +1,199 @@
"use strict";
const util = require("util");
const Base = require("../../structures/Base");
const ChildProcess = require("child_process");
const DuplexStream = require("stream").Duplex;
const PassThroughStream = require("stream").PassThrough;
const delegateEvents = {
readable: "_reader",
data: "_reader",
end: "_reader",
drain: "_writer",
finish: "_writer"
};
class FFmpegDuplex extends DuplexStream {
constructor(command, options = {}) {
if(options.highWaterMark === undefined) {
options.highWaterMark = 0;
}
super(options);
this.command = command;
this._reader = new PassThroughStream(options);
this._writer = new PassThroughStream(options);
this._onError = this.emit.bind(this, "error");
this._reader.on("error", this._onError);
this._writer.on("error", this._onError);
this._readableState = this._reader._readableState;
this._writableState = this._writer._writableState;
["on", "once", "removeListener", "removeListeners", "listeners"].forEach((method) => {
const og = DuplexStream.prototype[method];
this[method] = function(ev, fn) {
const substream = delegateEvents[ev];
if(substream) {
return this[substream][method](ev, fn);
} else {
return og.call(this, ev, fn);
}
};
});
}
destroy() {
}
end(chunk, enc, cb) {
return this._writer.end(chunk, enc, cb);
}
kill() {
}
noop() {
}
pipe(dest, opts) {
return this._reader.pipe(dest, opts);
}
read(size) {
return this._reader.read(size);
}
setEncoding(enc) {
return this._reader.setEncoding(enc);
}
spawn(args, options = {}) {
let ex, exited, killed, ended;
let stderr = [];
const onStdoutEnd = () => {
if(exited && !ended) {
ended = true;
this._reader.end();
setImmediate(this.emit.bind(this, "close"));
}
};
const onStderrData = (chunk) => {
stderr.push(chunk);
};
const cleanup = () => {
this._process =
this._stderr =
this._stdout =
this._stdin =
stderr =
ex =
killed = null;
this.kill =
this.destroy = this.noop;
};
const onExit = (code, signal) => {
if(exited) {
return;
}
exited = true;
if(killed) {
if(ex) {
this.emit("error", ex);
}
this.emit("close");
} else if(code === 0 && signal == null) {
// All is well
onStdoutEnd();
} else {
// Everything else
ex = new Error("Command failed: " + Buffer.concat(stderr).toString("utf8"));
ex.killed = this._process.killed || killed;
ex.code = code;
ex.signal = signal;
this.emit("error", ex);
this.emit("close");
}
cleanup();
};
const onError = (err) => {
ex = err;
this._stdout.destroy();
this._stderr.destroy();
onExit();
};
const kill = () => {
if(killed) {
return;
}
this._stdout.destroy();
this._stderr.destroy();
killed = true;
try {
this._process.kill(options.killSignal || "SIGTERM");
setTimeout(() => this._process && this._process.kill("SIGKILL"), 2000);
} catch(e) {
ex = e;
onExit();
}
};
this._process = ChildProcess.spawn(this.command, args, options);
this._stdin = this._process.stdin;
this._stdout = this._process.stdout;
this._stderr = this._process.stderr;
this._writer.pipe(this._stdin);
this._stdout.pipe(this._reader, {
end: false
});
this.kill = this.destroy = kill;
this._stderr.on("data", onStderrData);
// In some cases ECONNRESET can be emitted by stdin because the process is not interested in any
// more data but the _writer is still piping. Forget about errors emitted on stdin and stdout
this._stdin.on("error", this.noop);
this._stdout.on("error", this.noop);
this._stdout.on("end", onStdoutEnd);
this._process.once("close", onExit);
this._process.once("error", onError);
return this;
}
unpipe(dest) {
return this._reader.unpipe(dest) || this.kill();
}
write(chunk, enc, cb) {
return this._writer.write(chunk, enc, cb);
}
[util.inspect.custom]() {
return Base.prototype[util.inspect.custom].call(this);
}
}
FFmpegDuplex.prototype.addListener = FFmpegDuplex.prototype.on;
FFmpegDuplex.spawn = function(connection, args, options) {
return new FFmpegDuplex(connection, options).spawn(args, options);
};
module.exports = FFmpegDuplex;

View File

@ -0,0 +1,35 @@
"use strict";
const FFmpegDuplex = require("./FFmpegDuplex");
module.exports = function(options = {}) {
if(!options.command) {
throw new Error("Invalid converter command");
}
if(options.frameDuration === undefined) {
options.frameDuration = 60;
}
let inputArgs = [
"-analyzeduration", "0",
"-loglevel", "24"
].concat(options.inputArgs || []);
if(options.format === "pcm") {
inputArgs = inputArgs.concat(
"-f", "s16le",
"-ar", "48000",
"-ac", "2"
);
}
inputArgs = inputArgs.concat(
"-i", options.input || "-",
"-vn"
);
const outputArgs = [
"-c:a", "libopus",
"-vbr", "on",
"-frame_duration", "" + options.frameDuration,
"-f", "ogg",
"-"
];
return FFmpegDuplex.spawn(options.command, inputArgs.concat(options.encoderArgs || [], outputArgs));
};

View File

@ -0,0 +1,26 @@
"use strict";
const FFmpegDuplex = require("./FFmpegDuplex");
module.exports = function(options = {}) {
if(!options.command) {
throw new Error("Invalid converter command");
}
if(options.samplingRate === undefined) {
options.samplingRate = 48000;
}
const inputArgs = [
"-analyzeduration", "0",
"-loglevel", "24"
].concat(options.inputArgs || [],
"-i", options.input || "-",
"-vn"
);
const outputArgs = [
"-f", "s16le",
"-ar", "" + options.samplingRate,
"-ac", "2",
"-"
];
return FFmpegDuplex.spawn(options.command, inputArgs.concat(options.encoderArgs || [], outputArgs));
};

View File

@ -0,0 +1,107 @@
"use strict";
const BaseTransformer = require("./BaseTransformer");
class OggOpusTransformer extends BaseTransformer {
constructor(options = {}) {
super(options);
this._remainder = null;
this._bitstream = null;
}
process(buffer) {
if(buffer.length - buffer._index <= 26) {
return true;
}
if(buffer.toString("utf8", buffer._index, buffer._index + 4) !== "OggS") {
return new Error("Invalid OGG magic string: " + buffer.toString("utf8", buffer._index, buffer._index + 4));
}
const typeFlag = buffer.readUInt8(buffer._index + 5);
if(typeFlag === 1) {
return new Error("OGG continued page not supported");
}
const bitstream = buffer.readUInt32BE(buffer._index + 14);
buffer._index += 26;
const segmentCount = buffer.readUInt8(buffer._index);
if(buffer.length - buffer._index - 1 < segmentCount) {
return true;
}
const segments = [];
let size = 0;
let byte = 0;
let total = 0;
let i = 0;
for(; i < segmentCount; i++) {
byte = buffer.readUInt8(++buffer._index);
if(byte < 255) {
segments.push(size + byte);
size = 0;
} else {
size += byte;
}
total += byte;
}
++buffer._index;
if(buffer.length - buffer._index < total) {
return true;
}
for(let segment of segments) {
buffer._index += segment;
byte = (segment = buffer.subarray(buffer._index - segment, buffer._index)).toString("utf8", 0, 8);
if(this.head) {
if(byte === "OpusTags") {
this.emit("debug", segment.toString());
} else if(bitstream === this._bitstream) {
this.push(segment);
}
} else if(byte === "OpusHead") {
this._bitstream = bitstream;
this.emit("debug", (this.head = segment.toString()));
} else {
this.emit("debug", "Invalid codec: " + byte);
}
}
}
_final() {
if(!this._bitstream) {
this.emit("error", new Error("No Opus stream was found"));
}
}
_transform(chunk, enc, cb) {
if(this._remainder) {
chunk = Buffer.concat([this._remainder, chunk]);
this._remainder = null;
}
chunk._index = 0;
while(chunk._index < chunk.length) {
const offset = chunk._index;
const ret = this.process(chunk);
if(ret) {
this._remainder = chunk.subarray(offset);
if(ret instanceof Error) {
this.emit("error", ret);
}
cb();
return;
}
}
this.setTransformCB(cb);
}
}
module.exports = OggOpusTransformer;

View File

@ -0,0 +1,61 @@
"use strict";
const BaseTransformer = require("./BaseTransformer");
class PCMOpusTransformer extends BaseTransformer {
constructor(options = {}) {
super(options);
this.opus = options.opusFactory();
this.frameSize = options.frameSize || 2880;
this.pcmSize = options.pcmSize || 11520;
this._remainder = null;
}
_destroy(...args) {
if(this.opus.delete) {
this.opus.delete();
}
return super._destroy(...args);
}
_flush(cb) {
if(this._remainder) {
const buf = Buffer.allocUnsafe(this.pcmSize);
this._remainder.copy(buf);
buf.fill(0, this._remainder.length);
this.push(this.opus.encode(buf, this.frameSize));
this._remainder = null;
}
cb();
}
_transform(chunk, enc, cb) {
if(this._remainder) {
chunk = Buffer.concat([this._remainder, chunk]);
this._remainder = null;
}
if(chunk.length < this.pcmSize) {
this._remainder = chunk;
return cb();
}
chunk._index = 0;
while(chunk._index + this.pcmSize < chunk.length) {
chunk._index += this.pcmSize;
this.push(this.opus.encode(chunk.subarray(chunk._index - this.pcmSize, chunk._index), this.frameSize));
}
if(chunk._index < chunk.length) {
this._remainder = chunk.subarray(chunk._index);
}
this.setTransformCB(cb);
}
}
module.exports = PCMOpusTransformer;

View File

@ -0,0 +1,50 @@
"use strict";
const BaseTransformer = require("./BaseTransformer");
class VolumeTransformer extends BaseTransformer {
constructor(options = {}) {
super(options);
this._remainder = null;
this.setVolume(1.0);
}
setVolume(volume) {
if(isNaN(volume) || (volume = +volume) < 0) {
throw new Error("Invalid volume level: " + volume);
}
this.volume = volume;
this.db = 10 * Math.log(1 + this.volume) / 6.931471805599453;
}
_transform(chunk, enc, cb) {
if(this._remainder) {
chunk = Buffer.concat([this._remainder, chunk]);
this._remainder = null;
}
if(chunk.length < 2) {
return cb();
}
let buf;
if(chunk.length & 1) {
this._remainder = chunk.subarray(chunk.length - 1);
buf = Buffer.allocUnsafe(chunk.length - 1);
} else {
buf = Buffer.allocUnsafe(chunk.length);
}
for(let i = 0, num; i < buf.length - 1; i += 2) {
// Bind transformed chunk to to 16 bit
num = ~~(this.db * chunk.readInt16LE(i));
buf.writeInt16LE(num >= 32767 ? 32767 : num <= -32767 ? -32767 : num, i);
}
this.push(buf);
this.setTransformCB(cb);
}
}
module.exports = VolumeTransformer;

View File

@ -0,0 +1,258 @@
"use strict";
const BaseTransformer = require("./BaseTransformer");
// EBML VInt max value is (2 ^ 56 - 2), but JS only supports 2^53
// 45 = 53 - 8 - check before last 8 bytes
const MAX_SHIFTED_VINT = Math.pow(2, 45);
const STATE_CONTENT = 0;
const STATE_TAG = 1;
const TAG_TYPE_END = 0;
const TAG_TYPE_START = 1;
const TAG_TYPE_TAG = 2;
const TRACKTYPE_AUDIO = 2; // EBML spec: https://www.matroska.org/technical/specs/index.html#TrackType
class WebmOpusTransformer extends BaseTransformer {
constructor(options = {}) {
super(options);
this._tag_stack = [];
this._state = STATE_TAG;
this._total = 0;
}
getVIntLength(buffer, index) {
let length = 1;
for(; length <= 8; ++length) {
if(buffer[index] & (1 << (8 - length))) {
break;
}
}
if(length > 8) {
this.emit("debug", new Error(`VInt length ${length} | ${buffer.toString("hex", index, index + length)}`));
return null;
}
if(index + length > buffer.length) {
return null;
}
return length;
}
process(type, info) {
if(type === TAG_TYPE_TAG) {
if(info.name === "SimpleBlock" && (info.data.readUInt8(0) & 0xF) === this.firstAudioTrack.TrackNumber) {
this.push(info.data.subarray(4));
return;
}
if(info.name === "CodecPrivate") {
const head = info.data.toString("utf8", 0, 8);
if(head !== "OpusHead") {
this.emit("error", new Error("Invalid codec: " + head));
return;
}
this.codecData = {
version: info.data.readUInt8(8),
channelCount: info.data.readUInt8(9),
preSkip: info.data.readUInt16LE(10),
inputSampleRate: info.data.readUInt32LE(12),
outputGain: info.data.readUInt16LE(16),
mappingFamily: info.data.readUInt8(18)
};
return;
}
}
if(!this.firstAudioTrack) {
if(info.name === "TrackEntry") {
if(type === TAG_TYPE_START) {
this.parsingTrack = {};
} else if(type === TAG_TYPE_END) {
if(this.parsingTrack.TrackNumber && this.parsingTrack.TrackType === TRACKTYPE_AUDIO) {
this.firstAudioTrack = this.parsingTrack;
}
delete this.parsingTrack;
}
return;
}
if(this.parsingTrack) {
if(info.name === "TrackNumber") {
this.parsingTrack.TrackNumber = info.data[0];
return;
}
if(info.name === "TrackType") {
this.parsingTrack.TrackType = info.data[0];
return;
}
}
if(type === TAG_TYPE_END && info.name === "Tracks") {
this.emit("error", new Error("No audio track"));
return;
}
return;
}
}
readContent(buffer) {
const tagObj = this._tag_stack[this._tag_stack.length - 1];
if(tagObj.type === "m") {
this.process(TAG_TYPE_START, tagObj);
this._state = STATE_TAG;
return true;
}
if(buffer.length < buffer._index + tagObj.size) {
return false;
}
tagObj.data = buffer.subarray(buffer._index, buffer._index + tagObj.size);
buffer._index += tagObj.size;
this._total += tagObj.size;
this._state = STATE_TAG;
this._tag_stack.pop();
this.process(TAG_TYPE_TAG, tagObj);
while(this._tag_stack.length > 0) {
if(this._total < this._tag_stack[this._tag_stack.length - 1].end) {
break;
}
this.process(TAG_TYPE_END, this._tag_stack.pop());
}
return true;
}
readTag(buffer) {
const tagSize = this.getVIntLength(buffer, buffer._index);
if(tagSize === null) {
return false;
}
const size = this.getVIntLength(buffer, buffer._index + tagSize);
if(size === null) {
return false;
}
const tagStr = buffer.toString("hex", buffer._index, buffer._index + tagSize);
const tagObj = {
type: "unknown",
name: "unknown",
end: this._total + tagSize
};
if(schema[tagStr]) {
tagObj.type = schema[tagStr].type;
tagObj.name = schema[tagStr].name;
}
buffer._index += tagSize;
let value = buffer[buffer._index] & (1 << (8 - size)) - 1;
for(let i = 1; i < size; ++i) {
if(i === 7 && value >= MAX_SHIFTED_VINT && buffer[buffer._index + 7] > 0) {
tagObj.end = -1; // Special livestreaming int 0x1FFFFFFFFFFFFFF
break;
}
value = (value << 8) + buffer[buffer._index + i];
}
if(tagObj.end !== -1) {
tagObj.end += value + size;
}
tagObj.size = value;
buffer._index += size;
this._total += tagSize + size;
this._state = STATE_CONTENT;
this._tag_stack.push(tagObj);
return true;
}
_transform(chunk, enc, cb) {
if(this._remainder) {
chunk = Buffer.concat([this._remainder, chunk]);
this._remainder = null;
}
chunk._index = 0;
while(chunk._index < chunk.length) {
if(this._state === STATE_TAG && !this.readTag(chunk)) {
break;
}
if(this._state === STATE_CONTENT && !this.readContent(chunk)) {
break;
}
}
if(chunk._index < chunk.length) {
this._remainder = chunk.subarray(chunk._index);
}
this.setTransformCB(cb);
}
}
module.exports = WebmOpusTransformer;
const schema = {
ae: {
name: "TrackEntry",
type: "m"
},
d7: {
name: "TrackNumber",
type: "u"
},
"86": {
name: "CodecID",
type: "s"
},
"83": {
name: "TrackType",
type: "u"
},
"1654ae6b": {
name: "Tracks",
type: "m"
},
"63a2": {
name: "CodecPrivate",
type: "b"
},
a3: {
name: "SimpleBlock",
type: "b"
},
"1a45dfa3": {
name: "EBML",
type: "m"
},
"18538067": {
name: "Segment",
type: "m"
},
"114d9b74": {
name: "SeekHead",
type: "m"
},
"1549a966": {
name: "Info",
type: "m"
},
e1: {
name: "Audio",
type: "m"
},
"1f43b675": {
name: "Cluster",
type: "m"
}
};