discorb 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +56 -0
- data/.yardopts +6 -0
- data/Changelog.md +5 -0
- data/Gemfile +23 -0
- data/Gemfile.lock +70 -0
- data/LICENSE.txt +21 -0
- data/README.md +53 -0
- data/Rakefile +46 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/discorb.gemspec +37 -0
- data/docs/Examples.md +26 -0
- data/docs/events.md +480 -0
- data/docs/voice_events.md +283 -0
- data/examples/components/authorization_button.rb +43 -0
- data/examples/components/select_menu.rb +61 -0
- data/examples/extension/main.rb +12 -0
- data/examples/extension/message_expander.rb +41 -0
- data/examples/simple/eval.rb +32 -0
- data/examples/simple/ping_pong.rb +16 -0
- data/examples/simple/rolepanel.rb +65 -0
- data/examples/simple/wait_for_message.rb +30 -0
- data/lib/discorb/application.rb +157 -0
- data/lib/discorb/asset.rb +57 -0
- data/lib/discorb/audit_logs.rb +323 -0
- data/lib/discorb/channel.rb +1101 -0
- data/lib/discorb/client.rb +363 -0
- data/lib/discorb/color.rb +173 -0
- data/lib/discorb/common.rb +123 -0
- data/lib/discorb/components.rb +290 -0
- data/lib/discorb/dictionary.rb +119 -0
- data/lib/discorb/embed.rb +345 -0
- data/lib/discorb/emoji.rb +218 -0
- data/lib/discorb/emoji_table.rb +3799 -0
- data/lib/discorb/error.rb +98 -0
- data/lib/discorb/event.rb +35 -0
- data/lib/discorb/extend.rb +18 -0
- data/lib/discorb/extension.rb +54 -0
- data/lib/discorb/file.rb +69 -0
- data/lib/discorb/flag.rb +109 -0
- data/lib/discorb/gateway.rb +967 -0
- data/lib/discorb/gateway_requests.rb +47 -0
- data/lib/discorb/guild.rb +1244 -0
- data/lib/discorb/guild_template.rb +211 -0
- data/lib/discorb/image.rb +43 -0
- data/lib/discorb/integration.rb +111 -0
- data/lib/discorb/intents.rb +137 -0
- data/lib/discorb/interaction.rb +333 -0
- data/lib/discorb/internet.rb +285 -0
- data/lib/discorb/invite.rb +145 -0
- data/lib/discorb/log.rb +70 -0
- data/lib/discorb/member.rb +232 -0
- data/lib/discorb/message.rb +583 -0
- data/lib/discorb/modules.rb +138 -0
- data/lib/discorb/permission.rb +270 -0
- data/lib/discorb/presence.rb +308 -0
- data/lib/discorb/reaction.rb +48 -0
- data/lib/discorb/role.rb +189 -0
- data/lib/discorb/sticker.rb +157 -0
- data/lib/discorb/user.rb +163 -0
- data/lib/discorb/utils.rb +16 -0
- data/lib/discorb/voice_state.rb +251 -0
- data/lib/discorb/webhook.rb +420 -0
- data/lib/discorb.rb +51 -0
- 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
|
data/lib/discorb/file.rb
ADDED
@@ -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
|
data/lib/discorb/flag.rb
ADDED
@@ -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
|