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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ae206b1493cc2947c8e9e167a7b56beddb8dc6b0
4
- data.tar.gz: 2cd3796af4b5b0641bca15ccbadc9fe2969995c5
3
+ metadata.gz: 444d3ff3a3d9f6ead173e204baff8943bd5e6997
4
+ data.tar.gz: 28672c3ca7bf371d173f7bb30f08d8e5fcdce310
5
5
  SHA512:
6
- metadata.gz: 6ced598d35f407a828bf0e71de59426a6295299e63d4639b6149ec35673d07dc953fe7c8f93e3a238ef24cafb8ad63a4bde7f9fd7d1d0d0ebefbd331ffdd96c9
7
- data.tar.gz: 0a63a2826f570ea166c9e6d5f5de257643d9a3616140f3e3938f31e78c27d21dcba9b58c411403223332b5887e9bf8cd21a9b38e92083c93c97a624317a97469
6
+ metadata.gz: f7fee8556f251531d2091a42b2986b5e89008f2eee4f4677fda8e758e221eb310a3735e236ce1ee88dae1923fabc1984c2a229e46bc0b8b7fca28f6904638070
7
+ data.tar.gz: 9ac7891121c7a7d196ad9a2ef032c642a0e21a5993f9cdbfc7db8ded7a409bcd470a7a7a8729dbcd3ed1458c9c1cd1dc9f0b5533674426f32d35b3dfe681676c
data/.travis.yml CHANGED
@@ -2,3 +2,6 @@ language: ruby
2
2
  rvm:
3
3
  - 2.2.2
4
4
  before_install: gem install bundler -v 1.10.6
5
+ script:
6
+ - bundle exec rspec spec
7
+ - bundle exec rubocop -c .rubocop.yml
data/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.7.0
4
+ * **`bot.find` and `bot.find_user` have had their fuzzy search feature removed because it only caused problems. If you still need it, you can copy the code from the repo's history.** In addition, `find` was renamed to `find_channel` but still exists as a (deprecated) alias.
5
+ * The in-line documentation using Yard is now complete and can be [accessed at RubyDoc](http://www.rubydoc.info/github/meew0/discordrb/master/). It's not quite polished yet and some things may be confusing, but it should be mostly usable.
6
+ * Events and commands can now be thoroughly modularized using a system I call 'containers'. (TODO: Add a tutorial here later)
7
+ * Support for the latest API changes:
8
+ * `Server.leave` does something different than `Server.delete`
9
+ * The WebSocket connection now uses version 3 of the protocol
10
+ * Voice bots now support playing DCA files using the [`play_dca`](http://www.rubydoc.info/github/meew0/discordrb/master/Discordrb%2FVoice%2FVoiceBot%3Aplay_dca) method. (TODO: Add a section to the voice tutorial)
11
+ * The [volume](http://www.rubydoc.info/github/meew0/discordrb/master/Discordrb%2FVoice%2FVoiceBot%3Avolume) of a voice bot can now be changed during playback and not only for future playbacks.
12
+ * A `Channel.prune` method was added to quickly delete lots of messages from a channel. (It appears that this is something lots of bots do.)
13
+ * [`Server#members`](http://www.rubydoc.info/github/meew0/discordrb/master/Discordrb%2FServer%3Amembers) is now aliased to `users`.
14
+ * An attribute [`Server#member_count`](http://www.rubydoc.info/github/meew0/discordrb/master/Discordrb%2FServer%3Amember_count) was added that is accurate even if chunked members have not been added yet.
15
+ * An attribute [`Server#large?`](http://www.rubydoc.info/github/meew0/discordrb/master/Discordrb%2FServer%3Alarge) was added that is true if a server could possibly have an inaccurate list of members.
16
+ * Some more specific error classes have been added to replace the RestClient generic ones.
17
+ * Quickly sending a message using the `event << 'text'` syntax now works in every type of message event, not just commands.
18
+ * You can now set the bitrate of sent audio data using `bot.voice.encoder.bitrate = 64000` (see [`Encoder#bitrate=`](http://www.rubydoc.info/github/meew0/discordrb/master/Discordrb/Voice/Encoder#bitrate%3D-instance_method)). Note that sent audio data will always be unaffected by voice channel bitrate settings, those only tell the client at what bitrate it should send.
19
+ * A rate limiting feature was added to commands - you can define buckets using the [`bucket`](http://www.rubydoc.info/github/meew0/discordrb/master/Discordrb%2FCommands%2FRateLimiter%3Abucket) method and use them as a parameter for [`command`](http://www.rubydoc.info/github/meew0/discordrb/master/Discordrb%2FCommands%2FCommandContainer%3Acommand).
20
+ * A [`SimpleRateLimiter`](http://www.rubydoc.info/github/meew0/discordrb/master/Discordrb/Commands/SimpleRateLimiter) class was also added if you want rate limiting independent from commands (e. g. for events)
21
+ * Connecting to the WebSocket now uses an exponential falloff system so we don't spam Discord with requests anymore.
22
+ * Debug timestamps are now accurate to milliseconds.
23
+
24
+
25
+ ### Bugfixes
26
+ * The token cacher will now detect whether a cached token has been invalidated due to a password change.
27
+ * `break`ing from an event or command will no longer spew `LocalJumpError`s to the console.
28
+
3
29
  ## 1.6.6
4
30
 
5
31
  ### Bugfixes
data/README.md CHANGED
@@ -85,7 +85,7 @@ You can find me (@meew0, ID 66237334693085184) on the unofficial Discord API ser
85
85
 
86
86
  ## Development
87
87
 
88
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake false` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
88
+ After checking out the repo, run `bin/setup` to install dependencies. You can then run tests via `bundle exec rspec spec`. Make sure to run rubocop also: `bundle exec rubocop`. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
89
89
 
90
90
  To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
91
91
 
data/discordrb.gemspec CHANGED
@@ -31,4 +31,6 @@ Gem::Specification.new do |spec|
31
31
  spec.add_development_dependency 'bundler', '~> 1.10'
32
32
  spec.add_development_dependency 'rake', '~> 10.0'
33
33
  spec.add_development_dependency 'yard', '~> 0.8.7.6'
34
+ spec.add_development_dependency 'rspec', '~> 3.4.0'
35
+ spec.add_development_dependency 'rubocop', '0.36.0'
34
36
  end
data/lib/discordrb.rb CHANGED
@@ -25,4 +25,4 @@ class String
25
25
  def resolve_id
26
26
  to_i
27
27
  end
28
- end
28
+ end
data/lib/discordrb/api.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  require 'rest-client'
2
2
  require 'json'
3
3
 
4
+ require 'discordrb/errors'
5
+
4
6
  # List of methods representing endpoints in Discord's API
5
7
  module Discordrb::API
6
8
  # The base URL of the Discord REST API.
@@ -8,10 +10,12 @@ module Discordrb::API
8
10
 
9
11
  module_function
10
12
 
13
+ # @return [String] the bot name, previously specified using #bot_name=.
11
14
  def bot_name
12
15
  @bot_name
13
16
  end
14
17
 
18
+ # Sets the bot name to something.
15
19
  def bot_name=(value)
16
20
  @bot_name = value
17
21
  end
@@ -25,8 +29,13 @@ module Discordrb::API
25
29
  "rest-client/#{RestClient::VERSION} #{RUBY_ENGINE}/#{RUBY_VERSION}p#{RUBY_PATCHLEVEL} discordrb/#{Discordrb::VERSION} #{required} #{@bot_name}"
26
30
  end
27
31
 
32
+ # Performs a RestClient request.
33
+ # @param type [Symbol] The type of HTTP request to use.
34
+ # @param attributes [Array] The attributes for the request.
28
35
  def raw_request(type, attributes)
29
36
  RestClient.send(type, *attributes)
37
+ rescue RestClient::Forbidden
38
+ raise Discordrb::Errors::NoPermission, "The bot doesn't have the required permission to do this!"
30
39
  end
31
40
 
32
41
  # Make an API request. Utility function to implement message queueing
@@ -112,10 +121,10 @@ module Discordrb::API
112
121
  # Logout from the server
113
122
  def logout(token)
114
123
  request(
115
- :post,
116
- "#{APIBASE}/auth/logout",
117
- nil,
118
- Authorization: token
124
+ :post,
125
+ "#{APIBASE}/auth/logout",
126
+ nil,
127
+ Authorization: token
119
128
  )
120
129
  end
121
130
 
@@ -161,7 +170,14 @@ module Discordrb::API
161
170
  )
162
171
  end
163
172
 
164
- alias_method :leave_server, :delete_server
173
+ # Leave a server
174
+ def leave_server(token, server_id)
175
+ request(
176
+ :delete,
177
+ "#{APIBASE}/users/@me/guilds/#{server_id}",
178
+ Authorization: token
179
+ )
180
+ end
165
181
 
166
182
  # Get a channel's data
167
183
  def channel(token, channel_id)
@@ -271,6 +287,10 @@ module Discordrb::API
271
287
  Authorization: token,
272
288
  content_type: :json
273
289
  )
290
+ rescue RestClient::InternalServerError
291
+ raise Discordrb::Errors::MessageTooLong, "Message over the character limit (#{message.length} > 2000)"
292
+ rescue RestClient::BadGateway
293
+ raise Discordrb::Errors::CloudflareError, "Discord's Cloudflare system encountered an error! Usually you can ignore this error and retry the request."
274
294
  end
275
295
 
276
296
  # Delete a message
@@ -390,6 +410,17 @@ module Discordrb::API
390
410
  )
391
411
  end
392
412
 
413
+ # Validate a token (this request will fail if the token is invalid)
414
+ def validate_token(token)
415
+ request(
416
+ :post,
417
+ "#{APIBASE}/auth/login",
418
+ {}.to_json,
419
+ Authorization: token,
420
+ content_type: :json
421
+ )
422
+ end
423
+
393
424
  # Start typing (needs to be resent every 5 seconds to keep up the typing)
394
425
  def start_typing(token, channel_id)
395
426
  request(
@@ -1,3 +1,5 @@
1
+ require 'discordrb/container'
2
+
1
3
  module Discordrb
2
4
  # Awaits are a way to register new, temporary event handlers on the fly. Awaits can be
3
5
  # registered using {Bot#add_await}, {User#await}, {Message#await} and {Channel#await}.
@@ -37,7 +39,7 @@ module Discordrb
37
39
  # @param event [Event] An event to check for.
38
40
  # @return [Array] This await's key and whether or not it should be deleted. If there was no match, both are nil.
39
41
  def match(event)
40
- dummy_handler = @bot.handler_class(@type).new(@attributes, @bot)
42
+ dummy_handler = EventContainer.handler_class(@type).new(@attributes, @bot)
41
43
  return [nil, nil] unless dummy_handler.matches?(event)
42
44
 
43
45
  should_delete = nil
data/lib/discordrb/bot.rb CHANGED
@@ -19,18 +19,17 @@ require 'discordrb/events/await'
19
19
  require 'discordrb/events/bans'
20
20
 
21
21
  require 'discordrb/api'
22
- require 'discordrb/exceptions'
22
+ require 'discordrb/errors'
23
23
  require 'discordrb/data'
24
24
  require 'discordrb/await'
25
25
  require 'discordrb/token_cache'
26
+ require 'discordrb/container'
26
27
 
27
28
  require 'discordrb/voice/voice_bot'
28
29
 
29
30
  module Discordrb
30
31
  # Represents a Discord bot, including servers, users, etc.
31
32
  class Bot
32
- include Discordrb::Events
33
-
34
33
  # The user that represents the bot itself. This version will always be identical to
35
34
  # the user determined by {#user} called with the bot's ID.
36
35
  # @return [User] The bot user.
@@ -63,6 +62,8 @@ module Discordrb
63
62
  # same codebase. Not required but I recommend setting it anyway.
64
63
  attr_accessor :name
65
64
 
65
+ include EventContainer
66
+
66
67
  # Makes a new bot with the given email and password. It will be ready to be added event handlers to and can eventually be run with {#run}.
67
68
  # @param email [String] The email for your (or the bot's) Discord account.
68
69
  # @param password [String] The valid password that should be used to log in to the account.
@@ -87,13 +88,9 @@ module Discordrb
87
88
  debug('Token cache created successfully')
88
89
  @token = login
89
90
 
90
- @event_handlers = {}
91
-
92
91
  @channels = {}
93
92
  @users = {}
94
93
 
95
- @awaits = {}
96
-
97
94
  @event_threads = []
98
95
  @current_thread = 0
99
96
  end
@@ -120,6 +117,8 @@ module Discordrb
120
117
  sync
121
118
  end
122
119
 
120
+ # Runs the bot asynchronously. Equivalent to #run with the :async parameter.
121
+ # @see #run
123
122
  def run_async
124
123
  # Handle heartbeats
125
124
  @heartbeat_interval = 1
@@ -134,11 +133,19 @@ module Discordrb
134
133
 
135
134
  @ws_thread = Thread.new do
136
135
  Thread.current[:discordrb_name] = 'websocket'
136
+
137
+ # Initialize falloff so we wait for more time before reconnecting each time
138
+ @falloff = 1.0
139
+
137
140
  loop do
138
141
  websocket_connect
139
- debug('Disconnected! Attempting to reconnect in 5 seconds.')
140
- sleep 5
142
+ debug("Disconnected! Attempting to reconnect in #{@falloff} seconds.")
143
+ sleep @falloff
141
144
  @token = login
145
+
146
+ # Calculate new falloff
147
+ @falloff *= 1.5
148
+ @falloff = 115 + (rand * 10) if @falloff > 1 # Cap the falloff at 120 seconds and then add some random jitter
142
149
  end
143
150
  end
144
151
 
@@ -284,76 +291,33 @@ module Discordrb
284
291
  @servers[id]
285
292
  end
286
293
 
287
- # Finds a channel given its name and optionally the name of the server it is in. If the threshold
288
- # is not 0, it will use a Levenshtein distance function to find the channel in a fuzzy way, which
289
- # allows slight misspellings.
294
+ # Finds a channel given its name and optionally the name of the server it is in.
290
295
  # @param channel_name [String] The channel to search for.
291
296
  # @param server_name [String] The server to search for, or `nil` if only the channel should be searched for.
292
- # @param threshold [Integer] The threshold for the Levenshtein algorithm. The larger
293
- # the threshold is, the more misspellings will be allowed.
294
297
  # @return [Array<Channel>] The array of channels that were found. May be empty if none were found.
295
- def find(channel_name, server_name = nil, threshold = 0)
296
- begin
297
- require 'levenshtein' if threshold > 0
298
- levenshtein_available = true
299
- rescue LoadError; levenshtein_available = false; end
300
-
298
+ def find_channel(channel_name, server_name = nil)
301
299
  results = []
300
+
302
301
  @servers.values.each do |server|
303
302
  server.channels.each do |channel|
304
- if threshold > 0
305
- fail LoadError, 'Levenshtein distance unavailable! Either set threshold to 0 or install the `levenshtein-ffi` gem' unless levenshtein_available
306
- distance = Levenshtein.distance(channel.name, channel_name)
307
- distance += Levenshtein.distance(server_name || server.name, server.name)
308
- next if distance > threshold
309
- else
310
- distance = 0
311
- next if channel.name != channel_name || (server_name || server.name) != server.name
312
- end
313
-
314
- # Make a singleton accessor "distance"
315
- channel.instance_variable_set(:@distance, distance)
316
- class << channel
317
- attr_reader :distance
318
- end
319
-
320
- results << channel
303
+ results << channel if channel.name == channel_name && (server_name || server.name) == server.name
321
304
  end
322
305
  end
306
+
323
307
  results
324
308
  end
325
309
 
326
- # Finds a user given its username. This allows fuzzy finding using Levenshtein
327
- # distances, see {#find}
310
+ # Finds a user given its username.
328
311
  # @param username [String] The username to look for.
329
- # @param threshold [Integer] The threshold for the Levenshtein algorithm. The larger
330
- # the threshold is, the more misspellings will be allowed.
331
312
  # @return [Array<User>] The array of users that were found. May be empty if none were found.
332
- def find_user(username, threshold = 0)
333
- begin
334
- require 'levenshtein' if threshold > 0
335
- levenshtein_available = true
336
- rescue LoadError; levenshtein_available = false; end
337
-
338
- results = []
339
- @users.values.each do |user|
340
- if threshold > 0
341
- fail LoadError, 'Levenshtein distance unavailable! Either set threshold to 0 or install the `levenshtein-ffi` gem' unless levenshtein_available
342
- distance = Levenshtein.distance(user.username, username)
343
- next if distance > threshold
344
- else
345
- distance = 0
346
- next if user.username != username
347
- end
313
+ def find_user(username)
314
+ @users.values.find_all { |e| e.username == username }
315
+ end
348
316
 
349
- # Make a singleton accessor "distance"
350
- user.instance_variable_set(:@distance, distance)
351
- class << user
352
- attr_reader :distance
353
- end
354
- results << user
355
- end
356
- results
317
+ # @deprecated Use {#find_channel} instead
318
+ def find(channel_name, server_name = nil)
319
+ debug('Attempted to use bot.find - this method is deprecated! Use find_channel for the same functionality')
320
+ find_channel(channel_name, server_name)
357
321
  end
358
322
 
359
323
  # Sends a text message to a channel given its ID and the message's content.
@@ -376,19 +340,6 @@ module Discordrb
376
340
  Message.new(JSON.parse(response), self)
377
341
  end
378
342
 
379
- # Add an await the bot should listen to. For information on awaits, see {Await}.
380
- # @param key [Symbol] The key that uniquely identifies the await for {AwaitEvent}s to listen to (see {#await}).
381
- # @param type [Class] The event class that should be listened for.
382
- # @param attributes [Hash] The attributes the event should check for. The block will only be executed if all attributes match.
383
- # @yield Is executed when the await is triggered.
384
- # @yieldparam event [Event] The event object that was triggered.
385
- # @return [Await] The await that was created.
386
- def add_await(key, type, attributes = {}, &block)
387
- fail "You can't await an AwaitEvent!" if type == Discordrb::Events::AwaitEvent
388
- await = Await.new(self, key, type, attributes, block)
389
- @awaits[key] = await
390
- end
391
-
392
343
  # Creates a server on Discord with a specified name and a region.
393
344
  # @note Discord's API doesn't directly return the server when creating it, so this method
394
345
  # waits until the data has been received via the websocket. This may make the execution take a while.
@@ -449,176 +400,30 @@ module Discordrb
449
400
  @prevent_ready = true
450
401
  end
451
402
 
452
- ## ## ### ## ## ######## ## ######## ######## ######
453
- ## ## ## ## ### ## ## ## ## ## ## ## ## ##
454
- ## ## ## ## #### ## ## ## ## ## ## ## ##
455
- ######### ## ## ## ## ## ## ## ## ###### ######## ######
456
- ## ## ######### ## #### ## ## ## ## ## ## ##
457
- ## ## ## ## ## ### ## ## ## ## ## ## ## ##
458
- ## ## ## ## ## ## ######## ######## ######## ## ## ######
459
-
460
- # This **event** is raised when a message is sent to a text channel the bot is currently in.
461
- # @param attributes [Hash] The event's attributes.
462
- # @option attributes [String, Regexp] :start_with Matches the string the message starts with.
463
- # @option attributes [String, Regexp] :end_with Matches the string the message ends with.
464
- # @option attributes [String, Regexp] :contains Matches a string the message contains.
465
- # @option attributes [String, Integer, Channel] :in Matches the channel the message was sent in.
466
- # @option attributes [String, Integer, User] :from Matches the user that sent the message.
467
- # @option attributes [String] :content Exactly matches the entire content of the message.
468
- # @option attributes [String] :content Exactly matches the entire content of the message.
469
- # @option attributes [Time] :after Matches a time after the time the message was sent at.
470
- # @option attributes [Time] :before Matches a time before the time the message was sent at.
471
- # @option attributes [Boolean] :private Matches whether or not the channel is private.
472
- # @yield The block is executed when the event is raised.
473
- # @yieldparam event [MessageEvent] The event that was raised.
474
- # @return [MessageEventHandler] The event handler that was registered.
475
- def message(attributes = {}, &block)
476
- register_event(MessageEvent, attributes, block)
477
- end
478
-
479
- def ready(attributes = {}, &block)
480
- register_event(ReadyEvent, attributes, block)
481
- end
482
-
483
- def disconnected(attributes = {}, &block)
484
- register_event(DisconnectEvent, attributes, block)
485
- end
486
-
487
- def typing(attributes = {}, &block)
488
- register_event(TypingEvent, attributes, block)
489
- end
490
-
491
- def message_edit(attributes = {}, &block)
492
- register_event(MessageEditEvent, attributes, block)
493
- end
494
-
495
- def message_delete(attributes = {}, &block)
496
- register_event(MessageDeleteEvent, attributes, block)
497
- end
498
-
499
- def presence(attributes = {}, &block)
500
- register_event(PresenceEvent, attributes, block)
501
- end
502
-
503
- def playing(attributes = {}, &block)
504
- register_event(PlayingEvent, attributes, block)
505
- end
506
-
507
- def mention(attributes = {}, &block)
508
- register_event(MentionEvent, attributes, block)
509
- end
510
-
511
- # Handle channel creation
512
- # Attributes:
513
- # * type: Channel type ('text' or 'voice')
514
- # * name: Channel name
515
- def channel_create(attributes = {}, &block)
516
- register_event(ChannelCreateEvent, attributes, block)
517
- end
518
-
519
- # Handle channel update
520
- # Attributes:
521
- # * type: Channel type ('text' or 'voice')
522
- # * name: Channel name
523
- def channel_update(attributes = {}, &block)
524
- register_event(ChannelUpdateEvent, attributes, block)
525
- end
526
-
527
- # Handle channel deletion
528
- # Attributes:
529
- # * type: Channel type ('text' or 'voice')
530
- # * name: Channel name
531
- def channel_delete(attributes = {}, &block)
532
- register_event(ChannelDeleteEvent, attributes, block)
533
- end
534
-
535
- # Handle a change to a voice state.
536
- # This includes joining a voice channel or changing mute or deaf state.
537
- # Attributes:
538
- # * from: User whose voice state changed
539
- # * mute: server mute status
540
- # * deaf: server deaf status
541
- # * self_mute: self mute status
542
- # * self_deaf: self deaf status
543
- # * channel: channel the user joined
544
- def voice_state_update(attributes = {}, &block)
545
- register_event(VoiceStateUpdateEvent, attributes, block)
546
- end
547
-
548
- def member_join(attributes = {}, &block)
549
- register_event(GuildMemberAddEvent, attributes, block)
550
- end
551
-
552
- def member_update(attributes = {}, &block)
553
- register_event(GuildMemberUpdateEvent, attributes, block)
554
- end
555
-
556
- def member_leave(attributes = {}, &block)
557
- register_event(GuildMemberDeleteEvent, attributes, block)
558
- end
559
-
560
- def user_ban(attributes = {}, &block)
561
- register_event(UserBanEvent, attributes, block)
562
- end
563
-
564
- def user_unban(attributes = {}, &block)
565
- register_event(UserUnbanEvent, attributes, block)
566
- end
567
-
568
- def server_create(attributes = {}, &block)
569
- register_event(GuildCreateEvent, attributes, block)
570
- end
571
-
572
- def server_update(attributes = {}, &block)
573
- register_event(GuildUpdateEvent, attributes, block)
574
- end
575
-
576
- def server_delete(attributes = {}, &block)
577
- register_event(GuildDeleteEvent, attributes, block)
578
- end
579
-
580
- # This **event** is raised when an {Await} is triggered. It provides an easy way to execute code
581
- # on an await without having to rely on the await's block.
582
- # @param attributes [Hash] The event's attributes.
583
- # @option attributes [Symbol] :key Exactly matches the await's key.
584
- # @option attributes [Class] :type Exactly matches the event's type.
585
- # @yield The block is executed when the event is raised.
586
- # @yieldparam event [AwaitEvent] The event that was raised.
587
- # @return [AwaitEventHandler] The event handler that was registered.
588
- def await(attributes = {}, &block)
589
- register_event(AwaitEvent, attributes, block)
590
- end
591
-
592
- def pm(attributes = {}, &block)
593
- register_event(PrivateMessageEvent, attributes, block)
594
- end
595
-
596
- alias_method :private_message, :pm
597
-
598
- def remove_handler(handler)
599
- clazz = event_class(handler.class)
600
- @event_handlers[clazz].delete(handler)
601
- end
602
-
603
- def add_handler(handler)
604
- clazz = event_class(handler.class)
605
- @event_handlers[clazz] << handler
403
+ # Add an await the bot should listen to. For information on awaits, see {Await}.
404
+ # @param key [Symbol] The key that uniquely identifies the await for {AwaitEvent}s to listen to (see {#await}).
405
+ # @param type [Class] The event class that should be listened for.
406
+ # @param attributes [Hash] The attributes the event should check for. The block will only be executed if all attributes match.
407
+ # @yield Is executed when the await is triggered.
408
+ # @yieldparam event [Event] The event object that was triggered.
409
+ # @return [Await] The await that was created.
410
+ def add_await(key, type, attributes = {}, &block)
411
+ fail "You can't await an AwaitEvent!" if type == Discordrb::Events::AwaitEvent
412
+ await = Await.new(self, key, type, attributes, block)
413
+ @awaits ||= {}
414
+ @awaits[key] = await
606
415
  end
607
416
 
417
+ # @see Logger#debug
608
418
  def debug(message, important = false)
609
419
  LOGGER.debug(message, important)
610
420
  end
611
421
 
422
+ # @see Logger#log_exception
612
423
  def log_exception(e)
613
424
  LOGGER.log_exception(e)
614
425
  end
615
426
 
616
- def handler_class(event_class)
617
- class_from_string(event_class.to_s + 'Handler')
618
- end
619
-
620
- alias_method :<<, :add_handler
621
-
622
427
  private
623
428
 
624
429
  ####### ### ###### ## ## ########
@@ -673,6 +478,13 @@ module Discordrb
673
478
  server.members << user
674
479
  end
675
480
  end
481
+
482
+ username = data['user']['username']
483
+ if username
484
+ debug "User changed username: #{user.username} #{username}"
485
+ user.update_username(username)
486
+ end
487
+
676
488
  user.status = status
677
489
  user.game = data['game'] ? data['game']['name'] : nil
678
490
  user
@@ -877,11 +689,11 @@ module Discordrb
877
689
 
878
690
  # Login
879
691
  login_response = API.login(@email, @password)
880
- fail HTTPStatusException, login_response.code if login_response.code >= 400
692
+ fail Discordrb::Errors::HTTPStatusError, login_response.code if login_response.code >= 400
881
693
 
882
694
  # Parse response
883
695
  login_response_object = JSON.parse(login_response)
884
- fail InvalidAuthenticationException unless login_response_object['token']
696
+ fail Discordrb::Errors::InvalidAuthenticationError unless login_response_object['token']
885
697
 
886
698
  debug('Received token from Discord!')
887
699
 
@@ -1164,7 +976,7 @@ module Discordrb
1164
976
  packet = {
1165
977
  op: 2, # Packet identifier
1166
978
  d: { # Packet data
1167
- v: 2, # Another identifier
979
+ v: 3, # WebSocket protocol version
1168
980
  token: @token,
1169
981
  properties: { # I'm unsure what these values are for exactly, but they don't appear to impact bot functionality in any way.
1170
982
  :'$os' => RUBY_PLATFORM.to_s,
@@ -1180,10 +992,22 @@ module Discordrb
1180
992
  @ws.send(packet.to_json)
1181
993
  end
1182
994
 
995
+ def send_heartbeat
996
+ millis = Time.now.strftime('%s%L').to_i
997
+ debug("Sending heartbeat at #{millis}")
998
+ data = {
999
+ op: 1,
1000
+ d: millis
1001
+ }
1002
+
1003
+ @ws.send(data.to_json)
1004
+ end
1005
+
1183
1006
  def raise_event(event)
1184
1007
  debug("Raised a #{event.class}")
1185
1008
  handle_awaits(event)
1186
1009
 
1010
+ @event_handlers ||= {}
1187
1011
  handlers = @event_handlers[event.class]
1188
1012
  (handlers || []).each do |handler|
1189
1013
  call_event(handler, event) if handler.matches?(event)
@@ -1192,10 +1016,14 @@ module Discordrb
1192
1016
 
1193
1017
  def call_event(handler, event)
1194
1018
  t = Thread.new do
1019
+ @event_threads ||= []
1020
+ @current_thread ||= 0
1021
+
1195
1022
  @event_threads << t
1196
1023
  Thread.current[:discordrb_name] = "et-#{@current_thread += 1}"
1197
1024
  begin
1198
1025
  handler.call(event)
1026
+ handler.after_call(event)
1199
1027
  rescue => e
1200
1028
  log_exception(e)
1201
1029
  ensure
@@ -1205,6 +1033,7 @@ module Discordrb
1205
1033
  end
1206
1034
 
1207
1035
  def handle_awaits(event)
1036
+ @awaits ||= {}
1208
1037
  @awaits.each do |_, await|
1209
1038
  key, should_delete = await.match(event)
1210
1039
  next unless key
@@ -1215,39 +1044,5 @@ module Discordrb
1215
1044
  raise_event(await_event)
1216
1045
  end
1217
1046
  end
1218
-
1219
- def register_event(clazz, attributes, block)
1220
- handler = handler_class(clazz).new(attributes, block)
1221
-
1222
- @event_handlers[clazz] ||= []
1223
- @event_handlers[clazz] << handler
1224
-
1225
- # Return the handler so it can be removed later
1226
- handler
1227
- end
1228
-
1229
- def send_heartbeat
1230
- millis = Time.now.strftime('%s%L').to_i
1231
- debug("Sending heartbeat at #{millis}")
1232
- data = {
1233
- op: 1,
1234
- d: millis
1235
- }
1236
-
1237
- @ws.send(data.to_json)
1238
- end
1239
-
1240
- def class_from_string(str)
1241
- str.split('::').inject(Object) do |mod, class_name|
1242
- mod.const_get(class_name)
1243
- end
1244
- end
1245
-
1246
- def event_class(handler_class)
1247
- class_name = handler_class.to_s
1248
- return nil unless class_name.end_with? 'Handler'
1249
-
1250
- class_from_string(class_name[0..-8])
1251
- end
1252
1047
  end
1253
1048
  end