Pushing changes

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

14
node_modules/discordie/.npmignore generated vendored Normal file
View File

@ -0,0 +1,14 @@
.eslintrc
logs
*.log
npm-debug.log*
pids
*.pid
*.seed
.idea
.grunt
.lock-wscript
build/Release
node_modules
examples/auth.js
examples/test.mp3

24
node_modules/discordie/LICENSE generated vendored Normal file
View File

@ -0,0 +1,24 @@
Copyright (c) 2015-2016, qeled
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

501
node_modules/discordie/changelog.md generated vendored Normal file
View File

@ -0,0 +1,501 @@
# Discordie changelog
## 2017-01-08, Version 0.11.0
#### New:
- GIF support in `IUser.avatarURL`, added `IUser.staticAvatarURL`;
- Added `isGuildVoice`, `isGuildText`, `isDM`, `isGroupDM` getters to channels.
#### Fixed:
- Pending voice connections failing to create while already connecting;
## 2016-12-01, Version 0.10.0
#### New:
- Reactions:
- Events `MESSAGE_REACTION_ADD`, `MESSAGE_REACTION_REMOVE`,
`MESSAGE_REACTION_REMOVE_ALL`;
- Permission `ADD_REACTIONS`;
- Methods:
- **{Promise\<Array\<IUser>, Error>}** `IMessage.fetchReactions(emoji, limit, after)`;
- **{Promise}** `IMessage.addReaction(emoji)`;
- **{Promise}** `IMessage.removeReaction(emoji, user)`;
- `IGuild.editEmoji` now supports arrays of `IRole`;
- Embed support in messages: `ITextChannel.sendMessage(..., embed)`,
`IMessage.edit(content, embed)`;
- `IMessage.edit(content, ...)` now stringifies `content` if not string.
#### Fixed:
- Guild icon/splash/emoji url generators not using CDN endpoint;
- Sharding option validation error messages are now more specific.
## 2016-10-19, Version 0.9.0
#### New:
- Events `CHANNEL_UPDATE`, `GUILD_UPDATE`, `GUILD_MEMBER_UPDATE`,
`GUILD_ROLE_UPDATE`, `GUILD_EMOJIS_UPDATE` now contain a function
`getChanges()` for getting state object `{before, after}`;
- Events `GUILD_DELETE`, `GUILD_MEMBER_REMOVE`, `GUILD_ROLE_DELETE`
now contain a function `getCachedData()` for getting deleted state object;
- Added `data` field to `GUILD_MEMBER_REMOVE` event;
- Method `sendMessage` now converts into string any non-string input data;
- Support for `"dnd"` and `"invisible"` statuses;
- `IAuthenticatedUser.setStatus` now supports status object
`{status: String, afk: Boolean}`;
- Added `General` permissions: `MANAGE_WEBHOOKS`, `MANAGE_EMOJIS`;
- Getter `IMessage.displayUsername` returning a username or nick if present;
- Webhooks:
- Event `WEBHOOKS_UPDATE`;
- Message field **{String}** `IMessage.webhook_id`;
- Getter **{Boolean}**`IUser.isWebhook`;
- `IWebhookManager` as `Discordie.Webhooks`:
- **{Promise\<Array\<Object>, Error>}** `fetchForGuild(guild)`;
- **{Promise\<Array\<Object>, Error>}** `fetchForChannel(channel)`;
- **{Promise\<Object, Error>}** `create(channel, options)`;
- **{Promise\<Object, Error>}** `fetch(webhook, token)`;
- **{Promise\<Object, Error>}** `edit(webhook, token, options)`;
- **{Promise}** `delete(webhook, token)`;
- **{Promise}** `execute(webhook, token, options, wait)`;
- **{Promise}** `executeSlack(webhook, token, options, wait)`;
- `IGuild` emoji methods (user accounts only):
- **{Promise\<Array\<Object>, Error>}** `fetchEmoji()`;
- **{Promise\<Object, Error>}** `uploadEmoji(image, name)`;
- **{Promise}** `deleteEmoji(emoji)`;
- **{Promise\<Object, Error>}** `editEmoji(emoji, options)`;
- **{String|null}** `getEmojiURL(emoji)`;
- Presence (status and game) can now be set before `GATEWAY_READY`;
- Added param `userLimit` to method `IGuild.createChannel` and
`IChannel.clone`;
- Added `IGuild` field `default_message_notifications`;
- Added params `roles`, `channels`, `verificationLevel`,
`defaultMessageNotifications` to method `IGuild.create`;
- Added param `defaultMessageNotifications` to method `IGuild.edit`;
- `IGuild` widget properties `embed_enabled`, `embed_channel_id` have been
replaced with methods `getWidget()` and `editWidget(options)`;
#### Fixed:
- Fixed `guild.member_count` being erased after `GUILD_UPDATE`;
- Calling `JSON.stringify` on interfaces not converting internal
`Map`s and `Set`s into arrays (ex. `roles` array in an `IGuild`);
- Event `GUILD_MEMBER_REMOVE` no longer emits for self due to
race condition between `GUILD_DELETE` and `GUILD_MEMBER_REMOVE`;
- Fixed role reordering;
- Fixed `IChannel.clone` not handling channel types correctly;
## 2016-08-31, Version 0.8.1
- Minor changes in rate limit bucket structure, X-RateLimit-Reset support;
- Calling `member.unban()` on invalid member objects no longer throws;
- Added `CHANNEL_PINNED_MESSAGE` (type 6) to `Discordie.MessageTypes`.
## 2016-08-17, Version 0.8.0
#### Breaking Discord API v6 changes:
- Channel type is now a number, not string `"text"` and `"voice"`:
see [`Discordie.ChannelTypes`](https://qeled.github.io/discordie/#/docs/IChannel);
- Direct message channels now have **{Array\<IUser>}** `recipients`
instead of **{String}** `recipient_id`, getter **{IUser}** `recipient`
added and marked as deprecated;
- Channel field `is_private` has been removed from the API,
getters [`isPrivate` and `is_private`](https://qeled.github.io/discordie/#/docs/IChannel?p=IChannel%23is_private)
added to replace it. Getter `is_private` is marked as deprecated.
#### Overview of API v6 changes:
- New events `CALL_CREATE`, `CALL_UPDATE`, `CALL_DELETE`,
`CHANNEL_RECIPIENT_ADD`, `CHANNEL_RECIPIENT_REMOVE`;
- Message fields added:
- **{Number}** `type`:
everything other than 0 is a system message,
see [`Discordie.MessageTypes`](https://qeled.github.io/discordie/#/docs/IMessage);
- **{Object}** `call`: [object](http://qeled.github.io/discordie/#/docs/IMessage?p=IMessage.call)
present if this is a system message with call info.
- Channel fields removed:
**{String}** `type`,
**{Boolean}** `is_private`;
- Channel fields added:
**{Number}** `type`,
**{Array<IUser>}** `recipients`,
**{String|null}** `owner_id`,
**{String|null}** `icon`.
#### Library changes:
- New local events `CALL_UNAVAILABLE`, `CALL_RING`;
- New method `IDirectMessageChannelCollection.createGroupDM()`;
- **`IDirectMessageChannel`**:
- New getters:
- **{[ICall](http://qeled.github.io/discordie/#/docs/ICall)}** `call`;
- **{Array<IUser>|null}** `usersInCall`;
- **{IAuthenticatedUser|IUser|null}** `owner`;
- **{String|null}** `iconURL`;
- New methods:
- **{Boolean}** `isOwner(user)`;
- **{Promise}** `ring(recipients)` (user accounts only);
- **{Promise}** `stopRinging(recipients)` (user accounts only);
- **{Promise}** `changeCallRegion(region)` (user accounts only);
- **{Promise}** `addRecipient(user)` (user accounts only);
- **{Promise}** `removeRecipient(user)` (user accounts only);
- **{Promise\<IDirectMessageChannel, Error>}** `setName(name)`;
- **{Promise\<IDirectMessageChannel, Error>}** `setIcon(icon)`;
- **{Promise}** `joinCall(selfMute, selfDeaf)` (user accounts only);
- **{void}** `leaveCall()`;
- **{VoiceConnectionInfo|null}** `getVoiceConnectionInfo()`;
- **{Promise<ICall|null, Error>}** `fetchCall()`;
- Marked getter `recipient` as deprecated, use `recipients` instead;
- **`IMessage`**:
- New getters:
- **{Boolean|null}** `isSystem`;
- **{String|null}** `systemMessage`;
- `IUser.getVoiceChannel(guild)` now accepts null guild;
- New method `IUserCollection.usersInCall(channel)`.
#### New:
- Option to disable implicit mentions in
`user.isMentioned(message, ignoreImplicitMentions)`;
- Bot token header is now forced depending on account type received in
`READY`;
- Updated rate limits, including header support.
#### Fixed:
- `channel.fetchMessages()` failing with Discord's new parameter validation
(since 2016-08-11).
## 2016-08-04, Version 0.7.6
#### New:
- Emoji field support: `IGuild.emojis`;
- Bot application and owner info: `IAuthenticatedUser.getApplication()`.
#### Other:
- `ICollectionBase.forEach` now doesn't stop iterating if you return a
truthy value in the callback.
## 2016-07-29, Version 0.7.5
#### API changes:
- New permission `General.EXTERNAL_EMOTES`.
#### New:
- Preemptive rate limit handling: bots can now send messages as fast
as they are allowed;
- Collection lengths are now printed when inspected with `console.log`:
ex. `IGuildCollection { length: 8 }`;
- `IDirectMessageChannel` now supports fetching pinned messages.
#### Fixes:
- Fixed rare case of voice states not syncing after gateway being `RESUMED`;
- Fixed some promises returning strings instead of proper `Error` objects;
- Fixed array collections crashing on latest V8 engine.
#### Other:
- Buffers are now allocated using `Buffer.alloc`/`Buffer.from` instead of
deprecated `Buffer` constructor whenever possible.
## 2016-07-16, Version 0.7.3
#### API changes:
- Removed human readable invites.
#### Fixes:
- Order of pinned messages now matches the client;
- Upgraded `ws` dependency from v0.8.0 to v1.1.1, fixes crash on node v6.x.x;
- Property `previousNick` in `GUILD_MEMBER_UPDATE` is now set correctly.
#### Other:
- Changed permission (methods `IUser/IGuildMember.permissionsFor/can`)
error message from `"Invalid user"` to
`"User is not a member of the context"`.
## 2016-06-25, Version 0.7.2
#### New:
- Event `GUILD_MEMBER_UPDATE` now exposes object changes:
- **{Array\<IRole>}** `rolesAdded`;
- **{Array\<IRole>}** `rolesRemoved`;
- **{String|null}** `previousNick`.
- `IAuthenticatedUser.setGame/setStatus` now also accept games as strings;
- Message pinning support:
- ITextChannel
- **{Array\<IMessage>}** `ITextChannel.pinnedMessages`;
- **{Promise\<Object, Error>}** `ITextChannel.fetchPinned()`;
- IMessage
- **{Boolean}** `IMessage.pinned`;
- **{Promise\<IMessage, Error>}** `IMessage.pin()`;
- **{Promise\<IMessage, Error>}** `IMessage.unpin()`;
- IMessageCollection
- **{Array\<IMessage>}** `IMessageCollection.forChannelPinned(channel)` (same as `pinnedMessages`);
- **{Promise}** `IMessageCollection.pinMessage(messageId, channelId)`;
- **{Promise}** `IMessageCollection.unpinMessage(messageId, channelId)`;
- **{void}** `IMessageCollection.purgeChannelPinned(channel)`;
- **{void}** `IMessageCollection.purgePinned()`.
## 2016-06-18, Version 0.7.0
#### New:
- Gateway V5 support;
- Event `MESSAGE_DELETE_BULK`;
- Integrated API (REST) error messages;
(ex. `Error: Bad Request (Cannot send an empty message)`);
- MFA fields: `IAuthenticatedUser.mfa_enabled`, `IGuild.mfa_level`;
- Automatic `Bad Gateway` (HTTP 502) handling, will only throw a 502 after
10 retries.
#### Fixes:
- Permission overwrites for voice channels now actually have `Voice` section.
## 2016-05-29, Version 0.6.5
#### Fixes:
- Workaround for `FFmpegEncoder` 'end' event not firing on
nodejs v5.11.0/4.4.5+.
## 2016-05-23, Version 0.6.4
#### New:
- User limits in voice channels:
- Added param `userLimit` to `IChannel.update`;
- Channel property `user_limit`;
- Method `channel.join()` will return rejected promise if channel is full
and permission `Voice.MOVE_MEMBERS` is denied.
## 2016-05-14, Version 0.6.2
#### New:
- Auto-reconnect with a constructor option:
`new Discordie({autoReconnect: true});`;
- Profile editing aliases `IAuthenticatedUser.setAvatar/setUsername`.
## 2016-05-10, Version 0.6.1
#### Discord API Changes:
- [New `MANAGE_ROLES` (`ADMINISTRATOR`) permission](https://github.com/hammerandchisel/discord-api-docs/issues/41).
#### New:
- Bulk-delete messages with `IMessageCollection.deleteMessages(array)`;
- Channel cloning `IChannel.clone(name, type, bitrate)`;
- Added params `permissionOverwrites`, `bitrate` to `IGuild.createChannel`.
#### Fixes:
- Fixed self nicknames not setting without `MANAGE_NICKNAMES` permission.
## 2016-05-04, Version 0.6.0
#### New:
- Method `IMessage.resolveContent()` resolving `<@(#|!|&)?id>` entities to
names to get human readable content;
- Nickname support:
- Permissions `CHANGE_NICKNAME`, `MANAGE_NICKNAMES`;
- `IGuildMember.nick` property;
- `IGuildMember.name` getter, returns nick if exists, otherwise username;
- `IGuildMember.setNickname(nick)` method.
- Mentionable roles:
- `IMessage.mention_roles` property;
- Param `mentionable` in `IRole.commit(name, color, hoist, mentionable)`.
- Mentions:
- `IUser/IGuildMember.nickMention` getter;
- `ITextChannel.mention` getter;
- `IRole.mention` getter.
## 2016-04-28, Version 0.5.7
#### Fixes:
- FFmpeg processes will be killed with `SIGKILL` if not exited within
5 second timeout;
- Method `uploadFile` now checks file existence if called with a file path.
## 2016-04-25, Version 0.5.6
#### Fixes:
- `READY` timeout no longer fires after a disconnect.
#### Performance:
- Internal opus now starts faster.
## 2016-04-19, Version 0.5.5
#### Fixes:
- FFmpegEncoder stdin errors can now be handled with the standard
`encoder.stdin.on("error", handler)`;
- Fixed debug mode FFmpegEncoder listener leak warnings from EventEmitter.
#### Performance:
- Optimized voice state tracking;
- Optimized `(DirectMessage)ChannelCollection.get`;
- Events `PRESENCE_UPDATE` and `TYPING_START` will only fire if there are
listeners assigned to them.
## 2016-04-17, Version 0.5.4
#### New:
- Gateway sharding support (`shardId` and `shardCount` options in
`Discordie` constructor).
#### Fixes:
- Fixed empty audio output with 48kHz input data.
## 2016-04-14, Version 0.5.2
#### Fixes:
- Fixed audio subsystem breaking (not buffering data) when using
[PM2](http://pm2.keymetrics.io/) (process manager);
## 2016-04-13, Version 0.5.1
#### Fixes:
- Rate limited file uploads with streams now resend data correctly;
#### Performance:
- Minor performance improvement for audio mixing without volume set.
## 2016-04-12, Version 0.5.0
#### New:
- High level audio streams (`AudioEncoderStream`, `FFmpegEncoder`,
`OggOpusPlayer`, `WebmOpusPlayer`),
instantiated using `IVoiceConnection.createExternalEncoder`
and `IVoiceConnection.getEncoderStream`.
## 2016-04-09, Version 0.4.4
#### New:
- Rate limit handling for messages. All messages are now put in a queue
and sent sequentially;
- Low level audio API extensions (`AudioEncoder`) -
new methods `.enqueueMultiple` and `.clearQueue`;
- Event `GUILD_CREATE` now has a parameter `becameAvailable` to
discriminate between joined and unavailable guilds.
#### Fixes:
- Normal precision scheduling now processes packet queue correctly;
- AudioEncoder queue changed to pause after 1 second of inactivity;
- V4 READY timeout changed to reset after each `GUILD_CREATE`;
- Fixed voice disconnecting after resuming gateway connection;
## 2016-04-06, Version 0.4.2
#### New:
- Gateway V4 support;
- Exposed `GATEWAY_RESUMED` event;
- Bans now can be added without member object - `IGuild.ban(user)`.
## 2016-03-25, Version 0.4.0
#### Notable changes:
- Fully migrated to bot multiserver voice API (user accounts no longer can
connect to more than one guild concurrently);
- Improved voice disconnect handling logic: more info in `VOICE_DISCONNECTED`
event docs (no breaking changes);
- Presence updates for friend lists are no longer dispatched over
`PRESENCE_UPDATE` event;
- *(Discord-side)* `Invites.accept` no longer works on bot accounts.
#### New:
- Exposed `user.bot` boolean property;
- Implemented offline guild members requesting:
`Users.fetchMembers(singleGuildOrGuildsArray)`;
- Alternative methods for deleting/editing messages by id:
- `Messages.editMessage(content, messageId, channelId)`;
- `Messages.deleteMessage(messageId, channelId)`;
- Pending voice connections can now be cancelled with `.leave()` on the same
channel.
#### Fixes:
- `GUILD_MEMBER_REMOVE` is now handled correctly and actually removes
members from cache;
- Fixed `IGuild.getPruneEstimate()` and `IGuild.pruneMembers()`;
- Encoder states are no longer created in proxy mode;
- Voice connections now properly disconnect on `GUILD_UNAVAILABLE`;
- Fixed `IVoiceChannel.joined` reporting incorrect state for pending
connections.
#### Performance:
- Improved performance for voice encryption and RTP muxing.
## 2016-03-09, Version 0.3.0
#### Notable changes:
- Memory and CPU usage has been reduced greatly;
- Implemented caching of member interfaces;
- Messages are sorted on insertion using binary sort, sorting after fetching
is removed;
- `JSON.stringify` on interfaces returns a copy of raw model data instead
of stringifying models recursively;
- Interfaces can now be properly formatted (inspected) using `console.log`
and `util.inspect`;
#### Fixes:
- Fix voice state tracking on `READY` for clients in multiple servers;
- Cache voice server address on connect and no longer attempt to resolve
hostname during UDP packet send calls;
- Fix `DirectMessageChannels.getOrOpen(recipient)` crashing on node 5.7.0;
## 2016-02-27, Version 0.2.1
#### Performance:
- Improve performance of `<Collection>.get`;
#### Fixes:
- Fix voice leave or disconnect crashing the library when called for voice
connections on secondary gateways;

View File

@ -0,0 +1,25 @@
-----BEGIN CERTIFICATE-----
MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU
MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs
IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290
MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux
FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h
bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v
dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt
H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9
uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX
mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX
a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN
E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0
WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD
VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0
Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU
cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx
IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN
AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH
YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5
6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC
Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX
c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a
mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ=
-----END CERTIFICATE-----

View File

@ -0,0 +1,32 @@
-----BEGIN CERTIFICATE-----
MIIFdDCCBFygAwIBAgIQJ2buVutJ846r13Ci/ITeIjANBgkqhkiG9w0BAQwFADBv
MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk
ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF
eHRlcm5hbCBDQSBSb290MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFow
gYUxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO
BgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9ETyBDQSBMaW1pdGVkMSswKQYD
VQQDEyJDT01PRE8gUlNBIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkq
hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAkehUktIKVrGsDSTdxc9EZ3SZKzejfSNw
AHG8U9/E+ioSj0t/EFa9n3Byt2F/yUsPF6c947AEYe7/EZfH9IY+Cvo+XPmT5jR6
2RRr55yzhaCCenavcZDX7P0N+pxs+t+wgvQUfvm+xKYvT3+Zf7X8Z0NyvQwA1onr
ayzT7Y+YHBSrfuXjbvzYqOSSJNpDa2K4Vf3qwbxstovzDo2a5JtsaZn4eEgwRdWt
4Q08RWD8MpZRJ7xnw8outmvqRsfHIKCxH2XeSAi6pE6p8oNGN4Tr6MyBSENnTnIq
m1y9TBsoilwie7SrmNnu4FGDwwlGTm0+mfqVF9p8M1dBPI1R7Qu2XK8sYxrfV8g/
vOldxJuvRZnio1oktLqpVj3Pb6r/SVi+8Kj/9Lit6Tf7urj0Czr56ENCHonYhMsT
8dm74YlguIwoVqwUHZwK53Hrzw7dPamWoUi9PPevtQ0iTMARgexWO/bTouJbt7IE
IlKVgJNp6I5MZfGRAy1wdALqi2cVKWlSArvX31BqVUa/oKMoYX9w0MOiqiwhqkfO
KJwGRXa/ghgntNWutMtQ5mv0TIZxMOmm3xaG4Nj/QN370EKIf6MzOi5cHkERgWPO
GHFrK+ymircxXDpqR+DDeVnWIBqv8mqYqnK8V0rSS527EPywTEHl7R09XiidnMy/
s1Hap0flhFMCAwEAAaOB9DCB8TAfBgNVHSMEGDAWgBStvZh6NLQm9/rEJlTvA73g
JMtUGjAdBgNVHQ4EFgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQD
AgGGMA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0gBAowCDAGBgRVHSAAMEQGA1UdHwQ9
MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9BZGRUcnVzdEV4dGVy
bmFsQ0FSb290LmNybDA1BggrBgEFBQcBAQQpMCcwJQYIKwYBBQUHMAGGGWh0dHA6
Ly9vY3NwLnVzZXJ0cnVzdC5jb20wDQYJKoZIhvcNAQEMBQADggEBAGS/g/FfmoXQ
zbihKVcN6Fr30ek+8nYEbvFScLsePP9NDXRqzIGCJdPDoCpdTPW6i6FtxFQJdcfj
Jw5dhHk3QBN39bSsHNA7qxcS1u80GH4r6XnTq1dFDK8o+tDb5VCViLvfhVdpfZLY
Uspzgb8c8+a4bmYRBbMelC1/kZWSWfFMzqORcUx8Rww7Cxn2obFshj5cqsQugsv5
B5a6SE2Q8pTIqXOi6wZ7I53eovNNVZ96YUWYGGjHXkBrI/V5eu+MtWuLt29G9Hvx
PUsE2JOAWVrgQSQdso8VYFhH2+9uRv0V9dlfmrPb2LjkQLPNlzmuhbsdjrzch5vR
pu/xO28QOG8=
-----END CERTIFICATE-----

View File

@ -0,0 +1,35 @@
-----BEGIN CERTIFICATE-----
MIIGCDCCA/CgAwIBAgIQKy5u6tl1NmwUim7bo3yMBzANBgkqhkiG9w0BAQwFADCB
hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV
BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTQwMjEy
MDAwMDAwWhcNMjkwMjExMjM1OTU5WjCBkDELMAkGA1UEBhMCR0IxGzAZBgNVBAgT
EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR
Q09NT0RPIENBIExpbWl0ZWQxNjA0BgNVBAMTLUNPTU9ETyBSU0EgRG9tYWluIFZh
bGlkYXRpb24gU2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP
ADCCAQoCggEBAI7CAhnhoFmk6zg1jSz9AdDTScBkxwtiBUUWOqigwAwCfx3M28Sh
bXcDow+G+eMGnD4LgYqbSRutA776S9uMIO3Vzl5ljj4Nr0zCsLdFXlIvNN5IJGS0
Qa4Al/e+Z96e0HqnU4A7fK31llVvl0cKfIWLIpeNs4TgllfQcBhglo/uLQeTnaG6
ytHNe+nEKpooIZFNb5JPJaXyejXdJtxGpdCsWTWM/06RQ1A/WZMebFEh7lgUq/51
UHg+TLAchhP6a5i84DuUHoVS3AOTJBhuyydRReZw3iVDpA3hSqXttn7IzW3uLh0n
c13cRTCAquOyQQuvvUSH2rnlG51/ruWFgqUCAwEAAaOCAWUwggFhMB8GA1UdIwQY
MBaAFLuvfgI9+qbxPISOre44mOzZMjLUMB0GA1UdDgQWBBSQr2o6lFoL2JDqElZz
30O0Oija5zAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNV
HSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwGwYDVR0gBBQwEjAGBgRVHSAAMAgG
BmeBDAECATBMBgNVHR8ERTBDMEGgP6A9hjtodHRwOi8vY3JsLmNvbW9kb2NhLmNv
bS9DT01PRE9SU0FDZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDBxBggrBgEFBQcB
AQRlMGMwOwYIKwYBBQUHMAKGL2h0dHA6Ly9jcnQuY29tb2RvY2EuY29tL0NPTU9E
T1JTQUFkZFRydXN0Q0EuY3J0MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21v
ZG9jYS5jb20wDQYJKoZIhvcNAQEMBQADggIBAE4rdk+SHGI2ibp3wScF9BzWRJ2p
mj6q1WZmAT7qSeaiNbz69t2Vjpk1mA42GHWx3d1Qcnyu3HeIzg/3kCDKo2cuH1Z/
e+FE6kKVxF0NAVBGFfKBiVlsit2M8RKhjTpCipj4SzR7JzsItG8kO3KdY3RYPBps
P0/HEZrIqPW1N+8QRcZs2eBelSaz662jue5/DJpmNXMyYE7l3YphLG5SEXdoltMY
dVEVABt0iN3hxzgEQyjpFv3ZBdRdRydg1vs4O2xyopT4Qhrf7W8GjEXCBgCq5Ojc
2bXhc3js9iPc0d1sjhqPpepUfJa3w/5Vjo1JXvxku88+vZbrac2/4EjxYoIQ5QxG
V/Iz2tDIY+3GH5QFlkoakdH368+PUq4NCNk+qKBR6cGHdNXJ93SrLlP7u3r7l+L4
HyaPs9Kg4DdbKDsx5Q5XLVq4rXmsXiBmGqW5prU5wfWYQ//u+aen/e7KJD2AFsQX
j4rBYKEMrltDR5FL1ZoXX/nUh8HCjLfn4g8wGTeGrODcQgPmlKidrv0PJFGUzpII
0fxQ8ANAe4hZ7Q7drNJ3gjTcBpUC2JD5Leo31Rpg0Gcg19hCC0Wvgmje3WYkN5Ap
lBlGGSW4gNfL1IYoakRwJiNiqZ+Gb7+6kHDSVneFeO/qJakXzlByjAA6quPbYzSf
+AZxAeKCINT+b72x
-----END CERTIFICATE-----

14
node_modules/discordie/deps/nopus/index.js generated vendored Normal file
View File

@ -0,0 +1,14 @@
var ex = "uncaughtException";
var pl = process.listeners(ex);
var opus = require("./opus-js/opus");
var resampler = require("./opus-js/resampler");
process.removeAllListeners(ex);
for (var i = 0; i < pl.length; i++) process.on(ex, pl[i]);
module.exports.Opus = opus.Opus;
module.exports.OpusApplication = opus.OpusApplication;
module.exports.OpusEncoder = opus.OpusEncoder;
module.exports.OpusDecoder = opus.OpusDecoder;
module.exports.Resampler = resampler.SpeexResampler;

View File

@ -0,0 +1,28 @@
Copyright (c) 1994-2013 Xiph.Org Foundation and contributors
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
- Neither the name of the Xiph.Org Foundation nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,35 @@
Copyright 2002-2008 Xiph.org Foundation
Copyright 2002-2008 Jean-Marc Valin
Copyright 2005-2007 Analog Devices Inc.
Copyright 2005-2008 Commonwealth Scientific and Industrial Research
Organisation (CSIRO)
Copyright 1993, 2002, 2006 David Rowe
Copyright 2003 EpicGames
Copyright 1992-1994 Jutta Degener, Carsten Bormann
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
- Neither the name of the Xiph.org Foundation nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,29 @@
Copyright (c) 2013-2014, Kazuki Oikawa
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

File diff suppressed because one or more lines are too long

217
node_modules/discordie/deps/nopus/opus-js/opus.js generated vendored Normal file
View File

@ -0,0 +1,217 @@
///<reference path="d.ts/asm.d.ts" />
///<reference path="d.ts/libopus.d.ts" />
var native = require("./libopus_libspeexdsp");
var OPUS_SET_BITRATE_REQUEST = 4002;
var OpusApplication;
(function (OpusApplication) {
OpusApplication[OpusApplication["VoIP"] = 2048] = "VoIP";
OpusApplication[OpusApplication["Audio"] = 2049] = "Audio";
OpusApplication[OpusApplication["RestrictedLowDelay"] = 2051] = "RestrictedLowDelay";
})(OpusApplication || (OpusApplication = {}));
var OpusError;
(function (OpusError) {
OpusError[OpusError["OK"] = 0] = "OK";
OpusError[OpusError["BadArgument"] = -1] = "BadArgument";
OpusError[OpusError["BufferTooSmall"] = -2] = "BufferTooSmall";
OpusError[OpusError["InternalError"] = -3] = "InternalError";
OpusError[OpusError["InvalidPacket"] = -4] = "InvalidPacket";
OpusError[OpusError["Unimplemented"] = -5] = "Unimplemented";
OpusError[OpusError["InvalidState"] = -6] = "InvalidState";
OpusError[OpusError["AllocFail"] = -7] = "AllocFail";
})(OpusError || (OpusError = {}));
var Opus = (function () {
function Opus() {
}
Opus.getVersion = function () {
var ptr = native._opus_get_version_string();
return Pointer_stringify(ptr);
};
Opus.getMaxFrameSize = function (numberOfStreams) {
if (numberOfStreams === void 0) { numberOfStreams = 1; }
return (1275 * 3 + 7) * numberOfStreams;
};
Opus.getMinFrameDuration = function () {
return 2.5;
};
Opus.getMaxFrameDuration = function () {
return 60;
};
Opus.validFrameDuration = function (x) {
return [2.5, 5, 10, 20, 40, 60].some(function (element) {
return element == x;
});
};
Opus.getMaxSamplesPerChannel = function (sampling_rate) {
return sampling_rate / 1000 * Opus.getMaxFrameDuration();
};
return Opus;
})();
var OpusEncoder = (function () {
function OpusEncoder(sampling_rate, channels, app, frame_duration) {
if (frame_duration === void 0) { frame_duration = 20; }
this.handle = 0;
this.frame_size = 0;
this.in_ptr = 0;
this.in_off = 0;
this.out_ptr = 0;
if (!Opus.validFrameDuration(frame_duration))
throw 'invalid frame duration';
this.frame_size = sampling_rate * frame_duration / 1000;
var err_ptr = native.allocate(4, 'i32', native.ALLOC_STACK);
this.handle = native._opus_encoder_create(sampling_rate, channels, app, err_ptr);
if (native.getValue(err_ptr, 'i32') != 0 /* OK */)
throw 'opus_encoder_create failed: ' + native.getValue(err_ptr, 'i32');
this.in_ptr = native._malloc(this.frame_size * channels * 4);
this.in_len = this.frame_size * channels;
this.in_i16 = native.HEAP16.subarray(this.in_ptr >> 1, (this.in_ptr >> 1) + this.in_len);
this.in_f32 = native.HEAPF32.subarray(this.in_ptr >> 2, (this.in_ptr >> 2) + this.in_len);
this.out_bytes = Opus.getMaxFrameSize();
this.out_ptr = native._malloc(this.out_bytes);
this.out_buf = native.HEAPU8.subarray(this.out_ptr, this.out_ptr + this.out_bytes);
}
OpusEncoder.prototype.set_bitrate = function (bitrate) {
var bitrate_ptr = native.allocate(4, 'i32', native.ALLOC_STACK);
native.setValue(bitrate_ptr, bitrate, 'i32');
var ret = native._opus_encoder_ctl(this.handle, OPUS_SET_BITRATE_REQUEST, bitrate_ptr);
if (ret < 0)
throw 'opus_encoder_ctl failed: ' + ret;
};
OpusEncoder.prototype.encode = function (pcm) {
var output = [];
var pcm_off = 0;
while (pcm.length - pcm_off >= this.in_len - this.in_off) {
if (this.in_off > 0) {
this.in_i16.set(pcm.subarray(pcm_off, pcm_off + this.in_len - this.in_off), this.in_off);
pcm_off += this.in_len - this.in_off;
this.in_off = 0;
}
else {
this.in_i16.set(pcm.subarray(pcm_off, pcm_off + this.in_len));
pcm_off += this.in_len;
}
var ret = native._opus_encode(this.handle, this.in_ptr, this.frame_size, this.out_ptr, this.out_bytes);
if (ret <= 0)
throw 'opus_encode failed: ' + ret;
var packet = new ArrayBuffer(ret);
new Uint8Array(packet).set(this.out_buf.subarray(0, ret));
output.push(packet);
}
if (pcm_off < pcm.length) {
this.in_i16.set(pcm.subarray(pcm_off));
this.in_off = pcm.length - pcm_off;
}
return output;
};
OpusEncoder.prototype.encode_float = function (pcm) {
var output = [];
var pcm_off = 0;
while (pcm.length - pcm_off >= this.in_len - this.in_off) {
if (this.in_off > 0) {
this.in_f32.set(pcm.subarray(pcm_off, pcm_off + this.in_len - this.in_off), this.in_off);
pcm_off += this.in_len - this.in_off;
this.in_off = 0;
}
else {
this.in_f32.set(pcm.subarray(pcm_off, pcm_off + this.in_len));
pcm_off += this.in_len;
}
var ret = native._opus_encode_float(this.handle, this.in_ptr, this.frame_size, this.out_ptr, this.out_bytes);
if (ret <= 0)
throw 'opus_encode failed: ' + ret;
var packet = new ArrayBuffer(ret);
new Uint8Array(packet).set(this.out_buf.subarray(0, ret));
output.push(packet);
}
if (pcm_off < pcm.length) {
this.in_f32.set(pcm.subarray(pcm_off));
this.in_off = pcm.length - pcm_off;
}
return output;
};
OpusEncoder.prototype.encode_final = function () {
if (this.in_off == 0)
return new ArrayBuffer(0);
for (var i = this.in_off; i < this.in_len; ++i)
this.in_i16[i] = 0;
var ret = native._opus_encode(this.handle, this.in_ptr, this.frame_size, this.out_ptr, this.out_bytes);
if (ret <= 0)
throw 'opus_encode failed: ' + ret;
var packet = new ArrayBuffer(ret);
new Uint8Array(packet).set(this.out_buf.subarray(0, ret));
return packet;
};
OpusEncoder.prototype.encode_float_final = function () {
if (this.in_off == 0)
return new ArrayBuffer(0);
for (var i = this.in_off; i < this.in_len; ++i)
this.in_f32[i] = 0;
var ret = native._opus_encode_float(this.handle, this.in_ptr, this.frame_size, this.out_ptr, this.out_bytes);
if (ret <= 0)
throw 'opus_encode failed: ' + ret;
var packet = new ArrayBuffer(ret);
new Uint8Array(packet).set(this.out_buf.subarray(0, ret));
return packet;
};
OpusEncoder.prototype.destroy = function () {
if (!this.handle)
return;
native._opus_encoder_destroy(this.handle);
native._free(this.in_ptr);
this.handle = this.in_ptr = 0;
};
return OpusEncoder;
})();
var OpusDecoder = (function () {
function OpusDecoder(sampling_rate, channels) {
this.handle = 0;
this.in_ptr = 0;
this.out_ptr = 0;
this.channels = channels;
var err_ptr = native.allocate(4, 'i32', native.ALLOC_STACK);
this.handle = native._opus_decoder_create(sampling_rate, channels, err_ptr);
if (native.getValue(err_ptr, 'i32') != 0 /* OK */)
throw 'opus_decoder_create failed: ' + native.getValue(err_ptr, 'i32');
this.in_ptr = native._malloc(Opus.getMaxFrameSize(channels));
this.in_buf = native.HEAPU8.subarray(this.in_ptr, this.in_ptr + Opus.getMaxFrameSize(channels));
this.out_len = Opus.getMaxSamplesPerChannel(sampling_rate);
var out_bytes = this.out_len * channels * 4;
this.out_ptr = native._malloc(out_bytes);
this.out_i16 = native.HEAP16.subarray(this.out_ptr >> 1, (this.out_ptr + out_bytes) >> 1);
this.out_f32 = native.HEAPF32.subarray(this.out_ptr >> 2, (this.out_ptr + out_bytes) >> 2);
}
OpusDecoder.prototype.decode = function (packet) {
this.in_buf.set(new Uint8Array(packet));
var ret = native._opus_decode(this.handle, this.in_ptr, packet.byteLength, this.out_ptr, this.out_len, 0);
if (ret < 0)
throw 'opus_decode failed: ' + ret;
var samples = new Int16Array(ret * this.channels);
samples.set(this.out_i16.subarray(0, samples.length));
return samples;
};
OpusDecoder.prototype.decode_float = function (packet) {
this.in_buf.set(new Uint8Array(packet));
var ret = native._opus_decode_float(this.handle, this.in_ptr, packet.byteLength, this.out_ptr, this.out_len, 0);
if (ret < 0)
throw 'opus_decode failed: ' + ret;
var samples = new Float32Array(ret * this.channels);
samples.set(this.out_f32.subarray(0, samples.length));
return samples;
};
OpusDecoder.prototype.destroy = function () {
if (!this.handle)
return;
native._opus_decoder_destroy(this.handle);
native._free(this.in_ptr);
native._free(this.out_ptr);
this.handle = this.in_ptr = this.out_ptr = 0;
};
return OpusDecoder;
})();
module.exports.Opus = Opus;
module.exports.OpusApplication = OpusApplication;
module.exports.OpusEncoder = OpusEncoder;
module.exports.OpusDecoder = OpusDecoder;

152
node_modules/discordie/deps/nopus/opus-js/resampler.js generated vendored Normal file
View File

@ -0,0 +1,152 @@
///<reference path="d.ts/asm.d.ts" />
///<reference path="d.ts/libspeexdsp.d.ts" />
var native = require("./libopus_libspeexdsp");
var SpeexResampler = (function () {
function SpeexResampler(channels, in_rate, out_rate, bits_per_sample, is_float, quality) {
if (quality === void 0) { quality = 5; }
this.handle = 0;
this.in_ptr = 0;
this.out_ptr = 0;
this.in_capacity = 0;
this.in_len_ptr = 0;
this.out_len_ptr = 0;
this.channels = channels;
this.in_rate = in_rate;
this.out_rate = out_rate;
this.bits_per_sample = bits_per_sample;
var bytes = bits_per_sample / 8;
if (bits_per_sample % 8 != 0 || bytes < 1 || bytes > 4)
throw 'argument error: bits_per_sample = ' + bits_per_sample;
if (is_float && bits_per_sample != 32)
throw 'argument error: if is_float=true, bits_per_sample must be 32';
var err_ptr = native.allocate(4, 'i32', native.ALLOC_STACK);
this.handle = native._speex_resampler_init(channels, in_rate, out_rate, quality, err_ptr);
if (native.getValue(err_ptr, 'i32') != 0)
throw 'speex_resampler_init failed: ret=' + native.getValue(err_ptr, 'i32');
if (!is_float) {
if (bits_per_sample == 8)
this.copy_to_buf = this._from_i8;
else if (bits_per_sample == 16) {
this.copy_to_buf = this._from_i16;
}
else if (bits_per_sample == 24)
this.copy_to_buf = this._from_i24;
else if (bits_per_sample == 32)
this.copy_to_buf = this._from_i32;
}
else {
this.copy_to_buf = this._from_f32;
}
this.in_len_ptr = native._malloc(4);
this.out_len_ptr = native._malloc(4);
}
SpeexResampler.prototype.process = function (raw_input) {
if (!this.handle)
throw 'disposed object';
var samples = (raw_input.byteLength / (this.bits_per_sample / 8) / this.channels);
var outSamples = Math.ceil(samples * this.out_rate / this.in_rate);
var requireSize = samples * 4;
if (this.in_capacity < requireSize) {
if (this.in_ptr)
native._free(this.in_ptr);
if (this.out_ptr)
native._free(this.out_ptr);
this.in_ptr = native._malloc(requireSize);
this.out_ptr = native._malloc(outSamples * 4);
this.in_capacity = requireSize;
}
var results = [];
for (var ch = 0; ch < this.channels; ++ch) {
this.copy_to_buf(raw_input, ch, samples);
native.setValue(this.in_len_ptr, samples, 'i32');
native.setValue(this.out_len_ptr, outSamples, 'i32');
var ret = native._speex_resampler_process_float(this.handle, ch, this.in_ptr, this.in_len_ptr, this.out_ptr, this.out_len_ptr);
if (ret != 0)
throw 'speex_resampler_process_float failed: ' + ret;
var ret_samples = native.getValue(this.out_len_ptr, 'i32');
var ary = new Float32Array(ret_samples);
ary.set(native.HEAPF32.subarray(this.out_ptr >> 2, (this.out_ptr >> 2) + ret_samples));
results.push(ary);
}
return results;
};
SpeexResampler.prototype.process_interleaved = function (raw_input) {
if (!this.handle)
throw 'disposed object';
var samples = raw_input.byteLength / (this.bits_per_sample / 8);
var outSamples = Math.ceil(samples * this.out_rate / this.in_rate);
var requireSize = samples * 4;
if (this.in_capacity < requireSize) {
if (this.in_ptr)
native._free(this.in_ptr);
if (this.out_ptr)
native._free(this.out_ptr);
this.in_ptr = native._malloc(requireSize);
this.out_ptr = native._malloc(outSamples * 4);
this.in_capacity = requireSize;
}
this.copy_to_buf(raw_input, -1, samples);
native.setValue(this.in_len_ptr, samples / this.channels, 'i32');
native.setValue(this.out_len_ptr, outSamples / this.channels, 'i32');
var ret = native._speex_resampler_process_interleaved_float(this.handle, this.in_ptr, this.in_len_ptr, this.out_ptr, this.out_len_ptr);
if (ret != 0)
throw 'speex_resampler_process_interleaved_float failed: ' + ret;
var ret_samples = native.getValue(this.out_len_ptr, 'i32') * this.channels;
var result = new Float32Array(ret_samples);
result.set(native.HEAPF32.subarray(this.out_ptr >> 2, (this.out_ptr >> 2) + ret_samples));
return result;
};
SpeexResampler.prototype.destroy = function () {
if (!this.handle)
return;
native._speex_resampler_destroy(this.handle);
this.handle = 0;
native._free(this.in_len_ptr);
native._free(this.out_len_ptr);
if (this.in_ptr)
native._free(this.in_ptr);
if (this.out_ptr)
native._free(this.out_ptr);
this.in_len_ptr = this.out_len_ptr = this.in_ptr = this.out_ptr = 0;
};
SpeexResampler.prototype._from_i8 = function (raw_input, ch, samples) {
var input = new Int8Array(raw_input);
};
SpeexResampler.prototype._from_i16 = function (raw_input, ch, samples) {
var input = new Int16Array(raw_input);
var off = this.in_ptr >> 2;
if (ch >= 0) {
var tc = this.channels;
for (var i = 0; i < samples; ++i)
native.HEAPF32[off + i] = input[i * tc + ch] / 32768.0;
}
else {
for (var i = 0; i < samples; ++i)
native.HEAPF32[off + i] = input[i] / 32768.0;
}
};
SpeexResampler.prototype._from_i24 = function (raw_input, ch, samples) {
var input = new Uint8Array(raw_input);
};
SpeexResampler.prototype._from_i32 = function (raw_input, ch, samples) {
var input = new Int32Array(raw_input);
};
SpeexResampler.prototype._from_f32 = function (raw_input, ch, samples) {
var input = new Float32Array(raw_input);
var off = this.in_ptr >> 2;
if (ch >= 0) {
var tc = this.channels;
for (var i = 0; i < samples; ++i)
native.HEAPF32[off + i] = input[i * tc + ch];
}
else {
for (var i = 0; i < samples; ++i)
native.HEAPF32[off + i] = input[i];
}
};
return SpeexResampler;
})();
module.exports.SpeexResampler = SpeexResampler;

194
node_modules/discordie/examples/echo.js generated vendored Normal file
View File

@ -0,0 +1,194 @@
"use strict";
// voice echo bot
// records and sends audio using `proxy` mode (without decoding/encoding/saving anything)
// variable `var recordTime = 10;` controls duration of recording in seconds
// commands:
// ~!~echo - joins voice channel and records audio, then plays recorded audio back
// ~!~stop - stops recording/playing
var fs = require('fs');
var Discordie;
try { Discordie = require("../"); } catch(e) {}
try { Discordie = require("discordie"); } catch(e) {}
var client = new Discordie({autoReconnect: true});
var auth = { token: "<BOT-TOKEN>" };
try { auth = require("./auth"); } catch(e) {}
client.connect(auth);
var recordTime = 10;
var recordTimer = null;
var recordReply = null;
var recordStartTime = null;
var recordedData = [];
var recordingUser = null;
client.Dispatcher.on("MESSAGE_CREATE", e => {
console.log("new message: ");
console.log(JSON.stringify(e.message, null, " "));
console.log("e.message.content: " + e.message.content);
const content = e.message.content;
const guild = e.message.channel.guild;
if (content == "~!~echo") {
if (recordingUser)
return e.message.reply("Already recording");
const channelToJoin = e.message.author.getVoiceChannel(guild);
if (!channelToJoin)
return e.message.reply("Join a voice channel first");
channelToJoin.join().then(info => {
recordReply = e.message.reply.bind(e.message);
e.message.reply("Recording audio for " + recordTime + " seconds");
recordedData = [];
recordingUser = e.message.author.id;
recordStartTime = Date.now();
recordTimer = setTimeout(() => {
play(info);
e.message.reply("Playing");
}, recordTime * 1000);
});
}
if (content == "~!~stop") {
e.message.reply("Stopped");
stopPlaying = true;
recordingUser = false;
if (recordTimer) {
clearTimeout(recordTimer);
recordTimer = null;
}
}
});
client.Dispatcher.on("VOICE_CONNECTED", e => {
e.voiceConnection.getDecoder()
.onPacket = (packet) => {
const user = e.voiceConnection.ssrcToMember(packet.ssrc);
if (!user) return;
if (user.id != recordingUser) return;
packet.playbackTime = Date.now() - recordStartTime;
recordedData.push(packet);
const name = user ? user.username : "<unknown>";
console.log(
"recording " + packet.chunk.length +
" bytes from " + name +
" @ " + packet.timestamp
);
};
// set callback on `.onPacketDecoded` to enable decoding and
// have decoded audio in `packet.chunk`
});
var stopPlaying = false;
function play(info) {
stopPlaying = false;
var playbackStartTime = Date.now();
if (!client.VoiceConnections.length)
return console.log("Voice not connected");
if (!info) info = client.VoiceConnections[0];
var voiceConnection = info.voiceConnection;
var encoder = voiceConnection.getEncoder({ frameDuration: 20, proxy: true });
function sendPacket() {
var packet = recordedData[0];
if (!packet && recordReply) {
recordReply("Finished playing");
}
if (!packet || stopPlaying) {
recordingUser = null;
return;
}
var currentTime = (Date.now() - playbackStartTime);
var nextTime = packet.playbackTime - currentTime;
setTimeout(sendPacket, nextTime);
if (currentTime < nextTime) return;
recordedData.shift(packet);
var numSamples = opus_packet_get_samples_per_frame(packet.chunk);
console.log("playing ", packet.chunk.length, numSamples);
encoder.enqueue(packet.chunk, numSamples);
}
sendPacket();
}
client.Dispatcher.onAny((type, args) => {
console.log("\nevent "+type);
if (args.type == "READY" || args.type == "READY" ||
type == "GATEWAY_READY" || type == "ANY_GATEWAY_READY" ||
type == "GATEWAY_DISPATCH") {
return console.log("e " + (args.type || type));
}
console.log("args " + JSON.stringify(args));
});
// ===========================================================================
// Opus helper functions
// ===========================================================================
const Constants = {
OPUS_BAD_ARG: -1,
OPUS_INVALID_PACKET: -4
};
function opus_packet_get_samples_per_frame(packet, sampleRate) {
sampleRate = sampleRate || 48000;
let audiosize;
if (packet[0] & 0x80) {
audiosize = ((packet[0] >> 3) & 0x3);
audiosize = (sampleRate << audiosize) / 400;
} else if ((packet[0] & 0x60) == 0x60) {
audiosize = (packet[0] & 0x08) ? sampleRate / 50 : sampleRate / 100;
} else {
audiosize = ((packet[0] >> 3) & 0x3);
if (audiosize == 3) {
audiosize = sampleRate * 60 / 1000;
} else {
audiosize = (sampleRate << audiosize) / 100;
}
}
return audiosize;
}
function opus_packet_get_nb_frames(packet) {
var count;
if (packet.length < 1) return Constants.OPUS_BAD_ARG;
count = packet[0] & 0x3;
if (count == 0) return 1;
else if (count != 3) return 2;
else if (packet.length < 2) return Constants.OPUS_INVALID_PACKET;
else return packet[1] & 0x3F;
}
function opus_packet_get_nb_samples(packet, sampleRate)
{
sampleRate = sampleRate || 48000;
var count = opus_packet_get_nb_frames(packet);
if (count < 0) return count;
var samples = count * opus_packet_get_samples_per_frame(packet, sampleRate);
/* Can't have more than 120 ms */
if (samples * 25 > sampleRate * 3)
return Constants.OPUS_INVALID_PACKET;
return samples;
}

145
node_modules/discordie/examples/echoproxy.js generated vendored Normal file
View File

@ -0,0 +1,145 @@
"use strict";
// voice echo bot
// simple audio proxy - mirrors user's audio
// can be useful for redirecting audio into another channel/server through
// different bot instance
// commands:
// ~!~start - starts proxying audio for the invoker
// ~!~stop - stops proxying audio for the invoker
var fs = require('fs');
var Discordie;
try { Discordie = require("../"); } catch(e) {}
try { Discordie = require("discordie"); } catch(e) {}
var client = new Discordie({autoReconnect: true});
var auth = { token: "<BOT-TOKEN>" };
try { auth = require("./auth"); } catch(e) {}
client.connect(auth);
var proxyingUser = null;
client.Dispatcher.on("MESSAGE_CREATE", e => {
console.log("new message: ");
console.log(JSON.stringify(e.message, null, " "));
console.log("e.message.content: " + e.message.content);
const content = e.message.content;
const guild = e.message.channel.guild;
if (content == "~!~start") {
if (proxyingUser)
return e.message.reply("Already proxying");
const channelToJoin = e.message.author.getVoiceChannel(guild);
if (!channelToJoin)
return e.message.reply("Join a voice channel first");
channelToJoin.join().then(info => {
e.message.reply("Proxying audio for user " + e.message.author.username);
proxyingUser = e.message.author.id;
});
}
if (content == "~!~stop") {
e.message.reply("Stopped");
proxyingUser = null;
}
});
client.Dispatcher.on("VOICE_CONNECTED", e => {
var encoder = e.voiceConnection.getEncoder({ frameDuration: 20, proxy: true });
var decoder = e.voiceConnection.getDecoder();
decoder.onPacket = (packet) => {
if (!proxyingUser) return;
const user = e.voiceConnection.ssrcToMember(packet.ssrc);
if (!user) return;
if (user.id != proxyingUser) return;
const name = user ? user.username : "<unknown>";
console.log(
"proxying " + packet.chunk.length +
" bytes from " + name +
" @ " + packet.timestamp
);
var numSamples = opus_packet_get_samples_per_frame(packet.chunk);
encoder.enqueue(packet.chunk, numSamples);
};
// set callback on `.onPacketDecoded` to enable decoding and
// have decoded audio in `packet.chunk`
});
client.Dispatcher.onAny((type, args) => {
console.log("\nevent "+type);
if (args.type == "READY" || args.type == "READY" ||
type == "GATEWAY_READY" || type == "ANY_GATEWAY_READY" ||
type == "GATEWAY_DISPATCH") {
return console.log("e " + (args.type || type));
}
console.log("args " + JSON.stringify(args));
});
// ===========================================================================
// Opus helper functions
// ===========================================================================
const Constants = {
OPUS_BAD_ARG: -1,
OPUS_INVALID_PACKET: -4
};
function opus_packet_get_samples_per_frame(packet, sampleRate) {
sampleRate = sampleRate || 48000;
let audiosize;
if (packet[0] & 0x80) {
audiosize = ((packet[0] >> 3) & 0x3);
audiosize = (sampleRate << audiosize) / 400;
} else if ((packet[0] & 0x60) == 0x60) {
audiosize = (packet[0] & 0x08) ? sampleRate / 50 : sampleRate / 100;
} else {
audiosize = ((packet[0] >> 3) & 0x3);
if (audiosize == 3) {
audiosize = sampleRate * 60 / 1000;
} else {
audiosize = (sampleRate << audiosize) / 100;
}
}
return audiosize;
}
function opus_packet_get_nb_frames(packet) {
var count;
if (packet.length < 1) return Constants.OPUS_BAD_ARG;
count = packet[0] & 0x3;
if (count == 0) return 1;
else if (count != 3) return 2;
else if (packet.length < 2) return Constants.OPUS_INVALID_PACKET;
else return packet[1] & 0x3F;
}
function opus_packet_get_nb_samples(packet, sampleRate)
{
sampleRate = sampleRate || 48000;
var count = opus_packet_get_nb_frames(packet);
if (count < 0) return count;
var samples = count * opus_packet_get_samples_per_frame(packet, sampleRate);
/* Can't have more than 120 ms */
if (samples * 25 > sampleRate * 3)
return Constants.OPUS_INVALID_PACKET;
return samples;
}

145
node_modules/discordie/examples/encoderstream.js generated vendored Normal file
View File

@ -0,0 +1,145 @@
"use strict";
//// note: run "npm install lame" in this folder first
// audio example implemented using AudioEncoderStream
// audio decoding using "lame"
// commands:
// ping
// vjoin <channelname> -- joins matching channel for current guild
// vleave
// play -- plays test.mp3
// stop
var lame = require('lame');
var fs = require('fs');
var Discordie;
try { Discordie = require("../"); } catch(e) {}
try { Discordie = require("discordie"); } catch(e) {}
var client = new Discordie({autoReconnect: true});
var auth = { token: "<BOT-TOKEN>" };
try { auth = require("./auth"); } catch(e) {}
client.connect(auth);
client.Dispatcher.on("GATEWAY_READY", e => {
const guild = client.Guilds.getBy("name", "test");
if (!guild) return console.log("Guild not found");
const general = guild.voiceChannels.find(c => c.name == "General");
if (!general) return console.log("Channel not found");
return general.join(false, false);
});
client.Dispatcher.on("MESSAGE_CREATE", (e) => {
const content = e.message.content;
const channel = e.message.channel;
const guild = e.message.channel.guild;
if (content == "ping") {
channel.sendMessage("pong");
}
if (content == "vleave") {
client.Channels
.filter(channel => channel.isGuildVoice && channel.joined)
.forEach(channel => channel.leave());
}
if (content.indexOf("vjoin ") == 0) {
const targetChannel = content.replace("vjoin ", "");
var vchannel =
guild.voiceChannels
.find(channel => channel.name.toLowerCase().indexOf(targetChannel) >= 0);
if (vchannel) vchannel.join().then(info => play(info));
}
if (content.indexOf("play") == 0) {
if (!client.VoiceConnections.length) {
return e.message.reply("Not connected to any channel");
}
var info = client.VoiceConnections.getForGuild(guild);
if (info) play(info);
}
if (content.indexOf("stop") == 0) {
var info = client.VoiceConnections.getForGuild(guild);
if (info) {
var encoderStream = info.voiceConnection.getEncoderStream();
encoderStream.unpipeAll();
}
}
});
client.Dispatcher.on("VOICE_CONNECTED", e => {
// uncomment to play on join
//play();
});
function play(info) {
if (!client.VoiceConnections.length) {
return console.log("Voice not connected");
}
if (!info) info = client.VoiceConnections[0];
var mp3decoder = new lame.Decoder();
var file = fs.createReadStream("test.mp3");
file.pipe(mp3decoder);
mp3decoder.on('format', pcmfmt => {
// note: discordie encoder does resampling if rate != 48000
var options = {
frameDuration: 60,
sampleRate: pcmfmt.sampleRate,
channels: pcmfmt.channels,
float: false
};
var encoderStream = info.voiceConnection.getEncoderStream(options);
if (!encoderStream) {
return console.log(
"Unable to get encoder stream, connection is disposed"
);
}
// Stream instance is persistent until voice connection is disposed;
// you can register timestamp listener once when connection is initialized
// or access timestamp with `encoderStream.timestamp`
encoderStream.resetTimestamp();
encoderStream.removeAllListeners("timestamp");
encoderStream.on("timestamp", time => console.log("Time " + time));
// only 1 stream at a time can be piped into AudioEncoderStream
// previous stream will automatically unpipe
mp3decoder.pipe(encoderStream);
mp3decoder.once('end', () => play(info));
// must be registered after `pipe()`
encoderStream.once("unpipe", () => file.destroy());
});
}
client.Dispatcher.onAny((type, e) => {
var ignore = [
"READY",
"GATEWAY_READY",
"ANY_GATEWAY_READY",
"GATEWAY_DISPATCH",
"PRESENCE_UPDATE",
"TYPING_START",
];
if (ignore.find(t => (t == type || t == e.type))) {
return console.log("<" + type + ">");
}
console.log("\nevent " + type);
return console.log("args " + JSON.stringify(e));
});

120
node_modules/discordie/examples/ffmpegencoder.js generated vendored Normal file
View File

@ -0,0 +1,120 @@
"use strict";
//// note: install ffmpeg/avconv first
// audio decoding and encoding using built in ffmpeg wrappers
// (opus encoding is done entierly by ffmpeg)
// commands:
// ping
// vjoin <channelname> -- joins matching channel for current guild
// vleave
// play -- plays test.mp3
// stop
var fs = require('fs');
var Discordie;
try { Discordie = require("../"); } catch(e) {}
try { Discordie = require("discordie"); } catch(e) {}
var client = new Discordie({autoReconnect: true});
var auth = { token: "<BOT-TOKEN>" };
try { auth = require("./auth"); } catch(e) {}
client.connect(auth);
client.Dispatcher.on("GATEWAY_READY", e => {
const guild = client.Guilds.getBy("name", "test");
if (!guild) return console.log("Guild not found");
const general = guild.voiceChannels.find(c => c.name == "General");
if (!general) return console.log("Channel not found");
return general.join(false, false);
});
client.Dispatcher.on("MESSAGE_CREATE", (e) => {
const content = e.message.content;
const channel = e.message.channel;
const guild = e.message.channel.guild;
if (content == "ping") {
channel.sendMessage("pong");
}
if (content == "vleave") {
client.Channels
.filter(channel => channel.isGuildVoice && channel.joined)
.forEach(channel => channel.leave());
}
if (content.indexOf("vjoin ") == 0) {
const targetChannel = content.replace("vjoin ", "");
var vchannel =
guild.voiceChannels
.find(channel => channel.name.toLowerCase().indexOf(targetChannel) >= 0);
if (vchannel) vchannel.join().then(info => play(info));
}
if (content.indexOf("play") == 0) {
if (!client.VoiceConnections.length) {
return e.message.reply("Not connected to any channel");
}
var info = client.VoiceConnections.getForGuild(guild);
if (info) play(info);
}
if (content.indexOf("stop") == 0) {
var info = client.VoiceConnections.getForGuild(guild);
if (info) {
var encoderStream = info.voiceConnection.getEncoderStream();
encoderStream.unpipeAll();
}
}
});
client.Dispatcher.on("VOICE_CONNECTED", e => {
// uncomment to play on join
// play();
});
function play(info) {
if (!client.VoiceConnections.length) {
return console.log("Voice not connected");
}
if (!info) info = client.VoiceConnections[0];
var encoder = info.voiceConnection.createExternalEncoder({
type: "ffmpeg",
source: "test.mp3"
});
if (!encoder) return console.log("Voice connection is no longer valid");
encoder.once("end", () => play(info));
var encoderStream = encoder.play();
encoderStream.resetTimestamp();
encoderStream.removeAllListeners("timestamp");
encoderStream.on("timestamp", time => console.log("Time " + time));
}
client.Dispatcher.onAny((type, e) => {
var ignore = [
"READY",
"GATEWAY_READY",
"ANY_GATEWAY_READY",
"GATEWAY_DISPATCH",
"PRESENCE_UPDATE",
"TYPING_START",
];
if (ignore.find(t => (t == type || t == e.type))) {
return console.log("<" + type + ">");
}
console.log("\nevent " + type);
return console.log("args " + JSON.stringify(e));
});

View File

@ -0,0 +1,193 @@
"use strict";
//// note: run "npm install lame" in this folder first
// example bot
// audio decoding using "lame"
// commands:
// ping
// vjoin <channelname> -- joins matching channel for current guild
// vleave
// play -- plays test.mp3
// stop
var lame = require('lame');
var fs = require('fs');
var Discordie;
try { Discordie = require("../../"); } catch(e) {}
try { Discordie = require("discordie"); } catch(e) {}
var client = new Discordie({autoReconnect: true});
var auth = { token: "<BOT-TOKEN>" };
try { auth = require("./auth"); } catch(e) {}
function connect() { client.connect(auth); }
connect();
client.Dispatcher.on(Discordie.Events.GATEWAY_READY, (e) => {
// client.Users
// client.Channels
// client.DirectMessageChannels
// client.Guilds
// client.Messages
// are collection interfaces with filter(fn), get(id), getBy(k, v)
// client.User
// contains current user
const guild = client.Guilds.getBy("name", "test");
// or:
// client.Guilds.filter(g => (g.name == "test"))[0];
// e.data contains raw READY event
if (guild) {
// guild.voiceChannels returns an array
const general = guild.voiceChannels.filter(c => c.name == "General")[0];
// IVoiceChannel.join(selfMute, selfDeaf)
if (general)
return general.join(false, false);
return console.log("Channel not found");
}
console.log("Guild not found");
});
client.Dispatcher.on(Discordie.Events.MESSAGE_CREATE, (e) => {
console.log("new message: ");
console.log(JSON.stringify(e.message, null, " "));
console.log("e.message.content: " + e.message.content);
if(e.message.content == "ping") {
e.message.channel.sendMessage("pong");
// e.message.reply("pong")
// will prefix the message with a mention
}
if(e.message.content == "vleave") {
var c = e.message.channel;
client.Channels
.filter(channel => channel.isGuildVoice && channel.joined)
.forEach(channel => channel.leave());
}
if(e.message.content.indexOf("vjoin ") == 0) {
const targetChannel = e.message.content.replace("vjoin ", "");
e.message.channel.guild.voiceChannels
.forEach(channel => {
if(channel.name.toLowerCase().indexOf(targetChannel) >= 0)
channel.join().then(v => play(v));
// channel.join() returns a promise with voiceConnectionInfo
});
}
if(e.message.content.indexOf("play") == 0) {
if(!client.VoiceConnections.length) {
return e.message.reply("Not connected to any channel");
}
play();
}
if(e.message.content.indexOf("stop") == 0) {
stopPlaying = true;
}
});
client.Dispatcher.on(Discordie.Events.MESSAGE_UPDATE, (e) => {
console.log("updated message: ");
console.log(JSON.stringify(e.message));
});
client.Dispatcher.on(Discordie.Events.MESSAGE_DELETE, (e) => {
console.log("deleted message: ");
console.log(JSON.stringify(e.message));
// e.message now has 'e.message.deleted' set to true
// properties in e.message will be null if the message is not cached
});
client.Dispatcher.on(Discordie.Events.VOICE_CONNECTED, (data) => {
if(client.VoiceConnections.length <= 0) {
return console.log("Voice not connected");
}
// uncomment to play on join
//play();
});
var stopPlaying = false;
function play(voiceConnectionInfo) {
stopPlaying = false;
var mp3decoder = new lame.Decoder();
mp3decoder.on('format', decode);
fs.createReadStream("test.mp3").pipe(mp3decoder);
function decode(pcmfmt) {
// note: discordie encoder does resampling if rate != 48000
var options = {
frameDuration: 60,
sampleRate: pcmfmt.sampleRate,
channels: pcmfmt.channels,
float: false
};
const frameDuration = 60;
var readSize =
pcmfmt.sampleRate / 1000 *
options.frameDuration *
pcmfmt.bitDepth / 8 *
pcmfmt.channels;
mp3decoder.once('readable', function() {
if(!client.VoiceConnections.length) {
return console.log("Voice not connected");
}
if(!voiceConnectionInfo) {
// get first if not specified
voiceConnectionInfo = client.VoiceConnections[0];
}
var voiceConnection = voiceConnectionInfo.voiceConnection;
// one encoder per voice connection
var encoder = voiceConnection.getEncoder(options);
const needBuffer = () => encoder.onNeedBuffer();
encoder.onNeedBuffer = function() {
var chunk = mp3decoder.read(readSize);
if (stopPlaying) return;
// delay the packet if no data buffered
if (!chunk) return setTimeout(needBuffer, options.frameDuration);
var sampleCount = readSize / pcmfmt.channels / (pcmfmt.bitDepth / 8);
encoder.enqueue(chunk, sampleCount);
};
needBuffer();
});
mp3decoder.once('end', () => setTimeout(play, 100, voiceConnectionInfo));
}
}
client.Dispatcher.onAny((type, e) => {
var ignore = [
"READY",
"GATEWAY_READY",
"ANY_GATEWAY_READY",
"GATEWAY_DISPATCH",
"PRESENCE_UPDATE",
"TYPING_START",
];
if(ignore.find(t => (t == type || t == e.type))) {
return console.log("<" + type + ">");
}
console.log("\nevent " + type);
return console.log("args " + JSON.stringify(e));
});

View File

@ -0,0 +1,238 @@
"use strict";
//// note: install ffmpeg/avconv first
// example bot
// commands:
// ping
// vjoin <channelname> -- joins matching channel for current guild
// vleave
// play -- plays test.mp3
// stop
var fs = require('fs');
var path = require('path');
var child_process = require('child_process');
var Discordie;
try { Discordie = require("../../"); } catch(e) {}
try { Discordie = require("discordie"); } catch(e) {}
var client = new Discordie({autoReconnect: true});
var auth = { token: "<BOT-TOKEN>" };
try { auth = require("./auth"); } catch(e) {}
function connect() { client.connect(auth); }
connect();
client.Dispatcher.on(Discordie.Events.GATEWAY_READY, (e) => {
// client.Users
// client.Channels
// client.DirectMessageChannels
// client.Guilds
// client.Messages
// are collection interfaces with filter(fn), get(id), getBy(k, v)
// client.User
// contains current user
const guild = client.Guilds.getBy("name", "test");
// or:
// client.Guilds.filter(g => (g.name == "test"))[0];
// e.data contains raw READY event
if (guild) {
// guild.voiceChannels returns an array
const general = guild.voiceChannels.filter(c => c.name == "General")[0];
// IVoiceChannel.join(selfMute, selfDeaf)
if (general)
return general.join(false, false);
return console.log("Channel not found");
}
console.log("Guild not found");
});
client.Dispatcher.on(Discordie.Events.MESSAGE_CREATE, (e) => {
console.log("new message: ");
console.log(JSON.stringify(e.message, null, " "));
console.log("e.message.content: " + e.message.content);
if(e.message.content == "ping") {
e.message.channel.sendMessage("pong");
// e.message.reply("pong")
// will prefix the message with a mention
}
if(e.message.content == "vleave") {
var c = e.message.channel;
client.Channels
.filter(channel => channel.isGuildVoice && channel.joined)
.forEach(channel => channel.leave());
}
if(e.message.content.indexOf("vjoin ") == 0) {
const targetChannel = e.message.content.replace("vjoin ", "");
e.message.channel.guild.voiceChannels
.forEach(channel => {
if(channel.name.toLowerCase().indexOf(targetChannel) >= 0)
channel.join().then(v => play(v));
// channel.join() returns a promise with voiceConnectionInfo
});
}
if(e.message.content.indexOf("play") == 0) {
if(!client.VoiceConnections.length) {
return e.message.reply("Not connected to any channel");
}
play();
}
if(e.message.content.indexOf("stop") == 0) {
stop();
}
});
client.Dispatcher.on(Discordie.Events.MESSAGE_UPDATE, (e) => {
console.log("updated message: ");
console.log(JSON.stringify(e.message));
});
client.Dispatcher.on(Discordie.Events.MESSAGE_DELETE, (e) => {
console.log("deleted message: ");
console.log(JSON.stringify(e.message));
// e.message now has 'e.message.deleted' set to true
// properties in e.message will be null if the message is not cached
});
client.Dispatcher.on(Discordie.Events.VOICE_CONNECTED, (data) => {
if(client.VoiceConnections.length <= 0) {
return console.log("Voice not connected");
}
// uncomment to play on join
//play();
});
function getConverter(args, options) {
var binaries = [
'ffmpeg',
'ffmpeg.exe',
'avconv',
'avconv.exe'
];
var paths = process.env.PATH.split(path.delimiter).concat(["."]);
for (var name of binaries) {
for (var p of paths) {
var binary = p + path.sep + name;
if (!fs.existsSync(binary)) continue;
return child_process.spawn(name, args, options);
}
}
return null;
}
var ffmpeg = null;
function stop() {
stopPlaying = true;
if (!ffmpeg) return;
ffmpeg.kill();
ffmpeg = null;
}
var stopPlaying = false;
function play(voiceConnectionInfo) {
stopPlaying = false;
var sampleRate = 48000;
var bitDepth = 16;
var channels = 2;
if (ffmpeg) ffmpeg.kill();
ffmpeg = getConverter([
"-re",
"-i", "test.mp3",
"-f", "s16le",
"-ar", sampleRate,
"-ac", channels,
"-"
], {stdio: ['pipe', 'pipe', 'ignore']});
if (!ffmpeg) return console.log("ffmpeg/avconv not found");
var _ffmpeg = ffmpeg;
var ff = ffmpeg.stdout;
// note: discordie encoder does resampling if rate != 48000
var options = {
frameDuration: 60,
sampleRate: sampleRate,
channels: channels,
float: false
};
const frameDuration = 60;
var readSize =
sampleRate / 1000 *
options.frameDuration *
bitDepth / 8 *
channels;
ff.once('readable', function() {
if(!client.VoiceConnections.length) {
return console.log("Voice not connected");
}
if(!voiceConnectionInfo) {
// get first if not specified
voiceConnectionInfo = client.VoiceConnections[0];
}
var voiceConnection = voiceConnectionInfo.voiceConnection;
// one encoder per voice connection
var encoder = voiceConnection.getEncoder(options);
const needBuffer = () => encoder.onNeedBuffer();
encoder.onNeedBuffer = function() {
var chunk = ff.read(readSize);
if (_ffmpeg.killed) return;
if (stopPlaying) return stop();
// delay the packet if no data buffered
if (!chunk) return setTimeout(needBuffer, options.frameDuration);
var sampleCount = readSize / channels / (bitDepth / 8);
encoder.enqueue(chunk, sampleCount);
};
needBuffer();
});
ff.once('end', () => {
if (stopPlaying) return;
setTimeout(play, 100, voiceConnectionInfo);
});
}
client.Dispatcher.onAny((type, e) => {
var ignore = [
"READY",
"GATEWAY_READY",
"ANY_GATEWAY_READY",
"GATEWAY_DISPATCH",
"PRESENCE_UPDATE",
"TYPING_START",
];
if(ignore.find(t => (t == type || t == e.type))) {
return console.log("<" + type + ">");
}
console.log("\nevent " + type);
return console.log("args " + JSON.stringify(e));
});

227
node_modules/discordie/examples/permissions.js generated vendored Normal file
View File

@ -0,0 +1,227 @@
"use strict";
// example bot
// commands:
// ping
// do - creates a role Testing, assigns it to the member, adds member permission for channel
// undo
var Discordie;
try { Discordie = require("../"); } catch(e) {}
try { Discordie = require("discordie"); } catch(e) {}
var client = new Discordie({autoReconnect: true});
var Events = Discordie.Events;
var auth = { token: "<BOT-TOKEN>" };
try { auth = require("./auth"); } catch(e) {}
client.connect(auth);
client.Dispatcher.on("GATEWAY_READY", e => {
console.log("Ready");
});
client.Dispatcher.on("MESSAGE_CREATE", e => {
console.log("new message: ");
console.log(JSON.stringify(e.message, null, " "));
console.log("e.message.content: " + e.message.content);
var content = e.message.content;
if (content === "ping") channel.sendMessage("pong");
if (content === "do") doCommand(e);
if (content === "undo") undoCommand(e);
});
function onError(e) {
if (!e) return console.error("Unknown error");
if(e.response && e.response.error)
return console.error(e.response.error);
console.error(e.toString());
}
function doCommand(e) {
var guild = e.message.guild;
var channel = e.message.channel;
var member = e.message.member;
// chain of actions
console.log(" >> creating role");
guild.createRole()
.then(assignRole)
.catch(onError);
function assignRole(role) {
console.log(" >> assigning role");
member.assignRole(role)
.then(() => setRolePermissions(role))
.catch(onError);
}
function setRolePermissions(role) {
console.log(" >> setting role permissions");
/*
List of role permissions:
General: {
CREATE_INSTANT_INVITE,
KICK_MEMBERS,
BAN_MEMBERS,
ADMINISTRATOR,
MANAGE_CHANNELS,
MANAGE_GUILD,
CHANGE_NICKNAME,
MANAGE_NICKNAMES,
MANAGE_ROLES,
MANAGE_WEBHOOKS,
MANAGE_EMOJIS,
},
Text: {
READ_MESSAGES,
SEND_MESSAGES,
SEND_TTS_MESSAGES,
MANAGE_MESSAGES,
EMBED_LINKS,
ATTACH_FILES,
READ_MESSAGE_HISTORY,
MENTION_EVERYONE,
EXTERNAL_EMOTES,
ADD_REACTIONS,
},
Voice: {
CONNECT,
SPEAK,
MUTE_MEMBERS,
DEAFEN_MEMBERS,
MOVE_MEMBERS,
USE_VAD,
}
*/
var perms = role.permissions;
perms.General.KICK_MEMBERS = true;
perms.General.BAN_MEMBERS = true;
perms.Text.MENTION_EVERYONE = true;
// 'role.permissions' object resets on error
var newRoleName = "Testing";
var color = 0xE74C3C; // RED
var hoist = true; // show separate group
role.commit(newRoleName, color, hoist)
.then(createChannelPermissions)
.catch(onError);
}
function createChannelPermissions() {
console.log(" >> creating channel permissions");
channel.createPermissionOverwrite(member)
.then(setChannelPermissions)
.catch(onError);
}
function setChannelPermissions(overwrite) {
console.log(" >> setting channel permissions");
var allow = overwrite.allow;
var deny = overwrite.deny;
/*
List of channel permissions:
General: {
CREATE_INSTANT_INVITE,
MANAGE_CHANNEL,
MANAGE_PERMISSIONS
},
Text: {
READ_MESSAGES,
SEND_MESSAGES,
SEND_TTS_MESSAGES,
MANAGE_MESSAGES,
EMBED_LINKS,
ATTACH_FILES,
READ_MESSAGE_HISTORY,
MENTION_EVERYONE,
},
Voice: {
CONNECT,
SPEAK,
MUTE_MEMBERS,
DEAFEN_MEMBERS,
MOVE_MEMBERS,
USE_VAD,
}
*/
// .Text only exists for text channels
// .Voice only exists for voice channels
// .General exists for both
allow.General.MANAGE_CHANNEL = true;
allow.General.MANAGE_PERMISSIONS = true;
overwrite.commit()
.then((overwrite) => console.log(" >> finished"))
.catch(onError);
}
}
function undoCommand(e) {
var channel = e.message.channel;
var member = e.message.member;
removeOverwrite();
function removeOverwrite() {
console.log(" >> removing overwrite");
var overwrites = channel.permission_overwrites;
var overwrite = overwrites.find(o => (o.id == member.id));
if (!overwrite) {
console.log(" >> no overwrite");
unassignRoleTesting();
return;
}
overwrite.delete()
.then(unassignRoleTesting)
.catch(onError);
}
function unassignRoleTesting() {
console.log(" >> unassigning role");
var role = member.roles.find(r => r.name == "Testing");
if (!role) {
console.log(" >> no role, finished");
return;
}
member.unassignRole(role)
.then(() => deleteRoleTesting(role))
.catch(onError);
}
function deleteRoleTesting(role) {
console.log(" >> unassigning role");
role.delete()
.then((overwrite) => console.log(" >> finished"))
.catch(onError);
}
}
client.Dispatcher.onAny((type, e) => {
var ignore = [
"READY",
"GATEWAY_READY",
"ANY_GATEWAY_READY",
"GATEWAY_DISPATCH",
"PRESENCE_UPDATE",
"TYPING_START",
];
if(ignore.find(t => (t == type || t == e.type))) {
return console.log("<" + type + ">");
}
console.log("\nevent " + type);
return console.log("args " + JSON.stringify(e));
});

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

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

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

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

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

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

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

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

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

View File

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

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

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

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

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

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

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

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

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

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

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

View File

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

View File

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

View File

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

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

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

View File

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

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

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

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

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

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

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

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

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

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

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

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

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

View File

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

View File

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

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

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

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

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

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

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

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

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

View File

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

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

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

View File

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

View File

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

View File

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

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

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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