604 lines
21 KiB
Org Mode
604 lines
21 KiB
Org Mode
#+title: Vrobbler Project
|
|
|
|
We should convert this PROJECT file to put tickets in a subdirectory, tickets, with each ticket having it's own shortid_title.org
|
|
|
|
* Overview
|
|
|
|
Vrobbler began humbly enough as a way to use Jellyfin's webhook to keep track of
|
|
the shows and movies I was watching. More specifically, I broke my ankle a few
|
|
days after Christmas in 2022 and spent the next four months very slowly
|
|
recovering after surgical repair. So once I had the webhook working, and
|
|
scrobbling videos, it was only a matter of time till I expaned it to mopidy to
|
|
replicate LastFM. Then I added board games, books via KoReader, sports events,
|
|
podcasts ... it just keeps going. Vrobbler is now a sort of Frankenstein's
|
|
monster of scrobbling an entire life.
|
|
|
|
I am still unconvinced I can keep this going, but being able to scrobble org
|
|
tasks, Todoist tasks, web pages I've read and trails I've hiked has turned out
|
|
to be sometimes cathartic and sometimes functional as I try to remember when I
|
|
did a thing.
|
|
|
|
* Backlog [0/32] :vrobbler:project:personal:
|
|
** TODO [#C] Configure IMAP folder/start in user profile :imap:settings:
|
|
*** Description
|
|
|
|
Refactor the IMAP import flow as something that can be configured in a tab of the user profile's settings.
|
|
|
|
*** Implementation
|
|
|
|
Mostly I used webdav now, but we should maintain IMAP importing as an option in user settings.
|
|
|
|
- File: ~vrobbler/apps/scrobbles/importers/imap.py~ (line 30)
|
|
- Mailbox folder is hardcoded to ="INBOX"=. Should be configurable in the
|
|
user's IMAP profile settings, especially for users with complex folder structures.
|
|
|
|
** TODO [#C] Verify Last.fm timestamp timezone handling :lastfm:timestamps:
|
|
*** Description
|
|
|
|
Last.FM timezone handling may not work 100% correctly. And maybe that's okay.
|
|
|
|
*** Implementation
|
|
|
|
- File: ~vrobbler/apps/scrobbles/importers/lastfm.py~ (line 148)
|
|
- A TODO questions whether the current =utcfromtimestamp()= + =.replace()=
|
|
pattern is correct. The commented-out =datetime.fromtimestamp(..., UTC)=
|
|
may be the right approach. Needs review.
|
|
|
|
** TODO [#C] After transition to linux add curl_cffi as webpage scrapper again :webpages:metadata:
|
|
:PROPERTIES:
|
|
:ID: d3cce1a7-d540-4d66-bf66-e75378e4eac7
|
|
:END:
|
|
** TODO [#C] Create small utility to clean up tracks scrobbled with wonky playback times :bug:music:scrobbles:
|
|
:PROPERTIES:
|
|
:ID: 702462cf-d54b-48c6-8a7c-78b8de751deb
|
|
:END:
|
|
** TODO [#C] Move to using more robust mopidy-webhooks pacakge form pypi :utility:improvement:
|
|
:PROPERTIES:
|
|
:ID: ab31fdc3-359c-1b1d-6b9d-546b476021ba
|
|
:END:
|
|
*** Example payloads from mopidy-webhooks
|
|
**** Podcast playback ended
|
|
#+begin_src json
|
|
{
|
|
"type": "event",
|
|
"event": "track_playback_ended",
|
|
"data": {
|
|
"tl_track": {
|
|
"__model__": "TlTrack",
|
|
"tlid": 13,
|
|
"track": {
|
|
"__model__": "Track",
|
|
"uri": "file:///var/lib/mopidy/media/podcasts/The%20Prince/2022-09-28-Wolf-warriors.mp3",
|
|
"name": "Wolf warriors",
|
|
"artists": [
|
|
{
|
|
"__model__": "Artist",
|
|
"name": "The Economist"
|
|
}
|
|
],
|
|
"album": {
|
|
"__model__": "Album",
|
|
"name": "The Prince",
|
|
"date": "2022"
|
|
},
|
|
"genre": "Blues",
|
|
"date": "2022",
|
|
"length": 2437778,
|
|
"bitrate": 127988
|
|
}
|
|
},
|
|
"time_position": 3290
|
|
}
|
|
}
|
|
#+end_src
|
|
**** Podcast playback state changes
|
|
#+begin_src json
|
|
{
|
|
"type": "event",
|
|
"event": "playback_state_changed",
|
|
"data": {
|
|
"old_state": "paused",
|
|
"new_state": "playing"
|
|
}
|
|
}
|
|
#+end_src
|
|
|
|
#+begin_src json
|
|
{
|
|
"type": "event",
|
|
"event": "playback_state_changed",
|
|
"data": {
|
|
"old_state": "stopped",
|
|
"new_state": "playing"
|
|
}
|
|
}
|
|
#+end_src
|
|
**** Podcast playback started
|
|
#+begin_src json
|
|
{
|
|
"type": "event",
|
|
"event": "track_playback_started",
|
|
"data": {
|
|
"tl_track": {
|
|
"__model__": "TlTrack",
|
|
"tlid": 13,
|
|
"track": {
|
|
"__model__": "Track",
|
|
"uri": "file:///var/lib/mopidy/media/podcasts/The%20Prince/2022-09-28-Wolf-warriors.mp3",
|
|
"name": "Wolf warriors",
|
|
"artists": [
|
|
{
|
|
"__model__": "Artist",
|
|
"name": "The Economist"
|
|
}
|
|
],
|
|
"album": {
|
|
"__model__": "Album",
|
|
"name": "The Prince",
|
|
"date": "2022"
|
|
},
|
|
"genre": "Blues",
|
|
"date": "2022",
|
|
"length": 2437778,
|
|
"bitrate": 127988
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#+end_src
|
|
**** Podcast playback paused
|
|
#+begin_src json
|
|
{
|
|
"type": "status",
|
|
"data": {
|
|
"state": "paused",
|
|
"current_track": {
|
|
"__model__": "Track",
|
|
"uri": "file:///var/lib/mopidy/media/podcasts/The%20Prince/2022-09-28-Wolf-warriors.mp3",
|
|
"name": "Wolf warriors",
|
|
"artists": [
|
|
{
|
|
"__model__": "Artist",
|
|
"name": "The Economist"
|
|
}
|
|
],
|
|
"album": {
|
|
"__model__": "Album",
|
|
"name": "The Prince",
|
|
"date": "2022"
|
|
},
|
|
"genre": "Blues",
|
|
"date": "2022",
|
|
"length": 2437778,
|
|
"bitrate": 127988
|
|
},
|
|
"time_position": 2350
|
|
}
|
|
}
|
|
|
|
#+end_src
|
|
**** Track playback started
|
|
#+begin_src json
|
|
{
|
|
"type": "event",
|
|
"event": "track_playback_started",
|
|
"data": {
|
|
"tl_track": {
|
|
"__model__": "TlTrack",
|
|
"tlid": 14,
|
|
"track": {
|
|
"__model__": "Track",
|
|
"uri": "local:track:Various%20Artists%20-%202008%20-%20Twilight%20OST/01-muse-supermassive_black_hole.mp3",
|
|
"name": "Supermassive Black Hole",
|
|
"artists": [
|
|
{
|
|
"__model__": "Artist",
|
|
"uri": "local:artist:md5:250dd6551b66a58a6b4897aa697f200c",
|
|
"name": "Muse",
|
|
"musicbrainz_id": "9c9f1380-2516-4fc9-a3e6-f9f61941d090"
|
|
}
|
|
],
|
|
"album": {
|
|
"__model__": "Album",
|
|
"uri": "local:album:md5:455343d54cdd89cb5a3b5ad537ea99d0",
|
|
"name": "Twilight: Original Motion Picture Soundtrack",
|
|
"artists": [
|
|
{
|
|
"__model__": "Artist",
|
|
"uri": "local:artist:md5:54e4db2d5624f80b0cc290346e696756",
|
|
"name": "Various Artists",
|
|
"musicbrainz_id": "89ad4ac3-39f7-470e-963a-56509c546377"
|
|
}
|
|
],
|
|
"num_tracks": 12,
|
|
"num_discs": 1,
|
|
"date": "2008-11-04",
|
|
"musicbrainz_id": "b4889eaf-d9f4-434c-a68d-69227b12b6a4"
|
|
},
|
|
"composers": [
|
|
{
|
|
"__model__": "Artist",
|
|
"uri": "local:artist:md5:4d49cbca0b347e0a89047bb019d2779d",
|
|
"name": "Matt Bellamy"
|
|
}
|
|
],
|
|
"genre": "Rock",
|
|
"track_no": 1,
|
|
"disc_no": 1,
|
|
"date": "2008-11-04",
|
|
"length": 211121,
|
|
"musicbrainz_id": "ff1e3e1a-f6e8-4692-b426-355880383bb6",
|
|
"last_modified": 1672712949510
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#+end_src
|
|
**** Track playback in progress
|
|
#+begin_src json
|
|
{
|
|
"type": "status",
|
|
"data": {
|
|
"state": "playing",
|
|
"current_track": {
|
|
"__model__": "Track",
|
|
"uri": "local:track:Various%20Artists%20-%202008%20-%20Twilight%20OST/01-muse-supermassive_black_hole.mp3",
|
|
"name": "Supermassive Black Hole",
|
|
"artists": [
|
|
{
|
|
"__model__": "Artist",
|
|
"uri": "local:artist:md5:250dd6551b66a58a6b4897aa697f200c",
|
|
"name": "Muse",
|
|
"musicbrainz_id": "9c9f1380-2516-4fc9-a3e6-f9f61941d090"
|
|
}
|
|
],
|
|
"album": {
|
|
"__model__": "Album",
|
|
"uri": "local:album:md5:455343d54cdd89cb5a3b5ad537ea99d0",
|
|
"name": "Twilight: Original Motion Picture Soundtrack",
|
|
"artists": [
|
|
{
|
|
"__model__": "Artist",
|
|
"uri": "local:artist:md5:54e4db2d5624f80b0cc290346e696756",
|
|
"name": "Various Artists",
|
|
"musicbrainz_id": "89ad4ac3-39f7-470e-963a-56509c546377"
|
|
}
|
|
],
|
|
"num_tracks": 12,
|
|
"num_discs": 1,
|
|
"date": "2008-11-04",
|
|
"musicbrainz_id": "b4889eaf-d9f4-434c-a68d-69227b12b6a4"
|
|
},
|
|
"composers": [
|
|
{
|
|
"__model__": "Artist",
|
|
"uri": "local:artist:md5:4d49cbca0b347e0a89047bb019d2779d",
|
|
"name": "Matt Bellamy"
|
|
}
|
|
],
|
|
"genre": "Rock",
|
|
"track_no": 1,
|
|
"disc_no": 1,
|
|
"date": "2008-11-04",
|
|
"length": 211121,
|
|
"musicbrainz_id": "ff1e3e1a-f6e8-4692-b426-355880383bb6",
|
|
"last_modified": 1672712949510
|
|
},
|
|
"time_position": 17031
|
|
}
|
|
}
|
|
#+end_src
|
|
**** Track event playback paused
|
|
#+begin_src json
|
|
{
|
|
"type": "event",
|
|
"event": "track_playback_paused",
|
|
"data": {
|
|
"tl_track": {
|
|
"__model__": "TlTrack",
|
|
"tlid": 14,
|
|
"track": {
|
|
"__model__": "Track",
|
|
"uri": "local:track:Various%20Artists%20-%202008%20-%20Twilight%20OST/01-muse-supermassive_black_hole.mp3",
|
|
"name": "Supermassive Black Hole",
|
|
"artists": [
|
|
{
|
|
"__model__": "Artist",
|
|
"uri": "local:artist:md5:250dd6551b66a58a6b4897aa697f200c",
|
|
"name": "Muse",
|
|
"musicbrainz_id": "9c9f1380-2516-4fc9-a3e6-f9f61941d090"
|
|
}
|
|
],
|
|
"album": {
|
|
"__model__": "Album",
|
|
"uri": "local:album:md5:455343d54cdd89cb5a3b5ad537ea99d0",
|
|
"name": "Twilight: Original Motion Picture Soundtrack",
|
|
"artists": [
|
|
{
|
|
"__model__": "Artist",
|
|
"uri": "local:artist:md5:54e4db2d5624f80b0cc290346e696756",
|
|
"name": "Various Artists",
|
|
"musicbrainz_id": "89ad4ac3-39f7-470e-963a-56509c546377"
|
|
}
|
|
],
|
|
"num_tracks": 12,
|
|
"num_discs": 1,
|
|
"date": "2008-11-04",
|
|
"musicbrainz_id": "b4889eaf-d9f4-434c-a68d-69227b12b6a4"
|
|
},
|
|
"composers": [
|
|
{
|
|
"__model__": "Artist",
|
|
"uri": "local:artist:md5:4d49cbca0b347e0a89047bb019d2779d",
|
|
"name": "Matt Bellamy"
|
|
}
|
|
],
|
|
"genre": "Rock",
|
|
"track_no": 1,
|
|
"disc_no": 1,
|
|
"date": "2008-11-04",
|
|
"length": 211121,
|
|
"musicbrainz_id": "ff1e3e1a-f6e8-4692-b426-355880383bb6",
|
|
"last_modified": 1672712949510
|
|
}
|
|
},
|
|
"time_position": 67578
|
|
}
|
|
}
|
|
#+end_src
|
|
** TODO [#C] Fix exporting so it works reliably :exporting:feature:
|
|
|
|
*** Description
|
|
|
|
The existing export function is very naive. It runs in the web process, takes
|
|
too long and just dumps tracks. We should make it more robust by creating one
|
|
CSV file per scrobble media type and writing them into a zip file that gets
|
|
placed in the media directory:
|
|
|
|
`/media/exports/user_<user_id>/<timestamp>-export.zip`
|
|
|
|
And this should all be done in a celery task that is just kicked off by the
|
|
"Export" button on the frontend
|
|
|
|
** TODO [#C] Implement loguru into project :feature:loguru:logging:
|
|
:PROPERTIES:
|
|
:ID: efcd0c0a-db81-4518-9c23-5505d59e8ef5
|
|
:END:
|
|
|
|
*** Description
|
|
|
|
Would be great to formalize how we log so we can search for errors and such more
|
|
easily. And our exposure to PII is really low at this point in the project,
|
|
so we can probably use backtrace=True and diagnose=True to help us root cause
|
|
bugs faster.
|
|
|
|
** TODO [#C] Make podcast date format configurable in settings :podcasts:configuration:
|
|
:PROPERTIES:
|
|
:ID: b01a94f8-328f-41ed-a62e-8b99c755b82d
|
|
:END:
|
|
|
|
*** Description
|
|
|
|
=PODCAST_DATE_FORMAT= is hardcoded to ="YYYY-MM-DD"=. Should be in Django settings or environment variables for deploy-specific configuration.
|
|
|
|
File: ~vrobbler/apps/podcasts/utils.py~ (line 13)
|
|
|
|
** TODO [#C] Extract zombie scrobble query into custom manager :refactoring:manager:
|
|
:PROPERTIES:
|
|
:ID: 79c874e1-ca6f-4bce-9259-e3eebdca8a41
|
|
:END:
|
|
|
|
*** Description
|
|
|
|
The zombie scrobble cleanup query lives in a utility function. Should be a
|
|
custom model manager method (e.g. =Scrobble.objects.zombies()=).
|
|
|
|
File: ~vrobbler/apps/scrobbles/utils.py~ (line 204)
|
|
|
|
** TODO [#C] Allow profile to set start of week :profiles:configuration:
|
|
:PROPERTIES:
|
|
:ID: 0449279a-9550-430e-be0c-816df7273080
|
|
:END:
|
|
|
|
*** Description
|
|
=start_of_week()= and =end_of_week()= use Monday as default. Should be a user
|
|
profile setting for different cultural week start conventions.
|
|
|
|
File: ~vrobbler/apps/profiles/utils.py~ (lines 39, 44)
|
|
|
|
** TODO [#C] Add constants for data dictionary keys (multiple files) :refactoring:constants:
|
|
:PROPERTIES:
|
|
:ID: d4415f9b-620a-4be7-925d-fa71c02ba1d1
|
|
:END:
|
|
|
|
*** Description
|
|
Multiple files use magic string literals for dict keys. Should be extracted to
|
|
named constants for maintainability.
|
|
|
|
- Files:
|
|
- ~vrobbler/apps/locations/models.py~ (line 63) -- ="lat"=, ="lon"= etc.
|
|
- ~vrobbler/apps/webpages/models.py~ (line 290) -- ="url"=
|
|
- ~vrobbler/apps/scrobbles/importers/tsv.py~ (line 55) -- ="S"= completion status
|
|
|
|
** TODO [#C] Add track number and rating to CSV export :export:metadata:
|
|
|
|
*** Description
|
|
|
|
Track number and rating are missing in the CSV export of scrobbles.
|
|
|
|
*** Implementation
|
|
Also need to generally clean up how exporting works.
|
|
|
|
- File: ~vrobbler/apps/scrobbles/export.py~ (lines 51-52)
|
|
- Track number is hardcoded to =0= and rating is hardcoded to ="S"=.
|
|
The data model may need extension to support these fields.
|
|
|
|
** TODO [#C] Simplify Track creation API in scrobblers :refactoring:api:
|
|
- File: ~vrobbler/apps/scrobbles/scrobblers.py~ (line 79)
|
|
- =Track.find_or_create()= requires individual kwargs extracted from =post_data=.
|
|
Should accept a dict or have a cleaner unified interface.
|
|
|
|
** TODO [#C] Enrich books/authors after creation (recurring pattern) :books:enrichment:
|
|
|
|
*** Description
|
|
|
|
Generally move enrichment of media to follow-up tasks, so long as we have media length and
|
|
title from the input source.
|
|
|
|
*** Implementation
|
|
- Files:
|
|
- ~vrobbler/apps/books/models.py~ (lines 239, 320, 548)
|
|
- ~vrobbler/apps/books/koreader.py~ (lines 79, 119)
|
|
- Multiple places where Authors and Books are created without follow-up enrichment.
|
|
Should fire async enrichment tasks (Celery) after creation.
|
|
|
|
** TODO [#C] Enrich TMDB resultw with TMDB URL :videos:enrichment:
|
|
- File: ~vrobbler/apps/videos/sources/tmdb.py~ (lines 46, 56)
|
|
- Cover URLs are constructed but the actual TMDB page URL for the movie/show
|
|
is not stored. Minor enrichment.
|
|
|
|
** TODO [#C] Enrich bricksets from website on creation :bricksets:enrichment:
|
|
*** Implementation
|
|
- File: ~vrobbler/apps/bricksets/models.py~ (line 75)
|
|
- =BrickSet.find_or_create()= only stores the set number. Should fetch additional
|
|
data (name, piece count, image) from brickset.com or similar.
|
|
|
|
** TODO [#B] Make WebDAV paths configurable per-user :webdav:settings:
|
|
*** Description
|
|
|
|
This may be a no-op now. A lot of webdav code has changed.
|
|
|
|
*** Implementation
|
|
|
|
- File: ~vrobbler/apps/scrobbles/importers/webdav.py~ (lines 125, 410)
|
|
- Both the GPX path and scale path are hardcoded via =DEFAULT_GPX_PATH= and
|
|
=DEFAULT_SCALE_PATH=. Should be configurable in user profile settings.
|
|
** TODO [#B] Add auto-population of serial scrobble IDs for bricksets :bricksets:serials:
|
|
:PROPERTIES:
|
|
:ID: 69dcb387-c7f0-40d0-895f-60d79c664b10
|
|
:END:
|
|
|
|
*** Implementation
|
|
|
|
When scrobbling bricksets (LEGO), should check for past scrobbles and auto-populate a serial scrobble ID for tracking multiple builds of the same set.
|
|
|
|
File: ~vrobbler/apps/scrobbles/scrobblers.py~ (line 1245)
|
|
** TODO [#B] Raise exception on WebDAV fetch failure :webdav:error-handling:
|
|
*** Implementation
|
|
|
|
- File: ~vrobbler/apps/books/koreader.py~ (line 418)
|
|
- =fetch_file_from_webdav()= returns an empty string on failure, which could
|
|
silently propagate. Should raise an exception for proper error handling.
|
|
|
|
** TODO [#B] Make book matching use Google Books ID or author name :books:matching:
|
|
*** Description
|
|
|
|
This may be a no-op situation as we've moved away from Google Books. But maybe we haven't.
|
|
|
|
*** Implementation
|
|
|
|
- File: ~vrobbler/apps/books/models.py~ (line 270)
|
|
- =Book.find_or_create()= only matches by =original_title=, which is fragile.
|
|
Should also match by Google Books ID or author name (like Track does).
|
|
|
|
** TODO [#B] Add AllTrails as a source for Trail data :trails:feature:
|
|
:PROPERTIES:
|
|
:ID: 39313362-cdfe-46e7-bbd4-9139a65c0b3c
|
|
:END:
|
|
|
|
*** Description
|
|
Pretty clear, I would love to make trails more useful. Historically I wasn't
|
|
hiking a lot, which made the source for this a bit silly. But it's clear that
|
|
AllTrails is the best source, though having TrailForks is nice to.
|
|
|
|
** TODO [#B] Add `garmin_activity_id` to the TrailLogData class :trails:feature:
|
|
:PROPERTIES:
|
|
:ID: 5a4fb0f8-0555-40ec-b06f-93c26bd686f4
|
|
:END:
|
|
*** Description
|
|
|
|
Would be nice to have some loose connection to the actual event in my Garmin
|
|
profile.
|
|
|
|
** TODO [#B] Make IMAP and WebDAV configurable :webdav:feature:imap:importers:
|
|
:PROPERTIES:
|
|
:ID: b1426d92-2feb-4d15-9738-d5b7b0594f96
|
|
:END:
|
|
|
|
*** Description
|
|
|
|
Currently we have webdav able to import post types of file-based incoming data,
|
|
usually in the form of CSVs but also gpx files, bgstats json files, and
|
|
audioscrobbler TSV files.
|
|
|
|
What if the user could specify via their profile (settings) which imports they
|
|
wanted to use IMAP for and which ones they wanted to use WebDAV for.
|
|
|
|
Then we'd have two celery tasks that would be kicked off periodically via
|
|
celerybeat, one for IMAP imports every 12 minutes and one for WebDAV every 3
|
|
minutes. Both would be responsible for checking if a user has an configured
|
|
imports of their type, check if an import needs to run, and dispatch the
|
|
needed import celery task. This is how the WebDAV celery task currently works.
|
|
|
|
This would also be an opporunity to clean up the code around WebDAV imports
|
|
and make them more re-usable for other import services.
|
|
|
|
** TODO [#B] Allow browing a user's favorited media :favorites:feature:
|
|
:PROPERTIES:
|
|
:ID: 5c2cf004-d01f-4576-9bbb-974235e7408a
|
|
:END:
|
|
|
|
*** Description
|
|
|
|
We should have a global view `/favorites/` that shows the logged in users's
|
|
favorited media objects.
|
|
|
|
** TODO [#B] Find page numbers for comic books from ComicVine :feature:books:
|
|
:PROPERTIES:
|
|
:ID: 79f867c3-1288-4143-b6bf-2a452983ee9f
|
|
:END:
|
|
** TODO [#B] Is there way to create unique slugs for media instances :media_types:
|
|
** TODO [#B] Add async media enrichment after scrobbling (recurring pattern) :async:enrichment:
|
|
- Files: ~vrobbler/apps/scrobbles/scrobblers.py~ (lines 1179, 1211, 1244)
|
|
- Three separate TODOs for beer, puzzle, and brickset scrobbles all request
|
|
firing an async process to enrich media after creation. This is the same
|
|
pattern repeated across media types -- should be solved generically.
|
|
|
|
** TODO [#B] Auto sync board game scrobbles to BGG :boardgames:
|
|
|
|
*** Description
|
|
|
|
** TODO [#B] Scrape ComicBookRoundUp ratings for comic book metadata :books:feature:comicbook:
|
|
:PROPERTIES:
|
|
:ID: b3cc57ca-3d2c-468d-ab7c-c47f1120309b
|
|
:END:
|
|
|
|
*** Description
|
|
As an example https://comicbookroundup.com/comic-books/reviews/humanoids-publishing/the-history-of-science-fiction
|
|
|
|
** TODO [#B] Update how board game scrobbles work :boardgames:
|
|
|
|
*** Description
|
|
|
|
When we scrobble a board game from a BGG URL, instead of going to the media
|
|
detail page, we should go to the scrobble detail page, with the Edit Log form
|
|
expanded by default.
|
|
|
|
The Edit log form should have from top to bottom:
|
|
|
|
- Board/Variant (one or many BoardGameVariant in a multi-select widget)
|
|
- People (which should be similar to the Bird widget on BirdLocation and allow setting per user score, win true/false, rank, new true/false, seat_ordrer)
|
|
- Expansion ids (which should a multi-select widget of expansions for this game)
|
|
- Location (which should be a drop down of BoardGameLocations for this user)
|
|
** TODO [#B] Remove vestigial "Grid view" from all templates :templates:ui:
|
|
** TODO [#B] Fix links on book page to sources :ui:templates:
|
|
:PROPERTIES:
|
|
:ID: d54e6350-6200-4c04-95c9-4ff82448307a
|
|
:END:
|
|
|
|
*** Description
|
|
|
|
Currently the images on the book source page go nowhere. We should add links to Amazon and Goodreads, and maybe Bookshop.org if we can find a way to generate that link easily.
|
|
|
|
|