steam_api_client 0.9.1 → 0.10.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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +47 -1
  3. data/README.md +25 -5
  4. data/lib/steam_api_client/errors/api_error.rb +29 -0
  5. data/lib/steam_api_client/errors/missing_api_token.rb +11 -0
  6. data/lib/steam_api_client/failure_handler.rb +3 -0
  7. data/lib/steam_api_client/request.rb +13 -6
  8. data/lib/steam_api_client/requestors/app_global_achievements.rb +3 -3
  9. data/lib/steam_api_client/requestors/app_news.rb +1 -1
  10. data/lib/steam_api_client/requestors/game_current_players.rb +1 -1
  11. data/lib/steam_api_client/requestors/player_achievements.rb +1 -1
  12. data/lib/steam_api_client/requestors/player_friends.rb +1 -1
  13. data/lib/steam_api_client/requestors/player_game_stats.rb +1 -1
  14. data/lib/steam_api_client/requestors/player_level.rb +1 -1
  15. data/lib/steam_api_client/requestors/player_owned_games.rb +1 -1
  16. data/lib/steam_api_client/requestors/player_recent_games.rb +1 -1
  17. data/lib/steam_api_client/requestors/player_steamid.rb +1 -1
  18. data/lib/steam_api_client/requestors/player_summaries.rb +1 -1
  19. data/lib/steam_api_client/response.rb +9 -1
  20. data/lib/steam_api_client/structs/app_global_achievements.rb +2 -2
  21. data/lib/steam_api_client/structs/app_new.rb +1 -1
  22. data/lib/steam_api_client/structs/player_achievements.rb +1 -1
  23. data/lib/steam_api_client/structs/player_friends.rb +1 -1
  24. data/lib/steam_api_client/structs/player_game_stats.rb +9 -1
  25. data/lib/steam_api_client/structs/player_owned_games.rb +11 -5
  26. data/lib/steam_api_client/structs/player_recent_games.rb +1 -1
  27. data/lib/steam_api_client/structs/player_summary.rb +15 -3
  28. data/lib/steam_api_client/structs/requests/get_app_details.rb +1 -7
  29. data/lib/steam_api_client/structs/requests/get_app_global_achievements.rb +1 -7
  30. data/lib/steam_api_client/structs/requests/get_app_news.rb +1 -7
  31. data/lib/steam_api_client/structs/requests/get_game_current_players.rb +1 -7
  32. data/lib/steam_api_client/structs/requests/get_player_achievements.rb +1 -7
  33. data/lib/steam_api_client/structs/requests/get_player_friends.rb +2 -8
  34. data/lib/steam_api_client/structs/requests/get_player_game_stats.rb +1 -7
  35. data/lib/steam_api_client/structs/requests/get_player_level.rb +1 -7
  36. data/lib/steam_api_client/structs/requests/get_player_owned_games.rb +1 -7
  37. data/lib/steam_api_client/structs/requests/get_player_recent_games.rb +1 -7
  38. data/lib/steam_api_client/structs/requests/get_player_steamid.rb +1 -7
  39. data/lib/steam_api_client/structs/requests/get_player_summaries.rb +1 -7
  40. data/lib/steam_api_client/structs/types.rb +13 -4
  41. data/lib/steam_api_client/version.rb +1 -1
  42. data/lib/steam_api_client.rb +2 -0
  43. metadata +7 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f7fbb426d064f384de34ab6414afea4442d800e5f45e29afd04afedc3ea6c126
4
- data.tar.gz: 9e4ecb25dea5bcbe1543e9b8562cc020701f5e1ca6853d913889d8584d21afc5
3
+ metadata.gz: d0c5680dd7c5f04050858684ffdedba92a405af03105ff67d5ab5f83786a9be3
4
+ data.tar.gz: 18dace5a415dbae06ab33d22af1d8d2f89a692c18b33d0fde7632e9608860476
5
5
  SHA512:
6
- metadata.gz: 925f466b43889aba96d7897f9eb4f88698cc9bfbf6f634caa2270519b0d3c386dcd24c9c372f1394c2dc265d200a478c294f0c1b04862c8e263c2440860b1cb8
7
- data.tar.gz: 1939102b827494c98f1dea87f8484f54e2152aec29ec38c8ddaad3098afb63dc05c72ace861cbdc16474ab52ac2c396ef6653de6d2c4042d717844d59f5a0ae8
6
+ metadata.gz: '008e3f030129225f200f25063f45bc1df84a0abc0aba381d1c2a919ef99f9cfebba87a908a14ebfc6652a0d9ea9dfdd76e8a7dc5299762df648b03423879dc07'
7
+ data.tar.gz: 886f409484facc5b94264c94a488e07d85bcf0514c84b81b9541f395da769d53d9407b8af83b2dc8cfcf45cb8bae81a5044a4d8542074c3940a3cec9ccfa2a71
data/CHANGELOG.md CHANGED
@@ -6,6 +6,51 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.10.0] - 2026-10-23
10
+
11
+ ### Added
12
+
13
+ - `player_game_stats` exposes the game's stats: `playerstats.stats`, a list of `name`/`value` pairs. Values keep the Integer or Float Steam sends (#71).
14
+ - `PlayerSummary` exposes `realname`, `timecreated`, `loccountrycode`, `avatarhash`, `primaryclanid` (an Integer, like other SteamIDs) and `personastateflags`. They're `nil` when the profile doesn't make them public (#71).
15
+ - `SteamApiClient::Errors::HttpTooManyRequests` (429), `HttpBadGateway` (502) and `HttpServiceUnavailable` (503), so rate limits and outages can be retried selectively. 429 and 503 errors expose `#retry_after`, the seconds from the `Retry-After` header (`nil` when absent). All three are `ApiError`s, like before (#27).
16
+ - `SteamApiClient::Errors::MissingApiToken`, raised before any request when an endpoint needs an API token and the client has none (nil or empty). Previously the request was sent and Steam's HTML "key is missing" page came back as `HttpBadRequest` (#25).
17
+
18
+ ### Changed
19
+
20
+ - Every response struct, including the nested element structs (`PlayerSummary`, `AppNew`, friends, games and achievements), inherits from `SteamApiClient::Structs::Base`, so they all accept string keys and respond to `build_from_response` (#28).
21
+ - GET requests (every endpoint) no longer send an empty JSON body (`"{}"`) and `Content-Type: application/json` (#26).
22
+ - Missing or invalid request parameters raise `ArgumentError` instead of a bare `StandardError` (#25). `ArgumentError` is a `StandardError`, so existing `rescue StandardError` code keeps working.
23
+ - **Breaking:** every `retrieve` method takes keyword arguments (#24), so IDs can't be swapped by position. Defaults are unchanged.
24
+
25
+ | Before | After |
26
+ |---|---|
27
+ | `player_summaries.retrieve([id])` | `player_summaries.retrieve(steamids: [id])` |
28
+ | `player_steamid.retrieve("name")` | `player_steamid.retrieve(vanityurl: "name")` |
29
+ | `player_friends.retrieve(id, "friend")` | `player_friends.retrieve(steamid: id, relationship: "friend")` |
30
+ | `player_level.retrieve(id)` | `player_level.retrieve(steamid: id)` |
31
+ | `player_owned_games.retrieve(id, include_appinfo: true)` | `player_owned_games.retrieve(steamid: id, include_appinfo: true)` |
32
+ | `player_recent_games.retrieve(id, 10)` | `player_recent_games.retrieve(steamid: id, count: 10)` |
33
+ | `player_achievements.retrieve(appid, id)` | `player_achievements.retrieve(appid: appid, steamid: id)` |
34
+ | `player_game_stats.retrieve(appid, id)` | `player_game_stats.retrieve(appid: appid, steamid: id)` |
35
+ | `app_news.retrieve(appid, 5, 500)` | `app_news.retrieve(appid: appid, count: 5, maxlength: 500)` |
36
+ | `app_global_achievements.retrieve(appid)` | `app_global_achievements.retrieve(appid: appid)` |
37
+ | `game_current_players.retrieve(appid)` | `game_current_players.retrieve(appid: appid)` |
38
+
39
+ ### Fixed
40
+
41
+ - `player_owned_games` failed on real public libraries: it required `playtime_windows_forever`, `playtime_mac_forever`, `playtime_linux_forever` and `rtime_last_played`, which Steam doesn't send. They're optional now, and `playtime_2weeks`, `has_community_visible_stats`, `has_leaderboards` and `content_descriptorids` are exposed (#70).
42
+ - Responses with duplicate JSON keys parse the same on every `json` gem version, with the last value winning. json 3 rejects duplicate keys by default, which would have raised `ResponseParseError` (#19).
43
+
44
+ ### Changed
45
+
46
+ - Allows `dry-configurable` 1.2 and newer (`~> 1.1` instead of `~> 1.1.0`, which only allowed 1.1.x) (#19).
47
+ - **Breaking:** requires Ruby 3.3 or newer (#17). Ruby 3.0, 3.1 and 3.2 have reached end of life. CI now tests 3.3, 3.4 and 4.0.
48
+ - **Breaking:** response structs now validate their declared types (#15). Integer, Float and String fields convert Steam's values (Steam often sends numbers as strings), and a value that can't be converted raises `SteamApiClient::Errors::ResponseParseError` instead of passing through unchanged. `null` values still come through as `nil`.
49
+ - `PlayerSummary#steamid` is now an Integer, like every other SteamID in the gem (it was a String).
50
+ - `PlayerSummary#profilestate` is now an Integer (it was declared String; Steam sends `1`).
51
+ - Global achievement `percent` is now a Float (it held Steam's raw value, e.g. `"49.6"`).
52
+ - The gem homepage and README links point to `github.com/jpechaves/steam_api` instead of the old `sosolidkk` account (#22).
53
+
9
54
  ## [0.9.1] - 2026-10-23
10
55
 
11
56
  ### Fixed
@@ -97,6 +142,7 @@ The versions below were bumped in `version.rb` during development but **never pu
97
142
 
98
143
  - Initial gem skeleton.
99
144
 
100
- [Unreleased]: https://github.com/jpechaves/steam_api/compare/v0.9.1...HEAD
145
+ [Unreleased]: https://github.com/jpechaves/steam_api/compare/v0.10.0...HEAD
146
+ [0.10.0]: https://github.com/jpechaves/steam_api/compare/v0.9.1...v0.10.0
101
147
  [0.9.1]: https://github.com/jpechaves/steam_api/compare/v0.9.0...v0.9.1
102
148
  [0.9.0]: https://github.com/jpechaves/steam_api/releases/tag/v0.9.0
data/README.md CHANGED
@@ -14,6 +14,7 @@
14
14
  - [Running the Interactive Console](#running-the-interactive-console)
15
15
  - [Running Tests](#running-tests)
16
16
  - [Code Quality](#code-quality)
17
+ - [Releasing](#releasing)
17
18
  - [Contributing](#contributing)
18
19
  - [How to Contribute](#how-to-contribute)
19
20
  - [License](#license)
@@ -95,7 +96,7 @@ client = SteamApiClient::Client.new(api_token: "your-API-token-value", timeout:
95
96
 
96
97
  ```ruby
97
98
  client = SteamApiClient::Client.new(api_token: "your-API-token-value")
98
- client.player_summaries.retrieve([76561198160009756])
99
+ client.player_summaries.retrieve(steamids: [76561198160009756])
99
100
  ```
100
101
 
101
102
  #### Get App Details
@@ -111,8 +112,7 @@ For more examples, see the [Usage](#usage) section above.
111
112
 
112
113
  ### Requirements
113
114
 
114
- - Ruby >= 3.0
115
- - Docker (optional, for consistent development environments)
115
+ - Ruby >= 3.3
116
116
 
117
117
  ### Initial Setup
118
118
 
@@ -146,9 +146,29 @@ We use [standardrb](https://github.com/standardrb/standard) for linting and code
146
146
  $ bin/lint
147
147
  ```
148
148
 
149
+ ### Releasing
150
+
151
+ Each pull request with a user-visible change adds a line under `## [Unreleased]` in `CHANGELOG.md`. Pull requests don't change the version.
152
+
153
+ To release the merged changes, open a release pull request:
154
+
155
+ ```bash
156
+ $ git checkout -b release/0.10.0
157
+ $ bin/bump minor # or patch / major: bumps version.rb and Gemfile.lock, moves [Unreleased] under "## [0.10.0] - <today>"
158
+ ```
159
+
160
+ Commit, open the pull request, and merge it. Then publish from `main`:
161
+
162
+ ```bash
163
+ $ bin/release # build and push the gem (asks for your RubyGems MFA code), then tag v<version> and push the tag
164
+ $ bin/release --dry-run # show what would happen without building, pushing or tagging
165
+ ```
166
+
167
+ `bin/release` checks out and pulls `main` first, and refuses to run with uncommitted changes or without a CHANGELOG entry. Steps that are already done are skipped, so running it again for a released version does nothing.
168
+
149
169
  ## Contributing
150
170
 
151
- Bug reports and pull requests are welcome on GitHub at [GitHub Repository](https://github.com/sosolidkk/steam_api). This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Code of Conduct](https://github.com/sosolidkk/steam_api/blob/main/CODE_OF_CONDUCT.md).
171
+ Bug reports and pull requests are welcome on GitHub at [GitHub Repository](https://github.com/jpechaves/steam_api). This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Code of Conduct](https://github.com/jpechaves/steam_api/blob/main/CODE_OF_CONDUCT.md).
152
172
 
153
173
  ### How to Contribute
154
174
 
@@ -164,4 +184,4 @@ This gem is available as open source under the terms of the [MIT License](https:
164
184
 
165
185
  ## Code of Conduct
166
186
 
167
- Everyone interacting in the Steam project's codebases, issue trackers, chat rooms, and mailing lists is expected to follow the [Code of Conduct](https://github.com/sosolidkk/steam_api/blob/main/CODE_OF_CONDUCT.md).
187
+ Everyone interacting in the Steam project's codebases, issue trackers, chat rooms, and mailing lists is expected to follow the [Code of Conduct](https://github.com/jpechaves/steam_api/blob/main/CODE_OF_CONDUCT.md).
@@ -44,5 +44,34 @@ module SteamApiClient
44
44
 
45
45
  # status_code: 500
46
46
  class HttpInternalServerError < ApiError; end
47
+
48
+ # Reads the Retry-After header of rate limited / unavailable responses
49
+ module RetryAfter
50
+ # Seconds to wait before retrying. Retry-After is either a number of
51
+ # seconds or an HTTP date.
52
+ # @return [Integer, nil] nil when the header is missing or invalid
53
+ def retry_after
54
+ value = response.headers&.[]("Retry-After").to_s.strip
55
+ return Integer(value, 10) if value.match?(/\A\d+\z/)
56
+ return if value.empty?
57
+
58
+ [(Time.httpdate(value) - Time.now).ceil, 0].max
59
+ rescue ArgumentError
60
+ nil
61
+ end
62
+ end
63
+
64
+ # status_code: 429. Steam rate limits frequent requests.
65
+ class HttpTooManyRequests < ApiError
66
+ include RetryAfter
67
+ end
68
+
69
+ # status_code: 502
70
+ class HttpBadGateway < ApiError; end
71
+
72
+ # status_code: 503
73
+ class HttpServiceUnavailable < ApiError
74
+ include RetryAfter
75
+ end
47
76
  end
48
77
  end
@@ -0,0 +1,11 @@
1
+ module SteamApiClient
2
+ module Errors
3
+ # Raised before a request is sent when the endpoint needs an API token
4
+ # and the client has none
5
+ class MissingApiToken < ::StandardError
6
+ def initialize(message = "This endpoint needs a Steam Web API token. Pass Client.new(api_token: ...) or set it with Client.configure { |config| config.api_token = ... }.")
7
+ super
8
+ end
9
+ end
10
+ end
11
+ end
@@ -30,12 +30,15 @@ module SteamApiClient
30
30
  when 401 then raise SteamApiClient::Errors::HttpUnauthorized, response
31
31
  when 403 then raise SteamApiClient::Errors::HttpForbidden, response
32
32
  when 404 then raise SteamApiClient::Errors::HttpNotFound, response
33
+ when 429 then raise SteamApiClient::Errors::HttpTooManyRequests, response
33
34
  end
34
35
  end
35
36
 
36
37
  def raise_response_5xx(response)
37
38
  case response.status_code
38
39
  when 500 then raise SteamApiClient::Errors::HttpInternalServerError, response
40
+ when 502 then raise SteamApiClient::Errors::HttpBadGateway, response
41
+ when 503 then raise SteamApiClient::Errors::HttpServiceUnavailable, response
39
42
  end
40
43
  end
41
44
 
@@ -6,6 +6,7 @@ module SteamApiClient
6
6
  @response_struct = response_struct
7
7
  @client = client
8
8
 
9
+ ensure_api_token!
9
10
  build_request
10
11
  end
11
12
 
@@ -23,6 +24,13 @@ module SteamApiClient
23
24
 
24
25
  attr_accessor :request_struct, :response_struct, :client, :raw_request, :failure_handler
25
26
 
27
+ def ensure_api_token!
28
+ return unless request_struct.needs_authorization?
29
+ return unless client.api_token.to_s.empty?
30
+
31
+ raise SteamApiClient::Errors::MissingApiToken
32
+ end
33
+
26
34
  def build_request
27
35
  url = nil
28
36
 
@@ -67,20 +75,19 @@ module SteamApiClient
67
75
  end
68
76
 
69
77
  def build_options
70
- {
78
+ options = {
71
79
  method: request_struct.method,
72
- headers: default_headers,
73
- body: request_struct.body.to_json,
80
+ headers: {},
74
81
  followlocation: true,
75
82
  timeout_ms: (client.timeout * 1000).to_i,
76
83
  connecttimeout_ms: (client.connect_timeout * 1000).to_i,
77
84
  # Required for sub-second timeouts and safe use of timeouts across threads
78
85
  nosignal: true
79
86
  }
80
- end
87
+ # GET requests (every Steam endpoint so far) carry their params in the query string
88
+ return options if request_struct.method == :get
81
89
 
82
- def default_headers
83
- {"Content-Type" => "application/json"}
90
+ options.merge(headers: {"Content-Type" => "application/json"}, body: request_struct.body.to_json)
84
91
  end
85
92
  end
86
93
  end
@@ -4,14 +4,14 @@ module SteamApiClient
4
4
  # of an app in the Steam Web Api.
5
5
  class AppGlobalAchievements < Base
6
6
  # Get on the Api all global achievements info of an App in the Steam.
7
- # @param [Integer] gameid The Steam app id
7
+ # @param [Integer] appid The Steam app id (sent to Steam as `gameid`)
8
8
  # @return [SteamApiClient::Structs::AppGlobalAchievements] instance with the
9
9
  # player summary details.
10
- def retrieve(gameid)
10
+ def retrieve(appid:)
11
11
  build_request_and_run(
12
12
  SteamApiClient::Structs::Requests::GetAppGlobalAchievements,
13
13
  SteamApiClient::Structs::AppGlobalAchievements,
14
- gameid: gameid
14
+ gameid: appid
15
15
  )
16
16
  end
17
17
  end
@@ -9,7 +9,7 @@ module SteamApiClient
9
9
  # @param [Integer] maxlength The max length of the news content
10
10
  # @return [SteamApiClient::Structs::AppNews] instance with the
11
11
  # player summary details.
12
- def retrieve(appid, count, maxlength = 300)
12
+ def retrieve(appid:, count:, maxlength: 300)
13
13
  build_request_and_run(
14
14
  SteamApiClient::Structs::Requests::GetAppNews,
15
15
  SteamApiClient::Structs::AppNews,
@@ -7,7 +7,7 @@ module SteamApiClient
7
7
  # @param [Integer] appid The Steam app id
8
8
  # @return [SteamApiClient::Structs::GameCurrentPlayers] instance with the
9
9
  # game current players count.
10
- def retrieve(appid)
10
+ def retrieve(appid:)
11
11
  build_request_and_run(
12
12
  SteamApiClient::Structs::Requests::GetGameCurrentPlayers,
13
13
  SteamApiClient::Structs::GameCurrentPlayers,
@@ -8,7 +8,7 @@ module SteamApiClient
8
8
  # @param [Integer] steamid The Steam user id
9
9
  # @return [SteamApiClient::Structs::PlayerAchievements] instance with the
10
10
  # player summary details.
11
- def retrieve(appid, steamid)
11
+ def retrieve(appid:, steamid:)
12
12
  build_request_and_run(
13
13
  SteamApiClient::Structs::Requests::GetPlayerAchievements,
14
14
  SteamApiClient::Structs::PlayerAchievements,
@@ -8,7 +8,7 @@ module SteamApiClient
8
8
  # @param [String] relationship Relationship filter. Possibles values: 'all', 'friend'.
9
9
  # @return [SteamApiClient::Structs::PlayerFriends] instance with the
10
10
  # player friends list.
11
- def retrieve(steamid, relationship = "all")
11
+ def retrieve(steamid:, relationship: "all")
12
12
  build_request_and_run(
13
13
  SteamApiClient::Structs::Requests::GetPlayerFriends,
14
14
  SteamApiClient::Structs::PlayerFriends,
@@ -8,7 +8,7 @@ module SteamApiClient
8
8
  # @param [Integer] steamid The Steam user id
9
9
  # @return [SteamApiClient::Structs::PlayerGameStats] instance with the
10
10
  # player summary details.
11
- def retrieve(appid, steamid)
11
+ def retrieve(appid:, steamid:)
12
12
  build_request_and_run(
13
13
  SteamApiClient::Structs::Requests::GetPlayerGameStats,
14
14
  SteamApiClient::Structs::PlayerGameStats,
@@ -6,7 +6,7 @@ module SteamApiClient
6
6
  # @param [Integer] steamid The Steam user id
7
7
  # @return [SteamApiClient::Structs::PlayerLevel] instance with the
8
8
  # player level.
9
- def retrieve(steamid)
9
+ def retrieve(steamid:)
10
10
  build_request_and_run(
11
11
  SteamApiClient::Structs::Requests::GetPlayerLevel,
12
12
  SteamApiClient::Structs::PlayerLevel,
@@ -9,7 +9,7 @@ module SteamApiClient
9
9
  # @param [Boolean] include_played_free_games Flag to include free games
10
10
  # @return [SteamApiClient::Structs::PlayerOwnedGames] instance with the
11
11
  # player owned games details.
12
- def retrieve(steamid, include_appinfo: false, include_played_free_games: false)
12
+ def retrieve(steamid:, include_appinfo: false, include_played_free_games: false)
13
13
  build_request_and_run(
14
14
  SteamApiClient::Structs::Requests::GetPlayerOwnedGames,
15
15
  SteamApiClient::Structs::PlayerOwnedGames,
@@ -8,7 +8,7 @@ module SteamApiClient
8
8
  # @param [Integer] count - The number of games that should return
9
9
  # @return [SteamApiClient::Structs::PlayerRecentGames] instance with the
10
10
  # player summary details.
11
- def retrieve(steamid, count = 5)
11
+ def retrieve(steamid:, count: 5)
12
12
  build_request_and_run(
13
13
  SteamApiClient::Structs::Requests::GetPlayerRecentGames,
14
14
  SteamApiClient::Structs::PlayerRecentGames,
@@ -7,7 +7,7 @@ module SteamApiClient
7
7
  # @param [String] vanityurl - The Steam user short url
8
8
  # @return [SteamApiClient::Structs::PlayerSteamId] instance with the
9
9
  # player SteamId.
10
- def retrieve(vanityurl)
10
+ def retrieve(vanityurl:)
11
11
  build_request_and_run(
12
12
  SteamApiClient::Structs::Requests::GetPlayerSteamId,
13
13
  SteamApiClient::Structs::PlayerSteamId,
@@ -7,7 +7,7 @@ module SteamApiClient
7
7
  # @param [Array<Integer>] steamids - The Steam order's id
8
8
  # @return [SteamApiClient::Structs::PlayerSummaries] instance with the
9
9
  # player summary details.
10
- def retrieve(steamids)
10
+ def retrieve(steamids:)
11
11
  build_request_and_run(
12
12
  SteamApiClient::Structs::Requests::GetPlayerSummaries,
13
13
  SteamApiClient::Structs::PlayerSummaries,
@@ -25,6 +25,12 @@ module SteamApiClient
25
25
  !success?
26
26
  end
27
27
 
28
+ # Returns the HTTP response headers (case-insensitive)
29
+ # @return [Typhoeus::Response::Header, nil]
30
+ def headers
31
+ raw_response.headers
32
+ end
33
+
28
34
  # Returns the HTTP status code
29
35
  # @return [Integer]
30
36
  def status_code
@@ -57,8 +63,10 @@ module SteamApiClient
57
63
 
58
64
  attr_accessor :raw_response, :response_struct
59
65
 
66
+ # json 3 rejects duplicate keys by default; keep json 2's behavior (last one wins)
67
+ # so a response parses the same whichever json version the app uses
60
68
  def parse_body
61
- ::JSON.parse(raw_response.body.to_s, symbolize_names: true)
69
+ ::JSON.parse(raw_response.body.to_s, symbolize_names: true, allow_duplicate_key: true)
62
70
  end
63
71
  end
64
72
  end
@@ -3,9 +3,9 @@ module SteamApiClient
3
3
  # Struct that holds all App Global Achievements information
4
4
  # @see https://developer.valvesoftware.com/wiki/Steam_Web_API#GetGlobalAchievementPercentagesForApp_.28v0002.29
5
5
  class AppGlobalAchievements < Base
6
- class InternalAppAchievementsAttributes < Dry::Struct
6
+ class InternalAppAchievementsAttributes < Base
7
7
  attribute :name, Types::String
8
- attribute :percent, Types::Decimal
8
+ attribute :percent, Types::Float
9
9
  end
10
10
 
11
11
  attribute :achievementpercentages do
@@ -2,7 +2,7 @@ module SteamApiClient
2
2
  module Structs
3
3
  # Struct that holds an App news information
4
4
  # @see https://developer.valvesoftware.com/wiki/Steam_Web_API#GetNewsForApp_.28v0002.29
5
- class AppNew < Dry::Struct
5
+ class AppNew < Base
6
6
  attribute :gid, Types::String
7
7
  attribute :title, Types::String
8
8
  attribute :url, Types::String
@@ -3,7 +3,7 @@ module SteamApiClient
3
3
  # Struct that holds all Player Achievements of an app information
4
4
  # @see https://developer.valvesoftware.com/wiki/Steam_Web_API#GetPlayerAchievements_.28v0001.29
5
5
  class PlayerAchievements < Base
6
- class InternalPlayerAchievementsAttributes < Dry::Struct
6
+ class InternalPlayerAchievementsAttributes < Base
7
7
  attribute :apiname?, Types::String
8
8
  attribute :achieved?, Types::Integer
9
9
  attribute :unlocktime?, Types::Integer
@@ -2,7 +2,7 @@ module SteamApiClient
2
2
  module Structs
3
3
  # Struct that holds information of all friends the player has
4
4
  # @see https://developer.valvesoftware.com/wiki/Steam_Web_API#GetFriendList_.28v0001.29
5
- class InternalPlayerFriends < Dry::Struct
5
+ class InternalPlayerFriends < Base
6
6
  attribute :steamid?, Types::Integer
7
7
  attribute :relationship?, Types::String
8
8
  attribute :friend_since, Types::Integer
@@ -3,14 +3,22 @@ module SteamApiClient
3
3
  # Struct that holds all Player Stats for a given game/appid
4
4
  # @see https://developer.valvesoftware.com/wiki/Steam_Web_API#GetUserStatsForGame_.28v0002.29
5
5
  class PlayerGameStats < Base
6
- class InternalPlayerGameStatsAttributes < Dry::Struct
6
+ class InternalPlayerGameStatsAttributes < Base
7
7
  attribute :name?, Types::String
8
8
  attribute :achieved?, Types::Integer
9
9
  end
10
10
 
11
+ # A game-defined stat, e.g. "Scout.accum.iDamageDealt" => 26580
12
+ class Stat < Base
13
+ attribute :name, Types::String
14
+ # Integer or Float exactly as Steam sends it (coercing would truncate float stats)
15
+ attribute :value, (Types::Strict::Integer | Types::Strict::Float).optional
16
+ end
17
+
11
18
  attribute :playerstats do
12
19
  attribute :steamID?, Types::Coercible::Integer.optional.default(nil)
13
20
  attribute :gameName?, Types::String
21
+ attribute :stats?, Types::Strict::Array.of(Stat)
14
22
  attribute :achievements?, Types::Strict::Array.of(
15
23
  InternalPlayerGameStatsAttributes
16
24
  )
@@ -3,15 +3,21 @@ module SteamApiClient
3
3
  # Struct that holds all user owned games
4
4
  # @see https://developer.valvesoftware.com/wiki/Steam_Web_API#GetOwnedGames_.28v0001.29
5
5
  class PlayerOwnedGames < Base
6
- class InternalPlayerOwnedGames < Dry::Struct
6
+ class InternalPlayerOwnedGames < Base
7
7
  attribute :appid, Types::Integer
8
8
  attribute :name?, Types::String
9
+ # Minutes played, in total and in the last two weeks (only sent when played recently)
9
10
  attribute :playtime_forever, Types::Integer
11
+ attribute? :playtime_2weeks, Types::Integer
10
12
  attribute :img_icon_url?, Types::String
11
- attribute :playtime_windows_forever, Types::Integer
12
- attribute :playtime_mac_forever, Types::Integer
13
- attribute :playtime_linux_forever, Types::Integer
14
- attribute :rtime_last_played, Types::Integer
13
+ attribute? :has_community_visible_stats, Types::Bool
14
+ attribute? :has_leaderboards, Types::Bool
15
+ attribute? :content_descriptorids, Types::Array.of(Types::Integer)
16
+ # Steam doesn't send these for most libraries anymore
17
+ attribute? :playtime_windows_forever, Types::Integer
18
+ attribute? :playtime_mac_forever, Types::Integer
19
+ attribute? :playtime_linux_forever, Types::Integer
20
+ attribute? :rtime_last_played, Types::Integer
15
21
  end
16
22
 
17
23
  attribute :response do
@@ -3,7 +3,7 @@ module SteamApiClient
3
3
  # Struct that holds all user recent played games
4
4
  # @see https://developer.valvesoftware.com/wiki/Steam_Web_API#GetRecentlyPlayedGames_.28v0001.29
5
5
  class PlayerRecentGames < Base
6
- class InternalPlayerRecentGames < Dry::Struct
6
+ class InternalPlayerRecentGames < Base
7
7
  attribute :appid, Types::Integer
8
8
  attribute :name, Types::String
9
9
  attribute :playtime_2weeks, Types::Integer
@@ -2,9 +2,9 @@ module SteamApiClient
2
2
  module Structs
3
3
  # Struct that holds the Player Summary information
4
4
  # @see https://developer.valvesoftware.com/wiki/Steam_Web_API#GetPlayerSummaries_.28v0002.29
5
- class PlayerSummary < Dry::Struct
5
+ class PlayerSummary < Base
6
6
  # 64bit SteamID of the user
7
- attribute :steamid, Types::String
7
+ attribute :steamid, Types::Integer
8
8
  # The player's persona name (display name)
9
9
  attribute :personaname, Types::String
10
10
  # The full URL of the player's Steam Community profile.
@@ -24,11 +24,23 @@ module SteamApiClient
24
24
  # This represents whether the profile is visible or not, and if it is visible, why you are allowed to see it.
25
25
  attribute :communityvisibilitystate, Types::Integer
26
26
  # If set, indicates the user has a community profile configured (will be set to '1')
27
- attribute :profilestate, Types::String
27
+ attribute :profilestate, Types::Integer
28
28
  # The last time the user was online, in unix time.
29
29
  attribute :lastlogoff, Types::Optional::Integer
30
30
  # If set, indicates the profile allows public comments.
31
31
  attribute :commentpermission, Types::Optional::Integer
32
+ # Optional fields: only sent when the profile (or that part of it) is public
33
+ # The player's "real" name, if they set one
34
+ attribute? :realname, Types::String
35
+ # When the account was created, in unix time
36
+ attribute? :timecreated, Types::Integer
37
+ # ISO 3166 country code of the player's location
38
+ attribute? :loccountrycode, Types::String
39
+ # Hash used in the avatar URLs
40
+ attribute? :avatarhash, Types::String
41
+ # The player's primary group (a group SteamID)
42
+ attribute? :primaryclanid, Types::Integer
43
+ attribute? :personastateflags, Types::Integer
32
44
  end
33
45
  end
34
46
  end
@@ -12,7 +12,7 @@ module SteamApiClient
12
12
  # @return an instance of the Struct
13
13
  def self.build_from_params(params)
14
14
  %i[appid].each do |attrib|
15
- raise StandardError, "#{attrib} is missing" unless params.key?(attrib)
15
+ raise ArgumentError, "#{attrib} is missing" unless params.key?(attrib)
16
16
  end
17
17
 
18
18
  new(params)
@@ -37,12 +37,6 @@ module SteamApiClient
37
37
  )
38
38
  end
39
39
 
40
- # Return the class attributes as a Hash
41
- # @return [Hash]
42
- def body
43
- {}
44
- end
45
-
46
40
  # Tells if the request require the api_token in the query params
47
41
  # @return [Boolean]
48
42
  def needs_authorization?
@@ -8,7 +8,7 @@ module SteamApiClient
8
8
  # @params [Hash] params The hash params.
9
9
  # @return an instance of the Struct
10
10
  def self.build_from_params(params)
11
- raise StandardError, ":gameid is missing" unless params.key?(:gameid)
11
+ raise ArgumentError, ":gameid is missing" unless params.key?(:gameid)
12
12
 
13
13
  new(params)
14
14
  end
@@ -37,12 +37,6 @@ module SteamApiClient
37
37
  {gameid: gameid}
38
38
  end
39
39
 
40
- # Return the class attributes as a Hash
41
- # @return [Hash]
42
- def body
43
- {}
44
- end
45
-
46
40
  # Tells if the request require the api_token in the query params
47
41
  # @return [Boolean]
48
42
  def needs_authorization?
@@ -11,7 +11,7 @@ module SteamApiClient
11
11
  # @return an instance of the Struct
12
12
  def self.build_from_params(params)
13
13
  %i[appid count maxlength].each do |attrib|
14
- raise StandardError, "#{attrib} is missing" unless params.key?(attrib)
14
+ raise ArgumentError, "#{attrib} is missing" unless params.key?(attrib)
15
15
  end
16
16
 
17
17
  new(params)
@@ -45,12 +45,6 @@ module SteamApiClient
45
45
  }
46
46
  end
47
47
 
48
- # Return the class attributes as a Hash
49
- # @return [Hash]
50
- def body
51
- {}
52
- end
53
-
54
48
  # Tells if the request require the api_token in the query params
55
49
  # @return [Boolean]
56
50
  def needs_authorization?
@@ -8,7 +8,7 @@ module SteamApiClient
8
8
  # @params [Hash] params The hash params.
9
9
  # @return an instance of the Struct
10
10
  def self.build_from_params(params)
11
- raise StandardError, ":appid is missing" unless params.key?(:appid)
11
+ raise ArgumentError, ":appid is missing" unless params.key?(:appid)
12
12
 
13
13
  new(params)
14
14
  end
@@ -37,12 +37,6 @@ module SteamApiClient
37
37
  {appid: appid}
38
38
  end
39
39
 
40
- # Return the class attributes as a Hash
41
- # @return [Hash]
42
- def body
43
- {}
44
- end
45
-
46
40
  # Tells if the request require the api_token in the query params
47
41
  # @return [Boolean]
48
42
  def needs_authorization?
@@ -10,7 +10,7 @@ module SteamApiClient
10
10
  # @return an instance of the Struct
11
11
  def self.build_from_params(params)
12
12
  %i[appid steamid].each do |attrib|
13
- raise StandardError, "#{attrib} is missing" unless params.key?(attrib)
13
+ raise ArgumentError, "#{attrib} is missing" unless params.key?(attrib)
14
14
  end
15
15
 
16
16
  new(params)
@@ -42,12 +42,6 @@ module SteamApiClient
42
42
  steamid: steamid
43
43
  }
44
44
  end
45
-
46
- # Return the class attributes as a Hash
47
- # @return [Hash]
48
- def body
49
- {}
50
- end
51
45
  end
52
46
  end
53
47
  end
@@ -10,11 +10,11 @@ module SteamApiClient
10
10
  # @return an instance of the Struct
11
11
  def self.build_from_params(params)
12
12
  %i[steamid relationship].each do |attrib|
13
- raise StandardError, "#{attrib} is missing" unless params.key?(attrib)
13
+ raise ArgumentError, "#{attrib} is missing" unless params.key?(attrib)
14
14
  end
15
15
 
16
16
  # The only possible values are: all, friend.
17
- raise StandardError, ":relationship value not allowed" unless %w[all friend].include? params[:relationship]
17
+ raise ArgumentError, ":relationship value not allowed" unless %w[all friend].include? params[:relationship]
18
18
 
19
19
  new(params)
20
20
  end
@@ -45,12 +45,6 @@ module SteamApiClient
45
45
  relationship: relationship
46
46
  }
47
47
  end
48
-
49
- # Return the class attributes as a Hash
50
- # @return [Hash]
51
- def body
52
- {}
53
- end
54
48
  end
55
49
  end
56
50
  end
@@ -10,7 +10,7 @@ module SteamApiClient
10
10
  # @return an instance of the Struct
11
11
  def self.build_from_params(params)
12
12
  %i[appid steamid].each do |attrib|
13
- raise StandardError, "#{attrib} is missing" unless params.key?(attrib)
13
+ raise ArgumentError, "#{attrib} is missing" unless params.key?(attrib)
14
14
  end
15
15
 
16
16
  new(params)
@@ -42,12 +42,6 @@ module SteamApiClient
42
42
  steamid: steamid
43
43
  }
44
44
  end
45
-
46
- # Return the class attributes as a Hash
47
- # @return [Hash]
48
- def body
49
- {}
50
- end
51
45
  end
52
46
  end
53
47
  end
@@ -8,7 +8,7 @@ module SteamApiClient
8
8
  # @params [Hash] params The hash params.
9
9
  # @return an instance of the Struct
10
10
  def self.build_from_params(params)
11
- raise StandardError, ":steamid is missing" unless params.key?(:steamid)
11
+ raise ArgumentError, ":steamid is missing" unless params.key?(:steamid)
12
12
 
13
13
  new(params)
14
14
  end
@@ -36,12 +36,6 @@ module SteamApiClient
36
36
  def query_params
37
37
  {steamid: steamid}
38
38
  end
39
-
40
- # Return the class attributes as a Hash
41
- # @return [Hash]
42
- def body
43
- {}
44
- end
45
39
  end
46
40
  end
47
41
  end
@@ -11,7 +11,7 @@ module SteamApiClient
11
11
  # @return an instance of the Struct
12
12
  def self.build_from_params(params)
13
13
  %i[steamid include_appinfo include_played_free_games].each do |attrib|
14
- raise StandardError, "#{attrib} is missing" unless params.key?(attrib)
14
+ raise ArgumentError, "#{attrib} is missing" unless params.key?(attrib)
15
15
  end
16
16
 
17
17
  new(params)
@@ -44,12 +44,6 @@ module SteamApiClient
44
44
  include_played_free_games: include_played_free_games
45
45
  }
46
46
  end
47
-
48
- # Return the class attributes as a Hash
49
- # @return [Hash]
50
- def body
51
- {}
52
- end
53
47
  end
54
48
  end
55
49
  end
@@ -10,7 +10,7 @@ module SteamApiClient
10
10
  # @return an instance of the Struct
11
11
  def self.build_from_params(params)
12
12
  %i[steamid count].each do |attrib|
13
- raise StandardError, "#{attrib} is missing" unless params.key?(attrib)
13
+ raise ArgumentError, "#{attrib} is missing" unless params.key?(attrib)
14
14
  end
15
15
 
16
16
  new(params)
@@ -42,12 +42,6 @@ module SteamApiClient
42
42
  count: count
43
43
  }
44
44
  end
45
-
46
- # Return the class attributes as a Hash
47
- # @return [Hash]
48
- def body
49
- {}
50
- end
51
45
  end
52
46
  end
53
47
  end
@@ -8,7 +8,7 @@ module SteamApiClient
8
8
  # @params [Hash] params The hash params.
9
9
  # @return an instance of the Struct
10
10
  def self.build_from_params(params)
11
- raise StandardError, ":vanityurl is missing" unless params.key?(:vanityurl)
11
+ raise ArgumentError, ":vanityurl is missing" unless params.key?(:vanityurl)
12
12
 
13
13
  new(params)
14
14
  end
@@ -36,12 +36,6 @@ module SteamApiClient
36
36
  def query_params
37
37
  {vanityurl: vanityurl}
38
38
  end
39
-
40
- # Return the class attributes as a Hash
41
- # @return [Hash]
42
- def body
43
- {}
44
- end
45
39
  end
46
40
  end
47
41
  end
@@ -8,7 +8,7 @@ module SteamApiClient
8
8
  # @params [Hash] params The hash params.
9
9
  # @return an instance of the Struct
10
10
  def self.build_from_params(params)
11
- raise StandardError, ":steamids are missing" unless params.key?(:steamids)
11
+ raise ArgumentError, ":steamids are missing" unless params.key?(:steamids)
12
12
 
13
13
  new(params)
14
14
  end
@@ -40,12 +40,6 @@ module SteamApiClient
40
40
  steamids: steamids.join(",")
41
41
  }
42
42
  end
43
-
44
- # Return the class attributes as a Hash
45
- # @return [Hash]
46
- def body
47
- {}
48
- end
49
43
  end
50
44
  end
51
45
  end
@@ -1,13 +1,22 @@
1
1
  module SteamApiClient
2
2
  module Structs
3
- # Wrapper module to help use the dry types and easily extend them
4
- # when necessary
3
+ # Types used by the structs. Steam's JSON often sends numbers as strings
4
+ # (e.g. achievement percentages, ages), so scalar types coerce values, and
5
+ # anything that can't be coerced raises instead of passing through.
6
+ # Steam also sends null for values it doesn't have (e.g. an app's website),
7
+ # so nil passes through every scalar type unchanged.
5
8
  module Types
6
9
  include ::Dry.Types(default: :nominal)
7
10
 
11
+ Integer = Coercible::Integer.optional
12
+ Float = Coercible::Float.optional
13
+ # Numbers become strings
14
+ String = Strict::String.constructor { |value| value.is_a?(::Numeric) ? value.to_s : value }.optional
15
+ Bool = Strict::Bool.optional
16
+
8
17
  module Optional
9
- String = ::Dry.Types::String.optional.default(nil)
10
- Integer = ::Dry.Types::Integer.optional.default(nil)
18
+ String = Types::String.optional.default(nil)
19
+ Integer = Types::Integer.optional.default(nil)
11
20
  end
12
21
  end
13
22
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SteamApiClient
4
- VERSION = "0.9.1"
4
+ VERSION = "0.10.0"
5
5
  end
@@ -3,6 +3,7 @@
3
3
  require "dry-configurable"
4
4
  require "dry-struct"
5
5
  require "json"
6
+ require "time"
6
7
  require "singleton"
7
8
  require "typhoeus"
8
9
 
@@ -12,6 +13,7 @@ require_relative "steam_api_client/response"
12
13
  require_relative "steam_api_client/failure_handler"
13
14
 
14
15
  require_relative "steam_api_client/errors/api_error"
16
+ require_relative "steam_api_client/errors/missing_api_token"
15
17
  require_relative "steam_api_client/errors/response_parse_error"
16
18
  require_relative "steam_api_client/errors/url_error"
17
19
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: steam_api_client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.1
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - João Pedro
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 1.1.0
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: 1.1.0
26
+ version: '1.1'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: dry-struct
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -65,6 +65,7 @@ files:
65
65
  - lib/steam_api_client.rb
66
66
  - lib/steam_api_client/client.rb
67
67
  - lib/steam_api_client/errors/api_error.rb
68
+ - lib/steam_api_client/errors/missing_api_token.rb
68
69
  - lib/steam_api_client/errors/response_parse_error.rb
69
70
  - lib/steam_api_client/errors/url_error.rb
70
71
  - lib/steam_api_client/failure_handler.rb
@@ -122,11 +123,11 @@ files:
122
123
  - lib/steam_api_client/structs/requests/storefront_abstract.rb
123
124
  - lib/steam_api_client/structs/types.rb
124
125
  - lib/steam_api_client/version.rb
125
- homepage: https://github.com/sosolidkk/steam_api
126
+ homepage: https://github.com/jpechaves/steam_api
126
127
  licenses:
127
128
  - MIT
128
129
  metadata:
129
- homepage_uri: https://github.com/sosolidkk/steam_api
130
+ homepage_uri: https://github.com/jpechaves/steam_api
130
131
  rubygems_mfa_required: 'true'
131
132
  post_install_message:
132
133
  rdoc_options: []
@@ -136,7 +137,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
136
137
  requirements:
137
138
  - - ">="
138
139
  - !ruby/object:Gem::Version
139
- version: '3.0'
140
+ version: '3.3'
140
141
  required_rubygems_version: !ruby/object:Gem::Requirement
141
142
  requirements:
142
143
  - - ">="