A comprehensive project featuring a website, API, and bots for displaying and sharing random quotes from the Halo video game series.
This repository provides three main functions:
- Website: https://haloquotes.teamrespawntv.com/ A modern, static web application that displays random quotes from all Halo games
- Bots: https://bsky.app/profile/LocalUnits.bsky.social Automated bots that post quotes on a scheduled run to both Bluesky and Twitter
- API: https://api.haloquotes.teamrespawntv.com/ A RESTful API that allows you to query quotes for your own projects
- Uniform random quotes. All quote files are loaded once at startup and flattened into a single pool, so every quote is equally likely regardless of how many quotes its game has.
- Filter by game. Pick a single game from the dropdown, or leave it on "All games". The choice is remembered between visits.
- Permalinks. Every quote has an address like
haloquotes.teamrespawntv.com/#/halo-2/6d0d9f1a, built from the quote's own id, so a shared link reopens the same quote rather than a new random one - and keeps working when quotes are added, removed or reordered. Older links that carry a position instead (#/halo-2/42) still resolve, by position. - Share and copy. Copy the quote to the clipboard, or open it prefilled in the Bluesky or X composer.
- History. Previous and Next walk back and forth through the quotes you have already seen.
- Keyboard shortcuts. Left and right arrows browse,
Ccopies. - Works without JavaScript. The page ships with a quote in the markup and explains what is unavailable.
Halo-Quotes/
├── .github/ # GitHub configuration
│ └── workflows/ # GitHub Actions workflows
│ ├── bsky-bot.yml # Bluesky bot automation (every 3 hours)
│ └── tests.yml # Test suite on every push and pull request
├── __tests__/ # Website tests (run against index.html/app.js)
│ ├── helpers/
│ │ └── load-page.js # Loads the real page into JSDOM
│ ├── accessibility.test.js # Menu focus, ARIA, reduced motion
│ ├── features.test.js # Filter, permalinks, sharing, shortcuts
│ ├── hamburger-menu.test.js # Menu structure and links
│ ├── metadata.test.js # Social cards, icons, structured data
│ └── quotes.test.js # Quote pool, history, quote data
├── api-worker/ # Cloudflare Worker API
│ ├── src/
│ │ ├── __tests__/
│ │ │ └── index.test.js # API tests
│ │ └── index.js # API worker implementation
│ ├── package.json # API dependencies
│ ├── package-lock.json # Locked dependency versions
│ ├── vitest.config.js # Vitest test configuration
│ ├── wrangler.toml # Cloudflare Worker configuration
│ └── README.md # API-specific documentation
├── bots/ # Social media bots
│ ├── bsky-bot.ts # Bluesky bot implementation
│ ├── twitter-bot.js # Twitter bot implementation (not scheduled)
│ ├── package.json # Bot dependencies
│ └── package-lock.json # Locked dependency versions
├── quotes/ # Quote data files
│ ├── halo-ce.json # Halo: Combat Evolved quotes
│ ├── halo-2.json # Halo 2 quotes
│ ├── halo-3.json # Halo 3 quotes
│ ├── halo-odst.json # Halo 3: ODST quotes
│ ├── halo-reach.json # Halo: Reach quotes
│ ├── halo-4.json # Halo 4 quotes
│ ├── halo-5.json # Halo 5: Guardians quotes
│ ├── halo-infinite.json # Halo Infinite quotes
│ ├── halo-wars.json # Halo Wars quotes
│ ├── halo-wars-2.json # Halo Wars 2 quotes
│ └── halo-multiplayer.json # Halo Multiplayer quotes
├── img/ # Image assets
│ ├── 256x256.png # Favicon
│ ├── apple-touch-icon.png # iOS home screen icon
│ ├── icon-192.png # Web manifest icon
│ ├── bg-1920.webp # Background, desktop
│ ├── bg-1280.webp # Background, laptop
│ ├── bg-960.webp # Background, mobile
│ ├── bg-1280.jpg # Background fallback for browsers without WebP
│ └── Team-Respawn-Full.png # Team Respawn logo
├── index.html # Main website HTML file
├── 404.html # Not found page
├── app.js # Website behaviour
├── styles.css # Website styles
├── og-image.jpg # Social sharing card (1200x630)
├── site.webmanifest # Web app manifest
├── package.json # Root package.json
├── package-lock.json # Root locked dependency versions
├── vitest.config.js # Root Vitest test configuration
├── CNAME # Custom domain configuration
├── robots.txt # Robots.txt for web crawlers
├── sitemap.xml # Sitemap for search engines
├── LICENSE # License file
└── README.md # This file
Each file in quotes/ holds one game:
{
"gameName": "Halo 2",
"quotes": [
{ "id": "6d0d9f1a", "text": "I need a weapon." }
]
}id is that quote's permanent handle. The website builds permalinks from it,
and the API returns it alongside the quote, so an id must never change once
it is published - not when the quote's text is corrected, and not when quotes
around it are added or removed. Changing one silently breaks every link anyone
has shared to that quote.
To add a quote, append an entry with a fresh id. The existing ids are the first 8 hex characters of the SHA-1 of the quote's text, which is a convenient way to mint a new one:
python3 -c "import hashlib; print(hashlib.sha1('Your quote here'.encode()).hexdigest()[:8])"Any unique lowercase alphanumeric string works, though - the value carries no meaning beyond being unique within its file. CI checks that every quote has an id, that ids are unique within a game, and that no quote text is repeated within a game.
The website must be served through a web server due to browser CORS restrictions. Opening index.html directly in a browser will not work.
Using Python:
# Python 3
python -m http.server 8000Then open http://localhost:8000 in your browser.
index.html loads the script with a cache-busting stamp - app.js?v=...,
where the stamp is a short hash of app.js. The site has no build step, so a
browser can otherwise hold a cached app.js while fetching freshly deployed
quote files.
You do not have to remember this: the Website CI job re-stamps index.html
and pushes the result back to the branch. To do it yourself, or to keep the
local test suite green:
npm run stampnpm test fails while the stamp is stale, which is also what catches a pull
request from a fork - CI cannot push to those, so it asks the contributor to
run the command instead.
The website tests run against the real index.html and app.js, so they fail
if the shipped page breaks.
npm install
npm test # website tests
npm run test:watchThe API worker has its own suite:
cd api-worker
npm install
npm testBoth run automatically on every push and pull request via
.github/workflows/tests.yml, along with a check that validates every file in
quotes/.
To run the API locally for development:
-
Navigate to the API worker directory:
cd api-worker -
Install dependencies:
npm install
-
Authenticate with Cloudflare (first time only):
npx wrangler login
-
Start the local development server:
npm run dev
This will start a local server (usually at
http://localhost:8787). -
Test the API:
- Visit
http://localhost:8787/for API information - Visit
http://localhost:8787/quotefor a random quote - Visit
http://localhost:8787/quote?game=halo-2for a quote from a specific game
- Visit
The Halo Quotes API is a Cloudflare Worker that provides programmatic access to random quotes from the Halo game series.
Production: https://api.haloquotes.teamrespawntv.com
Local Development: http://localhost:8787 (when running npm run dev)
GET /
Returns information about the API and available endpoints.
Response:
{
"name": "Halo Quotes API",
"version": "1.0.0",
"endpoints": {
"/quote": "Get a random quote from all games",
"/quote?game=<game-id>": "Get a random quote from a specific game"
},
"availableGames": [
"halo-ce",
"halo-2",
"halo-3",
"halo-odst",
"halo-reach",
"halo-4",
"halo-5",
"halo-infinite",
"halo-wars",
"halo-wars-2",
"halo-multiplayer"
],
"example": "/quote?game=halo-2"
}GET /quote
Returns a random quote from any Halo game.
Response:
{
"quote": "I need a weapon.",
"id": "6d0d9f1a",
"game": "Halo 2",
"gameId": "halo-2"
}id identifies the quote within its game and is stable across releases, so it
can be stored or turned into a permalink
(https://haloquotes.teamrespawntv.com/#/<gameId>/<id>).
GET /quote?game=<game-id>
Returns a random quote from a specific game.
Available Game IDs:
halo-ce- Halo: Combat Evolvedhalo-2- Halo 2halo-3- Halo 3halo-odst- Halo 3: ODSThalo-reach- Halo: Reachhalo-4- Halo 4halo-5- Halo 5: Guardianshalo-infinite- Halo Infinitehalo-wars- Halo Warshalo-wars-2- Halo Wars 2halo-multiplayer- Halo Multiplayer
Example Request:
GET /quote?game=halo-3
Response:
{
"quote": "What is it? More Brutes?",
"id": "534e9c1e",
"game": "Halo 3",
"gameId": "halo-3"
}All errors return JSON with an error field:
{
"error": "Invalid game: invalid-game. Available games: halo-ce, halo-2, ..."
}Status Codes:
400- Bad Request (invalid game ID, etc.)404- Not Found (invalid endpoint)405- Method Not Allowed (non-GET requests)500- Internal Server Error
The API supports CORS and can be accessed from any origin. All responses include appropriate CORS headers.
Automated bots that post random Halo quotes to social media platforms on a scheduled basis.
The bots support:
- Bluesky: Posts random quotes to Bluesky every 3 hours via
.github/workflows/bsky-bot.yml - Twitter/X:
bots/twitter-bot.jsis implemented but has no workflow, so it does not currently run on a schedule. Run it manually withnpm startfrombots/, or add a workflow modelled onbsky-bot.yml.
Found a mistake or want to contribute? Please feel free to:
- Open a Pull Request
- Fill out this form with the wrong quote: https://forms.gle/QRq66zXDMhUBrDYB8
- Report issues in our Discord server: https://discord.com/invite/teamrespawn
- The Halo 3 loading screen project for the background: https://github.com/Xephorium/Halo3LoadingScreen?tab=readme-ov-file