djs-selfbot-v13 3.2.2 → 3.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (363) hide show
  1. package/README.md +37 -36
  2. package/package.json +89 -85
  3. package/src/WebSocket.js +39 -39
  4. package/src/client/BaseClient.js +86 -86
  5. package/src/client/Client.js +934 -836
  6. package/src/client/WebhookClient.js +61 -61
  7. package/src/client/actions/Action.js +116 -120
  8. package/src/client/actions/ActionsManager.js +80 -78
  9. package/src/client/actions/ApplicationCommandPermissionsUpdate.js +34 -34
  10. package/src/client/actions/AutoModerationActionExecution.js +27 -27
  11. package/src/client/actions/AutoModerationRuleCreate.js +28 -28
  12. package/src/client/actions/AutoModerationRuleDelete.js +32 -32
  13. package/src/client/actions/AutoModerationRuleUpdate.js +30 -30
  14. package/src/client/actions/ChannelCreate.js +23 -23
  15. package/src/client/actions/ChannelDelete.js +39 -39
  16. package/src/client/actions/ChannelUpdate.js +43 -43
  17. package/src/client/actions/GuildAuditLogEntryCreate.js +29 -29
  18. package/src/client/actions/GuildBanAdd.js +20 -20
  19. package/src/client/actions/GuildBanRemove.js +25 -25
  20. package/src/client/actions/GuildChannelsPositionUpdate.js +21 -21
  21. package/src/client/actions/GuildDelete.js +65 -65
  22. package/src/client/actions/GuildEmojiCreate.js +20 -20
  23. package/src/client/actions/GuildEmojiDelete.js +21 -21
  24. package/src/client/actions/GuildEmojiUpdate.js +20 -20
  25. package/src/client/actions/GuildEmojisUpdate.js +34 -34
  26. package/src/client/actions/GuildIntegrationsUpdate.js +19 -19
  27. package/src/client/actions/GuildMemberRemove.js +33 -32
  28. package/src/client/actions/GuildMemberUpdate.js +44 -43
  29. package/src/client/actions/GuildRoleCreate.js +25 -25
  30. package/src/client/actions/GuildRoleDelete.js +31 -31
  31. package/src/client/actions/GuildRoleUpdate.js +39 -39
  32. package/src/client/actions/GuildRolesPositionUpdate.js +21 -21
  33. package/src/client/actions/GuildScheduledEventCreate.js +27 -27
  34. package/src/client/actions/GuildScheduledEventDelete.js +31 -31
  35. package/src/client/actions/GuildScheduledEventUpdate.js +30 -30
  36. package/src/client/actions/GuildScheduledEventUserAdd.js +32 -32
  37. package/src/client/actions/GuildScheduledEventUserRemove.js +32 -32
  38. package/src/client/actions/GuildStickerCreate.js +20 -20
  39. package/src/client/actions/GuildStickerDelete.js +21 -21
  40. package/src/client/actions/GuildStickerUpdate.js +20 -20
  41. package/src/client/actions/GuildStickersUpdate.js +34 -34
  42. package/src/client/actions/GuildUpdate.js +33 -33
  43. package/src/client/actions/InviteCreate.js +28 -28
  44. package/src/client/actions/InviteDelete.js +30 -30
  45. package/src/client/actions/MessageCreate.js +50 -46
  46. package/src/client/actions/MessageDelete.js +32 -32
  47. package/src/client/actions/MessageDeleteBulk.js +46 -46
  48. package/src/client/actions/MessagePollVoteAdd.js +33 -0
  49. package/src/client/actions/MessagePollVoteRemove.js +33 -0
  50. package/src/client/actions/MessageReactionAdd.js +68 -56
  51. package/src/client/actions/MessageReactionRemove.js +50 -45
  52. package/src/client/actions/MessageReactionRemoveAll.js +33 -33
  53. package/src/client/actions/MessageReactionRemoveEmoji.js +28 -28
  54. package/src/client/actions/MessageUpdate.js +26 -26
  55. package/src/client/actions/PresenceUpdate.js +50 -46
  56. package/src/client/actions/StageInstanceCreate.js +28 -28
  57. package/src/client/actions/StageInstanceDelete.js +33 -33
  58. package/src/client/actions/StageInstanceUpdate.js +30 -30
  59. package/src/client/actions/ThreadCreate.js +24 -24
  60. package/src/client/actions/ThreadDelete.js +32 -32
  61. package/src/client/actions/ThreadListSync.js +59 -59
  62. package/src/client/actions/ThreadMemberUpdate.js +30 -30
  63. package/src/client/actions/ThreadMembersUpdate.js +34 -34
  64. package/src/client/actions/TypingStart.js +29 -29
  65. package/src/client/actions/UserUpdate.js +35 -35
  66. package/src/client/actions/VoiceStateUpdate.js +50 -57
  67. package/src/client/actions/WebhooksUpdate.js +20 -20
  68. package/src/client/voice/ClientVoiceManager.js +151 -51
  69. package/src/client/voice/VoiceConnection.js +1249 -0
  70. package/src/client/voice/dispatcher/AnnexBDispatcher.js +120 -0
  71. package/src/client/voice/dispatcher/AudioDispatcher.js +145 -0
  72. package/src/client/voice/dispatcher/BaseDispatcher.js +459 -0
  73. package/src/client/voice/dispatcher/VPxDispatcher.js +54 -0
  74. package/src/client/voice/dispatcher/VideoDispatcher.js +68 -0
  75. package/src/client/voice/networking/VoiceUDPClient.js +173 -0
  76. package/src/client/voice/networking/VoiceWebSocket.js +286 -0
  77. package/src/client/voice/player/MediaPlayer.js +321 -0
  78. package/src/client/voice/player/processing/AnnexBNalSplitter.js +244 -0
  79. package/src/client/voice/player/processing/IvfSplitter.js +106 -0
  80. package/src/client/voice/player/processing/PCMInsertSilence.js +37 -0
  81. package/src/client/voice/receiver/PacketHandler.js +260 -0
  82. package/src/client/voice/receiver/Receiver.js +96 -0
  83. package/src/client/voice/receiver/Recorder.js +173 -0
  84. package/src/client/voice/util/Function.js +116 -0
  85. package/src/client/voice/util/PlayInterface.js +122 -0
  86. package/src/client/voice/util/Secretbox.js +64 -0
  87. package/src/client/voice/util/Silence.js +16 -0
  88. package/src/client/voice/util/Socket.js +62 -0
  89. package/src/client/voice/util/VolumeInterface.js +104 -0
  90. package/src/client/websocket/WebSocketManager.js +392 -392
  91. package/src/client/websocket/WebSocketShard.js +907 -906
  92. package/src/client/websocket/handlers/APPLICATION_COMMAND_CREATE.js +18 -18
  93. package/src/client/websocket/handlers/APPLICATION_COMMAND_DELETE.js +20 -20
  94. package/src/client/websocket/handlers/APPLICATION_COMMAND_PERMISSIONS_UPDATE.js +5 -5
  95. package/src/client/websocket/handlers/APPLICATION_COMMAND_UPDATE.js +20 -20
  96. package/src/client/websocket/handlers/AUTO_MODERATION_ACTION_EXECUTION.js +5 -5
  97. package/src/client/websocket/handlers/AUTO_MODERATION_RULE_CREATE.js +5 -5
  98. package/src/client/websocket/handlers/AUTO_MODERATION_RULE_DELETE.js +5 -5
  99. package/src/client/websocket/handlers/AUTO_MODERATION_RULE_UPDATE.js +5 -5
  100. package/src/client/websocket/handlers/CALL_CREATE.js +14 -14
  101. package/src/client/websocket/handlers/CALL_DELETE.js +11 -11
  102. package/src/client/websocket/handlers/CALL_UPDATE.js +11 -11
  103. package/src/client/websocket/handlers/CHANNEL_CREATE.js +5 -5
  104. package/src/client/websocket/handlers/CHANNEL_DELETE.js +5 -5
  105. package/src/client/websocket/handlers/CHANNEL_PINS_UPDATE.js +22 -22
  106. package/src/client/websocket/handlers/CHANNEL_RECIPIENT_ADD.js +19 -19
  107. package/src/client/websocket/handlers/CHANNEL_RECIPIENT_REMOVE.js +16 -16
  108. package/src/client/websocket/handlers/CHANNEL_UPDATE.js +16 -16
  109. package/src/client/websocket/handlers/GUILD_AUDIT_LOG_ENTRY_CREATE.js +5 -5
  110. package/src/client/websocket/handlers/GUILD_BAN_ADD.js +5 -5
  111. package/src/client/websocket/handlers/GUILD_BAN_REMOVE.js +5 -5
  112. package/src/client/websocket/handlers/GUILD_CREATE.js +52 -52
  113. package/src/client/websocket/handlers/GUILD_DELETE.js +5 -5
  114. package/src/client/websocket/handlers/GUILD_EMOJIS_UPDATE.js +5 -5
  115. package/src/client/websocket/handlers/GUILD_INTEGRATIONS_UPDATE.js +5 -5
  116. package/src/client/websocket/handlers/GUILD_MEMBERS_CHUNK.js +39 -39
  117. package/src/client/websocket/handlers/GUILD_MEMBER_ADD.js +20 -19
  118. package/src/client/websocket/handlers/GUILD_MEMBER_REMOVE.js +5 -5
  119. package/src/client/websocket/handlers/GUILD_MEMBER_UPDATE.js +5 -5
  120. package/src/client/websocket/handlers/GUILD_ROLE_CREATE.js +5 -5
  121. package/src/client/websocket/handlers/GUILD_ROLE_DELETE.js +5 -5
  122. package/src/client/websocket/handlers/GUILD_ROLE_UPDATE.js +5 -5
  123. package/src/client/websocket/handlers/GUILD_SCHEDULED_EVENT_CREATE.js +5 -5
  124. package/src/client/websocket/handlers/GUILD_SCHEDULED_EVENT_DELETE.js +5 -5
  125. package/src/client/websocket/handlers/GUILD_SCHEDULED_EVENT_UPDATE.js +5 -5
  126. package/src/client/websocket/handlers/GUILD_SCHEDULED_EVENT_USER_ADD.js +5 -5
  127. package/src/client/websocket/handlers/GUILD_SCHEDULED_EVENT_USER_REMOVE.js +5 -5
  128. package/src/client/websocket/handlers/GUILD_STICKERS_UPDATE.js +5 -5
  129. package/src/client/websocket/handlers/GUILD_UPDATE.js +5 -5
  130. package/src/client/websocket/handlers/INTERACTION_MODAL_CREATE.js +12 -12
  131. package/src/client/websocket/handlers/INVITE_CREATE.js +5 -5
  132. package/src/client/websocket/handlers/INVITE_DELETE.js +5 -5
  133. package/src/client/websocket/handlers/MESSAGE_CREATE.js +5 -5
  134. package/src/client/websocket/handlers/MESSAGE_DELETE.js +5 -5
  135. package/src/client/websocket/handlers/MESSAGE_DELETE_BULK.js +5 -5
  136. package/src/client/websocket/handlers/MESSAGE_POLL_VOTE_ADD.js +5 -22
  137. package/src/client/websocket/handlers/MESSAGE_POLL_VOTE_REMOVE.js +5 -12
  138. package/src/client/websocket/handlers/MESSAGE_REACTION_ADD.js +5 -5
  139. package/src/client/websocket/handlers/MESSAGE_REACTION_REMOVE.js +5 -5
  140. package/src/client/websocket/handlers/MESSAGE_REACTION_REMOVE_ALL.js +5 -5
  141. package/src/client/websocket/handlers/MESSAGE_REACTION_REMOVE_EMOJI.js +5 -5
  142. package/src/client/websocket/handlers/MESSAGE_UPDATE.js +16 -16
  143. package/src/client/websocket/handlers/PRESENCE_UPDATE.js +5 -5
  144. package/src/client/websocket/handlers/READY.js +121 -120
  145. package/src/client/websocket/handlers/RELATIONSHIP_ADD.js +19 -19
  146. package/src/client/websocket/handlers/RELATIONSHIP_REMOVE.js +17 -17
  147. package/src/client/websocket/handlers/RELATIONSHIP_UPDATE.js +41 -41
  148. package/src/client/websocket/handlers/RESUMED.js +14 -14
  149. package/src/client/websocket/handlers/STAGE_INSTANCE_CREATE.js +5 -5
  150. package/src/client/websocket/handlers/STAGE_INSTANCE_DELETE.js +5 -5
  151. package/src/client/websocket/handlers/STAGE_INSTANCE_UPDATE.js +5 -5
  152. package/src/client/websocket/handlers/THREAD_CREATE.js +5 -5
  153. package/src/client/websocket/handlers/THREAD_DELETE.js +5 -5
  154. package/src/client/websocket/handlers/THREAD_LIST_SYNC.js +5 -5
  155. package/src/client/websocket/handlers/THREAD_MEMBERS_UPDATE.js +5 -5
  156. package/src/client/websocket/handlers/THREAD_MEMBER_UPDATE.js +5 -5
  157. package/src/client/websocket/handlers/THREAD_UPDATE.js +16 -16
  158. package/src/client/websocket/handlers/TYPING_START.js +5 -5
  159. package/src/client/websocket/handlers/USER_GUILD_SETTINGS_UPDATE.js +6 -6
  160. package/src/client/websocket/handlers/USER_NOTE_UPDATE.js +5 -5
  161. package/src/client/websocket/handlers/USER_REQUIRED_ACTION_UPDATE.js +78 -78
  162. package/src/client/websocket/handlers/USER_SETTINGS_UPDATE.js +5 -5
  163. package/src/client/websocket/handlers/USER_UPDATE.js +5 -5
  164. package/src/client/websocket/handlers/VOICE_CHANNEL_EFFECT_SEND.js +16 -0
  165. package/src/client/websocket/handlers/VOICE_CHANNEL_STATUS_UPDATE.js +12 -12
  166. package/src/client/websocket/handlers/VOICE_SERVER_UPDATE.js +6 -6
  167. package/src/client/websocket/handlers/VOICE_STATE_UPDATE.js +5 -5
  168. package/src/client/websocket/handlers/WEBHOOKS_UPDATE.js +5 -5
  169. package/src/client/websocket/handlers/index.js +84 -83
  170. package/src/errors/DJSError.js +61 -61
  171. package/src/errors/Messages.js +217 -183
  172. package/src/errors/index.js +4 -4
  173. package/src/index.js +172 -159
  174. package/src/managers/ApplicationCommandManager.js +264 -264
  175. package/src/managers/ApplicationCommandPermissionsManager.js +417 -417
  176. package/src/managers/AutoModerationRuleManager.js +296 -296
  177. package/src/managers/BaseGuildEmojiManager.js +80 -80
  178. package/src/managers/BaseManager.js +19 -19
  179. package/src/managers/BillingManager.js +66 -66
  180. package/src/managers/CachedManager.js +71 -71
  181. package/src/managers/ChannelManager.js +148 -138
  182. package/src/managers/ClientUserSettingManager.js +372 -372
  183. package/src/managers/DataManager.js +61 -61
  184. package/src/managers/GuildBanManager.js +250 -250
  185. package/src/managers/GuildChannelManager.js +488 -488
  186. package/src/managers/GuildEmojiManager.js +171 -171
  187. package/src/managers/GuildEmojiRoleManager.js +118 -118
  188. package/src/managers/GuildForumThreadManager.js +108 -108
  189. package/src/managers/GuildInviteManager.js +213 -213
  190. package/src/managers/GuildManager.js +338 -304
  191. package/src/managers/GuildMemberManager.js +599 -597
  192. package/src/managers/GuildMemberRoleManager.js +195 -191
  193. package/src/managers/GuildScheduledEventManager.js +314 -296
  194. package/src/managers/GuildSettingManager.js +155 -155
  195. package/src/managers/GuildStickerManager.js +179 -179
  196. package/src/managers/GuildTextThreadManager.js +98 -98
  197. package/src/managers/InteractionManager.js +39 -39
  198. package/src/managers/MessageManager.js +423 -391
  199. package/src/managers/PermissionOverwriteManager.js +164 -166
  200. package/src/managers/PresenceManager.js +71 -58
  201. package/src/managers/ReactionManager.js +67 -67
  202. package/src/managers/ReactionUserManager.js +73 -71
  203. package/src/managers/RelationshipManager.js +278 -265
  204. package/src/managers/RoleManager.js +448 -352
  205. package/src/managers/SessionManager.js +66 -0
  206. package/src/managers/StageInstanceManager.js +162 -162
  207. package/src/managers/ThreadManager.js +175 -174
  208. package/src/managers/ThreadMemberManager.js +186 -186
  209. package/src/managers/UserManager.js +136 -146
  210. package/src/managers/UserNoteManager.js +53 -53
  211. package/src/managers/VoiceStateManager.js +59 -37
  212. package/src/rest/APIRequest.js +154 -160
  213. package/src/rest/APIRouter.js +53 -53
  214. package/src/rest/DiscordAPIError.js +119 -104
  215. package/src/rest/HTTPError.js +62 -62
  216. package/src/rest/RESTManager.js +67 -62
  217. package/src/rest/RateLimitError.js +55 -55
  218. package/src/rest/RequestHandler.js +466 -444
  219. package/src/sharding/Shard.js +444 -443
  220. package/src/sharding/ShardClientUtil.js +279 -275
  221. package/src/sharding/ShardingManager.js +319 -318
  222. package/src/structures/AnonymousGuild.js +98 -98
  223. package/src/structures/ApplicationCommand.js +593 -593
  224. package/src/structures/ApplicationRoleConnectionMetadata.js +48 -48
  225. package/src/structures/AutoModerationActionExecution.js +89 -89
  226. package/src/structures/AutoModerationRule.js +294 -294
  227. package/src/structures/AutocompleteInteraction.js +107 -107
  228. package/src/structures/Base.js +43 -43
  229. package/src/structures/BaseCommandInteraction.js +211 -211
  230. package/src/structures/BaseGuild.js +116 -116
  231. package/src/structures/BaseGuildEmoji.js +56 -56
  232. package/src/structures/BaseGuildTextChannel.js +191 -191
  233. package/src/structures/BaseGuildVoiceChannel.js +241 -241
  234. package/src/structures/BaseMessageComponent.js +181 -114
  235. package/src/structures/ButtonInteraction.js +11 -11
  236. package/src/structures/CallState.js +63 -63
  237. package/src/structures/CategoryChannel.js +85 -85
  238. package/src/structures/Channel.js +284 -270
  239. package/src/structures/ClientPresence.js +77 -85
  240. package/src/structures/ClientUser.js +479 -448
  241. package/src/structures/CommandInteraction.js +41 -41
  242. package/src/structures/CommandInteractionOptionResolver.js +276 -276
  243. package/src/structures/ContainerComponent.js +68 -0
  244. package/src/structures/ContextMenuInteraction.js +65 -65
  245. package/src/structures/DMChannel.js +219 -217
  246. package/src/structures/DirectoryChannel.js +20 -20
  247. package/src/structures/Emoji.js +148 -148
  248. package/src/structures/FileComponent.js +49 -0
  249. package/src/structures/ForumChannel.js +31 -261
  250. package/src/structures/GroupDMChannel.js +394 -387
  251. package/src/structures/Guild.js +1643 -1608
  252. package/src/structures/GuildAuditLogs.js +746 -729
  253. package/src/structures/GuildBan.js +59 -59
  254. package/src/structures/GuildBoost.js +108 -108
  255. package/src/structures/GuildChannel.js +470 -468
  256. package/src/structures/GuildEmoji.js +161 -161
  257. package/src/structures/GuildMember.js +636 -568
  258. package/src/structures/GuildPreview.js +191 -191
  259. package/src/structures/GuildPreviewEmoji.js +27 -27
  260. package/src/structures/GuildScheduledEvent.js +536 -441
  261. package/src/structures/GuildTemplate.js +236 -236
  262. package/src/structures/Integration.js +188 -188
  263. package/src/structures/IntegrationApplication.js +96 -96
  264. package/src/structures/Interaction.js +290 -290
  265. package/src/structures/InteractionCollector.js +248 -248
  266. package/src/structures/InteractionWebhook.js +43 -43
  267. package/src/structures/Invite.js +358 -358
  268. package/src/structures/InviteGuild.js +23 -23
  269. package/src/structures/InviteStageInstance.js +86 -86
  270. package/src/structures/MediaChannel.js +11 -0
  271. package/src/structures/MediaGalleryComponent.js +41 -0
  272. package/src/structures/MediaGalleryItem.js +47 -0
  273. package/src/structures/Message.js +1252 -1227
  274. package/src/structures/MessageActionRow.js +105 -103
  275. package/src/structures/MessageAttachment.js +216 -204
  276. package/src/structures/MessageButton.js +166 -165
  277. package/src/structures/MessageCollector.js +146 -146
  278. package/src/structures/MessageComponentInteraction.js +120 -120
  279. package/src/structures/MessageContextMenuInteraction.js +20 -20
  280. package/src/structures/MessageEmbed.js +596 -586
  281. package/src/structures/MessageMentions.js +273 -273
  282. package/src/structures/MessagePayload.js +354 -318
  283. package/src/structures/MessageReaction.js +181 -171
  284. package/src/structures/MessageSelectMenu.js +141 -140
  285. package/src/structures/Modal.js +161 -161
  286. package/src/structures/ModalSubmitFieldsResolver.js +53 -53
  287. package/src/structures/ModalSubmitInteraction.js +119 -119
  288. package/src/structures/NewsChannel.js +32 -32
  289. package/src/structures/OAuth2Guild.js +28 -28
  290. package/src/structures/PermissionOverwrites.js +198 -196
  291. package/src/structures/Poll.js +108 -0
  292. package/src/structures/PollAnswer.js +88 -0
  293. package/src/structures/Presence.js +1105 -1101
  294. package/src/structures/ReactionCollector.js +229 -229
  295. package/src/structures/ReactionEmoji.js +31 -31
  296. package/src/structures/Role.js +590 -531
  297. package/src/structures/SectionComponent.js +48 -0
  298. package/src/structures/SelectMenuInteraction.js +21 -21
  299. package/src/structures/SeparatorComponent.js +48 -0
  300. package/src/structures/Session.js +81 -0
  301. package/src/structures/StageChannel.js +104 -104
  302. package/src/structures/StageInstance.js +208 -208
  303. package/src/structures/Sticker.js +310 -310
  304. package/src/structures/StickerPack.js +95 -95
  305. package/src/structures/StoreChannel.js +56 -56
  306. package/src/structures/Team.js +118 -118
  307. package/src/structures/TeamMember.js +80 -71
  308. package/src/structures/TextChannel.js +33 -33
  309. package/src/structures/TextDisplayComponent.js +40 -0
  310. package/src/structures/TextInputComponent.js +132 -131
  311. package/src/structures/ThreadChannel.js +605 -607
  312. package/src/structures/ThreadMember.js +105 -105
  313. package/src/structures/ThreadOnlyChannel.js +249 -0
  314. package/src/structures/ThumbnailComponent.js +57 -0
  315. package/src/structures/Typing.js +74 -74
  316. package/src/structures/UnfurledMediaItem.js +29 -0
  317. package/src/structures/User.js +640 -543
  318. package/src/structures/UserContextMenuInteraction.js +29 -29
  319. package/src/structures/VoiceChannel.js +110 -110
  320. package/src/structures/VoiceChannelEffect.js +69 -0
  321. package/src/structures/VoiceRegion.js +53 -53
  322. package/src/structures/VoiceState.js +354 -341
  323. package/src/structures/WebEmbed.js +373 -373
  324. package/src/structures/Webhook.js +478 -467
  325. package/src/structures/WelcomeChannel.js +60 -60
  326. package/src/structures/WelcomeScreen.js +48 -48
  327. package/src/structures/Widget.js +87 -87
  328. package/src/structures/WidgetMember.js +99 -99
  329. package/src/structures/interfaces/Application.js +825 -313
  330. package/src/structures/interfaces/Collector.js +300 -300
  331. package/src/structures/interfaces/InteractionResponses.js +313 -313
  332. package/src/structures/interfaces/TextBasedChannel.js +759 -719
  333. package/src/util/APITypes.js +59 -0
  334. package/src/util/ActivityFlags.js +44 -44
  335. package/src/util/ApplicationFlags.js +76 -76
  336. package/src/util/AttachmentFlags.js +38 -38
  337. package/src/util/BitField.js +170 -170
  338. package/src/util/ChannelFlags.js +45 -45
  339. package/src/util/Constants.js +1914 -1773
  340. package/src/util/DataResolver.js +146 -145
  341. package/src/util/Formatters.js +228 -228
  342. package/src/util/GuildMemberFlags.js +43 -43
  343. package/src/util/Intents.js +74 -74
  344. package/src/util/InviteFlags.js +34 -29
  345. package/src/util/LimitedCollection.js +131 -131
  346. package/src/util/MessageFlags.js +63 -54
  347. package/src/util/Options.js +358 -336
  348. package/src/util/Permissions.js +202 -202
  349. package/src/util/PremiumUsageFlags.js +31 -31
  350. package/src/util/PurchasedFlags.js +33 -33
  351. package/src/util/RemoteAuth.js +382 -379
  352. package/src/util/RoleFlags.js +37 -37
  353. package/src/util/SnowflakeUtil.js +92 -92
  354. package/src/util/Speaking.js +33 -0
  355. package/src/util/Sweepers.js +466 -466
  356. package/src/util/SystemChannelFlags.js +55 -55
  357. package/src/util/ThreadMemberFlags.js +30 -30
  358. package/src/util/UserFlags.js +104 -104
  359. package/src/util/Util.js +1048 -889
  360. package/typings/enums.d.ts +439 -297
  361. package/typings/index.d.ts +8247 -7432
  362. package/typings/rawDataTypes.d.ts +403 -342
  363. package/src/structures/MessagePoll.js +0 -238
@@ -1,906 +1,907 @@
1
- 'use strict';
2
-
3
- const EventEmitter = require('node:events');
4
- const { setTimeout, setInterval, clearTimeout } = require('node:timers');
5
- const WebSocket = require('../../WebSocket');
6
- const { Status, Events, ShardEvents, Opcodes, WSEvents, WSCodes } = require('../../util/Constants');
7
- const Intents = require('../../util/Intents');
8
- const Util = require('../../util/Util');
9
-
10
- const STATUS_KEYS = Object.keys(Status);
11
- const CONNECTION_STATE = Object.keys(WebSocket.WebSocket);
12
-
13
- let zlib;
14
-
15
- try {
16
- zlib = require('zlib-sync');
17
- } catch {} // eslint-disable-line no-empty
18
-
19
- /**
20
- * Represents a Shard's WebSocket connection
21
- * @extends {EventEmitter}
22
- */
23
- class WebSocketShard extends EventEmitter {
24
- constructor(manager, id) {
25
- super();
26
-
27
- /**
28
- * The WebSocketManager of the shard
29
- * @type {WebSocketManager}
30
- */
31
- this.manager = manager;
32
-
33
- /**
34
- * The shard's id
35
- * @type {number}
36
- */
37
- this.id = id;
38
-
39
- /**
40
- * The resume URL for this shard
41
- * @type {?string}
42
- * @private
43
- */
44
- this.resumeURL = null;
45
-
46
- /**
47
- * The current status of the shard
48
- * @type {Status}
49
- */
50
- this.status = Status.IDLE;
51
-
52
- /**
53
- * The current sequence of the shard
54
- * @type {number}
55
- * @private
56
- */
57
- this.sequence = -1;
58
-
59
- /**
60
- * The sequence of the shard after close
61
- * @type {number}
62
- * @private
63
- */
64
- this.closeSequence = 0;
65
-
66
- /**
67
- * The current session id of the shard
68
- * @type {?string}
69
- * @private
70
- */
71
- this.sessionId = null;
72
-
73
- /**
74
- * The previous heartbeat ping of the shard
75
- * @type {number}
76
- */
77
- this.ping = -1;
78
-
79
- /**
80
- * The last time a ping was sent (a timestamp)
81
- * @type {number}
82
- * @private
83
- */
84
- this.lastPingTimestamp = -1;
85
-
86
- /**
87
- * If we received a heartbeat ack back. Used to identify zombie connections
88
- * @type {boolean}
89
- * @private
90
- */
91
- this.lastHeartbeatAcked = true;
92
-
93
- /**
94
- * Used to prevent calling {@link WebSocketShard#event:close} twice while closing or terminating the WebSocket.
95
- * @type {boolean}
96
- * @private
97
- */
98
- this.closeEmitted = false;
99
-
100
- /**
101
- * Contains the rate limit queue and metadata
102
- * @name WebSocketShard#ratelimit
103
- * @type {Object}
104
- * @private
105
- */
106
- Object.defineProperty(this, 'ratelimit', {
107
- value: {
108
- queue: [],
109
- total: 120,
110
- remaining: 120,
111
- time: 60e3,
112
- timer: null,
113
- },
114
- });
115
-
116
- /**
117
- * The WebSocket connection for the current shard
118
- * @name WebSocketShard#connection
119
- * @type {?WebSocket}
120
- * @private
121
- */
122
- Object.defineProperty(this, 'connection', { value: null, writable: true });
123
-
124
- /**
125
- * @external Inflate
126
- * @see {@link https://www.npmjs.com/package/zlib-sync}
127
- */
128
-
129
- /**
130
- * The compression to use
131
- * @name WebSocketShard#inflate
132
- * @type {?Inflate}
133
- * @private
134
- */
135
- Object.defineProperty(this, 'inflate', { value: null, writable: true });
136
-
137
- /**
138
- * The HELLO timeout
139
- * @name WebSocketShard#helloTimeout
140
- * @type {?NodeJS.Timeout}
141
- * @private
142
- */
143
- Object.defineProperty(this, 'helloTimeout', { value: null, writable: true });
144
-
145
- /**
146
- * The WebSocket timeout.
147
- * @name WebSocketShard#wsCloseTimeout
148
- * @type {?NodeJS.Timeout}
149
- * @private
150
- */
151
- Object.defineProperty(this, 'wsCloseTimeout', { value: null, writable: true });
152
-
153
- /**
154
- * If the manager attached its event handlers on the shard
155
- * @name WebSocketShard#eventsAttached
156
- * @type {boolean}
157
- * @private
158
- */
159
- Object.defineProperty(this, 'eventsAttached', { value: false, writable: true });
160
-
161
- /**
162
- * A set of guild ids this shard expects to receive
163
- * @name WebSocketShard#expectedGuilds
164
- * @type {?Set<string>}
165
- * @private
166
- */
167
- Object.defineProperty(this, 'expectedGuilds', { value: null, writable: true });
168
-
169
- /**
170
- * The ready timeout
171
- * @name WebSocketShard#readyTimeout
172
- * @type {?NodeJS.Timeout}
173
- * @private
174
- */
175
- Object.defineProperty(this, 'readyTimeout', { value: null, writable: true });
176
-
177
- /**
178
- * Time when the WebSocket connection was opened
179
- * @name WebSocketShard#connectedAt
180
- * @type {number}
181
- * @private
182
- */
183
- Object.defineProperty(this, 'connectedAt', { value: 0, writable: true });
184
- }
185
-
186
- /**
187
- * Emits a debug event.
188
- * @param {string} message The debug message
189
- * @private
190
- */
191
- debug(message) {
192
- this.manager.debug(message, this);
193
- }
194
-
195
- /**
196
- * Connects the shard to the gateway.
197
- * @private
198
- * @returns {Promise<void>} A promise that will resolve if the shard turns ready successfully,
199
- * or reject if we couldn't connect
200
- */
201
- connect() {
202
- const { client } = this.manager;
203
-
204
- if (this.connection?.readyState === WebSocket.OPEN && this.status === Status.READY) {
205
- return Promise.resolve();
206
- }
207
-
208
- const gateway = this.resumeURL ?? this.manager.gateway;
209
-
210
- return new Promise((resolve, reject) => {
211
- const cleanup = () => {
212
- this.removeListener(ShardEvents.CLOSE, onClose);
213
- this.removeListener(ShardEvents.READY, onReady);
214
- this.removeListener(ShardEvents.RESUMED, onResumed);
215
- this.removeListener(ShardEvents.INVALID_SESSION, onInvalidOrDestroyed);
216
- this.removeListener(ShardEvents.DESTROYED, onInvalidOrDestroyed);
217
- };
218
-
219
- const onReady = () => {
220
- cleanup();
221
- resolve();
222
- };
223
-
224
- const onResumed = () => {
225
- cleanup();
226
- resolve();
227
- };
228
-
229
- const onClose = event => {
230
- cleanup();
231
- reject(event);
232
- };
233
-
234
- const onInvalidOrDestroyed = () => {
235
- cleanup();
236
- // eslint-disable-next-line prefer-promise-reject-errors
237
- reject();
238
- };
239
-
240
- this.once(ShardEvents.READY, onReady);
241
- this.once(ShardEvents.RESUMED, onResumed);
242
- this.once(ShardEvents.CLOSE, onClose);
243
- this.once(ShardEvents.INVALID_SESSION, onInvalidOrDestroyed);
244
- this.once(ShardEvents.DESTROYED, onInvalidOrDestroyed);
245
-
246
- if (this.connection?.readyState === WebSocket.OPEN) {
247
- this.debug('An open connection was found, attempting an immediate identify.');
248
- this.identify();
249
- return;
250
- }
251
-
252
- if (this.connection) {
253
- this.debug(`A connection object was found. Cleaning up before continuing.
254
- State: ${CONNECTION_STATE[this.connection.readyState]}`);
255
- this.destroy({ emit: false });
256
- }
257
-
258
- const wsQuery = { v: client.options.ws.version };
259
-
260
- if (zlib) {
261
- this.inflate = new zlib.Inflate({
262
- chunkSize: 65535,
263
- flush: zlib.Z_SYNC_FLUSH,
264
- to: WebSocket.encoding === 'json' ? 'string' : '',
265
- });
266
- wsQuery.compress = 'zlib-stream';
267
- }
268
-
269
- this.debug(
270
- `[CONNECT]
271
- Gateway : ${gateway}
272
- Version : ${client.options.ws.version}
273
- Encoding : ${WebSocket.encoding}
274
- Compression: ${zlib ? 'zlib-stream' : 'none'}
275
- Agent : ${Util.verifyProxyAgent(client.options.ws.agent)}`,
276
- );
277
-
278
- this.status = this.status === Status.DISCONNECTED ? Status.RECONNECTING : Status.CONNECTING;
279
- this.setHelloTimeout();
280
- this.setWsCloseTimeout(-1);
281
- this.connectedAt = Date.now();
282
-
283
- // Adding a handshake timeout to just make sure no zombie connection appears.
284
- const ws = (this.connection = WebSocket.create(gateway, wsQuery, {
285
- handshakeTimeout: 30_000,
286
- agent: Util.verifyProxyAgent(client.options.ws.agent) ? client.options.ws.agent : undefined,
287
- }));
288
- ws.onopen = this.onOpen.bind(this);
289
- ws.onmessage = this.onMessage.bind(this);
290
- ws.onerror = this.onError.bind(this);
291
- ws.onclose = this.onClose.bind(this);
292
- });
293
- }
294
-
295
- /**
296
- * Called whenever a connection is opened to the gateway.
297
- * @private
298
- */
299
- onOpen() {
300
- this.debug(`[CONNECTED] Took ${Date.now() - this.connectedAt}ms`);
301
- this.status = Status.NEARLY;
302
- }
303
-
304
- /**
305
- * Called whenever a message is received.
306
- * @param {MessageEvent} event Event received
307
- * @private
308
- */
309
- onMessage({ data }) {
310
- let raw;
311
- if (data instanceof ArrayBuffer) data = new Uint8Array(data);
312
- if (zlib) {
313
- const l = data.length;
314
- const flush =
315
- l >= 4 && data[l - 4] === 0x00 && data[l - 3] === 0x00 && data[l - 2] === 0xff && data[l - 1] === 0xff;
316
-
317
- this.inflate.push(data, flush && zlib.Z_SYNC_FLUSH);
318
- if (!flush) return;
319
- raw = this.inflate.result;
320
- } else {
321
- raw = data;
322
- }
323
- let packet;
324
- try {
325
- packet = WebSocket.unpack(raw);
326
- } catch (err) {
327
- this.manager.client.emit(Events.SHARD_ERROR, err, this.id);
328
- return;
329
- }
330
- this.manager.client.emit(Events.RAW, packet, this.id);
331
- if (packet.op === Opcodes.DISPATCH) this.manager.emit(packet.t, packet.d, this.id);
332
- this.onPacket(packet);
333
- }
334
-
335
- /**
336
- * Called whenever an error occurs with the WebSocket.
337
- * @param {ErrorEvent} event The error that occurred
338
- * @private
339
- */
340
- onError(event) {
341
- const error = event?.error ?? event;
342
- if (!error) return;
343
-
344
- /**
345
- * Emitted whenever a shard's WebSocket encounters a connection error.
346
- * @event Client#shardError
347
- * @param {Error} error The encountered error
348
- * @param {number} shardId The shard that encountered this error
349
- */
350
- this.manager.client.emit(Events.SHARD_ERROR, error, this.id);
351
- }
352
-
353
- /**
354
- * @external CloseEvent
355
- * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent}
356
- */
357
-
358
- /**
359
- * @external ErrorEvent
360
- * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/ErrorEvent}
361
- */
362
-
363
- /**
364
- * @external MessageEvent
365
- * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent}
366
- */
367
-
368
- /**
369
- * Called whenever a connection to the gateway is closed.
370
- * @param {CloseEvent} event Close event that was received
371
- * @private
372
- */
373
- onClose(event) {
374
- this.closeEmitted = true;
375
- if (this.sequence !== -1) this.closeSequence = this.sequence;
376
- this.sequence = -1;
377
- this.setHeartbeatTimer(-1);
378
- this.setHelloTimeout(-1);
379
- // Clearing the WebSocket close timeout as close was emitted.
380
- this.setWsCloseTimeout(-1);
381
- // If we still have a connection object, clean up its listeners
382
- if (this.connection) {
383
- this._cleanupConnection();
384
- // Having this after _cleanupConnection to just clean up the connection and not listen to ws.onclose
385
- this.destroy({ reset: !this.sessionId, emit: false, log: false });
386
- }
387
- this.status = Status.DISCONNECTED;
388
- this.emitClose(event);
389
- }
390
-
391
- /**
392
- * This method is responsible to emit close event for this shard.
393
- * This method helps the shard reconnect.
394
- * @param {CloseEvent} [event] Close event that was received
395
- */
396
- emitClose(
397
- event = {
398
- code: 1011,
399
- reason: WSCodes[1011],
400
- wasClean: false,
401
- },
402
- ) {
403
- this.debug(`[CLOSE]
404
- Event Code: ${event.code}
405
- Clean : ${event.wasClean}
406
- Reason : ${event.reason ?? 'No reason received'}`);
407
- /**
408
- * Emitted when a shard's WebSocket closes.
409
- * @private
410
- * @event WebSocketShard#close
411
- * @param {CloseEvent} event The received event
412
- */
413
- this.emit(ShardEvents.CLOSE, event);
414
- }
415
-
416
- /**
417
- * Called whenever a packet is received.
418
- * @param {Object} packet The received packet
419
- * @private
420
- */
421
- onPacket(packet) {
422
- if (!packet) {
423
- this.debug(`Received broken packet: '${packet}'.`);
424
- return;
425
- }
426
-
427
- switch (packet.t) {
428
- case WSEvents.READY:
429
- /**
430
- * Emitted when the shard receives the READY payload and is now waiting for guilds
431
- * @event WebSocketShard#ready
432
- */
433
- this.emit(ShardEvents.READY);
434
-
435
- this.resumeURL = packet.d.resume_gateway_url;
436
- this.sessionId = packet.d.session_id;
437
- this.expectedGuilds = new Set(packet.d.guilds.filter(d => d?.unavailable == true).map(d => d.id));
438
- this.status = Status.WAITING_FOR_GUILDS;
439
- this.debug(`[READY] Session ${this.sessionId} | Resume url ${this.resumeURL}.`);
440
- this.lastHeartbeatAcked = true;
441
- this.sendHeartbeat('ReadyHeartbeat');
442
- break;
443
- case WSEvents.RESUMED: {
444
- /**
445
- * Emitted when the shard resumes successfully
446
- * @event WebSocketShard#resumed
447
- */
448
- this.emit(ShardEvents.RESUMED);
449
-
450
- this.status = Status.READY;
451
- const replayed = packet.s - this.closeSequence;
452
- this.debug(`[RESUMED] Session ${this.sessionId} | Replayed ${replayed} events.`);
453
- this.lastHeartbeatAcked = true;
454
- this.sendHeartbeat('ResumeHeartbeat');
455
- break;
456
- }
457
- }
458
-
459
- if (packet.s > this.sequence) this.sequence = packet.s;
460
-
461
- switch (packet.op) {
462
- case Opcodes.HELLO:
463
- this.setHelloTimeout(-1);
464
- this.setHeartbeatTimer(packet.d.heartbeat_interval);
465
- this.identify();
466
- break;
467
- case Opcodes.RECONNECT:
468
- this.debug('[RECONNECT] Discord asked us to reconnect');
469
- this.destroy({ closeCode: 4_000 });
470
- break;
471
- case Opcodes.INVALID_SESSION:
472
- this.debug(`[INVALID SESSION] Resumable: ${packet.d}.`);
473
- // If we can resume the session, do so immediately
474
- if (packet.d) {
475
- this.identifyResume();
476
- return;
477
- }
478
- // Reset the sequence
479
- this.sequence = -1;
480
- // Reset the session id as it's invalid
481
- this.sessionId = null;
482
- // Set the status to reconnecting
483
- this.status = Status.RECONNECTING;
484
- // Finally, emit the INVALID_SESSION event
485
- /**
486
- * Emitted when the session has been invalidated.
487
- * @event WebSocketShard#invalidSession
488
- */
489
- this.emit(ShardEvents.INVALID_SESSION);
490
- break;
491
- case Opcodes.HEARTBEAT_ACK:
492
- this.ackHeartbeat();
493
- break;
494
- case Opcodes.HEARTBEAT:
495
- this.sendHeartbeat('HeartbeatRequest', true);
496
- break;
497
- default:
498
- this.manager.handlePacket(packet, this);
499
- if (this.status === Status.WAITING_FOR_GUILDS && packet.t === WSEvents.GUILD_CREATE) {
500
- this.expectedGuilds.delete(packet.d.id);
501
- this.checkReady();
502
- }
503
- }
504
- }
505
-
506
- /**
507
- * Checks if the shard can be marked as ready
508
- * @private
509
- */
510
- checkReady() {
511
- // Step 0. Clear the ready timeout, if it exists
512
- if (this.readyTimeout) {
513
- clearTimeout(this.readyTimeout);
514
- this.readyTimeout = null;
515
- }
516
- // Step 1. If we don't have any other guilds pending, we are ready
517
- if (!this.expectedGuilds.size) {
518
- this.debug('Shard received all its guilds. Marking as fully ready.');
519
- this.status = Status.READY;
520
-
521
- /**
522
- * Emitted when the shard is fully ready.
523
- * This event is emitted if:
524
- * * all guilds were received by this shard
525
- * * the ready timeout expired, and some guilds are unavailable
526
- * @event WebSocketShard#allReady
527
- * @param {?Set<string>} unavailableGuilds Set of unavailable guilds, if any
528
- */
529
- this.emit(ShardEvents.ALL_READY);
530
- return;
531
- }
532
- const hasGuildsIntent = new Intents(this.manager.client.options.intents).has(Intents.FLAGS.GUILDS);
533
- // Step 2. Create a timeout that will mark the shard as ready if there are still unavailable guilds
534
- // * The timeout is 15 seconds by default
535
- // * This can be optionally changed in the client options via the `waitGuildTimeout` option
536
- // * a timeout time of zero will skip this timeout, which potentially could cause the Client to miss guilds.
537
-
538
- const { waitGuildTimeout } = this.manager.client.options;
539
-
540
- this.readyTimeout = setTimeout(
541
- () => {
542
- this.debug(
543
- `Shard ${hasGuildsIntent ? 'did' : 'will'} not receive any more guild packets` +
544
- `${hasGuildsIntent ? ` in ${waitGuildTimeout} ms` : ''}.\nUnavailable guild count: ${
545
- this.expectedGuilds.size
546
- }`,
547
- );
548
-
549
- this.readyTimeout = null;
550
-
551
- this.status = Status.READY;
552
-
553
- this.emit(ShardEvents.ALL_READY, this.expectedGuilds);
554
- },
555
- hasGuildsIntent ? waitGuildTimeout : 0,
556
- ).unref();
557
- }
558
-
559
- /**
560
- * Sets the HELLO packet timeout.
561
- * @param {number} [time] If set to -1, it will clear the hello timeout
562
- * @private
563
- */
564
- setHelloTimeout(time) {
565
- if (time === -1) {
566
- if (this.helloTimeout) {
567
- this.debug('Clearing the HELLO timeout.');
568
- clearTimeout(this.helloTimeout);
569
- this.helloTimeout = null;
570
- }
571
- return;
572
- }
573
- this.debug('Setting a HELLO timeout for 20s.');
574
- this.helloTimeout = setTimeout(() => {
575
- this.debug('Did not receive HELLO in time. Destroying and connecting again.');
576
- this.destroy({ reset: true, closeCode: 4009 });
577
- }, 20_000).unref();
578
- }
579
-
580
- /**
581
- * Sets the WebSocket Close timeout.
582
- * This method is responsible for detecting any zombie connections if the WebSocket fails to close properly.
583
- * @param {number} [time] If set to -1, it will clear the timeout
584
- * @private
585
- */
586
- setWsCloseTimeout(time) {
587
- if (this.wsCloseTimeout) {
588
- this.debug('[WebSocket] Clearing the close timeout.');
589
- clearTimeout(this.wsCloseTimeout);
590
- }
591
- if (time === -1) {
592
- this.wsCloseTimeout = null;
593
- return;
594
- }
595
- this.wsCloseTimeout = setTimeout(() => {
596
- this.setWsCloseTimeout(-1);
597
-
598
- // Check if close event was emitted.
599
- if (this.closeEmitted) {
600
- this.debug(`[WebSocket] close was already emitted, assuming the connection was closed properly.`);
601
- // Setting the variable false to check for zombie connections.
602
- this.closeEmitted = false;
603
- return;
604
- }
605
-
606
- this.debug(
607
- // eslint-disable-next-line max-len
608
- `[WebSocket] Close Emitted: ${this.closeEmitted} | did not close properly, assuming a zombie connection.\nEmitting close and reconnecting again.`,
609
- );
610
-
611
- if (this.connection) this._cleanupConnection();
612
-
613
- this.emitClose({
614
- code: 4009,
615
- reason: 'Session time out.',
616
- wasClean: false,
617
- });
618
- }, time);
619
- }
620
-
621
- /**
622
- * Sets the heartbeat timer for this shard.
623
- * @param {number} time If -1, clears the interval, any other number sets an interval
624
- * @private
625
- */
626
- setHeartbeatTimer(time) {
627
- if (time === -1) {
628
- if (this.heartbeatInterval) {
629
- this.debug('Clearing the heartbeat interval.');
630
- clearInterval(this.heartbeatInterval);
631
- this.heartbeatInterval = null;
632
- }
633
- return;
634
- }
635
- this.debug(`Setting a heartbeat interval for ${time}ms.`);
636
- // Sanity checks
637
- if (this.heartbeatInterval) clearInterval(this.heartbeatInterval);
638
- this.heartbeatInterval = setInterval(() => this.sendHeartbeat(), time).unref();
639
- }
640
-
641
- /**
642
- * Sends a heartbeat to the WebSocket.
643
- * If this shard didn't receive a heartbeat last time, it will destroy it and reconnect
644
- * @param {string} [tag='HeartbeatTimer'] What caused this heartbeat to be sent
645
- * @param {boolean} [ignoreHeartbeatAck] If we should send the heartbeat forcefully.
646
- * @private
647
- */
648
- sendHeartbeat(
649
- tag = 'HeartbeatTimer',
650
- ignoreHeartbeatAck = [Status.WAITING_FOR_GUILDS, Status.IDENTIFYING, Status.RESUMING].includes(this.status),
651
- ) {
652
- if (ignoreHeartbeatAck && !this.lastHeartbeatAcked) {
653
- this.debug(`[${tag}] Didn't process heartbeat ack yet but we are still connected. Sending one now.`);
654
- } else if (!this.lastHeartbeatAcked) {
655
- this.debug(
656
- `[${tag}] Didn't receive a heartbeat ack last time, assuming zombie connection. Destroying and reconnecting.
657
- Status : ${STATUS_KEYS[this.status]}
658
- Sequence : ${this.sequence}
659
- Connection State: ${this.connection ? CONNECTION_STATE[this.connection.readyState] : 'No Connection??'}`,
660
- );
661
- this.destroy({ reset: true, closeCode: 4009 });
662
- return;
663
- }
664
-
665
- this.debug(`[${tag}] Sending a heartbeat.`);
666
- this.lastHeartbeatAcked = false;
667
- this.lastPingTimestamp = Date.now();
668
- this.send({ op: Opcodes.HEARTBEAT, d: this.sequence }, true);
669
- }
670
-
671
- /**
672
- * Acknowledges a heartbeat.
673
- * @private
674
- */
675
- ackHeartbeat() {
676
- this.lastHeartbeatAcked = true;
677
- const latency = Date.now() - this.lastPingTimestamp;
678
- this.debug(`Heartbeat acknowledged, latency of ${latency}ms.`);
679
- this.ping = latency;
680
- }
681
-
682
- /**
683
- * Identifies the client on the connection.
684
- * @private
685
- * @returns {void}
686
- */
687
- identify() {
688
- return this.sessionId ? this.identifyResume() : this.identifyNew();
689
- }
690
-
691
- /**
692
- * Identifies as a new connection on the gateway.
693
- * @private
694
- */
695
- identifyNew() {
696
- const { client } = this.manager;
697
- if (!client.token) {
698
- this.debug('[IDENTIFY] No token available to identify a new session.');
699
- return;
700
- }
701
-
702
- this.status = Status.IDENTIFYING;
703
-
704
- // Patch something
705
- Object.keys(client.options.ws.properties)
706
- .filter(k => k.startsWith('$'))
707
- .forEach(k => {
708
- client.options.ws.properties[k.slice(1)] = client.options.ws.properties[k];
709
- delete client.options.ws.properties[k];
710
- });
711
-
712
- // Clone the identify payload and assign the token and shard info
713
- const d = {
714
- ...client.options.ws,
715
- token: client.token,
716
- };
717
-
718
- delete d.version;
719
- delete d.agent;
720
-
721
- this.debug(`[IDENTIFY] Shard ${this.id}`);
722
- this.send({ op: Opcodes.IDENTIFY, d }, true);
723
- }
724
-
725
- /**
726
- * Resumes a session on the gateway.
727
- * @private
728
- */
729
- identifyResume() {
730
- if (!this.sessionId) {
731
- this.debug('[RESUME] No session id was present; identifying as a new session.');
732
- this.identifyNew();
733
- return;
734
- }
735
-
736
- this.status = Status.RESUMING;
737
-
738
- this.debug(`[RESUME] Session ${this.sessionId}, sequence ${this.closeSequence}`);
739
-
740
- const d = {
741
- token: this.manager.client.token,
742
- session_id: this.sessionId,
743
- seq: this.closeSequence,
744
- };
745
-
746
- this.send({ op: Opcodes.RESUME, d }, true);
747
- }
748
-
749
- /**
750
- * Adds a packet to the queue to be sent to the gateway.
751
- * <warn>If you use this method, make sure you understand that you need to provide
752
- * a full [Payload](https://discord.com/developers/docs/topics/gateway-events#payload-structure).
753
- * Do not use this method if you don't know what you're doing.</warn>
754
- * @param {Object} data The full packet to send
755
- * @param {boolean} [important=false] If this packet should be added first in queue
756
- */
757
- send(data, important = false) {
758
- this.ratelimit.queue[important ? 'unshift' : 'push'](data);
759
- this.processQueue();
760
- }
761
-
762
- /**
763
- * Sends data, bypassing the queue.
764
- * @param {Object} data Packet to send
765
- * @returns {void}
766
- * @private
767
- */
768
- _send(data) {
769
- if (this.connection?.readyState !== WebSocket.OPEN) {
770
- this.debug(`Tried to send packet '${JSON.stringify(data)}' but no WebSocket is available!`);
771
- this.destroy({ closeCode: 4_000 });
772
- return;
773
- }
774
-
775
- this.connection.send(WebSocket.pack(data), err => {
776
- if (err) this.manager.client.emit(Events.SHARD_ERROR, err, this.id);
777
- });
778
- }
779
-
780
- /**
781
- * Processes the current WebSocket queue.
782
- * @returns {void}
783
- * @private
784
- */
785
- processQueue() {
786
- if (this.ratelimit.remaining === 0) return;
787
- if (this.ratelimit.queue.length === 0) return;
788
- if (this.ratelimit.remaining === this.ratelimit.total) {
789
- this.ratelimit.timer = setTimeout(() => {
790
- this.ratelimit.remaining = this.ratelimit.total;
791
- this.processQueue();
792
- }, this.ratelimit.time).unref();
793
- }
794
- while (this.ratelimit.remaining > 0) {
795
- const item = this.ratelimit.queue.shift();
796
- if (!item) return;
797
- this._send(item);
798
- this.ratelimit.remaining--;
799
- }
800
- }
801
-
802
- /**
803
- * Destroys this shard and closes its WebSocket connection.
804
- * @param {Object} [options={ closeCode: 1000, reset: false, emit: true, log: true }] Options for destroying the shard
805
- * @private
806
- */
807
- destroy({ closeCode = 1_000, reset = false, emit = true, log = true } = {}) {
808
- if (log) {
809
- this.debug(`[DESTROY]
810
- Close Code : ${closeCode}
811
- Reset : ${reset}
812
- Emit DESTROYED: ${emit}`);
813
- }
814
-
815
- // Step 0: Remove all timers
816
- this.setHeartbeatTimer(-1);
817
- this.setHelloTimeout(-1);
818
-
819
- this.debug(
820
- `[WebSocket] Destroy: Attempting to close the WebSocket. | WS State: ${
821
- CONNECTION_STATE[this.connection?.readyState ?? WebSocket.CLOSED]
822
- }`,
823
- );
824
- // Step 1: Close the WebSocket connection, if any, otherwise, emit DESTROYED
825
- if (this.connection) {
826
- // If the connection is currently opened, we will (hopefully) receive close
827
- if (this.connection.readyState === WebSocket.OPEN) {
828
- this.connection.close(closeCode);
829
- this.debug(`[WebSocket] Close: Tried closing. | WS State: ${CONNECTION_STATE[this.connection.readyState]}`);
830
- } else {
831
- // Connection is not OPEN
832
- this.debug(`WS State: ${CONNECTION_STATE[this.connection.readyState]}`);
833
- // Attempt to close the connection just in case
834
- try {
835
- this.connection.close(closeCode);
836
- } catch (err) {
837
- this.debug(
838
- `[WebSocket] Close: Something went wrong while closing the WebSocket: ${
839
- err.message || err
840
- }. Forcefully terminating the connection | WS State: ${CONNECTION_STATE[this.connection.readyState]}`,
841
- );
842
- this.connection.terminate();
843
- }
844
- // Emit the destroyed event if needed
845
- if (emit) this._emitDestroyed();
846
- }
847
- } else if (emit) {
848
- // We requested a destroy, but we had no connection. Emit destroyed
849
- this._emitDestroyed();
850
- }
851
-
852
- this.debug(
853
- `[WebSocket] Adding a WebSocket close timeout to ensure a correct WS reconnect.
854
- Timeout: ${this.manager.client.options.closeTimeout}ms`,
855
- );
856
- this.setWsCloseTimeout(this.manager.client.options.closeTimeout);
857
-
858
- // Step 2: Null the connection object
859
- this.connection = null;
860
-
861
- // Step 3: Set the shard status to DISCONNECTED
862
- this.status = Status.DISCONNECTED;
863
-
864
- // Step 4: Cache the old sequence (use to attempt a resume)
865
- if (this.sequence !== -1) this.closeSequence = this.sequence;
866
-
867
- // Step 5: Reset the sequence, resume URL and session id if requested
868
- if (reset) {
869
- this.resumeURL = null;
870
- this.sequence = -1;
871
- this.sessionId = null;
872
- }
873
-
874
- // Step 6: reset the rate limit data
875
- this.ratelimit.remaining = this.ratelimit.total;
876
- this.ratelimit.queue.length = 0;
877
- if (this.ratelimit.timer) {
878
- clearTimeout(this.ratelimit.timer);
879
- this.ratelimit.timer = null;
880
- }
881
- }
882
-
883
- /**
884
- * Cleans up the WebSocket connection listeners.
885
- * @private
886
- */
887
- _cleanupConnection() {
888
- this.connection.onopen = this.connection.onclose = this.connection.onmessage = null;
889
- this.connection.onerror = () => null;
890
- }
891
-
892
- /**
893
- * Emits the DESTROYED event on the shard
894
- * @private
895
- */
896
- _emitDestroyed() {
897
- /**
898
- * Emitted when a shard is destroyed, but no WebSocket connection was present.
899
- * @private
900
- * @event WebSocketShard#destroyed
901
- */
902
- this.emit(ShardEvents.DESTROYED);
903
- }
904
- }
905
-
906
- module.exports = WebSocketShard;
1
+ 'use strict';
2
+
3
+ const EventEmitter = require('node:events');
4
+ const { setTimeout, setInterval, clearTimeout } = require('node:timers');
5
+ const WebSocket = require('../../WebSocket');
6
+ const { Status, Events, ShardEvents, Opcodes, WSEvents, WSCodes } = require('../../util/Constants');
7
+ const Intents = require('../../util/Intents');
8
+ const Util = require('../../util/Util');
9
+
10
+ const STATUS_KEYS = Object.keys(Status);
11
+ const CONNECTION_STATE = Object.keys(WebSocket.WebSocket);
12
+
13
+ let zlib;
14
+
15
+ try {
16
+ zlib = require('zlib-sync');
17
+ } catch {} // eslint-disable-line no-empty
18
+
19
+ /**
20
+ * Represents a Shard's WebSocket connection
21
+ * @extends {EventEmitter}
22
+ */
23
+ class WebSocketShard extends EventEmitter {
24
+ constructor(manager, id) {
25
+ super();
26
+
27
+ /**
28
+ * The WebSocketManager of the shard
29
+ * @type {WebSocketManager}
30
+ */
31
+ this.manager = manager;
32
+
33
+ /**
34
+ * The shard's id
35
+ * @type {number}
36
+ */
37
+ this.id = id;
38
+
39
+ /**
40
+ * The resume URL for this shard
41
+ * @type {?string}
42
+ * @private
43
+ */
44
+ this.resumeURL = null;
45
+
46
+ /**
47
+ * The current status of the shard
48
+ * @type {Status}
49
+ */
50
+ this.status = Status.IDLE;
51
+
52
+ /**
53
+ * The current sequence of the shard
54
+ * @type {number}
55
+ * @private
56
+ */
57
+ this.sequence = -1;
58
+
59
+ /**
60
+ * The sequence of the shard after close
61
+ * @type {number}
62
+ * @private
63
+ */
64
+ this.closeSequence = 0;
65
+
66
+ /**
67
+ * The current session id of the shard
68
+ * @type {?string}
69
+ * @private
70
+ */
71
+ this.sessionId = null;
72
+
73
+ /**
74
+ * The previous heartbeat ping of the shard
75
+ * @type {number}
76
+ */
77
+ this.ping = -1;
78
+
79
+ /**
80
+ * The last time a ping was sent (a timestamp)
81
+ * @type {number}
82
+ * @private
83
+ */
84
+ this.lastPingTimestamp = -1;
85
+
86
+ /**
87
+ * If we received a heartbeat ack back. Used to identify zombie connections
88
+ * @type {boolean}
89
+ * @private
90
+ */
91
+ this.lastHeartbeatAcked = true;
92
+
93
+ /**
94
+ * Used to prevent calling {@link WebSocketShard#event:close} twice while closing or terminating the WebSocket.
95
+ * @type {boolean}
96
+ * @private
97
+ */
98
+ this.closeEmitted = false;
99
+
100
+ /**
101
+ * Contains the rate limit queue and metadata
102
+ * @name WebSocketShard#ratelimit
103
+ * @type {Object}
104
+ * @private
105
+ */
106
+ Object.defineProperty(this, 'ratelimit', {
107
+ value: {
108
+ queue: [],
109
+ total: 120,
110
+ remaining: 120,
111
+ time: 60e3,
112
+ timer: null,
113
+ },
114
+ });
115
+
116
+ /**
117
+ * The WebSocket connection for the current shard
118
+ * @name WebSocketShard#connection
119
+ * @type {?WebSocket}
120
+ * @private
121
+ */
122
+ Object.defineProperty(this, 'connection', { value: null, writable: true });
123
+
124
+ /**
125
+ * @external Inflate
126
+ * @see {@link https://www.npmjs.com/package/zlib-sync}
127
+ */
128
+
129
+ /**
130
+ * The compression to use
131
+ * @name WebSocketShard#inflate
132
+ * @type {?Inflate}
133
+ * @private
134
+ */
135
+ Object.defineProperty(this, 'inflate', { value: null, writable: true });
136
+
137
+ /**
138
+ * The HELLO timeout
139
+ * @name WebSocketShard#helloTimeout
140
+ * @type {?NodeJS.Timeout}
141
+ * @private
142
+ */
143
+ Object.defineProperty(this, 'helloTimeout', { value: null, writable: true });
144
+
145
+ /**
146
+ * The WebSocket timeout.
147
+ * @name WebSocketShard#wsCloseTimeout
148
+ * @type {?NodeJS.Timeout}
149
+ * @private
150
+ */
151
+ Object.defineProperty(this, 'wsCloseTimeout', { value: null, writable: true });
152
+
153
+ /**
154
+ * If the manager attached its event handlers on the shard
155
+ * @name WebSocketShard#eventsAttached
156
+ * @type {boolean}
157
+ * @private
158
+ */
159
+ Object.defineProperty(this, 'eventsAttached', { value: false, writable: true });
160
+
161
+ /**
162
+ * A set of guild ids this shard expects to receive
163
+ * @name WebSocketShard#expectedGuilds
164
+ * @type {?Set<string>}
165
+ * @private
166
+ */
167
+ Object.defineProperty(this, 'expectedGuilds', { value: null, writable: true });
168
+
169
+ /**
170
+ * The ready timeout
171
+ * @name WebSocketShard#readyTimeout
172
+ * @type {?NodeJS.Timeout}
173
+ * @private
174
+ */
175
+ Object.defineProperty(this, 'readyTimeout', { value: null, writable: true });
176
+
177
+ /**
178
+ * Time when the WebSocket connection was opened
179
+ * @name WebSocketShard#connectedAt
180
+ * @type {number}
181
+ * @private
182
+ */
183
+ Object.defineProperty(this, 'connectedAt', { value: 0, writable: true });
184
+ }
185
+
186
+ /**
187
+ * Emits a debug event.
188
+ * @param {string} message The debug message
189
+ * @private
190
+ */
191
+ debug(message) {
192
+ this.manager.debug(message, this);
193
+ }
194
+
195
+ /**
196
+ * Connects the shard to the gateway.
197
+ * @private
198
+ * @returns {Promise<void>} A promise that will resolve if the shard turns ready successfully,
199
+ * or reject if we couldn't connect
200
+ */
201
+ connect() {
202
+ const { client } = this.manager;
203
+
204
+ if (this.connection?.readyState === WebSocket.OPEN && this.status === Status.READY) {
205
+ return Promise.resolve();
206
+ }
207
+
208
+ const gateway = this.resumeURL ?? this.manager.gateway;
209
+
210
+ return new Promise((resolve, reject) => {
211
+ const cleanup = () => {
212
+ this.removeListener(ShardEvents.CLOSE, onClose);
213
+ this.removeListener(ShardEvents.READY, onReady);
214
+ this.removeListener(ShardEvents.RESUMED, onResumed);
215
+ this.removeListener(ShardEvents.INVALID_SESSION, onInvalidOrDestroyed);
216
+ this.removeListener(ShardEvents.DESTROYED, onInvalidOrDestroyed);
217
+ };
218
+
219
+ const onReady = () => {
220
+ cleanup();
221
+ resolve();
222
+ };
223
+
224
+ const onResumed = () => {
225
+ cleanup();
226
+ resolve();
227
+ };
228
+
229
+ const onClose = event => {
230
+ cleanup();
231
+ reject(event);
232
+ };
233
+
234
+ const onInvalidOrDestroyed = () => {
235
+ cleanup();
236
+ // eslint-disable-next-line prefer-promise-reject-errors
237
+ reject();
238
+ };
239
+
240
+ this.once(ShardEvents.READY, onReady);
241
+ this.once(ShardEvents.RESUMED, onResumed);
242
+ this.once(ShardEvents.CLOSE, onClose);
243
+ this.once(ShardEvents.INVALID_SESSION, onInvalidOrDestroyed);
244
+ this.once(ShardEvents.DESTROYED, onInvalidOrDestroyed);
245
+
246
+ if (this.connection?.readyState === WebSocket.OPEN) {
247
+ this.debug('An open connection was found, attempting an immediate identify.');
248
+ this.identify();
249
+ return;
250
+ }
251
+
252
+ if (this.connection) {
253
+ this.debug(`A connection object was found. Cleaning up before continuing.
254
+ State: ${CONNECTION_STATE[this.connection.readyState]}`);
255
+ this.destroy({ emit: false });
256
+ }
257
+
258
+ const wsQuery = { v: client.options.ws.version };
259
+
260
+ if (zlib) {
261
+ this.inflate = new zlib.Inflate({
262
+ chunkSize: 65535,
263
+ flush: zlib.Z_SYNC_FLUSH,
264
+ to: WebSocket.encoding === 'json' ? 'string' : '',
265
+ });
266
+ wsQuery.compress = 'zlib-stream';
267
+ }
268
+
269
+ this.debug(
270
+ `[CONNECT]
271
+ Gateway : ${gateway}
272
+ Version : ${client.options.ws.version}
273
+ Encoding : ${WebSocket.encoding}
274
+ Compression: ${zlib ? 'zlib-stream' : 'none'}
275
+ Agent : ${Util.verifyProxyAgent(client.options.ws.agent)}`,
276
+ );
277
+
278
+ this.status = this.status === Status.DISCONNECTED ? Status.RECONNECTING : Status.CONNECTING;
279
+ this.setHelloTimeout();
280
+ this.setWsCloseTimeout(-1);
281
+ this.connectedAt = Date.now();
282
+
283
+ // Adding a handshake timeout to just make sure no zombie connection appears.
284
+ const ws = (this.connection = WebSocket.create(gateway, wsQuery, {
285
+ handshakeTimeout: 30_000,
286
+ agent: Util.verifyProxyAgent(client.options.ws.agent) ? client.options.ws.agent : undefined,
287
+ }));
288
+ ws.onopen = this.onOpen.bind(this);
289
+ ws.onmessage = this.onMessage.bind(this);
290
+ ws.onerror = this.onError.bind(this);
291
+ ws.onclose = this.onClose.bind(this);
292
+ });
293
+ }
294
+
295
+ /**
296
+ * Called whenever a connection is opened to the gateway.
297
+ * @private
298
+ */
299
+ onOpen() {
300
+ this.debug(`[CONNECTED] Took ${Date.now() - this.connectedAt}ms`);
301
+ this.status = Status.NEARLY;
302
+ }
303
+
304
+ /**
305
+ * Called whenever a message is received.
306
+ * @param {MessageEvent} event Event received
307
+ * @private
308
+ */
309
+ onMessage({ data }) {
310
+ let raw;
311
+ if (data instanceof ArrayBuffer) data = new Uint8Array(data);
312
+ if (zlib) {
313
+ const l = data.length;
314
+ const flush =
315
+ l >= 4 && data[l - 4] === 0x00 && data[l - 3] === 0x00 && data[l - 2] === 0xff && data[l - 1] === 0xff;
316
+
317
+ this.inflate.push(data, flush && zlib.Z_SYNC_FLUSH);
318
+ if (!flush) return;
319
+ raw = this.inflate.result;
320
+ } else {
321
+ raw = data;
322
+ }
323
+ let packet;
324
+ try {
325
+ packet = WebSocket.unpack(raw);
326
+ } catch (err) {
327
+ this.manager.client.emit(Events.SHARD_ERROR, err, this.id);
328
+ return;
329
+ }
330
+ this.manager.client.emit(Events.RAW, packet, this.id);
331
+ if (packet.op === Opcodes.DISPATCH) this.manager.emit(packet.t, packet.d, this.id);
332
+ this.onPacket(packet);
333
+ }
334
+
335
+ /**
336
+ * Called whenever an error occurs with the WebSocket.
337
+ * @param {ErrorEvent} event The error that occurred
338
+ * @private
339
+ */
340
+ onError(event) {
341
+ const error = event?.error ?? event;
342
+ if (!error) return;
343
+
344
+ /**
345
+ * Emitted whenever a shard's WebSocket encounters a connection error.
346
+ * @event Client#shardError
347
+ * @param {Error} error The encountered error
348
+ * @param {number} shardId The shard that encountered this error
349
+ */
350
+ this.manager.client.emit(Events.SHARD_ERROR, error, this.id);
351
+ }
352
+
353
+ /**
354
+ * @external CloseEvent
355
+ * @see {@link https://developer.mozilla.org/docs/Web/API/CloseEvent}
356
+ */
357
+
358
+ /**
359
+ * @external ErrorEvent
360
+ * @see {@link https://developer.mozilla.org/docs/Web/API/ErrorEvent}
361
+ */
362
+
363
+ /**
364
+ * @external MessageEvent
365
+ * @see {@link https://developer.mozilla.org/docs/Web/API/MessageEvent}
366
+ */
367
+
368
+ /**
369
+ * Called whenever a connection to the gateway is closed.
370
+ * @param {CloseEvent} event Close event that was received
371
+ * @private
372
+ */
373
+ onClose(event) {
374
+ this.closeEmitted = true;
375
+ if (this.sequence !== -1) this.closeSequence = this.sequence;
376
+ this.sequence = -1;
377
+ this.setHeartbeatTimer(-1);
378
+ this.setHelloTimeout(-1);
379
+ // Clearing the WebSocket close timeout as close was emitted.
380
+ this.setWsCloseTimeout(-1);
381
+ // If we still have a connection object, clean up its listeners
382
+ if (this.connection) {
383
+ this._cleanupConnection();
384
+ // Having this after _cleanupConnection to just clean up the connection and not listen to ws.onclose
385
+ this.destroy({ reset: !this.sessionId, emit: false, log: false });
386
+ }
387
+ this.status = Status.DISCONNECTED;
388
+ this.emitClose(event);
389
+ }
390
+
391
+ /**
392
+ * This method is responsible to emit close event for this shard.
393
+ * This method helps the shard reconnect.
394
+ * @param {CloseEvent} [event] Close event that was received
395
+ */
396
+ emitClose(
397
+ event = {
398
+ code: 1011,
399
+ reason: WSCodes[1011],
400
+ wasClean: false,
401
+ },
402
+ ) {
403
+ this.debug(`[CLOSE]
404
+ Event Code: ${event.code}
405
+ Clean : ${event.wasClean}
406
+ Reason : ${event.reason ?? 'No reason received'}`);
407
+ /**
408
+ * Emitted when a shard's WebSocket closes.
409
+ * @private
410
+ * @event WebSocketShard#close
411
+ * @param {CloseEvent} event The received event
412
+ */
413
+ this.emit(ShardEvents.CLOSE, event);
414
+ }
415
+
416
+ /**
417
+ * Called whenever a packet is received.
418
+ * @param {Object} packet The received packet
419
+ * @private
420
+ */
421
+ onPacket(packet) {
422
+ if (!packet) {
423
+ this.debug(`Received broken packet: '${packet}'.`);
424
+ return;
425
+ }
426
+
427
+ switch (packet.t) {
428
+ case WSEvents.READY:
429
+ /**
430
+ * Emitted when the shard receives the READY payload and is now waiting for guilds
431
+ * @event WebSocketShard#ready
432
+ */
433
+ this.emit(ShardEvents.READY);
434
+
435
+ this.resumeURL = packet.d.resume_gateway_url;
436
+ this.sessionId = packet.d.session_id;
437
+ this.expectedGuilds = new Set(packet.d.guilds.filter(d => d?.unavailable == true).map(d => d.id));
438
+ this.status = Status.WAITING_FOR_GUILDS;
439
+ this.debug(`[READY] Session ${this.sessionId} | Resume url ${this.resumeURL}.`);
440
+ this.lastHeartbeatAcked = true;
441
+ this.sendHeartbeat('ReadyHeartbeat');
442
+ break;
443
+ case WSEvents.RESUMED: {
444
+ /**
445
+ * Emitted when the shard resumes successfully
446
+ * @event WebSocketShard#resumed
447
+ */
448
+ this.emit(ShardEvents.RESUMED);
449
+
450
+ this.status = Status.READY;
451
+ const replayed = packet.s - this.closeSequence;
452
+ this.debug(`[RESUMED] Session ${this.sessionId} | Replayed ${replayed} events.`);
453
+ this.lastHeartbeatAcked = true;
454
+ this.sendHeartbeat('ResumeHeartbeat');
455
+ break;
456
+ }
457
+ }
458
+
459
+ if (packet.s > this.sequence) this.sequence = packet.s;
460
+
461
+ switch (packet.op) {
462
+ case Opcodes.HELLO:
463
+ this.setHelloTimeout(-1);
464
+ this.setHeartbeatTimer(packet.d.heartbeat_interval);
465
+ this.identify();
466
+ break;
467
+ case Opcodes.RECONNECT:
468
+ this.debug('[RECONNECT] Discord asked us to reconnect');
469
+ this.destroy({ closeCode: 4_000 });
470
+ break;
471
+ case Opcodes.INVALID_SESSION:
472
+ this.debug(`[INVALID SESSION] Resumable: ${packet.d}.`);
473
+ // If we can resume the session, do so immediately
474
+ if (packet.d) {
475
+ this.identifyResume();
476
+ return;
477
+ }
478
+ // Reset the sequence
479
+ this.sequence = -1;
480
+ // Reset the session id as it's invalid
481
+ this.sessionId = null;
482
+ // Set the status to reconnecting
483
+ this.status = Status.RECONNECTING;
484
+ // Finally, emit the INVALID_SESSION event
485
+ /**
486
+ * Emitted when the session has been invalidated.
487
+ * @event WebSocketShard#invalidSession
488
+ */
489
+ this.emit(ShardEvents.INVALID_SESSION);
490
+ break;
491
+ case Opcodes.HEARTBEAT_ACK:
492
+ this.ackHeartbeat();
493
+ break;
494
+ case Opcodes.HEARTBEAT:
495
+ this.sendHeartbeat('HeartbeatRequest', true);
496
+ break;
497
+ default:
498
+ this.manager.handlePacket(packet, this);
499
+ if (this.status === Status.WAITING_FOR_GUILDS && packet.t === WSEvents.GUILD_CREATE) {
500
+ this.expectedGuilds.delete(packet.d.id);
501
+ this.checkReady();
502
+ }
503
+ }
504
+ }
505
+
506
+ /**
507
+ * Checks if the shard can be marked as ready
508
+ * @private
509
+ */
510
+ checkReady() {
511
+ // Step 0. Clear the ready timeout, if it exists
512
+ if (this.readyTimeout) {
513
+ clearTimeout(this.readyTimeout);
514
+ this.readyTimeout = null;
515
+ }
516
+ // Step 1. If we don't have any other guilds pending, we are ready
517
+ if (!this.expectedGuilds.size) {
518
+ this.debug('Shard received all its guilds. Marking as fully ready.');
519
+ this.status = Status.READY;
520
+
521
+ /**
522
+ * Emitted when the shard is fully ready.
523
+ * This event is emitted if:
524
+ * * all guilds were received by this shard
525
+ * * the ready timeout expired, and some guilds are unavailable
526
+ * @event WebSocketShard#allReady
527
+ * @param {?Set<string>} unavailableGuilds Set of unavailable guilds, if any
528
+ */
529
+ this.emit(ShardEvents.ALL_READY);
530
+ return;
531
+ }
532
+ const hasGuildsIntent = new Intents(this.manager.client.options.intents).has(Intents.FLAGS.GUILDS);
533
+ // Step 2. Create a timeout that will mark the shard as ready if there are still unavailable guilds
534
+ // * The timeout is 15 seconds by default
535
+ // * This can be optionally changed in the client options via the `waitGuildTimeout` option
536
+ // * a timeout time of zero will skip this timeout, which potentially could cause the Client to miss guilds.
537
+
538
+ const { waitGuildTimeout } = this.manager.client.options;
539
+
540
+ this.readyTimeout = setTimeout(
541
+ () => {
542
+ this.debug(
543
+ `Shard ${hasGuildsIntent ? 'did' : 'will'} not receive any more guild packets` +
544
+ `${hasGuildsIntent ? ` in ${waitGuildTimeout} ms` : ''}.\nUnavailable guild count: ${
545
+ this.expectedGuilds.size
546
+ }`,
547
+ );
548
+
549
+ this.readyTimeout = null;
550
+
551
+ this.status = Status.READY;
552
+
553
+ this.emit(ShardEvents.ALL_READY, this.expectedGuilds);
554
+ },
555
+ hasGuildsIntent ? waitGuildTimeout : 0,
556
+ ).unref();
557
+ }
558
+
559
+ /**
560
+ * Sets the HELLO packet timeout.
561
+ * @param {number} [time] If set to -1, it will clear the hello timeout
562
+ * @private
563
+ */
564
+ setHelloTimeout(time) {
565
+ if (time === -1) {
566
+ if (this.helloTimeout) {
567
+ this.debug('Clearing the HELLO timeout.');
568
+ clearTimeout(this.helloTimeout);
569
+ this.helloTimeout = null;
570
+ }
571
+ return;
572
+ }
573
+ this.debug('Setting a HELLO timeout for 20s.');
574
+ this.helloTimeout = setTimeout(() => {
575
+ this.debug('Did not receive HELLO in time. Destroying and connecting again.');
576
+ this.destroy({ reset: true, closeCode: 4009 });
577
+ }, 20_000).unref();
578
+ }
579
+
580
+ /**
581
+ * Sets the WebSocket Close timeout.
582
+ * This method is responsible for detecting any zombie connections if the WebSocket fails to close properly.
583
+ * @param {number} [time] If set to -1, it will clear the timeout
584
+ * @private
585
+ */
586
+ setWsCloseTimeout(time) {
587
+ if (this.wsCloseTimeout) {
588
+ this.debug('[WebSocket] Clearing the close timeout.');
589
+ clearTimeout(this.wsCloseTimeout);
590
+ }
591
+ if (time === -1) {
592
+ this.wsCloseTimeout = null;
593
+ return;
594
+ }
595
+ this.wsCloseTimeout = setTimeout(() => {
596
+ this.setWsCloseTimeout(-1);
597
+
598
+ // Check if close event was emitted.
599
+ if (this.closeEmitted) {
600
+ this.debug(`[WebSocket] close was already emitted, assuming the connection was closed properly.`);
601
+ // Setting the variable false to check for zombie connections.
602
+ this.closeEmitted = false;
603
+ return;
604
+ }
605
+
606
+ this.debug(
607
+ // eslint-disable-next-line max-len
608
+ `[WebSocket] Close Emitted: ${this.closeEmitted} | did not close properly, assuming a zombie connection.\nEmitting close and reconnecting again.`,
609
+ );
610
+
611
+ if (this.connection) this._cleanupConnection();
612
+
613
+ this.emitClose({
614
+ code: 4009,
615
+ reason: 'Session time out.',
616
+ wasClean: false,
617
+ });
618
+ }, time);
619
+ }
620
+
621
+ /**
622
+ * Sets the heartbeat timer for this shard.
623
+ * @param {number} time If -1, clears the interval, any other number sets an interval
624
+ * @private
625
+ */
626
+ setHeartbeatTimer(time) {
627
+ if (time === -1) {
628
+ if (this.heartbeatInterval) {
629
+ this.debug('Clearing the heartbeat interval.');
630
+ clearInterval(this.heartbeatInterval);
631
+ this.heartbeatInterval = null;
632
+ }
633
+ return;
634
+ }
635
+ this.debug(`Setting a heartbeat interval for ${time}ms.`);
636
+ // Sanity checks
637
+ if (this.heartbeatInterval) clearInterval(this.heartbeatInterval);
638
+ this.heartbeatInterval = setInterval(() => this.sendHeartbeat(), time).unref();
639
+ }
640
+
641
+ /**
642
+ * Sends a heartbeat to the WebSocket.
643
+ * If this shard didn't receive a heartbeat last time, it will destroy it and reconnect
644
+ * @param {string} [tag='HeartbeatTimer'] What caused this heartbeat to be sent
645
+ * @param {boolean} [ignoreHeartbeatAck] If we should send the heartbeat forcefully.
646
+ * @private
647
+ */
648
+ sendHeartbeat(
649
+ tag = 'HeartbeatTimer',
650
+ ignoreHeartbeatAck = [Status.WAITING_FOR_GUILDS, Status.IDENTIFYING, Status.RESUMING].includes(this.status),
651
+ ) {
652
+ if (ignoreHeartbeatAck && !this.lastHeartbeatAcked) {
653
+ this.debug(`[${tag}] Didn't process heartbeat ack yet but we are still connected. Sending one now.`);
654
+ } else if (!this.lastHeartbeatAcked) {
655
+ this.debug(
656
+ `[${tag}] Didn't receive a heartbeat ack last time, assuming zombie connection. Destroying and reconnecting.
657
+ Status : ${STATUS_KEYS[this.status]}
658
+ Sequence : ${this.sequence}
659
+ Connection State: ${this.connection ? CONNECTION_STATE[this.connection.readyState] : 'No Connection??'}`,
660
+ );
661
+ this.destroy({ reset: true, closeCode: 4009 });
662
+ return;
663
+ }
664
+
665
+ this.debug(`[${tag}] Sending a heartbeat.`);
666
+ this.lastHeartbeatAcked = false;
667
+ this.lastPingTimestamp = Date.now();
668
+ this.send({ op: Opcodes.HEARTBEAT, d: this.sequence }, true);
669
+ }
670
+
671
+ /**
672
+ * Acknowledges a heartbeat.
673
+ * @private
674
+ */
675
+ ackHeartbeat() {
676
+ this.lastHeartbeatAcked = true;
677
+ const latency = Date.now() - this.lastPingTimestamp;
678
+ this.debug(`Heartbeat acknowledged, latency of ${latency}ms.`);
679
+ this.ping = latency;
680
+ }
681
+
682
+ /**
683
+ * Identifies the client on the connection.
684
+ * @private
685
+ * @returns {void}
686
+ */
687
+ identify() {
688
+ return this.sessionId ? this.identifyResume() : this.identifyNew();
689
+ }
690
+
691
+ /**
692
+ * Identifies as a new connection on the gateway.
693
+ * @private
694
+ */
695
+ identifyNew() {
696
+ const { client } = this.manager;
697
+ if (!client.token) {
698
+ this.debug('[IDENTIFY] No token available to identify a new session.');
699
+ return;
700
+ }
701
+
702
+ this.status = Status.IDENTIFYING;
703
+
704
+ // Patch something
705
+ Object.keys(client.options.ws.properties)
706
+ .filter(k => k.startsWith('$'))
707
+ .forEach(k => {
708
+ client.options.ws.properties[k.slice(1)] = client.options.ws.properties[k];
709
+ delete client.options.ws.properties[k];
710
+ });
711
+
712
+ // Clone the identify payload and assign the token and shard info
713
+ const d = {
714
+ ...client.options.ws,
715
+ token: client.token,
716
+ };
717
+
718
+ delete d.version;
719
+ delete d.agent;
720
+
721
+ this.debug(`[IDENTIFY] Shard ${this.id}`);
722
+ this.send({ op: Opcodes.IDENTIFY, d }, true);
723
+ }
724
+
725
+ /**
726
+ * Resumes a session on the gateway.
727
+ * @private
728
+ */
729
+ identifyResume() {
730
+ if (!this.sessionId) {
731
+ this.debug('[RESUME] No session id was present; identifying as a new session.');
732
+ this.identifyNew();
733
+ return;
734
+ }
735
+
736
+ this.status = Status.RESUMING;
737
+
738
+ this.debug(`[RESUME] Session ${this.sessionId}, sequence ${this.closeSequence}`);
739
+
740
+ const d = {
741
+ token: this.manager.client.token,
742
+ session_id: this.sessionId,
743
+ seq: this.closeSequence,
744
+ };
745
+
746
+ this.send({ op: Opcodes.RESUME, d }, true);
747
+ }
748
+
749
+ /**
750
+ * Adds a packet to the queue to be sent to the gateway.
751
+ * <warn>If you use this method, make sure you understand that you need to provide
752
+ * a full [Payload](https://discord.com/developers/docs/topics/gateway-events#payload-structure).
753
+ * Do not use this method if you don't know what you're doing.</warn>
754
+ * @param {Object} data The full packet to send
755
+ * @param {boolean} [important=false] If this packet should be added first in queue
756
+ */
757
+ send(data, important = false) {
758
+ this.ratelimit.queue[important ? 'unshift' : 'push'](data);
759
+ this.processQueue();
760
+ }
761
+
762
+ /**
763
+ * Sends data, bypassing the queue.
764
+ * @param {Object} data Packet to send
765
+ * @returns {void}
766
+ * @private
767
+ */
768
+ _send(data) {
769
+ if (this.connection?.readyState !== WebSocket.OPEN) {
770
+ this.debug(`Tried to send packet '${JSON.stringify(data)}' but no WebSocket is available!`);
771
+ this.destroy({ closeCode: 4_000 });
772
+ return;
773
+ }
774
+
775
+ this.debug(`[WebSocketShard] send packet '${JSON.stringify(data)}'`);
776
+ this.connection.send(WebSocket.pack(data), err => {
777
+ if (err) this.manager.client.emit(Events.SHARD_ERROR, err, this.id);
778
+ });
779
+ }
780
+
781
+ /**
782
+ * Processes the current WebSocket queue.
783
+ * @returns {void}
784
+ * @private
785
+ */
786
+ processQueue() {
787
+ if (this.ratelimit.remaining === 0) return;
788
+ if (this.ratelimit.queue.length === 0) return;
789
+ if (this.ratelimit.remaining === this.ratelimit.total) {
790
+ this.ratelimit.timer = setTimeout(() => {
791
+ this.ratelimit.remaining = this.ratelimit.total;
792
+ this.processQueue();
793
+ }, this.ratelimit.time).unref();
794
+ }
795
+ while (this.ratelimit.remaining > 0) {
796
+ const item = this.ratelimit.queue.shift();
797
+ if (!item) return;
798
+ this._send(item);
799
+ this.ratelimit.remaining--;
800
+ }
801
+ }
802
+
803
+ /**
804
+ * Destroys this shard and closes its WebSocket connection.
805
+ * @param {Object} [options={ closeCode: 1000, reset: false, emit: true, log: true }] Options for destroying the shard
806
+ * @private
807
+ */
808
+ destroy({ closeCode = 1_000, reset = false, emit = true, log = true } = {}) {
809
+ if (log) {
810
+ this.debug(`[DESTROY]
811
+ Close Code : ${closeCode}
812
+ Reset : ${reset}
813
+ Emit DESTROYED: ${emit}`);
814
+ }
815
+
816
+ // Step 0: Remove all timers
817
+ this.setHeartbeatTimer(-1);
818
+ this.setHelloTimeout(-1);
819
+
820
+ this.debug(
821
+ `[WebSocket] Destroy: Attempting to close the WebSocket. | WS State: ${
822
+ CONNECTION_STATE[this.connection?.readyState ?? WebSocket.CLOSED]
823
+ }`,
824
+ );
825
+ // Step 1: Close the WebSocket connection, if any, otherwise, emit DESTROYED
826
+ if (this.connection) {
827
+ // If the connection is currently opened, we will (hopefully) receive close
828
+ if (this.connection.readyState === WebSocket.OPEN) {
829
+ this.connection.close(closeCode);
830
+ this.debug(`[WebSocket] Close: Tried closing. | WS State: ${CONNECTION_STATE[this.connection.readyState]}`);
831
+ } else {
832
+ // Connection is not OPEN
833
+ this.debug(`WS State: ${CONNECTION_STATE[this.connection.readyState]}`);
834
+ // Attempt to close the connection just in case
835
+ try {
836
+ this.connection.close(closeCode);
837
+ } catch (err) {
838
+ this.debug(
839
+ `[WebSocket] Close: Something went wrong while closing the WebSocket: ${
840
+ err.message || err
841
+ }. Forcefully terminating the connection | WS State: ${CONNECTION_STATE[this.connection.readyState]}`,
842
+ );
843
+ this.connection.terminate();
844
+ }
845
+ // Emit the destroyed event if needed
846
+ if (emit) this._emitDestroyed();
847
+ }
848
+ } else if (emit) {
849
+ // We requested a destroy, but we had no connection. Emit destroyed
850
+ this._emitDestroyed();
851
+ }
852
+
853
+ this.debug(
854
+ `[WebSocket] Adding a WebSocket close timeout to ensure a correct WS reconnect.
855
+ Timeout: ${this.manager.client.options.closeTimeout}ms`,
856
+ );
857
+ this.setWsCloseTimeout(this.manager.client.options.closeTimeout);
858
+
859
+ // Step 2: Null the connection object
860
+ this.connection = null;
861
+
862
+ // Step 3: Set the shard status to DISCONNECTED
863
+ this.status = Status.DISCONNECTED;
864
+
865
+ // Step 4: Cache the old sequence (use to attempt a resume)
866
+ if (this.sequence !== -1) this.closeSequence = this.sequence;
867
+
868
+ // Step 5: Reset the sequence, resume URL and session id if requested
869
+ if (reset) {
870
+ this.resumeURL = null;
871
+ this.sequence = -1;
872
+ this.sessionId = null;
873
+ }
874
+
875
+ // Step 6: reset the rate limit data
876
+ this.ratelimit.remaining = this.ratelimit.total;
877
+ this.ratelimit.queue.length = 0;
878
+ if (this.ratelimit.timer) {
879
+ clearTimeout(this.ratelimit.timer);
880
+ this.ratelimit.timer = null;
881
+ }
882
+ }
883
+
884
+ /**
885
+ * Cleans up the WebSocket connection listeners.
886
+ * @private
887
+ */
888
+ _cleanupConnection() {
889
+ this.connection.onopen = this.connection.onclose = this.connection.onmessage = null;
890
+ this.connection.onerror = () => null;
891
+ }
892
+
893
+ /**
894
+ * Emits the DESTROYED event on the shard
895
+ * @private
896
+ */
897
+ _emitDestroyed() {
898
+ /**
899
+ * Emitted when a shard is destroyed, but no WebSocket connection was present.
900
+ * @private
901
+ * @event WebSocketShard#destroyed
902
+ */
903
+ this.emit(ShardEvents.DESTROYED);
904
+ }
905
+ }
906
+
907
+ module.exports = WebSocketShard;