Skip to content
SideQuest-GuildPublic

About

Website, discord bot, and API for the SideQuest guild in WoW: Forever

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

SideQuest Guild website

The website and API for SideQuest, an Alliance guild on World of Warcraft: Forever. Members sign in with Discord, sign up for events, check their loot points and see the Hall of Heroes. Officers run events, attendance and the roster from the Officers page. It runs at sidequest-guild.com.

Two other repos work with it: the Discord bot, SideQuest-Guild/bot, and the in-game addon, SideQuest-Guild/addon. Neither one opens the database. They both go through the API.

flowchart LR
    Browser -->|"/ (static site)"| API
    Discord <-->|gateway + REST| Bot["SideQuest-Guild/bot<br/>discord.py plugins"]
    Bot -->|"/api/guilds/... + X-API-Key"| API["api/<br/>Litestar backend"]
    Addon["SideQuest-Guild/addon<br/>in game"] -->|"raid nights via /officers/sync/"| API
    Website["website/<br/>Astro → dist/"] -.->|built output| API
    API --> DB[(PostgreSQL)]
    Common["packages/sidequest-common<br/>models · schemas · leveling math"] -.-> Bot & API
Loading

What's on the site

Page Who sees it What it's for
/ everyone The next raid, who is in Discord right now, and what the guild is recruiting.
/events/ everyone Upcoming and past events. Members also see who signed up, and can sign up themselves.
/raids/ everyone How raid nights, SQL loot and attendance work.
/ledger/ everyone Loot points for every raider, the loot rules, recent loot and boss kills.
/heroes/ everyone (the rest is members) The Hall of Heroes: a public "most grouped" board, plus Renown, raids attended, chat level and badges for each member.
/guild/ members The in-game roster, who can craft what, the guild bank log and guild news.
/addons/ everyone The addons the guild uses, with download links.
/account/ anyone signed in Link Battle.net, get a calendar link, mark away dates, or delete your account.
/officers/ officers Events, addons, recruiting, attendance and group voice stats. Game Masters also get members, the activity log and moderation.
/officers/sync/ officers Move a raid night from the addon to the site, and points back to the addon.
/rules/, /privacy/, /terms/ everyone Guild rules, what the site keeps about you, and the terms.
/api-reference/ everyone The same API reference below, styled like the rest of the site.

A member is anyone in the SideQuest Discord who has signed in. An officer holds Quest Master or Quest Guide, and a Game Master holds Quest Master. Roles are read at sign-in and kept up to date by the bot, so a promotion in Discord shows on the site within a few minutes.

Run it on your computer

You need uv, mise and Docker. uv installs the right Python and mise installs the right Node.

cp .env.example .env      # set API_KEY to any long random string
make install              # Python and Node packages
make db-up migrate        # start Postgres in Docker and create the tables
make dev                  # the site and the API at http://localhost:8000

make dev reloads as you edit. The site and the API share port 8000, so the sign-in cookie works on both.

Signing in needs a Discord app (see Sign-in). Without one, the public pages still work and the sign-in button returns an error.

To run the bot against your local API, clone the bot repo and set its API_URL to http://localhost:8000 and its API_KEY to the same value as yours.

make help lists every command.

Before you push

make ci runs every check GitHub runs: formatting, lint, type checks, the Python tests, the website checks, a build and the browser tests. make fmt fixes formatting.

  • Python tests (make test) start the real API with a fresh database for each test, so they don't need Postgres.
  • Browser tests (make web-e2e; run make web-e2e-install once first) open every page in desktop Chrome, on an iPhone, an Android phone and a 320px phone. The API is faked from website/e2e/data.ts. A test fails if anything scrolls sideways, falls off the screen, doesn't load or logs an error. npx playwright show-report in website/ shows the screenshots.
  • Type checks fail only on new errors. Known ones are listed in pyrefly-baseline.json. After you fix some, run make type-baseline.

After you change a database model, make migration MSG="add away dates" writes the migration and make migrate applies it.

Where things are

Folder What's in it
api/ The API, built with Litestar. Each feature has a folder in api/src/sidequest_api/domain/. Migrations are in api/src/sidequest_api/lib/migrations/.
website/ The site, built with Astro. Pages are in src/pages/, page scripts in src/scripts/, paintings in assets/.
packages/sidequest-common/ The database models, the shape of every API request and reply, and the SQL loot rules. The bot uses it too.
tests/ Python tests.
website/e2e/ Browser tests.

With the API running, /schema lists every route with what it takes and returns; /api-reference/ on the site shows the same thing, themed to match, with routes grouped by who calls them (public, members, officers, Game Masters, the addon's sync tool, MCP, and the Discord bot).

How the pieces talk

  • The bot calls /api/guilds/{guild_id}/... with the shared API_KEY. About once a minute it asks what changed (new events, sign-ups, who should hold which role) and reports what happened in Discord (button clicks, voice channels, name and role changes). A change on either side reaches the other within about a minute.
  • The addon records raid nights in game. An officer sends one to the site from /officers/sync/ by pasting a SQX1: string, uploading the addon's save file, running sidequest-sync, or running SideQuest Companion, a tray app that uploads on its own and stages the import back. The site sends points back as an SQI1: string for /sq import. Both formats are in packages/sidequest-common/src/sidequest_common/schemas/ (epgp.py and guild_hub.py).
  • The bot pins sidequest-common to a commit of this repo. If you change a schema the bot uses, keep the old fields working until the bot moves to the new commit.

How the features work

Events. Each event on the site gets a Discord event and a thread in the events forum. The thread has Going, Maybe and Can't buttons, so members can sign up from either place. Edits on the site reach Discord in about 15 seconds, and edits in Discord reach the site at once. A repeating event keeps its next 14 days filled in as ordinary events, each with its own sign-ups.

Two kinds of points. Renown is for the Hall of Heroes. Each event is worth some: a raid 10, a dungeon or world boss 5, leveling and PvP 3, anything else 2, and an officer can change it for one event. EP and GP are the SQL loot points. The addon records them and the site shows them on /ledger/. In the code and the API, Renown is still called ep. The loot rules and prices are in schemas/epgp.py.

Attendance. Officers mark who came on the Officers page. Anyone who spent 30 minutes in voice during the event is ticked already. Every number about who came (Hall of Heroes, badges, Renown, the attendance report) uses one rule, attended_condition() in api/src/sidequest_api/lib/attendance.py. Use it for any new one too.

Hall of Heroes and badges. Seasons are listed in SEASONS in schemas/heroes.py. Season One starts on November 4, 2026. Each badge is also a Discord role, which the bot grants and announces.

Badge How to earn it
First Steps Come to your first guild event.
First Raid Come to your first raid.
Raid Regular Come to 10 raids.
Raid Veteran Come to 25 raids.
Perfect Month Come to every raid in a month that had at least 4.
Founding Member Be on the guild roster before launch.
Chatterbox Reach chat level 10.
Top Helper Come to the most dungeons and leveling groups last month. It moves to the next winner each month.
Party Animal Group up from #lfg 50 times, ten minutes or more with at least one other member.
Party Leader Start 25 groups from #lfg that others joined.

Discord roles from the site. Signing in gives Signed the Ledger, and linking Battle.net gives Battle-Tagged. New people who haven't signed in get At the Gate. When a Game Master confirms someone on the in-game roster, they get New Arrival, which opens the guild channels. The bot checks every member once a minute, and it never hands out a role with moderator powers or one above its own.

Calendar and away dates. Each member can get a private calendar link for Google or Apple Calendar with the events they signed up for. Anyone with the link can read it, so "Make a new link" on /account/ turns the old one off. Members can also mark dates they'll be away. The bot gives them Away on a Quest for those days, and the raid chart shows who is away.

Groups and #lfg voice channels. A locked #lfg channel in Discord has one button per kind of group: Dungeon, Raid, World Boss, Leveling, PvP and Hangout. Clicking one makes a voice channel right away, and a live card in #lfg shows who's in it. The owner can rename it, set a player cap, lock it, hand it off or end it from buttons in its own text chat. A channel that sits empty, or that nobody joins, goes away by itself after two minutes. The bot reports each group to the API: who started it, what kind, who was in it and for how long, and who grouped with whom. Officers see it all on /officers/#groups: most grouped, best duos, busiest hours, what gets run most, and a list of groups with their chat (kept 30 days for moderation, then deleted). Everyone sees a "most grouped" board on /heroes/, and the Party Animal and Party Leader badges come from it too. The Discord side is plugins/group_finder.py in the bot repo; the API side is api/src/sidequest_api/domain/groups/.

Your data. If you start keeping something new about members, add it to /privacy/ and make delete_account in api/src/sidequest_api/domain/members/services.py remove it.

MCP access for officers

Officers can let Claude Code, omp or any other MCP client work on the guild for them: look up members, read and post in channels, post announcements, and make, change or cancel events. Make a token on /officers/ under Claude / MCP access, then run:

claude mcp add --transport http sidequest https://sidequest-guild.com/api/mcp --header "Authorization: Bearer <token>"
  • The site shows the token once and keeps only a scrambled copy. Revoke it on the same page.
  • It works only while you are an officer. A few tools, such as the attendance report, need a Game Master.
  • No tool can hand out or take away a role with moderator powers, a role another app manages, or a role above the bot's own.
  • Everything a token does shows in the log on /officers/#log, under your name.
  • The site calls Discord with the bot's token (DISCORD_TOKEN). Without it, the Discord tools say so and the rest work.

The code is in api/src/sidequest_api/domain/mcp/.

Sign-in

Discord. In the Discord Developer Portal, open the Game Master app, then OAuth2. Add the redirect http://localhost:8000/auth/discord/callback (on the live site, https://sidequest-guild.com/auth/discord/callback). Copy the client ID and secret into WEB_DISCORD_CLIENT_ID and WEB_DISCORD_CLIENT_SECRET, and set WEB_SESSION_SECRET to a long random string. Without it, everyone is signed out each time the API restarts.

Battle.net. Make a client at develop.battle.net with the redirect http://localhost:8000/auth/battlenet/callback, and copy its ID and secret into WEB_BATTLENET_CLIENT_ID and WEB_BATTLENET_CLIENT_SECRET. The site asks only for the BattleTag. It can't read characters yet, because Blizzard hasn't said whether Forever will be in its API.

Deploying

Push to main. When CI passes, Cabotage builds the Docker image, runs the migrations, then starts the new version. It takes about 13 minutes. Two things to know:

  • A push deploys only if its newest commit changes app files. A commit that touches only .github/ deploys nothing, even if older commits under it were never deployed.
  • For a minute the old and new versions run side by side, and the migrations run first. So a migration must work with the old code too: give new columns a default or let them be empty.

After each deploy, the Deployed workflow clears Cloudflare's cache and runs the browser tests against the live site. gh run list --workflow deployed.yml shows how it went. Each deploy also starts two or three runs that skip themselves, so look for the one that didn't.

The Procfile has two processes from the same image: release runs the migrations, and web serves the API and the site. The health check path is /health/live. Settings for the live site:

Setting Value
DB_URL postgresql+asyncpg://...?ssl=verify-full, built from the database's SIDEQUEST_PG* settings
PGSSLROOTCERT the database's SIDEQUEST_PGSSLROOTCERT
API_KEY a long random string, the same one the bot uses
WEB_BASE_URL https://sidequest-guild.com
WEB_COOKIE_SECURE true
WEB_SESSION_SECRET at least 32 characters
WEB_DISCORD_CLIENT_ID, WEB_DISCORD_CLIENT_SECRET the Discord app
WEB_BATTLENET_CLIENT_ID, WEB_BATTLENET_CLIENT_SECRET the Battle.net client
WEB_OFFICER_ROLE_IDS, WEB_GM_ROLE_IDS only to change who counts as an officer or Game Master

To run the whole thing in Docker on your computer, use make docker-up, and make docker-down to stop it.

Cloudflare

Cloudflare answers almost every request itself, so the server rarely sees a visitor.

What How long Cloudflare keeps it
/_astro/* (scripts, fonts, pictures) a year. The file names change with every build.
Pages a minute. After that it shows the copy it has while it fetches a new one.
/api/discord/live 15 seconds. Everyone sees the same counts.
Everything else in /api/ and /auth/ never kept

A route is kept only if it sends a Cloudflare-CDN-Cache-Control header and skips the session, so it can never hand one person's cookie to another. The cache rule is "Pages and live counts at the edge" on the sidequest-guild.com zone. It ignores the query string, so every /events/detail/?id=... is one page. Smart Tiered Cache, 0-RTT and Early Hints are on.

Page scripts and the offline copy

Links change the page without a full reload. A page script must start with onPage from website/src/scripts/page.ts, or it runs only on the first page a visitor opens.

After the first visit, a service worker (website/service-worker/sw.js) keeps every page, script and font on the device, so pages open at once and work offline. Public data shows from the device first and updates when the fresh copy arrives. The saved data is deleted on every sign-up, edit, sign-in and sign-out, so nobody sees another person's data. To turn the service worker off, set SERVICE_WORKER = false in website/astro.config.mjs and deploy. It deletes its copies and removes itself.

About

Website, discord bot, and API for the SideQuest guild in WoW: Forever

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Used by

Contributors

Languages