discordrb 3.5.0 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/.devcontainer/Dockerfile +13 -0
  3. data/.devcontainer/devcontainer.json +29 -0
  4. data/.devcontainer/postcreate.sh +4 -0
  5. data/.github/workflows/ci.yml +78 -0
  6. data/.github/workflows/codeql.yml +3 -3
  7. data/.github/workflows/deploy.yml +54 -0
  8. data/.github/workflows/release.yml +45 -0
  9. data/.rubocop.yml +52 -2
  10. data/CHANGELOG.md +95 -0
  11. data/README.md +5 -5
  12. data/discordrb-webhooks.gemspec +1 -1
  13. data/discordrb.gemspec +16 -11
  14. data/lib/discordrb/api/application.rb +84 -8
  15. data/lib/discordrb/api/channel.rb +51 -13
  16. data/lib/discordrb/api/interaction.rb +15 -6
  17. data/lib/discordrb/api/invite.rb +1 -1
  18. data/lib/discordrb/api/server.rb +96 -60
  19. data/lib/discordrb/api/user.rb +12 -2
  20. data/lib/discordrb/api/webhook.rb +20 -5
  21. data/lib/discordrb/api.rb +16 -20
  22. data/lib/discordrb/bot.rb +139 -53
  23. data/lib/discordrb/cache.rb +15 -1
  24. data/lib/discordrb/commands/command_bot.rb +7 -17
  25. data/lib/discordrb/commands/parser.rb +7 -7
  26. data/lib/discordrb/container.rb +46 -0
  27. data/lib/discordrb/data/activity.rb +1 -1
  28. data/lib/discordrb/data/application.rb +1 -0
  29. data/lib/discordrb/data/attachment.rb +23 -3
  30. data/lib/discordrb/data/avatar_decoration.rb +26 -0
  31. data/lib/discordrb/data/call.rb +22 -0
  32. data/lib/discordrb/data/channel.rb +140 -15
  33. data/lib/discordrb/data/collectibles.rb +45 -0
  34. data/lib/discordrb/data/embed.rb +10 -3
  35. data/lib/discordrb/data/emoji.rb +20 -1
  36. data/lib/discordrb/data/integration.rb +3 -0
  37. data/lib/discordrb/data/interaction.rb +164 -27
  38. data/lib/discordrb/data/member.rb +145 -28
  39. data/lib/discordrb/data/message.rb +198 -51
  40. data/lib/discordrb/data/overwrite.rb +2 -0
  41. data/lib/discordrb/data/primary_server.rb +60 -0
  42. data/lib/discordrb/data/profile.rb +2 -7
  43. data/lib/discordrb/data/reaction.rb +2 -1
  44. data/lib/discordrb/data/recipient.rb +1 -1
  45. data/lib/discordrb/data/role.rb +151 -22
  46. data/lib/discordrb/data/server.rb +115 -41
  47. data/lib/discordrb/data/server_preview.rb +68 -0
  48. data/lib/discordrb/data/snapshot.rb +110 -0
  49. data/lib/discordrb/data/user.rb +68 -8
  50. data/lib/discordrb/data/voice_region.rb +1 -0
  51. data/lib/discordrb/data/webhook.rb +2 -5
  52. data/lib/discordrb/data.rb +6 -0
  53. data/lib/discordrb/errors.rb +5 -2
  54. data/lib/discordrb/events/await.rb +1 -1
  55. data/lib/discordrb/events/channels.rb +37 -0
  56. data/lib/discordrb/events/generic.rb +2 -0
  57. data/lib/discordrb/events/guilds.rb +6 -1
  58. data/lib/discordrb/events/interactions.rb +135 -42
  59. data/lib/discordrb/events/invites.rb +2 -0
  60. data/lib/discordrb/events/members.rb +19 -2
  61. data/lib/discordrb/events/message.rb +39 -8
  62. data/lib/discordrb/events/presence.rb +2 -0
  63. data/lib/discordrb/events/raw.rb +1 -0
  64. data/lib/discordrb/events/reactions.rb +2 -0
  65. data/lib/discordrb/events/roles.rb +2 -0
  66. data/lib/discordrb/events/threads.rb +10 -6
  67. data/lib/discordrb/events/typing.rb +1 -0
  68. data/lib/discordrb/events/voice_server_update.rb +1 -0
  69. data/lib/discordrb/events/voice_state_update.rb +1 -0
  70. data/lib/discordrb/events/webhooks.rb +1 -0
  71. data/lib/discordrb/gateway.rb +29 -13
  72. data/lib/discordrb/paginator.rb +3 -3
  73. data/lib/discordrb/permissions.rb +54 -43
  74. data/lib/discordrb/version.rb +1 -1
  75. data/lib/discordrb/websocket.rb +0 -10
  76. data/lib/discordrb.rb +17 -1
  77. metadata +53 -25
  78. data/.circleci/config.yml +0 -152
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Discordrb
4
+ # A partial and immutable copy of a message that has been forwarded.
5
+ class Snapshot
6
+ # @return [Integer] the message type of the message snapshot.
7
+ attr_reader :type
8
+
9
+ # @return [String] the text content of the message snapshot.
10
+ attr_reader :content
11
+
12
+ # @return [Array<Embed>] the embeds attached to the message snapshot.
13
+ attr_reader :embeds
14
+
15
+ # @return [Array<Attachment>] the files attached to the message snapshot.
16
+ attr_reader :attachments
17
+
18
+ # @return [Time] the time at which the message snapshot was created.
19
+ attr_reader :created_at
20
+
21
+ # @return [Time, nil] the time at which the message snapshot was edited.
22
+ attr_reader :edited_at
23
+
24
+ # @return [Integer] the flags that have been set on the message snapshot.
25
+ attr_reader :flags
26
+
27
+ # @return [Array<User>] the users that were mentioned in the message snapshot.
28
+ attr_reader :mentions
29
+
30
+ # @return [Array<Component>] the interaction components associated with the message snapshot.
31
+ attr_reader :components
32
+
33
+ # @!visibility private
34
+ def initialize(data, bot)
35
+ @bot = bot
36
+ @type = data['type']
37
+ @flags = data['flags'] || 0
38
+ @content = data['content']
39
+ @mention_roles = data['mention_roles']&.map(&:resolve_id) || []
40
+ @embeds = data['embeds']&.map { |embed| Embed.new(embed, self) } || []
41
+ @attachments = data['attachments']&.map { |file| Attachment.new(file, self, bot) } || []
42
+ @created_at = data['timestamp'] ? Time.parse(data['timestamp']) : nil
43
+ @edited_at = data['edited_timestamp'] ? Time.parse(data['edited_timestamp']) : nil
44
+ @mentions = data['mentions']&.map { |mention| bot.ensure_user(mention) } || []
45
+ @components = data['components']&.map { |component| Components.from_data(component, bot) } || []
46
+ end
47
+
48
+ # Check whether the message snapshot has been edited.
49
+ # @return [true, false] whether the snapshot was edited or not.
50
+ def edited?
51
+ !@edited_at.nil?
52
+ end
53
+
54
+ # Check whether the message snapshot contains any custom emojis.
55
+ # @return [true, false] whether or not any emoji were used in the snapshot.
56
+ def emojis?
57
+ emojis.any?
58
+ end
59
+
60
+ # Get the custom emojis that were used in the message snapshot.
61
+ # @return [Array<Emoji>] the emojis used in the message snapshot.
62
+ def emojis
63
+ return [] if @content.nil? || @content.empty?
64
+
65
+ @emojis ||= @bot.parse_mentions(@content).select { |parsed| parsed.is_a?(Emoji) }
66
+ end
67
+
68
+ # Get the roles that were mentioned in the message snapshot.
69
+ # @return [Array<Role>] the roles that were mentioned in the message snapshot.
70
+ # @note this can only resolve roles in servers that the bot has access to via {Bot#servers}.
71
+ def role_mentions
72
+ return [] if @mention_roles.empty?
73
+
74
+ return @role_mentions if @role_mentions
75
+
76
+ roles = @bot.servers.values.flat_map(&:roles)
77
+
78
+ @role_mentions = @mention_roles.filter_map { |id| roles.find { |r| r.id == id } }
79
+ end
80
+
81
+ # Get the buttons that were used in the message snapshot.
82
+ # @return [Array<Components::Button>] the button components used in the message snapshot.
83
+ def buttons
84
+ buttons = @components.flat_map do |component|
85
+ case component
86
+ when Components::Button
87
+ component
88
+ when Components::ActionRow
89
+ component.buttons
90
+ end
91
+ end
92
+
93
+ buttons.compact
94
+ end
95
+
96
+ # @see Discordrb::Message::FLAGS
97
+ Message::FLAGS.each do |name, value|
98
+ define_method("#{name}?") do
99
+ @flags.anybits?(value)
100
+ end
101
+ end
102
+
103
+ # @see Discordrb::Message::TYPES
104
+ Message::TYPES.each do |name, value|
105
+ define_method("#{name}?") do
106
+ @type == value
107
+ end
108
+ end
109
+ end
110
+ end
@@ -49,6 +49,20 @@ module Discordrb
49
49
  # @see #avatar_url
50
50
  attr_accessor :avatar_id
51
51
 
52
+ # @return [true, false] whether the user is an offical Discord System user (part of the urgent message system).
53
+ attr_reader :system_account
54
+ alias_method :system_account?, :system_account
55
+
56
+ # @return [AvatarDecoration, nil] the current user's avatar decoration, or nil if the user doesn't have one.
57
+ attr_reader :avatar_decoration
58
+
59
+ # @return [Collectibles] the collectibles that this user has collected.
60
+ attr_reader :collectibles
61
+
62
+ # @return [PrimaryServer, nil] the server tag the user has adopted, or nil if the user doesn't have one displayed.
63
+ attr_reader :primary_server
64
+ alias_method :server_tag, :primary_server
65
+
52
66
  # Utility function to get Discord's display name of a user not in server
53
67
  # @return [String] the name the user displays as (global_name if they have one, username otherwise)
54
68
  def display_name
@@ -91,9 +105,17 @@ module Discordrb
91
105
 
92
106
  FLAGS.each do |name, value|
93
107
  define_method("#{name}?") do
94
- (@public_flags & value).positive?
108
+ @public_flags.anybits?(value)
95
109
  end
96
110
  end
111
+
112
+ # Utility function to get a user's banner URL.
113
+ # @param format [String, nil] If `nil`, the URL will default to `png` for static banners and will detect if the user has a `gif` banner.
114
+ # You can otherwise specify one of `webp`, `jpg`, `png`, or `gif` to override this.
115
+ # @return [String, nil] the URL to the banner image or nil if the user doesn't have one.
116
+ def banner_url(format = nil)
117
+ API::User.banner_url(@id, banner_id, format) if banner_id
118
+ end
97
119
  end
98
120
 
99
121
  # User on Discord, including internal data like discriminators
@@ -120,18 +142,19 @@ module Discordrb
120
142
  @id = data['id'].to_i
121
143
  @discriminator = data['discriminator']
122
144
  @avatar_id = data['avatar']
123
- @roles = {}
124
145
  @activities = Discordrb::ActivitySet.new
125
146
  @public_flags = data['public_flags'] || 0
126
-
127
- @bot_account = false
128
- @bot_account = true if data['bot']
129
-
130
- @webhook_account = false
131
- @webhook_account = true if data['_webhook']
147
+ @bot_account = data['bot'] || false
148
+ @webhook_account = data['_webhook'] || false
132
149
 
133
150
  @status = :offline
134
151
  @client_status = process_client_status(data['client_status'])
152
+ @banner_id = data['banner']
153
+ @system_account = data['system'] || false
154
+ @avatar_decoration = process_avatar_decoration(data['avatar_decoration_data'])
155
+ @collectibles = Collectibles.new(data['collectibles'] || {}, bot)
156
+
157
+ @primary_server = process_primary_server(data['primary_guild'] || {})
135
158
  end
136
159
 
137
160
  # Get a user's PM channel or send them a PM
@@ -167,6 +190,12 @@ module Discordrb
167
190
  pm.send_file(file, caption: caption, filename: filename, spoiler: spoiler)
168
191
  end
169
192
 
193
+ # @return [String, nil] the ID of this user's current banner, can be used to generate a banner URL.
194
+ # @see #banner_url
195
+ def banner_id
196
+ @banner_id ||= JSON.parse(API::User.resolve(@bot.token, @id))['banner']
197
+ end
198
+
170
199
  # Set the user's username
171
200
  # @note for internal use only
172
201
  # @!visibility private
@@ -181,6 +210,27 @@ module Discordrb
181
210
  @global_name = global_name
182
211
  end
183
212
 
213
+ # Set the user's avatar_decoration
214
+ # @note For internal use only.
215
+ # @!visibility private
216
+ def update_avatar_decoration(decoration)
217
+ @avatar_decoration = process_avatar_decoration(decoration)
218
+ end
219
+
220
+ # Set the user's collectibles
221
+ # @note For internal use only.
222
+ # @!visibility private
223
+ def update_collectibles(collectibles)
224
+ @collectibles = Collectibles.new(collectibles || {}, @bot)
225
+ end
226
+
227
+ # Set the user's primary server
228
+ # @note For internal use only.
229
+ # @!visibility private
230
+ def update_primary_server(server)
231
+ @primary_server = process_primary_server(server || {})
232
+ end
233
+
184
234
  # Set the user's presence data
185
235
  # @note for internal use only
186
236
  # @!visibility private
@@ -224,6 +274,16 @@ module Discordrb
224
274
  (client_status || {}).to_h { |k, v| [k.to_sym, v.to_sym] }
225
275
  end
226
276
 
277
+ # @!visibility private
278
+ def process_avatar_decoration(decoration)
279
+ decoration ? AvatarDecoration.new(decoration, @bot) : nil
280
+ end
281
+
282
+ # @!visibility private
283
+ def process_primary_server(server)
284
+ PrimaryServer.new(server, @bot) if server['identity_enabled']
285
+ end
286
+
227
287
  # @!method offline?
228
288
  # @return [true, false] whether this user is offline.
229
289
  # @!method idle?
@@ -28,6 +28,7 @@ module Discordrb
28
28
  # @return [true, false] whether this is a custom voice region (used for events/etc)
29
29
  attr_reader :custom
30
30
 
31
+ # @!visibility private
31
32
  def initialize(data)
32
33
  @id = data['id']
33
34
 
@@ -31,6 +31,7 @@ module Discordrb
31
31
  # @return [Member, User, nil] the user object of the owner or nil if the webhook was requested using the token.
32
32
  attr_reader :owner
33
33
 
34
+ # @!visibility private
34
35
  def initialize(data, bot)
35
36
  @bot = bot
36
37
 
@@ -211,11 +212,7 @@ module Discordrb
211
212
  private
212
213
 
213
214
  def avatarise(avatar)
214
- if avatar.respond_to? :read
215
- "data:image/jpg;base64,#{Base64.strict_encode64(avatar.read)}"
216
- else
217
- avatar
218
- end
215
+ avatar.respond_to?(:read) ? Discordrb.encode64(avatar) : avatar
219
216
  end
220
217
 
221
218
  def update_internal(data)
@@ -40,3 +40,9 @@ require 'discordrb/data/webhook'
40
40
  require 'discordrb/data/audit_logs'
41
41
  require 'discordrb/data/interaction'
42
42
  require 'discordrb/data/component'
43
+ require 'discordrb/data/avatar_decoration'
44
+ require 'discordrb/data/collectibles'
45
+ require 'discordrb/data/primary_server'
46
+ require 'discordrb/data/server_preview'
47
+ require 'discordrb/data/call'
48
+ require 'discordrb/data/snapshot'
@@ -73,7 +73,7 @@ module Discordrb
73
73
  if (errs = sub_err['_errors'])
74
74
  "#{key}: #{errs.map { |e| e['message'] }.join(' ')}"
75
75
  elsif sub_err['message'] || sub_err['code']
76
- "#{sub_err['code'] ? "#{sub_err['code']}: " : nil}#{err_msg}"
76
+ "#{"#{sub_err['code']}: " if sub_err['code']}#{err_msg}"
77
77
  elsif sub_err.is_a? String
78
78
  sub_err
79
79
  else
@@ -99,7 +99,7 @@ module Discordrb
99
99
  # @param code [Integer] The code to check
100
100
  # @return [Class] the error class for the given code
101
101
  def self.error_class_for(code)
102
- @code_classes[code] || UnknownError
102
+ @code_classes[code] || Code(code)
103
103
  end
104
104
 
105
105
  # Used when Discord doesn't provide a more specific code
@@ -174,6 +174,9 @@ module Discordrb
174
174
  # Unauthorized
175
175
  Unauthorized = Unauthorised = Code(40_001)
176
176
 
177
+ # Unable to bulk ban any users
178
+ UnableToBulkBanUsers = Code(500_000)
179
+
177
180
  # Missing Access
178
181
  MissingAccess = Code(50_001)
179
182
 
@@ -25,7 +25,7 @@ module Discordrb::Events
25
25
  # @see Await#attributes
26
26
  delegate :key, :type, :attributes, to: :await
27
27
 
28
- # For internal use only
28
+ # @!visibility private
29
29
  def initialize(await, event, bot)
30
30
  @await = await
31
31
  @event = event
@@ -29,6 +29,7 @@ module Discordrb::Events
29
29
  # @see Channel#server
30
30
  delegate :name, :server, :type, :owner_id, :recipients, :topic, :user_limit, :position, :permission_overwrites, to: :channel
31
31
 
32
+ # @!visibility private
32
33
  def initialize(data, bot)
33
34
  @bot = bot
34
35
  @channel = data.is_a?(Discordrb::Channel) ? data : bot.channel(data['id'].to_i)
@@ -83,6 +84,7 @@ module Discordrb::Events
83
84
  # @return [Integer, nil] the channel's owner ID if this is a group channel
84
85
  attr_reader :owner_id
85
86
 
87
+ # @!visibility private
86
88
  def initialize(data, bot)
87
89
  @bot = bot
88
90
 
@@ -134,6 +136,7 @@ module Discordrb::Events
134
136
 
135
137
  delegate :id, to: :recipient
136
138
 
139
+ # @!visibility private
137
140
  def initialize(data, bot)
138
141
  @bot = bot
139
142
 
@@ -168,6 +171,40 @@ module Discordrb::Events
168
171
  end
169
172
  end
170
173
 
174
+ # Raised when a message is pinned or unpinned.
175
+ class ChannelPinsUpdateEvent < Event
176
+ # @return [Time, nil] Time at which the most recent pinned message was pinned.
177
+ attr_reader :last_pin_timestamp
178
+
179
+ # @return [Channel] The channel this event originates from.
180
+ attr_reader :channel
181
+
182
+ # @return [Server, nil] The server this event originates from.
183
+ attr_reader :server
184
+
185
+ # @!visibility private
186
+ def initialize(data, bot)
187
+ @bot = bot
188
+
189
+ @server = bot.server(data['guild_id']) if data['guild_id']
190
+ @channel = bot.channel(data['channel_id'])
191
+ @last_pin_timestamp = Time.iso8601(data['last_pin_timestamp']) if data['last_pin_timestamp']
192
+ end
193
+ end
194
+
195
+ # Event handler for ChannelPinsUpdateEvent.
196
+ class ChannelPinsUpdateEventHandler < EventHandler
197
+ def matches?(event)
198
+ # Check for the proper event type.
199
+ return false unless event.is_a? ChannelPinsUpdateEvent
200
+
201
+ [
202
+ matches_all(@attributes[:server], event.server) { |a, e| a.resolve_id == e&.id },
203
+ matches_all(@attributes[:channel], event.channel) { |a, e| a.resolve_id == e.id }
204
+ ].reduce(true, &:&)
205
+ end
206
+ end
207
+
171
208
  # Raised when a user is added to a private channel
172
209
  class ChannelRecipientAddEvent < ChannelRecipientEvent; end
173
210
 
@@ -7,6 +7,7 @@ module Discordrb::Events
7
7
  class Negated
8
8
  attr_reader :object
9
9
 
10
+ # @!visibility private
10
11
  def initialize(object)
11
12
  @object = object
12
13
  end
@@ -75,6 +76,7 @@ module Discordrb::Events
75
76
 
76
77
  # Generic event handler that can be extended
77
78
  class EventHandler
79
+ # @!visibility private
78
80
  def initialize(attributes, block)
79
81
  @attributes = attributes
80
82
  @block = block
@@ -9,6 +9,7 @@ module Discordrb::Events
9
9
  # @return [Server] the server in question.
10
10
  attr_reader :server
11
11
 
12
+ # @!visibility private
12
13
  def initialize(data, bot)
13
14
  @bot = bot
14
15
 
@@ -64,7 +65,8 @@ module Discordrb::Events
64
65
  # @return [Integer] The ID of the server that was left.
65
66
  attr_reader :server
66
67
 
67
- # Override init_server to account for the deleted server
68
+ # @!visibility private
69
+ # @note Override init_server to account for the deleted server
68
70
  def init_server(data, _bot)
69
71
  @server = data['id'].to_i
70
72
  end
@@ -81,6 +83,7 @@ module Discordrb::Events
81
83
  # @return [Array<Emoji>] array of emojis.
82
84
  attr_reader :emoji
83
85
 
86
+ # @!visibility private
84
87
  def initialize(server, data, bot)
85
88
  @bot = bot
86
89
  @server = server
@@ -103,6 +106,7 @@ module Discordrb::Events
103
106
  # @return [Emoji] the emoji data.
104
107
  attr_reader :emoji
105
108
 
109
+ # @!visibility private
106
110
  def initialize(server, emoji, bot)
107
111
  @bot = bot
108
112
  @emoji = emoji
@@ -127,6 +131,7 @@ module Discordrb::Events
127
131
  # @return [Emoji, nil] the updated emoji data.
128
132
  attr_reader :emoji
129
133
 
134
+ # @!visibility private
130
135
  def initialize(server, old_emoji, emoji, bot)
131
136
  @bot = bot
132
137
  @old_emoji = old_emoji