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.
- checksums.yaml +4 -4
- data/lib/onyxcord/application_commands/command.rb +4 -97
- data/lib/onyxcord/application_commands/context.rb +4 -94
- data/lib/onyxcord/application_commands/option.rb +4 -75
- data/lib/onyxcord/application_commands/registry.rb +4 -45
- data/lib/onyxcord/application_commands.rb +8 -5
- data/lib/onyxcord/bot.rb +24 -1759
- data/lib/onyxcord/{cache.rb → cache/manager.rb} +16 -14
- data/lib/onyxcord/cache/stores/gateway.rb +272 -0
- data/lib/onyxcord/commands/bot/channels.rb +48 -0
- data/lib/onyxcord/commands/bot/execution.rb +174 -0
- data/lib/onyxcord/commands/bot/message_dispatch.rb +58 -0
- data/lib/onyxcord/commands/bot/permissions.rb +72 -0
- data/lib/onyxcord/commands/bot.rb +209 -0
- data/lib/onyxcord/commands/container.rb +21 -2
- data/lib/onyxcord/commands/parser.rb +30 -1
- data/lib/onyxcord/container.rb +16 -960
- data/lib/onyxcord/core/bot/application_commands.rb +210 -0
- data/lib/onyxcord/core/bot/awaits.rb +112 -0
- data/lib/onyxcord/core/bot/invites.rb +42 -0
- data/lib/onyxcord/core/bot/messaging.rb +161 -0
- data/lib/onyxcord/core/bot/oauth.rb +27 -0
- data/lib/onyxcord/core/bot/presence.rb +103 -0
- data/lib/onyxcord/core/bot/runtime.rb +183 -0
- data/lib/onyxcord/core/bot/voice.rb +69 -0
- data/lib/onyxcord/{logger.rb → core/logger.rb} +5 -0
- data/lib/onyxcord/core/version.rb +5 -0
- data/lib/onyxcord/events/await/base.rb +48 -0
- data/lib/onyxcord/events/await.rb +1 -46
- data/lib/onyxcord/events/ban/base.rb +54 -0
- data/lib/onyxcord/events/ban/remove.rb +11 -0
- data/lib/onyxcord/events/bans.rb +2 -58
- data/lib/onyxcord/events/channels/base.rb +91 -0
- data/lib/onyxcord/events/channels/delete.rb +73 -0
- data/lib/onyxcord/events/channels/pins.rb +44 -0
- data/lib/onyxcord/events/channels/recipients.rb +77 -0
- data/lib/onyxcord/events/channels.rb +4 -239
- data/lib/onyxcord/events/guilds/audit_logs.rb +79 -0
- data/lib/onyxcord/events/guilds/base.rb +97 -0
- data/lib/onyxcord/events/guilds/emoji.rb +147 -0
- data/lib/onyxcord/events/guilds.rb +3 -267
- data/lib/onyxcord/events/handlers/channels.rb +133 -0
- data/lib/onyxcord/events/handlers/core.rb +146 -0
- data/lib/onyxcord/events/handlers/guilds.rb +375 -0
- data/lib/onyxcord/events/handlers/interactions.rb +238 -0
- data/lib/onyxcord/events/handlers/lifetime.rb +71 -0
- data/lib/onyxcord/events/handlers/messages.rb +218 -0
- data/lib/onyxcord/events/handlers/presence.rb +56 -0
- data/lib/onyxcord/events/handlers/raw.rb +39 -0
- data/lib/onyxcord/events/handlers/reactions.rb +98 -0
- data/lib/onyxcord/events/handlers/scheduled_events.rb +243 -0
- data/lib/onyxcord/events/handlers/voice.rb +46 -0
- data/lib/onyxcord/events/integration/base.rb +45 -0
- data/lib/onyxcord/events/integration/create.rb +11 -0
- data/lib/onyxcord/events/integration/delete.rb +48 -0
- data/lib/onyxcord/events/integration/update.rb +11 -0
- data/lib/onyxcord/events/integrations.rb +4 -98
- data/lib/onyxcord/events/interactions/application_commands.rb +185 -0
- data/lib/onyxcord/events/interactions/autocomplete.rb +62 -0
- data/lib/onyxcord/events/interactions/base.rb +175 -0
- data/lib/onyxcord/events/interactions/components.rb +221 -0
- data/lib/onyxcord/events/interactions/permissions.rb +53 -0
- data/lib/onyxcord/events/interactions.rb +5 -622
- data/lib/onyxcord/events/invite/create.rb +79 -0
- data/lib/onyxcord/events/invite/delete.rb +55 -0
- data/lib/onyxcord/events/invites.rb +2 -125
- data/lib/onyxcord/events/lifetime/base.rb +31 -0
- data/lib/onyxcord/events/lifetime.rb +1 -29
- data/lib/onyxcord/events/member/add.rb +12 -0
- data/lib/onyxcord/events/member/base.rb +67 -0
- data/lib/onyxcord/events/member/delete.rb +21 -0
- data/lib/onyxcord/events/member/update.rb +25 -0
- data/lib/onyxcord/events/members.rb +4 -108
- data/lib/onyxcord/events/message/base.rb +295 -0
- data/lib/onyxcord/events/message/ids.rb +71 -0
- data/lib/onyxcord/events/message/mentions.rb +55 -0
- data/lib/onyxcord/events/message/updates.rb +27 -0
- data/lib/onyxcord/events/message.rb +4 -397
- data/lib/onyxcord/events/poll/add.rb +11 -0
- data/lib/onyxcord/events/poll/base.rb +106 -0
- data/lib/onyxcord/events/poll/remove.rb +11 -0
- data/lib/onyxcord/events/polls.rb +3 -116
- data/lib/onyxcord/events/presence/playing.rb +77 -0
- data/lib/onyxcord/events/presence/status.rb +60 -0
- data/lib/onyxcord/events/presence.rb +2 -129
- data/lib/onyxcord/events/raw/base.rb +74 -0
- data/lib/onyxcord/events/raw.rb +1 -72
- data/lib/onyxcord/events/reactions/base.rb +143 -0
- data/lib/onyxcord/events/reactions/clear.rb +99 -0
- data/lib/onyxcord/events/reactions.rb +2 -216
- data/lib/onyxcord/events/role/create.rb +46 -0
- data/lib/onyxcord/events/role/delete.rb +40 -0
- data/lib/onyxcord/events/role/update.rb +11 -0
- data/lib/onyxcord/events/roles.rb +3 -85
- data/lib/onyxcord/events/scheduled_events/events.rb +112 -0
- data/lib/onyxcord/events/scheduled_events/users.rb +91 -0
- data/lib/onyxcord/events/scheduled_events.rb +2 -169
- data/lib/onyxcord/events/thread/base.rb +49 -0
- data/lib/onyxcord/events/thread/members.rb +44 -0
- data/lib/onyxcord/events/thread/update.rb +18 -0
- data/lib/onyxcord/events/threads.rb +3 -98
- data/lib/onyxcord/events/typing/base.rb +73 -0
- data/lib/onyxcord/events/typing.rb +1 -71
- data/lib/onyxcord/events/voice/server_update.rb +48 -0
- data/lib/onyxcord/events/voice/state_update.rb +137 -0
- data/lib/onyxcord/events/voice_server_update.rb +2 -46
- data/lib/onyxcord/events/voice_state_update.rb +2 -135
- data/lib/onyxcord/events/webhook/update.rb +65 -0
- data/lib/onyxcord/events/webhooks.rb +1 -63
- data/lib/onyxcord/gateway/client.rb +460 -0
- data/lib/onyxcord/interactions/command.rb +120 -0
- data/lib/onyxcord/interactions/context.rb +109 -0
- data/lib/onyxcord/interactions/internal/application_command.rb +203 -0
- data/lib/onyxcord/interactions/internal/message.rb +152 -0
- data/lib/onyxcord/interactions/internal/metadata.rb +109 -0
- data/lib/onyxcord/interactions/internal/option_builder.rb +230 -0
- data/lib/onyxcord/interactions/internal/permission_builder.rb +90 -0
- data/lib/onyxcord/interactions/option.rb +88 -0
- data/lib/onyxcord/interactions/registry.rb +50 -0
- data/lib/onyxcord/interactions.rb +16 -0
- data/lib/onyxcord/internal/async_runtime.rb +32 -0
- data/lib/onyxcord/internal/event_bus.rb +591 -0
- data/lib/onyxcord/internal/event_executor.rb +155 -0
- data/lib/onyxcord/internal/gateway/opcodes.rb +22 -0
- data/lib/onyxcord/internal/gateway/session.rb +45 -0
- data/lib/onyxcord/internal/http.rb +189 -0
- data/lib/onyxcord/internal/json.rb +51 -0
- data/lib/onyxcord/{message_payload.rb → internal/message_payload.rb} +3 -3
- data/lib/onyxcord/internal/rate_limiter/async_rest.rb +151 -0
- data/lib/onyxcord/internal/rate_limiter/gateway.rb +44 -0
- data/lib/onyxcord/internal/rate_limiter/rest.rb +144 -0
- data/lib/onyxcord/internal/websocket.rb +79 -0
- data/lib/onyxcord/light/data.rb +1 -1
- data/lib/onyxcord/light/integrations.rb +1 -1
- data/lib/onyxcord/light/light_bot.rb +7 -7
- data/lib/onyxcord/{data → models}/activity.rb +2 -2
- data/lib/onyxcord/{data → models}/application.rb +3 -3
- data/lib/onyxcord/{data → models}/avatar_decoration.rb +1 -1
- data/lib/onyxcord/{data → models}/channel.rb +23 -23
- data/lib/onyxcord/{data → models}/collectibles.rb +1 -1
- data/lib/onyxcord/{data → models}/emoji.rb +1 -1
- data/lib/onyxcord/models/interaction.rb +450 -0
- data/lib/onyxcord/{data → models}/invite.rb +1 -1
- data/lib/onyxcord/{data → models}/member.rb +11 -11
- data/lib/onyxcord/{data → models}/message.rb +18 -14
- data/lib/onyxcord/{data → models}/poll.rb +2 -2
- data/lib/onyxcord/{data → models}/primary_server.rb +1 -1
- data/lib/onyxcord/{data → models}/profile.rb +4 -4
- data/lib/onyxcord/{data → models}/role.rb +6 -12
- data/lib/onyxcord/{data → models}/scheduled_event.rb +5 -5
- data/lib/onyxcord/{data → models}/server.rb +41 -41
- data/lib/onyxcord/{data → models}/server_preview.rb +2 -2
- data/lib/onyxcord/{data → models}/team.rb +1 -1
- data/lib/onyxcord/{data → models}/user.rb +5 -5
- data/lib/onyxcord/{data → models}/webhook.rb +10 -10
- data/lib/onyxcord/models.rb +57 -0
- data/lib/onyxcord/{api.rb → rest/client.rb} +23 -22
- data/lib/onyxcord/{api → rest/routes}/application.rb +79 -57
- data/lib/onyxcord/rest/routes/channel/base.rb +73 -0
- data/lib/onyxcord/rest/routes/channel/groups.rb +78 -0
- data/lib/onyxcord/rest/routes/channel/messages.rb +130 -0
- data/lib/onyxcord/rest/routes/channel/permissions.rb +73 -0
- data/lib/onyxcord/rest/routes/channel/pins.rb +45 -0
- data/lib/onyxcord/rest/routes/channel/polls.rb +32 -0
- data/lib/onyxcord/rest/routes/channel/reactions.rb +86 -0
- data/lib/onyxcord/rest/routes/channel/threads.rb +176 -0
- data/lib/onyxcord/rest/routes/channel/webhooks.rb +47 -0
- data/lib/onyxcord/rest/routes/channel.rb +12 -0
- data/lib/onyxcord/{api/interaction.rb → rest/routes/interaction/base.rb} +7 -25
- data/lib/onyxcord/rest/routes/interaction/response.rb +24 -0
- data/lib/onyxcord/rest/routes/interaction.rb +4 -0
- data/lib/onyxcord/{api → rest/routes}/invite.rb +7 -7
- data/lib/onyxcord/rest/routes/server/admin.rb +174 -0
- data/lib/onyxcord/rest/routes/server/assets.rb +25 -0
- data/lib/onyxcord/rest/routes/server/bans.rb +67 -0
- data/lib/onyxcord/rest/routes/server/base.rb +77 -0
- data/lib/onyxcord/rest/routes/server/channels.rb +58 -0
- data/lib/onyxcord/rest/routes/server/emoji.rb +48 -0
- data/lib/onyxcord/rest/routes/server/members.rb +109 -0
- data/lib/onyxcord/rest/routes/server/messages.rb +19 -0
- data/lib/onyxcord/rest/routes/server/roles.rb +156 -0
- data/lib/onyxcord/rest/routes/server/scheduled_events.rb +86 -0
- data/lib/onyxcord/rest/routes/server.rb +12 -0
- data/lib/onyxcord/{api → rest/routes}/user.rb +23 -23
- data/lib/onyxcord/{api → rest/routes}/webhook.rb +22 -22
- data/lib/onyxcord/{allowed_mentions.rb → utils/allowed_mentions.rb} +1 -1
- data/lib/onyxcord/utils/timestamp.rb +69 -0
- data/lib/onyxcord/voice/{voice_bot.rb → client.rb} +4 -4
- data/lib/onyxcord/voice/encoder.rb +1 -1
- data/lib/onyxcord/voice/network/udp.rb +135 -0
- data/lib/onyxcord/voice/network/websocket.rb +225 -0
- data/lib/onyxcord/voice/network.rb +5 -357
- data/lib/onyxcord/webhooks/builder.rb +1 -1
- data/lib/onyxcord/webhooks/client.rb +13 -13
- data/lib/onyxcord/webhooks/modal/group_builder.rb +55 -0
- data/lib/onyxcord/webhooks/modal/label_builder.rb +183 -0
- data/lib/onyxcord/webhooks/modal.rb +2 -239
- data/lib/onyxcord/webhooks/version.rb +3 -3
- data/lib/onyxcord/webhooks/view/container_builder.rb +80 -0
- data/lib/onyxcord/webhooks/view/file_builder.rb +24 -0
- data/lib/onyxcord/webhooks/view/media_gallery_builder.rb +50 -0
- data/lib/onyxcord/webhooks/view/row_builder.rb +120 -0
- data/lib/onyxcord/webhooks/view/section_builder.rb +61 -0
- data/lib/onyxcord/webhooks/view/select_menu_builder.rb +77 -0
- data/lib/onyxcord/webhooks/view/separator_builder.rb +22 -0
- data/lib/onyxcord/webhooks/view/text_display_builder.rb +20 -0
- data/lib/onyxcord/webhooks/view.rb +9 -425
- data/lib/onyxcord.rb +106 -42
- metadata +207 -92
- data/.github/CONTRIBUTING.md +0 -13
- data/.github/ISSUE_TEMPLATE/bug_report.md +0 -38
- data/.github/ISSUE_TEMPLATE/feature_request.md +0 -24
- data/.github/pull_request_template.md +0 -37
- data/.github/workflows/ci.yml +0 -78
- data/.github/workflows/codeql.yml +0 -65
- data/.github/workflows/deploy.yml +0 -54
- data/.github/workflows/release.yml +0 -51
- data/.gitignore +0 -17
- data/.markdownlint.json +0 -4
- data/.overcommit.yml +0 -7
- data/.rspec +0 -2
- data/.rubocop.yml +0 -129
- data/.yardopts +0 -1
- data/CHANGELOG.md +0 -257
- data/Gemfile +0 -6
- data/LICENSE.txt +0 -21
- data/README.md +0 -448
- data/Rakefile +0 -13
- data/lib/onyxcord/api/channel.rb +0 -703
- data/lib/onyxcord/api/server.rb +0 -775
- data/lib/onyxcord/async/runtime.rb +0 -30
- data/lib/onyxcord/commands/command_bot.rb +0 -505
- data/lib/onyxcord/data/interaction.rb +0 -1151
- data/lib/onyxcord/data.rb +0 -57
- data/lib/onyxcord/event_executor.rb +0 -153
- data/lib/onyxcord/gateway.rb +0 -505
- data/lib/onyxcord/http.rb +0 -186
- data/lib/onyxcord/json.rb +0 -49
- data/lib/onyxcord/rate_limiter/async_rest.rb +0 -150
- data/lib/onyxcord/rate_limiter/gateway.rb +0 -42
- data/lib/onyxcord/rate_limiter/rest.rb +0 -142
- data/lib/onyxcord/version.rb +0 -7
- data/lib/onyxcord/websocket.rb +0 -77
- data/onyxcord.gemspec +0 -63
- /data/lib/onyxcord/{configuration.rb → core/configuration.rb} +0 -0
- /data/lib/onyxcord/{errors.rb → core/errors.rb} +0 -0
- /data/lib/onyxcord/{upload.rb → internal/upload.rb} +0 -0
- /data/lib/onyxcord/{data → models}/attachment.rb +0 -0
- /data/lib/onyxcord/{data → models}/audit_logs.rb +0 -0
- /data/lib/onyxcord/{data → models}/call.rb +0 -0
- /data/lib/onyxcord/{data → models}/channel_tag.rb +0 -0
- /data/lib/onyxcord/{data → models}/component.rb +0 -0
- /data/lib/onyxcord/{data → models}/embed.rb +0 -0
- /data/lib/onyxcord/{data → models}/install_params.rb +0 -0
- /data/lib/onyxcord/{data → models}/integration.rb +0 -0
- /data/lib/onyxcord/{data → models}/message_activity.rb +0 -0
- /data/lib/onyxcord/{data → models}/overwrite.rb +0 -0
- /data/lib/onyxcord/{data → models}/reaction.rb +0 -0
- /data/lib/onyxcord/{data → models}/recipient.rb +0 -0
- /data/lib/onyxcord/{data → models}/role_connection_data.rb +0 -0
- /data/lib/onyxcord/{data → models}/role_subscription.rb +0 -0
- /data/lib/onyxcord/{data → models}/snapshot.rb +0 -0
- /data/lib/onyxcord/{data → models}/timestamp.rb +0 -0
- /data/lib/onyxcord/{data → models}/voice_region.rb +0 -0
- /data/lib/onyxcord/{data → models}/voice_state.rb +0 -0
- /data/lib/onyxcord/{colour_rgb.rb → utils/colour_rgb.rb} +0 -0
- /data/lib/onyxcord/{id_object.rb → utils/id_object.rb} +0 -0
- /data/lib/onyxcord/{message_components.rb → utils/message_components.rb} +0 -0
- /data/lib/onyxcord/{paginator.rb → utils/paginator.rb} +0 -0
- /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
|