diff --git a/.claude/skills/bgg-api/SKILL.md b/.claude/skills/bgg-api/SKILL.md index c9245bd..00c0af7 100644 --- a/.claude/skills/bgg-api/SKILL.md +++ b/.claude/skills/bgg-api/SKILL.md @@ -25,6 +25,7 @@ BGG" logo linking back to boardgamegeek.com. ## Endpoints (XML API2 — the only sanctioned read API) - Search: `https://boardgamegeek.com/xmlapi2/search?query=&type=boardgame,boardgameexpansion` +- RPG search: same endpoint with `type=rpgitem` — the geekdo database is shared across BGG/RPGGeek, so the same API and token serve RPG products. bggpipe uses this as a fallback for titles absent from the board-game types; matched rpgitems are LOCAL-ONLY (enriched, browsable, never uploaded — RPG collections live on rpggeek.com, outside this pipeline's write scope). - Thing (details/stats): `https://boardgamegeek.com/xmlapi2/thing?id=<id1,id2,...>&stats=1` — accepts comma-separated IDs; batch (~20) to reduce request count. - Thing versions: `https://boardgamegeek.com/xmlapi2/thing?id=<id>&versions=1` — lists every published edition/version of a game, each with its own version id, name, publisher, year, and language. Used to match photo edition cues to a concrete version. - Collection: `https://boardgamegeek.com/xmlapi2/collection?username=<user>&own=1` — add `&version=1` to include version info on collection items. diff --git a/CLAUDE.md b/CLAUDE.md index 206c8a5..1106766 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -47,6 +47,7 @@ Full design lives in `bgg-shelf-pipeline-spec.md` (read it before changing pipel - Base game vs. expansion vs. new edition is the top failure mode — bias matching toward `ambiguous` over auto-match ("Wingspan Europe" must not match base Wingspan). - Editions/versions matter: Eric owns multiple editions of some games — each is a separate collection entry (keyed by `collid` on BGG). Never guess a version: no legible cues → `version_unknown` and a version-less collection entry. - Normalize titles (casefold, strip punctuation/articles, special chars like é/&/:) identically on both sides of a match; dedupe across photos but keep `source_photos` provenance. +- **RPGs are local-only citizens**: when the board-game search runs dry, resolve falls back to `type=rpgitem` (same geekdo API/token). Matched rpgitems enrich into the library but diff routes them to `local_only` — they must never reach `to_add.csv`/upload (their collection lives on RPGGeek, out of scope). - Detailed BGG API behavior (202 queueing, collection-endpoint quirks, endpoints): use the `bgg-api` skill. **If the spec's BGG behavior changes, update the `bgg-api` skill to match** — they must not drift. ## Git diff --git a/README.md b/README.md index c063618..9a30538 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,10 @@ uv run bggpipe enrich # full metadata → data/games.json Each stage skips work it has already done; `--force`/`--refresh` flags redo it. `review` without `--web` runs in the terminal instead. `upload` also has `--retry-failed`, `--verify` (re-fetches your collection and cross-checks the log), and runs a **headed** browser by default — BGG's Cloudflare check blocks headless ones, and a first login may need one human click before the session is saved locally and reused. +### RPGs on your shelves + +Tabletop RPGs aren't in BGG's board-game database — they live on RPGGeek, which shares the same underlying API. When a title isn't found as a board game, bggpipe retries as an RPG: matches are identified, enriched, and browsable in the library (filter: RPGs), but they stay **local only** — they're never uploaded, since your BGG collection can't hold them. + ### Taking good shelf photos Straight-on, one shelf (or part of one) per shot, close enough that spine text is legible to a human. If you can't read it, the model can't either. Overlap between shots is fine: duplicate reads are deduped automatically, with the merge shown (and veto-able) in review. Boxes the model spots but can't identify become retake prompts in `unidentified.json` and the review UI's "reshoot" list: photograph those boxes up close, drop the new photo in `photos/`, and run `extract` again. diff --git a/data/matches.csv b/data/matches.csv index 69a9d4f..62ee227 100644 --- a/data/matches.csv +++ b/data/matches.csv @@ -1,36 +1,36 @@ -title_raw,bgg_id,bgg_name,year,type,match_status,version_id,version_name,version_status,candidates_json,version_candidates_json,source_photos,merged_into -ROYALS,165986,Royals,2014,boardgame,auto,,,version_unknown,"[{""bgg_id"": 165986, ""name"": ""Royals"", ""year"": 2014, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4499.jpeg, -CAT CRIMES,235096,Cat Crimes,2017,boardgame,auto,360982,English edition,version_auto,"[{""bgg_id"": 235096, ""name"": ""Cat Crimes"", ""year"": 2017, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 360982, ""name"": ""English edition"", ""year"": 2017, ""publishers"": [""ThinkFun""], ""languages"": [""English""], ""score"": 3}]",IMG_4499.jpeg, -COSMIC ENCOUNTER,40529,Cosmic Encounter,2008,boardgame,auto,,,version_unknown,"[{""bgg_id"": 39, ""name"": ""Cosmic Encounter"", ""year"": 1977, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 7000, ""rank"": 800}, {""bgg_id"": 40529, ""name"": ""Cosmic Encounter"", ""year"": 2008, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 75000, ""rank"": 150}]",[],IMG_4499.jpeg, -SHERIFF OF NOTTINGHAM,157969,Sheriff of Nottingham,2014,boardgame,auto,,,version_unknown,"[{""bgg_id"": 157969, ""name"": ""Sheriff of Nottingham"", ""year"": 2014, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4499.jpeg, -5 MINUTE DUNGEON,207830,5-Minute Dungeon,2017,boardgame,auto,,,version_unknown,"[{""bgg_id"": 207830, ""name"": ""5-Minute Dungeon"", ""year"": 2017, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4499.jpeg, -Britannia,240,Britannia,1986,boardgame,auto,25668,Avalon Hill English edition,version_auto,"[{""bgg_id"": 240, ""name"": ""Britannia"", ""year"": 1986, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 25668, ""name"": ""Avalon Hill English edition"", ""year"": 1986, ""publishers"": [""The Avalon Hill Game Co""], ""languages"": [""English""], ""score"": 3}]",IMG_4500.jpeg, -CIVILIZATION Game of the Heroic Age - The Dawn of History 8000 BC to 250 BC,71,Civilization,1980,boardgame,auto,71001,Avalon Hill English edition,version_auto,"[{""bgg_id"": 71, ""name"": ""Civilization"", ""year"": 1980, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 31.2, ""owned"": null, ""rank"": null}]","[{""version_id"": 71001, ""name"": ""Avalon Hill English edition"", ""year"": 1981, ""publishers"": [""The Avalon Hill Game Co""], ""languages"": [""English""], ""score"": 3}]",IMG_4500.jpeg, -"ADVANCED CIVILIZATION Game Expansion of the Heroic Age - Featuring New Civilization, Commodity, and Calamity Cards",177,Advanced Civilization,1991,boardgameexpansion,auto,177001,Avalon Hill English edition,version_auto,"[{""bgg_id"": 177, ""name"": ""Advanced Civilization"", ""year"": 1991, ""type"": ""boardgameexpansion"", ""exact"": true, ""fuzzy"": 33.1, ""owned"": null, ""rank"": null}]","[{""version_id"": 177001, ""name"": ""Avalon Hill English edition"", ""year"": 1991, ""publishers"": [""The Avalon Hill Game Co""], ""languages"": [""English""], ""score"": 3}]",IMG_4500.jpeg, -DOCTOR WHO The Game of Time & Space,3090,Doctor Who: The Game of Time & Space,1980,boardgame,auto,309001,Games Workshop English edition,version_auto,"[{""bgg_id"": 3090, ""name"": ""Doctor Who: The Game of Time & Space"", ""year"": 1980, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 309001, ""name"": ""Games Workshop English edition"", ""year"": 1980, ""publishers"": [""Games Workshop Ltd.""], ""languages"": [""English""], ""score"": 3}]",IMG_4500.jpeg, -SORCERER The Game of Magical Conflict,3585,Sorcerer,1975,boardgame,auto,358501,SPI Designer's Edition,version_auto,"[{""bgg_id"": 3585, ""name"": ""Sorcerer"", ""year"": 1975, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 39.0, ""owned"": 900, ""rank"": 5000}, {""bgg_id"": 244115, ""name"": ""Sorcerer"", ""year"": 2019, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 39.0, ""owned"": 6000, ""rank"": 1500}]","[{""version_id"": 358501, ""name"": ""SPI Designer's Edition"", ""year"": 1975, ""publishers"": [""Simulations Publications, Inc. (SPI)""], ""languages"": [""English""], ""score"": 5}, {""version_id"": 358502, ""name"": ""SPI folio edition"", ""year"": 1975, ""publishers"": [""Simulations Publications, Inc. (SPI)""], ""languages"": [""English""], ""score"": 3}]",IMG_4500.jpeg, -STARFORCE ALPHA CENTAURI Interstellar Conflict in the 25th Century,2524,StarForce 'Alpha Centauri': Interstellar Conflict in the 25th Century,1974,boardgame,auto,252401,SPI Designer's Edition,version_auto,"[{""bgg_id"": 2524, ""name"": ""StarForce 'Alpha Centauri': Interstellar Conflict in the 25th Century"", ""year"": 1974, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 252401, ""name"": ""SPI Designer's Edition"", ""year"": 1974, ""publishers"": [""Simulations Publications, Inc. (SPI)""], ""languages"": [""English""], ""score"": 5}, {""version_id"": 252402, ""name"": ""SPI folio edition"", ""year"": 1974, ""publishers"": [""Simulations Publications, Inc. (SPI)""], ""languages"": [""English""], ""score"": 3}]",IMG_4500.jpeg, -SKIP-BO,1078,Skip-Bo,1967,boardgame,auto,,,version_unknown,"[{""bgg_id"": 1078, ""name"": ""Skip-Bo"", ""year"": 1967, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4501.jpeg, -a Gentle Rain,268163,A Gentle Rain,2019,boardgame,auto,268164,Bloom Edition,version_auto,"[{""bgg_id"": 268163, ""name"": ""A Gentle Rain"", ""year"": 2019, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 268164, ""name"": ""Bloom Edition"", ""year"": 2021, ""publishers"": [""Mondo Games""], ""languages"": [""English""], ""score"": 3}]",IMG_4501.jpeg, -PATCH WORK,163412,Patchwork,2014,boardgame,auto,,,version_unknown,"[{""bgg_id"": 163412, ""name"": ""Patchwork"", ""year"": 2014, ""type"": ""boardgame"", ""exact"": false, ""fuzzy"": 94.7, ""owned"": null, ""rank"": null}]",[],IMG_4501.jpeg, -SANTORINI,194655,Santorini,2016,boardgame,auto,,,version_unknown,"[{""bgg_id"": 194655, ""name"": ""Santorini"", ""year"": 2016, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4501.jpeg;IMG_4502.jpeg, -Utter Nonsense!,181254,Utter Nonsense,2015,boardgame,auto,,,version_unknown,"[{""bgg_id"": 181254, ""name"": ""Utter Nonsense"", ""year"": 2015, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4501.jpeg, -Cards Against Humanity,50381,Cards Against Humanity,2009,boardgame,auto,,,version_unknown,"[{""bgg_id"": 50381, ""name"": ""Cards Against Humanity"", ""year"": 2009, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4501.jpeg, -Joking Hazard,193621,Joking Hazard,2016,boardgame,auto,19362101,First edition,version_auto,"[{""bgg_id"": 193621, ""name"": ""Joking Hazard"", ""year"": 2016, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 19362101, ""name"": ""First edition"", ""year"": 2016, ""publishers"": [""Cyanide & Happiness""], ""languages"": [""English""], ""score"": 3}]",IMG_4501.jpeg;IMG_4502.jpeg, -EXPLODING KITTENS,172225,Exploding Kittens,2015,boardgame,auto,,,version_unknown,"[{""bgg_id"": 172225, ""name"": ""Exploding Kittens"", ""year"": 2015, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4501.jpeg, -Hebarceos,195314,Herbaceous,2017,boardgame,merged,,,version_unknown,[],[],IMG_4502.jpeg,Herbaceous -Wiz-War,104710,Wiz-War,2012,boardgame,approved,,,version_unknown,"[{""bgg_id"": 1218, ""name"": ""Wiz-War"", ""year"": 1983, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 4000, ""rank"": 2500}, {""bgg_id"": 104710, ""name"": ""Wiz-War"", ""year"": 2012, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 8000, ""rank"": 1200}]",[],IMG_4502.jpeg, -SCRAWL,218866,Scrawl,2017,boardgame,auto,,,version_unknown,"[{""bgg_id"": 218866, ""name"": ""Scrawl"", ""year"": 2017, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4502.jpeg, -FLAT TOP,2529,Flat Top,1977,boardgame,auto,,,version_unknown,"[{""bgg_id"": 2529, ""name"": ""Flat Top"", ""year"": 1977, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4502.jpeg, -Diplomacy,483,Diplomacy,1959,boardgame,auto,,,version_unknown,"[{""bgg_id"": 483, ""name"": ""Diplomacy"", ""year"": 1959, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4502.jpeg, -Age of Renaissance,26,Age of Renaissance,1996,boardgame,auto,2601,Avalon Hill English edition,version_auto,"[{""bgg_id"": 26, ""name"": ""Age of Renaissance"", ""year"": 1996, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 2601, ""name"": ""Avalon Hill English edition"", ""year"": 1996, ""publishers"": [""The Avalon Hill Game Co""], ""languages"": [""English""], ""score"": 3}]",IMG_4502.jpeg, -History of the World,224,History of the World,1991,boardgame,auto,22401,Avalon Hill English edition,version_auto,"[{""bgg_id"": 224, ""name"": ""History of the World"", ""year"": 1991, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 22401, ""name"": ""Avalon Hill English edition"", ""year"": 1993, ""publishers"": [""The Avalon Hill Game Co""], ""languages"": [""English""], ""score"": 3}]",IMG_4502.jpeg, -DUNGEON!,1339,Dungeon!,1975,boardgame,auto,133903,TSR New Dungeon edition,version_approved,"[{""bgg_id"": 1339, ""name"": ""Dungeon!"", ""year"": 1975, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 133901, ""name"": ""TSR first edition"", ""year"": 1975, ""publishers"": [""TSR""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 133902, ""name"": ""TSR second edition"", ""year"": 1980, ""publishers"": [""TSR""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 133903, ""name"": ""TSR New Dungeon edition"", ""year"": 1989, ""publishers"": [""TSR""], ""languages"": [""English""], ""score"": 3}]",IMG_4502.jpeg, -CAT CRIMES,235096,Cat Crimes,2017,boardgame,merged,360982,English edition,version_auto,"[{""bgg_id"": 235096, ""name"": ""Cat Crimes"", ""year"": 2017, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 360982, ""name"": ""English edition"", ""year"": 2017, ""publishers"": [""ThinkFun""], ""languages"": [""English""], ""score"": 3}]",IMG_4499.jpeg;IMG_4505.jpeg,CAT CRIMES -a Gentle Rain,268163,A Gentle Rain,2019,boardgame,merged,268164,Bloom Edition,version_auto,"[{""bgg_id"": 268163, ""name"": ""A Gentle Rain"", ""year"": 2019, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 268164, ""name"": ""Bloom Edition"", ""year"": 2021, ""publishers"": [""Mondo Games""], ""languages"": [""English""], ""score"": 3}]",IMG_4501.jpeg;IMG_4507.jpeg,a Gentle Rain -SANTORINI,194655,Santorini,2016,boardgame,merged,,,version_unknown,"[{""bgg_id"": 194655, ""name"": ""Santorini"", ""year"": 2016, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4501.jpeg;IMG_4502.jpeg;IMG_4506.jpeg;IMG_4507.jpeg,SANTORINI -Utter Nonsense!,181254,Utter Nonsense,2015,boardgame,merged,,,version_unknown,"[{""bgg_id"": 181254, ""name"": ""Utter Nonsense"", ""year"": 2015, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4501.jpeg;IMG_4507.jpeg;IMG_4556.jpeg,Utter Nonsense! -EXPLODING KITTENS,172225,Exploding Kittens,2015,boardgame,merged,,,version_unknown,"[{""bgg_id"": 172225, ""name"": ""Exploding Kittens"", ""year"": 2015, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4501.jpeg;IMG_4505.jpeg,EXPLODING KITTENS -Herbaceous,195314,Herbaceous,2017,boardgame,auto,,,version_unknown,"[{""bgg_id"": 195314, ""name"": ""Herbaceous"", ""year"": 2017, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4507.jpeg, -ALICE IS MISSING: SILENT FALLS EXPANSION,,,,,unmatched,,,,[],[],IMG_4566.jpeg, -ALICE IS MISSING: A SILENT ROLE PLAYING GAME,,,,,unmatched,,,,[],[],IMG_4566.jpeg, +title_raw,bgg_id,bgg_name,year,type,match_status,version_id,version_name,version_status,candidates_json,version_candidates_json,source_photos,merged_into,dedupe_veto +ROYALS,165986,Royals,2014,boardgame,auto,,,version_unknown,"[{""bgg_id"": 165986, ""name"": ""Royals"", ""year"": 2014, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4499.jpeg,, +CAT CRIMES,235096,Cat Crimes,2017,boardgame,auto,360982,English edition,version_auto,"[{""bgg_id"": 235096, ""name"": ""Cat Crimes"", ""year"": 2017, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 360982, ""name"": ""English edition"", ""year"": 2017, ""publishers"": [""ThinkFun""], ""languages"": [""English""], ""score"": 3}]",IMG_4499.jpeg;IMG_4505.jpeg,, +COSMIC ENCOUNTER,40529,Cosmic Encounter,2008,boardgame,auto,,,version_unknown,"[{""bgg_id"": 39, ""name"": ""Cosmic Encounter"", ""year"": 1977, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 7000, ""rank"": 800}, {""bgg_id"": 40529, ""name"": ""Cosmic Encounter"", ""year"": 2008, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 75000, ""rank"": 150}]",[],IMG_4499.jpeg,, +SHERIFF OF NOTTINGHAM,157969,Sheriff of Nottingham,2014,boardgame,auto,,,version_unknown,"[{""bgg_id"": 157969, ""name"": ""Sheriff of Nottingham"", ""year"": 2014, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4499.jpeg,, +5 MINUTE DUNGEON,207830,5-Minute Dungeon,2017,boardgame,auto,,,version_unknown,"[{""bgg_id"": 207830, ""name"": ""5-Minute Dungeon"", ""year"": 2017, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4499.jpeg,, +Britannia,240,Britannia,1986,boardgame,auto,25668,Avalon Hill English edition,version_auto,"[{""bgg_id"": 240, ""name"": ""Britannia"", ""year"": 1986, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 25668, ""name"": ""Avalon Hill English edition"", ""year"": 1986, ""publishers"": [""The Avalon Hill Game Co""], ""languages"": [""English""], ""score"": 3}]",IMG_4500.jpeg,, +CIVILIZATION Game of the Heroic Age - The Dawn of History 8000 BC to 250 BC,71,Civilization,1980,boardgame,auto,71001,Avalon Hill English edition,version_auto,"[{""bgg_id"": 71, ""name"": ""Civilization"", ""year"": 1980, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 31.2, ""owned"": null, ""rank"": null}]","[{""version_id"": 71001, ""name"": ""Avalon Hill English edition"", ""year"": 1981, ""publishers"": [""The Avalon Hill Game Co""], ""languages"": [""English""], ""score"": 3}]",IMG_4500.jpeg,, +"ADVANCED CIVILIZATION Game Expansion of the Heroic Age - Featuring New Civilization, Commodity, and Calamity Cards",177,Advanced Civilization,1991,boardgameexpansion,auto,177001,Avalon Hill English edition,version_auto,"[{""bgg_id"": 177, ""name"": ""Advanced Civilization"", ""year"": 1991, ""type"": ""boardgameexpansion"", ""exact"": true, ""fuzzy"": 33.1, ""owned"": null, ""rank"": null}]","[{""version_id"": 177001, ""name"": ""Avalon Hill English edition"", ""year"": 1991, ""publishers"": [""The Avalon Hill Game Co""], ""languages"": [""English""], ""score"": 3}]",IMG_4500.jpeg,, +DOCTOR WHO The Game of Time & Space,3090,Doctor Who: The Game of Time & Space,1980,boardgame,auto,309001,Games Workshop English edition,version_auto,"[{""bgg_id"": 3090, ""name"": ""Doctor Who: The Game of Time & Space"", ""year"": 1980, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 309001, ""name"": ""Games Workshop English edition"", ""year"": 1980, ""publishers"": [""Games Workshop Ltd.""], ""languages"": [""English""], ""score"": 3}]",IMG_4500.jpeg,, +SORCERER The Game of Magical Conflict,3585,Sorcerer,1975,boardgame,auto,358501,SPI Designer's Edition,version_auto,"[{""bgg_id"": 3585, ""name"": ""Sorcerer"", ""year"": 1975, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 39.0, ""owned"": 900, ""rank"": 5000}, {""bgg_id"": 244115, ""name"": ""Sorcerer"", ""year"": 2019, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 39.0, ""owned"": 6000, ""rank"": 1500}]","[{""version_id"": 358501, ""name"": ""SPI Designer's Edition"", ""year"": 1975, ""publishers"": [""Simulations Publications, Inc. (SPI)""], ""languages"": [""English""], ""score"": 5}, {""version_id"": 358502, ""name"": ""SPI folio edition"", ""year"": 1975, ""publishers"": [""Simulations Publications, Inc. (SPI)""], ""languages"": [""English""], ""score"": 3}]",IMG_4500.jpeg,, +STARFORCE ALPHA CENTAURI Interstellar Conflict in the 25th Century,2524,StarForce 'Alpha Centauri': Interstellar Conflict in the 25th Century,1974,boardgame,auto,252401,SPI Designer's Edition,version_auto,"[{""bgg_id"": 2524, ""name"": ""StarForce 'Alpha Centauri': Interstellar Conflict in the 25th Century"", ""year"": 1974, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 252401, ""name"": ""SPI Designer's Edition"", ""year"": 1974, ""publishers"": [""Simulations Publications, Inc. (SPI)""], ""languages"": [""English""], ""score"": 5}, {""version_id"": 252402, ""name"": ""SPI folio edition"", ""year"": 1974, ""publishers"": [""Simulations Publications, Inc. (SPI)""], ""languages"": [""English""], ""score"": 3}]",IMG_4500.jpeg,, +SKIP-BO,1078,Skip-Bo,1967,boardgame,auto,,,version_unknown,"[{""bgg_id"": 1078, ""name"": ""Skip-Bo"", ""year"": 1967, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4501.jpeg;IMG_4542.jpeg,, +a Gentle Rain,268163,A Gentle Rain,2019,boardgame,auto,268164,Bloom Edition,version_auto,"[{""bgg_id"": 268163, ""name"": ""A Gentle Rain"", ""year"": 2019, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 268164, ""name"": ""Bloom Edition"", ""year"": 2021, ""publishers"": [""Mondo Games""], ""languages"": [""English""], ""score"": 3}]",IMG_4501.jpeg;IMG_4507.jpeg,, +PATCH WORK,163412,Patchwork,2014,boardgame,auto,,,version_unknown,"[{""bgg_id"": 163412, ""name"": ""Patchwork"", ""year"": 2014, ""type"": ""boardgame"", ""exact"": false, ""fuzzy"": 94.7, ""owned"": null, ""rank"": null}]",[],IMG_4501.jpeg,, +SANTORINI,194655,Santorini,2016,boardgame,auto,,,version_unknown,"[{""bgg_id"": 194655, ""name"": ""Santorini"", ""year"": 2016, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4501.jpeg;IMG_4502.jpeg;IMG_4506.jpeg;IMG_4507.jpeg,, +Utter Nonsense!,181254,Utter Nonsense,2015,boardgame,auto,,,version_unknown,"[{""bgg_id"": 181254, ""name"": ""Utter Nonsense"", ""year"": 2015, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4501.jpeg;IMG_4507.jpeg;IMG_4556.jpeg,, +Cards Against Humanity,50381,Cards Against Humanity,2009,boardgame,auto,,,version_unknown,"[{""bgg_id"": 50381, ""name"": ""Cards Against Humanity"", ""year"": 2009, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4501.jpeg,, +Joking Hazard,193621,Joking Hazard,2016,boardgame,auto,19362101,First edition,version_auto,"[{""bgg_id"": 193621, ""name"": ""Joking Hazard"", ""year"": 2016, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 19362101, ""name"": ""First edition"", ""year"": 2016, ""publishers"": [""Cyanide & Happiness""], ""languages"": [""English""], ""score"": 3}]",IMG_4501.jpeg;IMG_4502.jpeg,, +EXPLODING KITTENS,172225,Exploding Kittens,2015,boardgame,auto,,,version_unknown,"[{""bgg_id"": 172225, ""name"": ""Exploding Kittens"", ""year"": 2015, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4501.jpeg;IMG_4505.jpeg,, +Hebarceos,195314,Herbaceous,2017,boardgame,merged,,,version_unknown,[],[],IMG_4502.jpeg,Herbaceous, +Wiz-War,104710,Wiz-War,2012,boardgame,approved,,,version_unknown,"[{""bgg_id"": 1218, ""name"": ""Wiz-War"", ""year"": 1983, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 4000, ""rank"": 2500}, {""bgg_id"": 104710, ""name"": ""Wiz-War"", ""year"": 2012, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 8000, ""rank"": 1200}]",[],IMG_4502.jpeg;IMG_4504.jpeg;IMG_4528.jpeg,, +SCRAWL,218866,Scrawl,2017,boardgame,auto,,,version_unknown,"[{""bgg_id"": 218866, ""name"": ""Scrawl"", ""year"": 2017, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4502.jpeg,, +FLAT TOP,2529,Flat Top,1977,boardgame,auto,,,version_unknown,"[{""bgg_id"": 2529, ""name"": ""Flat Top"", ""year"": 1977, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4502.jpeg,, +Diplomacy,483,Diplomacy,1959,boardgame,auto,,,version_unknown,"[{""bgg_id"": 483, ""name"": ""Diplomacy"", ""year"": 1959, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4502.jpeg,, +Age of Renaissance,26,Age of Renaissance,1996,boardgame,auto,2601,Avalon Hill English edition,version_auto,"[{""bgg_id"": 26, ""name"": ""Age of Renaissance"", ""year"": 1996, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 2601, ""name"": ""Avalon Hill English edition"", ""year"": 1996, ""publishers"": [""The Avalon Hill Game Co""], ""languages"": [""English""], ""score"": 3}]",IMG_4502.jpeg,, +History of the World,224,History of the World,1991,boardgame,auto,22401,Avalon Hill English edition,version_auto,"[{""bgg_id"": 224, ""name"": ""History of the World"", ""year"": 1991, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 22401, ""name"": ""Avalon Hill English edition"", ""year"": 1993, ""publishers"": [""The Avalon Hill Game Co""], ""languages"": [""English""], ""score"": 3}]",IMG_4502.jpeg,, +DUNGEON!,1339,Dungeon!,1975,boardgame,auto,133903,TSR New Dungeon edition,version_approved,"[{""bgg_id"": 1339, ""name"": ""Dungeon!"", ""year"": 1975, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 133901, ""name"": ""TSR first edition"", ""year"": 1975, ""publishers"": [""TSR""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 133902, ""name"": ""TSR second edition"", ""year"": 1980, ""publishers"": [""TSR""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 133903, ""name"": ""TSR New Dungeon edition"", ""year"": 1989, ""publishers"": [""TSR""], ""languages"": [""English""], ""score"": 3}]",IMG_4502.jpeg,, +CAT CRIMES,235096,Cat Crimes,2017,boardgame,merged,360982,English edition,version_auto,"[{""bgg_id"": 235096, ""name"": ""Cat Crimes"", ""year"": 2017, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 360982, ""name"": ""English edition"", ""year"": 2017, ""publishers"": [""ThinkFun""], ""languages"": [""English""], ""score"": 3}]",IMG_4499.jpeg;IMG_4505.jpeg,CAT CRIMES, +a Gentle Rain,268163,A Gentle Rain,2019,boardgame,merged,268164,Bloom Edition,version_auto,"[{""bgg_id"": 268163, ""name"": ""A Gentle Rain"", ""year"": 2019, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 268164, ""name"": ""Bloom Edition"", ""year"": 2021, ""publishers"": [""Mondo Games""], ""languages"": [""English""], ""score"": 3}]",IMG_4501.jpeg;IMG_4507.jpeg,a Gentle Rain, +SANTORINI,194655,Santorini,2016,boardgame,merged,,,version_unknown,"[{""bgg_id"": 194655, ""name"": ""Santorini"", ""year"": 2016, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4501.jpeg;IMG_4502.jpeg;IMG_4506.jpeg;IMG_4507.jpeg,SANTORINI, +Utter Nonsense!,181254,Utter Nonsense,2015,boardgame,merged,,,version_unknown,"[{""bgg_id"": 181254, ""name"": ""Utter Nonsense"", ""year"": 2015, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4501.jpeg;IMG_4507.jpeg;IMG_4556.jpeg,Utter Nonsense!, +EXPLODING KITTENS,172225,Exploding Kittens,2015,boardgame,merged,,,version_unknown,"[{""bgg_id"": 172225, ""name"": ""Exploding Kittens"", ""year"": 2015, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4501.jpeg;IMG_4505.jpeg,EXPLODING KITTENS, +Herbaceous,195314,Herbaceous,2017,boardgame,auto,,,version_unknown,"[{""bgg_id"": 195314, ""name"": ""Herbaceous"", ""year"": 2017, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4507.jpeg,, +ALICE IS MISSING: SILENT FALLS EXPANSION,400002,Alice Is Missing: Silent Falls Expansion,2023,rpgitem,auto,,,version_unknown,"[{""bgg_id"": 400002, ""name"": ""Alice Is Missing: Silent Falls Expansion"", ""year"": 2023, ""type"": ""rpgitem"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4566.jpeg,, +ALICE IS MISSING: A SILENT ROLE PLAYING GAME,400001,Alice Is Missing: A Silent Role Playing Game,2020,rpgitem,auto,,,version_unknown,"[{""bgg_id"": 400001, ""name"": ""Alice Is Missing: A Silent Role Playing Game"", ""year"": 2020, ""type"": ""rpgitem"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4566.jpeg,, diff --git a/data/to_add.csv b/data/to_add.csv index 45fd2f7..b680301 100644 --- a/data/to_add.csv +++ b/data/to_add.csv @@ -1,9 +1,9 @@ -bgg_id,bgg_name,year,type,version_id,version_name,title_raw,source_photos -235096,Cat Crimes,2017,boardgame,360982,English edition,CAT CRIMES,IMG_4499.jpeg -1078,Skip-Bo,1967,boardgame,,,SKIP-BO,IMG_4501.jpeg -268163,A Gentle Rain,2019,boardgame,268164,Bloom Edition,a Gentle Rain,IMG_4501.jpeg -163412,Patchwork,2014,boardgame,,,PATCH WORK,IMG_4501.jpeg -181254,Utter Nonsense,2015,boardgame,,,Utter Nonsense!,IMG_4501.jpeg -50381,Cards Against Humanity,2009,boardgame,,,Cards Against Humanity,IMG_4501.jpeg -195314,Herbaceous,2017,boardgame,,,Hebarceos,IMG_4502.jpeg -218866,Scrawl,2017,boardgame,,,SCRAWL,IMG_4502.jpeg +bgg_id,bgg_name,year,type,version_id,version_name,title_raw,source_photos,second_copy +235096,Cat Crimes,2017,boardgame,360982,English edition,CAT CRIMES,IMG_4499.jpeg;IMG_4505.jpeg, +268163,A Gentle Rain,2019,boardgame,268164,Bloom Edition,a Gentle Rain,IMG_4501.jpeg;IMG_4507.jpeg, +1078,Skip-Bo,1967,boardgame,,,SKIP-BO,IMG_4501.jpeg;IMG_4542.jpeg, +163412,Patchwork,2014,boardgame,,,PATCH WORK,IMG_4501.jpeg, +181254,Utter Nonsense,2015,boardgame,,,Utter Nonsense!,IMG_4501.jpeg;IMG_4507.jpeg;IMG_4556.jpeg, +50381,Cards Against Humanity,2009,boardgame,,,Cards Against Humanity,IMG_4501.jpeg, +218866,Scrawl,2017,boardgame,,,SCRAWL,IMG_4502.jpeg, +195314,Herbaceous,2017,boardgame,,,Herbaceous,IMG_4502.jpeg;IMG_4507.jpeg, diff --git a/data/to_update.csv b/data/to_update.csv index 4767a43..0f2c24a 100644 --- a/data/to_update.csv +++ b/data/to_update.csv @@ -8,3 +8,4 @@ collid,bgg_id,bgg_name,version_id,version_name 53430387,193621,Joking Hazard,19362101,First edition 53429831,26,Age of Renaissance,2601,Avalon Hill English edition 53429816,224,History of the World,22401,Avalon Hill English edition +53430097,1339,Dungeon!,133903,TSR New Dungeon edition diff --git a/scripts/write_photo_fixtures.py b/scripts/write_photo_fixtures.py index 7b013d2..25c882e 100644 --- a/scripts/write_photo_fixtures.py +++ b/scripts/write_photo_fixtures.py @@ -110,6 +110,19 @@ SEARCHES: dict[str, list[tuple]] = { "ALICE IS": [], } +# rpgitem-search results for titles that live on RPGGeek, not BGG. Ids are +# SYNTHETIC like everything else here; the marker guards them. Queries not +# listed get an empty rpgitem stub automatically. +RPG = "rpgitem" +RPG_SEARCHES: dict[str, list[tuple]] = { + "ALICE IS MISSING: A SILENT ROLE PLAYING GAME": [ + (400001, "Alice Is Missing: A Silent Role Playing Game", 2020, RPG, "primary") + ], + "ALICE IS MISSING: SILENT FALLS EXPANSION": [ + (400002, "Alice Is Missing: Silent Falls Expansion", 2023, RPG, "primary") + ], +} + # ids-param -> [(bgg_id, name, year, type, owned, rank, [publishers])] THING_STATS: dict[str, list[tuple]] = { "39,40529": [ @@ -144,6 +157,8 @@ THING_STATS: dict[str, list[tuple]] = { # bgg_id -> [(version_id, name, year, [publishers], [languages])] VERSIONS: dict[int, list[tuple]] = { + 400001: [], # Alice Is Missing (rpgitem): no cataloged versions in stubs + 400002: [], 235096: [(360982, "English edition", 2017, ["ThinkFun"], ["English"])], 240: [ ( @@ -344,6 +359,8 @@ def main() -> None: for query, results in SEARCHES.items(): key = cache_key("search", {"query": query, "type": SEARCH_TYPES}) files[key] = search_xml(results) + rpg_key = cache_key("search", {"query": query, "type": "rpgitem"}) + files[rpg_key] = search_xml(RPG_SEARCHES.get(query, [])) for ids, things in THING_STATS.items(): files[cache_key("thing", {"id": ids, "stats": "1"})] = stats_xml(things) for bgg_id, versions in VERSIONS.items(): diff --git a/scripts/write_stub_fixtures.py b/scripts/write_stub_fixtures.py index 03a1441..249038b 100644 --- a/scripts/write_stub_fixtures.py +++ b/scripts/write_stub_fixtures.py @@ -106,6 +106,10 @@ def main() -> None: key = cache_key("search", {"query": query, "type": SEARCH_TYPES}) total = items.count("<item ") (FIXTURE_CACHE / key).write_text(f'<items total="{total}">{items}</items>') + # resolve falls back to an rpgitem search whenever the board-game + # search runs dry: every known query needs an (empty) answer there + rpg_key = cache_key("search", {"query": query, "type": "rpgitem"}) + (FIXTURE_CACHE / rpg_key).write_text('<items total="0"></items>') for (ids, flavor), xml in THINGS.items(): key = cache_key("thing", {"id": ids, flavor: "1"}) (FIXTURE_CACHE / key).write_text(xml) diff --git a/src/bggpipe/diff.py b/src/bggpipe/diff.py index 1e0f165..9ba3cbc 100644 --- a/src/bggpipe/diff.py +++ b/src/bggpipe/diff.py @@ -62,6 +62,7 @@ class DiffResult: to_add: list[dict] = field(default_factory=list) to_update: list[dict] = field(default_factory=list) already_owned: list[str] = field(default_factory=list) # title_raw + local_only: list[str] = field(default_factory=list) # RPGs etc: never uploaded second_copies: list[str] = field(default_factory=list) # notes for adds disagreements: list[str] = field(default_factory=list) # report-only unseen: list[CollectionItem] = field(default_factory=list) @@ -137,6 +138,11 @@ def compute_diff(rows: list[dict], collection: list[CollectionItem]) -> DiffResu if status not in RECOGNIZED_MATCH_STATUSES or not row["bgg_id"]: result.pending.append(row["title_raw"]) continue + if row["type"] == "rpgitem": + # a real, identified game — but it lives on RPGGeek, not in the + # BGG collection: a library citizen only, never queued + result.local_only.append(row["title_raw"]) + continue result.recognized += 1 recognized.append(row) if by_object.get(int(row["bgg_id"])): @@ -309,11 +315,14 @@ def run_diff(cfg: Config, *, client: BGGClient | None = None) -> DiffResult: atomic_write_csv(cfg.to_update_path, TO_UPDATE_COLUMNS, result.to_update) merged_note = f" · {result.merged} merged duplicate(s)" if result.merged else "" + local_note = ( + f" · {len(result.local_only)} local-only (RPGs)" if result.local_only else "" + ) typer.echo( f"\n{result.recognized} recognized · {len(result.already_owned)} already " f"owned · {len(result.to_add)} to add · {len(result.to_update)} version " f"update(s) · {len(result.pending)} pending review · " - f"{result.rejected} rejected{merged_note}" + f"{result.rejected} rejected{merged_note}{local_note}" ) if result.second_copies: typer.echo("\nSecond copies (verify these on the dry run before upload):") diff --git a/src/bggpipe/resolve.py b/src/bggpipe/resolve.py index 13d6d38..76e86ab 100644 --- a/src/bggpipe/resolve.py +++ b/src/bggpipe/resolve.py @@ -207,7 +207,11 @@ def _truncation_heads(title_raw: str) -> list[str]: def _plausible_candidates( - client: BGGClient, entry: TitleEntry, query: str, head_normalized: str = "" + client: BGGClient, + entry: TitleEntry, + query: str, + head_normalized: str = "", + types: str | None = None, ) -> list[Candidate]: """Search BGG and keep plausible candidates, one per id: exact-normalized or fuzzy>=90 against the FULL title, or — on truncated retries — exact @@ -218,7 +222,8 @@ def _plausible_candidates( if not entry.title_normalized: return [] by_id: dict[int, Candidate] = {} - for result in client.search(query): + results = client.search(query, types) if types else client.search(query) + for result in results: norm = normalize_title(result.name) exact = norm == entry.title_normalized fuzzy = fuzz.token_sort_ratio(norm, entry.title_normalized) @@ -396,6 +401,21 @@ def resolve_entry(client: BGGClient, entry: TitleEntry) -> MatchRow: cands = _plausible_candidates(client, entry, head, normalize_title(head)) if cands: break + if not cands: + # Not a board game? RPGs live in the same geekdo database under + # type=rpgitem (same API, same token). A hit becomes a LOCAL + # library citizen: identified and enriched, never uploaded (diff + # routes rpgitem rows to local_only). + cands = _plausible_candidates( + client, entry, entry.title_raw, types="rpgitem" + ) + if not cands: + for head in _truncation_heads(entry.title_raw): + cands = _plausible_candidates( + client, entry, head, normalize_title(head), types="rpgitem" + ) + if cands: + break row = _classify(client, entry, cands) if row.match_status == "auto": resolve_version(client, entry, row) diff --git a/src/bggpipe/static/app.css b/src/bggpipe/static/app.css index 62343d4..3057979 100644 --- a/src/bggpipe/static/app.css +++ b/src/bggpipe/static/app.css @@ -428,6 +428,9 @@ button.danger { color: var(--stop-ink); border-color: var(--stop); background: # padding: .15rem .5rem; border-radius: 6px; } .filterbar { display: flex; gap: .6rem; margin-bottom: 1rem; flex-wrap: wrap; } +.filterbar button[aria-pressed="true"] { + background: var(--accent); border-color: var(--ink); color: #fff; +} .filterbar input[type=search] { font: inherit; padding: .35rem .7rem; min-width: 16rem; border: var(--line); border-radius: var(--radius); background: #fff; diff --git a/src/bggpipe/templates/pages/catalog.html b/src/bggpipe/templates/pages/catalog.html index 7e1f1ca..ceca5c9 100644 --- a/src/bggpipe/templates/pages/catalog.html +++ b/src/bggpipe/templates/pages/catalog.html @@ -21,7 +21,8 @@ function render() { <td class="t">${esc(c.title_raw)}</td> <td>${statusChip(c)}</td> <td class="meta">${c.bgg_name ? esc(c.bgg_name) + (c.bgg_id ? " · " + esc(c.bgg_id) : "") : ""} - ${c.version_name ? " · " + esc(c.version_name) : ""}</td> + ${c.version_name ? " · " + esc(c.version_name) : ""} + ${c.type === "rpgitem" ? ` <span class="chip open">RPG · local only</span>` : ""}</td> <td class="meta">${c.photos.map(p => `<a href="/photos/view/${encodeURIComponent(p)}">${esc(p)}</a>` ).join(", ")}</td> diff --git a/src/bggpipe/templates/pages/library.html b/src/bggpipe/templates/pages/library.html index 38394fb..a6642fe 100644 --- a/src/bggpipe/templates/pages/library.html +++ b/src/bggpipe/templates/pages/library.html @@ -1,12 +1,16 @@ <h1>Library</h1> <div class="pagebar"><span id="libcount"></span></div> -<div class="filterbar"> +<div class="filterbar" role="group" aria-label="filter by kind"> <input type="search" id="libsearch" placeholder="search your games…" aria-label="search library"> + <button type="button" data-kind="" aria-pressed="true">All</button> + <button type="button" data-kind="boardgame" aria-pressed="false">Board games</button> + <button type="button" data-kind="rpgitem" aria-pressed="false">RPGs</button> </div> <div id="libbody"></div> <script> "use strict"; let GAMES = []; +let KIND = ""; // "" = all; "boardgame" also covers expansions function gameCard(g) { const art = g.thumbnail || g.image; @@ -22,18 +26,25 @@ function gameCard(g) { ? `<img src="${esc(art)}" alt="" loading="lazy">` : `<div class="noart" aria-hidden="true">${esc((g.name || "?").charAt(0).toUpperCase())}</div>`} <div class="info"> - <div class="gname">${esc(g.name)}${g.year ? ` <span class="meta">(${esc(g.year)})</span>` : ""}</div> + <div class="gname">${esc(g.name)}${g.year ? ` <span class="meta">(${esc(g.year)})</span>` : ""} + ${g.type === "rpgitem" ? `<span class="chip open">RPG · local only</span>` : ""}</div> <div class="gmeta">${[players, time, weight, rank].filter(Boolean).map(esc).join(" · ")}</div> ${g.version ? `<div class="gmeta">${esc(g.version.name || "")}</div>` : ""} </div> </article>`; } +function matchesKind(g) { + if (!KIND) return true; + if (KIND === "boardgame") return g.type !== "rpgitem"; + return g.type === "rpgitem"; +} + function render() { const q = document.getElementById("libsearch").value.trim().toLowerCase(); - const rows = q - ? GAMES.filter(g => (g.name + " " + (g.designers || []).join(" ")).toLowerCase().includes(q)) - : GAMES; + let rows = GAMES.filter(matchesKind); + if (q) rows = rows.filter(g => + (g.name + " " + (g.designers || []).join(" ")).toLowerCase().includes(q)); document.getElementById("libcount").innerHTML = `<b>${rows.length}</b> of <b>${GAMES.length}</b> game(s)`; document.getElementById("libbody").innerHTML = rows.length @@ -56,6 +67,12 @@ async function refresh() { } document.getElementById("libsearch").addEventListener("input", render); +document.querySelectorAll("[data-kind]").forEach(b => b.addEventListener("click", () => { + KIND = b.dataset.kind; + document.querySelectorAll("[data-kind]").forEach(o => + o.setAttribute("aria-pressed", String(o === b))); + render(); +})); refresh().catch(err => errorBanner(err.message || err)); pollLoop(refresh, 10000, () => showBanner("")); </script> diff --git a/src/bggpipe/webreview.py b/src/bggpipe/webreview.py index 02657eb..66f5193 100644 --- a/src/bggpipe/webreview.py +++ b/src/bggpipe/webreview.py @@ -367,6 +367,7 @@ def create_app( "confidence": entry.confidence, "photos": list(entry.source_photos), "status": row["match_status"] if row else "awaiting_resolve", + "type": row["type"] if row else "", "bgg_id": row["bgg_id"] if row else "", "bgg_name": row["bgg_name"] if row else "", "version_name": row["version_name"] if row else "", diff --git a/tests/fixtures/bgg_cache/search_query=5-MINUTE-DUNGEON-type=rpgitem_58faea4564.xml b/tests/fixtures/bgg_cache/search_query=5-MINUTE-DUNGEON-type=rpgitem_58faea4564.xml new file mode 100644 index 0000000..ba791c3 --- /dev/null +++ b/tests/fixtures/bgg_cache/search_query=5-MINUTE-DUNGEON-type=rpgitem_58faea4564.xml @@ -0,0 +1 @@ +<items total="0"></items> \ No newline at end of file diff --git a/tests/fixtures/bgg_cache/search_query=ADVANCED-CIVILIZATION-Game-Expansion-of-the-Heroic-Age---Featuring-New-Civ_7c5116c957.xml b/tests/fixtures/bgg_cache/search_query=ADVANCED-CIVILIZATION-Game-Expansion-of-the-Heroic-Age---Featuring-New-Civ_7c5116c957.xml new file mode 100644 index 0000000..ba791c3 --- /dev/null +++ b/tests/fixtures/bgg_cache/search_query=ADVANCED-CIVILIZATION-Game-Expansion-of-the-Heroic-Age---Featuring-New-Civ_7c5116c957.xml @@ -0,0 +1 @@ +<items total="0"></items> \ No newline at end of file diff --git a/tests/fixtures/bgg_cache/search_query=ADVANCED-CIVILIZATION-Game-Expansion-of-the-Heroic-Age-type=rpgitem_8704f88656.xml b/tests/fixtures/bgg_cache/search_query=ADVANCED-CIVILIZATION-Game-Expansion-of-the-Heroic-Age-type=rpgitem_8704f88656.xml new file mode 100644 index 0000000..ba791c3 --- /dev/null +++ b/tests/fixtures/bgg_cache/search_query=ADVANCED-CIVILIZATION-Game-Expansion-of-the-Heroic-Age-type=rpgitem_8704f88656.xml @@ -0,0 +1 @@ +<items total="0"></items> \ No newline at end of file diff --git a/tests/fixtures/bgg_cache/search_query=ADVANCED-CIVILIZATION-type=rpgitem_436f8cfac3.xml b/tests/fixtures/bgg_cache/search_query=ADVANCED-CIVILIZATION-type=rpgitem_436f8cfac3.xml new file mode 100644 index 0000000..ba791c3 --- /dev/null +++ b/tests/fixtures/bgg_cache/search_query=ADVANCED-CIVILIZATION-type=rpgitem_436f8cfac3.xml @@ -0,0 +1 @@ +<items total="0"></items> \ No newline at end of file diff --git a/tests/fixtures/bgg_cache/search_query=ALICE-IS-MISSING-3A-A-SILENT-ROLE-PLAYING-GAME-type=rpgitem_8efe1bd6f0.xml b/tests/fixtures/bgg_cache/search_query=ALICE-IS-MISSING-3A-A-SILENT-ROLE-PLAYING-GAME-type=rpgitem_8efe1bd6f0.xml new file mode 100644 index 0000000..3868c9e --- /dev/null +++ b/tests/fixtures/bgg_cache/search_query=ALICE-IS-MISSING-3A-A-SILENT-ROLE-PLAYING-GAME-type=rpgitem_8efe1bd6f0.xml @@ -0,0 +1 @@ +<items total="1"><item type="rpgitem" id="400001"><name type="primary" value="Alice Is Missing: A Silent Role Playing Game"/><yearpublished value="2020"/></item></items> \ No newline at end of file diff --git a/tests/fixtures/bgg_cache/search_query=ALICE-IS-MISSING-3A-A-SILENT-ROLE-PLAYING-type=rpgitem_daff8eceef.xml b/tests/fixtures/bgg_cache/search_query=ALICE-IS-MISSING-3A-A-SILENT-ROLE-PLAYING-type=rpgitem_daff8eceef.xml new file mode 100644 index 0000000..ba791c3 --- /dev/null +++ b/tests/fixtures/bgg_cache/search_query=ALICE-IS-MISSING-3A-A-SILENT-ROLE-PLAYING-type=rpgitem_daff8eceef.xml @@ -0,0 +1 @@ +<items total="0"></items> \ No newline at end of file diff --git a/tests/fixtures/bgg_cache/search_query=ALICE-IS-MISSING-3A-SILENT-FALLS-EXPANSION-type=rpgitem_82261134d9.xml b/tests/fixtures/bgg_cache/search_query=ALICE-IS-MISSING-3A-SILENT-FALLS-EXPANSION-type=rpgitem_82261134d9.xml new file mode 100644 index 0000000..2988563 --- /dev/null +++ b/tests/fixtures/bgg_cache/search_query=ALICE-IS-MISSING-3A-SILENT-FALLS-EXPANSION-type=rpgitem_82261134d9.xml @@ -0,0 +1 @@ +<items total="1"><item type="rpgitem" id="400002"><name type="primary" value="Alice Is Missing: Silent Falls Expansion"/><yearpublished value="2023"/></item></items> \ No newline at end of file diff --git a/tests/fixtures/bgg_cache/search_query=ALICE-IS-MISSING-type=rpgitem_088ab34fc2.xml b/tests/fixtures/bgg_cache/search_query=ALICE-IS-MISSING-type=rpgitem_088ab34fc2.xml new file mode 100644 index 0000000..ba791c3 --- /dev/null +++ b/tests/fixtures/bgg_cache/search_query=ALICE-IS-MISSING-type=rpgitem_088ab34fc2.xml @@ -0,0 +1 @@ +<items total="0"></items> \ No newline at end of file diff --git a/tests/fixtures/bgg_cache/search_query=ALICE-IS-type=rpgitem_f2de3eea31.xml b/tests/fixtures/bgg_cache/search_query=ALICE-IS-type=rpgitem_f2de3eea31.xml new file mode 100644 index 0000000..ba791c3 --- /dev/null +++ b/tests/fixtures/bgg_cache/search_query=ALICE-IS-type=rpgitem_f2de3eea31.xml @@ -0,0 +1 @@ +<items total="0"></items> \ No newline at end of file diff --git a/tests/fixtures/bgg_cache/search_query=Age-of-Renaissance-type=rpgitem_83ec5d7426.xml b/tests/fixtures/bgg_cache/search_query=Age-of-Renaissance-type=rpgitem_83ec5d7426.xml new file mode 100644 index 0000000..ba791c3 --- /dev/null +++ b/tests/fixtures/bgg_cache/search_query=Age-of-Renaissance-type=rpgitem_83ec5d7426.xml @@ -0,0 +1 @@ +<items total="0"></items> \ No newline at end of file diff --git a/tests/fixtures/bgg_cache/search_query=Blorvath-3A-Quest-of-the-Zzyzx-type=rpgitem_288971e44c.xml b/tests/fixtures/bgg_cache/search_query=Blorvath-3A-Quest-of-the-Zzyzx-type=rpgitem_288971e44c.xml new file mode 100644 index 0000000..ba791c3 --- /dev/null +++ b/tests/fixtures/bgg_cache/search_query=Blorvath-3A-Quest-of-the-Zzyzx-type=rpgitem_288971e44c.xml @@ -0,0 +1 @@ +<items total="0"></items> \ No newline at end of file diff --git a/tests/fixtures/bgg_cache/search_query=Blorvath-type=rpgitem_ad250413dc.xml b/tests/fixtures/bgg_cache/search_query=Blorvath-type=rpgitem_ad250413dc.xml new file mode 100644 index 0000000..ba791c3 --- /dev/null +++ b/tests/fixtures/bgg_cache/search_query=Blorvath-type=rpgitem_ad250413dc.xml @@ -0,0 +1 @@ +<items total="0"></items> \ No newline at end of file diff --git a/tests/fixtures/bgg_cache/search_query=Britannia-type=rpgitem_78177b4e85.xml b/tests/fixtures/bgg_cache/search_query=Britannia-type=rpgitem_78177b4e85.xml new file mode 100644 index 0000000..ba791c3 --- /dev/null +++ b/tests/fixtures/bgg_cache/search_query=Britannia-type=rpgitem_78177b4e85.xml @@ -0,0 +1 @@ +<items total="0"></items> \ No newline at end of file diff --git a/tests/fixtures/bgg_cache/search_query=CAT-CRIMES-type=rpgitem_3d56b6309b.xml b/tests/fixtures/bgg_cache/search_query=CAT-CRIMES-type=rpgitem_3d56b6309b.xml new file mode 100644 index 0000000..ba791c3 --- /dev/null +++ b/tests/fixtures/bgg_cache/search_query=CAT-CRIMES-type=rpgitem_3d56b6309b.xml @@ -0,0 +1 @@ +<items total="0"></items> \ No newline at end of file diff --git a/tests/fixtures/bgg_cache/search_query=CIVILIZATION-Game-of-the-Heroic-Age---The-Dawn-of-History-8000-BC-to-250-B_4292e34c30.xml b/tests/fixtures/bgg_cache/search_query=CIVILIZATION-Game-of-the-Heroic-Age---The-Dawn-of-History-8000-BC-to-250-B_4292e34c30.xml new file mode 100644 index 0000000..ba791c3 --- /dev/null +++ b/tests/fixtures/bgg_cache/search_query=CIVILIZATION-Game-of-the-Heroic-Age---The-Dawn-of-History-8000-BC-to-250-B_4292e34c30.xml @@ -0,0 +1 @@ +<items total="0"></items> \ No newline at end of file diff --git a/tests/fixtures/bgg_cache/search_query=CIVILIZATION-Game-of-the-Heroic-Age-type=rpgitem_05fdc9d88b.xml b/tests/fixtures/bgg_cache/search_query=CIVILIZATION-Game-of-the-Heroic-Age-type=rpgitem_05fdc9d88b.xml new file mode 100644 index 0000000..ba791c3 --- /dev/null +++ b/tests/fixtures/bgg_cache/search_query=CIVILIZATION-Game-of-the-Heroic-Age-type=rpgitem_05fdc9d88b.xml @@ -0,0 +1 @@ +<items total="0"></items> \ No newline at end of file diff --git a/tests/fixtures/bgg_cache/search_query=CIVILIZATION-type=rpgitem_5e9e2b0726.xml b/tests/fixtures/bgg_cache/search_query=CIVILIZATION-type=rpgitem_5e9e2b0726.xml new file mode 100644 index 0000000..ba791c3 --- /dev/null +++ b/tests/fixtures/bgg_cache/search_query=CIVILIZATION-type=rpgitem_5e9e2b0726.xml @@ -0,0 +1 @@ +<items total="0"></items> \ No newline at end of file diff --git a/tests/fixtures/bgg_cache/search_query=COSMIC-ENCOUNTER-type=rpgitem_14e74aa71f.xml b/tests/fixtures/bgg_cache/search_query=COSMIC-ENCOUNTER-type=rpgitem_14e74aa71f.xml new file mode 100644 index 0000000..ba791c3 --- /dev/null +++ b/tests/fixtures/bgg_cache/search_query=COSMIC-ENCOUNTER-type=rpgitem_14e74aa71f.xml @@ -0,0 +1 @@ +<items total="0"></items> \ No newline at end of file diff --git a/tests/fixtures/bgg_cache/search_query=Caf-C3-A9-International-type=rpgitem_957f2522ae.xml b/tests/fixtures/bgg_cache/search_query=Caf-C3-A9-International-type=rpgitem_957f2522ae.xml new file mode 100644 index 0000000..ba791c3 --- /dev/null +++ b/tests/fixtures/bgg_cache/search_query=Caf-C3-A9-International-type=rpgitem_957f2522ae.xml @@ -0,0 +1 @@ +<items total="0"></items> \ No newline at end of file diff --git a/tests/fixtures/bgg_cache/search_query=Cards-Against-Humanity-type=rpgitem_04b2bcdf24.xml b/tests/fixtures/bgg_cache/search_query=Cards-Against-Humanity-type=rpgitem_04b2bcdf24.xml new file mode 100644 index 0000000..ba791c3 --- /dev/null +++ b/tests/fixtures/bgg_cache/search_query=Cards-Against-Humanity-type=rpgitem_04b2bcdf24.xml @@ -0,0 +1 @@ +<items total="0"></items> \ No newline at end of file diff --git a/tests/fixtures/bgg_cache/search_query=Catan-type=rpgitem_63fb6e5035.xml b/tests/fixtures/bgg_cache/search_query=Catan-type=rpgitem_63fb6e5035.xml new file mode 100644 index 0000000..ba791c3 --- /dev/null +++ b/tests/fixtures/bgg_cache/search_query=Catan-type=rpgitem_63fb6e5035.xml @@ -0,0 +1 @@ +<items total="0"></items> \ No newline at end of file diff --git a/tests/fixtures/bgg_cache/search_query=Citadels-type=rpgitem_22dea38263.xml b/tests/fixtures/bgg_cache/search_query=Citadels-type=rpgitem_22dea38263.xml new file mode 100644 index 0000000..ba791c3 --- /dev/null +++ b/tests/fixtures/bgg_cache/search_query=Citadels-type=rpgitem_22dea38263.xml @@ -0,0 +1 @@ +<items total="0"></items> \ No newline at end of file diff --git a/tests/fixtures/bgg_cache/search_query=DOCTOR-WHO-The-Game-of-Time-26-Space-type=rpgitem_48015c5934.xml b/tests/fixtures/bgg_cache/search_query=DOCTOR-WHO-The-Game-of-Time-26-Space-type=rpgitem_48015c5934.xml new file mode 100644 index 0000000..ba791c3 --- /dev/null +++ b/tests/fixtures/bgg_cache/search_query=DOCTOR-WHO-The-Game-of-Time-26-Space-type=rpgitem_48015c5934.xml @@ -0,0 +1 @@ +<items total="0"></items> \ No newline at end of file diff --git a/tests/fixtures/bgg_cache/search_query=DUNGEON-21-type=rpgitem_95d15d03fd.xml b/tests/fixtures/bgg_cache/search_query=DUNGEON-21-type=rpgitem_95d15d03fd.xml new file mode 100644 index 0000000..ba791c3 --- /dev/null +++ b/tests/fixtures/bgg_cache/search_query=DUNGEON-21-type=rpgitem_95d15d03fd.xml @@ -0,0 +1 @@ +<items total="0"></items> \ No newline at end of file diff --git a/tests/fixtures/bgg_cache/search_query=Diplomacy-type=rpgitem_0747a2387d.xml b/tests/fixtures/bgg_cache/search_query=Diplomacy-type=rpgitem_0747a2387d.xml new file mode 100644 index 0000000..ba791c3 --- /dev/null +++ b/tests/fixtures/bgg_cache/search_query=Diplomacy-type=rpgitem_0747a2387d.xml @@ -0,0 +1 @@ +<items total="0"></items> \ No newline at end of file diff --git a/tests/fixtures/bgg_cache/search_query=Dungeon-21-type=rpgitem_3bf546910a.xml b/tests/fixtures/bgg_cache/search_query=Dungeon-21-type=rpgitem_3bf546910a.xml new file mode 100644 index 0000000..ba791c3 --- /dev/null +++ b/tests/fixtures/bgg_cache/search_query=Dungeon-21-type=rpgitem_3bf546910a.xml @@ -0,0 +1 @@ +<items total="0"></items> \ No newline at end of file diff --git a/tests/fixtures/bgg_cache/search_query=EXPLODING-KITTENS-type=rpgitem_49ed26a640.xml b/tests/fixtures/bgg_cache/search_query=EXPLODING-KITTENS-type=rpgitem_49ed26a640.xml new file mode 100644 index 0000000..ba791c3 --- /dev/null +++ b/tests/fixtures/bgg_cache/search_query=EXPLODING-KITTENS-type=rpgitem_49ed26a640.xml @@ -0,0 +1 @@ +<items total="0"></items> \ No newline at end of file diff --git a/tests/fixtures/bgg_cache/search_query=FLAT-TOP-type=rpgitem_c1e66fb254.xml b/tests/fixtures/bgg_cache/search_query=FLAT-TOP-type=rpgitem_c1e66fb254.xml new file mode 100644 index 0000000..ba791c3 --- /dev/null +++ b/tests/fixtures/bgg_cache/search_query=FLAT-TOP-type=rpgitem_c1e66fb254.xml @@ -0,0 +1 @@ +<items total="0"></items> \ No newline at end of file diff --git a/tests/fixtures/bgg_cache/search_query=Hebarceos-type=rpgitem_07740d9a32.xml b/tests/fixtures/bgg_cache/search_query=Hebarceos-type=rpgitem_07740d9a32.xml new file mode 100644 index 0000000..ba791c3 --- /dev/null +++ b/tests/fixtures/bgg_cache/search_query=Hebarceos-type=rpgitem_07740d9a32.xml @@ -0,0 +1 @@ +<items total="0"></items> \ No newline at end of file diff --git a/tests/fixtures/bgg_cache/search_query=Herbaceous-type=rpgitem_d78832e3ed.xml b/tests/fixtures/bgg_cache/search_query=Herbaceous-type=rpgitem_d78832e3ed.xml new file mode 100644 index 0000000..ba791c3 --- /dev/null +++ b/tests/fixtures/bgg_cache/search_query=Herbaceous-type=rpgitem_d78832e3ed.xml @@ -0,0 +1 @@ +<items total="0"></items> \ No newline at end of file diff --git a/tests/fixtures/bgg_cache/search_query=History-of-the-World-type=rpgitem_7928f17f52.xml b/tests/fixtures/bgg_cache/search_query=History-of-the-World-type=rpgitem_7928f17f52.xml new file mode 100644 index 0000000..ba791c3 --- /dev/null +++ b/tests/fixtures/bgg_cache/search_query=History-of-the-World-type=rpgitem_7928f17f52.xml @@ -0,0 +1 @@ +<items total="0"></items> \ No newline at end of file diff --git a/tests/fixtures/bgg_cache/search_query=Joking-Hazard-type=rpgitem_3550758066.xml b/tests/fixtures/bgg_cache/search_query=Joking-Hazard-type=rpgitem_3550758066.xml new file mode 100644 index 0000000..ba791c3 --- /dev/null +++ b/tests/fixtures/bgg_cache/search_query=Joking-Hazard-type=rpgitem_3550758066.xml @@ -0,0 +1 @@ +<items total="0"></items> \ No newline at end of file diff --git a/tests/fixtures/bgg_cache/search_query=PATCH-WORK-type=rpgitem_afb691fd45.xml b/tests/fixtures/bgg_cache/search_query=PATCH-WORK-type=rpgitem_afb691fd45.xml new file mode 100644 index 0000000..ba791c3 --- /dev/null +++ b/tests/fixtures/bgg_cache/search_query=PATCH-WORK-type=rpgitem_afb691fd45.xml @@ -0,0 +1 @@ +<items total="0"></items> \ No newline at end of file diff --git a/tests/fixtures/bgg_cache/search_query=ROYALS-type=rpgitem_740855ed66.xml b/tests/fixtures/bgg_cache/search_query=ROYALS-type=rpgitem_740855ed66.xml new file mode 100644 index 0000000..ba791c3 --- /dev/null +++ b/tests/fixtures/bgg_cache/search_query=ROYALS-type=rpgitem_740855ed66.xml @@ -0,0 +1 @@ +<items total="0"></items> \ No newline at end of file diff --git a/tests/fixtures/bgg_cache/search_query=SANTORINI-type=rpgitem_b3713fc18f.xml b/tests/fixtures/bgg_cache/search_query=SANTORINI-type=rpgitem_b3713fc18f.xml new file mode 100644 index 0000000..ba791c3 --- /dev/null +++ b/tests/fixtures/bgg_cache/search_query=SANTORINI-type=rpgitem_b3713fc18f.xml @@ -0,0 +1 @@ +<items total="0"></items> \ No newline at end of file diff --git a/tests/fixtures/bgg_cache/search_query=SCRAWL-type=rpgitem_28917ca749.xml b/tests/fixtures/bgg_cache/search_query=SCRAWL-type=rpgitem_28917ca749.xml new file mode 100644 index 0000000..ba791c3 --- /dev/null +++ b/tests/fixtures/bgg_cache/search_query=SCRAWL-type=rpgitem_28917ca749.xml @@ -0,0 +1 @@ +<items total="0"></items> \ No newline at end of file diff --git a/tests/fixtures/bgg_cache/search_query=SHERIFF-OF-NOTTINGHAM-type=rpgitem_77cf1f8445.xml b/tests/fixtures/bgg_cache/search_query=SHERIFF-OF-NOTTINGHAM-type=rpgitem_77cf1f8445.xml new file mode 100644 index 0000000..ba791c3 --- /dev/null +++ b/tests/fixtures/bgg_cache/search_query=SHERIFF-OF-NOTTINGHAM-type=rpgitem_77cf1f8445.xml @@ -0,0 +1 @@ +<items total="0"></items> \ No newline at end of file diff --git a/tests/fixtures/bgg_cache/search_query=SKIP-BO-type=rpgitem_3f9e6315a6.xml b/tests/fixtures/bgg_cache/search_query=SKIP-BO-type=rpgitem_3f9e6315a6.xml new file mode 100644 index 0000000..ba791c3 --- /dev/null +++ b/tests/fixtures/bgg_cache/search_query=SKIP-BO-type=rpgitem_3f9e6315a6.xml @@ -0,0 +1 @@ +<items total="0"></items> \ No newline at end of file diff --git a/tests/fixtures/bgg_cache/search_query=SORCERER-The-Game-of-Magical-Conflict-type=rpgitem_2ba94ce51c.xml b/tests/fixtures/bgg_cache/search_query=SORCERER-The-Game-of-Magical-Conflict-type=rpgitem_2ba94ce51c.xml new file mode 100644 index 0000000..ba791c3 --- /dev/null +++ b/tests/fixtures/bgg_cache/search_query=SORCERER-The-Game-of-Magical-Conflict-type=rpgitem_2ba94ce51c.xml @@ -0,0 +1 @@ +<items total="0"></items> \ No newline at end of file diff --git a/tests/fixtures/bgg_cache/search_query=SORCERER-type=rpgitem_0a90b76555.xml b/tests/fixtures/bgg_cache/search_query=SORCERER-type=rpgitem_0a90b76555.xml new file mode 100644 index 0000000..ba791c3 --- /dev/null +++ b/tests/fixtures/bgg_cache/search_query=SORCERER-type=rpgitem_0a90b76555.xml @@ -0,0 +1 @@ +<items total="0"></items> \ No newline at end of file diff --git a/tests/fixtures/bgg_cache/search_query=STARFORCE-ALPHA-CENTAURI-Interstellar-Conflict-in-the-25th-Century-type=rp_d8afe77786.xml b/tests/fixtures/bgg_cache/search_query=STARFORCE-ALPHA-CENTAURI-Interstellar-Conflict-in-the-25th-Century-type=rp_d8afe77786.xml new file mode 100644 index 0000000..ba791c3 --- /dev/null +++ b/tests/fixtures/bgg_cache/search_query=STARFORCE-ALPHA-CENTAURI-Interstellar-Conflict-in-the-25th-Century-type=rp_d8afe77786.xml @@ -0,0 +1 @@ +<items total="0"></items> \ No newline at end of file diff --git a/tests/fixtures/bgg_cache/search_query=STARFORCE-ALPHA-type=rpgitem_5fc9118ed9.xml b/tests/fixtures/bgg_cache/search_query=STARFORCE-ALPHA-type=rpgitem_5fc9118ed9.xml new file mode 100644 index 0000000..ba791c3 --- /dev/null +++ b/tests/fixtures/bgg_cache/search_query=STARFORCE-ALPHA-type=rpgitem_5fc9118ed9.xml @@ -0,0 +1 @@ +<items total="0"></items> \ No newline at end of file diff --git a/tests/fixtures/bgg_cache/search_query=Utter-Nonsense-21-type=rpgitem_f01cc4ca29.xml b/tests/fixtures/bgg_cache/search_query=Utter-Nonsense-21-type=rpgitem_f01cc4ca29.xml new file mode 100644 index 0000000..ba791c3 --- /dev/null +++ b/tests/fixtures/bgg_cache/search_query=Utter-Nonsense-21-type=rpgitem_f01cc4ca29.xml @@ -0,0 +1 @@ +<items total="0"></items> \ No newline at end of file diff --git a/tests/fixtures/bgg_cache/search_query=Wingspan-3A-European-Expansion-type=rpgitem_38f0ad0fff.xml b/tests/fixtures/bgg_cache/search_query=Wingspan-3A-European-Expansion-type=rpgitem_38f0ad0fff.xml new file mode 100644 index 0000000..ba791c3 --- /dev/null +++ b/tests/fixtures/bgg_cache/search_query=Wingspan-3A-European-Expansion-type=rpgitem_38f0ad0fff.xml @@ -0,0 +1 @@ +<items total="0"></items> \ No newline at end of file diff --git a/tests/fixtures/bgg_cache/search_query=Wingspan-Europe-type=rpgitem_4387722048.xml b/tests/fixtures/bgg_cache/search_query=Wingspan-Europe-type=rpgitem_4387722048.xml new file mode 100644 index 0000000..ba791c3 --- /dev/null +++ b/tests/fixtures/bgg_cache/search_query=Wingspan-Europe-type=rpgitem_4387722048.xml @@ -0,0 +1 @@ +<items total="0"></items> \ No newline at end of file diff --git a/tests/fixtures/bgg_cache/search_query=Wingspan-type=rpgitem_f93bee11fc.xml b/tests/fixtures/bgg_cache/search_query=Wingspan-type=rpgitem_f93bee11fc.xml new file mode 100644 index 0000000..ba791c3 --- /dev/null +++ b/tests/fixtures/bgg_cache/search_query=Wingspan-type=rpgitem_f93bee11fc.xml @@ -0,0 +1 @@ +<items total="0"></items> \ No newline at end of file diff --git a/tests/fixtures/bgg_cache/search_query=Wiz-War-type=rpgitem_3adf0a77c6.xml b/tests/fixtures/bgg_cache/search_query=Wiz-War-type=rpgitem_3adf0a77c6.xml new file mode 100644 index 0000000..ba791c3 --- /dev/null +++ b/tests/fixtures/bgg_cache/search_query=Wiz-War-type=rpgitem_3adf0a77c6.xml @@ -0,0 +1 @@ +<items total="0"></items> \ No newline at end of file diff --git a/tests/fixtures/bgg_cache/search_query=a-Gentle-Rain-type=rpgitem_a3470f199b.xml b/tests/fixtures/bgg_cache/search_query=a-Gentle-Rain-type=rpgitem_a3470f199b.xml new file mode 100644 index 0000000..ba791c3 --- /dev/null +++ b/tests/fixtures/bgg_cache/search_query=a-Gentle-Rain-type=rpgitem_a3470f199b.xml @@ -0,0 +1 @@ +<items total="0"></items> \ No newline at end of file diff --git a/tests/fixtures/bgg_cache/thing_id=400001-versions=1_6c87ca2e4a.xml b/tests/fixtures/bgg_cache/thing_id=400001-versions=1_6c87ca2e4a.xml new file mode 100644 index 0000000..ebee33f --- /dev/null +++ b/tests/fixtures/bgg_cache/thing_id=400001-versions=1_6c87ca2e4a.xml @@ -0,0 +1 @@ +<items><item type="boardgame" id="400001"><name type="primary" value="game"/><versions></versions></item></items> \ No newline at end of file diff --git a/tests/fixtures/bgg_cache/thing_id=400002-versions=1_a82fe6afff.xml b/tests/fixtures/bgg_cache/thing_id=400002-versions=1_a82fe6afff.xml new file mode 100644 index 0000000..24e2613 --- /dev/null +++ b/tests/fixtures/bgg_cache/thing_id=400002-versions=1_a82fe6afff.xml @@ -0,0 +1 @@ +<items><item type="boardgame" id="400002"><name type="primary" value="game"/><versions></versions></item></items> \ No newline at end of file diff --git a/tests/test_diff.py b/tests/test_diff.py index 9f708f8..acc3c1b 100644 --- a/tests/test_diff.py +++ b/tests/test_diff.py @@ -397,3 +397,11 @@ def test_second_copy_flag_travels_on_exhausted_adds(): result = compute_diff(rows, [_item(13, 900, version_id=123)]) (added,) = result.to_add assert added["second_copy"] == "1" + + +def test_rpgitem_rows_are_local_only_never_queued(): + rpg = {**_match("ALICE IS MISSING", "400001"), "type": "rpgitem"} + result = compute_diff([rpg, _match("Catan", "13")], []) + assert result.local_only == ["ALICE IS MISSING"] + assert [r["bgg_id"] for r in result.to_add] == ["13"] # RPG never queued + assert result.recognized == 1 # RPGs aren't counted against the queue diff --git a/tests/test_resolve.py b/tests/test_resolve.py index 8e0b90a..866ecb8 100644 --- a/tests/test_resolve.py +++ b/tests/test_resolve.py @@ -672,3 +672,16 @@ def test_merged_into_chains_resolve_to_terminal_survivor(): dedupe_matches([x, y, w], []) assert y["match_status"] == "merged" and y["merged_into"] == "Wingspan" assert x["merged_into"] == "Wingspan" # chain collapsed + + +def test_rpg_falls_back_to_rpgitem_search(client): + # Alice Is Missing: absent from the board-game search, present as an + # rpgitem — same geekdo API, resolved as a local library citizen + entry = TitleEntry( + title_raw="ALICE IS MISSING: A SILENT ROLE PLAYING GAME", + title_normalized="alice is missing silent role playing game", + ) + row = resolve_entry(client, entry) + assert row.match_status == "auto" + assert row.type == "rpgitem" + assert row.bgg_id == 400001