listhist
- 3 Devlogs
- 21 Total hours
A listening history server
A listening history server
Welcome back to my listening history tracking project! You are now reading the third entry, and I’ve achieved a great milestone - the listening history tracking actually works. You can query your listening history and it will be fed back to you in JSON, supporting specific queries like a start date, an end date, a limit on the number of entries, etc. (i actually ran out of examples.)
This JSON history endpoint does something that was very cool to implement; it gets the history entries, but obviously, those entries aren’t everything that a client needs to display them to the user. They consist merely of an entry ID, a track ID, and a date. The client would need to fetch the track details, then the album details, and then the artist details on its own, which would make for a lot of requests, considering that people usually listen to more than one track per day. So this would mean that, had the client not implemented some sort of caching, if you listened to, say, 25 tracks in a day and then queried your history, you would end up at a beautiful
n_entries * len(["track", "album", "artist"]) = 25 * 3 = 75
requests! Which is insane. This listening history endpoint reduces this number to 1. Yes, ONE request. This is done by directly including the necessary tracks, albums, and artists in the response, and the client can just iterate over the arrays of these resources and parse them into a hashmap keyed by their respective IDs. Pretty neat!
So had you listened to a track, like “Keep The Streets Empty For Me” by Fever Ray from their album Fever Ray, the (prettified) response from the endpoint would look like this:
{
"entries": [
{
"id": 3,
"track_id": 8,
"date": "2026-08-02T02:27:13.727572Z"
}
],
"tracks": [
{
"id": 8,
"title": "Keep The Streets Empty For Me",
"artist_id": 2,
"album_id": 4,
"genre": "Electronic"
}
],
"albums": [
{
"id": 4,
"name": "Fever Ray",
"artist_id": 2
}
],
"artist": [
{
"id": 2,
"name": "Fever Ray"
}
]
}
Looking at this, I’ve just realized that I probably need to make the genre column an array, since tracks usually exhibit properties of many different genres at once… that’s a problem for future me
Obviously I’ve improved the code a bit here and there, the Compose configuration now actually makes the backend wait for the database so there isn’t a race condition (which may have been there for a few weeks…), and the data schema has changed a bit so that it makes more sense.
There is now also a web app, but that is far from finished as it stands and so instead I’ve included a screenshot of the Firefox devtools I used to poke at my API and the successful response I finally got in the end.
Welcome to my second devlog regarding listhist!
Since the last entry, I’ve rewritten the shared client code to take advantage of the Rust type system much more. I’ve written quite a bit of Haskell in the past few weeks and I love the strong type system of the language; however, monadic I/O and especially the mixture of pure and impure exceptions makes my head hurt, which is why I decided to instead write this project mostly in Go and Rust. Either way, this means that without logging in, you can’t query endpoints which require an access token; so, for example, in order to create an album in the system, you have to do something like this:
let client = listhist_client::Client::new("your_base_url");
Now, client is of type Client<Unauthorized>. You can then call Client::login:
let client = client.login("username", "password").await?;
Now client is of type Client<Authorized> and you can actually create an album!
let album = client.create_album(...).await?;
Oh yeah, the client is now also asynchronous and therefore much more flexible… but that’s not as exciting.
The other exciting thing that I managed to add to the repo is a Containerfile and a Compose file, so spinning up an instance, whether for testing or “for real” is super easy. So in this regard, I think I’ve achieved what I wrote about in the last entry.
Now, the next step is polishing the existing code, resolving the TODO/FIXME items I have left for myself, adding some tests, etc. It’s boring work, but I believe that if I do it sooner than later, it will come back around and I can catch some of the more annoying bugs early on.
And then I want to make a web interface for easy viewing of the data. The CLI should be the main way to enter listening history entries into the system, automatically of course (probably through MPRIS, custom player hooks and some Windows-specific mechanism), but viewing of the data should be possible from any device, without having to install the CLI. So, the web interface will be a relatively simple, fully client-sided web application, probably written with something like Preact.
I have a lot of things planned for listhist, maybe even a friends system, which would be very exciting to have! Stay tuned for upcoming updates.
Hi, I’m making a project to track my listening history over all the devices I use to listen to music because I don’t use Spotify or YouTube Music.
Think something like last.fm if you’ve ever used it, but a little simpler.
please just imagine that you’ve been following along for a while now, i may or may not have forgotten to set up hackatime and log my work ;_;
Anyways, the project is written in Go for the backend and Rust for the client library and the CLI that currently uses it.
I’ve so far fleshed out the basic structure of what the data should look like, I have a database schema, yada yada… I also had FFI bindings so that I could use the client library from other code like WebAssembly so that I could have a web client! However… I’m currently going over the code, resolving some of the // TODO comments that I left for myself and kind of realized that the FFI is… completely… and utterly… out of date…
So I deleted it. And I’m also going to rewrite parts of the FFI interface which just sort of duplicated the Rust-native structures with derive macros, which I’ve been wanting to learn for a long, long time now!
…That is, when I get to it. For now, I’m going to try to get listhist into a functional state so that you (yes, YOU!) can try it out for yourself.
Don’t worry, I won’t make you download anything, unless you decide you want to use listhist (wink wink)
One of the main goals of the project is to make it easily self-hostable, so while I will provide an instance of my own, if you really like listhist, you will be able to do so easily with an OCI-compliant container image and maybe a Compose file.
Thank you for reading this first devlog and I hope to see you here again!