steam_api_client 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fff398c488310a8cd495c37bc213bc6b41645c21aef707a5b83a951c4ee7550e
4
- data.tar.gz: '0191bdcd553d89bcf7f15cf247eb5aa2e6a616377e791a544ee8ccfefcdc9e7d'
3
+ metadata.gz: '09f9edc74aff7de3fa4975dca173fd7a3388ea4a732671b67db45e09de6a06e5'
4
+ data.tar.gz: 92bbc4d8b4214e32fb2c8d5e8e5a18a3e6844712b4cf916552f02a46a96ec82b
5
5
  SHA512:
6
- metadata.gz: eee77c9968b4a7ba64330c7fde66690509e2f8c6e9ea77bacd16b7e61a097f6e4ac8daa4226d38c6c33fbf6ed2d753dbf4c5527d58951672a2d33d18c91d27d5
7
- data.tar.gz: e9fae7d714e744f4131951f3c45f73f4e8e81725b01b61d7ed6a9fc365293fe94808b58a6e96be457904e89c64b4fd3d073d1a1616051c6b5354a76d01c62a1e
6
+ metadata.gz: 87a2e53bcd76c574b6f6389071a5156073ad893734ef79377fb34c48406f120652dd93e0a4d8f79b2bd54b5984c6415a5a55e618839ba330d7febb6415c76af8
7
+ data.tar.gz: 3ee3b3c3059e76060a40f63a14bfabd9881983f077f438c8ea90a239d6efeb89602cfed40c7022dfec6225b8b6578508f8821abecae7b4e7287ba0dd261fc60b
data/CHANGELOG.md CHANGED
@@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.2.0] - 2026-11-02
10
+
11
+ ### Added
12
+
13
+ - `package_details`, `app_dlc` and `store_search` from the Storefront API: a package's apps and price, an app's DLC, and a search of the store by name. No API key needed (#107).
14
+
9
15
  ## [1.1.0] - 2026-11-02
10
16
 
11
17
  ### Added
@@ -204,7 +210,8 @@ The versions below were bumped in `version.rb` during development but **never pu
204
210
 
205
211
  - Initial gem skeleton.
206
212
 
207
- [Unreleased]: https://github.com/jpechaves/steam_api/compare/v1.1.0...HEAD
213
+ [Unreleased]: https://github.com/jpechaves/steam_api/compare/v1.2.0...HEAD
214
+ [1.2.0]: https://github.com/jpechaves/steam_api/compare/v1.1.0...v1.2.0
208
215
  [1.1.0]: https://github.com/jpechaves/steam_api/compare/v1.0.0...v1.1.0
209
216
  [1.0.0]: https://github.com/jpechaves/steam_api/compare/v0.10.0...v1.0.0
210
217
  [0.11.0]: https://github.com/jpechaves/steam_api/compare/v0.10.0...v0.11.0
data/docs/USAGE.md CHANGED
@@ -238,6 +238,49 @@ client.app_details.retrieve(appid: 440).platforms
238
238
 
239
239
  `SteamApiClient::AppDetails` fields: `app_id`, `type`, `name`, `required_age`, `is_free`, `dlc`, `detailed_description`, `about_the_game`, `short_description`, `supported_languages`, `header_image`, `website`, `pc_requirements` / `mac_requirements` / `linux_requirements` (`AppDetails::Requirements`: `minimum`, `recommended`, as HTML), `developers`, `publishers`, `price_overview` (`AppDetails::Price`, in cents; `nil` for free apps), `packages`, `package_groups` (`AppDetails::PackageGroup` with `subs`: `AppDetails::Package`), `platforms`, `metacritic` (`score`, `url`), `categories` and `genres` (`id`, `description`), `screenshots`, `movies` (`AppDetails::Movie` with DASH and HLS streams), `recommendations` (the number of user reviews), `achievements` (`total`, `highlighted`), `release_date` (`coming_soon`, `date`), `support_info` (`url`, `email`), `background`, `background_raw`, `content_descriptors` (`ids`, `notes`).
240
240
 
241
+ #### `package_details`
242
+
243
+ A package (a bundle, or a "sub" such as an edition or a license) in the store, or `nil` when it's unavailable. Storefront API, no key needed. Takes one `packageid:` (Steam rejects several at once), and `country_code:` / `locale:` like `app_details`. Package IDs are in `app_details`' `packages` and `package_groups`.
244
+
245
+ ```ruby
246
+ package = client.package_details.retrieve(packageid: 469)
247
+ package.name # => "The Orange Box"
248
+ package.apps # => [#<data SteamApiClient::PackageDetails::App id=220, name="Half-Life 2">, ...]
249
+ package.price # => #<data SteamApiClient::PackageDetails::Price currency="USD", initial=1999, final=1999, discount_percent=0, individual=1998>
250
+ ```
251
+
252
+ `SteamApiClient::PackageDetails` fields: `package_id`, `name`, `page_content` (HTML), `purchase_text`, `page_image`, `header_image`, `small_logo`, `apps` (`id`, `name`), `price` (in cents; `individual` is what the apps cost on their own; `nil` for free packages), `platforms` and `release_date` (as in `AppDetails`), `controller` (`full_gamepad`). An invalid ID such as `0` raises `Errors::HttpBadRequest`.
253
+
254
+ #### `app_dlc`
255
+
256
+ An app's DLC, or `nil` when the app is unknown. Storefront API, no key needed. Takes `appid:`, `country_code:` and `locale:`.
257
+
258
+ ```ruby
259
+ result = client.app_dlc.retrieve(appid: 292030)
260
+ result.name # => "The Witcher 3: Wild Hunt — Remastered"
261
+ result.dlc.last
262
+ # => #<data SteamApiClient::AppDlc::Dlc id=1229320, name="The Witcher 3: Wild Hunt — Remastered Soundtrack", header_image="https://...",
263
+ # price_overview=#<data SteamApiClient::AppDlc::Price currency="USD", initial=1099, final=1099, discount_percent=0>,
264
+ # platforms=#<data SteamApiClient::AppDetails::Platforms windows=true, mac=true, linux=true>,
265
+ # release_date=#<data SteamApiClient::AppDlc::ReleaseDates steam=1581374766, mac=1581374752, linux=1581374752>, controller_support="full">
266
+ ```
267
+
268
+ `price_overview` is `nil` for free DLC and DLC not sold on its own. `release_date` has a unix time per platform (`steam`, `mac`, `linux`), each `nil` when Steam doesn't know it. Steam sometimes adds milliseconds, and the time is then a Float.
269
+
270
+ #### `store_search`
271
+
272
+ Apps in the store matching a search term, at most 10 (Steam's limit, with no paging). Storefront API, no key needed. Takes `term:`, `country_code:` and `locale:`.
273
+
274
+ ```ruby
275
+ client.store_search.retrieve(term: "portal").first
276
+ # => #<data SteamApiClient::StoreSearchResult id=620, type="app", name="Portal 2",
277
+ # price=#<data SteamApiClient::StoreSearchResult::Price currency="USD", initial=999, final=999>, tiny_image="https://...",
278
+ # metascore=95, platforms=#<data SteamApiClient::AppDetails::Platforms windows=true, mac=false, linux=true>,
279
+ # streaming_video=false, controller_support="full">
280
+ ```
281
+
282
+ `price` is `nil` for free apps, `metascore` when the app has no Metacritic score. No match is `[]`.
283
+
241
284
  #### `app_news`
242
285
 
243
286
  The latest news of an app. `count:` is required, `maxlength:` (default 300) shortens each item's contents.
@@ -346,8 +389,8 @@ Don't retry other 4xx errors: they won't succeed on their own.
346
389
 
347
390
  ## Limits and caveats
348
391
 
349
- - **Rate limits.** Steam's [Web API terms](https://steamcommunity.com/dev/apiterms) allow 100,000 calls per day per key. The Storefront API (`app_details`) is rate limited per IP address, commonly reported at about 200 requests per 5 minutes. Both answer `429` when you go over. Cache what you can.
350
- - **The Storefront API is unofficial.** `app_details` uses the API behind Steam's store and Big Picture mode. Valve doesn't document it, and its fields change without notice.
392
+ - **Rate limits.** Steam's [Web API terms](https://steamcommunity.com/dev/apiterms) allow 100,000 calls per day per key. The Storefront API (`app_details`, `package_details`, `app_dlc`, `store_search`) is rate limited per IP address, commonly reported at about 200 requests per 5 minutes. Both answer `429` when you go over. Cache what you can.
393
+ - **The Storefront API is unofficial.** The Storefront endpoints use the API behind Steam's store and Big Picture mode. Valve doesn't document it, and its fields change without notice.
351
394
  - **Private profiles.** What a player hides depends on their privacy settings. Depending on the endpoint, Steam answers with an error (`player_achievements`: 403, `player_game_stats`: 400, `player_friends`: 401) or with empty data, which the gem returns as `nil` or `[]` (`player_owned_games`, `player_recent_games`, `player_level`, `player_wishlist`, `player_wishlist_count`, `player_followed_games`). Steam answers the same way for an empty wishlist, so the wishlist endpoints can't tell the two apart. `player_summaries` still returns the public part of the profile.
352
395
  - **The key is in the URL.** Steam requires `?key=...` in the query string. Treat logs of request URLs as secrets (see [Configuration](#configuration)), and [rotate your key](https://steamcommunity.com/dev/apikey) if it leaks.
353
396
  - **SteamIDs are 64 bit Integers** (e.g. `76561197960435530`). Use `player_steamid` to get one from a vanity URL.
@@ -26,6 +26,16 @@ module SteamApiClient
26
26
  }
27
27
  ),
28
28
 
29
+ # An app's DLC => AppDlc, or nil when the app is unknown
30
+ Endpoint.new(
31
+ :app_dlc, "dlcforapp/", api: :store, key: false, returns: AppDlc, at: AppDlc.method(:unwrap),
32
+ params: {
33
+ appid: param.integer,
34
+ country_code: param.string(as: :cc, default: "US"),
35
+ locale: param.string(as: :l, default: "en")
36
+ }
37
+ ),
38
+
29
39
  # Global achievement unlock percentages of an app => [GlobalAchievement]
30
40
  # @see https://developer.valvesoftware.com/wiki/Steam_Web_API#GetGlobalAchievementPercentagesForApp_.28v0002.29
31
41
  Endpoint.new(
@@ -55,6 +65,19 @@ module SteamApiClient
55
65
  params: {appid: param.integer}
56
66
  ),
57
67
 
68
+ # Details of a package (a bundle or a sub) in the store => PackageDetails, or nil when unavailable
69
+ # @see https://wiki.teamfortress.com/wiki/User:RJackson/StorefrontAPI#packagedetails
70
+ Endpoint.new(
71
+ :package_details, "packagedetails/", api: :store, key: false, returns: PackageDetails,
72
+ at: PackageDetails.method(:unwrap),
73
+ params: {
74
+ # Steam rejects several IDs at once
75
+ packageid: param.integer(as: :packageids),
76
+ country_code: param.string(as: :cc, default: "US"),
77
+ locale: param.string(as: :l, default: "en")
78
+ }
79
+ ),
80
+
58
81
  # A player's achievements in an app => PlayerAchievements
59
82
  # @see https://developer.valvesoftware.com/wiki/Steam_Web_API#GetPlayerAchievements_.28v0001.29
60
83
  Endpoint.new(
@@ -137,6 +160,17 @@ module SteamApiClient
137
160
  Endpoint.new(
138
161
  :player_wishlist_count, "IWishlistService/GetWishlistItemCount/v1/", key: false, returns: :integer, at: %i[response count],
139
162
  params: {steamid: param.integer}
163
+ ),
164
+
165
+ # Apps in the store matching a search term => [StoreSearchResult] (at most 10)
166
+ Endpoint.new(
167
+ :store_search, "storesearch/", api: :store, key: false, returns: [StoreSearchResult],
168
+ at: StoreSearchResult.method(:unwrap),
169
+ params: {
170
+ term: param.string,
171
+ country_code: param.string(as: :cc, default: "US"),
172
+ locale: param.string(as: :l, default: "en")
173
+ }
140
174
  )
141
175
  ].to_h { |endpoint| [endpoint.name, endpoint] }.freeze
142
176
  end
@@ -105,13 +105,15 @@ module SteamApiClient
105
105
  ) do
106
106
  # Steam keys the response by app ID: {"570": {"success": true, "data": {...}}}.
107
107
  # Returns the data, or nil when the app is unavailable (success: false).
108
+ # The Storefront's other details endpoints (packagedetails) answer the same way.
108
109
  # @param [Hash] json
110
+ # @param [Symbol] id_key the key the ID is added under
109
111
  # @return [Hash, nil]
110
- def self.unwrap(json)
111
- app_id, entry = json.first
112
+ def self.unwrap(json, id_key: :steam_appid)
113
+ id, entry = json.first
112
114
  return unless entry.is_a?(Hash) && entry[:success]
113
115
 
114
- {steam_appid: app_id.to_s}.merge(entry.fetch(:data, {}))
116
+ {id_key => id.to_s}.merge(entry.fetch(:data, {}))
115
117
  end
116
118
  end
117
119
  parts.each { |name, model| AppDetails.const_set(name, model) }
@@ -0,0 +1,44 @@
1
+ module SteamApiClient
2
+ # The parts of AppDlc, built first, then named under it below (e.g. AppDlc::Dlc)
3
+ parts = {
4
+ Price: Model.define(
5
+ currency: :string,
6
+ # In cents
7
+ initial: :integer,
8
+ final: :integer,
9
+ discount_percent: :integer
10
+ ),
11
+ # Unix times, per platform. Steam sometimes adds milliseconds, which makes them Floats.
12
+ ReleaseDates: Model.define(
13
+ steam: [:number, optional: true],
14
+ mac: [:number, optional: true],
15
+ linux: [:number, optional: true]
16
+ )
17
+ }
18
+ parts[:Dlc] = Model.define(
19
+ id: :integer,
20
+ name: :string,
21
+ header_image: [:string, optional: true],
22
+ # Not sent for free DLC, or DLC not sold on its own
23
+ price_overview: [parts[:Price], optional: true],
24
+ platforms: [AppDetails::Platforms, optional: true],
25
+ release_date: [parts[:ReleaseDates], optional: true],
26
+ # "full", "partial", or nil
27
+ controller_support: [:string, optional: true]
28
+ )
29
+
30
+ # An app's DLC, from `app_dlc`
31
+ AppDlc = Model.define(
32
+ app_id: [:integer, from: :appid],
33
+ name: :string,
34
+ dlc: [[parts[:Dlc]], default: []]
35
+ ) do
36
+ # Steam answers {"status": 2} for an unknown app
37
+ # @param [Hash] json
38
+ # @return [Hash, nil] the JSON, or nil when the app is unknown
39
+ def self.unwrap(json)
40
+ json if json[:status] == 1
41
+ end
42
+ end
43
+ parts.each { |name, model| AppDlc.const_set(name, model) }
44
+ end
@@ -0,0 +1,45 @@
1
+ module SteamApiClient
2
+ # The parts of PackageDetails, built first, then named under it below
3
+ # (e.g. PackageDetails::App)
4
+ parts = {
5
+ App: Model.define(id: :integer, name: :string),
6
+ Price: Model.define(
7
+ currency: :string,
8
+ # In cents
9
+ initial: :integer,
10
+ final: :integer,
11
+ discount_percent: :integer,
12
+ # The sum of the apps' own prices, in cents (0 when they aren't sold separately)
13
+ individual: [:integer, optional: true]
14
+ ),
15
+ Controller: Model.define(full_gamepad: :boolean)
16
+ }
17
+
18
+ # A package (a bundle or a sub) in the store, from `package_details`. Every
19
+ # field is nil when Steam leaves it out.
20
+ # @see https://wiki.teamfortress.com/wiki/User:RJackson/StorefrontAPI#packagedetails
21
+ PackageDetails = Model.define(
22
+ package_id: [:integer, from: :packageid],
23
+ name: [:string, optional: true],
24
+ # HTML
25
+ page_content: [:string, optional: true],
26
+ purchase_text: [:string, optional: true],
27
+ page_image: [:string, optional: true],
28
+ header_image: [:string, optional: true],
29
+ small_logo: [:string, optional: true],
30
+ apps: [[parts[:App]], default: []],
31
+ # Not sent for free packages
32
+ price: [parts[:Price], optional: true],
33
+ platforms: [AppDetails::Platforms, optional: true],
34
+ controller: [parts[:Controller], optional: true],
35
+ release_date: [AppDetails::ReleaseDate, optional: true]
36
+ ) do
37
+ # The package's data, or nil when it's unavailable (see AppDetails.unwrap)
38
+ # @param [Hash] json
39
+ # @return [Hash, nil]
40
+ def self.unwrap(json)
41
+ AppDetails.unwrap(json, id_key: :packageid)
42
+ end
43
+ end
44
+ parts.each { |name, model| PackageDetails.const_set(name, model) }
45
+ end
@@ -0,0 +1,34 @@
1
+ module SteamApiClient
2
+ # Built first, then named StoreSearchResult::Price below
3
+ price = Model.define(
4
+ currency: :string,
5
+ # In cents
6
+ initial: :integer,
7
+ final: :integer
8
+ )
9
+
10
+ # One result of a store search, from `store_search`
11
+ StoreSearchResult = Model.define(
12
+ id: :integer,
13
+ # "app"
14
+ type: :string,
15
+ name: :string,
16
+ # Not sent for free apps
17
+ price: [price, optional: true],
18
+ tiny_image: [:string, optional: true],
19
+ # nil when the app has no Metacritic score
20
+ metascore: [:integer, optional: true],
21
+ platforms: [AppDetails::Platforms, optional: true],
22
+ streaming_video: [:boolean, from: :streamingvideo, optional: true],
23
+ # "full", "partial", or nil
24
+ controller_support: [:string, optional: true]
25
+ ) do
26
+ # The results. Steam sends "" as the metascore of apps without one.
27
+ # @param [Hash] json
28
+ # @return [Array<Hash>, nil]
29
+ def self.unwrap(json)
30
+ json[:items]&.map { |item| (item[:metascore] == "") ? item.merge(metascore: nil) : item }
31
+ end
32
+ end
33
+ StoreSearchResult.const_set(:Price, price)
34
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SteamApiClient
4
- VERSION = "1.1.0"
4
+ VERSION = "1.2.0"
5
5
  end
@@ -30,6 +30,9 @@ require_relative "steam_api_client/models/player_game_stats"
30
30
  require_relative "steam_api_client/models/news_item"
31
31
  require_relative "steam_api_client/models/global_achievement"
32
32
  require_relative "steam_api_client/models/app_details"
33
+ require_relative "steam_api_client/models/package_details"
34
+ require_relative "steam_api_client/models/app_dlc"
35
+ require_relative "steam_api_client/models/store_search_result"
33
36
  require_relative "steam_api_client/models/wishlist_item"
34
37
 
35
38
  require_relative "steam_api_client/endpoints"
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: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - João Pedro
@@ -54,13 +54,16 @@ files:
54
54
  - lib/steam_api_client/model/field.rb
55
55
  - lib/steam_api_client/model/scalars.rb
56
56
  - lib/steam_api_client/models/app_details.rb
57
+ - lib/steam_api_client/models/app_dlc.rb
57
58
  - lib/steam_api_client/models/friend.rb
58
59
  - lib/steam_api_client/models/game.rb
59
60
  - lib/steam_api_client/models/global_achievement.rb
60
61
  - lib/steam_api_client/models/news_item.rb
62
+ - lib/steam_api_client/models/package_details.rb
61
63
  - lib/steam_api_client/models/player_achievements.rb
62
64
  - lib/steam_api_client/models/player_game_stats.rb
63
65
  - lib/steam_api_client/models/player_summary.rb
66
+ - lib/steam_api_client/models/store_search_result.rb
64
67
  - lib/steam_api_client/models/wishlist_item.rb
65
68
  - lib/steam_api_client/request.rb
66
69
  - lib/steam_api_client/requestor.rb