restiny 0.1.0 → 0.3.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: 9ad23e571d4f6b91fc296e0578074983f8d6883518ffe94743a6003bbf20dfd0
4
+ data.tar.gz: 6aba22d2cfb3a8bc2f8c93d0a11ccfef63a55eaa8aaec008a3b769710d4b3ff5
5
5
  SHA512:
6
- metadata.gz: 269e932d167058936ac4ccc092493b72da71026dbf5947467367671e2a64421496f0f9d40b99962eb1f1dc416754734ed241dfaaee53440ea03bee610efdb2d2
7
- data.tar.gz: def65d2d7fc397ca3e833a6eec1b922d78639d7c800f763f4e15740a9bde08d8e2e36e5db020dd9654594575c11b1f1c078d3d57f0b9f1eb1bbaf2197e70729a
6
+ metadata.gz: e600c2e4a0552420404dd6aa11692613e65cd6ee7efddc7a5200f8750569e39f259d5156b25fb8baca7175f7575b870794827213548e74877c7faeea23c18e75
7
+ data.tar.gz: 2e319308c54b1e793862400bc5ff362d0570924edf372d1f07a1f06766210e031dda2c79b7db8d171588d8dc8b849c482f6bb4d0ca86902e7d739bd6c831a410
@@ -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
@@ -1,5 +1,4 @@
1
1
  # frozen_string/literal: true
2
-
3
2
  require 'down'
4
3
  require 'json'
5
4
  require 'sqlite3'
@@ -8,73 +7,91 @@ require 'zip'
8
7
  module Restiny
9
8
  class Manifest
10
9
  TABLES = {
11
- 'DestinyAchievementDefinition': { item: 'achievement', items: 'achievements' },
12
- 'DestinyActivityDefinition': { item: 'activity', items: 'activities' },
13
- 'DestinyActivityGraphDefinition': { item: 'activity_graph', items: 'activity_graphs' },
14
- 'DestinyActivityModeDefinition': { item: 'activity_modes', items: 'activity_modes' },
15
- 'DestinyActivityModifierDefinition': { item: 'activity_modifier', items: 'activity_modifiers' },
16
- 'DestinyActivityTypeDefinition': { item: 'activity_type', items: 'activity_types' },
17
- 'DestinyArtifactDefinition': { item: 'artifact', items: 'artifacts' },
18
- 'DestinyBondDefinition': { item: 'bond', items: 'bonds' },
19
- 'DestinyBreakerTypeDefinition': { item: 'breaker_type', items: 'breaker_types' },
20
- 'DestinyChecklistDefinition': { item: 'checklist', items: 'checklists' },
21
- 'DestinyClassDefinition': { item: 'class', items: 'classes' },
22
- 'DestinyCollectibleDefinition': { item: 'collectible', items: 'collectibles' },
23
- 'DestinyDamageTypeDefinition': { item: 'damage_type', items: 'damage_types' },
24
- 'DestinyDestinationDefinition': { item: 'destination', items: 'destinations' },
25
- 'DestinyEnergyTypeDefinition': { item: 'energy_type', items: 'energy_types' },
26
- 'DestinyEquipmentSlotDefinition': { item: 'eqiupment_slot', items: 'equipment_slots' },
27
- 'DestinyEventCardDefinition': { item: 'event_card', items: 'event_cards' },
28
- 'DestinyFactionDefinition': { item: 'faction', items: 'factions' },
29
- 'DestinyGenderDefinition': { item: 'gender', items: 'genders' },
30
- 'DestinyHistoricalStatsDefinition': { item: 'historical_stat', items: 'historical_stats' },
31
- 'DestinyInventoryBucketDefinition': { item: 'inventory_bucket', items: 'inventory_buckets' },
32
- 'DestinyInventoryItemDefinition': { item: 'inventory_item', items: 'inventory_items' },
33
- 'DestinyItemCategoryDefinition': { item: 'item_category', items: 'item_categories' },
34
- 'DestinyItemTierTypeDefinition': { item: 'item_tier_type', items: 'item_tier_types' },
35
- 'DestinyLocationDefinition': { item: 'location', items: 'locations' },
36
- 'DestinyLoreDefinition': { item: 'lore', items: 'lore_entries' },
37
- 'DestinyMaterialRequirementSetDefinition': { item: 'material_requirement_set', items: 'material_requirement_sets' },
38
- 'DestinyMedalTierDefinition': { item: 'medal_tier', items: 'medal_tiers' },
39
- 'DestinyMetricDefinition': { item: 'metric', items: 'metrics' },
40
- 'DestinyMilestoneDefinition': { item: 'milestone', items: 'milestones' },
41
- 'DestinyObjectiveDefinition': { item: 'objective', items: 'objectives' },
42
- 'DestinyPlaceDefinition': { item: 'place', items: 'places' },
43
- 'DestinyPlugSetDefinition': { item: 'plug_set', items: 'plug_sets' },
44
- 'DestinyPowerCapDefinition': { item: 'power_cap', items: 'power_caps' },
45
- 'DestinyPresentationNodeDefinition': { item: 'presentation_node', items: 'presentation_nodes' },
46
- 'DestinyProgressionDefinition': { item: 'progression', items: 'progression_data' },
47
- 'DestinyProgressionLevelRequirementDefinition': { item: 'progression_level_requirement', items: 'progression_level_requirements' },
48
- 'DestinyRaceDefinition': { item: 'race', items: 'races' },
49
- 'DestinyRecordDefinition': { item: 'record', items: 'records' },
50
- 'DestinyReportReasonCategoryDefinition': { item: 'report_reason_category', items: 'report_reason_categories' },
51
- 'DestinyRewardSourceDefinition': { item: 'reward_source', items: 'reward_sources' },
52
- 'DestinySackRewardItemListDefinition': { item: 'sack_reward_item_list', items: 'sack_reward_item_lists' },
53
- 'DestinySandboxPatternDefinition': { item: 'sandbox_pattern', items: 'sandbox_patterns' },
54
- 'DestinySandboxPerkDefinition': { item: 'sandbox_perk', items: 'sandbox_perks' },
55
- 'DestinySeasonDefinition': { item: 'season', items: 'seasons' },
56
- 'DestinySeasonPassDefinition': { item: 'season_pass', items: 'season_passes' },
57
- 'DestinySocketCategoryDefinition': { item: 'socket_category', items: 'socket_categories' },
58
- 'DestinySocketTypeDefinition': { item: 'socket_type', items: 'socket_types' },
59
- 'DestinyStatDefinition': { item: 'stat', items: 'stats' },
60
- 'DestinyStatGroupDefinition': { item: 'stat_group', items: 'stat_groups' },
61
- 'DestinyTalentGridDefinition': { item: 'talent_grid', items: 'talent_grids' },
62
- 'DestinyTraitCategoryDefinition': { item: 'trait_category', items: 'trait_categories' },
63
- 'DestinyTraitDefinition': { item: 'trait', items: 'traits' },
64
- 'DestinyUnlockDefinition': { item: 'unlock', items: 'unlocks' },
65
- 'DestinyVendorDefinition': { item: 'vendor', items: 'vendors' },
66
- 'DestinyVendorGroupDefinition': { item: 'vendor_group', items: 'vendor_groups' }
10
+ 'Achievement': { item: 'achievement', items: 'achievements' },
11
+ 'Activity': { item: 'activity', items: 'activities' },
12
+ 'ActivityGraph': { item: 'activity_graph', items: 'activity_graphs' },
13
+ 'ActivityMode': { item: 'activity_modes', items: 'activity_modes' },
14
+ 'ActivityModifier': { item: 'activity_modifier', items: 'activity_modifiers' },
15
+ 'ActivityType': { item: 'activity_type', items: 'activity_types' },
16
+ 'Artifact': { item: 'artifact', items: 'artifacts' },
17
+ 'Bond': { item: 'bond', items: 'bonds' },
18
+ 'BreakerType': { item: 'breaker_type', items: 'breaker_types' },
19
+ 'Checklist': { item: 'checklist', items: 'checklists' },
20
+ 'Class': { item: 'class', items: 'classes' },
21
+ 'Collectible': { item: 'collectible', items: 'collectibles' },
22
+ 'DamageType': { item: 'damage_type', items: 'damage_types' },
23
+ 'Destination': { item: 'destination', items: 'destinations' },
24
+ 'EnergyType': { item: 'energy_type', items: 'energy_types' },
25
+ 'EquipmentSlot': { item: 'equipment_slot', items: 'equipment_slots' },
26
+ 'EventCard': { item: 'event_card', items: 'event_cards' },
27
+ 'Faction': { item: 'faction', items: 'factions' },
28
+ 'Gender': { item: 'gender', items: 'genders' },
29
+ 'HistoricalStats': { item: 'historical_stat', items: 'historical_stats' },
30
+ 'InventoryBucket': { item: 'inventory_bucket', items: 'inventory_buckets' },
31
+ 'InventoryItem': { item: 'inventory_item', items: 'inventory_items' },
32
+ 'ItemCategory': { item: 'item_category', items: 'item_categories' },
33
+ 'ItemTierType': { item: 'item_tier_type', items: 'item_tier_types' },
34
+ 'Location': { item: 'location', items: 'locations' },
35
+ 'Lore': { item: 'lore', items: 'lore_entries' },
36
+ 'MaterialRequirementSet': { item: 'material_requirement_set', items: 'material_requirement_sets' },
37
+ 'MedalTier': { item: 'medal_tier', items: 'medal_tiers' },
38
+ 'Metric': { item: 'metric', items: 'metrics' },
39
+ 'Milestone': { item: 'milestone', items: 'milestones' },
40
+ 'Objective': { item: 'objective', items: 'objectives' },
41
+ 'Place': { item: 'place', items: 'places' },
42
+ 'PlugSet': { item: 'plug_set', items: 'plug_sets' },
43
+ 'PowerCap': { item: 'power_cap', items: 'power_caps' },
44
+ 'PresentationNode': { item: 'presentation_node', items: 'presentation_nodes' },
45
+ 'Progression': { item: 'progression', items: 'progression_data' },
46
+ 'ProgressionLevelRequirement': { item: 'progression_level_requirement', items: 'progression_level_requirements' },
47
+ 'Race': { item: 'race', items: 'races' },
48
+ 'Record': { item: 'record', items: 'records' },
49
+ 'ReportReasonCategory': { item: 'report_reason_category', items: 'report_reason_categories' },
50
+ 'RewardSource': { item: 'reward_source', items: 'reward_sources' },
51
+ 'SackRewardItemList': { item: 'sack_reward_item_list', items: 'sack_reward_item_lists' },
52
+ 'SandboxPattern': { item: 'sandbox_pattern', items: 'sandbox_patterns' },
53
+ 'SandboxPerk': { item: 'sandbox_perk', items: 'sandbox_perks' },
54
+ 'Season': { item: 'season', items: 'seasons' },
55
+ 'SeasonPass': { item: 'season_pass', items: 'season_passes' },
56
+ 'SocketCategory': { item: 'socket_category', items: 'socket_categories' },
57
+ 'SocketType': { item: 'socket_type', items: 'socket_types' },
58
+ 'Stat': { item: 'stat', items: 'stats' },
59
+ 'StatGroup': { item: 'stat_group', items: 'stat_groups' },
60
+ 'TalentGrid': { item: 'talent_grid', items: 'talent_grids' },
61
+ 'TraitCategory': { item: 'trait_category', items: 'trait_categories' },
62
+ 'Trait': { item: 'trait', items: 'traits' },
63
+ 'Unlock': { item: 'unlock', items: 'unlocks' },
64
+ 'Vendor': { item: 'vendor', items: 'vendors' },
65
+ 'VendorGroup': { item: 'vendor_group', items: 'vendor_groups' }
67
66
  }
68
67
 
69
68
  attr_reader :file_path
70
69
 
71
70
  TABLES.each do |table_name, method_names|
72
- define_method method_names[:item] do |id|
73
- query_table(table_name, id: id)[0]
71
+ full_table_name = "Destiny#{table_name}Definition"
72
+
73
+ define_method method_names[:item] do |hash|
74
+ query = "SELECT json FROM #{full_table_name} WHERE json_extract(json, '$.hash')=?"
75
+
76
+ result = perform_query(query, [hash])
77
+ result = JSON.parse(result[0]['json']) unless result.nil?
74
78
  end
75
79
 
76
- define_method method_names[:items] do |limit: nil|
77
- query_table(table_name, limit: limit)
80
+ define_method method_names[:items] do |limit: nil, filter_empty: false, &block|
81
+ query = "SELECT json_extract(json, '$.hash') AS hash, json_extract(json, '$.displayProperties.name') AS name
82
+ FROM #{full_table_name} "
83
+
84
+ query << "WHERE json_extract(json, '$.displayProperties.name') IS NOT NULL " if filter_empty
85
+ query << "ORDER BY json_extract(json, '$.index')"
86
+
87
+ bindings = []
88
+
89
+ if limit
90
+ query << " LIMIT ?"
91
+ bindings << limit
92
+ end
93
+
94
+ perform_query(query, bindings)
78
95
  end
79
96
  end
80
97
 
@@ -85,14 +102,16 @@ module Restiny
85
102
  Zip::File.open(zipped_file) { |file| file.first.extract(manifest_path) }
86
103
 
87
104
  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"
105
+ rescue Down::ResponseError => error
106
+ raise Restiny::NetworkError.new("Unable to download the manifest file", error.response.code)
107
+ rescue Zip::Error => error
108
+ raise Restiny::Error.new("Unable to unzip the manifest file (#{e})")
92
109
  end
93
110
 
94
111
  def initialize(file_path)
95
- raise "You must provide the file path for the manifest file" if file_path.empty?
112
+ if file_path.empty? || !File.exist?(file_path) || !File.file?(file_path)
113
+ raise Restiny::InvalidParamsError.new("You must provide a valid path for the manifest file")
114
+ end
96
115
 
97
116
  @database = SQLite3::Database.new(file_path, results_as_hash: true)
98
117
  @file_path = file_path
@@ -100,40 +119,10 @@ module Restiny
100
119
 
101
120
  private
102
121
 
103
- def self.clean_row_keys(row_hash)
104
- OpenStruct.new.tap do |output|
105
- row_hash.each_pair do |key, value|
106
- key = key.gsub(/(\B[A-Z])/, '_\1') if key =~ /\B[A-Z]/
107
- output[key.downcase] = if value.is_a?(Hash)
108
- clean_row_keys(value)
109
- else
110
- value
111
- end
112
- end
113
- end
114
- end
115
-
116
- def query_table(table_name, id: nil, limit: nil)
117
- query = "SELECT json FROM #{table_name}"
118
- bindings = []
119
-
120
- if id
121
- query << " WHERE id=?"
122
- bindings << id
123
- end
124
-
125
- query << " ORDER BY json_extract(json, '$.index')" unless id
126
-
127
- if limit
128
- query << " LIMIT ?"
129
- bindings << limit
130
- end
131
-
132
- @database.execute(query, bindings).map do |row|
133
- Manifest::clean_row_keys(JSON.parse(row['json'])) unless row['json'].nil?
134
- end
122
+ def perform_query(query, bindings, &block)
123
+ @database.execute(query, bindings)
135
124
  rescue SQLite3::Exception => e
136
- raise "Error while querying the manifest (#{e})"
125
+ raise Restiny::RequestError.new("Error while querying the manifest (#{e})")
137
126
  end
138
127
  end
139
128
  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.3.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.3.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-15 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