Compare commits

..

No commits in common. "master" and "5.15.0" have entirely different histories.

23 changed files with 1478 additions and 3660 deletions

View File

@ -24,27 +24,23 @@ jobs:
node-version: 22.4 node-version: 22.4
- run: npm install - run: npm install
- run: npm run build - run: npm run build
# - uses: actions/upload-artifact@master
# with:
# name: linux-builds
# path: dist/
# build_on_mac: build_on_mac:
# runs-on: macos-latest runs-on: macos-latest
# steps: steps:
# - uses: actions/checkout@master - uses: actions/checkout@master
# - uses: actions/setup-node@master - uses: actions/setup-node@master
# with: with:
# node-version: 22.4 node-version: 22.4
# - run: npm install - run: npm install
# - run: npm run build - run: npm run build
# build_on_win: build_on_win:
# runs-on: windows-latest runs-on: windows-latest
# steps: steps:
# - uses: actions/checkout@master - uses: actions/checkout@master
# - uses: actions/setup-node@master - uses: actions/setup-node@master
# with: with:
# node-version: 22.4 node-version: 22.4
# - run: npm install - run: npm install
# - run: npm run build - run: npm run build

View File

@ -29,30 +29,30 @@ jobs:
name: linux-builds name: linux-builds
path: dist/ path: dist/
# build_on_mac: build_on_mac:
# runs-on: macos-latest runs-on: macos-latest
# steps: steps:
# - uses: actions/checkout@master - uses: actions/checkout@master
# - uses: actions/setup-node@master - uses: actions/setup-node@master
# with: with:
# node-version: 22.4 node-version: 22.4
# - run: npm install - run: npm install
# - run: npm run build - run: npm run build
# - uses: actions/upload-artifact@master - uses: actions/upload-artifact@master
# with: with:
# name: mac-builds name: mac-builds
# path: ./dist/ path: ./dist/
# build_on_win: build_on_win:
# runs-on: windows-latest runs-on: windows-latest
# steps: steps:
# - uses: actions/checkout@master - uses: actions/checkout@master
# - uses: actions/setup-node@master - uses: actions/setup-node@master
# with: with:
# node-version: 22.4 node-version: 22.4
# - run: npm install - run: npm install
# - run: npm run build - run: npm run build
# - uses: actions/upload-artifact@master - uses: actions/upload-artifact@master
# with: with:
# name: windows-builds name: windows-builds
# path: dist/ path: dist/

View File

@ -14,6 +14,7 @@
"rescrobbler", "rescrobbler",
"scrobble", "scrobble",
"scrobbling", "scrobbling",
"Songwhip",
"trackid", "trackid",
"tracklist", "tracklist",
"widevine", "widevine",

View File

@ -4,38 +4,6 @@ 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.18.2]
- Reverted to sass 1.79.4 to fix `Nix` builds
- Changed electron-builder.base.yml to now generate the correct .desktop entries again
- Should fix flatpak build
## [5.18.1]
- Fixed the login bug
- Upgraded electron to 35.1.1
- Added Widevine/CDM info to startup
- delayed remote electron initializer
## [5.18.0]
- [Dianoga](https://github.com/Dianoga) fixed the duration selector, restoring mpris & partial API data.
- PR: #554
- Added `xesam:url` property to mpris metadata fixes [#506](https://github.com/Mastermindzh/tidal-hifi/issues/506)
## [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]
- Fix issue #449 Discord RPC stuck on "Browsing Tidal".
- Fix issue #448 Add option to disable the discord rpc idle text
- Notifications are now send at the end of the update process, allowing other events to happen sooner.
## [5.15.0] ## [5.15.0]
- Added all missing swagger/openApi info with the help of [Times-Z](https://github.com/Times-Z) - Added all missing swagger/openApi info with the help of [Times-Z](https://github.com/Times-Z)

View File

@ -48,6 +48,7 @@ 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

View File

@ -1,7 +1,7 @@
appId: com.rickvanlieshout.tidal-hifi appId: com.rickvanlieshout.tidal-hifi
electronVersion: 35.1.1 electronVersion: 28.1.1
electronDownload: electronDownload:
version: 35.1.1+wvcus version: 28.1.1+wvcus
mirror: https://github.com/castlabs/electron-releases/releases/download/v mirror: https://github.com/castlabs/electron-releases/releases/download/v
snap: snap:
plugs: plugs:
@ -22,19 +22,19 @@ linux:
"--enable-features=WaylandWindowDecorations", "--enable-features=WaylandWindowDecorations",
] ]
desktop: desktop:
entry: Encoding: UTF-8
Encoding: "UTF-8" Name: TIDAL Hi-Fi
Name: "TIDAL Hi-Fi" GenericName: TIDAL Hi-Fi
GenericName: "TIDAL Hi-Fi" Comment: The web version of listen.tidal.com running in electron with hifi support thanks to widevine.
Comment: "The web version of listen.tidal.com running in electron with hifi support thanks to widevine." Icon: tidal-hifi
Icon: "tidal-hifi" StartupNotify: true
StartupNotify: "true" Terminal: false
Terminal: "false" Type: Application
Type: "Application" Categories: Network;Application;AudioVideo;Audio;Video
Categories: "Network;Application;AudioVideo;Audio;Video" StartupWMClass: tidal-hifi
StartupWMClass: "tidal-hifi" X-PulseAudio-Properties: media.role=music
X-PulseAudio-Properties: "media.role=music" MimeType: x-scheme-handler/tidal;
MimeType: "x-scheme-handler/tidal;"
mac: mac:
category: public.app-category.entertainment category: public.app-category.entertainment
win: win:

4546
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,10 @@
{ {
"name": "tidal-hifi", "name": "tidal-hifi",
"version": "5.18.2", "version": "5.15.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": {
"start": "electron --inspect=0.0.0.0:5858 --remote-debugging-port=8315 --remote-allow-origins=* .", "start": "electron --inspect=0.0.0.0:5858 .",
"watchStart": "nodemon dist -x \"npm run start\"", "watchStart": "nodemon dist -x \"npm run start\"",
"compile": "tsc && npm run sass-and-copy", "compile": "tsc && npm run sass-and-copy",
"deps": "npm run watch", "deps": "npm run watch",
@ -42,20 +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",
"@xhayper/discord-rpc": "^1.2.1", "axios": "^1.7.2",
"axios": "^1.8.4",
"electron-store": "^8.2.0",
"express": "^4.21.2",
"hotkeys-js": "^3.13.9",
"mpris-service": "^2.1.2",
"sass": "1.86.0",
"swagger-ui-express": "^5.0.1",
"cors": "^2.8.5", "cors": "^2.8.5",
"request": "^2.88.2" "discord-rpc": "^4.0.1",
"electron-store": "^8.2.0",
"express": "^4.19.2",
"hotkeys-js": "^3.13.7",
"mpris-service": "^2.1.2",
"request": "^2.88.2",
"sass": "^1.77.7",
"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/request": "^2.48.12",
@ -63,8 +64,8 @@
"@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",
"copyfiles": "^2.4.1", "copyfiles": "^2.4.1",
"electron": "github:castlabs/electron-releases#v35.1.1+wvcus", "electron": "git+https://github.com/castlabs/electron-releases#v31.1.0+wvcus",
"electron-builder": "~26.0.12", "electron-builder": "~24.9.4",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"markdown-toc": "^1.2.0", "markdown-toc": "^1.2.0",

View File

@ -16,19 +16,17 @@
search: '[class^="searchField"]', search: '[class^="searchField"]',
shuffle: '*[data-test="shuffle"]', shuffle: '*[data-test="shuffle"]',
repeat: '*[data-test="repeat"]', repeat: '*[data-test="repeat"]',
account: '*[data-test^="profile-image-button"]', account: '*[class^="profileOptions"]',
settings: '*[data-test^="sidebar-menu-button"]', settings: '*[data-test^="open-settings"]',
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"]',
duration: '*[class^=_playbackControlsContainer] *[data-test="duration"]', duration: '*[class^=playbackControlsContainer] *[data-test="duration"]',
bar: '*[data-test="progress-bar"]', bar: '*[data-test="progress-bar"]',
footer: "#footerPlayer", footer: "#footerPlayer",
mediaItem: "[data-type='mediaItem']", mediaItem: "[data-type='mediaItem']",
album_header_title: '*[class^="_playingFrom"] span:nth-child(2)', album_header_title: '*[class^="playingFrom"] span:nth-child(2)',
playing_from: '*[class^="_playingFrom"] span:nth-child(2)', playingFrom: '*[class^="playingFrom"] span:nth-child(2)',
queue_album: "*[class^=playQueueItemsContainer] *[class^=groupTitle] span:nth-child(2)",
currentlyPlaying: "[class^='isPlayingIcon'], [data-test-is-playing='true']", currentlyPlaying: "[class^='isPlayingIcon'], [data-test-is-playing='true']",
album_name_cell: '[class^="album"]', album_name_cell: '[class^="album"]',
tracklist_row: '[data-test="tracklist-row"]', tracklist_row: '[data-test="tracklist-row"]',

View File

@ -10,7 +10,7 @@ export const globalEvents = {
showSettings: "showSettings", showSettings: "showSettings",
storeChanged: "storeChanged", storeChanged: "storeChanged",
error: "error", error: "error",
getUniversalLink: "getUniversalLink", whip: "whip",
log: "log", log: "log",
toggleFavorite: "toggleFavorite", toggleFavorite: "toggleFavorite",
toggleShuffle: "toggleShuffle", toggleShuffle: "toggleShuffle",

View File

@ -30,7 +30,6 @@ export const settings = {
buttonText: "discord.buttonText", buttonText: "discord.buttonText",
includeTimestamps: "discord.includeTimestamps", includeTimestamps: "discord.includeTimestamps",
showSong: "discord.showSong", showSong: "discord.showSong",
showIdle: "discord.showIdle",
idleText: "discord.idleText", idleText: "discord.idleText",
usingText: "discord.usingText", usingText: "discord.usingText",
}, },
@ -55,7 +54,6 @@ 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",

View File

@ -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.18.2", "version": "5.15.0",
"description": "", "description": "",
"license": { "license": {
"name": "MIT", "name": "MIT",
@ -21,7 +21,9 @@
"/current": { "/current": {
"get": { "get": {
"summary": "Get current media info", "summary": "Get current media info",
"tags": ["current"], "tags": [
"current"
],
"responses": { "responses": {
"200": { "200": {
"description": "Current media info", "description": "Current media info",
@ -39,7 +41,9 @@
"/current/image": { "/current/image": {
"get": { "get": {
"summary": "Get current media image", "summary": "Get current media image",
"tags": ["current"], "tags": [
"current"
],
"responses": { "responses": {
"200": { "200": {
"description": "Current media image", "description": "Current media image",
@ -61,7 +65,9 @@
"/player/play": { "/player/play": {
"post": { "post": {
"summary": "Play the current media", "summary": "Play the current media",
"tags": ["player"], "tags": [
"player"
],
"responses": { "responses": {
"200": { "200": {
"description": "Ok", "description": "Ok",
@ -79,7 +85,9 @@
"/player/favorite/toggle": { "/player/favorite/toggle": {
"post": { "post": {
"summary": "Add the current media to your favorites, or remove it if its already added to your favorites", "summary": "Add the current media to your favorites, or remove it if its already added to your favorites",
"tags": ["player"], "tags": [
"player"
],
"responses": { "responses": {
"200": { "200": {
"description": "Ok", "description": "Ok",
@ -97,7 +105,9 @@
"/player/pause": { "/player/pause": {
"post": { "post": {
"summary": "Pause the current media", "summary": "Pause the current media",
"tags": ["player"], "tags": [
"player"
],
"responses": { "responses": {
"200": { "200": {
"description": "Ok", "description": "Ok",
@ -115,7 +125,9 @@
"/player/next": { "/player/next": {
"post": { "post": {
"summary": "Play the next song", "summary": "Play the next song",
"tags": ["player"], "tags": [
"player"
],
"responses": { "responses": {
"200": { "200": {
"description": "Ok", "description": "Ok",
@ -133,7 +145,9 @@
"/player/previous": { "/player/previous": {
"post": { "post": {
"summary": "Play the previous song", "summary": "Play the previous song",
"tags": ["player"], "tags": [
"player"
],
"responses": { "responses": {
"200": { "200": {
"description": "Ok", "description": "Ok",
@ -151,7 +165,9 @@
"/player/shuffle/toggle": { "/player/shuffle/toggle": {
"post": { "post": {
"summary": "Play the previous song", "summary": "Play the previous song",
"tags": ["player"], "tags": [
"player"
],
"responses": { "responses": {
"200": { "200": {
"description": "Ok", "description": "Ok",
@ -169,7 +185,9 @@
"/player/repeat/toggle": { "/player/repeat/toggle": {
"post": { "post": {
"summary": "Toggle the repeat status, toggles between \"off\" , \"single\" and \"all\"", "summary": "Toggle the repeat status, toggles between \"off\" , \"single\" and \"all\"",
"tags": ["player"], "tags": [
"player"
],
"responses": { "responses": {
"200": { "200": {
"description": "Ok", "description": "Ok",
@ -187,7 +205,9 @@
"/player/playpause": { "/player/playpause": {
"post": { "post": {
"summary": "Start playing the media if paused, or pause the media if playing", "summary": "Start playing the media if paused, or pause the media if playing",
"tags": ["player"], "tags": [
"player"
],
"responses": { "responses": {
"200": { "200": {
"description": "Ok", "description": "Ok",
@ -205,7 +225,9 @@
"/settings/skipped-artists": { "/settings/skipped-artists": {
"get": { "get": {
"summary": "get a list of artists that TIDAL Hi-Fi will skip if skipping is enabled", "summary": "get a list of artists that TIDAL Hi-Fi will skip if skipping is enabled",
"tags": ["settings"], "tags": [
"settings"
],
"responses": { "responses": {
"200": { "200": {
"description": "The list book.", "description": "The list book.",
@ -221,7 +243,9 @@
}, },
"post": { "post": {
"summary": "Add new artists to the list of skipped artists", "summary": "Add new artists to the list of skipped artists",
"tags": ["settings"], "tags": [
"settings"
],
"requestBody": { "requestBody": {
"required": true, "required": true,
"content": { "content": {
@ -242,7 +266,9 @@
"/settings/skipped-artists/delete": { "/settings/skipped-artists/delete": {
"post": { "post": {
"summary": "Remove artists from the list of skipped artists", "summary": "Remove artists from the list of skipped artists",
"tags": ["settings"], "tags": [
"settings"
],
"requestBody": { "requestBody": {
"required": true, "required": true,
"content": { "content": {
@ -263,7 +289,9 @@
"/settings/skipped-artists/current": { "/settings/skipped-artists/current": {
"post": { "post": {
"summary": "Add the current artist to the list of skipped artists", "summary": "Add the current artist to the list of skipped artists",
"tags": ["settings"], "tags": [
"settings"
],
"responses": { "responses": {
"200": { "200": {
"description": "Ok" "description": "Ok"
@ -272,7 +300,9 @@
}, },
"delete": { "delete": {
"summary": "Remove the current artist from the list of skipped artists", "summary": "Remove the current artist from the list of skipped artists",
"tags": ["settings"], "tags": [
"settings"
],
"responses": { "responses": {
"200": { "200": {
"description": "Ok" "description": "Ok"
@ -283,7 +313,9 @@
"/image": { "/image": {
"get": { "get": {
"summary": "Get current image", "summary": "Get current image",
"tags": ["legacy"], "tags": [
"legacy"
],
"deprecated": true, "deprecated": true,
"responses": { "responses": {
"200": { "200": {
@ -306,7 +338,9 @@
"/play": { "/play": {
"get": { "get": {
"summary": "Play the current media", "summary": "Play the current media",
"tags": ["legacy"], "tags": [
"legacy"
],
"deprecated": true, "deprecated": true,
"responses": { "responses": {
"200": { "200": {
@ -325,7 +359,9 @@
"/favorite/toggle": { "/favorite/toggle": {
"get": { "get": {
"summary": "Add the current media to your favorites, or remove it if its already added to your favorites", "summary": "Add the current media to your favorites, or remove it if its already added to your favorites",
"tags": ["legacy"], "tags": [
"legacy"
],
"deprecated": true, "deprecated": true,
"responses": { "responses": {
"200": { "200": {
@ -344,7 +380,9 @@
"/pause": { "/pause": {
"get": { "get": {
"summary": "Pause the current media", "summary": "Pause the current media",
"tags": ["legacy"], "tags": [
"legacy"
],
"deprecated": true, "deprecated": true,
"responses": { "responses": {
"200": { "200": {
@ -363,7 +401,9 @@
"/next": { "/next": {
"get": { "get": {
"summary": "Play the next song", "summary": "Play the next song",
"tags": ["legacy"], "tags": [
"legacy"
],
"deprecated": true, "deprecated": true,
"responses": { "responses": {
"200": { "200": {
@ -382,7 +422,9 @@
"/previous": { "/previous": {
"get": { "get": {
"summary": "Play the previous song", "summary": "Play the previous song",
"tags": ["legacy"], "tags": [
"legacy"
],
"deprecated": true, "deprecated": true,
"responses": { "responses": {
"200": { "200": {
@ -401,7 +443,9 @@
"/playpause": { "/playpause": {
"get": { "get": {
"summary": "Toggle play/pause", "summary": "Toggle play/pause",
"tags": ["legacy"], "tags": [
"legacy"
],
"deprecated": true, "deprecated": true,
"responses": { "responses": {
"200": { "200": {
@ -514,7 +558,10 @@
"items": { "items": {
"type": "string" "type": "string"
}, },
"example": ["Artist1", "Artist2"] "example": [
"Artist1",
"Artist2"
]
} }
} }
}, },

View File

@ -1,10 +0,0 @@
export class SharingService {
/**
* Retrieve the universal link given a regular track link
* @param currentUrl
* @returns
*/
public static getUniversalLink(currentUrl: string): string {
return `${currentUrl}?u`;
}
}

View File

@ -0,0 +1,21 @@
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;
}

View File

@ -0,0 +1,4 @@
export interface ServiceLinks {
link: string;
countries: string[];
}

View File

@ -0,0 +1,27 @@
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[];
};
};
}

View File

@ -0,0 +1,32 @@
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}`;
}
}

View File

@ -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 { SharingService } from "./features/sharingService/sharingService"; import { Songwhip } from "./features/songwhip/songwhip";
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";
@ -26,6 +26,7 @@ import {
import { addTray, refreshTray } from "./scripts/tray"; import { addTray, refreshTray } from "./scripts/tray";
let mainInhibitorId = -1; let mainInhibitorId = -1;
initialize();
let mainWindow: BrowserWindow; let mainWindow: BrowserWindow;
const icon = path.join(__dirname, "../assets/icon.png"); const icon = path.join(__dirname, "../assets/icon.png");
const PROTOCOL_PREFIX = "tidal"; const PROTOCOL_PREFIX = "tidal";
@ -97,7 +98,6 @@ function createWindow(options = { x: 0, y: 0, backgroundColor: "white" }) {
}, },
}, },
}); });
enable(mainWindow.webContents); enable(mainWindow.webContents);
registerHttpProtocols(); registerHttpProtocols();
syncMenuBarWithStore(); syncMenuBarWithStore();
@ -126,7 +126,6 @@ function createWindow(options = { x: 0, y: 0, backgroundColor: "white" }) {
} }
return false; return false;
}); });
// Emitted when the window is closed. // Emitted when the window is closed.
mainWindow.on("closed", function () { mainWindow.on("closed", function () {
releaseInhibitorIfActive(mainInhibitorId); releaseInhibitorIfActive(mainInhibitorId);
@ -179,7 +178,6 @@ app.on("ready", async () => {
if (isMainInstance() || isMultipleInstancesAllowed()) { if (isMainInstance() || isMultipleInstancesAllowed()) {
await components.whenReady(); await components.whenReady();
initialize();
// Adblock // Adblock
if (settingsStore.get(settings.adBlock)) { if (settingsStore.get(settings.adBlock)) {
@ -190,8 +188,6 @@ app.on("ready", async () => {
}); });
} }
Logger.log("components ready:", components.status());
createWindow(); createWindow();
addMenu(mainWindow); addMenu(mainWindow);
createSettingsWindow(); createSettingsWindow();
@ -254,8 +250,8 @@ ipcMain.on(globalEvents.error, (event) => {
console.log(event); console.log(event);
}); });
ipcMain.handle(globalEvents.getUniversalLink, async (event, url) => { ipcMain.handle(globalEvents.whip, async (event, url) => {
return SharingService.getUniversalLink(url); return Songwhip.whip(url);
}); });
Logger.watch(ipcMain); Logger.watch(ipcMain);

View File

@ -46,7 +46,6 @@ 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,
@ -59,7 +58,6 @@ let adBlock: HTMLInputElement,
discord_include_timestamps: HTMLInputElement, discord_include_timestamps: HTMLInputElement,
discord_button_text: HTMLInputElement, discord_button_text: HTMLInputElement,
discord_show_song: HTMLInputElement, discord_show_song: HTMLInputElement,
discord_show_idle: HTMLInputElement,
discord_idle_text: HTMLInputElement, discord_idle_text: HTMLInputElement,
discord_using_text: HTMLInputElement; discord_using_text: HTMLInputElement;
@ -141,9 +139,8 @@ 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);
skippedArtists.value = settingsStore.get<string, string[]>(settings.skippedArtists).join("\n");
staticWindowTitle.checked = settingsStore.get(settings.staticWindowTitle);
theme.value = settingsStore.get(settings.theme); theme.value = settingsStore.get(settings.theme);
skippedArtists.value = settingsStore.get<string, string[]>(settings.skippedArtists).join("\n");
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);
@ -154,7 +151,6 @@ function refreshSettings() {
discord_include_timestamps.checked = settingsStore.get(settings.discord.includeTimestamps); discord_include_timestamps.checked = settingsStore.get(settings.discord.includeTimestamps);
discord_button_text.value = settingsStore.get(settings.discord.buttonText); discord_button_text.value = settingsStore.get(settings.discord.buttonText);
discord_show_song.checked = settingsStore.get(settings.discord.showSong); discord_show_song.checked = settingsStore.get(settings.discord.showSong);
discord_show_idle.checked = settingsStore.get(settings.discord.showIdle);
discord_idle_text.value = settingsStore.get(settings.discord.idleText); discord_idle_text.value = settingsStore.get(settings.discord.idleText);
discord_using_text.value = settingsStore.get(settings.discord.usingText); discord_using_text.value = settingsStore.get(settings.discord.usingText);
@ -263,7 +259,6 @@ 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");
@ -274,7 +269,6 @@ window.addEventListener("DOMContentLoaded", () => {
listenbrainz_delay = get("listenbrainz_delay"); listenbrainz_delay = get("listenbrainz_delay");
discord_button_text = get("discord_button_text"); discord_button_text = get("discord_button_text");
discord_show_song = get("discord_show_song"); discord_show_song = get("discord_show_song");
discord_show_idle = get("discord_show_idle");
discord_using_text = get("discord_using_text"); discord_using_text = get("discord_using_text");
discord_idle_text = get("discord_idle_text"); discord_idle_text = get("discord_idle_text");
@ -298,7 +292,6 @@ 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);
@ -319,7 +312,6 @@ window.addEventListener("DOMContentLoaded", () => {
settings.discord.showSong, settings.discord.showSong,
switchesWithSettings.discord_show_song switchesWithSettings.discord_show_song
); );
addInputListener(discord_show_idle, settings.discord.showIdle);
addInputListener(discord_idle_text, settings.discord.idleText); addInputListener(discord_idle_text, settings.discord.idleText);
addInputListener(discord_using_text, settings.discord.usingText); addInputListener(discord_using_text, settings.discord.usingText);
}); });

View File

@ -106,16 +106,6 @@
<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>
@ -237,17 +227,6 @@
</div> </div>
<div id="discord_options"> <div id="discord_options">
<div class="group__option" class="hidden">
<div class="group__description">
<h4>Show Idle Text</h4>
<p>Should the idle text be shown when idle?</p>
</div>
<label class="switch">
<input id="discord_show_idle" type="checkbox" />
<span class="switch__slider"></span>
</label>
</div>
<div class="group__option" class="hidden"> <div class="group__option" class="hidden">
<div class="group__description"> <div class="group__description">
<h4>Idle Text</h4> <h4>Idle Text</h4>
@ -478,7 +457,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.18.2">5.18.2</a> href="https://github.com/Mastermindzh/tidal-hifi/releases/tag/5.15.0">5.15.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/"

View File

@ -10,7 +10,7 @@ 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 { SharingService } from "./features/sharingService/sharingService"; import { Songwhip } from "./features/songwhip/songwhip";
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";
@ -48,18 +48,17 @@ 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: '*[data-test^="profile-image-button"]', account: '*[class^="profileOptions"]',
settings: '*[data-test^="sidebar-menu-button"]', settings: '*[data-test^="open-settings"]',
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"]',
duration: '*[class^=_playbackControlsContainer] *[data-test="duration"]', duration: '*[class^=playbackControlsContainer] *[data-test="duration"]',
bar: '*[data-test="progress-bar"]', bar: '*[data-test="progress-bar"]',
footer: "#footerPlayer", footer: "#footerPlayer",
mediaItem: "[data-type='mediaItem']", mediaItem: "[data-type='mediaItem']",
album_header_title: '*[class^="_playingFrom"] span:nth-child(2)', album_header_title: '*[class^="playingFrom"] span:nth-child(2)',
playing_from: '*[class^="_playingFrom"] span:nth-child(2)', playing_from: '*[class^="playingFrom"] span:nth-child(2)',
queue_album: "*[class^=playQueueItemsContainer] *[class^=groupTitle] span:nth-child(2)", queue_album: "*[class^=playQueueItemsContainer] *[class^=groupTitle] span:nth-child(2)",
currentlyPlaying: "[class^='isPlayingIcon'], [data-test-is-playing='true']", currentlyPlaying: "[class^='isPlayingIcon'], [data-test-is-playing='true']",
album_name_cell: '[class^="album"]', album_name_cell: '[class^="album"]',
@ -221,9 +220,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("settings"); elements.click("account");
setTimeout(() => { setTimeout(() => {
elements.click("openSettings"); elements.click("settings");
}, 100); }, 100);
}); });
addHotkey("Control+l", function () { addHotkey("Control+l", function () {
@ -255,10 +254,11 @@ function addHotKeys() {
elements.click("repeat"); elements.click("repeat");
}); });
addHotkey("control+w", async function () { addHotkey("control+w", async function () {
const url = SharingService.getUniversalLink(getTrackURL()); const result = await ipcRenderer.invoke(globalEvents.whip, getTrackURL());
const url = Songwhip.getWhipUrl(result);
clipboard.writeText(url); clipboard.writeText(url);
new Notification({ new Notification({
title: `Universal link generated: `, title: `Successfully whipped: `,
body: `URL copied to clipboard: ${url}`, body: `URL copied to clipboard: ${url}`,
}).show(); }).show();
}); });
@ -394,24 +394,8 @@ function updateMediaInfo(mediaInfo: MediaInfo, notify: boolean) {
if (mediaInfo) { if (mediaInfo) {
currentMediaInfo = mediaInfo; currentMediaInfo = mediaInfo;
ipcRenderer.send(globalEvents.updateInfo, mediaInfo); ipcRenderer.send(globalEvents.updateInfo, mediaInfo);
updateMpris(mediaInfo); if (settingsStore.get(settings.notifications) && notify) {
updateListenBrainz(mediaInfo); if (currentNotification) currentNotification.close();
if (notify) {
sendNotification(mediaInfo);
}
}
}
/**
* send a desktop notification if enabled in settings
* @param mediaInfo
* @param notify Whether to notify
*/
async function sendNotification(mediaInfo: MediaInfo) {
if (settingsStore.get(settings.notifications)) {
if (currentNotification) {
currentNotification.close();
}
currentNotification = new Notification({ currentNotification = new Notification({
title: mediaInfo.title, title: mediaInfo.title,
body: mediaInfo.artists, body: mediaInfo.artists,
@ -419,6 +403,10 @@ async function sendNotification(mediaInfo: MediaInfo) {
}); });
currentNotification.show(); currentNotification.show();
} }
updateMpris(mediaInfo);
updateListenBrainz(mediaInfo);
}
} }
function addMPRIS() { function addMPRIS() {
@ -483,7 +471,6 @@ function updateMpris(mediaInfo: MediaInfo) {
"xesam:title": mediaInfo.title, "xesam:title": mediaInfo.title,
"xesam:artist": [mediaInfo.artists], "xesam:artist": [mediaInfo.artists],
"xesam:album": mediaInfo.album, "xesam:album": mediaInfo.album,
"xesam:url": mediaInfo.url,
"mpris:artUrl": mediaInfo.image, "mpris:artUrl": mediaInfo.image,
"mpris:length": convertDuration(mediaInfo.duration) * 1000 * 1000, "mpris:length": convertDuration(mediaInfo.duration) * 1000 * 1000,
"mpris:trackid": "/org/mpris/MediaPlayer2/track/" + getTrackID(), "mpris:trackid": "/org/mpris/MediaPlayer2/track/" + getTrackID(),
@ -551,7 +538,6 @@ 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();
@ -596,9 +582,7 @@ setInterval(function () {
}; };
// update title, url and play info with new info // update title, url and play info with new info
settingsStore.get(settings.staticWindowTitle) setTitle(songDashArtistTitle);
? setTitle(staticTitle)
: setTitle(songDashArtistTitle);
getTrackURL(); getTrackURL();
currentSong = songDashArtistTitle; currentSong = songDashArtistTitle;
currentPlayStatus = currentStatus; currentPlayStatus = currentStatus;

View File

@ -1,4 +1,4 @@
import { Client, SetActivity } from "@xhayper/discord-rpc"; import { Client, Presence } from "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,13 +12,9 @@ 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(); rpc.setActivity(getActivity());
} }
}; };
@ -26,20 +22,10 @@ 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 getActivity = (): Presence => {
const showIdle = settingsStore.get<string, boolean>(settings.discord.showIdle) ?? true; const presence: Presence = { ...defaultPresence };
if (mediaInfo.status === MediaStatus.paused && !showIdle) {
rpc.user?.clearActivity();
} else {
rpc.user?.setActivity(getActivity());
}
};
const getActivity = (): SetActivity => {
const presence: SetActivity = { ...defaultPresence };
if (mediaInfo.status === MediaStatus.paused) { if (mediaInfo.status === MediaStatus.paused) {
presence.details = presence.details =
@ -55,7 +41,6 @@ const getActivity = (): SetActivity => {
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() {
@ -69,33 +54,18 @@ const getActivity = (): SetActivity => {
return { includeTimestamps, detailsPrefix, buttonText }; return { includeTimestamps, detailsPrefix, buttonText };
} }
/**
* Pad a string using spaces to at least 2 characters
* @param input string to pad with 2 characters
* @returns
*/
function pad(input: string): string {
return input.padEnd(2, " ");
}
function setPresenceFromMediaInfo(detailsPrefix: string, buttonText: string) { function setPresenceFromMediaInfo(detailsPrefix: string, buttonText: string) {
// discord requires a minimum of 2 characters
const title = pad(mediaInfo.title);
const album = pad(mediaInfo.album);
const artists = pad(mediaInfo.artists);
if (mediaInfo.url) { if (mediaInfo.url) {
presence.details = `${detailsPrefix}${title}`; presence.details = `${detailsPrefix}${mediaInfo.title}`;
presence.state = artists ? artists : "unknown artist(s)"; presence.state = mediaInfo.artists ? mediaInfo.artists : "unknown artist(s)";
presence.largeImageKey = mediaInfo.image; presence.largeImageKey = mediaInfo.image;
if (album) { if (mediaInfo.album) {
presence.largeImageText = album; presence.largeImageText = mediaInfo.album;
} }
presence.buttons = [{ label: buttonText, url: mediaInfo.url }]; presence.buttons = [{ label: buttonText, url: mediaInfo.url }];
} else { } else {
presence.details = `Watching ${title}`; presence.details = `Watching ${mediaInfo.title}`;
presence.state = artists; presence.state = mediaInfo.artists;
} }
} }
@ -103,29 +73,11 @@ const getActivity = (): SetActivity => {
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 now = Math.trunc((Date.now() + 500) / 1000); const date = new Date();
presence.startTimestamp = now - currentSeconds; const now = (date.getTime() / 1000) | 0;
presence.endTimestamp = presence.startTimestamp + durationSeconds; const remaining = date.setSeconds(date.getSeconds() + (durationSeconds - currentSeconds));
} 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');
} }
} }
}; };
@ -134,8 +86,18 @@ const connectWithRetry = async (retryCount = 0) => {
* 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: {type: "ipc"}, clientId }); rpc = new Client({ transport: "ipc" });
connectWithRetry(); rpc.login({ clientId }).then(
() => {
rpc.on("ready", () => {
rpc.setActivity(getActivity());
});
ipcMain.on(globalEvents.updateInfo, observer);
},
() => {
Logger.log("Can't connect to Discord, is it running?");
}
);
}; };
/** /**
@ -143,7 +105,7 @@ export const initRPC = () => {
*/ */
export const unRPC = () => { export const unRPC = () => {
if (rpc) { if (rpc) {
rpc.user?.clearActivity(); rpc.clearActivity();
rpc.destroy(); rpc.destroy();
rpc = null; rpc = null;
ipcMain.removeListener(globalEvents.updateInfo, observer); ipcMain.removeListener(globalEvents.updateInfo, observer);

View File

@ -43,7 +43,6 @@ export const settingsStore = new Store({
enableDiscord: false, enableDiscord: false,
discord: { discord: {
showSong: true, showSong: true,
showIdle: true,
idleText: "Browsing Tidal", idleText: "Browsing Tidal",
usingText: "Playing media on TIDAL", usingText: "Playing media on TIDAL",
includeTimestamps: true, includeTimestamps: true,
@ -69,7 +68,6 @@ 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,
@ -117,9 +115,6 @@ export const settingsStore = new Store({
{ key: settings.advanced.tidalUrl, value: "https://listen.tidal.com" }, { key: settings.advanced.tidalUrl, value: "https://listen.tidal.com" },
]); ]);
}, },
"5.16.0": (migrationStore) => {
buildMigration("5.16.0", migrationStore, [{ key: settings.discord.showIdle, value: "true" }]);
},
}, },
}); });