#+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.

* Features
** Beer
*** Triggers
**** Bookmarklet
**** Manual
*** Metadata sources
**** Untappd
** Book
*** Triggers
**** Webdav via KoReader
**** Manual
*** Metadata sources
**** Google Books
This is the preferred method at this time. Also, the Book model implements a
`find_or_create` classmethod which is an example of an interface we can use for
other data models to get metadata in a way that provides easy testing, bulk
fetching and simple saving.
**** OpenLibrary
**** ComicVine
** Board Game
*** Triggers
**** IMAP import
**** Bookmarklet
**** Manual
** Location
*** Triggers
**** GPSLogger (Android)
*** Metadata sources
**** User input
** Music
*** Triggers
**** Last.FM
**** Rockbox files
**** Mopidy
**** Jellyfin
*** Metadata sources
**** Musicbrainz
** Podcast
*** Triggers
**** Mopidy
*** Metadata sources
**** Google Podcasts
**** PodcastIndex
** Sport
*** Triggers
**** Bookmarklet
**** Manual
*** Metadata sources
**** Thes Sports DB
** Task
*** Triggers
**** Todoist
**** Org-mode
*** Metadata sources
**** User profile
** Trails
** Video
*** Triggers
**** Jellyfin
**** Bookmarklet
**** Manual
*** Metadata sources
**** IMDB
**** Youtube
** Web Page
*** Triggers
**** Bookmarklet
*** Metadata sources
**** Scraper
* Chores
** DONE Document various vrobbler features :chore:personal:project:vrobbler:documentation:
:PROPERTIES:
:ID:       514e9285-96f1-265f-56df-118c12f60918
:END:
:LOGBOOK:
CLOCK: [2025-07-09 Wed 09:55]--[2025-07-09 Wed 10:15] =>  0:20
:END:
* Backlog [2/15] :vrobbler:project:personal:
** TODO [#C] Add sentiment parsing for Scrobbles with notes :vrobbler:project:scrobbles:sentiment:
:PROPERTIES:
:ID:       37781d6a-f3b0-48b2-bf98-33c2c791cf85
:END:
** TODO [#C] Create small utility to clean up tracks scrobbled with wonky playback times :vrobbler:personal: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] User should be able to enable auto trail tracking via  amail reader with Garmin LiveTrack URLs :vrobbler:trails:project:feature:personal:
:PROPERTIES:
:ID:       133bcf71-078f-4efa-a029-1eae4b4d146d
:END:
** TODO [#C] Fix exporting so it works reliably :exporting:project:feature:

*** Description

The existing export function is very naieve. 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 [#B] Add importer class for IMAP imports :vrobbler:feature:imap:importers:project:personal:
:PROPERTIES:
:ID:       b1426d92-2feb-4d15-9738-d5b7b0594f96
:END:
** TODO [#B] Add AllTrails as a source for Trail data :vrobbler:trails:feature:personal:project:
: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 TrailMetadataLog class :vrobbler:trails:feature:personal:project:
: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] Fix how we show notes and descriptions from scrobbles to users :metadata:notes:tasks:
:PROPERTIES:
:ID:       adf4c513-a417-4ec9-8831-f01ffcf63276
:END:
*** Description

Currently the display of notes leaves something to be desired. The biggest issue
is that they don't look good on mobile and are probably trying to be too cute.
Rather than post-it note style, we should just put notes in a list under the
description, above the Edit Log toggle, with timestamps for when they were
added.

They should also probably support markdown formatting and that should be
displayed in the template.

** TODO [#B] Add CSV endpoint for book scrobbles that LibraryThing can ingest :personal:project:books:feature:export:
https://app.todoist.com/app/task/add-a-csv-endpoint-for-users-book-reads-that-library-thing-can-ingest-6X7QPMRp265xMXqg#comment-6X7QrXq6gJjMP4hg
** TODO [#B] Scrape ComicBookRoundUp ratings for comic book metadata :vrobbler:books:feature:comicbook:personal:project:

- Note taken on [2025-09-25 Thu 10:51]

  As an example https://comicbookroundup.com/comic-books/reviews/humanoids-publishing/the-history-of-science-fiction
** TODO [#B] Find page numbers for comic books from ComicVine :vrobbler:feature:books:personal:project:
:PROPERTIES:
:ID:       79f867c3-1288-4143-b6bf-2a452983ee9f
:END:
** TODO [#B] Fix koreader scrobble imports to use DST properly :vrobbler:personal:bug:books:imports:
:PROPERTIES:
:ID:       79758cba-a440-48b6-a637-efb88827acf2
:END:

- Note taken on [2025-09-25 Thu 10:37] \\

  This may already be fixed ... need to check.

- Note taken on [2025-02-25 12:34] \\

  The page data has the canonical date something was read in it, but it seems
  to be an hour off. I traced this back to being off during DST, so we just need
  the importer to be aware of whether a user is using DST or not and roll back
  an hour for part of the year. Also, we'd need to adjust any old scrobbles that
  took place with DST off to roll them back by an hour.
      
  
*** Description

This is a long-standing problem when daylight saving time takes effect. Time is
manually set on a KoReader device (or at least, always saved in local time). So
whatever time KoReader reports, we need to know, given the date and the user
profile's historic timezone, how many hours to adjust the KoReader time to get
to GMT to save it in the database.

** DONE [#B] Releases do not pin commit to the repo for display :bug:tooling:releases:
:PROPERTIES:
:ID:       2a9f2ff5-2642-47ab-ba1d-e41825411713
:END:

*** Description

Somewhere in implementing the justfile release flow, we lost the capture of the
latest commit in the relesae flow so the footer now always says: vXX.x (unknown)

It should have the first bit of the commit in the parens at the end.

** DONE [#B] Fix the way timestamps are stored for notes on tasks :bug:scrobbles:tasks:
:PROPERTIES:
:ID:       32973bb3-079b-8cdf-6495-82f8ae907299
:END:

*** Description
:PROPERTIES:
:ID:       d833a3df-6eb2-36bb-1863-e438e5d36151
:END:

Turns out Todoist uses a human-readable timestamp format for comments. We should
adapt that for use from org-mode as well.

Format should be: "%Y-%m-%dT%H:%M:%S.%fZ"
  
* Version 39.1 [1/1]
** DONE [#A] Fix bug in tests for notes saving :bug:scrobbles:forms:
:PROPERTIES:
:ID:       68a011b2-bb6f-3ba8-2312-5947c41db9ac
:END:

* Version 39.0 [3/3]
** DONE [#B] Clean up org-mode tasks metadata :bug:tasks:metadata:
:PROPERTIES:
:ID:       0c762d09-fc69-4e75-be40-7eaaf04f178e
:END:

*** Description

Org-mode tasks have a `Description` subheader, which should populate the
"Description" of a task.

The title should come from the actual TODO content, with tags coming from the
tags in colons after the task. This behaviour should already work.

Next, any comments should go under a `Comments` subheader. Under these should be a list tag like:

"Note taken on [2026-05-31 Sun 20:03]"

Which declares it's a note with a timestamp like "YYYY-MM-DD d HH:MM".

When scrobbling a task from org-mode, the description should always be copied to
the scrobble's log["description"].

Any comments that exist on the task when the scrobble is first created, should
be ignored.

Any comments on a task that is updated (a re-POST'd while the task is in
progress, should add a comment in the log["notes"], a dictionary, keyed off the
timestamp the note string. If a note for that datetime already exists, the
content should be replaced with the new comment.

Additionally, when a task is re-POST'd while the task is in progress, the
description should also be compared, and if different, the newest description
should be used.

Note, the biggest change for this flow is making sure comments that already
exist are not added to any new tasks and that both comments and descriptions are
added or updated if the new values are different than what is already in the
currently in-progress task.

If the task is completed, don't touch it.

*** Comments
- Note taken on [2026-05-31 Sun 20:03]
  
** DONE [#A] Actually push branches up and add a just command to do it :release:justfile:tooling:
:PROPERTIES:
:ID:       50aa5daa-a802-6aa9-38a3-218b7a9d4b34
:END:
** DONE [#A] Try to fix deploy failing with bad release plan :release:tooling:pyproject:
:PROPERTIES:
:ID:       63dc633c-4382-e6a5-e663-b01871ce86ce
:END:

* Version 38.0 [38/38]
** DONE [#A] Fix release flow to be easier to trigger :pyproject:release:tooling:
:PROPERTIES:
:ID:       e321f8a3-26ea-b316-8493-294b50c0b516
:END:

*** Description

We should have a command like `just relesae <major|minor>` that can cut new
releases on command by updating the PROJECT.org file with a release header for
all DONE tasks and creating the appropriate release commits and tags based on
either the new release version type.

It should also update the pyproject file.

** DONE [#A] Move imported retroarch lrtl files to processed/ directory on WebDAV :webdav:retroarch:importers:
:PROPERTIES:
:ID:       881b5af6-e96f-4e71-9e66-2d30824e5504
:END:

    - File: ~vrobbler/apps/scrobbles/importers/webdav.py~ (line 439)
    - Same pattern as the GPX importer: after importing a =.csv= file from
      WebDAV, move it to =var/retroarch/processed/= with a timestamp appended.

** DONE [#A] Add listenbrainz support for similar tracks :feature:music:metadata:
:PROPERTIES:
:ID:       a3abba14-7603-db51-58e1-1941a47fa35e
:END:
** DONE [#B] Consolidate albums in the same musicbrainz_releasegroup_id :music:albums:metadata:
:PROPERTIES:
:ID:       6b331bdb-1516-bb86-7cb3-8cb9e3de59ce
:END:

*** Description

When we look up albums, we should check if one already exists with the same
musicbrainz_releasegroup_id and prefer that one, rather than creating a new
album.

Also, we should create a data migration to clean up albums with matching
musicbrainz_releasegroup_id fields by consolidating tracks around the first
album found.

Whether the track was enriched by musicbrainz or not, the track should get
tagged with `musicbrainz_enriched` or `musicbrainz_notfound`

** DONE [#A] Clean up metadata on music tracks :music:tracks:metadata:musicbrainz:
:PROPERTIES:
:ID:       3a007bd7-b901-43cb-892e-102d39fffc85
:END:

*** Description 

There's a over 3K tracks without a musicbrainz_id and almost 30K without a
base_run_time_seconds. We should have a clean up script that can run through the
ones missing musicbrainz_ids and see about getting metadata from musicbrainz and
for the 30K without base_run_time_seconds, check with their musicbrainz_ids to
see if we can look up the track length, and if not, tag the track with
"missing-metadata" tag.

Also, if the tracks without musicbrainz_ids actually are not in the MB database,
we should tag those with "not-in-musicbrainz"

And a big part of this work will probably involve checking for "Various Artists"
tracks where the track erroneously got set with a generic artist. In those
cases, we should try just looking up by track title.

** DONE [#B] Make artists_m2m field source of artist truth for albums :music:bug:albums:
:PROPERTIES:
:ID:       05a24455-0e71-45ef-ac6e-c1b7b843047d
:END:

*** Description

Albums have an FK for album_artist, but like artists, the M2M should be the
source of truth. We should migrate all uses of album_artist to an `artist`
property on the Album model and use a data migration to populate artists with
the album_artist value.

** DONE [#A] Fix various artist album problem with Superwolves (track with multiple artists) :vrobbler:project:music:bug:artists:
:PROPERTIES:
:ID:       590bc038-745f-710b-8272-4d8a3d2efa01
:END:

*** Description
We have an issue with tracks where there are two artists, like `Matt Sweeney &
Bonnie "Prince" Billy`

I think we need to allow creating a single artist with both names, so the Artist
is the full name. That said, the ampersand is usually used to split feature
artsits (I think) so I'm not sure how this would work reliably.

Also, it's possible musicbrainz does not have dual artist listings. So it's
possible the longer term solution is to allow multiple artists per track the way
we now allow tracks to be on multiple albums. We could deprecate the Artist FK
or at least make it optional, and then require a M2M between Track and Artist.

Then this one would be a Track by both `Matt Sweeney` and `Bonnie "Prince"
Billy`

** DONE [#A] Move imported eBird CSV files to processed/ directory on WebDAV :webdav:ebird:importers:
:PROPERTIES:
:ID:       445e1253-d353-4b55-b1d8-39d0a0dcdd34
:END:

    - File: ~vrobbler/apps/scrobbles/importers/webdav.py~ (line 439)
    - Same pattern as the GPX importer: after importing a =.csv= file from
      WebDAV, move it to =var/ebird/processed/= with a timestamp appended.

** DONE [#A] Move imported Board Game CSV files to processed/ directory on WebDAV :webdav:boardgames:importers:
:PROPERTIES:
:ID:       a3f8d30c-d2c3-4ee7-b062-f0f16bd9b0b4
:END:

    - File: ~vrobbler/apps/scrobbles/importers/webdav.py~ (line 496)
    - Same pattern as the GPX importer: after importing a =.csv= file from
      WebDAV, move it to =var/bgstats/processed/= with a timestamp appended.
      
** DONE [#A] Move imported Scale CSV files to processed/ directory on WebDAV :webdav:scale:importers:
:PROPERTIES:
:ID:       1a0de363-d1ea-466e-9966-e24941a6180b
:END:
    - File: ~vrobbler/apps/scrobbles/importers/webdav.py~ (line 496)
    - Same pattern as the GPX importer: after importing a =.csv= file from
      WebDAV, move it to =var/scale/processed/= with a timestamp appended.

** DONE [#A] Allow special parameter to re-import already processed GPX files :imports:gpx:
:PROPERTIES:
:ID:       166c0809-c11a-4d02-8071-7a69dcb36e64
:END:

*** Description

Now that we stash imported GPX files in the processed/ subdirectory on import,
it would be nice to have a flag like --include-processed on the webdav importer
that would import files both in the root of gpx/ and also in the processed
directory. This would aide testing imports in staging quickly without constantly
moving files back and forth.

** DONE [#A] Move imported GPX files to processed/ directory on WebDAV :webdav:gpx:importers:
:PROPERTIES:
:ID:       db2b02fc-817c-4c39-bd51-13f9b77c7888
:END:
    - File: ~vrobbler/apps/scrobbles/importers/webdav.py~ (line 198)
    - After importing a GPX/FIT file from WebDAV, move it to a =processed/=
      subdirectory with a timestamp appended. This eliminates the DB lookup for
      already-imported filenames — any file present in the top-level directory
      is new. Also makes manual re-imports easy (just move a file back).

** DONE [#A] Add CSS Grid calendar view for scrobbles :vrobbler:personal:project:templates:feature:
:PROPERTIES:
:ID:       be915acf-d803-466a-8770-823819ebf2a9
:END:

*** Description

Calendar view at /scrobbles/calendar/ showing select media types (Tasks,
Birding, Food, Trails, VideoGames, Books) in a CSS Grid month layout.

- Emoji badges per scrobble on each day cell, hover reveals title
- Click emoji to navigate to scrobble detail
- Prev/Next month navigation
- Based on the CodePen CSS Grid calendar pattern:
  https://codepen.io/oliviale/pen/QYqybo

** DONE [#C] Come up with a possible flow using WebDAV and super-productivity for tasks :personal:feature:project:vrobbler:tasks:
:PROPERTIES:
:ID:       dd751edf-6192-2ed0-cd85-905f423f82b0
:END:
** DONE [#B] Fix PuzzleLogData has no attribute form :vrobbler:puzzles:personal:project:logdata:
:PROPERTIES:
:ID:       cb8e697a-9fc2-c463-5d4d-fa15d3e833a0
:END:
** DONE [#B] Add PuzzleLogData class with with_people and completed :vrobbler:feature:puzzles:logdata:personal:project:
:PROPERTIES:
:ID:       1b38c8a8-86d3-b73d-c584-dfb0497970dc
:END:
** DONE Add weather lookup to the mood check-in flow :vrobbler:project:moods:feature:checkin:
:PROPERTIES:
:ID:       175678e2-bfe1-0833-f2a9-f03b9323db78
:END:
<2026-05-20 Wed>
** DONE Add importing of openScale CSV files to Tasks :vrobbler:project:personal:tasks:openscale:
:PROPERTIES:
:ID:       344d1f5d-dda6-a1fb-da53-e00eebdd2262
:END:
** DONE Add ability to track Birding sessions via BirdingLocation scrobbles :vrobbler:project:birds:feature:
:PROPERTIES:
:ID:       8200ce29-a691-5cf6-c11a-c77e3d8b64c6
:END:
** DONE List only the last 20 scrobbles per category on the home page :vrobbler:project:scrobbles:templates:
:PROPERTIES:
:ID:       b56402b9-5542-a10c-6c13-e12f56f2a2d1
:END:
** DONE Fix display of notes so they look like stickies :vrobbler:project:personal:notes:scrobbles:
:PROPERTIES:
:ID:       0ace8814-e96e-fa54-28d1-c57dcb508f1e
:END:
** DONE Add searching to scrobbles :vrobbler:project:personal:search:scrobbles: 
:PROPERTIES:
:ID:       b4690c76-d741-4320-20e8-c003251fe035
:END:
** DONE Fix uniqueness of imdb_id messing up youtube videos :vrobbler:project:bug:videos:
:PROPERTIES:
:ID:       610b8c4c-abf1-8630-a4fd-08d9939da9f5
:END:

Turns out you can't make imdb_id unique by itself or you never get to create
youtube videos. Rather, we should make imdb_id and youtube_id unique together so
imdb_id can be empty for every youtube_id and vice versa.

** DONE Fix genearting chart records :vrobbler:bug:personal:project:chartrecords:
:PROPERTIES:
:ID:       e749d762-44ac-dd7e-017e-2423016737ff
:END:

** DONE [#A] Save raw scrobble request data to every scrobble log :vrobbler:personal:feature:scrobbles:
:PROPERTIES:
:ID:       b4686db4-1319-399c-9b84-f0b1036c6815
:END:

The idea here is that no matter where the data comes from, we should just save
it in the scrobble for posterity, so we can always in some form recover the
original intent of the scrobble.

It may also allow us to clean up junk scrobbles if the data was just horribly
wrong.

** DONE [#B] Clean up follow up notifications for if you're still scrobbling :vrobbler:personal:project:beers:boardgames:notifications:feature:
:PROPERTIES:
:ID:       7937545f-2a62-4188-ad49-34f85fad0c25
:END:

- Note taken on [2025-09-30 Tue 09:32]

  I added this feature in a very rough way, but now we should add "Action"
  headers so that we can either Finish or Cancel the associated scrobble:

  https://docs.ntfy.sh/publish/#send-http-request

** DONE [#A] Fix lookup of music tracks from Musicbrainz :vrobbler:bug:tracks:music:
:PROPERTIES:
:ID:       2bc1cc0b-6c4e-bc3d-e7c3-47df364df206
:END:

Turns out we're not looking up music tracks properly, again.

** DONE Check opencode about a way to present stats like movies per month :vrobbler:scrobbles:stats:personal:project:
:PROPERTIES:
:ID:       eed39a0b-3aa7-dc65-9e03-a58fa401c8c6
:END:
** DONE Fix bug in Jellyfin audio track playback :vrobbler:personal:project:bug:music:jellyfin:
:PROPERTIES:
:ID:       002bd234-4507-5313-fb59-2d3baa49b847
:END:

#+begin_src shell
ERROR    django.request:241 log_response Internal Server Error: /webhook/jellyfin/
Traceback (most recent call last):
  File "/usr/local/lib/python3.11/site-packages/django/core/handlers/exception.py", line 55, in inner
    response = get_response(request)
               ^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/django/core/handlers/base.py", line 197, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/django/views/decorators/csrf.py", line 56, in wrapper_view
    return view_func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/django/views/decorators/csrf.py", line 56, in wrapper_view
    return view_func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/django/views/generic/base.py", line 104, in view
    return self.dispatch(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/rest_framework/views.py", line 509, in dispatch
    response = self.handle_exception(exc)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/rest_framework/views.py", line 469, in handle_exception
    self.raise_uncaught_exception(exc)
  File "/usr/local/lib/python3.11/site-packages/rest_framework/views.py", line 480, in raise_uncaught_exception
    raise exc
  File "/usr/local/lib/python3.11/site-packages/rest_framework/views.py", line 506, in dispatch
    response = handler(request, *args, **kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/rest_framework/decorators.py", line 50, in handler
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/vrobbler/apps/scrobbles/views.py", line 494, in jellyfin_webhook
    scrobble = jellyfin_scrobble_media(post_data, request.user.id)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/vrobbler/apps/scrobbles/scrobblers.py", line 177, in jellyfin_scrobble_media
    log["album_id"] = media_obj.album_id
    ~~~^^^^^^^^^^^^
TypeError: 'tuple' object does not support item assignment
#+end_src
** DONE [#B] Auto calc duration if no playback time seconds present :vrobbler:bug:scrobbles:personal:project:
:PROPERTIES:
:ID:       e16228b2-b062-bd00-32e6-b2353e6406e9
:END:
** DONE Fix bug in video find_or_create :vrobbler:personal:project:bug:videos:
:PROPERTIES:
:ID:       10e611a5-fbcf-7473-25f0-c9bad7b79ffd
:END:
The error:
#+begin_src shell
  File "/usr/local/lib/python3.11/site-packages/vrobbler/apps/scrobbles/views.py", line 493, in jellyfin_webhook
    scrobble = jellyfin_scrobble_media(post_data, request.user.id)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/vrobbler/apps/scrobbles/scrobblers.py", line 149, in jellyfin_scrobble_media
    media_obj = Video.find_or_create(imdb_id)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/vrobbler/apps/videos/models.py", line 390, in find_or_create
    return cls.get_from_imdb_id(source_id, overwrite)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/vrobbler/apps/videos/models.py", line 374, in get_from_imdb_id
    video.tv_series = Series.find_or_create(
                      ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/vrobbler/apps/videos/models.py", line 203, in find_or_create
    vdict, _, cover, genres = lookup_video_from_imdb(
                              ^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/vrobbler/apps/videos/sources/imdb.py", line 17, in lookup_video_from_imdb
    imdb_result = imdb.get_title(imdb_id)
                  ^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/cinemagoerng/web.py", line 127, in get_title
    data = _scrape(spec=spec, context=context, headers=headers)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/cinemagoerng/web.py", line 120, in _scrape
    document = fetch(url, headers=request_headers)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/cinemagoerng/web.py", line 44, in fetch
    with urlopen(request) as response:
         ^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/urllib/request.py", line 216, in urlopen
    return opener.open(url, data, timeout)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/urllib/request.py", line 525, in open
    response = meth(req, response)
               ^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/urllib/request.py", line 634, in http_response
    response = self.parent.error(
               ^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/urllib/request.py", line 563, in error
    return self._call_chain(*args)
           ^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/urllib/request.py", line 496, in _call_chain
    result = func(*args)
             ^^^^^^^^^^^
  File "/usr/local/lib/python3.11/urllib/request.py", line 643, in http_error_default
    raise HTTPError(req.full_url, code, msg, hdrs, fp)
urllib.error.HTTPError: HTTP Error 500: Internal Server Error
#+end_src

- Note taken on [2026-03-11 Wed 20:53]

  It turns out that every time a TV show was scrobbled were were hitting IMDB.
  Woof.

** DONE Update admin page to be easier to use :vrobbler:djadmin:project:personal:
:PROPERTIES:
:ID:       0779be1f-1ad7-ddc7-8861-d3c7c833cbe9
:END:
** DONE Fix migrations and update repo :vrobbler:scrobbles:admin:personal:project:
:PROPERTIES:
:ID:       ccbc2ef0-68bf-9ef7-dcaf-febab5ebefe2
:END:
** DONE Add recipe parsing for food lookups :vrobbler:foods:project:feature:personal:
:PROPERTIES:
:ID:       86456c78-247b-fb63-7ae8-d6d17e7666b1
:END:
** DONE [#A] Videos are scrobbling duplicates again :vrobbler:bug:videos:scrobbles:
:PROPERTIES:
:ID:       a46884fe-7ef1-410a-316b-7ac6d7599331
:END:
<2026-03-06 Fri>
** DONE Fix board games not saving BGG id on lookup :vrobbler:bug:boardgames:
:PROPERTIES:
:ID:       506c2965-51d6-6cb9-fc4f-4f0468d2d62f
:END:
** DONE Fix board game lookup with name like Unmatched Game System :vrobbler:bug:boardgames:
:PROPERTIES:
:ID:       57956723-b34a-37ad-fde9-44947cc9bb65
:END:
** DONE [#A] Fix raw text webpage title not truncating to 254 chars :vrobbler:personal:bug:webpages:
:PROPERTIES:
:ID:       13fa0efd-2c3f-dd07-deb2-62882096feff
:END:

- Note taken on [2025-09-30 Tue 09:33]

  This may have already been resolved ... need to just confirm it.

* Version 37.0 [4/4]
** DONE [#A] Tasks from org-mode should properly update notes and leave them out of the body :vrobbler:bug:tasks:
:PROPERTIES:
:ID:       c8410001-dbb7-1536-bd89-9784189e058f
:END:
** DONE [#A] Allow scrobbling from the Food list page's start links :vrobbler:bug:food:scrobbling:personal:project:
:PROPERTIES:
:ID:       00f99f60-ac00-6cde-311d-c31f41a01353
:END:
https://life.lab.unbl.ink/scrobble/e39779c8-62a5-46a6-bdef-fb7662810dc6/start/
** DONE [#B] Food scrobbles should inherit calories from obj if missing :vrobbler:feature:food:personal:project:
:PROPERTIES:
:ID:       3322ff69-4252-db65-36b3-fae56c1b9327
:END:
** DONE [#A] Puzzles (and all longplays) should have a "Completed?" column on their detail page :vrobbler:bug:puzzles:personal:project:
:PROPERTIES:
:ID:       e3e49a9a-67d2-8ad8-1114-6f05effee9b7
:END:
* Version 36.0 [1/1]
** DONE [#A] Refactor how videos are scrobbled :vrobbler:vidoes:feature:personal:project:
:PROPERTIES:
:ID:       6034a11d-5376-994d-9a4b-e1640e258cfa
:END:
* Version 35.0 [3/3]
** DONE [#B] Add youtube link in place of IMDB on video detail page :vrobbler:feature:videos:personal:project:
:PROPERTIES:
:ID:       84064bd6-2258-a4de-f048-b131db9465c9
:END:
** DONE [#B] Add missing API lookups to resolve broken scrobbles endpoint :vrobbler:feature:api:scrobbles:personal:project:
:PROPERTIES:
:ID:       0f668a54-f587-3b17-353e-3a56969d3a82
:END:
** DONE [#A] IMDB lookups are not working :vrobbler:bug:videos:personal:project:
:PROPERTIES:
:ID:       d1ba1ca1-509b-13a9-1307-b2dc94a2eafe
:END:
* Version 34.0 [4/4]
** DONE [#A] Use bgg-api for BoardGameGeek lookups :vrobbler:feature:boardgames:personal:project:
:PROPERTIES:
:ID:       738abb5a-c796-b16b-fe10-6e5639a0e10d
:END:
** DONE [#A] Add classmethod for metadata fetching to tracks :vrobbler:feature:music:personal:project:
:PROPERTIES:
:ID:       bc4b45e5-4c65-13c5-ab7b-1937d3fbf5c2
:END:

- Note taken on [2025-10-29 Wed 21:44]

  Beyond a classmethod (which I think we have now), we need to update the flow of how we look up tracks.

  It's a hot mess right now where Various Artists walks over the actual artist, and we often hit MB when we don't have to.

** DONE [#A] Fix views for TV series where next episode is now None :vrobbler:bug:personal:videos:
:PROPERTIES:
:ID:       d7014ac4-cda6-0802-2cdf-8f66c6389fea
:END:

#+begin_src python
ERROR    django.request:241 log_response Internal Server Error: /series/c24100d1-da45-4abe-86bf-27cfce9b1f89/
Traceback (most recent call last):
  File "/usr/local/lib/python3.11/site-packages/django/core/handlers/exception.py", line 55, in inner
    response = get_response(request)
               ^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/django/core/handlers/base.py", line 197, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/django/views/generic/base.py", line 104, in view
    return self.dispatch(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/django/contrib/auth/mixins.py", line 73, in dispatch
    return super().dispatch(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/django/views/generic/base.py", line 143, in dispatch
    return handler(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/django/views/generic/detail.py", line 109, in get
    context = self.get_context_data(object=self.object)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/vrobbler/apps/videos/views.py", line 33, in get_context_data
    context_data["next_episode_id"] = "tt" + next_episode_id
                                      ~~~~~^~~~~~~~~~~~~~~~~
TypeError: can only concatenate str (not "NoneType") to str
#+end_src

** DONE [#A] Emacs tasks are duplicating rather than updating :vrobbler:bug:tasks:emacs:personal:project:
:PROPERTIES:
:ID:       e93efc25-7ce9-8ef2-662e-0a19dd0b29c9
:END:

- Note taken on [2025-10-29 Wed 16:38]

  Turns out I was misusing `orgmode` for the source of tasks when it shoulda been `Org-mode`

  A good lesson in using constants for things.

* Version 33.0 [3/3]
** DONE [#A] Fix bug where scrobble is_stale only uses seconds not total_seconds :vrobbler:bug:scrobbles:personal:project:
:PROPERTIES:
:ID:       7f6070ac-4f67-011d-ebd5-f3dc47da46ed
:END:
** DONE [#B] Fix duplicatged Read next issue for Comic books :vrobbler:bug:books:personal:project:
:PROPERTIES:
:ID:       97943040-1f03-b0b7-b0aa-123a783e4f7b
:END:
** DONE [#A] Add API authentication to BGG calls :vrobbler:bug:boardgames:personal:project:
:PROPERTIES:
:ID:       4955cc34-0882-50db-92f7-f36a95bf57a4
:END:
<2025-10-28 Tue>
* Version 32.0 [2/2]
** DONE [#B] Save path to reading source on book scrobbles and show it on the detail page :vrobbler:feature:books:personal:project:
:PROPERTIES:
:ID:       f1ef3945-e6e4-66c1-b72e-3cede7a0f84a
:END:
** DONE [#B] Move comic resume URL to next page and check if it exists :vrobbler:feature:books:personal:project:
:PROPERTIES:
:ID:       9fe09567-11a3-7083-53c7-07458a9591d0
:END:
* Version 31.0 [3/3]
** DONE [#A] Stop comic book webpage scrobbles from overwriting old scrobbles :vrobbler:personal:bug:books:scrobbling:
:PROPERTIES:
:ID:       4b2ec068-a281-a88b-c31d-6248d6eb0aa0
:END:
** DONE [#A] Add page calculation to manually scrobbled books :vrobbler:personal:feature:books:scrobbling:
:PROPERTIES:
:ID:       b2e313b3-5c35-57e7-8933-627535baf34b
:END:
** DONE [#A] Fix bug in scrobbling comics where google fails :vrobbler:personal:bug:books:scrobbling:
:PROPERTIES:
:ID:       9a870c05-6d20-0803-d35d-c03fbe1d0ee1
:END:
* Version 30.0 [3/3]
** DONE [#A] Fix readcomicsonline browsing to update pages :vrobbler:books:feature:comicbook:personal:project:scrobbling:
:PROPERTIES:
:ID:       981b215a-6473-5fc7-d4cc-51b3eddec4c3
:END:
** DONE [#B] Redirect webpages back to the original page when starting or stopping :vrobbler:project:webpages:bug:
:PROPERTIES:
:ID:       6183d03a-452b-51d5-cceb-5bfeada947aa
:END:

** DONE [#B] Fix ComicVine as source for comic book metadata :vrobbler:books:feature:comicbook:personal:project:scrobbling:
:PROPERTIES:
:ID:       d22cec3f-117f-f203-33a5-efbefa8a5cee
:END:
* Version 29.0 [1/1]
** DONE HOTFIX podcast lookups, final
* Version 28.0 [1/1]
** DONE HOTFIX podcast lookups
* Version 27.0 [3/3]
** DONE [#A] Fix bug where podcast scrobbling creates duplicate Podcast :project:vrobbler:scrobbling:podcasts:bug:personal:
:PROPERTIES:
:ID:       7377ef6c-5fa7-9e4e-9080-f9810a76118c
:END:

Rather than pick up an existing Podcast using the podcast title in the mopidy
file name, Vrobbler creates a new podcast with no enriched data. Not a big deal
for my use as the volume of podcasts I listen to makes manual fixes easy. But
it's annoying.

** DONE [#A] Allow reading comic books from readcomicsoline.ru :vrobbler:books:feature:comicbook:personal:project:scrobbling:
:PROPERTIES:
:ID:       7c7e9ecc-b675-68c3-764f-ef771ce5d88f
:END:

- Note taken on [2025-09-25 Thu 10:52]

  Things to consider are whether we scrobble the issue on one page, send it to
  archivebox? (yes), and how best to enrich the data

** DONE [#A] Add RSS feed lookups to podcasts :vrobbler:personal:feature:podcasts:
:PROPERTIES:
:ID:       d60645b0-7578-97c1-0278-05bd9de4269c
:END:

- Note taken on [2025-10-14 Tue 10:08]

  Turns out the Podcast plugin for mopidy does a pretty good job of showing the
  latest file without having to scroll the bottom using only Muse to not parse
  the podcast title name. BUT, now we're getting urls like this:

  https://nsf.libsyn.com/rss#77e01251-cb20-4609-b577-d48e985d2e7b

  This is great, because there's more context there, but it has to read out of
  the RSS feed. We should add a check in the podcast util to sniff out the file
  referenced in the # in that url and populate the info from there. This should
  actually be much more reliable than the current state of the podcast lookup
  which depends on the file to be name properly.

* Version 26.0 [3/3]
** DONE Clean up templates for scrobble details :vrobbler:personal:bug:templates:
:PROPERTIES:
:ID:       43dc1e02-c110-5b49-0ac7-4c4f7656d1aa
:END:
** DONE Add named locations visited to dashboard :vrobbler:personal:feature:locations:templates:
:PROPERTIES:
:ID:       ebc365a1-cef4-f75d-569f-c24b072ef5a4
:END:
** DONE Add moods to dashboard :vrobbler:moods:feature:templates:personal:
:PROPERTIES:
:ID:       c03a38ce-b337-f4fa-adba-aee08d4329f5
:END:
* Version 25.0 [3/3]
** DONE Add basic food templates and fix urls :food:vrobbler:personal:project:bug:urls:
:PROPERTIES:
:ID:       3de3459e-8e7e-abba-e068-b919a819d3e3
:END:
** DONE [#C] Fix how elapsed time is calculated :vrobbler:personal:project:scrobbles:bug:
:PROPERTIES:
:ID:       cff58fc4-06ac-8016-4eae-130b51e3c9b7
:END:
** DONE Fix templates for videos and dashboard links :personal:feature:project:vrobbler:templates:
:PROPERTIES:
:ID:       7debfbaf-cdd8-f49b-57ff-804bfe7c9236
:END:
* Version 24.0 [2/2]
** DONE Clean up logdata for various media :personal:feature:project:vrobbler:logdata:
:PROPERTIES:
:ID:       d5cce807-1f45-ef19-45a4-9f7069fa2a93
:END:
** DONE Removed sidebar and add links to headers :personal:feature:templates:scrobbles:
:PROPERTIES:
:ID:       1a1c0aa6-0313-c8be-1676-5d6adddef0a4
:END:

* Version 23.0 [3/3]
** DONE Add dynamic forms for LogData classes :personal:feature:vrobbler:project:forms:logdata:
:PROPERTIES:
:ID:       0db889a1-f262-fba2-7fed-ed99eded1c88
:END:
** DONE Look in comments for a timestamp for start from BG stats if the time is missing :vrobbler:feature:boardgames:project:personal:
** DONE Fix long play scrobbles to provide better data :vrobbler:feature:scrobbles:longplay:personal:project:
:PROPERTIES:
:ID:       99f6bd77-dc8f-6ed1-0321-32a52c944264
:END:
* Version 19.0 [1/1]
** DONE Add periodic check for mood :vrobbler:feature:moods:personal:project:
:PROPERTIES:
:ID:       55404488-c69f-0dd5-838e-1d1e15c873eb
:END:
* Version 18.7 [1/1]
** DONE Use the timezone history log to fix old Scrobbles that fall into those timezone blocks :vrobbler:chore:scrobbles:project:personal:
:PROPERTIES:
:ID:       9d055ac1-584b-20c8-7ad9-9ce36b329dc7
:END:
* Version 18.4 [2/2]
** DONE Track timezone changes for profiles :vrobbler:feature:profiles:personal:project:
:PROPERTIES:
:ID:       89ec867f-29fd-82f1-be17-b49dddc30c78
:END:
[2025-07-11 14:23]
** DONE Only create a LastFM import if there are files to import :vrobbler:feature:lastfm:importers:project:personal:
:PROPERTIES:
:ID:       7a1456af-c5f1-6385-b34f-0be24a6b65b0
:END:
- Note taken on [2025-07-20 Sun 16:21]

  This thing is kicking my butt. As it stands it works, but the scrobbles are not assigned to the tracks properly.
* Version 18.3 [1/1]
** DONE Add timezone awarness to IMAP importer :personal:project:vrobbler:feature:importer:imap:timezones:
:PROPERTIES:
:ID:       05837b7c-96aa-6190-3678-e2ae7c7cac75
:END:
* Version 18 [4/4]
** DONE Condense tracks of the same title by the same artist with multiple albums :vrobbler:feature:music:project:personal:
:PROPERTIES:
:ID:       b39fcec8-59fd-eab0-5809-b8144c7d2708
:END:
** DONE Import from BG stats a "learning" log field when "Learning to play" is in the comment :vrobbler:feature:boardgames:project:personal:
:PROPERTIES:
:ID:       fda59fab-4349-e99e-54c6-9f1392a1c474
:END:
** DONE [#A] Add email importer for BG stats file uploads :vrobbler:feature:boardgames:personal:project:
:PROPERTIES:
:ID:       116fe738-7966-615c-d195-ccff0337b101
:END:
#+begin_src json example of a file
{
  "about": "This is a Play file that can be read by Board Game Stats. If you see this text, try to use a share, export or open-in function to open it with Board Game Stats.",
  "players": [
    {
      "uuid": "31f8b92e-11d8-4162-88b1-fd9c79eea249",
      "id": 2,
      "name": "Colin",
      "isAnonymous": false,
      "modificationDate": "2025-07-01 18:10:32",
      "metaData": "{\"isNpc\":0}"
    },
    {
      "uuid": "00074700-cf4e-4ad3-b334-d35805bb0d90",
      "id": 4,
      "name": "Asa Sewell",
      "isAnonymous": false,
      "modificationDate": "2025-07-01 18:03:37"
    }
  ],
  "locations": [
    {
      "uuid": "14f7389c-767f-4725-9b35-906c407b293c",
      "id": 3,
      "name": "Timberwyck Farm",
      "modificationDate": "2025-07-01 18:03:38"
    }
  ],
  "games": [
    {
      "uuid": "043a2851-f201-467a-a60c-0b0a7e9c33d2",
      "id": 333,
      "name": "Ghost Fightin' Treasure Hunters: Anniversary Edition",
      "modificationDate": "2025-07-02 01:37:14",
      "cooperative": true,
      "highestWins": true,
      "noPoints": false,
      "usesTeams": false,
      "urlThumb": "https://cf.geekdo-images.com/DHA-mcH3zzw_OjfDxOPj1A__thumb/img/UhaIm4KIDIiraUc44QIvSAbMUXI=/fit-in/200x150/filters:strip_icc()/pic8266874.jpg",
      "urlImage": "https://cf.geekdo-images.com/DHA-mcH3zzw_OjfDxOPj1A__original/img/2-Lb6nLePhn0I0Hh2j1pOtbO4rg=/0x0/filters:format(jpeg)/pic8266874.jpg",
      "bggName": "Ghost Fightin' Treasure Hunters: Anniversary Edition",
      "bggYear": 2024,
      "bggId": 422668,
      "designers": "Brian Yu",
      "isBaseGame": 1,
      "isExpansion": 0,
      "rating": 75,
      "minPlayerCount": 2,
      "maxPlayerCount": 5,
      "minPlayTime": 30,
      "maxPlayTime": 0,
      "minAge": 8
    }
  ],
  "plays": [
    {
      "uuid": "bae3f29e-5e1e-45d8-b409-47a665c8d5b5",
      "modificationDate": "2025-07-02 01:37:59",
      "entryDate": "2025-07-02 01:31:38",
      "playDate": "2025-07-02 01:31:38",
      "usesTeams": false,
      "durationMin": 23,
      "ignored": false,
      "manualWinner": true,
      "rounds": 3,
      "scoresheet": "{\"bggId\":244711,\"version\":1,\"langCode\":\"en\",\"scoreType\":\"bestTotalWins\",\"groups\":[{\"templateId\":\"1\",\"maxRepeat\":-1,\"repetition\":1,\"hasSubTotal\":false,\"hideSingleGroupLabel\":false,\"isExtra\":false,\"rows\":[{\"templateId\":\"vptrack\",\"label\":\"VP track\",\"repetition\":1,\"repeatable\":false,\"negative\":false,\"isExtra\":false,\"scores\":{}},{\"templateId\":\"objectives\",\"label\":\"Objectives\",\"repetition\":1,\"repeatable\":false,\"negative\":false,\"isExtra\":false,\"scores\":{}},{\"templateId\":\"mastercards\",\"label\":\"Master cards\",\"repetition\":1,\"repeatable\":false,\"negative\":false,\"isExtra\":false,\"scores\":{}}]}]}",
      "locationRefId": 3,
      "gameRefId": 333,
      "board": "",
      "scoringSetting": 4,
      "metaData": "{\"playUsedGameCopy\":2}",
      "playerScores": [
        {
          "score": "",
          "winner": true,
          "newPlayer": true,
          "startPlayer": false,
          "playerRefId": 4,
          "role": "",
          "rank": 0,
          "seatOrder": 0,
          "metaData": "{\"scoreUuid\":\"00074700-cf4e-4ad3-b334-d35805bb0d90\"}"
        },
        {
          "score": "",
          "winner": true,
          "newPlayer": true,
          "startPlayer": false,
          "playerRefId": 2,
          "role": "",
          "rank": 0,
          "seatOrder": 0,
          "metaData": "{\"scoreUuid\":\"31f8b92e-11d8-4162-88b1-fd9c79eea249\"}"
        }
      ],
      "expansionPlays": []
    }
  ],
  "userInfo": {
    "meRefId": 2
  }
}

#+end_src
** DONE [#B] Fix task app to only use one tag for the context a task was done in and allow configurable contexts by user profile :personal:vrobbler:feature:tasks:project:
:PROPERTIES:
:ID:       23f485e3-988c-6198-c79d-91fdf92f001c
:END:
* Version 17.0 [6/6]
** DONE [#A] Fix bug in new task label lookup for Emacs/Org-mode :vrobbler:bug:tasks:
:PROPERTIES:
:ID:       683fb109-dfc4-85e4-80f0-ea618434f61e
:END:
** DONE [#C] Replace commas in the bandcamp URL for artists with nothing :vrobbler:music:bug:personal:
:PROPERTIES:
:ID:       9b30d67b-91f0-a480-dfaa-5d9dc090e76c
:END:

- Note taken on [2025-06-16 Mon 09:36]

  This firt appeared with Black Country, New Road, where the RYM slug generator
  leaves commas in and ends up sending you to a 404. I suspect this wont be the
  first tweak we'll need to this, as the RYM link creator is really just
  guessing based on the artist name at the path.

** DONE [#A] Investigate new source of video metadata :personal:project:video:imdb:
:PROPERTIES:
:ID:       df2b486c-1170-5199-c312-9bc87760d962
:END:

Cinemagoer broke and I probably should find a more reilable source of video data.

- Note taken on [2025-06-13 Fri 11:19]

  TMDB is much more reliable, but does require an API key. That's all setup now,
  so hopefully this breaking IMDB crap is over.

** DONE [#A] IMDB video lookups are failing :personal:bug:video:imdb:
:PROPERTIES:
:ID:       38f1081f-37b4-f4f2-79aa-c1e87eca4b69
:END:
<2025-06-13 Fri>

- Note taken on [2025-06-13 Fri 08:24]

  Looks like Cinemagoer is broken: https://github.com/cinemagoer/cinemagoer/issues/537

** DONE [#A] Emacs is not syncing notes :personal:scrobbling:emacs:bug:
:PROPERTIES:
:ID:       c79cd491-b30f-0945-d84b-b8cac7562791
:END:
<2025-06-12 Thu 9:30>

Not sure if the problem is in my Emacs hook sending or Vrobbler itself.

- Note taken on [2025-06-12 Thu 09:47]

  Adding a quick note to check on it

- Note taken on [2025-06-12 Thu 09:50]

  Ah ha. All the messing about with the source field meant that I was looking
  for `emacs` as a source but the hook was initially setting sources to
  `orgmode` I think I prefer `orgmode` as the source, so updating it thusly.

  Fixed in `490d60cbbb1f8bf90b5fc47d8685b15bdc1d485b`

** DONE [#A] Show the description of a task in the string rep for a scrobble of a Task :personal:project:scrobbling:vrobbler:feature:
:PROPERTIES:
:ID:       df58f8d0-fa4a-2037-c7d7-e5388c239042
:END:
* Version 0.16.0 [19/19]
** DONE [#A] Jellyfin, bandcamp tracks from Mopidy create duplicate music tracks :bug:scrobbling:music:
:PROPERTIES:
:ID:       670e8634-49b5-dce9-1684-14f2ffb797f1
:END:
Effectively, any track that comes in without a MusicBrainz ID does some funky
lookup where it doesn't find a track without an MB id and the track title /
artist combination and creates a new track every time. This has to be cleaned up
by condensing the duplicated tracks into the original proper track.

But it opens a bigger question about how much MB id should the drive the app
lookup. If it can't be depended on to exist from all sources, it really can't be
canonical. Instead, the combination of track title / artist is really the best
we can do. Last.fm also has this problem, where it doesn't know about albums and
definitely does not know or care about MB ids.

** DONE Add a user profile page with ability to change settings :profiles:improvement:
- Note taken on [2025-04-04 Fri 10:51]
 [[orgit-rev:~/src/code.unbl.ink/secstate/vrobbler/::93c16d80ecff4cd1663cf9ec40fbe6d8f58c3e44][~/src/code.unbl.ink/secstate/vrobbler/ (magit-rev 93c16d8)]]

 https://code.unbl.ink/secstate/vrobbler/commit/93c16d80ecff4cd1663cf9ec40fbe6d8f58c3e44

** DONE What to do with Youtube videos from LastFM and web-scrobbler :bug:source:lastfm:
- Note taken on [2025-04-04 Fri 10:46]

  Nothing. Over the last few months I built out a youtube model in videos and
  use a bookmarklet scrobbling pattern. Now web-scrobbler is just disabled for
  Youtube.

  May want to revisit this at some point and only scrobble tracks from Youtube,
  because many people use YT for music listening.

** DONE [#C] Consider a purge command for duplicated and stuck in-progress scrobbles :utililty:improvement:
CLOSED: [2023-04-06 Thu 14:09]
** DONE Add a "stop_timestamp" so we don't rely on content length :improvement:scrobbling:
CLOSED: [2023-04-02 Sun 23:58]

Essentially, we currently have the timestamp as when the content began
scrobbling and then calculate the finish time from the length of the content.
This works pretty well because we know how long most things are.

But in some cases, sports events or long podcasts, we may start mid-way through
an event or finish halfway through but still want to mark it as done. In these
cases, knowing the finish time could be useful, especially when interfacing with
other scrobblers which may have different definitions of when a scrobble
finishes or started.
** DONE Fix bug with Various Artist albums being labeled with first artist as album artist :scrobbling:bug:music:
CLOSED: [2023-03-27 Mon 20:18]
:LOGBOOK:
CLOCK: [2023-03-26 Sun 22:01]--[2023-03-27 Mon 01:07] =>  3:06
:END:
** DONE Fix bug with weekly aggregator being blank on Sundays :aggregators:music:bug:
CLOSED: [2023-03-26 Sun 13:52]
** DONE Fix KoReader scrobbling to use pages rather than time of last read :scrobbling:books:improvement:
CLOSED: [2023-03-26 Sun 13:51]
:LOGBOOK:
CLOCK: [2023-03-26 Sun 13:11]--[2023-03-26 Sun 13:51] =>  0:40
:END:
** DONE [#A] Add django-storage to store files on S3 :settings:improvement:
CLOSED: [2023-03-24 Fri 14:46]
:LOGBOOK:
CLOCK: [2023-03-24 Fri 10:47]--[2023-03-24 Fri 14:46] =>  3:59
CLOCK: [2023-03-24 Fri 10:36]--[2023-03-24 Fri 10:40] =>  0:04
:END:
** DONE Fix vrobbler settings not using booleans :settings:bug:
CLOSED: [2023-03-24 Fri 10:45]
:LOGBOOK:
CLOCK: [2023-03-24 Fri 10:40]--[2023-03-24 Fri 10:46] =>  0:06
:END:
** DONE Update weekly live chart to be 7-day continuous rather than weekly :views:bug:
CLOSED: [2023-03-24 Fri 00:31]
The live view will be blank every Monday, no reason to tie it to a day of the
week. It should be "the last 7 days"
** DONE [#B] Implement a detail view for TV shows :improvement:views:
CLOSED: [2023-03-22 Wed 17:05]
** DONE [#B] Implement a detail view for Movies :improvement:views:
CLOSED: [2023-03-22 Wed 17:05]
** DONE Add "service provider" to TV Series, and use that for source when available :bug:scrobbling:
CLOSED: [2023-03-22 Wed 17:04]
** DONE Add view for long-play content (books, video games) to restart them :views:improvement:
CLOSED: [2023-03-22 Wed 17:01]
** DONE Add live chart view like Maloja :improvement:views:
CLOSED: [2023-03-07 Tue 11:13]
** DONE [#C] Figure out how to add to web-scrobbler :improvement:scrobbling:
CLOSED: [2023-03-22 Wed 17:06]

An example:
https://github.com/web-scrobbler/web-scrobbler/blob/master/src/core/background/scrobbler/maloja-scrobbler.js

This is actually going to be moot because we can import from LastFM, and
web-scrobbler integrates well with LastFM. The only thing to think through here
now is what to do with all the garbage web-scrobbler sometimes pushes to LastFM
from Youtube (all videos get pushed, sigh).

** DONE Add Amazon scraper to look up books when OL fails :books:improvement:
This turned out to be a non-starter ... Amazon is aggressive at disallowing
scraping quality. And all the OSS tools out there are stuck in an arms race
trying to keep them from breaking.

That said, Google Books actually has a decent API (for now), and I've built this
out using that.

** DONE Fix bug in Jellyfin scrobbles that spam more scrobbles after completion :scrobbling:videos:bug:
This was fixed a while ago, but there's a new manifested bug. Going to create a
separate bug tracking ticket for that.
* Version 0.11.4 [9/9]
** DONE Add rudimentary video game scrobbling :improvement:content:videogames:
CLOSED: [2023-03-07 Tue 11:11]
** DONE Add ability to scrobble from KOReader statistics files :improvement:books:content:
CLOSED: [2023-03-07 Tue 11:11]

** DONE [#A] Fix fetching artwork without release group :bug:
CLOSED: [2023-01-29 Sun 14:27]

When we get artwork from Musicbrianz, and it's not found, we should check for
release groups as well. This will stop issues with missing artwork because of
obscure MB release matches.

** DONE [#A] Fix Jellyfin music scrobbling N+1 past 90 completion percent :bug:
CLOSED: [2023-01-30 Mon 18:31]
:LOGBOOK:
CLOCK: [2023-01-30 Mon 18:00]--[2023-01-30 Mon 18:31] =>  0:31
:END:

If we play music from Jellyfin and the track reaches 90% completion, the
scrobbling goes crazy and starts creating new scrobbles with every update.

The cause is pretty simple, but the solution is hard. We want to mark a scrobble
as complete for the following conditions:

- Play stopped and percent played beyond 90%
- Play completely finished

But if we keep listening beyond 90, we should basically ignore updates (or just
update the existing scrobble)
** DONE [#A] Add support for Audioscrobbler tab-separated file uploads :improvement:
CLOSED: [2023-02-03 Fri 16:52]

An example of the format:
#+begin_src csv
,
#AUDIOSCROBBLER/1.1
#TZ/UNKNOWN
#CLIENT/Rockbox sansaclipplus $Revision$
75 Dollar Bill	I Was Real	I Was Real	4	1015	S	1740494944	64ff5f53-d187-4512-827e-7606c69e66ff
75 Dollar Bill	I Was Real	I Was Real	4	1015	S	1740494990	64ff5f53-d187-4512-827e-7606c69e66ff
311	311	Down	1	173	S	1740495003	00476c23-fd9e-464b-9b27-a62d69f3d4f4
311	311	Down	1	173	L	1740495049	00476c23-fd9e-464b-9b27-a62d69f3d4f4
311	311	Down	1	173	L	1740495113	00476c23-fd9e-464b-9b27-a62d69f3d4f4
311	311	Random	2	187	S	1740495190	530c09f3-46fe-4d90-b11f-7b63bcb4b373
311	311	Random	2	187	L	1740495194	530c09f3-46fe-4d90-b11f-7b63bcb4b373
311	311	Jackolantern’s Weather	3	204	L	1740495382	cc3b2dec-5d99-47ea-8930-20bf258be4ea
311	311	All Mixed Up	4	182	L	1740495586	980a78b5-5bdd-4f50-9e3a-e13261e2817b
311	311	Hive	5	179	L	1740495768	18f6dc98-d3a2-4f81-b967-97359d14c68c
311	311	Guns (Are for Pussies)	6	137	L	1740495948	5e97ed9f-c8cc-4282-9cbe-f8e17aee5128
311	311	Misdirected Hostility	7	179	S	1740496085	61ff2c1a-fc9c-44c3-8da1-5e50a44245af
,
#+end_src
** DONE [#B] Allow scrobbling music without MB IDs by grabbing them before scrobble :improvement:
CLOSED: [2023-02-17 Fri 00:10]

This would allow a few nice flows. One, you'd be able to record the play of an
entire album by just dropping the muscibrainz_id in. This could be helpful for
offline listening. It would also mean bad metadata from mopidy would not break
scrobbling.
** DONE When updating musicbrainz IDs, clear and run fetch artwrok :improvement:
CLOSED: [2023-02-17 Fri 00:11]
** DONE [#A] Add ability to manually scrobble albums or tracks from MB :improvement:
CLOSED: [2023-03-07 Tue 11:09]

Given a UUID from musicbrainz, we should be able to scrobble an album or
individual track.

** DONE [#C] Implement keeping track of week/month/year chart-toppers :improvement:
CLOSED: [2023-03-07 Tue 11:10]
:LOGBOOK:
CLOCK: [2023-01-30 Mon 16:30]--[2023-01-30 Mon 18:00] =>  1:30
:END:

Maloja does this cool thing where artists and tracks get recorded as the top
track of a given week, month or year. They get gold, silver or bronze stars for
their place in the time period.

I could see this being implemented as a separate Chart table which gets
populated at the end of a time period and has a start and end date that defines
a period, along with a one, two, three instance.

Of course, it could also be a data model without a table, where it runs some fun
calculations, stores it's values in Redis as a long-term lookup table and just
has to re-populate when the server restarts.
