Docs: BGG auth policy in spec/README, upload-flow recon notes
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,10 @@ put it in config.toml, code, or logs. Requests must go to
|
|||||||
Exception: downloading your own collection while logged in on the website
|
Exception: downloading your own collection while logged in on the website
|
||||||
needs no registration — relevant to the Playwright stages, not the API
|
needs no registration — relevant to the Playwright stages, not the API
|
||||||
client. Usage is monitored per-application at `/applications` → "Usage".
|
client. Usage is monitored per-application at `/applications` → "Usage".
|
||||||
|
Open-source note: each user of this tool registers their OWN application
|
||||||
|
and supplies their own token — never ship or share a token in the repo.
|
||||||
|
Future frontend note: public-facing apps must display the "Powered by
|
||||||
|
BGG" logo linking back to boardgamegeek.com.
|
||||||
|
|
||||||
## Endpoints (XML API2 — the only sanctioned read API)
|
## Endpoints (XML API2 — the only sanctioned read API)
|
||||||
|
|
||||||
@@ -55,11 +59,11 @@ The first `/collection` call typically returns **HTTP 202** with a "please retry
|
|||||||
## Website automation (upload stage)
|
## Website automation (upload stage)
|
||||||
|
|
||||||
- Login with `BGG_USERNAME` / `BGG_PASSWORD` env vars; persist Playwright storage state locally (gitignored) so repeat runs skip login. Never write credentials to disk, logs, or error messages.
|
- Login with `BGG_USERNAME` / `BGG_PASSWORD` env vars; persist Playwright storage state locally (gitignored) so repeat runs skip login. Never write credentials to disk, logs, or error messages.
|
||||||
- Per game: navigate to the game page → "Add to Collection" flow → status **Owned** → if a version_id is known, set it in the collection item's version picker → save. Never guess a version — omit it when unknown. The version picker is the most fragile part of the UI: walk it manually once and document the selectors before automating.
|
- Per game: navigate to the game page → "Add to Collection" flow → status **Owned** → if a version_id is known, set it in the collection item's version picker → save. Never guess a version — omit it when unknown. The flow has been walked and documented: see `docs/bgg-upload-flow.md` for the dialog structure, version-picker behavior (paginated, no search — match by canonical version NAME from the XML API), and observed automation gotchas (stale elements, hidden-not-removed dialogs, hydration races).
|
||||||
- A second copy of an owned game must be a NEW collection entry (new collid), not an edit of the existing item.
|
- A second copy of an owned game must be a NEW collection entry (new collid), not an edit of the existing item.
|
||||||
- Expect UI fragility: wrap each game in its own try/except, log the failure to `upload_log.csv`, and continue. `--retry-failed` re-attempts failures; `--dry-run` logs without touching the site.
|
- Expect UI fragility: wrap each game in its own try/except, log the failure to `upload_log.csv`, and continue. `--retry-failed` re-attempts failures; `--dry-run` logs without touching the site.
|
||||||
- Idempotency: skip IDs already logged `added`; `--verify` re-fetches the collection to confirm.
|
- Idempotency: skip IDs already logged `added`; `--verify` re-fetches the collection to confirm.
|
||||||
|
|
||||||
## Policy note
|
## Policy note
|
||||||
|
|
||||||
BGG changed API access policies in 2025 and broke older community tools. Do not use undocumented endpoints, scraped JSON blobs, or third-party mirrors — only XML API2 and the public website.
|
BGG's 2025 policy (version 2025-07-02, boardgamegeek.com/using_the_xml_api) locked the API behind registered applications and Bearer tokens, breaking older community tools. Do not use undocumented endpoints, scraped JSON blobs, or third-party mirrors — only the authenticated XML API2 and the public website. Third-party services that proxy BGG data to other applications are explicitly prohibited. The policy asks for server-side requests, aggressive caching, and minimal request counts — the cache-first client design is mandatory, not optional. Policies can change at any time; changes are announced in BGG's Geek Tools News forum.
|
||||||
|
|||||||
@@ -30,14 +30,14 @@ Every stage is idempotent and resumable: kill it mid-run, restart, lose nothing.
|
|||||||
|
|
||||||
- macOS or Linux, Python 3.12+, [uv](https://docs.astral.sh/uv/)
|
- macOS or Linux, Python 3.12+, [uv](https://docs.astral.sh/uv/)
|
||||||
- An [Anthropic API key](https://console.anthropic.com/) (vision extraction)
|
- An [Anthropic API key](https://console.anthropic.com/) (vision extraction)
|
||||||
- A BoardGameGeek account
|
- A BoardGameGeek account **and a registered BGG application** — as of BGG's [2025 API policy](https://boardgamegeek.com/using_the_xml_api), the XML API requires a Bearer token from a registered app. Register a free non-commercial application at [boardgamegeek.com/applications](https://boardgamegeek.com/applications) (approval can take a week or more — apply early), then create a token. Each user needs their own — tokens must not be shared.
|
||||||
- Playwright Chromium: `uv run playwright install chromium`
|
- Playwright Chromium: `uv run playwright install chromium`
|
||||||
|
|
||||||
Secrets come from environment variables only — `ANTHROPIC_API_KEY`, `BGG_USERNAME`, `BGG_PASSWORD` — and are never written to disk or logs.
|
Secrets come from environment variables only — `ANTHROPIC_API_KEY`, `BGG_USERNAME`, `BGG_PASSWORD`, `BGG_API_TOKEN` — and are never written to disk or logs.
|
||||||
|
|
||||||
## A note on being a good BGG citizen
|
## A note on being a good BGG citizen
|
||||||
|
|
||||||
This tool is **not affiliated with or supported by BoardGameGeek**. It uses only the sanctioned XML API2 for reads and drives the regular website for writes, deliberately slowly (one request every couple of seconds, slower for uploads). Please keep it that way: BGG is a community resource running on community goodwill. You are responsible for your own account — review the dry-run output before a real upload.
|
This tool is **not affiliated with or supported by BoardGameGeek**. It uses only the sanctioned XML API2 for reads (with your own registered application token, per BGG's current policy) and drives the regular website for writes, deliberately slowly (one request every couple of seconds, slower for uploads). Please keep it that way: BGG is a community resource running on community goodwill. You are responsible for your own account — review the dry-run output before a real upload.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ Beyond the bare game, capture **which edition/version I own** wherever the photo
|
|||||||
## Constraints & Context
|
## Constraints & Context
|
||||||
|
|
||||||
- BGG has **no write API**. Reads go through the XML API2 (`https://boardgamegeek.com/xmlapi2/`); writes must automate the website itself with a real login session.
|
- BGG has **no write API**. Reads go through the XML API2 (`https://boardgamegeek.com/xmlapi2/`); writes must automate the website itself with a real login session.
|
||||||
|
- The XML API **requires a registered application** (boardgamegeek.com/using_the_xml_api, policy 2025-07-02): every request carries `Authorization: Bearer <token>` from the `BGG_API_TOKEN` env var, sent to `boardgamegeek.com` without a leading `www`. Register a free non-commercial application at boardgamegeek.com/applications — approval can take a week+, so development runs on recorded/stub XML fixtures until then.
|
||||||
- BGG's XML API queues collection requests: a first call may return HTTP 202 ("try again"). Retry with backoff.
|
- BGG's XML API queues collection requests: a first call may return HTTP 202 ("try again"). Retry with backoff.
|
||||||
- BGG will throttle aggressive clients. Target ≤1 request every 2 seconds to any BGG endpoint, with jittered backoff on 429/503.
|
- BGG will throttle aggressive clients. Target ≤1 request every 2 seconds to any BGG endpoint, with jittered backoff on 429/503.
|
||||||
- BGG changed API access policies in 2025; some older community tools broke. Don't depend on undocumented endpoints beyond XML API2 and the public website.
|
- BGG changed API access policies in 2025; some older community tools broke. Don't depend on undocumented endpoints beyond XML API2 and the public website.
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
# BGG "Add to Collection" flow — recon notes for the upload stage
|
||||||
|
|
||||||
|
Recorded 2026-08-01 by walking the flow manually on boardgamegeek.com (Wingspan,
|
||||||
|
id 266192) while logged in. Dialog opened, inspected, and **cancelled without
|
||||||
|
saving**. These notes are the selector documentation the spec requires before
|
||||||
|
automating Stage 5.
|
||||||
|
|
||||||
|
## Entry point
|
||||||
|
|
||||||
|
- Game page has **two** "Add To" buttons (one in the game header module, one
|
||||||
|
lower on the page) — target the first, but match by accessible name, not
|
||||||
|
position. Button's accessible name is "Add To" with adjacent text
|
||||||
|
"Collection".
|
||||||
|
- Clicking opens a `role="dialog"` containing a `<form>`. Dialog heading shows
|
||||||
|
a "Loading..." span before content settles — **wait for the game-name
|
||||||
|
heading** (e.g. `getByRole('heading', {name: gameName})`) before
|
||||||
|
interacting.
|
||||||
|
|
||||||
|
## Main dialog structure
|
||||||
|
|
||||||
|
- **Status checkboxes**, each wrapped in a `<label>`: Own, Prev. Owned,
|
||||||
|
For Trade, Want to Play, Want in Trade, Want to Buy, Pre-ordered, Wishlist.
|
||||||
|
→ `dialog.getByLabel('Own')` and check it. Nothing is pre-checked.
|
||||||
|
- Rating: a 1–10 slider plus a "Rating" text input. We do not set ratings.
|
||||||
|
- Comment (public) textbox — unused by us.
|
||||||
|
- "Advanced (private info, parts exchange)" expander: Price Paid, Current
|
||||||
|
Price, Quantity, Acquisition Date, Acquired From, Inventory Date/Location,
|
||||||
|
Private Comment, Want/Has Parts. All unused (we don't track provenance).
|
||||||
|
- "Customize Item Info (title, image)" expander: Custom Title, Custom Image
|
||||||
|
Id, plus **manual version-override fields** (Publisher Id, Language select,
|
||||||
|
Year, Other, Barcode). These are for defining a custom version — do NOT use
|
||||||
|
them; always pick a cataloged version instead (or none).
|
||||||
|
- Footer: `Save` (`type="submit"`) and `Cancel` buttons.
|
||||||
|
|
||||||
|
## Version picker ("Set version/edition")
|
||||||
|
|
||||||
|
- Button labeled **"Set version/edition"** near the top of the dialog swaps
|
||||||
|
the dialog content to a "Versions" sub-view (same `role="dialog"`).
|
||||||
|
- The sub-view is a **paginated list with NO search/filter box**. Each
|
||||||
|
listitem's text is the full canonical version name + year, e.g.
|
||||||
|
"Flügelschlag (German fifth edition) (2024)" — these names match the
|
||||||
|
version names returned by `/thing?id=X&versions=1`.
|
||||||
|
- Selection strategy: resolve the target version NAME from the XML API,
|
||||||
|
then page through the list matching listitem text
|
||||||
|
(`getByRole('listitem').filter({hasText: versionName})`). Newest years
|
||||||
|
appear first.
|
||||||
|
- The sub-view has its own **Cancel** that returns to the main dialog — it
|
||||||
|
is a different button from the main dialog's Cancel. Two-level dismissal.
|
||||||
|
|
||||||
|
## Automation gotchas observed
|
||||||
|
|
||||||
|
1. **Element references go stale constantly.** The page re-renders after
|
||||||
|
load and after every dialog transition; clicks on cached handles silently
|
||||||
|
miss. Playwright's auto-waiting role/label locators handle this — never
|
||||||
|
cache element handles across a dialog state change.
|
||||||
|
2. **Dialog persists in the DOM after cancel**, just hidden
|
||||||
|
(`offsetParent === null`). "Is the dialog gone" checks must test
|
||||||
|
visibility, not existence. Same applies when verifying a save completed.
|
||||||
|
3. First click on "Add To" right after page load can no-op (hydration race).
|
||||||
|
Wait for network-idle or the button's stable state before clicking.
|
||||||
|
4. Verify saves via the collection API (`--verify`), not by UI state.
|
||||||
|
|
||||||
|
## Playwright locator sketch
|
||||||
|
|
||||||
|
```python
|
||||||
|
page.get_by_role("button", name="Add To").first.click()
|
||||||
|
dialog = page.get_by_role("dialog")
|
||||||
|
dialog.get_by_role("heading", name=game_name).wait_for()
|
||||||
|
dialog.get_by_label("Own").check()
|
||||||
|
if version_name:
|
||||||
|
dialog.get_by_role("button", name="Set version/edition").click()
|
||||||
|
# page through listitems until version_name matches, then click it
|
||||||
|
dialog.get_by_role("button", name="Save").click()
|
||||||
|
```
|
||||||
|
|
||||||
|
Unverified so far (needs a real, sacrificial save on one game before batch
|
||||||
|
runs): pagination controls in the version sub-view, exact post-save behavior
|
||||||
|
(toast? dialog close? redirect?), and how the dialog differs when the game is
|
||||||
|
ALREADY in the collection (second-copy flow must create a new entry, not edit
|
||||||
|
the existing one).
|
||||||
Reference in New Issue
Block a user