discordrb 1.8.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of discordrb might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.overcommit.yml +7 -0
- data/.rubocop.yml +5 -4
- data/CHANGELOG.md +77 -0
- data/README.md +25 -15
- data/discordrb.gemspec +2 -3
- data/examples/commands.rb +14 -2
- data/examples/ping.rb +1 -1
- data/examples/pm_send.rb +1 -1
- data/lib/discordrb.rb +9 -0
- data/lib/discordrb/api.rb +176 -50
- data/lib/discordrb/await.rb +3 -0
- data/lib/discordrb/bot.rb +607 -372
- data/lib/discordrb/cache.rb +208 -0
- data/lib/discordrb/commands/command_bot.rb +50 -18
- data/lib/discordrb/commands/container.rb +11 -2
- data/lib/discordrb/commands/events.rb +2 -0
- data/lib/discordrb/commands/parser.rb +10 -8
- data/lib/discordrb/commands/rate_limiter.rb +2 -0
- data/lib/discordrb/container.rb +24 -25
- data/lib/discordrb/data.rb +521 -219
- data/lib/discordrb/errors.rb +6 -7
- data/lib/discordrb/events/await.rb +2 -0
- data/lib/discordrb/events/bans.rb +3 -1
- data/lib/discordrb/events/channels.rb +124 -0
- data/lib/discordrb/events/generic.rb +2 -0
- data/lib/discordrb/events/guilds.rb +16 -13
- data/lib/discordrb/events/lifetime.rb +12 -2
- data/lib/discordrb/events/members.rb +26 -15
- data/lib/discordrb/events/message.rb +20 -7
- data/lib/discordrb/events/presence.rb +18 -2
- data/lib/discordrb/events/roles.rb +83 -0
- data/lib/discordrb/events/typing.rb +15 -2
- data/lib/discordrb/events/voice_state_update.rb +2 -0
- data/lib/discordrb/light.rb +8 -0
- data/lib/discordrb/light/data.rb +62 -0
- data/lib/discordrb/light/integrations.rb +73 -0
- data/lib/discordrb/light/light_bot.rb +56 -0
- data/lib/discordrb/logger.rb +4 -0
- data/lib/discordrb/permissions.rb +16 -12
- data/lib/discordrb/token_cache.rb +3 -0
- data/lib/discordrb/version.rb +3 -1
- data/lib/discordrb/voice/encoder.rb +2 -0
- data/lib/discordrb/voice/network.rb +21 -14
- data/lib/discordrb/voice/voice_bot.rb +26 -3
- data/lib/discordrb/websocket.rb +69 -0
- metadata +15 -26
- data/lib/discordrb/events/channel_create.rb +0 -44
- data/lib/discordrb/events/channel_delete.rb +0 -44
- data/lib/discordrb/events/channel_update.rb +0 -46
- data/lib/discordrb/events/guild_role_create.rb +0 -35
- data/lib/discordrb/events/guild_role_delete.rb +0 -36
- data/lib/discordrb/events/guild_role_update.rb +0 -35
data/lib/discordrb/container.rb
CHANGED
@@ -1,15 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'discordrb/events/message'
|
2
4
|
require 'discordrb/events/typing'
|
3
5
|
require 'discordrb/events/lifetime'
|
4
6
|
require 'discordrb/events/presence'
|
5
7
|
require 'discordrb/events/voice_state_update'
|
6
|
-
require 'discordrb/events/
|
7
|
-
require 'discordrb/events/channel_update'
|
8
|
-
require 'discordrb/events/channel_delete'
|
8
|
+
require 'discordrb/events/channels'
|
9
9
|
require 'discordrb/events/members'
|
10
|
-
require 'discordrb/events/
|
11
|
-
require 'discordrb/events/guild_role_delete'
|
12
|
-
require 'discordrb/events/guild_role_update'
|
10
|
+
require 'discordrb/events/roles'
|
13
11
|
require 'discordrb/events/guilds'
|
14
12
|
require 'discordrb/events/await'
|
15
13
|
require 'discordrb/events/bans'
|
@@ -189,30 +187,30 @@ module Discordrb
|
|
189
187
|
# @param attributes [Hash] The event's attributes.
|
190
188
|
# @option attributes [String] :username Matches the username of the joined user.
|
191
189
|
# @yield The block is executed when the event is raised.
|
192
|
-
# @yieldparam event [
|
193
|
-
# @return [
|
190
|
+
# @yieldparam event [ServerMemberAddEvent] The event that was raised.
|
191
|
+
# @return [ServerMemberAddEventHandler] The event handler that was registered.
|
194
192
|
def member_join(attributes = {}, &block)
|
195
|
-
register_event(
|
193
|
+
register_event(ServerMemberAddEvent, attributes, block)
|
196
194
|
end
|
197
195
|
|
198
196
|
# This **event** is raised when a member update happens.
|
199
197
|
# @param attributes [Hash] The event's attributes.
|
200
198
|
# @option attributes [String] :username Matches the username of the updated user.
|
201
199
|
# @yield The block is executed when the event is raised.
|
202
|
-
# @yieldparam event [
|
203
|
-
# @return [
|
200
|
+
# @yieldparam event [ServerMemberUpdateEvent] The event that was raised.
|
201
|
+
# @return [ServerMemberUpdateEventHandler] The event handler that was registered.
|
204
202
|
def member_update(attributes = {}, &block)
|
205
|
-
register_event(
|
203
|
+
register_event(ServerMemberUpdateEvent, attributes, block)
|
206
204
|
end
|
207
205
|
|
208
206
|
# This **event** is raised when a member leaves a server.
|
209
207
|
# @param attributes [Hash] The event's attributes.
|
210
208
|
# @option attributes [String] :username Matches the username of the member.
|
211
209
|
# @yield The block is executed when the event is raised.
|
212
|
-
# @yieldparam event [
|
213
|
-
# @return [
|
210
|
+
# @yieldparam event [ServerMemberDeleteEvent] The event that was raised.
|
211
|
+
# @return [ServerMemberDeleteEventHandler] The event handler that was registered.
|
214
212
|
def member_leave(attributes = {}, &block)
|
215
|
-
register_event(
|
213
|
+
register_event(ServerMemberDeleteEvent, attributes, block)
|
216
214
|
end
|
217
215
|
|
218
216
|
# This **event** is raised when a user is banned from a server.
|
@@ -243,20 +241,20 @@ module Discordrb
|
|
243
241
|
# @param attributes [Hash] The event's attributes.
|
244
242
|
# @option attributes [String, Integer, Server] :server Matches the server that was created.
|
245
243
|
# @yield The block is executed when the event is raised.
|
246
|
-
# @yieldparam event [
|
247
|
-
# @return [
|
244
|
+
# @yieldparam event [ServerCreateEvent] The event that was raised.
|
245
|
+
# @return [ServerCreateEventHandler] The event handler that was registered.
|
248
246
|
def server_create(attributes = {}, &block)
|
249
|
-
register_event(
|
247
|
+
register_event(ServerCreateEvent, attributes, block)
|
250
248
|
end
|
251
249
|
|
252
250
|
# This **event** is raised when a server is updated, for example if the name or region has changed.
|
253
251
|
# @param attributes [Hash] The event's attributes.
|
254
252
|
# @option attributes [String, Integer, Server] :server Matches the server that was updated.
|
255
253
|
# @yield The block is executed when the event is raised.
|
256
|
-
# @yieldparam event [
|
257
|
-
# @return [
|
254
|
+
# @yieldparam event [ServerUpdateEvent] The event that was raised.
|
255
|
+
# @return [ServerUpdateEventHandler] The event handler that was registered.
|
258
256
|
def server_update(attributes = {}, &block)
|
259
|
-
register_event(
|
257
|
+
register_event(ServerUpdateEvent, attributes, block)
|
260
258
|
end
|
261
259
|
|
262
260
|
# This **event** is raised when a server is deleted, or when the bot leaves a server. (These two cases are identical
|
@@ -264,10 +262,10 @@ module Discordrb
|
|
264
262
|
# @param attributes [Hash] The event's attributes.
|
265
263
|
# @option attributes [String, Integer, Server] :server Matches the server that was deleted.
|
266
264
|
# @yield The block is executed when the event is raised.
|
267
|
-
# @yieldparam event [
|
268
|
-
# @return [
|
265
|
+
# @yieldparam event [ServerDeleteEvent] The event that was raised.
|
266
|
+
# @return [ServerDeleteEventHandler] The event handler that was registered.
|
269
267
|
def server_delete(attributes = {}, &block)
|
270
|
-
register_event(
|
268
|
+
register_event(ServerDeleteEvent, attributes, block)
|
271
269
|
end
|
272
270
|
|
273
271
|
# This **event** is raised when an {Await} is triggered. It provides an easy way to execute code
|
@@ -330,7 +328,8 @@ module Discordrb
|
|
330
328
|
# @param container [Module] A module that `extend`s {EventContainer} from which the handlers will be added.
|
331
329
|
def include_events(container)
|
332
330
|
handlers = container.instance_variable_get '@event_handlers'
|
333
|
-
|
331
|
+
return unless handlers
|
332
|
+
|
334
333
|
@event_handlers ||= {}
|
335
334
|
@event_handlers.merge!(handlers) { |_, old, new| old + new }
|
336
335
|
end
|
data/lib/discordrb/data.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# These classes hold relevant Discord data, such as messages or channels.
|
2
4
|
|
3
5
|
require 'ostruct'
|
@@ -10,13 +12,55 @@ require 'base64'
|
|
10
12
|
# Discordrb module
|
11
13
|
module Discordrb
|
12
14
|
# The unix timestamp Discord IDs are based on
|
13
|
-
DISCORD_EPOCH =
|
15
|
+
DISCORD_EPOCH = 1_420_070_400_000
|
14
16
|
|
15
17
|
# Compares two objects based on IDs - either the objects' IDs are equal, or one object is equal to the other's ID.
|
16
18
|
def self.id_compare(one_id, other)
|
17
19
|
other.respond_to?(:resolve_id) ? (one_id.resolve_id == other.resolve_id) : (one_id == other)
|
18
20
|
end
|
19
21
|
|
22
|
+
# The maximum length a Discord message can have
|
23
|
+
CHARACTER_LIMIT = 2000
|
24
|
+
|
25
|
+
# Splits a message into chunks of 2000 characters. Attempts to split by lines if possible.
|
26
|
+
# @param msg [String] The message to split.
|
27
|
+
# @return [Array<String>] the message split into chunks
|
28
|
+
def self.split_message(msg)
|
29
|
+
# If the messages is empty, return an empty array
|
30
|
+
return [] if msg.empty?
|
31
|
+
|
32
|
+
# Split the message into lines
|
33
|
+
lines = msg.lines
|
34
|
+
|
35
|
+
# Turn the message into a "triangle" of consecutively longer slices, for example the array [1,2,3,4] would become
|
36
|
+
# [
|
37
|
+
# [1],
|
38
|
+
# [1, 2],
|
39
|
+
# [1, 2, 3],
|
40
|
+
# [1, 2, 3, 4]
|
41
|
+
# ]
|
42
|
+
tri = [*0..(lines.length - 1)].map { |i| lines.combination(i + 1).first }
|
43
|
+
|
44
|
+
# Join the individual elements together to get an array of strings with consecutively more lines
|
45
|
+
joined = tri.map { |e| e.join("\n") }
|
46
|
+
|
47
|
+
# Find the largest element that is still below the character limit, or if none such element exists return the first
|
48
|
+
ideal = joined.max_by { |e| e.length > CHARACTER_LIMIT ? -1 : e.length }
|
49
|
+
|
50
|
+
# If it's still larger than the character limit (none was smaller than it) split it into slices with the length
|
51
|
+
# being the character limit, otherwise just return an array with one element
|
52
|
+
ideal_ary = (ideal.length > CHARACTER_LIMIT) ? ideal.chars.each_slice(CHARACTER_LIMIT).map(&:join) : [ideal]
|
53
|
+
|
54
|
+
# Slice off the ideal part and strip newlines
|
55
|
+
rest = msg[ideal.length..-1].strip
|
56
|
+
|
57
|
+
# If none remains, return an empty array -> we're done
|
58
|
+
return [] unless rest
|
59
|
+
|
60
|
+
# Otherwise, call the method recursively to split the rest of the string and add it onto the ideal array
|
61
|
+
ideal_ary + split_message(rest)
|
62
|
+
end
|
63
|
+
|
20
64
|
# Mixin for objects that have IDs
|
21
65
|
module IDObject
|
22
66
|
# @return [Integer] the ID which uniquely identifies this object across Discord.
|
@@ -38,27 +82,49 @@ module Discordrb
|
|
38
82
|
end
|
39
83
|
end
|
40
84
|
|
41
|
-
#
|
42
|
-
|
43
|
-
include IDObject
|
44
|
-
|
85
|
+
# Mixin for the attributes users should have
|
86
|
+
module UserAttributes
|
45
87
|
# @return [String] this user's username
|
46
88
|
attr_reader :username
|
89
|
+
alias_method :name, :username
|
47
90
|
|
48
91
|
# @return [String] this user's discriminator which is used internally to identify users with identical usernames.
|
49
92
|
attr_reader :discriminator
|
93
|
+
alias_method :discrim, :discriminator
|
50
94
|
alias_method :tag, :discriminator
|
51
95
|
alias_method :discord_tag, :discriminator
|
52
96
|
|
97
|
+
# @return [true, false] whether this user is a Discord bot account
|
98
|
+
attr_reader :bot_account
|
99
|
+
alias_method :bot_account?, :bot_account
|
100
|
+
|
53
101
|
# @return [String] the ID of this user's current avatar, can be used to generate an avatar URL.
|
54
102
|
# @see #avatar_url
|
55
103
|
attr_reader :avatar_id
|
56
104
|
|
57
|
-
#
|
58
|
-
|
105
|
+
# Utility function to mention users in messages
|
106
|
+
# @return [String] the mention code in the form of <@id>
|
107
|
+
def mention
|
108
|
+
"<@#{@id}>"
|
109
|
+
end
|
59
110
|
|
60
|
-
#
|
61
|
-
|
111
|
+
# Utility function to get Discord's distinct representation of a user, i. e. username + discriminator
|
112
|
+
# @return [String] distinct representation of user
|
113
|
+
def distinct
|
114
|
+
"#{@username}##{@discriminator}"
|
115
|
+
end
|
116
|
+
|
117
|
+
# Utility function to get a user's avatar URL.
|
118
|
+
# @return [String] the URL to the avatar image.
|
119
|
+
def avatar_url
|
120
|
+
API.avatar_url(@id, @avatar_id)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# User on Discord, including internal data like discriminators
|
125
|
+
class User
|
126
|
+
include IDObject
|
127
|
+
include UserAttributes
|
62
128
|
|
63
129
|
# @!attribute [r] status
|
64
130
|
# @return [Symbol] the current online status of the user (`:online`, `:offline` or `:idle`)
|
@@ -68,16 +134,6 @@ module Discordrb
|
|
68
134
|
# @return [String, nil] the game the user is currently playing, or `nil` if none is being played.
|
69
135
|
attr_accessor :game
|
70
136
|
|
71
|
-
# @!attribute [r] self_mute
|
72
|
-
# @return [true, false] whether or not the user is currently muted by the bot.
|
73
|
-
attr_accessor :self_mute
|
74
|
-
|
75
|
-
# @todo Fix these (server_mute and _deaf should be server specific, not sure about self_deaf or what it does anyway)
|
76
|
-
# @!visibility private
|
77
|
-
attr_accessor :server_mute, :server_deaf, :self_deaf
|
78
|
-
|
79
|
-
alias_method :name, :username
|
80
|
-
|
81
137
|
def initialize(data, bot)
|
82
138
|
@bot = bot
|
83
139
|
|
@@ -87,26 +143,10 @@ module Discordrb
|
|
87
143
|
@avatar_id = data['avatar']
|
88
144
|
@roles = {}
|
89
145
|
|
90
|
-
@
|
91
|
-
|
92
|
-
|
93
|
-
# Gets the user's avatar ID.
|
94
|
-
# @deprecated Use {#avatar_id} instead.
|
95
|
-
def avatar
|
96
|
-
LOGGER.debug('Warning: Deprecated reader User.avatar was used! Use User.avatar_id (or User.avatar_url if you just want the URL) instead.', true)
|
97
|
-
@avatar_id
|
98
|
-
end
|
99
|
-
|
100
|
-
# Utility function to mention users in messages
|
101
|
-
# @return [String] the mention code in the form of <@id>
|
102
|
-
def mention
|
103
|
-
"<@#{@id}>"
|
104
|
-
end
|
146
|
+
@bot_account = false
|
147
|
+
@bot_account = true if data['bot']
|
105
148
|
|
106
|
-
|
107
|
-
# @return [String] the URL to the avatar image.
|
108
|
-
def avatar_url
|
109
|
-
API.avatar_url(@id, @avatar_id)
|
149
|
+
@status = :offline
|
110
150
|
end
|
111
151
|
|
112
152
|
# Get a user's PM channel or send them a PM
|
@@ -128,44 +168,6 @@ module Discordrb
|
|
128
168
|
end
|
129
169
|
end
|
130
170
|
|
131
|
-
# Changes a user's voice channel.
|
132
|
-
# @note For internal use only
|
133
|
-
# @!visibility private
|
134
|
-
def move(to_channel)
|
135
|
-
return if to_channel && to_channel.type != 'voice'
|
136
|
-
@voice_channel = to_channel
|
137
|
-
end
|
138
|
-
|
139
|
-
# Adds a role to this user on the specified server.
|
140
|
-
# @param server [Server] The server on which to add the role.
|
141
|
-
# @param role [Role] The role to add.
|
142
|
-
def add_role(server, role)
|
143
|
-
user_roles = @roles[server.id] || []
|
144
|
-
user_roles << role
|
145
|
-
ids = user_roles.map(&:id)
|
146
|
-
API.update_user_roles(@bot.token, server.id, @id, ids)
|
147
|
-
end
|
148
|
-
|
149
|
-
# Removes a role from this user on the specified server.
|
150
|
-
# @param server [Server] The server on which to remove the role.
|
151
|
-
# @param role [Role] The role to remove.
|
152
|
-
def remove_role(server, role)
|
153
|
-
user_roles = @roles[server.id] || []
|
154
|
-
|
155
|
-
# If the given role has an ID (i.e. is a Role object), then check whether its ID is equal, otherwise check whether it's equal directly
|
156
|
-
user_roles.delete_if { |e| e == role }
|
157
|
-
ids = user_roles.map(&:id)
|
158
|
-
API.update_user_roles(@bot.token, server.id, @id, ids)
|
159
|
-
end
|
160
|
-
|
161
|
-
# Set this user's roles in the cache
|
162
|
-
# @note For internal use only
|
163
|
-
# @!visibility private
|
164
|
-
def update_roles(server, roles)
|
165
|
-
@roles ||= {}
|
166
|
-
@roles[server.id] = roles
|
167
|
-
end
|
168
|
-
|
169
171
|
# Set the user's name
|
170
172
|
# @note for internal use only
|
171
173
|
# @!visibility private
|
@@ -173,24 +175,6 @@ module Discordrb
|
|
173
175
|
@username = username
|
174
176
|
end
|
175
177
|
|
176
|
-
# Merge this user's roles with the roles from another instance of this user (from another server)
|
177
|
-
# @note For internal use only
|
178
|
-
# @!visibility private
|
179
|
-
def merge_roles(server, roles)
|
180
|
-
@roles[server.id] = if @roles[server.id]
|
181
|
-
(@roles[server.id] + roles).uniq
|
182
|
-
else
|
183
|
-
roles
|
184
|
-
end
|
185
|
-
end
|
186
|
-
|
187
|
-
# Delete a specific server from the roles (in case a user leaves a server)
|
188
|
-
# @note For internal use only
|
189
|
-
# @!visibility private
|
190
|
-
def delete_roles(server_id)
|
191
|
-
@roles.delete(server_id)
|
192
|
-
end
|
193
|
-
|
194
178
|
# Add an await for a message from this user. Specifically, this adds a global await for a MessageEvent with this
|
195
179
|
# user's ID as a :from attribute.
|
196
180
|
# @see Bot#add_await
|
@@ -198,54 +182,218 @@ module Discordrb
|
|
198
182
|
@bot.add_await(key, Discordrb::Events::MessageEvent, { from: @id }.merge(attributes), &block)
|
199
183
|
end
|
200
184
|
|
185
|
+
# Gets the member this user is on a server
|
186
|
+
# @param server [Server] The server to get the member for
|
187
|
+
# @return [Member] this user as a member on a particular server
|
188
|
+
def on(server)
|
189
|
+
id = server.resolve_id
|
190
|
+
@bot.server(id).member(@id)
|
191
|
+
end
|
192
|
+
|
201
193
|
# Is the user the bot?
|
202
194
|
# @return [true, false] whether this user is the bot
|
203
|
-
def
|
204
|
-
@bot.
|
195
|
+
def current_bot?
|
196
|
+
@bot.profile.id == @id
|
205
197
|
end
|
206
198
|
|
207
|
-
#
|
199
|
+
# The inspect method is overwritten to give more useful output
|
200
|
+
def inspect
|
201
|
+
"<User username=#{@username} id=#{@id} discriminator=#{@discriminator}>"
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
# Mixin for the attributes members and private members should have
|
206
|
+
module MemberAttributes
|
207
|
+
# @return [true, false] whether this member is muted server-wide.
|
208
|
+
attr_reader :mute
|
209
|
+
alias_method :muted?, :mute
|
210
|
+
|
211
|
+
# @return [true, false] whether this member is deafened server-wide.
|
212
|
+
attr_reader :deaf
|
213
|
+
alias_method :deafened?, :deaf
|
214
|
+
|
215
|
+
# @return [Time] when this member joined the server.
|
216
|
+
attr_reader :joined_at
|
217
|
+
|
218
|
+
# @return [Array<Role>] the roles this member has.
|
219
|
+
attr_reader :roles
|
220
|
+
|
221
|
+
# @return [Server] the server this member is on.
|
222
|
+
attr_reader :server
|
223
|
+
|
224
|
+
# @return [Channel] the voice channel the user is in.
|
225
|
+
attr_reader :voice_channel
|
226
|
+
end
|
227
|
+
|
228
|
+
# Mixin to calculate resulting permissions from overrides etc.
|
229
|
+
module PermissionCalculator
|
230
|
+
# Checks whether this user can do the particular action, regardless of whether it has the permission defined,
|
231
|
+
# through for example being the server owner or having the Manage Roles permission
|
208
232
|
# @param action [Symbol] The permission that should be checked. See also {Permissions::Flags} for a list.
|
209
|
-
# @param server [Server] The server on which the permission should be checked.
|
210
233
|
# @param channel [Channel, nil] If channel overrides should be checked too, this channel specifies where the overrides should be checked.
|
211
234
|
# @return [true, false] whether or not this user has the permission.
|
212
|
-
def permission?(action,
|
235
|
+
def permission?(action, channel = nil)
|
236
|
+
# If the member is the server owner, it irrevocably has all permissions.
|
237
|
+
return true if owner?
|
238
|
+
|
239
|
+
# First, check whether the user has Manage Roles defined.
|
240
|
+
# (Coincidentally, Manage Permissions is the same permission as Manage Roles, and a
|
241
|
+
# Manage Permissions deny overwrite will override Manage Roles, so we can just check for
|
242
|
+
# Manage Roles once and call it a day.)
|
243
|
+
return true if defined_permission?(:manage_roles, channel)
|
244
|
+
|
245
|
+
# Otherwise, defer to defined_permission
|
246
|
+
defined_permission?(action, channel)
|
247
|
+
end
|
248
|
+
|
249
|
+
# Checks whether this user has a particular permission defined (i. e. not implicit, through for example
|
250
|
+
# Manage Roles)
|
251
|
+
# @param action [Symbol] The permission that should be checked. See also {Permissions::Flags} for a list.
|
252
|
+
# @param channel [Channel, nil] If channel overrides should be checked too, this channel specifies where the overrides should be checked.
|
253
|
+
# @return [true, false] whether or not this user has the permission defined.
|
254
|
+
def defined_permission?(action, channel = nil)
|
255
|
+
# Get the permission the user's roles have
|
256
|
+
role_permission = defined_role_permission?(action, channel)
|
257
|
+
|
258
|
+
# Once we have checked the role permission, we have to check the channel overrides for the
|
259
|
+
# specific user
|
260
|
+
user_specific_override = permission_overwrite(action, channel, id) # Use the ID reader as members have no ID instance variable
|
261
|
+
|
262
|
+
# Merge the two permissions - if an override is defined, it has to be allow, otherwise we only care about the role
|
263
|
+
return role_permission unless user_specific_override
|
264
|
+
user_specific_override == :allow
|
265
|
+
end
|
266
|
+
|
267
|
+
# Define methods for querying permissions
|
268
|
+
Discordrb::Permissions::Flags.each_value do |flag|
|
269
|
+
define_method "can_#{flag}?" do |channel = nil|
|
270
|
+
permission? flag, channel
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
private
|
275
|
+
|
276
|
+
def defined_role_permission?(action, channel)
|
213
277
|
# For each role, check if
|
214
278
|
# (1) the channel explicitly allows or permits an action for the role and
|
215
279
|
# (2) if the user is allowed to do the action if the channel doesn't specify
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
if allow.instance_variable_get("@#{action}")
|
224
|
-
channel_allow = true
|
225
|
-
elsif deny.instance_variable_get("@#{action}")
|
226
|
-
channel_allow = false
|
227
|
-
end
|
228
|
-
# If the channel has nothing to say on the matter, we can defer to the role itself
|
229
|
-
end
|
230
|
-
can_act = if channel_allow.nil?
|
231
|
-
role.permissions.instance_variable_get("@#{action}") || can_act
|
280
|
+
@roles.reduce(false) do |can_act, role|
|
281
|
+
# Get the override defined for the role on the channel
|
282
|
+
channel_allow = permission_overwrite(action, channel, role.id)
|
283
|
+
can_act = if channel_allow
|
284
|
+
# If the channel has an override, check whether it is an allow - if yes,
|
285
|
+
# the user can act, if not, it can't
|
286
|
+
channel_allow == :allow
|
232
287
|
else
|
233
|
-
|
288
|
+
# Otherwise defer to the role
|
289
|
+
role.permissions.instance_variable_get("@#{action}") || can_act
|
234
290
|
end
|
235
291
|
can_act
|
236
292
|
end
|
237
293
|
end
|
238
294
|
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
295
|
+
def permission_overwrite(action, channel, id)
|
296
|
+
# If no overwrites are defined, or no channel is set, no overwrite will be present
|
297
|
+
return nil unless channel && channel.permission_overwrites[id]
|
298
|
+
|
299
|
+
# Otherwise, check the allow and deny objects
|
300
|
+
allow = channel.permission_overwrites[id].allow
|
301
|
+
deny = channel.permission_overwrites[id].deny
|
302
|
+
if allow.instance_variable_get("@#{action}")
|
303
|
+
:allow
|
304
|
+
elsif deny.instance_variable_get("@#{action}")
|
305
|
+
:deny
|
243
306
|
end
|
307
|
+
|
308
|
+
# If there's no variable defined, nil will implicitly be returned
|
244
309
|
end
|
310
|
+
end
|
245
311
|
|
246
|
-
|
312
|
+
# A member is a user on a server. It differs from regular users in that it has roles, voice statuses and things like
|
313
|
+
# that.
|
314
|
+
class Member < DelegateClass(User)
|
315
|
+
include MemberAttributes
|
316
|
+
|
317
|
+
# @!visibility private
|
318
|
+
def initialize(data, server, bot)
|
319
|
+
@bot = bot
|
320
|
+
|
321
|
+
@user = bot.ensure_user(data['user'])
|
322
|
+
super @user # Initialize the delegate class
|
323
|
+
|
324
|
+
# Somehow, Discord doesn't send the server ID in the standard member format...
|
325
|
+
raise ArgumentError, 'Cannot create a member without any information about the server!' if server.nil? && data['guild_id'].nil?
|
326
|
+
@server = server || bot.server(data['guild_id'].to_i)
|
327
|
+
|
328
|
+
# Initialize the roles by getting the roles from the server one-by-one
|
329
|
+
update_roles(data['roles'])
|
330
|
+
|
331
|
+
@deaf = data['deaf']
|
332
|
+
@mute = data['mute']
|
333
|
+
@joined_at = data['joined_at'] ? Time.parse(data['joined_at']) : nil
|
334
|
+
end
|
335
|
+
|
336
|
+
# @return [true, false] whether this member is the server owner.
|
337
|
+
def owner?
|
338
|
+
@server.owner == self
|
339
|
+
end
|
340
|
+
|
341
|
+
# Update this member's roles
|
342
|
+
# @note For internal use only.
|
343
|
+
# @!visibility private
|
344
|
+
def update_roles(roles)
|
345
|
+
@roles = roles.map do |role_id|
|
346
|
+
@server.role(role_id.to_i)
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
# Update this member's voice state
|
351
|
+
# @note For internal use only.
|
352
|
+
# @!visibility private
|
353
|
+
def update_voice_state(channel, mute, deaf, self_mute, self_deaf)
|
354
|
+
@voice_channel = channel
|
355
|
+
@mute = mute
|
356
|
+
@deaf = deaf
|
357
|
+
@self_mute = self_mute
|
358
|
+
@self_deaf = self_deaf
|
359
|
+
end
|
360
|
+
|
361
|
+
include PermissionCalculator
|
362
|
+
|
363
|
+
# Overwriting inspect for debug purposes
|
247
364
|
def inspect
|
248
|
-
"<
|
365
|
+
"<Member user=#{@user.inspect} server=#{@server.inspect} joined_at=#{@joined_at} roles=#{@roles.inspect} voice_channel=#{@voice_channel.inspect} mute=#{@mute} deaf=#{@deaf} self_mute=#{@self_mute} self_deaf=#{@self_deaf}>"
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
# Recipients are members on private channels - they exist for completeness purposes, but all
|
370
|
+
# the attributes will be empty.
|
371
|
+
class Recipient < DelegateClass(User)
|
372
|
+
include MemberAttributes
|
373
|
+
|
374
|
+
# @return [Channel] the private channel this recipient is the recipient of.
|
375
|
+
attr_reader :channel
|
376
|
+
|
377
|
+
# @!visibility private
|
378
|
+
def initialize(user, channel, bot)
|
379
|
+
@bot = bot
|
380
|
+
@channel = channel
|
381
|
+
raise ArgumentError, 'Tried to create a recipient for a public channel!' unless @channel.private?
|
382
|
+
|
383
|
+
@user = user
|
384
|
+
super @user
|
385
|
+
|
386
|
+
# Member attributes
|
387
|
+
@mute = @deaf = @self_mute = @self_deaf = false
|
388
|
+
@voice_channel = nil
|
389
|
+
@server = nil
|
390
|
+
@roles = []
|
391
|
+
@joined_at = @channel.creation_time
|
392
|
+
end
|
393
|
+
|
394
|
+
# Overwriting inspect for debug purposes
|
395
|
+
def inspect
|
396
|
+
"<Recipient user=#{@user.inspect} channel=#{@channel.inspect}>"
|
249
397
|
end
|
250
398
|
end
|
251
399
|
|
@@ -260,38 +408,39 @@ module Discordrb
|
|
260
408
|
|
261
409
|
# Whether or not the user is the bot. The Profile can only ever be the bot user, so this always returns true.
|
262
410
|
# @return [true]
|
263
|
-
def
|
411
|
+
def current_bot?
|
264
412
|
true
|
265
413
|
end
|
266
414
|
|
267
415
|
# Sets the bot's username.
|
268
416
|
# @param username [String] The new username.
|
269
417
|
def username=(username)
|
270
|
-
|
418
|
+
update_profile_data(username: username)
|
271
419
|
end
|
272
420
|
|
273
421
|
# Sets the bot's email address. If you use this method, make sure that the login email in the script matches this
|
274
422
|
# one afterwards, so the bot doesn't have any trouble logging in in the future.
|
275
423
|
# @param email [String] The new email address.
|
276
424
|
def email=(email)
|
277
|
-
|
425
|
+
update_profile_data(email: email)
|
278
426
|
end
|
279
427
|
|
280
428
|
# Changes the bot's password. This will invalidate all tokens so you will have to relog the bot.
|
281
429
|
# @param password [String] The new password.
|
282
430
|
def password=(password)
|
283
|
-
|
431
|
+
update_profile_data(new_password: password)
|
284
432
|
end
|
285
433
|
|
286
434
|
# Changes the bot's avatar.
|
287
|
-
# @param avatar [String,
|
435
|
+
# @param avatar [String, #read] A JPG file to be used as the avatar, either
|
436
|
+
# something readable (e. g. File) or as a data URL.
|
288
437
|
def avatar=(avatar)
|
289
438
|
if avatar.respond_to? :read
|
290
439
|
avatar_string = 'data:image/jpg;base64,'
|
291
440
|
avatar_string += Base64.strict_encode64(avatar.read)
|
292
|
-
|
441
|
+
update_profile_data(avatar: avatar_string)
|
293
442
|
else
|
294
|
-
|
443
|
+
update_profile_data(avatar: avatar)
|
295
444
|
end
|
296
445
|
end
|
297
446
|
|
@@ -312,7 +461,7 @@ module Discordrb
|
|
312
461
|
|
313
462
|
private
|
314
463
|
|
315
|
-
def
|
464
|
+
def update_profile_data(new_data)
|
316
465
|
API.update_user(@bot.token,
|
317
466
|
new_data[:email] || @email,
|
318
467
|
@password,
|
@@ -525,14 +674,7 @@ module Discordrb
|
|
525
674
|
# @return [String] the type of this channel (currently either 'text' or 'voice')
|
526
675
|
attr_reader :type
|
527
676
|
|
528
|
-
# @
|
529
|
-
# channels (like Twitch subscriber ones). This appears to be a Discord bug that I can't reproduce myself, due to
|
530
|
-
# not having any integrations in place. If this occurs to you please tell me.
|
531
|
-
# @deprecated Use {#private?} instead, it's guaranteed to be accurate.
|
532
|
-
# @return [true, false] whether or not this channel is a private messaging channel.
|
533
|
-
attr_reader :is_private
|
534
|
-
|
535
|
-
# @return [User, nil] the recipient of the private messages, or nil if this is not a PM channel
|
677
|
+
# @return [Recipient, nil] the recipient of the private messages, or nil if this is not a PM channel
|
536
678
|
attr_reader :recipient
|
537
679
|
|
538
680
|
# @return [String] the channel's topic
|
@@ -546,7 +688,7 @@ module Discordrb
|
|
546
688
|
# @return [Hash<Integer => OpenStruct>] the channel's permission overwrites
|
547
689
|
attr_reader :permission_overwrites
|
548
690
|
|
549
|
-
# @return [true, false] whether or not this channel is a PM channel
|
691
|
+
# @return [true, false] whether or not this channel is a PM channel.
|
550
692
|
def private?
|
551
693
|
@server.nil?
|
552
694
|
end
|
@@ -570,12 +712,16 @@ module Discordrb
|
|
570
712
|
|
571
713
|
@is_private = data['is_private']
|
572
714
|
if @is_private
|
573
|
-
|
715
|
+
recipient_user = bot.ensure_user(data['recipient'])
|
716
|
+
@recipient = Recipient.new(recipient_user, self, bot)
|
574
717
|
@name = @recipient.username
|
575
718
|
else
|
576
719
|
@name = data['name']
|
577
|
-
@server =
|
578
|
-
|
720
|
+
@server = if server
|
721
|
+
server
|
722
|
+
else
|
723
|
+
bot.server(data['guild_id'].to_i)
|
724
|
+
end
|
579
725
|
end
|
580
726
|
|
581
727
|
# Populate permission overwrites
|
@@ -609,6 +755,18 @@ module Discordrb
|
|
609
755
|
@bot.send_message(@id, content, tts)
|
610
756
|
end
|
611
757
|
|
758
|
+
# Sends multiple messages to a channel
|
759
|
+
# @param content [Array<String>] The messages to send.
|
760
|
+
def send_multiple(content)
|
761
|
+
content.each { |e| send_message(e) }
|
762
|
+
end
|
763
|
+
|
764
|
+
# Splits a message into chunks whose length is at most the Discord character limit, then sends them individually.
|
765
|
+
# Useful for sending long messages, but be wary of rate limits!
|
766
|
+
def split_send(content)
|
767
|
+
send_multiple(Discordrb.split_message(content))
|
768
|
+
end
|
769
|
+
|
612
770
|
# Sends a file to this channel. If it is an image, it will be embedded.
|
613
771
|
# @param file [File] The file to send. There's no clear size limit for this, you'll have to attempt it for yourself (most non-image files are fine, large images may fail to embed)
|
614
772
|
def send_file(file)
|
@@ -641,20 +799,46 @@ module Discordrb
|
|
641
799
|
update_channel_data
|
642
800
|
end
|
643
801
|
|
802
|
+
# Defines a permission overwrite for this channel that sets the specified thing to the specified allow and deny
|
803
|
+
# permission sets, or change an existing one.
|
804
|
+
# @param thing [User, Role] What to define an overwrite for.
|
805
|
+
# @param allow [#bits, Permissions, Integer] The permission sets that should receive an `allow` override (i. e. a
|
806
|
+
# green checkmark on Discord)
|
807
|
+
# @param deny [#bits, Permissions, Integer] The permission sets that should receive a `deny` override (i. e. a red
|
808
|
+
# cross on Discord)
|
809
|
+
# @example Define a permission overwrite for a user that can then mention everyone and use TTS, but not create any invites
|
810
|
+
# allow = Discordrb::Permissions.new
|
811
|
+
# allow.can_mention_everyone = true
|
812
|
+
# allow.can_send_tts_messages = true
|
813
|
+
#
|
814
|
+
# deny = Discordrb::Permissions.new
|
815
|
+
# deny.can_create_instant_invite = true
|
816
|
+
#
|
817
|
+
# channel.define_overwrite(user, allow, deny)
|
818
|
+
def define_overwrite(thing, allow, deny)
|
819
|
+
allow_bits = allow.respond_to?(:bits) ? allow.bits : allow
|
820
|
+
deny_bits = deny.respond_to?(:bits) ? deny.bits : deny
|
821
|
+
|
822
|
+
if thing.is_a? User
|
823
|
+
API.update_user_overrides(@bot.token, @id, thing.id, allow_bits, deny_bits)
|
824
|
+
elsif thing.is_a? Role
|
825
|
+
API.update_role_overrides(@bot.token, @id, thing.id, allow_bits, deny_bits)
|
826
|
+
end
|
827
|
+
end
|
828
|
+
|
644
829
|
# Updates the cached data from another channel.
|
645
830
|
# @note For internal use only
|
646
831
|
# @!visibility private
|
647
832
|
def update_from(other)
|
648
833
|
@topic = other.topic
|
649
834
|
@name = other.name
|
650
|
-
@is_private = other.is_private
|
651
835
|
@recipient = other.recipient
|
652
836
|
@permission_overwrites = other.permission_overwrites
|
653
837
|
end
|
654
838
|
|
655
839
|
# The list of users currently in this channel. This is mostly useful for a voice channel, for a text channel it will
|
656
840
|
# just return the users on the server that are online.
|
657
|
-
# @return [Array<
|
841
|
+
# @return [Array<Member>] the users in this channel
|
658
842
|
def users
|
659
843
|
if @type == 'text'
|
660
844
|
@server.members.select { |u| u.status != :offline }
|
@@ -746,6 +930,53 @@ module Discordrb
|
|
746
930
|
end
|
747
931
|
end
|
748
932
|
|
933
|
+
# An attachment to a message
|
934
|
+
class Attachment
|
935
|
+
include IDObject
|
936
|
+
|
937
|
+
# @return [Message] the message this attachment belongs to.
|
938
|
+
attr_reader :message
|
939
|
+
|
940
|
+
# @return [String] the CDN URL this attachment can be downloaded at.
|
941
|
+
attr_reader :url
|
942
|
+
|
943
|
+
# @return [String] the attachment's proxy URL - I'm not sure what exactly this does, but I think it has something to
|
944
|
+
# do with CDNs
|
945
|
+
attr_reader :proxy_url
|
946
|
+
|
947
|
+
# @return [String] the attachment's filename.
|
948
|
+
attr_reader :filename
|
949
|
+
|
950
|
+
# @return [Integer] the attachment's file size in bytes.
|
951
|
+
attr_reader :size
|
952
|
+
|
953
|
+
# @return [Integer, nil] the width of an image file, in pixels, or nil if the file is not an image.
|
954
|
+
attr_reader :width
|
955
|
+
|
956
|
+
# @return [Integer, nil] the height of an image file, in pixels, or nil if the file is not an image.
|
957
|
+
attr_reader :height
|
958
|
+
|
959
|
+
# @!visibility private
|
960
|
+
def initialize(data, message, bot)
|
961
|
+
@bot = bot
|
962
|
+
@message = message
|
963
|
+
|
964
|
+
@url = data['url']
|
965
|
+
@proxy_url = data['proxy_url']
|
966
|
+
@filename = data['filename']
|
967
|
+
|
968
|
+
@size = data['size']
|
969
|
+
|
970
|
+
@width = data['width']
|
971
|
+
@height = data['height']
|
972
|
+
end
|
973
|
+
|
974
|
+
# @return [true, false] whether this file is an image file.
|
975
|
+
def image?
|
976
|
+
!(@width.nil? || @height.nil?)
|
977
|
+
end
|
978
|
+
end
|
979
|
+
|
749
980
|
# A message on Discord that was sent to a text channel
|
750
981
|
class Message
|
751
982
|
include IDObject
|
@@ -753,7 +984,7 @@ module Discordrb
|
|
753
984
|
# @return [String] the content of this message.
|
754
985
|
attr_reader :content
|
755
986
|
|
756
|
-
# @return [
|
987
|
+
# @return [Member] the user that sent this message.
|
757
988
|
attr_reader :author
|
758
989
|
|
759
990
|
# @return [Channel] the channel in which this message was sent.
|
@@ -765,6 +996,9 @@ module Discordrb
|
|
765
996
|
# @return [Array<User>] the users that were mentioned in this message.
|
766
997
|
attr_reader :mentions
|
767
998
|
|
999
|
+
# @return [Array<Attachment>] the files attached to this message.
|
1000
|
+
attr_reader :attachments
|
1001
|
+
|
768
1002
|
alias_method :user, :author
|
769
1003
|
alias_method :text, :content
|
770
1004
|
alias_method :to_s, :content
|
@@ -773,16 +1007,29 @@ module Discordrb
|
|
773
1007
|
def initialize(data, bot)
|
774
1008
|
@bot = bot
|
775
1009
|
@content = data['content']
|
776
|
-
@author = bot.user(data['author']['id'].to_i)
|
777
1010
|
@channel = bot.channel(data['channel_id'].to_i)
|
778
|
-
|
1011
|
+
|
1012
|
+
@author = if data['author']
|
1013
|
+
if @channel.private?
|
1014
|
+
# Turn the message user into a recipient - we can't use the channel recipient
|
1015
|
+
# directly because the bot may also send messages to the channel
|
1016
|
+
Recipient.new(bot.user(data['author']['id'].to_i), @channel, bot)
|
1017
|
+
else
|
1018
|
+
@channel.server.member(data['author']['id'].to_i)
|
1019
|
+
end
|
1020
|
+
end
|
1021
|
+
|
1022
|
+
@timestamp = Time.parse(data['timestamp']) if data['timestamp']
|
779
1023
|
@id = data['id'].to_i
|
780
1024
|
|
781
1025
|
@mentions = []
|
782
1026
|
|
783
1027
|
data['mentions'].each do |element|
|
784
|
-
@mentions <<
|
785
|
-
end
|
1028
|
+
@mentions << bot.ensure_user(element)
|
1029
|
+
end if data['mentions']
|
1030
|
+
|
1031
|
+
@attachments = []
|
1032
|
+
@attachments = data['attachments'].map { |e| Attachment.new(e, self, @bot) } if data['attachments']
|
786
1033
|
end
|
787
1034
|
|
788
1035
|
# Replies to this message with the specified content.
|
@@ -793,13 +1040,16 @@ module Discordrb
|
|
793
1040
|
|
794
1041
|
# Edits this message to have the specified content instead.
|
795
1042
|
# @param new_content [String] the new content the message should have.
|
1043
|
+
# @return [Message] the resulting message.
|
796
1044
|
def edit(new_content)
|
797
|
-
API.edit_message(@bot.token, @channel.id, @id, new_content)
|
1045
|
+
response = API.edit_message(@bot.token, @channel.id, @id, new_content)
|
1046
|
+
Message.new(JSON.parse(response), @bot)
|
798
1047
|
end
|
799
1048
|
|
800
1049
|
# Deletes this message.
|
801
1050
|
def delete
|
802
1051
|
API.delete_message(@bot.token, @channel.id, @id)
|
1052
|
+
nil
|
803
1053
|
end
|
804
1054
|
|
805
1055
|
# Add an {Await} for a message with the same user and channel.
|
@@ -810,7 +1060,7 @@ module Discordrb
|
|
810
1060
|
|
811
1061
|
# @return [true, false] whether this message was sent by the current {Bot}.
|
812
1062
|
def from_bot?
|
813
|
-
@author.
|
1063
|
+
@author && @author.current_bot?
|
814
1064
|
end
|
815
1065
|
|
816
1066
|
# The inspect method is overwritten to give more useful output
|
@@ -819,27 +1069,32 @@ module Discordrb
|
|
819
1069
|
end
|
820
1070
|
end
|
821
1071
|
|
1072
|
+
# Basic attributes a server should have
|
1073
|
+
module ServerAttributes
|
1074
|
+
# @return [String] this server's name.
|
1075
|
+
attr_reader :name
|
1076
|
+
|
1077
|
+
# @return [String] the hexadecimal ID used to identify this server's icon.
|
1078
|
+
attr_reader :icon_id
|
1079
|
+
|
1080
|
+
# Utility function to get the URL for the icon image
|
1081
|
+
# @return [String] the URL to the icon image
|
1082
|
+
def icon_url
|
1083
|
+
API.icon_url(@id, @icon_id)
|
1084
|
+
end
|
1085
|
+
end
|
1086
|
+
|
822
1087
|
# A server on Discord
|
823
1088
|
class Server
|
824
1089
|
include IDObject
|
1090
|
+
include ServerAttributes
|
825
1091
|
|
826
1092
|
# @return [String] the region the server is on (e. g. `amsterdam`).
|
827
1093
|
attr_reader :region
|
828
1094
|
|
829
|
-
# @return [
|
830
|
-
attr_reader :name
|
831
|
-
|
832
|
-
# @deprecated Use #owner instead, then get the resulting {User}'s ID.
|
833
|
-
# @return [Integer] the server owner's user ID.
|
834
|
-
attr_reader :owner_id
|
835
|
-
|
836
|
-
# @return [User] The server owner.
|
1095
|
+
# @return [Member] The server owner.
|
837
1096
|
attr_reader :owner
|
838
1097
|
|
839
|
-
# @return [Array<User>] an array of all the users on this server.
|
840
|
-
attr_reader :members
|
841
|
-
alias_method :users, :members
|
842
|
-
|
843
1098
|
# @return [Array<Channel>] an array of all the channels (text and voice) on this server.
|
844
1099
|
attr_reader :channels
|
845
1100
|
|
@@ -854,33 +1109,34 @@ module Discordrb
|
|
854
1109
|
# @return [Integer] the absolute number of members on this server, offline or not.
|
855
1110
|
attr_reader :member_count
|
856
1111
|
|
857
|
-
# @todo Make this behave like user.avatar where a URL is available as well.
|
858
|
-
# @return [String] the hexadecimal ID used to identify this server's icon.
|
859
|
-
attr_reader :icon
|
860
|
-
|
861
1112
|
# @return [Integer] the amount of time after which a voice user gets moved into the AFK channel, in seconds.
|
862
1113
|
attr_reader :afk_timeout
|
863
1114
|
|
864
|
-
# @
|
865
|
-
|
866
|
-
attr_reader :afk_channel_id
|
1115
|
+
# @return [Channel, nil] the AFK voice channel of this server, or nil if none is set
|
1116
|
+
attr_reader :afk_channel
|
867
1117
|
|
868
1118
|
# @!visibility private
|
869
1119
|
def initialize(data, bot)
|
870
1120
|
@bot = bot
|
871
1121
|
@owner_id = data['owner_id'].to_i
|
872
|
-
@owner = bot.user(@owner_id)
|
873
1122
|
@id = data['id'].to_i
|
874
1123
|
update_data(data)
|
875
1124
|
|
876
1125
|
@large = data['large']
|
877
1126
|
@member_count = data['member_count']
|
1127
|
+
@members = {}
|
878
1128
|
|
879
1129
|
process_roles(data['roles'])
|
880
1130
|
process_members(data['members'])
|
881
1131
|
process_presences(data['presences'])
|
882
1132
|
process_channels(data['channels'])
|
883
1133
|
process_voice_states(data['voice_states'])
|
1134
|
+
|
1135
|
+
# Whether this server's members have been chunked (resolved using op 8 and GUILD_MEMBERS_CHUNK) yet
|
1136
|
+
@chunked = false
|
1137
|
+
@processed_chunk_members = 0
|
1138
|
+
|
1139
|
+
@owner = member(@owner_id)
|
884
1140
|
end
|
885
1141
|
|
886
1142
|
# @return [Channel] The default channel on this server (usually called #general)
|
@@ -896,6 +1152,28 @@ module Discordrb
|
|
896
1152
|
@roles.find { |e| e.id == id }
|
897
1153
|
end
|
898
1154
|
|
1155
|
+
# Gets a member on this server based on user ID
|
1156
|
+
# @param id [Integer] The user ID to look for
|
1157
|
+
def member(id)
|
1158
|
+
id = id.resolve_id
|
1159
|
+
return @members[id] if member_cached?(id)
|
1160
|
+
|
1161
|
+
member = bot.member(@id, id)
|
1162
|
+
@members[id] = member
|
1163
|
+
end
|
1164
|
+
|
1165
|
+
# @return [Array<Member>] an array of all the members on this server.
|
1166
|
+
def members
|
1167
|
+
return @members.values if @chunked
|
1168
|
+
|
1169
|
+
@bot.debug("Members for server #{@id} not chunked yet - initiating")
|
1170
|
+
@bot.request_chunks(@id)
|
1171
|
+
sleep 0.05 until @chunked
|
1172
|
+
@members.values
|
1173
|
+
end
|
1174
|
+
|
1175
|
+
alias_method :users, :members
|
1176
|
+
|
899
1177
|
# Adds a role to the role cache
|
900
1178
|
# @note For internal use only
|
901
1179
|
# @!visibility private
|
@@ -908,9 +1186,9 @@ module Discordrb
|
|
908
1186
|
# @!visibility private
|
909
1187
|
def delete_role(role_id)
|
910
1188
|
@roles.reject! { |r| r.id == role_id }
|
911
|
-
@members.each do |
|
912
|
-
new_roles =
|
913
|
-
|
1189
|
+
@members.each do |_, member|
|
1190
|
+
new_roles = member.roles.reject { |r| r.id == role_id }
|
1191
|
+
member.update_roles(new_roles)
|
914
1192
|
end
|
915
1193
|
@channels.each do |channel|
|
916
1194
|
overwrites = channel.permission_overwrites.reject { |id, _| id == role_id }
|
@@ -918,26 +1196,40 @@ module Discordrb
|
|
918
1196
|
end
|
919
1197
|
end
|
920
1198
|
|
921
|
-
# Adds a
|
1199
|
+
# Adds a member to the member cache.
|
922
1200
|
# @note For internal use only
|
923
1201
|
# @!visibility private
|
924
|
-
def
|
925
|
-
@members
|
1202
|
+
def add_member(member)
|
1203
|
+
@members[member.id] = member
|
926
1204
|
@member_count += 1
|
927
1205
|
end
|
928
1206
|
|
929
|
-
# Removes a
|
1207
|
+
# Removes a member from the member cache.
|
930
1208
|
# @note For internal use only
|
931
1209
|
# @!visibility private
|
932
|
-
def
|
933
|
-
@members.
|
1210
|
+
def delete_member(user_id)
|
1211
|
+
@members.delete(user_id)
|
934
1212
|
@member_count -= 1
|
935
1213
|
end
|
936
1214
|
|
1215
|
+
# Checks whether a member is cached
|
1216
|
+
# @note For internal use only
|
1217
|
+
# @!visibility private
|
1218
|
+
def member_cached?(user_id)
|
1219
|
+
@members.include?(user_id)
|
1220
|
+
end
|
1221
|
+
|
1222
|
+
# Adds a member to the cache
|
1223
|
+
# @note For internal use only
|
1224
|
+
# @!visibility private
|
1225
|
+
def cache_member(member)
|
1226
|
+
@members[member.id] = member
|
1227
|
+
end
|
1228
|
+
|
937
1229
|
# Creates a channel on this server with the given name.
|
938
1230
|
# @return [Channel] the created channel.
|
939
|
-
def create_channel(name)
|
940
|
-
response = API.create_channel(@bot.token, @id, name,
|
1231
|
+
def create_channel(name, type = 'text')
|
1232
|
+
response = API.create_channel(@bot.token, @id, name, type)
|
941
1233
|
Channel.new(JSON.parse(response), @bot)
|
942
1234
|
end
|
943
1235
|
|
@@ -959,22 +1251,22 @@ module Discordrb
|
|
959
1251
|
end
|
960
1252
|
|
961
1253
|
# Bans a user from this server.
|
962
|
-
# @param user [User] The user to ban.
|
1254
|
+
# @param user [User, #resolve_id] The user to ban.
|
963
1255
|
# @param message_days [Integer] How many days worth of messages sent by the user should be deleted.
|
964
1256
|
def ban(user, message_days = 0)
|
965
|
-
API.ban_user(@bot.token, @id, user.
|
1257
|
+
API.ban_user(@bot.token, @id, user.resolve_id, message_days)
|
966
1258
|
end
|
967
1259
|
|
968
1260
|
# Unbans a previously banned user from this server.
|
969
|
-
# @param user [User] The user to unban.
|
1261
|
+
# @param user [User, #resolve_id] The user to unban.
|
970
1262
|
def unban(user)
|
971
|
-
API.unban_user(@bot.token, @id, user.
|
1263
|
+
API.unban_user(@bot.token, @id, user.resolve_id)
|
972
1264
|
end
|
973
1265
|
|
974
1266
|
# Kicks a user from this server.
|
975
|
-
# @param user [User] The user to kick.
|
1267
|
+
# @param user [User, #resolve_id] The user to kick.
|
976
1268
|
def kick(user)
|
977
|
-
API.kick_user(@bot.token, @id, user.
|
1269
|
+
API.kick_user(@bot.token, @id, user.resolve_id)
|
978
1270
|
end
|
979
1271
|
|
980
1272
|
# Forcibly moves a user into a different voice channel. Only works if the bot has the permission needed.
|
@@ -1013,10 +1305,15 @@ module Discordrb
|
|
1013
1305
|
end
|
1014
1306
|
|
1015
1307
|
# Sets the server's icon.
|
1016
|
-
# @
|
1017
|
-
# @param icon [String] The new icon, in base64-encoded JPG format.
|
1308
|
+
# @param icon [String, #read] The new icon, in base64-encoded JPG format.
|
1018
1309
|
def icon=(icon)
|
1019
|
-
|
1310
|
+
if icon.respond_to? :read
|
1311
|
+
icon_string = 'data:image/jpg;base64,'
|
1312
|
+
icon_string += Base64.strict_encode64(icon.read)
|
1313
|
+
update_server_data(icon: icon_string)
|
1314
|
+
else
|
1315
|
+
update_server_data(icon: icon)
|
1316
|
+
end
|
1020
1317
|
end
|
1021
1318
|
|
1022
1319
|
# Sets the server's AFK channel.
|
@@ -1025,28 +1322,41 @@ module Discordrb
|
|
1025
1322
|
update_server_data(afk_channel_id: afk_channel.resolve_id)
|
1026
1323
|
end
|
1027
1324
|
|
1028
|
-
# @deprecated Use #afk_channel= with the ID instead.
|
1029
|
-
def afk_channel_id=(afk_channel_id)
|
1030
|
-
update_server_data(afk_channel_id: afk_channel_id)
|
1031
|
-
end
|
1032
|
-
|
1033
1325
|
# Sets the amount of time after which a user gets moved into the AFK channel.
|
1034
1326
|
# @param afk_timeout [Integer] The AFK timeout, in seconds.
|
1035
1327
|
def afk_timeout=(afk_timeout)
|
1036
1328
|
update_server_data(afk_timeout: afk_timeout)
|
1037
1329
|
end
|
1038
1330
|
|
1331
|
+
# Processes a GUILD_MEMBERS_CHUNK packet, specifically the members field
|
1332
|
+
# @note For internal use only
|
1333
|
+
# @!visibility private
|
1334
|
+
def process_chunk(members)
|
1335
|
+
process_members(members)
|
1336
|
+
@processed_chunk_members += members.length
|
1337
|
+
LOGGER.debug("Processed one chunk on server #{@id} - length #{members.length}")
|
1338
|
+
|
1339
|
+
# Don't bother with the rest of the method if it's not truly the last packet
|
1340
|
+
return unless @processed_chunk_members == @member_count
|
1341
|
+
|
1342
|
+
LOGGER.debug("Finished chunking server #{@id}")
|
1343
|
+
|
1344
|
+
# Reset everything to normal
|
1345
|
+
@chunked = true
|
1346
|
+
@processed_chunk_members = 0
|
1347
|
+
end
|
1348
|
+
|
1039
1349
|
# Updates the cached data with new data
|
1040
1350
|
# @note For internal use only
|
1041
1351
|
# @!visibility private
|
1042
1352
|
def update_data(new_data)
|
1043
1353
|
@name = new_data[:name] || new_data['name'] || @name
|
1044
1354
|
@region = new_data[:region] || new_data['region'] || @region
|
1045
|
-
@
|
1355
|
+
@icon_id = new_data[:icon] || new_data['icon'] || @icon_id
|
1046
1356
|
@afk_timeout = new_data[:afk_timeout] || new_data['afk_timeout'].to_i || @afk_timeout
|
1047
1357
|
|
1048
1358
|
@afk_channel_id = new_data[:afk_channel_id] || new_data['afk_channel_id'].to_i || @afk_channel.id
|
1049
|
-
@afk_channel = @bot.channel(@afk_channel_id) if @afk_channel_id != 0 && (!@afk_channel || @afk_channel_id != @afk_channel.id)
|
1359
|
+
@afk_channel = @bot.channel(@afk_channel_id, self) if @afk_channel_id != 0 && (!@afk_channel || @afk_channel_id != @afk_channel.id)
|
1050
1360
|
end
|
1051
1361
|
|
1052
1362
|
# The inspect method is overwritten to give more useful output
|
@@ -1060,7 +1370,7 @@ module Discordrb
|
|
1060
1370
|
API.update_server(@bot.token, @id,
|
1061
1371
|
new_data[:name] || @name,
|
1062
1372
|
new_data[:region] || @region,
|
1063
|
-
new_data[:
|
1373
|
+
new_data[:icon_id] || @icon_id,
|
1064
1374
|
new_data[:afk_channel_id] || @afk_channel_id,
|
1065
1375
|
new_data[:afk_timeout] || @afk_timeout)
|
1066
1376
|
update_data(new_data)
|
@@ -1080,20 +1390,10 @@ module Discordrb
|
|
1080
1390
|
end
|
1081
1391
|
|
1082
1392
|
def process_members(members)
|
1083
|
-
@members = []
|
1084
|
-
@members_by_id = {}
|
1085
|
-
|
1086
1393
|
return unless members
|
1087
1394
|
members.each do |element|
|
1088
|
-
|
1089
|
-
@members
|
1090
|
-
@members_by_id[user.id] = user
|
1091
|
-
user_roles = []
|
1092
|
-
element['roles'].each do |e|
|
1093
|
-
role_id = e.to_i
|
1094
|
-
user_roles << @roles_by_id[role_id]
|
1095
|
-
end
|
1096
|
-
user.update_roles(self, user_roles)
|
1395
|
+
member = Member.new(element, self, @bot)
|
1396
|
+
@members[member.id] = member
|
1097
1397
|
end
|
1098
1398
|
end
|
1099
1399
|
|
@@ -1103,7 +1403,7 @@ module Discordrb
|
|
1103
1403
|
presences.each do |element|
|
1104
1404
|
next unless element['user']
|
1105
1405
|
user_id = element['user']['id'].to_i
|
1106
|
-
user = @
|
1406
|
+
user = @members[user_id]
|
1107
1407
|
if user
|
1108
1408
|
user.status = element['status'].to_sym
|
1109
1409
|
user.game = element['game'] ? element['game']['name'] : nil
|
@@ -1127,15 +1427,17 @@ module Discordrb
|
|
1127
1427
|
return unless voice_states
|
1128
1428
|
voice_states.each do |element|
|
1129
1429
|
user_id = element['user_id'].to_i
|
1130
|
-
|
1131
|
-
next unless
|
1132
|
-
user.server_mute = element['mute']
|
1133
|
-
user.server_deaf = element['deaf']
|
1134
|
-
user.self_mute = element['self_mute']
|
1135
|
-
user.self_mute = element['self_mute']
|
1430
|
+
member = @members[user_id]
|
1431
|
+
next unless member
|
1136
1432
|
channel_id = element['channel_id'].to_i
|
1137
1433
|
channel = channel_id ? @channels_by_id[channel_id] : nil
|
1138
|
-
|
1434
|
+
|
1435
|
+
member.update_voice_state(
|
1436
|
+
channel,
|
1437
|
+
element['mute'],
|
1438
|
+
element['deaf'],
|
1439
|
+
element['self_mute'],
|
1440
|
+
element['self_deaf'])
|
1139
1441
|
end
|
1140
1442
|
end
|
1141
1443
|
end
|