Skip to content

About

Room and machine info cards for CTF writeup sites (TryHackMe, Hack The Box). No framework, no build step.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

writeup-cards

Room and machine info cards for CTF writeup sites. Point a writeup at its TryHackMe room or Hack The Box machine with url: in the front-matter, and writeup-cards fetches the platform's public details and renders a card at the top of the writeup:

  • Hack The Box: synopsis, difficulty, release and retirement, OS, creators, rating, user/root owns, first bloods, the player-rated difficulty bars and the radar graph (Enumeration, Real-Life, CVE, Custom Exploitation, CTF-Like).
  • TryHackMe: description, difficulty, release date, type, estimated time, people joined, votes and access.

No framework, no build step and no runtime requests: a Python script saves JSON, and a dependency-free ES module turns it into HTML at build time or in the browser. Charts are inline SVG. The cards are accessible, escape every value, and follow light/dark mode.

Open examples/demo.html to see both cards with sample data (run node examples/render.mjs to regenerate it).

Quick start

pip install pillow                      # icons are saved as small WebP files
export HTB_APP_TOKEN=...                # optional, for Hack The Box (see below)
python writeup_cards.py fetch writeups/ --out meta/

Your writeups need a url: (or link: / room:) in their front-matter:

---
title: Titanic
url: https://app.hackthebox.com/machines/Titanic
---

That writes meta/htb/machine-titanic.json (and .webp), or meta/thm/<room-code>.json for a TryHackMe room. Then render:

import { roomCardHtml } from "./room-card.js";

const meta = JSON.parse(await readFile("meta/htb/machine-titanic.json", "utf8"));
const html = roomCardHtml(meta, { iconBase: "/meta/htb/" });

and include room-card.css on the page. examples/render.mjs is a complete build-time example.

roomCardHtml(meta, options)

Option Default
iconBase none URL folder holding the saved icon (meta.icon). Without it, or meta.iconSrc, no icon is shown.
locale "en-GB" Locale for numbers and dates.
headingLevel "h2" Tag for the room name, to fit your page's outline.

difficultyBars(buckets) and radarGraph(values) are exported too if you only want the charts. Links and icons are only rendered for http(s) or site-relative URLs.

Theming

Override the --wc-* custom properties on .wc-card or any ancestor, for example:

.wc-card { --wc-surface: #0b0c0e; --wc-link: #9fef00; --wc-font: "Inter", sans-serif; }

Difficulty colours are --wc-easy, --wc-medium, --wc-hard and --wc-insane.

Data sources

Hack The Box

Uses the official v4 API with your own App Token: on app.hackthebox.com go to Account Settings → App Tokens, create one, and set it as HTB_APP_TOKEN (in CI, as a repository secret). Machines and challenges are supported; Academy modules and CTF-event challenges aren't (they have no public details). Retired machines are fetched once and kept.

Check your token and see exactly what gets parsed:

python writeup_cards.py probe machine:Titanic

TryHackMe

Uses TryHackMe's public room API. Plain HTTP requests are often refused by TryHackMe's bot protection (HTTP 429), almost always from CI runners. When that happens, writeup-cards keeps the details it already saved and says so.

To refresh TryHackMe rooms, run locally with --browser. That retries with an ordinary headless browser session, which a home connection is normally let through. It needs Playwright (pip install playwright && playwright install chromium).

python writeup_cards.py fetch writeups/ --out meta/ --browser

Then commit the updated meta/ folder.

writeup-cards does not disguise browsers or work around bot protection, and pull requests that do won't be accepted. If you need TryHackMe data in CI, ask TryHackMe about API access.

Refreshing

Saved details refresh after --refresh-days (default 7). A failed fetch keeps the last saved file, and the command exits 0, so a bad day never breaks your site build.

GitHub Action

name: Writeup cards
on:
  schedule: [{ cron: "0 6 * * 1" }]   # weekly
  workflow_dispatch:
permissions:
  contents: write
jobs:
  cards:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: paracausaltelemetry/writeup-cards@v1
        with:
          writeups: writeups
          out: meta
          htb-token: ${{ secrets.HTB_APP_TOKEN }}
      - run: |
          git config user.name "github-actions[bot]"
          git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
          git add meta
          git diff --cached --quiet || (git commit -m "Refresh writeup cards" && git push)

Then rebuild your site as usual. In CI the Action refreshes Hack The Box; refresh TryHackMe locally as described above.

Requirements

  • Python 3.9+ and Pillow for fetching; Playwright only for --browser.
  • Any modern browser or Node 18+ for room-card.js (an ES module).

Development

python -m unittest discover -s tests
node --test tests/room-card.test.mjs
node examples/render.mjs

Credits

Built for paracausaltelemetry.com, where it powers the writeup pages. Not affiliated with TryHackMe or Hack The Box. Their names, data and images belong to them, and the sample data here is for demonstration only. Use their services within their terms.

Licence

MIT

About

Room and machine info cards for CTF writeup sites (TryHackMe, Hack The Box). No framework, no build step.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages