psn-api 2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8fd01e29b9e381d0e5c7fff90f9f471daf2ed212c587cb18784246f6a89b4dea
4
+ data.tar.gz: a085e812ec9f4810c510d3e746ff4f3918ebbc1e7ee9a99a244f143c7d992b8a
5
+ SHA512:
6
+ metadata.gz: 866827eb43bc81a4db69c2ec0eaf5099cf7c3dbd342c563649fd775e4f947d32f61b38baa8688f735ec605cb6830475ad4b8eab9de6c1841d1bd6d1389bf58dc
7
+ data.tar.gz: 8e39cab6c3a08cb2ae2060bc11916733e74f10a1df86b0c2a84300eb9e154f2c7952dee75a92a0b1f4a2c3270e692e1d6abab461633a4f7b9207f8fa23ec208e
data/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ # Ignore Gemfile.lock
2
+ #
3
+ *.lock
4
+
5
+ # Ignore generated .gem files
6
+ #
7
+ *.gem
@@ -0,0 +1,76 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, sex characteristics, gender identity and expression,
9
+ level of experience, education, socio-economic status, nationality, personal
10
+ appearance, race, religion, or sexual identity and orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at info@games.directory. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72
+
73
+ [homepage]: https://www.contributor-covenant.org
74
+
75
+ For answers to common questions about this code of conduct, see
76
+ https://www.contributor-covenant.org/faq
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/README.md ADDED
@@ -0,0 +1,35 @@
1
+ # PlayStationNetwork::API
2
+
3
+ Retrieve User Information, Trophies, Game and Store data from the PlayStation Network
4
+
5
+ ## Development
6
+
7
+ While I work on a proper documentation on how to use this gem, please refer to this issue on how to install and use it: https://github.com/games-directory/api-psn/issues/1
8
+
9
+ ## Contributing
10
+
11
+ Bug reports and pull requests are welcome on GitHub at https://github.com/games-directory/api-playstationnetwork/. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
12
+
13
+ ## License
14
+
15
+ The MIT License (MIT)
16
+
17
+ Copyright (c) 2020 Vlad Radulescu, Studio51 Solutions, Studio51 Gaming Solutions, games.directory
18
+
19
+ Permission is hereby granted, free of charge, to any person obtaining a copy
20
+ of this software and associated documentation files (the "Software"), to deal
21
+ in the Software without restriction, including without limitation the rights
22
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
23
+ copies of the Software, and to permit persons to whom the Software is
24
+ furnished to do so, subject to the following conditions:
25
+
26
+ The above copyright notice and this permission notice shall be included in
27
+ all copies or substantial portions of the Software.
28
+
29
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
30
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
31
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
32
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
33
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
34
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
35
+ THE SOFTWARE.
data/bin/console ADDED
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+
5
+ # You can add fixtures and/or initialization code here to make experimenting
6
+ # with your gem easier. You can also use a different console, if you like.
7
+
8
+ # (If you use this, don't forget to add pry to your Gemfile!)
9
+ # require 'pry'
10
+ # Pry.start
11
+
12
+ require 'irb'
13
+ require 'irb/completion'
14
+ require 'psn-api'
15
+
16
+ def reload!
17
+ files = $LOADED_FEATURES.select { |feat| feat =~ /\/play_station_network_api\// }
18
+ files.each { |file| load file }
19
+ end
20
+
21
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,91 @@
1
+ module PlayStationNetworkAPI
2
+ class Catalog < PlayStationNetworkAPI::Client
3
+
4
+ # offset [Integer]
5
+ # limit [Integer] {
6
+ # min: 1,
7
+ # max: 1000
8
+ # }
9
+ def most_purchased(offset: 0, limit: 10)
10
+ raise 'limit must be less than or equal to 1000' if limit > 1000
11
+ warn "[DEPRECATED] This endpoint has been marked as deprecated on the PlayStation API, don't rely on it as things might break in the future!"
12
+
13
+ # https://m.np.playstation.net/api/catalog/v2/concepts/fetch?country=GB&language=en&age=999&limit=1000&offset=1
14
+ get([path, 'concepts', 'fetch'].join('/'),
15
+ query: {
16
+ country: country,
17
+ language: language,
18
+ age: 999,
19
+ limit: limit,
20
+ offset: offset
21
+ }
22
+ ).parsed_response
23
+ end
24
+
25
+ # concept_id [Integer]
26
+ # metadata [Boolean]: Different data is returned when true; stuff like age restrictions, release dates in different countries etc..
27
+ def concept(concept_id, metadata: false)
28
+ options = {}
29
+
30
+ if metadata
31
+ options[:query] = {
32
+ country: country,
33
+ language: language,
34
+ age: 999
35
+ }
36
+ end
37
+
38
+ # https://m.np.playstation.net/api/catalog/v2/concepts/10000470
39
+ # https://m.np.playstation.net/api/catalog/v2/concepts/10000470?country=GB&language=en&age=999
40
+ get([path, 'concepts', concept_id].join('/'), options).parsed_response
41
+ end
42
+
43
+ # concept_id [Integer]
44
+ # offset [Integer]
45
+ # limit [Integer] {
46
+ # min: 1,
47
+ # max: 1000
48
+ # }
49
+ def products(concept_id, offset: 0, limit: 10)
50
+ raise 'limit must be less than or equal to 1000' if limit > 1000
51
+
52
+ # https://m.np.playstation.net/api/catalog/v2/concepts/10000470/products?country=GB&language=en&age=999&limit=1000
53
+ get([path, 'concepts', concept_id, 'products'].join('/'),
54
+ query: {
55
+ country: country,
56
+ language: language,
57
+ age: age,
58
+ limit: limit,
59
+ offset: offset
60
+ }
61
+ ).parsed_response
62
+ end
63
+
64
+ # title_ids [Array[String]]
65
+ def title_ids_to_concept_id(title_ids)
66
+ raise 'limit must be less than or equal to 500' if title_ids.length > 500
67
+
68
+ # https://m.np.playstation.net/api/catalog/v2/titles/[..]/concepts/id
69
+ response = get([path, 'titles', title_ids, 'concepts', 'id'].join('/')).parsed_response
70
+
71
+ return title_ids.length == 1 ? response[0] : response
72
+ end
73
+
74
+ # title_id [String]
75
+ def title(title_id)
76
+ # https://m.np.playstation.net/api/catalog/v2/titles/CUSA18182_00/?country=GB&language=en&age=999
77
+ get([path, 'title', title_id].join('/')).parsed_response
78
+ end
79
+
80
+ # title_id [String]
81
+ def title_age_limit(title_id)
82
+ get([path, 'title', title_id, 'ageLimit'].join('/')).parsed_response
83
+ end
84
+
85
+ private
86
+
87
+ def path
88
+ '/catalog/v2'.freeze
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,152 @@
1
+ require 'httparty'
2
+
3
+ module PlayStationNetworkAPI
4
+ class Client
5
+ VERSION ||= '2.0'
6
+
7
+ include HTTParty
8
+ base_uri 'https://m.np.playstation.net/api'
9
+
10
+ attr_accessor :refresh_token, :default_headers, :account_id, :country, :language, :age
11
+
12
+ def initialize(refresh_token, account_id: 'me', country: 'GB', language: 'en')
13
+ @refresh_token = refresh_token
14
+ @default_headers = {
15
+ # TODO: Make this a variable for other languages
16
+ 'Accept-Language' => 'en-US',
17
+ 'User-Agent' => "psn-api/#{ VERSION }"
18
+ }
19
+
20
+ @account_id = account_id
21
+ @country = country
22
+ @language = language
23
+ end
24
+
25
+ public
26
+
27
+ def catalog
28
+ PlayStationNetworkAPI::Catalog.new(refresh_token,
29
+ account_id: account_id,
30
+ country: country,
31
+ language: language
32
+ )
33
+ end
34
+
35
+ def entitlement
36
+ PlayStationNetworkAPI::Entitlement.new(refresh_token,
37
+ account_id: account_id,
38
+ country: country,
39
+ language: language
40
+ )
41
+ end
42
+
43
+ def explore
44
+ PlayStationNetworkAPI::Explore.new(refresh_token,
45
+ account_id: account_id,
46
+ country: country,
47
+ language: language
48
+ )
49
+ end
50
+
51
+ def game
52
+ PlayStationNetworkAPI::Game.new(refresh_token,
53
+ account_id: account_id,
54
+ country: country,
55
+ language: language
56
+ )
57
+ end
58
+
59
+ def search
60
+ PlayStationNetworkAPI::Search.new(refresh_token,
61
+ account_id: account_id,
62
+ country: country,
63
+ language: language
64
+ )
65
+ end
66
+
67
+ def session
68
+ PlayStationNetworkAPI::Session.new(refresh_token,
69
+ account_id: account_id,
70
+ country: country,
71
+ language: language
72
+ )
73
+ end
74
+
75
+ def trophy
76
+ PlayStationNetworkAPI::Trophy.new(refresh_token,
77
+ account_id: account_id,
78
+ country: country,
79
+ language: language
80
+ )
81
+ end
82
+
83
+ def user
84
+ PlayStationNetworkAPI::User.new(refresh_token,
85
+ account_id: account_id,
86
+ country: country,
87
+ language: language
88
+ )
89
+ end
90
+
91
+ # @private true
92
+ def get_account_id
93
+ self.class.base_uri 'https://dms.api.playstation.com/api'
94
+
95
+ # https://dms.api.playstation.com/api/v1/devices/accounts/me
96
+ get('/v1/devices/accounts/me').parsed_response['accountId']
97
+ end
98
+
99
+ def get_account_devices
100
+ self.class.base_uri 'https://dms.api.playstation.com/api'
101
+
102
+ # https://dms.api.playstation.com/api/v1/devices/accounts/me
103
+ get('/v1/devices/accounts/me').parsed_response['accountDevices']
104
+ end
105
+
106
+ protected
107
+
108
+ def get(url, options = {})
109
+ access_token = PlayStationNetworkAPI::Session.new(refresh_token).access_token
110
+
111
+ base_uri = options[:base_uri]
112
+ options.delete(:base_uri)
113
+
114
+ headers = options[:headers] || {}
115
+ options.delete(:headers)
116
+
117
+ self.class.base_uri base_uri if base_uri
118
+
119
+ self.class.get(url,
120
+ headers: {
121
+ **default_headers,
122
+ 'Authorization' => "Bearer #{ access_token }",
123
+ **headers
124
+ },
125
+ **options
126
+ )
127
+ end
128
+
129
+ def post(url, options = {})
130
+ access_token = PlayStationNetworkAPI::Session.new(refresh_token).access_token
131
+
132
+ self.class.post(url,
133
+ headers: {
134
+ **default_headers,
135
+ 'Content-Type' => 'application/json',
136
+ 'Authorization' => "Bearer #{ access_token }"
137
+ },
138
+ **options
139
+ )
140
+ end
141
+
142
+ private
143
+
144
+ def self.changelog
145
+ # v2.0
146
+ # Implemented the new API that's currently present in the new PlayStation App.
147
+ # There are quite a few endpoints that currently return Access Denied, which probably means they're not active yet
148
+
149
+ # v1.0
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,33 @@
1
+ module PlayStationNetworkAPI
2
+ class Entitlement < PlayStationNetworkAPI::Client
3
+
4
+ # offset [Integer]
5
+ # limit [Integer] {
6
+ # min: 1,
7
+ # max: 500
8
+ # }
9
+ def entitlements(offset: 0, limit: 500)
10
+ raise 'limit must be less than or equal to 500' if limit > 500
11
+
12
+ # https://m.np.playstation.net/api/entitlement/v2/users/me/internal/entitlements
13
+ get(path,
14
+ query: {
15
+ limit: limit,
16
+ offset: offset
17
+ }
18
+ ).parsed_response
19
+ end
20
+
21
+ # entitlement_id [String]
22
+ def entitlement(entitlement_id)
23
+ # https://m.np.playstation.net/api/entitlement/v2/users/me/internal/entitlements/EP0700-NPEB90430_00-RRUDEMOEU0000100
24
+ get([path, entitlement_id].join('/').parsed_response)
25
+ end
26
+
27
+ private
28
+
29
+ def path
30
+ '/entitlement/v2/users/me/internal/entitlements'.freeze
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,15 @@
1
+ module PlayStationNetworkAPI
2
+ class Explore < PlayStationNetworkAPI::Client
3
+
4
+ def hub
5
+ # https://m.np.playstation.net/api/explore/v1/users/6462910331343535058/hub
6
+ get([path, account_id, 'hub'].join('/')).parsed_response
7
+ end
8
+
9
+ private
10
+
11
+ def path
12
+ '/explore/v1/users'.freeze
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,55 @@
1
+ module PlayStationNetworkAPI
2
+ class Game < PlayStationNetworkAPI::Client
3
+
4
+ # @private false
5
+ # offset [Integer]
6
+ # limit [Integer] {
7
+ # min: 1,
8
+ # max: 300
9
+ # }
10
+ def titles(offset: 0, limit: 10)
11
+ raise 'limit must be less than or equal to 300' if limit > 300
12
+
13
+ # https://m.np.playstation.net/api/gamelist/v2/users/6462910331343535058/titles?limit=300&offset=0
14
+ get([path, account_id, 'titles'].join('/'),
15
+ query: {
16
+ offset: offset,
17
+ limit: limit
18
+ }
19
+ ).parsed_response
20
+ end
21
+
22
+ # @private false
23
+ # title_id [String]
24
+ def title(title_id)
25
+ # https://m.np.playstation.net/api/gamelist/v2/users/6462910331343535058/titles/CUSA00938_00
26
+ get([path, account_id, 'titles', title_id].join('/')).parsed_response
27
+ end
28
+
29
+ # COMMENT: Pretty much the same endpoint as [titles], except it gives a lot less data and there's no conceptId information
30
+ #
31
+ # @private false
32
+ # offset [Integer]
33
+ # limit [Integer] {
34
+ # min: 1,
35
+ # max: 300
36
+ # }
37
+ def games(offset: 0, limit: 10)
38
+ raise 'limit must be less than or equal to 300' if limit > 300
39
+
40
+ # https://m.np.playstation.net/api/gamelist/v3/users/6462910331343535058/profile/games?limit=300&offset=0
41
+ get([path(version: 3), account_id, 'profile', 'games'].join('/'),
42
+ query: {
43
+ offset: offset,
44
+ limit: limit
45
+ }
46
+ ).parsed_response
47
+ end
48
+
49
+ private
50
+
51
+ def path(version: 2, resource: 'users')
52
+ "/gamelist/v#{ version }/#{ resource }/"
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,86 @@
1
+ module PlayStationNetworkAPI
2
+ class Search < PlayStationNetworkAPI::Client
3
+ BUCKETS ||= {
4
+ BLENDED: 'Blended',
5
+ CONCEPT_GAME: 'ConceptGame',
6
+ CONCEPT_GAME_ALL: 'ConceptGameAll',
7
+ CONCEPT_GAME_MOBILE_APP: 'ConceptGameMobileApp',
8
+ CONCEPT_GAME_PS_NOW: 'ConceptGamePsNow',
9
+ CONCEPT_GAME_EXCLUDING_PS_NOW: 'ConceptGameExcludingPsNow',
10
+ CONCEPT_GAME_ADDONS: 'ConceptGameAddons',
11
+ CONCEPT_APPLICATION_GAME: 'ConceptApplicationGame',
12
+ CONCEPT_APPLICATION_VIDEO: 'ConceptApplicationVideo',
13
+ CONCEPT_OTHER_NON_GAME: 'ConceptOtherNonGame',
14
+ VIDEO_CATALOG: 'VideoCatalog',
15
+ VIDEO_CATALOG_MOVIE: 'VideoCatalogMovie',
16
+ VIDEO_CATALOG_TV_SERIES: 'VideoCatalogTVSeries',
17
+ SOCIAL_ALL_ACCOUNTS: 'SocialAllAccounts',
18
+ SOCIAL_FRIENDS: 'SocialFriends',
19
+ SOCIAL_FRIENDS_OF_FRIENDS: 'SocialFriendsOfFriends',
20
+ SOCIAL_FRIENDS_AND_FRIENDS_OF_FRIENDS: 'SocialFriendsAndFriendsOfFriends',
21
+ SOCIAL_UNRELATED_AND_FRIENDS_OF_FRIENDS: 'SocialUnrelatedAndFriendsOfFriends',
22
+ SOCIAL_UNRELATED: 'SocialUnrelated'
23
+ }.freeze
24
+
25
+ # Returns a list of buckets available to pass to .query(domains: [])
26
+ #
27
+ def buckets
28
+ BUCKETS.keys
29
+ end
30
+
31
+ # query [String]
32
+ # limit [Integer] {
33
+ # min: 1,
34
+ # max: 50
35
+ # }
36
+ # country [String]
37
+ # language [String]
38
+ def query(query, offset: 0, limit: 50, buckets: [:CONCEPT_GAME_MOBILE_APP])
39
+ search_domains = []
40
+
41
+ buckets.each do |bucket|
42
+ search_domains.push({
43
+ "domain": BUCKETS[bucket.to_sym],
44
+ "pagination": {
45
+
46
+ # TODO: It seems to be a Java GraphQL pagination thingy, when present it breaks the offset.
47
+ # So let's leave it out for now as we don't really need to paginate
48
+ # "cursor": "10"
49
+
50
+ "pageSize": limit,
51
+ "offset": offset
52
+ }
53
+ })
54
+ end
55
+
56
+ # https://m.np.playstation.net/api/search/v1/universalSearch
57
+ request = post('/search/v1/universalSearch',
58
+ body: {
59
+ "domainRequests": search_domains,
60
+ "age": "999",
61
+ "countryCode": country,
62
+ "searchTerm": query,
63
+ "languageCode": language
64
+ }.to_json
65
+ )
66
+
67
+ buckets = []
68
+
69
+ request.dig('domainResponses').map do |domain_response|
70
+ bucket = {}
71
+ bucket[domain_response['domain']] = {
72
+ total_results: domain_response['totalResultCount'],
73
+ results: domain_response['results']
74
+ }
75
+
76
+ buckets.push(bucket)
77
+ end
78
+
79
+ return {
80
+ query: request['prefix'],
81
+ suggestions: request['suggestions'],
82
+ buckets: buckets,
83
+ }
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,94 @@
1
+ require 'httparty'
2
+
3
+ module PlayStationNetworkAPI
4
+ class Session
5
+ include HTTParty
6
+ base_uri 'https://ca.account.sony.com/api'
7
+
8
+ DEFAULT_SCOPES ||= ['psn:clientapp', 'psn:mobile.v1'].join(' ').freeze
9
+ CLIENT_ID ||= 'ac8d161a-d966-4728-b0ea-ffec22f69edc'.freeze
10
+
11
+ attr_accessor :token
12
+
13
+ def initialize(token)
14
+ @token = token
15
+ end
16
+
17
+ def authenticate
18
+ code, cid = oauth_authorize
19
+ oauth_token(code)
20
+ end
21
+
22
+ alias_method :refresh, :authenticate
23
+
24
+ def access_token
25
+ oauth_token['access_token']
26
+ end
27
+
28
+ def expiration_date
29
+ (Time.now + oauth_token['refresh_token_expires_in']).to_datetime.to_s
30
+ end
31
+
32
+ def expired?
33
+ expiration_date < DateTime.now.to_s
34
+ end
35
+
36
+ # private
37
+
38
+ def oauth_authorize
39
+ request = self.class.get('/authz/v3/oauth/authorize',
40
+ headers: {
41
+ 'Cookie' => HTTParty::CookieHash.new().add_cookies({ npsso: token }).to_cookie_string
42
+ },
43
+
44
+ query: {
45
+ 'access_type' => 'offline',
46
+ 'client_id' => CLIENT_ID,
47
+ 'redirect_uri' => 'com.playstation.PlayStationApp://redirect',
48
+ 'response_type' => 'code',
49
+ 'scope' => DEFAULT_SCOPES
50
+ },
51
+
52
+ follow_redirects: false
53
+ )
54
+
55
+ if (location = request.headers['location'])
56
+ code = location.match(/code=([A-Za-z0-9:\?_\-\.\/=]+)/)[1]
57
+ cid = location.match(/cid=([A-Za-z0-9:\?_\-\.\/=]+)/)[1]
58
+
59
+ return code, cid
60
+ end
61
+ end
62
+
63
+ def oauth_token(code = nil)
64
+ body = {}
65
+
66
+ if code
67
+ body = {
68
+ 'code' => code,
69
+ 'grant_type' => 'authorization_code',
70
+ 'redirect_uri' => 'com.playstation.PlayStationApp://redirect'
71
+ }
72
+ else
73
+ body = {
74
+ 'refresh_token' => token,
75
+ 'grant_type' => 'refresh_token'
76
+ }
77
+ end
78
+
79
+ self.class.post('/authz/v3/oauth/token',
80
+ headers: {
81
+ 'Host' => 'ca.account.sony.com',
82
+ 'Referer' => 'https://my.playstation.com/',
83
+ 'Authorization' => 'Basic YWM4ZDE2MWEtZDk2Ni00NzI4LWIwZWEtZmZlYzIyZjY5ZWRjOkRFaXhFcVhYQ2RYZHdqMHY='
84
+ },
85
+
86
+ body: {
87
+ 'scope' => DEFAULT_SCOPES,
88
+ **body,
89
+ 'token_format' => 'jwt'
90
+ }
91
+ ).parsed_response
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,111 @@
1
+ module PlayStationNetworkAPI
2
+ class Trophy < PlayStationNetworkAPI::Client
3
+ # TODO: Test against PS5 titles, using 'trophy2' as the npServiceName
4
+
5
+ def summary
6
+ # https://m.np.playstation.net/api/trophy/v1/users/6462910331343535058/trophySummary
7
+ get([path, 'trophySummary'].join('/')).parsed_response
8
+ end
9
+
10
+ # title_ids [Array[String]]
11
+ # offset [Integer]
12
+ # limit [Integer] {
13
+ # min: 1,
14
+ # max: 1000
15
+ # }
16
+ def titles(title_ids = [], offset: 0, limit: 10)
17
+ raise 'title_ids size must be less than or equal to 5' if title_ids.length > 5
18
+ raise 'limit must be less than or equal to 5000' if limit > 5000
19
+
20
+ if title_ids.empty?
21
+ # https://m.np.playstation.net/api/trophy/v1/users/6462910331343535058/trophyTitles
22
+ get([path, 'trophyTitles'].join('/'),
23
+ query: {
24
+ offset: offset,
25
+ limit: limit
26
+ }
27
+ ).parsed_response
28
+
29
+ # TODO: Cast every response to a Model
30
+ # return PlayStationNetworkAPI::Models::Trophy.new(request.parsed_response)
31
+ else
32
+ # https://m.np.playstation.net/api/trophy/v1/users/6462910331343535058/titles/trophyTitles?npTitleIds=CUSA00054_00,CUSA07970_00,CUSA00208_00,CUSA00938_00,CUSA01985_00
33
+
34
+ get([path, 'titles', 'trophyTitles'].join('/'),
35
+ query: {
36
+ npTitleIds: title_ids.join(',')
37
+ }
38
+ ).parsed_response
39
+ end
40
+ end
41
+
42
+ # communication_id [String]
43
+ def trophy_groups(communication_id)
44
+ url = if account_id
45
+ # https://m.np.playstation.net/api/trophy/v1/users/6462910331343535058/npCommunicationIds/NPWR00133_00/trophyGroups?npServiceName=trophy
46
+ [path, 'npCommunicationIds', communication_id, 'trophyGroups']
47
+ else
48
+ # https://m.np.playstation.net/api/trophy/v1/npCommunicationIds/NPWR00133_00/trophyGroups?npServiceName=trophy
49
+ [path(communication_id), 'trophyGroups']
50
+ end
51
+
52
+ get(url.join('/'),
53
+ query: {
54
+ npServiceName: 'trophy'
55
+ }
56
+ ).parsed_response
57
+ end
58
+
59
+ # communication_id [String]
60
+ # trophy_group_id [String | Integer] => 'default', '001', 002
61
+ def trophies(communication_id, trophy_group_id = nil)
62
+ # BROKEN: https://m.np.playstation.net/api/trophy/v1/users/6462910331343535058/npCommunicationIds/NPWR00133_00/trophies?npServiceName=trophy
63
+ # https://m.np.playstation.net/api/trophy/v1/users/6462910331343535058/npCommunicationIds/NPWR00133_00/trophyGroups/default/trophies?npServiceName=trophy
64
+ get([path, 'npCommunicationIds', communication_id, 'trophyGroups', ([trophy_group_id, 'trophies'] if trophy_group_id)].flatten.compact.join('/'),
65
+ query: {
66
+ npServiceName: 'trophy'
67
+ }
68
+ ).parsed_response
69
+ end
70
+
71
+ # communication_id [String]
72
+ # trophy_id [Integer]
73
+ def trophy_details(communication_id, trophy_id)
74
+ url = if account_id
75
+ # https://m.np.playstation.net/api/trophy/v1/npCommunicationIds/NPWR00133_00/trophies/2?npServiceName=trophy
76
+ [path, 'npCommunicationIds', communication_id, 'trophies', trophy_id]
77
+ else
78
+ # https://m.np.playstation.net/api/trophy/v1/users/6462910331343535058/npCommunicationIds/NPWR00133_00/trophies/2?npServiceName=trophy
79
+ [path(communication_id), 'trophies', trophy_id]
80
+ end
81
+
82
+ get(url.join('/'),
83
+ query: {
84
+ npServiceName: 'trophy'
85
+ }
86
+ ).parsed_response
87
+ end
88
+
89
+ # title_ids Array[[String]]
90
+ def get_communication_id(title_ids = [])
91
+ # https://gb-tpy.np.community.playstation.net/trophy/v1/apps/trophyTitles?npTitleIds=CUSA15010_00,CUSA20046_00,CUSA24894_00,CUSA24269_00,CUSA24267_00
92
+ get('/trophy/v1/apps/trophyTitles',
93
+ base_uri: 'https://gb-tpy.np.community.playstation.net',
94
+ query: {
95
+ npTitleIds: title_ids.join(',')
96
+ },
97
+
98
+ ).parsed_response
99
+ end
100
+
101
+ private
102
+
103
+ def path(communication_id = nil)
104
+ if communication_id
105
+ "/trophy/v1/npCommunicationIds/#{ communication_id }"
106
+ else
107
+ "/trophy/v1/users/#{ account_id }"
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,95 @@
1
+ module PlayStationNetworkAPI
2
+ class User < PlayStationNetworkAPI::Client
3
+
4
+ # @private false
5
+ def profile
6
+ # https://m.np.playstation.net/api/userProfile/v1/internal/users/6462910331343535058/profiles
7
+ get([path, account_id, 'profiles'].join('/')).parsed_response
8
+ end
9
+
10
+ # @private false
11
+ # account_ids [Array[Integer]]
12
+ def profiles(account_ids)
13
+ raise 'account_ids size must be less than or equal to 100' if account_ids.length > 100
14
+
15
+ # https://m.np.playstation.net/api/userProfile/v1/internal/users/profiles?accountIds=6462910331343535058
16
+ get([path, 'profiles'].join('/'),
17
+ query: {
18
+ accountIds: account_ids.split.join(',')
19
+ }
20
+ ).parsed_response
21
+ end
22
+
23
+ # @private false
24
+ def presence
25
+ # https://m.np.playstation.net/api/userProfile/v1/internal/users/6462910331343535058/basicPresences?type=primary
26
+ get([path, account_id, 'basicPresences'].join('/'),
27
+ query: {
28
+ type: 'primary'
29
+ }
30
+ ).parsed_response
31
+ end
32
+
33
+ # @private false
34
+ # account_ids [Array[Integer]]
35
+ def presences(account_ids)
36
+ raise 'account_ids size must be less than or equal to 100' if account_ids.length > 100
37
+
38
+ # https://m.np.playstation.net/api/userProfile/v1/internal/users/me/basicPresences?type=primary
39
+ get([path, 'basicPresences'].join('/'),
40
+ query: {
41
+ type: 'primary',
42
+ accountIds: account_ids.split.join(',')
43
+ }
44
+ ).parsed_response
45
+ end
46
+
47
+ # @private false
48
+ # limit [Integer] {
49
+ # min: 1,
50
+ # max: 1000
51
+ # }
52
+ def friends(limit: 1000)
53
+ raise 'limit must be less than or equal to 1000' if limit > 1000
54
+
55
+ # https://m.np.playstation.net/api/userProfile/v1/internal/users/me/friends?limit=1000
56
+ get([path, account_id, 'friends'].join('/'),
57
+ query: {
58
+ limit: limit
59
+ }
60
+ ).parsed_response
61
+ end
62
+
63
+ # @private true
64
+ # friend_account_id [Integer]
65
+ def friendship(friend_account_id)
66
+ # https://m.np.playstation.net/api/userProfile/v1/internal/users/me/friends/9014970682312518995/summary
67
+ get([path, 'me', 'friends', friend_account_id, 'summary'].join('/')).parsed_response
68
+ end
69
+
70
+ # @private true
71
+ # type [Symbol] => :received, :sent
72
+ def friend_requests(type: :received)
73
+ # https://m.np.playstation.net/api/userProfile/v1/internal/users/me/friends/9014970682312518995/summary
74
+ get([path, 'me', 'friends', "#{ type.to_s }Requests"].join('/')).parsed_response
75
+ end
76
+
77
+ # @private true
78
+ def blocks
79
+ # https://m.np.playstation.net/api/userProfile/v1/internal/users/me/blocks
80
+ get([path, 'me', 'blocks'].join('/')).parsed_response
81
+ end
82
+
83
+ # @private true
84
+ def available_to_play
85
+ # https://m.np.playstation.net/api/userProfile/v1/internal/users/me/friends/subscribing/availableToPlay
86
+ get([path, 'me', 'friends', 'subscribing', 'availableToPlay'].join('/')).parsed_response
87
+ end
88
+
89
+ private
90
+
91
+ def path
92
+ '/userProfile/v1/internal/users'.freeze
93
+ end
94
+ end
95
+ end
data/lib/psn-api.rb ADDED
@@ -0,0 +1,14 @@
1
+ module PlayStationNetworkAPI
2
+ # Dir[File.dirname(__FILE__) + '/play_station_network_api/*.rb'].each do |file|
3
+ # end
4
+ require 'play_station_network_api/session.rb'
5
+ require 'play_station_network_api/client.rb'
6
+ require 'play_station_network_api/catalog.rb'
7
+ require 'play_station_network_api/entitlement.rb'
8
+ require 'play_station_network_api/explore.rb'
9
+ require 'play_station_network_api/game.rb'
10
+ require 'play_station_network_api/search.rb'
11
+ require 'play_station_network_api/session.rb'
12
+ require 'play_station_network_api/trophy.rb'
13
+ require 'play_station_network_api/user.rb'
14
+ end
data/psn-api.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+
3
+ # require './lib/play_station_network_api/client'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'psn-api'
7
+ spec.version = '2.0'#PlayStationNetworkAPI::Client::VERSION
8
+ spec.authors = ['Vlad Radulescu']
9
+ spec.email = ['oss@games.directory']
10
+
11
+ spec.summary = %q{ Ruby wrapper for the PlayStation Network API }
12
+ spec.description = %q{ Use the new PlayStation Network API to get a User's Profile, Trophies, Games and more.. }
13
+ spec.homepage = 'https://playstation.games.directory'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files`.split("\n")
17
+ spec.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ spec.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_dependency 'httparty', '~> 0.16'
22
+
23
+ spec.add_development_dependency 'bundler', '~> 2'
24
+ spec.add_development_dependency 'rake', '~> 12.3'
25
+ end
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: psn-api
3
+ version: !ruby/object:Gem::Version
4
+ version: '2.0'
5
+ platform: ruby
6
+ authors:
7
+ - Vlad Radulescu
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-02-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: httparty
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.16'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.16'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '12.3'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '12.3'
55
+ description: " Use the new PlayStation Network API to get a User's Profile, Trophies,
56
+ Games and more.. "
57
+ email:
58
+ - oss@games.directory
59
+ executables:
60
+ - console
61
+ - setup
62
+ extensions: []
63
+ extra_rdoc_files: []
64
+ files:
65
+ - ".gitignore"
66
+ - CODE_OF_CONDUCT.md
67
+ - Gemfile
68
+ - README.md
69
+ - bin/console
70
+ - bin/setup
71
+ - lib/play_station_network_api/catalog.rb
72
+ - lib/play_station_network_api/client.rb
73
+ - lib/play_station_network_api/entitlement.rb
74
+ - lib/play_station_network_api/explore.rb
75
+ - lib/play_station_network_api/game.rb
76
+ - lib/play_station_network_api/search.rb
77
+ - lib/play_station_network_api/session.rb
78
+ - lib/play_station_network_api/trophy.rb
79
+ - lib/play_station_network_api/user.rb
80
+ - lib/psn-api.rb
81
+ - psn-api.gemspec
82
+ homepage: https://playstation.games.directory
83
+ licenses:
84
+ - MIT
85
+ metadata: {}
86
+ post_install_message:
87
+ rdoc_options: []
88
+ require_paths:
89
+ - lib
90
+ required_ruby_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ requirements: []
101
+ rubygems_version: 3.1.4
102
+ signing_key:
103
+ specification_version: 4
104
+ summary: Ruby wrapper for the PlayStation Network API
105
+ test_files: []