onyxcord 1.1.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 (133) hide show
  1. checksums.yaml +7 -0
  2. data/.devcontainer/Dockerfile +13 -0
  3. data/.devcontainer/devcontainer.json +29 -0
  4. data/.devcontainer/postcreate.sh +4 -0
  5. data/.github/CONTRIBUTING.md +13 -0
  6. data/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
  7. data/.github/ISSUE_TEMPLATE/feature_request.md +24 -0
  8. data/.github/pull_request_template.md +37 -0
  9. data/.github/workflows/ci.yml +78 -0
  10. data/.github/workflows/codeql.yml +65 -0
  11. data/.github/workflows/deploy.yml +54 -0
  12. data/.github/workflows/release.yml +51 -0
  13. data/.gitignore +16 -0
  14. data/.markdownlint.json +4 -0
  15. data/.overcommit.yml +7 -0
  16. data/.rspec +2 -0
  17. data/.rubocop.yml +129 -0
  18. data/.yardopts +1 -0
  19. data/CHANGELOG.md +0 -0
  20. data/Gemfile +7 -0
  21. data/LICENSE.txt +21 -0
  22. data/README.md +305 -0
  23. data/Rakefile +17 -0
  24. data/bin/console +15 -0
  25. data/bin/setup +7 -0
  26. data/lib/onyxcord/allowed_mentions.rb +43 -0
  27. data/lib/onyxcord/api/application.rb +316 -0
  28. data/lib/onyxcord/api/channel.rb +700 -0
  29. data/lib/onyxcord/api/interaction.rb +67 -0
  30. data/lib/onyxcord/api/invite.rb +44 -0
  31. data/lib/onyxcord/api/server.rb +775 -0
  32. data/lib/onyxcord/api/user.rb +158 -0
  33. data/lib/onyxcord/api/webhook.rb +163 -0
  34. data/lib/onyxcord/api.rb +335 -0
  35. data/lib/onyxcord/await.rb +51 -0
  36. data/lib/onyxcord/bot.rb +1971 -0
  37. data/lib/onyxcord/cache.rb +326 -0
  38. data/lib/onyxcord/colour_rgb.rb +43 -0
  39. data/lib/onyxcord/commands/command_bot.rb +511 -0
  40. data/lib/onyxcord/commands/container.rb +112 -0
  41. data/lib/onyxcord/commands/events.rb +11 -0
  42. data/lib/onyxcord/commands/parser.rb +327 -0
  43. data/lib/onyxcord/commands/rate_limiter.rb +144 -0
  44. data/lib/onyxcord/configuration.rb +125 -0
  45. data/lib/onyxcord/container.rb +988 -0
  46. data/lib/onyxcord/data/activity.rb +271 -0
  47. data/lib/onyxcord/data/application.rb +341 -0
  48. data/lib/onyxcord/data/attachment.rb +91 -0
  49. data/lib/onyxcord/data/audit_logs.rb +438 -0
  50. data/lib/onyxcord/data/avatar_decoration.rb +26 -0
  51. data/lib/onyxcord/data/call.rb +22 -0
  52. data/lib/onyxcord/data/channel.rb +1355 -0
  53. data/lib/onyxcord/data/channel_tag.rb +69 -0
  54. data/lib/onyxcord/data/collectibles.rb +47 -0
  55. data/lib/onyxcord/data/component.rb +583 -0
  56. data/lib/onyxcord/data/embed.rb +258 -0
  57. data/lib/onyxcord/data/emoji.rb +123 -0
  58. data/lib/onyxcord/data/install_params.rb +24 -0
  59. data/lib/onyxcord/data/integration.rb +144 -0
  60. data/lib/onyxcord/data/interaction.rb +1141 -0
  61. data/lib/onyxcord/data/invite.rb +137 -0
  62. data/lib/onyxcord/data/member.rb +528 -0
  63. data/lib/onyxcord/data/message.rb +612 -0
  64. data/lib/onyxcord/data/message_activity.rb +41 -0
  65. data/lib/onyxcord/data/overwrite.rb +109 -0
  66. data/lib/onyxcord/data/poll.rb +365 -0
  67. data/lib/onyxcord/data/primary_server.rb +60 -0
  68. data/lib/onyxcord/data/profile.rb +79 -0
  69. data/lib/onyxcord/data/reaction.rb +64 -0
  70. data/lib/onyxcord/data/recipient.rb +34 -0
  71. data/lib/onyxcord/data/role.rb +449 -0
  72. data/lib/onyxcord/data/role_connection_data.rb +69 -0
  73. data/lib/onyxcord/data/role_subscription.rb +41 -0
  74. data/lib/onyxcord/data/scheduled_event.rb +513 -0
  75. data/lib/onyxcord/data/server.rb +1614 -0
  76. data/lib/onyxcord/data/server_preview.rb +68 -0
  77. data/lib/onyxcord/data/snapshot.rb +112 -0
  78. data/lib/onyxcord/data/team.rb +98 -0
  79. data/lib/onyxcord/data/timestamp.rb +69 -0
  80. data/lib/onyxcord/data/user.rb +324 -0
  81. data/lib/onyxcord/data/voice_region.rb +46 -0
  82. data/lib/onyxcord/data/voice_state.rb +41 -0
  83. data/lib/onyxcord/data/webhook.rb +238 -0
  84. data/lib/onyxcord/data.rb +57 -0
  85. data/lib/onyxcord/errors.rb +246 -0
  86. data/lib/onyxcord/event_executor.rb +80 -0
  87. data/lib/onyxcord/events/await.rb +48 -0
  88. data/lib/onyxcord/events/bans.rb +60 -0
  89. data/lib/onyxcord/events/channels.rb +225 -0
  90. data/lib/onyxcord/events/generic.rb +129 -0
  91. data/lib/onyxcord/events/guilds.rb +269 -0
  92. data/lib/onyxcord/events/integrations.rb +100 -0
  93. data/lib/onyxcord/events/interactions.rb +624 -0
  94. data/lib/onyxcord/events/invites.rb +127 -0
  95. data/lib/onyxcord/events/lifetime.rb +31 -0
  96. data/lib/onyxcord/events/members.rb +110 -0
  97. data/lib/onyxcord/events/message.rb +399 -0
  98. data/lib/onyxcord/events/polls.rb +118 -0
  99. data/lib/onyxcord/events/presence.rb +131 -0
  100. data/lib/onyxcord/events/raw.rb +74 -0
  101. data/lib/onyxcord/events/reactions.rb +218 -0
  102. data/lib/onyxcord/events/roles.rb +87 -0
  103. data/lib/onyxcord/events/scheduled_events.rb +171 -0
  104. data/lib/onyxcord/events/threads.rb +100 -0
  105. data/lib/onyxcord/events/typing.rb +73 -0
  106. data/lib/onyxcord/events/voice_server_update.rb +48 -0
  107. data/lib/onyxcord/events/voice_state_update.rb +106 -0
  108. data/lib/onyxcord/events/webhooks.rb +65 -0
  109. data/lib/onyxcord/gateway.rb +890 -0
  110. data/lib/onyxcord/id_object.rb +39 -0
  111. data/lib/onyxcord/light/data.rb +62 -0
  112. data/lib/onyxcord/light/integrations.rb +73 -0
  113. data/lib/onyxcord/light/light_bot.rb +58 -0
  114. data/lib/onyxcord/light.rb +8 -0
  115. data/lib/onyxcord/logger.rb +120 -0
  116. data/lib/onyxcord/message_components.rb +70 -0
  117. data/lib/onyxcord/paginator.rb +60 -0
  118. data/lib/onyxcord/permissions.rb +255 -0
  119. data/lib/onyxcord/rate_limiter/gateway.rb +42 -0
  120. data/lib/onyxcord/rate_limiter/rest.rb +89 -0
  121. data/lib/onyxcord/version.rb +7 -0
  122. data/lib/onyxcord/voice/encoder.rb +115 -0
  123. data/lib/onyxcord/voice/network.rb +380 -0
  124. data/lib/onyxcord/voice/opcodes.rb +29 -0
  125. data/lib/onyxcord/voice/sodium.rb +157 -0
  126. data/lib/onyxcord/voice/timer.rb +19 -0
  127. data/lib/onyxcord/voice/voice_bot.rb +386 -0
  128. data/lib/onyxcord/webhooks.rb +14 -0
  129. data/lib/onyxcord/websocket.rb +62 -0
  130. data/lib/onyxcord.rb +180 -0
  131. data/onyxcord-webhooks.gemspec +30 -0
  132. data/onyxcord.gemspec +50 -0
  133. metadata +421 -0
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OnyxCord
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,528 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OnyxCord
4
+ # Mixin for the attributes members and private members should have
5
+ module MemberAttributes
6
+ # Map of server member flags
7
+ MEMBER_FLAGS = {
8
+ rejoined: 1 << 0,
9
+ completed_onboarding: 1 << 1,
10
+ bypassed_verification: 1 << 2,
11
+ started_onboarding: 1 << 3,
12
+ guest: 1 << 4,
13
+ started_home_actions: 1 << 5,
14
+ completed_home_actions: 1 << 6,
15
+ automod_quarantined_username: 1 << 7,
16
+ dm_settings_upsell_acknowledged: 1 << 9,
17
+ automod_quarantined_server_tag: 1 << 10
18
+ }.freeze
19
+
20
+ # @return [Time] when this member joined the server.
21
+ attr_reader :joined_at
22
+
23
+ # @return [Time, nil] when this member boosted this server, `nil` if they haven't.
24
+ attr_reader :boosting_since
25
+
26
+ # @return [String, nil] the nickname this member has, or `nil` if it has none.
27
+ attr_reader :nick
28
+ alias_method :nickname, :nick
29
+
30
+ # @return [Array<Role>] the roles this member has.
31
+ attr_reader :roles
32
+
33
+ # @return [Server] the server this member is on.
34
+ attr_reader :server
35
+
36
+ # @return [Time] When the user's timeout will expire.
37
+ attr_reader :communication_disabled_until
38
+ alias_method :timeout, :communication_disabled_until
39
+
40
+ # @return [Integer] the flags set on this member.
41
+ attr_reader :flags
42
+
43
+ # @return [true, false] whether the member has not yet passed the server's membership screening requirements.
44
+ attr_reader :pending
45
+ alias_method :pending?, :pending
46
+
47
+ # @return [String, nil] the ID of this user's current avatar, can be used to generate a server avatar URL.
48
+ # @see #server_avatar_url
49
+ attr_reader :server_avatar_id
50
+
51
+ # @return [String, nil] the ID of this user's current server banner, can be used to generate a banner URL.
52
+ # @see #server_banner_url
53
+ attr_reader :server_banner_id
54
+
55
+ # @return [AvatarDecoration, nil] the user's current server avatar decoration, or nil for no server avatar decoration.
56
+ attr_reader :server_avatar_decoration
57
+
58
+ # @return [Collectibles] the server-specific collectibles that this user has collected.
59
+ attr_reader :server_collectibles
60
+
61
+ # Utility method to get a member's server avatar URL.
62
+ # @param format [String, nil] If `nil`, the URL will default to `webp` for static avatars, and will detect if the member has a `gif` avatar. You can otherwise specify one of `webp`, `jpg`, `png`, or `gif` to override this.
63
+ # @return [String, nil] the URL to the avatar image, or nil if the member doesn't have one.
64
+ def server_avatar_url(format = nil)
65
+ API::Server.avatar_url(@server_id, @user.id, @server_avatar_id, format) if @server_avatar_id
66
+ end
67
+
68
+ # Utility method to get a member's server banner URL.
69
+ # @param format [String, nil] If `nil`, the URL will default to `webp` for static banners, and will detect if the member has a `gif` banner. You can otherwise specify one of `webp`, `jpg`, `png`, or `gif` to override this.
70
+ # @return [String, nil] the URL to the banner image, or nil if the member doesn't have one.
71
+ def server_banner_url(format = nil)
72
+ API::Server.banner_url(@server_id, @user.id, @server_banner_id, format) if @server_banner_id
73
+ end
74
+
75
+ MEMBER_FLAGS.each do |name, value|
76
+ define_method("#{name}?") do
77
+ @flags.anybits?(value)
78
+ end
79
+ end
80
+ end
81
+
82
+ # A member is a user on a server. It differs from regular users in that it has roles, voice statuses and things like
83
+ # that.
84
+ class Member < DelegateClass(User)
85
+ # @return [true, false] whether this member is muted server-wide.
86
+ def mute
87
+ voice_state_attribute(:mute)
88
+ end
89
+
90
+ # @return [true, false] whether this member is deafened server-wide.
91
+ def deaf
92
+ voice_state_attribute(:deaf)
93
+ end
94
+
95
+ # @return [true, false] whether this member has muted themselves.
96
+ def self_mute
97
+ voice_state_attribute(:self_mute)
98
+ end
99
+
100
+ # @return [true, false] whether this member has deafened themselves.
101
+ def self_deaf
102
+ voice_state_attribute(:self_deaf)
103
+ end
104
+
105
+ # @return [Channel] the voice channel this member is in.
106
+ def voice_channel
107
+ voice_state_attribute(:voice_channel)
108
+ end
109
+
110
+ alias_method :muted?, :mute
111
+ alias_method :deafened?, :deaf
112
+ alias_method :self_muted?, :self_mute
113
+ alias_method :self_deafened?, :self_deaf
114
+
115
+ include MemberAttributes
116
+
117
+ # @!visibility private
118
+ def initialize(data, server, bot)
119
+ @bot = bot
120
+
121
+ @user = bot.ensure_user(data['user'])
122
+ super(@user) # Initialize the delegate class
123
+
124
+ @server = server
125
+ @server_id = server&.id || data['guild_id'].to_i
126
+
127
+ @role_ids = data['roles']&.map(&:to_i) || []
128
+
129
+ @nick = data['nick']
130
+ @joined_at = data['joined_at'] ? Time.parse(data['joined_at']) : nil
131
+ @boosting_since = data['premium_since'] ? Time.parse(data['premium_since']) : nil
132
+ timeout_until = data['communication_disabled_until']
133
+ @communication_disabled_until = timeout_until ? Time.parse(timeout_until) : nil
134
+ @permissions = Permissions.new(data['permissions']) if data['permissions']
135
+ @server_avatar_id = data['avatar']
136
+ @server_banner_id = data['banner']
137
+ @flags = data['flags'] || 0
138
+ @pending = data.key?('pending') ? data['pending'] : false
139
+ @server_avatar_decoration = process_avatar_decoration(data['avatar_decoration_data'])
140
+ @server_collectibles = Collectibles.new(data['collectibles'] || {}, @bot)
141
+ end
142
+
143
+ # @return [Server] the server this member is on.
144
+ # @raise [OnyxCord::Errors::NoPermission] This can happen when receiving interactions for servers in which the bot is not
145
+ # authorized with the `bot` scope.
146
+ def server
147
+ return @server if @server
148
+
149
+ @server = @bot.server(@server_id)
150
+ raise OnyxCord::Errors::NoPermission, 'The bot does not have access to this server' unless @server
151
+
152
+ @server
153
+ end
154
+
155
+ # @return [Array<Role>] the roles this member has.
156
+ # @raise [OnyxCord::Errors::NoPermission] This can happen when receiving interactions for servers in which the bot is not
157
+ # authorized with the `bot` scope.
158
+ def roles
159
+ return @roles if @roles
160
+
161
+ update_roles(@role_ids)
162
+ @roles
163
+ end
164
+
165
+ # @return [true, false] if this user is a Nitro Booster of this server.
166
+ def boosting?
167
+ !@boosting_since.nil?
168
+ end
169
+
170
+ # @return [true, false] whether this member is the server owner.
171
+ def owner?
172
+ server.owner == self
173
+ end
174
+
175
+ # @param role [Role, String, Integer] the role to check or its ID.
176
+ # @return [true, false] whether this member has the specified role.
177
+ def role?(role)
178
+ role = role.resolve_id
179
+ roles.any?(role)
180
+ end
181
+
182
+ # @see Member#set_roles
183
+ def roles=(role)
184
+ set_roles(role)
185
+ end
186
+
187
+ # Check if the current user has communication disabled.
188
+ # @return [true, false]
189
+ def communication_disabled?
190
+ !@communication_disabled_until.nil? && @communication_disabled_until > Time.now
191
+ end
192
+
193
+ alias_method :timeout?, :communication_disabled?
194
+
195
+ # Set a user's timeout duration, or remove it by setting the timeout to `nil`.
196
+ # @param timeout_until [Time, nil] When the timeout will end.
197
+ def communication_disabled_until=(timeout_until)
198
+ raise ArgumentError, 'A time out cannot exceed 28 days' if timeout_until && timeout_until > (Time.now + 2_419_200)
199
+
200
+ update_member_data(communication_disabled_until: timeout_until&.iso8601)
201
+ end
202
+
203
+ alias_method :timeout=, :communication_disabled_until=
204
+
205
+ # Bulk sets a member's roles.
206
+ # @param role [Role, Array<Role>] The role(s) to set.
207
+ # @param reason [String] The reason the user's roles are being changed.
208
+ def set_roles(role, reason = nil)
209
+ role_ids = role_id_array(role)
210
+ update_member_data(roles: role_ids, reason: reason)
211
+ end
212
+
213
+ # Adds and removes roles from a member.
214
+ # @param add [Role, Array<Role>] The role(s) to add.
215
+ # @param remove [Role, Array<Role>] The role(s) to remove.
216
+ # @param reason [String] The reason the user's roles are being changed.
217
+ # @example Remove the 'Member' role from a user, and add the 'Muted' role to them.
218
+ # to_add = server.roles.find {|role| role.name == 'Muted'}
219
+ # to_remove = server.roles.find {|role| role.name == 'Member'}
220
+ # member.modify_roles(to_add, to_remove)
221
+ def modify_roles(add, remove, reason = nil)
222
+ add_role_ids = role_id_array(add)
223
+ remove_role_ids = role_id_array(remove)
224
+ old_role_ids = resolve_role_ids
225
+ new_role_ids = (old_role_ids - remove_role_ids + add_role_ids).uniq
226
+
227
+ update_member_data(roles: new_role_ids, reason: reason)
228
+ end
229
+
230
+ # Adds one or more roles to this member.
231
+ # @param role [Role, Array<Role, String, Integer>, String, Integer] The role(s), or their ID(s), to add.
232
+ # @param reason [String] The reason the user's roles are being changed.
233
+ def add_role(role, reason = nil)
234
+ role_ids = role_id_array(role)
235
+
236
+ if role_ids.one?
237
+ API::Server.add_member_role(@bot.token, @server_id, @user.id, role_ids[0], reason)
238
+ else
239
+ old_role_ids = resolve_role_ids
240
+ new_role_ids = (old_role_ids + role_ids).uniq
241
+ update_member_data(roles: new_role_ids, reason: reason)
242
+ end
243
+ end
244
+
245
+ # Removes one or more roles from this member.
246
+ # @param role [Role, Array<Role>] The role(s) to remove.
247
+ # @param reason [String] The reason the user's roles are being changed.
248
+ def remove_role(role, reason = nil)
249
+ role_ids = role_id_array(role)
250
+
251
+ if role_ids.one?
252
+ API::Server.remove_member_role(@bot.token, @server_id, @user.id, role_ids[0], reason)
253
+ else
254
+ old_role_ids = resolve_role_ids
255
+ new_role_ids = old_role_ids.reject { |i| role_ids.include?(i) }
256
+ update_member_data(roles: new_role_ids, reason: reason)
257
+ end
258
+ end
259
+
260
+ # @return [Role] the highest role this member has.
261
+ def highest_role
262
+ roles.max_by(&:position)
263
+ end
264
+
265
+ # @return [Role, nil] the role this member is being hoisted with.
266
+ def hoist_role
267
+ hoisted_roles = roles.select(&:hoist)
268
+ return nil if hoisted_roles.empty?
269
+
270
+ hoisted_roles.max_by(&:position)
271
+ end
272
+
273
+ # @return [Role, nil] the role this member is basing their colour on.
274
+ def colour_role
275
+ coloured_roles = roles.select { |v| v.colour.combined.nonzero? }
276
+ return nil if coloured_roles.empty?
277
+
278
+ coloured_roles.max_by(&:position)
279
+ end
280
+
281
+ alias_method :color_role, :colour_role
282
+
283
+ # @return [ColourRGB, nil] the colour this member has.
284
+ def colour
285
+ return nil unless colour_role
286
+
287
+ colour_role.color
288
+ end
289
+
290
+ alias_method :color, :colour
291
+
292
+ # Get the member's roles sorted by their order in the hierarchy.
293
+ # @return [Array<Role>] the roles the member has, ordered by hierarchy.
294
+ def sort_roles
295
+ roles.sort_by { |role| [role.position, role.id] }
296
+ end
297
+
298
+ # Server deafens this member.
299
+ # @param reason [String, nil] The reason for defeaning this member.
300
+ def server_deafen(reason: nil)
301
+ update_member_data(deaf: true, reason: reason)
302
+ end
303
+
304
+ # Server undeafens this member.
305
+ # @param reason [String, nil] The reason for un-defeaning this member.
306
+ def server_undeafen(reason: nil)
307
+ update_member_data(deaf: false, reason: reason)
308
+ end
309
+
310
+ # Server mutes this member.
311
+ # @param reason [String, nil] The reason for muting this member.
312
+ def server_mute(reason: nil)
313
+ update_member_data(mute: true, reason: reason)
314
+ end
315
+
316
+ # Server unmutes this member.
317
+ # @param reason [String, nil] The reason for un-muting this member.
318
+ def server_unmute(reason: nil)
319
+ update_member_data(mute: false, reason: reason)
320
+ end
321
+
322
+ # Bans this member from the server.
323
+ # @param message_days [Integer] How many days worth of messages sent by the member should be deleted. This parameter is deprecated and will be removed in 4.0.
324
+ # @param message_seconds [Integer] How many seconds worth of messages sent by the member should be deleted.
325
+ # @param reason [String] The reason this member is being banned.
326
+ def ban(message_days = 0, message_seconds: nil, reason: nil)
327
+ server.ban(@user, message_days, message_seconds: message_seconds, reason: reason)
328
+ end
329
+
330
+ # Unbans this member from the server.
331
+ # @param reason [String] The reason this member is being unbanned.
332
+ def unban(reason = nil)
333
+ server.unban(@user, reason)
334
+ end
335
+
336
+ # Kicks this member from the server.
337
+ # @param reason [String] The reason this member is being kicked.
338
+ def kick(reason = nil)
339
+ server.kick(@user, reason)
340
+ end
341
+
342
+ # @see Member#set_nick
343
+ def nick=(nick)
344
+ set_nick(nick)
345
+ end
346
+
347
+ alias_method :nickname=, :nick=
348
+
349
+ # Sets or resets this member's nickname. Requires the Change Nickname permission for the bot itself and Manage
350
+ # Nicknames for other users.
351
+ # @param nick [String, nil] The string to set the nickname to, or nil if it should be reset.
352
+ # @param reason [String] The reason the user's nickname is being changed.
353
+ def set_nick(nick, reason = nil)
354
+ if @user.current_bot?
355
+ update_current_member_data(nick: nick, reason: reason)
356
+ else
357
+ update_member_data(nick: nick, reason: reason)
358
+ end
359
+ end
360
+
361
+ alias_method :set_nickname, :set_nick
362
+
363
+ # @return [String] the name the user displays as (nickname if they have one, global_name if they have one, username otherwise)
364
+ def display_name
365
+ nickname || global_name || username
366
+ end
367
+
368
+ # @param format [String, nil] If `nil`, the URL will default to `webp` for static avatars, and will detect if the member has a `gif` avatar. You can otherwise specify one of `webp`, `jpg`, `png`, or `gif` to override this.
369
+ # @return [String, nil] the avatar that the user has displayed (server avatar if they have one, user avatar if they have one, nil otherwise)
370
+ def display_avatar_url(format = nil)
371
+ server_avatar_url(format) || avatar_url(format)
372
+ end
373
+
374
+ # @param format [String, nil] If `nil`, the URL will default to `webp` for static banners, and will detect if the member has a `gif` banner. You can otherwise specify one of `webp`, `jpg`, `png`, or `gif` to override this.
375
+ # @return [String, nil] the banner that the user has displayed (server banner if they have one, user banner if they have one, nil otherwise)
376
+ def display_banner_url(format = nil)
377
+ server_banner_url(format) || banner_url(format)
378
+ end
379
+
380
+ # @return [AvatarDecoration, nil] the avatar decoration that the user displays (server avatar decoration if they have one, user avatar decoration if they have one, nil otherwise)
381
+ def display_avatar_decoration
382
+ server_avatar_decoration || avatar_decoration
383
+ end
384
+
385
+ # Set the flags for this member.
386
+ # @param flags [Integer, nil] The new bitwise value of flags for this member, or nil.
387
+ def flags=(flags)
388
+ update_member_data(flags: flags)
389
+ end
390
+
391
+ # Set the server banner for the current bot.
392
+ # @param banner [File, nil] A file like object that responds to read, or `nil`.
393
+ def server_banner=(banner)
394
+ raise 'Can only set a banner for the current bot' unless current_bot?
395
+
396
+ update_current_member_data(banner: banner.respond_to?(:read) ? OnyxCord.encode64(banner) : banner)
397
+ end
398
+
399
+ # Set the server avatar for the current bot.
400
+ # @param avatar [File, nil] A file like object that responds to read, or `nil`.
401
+ def server_avatar=(avatar)
402
+ raise 'Can only set an avatar for the current bot' unless current_bot?
403
+
404
+ update_current_member_data(avatar: avatar.respond_to?(:read) ? OnyxCord.encode64(avatar) : avatar)
405
+ end
406
+
407
+ # Set the server bio for the current bot.
408
+ # @param bio [String, nil] The new server bio for the bot, or nil.
409
+ def server_bio=(bio)
410
+ raise 'Can only set a bio for the current bot' unless current_bot?
411
+
412
+ update_current_member_data(bio: bio)
413
+ end
414
+
415
+ # Update this member's roles
416
+ # @note For internal use only.
417
+ # @!visibility private
418
+ def update_roles(role_ids)
419
+ @roles = [server.role(@server_id)]
420
+ role_ids.each do |id|
421
+ # It is possible for members to have roles that do not exist
422
+ # on the server any longer. See https://github.com/onyxcord/onyxcord/issues/371
423
+ role = server.role(id)
424
+ @roles << role if role
425
+ end
426
+ end
427
+
428
+ # Update this member's nick
429
+ # @note For internal use only.
430
+ # @!visibility private
431
+ def update_nick(nick)
432
+ @nick = nick
433
+ end
434
+
435
+ # Update this member's boosting timestamp
436
+ # @note For internal user only.
437
+ # @!visibility private
438
+ def update_boosting_since(time)
439
+ @boosting_since = time
440
+ end
441
+
442
+ # @!visibility private
443
+ def update_communication_disabled_until(time)
444
+ time = time ? Time.parse(time) : nil
445
+ @communication_disabled_until = time
446
+ end
447
+
448
+ # Update this member
449
+ # @note For internal use only.
450
+ # @!visibility private
451
+ def update_data(data)
452
+ update_roles(data['roles']) if data['roles']
453
+ @nick = data['nick'] if data.key?('nick')
454
+ @mute = data['mute'] if data.key?('mute')
455
+ @deaf = data['deaf'] if data.key?('deaf')
456
+ @server_avatar_id = data['avatar'] if data.key?('avatar')
457
+ @server_banner_id = data['banner'] if data.key?('banner')
458
+ @flags = data['flags'] if data.key?('flags')
459
+ @pending = data['pending'] if data.key?('pending')
460
+
461
+ @joined_at = Time.parse(data['joined_at']) if data['joined_at']
462
+
463
+ if data.key?('communication_disabled_until')
464
+ timeout_until = data['communication_disabled_until']
465
+ @communication_disabled_until = timeout_until ? Time.parse(timeout_until) : nil
466
+ end
467
+
468
+ if data.key?('premium_since')
469
+ @boosting_since = data['premium_since'] ? Time.parse(data['premium_since']) : nil
470
+ end
471
+
472
+ if (user = data['user'])
473
+ @user.update_global_name(user['global_name']) if user['global_name']
474
+ @user.avatar_id = user['avatar'] if user.key('avatar')
475
+ @user.update_avatar_decoration(user['avatar_decoration_data']) if user.key?('avatar_decoration_data')
476
+ @user.update_collectibles(user['collectibles']) if user.key?('collectibles')
477
+ @user.update_primary_server(user['primary_guild']) if user.key?('primary_guild')
478
+ end
479
+
480
+ @server_collectibles = Collectibles.new(data['collectibles'] || {}, @bot) if data.key?('collectibles')
481
+ @server_avatar_decoration = process_avatar_decoration(data['avatar_decoration_data']) if data.key?('avatar_decoration_data')
482
+ end
483
+
484
+ include PermissionCalculator
485
+
486
+ # Overwriting inspect for debug purposes
487
+ def inspect
488
+ "<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}>"
489
+ end
490
+
491
+ private
492
+
493
+ # Utility method to get a list of role IDs from one role or an array of roles
494
+ def role_id_array(role)
495
+ if role.is_a? Array
496
+ role.map(&:resolve_id)
497
+ else
498
+ [role.resolve_id]
499
+ end
500
+ end
501
+
502
+ # Utility method to get data out of this member's voice state
503
+ def voice_state_attribute(name)
504
+ voice_state = server.voice_states[@user.id]
505
+ voice_state&.send name
506
+ end
507
+
508
+ # @!visibility private
509
+ def resolve_role_ids
510
+ @roles ? @roles.collect(&:id) : @role_ids
511
+ end
512
+
513
+ # @!visibility private
514
+ def update_member_data(new_data)
515
+ update_data(JSON.parse(API::Server.update_member(@bot.token, @server_id, @user.id, **new_data)))
516
+ end
517
+
518
+ # @!visibility private
519
+ def update_current_member_data(new_data)
520
+ update_data(JSON.parse(API::Server.update_current_member(@bot.token, @server_id,
521
+ new_data.key?(:nick) ? new_data[:nick] : :undef,
522
+ new_data[:reason],
523
+ new_data.key?(:bio) ? new_data[:bio] : :undef,
524
+ new_data.key?(:banner) ? new_data[:banner] : :undef,
525
+ new_data.key?(:avatar) ? new_data[:avatar] : :undef)))
526
+ end
527
+ end
528
+ end