for more info check out the pull request thread
ListenBrainz Rich Presence in Vencord!
I’ve always been a fan of tools that track your listening activity. Last.FM is a website that does exactly that! Vencord—a client mod for Discord—included a plugin that showed your Last.FM “Now Playing” as a Rich Presence activity, and I used that for a while. However, I grew increasingly dissatisfied with how Last.FM handled metadata, which is to say, it didn’t. There would be like, five different entries for one song, six for one artist, and so on and so forth. Also, oftentimes Last.FM wouldn’t associate albums with tracks—that meant the track would have no album art, and it was really annoying to see a placeholder image show up on your Discord profile. The solution…?
ListenBrainz! A scrobbler application made by the same organization responsible for MusicBrainz, the database on everything song metadata. However, using ListenBrainz instead of Last.FM meant that I wouldn’t have my currently playing song show up on my Discord profile. Which obviously meant I gave up.
Just kidding, I wrote my own plugin: ListenBrainz Rich Presence for Vencord. That isn’t part of my Stardance time though, I wrote that janky mess all the way back in March. I submitted this plugin as a pull request to the Vencord repository, only to get this response:
Vendicated: Please merge this into the lastfm rpc plugin to avoid duplicating code
And I said, sure, I can do that—then proceeded to forget all about this project for a whole month. This week, Vendicated bumped the thread, asking whether I was gonna actually do it, and that’s where our story actually starts.
The idea: a unified scrobbler plugin (obviously) that accepts a variety of different backends for its status. Last.FM and ListenBrainz would be separate backends that the user could choose from. I would also make the whole fetching metadata feature a separate thing that the user could access from either backend, using MusicBrainz instead of the ListenBrainz lookup API (which requires a token now because of AI abuse.)
Here’s the thing, though—I thought I would make the backend selection an enum. However, a backend tends to be more of an object than just a string—it’s got an ID, a name, a website, and tons of other properties that all have to be expressed. My initial implementation was janky as shit:
interface ScrobblerBackend {
name: string,
id: string,
url: string,
userProfilePath: string;
}
const ScrobblerBackends: Map<string, ScrobblerBackend> = new Map([
[
"lastfm",
{
name: "Last.FM",
id: "lastfm",
url: "https://www.last.fm/",
userProfilePath: "/user"
}
],
[
"listenbrainz",
{
name: "ListenBrainz",
id: "listenbrainz",
url: "https://listenbrainz.org/",
userProfilePath: "/user"
}
]
]);
what the hell dawg 😭
This is partially attributed to my fairly weak understanding of TypeScript as it relates to object-oriented programming—I didn’t know how classes and inheritance actually worked. I learned OOP in Java and C#, okay 😭 this was all still kinda new to me!
Obviously Vendicated caught and corrected me real quick there, and I learned that yes, inheritance is a thing. Classes can inherit interfaces. Isn’t that crazy.
We construct this interface…
export interface ScrobblerBackend {
name: string,
id: string,
url: string,
fetchTrackData(username: string, apiKey?: string): Promise<TrackData | null>;
getUserURL(username: string): string;
}
…and then we do export class ListenBrainzScrobbler implements ScrobblerBackend. Woohoo. Yippee.
Comments 0
No comments yet. Be the first!
Sign in to join the conversation.