discordrb 1.1.3 → 1.2.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/.gitignore +9 -9
- data/.travis.yml +4 -4
- data/Gemfile +4 -4
- data/LICENSE.txt +21 -21
- data/README.md +78 -78
- data/Rakefile +4 -4
- data/bin/console +14 -14
- data/bin/setup +7 -7
- data/discordrb.gemspec +28 -28
- data/examples/ping.rb +11 -11
- data/examples/pm.rb +11 -11
- data/lib/discordrb.rb +6 -6
- data/lib/discordrb/bot.rb +525 -342
- data/lib/discordrb/data.rb +391 -140
- data/lib/discordrb/endpoints/endpoints.rb +16 -16
- data/lib/discordrb/events/channel-create.rb +48 -0
- data/lib/discordrb/events/channel-delete.rb +48 -0
- data/lib/discordrb/events/channel-update.rb +49 -0
- data/lib/discordrb/events/generic.rb +59 -59
- data/lib/discordrb/events/guild-member-update.rb +40 -0
- data/lib/discordrb/events/guild-role-create.rb +34 -0
- data/lib/discordrb/events/guild-role-delete.rb +35 -0
- data/lib/discordrb/events/guild-role-update.rb +34 -0
- data/lib/discordrb/events/lifetime.rb +9 -9
- data/lib/discordrb/events/message.rb +59 -59
- data/lib/discordrb/events/presence.rb +40 -40
- data/lib/discordrb/events/typing.rb +45 -45
- data/lib/discordrb/events/voice-state-update.rb +89 -0
- data/lib/discordrb/exceptions.rb +10 -10
- data/lib/discordrb/version.rb +3 -3
- metadata +10 -2
data/lib/discordrb/data.rb
CHANGED
@@ -1,140 +1,391 @@
|
|
1
|
-
# These classes hold relevant Discord data, such as messages or channels.
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
def
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
@
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
end
|
1
|
+
# These classes hold relevant Discord data, such as messages or channels.
|
2
|
+
|
3
|
+
require 'ostruct'
|
4
|
+
|
5
|
+
module Discordrb
|
6
|
+
class User
|
7
|
+
attr_reader :username, :id, :discriminator, :avatar
|
8
|
+
|
9
|
+
attr_accessor :status
|
10
|
+
attr_accessor :game_id
|
11
|
+
attr_accessor :server_mute
|
12
|
+
attr_accessor :server_deaf
|
13
|
+
attr_accessor :self_mute
|
14
|
+
attr_accessor :self_deaf
|
15
|
+
attr_reader :voice_channel
|
16
|
+
attr_reader :roles
|
17
|
+
|
18
|
+
alias_method :name, :username
|
19
|
+
|
20
|
+
def initialize(data, bot, server = nil)
|
21
|
+
@bot = bot
|
22
|
+
|
23
|
+
@username = data['username']
|
24
|
+
@id = data['id'].to_i
|
25
|
+
@discriminator = data['discriminator']
|
26
|
+
@avatar = data['avatar']
|
27
|
+
@server = server
|
28
|
+
@roles = []
|
29
|
+
|
30
|
+
@status = :offline
|
31
|
+
end
|
32
|
+
|
33
|
+
# Utility function to mention users in messages
|
34
|
+
def mention
|
35
|
+
"<@#{@id}>"
|
36
|
+
end
|
37
|
+
|
38
|
+
# Utility function to send a PM
|
39
|
+
def pm(content = nil)
|
40
|
+
if content
|
41
|
+
# Recursively call pm to get the channel, then send a message to it
|
42
|
+
channel = pm
|
43
|
+
channel.send_message(content)
|
44
|
+
else
|
45
|
+
# If no message was specified, return the PM channel
|
46
|
+
@bot.private_channel(@id)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Move a user into a voice channel
|
51
|
+
def move(to_channel)
|
52
|
+
return if to_channel && to_channel.type != 'voice'
|
53
|
+
@voice_channel = to_channel
|
54
|
+
end
|
55
|
+
|
56
|
+
# Set this user's roles
|
57
|
+
def update_roles(roles)
|
58
|
+
@roles = roles
|
59
|
+
end
|
60
|
+
|
61
|
+
# Determine if the user has permission to do an action
|
62
|
+
# action is a permission from Permissions::Flags.
|
63
|
+
# channel is the channel in which the action takes place (not applicable for server-wide actions).
|
64
|
+
def has_permission?(action, channel = nil)
|
65
|
+
# For each role, check if
|
66
|
+
# (1) the channel explicitly allows or permits an action for the role and
|
67
|
+
# (2) if the user is allowed to do the action if the channel doesn't specify
|
68
|
+
@roles.reduce(false) do |can_act, role|
|
69
|
+
channel_allow = nil
|
70
|
+
if channel && channel.permission_overwrites[role.id]
|
71
|
+
allow = channel.permission_overwrites[role.id].allow
|
72
|
+
deny = channel.permission_overwrites[role.id].deny
|
73
|
+
if allow.instance_variable_get("@#{action}")
|
74
|
+
channel_allow = true
|
75
|
+
elsif deny.instance_variable_get("@#{action}")
|
76
|
+
channel_allow = false
|
77
|
+
# else
|
78
|
+
# If the channel has nothing to say on the matter, we can defer to the role itself
|
79
|
+
end
|
80
|
+
end
|
81
|
+
if channel_allow == false
|
82
|
+
can_act = can_act || false
|
83
|
+
elsif channel_allow == true
|
84
|
+
can_act = true
|
85
|
+
else # channel_allow == nil
|
86
|
+
can_act = role.permissions.instance_variable_get("@#{action}") || can_act
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def method_missing(method_name, *args, &block)
|
92
|
+
if /\Acan_(?<action>\w+)\?\Z/ =~ method_name
|
93
|
+
action = action.to_sym
|
94
|
+
if Permissions::Flags.has_value? action
|
95
|
+
has_permission? action, args.first
|
96
|
+
else
|
97
|
+
super
|
98
|
+
end
|
99
|
+
else
|
100
|
+
super
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Respond to can_*? methods
|
105
|
+
def respond_to?(method_name, include_private = false)
|
106
|
+
if /\Acan_(?<action>\w+)\?\Z/ =~ method_name
|
107
|
+
action = action.to_sym
|
108
|
+
if Permissions::Flags.has_value? action
|
109
|
+
true
|
110
|
+
else
|
111
|
+
super
|
112
|
+
end
|
113
|
+
else
|
114
|
+
super
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
class Role
|
120
|
+
attr_reader :permissions
|
121
|
+
attr_reader :name
|
122
|
+
attr_reader :id
|
123
|
+
attr_reader :hoist
|
124
|
+
attr_reader :color
|
125
|
+
|
126
|
+
def initialize(data, bot, server = nil)
|
127
|
+
@permissions = Permissions.new(data['permissions'])
|
128
|
+
@name = data['name']
|
129
|
+
@id = data['id'].to_i
|
130
|
+
@hoist = data['hoist']
|
131
|
+
@color = ColorRGB.new(data['color'])
|
132
|
+
end
|
133
|
+
|
134
|
+
def update_from(other)
|
135
|
+
@permissions = other.permissions
|
136
|
+
@name = other.name
|
137
|
+
@hoist = other.hoist
|
138
|
+
@color = other.color
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
class Permissions
|
143
|
+
# This hash maps bit positions to logical permissions.
|
144
|
+
# I'm not sure what the unlabeled bits are reserved for.
|
145
|
+
Flags = {
|
146
|
+
# Bit => Permission # Value
|
147
|
+
0 => :create_instant_invite, # 1
|
148
|
+
1 => :kick_members, # 2
|
149
|
+
2 => :ban_members, # 4
|
150
|
+
3 => :manage_roles , # 8
|
151
|
+
4 => :manage_channels, # 16
|
152
|
+
5 => :manage_server, # 32
|
153
|
+
#6 # 64
|
154
|
+
#7 # 128
|
155
|
+
#8 # 256
|
156
|
+
#9 # 512
|
157
|
+
10 => :read_messages, # 1024
|
158
|
+
11 => :send_messages, # 2048
|
159
|
+
12 => :send_tts_messages, # 4096
|
160
|
+
13 => :manage_messages, # 8192
|
161
|
+
14 => :embed_links, # 16384
|
162
|
+
15 => :attach_files, # 32768
|
163
|
+
16 => :read_message_history, # 65536
|
164
|
+
17 => :mention_everyone, # 131072
|
165
|
+
#18 # 262144
|
166
|
+
#19 # 524288
|
167
|
+
20 => :connect, # 1048576
|
168
|
+
21 => :speak, # 2097152
|
169
|
+
22 => :mute_members, # 4194304
|
170
|
+
23 => :deafen_members, # 8388608
|
171
|
+
24 => :move_members, # 16777216
|
172
|
+
25 => :use_voice_activity # 33554432
|
173
|
+
}
|
174
|
+
|
175
|
+
Flags.each_value do |flag|
|
176
|
+
attr_reader flag
|
177
|
+
end
|
178
|
+
|
179
|
+
def initialize(bits)
|
180
|
+
Flags.each do |position, flag|
|
181
|
+
flag_set = ((bits >> position) & 0x1) == 1
|
182
|
+
instance_variable_set "@#{flag}", flag_set
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
class Channel
|
188
|
+
attr_reader :name, :server, :type, :id, :is_private, :recipient, :topic
|
189
|
+
|
190
|
+
attr_reader :permission_overwrites
|
191
|
+
|
192
|
+
def initialize(data, bot, server = nil)
|
193
|
+
@bot = bot
|
194
|
+
|
195
|
+
#data is a sometimes a Hash and othertimes an array of Hashes, you only want the last one if it's an array
|
196
|
+
data = data[-1] if data.is_a?(Array)
|
197
|
+
|
198
|
+
@id = data['id'].to_i
|
199
|
+
@type = data['type'] || 'text'
|
200
|
+
@topic = data['topic']
|
201
|
+
|
202
|
+
@is_private = data['is_private']
|
203
|
+
if @is_private
|
204
|
+
@recipient = User.new(data['recipient'], bot)
|
205
|
+
@name = @recipient.username
|
206
|
+
else
|
207
|
+
@name = data['name']
|
208
|
+
@server = bot.server(data['guild_id'].to_i)
|
209
|
+
@server = server if !@server
|
210
|
+
end
|
211
|
+
|
212
|
+
# Populate permission overwrites
|
213
|
+
@permission_overwrites = {}
|
214
|
+
if data['permission_overwrites']
|
215
|
+
data['permission_overwrites'].each do |element|
|
216
|
+
role_id = element['id'].to_i
|
217
|
+
deny = Permissions.new(element['deny'])
|
218
|
+
allow = Permissions.new(element['allow'])
|
219
|
+
@permission_overwrites[role_id] = OpenStruct.new
|
220
|
+
@permission_overwrites[role_id].deny = deny
|
221
|
+
@permission_overwrites[role_id].allow = allow
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def send_message(content)
|
227
|
+
@bot.send_message(@id, content)
|
228
|
+
end
|
229
|
+
|
230
|
+
def update_from(other)
|
231
|
+
@topic = other.topic
|
232
|
+
@name = other.name
|
233
|
+
@is_private = other.is_private
|
234
|
+
@recipient = other.recipient
|
235
|
+
@permission_overwrites = other.permission_overwrites
|
236
|
+
end
|
237
|
+
|
238
|
+
# List of users currently in a channel
|
239
|
+
def users
|
240
|
+
if @type == 'text'
|
241
|
+
@server.members.select {|u| u.status != :offline }
|
242
|
+
else
|
243
|
+
@server.members.select do |user|
|
244
|
+
if user.voice_channel
|
245
|
+
user.voice_channel.id == @id
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
def update_overwrites(overwrites)
|
252
|
+
@permission_overwrites = overwrites
|
253
|
+
end
|
254
|
+
|
255
|
+
alias_method :send, :send_message
|
256
|
+
alias_method :message, :send_message
|
257
|
+
end
|
258
|
+
|
259
|
+
class Message
|
260
|
+
attr_reader :content, :author, :channel, :timestamp, :id, :mentions
|
261
|
+
alias_method :user, :author
|
262
|
+
alias_method :text, :content
|
263
|
+
|
264
|
+
def initialize(data, bot)
|
265
|
+
@bot = bot
|
266
|
+
@content = data['content']
|
267
|
+
@author = User.new(data['author'], bot)
|
268
|
+
@channel = bot.channel(data['channel_id'].to_i)
|
269
|
+
@timestamp = Time.at(data['timestamp'].to_i)
|
270
|
+
@id = data['id'].to_i
|
271
|
+
|
272
|
+
@mentions = []
|
273
|
+
|
274
|
+
data['mentions'].each do |element|
|
275
|
+
@mentions << User.new(element, bot)
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
class Server
|
281
|
+
attr_reader :region, :name, :owner_id, :id, :members
|
282
|
+
|
283
|
+
# Array of channels on the server
|
284
|
+
attr_reader :channels
|
285
|
+
|
286
|
+
# Array of roles on the server
|
287
|
+
attr_reader :roles
|
288
|
+
|
289
|
+
def initialize(data, bot)
|
290
|
+
@bot = bot
|
291
|
+
@region = data['region']
|
292
|
+
@name = data['name']
|
293
|
+
@owner_id = data['owner_id'].to_i
|
294
|
+
@id = data['id'].to_i
|
295
|
+
|
296
|
+
# Create roles
|
297
|
+
@roles = []
|
298
|
+
roles_by_id = {}
|
299
|
+
data['roles'].each do |element|
|
300
|
+
role = Role.new(element, bot)
|
301
|
+
@roles << role
|
302
|
+
roles_by_id[role.id] = role
|
303
|
+
end
|
304
|
+
|
305
|
+
@members = []
|
306
|
+
members_by_id = {}
|
307
|
+
|
308
|
+
data['members'].each do |element|
|
309
|
+
user = User.new(element['user'], bot, self)
|
310
|
+
@members << user
|
311
|
+
members_by_id[user.id] = user
|
312
|
+
user_roles = []
|
313
|
+
element['roles'].each do |element|
|
314
|
+
role_id = element.to_i
|
315
|
+
user_roles << roles_by_id[role_id]
|
316
|
+
end
|
317
|
+
user.update_roles(user_roles)
|
318
|
+
end
|
319
|
+
|
320
|
+
# Update user statuses with presence info
|
321
|
+
if data['presences']
|
322
|
+
data['presences'].each do |element|
|
323
|
+
if element['user']
|
324
|
+
user_id = element['user']['id'].to_i
|
325
|
+
user = members_by_id[user_id]
|
326
|
+
if user
|
327
|
+
user.status = element['status'].to_sym
|
328
|
+
user.game_id = element['game_id']
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
@channels = []
|
335
|
+
channels_by_id = {}
|
336
|
+
|
337
|
+
if data['channels']
|
338
|
+
data['channels'].each do |element|
|
339
|
+
channel = Channel.new(element, bot, self)
|
340
|
+
@channels << channel
|
341
|
+
channels_by_id[channel.id] = channel
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
if data['voice_states']
|
346
|
+
data['voice_states'].each do |element|
|
347
|
+
user_id = element['user_id'].to_i
|
348
|
+
user = members_by_id[user_id]
|
349
|
+
if user
|
350
|
+
user.server_mute = element['mute']
|
351
|
+
user.server_deaf = element['deaf']
|
352
|
+
user.self_mute = element['self_mute']
|
353
|
+
user.self_mute = element['self_mute']
|
354
|
+
channel_id = element['channel_id']
|
355
|
+
channel = nil
|
356
|
+
if channel_id
|
357
|
+
channel = channels_by_id[channel_id]
|
358
|
+
end
|
359
|
+
user.move(channel)
|
360
|
+
end
|
361
|
+
end
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
def add_role(role)
|
366
|
+
@roles << role
|
367
|
+
end
|
368
|
+
|
369
|
+
def delete_role(role_id)
|
370
|
+
@roles.reject! {|r| r.id == role_id}
|
371
|
+
@members.each do |user|
|
372
|
+
new_roles = user.roles.reject {|r| r.id == role_id}
|
373
|
+
user.update_roles(new_roles)
|
374
|
+
end
|
375
|
+
@channels.each do |channel|
|
376
|
+
overwrites = channel.permission_overwrites.reject {|id, perm| id == role_id}
|
377
|
+
channel.update_overwrites(overwrites)
|
378
|
+
end
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
class ColorRGB
|
383
|
+
attr_reader :red, :green, :blue
|
384
|
+
|
385
|
+
def initialize(combined)
|
386
|
+
@red = (combined >> 16) & 0xFF
|
387
|
+
@green = (combined >> 8) & 0xFF
|
388
|
+
@blue = combined & 0xFF
|
389
|
+
end
|
390
|
+
end
|
391
|
+
end
|