discordrb 1.6.6 → 1.7.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/.travis.yml +3 -0
- data/CHANGELOG.md +26 -0
- data/README.md +1 -1
- data/discordrb.gemspec +2 -0
- data/lib/discordrb.rb +1 -1
- data/lib/discordrb/api.rb +36 -5
- data/lib/discordrb/await.rb +3 -1
- data/lib/discordrb/bot.rb +70 -275
- data/lib/discordrb/commands/command_bot.rb +81 -28
- data/lib/discordrb/commands/container.rb +78 -0
- data/lib/discordrb/commands/events.rb +2 -12
- data/lib/discordrb/commands/parser.rb +40 -4
- data/lib/discordrb/commands/rate_limiter.rb +141 -0
- data/lib/discordrb/container.rb +379 -0
- data/lib/discordrb/data.rb +52 -4
- data/lib/discordrb/errors.rb +24 -0
- data/lib/discordrb/events/bans.rb +10 -8
- data/lib/discordrb/events/generic.rb +38 -2
- data/lib/discordrb/events/guilds.rb +11 -1
- data/lib/discordrb/events/lifetime.rb +6 -0
- data/lib/discordrb/events/members.rb +9 -1
- data/lib/discordrb/events/message.rb +33 -1
- data/lib/discordrb/logger.rb +7 -1
- data/lib/discordrb/permissions.rb +3 -0
- data/lib/discordrb/token_cache.rb +38 -4
- data/lib/discordrb/version.rb +1 -1
- data/lib/discordrb/voice/encoder.rb +29 -19
- data/lib/discordrb/voice/network.rb +6 -11
- data/lib/discordrb/voice/voice_bot.rb +112 -43
- metadata +34 -3
- data/lib/discordrb/exceptions.rb +0 -12
@@ -0,0 +1,24 @@
|
|
1
|
+
module Discordrb
|
2
|
+
# Custom errors raised in various places
|
3
|
+
module Errors
|
4
|
+
# Raised when authentication data is invalid or incorrect.
|
5
|
+
class InvalidAuthenticationError < RuntimeError; end
|
6
|
+
|
7
|
+
# Raised when a HTTP status code indicates a failure
|
8
|
+
class HTTPStatusError < RuntimeError
|
9
|
+
attr_reader :status
|
10
|
+
def initialize(status)
|
11
|
+
@status = status
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# Raised when a message is over the character limit
|
16
|
+
class MessageTooLong < RuntimeError; end
|
17
|
+
|
18
|
+
# Raised when the bot can't do something because its permissions on the server are insufficient
|
19
|
+
class NoPermission < RuntimeError; end
|
20
|
+
|
21
|
+
# Raised when the bot gets a HTTP 502 error, which is usually caused by Cloudflare.
|
22
|
+
class CloudflareError < RuntimeError; end
|
23
|
+
end
|
24
|
+
end
|
@@ -36,13 +36,13 @@ module Discordrb::Events
|
|
36
36
|
end
|
37
37
|
end,
|
38
38
|
matches_all(@attributes[:server], event.server) do |a, e|
|
39
|
-
if a.is_a? String
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
39
|
+
a == if a.is_a? String
|
40
|
+
e.name
|
41
|
+
elsif a.is_a? Integer
|
42
|
+
e.id
|
43
|
+
else
|
44
|
+
e
|
45
|
+
end
|
46
46
|
end
|
47
47
|
].reduce(true, &:&)
|
48
48
|
end
|
@@ -50,5 +50,7 @@ module Discordrb::Events
|
|
50
50
|
|
51
51
|
# Raised when a user is unbanned from a server
|
52
52
|
class UserUnbanEvent < UserBanEvent; end
|
53
|
+
|
54
|
+
# Event handler for {UserUnbanEvent}
|
53
55
|
class UserUnbanEventHandler < UserBanEventHandler; end
|
54
|
-
end
|
56
|
+
end
|
@@ -2,7 +2,8 @@ require 'active_support/core_ext/module'
|
|
2
2
|
|
3
3
|
# Events used by discordrb
|
4
4
|
module Discordrb::Events
|
5
|
-
# A negated object, used to not match something in event parameters
|
5
|
+
# A negated object, used to not match something in event parameters.
|
6
|
+
# @see Discordrb::Events.matches_all
|
6
7
|
class Negated
|
7
8
|
attr_reader :object
|
8
9
|
|
@@ -11,6 +12,24 @@ module Discordrb::Events
|
|
11
12
|
end
|
12
13
|
end
|
13
14
|
|
15
|
+
# Attempts to match possible formats of event attributes to a set comparison value, using a comparison block.
|
16
|
+
# It allows five kinds of attribute formats:
|
17
|
+
# 0. nil -> always returns true
|
18
|
+
# 1. A single attribute, not negated
|
19
|
+
# 2. A single attribute, negated
|
20
|
+
# 3. An array of attributes, not negated
|
21
|
+
# 4. An array of attributes, not negated
|
22
|
+
# Note that it doesn't allow an array of negated attributes. For info on negation stuff, see {not!}
|
23
|
+
# @param attributes [Object, Array<Object>, Negated<Object>, Negated<Array<Object>>, nil] One or more attributes to
|
24
|
+
# compare to the to_check value.
|
25
|
+
# @param to_check [Object] What to compare the attributes to.
|
26
|
+
# @yield [a, e] The block will be called when a comparison happens.
|
27
|
+
# @yieldparam [Object] a The attribute to compare to the value to check. Will always be a single not-negated object,
|
28
|
+
# all the negation and array handling is done by the method
|
29
|
+
# @yieldparam [Object] e The value to compare the attribute to. Will always be the value passed to the function as
|
30
|
+
# to_check.
|
31
|
+
# @yieldreturn [true, false] Whether or not the attribute a matches the given comparison value e.
|
32
|
+
# @return [true, false] whether the attributes match the comparison value in at least one way.
|
14
33
|
def self.matches_all(attributes, to_check, &block)
|
15
34
|
# "Zeroth" case: attributes is nil
|
16
35
|
return true if attributes.nil?
|
@@ -37,31 +56,48 @@ module Discordrb::Events
|
|
37
56
|
@block = block
|
38
57
|
end
|
39
58
|
|
59
|
+
# Whether or not this event handler matches the given event with its attributes.
|
60
|
+
# @raise [RuntimeError] if this method is called - overwrite it in your event handler!
|
40
61
|
def matches?(_)
|
41
62
|
fail 'Attempted to call matches?() from a generic EventHandler'
|
42
63
|
end
|
43
64
|
|
65
|
+
# Checks whether this handler matches the given event, and then calls it.
|
66
|
+
# @param event [Object] The event object to match and call the handler with
|
44
67
|
def match(event)
|
45
68
|
call(event) if matches? event
|
46
69
|
end
|
47
70
|
|
71
|
+
# Calls this handler
|
72
|
+
# @param event [Object] The event object to call this handler with
|
48
73
|
def call(event)
|
49
74
|
@block.call(event)
|
50
75
|
end
|
51
76
|
|
77
|
+
# to be overwritten by extending event handlers
|
78
|
+
def after_call(event); end
|
79
|
+
|
80
|
+
# @see Discordrb::Events::matches_all
|
52
81
|
def matches_all(attributes, to_check, &block)
|
53
82
|
Discordrb::Events.matches_all(attributes, to_check, &block)
|
54
83
|
end
|
55
84
|
end
|
56
85
|
|
57
|
-
# Event handler that matches all events
|
86
|
+
# Event handler that matches all events. Only useful for making an event that has no attributes, such as {ReadyEvent}.
|
58
87
|
class TrueEventHandler < EventHandler
|
88
|
+
# Always returns true.
|
89
|
+
# @return [true]
|
59
90
|
def matches?(_)
|
60
91
|
true
|
61
92
|
end
|
62
93
|
end
|
63
94
|
end
|
64
95
|
|
96
|
+
# Utility function that creates a negated object for {Discordrb::Events.matches_all}
|
97
|
+
# @param [Object] object The object to negate
|
98
|
+
# @see Discordrb::Events::Negated
|
99
|
+
# @see Discordrb::Events.matches_all
|
100
|
+
# @return [Negated<Object>] the object, negated, as an attribute to pass to matches_all
|
65
101
|
def not!(object)
|
66
102
|
Discordrb::Events::Negated.new(object)
|
67
103
|
end
|
@@ -10,6 +10,8 @@ module Discordrb::Events
|
|
10
10
|
init_server(data, bot)
|
11
11
|
end
|
12
12
|
|
13
|
+
# Initializes this event with server data. Should be overwritten in case the server doesn't exist at the time
|
14
|
+
# of event creation (e. g. {GuildDeleteEvent})
|
13
15
|
def init_server(data, bot)
|
14
16
|
@server = bot.server(data['id'].to_i)
|
15
17
|
end
|
@@ -35,21 +37,29 @@ module Discordrb::Events
|
|
35
37
|
end
|
36
38
|
end
|
37
39
|
|
38
|
-
# Specialized subclasses
|
39
40
|
# Server is created
|
41
|
+
# @see Discordrb::EventContainer#server_create
|
40
42
|
class GuildCreateEvent < GuildEvent; end
|
43
|
+
|
44
|
+
# Event handler for {GuildCreateEvent}
|
41
45
|
class GuildCreateEventHandler < GuildEventHandler; end
|
42
46
|
|
43
47
|
# Server is updated (e.g. name changed)
|
48
|
+
# @see Discordrb::EventContainer#server_update
|
44
49
|
class GuildUpdateEvent < GuildEvent; end
|
50
|
+
|
51
|
+
# Event handler for {GuildUpdateEvent}
|
45
52
|
class GuildUpdateEventHandler < GuildEventHandler; end
|
46
53
|
|
47
54
|
# Server is deleted
|
55
|
+
# @see Discordrb::EventContainer#server_delete
|
48
56
|
class GuildDeleteEvent < GuildEvent
|
49
57
|
# Overide init_server to account for the deleted server
|
50
58
|
def init_server(data, bot)
|
51
59
|
@server = Discordrb::Server.new(data, bot)
|
52
60
|
end
|
53
61
|
end
|
62
|
+
|
63
|
+
# Event handler for {GuildDeleteEvent}
|
54
64
|
class GuildDeleteEventHandler < GuildEventHandler; end
|
55
65
|
end
|
@@ -1,9 +1,15 @@
|
|
1
1
|
require 'discordrb/events/generic'
|
2
2
|
|
3
3
|
module Discordrb::Events
|
4
|
+
# @see Discordrb::EventContainer#ready
|
4
5
|
class ReadyEvent; end
|
6
|
+
|
7
|
+
# Event handler for {ReadyEvent}
|
5
8
|
class ReadyEventHandler < TrueEventHandler; end
|
6
9
|
|
10
|
+
# @see Discordrb::EventContainer#disconnected
|
7
11
|
class DisconnectEvent; end
|
12
|
+
|
13
|
+
# Event handler for {DisconnectEvent}
|
8
14
|
class DisconnectEventHandler < TrueEventHandler; end
|
9
15
|
end
|
@@ -49,21 +49,29 @@ module Discordrb::Events
|
|
49
49
|
end
|
50
50
|
end
|
51
51
|
|
52
|
-
# Specialized subclasses
|
53
52
|
# Member joins
|
53
|
+
# @see Discordrb::EventContainer#member_join
|
54
54
|
class GuildMemberAddEvent < GuildMemberEvent; end
|
55
|
+
|
56
|
+
# Event handler for {GuildMemberAddEvent}
|
55
57
|
class GuildMemberAddEventHandler < GuildMemberEventHandler; end
|
56
58
|
|
57
59
|
# Member is updated (e.g. name changed)
|
60
|
+
# @see Discordrb::EventContainer#member_update
|
58
61
|
class GuildMemberUpdateEvent < GuildMemberEvent; end
|
62
|
+
|
63
|
+
# Event handler for {GuildMemberUpdateEvent}
|
59
64
|
class GuildMemberUpdateEventHandler < GuildMemberEventHandler; end
|
60
65
|
|
61
66
|
# Member leaves
|
67
|
+
# @see Discordrb::EventContainer#member_leave
|
62
68
|
class GuildMemberDeleteEvent < GuildMemberEvent
|
63
69
|
# Overide init_user to account for the deleted user on the server
|
64
70
|
def init_user(data, bot)
|
65
71
|
@user = Discordrb::User.new(data['user'], bot)
|
66
72
|
end
|
67
73
|
end
|
74
|
+
|
75
|
+
# Event handler for {GuildMemberDeleteEvent}
|
68
76
|
class GuildMemberDeleteEventHandler < GuildMemberEventHandler; end
|
69
77
|
end
|
@@ -3,7 +3,7 @@ require 'discordrb/events/generic'
|
|
3
3
|
module Discordrb::Events
|
4
4
|
# Event raised when a text message is sent to a channel
|
5
5
|
class MessageEvent
|
6
|
-
attr_reader :message
|
6
|
+
attr_reader :message, :saved_message
|
7
7
|
|
8
8
|
delegate :author, :channel, :content, :timestamp, to: :message
|
9
9
|
delegate :server, to: :channel
|
@@ -11,16 +11,31 @@ module Discordrb::Events
|
|
11
11
|
def initialize(message, bot)
|
12
12
|
@bot = bot
|
13
13
|
@message = message
|
14
|
+
@saved_message = ''
|
14
15
|
end
|
15
16
|
|
17
|
+
# Sends a message to the channel this message was sent in, right now. It is usually preferable to use {#<<} instead
|
18
|
+
# because it avoids rate limiting problems
|
19
|
+
# @param content [String] The message to send to the channel
|
20
|
+
# @return [Discordrb::Message] the message that was sent
|
16
21
|
def send_message(content)
|
17
22
|
@message.channel.send_message(content)
|
18
23
|
end
|
19
24
|
|
25
|
+
# @return [true, false] whether or not this message was sent by the bot itself
|
20
26
|
def from_bot?
|
21
27
|
@message.user.id == @bot.bot_user.id
|
22
28
|
end
|
23
29
|
|
30
|
+
# Adds a string to be sent after the event has finished execution. Avoids problems with rate limiting because only
|
31
|
+
# one message is ever sent. If it is used multiple times, the strings will bunch up into one message (separated by
|
32
|
+
# newlines)
|
33
|
+
# @param message [String] The message to send to the channel
|
34
|
+
def <<(message)
|
35
|
+
@saved_message += "#{message}\n"
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
|
24
39
|
alias_method :user, :author
|
25
40
|
alias_method :text, :content
|
26
41
|
alias_method :send, :send_message
|
@@ -88,12 +103,23 @@ module Discordrb::Events
|
|
88
103
|
matches_all(@attributes[:private], event.channel.private?) { |a, e| !e == !a }
|
89
104
|
].reduce(true, &:&)
|
90
105
|
end
|
106
|
+
|
107
|
+
# @see EventHandler#after_call
|
108
|
+
def after_call(event)
|
109
|
+
event.send_message(event.saved_message) unless event.saved_message.empty?
|
110
|
+
end
|
91
111
|
end
|
92
112
|
|
113
|
+
# @see Discordrb::EventContainer#mention
|
93
114
|
class MentionEvent < MessageEvent; end
|
115
|
+
|
116
|
+
# Event handler for {MentionEvent}
|
94
117
|
class MentionEventHandler < MessageEventHandler; end
|
95
118
|
|
119
|
+
# @see Discordrb::EventContainer#pm
|
96
120
|
class PrivateMessageEvent < MessageEvent; end
|
121
|
+
|
122
|
+
# Event handler for {PrivateMessageEvent}
|
97
123
|
class PrivateMessageEventHandler < MessageEventHandler; end
|
98
124
|
|
99
125
|
# A subset of MessageEvent that only contains a message ID and a channel
|
@@ -137,10 +163,16 @@ module Discordrb::Events
|
|
137
163
|
end
|
138
164
|
|
139
165
|
# Raised when a message is edited
|
166
|
+
# @see Discordrb::EventContainer#message_edit
|
140
167
|
class MessageEditEvent < MessageIDEvent; end
|
168
|
+
|
169
|
+
# Event handler for {MessageEditEvent}
|
141
170
|
class MessageEditEventHandler < MessageIDEventHandler; end
|
142
171
|
|
143
172
|
# Raised when a message is deleted
|
173
|
+
# @see Discordrb::EventContainer#message_delete
|
144
174
|
class MessageDeleteEvent < MessageIDEvent; end
|
175
|
+
|
176
|
+
# Event handler for {MessageDeleteEvent}
|
145
177
|
class MessageDeleteEventHandler < MessageIDEventHandler; end
|
146
178
|
end
|
data/lib/discordrb/logger.rb
CHANGED
@@ -1,12 +1,18 @@
|
|
1
1
|
module Discordrb
|
2
2
|
# Logs debug messages
|
3
3
|
class Logger
|
4
|
+
# @return [true, false] whether or not this logger should be in debug mode (all debug messages will be printed)
|
4
5
|
attr_writer :debug
|
5
6
|
|
7
|
+
# Writes a debug message to the console.
|
8
|
+
# @param important [true, false] Whether this message should be printed regardless of debug mode being on or off.
|
6
9
|
def debug(message, important = false)
|
7
|
-
puts "[DEBUG : #{Thread.current[:discordrb_name]} @ #{Time.now}] #{message}" if @debug || important
|
10
|
+
puts "[DEBUG : #{Thread.current[:discordrb_name]} @ #{Time.now.strftime('%Y-%m-%d %H:%M:%S.%L %z')}] #{message}" if @debug || important
|
8
11
|
end
|
9
12
|
|
13
|
+
# Logs an exception to the console.
|
14
|
+
# @param e [Exception] The exception to log.
|
15
|
+
# @param important [true, false] Whether this exception should be printed regardless of debug mode being on or off.
|
10
16
|
def log_exception(e, important = true)
|
11
17
|
debug("Exception: #{e.inspect}", important)
|
12
18
|
e.backtrace.each { |line| debug(line, important) }
|
@@ -52,11 +52,14 @@ module Discordrb
|
|
52
52
|
|
53
53
|
attr_reader :bits
|
54
54
|
|
55
|
+
# Set the raw bitset of this permission object
|
56
|
+
# @param bits [Fixnum] A number whose binary representation is the desired bitset.
|
55
57
|
def bits=(bits)
|
56
58
|
@bits = bits
|
57
59
|
init_vars
|
58
60
|
end
|
59
61
|
|
62
|
+
# Initialize the instance variables based on the bitset.
|
60
63
|
def init_vars
|
61
64
|
Flags.each do |position, flag|
|
62
65
|
flag_set = ((@bits >> position) & 0x1) == 1
|
@@ -22,6 +22,7 @@ module Discordrb
|
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
|
+
# @return [Hash<Symbol => String>] the data representing the token and encryption data, all encrypted and base64-encoded
|
25
26
|
def data
|
26
27
|
{
|
27
28
|
verify_salt: Base64.encode64(@verify_salt),
|
@@ -32,23 +33,37 @@ module Discordrb
|
|
32
33
|
}
|
33
34
|
end
|
34
35
|
|
36
|
+
# Verifies this encrypted token with a given password
|
37
|
+
# @param password [String] A plaintext password to verify
|
38
|
+
# @see #hash_password
|
39
|
+
# @return [true, false] whether or not the verification succeeded
|
35
40
|
def verify_password(password)
|
36
41
|
hash_password(password) == @password_hash
|
37
42
|
end
|
38
43
|
|
44
|
+
# Sets the given password as the verification password
|
45
|
+
# @param password [String] A plaintext password to set
|
46
|
+
# @see #hash_password
|
39
47
|
def generate_verify_hash(password)
|
40
48
|
@password_hash = hash_password(password)
|
41
49
|
end
|
42
50
|
|
51
|
+
# Generates a key from a given password using PBKDF2 with a SHA1 HMAC, 300k iterations and 32 bytes long
|
52
|
+
# @param password [String] A password to use as the base for the key
|
53
|
+
# @return [String] The generated key
|
43
54
|
def obtain_key(password)
|
44
55
|
@key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(password, @encrypt_salt, 300_000, KEYLEN)
|
45
56
|
end
|
46
57
|
|
58
|
+
# Generates cryptographically random salts for this token
|
47
59
|
def generate_salts
|
48
60
|
@verify_salt = OpenSSL::Random.random_bytes(KEYLEN)
|
49
61
|
@encrypt_salt = OpenSSL::Random.random_bytes(KEYLEN)
|
50
62
|
end
|
51
63
|
|
64
|
+
# Decrypts a token using a given password
|
65
|
+
# @param password [String] The plaintext password to decrypt the token with
|
66
|
+
# @return [String] the plaintext token
|
52
67
|
def decrypt_token(password)
|
53
68
|
key = obtain_key(password)
|
54
69
|
decipher = OpenSSL::Cipher::AES256.new(:CBC)
|
@@ -58,6 +73,10 @@ module Discordrb
|
|
58
73
|
decipher.update(@encrypted_token) + decipher.final
|
59
74
|
end
|
60
75
|
|
76
|
+
# Encrypts a given token with the given password, using AES256 CBC
|
77
|
+
# @param password [String] The plaintext password to encrypt the token with
|
78
|
+
# @param token [String] The plaintext token to encrypt
|
79
|
+
# @return [String] the encrypted token
|
61
80
|
def encrypt_token(password, token)
|
62
81
|
key = obtain_key(password)
|
63
82
|
cipher = OpenSSL::Cipher::AES256.new(:CBC)
|
@@ -67,12 +86,15 @@ module Discordrb
|
|
67
86
|
@encrypted_token = cipher.update(token) + cipher.final
|
68
87
|
end
|
69
88
|
|
89
|
+
# Tests a token by making an API request, throws an error if not successful
|
90
|
+
# @param token [String] A plaintext token to test
|
70
91
|
def test_token(token)
|
71
|
-
Discordrb::API.
|
92
|
+
Discordrb::API.validate_token(token + 'a')
|
72
93
|
end
|
73
94
|
|
74
|
-
|
75
|
-
|
95
|
+
# Hashes a password using PBKDF2 with a SHA256 digest
|
96
|
+
# @param password [String] The password to hash
|
97
|
+
# @return [String] The hashed password
|
76
98
|
def hash_password(password)
|
77
99
|
digest = OpenSSL::Digest::SHA256.new
|
78
100
|
OpenSSL::PKCS5.pbkdf2_hmac(password, @verify_salt, 300_000, digest.digest_length, digest)
|
@@ -98,6 +120,10 @@ module Discordrb
|
|
98
120
|
@data = {}
|
99
121
|
end
|
100
122
|
|
123
|
+
# Gets a token from this token cache
|
124
|
+
# @param email [String] The email to get the token for
|
125
|
+
# @param password [String] The plaintext password to get the token for
|
126
|
+
# @return [String, nil] the stored token, or nil if unsuccessful (e. g. token not cached or cached token invalid)
|
101
127
|
def token(email, password)
|
102
128
|
if @data[email]
|
103
129
|
begin
|
@@ -108,7 +134,10 @@ module Discordrb
|
|
108
134
|
begin
|
109
135
|
cached.test_token(token)
|
110
136
|
token
|
111
|
-
rescue => e
|
137
|
+
rescue => e
|
138
|
+
fail_token('Token cached, verified and decrypted, but rejected by Discord', email, e)
|
139
|
+
sleep 1 # wait some time so we don't get immediately rate limited
|
140
|
+
nil
|
112
141
|
end
|
113
142
|
else; fail_token('Token cached and verified, but decryption failed', email)
|
114
143
|
end
|
@@ -120,6 +149,10 @@ module Discordrb
|
|
120
149
|
end
|
121
150
|
end
|
122
151
|
|
152
|
+
# Caches a token
|
153
|
+
# @param email [String] The email to store this token under
|
154
|
+
# @param password [String] The plaintext password to encrypt the token with
|
155
|
+
# @param token [String] The plaintext token to cache
|
123
156
|
def store_token(email, password, token)
|
124
157
|
cached = CachedToken.new
|
125
158
|
cached.generate_verify_hash(password)
|
@@ -128,6 +161,7 @@ module Discordrb
|
|
128
161
|
write_cache
|
129
162
|
end
|
130
163
|
|
164
|
+
# Writes the cache to a file
|
131
165
|
def write_cache
|
132
166
|
File.write(CACHE_PATH, @data.to_json)
|
133
167
|
end
|