onyxcord 2.1.0 → 3.2.0

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 (270) hide show
  1. checksums.yaml +4 -4
  2. data/lib/onyxcord/application_commands/command.rb +4 -97
  3. data/lib/onyxcord/application_commands/context.rb +4 -94
  4. data/lib/onyxcord/application_commands/option.rb +4 -75
  5. data/lib/onyxcord/application_commands/registry.rb +4 -45
  6. data/lib/onyxcord/application_commands.rb +8 -5
  7. data/lib/onyxcord/bot.rb +24 -1759
  8. data/lib/onyxcord/{cache.rb → cache/manager.rb} +16 -14
  9. data/lib/onyxcord/cache/stores/gateway.rb +272 -0
  10. data/lib/onyxcord/commands/bot/channels.rb +48 -0
  11. data/lib/onyxcord/commands/bot/execution.rb +174 -0
  12. data/lib/onyxcord/commands/bot/message_dispatch.rb +58 -0
  13. data/lib/onyxcord/commands/bot/permissions.rb +72 -0
  14. data/lib/onyxcord/commands/bot.rb +209 -0
  15. data/lib/onyxcord/commands/container.rb +21 -2
  16. data/lib/onyxcord/commands/parser.rb +30 -1
  17. data/lib/onyxcord/container.rb +16 -960
  18. data/lib/onyxcord/core/bot/application_commands.rb +210 -0
  19. data/lib/onyxcord/core/bot/awaits.rb +112 -0
  20. data/lib/onyxcord/core/bot/invites.rb +42 -0
  21. data/lib/onyxcord/core/bot/messaging.rb +161 -0
  22. data/lib/onyxcord/core/bot/oauth.rb +27 -0
  23. data/lib/onyxcord/core/bot/presence.rb +103 -0
  24. data/lib/onyxcord/core/bot/runtime.rb +183 -0
  25. data/lib/onyxcord/core/bot/voice.rb +69 -0
  26. data/lib/onyxcord/{logger.rb → core/logger.rb} +5 -0
  27. data/lib/onyxcord/core/version.rb +5 -0
  28. data/lib/onyxcord/events/await/base.rb +48 -0
  29. data/lib/onyxcord/events/await.rb +1 -46
  30. data/lib/onyxcord/events/ban/base.rb +54 -0
  31. data/lib/onyxcord/events/ban/remove.rb +11 -0
  32. data/lib/onyxcord/events/bans.rb +2 -58
  33. data/lib/onyxcord/events/channels/base.rb +91 -0
  34. data/lib/onyxcord/events/channels/delete.rb +73 -0
  35. data/lib/onyxcord/events/channels/pins.rb +44 -0
  36. data/lib/onyxcord/events/channels/recipients.rb +77 -0
  37. data/lib/onyxcord/events/channels.rb +4 -239
  38. data/lib/onyxcord/events/guilds/audit_logs.rb +79 -0
  39. data/lib/onyxcord/events/guilds/base.rb +97 -0
  40. data/lib/onyxcord/events/guilds/emoji.rb +147 -0
  41. data/lib/onyxcord/events/guilds.rb +3 -267
  42. data/lib/onyxcord/events/handlers/channels.rb +133 -0
  43. data/lib/onyxcord/events/handlers/core.rb +146 -0
  44. data/lib/onyxcord/events/handlers/guilds.rb +375 -0
  45. data/lib/onyxcord/events/handlers/interactions.rb +238 -0
  46. data/lib/onyxcord/events/handlers/lifetime.rb +71 -0
  47. data/lib/onyxcord/events/handlers/messages.rb +218 -0
  48. data/lib/onyxcord/events/handlers/presence.rb +56 -0
  49. data/lib/onyxcord/events/handlers/raw.rb +39 -0
  50. data/lib/onyxcord/events/handlers/reactions.rb +98 -0
  51. data/lib/onyxcord/events/handlers/scheduled_events.rb +243 -0
  52. data/lib/onyxcord/events/handlers/voice.rb +46 -0
  53. data/lib/onyxcord/events/integration/base.rb +45 -0
  54. data/lib/onyxcord/events/integration/create.rb +11 -0
  55. data/lib/onyxcord/events/integration/delete.rb +48 -0
  56. data/lib/onyxcord/events/integration/update.rb +11 -0
  57. data/lib/onyxcord/events/integrations.rb +4 -98
  58. data/lib/onyxcord/events/interactions/application_commands.rb +185 -0
  59. data/lib/onyxcord/events/interactions/autocomplete.rb +62 -0
  60. data/lib/onyxcord/events/interactions/base.rb +175 -0
  61. data/lib/onyxcord/events/interactions/components.rb +221 -0
  62. data/lib/onyxcord/events/interactions/permissions.rb +53 -0
  63. data/lib/onyxcord/events/interactions.rb +5 -622
  64. data/lib/onyxcord/events/invite/create.rb +79 -0
  65. data/lib/onyxcord/events/invite/delete.rb +55 -0
  66. data/lib/onyxcord/events/invites.rb +2 -125
  67. data/lib/onyxcord/events/lifetime/base.rb +31 -0
  68. data/lib/onyxcord/events/lifetime.rb +1 -29
  69. data/lib/onyxcord/events/member/add.rb +12 -0
  70. data/lib/onyxcord/events/member/base.rb +67 -0
  71. data/lib/onyxcord/events/member/delete.rb +21 -0
  72. data/lib/onyxcord/events/member/update.rb +25 -0
  73. data/lib/onyxcord/events/members.rb +4 -108
  74. data/lib/onyxcord/events/message/base.rb +295 -0
  75. data/lib/onyxcord/events/message/ids.rb +71 -0
  76. data/lib/onyxcord/events/message/mentions.rb +55 -0
  77. data/lib/onyxcord/events/message/updates.rb +27 -0
  78. data/lib/onyxcord/events/message.rb +4 -397
  79. data/lib/onyxcord/events/poll/add.rb +11 -0
  80. data/lib/onyxcord/events/poll/base.rb +106 -0
  81. data/lib/onyxcord/events/poll/remove.rb +11 -0
  82. data/lib/onyxcord/events/polls.rb +3 -116
  83. data/lib/onyxcord/events/presence/playing.rb +77 -0
  84. data/lib/onyxcord/events/presence/status.rb +60 -0
  85. data/lib/onyxcord/events/presence.rb +2 -129
  86. data/lib/onyxcord/events/raw/base.rb +74 -0
  87. data/lib/onyxcord/events/raw.rb +1 -72
  88. data/lib/onyxcord/events/reactions/base.rb +143 -0
  89. data/lib/onyxcord/events/reactions/clear.rb +99 -0
  90. data/lib/onyxcord/events/reactions.rb +2 -216
  91. data/lib/onyxcord/events/role/create.rb +46 -0
  92. data/lib/onyxcord/events/role/delete.rb +40 -0
  93. data/lib/onyxcord/events/role/update.rb +11 -0
  94. data/lib/onyxcord/events/roles.rb +3 -85
  95. data/lib/onyxcord/events/scheduled_events/events.rb +112 -0
  96. data/lib/onyxcord/events/scheduled_events/users.rb +91 -0
  97. data/lib/onyxcord/events/scheduled_events.rb +2 -169
  98. data/lib/onyxcord/events/thread/base.rb +49 -0
  99. data/lib/onyxcord/events/thread/members.rb +44 -0
  100. data/lib/onyxcord/events/thread/update.rb +18 -0
  101. data/lib/onyxcord/events/threads.rb +3 -98
  102. data/lib/onyxcord/events/typing/base.rb +73 -0
  103. data/lib/onyxcord/events/typing.rb +1 -71
  104. data/lib/onyxcord/events/voice/server_update.rb +48 -0
  105. data/lib/onyxcord/events/voice/state_update.rb +137 -0
  106. data/lib/onyxcord/events/voice_server_update.rb +2 -46
  107. data/lib/onyxcord/events/voice_state_update.rb +2 -135
  108. data/lib/onyxcord/events/webhook/update.rb +65 -0
  109. data/lib/onyxcord/events/webhooks.rb +1 -63
  110. data/lib/onyxcord/gateway/client.rb +460 -0
  111. data/lib/onyxcord/interactions/command.rb +120 -0
  112. data/lib/onyxcord/interactions/context.rb +109 -0
  113. data/lib/onyxcord/interactions/internal/application_command.rb +203 -0
  114. data/lib/onyxcord/interactions/internal/message.rb +152 -0
  115. data/lib/onyxcord/interactions/internal/metadata.rb +109 -0
  116. data/lib/onyxcord/interactions/internal/option_builder.rb +230 -0
  117. data/lib/onyxcord/interactions/internal/permission_builder.rb +90 -0
  118. data/lib/onyxcord/interactions/option.rb +88 -0
  119. data/lib/onyxcord/interactions/registry.rb +50 -0
  120. data/lib/onyxcord/interactions.rb +16 -0
  121. data/lib/onyxcord/internal/async_runtime.rb +32 -0
  122. data/lib/onyxcord/internal/event_bus.rb +591 -0
  123. data/lib/onyxcord/internal/event_executor.rb +155 -0
  124. data/lib/onyxcord/internal/gateway/opcodes.rb +22 -0
  125. data/lib/onyxcord/internal/gateway/session.rb +45 -0
  126. data/lib/onyxcord/internal/http.rb +189 -0
  127. data/lib/onyxcord/internal/json.rb +51 -0
  128. data/lib/onyxcord/{message_payload.rb → internal/message_payload.rb} +3 -3
  129. data/lib/onyxcord/internal/rate_limiter/async_rest.rb +151 -0
  130. data/lib/onyxcord/internal/rate_limiter/gateway.rb +44 -0
  131. data/lib/onyxcord/internal/rate_limiter/rest.rb +144 -0
  132. data/lib/onyxcord/internal/websocket.rb +79 -0
  133. data/lib/onyxcord/light/data.rb +1 -1
  134. data/lib/onyxcord/light/integrations.rb +1 -1
  135. data/lib/onyxcord/light/light_bot.rb +7 -7
  136. data/lib/onyxcord/{data → models}/activity.rb +2 -2
  137. data/lib/onyxcord/{data → models}/application.rb +3 -3
  138. data/lib/onyxcord/{data → models}/avatar_decoration.rb +1 -1
  139. data/lib/onyxcord/{data → models}/channel.rb +23 -23
  140. data/lib/onyxcord/{data → models}/collectibles.rb +1 -1
  141. data/lib/onyxcord/{data → models}/emoji.rb +1 -1
  142. data/lib/onyxcord/models/interaction.rb +450 -0
  143. data/lib/onyxcord/{data → models}/invite.rb +1 -1
  144. data/lib/onyxcord/{data → models}/member.rb +11 -11
  145. data/lib/onyxcord/{data → models}/message.rb +18 -14
  146. data/lib/onyxcord/{data → models}/poll.rb +2 -2
  147. data/lib/onyxcord/{data → models}/primary_server.rb +1 -1
  148. data/lib/onyxcord/{data → models}/profile.rb +4 -4
  149. data/lib/onyxcord/{data → models}/role.rb +6 -12
  150. data/lib/onyxcord/{data → models}/scheduled_event.rb +5 -5
  151. data/lib/onyxcord/{data → models}/server.rb +41 -41
  152. data/lib/onyxcord/{data → models}/server_preview.rb +2 -2
  153. data/lib/onyxcord/{data → models}/team.rb +1 -1
  154. data/lib/onyxcord/{data → models}/user.rb +5 -5
  155. data/lib/onyxcord/{data → models}/webhook.rb +10 -10
  156. data/lib/onyxcord/models.rb +57 -0
  157. data/lib/onyxcord/{api.rb → rest/client.rb} +23 -22
  158. data/lib/onyxcord/{api → rest/routes}/application.rb +79 -57
  159. data/lib/onyxcord/rest/routes/channel/base.rb +73 -0
  160. data/lib/onyxcord/rest/routes/channel/groups.rb +78 -0
  161. data/lib/onyxcord/rest/routes/channel/messages.rb +130 -0
  162. data/lib/onyxcord/rest/routes/channel/permissions.rb +73 -0
  163. data/lib/onyxcord/rest/routes/channel/pins.rb +45 -0
  164. data/lib/onyxcord/rest/routes/channel/polls.rb +32 -0
  165. data/lib/onyxcord/rest/routes/channel/reactions.rb +86 -0
  166. data/lib/onyxcord/rest/routes/channel/threads.rb +176 -0
  167. data/lib/onyxcord/rest/routes/channel/webhooks.rb +47 -0
  168. data/lib/onyxcord/rest/routes/channel.rb +12 -0
  169. data/lib/onyxcord/{api/interaction.rb → rest/routes/interaction/base.rb} +7 -25
  170. data/lib/onyxcord/rest/routes/interaction/response.rb +24 -0
  171. data/lib/onyxcord/rest/routes/interaction.rb +4 -0
  172. data/lib/onyxcord/{api → rest/routes}/invite.rb +7 -7
  173. data/lib/onyxcord/rest/routes/server/admin.rb +174 -0
  174. data/lib/onyxcord/rest/routes/server/assets.rb +25 -0
  175. data/lib/onyxcord/rest/routes/server/bans.rb +67 -0
  176. data/lib/onyxcord/rest/routes/server/base.rb +77 -0
  177. data/lib/onyxcord/rest/routes/server/channels.rb +58 -0
  178. data/lib/onyxcord/rest/routes/server/emoji.rb +48 -0
  179. data/lib/onyxcord/rest/routes/server/members.rb +109 -0
  180. data/lib/onyxcord/rest/routes/server/messages.rb +19 -0
  181. data/lib/onyxcord/rest/routes/server/roles.rb +156 -0
  182. data/lib/onyxcord/rest/routes/server/scheduled_events.rb +86 -0
  183. data/lib/onyxcord/rest/routes/server.rb +12 -0
  184. data/lib/onyxcord/{api → rest/routes}/user.rb +23 -23
  185. data/lib/onyxcord/{api → rest/routes}/webhook.rb +22 -22
  186. data/lib/onyxcord/{allowed_mentions.rb → utils/allowed_mentions.rb} +1 -1
  187. data/lib/onyxcord/utils/timestamp.rb +69 -0
  188. data/lib/onyxcord/voice/{voice_bot.rb → client.rb} +4 -4
  189. data/lib/onyxcord/voice/encoder.rb +1 -1
  190. data/lib/onyxcord/voice/network/udp.rb +135 -0
  191. data/lib/onyxcord/voice/network/websocket.rb +225 -0
  192. data/lib/onyxcord/voice/network.rb +5 -357
  193. data/lib/onyxcord/webhooks/builder.rb +1 -1
  194. data/lib/onyxcord/webhooks/client.rb +13 -13
  195. data/lib/onyxcord/webhooks/modal/group_builder.rb +55 -0
  196. data/lib/onyxcord/webhooks/modal/label_builder.rb +183 -0
  197. data/lib/onyxcord/webhooks/modal.rb +2 -239
  198. data/lib/onyxcord/webhooks/version.rb +3 -3
  199. data/lib/onyxcord/webhooks/view/container_builder.rb +80 -0
  200. data/lib/onyxcord/webhooks/view/file_builder.rb +24 -0
  201. data/lib/onyxcord/webhooks/view/media_gallery_builder.rb +50 -0
  202. data/lib/onyxcord/webhooks/view/row_builder.rb +120 -0
  203. data/lib/onyxcord/webhooks/view/section_builder.rb +61 -0
  204. data/lib/onyxcord/webhooks/view/select_menu_builder.rb +77 -0
  205. data/lib/onyxcord/webhooks/view/separator_builder.rb +22 -0
  206. data/lib/onyxcord/webhooks/view/text_display_builder.rb +20 -0
  207. data/lib/onyxcord/webhooks/view.rb +9 -425
  208. data/lib/onyxcord.rb +106 -42
  209. metadata +207 -92
  210. data/.github/CONTRIBUTING.md +0 -13
  211. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -38
  212. data/.github/ISSUE_TEMPLATE/feature_request.md +0 -24
  213. data/.github/pull_request_template.md +0 -37
  214. data/.github/workflows/ci.yml +0 -78
  215. data/.github/workflows/codeql.yml +0 -65
  216. data/.github/workflows/deploy.yml +0 -54
  217. data/.github/workflows/release.yml +0 -51
  218. data/.gitignore +0 -17
  219. data/.markdownlint.json +0 -4
  220. data/.overcommit.yml +0 -7
  221. data/.rspec +0 -2
  222. data/.rubocop.yml +0 -129
  223. data/.yardopts +0 -1
  224. data/CHANGELOG.md +0 -257
  225. data/Gemfile +0 -6
  226. data/LICENSE.txt +0 -21
  227. data/README.md +0 -448
  228. data/Rakefile +0 -13
  229. data/lib/onyxcord/api/channel.rb +0 -703
  230. data/lib/onyxcord/api/server.rb +0 -775
  231. data/lib/onyxcord/async/runtime.rb +0 -30
  232. data/lib/onyxcord/commands/command_bot.rb +0 -505
  233. data/lib/onyxcord/data/interaction.rb +0 -1151
  234. data/lib/onyxcord/data.rb +0 -57
  235. data/lib/onyxcord/event_executor.rb +0 -153
  236. data/lib/onyxcord/gateway.rb +0 -505
  237. data/lib/onyxcord/http.rb +0 -186
  238. data/lib/onyxcord/json.rb +0 -49
  239. data/lib/onyxcord/rate_limiter/async_rest.rb +0 -150
  240. data/lib/onyxcord/rate_limiter/gateway.rb +0 -42
  241. data/lib/onyxcord/rate_limiter/rest.rb +0 -142
  242. data/lib/onyxcord/version.rb +0 -7
  243. data/lib/onyxcord/websocket.rb +0 -77
  244. data/onyxcord.gemspec +0 -63
  245. /data/lib/onyxcord/{configuration.rb → core/configuration.rb} +0 -0
  246. /data/lib/onyxcord/{errors.rb → core/errors.rb} +0 -0
  247. /data/lib/onyxcord/{upload.rb → internal/upload.rb} +0 -0
  248. /data/lib/onyxcord/{data → models}/attachment.rb +0 -0
  249. /data/lib/onyxcord/{data → models}/audit_logs.rb +0 -0
  250. /data/lib/onyxcord/{data → models}/call.rb +0 -0
  251. /data/lib/onyxcord/{data → models}/channel_tag.rb +0 -0
  252. /data/lib/onyxcord/{data → models}/component.rb +0 -0
  253. /data/lib/onyxcord/{data → models}/embed.rb +0 -0
  254. /data/lib/onyxcord/{data → models}/install_params.rb +0 -0
  255. /data/lib/onyxcord/{data → models}/integration.rb +0 -0
  256. /data/lib/onyxcord/{data → models}/message_activity.rb +0 -0
  257. /data/lib/onyxcord/{data → models}/overwrite.rb +0 -0
  258. /data/lib/onyxcord/{data → models}/reaction.rb +0 -0
  259. /data/lib/onyxcord/{data → models}/recipient.rb +0 -0
  260. /data/lib/onyxcord/{data → models}/role_connection_data.rb +0 -0
  261. /data/lib/onyxcord/{data → models}/role_subscription.rb +0 -0
  262. /data/lib/onyxcord/{data → models}/snapshot.rb +0 -0
  263. /data/lib/onyxcord/{data → models}/timestamp.rb +0 -0
  264. /data/lib/onyxcord/{data → models}/voice_region.rb +0 -0
  265. /data/lib/onyxcord/{data → models}/voice_state.rb +0 -0
  266. /data/lib/onyxcord/{colour_rgb.rb → utils/colour_rgb.rb} +0 -0
  267. /data/lib/onyxcord/{id_object.rb → utils/id_object.rb} +0 -0
  268. /data/lib/onyxcord/{message_components.rb → utils/message_components.rb} +0 -0
  269. /data/lib/onyxcord/{paginator.rb → utils/paginator.rb} +0 -0
  270. /data/lib/onyxcord/{permissions.rb → utils/permissions.rb} +0 -0
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OnyxCord::Voice
4
+ class VoiceUDP
5
+ # @return [true, false] whether or not UDP communications are encrypted.
6
+ # @deprecated Discord no longer supports unencrypted voice communication.
7
+ attr_accessor :encrypted
8
+ alias_method :encrypted?, :encrypted
9
+
10
+ # Sets the secret key used for encryption
11
+ attr_writer :secret_key
12
+
13
+ # The UDP encryption mode
14
+ attr_reader :mode
15
+
16
+ # @!visibility private
17
+ attr_writer :mode
18
+
19
+ # Creates a new UDP connection. Only creates a socket as the discovery reply may come before the data is
20
+ # initialized.
21
+ def initialize
22
+ @socket = UDPSocket.new
23
+ @encrypted = true
24
+ end
25
+
26
+ # Initializes the UDP socket with data obtained from opcode 2.
27
+ # @param ip [String] The IP address to connect to.
28
+ # @param port [Integer] The port to connect to.
29
+ # @param ssrc [Integer] The Super Secret Relay Code (SSRC). Discord uses this to identify different voice users
30
+ # on the same endpoint.
31
+ def connect(ip, port, ssrc)
32
+ @ip = ip
33
+ @port = port
34
+ @ssrc = ssrc
35
+ end
36
+
37
+ # Waits for a UDP discovery reply, and returns the sent data.
38
+ # @return [Array(String, Integer)] the IP and port received from the discovery reply.
39
+ def receive_discovery_reply
40
+ # Wait for a UDP message
41
+ message = @socket.recv(74)
42
+ ip = message[8..-3].delete("\0")
43
+ port = message[-2..].unpack1('n')
44
+ [ip, port]
45
+ end
46
+
47
+ # Makes an audio packet from a buffer and sends it to Discord.
48
+ # @param buf [String] The audio data to send, must be exactly one Opus frame
49
+ # @param sequence [Integer] The packet sequence number, incremented by one for subsequent packets
50
+ # @param time [Integer] When this packet should be played back, in no particular unit (essentially just the
51
+ # sequence number multiplied by 960)
52
+ def send_audio(buf, sequence, time)
53
+ # Header of the audio packet
54
+ header = generate_header(sequence, time)
55
+
56
+ nonce = generate_nonce
57
+ buf = encrypt_audio(buf, header, nonce)
58
+ data = header + buf + nonce.byteslice(0, 4)
59
+
60
+ send_packet(data)
61
+ end
62
+
63
+ # Sends the UDP discovery packet with the internally stored SSRC. Discord will send a reply afterwards which can
64
+ # be received using {#receive_discovery_reply}
65
+ def send_discovery
66
+ # Create empty packet
67
+ discovery_packet = ''
68
+
69
+ # Add Type request (0x1 = request, 0x2 = response)
70
+ discovery_packet += [0x1].pack('n')
71
+
72
+ # Add Length (excluding Type and itself = 70)
73
+ discovery_packet += [70].pack('n')
74
+
75
+ # Add SSRC
76
+ discovery_packet += [@ssrc].pack('N')
77
+
78
+ # Add 66 zeroes so the packet is 74 bytes long
79
+ discovery_packet += "\0" * 66
80
+
81
+ send_packet(discovery_packet)
82
+ end
83
+
84
+ def close
85
+ @socket.close unless @socket.closed?
86
+ end
87
+
88
+ private
89
+
90
+ # Encrypts audio data using libsodium
91
+ # @param buf [String] The encoded audio data to be encrypted
92
+ # @param header [String] The RTP header of the packet, used as associated data
93
+ # @param nonce [String] The nonce to be used to encrypt the data
94
+ # @return [String] the audio data, encrypted
95
+ def encrypt_audio(buf, header, nonce)
96
+ raise 'No secret key found, despite encryption being enabled!' unless @secret_key
97
+
98
+ case @mode
99
+ when 'aead_xchacha20_poly1305_rtpsize'
100
+ OnyxCord::Voice::XChaCha20AEAD.encrypt(buf, header, nonce, @secret_key)
101
+ else
102
+ raise "`#{@mode}' is not a supported encryption mode"
103
+ end
104
+ end
105
+
106
+ def send_packet(packet)
107
+ @socket.send(packet, 0, @ip, @port)
108
+ end
109
+
110
+ # @return [String]
111
+ def generate_nonce
112
+ case @mode
113
+ when 'aead_xchacha20_poly1305_rtpsize'
114
+ case @incremental_nonce
115
+ when nil, 0xff_ff_ff_ff
116
+ @incremental_nonce = 0
117
+ else
118
+ @incremental_nonce += 1
119
+ end
120
+ [@incremental_nonce].pack('N').ljust(24, "\0")
121
+ else
122
+ raise "`#{@mode}' is not a supported encryption mode"
123
+ end
124
+ end
125
+
126
+ # @return [String]
127
+ def generate_header(sequence, time)
128
+ [0x80, 0x78, sequence, time, @ssrc].pack('CCnNN')
129
+ end
130
+ end
131
+
132
+ # Represents a websocket client connection to the voice server. The websocket connection (sometimes called vWS) is
133
+ # used to manage general data about the connection, such as sending the speaking packet, which determines the green
134
+ # circle around users on Discord, and obtaining UDP connection info.
135
+ end
@@ -0,0 +1,225 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OnyxCord::Voice
4
+ class VoiceWS
5
+ # The version of the voice gateway that's supposed to be used.
6
+ VOICE_GATEWAY_VERSION = 8
7
+
8
+ # @return [VoiceUDP] the UDP voice connection over which the actual audio data is sent.
9
+ attr_reader :udp
10
+
11
+ # Makes a new voice websocket client, but doesn't connect it (see {#connect} for that)
12
+ # @param channel [Channel] The voice channel to connect to
13
+ # @param bot [Bot] The regular bot to which this vWS is bound
14
+ # @param token [String] The authentication token which is also used for REST requests
15
+ # @param session [String] The voice session ID Discord sends over the regular websocket
16
+ # @param endpoint [String] The endpoint URL to connect to
17
+ def initialize(channel, bot, token, session, endpoint)
18
+ raise 'libsodium is unavailable - unable to create voice bot! Please read https://github.com/kruldevb/OnyxCord/wiki/Installing-libsodium' unless LIBSODIUM_AVAILABLE
19
+
20
+ @channel = channel
21
+ @bot = bot
22
+ @token = token
23
+ @session = session
24
+
25
+ @endpoint = endpoint
26
+
27
+ @udp = VoiceUDP.new
28
+ end
29
+
30
+ # Send a connection init packet (op 0)
31
+ # @param server_id [Integer] The ID of the server to connect to
32
+ # @param bot_user_id [Integer] The ID of the bot that is connecting
33
+ # @param session_id [String] The voice session ID
34
+ # @param token [String] The Discord authentication token
35
+ def send_init(server_id, bot_user_id, session_id, token)
36
+ send_opcode(
37
+ Opcodes::IDENTIFY,
38
+ {
39
+ server_id: server_id,
40
+ user_id: bot_user_id,
41
+ session_id: session_id,
42
+ token: token
43
+ }
44
+ )
45
+ end
46
+
47
+ # Sends the UDP connection packet (op 1)
48
+ # @param ip [String] The IP to bind UDP to
49
+ # @param port [Integer] The port to bind UDP to
50
+ # @param mode [Object] Which mode to use for the voice connection
51
+ def send_udp_connection(ip, port, mode)
52
+ send_opcode(
53
+ Opcodes::SELECT_PROTOCOL,
54
+ {
55
+ protocol: 'udp',
56
+ data: {
57
+ address: ip,
58
+ port: port,
59
+ mode: mode
60
+ }
61
+ }
62
+ )
63
+ end
64
+
65
+ # Send a heartbeat (op 3), has to be done every @heartbeat_interval seconds or the connection will terminate
66
+ def send_heartbeat
67
+ millis = Time.now.strftime('%s%L').to_i
68
+ @bot.debug("Sending voice heartbeat at #{millis}")
69
+
70
+ send_opcode(
71
+ Opcodes::HEARTBEAT,
72
+ {
73
+ t: millis,
74
+ seq_ack: @seq
75
+ }
76
+ )
77
+ end
78
+
79
+ # Send a speaking packet (op 5). This determines the green circle around the avatar in the voice channel
80
+ # @param value [true, false, Integer] Whether or not the bot should be speaking, can also be a bitmask denoting audio type.
81
+ def send_speaking(value)
82
+ @bot.debug("Speaking: #{value}")
83
+ send_opcode(
84
+ Opcodes::SPEAKING,
85
+ {
86
+ speaking: value,
87
+ delay: 0
88
+ }
89
+ )
90
+ end
91
+
92
+ def send_opcode(opcode, data)
93
+ @bot.debug("Sending voice opcode #{opcode} with data: #{data}")
94
+ @client.send({
95
+ op: opcode,
96
+ d: data
97
+ }.to_json)
98
+ end
99
+
100
+ # Event handlers; public for websocket-simple to work correctly
101
+ # @!visibility private
102
+ def websocket_open
103
+ # Give the current thread a name ('Voice Web Socket Internal')
104
+ Thread.current[:onyxcord_name] = 'vws-i'
105
+
106
+ # Send the init packet
107
+ send_init(@channel.server.id, @bot.profile.id, @session, @token)
108
+ end
109
+
110
+ # @!visibility private
111
+ def websocket_message(msg)
112
+ @bot.debug("Received VWS message! #{msg}")
113
+ packet = JSON.parse(msg)
114
+
115
+ @seq = packet['seq'] if packet['seq']
116
+
117
+ case packet['op']
118
+ when OnyxCord::Voice::Opcodes::READY
119
+ # Opcode 2 contains data to initialize the UDP connection
120
+ @ws_data = packet['d']
121
+
122
+ @ssrc = @ws_data['ssrc']
123
+ @port = @ws_data['port']
124
+
125
+ @udp_mode = (ENCRYPTION_MODES & @ws_data['modes']).first
126
+
127
+ @udp.connect(@ws_data['ip'], @port, @ssrc)
128
+ @udp.send_discovery
129
+ when OnyxCord::Voice::Opcodes::SESSION_DESCRIPTION
130
+ # Opcode 4 sends the secret key used for encryption
131
+ @ws_data = packet['d']
132
+
133
+ # Reset the sequence when starting a new session
134
+ @seq = 0
135
+
136
+ @ready = true
137
+ @udp.secret_key = @ws_data['secret_key'].pack('C*')
138
+ @udp.mode = @ws_data['mode']
139
+ when OnyxCord::Voice::Opcodes::HELLO
140
+ # Opcode 8 contains the heartbeat interval.
141
+ @heartbeat_interval = packet['d']['heartbeat_interval']
142
+ send_heartbeat
143
+ end
144
+ end
145
+
146
+ # Communication goes like this:
147
+ # me discord
148
+ # | |
149
+ # websocket connect -> |
150
+ # | |
151
+ # | <- websocket opcode 2
152
+ # | |
153
+ # UDP discovery -> |
154
+ # | |
155
+ # | <- UDP reply packet
156
+ # | |
157
+ # websocket opcode 1 -> |
158
+ # | |
159
+ # ...
160
+ def connect
161
+ # Connect websocket
162
+ @thread = Thread.new do
163
+ Thread.current[:onyxcord_name] = 'vws'
164
+ init_ws
165
+ end
166
+
167
+ @bot.debug('Started websocket initialization, now waiting for UDP discovery reply')
168
+
169
+ # Now wait for opcode 2 and the resulting UDP reply packet
170
+ ip, port = @udp.receive_discovery_reply
171
+ @bot.debug("UDP discovery reply received! #{ip} #{port}")
172
+
173
+ # Send UDP init packet with received UDP data
174
+ send_udp_connection(ip, port, @udp_mode)
175
+
176
+ @bot.debug('Waiting for op 4 now')
177
+
178
+ # Wait for op 4, then finish
179
+ sleep 0.05 until @ready
180
+ end
181
+
182
+ # Disconnects the websocket and kills the thread
183
+ def destroy(join_timeout = 1)
184
+ @heartbeat_running = false
185
+ @client&.close
186
+ @udp.close
187
+ @thread&.join(join_timeout)
188
+ @thread&.kill if @thread&.alive?
189
+ end
190
+
191
+ private
192
+
193
+ def heartbeat_loop
194
+ @heartbeat_running = true
195
+ while @heartbeat_running
196
+ if @heartbeat_interval
197
+ sleep @heartbeat_interval / 1000.0
198
+ send_heartbeat
199
+ else
200
+ # If no interval has been set yet, sleep a second and check again
201
+ sleep 1
202
+ end
203
+ end
204
+ end
205
+
206
+ def init_ws
207
+ host = "wss://#{@endpoint}/?v=#{VOICE_GATEWAY_VERSION}"
208
+ @bot.debug("Connecting VWS to host: #{host}")
209
+
210
+ # Connect the WS
211
+ @client = Internal::WebSocket.new(
212
+ host,
213
+ method(:websocket_open),
214
+ method(:websocket_message),
215
+ proc { |e| OnyxCord::LOGGER.error "VWS error: #{e}" },
216
+ proc { |e| OnyxCord::LOGGER.warn "VWS close: #{e}" }
217
+ )
218
+
219
+ @bot.debug('VWS connected')
220
+
221
+ # Block any further execution
222
+ heartbeat_loop
223
+ end
224
+ end
225
+ end