discorb 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +56 -0
  3. data/.yardopts +6 -0
  4. data/Changelog.md +5 -0
  5. data/Gemfile +23 -0
  6. data/Gemfile.lock +70 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +53 -0
  9. data/Rakefile +46 -0
  10. data/bin/console +15 -0
  11. data/bin/setup +8 -0
  12. data/discorb.gemspec +37 -0
  13. data/docs/Examples.md +26 -0
  14. data/docs/events.md +480 -0
  15. data/docs/voice_events.md +283 -0
  16. data/examples/components/authorization_button.rb +43 -0
  17. data/examples/components/select_menu.rb +61 -0
  18. data/examples/extension/main.rb +12 -0
  19. data/examples/extension/message_expander.rb +41 -0
  20. data/examples/simple/eval.rb +32 -0
  21. data/examples/simple/ping_pong.rb +16 -0
  22. data/examples/simple/rolepanel.rb +65 -0
  23. data/examples/simple/wait_for_message.rb +30 -0
  24. data/lib/discorb/application.rb +157 -0
  25. data/lib/discorb/asset.rb +57 -0
  26. data/lib/discorb/audit_logs.rb +323 -0
  27. data/lib/discorb/channel.rb +1101 -0
  28. data/lib/discorb/client.rb +363 -0
  29. data/lib/discorb/color.rb +173 -0
  30. data/lib/discorb/common.rb +123 -0
  31. data/lib/discorb/components.rb +290 -0
  32. data/lib/discorb/dictionary.rb +119 -0
  33. data/lib/discorb/embed.rb +345 -0
  34. data/lib/discorb/emoji.rb +218 -0
  35. data/lib/discorb/emoji_table.rb +3799 -0
  36. data/lib/discorb/error.rb +98 -0
  37. data/lib/discorb/event.rb +35 -0
  38. data/lib/discorb/extend.rb +18 -0
  39. data/lib/discorb/extension.rb +54 -0
  40. data/lib/discorb/file.rb +69 -0
  41. data/lib/discorb/flag.rb +109 -0
  42. data/lib/discorb/gateway.rb +967 -0
  43. data/lib/discorb/gateway_requests.rb +47 -0
  44. data/lib/discorb/guild.rb +1244 -0
  45. data/lib/discorb/guild_template.rb +211 -0
  46. data/lib/discorb/image.rb +43 -0
  47. data/lib/discorb/integration.rb +111 -0
  48. data/lib/discorb/intents.rb +137 -0
  49. data/lib/discorb/interaction.rb +333 -0
  50. data/lib/discorb/internet.rb +285 -0
  51. data/lib/discorb/invite.rb +145 -0
  52. data/lib/discorb/log.rb +70 -0
  53. data/lib/discorb/member.rb +232 -0
  54. data/lib/discorb/message.rb +583 -0
  55. data/lib/discorb/modules.rb +138 -0
  56. data/lib/discorb/permission.rb +270 -0
  57. data/lib/discorb/presence.rb +308 -0
  58. data/lib/discorb/reaction.rb +48 -0
  59. data/lib/discorb/role.rb +189 -0
  60. data/lib/discorb/sticker.rb +157 -0
  61. data/lib/discorb/user.rb +163 -0
  62. data/lib/discorb/utils.rb +16 -0
  63. data/lib/discorb/voice_state.rb +251 -0
  64. data/lib/discorb/webhook.rb +420 -0
  65. data/lib/discorb.rb +51 -0
  66. metadata +120 -0
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Discorb
4
+ #
5
+ # Error class for Discorb.
6
+ # @abstract
7
+ #
8
+ class DiscorbError < StandardError
9
+ private
10
+
11
+ def enumerate_errors(hash)
12
+ res = {}
13
+ _recr_items([], hash, res)
14
+ res
15
+ end
16
+
17
+ def _recr_items(key, item, res)
18
+ case item
19
+ when Array
20
+ item.each_with_index do |v, i|
21
+ _recr_items (key + [i]), v, res
22
+ end
23
+ when Hash
24
+ item.each do |k, v|
25
+ _recr_items (key + [k]), v, res
26
+ end
27
+ else
28
+ res[key.join(".").gsub("_errors.", "")] = item
29
+ end
30
+ end
31
+ end
32
+
33
+ #
34
+ # Represents a HTTP error.
35
+ #
36
+ class HTTPError < DiscorbError
37
+ # @return [String] the HTTP response code.
38
+ attr_reader :code
39
+ # @return [Net::HTTPResponse] the HTTP response.
40
+ attr_reader :response
41
+
42
+ # @!visibility private
43
+ def initialize(resp, data)
44
+ @code = data[:code]
45
+ @response = resp
46
+ super(data[:message])
47
+ end
48
+ end
49
+
50
+ #
51
+ # Represents a 400 error.
52
+ #
53
+ class BadRequestError < HTTPError
54
+ # @!visibility private
55
+ def initialize(resp, data)
56
+ @code = data[:code]
57
+ @response = resp
58
+ DiscorbError.instance_method(:initialize).bind(self).call(
59
+ [data[:message], "\n", enumerate_errors(data[:errors]).map do |ek, ev|
60
+ "#{ek}=>#{ev}"
61
+ end.join("\n")].join("\n")
62
+ )
63
+ end
64
+ end
65
+
66
+ #
67
+ # Represents a 403 error.
68
+ #
69
+ class ForbiddenError < HTTPError
70
+ end
71
+
72
+ #
73
+ # Represents a 404 error.
74
+ #
75
+ class NotFoundError < HTTPError
76
+ end
77
+
78
+ #
79
+ # Represents a error in client-side.
80
+ #
81
+ class ClientError < DiscorbError
82
+ end
83
+
84
+ #
85
+ # Represents a timeout error.
86
+ #
87
+ class TimeoutError < DiscorbError
88
+ end
89
+
90
+ #
91
+ # Represents a warning.
92
+ #
93
+ class NotSupportedWarning < DiscorbError
94
+ def initialize(message)
95
+ super("#{message} is not supported yet.")
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Discorb
4
+ #
5
+ # Represents a event.
6
+ # This class shouldn't be instantiated directly.
7
+ # Use {Client#on} instead.
8
+ #
9
+ class Event
10
+ # @return [Proc] the block to be called.
11
+ attr_reader :block
12
+ # @return [Symbol] the event id.
13
+ attr_reader :id
14
+ # @return [Hash] the event discriminator.
15
+ attr_reader :discriminator
16
+ # @return [Boolean] whether the event is once or not.
17
+ attr_reader :once
18
+ alias once? once
19
+
20
+ def initialize(block, id, discriminator)
21
+ @block = block
22
+ @id = id
23
+ @once = discriminator.fetch(:once, false)
24
+ @discriminator = discriminator
25
+ @rescue = nil
26
+ end
27
+
28
+ #
29
+ # Calls the block associated with the event.
30
+ #
31
+ def call(...)
32
+ @block.call(...)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Time
4
+ #
5
+ # Format a time object to a Discord formatted string.
6
+ #
7
+ # @param ["f", "F", "d", "D", "t", "T", "R"] type The format to use.
8
+ #
9
+ # @return [String] The formatted time.
10
+ #
11
+ def to_df(type = nil)
12
+ if type.nil?
13
+ "<t:#{to_i}>"
14
+ else
15
+ "<t:#{to_i}:#{type}>"
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Discorb
4
+ #
5
+ # Module to make extension.
6
+ # extend this module to make your own extension.
7
+ # @see file:docs/extension.md
8
+ # @abstract
9
+ #
10
+ module Extension
11
+ @events = {}
12
+ @client = nil
13
+
14
+ #
15
+ # Define a new event.
16
+ #
17
+ # @param [Symbol] event_name The name of the event.
18
+ # @param [Symbol] id The id of the event. Used to delete the event.
19
+ # @param [Hash] discriminator Other discriminators.
20
+ # @param [Proc] block The block to execute when the event is triggered.
21
+ #
22
+ # @return [Discorb::Event] The event.
23
+ #
24
+ def event(event_name, id: nil, **discriminator, &block)
25
+ raise ArgumentError, "Event name must be a symbol" unless event_name.is_a?(Symbol)
26
+ raise ArgumentError, "block must be a Proc" unless block.is_a?(Proc)
27
+
28
+ @events = {} if @events.nil?
29
+ @events[event_name] ||= []
30
+ discriminator[:extension] = Extension
31
+ @events[event_name] << Discorb::Event.new(block, id, discriminator)
32
+ end
33
+
34
+ #
35
+ # Define a new once event.
36
+ #
37
+ # @param [Symbol] event_name The name of the event.
38
+ # @param [Symbol] id The id of the event. Used to delete the event.
39
+ # @param [Hash] discriminator Other discriminators.
40
+ # @param [Proc] block The block to execute when the event is triggered.
41
+ #
42
+ # @return [Discorb::Event] The event.
43
+ #
44
+ def once_event(event_name, id: nil, **discriminator, &block)
45
+ event(event_name, id: id, once: true, **discriminator, &block)
46
+ end
47
+
48
+ # @return [Hash{Symbol => Array<Discorb::Event>}] The events of the extension.
49
+ attr_reader :events
50
+
51
+ # @!visibility private
52
+ attr_accessor :client
53
+ end
54
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mime/types"
4
+
5
+ module Discorb
6
+ #
7
+ # Represents a attachment file.
8
+ #
9
+ class Attachment < DiscordModel
10
+ # @return [#read] The file content.
11
+ attr_reader :io
12
+ # @return [Discorb::Snowflake] The attachment id.
13
+ attr_reader :id
14
+ # @return [String] The attachment filename.
15
+ attr_reader :filename
16
+ # @return [String] The attachment content type.
17
+ attr_reader :content_type
18
+ # @return [Integer] The attachment size in bytes.
19
+ attr_reader :size
20
+ # @return [String] The attachment url.
21
+ attr_reader :url
22
+ # @return [String] The attachment proxy url.
23
+ attr_reader :proxy_url
24
+ # @return [Integer] The image height.
25
+ # @return [nil] If the attachment is not an image.
26
+ attr_reader :height
27
+ # @return [Integer] The image width.
28
+ # @return [nil] If the attachment is not an image.
29
+ attr_reader :width
30
+
31
+ # @!attribute [r] image?
32
+ # @return [Boolean] whether the file is an image.
33
+
34
+ # @!visibility private
35
+ def initialize(data)
36
+ @id = Snowflake.new(data[:id])
37
+ @filename = data[:filename]
38
+ @content_type = data[:content_type]
39
+ @size = data[:size]
40
+ @url = data[:url]
41
+ @proxy_url = data[:proxy_url]
42
+ @height = data[:height]
43
+ @width = data[:width]
44
+ end
45
+
46
+ def image?
47
+ @content_type.start_with? "image/"
48
+ end
49
+ end
50
+
51
+ #
52
+ # Represents a file to send as an attachment.
53
+ #
54
+ class File
55
+ # @return [#read] The IO of the file.
56
+ attr_accessor :io
57
+ # @return [String] The filename of the file. If not set, path or object_id of the IO is used.
58
+ attr_accessor :filename
59
+ # @return [String] The content type of the file. If not set, it is guessed from the filename.
60
+ attr_accessor :content_type
61
+
62
+ def initialize(io, filename = nil, content_type: nil)
63
+ @io = io
64
+ @filename = filename || (io.respond_to?(:path) ? io.path : io.object_id)
65
+ @content_type = content_type || MIME::Types.type_for(@filename)[0].to_s
66
+ @content_type = "application/octet-stream" if @content_type == ""
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Discorb
4
+ #
5
+ # Represents a flag.
6
+ # @abstract
7
+ #
8
+ class Flag
9
+ # @return [Hash{Symbol => Boolean}] the values of the flag.
10
+ attr_reader :values
11
+ # @return [Integer] the value of the flag.
12
+ attr_reader :value
13
+
14
+ @bits = {}
15
+
16
+ # Initialize the flag.
17
+ # @note This is usually called by the subclass.
18
+ #
19
+ # @param [Integer] value The value of the flag.
20
+ def initialize(value)
21
+ @value = value
22
+ @values = {}
23
+ self.class.bits.each_with_index do |(bn, bv), _i|
24
+ @values[bn] = value & (1 << bv) != 0
25
+ end
26
+ end
27
+
28
+ def method_missing(name, args = nil)
29
+ if @values.key?(name.to_s.delete_suffix("?").to_sym)
30
+ @values[name.to_s.delete_suffix("?").to_sym]
31
+ else
32
+ super
33
+ end
34
+ end
35
+
36
+ def respond_to_missing?(sym, include_private)
37
+ @values.key?(name.to_s.delete_suffix("?").to_sym) ? true : super
38
+ end
39
+
40
+ #
41
+ # Union of two flags.
42
+ #
43
+ # @param [Discorb::Flag] other The other flag.
44
+ #
45
+ # @return [Discorb::Flag] The union of the two flags.
46
+ #
47
+ def |(other)
48
+ self.class.new(@value | other.value)
49
+ end
50
+
51
+ alias + |
52
+
53
+ #
54
+ # Subtraction of two flags.
55
+ #
56
+ # @param [Discorb::Flag] other The other flag.
57
+ #
58
+ # @return [Discorb::Flag] The subtraction of the two flags.
59
+ #
60
+ def -(other)
61
+ self.class.new(@value & (@value ^ other.value))
62
+ end
63
+
64
+ #
65
+ # Intersection of two flags.
66
+ #
67
+ # @param [Discorb::Flag] other The other flag.
68
+ #
69
+ # @return [Discorb::Flag] The intersection of the two flags.
70
+ #
71
+ def &(other)
72
+ self.class.new(@value & other.value)
73
+ end
74
+
75
+ #
76
+ # XOR of two flags.
77
+ #
78
+ # @param [Discorb::Flag] other The other flag.
79
+ #
80
+ # @return [Discorb::Flag] The XOR of the two flags.
81
+ #
82
+ def ^(other)
83
+ self.class.new(@value ^ other.value)
84
+ end
85
+
86
+ #
87
+ # Negation of the flag.
88
+ #
89
+ # @return [Discorb::Flag] The negation of the flag.
90
+ #
91
+ def ~@
92
+ self.class.new(~@value)
93
+ end
94
+
95
+ class << self
96
+ # @return [Hash{Integer => Symbol}] the bits of the flag.
97
+ attr_reader :bits
98
+
99
+ #
100
+ # Max value of the flag.
101
+ #
102
+ # @return [Integer] the max value of the flag.
103
+ #
104
+ def max_value
105
+ 2 ** @bits.values.max - 1
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,967 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Discorb
4
+ #
5
+ # A module to handle gateway.
6
+ # This module is internal use only.
7
+ #
8
+ module GatewayHandler
9
+ #
10
+ # Represents an event.
11
+ #
12
+ class GatewayEvent
13
+ # @!visibility private
14
+ def initialize(data)
15
+ @data = data
16
+ end
17
+ end
18
+
19
+ #
20
+ # Represents a reaction event.
21
+ #
22
+ class ReactionEvent < GatewayEvent
23
+ # @return [Hash] The raw data of the event.
24
+ attr_reader :data
25
+ # @return [Discorb::Snowflake] The ID of the user who reacted.
26
+ attr_reader :user_id
27
+ alias member_id user_id
28
+ # @return [Discorb::Snowflake] The ID of the channel the message was sent in.
29
+ attr_reader :channel_id
30
+ # @return [Discorb::Snowflake] The ID of the message.
31
+ attr_reader :message_id
32
+ # @return [Discorb::Snowflake] The ID of the guild the message was sent in.
33
+ attr_reader :guild_id
34
+ # @macro client_cache
35
+ # @return [Discorb::User] The user who reacted.
36
+ attr_reader :user
37
+ # @macro client_cache
38
+ # @return [Discorb::Channel] The channel the message was sent in.
39
+ attr_reader :channel
40
+ # @macro client_cache
41
+ # @return [Discorb::Guild] The guild the message was sent in.
42
+ attr_reader :guild
43
+ # @macro client_cache
44
+ # @return [Discorb::Message] The message the reaction was sent in.
45
+ attr_reader :message
46
+ # @macro client_cache
47
+ # @return [Discorb::Member] The member who reacted.
48
+ attr_reader :member
49
+ # @return [Discorb::UnicodeEmoji, Discorb::PartialEmoji] The emoji that was reacted with.
50
+ attr_reader :emoji
51
+
52
+ # @!visibility private
53
+ def initialize(client, data)
54
+ @client = client
55
+ @data = data
56
+ if data.key?(:user_id)
57
+ @user_id = Snowflake.new(data[:user_id])
58
+ else
59
+ @member_data = data[:member]
60
+ end
61
+ @channel_id = Snowflake.new(data[:channel_id])
62
+ @message_id = Snowflake.new(data[:message_id])
63
+ @guild_id = Snowflake.new(data[:guild_id])
64
+ @guild = client.guilds[data[:guild_id]]
65
+ @channel = client.channels[data[:channel_id]] unless @guild.nil?
66
+
67
+ @user = client.users[data[:user_id]] if data.key?(:user_id)
68
+
69
+ unless @guild.nil?
70
+ @member = if data.key?(:member)
71
+ @guild.members[data[:member][:user][:id]] || Member.new(@client, @guild_id, data[:member][:user], data[:member])
72
+ else
73
+ @guild.members[data[:user_id]]
74
+ end
75
+ end
76
+
77
+ @message = client.messages[data[:message_id]]
78
+ @emoji = data[:emoji][:id].nil? ? UnicodeEmoji.new(data[:emoji][:name]) : PartialEmoji.new(data[:emoji])
79
+ end
80
+
81
+ # Fetch the message.
82
+ # If message is cached, it will be returned.
83
+ # @macro async
84
+ # @macro http
85
+ #
86
+ # @param [Boolean] force Whether to force fetching the message.
87
+ #
88
+ # @return [Discorb::Message] The message.
89
+ def fetch_message(force: false)
90
+ Async do |_task|
91
+ next @message if !force && @message
92
+
93
+ @message = @channel.fetch_message(@message_id).wait
94
+ end
95
+ end
96
+ end
97
+
98
+ #
99
+ # Represents a `MESSAGE_REACTION_REMOVE_ALL` event.
100
+ #
101
+ class ReactionRemoveAllEvent < GatewayEvent
102
+ # @return [Discorb::Snowflake] The ID of the channel the message was sent in.
103
+ attr_reader :channel_id
104
+ # @return [Discorb::Snowflake] The ID of the message.
105
+ attr_reader :message_id
106
+ # @return [Discorb::Snowflake] The ID of the guild the message was sent in.
107
+ attr_reader :guild_id
108
+ # @macro client_cache
109
+ # @return [Discorb::Channel] The channel the message was sent in.
110
+ attr_reader :channel
111
+ # @macro client_cache
112
+ # @return [Discorb::Guild] The guild the message was sent in.
113
+ attr_reader :guild
114
+ # @macro client_cache
115
+ # @return [Discorb::Message] The message the reaction was sent in.
116
+ attr_reader :message
117
+
118
+ # @!visibility private
119
+ def initialize(client, data)
120
+ @client = client
121
+ @data = data
122
+ @guild_id = Snowflake.new(data[:guild_id])
123
+ @channel_id = Snowflake.new(data[:channel_id])
124
+ @message_id = Snowflake.new(data[:message_id])
125
+ @guild = client.guilds[data[:guild_id]]
126
+ @channel = client.channels[data[:channel_id]]
127
+ @message = client.messages[data[:message_id]]
128
+ end
129
+
130
+ # Fetch the message.
131
+ # If message is cached, it will be returned.
132
+ # @macro async
133
+ # @macro http
134
+ #
135
+ # @param [Boolean] force Whether to force fetching the message.
136
+ #
137
+ # @return [Discorb::Message] The message.
138
+ def fetch_message(force: false)
139
+ Async do |_task|
140
+ next @message if !force && @message
141
+
142
+ @message = @channel.fetch_message(@message_id).wait
143
+ end
144
+ end
145
+ end
146
+
147
+ #
148
+ # Represents a `MESSAGE_REACTION_REMOVE_EMOJI` event.
149
+ #
150
+ class ReactionRemoveEmojiEvent < GatewayEvent
151
+ # @return [Discorb::Snowflake] The ID of the channel the message was sent in.
152
+ attr_reader :channel_id
153
+ # @return [Discorb::Snowflake] The ID of the message.
154
+ attr_reader :message_id
155
+ # @return [Discorb::Snowflake] The ID of the guild the message was sent in.
156
+ attr_reader :guild_id
157
+ # @macro client_cache
158
+ # @return [Discorb::Channel] The channel the message was sent in.
159
+ attr_reader :channel
160
+ # @macro client_cache
161
+ # @return [Discorb::Guild] The guild the message was sent in.
162
+ attr_reader :guild
163
+ # @macro client_cache
164
+ # @return [Discorb::Message] The message the reaction was sent in.
165
+ attr_reader :message
166
+ # @return [Discorb::UnicodeEmoji, Discorb::PartialEmoji] The emoji that was reacted with.
167
+ attr_reader :emoji
168
+
169
+ # @!visibility private
170
+ def initialize(client, data)
171
+ @client = client
172
+ @data = data
173
+ @guild_id = Snowflake.new(data[:guild_id])
174
+ @channel_id = Snowflake.new(data[:channel_id])
175
+ @message_id = Snowflake.new(data[:message_id])
176
+ @guild = client.guilds[data[:guild_id]]
177
+ @channel = client.channels[data[:channel_id]]
178
+ @message = client.messages[data[:message_id]]
179
+ @emoji = data[:emoji][:id].nil? ? DiscordEmoji.new(data[:emoji][:name]) : PartialEmoji.new(data[:emoji])
180
+ end
181
+
182
+ # Fetch the message.
183
+ # If message is cached, it will be returned.
184
+ # @macro async
185
+ # @macro http
186
+ #
187
+ # @param [Boolean] force Whether to force fetching the message.
188
+ #
189
+ # @return [Discorb::Message] The message.
190
+ def fetch_message(force: false)
191
+ Async do |_task|
192
+ next @message if !force && @message
193
+
194
+ @message = @channel.fetch_message(@message_id).wait
195
+ end
196
+ end
197
+ end
198
+
199
+ #
200
+ # Represents a `MESSAGE_UPDATE` event.
201
+ #
202
+
203
+ class MessageUpdateEvent < GatewayEvent
204
+ # @return [Discorb::Message] The message before update.
205
+ attr_reader :before
206
+ # @return [Discorb::Message] The message after update.
207
+ attr_reader :after
208
+ # @return [Discorb::Snowflake] The ID of the message.
209
+ attr_reader :id
210
+ # @return [Discorb::Snowflake] The ID of the channel the message was sent in.
211
+ attr_reader :channel_id
212
+ # @return [Discorb::Snowflake] The ID of the guild the message was sent in.
213
+ attr_reader :guild_id
214
+ # @return [String] The new content of the message.
215
+ attr_reader :content
216
+ # @return [Time] The time the message was edited.
217
+ attr_reader :timestamp
218
+ # @return [Boolean] Whether the message pings @everyone.
219
+ attr_reader :mention_everyone
220
+ # @macro client_cache
221
+ # @return [Array<Discorb::Role>] The roles mentioned in the message.
222
+ attr_reader :mention_roles
223
+ # @return [Array<Discorb::Attachment>] The attachments in the message.
224
+ attr_reader :attachments
225
+ # @return [Array<Discorb::Embed>] The embeds in the message.
226
+ attr_reader :embeds
227
+
228
+ # @!attribute [r] channel
229
+ # @macro client_cache
230
+ # @return [Discorb::Channel] The channel the message was sent in.
231
+ # @!attribute [r] guild
232
+ # @macro client_cache
233
+ # @return [Discorb::Guild] The guild the message was sent in.
234
+
235
+ def initialize(client, data, before, after)
236
+ @client = client
237
+ @data = data
238
+ @before = before
239
+ @after = after
240
+ @id = Snowflake.new(data[:id])
241
+ @channel_id = Snowflake.new(data[:channel_id])
242
+ @guild_id = Snowflake.new(data[:guild_id]) if data.key?(:guild_id)
243
+ @content = data[:content]
244
+ @timestamp = Time.iso8601(data[:edited_timestamp])
245
+ @mention_everyone = data[:mention_everyone]
246
+ @mention_roles = data[:mention_roles].map { |r| guild.roles[r] } if data.key?(:mention_roles)
247
+ @attachments = data[:attachments].map { |a| Attachment.new(a) } if data.key?(:attachments)
248
+ @embeds = data[:embeds] ? data[:embeds].map { |e| Embed.new(data: e) } : [] if data.key?(:embeds)
249
+ end
250
+
251
+ def channel
252
+ @client.channels[@channel_id]
253
+ end
254
+
255
+ def guild
256
+ @client.guilds[@guild_id]
257
+ end
258
+
259
+ # Fetch the message.
260
+ # @macro async
261
+ # @macro http
262
+ #
263
+ # @return [Discorb::Message] The message.
264
+ def fetch_message
265
+ Async do
266
+ channel.fetch_message(@id).wait
267
+ end
268
+ end
269
+ end
270
+
271
+ #
272
+ # Represents a message but it has only ID.
273
+ #
274
+ class UnknownDeleteBulkMessage < GatewayEvent
275
+ # @return [Discorb::Snowflake] The ID of the message.
276
+ attr_reader :id
277
+
278
+ # @!attribute [r] channel
279
+ # @macro client_cache
280
+ # @return [Discorb::Channel] The channel the message was sent in.
281
+ # @!attribute [r] guild
282
+ # @macro client_cache
283
+ # @return [Discorb::Guild] The guild the message was sent in.
284
+
285
+ # @!visibility private
286
+ def initialize(client, id, data)
287
+ @client = client
288
+ @id = id
289
+ @data = data
290
+ @channel_id = Snowflake.new(data[:channel_id])
291
+ @guild_id = Snowflake.new(data[:guild_id]) if data.key?(:guild_id)
292
+ end
293
+
294
+ def channel
295
+ @client.channels[@channel_id]
296
+ end
297
+
298
+ def guild
299
+ @client.guilds[@guild_id]
300
+ end
301
+ end
302
+
303
+ #
304
+ # Represents a `INVITE_DELETE` event.
305
+ #
306
+ class InviteDeleteEvent < GatewayEvent
307
+ # @return [String] The invite code.
308
+ attr_reader :code
309
+
310
+ # @!attribute [r] channel
311
+ # @macro client_cache
312
+ # @return [Discorb::Channel] The channel the message was sent in.
313
+ # @!attribute [r] guild
314
+ # @macro client_cache
315
+ # @return [Discorb::Guild] The guild the message was sent in.
316
+
317
+ # @!visibility private
318
+ def initialize(client, data)
319
+ @client = client
320
+ @data = data
321
+ @channel_id = Snowflake.new(data[:channel])
322
+ @guild_id = Snowflake.new(data[:guild_id])
323
+ @code = data[:code]
324
+ end
325
+
326
+ def channel
327
+ @client.channels[@channel_id]
328
+ end
329
+
330
+ def guild
331
+ @client.guilds[@guild_id]
332
+ end
333
+ end
334
+
335
+ class GuildIntegrationsUpdateEvent < GatewayEvent
336
+ def initialize(client, data)
337
+ @client = client
338
+ @data = data
339
+ @guild_id = Snowflake.new(data[:guild_id])
340
+ end
341
+
342
+ def guild
343
+ @client.guilds[@guild_id]
344
+ end
345
+ end
346
+
347
+ #
348
+ # Represents a `TYPING_START` event.
349
+ #
350
+ class TypingStartEvent < GatewayEvent
351
+ # @return [Discorb::Snowflake] The ID of the channel the user is typing in.
352
+ attr_reader :user_id
353
+
354
+ # @!attribute [r] channel
355
+ # @macro client_cache
356
+ # @return [Discorb::Channel] The channel the user is typing in.
357
+ # @!attribute [r] guild
358
+ # @macro client_cache
359
+ # @return [Discorb::Guild] The guild the user is typing in.
360
+ # @!attribute [r] user
361
+ # @macro client_cache
362
+ # @return [Discorb::User] The user that is typing.
363
+
364
+ # @!visibility private
365
+ def initialize(client, data)
366
+ @client = client
367
+ @data = data
368
+ @channel_id = Snowflake.new(data[:channel_id])
369
+ @guild_id = Snowflake.new(data[:guild_id]) if data.key?(:guild_id)
370
+ @user_id = Snowflake.new(data[:user_id])
371
+ @timestamp = Time.at(data[:timestamp])
372
+ @member = guild.members[@user_id] || Member.new(@client, @guild_id, @client.users[@user_id].instance_variable_get(:@data), data[:member]) if guild
373
+ end
374
+
375
+ def user
376
+ @client.users[@user_id]
377
+ end
378
+
379
+ def channel
380
+ @client.channels[@channel_id]
381
+ end
382
+
383
+ def guild
384
+ @client.guilds[@guild_id]
385
+ end
386
+ end
387
+
388
+ #
389
+ # Represents a message pin event.
390
+ #
391
+ class MessagePinEvent < GatewayEvent
392
+ # @return [Discorb::Message] The message that was pinned.
393
+ attr_reader :message
394
+ # @return [:pinned, :unpinned] The type of event.
395
+ attr_reader :type
396
+
397
+ # @!attribute [r] pinned?
398
+ # @return [Boolean] Whether the message was pinned.
399
+ # @!attribute [r] unpinned?
400
+ # @return [Boolean] Whether the message was unpinned.
401
+
402
+ def initialize(client, data, message)
403
+ @client = client
404
+ @data = data
405
+ @message = message
406
+ @type = if message.nil?
407
+ :unknown
408
+ elsif @message.pinned?
409
+ :pinned
410
+ else
411
+ :unpinned
412
+ end
413
+ end
414
+
415
+ def pinned?
416
+ @type == :pinned
417
+ end
418
+
419
+ def unpinned?
420
+ @type = :unpinned
421
+ end
422
+ end
423
+
424
+ #
425
+ # Represents a `WEBHOOKS_UPDATE` event.
426
+ #
427
+ class WebhooksUpdateEvent < GatewayEvent
428
+ # @!attribute [r] channel
429
+ # @macro client_cache
430
+ # @return [Discorb::Channel] The channel where the webhook was updated.
431
+ # @!attribute [r] guild
432
+ # @macro client_cache
433
+ # @return [Discorb::Guild] The guild where the webhook was updated.
434
+
435
+ # @!visibility private
436
+ def initialize(client, data)
437
+ @client = client
438
+ @data = data
439
+ @guild_id = Snowflake.new(data[:guild_id])
440
+ @channel_id = Snowflake.new(data[:channel_id])
441
+ end
442
+
443
+ def guild
444
+ @client.guilds[@guild_id]
445
+ end
446
+
447
+ def channel
448
+ @client.channels[@channel_id]
449
+ end
450
+ end
451
+
452
+ private
453
+
454
+ def connect_gateway(first)
455
+ @log.info "Connecting to gateway."
456
+ Async do |_task|
457
+ @internet = Internet.new(self) if first
458
+ @first = first
459
+ _, gateway_response = @internet.get("/gateway").wait
460
+ gateway_url = gateway_response[:url]
461
+ endpoint = Async::HTTP::Endpoint.parse("#{gateway_url}?v=9&encoding=json",
462
+ alpn_protocols: Async::HTTP::Protocol::HTTP11.names)
463
+ begin
464
+ Async::WebSocket::Client.connect(endpoint, headers: [["User-Agent", Discorb::USER_AGENT]]) do |connection|
465
+ @connection = connection
466
+ while (message = @connection.read)
467
+ handle_gateway(message)
468
+ end
469
+ end
470
+ rescue Protocol::WebSocket::ClosedError => e
471
+ case e.message
472
+ when "Authentication failed."
473
+ @tasks.map(&:stop)
474
+ raise ClientError.new("Authentication failed."), cause: nil
475
+ when "Discord WebSocket requesting client reconnect."
476
+ @log.info "Discord WebSocket requesting client reconnect"
477
+ @tasks.map(&:stop)
478
+ connect_gateway(false)
479
+ end
480
+ rescue EOFError, Async::Wrapper::Cancelled
481
+ connect_gateway(false)
482
+ end
483
+ end
484
+ end
485
+
486
+ def send_gateway(opcode, **value)
487
+ @connection.write({ op: opcode, d: value })
488
+ @connection.flush
489
+ @log.debug "Sent message with opcode #{opcode}: #{value.to_json.gsub(@token, "[Token]")}"
490
+ end
491
+
492
+ def handle_gateway(payload)
493
+ Async do |task|
494
+ data = payload[:d]
495
+ @last_s = payload[:s] if payload[:s]
496
+ @log.debug "Received message with opcode #{payload[:op]} from gateway: #{data}"
497
+ case payload[:op]
498
+ when 10
499
+ @heartbeat_interval = data[:heartbeat_interval]
500
+ @tasks << handle_heartbeat(@heartbeat_interval)
501
+ if @first
502
+ payload = {
503
+ token: @token,
504
+ intents: @intents.value,
505
+ compress: false,
506
+ properties: { "$os" => RUBY_PLATFORM, "$browser" => "discorb", "$device" => "discorb" },
507
+ }
508
+ payload[:presence] = @identify_presence if @identify_presence
509
+ send_gateway(2, **payload)
510
+ else
511
+ payload = {
512
+ token: @token,
513
+ session_id: @session_id,
514
+ seq: @last_s,
515
+ }
516
+ send_gateway(6, **payload)
517
+ end
518
+ when 9
519
+ @log.warn "Received opcode 9, closed connection"
520
+ @tasks.map(&:stop)
521
+ if data
522
+ @log.info "Connection is resumable, reconnecting"
523
+ @connection.close
524
+ connect_gateway(false)
525
+ else
526
+ @log.info "Connection is not resumable, reconnecting with opcode 2"
527
+ task.sleep(2)
528
+ @connection.close
529
+ connect_gateway(true)
530
+ end
531
+ when 0
532
+ handle_event(payload[:t], data)
533
+ end
534
+ end
535
+ end
536
+
537
+ def handle_heartbeat(interval)
538
+ Async do |task|
539
+ task.sleep((interval / 1000.0 - 1) * rand)
540
+ loop do
541
+ @connection.write({ op: 1, d: @last_s })
542
+ @connection.flush
543
+ @log.debug "Sent opcode 1."
544
+ @log.debug "Waiting for heartbeat."
545
+ sleep(interval / 1000.0 - 1)
546
+ end
547
+ end
548
+ end
549
+
550
+ def handle_event(event_name, data)
551
+ return @log.debug "Client isn't ready; event #{event_name} wasn't handled" if @wait_until_ready && !@ready && !%w[READY GUILD_CREATE].include?(event_name)
552
+
553
+ dispatch(:event_receive, event_name, data)
554
+ @log.debug "Handling event #{event_name}"
555
+ case event_name
556
+ when "READY"
557
+ @api_version = data[:v]
558
+ @session_id = data[:session_id]
559
+ @user = ClientUser.new(self, data[:user])
560
+ @uncached_guilds = data[:guilds].map { |g| g[:id] }
561
+ when "GUILD_CREATE"
562
+ if @uncached_guilds.include?(data[:id])
563
+ Guild.new(self, data, true)
564
+ @uncached_guilds.delete(data[:id])
565
+ if @uncached_guilds == []
566
+ @ready = true
567
+ dispatch(:ready)
568
+ @log.info("Guilds were cached")
569
+ end
570
+ elsif @guilds.has?(data[:id])
571
+ @guilds[data[:id]].send(:_set_data, data)
572
+ dispatch(:guild_available, guild)
573
+ else
574
+ guild = Guild.new(self, data, true)
575
+ dispatch(:guild_join, guild)
576
+ end
577
+ dispatch(:guild_create, @guilds[data[:id]])
578
+ when "MESSAGE_CREATE"
579
+ message = Message.new(self, data)
580
+ dispatch(:message, message)
581
+ when "GUILD_UPDATE"
582
+ if @guilds.has?(data[:id])
583
+ current = @guilds[data[:id]]
584
+ before = Guild.new(self, current.instance_variable_get(:@data).merge(no_cache: true), false)
585
+ current.send(:_set_data, data, false)
586
+ dispatch(:guild_update, before, current)
587
+ else
588
+ @log.warn "Unknown guild id #{data[:id]}, ignoring"
589
+ end
590
+ when "GUILD_DELETE"
591
+ return @log.warn "Unknown guild id #{data[:id]}, ignoring" unless (guild = @guilds.delete(data[:id]))
592
+
593
+ dispatch(:guild_delete, guild)
594
+ if guild.has?(:unavailable)
595
+ dispatch(:guild_destroy, guild)
596
+ else
597
+ dispatch(:guild_leave, guild)
598
+ end
599
+ when "GUILD_ROLE_CREATE"
600
+ return @log.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
601
+
602
+ nr = Role.new(@client, guild, data[:role])
603
+ guild.roles[data[:role][:id]] = nr
604
+ dispatch(:role_create, nr)
605
+ when "GUILD_ROLE_UPDATE"
606
+ return @log.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
607
+ return @log.warn "Unknown role id #{data[:role][:id]}, ignoring" unless guild.roles.has?(data[:role][:id])
608
+
609
+ current = guild.roles[data[:role][:id]]
610
+ before = Role.new(@client, guild, current.instance_variable_get(:@data).update({ no_cache: true }))
611
+ current.send(:_set_data, data[:role])
612
+ dispatch(:role_update, before, current)
613
+ when "GUILD_ROLE_DELETE"
614
+ return @log.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
615
+ return @log.warn "Unknown role id #{data[:role_id]}, ignoring" unless (role = guild.roles.delete(data[:role_id]))
616
+
617
+ dispatch(:role_delete, role)
618
+ when "CHANNEL_CREATE"
619
+ return @log.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
620
+
621
+ nc = Channel.make_channel(self, data)
622
+ guild.channels[data[:id]] = nc
623
+
624
+ dispatch(:channel_create, nc)
625
+ when "CHANNEL_UPDATE"
626
+ return @log.warn "Unknown channel id #{data[:id]}, ignoring" unless (current = @channels[data[:id]])
627
+
628
+ before = Channel.make_channel(self, current.instance_variable_get(:@data), no_cache: true)
629
+ current.send(:_set_data, data)
630
+ dispatch(:channel_update, before, current)
631
+ when "CHANNEL_DELETE"
632
+ return @log.warn "Unknown channel id #{data[:id]}, ignoring" unless (channel = @channels.delete(data[:id]))
633
+
634
+ @guilds[data[:guild_id]]&.channels&.delete(data[:id])
635
+ dispatch(:channel_delete, channel)
636
+ when "CHANNEL_PINS_UPDATE"
637
+ nil # do in MESSAGE_UPDATE
638
+ when "THREAD_CREATE"
639
+ thread = Channel.make_channel(self, data)
640
+
641
+ dispatch(:thread_create, thread)
642
+ if data.key?(:member)
643
+ dispatch(:thread_join, thread)
644
+ else
645
+ dispatch(:thread_new, thread)
646
+ end
647
+ when "THREAD_UPDATE"
648
+ return @log.warn "Unknown thread id #{data[:id]}, ignoring" unless (thread = @channels[data[:id]])
649
+
650
+ before = Channel.make_channel(self, thread.instance_variable_get(:@data), no_cache: true)
651
+ thread.send(:_set_data, data)
652
+ dispatch(:thread_update, before, thread)
653
+ when "THREAD_DELETE"
654
+ return @log.warn "Unknown thread id #{data[:id]}, ignoring" unless (thread = @channels.delete(data[:id]))
655
+
656
+ @guilds[data[:guild_id]]&.channels&.delete(data[:id])
657
+ dispatch(:thread_delete, thread)
658
+ when "THREAD_LIST_SYNC"
659
+ data[:threads].each do |raw_thread|
660
+ thread = Channel.make_channel(self, raw_thread.merge({ member: raw_thread[:members].find { |m| m[:id] == raw_thread[:id] } }))
661
+ @channels[thread.id] = thread
662
+ end
663
+ when "THREAD_MEMBER_UPDATE"
664
+ return @log.warn "Unknown thread id #{data[:id]}, ignoring" unless (thread = @channels[data[:id]])
665
+
666
+ if (member = thread.members[data[:id]])
667
+ old = ThreadChannel::Member.new(self, member.instance_variable_get(:@data))
668
+ member._set_data(data)
669
+ else
670
+ old = nil
671
+ member = ThreadChannel::Member.new(self, data)
672
+ thread.members[data[:user_id]] = member
673
+ end
674
+ dispatch(:thread_member_update, thread, old, member)
675
+ when "THREAD_MEMBERS_UPDATE"
676
+ return @log.warn "Unknown thread id #{data[:id]}, ignoring" unless (thread = @channels[data[:id]])
677
+
678
+ thread.instance_variable_set(:@member_count, data[:member_count])
679
+ members = []
680
+ (data[:added_members] || []).each do |raw_member|
681
+ member = ThreadChannel::Member.new(self, raw_member)
682
+ thread.members[member.id] = member
683
+ members << member
684
+ end
685
+ removed_members = []
686
+ (data[:removed_member_ids] || []).each do |id|
687
+ removed_members << thread.members.delete(id)
688
+ end
689
+ dispatch(:thread_members_update, thread, members, removed_members)
690
+ when "STAGE_INSTANCE_CREATE"
691
+ instance = StageInstance.new(self, data)
692
+ dispatch(:stage_instance_create, instance)
693
+ when "STAGE_INSTANCE_UPDATE"
694
+ return @log.warn "Unknown channel id #{data[:channel_id]} , ignoring" unless (channel = @channels[data[:channel_id]])
695
+ return @log.warn "Unknown stage instance id #{data[:id]}, ignoring" unless (instance = channel.stage_instances[data[:id]])
696
+
697
+ old = StageInstance.new(self, instance.instance_variable_get(:@data), no_cache: true)
698
+ current.send(:_set_data, data)
699
+ dispatch(:stage_instance_update, old, current)
700
+ when "STAGE_INSTANCE_DELETE"
701
+ return @log.warn "Unknown channel id #{data[:channel_id]} , ignoring" unless (channel = @channels[data[:channel_id]])
702
+ return @log.warn "Unknown stage instance id #{data[:id]}, ignoring" unless (instance = channel.stage_instances.delete(data[:id]))
703
+
704
+ dispatch(:stage_instance_delete, instance)
705
+ when "GUILD_MEMBER_ADD"
706
+ return @log.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
707
+
708
+ nm = Member.new(self, data[:guild_id], data[:user].update({ no_cache: true }), data)
709
+ guild.members[nm.id] = nm
710
+ dispatch(:member_add, nm)
711
+ when "GUILD_MEMBER_UPDATE"
712
+ return @log.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
713
+ return @log.warn "Unknown member id #{data[:id]}, ignoring" unless (nm = guild.members[data[:id]])
714
+
715
+ old = Member.new(self, data[:guild_id], data[:user], data.update({ no_cache: true }))
716
+ nm.send(:_set_data, data[:user], data)
717
+ dispatch(:member_update, old, nm)
718
+ when "GUILD_MEMBER_REMOVE"
719
+ return @log.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
720
+ return @log.warn "Unknown member id #{data[:id]}, ignoring" unless (member = guild.members.delete(data[:id]))
721
+
722
+ dispatch(:member_remove, member)
723
+ when "GUILD_BAN_ADD"
724
+ return @log.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
725
+
726
+ user = if @users.has? data[:user][:id]
727
+ @users[data[:user][:id]]
728
+ else
729
+ User.new(self, data[:user].update({ no_cache: true }))
730
+ end
731
+
732
+ dispatch(:guild_ban_add, guild, user)
733
+ when "GUILD_BAN_REMOVE"
734
+ return @log.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
735
+
736
+ user = if @users.has? data[:user][:id]
737
+ @users[data[:user][:id]]
738
+ else
739
+ User.new(self, data[:user].update({ no_cache: true }))
740
+ end
741
+
742
+ dispatch(:guild_ban_remove, guild, user)
743
+ when "GUILD_EMOJIS_UPDATE"
744
+ return @log.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
745
+
746
+ before_emojis = guild.emojis.values.map(&:id).to_set
747
+ data[:emojis].each do |emoji|
748
+ guild.emojis[emoji[:id]] = CustomEmoji.new(self, guild, emoji)
749
+ end
750
+ deleted_emojis = before_emojis - guild.emojis.values.map(&:id).to_set
751
+ deleted_emojis.each do |emoji|
752
+ guild.emojis.delete(emoji)
753
+ end
754
+ when "GUILD_INTEGRATIONS_UPDATE"
755
+ # dispatch(:guild_integrations_update, GuildIntegrationsUpdateEvent.new(self, data))
756
+ # Currently not implemented
757
+ when "INTEGRATION_CREATE"
758
+ dispatch(:integration_create, Integration.new(self, data, data[:guild_id]))
759
+ when "INTEGRATION_UPDATE"
760
+ return @log.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
761
+ return @log.warn "Unknown integration id #{data[:id]}, ignoring" unless (integration = guild.integrations[data[:id]])
762
+
763
+ before = Integration.new(self, integration.instance_variable_get(:@data), data[:guild_id], no_cache: true)
764
+ integration.send(:_set_data, data)
765
+ dispatch(:integration_update, before, integration)
766
+ when "INTEGRATION_DELETE"
767
+ return @log.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
768
+ return @log.warn "Unknown integration id #{data[:id]}, ignoring" unless (integration = guild.integrations.delete(data[:id]))
769
+
770
+ dispatch(:integration_delete, integration)
771
+ when "WEBHOOKS_UPDATE"
772
+ dispatch(:webhooks_update, WebhooksUpdateEvent.new(self, data))
773
+ when "INVITE_CREATE"
774
+ dispatch(:invite_create, Invite.new(self, data, true))
775
+ when "INVITE_DELETE"
776
+ dispatch(:invite_delete, InviteDeleteEvent.new(self, data))
777
+ when "VOICE_STATE_UPDATE"
778
+ return @log.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
779
+
780
+ current = guild.voice_states[data[:user_id]]
781
+ if current.nil?
782
+ old = nil
783
+ current = VoiceState.new(self, data)
784
+ guild.voice_states[data[:user_id]] = current
785
+ else
786
+ old = VoiceState.new(self, current.instance_variable_get(:@data))
787
+ current.send(:_set_data, data)
788
+ end
789
+ dispatch(:voice_state_update, old, current)
790
+ if old&.channel != current&.channel
791
+ dispatch(:voice_channel_update, old, current)
792
+ case [old&.channel, current&.channel]
793
+ in [nil, _]
794
+ dispatch(:voice_channel_connect, current)
795
+ in [_, nil]
796
+ dispatch(:voice_channel_disconnect, old)
797
+ in _
798
+ dispatch(:voice_channel_move, old, current)
799
+ end
800
+ end
801
+ if old&.mute? != current&.mute?
802
+ dispatch(:voice_mute_update, old, current)
803
+ case [old&.mute?, current&.mute?]
804
+ when [false, true]
805
+ dispatch(:voice_mute_enable, current)
806
+ when [true, false]
807
+ dispatch(:voice_mute_disable, old)
808
+ end
809
+ end
810
+ if old&.deaf? != current&.deaf?
811
+ dispatch(:voice_deaf_update, old, current)
812
+ case [old&.deaf?, current&.deaf?]
813
+ when [false, true]
814
+ dispatch(:voice_deaf_enable, current)
815
+ when [true, false]
816
+ dispatch(:voice_deaf_disable, old)
817
+ end
818
+ end
819
+ if old&.self_mute? != current&.self_mute?
820
+ dispatch(:voice_self_mute_update, old, current)
821
+ case [old&.self_mute?, current&.self_mute?]
822
+ when [false, true]
823
+ dispatch(:voice_self_mute_enable, current)
824
+ when [true, false]
825
+ dispatch(:voice_self_mute_disable, old)
826
+ end
827
+ end
828
+ if old&.self_deaf? != current&.self_deaf?
829
+ dispatch(:voice_self_deaf_update, old, current)
830
+ case [old&.self_deaf?, current&.self_deaf?]
831
+ when [false, true]
832
+ dispatch(:voice_self_deaf_enable, current)
833
+ when [true, false]
834
+ dispatch(:voice_self_deaf_disable, old)
835
+ end
836
+ end
837
+ if old&.server_mute? != current&.server_mute?
838
+ dispatch(:voice_server_mute_update, old, current)
839
+ case [old&.server_mute?, current&.server_mute?]
840
+ when [false, true]
841
+ dispatch(:voice_server_mute_enable, current)
842
+ when [true, false]
843
+ dispatch(:voice_server_mute_disable, old)
844
+ end
845
+ end
846
+ if old&.server_deaf? != current&.server_deaf?
847
+ dispatch(:voice_server_deaf_update, old, current)
848
+ case [old&.server_deaf?, current&.server_deaf?]
849
+ when [false, true]
850
+ dispatch(:voice_server_deaf_enable, current)
851
+ when [true, false]
852
+ dispatch(:voice_server_deaf_disable, old)
853
+ end
854
+ end
855
+ if old&.video? != current&.video?
856
+ dispatch(:voice_video_update, old, current)
857
+ case [old&.video?, current&.video?]
858
+ when [false, true]
859
+ dispatch(:voice_video_start, current)
860
+ when [true, false]
861
+ dispatch(:voice_video_end, old)
862
+ end
863
+ end
864
+ if old&.stream? != current&.stream?
865
+ dispatch(:voice_stream_update, old, current)
866
+ case [old&.stream?, current&.stream?]
867
+ when [false, true]
868
+ dispatch(:voice_stream_start, current)
869
+ when [true, false]
870
+ dispatch(:voice_stream_end, old)
871
+ end
872
+ end
873
+ when "PRESENCE_UPDATE"
874
+ return @log.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
875
+
876
+ guild.presences[data[:user][:id]] = Presence.new(self, data)
877
+ when "MESSAGE_UPDATE"
878
+ if (message = @messages[data[:id]])
879
+ before = Message.new(self, message.instance_variable_get(:@data), no_cache: true)
880
+ message.send(:_set_data, message.instance_variable_get(:@data).merge(data))
881
+ else
882
+ @log.info "Uncached message ID #{data[:id]}, ignoring"
883
+ before = nil
884
+ message = nil
885
+ end
886
+ if data[:edited_timestamp].nil?
887
+ if message.nil?
888
+ nil
889
+ elsif message.pinned?
890
+ message.instance_variable_set(:@pinned, false)
891
+ else
892
+ message.instance_variable_set(:@pinned, true)
893
+ end
894
+ dispatch(:message_pin_update, MessagePinEvent.new(self, data, message))
895
+ else
896
+ dispatch(:message_update, MessageUpdateEvent.new(self, data, before, current))
897
+ end
898
+ when "MESSAGE_DELETE"
899
+ message.instance_variable_set(:@deleted, true) if (message = @messages[data[:id]])
900
+
901
+ dispatch(:message_delete_id, Snowflake.new(data[:id]), channels[data[:channel_id]], data[:guild_id] && guilds[data[:guild_id]])
902
+ dispatch(:message_delete, message, channels[data[:channel_id]], data[:guild_id] && guilds[data[:guild_id]])
903
+ when "MESSAGE_DELETE_BULK"
904
+ messages = []
905
+ data[:ids].each do |id|
906
+ if (message = @messages[id])
907
+ message.instance_variable_set(:@deleted, true)
908
+ messages.push(message)
909
+ else
910
+ messages.push(UnknownDeleteBulkMessage.new(self, id))
911
+ end
912
+ end
913
+ dispatch(:message_delete_bulk, messages)
914
+ when "MESSAGE_REACTION_ADD"
915
+ if (target_message = @messages[data[:message_id]])
916
+ if (target_reaction = target_message.reactions.find do |r|
917
+ r.emoji.is_a?(UnicodeEmoji) ? r.emoji.value == data[:emoji][:name] : r.emoji.id == data[:emoji][:id]
918
+ end)
919
+ target_reaction.set_instance_variable(:@count, target_reaction.count + 1)
920
+ else
921
+ target_message.reactions << Reaction.new(
922
+ self,
923
+ {
924
+ count: 1,
925
+ me: @user.id == data[:user_id],
926
+ emoji: data[:emoji],
927
+ }
928
+ )
929
+ end
930
+ end
931
+ dispatch(:reaction_add, ReactionEvent.new(self, data))
932
+ when "MESSAGE_REACTION_REMOVE"
933
+ if (target_message = @messages[data[:message_id]]) &&
934
+ (target_reaction = target_message.reactions.find do |r|
935
+ data[:emoji][:id].nil? ? r.emoji.name == data[:emoji][:name] : r.emoji.id == data[:emoji][:id]
936
+ end)
937
+ target_reaction.set_instance_variable(:@count, target_reaction.count - 1)
938
+ target_message.reactions.delete(target_reaction) if target_reaction.count.zero?
939
+ end
940
+ dispatch(:reaction_remove, ReactionEvent.new(self, data))
941
+ when "MESSAGE_REACTION_REMOVE_ALL"
942
+ if (target_message = @messages[data[:message_id]])
943
+ target_message.reactions = []
944
+ end
945
+ dispatch(:reaction_remove_all, ReactionRemoveAllEvent.new(self, data))
946
+ when "MESSAGE_REACTION_REMOVE_EMOJI"
947
+ if (target_message = @messages[data[:message_id]]) &&
948
+ (target_reaction = target_message.reactions.find { |r| data[:emoji][:id].nil? ? r.name == data[:emoji][:name] : r.id == data[:emoji][:id] })
949
+ target_message.reactions.delete(target_reaction)
950
+ end
951
+ dispatch(:reaction_remove_emoji, ReactionRemoveEmojiEvent.new(data))
952
+ when "TYPING_START"
953
+ dispatch(:typing_start, TypingStartEvent.new(self, data))
954
+ when "INTERACTION_CREATE"
955
+ interaction = Interaction.make_interaction(self, data)
956
+ dispatch(:integration_create, interaction)
957
+
958
+ dispatch(interaction.class.event_name, interaction)
959
+ when "RESUMED"
960
+ @log.info("Successfully resumed connection")
961
+ dispatch(:resumed)
962
+ else
963
+ @log.warn "Unknown event: #{event_name}\n#{data.inspect}"
964
+ end
965
+ end
966
+ end
967
+ end