Archive Steam reviews for easy integration in a static site generator

While Steam does have an API and even some endpoints that allow accessing your own data, there is no official way of retrieving all of your game reviews. You can use the API to download reviews per game, but not per user – which is what I’m much more interested in.

So I wrote the archive-steam-reviews script to do just that.1

I enjoy publishing my reviews on Steam, but I don’t want them to solely live on that platform. Automatically downloading them into simple text files does two things:

  1. I can ensure my reviews don’t vanish in case the platform ever goes away – however unlikely that may be in the case of Steam.
  2. More importantly, integrating the reviews into this blog in a somewhat automated fashion becomes feasible.

Here’s a brief summary on how I went about it:

Add all Steam reviews to Hugo

Running the script with both the --all and --save parameters will a) download all existing reviews instead of just the first page, and b) save them into Markdown files with YAML front matter – just like my Hugo blog posts are stored already.

The script downloads the files into the current working directory. I decided to create a new steam directory within my /content directory. This is the same level where the posts directory housing all my blog posts already exists as well. In Hugo terminology that means I created a new content type called steam.

Initially I thought that I would then continue to create some custom templates that only apply to this content type, and have the reviews show up as blog posts fully automatically. However, I ultimately found that undesirable: I want a bit more manual control over how and when posts get published, as well as the ability to add additional commentary below or above the Steam review text.

It’s quite possible that storing the data in this format and/or place is not ideal, and that Hugo offers a better way to solve this – please do let me know, because I always struggle getting Hugo to do the things I want it to do in a way that doesn’t feel hacky.

Add a custom Hugo short-code

Instead of having each review show up as a post automatically, I decided to add the ability to include a Steam review within a normal post. Adding a custom short-code is pretty simple and rather similar to templating. Here’s what I ended up adding:

{{ $filename := path.Join "/steam" $.Page.Params.steam }}
{{ $page := site.GetPage $filename }}
{{ with $page }}
_Below is an automated copy of my [Steam review]({{ $page.Params.review_link }}), which was published there on **{{ $page.Params.review_date }}**{{ with $page.Params.last_updated }} and last edited on **{{ $page.Params.last_updated }}**{{ end }}. My total playtime is **{{ $page.Params.total_playtime }}** hours{{ with $page.Params.playtime_at_review }}, with **{{ $page.Params.playtime_at_review }}** hours played when the review was written{{ end }}._

---

{{ .Content }}

---
{{ end }}%

With the above code stored as layouts/shortcodes/steam_review.html I can use {{% steam_review %}} anywhere in my Hugo posts to include the downloaded review data.

To specify which review to include, I have to add a steam attribute with the corresponding Steam App ID to my post’s front matter. I can then access that via $.Page.Params.steam in the short-code to construct the $filename of the review data I want to include.
With site.GetPage $filename Hugo loads that file as if it was a normal Hugo post – which is very convenient, as that includes parsing the front matter. This allows me to use for example $page.Params.review_link to reference data from the review’s front matter, and .Content for the review text itself.

I could also have made a short-code with an explicit parameter that would be called {{% steam_review 400 %}} to include the review data for Steam App ID 400,2 but I wanted the App ID reference to be a part of the blog post front matter. That way it’s a clean 1:1 relationship that could more cleanly be processed in further automations.

Disable rendering the Steam reviews on their own

There’s one more detail worth mentioning: Because I store the review files within my /content directory, they are valid content for Hugo. Due to my Hugo configuration and templates, none of the reviews showed up in any lists or feeds. But it was absolutely possible to navigate to /steam/123456 directly and view the generated page for that specific review. Because the downloaded review files don’t have the standard front matter attributes (like title or date) these pages looked pretty crude.

As I only want the review files there for use with the short code and not as actual, rendered pages, I added the following configuration to disable rendering and listing of any files within the /content/steam/ directory:

[[cascade]]
  _build.render = false
  _build.list = false
  [cascade._target]
    path = "/steam/*"

Figuring that part out was rather annoying, because the exact syntax needed was not clear to me at all from both relevant docs pages, and responses in the forums had big owl energy. (Not necessarily meant as criticism, I just always find it pretty hard to fully grok the concepts within Hugo.)

Usage going forward

With all that in place, now I just need to run archive_steam_reviews.py --username manu_faktur --save once in my /content/steam directory after posting a new Steam review to have it available for a blog post. The script would also overwrite some existing reviews (because the entire first page is downloaded), but because everything is nicely versioned in Git I can always verify with a quick diff that nothing has changed there.

And that’s about it! You can see it in action in this post and its source, as well as the relevant preparatory commits to my blog’s repository in general.


  1. I won’t open sign-ups on my personal GitLab instance, but contributions are welcome here. Federated merge requests when?! ↩︎

  2. ID 400 is Portal, by the way. ↩︎