ra-signatures example

What it is

A small self-hosted Flask app that renders your RetroAchievements profile as a 768x192 PNG at an unchanging URL, like https://ra.example.com/users/YourRAUsername.png. Every time the image is requested, it shows your current point totals, games mastered, the game you’re playing and its rich presence line, so a forum signature stays up to date without you touching it.

Typical use: embed it in a retro gaming forum signature, a Discord profile, or anywhere else that accepts an image URL.

What it does

  • Pulls hardcore points, softcore points and games mastered from the RetroAchievements Web API.
  • Shows the current game, platform, rich presence (“Currently: …”) line and game icon. Long titles drop to a smaller font, and long rich presence messages are truncated with an ellipsis.
  • Draws the console’s system icon inline on the Platform line, sourced from RetroAchievements’ own icons.
  • Renders emoji in titles and rich presence in full color (Noto Color Emoji), with a monochrome fallback (OpenMoji) instead of tofu boxes.
  • Swaps characters the pixel font can’t render (bullets, en/em dashes, curly quotes) for ASCII lookalikes.
  • Picks a random background from ./backgrounds/ on each render.
  • Applies a warning overlay if the account has any softcore points.
  • Supports per-user overrides that fake the “now playing” section while real stats stay live. Mostly useful for pranking friends.
  • Caches rendered images and API responses on disk, with ETag and Last-Modified support, to keep API usage low.
  • Refuses to start if its API credentials are missing, instead of failing on every request.

How it works

  1. A request hits /users/<username>.png.
  2. If a rendered image younger than CACHE_TTL (default 5 minutes) exists, it’s served, or 304 Not Modified is returned if the client’s copy matches.
  3. Otherwise, profile and awards data are fetched (or read from the API cache), then current-game data. Overridden users skip that last call.
  4. Pillow composes the image, writes it to ./cache/, and serves it.

All API calls go through a single lock with retry/backoff, and identify themselves with a ra-signatures/<version> User-Agent.

Requirements

  • Python 3 with venv and pip
  • Python packages: flask, gunicorn, pillow, python-dotenv, requests
  • A RetroAchievements account and its Web API key (in your account settings)
  • Optional: Nginx and Certbot for HTTPS, and a CDN such as Cloudflare in front

Configuration

Credentials go in .env, copied from the included .env.example:

VariableRequiredDescription
RA_API_USERNAMEYesYour RetroAchievements username.
RA_API_KEYYesYour RetroAchievements Web API key. Treat it like a password.

Everything else is a constant at the top of app.py: cache TTLs, console icon size and spacing, emoji scaling, and the character substitution map.

Local customizations are gitignored, so they never end up in a commit:

FilePurpose
backgrounds/*.pngYour own 768x192 backgrounds. Once any exist, the bundled placeholders are left out of the rotation.
softcore.pngYour own softcore overlay. Falls back to the bundled softcore_default.png.
overrides.pyPer-user “now playing” overrides, copied from overrides.example.py.

Usage

# Install
sudo git clone https://github.com/stevelaporte/ra-signatures.git /opt/ra-signatures
cd /opt/ra-signatures
sudo python3 -m venv venv
sudo venv/bin/pip install -r requirements.txt
sudo cp .env.example .env && sudo chmod 600 .env    # then add your credentials

# Run (the README has a full systemd unit and Nginx config)
venv/bin/gunicorn --workers=1 --threads=1 --bind 127.0.0.1:5000 app:app

Embed it anywhere that takes an image URL, e.g. as forum BBCode:

[img]https://ra.example.com/users/YourRAUsername.png[/img]

Fake a friend’s “now playing” section with an override:

# overrides.py
OVERRIDES = {
    "someusername": {
        "Title": "Barbie Dreamhouse Adventures",
        "ConsoleName": "PC",
        "RichPresenceMsg": "Decorating the Dreamhouse",
    },
}

Restart the service after editing overrides.py, then clear that user’s cache:

curl -X POST http://127.0.0.1:5000/invalidate/someusername

Endpoints

RouteMethodPurpose
/users/<username>.pngGETThe signature image.
/invalidate/<username>GET, POSTDeletes that user’s cached image and API data.
/invalidate_allGET, POSTClears all cached images and API data.

Notes

  • The invalidation endpoints are unauthenticated. The example Nginx config only proxies /users/, so call them from the server itself.
  • Run Gunicorn with one worker and one thread so API requests stay serialized, and put a CDN in front if you can. Both keep you well clear of RetroAchievements’ rate limits.
  • The bundled backgrounds are generic placeholders. The app looks best with real 8- and 16-bit game backgrounds, which can’t be redistributed, so bring your own.
  • Bundled fonts keep their own licenses (Pixellari, Noto Color Emoji under OFL 1.1, OpenMoji under CC BY-SA 4.0). Full texts are in the repo’s licenses/ folder.
  • Not affiliated with RetroAchievements. Game and console icons are fetched at runtime and not redistributed.

Source

The code is on GitHub at stevelaporte/ra-signatures , with release notes on the releases page and a full history in CHANGELOG.md . It’s licensed under GPL-3.0.