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.

@@ -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
- a == e.name
41
- elsif a.is_a? Integer
42
- a == e.id
43
- else
44
- a == e
45
- end
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
@@ -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.gateway(token)
92
+ Discordrb::API.validate_token(token + 'a')
72
93
  end
73
94
 
74
- private
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; fail_token('Token cached, verified and decrypted, but rejected by Discord', email, 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