mirror of
https://github.com/Mastermindzh/tidal-hifi.git
synced 2024-11-22 05:23:09 +01:00
various code improvements and some boyscout rule fixes :)
This commit is contained in:
parent
de8a5a1b07
commit
586f7b595b
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@ -1,10 +1,14 @@
|
||||
{
|
||||
"cSpell.words": [
|
||||
"Brainz",
|
||||
"flac",
|
||||
"geqnfr",
|
||||
"hifi",
|
||||
"listenbrainz",
|
||||
"playpause",
|
||||
"rescrobbler",
|
||||
"scrobble",
|
||||
"scrobbling",
|
||||
"Songwhip",
|
||||
"trackid",
|
||||
"tracklist",
|
||||
|
@ -1,4 +0,0 @@
|
||||
export const statuses = {
|
||||
playing: "playing",
|
||||
paused: "paused",
|
||||
};
|
@ -1,12 +1,35 @@
|
||||
import axios from "axios";
|
||||
import { settingsStore } from "../../scripts/settings";
|
||||
import { ipcRenderer } from "electron";
|
||||
import Store from "electron-store";
|
||||
import { settings } from "../../constants/settings";
|
||||
import { MediaStatus } from "../../models/mediaStatus";
|
||||
import Store from "electron-store";
|
||||
import { settingsStore } from "../../scripts/settings";
|
||||
import { Logger } from "../logger";
|
||||
import { StoreData } from "./models/storeData";
|
||||
|
||||
const ListenBrainzStore = new Store({name: "listenbrainz"});
|
||||
const ListenBrainzStore = new Store({ name: "listenbrainz" });
|
||||
|
||||
export const ListenBrainzConstants = {
|
||||
oldData: "oldData",
|
||||
};
|
||||
|
||||
export class ListenBrainz {
|
||||
/**
|
||||
* Create the object to store old information in the Store :)
|
||||
* @param title
|
||||
* @param artists
|
||||
* @param duration
|
||||
* @returns data passed along in an object + a "listenedAt" key with the current time
|
||||
*/
|
||||
private static constructStoreData(title: string, artists: string, duration: number): StoreData {
|
||||
return {
|
||||
listenedAt: Math.floor(new Date().getTime() / 1000),
|
||||
title,
|
||||
artists,
|
||||
duration,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the ListenBrainz API and create playing now payload and scrobble old song
|
||||
* @param title
|
||||
@ -14,72 +37,98 @@ export class ListenBrainz {
|
||||
* @param status
|
||||
* @param duration
|
||||
*/
|
||||
public static async scrobble(title: string, artists: string, status: string, duration: number): Promise<any> {
|
||||
public static async scrobble(
|
||||
title: string,
|
||||
artists: string,
|
||||
status: string,
|
||||
duration: number
|
||||
): Promise<void> {
|
||||
try {
|
||||
if (status == MediaStatus.paused) {
|
||||
return false;
|
||||
if (status === MediaStatus.paused) {
|
||||
return;
|
||||
} 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[];
|
||||
// Fetches the oldData required for scrobbling and proceeds to construct a playing_now data payload for the Playing Now area
|
||||
const oldData = ListenBrainzStore.get(ListenBrainzConstants.oldData) as StoreData;
|
||||
const playing_data = {
|
||||
listen_type: "playing_now",
|
||||
payload: [
|
||||
{
|
||||
track_metadata: {
|
||||
additional_info: {
|
||||
media_player: "Tidal Hi-Fi",
|
||||
submission_client: "Tidal Hi-Fi",
|
||||
music_service: "tidal.com",
|
||||
duration: duration,
|
||||
},
|
||||
artist_name: artists,
|
||||
track_name: title,
|
||||
}
|
||||
}
|
||||
]
|
||||
listen_type: "playing_now",
|
||||
payload: [
|
||||
{
|
||||
track_metadata: {
|
||||
additional_info: {
|
||||
media_player: "Tidal Hi-Fi",
|
||||
submission_client: "Tidal Hi-Fi",
|
||||
music_service: "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) {
|
||||
await axios.post(
|
||||
`${settingsStore.get<string, string>(settings.ListenBrainz.api)}/1/submit-listens`,
|
||||
playing_data,
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Token ${settingsStore.get<string, string>(
|
||||
settings.ListenBrainz.token
|
||||
)}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
if (!oldData) {
|
||||
ListenBrainzStore.set(
|
||||
ListenBrainzConstants.oldData,
|
||||
this.constructStoreData(title, artists, duration)
|
||||
);
|
||||
} else {
|
||||
if (oldData.title !== 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],
|
||||
}
|
||||
}
|
||||
]
|
||||
listen_type: "single",
|
||||
payload: [
|
||||
{
|
||||
listened_at: oldData.listenedAt,
|
||||
track_metadata: {
|
||||
additional_info: {
|
||||
media_player: "Tidal Hi-Fi",
|
||||
submission_client: "Tidal Hi-Fi",
|
||||
music_service: "listen.tidal.com",
|
||||
duration: oldData.duration,
|
||||
},
|
||||
artist_name: oldData.artists,
|
||||
track_name: oldData.title,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
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]);
|
||||
await axios.post(
|
||||
`${settingsStore.get<string, string>(settings.ListenBrainz.api)}/1/submit-listens`,
|
||||
scrobble_data,
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Token ${settingsStore.get<string, string>(
|
||||
settings.ListenBrainz.token
|
||||
)}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
ListenBrainzStore.set(
|
||||
ListenBrainzConstants.oldData,
|
||||
this.constructStoreData(title, artists, duration)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(JSON.stringify(error));
|
||||
const logger = new Logger(ipcRenderer);
|
||||
logger.log(JSON.stringify(error));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { ListenBrainzStore };
|
||||
export { ListenBrainzStore };
|
||||
|
9
src/features/listenbrainz/models/storeData.ts
Normal file
9
src/features/listenbrainz/models/storeData.ts
Normal file
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Data saved for ListenBrainz
|
||||
*/
|
||||
export interface StoreData {
|
||||
listenedAt: number;
|
||||
title: string;
|
||||
artists: string;
|
||||
duration: number;
|
||||
}
|
@ -26,8 +26,7 @@ export class Logger {
|
||||
public log(content: string, object: object = {}) {
|
||||
if (this.ipcRenderer) {
|
||||
this.ipcRenderer.send(globalEvents.log, { content, object });
|
||||
} else {
|
||||
console.log(`${content} \n ${JSON.stringify(object, null, 2)}`);
|
||||
}
|
||||
console.log(`${content} \n ${JSON.stringify(object, null, 2)}`);
|
||||
}
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ function setFlags() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the menuBarVisbility according to the store value
|
||||
* Update the menuBarVisibility according to the store value
|
||||
*
|
||||
*/
|
||||
function syncMenuBarWithStore() {
|
||||
|
@ -211,9 +211,12 @@
|
||||
<span class="switch__slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="group">
|
||||
<p class="group__title">ListenBrainz</p>
|
||||
<div class="group__option">
|
||||
<div class="group__description">
|
||||
<h4>ListenBrainz</h4>
|
||||
<h4>Enable ListenBrainz</h4>
|
||||
<p>Scrobble your listens directly to ListenBrainz.</p>
|
||||
</div>
|
||||
<label class="switch">
|
||||
|
@ -4,20 +4,25 @@ import fs from "fs";
|
||||
import Player from "mpris-service";
|
||||
import { globalEvents } from "./constants/globalEvents";
|
||||
import { settings } from "./constants/settings";
|
||||
import { statuses } from "./constants/statuses";
|
||||
import { Songwhip } from "./features/songwhip/songwhip";
|
||||
import { ListenBrainz, ListenBrainzStore } from "./features/listenbrainz/listenbrainz";
|
||||
import {
|
||||
ListenBrainz,
|
||||
ListenBrainzConstants,
|
||||
ListenBrainzStore,
|
||||
} from "./features/listenbrainz/listenbrainz";
|
||||
import { Options } from "./models/options";
|
||||
import { downloadFile } from "./scripts/download";
|
||||
import { addHotkey } from "./scripts/hotkeys";
|
||||
import { settingsStore } from "./scripts/settings";
|
||||
import { setTitle } from "./scripts/window-functions";
|
||||
import { StoreData } from "./features/listenbrainz/models/storeData";
|
||||
import { MediaStatus } from "./models/mediaStatus";
|
||||
|
||||
const notificationPath = `${app.getPath("userData")}/notification.jpg`;
|
||||
const appName = "Tidal Hifi";
|
||||
let currentSong = "";
|
||||
let player: Player;
|
||||
let currentPlayStatus = statuses.paused;
|
||||
let currentPlayStatus = MediaStatus.paused;
|
||||
|
||||
const elements = {
|
||||
play: '*[data-test="play"]',
|
||||
@ -106,7 +111,7 @@ const elements = {
|
||||
window.location.href.includes("/playlist/") ||
|
||||
window.location.href.includes("/mix/")
|
||||
) {
|
||||
if (currentPlayStatus === statuses.playing) {
|
||||
if (currentPlayStatus === MediaStatus.playing) {
|
||||
// find the currently playing element from the list (which might be in an album icon), traverse back up to the mediaItem (row) and select the album cell.
|
||||
// document.querySelector("[class^='isPlayingIcon'], [data-test-is-playing='true']").closest('[data-type="mediaItem"]').querySelector('[class^="album"]').textContent
|
||||
const row = window.document.querySelector(this.currentlyPlaying).closest(this.mediaItem);
|
||||
@ -179,7 +184,7 @@ function addCustomCss() {
|
||||
* make sure it returns a number, if not use the default
|
||||
*/
|
||||
function getUpdateFrequency() {
|
||||
const storeValue = settingsStore.get(settings.updateFrequency) as number;
|
||||
const storeValue = settingsStore.get<string, number>(settings.updateFrequency);
|
||||
const defaultValue = 500;
|
||||
|
||||
if (!isNaN(storeValue)) {
|
||||
@ -280,7 +285,7 @@ function handleLogout() {
|
||||
defaultId: 2,
|
||||
})
|
||||
.then((result: { response: number }) => {
|
||||
if (logoutOptions.indexOf("Yes, please") == result.response) {
|
||||
if (logoutOptions.indexOf("Yes, please") === result.response) {
|
||||
for (let i = 0; i < window.localStorage.length; i++) {
|
||||
const key = window.localStorage.key(i);
|
||||
if (key.startsWith("_TIDAL_activeSession")) {
|
||||
@ -322,6 +327,8 @@ function addIPCEventListeners() {
|
||||
case globalEvents.pause:
|
||||
elements.click("pause");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -336,9 +343,9 @@ function getCurrentlyPlayingStatus() {
|
||||
|
||||
// if pause button is visible tidal is playing
|
||||
if (pause) {
|
||||
status = statuses.playing;
|
||||
status = MediaStatus.playing;
|
||||
} else {
|
||||
status = statuses.paused;
|
||||
status = MediaStatus.paused;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
@ -363,27 +370,41 @@ function updateMediaInfo(options: Options, notify: boolean) {
|
||||
if (settingsStore.get(settings.notifications) && notify) {
|
||||
new Notification({ title: options.title, body: options.artists, icon: options.icon }).show();
|
||||
}
|
||||
if (player) {
|
||||
player.metadata = {
|
||||
...player.metadata,
|
||||
...{
|
||||
"xesam:title": options.title,
|
||||
"xesam:artist": [options.artists],
|
||||
"xesam:album": options.album,
|
||||
"mpris:artUrl": options.image,
|
||||
"mpris:length": convertDuration(options.duration) * 1000 * 1000,
|
||||
"mpris:trackid": "/org/mpris/MediaPlayer2/track/" + getTrackID(),
|
||||
},
|
||||
};
|
||||
player.playbackStatus = options.status == statuses.paused ? "Paused" : "Playing";
|
||||
}
|
||||
if (settingsStore.get(settings.ListenBrainz.enabled)) {
|
||||
const data = ListenBrainzStore.get("OldData") as string[];
|
||||
if (data && data[1] !== options.title) {
|
||||
ListenBrainz.scrobble(options.title, options.artists, options.status, convertDuration(options.duration));
|
||||
} else if (!data) {
|
||||
ListenBrainz.scrobble(options.title, options.artists, options.status, convertDuration(options.duration));
|
||||
}
|
||||
updateMpris(options);
|
||||
updateListenBrainz(options);
|
||||
}
|
||||
}
|
||||
|
||||
function updateMpris(options: Options) {
|
||||
if (player) {
|
||||
player.metadata = {
|
||||
...player.metadata,
|
||||
...{
|
||||
"xesam:title": options.title,
|
||||
"xesam:artist": [options.artists],
|
||||
"xesam:album": options.album,
|
||||
"mpris:artUrl": options.image,
|
||||
"mpris:length": convertDuration(options.duration) * 1000 * 1000,
|
||||
"mpris:trackid": "/org/mpris/MediaPlayer2/track/" + getTrackID(),
|
||||
},
|
||||
};
|
||||
player.playbackStatus = options.status === MediaStatus.paused ? "Paused" : "Playing";
|
||||
}
|
||||
}
|
||||
|
||||
function updateListenBrainz(options: Options) {
|
||||
if (settingsStore.get(settings.ListenBrainz.enabled)) {
|
||||
const oldData = ListenBrainzStore.get(ListenBrainzConstants.oldData) as StoreData;
|
||||
if (
|
||||
(!oldData && options.status === MediaStatus.playing) ||
|
||||
(oldData && oldData.title !== options.title)
|
||||
) {
|
||||
ListenBrainz.scrobble(
|
||||
options.title,
|
||||
options.artists,
|
||||
options.status,
|
||||
convertDuration(options.duration)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { BrowserWindow, dialog } from "electron";
|
||||
import express, { Response } from "express";
|
||||
import fs from "fs";
|
||||
import { settings } from "../constants/settings";
|
||||
import { MediaStatus } from "../models/mediaStatus";
|
||||
import { globalEvents } from "./../constants/globalEvents";
|
||||
import { statuses } from "./../constants/statuses";
|
||||
import { mediaInfo } from "./mediaInfo";
|
||||
import { settingsStore } from "./settings";
|
||||
import { settings } from "../constants/settings";
|
||||
|
||||
/**
|
||||
* Function to enable tidal-hifi's express api
|
||||
@ -44,7 +44,7 @@ export const startExpress = (mainWindow: BrowserWindow) => {
|
||||
expressApp.get("/next", (req, res) => handleGlobalEvent(res, globalEvents.next));
|
||||
expressApp.get("/previous", (req, res) => handleGlobalEvent(res, globalEvents.previous));
|
||||
expressApp.get("/playpause", (req, res) => {
|
||||
if (mediaInfo.status == statuses.playing) {
|
||||
if (mediaInfo.status === MediaStatus.playing) {
|
||||
handleGlobalEvent(res, globalEvents.pause);
|
||||
} else {
|
||||
handleGlobalEvent(res, globalEvents.play);
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { MediaInfo } from "../models/mediaInfo";
|
||||
import { statuses } from "./../constants/statuses";
|
||||
import { MediaStatus } from "../models/mediaStatus";
|
||||
|
||||
export const mediaInfo = {
|
||||
title: "",
|
||||
artists: "",
|
||||
album: "",
|
||||
icon: "",
|
||||
status: statuses.paused,
|
||||
status: MediaStatus.paused as string,
|
||||
url: "",
|
||||
current: "",
|
||||
duration: "",
|
||||
|
Loading…
Reference in New Issue
Block a user