├── .gitignore ├── LICENSE ├── changelog.md ├── deps ├── cas │ ├── addtrustexternalcaroot.crt │ ├── comodorsaaddtrustca.crt │ └── comodorsadomainvalidationsecureserverca.crt └── nopus │ ├── index.js │ └── opus-js │ ├── copying.libopus │ ├── copying.libspeex │ ├── copying.wrapper │ ├── libopus_libspeexdsp.js │ ├── opus.js │ └── resampler.js ├── examples ├── echo.js ├── echoproxy.js ├── encoderstream.js ├── ffmpegencoder.js ├── lowlevelaudio │ ├── massive.js │ └── massive2.js └── permissions.js ├── lib ├── Constants.js ├── collections │ ├── BaseArrayCollection.js │ ├── BaseCollection.js │ ├── CallCollection.js │ ├── ChannelCollection.js │ ├── GuildCollection.js │ ├── GuildMemberCollection.js │ ├── GuildSyncCollection.js │ ├── MessageCollection.js │ ├── PresenceCollection.js │ ├── UnavailableGuildCollection.js │ ├── UserCollection.js │ ├── VoiceConnectionCollection.js │ └── VoiceStateCollection.js ├── core │ ├── ApiRequest.js │ ├── Backoff.js │ ├── DiscordieDispatcher.js │ ├── DiscordieError.js │ ├── DiscordieProfiler.js │ ├── GatewayReconnectHandler.js │ ├── LimitedCache.js │ ├── MessageHandlerCache.js │ ├── ReadyEventScheduler.js │ ├── Utils.js │ └── ratelimiting │ │ ├── Bucket.js │ │ ├── ChainedBucket.js │ │ ├── RequestQueue.js │ │ └── RequestQueueManager.js ├── index.js ├── interfaces │ ├── IAuthenticatedUser.js │ ├── IBase.js │ ├── ICall.js │ ├── IChannel.js │ ├── IChannelCollection.js │ ├── ICollectionBase.js │ ├── IDirectMessageChannel.js │ ├── IDirectMessageChannelCollection.js │ ├── IGuild.js │ ├── IGuildCollection.js │ ├── IGuildMember.js │ ├── IInviteManager.js │ ├── IMessage.js │ ├── IMessageCollection.js │ ├── IPermissionOverwrite.js │ ├── IPermissions.js │ ├── IRole.js │ ├── ITextChannel.js │ ├── IUser.js │ ├── IUserCollection.js │ ├── IVoiceChannel.js │ ├── IVoiceConnection.js │ └── IWebhookManager.js ├── models │ ├── AuthenticatedUser.js │ ├── BaseModel.js │ ├── Call.js │ ├── Channel.js │ ├── Guild.js │ ├── GuildMember.js │ ├── Message.js │ ├── PermissionOverwrite.js │ ├── Role.js │ └── User.js ├── networking │ ├── messages │ │ ├── MessageValidator.js │ │ ├── gateway │ │ │ ├── call_create.js │ │ │ ├── call_delete.js │ │ │ ├── call_update.js │ │ │ ├── channel_create.js │ │ │ ├── channel_delete.js │ │ │ ├── channel_recipient_add.js │ │ │ ├── channel_recipient_remove.js │ │ │ ├── channel_update.js │ │ │ ├── guild_ban_add.js │ │ │ ├── guild_ban_remove.js │ │ │ ├── guild_create.js │ │ │ ├── guild_delete.js │ │ │ ├── guild_emojis_update.js │ │ │ ├── guild_member_add.js │ │ │ ├── guild_member_remove.js │ │ │ ├── guild_member_update.js │ │ │ ├── guild_role_create.js │ │ │ ├── guild_role_delete.js │ │ │ ├── guild_role_update.js │ │ │ ├── guild_update.js │ │ │ ├── message_create.js │ │ │ ├── message_delete.js │ │ │ ├── message_delete_bulk.js │ │ │ ├── message_reaction_add.js │ │ │ ├── message_reaction_remove.js │ │ │ ├── message_reaction_remove_all.js │ │ │ ├── message_update.js │ │ │ ├── presence_update.js │ │ │ ├── ready.js │ │ │ ├── resumed.js │ │ │ ├── typing_start.js │ │ │ ├── user_update.js │ │ │ ├── voice_server_update.js │ │ │ ├── voice_state_update.js │ │ │ └── webhooks_update.js │ │ └── voice │ │ │ ├── ready.js │ │ │ ├── session_description.js │ │ │ └── speaking.js │ ├── rest │ │ ├── auth │ │ │ ├── index.js │ │ │ └── login.js │ │ ├── channels │ │ │ ├── addReaction.js │ │ │ ├── batchPatchChannels.js │ │ │ ├── calls │ │ │ │ ├── changeRegion.js │ │ │ │ ├── index.js │ │ │ │ ├── ring.js │ │ │ │ └── stopRinging.js │ │ │ ├── createChannel.js │ │ │ ├── createMessage.js │ │ │ ├── deleteChannel.js │ │ │ ├── deleteMessage.js │ │ │ ├── deleteMessages.js │ │ │ ├── deletePermissionOverwrite.js │ │ │ ├── deleteReactions.js │ │ │ ├── dm │ │ │ │ ├── addRecipient.js │ │ │ │ ├── index.js │ │ │ │ ├── removeRecipient.js │ │ │ │ ├── setIcon.js │ │ │ │ └── setName.js │ │ │ ├── getInvites.js │ │ │ ├── getMessages.js │ │ │ ├── getPinnedMessages.js │ │ │ ├── getReactions.js │ │ │ ├── index.js │ │ │ ├── patchChannel.js │ │ │ ├── patchMessage.js │ │ │ ├── pinMessage.js │ │ │ ├── postTyping.js │ │ │ ├── putPermissionOverwrite.js │ │ │ ├── removeReaction.js │ │ │ ├── unpinMessage.js │ │ │ └── uploadFile.js │ │ ├── gateway.js │ │ ├── guilds │ │ │ ├── bans │ │ │ │ ├── banMember.js │ │ │ │ ├── getBans.js │ │ │ │ ├── index.js │ │ │ │ └── unbanMember.js │ │ │ ├── createGuild.js │ │ │ ├── deleteGuild.js │ │ │ ├── emoji │ │ │ │ ├── deleteEmoji.js │ │ │ │ ├── getEmoji.js │ │ │ │ ├── index.js │ │ │ │ ├── patchEmoji.js │ │ │ │ └── postEmoji.js │ │ │ ├── getEmbed.js │ │ │ ├── getInvites.js │ │ │ ├── index.js │ │ │ ├── leaveGuild.js │ │ │ ├── members │ │ │ │ ├── index.js │ │ │ │ ├── kickMember.js │ │ │ │ ├── setChannel.js │ │ │ │ ├── setDeaf.js │ │ │ │ ├── setMute.js │ │ │ │ ├── setNickname.js │ │ │ │ └── setRoles.js │ │ │ ├── patchEmbed.js │ │ │ ├── patchGuild.js │ │ │ ├── prune │ │ │ │ ├── getPrune.js │ │ │ │ ├── index.js │ │ │ │ └── postPrune.js │ │ │ ├── roles │ │ │ │ ├── batchPatchRoles.js │ │ │ │ ├── createRole.js │ │ │ │ ├── deleteRole.js │ │ │ │ ├── index.js │ │ │ │ └── patchRole.js │ │ │ └── transferOwnership.js │ │ ├── index.js │ │ ├── invites │ │ │ ├── createInvite.js │ │ │ ├── deleteInvite.js │ │ │ ├── getInvite.js │ │ │ ├── index.js │ │ │ └── postInvite.js │ │ ├── oauth2 │ │ │ ├── getApplication.js │ │ │ └── index.js │ │ ├── users │ │ │ ├── createDirectMessageChannel.js │ │ │ ├── index.js │ │ │ └── me.js │ │ ├── voice │ │ │ ├── getRegions.js │ │ │ └── index.js │ │ └── webhooks │ │ │ ├── createWebhook.js │ │ │ ├── deleteWebhook.js │ │ │ ├── executeSlackWebhook.js │ │ │ ├── executeWebhook.js │ │ │ ├── getChannelWebhooks.js │ │ │ ├── getGuildWebhooks.js │ │ │ ├── getWebhook.js │ │ │ ├── index.js │ │ │ └── patchWebhook.js │ ├── voicetransports │ │ ├── VoiceTransportBase.js │ │ └── VoiceUDP.js │ └── ws │ │ ├── BaseSocket.js │ │ ├── BrowserWebSocket.js │ │ ├── GatewaySocket.js │ │ └── VoiceSocket.js └── voice │ ├── AudioDecoder.js │ ├── AudioEncoder.js │ ├── AudioResampler.js │ ├── players │ ├── ExternalEncoderBase.js │ ├── ExternalEncoderFactory.js │ ├── FFmpegEncoder.js │ ├── OggOpusPlayer.js │ ├── WebmOpusPlayer.js │ └── demuxers │ │ ├── BufferStream.js │ │ ├── EBMLDecoder.js │ │ ├── OggOpusDemuxer.js │ │ └── WebmOpusDemuxer.js │ ├── streams │ ├── AudioEncoderStream.js │ └── OpusUtils.js │ └── threading │ ├── DecoderWorker.js │ └── EncoderWorker.js ├── package.json └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | .eslintrc 2 | logs 3 | *.log 4 | npm-debug.log* 5 | pids 6 | *.pid 7 | *.seed 8 | .idea 9 | .grunt 10 | .lock-wscript 11 | build/Release 12 | node_modules 13 | examples/auth.js 14 | examples/test.mp3 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015-2016, qeled 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | -------------------------------------------------------------------------------- /deps/cas/addtrustexternalcaroot.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU 3 | MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs 4 | IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290 5 | MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux 6 | FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h 7 | bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v 8 | dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt 9 | H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9 10 | uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX 11 | mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX 12 | a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN 13 | E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0 14 | WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD 15 | VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0 16 | Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU 17 | cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx 18 | IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN 19 | AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH 20 | YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5 21 | 6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC 22 | Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX 23 | c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a 24 | mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ= 25 | -----END CERTIFICATE----- 26 | -------------------------------------------------------------------------------- /deps/cas/comodorsaaddtrustca.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFdDCCBFygAwIBAgIQJ2buVutJ846r13Ci/ITeIjANBgkqhkiG9w0BAQwFADBv 3 | MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk 4 | ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF 5 | eHRlcm5hbCBDQSBSb290MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFow 6 | gYUxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO 7 | BgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9ETyBDQSBMaW1pdGVkMSswKQYD 8 | VQQDEyJDT01PRE8gUlNBIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkq 9 | hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAkehUktIKVrGsDSTdxc9EZ3SZKzejfSNw 10 | AHG8U9/E+ioSj0t/EFa9n3Byt2F/yUsPF6c947AEYe7/EZfH9IY+Cvo+XPmT5jR6 11 | 2RRr55yzhaCCenavcZDX7P0N+pxs+t+wgvQUfvm+xKYvT3+Zf7X8Z0NyvQwA1onr 12 | ayzT7Y+YHBSrfuXjbvzYqOSSJNpDa2K4Vf3qwbxstovzDo2a5JtsaZn4eEgwRdWt 13 | 4Q08RWD8MpZRJ7xnw8outmvqRsfHIKCxH2XeSAi6pE6p8oNGN4Tr6MyBSENnTnIq 14 | m1y9TBsoilwie7SrmNnu4FGDwwlGTm0+mfqVF9p8M1dBPI1R7Qu2XK8sYxrfV8g/ 15 | vOldxJuvRZnio1oktLqpVj3Pb6r/SVi+8Kj/9Lit6Tf7urj0Czr56ENCHonYhMsT 16 | 8dm74YlguIwoVqwUHZwK53Hrzw7dPamWoUi9PPevtQ0iTMARgexWO/bTouJbt7IE 17 | IlKVgJNp6I5MZfGRAy1wdALqi2cVKWlSArvX31BqVUa/oKMoYX9w0MOiqiwhqkfO 18 | KJwGRXa/ghgntNWutMtQ5mv0TIZxMOmm3xaG4Nj/QN370EKIf6MzOi5cHkERgWPO 19 | GHFrK+ymircxXDpqR+DDeVnWIBqv8mqYqnK8V0rSS527EPywTEHl7R09XiidnMy/ 20 | s1Hap0flhFMCAwEAAaOB9DCB8TAfBgNVHSMEGDAWgBStvZh6NLQm9/rEJlTvA73g 21 | JMtUGjAdBgNVHQ4EFgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQD 22 | AgGGMA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0gBAowCDAGBgRVHSAAMEQGA1UdHwQ9 23 | MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9BZGRUcnVzdEV4dGVy 24 | bmFsQ0FSb290LmNybDA1BggrBgEFBQcBAQQpMCcwJQYIKwYBBQUHMAGGGWh0dHA6 25 | Ly9vY3NwLnVzZXJ0cnVzdC5jb20wDQYJKoZIhvcNAQEMBQADggEBAGS/g/FfmoXQ 26 | zbihKVcN6Fr30ek+8nYEbvFScLsePP9NDXRqzIGCJdPDoCpdTPW6i6FtxFQJdcfj 27 | Jw5dhHk3QBN39bSsHNA7qxcS1u80GH4r6XnTq1dFDK8o+tDb5VCViLvfhVdpfZLY 28 | Uspzgb8c8+a4bmYRBbMelC1/kZWSWfFMzqORcUx8Rww7Cxn2obFshj5cqsQugsv5 29 | B5a6SE2Q8pTIqXOi6wZ7I53eovNNVZ96YUWYGGjHXkBrI/V5eu+MtWuLt29G9Hvx 30 | PUsE2JOAWVrgQSQdso8VYFhH2+9uRv0V9dlfmrPb2LjkQLPNlzmuhbsdjrzch5vR 31 | pu/xO28QOG8= 32 | -----END CERTIFICATE----- 33 | -------------------------------------------------------------------------------- /deps/cas/comodorsadomainvalidationsecureserverca.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIGCDCCA/CgAwIBAgIQKy5u6tl1NmwUim7bo3yMBzANBgkqhkiG9w0BAQwFADCB 3 | hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G 4 | A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV 5 | BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTQwMjEy 6 | MDAwMDAwWhcNMjkwMjExMjM1OTU5WjCBkDELMAkGA1UEBhMCR0IxGzAZBgNVBAgT 7 | EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR 8 | Q09NT0RPIENBIExpbWl0ZWQxNjA0BgNVBAMTLUNPTU9ETyBSU0EgRG9tYWluIFZh 9 | bGlkYXRpb24gU2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP 10 | ADCCAQoCggEBAI7CAhnhoFmk6zg1jSz9AdDTScBkxwtiBUUWOqigwAwCfx3M28Sh 11 | bXcDow+G+eMGnD4LgYqbSRutA776S9uMIO3Vzl5ljj4Nr0zCsLdFXlIvNN5IJGS0 12 | Qa4Al/e+Z96e0HqnU4A7fK31llVvl0cKfIWLIpeNs4TgllfQcBhglo/uLQeTnaG6 13 | ytHNe+nEKpooIZFNb5JPJaXyejXdJtxGpdCsWTWM/06RQ1A/WZMebFEh7lgUq/51 14 | UHg+TLAchhP6a5i84DuUHoVS3AOTJBhuyydRReZw3iVDpA3hSqXttn7IzW3uLh0n 15 | c13cRTCAquOyQQuvvUSH2rnlG51/ruWFgqUCAwEAAaOCAWUwggFhMB8GA1UdIwQY 16 | MBaAFLuvfgI9+qbxPISOre44mOzZMjLUMB0GA1UdDgQWBBSQr2o6lFoL2JDqElZz 17 | 30O0Oija5zAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNV 18 | HSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwGwYDVR0gBBQwEjAGBgRVHSAAMAgG 19 | BmeBDAECATBMBgNVHR8ERTBDMEGgP6A9hjtodHRwOi8vY3JsLmNvbW9kb2NhLmNv 20 | bS9DT01PRE9SU0FDZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDBxBggrBgEFBQcB 21 | AQRlMGMwOwYIKwYBBQUHMAKGL2h0dHA6Ly9jcnQuY29tb2RvY2EuY29tL0NPTU9E 22 | T1JTQUFkZFRydXN0Q0EuY3J0MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21v 23 | ZG9jYS5jb20wDQYJKoZIhvcNAQEMBQADggIBAE4rdk+SHGI2ibp3wScF9BzWRJ2p 24 | mj6q1WZmAT7qSeaiNbz69t2Vjpk1mA42GHWx3d1Qcnyu3HeIzg/3kCDKo2cuH1Z/ 25 | e+FE6kKVxF0NAVBGFfKBiVlsit2M8RKhjTpCipj4SzR7JzsItG8kO3KdY3RYPBps 26 | P0/HEZrIqPW1N+8QRcZs2eBelSaz662jue5/DJpmNXMyYE7l3YphLG5SEXdoltMY 27 | dVEVABt0iN3hxzgEQyjpFv3ZBdRdRydg1vs4O2xyopT4Qhrf7W8GjEXCBgCq5Ojc 28 | 2bXhc3js9iPc0d1sjhqPpepUfJa3w/5Vjo1JXvxku88+vZbrac2/4EjxYoIQ5QxG 29 | V/Iz2tDIY+3GH5QFlkoakdH368+PUq4NCNk+qKBR6cGHdNXJ93SrLlP7u3r7l+L4 30 | HyaPs9Kg4DdbKDsx5Q5XLVq4rXmsXiBmGqW5prU5wfWYQ//u+aen/e7KJD2AFsQX 31 | j4rBYKEMrltDR5FL1ZoXX/nUh8HCjLfn4g8wGTeGrODcQgPmlKidrv0PJFGUzpII 32 | 0fxQ8ANAe4hZ7Q7drNJ3gjTcBpUC2JD5Leo31Rpg0Gcg19hCC0Wvgmje3WYkN5Ap 33 | lBlGGSW4gNfL1IYoakRwJiNiqZ+Gb7+6kHDSVneFeO/qJakXzlByjAA6quPbYzSf 34 | +AZxAeKCINT+b72x 35 | -----END CERTIFICATE----- 36 | -------------------------------------------------------------------------------- /deps/nopus/index.js: -------------------------------------------------------------------------------- 1 | var ex = "uncaughtException"; 2 | var pl = process.listeners(ex); 3 | 4 | var opus = require("./opus-js/opus"); 5 | var resampler = require("./opus-js/resampler"); 6 | 7 | process.removeAllListeners(ex); 8 | for (var i = 0; i < pl.length; i++) process.on(ex, pl[i]); 9 | 10 | module.exports.Opus = opus.Opus; 11 | module.exports.OpusApplication = opus.OpusApplication; 12 | module.exports.OpusEncoder = opus.OpusEncoder; 13 | module.exports.OpusDecoder = opus.OpusDecoder; 14 | module.exports.Resampler = resampler.SpeexResampler; 15 | -------------------------------------------------------------------------------- /deps/nopus/opus-js/copying.libopus: -------------------------------------------------------------------------------- 1 | Copyright (c) 1994-2013 Xiph.Org Foundation and contributors 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions 5 | are met: 6 | 7 | - Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 10 | - Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | - Neither the name of the Xiph.Org Foundation nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION 22 | OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /deps/nopus/opus-js/copying.libspeex: -------------------------------------------------------------------------------- 1 | Copyright 2002-2008 Xiph.org Foundation 2 | Copyright 2002-2008 Jean-Marc Valin 3 | Copyright 2005-2007 Analog Devices Inc. 4 | Copyright 2005-2008 Commonwealth Scientific and Industrial Research 5 | Organisation (CSIRO) 6 | Copyright 1993, 2002, 2006 David Rowe 7 | Copyright 2003 EpicGames 8 | Copyright 1992-1994 Jutta Degener, Carsten Bormann 9 | 10 | Redistribution and use in source and binary forms, with or without 11 | modification, are permitted provided that the following conditions 12 | are met: 13 | 14 | - Redistributions of source code must retain the above copyright 15 | notice, this list of conditions and the following disclaimer. 16 | 17 | - Redistributions in binary form must reproduce the above copyright 18 | notice, this list of conditions and the following disclaimer in the 19 | documentation and/or other materials provided with the distribution. 20 | 21 | - Neither the name of the Xiph.org Foundation nor the names of its 22 | contributors may be used to endorse or promote products derived from 23 | this software without specific prior written permission. 24 | 25 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 26 | ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 27 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 28 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR 29 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 30 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 31 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 32 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 33 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 34 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 35 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 36 | -------------------------------------------------------------------------------- /deps/nopus/opus-js/copying.wrapper: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2014, Kazuki Oikawa 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions 6 | are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived 17 | from this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /examples/encoderstream.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | //// note: run "npm install lame" in this folder first 4 | 5 | // audio example implemented using AudioEncoderStream 6 | 7 | // audio decoding using "lame" 8 | 9 | // commands: 10 | // ping 11 | // vjoin -- joins matching channel for current guild 12 | // vleave 13 | // play -- plays test.mp3 14 | // stop 15 | 16 | var lame = require('lame'); 17 | var fs = require('fs'); 18 | 19 | var Discordie; 20 | try { Discordie = require("../"); } catch(e) {} 21 | try { Discordie = require("discordie"); } catch(e) {} 22 | 23 | var client = new Discordie({autoReconnect: true}); 24 | 25 | var auth = { token: "" }; 26 | try { auth = require("./auth"); } catch(e) {} 27 | 28 | client.connect(auth); 29 | 30 | client.Dispatcher.on("GATEWAY_READY", e => { 31 | const guild = client.Guilds.getBy("name", "test"); 32 | if (!guild) return console.log("Guild not found"); 33 | 34 | const general = guild.voiceChannels.find(c => c.name == "General"); 35 | if (!general) return console.log("Channel not found"); 36 | 37 | return general.join(false, false); 38 | }); 39 | 40 | client.Dispatcher.on("MESSAGE_CREATE", (e) => { 41 | const content = e.message.content; 42 | const channel = e.message.channel; 43 | const guild = e.message.channel.guild; 44 | 45 | if (content == "ping") { 46 | channel.sendMessage("pong"); 47 | } 48 | 49 | if (content == "vleave") { 50 | client.Channels 51 | .filter(channel => channel.isGuildVoice && channel.joined) 52 | .forEach(channel => channel.leave()); 53 | } 54 | 55 | if (content.indexOf("vjoin ") == 0) { 56 | const targetChannel = content.replace("vjoin ", ""); 57 | 58 | var vchannel = 59 | guild.voiceChannels 60 | .find(channel => channel.name.toLowerCase().indexOf(targetChannel) >= 0); 61 | if (vchannel) vchannel.join().then(info => play(info)); 62 | } 63 | 64 | if (content.indexOf("play") == 0) { 65 | if (!client.VoiceConnections.length) { 66 | return e.message.reply("Not connected to any channel"); 67 | } 68 | var info = client.VoiceConnections.getForGuild(guild); 69 | if (info) play(info); 70 | } 71 | 72 | if (content.indexOf("stop") == 0) { 73 | var info = client.VoiceConnections.getForGuild(guild); 74 | if (info) { 75 | var encoderStream = info.voiceConnection.getEncoderStream(); 76 | encoderStream.unpipeAll(); 77 | } 78 | } 79 | }); 80 | 81 | client.Dispatcher.on("VOICE_CONNECTED", e => { 82 | // uncomment to play on join 83 | //play(); 84 | }); 85 | 86 | function play(info) { 87 | if (!client.VoiceConnections.length) { 88 | return console.log("Voice not connected"); 89 | } 90 | 91 | if (!info) info = client.VoiceConnections[0]; 92 | 93 | var mp3decoder = new lame.Decoder(); 94 | var file = fs.createReadStream("test.mp3"); 95 | file.pipe(mp3decoder); 96 | 97 | mp3decoder.on('format', pcmfmt => { 98 | // note: discordie encoder does resampling if rate != 48000 99 | var options = { 100 | frameDuration: 60, 101 | sampleRate: pcmfmt.sampleRate, 102 | channels: pcmfmt.channels, 103 | float: false 104 | }; 105 | 106 | var encoderStream = info.voiceConnection.getEncoderStream(options); 107 | if (!encoderStream) { 108 | return console.log( 109 | "Unable to get encoder stream, connection is disposed" 110 | ); 111 | } 112 | 113 | // Stream instance is persistent until voice connection is disposed; 114 | // you can register timestamp listener once when connection is initialized 115 | // or access timestamp with `encoderStream.timestamp` 116 | encoderStream.resetTimestamp(); 117 | encoderStream.removeAllListeners("timestamp"); 118 | encoderStream.on("timestamp", time => console.log("Time " + time)); 119 | 120 | // only 1 stream at a time can be piped into AudioEncoderStream 121 | // previous stream will automatically unpipe 122 | mp3decoder.pipe(encoderStream); 123 | mp3decoder.once('end', () => play(info)); 124 | 125 | // must be registered after `pipe()` 126 | encoderStream.once("unpipe", () => file.destroy()); 127 | }); 128 | } 129 | 130 | client.Dispatcher.onAny((type, e) => { 131 | var ignore = [ 132 | "READY", 133 | "GATEWAY_READY", 134 | "ANY_GATEWAY_READY", 135 | "GATEWAY_DISPATCH", 136 | "PRESENCE_UPDATE", 137 | "TYPING_START", 138 | ]; 139 | if (ignore.find(t => (t == type || t == e.type))) { 140 | return console.log("<" + type + ">"); 141 | } 142 | 143 | console.log("\nevent " + type); 144 | return console.log("args " + JSON.stringify(e)); 145 | }); 146 | -------------------------------------------------------------------------------- /examples/ffmpegencoder.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | //// note: install ffmpeg/avconv first 4 | 5 | // audio decoding and encoding using built in ffmpeg wrappers 6 | // (opus encoding is done entierly by ffmpeg) 7 | 8 | // commands: 9 | // ping 10 | // vjoin -- joins matching channel for current guild 11 | // vleave 12 | // play -- plays test.mp3 13 | // stop 14 | 15 | var fs = require('fs'); 16 | 17 | var Discordie; 18 | try { Discordie = require("../"); } catch(e) {} 19 | try { Discordie = require("discordie"); } catch(e) {} 20 | 21 | var client = new Discordie({autoReconnect: true}); 22 | 23 | var auth = { token: "" }; 24 | try { auth = require("./auth"); } catch(e) {} 25 | 26 | client.connect(auth); 27 | 28 | client.Dispatcher.on("GATEWAY_READY", e => { 29 | const guild = client.Guilds.getBy("name", "test"); 30 | if (!guild) return console.log("Guild not found"); 31 | 32 | const general = guild.voiceChannels.find(c => c.name == "General"); 33 | if (!general) return console.log("Channel not found"); 34 | 35 | return general.join(false, false); 36 | }); 37 | 38 | client.Dispatcher.on("MESSAGE_CREATE", (e) => { 39 | const content = e.message.content; 40 | const channel = e.message.channel; 41 | const guild = e.message.channel.guild; 42 | 43 | if (content == "ping") { 44 | channel.sendMessage("pong"); 45 | } 46 | 47 | if (content == "vleave") { 48 | client.Channels 49 | .filter(channel => channel.isGuildVoice && channel.joined) 50 | .forEach(channel => channel.leave()); 51 | } 52 | 53 | if (content.indexOf("vjoin ") == 0) { 54 | const targetChannel = content.replace("vjoin ", ""); 55 | 56 | var vchannel = 57 | guild.voiceChannels 58 | .find(channel => channel.name.toLowerCase().indexOf(targetChannel) >= 0); 59 | if (vchannel) vchannel.join().then(info => play(info)); 60 | } 61 | 62 | if (content.indexOf("play") == 0) { 63 | if (!client.VoiceConnections.length) { 64 | return e.message.reply("Not connected to any channel"); 65 | } 66 | var info = client.VoiceConnections.getForGuild(guild); 67 | if (info) play(info); 68 | } 69 | 70 | if (content.indexOf("stop") == 0) { 71 | var info = client.VoiceConnections.getForGuild(guild); 72 | if (info) { 73 | var encoderStream = info.voiceConnection.getEncoderStream(); 74 | encoderStream.unpipeAll(); 75 | } 76 | } 77 | }); 78 | 79 | client.Dispatcher.on("VOICE_CONNECTED", e => { 80 | // uncomment to play on join 81 | // play(); 82 | }); 83 | 84 | function play(info) { 85 | if (!client.VoiceConnections.length) { 86 | return console.log("Voice not connected"); 87 | } 88 | 89 | if (!info) info = client.VoiceConnections[0]; 90 | 91 | var encoder = info.voiceConnection.createExternalEncoder({ 92 | type: "ffmpeg", 93 | source: "test.mp3" 94 | }); 95 | if (!encoder) return console.log("Voice connection is no longer valid"); 96 | 97 | encoder.once("end", () => play(info)); 98 | 99 | var encoderStream = encoder.play(); 100 | encoderStream.resetTimestamp(); 101 | encoderStream.removeAllListeners("timestamp"); 102 | encoderStream.on("timestamp", time => console.log("Time " + time)); 103 | } 104 | 105 | client.Dispatcher.onAny((type, e) => { 106 | var ignore = [ 107 | "READY", 108 | "GATEWAY_READY", 109 | "ANY_GATEWAY_READY", 110 | "GATEWAY_DISPATCH", 111 | "PRESENCE_UPDATE", 112 | "TYPING_START", 113 | ]; 114 | if (ignore.find(t => (t == type || t == e.type))) { 115 | return console.log("<" + type + ">"); 116 | } 117 | 118 | console.log("\nevent " + type); 119 | return console.log("args " + JSON.stringify(e)); 120 | }); 121 | -------------------------------------------------------------------------------- /lib/collections/BaseArrayCollection.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | class BaseArrayCollection extends Array { 4 | // avoid constructor, calling array mutation methods will call it (ES2015) 5 | static create() { 6 | const instance = new this(); 7 | this._constructor.apply(instance, arguments); 8 | return instance; 9 | } 10 | } 11 | 12 | module.exports = BaseArrayCollection; 13 | -------------------------------------------------------------------------------- /lib/collections/BaseCollection.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const BaseModel = require("../models/BaseModel"); 4 | 5 | class BaseCollection extends Map { 6 | constructor() { 7 | super(); 8 | } 9 | mergeOrSet(key, value) { 10 | const old = this.get(key); 11 | let merged = value; 12 | if (old && old instanceof BaseModel) { 13 | merged = old.merge(value); 14 | } 15 | this.set(key, merged); 16 | } 17 | } 18 | 19 | module.exports = BaseCollection; 20 | -------------------------------------------------------------------------------- /lib/collections/CallCollection.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../Constants"); 4 | const Events = Constants.Events; 5 | const Utils = require("../core/Utils"); 6 | const BaseCollection = require("./BaseCollection"); 7 | 8 | const Call = require("../models/Call"); 9 | 10 | function emitRing(gw, channelId) { 11 | const channel = this._discordie.DirectMessageChannels.get(channelId); 12 | if (!channel) return; 13 | 14 | this._discordie.Dispatcher.emit(Events.CALL_RING, { 15 | socket: gw, 16 | channel: channel 17 | }); 18 | } 19 | 20 | function checkRing(gw, prev, next) { 21 | const channelId = next.channel_id; 22 | const userId = this._discordie._user && this._discordie._user.id; 23 | if (!channelId || !userId) return; 24 | if (!next || !next.ringing) return; 25 | 26 | const hasPrev = prev ? prev.ringing.indexOf(userId) >= 0 : false; 27 | const hasNext = next.ringing.indexOf(userId) >= 0; 28 | 29 | if (!hasPrev && hasNext) emitRing.call(this, gw, channelId); 30 | } 31 | 32 | function handleConnectionOpen(data) { 33 | this.clear(); 34 | return true; 35 | } 36 | 37 | function handleCallCreate(call, e) { 38 | this.set(call.channel_id, new Call(call)); 39 | checkRing.call(this, e.socket, null, call); 40 | return true; 41 | } 42 | 43 | function handleCallUpdate(call, e) { 44 | const prev = this.get(call.channel_id); 45 | this.mergeOrSet(call.channel_id, new Call(call)); 46 | checkRing.call(this, e.socket, prev, call); 47 | return true; 48 | } 49 | 50 | function handleCallDelete(call) { 51 | const _call = this.get(call.channel_id); 52 | if (!_call) return true; 53 | 54 | if (call.unavailable === true) { 55 | this.mergeOrSet(call.channel_id, {unavailable: true}); 56 | } else { 57 | this.delete(call.channel_id); 58 | } 59 | 60 | return true; 61 | } 62 | 63 | class CallCollection extends BaseCollection { 64 | constructor(discordie, gateway) { 65 | super(); 66 | 67 | if (typeof gateway !== "function") 68 | throw new Error("Gateway parameter must be a function"); 69 | 70 | discordie.Dispatcher.on(Events.GATEWAY_DISPATCH, e => { 71 | if (e.socket != gateway()) return; 72 | 73 | Utils.bindGatewayEventHandlers(this, e, { 74 | READY: handleConnectionOpen, 75 | CALL_CREATE: handleCallCreate, 76 | CALL_UPDATE: handleCallUpdate, 77 | CALL_DELETE: handleCallDelete 78 | }); 79 | }); 80 | 81 | this._discordie = discordie; 82 | Utils.privatify(this); 83 | } 84 | isActive(channelId, messageId) { 85 | const call = this.get(channelId); 86 | if (messageId) { 87 | return call && !call.unavailable && call.message_id == messageId; 88 | } 89 | return call && !call.unavailable; 90 | } 91 | isUnavailable(channelId) { 92 | const call = this.get(channelId); 93 | return call && call.unavailable; 94 | } 95 | } 96 | 97 | module.exports = CallCollection; 98 | -------------------------------------------------------------------------------- /lib/collections/UnavailableGuildCollection.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../Constants"); 4 | const Events = Constants.Events; 5 | const Utils = require("../core/Utils"); 6 | const BaseArrayCollection = require("./BaseArrayCollection"); 7 | 8 | const GUILD_STREAMING_TIMEOUT = 3000; 9 | 10 | function emitReady(gw) { 11 | const timedOut = Array.from(this._streamingGuilds); 12 | this._streamingGuilds.clear(); 13 | 14 | this._discordie.Dispatcher.emit(Events.COLLECTION_READY, { 15 | socket: gw, 16 | name: this.constructor.name, 17 | collection: this 18 | }); 19 | 20 | if (!timedOut.length) return; 21 | timedOut.forEach(id => { 22 | this._discordie.Dispatcher.emit(Events.GUILD_UNAVAILABLE, { 23 | socket: gw, 24 | guildId: id 25 | }); 26 | }); 27 | } 28 | 29 | function scheduleReadyTimeout(gw) { 30 | this._streamingGuildsTimeout = setTimeout(() => { 31 | this._streamingGuildsTimeout = null; 32 | if (!this._discordie.connected) return; 33 | 34 | setImmediate(() => emitReady.call(this, gw)); 35 | }, GUILD_STREAMING_TIMEOUT); 36 | } 37 | 38 | function maybeReady(gw) { 39 | if (this._streamingGuildsTimeout) { 40 | clearTimeout(this._streamingGuildsTimeout); 41 | } 42 | if (this._streamingGuilds.size) { 43 | scheduleReadyTimeout.call(this, gw); 44 | } else { 45 | setImmediate(() => emitReady.call(this, gw)); 46 | } 47 | } 48 | 49 | function handleConnectionOpen(data, e) { 50 | clearCollections.call(this); 51 | 52 | data.guilds.forEach(guild => { 53 | if (!guild.unavailable) return; 54 | addUnavailable.call(this, guild.id); 55 | this._streamingGuilds.add(guild.id); 56 | }); 57 | 58 | maybeReady.call(this, e.socket); 59 | return true; 60 | } 61 | 62 | function handleGuildCreate(guild, e) { 63 | handleUnavailable.call(this, guild); 64 | 65 | if (this.isGuildAvailable(guild) && this._streamingGuilds.has(guild.id)) { 66 | e.suppress = true; 67 | this._streamingGuilds.delete(guild.id); 68 | 69 | maybeReady.call(this, e.socket); 70 | } 71 | return true; 72 | } 73 | 74 | function handleGuildDelete(guild) { 75 | handleUnavailable.call(this, guild); 76 | return true; 77 | } 78 | 79 | function handleUnavailable(guild) { 80 | if (guild.unavailable) { 81 | addUnavailable.call(this, guild.id) 82 | } else { 83 | removeUnavailable.call(this, guild.id); 84 | } 85 | } 86 | 87 | function addUnavailable(id) { 88 | if (this._set.has(id)) return; 89 | this._set.add(id); 90 | 91 | this.push(id); 92 | } 93 | function removeUnavailable(id) { 94 | if (!this._set.has(id)) return; 95 | this._set.delete(id); 96 | 97 | var idx = this.indexOf(id); 98 | this.splice(idx, 1); 99 | } 100 | function clearCollections() { 101 | this._streamingGuilds.clear(); 102 | this._set.clear(); 103 | this.length = 0; 104 | } 105 | 106 | class UnavailableGuildCollection extends BaseArrayCollection { 107 | static _constructor(discordie, gateway) { 108 | if (typeof gateway !== "function") 109 | throw new Error("Gateway parameter must be a function"); 110 | 111 | discordie.Dispatcher.on(Events.GATEWAY_DISPATCH, e => { 112 | if (e.socket != gateway()) return; 113 | 114 | Utils.bindGatewayEventHandlers(this, e, { 115 | READY: handleConnectionOpen, 116 | GUILD_CREATE: handleGuildCreate, 117 | GUILD_DELETE: handleGuildDelete, 118 | }); 119 | }); 120 | 121 | this._discordie = discordie; 122 | this._set = new Set(); 123 | 124 | this._streamingGuilds = new Set(); 125 | this._streamingGuildsTimeout = null; 126 | 127 | Utils.privatify(this); 128 | } 129 | isGuildAvailable(guild) { 130 | // unavailable guilds that became available have key `unavailable` 131 | return guild.unavailable === false; 132 | } 133 | } 134 | 135 | module.exports = UnavailableGuildCollection; 136 | -------------------------------------------------------------------------------- /lib/core/Backoff.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | class Backoff { 4 | constructor(min, max, jitter) { 5 | this._min = (min != null && min > 0) ? min : 500; 6 | this._max = (max != null && max > this._min) ? max : (this._min * 30); 7 | this.jitter = (jitter != null) ? jitter : true; 8 | 9 | this.fails = 0; 10 | this.delay = this._min; 11 | 12 | this._timer = null; 13 | } 14 | 15 | get min() { return this._min; } 16 | set min(value) { 17 | if (value < 0) throw new TypeError("Param 'value' must be >= 0"); 18 | this._min = value; 19 | if (!this.fails) this.delay = this._min; 20 | } 21 | 22 | get max() { return this._max; } 23 | set max(value) { 24 | if (value < 5000) throw new TypeError("Param 'value' must be >= 5000"); 25 | this._max = value; 26 | } 27 | 28 | reset() { 29 | this.cancel(); 30 | this.fails = 0; 31 | this.delay = this._min; 32 | } 33 | fail(cb) { 34 | this.fails++; 35 | const delay = this.delay * 2 * (this.jitter ? Math.random() : 1); 36 | this.delay = Math.min(this.delay + delay, this._max); 37 | 38 | if (typeof cb !== "function") return this.delay; 39 | 40 | this.cancel(); 41 | this._timer = setTimeout(() => { 42 | try { cb(); } 43 | catch (e) { console.log(e instanceof Error ? e.stack : e); } 44 | finally { this._timer = null; } 45 | }, this.delay); 46 | 47 | return this.delay; 48 | } 49 | cancel() { 50 | if (!this._timer) return; 51 | clearTimeout(this._timer); 52 | this._timer = null; 53 | } 54 | } 55 | 56 | module.exports = Backoff; 57 | -------------------------------------------------------------------------------- /lib/core/DiscordieDispatcher.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const DiscordieError = require("./DiscordieError"); 4 | const Constants = require("../Constants"); 5 | const EventTypes = Object.keys(Constants.Events); 6 | 7 | const events = require("events"); 8 | 9 | let lastEvent = null; 10 | 11 | function validateEvent(eventType) { 12 | if (EventTypes.indexOf(eventType) < 0) 13 | throw new DiscordieError(`Invalid event '${eventType}'`); 14 | } 15 | 16 | class DiscordieDispatcher extends events.EventEmitter { 17 | constructor() { 18 | super(); 19 | this._anyeventlisteners = []; 20 | this.setMaxListeners(14); 21 | } 22 | static _getLastEvent() { return lastEvent; } 23 | on(eventType, listener) { 24 | validateEvent(eventType); 25 | return super.on.apply(this, arguments); 26 | } 27 | onAny(fn) { 28 | if (typeof fn !== "function") 29 | return this; 30 | this._anyeventlisteners.push(fn); 31 | return this; 32 | } 33 | emit(eventType) { 34 | validateEvent(eventType); 35 | 36 | lastEvent = [].slice.call(arguments); 37 | super.emit.apply(this, arguments); 38 | const _arguments = arguments; 39 | this._anyeventlisteners.forEach((fn) => { 40 | fn.apply(this, _arguments); 41 | }); 42 | } 43 | hasListeners(eventType) { 44 | validateEvent(eventType); 45 | 46 | if (this._anyeventlisteners.length || this.listenerCount(eventType)) 47 | return true; 48 | return false; 49 | } 50 | } 51 | 52 | module.exports = DiscordieDispatcher; 53 | -------------------------------------------------------------------------------- /lib/core/DiscordieError.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | class DiscordieError extends Error { 4 | constructor(message, exception) { 5 | super(message); 6 | if (exception) this.exception = exception; 7 | } 8 | toJSON() { 9 | return this.message; 10 | } 11 | } 12 | 13 | module.exports = DiscordieError; 14 | -------------------------------------------------------------------------------- /lib/core/DiscordieProfiler.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const state = new Map(); 4 | const stats = new Map(); 5 | 6 | class DiscordieProfiler { 7 | static hrtime() { 8 | const t = process.hrtime(); 9 | return t[0] * 1000 + t[1] / 1000000; 10 | } 11 | static start(n) { 12 | state.set(n, this.hrtime()); 13 | } 14 | static stop(n) { 15 | const d = this.hrtime() - state.get(n); 16 | stats.set(n, d); 17 | return d; 18 | } 19 | static get(n) { 20 | return stats.get(n); 21 | } 22 | } 23 | 24 | module.exports = DiscordieProfiler; 25 | -------------------------------------------------------------------------------- /lib/core/GatewayReconnectHandler.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../Constants"); 4 | const Events = Constants.Events; 5 | const Backoff = require("./Backoff"); 6 | 7 | /** 8 | * @class 9 | */ 10 | class GatewayReconnectHandler { 11 | constructor(discordie) { 12 | this._discordie = discordie; 13 | this._backoff = new Backoff(1000, 60000); 14 | this._enabled = false; 15 | } 16 | 17 | _disconnected(e) { 18 | if (!this._enabled) return; 19 | e.autoReconnect = true; 20 | e.delay = this._backoff.fail(() => this._discordie.connect()); 21 | } 22 | 23 | _reset() { 24 | this._backoff.reset(); 25 | } 26 | 27 | /** 28 | * Enables auto-reconnect. 29 | */ 30 | enable() { this._enabled = true; } 31 | 32 | /** 33 | * Disables auto-reconnect. 34 | */ 35 | disable() { this._enabled = false; } 36 | 37 | /** 38 | * Boolean indicating whether auto-reconnect is enabled. 39 | * @returns {boolean} 40 | * @readonly 41 | */ 42 | get enabled() { return this._enabled; } 43 | 44 | /** 45 | * Gets/sets minimum delay in milliseconds. Must be >= 0. 46 | * 47 | * Default is 1000. 48 | * @returns {Number} 49 | */ 50 | get min() { return this._backoff.min; } 51 | /** 52 | * @ignore 53 | * @type {Number} 54 | **/ 55 | set min(value) { this._backoff.min = value; } 56 | 57 | /** 58 | * Gets/sets maximum delay in milliseconds. Must be >= 5000. 59 | * 60 | * Default is 60000. 61 | * @returns {Number} 62 | */ 63 | get max() { return this._backoff.max; } 64 | /** 65 | * @ignore 66 | * @type {Number} 67 | **/ 68 | set max(value) { this._backoff.max = value; } 69 | } 70 | 71 | module.exports = GatewayReconnectHandler; 72 | -------------------------------------------------------------------------------- /lib/core/LimitedCache.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function findKeyInsertionPoint(array, element, start, end) { 4 | if (start === undefined) start = 0; 5 | if (end === undefined) end = array.length; 6 | 7 | var min = start, max = end; 8 | while (true) { 9 | var k = ((min + max) / 2) | 0; 10 | if (k > end || min === max) break; 11 | 12 | var result = (+element) - (+array[k]); 13 | if (result === 0) break; 14 | else if (result > 0) min = k + 1; 15 | else if (result < 0) max = k; 16 | } 17 | 18 | return k; 19 | } 20 | 21 | class LimitedCache { 22 | constructor(limit) { 23 | this._keys = []; 24 | Object.defineProperty(this, "_keys", { enumerable: false }); 25 | this._map = new Map; 26 | this.setLimit(limit); 27 | } 28 | setLimit(limit) { 29 | this.limit = limit || 1000; 30 | if (!(this.limit > 0)) this.limit = 1; 31 | return this.trim(); 32 | } 33 | trim() { 34 | var keys = this._keys; 35 | if (keys.length <= this.limit) return null; 36 | var removed = keys.splice(0, keys.length - this.limit); 37 | for (var i = 0; i < removed.length; i++) 38 | this._map.delete(removed[i]); 39 | return removed; 40 | } 41 | set(k, v) { 42 | if (!this._map.has(k)) { 43 | this._keys.splice(findKeyInsertionPoint(this._keys, k), 0, k); 44 | this.trim(); 45 | } 46 | return this._map.set(k, v); 47 | } 48 | rename(from, to) { 49 | var i = this._keys.indexOf(from); 50 | if (i >= 0) { 51 | this._keys.splice(i, 1); 52 | this._keys.splice(findKeyInsertionPoint(this._keys, to), 0, to); 53 | } 54 | if (this._map.has(from)) { 55 | this._map.set(to, this._map.get(from)); 56 | this._map.delete(from); 57 | } 58 | } 59 | delete(k) { 60 | if (this._map.delete(k)) { 61 | var i = this._keys.indexOf(k); 62 | if (i >= 0) this._keys.splice(i, 1); 63 | return true; 64 | } 65 | return false; 66 | } 67 | clear() { 68 | this._keys.length = 0; 69 | return this._map.clear(); 70 | } 71 | forEach(fn) { return this._map.forEach(fn); } 72 | values() { return this._map.values(); } 73 | entries() { return this._map.entries(); } 74 | keys() { return this._map.keys(); } 75 | get(k) { return this._map.get(k); } 76 | has(k) { return this._map.has(k); } 77 | get size() { return this._map.size; } 78 | 79 | map(fn) { 80 | if (typeof fn !== "function") { 81 | throw new TypeError("fn is not a function"); 82 | } 83 | if (!this._keys.length) return []; 84 | var items = []; 85 | for (var i = 0, len = this._keys.length; i < len; i++) { 86 | var item = this.get(this._keys[i]); 87 | items.push(fn(item)); 88 | } 89 | return items; 90 | } 91 | } 92 | 93 | module.exports = LimitedCache; 94 | -------------------------------------------------------------------------------- /lib/core/MessageHandlerCache.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../Constants"); 4 | 5 | const discordie = new WeakMap(); 6 | 7 | const messageHandlerCache = {}; 8 | function processMessage(source, socket, type, data) { 9 | if (messageHandlerCache[`${source}/${type}`]) { 10 | if (typeof messageHandlerCache[`${source}/${type}`] === "function") 11 | return messageHandlerCache[`${source}/${type}`].apply(this, [data, socket]); 12 | return false; 13 | } 14 | 15 | let messageHandler = null; 16 | 17 | if (process.browser) { 18 | const ctx = require.context("../networking/messages/", true, /\.js$/); 19 | const modulePath = `./${source}/${type.toLowerCase()}.js`; 20 | 21 | try { 22 | messageHandler = ctx(modulePath); 23 | } catch (e) { } //eslint-disable-line no-empty 24 | } else { 25 | const modulePath = 26 | `../networking/messages/${source}/${type.toLowerCase()}`; 27 | 28 | try { 29 | messageHandler = require(modulePath); 30 | } catch (e) { 31 | if (e.code != "MODULE_NOT_FOUND") 32 | throw e; 33 | } 34 | } 35 | 36 | if (messageHandler) { 37 | messageHandlerCache[`${source}/${type}`] = messageHandler; 38 | return processMessage.apply(this, arguments); 39 | } 40 | } 41 | 42 | class MessageHandlerCache { 43 | constructor(_discordie) { 44 | discordie.set(this, _discordie); 45 | } 46 | 47 | processVoiceMessage(socket, type, data) { 48 | return processMessage.call(discordie.get(this), 49 | "voice", socket, type, data 50 | ); 51 | } 52 | processGatewayMessage(socket, type, data) { 53 | return processMessage.call(discordie.get(this), 54 | "gateway", socket, type, data 55 | ); 56 | } 57 | } 58 | 59 | module.exports = MessageHandlerCache; 60 | -------------------------------------------------------------------------------- /lib/core/ReadyEventScheduler.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../Constants"); 4 | const Events = Constants.Events; 5 | const Utils = require("../core/Utils"); 6 | 7 | function emitReady(gw) { 8 | const ready = this._readyPayload; 9 | 10 | if (!this._readyPayload) return; 11 | this._readyPayload = null; 12 | 13 | if (!this._discordie.connected) return; 14 | 15 | if (gw.isPrimary) { 16 | this._discordie.Dispatcher.emit(Events.GATEWAY_READY, { 17 | socket: gw, 18 | data: ready 19 | }); 20 | } 21 | this._discordie.Dispatcher.emit(Events.ANY_GATEWAY_READY, { 22 | socket: gw, 23 | data: ready 24 | }); 25 | } 26 | 27 | function handleConnectionOpen(data, e) { 28 | this._readyPayload = data; 29 | this._collections = new Set(this._registeredCollections); 30 | this._tasks = Array.from(this._registeredTasks); 31 | return true; 32 | } 33 | 34 | function executeNextTask(gw) { 35 | if (!this._readyPayload || !this._tasks.length) return; 36 | const nextTask = this._tasks.shift(); 37 | nextTask.handler._executeReadyTask(this._readyPayload, gw); 38 | } 39 | 40 | class ReadyEventScheduler { 41 | constructor(discordie, gateway) { 42 | this._discordie = discordie; 43 | 44 | this._readyPayload = null; 45 | 46 | discordie.Dispatcher.on(Events.GATEWAY_DISPATCH, e => { 47 | if (e.socket != gateway()) return; 48 | 49 | Utils.bindGatewayEventHandlers(this, e, { 50 | READY: handleConnectionOpen 51 | }); 52 | }); 53 | 54 | discordie.Dispatcher.on(Events.GATEWAY_DISCONNECT, e => { 55 | if (e.socket != gateway()) return; 56 | 57 | this._readyPayload = null; 58 | }); 59 | 60 | // parallel 61 | 62 | this._registeredCollections = new Set(); 63 | this._collections = new Set(); 64 | discordie.Dispatcher.on(Events.COLLECTION_READY, e => { 65 | this._collections.delete(e.collection); 66 | if (this._collections.size) return; 67 | 68 | executeNextTask.call(this, gateway()); 69 | }); 70 | 71 | // sequential 72 | 73 | this._registeredTasks = []; 74 | this._tasks = []; 75 | discordie.Dispatcher.on(Events.READY_TASK_FINISHED, lastTask => { 76 | const idx = this._tasks.findIndex(t => t.handler === lastTask.handler); 77 | this._tasks.splice(idx, 1); 78 | 79 | if (!this._tasks.length) { 80 | return setImmediate(() => emitReady.call(this, gateway())); 81 | } 82 | 83 | executeNextTask.call(this, gateway()); 84 | }); 85 | 86 | Utils.privatify(this); 87 | } 88 | _waitFor(collection) { 89 | this._registeredCollections.add(collection); 90 | } 91 | _addTask(name, handler) { 92 | if (typeof name !== "string") { 93 | throw new TypeError( 94 | "ReadyEventScheduler: Invalid task name " + name 95 | ); 96 | } 97 | if (typeof handler._executeReadyTask !== "function") { 98 | throw new TypeError( 99 | "ReadyEventScheduler: Invalid handler for task " + name 100 | ); 101 | } 102 | this._registeredTasks.push({name, handler}); 103 | } 104 | } 105 | 106 | module.exports = ReadyEventScheduler; 107 | -------------------------------------------------------------------------------- /lib/core/ratelimiting/Bucket.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Profiler = require("../DiscordieProfiler"); 4 | 5 | class Bucket { 6 | constructor(size, duration) { 7 | this.size = size; 8 | this.duration = duration; 9 | 10 | if (typeof size !== "number") 11 | throw new TypeError("Param 'size' is not a number"); 12 | if (typeof duration !== "number") 13 | throw new TypeError("Param 'duration' is not a number"); 14 | 15 | this.refill(); 16 | } 17 | resize(newSize) { 18 | if (newSize > 0) { 19 | if (this.size === this.dropsLeft) this.dropsLeft = newSize; 20 | if ((this.size - 1) === this.dropsLeft) this.dropsLeft = newSize - 1; 21 | } 22 | this.size = newSize; 23 | } 24 | rescheduleRefill(timeFromNow) { 25 | this.lastRefill = (Profiler.hrtime() - this.duration) + timeFromNow; 26 | } 27 | wait(time) { 28 | this.rescheduleRefill(time); 29 | this.dropsLeft = 0; 30 | } 31 | refill() { 32 | this.lastRefill = Profiler.hrtime(); 33 | this.dropsLeft = this.size; 34 | } 35 | get waitTime() { 36 | var now = Profiler.hrtime(); 37 | return (this.lastRefill + this.duration) - now; 38 | } 39 | get available() { 40 | if (this.waitTime < 0) 41 | this.refill(); 42 | return this.dropsLeft; 43 | } 44 | consume(n) { 45 | if (!n) n = 1; 46 | if (this.available < n) 47 | return false; 48 | 49 | this.dropsLeft -= n; 50 | return true; 51 | } 52 | } 53 | 54 | module.exports = Bucket; -------------------------------------------------------------------------------- /lib/core/ratelimiting/ChainedBucket.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Bucket = require("./Bucket"); 4 | 5 | class ChainedBucket extends Bucket { 6 | constructor(size, duration, name, parent) { 7 | super(size, duration); 8 | this.parent = parent || null; 9 | this.name = name || null; 10 | } 11 | refillByName(name) { 12 | if (this.parent) this.parent.refillByName(name); 13 | if (this.name && this.name.indexOf(name) === 0) this.refill(); 14 | } 15 | get waitingBucket() { 16 | if (this.parent && this.parent.available <= 0) 17 | return this.parent; 18 | if (this.available <= 0) 19 | return this; 20 | return null; 21 | } 22 | get available() { 23 | // this getter triggers refill 24 | const availableParent = this.parent && this.parent.available; 25 | const availableSelf = super.available; 26 | if (this.parent && availableParent <= 0) 27 | return availableParent; 28 | return availableSelf; 29 | } 30 | consume(n) { 31 | // this triggers 'available' getter which triggers refill 32 | if (this.parent) { 33 | return super.consume(n) && this.parent.consume(n); 34 | } 35 | return super.consume(n); 36 | } 37 | } 38 | 39 | module.exports = ChainedBucket; -------------------------------------------------------------------------------- /lib/core/ratelimiting/RequestQueue.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Deque = require("double-ended-queue"); 4 | 5 | const DEFAULT_RETRY_AFTER = 100; 6 | 7 | const POST_RATELIMIT_DELAY = 1000; 8 | 9 | class RequestQueue { 10 | constructor(bucket) { 11 | this.bucket = bucket || null; 12 | 13 | this.queue = new Deque(); 14 | this.timeout = null; 15 | this.draining = false; 16 | 17 | this.lastReset = 0; 18 | 19 | this.disabled = false; 20 | } 21 | enqueue(request, sendCallback) { 22 | this.queue.push({request, sendCallback}); 23 | this._drain(); 24 | } 25 | _drain() { 26 | if (this.disabled) { 27 | const entry = this.queue.shift(); 28 | entry.request.send(entry.sendCallback); 29 | if (this.queue.length) { 30 | setImmediate(() => this._drain()); 31 | } 32 | return; 33 | } 34 | 35 | if (!this.queue.length) return; 36 | if (this.timeout !== null || this.draining) return; 37 | 38 | if (this.bucket && !this.bucket.consume()) { 39 | this._scheduleDrain(this.bucket.waitTime); 40 | return; 41 | } 42 | 43 | this.draining = true; 44 | const entry = this.queue.shift(); 45 | 46 | entry.request.send((err, res) => { 47 | this.draining = false; 48 | 49 | this._updateBucket(res); 50 | 51 | if (err && res && res.status === 429) { 52 | this.queue.unshift(entry); 53 | 54 | const retryAfter = 55 | res.body["retry_after"] || 56 | (+res.headers["retry-after"]) || 57 | DEFAULT_RETRY_AFTER; 58 | 59 | const isGlobal = 60 | res.body["global"] || 61 | (res.headers["x-ratelimit-global"] === "true") || 62 | false; 63 | 64 | if (isGlobal) { 65 | const manager = entry.request._discordie._queueManager; 66 | if (manager && manager.globalBucket) { 67 | manager.globalBucket.wait(retryAfter); 68 | } 69 | } else if (this.bucket) { 70 | this.bucket.wait(retryAfter); 71 | } 72 | 73 | this._scheduleDrain(retryAfter); 74 | return; 75 | } 76 | 77 | setImmediate(() => this._drain()); 78 | if (typeof entry.sendCallback === "function") { 79 | entry.sendCallback(err, res); 80 | } 81 | }); 82 | } 83 | _updateBucket(res) { 84 | if (!res || !res.headers) return; 85 | if (!this.bucket) return; 86 | 87 | // update limits and timing based on server info if available, 88 | // otherwise fallback to hardcoded buckets 89 | 90 | // each window is delayed by POST_RATELIMIT_DELAY = 1s: 91 | // this makes it slower, but ensures not hitting 429 unless: 92 | // - some other client has drained all tokens already, 93 | // - server time is out of sync by more than 1 second, 94 | // - or headers X-RateLimit-Reset/Date do not contain usable time 95 | 96 | if (res.headers["x-ratelimit-limit"]) { 97 | const limit = +res.headers["x-ratelimit-limit"]; 98 | if (limit > 0) this.bucket.resize(limit); 99 | } 100 | 101 | if (res.headers["x-ratelimit-remaining"]) { 102 | const remaining = +res.headers["x-ratelimit-remaining"]; 103 | if (!isNaN(remaining)) this.bucket.dropsLeft = remaining; 104 | } 105 | 106 | if (res.headers["x-ratelimit-reset"] && res.headers["date"]) { 107 | const resetSeconds = (+res.headers["x-ratelimit-reset"]) || 0; 108 | if (resetSeconds > 0) { 109 | const date = new Date(res.headers["date"]).getTime(); 110 | const reset = new Date(resetSeconds * 1000).getTime(); 111 | const now = date > 0 ? date : Date.now(); 112 | 113 | if (!this.lastReset || reset > this.lastReset) { 114 | this.lastReset = reset; 115 | this.bucket.rescheduleRefill(reset - now); 116 | } 117 | } 118 | } 119 | } 120 | _scheduleDrain(time) { 121 | this.timeout = setTimeout(() => { 122 | this.timeout = null; 123 | this._drain(); 124 | }, time + POST_RATELIMIT_DELAY); 125 | } 126 | } 127 | 128 | module.exports = RequestQueue; -------------------------------------------------------------------------------- /lib/interfaces/ICall.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../Constants"); 4 | 5 | const IBase = require("./IBase"); 6 | const Utils = require("../core/Utils"); 7 | 8 | const Call = require("../models/Call"); 9 | 10 | /** 11 | * @interface 12 | * @model Call 13 | * @extends IBase 14 | */ 15 | class ICall extends IBase { 16 | constructor(discordie, directMessageChannelId) { 17 | super(); 18 | Utils.definePrivate(this, { 19 | _discordie: discordie, 20 | _directMessageChannelId: directMessageChannelId 21 | }); 22 | 23 | Object.freeze(this); 24 | } 25 | 26 | /** 27 | * Gets date and time this call was created at. 28 | * @returns {Date} 29 | * @readonly 30 | */ 31 | get createdAt() { 32 | const call = this._discordie._calls.get(this._directMessageChannelId); 33 | if (!call) return new Date(null); 34 | 35 | return new Date(Utils.timestampFromSnowflake(call.message_id)); 36 | } 37 | 38 | /** 39 | * Checks if the call is ringing for current user. 40 | * @return {boolean} 41 | * @readonly 42 | */ 43 | get isRinging() { 44 | const call = this._discordie._calls.get(this._directMessageChannelId); 45 | if (!call) return false; 46 | 47 | const userId = this._discordie._user && this._discordie._user.id; 48 | if (!userId) return false; 49 | 50 | return call.ringing ? call.ringing.indexOf(userId) >= 0 : false; 51 | } 52 | } 53 | 54 | ICall._inherit(Call, function modelPropertyGetter(key) { 55 | return this._discordie._calls.get(this._directMessageChannelId)[key]; 56 | }); 57 | 58 | /** 59 | * @readonly 60 | * @instance 61 | * @memberOf ICall 62 | * @name ringing 63 | * @returns {Array|null} 64 | */ 65 | ICall._setValueOverride("ringing", function(ringing) { 66 | const users = []; 67 | if (!ringing) return users; 68 | for (let id of ringing) { 69 | const user = this._discordie.Users.get(id); 70 | if (user) users.push(user); 71 | } 72 | return users; 73 | }); 74 | 75 | module.exports = ICall; 76 | -------------------------------------------------------------------------------- /lib/interfaces/IChannelCollection.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const ICollectionBase = require("./ICollectionBase"); 4 | const IChannel = require("./IChannel"); 5 | const ITextChannel = require("./ITextChannel"); 6 | const IVoiceChannel = require("./IVoiceChannel"); 7 | const IGuild = require("./IGuild"); 8 | const Utils = require("../core/Utils"); 9 | const Constants = require("../Constants"); 10 | const ChannelTypes = Constants.ChannelTypes; 11 | 12 | /** 13 | * @interface 14 | * @extends ICollectionBase 15 | */ 16 | class IChannelCollection extends ICollectionBase { 17 | constructor(discordie, valuesGetter, valueGetter) { 18 | super({ 19 | valuesGetter: valuesGetter, 20 | valueGetter: valueGetter, 21 | itemFactory: (id) => { 22 | const type = this._discordie._channels.getChannelType(id); 23 | if (type && type === ChannelTypes.GUILD_VOICE) { 24 | return new IVoiceChannel(this._discordie, id); 25 | } 26 | return new ITextChannel(this._discordie, id); 27 | } 28 | }); 29 | Utils.definePrivate(this, {_discordie: discordie}); 30 | } 31 | 32 | /** 33 | * Creates an array of `IChannel` (`ITextChannel` and `IVoiceChannel`) 34 | * for `guild`. 35 | * @param {IGuild|String} guild 36 | * @returns {Array} 37 | */ 38 | forGuild(guild) { 39 | return this.filter(channel => channel.guild_id == guild.valueOf()); 40 | } 41 | 42 | /** 43 | * Creates an array of `ITextChannel` for `guild`. 44 | * @param {IGuild|String} guild 45 | * @returns {Array} 46 | */ 47 | textForGuild(guild) { 48 | return this.filter(channel => 49 | channel.guild_id == guild && channel.type == ChannelTypes.GUILD_TEXT 50 | ); 51 | } 52 | 53 | /** 54 | * Creates an array of `IVoiceChannel` for `guild`. 55 | * @param {IGuild|String} guild 56 | * @returns {Array} 57 | */ 58 | voiceForGuild(guild) { 59 | return this.filter(channel => 60 | channel.guild_id == guild && channel.type == ChannelTypes.GUILD_VOICE 61 | ); 62 | } 63 | } 64 | 65 | module.exports = IChannelCollection; 66 | -------------------------------------------------------------------------------- /lib/interfaces/IDirectMessageChannelCollection.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const ICollectionBase = require("./ICollectionBase"); 4 | const IDirectMessageChannel = require("./IDirectMessageChannel"); 5 | const Utils = require("../core/Utils"); 6 | const Constants = require("../Constants"); 7 | const ChannelTypes = Constants.ChannelTypes; 8 | 9 | const rest = require("../networking/rest"); 10 | 11 | /** 12 | * @interface 13 | * @extends ICollectionBase 14 | */ 15 | class IDirectMessageChannelCollection extends ICollectionBase { 16 | constructor(discordie, valuesGetter, valueGetter) { 17 | super({ 18 | valuesGetter: valuesGetter, 19 | valueGetter: valueGetter, 20 | itemFactory: (id) => new IDirectMessageChannel(this._discordie, id) 21 | }); 22 | this._discordie = discordie; 23 | Utils.privatify(this); 24 | } 25 | 26 | /** 27 | * Gets a DM channel from cache or makes a request to create one. 28 | * @param {IUser|IGuildMember|String} recipient 29 | * @returns {Promise} 30 | */ 31 | getOrOpen(recipient) { 32 | const existing = this.find(c => 33 | c.type === ChannelTypes.DM && 34 | c.recipients.length === 1 && 35 | c.recipients[0].equals(recipient) 36 | ); 37 | if (existing) 38 | return Promise.resolve(existing); 39 | return this.open(recipient); 40 | } 41 | 42 | /** 43 | * Makes a request to create a DM channel. 44 | * @param {IUser|IGuildMember|String} recipient 45 | * @returns {Promise} 46 | */ 47 | open(recipient) { 48 | recipient = recipient.valueOf(); 49 | return this.createGroupDM([recipient]); 50 | } 51 | 52 | /** 53 | * Makes a request to create a group DM channel. 54 | * 55 | * Bot accounts cannot use this endpoint. 56 | * @param {Array} [recipients] 57 | * @returns {Promise} 58 | */ 59 | createGroupDM(recipients) { 60 | recipients = recipients || []; 61 | recipients = recipients.filter(u => u).map(u => u.valueOf()); 62 | const userId = this._discordie.User.id; 63 | return new Promise((rs, rj) => { 64 | rest(this._discordie) 65 | .users.createDirectMessageChannel(userId, recipients) 66 | .then(c => rs(this._discordie.DirectMessageChannels.get(c.id))) 67 | .catch(rj); 68 | }); 69 | } 70 | } 71 | 72 | module.exports = IDirectMessageChannelCollection; 73 | -------------------------------------------------------------------------------- /lib/interfaces/IGuildCollection.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const ICollectionBase = require("./ICollectionBase"); 4 | const IGuild = require("./IGuild"); 5 | const Utils = require("../core/Utils"); 6 | 7 | const rest = require("../networking/rest"); 8 | 9 | /** 10 | * @interface 11 | * @extends ICollectionBase 12 | */ 13 | class IGuildCollection extends ICollectionBase { 14 | constructor(discordie, valuesGetter, valueGetter) { 15 | super({ 16 | valuesGetter: valuesGetter, 17 | valueGetter: valueGetter, 18 | itemFactory: (id) => new IGuild(this._discordie, id) 19 | }); 20 | this._discordie = discordie; 21 | Utils.privatify(this); 22 | } 23 | 24 | /** 25 | * Makes a request to create a guild. 26 | * @param {String} name 27 | * @param {String} region 28 | * @param {Buffer|null} [icon] 29 | * @param {Array} [roles] 30 | * @param {Array} [channels] 31 | * @param {Number} [verificationLevel] 32 | * See Discordie.VerificationLevel 33 | * @param {Number} [defaultMessageNotifications] 34 | * See Discordie.UserNotificationSettings 35 | * @returns {Promise} 36 | */ 37 | create(name, region, icon, 38 | roles, channels, 39 | verificationLevel, defaultMessageNotifications) { 40 | if (icon instanceof Buffer) { 41 | icon = Utils.imageToDataURL(icon); 42 | } 43 | 44 | const toRaw = (data, param) => { 45 | data = data || []; 46 | if (!Array.isArray(data)) 47 | throw TypeError("Param '" + param + "' must be an array"); 48 | 49 | return data.map(v => { 50 | return typeof v.getRaw === "function" ? v.getRaw() : null; 51 | }).filter(v => v); 52 | }; 53 | 54 | roles = toRaw(roles, "roles"); 55 | channels = toRaw(channels, "channels"); 56 | 57 | return new Promise((rs, rj) => { 58 | rest(this._discordie).guilds.createGuild( 59 | name, region, icon, 60 | roles, channels, 61 | verificationLevel, defaultMessageNotifications 62 | ) 63 | .then(guild => rs(this._discordie.Guilds.get(guild.id))) 64 | .catch(rj); 65 | }); 66 | } 67 | 68 | /** 69 | * Makes a request to get a default list of voice regions. 70 | * Use IGuild.fetchRegions for getting guild-specific list. 71 | * @returns {Promise, Error>} 72 | */ 73 | fetchRegions() { 74 | return rest(this._discordie).voice.getRegions(); 75 | } 76 | } 77 | 78 | module.exports = IGuildCollection; 79 | -------------------------------------------------------------------------------- /lib/interfaces/IInviteManager.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Utils = require("../core/Utils"); 4 | 5 | const rest = require("../networking/rest"); 6 | 7 | /** 8 | * @interface 9 | */ 10 | class IInviteManager { 11 | constructor(discordie) { 12 | this._discordie = discordie; 13 | Utils.privatify(this); 14 | Object.freeze(this); 15 | } 16 | 17 | /** 18 | * Makes a request to create an invite. 19 | * See `IChannel.createInvite` for more info. 20 | * @see IChannel.createInvite 21 | * @param {IChannel|String} channel 22 | * @param {Object} options 23 | * @returns {Promise} 24 | */ 25 | create(channel, options) { 26 | options = options || { 27 | max_age: 60 * 30, 28 | // value in seconds 29 | max_uses: 0, 30 | // pretty obvious 31 | temporary: false 32 | // temporary membership, kicks members without roles on disconnect 33 | }; 34 | channel = channel.valueOf(); 35 | return rest(this._discordie).invites.createInvite(channel, options); 36 | } 37 | 38 | /** 39 | * Makes a request to regenerate existing invite. 40 | * @param {Object|String} code 41 | * @returns {Promise} 42 | */ 43 | regenerate(code) { 44 | if (code && code.code) code = code.code; 45 | const options = {regenerate: code}; 46 | return rest(this._discordie).invites.createInvite(channel, options); 47 | } 48 | 49 | /** 50 | * Makes a request to revoke existing invite. 51 | * @param {Object|String} code 52 | * @returns {Promise} 53 | */ 54 | revoke(code) { 55 | if (code && code.code) code = code.code; 56 | return rest(this._discordie).invites.deleteInvite(code); 57 | } 58 | 59 | /** 60 | * Makes a request to resolve existing invite. 61 | * @param {Object|String} code 62 | * @returns {Promise} 63 | * @example 64 | * client.Invites.resolve("Zt5yW").then(console.log).catch(console.log); 65 | * // Example response: 66 | * { 67 | * "code": "Zt5yW", 68 | * "guild": { 69 | * "splash_hash": null, 70 | * "id": "00000000000000000", 71 | * "name": "test" 72 | * }, 73 | * "channel": { 74 | * "type": "text", 75 | * "id": "000000000000000000", 76 | * "name": "testchannel" 77 | * } 78 | * } 79 | */ 80 | resolve(code) { 81 | if (code && code.code) code = code.code; 82 | return rest(this._discordie).invites.getInvite(code); 83 | } 84 | 85 | /** 86 | * **Deprecated**: Only works with user accounts. 87 | * Bot accounts can be invited by users with Manage Server permission using 88 | * the `https://discordapp.com/oauth2/authorize?client_id=%APP_ID%&scope=bot` 89 | * page. See official Discord API documentation for more info. 90 | * 91 | * Makes a request to accept existing invite. 92 | * @param {Object|String} code 93 | * @returns {Promise} 94 | */ 95 | accept(code) { 96 | if (code && code.code) code = code.code; 97 | return rest(this._discordie).invites.postInvite(code); 98 | } 99 | } 100 | 101 | module.exports = IInviteManager; 102 | -------------------------------------------------------------------------------- /lib/interfaces/IPermissionOverwrite.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const IBase = require("./IBase"); 4 | const IPermissions = require("./IPermissions"); 5 | const Utils = require("../core/Utils"); 6 | const PermissionOverwrite = require("../models/PermissionOverwrite"); 7 | 8 | const Constants = require("../Constants"); 9 | const ChannelTypes = Constants.ChannelTypes; 10 | const PermissionSpecs = Constants.PermissionSpecs; 11 | 12 | const rest = require("../networking/rest"); 13 | 14 | /** 15 | * @interface 16 | * @model PermissionOverwrite 17 | * @extends IBase 18 | */ 19 | class IPermissionOverwrite extends IBase { 20 | constructor(discordie, overwriteId, channelId) { 21 | super(); 22 | this._discordie = discordie; 23 | this._overwriteId = overwriteId; 24 | this._channelId = channelId; 25 | 26 | const channel = discordie._channels.get(channelId) || {}; 27 | 28 | const spec = (channel.type == ChannelTypes.GUILD_VOICE ? 29 | PermissionSpecs.VoiceChannel : 30 | PermissionSpecs.TextChannel 31 | ); 32 | this._allow = new IPermissions(this.getRaw().allow, spec); 33 | this._deny = new IPermissions(this.getRaw().deny, spec); 34 | 35 | Utils.privatify(this); 36 | 37 | Object.freeze(this); 38 | } 39 | 40 | /** 41 | * Gets date and time the member or role of this overwrite was created at. 42 | * @returns {Date} 43 | * @readonly 44 | * @ignore 45 | */ 46 | get createdAt() { 47 | return new Date(Utils.timestampFromSnowflake(this.id)); 48 | } 49 | 50 | /** 51 | * Loads original permissions from cache and updates this object. 52 | */ 53 | reload() { 54 | const raw = this.getRaw(); 55 | if (!raw) return; 56 | this._allow.raw = raw.allow; 57 | this._deny.raw = raw.deny; 58 | } 59 | 60 | /** 61 | * Makes a request to commit changes made to this permission overwrite object. 62 | * @returns {Promise} 63 | */ 64 | commit() { 65 | return new Promise((rs, rj) => { 66 | const raw = this.getRaw(); 67 | raw.allow = this._allow.raw; 68 | raw.deny = this._deny.raw; 69 | 70 | rest(this._discordie) 71 | .channels.putPermissionOverwrite(this._channelId, raw) 72 | .then(() => rs(this)) 73 | .catch(e => { 74 | this.reload(); 75 | return rj(e); 76 | }); 77 | }); 78 | } 79 | 80 | /** 81 | * Makes a request to delete this permission overwrite. 82 | * @returns {Promise} 83 | */ 84 | delete() { 85 | return rest(this._discordie) 86 | .channels.deletePermissionOverwrite(this._channelId, this.id); 87 | } 88 | } 89 | 90 | IPermissionOverwrite._inherit(PermissionOverwrite, function(key) { 91 | const channel = this._discordie._channels.get(this._channelId); 92 | if (!channel) return null; 93 | const overwrite = channel.permission_overwrites 94 | .find(o => o.id == this._overwriteId); 95 | if (!overwrite) return null; 96 | return overwrite[key]; 97 | }); 98 | 99 | /** 100 | * @readonly 101 | * @instance 102 | * @memberOf IPermissionOverwrite 103 | * @name allow 104 | * @returns {IPermissions} 105 | */ 106 | IPermissionOverwrite._setValueOverride("allow", function(v) { 107 | return this._allow; 108 | }); 109 | 110 | /** 111 | * @readonly 112 | * @instance 113 | * @memberOf IPermissionOverwrite 114 | * @name deny 115 | * @returns {IPermissions} 116 | */ 117 | IPermissionOverwrite._setValueOverride("deny", function(v) { 118 | return this._deny; 119 | }); 120 | 121 | module.exports = IPermissionOverwrite; 122 | -------------------------------------------------------------------------------- /lib/interfaces/IRole.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const IBase = require("./IBase"); 4 | const IPermissions = require("./IPermissions"); 5 | const Utils = require("../core/Utils"); 6 | const Role = require("../models/Role"); 7 | 8 | const Constants = require("../Constants"); 9 | const PermissionSpecs = Constants.PermissionSpecs; 10 | 11 | const rest = require("../networking/rest"); 12 | 13 | /** 14 | * @interface 15 | * @extends IBase 16 | * @model Role 17 | */ 18 | class IRole extends IBase { 19 | constructor(discordie, roleId, guildId) { 20 | super(); 21 | Utils.definePrivate(this, { 22 | _discordie: discordie, 23 | _roleId: roleId, 24 | _guildId: guildId 25 | }); 26 | Utils.definePrivate(this, { 27 | _permissions: new IPermissions( 28 | this.getRaw("permissions"), 29 | PermissionSpecs.Role 30 | ) 31 | }); 32 | 33 | Object.freeze(this); 34 | } 35 | 36 | /** 37 | * Creates a mention from this role's id. 38 | * @returns {String} 39 | * @readonly 40 | * @example 41 | * channel.sendMessage(role.mention + ", example mention"); 42 | */ 43 | get mention() { 44 | return `<@&${this.id}>`; 45 | } 46 | 47 | /** 48 | * Loads original permissions from cache and updates this object. 49 | */ 50 | reload() { 51 | const rawPermissions = this.getRaw("permissions"); 52 | if (!rawPermissions) return; 53 | this._permissions.raw = rawPermissions; 54 | } 55 | 56 | /** 57 | * Makes a request to commit changes made to this role object. 58 | * @param {String} [name] 59 | * @param {Number} [color] - RGB color number 60 | * @param {boolean} [hoist] - true if role should be displayed separately 61 | * @param {boolean} [mentionable] 62 | * @returns {Promise} 63 | */ 64 | commit(name, color, hoist, mentionable) { 65 | const everyone = (this.id == this._guildId); 66 | if (!name || everyone) name = this.name; 67 | if (hoist === undefined || hoist === null || everyone) hoist = this.hoist; 68 | if (color === undefined || color === null || everyone) color = this.color; 69 | if (mentionable === undefined || mentionable === null || everyone) { 70 | mentionable = this.mentionable; 71 | } 72 | 73 | return new Promise((rs, rj) => { 74 | rest(this._discordie).guilds.roles.patchRole( 75 | this._guildId, this.id, 76 | name, this.permissions.raw, color, hoist, mentionable 77 | ) 78 | .then(() => rs(this)) 79 | .catch(err => { 80 | this.reload(); 81 | return rj(err); 82 | }); 83 | }); 84 | } 85 | 86 | /** 87 | * Moves this role to `position` and makes a batch role update request. 88 | * @param {Number} position 89 | * @returns {Promise} 90 | */ 91 | setPosition(position) { 92 | const guild = this._discordie.Guilds.get(this._guildId); 93 | if (!guild) return Promise.reject(new Error("Guild does not exist")); 94 | 95 | // maybe todo: disallow assigning position 0 (role @everyone) 96 | const changes = Utils.reorderObjects(guild.roles, this, position); 97 | if (!changes) return Promise.resolve(); 98 | 99 | return rest(this._discordie) 100 | .guilds.roles.batchPatchRoles(this._guildId, changes); 101 | } 102 | 103 | /** 104 | * Makes a request to delete this role. 105 | * @returns {Promise} 106 | */ 107 | delete() { 108 | return rest(this._discordie) 109 | .guilds.roles.deleteRole(this._guildId, this.id); 110 | } 111 | } 112 | 113 | IRole._inherit(Role, function modelPropertyGetter(key) { 114 | const guild = this._discordie._guilds.get(this._guildId); 115 | if (!guild) return null; 116 | const role = guild.roles.get(this._roleId); 117 | if (!role) return null; 118 | return role[key]; 119 | }); 120 | 121 | /** 122 | * @readonly 123 | * @instance 124 | * @memberOf IRole 125 | * @name permissions 126 | * @returns {IPermissions} 127 | */ 128 | IRole._setValueOverride("permissions", function(v) { 129 | return this._permissions; 130 | }); 131 | 132 | module.exports = IRole; 133 | -------------------------------------------------------------------------------- /lib/interfaces/IVoiceChannel.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Utils = require("../core/Utils"); 4 | const User = require("../models/User"); 5 | const IChannel = require("./IChannel"); 6 | 7 | /** 8 | * @interface 9 | * @model Channel 10 | * @extends IChannel 11 | */ 12 | class IVoiceChannel extends IChannel { 13 | constructor(discordie, channelId) { 14 | super(discordie, channelId); 15 | } 16 | 17 | /** 18 | * Creates an array of members joined in this voice channels. 19 | * @returns {Array} 20 | * @readonly 21 | */ 22 | get members() { 23 | return this._discordie.Users.membersInVoiceChannel(this); 24 | } 25 | 26 | /** 27 | * Checks whether current user is in this voice channel. 28 | * @returns {boolean} 29 | * @readonly 30 | */ 31 | get joined() { 32 | const vc = this._discordie.VoiceConnections; 33 | const pendingChannel = vc.getPendingChannel(this.guild_id); 34 | return !!(pendingChannel && pendingChannel === this._channelId); 35 | } 36 | 37 | /** 38 | * Joins this voice channel. 39 | * Creates a new voice connection if there are no active connections for 40 | * this channels' guild. 41 | * 42 | * > **Note:** One account can be only in one channel per guild. 43 | * > Promise will resolve instantly and contain the same instance 44 | * > if connection to the server is already established. 45 | * 46 | * If there is a pending connection for the guild this channel belongs to, 47 | * it will return the same promise. 48 | * 49 | * Checks permissions locally and returns a rejected promise with: 50 | * 51 | * - **`Error "Missing permission"`** if `Voice.CONNECT` permission is denied. 52 | * 53 | * - **`Error "Channel is full"`** if `Voice.MOVE_MEMBERS` permission is 54 | * denied and channel is full. 55 | * 56 | * Returns a rejected promise with **`Error "Channel does not exist"`** if 57 | * guild is unavailable or channel does not exist in cache. 58 | * 59 | * Returned promise can be cancelled or rejected: see `VOICE_DISCONNECTED` 60 | * event for more info. 61 | * 62 | * @param {boolean} [selfMute] 63 | * @param {boolean} [selfDeaf] 64 | * @returns {Promise} 65 | */ 66 | join(selfMute, selfDeaf) { 67 | selfMute = !!selfMute; 68 | selfDeaf = !!selfDeaf; 69 | 70 | if (!this._valid) 71 | return Promise.reject(new Error("Channel does not exist")); 72 | 73 | // check permissions locally 74 | // since server silently drops invalid voice state updates 75 | if (!this.joined) { 76 | const permissions = this._discordie.User.permissionsFor(this); 77 | if (!permissions.Voice.CONNECT) 78 | return Promise.reject(new Error("Missing permission")); 79 | 80 | if (this.user_limit > 0) { 81 | const states = this._discordie._voicestates.getStatesInChannel(this.id); 82 | if (states.size >= this.user_limit) { 83 | if (!permissions.Voice.MOVE_MEMBERS) 84 | return Promise.reject(new Error("Channel is full")); 85 | } 86 | } 87 | } 88 | 89 | const vc = this._discordie.VoiceConnections; 90 | return vc._getOrCreate(this.guild_id, this._channelId, selfMute, selfDeaf); 91 | } 92 | 93 | /** 94 | * Leaves this voice channel if joined. 95 | */ 96 | leave() { 97 | const info = this.getVoiceConnectionInfo(); 98 | if (info) return info.voiceConnection.disconnect(); 99 | 100 | this._discordie.VoiceConnections 101 | .cancelIfPending(this.guild_id, this._channelId); 102 | } 103 | 104 | /** 105 | * Retrieves `VoiceConnectionInfo` for this voice channel. 106 | * @returns {VoiceConnectionInfo|null} 107 | */ 108 | getVoiceConnectionInfo() { 109 | return this._discordie.VoiceConnections.getForChannel(this._channelId); 110 | } 111 | } 112 | 113 | module.exports = IVoiceChannel; 114 | -------------------------------------------------------------------------------- /lib/models/AuthenticatedUser.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../Constants"); 4 | const StatusTypes = Constants.StatusTypes; 5 | const BaseModel = require("./BaseModel"); 6 | 7 | /** 8 | * @kind model 9 | * @alias AuthenticatedUser 10 | */ 11 | const BaseAuthenticatedUser = { 12 | /** @returns {String|null} */ 13 | id: null, 14 | /** @returns {String|null} */ 15 | username: "", 16 | /** @returns {String|null} */ 17 | discriminator: null, 18 | /** @returns {String|null} */ 19 | email: null, 20 | /** @returns {boolean|null} */ 21 | verified: false, 22 | /** @returns {String|null} */ 23 | status: StatusTypes.ONLINE, 24 | /** @returns {String|null} */ 25 | avatar: null, 26 | /** @returns {String|null} */ 27 | token: null, 28 | /** @returns {boolean|null} */ 29 | bot: false, 30 | /** @returns {boolean|null} */ 31 | mfa_enabled: false, 32 | /** @returns {Object|null} */ 33 | game: null, // clientside cache 34 | /** @returns {boolean|null} */ 35 | afk: false // clientside cache 36 | }; 37 | 38 | class AuthenticatedUser extends BaseModel { 39 | constructor(def) { 40 | super(BaseAuthenticatedUser, def); 41 | } 42 | } 43 | 44 | module.exports = AuthenticatedUser; 45 | -------------------------------------------------------------------------------- /lib/models/BaseModel.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | class BaseModel { 4 | constructor(base, def) { 5 | for (let k in base) { 6 | this[k] = ((def && def.hasOwnProperty(k)) ? def[k] : base[k]); 7 | } 8 | Object.freeze(this); 9 | } 10 | merge(def) { 11 | if (!def) { 12 | return this; 13 | } 14 | let merged = {}; 15 | for (let k in this) { 16 | merged[k] = this[k]; 17 | } 18 | for (let k in def) { 19 | if (merged.hasOwnProperty(k)) { 20 | merged[k] = def[k]; 21 | } 22 | } 23 | return new this.constructor(merged); 24 | } 25 | } 26 | 27 | module.exports = BaseModel; 28 | -------------------------------------------------------------------------------- /lib/models/Call.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../Constants"); 4 | const BaseModel = require("./BaseModel"); 5 | 6 | /** 7 | * @kind model 8 | * @alias Call 9 | */ 10 | const BaseCall = { 11 | /** @returns {String|null} */ 12 | channel_id: null, 13 | /** @returns {String|null} */ 14 | message_id: null, 15 | /** @returns {String|null} */ 16 | region: null, 17 | /** @returns {Array|null} */ 18 | ringing: [], 19 | /** @returns {boolean|null} */ 20 | unavailable: false 21 | }; 22 | 23 | class Call extends BaseModel { 24 | constructor(def) { 25 | super(BaseCall, def); 26 | } 27 | } 28 | 29 | module.exports = Call; 30 | -------------------------------------------------------------------------------- /lib/models/Channel.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../Constants"); 4 | const ChannelTypes = Constants.ChannelTypes; 5 | const BaseModel = require("./BaseModel"); 6 | 7 | /** 8 | * @kind model 9 | * @alias Channel 10 | */ 11 | const BaseChannel = { 12 | /** @returns {String|null} */ 13 | id: null, 14 | /** @returns {String|null} */ 15 | name: "", 16 | /** @returns {String|null} */ 17 | topic: "", 18 | /** @returns {Number|null} */ 19 | position: 0, 20 | /** @returns {Number|null} */ 21 | type: ChannelTypes.GUILD_TEXT, 22 | /** @returns {String|null} */ 23 | guild_id: null, 24 | /** @returns {Set|null} */ 25 | recipients: new Set(), 26 | /** @returns {Array|null} */ 27 | permission_overwrites: [], 28 | /** @returns {Number|null} */ 29 | bitrate: 0, 30 | /** @returns {Number|null} */ 31 | user_limit: 0, 32 | /** @returns {String|null} */ 33 | owner_id: null, 34 | /** @returns {String|null} */ 35 | icon: null 36 | }; 37 | 38 | class Channel extends BaseModel { 39 | constructor(def) { 40 | super(BaseChannel, def); 41 | } 42 | } 43 | 44 | module.exports = Channel; 45 | -------------------------------------------------------------------------------- /lib/models/Guild.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../Constants"); 4 | const BaseModel = require("./BaseModel"); 5 | 6 | /** 7 | * @kind model 8 | * @alias Guild 9 | */ 10 | const BaseGuild = { 11 | /** @returns {String|null} */ 12 | id: null, 13 | /** @returns {String|null} */ 14 | name: null, 15 | /** @returns {String|null} */ 16 | owner_id: null, 17 | /** @returns {String|null} */ 18 | icon: null, 19 | /** @returns {String|null} */ 20 | splash: null, 21 | /** @returns {Set|null} */ 22 | features: new Set(), 23 | /** @returns {Array|null} */ 24 | emojis: [], 25 | /** @returns {Number|null} */ 26 | default_message_notifications: 0, 27 | /** @returns {Map|null} */ 28 | roles: new Map(), 29 | /** @returns {String|null} */ 30 | afk_channel_id: null, 31 | /** @returns {Number|null} */ 32 | afk_timeout: null, 33 | /** @returns {Number|null} */ 34 | verification_level: 0, 35 | /** @returns {String|null} */ 36 | region: null, 37 | /** @returns {Number|null} */ 38 | member_count: 0, 39 | /** @returns {Boolean|null} */ 40 | large: false, 41 | /** @returns {Number|null} */ 42 | mfa_level: 0, 43 | /** @returns {String|null} */ 44 | joined_at: "" 45 | }; 46 | 47 | class Guild extends BaseModel { 48 | constructor(def) { 49 | super(BaseGuild, def); 50 | } 51 | } 52 | 53 | module.exports = Guild; 54 | -------------------------------------------------------------------------------- /lib/models/GuildMember.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../Constants"); 4 | const BaseModel = require("./BaseModel"); 5 | 6 | /** 7 | * @kind model 8 | * @alias GuildMember 9 | */ 10 | const BaseGuildMember = { 11 | /** @returns {String|null} */ 12 | id: null, 13 | /** @returns {String|null} */ 14 | guild_id: null, 15 | /** @returns {String|null} */ 16 | nick: null, 17 | /** @returns {Array|null} */ 18 | roles: [], 19 | /** @returns {boolean|null} */ 20 | mute: false, 21 | /** @returns {boolean|null} */ 22 | deaf: false, 23 | /** @returns {boolean|null} */ 24 | self_mute: false, 25 | /** @returns {boolean|null} */ 26 | self_deaf: false, 27 | /** @returns {String|null} */ 28 | joined_at: "" 29 | }; 30 | 31 | class GuildMember extends BaseModel { 32 | constructor(def) { 33 | super(BaseGuildMember, def); 34 | } 35 | } 36 | 37 | module.exports = GuildMember; 38 | -------------------------------------------------------------------------------- /lib/models/Message.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../Constants"); 4 | const MessageTypes = Constants.MessageTypes; 5 | const BaseModel = require("./BaseModel"); 6 | 7 | /** 8 | * @kind model 9 | * @alias Message 10 | */ 11 | const BaseMessage = { 12 | /** @returns {String|null} */ 13 | id: null, 14 | /** @returns {Number|null} */ 15 | type: MessageTypes.DEFAULT, 16 | /** @returns {String|null} */ 17 | channel_id: null, 18 | /** @returns {User|null} */ 19 | author: null, 20 | /** @returns {String|null} */ 21 | content: "", 22 | /** @returns {Array|null} */ 23 | attachments: [], 24 | /** @returns {Array|null} */ 25 | embeds: [], 26 | /** @returns {Array|null} */ 27 | mentions: [], 28 | /** @returns {Array|null} */ 29 | mention_roles: [], 30 | /** @returns {boolean|null} */ 31 | mention_everyone: false, 32 | /** @returns {boolean|null} */ 33 | tts: false, 34 | /** @returns {String|null} */ 35 | timestamp: "", 36 | /** @returns {String|null} */ 37 | edited_timestamp: null, 38 | /** @returns {String|null} */ 39 | nonce: null, 40 | /** @returns {String|null} */ 41 | webhook_id: null, 42 | /** @returns {Array|null} */ 43 | reactions: [], 44 | /** @returns {boolean|null} */ 45 | pinned: false, 46 | /** 47 | * Raw MessageCall object: 48 | * 49 | * ```js 50 | * { 51 | * // Array of user ids participating in this call, 52 | * // only used to check if the call was missed 53 | * participants: [ "108721394061205504" ], // Array 54 | * 55 | * // Timestamp when call ended, null if call is in progress 56 | * ended_timestamp: "2016-07-24T06:52:11.860000+00:00" // String | null 57 | * } 58 | * ``` 59 | * @returns {Object|null} 60 | * @memberOf IMessage 61 | * @readonly 62 | * */ 63 | call: null, 64 | 65 | /** @returns {boolean|null} */ 66 | deleted: false // for clientside cache 67 | }; 68 | 69 | class Message extends BaseModel { 70 | constructor(def) { 71 | super(BaseMessage, def); 72 | } 73 | } 74 | 75 | module.exports = Message; 76 | -------------------------------------------------------------------------------- /lib/models/PermissionOverwrite.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../Constants"); 4 | const BaseModel = require("./BaseModel"); 5 | 6 | /** 7 | * @kind model 8 | * @alias PermissionOverwrite 9 | */ 10 | const BasePermissionOverwrite = { 11 | /** @returns {String|null} */ 12 | id: null, 13 | /** @returns {String|null} */ 14 | type: null, 15 | /** @returns {Number|null} */ 16 | allow: 0, 17 | /** @returns {Number|null} */ 18 | deny: 0 19 | }; 20 | 21 | class PermissionOverwrite extends BaseModel { 22 | constructor(def) { 23 | super(BasePermissionOverwrite, def); 24 | } 25 | } 26 | 27 | module.exports = PermissionOverwrite; 28 | -------------------------------------------------------------------------------- /lib/models/Role.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../Constants"); 4 | const BaseModel = require("./BaseModel"); 5 | 6 | /** 7 | * @kind model 8 | * @alias Role 9 | */ 10 | const BaseRole = { 11 | /** @returns {String|null} */ 12 | id: null, 13 | /** @returns {String|null} */ 14 | name: null, 15 | /** @returns {Number|null} */ 16 | permissions: 0, 17 | /** @returns {boolean|null} */ 18 | mentionable: false, 19 | /** @returns {Number|null} */ 20 | position: -1, 21 | /** @returns {boolean|null} */ 22 | hoist: false, 23 | /** @returns {Number|null} */ 24 | color: 0, 25 | /** @returns {boolean|null} */ 26 | managed: false 27 | }; 28 | 29 | class Role extends BaseModel { 30 | constructor(def) { 31 | super(BaseRole, def); 32 | } 33 | } 34 | 35 | module.exports = Role; 36 | -------------------------------------------------------------------------------- /lib/models/User.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../Constants"); 4 | const BaseModel = require("./BaseModel"); 5 | 6 | /** 7 | * @kind model 8 | * @alias User 9 | */ 10 | const BaseUser = { 11 | /** @returns {String|null} */ 12 | id: null, 13 | /** @returns {String|null} */ 14 | username: "", 15 | /** @returns {String|null} */ 16 | discriminator: null, 17 | /** @returns {String|null} */ 18 | avatar: null, 19 | /** @returns {boolean|null} */ 20 | bot: false, 21 | }; 22 | 23 | class User extends BaseModel { 24 | constructor(def) { 25 | super(BaseUser, def); 26 | } 27 | } 28 | 29 | module.exports = User; 30 | -------------------------------------------------------------------------------- /lib/networking/messages/MessageValidator.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = function(schema, data) { 4 | 5 | }; 6 | -------------------------------------------------------------------------------- /lib/networking/messages/gateway/call_create.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | 6 | module.exports = function handler(data, gw) { 7 | if (!gw.isPrimary) return true; 8 | 9 | const channel = this.DirectMessageChannels.get(data.channel_id); 10 | if (!channel) return true; 11 | 12 | this.Dispatcher.emit(Events.CALL_CREATE, { 13 | socket: gw, 14 | channel: channel, 15 | call: channel.call 16 | }); 17 | return true; 18 | }; 19 | -------------------------------------------------------------------------------- /lib/networking/messages/gateway/call_delete.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | 6 | module.exports = function handler(data, gw) { 7 | if (!gw.isPrimary) return true; 8 | 9 | const channel = this.DirectMessageChannels.get(data.channel_id); 10 | if (!channel) return true; 11 | 12 | if (data.unavailable) { 13 | this.Dispatcher.emit(Events.CALL_UNAVAILABLE, { 14 | socket: gw, 15 | channelId: data.channel_id, 16 | data: data 17 | }); 18 | return true; 19 | } 20 | 21 | this.Dispatcher.emit(Events.CALL_DELETE, { 22 | socket: gw, 23 | channelId: data.channel_id, 24 | data: data 25 | }); 26 | return true; 27 | }; 28 | -------------------------------------------------------------------------------- /lib/networking/messages/gateway/call_update.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | 6 | module.exports = function handler(data, gw) { 7 | if (!gw.isPrimary) return true; 8 | 9 | const channel = this.DirectMessageChannels.get(data.channel_id); 10 | if (!channel) return true; 11 | 12 | this.Dispatcher.emit(Events.CALL_UPDATE, { 13 | socket: gw, 14 | channel: channel, 15 | call: channel.call 16 | }); 17 | return true; 18 | }; 19 | -------------------------------------------------------------------------------- /lib/networking/messages/gateway/channel_create.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | 6 | module.exports = function handler(data, gw) { 7 | if (!gw.isPrimary) return true; 8 | this.Dispatcher.emit(Events.CHANNEL_CREATE, { 9 | socket: gw, 10 | channel: this.Channels.get(data.id) || 11 | this.DirectMessageChannels.get(data.id) 12 | }); 13 | return true; 14 | }; 15 | -------------------------------------------------------------------------------- /lib/networking/messages/gateway/channel_delete.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | 6 | module.exports = function handler(data, gw) { 7 | if (!gw.isPrimary) return true; 8 | this.Dispatcher.emit(Events.CHANNEL_DELETE, { 9 | socket: gw, 10 | channelId: data.id, 11 | data: data 12 | }); 13 | return true; 14 | }; 15 | -------------------------------------------------------------------------------- /lib/networking/messages/gateway/channel_recipient_add.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | 6 | module.exports = function handler(data, gw) { 7 | if (!gw.isPrimary) return true; 8 | 9 | const channel = this.DirectMessageChannels.get(data.channel_id); 10 | const user = this.Users.get(data.user && data.user.id); 11 | if (!channel || !user) return true; 12 | 13 | this.Dispatcher.emit(Events.CHANNEL_RECIPIENT_ADD, { 14 | socket: gw, 15 | channel: channel, 16 | user: user 17 | }); 18 | return true; 19 | }; 20 | -------------------------------------------------------------------------------- /lib/networking/messages/gateway/channel_recipient_remove.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | 6 | module.exports = function handler(data, gw) { 7 | if (!gw.isPrimary) return true; 8 | 9 | const channel = this.DirectMessageChannels.get(data.channel_id); 10 | const user = this.Users.get(data.user && data.user.id); 11 | if (!channel || !user) return true; 12 | 13 | this.Dispatcher.emit(Events.CHANNEL_RECIPIENT_REMOVE, { 14 | socket: gw, 15 | channel: channel, 16 | user: user 17 | }); 18 | return true; 19 | }; 20 | -------------------------------------------------------------------------------- /lib/networking/messages/gateway/channel_update.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Utils = require("../../../core/Utils"); 6 | 7 | module.exports = function handler(data, gw) { 8 | const prev = data._prev; delete data._prev; 9 | const next = data._next; delete data._next; 10 | 11 | if (!gw.isPrimary) return true; 12 | 13 | if (!this.Dispatcher.hasListeners(Events.CHANNEL_UPDATE)) return true; 14 | 15 | this.Dispatcher.emit(Events.CHANNEL_UPDATE, { 16 | socket: gw, 17 | channel: this.Channels.get(data.id) || 18 | this.DirectMessageChannels.get(data.id), 19 | getChanges: () => ({ 20 | before: Utils.modelToObject(prev), 21 | after: Utils.modelToObject(next) 22 | }) 23 | }); 24 | return true; 25 | }; 26 | -------------------------------------------------------------------------------- /lib/networking/messages/gateway/guild_ban_add.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | 6 | module.exports = function handler(data, gw) { 7 | if (!gw.isPrimary) return true; 8 | this.Dispatcher.emit(Events.GUILD_BAN_ADD, { 9 | socket: gw, 10 | guild: this.Guilds.get(data.guild_id), 11 | user: this.Users.get(data.user.id) 12 | }); 13 | return true; 14 | }; 15 | -------------------------------------------------------------------------------- /lib/networking/messages/gateway/guild_ban_remove.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | 6 | module.exports = function handler(data, gw) { 7 | if (!gw.isPrimary) return true; 8 | this.Dispatcher.emit(Events.GUILD_BAN_REMOVE, { 9 | socket: gw, 10 | guild: this.Guilds.get(data.guild_id), 11 | user: this.Users.get(data.user.id) 12 | }); 13 | return true; 14 | }; 15 | -------------------------------------------------------------------------------- /lib/networking/messages/gateway/guild_create.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | 6 | module.exports = function handler(data, gw) { 7 | if (!gw.isPrimary) return true; 8 | if (data.unavailable) { 9 | this.Dispatcher.emit(Events.GUILD_UNAVAILABLE, { 10 | socket: gw, 11 | guildId: data.id 12 | }); 13 | return true; 14 | } 15 | 16 | this.Dispatcher.emit(Events.GUILD_CREATE, { 17 | socket: gw, 18 | guild: this.Guilds.get(data.id), 19 | becameAvailable: this.UnavailableGuilds.isGuildAvailable(data) 20 | }); 21 | return true; 22 | }; 23 | -------------------------------------------------------------------------------- /lib/networking/messages/gateway/guild_delete.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Utils = require("../../../core/Utils"); 6 | 7 | module.exports = function handler(data, gw) { 8 | if (!gw.isPrimary) return true; 9 | if (data.unavailable) { 10 | this.Dispatcher.emit(Events.GUILD_UNAVAILABLE, { 11 | socket: gw, 12 | guildId: data.id, 13 | data: data 14 | }); 15 | return true; 16 | } 17 | 18 | const cached = data._cached; delete data._cached; 19 | 20 | if (!this.Dispatcher.hasListeners(Events.GUILD_DELETE)) return true; 21 | 22 | this.Dispatcher.emit(Events.GUILD_DELETE, { 23 | socket: gw, 24 | guildId: data.id, 25 | data: data, 26 | getCachedData: () => Utils.modelToObject(cached || null) 27 | }); 28 | return true; 29 | }; 30 | -------------------------------------------------------------------------------- /lib/networking/messages/gateway/guild_emojis_update.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Utils = require("../../../core/Utils"); 6 | 7 | module.exports = function handler(data, gw) { 8 | const prev = data._prev; delete data._prev; 9 | const next = data._next; delete data._next; 10 | 11 | if (!gw.isPrimary) return true; 12 | 13 | if (!this.Dispatcher.hasListeners(Events.GUILD_EMOJIS_UPDATE)) return true; 14 | 15 | const guild = this.Guilds.get(data.guild_id); 16 | if (!guild) return; 17 | 18 | this.Dispatcher.emit(Events.GUILD_EMOJIS_UPDATE, { 19 | socket: gw, 20 | guild: guild, 21 | getChanges: () => ({ 22 | before: Utils.modelToObject(prev), 23 | after: Utils.modelToObject(next) 24 | }) 25 | }); 26 | return true; 27 | }; 28 | -------------------------------------------------------------------------------- /lib/networking/messages/gateway/guild_member_add.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | 6 | module.exports = function handler(data, gw) { 7 | if (!gw.isPrimary) return true; 8 | this.Dispatcher.emit(Events.GUILD_MEMBER_ADD, { 9 | socket: gw, 10 | guild: this.Guilds.get(data.guild_id), 11 | member: this.Users.getMember(data.guild_id, data.user.id) 12 | }); 13 | return true; 14 | }; 15 | -------------------------------------------------------------------------------- /lib/networking/messages/gateway/guild_member_remove.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Utils = require("../../../core/Utils"); 6 | 7 | module.exports = function handler(data, gw) { 8 | if (!gw.isPrimary) return true; 9 | 10 | const cached = data._cached; delete data._cached; 11 | 12 | if (!this.Dispatcher.hasListeners(Events.GUILD_MEMBER_REMOVE)) return true; 13 | 14 | if (data.user && data.user.id === this._user.id) return true; 15 | 16 | this.Dispatcher.emit(Events.GUILD_MEMBER_REMOVE, { 17 | socket: gw, 18 | guild: this.Guilds.get(data.guild_id), 19 | user: this.Users.get(data.user.id), 20 | data: data, 21 | getCachedData: () => Utils.modelToObject(cached || null) 22 | }); 23 | return true; 24 | }; 25 | -------------------------------------------------------------------------------- /lib/networking/messages/gateway/guild_member_update.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Utils = require("../../../core/Utils"); 6 | const IRole = require("../../../interfaces/IRole"); 7 | 8 | function diffArray(a, b) { 9 | return a.filter(id => b.indexOf(id) < 0); 10 | } 11 | 12 | function filterExistingRoles(roleIds, guildId) { 13 | const guild = this._guilds.get(guildId); 14 | if (!guild) return []; 15 | return roleIds.filter(id => guild.roles.get(id)); 16 | } 17 | function mapRoles(ids, guildId) { 18 | return filterExistingRoles.call(this, ids, guildId) 19 | .map(id => new IRole(this, id, guildId)); 20 | } 21 | 22 | module.exports = function handler(data, gw) { 23 | const prev = data._prev; delete data._prev; 24 | const next = data._next; delete data._next; 25 | 26 | if (!gw.isPrimary) return true; 27 | 28 | if (!this.Dispatcher.hasListeners(Events.GUILD_MEMBER_UPDATE)) return true; 29 | 30 | const event = { 31 | socket: gw, 32 | guild: this.Guilds.get(data.guild_id), 33 | member: this.Users.getMember(data.guild_id, data.user.id), 34 | rolesAdded: [], 35 | rolesRemoved: [], 36 | previousNick: null, 37 | getChanges: () => ({ 38 | before: Utils.modelToObject(prev), 39 | after: Utils.modelToObject(next) 40 | }) 41 | }; 42 | 43 | if (prev && next) { 44 | if (Array.isArray(prev.roles) && Array.isArray(next.roles)) { 45 | const rolesAdded = diffArray(next.roles, prev.roles); 46 | const rolesRemoved = diffArray(prev.roles, next.roles); 47 | const rolesChanged = rolesAdded.length || rolesRemoved.length; 48 | if (rolesChanged) { 49 | event.rolesAdded = mapRoles.call(this, rolesAdded, data.guild_id); 50 | event.rolesRemoved = mapRoles.call(this, rolesRemoved, data.guild_id); 51 | } 52 | } 53 | 54 | event.previousNick = prev.nick; 55 | } 56 | 57 | this.Dispatcher.emit(Events.GUILD_MEMBER_UPDATE, event); 58 | return true; 59 | }; 60 | -------------------------------------------------------------------------------- /lib/networking/messages/gateway/guild_role_create.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const IRole = require("../../../interfaces/IRole"); 6 | 7 | module.exports = function handler(data, gw) { 8 | if (!gw.isPrimary) return true; 9 | this.Dispatcher.emit(Events.GUILD_ROLE_CREATE, { 10 | socket: gw, 11 | guild: this.Guilds.get(data.guild_id), 12 | role: new IRole(this, data.role.id, data.guild_id) 13 | }); 14 | return true; 15 | }; 16 | -------------------------------------------------------------------------------- /lib/networking/messages/gateway/guild_role_delete.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Utils = require("../../../core/Utils"); 6 | 7 | module.exports = function handler(data, gw) { 8 | if (!gw.isPrimary) return true; 9 | 10 | const cached = data._cached; delete data._cached; 11 | 12 | if (!this.Dispatcher.hasListeners(Events.GUILD_ROLE_DELETE)) return true; 13 | 14 | this.Dispatcher.emit(Events.GUILD_ROLE_DELETE, { 15 | socket: gw, 16 | guild: this.Guilds.get(data.guild_id), 17 | roleId: data.role_id, 18 | getCachedData: () => Utils.modelToObject(cached || null) 19 | }); 20 | return true; 21 | }; 22 | -------------------------------------------------------------------------------- /lib/networking/messages/gateway/guild_role_update.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Utils = require("../../../core/Utils"); 6 | const IRole = require("../../../interfaces/IRole"); 7 | 8 | module.exports = function handler(data, gw) { 9 | const prev = data._prev; delete data._prev; 10 | const next = data._next; delete data._next; 11 | 12 | if (!gw.isPrimary) return true; 13 | 14 | if (!this.Dispatcher.hasListeners(Events.GUILD_ROLE_UPDATE)) return true; 15 | 16 | this.Dispatcher.emit(Events.GUILD_ROLE_UPDATE, { 17 | socket: gw, 18 | guild: this.Guilds.get(data.guild_id), 19 | role: new IRole(this, data.role.id, data.guild_id), 20 | getChanges: () => ({ 21 | before: Utils.modelToObject(prev), 22 | after: Utils.modelToObject(next) 23 | }) 24 | }); 25 | return true; 26 | }; 27 | -------------------------------------------------------------------------------- /lib/networking/messages/gateway/guild_update.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Utils = require("../../../core/Utils"); 6 | 7 | module.exports = function handler(data, gw) { 8 | const prev = data._prev; delete data._prev; 9 | const next = data._next; delete data._next; 10 | 11 | if (!gw.isPrimary) return true; 12 | 13 | if (!this.Dispatcher.hasListeners(Events.GUILD_UPDATE)) return true; 14 | 15 | this.Dispatcher.emit(Events.GUILD_UPDATE, { 16 | socket: gw, 17 | guild: this.Guilds.get(data.id), 18 | getChanges: () => ({ 19 | before: Utils.modelToObject(prev), 20 | after: Utils.modelToObject(next) 21 | }) 22 | }); 23 | return true; 24 | }; 25 | -------------------------------------------------------------------------------- /lib/networking/messages/gateway/message_create.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | 6 | module.exports = function handler(data, gw) { 7 | if (!gw.isPrimary) return true; 8 | 9 | const message = this.Messages.get(data.id); 10 | if (!message) return true; 11 | 12 | this.Dispatcher.emit(Events.MESSAGE_CREATE, { 13 | socket: gw, 14 | message: message 15 | }); 16 | return true; 17 | }; 18 | -------------------------------------------------------------------------------- /lib/networking/messages/gateway/message_delete.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | 6 | module.exports = function handler(data, gw) { 7 | if (!gw.isPrimary) return true; 8 | this.Dispatcher.emit(Events.MESSAGE_DELETE, { 9 | socket: gw, 10 | channelId: data.channel_id, 11 | messageId: data.id, 12 | message: this.Messages.get(data.id) 13 | }); 14 | return true; 15 | }; 16 | -------------------------------------------------------------------------------- /lib/networking/messages/gateway/message_delete_bulk.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | 6 | module.exports = function handler(data, gw) { 7 | if (!gw.isPrimary) return true; 8 | this.Dispatcher.emit(Events.MESSAGE_DELETE_BULK, { 9 | socket: gw, 10 | channelId: data.channel_id, 11 | messageIds: data.ids, 12 | messages: data.ids.map(id => this.Messages.get(id)).filter(e => e) 13 | }); 14 | return true; 15 | }; 16 | -------------------------------------------------------------------------------- /lib/networking/messages/gateway/message_reaction_add.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | 6 | module.exports = function handler(data, gw) { 7 | if (!gw.isPrimary) return true; 8 | 9 | const user = this.Users.get(data.user_id); 10 | const message = this.Messages.get(data.message_id); 11 | const channel = this.Channels.get(data.channel_id) || 12 | this.DirectMessageChannels.get(data.channel_id); 13 | 14 | this.Dispatcher.emit(Events.MESSAGE_REACTION_ADD, { 15 | socket: gw, 16 | user, channel, message, 17 | emoji: data.emoji, 18 | data 19 | }); 20 | return true; 21 | }; 22 | -------------------------------------------------------------------------------- /lib/networking/messages/gateway/message_reaction_remove.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | 6 | module.exports = function handler(data, gw) { 7 | if (!gw.isPrimary) return true; 8 | 9 | const user = this.Users.get(data.user_id); 10 | const message = this.Messages.get(data.message_id); 11 | const channel = this.Channels.get(data.channel_id) || 12 | this.DirectMessageChannels.get(data.channel_id); 13 | 14 | this.Dispatcher.emit(Events.MESSAGE_REACTION_REMOVE, { 15 | socket: gw, 16 | user, channel, message, 17 | emoji: data.emoji, 18 | data 19 | }); 20 | return true; 21 | }; 22 | -------------------------------------------------------------------------------- /lib/networking/messages/gateway/message_reaction_remove_all.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Utils = require("../../../core/Utils"); 6 | 7 | module.exports = function handler(data, gw) { 8 | if (!gw.isPrimary) return true; 9 | 10 | const cached = data._cached; delete data._cached; 11 | 12 | const message = this.Messages.get(data.message_id); 13 | const channel = this.Channels.get(data.channel_id) || 14 | this.DirectMessageChannels.get(data.channel_id); 15 | 16 | this.Dispatcher.emit(Events.MESSAGE_REACTION_REMOVE_ALL, { 17 | socket: gw, 18 | channel, message, 19 | data, 20 | getCachedData: () => Utils.modelToObject(cached || null) 21 | }); 22 | return true; 23 | }; 24 | -------------------------------------------------------------------------------- /lib/networking/messages/gateway/message_update.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | 6 | module.exports = function handler(data, gw) { 7 | if (!gw.isPrimary) return true; 8 | this.Dispatcher.emit(Events.MESSAGE_UPDATE, { 9 | socket: gw, 10 | message: this.Messages.get(data.id), 11 | data: data 12 | }); 13 | return true; 14 | }; 15 | -------------------------------------------------------------------------------- /lib/networking/messages/gateway/presence_update.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | 6 | module.exports = function handler(data, gw) { 7 | if (!gw.isPrimary) return true; 8 | 9 | if (!this.Dispatcher.hasListeners(Events.PRESENCE_UPDATE)) return true; 10 | 11 | if (this.User.id != data.user.id) { 12 | // Presences for friends are emitted without `guild_id` 13 | if (!data.guild_id) return true; 14 | 15 | var user = this.Users.get(data.user.id); 16 | var member = this.Users.getMember(data.guild_id, data.user.id) || user; 17 | var guild = this.Guilds.get(data.guild_id); 18 | 19 | this.Dispatcher.emit(Events.PRESENCE_UPDATE, { 20 | socket: gw, 21 | guild, user, member 22 | }); 23 | } 24 | return true; 25 | }; 26 | -------------------------------------------------------------------------------- /lib/networking/messages/gateway/ready.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const User = require("../../../models/User"); 6 | const AuthenticatedUser = require("../../../models/AuthenticatedUser"); 7 | 8 | module.exports = function handler(data, gw) { 9 | if (gw.isPrimary) { 10 | this._user = new AuthenticatedUser(data.user); 11 | this.bot = this._user.bot; 12 | 13 | // GW V4 READY emits all guilds as unavailable and 14 | // implements data streaming with GUILD_CREATE 15 | 16 | // GATEWAY_READY event has been moved to ReadyEventScheduler 17 | } 18 | return true; 19 | }; 20 | -------------------------------------------------------------------------------- /lib/networking/messages/gateway/resumed.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | 6 | module.exports = function handler(data, gw) { 7 | if (!gw.isPrimary) return true; 8 | this.Dispatcher.emit(Events.GATEWAY_RESUMED, { 9 | socket: gw, 10 | data: data 11 | }); 12 | return true; 13 | }; 14 | -------------------------------------------------------------------------------- /lib/networking/messages/gateway/typing_start.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | 6 | module.exports = function handler(data, gw) { 7 | if (!gw.isPrimary) return true; 8 | 9 | if (!this.Dispatcher.hasListeners(Events.TYPING_START)) return true; 10 | 11 | const channel = this.Channels.get(data.channel_id) || 12 | this.DirectMessageChannels.get(data.channel_id); 13 | if (!channel) return true; 14 | 15 | const user = this.Users.get(data.user_id); 16 | if (!user) return true; 17 | 18 | this.Dispatcher.emit(Events.TYPING_START, { 19 | socket: gw, 20 | user: user, 21 | timestamp: data.timestamp, 22 | channel: channel 23 | }); 24 | return true; 25 | }; 26 | -------------------------------------------------------------------------------- /lib/networking/messages/gateway/user_update.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const User = require("../../../models/User"); 5 | 6 | module.exports = function handler(data, gw) { 7 | return true; 8 | }; 9 | -------------------------------------------------------------------------------- /lib/networking/messages/gateway/voice_server_update.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = function handler(data, gw) { 4 | data.guild_id = data.guild_id || null; 5 | 6 | gw.createVoiceSocket( 7 | data.endpoint, 8 | data.guild_id, data.channel_id, gw.userId, 9 | gw.sessionId, data.token 10 | ); 11 | return true; 12 | }; 13 | -------------------------------------------------------------------------------- /lib/networking/messages/gateway/voice_state_update.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Errors = Constants.Errors; 5 | const DiscordieError = require("../../../core/DiscordieError"); 6 | 7 | function disconnectVoice(gw, data, errorDescription) { 8 | return gw.disconnectVoice( 9 | data.guild_id, 10 | true, /* noStateUpdate=true */ 11 | new DiscordieError(errorDescription) 12 | ); 13 | } 14 | 15 | module.exports = function handler(data, gw) { 16 | data.guild_id = data.guild_id || null; 17 | 18 | const user = this.User; 19 | const isLocal = this.VoiceConnections.isLocalSession(data.session_id); 20 | 21 | const localState = gw.voiceStates.get(data.guild_id); 22 | 23 | if (user.id == data.user_id && localState) { 24 | gw.voiceStateUpdate( 25 | data.guild_id, data.channel_id, 26 | data.self_mute, data.self_deaf, 27 | true /* external=true */ 28 | ); 29 | 30 | if (!isLocal) { 31 | disconnectVoice(gw, data, Errors.VOICE_CONNECTED_FROM_ANOTHER_LOCATION); 32 | } 33 | if (!data.channel_id) { 34 | disconnectVoice(gw, data, Errors.VOICE_KICKED_FROM_CHANNEL); 35 | } 36 | } 37 | 38 | if (user.id != data.user_id && !data.channel_id) { 39 | var voiceSocket = gw.voiceSockets.get(data.guild_id); 40 | if (voiceSocket && voiceSocket.audioDecoder) { 41 | voiceSocket.audioDecoder.destroyUser(data.user_id); 42 | } 43 | } 44 | 45 | return true; 46 | }; 47 | -------------------------------------------------------------------------------- /lib/networking/messages/gateway/webhooks_update.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | 6 | module.exports = function handler(data, gw) { 7 | if (!gw.isPrimary) return true; 8 | 9 | const guild = this.Guilds.get(data.guild_id); 10 | const channel = this.Channels.get(data.channel_id); 11 | if (!guild || !channel) return true; 12 | 13 | this.Dispatcher.emit(Events.WEBHOOKS_UPDATE, { 14 | socket: gw, 15 | guild, channel, data 16 | }); 17 | return true; 18 | }; 19 | -------------------------------------------------------------------------------- /lib/networking/messages/voice/ready.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const VoiceUDP = require("../../voicetransports/VoiceUDP"); 5 | 6 | module.exports = function handler(data, voicews) { 7 | voicews.connectAudioTransport(data.ssrc, data.port, data.modes); 8 | return true; 9 | }; 10 | -------------------------------------------------------------------------------- /lib/networking/messages/voice/session_description.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = function handler(data, voicews) { 4 | voicews.speaking(true, 0); 5 | return true; 6 | }; 7 | -------------------------------------------------------------------------------- /lib/networking/messages/voice/speaking.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = function handler(data, voicews) { 4 | return true; 5 | }; 6 | -------------------------------------------------------------------------------- /lib/networking/rest/auth/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = require("requireindex")(__dirname); 4 | -------------------------------------------------------------------------------- /lib/networking/rest/auth/login.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../core/ApiRequest"); 7 | 8 | module.exports = function(auth) { 9 | return new Promise((rs, rj) => { 10 | apiRequest 11 | .post(this, { 12 | url: Endpoints.LOGIN, 13 | body: auth 14 | }) 15 | .send((err, res) => { 16 | if (err || !res.ok) { 17 | this.Dispatcher.emit( 18 | Events.REQUEST_AUTH_LOGIN_ERROR, 19 | {error: err} 20 | ); 21 | return rj(err); 22 | } 23 | this.Dispatcher.emit( 24 | Events.REQUEST_AUTH_LOGIN_SUCCESS, 25 | {token: res.body.token, password: auth.password} 26 | ); 27 | rs(); 28 | }); 29 | }); 30 | }; 31 | -------------------------------------------------------------------------------- /lib/networking/rest/channels/addReaction.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../core/ApiRequest"); 7 | 8 | module.exports = function(channelId, messageId, emoji) { 9 | emoji = encodeURIComponent(emoji); 10 | 11 | return new Promise((rs, rj) => { 12 | var request = apiRequest 13 | .put(this, Endpoints.REACTION(channelId, messageId, emoji, "@me")); 14 | 15 | const route = 16 | Endpoints.REACTION(channelId, ":messageId", ":emoji", ":userId"); 17 | 18 | this._queueManager.putToRoute(request, route, (err, res) => { 19 | return (!err && res.ok) ? rs() : rj(err); 20 | }); 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /lib/networking/rest/channels/batchPatchChannels.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../core/ApiRequest"); 7 | 8 | module.exports = function(guildId, channels) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .patch(this, { 12 | url: Endpoints.GUILD_CHANNELS(guildId), 13 | body: channels 14 | }); 15 | 16 | this._queueManager.put(request, (err, res) => { 17 | return (!err && res.ok) ? rs() : rj(err); 18 | }); 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /lib/networking/rest/channels/calls/changeRegion.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../../core/ApiRequest"); 7 | 8 | module.exports = function(channelId, region) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .patch(this, { 12 | url: Endpoints.CALL(channelId), 13 | body: {region} 14 | }); 15 | 16 | this._queueManager.put(request, (err, res) => { 17 | return (!err && res.ok) ? rs() : rj(err); 18 | }); 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /lib/networking/rest/channels/calls/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = require("requireindex")(__dirname); 4 | -------------------------------------------------------------------------------- /lib/networking/rest/channels/calls/ring.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../../core/ApiRequest"); 7 | 8 | module.exports = function(channelId, recipients) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .post(this, { 12 | url: Endpoints.CALL_RING(channelId), 13 | body: recipients ? {recipients} : [] 14 | }); 15 | 16 | this._queueManager.put(request, (err, res) => { 17 | return (!err && res.ok) ? rs() : rj(err); 18 | }); 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /lib/networking/rest/channels/calls/stopRinging.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../../core/ApiRequest"); 7 | 8 | module.exports = function(channelId, recipients) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .post(this, { 12 | url: Endpoints.CALL_STOP_RINGING(channelId), 13 | body: recipients ? {recipients} : [] 14 | }); 15 | 16 | this._queueManager.put(request, (err, res) => { 17 | return (!err && res.ok) ? rs() : rj(err); 18 | }); 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /lib/networking/rest/channels/createChannel.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../core/ApiRequest"); 7 | 8 | module.exports = function(guildId, type, name, permissionOverwrites, 9 | bitrate, userLimit) { 10 | return new Promise((rs, rj) => { 11 | const body = { 12 | type: type, 13 | name: name, 14 | permission_overwrites: permissionOverwrites 15 | }; 16 | 17 | if (bitrate != null) body.bitrate = bitrate; 18 | if (userLimit != null) body.user_limit = userLimit; 19 | 20 | var request = apiRequest 21 | .post(this,{ 22 | url: Endpoints.GUILD_CHANNELS(guildId), 23 | body 24 | }); 25 | 26 | this._queueManager.put(request, (err, res) => { 27 | if (err || !res.ok) 28 | return rj(err); 29 | 30 | this._channels.update(res.body); 31 | rs(res.body); 32 | }); 33 | }); 34 | }; 35 | -------------------------------------------------------------------------------- /lib/networking/rest/channels/createMessage.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../core/ApiRequest"); 7 | 8 | function createNonce() { 9 | return (Math.random() * Math.pow(2,53)).toString(10).slice(8) + 10 | (Math.random() * Math.pow(2,53)).toString(10).slice(8); 11 | } 12 | 13 | module.exports = function(channelId, content, mentions, tts, embed) { 14 | return new Promise((rs, rj) => { 15 | var request = apiRequest 16 | .post(this, { 17 | url: Endpoints.MESSAGES(channelId), 18 | body: { 19 | content: content || "", 20 | mentions: mentions || [], 21 | nonce: createNonce(), 22 | tts: !!tts, 23 | embed 24 | } 25 | }); 26 | 27 | this._queueManager.putMessage(request, channelId, (err, res) => { 28 | if (err || !res.ok) 29 | return rj(err); 30 | 31 | this._messages.create(res.body); 32 | rs(res.body); 33 | }); 34 | }); 35 | }; 36 | -------------------------------------------------------------------------------- /lib/networking/rest/channels/deleteChannel.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../core/ApiRequest"); 7 | 8 | module.exports = function(channelId) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .del(this, `${Endpoints.CHANNELS}/${channelId}`); 12 | 13 | this._queueManager.put(request, (err, res) => { 14 | return (!err && res.ok) ? rs() : rj(err); 15 | }); 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /lib/networking/rest/channels/deleteMessage.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../core/ApiRequest"); 7 | 8 | module.exports = function(channelId, messageId) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .del(this, `${Endpoints.MESSAGES(channelId)}/${messageId}`); 12 | 13 | this._queueManager.putDeleteMessage(request, channelId, (err, res) => { 14 | return (!err && res.ok) ? rs() : rj(err); 15 | }); 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /lib/networking/rest/channels/deleteMessages.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../core/ApiRequest"); 7 | 8 | module.exports = function(channelId, messages) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .post(this, { 12 | url: `${Endpoints.MESSAGES(channelId)}/bulk-delete`, 13 | body: {messages: messages} 14 | }); 15 | 16 | this._queueManager.putBulkDeleteMessage(request, channelId, (err, res) => { 17 | return (!err && res.ok) ? rs() : rj(err); 18 | }); 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /lib/networking/rest/channels/deletePermissionOverwrite.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../core/ApiRequest"); 7 | 8 | module.exports = function(channelId, overwriteId) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .del(this, `${Endpoints.CHANNEL_PERMISSIONS(channelId)}/${overwriteId}`); 12 | 13 | this._queueManager.put(request, (err, res) => { 14 | return (!err && res.ok) ? rs() : rj(err); 15 | }); 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /lib/networking/rest/channels/deleteReactions.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../core/ApiRequest"); 7 | 8 | module.exports = function(channelId, messageId) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .del(this, Endpoints.REACTIONS(channelId, messageId)); 12 | 13 | const route = 14 | Endpoints.REACTIONS(channelId, ":messageId"); 15 | 16 | this._queueManager.putToRoute(request, route, (err, res) => { 17 | return (!err && res.ok) ? rs() : rj(err); 18 | }); 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /lib/networking/rest/channels/dm/addRecipient.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../../core/ApiRequest"); 7 | 8 | module.exports = function(channelId, userId) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .put(this, `${Endpoints.CHANNEL_RECIPIENTS(channelId)}/${userId}`); 12 | 13 | this._queueManager.put(request, (err, res) => { 14 | if (err || !res.ok) 15 | return rj(err); 16 | 17 | const HTTP_CREATED = 201; 18 | rs(res.status === HTTP_CREATED ? res.body.id : channelId); 19 | }); 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /lib/networking/rest/channels/dm/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = require("requireindex")(__dirname); 4 | -------------------------------------------------------------------------------- /lib/networking/rest/channels/dm/removeRecipient.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../../core/ApiRequest"); 7 | 8 | module.exports = function(channelId, userId) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .del(this, `${Endpoints.CHANNEL_RECIPIENTS(channelId)}/${userId}`); 12 | 13 | this._queueManager.put(request, (err, res) => { 14 | return (!err && res.ok) ? rs() : rj(err); 15 | }); 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /lib/networking/rest/channels/dm/setIcon.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../../core/ApiRequest"); 7 | 8 | module.exports = function(channelId, icon) { 9 | icon = icon || null; 10 | 11 | return new Promise((rs, rj) => { 12 | var request = apiRequest 13 | .patch(this, { 14 | url: `${Endpoints.CHANNELS}/${channelId}`, 15 | body: {icon} 16 | }); 17 | 18 | this._queueManager.put(request, (err, res) => { 19 | if (err || !res.ok) 20 | return rj(err); 21 | 22 | this._channels.update(res.body); 23 | rs(res.body); 24 | }); 25 | }); 26 | }; 27 | -------------------------------------------------------------------------------- /lib/networking/rest/channels/dm/setName.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../../core/ApiRequest"); 7 | 8 | module.exports = function(channelId, name) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .patch(this, { 12 | url: `${Endpoints.CHANNELS}/${channelId}`, 13 | body: {name} 14 | }); 15 | 16 | this._queueManager.put(request, (err, res) => { 17 | if (err || !res.ok) 18 | return rj(err); 19 | 20 | this._channels.update(res.body); 21 | rs(res.body); 22 | }); 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /lib/networking/rest/channels/getInvites.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../core/ApiRequest"); 7 | 8 | module.exports = function(channelId) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .get(this, Endpoints.INSTANT_INVITES(channelId)); 12 | 13 | this._queueManager.put(request, (err, res) => { 14 | return (!err && res.ok) ? rs(res.body) : rj(err); 15 | }); 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /lib/networking/rest/channels/getMessages.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../core/ApiRequest"); 7 | 8 | module.exports = function(channelId, limit, before, after) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .get(this, { 12 | url: Endpoints.MESSAGES(channelId), 13 | query: { 14 | before: before || undefined, 15 | after: after || undefined, 16 | limit: limit 17 | } 18 | }); 19 | 20 | this._queueManager.put(request, (err, res) => { 21 | if (err || !res.ok) 22 | return rj(err); 23 | 24 | const event = { 25 | messages: res.body, 26 | limit: limit, 27 | before: before, 28 | after: after 29 | }; 30 | this.Dispatcher.emit(Events.LOADED_MORE_MESSAGES, event); 31 | rs(event); 32 | }); 33 | }); 34 | }; 35 | -------------------------------------------------------------------------------- /lib/networking/rest/channels/getPinnedMessages.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../core/ApiRequest"); 7 | 8 | module.exports = function(channelId) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .get(this, Endpoints.PINS(channelId)); 12 | 13 | this._queueManager.put(request, (err, res) => { 14 | if (err || !res.ok) 15 | return rj(err); 16 | 17 | const event = { 18 | channelId: channelId, 19 | messages: res.body 20 | }; 21 | this.Dispatcher.emit(Events.LOADED_PINNED_MESSAGES, event); 22 | rs(event); 23 | }); 24 | }); 25 | }; 26 | -------------------------------------------------------------------------------- /lib/networking/rest/channels/getReactions.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../core/ApiRequest"); 7 | 8 | module.exports = function(channelId, messageId, emoji, limit, after) { 9 | emoji = encodeURIComponent(emoji); 10 | 11 | return new Promise((rs, rj) => { 12 | var request = apiRequest 13 | .get(this, { 14 | url: Endpoints.REACTIONS_EMOJI(channelId, messageId, emoji), 15 | query: { 16 | limit: limit, 17 | after: after || undefined 18 | } 19 | }); 20 | 21 | const route = 22 | Endpoints.REACTIONS_EMOJI(channelId, ":messageId", ":emoji"); 23 | 24 | this._queueManager.putToRoute(request, route, (err, res) => { 25 | if (err || !res.ok) 26 | return rj(err); 27 | 28 | if (Array.isArray(res.body)) { 29 | res.body.forEach(user => this._users.update(user)); 30 | } 31 | 32 | rs(res.body); 33 | }); 34 | }); 35 | }; 36 | -------------------------------------------------------------------------------- /lib/networking/rest/channels/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = require("requireindex")(__dirname); 4 | -------------------------------------------------------------------------------- /lib/networking/rest/channels/patchChannel.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../core/ApiRequest"); 7 | 8 | module.exports = function(channelId, name, topic, bitrate, user_limit, position) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .patch(this, { 12 | url: `${Endpoints.CHANNELS}/${channelId}`, 13 | body: { 14 | name: name, 15 | position: position, 16 | topic: topic, 17 | bitrate: bitrate, 18 | user_limit: user_limit 19 | } 20 | }); 21 | 22 | this._queueManager.put(request, (err, res) => { 23 | if (err || !res.ok) 24 | return rj(err); 25 | 26 | this._channels.update(res.body); 27 | rs(res.body); 28 | }); 29 | }); 30 | }; 31 | -------------------------------------------------------------------------------- /lib/networking/rest/channels/patchMessage.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../core/ApiRequest"); 7 | 8 | module.exports = function(channelId, messageId, content, embed) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .patch(this, { 12 | url: `${Endpoints.MESSAGES(channelId)}/${messageId}`, 13 | body: {content, embed} 14 | }); 15 | 16 | this._queueManager.putMessage(request, channelId, (err, res) => { 17 | if (err || !res.ok) 18 | return rj(err); 19 | 20 | this._messages.update(res.body); 21 | rs(res.body); 22 | }); 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /lib/networking/rest/channels/pinMessage.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../core/ApiRequest"); 7 | 8 | module.exports = function(channelId, messageId) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .put(this, `${Endpoints.PINS(channelId)}/${messageId}`); 12 | 13 | this._queueManager.put(request, (err, res) => { 14 | if (err || !res.ok) 15 | return rj(err); 16 | 17 | this._messages.update({ 18 | id: messageId, channel_id: channelId, 19 | pinned: true 20 | }); 21 | rs(); 22 | }); 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /lib/networking/rest/channels/postTyping.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../core/ApiRequest"); 7 | 8 | module.exports = function(channelId) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .post(this, Endpoints.TYPING(channelId)); 12 | 13 | this._queueManager.put(request, (err, res) => { 14 | if (err || !res.ok) 15 | return rj(err); 16 | 17 | // todo: do something? fire TYPING on setTimeout? 18 | rs(); 19 | }); 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /lib/networking/rest/channels/putPermissionOverwrite.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../core/ApiRequest"); 7 | 8 | module.exports = function(channelId, overwrite) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .put(this, { 12 | url: `${Endpoints.CHANNEL_PERMISSIONS(channelId)}/${overwrite.id}`, 13 | body: overwrite 14 | }); 15 | 16 | this._queueManager.put(request, (err, res) => { 17 | if (err || !res.ok) 18 | return rj(err); 19 | 20 | this._channels.updatePermissionOverwrite(channelId, overwrite); 21 | rs(overwrite); 22 | }); 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /lib/networking/rest/channels/removeReaction.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../core/ApiRequest"); 7 | 8 | module.exports = function(channelId, messageId, emoji, userId) { 9 | emoji = encodeURIComponent(emoji); 10 | userId = userId || "@me"; 11 | 12 | return new Promise((rs, rj) => { 13 | var request = apiRequest 14 | .del(this, Endpoints.REACTION(channelId, messageId, emoji, userId)); 15 | 16 | const route = 17 | Endpoints.REACTION(channelId, ":messageId", ":emoji", ":userId"); 18 | 19 | this._queueManager.putToRoute(request, route, (err, res) => { 20 | return (!err && res.ok) ? rs() : rj(err); 21 | }); 22 | }); 23 | }; 24 | -------------------------------------------------------------------------------- /lib/networking/rest/channels/unpinMessage.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../core/ApiRequest"); 7 | 8 | module.exports = function(channelId, messageId) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .del(this, `${Endpoints.PINS(channelId)}/${messageId}`); 12 | 13 | this._queueManager.put(request, (err, res) => { 14 | if (err || !res.ok) 15 | return rj(err); 16 | 17 | this._messages.update({ 18 | id: messageId, channel_id: channelId, 19 | pinned: false 20 | }); 21 | rs(); 22 | }); 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /lib/networking/rest/channels/uploadFile.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const fs = require("fs"); 4 | const path = require("path"); 5 | 6 | const Utils = require("../../../core/Utils"); 7 | const Constants = require("../../../Constants"); 8 | const Events = Constants.Events; 9 | const Endpoints = Constants.Endpoints; 10 | const apiRequest = require("../../../core/ApiRequest"); 11 | 12 | function validateFile(file) { 13 | if (typeof file !== "string") return; 14 | file = path.resolve(file); 15 | var stat = fs.statSync(file); 16 | if (!stat.isFile()) 17 | throw new Error("uploadFile: path does not point to a file: " + file); 18 | } 19 | 20 | // must specify 'filename' with image extension for image embeds 21 | // if passing a Buffer or Stream into 'readStream' 22 | module.exports = function(channelId, readStream, filename, 23 | content, mentions, tts) { 24 | return new Promise((rs, rj) => { 25 | validateFile(readStream); 26 | 27 | Utils.cacheStream(readStream, uploadData => { 28 | var request = apiRequest 29 | .post(this, { 30 | url: Endpoints.MESSAGES(channelId), 31 | attachDelegate: req => { 32 | if (!filename && uploadData !== "string") { 33 | filename = "unknown.png"; 34 | } 35 | 36 | req 37 | .attach("file", uploadData, filename) 38 | .field("content", content || "") 39 | .field("tts", JSON.stringify(!!tts)); 40 | } 41 | }); 42 | 43 | this._queueManager.putMessage(request, channelId, (err, res) => { 44 | if (err || !res.ok) 45 | return rj(err); 46 | 47 | this._messages.create(res.body); 48 | rs(res.body); 49 | }); 50 | }); 51 | }); 52 | }; 53 | -------------------------------------------------------------------------------- /lib/networking/rest/gateway.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../Constants"); 4 | const Events = Constants.Events; 5 | const apiRequest = require("../../core/ApiRequest"); 6 | 7 | module.exports = function(auth) { 8 | return new Promise((rs, rj) => { 9 | apiRequest 10 | .get(this, "/gateway") 11 | .send((err, res) => { 12 | if (err || !res.ok) { 13 | this.Dispatcher.emit(Events.REQUEST_GATEWAY_ERROR, {error: err}); 14 | return rj(err); 15 | } 16 | const event = {gateway: res.body.url}; 17 | this.Dispatcher.emit(Events.REQUEST_GATEWAY_SUCCESS, event); 18 | rs(event); 19 | }); 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /lib/networking/rest/guilds/bans/banMember.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../../core/ApiRequest"); 7 | 8 | module.exports = function(guildId, userId, deleteMessageForDays) { 9 | deleteMessageForDays = deleteMessageForDays || 0; 10 | if (deleteMessageForDays && [1, 7].indexOf(deleteMessageForDays) < 0) { 11 | return Promise.reject( 12 | new Error("deleteMessageForDays can only be 0, 1 or 7") 13 | ); 14 | } 15 | 16 | return new Promise((rs, rj) => { 17 | var request = apiRequest 18 | .put(this, { 19 | url: `${Endpoints.GUILD_BANS(guildId)}/${userId}`, 20 | query: { "delete-message-days": deleteMessageForDays } 21 | }); 22 | 23 | this._queueManager.put(request, (err, res) => { 24 | return (!err && res.ok) ? rs() : rj(err); 25 | }); 26 | }); 27 | }; 28 | -------------------------------------------------------------------------------- /lib/networking/rest/guilds/bans/getBans.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../../core/ApiRequest"); 7 | 8 | module.exports = function(guildId) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .get(this, Endpoints.GUILD_BANS(guildId)); 12 | 13 | this._queueManager.put(request, (err, res) => { 14 | if (err || !res.ok) 15 | return rj(err); 16 | 17 | this.Dispatcher.emit(Events.LOADED_GUILD_BANS, res.body); 18 | rs(res.body); 19 | }); 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /lib/networking/rest/guilds/bans/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = require("requireindex")(__dirname); 4 | -------------------------------------------------------------------------------- /lib/networking/rest/guilds/bans/unbanMember.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../../core/ApiRequest"); 7 | 8 | module.exports = function(guildId, userId) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .del(this, `${Endpoints.GUILD_BANS(guildId)}/${userId}`); 12 | 13 | this._queueManager.put(request, (err, res) => { 14 | return (!err && res.ok) ? rs() : rj(err); 15 | }); 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /lib/networking/rest/guilds/createGuild.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../core/ApiRequest"); 7 | 8 | module.exports = function(name, region, icon, 9 | roles, channels, 10 | verificationLevel, defaultMessageNotifications) { 11 | icon = icon || null; 12 | 13 | const body = { 14 | name: name, 15 | region: region, 16 | icon: icon 17 | }; 18 | 19 | if (roles != null) body.roles = roles; 20 | if (channels != null) body.channels = channels; 21 | if (verificationLevel != null) body.verification_level = verificationLevel; 22 | if (defaultMessageNotifications != null) { 23 | body.default_message_notifications = defaultMessageNotifications; 24 | } 25 | 26 | return new Promise((rs, rj) => { 27 | var request = apiRequest 28 | .post(this, { 29 | url: Endpoints.GUILDS, 30 | body 31 | }); 32 | 33 | this._queueManager.put(request, (err, res) => { 34 | if (err || !res.ok) 35 | return rj(err); 36 | 37 | this._guilds.update(res.body); 38 | rs(res.body); 39 | }); 40 | }); 41 | }; 42 | -------------------------------------------------------------------------------- /lib/networking/rest/guilds/deleteGuild.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../core/ApiRequest"); 7 | 8 | module.exports = function(guildId) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .del(this, `${Endpoints.GUILDS}/${guildId}`); 12 | 13 | this._queueManager.put(request, (err, res) => { 14 | return (!err && res.ok) ? rs() : rj(err); 15 | }); 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /lib/networking/rest/guilds/emoji/deleteEmoji.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../../core/ApiRequest"); 7 | 8 | module.exports = function(guildId, emojiId) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .del(this, Endpoints.GUILD_EMOJI(guildId, emojiId)); 12 | 13 | this._queueManager.put(request, (err, res) => { 14 | return (!err && res.ok) ? rs() : rj(err); 15 | }); 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /lib/networking/rest/guilds/emoji/getEmoji.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../../core/ApiRequest"); 7 | 8 | module.exports = function(guildId) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .get(this, Endpoints.GUILD_EMOJIS(guildId)); 12 | 13 | this._queueManager.put(request, (err, res) => { 14 | return (!err && res.ok) ? rs(res.body) : rj(err); 15 | }); 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /lib/networking/rest/guilds/emoji/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = require("requireindex")(__dirname); 4 | -------------------------------------------------------------------------------- /lib/networking/rest/guilds/emoji/patchEmoji.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../../core/ApiRequest"); 7 | 8 | module.exports = function(guildId, emojiId, options) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .patch(this, { 12 | url: Endpoints.GUILD_EMOJI(guildId, emojiId), 13 | body: options 14 | }); 15 | 16 | this._queueManager.put(request, (err, res) => { 17 | return (!err && res.ok) ? rs(res.body) : rj(err); 18 | }); 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /lib/networking/rest/guilds/emoji/postEmoji.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../../core/ApiRequest"); 7 | 8 | module.exports = function(guildId, image, name) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .post(this, { 12 | url: Endpoints.GUILD_EMOJIS(guildId), 13 | body: { image, name } 14 | }); 15 | 16 | this._queueManager.put(request, (err, res) => { 17 | return (!err && res.ok) ? rs(res.body) : rj(err); 18 | }); 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /lib/networking/rest/guilds/getEmbed.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../core/ApiRequest"); 7 | 8 | module.exports = function(guildId) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .get(this, Endpoints.GUILD_EMBED(guildId)); 12 | 13 | this._queueManager.put(request, (err, res) => { 14 | return (!err && res.ok) ? rs(res.body) : rj(err); 15 | }); 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /lib/networking/rest/guilds/getInvites.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../core/ApiRequest"); 7 | 8 | module.exports = function(guildId) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .get(this, Endpoints.GUILD_INSTANT_INVITES(guildId)); 12 | 13 | this._queueManager.put(request, (err, res) => { 14 | return (!err && res.ok) ? rs(res.body) : rj(err); 15 | }); 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /lib/networking/rest/guilds/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = require("requireindex")(__dirname); 4 | -------------------------------------------------------------------------------- /lib/networking/rest/guilds/leaveGuild.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../core/ApiRequest"); 7 | 8 | module.exports = function(guildId) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .del(this, `${Endpoints.ME}/guilds/${guildId}`) 12 | 13 | this._queueManager.put(request, (err, res) => { 14 | return (!err && res.ok) ? rs() : rj(err); 15 | }); 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /lib/networking/rest/guilds/members/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = require("requireindex")(__dirname); 4 | -------------------------------------------------------------------------------- /lib/networking/rest/guilds/members/kickMember.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../../core/ApiRequest"); 7 | 8 | module.exports = function(guildId, userId) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .del(this, `${Endpoints.GUILD_MEMBERS(guildId)}/${userId}`); 12 | 13 | this._queueManager.putGuildMemberPatch(request, guildId, (err, res) => { 14 | return (!err && res.ok) ? rs() : rj(err); 15 | }); 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /lib/networking/rest/guilds/members/setChannel.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../../core/ApiRequest"); 7 | 8 | module.exports = function(guildId, userId, channelId) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .patch(this, { 12 | url: `${Endpoints.GUILD_MEMBERS(guildId)}/${userId}`, 13 | body: {channel_id: channelId} 14 | }); 15 | 16 | this._queueManager.putGuildMemberPatch(request, guildId, (err, res) => { 17 | return (!err && res.ok) ? rs() : rj(err); 18 | }); 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /lib/networking/rest/guilds/members/setDeaf.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../../core/ApiRequest"); 7 | 8 | module.exports = function(guildId, userId, deaf) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .patch(this, { 12 | url: `${Endpoints.GUILD_MEMBERS(guildId)}/${userId}`, 13 | body: {deaf: deaf} 14 | }); 15 | 16 | this._queueManager.putGuildMemberPatch(request, guildId, (err, res) => { 17 | return (!err && res.ok) ? rs() : rj(err); 18 | }); 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /lib/networking/rest/guilds/members/setMute.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../../core/ApiRequest"); 7 | 8 | module.exports = function(guildId, userId, mute) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .patch(this, { 12 | url: `${Endpoints.GUILD_MEMBERS(guildId)}/${userId}`, 13 | body: {mute: mute} 14 | }); 15 | 16 | this._queueManager.putGuildMemberPatch(request, guildId, (err, res) => { 17 | return (!err && res.ok) ? rs() : rj(err); 18 | }); 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /lib/networking/rest/guilds/members/setNickname.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../../core/ApiRequest"); 7 | 8 | module.exports = function(guildId, userId, nick) { 9 | const endpoint = this._user.id === userId ? 10 | `${Endpoints.GUILD_MEMBERS(guildId)}/${Constants.ME}/nick` : 11 | `${Endpoints.GUILD_MEMBERS(guildId)}/${userId}`; 12 | 13 | return new Promise((rs, rj) => { 14 | var request = apiRequest 15 | .patch(this, { 16 | url: endpoint, 17 | body: {nick: nick} 18 | }); 19 | 20 | this._queueManager.putGuildMemberNick(request, guildId, (err, res) => { 21 | return (!err && res.ok) ? rs() : rj(err); 22 | }); 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /lib/networking/rest/guilds/members/setRoles.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../../core/ApiRequest"); 7 | 8 | module.exports = function(guildId, userId, roles) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .patch(this, { 12 | url: `${Endpoints.GUILD_MEMBERS(guildId)}/${userId}`, 13 | body: {roles: roles} 14 | }); 15 | 16 | this._queueManager.putGuildMemberPatch(request, guildId, (err, res) => { 17 | return (!err && res.ok) ? rs() : rj(err); 18 | }); 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /lib/networking/rest/guilds/patchEmbed.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../core/ApiRequest"); 7 | 8 | module.exports = function(guildId, options) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .patch(this, { 12 | url: Endpoints.GUILD_EMBED(guildId), 13 | body: { 14 | enabled: options.enabled, 15 | channel_id: options.channelId !== undefined ? 16 | options.channelId : 17 | options.channel_id 18 | } 19 | }); 20 | 21 | this._queueManager.put(request, (err, res) => { 22 | return (!err && res.ok) ? rs(res.body) : rj(err); 23 | }); 24 | }); 25 | }; 26 | -------------------------------------------------------------------------------- /lib/networking/rest/guilds/patchGuild.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../core/ApiRequest"); 7 | 8 | module.exports = function(guildId, 9 | name, icon, region, 10 | afkChannelId, afkTimeout, 11 | verificationLevel, defaultMessageNotifications) { 12 | icon = icon || null; 13 | 14 | return new Promise((rs, rj) => { 15 | var request = apiRequest 16 | .patch(this, { 17 | url: `${Endpoints.GUILDS}/${guildId}`, 18 | body: { 19 | name: name, 20 | icon: icon, 21 | region: region, 22 | afk_channel_id: afkChannelId, 23 | afk_timeout: afkTimeout, 24 | verification_level: verificationLevel, 25 | default_message_notifications: defaultMessageNotifications 26 | } 27 | }); 28 | 29 | this._queueManager.put(request, (err, res) => { 30 | if (err || !res.ok) 31 | return rj(err); 32 | 33 | this._guilds.update(res.body); 34 | rs(res.body); 35 | }); 36 | }); 37 | }; 38 | -------------------------------------------------------------------------------- /lib/networking/rest/guilds/prune/getPrune.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../../core/ApiRequest"); 7 | 8 | module.exports = function(guildId, days) { 9 | if (days === undefined) days = 7; 10 | if (days <= 1) days = 1; 11 | return new Promise((rs, rj) => { 12 | var request = apiRequest 13 | .get(this, { 14 | url: Endpoints.GUILD_PRUNE(guildId), 15 | query: {days: days} 16 | }); 17 | 18 | this._queueManager.put(request, (err, res) => { 19 | if (err || !res.ok) return rj(err); 20 | return rs({ 21 | guildId: guildId, 22 | days: days, 23 | estimate: res.body.pruned 24 | }); 25 | }); 26 | }); 27 | }; 28 | -------------------------------------------------------------------------------- /lib/networking/rest/guilds/prune/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = require("requireindex")(__dirname); 4 | -------------------------------------------------------------------------------- /lib/networking/rest/guilds/prune/postPrune.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../../core/ApiRequest"); 7 | 8 | module.exports = function(guildId, days) { 9 | if (days === undefined) days = 7; 10 | if (days <= 1) days = 1; 11 | return new Promise((rs, rj) => { 12 | var request = apiRequest 13 | .post(this, { 14 | url: Endpoints.GUILD_PRUNE(guildId), 15 | query: {days: days} 16 | }); 17 | 18 | this._queueManager.put(request, (err, res) => { 19 | if (err || !res.ok) return rj(err); 20 | return rs({ 21 | guildId: guildId, 22 | days: days, 23 | pruned: res.body.pruned 24 | }); 25 | }); 26 | }); 27 | }; 28 | -------------------------------------------------------------------------------- /lib/networking/rest/guilds/roles/batchPatchRoles.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../../core/ApiRequest"); 7 | 8 | module.exports = function(guildId, roles) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .patch(this, { 12 | url: Endpoints.GUILD_ROLES(guildId), 13 | body: roles 14 | }); 15 | 16 | this._queueManager.put(request, (err, res) => { 17 | // the server returns an array of roles, but we don't really need that 18 | // it doesn't return anything for batchPatchChannels 19 | 20 | return (!err && res.ok) ? rs() : rj(err); 21 | }); 22 | }); 23 | }; 24 | -------------------------------------------------------------------------------- /lib/networking/rest/guilds/roles/createRole.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../../core/ApiRequest"); 7 | 8 | module.exports = function(guildId) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .post(this, Endpoints.GUILD_ROLES(guildId)); 12 | 13 | this._queueManager.put(request, (err, res) => { 14 | if (err || !res.ok) 15 | return rj(err); 16 | 17 | this._guilds.updateRole(guildId, res.body); 18 | rs(res.body); 19 | }); 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /lib/networking/rest/guilds/roles/deleteRole.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../../core/ApiRequest"); 7 | 8 | module.exports = function(guildId, roleId) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .del(this, `${Endpoints.GUILD_ROLES(guildId)}/${roleId}`); 12 | 13 | this._queueManager.put(request, (err, res) => { 14 | return (!err && res.ok) ? rs() : rj(err); 15 | }); 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /lib/networking/rest/guilds/roles/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = require("requireindex")(__dirname); 4 | -------------------------------------------------------------------------------- /lib/networking/rest/guilds/roles/patchRole.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../../core/ApiRequest"); 7 | 8 | module.exports = function(guildId, roleId, 9 | name, permissions, color, hoist, mentionable) { 10 | return new Promise((rs, rj) => { 11 | var request = apiRequest 12 | .patch(this, { 13 | url: `${Endpoints.GUILD_ROLES(guildId)}/${roleId}`, 14 | body: { 15 | name: name, 16 | permissions: permissions, 17 | color: color, 18 | hoist: hoist, 19 | mentionable: mentionable 20 | } 21 | }); 22 | 23 | this._queueManager.put(request, (err, res) => { 24 | if (err || !res.ok) 25 | return rj(err); 26 | 27 | this._guilds.updateRole(guildId, res.body); 28 | rs(res.body); 29 | }); 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /lib/networking/rest/guilds/transferOwnership.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../core/ApiRequest"); 7 | 8 | module.exports = function(guildId, userId) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .patch(this, { 12 | url: `${Endpoints.GUILDS}/${guildId}`, 13 | body: {owner_id: userId} 14 | }); 15 | 16 | this._queueManager.put(request, (err, res) => { 17 | if (err || !res.ok) 18 | return rj(err); 19 | 20 | this._guilds.update(res.body); 21 | rs(res.body); 22 | }); 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /lib/networking/rest/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function loadHandlers() { 4 | // webpack build: 5 | if (process.browser) { 6 | const writeModule = (root, path, module) => { 7 | const child = path.shift(); 8 | if (path.length) { 9 | root[child] = root[child] || {}; 10 | return writeModule(root[child], path, module); 11 | } 12 | root[child] = module; 13 | }; 14 | 15 | const ctx = require.context("./", true, /\.js$/); 16 | const handlers = {}; 17 | ctx.keys().forEach(key => { 18 | if (key.match(/index\.js$/)) return; 19 | const path = key 20 | .replace(/\.js$/, "") 21 | .replace(/^\.[\/\\]/, "") 22 | .split(/[\/\\]/g); 23 | writeModule(handlers, path, ctx(key)); 24 | }); 25 | return handlers; 26 | } 27 | 28 | // regular node: 29 | return require("requireindex")(__dirname); 30 | } 31 | 32 | const handlers = loadHandlers(); 33 | 34 | function bindHandlers(discordie, root) { 35 | var root = Object.assign({}, root); 36 | for (let k in root) { 37 | if (!root.hasOwnProperty(k)) continue; 38 | if (typeof root[k] === "function") { 39 | root[k] = root[k].bind(discordie); 40 | } else { 41 | root[k] = bindHandlers(discordie, root[k]); 42 | } 43 | } 44 | return root; 45 | } 46 | 47 | module.exports = function(discordie) { 48 | return bindHandlers(discordie, handlers); 49 | }; 50 | -------------------------------------------------------------------------------- /lib/networking/rest/invites/createInvite.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../core/ApiRequest"); 7 | 8 | module.exports = function(channelId, options) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .post(this, { 12 | url: Endpoints.INSTANT_INVITES(channelId), 13 | body: options 14 | }); 15 | 16 | this._queueManager.put(request, (err, res) => { 17 | return (!err && res.ok) ? rs(res.body) : rj(err); 18 | }); 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /lib/networking/rest/invites/deleteInvite.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../core/ApiRequest"); 7 | 8 | module.exports = function(code) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .del(this, `${Endpoints.INVITE}/${code}`); 12 | 13 | this._queueManager.putToRoute(request, Endpoints.INVITE, (err, res) => { 14 | return (!err && res.ok) ? rs() : rj(err); 15 | }); 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /lib/networking/rest/invites/getInvite.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../core/ApiRequest"); 7 | 8 | module.exports = function(code) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .get(this, `${Endpoints.INVITE}/${code}`); 12 | 13 | this._queueManager.putToRoute(request, Endpoints.INVITE, (err, res) => { 14 | return (!err && res.ok) ? rs(res.body) : rj(err); 15 | }); 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /lib/networking/rest/invites/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = require("requireindex")(__dirname); 4 | -------------------------------------------------------------------------------- /lib/networking/rest/invites/postInvite.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../core/ApiRequest"); 7 | 8 | module.exports = function(code) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .post(this, `${Endpoints.INVITE}/${code}`); 12 | 13 | this._queueManager.putToRoute(request, Endpoints.INVITE, (err, res) => { 14 | return (!err && res.ok) ? rs(res.body) : rj(err); 15 | }); 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /lib/networking/rest/oauth2/getApplication.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../core/ApiRequest"); 7 | 8 | module.exports = function(id) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .get(this, Endpoints.OAUTH2_APPLICATION(id)); 12 | 13 | this._queueManager.put(request, (err, res) => { 14 | return (!err && res.ok) ? rs(res.body) : rj(err); 15 | }); 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /lib/networking/rest/oauth2/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = require("requireindex")(__dirname); 4 | -------------------------------------------------------------------------------- /lib/networking/rest/users/createDirectMessageChannel.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../core/ApiRequest"); 7 | 8 | module.exports = function(userId, recipients) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .post(this, { 12 | url: Endpoints.USER_CHANNELS(userId), 13 | body: {recipients: recipients} 14 | }); 15 | 16 | this._queueManager.put(request, (err, res) => { 17 | if (err || !res.ok) 18 | return rj(err); 19 | 20 | this._channels.update(res.body); 21 | rs(res.body); 22 | }); 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /lib/networking/rest/users/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = require("requireindex")(__dirname); 4 | -------------------------------------------------------------------------------- /lib/networking/rest/users/me.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../core/ApiRequest"); 7 | 8 | module.exports = function(currentPassword, username, avatar, email, newPassword) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .patch(this, { 12 | url: Endpoints.ME, 13 | body: { 14 | username: username, 15 | email: email, 16 | password: currentPassword, 17 | avatar: avatar, 18 | new_password: newPassword 19 | } 20 | }); 21 | 22 | this._queueManager.put(request, (err, res) => { 23 | if (err || !res.ok) 24 | return rj(err); 25 | 26 | if (res.body && typeof res.body.token === "string") { 27 | this.token = res.body.token.replace(/^Bot /, ""); 28 | } 29 | this._users.updateAuthenticatedUser(res.body); 30 | rs(); 31 | }); 32 | }); 33 | }; 34 | -------------------------------------------------------------------------------- /lib/networking/rest/voice/getRegions.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../core/ApiRequest"); 7 | 8 | module.exports = function(guildId) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .get(this, guildId ? Endpoints.GUILD_REGIONS(guildId) : Endpoints.REGIONS); 12 | 13 | this._queueManager.put(request, (err, res) => { 14 | if (err || !res.ok) 15 | return rj(err); 16 | 17 | rs(res.body); 18 | }); 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /lib/networking/rest/voice/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = require("requireindex")(__dirname); 4 | -------------------------------------------------------------------------------- /lib/networking/rest/webhooks/createWebhook.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../core/ApiRequest"); 7 | 8 | module.exports = function(channelId, options) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .post(this, { 12 | url: Endpoints.CHANNEL_WEBHOOKS(channelId), 13 | body: options 14 | }); 15 | 16 | this._queueManager.put(request, (err, res) => { 17 | return (!err && res.ok) ? rs(res.body) : rj(err); 18 | }); 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /lib/networking/rest/webhooks/deleteWebhook.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../core/ApiRequest"); 7 | 8 | module.exports = function(webhookId, token) { 9 | return new Promise((rs, rj) => { 10 | const endpoint = token ? 11 | `${Endpoints.WEBHOOK(webhookId)}/${token}` : 12 | `${Endpoints.WEBHOOK(webhookId)}`; 13 | 14 | var request = apiRequest 15 | .del(this, endpoint); 16 | 17 | const route = Endpoints.WEBHOOK(webhookId); 18 | this._queueManager.putToRoute(request, route, (err, res) => { 19 | return (!err && res.ok) ? rs() : rj(err); 20 | }); 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /lib/networking/rest/webhooks/executeSlackWebhook.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../core/ApiRequest"); 7 | 8 | module.exports = function(webhookId, token, options, wait) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .post(this, { 12 | url: `${Endpoints.WEBHOOK(webhookId)}/${token}/slack`, 13 | query: wait != null ? {wait: !!wait} : {}, 14 | body: options 15 | }); 16 | 17 | const route = Endpoints.WEBHOOK(webhookId); 18 | this._queueManager.putToRoute(request, route, (err, res) => { 19 | return (!err && res.ok) ? rs(res.body) : rj(err); 20 | }); 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /lib/networking/rest/webhooks/executeWebhook.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const fs = require("fs"); 4 | const path = require("path"); 5 | 6 | const Utils = require("../../../core/Utils"); 7 | const Constants = require("../../../Constants"); 8 | const Events = Constants.Events; 9 | const Endpoints = Constants.Endpoints; 10 | const apiRequest = require("../../../core/ApiRequest"); 11 | 12 | function validateFile(file) { 13 | if (typeof file !== "string") return; 14 | file = path.resolve(file); 15 | var stat = fs.statSync(file); 16 | if (!stat.isFile()) 17 | throw new Error("executeWebhook: path does not point to a file: " + file); 18 | } 19 | 20 | module.exports = function(webhookId, token, options, wait) { 21 | return new Promise((rs, rj) => { 22 | validateFile(options.file); 23 | 24 | const body = { 25 | content: options.content || undefined, 26 | embeds: options.embeds || undefined, 27 | username: options.username || undefined, 28 | avatar_url: options.avatarURL || options.avatar_url || undefined, 29 | tts: options.tts || false 30 | }; 31 | 32 | Utils.cacheStream(options.file, uploadData => { 33 | var request = apiRequest 34 | .post(this, { 35 | url: `${Endpoints.WEBHOOK(webhookId)}/${token}`, 36 | query: wait != null ? {wait: !!wait} : {}, 37 | body, 38 | attachDelegate: req => { 39 | if (!uploadData) return; 40 | if (!options.filename && uploadData !== "string") { 41 | options.filename = "unknown.png"; 42 | } 43 | 44 | req.attach("file", uploadData, options.filename); 45 | if (body.content) req.field("content", body.content); 46 | if (body.username) req.field("username", body.username); 47 | if (body.avatar_url) req.field("avatar_url", body.avatar_url); 48 | req.field("tts", JSON.stringify(!!body.tts)); 49 | } 50 | }); 51 | 52 | const route = Endpoints.WEBHOOK(webhookId); 53 | this._queueManager.putToRoute(request, route, (err, res) => { 54 | return (!err && res.ok) ? rs(res.body) : rj(err); 55 | }); 56 | }); 57 | }); 58 | }; 59 | -------------------------------------------------------------------------------- /lib/networking/rest/webhooks/getChannelWebhooks.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../core/ApiRequest"); 7 | 8 | module.exports = function(channelId) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .get(this, Endpoints.CHANNEL_WEBHOOKS(channelId)); 12 | 13 | this._queueManager.put(request, (err, res) => { 14 | return (!err && res.ok) ? rs(res.body) : rj(err); 15 | }); 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /lib/networking/rest/webhooks/getGuildWebhooks.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../core/ApiRequest"); 7 | 8 | module.exports = function(channelId) { 9 | return new Promise((rs, rj) => { 10 | var request = apiRequest 11 | .get(this, Endpoints.GUILD_WEBHOOKS(channelId)); 12 | 13 | this._queueManager.put(request, (err, res) => { 14 | return (!err && res.ok) ? rs(res.body) : rj(err); 15 | }); 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /lib/networking/rest/webhooks/getWebhook.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../core/ApiRequest"); 7 | 8 | module.exports = function(webhookId, token) { 9 | return new Promise((rs, rj) => { 10 | const endpoint = token ? 11 | `${Endpoints.WEBHOOK(webhookId)}/${token}` : 12 | `${Endpoints.WEBHOOK(webhookId)}`; 13 | 14 | var request = apiRequest 15 | .get(this, endpoint); 16 | 17 | const route = Endpoints.WEBHOOK(webhookId); 18 | this._queueManager.putToRoute(request, route, (err, res) => { 19 | return (!err && res.ok) ? rs(res.body) : rj(err); 20 | }); 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /lib/networking/rest/webhooks/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = require("requireindex")(__dirname); 4 | -------------------------------------------------------------------------------- /lib/networking/rest/webhooks/patchWebhook.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = require("../../../Constants"); 4 | const Events = Constants.Events; 5 | const Endpoints = Constants.Endpoints; 6 | const apiRequest = require("../../../core/ApiRequest"); 7 | 8 | module.exports = function(webhookId, token, options) { 9 | return new Promise((rs, rj) => { 10 | const endpoint = token ? 11 | `${Endpoints.WEBHOOK(webhookId)}/${token}` : 12 | `${Endpoints.WEBHOOK(webhookId)}`; 13 | 14 | const body = { 15 | channel_id: options.channelId || options.channel_id || undefined, 16 | name: options.name || undefined 17 | }; 18 | if (options.avatar !== undefined) body.avatar = options.avatar; 19 | 20 | var request = apiRequest 21 | .patch(this, { 22 | url: endpoint, 23 | body 24 | }); 25 | 26 | const route = Endpoints.WEBHOOK(webhookId); 27 | this._queueManager.putToRoute(request, route, (err, res) => { 28 | return (!err && res.ok) ? rs(res.body) : rj(err); 29 | }); 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /lib/networking/voicetransports/VoiceUDP.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const dns = require("dns"); 4 | const udp = require("dgram"); 5 | const Constants = require("../../Constants"); 6 | const ReadyState = Constants.ReadyState; 7 | const VoiceTransportBase = require("./VoiceTransportBase"); 8 | const DiscordieError = require("../../core/DiscordieError"); 9 | const Utils = require("../../core/Utils"); 10 | 11 | const UDP_TIMEOUT = 5000; 12 | 13 | class VoiceUDP extends VoiceTransportBase { 14 | constructor(voicews, ssrc, remotehost, remoteport) { 15 | super(voicews, ssrc); 16 | 17 | this.ssrc = ssrc; 18 | 19 | this.remoteip = null; 20 | this.remotehost = remotehost; 21 | this.remoteport = remoteport; 22 | 23 | this.onConnect = function() {}; 24 | this.onError = function() {}; 25 | this.onTimeout = function() {}; 26 | 27 | this.connectionTimeout = null; 28 | 29 | this.senderInfoTimer = null; 30 | } 31 | _timeout() { 32 | this.close(); 33 | if (typeof this.onTimeout == "function") 34 | this.onTimeout(); 35 | } 36 | _error(error) { 37 | this.close(); 38 | if (typeof this.onError == "function") 39 | this.onError(error); 40 | } 41 | connect() { 42 | dns.resolve(this.remotehost, (err, servers) => { 43 | if (err || !servers[0]) 44 | return this._error(`Could not resolve name '${this.remotehost}'`); 45 | 46 | this.remoteip = servers[0]; 47 | this._connect(); 48 | }); 49 | } 50 | _connect() { 51 | this.close(); 52 | this.socket = udp.createSocket("udp4"); 53 | this.readyState = ReadyState.CONNECTING; 54 | 55 | const packet = Utils.allocBuffer(70); 56 | packet.fill(0); 57 | packet.writeUInt32BE(this.ssrc, 0); 58 | 59 | this.sendRaw(packet); 60 | 61 | this.socket.on("message", (msg, from) => { 62 | if (msg.length == 70) { 63 | if (this.readyState == ReadyState.OPEN) { 64 | console.warn("Warning: Late UDP initialization message"); 65 | return; 66 | } 67 | 68 | if (this.connectionTimeout) { 69 | clearTimeout(this.connectionTimeout); 70 | this.connectionTimeout = null; 71 | } 72 | 73 | const ssrc = msg.readUInt32LE(0); 74 | if (ssrc != this.ssrc) 75 | return this._error(new DiscordieError( 76 | `UDP received ssrc does not match (${ssrc} != ${this.ssrc})` 77 | )); 78 | 79 | this.localip = msg.slice(4, 68).toString().split("\x00")[0]; 80 | this.localport = msg[68] | msg[69] << 8; 81 | 82 | this.readyState = ReadyState.OPEN; 83 | 84 | if (typeof this.onConnect === "function") 85 | this.onConnect(this.localip, this.localport); 86 | 87 | return; 88 | } 89 | 90 | if (this.readyState != ReadyState.OPEN) return; 91 | 92 | this.demux(msg); 93 | }); 94 | 95 | if (this.connectionTimeout) clearTimeout(this.connectionTimeout); 96 | this.connectionTimeout = setTimeout(this._timeout.bind(this), UDP_TIMEOUT); 97 | } 98 | send(packet, sampleCount) { 99 | this.sendRaw(this.mux(packet, sampleCount)); 100 | } 101 | sendRaw(data) { 102 | this.socket.send( 103 | data, 0, data.length, 104 | this.remoteport, this.remoteip, 105 | (err, bytes) => { 106 | if (err) console.error(err.stack); 107 | } 108 | ); 109 | } 110 | sendSenderInfo() { 111 | if (this.senderInfoTimer) { 112 | clearTimeout(this.senderInfoTimer); 113 | this.senderInfoTimer = null; 114 | } 115 | if (this.readyState != ReadyState.OPEN) return; 116 | this.sendRaw(this.updateSenderReportRTCP()); 117 | const delay = Math.max(Math.random() * 10, 1) * 1000; 118 | this.senderInfoTimer = setTimeout(() => this.sendSenderInfo(), delay); 119 | } 120 | close() { 121 | if (this.senderInfoTimer) { 122 | clearTimeout(this.senderInfoTimer); 123 | this.senderInfoTimer = null; 124 | } 125 | if (!this.socket) return; 126 | this.readyState = Constants.ReadyState.CLOSED; 127 | this.socket.close(); 128 | this.socket = null; 129 | } 130 | } 131 | 132 | module.exports = VoiceUDP; 133 | -------------------------------------------------------------------------------- /lib/networking/ws/BaseSocket.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const BrowserWebSocket = require("./BrowserWebSocket"); 4 | const WebSocket = BrowserWebSocket.available ? BrowserWebSocket : require("ws"); 5 | const Constants = require("../../Constants"); 6 | 7 | const heartbeat = new WeakMap(); 8 | 9 | class BaseSocket extends WebSocket { 10 | constructor(url) { 11 | super(url); 12 | this.readyState = Constants.ReadyState.CONNECTING; 13 | this.on("open", () => this.readyState = Constants.ReadyState.OPEN); 14 | 15 | const close = () => { 16 | this.unsetHeartbeat(); 17 | this.readyState = Constants.ReadyState.CLOSED; 18 | }; 19 | this.on("close", close); 20 | this.on("error", close); 21 | 22 | this._connectionTimer = null; 23 | } 24 | _startTimeout(callback, time) { 25 | this._stopTimeout(); 26 | this._connectionTimer = setTimeout(callback, time); 27 | } 28 | _stopTimeout() { 29 | if (!this._connectionTimer) return; 30 | clearTimeout(this._connectionTimer); 31 | this._connectionTimer = null; 32 | } 33 | get connected() { 34 | return this.readyState == Constants.ReadyState.OPEN; 35 | } 36 | get connecting() { 37 | return this.readyState == Constants.ReadyState.CONNECTING; 38 | } 39 | send(op, data) { 40 | let m = {op: op, d: data}; 41 | 42 | try { 43 | super.send(JSON.stringify(m)); 44 | } catch (e) { console.error(e.stack); } 45 | } 46 | setHeartbeat(callback, msec) { 47 | if (typeof callback !== "function") 48 | throw new TypeError("Heartbeat callback is not a function"); 49 | 50 | if (!this.connected) 51 | return; 52 | 53 | this.unsetHeartbeat(); 54 | heartbeat.set(this, setInterval(() => { 55 | if (!this.connected) 56 | return this.close(); 57 | callback(); 58 | }, msec)); 59 | } 60 | unsetHeartbeat() { 61 | var handle = heartbeat.get(this); 62 | if (handle !== undefined) clearInterval(handle); 63 | heartbeat.delete(this); 64 | } 65 | close(code, data) { 66 | super.close(code, data); 67 | this.unsetHeartbeat(); 68 | this._stopTimeout(); 69 | 70 | // wait close frame for 5 seconds instead of default 30 71 | if (this._closeTimer && this._closeTimer._onTimeout) { 72 | var callback = this._closeTimer._onTimeout; 73 | setTimeout(() => callback(), 5000); 74 | } 75 | clearTimeout(this._closeTimer); 76 | } 77 | } 78 | 79 | module.exports = BaseSocket; 80 | -------------------------------------------------------------------------------- /lib/networking/ws/BrowserWebSocket.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const EventEmitter = require("events").EventEmitter; 4 | 5 | class BrowserWebSocket extends EventEmitter { 6 | constructor(url) { 7 | super(); 8 | const ws = this._websocket = new WebSocket(url); 9 | 10 | ws.binaryType = "arraybuffer"; 11 | 12 | ws.onopen = e => this.emit("open"); 13 | ws.onmessage = e => this.emit("message", e.data); 14 | ws.onclose = e => this.emit("close", e.code, e.reason); 15 | } 16 | send(data) { 17 | this._websocket.send.apply(this._websocket, arguments); 18 | } 19 | close(code, reason) { 20 | this._websocket.close.apply(this._websocket, arguments); 21 | } 22 | } 23 | 24 | BrowserWebSocket.available = !!global.WebSocket; 25 | 26 | module.exports = BrowserWebSocket; -------------------------------------------------------------------------------- /lib/voice/AudioDecoder.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const fork = require("child_process").fork; 4 | const DecoderWorker = require("./threading/DecoderWorker"); 5 | const Utils = require("../core/Utils"); 6 | 7 | const defaultOptions = { 8 | multiThreadedVoice: false, 9 | }; 10 | 11 | class AudioDecoder { 12 | constructor(voicews, options) { 13 | this.options = {}; 14 | if (!options) options = defaultOptions; 15 | 16 | this.voicews = voicews; 17 | this.onPacketDecoded = null; 18 | this.onPacket = null; 19 | 20 | this.initialize(options); 21 | } 22 | get canStream() { 23 | return this.voicews && this.voicews.canStream; 24 | } 25 | destroyUser(userId) { 26 | if (this.disposed) return; 27 | this.worker.send({ 28 | op: "destroyUser", 29 | userId: userId 30 | }); 31 | } 32 | assignUser(ssrc, userId) { 33 | if (this.disposed) return; 34 | this.worker.send({ 35 | op: "assignUser", 36 | ssrc: ssrc, 37 | userId: userId 38 | }); 39 | } 40 | enqueue(packet) { 41 | if (typeof this.onPacket === "function") { 42 | const packetCopy = Object.assign({}, packet); 43 | this.onPacket(packetCopy); 44 | } 45 | 46 | if (!this.onPacketDecoded) 47 | return; 48 | 49 | if (this.disposed) { 50 | if (this.onPacketDecoded !== null) 51 | throw new Error("AudioDecoder is not initialized"); 52 | return; 53 | } 54 | this.worker.send({ 55 | op: "enqueue", 56 | packet: packet 57 | }); 58 | } 59 | get disposed() { 60 | return this.worker == null; 61 | } 62 | initialize(options) { 63 | if (!options) options = defaultOptions; 64 | 65 | const hasChanges = Object.keys(options).reduce((r, k) => { 66 | return r || (this.options[k] != options[k]) 67 | }, false); 68 | if (hasChanges) this.kill(); 69 | 70 | const _defaultOptions = Object.assign({}, defaultOptions); 71 | this.options = Object.assign(_defaultOptions, options); 72 | 73 | if (this.disposed) { 74 | if (options.multiThreadedVoice) { 75 | this.worker = fork(__dirname + "/threading/DecoderWorker"); 76 | } else { 77 | this.worker = new DecoderWorker(); 78 | } 79 | 80 | this.worker.on("message", (msg) => { 81 | switch (msg.op) { 82 | case "packet": 83 | if (!msg.packet) break; 84 | const packet = msg.packet; 85 | 86 | if (packet.chunk.data && packet.chunk.data.length > 0) 87 | packet.chunk = Utils.createBuffer(packet.chunk.data); 88 | 89 | if (!(packet.chunk instanceof Buffer)) 90 | return; 91 | 92 | if (typeof this.onPacketDecoded === "function") 93 | this.onPacketDecoded(packet); 94 | 95 | break; 96 | } 97 | }); 98 | } 99 | 100 | this.worker.send({ 101 | op: "initialize", 102 | options 103 | }); 104 | 105 | this.onPacketDecoded = null; 106 | } 107 | kill() { 108 | if (this.disposed) return; 109 | this.worker.kill(); 110 | this.worker = null; 111 | } 112 | } 113 | 114 | module.exports = AudioDecoder; 115 | -------------------------------------------------------------------------------- /lib/voice/AudioResampler.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function interpLinear(i, i0, v0, v1) { 4 | var d = i - i0; 5 | return v0 * (1 - d) + v1 * d; 6 | } 7 | 8 | function interpNearest(i, i0, v0, v1) { 9 | return (i - i0) < 0.5 ? v0 : v1; 10 | } 11 | 12 | class AudioResampler { 13 | constructor(channels, sourceRate, targetRate, interpolation) { 14 | if (channels <= 0) throw new TypeError("Invalid channel count"); 15 | 16 | this.channels = channels; 17 | this.sourceRate = sourceRate; 18 | this.targetRate = targetRate; 19 | this.interpolation = interpolation || "linear"; 20 | 21 | const interp = { 22 | linear: interpLinear, 23 | nearest: interpNearest, 24 | }; 25 | 26 | if (!interp[this.interpolation]) 27 | throw new Error("Unknown interpolation type"); 28 | 29 | this.interp = interp[this.interpolation]; 30 | } 31 | process(buffer) { 32 | var ratio = this.sourceRate / this.targetRate; 33 | var resampled = new Float32Array(buffer.length / ratio); 34 | var bufferLength = buffer.length; 35 | 36 | var interp = this.interp; 37 | 38 | var channels = this.channels; 39 | if (channels == 1) { 40 | for (var i = 0, r = 0; i < bufferLength; i += ratio) { 41 | var i0 = Math.floor(i); 42 | var i1 = i0 + 1; 43 | while (i1 >= buffer.length) i1--; 44 | resampled[r++] = interp(i, i0, buffer[i0], buffer[i1]); 45 | } 46 | } else { 47 | var channelLength = bufferLength / channels; 48 | for (var c = 0; c < channels; c++) { 49 | for (var i = 0, r = 0; i < channelLength; i += ratio) { 50 | var ifl = Math.floor(i); 51 | var i0 = ifl * channels + c; 52 | var i1 = i0 + channels; 53 | while (i0 >= buffer.length) i0 -= channels; 54 | while (i1 >= buffer.length) i1 -= channels; 55 | resampled[r++ * channels + c] = 56 | interp(i, ifl, buffer[i0], buffer[i1]); 57 | } 58 | } 59 | } 60 | 61 | return resampled; 62 | } 63 | destroy() {} 64 | } 65 | 66 | module.exports = AudioResampler; -------------------------------------------------------------------------------- /lib/voice/players/ExternalEncoderBase.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const fs = require("fs"); 4 | const path = require("path"); 5 | const spawn = require("child_process").spawn; 6 | const EventEmitter = require("events").EventEmitter; 7 | 8 | const Utils = require("../../core/Utils"); 9 | 10 | const ENVPATH = ["."].concat((process.env.PATH || "").split(path.delimiter)); 11 | 12 | class ExternalEncoderBase extends EventEmitter { 13 | constructor(voiceConnection, options) { 14 | super(); 15 | 16 | this._voiceConnection = voiceConnection; 17 | this._encoderStream = null; 18 | this._destroyOnUnpipe = options.destroyOnUnpipe !== false; 19 | 20 | this._handle = null; 21 | 22 | this._disposed = false; 23 | } 24 | _createProcess(executable, args, options) { 25 | var binaries = [executable, executable + ".exe"]; 26 | 27 | for (var name of binaries) { 28 | for (var dir of ENVPATH) { 29 | var binary = dir + path.sep + name; 30 | if (!Utils.fileExists(binary)) continue; 31 | 32 | return spawn(name, args, options); 33 | } 34 | } 35 | return null; 36 | } 37 | _setHandle(handle, onExit) { 38 | this._handle = handle; 39 | this._handle.on("exit", code => { 40 | if (typeof onExit === "function") onExit(code); 41 | this.destroy(); 42 | }); 43 | } 44 | 45 | /** 46 | * Gets the voice connection this instance is bound to. 47 | * @returns {IVoiceConnection} 48 | * @readonly 49 | */ 50 | get voiceConnection() { return this._voiceConnection; } 51 | 52 | /** 53 | * Connects pipe into AudioEncoderStream of the bound voice connection. 54 | * 55 | * This function handles automatic unpiping and kills process. 56 | * The stream will become disposed and no longer playable after 57 | * calling `unpipe()` or `stop()`. 58 | * 59 | * Use `pipe()` method if you want to control the process manually 60 | * and `destroy()` it later. 61 | * @returns {AudioEncoderStream} 62 | */ 63 | play(encoderOptions) { 64 | if (this._disposed) throw new Error("Unable to play disposed stream"); 65 | 66 | // already playing, ignore request 67 | if (this._encoderStream) return this._encoderStream; 68 | 69 | encoderOptions = encoderOptions || {proxy: true}; 70 | 71 | const encoder = this._voiceConnection.getEncoderStream(encoderOptions); 72 | if (!encoder) return null; 73 | 74 | this.pipe(encoder); 75 | 76 | encoder.once("unpipe", () => { 77 | if (!this._destroyOnUnpipe) return; 78 | this.emit("unpipe"); 79 | this.destroy(); 80 | this._encoderStream = null; 81 | }); 82 | 83 | this._encoderStream = encoder; 84 | 85 | return encoder; 86 | } 87 | 88 | /** 89 | * Unpipes internal stream from the bound voice connection. 90 | */ 91 | stop() { 92 | this.unpipe(this._encoderStream); 93 | } 94 | 95 | /** 96 | * Pipes stream into destination. 97 | * 98 | * > **Note:** In case of manual piping you have to invoke 99 | * > `IVoiceConnection.getEncoderStream` with the correct settings yourself. 100 | * > For proxy (externally encoding) streams only `{proxy: true}` is required. 101 | * @param {WritableStream} dest 102 | */ 103 | pipe(dest) { throw new Error("Not implemented"); } 104 | 105 | /** 106 | * Unpipes stream from destination. 107 | * @param {WritableStream} [dest] 108 | */ 109 | unpipe(dest) { throw new Error("Not implemented"); } 110 | 111 | /** 112 | * Destroys all handles, releases resources and disposes this instance. 113 | */ 114 | destroy() { 115 | this._disposed = true; 116 | if (!this._handle) return; 117 | this._handle.kill(); 118 | this._handle = null; 119 | } 120 | } 121 | 122 | module.exports = ExternalEncoderBase; -------------------------------------------------------------------------------- /lib/voice/players/ExternalEncoderFactory.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const FFmpegEncoder = require("./FFmpegEncoder"); 4 | const OggOpusPlayer = require("./OggOpusPlayer"); 5 | const WebmOpusPlayer = require("./WebmOpusPlayer"); 6 | 7 | module.exports = { 8 | create(voiceConnection, options) { 9 | options = options || {}; 10 | const type = options.type || "ffmpeg"; 11 | 12 | if (type === "ffmpeg" || type === "avconv") { 13 | return new FFmpegEncoder(voiceConnection, options); 14 | } 15 | 16 | if (type.toLowerCase() === "OggOpusPlayer".toLowerCase()) { 17 | return new OggOpusPlayer(voiceConnection, options); 18 | } 19 | if (type.toLowerCase() === "WebmOpusPlayer".toLowerCase()) { 20 | return new WebmOpusPlayer(voiceConnection, options); 21 | } 22 | 23 | throw new Error(`Invalid type '${options.type}'`); 24 | } 25 | }; -------------------------------------------------------------------------------- /lib/voice/players/OggOpusPlayer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const ExternalEncoderBase = require("./ExternalEncoderBase"); 4 | const OggOpusDemuxer = require("./demuxers/OggOpusDemuxer"); 5 | 6 | /** 7 | * @class 8 | * @extends ExternalEncoderBase 9 | * @classdesc 10 | * Simple wrapper for ogg opus streams. Streams audio on the fly without 11 | * decoding. 12 | * 13 | * ```js 14 | * var info = client.VoiceConnections[0]; 15 | * if (!info) return console.log("Voice not connected"); 16 | * 17 | * var source = fs.createReadStream("test.opus"); 18 | * var encoder = info.voiceConnection.createExternalEncoder({ 19 | * type: "OggOpusPlayer", 20 | * source: source 21 | * }); 22 | * if (!encoder) return console.log("Voice connection is disposed"); 23 | * 24 | * encoder.once("end", () => console.log("stream end")); 25 | * encoder.once("error", err => console.log("Ogg Error", err)); 26 | * 27 | * var encoderStream = encoder.play(); 28 | * encoderStream.once("unpipe", () => source.destroy()); // close descriptor 29 | * 30 | * encoderStream.resetTimestamp(); 31 | * encoderStream.removeAllListeners("timestamp"); 32 | * encoderStream.on("timestamp", time => console.log("Time " + time)); 33 | * ``` 34 | * 35 | * #### Events: 36 | * 37 | * - ** Event: `end` ** 38 | * 39 | * Emitted when stream done playing. 40 | * 41 | * - ** Event: `unpipe` ** 42 | * 43 | * Emitted when stream gets unpiped from `AudioEncoderStream`. 44 | * Proxies `AudioEncoderStream` unpipe event, fires only when 45 | * using `play()` method. 46 | * If you create file or http streams make sure descriptors get destroyed on 47 | * unpiping. 48 | * 49 | * - ** Event: `error` ** 50 | * 51 | * Emitted if an error occurred while demuxing. The stream will unpipe 52 | * itself on this event. 53 | */ 54 | class OggOpusPlayer extends ExternalEncoderBase { 55 | constructor(voiceConnection, options) { 56 | super(voiceConnection, options); 57 | 58 | if (!options.source || typeof options.source.pipe !== "function") { 59 | throw new TypeError(`Invalid source '${options.source}'`); 60 | } 61 | 62 | this._stream = new OggOpusDemuxer(); 63 | this._stream.on("error", err => { 64 | this.emit("error", err); 65 | this.unpipe(); 66 | }); 67 | this._stream.on("end", () => { 68 | if (!this._stream._readableState.pipesCount) return; 69 | // don't emit 'end' after 'unpipe' 70 | this.emit("end"); 71 | }); 72 | 73 | options.source.pipe(this._stream); 74 | } 75 | pipe(dest) { 76 | this._stream.pipe(dest); 77 | } 78 | unpipe(dest) { 79 | this._stream.unpipe(dest); 80 | } 81 | } 82 | 83 | module.exports = OggOpusPlayer; -------------------------------------------------------------------------------- /lib/voice/players/WebmOpusPlayer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const ExternalEncoderBase = require("./ExternalEncoderBase"); 4 | 5 | const EBMLDecoder = require("./demuxers/EBMLDecoder"); 6 | const WebmOpusDemuxer = require("./demuxers/WebmOpusDemuxer"); 7 | 8 | /** 9 | * @class 10 | * @extends ExternalEncoderBase 11 | * @classdesc 12 | * Simple wrapper for webm opus streams. Streams audio on the fly without 13 | * decoding. 14 | * 15 | * ```js 16 | * var info = client.VoiceConnections[0]; 17 | * if (!info) return console.log("Voice not connected"); 18 | * 19 | * var source = fs.createReadStream("test.webm"); 20 | * var encoder = info.voiceConnection.createExternalEncoder({ 21 | * type: "WebmOpusPlayer", 22 | * source: source 23 | * }); 24 | * if (!encoder) return console.log("Voice connection is disposed"); 25 | * 26 | * encoder.once("end", () => console.log("stream end")); 27 | * encoder.once("error", err => console.log("WebM Error", err)); 28 | * 29 | * var encoderStream = encoder.play(); 30 | * encoderStream.once("unpipe", () => source.destroy()); // close descriptor 31 | * 32 | * encoderStream.resetTimestamp(); 33 | * encoderStream.removeAllListeners("timestamp"); 34 | * encoderStream.on("timestamp", time => console.log("Time " + time)); 35 | * ``` 36 | * 37 | * #### Events: 38 | * 39 | * - ** Event: `end` ** 40 | * 41 | * Emitted when stream done playing. 42 | * 43 | * - ** Event: `unpipe` ** 44 | * 45 | * Emitted when stream gets unpiped from `AudioEncoderStream`. 46 | * Proxies `AudioEncoderStream` unpipe event, fires only when 47 | * using `play()` method. 48 | * If you create file or http streams make sure descriptors get destroyed on 49 | * unpiping. 50 | * 51 | * - ** Event: `error` ** 52 | * 53 | * Emitted if an error occurred while demuxing. The stream will unpipe 54 | * itself on this event. 55 | */ 56 | class WebmOpusPlayer extends ExternalEncoderBase { 57 | constructor(voiceConnection, options) { 58 | super(voiceConnection, options); 59 | 60 | if (!options.source || typeof options.source.pipe !== "function") { 61 | throw new TypeError(`Invalid source '${options.source}'`); 62 | } 63 | 64 | this._ebmld = new EBMLDecoder(); 65 | this._stream = new WebmOpusDemuxer(); 66 | options.source.pipe(this._ebmld).pipe(this._stream); 67 | 68 | this._stream.on("error", err => { 69 | this.emit("error", err); 70 | this.unpipe(); 71 | }); 72 | this._stream.on("end", () => { 73 | if (!this._stream._readableState.pipesCount) return; 74 | // don't emit 'end' after 'unpipe' 75 | this.emit("end"); 76 | }); 77 | } 78 | pipe(dest) { 79 | this._stream.pipe(dest); 80 | } 81 | unpipe(dest) { 82 | this._stream.unpipe(dest); 83 | } 84 | } 85 | 86 | module.exports = WebmOpusPlayer; -------------------------------------------------------------------------------- /lib/voice/players/demuxers/BufferStream.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | class BufferStream { 4 | constructor(buffer) { 5 | this._buffer = buffer; 6 | this._offset = 0; 7 | } 8 | get ended() { return this._offset >= this._buffer.length; } 9 | get available() { return this._buffer.length - this._offset; } 10 | skip(bytes) { this.read(bytes); } 11 | read(bytes) { 12 | if (bytes <= 0) return null; 13 | if (this.ended) return null; 14 | if (this._offset + bytes > this._buffer.length) return null; 15 | var offset = this._offset; 16 | this._offset += bytes; 17 | return this._buffer.slice(offset, offset + bytes); 18 | } 19 | readByte() { return (this.read(1) || [null])[0]; } 20 | readString(size) { 21 | var v = this.read(size); 22 | return v ? v.toString("utf8", 0) : null; 23 | } 24 | readInt32LE() { 25 | var v = this.read(4); 26 | return v ? v.readInt32LE(0) : null; 27 | } 28 | readUInt32LE() { 29 | var v = this.read(4); 30 | return v ? v.readUInt32LE(0) : null; 31 | } 32 | readInt16LE() { 33 | var v = this.read(2); 34 | return v ? v.readInt16LE(0) : null; 35 | } 36 | readUInt16LE() { 37 | var v = this.read(2); 38 | return v ? v.readUInt16LE(0) : null; 39 | } 40 | readUInt8() { 41 | var v = this.read(1); 42 | return v ? v.readUInt8(0) : null; 43 | } 44 | } 45 | 46 | module.exports = BufferStream; -------------------------------------------------------------------------------- /lib/voice/players/demuxers/EBMLDecoder.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const ebml = require('ebml'); 4 | ebml.Decoder.prototype.__transform = ebml.Decoder.prototype._transform; 5 | ebml.Decoder.prototype._transform = function(chunk, encoding, done) { 6 | // catch "Unrepresentable length" errors 7 | try { 8 | this.__transform.apply(this, arguments); 9 | } catch (e) { 10 | this.push(null); 11 | done(); 12 | } 13 | }; 14 | 15 | module.exports = ebml.Decoder; -------------------------------------------------------------------------------- /lib/voice/players/demuxers/OggOpusDemuxer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const OGG_PAGE_HEADER_SIZE = 26; 4 | 5 | const BufferStream = require("./BufferStream"); 6 | 7 | const Transform = require("stream").Transform; 8 | class OggOpusDemuxer extends Transform { 9 | constructor() { 10 | super({ 11 | writableObjectMode: true, 12 | readableObjectMode: true 13 | }); 14 | } 15 | readSegments(reader) { 16 | var tableSize = reader.readByte(); 17 | if (reader.available < tableSize) return null; 18 | 19 | var segmentSizes = []; 20 | for (var i = 0; i < tableSize; ) { 21 | var read = reader.readByte(); i++; 22 | var size = read; 23 | while (read === 255) { 24 | read = reader.readByte(); i++; 25 | size += read; 26 | } 27 | segmentSizes.push(size); 28 | } 29 | 30 | var dataSize = segmentSizes.reduce((c, n) => c + n, 0); 31 | if (reader.available < dataSize) return null; 32 | 33 | return segmentSizes.map(size => reader.read(size)); 34 | } 35 | readPage(reader, done) { 36 | if (reader.available < OGG_PAGE_HEADER_SIZE) return false; 37 | var magic = reader.readString(4); 38 | if (magic !== "OggS") 39 | return new Error("OGG magic does not match"); 40 | 41 | var version = reader.readByte(); 42 | var headerType = reader.readByte(); 43 | 44 | var isContinuation = headerType & (1 << 0); 45 | if (isContinuation) 46 | return new Error("OGG page continuation handling not implemented"); 47 | 48 | var isBeginning = headerType & (1 << 1); 49 | var isEnd = headerType & (1 << 2); 50 | 51 | reader.skip(8); // granule position 52 | reader.skip(4); // stream serial number 53 | var pageSeq = reader.readInt32LE(); 54 | reader.skip(4); // checksum 55 | 56 | var segments = this.readSegments(reader); 57 | if (segments == null) return false; 58 | if (segments.indexOf(null) >= 0) return false; 59 | 60 | var packets = []; 61 | for (var segment of segments) { 62 | var header = segment.toString("utf8", 0, 8); 63 | 64 | if (header === "OpusHead") { 65 | this._opusHeader = segment; 66 | this.emit("OpusHead", this._opusHeader); 67 | } else if (header === "OpusTags") { 68 | this._opusTags = segment; 69 | this.emit("OpusTags", this._opusTags); 70 | } else packets.push(segment); 71 | } 72 | 73 | if (!this._opusHeader) return true; 74 | packets.forEach(packet => this.push(packet)); 75 | return true; 76 | } 77 | _transform(chunk, encoding, done) { 78 | if (this._leftover) { 79 | chunk = Buffer.concat([this._leftover, chunk]); 80 | this._leftover = null; 81 | } 82 | 83 | var reader = new BufferStream(chunk); 84 | 85 | while (!reader.ended) { 86 | // save current position if page reading fails 87 | var offset = reader._offset; 88 | var ok = this.readPage(reader, done); 89 | if (ok instanceof Error) return done(ok, null); 90 | if (!ok) { 91 | // save remaining buffer 92 | this._leftover = chunk.slice(offset, chunk.length); 93 | break; 94 | } 95 | } 96 | 97 | done(); 98 | } 99 | } 100 | 101 | module.exports = OggOpusDemuxer; -------------------------------------------------------------------------------- /lib/voice/players/demuxers/WebmOpusDemuxer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const BufferStream = require("./BufferStream"); 4 | 5 | const Transform = require("stream").Transform; 6 | 7 | const TRACKTYPE_VIDEO = 1; 8 | const TRACKTYPE_AUDIO = 2; 9 | const TRACKTYPE_COMPLEX = 3; 10 | 11 | class WebmOpusDemuxer extends Transform { 12 | constructor() { 13 | super({ 14 | writableObjectMode: true, 15 | readableObjectMode: true, 16 | }); 17 | this.demuxingAudio = false; 18 | this.tracks = new Map(); 19 | } 20 | _parseTracks(type, info, done) { 21 | if (info.name == "TrackEntry") { 22 | if (type == "start") { 23 | this.parsingTrack = {}; 24 | } 25 | if (type == "end") { 26 | if (this.parsingTrack.hasOwnProperty("TrackNumber")) { 27 | const id = this.parsingTrack.TrackNumber; 28 | this.tracks.set(id, this.parsingTrack); 29 | } 30 | delete this.parsingTrack; 31 | } 32 | } 33 | 34 | if (this.parsingTrack && info.name == "TrackNumber") 35 | this.parsingTrack.TrackNumber = info.data[0]; 36 | if (this.parsingTrack && info.name == "CodecID") 37 | this.parsingTrack.CodecID = info.data.toString(); 38 | if (this.parsingTrack && info.name == "TrackType") 39 | this.parsingTrack.TrackType = info.data[0]; 40 | 41 | if (type == "end" && info.name == "Tracks") { 42 | for (var track of this.tracks.values()) { 43 | if (track.TrackType != TRACKTYPE_AUDIO) continue; 44 | this.firstAudioTrack = track; 45 | } 46 | if (!this.firstAudioTrack) { 47 | return done(new Error("No audio track"), null); 48 | } 49 | } 50 | } 51 | _parseCodecPrivate(type, info, done) { 52 | const bin = info.data; 53 | if (type != "tag" || info.name != "CodecPrivate") return; 54 | 55 | const reader = new BufferStream(bin); 56 | 57 | var head = reader.readString(8); 58 | if (head != "OpusHead") { 59 | return done(new Error("Invalid codec " + head), null); 60 | } 61 | 62 | this.codecdata = { 63 | version: reader.readUInt8(), 64 | channelCount: reader.readUInt8(), 65 | preSkip: reader.readUInt16LE(), 66 | inputSampleRate: reader.readUInt32LE(), 67 | outputGain: reader.readUInt16LE(), 68 | mappingFamily: reader.readUInt8() 69 | }; 70 | 71 | this.channels = this.codecdata.channelCount; 72 | this.sampleRate = this.codecdata.inputSampleRate; 73 | this.emit("format", { 74 | channels: this.channels, 75 | sampleRate: this.sampleRate 76 | }); 77 | } 78 | _transform(chunk, encoding, done) { 79 | const type = chunk[0]; 80 | const info = chunk[1]; 81 | const bin = info.data; 82 | 83 | this._parseTracks(type, info, done); 84 | this._parseCodecPrivate(type, info, done); 85 | 86 | if (type == "tag" && info.name == "SimpleBlock") { 87 | const tracknumber = bin.readUInt8(0) & 0xF; 88 | if (tracknumber == this.firstAudioTrack.TrackNumber) { 89 | const timestamp = bin.readUInt16BE(1); 90 | const flags = bin.readUInt8(3); 91 | const data = bin.slice(4); 92 | 93 | this.push(data); 94 | 95 | if (!this.demuxingAudio) { 96 | this.emit("demux"); 97 | this.demuxingAudio = true; 98 | } 99 | } 100 | } 101 | 102 | done(); 103 | } 104 | } 105 | module.exports = WebmOpusDemuxer; -------------------------------------------------------------------------------- /lib/voice/streams/OpusUtils.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Constants = { 4 | OPUS_BAD_ARG: -1, 5 | OPUS_INVALID_PACKET: -4 6 | }; 7 | 8 | function opus_packet_get_samples_per_frame(packet, sampleRate) { 9 | sampleRate = sampleRate || 48000; 10 | 11 | let audiosize; 12 | if (packet[0] & 0x80) { 13 | audiosize = ((packet[0] >> 3) & 0x3); 14 | audiosize = (sampleRate << audiosize) / 400; 15 | } else if ((packet[0] & 0x60) == 0x60) { 16 | audiosize = (packet[0] & 0x08) ? sampleRate / 50 : sampleRate / 100; 17 | } else { 18 | audiosize = ((packet[0] >> 3) & 0x3); 19 | if (audiosize == 3) { 20 | audiosize = sampleRate * 60 / 1000; 21 | } else { 22 | audiosize = (sampleRate << audiosize) / 100; 23 | } 24 | } 25 | return audiosize; 26 | } 27 | 28 | function opus_packet_get_nb_frames(packet) { 29 | var count; 30 | if (packet.length < 1) return Constants.OPUS_BAD_ARG; 31 | count = packet[0] & 0x3; 32 | 33 | if (count == 0) return 1; 34 | else if (count != 3) return 2; 35 | else if (packet.length < 2) return Constants.OPUS_INVALID_PACKET; 36 | else return packet[1] & 0x3F; 37 | } 38 | 39 | function opus_packet_get_nb_samples(packet, sampleRate) 40 | { 41 | sampleRate = sampleRate || 48000; 42 | 43 | var count = opus_packet_get_nb_frames(packet); 44 | if (count < 0) return count; 45 | 46 | var samples = count * opus_packet_get_samples_per_frame(packet, sampleRate); 47 | /* Can't have more than 120 ms */ 48 | if (samples * 25 > sampleRate * 3) 49 | return Constants.OPUS_INVALID_PACKET; 50 | return samples; 51 | } 52 | 53 | module.exports = { 54 | Constants, 55 | packet_get_samples_per_frame: opus_packet_get_samples_per_frame, 56 | packet_get_nb_frames: opus_packet_get_nb_frames, 57 | packet_get_nb_samples: opus_packet_get_nb_samples 58 | }; -------------------------------------------------------------------------------- /lib/voice/threading/DecoderWorker.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const events = require("events"); 4 | const nopus = () => require("../../../deps/nopus"); 5 | const Utils = require("../../core/Utils"); 6 | const Constants = require("../../Constants"); 7 | 8 | var IPC = true; 9 | 10 | class DecoderWorker extends events.EventEmitter { 11 | constructor() { 12 | super(); 13 | IPC = false; 14 | } 15 | kill() { 16 | this.destroyStates(); 17 | } 18 | } 19 | 20 | function destroyStates() { 21 | if ((this.states || []).length) { 22 | this.states.forEach(s => s.decoder.destroy()); 23 | } 24 | this.states = []; 25 | this.userMap = {}; 26 | } 27 | 28 | function initialize(_options) { 29 | this.destroyStates(); 30 | 31 | _options.float = _options.float || false; 32 | _options.channels = _options.channels || 1; 33 | if (_options.channels < 1) _options.channels = 1; 34 | 35 | this.options = _options; 36 | } 37 | 38 | function createDecoder() { 39 | const channels = this.options.channels; 40 | return new (nopus().OpusDecoder)(Constants.DISCORD_SAMPLE_RATE, channels); 41 | } 42 | 43 | function destroyUnknown() { 44 | const unknown = this.states.filter(u => !u.userId); 45 | unknown.forEach(s => { 46 | s.decoder.destroy(); 47 | const index = this.states.indexOf(s); 48 | if (index >= 0) this.states.splice(index, 1); 49 | }); 50 | } 51 | 52 | function destroyUser(userId) { 53 | destroyUnknown.call(this); 54 | 55 | const index = this.states.findIndex(s => s.userId == userId); 56 | if (index < 0) return; 57 | const state = this.states[index]; 58 | if (state.ssrc) delete this.userMap[state.ssrc]; 59 | state.decoder.destroy(); 60 | this.states.splice(index, 1); 61 | } 62 | 63 | function assignUser(ssrc, userId) { 64 | this.userMap[ssrc] = userId; 65 | const state = this.states.find(s => s.ssrc == ssrc); 66 | if (!state || state.userId == userId) return; 67 | state.decoder.destroy(); 68 | state.decoder = this.createDecoder(); 69 | state.userId = userId; 70 | } 71 | 72 | function getOrCreateDecoder(ssrc) { 73 | let state = this.states.find(s => s.ssrc == ssrc); 74 | if (!state) { 75 | state = { 76 | ssrc: ssrc, 77 | decoder: this.createDecoder(), 78 | userId: this.userMap[ssrc] || null 79 | }; 80 | this.states.push(state); 81 | } 82 | return state.decoder; 83 | } 84 | 85 | function decode(packet) { 86 | const decoder = getOrCreateDecoder.call(this, packet.ssrc); 87 | 88 | let frameData = packet.chunk.data; 89 | if (packet.chunk instanceof Buffer) 90 | frameData = packet.chunk; 91 | 92 | if (!packet.chunk || !frameData) return; 93 | 94 | const decode = ( 95 | this.options.float ? 96 | decoder.decode_float : 97 | decoder.decode 98 | ).bind(decoder); 99 | 100 | try { 101 | const dataBuffer = new Uint8Array(Utils.createArrayBuffer(frameData)); 102 | const decoded = new Uint8Array(decode(dataBuffer).buffer); 103 | 104 | packet.chunk = Utils.createBuffer(decoded); 105 | this.sendPacket(packet); 106 | } catch (e) { console.error((e instanceof Error ? e : new Error(e)).stack); } 107 | } 108 | 109 | function onIPCMessage(msg) { 110 | if (!msg) return; 111 | switch (msg.op) { 112 | case "initialize": 113 | this.initialize(msg.options); 114 | break; 115 | case "enqueue": 116 | this.decode(msg.packet); 117 | break; 118 | case "assignUser": 119 | this.assignUser(msg.ssrc, msg.userId); 120 | break; 121 | case "destroyUser": 122 | this.destroyUser(msg.userId); 123 | break; 124 | } 125 | } 126 | 127 | function sendIPC(data) { 128 | if (!IPC) { 129 | this.emit("message", data); 130 | return; 131 | } 132 | process.send(data); 133 | } 134 | function sendPacket(packet, sampleCount) { 135 | this.sendIPC({ 136 | op: "packet", 137 | packet: packet 138 | }); 139 | } 140 | 141 | process.on("message", onIPCMessage.bind(DecoderWorker.prototype)); 142 | 143 | Object.assign(DecoderWorker.prototype, { 144 | send: onIPCMessage, 145 | 146 | initialize: initialize, 147 | decode: decode, 148 | 149 | sendIPC: sendIPC, 150 | sendPacket: sendPacket, 151 | 152 | createDecoder: createDecoder, 153 | destroyStates: destroyStates, 154 | assignUser: assignUser, 155 | destroyUser: destroyUser, 156 | }); 157 | 158 | module.exports = DecoderWorker; 159 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "discordie", 3 | "version": "0.11.0", 4 | "description": "A Node.js module for interfacing with the Discord API.", 5 | "main": "./lib/index.js", 6 | "scripts": { 7 | "test": "node ./tests/index.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/qeled/discordie.git" 12 | }, 13 | "keywords": [ 14 | "discordapp", 15 | "discord", 16 | "api", 17 | "client", 18 | "node", 19 | "node.js" 20 | ], 21 | "author": "qeled", 22 | "license": "BSD-2-Clause", 23 | "dependencies": { 24 | "double-ended-queue": "^0.9.7", 25 | "ebml": "^2.1.0", 26 | "pako": "^0.2.8", 27 | "requireindex": "^1.1.0", 28 | "superagent": "^1.4.0", 29 | "tweetnacl": "^0.14.1", 30 | "ws": "^1.1.1" 31 | }, 32 | "engineStrict": true, 33 | "engines": { 34 | "node": ">=4.0.0" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Discordie 2 | 3 | [![npm](https://img.shields.io/npm/dm/discordie.svg)](https://www.npmjs.com/package/discordie) 4 | 5 | A Node.js module providing a set of interfaces to interact with Discord API. 6 | 7 | [**Documentation**](http://qeled.github.io/discordie/) 8 | 9 | **Requires at least Node.js 4.0.0.** 10 | 11 | **No native modules required to work with audio. Choose from precompiled (default) or `node-opus` (optional).** 12 | 13 | Join [#node_discordie](https://discord.gg/0SBTUU1wZTYM8nHo) in [Discord API](https://discord.gg/0SBTUU1wZTYM8nHo). 14 | 15 | ## Fully Implemented 16 | 17 | * Messaging 18 | * Role and channel permission management API 19 | * Member management API (kicking, banning, etc.) 20 | * Direct messages 21 | * Voice encoding, sending, decoding and receiving 22 | (audio streaming example: [`examples/encoderstream.js`](https://github.com/qeled/discordie/blob/master/examples/encoderstream.js)) 23 | * Guild (server) and channel management API 24 | * Local user profile (username change, statuses, avatars) 25 | * Multiserver voice support 26 | 27 | ## Documentation 28 | 29 | http://qeled.github.io/discordie/ 30 | 31 | Mirrors inline documentation in files: 32 | * `lib/interfaces/*.js` 33 | * `lib/models/*.js` 34 | * `lib/voice/*.js` 35 | * `lib/Constants.js` 36 | * `lib/index.js` 37 | 38 | ## Example 39 | 40 | ```js 41 | var Discordie = require("discordie"); 42 | var Events = Discordie.Events; 43 | 44 | var client = new Discordie(); 45 | 46 | client.connect({ token: "" }); 47 | 48 | client.Dispatcher.on(Events.GATEWAY_READY, e => { 49 | console.log("Connected as: " + client.User.username); 50 | }); 51 | 52 | client.Dispatcher.on(Events.MESSAGE_CREATE, e => { 53 | if (e.message.content == "ping") 54 | e.message.channel.sendMessage("pong"); 55 | }); 56 | ``` 57 | 58 | ## Related 59 | 60 | ### Library comparison: https://discordapi.com/unofficial/comparison.html 61 | 62 | **.NET**: 63 | [RogueException/**Discord.Net**](https://github.com/RogueException/Discord.Net) || 64 | [Luigifan/**DiscordSharp**](https://github.com/Luigifan/DiscordSharp) || 65 | [robinhood128/**DiscordUnity**](https://github.com/robinhood128/DiscordUnity) 66 | 67 | **Node.js**: 68 | [izy521/**discord.io**](https://github.com/izy521/discord.io) || 69 | [hydrabolt/**discord.js**](https://github.com/hydrabolt/discord.js) || 70 | [abalabahaha/**eris**](https://github.com/abalabahaha/eris) 71 | 72 | **Python**: 73 | [Rapptz/**discord.py**](https://github.com/Rapptz/discord.py) 74 | 75 | **Ruby**: 76 | [meew0/**discordrb**](https://github.com/meew0/discordrb) 77 | 78 | **Go**: 79 | [bwmarrin/**discordgo**](https://github.com/bwmarrin/discordgo) 80 | 81 | **Rust**: 82 | [SpaceManiac/**discord-rs**](https://github.com/SpaceManiac/discord-rs) 83 | 84 | **PHP**: 85 | [teamreflex/**DiscordPHP**](https://github.com/teamreflex/DiscordPHP) || 86 | [Cleanse/**discord-hypertext**](https://github.com/Cleanse/discord-hypertext) 87 | 88 | **Java**: 89 | [austinv11/**Discord4J**](https://github.com/austinv11/Discord4J) || 90 | [DV8FromTheWorld/**JDA**](https://github.com/DV8FromTheWorld/JDA/) || 91 | [BtoBastian/**Javacord**](https://github.com/BtoBastian/Javacord) 92 | 93 | **Lua**: 94 | [SinisterRectus/**Discordia**](https://github.com/SinisterRectus/Discordia) || 95 | [satom99/**litcord**](https://github.com/satom99/litcord) 96 | 97 | --------------------------------------------------------------------------------