Posted on :: Tags:

The Problem Nobody Thinks About Until It's Too Late

I've got 75+ game reviews on backloggd.com. I like the social element, the community, leaving snarky comments. But one thing nags at me: I don't own any of it.

Backloggd is run by a single developer. No API. If he gets hit by a bus, or pivots to monetization, or just burns out, my entire review archive evaporates.

All those hours documenting what I actually thought about these games, gone, because a service I have zero control over decided it was done.

I used to want to build a TUI for backlog tracking. Backloggd solved that. But it created a new problem: total dependency.

Caveat: I'm a Backloggd backer and I genuinely like the service. This isn't about distrust. It's about agency. I want my data on my terms.

So I decided to grab it before it disappears.

The Asterisk: Historical Data

There's a problem I'm not solving today: all my reviews from before today are still trapped on Backloggd.com's servers. The RSS feed only shows the ~25 most recent reviews. Everything before July 2025? Gone from the feed, still on the site. That is an entirely different beast.

For now, I'm solving the forward problem: from today onward, every review gets captured. The historical stuff? I'll circle back to that when I figure out the scraping approach. This post is just the foundation: the infrastructure that makes the future easier.

So this article covers future-proofing my reviews starting now, not rescuing the past. That's coming next. Probably. Hopefully.

The Trap (You Know This Part)

I started simple. "Just backup the RSS feed. Extract the data. Done."

Then my brain went:

  • "What if I parse it into JSON?"
  • "What if I build a dashboard showing my gaming patterns over time?"
  • "What if I create an AI insights engine analyzing my review sentiment?"
  • "Wait, this could be a case study. Personal branding moment. Blog post material."

By the time I got to "case study," I was building a 40-hour project in my head while staring at a 1-hour problem.

This is the pattern. It never changes. The fun part (pathfinding, solving, optimizing) is maybe 5% of the work. The boring part (polish, iteration, maintenance) is 95%. And I have the discipline of a goldfish with ADHD when it comes to the boring part.

The Decision (Fast)

Remove the constraint.

The constraint was: "Will this impress client prospects?" The answer is nobody fucking cares about my gaming review backup strategy. This is my blog. I put whatever I want on it. If someone reads it and thinks "oh that's useful," great. If not, irrelevant.

So: minimal bash script, honest article about why I didn't over-engineer it, publish, done.

Here's What I Actually Built

The logic is stupid simple: poll the Backloggd RSS feed, extract review IDs, check if I've already got them, append the new ones, send a webhook notification if anything breaks.

That's it.

The Script

I created a directory in my cloud-synced folder (I use mega.io for this):

mkdir -p ~/MY_SYNC_FOLDER/scripts/backup/backloggd-archive

Then create the script:

#!/bin/bash

FEED_URL="https://backloggd.com/u/YOUR_USERNAME/reviews/rss/"
ARCHIVE_DIR="$(dirname "$0")"
ITEMS_FILE="$ARCHIVE_DIR/items.xml"
ERROR_LOG="$ARCHIVE_DIR/error.log"
WEBHOOK_URL="https://ntfy.sh/YOUR_TOPIC_NAME"

mkdir -p "$ARCHIVE_DIR"

# Fetch RSS
FEED=$(curl -s --max-time 10 "$FEED_URL" 2>/dev/null) || {
  curl -d "Backloggd backup failed: Fetch error at $(date '+%Y-%m-%d %H:%M:%S')" "$WEBHOOK_URL"
  echo "$(date '+%Y-%m-%d %H:%M:%S') - FETCH FAILED" >> "$ERROR_LOG"
  exit 1
}

# Extract all items
ITEMS=$(echo "$FEED" | sed -n '/<item>/,/<\/item>/p' 2>/dev/null) || {
  curl -d "Backloggd backup failed: Parse error at $(date '+%Y-%m-%d %H:%M:%S')" "$WEBHOOK_URL"
  echo "$(date '+%Y-%m-%d %H:%M:%S') - PARSE FAILED" >> "$ERROR_LOG"
  exit 1
}

# Process each item, skip if ID already exists
ADDED=0
while IFS= read -r line; do
  if [[ $line == *"<item>"* ]]; then
    ITEM="$line"
    while IFS= read -r next_line; do
      ITEM="$ITEM"$'\n'"$next_line"
      if [[ $next_line == *"</item>"* ]]; then
        break
      fi
    done
    
    ID=$(echo "$ITEM" | grep -oP 'review/\K[0-9]+' | head -1)
    
    if [ -z "$ID" ] || grep -q "review/$ID/" "$ITEMS_FILE" 2>/dev/null; then
      continue
    fi
    
    echo "$ITEM" >> "$ITEMS_FILE"
    ((ADDED++))
  fi
done <<< "$ITEMS"

echo "$(date '+%Y-%m-%d %H:%M:%S') - Added $ADDED new items" >> "$ERROR_LOG"

Then just make the file executable:

chmod +x ~/YOUR_SYNC_FOLDER/backup/backloggd-archive/backloggd-backup.sh

What to replace:

  • YOUR_SYNC_FOLDER: Whatever you use (mine is /home/balint/Sync)
  • YOUR_USERNAME: Your Backloggd username
  • YOUR_TOPIC_NAME: Pick any name

How It Works

  1. Fetches the RSS feed - Gets your latest reviews from Backloggd
  2. Extracts review blocks - Uses sed to pull complete <item> elements (preserves all metadata, no parsing overhead)
  3. Deduplicates by review ID - Extracts the ID from the review URL, checks if it's already in our file, skips if yes
  4. Appends new reviews - Only the new ones get added
  5. Logs everything - Success count, timestamps, all errors
  6. Notifies on failure - If fetch or parse breaks, sends a webhook notification (more on that in a second)

Why raw XML and no parsing layer? Because I don't know what I'll want to query later. Raw XML preserves options. If I decide in six months I want to export to JSON or filter by rating or whatever, the raw data is still there, unchanged.

A good lesson here was while thinking this through: do not transform the data at extraction time. Transform when actually needed. Leaves format wide open and does not lock me into anything.

Granted: this is technically NOT a valid RSS, BUT it's structured. Easy to query later. It's a future me problem that let me make a decision fast and move fast.

Setup: Notifications

The script uses ntfy.sh , a dead simple webhook notifications. No account needed, no complex SMTP config.

  1. Install the ntfy app on your phone (Android/iOS) or use the web version
  2. Open the app, tap the "+" button, enter a topic name (can be anything, make sure it's not guessable, so strangers don't get funny ideas).
  3. Hit subscribe
  4. Done. Any message to that URL now shows up on your phone

In the script, both error cases (fetch fail, parse fail) send a webhook:

curl -d "Backloggd backup failed: Fetch error at $(date '+%Y-%m-%d %H:%M:%S')" "$WEBHOOK_URL"

If things work? Silent. If things break? Notification.

Automate It

I've added this to my crontab (I use Arch, BTW):

crontab -e

Add this line (runs daily at 9 PM):

0 21 * * * ~/MY_SYNC_FOLDER/scripts/backup/backloggd-archive/backloggd-backup.sh

Save and exit. Done. Every day at 9 PM, if I wrote a new review, it gets backed up (appended to existing file to avoid complications).

Storage

The whole backloggd-archive folder lives in my Mega.io encrypted sync folder. Not GitHub, not some public repo. Encrypted at rest, synced automatically. If my computer dies, I reinstall, mount Mega, add the cron line back, and it's running again.

That's the actual portability I need, not "how do I set up CI/CD," but "if this machine explodes, how fast can I resurrect this?"

Why This Actually Works

The lesson I reluctantly learn time and again: Simple systems survive. Complex systems get abandoned. I tended to create complex systems (whenever I did not abandon them first = 90% of the time). So here I'm writing this article 2 hours after I thought of this project.

My reviews are safe. I own the data. The script runs in the background. I don't think about it again.

That was the entire point. Now I can go off and kill monsters in Resistance 3.

Thank you for coming to my TED talk.


Bálint Kőrösi
Bálint Kőrösi
I'm a systems architect for online businesses: I help founders understand and fix subscriber and revenue systems they’ve quietly outgrown.
About · Home · Email me