restiny 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e2ff82944ba14279df69b4c5e8d151a4d0b18c5176273af3ed68142327473993
4
- data.tar.gz: a214d550713959f482943f2120d491a91227f46599ab5f5da46e55abed7174be
3
+ metadata.gz: 525f30b84be418f2709f43396452fe3fb5f1b7fc307f5096a6698da8a1a61d3b
4
+ data.tar.gz: 3e008a19f1766d4d0a89a840e7439acae17d714bd5ec22cfd637af8aa9790f46
5
5
  SHA512:
6
- metadata.gz: 269e932d167058936ac4ccc092493b72da71026dbf5947467367671e2a64421496f0f9d40b99962eb1f1dc416754734ed241dfaaee53440ea03bee610efdb2d2
7
- data.tar.gz: def65d2d7fc397ca3e833a6eec1b922d78639d7c800f763f4e15740a9bde08d8e2e36e5db020dd9654594575c11b1f1c078d3d57f0b9f1eb1bbaf2197e70729a
6
+ metadata.gz: 4a741c23f022a5a9faa659aa26a8de4b39233caceaf1b11713209b605012d0cf721b2c2f06c4bbb865fe5f28f166d0b88a1446a06052aad88b35810ebc293a33
7
+ data.tar.gz: 530fd77d6b734bf749e9ce55b950dae7da4acc8f4719c95d57bf8f3dbb6403f0b804b7960b09cc434814b6f77e1c649ca651b86e649dd3b69081cea56d3e0359
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Restiny
4
+ class Error < StandardError; end
5
+ class RequestError < Error; end
6
+ class InvalidParamsError < RequestError; end
7
+ class RateLimitedError < RequestError
8
+ end
9
+
10
+ class ResponseError < Error; end
11
+ class NetworkError < Error
12
+ attr_accessor :status_code
13
+
14
+ def initialize(message, status_code = nil)
15
+ @status_code = status_code
16
+ super(message)
17
+ end
18
+ end
19
+ end
@@ -23,7 +23,7 @@ module Restiny
23
23
  'DestinyDamageTypeDefinition': { item: 'damage_type', items: 'damage_types' },
24
24
  'DestinyDestinationDefinition': { item: 'destination', items: 'destinations' },
25
25
  'DestinyEnergyTypeDefinition': { item: 'energy_type', items: 'energy_types' },
26
- 'DestinyEquipmentSlotDefinition': { item: 'eqiupment_slot', items: 'equipment_slots' },
26
+ 'DestinyEquipmentSlotDefinition': { item: 'equipment_slot', items: 'equipment_slots' },
27
27
  'DestinyEventCardDefinition': { item: 'event_card', items: 'event_cards' },
28
28
  'DestinyFactionDefinition': { item: 'faction', items: 'factions' },
29
29
  'DestinyGenderDefinition': { item: 'gender', items: 'genders' },
@@ -85,14 +85,16 @@ module Restiny
85
85
  Zip::File.open(zipped_file) { |file| file.first.extract(manifest_path) }
86
86
 
87
87
  self.new(manifest_path)
88
- rescue Down::Error
89
- raise "Unable to download the manifest from Bungie"
90
- rescue Zip::Error
91
- raise "Unable to unzip the manifest file"
88
+ rescue Down::ResponseError => error
89
+ raise Restiny::NetworkError.new("Unable to download the manifest file", error.response.code)
90
+ rescue Zip::Error => error
91
+ raise Restiny::Error.new("Unable to unzip the manifest file (#{e})")
92
92
  end
93
93
 
94
94
  def initialize(file_path)
95
- raise "You must provide the file path for the manifest file" if file_path.empty?
95
+ if file_path.empty? || !File.exist?(file_path) || !File.file?(file_path)
96
+ raise Restiny::InvalidParamsError.new("You must provide a valid path for the manifest file")
97
+ end
96
98
 
97
99
  @database = SQLite3::Database.new(file_path, results_as_hash: true)
98
100
  @file_path = file_path
@@ -106,6 +108,8 @@ module Restiny
106
108
  key = key.gsub(/(\B[A-Z])/, '_\1') if key =~ /\B[A-Z]/
107
109
  output[key.downcase] = if value.is_a?(Hash)
108
110
  clean_row_keys(value)
111
+ elsif value.is_a?(Array)
112
+ value.map { |item| item.is_a?(Hash) ? clean_row_keys(item) : value }
109
113
  else
110
114
  value
111
115
  end
@@ -133,7 +137,7 @@ module Restiny
133
137
  Manifest::clean_row_keys(JSON.parse(row['json'])) unless row['json'].nil?
134
138
  end
135
139
  rescue SQLite3::Exception => e
136
- raise "Error while querying the manifest (#{e})"
140
+ raise Restiny::Error.new("Error while querying the manifest (#{e})")
137
141
  end
138
142
  end
139
143
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Restiny
4
- VERSION = '0.1.0'
4
+ VERSION = '0.2.0'
5
5
  end
data/lib/restiny.rb CHANGED
@@ -4,8 +4,216 @@ $LOAD_PATH.unshift(__dir__)
4
4
 
5
5
  require 'restiny/version'
6
6
  require 'restiny/constants'
7
- require 'restiny/client'
7
+ require 'restiny/errors'
8
8
  require 'restiny/manifest'
9
9
  require 'restiny/user'
10
10
  require 'restiny/membership'
11
11
  require 'restiny/character'
12
+
13
+ require 'faraday'
14
+ require 'faraday/follow_redirects'
15
+ require 'securerandom'
16
+
17
+ module Restiny
18
+ extend self
19
+
20
+ BUNGIE_URL = "https://www.bungie.net"
21
+ API_BASE_URL = BUNGIE_URL + "/platform"
22
+
23
+ attr_accessor :api_key, :oauth_state, :oauth_client_id, :access_token, :refresh_token, :manifest
24
+
25
+ # OAuth methods
26
+
27
+ def authorise_url(redirect_url = nil, state = nil)
28
+ check_oauth_client_id
29
+
30
+ @oauth_state = state || SecureRandom.hex(15)
31
+
32
+ params = {
33
+ response_type: 'code',
34
+ client_id: @oauth_client_id,
35
+ state: @oauth_state
36
+ }
37
+
38
+ params[:redirect_url] = redirect_url unless redirect_url.nil?
39
+
40
+ connection.build_url(BUNGIE_URL + "/en/oauth/authorize", params).to_s
41
+ end
42
+
43
+ def request_access_token(code, redirect_url = nil)
44
+ check_oauth_client_id
45
+
46
+ params = {
47
+ code: code,
48
+ grant_type: 'authorization_code',
49
+ client_id: @oauth_client_id
50
+ }
51
+
52
+ params[:redirect_url] = redirect_url unless redirect_url.nil?
53
+
54
+ post('/platform/app/oauth/token/', params, "Content-Type" => "application/x-www-form-urlencoded")
55
+ end
56
+
57
+ def request_refresh_token
58
+ end
59
+
60
+ # Manifest methods
61
+
62
+ def download_manifest(locale = 'en')
63
+ response = get("/platform/Destiny2/Manifest/")
64
+
65
+ manifest_path = response.dig('Response', 'mobileWorldContentPaths', locale)
66
+ raise Restiny::ResponseError.new("Unable to determine manifest URL") if manifest_path.nil?
67
+
68
+ Manifest.download(BUNGIE_URL + manifest_path)
69
+ end
70
+
71
+ # Profile methods
72
+
73
+ def get_profile(membership_id, membership_type, components = [])
74
+ raise Restiny::InvalidParamsError.new("You must provide at least one component") if components.empty?
75
+
76
+ component_query = components.join(",")
77
+ response = get("/platform/Destiny2/#{membership_type}/Profile/#{membership_id}?components=#{component_query}")
78
+
79
+ {}.tap do |output|
80
+ components.each do |component|
81
+ case component.downcase
82
+ when 'characters'
83
+ output[:characters] = parse_profile_characters_response(response)
84
+ end
85
+ end
86
+ end
87
+ end
88
+
89
+ # Account methods
90
+
91
+ def get_user_by_membership_id(membership_id, membership_type = PLATFORM_ALL)
92
+ raise Restiny::InvalidParamsError.new("You must provide a valid membership ID") if membership_id.nil?
93
+
94
+ response = get("/platform/User/GetMembershipsById/#{membership_id}/#{membership_type}/")
95
+ results = response.dig('Response')
96
+
97
+ return nil if results.nil?
98
+
99
+ Restiny::User.new(
100
+ display_name: results['bungieNetUser']['cachedBungieGlobalDisplayName'],
101
+ display_name_code: results['bungieNetUser']['cachedBungieGlobalDisplayNameCode'],
102
+ memberships: results['destinyMemberships']
103
+ )
104
+ end
105
+
106
+ def get_user_by_bungie_name(full_display_name, membership_type = PLATFORM_ALL)
107
+ display_name, display_name_code = full_display_name.split('#')
108
+ raise Restiny::InvalidParamsError.new("You must provide a valid Bungie name") if display_name.nil? || display_name_code.nil?
109
+
110
+ params = {
111
+ displayName: display_name,
112
+ displayNameCode: display_name_code
113
+ }
114
+
115
+ response = post("/platform/Destiny2/SearchDestinyPlayerByBungieName/#{membership_type}/", params)
116
+ result = response.dig('Response')
117
+
118
+ return nil if result.nil?
119
+
120
+ Restiny::User.new(
121
+ display_name: result[0]['bungieGlobalDisplayName'],
122
+ display_name_code: result[0]['bungieGlobalDisplayNameCode'],
123
+ memberships: result
124
+ )
125
+ end
126
+
127
+ def search_users(name, page = 0)
128
+ response = post("/platform/User/Search/GlobalName/#{page}", displayNamePrefix: name)
129
+ search_results = response.dig('Response', 'searchResults')
130
+ return [] if search_results.nil?
131
+
132
+ search_results.map do |user|
133
+ Restiny::User.new(
134
+ display_name: user['bungieGlobalDisplayName'],
135
+ display_name_code: user['bungieGlobalDisplayNameCode'],
136
+ memberships: user['destinyMemberships']
137
+ )
138
+ end
139
+ end
140
+
141
+ private
142
+
143
+ def get(endpoint_url, params = {}, headers = {})
144
+ make_api_request(:get, endpoint_url, params, headers)
145
+ end
146
+
147
+ def post(endpoint_url, body, headers = {})
148
+ make_api_request(:post, endpoint_url, body, headers)
149
+ end
150
+
151
+ def make_api_request(type, url, params, headers = {})
152
+ raise Restiny::InvalidParamsError.new("You need to set an API key (Restiny.api_key = XXX)") unless @api_key
153
+
154
+ headers[:authorization] = "Bearer #{@oauth_token}" if @oauth_token
155
+
156
+ response = case type
157
+ when :get
158
+ connection.get(url, params, headers)
159
+ when :post
160
+ connection.post(url, params, headers)
161
+ end
162
+
163
+ response.body
164
+ rescue Faraday::Error => error
165
+ message = if error.response_body && error.response_headers['content-type'] =~ /application\/json;/i
166
+ JSON.parse(error.response_body)['Message']
167
+ else
168
+ error.message
169
+ end
170
+
171
+ case error
172
+ when Faraday::ClientError, Faraday::ServerError, Faraday::ConnectionFailed
173
+ raise Restiny::NetworkError.new(message, error.response_status)
174
+ else
175
+ end
176
+ end
177
+
178
+ def parse_profile_characters_response(response)
179
+ characters = response.dig('Response', 'characters', 'data')
180
+ return [] if characters.nil?
181
+
182
+ result = []
183
+
184
+ [].tap do |result|
185
+ characters.each_value do |character|
186
+ result << Restiny::Character.new(
187
+ id: character['characterId'],
188
+ session_playtime: character['minutesPlayedThisSession'],
189
+ total_playtime: character['minutesPlayedTotal'],
190
+ light_level: character['light'],
191
+ stats: [],
192
+ emblem: [],
193
+ progression: character['progression']
194
+ )
195
+ end
196
+ end
197
+ end
198
+
199
+ def check_oauth_client_id
200
+ raise Restiny::RequestError.new("You need to set an OAuth client ID (Restiny.oauth_client_id = XXX)") unless @oauth_client_id
201
+ end
202
+
203
+ def default_headers
204
+ {
205
+ 'User-Agent': "restiny v#{Restiny::VERSION}",
206
+ 'X-API-KEY': @api_key
207
+ }
208
+ end
209
+
210
+ def connection
211
+ @connection ||= Faraday.new(url: API_BASE_URL, headers: default_headers) do |faraday|
212
+ faraday.request :json
213
+ faraday.request :url_encoded
214
+ faraday.response :json
215
+ faraday.response :follow_redirects
216
+ faraday.response :raise_error
217
+ end
218
+ end
219
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: restiny
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Bogan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-02-19 00:00:00.000000000 Z
11
+ date: 2023-05-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -108,6 +108,20 @@ dependencies:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
110
  version: '3.10'
111
+ - !ruby/object:Gem::Dependency
112
+ name: vcr
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'
111
125
  description: A gem for interacting with Bungie's Destiny API.
112
126
  email:
113
127
  - d+restiny@waferbaby.com
@@ -117,8 +131,8 @@ extra_rdoc_files: []
117
131
  files:
118
132
  - lib/restiny.rb
119
133
  - lib/restiny/character.rb
120
- - lib/restiny/client.rb
121
134
  - lib/restiny/constants.rb
135
+ - lib/restiny/errors.rb
122
136
  - lib/restiny/manifest.rb
123
137
  - lib/restiny/membership.rb
124
138
  - lib/restiny/user.rb
@@ -1,134 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'faraday'
4
- require 'faraday/follow_redirects'
5
-
6
- module Restiny
7
- class Client
8
- BUNGIE_URL = "https://www.bungie.net"
9
- API_BASE_URL = BUNGIE_URL + "/Platform"
10
-
11
- attr_accessor :api_key, :manifest
12
-
13
- def initialize(api_key)
14
- @api_key = api_key
15
- end
16
-
17
- # Manifest methods
18
-
19
- def download_manifest(locale = 'en')
20
- response = get("/Destiny2/Manifest/")
21
-
22
- manifest_path = response.body.dig('Response', 'mobileWorldContentPaths', locale)
23
- raise "Unable to determine manifest URL" if manifest_path.nil?
24
-
25
- Manifest.download(BUNGIE_URL + manifest_path)
26
- end
27
-
28
- # Profile methods
29
-
30
- def get_profile(membership_id, membership_type, components = [])
31
- raise "You must provide at least one component" if components.empty?
32
-
33
- component_query = components.join(",")
34
- response = get("/Destiny2/#{membership_type}/Profile/#{membership_id}?components=#{component_query}")
35
-
36
- {}.tap do |output|
37
- components.each do |component|
38
- case component.downcase
39
- when 'characters'
40
- output[:characters] = parse_profile_characters_response(response)
41
- end
42
- end
43
- end
44
- end
45
-
46
- # Account methods
47
-
48
- def get_user_by_bungie_name(full_display_name, membership_type = PLATFORM_ALL)
49
- display_name, display_name_code = full_display_name.split('#')
50
- raise "You must provide a valid Bungie name" if display_name.nil? || display_name_code.nil?
51
-
52
- params = {
53
- displayName: display_name,
54
- displayNameCode: display_name_code
55
- }
56
-
57
- response = post("/Destiny2/SearchDestinyPlayerByBungieName/#{membership_type}/", params)
58
- result = response.body.dig('Response')
59
-
60
- return [] if result.nil?
61
-
62
- Restiny::User.new(
63
- display_name: result[0]['bungieGlobalDisplayName'],
64
- display_name_code: result[0]['bungieGlobalDisplayNameCode'],
65
- memberships: result
66
- )
67
- end
68
-
69
- def search_users(name, page = 0)
70
- params = { displayNamePrefix: name }
71
- response = post("/User/Search/GlobalName/#{page}", params).body
72
- return [] if response.nil?
73
-
74
- search_results = response.dig('Response', 'searchResults')
75
- return [] if search_results.nil?
76
-
77
- search_results.map do |user|
78
- Restiny::User.new(
79
- display_name: user['bungieGlobalDisplayName'],
80
- display_name_code: user['bungieGlobalDisplayNameCode'],
81
- memberships: user['destinyMemberships']
82
- )
83
- end
84
- end
85
-
86
- def get(endpoint_url, params = {})
87
- connection.get(API_BASE_URL + endpoint_url, params)
88
- end
89
-
90
- def post(endpoint_url, body, headers = {})
91
- connection.post(API_BASE_URL + endpoint_url, body, headers)
92
- end
93
-
94
- private
95
-
96
- def parse_profile_characters_response(response)
97
- characters = response.body.dig('Response', 'characters', 'data')
98
- return [] if characters.nil?
99
-
100
- result = []
101
-
102
- [].tap do |result|
103
- characters.each_value do |character|
104
- result << Restiny::Character.new(
105
- id: character['characterId'],
106
- session_playtime: character['minutesPlayedThisSession'],
107
- total_playtime: character['minutesPlayedTotal'],
108
- light_level: character['light'],
109
- stats: [],
110
- emblem: [],
111
- progression: character['progression']
112
- )
113
- end
114
- end
115
- end
116
-
117
- def default_headers
118
- {
119
- 'User-Agent': "restiny v#{Restiny::VERSION}",
120
- 'X-API-KEY': @api_key
121
- }
122
- end
123
-
124
- def connection
125
- @connection ||= Faraday.new(headers: default_headers) do |faraday|
126
- faraday.request :json
127
- faraday.request :url_encoded
128
- faraday.response :json
129
- faraday.response :follow_redirects
130
- faraday.response :raise_error
131
- end
132
- end
133
- end
134
- end