discordrb 2.1.3 → 3.0.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.

@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'discordrb/api'
4
+ require 'discordrb/api/invite'
5
+ require 'discordrb/api/user'
4
6
  require 'discordrb/light/data'
5
7
  require 'discordrb/light/integrations'
6
8
 
@@ -29,13 +31,13 @@ module Discordrb::Light
29
31
 
30
32
  # @return [LightProfile] the details of the user this bot is connected to.
31
33
  def profile
32
- response = Discordrb::API.profile(@token)
34
+ response = Discordrb::API::User.profile(@token)
33
35
  LightProfile.new(JSON.parse(response), self)
34
36
  end
35
37
 
36
38
  # @return [Array<LightServer>] the servers this bot is connected to.
37
39
  def servers
38
- response = Discordrb::API.servers(@token)
40
+ response = Discordrb::API::User.servers(@token)
39
41
  JSON.parse(response).map { |e| LightServer.new(e, self) }
40
42
  end
41
43
 
@@ -43,13 +45,13 @@ module Discordrb::Light
43
45
  # @param code [String] The code part of the invite (for example 0cDvIgU2voWn4BaD if the invite URL is
44
46
  # https://discord.gg/0cDvIgU2voWn4BaD)
45
47
  def join(code)
46
- Discordrb::API.join_server(@token, code)
48
+ Discordrb::API::Invite.accept(@token, code)
47
49
  end
48
50
 
49
51
  # Gets the connections associated with this account.
50
52
  # @return [Array<Connection>] this account's connections.
51
53
  def connections
52
- response = Discordrb::API.connections(@token)
54
+ response = Discordrb::API::User.connections(@token)
53
55
  JSON.parse(response).map { |e| Connection.new(e, self) }
54
56
  end
55
57
  end
@@ -9,11 +9,20 @@ module Discordrb
9
9
  # @return [true, false] whether this logger is in extra-fancy mode!
10
10
  attr_writer :fancy
11
11
 
12
+ # @return [String, nil] The bot token to be redacted or nil if it shouldn't.
13
+ attr_writer :token
14
+
15
+ # @return [Array<IO>, Array<#puts & #flush>] the streams the logger should write to.
16
+ attr_accessor :streams
17
+
12
18
  # Creates a new logger.
13
19
  # @param fancy [true, false] Whether this logger uses fancy mode (ANSI escape codes to make the output colourful)
14
- def initialize(fancy = false)
20
+ # @param streams [Array<IO>, Array<#puts & #flush>] the streams the logger should write to.
21
+ def initialize(fancy = false, streams = [STDOUT])
15
22
  @fancy = fancy
16
23
  self.mode = :normal
24
+
25
+ @streams = streams
17
26
  end
18
27
 
19
28
  # The modes this logger can have. This is probably useless unless you want to write your own Logger
@@ -80,19 +89,27 @@ module Discordrb
80
89
  def write(message, mode)
81
90
  thread_name = Thread.current[:discordrb_name]
82
91
  timestamp = Time.now.strftime(LOG_TIMESTAMP_FORMAT)
83
- if @fancy
84
- fancy_write(message, mode, thread_name, timestamp)
85
- else
86
- simple_write(message, mode, thread_name, timestamp)
92
+
93
+ # Redact token if set
94
+ message.gsub!(@token, 'REDACTED_TOKEN') if @token
95
+
96
+ @streams.each do |stream|
97
+ if @fancy && !stream.is_a?(File)
98
+ fancy_write(stream, message, mode, thread_name, timestamp)
99
+ else
100
+ simple_write(stream, message, mode, thread_name, timestamp)
101
+ end
87
102
  end
88
103
  end
89
104
 
90
- def fancy_write(message, mode, thread_name, timestamp)
91
- puts "#{timestamp} #{FORMAT_BOLD}#{thread_name.ljust(16)}#{FORMAT_RESET} #{mode[:format_code]}#{mode[:short]}#{FORMAT_RESET} #{message}"
105
+ def fancy_write(stream, message, mode, thread_name, timestamp)
106
+ stream.puts "#{timestamp} #{FORMAT_BOLD}#{thread_name.ljust(16)}#{FORMAT_RESET} #{mode[:format_code]}#{mode[:short]}#{FORMAT_RESET} #{message}"
107
+ stream.flush
92
108
  end
93
109
 
94
- def simple_write(message, mode, thread_name, timestamp)
95
- puts "[#{mode[:long]} : #{thread_name} @ #{timestamp}] #{message}"
110
+ def simple_write(stream, message, mode, thread_name, timestamp)
111
+ stream.puts "[#{mode[:long]} : #{thread_name} @ #{timestamp}] #{message}"
112
+ stream.flush
96
113
  end
97
114
  end
98
115
  end
@@ -10,7 +10,7 @@ module Discordrb
10
10
  0 => :create_instant_invite, # 1
11
11
  1 => :kick_members, # 2
12
12
  2 => :ban_members, # 4
13
- 3 => :manage_roles, # 8, also Manage Permissions
13
+ 3 => :administrator, # 8
14
14
  4 => :manage_channels, # 16
15
15
  5 => :manage_server, # 32
16
16
  # 6 # 64
@@ -25,7 +25,7 @@ module Discordrb
25
25
  15 => :attach_files, # 32768
26
26
  16 => :read_message_history, # 65536
27
27
  17 => :mention_everyone, # 131072
28
- # 18 # 262144
28
+ 18 => :use_external_emoji, # 262144
29
29
  # 19 # 524288
30
30
  20 => :connect, # 1048576
31
31
  21 => :speak, # 2097152
@@ -34,7 +34,8 @@ module Discordrb
34
34
  24 => :move_members, # 16777216
35
35
  25 => :use_voice_activity, # 33554432
36
36
  26 => :change_nickname, # 67108864
37
- 27 => :manage_nicknames # 134217728
37
+ 27 => :manage_nicknames, # 134217728
38
+ 28 => :manage_roles # 268435456, also Manage Permissions
38
39
  }.freeze
39
40
 
40
41
  Flags.each do |position, flag|
@@ -52,6 +53,8 @@ module Discordrb
52
53
  end
53
54
  end
54
55
 
56
+ alias_method :can_administrate=, :can_administrator=
57
+
55
58
  attr_reader :bits
56
59
 
57
60
  # Set the raw bitset of this permission object
@@ -3,5 +3,5 @@
3
3
  # Discordrb and all its functionality, in this case only the version.
4
4
  module Discordrb
5
5
  # The current version of discordrb.
6
- VERSION = '2.1.3'.freeze
6
+ VERSION = '3.0.0'.freeze
7
7
  end
@@ -21,6 +21,9 @@ module Discordrb::Voice
21
21
  # should check the parameters and adjust them to your connection: {VoiceBot#adjust_interval},
22
22
  # {VoiceBot#adjust_offset}, and {VoiceBot#adjust_average}.
23
23
  class VoiceBot
24
+ # @return [Channel] the current voice channel
25
+ attr_reader :channel
26
+
24
27
  # @return [Integer, nil] the amount of time the stream has been playing, or `nil` if nothing has been played yet.
25
28
  attr_reader :stream_time
26
29
 
@@ -88,6 +91,7 @@ module Discordrb::Voice
88
91
  @adjust_debug = true
89
92
 
90
93
  @volume = 1.0
94
+ @playing = false
91
95
 
92
96
  @encoder = Encoder.new
93
97
  @ws.connect
@@ -120,6 +124,14 @@ module Discordrb::Voice
120
124
  @paused = true
121
125
  end
122
126
 
127
+ # @see #play
128
+ # @return [true, false] Whether it is playing sound or not.
129
+ def playing?
130
+ @playing
131
+ end
132
+
133
+ alias_method :isplaying?, :playing?
134
+
123
135
  # Continue playback. This change may take up to 100 ms to take effect, which is usually negligible.
124
136
  def continue
125
137
  @paused = false
@@ -140,11 +152,19 @@ module Discordrb::Voice
140
152
  end
141
153
 
142
154
  # Stops the current playback entirely.
143
- def stop_playing
155
+ # @param wait_for_confirmation [true, false] Whether the method should wait for confirmation from the playback
156
+ # method that the playback has actually stopped.
157
+ def stop_playing(wait_for_confirmation = false)
144
158
  @was_playing_before = @playing
145
159
  @speaking = false
146
160
  @playing = false
147
161
  sleep IDEAL_LENGTH / 1000.0 if @was_playing_before
162
+
163
+ if wait_for_confirmation
164
+ @has_stopped_playing = false
165
+ sleep IDEAL_LENGTH / 1000.0 until @has_stopped_playing
166
+ @has_stopped_playing = false
167
+ end
148
168
  end
149
169
 
150
170
  # Permanently disconnects from the voice channel; to reconnect you will have to call {Bot#voice_connect} again.
@@ -160,7 +180,7 @@ module Discordrb::Voice
160
180
  # methods in separate threads.
161
181
  # @param encoded_io [IO] A stream of raw PCM data (s16le)
162
182
  def play(encoded_io)
163
- stop_playing if @playing
183
+ stop_playing(true) if @playing
164
184
  @retry_attempts = 3
165
185
  @first_packet = true
166
186
 
@@ -180,7 +200,7 @@ module Discordrb::Voice
180
200
  # Check whether the buffer has enough data
181
201
  if !buf || buf.length != DATA_LENGTH
182
202
  @bot.debug("No data is available! Retrying #{@retry_attempts} more times")
183
- break if @retry_attempts == 0
203
+ break if @retry_attempts.zero?
184
204
 
185
205
  @retry_attempts -= 1
186
206
  next
@@ -233,7 +253,7 @@ module Discordrb::Voice
233
253
  # @see https://github.com/bwmarrin/dca
234
254
  # @see #play
235
255
  def play_dca(file)
236
- stop_playing if @playing
256
+ stop_playing(true) if @playing
237
257
 
238
258
  @bot.debug "Reading DCA file #{file}"
239
259
  input_stream = open(file)
@@ -362,12 +382,15 @@ module Discordrb::Voice
362
382
 
363
383
  # Final cleanup
364
384
  stop_playing
385
+
386
+ # Notify any stop_playing methods running right now that we have actually stopped
387
+ @has_stopped_playing = true
365
388
  end
366
389
 
367
390
  # Increment sequence and time
368
391
  def increment_packet_headers
369
- (@sequence + 10 < 65_535) ? @sequence += 1 : @sequence = 0
370
- (@time + 9600 < 4_294_967_295) ? @time += 960 : @time = 0
392
+ @sequence + 10 < 65_535 ? @sequence += 1 : @sequence = 0
393
+ @time + 9600 < 4_294_967_295 ? @time += 960 : @time = 0
371
394
  end
372
395
  end
373
396
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: discordrb
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.3
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - meew0
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-06-11 00:00:00.000000000 Z
11
+ date: 2016-09-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rest-client
@@ -142,14 +142,14 @@ dependencies:
142
142
  requirements:
143
143
  - - '='
144
144
  - !ruby/object:Gem::Version
145
- version: 0.39.0
145
+ version: 0.42.0
146
146
  type: :development
147
147
  prerelease: false
148
148
  version_requirements: !ruby/object:Gem::Requirement
149
149
  requirements:
150
150
  - - '='
151
151
  - !ruby/object:Gem::Version
152
- version: 0.39.0
152
+ version: 0.42.0
153
153
  description: A Ruby implementation of the Discord (https://discordapp.com) API.
154
154
  email:
155
155
  - ''
@@ -172,13 +172,20 @@ files:
172
172
  - bin/setup
173
173
  - discordrb.gemspec
174
174
  - examples/commands.rb
175
+ - examples/data/music.dca
176
+ - examples/data/music.mp3
175
177
  - examples/eval.rb
176
178
  - examples/ping.rb
177
179
  - examples/ping_with_respond_time.rb
178
180
  - examples/pm_send.rb
179
181
  - examples/shutdown.rb
182
+ - examples/voice_send.rb
180
183
  - lib/discordrb.rb
181
184
  - lib/discordrb/api.rb
185
+ - lib/discordrb/api/channel.rb
186
+ - lib/discordrb/api/invite.rb
187
+ - lib/discordrb/api/server.rb
188
+ - lib/discordrb/api/user.rb
182
189
  - lib/discordrb/await.rb
183
190
  - lib/discordrb/bot.rb
184
191
  - lib/discordrb/cache.rb
@@ -202,13 +209,13 @@ files:
202
209
  - lib/discordrb/events/roles.rb
203
210
  - lib/discordrb/events/typing.rb
204
211
  - lib/discordrb/events/voice_state_update.rb
212
+ - lib/discordrb/gateway.rb
205
213
  - lib/discordrb/light.rb
206
214
  - lib/discordrb/light/data.rb
207
215
  - lib/discordrb/light/integrations.rb
208
216
  - lib/discordrb/light/light_bot.rb
209
217
  - lib/discordrb/logger.rb
210
218
  - lib/discordrb/permissions.rb
211
- - lib/discordrb/token_cache.rb
212
219
  - lib/discordrb/version.rb
213
220
  - lib/discordrb/voice/encoder.rb
214
221
  - lib/discordrb/voice/network.rb
@@ -1,181 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'base64'
4
- require 'json'
5
- require 'openssl'
6
- require 'discordrb/api'
7
-
8
- # Discordrb
9
- module Discordrb
10
- # Amount of bytes the token encryption key should be long (32 bytes = 256 bits -> AES256)
11
- KEYLEN = 32
12
-
13
- # Represents a cached token with encryption data
14
- class CachedToken
15
- # Parse the cached token from the JSON data read from the file.
16
- def initialize(data = nil)
17
- if data
18
- @verify_salt = Base64.decode64(data['verify_salt'])
19
- @password_hash = Base64.decode64(data['password_hash'])
20
- @encrypt_salt = Base64.decode64(data['encrypt_salt'])
21
- @iv = Base64.decode64(data['iv'])
22
- @encrypted_token = Base64.decode64(data['encrypted_token'])
23
- else
24
- generate_salts
25
- end
26
- end
27
-
28
- # @return [Hash<Symbol => String>] the data representing the token and encryption data, all encrypted and base64-encoded
29
- def data
30
- {
31
- verify_salt: Base64.encode64(@verify_salt),
32
- password_hash: Base64.encode64(@password_hash),
33
- encrypt_salt: Base64.encode64(@encrypt_salt),
34
- iv: Base64.encode64(@iv),
35
- encrypted_token: Base64.encode64(@encrypted_token)
36
- }
37
- end
38
-
39
- # Verifies this encrypted token with a given password
40
- # @param password [String] A plaintext password to verify
41
- # @see #hash_password
42
- # @return [true, false] whether or not the verification succeeded
43
- def verify_password(password)
44
- hash_password(password) == @password_hash
45
- end
46
-
47
- # Sets the given password as the verification password
48
- # @param password [String] A plaintext password to set
49
- # @see #hash_password
50
- def generate_verify_hash(password)
51
- @password_hash = hash_password(password)
52
- end
53
-
54
- # Generates a key from a given password using PBKDF2 with a SHA1 HMAC, 300k iterations and 32 bytes long
55
- # @param password [String] A password to use as the base for the key
56
- # @return [String] The generated key
57
- def obtain_key(password)
58
- @key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(password, @encrypt_salt, 300_000, KEYLEN)
59
- end
60
-
61
- # Generates cryptographically random salts for this token
62
- def generate_salts
63
- @verify_salt = OpenSSL::Random.random_bytes(KEYLEN)
64
- @encrypt_salt = OpenSSL::Random.random_bytes(KEYLEN)
65
- end
66
-
67
- # Decrypts a token using a given password
68
- # @param password [String] The plaintext password to decrypt the token with
69
- # @return [String] the plaintext token
70
- def decrypt_token(password)
71
- key = obtain_key(password)
72
- decipher = OpenSSL::Cipher::AES256.new(:CBC)
73
- decipher.decrypt
74
- decipher.key = key
75
- decipher.iv = @iv
76
- decipher.update(@encrypted_token) + decipher.final
77
- end
78
-
79
- # Encrypts a given token with the given password, using AES256 CBC
80
- # @param password [String] The plaintext password to encrypt the token with
81
- # @param token [String] The plaintext token to encrypt
82
- # @return [String] the encrypted token
83
- def encrypt_token(password, token)
84
- key = obtain_key(password)
85
- cipher = OpenSSL::Cipher::AES256.new(:CBC)
86
- cipher.encrypt
87
- cipher.key = key
88
- @iv = cipher.random_iv
89
- @encrypted_token = cipher.update(token) + cipher.final
90
- end
91
-
92
- # Tests a token by making an API request, throws an error if not successful
93
- # @param token [String] A plaintext token to test
94
- def test_token(token)
95
- Discordrb::API.validate_token(token)
96
- end
97
-
98
- # Hashes a password using PBKDF2 with a SHA256 digest
99
- # @param password [String] The password to hash
100
- # @return [String] The hashed password
101
- def hash_password(password)
102
- digest = OpenSSL::Digest::SHA256.new
103
- OpenSSL::PKCS5.pbkdf2_hmac(password, @verify_salt, 300_000, digest.digest_length, digest)
104
- end
105
- end
106
-
107
- # Path where the token cache file will be stored
108
- CACHE_PATH = Dir.home + '/.discordrb_token_cache.json'
109
-
110
- # Represents a token file
111
- class TokenCache
112
- def initialize
113
- if File.file? CACHE_PATH
114
- @data = JSON.parse(File.read(CACHE_PATH))
115
- else
116
- LOGGER.debug("Cache file #{CACHE_PATH} not found. Using empty cache")
117
- @data = {}
118
- end
119
- rescue => e
120
- LOGGER.debug('Exception occurred while parsing token cache file:', true)
121
- LOGGER.log_exception(e)
122
- LOGGER.debug('Continuing with empty cache')
123
- @data = {}
124
- end
125
-
126
- # Gets a token from this token cache
127
- # @param email [String] The email to get the token for
128
- # @param password [String] The plaintext password to get the token for
129
- # @return [String, nil] the stored token, or nil if unsuccessful (e. g. token not cached or cached token invalid)
130
- def token(email, password)
131
- if @data[email]
132
- begin
133
- cached = CachedToken.new(@data[email])
134
- if cached.verify_password(password)
135
- token = cached.decrypt_token(password)
136
- if token
137
- begin
138
- cached.test_token(token)
139
- token
140
- rescue => e
141
- fail_token('Token cached, verified and decrypted, but rejected by Discord', email, e)
142
- sleep 1 # wait some time so we don't get immediately rate limited
143
- nil
144
- end
145
- else; fail_token('Token cached and verified, but decryption failed', email)
146
- end
147
- else; fail_token('Token verification failed', email)
148
- end
149
- rescue => e; fail_token('Token cached but invalid', email, e)
150
- end
151
- else; fail_token('Token not cached at all')
152
- end
153
- end
154
-
155
- # Caches a token
156
- # @param email [String] The email to store this token under
157
- # @param password [String] The plaintext password to encrypt the token with
158
- # @param token [String] The plaintext token to cache
159
- def store_token(email, password, token)
160
- cached = CachedToken.new
161
- cached.generate_verify_hash(password)
162
- cached.encrypt_token(password, token)
163
- @data[email] = cached.data
164
- write_cache
165
- end
166
-
167
- # Writes the cache to a file
168
- def write_cache
169
- File.write(CACHE_PATH, @data.to_json)
170
- end
171
-
172
- private
173
-
174
- def fail_token(msg, email = nil, e = nil)
175
- LOGGER.warn("Token not retrieved from cache - #{msg}")
176
- LOGGER.log_exception(e) if e
177
- @data.delete(email) if email
178
- nil
179
- end
180
- end
181
- end