esi 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []