
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
- A request hits
/users/<username>.png. - If a rendered image younger than
CACHE_TTL(default 5 minutes) exists, it’s served, or304 Not Modifiedis returned if the client’s copy matches. - Otherwise, profile and awards data are fetched (or read from the API cache), then current-game data. Overridden users skip that last call.
- 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
venvandpip - 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:
| Variable | Required | Description |
|---|---|---|
RA_API_USERNAME | Yes | Your RetroAchievements username. |
RA_API_KEY | Yes | Your 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:
| File | Purpose |
|---|---|
backgrounds/*.png | Your own 768x192 backgrounds. Once any exist, the bundled placeholders are left out of the rotation. |
softcore.png | Your own softcore overlay. Falls back to the bundled softcore_default.png. |
overrides.py | Per-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
| Route | Method | Purpose |
|---|---|---|
/users/<username>.png | GET | The signature image. |
/invalidate/<username> | GET, POST | Deletes that user’s cached image and API data. |
/invalidate_all | GET, POST | Clears 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.