restiny 5.0.0 → 6.0.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 +4 -4
- data/lib/restiny/api/authentication.rb +63 -0
- data/lib/restiny/api/base.rb +73 -0
- data/lib/restiny/api/manifest.rb +51 -0
- data/lib/restiny/api/membership.rb +33 -0
- data/lib/restiny/api/profile.rb +39 -0
- data/lib/restiny/api/search.rb +26 -0
- data/lib/restiny/api/stats.rb +19 -0
- data/lib/restiny/constants.rb +202 -11
- data/lib/restiny/errors.rb +7 -24
- data/lib/restiny/version.rb +1 -1
- data/lib/restiny.rb +14 -191
- metadata +12 -64
- data/lib/faraday/destiny/api.rb +0 -33
- data/lib/faraday/destiny/auth.rb +0 -23
- data/lib/restiny/manifest.rb +0 -127
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7a11b1fe287424ab29d345e8598b810afd733d59f634e00badf837ac7f16ec3d
|
|
4
|
+
data.tar.gz: fb704f715f42ad34aa722435079e7aae31c9660d5b9e7b32e1f0d9e82efafabd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b32846afdb0a3f1d89c837064f2809d772b8eb1d74903ed449484c75c70975a82a367f591c958a217a616fd55066beef55db5fb6fbff8350787dc163ffe04b6b
|
|
7
|
+
data.tar.gz: 2b8b0b66fdaedcf77cb1b3b308ad54aafd92d5aaa175e9238b28d98ce4ad41da9bc6d6fb4e0c4fdd78dd7c23d6de247bdf7263e28ff4fec1723892866ddae596
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
|
|
5
|
+
require 'securerandom'
|
|
6
|
+
|
|
7
|
+
module Restiny
|
|
8
|
+
module Api
|
|
9
|
+
module Authentication
|
|
10
|
+
include Base
|
|
11
|
+
|
|
12
|
+
CODE_RESPONSE_TYPE = 'code'
|
|
13
|
+
AUTH_CODE_GRANT_TYPE = 'authorization_code'
|
|
14
|
+
|
|
15
|
+
def get_authorise_url(redirect_url: nil, state: nil)
|
|
16
|
+
check_oauth_client_id
|
|
17
|
+
|
|
18
|
+
params = {
|
|
19
|
+
response_type: CODE_RESPONSE_TYPE,
|
|
20
|
+
client_id: @oauth_client_id,
|
|
21
|
+
state: state || SecureRandom.hex(15)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
params['redirect_url'] = redirect_url unless redirect_url.nil?
|
|
25
|
+
|
|
26
|
+
query = params.map { |k, v| "#{k}=#{v}" }.join('&')
|
|
27
|
+
|
|
28
|
+
"#{BUNGIE_URL}/en/oauth/authorize/?#{query}"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def request_access_token(code, redirect_url: nil)
|
|
32
|
+
check_oauth_client_id
|
|
33
|
+
|
|
34
|
+
params = { code: code, grant_type: AUTH_CODE_GRANT_TYPE, client_id: @oauth_client_id }
|
|
35
|
+
params['redirect_url'] = redirect_url unless redirect_url.nil?
|
|
36
|
+
|
|
37
|
+
response = http_client.post('/platform/app/oauth/token/', form: params)
|
|
38
|
+
response.raise_for_status
|
|
39
|
+
|
|
40
|
+
response.json
|
|
41
|
+
rescue HTTPX::Error => e
|
|
42
|
+
handle_authentication_error(e)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def handle_authentication_error(error)
|
|
48
|
+
raise Restiny::AuthenticationError,
|
|
49
|
+
"#{error.response.json['error_description']} (#{error.response.json['error']})"
|
|
50
|
+
rescue HTTPX::Error
|
|
51
|
+
raise Restiny::AuthenticationError,
|
|
52
|
+
"#{error.response.status}: #{error.response.headers['x-selfurl']}"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def check_oauth_client_id
|
|
56
|
+
return if @oauth_client_id
|
|
57
|
+
|
|
58
|
+
raise Restiny::RequestError,
|
|
59
|
+
'You need to set an OAuth client ID (Restiny.oauth_client_id)'
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'restiny/constants'
|
|
4
|
+
require 'restiny/errors'
|
|
5
|
+
require 'httpx'
|
|
6
|
+
require 'json'
|
|
7
|
+
|
|
8
|
+
module Restiny
|
|
9
|
+
BUNGIE_URL = 'https://www.bungie.net'
|
|
10
|
+
|
|
11
|
+
attr_accessor :api_key, :oauth_state, :oauth_client_id, :access_token, :user_agent
|
|
12
|
+
|
|
13
|
+
module Api
|
|
14
|
+
module Base
|
|
15
|
+
def get(endpoint)
|
|
16
|
+
make_api_request(endpoint, method: :get)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def post(endpoint, params: {})
|
|
20
|
+
make_api_request(endpoint, method: :post, params: params)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def http_client
|
|
26
|
+
HTTPX.with(origin: BUNGIE_URL, headers: api_headers).plugin(:follow_redirects, follow_insecure_redirects: true)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def make_api_request(endpoint, method: :get, params: {})
|
|
30
|
+
raise Restiny::InvalidParamsError, 'You need to set an API key (Restiny.api_key)' if @api_key.nil?
|
|
31
|
+
|
|
32
|
+
response = http_client.with(base_path: '/platform/').request(method, endpoint, json: params)
|
|
33
|
+
response.raise_for_status
|
|
34
|
+
|
|
35
|
+
response.json['Response']
|
|
36
|
+
rescue HTTPX::TimeoutError, HTTPX::ResolveError => e
|
|
37
|
+
raise Restiny::RequestError, e.message
|
|
38
|
+
rescue HTTPX::HTTPError => e
|
|
39
|
+
handle_api_error(e)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def handle_api_error(error)
|
|
43
|
+
klass = case error.response.status
|
|
44
|
+
when 400..499 then ::Restiny::RequestError
|
|
45
|
+
when 500..599 then ::Restiny::ResponseError
|
|
46
|
+
else ::Restiny::Error
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
raise klass, if error.response.headers['content-type'].match?(%r{^application/json})
|
|
50
|
+
error_message_from_json(error.response.json)
|
|
51
|
+
else
|
|
52
|
+
error.status
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def error_message_from_json(json)
|
|
57
|
+
"#{json['ErrorStatus']} (#{json['ErrorCode']}): #{json['Message']}"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def api_headers
|
|
61
|
+
{}.tap do |headers|
|
|
62
|
+
headers['x-api-key'] = @api_key
|
|
63
|
+
headers['user-agent'] = @user_agent || "restiny v#{Restiny::VERSION}"
|
|
64
|
+
headers['authentication'] = "Bearer #{@access_token}" unless @access_token.nil?
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def valid_array_param?(param)
|
|
69
|
+
param.is_a?(Array) && !param.empty?
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
|
|
5
|
+
require 'tmpdir'
|
|
6
|
+
require 'uri'
|
|
7
|
+
|
|
8
|
+
module Restiny
|
|
9
|
+
module Api
|
|
10
|
+
module Manifest
|
|
11
|
+
include Base
|
|
12
|
+
|
|
13
|
+
def fetch_manifest
|
|
14
|
+
result = get('/Destiny2/Manifest/')
|
|
15
|
+
return result unless result.nil?
|
|
16
|
+
|
|
17
|
+
raise Restiny::ResponseError, 'Unable to fetch manifest details'
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def download_manifest_json(locale: 'en', definitions: [])
|
|
21
|
+
raise Restiny::InvalidParamsError, 'No definitions provided' unless valid_array_param?(definitions)
|
|
22
|
+
raise Restiny::InvalidParamsError, 'Unknown definitions provided' unless known_definitions?(definitions)
|
|
23
|
+
|
|
24
|
+
paths = fetch_manifest.dig('jsonWorldComponentContentPaths', locale)
|
|
25
|
+
raise Restiny::ResponseError, "Unable to find manifest JSON for locale '#{locale}'" if paths.nil?
|
|
26
|
+
|
|
27
|
+
{}.tap do |files|
|
|
28
|
+
definitions.each do |definition|
|
|
29
|
+
files[definition] = download_manifest_json_by_url(BUNGIE_URL + paths[definition])
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def known_definitions?(definitions)
|
|
35
|
+
definitions.difference(Restiny::ManifestDefinition.values).empty?
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def download_manifest_json_by_url(url)
|
|
39
|
+
filename = URI(url).path.split('/').last
|
|
40
|
+
path = File.join(Dir.tmpdir, filename)
|
|
41
|
+
|
|
42
|
+
HTTPX.get(url).copy_to(path)
|
|
43
|
+
raise Restiny::Error, "Unable to download JSON from #{url}" unless File.exist?(path)
|
|
44
|
+
|
|
45
|
+
path
|
|
46
|
+
rescue HTTPX::Error
|
|
47
|
+
raise Restiny::ResponseError, "Unable to download #{definition} JSON file"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
|
|
5
|
+
module Restiny
|
|
6
|
+
module Api
|
|
7
|
+
module Membership
|
|
8
|
+
include Base
|
|
9
|
+
|
|
10
|
+
def get_user_memberships_by_id(membership_id, membership_type: Platform::ALL)
|
|
11
|
+
raise Restiny::InvalidParamsError, 'Please provide a membership ID' if membership_id.nil?
|
|
12
|
+
raise Restiny::InvalidParamsError, 'Please provide a membership type' if membership_type.nil?
|
|
13
|
+
|
|
14
|
+
get("/User/GetMembershipsById/#{membership_id}/#{membership_type}/")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def get_primary_user_membership(membership_id, use_fallback: true)
|
|
18
|
+
raise Restiny::InvalidParamsError, 'Please provide a membership ID' if membership_id.nil?
|
|
19
|
+
|
|
20
|
+
result = get_user_memberships_by_id(membership_id)
|
|
21
|
+
return nil if result.nil?
|
|
22
|
+
|
|
23
|
+
unless result['primaryMembershipId'].nil?
|
|
24
|
+
result['destinyMemberships'].each do |membership|
|
|
25
|
+
return membership if membership['membershipID'] == result['primaryMembershipId']
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
result['destinyMemberships'][0] if use_fallback
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
|
|
5
|
+
module Restiny
|
|
6
|
+
module Api
|
|
7
|
+
module Profile
|
|
8
|
+
include Base
|
|
9
|
+
|
|
10
|
+
def get_profile(membership_id:, membership_type:, components:, type_url: nil)
|
|
11
|
+
raise Restiny::InvalidParamsError, 'No components provided' unless valid_array_param?(components)
|
|
12
|
+
|
|
13
|
+
url = "/Destiny2/#{membership_type}/Profile/#{membership_id}/"
|
|
14
|
+
url += type_url if type_url
|
|
15
|
+
url += "?components=#{components.join(',')}"
|
|
16
|
+
|
|
17
|
+
get(url)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def get_character_profile(character_id:, membership_id:, membership_type:, components:)
|
|
21
|
+
get_profile(
|
|
22
|
+
membership_id: membership_id,
|
|
23
|
+
membership_type: membership_type,
|
|
24
|
+
components: components,
|
|
25
|
+
type_url: "Character/#{character_id}/"
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def get_instanced_item_profile(item_id:, membership_id:, membership_type:, components:)
|
|
30
|
+
get_profile(
|
|
31
|
+
membership_id: membership_id,
|
|
32
|
+
membership_type: membership_type,
|
|
33
|
+
components: components,
|
|
34
|
+
type_url: "Item/#{item_id}/"
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
|
|
5
|
+
module Restiny
|
|
6
|
+
module Api
|
|
7
|
+
module Search
|
|
8
|
+
include Base
|
|
9
|
+
|
|
10
|
+
def search_player_by_bungie_name(name, membership_type: Platform::ALL)
|
|
11
|
+
display_name, display_name_code = name.split('#')
|
|
12
|
+
if display_name.nil? || display_name_code.nil?
|
|
13
|
+
raise Restiny::InvalidParamsError, 'You must provide a valid Bungie name'
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
post("/Destiny2/SearchDestinyPlayerByBungieName/#{membership_type}/", params: {
|
|
17
|
+
displayName: display_name, displayNameCode: display_name_code
|
|
18
|
+
})
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def search_users_by_global_name(name:, page: 0)
|
|
22
|
+
post("/User/Search/GlobalName/#{page}/", params: { displayNamePrefix: name })
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
|
|
5
|
+
module Restiny
|
|
6
|
+
module Api
|
|
7
|
+
module Stats
|
|
8
|
+
include Base
|
|
9
|
+
|
|
10
|
+
def get_post_game_carnage_report(activity_id:)
|
|
11
|
+
raise Restiny::InvalidParamsError, 'Please provide an activity ID' if activity_id.nil?
|
|
12
|
+
|
|
13
|
+
get("/Destiny2/Stats/PostGameCarnageReport/#{activity_id}/")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
alias get_pgcr get_post_game_carnage_report
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
data/lib/restiny/constants.rb
CHANGED
|
@@ -1,22 +1,45 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Restiny
|
|
4
|
+
# Definitions for the gaming platforms supported by Destiny 2.
|
|
4
5
|
module Platform
|
|
5
6
|
ALL = -1
|
|
6
7
|
XBOX = 1
|
|
7
8
|
PSN = 2
|
|
8
9
|
STEAM = 3
|
|
9
10
|
EPIC = 6
|
|
11
|
+
|
|
12
|
+
def self.names
|
|
13
|
+
{
|
|
14
|
+
ALL => 'All',
|
|
15
|
+
XBOX => 'Xbox',
|
|
16
|
+
PSN => 'PSN',
|
|
17
|
+
STEAM => 'Steam',
|
|
18
|
+
EPIC => 'Epic'
|
|
19
|
+
}
|
|
20
|
+
end
|
|
10
21
|
end
|
|
11
22
|
|
|
12
23
|
module ItemLocation
|
|
24
|
+
# Definitions for the possible locations an item can be related to a character.
|
|
13
25
|
UNKNOWN = 0
|
|
14
26
|
INVENTORY = 1
|
|
15
27
|
VAULT = 2
|
|
16
28
|
VENDOR = 3
|
|
17
29
|
POSTMASTER = 4
|
|
30
|
+
|
|
31
|
+
def self.names
|
|
32
|
+
{
|
|
33
|
+
UNKNOWN => 'Unknown',
|
|
34
|
+
INVENTORY => 'Inventory',
|
|
35
|
+
VAULT => 'Vault',
|
|
36
|
+
VENDOR => 'Vendor',
|
|
37
|
+
POSTMASTER => 'Postmaster'
|
|
38
|
+
}
|
|
39
|
+
end
|
|
18
40
|
end
|
|
19
41
|
|
|
42
|
+
# Definitions for the tier/rarity of a particular item.
|
|
20
43
|
module TierType
|
|
21
44
|
UNKNOWN = 0
|
|
22
45
|
CURRENCY = 1
|
|
@@ -25,47 +48,215 @@ module Restiny
|
|
|
25
48
|
RARE = 4
|
|
26
49
|
SUPERIOR = 5
|
|
27
50
|
EXOTIC = 6
|
|
51
|
+
|
|
52
|
+
def self.names
|
|
53
|
+
{
|
|
54
|
+
UNKNOWN => 'Unknown',
|
|
55
|
+
CURRENCY => 'Currency',
|
|
56
|
+
BASIC => 'Basic',
|
|
57
|
+
COMMON => 'Common',
|
|
58
|
+
RARE => 'Rare',
|
|
59
|
+
SUPERIOR => 'Superior',
|
|
60
|
+
EXOTIC => 'Exotic'
|
|
61
|
+
}
|
|
62
|
+
end
|
|
28
63
|
end
|
|
29
64
|
|
|
65
|
+
# Definitions for a Guardian's class.
|
|
30
66
|
module GuardianClass
|
|
31
67
|
TITAN = 0
|
|
32
68
|
HUNTER = 1
|
|
33
69
|
WARLOCK = 2
|
|
34
70
|
UNKNOWN = 3
|
|
71
|
+
|
|
72
|
+
def self.names
|
|
73
|
+
{
|
|
74
|
+
TITAN => 'Titan',
|
|
75
|
+
HUNTER => 'Hunter',
|
|
76
|
+
WARLOCK => 'Warlock',
|
|
77
|
+
UNKNOWN => 'Unknown'
|
|
78
|
+
}
|
|
79
|
+
end
|
|
35
80
|
end
|
|
36
81
|
|
|
82
|
+
# Definitions for a Guardian's race.
|
|
37
83
|
module Race
|
|
38
84
|
HUMAN = 0
|
|
39
85
|
AWOKEN = 1
|
|
40
86
|
EXO = 2
|
|
41
87
|
UNKNOWN = 3
|
|
88
|
+
|
|
89
|
+
def self.names
|
|
90
|
+
{
|
|
91
|
+
HUMAN => 'Human',
|
|
92
|
+
AWOKEN => 'Awoken',
|
|
93
|
+
EXO => 'Exo',
|
|
94
|
+
UNKNOWN => 'Unknown'
|
|
95
|
+
}
|
|
96
|
+
end
|
|
42
97
|
end
|
|
43
98
|
|
|
99
|
+
# Definitions for a Guardian's gender.
|
|
44
100
|
module Gender
|
|
45
101
|
MASCULINE = 0
|
|
46
102
|
FEMININE = 1
|
|
47
103
|
UNKNOWN = 2
|
|
104
|
+
|
|
105
|
+
def self.names
|
|
106
|
+
{
|
|
107
|
+
MASCULINE => 'Masculine',
|
|
108
|
+
FEMININE => 'Feminine',
|
|
109
|
+
UNKNOWN => 'Unknown'
|
|
110
|
+
}
|
|
111
|
+
end
|
|
48
112
|
end
|
|
49
113
|
|
|
114
|
+
# Definitions for the various types of ammunition used in the game.
|
|
50
115
|
module Ammunition
|
|
51
116
|
NONE = 0
|
|
52
117
|
PRIMARY = 1
|
|
53
118
|
SPECIAL = 2
|
|
54
119
|
HEAVY = 3
|
|
55
120
|
UNKNOWN = 4
|
|
121
|
+
|
|
122
|
+
def self.names
|
|
123
|
+
{
|
|
124
|
+
NONE => 'None',
|
|
125
|
+
PRIMARY => 'Primary',
|
|
126
|
+
SPECIAL => 'Special',
|
|
127
|
+
HEAVY => 'Heavy',
|
|
128
|
+
UNKNOWN => 'Unknown'
|
|
129
|
+
}
|
|
130
|
+
end
|
|
56
131
|
end
|
|
57
132
|
|
|
133
|
+
# Definitions for the various component types used when requesting a profile entry.
|
|
58
134
|
module ComponentType
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
135
|
+
PROFILES = '100'
|
|
136
|
+
VENDOR_RECEIPTS = '101'
|
|
137
|
+
PROFILE_INVENTORIES = '102'
|
|
138
|
+
PROFILE_CURRENCIES = '103'
|
|
139
|
+
PROFILE_PROGRESSION = '104'
|
|
140
|
+
PLATFORM_SILVER = '105'
|
|
141
|
+
CHARACTERS = '200'
|
|
142
|
+
CHARACTER_INVENTORIES = '201'
|
|
143
|
+
CHARACTER_PROGRESSIONS = '202'
|
|
144
|
+
CHARACTER_RENDERDATA = '203'
|
|
145
|
+
CHARACTER_ACTIVITIES = '204'
|
|
146
|
+
CHARACTER_EQUIPMENT = '205'
|
|
147
|
+
CHARACTER_LOADOUTS = '206'
|
|
148
|
+
ITEM_INSTANCES = '300'
|
|
149
|
+
ITEM_OBJECTIVES = '301'
|
|
150
|
+
ITEM_PERKS = '302'
|
|
151
|
+
ITEM_RENDER_DATA = '303'
|
|
152
|
+
ITEM_STATS = '304'
|
|
153
|
+
ITEM_SOCKETS = '305'
|
|
154
|
+
ITEM_TALENT_GRIDS = '306'
|
|
155
|
+
ITEM_COMMON_DATA = '307'
|
|
156
|
+
ITEM_PLUG_STATES = '308'
|
|
157
|
+
ITEM_PLUG_OBJECTIVES = '309'
|
|
158
|
+
ITEM_REUSABLE_PLUGS = '310'
|
|
159
|
+
VENDORS = '400'
|
|
160
|
+
VENDOR_CATEGORIES = '401'
|
|
161
|
+
VENDOR_SALES = '402'
|
|
162
|
+
KIOSKS = '500'
|
|
163
|
+
CURRENCY_LOOKUPS = '600'
|
|
164
|
+
PRESENTATION_NODES = '700'
|
|
165
|
+
COLLECTIBLES = '800'
|
|
166
|
+
RECORDS = '900'
|
|
167
|
+
TRANSITORY = '1000'
|
|
168
|
+
METRICS = '1100'
|
|
169
|
+
STRING_VARIABLES = '1200'
|
|
170
|
+
CRAFTABLES = '1300'
|
|
171
|
+
SOCIAL_COMMENDATIONS = '1400'
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# The categories of data stored in the manifest.
|
|
175
|
+
module ManifestDefinition
|
|
176
|
+
def self.values
|
|
177
|
+
constants.map { |c| const_get(c) }
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
ACHIEVEMENT = 'DestinyAchievementDefinition'
|
|
181
|
+
ACTIVITY = 'DestinyActivityDefinition'
|
|
182
|
+
ACTIVITY_GRAPH = 'DestinyActivityGraphDefinition'
|
|
183
|
+
ACTIVITY_INTERACTABLE = 'DestinyActivityInteractableDefinition'
|
|
184
|
+
ACTIVITY_MODE = 'DestinyActivityModeDefinition'
|
|
185
|
+
ACTIVITY_MODIFIER = 'DestinyActivityModifierDefinition'
|
|
186
|
+
ACTIVITY_TYPE = 'DestinyActivityTypeDefinition'
|
|
187
|
+
ART_DYE_CHANNEL = 'DestinyArtDyeChannelDefinition'
|
|
188
|
+
ART_DYE_REFERENCE = 'DestinyArtDyeReferenceDefinition'
|
|
189
|
+
ARTIFACT = 'DestinyArtifactDefinition'
|
|
190
|
+
BOND = 'DestinyBondDefinition'
|
|
191
|
+
BREAKER_TYPE = 'DestinyBreakerTypeDefinition'
|
|
192
|
+
CHARACTER_CUSTOMIZATION_CATEGORY = 'DestinyCharacterCustomizationCategoryDefinition'
|
|
193
|
+
CHARACTER_CUSTOMIZATION_OPTION = 'DestinyCharacterCustomizationOptionDefinition'
|
|
194
|
+
CHECKLIST = 'DestinyChecklistDefinition'
|
|
195
|
+
CLASS = 'DestinyClassDefinition'
|
|
196
|
+
COLLECTIBLE = 'DestinyCollectibleDefinition'
|
|
197
|
+
DAMAGE_TYPE = 'DestinyDamageTypeDefinition'
|
|
198
|
+
DESTINATION = 'DestinyDestinationDefinition'
|
|
199
|
+
ENERGY_TYPE = 'DestinyEnergyTypeDefinition'
|
|
200
|
+
ENTITLEMENT_OFFER = 'DestinyEntitlementOfferDefinition'
|
|
201
|
+
EQUIPMENT_SLOT = 'DestinyEquipmentSlotDefinition'
|
|
202
|
+
EVENT_CARD = 'DestinyEventCardDefinition'
|
|
203
|
+
FACTION = 'DestinyFactionDefinition'
|
|
204
|
+
GENDER = 'DestinyGenderDefinition'
|
|
205
|
+
GUARDIAN_RANK_CONSTANTS = 'DestinyGuardianRankConstantsDefinition'
|
|
206
|
+
GUARDIAN_RANK = 'DestinyGuardianRankDefinition'
|
|
207
|
+
INVENTORY_BUCKET = 'DestinyInventoryBucketDefinition'
|
|
208
|
+
INVENTORY_ITEM = 'DestinyInventoryItemDefinition'
|
|
209
|
+
INVENTORY_ITEM_LITE = 'DestinyInventoryItemLiteDefinition'
|
|
210
|
+
ITEM_CATEGORY = 'DestinyItemCategoryDefinition'
|
|
211
|
+
ITEM_TIER_TYPE = 'DestinyItemTierTypeDefinition'
|
|
212
|
+
LOADOUT_COLOR = 'DestinyLoadoutColorDefinition'
|
|
213
|
+
LOADOUT_CONSTANTS = 'DestinyLoadoutConstantsDefinition'
|
|
214
|
+
LOADOUT_ICON = 'DestinyLoadoutIconDefinition'
|
|
215
|
+
LOADOUT_NAME = 'DestinyLoadoutNameDefinition'
|
|
216
|
+
LOCATION = 'DestinyLocationDefinition'
|
|
217
|
+
LORE = 'DestinyLoreDefinition'
|
|
218
|
+
MATERIAL_REQUIREMENT_SET = 'DestinyMaterialRequirementSetDefinition'
|
|
219
|
+
MEDAL_TIER = 'DestinyMedalTierDefinition'
|
|
220
|
+
METRIC = 'DestinyMetricDefinition'
|
|
221
|
+
MILESTONE = 'DestinyMilestoneDefinition'
|
|
222
|
+
NODE_STEP_SUMMARY = 'DestinyNodeStepSummaryDefinition'
|
|
223
|
+
OBJECTIVE = 'DestinyObjectiveDefinition'
|
|
224
|
+
PLACE = 'DestinyPlaceDefinition'
|
|
225
|
+
PLATFORM_BUCKET_MAPPING = 'DestinyPlatformBucketMappingDefinition'
|
|
226
|
+
PLUG_SET = 'DestinyPlugSetDefinition'
|
|
227
|
+
POWER_CAP = 'DestinyPowerCapDefinition'
|
|
228
|
+
PRESENTATION_NODE = 'DestinyPresentationNodeDefinition'
|
|
229
|
+
PROGRESSION = 'DestinyProgressionDefinition'
|
|
230
|
+
PROGRESSION_LEVEL_REQUIREMENT = 'DestinyProgressionLevelRequirementDefinition'
|
|
231
|
+
PROGRESSION_MAPPING = 'DestinyProgressionMappingDefinition'
|
|
232
|
+
RACE = 'DestinyRaceDefinition'
|
|
233
|
+
RECORD = 'DestinyRecordDefinition'
|
|
234
|
+
REPORT_REASON_CATEGORY = 'DestinyReportReasonCategoryDefinition'
|
|
235
|
+
REWARD_ADJUSTER_POINTER = 'DestinyRewardAdjusterPointerDefinition'
|
|
236
|
+
REWARD_ADJUSTER_PROGRESSION_MAP = 'DestinyRewardAdjusterProgressionMapDefinition'
|
|
237
|
+
REWARD_ITEM_LIST = 'DestinyRewardItemListDefinition'
|
|
238
|
+
REWARD_MAPPING = 'DestinyRewardMappingDefinition'
|
|
239
|
+
REWARD_SHEET = 'DestinyRewardSheetDefinition'
|
|
240
|
+
REWARD_SOURCE = 'DestinyRewardSourceDefinition'
|
|
241
|
+
SACK_REWARD_ITEM_LIST = 'DestinySackRewardItemListDefinition'
|
|
242
|
+
SANDBOX_PATTERN = 'DestinySandboxPatternDefinition'
|
|
243
|
+
SANDBOX_PERK = 'DestinySandboxPerkDefinition'
|
|
244
|
+
SEASON = 'DestinySeasonDefinition'
|
|
245
|
+
SEASON_PASS = 'DestinySeasonPassDefinition'
|
|
246
|
+
SOCIAL_COMMENDATION = 'DestinySocialCommendationDefinition'
|
|
247
|
+
SOCIAL_COMMENDATION_NODE = 'DestinySocialCommendationNodeDefinition'
|
|
248
|
+
SOCKET_CATEGORY = 'DestinySocketCategoryDefinition'
|
|
249
|
+
SOCKET_TYPE = 'DestinySocketTypeDefinition'
|
|
250
|
+
STAT = 'DestinyStatDefinition'
|
|
251
|
+
STAT_GROUP = 'DestinyStatGroupDefinition'
|
|
252
|
+
TALENT_GRID = 'DestinyTalentGridDefinition'
|
|
253
|
+
TRAIT = 'DestinyTraitDefinition'
|
|
254
|
+
UNLOCK_COUNT_MAPPING = 'DestinyUnlockCountMappingDefinition'
|
|
255
|
+
UNLOCK = 'DestinyUnlockDefinition'
|
|
256
|
+
UNLOCK_EVENT = 'DestinyUnlockEventDefinition'
|
|
257
|
+
UNLOCK_EXPRESSION_MAPPING = 'DestinyUnlockExpressionMappingDefinition'
|
|
258
|
+
UNLOCK_VALUE = 'DestinyUnlockValueDefinition'
|
|
259
|
+
VENDOR = 'DestinyVendorDefinition'
|
|
260
|
+
VENDOR_GROUP = 'DestinyVendorGroupDefinition'
|
|
70
261
|
end
|
|
71
262
|
end
|
data/lib/restiny/errors.rb
CHANGED
|
@@ -1,28 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Restiny
|
|
4
|
-
class Error < StandardError
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
class NetworkError < Error
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
class RequestError < Error
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
class InvalidParamsError < RequestError
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
class RateLimitedError < RequestError
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
class AuthenticationError < RequestError
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
class ResponseError < Error
|
|
27
|
-
end
|
|
4
|
+
class Error < StandardError; end
|
|
5
|
+
class NetworkError < Error; end
|
|
6
|
+
class RequestError < Error; end
|
|
7
|
+
class InvalidParamsError < RequestError; end
|
|
8
|
+
class RateLimitedError < RequestError; end
|
|
9
|
+
class AuthenticationError < RequestError; end
|
|
10
|
+
class ResponseError < Error; end
|
|
28
11
|
end
|
data/lib/restiny/version.rb
CHANGED
data/lib/restiny.rb
CHANGED
|
@@ -3,198 +3,21 @@
|
|
|
3
3
|
$LOAD_PATH.unshift(__dir__)
|
|
4
4
|
|
|
5
5
|
require 'restiny/version'
|
|
6
|
-
require 'restiny/
|
|
7
|
-
require 'restiny/
|
|
8
|
-
require 'restiny/
|
|
9
|
-
|
|
10
|
-
require '
|
|
11
|
-
require '
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
require 'faraday/destiny/auth'
|
|
15
|
-
require 'json'
|
|
16
|
-
require 'securerandom'
|
|
17
|
-
require 'tmpdir'
|
|
18
|
-
require 'zip'
|
|
19
|
-
|
|
6
|
+
require 'restiny/api/authentication'
|
|
7
|
+
require 'restiny/api/manifest'
|
|
8
|
+
require 'restiny/api/membership'
|
|
9
|
+
require 'restiny/api/profile'
|
|
10
|
+
require 'restiny/api/search'
|
|
11
|
+
require 'restiny/api/stats'
|
|
12
|
+
|
|
13
|
+
# The main Restiny module.
|
|
20
14
|
module Restiny
|
|
21
15
|
extend self
|
|
22
16
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def get_authorise_url(redirect_url: nil, state: nil)
|
|
31
|
-
check_oauth_client_id
|
|
32
|
-
|
|
33
|
-
@oauth_state = state || SecureRandom.hex(15)
|
|
34
|
-
|
|
35
|
-
params = { response_type: 'code', client_id: @oauth_client_id, state: @oauth_state }
|
|
36
|
-
params['redirect_url'] = redirect_url unless redirect_url.nil?
|
|
37
|
-
|
|
38
|
-
auth_connection.build_url("#{BUNGIE_URL}/en/oauth/authorize/", params).to_s
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def request_access_token(code:, redirect_url: nil)
|
|
42
|
-
check_oauth_client_id
|
|
43
|
-
|
|
44
|
-
params = { code: code, grant_type: 'authorization_code', client_id: @oauth_client_id }
|
|
45
|
-
params['redirect_url'] = redirect_url unless redirect_url.nil?
|
|
46
|
-
|
|
47
|
-
auth_connection.post('app/oauth/token/', params).body
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
# Manifest methods
|
|
51
|
-
|
|
52
|
-
def download_manifest(locale: 'en', force_download: false)
|
|
53
|
-
result = api_get('Destiny2/Manifest/')
|
|
54
|
-
raise Restiny::ResponseError, 'Unable to determine manifest details' if result.nil?
|
|
55
|
-
|
|
56
|
-
live_version = result['version']
|
|
57
|
-
|
|
58
|
-
@manifests ||= {}
|
|
59
|
-
@manifest_versions ||= {}
|
|
60
|
-
|
|
61
|
-
if force_download || @manifests[locale].nil? || @manifest_versions[locale] != live_version
|
|
62
|
-
manifest_db_url = result.dig('mobileWorldContentPaths', locale)
|
|
63
|
-
raise Restiny::RequestError, 'Unknown locale' if manifest_db_url.nil?
|
|
64
|
-
|
|
65
|
-
database_file_path = Zip::File.open(Down.download(BUNGIE_URL + manifest_db_url)) do |zip_file|
|
|
66
|
-
File.join(Dir.tmpdir, "#{live_version}.en.content.db").tap do |path|
|
|
67
|
-
zip_file.first.extract(path) unless File.exist?(path)
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
@manifests[locale] = Manifest.new(database_file_path, live_version)
|
|
72
|
-
@manifest_versions[locale] = live_version
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
@manifests[locale]
|
|
76
|
-
rescue Down::Error => e
|
|
77
|
-
raise Restiny::NetworkError.new('Unable to download the manifest file', e.response.code)
|
|
78
|
-
rescue Zip::Error => e
|
|
79
|
-
raise Restiny::Error, "Unable to unzip the manifest file (#{e})"
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
# Profile and related methods
|
|
83
|
-
|
|
84
|
-
def get_profile(membership_id:, membership_type:, components:, type_url: nil)
|
|
85
|
-
if !components.is_a?(Array) || components.empty?
|
|
86
|
-
raise Restiny::InvalidParamsError, 'Please provide at least one component'
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
url = "Destiny2/#{membership_type}/Profile/#{membership_id}/"
|
|
90
|
-
url += type_url if type_url
|
|
91
|
-
url += "?components=#{components.join(',')}"
|
|
92
|
-
|
|
93
|
-
api_get(url)
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
def get_character_profile(character_id:, membership_id:, membership_type:, components:)
|
|
97
|
-
get_profile(
|
|
98
|
-
membership_id: membership_id,
|
|
99
|
-
membership_type: membership_type,
|
|
100
|
-
components: components,
|
|
101
|
-
type_url: "Character/#{character_id}/"
|
|
102
|
-
)
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
def get_instanced_item_profile(item_id:, membership_id:, membership_type:, components:)
|
|
106
|
-
get_profile(
|
|
107
|
-
membership_id: membership_id,
|
|
108
|
-
membership_type: membership_type,
|
|
109
|
-
components: components,
|
|
110
|
-
type_url: "Item/#{item_id}/"
|
|
111
|
-
)
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
# User methods.
|
|
115
|
-
|
|
116
|
-
def get_user_memberships_by_id(membership_id, membership_type: Platform::ALL)
|
|
117
|
-
raise Restiny::InvalidParamsError, 'Please provide a membership ID' if membership_id.nil?
|
|
118
|
-
|
|
119
|
-
api_get("User/GetMembershipsById/#{membership_id}/#{membership_type}/")
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
def get_user_primary_membership(parent_membership_id, use_fallback: true)
|
|
123
|
-
result = get_user_memberships_by_id(parent_membership_id)
|
|
124
|
-
return nil if result.nil? || result['primaryMembershipId'].nil?
|
|
125
|
-
|
|
126
|
-
result['destinyMemberships'].each do |membership|
|
|
127
|
-
return membership if membership['membershipID'] == result['primaryMembershipId']
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
result['destinyMemberships'][0] if use_fallback
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
def search_player_by_bungie_name(name, membership_type: Platform::ALL)
|
|
134
|
-
display_name, display_name_code = name.split('#')
|
|
135
|
-
if display_name.nil? || display_name_code.nil?
|
|
136
|
-
raise Restiny::InvalidParamsError, 'You must provide a valid Bungie name'
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
api_post(
|
|
140
|
-
"Destiny2/SearchDestinyPlayerByBungieName/#{membership_type}/",
|
|
141
|
-
params: {
|
|
142
|
-
displayName: display_name,
|
|
143
|
-
displayNameCode: display_name_code
|
|
144
|
-
}
|
|
145
|
-
)
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
def search_users_by_global_name(name, page: 0)
|
|
149
|
-
api_post("User/Search/GlobalName/#{page}/", params: { displayNamePrefix: name })
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
# General request methods
|
|
153
|
-
|
|
154
|
-
def api_get(url, params: {})
|
|
155
|
-
api_connection.get(url, params, token_header).body
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
def api_post(url, params: {})
|
|
159
|
-
api_connection.post(url, params, token_header).body
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
private
|
|
163
|
-
|
|
164
|
-
def check_oauth_client_id
|
|
165
|
-
raise Restiny::RequestError, 'You need to set an OAuth client ID' unless @oauth_client_id
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
def default_headers
|
|
169
|
-
{ 'User-Agent': @user_agent || "restiny v#{Restiny::VERSION}" }
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
def api_connection
|
|
173
|
-
raise Restiny::InvalidParamsError, 'You need to set an API key' unless @api_key
|
|
174
|
-
|
|
175
|
-
@connection ||=
|
|
176
|
-
Faraday.new(
|
|
177
|
-
url: API_BASE_URL,
|
|
178
|
-
headers: default_headers.merge('X-API-KEY': @api_key)
|
|
179
|
-
) do |faraday|
|
|
180
|
-
faraday.request :json
|
|
181
|
-
faraday.response :follow_redirects
|
|
182
|
-
faraday.response :destiny_api
|
|
183
|
-
faraday.response :json
|
|
184
|
-
end
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
def auth_connection
|
|
188
|
-
@auth_connection ||=
|
|
189
|
-
Faraday.new(url: API_BASE_URL, headers: default_headers) do |faraday|
|
|
190
|
-
faraday.request :url_encoded
|
|
191
|
-
faraday.response :follow_redirects
|
|
192
|
-
faraday.response :destiny_auth
|
|
193
|
-
faraday.response :json
|
|
194
|
-
end
|
|
195
|
-
end
|
|
196
|
-
|
|
197
|
-
def token_header
|
|
198
|
-
{}.tap { |headers| headers['authorization'] = "Bearer #{@access_token}" if @access_token }
|
|
199
|
-
end
|
|
17
|
+
include Api::Authentication
|
|
18
|
+
include Api::Manifest
|
|
19
|
+
include Api::Membership
|
|
20
|
+
include Api::Profile
|
|
21
|
+
include Api::Search
|
|
22
|
+
include Api::Stats
|
|
200
23
|
end
|
metadata
CHANGED
|
@@ -1,85 +1,29 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: restiny
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 6.0.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-
|
|
11
|
+
date: 2023-12-09 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
|
-
name:
|
|
14
|
+
name: httpx
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
17
|
- - "~>"
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '
|
|
19
|
+
version: '1.1'
|
|
20
20
|
type: :runtime
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
24
|
- - "~>"
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: '
|
|
27
|
-
- !ruby/object:Gem::Dependency
|
|
28
|
-
name: faraday
|
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
|
30
|
-
requirements:
|
|
31
|
-
- - "~>"
|
|
32
|
-
- !ruby/object:Gem::Version
|
|
33
|
-
version: '2.0'
|
|
34
|
-
type: :runtime
|
|
35
|
-
prerelease: false
|
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
-
requirements:
|
|
38
|
-
- - "~>"
|
|
39
|
-
- !ruby/object:Gem::Version
|
|
40
|
-
version: '2.0'
|
|
41
|
-
- !ruby/object:Gem::Dependency
|
|
42
|
-
name: faraday-follow_redirects
|
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
|
44
|
-
requirements:
|
|
45
|
-
- - "~>"
|
|
46
|
-
- !ruby/object:Gem::Version
|
|
47
|
-
version: '0.3'
|
|
48
|
-
type: :runtime
|
|
49
|
-
prerelease: false
|
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
-
requirements:
|
|
52
|
-
- - "~>"
|
|
53
|
-
- !ruby/object:Gem::Version
|
|
54
|
-
version: '0.3'
|
|
55
|
-
- !ruby/object:Gem::Dependency
|
|
56
|
-
name: rubyzip
|
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
|
58
|
-
requirements:
|
|
59
|
-
- - "~>"
|
|
60
|
-
- !ruby/object:Gem::Version
|
|
61
|
-
version: '2.3'
|
|
62
|
-
type: :runtime
|
|
63
|
-
prerelease: false
|
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
-
requirements:
|
|
66
|
-
- - "~>"
|
|
67
|
-
- !ruby/object:Gem::Version
|
|
68
|
-
version: '2.3'
|
|
69
|
-
- !ruby/object:Gem::Dependency
|
|
70
|
-
name: sqlite3
|
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
|
72
|
-
requirements:
|
|
73
|
-
- - "~>"
|
|
74
|
-
- !ruby/object:Gem::Version
|
|
75
|
-
version: '1.3'
|
|
76
|
-
type: :runtime
|
|
77
|
-
prerelease: false
|
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
-
requirements:
|
|
80
|
-
- - "~>"
|
|
81
|
-
- !ruby/object:Gem::Version
|
|
82
|
-
version: '1.3'
|
|
26
|
+
version: '1.1'
|
|
83
27
|
description: A gem for interacting with Bungie's Destiny API.
|
|
84
28
|
email:
|
|
85
29
|
- d+restiny@waferbaby.com
|
|
@@ -87,12 +31,16 @@ executables: []
|
|
|
87
31
|
extensions: []
|
|
88
32
|
extra_rdoc_files: []
|
|
89
33
|
files:
|
|
90
|
-
- lib/faraday/destiny/api.rb
|
|
91
|
-
- lib/faraday/destiny/auth.rb
|
|
92
34
|
- lib/restiny.rb
|
|
35
|
+
- lib/restiny/api/authentication.rb
|
|
36
|
+
- lib/restiny/api/base.rb
|
|
37
|
+
- lib/restiny/api/manifest.rb
|
|
38
|
+
- lib/restiny/api/membership.rb
|
|
39
|
+
- lib/restiny/api/profile.rb
|
|
40
|
+
- lib/restiny/api/search.rb
|
|
41
|
+
- lib/restiny/api/stats.rb
|
|
93
42
|
- lib/restiny/constants.rb
|
|
94
43
|
- lib/restiny/errors.rb
|
|
95
|
-
- lib/restiny/manifest.rb
|
|
96
44
|
- lib/restiny/version.rb
|
|
97
45
|
homepage: http://github.com/waferbaby/restiny
|
|
98
46
|
licenses:
|
data/lib/faraday/destiny/api.rb
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'faraday'
|
|
4
|
-
require 'restiny/errors'
|
|
5
|
-
|
|
6
|
-
module Faraday
|
|
7
|
-
module Restiny
|
|
8
|
-
Faraday::Response.register_middleware(destiny_api: 'Faraday::Restiny::Api')
|
|
9
|
-
|
|
10
|
-
class Api < Middleware
|
|
11
|
-
def on_complete(env)
|
|
12
|
-
return if env['response_body'].empty? || !env['response_body']['ErrorCode']
|
|
13
|
-
|
|
14
|
-
if env['response_body']['ErrorCode'] == 1
|
|
15
|
-
env[:body] = env['response_body']['Response']
|
|
16
|
-
return
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
klass =
|
|
20
|
-
case env['status']
|
|
21
|
-
when 400..499
|
|
22
|
-
::Restiny::RequestError
|
|
23
|
-
when 500..599
|
|
24
|
-
::Restiny::ResponseError
|
|
25
|
-
else
|
|
26
|
-
::Restiny::Error
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
raise klass.new(env['response_body']['Message'], env['response_body']['ErrorStatus'])
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
end
|
data/lib/faraday/destiny/auth.rb
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'faraday'
|
|
4
|
-
require 'restiny/errors'
|
|
5
|
-
|
|
6
|
-
module Faraday
|
|
7
|
-
module Restiny
|
|
8
|
-
Faraday::Response.register_middleware(destiny_auth: 'Faraday::Restiny::Auth')
|
|
9
|
-
|
|
10
|
-
class Auth < Middleware
|
|
11
|
-
def on_complete(env)
|
|
12
|
-
return if env['response_body'].empty? || env['url'].to_s !~ /oauth/
|
|
13
|
-
|
|
14
|
-
return unless env['response_body']['error']
|
|
15
|
-
|
|
16
|
-
raise ::Restiny::AuthenticationError.new(
|
|
17
|
-
env['response_body']['error_description'],
|
|
18
|
-
env['response_body']['error']
|
|
19
|
-
)
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
end
|
data/lib/restiny/manifest.rb
DELETED
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'sqlite3'
|
|
4
|
-
|
|
5
|
-
module Restiny
|
|
6
|
-
class Manifest
|
|
7
|
-
ENTITIES = {
|
|
8
|
-
Achievement: %w[achievement achievements],
|
|
9
|
-
Activity: %w[activity activities],
|
|
10
|
-
ActivityGraph: %w[activity_graph activity_graphs],
|
|
11
|
-
ActivityMode: %w[activity_mode activity_modes],
|
|
12
|
-
ActivityModifier: %w[activity_modifier activity_modifiers],
|
|
13
|
-
ActivityType: %w[activity_type activity_types],
|
|
14
|
-
Artifact: %w[artifact artifacts],
|
|
15
|
-
Bond: %w[bonds bonds],
|
|
16
|
-
BreakerType: %w[breaker_type breaker_types],
|
|
17
|
-
Checklist: %w[checklist checklists],
|
|
18
|
-
Class: %w[guardian_class guardian_classes],
|
|
19
|
-
Collectible: %w[collectible collectibles],
|
|
20
|
-
DamageType: %w[damage_type damage_types],
|
|
21
|
-
Destination: %w[destination destinations],
|
|
22
|
-
EnergyType: %w[energy_type energy_types],
|
|
23
|
-
EquipmentSlot: %w[equipment_slot equipment_slots],
|
|
24
|
-
EventCard: %w[event_card event_cards],
|
|
25
|
-
Faction: %w[faction factions],
|
|
26
|
-
Gender: %w[guardian_gender guardian_genders],
|
|
27
|
-
GuardianRank: %w[guardian_rank guardian_ranks],
|
|
28
|
-
GuardianRankConstants: %w[guardian_rank_constant guardian_rank_constants],
|
|
29
|
-
HistoricalStats: %w[historical_stat historical_stats],
|
|
30
|
-
InventoryBucket: %w[inventory_bucket inventory_buckets],
|
|
31
|
-
InventoryItem: %w[inventory_item inventory_items],
|
|
32
|
-
ItemCategory: %w[item_category item_categories],
|
|
33
|
-
ItemTierType: %w[item_tier_type item_tier_types],
|
|
34
|
-
LoadoutColor: %w[loadout_color loadout_colors],
|
|
35
|
-
LoadoutConstants: %w[loadout_constant loadout_constants],
|
|
36
|
-
LoadoutIcon: %w[loadout_icon loadout_icons],
|
|
37
|
-
LoadoutName: %w[loadout_name loadout_names],
|
|
38
|
-
Location: %w[location locations],
|
|
39
|
-
Lore: %w[lore_entry lore_entries],
|
|
40
|
-
MaterialRequirementSet: %w[material_requirement_set material_requirement_sets],
|
|
41
|
-
MedalTier: %w[medal_tier medal_tiers],
|
|
42
|
-
Metric: %w[metric metrics],
|
|
43
|
-
Milestone: %w[milestone milestones],
|
|
44
|
-
Objective: %w[objective objectives],
|
|
45
|
-
Place: %w[place places],
|
|
46
|
-
PlugSet: %w[plug_set plug_sets],
|
|
47
|
-
PowerCap: %w[power_cap power_caps],
|
|
48
|
-
PresentationNode: %w[presentation_node presentation_nodes],
|
|
49
|
-
Progression: %w[progression progressions],
|
|
50
|
-
ProgressionLevelRequirement: %w[progression_level_requirement progression_level_requirements],
|
|
51
|
-
Race: %w[guardian_race guardian_races],
|
|
52
|
-
Record: %w[record records],
|
|
53
|
-
ReportReasonCategory: %w[report_reason_category report_reason_categories],
|
|
54
|
-
RewardSource: %w[reward_source reward_sources],
|
|
55
|
-
SackRewardItemList: %w[sack_reward_item_list sack_reward_item_lists],
|
|
56
|
-
SandboxPattern: %w[sandbox_pattern sandbox_patterns],
|
|
57
|
-
SandboxPerk: %w[sandbox_perk sandbox_perks],
|
|
58
|
-
Season: %w[season seasons],
|
|
59
|
-
SeasonPass: %w[season_pass season_passes],
|
|
60
|
-
SocialCommendation: %w[commendation commendations],
|
|
61
|
-
SocialCommendationNode: %w[commendation_node commendation_nodes],
|
|
62
|
-
SocketCategory: %w[socket_category socket_categories],
|
|
63
|
-
SocketType: %w[socket_type socket_types],
|
|
64
|
-
Stat: %w[stat stats],
|
|
65
|
-
StatGroup: %w[stat_group stat_groups],
|
|
66
|
-
TalentGrid: %w[talent_grid talent_grids],
|
|
67
|
-
Trait: %w[trait traits],
|
|
68
|
-
Unlock: %w[unlock unlocks],
|
|
69
|
-
Vendor: %w[vendor vendors],
|
|
70
|
-
VendorGroup: %w[vendor_group vendor_groups]
|
|
71
|
-
}.freeze
|
|
72
|
-
|
|
73
|
-
attr_reader :version
|
|
74
|
-
|
|
75
|
-
ENTITIES.each do |entity, method_names|
|
|
76
|
-
full_table_name = "Destiny#{entity}Definition"
|
|
77
|
-
single_method_name, plural_method_name = method_names
|
|
78
|
-
|
|
79
|
-
define_method(single_method_name) { |id| fetch_item(table_name: full_table_name, id: id) }
|
|
80
|
-
define_method(plural_method_name) { |limit: nil| fetch_items(table_name: full_table_name, limit: limit) }
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
def initialize(file_path, version)
|
|
84
|
-
if file_path.empty? || !File.exist?(file_path) || !File.file?(file_path)
|
|
85
|
-
raise Restiny::InvalidParamsError, 'You must provide a valid path for the manifest file'
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
@database = SQLite3::Database.new(file_path, results_as_hash: true)
|
|
89
|
-
@version = version
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
private
|
|
93
|
-
|
|
94
|
-
def fetch_item(table_name:, id:)
|
|
95
|
-
query = "SELECT json FROM #{table_name} WHERE json_extract(json, '$.hash')=?"
|
|
96
|
-
result = @database.execute(query, id)
|
|
97
|
-
|
|
98
|
-
JSON.parse(result[0]['json']) unless result.nil? || result.count < 1 || !result[0].include?('json')
|
|
99
|
-
rescue SQLite3::Exception => e
|
|
100
|
-
raise Restiny::RequestError, "Error while fetching item (#{e})"
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
def fetch_items(table_name:, limit: nil)
|
|
104
|
-
bindings = []
|
|
105
|
-
|
|
106
|
-
query = "SELECT json FROM #{table_name} ORDER BY json_extract(json, '$.index')"
|
|
107
|
-
|
|
108
|
-
if limit
|
|
109
|
-
query << ' LIMIT ?'
|
|
110
|
-
bindings << limit
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
items = []
|
|
114
|
-
|
|
115
|
-
@database.execute(query, bindings) do |row|
|
|
116
|
-
item = JSON.parse(row['json'])
|
|
117
|
-
yield item if block_given?
|
|
118
|
-
|
|
119
|
-
items << item
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
items unless block_given?
|
|
123
|
-
rescue SQLite3::Exception => e
|
|
124
|
-
raise Restiny::RequestError, "Error while fetching items (#{e})"
|
|
125
|
-
end
|
|
126
|
-
end
|
|
127
|
-
end
|