discordrb 1.2.0 → 1.3.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.

@@ -0,0 +1,135 @@
1
+ require 'discordrb/bot'
2
+ require 'discordrb/data'
3
+ require 'discordrb/commands/parser'
4
+
5
+ # Specialized bot to run commands
6
+
7
+ module Discordrb::Commands
8
+ class CommandBot < Discordrb::Bot
9
+ attr_reader :attributes, :prefix
10
+
11
+ def initialize(email, password, prefix, attributes = {}, debug = false)
12
+ super(email, password, debug)
13
+ @prefix = prefix
14
+ @commands = {}
15
+ @attributes = {
16
+ # Whether advanced functionality such as command chains are enabled
17
+ advanced_functionality: attributes[:advanced_functionality].nil? ? true : attributes[:advanced_functionality],
18
+
19
+ # The name of the help command (that displays information to other commands). Nil if none should exist
20
+ help_command: attributes[:help_command] || :help,
21
+
22
+ # All of the following need to be one character
23
+ # String to designate previous result in command chain
24
+ previous: attributes[:previous] || '~',
25
+
26
+ # Command chain delimiter
27
+ chain_delimiter: attributes[:chain_delimiter] || '>',
28
+
29
+ # Chain argument delimiter
30
+ chain_args_delim: attributes[:chain_args_delim] || ':',
31
+
32
+ # Sub-chain starting character
33
+ sub_chain_start: attributes[:sub_chain_start] || '[',
34
+
35
+ # Sub-chain ending character
36
+ sub_chain_end: attributes[:sub_chain_end] || ']',
37
+
38
+ # Quoted mode starting character
39
+ quote_start: attributes[:quote_start] || '"',
40
+
41
+ # Quoted mode ending character
42
+ quote_end: attributes[:quote_end] || '"'
43
+ }
44
+
45
+ @permissions = {
46
+ roles: {},
47
+ users: {}
48
+ }
49
+
50
+ if @attributes[:help_command]
51
+ command(@attributes[:help_command], max_args: 1, description: 'Shows a list of all the commands available or displays help for a specific command.', usage: 'help [command name]') do |event, command_name|
52
+ if command_name
53
+ command = @commands[command_name.to_sym]
54
+ unless command
55
+ return "The command `#{command_name}` does not exist!"
56
+ end
57
+ desc = command.attributes[:description] || '*No description available*'
58
+ usage = command.attributes[:usage]
59
+ result = "**`#{command_name}`**: #{desc}"
60
+ result << "\nUsage: `#{usage}`" if usage
61
+ else
62
+ available_commands = @commands.values.reject { |command| !command.attributes[:help_available] }
63
+ case available_commands.length
64
+ when 0..5
65
+ available_commands.reduce "**List of commands:**\n" do |memo, command|
66
+ memo + "**`#{command.name}`**: #{command.attributes[:description] || '*No description available*'}\n"
67
+ end
68
+ when 5..50
69
+ (available_commands.reduce "**List of commands:**\n" do |memo, command|
70
+ memo + "`#{command.name}`, "
71
+ end)[0..-3]
72
+ else
73
+ event.user.pm (available_commands.reduce "**List of commands:**\n" do |memo, command|
74
+ memo + "`#{command.name}`, "
75
+ end)[0..-3]
76
+ "Sending list in PM!"
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+ def command(name, attributes = {}, &block)
84
+ @commands[name] = Command.new(name, attributes, &block)
85
+ end
86
+
87
+ def execute_command(name, event, arguments, chained = false)
88
+ debug("Executing command #{name} with arguments #{arguments}")
89
+ command = @commands[name]
90
+ unless command
91
+ event.respond "The command `#{name}` doesn't exist!"
92
+ return
93
+ end
94
+ if permission?(user(event.user.id), command.attributes[:permission_level], event.server.id)
95
+ command.call(event, arguments, chained)
96
+ else
97
+ event.respond "You don't have permission to execute command `#{name}`!"
98
+ return
99
+ end
100
+ end
101
+
102
+ def simple_execute(chain, event)
103
+ args = chain.split(' ')
104
+ execute_command(args[0].to_sym, event, args[1..-1])
105
+ end
106
+
107
+ def create_message(data)
108
+ message = Discordrb::Message.new(data, self)
109
+ event = Discordrb::Events::MessageEvent.new(message, self)
110
+
111
+ if message.content.start_with? @prefix
112
+ chain = message.content[@prefix.length..-1]
113
+ debug("Parsing command chain #{chain}")
114
+ result = (@attributes[:advanced_functionality]) ? CommandChain.new(chain, self).execute(event) : simple_execute(chain, event)
115
+ p result
116
+ event.respond result if result
117
+ end
118
+ end
119
+
120
+ def set_user_permission(id, level)
121
+ @permissions[:users][id] = level
122
+ end
123
+
124
+ def set_role_permission(id, level)
125
+ @permissions[:roles][id] = level
126
+ end
127
+
128
+ def permission?(user, level, server_id)
129
+ determined_level = user.roles[server_id].each.reduce(0) do |memo, role|
130
+ [@permissions[:roles][role.id] || 0, memo].max
131
+ end
132
+ [@permissions[:users][user.id] || 0, determined_level].max >= level
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,210 @@
1
+ module Discordrb::Commands
2
+ class Command
3
+ attr_reader :attributes, :name
4
+
5
+ def initialize(name, attributes = {}, &block)
6
+ @name = name
7
+ @attributes = {
8
+ # The lowest permission level that can use the command
9
+ permission_level: attributes[:permission_level] || 0,
10
+
11
+ # Whether this command is usable in a command chain
12
+ chain_usable: attributes[:chain_usable].nil? ? true : attributes[:chain_usable],
13
+
14
+ # Whether this command should show up in the help command
15
+ help_available: attributes[:help_available].nil? ? true : attributes[:help_available],
16
+
17
+ # Description (for help command)
18
+ description: attributes[:description] || nil,
19
+
20
+ # Usage description (for help command and error messages)
21
+ usage: attributes[:usage] || nil,
22
+
23
+ # Minimum number of arguments
24
+ min_args: attributes[:min_args] || 0,
25
+
26
+ # Maximum number of arguments (-1 for no limit)
27
+ max_args: attributes[:max_args] || -1
28
+ }
29
+
30
+ @block = block
31
+ end
32
+
33
+ def call(event, arguments, chained = false)
34
+ if arguments.length < @attributes[:min_args]
35
+ event.respond "Too few arguments for command `#{name}`!"
36
+ event.respond "Usage: `#{@attributes[:usage]}`" if @attributes[:usage]
37
+ return
38
+ end
39
+ if @attributes[:max_args] >= 0 && arguments.length > @attributes[:max_args]
40
+ event.respond "Too many arguments for command `#{name}`!"
41
+ event.respond "Usage: `#{@attributes[:usage]}`" if @attributes[:usage]
42
+ return
43
+ end
44
+ unless @attributes[:chain_usable]
45
+ if chained
46
+ event.respond "Command `#{name}` cannot be used in a command chain!"
47
+ return
48
+ end
49
+ end
50
+ @block.call(event, *arguments)
51
+ end
52
+ end
53
+
54
+ class CommandChain
55
+ def initialize(chain, bot, subchain = false)
56
+ @attributes = bot.attributes
57
+ @chain = chain
58
+ @bot = bot
59
+ @subchain = subchain
60
+ end
61
+
62
+ def execute_bare(event)
63
+ b_start, b_level = -1, 0
64
+ result = ''
65
+ quoted = false
66
+ hacky_delim, hacky_space, hacky_prev = [0xe001, 0xe002, 0xe003].pack('U*').chars
67
+
68
+ @chain.each_char.each_with_index do |char, index|
69
+ # Quote begin
70
+ if char == @attributes[:quote_start] && !quoted
71
+ quoted = true
72
+ next
73
+ end
74
+
75
+ # Quote end
76
+ if char == @attributes[:quote_end] && quoted
77
+ quoted = false
78
+ next
79
+ end
80
+
81
+ if char == @attributes[:chain_delimiter] && quoted
82
+ result += hacky_delim
83
+ next
84
+ end
85
+
86
+ if char == @attributes[:previous] && quoted
87
+ result += hacky_prev
88
+ next
89
+ end
90
+
91
+ if char == ' ' && quoted
92
+ result += hacky_space
93
+ next
94
+ end
95
+
96
+ if char == @attributes[:sub_chain_start] && !quoted
97
+ b_start = index if b_level == 0
98
+ b_level += 1
99
+ end
100
+
101
+ result << char if b_level <= 0
102
+
103
+ if char == @attributes[:sub_chain_end] && !quoted
104
+ b_level -= 1
105
+ if b_level == 0
106
+ nested = @chain[b_start + 1 .. index - 1]
107
+ subchain = CommandChain.new(nested, @bot, true)
108
+ result << subchain.execute(event)
109
+ end
110
+ end
111
+ end
112
+
113
+ event.respond("Your subchains are mismatched! Make sure you don't have any extra #{@attributes[:sub_chain_start]}'s or #{@attributes[:sub_chain_end]}'s") unless b_level == 0
114
+
115
+ @chain = result
116
+
117
+ @chain_args, @chain = divide_chain(@chain)
118
+
119
+ prev = ''
120
+
121
+ chain_to_split = @chain
122
+
123
+ # Don't break if a command is called the same thing as the chain delimiter
124
+ chain_to_split.slice!(1..-1) if chain_to_split.start_with?(@attributes[:chain_delimiter])
125
+
126
+ first = true
127
+ split_chain = chain_to_split.split(@attributes[:chain_delimiter])
128
+ split_chain.each do |command|
129
+ command = @attributes[:chain_delimiter] + command if first && @chain.start_with?(@attributes[:chain_delimiter])
130
+ first = false
131
+
132
+ command.strip!
133
+
134
+ # Replace the hacky delimiter that was used inside quotes with actual delimiters
135
+ command.gsub! hacky_delim, @attributes[:chain_delimiter]
136
+
137
+ first_space = command.index ' '
138
+ command_name = first_space ? command[0..first_space-1] : command
139
+ arguments = first_space ? command[first_space+1..-1] : ''
140
+
141
+ # Append a previous sign if none is present
142
+ arguments << @attributes[:previous] unless arguments.include? @attributes[:previous]
143
+ arguments.gsub! @attributes[:previous], prev
144
+
145
+ # Replace hacky previous signs with actual ones
146
+ arguments.gsub! hacky_prev, @attributes[:previous]
147
+
148
+ arguments = arguments.split ' '
149
+
150
+ # Replace the hacky spaces with actual spaces
151
+ arguments.map! do |elem|
152
+ elem.gsub hacky_space, ' '
153
+ end
154
+
155
+ # Finally execute the command
156
+ prev = @bot.execute_command(command_name.to_sym, event, arguments, split_chain.length > 1 || @subchain)
157
+ end
158
+
159
+ prev
160
+ end
161
+
162
+ def execute(event)
163
+ old_chain = @chain
164
+ @bot.debug "Executing bare chain"
165
+ result = execute_bare(event)
166
+
167
+ @chain_args ||= []
168
+
169
+ @bot.debug "Found chain args #{@chain_args}, preliminary result #{result}"
170
+
171
+ @chain_args.each do |arg|
172
+ case arg.first
173
+ when 'repeat'
174
+ new_result = ''
175
+ executed_chain = divide_chain(old_chain).last
176
+
177
+ arg[1].to_i.times do
178
+ new_result << CommandChain.new(executed_chain, @bot).execute(event)
179
+ end
180
+
181
+ result = new_result
182
+ # TODO: more chain arguments
183
+ end
184
+ end
185
+
186
+ result
187
+ end
188
+
189
+ private
190
+
191
+ def divide_chain(chain)
192
+ chain_args_index = chain.index @attributes[:chain_args_delim]
193
+ chain_args = []
194
+
195
+ if chain_args_index
196
+ chain_args = chain[0..chain_args_index].split ','
197
+
198
+ # Split up the arguments
199
+
200
+ chain_args.map! do |arg|
201
+ arg.split ' '
202
+ end
203
+
204
+ chain = chain[chain_args_index+1..-1]
205
+ end
206
+
207
+ [chain_args, chain]
208
+ end
209
+ end
210
+ end
@@ -1,391 +1,341 @@
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
1
+ # These classes hold relevant Discord data, such as messages or channels.
2
+
3
+ require 'ostruct'
4
+ require 'discordrb/permissions'
5
+
6
+ module Discordrb
7
+ class User
8
+ attr_reader :username, :id, :discriminator, :avatar
9
+
10
+ attr_accessor :status
11
+ attr_accessor :game_id
12
+ attr_accessor :server_mute
13
+ attr_accessor :server_deaf
14
+ attr_accessor :self_mute
15
+ attr_accessor :self_deaf
16
+ attr_reader :voice_channel
17
+
18
+ # Hash of user roles.
19
+ # Key: Server ID
20
+ # Value: Array of roles.
21
+ attr_reader :roles
22
+
23
+ alias_method :name, :username
24
+
25
+ def initialize(data, bot)
26
+ @bot = bot
27
+
28
+ @username = data['username']
29
+ @id = data['id'].to_i
30
+ @discriminator = data['discriminator']
31
+ @avatar = data['avatar']
32
+ @roles = {}
33
+
34
+ @status = :offline
35
+ end
36
+
37
+ # Utility function to mention users in messages
38
+ def mention
39
+ "<@#{@id}>"
40
+ end
41
+
42
+ # Utility function to send a PM
43
+ def pm(content = nil)
44
+ if content
45
+ # Recursively call pm to get the channel, then send a message to it
46
+ channel = pm
47
+ channel.send_message(content)
48
+ else
49
+ # If no message was specified, return the PM channel
50
+ @bot.private_channel(@id)
51
+ end
52
+ end
53
+
54
+ # Move a user into a voice channel
55
+ def move(to_channel)
56
+ return if to_channel && to_channel.type != 'voice'
57
+ @voice_channel = to_channel
58
+ end
59
+
60
+ # Set this user's roles
61
+ def update_roles(server, roles)
62
+ @roles[server.id] = roles
63
+ end
64
+
65
+ # Merge this user's roles with the roles from another instance of this user (from another server)
66
+ def merge_roles(server, roles)
67
+ if @roles[server.id]
68
+ @roles[server.id] = (@roles[server.id] + roles).uniq
69
+ else
70
+ @roles[server.id] = roles
71
+ end
72
+ end
73
+
74
+ # Determine if the user has permission to do an action
75
+ # action is a permission from Permissions::Flags.
76
+ # channel is the channel in which the action takes place (not applicable for server-wide actions).
77
+ def has_permission?(action, server, channel = nil)
78
+ # For each role, check if
79
+ # (1) the channel explicitly allows or permits an action for the role and
80
+ # (2) if the user is allowed to do the action if the channel doesn't specify
81
+ return false if !@roles[server.id]
82
+
83
+ @roles[server.id].reduce(false) do |can_act, role|
84
+ channel_allow = nil
85
+ if channel && channel.permission_overwrites[role.id]
86
+ allow = channel.permission_overwrites[role.id].allow
87
+ deny = channel.permission_overwrites[role.id].deny
88
+ if allow.instance_variable_get("@#{action}")
89
+ channel_allow = true
90
+ elsif deny.instance_variable_get("@#{action}")
91
+ channel_allow = false
92
+ # else
93
+ # If the channel has nothing to say on the matter, we can defer to the role itself
94
+ end
95
+ end
96
+ if channel_allow == false
97
+ can_act = can_act || false
98
+ elsif channel_allow == true
99
+ can_act = true
100
+ else # channel_allow == nil
101
+ can_act = role.permissions.instance_variable_get("@#{action}") || can_act
102
+ end
103
+ end
104
+ end
105
+
106
+ # Define methods for querying permissions
107
+ Discordrb::Permissions::Flags.each_value do |flag|
108
+ define_method "can_#{flag}?" do |server, channel = nil|
109
+ has_permission? flag, server, channel
110
+ end
111
+ end
112
+ end
113
+
114
+ class Role
115
+ attr_reader :permissions
116
+ attr_reader :name
117
+ attr_reader :id
118
+ attr_reader :hoist
119
+ attr_reader :color
120
+
121
+ def initialize(data, bot, server = nil)
122
+ @permissions = Permissions.new(data['permissions'])
123
+ @name = data['name']
124
+ @id = data['id'].to_i
125
+ @hoist = data['hoist']
126
+ @color = ColorRGB.new(data['color'])
127
+ end
128
+
129
+ def update_from(other)
130
+ @permissions = other.permissions
131
+ @name = other.name
132
+ @hoist = other.hoist
133
+ @color = other.color
134
+ end
135
+ end
136
+
137
+ class Channel
138
+ attr_reader :name, :server, :type, :id, :is_private, :recipient, :topic
139
+
140
+ attr_reader :permission_overwrites
141
+
142
+ def initialize(data, bot, server = nil)
143
+ @bot = bot
144
+
145
+ #data is a sometimes a Hash and othertimes an array of Hashes, you only want the last one if it's an array
146
+ data = data[-1] if data.is_a?(Array)
147
+
148
+ @id = data['id'].to_i
149
+ @type = data['type'] || 'text'
150
+ @topic = data['topic']
151
+
152
+ @is_private = data['is_private']
153
+ if @is_private
154
+ @recipient = User.new(data['recipient'], bot)
155
+ @name = @recipient.username
156
+ else
157
+ @name = data['name']
158
+ @server = bot.server(data['guild_id'].to_i)
159
+ @server = server if !@server
160
+ end
161
+
162
+ # Populate permission overwrites
163
+ @permission_overwrites = {}
164
+ if data['permission_overwrites']
165
+ data['permission_overwrites'].each do |element|
166
+ role_id = element['id'].to_i
167
+ deny = Permissions.new(element['deny'])
168
+ allow = Permissions.new(element['allow'])
169
+ @permission_overwrites[role_id] = OpenStruct.new
170
+ @permission_overwrites[role_id].deny = deny
171
+ @permission_overwrites[role_id].allow = allow
172
+ end
173
+ end
174
+ end
175
+
176
+ def send_message(content)
177
+ @bot.send_message(@id, content)
178
+ end
179
+
180
+ def update_from(other)
181
+ @topic = other.topic
182
+ @name = other.name
183
+ @is_private = other.is_private
184
+ @recipient = other.recipient
185
+ @permission_overwrites = other.permission_overwrites
186
+ end
187
+
188
+ # List of users currently in a channel
189
+ def users
190
+ if @type == 'text'
191
+ @server.members.select {|u| u.status != :offline }
192
+ else
193
+ @server.members.select do |user|
194
+ if user.voice_channel
195
+ user.voice_channel.id == @id
196
+ end
197
+ end
198
+ end
199
+ end
200
+
201
+ def update_overwrites(overwrites)
202
+ @permission_overwrites = overwrites
203
+ end
204
+
205
+ alias_method :send, :send_message
206
+ alias_method :message, :send_message
207
+ end
208
+
209
+ class Message
210
+ attr_reader :content, :author, :channel, :timestamp, :id, :mentions
211
+ alias_method :user, :author
212
+ alias_method :text, :content
213
+
214
+ def initialize(data, bot)
215
+ @bot = bot
216
+ @content = data['content']
217
+ @author = User.new(data['author'], bot)
218
+ @channel = bot.channel(data['channel_id'].to_i)
219
+ @timestamp = Time.at(data['timestamp'].to_i)
220
+ @id = data['id'].to_i
221
+
222
+ @mentions = []
223
+
224
+ data['mentions'].each do |element|
225
+ @mentions << User.new(element, bot)
226
+ end
227
+ end
228
+ end
229
+
230
+ class Server
231
+ attr_reader :region, :name, :owner_id, :id, :members
232
+
233
+ # Array of channels on the server
234
+ attr_reader :channels
235
+
236
+ # Array of roles on the server
237
+ attr_reader :roles
238
+
239
+ def initialize(data, bot)
240
+ @bot = bot
241
+ @region = data['region']
242
+ @name = data['name']
243
+ @owner_id = data['owner_id'].to_i
244
+ @id = data['id'].to_i
245
+
246
+ # Create roles
247
+ @roles = []
248
+ roles_by_id = {}
249
+ data['roles'].each do |element|
250
+ role = Role.new(element, bot)
251
+ @roles << role
252
+ roles_by_id[role.id] = role
253
+ end
254
+
255
+ @members = []
256
+ members_by_id = {}
257
+
258
+ data['members'].each do |element|
259
+ user = User.new(element['user'], bot)
260
+ @members << user
261
+ members_by_id[user.id] = user
262
+ user_roles = []
263
+ element['roles'].each do |element|
264
+ role_id = element.to_i
265
+ user_roles << roles_by_id[role_id]
266
+ end
267
+ user.update_roles(self, user_roles)
268
+ end
269
+
270
+ # Update user statuses with presence info
271
+ if data['presences']
272
+ data['presences'].each do |element|
273
+ if element['user']
274
+ user_id = element['user']['id'].to_i
275
+ user = members_by_id[user_id]
276
+ if user
277
+ user.status = element['status'].to_sym
278
+ user.game_id = element['game_id']
279
+ end
280
+ end
281
+ end
282
+ end
283
+
284
+ @channels = []
285
+ channels_by_id = {}
286
+
287
+ if data['channels']
288
+ data['channels'].each do |element|
289
+ channel = Channel.new(element, bot, self)
290
+ @channels << channel
291
+ channels_by_id[channel.id] = channel
292
+ end
293
+ end
294
+
295
+ if data['voice_states']
296
+ data['voice_states'].each do |element|
297
+ user_id = element['user_id'].to_i
298
+ user = members_by_id[user_id]
299
+ if user
300
+ user.server_mute = element['mute']
301
+ user.server_deaf = element['deaf']
302
+ user.self_mute = element['self_mute']
303
+ user.self_mute = element['self_mute']
304
+ channel_id = element['channel_id']
305
+ channel = nil
306
+ if channel_id
307
+ channel = channels_by_id[channel_id]
308
+ end
309
+ user.move(channel)
310
+ end
311
+ end
312
+ end
313
+ end
314
+
315
+ def add_role(role)
316
+ @roles << role
317
+ end
318
+
319
+ def delete_role(role_id)
320
+ @roles.reject! {|r| r.id == role_id}
321
+ @members.each do |user|
322
+ new_roles = user.roles.reject {|r| r.id == role_id}
323
+ user.update_roles(self, new_roles)
324
+ end
325
+ @channels.each do |channel|
326
+ overwrites = channel.permission_overwrites.reject {|id, perm| id == role_id}
327
+ channel.update_overwrites(overwrites)
328
+ end
329
+ end
330
+ end
331
+
332
+ class ColorRGB
333
+ attr_reader :red, :green, :blue
334
+
335
+ def initialize(combined)
336
+ @red = (combined >> 16) & 0xFF
337
+ @green = (combined >> 8) & 0xFF
338
+ @blue = combined & 0xFF
339
+ end
340
+ end
341
+ end