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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 444d3ff3a3d9f6ead173e204baff8943bd5e6997
|
4
|
+
data.tar.gz: 28672c3ca7bf371d173f7bb30f08d8e5fcdce310
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f7fee8556f251531d2091a42b2986b5e89008f2eee4f4677fda8e758e221eb310a3735e236ce1ee88dae1923fabc1984c2a229e46bc0b8b7fca28f6904638070
|
7
|
+
data.tar.gz: 9ac7891121c7a7d196ad9a2ef032c642a0e21a5993f9cdbfc7db8ded7a409bcd470a7a7a8729dbcd3ed1458c9c1cd1dc9f0b5533674426f32d35b3dfe681676c
|
data/.travis.yml
CHANGED
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.
|
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
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
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
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
|
-
|
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(
|
data/lib/discordrb/await.rb
CHANGED
@@ -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 =
|
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/
|
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(
|
140
|
-
sleep
|
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.
|
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
|
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
|
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.
|
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
|
333
|
-
|
334
|
-
|
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
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
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
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
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
|
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
|
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:
|
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
|