diff --git a/src/constants/settings.ts b/src/constants/settings.ts index df2c39f..f437a57 100644 --- a/src/constants/settings.ts +++ b/src/constants/settings.ts @@ -20,6 +20,12 @@ export const settings = { disableHardwareMediaKeys: "disableHardwareMediaKeys", enableCustomHotkeys: "enableCustomHotkeys", enableDiscord: "enableDiscord", + ListenBrainz: { + root: "ListenBrainz", + enabled: "ListenBrainz.enabled", + api: "ListenBrainz.api", + token: "ListenBrainz.token", + }, flags: { root: "flags", disableHardwareMediaKeys: "flags.disableHardwareMediaKeys", diff --git a/src/features/listenbrainz/listenbrainz.ts b/src/features/listenbrainz/listenbrainz.ts new file mode 100644 index 0000000..812680c --- /dev/null +++ b/src/features/listenbrainz/listenbrainz.ts @@ -0,0 +1,83 @@ +import axios from "axios"; +import { settingsStore } from "../../scripts/settings"; +import { settings } from "../../constants/settings"; +import { MediaStatus } from "../../models/mediaStatus"; +import Store from "electron-store"; + +const ListenBrainzStore = new Store(); + +export class ListenBrainz { + /** + * Call the ListenBrainz API and create playing now payload + * @param title + * @param artists + * @param status + * @param duration + */ + public static async scrobble(title: string, artists: string, status: string, duration: number): Promise { + try { + if (status == MediaStatus.paused) { + return false; + } else { + // Fetches the OldData required for scrobbling and proceeds to construct a playing_now data payload for the Playing Now area + const OldData = ListenBrainzStore.get("OldData") as string[]; + const playing_data = { + listen_type: "playing_now", + payload: [ + { + track_metadata: { + additional_info: { + media_player: "Tidal Hi-Fi", + submission_client: "Tidal Hi-Fi", + music_service: "listen.tidal.com", + duration: duration, + }, + artist_name: artists, + track_name: title, + } + } + ] + }; + + await axios.post(`${settingsStore.get(settings.ListenBrainz.api)}/1/submit-listens`, playing_data, { + headers:{ + "Content-Type": "application/json", + "Authorization": `Token ${settingsStore.get(settings.ListenBrainz.token)}` + } + }); + if (!OldData) { + ListenBrainzStore.set("OldData", [Math.floor(new Date().getTime() / 1000), title, artists, duration]); + } else if (OldData[1] != title) { + // This constructs the data required to scrobble the data after the song finishes + const scrobble_data = { + listen_type: "single", + payload: [ + { + listened_at: OldData[0], + track_metadata: { + additional_info: { + media_player: "Tidal Hi-Fi", + submission_client: "Tidal Hi-Fi", + music_service: "listen.tidal.com", + duration: OldData[3], + }, + artist_name: OldData[2], + track_name: OldData[1], + } + } + ] + }; + await axios.post(`${settingsStore.get(settings.ListenBrainz.api)}/1/submit-listens`, scrobble_data, { + headers:{ + "Content-Type": "application/json", + "Authorization": `Token ${settingsStore.get(settings.ListenBrainz.token)}` + } + }); + ListenBrainzStore.set("OldData", [Math.floor(new Date().getTime() / 1000), title, artists, duration]); + } + } + } catch (error) { + console.log(JSON.stringify(error)); + } + } +} diff --git a/src/pages/settings/preload.ts b/src/pages/settings/preload.ts index 53ad096..ec2ed8e 100644 --- a/src/pages/settings/preload.ts +++ b/src/pages/settings/preload.ts @@ -25,7 +25,11 @@ let adBlock: HTMLInputElement, skippedArtists: HTMLInputElement, theme: HTMLSelectElement, trayIcon: HTMLInputElement, - updateFrequency: HTMLInputElement; + updateFrequency: HTMLInputElement, + enableListenBrainz: HTMLInputElement, + ListenBrainzAPI: HTMLInputElement, + ListenBrainzToken: HTMLInputElement; + function getThemeFiles() { const selectElement = document.getElementById("themesList") as HTMLSelectElement; const builtInThemes = getThemeListFromDirectory(process.resourcesPath); @@ -87,6 +91,9 @@ function refreshSettings() { skippedArtists.value = settingsStore.get(settings.skippedArtists).join("\n"); trayIcon.checked = settingsStore.get(settings.trayIcon); updateFrequency.value = settingsStore.get(settings.updateFrequency); + enableListenBrainz.checked = settingsStore.get(settings.ListenBrainz.enabled); + ListenBrainzAPI.value = settingsStore.get(settings.ListenBrainz.api); + ListenBrainzToken.value = settingsStore.get(settings.ListenBrainz.token); } /** @@ -183,6 +190,9 @@ window.addEventListener("DOMContentLoaded", () => { skippedArtists = get("skippedArtists"); singleInstance = get("singleInstance"); updateFrequency = get("updateFrequency"); + enableListenBrainz = get("enableListenBrainz"); + ListenBrainzAPI = get("ListenBrainzAPI"); + ListenBrainzToken = get("ListenBrainzToken"); refreshSettings(); @@ -206,4 +216,7 @@ window.addEventListener("DOMContentLoaded", () => { addSelectListener(theme, settings.theme); addInputListener(trayIcon, settings.trayIcon); addInputListener(updateFrequency, settings.updateFrequency); + addInputListener(enableListenBrainz, settings.ListenBrainz.enabled); + addTextAreaListener(ListenBrainzAPI, settings.ListenBrainz.api); + addTextAreaListener(ListenBrainzToken, settings.ListenBrainz.token); }); diff --git a/src/pages/settings/settings.html b/src/pages/settings/settings.html index 583b24b..5581777 100644 --- a/src/pages/settings/settings.html +++ b/src/pages/settings/settings.html @@ -211,6 +211,30 @@

Discord RPC

+
+
+

ListenBrainz

+

Scrobble your listens directly to ListenBrainz.

+
+ +
+
+
+

ListenBrainz API Url

+

There are multiple instances for ListenBrainz you can set the corresponding API url below.

+
+
+ +
+
+

ListenBrainz User Token

+

Provide the user token you can get from the settings page.

+
+
+ diff --git a/src/preload.ts b/src/preload.ts index dffedb6..40678a3 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -6,6 +6,7 @@ import { globalEvents } from "./constants/globalEvents"; import { settings } from "./constants/settings"; import { statuses } from "./constants/statuses"; import { Songwhip } from "./features/songwhip/songwhip"; +import { ListenBrainz } from "./features/listenbrainz/listenbrainz"; import { Options } from "./models/options"; import { downloadFile } from "./scripts/download"; import { addHotkey } from "./scripts/hotkeys"; @@ -468,6 +469,9 @@ setInterval(function () { updateMediaInfo(options, titleOrArtistsChanged); if (titleOrArtistsChanged) { updateMediaSession(options); + if (settingsStore.get(settings.ListenBrainz.enabled)) { + ListenBrainz.scrobble(options.title, options.artists, options.status, convertDuration(options.duration)); + } } }); diff --git a/src/scripts/settings.ts b/src/scripts/settings.ts index 614f8d1..086504e 100644 --- a/src/scripts/settings.ts +++ b/src/scripts/settings.ts @@ -18,6 +18,11 @@ export const settingsStore = new Store({ disableHardwareMediaKeys: false, enableCustomHotkeys: false, enableDiscord: false, + ListenBrainz: { + enabled: false, + api: "https://api.listenbrainz.org", + token: "", + }, flags: { gpuRasterization: true, disableHardwareMediaKeys: false,