lita-discord 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a37c01b611457e695c4a7f73d5c46b79058ee502
4
+ data.tar.gz: 75d7e3923aead4abb4ecc9010c53a906c3f1da70
5
+ SHA512:
6
+ metadata.gz: 48dcfc7e93bf466930fd4cd209cb384ce3824ad86c5986d625ef1975d994402fe8818e0b70be27582844bac60e85ec1eab9f2acada0dd2e79ac3a501da973d1d
7
+ data.tar.gz: 2dbd6a02802ef02058a49c02469743da17eee803426656e2561e57408bbb6e596e71b05f654a83a208b6f1c78dbd14f290419f7ace08504cfef770c9dbbb1b34
data/.gitignore ADDED
@@ -0,0 +1,20 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ lita_config.rb
19
+ lib/lita/adapters/discord/template.rb
20
+ *.json
data/.travis.yml ADDED
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ script: bundle exec rake
5
+ before_install:
6
+ - gem update --system
7
+ services:
8
+ - redis-server
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
data/README.md ADDED
@@ -0,0 +1,31 @@
1
+ # lita-discord
2
+
3
+ [![Build Status](https://travis-ci.org/kyleboe/lita-discord.png?branch=master)](https://travis-ci.org/kyleboe/lita-discord)
4
+
5
+ **lita-discord** is an adapter for [Lita](https://www.lita.io/) that allows you to use the robot with [Discord](https://discordapp.com/)
6
+
7
+ ## Installation
8
+
9
+ Add lita-discord to your Lita instance's Gemfile:
10
+
11
+ ``` ruby
12
+ gem "lita-discord"
13
+ ```
14
+
15
+ ## Configuration
16
+
17
+ You will need to go to the discord website and make an account that will be the account that your bot will function as. (Fix this awful wording later)
18
+
19
+ ### Required attributes
20
+
21
+ * `email` (String) - The email of the account of the bot you created
22
+ * `password` (String) - The password of the account of the bot you created
23
+
24
+
25
+ ### Optional attributes
26
+
27
+ TODO: Put the optional attributes here
28
+
29
+ ## Usage
30
+
31
+ TODO: Describe the plugin's features and how to use them.
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
@@ -0,0 +1,64 @@
1
+ require 'rest-client'
2
+ require 'json'
3
+
4
+ module Lita
5
+ module Adapters
6
+ class Discord < Adapter
7
+ class API
8
+ APIBASE = 'https://discordapp.com/api'.freeze
9
+
10
+ def user_agent
11
+ # This particular string is required by the Discord devs.
12
+ required = "lita-discord (https://github.com/kyleboe/lita-discord, v0.1.0)"
13
+ @bot_name ||= ''
14
+
15
+ "rest-client/#{RestClient::VERSION} #{RUBY_ENGINE}/#{RUBY_VERSION}p#{RUBY_PATCHLEVEL} lita-discord/0.1.0 #{required} #{@bot_name}"
16
+ end
17
+
18
+ # Referenced https://github.com/meew0/discordrb/blob/9897dad08370d4de5de738c8f6c27b8c7764c429/lib/discordrb/api.rb#L35
19
+ def raw_request(type, attributes)
20
+ RestClient.send(type, *attributes)
21
+ rescue RestClient::Forbidden
22
+ raise Lita.logger.debug("The bot doesn't have the required permission to do this!")
23
+ end
24
+
25
+ def request(type, *attributes)
26
+ # Add a custom user agent
27
+ attributes.last[:user_agent] = user_agent if attributes.last.is_a? Hash
28
+ response = raw_request(type, attributes)
29
+
30
+ while response.code == 429
31
+ wait_seconds = response[:retry_after].to_i / 1000.0
32
+ Lita.logger.debug("WARNING: Discord rate limiting will cause a delay of #{wait_seconds} seconds for the request: #{type} #{attributes}")
33
+ sleep wait_seconds / 1000.0
34
+ response = raw_request(type, attributes)
35
+ end
36
+ response
37
+ end
38
+
39
+ # Make an avatar URL from the user and avatar IDs
40
+ def avatar_url(user_id, avatar_id)
41
+ "#{APIBASE}/users/#{user_id}/avatars/#{avatar_id}.jpg"
42
+ end
43
+
44
+ # Login to the server
45
+ def login(email, password)
46
+ request( :post, "#{APIBASE}/auth/login", email: email, password: password )
47
+ end
48
+
49
+ # Logout from the server
50
+ def logout(token)
51
+ request( :post, "#{APIBASE}/auth/logout", nil, Authorization: token )
52
+ end
53
+
54
+ def validate_token(token)
55
+ request( :post, "#{APIBASE}/auth/login",{}.to_json, Authorization: token, content_type: :json )
56
+ end
57
+
58
+ def user(token, user_id)
59
+ request( :get, "#{APIBASE}/users/#{user_id}", Authorization: token )
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,87 @@
1
+ require 'rest-client'
2
+ require 'faye/websocket'
3
+ require 'eventmachine'
4
+ require 'lita/adapters/discord/api'
5
+ require 'lita/adapters/discord/token_cache'
6
+
7
+ module Lita
8
+ module Adapters
9
+ class Discord < Adapter
10
+ class Client
11
+ def initialize(email, password)
12
+ @email = email
13
+ @password = password
14
+ @token_cache = TokenCache.new
15
+ @channels = {}
16
+ @users = {}
17
+ @restricted_channels = []
18
+ @event_threads = []
19
+ @current_thread = 0
20
+ end
21
+
22
+ def connect
23
+ @token = login
24
+ end
25
+
26
+ def disconnect
27
+ API.logout
28
+ end
29
+
30
+
31
+ private
32
+
33
+ def login
34
+ if @email == :token
35
+ Lita.logger.debug('Logging in using static token')
36
+ # The password is the token!
37
+ return @password
38
+ end
39
+
40
+ Lita.logger.debug('Logging in')
41
+ login_attempts ||= 0
42
+
43
+ # First, attempt to get the token from the cache
44
+ token = @token_cache.token(@email, @password)
45
+
46
+ if token
47
+ Lita.logger.debug('Token successfully obtained from cache!')
48
+ return token
49
+ end
50
+
51
+ # Login
52
+ login_response = API.new.login(@email, @password)
53
+ # raise Discordrb::Errors::HTTPStatusError, login_response.code if login_response.code >= 400
54
+
55
+ # Parse response
56
+ login_response_object = JSON.parse(login_response)
57
+ # raise Discordrb::Errors::InvalidAuthenticationError unless login_response_object['token']
58
+
59
+ Lita.logger.debug('Received token from Discord!')
60
+
61
+ # Cache the token
62
+ @token_cache.store_token(@email, @password, login_response_object['token'])
63
+
64
+ login_response_object['token']
65
+ # rescue Exception => e
66
+ # response_code = login_response.nil? ? 0 : login_response.code ######## mackmm145
67
+ # if login_attempts < 100 && (e.inspect.include?('No such host is known.') || response_code == 523)
68
+ # Lita.logger.debug("Login failed! Reattempting in 5 seconds. #{100 - login_attempts} attempts remaining.")
69
+ # Lita.logger.debug("Error was: #{e.inspect}")
70
+ # sleep 5
71
+ # login_attempts += 1
72
+ # retry
73
+ # else
74
+ # Lita.logger.debug("Login failed permanently after #{login_attempts + 1} attempts")
75
+ # # Apparently we get a 400 if the password or username is incorrect. In that case, tell the user
76
+ # Lita.logger.debug("Are you sure you're using the correct username and password?") if e.class == RestClient::BadRequest
77
+ # # raise $ERROR_INFO
78
+ # end
79
+ end
80
+
81
+ def logout
82
+
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,147 @@
1
+ require 'base64'
2
+ require 'json'
3
+ require 'openssl'
4
+ require 'lita/adapters/discord/api'
5
+
6
+ module Lita
7
+ module Adapters
8
+ class Discord < Adapter
9
+ KEYLEN = 32
10
+ CACHE_PATH = Dir.pwd + '/.discord_token_cache.json'
11
+ class CachedToken
12
+ def initialize(data = nil)
13
+ if data
14
+ @verify_salt = Base64.decode64(data['verify_salt'])
15
+ @password_hash = Base64.decode64(data['password_hash'])
16
+ @encrypt_salt = Base64.decode64(data['encrypt_salt'])
17
+ @iv = Base64.decode64(data['iv'])
18
+ @encrypted_token = Base64.decode64(data['encrypted_token'])
19
+ else
20
+ generate_salts
21
+ end
22
+ end
23
+
24
+ # @return [Hash<Symbol => String>] the data representing the token and encryption data, all encrypted and base64-encoded
25
+ def data
26
+ {
27
+ verify_salt: Base64.encode64(@verify_salt),
28
+ password_hash: Base64.encode64(@password_hash),
29
+ encrypt_salt: Base64.encode64(@encrypt_salt),
30
+ iv: Base64.encode64(@iv),
31
+ encrypted_token: Base64.encode64(@encrypted_token)
32
+ }
33
+ end
34
+
35
+ def verify_password(password)
36
+ hash_password(password) == @password_hash
37
+ end
38
+
39
+ def generate_verify_hash(password)
40
+ @password_hash = hash_password(password)
41
+ end
42
+
43
+ def obtain_key(password)
44
+ @key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(password, @encrypt_salt, 300_000, KEYLEN)
45
+ end
46
+
47
+ def generate_salts
48
+ @verify_salt = OpenSSL::Random.random_bytes(KEYLEN)
49
+ @encrypt_salt = OpenSSL::Random.random_bytes(KEYLEN)
50
+ end
51
+
52
+ def decrypt_token(password)
53
+ key = obtain_key(password)
54
+ decipher = OpenSSL::Cipher::AES256.new(:CBC)
55
+ decipher.decrypt
56
+ decipher.key = key
57
+ decipher.iv = @iv
58
+ decipher.update(@encrypted_token) + decipher.final
59
+ end
60
+
61
+ def encrypt_token(password, token)
62
+ key = obtain_key(password)
63
+ cipher = OpenSSL::Cipher::AES256.new(:CBC)
64
+ cipher.encrypt
65
+ cipher.key = key
66
+ @iv = cipher.random_iv
67
+ @encrypted_token = cipher.update(token) + cipher.final
68
+ end
69
+
70
+ def test_token(token)
71
+ API.validate_token(token)
72
+ end
73
+
74
+ def hash_password(password)
75
+ OpenSSL::PKCS5.pbkdf2_hmac_sha1(password, @verify_salt, 300_000, KEYLEN)
76
+ end
77
+ end
78
+
79
+ class TokenCache
80
+
81
+ def initialize
82
+ if File.file? CACHE_PATH
83
+ @data = JSON.parse(File.read(CACHE_PATH))
84
+ else
85
+ Lita.logger.debug("Cache file #{CACHE_PATH} not found. Using empty cache")
86
+ @data = {}
87
+ end
88
+ rescue => e
89
+ Lita.logger.debug('Exception occurred while parsing token cache file:')
90
+ # I guess start here if stuff is super broken
91
+ Lita.logger.debug('Continuing with empty cache')
92
+ @data = {}
93
+ end
94
+
95
+ def token(email, password)
96
+ if @data[email]
97
+ begin
98
+ cached = CachedToken.new(@data[email])
99
+ if cached.verify_password(password)
100
+ token = cached.decrypt_token(password)
101
+ if token
102
+ begin
103
+ cached.test_token
104
+ token
105
+ rescue => e
106
+ fail_token('Token cached, verified and decrypted, but rejected by Discord', email)
107
+ sleep 1
108
+ nil
109
+ end
110
+ else
111
+ fail_token('Token cached and verified, but decryption failed', email)
112
+ end
113
+ else
114
+ fail_token('Token verification failed', email)
115
+ end
116
+ rescue => e
117
+ fail_token('Token cached but invalid', email)
118
+ end
119
+ else
120
+ fail_token('Token not cached at all')
121
+ end
122
+ end
123
+
124
+ def store_token(email, password, token)
125
+ cached = CachedToken.new
126
+ cached.generate_verify_hash(password)
127
+ cached.encrypt_token(password, token)
128
+ @data[email] = cached.data
129
+ write_cache
130
+ end
131
+
132
+ def write_cache
133
+ File.write(CACHE_PATH, @data.to_json)
134
+ end
135
+
136
+ private
137
+
138
+ def fail_token(msg, email = nil)
139
+ Lita.logger.warn("Token not retrieved from cache - #{msg}")
140
+ @data.delete(email) if email
141
+ nil
142
+ end
143
+
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,39 @@
1
+ require 'lita'
2
+ require "lita/adapters/discord/client"
3
+
4
+ module Lita
5
+ module Adapters
6
+ class Discord < Adapter
7
+ config :email, type: String, required: true
8
+ config :password, type: String, required: true
9
+
10
+ def initialize(robot)
11
+ super
12
+ ## TIME TO REBUILD THIS SHIZZZZZZ
13
+ @client = Client.new(config.email, config.password)
14
+ end
15
+
16
+ def run
17
+ @client.connect
18
+ end
19
+
20
+ def shut_down
21
+ @client.disconnect
22
+ end
23
+
24
+ # def mention_format(user_id)
25
+ # "@<#{user_id}>"
26
+ # end
27
+
28
+ # Referenced https://github.com/litaio/lita-slack/blob/master/lib/lita/adapters/slack.rb#L46
29
+ # Trying to build out basic commands for max plugin compatibility
30
+ # Update when api is built out
31
+ # def set_topic(target, topic)
32
+
33
+
34
+ # end
35
+
36
+ Lita.register_adapter(:discord, self)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,7 @@
1
+ require "lita"
2
+
3
+ Lita.load_locales Dir[File.expand_path(
4
+ File.join("..", "..", "locales", "*.yml"), __FILE__
5
+ )]
6
+
7
+ require "lita/adapters/discord"
@@ -0,0 +1,29 @@
1
+ Gem::Specification.new do |spec|
2
+ spec.name = "lita-discord"
3
+ spec.version = "0.1.0"
4
+ spec.authors = ["Kyle Boe"]
5
+ spec.email = ["ksboe1@gmail.com"]
6
+ spec.description = "A Discord adapter for Lita"
7
+ spec.summary = "lita-discord is an adapter for Lita that allows you to use the robot to manage a Discord server."
8
+ spec.homepage = "https://github.com/kyleboe/lita-discord"
9
+ spec.license = "MIT"
10
+ spec.metadata = { "lita_plugin_type" => "adapter" }
11
+
12
+ spec.files = `git ls-files`.split($/)
13
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
14
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
15
+ spec.require_paths = ["lib"]
16
+
17
+ spec.add_runtime_dependency "lita", ">= 4.7"
18
+
19
+ spec.add_dependency 'rest-client'
20
+ spec.add_dependency 'faye-websocket'
21
+ spec.add_dependency 'websocket-client-simple'
22
+ spec.add_dependency 'eventmachine'
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.3"
25
+ spec.add_development_dependency "pry-byebug"
26
+ spec.add_development_dependency "rake"
27
+ spec.add_development_dependency "rack-test"
28
+ spec.add_development_dependency "rspec", ">= 3.0.0"
29
+ end
data/locales/en.yml ADDED
@@ -0,0 +1,4 @@
1
+ en:
2
+ lita:
3
+ adapters:
4
+ discord:
@@ -0,0 +1,4 @@
1
+ require "spec_helper"
2
+
3
+ describe Lita::Adapters::Discord, lita: true do
4
+ end
@@ -0,0 +1,6 @@
1
+ require "lita-discord"
2
+ require "lita/rspec"
3
+
4
+ # A compatibility mode is provided for older plugins upgrading from Lita 3. Since this plugin
5
+ # was generated with Lita 4, the compatibility mode should be left disabled.
6
+ Lita.version_3_compatibility_mode = false
metadata ADDED
@@ -0,0 +1,202 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lita-discord
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Kyle Boe
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-04-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: lita
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '4.7'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '4.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rest-client
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: faye-websocket
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: websocket-client-simple
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: eventmachine
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: bundler
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.3'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.3'
97
+ - !ruby/object:Gem::Dependency
98
+ name: pry-byebug
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rake
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rack-test
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: rspec
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: 3.0.0
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: 3.0.0
153
+ description: A Discord adapter for Lita
154
+ email:
155
+ - ksboe1@gmail.com
156
+ executables: []
157
+ extensions: []
158
+ extra_rdoc_files: []
159
+ files:
160
+ - ".gitignore"
161
+ - ".travis.yml"
162
+ - Gemfile
163
+ - README.md
164
+ - Rakefile
165
+ - lib/lita-discord.rb
166
+ - lib/lita/adapters/discord.rb
167
+ - lib/lita/adapters/discord/api.rb
168
+ - lib/lita/adapters/discord/client.rb
169
+ - lib/lita/adapters/discord/token_cache.rb
170
+ - lita-discord.gemspec
171
+ - locales/en.yml
172
+ - spec/lita/adapters/discord_spec.rb
173
+ - spec/spec_helper.rb
174
+ homepage: https://github.com/kyleboe/lita-discord
175
+ licenses:
176
+ - MIT
177
+ metadata:
178
+ lita_plugin_type: adapter
179
+ post_install_message:
180
+ rdoc_options: []
181
+ require_paths:
182
+ - lib
183
+ required_ruby_version: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - ">="
186
+ - !ruby/object:Gem::Version
187
+ version: '0'
188
+ required_rubygems_version: !ruby/object:Gem::Requirement
189
+ requirements:
190
+ - - ">="
191
+ - !ruby/object:Gem::Version
192
+ version: '0'
193
+ requirements: []
194
+ rubyforge_project:
195
+ rubygems_version: 2.5.1
196
+ signing_key:
197
+ specification_version: 4
198
+ summary: lita-discord is an adapter for Lita that allows you to use the robot to manage
199
+ a Discord server.
200
+ test_files:
201
+ - spec/lita/adapters/discord_spec.rb
202
+ - spec/spec_helper.rb