esi 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b5d0ba8745382e4dc76d80883f103938d1db197f
4
+ data.tar.gz: 86b2d0155c58f106b2115ae636cd9dcf152b667d
5
+ SHA512:
6
+ metadata.gz: 495ca804fd67f4e55a327a9e6990613ab9cb36682d921c8913413c2e2ff8b4b161bac61e4536441817a75cb94f6740dba3b2059f7cde0fac8b970f83dd554d48
7
+ data.tar.gz: 3117e5bf411a9808f9fcbc621df104e79d2c9527e567975723c7500b0ec3d96a2b3717ceaa33e8b2dc7c0b854d51b92f02d5034723009918ebcf2d931aaa2e54
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Danny Hiemstra
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.
@@ -0,0 +1,59 @@
1
+ # Esi
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/esi`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'esi'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install esi
22
+
23
+ ## Usage
24
+
25
+ esi = Esi::Client.new(token: 'YOUR_TOKEN', refresh_token: 'REFRESH_TOKEN', token_expires_at: EXPIRE_TIMESTAMP)
26
+ esi.character()
27
+
28
+ ### You can optionally specify a callback that will be executed after a new token has been received using the refresh token
29
+
30
+ esi.refresh_callback = ~> (token, expires_at) {
31
+ # save new token & expires at
32
+ }
33
+
34
+ ## Authentication
35
+
36
+ Register your application on [https://developers.eveonline.com/applications].
37
+ Add your client id and secret in an initializer or so.
38
+
39
+ Esi.config.client_id = 'APP_CLIENT_ID'
40
+ Esi.config.client_secret = 'APP_CLIENT_SECRET'
41
+
42
+ ## Configuration
43
+
44
+ Create a file `config/initializers/esi.rb` with the following options:
45
+
46
+ # Specify a custom log level
47
+ Esi.config.log_level = :debug
48
+
49
+ # Specify a custom log path
50
+ Esi.config.log_target = Rails.root.join('log', 'esi.log')
51
+
52
+ # Specify a custom logger
53
+ Esi.config.logger = Rails.logger
54
+
55
+ # Set esi api version to dev
56
+ Esi.config.api_version = :dev
57
+
58
+ # Save all responses in this folder
59
+ Esi.config.response_log_path = Rails.root.join('tmp', 'esi')
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'esi/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "esi"
8
+ spec.version = Esi::VERSION
9
+ spec.authors = ["Danny Hiemstra"]
10
+ spec.email = ["dannyhiemstra@gmail.com"]
11
+
12
+ spec.summary = "EVE ESI API wrapper"
13
+ spec.description = "EVE ESI API wrapper"
14
+ spec.homepage = "https://github.com/dhiemstra/esi"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = %w(LICENSE.txt README.md esi.gemspec) + Dir['lib/**/*.rb']
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_dependency "oauth2", "~> 1.2"
21
+ spec.add_dependency "addressable", "~> 2.3"
22
+ spec.add_dependency "recursive-open-struct", "~> 1"
23
+ spec.add_development_dependency "bundler", "~> 1.14"
24
+ spec.add_development_dependency "rake", "~> 10.0"
25
+ spec.add_development_dependency "minitest", "~> 5.0"
26
+ end
@@ -0,0 +1,120 @@
1
+ require "oauth2"
2
+ require "forwardable"
3
+ require "ostruct"
4
+ require "addressable/uri"
5
+
6
+ module Esi
7
+ autoload :Version, 'esi/version'
8
+ autoload :AccessToken, 'esi/access_token'
9
+ autoload :OAuth, 'esi/o_auth'
10
+ autoload :Calls, 'esi/calls'
11
+ autoload :Client, 'esi/client'
12
+ autoload :Response, 'esi/response'
13
+
14
+ SCOPES = %w(
15
+ esi-calendar.respond_calendar_events.v1
16
+ esi-calendar.read_calendar_events.v1
17
+ esi-location.read_location.v1
18
+ esi-location.read_ship_type.v1
19
+ esi-mail.organize_mail.v1
20
+ esi-mail.read_mail.v1
21
+ esi-mail.send_mail.v1
22
+ esi-skills.read_skills.v1
23
+ esi-skills.read_skillqueue.v1
24
+ esi-wallet.read_character_wallet.v1
25
+ esi-search.search_structures.v1
26
+ esi-clones.read_clones.v1
27
+ esi-characters.read_contacts.v1
28
+ esi-universe.read_structures.v1
29
+ esi-bookmarks.read_character_bookmarks.v1
30
+ esi-killmails.read_killmails.v1
31
+ esi-corporations.read_corporation_membership.v1
32
+ esi-assets.read_assets.v1
33
+ esi-planets.manage_planets.v1
34
+ esi-fleets.read_fleet.v1
35
+ esi-fleets.write_fleet.v1
36
+ esi-ui.open_window.v1
37
+ esi-ui.write_waypoint.v1
38
+ esi-characters.write_contacts.v1
39
+ esi-fittings.read_fittings.v1
40
+ esi-fittings.write_fittings.v1
41
+ esi-markets.structure_markets.v1
42
+ esi-corporations.read_structures.v1
43
+ esi-corporations.write_structures.v1
44
+ esi-characters.read_loyalty.v1
45
+ esi-characters.read_opportunities.v1
46
+ esi-characters.read_chat_channels.v1
47
+ esi-characters.read_medals.v1
48
+ esi-characters.read_standings.v1
49
+ esi-characters.read_agents_research.v1
50
+ esi-industry.read_character_jobs.v1
51
+ esi-markets.read_character_orders.v1
52
+ esi-characters.read_blueprints.v1
53
+ esi-characters.read_corporation_roles.v1
54
+ esi-location.read_online.v1
55
+ esi-contracts.read_character_contracts.v1
56
+ )
57
+ DEFAULT_CONFIG = {
58
+ datasource: :tranquility,
59
+ oauth_host: 'https://login.eveonline.com',
60
+ api_host: 'https://esi.tech.ccp.is',
61
+ api_version: :latest,
62
+ log_level: :info,
63
+ log_target: STDOUT,
64
+ response_log_path: nil,
65
+ client_id: nil,
66
+ client_secret: nil,
67
+ scopes: SCOPES
68
+ }
69
+
70
+ class << self
71
+ attr_writer :api_version, :logger
72
+
73
+ def config
74
+ @config ||= OpenStruct.new(DEFAULT_CONFIG)
75
+ end
76
+
77
+ def logger
78
+ @logger ||= Esi.config.logger || Logger.new(Esi.config.log_target).tap do |l|
79
+ l.level = Logger.const_get(Esi.config.log_level.upcase)
80
+ end
81
+ end
82
+
83
+ def api_version
84
+ @api_version || :latest
85
+ end
86
+
87
+ def generate_url(path, params={})
88
+ path = path[1..-1] if path.start_with?('/')
89
+ path += "/" unless path.end_with?('/')
90
+
91
+ url = [config.api_host, config.api_version, path].join('/')
92
+ uri = Addressable::URI.parse(url)
93
+ uri.query_values = {datasource: config.datasource}.merge(params.to_h)
94
+ uri.to_s
95
+ end
96
+
97
+ def client
98
+ @client ||= Client.new
99
+ end
100
+ end
101
+
102
+ class ApiError < OAuth2::Error
103
+ attr_reader :key, :message, :type
104
+
105
+ def initialize(response)
106
+ super(response.original_response)
107
+
108
+ @code = response.original_response.status
109
+ @key = response.data[:key]
110
+ @message = response.data[:message].presence || response.data[:error]
111
+ @type = response.data[:exceptionType]
112
+ end
113
+ end
114
+
115
+ class ApiUnknownError < ApiError; end
116
+ class ApiInvalidAppClientKeysError < ApiError; end
117
+ class ApiNotFoundError < ApiError; end
118
+ class ApiForbiddenError < ApiError; end
119
+ class Error < StandardError; end
120
+ end
@@ -0,0 +1,17 @@
1
+ module Esi
2
+ class AccessToken < OAuth2::AccessToken
3
+ def initialize(*args)
4
+ if args[0].is_a?(OAuth2::AccessToken)
5
+ token = args[0]
6
+ options = { refresh_token: token.refresh_token, expires_at: token.expires_at }
7
+ super(token.client, token.token, options)
8
+ else
9
+ super(*args)
10
+ end
11
+ end
12
+
13
+ def verify
14
+ Esi::Response.new(get("/oauth/verify"))
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,339 @@
1
+ module Esi
2
+ class Calls
3
+ class Base
4
+ attr_accessor :path, :params
5
+
6
+ def method
7
+ @method ||= :get
8
+ end
9
+
10
+ def url
11
+ Esi.generate_url(path, params)
12
+ end
13
+
14
+ def page=(page)
15
+ self.params ||= {}
16
+ self.params[:page] = page
17
+ end
18
+
19
+ def paginated?
20
+ !!@paginated
21
+ end
22
+ end
23
+
24
+ class OpenMarketDetails < Base
25
+ def initialize(type_id)
26
+ @path = "/ui/openwindow/marketdetails"
27
+ @method = :post
28
+ @params = { type_id: type_id }
29
+ end
30
+ end
31
+
32
+ class SolarSystem < Base
33
+ def initialize(system_id)
34
+ @path = "/universe/systems/#{system_id}"
35
+ end
36
+ end
37
+
38
+ class Types < Base
39
+ def initialize
40
+ @path = "/universe/types"
41
+ @paginated = true
42
+ end
43
+ end
44
+
45
+ class Type < Base
46
+ def initialize(type_id)
47
+ @path = "/universe/types/#{type_id}"
48
+ end
49
+ end
50
+
51
+ class Search < Base
52
+ def initialize(character_id: nil, categories:, search:, strict: false)
53
+ @path = (character_id ? "/characters/#{character_id}" : '') + "/search"
54
+ @params = { categories: categories, search: search, strict: strict }
55
+ end
56
+ end
57
+
58
+ class Characters < Base
59
+ def initialize(character_ids)
60
+ @path = "/characters/names"
61
+ @params = { character_ids: character_ids.join(',') }
62
+ end
63
+ end
64
+
65
+ class CharacterNames < Base
66
+ def initialize(character_ids)
67
+ @path = "/characters/names"
68
+ @params = { character_ids: character_ids.join(',') }
69
+ end
70
+ end
71
+
72
+ # Link: https://esi.tech.ccp.is/latest/#!/Character/get_characters_character_id
73
+ # Cache: 1 hour
74
+ class Character < Base
75
+ def initialize(character_id)
76
+ @path = "/characters/#{character_id}"
77
+ end
78
+ end
79
+
80
+ # Link: https://esi.tech.ccp.is/dev/#!/Wallet/get_characters_character_id_wallet
81
+ # Scope: esi-wallet.read_character_wallet.v1
82
+ # Cache: 2 minutes
83
+ class CharacterWallet < Base
84
+ def initialize(character_id)
85
+ @path = "/characters/#{character_id}/wallet"
86
+ end
87
+ end
88
+
89
+ # Link: https://esi.tech.ccp.is/dev/#!/Wallet/get_characters_character_id_wallet_journal
90
+ # Scope: esi-wallet.read_character_wallet.v1
91
+ # Cache: 1 hour
92
+ class CharacterWalletJournal < Base
93
+ def initialize(character_id)
94
+ @path = "/characters/#{character_id}/wallet/journal"
95
+ end
96
+ end
97
+
98
+ # Link: https://esi.tech.ccp.is/dev/#!/Wallet/get_characters_character_id_wallet_transactions
99
+ # Scope: esi-wallet.read_character_wallet.v1
100
+ # Cache: 1 hour
101
+ class CharacterWalletTransactions < Base
102
+ def initialize(character_id)
103
+ @path = "/characters/#{character_id}/wallet/transactions"
104
+ end
105
+ end
106
+
107
+ # Link: https://esi.tech.ccp.is/latest/#!/Market/get_characters_character_id_orders
108
+ # Scope: esi-markets.read_character_orders.v1
109
+ # Cache: 1 hour
110
+ class CharacterOrders < Base
111
+ def initialize(character_id)
112
+ @path = "/characters/#{character_id}/orders"
113
+ end
114
+ end
115
+
116
+ # Link: https://esi.tech.ccp.is/latest/#!/Industry/get_characters_character_id_industry_jobs
117
+ # Scope: esi-industry.read_character_jobs.v1
118
+ # Cache: 5 minutes
119
+ class CharacterIndustryJobs < Base
120
+ def initialize(character_id, with_completed: false)
121
+ @path = "/characters/#{character_id}/industry/jobs"
122
+ @params = { with_completed: with_completed }
123
+ end
124
+ end
125
+
126
+ # Link: https://esi.tech.ccp.is/dev/?datasource=tranquility#!/Contacts/get_characters_character_id_contacts
127
+ # Scope: esi-contracts.read_character_contracts.v1
128
+ # Cache: 1 hour
129
+ class CharacterContracts < Base
130
+ def initialize(character_id)
131
+ @path = "/characters/#{character_id}/contracts"
132
+ end
133
+ end
134
+
135
+ # Link: https://esi.tech.ccp.is/dev/#!/Location/get_characters_character_id_location
136
+ # Scope: esi-location.read_location.v1
137
+ # Cache: 1 hour
138
+ class CharacterLocation < Base
139
+ def initialize(character_id)
140
+ @path = "/characters/#{character_id}/location"
141
+ end
142
+ end
143
+
144
+ # Link: https://esi.tech.ccp.is/dev/#!/Contracts/get_characters_character_id_contracts_contract_id_items
145
+ # Scope: esi-contracts.read_character_contracts.v1
146
+ # Cache: 1 hour
147
+ class ContractItems < Base
148
+ def initialize(character_id, contract_id)
149
+ @path = "/characters/#{character_id}/contracts/#{contract_id}/items"
150
+ end
151
+ end
152
+
153
+ # Link: https://esi.tech.ccp.is/dev/#!/Location/get_characters_character_id_location
154
+ # Scope: esi-location.read_location.v1
155
+ # Cache: 5 seconds
156
+ class CharacterLocation < Base
157
+ def initialize(character_id)
158
+ @path = "/characters/#{character_id}/location"
159
+ end
160
+ end
161
+
162
+ # Link: https://esi.tech.ccp.is/dev/#!/Assets/get_characters_character_id_assets
163
+ # Scope: esi-assets.read_assets.v1
164
+ # Cache: 1 hour
165
+ class Assets < Base
166
+ def initialize(character_id)
167
+ @path = "/characters/#{character_id}/assets"
168
+ end
169
+ end
170
+
171
+ class Alliances < Base
172
+ def initialize
173
+ @path = "/alliances"
174
+ end
175
+ end
176
+
177
+ class AllianceNames < Base
178
+ def initialize(alliance_ids)
179
+ @path = "/alliances/names"
180
+ @params = { alliance_ids: alliance_ids.join(',') }
181
+ end
182
+ end
183
+
184
+ class Alliance < Base
185
+ def initialize(alliance_id)
186
+ @path = "/alliances/#{alliance_id}"
187
+ end
188
+ end
189
+
190
+ class CorporationNames < Base
191
+ def initialize(corporation_ids)
192
+ @path = "/corporations/names"
193
+ @params = { corporation_ids: corporation_ids.join(',') }
194
+ end
195
+ end
196
+
197
+ class Corporation < Base
198
+ def initialize(corporation_id)
199
+ @path = "/corporations/#{corporation_id}"
200
+ end
201
+ end
202
+
203
+ class CorporationStructures < Base
204
+ def initialize(corporation_id)
205
+ @path = "/corporations/#{corporation_id}/structures"
206
+ end
207
+ end
208
+
209
+ class CorporationStructure < Base
210
+ def initialize(corporation_id, structure_id)
211
+ @path = "/corporations/#{corporation_id}/structures/#{structure_id}"
212
+ end
213
+ end
214
+
215
+ # Link: https://esi.tech.ccp.is/dev/#!/Corporation/get_corporations_corporation_id_members
216
+ # Scope: esi-corporations.read_corporation_membership.v1
217
+ # Cache: 1 hour
218
+ class CorporationMembers < Base
219
+ def initialize(corporation_id)
220
+ @path = "/corporations/#{corporation_id}/members"
221
+ end
222
+ end
223
+
224
+ # Link: https://esi.tech.ccp.is/dev/#!/Corporation/get_corporations_corporation_id_roles
225
+ # Scope: esi-corporations.read_corporation_membership.v1
226
+ # Cache: 1 hour
227
+ class CorporationRoles < Base
228
+ def initialize(corporation_id)
229
+ @path = "/corporations/#{corporation_id}/roles"
230
+ end
231
+ end
232
+
233
+ class Structures < Base
234
+ def initialize
235
+ @path = "/universe/structures"
236
+ end
237
+ end
238
+
239
+ class Structure < Base
240
+ def initialize(structure_id)
241
+ @path = "/universe/structures/#{structure_id}"
242
+ end
243
+ end
244
+
245
+ class Route < Base
246
+ def initialize(origin_id, destination_id)
247
+ @path = "/route/#{origin_id}/#{destination_id}"
248
+ end
249
+ end
250
+
251
+ class MarketGroups < Base
252
+ def initialize
253
+ @path = "/markets/groups"
254
+ @paginated = true
255
+ end
256
+ end
257
+
258
+ class MarketGroup < Base
259
+ def initialize(id)
260
+ @path = "/markets/groups/#{id}"
261
+ end
262
+ end
263
+
264
+ class MarketPrices < Base
265
+ def initialize
266
+ @path = "/markets/prices"
267
+ end
268
+ end
269
+
270
+ class MarketOrders < Base
271
+ def initialize(type_id: nil, structure_id: nil, region_id: Region::FORGE)
272
+ @path = "/markets/structures/#{structure_id}"
273
+ @path = "/markets/#{region_id}/orders"
274
+ @params = { order_type: :all }
275
+ @params[:type_id] = type_id if type_id.present?
276
+ @paginated = type_id.blank?
277
+ end
278
+ end
279
+
280
+ class StructureOrders < Base
281
+ def initialize(structure_id:)
282
+ @path = "/markets/structures/#{structure_id}"
283
+ @paginated = true
284
+ end
285
+ end
286
+
287
+ class MarketHistory < Base
288
+ def initialize(type_id:, region_id: Region::FORGE)
289
+ @path = "/markets/#{region_id}/history"
290
+ @params = { type_id: type_id }
291
+ end
292
+ end
293
+
294
+ class Killmails < Base
295
+ def initialize(character_id:, max_count: 50, max_kill_id: nil)
296
+ @path = "/characters/#{character_id}/killmails/recent"
297
+ @params = { max_count: max_count }
298
+ @params[:max_kill_id] = max_kill_id if max_kill_id
299
+ end
300
+ end
301
+
302
+ class Killmail < Base
303
+ def initialize(id:, hash:)
304
+ @path = "killmails/#{id}/#{hash}"
305
+ end
306
+ end
307
+
308
+ class Fleet < Base
309
+ def initialize(fleet_id)
310
+ @path = "fleets/#{fleet_id}"
311
+ end
312
+ end
313
+
314
+ class FleetMembers < Base
315
+ def initialize(fleet_id)
316
+ @path = "fleets/#{fleet_id}/members"
317
+ end
318
+ end
319
+
320
+ class FleetWings < Base
321
+ def initialize(fleet_id)
322
+ @path = "fleets/#{fleet_id}/wings"
323
+ end
324
+ end
325
+
326
+ class Fittings < Base
327
+ def initialize(character_id)
328
+ @path = "characters/#{character_id}/fittings"
329
+ end
330
+ end
331
+
332
+ class DeleteFitting < Base
333
+ def initialize(character_id, fitting_id)
334
+ @path = "characters/#{character_id}/fittings/#{fitting_id}"
335
+ @method = :delete
336
+ end
337
+ end
338
+ end
339
+ end
@@ -0,0 +1,121 @@
1
+ module Esi
2
+ class Client
3
+ MAX_ATTEMPTS = 5
4
+
5
+ attr_accessor :refresh_callback, :access_token, :refresh_token, :expires_at
6
+ attr_reader :logger, :oauth
7
+
8
+ def initialize(token: nil, refresh_token: nil, expires_at: nil)
9
+ @logger = Esi.logger
10
+ @token = token
11
+ @refresh_token = refresh_token
12
+ @expires_at = expires_at
13
+ end
14
+
15
+ def method_missing(name, *args, &block)
16
+ class_name = name.to_s.split('_').map(&:capitalize).join
17
+ begin
18
+ klass = Esi::Calls.const_get(class_name)
19
+ rescue NameError
20
+ super(name, *args, &block)
21
+ end
22
+
23
+ call = klass.new(*args)
24
+ call.paginated? ? request_paginated(call, &block) : request(call, &block)
25
+ end
26
+
27
+ def log(message)
28
+ logger.info message
29
+ end
30
+
31
+ def debug(message)
32
+ logger.debug message
33
+ end
34
+
35
+ private
36
+
37
+ def oauth
38
+ @oauth ||= OAuth.new(
39
+ access_token: @token,
40
+ refresh_token: @refresh_token,
41
+ expires_at: @expires_at,
42
+ callback: -> (token, expires_at) {
43
+ @token = token
44
+ @expires_at = expires_at
45
+ if refresh_callback.respond_to?(:call)
46
+ refresh_callback.call(token, expires_at)
47
+ end
48
+ }
49
+ )
50
+ end
51
+
52
+ def request_paginated(call, &block)
53
+ items = []
54
+ page = 1
55
+
56
+ loop do
57
+ call.page = page
58
+ response = request(call, &block)
59
+ break if response.data.blank?
60
+ items += response.data
61
+ page += 1
62
+ end
63
+
64
+ items
65
+ end
66
+
67
+ def request(call, url=nil, &block)
68
+ response = nil
69
+ url ||= call.url
70
+
71
+ debug "Starting request: #{url}"
72
+
73
+ 1.upto(MAX_ATTEMPTS) do |try|
74
+ begin
75
+ response = oauth.request(call.method, url)
76
+ rescue OAuth2::Error => e
77
+ case e.response.status
78
+ when 502 # Temporary server error
79
+ logger.error "TemporaryServerError, sleeping for 15 seconds"
80
+ sleep 15
81
+ next
82
+ when 503 # Rate Limit
83
+ logger.error "RateLimit error, sleeping for 15 seconds"
84
+ sleep 15
85
+ next
86
+ when 403 # Forbidden
87
+ logger.error "ApiForbiddenError: #{e.response.status}: #{e.response.body}"
88
+ logger.error url
89
+ raise Esi::ApiForbiddenError.new(Response.new(e.response))
90
+ when 404 # Not Found
91
+ raise Esi::ApiNotFoundError.new(Response.new(e.response))
92
+ else
93
+ response = Response.new(e.response)
94
+ logger.error "ApiUnknownError (#{response.status}): #{url}"
95
+
96
+ case response.error
97
+ when 'invalid_client' then
98
+ raise ApiInvalidAppClientKeysError.new(response)
99
+ else
100
+ raise Esi::ApiUnknownError.new(response)
101
+ end
102
+ end
103
+ end
104
+ break if response
105
+ end
106
+
107
+ debug "Request finished"
108
+ response = Response.new(response)
109
+
110
+ if Esi.config.response_log_path && Dir.exists?(Esi.config.response_log_path)
111
+ call_name = call.class.to_s.downcase.split('::').last
112
+ folder = Pathname.new(Esi.config.response_log_path).join(call_name)
113
+ FileUtils.mkdir_p(folder)
114
+ File.write(folder.join("#{Time.now.to_i.to_s}.json"), response.to_s)
115
+ end
116
+
117
+ response.data.each { |item| block.call(item) } if block
118
+ response
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,54 @@
1
+ module Esi
2
+ class OAuth
3
+ extend Forwardable
4
+
5
+ attr_reader :access_token, :refresh_token, :expires_at
6
+ def_delegators :token, :request, :get, :post, :delete, :patch, :put
7
+
8
+ class << self
9
+ def authorize_url(redirect_uri:, scopes: nil)
10
+ scopes ||= Esi.config.scopes
11
+ client.auth_code.authorize_url(scope: scopes.join(' '), redirect_uri: redirect_uri)
12
+ end
13
+
14
+ def obtain_token(code)
15
+ Esi::AccessToken.new(client.auth_code.get_token(code))
16
+ end
17
+
18
+ def client
19
+ @client ||= OAuth2::Client.new(
20
+ Esi.config.client_id, Esi.config.client_secret,
21
+ { site: Esi.config.oauth_host }
22
+ )
23
+ end
24
+ end
25
+
26
+ def initialize(access_token:, refresh_token:, expires_at:, callback: nil)
27
+ if callback && !callback.respond_to?(:call)
28
+ raise Esi::Error.new("Callback should be a callable Proc")
29
+ end
30
+
31
+ @access_token = access_token
32
+ @refresh_token = refresh_token
33
+ @expires_at = expires_at
34
+ @callback = callback if callback
35
+ end
36
+
37
+ private
38
+
39
+ def token
40
+ @token = Esi::AccessToken.new(OAuth.client, @access_token, {
41
+ refresh_token: @refresh_token, expires_at: @expires_at
42
+ })
43
+ refresh_access_token if @token.expired?
44
+ @token
45
+ end
46
+
47
+ def refresh_access_token
48
+ @token = @token.refresh!
49
+ @access_token = @token.token
50
+ @expires_at = @token.expires_at.integer? ? Time.at(@token.expires_at) : @token.expires_at
51
+ @callback.call(@access_token, @expires_at) if @callback
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,53 @@
1
+ require 'recursive-open-struct'
2
+
3
+ module Esi
4
+ class Response
5
+ extend Forwardable
6
+
7
+ attr_reader :original_response, :json
8
+ def_delegators :original_response, :status, :body, :headers
9
+
10
+ def initialize(response)
11
+ @original_response = response
12
+ @json = MultiJson.load(body, symbolize_keys: true) rescue {}
13
+ @json = normalize_keys(@json) if @json.is_a?(Hash)
14
+ end
15
+
16
+ def data
17
+ @data ||= begin
18
+ if @json.is_a?(Array)
19
+ @json[0].is_a?(Hash) ? @json.map { |e| RecursiveOpenStruct.new(e, recurse_over_arrays: true) } : @json
20
+ else
21
+ RecursiveOpenStruct.new(@json, recurse_over_arrays: true)
22
+ end
23
+ end
24
+ end
25
+
26
+ def to_s
27
+ MultiJson.dump(json, pretty: true)
28
+ end
29
+
30
+ def cached_until
31
+ original_response.headers[:expires] ? Time.parse(original_response.headers[:expires]) : nil
32
+ end
33
+
34
+ def method_missing(method, *args, &block)
35
+ begin
36
+ data.send(method, *args, &block)
37
+ rescue NameError
38
+ super(name, *args, &block)
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def normalize_keys(data)
45
+ data.dup.each_key { |k| data[underscore(k).to_sym] = data.delete(k) }
46
+ data
47
+ end
48
+
49
+ def underscore(str)
50
+ str.to_s.gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').gsub(/([a-z\d])([A-Z])/,'\1_\2').downcase
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,3 @@
1
+ module Esi
2
+ VERSION = "0.1.2"
3
+ end
@@ -0,0 +1 @@
1
+ require 'omniauth/strategies/esi'
@@ -0,0 +1,56 @@
1
+ require 'omniauth/strategies/oauth2'
2
+
3
+ module OmniAuth
4
+ module Strategies
5
+ class Esi < OmniAuth::Strategies::OAuth2
6
+ option :name, 'esi'
7
+ option :client_options, { site: ::Esi.config.oauth_host, verify_url: '/oauth/verify' }
8
+ option :authorize_options, [:scope]
9
+
10
+ uid { extra_info[:character_id] }
11
+
12
+ info do
13
+ {
14
+ character_id: extra_info[:character_id],
15
+ character_name: extra_info[:character_name],
16
+ character_owner_hash: extra_info[:character_owner_hash],
17
+ token_type: raw_info[:token_type]
18
+ }
19
+ end
20
+
21
+ credentials do
22
+ hash = {token: access_token.token}
23
+ hash.merge!(refresh_token: access_token.refresh_token) if access_token.refresh_token
24
+ hash.merge!(expires_at: access_token.expires_at) if access_token.expires?
25
+ hash.merge!(expires: access_token.expires?)
26
+ hash.merge!(scopes: extra_info[:scopes].split(' ')) if extra_info[:scopes]
27
+ hash
28
+ end
29
+
30
+ def raw_info
31
+ @raw_info ||= deep_symbolize(access_token.params)
32
+ end
33
+
34
+ def extra_info
35
+ @extra_info ||= deep_symbolize(access_token.get(options.client_options.verify_url).parsed.transform_keys!(&:underscore))
36
+ end
37
+
38
+ def authorize_params
39
+ params = super
40
+ params = params.merge(request.params) unless OmniAuth.config.test_mode
41
+ params[:scope] = params[:scope].join(' ') if params[:scope].is_a?(Array)
42
+ params[:redirect_uri] = options[:callback_url] unless options[:callback_url].nil?
43
+ params
44
+ end
45
+
46
+ def request_phase
47
+ redirect client.auth_code.authorize_url(authorize_params)
48
+ end
49
+
50
+ def build_access_token
51
+ verifier = request.params['code']
52
+ client.auth_code.get_token(verifier, token_params)
53
+ end
54
+ end
55
+ end
56
+ end
metadata ADDED
@@ -0,0 +1,140 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: esi
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.2
5
+ platform: ruby
6
+ authors:
7
+ - Danny Hiemstra
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-08-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: oauth2
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: addressable
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.3'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: recursive-open-struct
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.14'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.14'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '10.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '10.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: minitest
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '5.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '5.0'
97
+ description: EVE ESI API wrapper
98
+ email:
99
+ - dannyhiemstra@gmail.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - LICENSE.txt
105
+ - README.md
106
+ - esi.gemspec
107
+ - lib/esi.rb
108
+ - lib/esi/access_token.rb
109
+ - lib/esi/calls.rb
110
+ - lib/esi/client.rb
111
+ - lib/esi/o_auth.rb
112
+ - lib/esi/response.rb
113
+ - lib/esi/version.rb
114
+ - lib/omniauth/esi.rb
115
+ - lib/omniauth/strategies/esi.rb
116
+ homepage: https://github.com/dhiemstra/esi
117
+ licenses:
118
+ - MIT
119
+ metadata: {}
120
+ post_install_message:
121
+ rdoc_options: []
122
+ require_paths:
123
+ - lib
124
+ required_ruby_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ required_rubygems_version: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ requirements: []
135
+ rubyforge_project:
136
+ rubygems_version: 2.6.11
137
+ signing_key:
138
+ specification_version: 4
139
+ summary: EVE ESI API wrapper
140
+ test_files: []