discordrb 3.3.0 → 3.5.0

Sign up to get free protection for your applications and to get access to all the features.
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