restiny 0.1.0 → 0.2.0

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.
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