Compare commits

..

28 Commits

Author SHA1 Message Date
48f4fe47ef Merge pull request #573 from Mastermindzh/next
Next
2025-03-30 13:10:28 +02:00
a819e1eb45 removing mac/win builds, they aren't targetted anyway 2025-03-30 12:52:48 +02:00
57e6342b5f Merge branch 'next' of github.com:Mastermindzh/tidal-hifi into next 2025-03-30 12:50:31 +02:00
58a543a3c8 fixed .desktop file 2025-03-30 12:50:27 +02:00
1c915d99fe Merge pull request #572 from QF0xB/fix/nix-build-temporarily
Reverted sass to 1.79.4
2025-03-30 12:49:29 +02:00
Quirin Brändli
8994360415 Reverted sass to 1.79.4 2025-03-30 00:06:46 +01:00
a5c0d9e6e8 Merge pull request #570 from Mastermindzh/fix/login
Fix/login
2025-03-29 20:45:40 +01:00
f8eb36f4c7 bumped versions 2025-03-29 17:12:28 +01:00
5ca90c25d3 update builder to v35 as well 2025-03-29 17:05:17 +01:00
cf86969cfc updated builder, removed abstract-socket 2025-03-29 16:27:24 +01:00
afd05ae88b updated electron, deps, and moved remote calls after start 2025-03-29 15:58:28 +01:00
72c25dfdc1 Merge pull request #564 from Mastermindzh/snyk-fix-1995dd90a05e86ea7c2e4f0f7a284930
[Snyk] Security upgrade @xhayper/discord-rpc from 1.2.0 to 1.2.1
2025-03-23 21:32:20 +01:00
snyk-bot
25b30edf31 fix: package.json & package-lock.json to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-UNDICI-8641354
2025-03-22 08:54:58 +00:00
35ed4807e3 Merge pull request #558 from Mastermindzh/next
Next
2025-03-18 09:10:40 +01:00
deff9524a8 album & playingfrom info added to mediaInfo again. fixes #548 2025-03-17 11:05:10 +01:00
34c7777eeb Merge pull request #554 from Dianoga/duration-fix
fix duration selector
2025-03-17 10:27:42 +01:00
048e949f30 Merge branch 'master' of github.com:Mastermindzh/tidal-hifi into next 2025-03-17 09:54:06 +01:00
Brian Steere
4a976bc58c fix duration selector
Should fix the MPRIS issue as long as duration doesn't break again.
2025-03-12 14:21:13 -05:00
ff4c51234b Merge pull request #524 from Mastermindzh/snyk-upgrade-278f4ec84db5f4ebc26d987909c6c73f
[Snyk] Upgrade hotkeys-js from 3.13.7 to 3.13.9
2025-01-06 17:48:43 +01:00
snyk-bot
c4ee6b51b9 fix: upgrade hotkeys-js from 3.13.7 to 3.13.9
Snyk has created this PR to upgrade hotkeys-js from 3.13.7 to 3.13.9.

See this package in npm:
hotkeys-js

See this project in Snyk:
https://app.snyk.io/org/mastermindzh/project/dade8f03-2064-49a3-8957-edbacec3887c?utm_source=github&utm_medium=referral&page=upgrade-pr
2025-01-04 01:22:24 +00:00
45fe336598 Merge pull request #522 from Mastermindzh/snyk-upgrade-7f82ddec0efc65c72c6f7f2f182ace7c
[Snyk] Upgrade axios from 1.7.8 to 1.7.9
2024-12-28 12:45:52 +01:00
snyk-bot
fe9f50aaf5 fix: upgrade axios from 1.7.8 to 1.7.9
Snyk has created this PR to upgrade axios from 1.7.8 to 1.7.9.

See this package in npm:
axios

See this project in Snyk:
https://app.snyk.io/org/mastermindzh/project/dade8f03-2064-49a3-8957-edbacec3887c?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-12-27 23:17:27 +00:00
aaf7a1b662 Merge pull request #504 from darkiox/master
Fix Discord RPC Timestamp jitter
2024-12-09 11:21:27 +01:00
755be0ee30 Merge pull request #515 from Mastermindzh/snyk-fix-82857ea1745fc2cfbc5f06a87a8ff0b8
[Snyk] Security upgrade express from 4.21.1 to 4.21.2
2024-12-09 11:05:23 +01:00
snyk-bot
9d736b2bd9 fix: package.json & package-lock.json to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-PATHTOREGEXP-8482416
2024-12-06 20:33:54 +00:00
f4d4b1a1df Merge pull request #508 from Mastermindzh/snyk-fix-a6630fbdb3c8f0161d0b939cf0ec3ca3
[Snyk] Security upgrade axios from 1.7.7 to 1.7.8
2024-12-02 09:03:11 +01:00
snyk-bot
0c27c815f5 fix: package.json & package-lock.json to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-AXIOS-6671926
2024-11-30 06:57:38 +00:00
Darkiox
a3aa45a96b fix: discord rpc timestamp jitter 2024-11-25 19:15:26 -03:00
17 changed files with 3675 additions and 2557 deletions

View File

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

View File

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

View File

@@ -1,10 +1,16 @@
{
"plugins": ["stylelint-prettier"],
"extends": ["stylelint-config-standard-scss"],
"ignoreFiles": ["src/themes/**.scss"],
"plugins": [
"stylelint-prettier"
],
"extends": [
"stylelint-config-standard-scss"
],
"ignoreFiles": [
"src/themes/**.scss"
],
"rules": {
"prettier/prettier": true,
"scss/at-extend-no-missing-placeholder": null,
"no-descending-specificity": null
}
}
}

View File

@@ -4,6 +4,25 @@ 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/),
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)
@@ -21,7 +40,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added all missing swagger/openApi info with the help of [Times-Z](https://github.com/Times-Z)
- Updated most dependency versions
- This includes Electron 31!
- Added a channel selector so we can now use Tidal's staging environment directly from the app
@@ -113,12 +131,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Updated Electron to 28.1.1 (fixes [325](https://github.com/Mastermindzh/tidal-hifi/issues/325))
- Updated dependencies to latest
- added theme files to stylelint ignore
- fixed other stylelint errors
- Added functionality to favorite a song (fixes [#323](https://github.com/Mastermindzh/tidal-hifi/issues/323))
- Added a hotkey to favorite ("Add to collection") songs: Control+a
- Added the "favorite" field in the `mediaInfo` and the API `/current` endpoint
- Added an endpoint to toggle favoriting a song: `http://localhost:47836/favorite/toggle`
@@ -139,10 +155,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added settings to customize the Discord rich presence information
- Discord settings are now also collapsible like the ListenBrainz ones are
- Restyled settings menu to include version number and useful links on the about page
![The new about page](./docs/images/new-about.png)
![The new about page](./docs/images/new-about.png)
- The ListenBrainz integration has been extended with a configurable (5 seconds by default) delay in song reporting so that it doesn't spam the API when you are cycling through songs.
- Custom CSS now also applies to settings window
![Tokyo Night theme on settings window](./docs/images/customcss-menu.png)
![Tokyo Night theme on settings window](./docs/images/customcss-menu.png)
## [5.6.0]

View File

@@ -8,3 +8,4 @@ Only the very latest 😄.
If you find a vulnerability just add it as an issue.
If there's an especially bad vulnerability that you don't want to make public just send me a private message (email, discord, wherever).

View File

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

4172
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,10 @@
{
"name": "tidal-hifi",
"version": "5.17.0",
"version": "5.18.2",
"description": "Tidal on Electron with widevine(hifi) support",
"main": "ts-dist/main.js",
"scripts": {
"start": "electron --inspect=0.0.0.0:5858 .",
"start": "electron --inspect=0.0.0.0:5858 --remote-debugging-port=8315 --remote-allow-origins=* .",
"watchStart": "nodemon dist -x \"npm run start\"",
"compile": "tsc && npm run sass-and-copy",
"deps": "npm run watch",
@@ -23,7 +23,6 @@
"build-mac": "npm run builder -- -c ./build/electron-builder.yml -m",
"build-base": "npm run builder -- -c ./build/electron-builder.base.yml",
"prebuilder": "npm run compile",
"prettier": "prettier . --write",
"builder": "electron-builder --publish=never",
"sass": "sass ./src/pages/settings/settings.scss ./src/pages/settings/settings.css && sass --no-source-map src/themes:themes",
"style-lint": "npx stylelint **/*.scss",
@@ -43,16 +42,16 @@
"dependencies": {
"@electron/remote": "^2.1.2",
"@types/swagger-jsdoc": "^6.0.4",
"@xhayper/discord-rpc": "^1.2.0",
"axios": "^1.7.7",
"cors": "^2.8.5",
"@xhayper/discord-rpc": "^1.2.1",
"axios": "^1.8.4",
"electron-store": "^8.2.0",
"express": "^4.21.1",
"hotkeys-js": "^3.13.7",
"express": "^4.21.2",
"hotkeys-js": "^3.13.9",
"mpris-service": "^2.1.2",
"request": "^2.88.2",
"sass": "^1.79.4",
"swagger-ui-express": "^5.0.1"
"sass": "1.79.4",
"swagger-ui-express": "^5.0.1",
"cors": "^2.8.5",
"request": "^2.88.2"
},
"devDependencies": {
"@mastermindzh/prettier-config": "^1.0.0",
@@ -64,8 +63,8 @@
"@typescript-eslint/eslint-plugin": "^7.16.0",
"@typescript-eslint/parser": "^7.15.0",
"copyfiles": "^2.4.1",
"electron": "git+https://github.com/castlabs/electron-releases#v31.1.0+wvcus",
"electron-builder": "~24.9.4",
"electron": "github:castlabs/electron-releases#v35.1.1+wvcus",
"electron-builder": "~26.0.12",
"eslint": "^8.57.0",
"js-yaml": "^4.1.0",
"markdown-toc": "^1.2.0",

View File

@@ -22,12 +22,13 @@
media: '*[data-test="current-media-imagery"]',
image: "img",
current: '*[data-test="current-time"]',
duration: '*[class^=playbackControlsContainer] *[data-test="duration"]',
duration: '*[class^=_playbackControlsContainer] *[data-test="duration"]',
bar: '*[data-test="progress-bar"]',
footer: "#footerPlayer",
mediaItem: "[data-type='mediaItem']",
album_header_title: '*[class^="playingFrom"] span:nth-child(2)',
playingFrom: '*[class^="playingFrom"] span:nth-child(2)',
album_header_title: '*[class^="_playingFrom"] span:nth-child(2)',
playing_from: '*[class^="_playingFrom"] span:nth-child(2)',
queue_album: "*[class^=playQueueItemsContainer] *[class^=groupTitle] span:nth-child(2)",
currentlyPlaying: "[class^='isPlayingIcon'], [data-test-is-playing='true']",
album_name_cell: '[class^="album"]',
tracklist_row: '[data-test="tracklist-row"]',

View File

@@ -1,295 +0,0 @@
import { MediaStatus } from "../models/mediaStatus";
import { RepeatState } from "../models/repeatState";
import { TidalController } from "./TidalController";
export class DomTidalController implements TidalController {
private currentPlayStatus = MediaStatus.paused;
/**
* Convert the duration from MM:SS to seconds
* @param {*} duration
*/
private convertDuration(duration: string) {
const parts = duration.split(":");
return parseInt(parts[1]) + 60 * parseInt(parts[0]);
}
private readonly elements = {
play: '*[data-test="play"]',
pause: '*[data-test="pause"]',
next: '*[data-test="next"]',
previous: 'button[data-test="previous"]',
title: '*[data-test^="footer-track-title"]',
artists: '*[data-test^="grid-item-detail-text-title-artist"]',
home: '*[data-test="menu--home"]',
back: '[title^="Back"]',
forward: '[title^="Next"]',
search: '[class^="searchField"]',
shuffle: '*[data-test="shuffle"]',
repeat: '*[data-test="repeat"]',
account: '*[data-test^="profile-image-button"]',
settings: '*[data-test^="sidebar-menu-button"]',
openSettings: '*[data-test^="open-settings"]',
media: '*[data-test="current-media-imagery"]',
image: "img",
current: '*[data-test="current-time"]',
duration: '*[class^=playbackControlsContainer] *[data-test="duration"]',
bar: '*[data-test="progress-bar"]',
footer: "#footerPlayer",
mediaItem: "[data-type='mediaItem']",
album_header_title: '*[class^="playingFrom"] span:nth-child(2)',
playing_from: '*[class^="playingFrom"] span:nth-child(2)',
queue_album: "*[class^=playQueueItemsContainer] *[class^=groupTitle] span:nth-child(2)",
currentlyPlaying: "[class^='isPlayingIcon'], [data-test-is-playing='true']",
album_name_cell: '[class^="album"]',
tracklist_row: '[data-test="tracklist-row"]',
volume: '*[data-test="volume"]',
favorite: '*[data-test="footer-favorite-button"]',
/**
* Get an element from the dom
* @param {*} key key in elements object to fetch
*/
get: function (key: string) {
return globalThis.document.querySelector(this[key.toLowerCase()]);
},
/**
* Get the icon of the current media
*/
getSongIcon: function () {
const figure = this.get("media");
if (figure) {
const mediaElement = figure.querySelector(this["image"]);
if (mediaElement) {
return mediaElement.src.replace("80x80", "640x640");
}
}
return "";
},
/**
* returns an array of all artists in the current media
* @returns {Array} artists
*/
getArtistsArray: function () {
const footer = this.get("footer");
if (footer) {
const artists = footer.querySelectorAll(this.artists);
if (artists)
return Array.from(artists).map((artist) => (artist as HTMLElement).textContent);
}
return [];
},
/**
* unify the artists array into a string separated by commas
* @param {Array} artistsArray
* @returns {String} artists
*/
getArtistsString: function (artistsArray: string[]) {
if (artistsArray.length > 0) return artistsArray.join(", ");
return "unknown artist(s)";
},
getAlbumName: function () {
//If listening to an album, get its name from the header title
if (globalThis.location.href.includes("/album/")) {
const albumName = globalThis.document.querySelector(this.album_header_title);
if (albumName) {
return albumName.textContent;
}
//If listening to a playlist or a mix, get album name from the list
} else if (
globalThis.location.href.includes("/playlist/") ||
globalThis.location.href.includes("/mix/")
) {
if (this.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);
if (row) {
return row.querySelector(this.album_name_cell).textContent;
}
}
}
// see whether we're on the queue page and get it from there
const queueAlbumName = this.getText("queue_album");
if (queueAlbumName) {
return queueAlbumName;
}
return "";
},
isMuted: function () {
return this.get("volume").getAttribute("aria-checked") === "false"; // it's muted if aria-checked is false
},
isFavorite: function () {
return this.get("favorite").getAttribute("aria-checked") === "true";
},
/**
* Shorthand function to get the text of a dom element
* @param {*} key key in elements object to fetch
*/
getText: function (key: string) {
const element = this.get(key);
return element ? element.textContent : "";
},
/**
* Shorthand function to click a dom element
* @param {*} key key in elements object to fetch
*/
click: function (key: string) {
this.get(key).click();
return this;
},
/**
* Shorthand function to focus a dom element
* @param {*} key key in elements object to fetch
*/
focus: function (key: string) {
return this.get(key).focus();
},
};
playPause = (): void => {
const play = this.elements.get("play");
if (play) {
this.elements.click("play");
} else {
this.elements.click("pause");
}
};
goToHome(): void {
this.elements.click("home");
}
openSettings(): void {
this.elements.click("settings");
setTimeout(() => {
this.elements.click("openSettings");
}, 100);
}
toggleFavorite(): void {
this.elements.click("favorite");
}
back(): void {
this.elements.click("back");
}
forward(): void {
this.elements.click("forward");
}
repeat(): void {
this.elements.click("repeat");
}
next(): void {
this.elements.click("next");
}
previous(): void {
this.elements.click("previous");
}
toggleShuffle(): void {
this.elements.click("shuffle");
}
getCurrentlyPlayingStatus() {
const pause = this.elements.get("pause");
// if pause button is visible tidal is playing
if (pause) {
return MediaStatus.playing;
} else {
return MediaStatus.paused;
}
}
getCurrentShuffleState() {
const shuffle = this.elements.get("shuffle");
return shuffle?.getAttribute("aria-checked") === "true";
}
getCurrentRepeatState() {
const repeat = this.elements.get("repeat");
switch (repeat?.getAttribute("data-type")) {
case "button__repeatAll":
return RepeatState.all;
case "button__repeatSingle":
return RepeatState.single;
default:
return RepeatState.off;
}
}
play(): void {
this.playPause();
}
pause(): void {
this.playPause();
}
stop(): void {
this.playPause();
}
getCurrentPosition() {
return this.elements.getText("current");
}
getCurrentPositionInSeconds(): number {
return this.convertDuration(this.getCurrentPosition()) * 1000 * 1000;
}
getTrackId(): string {
const URLelement = this.elements.get("title").querySelector("a");
if (URLelement !== null) {
const id = URLelement.href.replace(/\D/g, "");
return id;
}
return window.location.toString();
}
getCurrentTime(): string {
return this.elements.getText("current");
}
getDuration(): string {
return this.elements.getText("duration");
}
getAlbumName(): string {
return this.elements.getAlbumName();
}
getTitle(): string {
return this.elements.getText("title");
}
getArtists(): string[] {
return this.elements.getArtistsArray();
}
getArtistsString(): string {
return this.elements.getArtistsString(this.getArtists());
}
getPlayingFrom(): string {
return this.elements.getText("playing_from");
}
isFavorite(): boolean {
return this.elements.isFavorite();
}
getSongIcon(): string {
return this.elements.getSongIcon();
}
setPlayStatus(status: MediaStatus): void {
this.currentPlayStatus = status;
}
}

View File

@@ -1,133 +0,0 @@
import { MediaStatus } from "../models/mediaStatus";
import { RepeatState } from "../models/repeatState";
import { DomTidalController } from "./DomTidalController";
import { TidalController } from "./TidalController";
export class MediaSessionTidalController implements TidalController {
public domMediaController: TidalController;
constructor() {
this.domMediaController = new DomTidalController();
}
// example of using the original domMediaController as a fallback
goToHome(): void {
this.domMediaController.goToHome();
}
setPlayStatus(status: MediaStatus): void {
globalThis.alert("Method not implemented: " + status);
throw new Error("Method not implemented.");
}
getDuration(): string {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
getAlbumName(): string {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
getTitle(): string {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
getArtists(): string[] {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
getArtistsString(): string {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
getPlayingFrom(): string {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
isFavorite(): boolean {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
getSongIcon(): string {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
getCurrentTime(): string {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
getCurrentPosition(): string {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
getCurrentPositionInSeconds(): number {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
getTrackId(): string {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
play(): void {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
pause(): void {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
stop(): void {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
getCurrentShuffleState(): boolean {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
getCurrentRepeatState(): RepeatState {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
getCurrentlyPlayingStatus(): MediaStatus {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
back(): void {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
forward(): void {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
repeat(): void {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
next(): void {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
previous(): void {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
toggleShuffle(): void {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
openSettings(): void {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
toggleFavorite(): void {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
playPause(): void {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
hookup(): void {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
}

View File

@@ -1,47 +0,0 @@
import { MediaStatus } from "../models/mediaStatus";
import { RepeatState } from "../models/repeatState";
export interface TidalController {
goToHome(): void;
openSettings(): void;
/**
* Play or pause the current media
*/
playPause(): void;
play(): void;
pause(): void;
stop(): void;
toggleFavorite(): void;
back(): void;
forward(): void;
repeat(): void;
next(): void;
previous(): void;
toggleShuffle(): void;
/**
* Update the current status of tidal (e.g playing or paused)
*/
getCurrentlyPlayingStatus(): MediaStatus;
getCurrentShuffleState(): boolean;
getCurrentRepeatState(): RepeatState;
getCurrentPosition(): string;
getCurrentPositionInSeconds(): number;
getTrackId(): string;
getCurrentTime(): string;
getDuration(): string;
getAlbumName(): string;
getTitle(): string;
getArtists(): string[];
getArtistsString(): string;
getPlayingFrom(): string;
getSongIcon(): string;
isFavorite(): boolean;
// add an observable to react on instead of a hookup function
// onMediaChange(): any;
// this can probably be removed after ^
setPlayStatus(status: MediaStatus): void;
}

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -21,13 +21,11 @@ import { addHotkey } from "./scripts/hotkeys";
import { ObjectToDotNotation } from "./scripts/objectUtilities";
import { settingsStore } from "./scripts/settings";
import { setTitle } from "./scripts/window-functions";
import { DomTidalController } from "./TidalControllers/DomTidalController";
import { MediaSessionTidalController } from "./TidalControllers/MediaSessionTidalController";
import { TidalController } from "./TidalControllers/TidalController";
const notificationPath = `${app.getPath("userData")}/notification.jpg`;
let currentSong = "";
let player: Player;
let currentPlayStatus = MediaStatus.paused;
let currentListenBrainzDelayId: ReturnType<typeof setTimeout>;
let scrobbleWaitingForDelay = false;
@@ -37,15 +35,150 @@ let currentShuffleState = false;
let currentMediaInfo: MediaInfo;
let currentNotification: Electron.Notification;
let tidalController: TidalController;
const elements = {
play: '*[data-test="play"]',
pause: '*[data-test="pause"]',
next: '*[data-test="next"]',
previous: 'button[data-test="previous"]',
title: '*[data-test^="footer-track-title"]',
artists: '*[data-test^="grid-item-detail-text-title-artist"]',
home: '*[data-test="menu--home"]',
back: '[title^="Back"]',
forward: '[title^="Next"]',
search: '[class^="searchField"]',
shuffle: '*[data-test="shuffle"]',
repeat: '*[data-test="repeat"]',
account: '*[data-test^="profile-image-button"]',
settings: '*[data-test^="sidebar-menu-button"]',
openSettings: '*[data-test^="open-settings"]',
media: '*[data-test="current-media-imagery"]',
image: "img",
current: '*[data-test="current-time"]',
duration: '*[class^=_playbackControlsContainer] *[data-test="duration"]',
bar: '*[data-test="progress-bar"]',
footer: "#footerPlayer",
mediaItem: "[data-type='mediaItem']",
album_header_title: '*[class^="_playingFrom"] span:nth-child(2)',
playing_from: '*[class^="_playingFrom"] span:nth-child(2)',
queue_album: "*[class^=playQueueItemsContainer] *[class^=groupTitle] span:nth-child(2)",
currentlyPlaying: "[class^='isPlayingIcon'], [data-test-is-playing='true']",
album_name_cell: '[class^="album"]',
tracklist_row: '[data-test="tracklist-row"]',
volume: '*[data-test="volume"]',
favorite: '*[data-test="footer-favorite-button"]',
/**
* Get an element from the dom
* @param {*} key key in elements object to fetch
*/
get: function (key: string) {
return window.document.querySelector(this[key.toLowerCase()]);
},
// TODO: replace with setting
// eslint-disable-next-line no-constant-condition
if (true) {
tidalController = new DomTidalController();
} else {
tidalController = new MediaSessionTidalController();
}
/**
* Get the icon of the current media
*/
getSongIcon: function () {
const figure = this.get("media");
if (figure) {
const mediaElement = figure.querySelector(this["image"]);
if (mediaElement) {
return mediaElement.src.replace("80x80", "640x640");
}
}
return "";
},
/**
* returns an array of all artists in the current media
* @returns {Array} artists
*/
getArtistsArray: function () {
const footer = this.get("footer");
if (footer) {
const artists = footer.querySelectorAll(this.artists);
if (artists) return Array.from(artists).map((artist) => (artist as HTMLElement).textContent);
}
return [];
},
/**
* unify the artists array into a string separated by commas
* @param {Array} artistsArray
* @returns {String} artists
*/
getArtistsString: function (artistsArray: string[]) {
if (artistsArray.length > 0) return artistsArray.join(", ");
return "unknown artist(s)";
},
getAlbumName: function () {
//If listening to an album, get its name from the header title
if (window.location.href.includes("/album/")) {
const albumName = window.document.querySelector(this.album_header_title);
if (albumName) {
return albumName.textContent;
}
//If listening to a playlist or a mix, get album name from the list
} else if (
window.location.href.includes("/playlist/") ||
window.location.href.includes("/mix/")
) {
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);
if (row) {
return row.querySelector(this.album_name_cell).textContent;
}
}
}
// see whether we're on the queue page and get it from there
const queueAlbumName = elements.getText("queue_album");
if (queueAlbumName) {
return queueAlbumName;
}
return "";
},
isMuted: function () {
return this.get("volume").getAttribute("aria-checked") === "false"; // it's muted if aria-checked is false
},
isFavorite: function () {
return this.get("favorite").getAttribute("aria-checked") === "true";
},
/**
* Shorthand function to get the text of a dom element
* @param {*} key key in elements object to fetch
*/
getText: function (key: string) {
const element = this.get(key);
return element ? element.textContent : "";
},
/**
* Shorthand function to click a dom element
* @param {*} key key in elements object to fetch
*/
click: function (key: string) {
this.get(key).click();
return this;
},
/**
* Shorthand function to focus a dom element
* @param {*} key key in elements object to fetch
*/
focus: function (key: string) {
return this.get(key).focus();
},
};
/**
* Get the update frequency from the store
@@ -62,6 +195,19 @@ function getUpdateFrequency() {
}
}
/**
* Play or pause the current media
*/
function playPause() {
const play = elements.get("play");
if (play) {
elements.click("play");
} else {
elements.click("pause");
}
}
/**
* Clears the old listenbrainz data on launch
*/
@@ -74,26 +220,30 @@ ListenBrainzStore.clear();
*/
function addHotKeys() {
if (settingsStore.get(settings.enableCustomHotkeys)) {
addHotkey("Control+p", () => {
tidalController.openSettings();
addHotkey("Control+p", function () {
elements.click("settings");
setTimeout(() => {
elements.click("openSettings");
}, 100);
});
addHotkey("Control+l", () => {
addHotkey("Control+l", function () {
handleLogout();
});
addHotkey("Control+a", () => {
tidalController.toggleFavorite();
addHotkey("Control+a", function () {
elements.click("favorite");
});
addHotkey("Control+h", () => {
tidalController.goToHome();
addHotkey("Control+h", function () {
elements.click("home");
});
addHotkey("backspace", function () {
tidalController.back();
elements.click("back");
});
addHotkey("shift+backspace", function () {
tidalController.forward();
elements.click("forward");
});
addHotkey("control+u", function () {
@@ -102,7 +252,7 @@ function addHotKeys() {
});
addHotkey("control+r", function () {
tidalController.repeat();
elements.click("repeat");
});
addHotkey("control+w", async function () {
const url = SharingService.getUniversalLink(getTrackURL());
@@ -169,22 +319,22 @@ function addIPCEventListeners() {
case globalEvents.playPause:
case globalEvents.play:
case globalEvents.pause:
tidalController.playPause();
playPause();
break;
case globalEvents.next:
tidalController.next();
elements.click("next");
break;
case globalEvents.previous:
tidalController.previous();
elements.click("previous");
break;
case globalEvents.toggleFavorite:
tidalController.toggleFavorite();
elements.click("favorite");
break;
case globalEvents.toggleShuffle:
tidalController.toggleShuffle();
elements.click("shuffle");
break;
case globalEvents.toggleRepeat:
tidalController.repeat();
elements.click("repeat");
break;
default:
break;
@@ -193,6 +343,39 @@ function addIPCEventListeners() {
});
}
/**
* Update the current status of tidal (e.g playing or paused)
*/
function getCurrentlyPlayingStatus() {
const pause = elements.get("pause");
let status = undefined;
// if pause button is visible tidal is playing
if (pause) {
status = MediaStatus.playing;
} else {
status = MediaStatus.paused;
}
return status;
}
function getCurrentShuffleState() {
const shuffle = elements.get("shuffle");
return shuffle?.getAttribute("aria-checked") === "true";
}
function getCurrentRepeatState() {
const repeat = elements.get("repeat");
switch (repeat?.getAttribute("data-type")) {
case "button__repeatAll":
return RepeatState.all;
case "button__repeatSingle":
return RepeatState.single;
default:
return RepeatState.off;
}
}
/**
* Convert the duration from MM:SS to seconds
* @param {*} duration
@@ -272,35 +455,16 @@ function addMPRIS() {
const eventValue = events[eventName];
switch (events[eventValue]) {
case events.playpause:
tidalController.playPause();
break;
case events.next:
tidalController.next();
break;
case events.previous:
tidalController.previous();
break;
case events.pause:
tidalController.pause();
break;
case events.stop:
tidalController.stop();
break;
case events.play:
tidalController.play();
break;
case events.loopStatus:
tidalController.repeat();
break;
case events.shuffle:
tidalController.toggleShuffle();
playPause();
break;
default:
elements.click(eventValue);
}
});
});
// Override get position function
player.getPosition = function () {
return tidalController.getCurrentPositionInSeconds();
return convertDuration(elements.getText("current")) * 1000 * 1000;
};
player.on("quit", function () {
app.quit();
@@ -319,9 +483,10 @@ function updateMpris(mediaInfo: MediaInfo) {
"xesam:title": mediaInfo.title,
"xesam:artist": [mediaInfo.artists],
"xesam:album": mediaInfo.album,
"xesam:url": mediaInfo.url,
"mpris:artUrl": mediaInfo.image,
"mpris:length": convertDuration(mediaInfo.duration) * 1000 * 1000,
"mpris:trackid": "/org/mpris/MediaPlayer2/track/" + tidalController.getTrackId(),
"mpris:trackid": "/org/mpris/MediaPlayer2/track/" + getTrackID(),
},
...ObjectToDotNotation(mediaInfo, "custom:"),
};
@@ -364,24 +529,34 @@ function updateListenBrainz(mediaInfo: MediaInfo) {
* If it's a song it returns the track URL, if not it will return undefined
*/
function getTrackURL() {
const id = tidalController.getTrackId();
const id = getTrackID();
return `https://tidal.com/browse/track/${id}`;
}
function getTrackID() {
const URLelement = elements.get("title").querySelector("a");
if (URLelement !== null) {
const id = URLelement.href.replace(/\D/g, "");
return id;
}
return window.location;
}
/**
* Watch for song changes and update title + notify
*/
setInterval(function () {
const title = tidalController.getTitle();
const artistsArray = tidalController.getArtists();
const artistsString = tidalController.getArtistsString();
const title = elements.getText("title");
const artistsArray = elements.getArtistsArray();
const artistsString = elements.getArtistsString(artistsArray);
const songDashArtistTitle = `${title} - ${artistsString}`;
const staticTitle = "TIDAL Hi-Fi";
const titleOrArtistsChanged = currentSong !== songDashArtistTitle;
const current = tidalController.getCurrentTime();
const currentStatus = tidalController.getCurrentlyPlayingStatus();
const shuffleState = tidalController.getCurrentShuffleState();
const repeatState = tidalController.getCurrentRepeatState();
const current = elements.getText("current");
const currentStatus = getCurrentlyPlayingStatus();
const shuffleState = getCurrentShuffleState();
const repeatState = getCurrentRepeatState();
const playStateChanged = currentStatus != currentlyPlaying;
const shuffleStateChanged = shuffleState != currentShuffleState;
@@ -396,13 +571,13 @@ setInterval(function () {
if (repeatStateChanged) currentRepeatState = repeatState;
skipArtistsIfFoundInSkippedArtistsList(artistsArray);
const album = tidalController.getAlbumName();
const duration = tidalController.getDuration();
const album = elements.getAlbumName();
const duration = elements.getText("duration");
const options: MediaInfo = {
title,
artists: artistsString,
album: album,
playingFrom: tidalController.getPlayingFrom(),
playingFrom: elements.getText("playing_from"),
status: currentStatus,
url: getTrackURL(),
current,
@@ -411,7 +586,7 @@ setInterval(function () {
durationInSeconds: convertDurationToSeconds(duration),
image: "",
icon: "",
favorite: tidalController.isFavorite(),
favorite: elements.isFavorite(),
player: {
status: currentStatus,
@@ -426,9 +601,9 @@ setInterval(function () {
: setTitle(songDashArtistTitle);
getTrackURL();
currentSong = songDashArtistTitle;
tidalController.setPlayStatus(currentStatus);
currentPlayStatus = currentStatus;
const image = tidalController.getSongIcon();
const image = elements.getSongIcon();
new Promise<void>((resolve) => {
if (image.startsWith("http")) {
@@ -470,7 +645,7 @@ setInterval(function () {
const artistNames = Object.values(artists).map((artist) => artist);
const foundArtist = artistNames.some((artist) => artistsToSkip.includes(artist));
if (foundArtist) {
tidalController.next();
elements.click("next");
}
}
}

View File

@@ -26,7 +26,7 @@ const defaultPresence = {
largeImageKey: "tidal-hifi-icon",
largeImageText: `TIDAL Hi-Fi ${app.getVersion()}`,
instance: false,
type: ACTIVITY_LISTENING,
type: ACTIVITY_LISTENING
};
const updateActivity = () => {
@@ -103,8 +103,7 @@ const getActivity = (): SetActivity => {
if (includeTimestamps) {
const currentSeconds = convertDurationToSeconds(mediaInfo.current);
const durationSeconds = convertDurationToSeconds(mediaInfo.duration);
const date = new Date();
const now = Math.floor(date.getTime() / 1000);
const now = Math.trunc((Date.now() + 500) / 1000);
presence.startTimestamp = now - currentSeconds;
presence.endTimestamp = presence.startTimestamp + durationSeconds;
}
@@ -118,17 +117,15 @@ const getActivity = (): SetActivity => {
const connectWithRetry = async (retryCount = 0) => {
try {
await rpc.login();
Logger.log("Connected to Discord");
Logger.log('Connected to Discord');
rpc.on("ready", updateActivity);
Object.values(globalEvents).forEach((event) => ipcMain.on(event, observer));
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})`
);
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");
Logger.log('Failed to connect to Discord after maximum retry attempts');
}
}
};
@@ -137,7 +134,7 @@ const connectWithRetry = async (retryCount = 0) => {
* Set up the discord rpc and listen on globalEvents.updateInfo
*/
export const initRPC = () => {
rpc = new Client({ transport: { type: "ipc" }, clientId });
rpc = new Client({ transport: {type: "ipc"}, clientId });
connectWithRetry();
};