mirror of
https://github.com/Mastermindzh/tidal-hifi.git
synced 2025-09-14 23:59:18 +02:00
Compare commits
46 Commits
5.16.0
...
16f98f4f1b
Author | SHA1 | Date | |
---|---|---|---|
|
16f98f4f1b | ||
|
256832dab6 | ||
|
977e50faa0 | ||
|
294701ec5f | ||
aaf7a1b662 | |||
a44bf331e4 | |||
755be0ee30 | |||
|
73051716e7 | ||
|
9d736b2bd9 | ||
f4d4b1a1df | |||
|
0c27c815f5 | ||
|
a3aa45a96b | ||
ae699887b2 | |||
a8c635932f | |||
66d0d004bf | |||
d1e0321058 | |||
2ab5a556ab | |||
baf719fc60 | |||
15c8f6a418 | |||
f73521e2e5 | |||
|
0f5e00c4df | ||
|
974877ea4f | ||
|
6be5774001 | ||
|
f96cf2e8da | ||
|
f832bd2712 | ||
|
cb8667bd41 | ||
91156e5936 | |||
|
57e9a2dcb8 | ||
|
21edcd6ad5 | ||
|
3dc42eceb0 | ||
|
b1830f5684 | ||
|
1a0c15e17f | ||
6e59d59a1d | |||
|
fab7497311 | ||
fccbcc77ea | |||
|
041c19fb52 | ||
e36c562afa | |||
1589aa5251 | |||
8c672dd1eb | |||
|
3769550f24 | ||
|
5a06b6c53f | ||
|
1d4ef66d27 | ||
e02f07401b | |||
118f92e75a | |||
|
788d302ce8 | ||
|
cc09f35b49 |
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@@ -14,7 +14,6 @@
|
|||||||
"rescrobbler",
|
"rescrobbler",
|
||||||
"scrobble",
|
"scrobble",
|
||||||
"scrobbling",
|
"scrobbling",
|
||||||
"Songwhip",
|
|
||||||
"trackid",
|
"trackid",
|
||||||
"tracklist",
|
"tracklist",
|
||||||
"widevine",
|
"widevine",
|
||||||
|
@@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [5.17.0]
|
||||||
|
|
||||||
|
- Added an option to disable the dynamic title and set it to a static one, [#491](https://github.com/Mastermindzh/tidal-hifi/pull/491)
|
||||||
|
- Discord integration now says "Listening to" instead of "playing" [#488](https://github.com/Mastermindzh/tidal-hifi/pull/488) && [#454](https://github.com/Mastermindzh/tidal-hifi/pull/454)
|
||||||
|
- Fixed several element names in the dom scraper
|
||||||
|
- Removed the Songwhip (they shut down) integration and replaced it with TIDAL's universal link system
|
||||||
|
|
||||||
## [5.16.0]
|
## [5.16.0]
|
||||||
|
|
||||||
- Fix issue #449 Discord RPC stuck on "Browsing Tidal".
|
- Fix issue #449 Discord RPC stuck on "Browsing Tidal".
|
||||||
|
@@ -48,7 +48,6 @@ The web version of [listen.tidal.com](https://listen.tidal.com) running in elect
|
|||||||
- AlbumArt in integrations ([best-effort](https://github.com/Mastermindzh/tidal-hifi/pull/88#pullrequestreview-840814847))
|
- AlbumArt in integrations ([best-effort](https://github.com/Mastermindzh/tidal-hifi/pull/88#pullrequestreview-840814847))
|
||||||
- Custom [integrations](#integrations)
|
- Custom [integrations](#integrations)
|
||||||
- [ListenBrainz](https://listenbrainz.org/?redirect=false) integration
|
- [ListenBrainz](https://listenbrainz.org/?redirect=false) integration
|
||||||
- Songwhip.com integration (hotkey `ctrl + w`)
|
|
||||||
- Discord RPC integration (showing "now listening", "Browsing", etc)
|
- Discord RPC integration (showing "now listening", "Browsing", etc)
|
||||||
- Flatpak version only works if both Discord and Tidal-HiFi are flatpaks
|
- Flatpak version only works if both Discord and Tidal-HiFi are flatpaks
|
||||||
- MPRIS integration
|
- MPRIS integration
|
||||||
|
796
package-lock.json
generated
796
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
13
package.json
13
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "tidal-hifi",
|
"name": "tidal-hifi",
|
||||||
"version": "5.16.0",
|
"version": "5.17.0",
|
||||||
"description": "Tidal on Electron with widevine(hifi) support",
|
"description": "Tidal on Electron with widevine(hifi) support",
|
||||||
"main": "ts-dist/main.js",
|
"main": "ts-dist/main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -42,24 +42,21 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@electron/remote": "^2.1.2",
|
"@electron/remote": "^2.1.2",
|
||||||
"@types/swagger-jsdoc": "^6.0.4",
|
"@types/swagger-jsdoc": "^6.0.4",
|
||||||
"axios": "^1.7.2",
|
"@xhayper/discord-rpc": "^1.2.0",
|
||||||
|
"axios": "^1.7.9",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"discord-rpc": "^4.0.1",
|
|
||||||
"electron-store": "^8.2.0",
|
"electron-store": "^8.2.0",
|
||||||
"express": "^4.19.2",
|
"express": "^4.21.2",
|
||||||
"hotkeys-js": "^3.13.7",
|
"hotkeys-js": "^3.13.7",
|
||||||
"mpris-service": "^2.1.2",
|
"mpris-service": "^2.1.2",
|
||||||
"request": "^2.88.2",
|
"sass": "^1.79.4",
|
||||||
"sass": "^1.77.8",
|
|
||||||
"swagger-ui-express": "^5.0.1"
|
"swagger-ui-express": "^5.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@mastermindzh/prettier-config": "^1.0.0",
|
"@mastermindzh/prettier-config": "^1.0.0",
|
||||||
"@types/cors": "^2.8.17",
|
"@types/cors": "^2.8.17",
|
||||||
"@types/discord-rpc": "^4.0.8",
|
|
||||||
"@types/express": "^4.17.21",
|
"@types/express": "^4.17.21",
|
||||||
"@types/node": "^20.14.10",
|
"@types/node": "^20.14.10",
|
||||||
"@types/request": "^2.48.12",
|
|
||||||
"@types/swagger-ui-express": "^4.1.6",
|
"@types/swagger-ui-express": "^4.1.6",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.16.0",
|
"@typescript-eslint/eslint-plugin": "^7.16.0",
|
||||||
"@typescript-eslint/parser": "^7.15.0",
|
"@typescript-eslint/parser": "^7.15.0",
|
||||||
|
@@ -16,8 +16,9 @@
|
|||||||
search: '[class^="searchField"]',
|
search: '[class^="searchField"]',
|
||||||
shuffle: '*[data-test="shuffle"]',
|
shuffle: '*[data-test="shuffle"]',
|
||||||
repeat: '*[data-test="repeat"]',
|
repeat: '*[data-test="repeat"]',
|
||||||
account: '*[class^="profileOptions"]',
|
account: '*[data-test^="profile-image-button"]',
|
||||||
settings: '*[data-test^="open-settings"]',
|
settings: '*[data-test^="sidebar-menu-button"]',
|
||||||
|
openSettings: '*[data-test^="open-settings"]',
|
||||||
media: '*[data-test="current-media-imagery"]',
|
media: '*[data-test="current-media-imagery"]',
|
||||||
image: "img",
|
image: "img",
|
||||||
current: '*[data-test="current-time"]',
|
current: '*[data-test="current-time"]',
|
||||||
|
@@ -10,9 +10,11 @@ export const globalEvents = {
|
|||||||
showSettings: "showSettings",
|
showSettings: "showSettings",
|
||||||
storeChanged: "storeChanged",
|
storeChanged: "storeChanged",
|
||||||
error: "error",
|
error: "error",
|
||||||
whip: "whip",
|
getUniversalLink: "getUniversalLink",
|
||||||
log: "log",
|
log: "log",
|
||||||
toggleFavorite: "toggleFavorite",
|
toggleFavorite: "toggleFavorite",
|
||||||
toggleShuffle: "toggleShuffle",
|
toggleShuffle: "toggleShuffle",
|
||||||
toggleRepeat: "toggleRepeat",
|
toggleRepeat: "toggleRepeat",
|
||||||
|
axios: "axios",
|
||||||
|
axiosReply: "axiosReply",
|
||||||
};
|
};
|
||||||
|
@@ -55,6 +55,7 @@ export const settings = {
|
|||||||
singleInstance: "singleInstance",
|
singleInstance: "singleInstance",
|
||||||
skipArtists: "skipArtists",
|
skipArtists: "skipArtists",
|
||||||
skippedArtists: "skippedArtists",
|
skippedArtists: "skippedArtists",
|
||||||
|
staticWindowTitle: "staticWindowTitle",
|
||||||
theme: "theme",
|
theme: "theme",
|
||||||
trayIcon: "trayIcon",
|
trayIcon: "trayIcon",
|
||||||
updateFrequency: "updateFrequency",
|
updateFrequency: "updateFrequency",
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
"openapi": "3.1.0",
|
"openapi": "3.1.0",
|
||||||
"info": {
|
"info": {
|
||||||
"title": "TIDAL Hi-Fi API",
|
"title": "TIDAL Hi-Fi API",
|
||||||
"version": "5.16.0",
|
"version": "5.17.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"license": {
|
"license": {
|
||||||
"name": "MIT",
|
"name": "MIT",
|
||||||
|
10
src/features/sharingService/sharingService.ts
Normal file
10
src/features/sharingService/sharingService.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
export class SharingService {
|
||||||
|
/**
|
||||||
|
* Retrieve the universal link given a regular track link
|
||||||
|
* @param currentUrl
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public static getUniversalLink(currentUrl: string): string {
|
||||||
|
return `${currentUrl}?u`;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,21 +0,0 @@
|
|||||||
import { ServiceLinks } from "./ServiceLinks";
|
|
||||||
|
|
||||||
export interface Artist {
|
|
||||||
type: string;
|
|
||||||
id: number;
|
|
||||||
path: string;
|
|
||||||
name: string;
|
|
||||||
sourceUrl: string;
|
|
||||||
sourceCountry: string;
|
|
||||||
url: string;
|
|
||||||
image: string;
|
|
||||||
createdAt: string;
|
|
||||||
updatedAt: string;
|
|
||||||
refreshedAt: string;
|
|
||||||
serviceIds: { [key: string]: string };
|
|
||||||
orchardId: string;
|
|
||||||
spotifyId: string;
|
|
||||||
links: { [key: string]: ServiceLinks[] };
|
|
||||||
linksCountries: string[];
|
|
||||||
description: string;
|
|
||||||
}
|
|
@@ -1,4 +0,0 @@
|
|||||||
export interface ServiceLinks {
|
|
||||||
link: string;
|
|
||||||
countries: string[];
|
|
||||||
}
|
|
@@ -1,27 +0,0 @@
|
|||||||
import { Artist } from "./Artist";
|
|
||||||
import { ServiceLinks } from "./ServiceLinks";
|
|
||||||
|
|
||||||
export interface WhippedResult {
|
|
||||||
status: string;
|
|
||||||
data: {
|
|
||||||
item: {
|
|
||||||
type: string;
|
|
||||||
id: number;
|
|
||||||
path: string;
|
|
||||||
name: string;
|
|
||||||
url: string;
|
|
||||||
sourceUrl: string;
|
|
||||||
sourceCountry: string;
|
|
||||||
releaseDate: string;
|
|
||||||
createdAt: string;
|
|
||||||
updatedAt: string;
|
|
||||||
refreshedAt: string;
|
|
||||||
image: string;
|
|
||||||
isrc: string;
|
|
||||||
isExplicit: boolean;
|
|
||||||
links: { [key: string]: ServiceLinks[] };
|
|
||||||
linksCountries: string[];
|
|
||||||
artists: Artist[];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
@@ -1,32 +0,0 @@
|
|||||||
import { WhippedResult } from "./models/whip";
|
|
||||||
import axios from "axios";
|
|
||||||
|
|
||||||
export class Songwhip {
|
|
||||||
/**
|
|
||||||
* Call the songwhip API and create a shareable songwhip page
|
|
||||||
* @param currentUrl
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
public static async whip(currentUrl: string): Promise<WhippedResult> {
|
|
||||||
try {
|
|
||||||
const response = await axios.post("https://songwhip.com/api/songwhip/create", {
|
|
||||||
url: currentUrl,
|
|
||||||
// doesn't actually matter.. returns everything the same way anyway
|
|
||||||
country: "NL",
|
|
||||||
});
|
|
||||||
|
|
||||||
return response.data;
|
|
||||||
} catch (error) {
|
|
||||||
console.log(JSON.stringify(error));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transform a songwhip response into a shareable url
|
|
||||||
* @param response
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
public static getWhipUrl(response: WhippedResult) {
|
|
||||||
return `https://songwhip.com${response.data.item.url}`;
|
|
||||||
}
|
|
||||||
}
|
|
17
src/main.ts
17
src/main.ts
@@ -10,7 +10,7 @@ import {
|
|||||||
releaseInhibitorIfActive,
|
releaseInhibitorIfActive,
|
||||||
} from "./features/idleInhibitor/idleInhibitor";
|
} from "./features/idleInhibitor/idleInhibitor";
|
||||||
import { Logger } from "./features/logger";
|
import { Logger } from "./features/logger";
|
||||||
import { Songwhip } from "./features/songwhip/songwhip";
|
import { SharingService } from "./features/sharingService/sharingService";
|
||||||
import { MediaInfo } from "./models/mediaInfo";
|
import { MediaInfo } from "./models/mediaInfo";
|
||||||
import { MediaStatus } from "./models/mediaStatus";
|
import { MediaStatus } from "./models/mediaStatus";
|
||||||
import { initRPC, rpc, unRPC } from "./scripts/discord";
|
import { initRPC, rpc, unRPC } from "./scripts/discord";
|
||||||
@@ -24,6 +24,7 @@ import {
|
|||||||
showSettingsWindow,
|
showSettingsWindow,
|
||||||
} from "./scripts/settings";
|
} from "./scripts/settings";
|
||||||
import { addTray, refreshTray } from "./scripts/tray";
|
import { addTray, refreshTray } from "./scripts/tray";
|
||||||
|
import { downloadFile } from "./scripts/download";
|
||||||
let mainInhibitorId = -1;
|
let mainInhibitorId = -1;
|
||||||
|
|
||||||
initialize();
|
initialize();
|
||||||
@@ -250,8 +251,18 @@ ipcMain.on(globalEvents.error, (event) => {
|
|||||||
console.log(event);
|
console.log(event);
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle(globalEvents.whip, async (event, url) => {
|
ipcMain.on(globalEvents.axios, (event, fileUrl: string, targetPath: string) => {
|
||||||
return Songwhip.whip(url);
|
const download = downloadFile(fileUrl, targetPath);
|
||||||
|
download.then(() => {
|
||||||
|
event.reply(globalEvents.axiosReply, fileUrl, targetPath, false);
|
||||||
|
}).catch(() => {
|
||||||
|
// don't send error information, it isn't used anyways
|
||||||
|
event.reply(globalEvents.axiosReply, fileUrl, targetPath, true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle(globalEvents.getUniversalLink, async (event, url) => {
|
||||||
|
return SharingService.getUniversalLink(url);
|
||||||
});
|
});
|
||||||
|
|
||||||
Logger.watch(ipcMain);
|
Logger.watch(ipcMain);
|
||||||
|
@@ -46,6 +46,7 @@ let adBlock: HTMLInputElement,
|
|||||||
singleInstance: HTMLInputElement,
|
singleInstance: HTMLInputElement,
|
||||||
skipArtists: HTMLInputElement,
|
skipArtists: HTMLInputElement,
|
||||||
skippedArtists: HTMLInputElement,
|
skippedArtists: HTMLInputElement,
|
||||||
|
staticWindowTitle: HTMLInputElement,
|
||||||
theme: HTMLSelectElement,
|
theme: HTMLSelectElement,
|
||||||
trayIcon: HTMLInputElement,
|
trayIcon: HTMLInputElement,
|
||||||
updateFrequency: HTMLInputElement,
|
updateFrequency: HTMLInputElement,
|
||||||
@@ -140,8 +141,9 @@ function refreshSettings() {
|
|||||||
port.value = settingsStore.get(settings.apiSettings.port);
|
port.value = settingsStore.get(settings.apiSettings.port);
|
||||||
singleInstance.checked = settingsStore.get(settings.singleInstance);
|
singleInstance.checked = settingsStore.get(settings.singleInstance);
|
||||||
skipArtists.checked = settingsStore.get(settings.skipArtists);
|
skipArtists.checked = settingsStore.get(settings.skipArtists);
|
||||||
theme.value = settingsStore.get(settings.theme);
|
|
||||||
skippedArtists.value = settingsStore.get<string, string[]>(settings.skippedArtists).join("\n");
|
skippedArtists.value = settingsStore.get<string, string[]>(settings.skippedArtists).join("\n");
|
||||||
|
staticWindowTitle.checked = settingsStore.get(settings.staticWindowTitle);
|
||||||
|
theme.value = settingsStore.get(settings.theme);
|
||||||
trayIcon.checked = settingsStore.get(settings.trayIcon);
|
trayIcon.checked = settingsStore.get(settings.trayIcon);
|
||||||
updateFrequency.value = settingsStore.get(settings.updateFrequency);
|
updateFrequency.value = settingsStore.get(settings.updateFrequency);
|
||||||
enableListenBrainz.checked = settingsStore.get(settings.ListenBrainz.enabled);
|
enableListenBrainz.checked = settingsStore.get(settings.ListenBrainz.enabled);
|
||||||
@@ -261,6 +263,7 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||||||
trayIcon = get("trayIcon");
|
trayIcon = get("trayIcon");
|
||||||
skipArtists = get("skipArtists");
|
skipArtists = get("skipArtists");
|
||||||
skippedArtists = get("skippedArtists");
|
skippedArtists = get("skippedArtists");
|
||||||
|
staticWindowTitle = get("staticWindowTitle");
|
||||||
singleInstance = get("singleInstance");
|
singleInstance = get("singleInstance");
|
||||||
updateFrequency = get("updateFrequency");
|
updateFrequency = get("updateFrequency");
|
||||||
enableListenBrainz = get("enableListenBrainz");
|
enableListenBrainz = get("enableListenBrainz");
|
||||||
@@ -295,6 +298,7 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||||||
addInputListener(port, settings.apiSettings.port);
|
addInputListener(port, settings.apiSettings.port);
|
||||||
addInputListener(skipArtists, settings.skipArtists);
|
addInputListener(skipArtists, settings.skipArtists);
|
||||||
addTextAreaListener(skippedArtists, settings.skippedArtists);
|
addTextAreaListener(skippedArtists, settings.skippedArtists);
|
||||||
|
addInputListener(staticWindowTitle, settings.staticWindowTitle);
|
||||||
addInputListener(singleInstance, settings.singleInstance);
|
addInputListener(singleInstance, settings.singleInstance);
|
||||||
addSelectListener(theme, settings.theme);
|
addSelectListener(theme, settings.theme);
|
||||||
addInputListener(trayIcon, settings.trayIcon);
|
addInputListener(trayIcon, settings.trayIcon);
|
||||||
|
@@ -106,6 +106,16 @@
|
|||||||
<span class="switch__slider"></span>
|
<span class="switch__slider"></span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="group__option">
|
||||||
|
<div class="group__description">
|
||||||
|
<h4>Static Window Title</h4>
|
||||||
|
<p>Makes the window title "TIDAL Hi-Fi" instead of changing to the currently playing song.</p>
|
||||||
|
</div>
|
||||||
|
<label class="switch">
|
||||||
|
<input id="staticWindowTitle" type="checkbox" />
|
||||||
|
<span class="switch__slider"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<div class="group__option">
|
<div class="group__option">
|
||||||
<div class="group__description">
|
<div class="group__description">
|
||||||
<h4>Minimize on Close</h4>
|
<h4>Minimize on Close</h4>
|
||||||
@@ -468,7 +478,7 @@
|
|||||||
<h4>TIDAL Hi-Fi</h4>
|
<h4>TIDAL Hi-Fi</h4>
|
||||||
<div class="about-section__version">
|
<div class="about-section__version">
|
||||||
<a target="_blank" rel="noopener"
|
<a target="_blank" rel="noopener"
|
||||||
href="https://github.com/Mastermindzh/tidal-hifi/releases/tag/5.16.0">5.16.0</a>
|
href="https://github.com/Mastermindzh/tidal-hifi/releases/tag/5.17.0">5.17.0</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="about-section__links">
|
<div class="about-section__links">
|
||||||
<a target="_blank" rel="noopener" href="https://github.com/mastermindzh/tidal-hifi/"
|
<a target="_blank" rel="noopener" href="https://github.com/mastermindzh/tidal-hifi/"
|
||||||
@@ -491,4 +501,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
@@ -10,13 +10,13 @@ import {
|
|||||||
} from "./features/listenbrainz/listenbrainz";
|
} from "./features/listenbrainz/listenbrainz";
|
||||||
import { StoreData } from "./features/listenbrainz/models/storeData";
|
import { StoreData } from "./features/listenbrainz/models/storeData";
|
||||||
import { Logger } from "./features/logger";
|
import { Logger } from "./features/logger";
|
||||||
import { Songwhip } from "./features/songwhip/songwhip";
|
import { SharingService } from "./features/sharingService/sharingService";
|
||||||
import { addCustomCss } from "./features/theming/theming";
|
import { addCustomCss } from "./features/theming/theming";
|
||||||
import { convertDurationToSeconds } from "./features/time/parse";
|
import { convertDurationToSeconds } from "./features/time/parse";
|
||||||
import { MediaInfo } from "./models/mediaInfo";
|
import { MediaInfo } from "./models/mediaInfo";
|
||||||
import { MediaStatus } from "./models/mediaStatus";
|
import { MediaStatus } from "./models/mediaStatus";
|
||||||
import { RepeatState } from "./models/repeatState";
|
import { RepeatState } from "./models/repeatState";
|
||||||
import { downloadFile } from "./scripts/download";
|
import { downloadFile } from "./scripts/downloadPreload";
|
||||||
import { addHotkey } from "./scripts/hotkeys";
|
import { addHotkey } from "./scripts/hotkeys";
|
||||||
import { ObjectToDotNotation } from "./scripts/objectUtilities";
|
import { ObjectToDotNotation } from "./scripts/objectUtilities";
|
||||||
import { settingsStore } from "./scripts/settings";
|
import { settingsStore } from "./scripts/settings";
|
||||||
@@ -48,8 +48,9 @@ const elements = {
|
|||||||
search: '[class^="searchField"]',
|
search: '[class^="searchField"]',
|
||||||
shuffle: '*[data-test="shuffle"]',
|
shuffle: '*[data-test="shuffle"]',
|
||||||
repeat: '*[data-test="repeat"]',
|
repeat: '*[data-test="repeat"]',
|
||||||
account: '*[class^="profileOptions"]',
|
account: '*[data-test^="profile-image-button"]',
|
||||||
settings: '*[data-test^="open-settings"]',
|
settings: '*[data-test^="sidebar-menu-button"]',
|
||||||
|
openSettings: '*[data-test^="open-settings"]',
|
||||||
media: '*[data-test="current-media-imagery"]',
|
media: '*[data-test="current-media-imagery"]',
|
||||||
image: "img",
|
image: "img",
|
||||||
current: '*[data-test="current-time"]',
|
current: '*[data-test="current-time"]',
|
||||||
@@ -220,9 +221,9 @@ ListenBrainzStore.clear();
|
|||||||
function addHotKeys() {
|
function addHotKeys() {
|
||||||
if (settingsStore.get(settings.enableCustomHotkeys)) {
|
if (settingsStore.get(settings.enableCustomHotkeys)) {
|
||||||
addHotkey("Control+p", function () {
|
addHotkey("Control+p", function () {
|
||||||
elements.click("account");
|
elements.click("settings");
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
elements.click("settings");
|
elements.click("openSettings");
|
||||||
}, 100);
|
}, 100);
|
||||||
});
|
});
|
||||||
addHotkey("Control+l", function () {
|
addHotkey("Control+l", function () {
|
||||||
@@ -254,11 +255,10 @@ function addHotKeys() {
|
|||||||
elements.click("repeat");
|
elements.click("repeat");
|
||||||
});
|
});
|
||||||
addHotkey("control+w", async function () {
|
addHotkey("control+w", async function () {
|
||||||
const result = await ipcRenderer.invoke(globalEvents.whip, getTrackURL());
|
const url = SharingService.getUniversalLink(getTrackURL());
|
||||||
const url = Songwhip.getWhipUrl(result);
|
|
||||||
clipboard.writeText(url);
|
clipboard.writeText(url);
|
||||||
new Notification({
|
new Notification({
|
||||||
title: `Successfully whipped: `,
|
title: `Universal link generated: `,
|
||||||
body: `URL copied to clipboard: ${url}`,
|
body: `URL copied to clipboard: ${url}`,
|
||||||
}).show();
|
}).show();
|
||||||
});
|
});
|
||||||
@@ -550,6 +550,7 @@ setInterval(function () {
|
|||||||
const artistsArray = elements.getArtistsArray();
|
const artistsArray = elements.getArtistsArray();
|
||||||
const artistsString = elements.getArtistsString(artistsArray);
|
const artistsString = elements.getArtistsString(artistsArray);
|
||||||
const songDashArtistTitle = `${title} - ${artistsString}`;
|
const songDashArtistTitle = `${title} - ${artistsString}`;
|
||||||
|
const staticTitle = "TIDAL Hi-Fi";
|
||||||
const titleOrArtistsChanged = currentSong !== songDashArtistTitle;
|
const titleOrArtistsChanged = currentSong !== songDashArtistTitle;
|
||||||
const current = elements.getText("current");
|
const current = elements.getText("current");
|
||||||
const currentStatus = getCurrentlyPlayingStatus();
|
const currentStatus = getCurrentlyPlayingStatus();
|
||||||
@@ -594,7 +595,9 @@ setInterval(function () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// update title, url and play info with new info
|
// update title, url and play info with new info
|
||||||
setTitle(songDashArtistTitle);
|
settingsStore.get(settings.staticWindowTitle)
|
||||||
|
? setTitle(staticTitle)
|
||||||
|
: setTitle(songDashArtistTitle);
|
||||||
getTrackURL();
|
getTrackURL();
|
||||||
currentSong = songDashArtistTitle;
|
currentSong = songDashArtistTitle;
|
||||||
currentPlayStatus = currentStatus;
|
currentPlayStatus = currentStatus;
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { Client, Presence } from "discord-rpc";
|
import { Client, SetActivity } from "@xhayper/discord-rpc";
|
||||||
import { app, ipcMain } from "electron";
|
import { app, ipcMain } from "electron";
|
||||||
import { globalEvents } from "../constants/globalEvents";
|
import { globalEvents } from "../constants/globalEvents";
|
||||||
import { settings } from "../constants/settings";
|
import { settings } from "../constants/settings";
|
||||||
@@ -12,6 +12,10 @@ const clientId = "833617820704440341";
|
|||||||
|
|
||||||
export let rpc: Client;
|
export let rpc: Client;
|
||||||
|
|
||||||
|
const ACTIVITY_LISTENING = 2;
|
||||||
|
const MAX_RETRIES = 5;
|
||||||
|
const RETRY_DELAY = 10000;
|
||||||
|
|
||||||
const observer = () => {
|
const observer = () => {
|
||||||
if (rpc) {
|
if (rpc) {
|
||||||
updateActivity();
|
updateActivity();
|
||||||
@@ -22,19 +26,20 @@ const defaultPresence = {
|
|||||||
largeImageKey: "tidal-hifi-icon",
|
largeImageKey: "tidal-hifi-icon",
|
||||||
largeImageText: `TIDAL Hi-Fi ${app.getVersion()}`,
|
largeImageText: `TIDAL Hi-Fi ${app.getVersion()}`,
|
||||||
instance: false,
|
instance: false,
|
||||||
|
type: ACTIVITY_LISTENING
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateActivity = () => {
|
const updateActivity = () => {
|
||||||
const showIdle = settingsStore.get<string, boolean>(settings.discord.showIdle) ?? true;
|
const showIdle = settingsStore.get<string, boolean>(settings.discord.showIdle) ?? true;
|
||||||
if (mediaInfo.status === MediaStatus.paused && !showIdle) {
|
if (mediaInfo.status === MediaStatus.paused && !showIdle) {
|
||||||
rpc.clearActivity();
|
rpc.user?.clearActivity();
|
||||||
} else {
|
} else {
|
||||||
rpc.setActivity(getActivity());
|
rpc.user?.setActivity(getActivity());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getActivity = (): Presence => {
|
const getActivity = (): SetActivity => {
|
||||||
const presence: Presence = { ...defaultPresence };
|
const presence: SetActivity = { ...defaultPresence };
|
||||||
|
|
||||||
if (mediaInfo.status === MediaStatus.paused) {
|
if (mediaInfo.status === MediaStatus.paused) {
|
||||||
presence.details =
|
presence.details =
|
||||||
@@ -50,6 +55,7 @@ const getActivity = (): Presence => {
|
|||||||
settingsStore.get<string, string>(settings.discord.usingText) ?? "Playing media on TIDAL";
|
settingsStore.get<string, string>(settings.discord.usingText) ?? "Playing media on TIDAL";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return presence;
|
return presence;
|
||||||
|
|
||||||
function getFromStore() {
|
function getFromStore() {
|
||||||
@@ -97,11 +103,29 @@ const getActivity = (): Presence => {
|
|||||||
if (includeTimestamps) {
|
if (includeTimestamps) {
|
||||||
const currentSeconds = convertDurationToSeconds(mediaInfo.current);
|
const currentSeconds = convertDurationToSeconds(mediaInfo.current);
|
||||||
const durationSeconds = convertDurationToSeconds(mediaInfo.duration);
|
const durationSeconds = convertDurationToSeconds(mediaInfo.duration);
|
||||||
const date = new Date();
|
const now = Math.trunc((Date.now() + 500) / 1000);
|
||||||
const now = (date.getTime() / 1000) | 0;
|
presence.startTimestamp = now - currentSeconds;
|
||||||
const remaining = date.setSeconds(date.getSeconds() + (durationSeconds - currentSeconds));
|
presence.endTimestamp = presence.startTimestamp + durationSeconds;
|
||||||
presence.startTimestamp = now;
|
}
|
||||||
presence.endTimestamp = remaining;
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to login to RPC and retry if it errors
|
||||||
|
* @param retryCount Max retry count
|
||||||
|
*/
|
||||||
|
const connectWithRetry = async (retryCount = 0) => {
|
||||||
|
try {
|
||||||
|
await rpc.login();
|
||||||
|
Logger.log('Connected to Discord');
|
||||||
|
rpc.on("ready", updateActivity);
|
||||||
|
Object.values(globalEvents).forEach(event => ipcMain.on(event, observer));
|
||||||
|
} catch (error) {
|
||||||
|
if (retryCount < MAX_RETRIES) {
|
||||||
|
Logger.log(`Failed to connect to Discord, retrying in ${RETRY_DELAY/1000} seconds... (Attempt ${retryCount + 1}/${MAX_RETRIES})`);
|
||||||
|
setTimeout(() => connectWithRetry(retryCount + 1), RETRY_DELAY);
|
||||||
|
} else {
|
||||||
|
Logger.log('Failed to connect to Discord after maximum retry attempts');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -110,22 +134,8 @@ const getActivity = (): Presence => {
|
|||||||
* Set up the discord rpc and listen on globalEvents.updateInfo
|
* Set up the discord rpc and listen on globalEvents.updateInfo
|
||||||
*/
|
*/
|
||||||
export const initRPC = () => {
|
export const initRPC = () => {
|
||||||
rpc = new Client({ transport: "ipc" });
|
rpc = new Client({ transport: {type: "ipc"}, clientId });
|
||||||
rpc.login({ clientId }).then(
|
connectWithRetry();
|
||||||
() => {
|
|
||||||
rpc.on("ready", () => {
|
|
||||||
updateActivity();
|
|
||||||
});
|
|
||||||
|
|
||||||
const { updateInfo, play, pause, playPause } = globalEvents;
|
|
||||||
[updateInfo, play, pause, playPause].forEach((status) => {
|
|
||||||
ipcMain.on(status, observer);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
Logger.log("Can't connect to Discord, is it running?");
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -133,7 +143,7 @@ export const initRPC = () => {
|
|||||||
*/
|
*/
|
||||||
export const unRPC = () => {
|
export const unRPC = () => {
|
||||||
if (rpc) {
|
if (rpc) {
|
||||||
rpc.clearActivity();
|
rpc.user?.clearActivity();
|
||||||
rpc.destroy();
|
rpc.destroy();
|
||||||
rpc = null;
|
rpc = null;
|
||||||
ipcMain.removeListener(globalEvents.updateInfo, observer);
|
ipcMain.removeListener(globalEvents.updateInfo, observer);
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import request from "request";
|
import axios from "axios";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* download and save a file
|
* download and save a file
|
||||||
@@ -8,16 +8,20 @@ import request from "request";
|
|||||||
*/
|
*/
|
||||||
export const downloadFile = function (fileUrl: string, targetPath: string) {
|
export const downloadFile = function (fileUrl: string, targetPath: string) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const req = request({
|
axios
|
||||||
method: "GET",
|
.get(fileUrl, {
|
||||||
uri: fileUrl,
|
responseType: "stream",
|
||||||
});
|
})
|
||||||
|
.then((res) => {
|
||||||
|
const out = fs.createWriteStream(targetPath);
|
||||||
|
|
||||||
const out = fs.createWriteStream(targetPath);
|
res.data.pipe(out);
|
||||||
req.pipe(out);
|
|
||||||
|
|
||||||
req.on("end", resolve);
|
out.on("finish", resolve);
|
||||||
|
|
||||||
req.on("error", reject);
|
out.on("error", reject);
|
||||||
|
|
||||||
|
res.data.on("error", reject);
|
||||||
|
}).catch(reject);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
23
src/scripts/downloadPreload.ts
Normal file
23
src/scripts/downloadPreload.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { ipcRenderer } from "electron";
|
||||||
|
import { globalEvents } from "../constants/globalEvents";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* download and save a file (renderer version)
|
||||||
|
* @param {string} fileUrl url to download
|
||||||
|
* @param {string} targetPath path to save it at
|
||||||
|
*/
|
||||||
|
export const downloadFile = function (fileUrl: string, targetPath: string) {
|
||||||
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
const handler = (event: Electron.IpcRendererEvent, newFileUrl: string, newTargetPath: string, error: boolean) => {
|
||||||
|
// it's possible for 2 requests to be running at the same time, so make sure it is the right one
|
||||||
|
// if there is 2 requests with the same fileUrl and targetPath, then it doesn't matter which one we intercept because the data is the same
|
||||||
|
if (fileUrl === newFileUrl && targetPath === newTargetPath) {
|
||||||
|
ipcRenderer.removeListener(globalEvents.axiosReply, handler);
|
||||||
|
if (error) reject();
|
||||||
|
else resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ipcRenderer.on(globalEvents.axiosReply, handler);
|
||||||
|
ipcRenderer.send(globalEvents.axios, fileUrl, targetPath);
|
||||||
|
});
|
||||||
|
};
|
@@ -69,6 +69,7 @@ export const settingsStore = new Store({
|
|||||||
singleInstance: true,
|
singleInstance: true,
|
||||||
skipArtists: false,
|
skipArtists: false,
|
||||||
skippedArtists: [""],
|
skippedArtists: [""],
|
||||||
|
staticWindowTitle: false,
|
||||||
theme: "none",
|
theme: "none",
|
||||||
trayIcon: true,
|
trayIcon: true,
|
||||||
updateFrequency: 500,
|
updateFrequency: 500,
|
||||||
|
Reference in New Issue
Block a user