mirror of
				https://github.com/Mastermindzh/tidal-hifi.git
				synced 2025-11-04 10:49:26 +01:00 
			
		
		
		
	Add ListenBrainz implementation
This commit is contained in:
		@@ -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",
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										83
									
								
								src/features/listenbrainz/listenbrainz.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								src/features/listenbrainz/listenbrainz.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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<any> {
 | 
			
		||||
    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));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -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<string, string[]>(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);
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -211,6 +211,30 @@
 | 
			
		||||
                <span class="switch__slider"></span>
 | 
			
		||||
              </label>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="group__option">
 | 
			
		||||
              <div class="group__description">
 | 
			
		||||
                <h4>ListenBrainz</h4>
 | 
			
		||||
                <p>Scrobble your listens directly to ListenBrainz.</p>
 | 
			
		||||
              </div>
 | 
			
		||||
              <label class="switch">
 | 
			
		||||
                <input id="enableListenBrainz" type="checkbox" />
 | 
			
		||||
                <span class="switch__slider"></span>
 | 
			
		||||
              </label>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="group__option">
 | 
			
		||||
              <div class="group__description">
 | 
			
		||||
                <h4>ListenBrainz API Url</h4>
 | 
			
		||||
                <p>There are multiple instances for ListenBrainz you can set the corresponding API url below.</p>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <textarea id="ListenBrainzAPI" class="textarea" cols="1" rows="1" spellcheck="false"></textarea>
 | 
			
		||||
            <div class="group__option">
 | 
			
		||||
              <div class="group__description">
 | 
			
		||||
                <h4>ListenBrainz User Token</h4>
 | 
			
		||||
                <p>Provide the user token you can get from the settings page.</p>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <textarea id="ListenBrainzToken" class="textarea" cols="1" rows="1" spellcheck="false"></textarea>
 | 
			
		||||
          </div>
 | 
			
		||||
        </section>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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));
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user