discordrb 3.3.0 → 3.5.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 (93) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +152 -0
  3. data/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
  4. data/.github/ISSUE_TEMPLATE/feature_request.md +24 -0
  5. data/.github/pull_request_template.md +37 -0
  6. data/.github/workflows/codeql.yml +65 -0
  7. data/.markdownlint.json +4 -0
  8. data/.rubocop.yml +39 -36
  9. data/CHANGELOG.md +874 -552
  10. data/Gemfile +2 -0
  11. data/LICENSE.txt +1 -1
  12. data/README.md +80 -86
  13. data/Rakefile +2 -0
  14. data/bin/console +1 -0
  15. data/discordrb-webhooks.gemspec +9 -6
  16. data/discordrb.gemspec +21 -18
  17. data/lib/discordrb/allowed_mentions.rb +36 -0
  18. data/lib/discordrb/api/application.rb +202 -0
  19. data/lib/discordrb/api/channel.rb +236 -47
  20. data/lib/discordrb/api/interaction.rb +54 -0
  21. data/lib/discordrb/api/invite.rb +5 -5
  22. data/lib/discordrb/api/server.rb +94 -66
  23. data/lib/discordrb/api/user.rb +17 -11
  24. data/lib/discordrb/api/webhook.rb +63 -6
  25. data/lib/discordrb/api.rb +55 -16
  26. data/lib/discordrb/await.rb +0 -1
  27. data/lib/discordrb/bot.rb +480 -93
  28. data/lib/discordrb/cache.rb +31 -24
  29. data/lib/discordrb/colour_rgb.rb +43 -0
  30. data/lib/discordrb/commands/command_bot.rb +35 -12
  31. data/lib/discordrb/commands/container.rb +21 -24
  32. data/lib/discordrb/commands/parser.rb +20 -20
  33. data/lib/discordrb/commands/rate_limiter.rb +4 -3
  34. data/lib/discordrb/container.rb +209 -20
  35. data/lib/discordrb/data/activity.rb +271 -0
  36. data/lib/discordrb/data/application.rb +50 -0
  37. data/lib/discordrb/data/attachment.rb +71 -0
  38. data/lib/discordrb/data/audit_logs.rb +345 -0
  39. data/lib/discordrb/data/channel.rb +993 -0
  40. data/lib/discordrb/data/component.rb +229 -0
  41. data/lib/discordrb/data/embed.rb +251 -0
  42. data/lib/discordrb/data/emoji.rb +82 -0
  43. data/lib/discordrb/data/integration.rb +122 -0
  44. data/lib/discordrb/data/interaction.rb +800 -0
  45. data/lib/discordrb/data/invite.rb +137 -0
  46. data/lib/discordrb/data/member.rb +372 -0
  47. data/lib/discordrb/data/message.rb +414 -0
  48. data/lib/discordrb/data/overwrite.rb +108 -0
  49. data/lib/discordrb/data/profile.rb +91 -0
  50. data/lib/discordrb/data/reaction.rb +33 -0
  51. data/lib/discordrb/data/recipient.rb +34 -0
  52. data/lib/discordrb/data/role.rb +248 -0
  53. data/lib/discordrb/data/server.rb +1004 -0
  54. data/lib/discordrb/data/user.rb +264 -0
  55. data/lib/discordrb/data/voice_region.rb +45 -0
  56. data/lib/discordrb/data/voice_state.rb +41 -0
  57. data/lib/discordrb/data/webhook.rb +238 -0
  58. data/lib/discordrb/data.rb +28 -4180
  59. data/lib/discordrb/errors.rb +46 -4
  60. data/lib/discordrb/events/bans.rb +7 -5
  61. data/lib/discordrb/events/channels.rb +3 -1
  62. data/lib/discordrb/events/guilds.rb +16 -9
  63. data/lib/discordrb/events/interactions.rb +482 -0
  64. data/lib/discordrb/events/invites.rb +125 -0
  65. data/lib/discordrb/events/members.rb +6 -2
  66. data/lib/discordrb/events/message.rb +72 -27
  67. data/lib/discordrb/events/presence.rb +35 -18
  68. data/lib/discordrb/events/raw.rb +1 -3
  69. data/lib/discordrb/events/reactions.rb +49 -4
  70. data/lib/discordrb/events/threads.rb +96 -0
  71. data/lib/discordrb/events/typing.rb +6 -4
  72. data/lib/discordrb/events/voice_server_update.rb +47 -0
  73. data/lib/discordrb/events/voice_state_update.rb +15 -10
  74. data/lib/discordrb/events/webhooks.rb +9 -6
  75. data/lib/discordrb/gateway.rb +99 -71
  76. data/lib/discordrb/id_object.rb +39 -0
  77. data/lib/discordrb/light/integrations.rb +1 -1
  78. data/lib/discordrb/light/light_bot.rb +1 -1
  79. data/lib/discordrb/logger.rb +4 -4
  80. data/lib/discordrb/paginator.rb +57 -0
  81. data/lib/discordrb/permissions.rb +159 -39
  82. data/lib/discordrb/version.rb +1 -1
  83. data/lib/discordrb/voice/encoder.rb +16 -7
  84. data/lib/discordrb/voice/network.rb +99 -47
  85. data/lib/discordrb/voice/sodium.rb +98 -0
  86. data/lib/discordrb/voice/voice_bot.rb +33 -25
  87. data/lib/discordrb/webhooks.rb +2 -0
  88. data/lib/discordrb.rb +107 -1
  89. metadata +126 -54
  90. data/.codeclimate.yml +0 -16
  91. data/.travis.yml +0 -33
  92. data/bin/travis_build_docs.sh +0 -17
  93. /data/{CONTRIBUTING.md → .github/CONTRIBUTING.md} +0 -0
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Discordrb
4
+ # A channel referenced by an invite. It has less data than regular channels, so it's a separate class
5
+ class InviteChannel
6
+ include IDObject
7
+
8
+ # @return [String] this channel's name.
9
+ attr_reader :name
10
+
11
+ # @return [Integer] this channel's type (0: text, 1: private, 2: voice, 3: group).
12
+ attr_reader :type
13
+
14
+ # @!visibility private
15
+ def initialize(data, bot)
16
+ @bot = bot
17
+
18
+ @id = data['id'].to_i
19
+ @name = data['name']
20
+ @type = data['type']
21
+ end
22
+ end
23
+
24
+ # A server referenced to by an invite
25
+ class InviteServer
26
+ include IDObject
27
+
28
+ # @return [String] this server's name.
29
+ attr_reader :name
30
+
31
+ # @return [String, nil] the hash of the server's invite splash screen (for partnered servers) or nil if none is
32
+ # present
33
+ attr_reader :splash_hash
34
+
35
+ # @!visibility private
36
+ def initialize(data, bot)
37
+ @bot = bot
38
+
39
+ @id = data['id'].to_i
40
+ @name = data['name']
41
+ @splash_hash = data['splash_hash']
42
+ end
43
+ end
44
+
45
+ # A Discord invite to a channel
46
+ class Invite
47
+ # @return [InviteChannel, Channel] the channel this invite references.
48
+ attr_reader :channel
49
+
50
+ # @return [InviteServer, Server] the server this invite references.
51
+ attr_reader :server
52
+
53
+ # @return [Integer] the amount of uses left on this invite.
54
+ attr_reader :uses
55
+ alias_method :max_uses, :uses
56
+
57
+ # @return [User, nil] the user that made this invite. May also be nil if the user can't be determined.
58
+ attr_reader :inviter
59
+ alias_method :user, :inviter
60
+
61
+ # @return [true, false] whether or not this invite grants temporary membership. If someone joins a server with this invite, they will be removed from the server when they go offline unless they've received a role.
62
+ attr_reader :temporary
63
+ alias_method :temporary?, :temporary
64
+
65
+ # @return [true, false] whether this invite is still valid.
66
+ attr_reader :revoked
67
+ alias_method :revoked?, :revoked
68
+
69
+ # @return [String] this invite's code
70
+ attr_reader :code
71
+
72
+ # @return [Integer, nil] the amount of members in the server. Will be nil if it has not been resolved.
73
+ attr_reader :member_count
74
+ alias_method :user_count, :member_count
75
+
76
+ # @return [Integer, nil] the amount of online members in the server. Will be nil if it has not been resolved.
77
+ attr_reader :online_member_count
78
+ alias_method :online_user_count, :online_member_count
79
+
80
+ # @return [Integer, nil] the invites max age before it expires, or nil if it's unknown. If the max age is 0, the invite will never expire unless it's deleted.
81
+ attr_reader :max_age
82
+
83
+ # @return [Time, nil] when this invite was created, or nil if it's unknown
84
+ attr_reader :created_at
85
+
86
+ # @!visibility private
87
+ def initialize(data, bot)
88
+ @bot = bot
89
+
90
+ @channel = if data['channel_id']
91
+ bot.channel(data['channel_id'])
92
+ else
93
+ InviteChannel.new(data['channel'], bot)
94
+ end
95
+
96
+ @server = if data['guild_id']
97
+ bot.server(data['guild_id'])
98
+ else
99
+ InviteServer.new(data['guild'], bot)
100
+ end
101
+
102
+ @uses = data['uses']
103
+ @inviter = data['inviter'] ? bot.ensure_user(data['inviter']) : nil
104
+ @temporary = data['temporary']
105
+ @revoked = data['revoked']
106
+ @online_member_count = data['approximate_presence_count']
107
+ @member_count = data['approximate_member_count']
108
+ @max_age = data['max_age']
109
+ @created_at = data['created_at']
110
+
111
+ @code = data['code']
112
+ end
113
+
114
+ # Code based comparison
115
+ def ==(other)
116
+ other.respond_to?(:code) ? (@code == other.code) : (@code == other)
117
+ end
118
+
119
+ # Deletes this invite
120
+ # @param reason [String] The reason the invite is being deleted.
121
+ def delete(reason = nil)
122
+ API::Invite.delete(@bot.token, @code, reason)
123
+ end
124
+
125
+ alias_method :revoke, :delete
126
+
127
+ # The inspect method is overwritten to give more useful output
128
+ def inspect
129
+ "<Invite code=#{@code} channel=#{@channel} uses=#{@uses} temporary=#{@temporary} revoked=#{@revoked} created_at=#{@created_at} max_age=#{@max_age}>"
130
+ end
131
+
132
+ # Creates an invite URL.
133
+ def url
134
+ "https://discord.gg/#{@code}"
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,372 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Discordrb
4
+ # Mixin for the attributes members and private members should have
5
+ module MemberAttributes
6
+ # @return [Time] when this member joined the server.
7
+ attr_reader :joined_at
8
+
9
+ # @return [Time, nil] when this member boosted this server, `nil` if they haven't.
10
+ attr_reader :boosting_since
11
+
12
+ # @return [String, nil] the nickname this member has, or `nil` if it has none.
13
+ attr_reader :nick
14
+ alias_method :nickname, :nick
15
+
16
+ # @return [Array<Role>] the roles this member has.
17
+ attr_reader :roles
18
+
19
+ # @return [Server] the server this member is on.
20
+ attr_reader :server
21
+
22
+ # @return [Time] When the user's timeout will expire.
23
+ attr_reader :communication_disabled_until
24
+ alias_method :timeout, :communication_disabled_until
25
+ end
26
+
27
+ # A member is a user on a server. It differs from regular users in that it has roles, voice statuses and things like
28
+ # that.
29
+ class Member < DelegateClass(User)
30
+ # @return [true, false] whether this member is muted server-wide.
31
+ def mute
32
+ voice_state_attribute(:mute)
33
+ end
34
+
35
+ # @return [true, false] whether this member is deafened server-wide.
36
+ def deaf
37
+ voice_state_attribute(:deaf)
38
+ end
39
+
40
+ # @return [true, false] whether this member has muted themselves.
41
+ def self_mute
42
+ voice_state_attribute(:self_mute)
43
+ end
44
+
45
+ # @return [true, false] whether this member has deafened themselves.
46
+ def self_deaf
47
+ voice_state_attribute(:self_deaf)
48
+ end
49
+
50
+ # @return [Channel] the voice channel this member is in.
51
+ def voice_channel
52
+ voice_state_attribute(:voice_channel)
53
+ end
54
+
55
+ alias_method :muted?, :mute
56
+ alias_method :deafened?, :deaf
57
+ alias_method :self_muted?, :self_mute
58
+ alias_method :self_deafened?, :self_deaf
59
+
60
+ include MemberAttributes
61
+
62
+ # @!visibility private
63
+ def initialize(data, server, bot)
64
+ @bot = bot
65
+
66
+ @user = bot.ensure_user(data['user'])
67
+ super @user # Initialize the delegate class
68
+
69
+ @server = server
70
+ @server_id = server&.id || data['guild_id'].to_i
71
+
72
+ @role_ids = data['roles']&.map(&:to_i) || []
73
+
74
+ @nick = data['nick']
75
+ @joined_at = data['joined_at'] ? Time.parse(data['joined_at']) : nil
76
+ @boosting_since = data['premium_since'] ? Time.parse(data['premium_since']) : nil
77
+ timeout_until = data['communication_disabled_until']
78
+ @communication_disabled_until = timeout_until ? Time.parse(timeout_until) : nil
79
+ @permissions = Permissions.new(data['permissions']) if data['permissions']
80
+ end
81
+
82
+ # @return [Server] the server this member is on.
83
+ # @raise [Discordrb::Errors::NoPermission] This can happen when receiving interactions for servers in which the bot is not
84
+ # authorized with the `bot` scope.
85
+ def server
86
+ return @server if @server
87
+
88
+ @server = @bot.server(@server_id)
89
+ raise Discordrb::Errors::NoPermission, 'The bot does not have access to this server' unless @server
90
+
91
+ @server
92
+ end
93
+
94
+ # @return [Array<Role>] the roles this member has.
95
+ # @raise [Discordrb::Errors::NoPermission] This can happen when receiving interactions for servers in which the bot is not
96
+ # authorized with the `bot` scope.
97
+ def roles
98
+ return @roles if @roles
99
+
100
+ update_roles(@role_ids)
101
+ @roles
102
+ end
103
+
104
+ # @return [true, false] if this user is a Nitro Booster of this server.
105
+ def boosting?
106
+ !@boosting_since.nil?
107
+ end
108
+
109
+ # @return [true, false] whether this member is the server owner.
110
+ def owner?
111
+ server.owner == self
112
+ end
113
+
114
+ # @param role [Role, String, Integer] the role to check or its ID.
115
+ # @return [true, false] whether this member has the specified role.
116
+ def role?(role)
117
+ role = role.resolve_id
118
+ roles.any?(role)
119
+ end
120
+
121
+ # @see Member#set_roles
122
+ def roles=(role)
123
+ set_roles(role)
124
+ end
125
+
126
+ # Check if the current user has communication disabled.
127
+ # @return [true, false]
128
+ def communication_disabled?
129
+ !@communication_disabled_until.nil? && @communication_disabled_until > Time.now
130
+ end
131
+
132
+ alias_method :timeout?, :communication_disabled?
133
+
134
+ # Set a user's timeout duration, or remove it by setting the timeout to `nil`.
135
+ # @param timeout_until [Time, nil] When the timeout will end.
136
+ def communication_disabled_until=(timeout_until)
137
+ raise ArgumentError, 'A time out cannot exceed 28 days' if timeout_until && timeout_until > (Time.now + 2_419_200)
138
+
139
+ API::Server.update_member(@bot.token, @server_id, @user.id, communication_disabled_until: timeout_until.iso8601)
140
+ end
141
+
142
+ alias_method :timeout=, :communication_disabled_until=
143
+
144
+ # Bulk sets a member's roles.
145
+ # @param role [Role, Array<Role>] The role(s) to set.
146
+ # @param reason [String] The reason the user's roles are being changed.
147
+ def set_roles(role, reason = nil)
148
+ role_ids = role_id_array(role)
149
+ API::Server.update_member(@bot.token, @server_id, @user.id, roles: role_ids, reason: reason)
150
+ end
151
+
152
+ # Adds and removes roles from a member.
153
+ # @param add [Role, Array<Role>] The role(s) to add.
154
+ # @param remove [Role, Array<Role>] The role(s) to remove.
155
+ # @param reason [String] The reason the user's roles are being changed.
156
+ # @example Remove the 'Member' role from a user, and add the 'Muted' role to them.
157
+ # to_add = server.roles.find {|role| role.name == 'Muted'}
158
+ # to_remove = server.roles.find {|role| role.name == 'Member'}
159
+ # member.modify_roles(to_add, to_remove)
160
+ def modify_roles(add, remove, reason = nil)
161
+ add_role_ids = role_id_array(add)
162
+ remove_role_ids = role_id_array(remove)
163
+ old_role_ids = resolve_role_ids
164
+ new_role_ids = (old_role_ids - remove_role_ids + add_role_ids).uniq
165
+
166
+ API::Server.update_member(@bot.token, @server_id, @user.id, roles: new_role_ids, reason: reason)
167
+ end
168
+
169
+ # Adds one or more roles to this member.
170
+ # @param role [Role, Array<Role, String, Integer>, String, Integer] The role(s), or their ID(s), to add.
171
+ # @param reason [String] The reason the user's roles are being changed.
172
+ def add_role(role, reason = nil)
173
+ role_ids = role_id_array(role)
174
+
175
+ if role_ids.count == 1
176
+ API::Server.add_member_role(@bot.token, @server_id, @user.id, role_ids[0], reason)
177
+ else
178
+ old_role_ids = resolve_role_ids
179
+ new_role_ids = (old_role_ids + role_ids).uniq
180
+ API::Server.update_member(@bot.token, @server_id, @user.id, roles: new_role_ids, reason: reason)
181
+ end
182
+ end
183
+
184
+ # Removes one or more roles from this member.
185
+ # @param role [Role, Array<Role>] The role(s) to remove.
186
+ # @param reason [String] The reason the user's roles are being changed.
187
+ def remove_role(role, reason = nil)
188
+ role_ids = role_id_array(role)
189
+
190
+ if role_ids.count == 1
191
+ API::Server.remove_member_role(@bot.token, @server_id, @user.id, role_ids[0], reason)
192
+ else
193
+ old_role_ids = resolve_role_ids
194
+ new_role_ids = old_role_ids.reject { |i| role_ids.include?(i) }
195
+ API::Server.update_member(@bot.token, @server_id, @user.id, roles: new_role_ids, reason: reason)
196
+ end
197
+ end
198
+
199
+ # @return [Role] the highest role this member has.
200
+ def highest_role
201
+ roles.max_by(&:position)
202
+ end
203
+
204
+ # @return [Role, nil] the role this member is being hoisted with.
205
+ def hoist_role
206
+ hoisted_roles = roles.select(&:hoist)
207
+ return nil if hoisted_roles.empty?
208
+
209
+ hoisted_roles.max_by(&:position)
210
+ end
211
+
212
+ # @return [Role, nil] the role this member is basing their colour on.
213
+ def colour_role
214
+ coloured_roles = roles.select { |v| v.colour.combined.nonzero? }
215
+ return nil if coloured_roles.empty?
216
+
217
+ coloured_roles.max_by(&:position)
218
+ end
219
+ alias_method :color_role, :colour_role
220
+
221
+ # @return [ColourRGB, nil] the colour this member has.
222
+ def colour
223
+ return nil unless colour_role
224
+
225
+ colour_role.color
226
+ end
227
+ alias_method :color, :colour
228
+
229
+ # Server deafens this member.
230
+ def server_deafen
231
+ API::Server.update_member(@bot.token, @server_id, @user.id, deaf: true)
232
+ end
233
+
234
+ # Server undeafens this member.
235
+ def server_undeafen
236
+ API::Server.update_member(@bot.token, @server_id, @user.id, deaf: false)
237
+ end
238
+
239
+ # Server mutes this member.
240
+ def server_mute
241
+ API::Server.update_member(@bot.token, @server_id, @user.id, mute: true)
242
+ end
243
+
244
+ # Server unmutes this member.
245
+ def server_unmute
246
+ API::Server.update_member(@bot.token, @server_id, @user.id, mute: false)
247
+ end
248
+
249
+ # Bans this member from the server.
250
+ # @param message_days [Integer] How many days worth of messages sent by the member should be deleted.
251
+ # @param reason [String] The reason this member is being banned.
252
+ def ban(message_days = 0, reason: nil)
253
+ server.ban(@user, message_days, reason: reason)
254
+ end
255
+
256
+ # Unbans this member from the server.
257
+ # @param reason [String] The reason this member is being unbanned.
258
+ def unban(reason = nil)
259
+ server.unban(@user, reason)
260
+ end
261
+
262
+ # Kicks this member from the server.
263
+ # @param reason [String] The reason this member is being kicked.
264
+ def kick(reason = nil)
265
+ server.kick(@user, reason)
266
+ end
267
+
268
+ # @see Member#set_nick
269
+ def nick=(nick)
270
+ set_nick(nick)
271
+ end
272
+
273
+ alias_method :nickname=, :nick=
274
+
275
+ # Sets or resets this member's nickname. Requires the Change Nickname permission for the bot itself and Manage
276
+ # Nicknames for other users.
277
+ # @param nick [String, nil] The string to set the nickname to, or nil if it should be reset.
278
+ # @param reason [String] The reason the user's nickname is being changed.
279
+ def set_nick(nick, reason = nil)
280
+ # Discord uses the empty string to signify 'no nickname' so we convert nil into that
281
+ nick ||= ''
282
+
283
+ if @user.current_bot?
284
+ API::User.change_own_nickname(@bot.token, @server_id, nick, reason)
285
+ else
286
+ API::Server.update_member(@bot.token, @server_id, @user.id, nick: nick, reason: nil)
287
+ end
288
+ end
289
+
290
+ alias_method :set_nickname, :set_nick
291
+
292
+ # @return [String] the name the user displays as (nickname if they have one, global_name if they have one, username otherwise)
293
+ def display_name
294
+ nickname || global_name || username
295
+ end
296
+
297
+ # Update this member's roles
298
+ # @note For internal use only.
299
+ # @!visibility private
300
+ def update_roles(role_ids)
301
+ @roles = [server.role(@server_id)]
302
+ role_ids.each do |id|
303
+ # It is possible for members to have roles that do not exist
304
+ # on the server any longer. See https://github.com/discordrb/discordrb/issues/371
305
+ role = server.role(id)
306
+ @roles << role if role
307
+ end
308
+ end
309
+
310
+ # Update this member's nick
311
+ # @note For internal use only.
312
+ # @!visibility private
313
+ def update_nick(nick)
314
+ @nick = nick
315
+ end
316
+
317
+ # Update this member's boosting timestamp
318
+ # @note For internal user only.
319
+ # @!visibility private
320
+ def update_boosting_since(time)
321
+ @boosting_since = time
322
+ end
323
+
324
+ # @!visibility private
325
+ def update_communication_disabled_until(time)
326
+ time = time ? Time.parse(time) : nil
327
+ @communication_disabled_until = time
328
+ end
329
+
330
+ # Update this member
331
+ # @note For internal use only.
332
+ # @!visibility private
333
+ def update_data(data)
334
+ update_roles(data['roles']) if data['roles']
335
+ update_nick(data['nick']) if data.key?('nick')
336
+ @mute = data['mute'] if data.key?('mute')
337
+ @deaf = data['deaf'] if data.key?('deaf')
338
+
339
+ @joined_at = Time.parse(data['joined_at']) if data['joined_at']
340
+ timeout_until = data['communication_disabled_until']
341
+ @communication_disabled_until = timeout_until ? Time.parse(timeout_until) : nil
342
+ end
343
+
344
+ include PermissionCalculator
345
+
346
+ # Overwriting inspect for debug purposes
347
+ def inspect
348
+ "<Member user=#{@user.inspect} server=#{@server&.inspect || @server_id} joined_at=#{@joined_at} roles=#{@roles&.inspect || @role_ids} voice_channel=#{@voice_channel.inspect} mute=#{@mute} deaf=#{@deaf} self_mute=#{@self_mute} self_deaf=#{@self_deaf}>"
349
+ end
350
+
351
+ private
352
+
353
+ # Utility method to get a list of role IDs from one role or an array of roles
354
+ def role_id_array(role)
355
+ if role.is_a? Array
356
+ role.map(&:resolve_id)
357
+ else
358
+ [role.resolve_id]
359
+ end
360
+ end
361
+
362
+ # Utility method to get data out of this member's voice state
363
+ def voice_state_attribute(name)
364
+ voice_state = server.voice_states[@user.id]
365
+ voice_state&.send name
366
+ end
367
+
368
+ def resolve_role_ids
369
+ @roles ? @roles.collect(&:id) : @role_ids
370
+ end
371
+ end
372
+ end