restiny 3.1.0 → 5.0.1
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/faraday/destiny/api.rb +10 -8
- data/lib/faraday/destiny/auth.rb +12 -10
- data/lib/restiny/api/authentication.rb +33 -0
- data/lib/restiny/api/base.rb +66 -0
- data/lib/restiny/api/manifest.rb +54 -0
- data/lib/restiny/api/membership.rb +28 -0
- data/lib/restiny/api/profile.rb +41 -0
- data/lib/restiny/api/search.rb +31 -0
- data/lib/restiny/api/stats.rb +19 -0
- data/lib/restiny/constants.rb +87 -11
- data/lib/restiny/manifest.rb +12 -25
- data/lib/restiny/version.rb +1 -1
- data/lib/restiny.rb +15 -195
- metadata +30 -92
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 19b9e9cb2183fde785169d457628f4f7517c74feed228aacee4d45cdfe901fff
|
4
|
+
data.tar.gz: bebc650be6668d5a46a284a2aeb6912f2309cce7a7100ec4de57d3a95b11805e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c4fbe785ad70936712f351e1f3453f1915220af599ea9d89a09dc3c67713b208c6232e12d52d20fb25153c6a9b04b706b4bf1b367a2c8ded96a79da10f9764af
|
7
|
+
data.tar.gz: ed736dce127c0c76cebec7051b89ba02505452a4f9d2aa6bce6beb836b8fc6169586a6970fea1db3387db7700e3cee2b032edcb9952abd7ba9f85940e1a320e4
|
data/lib/faraday/destiny/api.rb
CHANGED
@@ -1,21 +1,23 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'faraday'
|
4
|
+
require 'restiny/errors'
|
3
5
|
|
4
6
|
module Faraday
|
5
7
|
module Restiny
|
6
|
-
Faraday::Response.register_middleware(destiny_api:
|
8
|
+
Faraday::Response.register_middleware(destiny_api: 'Faraday::Restiny::Api')
|
7
9
|
|
8
10
|
class Api < Middleware
|
9
11
|
def on_complete(env)
|
10
|
-
return if env[
|
12
|
+
return if env['response_body'].empty? || !env['response_body']['ErrorCode']
|
11
13
|
|
12
|
-
if env[
|
13
|
-
env[:body] = env[
|
14
|
+
if env['response_body']['ErrorCode'] == 1
|
15
|
+
env[:body] = env['response_body']['Response']
|
14
16
|
return
|
15
17
|
end
|
16
18
|
|
17
19
|
klass =
|
18
|
-
case env[
|
20
|
+
case env['status']
|
19
21
|
when 400..499
|
20
22
|
::Restiny::RequestError
|
21
23
|
when 500..599
|
@@ -24,7 +26,7 @@ module Faraday
|
|
24
26
|
::Restiny::Error
|
25
27
|
end
|
26
28
|
|
27
|
-
raise klass.new(env[
|
29
|
+
raise klass.new(env['response_body']['Message'], env['response_body']['ErrorStatus'])
|
28
30
|
end
|
29
31
|
end
|
30
32
|
end
|
data/lib/faraday/destiny/auth.rb
CHANGED
@@ -1,20 +1,22 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'faraday'
|
4
|
+
require 'restiny/errors'
|
3
5
|
|
4
6
|
module Faraday
|
5
7
|
module Restiny
|
6
|
-
Faraday::Response.register_middleware(destiny_auth:
|
8
|
+
Faraday::Response.register_middleware(destiny_auth: 'Faraday::Restiny::Auth')
|
7
9
|
|
8
10
|
class Auth < Middleware
|
9
11
|
def on_complete(env)
|
10
|
-
return if env[
|
12
|
+
return if env['response_body'].empty? || env['url'].to_s !~ /oauth/
|
13
|
+
|
14
|
+
return unless env['response_body']['error']
|
11
15
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
)
|
17
|
-
end
|
16
|
+
raise ::Restiny::AuthenticationError.new(
|
17
|
+
env['response_body']['error_description'],
|
18
|
+
env['response_body']['error']
|
19
|
+
)
|
18
20
|
end
|
19
21
|
end
|
20
22
|
end
|
@@ -0,0 +1,33 @@
|
|
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
|
+
def get_authorise_url(redirect_url: nil, state: nil)
|
13
|
+
check_oauth_client_id
|
14
|
+
|
15
|
+
@oauth_state = state || SecureRandom.hex(15)
|
16
|
+
|
17
|
+
params = { response_type: 'code', client_id: @oauth_client_id, state: @oauth_state }
|
18
|
+
params['redirect_url'] = redirect_url unless redirect_url.nil?
|
19
|
+
|
20
|
+
auth_connection.build_url("#{BUNGIE_URL}/en/oauth/authorize/", params).to_s
|
21
|
+
end
|
22
|
+
|
23
|
+
def request_access_token(code:, redirect_url: nil)
|
24
|
+
check_oauth_client_id
|
25
|
+
|
26
|
+
params = { code: code, grant_type: 'authorization_code', client_id: @oauth_client_id }
|
27
|
+
params['redirect_url'] = redirect_url unless redirect_url.nil?
|
28
|
+
|
29
|
+
auth_connection.post('app/oauth/token/', params).body
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'restiny/constants'
|
4
|
+
require 'restiny/errors'
|
5
|
+
require 'faraday'
|
6
|
+
require 'faraday/follow_redirects'
|
7
|
+
require 'faraday/destiny/api'
|
8
|
+
require 'faraday/destiny/auth'
|
9
|
+
|
10
|
+
module Restiny
|
11
|
+
BUNGIE_URL = 'https://www.bungie.net'
|
12
|
+
API_BASE_URL = "#{BUNGIE_URL}/platform".freeze
|
13
|
+
|
14
|
+
attr_accessor :api_key, :oauth_state, :oauth_client_id, :access_token, :user_agent
|
15
|
+
|
16
|
+
module Api
|
17
|
+
module Base
|
18
|
+
def api_get(endpoint:, params: {})
|
19
|
+
api_connection.get(endpoint, params, token_header).body
|
20
|
+
end
|
21
|
+
|
22
|
+
def api_post(endpoint:, params: {})
|
23
|
+
api_connection.post(endpoint, params, token_header).body
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def check_oauth_client_id
|
29
|
+
raise Restiny::RequestError, 'You need to set an OAuth client ID' unless @oauth_client_id
|
30
|
+
end
|
31
|
+
|
32
|
+
def default_headers
|
33
|
+
{ 'User-Agent': @user_agent || "restiny v#{Restiny::VERSION}" }
|
34
|
+
end
|
35
|
+
|
36
|
+
def api_connection
|
37
|
+
raise Restiny::InvalidParamsError, 'You need to set an API key' unless @api_key
|
38
|
+
|
39
|
+
@api_connection ||=
|
40
|
+
Faraday.new(
|
41
|
+
url: API_BASE_URL,
|
42
|
+
headers: default_headers.merge('X-API-KEY': @api_key)
|
43
|
+
) do |faraday|
|
44
|
+
faraday.request :json
|
45
|
+
faraday.response :follow_redirects
|
46
|
+
faraday.response :destiny_api
|
47
|
+
faraday.response :json
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def auth_connection
|
52
|
+
@auth_connection ||=
|
53
|
+
Faraday.new(url: API_BASE_URL, headers: default_headers) do |faraday|
|
54
|
+
faraday.request :url_encoded
|
55
|
+
faraday.response :follow_redirects
|
56
|
+
faraday.response :destiny_auth
|
57
|
+
faraday.response :json
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def token_header
|
62
|
+
{}.tap { |headers| headers['authorization'] = "Bearer #{@access_token}" if @access_token }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
require 'restiny/manifest'
|
6
|
+
|
7
|
+
require 'down'
|
8
|
+
require 'tmpdir'
|
9
|
+
require 'zip'
|
10
|
+
|
11
|
+
module Restiny
|
12
|
+
module Api
|
13
|
+
module Manifest
|
14
|
+
include Base
|
15
|
+
|
16
|
+
def download_manifest(locale: 'en', force_download: false)
|
17
|
+
result = api_get(endpoint: 'Destiny2/Manifest/')
|
18
|
+
raise Restiny::ResponseError, 'Unable to determine manifest details' if result.nil?
|
19
|
+
|
20
|
+
return manifests[locale] if !force_download && manifest_version?(locale, result['version'])
|
21
|
+
|
22
|
+
manifests[locale] = download_manifest_by_url(result.dig('mobileWorldContentPaths', locale), result['version'])
|
23
|
+
end
|
24
|
+
|
25
|
+
def download_manifest_by_url(url, version)
|
26
|
+
raise Restiny::RequestError, 'Unknown locale' if url.nil?
|
27
|
+
|
28
|
+
database_file_path = extract_manifest_from_zip_file(Down.download(BUNGIE_URL + url), version)
|
29
|
+
|
30
|
+
Restiny::Manifest.new(database_file_path, version)
|
31
|
+
rescue Down::Error => e
|
32
|
+
raise Restiny::NetworkError.new('Unable to download the manifest file', e.response.code)
|
33
|
+
end
|
34
|
+
|
35
|
+
def extract_manifest_from_zip_file(source_path, version)
|
36
|
+
Zip::File.open(source_path) do |zip_file|
|
37
|
+
File.join(Dir.tmpdir, "#{version}.en.content.db").tap do |path|
|
38
|
+
zip_file.first.extract(path) unless File.exist?(path)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
rescue Zip::Error => e
|
42
|
+
raise Restiny::Error, "Unable to unzip the manifest file (#{e})"
|
43
|
+
end
|
44
|
+
|
45
|
+
def manifests
|
46
|
+
@manifests ||= {}
|
47
|
+
end
|
48
|
+
|
49
|
+
def manifest_version?(locale, version)
|
50
|
+
manifests[locale] && manifests[locale].version == version
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,28 @@
|
|
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
|
+
|
13
|
+
api_get(endpoint: "User/GetMembershipsById/#{membership_id}/#{membership_type}/")
|
14
|
+
end
|
15
|
+
|
16
|
+
def get_primary_user_membership(membership_id:, use_fallback: true)
|
17
|
+
result = get_user_memberships_by_id(membership_id: membership_id)
|
18
|
+
return nil if result.nil? || result['primaryMembershipId'].nil?
|
19
|
+
|
20
|
+
result['destinyMemberships'].each do |membership|
|
21
|
+
return membership if membership['membershipID'] == result['primaryMembershipId']
|
22
|
+
end
|
23
|
+
|
24
|
+
result['destinyMemberships'][0] if use_fallback
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,41 @@
|
|
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
|
+
if !components.is_a?(Array) || components.empty?
|
12
|
+
raise Restiny::InvalidParamsError, 'Please provide at least one component'
|
13
|
+
end
|
14
|
+
|
15
|
+
url = "Destiny2/#{membership_type}/Profile/#{membership_id}/"
|
16
|
+
url += type_url if type_url
|
17
|
+
url += "?components=#{components.join(',')}"
|
18
|
+
|
19
|
+
api_get(endpoint: url)
|
20
|
+
end
|
21
|
+
|
22
|
+
def get_character_profile(character_id:, membership_id:, membership_type:, components:)
|
23
|
+
get_profile(
|
24
|
+
membership_id: membership_id,
|
25
|
+
membership_type: membership_type,
|
26
|
+
components: components,
|
27
|
+
type_url: "Character/#{character_id}/"
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
def get_instanced_item_profile(item_id:, membership_id:, membership_type:, components:)
|
32
|
+
get_profile(
|
33
|
+
membership_id: membership_id,
|
34
|
+
membership_type: membership_type,
|
35
|
+
components: components,
|
36
|
+
type_url: "Item/#{item_id}/"
|
37
|
+
)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,31 @@
|
|
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,
|
14
|
+
'You must provide a valid Bungie name'
|
15
|
+
end
|
16
|
+
|
17
|
+
api_post(
|
18
|
+
endpoint: "Destiny2/SearchDestinyPlayerByBungieName/#{membership_type}/",
|
19
|
+
params: {
|
20
|
+
displayName: display_name,
|
21
|
+
displayNameCode: display_name_code
|
22
|
+
}
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
def search_users_by_global_name(name:, page: 0)
|
27
|
+
api_post(endpoint: "User/Search/GlobalName/#{page}/", params: { displayNamePrefix: name })
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
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
|
+
api_get(endpoint: "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,100 @@ 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 typos of ammunition 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
|
-
CHARACTERS =
|
60
|
-
CHARACTER_EQUIPMENT =
|
61
|
-
CHARACTER_INVENTORIES =
|
62
|
-
CHARACTER_LOADOUTS =
|
63
|
-
PROFILES =
|
64
|
-
PROFILE_INVENTORIES =
|
65
|
-
ITEM_INSTANCES =
|
66
|
-
ITEM_SOCKETS =
|
67
|
-
ITEM_COMMON_DATA =
|
68
|
-
ITEM_PLUG_STATES =
|
69
|
-
ITEM_REUSABLE_PLUGS =
|
135
|
+
CHARACTERS = 'Characters'
|
136
|
+
CHARACTER_EQUIPMENT = 'CharacterEquipment'
|
137
|
+
CHARACTER_INVENTORIES = 'CharacterInventories'
|
138
|
+
CHARACTER_LOADOUTS = 'CharacterLoadouts'
|
139
|
+
PROFILES = 'Profiles'
|
140
|
+
PROFILE_INVENTORIES = 'ProfileInventories'
|
141
|
+
ITEM_INSTANCES = 'ItemInstances'
|
142
|
+
ITEM_SOCKETS = 'ItemSockets'
|
143
|
+
ITEM_COMMON_DATA = 'ItemCommonData'
|
144
|
+
ITEM_PLUG_STATES = 'ItemPlugStates'
|
145
|
+
ITEM_REUSABLE_PLUGS = 'ItemReusablePlugs'
|
70
146
|
end
|
71
147
|
end
|
data/lib/restiny/manifest.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require 'sqlite3'
|
4
4
|
|
5
5
|
module Restiny
|
6
6
|
class Manifest
|
@@ -68,49 +68,36 @@ module Restiny
|
|
68
68
|
Unlock: %w[unlock unlocks],
|
69
69
|
Vendor: %w[vendor vendors],
|
70
70
|
VendorGroup: %w[vendor_group vendor_groups]
|
71
|
-
}
|
71
|
+
}.freeze
|
72
72
|
|
73
|
-
attr_reader :
|
73
|
+
attr_reader :version
|
74
74
|
|
75
75
|
ENTITIES.each do |entity, method_names|
|
76
76
|
full_table_name = "Destiny#{entity}Definition"
|
77
77
|
single_method_name, plural_method_name = method_names
|
78
78
|
|
79
|
-
define_method
|
80
|
-
|
81
|
-
end
|
82
|
-
|
83
|
-
define_method plural_method_name do |limit: nil|
|
84
|
-
fetch_items(table_name: full_table_name, limit: limit)
|
85
|
-
end
|
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) }
|
86
81
|
end
|
87
82
|
|
88
83
|
def initialize(file_path, version)
|
89
84
|
if file_path.empty? || !File.exist?(file_path) || !File.file?(file_path)
|
90
|
-
raise Restiny::InvalidParamsError
|
85
|
+
raise Restiny::InvalidParamsError, 'You must provide a valid path for the manifest file'
|
91
86
|
end
|
92
87
|
|
93
88
|
@database = SQLite3::Database.new(file_path, results_as_hash: true)
|
94
|
-
@file_path = file_path
|
95
89
|
@version = version
|
96
90
|
end
|
97
91
|
|
98
92
|
private
|
99
93
|
|
100
|
-
def get_entity_names
|
101
|
-
query = "SELECT name from sqlite_schema WHERE name LIKE 'Destiny%'"
|
102
|
-
@database.execute(query).map { |row| row["name"].gsub(/(Destiny|Definition)/, "") }
|
103
|
-
end
|
104
|
-
|
105
94
|
def fetch_item(table_name:, id:)
|
106
95
|
query = "SELECT json FROM #{table_name} WHERE json_extract(json, '$.hash')=?"
|
107
96
|
result = @database.execute(query, id)
|
108
97
|
|
109
|
-
|
110
|
-
|
111
|
-
JSON.parse(result[0]["json"])
|
98
|
+
JSON.parse(result[0]['json']) unless result.nil? || result.count < 1 || !result[0].include?('json')
|
112
99
|
rescue SQLite3::Exception => e
|
113
|
-
raise Restiny::RequestError
|
100
|
+
raise Restiny::RequestError, "Error while fetching item (#{e})"
|
114
101
|
end
|
115
102
|
|
116
103
|
def fetch_items(table_name:, limit: nil)
|
@@ -119,14 +106,14 @@ module Restiny
|
|
119
106
|
query = "SELECT json FROM #{table_name} ORDER BY json_extract(json, '$.index')"
|
120
107
|
|
121
108
|
if limit
|
122
|
-
query <<
|
109
|
+
query << ' LIMIT ?'
|
123
110
|
bindings << limit
|
124
111
|
end
|
125
112
|
|
126
113
|
items = []
|
127
114
|
|
128
115
|
@database.execute(query, bindings) do |row|
|
129
|
-
item = JSON.parse(row[
|
116
|
+
item = JSON.parse(row['json'])
|
130
117
|
yield item if block_given?
|
131
118
|
|
132
119
|
items << item
|
@@ -134,7 +121,7 @@ module Restiny
|
|
134
121
|
|
135
122
|
items unless block_given?
|
136
123
|
rescue SQLite3::Exception => e
|
137
|
-
raise Restiny::RequestError
|
124
|
+
raise Restiny::RequestError, "Error while fetching items (#{e})"
|
138
125
|
end
|
139
126
|
end
|
140
127
|
end
|
data/lib/restiny/version.rb
CHANGED
data/lib/restiny.rb
CHANGED
@@ -2,202 +2,22 @@
|
|
2
2
|
|
3
3
|
$LOAD_PATH.unshift(__dir__)
|
4
4
|
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
|
10
|
-
require
|
11
|
-
require
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
require "down"
|
16
|
-
require "json"
|
17
|
-
require "securerandom"
|
18
|
-
require "zip"
|
19
|
-
|
5
|
+
require 'restiny/version'
|
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_post("app/oauth/token/", params)
|
48
|
-
end
|
49
|
-
|
50
|
-
# Manifest methods
|
51
|
-
|
52
|
-
def get_manifest(locale: "en", force_download: false)
|
53
|
-
result = api_get("Destiny2/Manifest/")
|
54
|
-
raise Restiny::ResponseError.new("Unable to determine manifest details") if result.nil?
|
55
|
-
|
56
|
-
live_version = result.dig("version")
|
57
|
-
|
58
|
-
@manifests ||= {}
|
59
|
-
@manifest_versions ||= {}
|
60
|
-
|
61
|
-
if force_download || @manifests[locale].nil? || @manifest_versions[locale] != live_version
|
62
|
-
url = BUNGIE_URL + result.dig("mobileWorldContentPaths", locale)
|
63
|
-
|
64
|
-
zipped_file = Down.download(url)
|
65
|
-
database_file_path = zipped_file.path + ".db"
|
66
|
-
|
67
|
-
Zip::File.open(zipped_file) { |file| file.first.extract(database_file_path) }
|
68
|
-
|
69
|
-
@manifests[locale] = Manifest.new(database_file_path, live_version)
|
70
|
-
@manifest_versions[locale] = live_version
|
71
|
-
end
|
72
|
-
|
73
|
-
@manifests[locale]
|
74
|
-
rescue Down::Error => error
|
75
|
-
raise Restiny::NetworkError.new("Unable to download the manifest file", error.response.code)
|
76
|
-
rescue Zip::Error => error
|
77
|
-
raise Restiny::Error.new("Unable to unzip the manifest file (#{error})")
|
78
|
-
end
|
79
|
-
|
80
|
-
# Profile and related methods
|
81
|
-
|
82
|
-
def get_profile(membership_id:, membership_type:, components:, type_url: nil)
|
83
|
-
if !components.is_a?(Array) || components.empty?
|
84
|
-
raise Restiny::InvalidParamsError.new("Please provide at least one component")
|
85
|
-
end
|
86
|
-
|
87
|
-
url = "Destiny2/#{membership_type}/Profile/#{membership_id}/"
|
88
|
-
url += type_url if type_url
|
89
|
-
url += "?components=#{components.join(",")}"
|
90
|
-
|
91
|
-
api_get(url)
|
92
|
-
end
|
93
|
-
|
94
|
-
def get_character_profile(character_id:, membership_id:, membership_type:, components:)
|
95
|
-
get_profile(
|
96
|
-
membership_id: membership_id,
|
97
|
-
membership_type: membership_type,
|
98
|
-
components: components,
|
99
|
-
type_url: "Character/#{character_id}/"
|
100
|
-
)
|
101
|
-
end
|
102
|
-
|
103
|
-
def get_instanced_item_profile(item_id:, membership_id:, membership_type:, components:)
|
104
|
-
get_profile(
|
105
|
-
membership_id: membership_id,
|
106
|
-
membership_type: membership_type,
|
107
|
-
components: components,
|
108
|
-
type_url: "Item/#{item_id}/"
|
109
|
-
)
|
110
|
-
end
|
111
|
-
|
112
|
-
# User methods.
|
113
|
-
|
114
|
-
def get_user_memberships_by_id(membership_id, membership_type: Platform::ALL)
|
115
|
-
raise Restiny::InvalidParamsError.new("Please provide a membership ID") if membership_id.nil?
|
116
|
-
api_get("User/GetMembershipsById/#{membership_id}/#{membership_type}/")
|
117
|
-
end
|
118
|
-
|
119
|
-
def get_primary_membership(parent_membership_id, use_fallback: true)
|
120
|
-
result = get_user_memberships_by_id(parent_membership_id)
|
121
|
-
return nil if result.nil? || result["primaryMembershipId"].nil?
|
122
|
-
|
123
|
-
result["destinyMemberships"].each do |membership|
|
124
|
-
return membership if membership["membershipID"] == result["primaryMembershipId"]
|
125
|
-
end
|
126
|
-
|
127
|
-
return result["destinyMemberships"][0] if use_fallback
|
128
|
-
end
|
129
|
-
|
130
|
-
def search_player_by_bungie_name(name, membership_type: Platform::ALL)
|
131
|
-
display_name, display_name_code = name.split("#")
|
132
|
-
if display_name.nil? || display_name_code.nil?
|
133
|
-
raise Restiny::InvalidParamsError.new("You must provide a valid Bungie name")
|
134
|
-
end
|
135
|
-
|
136
|
-
api_post(
|
137
|
-
"Destiny2/SearchDestinyPlayerByBungieName/#{membership_type}/",
|
138
|
-
params: {
|
139
|
-
displayName: display_name,
|
140
|
-
displayNameCode: display_name_code
|
141
|
-
}
|
142
|
-
)
|
143
|
-
end
|
144
|
-
|
145
|
-
def search_users_by_global_name(name:, page: 0)
|
146
|
-
api_post("User/Search/GlobalName/#{page}/", params: { displayNamePrefix: name })
|
147
|
-
end
|
148
|
-
|
149
|
-
# General request methods
|
150
|
-
|
151
|
-
def api_get(url, params: {})
|
152
|
-
api_connection.get(url, params, token_header).body
|
153
|
-
end
|
154
|
-
|
155
|
-
def api_post(url, params: {})
|
156
|
-
api_connection.post(url, params, token_header).body
|
157
|
-
end
|
158
|
-
|
159
|
-
def auth_post(url, params)
|
160
|
-
auth_connection.post(url, params, "Content-Type" => "application/x-www-form-urlencoded").body
|
161
|
-
end
|
162
|
-
|
163
|
-
private
|
164
|
-
|
165
|
-
def check_oauth_client_id
|
166
|
-
raise Restiny::RequestError.new("You need to set an OAuth client ID") unless @oauth_client_id
|
167
|
-
end
|
168
|
-
|
169
|
-
def default_headers
|
170
|
-
{ "User-Agent": @user_agent || "restiny v#{Restiny::VERSION}" }
|
171
|
-
end
|
172
|
-
|
173
|
-
def api_connection
|
174
|
-
raise Restiny::InvalidParamsError.new("You need to set an API key") unless @api_key
|
175
|
-
|
176
|
-
@connection ||=
|
177
|
-
Faraday.new(
|
178
|
-
url: API_BASE_URL,
|
179
|
-
headers: default_headers.merge("X-API-KEY": @api_key)
|
180
|
-
) do |faraday|
|
181
|
-
faraday.request :url_encoded
|
182
|
-
faraday.request :json
|
183
|
-
faraday.response :follow_redirects
|
184
|
-
faraday.response :destiny_api
|
185
|
-
faraday.response :json
|
186
|
-
end
|
187
|
-
end
|
188
|
-
|
189
|
-
def auth_connection
|
190
|
-
@auth_connection ||=
|
191
|
-
Faraday.new(url: API_BASE_URL, headers: default_headers) do |faraday|
|
192
|
-
faraday.request :url_encoded
|
193
|
-
faraday.request :json
|
194
|
-
faraday.response :follow_redirects
|
195
|
-
faraday.response :destiny_auth
|
196
|
-
faraday.response :json
|
197
|
-
end
|
198
|
-
end
|
199
|
-
|
200
|
-
def token_header
|
201
|
-
{}.tap { |headers| headers["authorization"] = "Bearer #{@oauth_token}" if @oauth_token }
|
202
|
-
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
|
203
23
|
end
|
metadata
CHANGED
@@ -1,71 +1,57 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: restiny
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 5.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel Bogan
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-11-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - "~>"
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '2.0'
|
20
|
-
type: :runtime
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - "~>"
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: '2.0'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: faraday-follow_redirects
|
14
|
+
name: down
|
29
15
|
requirement: !ruby/object:Gem::Requirement
|
30
16
|
requirements:
|
31
17
|
- - "~>"
|
32
18
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
19
|
+
version: '5.4'
|
34
20
|
type: :runtime
|
35
21
|
prerelease: false
|
36
22
|
version_requirements: !ruby/object:Gem::Requirement
|
37
23
|
requirements:
|
38
24
|
- - "~>"
|
39
25
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
26
|
+
version: '5.4'
|
41
27
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
28
|
+
name: faraday
|
43
29
|
requirement: !ruby/object:Gem::Requirement
|
44
30
|
requirements:
|
45
31
|
- - "~>"
|
46
32
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
33
|
+
version: '2.0'
|
48
34
|
type: :runtime
|
49
35
|
prerelease: false
|
50
36
|
version_requirements: !ruby/object:Gem::Requirement
|
51
37
|
requirements:
|
52
38
|
- - "~>"
|
53
39
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
40
|
+
version: '2.0'
|
55
41
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
42
|
+
name: faraday-follow_redirects
|
57
43
|
requirement: !ruby/object:Gem::Requirement
|
58
44
|
requirements:
|
59
45
|
- - "~>"
|
60
46
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
47
|
+
version: '0.3'
|
62
48
|
type: :runtime
|
63
49
|
prerelease: false
|
64
50
|
version_requirements: !ruby/object:Gem::Requirement
|
65
51
|
requirements:
|
66
52
|
- - "~>"
|
67
53
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
54
|
+
version: '0.3'
|
69
55
|
- !ruby/object:Gem::Dependency
|
70
56
|
name: rubyzip
|
71
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -81,75 +67,19 @@ dependencies:
|
|
81
67
|
- !ruby/object:Gem::Version
|
82
68
|
version: '2.3'
|
83
69
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
85
|
-
requirement: !ruby/object:Gem::Requirement
|
86
|
-
requirements:
|
87
|
-
- - "~>"
|
88
|
-
- !ruby/object:Gem::Version
|
89
|
-
version: '13.0'
|
90
|
-
type: :development
|
91
|
-
prerelease: false
|
92
|
-
version_requirements: !ruby/object:Gem::Requirement
|
93
|
-
requirements:
|
94
|
-
- - "~>"
|
95
|
-
- !ruby/object:Gem::Version
|
96
|
-
version: '13.0'
|
97
|
-
- !ruby/object:Gem::Dependency
|
98
|
-
name: rspec
|
99
|
-
requirement: !ruby/object:Gem::Requirement
|
100
|
-
requirements:
|
101
|
-
- - "~>"
|
102
|
-
- !ruby/object:Gem::Version
|
103
|
-
version: '3.12'
|
104
|
-
type: :development
|
105
|
-
prerelease: false
|
106
|
-
version_requirements: !ruby/object:Gem::Requirement
|
107
|
-
requirements:
|
108
|
-
- - "~>"
|
109
|
-
- !ruby/object:Gem::Version
|
110
|
-
version: '3.12'
|
111
|
-
- !ruby/object:Gem::Dependency
|
112
|
-
name: rubocop-rspec
|
113
|
-
requirement: !ruby/object:Gem::Requirement
|
114
|
-
requirements:
|
115
|
-
- - "~>"
|
116
|
-
- !ruby/object:Gem::Version
|
117
|
-
version: '2.22'
|
118
|
-
type: :development
|
119
|
-
prerelease: false
|
120
|
-
version_requirements: !ruby/object:Gem::Requirement
|
121
|
-
requirements:
|
122
|
-
- - "~>"
|
123
|
-
- !ruby/object:Gem::Version
|
124
|
-
version: '2.22'
|
125
|
-
- !ruby/object:Gem::Dependency
|
126
|
-
name: standard
|
127
|
-
requirement: !ruby/object:Gem::Requirement
|
128
|
-
requirements:
|
129
|
-
- - "~>"
|
130
|
-
- !ruby/object:Gem::Version
|
131
|
-
version: '1.28'
|
132
|
-
type: :development
|
133
|
-
prerelease: false
|
134
|
-
version_requirements: !ruby/object:Gem::Requirement
|
135
|
-
requirements:
|
136
|
-
- - "~>"
|
137
|
-
- !ruby/object:Gem::Version
|
138
|
-
version: '1.28'
|
139
|
-
- !ruby/object:Gem::Dependency
|
140
|
-
name: vcr
|
70
|
+
name: sqlite3
|
141
71
|
requirement: !ruby/object:Gem::Requirement
|
142
72
|
requirements:
|
143
73
|
- - "~>"
|
144
74
|
- !ruby/object:Gem::Version
|
145
|
-
version: '
|
146
|
-
type: :
|
75
|
+
version: '1.3'
|
76
|
+
type: :runtime
|
147
77
|
prerelease: false
|
148
78
|
version_requirements: !ruby/object:Gem::Requirement
|
149
79
|
requirements:
|
150
80
|
- - "~>"
|
151
81
|
- !ruby/object:Gem::Version
|
152
|
-
version: '
|
82
|
+
version: '1.3'
|
153
83
|
description: A gem for interacting with Bungie's Destiny API.
|
154
84
|
email:
|
155
85
|
- d+restiny@waferbaby.com
|
@@ -160,6 +90,13 @@ files:
|
|
160
90
|
- lib/faraday/destiny/api.rb
|
161
91
|
- lib/faraday/destiny/auth.rb
|
162
92
|
- lib/restiny.rb
|
93
|
+
- lib/restiny/api/authentication.rb
|
94
|
+
- lib/restiny/api/base.rb
|
95
|
+
- lib/restiny/api/manifest.rb
|
96
|
+
- lib/restiny/api/membership.rb
|
97
|
+
- lib/restiny/api/profile.rb
|
98
|
+
- lib/restiny/api/search.rb
|
99
|
+
- lib/restiny/api/stats.rb
|
163
100
|
- lib/restiny/constants.rb
|
164
101
|
- lib/restiny/errors.rb
|
165
102
|
- lib/restiny/manifest.rb
|
@@ -167,24 +104,25 @@ files:
|
|
167
104
|
homepage: http://github.com/waferbaby/restiny
|
168
105
|
licenses:
|
169
106
|
- MIT
|
170
|
-
metadata:
|
171
|
-
|
107
|
+
metadata:
|
108
|
+
rubygems_mfa_required: 'true'
|
109
|
+
post_install_message:
|
172
110
|
rdoc_options: []
|
173
111
|
require_paths:
|
174
112
|
- lib
|
175
113
|
required_ruby_version: !ruby/object:Gem::Requirement
|
176
114
|
requirements:
|
177
|
-
- - "
|
115
|
+
- - ">"
|
178
116
|
- !ruby/object:Gem::Version
|
179
|
-
version: '0'
|
117
|
+
version: '3.0'
|
180
118
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
181
119
|
requirements:
|
182
120
|
- - ">="
|
183
121
|
- !ruby/object:Gem::Version
|
184
122
|
version: '0'
|
185
123
|
requirements: []
|
186
|
-
rubygems_version: 3.
|
187
|
-
signing_key:
|
124
|
+
rubygems_version: 3.2.33
|
125
|
+
signing_key:
|
188
126
|
specification_version: 4
|
189
127
|
summary: A Destiny API gem
|
190
128
|
test_files: []
|