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.
- 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 +7 -6
- data/lib/discordrb/bot.rb +550 -525
- data/lib/discordrb/commands/command-bot.rb +135 -0
- data/lib/discordrb/commands/parser.rb +210 -0
- data/lib/discordrb/data.rb +341 -391
- data/lib/discordrb/endpoints/endpoints.rb +16 -16
- data/lib/discordrb/events/channel-delete.rb +48 -48
- data/lib/discordrb/events/channel-update.rb +49 -49
- data/lib/discordrb/events/generic.rb +59 -59
- data/lib/discordrb/events/guild-member-update.rb +40 -40
- data/lib/discordrb/events/guild-role-create.rb +34 -34
- data/lib/discordrb/events/guild-role-delete.rb +35 -35
- data/lib/discordrb/events/guild-role-update.rb +34 -34
- data/lib/discordrb/events/lifetime.rb +9 -9
- data/lib/discordrb/events/message.rb +60 -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 -89
- data/lib/discordrb/exceptions.rb +10 -10
- data/lib/discordrb/permissions.rb +46 -0
- data/lib/discordrb/version.rb +3 -3
- metadata +5 -2
@@ -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
|
data/lib/discordrb/data.rb
CHANGED
@@ -1,391 +1,341 @@
|
|
1
|
-
# These classes hold relevant Discord data, such as messages or channels.
|
2
|
-
|
3
|
-
require 'ostruct'
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
attr_accessor :
|
11
|
-
attr_accessor :
|
12
|
-
attr_accessor :
|
13
|
-
attr_accessor :
|
14
|
-
attr_accessor :
|
15
|
-
|
16
|
-
attr_reader :
|
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
|
-
|
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
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
@
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
def
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
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
|