steam_api_client 0.9.0 → 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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +140 -1
  3. data/README.md +39 -10
  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 -17
  44. data/.dockerignore +0 -21
  45. data/.pronto.yml +0 -12
  46. data/.reek.yml +0 -25
  47. data/.rspec +0 -4
  48. data/.ruby-version +0 -1
  49. data/.standard.yml +0 -3
  50. data/.tool-versions +0 -1
  51. data/CLAUDE.md +0 -52
  52. data/CODE_OF_CONDUCT.md +0 -84
  53. data/Gemfile +0 -34
  54. data/Gemfile.lock +0 -220
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d18c324c4585ab45385d8edf00615002e7906faa914c90a961a597baee266447
4
- data.tar.gz: bfb5cb86136edac94cfb7829e0e14a944909d7d4db525424fc39d5aa3b03f77a
3
+ metadata.gz: d0c5680dd7c5f04050858684ffdedba92a405af03105ff67d5ab5f83786a9be3
4
+ data.tar.gz: 18dace5a415dbae06ab33d22af1d8d2f89a692c18b33d0fde7632e9608860476
5
5
  SHA512:
6
- metadata.gz: 45aa21466727fe4cc07592a9af86a47688ea1953154a09a9a4466f7fd6bea4b220874baeec8c029e1c99885f1865373c920a70a7a3db48b6de5faf1210763880
7
- data.tar.gz: 0bb3e7ac41550d22fadf112663de1cc4d569fd5719351b1b34ba78fc046248771dff510e0912887793c887158bca4b6121e4175b86f7165e37f8f30ac0f72fe8
6
+ metadata.gz: '008e3f030129225f200f25063f45bc1df84a0abc0aba381d1c2a919ef99f9cfebba87a908a14ebfc6652a0d9ea9dfdd76e8a7dc5299762df648b03423879dc07'
7
+ data.tar.gz: 886f409484facc5b94264c94a488e07d85bcf0514c84b81b9541f395da769d53d9407b8af83b2dc8cfcf45cb8bae81a5044a4d8542074c3940a3cec9ccfa2a71
data/CHANGELOG.md CHANGED
@@ -1,9 +1,148 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
1
7
  ## [Unreleased]
2
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
+
54
+ ## [0.9.1] - 2026-10-23
55
+
56
+ ### Fixed
57
+
58
+ - The gem no longer packages development files (`CLAUDE.md`, `CODE_OF_CONDUCT.md`, `Gemfile`, `Gemfile.lock` and tooling dotfiles). It ships only `lib/`, `LICENSE`, `README.md` and `CHANGELOG.md` (#37).
59
+
60
+ ## [0.9.0] - 2026-10-23
61
+
62
+ First release published to RubyGems, as [`steam_api_client`](https://rubygems.org/gems/steam_api_client).
63
+
3
64
  ### Changed
4
65
 
5
66
  - **Breaking:** renamed the gem from `steam_api` to `steam_api_client`, since `steam_api` is taken on RubyGems by an unrelated gem (#21). Update your Gemfile to `gem "steam_api_client"`, use `require "steam_api_client"`, and replace the `SteamApi` namespace with `SteamApiClient` (e.g. `SteamApiClient::Client`, `SteamApiClient::Errors::HttpTimeout`).
67
+ - **Breaking:** `Response#result` raises `SteamApiClient::Errors::ResponseParseError` instead of letting `Dry::Struct::Error` escape when a response can't be built into its struct, and for bodies that aren't a JSON object (#11). The original error is available as `#cause`.
68
+ - `gem push` requires MFA (`rubygems_mfa_required`).
69
+ - Removed the `activesupport`, `vcr` and `webmock` dependencies.
70
+
71
+ ### Added
72
+
73
+ - Configurable HTTP timeouts: 10s per request and 5s to connect by default. Set them with `Client.configure` (`timeout`, `connect_timeout`) or `Client.new(timeout:, connect_timeout:)`. Timed-out requests raise `HttpTimeout` (#12).
74
+ - `AppDetails::Movie` exposes Steam's streaming trailer manifests: `dash_av1`, `dash_h264`, `hls_h264` (#9).
75
+ - CI: Standard Ruby lint, a RubyCritic score gate, and bundler-audit on push, PRs and weekly (#31).
76
+
77
+ ### Fixed
78
+
79
+ - `app_details` raised `Dry::Struct::Error` for every app with trailers, because Steam no longer returns `webm`/`mp4` (#9).
80
+ - `app_details` raised `Dry::Struct::Error` for unavailable apps. It now returns `response.success == false` with `response.data == nil` (#10).
81
+ - The token set through `Client.configure` was never sent. `Client#api_token` now falls back to it.
82
+ - `require "steam_api"` didn't load `json`, so requests failed outside the test suite.
83
+ - The default request `body` used ActiveSupport methods (`as_json`, `deep_symbolize_keys`) after ActiveSupport was removed.
84
+ - The README examples used a positional `Client.new` argument instead of `api_token:`.
85
+
86
+ ### Security
87
+
88
+ - Bumped `concurrent-ruby` to 1.3.8 and `json` to 2.21.2 in `Gemfile.lock` to resolve bundler-audit advisories (#31). This affects development and CI only. Apps using the gem resolve their own versions.
89
+
90
+ ## Earlier versions
91
+
92
+ The versions below were bumped in `version.rb` during development but **never published or tagged**. They are reconstructed from the git history (the gem was named `steam` until 0.1.5 and `steam_api` until 0.9.0).
93
+
94
+ ## [0.8.0] - 2025-04-13
95
+
96
+ - Fixed YARD comments, added `bin/setup`, completed the struct specs and added the first request spec.
97
+ - Updated dependencies.
98
+
99
+ ## [0.7.0] - 2024-07-27
100
+
101
+ - Removed unused development tooling and ran Standard Ruby on the whole project. There was no 0.6.0.
102
+
103
+ ## [0.5.0] - 2024-06-03
104
+
105
+ - Raised the required Ruby version (needed by `dry-core`) and updated the CI Ruby versions.
106
+ - More specs.
107
+
108
+ ## [0.4.0] - 2024-04-14
109
+
110
+ - Updated gem metadata. More struct specs.
111
+
112
+ ## [0.3.0] - 2024-01-20
113
+
114
+ - Added a GitHub Actions workflow and dropped Ruby 2.6.
115
+ - Added specs for errors, responses and base structs. Removed unused code.
116
+
117
+ ## [0.2.2] - 2023-06-28
118
+
119
+ - Added `EndpointKindNotSupported` for unknown endpoint kinds.
120
+ - Fixed query params not being sent with requests.
121
+ - Clearer parameter names. Updated gems and the Ruby version. First specs for the client and the failure handler.
122
+
123
+ ## [0.2.1] - 2023-02-26
124
+
125
+ - Added support for the Storefront API and the app details endpoint.
126
+
127
+ ## [0.1.5] - 2023-01-15
128
+
129
+ - Renamed the gem from `steam` to `steam_api`. There were no 0.1.3 or 0.1.4.
130
+
131
+ ## [0.1.2] - 2022-11-25
132
+
133
+ - Client, structs, and request/response/error handling, with `HttpForbidden` (403) and `HttpInternalServerError` (500).
134
+ - Endpoints: player summaries, app news, global achievement percentages, player achievements, user game stats, recently played games, resolve vanity URL, current player count, player level, owned games and friend list.
135
+ - Development tooling configuration.
136
+
137
+ ## [0.1.1] - 2022-09-29
138
+
139
+ - Basic client configuration.
6
140
 
7
141
  ## [0.1.0] - 2022-09-28
8
142
 
9
- - Initial release
143
+ - Initial gem skeleton.
144
+
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
147
+ [0.9.1]: https://github.com/jpechaves/steam_api/compare/v0.9.0...v0.9.1
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)
@@ -21,6 +22,9 @@
21
22
 
22
23
  # steam_api_client
23
24
 
25
+ [![Gem Version](https://img.shields.io/gem/v/steam_api_client)](https://rubygems.org/gems/steam_api_client)
26
+ [![CI](https://github.com/jpechaves/steam_api/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/jpechaves/steam_api/actions/workflows/ci.yml)
27
+
24
28
  > Previously developed as `steam_api`. It is not related to the [`steam_api`](https://rubygems.org/gems/steam_api) gem on RubyGems.
25
29
 
26
30
  A Ruby gem for interacting with the [Steam Web API](https://developer.valvesoftware.com/wiki/Steam_Web_API). This gem provides a structured and user-friendly way to fetch various types of data from Steam's API endpoints, such as game details, player information, and achievements.
@@ -35,22 +39,28 @@ A Ruby gem for interacting with the [Steam Web API](https://developer.valvesoftw
35
39
 
36
40
  ## Installation
37
41
 
38
- Add this line to your application's Gemfile:
42
+ Add the gem to your application's Gemfile:
39
43
 
40
44
  ```ruby
45
+ gem "steam_api_client"
46
+ ```
47
+
48
+ and run `bundle install`. Or let Bundler do both:
49
+
50
+ ```bash
41
51
  $ bundle add steam_api_client
42
52
  ```
43
53
 
44
- Or install it manually:
54
+ Without Bundler, install it with:
45
55
 
46
56
  ```bash
47
57
  $ gem install steam_api_client
48
58
  ```
49
59
 
50
- Then execute:
60
+ and load it with:
51
61
 
52
- ```bash
53
- $ bundle install
62
+ ```ruby
63
+ require "steam_api_client"
54
64
  ```
55
65
 
56
66
  ## Usage
@@ -86,7 +96,7 @@ client = SteamApiClient::Client.new(api_token: "your-API-token-value", timeout:
86
96
 
87
97
  ```ruby
88
98
  client = SteamApiClient::Client.new(api_token: "your-API-token-value")
89
- client.player_summaries.retrieve([76561198160009756])
99
+ client.player_summaries.retrieve(steamids: [76561198160009756])
90
100
  ```
91
101
 
92
102
  #### Get App Details
@@ -102,8 +112,7 @@ For more examples, see the [Usage](#usage) section above.
102
112
 
103
113
  ### Requirements
104
114
 
105
- - Ruby >= 3.0
106
- - Docker (optional, for consistent development environments)
115
+ - Ruby >= 3.3
107
116
 
108
117
  ### Initial Setup
109
118
 
@@ -137,9 +146,29 @@ We use [standardrb](https://github.com/standardrb/standard) for linting and code
137
146
  $ bin/lint
138
147
  ```
139
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
+
140
169
  ## Contributing
141
170
 
142
- 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).
143
172
 
144
173
  ### How to Contribute
145
174
 
@@ -155,4 +184,4 @@ This gem is available as open source under the terms of the [MIT License](https:
155
184
 
156
185
  ## Code of Conduct
157
186
 
158
- 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