Skip to content

Latest commit

 

History

130 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Random Steam Game Picker & Bulk Library Exporter

Live Application License: MIT

Random Steam Game Picker is an ultra-fast, sub-second web utility designed to solve backlog analysis paralysis. Enter any 17-digit Steam ID, vanity name, or Steam Community URL to instantly roll a random game from your library.

Need your data offline? Use the built-in Steam Library CSV Exporter to download your entire public collection—complete with game titles, App IDs, playtime stats, last-played timestamps, and real-time Steam Deck compatibility tracking—straight to an Excel-ready spreadsheet.

Built for gamers with massive backlogs, limited decision-making energy, and a suspicious tendency to press Choose Again.

Key Capabilities

  • Instant Sub-Second Game Roulette: Leverages a high-performance, multi-tier hybrid cache to deliver near-zero latency rolls that beat typical 8-second scrapers.
  • Smart Backlog Filtering: Toggle "Unplayed-Only" mode to instantly isolate and roll games with zero recorded Steam playtime.
  • Bulk Steam to CSV Export: Download your entire game index at /library-export with granular columns tracking hidden metadata.
  • Steam Deck Compatibility Audit: Automates batch-processing queries against the Steam Store API to index verified, playable, unsupported, or unknown deck statuses.
  • Zero Authentication Required: Operates entirely over public Steam APIs—no Steam sign-in or account credentials required.
  • Browser-Based Exclusions: Block specific games or persistent titles from your roulette pool right in your local session.

Screenshots

Library picker

Random Steam Game Picker home page

Selected game

Random Steam Game Picker result page

Features

  • Accepts a 17-digit Steam ID, vanity name, or Steam Community vanity URL
  • Selects a random game the user owns from their public Steam library
  • Filters the pool to games with no Steam playtime recorded when unplayed-only mode is enabled
  • Supports repeated picks without returning to the home page
  • Caches Steam library and Store API data to reduce upstream requests
  • Blocks unwanted games from future picks in the current browser and can reset the blocked list
  • Refreshes cached library data after the user's Steam library changes
  • Displays the selected game's description and recorded playtime
  • Opens the selected game through the Steam desktop client
  • Exports a public Steam library from the /library-export page
  • Includes game name, App ID, playtime, last-played time, and Steam Deck compatibility in CSV exports
  • Accepts numeric Steam IDs, vanity names, and Steam Community vanity URLs for both picking and exporting

Steam must be able to access the profile's game details. The saved Steam identity, unplayed-only preference, blocked games, and library-refresh timing are stored in browser cookies, so they do not synchronize across browsers or devices. The application does not require Steam sign-in.

How It Works

  1. Enter a Steam ID or vanity URL.
  2. The application loads the user's public Steam library.
  3. It removes games that do not match the selected options or that were blocked in the current browser.
  4. It randomly selects one eligible game and loads its details.
  5. Play it, choose again, or block it and roll once more.

Library export

  1. Open /library-export and enter a Steam ID, vanity name, or Steam Community vanity URL.

  2. Vanity input is resolved to a numeric Steam ID through the Steam Web API.

  3. The application loads the public library and fetches Steam Deck compatibility in batches.

  4. The browser downloads steam-library-{steamId}.csv with these columns:

    Column Value
    game Steam game name
    id Steam App ID
    hours Total recorded playtime in decimal hours
    hours_2_weeks Recent playtime in decimal hours; 0 when Steam reports none
    hours_windows Windows playtime in decimal hours
    hours_mac macOS playtime in decimal hours
    hours_linux Linux playtime in decimal hours
    last_played UTC timestamp in yyyy-MM-ddTHH:mm:ssZ format, or blank when Steam reports no meaningful date
    steam_deck verified, playable, unsupported, or unknown
    steam_store_url Steam Store URL derived from App ID: https://store.steampowered.com/app/{appId}/

All playtime columns use invariant decimal hours with up to two decimal places, including 0 for zero minutes. These additional fields use the owned-library response and require no additional Steam requests. Store metadata such as developer, genres, and pricing is deferred until a bounded, batched, cached design is available.

If Deck compatibility cannot be retrieved for a game, the export uses unknown rather than failing the whole download. Exports are limited to one request per IP address every 72 hours to protect Steam API capacity.

Tech Stack

  • .NET 10 and ASP.NET Core
  • Blazor Web App with Interactive Auto components
  • Steam Web API and Steam Store API
  • ASP.NET Core HybridCache with memory and SQLite-backed caching
  • SQLite for lightweight application data
  • Server-rendered Razor components with Interactive Auto picker and export forms
  • ASP.NET Core rate limiting, Data Protection, and liveness/readiness health checks
  • Optional Mission Control telemetry

Repository Overview

Project Responsibility
RandomSteamGame ASP.NET Core host, routed pages, controllers, middleware, persistence, and server services
RandomSteamGame.Client Interactive picker components, browser state, and the browser-facing API client
RandomSteamGame.Shared Contracts and abstractions shared by the host and client
SteamApiClient Steam Web API and Store API clients plus caching
RandomSteamGame.Tests Host, controller, provider, persistence, and component-supporting tests
SteamApiClient.Tests Tests for the Steam integration library

The central picker flow is GameController → GameProviderFactory → SteamProvider → SteamApiClient. The /library-export page is server-rendered for metadata and indexability, while LibraryExportForm uses Interactive Auto for browser identity access and vanity resolution.

Running Locally

Prerequisites

Setup

Clone the repository:

git clone https://github.com/JoyfulReaper/RandomSteamGame.git
cd RandomSteamGame

Store the Steam API key with .NET user secrets. Do not put a real key in a tracked configuration file:

dotnet user-secrets set "Steam:ApiKey" "YOUR_STEAM_WEB_API_KEY" --project RandomSteamGame/RandomSteamGame.csproj

Restore, build, and run the application:

dotnet restore RandomSteamGame.slnx
dotnet build RandomSteamGame.slnx --no-restore
dotnet run --project RandomSteamGame/RandomSteamGame.csproj --no-build

The development profile listens at http://localhost:5182.

SQLite data and development Data Protection keys are created automatically. Mission Control is disabled by default and is not required for local development.

Configuration

The only secret required for normal local use is the Steam Web API key:

Configuration key Environment variable Purpose
Steam:ApiKey Steam__ApiKey Reads public library data from the Steam Web API

The tracked RandomSteamGame/appsettings.json also contains non-secret settings for caching, rate limiting, allowed origins, canonical hosts, Data Protection, and optional Mission Control telemetry.

Steam options are validated at startup. Steam:ConnectionString must be nonblank; validation does not open the cache database. All six Steam:Cache policies must be explicitly configured with AbsoluteMinutes from 1 through 525600 (one year). Steam:RateLimiting must specify PermitLimit from 1 through 1000000 and WindowSeconds from 1 through 86400 (one day). These generous bounds reject obvious mistakes while accommodating the shipped 43200-minute caches and future rate-limit tuning. Invalid settings are rejected with their configuration paths, without clamping or substituting defaults.

Important operational settings include:

Configuration key Purpose
Application:NetworkMode Public (default) or AltNet; voting is Public-only; also controls defaults for remote Steam browser assets and beta probing
Application:NetworkName Optional display name such as I2P, Yggdrasil, or DN42; does not select capabilities
Application:AllowRemoteBrowserAssets Optional override; null allows Steam browser assets in Public and disables them in AltNet
Application:EnableBetaProbe Optional override; null enables beta probing in Public and disables it in AltNet; public beta links are shown only in Public
Application:CanonicalOrigin Externally visible origin for canonical/social metadata and sitemap URLs; unset Public uses the existing production HTTPS origin; required in AltNet, where HTTP or HTTPS is accepted
Application:BetaHost Host that receives X-Robots-Tag: noindex, nofollow
DataProtection:KeysPath Optional persistent Data Protection key-ring location
Steam:ConnectionString SQLite connection string for Steam response caching
Steam:Cache:* Cache durations for libraries, app details, vanity results, and Deck compatibility
Steam:RateLimiting:* General Steam API request limit; CSV export has a separate policy
Steam:LibraryExport:RateLimitMode PerIp (default) or Global; independent of network mode
Steam:LibraryExport:GlobalConcurrency Maximum active exports across all IPs in PerIp mode (default 2); Global mode always allows only one
Steam:LibraryExport:GlobalCooldownMinutes Global-mode cooldown from acceptance, including failed attempts (default 20; range 1–43200 minutes)
Ingress:EnableForwardedHeaders Optional override; null trusts configured proxy addresses in Public and ignores forwarding in AltNet
Ingress:EnableCloudflareVisitorHeader Optional override; null enables trusted Cloudflare scheme metadata in Public and disables it in AltNet; requires forwarding enabled
Ingress:TrustedProxies Exact immediate-peer IP addresses allowed to supply forwarding headers; defaults to 127.0.0.1 and ::1
Cors:AllowedOrigins Browser origins permitted to call the API
MissionControl:* Optional deployment and game-pick telemetry configuration
Telemetry:VisitorHashKey Optional key used to pseudonymize visitor identifiers for telemetry

When DataProtection:KeysPath is empty, development and non-Windows deployments use .keys/data-protection beneath the application content root. Production Windows deployments use a machine-level application-data directory.

For example, set Application__NetworkMode=AltNet, Application__NetworkName=I2P, and Application__CanonicalOrigin=http://example.b32.i2p to omit Steam header artwork from game responses, restrict Steam descriptions to text, formatting, and clickable links, and disable the beta probe/banner. Changing the display name does not change these capabilities. The optional boolean overrides can enable either capability independently for a deployment that supports it. Restricted descriptions omit all embedded images and inline styles; clickable links can still lead to external sites. Steam API access remains server-side.

The origin must contain only an HTTP(S) scheme, host, and optional port, without credentials, a path (other than /), query, or fragment. Public requires HTTPS; AltNet requires an explicit origin and accepts intentional HTTP origins. Incoming Host headers do not select the canonical origin. The same origin is used for canonical links, OpenGraph, JSON-LD application URLs, sitemap URLs, and the AltNet footer/404 example. Public keeps its existing network and beta links; AltNet shows the current network instead and omits public beta links even if probing is enabled.

/robots.txt and /sitemap.xml are generated from configuration. Public allows crawling and advertises its configured sitemap with the existing four page URLs. AltNet uses Disallow: / without advertising a sitemap; its sitemap remains available using the configured origin. Robots directives are advisory, not access control.

Cookies, ingress, and library exports

Public production keeps Secure antiforgery and server-written Steam identity cookies; Public development keeps its existing request-dependent antiforgery policy. AltNet derives both cookie security and the effective request scheme from Application:CanonicalOrigin, so an intentional HTTP origin works behind an HTTP tunnel without Secure cookies, while an HTTPS origin retains them even when the backend listener uses HTTP. Antiforgery validation, HttpOnly on antiforgery cookies, SameSite=Lax, host-only scope, and cookie lifetimes remain unchanged. Browser-written Steam identity, exclusions, and cache-reset cookies already use the browser's actual protocol. Visitor telemetry uses a keyed IP hash, not a visitor cookie.

AltNet ignores X-Forwarded-For, X-Forwarded-Proto, and CF-Visitor by default, including from loopback. Public keeps trusted loopback proxy/Cloudflare support. To enable forwarding on an ingress that supplies reliable client addresses, set Ingress__EnableForwardedHeaders=true and configure Ingress:TrustedProxies with its exact IP addresses. That ingress must strip visitor-supplied forwarding headers and write its own values; address trust alone cannot distinguish headers passed through by a tunnel. Cloudflare metadata has a separate opt-in for AltNet. Only one forwarding hop is consumed; untrusted peers cannot change the effective client IP. AltNet's effective scheme always comes from its configured origin.

For an ingress that presents every visitor as the same local address, configure:

Steam__LibraryExport__RateLimitMode=Global
Steam__LibraryExport__GlobalCooldownMinutes=20

Global mode allows one active export request, rejects additional requests with HTTP 429, and atomically reserves the shared cooldown after provider/Steam ID validation and before Steam work starts. Completion does not restart the cooldown; failures consume it. Rejections return readable error text and Retry-After seconds (rounded up). Capacity rejections before the cooldown reservation or after its expiry suggest a five-second retry; availability still depends on completion. State is local to this process and resets on restart. PerIp mode preserves the 72-hour cooldown after a successful export, one active request per IP, and the configurable global capacity. Export policy hints follow the selected policy.

JavaScript cookie tests can be run with node --test RandomSteamGame.Tests/cookieHelper.test.mjs in addition to the .NET suite.

Support and feature voting

/support includes optional Ko-fi/PayPal donations, curated feature voting, GitHub suggestions, private sponsorship inquiries, and Kyle's portfolio. Donations and sponsorship agreements are separate; sponsors must contact Kyle before paying. Existing picker support links are preserved.

The InteractiveAuto voting board makes all API calls from the browser through same-origin fetch and the existing dynamic antiforgery token flow. The backend uses GET /api/features and POST /api/features/{id}/vote with a required supported boolean, a protected HttpOnly cookie, and separate rate/capacity limits. This is anonymous browser-based support, not verified one-person-one-vote.

Startup never seeds proposals. Kyle can manually run the idempotent initial proposal SQL against the initialized application database. See proposal maintenance for the command, rerun behavior, and adding/editing accepted proposals. No production database is modified automatically.

Proposals support open, planned, completed, and closed. Only open accepts vote mutations; all statuses retain visible totals/selections. The new voting tables use additive, idempotent CREATE TABLE IF NOT EXISTS initialization. The API is enabled only with Application:NetworkMode=Public. Every AltNet deployment (DN42, I2P, Yggdrasil) returns 404 before issuing voter cookies or invoking voting persistence. The mode is process-wide: alternate ingress paths routed to one Public instance would still reach its API. Use separate configured instances for network isolation; this change does not modify proxies or production routing.

See anonymous feature voting for the schema, API contract, cookie lifecycle, FeatureVoting:* settings, security model, and Public-only availability and deployment considerations. Persist the existing Data Protection keys and database. IP abuse signals stay in server memory and are not stored in votes. No additional infrastructure or secret is required.

Docker

The repository Dockerfile builds on the .NET 10 SDK image, runs the full Release test suite as an image-build gate, publishes the application, and runs it as the image's non-root application user.

The Dockerfile declares port 5182. Set ASPNETCORE_HTTP_PORTS=5182 in the container environment, or publish whichever ASP.NET Core port your deployment configures. Mount persistent storage for the SQLite files and Data Protection key ring when deploying containers that may be replaced or recreated. To use the Dockerfile's prepared key directory, set DataProtection__KeysPath=/data-protection and mount a volume there. The application provides:

  • /health/live for process liveness
  • /health/ready for local dependency readiness, including SQLite and the Data Protection key directory

The production deployment is designed to run behind a loopback reverse proxy or Cloudflare Tunnel. Forwarded client IP and scheme headers are accepted only from loopback proxies; update the trusted-proxy configuration if a proxy reaches the application from another address, especially when relying on per-IP rate limiting.

Production hosting

The production Random Steam Game deployment runs on GreenCloud infrastructure.

GreenCloud VPS

Disclosure: This is an affiliate link. If you purchase through it, I may receive a commission at no additional cost to you.

Footer presentation

The footer's hosting message is configured independently of the deployment's network mode through Hosting:Message, Hosting:ProviderName, Hosting:Url, Hosting:AffiliateUrl, and Hosting:ShowAffiliateDisclosure. The default keeps the GreenCloud message and disclosure. An affiliate URL takes precedence over the regular URL. Leave both URLs empty to display the provider as plain text. Disclosure appears only for an affiliate link when enabled.

For a message-only deployment, for example:

Hosting__Message=Available via I2P
Hosting__ProviderName=
Hosting__Url=
Hosting__AffiliateUrl=
Hosting__ShowAffiliateDisclosure=false

Leave both the message and provider name empty to hide the hosting block entirely.

Search and indexing

The home page and library-export page publish canonical URLs, descriptions, and social metadata. The application also serves robots.txt, sitemap.xml, and WebApplication JSON-LD. The configured beta host sends X-Robots-Tag: noindex, nofollow, and generated random-game result pages are excluded from indexing.

Tests

Run all solution tests from the repository root:

dotnet test RandomSteamGame.slnx

To run an individual test project:

dotnet test RandomSteamGame.Tests/RandomSteamGame.Tests.csproj
dotnet test SteamApiClient.Tests/SteamApiClient.Tests.csproj

Contributing

Focused issues and pull requests are welcome. Describe the user-visible behavior, keep unrelated changes separate, and add or update tests when the behavior is testable.

Use GitHub Issues for bug reports and feature proposals.

Roadmap

Planned or considered improvements include:

  • Steam sign-in and account-linked preferences
  • Permanent blocked games that follow the user across devices
  • Favorites and picker history
  • More game filters
  • Saved and shareable game lists

These are future ideas, not currently implemented features.

Additional Documentation

  • Developer handoff — deeper architecture, request flow, caching, deployment, and debugging notes
  • Technical privacy/data inventory — public engineering reference for collection, storage, retention/deletion behavior, and operational unknowns; see /privacy for the Privacy Policy
  • Premium feature issues — account-related roadmap and likely implementation areas

License

Random Steam Game Picker is licensed under the MIT License.

The license does not protect you from backlog guilt or from the picker selecting exactly the game you were secretly avoiding.

Releases

Packages

Used by

Contributors

Languages