discordrb 1.2.0 → 1.3.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: 002c1c37653e0e75bbe66c5727030a39f5eae9d8
4
- data.tar.gz: e703d694a84d3fe9b738403aee0517fa250ae549
3
+ metadata.gz: 8c01b23a0f26aef256c77bec16db1cc3526d3f7b
4
+ data.tar.gz: 53240d527a39b8d49d8bb684af53ed168c0a7ba5
5
5
  SHA512:
6
- metadata.gz: e43742e8e297e34d20892936267e1e60464945ee020b6c69c4b6d6f8277fb7256b32e3050a5af3696f0926f168e1e889e2dc9b0a7517d4ba04c62f38f1f1b24c
7
- data.tar.gz: ce006992dd5bf162e19c32f0bca74980982023954ee448ec7b04916d153bf7bade1d5f6b182a108bb05e298160d3f8b73f25f84d7cc71dff28cdb88a7300813e
6
+ metadata.gz: a5fef38890173193ba99fa598b875f8fb6fb738676ee25d2d5d79cee32b7783099d95c566d9c497f9036e15c3d72f92bed72b3ed4e4cd995f698738071fe6389
7
+ data.tar.gz: 043e53d22977fb1ded5d8c32c45974a61e5a77f69f68c0d1eb7120f9dceb4820cb66e29c1672f86bb241e16d39781b11435ec98832c91d5e34193f2dcea669b6
data/.gitignore CHANGED
@@ -1,9 +1,9 @@
1
- /.bundle/
2
- /.yardoc
3
- /Gemfile.lock
4
- /_yardoc/
5
- /coverage/
6
- /doc/
7
- /pkg/
8
- /spec/reports/
9
- /tmp/
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.travis.yml CHANGED
@@ -1,4 +1,4 @@
1
- language: ruby
2
- rvm:
3
- - 2.2.2
4
- before_install: gem install bundler -v 1.10.6
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.2
4
+ before_install: gem install bundler -v 1.10.6
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
- source 'https://rubygems.org'
2
-
3
- # Specify your gem's dependencies in discordrb.gemspec
4
- gemspec
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in discordrb.gemspec
4
+ gemspec
data/LICENSE.txt CHANGED
@@ -1,21 +1,21 @@
1
- The MIT License (MIT)
2
-
3
- Copyright (c) 2015 meew0
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in
13
- all copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
- THE SOFTWARE.
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 meew0
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md CHANGED
@@ -1,78 +1,78 @@
1
- [![Build Status](https://travis-ci.org/meew0/discordrb.svg?branch=master)](https://travis-ci.org/meew0/discordrb)
2
-
3
- # discordrb
4
-
5
- An implementation of the [Discord](https://discordapp.com/) API using Ruby.
6
-
7
- ## Installation
8
-
9
- ### Linux
10
-
11
- On Linux, it should be as simple as running:
12
-
13
- $ gem install discordrb
14
-
15
- ### Windows
16
-
17
- On Windows, to install discordrb, run this in a shell:
18
-
19
- $ gem install discordrb
20
-
21
- Run the [ping example](https://github.com/meew0/discordrb/blob/master/examples/ping.rb) to verify that the installation works (make sure to replace the username and password in there with your own or your bots'!):
22
-
23
- $ ruby ping.rb
24
-
25
- #### Troubleshooting
26
-
27
- **If you get an error like this when installing the gem**:
28
-
29
- ERROR: Error installing discordrb:
30
- The 'websocket-driver' native gem requires installed build tools.
31
-
32
- You're missing the development kit required to build native extensions. Follow [these instructions](https://github.com/oneclick/rubyinstaller/wiki/Development-Kit#installation-instructions) and reinstall discordrb:
33
-
34
- $ gem uninstall discordrb
35
- $ gem install discordrb
36
-
37
- **If you get an error like this when running the example**:
38
-
39
- terminate called after throwing an instance of 'std::runtime_error'
40
- what(): Encryption not available on this event-machine
41
-
42
- You're missing the OpenSSL libraries that EventMachine, a dependency of discordrb, needs to be built with to use encrypted connections (which Discord requires). Download the OpenSSL libraries from [here](http://slproweb.com/download/Win32OpenSSL-1_0_2d.exe), install them to their default location and reinstall EventMachine using these libraries:
43
-
44
- $ gem uninstall eventmachine
45
- $ gem install eventmachine -- --with-ssl-dir=C:/OpenSSL-Win32
46
-
47
- ## Usage
48
-
49
- You can make a simple bot like this:
50
-
51
- ```ruby
52
- require 'discordrb'
53
-
54
- bot = Discordrb::Bot.new "email@example.com", "hunter2"
55
-
56
- bot.message(with_text: "Ping!") do |event|
57
- event.respond "Pong!"
58
- end
59
-
60
- bot.run
61
- ```
62
-
63
- This bot responds to every "Ping!" with a "Pong!".
64
-
65
- ## Development
66
-
67
- 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.
68
-
69
- 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).
70
-
71
- ## Contributing
72
-
73
- Bug reports and pull requests are welcome on GitHub at https://github.com/meew0/discordrb.
74
-
75
-
76
- ## License
77
-
78
- The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
1
+ [![Build Status](https://travis-ci.org/meew0/discordrb.svg?branch=master)](https://travis-ci.org/meew0/discordrb)
2
+
3
+ # discordrb
4
+
5
+ An implementation of the [Discord](https://discordapp.com/) API using Ruby.
6
+
7
+ ## Installation
8
+
9
+ ### Linux
10
+
11
+ On Linux, it should be as simple as running:
12
+
13
+ $ gem install discordrb
14
+
15
+ ### Windows
16
+
17
+ On Windows, to install discordrb, run this in a shell:
18
+
19
+ $ gem install discordrb
20
+
21
+ Run the [ping example](https://github.com/meew0/discordrb/blob/master/examples/ping.rb) to verify that the installation works (make sure to replace the username and password in there with your own or your bots'!):
22
+
23
+ $ ruby ping.rb
24
+
25
+ #### Troubleshooting
26
+
27
+ **If you get an error like this when installing the gem**:
28
+
29
+ ERROR: Error installing discordrb:
30
+ The 'websocket-driver' native gem requires installed build tools.
31
+
32
+ You're missing the development kit required to build native extensions. Follow [these instructions](https://github.com/oneclick/rubyinstaller/wiki/Development-Kit#installation-instructions) and reinstall discordrb:
33
+
34
+ $ gem uninstall discordrb
35
+ $ gem install discordrb
36
+
37
+ **If you get an error like this when running the example**:
38
+
39
+ terminate called after throwing an instance of 'std::runtime_error'
40
+ what(): Encryption not available on this event-machine
41
+
42
+ You're missing the OpenSSL libraries that EventMachine, a dependency of discordrb, needs to be built with to use encrypted connections (which Discord requires). Download the OpenSSL libraries from [here](http://slproweb.com/download/Win32OpenSSL-1_0_2d.exe), install them to their default location and reinstall EventMachine using these libraries:
43
+
44
+ $ gem uninstall eventmachine
45
+ $ gem install eventmachine -- --with-ssl-dir=C:/OpenSSL-Win32
46
+
47
+ ## Usage
48
+
49
+ You can make a simple bot like this:
50
+
51
+ ```ruby
52
+ require 'discordrb'
53
+
54
+ bot = Discordrb::Bot.new "email@example.com", "hunter2"
55
+
56
+ bot.message(with_text: "Ping!") do |event|
57
+ event.respond "Pong!"
58
+ end
59
+
60
+ bot.run
61
+ ```
62
+
63
+ This bot responds to every "Ping!" with a "Pong!".
64
+
65
+ ## Development
66
+
67
+ 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.
68
+
69
+ 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).
70
+
71
+ ## Contributing
72
+
73
+ Bug reports and pull requests are welcome on GitHub at https://github.com/meew0/discordrb.
74
+
75
+
76
+ ## License
77
+
78
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile CHANGED
@@ -1,4 +1,4 @@
1
- require "bundler/gem_tasks"
2
-
3
- # Make "build" the default task
4
- task :default => :build
1
+ require "bundler/gem_tasks"
2
+
3
+ # Make "build" the default task
4
+ task :default => :build
data/bin/console CHANGED
@@ -1,14 +1,14 @@
1
- #!/usr/bin/env ruby
2
-
3
- require "bundler/setup"
4
- require "discordrb"
5
-
6
- # You can add fixtures and/or initialization code here to make experimenting
7
- # with your gem easier. You can also use a different console, if you like.
8
-
9
- # (If you use this, don't forget to add pry to your Gemfile!)
10
- # require "pry"
11
- # Pry.start
12
-
13
- require "irb"
14
- IRB.start
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "discordrb"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup CHANGED
@@ -1,7 +1,7 @@
1
- #!/bin/bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
-
5
- bundle install
6
-
7
- # Do any other automated setup that you need to do here
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
data/discordrb.gemspec CHANGED
@@ -1,28 +1,28 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
3
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'discordrb/version'
5
-
6
- Gem::Specification.new do |spec|
7
- spec.name = "discordrb"
8
- spec.version = Discordrb::VERSION
9
- spec.authors = ["meew0"]
10
- spec.email = [""]
11
-
12
- spec.summary = "Discord API for Ruby"
13
- spec.description = "A Ruby implementation of the Discord (https://discordapp.com) API."
14
- spec.homepage = "https://github.com/meew0/discordrb"
15
- spec.license = "MIT"
16
-
17
- spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
- spec.bindir = "exe"
19
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
- spec.require_paths = ["lib"]
21
-
22
- spec.add_dependency "faye-websocket"
23
- spec.add_dependency "rest-client"
24
- spec.add_dependency "activesupport"
25
-
26
- spec.add_development_dependency "bundler", "~> 1.10"
27
- spec.add_development_dependency "rake", "~> 10.0"
28
- end
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'discordrb/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "discordrb"
8
+ spec.version = Discordrb::VERSION
9
+ spec.authors = ["meew0"]
10
+ spec.email = [""]
11
+
12
+ spec.summary = "Discord API for Ruby"
13
+ spec.description = "A Ruby implementation of the Discord (https://discordapp.com) API."
14
+ spec.homepage = "https://github.com/meew0/discordrb"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = "exe"
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_dependency "faye-websocket"
23
+ spec.add_dependency "rest-client"
24
+ spec.add_dependency "activesupport"
25
+
26
+ spec.add_development_dependency "bundler", "~> 1.10"
27
+ spec.add_development_dependency "rake", "~> 10.0"
28
+ end
data/examples/ping.rb CHANGED
@@ -1,11 +1,11 @@
1
- # This simple bot responds to every "Ping!" message with a "Pong!"
2
-
3
- require 'discordrb'
4
-
5
- bot = Discordrb::Bot.new "email@example.com", "hunter2"
6
-
7
- bot.message(with_text: "Ping!") do |event|
8
- event.respond "Pong!"
9
- end
10
-
11
- bot.run
1
+ # This simple bot responds to every "Ping!" message with a "Pong!"
2
+
3
+ require 'discordrb'
4
+
5
+ bot = Discordrb::Bot.new "email@example.com", "hunter2"
6
+
7
+ bot.message(with_text: "Ping!") do |event|
8
+ event.respond "Pong!"
9
+ end
10
+
11
+ bot.run
data/examples/pm.rb CHANGED
@@ -1,11 +1,11 @@
1
- # This bot shows off PM functionality by sending a PM every time the bot is mentioned.
2
-
3
- require 'discordrb'
4
-
5
- bot = Discordrb::Bot.new "email@example.com", "hunter2"
6
-
7
- bot.mention do |event|
8
- event.user.pm("You have mentioned me!")
9
- end
10
-
11
- bot.run
1
+ # This bot shows off PM functionality by sending a PM every time the bot is mentioned.
2
+
3
+ require 'discordrb'
4
+
5
+ bot = Discordrb::Bot.new "email@example.com", "hunter2"
6
+
7
+ bot.mention do |event|
8
+ event.user.pm("You have mentioned me!")
9
+ end
10
+
11
+ bot.run
data/lib/discordrb.rb CHANGED
@@ -1,6 +1,7 @@
1
- require "discordrb/version"
2
- require "discordrb/bot"
3
-
4
- module Discordrb
5
- # Your code goes here...
6
- end
1
+ require "discordrb/version"
2
+ require "discordrb/bot"
3
+ require "discordrb/commands/command-bot"
4
+
5
+ module Discordrb
6
+ # Your code goes here...
7
+ end
data/lib/discordrb/bot.rb CHANGED
@@ -1,525 +1,550 @@
1
- require 'rest-client'
2
- require 'faye/websocket'
3
- require 'eventmachine'
4
-
5
- require 'discordrb/endpoints/endpoints'
6
-
7
- require 'discordrb/events/message'
8
- require 'discordrb/events/typing'
9
- require 'discordrb/events/lifetime'
10
- require 'discordrb/events/presence'
11
- require 'discordrb/events/voice-state-update'
12
- require 'discordrb/events/channel-create'
13
- require 'discordrb/events/channel-update'
14
- require 'discordrb/events/channel-delete'
15
- require 'discordrb/events/guild-member-update'
16
- require 'discordrb/events/guild-role-create'
17
- require 'discordrb/events/guild-role-delete'
18
- require 'discordrb/events/guild-role-update'
19
-
20
- require 'discordrb/exceptions'
21
- require 'discordrb/data'
22
-
23
- module Discordrb
24
- class Bot
25
- include Discordrb::Events
26
- def initialize(email, password, debug = false)
27
- # Make sure people replace the login details in the example files...
28
- if email.end_with? "example.com"
29
- puts "You have to replace the login details in the example files with your own!"
30
- exit
31
- end
32
-
33
- @debug = debug
34
-
35
- @email = email
36
- @password = password
37
-
38
- @token = login
39
-
40
- @event_handlers = {}
41
-
42
- @channels = {}
43
- @users = {}
44
- end
45
-
46
- def run
47
- # Handle heartbeats
48
- @heartbeat_interval = 1
49
- @heartbeat_active = false
50
- @heartbeat_thread = Thread.new do
51
- while true do
52
- sleep @heartbeat_interval
53
- send_heartbeat if @heartbeat_active
54
- end
55
- end
56
-
57
- while true do
58
- websocket_connect
59
- debug("Disconnected! Attempting to reconnect in 5 seconds.")
60
- sleep 5
61
- @token = login
62
- end
63
- end
64
-
65
- def channel(id)
66
- debug("Obtaining data for channel with id #{id}")
67
- return @channels[id] if @channels[id]
68
-
69
- response = RestClient.get Discordrb::Endpoints::CHANNELS + "/#{id}", {:Authorization => @token}
70
- channel = Channel.new(JSON.parse(response), self)
71
- @channels[id] = channel
72
- end
73
-
74
- def private_channel(id)
75
- debug("Creating private channel with user id #{id}")
76
- return @private_channels[id] if @private_channels[id]
77
-
78
- data = {
79
- 'recipient_id' => id
80
- }
81
-
82
- response = RestClient.post Discordrb::Endpoints::USERS + "/#{@bot_user.id}/channels", data.to_json, {:Authorization => @token, :content_type => :json}
83
- channel = Channel.new(JSON.parse(response), self)
84
- @private_channels[id] = channel
85
- end
86
-
87
- def user(id)
88
- @users[id]
89
- end
90
-
91
- def server(id)
92
- @servers[id]
93
- end
94
-
95
- def send_message(channel_id, content)
96
- debug("Sending message to #{channel_id} with content '#{content}'")
97
- data = {
98
- 'content' => content.to_s,
99
- 'mentions' => []
100
- }
101
- RestClient.post Discordrb::Endpoints::CHANNELS + "/#{channel_id}/messages", data.to_json, {:Authorization => @token, :content_type => :json}
102
- end
103
-
104
- def debug=(debug)
105
- @debug = debug
106
- end
107
-
108
- def message(attributes = {}, &block)
109
- register_event(MessageEvent, attributes, block)
110
- end
111
-
112
- def ready(attributes = {}, &block)
113
- register_event(ReadyEvent, attributes, block)
114
- end
115
-
116
- def disconnected(attributes = {}, &block)
117
- register_event(DisconnectEvent, attributes, block)
118
- end
119
-
120
- def typing(attributes = {}, &block)
121
- register_event(TypingEvent, attributes, block)
122
- end
123
-
124
- def presence(attributes = {}, &block)
125
- register_event(PresenceEvent, attributes, block)
126
- end
127
-
128
- def mention(attributes = {}, &block)
129
- register_event(MentionEvent, attributes, block)
130
- end
131
-
132
- # Handle channel creation
133
- # Attributes:
134
- # * type: Channel type ('text' or 'voice')
135
- # * name: Channel name
136
- def channel_create(attributes = {}, &block)
137
- register_event(ChannelCreateEvent, attributes, block)
138
- end
139
-
140
- # Handle channel update
141
- # Attributes:
142
- # * type: Channel type ('text' or 'voice')
143
- # * name: Channel name
144
- def channel_update(attributes = {}, &block)
145
- register_event(ChannelUpdateEvent, attributes, block)
146
- end
147
-
148
- # Handle channel deletion
149
- # Attributes:
150
- # * type: Channel type ('text' or 'voice')
151
- # * name: Channel name
152
- def channel_delete(attributes = {}, &block)
153
- register_event(ChannelDeleteEvent, attributes, block)
154
- end
155
-
156
- # Handle a change to a voice state.
157
- # This includes joining a voice channel or changing mute or deaf state.
158
- # Attributes:
159
- # * from: User whose voice state changed
160
- # * mute: server mute status
161
- # * deaf: server deaf status
162
- # * self_mute: self mute status
163
- # * self_deaf: self deaf status
164
- # * channel: channel the user joined
165
- def voice_state_update(attributes = {}, &block)
166
- register_event(VoiceStateUpdateEvent, attributes, block)
167
- end
168
-
169
- def remove_handler(handler)
170
- clazz = event_class(handler.class)
171
- @event_handlers[clazz].delete(handler)
172
- end
173
-
174
- def add_handler(handler)
175
- clazz = event_class(handler.class)
176
- @event_handlers[clazz] << handler
177
- end
178
-
179
- alias_method :<<, :add_handler
180
-
181
- private
182
-
183
- # Internal handler for PRESENCE_UPDATE
184
- def update_presence(data)
185
- user_id = data['user']['id'].to_i
186
- server_id = data['guild_id'].to_i
187
- server = @servers[server_id]
188
- return if !server
189
-
190
- user = @users[user_id]
191
- if !user
192
- user = User.new(data['user'], self)
193
- @users[user_id] = user
194
- end
195
-
196
- status = data['status'].to_sym
197
- if status != :offline
198
- if !(server.members.find {|u| u.id == user.id })
199
- server.members << user
200
- end
201
- end
202
- user.status = status
203
- user.game_id = data['game_id']
204
- end
205
-
206
- # Internal handler for VOICE_STATUS_UPDATE
207
- def update_voice_state(data)
208
- user_id = data['user_id'].to_i
209
- server_id = data['guild_id'].to_i
210
- server = @servers[server_id]
211
- return if !server
212
-
213
- user = @users[user_id]
214
- user.server_mute = data['mute']
215
- user.server_deaf = data['deaf']
216
- user.self_mute = data['self_mute']
217
- user.self_deaf = data['self_deaf']
218
-
219
- channel_id = data['channel_id']
220
- channel = nil
221
- if channel_id
222
- channel = @channels[channel_id.to_i]
223
- end
224
- user.move(channel)
225
- end
226
-
227
- # Internal handler for CHANNEL_CREATE
228
- def create_channel(data)
229
- channel = Channel.new(data, self)
230
- server = channel.server
231
- server.channels << channel
232
- @channels[channel.id] = channel
233
- end
234
-
235
- # Internal handler for CHANNEL_UPDATE
236
- def update_channel(data)
237
- channel = Channel.new(data, self)
238
- server = channel.server
239
- old_channel = @channels[channel.id]
240
- return if !old_channel
241
- old_channel.update_from(channel)
242
- end
243
-
244
- # Internal handler for CHANNEL_DELETE
245
- def delete_channel(data)
246
- channel = Channel.new(data, self)
247
- server = channel.server
248
- @channels[channel.id] = nil
249
- server.channels.reject! {|c| c.id == channel.id}
250
- end
251
-
252
- # Internal handler for GUILD_MEMBER_UPDATE
253
- def update_guild_member(data)
254
- user_data = data['user']
255
- server_id = data['guild_id'].to_i
256
- roles = []
257
- data['roles'].each do |element|
258
- role_id = element.to_i
259
- roles << @servers[server_id].roles.find {|r| r.id == role_id}
260
- end
261
- user_id = user_data['id'].to_i
262
- user = @users[user_id]
263
- user.update_roles(roles)
264
- end
265
-
266
- # Internal handler for GUILD_ROLE_UPDATE
267
- def update_guild_role(data)
268
- role_data = data['role']
269
- server_id = data['guild_id'].to_i
270
- server = @servers[server_id]
271
- new_role = Role.new(role_data, self, server)
272
- role_id = role_data['id'].to_i
273
- old_role = server.roles.find {|r| r.id == role_id}
274
- old_role.update_from(new_role)
275
- end
276
-
277
- # Internal handler for GUILD_ROLE_CREATE
278
- def create_guild_role(data)
279
- role_data = data['role']
280
- server_id = data['guild_id'].to_i
281
- server = @servers[server_id]
282
- new_role = Role.new(role_data, self, server)
283
- server.add_role(new_role)
284
- end
285
-
286
- # Internal handler for GUILD_ROLE_DELETE
287
- def delete_guild_role(data)
288
- role_data = data['role']
289
- role_id = role_data['id'].to_i
290
- server_id = data['guild_id'].to_i
291
- server = @servers[server_id]
292
- server.delete_role(role_id)
293
- end
294
-
295
- def debug(message)
296
- puts "[DEBUG @ #{Time.now.to_s}] #{message}" if @debug
297
- end
298
-
299
- def login
300
- debug("Logging in")
301
- login_attempts = login_attempts || 0
302
-
303
- # Login
304
- login_response = RestClient.post Discordrb::Endpoints::LOGIN, :email => @email, :password => @password
305
- raise HTTPStatusException.new(login_response.code) if login_response.code >= 400
306
-
307
- # Parse response
308
- login_response_object = JSON.parse(login_response)
309
- raise InvalidAuthenticationException unless login_response_object['token']
310
-
311
- debug("Received token: #{login_response_object['token']}")
312
- login_response_object['token']
313
- rescue Exception => e
314
- response_code = login_response.nil? ? 0 : login_response.code ######## mackmm145
315
- if login_attempts < 100 && (e.inspect.include?("No such host is known.") || response_code == 523)
316
- debug("Login failed! Reattempting in 5 seconds. #{100 - login_attempts} attempts remaining.")
317
- debug("Error was: #{e.inspect}")
318
- sleep 5
319
- login_attempts += 1
320
- retry
321
- else
322
- debug("Login failed permanently after #{login_attempts + 1} attempts")
323
-
324
- # Apparently we get a 400 if the password or username is incorrect. In that case, tell the user
325
- debug("Are you sure you're using the correct username and password?") if e.class == RestClient::BadRequest
326
- raise $!
327
- end
328
- end
329
-
330
- def get_gateway
331
- # Get updated websocket_hub
332
- response = RestClient.get Discordrb::Endpoints::GATEWAY, :authorization => @token
333
- JSON.parse(response)["url"]
334
- end
335
-
336
- def websocket_connect
337
- debug("Attempting to get gateway URL...")
338
- websocket_hub = get_gateway
339
- debug("Success! Gateway URL is #{websocket_hub}.")
340
- debug("Now running bot")
341
-
342
- EM.run {
343
- @ws = Faye::WebSocket::Client.new(websocket_hub)
344
-
345
- @ws.on :open do |event|; websocket_open(event); end
346
- @ws.on :message do |event|; websocket_message(event); end
347
- @ws.on :error do |event|; debug(event.message); end
348
- @ws.on :close do |event|; websocket_close(event); @ws = nil; end
349
- }
350
- end
351
-
352
- def websocket_message(event)
353
- begin
354
- debug("Received packet #{event.data}")
355
-
356
- # Parse packet
357
- packet = JSON.parse(event.data)
358
-
359
- raise "Invalid Packet" unless packet['op'] == 0 # TODO
360
-
361
- data = packet['d']
362
- case packet['t']
363
- when "READY"
364
- # Activate the heartbeats
365
- @heartbeat_interval = data['heartbeat_interval'].to_f / 1000.0
366
- @heartbeat_active = true
367
- debug("Desired heartbeat_interval: #{@heartbeat_interval}")
368
-
369
- bot_user_id = data['user']['id'].to_i
370
-
371
- # Initialize servers
372
- @servers = {}
373
- data['guilds'].each do |element|
374
- server = Server.new(element, self)
375
- @servers[server.id] = server
376
-
377
- # Initialize users
378
- server.members.each do |element|
379
- @users[element.id] = element
380
- end
381
-
382
- # Save the bot user
383
- @bot_user = @users[bot_user_id]
384
- end
385
-
386
- # Add private channels
387
- @private_channels = {}
388
- data['private_channels'].each do |element|
389
- channel = Channel.new(element, self)
390
- @channels[channel.id] = channel
391
- @private_channels[channel.recipient.id] = channel
392
- end
393
-
394
- # Make sure to raise the event
395
- raise_event(ReadyEvent.new)
396
- when "MESSAGE_CREATE"
397
- message = Message.new(data, self)
398
- event = MessageEvent.new(message, self)
399
- raise_event(event)
400
-
401
- if message.mentions.any? { |user| user.id == @bot_user.id }
402
- event = MentionEvent.new(message, self)
403
- raise_event(event)
404
- end
405
- when "TYPING_START"
406
- event = TypingEvent.new(data, self)
407
- raise_event(event)
408
- when "PRESENCE_UPDATE"
409
- update_presence(data)
410
- event = PresenceEvent.new(data, self)
411
- raise_event(event)
412
- when "VOICE_STATE_UPDATE"
413
- update_voice_state(data)
414
- event = VoiceStateUpdateEvent.new(data, self)
415
- raise_event(event)
416
- when "CHANNEL_CREATE"
417
- create_channel(data)
418
- event = ChannelCreateEvent.new(data, self)
419
- raise_event(event)
420
- when "CHANNEL_UPDATE"
421
- update_channel(data)
422
- event = ChannelUpdateEvent.new(data, self)
423
- raise_event(event)
424
- when "CHANNEL_DELETE"
425
- delete_channel(data)
426
- event = ChannelDeleteEvent.new(data, self)
427
- raise_event(event)
428
- when "GUILD_MEMBER_UPDATE"
429
- update_guild_member(data)
430
- event = GuildMemberUpdateEvent.new(data, self)
431
- raise_event(event)
432
- when "GUILD_ROLE_UPDATE"
433
- update_guild_role(data)
434
- event = GuildRoleUpdateEvent.new(data, self)
435
- raise_event(event)
436
- when "GUILD_ROLE_CREATE"
437
- create_guild_role(data)
438
- event = GuildRoleCreateEvent.new(data, self)
439
- raise_event(event)
440
- when "GUILD_ROLE_DELETE"
441
- delete_guild_role(data)
442
- event = GuildRoleDeleteEvent.new(data, self)
443
- raise_event(event)
444
- end
445
- rescue Exception => e
446
- debug("Exception: #{e.inspect}")
447
- e.backtrace.each {|line| debug(line) }
448
- end
449
- end
450
-
451
- def websocket_close(event)
452
- debug("Disconnected from WebSocket!")
453
- debug(" (Reason: #{event.reason})")
454
- debug(" (Code: #{event.code})")
455
- raise_event(DisconnectEvent.new)
456
- EM.stop
457
- end
458
-
459
- def websocket_open(event)
460
- # Send the initial packet
461
- packet = {
462
- "op" => 2, # Packet identifier
463
- "d" => { # Packet data
464
- "v" => 2, # Another identifier
465
- "token" => @token,
466
- "properties" => { # I'm unsure what these values are for exactly, but they don't appear to impact bot functionality in any way.
467
- "$os" => "Linux",
468
- "$browser" => "",
469
- "$device" => "discordrb",
470
- "$referrer" => "",
471
- "$referring_domain" => ""
472
- }
473
- }
474
- }
475
-
476
- @ws.send(packet.to_json)
477
- end
478
-
479
- def raise_event(event)
480
- debug("Raised a #{event.class}")
481
- handlers = @event_handlers[event.class]
482
- (handlers || []).each do |handler|
483
- handler.match(event)
484
- end
485
- end
486
-
487
- def register_event(clazz, attributes, block)
488
- handler = handler_class(clazz).new(attributes, block)
489
-
490
- @event_handlers[clazz] ||= []
491
- @event_handlers[clazz] << handler
492
-
493
- # Return the handler so it can be removed later
494
- handler
495
- end
496
-
497
- def send_heartbeat
498
- millis = Time.now.strftime("%s%L").to_i
499
- debug("Sending heartbeat at #{millis}")
500
- data = {
501
- 'op' => 1,
502
- 'd' => millis
503
- }
504
-
505
- @ws.send(data.to_json)
506
- end
507
-
508
- def class_from_string(str)
509
- str.split('::').inject(Object) do |mod, class_name|
510
- mod.const_get(class_name)
511
- end
512
- end
513
-
514
- def event_class(handler_class)
515
- class_name = handler_class.to_s
516
- return nil unless class_name.end_with? "Handler"
517
-
518
- class_from_string(class_name[0..-8])
519
- end
520
-
521
- def handler_class(event_class)
522
- class_from_string(event_class.to_s + "Handler")
523
- end
524
- end
525
- end
1
+ require 'rest-client'
2
+ require 'faye/websocket'
3
+ require 'eventmachine'
4
+
5
+ require 'discordrb/endpoints/endpoints'
6
+
7
+ require 'discordrb/events/message'
8
+ require 'discordrb/events/typing'
9
+ require 'discordrb/events/lifetime'
10
+ require 'discordrb/events/presence'
11
+ require 'discordrb/events/voice-state-update'
12
+ require 'discordrb/events/channel-create'
13
+ require 'discordrb/events/channel-update'
14
+ require 'discordrb/events/channel-delete'
15
+ require 'discordrb/events/guild-member-update'
16
+ require 'discordrb/events/guild-role-create'
17
+ require 'discordrb/events/guild-role-delete'
18
+ require 'discordrb/events/guild-role-update'
19
+
20
+ require 'discordrb/exceptions'
21
+ require 'discordrb/data'
22
+
23
+ module Discordrb
24
+ class Bot
25
+ include Discordrb::Events
26
+ def initialize(email, password, debug = false)
27
+ # Make sure people replace the login details in the example files...
28
+ if email.end_with? "example.com"
29
+ puts "You have to replace the login details in the example files with your own!"
30
+ exit
31
+ end
32
+
33
+ @debug = debug
34
+
35
+ @email = email
36
+ @password = password
37
+
38
+ @token = login
39
+
40
+ @event_handlers = {}
41
+
42
+ @channels = {}
43
+ @users = {}
44
+ end
45
+
46
+ def run
47
+ # Handle heartbeats
48
+ @heartbeat_interval = 1
49
+ @heartbeat_active = false
50
+ @heartbeat_thread = Thread.new do
51
+ while true do
52
+ sleep @heartbeat_interval
53
+ send_heartbeat if @heartbeat_active
54
+ end
55
+ end
56
+
57
+ while true do
58
+ websocket_connect
59
+ debug("Disconnected! Attempting to reconnect in 5 seconds.")
60
+ sleep 5
61
+ @token = login
62
+ end
63
+ end
64
+
65
+ def channel(id)
66
+ debug("Obtaining data for channel with id #{id}")
67
+ return @channels[id] if @channels[id]
68
+
69
+ response = RestClient.get Discordrb::Endpoints::CHANNELS + "/#{id}", {:Authorization => @token}
70
+ channel = Channel.new(JSON.parse(response), self)
71
+ @channels[id] = channel
72
+ end
73
+
74
+ def private_channel(id)
75
+ debug("Creating private channel with user id #{id}")
76
+ return @private_channels[id] if @private_channels[id]
77
+
78
+ data = {
79
+ 'recipient_id' => id
80
+ }
81
+
82
+ response = RestClient.post Discordrb::Endpoints::USERS + "/#{@bot_user.id}/channels", data.to_json, {:Authorization => @token, :content_type => :json}
83
+ channel = Channel.new(JSON.parse(response), self)
84
+ @private_channels[id] = channel
85
+ end
86
+
87
+ def user(id)
88
+ @users[id]
89
+ end
90
+
91
+ def server(id)
92
+ @servers[id]
93
+ end
94
+
95
+ def send_message(channel_id, content)
96
+ debug("Sending message to #{channel_id} with content '#{content}'")
97
+ data = {
98
+ 'content' => content.to_s,
99
+ 'mentions' => []
100
+ }
101
+ RestClient.post Discordrb::Endpoints::CHANNELS + "/#{channel_id}/messages", data.to_json, {:Authorization => @token, :content_type => :json}
102
+ end
103
+
104
+ def debug=(debug)
105
+ @debug = debug
106
+ end
107
+
108
+ def message(attributes = {}, &block)
109
+ register_event(MessageEvent, attributes, block)
110
+ end
111
+
112
+ def ready(attributes = {}, &block)
113
+ register_event(ReadyEvent, attributes, block)
114
+ end
115
+
116
+ def disconnected(attributes = {}, &block)
117
+ register_event(DisconnectEvent, attributes, block)
118
+ end
119
+
120
+ def typing(attributes = {}, &block)
121
+ register_event(TypingEvent, attributes, block)
122
+ end
123
+
124
+ def presence(attributes = {}, &block)
125
+ register_event(PresenceEvent, attributes, block)
126
+ end
127
+
128
+ def mention(attributes = {}, &block)
129
+ register_event(MentionEvent, attributes, block)
130
+ end
131
+
132
+ # Handle channel creation
133
+ # Attributes:
134
+ # * type: Channel type ('text' or 'voice')
135
+ # * name: Channel name
136
+ def channel_create(attributes = {}, &block)
137
+ register_event(ChannelCreateEvent, attributes, block)
138
+ end
139
+
140
+ # Handle channel update
141
+ # Attributes:
142
+ # * type: Channel type ('text' or 'voice')
143
+ # * name: Channel name
144
+ def channel_update(attributes = {}, &block)
145
+ register_event(ChannelUpdateEvent, attributes, block)
146
+ end
147
+
148
+ # Handle channel deletion
149
+ # Attributes:
150
+ # * type: Channel type ('text' or 'voice')
151
+ # * name: Channel name
152
+ def channel_delete(attributes = {}, &block)
153
+ register_event(ChannelDeleteEvent, attributes, block)
154
+ end
155
+
156
+ # Handle a change to a voice state.
157
+ # This includes joining a voice channel or changing mute or deaf state.
158
+ # Attributes:
159
+ # * from: User whose voice state changed
160
+ # * mute: server mute status
161
+ # * deaf: server deaf status
162
+ # * self_mute: self mute status
163
+ # * self_deaf: self deaf status
164
+ # * channel: channel the user joined
165
+ def voice_state_update(attributes = {}, &block)
166
+ register_event(VoiceStateUpdateEvent, attributes, block)
167
+ end
168
+
169
+ def remove_handler(handler)
170
+ clazz = event_class(handler.class)
171
+ @event_handlers[clazz].delete(handler)
172
+ end
173
+
174
+ def add_handler(handler)
175
+ clazz = event_class(handler.class)
176
+ @event_handlers[clazz] << handler
177
+ end
178
+
179
+ def debug(message)
180
+ puts "[DEBUG @ #{Time.now.to_s}] #{message}" if @debug
181
+ end
182
+
183
+ alias_method :<<, :add_handler
184
+
185
+ private
186
+
187
+ # Internal handler for PRESENCE_UPDATE
188
+ def update_presence(data)
189
+ user_id = data['user']['id'].to_i
190
+ server_id = data['guild_id'].to_i
191
+ server = @servers[server_id]
192
+ return if !server
193
+
194
+ user = @users[user_id]
195
+ if !user
196
+ user = User.new(data['user'], self)
197
+ @users[user_id] = user
198
+ end
199
+
200
+ status = data['status'].to_sym
201
+ if status != :offline
202
+ if !(server.members.find {|u| u.id == user.id })
203
+ server.members << user
204
+ end
205
+ end
206
+ user.status = status
207
+ user.game_id = data['game_id']
208
+ end
209
+
210
+ # Internal handler for VOICE_STATUS_UPDATE
211
+ def update_voice_state(data)
212
+ user_id = data['user_id'].to_i
213
+ server_id = data['guild_id'].to_i
214
+ server = @servers[server_id]
215
+ return if !server
216
+
217
+ user = @users[user_id]
218
+ user.server_mute = data['mute']
219
+ user.server_deaf = data['deaf']
220
+ user.self_mute = data['self_mute']
221
+ user.self_deaf = data['self_deaf']
222
+
223
+ channel_id = data['channel_id']
224
+ channel = nil
225
+ if channel_id
226
+ channel = @channels[channel_id.to_i]
227
+ end
228
+ user.move(channel)
229
+ end
230
+
231
+ # Internal handler for CHANNEL_CREATE
232
+ def create_channel(data)
233
+ channel = Channel.new(data, self)
234
+ server = channel.server
235
+ server.channels << channel
236
+ @channels[channel.id] = channel
237
+ end
238
+
239
+ # Internal handler for CHANNEL_UPDATE
240
+ def update_channel(data)
241
+ channel = Channel.new(data, self)
242
+ server = channel.server
243
+ old_channel = @channels[channel.id]
244
+ return if !old_channel
245
+ old_channel.update_from(channel)
246
+ end
247
+
248
+ # Internal handler for CHANNEL_DELETE
249
+ def delete_channel(data)
250
+ channel = Channel.new(data, self)
251
+ server = channel.server
252
+ @channels[channel.id] = nil
253
+ server.channels.reject! {|c| c.id == channel.id}
254
+ end
255
+
256
+ # Internal handler for GUILD_MEMBER_UPDATE
257
+ def update_guild_member(data)
258
+ user_data = data['user']
259
+ server_id = data['guild_id'].to_i
260
+ server = @servers[server_id]
261
+ roles = []
262
+ data['roles'].each do |element|
263
+ role_id = element.to_i
264
+ roles << server.roles.find {|r| r.id == role_id}
265
+ end
266
+ user_id = user_data['id'].to_i
267
+ user = @users[user_id]
268
+ user.update_roles(server, roles)
269
+ end
270
+
271
+ # Internal handler for GUILD_ROLE_UPDATE
272
+ def update_guild_role(data)
273
+ role_data = data['role']
274
+ server_id = data['guild_id'].to_i
275
+ server = @servers[server_id]
276
+ new_role = Role.new(role_data, self, server)
277
+ role_id = role_data['id'].to_i
278
+ old_role = server.roles.find {|r| r.id == role_id}
279
+ old_role.update_from(new_role)
280
+ end
281
+
282
+ # Internal handler for GUILD_ROLE_CREATE
283
+ def create_guild_role(data)
284
+ role_data = data['role']
285
+ server_id = data['guild_id'].to_i
286
+ server = @servers[server_id]
287
+ new_role = Role.new(role_data, self, server)
288
+ server.add_role(new_role)
289
+ end
290
+
291
+ # Internal handler for GUILD_ROLE_DELETE
292
+ def delete_guild_role(data)
293
+ role_data = data['role']
294
+ role_id = role_data['id'].to_i
295
+ server_id = data['guild_id'].to_i
296
+ server = @servers[server_id]
297
+ server.delete_role(role_id)
298
+ end
299
+
300
+ # Internal handler for MESSAGE_CREATE
301
+ def create_message(data); end
302
+
303
+ # Internal handler for TYPING_START
304
+ def start_typing(data); end
305
+
306
+ def login
307
+ debug("Logging in")
308
+ login_attempts = login_attempts || 0
309
+
310
+ # Login
311
+ login_response = RestClient.post Discordrb::Endpoints::LOGIN, :email => @email, :password => @password
312
+ raise HTTPStatusException.new(login_response.code) if login_response.code >= 400
313
+
314
+ # Parse response
315
+ login_response_object = JSON.parse(login_response)
316
+ raise InvalidAuthenticationException unless login_response_object['token']
317
+
318
+ debug("Received token: #{login_response_object['token']}")
319
+ login_response_object['token']
320
+ rescue Exception => e
321
+ response_code = login_response.nil? ? 0 : login_response.code ######## mackmm145
322
+ if login_attempts < 100 && (e.inspect.include?("No such host is known.") || response_code == 523)
323
+ debug("Login failed! Reattempting in 5 seconds. #{100 - login_attempts} attempts remaining.")
324
+ debug("Error was: #{e.inspect}")
325
+ sleep 5
326
+ login_attempts += 1
327
+ retry
328
+ else
329
+ debug("Login failed permanently after #{login_attempts + 1} attempts")
330
+
331
+ # Apparently we get a 400 if the password or username is incorrect. In that case, tell the user
332
+ debug("Are you sure you're using the correct username and password?") if e.class == RestClient::BadRequest
333
+ raise $!
334
+ end
335
+ end
336
+
337
+ def get_gateway
338
+ # Get updated websocket_hub
339
+ response = RestClient.get Discordrb::Endpoints::GATEWAY, :authorization => @token
340
+ JSON.parse(response)["url"]
341
+ end
342
+
343
+ def websocket_connect
344
+ debug("Attempting to get gateway URL...")
345
+ websocket_hub = get_gateway
346
+ debug("Success! Gateway URL is #{websocket_hub}.")
347
+ debug("Now running bot")
348
+
349
+ EM.run {
350
+ @ws = Faye::WebSocket::Client.new(websocket_hub)
351
+
352
+ @ws.on :open do |event|; websocket_open(event); end
353
+ @ws.on :message do |event|; websocket_message(event); end
354
+ @ws.on :error do |event|; debug(event.message); end
355
+ @ws.on :close do |event|; websocket_close(event); @ws = nil; end
356
+ }
357
+ end
358
+
359
+ def websocket_message(event)
360
+ begin
361
+ debug("Received packet #{event.data}")
362
+
363
+ # Parse packet
364
+ packet = JSON.parse(event.data)
365
+
366
+ raise "Invalid Packet" unless packet['op'] == 0 # TODO
367
+
368
+ data = packet['d']
369
+ case packet['t']
370
+ when "READY"
371
+ # Activate the heartbeats
372
+ @heartbeat_interval = data['heartbeat_interval'].to_f / 1000.0
373
+ @heartbeat_active = true
374
+ debug("Desired heartbeat_interval: #{@heartbeat_interval}")
375
+
376
+ bot_user_id = data['user']['id'].to_i
377
+
378
+ # Initialize servers
379
+ @servers = {}
380
+ data['guilds'].each do |element|
381
+ server = Server.new(element, self)
382
+ @servers[server.id] = server
383
+
384
+ # Initialize users
385
+ server.members.each do |element|
386
+ unless @users[element.id]
387
+ @users[element.id] = element
388
+ else
389
+ # If the user is already cached, just add the new roles
390
+ @users[element.id].merge_roles(server, element.roles[server.id])
391
+ end
392
+ end
393
+
394
+ # Save the bot user
395
+ @bot_user = @users[bot_user_id]
396
+ end
397
+
398
+ # Add private channels
399
+ @private_channels = {}
400
+ data['private_channels'].each do |element|
401
+ channel = Channel.new(element, self)
402
+ @channels[channel.id] = channel
403
+ @private_channels[channel.recipient.id] = channel
404
+ end
405
+
406
+ # Make sure to raise the event
407
+ raise_event(ReadyEvent.new)
408
+ when "MESSAGE_CREATE"
409
+ create_message(data)
410
+
411
+ message = Message.new(data, self)
412
+ event = MessageEvent.new(message, self)
413
+ raise_event(event)
414
+
415
+ if message.mentions.any? { |user| user.id == @bot_user.id }
416
+ event = MentionEvent.new(message, self)
417
+ raise_event(event)
418
+ end
419
+ when "TYPING_START"
420
+ start_typing(data)
421
+
422
+ event = TypingEvent.new(data, self)
423
+ raise_event(event)
424
+ when "PRESENCE_UPDATE"
425
+ update_presence(data)
426
+
427
+ event = PresenceEvent.new(data, self)
428
+ raise_event(event)
429
+ when "VOICE_STATE_UPDATE"
430
+ update_voice_state(data)
431
+
432
+ event = VoiceStateUpdateEvent.new(data, self)
433
+ raise_event(event)
434
+ when "CHANNEL_CREATE"
435
+ create_channel(data)
436
+
437
+ event = ChannelCreateEvent.new(data, self)
438
+ raise_event(event)
439
+ when "CHANNEL_UPDATE"
440
+ update_channel(data)
441
+
442
+ event = ChannelUpdateEvent.new(data, self)
443
+ raise_event(event)
444
+ when "CHANNEL_DELETE"
445
+ delete_channel(data)
446
+
447
+ event = ChannelDeleteEvent.new(data, self)
448
+ raise_event(event)
449
+ when "GUILD_MEMBER_UPDATE"
450
+ update_guild_member(data)
451
+
452
+ event = GuildMemberUpdateEvent.new(data, self)
453
+ raise_event(event)
454
+ when "GUILD_ROLE_UPDATE"
455
+ update_guild_role(data)
456
+
457
+ event = GuildRoleUpdateEvent.new(data, self)
458
+ raise_event(event)
459
+ when "GUILD_ROLE_CREATE"
460
+ create_guild_role(data)
461
+
462
+ event = GuildRoleCreateEvent.new(data, self)
463
+ raise_event(event)
464
+ when "GUILD_ROLE_DELETE"
465
+ delete_guild_role(data)
466
+
467
+ event = GuildRoleDeleteEvent.new(data, self)
468
+ raise_event(event)
469
+ end
470
+ rescue Exception => e
471
+ debug("Exception: #{e.inspect}")
472
+ e.backtrace.each {|line| debug(line) }
473
+ end
474
+ end
475
+
476
+ def websocket_close(event)
477
+ debug("Disconnected from WebSocket!")
478
+ debug(" (Reason: #{event.reason})")
479
+ debug(" (Code: #{event.code})")
480
+ raise_event(DisconnectEvent.new)
481
+ EM.stop
482
+ end
483
+
484
+ def websocket_open(event)
485
+ # Send the initial packet
486
+ packet = {
487
+ "op" => 2, # Packet identifier
488
+ "d" => { # Packet data
489
+ "v" => 2, # Another identifier
490
+ "token" => @token,
491
+ "properties" => { # I'm unsure what these values are for exactly, but they don't appear to impact bot functionality in any way.
492
+ "$os" => "Linux",
493
+ "$browser" => "",
494
+ "$device" => "discordrb",
495
+ "$referrer" => "",
496
+ "$referring_domain" => ""
497
+ }
498
+ }
499
+ }
500
+
501
+ @ws.send(packet.to_json)
502
+ end
503
+
504
+ def raise_event(event)
505
+ debug("Raised a #{event.class}")
506
+ handlers = @event_handlers[event.class]
507
+ (handlers || []).each do |handler|
508
+ handler.match(event)
509
+ end
510
+ end
511
+
512
+ def register_event(clazz, attributes, block)
513
+ handler = handler_class(clazz).new(attributes, block)
514
+
515
+ @event_handlers[clazz] ||= []
516
+ @event_handlers[clazz] << handler
517
+
518
+ # Return the handler so it can be removed later
519
+ handler
520
+ end
521
+
522
+ def send_heartbeat
523
+ millis = Time.now.strftime("%s%L").to_i
524
+ debug("Sending heartbeat at #{millis}")
525
+ data = {
526
+ 'op' => 1,
527
+ 'd' => millis
528
+ }
529
+
530
+ @ws.send(data.to_json)
531
+ end
532
+
533
+ def class_from_string(str)
534
+ str.split('::').inject(Object) do |mod, class_name|
535
+ mod.const_get(class_name)
536
+ end
537
+ end
538
+
539
+ def event_class(handler_class)
540
+ class_name = handler_class.to_s
541
+ return nil unless class_name.end_with? "Handler"
542
+
543
+ class_from_string(class_name[0..-8])
544
+ end
545
+
546
+ def handler_class(event_class)
547
+ class_from_string(event_class.to_s + "Handler")
548
+ end
549
+ end
550
+ end