Initial build: fullhouse, live music bingo from a real Spotify playlist
All checks were successful
Docker / build-and-push (push) Successful in 1m18s
All checks were successful
Docker / build-and-push (push) Successful in 1m18s
Based on bingo-bango by f8al (MIT) — same core card-generation idea
("both facets": a square is either an exact song title or an artist,
and an artist square lights up on any of their tracks), reworked with
a real host/guest split, live multiplayer sessions, and cover-art
squares. f8al credited in the footer, README, and PROPOSAL.md.
Three ways to play: host a live session (server-side Spotify
connection, join code + QR, caller screen, auto-call from real
playback), join someone else's session (no login needed), or solo
(guest PKCE login or a pasted public playlist URL, one-off card).
Genuinely verified against a real Spotify account throughout, not
just built and assumed working — including two real bugs found and
fixed along the way (Spotify's playlist-tracks endpoint quietly
renamed to /items with a reshaped response; reading playlist tracks
needs real user auth even for public playlists, so the "no guest
login" public-playlist path routes through the host's connection
instead of a dead-end app-only token). Both Docker images built and
run together on a real network with the nginx proxy verified working
end-to-end, and auto-call confirmed detecting an actual track playing
live through Spotify Connect within one poll tick.
This commit is contained in:
117
PROPOSAL.md
Normal file
117
PROPOSAL.md
Normal file
@@ -0,0 +1,117 @@
|
||||
# Proposal: `fullhouse` — music bingo, live-session and self-service
|
||||
|
||||
**Status:** proposed, build in progress. Self-contained — written so a fresh
|
||||
agent with no prior conversation context can pick this up and implement it
|
||||
without anything explained first.
|
||||
|
||||
## Origin & attribution
|
||||
|
||||
Inspired by [bingo-bango](https://github.com/f8al/bingo-bango) by **f8al** —
|
||||
a fully client-side (Spotify PKCE, no backend) music bingo card generator.
|
||||
`fullhouse` keeps the core idea (Spotify playlist → bingo cards, "both
|
||||
facets" title+artist marking) and the card-generation algorithm's shape, but
|
||||
changes the architecture significantly enough that it's a fresh build, not a
|
||||
fork: a real server-side host mode, live multiplayer sessions, cover-art
|
||||
squares, and heavier use of the Spotify API. MIT license from the original
|
||||
requires the copyright notice + license text be retained wherever the
|
||||
original code/algorithm is directly reused — credit f8al clearly in the
|
||||
README and footer regardless of how much code ends up shared verbatim.
|
||||
|
||||
## Motivation
|
||||
|
||||
The original requires *every player* to individually log into Spotify to
|
||||
pick a playlist — real friction at an actual game night where guests don't
|
||||
want to OAuth their own account just to get a bingo card. The fix: a proper
|
||||
host/guest split.
|
||||
|
||||
## Modes
|
||||
|
||||
Both exist simultaneously, chosen per session, not one replacing the other:
|
||||
|
||||
1. **Host mode** — the host (the site owner) has a real server-side Spotify
|
||||
connection (same shape as `goonk`'s existing `spotify.mjs`: Authorization
|
||||
Code flow, refresh token persisted server-side, no per-visitor login).
|
||||
Host picks from *their own* playlists (`playlist-read-private` +
|
||||
`playlist-read-collaborative` scopes) as the song pool for a session.
|
||||
2. **Guest self-service mode** — the original flow, kept: a visitor with no
|
||||
relationship to the host logs in via their own Spotify PKCE flow and
|
||||
generates a card from their own playlist, standalone, no session/host
|
||||
involved. Also supports pasting a public playlist URL with **no login at
|
||||
all**, via an app-level Client Credentials token (public playlists don't
|
||||
need user auth to read) — simpler than PKCE for the common "just grab a
|
||||
public playlist" case, and something the original doesn't offer.
|
||||
|
||||
## The four differentiators (all in scope for this build)
|
||||
|
||||
1. **Host-curated mode** — described above. Solves the real friction.
|
||||
2. **Live session mode** — host starts a session, gets a join code + QR.
|
||||
Guests join via the code (no login required in a host-curated session —
|
||||
the host already authorized the song pool), each gets an independently
|
||||
seeded card. A "caller" screen shows the current track large, for the
|
||||
host to read aloud or leave visible on a shared screen. State sync is
|
||||
**polling**, not WebSockets — deliberately, matching this whole family's
|
||||
precedent (`keep watch`, `wisp`'s sweep): a session isn't latency-
|
||||
sensitive enough to justify the operational complexity of a socket
|
||||
server. Poll every 3-5s for "what's been called."
|
||||
3. **Live now-playing auto-call** — if the host is actually playing the
|
||||
session's pool through Spotify Connect, poll `/me/player/currently-
|
||||
playing` (same pattern as `goonk`'s `now-playing` route) and
|
||||
auto-mark a track "called" the moment it starts, matched against the
|
||||
pool by track ID. Falls back to a manual "call next" button (classic
|
||||
random-draw-without-replacement bingo caller) for hosts not using live
|
||||
Spotify Connect playback during the game — auto-call is a bonus layer
|
||||
over manual, not a replacement for it.
|
||||
4. **Cover-art squares** — squares can show album art (from the same
|
||||
playlist-track API response, `track.album.images`, already fetched for
|
||||
everything else) instead of, or alongside, text. A visual variant beyond
|
||||
what the original offers.
|
||||
|
||||
## Architecture
|
||||
|
||||
**Server** (small Express app, same shape as `goonk`'s `api/`):
|
||||
- Host OAuth: `/auth/login`, `/auth/callback`, refresh-token persistence —
|
||||
ported near-verbatim in pattern (not code) from `goonk/api/routes/
|
||||
spotify.mjs`, since that flow is already proven correct in this family.
|
||||
- `GET /api/host/playlists` — the host's own playlists, cached.
|
||||
- `GET /api/host/playlist/:id/tracks` — full track+artist+album-art data
|
||||
for a chosen playlist, cached.
|
||||
- `GET /api/public-playlist?url=` — Client Credentials app token, fetch a
|
||||
*public* playlist's tracks with no user login. Client Credentials tokens
|
||||
are cached in memory and refreshed on expiry, same shape as the existing
|
||||
user-token cache pattern.
|
||||
- Session state: `POST /api/session` (host creates one from a chosen
|
||||
playlist + mode), `GET /api/session/:code` (guest join — hands back pool
|
||||
metadata, NOT the host's Spotify tokens), `POST /api/session/:code/call`
|
||||
(host marks a track called, or the auto-call poller does), `GET /api/
|
||||
session/:code/state` (guests poll this for the called-track list).
|
||||
In-memory session store is fine — sessions are ephemeral (a single game
|
||||
night), no need for persistence across restarts.
|
||||
- `GET /api/host/now-playing` — thin wrapper the auto-call poller uses,
|
||||
same call shape as `goonk`'s existing now-playing route.
|
||||
|
||||
**Client**: Vite + TypeScript, no framework — matches the `typo`/`latent`
|
||||
family's "plain, no heavy build" ethos more than the original's React
|
||||
choice. The card-generation engine is a pure, dependency-free TS module
|
||||
(same shape as the original's `src/cards/`), extended with a `coverArt`
|
||||
render mode alongside the text mode.
|
||||
|
||||
**Guest PKCE mode** stays fully client-side (no server involvement at all)
|
||||
for the self-service path — same as the original, since that's the whole
|
||||
point of PKCE (no secret needed, tokens never touch a server the host
|
||||
controls).
|
||||
|
||||
## Theming
|
||||
|
||||
Dark/mono/green house style shared with `typo`/`flit`/`wisp` (see any of
|
||||
those `style.css` files for the exact CSS custom properties), not the
|
||||
original's own visual design. Reads as part of this family, not a
|
||||
standalone fork.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- No accounts beyond the host's own Spotify connection — guests never
|
||||
create anything, a join code is the only credential a session needs.
|
||||
- No persistent session history — a session lives in memory for its game
|
||||
night and is gone after.
|
||||
- Not trying to be a general Spotify app — scoped tightly to bingo card
|
||||
generation + live session calling.
|
||||
Reference in New Issue
Block a user