Compare commits

...

107 Commits
5.2.0 ... 5.9.0

Author SHA1 Message Date
21cb0ea79d Merge pull request #343 from Mastermindzh/5.9.0
5.9.0
2024-02-12 22:56:05 +01:00
49bc737485 Made sure settingsWindow exists before operating on it. fixes #344 2024-02-12 22:37:03 +01:00
10a4af8e90 icons 2024-02-11 23:37:28 +01:00
317a685813 Fixed chromium mediaSession instance showing up. fixes #338 2024-02-11 23:17:20 +01:00
4da6d9feda Merge pull request #332 from TheRockYT/master
More customizable Discord-Presence
2024-02-11 22:55:06 +01:00
b11dbbd6d8 - More Discord options:
- Added the ability to hide the current song from the discord activity and display a custom text instead
  - Added the ability to customize the text that is shown when no song is playing
  - Discord now reacts to pausing/unpausing events
- Refactored media info updates so it only updates the required info, fixes #342, #306
- Added 5.9.0 logs/versions/migrations
2024-02-11 22:42:45 +01:00
887a3d8a45 Merge pull request #341 from Mastermindzh/snyk-upgrade-e734c4e044f11fcf328091d8b0248af0
[Snyk] Upgrade sass from 1.69.7 to 1.70.0
2024-02-10 10:44:13 +01:00
snyk-bot
2e17b066a3 fix: upgrade sass from 1.69.7 to 1.70.0
Snyk has created this PR to upgrade sass from 1.69.7 to 1.70.0.

See this package in npm:
https://www.npmjs.com/package/sass

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-02-08 18:52:27 +00:00
TheRockYT
e37b2f99cc Merge branch 'Mastermindzh:master' into master 2024-02-07 09:53:03 +01:00
1afd4d22a6 Merge pull request #339 from Mastermindzh/snyk-upgrade-09d7345c3d57cddc47b571cbaecbf842
[Snyk] Upgrade hotkeys-js from 3.13.3 to 3.13.5
2024-02-06 09:18:00 +01:00
snyk-bot
12a919df45 fix: upgrade hotkeys-js from 3.13.3 to 3.13.5
Snyk has created this PR to upgrade hotkeys-js from 3.13.3 to 3.13.5.

See this package in npm:
https://www.npmjs.com/package/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
2024-02-04 17:49:06 +00:00
TheRockYT
36a2367397 Implemented the logic in the discord.ts file as mentioned in earlier commits 2024-01-08 21:13:02 +01:00
TheRockYT
7c6d2df16a The setting "discord_include_timestamps" is now only shown if "discord_show_song_options" is enabled 2024-01-08 21:06:55 +01:00
TheRockYT
22383a9f45 Made order of settings more logical:
The settings now appear directly below the show song switch
2024-01-08 20:31:27 +01:00
TheRockYT
623033ccd7 Added discord options: showSong, idleText, listeningText
showSong (boolean): If enabled, the client will show the current song on discord.

idleText (string): This text is shown if no song is playing.

listeningText (string): This text is shown if a song is playing, but showSong is set to false.
2024-01-08 20:23:26 +01:00
5bd28913da Merge pull request #331 from Mastermindzh/feature/electron-28-and-like-api-call
5.8.0
2024-01-07 16:01:17 +01:00
5240f1eeeb fixed the discord end time stamp issue. fixes #282 2024-01-07 15:42:00 +01:00
5e82c18d8a added functionality to favorite a song. fixes #323 2024-01-07 14:58:49 +01:00
1d19857977 feat: updated to electron 28. fixes #325 2024-01-07 14:23:21 +01:00
98f75418eb Merge pull request #328 from Mastermindzh/snyk-fix-5b834b0a172c91c6dbd6e4c6b03d9877
[Snyk] Security upgrade axios from 1.6.1 to 1.6.4
2024-01-05 17:08:50 +01:00
snyk-bot
0d1a533f71 fix: package.json & package-lock.json to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-AXIOS-6144788
2024-01-05 14:45:13 +00:00
000bade444 Merge pull request #318 from Mastermindzh/snyk-upgrade-a7dcb80854fc3341d7e4a4c9b2d140dd
[Snyk] Upgrade hotkeys-js from 3.12.0 to 3.12.1
2024-01-03 11:13:26 +01:00
69f2e26ca9 Merge pull request #322 from Mastermindzh/snyk-fix-ac9b86db7e6eed757b21e57b4b9f4d50
[Snyk] Security upgrade axios from 1.6.1 to 1.6.3
2023-12-27 18:46:12 +01:00
snyk-bot
60d7da4652 fix: package.json to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-AXIOS-6124857
2023-12-27 17:22:47 +00:00
snyk-bot
6ef6bc0d40 fix: upgrade hotkeys-js from 3.12.0 to 3.12.1
Snyk has created this PR to upgrade hotkeys-js from 3.12.0 to 3.12.1.

See this package in npm:
https://www.npmjs.com/package/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
2023-12-16 18:05:45 +00:00
89592bcf4d Merge pull request #315 from Mastermindzh/snyk-upgrade-5b80ec2bed8c1f70adc33a0a4a10d7e0
[Snyk] Upgrade sass from 1.68.0 to 1.69.5
2023-12-11 09:51:26 +01:00
f5185c6627 Merge pull request #316 from Mastermindzh/snyk-upgrade-c73d2072bad8c0ab65966f379416a611
[Snyk] Upgrade @electron/remote from 2.0.10 to 2.1.0
2023-12-11 09:51:15 +01:00
snyk-bot
a01fcd0791 fix: upgrade @electron/remote from 2.0.10 to 2.1.0
Snyk has created this PR to upgrade @electron/remote from 2.0.10 to 2.1.0.

See this package in npm:
https://www.npmjs.com/package/@electron/remote

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
2023-12-06 21:30:41 +00:00
snyk-bot
69eef58f8e fix: upgrade sass from 1.68.0 to 1.69.5
Snyk has created this PR to upgrade sass from 1.68.0 to 1.69.5.

See this package in npm:
https://www.npmjs.com/package/sass

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
2023-12-06 21:30:36 +00:00
ff060a31e5 Merge pull request #303 from Mastermindzh/snyk-upgrade-f365250579f36ba9d4c0e6f3c2d02d6c
[Snyk] Upgrade sass from 1.67.0 to 1.68.0
2023-12-05 15:23:41 +01:00
cbc7fc4a4e Merge pull request #308 from Strum355/bump-settings-html-version
Bump tidal version string in settings.html
2023-12-05 15:23:24 +01:00
173c502143 Merge pull request #313 from Mastermindzh/snyk-upgrade-3196835d8546696d8f3d985ff9b04773
[Snyk] Upgrade axios from 1.6.0 to 1.6.1
2023-12-05 15:22:24 +01:00
snyk-bot
5111af6a71 fix: upgrade axios from 1.6.0 to 1.6.1
Snyk has created this PR to upgrade axios from 1.6.0 to 1.6.1.

See this package in npm:
https://www.npmjs.com/package/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
2023-11-29 16:31:32 +00:00
Noah Santschi-Cooney
f094139794 Bump tidal version string in settings.html 2023-11-08 12:58:37 +00:00
b3d9b187c1 Merge pull request #304 from Mastermindzh/snyk-fix-200f82c042bab393c56350118c69a58e
[Snyk] Security upgrade axios from 1.5.1 to 1.6.0
2023-10-28 22:28:30 +02:00
snyk-bot
276632ea9d fix: package.json & package-lock.json to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-AXIOS-6032459
2023-10-27 17:23:08 +00:00
snyk-bot
c298b73773 fix: upgrade sass from 1.67.0 to 1.68.0
Snyk has created this PR to upgrade sass from 1.67.0 to 1.68.0.

See this package in npm:
https://www.npmjs.com/package/sass

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
2023-10-24 06:35:47 +00:00
d14dbad9ea Merge pull request #298 from Mastermindzh/snyk-upgrade-3e4ac8a7e4360e5de1c0538d47c8656a
[Snyk] Upgrade sass from 1.66.1 to 1.67.0
2023-10-23 12:50:08 +02:00
9e8f6a61f3 Merge pull request #299 from Mastermindzh/dependabot/npm_and_yarn/postcss-8.4.31
chore(deps-dev): bump postcss from 8.4.25 to 8.4.31
2023-10-23 12:49:56 +02:00
387a544b0f Merge pull request #301 from Mastermindzh/snyk-upgrade-1427d58228aba014cc2ab24ae2c5a2b0
[Snyk] Upgrade axios from 1.5.0 to 1.5.1
2023-10-23 12:49:47 +02:00
snyk-bot
76fa8de96c fix: upgrade axios from 1.5.0 to 1.5.1
Snyk has created this PR to upgrade axios from 1.5.0 to 1.5.1.

See this package in npm:
https://www.npmjs.com/package/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
2023-10-18 04:44:56 +00:00
dependabot[bot]
fc2d5d20ca chore(deps-dev): bump postcss from 8.4.25 to 8.4.31
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.25 to 8.4.31.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.25...8.4.31)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-07 22:57:03 +00:00
snyk-bot
9ba2d0fe26 fix: upgrade sass from 1.66.1 to 1.67.0
Snyk has created this PR to upgrade sass from 1.66.1 to 1.67.0.

See this package in npm:
https://www.npmjs.com/package/sass

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
2023-10-05 04:01:35 +00:00
d94d42e2bd Merge pull request #294 from Mastermindzh/snyk-upgrade-824dc5a15cb4adae0ec625cbc576dbfb
[Snyk] Upgrade sass from 1.64.2 to 1.66.1
2023-09-20 13:51:08 +02:00
snyk-bot
00db9f753e fix: upgrade sass from 1.64.2 to 1.66.1
Snyk has created this PR to upgrade sass from 1.64.2 to 1.66.1.

See this package in npm:
https://www.npmjs.com/package/sass

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
2023-09-19 04:49:03 +00:00
696a2730be Merge pull request #278 from Mastermindzh/snyk-upgrade-8ee3a208d13f6a6df49e7a13d3e6ac99
[Snyk] Upgrade sass from 1.64.1 to 1.64.2
2023-09-18 09:32:58 +02:00
6a5814c446 Merge pull request #293 from Mastermindzh/snyk-upgrade-9d38cbb1535d5013d209793378f2adbd
[Snyk] Upgrade axios from 1.4.0 to 1.5.0
2023-09-18 09:32:40 +02:00
snyk-bot
2326c6dd6a fix: upgrade axios from 1.4.0 to 1.5.0
Snyk has created this PR to upgrade axios from 1.4.0 to 1.5.0.

See this package in npm:
https://www.npmjs.com/package/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
2023-09-17 07:09:49 +00:00
0dadce4596 Merge pull request #287 from Mastermindzh/bugfix/mpris-not-detected
fix: Fixed mpris not being set up correctly due to capitalization of …
2023-09-11 20:24:29 +02:00
9e2cbaed38 fix: Fixed mpris not being set up correctly due to capitalization of the instance name 2023-09-11 20:09:27 +02:00
33070c157a Merge pull request #281 from Mastermindzh/feature/update
5.7.0
2023-08-28 22:17:37 +02:00
eb91b66ac6 feat: Custom CSS now also applies to settings window 2023-08-28 16:38:08 +02:00
68f76a9e63 feat: The ListenBrainz integration has been extended with a configurable (5 seconds by default) delay in song reporting 2023-08-28 14:19:12 +02:00
cbb22ba688 fix: removed calls to restart function 2023-08-28 11:14:13 +02:00
3df82b93db Merge pull request #279 from Mastermindzh/snyk-upgrade-e43be58e4e7a5f073bb845196cc13d2b
[Snyk] Upgrade hotkeys-js from 3.11.2 to 3.12.0
2023-08-24 10:23:17 +02:00
snyk-bot
789ba83936 fix: upgrade hotkeys-js from 3.11.2 to 3.12.0
Snyk has created this PR to upgrade hotkeys-js from 3.11.2 to 3.12.0.

See this package in npm:
https://www.npmjs.com/package/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
2023-08-23 23:27:39 +00:00
a962029b0b Restyled settings menu to include version number and useful links on the about page. fixes #275 2023-08-23 20:40:02 +02:00
snyk-bot
b3fffc78ec fix: upgrade sass from 1.64.1 to 1.64.2
Snyk has created this PR to upgrade sass from 1.64.1 to 1.64.2.

See this package in npm:
https://www.npmjs.com/package/sass

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
2023-08-22 17:35:19 +00:00
d8e4a493b9 feat: Added settings to customize the Discord rich presence information 2023-08-21 16:24:16 +02:00
3d0b38361a chore: naming 2023-08-20 15:43:11 +02:00
1c7385fa50 Merge pull request #276 from Mar0xy/docs/update-picture
Update integrations picture
2023-08-20 14:26:05 +02:00
510812f384 Merge branch 'feature/update' into docs/update-picture 2023-08-20 14:25:56 +02:00
5144f67fbc fixing build issues 2023-08-20 14:08:08 +02:00
81b81580c6 new icon look 2023-08-20 12:10:23 +02:00
534547ce67 Merge branch 'master' of github.com:Mastermindzh/tidal-hifi into feature/update 2023-08-20 11:28:36 +02:00
Marie
1610e3cc05 merge conflict 101 2023-08-19 19:51:23 +02:00
Marie
e50e7de12e Merge branch 'feature/update' into docs/update-picture 2023-08-19 19:45:51 +02:00
Marie
42be522b8e Update integrations picture 2023-08-19 07:22:09 +02:00
40d80e0872 made sure all windows run with the same web preferences set 2023-08-14 21:26:09 +02:00
239139e674 updated name to TIDAL Hi-Fi 2023-08-14 21:20:53 +02:00
dc87b20ab8 Merge pull request #265 from Mastermindzh/feature/5.6.0
Feature/5.6.0
2023-08-12 15:42:27 +02:00
c7b3921514 Added app suspension inhibitors when music is playing. fixes #257 2023-08-12 15:02:23 +02:00
89f1ff4228 Create SECURITY.md 2023-08-12 14:30:10 +02:00
a0c73596e4 feat: added wayland support. fixes #262 #157 2023-08-07 20:48:29 +02:00
aa17d80450 added new quality names to readme + added neptune mention. fixes #261 2023-08-07 20:28:14 +02:00
5ea3972053 fixed errors with user theme files loading 2023-08-07 20:04:06 +02:00
4b81378423 fixed feature flag parsing & setting 2023-08-07 19:48:29 +02:00
c7931cf913 simplified logger 2023-08-07 19:45:44 +02:00
c6dff0b0e5 Merge pull request #260 from Mastermindzh/5.5.0
5.5.0
2023-07-31 21:13:39 +02:00
644beea2a6 fixed listenbrainz link 2023-07-31 21:13:24 +02:00
df1c45982b 5.5.0 docs, versions, etc 2023-07-31 15:49:29 +02:00
ec82aa8401 Merge pull request #258 from Mar0xy/master2
Add ListenBrainz implementation
2023-07-31 15:06:39 +02:00
586f7b595b various code improvements and some boyscout rule fixes :) 2023-07-31 13:43:32 +02:00
Mar0xy
de8a5a1b07 Fix bug where it does not run if condition 2023-07-31 12:14:06 +02:00
Mar0xy
38c1f05c35 Allow listenbrainz to be triggered on every play 2023-07-31 12:06:31 +02:00
Mar0xy
ed6f04b6d4 Fix bug where it does not unhide 2023-07-30 21:25:35 +02:00
Mar0xy
ffe8278c8c Fix complainy by sonarcloud 2023-07-30 21:22:38 +02:00
Mar0xy
e9434cc5ea Hide/Show ListenBrainz settings 2023-07-30 21:18:25 +02:00
Marie
d81912db0c Fix music_service domain 2023-07-30 11:46:27 +02:00
Mar0xy
c0110632e6 Seperate old ListenBrainz data from config 2023-07-30 10:42:32 +02:00
Mar0xy
3571289d28 Add ListenBrainz implementation 2023-07-30 02:38:38 +02:00
11cc209025 Merge pull request #255 from Mastermindzh/feature/5.4.0
Feature/5.4.0
2023-07-24 21:59:57 +02:00
f5ccbda7d9 set versions to 5.4.0 2023-07-24 12:04:08 +02:00
e8cf1783e8 chore(docs): updated README spelling 2023-07-23 23:59:45 +02:00
8037a73e57 Merge branch 'feature/5.4.0' of github.com:Mastermindzh/tidal-hifi into feature/5.4.0 2023-07-23 23:51:24 +02:00
45e191dae0 updated dependencies 2023-07-23 23:51:20 +02:00
f147536b12 updated dependencies 2023-07-23 23:43:28 +02:00
d03bb58afa removed windows builds from publishes 2023-07-23 23:20:01 +02:00
a39fef8d49 fix(hotkeys): Fixed bug with several hotkeys not working due to Tidal's HTML/css changes. fixes #250 2023-07-23 23:13:37 +02:00
41ca1d5a43 added songwhip 2023-07-23 23:12:18 +02:00
6969de8270 added several dev improvements 2023-07-23 23:07:19 +02:00
ad05b767d8 Merge pull request #245 from Mastermindzh/dependabot/npm_and_yarn/stylelint-15.10.1
chore(deps-dev): bump stylelint from 15.6.0 to 15.10.1
2023-07-09 00:38:31 +02:00
dependabot[bot]
6d873ce287 chore(deps-dev): bump stylelint from 15.6.0 to 15.10.1
Bumps [stylelint](https://github.com/stylelint/stylelint) from 15.6.0 to 15.10.1.
- [Release notes](https://github.com/stylelint/stylelint/releases)
- [Changelog](https://github.com/stylelint/stylelint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stylelint/stylelint/compare/15.6.0...15.10.1)

---
updated-dependencies:
- dependency-name: stylelint
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-07 22:30:49 +00:00
63d123f96a Merge pull request #241 from Mastermindzh/release/5.3.0
Release/5.3.0
2023-06-24 13:05:52 +02:00
f038412c50 release 5.3.0 2023-06-24 12:41:41 +02:00
ff02287df7 Merge pull request #240 from SPKChaosPhoenix/patch-1
Update Tokyo Night.scss
2023-06-23 15:19:31 +02:00
Marces
f221ded108 Update Tokyo Night.scss
Updatet Tokyo Night to work with the newest version of Tidal.
2023-06-22 16:12:41 +02:00
67 changed files with 3303 additions and 2805 deletions

View File

@@ -13,4 +13,4 @@ steps:
commands: commands:
- apt-get update && apt-get upgrade -y - apt-get update && apt-get upgrade -y
- apt-get install -y libarchive-tools rpm - apt-get install -y libarchive-tools rpm
- npm run build - npm run build-unpacked

View File

@@ -1,5 +1,9 @@
{ {
"root": true, "root": true,
"env": {
"node": true,
"browser": true
},
"parser": "@typescript-eslint/parser", "parser": "@typescript-eslint/parser",
"plugins": [ "plugins": [
"@typescript-eslint" "@typescript-eslint"

View File

@@ -5,6 +5,9 @@
"extends": [ "extends": [
"stylelint-config-standard-scss" "stylelint-config-standard-scss"
], ],
"ignoreFiles": [
"src/themes/**.scss"
],
"rules": { "rules": {
"prettier/prettier": true, "prettier/prettier": true,
"scss/at-extend-no-missing-placeholder": null, "scss/at-extend-no-missing-placeholder": null,

15
.vscode/settings.json vendored
View File

@@ -1,13 +1,26 @@
{ {
"cSpell.words": [ "cSpell.words": [
"Brainz",
"Castlabs",
"flac", "flac",
"Flatpak",
"geqnfr", "geqnfr",
"hifi", "hifi",
"libnotify",
"listenbrainz",
"playpause", "playpause",
"prs",
"rescrobbler", "rescrobbler",
"scrobble",
"scrobbling",
"Songwhip",
"trackid", "trackid",
"tracklist", "tracklist",
"widevine", "widevine",
"xesam" "xesam"
] ],
"sonarlint.connectedMode.project": {
"connectionId": "public-sonarcloud",
"projectKey": "Mastermindzh_tidal-hifi"
}
} }

View File

@@ -4,6 +4,84 @@ 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.9.0]
- More Discord options:
- Added the ability to hide the current song from the discord activity and display a custom text instead
- Added the ability to customize the text that is shown when no song is playing
- Discord now reacts to pausing/unpausing events
- Refactored media info updates so it only updates the required info, fixes #342, #306
- Added 5.9.0 logs/versions/migrations
### Fixed
- Fixed chromium mediaSession instance showing up. fixes #338 #198
- Set a new icon, should fix #302
- Made sure settingsWindow exists before operating on it. fixes #344
## [5.8.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`
- Fixed wrong "end time stamp" for currently playing song (fixes [#282](https://github.com/Mastermindzh/tidal-hifi/issues/282))
- Affected the API + all integrations
- As requested we also added toggle to sync the timestamps to Discord (default = true)
## [5.7.1]
- Fixed mpris not being set up correctly due to capitalization of the instance name.
## [5.7.0]
- Renamed app to TIDAL Hi-Fi.
- Made sure all windows run with the same web preferences set (compared to main app).
- Fixes the last.fm bug.
- 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 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)
## [5.6.0]
- Added support for Wayland (on by default) fixes [#262](https://github.com/Mastermindzh/tidal-hifi/issues/262) and [#157](https://github.com/Mastermindzh/tidal-hifi/issues/157)
- Made it clear in the readme that this TIDAL Hi-Fi client supports High & Max audio settings. fixes [#261](https://github.com/Mastermindzh/tidal-hifi/issues/261)
- Added app suspension inhibitors when music is playing. fixes [#257](https://github.com/Mastermindzh/tidal-hifi/issues/257)
- Fixed bug with theme files from user directory trying to load: "an error occurred reading the theme file"
- Fixed: config flags not being set correctly
- [DEV]:
- Logger is now static and will automatically call either ipcRenderer or ipcMain
## 5.5.0
- ListenBrainz integration added (thanks @Mar0xy)
## 5.4.0
- Removed Windows builds (from publishes) as they don't work anymore.
- Added [Songwhip](https://songwhip.com/) integration
- Fixed bug with several hotkeys not working due to Tidal's HTML/css changes
- [DEV]:
- added a logger to log into STDout
- added "watchStart" which will automatically restart electron when it detects a source code change
- added "listen.tidal.com-parsing-scripts" folder with a script to verify whether all elements (in the main preload.ts) are present on the page
## 5.3.0
- SPKChaosPhoenix updated the beautiful Tokyo Night theme:
![tidal with the tokyo night theme applied](./docs/images/tokyo-night.png)
## 5.2.0 ## 5.2.0
- moved from Javascript to Typescript for all files - moved from Javascript to Typescript for all files
@@ -59,7 +137,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- New settings window by BlueManCZ - New settings window by BlueManCZ
- Fixed the desktop files in electron-builder - Fixed the desktop files in electron-builder
- icon is set to new static path based on Arch/Debian - icon is set to new static path based on Arch/Debian
- Name has changed to Tidal-Hifi - Name has changed to TIDAL Hi-Fi
## 4.1.2 ## 4.1.2
@@ -107,7 +185,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Updated to Electron 15 - Updated to Electron 15
- Fixed the develop "build-unpacked" command - Fixed the develop "build-unpacked" command
- Added setting to disable multiple tidal-hifi windows (defaults to true) - Added setting to disable multiple TIDAL Hi-Fi windows (defaults to true)
- Added setting to disable HardwareMediaKeyHandling (defaults to false) - Added setting to disable HardwareMediaKeyHandling (defaults to false)
## 2.8.2 ## 2.8.2
@@ -145,7 +223,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## 2.5.0 ## 2.5.0
- Notify-send now correctly shows "Tidal HiFi" as the program name - Notify-send now correctly shows "Tidal Hi-Fi" as the program name
- Updated dependencies (including electron itself) - Updated dependencies (including electron itself)
### known issues ### known issues

View File

@@ -1,20 +1,20 @@
# Tidal-hifi<img src = "./build/icon.png" height="40" align="right"/> # TIDAL Hi-Fi (Max quality)<img src = "./build/icon.png" height="40" align="right"/>
![GitHub release](https://img.shields.io/github/release/Mastermindzh/tidal-hifi.svg) [![github builds](https://github.com/mastermindzh/tidal-hifi/actions/workflows/build.yml/badge.svg)](https://github.com/Mastermindzh/tidal-hifi/actions) [![Build Status](https://ci.mastermindzh.tech/api/badges/Mastermindzh/tidal-hifi/status.svg)](https://ci.mastermindzh.tech/Mastermindzh/tidal-hifi) [![Discord logo](./docs/images/discord.png)](https://discord.gg/yhNwf4v4He) ![GitHub release](https://img.shields.io/github/release/Mastermindzh/tidal-hifi.svg) [![github builds](https://github.com/mastermindzh/tidal-hifi/actions/workflows/build.yml/badge.svg)](https://github.com/Mastermindzh/tidal-hifi/actions) [![Build Status](https://ci.mastermindzh.tech/api/badges/Mastermindzh/tidal-hifi/status.svg)](https://ci.mastermindzh.tech/Mastermindzh/tidal-hifi) [![Discord logo](./docs/images/discord.png)](https://discord.gg/yhNwf4v4He)
The web version of [listen.tidal.com](https://listen.tidal.com) running in electron with hifi support thanks to widevine. The web version of [listen.tidal.com](https://listen.tidal.com) running in electron with Hi-Fi (High & Max) support thanks to widevine.
![tidal-hifi preview](./docs/images/preview.png) ![TIDAL Hi-Fi preview](./docs/images/preview.png)
## Table of Contents ## Table of Contents
<!-- toc --> <!-- toc -->
- [Tidal-hifi](#tidal-hifi) - [TIDAL Hi-Fi (Max quality)](#tidal-hi-fi-max-quality)
- [Table of Contents](#table-of-contents) - [Table of Contents](#table-of-contents)
- [Features](#features) - [Features](#features)
- [Contributions](#contributions) - [Contributions](#contributions)
- [Why did I create tidal-hifi?](#why-did-i-create-tidal-hifi) - [Why did I create TIDAL Hi-Fi?](#why-did-i-create-tidal-hi-fi)
- [Why not extend existing projects?](#why-not-extend-existing-projects) - [Why not extend existing projects?](#why-not-extend-existing-projects)
- [Installation](#installation) - [Installation](#installation)
- [Dependencies](#dependencies) - [Dependencies](#dependencies)
@@ -26,7 +26,7 @@ The web version of [listen.tidal.com](https://listen.tidal.com) running in elect
- [Using source](#using-source) - [Using source](#using-source)
- [Integrations](#integrations) - [Integrations](#integrations)
- [Known bugs](#known-bugs) - [Known bugs](#known-bugs)
- [last.fm doesn't work out of the box. Use rescrobbler as a workaround](#lastfm-doesnt-work-out-of-the-box-use-rescrobbler-as-a-workaround) - [DRM not working on Windows](#drm-not-working-on-windows)
- [Special thanks to](#special-thanks-to) - [Special thanks to](#special-thanks-to)
- [Donations](#donations) - [Donations](#donations)
- [Images](#images) - [Images](#images)
@@ -37,27 +37,33 @@ The web version of [listen.tidal.com](https://listen.tidal.com) running in elect
## Features ## Features
- HiFi playback - HiFi playback (High & Max settings)
- Notifications - Notifications
- Custom [theming](./docs/theming.md) - Custom [theming](./docs/theming.md)
- Custom hotkeys ([source](https://defkey.com/tidal-desktop-shortcuts)) - Custom hotkeys ([source](https://defkey.com/tidal-desktop-shortcuts))
- Better icons thanks to [Papirus-icon-theme](https://github.com/PapirusDevelopmentTeam/papirus-icon-theme/)
- [Settings feature](./docs/images/settings.png) to disable certain functionality. (`ctrl+=` or `ctrl+0`)
- API for status and playback - API for status and playback
- Disabled audio & visual ads, unlocked lyrics, suggested track, track info, and unlimited skips thanks to uBlockOrigin custom filters ([source](https://github.com/uBlockOrigin/uAssets/issues/17495)) - Disabled audio & visual ads, unlocked lyrics, suggested track, track info, and unlimited skips thanks to uBlockOrigin custom filters ([source](https://github.com/uBlockOrigin/uAssets/issues/17495))
- Custom [integrations](#integrations)
- [Settings feature](./docs/images/settings.png) to disable certain functionality. (`ctrl+=` or `ctrl+0`)
- 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)
- [ListenBrainz](https://listenbrainz.org/?redirect=false) integration
- Songwhip.com integration (hotkey `ctrl + w`)
- Discord RPC integration (showing "now listening", "Browsing", etc)
- MPRIS integration
- UI + Json config (`~/.config/tidal-hifi/`, or `~/.var/app/com.mastermindzh.tidal-hifi/` for Flatpak)
## Contributions ## Contributions
To contribute you can use the standard GitHub features (issues, prs, etc) or join the discord server to talk with like-minded individuals. To contribute you can use the standard GitHub features (issues, prs, etc.) or join the discord server to talk with like-minded individuals.
- ![Discord logo](./docs/images/discord.png) [Join the Discord server](https://discord.gg/yhNwf4v4He) - ![Discord logo](./docs/images/discord.png) [Join the Discord server](https://discord.gg/yhNwf4v4He)
## Why did I create tidal-hifi? ## Why did I create TIDAL Hi-Fi?
I moved from Spotify over to Tidal and found Linux support to be lacking. I moved from Spotify over to Tidal and found Linux support to be lacking.
When I started this project there weren't any Linux apps that offered Tidal's "hifi" options nor any scripts to control it. When I started this project there weren't any Linux apps that offered Tidal's "hifi" options nor any scripts to control it.
I made this app to support the highest quality audio available on the Linux platform. It used to be "hifi" but now is ["High & Max"](https://tidal.com/sound-quality).
### Why not extend existing projects? ### Why not extend existing projects?
@@ -65,10 +71,10 @@ Whilst there are a handful of projects attempting to run Tidal on Electron they
- Lack of maintainers/developers. (no hotfixes, no issues being handled etc) - Lack of maintainers/developers. (no hotfixes, no issues being handled etc)
- Most are simple web wrappers, not my cup of tea. - Most are simple web wrappers, not my cup of tea.
- Some are DE oriented. I want this to work on WM's too. - Some are DE-oriented. I want this to work on WM's too.
- None have widevine working at the moment - None have Widevine working at the moment
Sometimes it's just easier to start over, cover my own needs and then making it available to the public :) Sometimes it's just easier to start over, cover my own needs and after that making it available to the public :)
## Installation ## Installation
@@ -98,10 +104,10 @@ To install with `snap` you need to download the pre-packaged snap-package from t
### Arch Linux ### Arch Linux
Arch Linux users can use the AUR to install tidal-hifi: Arch Linux users can use the AUR to install TIDAL Hi-Fi:
```sh ```sh
trizen tidal-hifi-bin trizen tidal-hifi-git
``` ```
### Flatpak ### Flatpak
@@ -125,33 +131,28 @@ nix-env -iA nixpkgs.tidal-hifi
To install and work with the code on this project follow these steps: To install and work with the code on this project follow these steps:
- git clone [https://github.com/Mastermindzh/tidal-hifi.git](https://github.com/Mastermindzh/tidal-hifi.git) - git clone [https://github.com/Mastermindzh/tidal-hifi.git](https://github.com/Mastermindzh/tidal-hifi.git)
- cd tidal-hifi - cd TIDAL Hi-Fi
- npm install - npm install
- npm start - npm start
## Integrations ## Integrations
Tidal-hifi comes with several integrations out of the box. TIDAL Hi-Fi comes with several integrations out of the box.
You can find these in the settings menu (`ctrl + =` by default) under the "integrations" tab. You can find these in the settings menu (`ctrl + =` by default) under the "integrations" tab.
![integrations menu, showing a list of integrations](./docs/images/integrations.png) ![integrations menu, showing a list of integrations](./docs/images/integrations.png)
It currently includes: Integrations with other projects that are not included natively:
- MPRIS - MPRIS media player controls/status
- Discord - Shows what you're listening to on Discord.
Not included:
- [i3 blocks config](https://github.com/Mastermindzh/dotfiles/commit/9714b2fa1d670108ce811d5511fd3b7a43180647) - My dotfiles where I use this app to fetch currently playing music (direct commit) - [i3 blocks config](https://github.com/Mastermindzh/dotfiles/commit/9714b2fa1d670108ce811d5511fd3b7a43180647) - My dotfiles where I use this app to fetch currently playing music (direct commit)
- [neptune](https://github.com/uwu/neptune) third party plugins & theming
### Known bugs ## Known bugs
#### last.fm doesn't work out of the box. Use rescrobbler as a workaround ### DRM not working on Windows
The last.fm login doesn't work, as is evident from the following issue: [Last.fm login doesn't work](https://github.com/Mastermindzh/tidal-hifi/issues/4). Most Windows users run into DRM issues when trying to use TIDAL Hi-Fi.
However, in that same issue you can read about a workaround using [rescrobbler](https://github.com/InputUsername/rescrobbled). Nothing I can do about that I'm afraid... Tidal is working on removing/changing DRM so when they finish with that we can give it another shot.
For now that will be the default workaround.
## Special thanks to ## Special thanks to

11
SECURITY.md Normal file
View File

@@ -0,0 +1,11 @@
# Security Policy
## Supported Versions
Only the very latest 😄.
## Reporting a Vulnerability
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).

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -1,7 +1,7 @@
appId: com.rickvanlieshout.tidal-hifi appId: com.rickvanlieshout.tidal-hifi
electronVersion: 24.1.2 electronVersion: 28.1.1
electronDownload: electronDownload:
version: 24.1.2+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:
@@ -11,7 +11,7 @@ extraResources:
- "themes/**" - "themes/**"
linux: linux:
category: AudioVideo category: AudioVideo
icon: assets/icons icon: build/icons/256x256.png
target: target:
- dir - dir
executableName: tidal-hifi executableName: tidal-hifi

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 24 KiB

BIN
build/icon.icns Executable file → Normal file

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 32 KiB

BIN
build/icons/256x256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
build/icons/icon.icns Normal file

Binary file not shown.

BIN
build/icons/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 63 KiB

BIN
docs/images/new-about.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

BIN
docs/images/tokyo-night.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

View File

@@ -1,10 +1,10 @@
# Theming tidal-hifi # Theming TIDAL Hi-Fi
## Table of contents ## Table of contents
<!-- toc --> <!-- toc -->
- [Theming tidal-hifi](#theming-tidal-hifi) - [Theming TIDAL Hi-Fi](#theming-TIDAL Hi-Fi)
- [Table of contents](#table-of-contents) - [Table of contents](#table-of-contents)
- [Custom CSS](#custom-css) - [Custom CSS](#custom-css)
- [config](#config) - [config](#config)
@@ -12,7 +12,7 @@
<!-- tocstop --> <!-- tocstop -->
By default tidal-hifi comes with a few themes. By default TIDAL Hi-Fi comes with a few themes.
You can select these in the settings window under the theming tab as shown below. You can select these in the settings window under the theming tab as shown below.
![Settings window with the theming tab opened](./images/theming.png) ![Settings window with the theming tab opened](./images/theming.png)

4060
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,13 @@
{ {
"name": "tidal-hifi", "name": "tidal-hifi",
"version": "5.2.0", "version": "5.9.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 .", "start": "electron --inspect=0.0.0.0:5858 .",
"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",
"watch": "tsc-watch --onSuccess \"npm run sass-and-copy\"", "watch": "tsc-watch --onSuccess \"npm run sass-and-copy\"",
"copy-files": "copyfiles -u 1 --exclude './src/**/*.ts' --exclude './src/**/*.scss' \"./src/**/*\" ts-dist", "copy-files": "copyfiles -u 1 --exclude './src/**/*.ts' --exclude './src/**/*.scss' \"./src/**/*\" ts-dist",
"copy-themes-dev": "copyfiles -u 1 \"./themes/*\" node_modules/electron/dist/resources", "copy-themes-dev": "copyfiles -u 1 \"./themes/*\" node_modules/electron/dist/resources",
@@ -29,41 +31,46 @@
"electron", "electron",
"hifi", "hifi",
"widevine", "widevine",
"linux" "linux",
"drm",
"castlabs"
], ],
"author": "Rick van Lieshout <info@rickvanlieshout.com> (http://rickvanlieshout.com)", "author": "Rick van Lieshout <info@rickvanlieshout.com> (http://rickvanlieshout.com)",
"homepage": "https://github.com/Mastermindzh/tidal-hifi", "homepage": "https://github.com/Mastermindzh/tidal-hifi",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@electron/remote": "^2.0.9", "@electron/remote": "^2.1.1",
"axios": "^1.6.5",
"discord-rpc": "^4.0.1", "discord-rpc": "^4.0.1",
"electron-store": "^8.1.0", "electron-store": "^8.1.0",
"express": "^4.18.2", "express": "^4.18.2",
"hotkeys-js": "^3.10.2", "hotkeys-js": "^3.13.5",
"mpris-service": "^2.1.2", "mpris-service": "^2.1.2",
"request": "^2.88.2", "request": "^2.88.2",
"sass": "^1.62.0" "sass": "^1.70.0"
}, },
"devDependencies": { "devDependencies": {
"@mastermindzh/prettier-config": "^1.0.0", "@mastermindzh/prettier-config": "^1.0.0",
"@types/discord-rpc": "^4.0.4", "@types/discord-rpc": "^4.0.8",
"@types/express": "^4.17.17", "@types/express": "^4.17.21",
"@types/request": "^2.48.8", "@types/node": "^20.10.6",
"@typescript-eslint/eslint-plugin": "^5.59.1", "@types/request": "^2.48.12",
"@typescript-eslint/parser": "^5.59.1", "@typescript-eslint/eslint-plugin": "^6.18.0",
"@typescript-eslint/parser": "^6.18.0",
"copyfiles": "^2.4.1", "copyfiles": "^2.4.1",
"electron": "git+https://github.com/castlabs/electron-releases.git#v24.1.2+wvcus", "electron": "git+https://github.com/castlabs/electron-releases#v28.1.1+wvcus",
"electron-builder": "^24.2.1", "electron-builder": "^24.9.1",
"eslint": "^8.39.0", "eslint": "^8.56.0",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"markdown-toc": "^1.2.0", "markdown-toc": "^1.2.0",
"prettier": "^2.8.8", "nodemon": "^3.0.2",
"stylelint": "^15.6.0", "prettier": "^3.1.1",
"stylelint-config-standard": "^33.0.0", "stylelint": "^16.1.0",
"stylelint-config-standard-scss": "^9.0.0", "stylelint-config-standard": "^36.0.0",
"stylelint-prettier": "^3.0.0", "stylelint-config-standard-scss": "^13.0.0",
"stylelint-prettier": "^5.0.0",
"tsc-watch": "^6.0.4", "tsc-watch": "^6.0.4",
"typescript": "^5.0.4" "typescript": "^5.3.3"
}, },
"prettier": "@mastermindzh/prettier-config" "prettier": "@mastermindzh/prettier-config"
} }

16
scripts/resize-icons.sh Normal file
View File

@@ -0,0 +1,16 @@
#!/bin/bash
if [ "$1" != "" ]; then # check if arg 1 is present
FILE=$1
else
echo "Please provide a file as an argument."
exit 1
fi
SIZES=("16x16" "22x22" "24x24" "32x32" "48x48" "64x64" "128x128" "256x256" "384x384")
echo "Resizing $FILE..."
for i in "${SIZES[@]}"; do
convert "$FILE" -resize "$i" "$i.png"
done

45
scripts/verifyElements.js Normal file
View File

@@ -0,0 +1,45 @@
// for some dumb reason `listen.tidal.com` has disabled console.log
// we can simply return an array with values though...
// run this on a playlist or mix page and observe the result
// NOTE: play & pause can't live together so one or the other will throw an error
(() => {
let 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: '*[class^="profileOptions"]',
settings: '*[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: '.header-details [data-test="title"]',
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"]',
};
let results = [];
Object.entries(elements).forEach(([key, value]) => {
const returnValue = document.querySelector(`${value}`);
if (!returnValue) {
results.push(`element ${key} not found`);
}
});
return results;
})();

View File

@@ -1,4 +1,9 @@
export const flags: { [key: string]: { flag: string; value?: string }[] } = { export const flags: { [key: string]: { flag: string; value?: string }[] } = {
gpuRasterization: [{ flag: "enable-gpu-rasterization", value: undefined }], gpuRasterization: [{ flag: "enable-gpu-rasterization", value: undefined }],
disableHardwareMediaKeys: [{ flag: "disable-features", value: "HardwareMediaKeyHandling" }], disableHardwareMediaKeys: [{ flag: "disable-features", value: "HardwareMediaKeyHandling" }],
enableWaylandSupport: [
{ flag: "enable-features", value: "UseOzonePlatform" },
{ flag: "ozone-platform-hint", value: "auto" },
{ flag: "enable-features", value: "WaylandWindowDecorations" },
],
}; };

View File

@@ -10,4 +10,7 @@ export const globalEvents = {
showSettings: "showSettings", showSettings: "showSettings",
storeChanged: "storeChanged", storeChanged: "storeChanged",
error: "error", error: "error",
whip: "whip",
log: "log",
toggleFavorite: "toggleFavorite",
}; };

View File

@@ -20,10 +20,26 @@ export const settings = {
disableHardwareMediaKeys: "disableHardwareMediaKeys", disableHardwareMediaKeys: "disableHardwareMediaKeys",
enableCustomHotkeys: "enableCustomHotkeys", enableCustomHotkeys: "enableCustomHotkeys",
enableDiscord: "enableDiscord", enableDiscord: "enableDiscord",
discord: {
detailsPrefix: "discord.detailsPrefix",
buttonText: "discord.buttonText",
includeTimestamps: "discord.includeTimestamps",
showSong: "discord.showSong",
idleText: "discord.idleText",
usingText: "discord.usingText",
},
ListenBrainz: {
root: "ListenBrainz",
enabled: "ListenBrainz.enabled",
api: "ListenBrainz.api",
token: "ListenBrainz.token",
delay: "ListenBrainz.delay",
},
flags: { flags: {
root: "flags", root: "flags",
disableHardwareMediaKeys: "flags.disableHardwareMediaKeys", disableHardwareMediaKeys: "flags.disableHardwareMediaKeys",
gpuRasterization: "flags.gpuRasterization", gpuRasterization: "flags.gpuRasterization",
enableWaylandSupport: "flags.enableWaylandSupport",
}, },
menuBar: "menuBar", menuBar: "menuBar",
minimizeOnClose: "minimizeOnClose", minimizeOnClose: "minimizeOnClose",

View File

@@ -1,4 +0,0 @@
export const statuses = {
playing: "playing",
paused: "paused",
};

View File

@@ -1,3 +1,3 @@
export default { export default {
name: "tidal-hifi", name: "TIDAL Hi-Fi",
}; };

View File

@@ -0,0 +1,42 @@
import { App } from "electron";
import { flags } from "../../constants/flags";
import { settings } from "../../constants/settings";
import { settingsStore } from "../../scripts/settings";
import { Logger } from "../logger";
/**
* Set default Electron flags
*/
export function setDefaultFlags(app: App) {
setFlag(app, "disable-seccomp-filter-sandbox");
setFlag(app, "disable-features", "MediaSessionService");
}
/**
* Set Tidal's managed flags from the user settings
* @param app
*/
export function setManagedFlagsFromSettings(app: App) {
const flagsFromSettings = settingsStore.get(settings.flags.root);
if (flagsFromSettings) {
for (const [key, value] of Object.entries(flagsFromSettings)) {
if (value) {
flags[key].forEach((flag) => {
setFlag(app, flag.flag, flag.value);
});
}
}
}
}
/**
* Set a single flag for Electron
* @param app app to set it on
* @param flag flag name
* @param value value to be set for the flag
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function setFlag(app: App, flag: string, value?: any) {
Logger.log(`enabling command line option ${flag} with value ${value}`);
app.commandLine.appendSwitch(flag, value);
}

View File

@@ -0,0 +1,65 @@
import { PowerSaveBlocker, powerSaveBlocker } from "electron";
import { Logger } from "../logger";
/**
* Start blocking idle/screen timeouts
* @param blocker optional instance of the powerSaveBlocker to use
* @returns id of current block
*/
export const acquireInhibitor = (blocker?: PowerSaveBlocker): number => {
const currentBlocker = blocker ?? powerSaveBlocker;
const blockId = currentBlocker.start("prevent-app-suspension");
Logger.log(`Started preventing app suspension with id: ${blockId}`);
return blockId;
};
/**
* Check whether there is a blocker active for the current id, if not start it.
* @param id id of inhibitor you want to check activity against
* @param blocker optional instance of the powerSaveBlocker to use
*/
export const acquireInhibitorIfInactive = (id: number, blocker?: PowerSaveBlocker): number => {
const currentBlocker = blocker ?? powerSaveBlocker;
if (!isInhibitorActive(id, currentBlocker)) {
return acquireInhibitor();
}
return id;
};
/**
* stop blocking idle/screen timeouts
* @param id id of inhibitor you want to check activity against
* @param blocker optional instance of the powerSaveBlocker to use
*/
export const releaseInhibitor = (id: number, blocker?: PowerSaveBlocker) => {
try {
const currentBlocker = blocker ?? powerSaveBlocker;
currentBlocker.stop(id);
Logger.log(`Released inhibitor with id: ${id}`);
} catch (error) {
Logger.log("Releasing inhibitor failed");
}
};
/**
* stop blocking idle/screen timeouts if a inhibitor is active
* @param id id of inhibitor you want to check activity against
* @param blocker optional instance of the powerSaveBlocker to use
*/
export const releaseInhibitorIfActive = (id: number, blocker?: PowerSaveBlocker) => {
const currentBlocker = blocker ?? powerSaveBlocker;
if (isInhibitorActive(id, currentBlocker)) {
releaseInhibitor(id, currentBlocker);
}
};
/**
* check whether the inhibitor is active
* @param id id of inhibitor you want to check activity against
* @param blocker optional instance of the powerSaveBlocker to use
*/
export const isInhibitorActive = (id: number, blocker?: PowerSaveBlocker) => {
const currentBlocker = blocker ?? powerSaveBlocker;
return currentBlocker.isStarted(id);
};

View File

@@ -0,0 +1,132 @@
import axios from "axios";
import Store from "electron-store";
import { settings } from "../../constants/settings";
import { MediaStatus } from "../../models/mediaStatus";
import { settingsStore } from "../../scripts/settings";
import { Logger } from "../logger";
import { StoreData } from "./models/storeData";
const ListenBrainzStore = new Store({ name: "listenbrainz" });
export const ListenBrainzConstants = {
oldData: "oldData",
};
export class ListenBrainz {
/**
* Create the object to store old information in the Store :)
* @param title
* @param artists
* @param duration
* @returns data passed along in an object + a "listenedAt" key with the current time
*/
private static constructStoreData(title: string, artists: string, duration: number): StoreData {
return {
listenedAt: Math.floor(new Date().getTime() / 1000),
title,
artists,
duration,
};
}
/**
* Call the ListenBrainz API and create playing now payload and scrobble old song
* @param title
* @param artists
* @param status
* @param duration
*/
public static async scrobble(
title: string,
artists: string,
status: string,
duration: number
): Promise<void> {
try {
if (status === MediaStatus.paused) {
return;
} else {
// Fetches the oldData required for scrobbling and proceeds to construct a playing_now data payload for the Playing Now area
const oldData = ListenBrainzStore.get(ListenBrainzConstants.oldData) as StoreData;
const playing_data = {
listen_type: "playing_now",
payload: [
{
track_metadata: {
additional_info: {
media_player: "Tidal Hi-Fi",
submission_client: "Tidal Hi-Fi",
music_service: "tidal.com",
duration: duration,
},
artist_name: artists,
track_name: title,
},
},
],
};
await axios.post(
`${settingsStore.get<string, string>(settings.ListenBrainz.api)}/1/submit-listens`,
playing_data,
{
headers: {
"Content-Type": "application/json",
Authorization: `Token ${settingsStore.get<string, string>(
settings.ListenBrainz.token
)}`,
},
}
);
if (!oldData) {
ListenBrainzStore.set(
ListenBrainzConstants.oldData,
this.constructStoreData(title, artists, duration)
);
} else {
if (oldData.title !== title) {
// This constructs the data required to scrobble the data after the song finishes
const scrobble_data = {
listen_type: "single",
payload: [
{
listened_at: oldData.listenedAt,
track_metadata: {
additional_info: {
media_player: "Tidal Hi-Fi",
submission_client: "Tidal Hi-Fi",
music_service: "listen.tidal.com",
duration: oldData.duration,
},
artist_name: oldData.artists,
track_name: oldData.title,
},
},
],
};
await axios.post(
`${settingsStore.get<string, string>(settings.ListenBrainz.api)}/1/submit-listens`,
scrobble_data,
{
headers: {
"Content-Type": "application/json",
Authorization: `Token ${settingsStore.get<string, string>(
settings.ListenBrainz.token
)}`,
},
}
);
ListenBrainzStore.set(
ListenBrainzConstants.oldData,
this.constructStoreData(title, artists, duration)
);
}
}
}
} catch (error) {
Logger.log(JSON.stringify(error));
}
}
}
export { ListenBrainzStore };

View File

@@ -0,0 +1,9 @@
/**
* Data saved for ListenBrainz
*/
export interface StoreData {
listenedAt: number;
title: string;
artists: string;
duration: number;
}

52
src/features/logger.ts Normal file
View File

@@ -0,0 +1,52 @@
import { IpcMain, ipcMain, IpcMainEvent, ipcRenderer } from "electron";
import { globalEvents } from "../constants/globalEvents";
export class Logger {
/**
* Subscribe to watch for logs from the IPC client
* @param ipcMain main thread IPC client so we can subscribe to events
*/
public static watch(ipcMain: IpcMain) {
ipcMain.on(
globalEvents.log,
(event: IpcMainEvent | { content: string; message: string }, message) => {
const { content, object } = message ?? event;
this.logToSTDOut(content, object);
}
);
}
/**
* Log content to STDOut
* @param content
* @param object js(on) object that will be prettyPrinted
*/
public static log(content: string, object: object = {}) {
if (ipcRenderer) {
ipcRenderer.send(globalEvents.log, { content, object });
} else {
ipcMain.emit(globalEvents.log, { content, object });
}
}
/**
* Log content to STDOut and use the provided alert function to alert
* @param content
* @param object js(on) object that will be prettyPrinted
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public static alert(content: string, object: any = {}, alert?: (msg: string) => void) {
Logger.log(content, object);
if (alert) {
alert(`${content} \n\nwith details: \n${JSON.stringify(object, null, 2)}`);
}
}
/**
* Log to STDOut
* @param content
* @param object
*/
private static logToSTDOut(content: string, object = {}) {
console.log(content, Object.keys(object).length > 0 ? JSON.stringify(object, null, 2) : "");
}
}

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

@@ -0,0 +1,31 @@
import fs from "fs";
import { settings } from "../../constants/settings";
import { settingsStore } from "../../scripts/settings";
import { Logger } from "../logger";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function addCustomCss(app: any) {
window.addEventListener("DOMContentLoaded", () => {
const selectedTheme = settingsStore.get<string, string>(settings.theme);
if (selectedTheme !== "none") {
const userThemePath = `${app.getPath("userData")}/themes/${selectedTheme}`;
const resourcesThemePath = `${process.resourcesPath}/${selectedTheme}`;
const themeFile = fs.existsSync(userThemePath) ? userThemePath : resourcesThemePath;
fs.readFile(themeFile, "utf-8", (err, data) => {
if (err) {
Logger.alert("An error ocurred reading the theme file.", err, alert);
return;
}
const themeStyle = document.createElement("style");
themeStyle.innerHTML = data;
document.head.appendChild(themeStyle);
});
}
// read customCSS (it will override the theme)
const style = document.createElement("style");
style.innerHTML = settingsStore.get<string, string[]>(settings.customCSS).join("\n");
document.head.appendChild(style);
});
}

View File

@@ -1,7 +1,7 @@
import { enable, initialize } from "@electron/remote/main"; import { enable, initialize } from "@electron/remote/main";
import { import {
BrowserWindow,
app, app,
BrowserWindow,
components, components,
globalShortcut, globalShortcut,
ipcMain, ipcMain,
@@ -9,9 +9,18 @@ import {
session, session,
} from "electron"; } from "electron";
import path from "path"; import path from "path";
import { flags } from "./constants/flags";
import { globalEvents } from "./constants/globalEvents"; import { globalEvents } from "./constants/globalEvents";
import { mediaKeys } from "./constants/mediaKeys"; import { mediaKeys } from "./constants/mediaKeys";
import { settings } from "./constants/settings";
import { setDefaultFlags, setManagedFlagsFromSettings } from "./features/flags/flags";
import {
acquireInhibitorIfInactive,
releaseInhibitorIfActive,
} from "./features/idleInhibitor/idleInhibitor";
import { Logger } from "./features/logger";
import { Songwhip } from "./features/songwhip/songwhip";
import { MediaInfo } from "./models/mediaInfo";
import { MediaStatus } from "./models/mediaStatus";
import { initRPC, rpc, unRPC } from "./scripts/discord"; import { initRPC, rpc, unRPC } from "./scripts/discord";
import { startExpress } from "./scripts/express"; import { startExpress } from "./scripts/express";
import { updateMediaInfo } from "./scripts/mediaInfo"; import { updateMediaInfo } from "./scripts/mediaInfo";
@@ -20,43 +29,28 @@ import {
closeSettingsWindow, closeSettingsWindow,
createSettingsWindow, createSettingsWindow,
hideSettingsWindow, hideSettingsWindow,
showSettingsWindow,
settingsStore, settingsStore,
showSettingsWindow,
} from "./scripts/settings"; } from "./scripts/settings";
import { settings } from "./constants/settings";
import { addTray, refreshTray } from "./scripts/tray"; import { addTray, refreshTray } from "./scripts/tray";
import { MediaInfo } from "./models/mediaInfo";
const tidalUrl = "https://listen.tidal.com"; const tidalUrl = "https://listen.tidal.com";
let mainInhibitorId = -1;
initialize(); 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";
const windowPreferences = {
sandbox: false,
plugins: true,
devTools: true, // I like tinkering, others might too
};
setFlags(); setDefaultFlags(app);
setManagedFlagsFromSettings(app);
function setFlags() {
const flagsFromSettings = settingsStore.get(settings.flags.root);
if (flagsFromSettings) {
for (const [key, value] of Object.entries(flags)) {
if (value) {
flags[key].forEach((flag) => {
console.log(`enabling command line switch ${flag.flag} with value ${flag.value}`);
app.commandLine.appendSwitch(flag.flag, flag.value);
});
}
}
}
/**
* Fix Display Compositor issue.
*/
app.commandLine.appendSwitch("disable-seccomp-filter-sandbox");
}
/** /**
* Update the menuBarVisbility according to the store value * Update the menuBarVisibility according to the store value
* *
*/ */
function syncMenuBarWithStore() { function syncMenuBarWithStore() {
@@ -88,16 +82,16 @@ function createWindow(options = { x: 0, y: 0, backgroundColor: "white" }) {
mainWindow = new BrowserWindow({ mainWindow = new BrowserWindow({
x: options.x, x: options.x,
y: options.y, y: options.y,
width: settingsStore && settingsStore.get(settings.windowBounds.width), width: settingsStore?.get(settings.windowBounds.width),
height: settingsStore && settingsStore.get(settings.windowBounds.height), height: settingsStore?.get(settings.windowBounds.height),
icon, icon,
backgroundColor: options.backgroundColor, backgroundColor: options.backgroundColor,
autoHideMenuBar: true, autoHideMenuBar: true,
webPreferences: { webPreferences: {
sandbox: false, ...windowPreferences,
...{
preload: path.join(__dirname, "preload.js"), preload: path.join(__dirname, "preload.js"),
plugins: true, },
devTools: true, // I like tinkering, others might too
}, },
}); });
enable(mainWindow.webContents); enable(mainWindow.webContents);
@@ -122,6 +116,7 @@ function createWindow(options = { x: 0, y: 0, backgroundColor: "white" }) {
}); });
// Emitted when the window is closed. // Emitted when the window is closed.
mainWindow.on("closed", function () { mainWindow.on("closed", function () {
releaseInhibitorIfActive(mainInhibitorId);
closeSettingsWindow(); closeSettingsWindow();
app.quit(); app.quit();
}); });
@@ -129,6 +124,18 @@ function createWindow(options = { x: 0, y: 0, backgroundColor: "white" }) {
const { width, height } = mainWindow.getBounds(); const { width, height } = mainWindow.getBounds();
settingsStore.set(settings.windowBounds.root, { width, height }); settingsStore.set(settings.windowBounds.root, { width, height });
}); });
mainWindow.webContents.setWindowOpenHandler(() => {
return {
action: "allow",
overrideBrowserWindowOptions: {
webPreferences: {
sandbox: false,
plugins: true,
devTools: true, // I like tinkering, others might too
},
},
};
});
} }
function registerHttpProtocols() { function registerHttpProtocols() {
@@ -194,6 +201,12 @@ app.on("browser-window-created", (_, window) => {
// IPC // IPC
ipcMain.on(globalEvents.updateInfo, (_event, arg: MediaInfo) => { ipcMain.on(globalEvents.updateInfo, (_event, arg: MediaInfo) => {
updateMediaInfo(arg); updateMediaInfo(arg);
if (arg.status === MediaStatus.playing) {
mainInhibitorId = acquireInhibitorIfInactive(mainInhibitorId);
} else {
releaseInhibitorIfActive(mainInhibitorId);
mainInhibitorId = -1;
}
}); });
ipcMain.on(globalEvents.hideSettings, () => { ipcMain.on(globalEvents.hideSettings, () => {
@@ -220,3 +233,9 @@ ipcMain.on(globalEvents.storeChanged, () => {
ipcMain.on(globalEvents.error, (event) => { ipcMain.on(globalEvents.error, (event) => {
console.log(event); console.log(event);
}); });
ipcMain.handle(globalEvents.whip, async (event, url) => {
return Songwhip.whip(url);
});
Logger.watch(ipcMain);

View File

@@ -10,4 +10,5 @@ export interface MediaInfo {
current: string; current: string;
duration: string; duration: string;
image: string; image: string;
favorite: boolean;
} }

View File

@@ -9,4 +9,5 @@ export interface Options {
"app-name": string; "app-name": string;
image: string; image: string;
icon: string; icon: string;
favorite: boolean;
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

@@ -1,11 +1,32 @@
import remote, { app } from "@electron/remote"; import { app } from "@electron/remote";
import { ipcRenderer, shell } from "electron"; import { ipcRenderer, shell } from "electron";
import fs from "fs"; import fs from "fs";
import { globalEvents } from "../../constants/globalEvents"; import { globalEvents } from "../../constants/globalEvents";
import { settings } from "../../constants/settings"; import { settings } from "../../constants/settings";
import { Logger } from "../../features/logger";
import { addCustomCss } from "../../features/theming/theming";
import { settingsStore } from "./../../scripts/settings"; import { settingsStore } from "./../../scripts/settings";
import { getOptions, getOptionsHeader, getThemeListFromDirectory } from "./theming"; import { getOptions, getOptionsHeader, getThemeListFromDirectory } from "./theming";
// All switches on the settings screen that show additional options based on their state
const switchesWithSettings = {
listenBrainz: {
switch: "enableListenBrainz",
classToHide: "listenbrainz__options",
settingsKey: settings.ListenBrainz.enabled,
},
discord: {
switch: "enableDiscord",
classToHide: "discord_options",
settingsKey: settings.enableDiscord,
},
discord_show_song: {
switch: "discord_show_song",
classToHide: "discord_show_song_options",
settingsKey: settings.discord.showSong,
}
};
let adBlock: HTMLInputElement, let adBlock: HTMLInputElement,
api: HTMLInputElement, api: HTMLInputElement,
customCSS: HTMLInputElement, customCSS: HTMLInputElement,
@@ -25,7 +46,21 @@ let adBlock: HTMLInputElement,
skippedArtists: HTMLInputElement, skippedArtists: HTMLInputElement,
theme: HTMLSelectElement, theme: HTMLSelectElement,
trayIcon: HTMLInputElement, trayIcon: HTMLInputElement,
updateFrequency: HTMLInputElement; updateFrequency: HTMLInputElement,
enableListenBrainz: HTMLInputElement,
ListenBrainzAPI: HTMLInputElement,
ListenBrainzToken: HTMLInputElement,
listenbrainz_delay: HTMLInputElement,
enableWaylandSupport: HTMLInputElement,
discord_details_prefix: HTMLInputElement,
discord_include_timestamps: HTMLInputElement,
discord_button_text: HTMLInputElement,
discord_show_song: HTMLInputElement,
discord_idle_text: HTMLInputElement,
discord_using_text: HTMLInputElement;
addCustomCss(app);
function getThemeFiles() { function getThemeFiles() {
const selectElement = document.getElementById("themesList") as HTMLSelectElement; const selectElement = document.getElementById("themesList") as HTMLSelectElement;
const builtInThemes = getThemeListFromDirectory(process.resourcesPath); const builtInThemes = getThemeListFromDirectory(process.resourcesPath);
@@ -53,6 +88,7 @@ function handleFileUploads() {
const fileMessage = document.getElementById("file-message"); const fileMessage = document.getElementById("file-message");
fileMessage.innerText = "or drag and drop files here"; fileMessage.innerText = "or drag and drop files here";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
document.getElementById("theme-files").addEventListener("change", function (e: any) { document.getElementById("theme-files").addEventListener("change", function (e: any) {
Array.from(e.target.files).forEach((file: File) => { Array.from(e.target.files).forEach((file: File) => {
const destination = `${app.getPath("userData")}/themes/${file.name}`; const destination = `${app.getPath("userData")}/themes/${file.name}`;
@@ -63,10 +99,25 @@ function handleFileUploads() {
}); });
} }
/**
* hide or unhide an element
* @param checked
* @param toggleOptions
*/
function setElementHidden(
checked: boolean,
toggleOptions: { switch: string; classToHide: string }
) {
const element = document.getElementById(toggleOptions.classToHide);
checked ? element.classList.remove("hidden") : element.classList.add("hidden");
}
/** /**
* Sync the UI forms with the current settings * Sync the UI forms with the current settings
*/ */
function refreshSettings() { function refreshSettings() {
try {
adBlock.checked = settingsStore.get(settings.adBlock); adBlock.checked = settingsStore.get(settings.adBlock);
api.checked = settingsStore.get(settings.api); api.checked = settingsStore.get(settings.api);
customCSS.value = settingsStore.get<string, string[]>(settings.customCSS).join("\n"); customCSS.value = settingsStore.get<string, string[]>(settings.customCSS).join("\n");
@@ -74,6 +125,7 @@ function refreshSettings() {
disableHardwareMediaKeys.checked = settingsStore.get(settings.flags.disableHardwareMediaKeys); disableHardwareMediaKeys.checked = settingsStore.get(settings.flags.disableHardwareMediaKeys);
enableCustomHotkeys.checked = settingsStore.get(settings.enableCustomHotkeys); enableCustomHotkeys.checked = settingsStore.get(settings.enableCustomHotkeys);
enableDiscord.checked = settingsStore.get(settings.enableDiscord); enableDiscord.checked = settingsStore.get(settings.enableDiscord);
enableWaylandSupport.checked = settingsStore.get(settings.flags.enableWaylandSupport);
gpuRasterization.checked = settingsStore.get(settings.flags.gpuRasterization); gpuRasterization.checked = settingsStore.get(settings.flags.gpuRasterization);
menuBar.checked = settingsStore.get(settings.menuBar); menuBar.checked = settingsStore.get(settings.menuBar);
minimizeOnClose.checked = settingsStore.get(settings.minimizeOnClose); minimizeOnClose.checked = settingsStore.get(settings.minimizeOnClose);
@@ -87,6 +139,24 @@ function refreshSettings() {
skippedArtists.value = settingsStore.get<string, string[]>(settings.skippedArtists).join("\n"); 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);
ListenBrainzAPI.value = settingsStore.get(settings.ListenBrainz.api);
ListenBrainzToken.value = settingsStore.get(settings.ListenBrainz.token);
listenbrainz_delay.value = settingsStore.get(settings.ListenBrainz.delay);
discord_details_prefix.value = settingsStore.get(settings.discord.detailsPrefix);
discord_include_timestamps.checked = settingsStore.get(settings.discord.includeTimestamps);
discord_button_text.value = settingsStore.get(settings.discord.buttonText);
discord_show_song.checked = settingsStore.get(settings.discord.showSong);
discord_idle_text.value = settingsStore.get(settings.discord.idleText);
discord_using_text.value = settingsStore.get(settings.discord.usingText);
// set state of all switches with additional settings
Object.values(switchesWithSettings).forEach((settingSwitch) => {
setElementHidden(settingsStore.get(settingSwitch.settingsKey), settingSwitch);
});
} catch (error) {
Logger.log("Refreshing settings failed.", error);
}
} }
/** /**
@@ -103,14 +173,6 @@ function hide() {
ipcRenderer.send(globalEvents.hideSettings); ipcRenderer.send(globalEvents.hideSettings);
} }
/**
* Restart tidal-hifi after changes
*/
function restart() {
remote.app.relaunch();
remote.app.exit();
}
/** /**
* Bind UI components to functions after DOMContentLoaded * Bind UI components to functions after DOMContentLoaded
*/ */
@@ -123,20 +185,29 @@ window.addEventListener("DOMContentLoaded", () => {
handleFileUploads(); handleFileUploads();
document.getElementById("close").addEventListener("click", hide); document.getElementById("close").addEventListener("click", hide);
document.getElementById("restart").addEventListener("click", restart);
document.querySelectorAll(".external-link").forEach((elem) => document.querySelectorAll(".external-link").forEach((elem) =>
elem.addEventListener("click", function (event) { elem.addEventListener("click", function (event) {
openExternal((event.target as HTMLElement).getAttribute("data-url")); openExternal((event.target as HTMLElement).getAttribute("data-url"));
}) })
); );
function addInputListener(source: HTMLInputElement, key: string) { function addInputListener(
source: HTMLInputElement,
key: string,
toggleOptions?: { switch: string; classToHide: string }
) {
source.addEventListener("input", () => { source.addEventListener("input", () => {
if (source.value === "on") { if (source.value === "on") {
settingsStore.set(key, source.checked); settingsStore.set(key, source.checked);
} else { } else {
settingsStore.set(key, source.value); settingsStore.set(key, source.value);
} }
if (toggleOptions) {
if (source.value === "on" && source.id === toggleOptions.switch) {
setElementHidden(source.checked, toggleOptions);
}
}
ipcRenderer.send(globalEvents.storeChanged); ipcRenderer.send(globalEvents.storeChanged);
}); });
} }
@@ -170,6 +241,7 @@ window.addEventListener("DOMContentLoaded", () => {
disableHardwareMediaKeys = get("disableHardwareMediaKeys"); disableHardwareMediaKeys = get("disableHardwareMediaKeys");
enableCustomHotkeys = get("enableCustomHotkeys"); enableCustomHotkeys = get("enableCustomHotkeys");
enableDiscord = get("enableDiscord"); enableDiscord = get("enableDiscord");
enableWaylandSupport = get("enableWaylandSupport");
gpuRasterization = get("gpuRasterization"); gpuRasterization = get("gpuRasterization");
menuBar = get("menuBar"); menuBar = get("menuBar");
minimizeOnClose = get("minimizeOnClose"); minimizeOnClose = get("minimizeOnClose");
@@ -183,16 +255,26 @@ window.addEventListener("DOMContentLoaded", () => {
skippedArtists = get("skippedArtists"); skippedArtists = get("skippedArtists");
singleInstance = get("singleInstance"); singleInstance = get("singleInstance");
updateFrequency = get("updateFrequency"); updateFrequency = get("updateFrequency");
enableListenBrainz = get("enableListenBrainz");
ListenBrainzAPI = get("ListenBrainzAPI");
ListenBrainzToken = get("ListenBrainzToken");
discord_details_prefix = get("discord_details_prefix");
discord_include_timestamps = get("discord_include_timestamps");
listenbrainz_delay = get("listenbrainz_delay");
discord_button_text = get("discord_button_text");
discord_show_song = get("discord_show_song");
discord_using_text = get("discord_using_text");
discord_idle_text = get("discord_idle_text")
refreshSettings(); refreshSettings();
addInputListener(adBlock, settings.adBlock); addInputListener(adBlock, settings.adBlock);
addInputListener(api, settings.api); addInputListener(api, settings.api);
addTextAreaListener(customCSS, settings.customCSS); addTextAreaListener(customCSS, settings.customCSS);
addInputListener(disableBackgroundThrottle, settings.disableBackgroundThrottle); addInputListener(disableBackgroundThrottle, settings.disableBackgroundThrottle);
addInputListener(disableHardwareMediaKeys, settings.flags.disableHardwareMediaKeys); addInputListener(disableHardwareMediaKeys, settings.flags.disableHardwareMediaKeys);
addInputListener(enableCustomHotkeys, settings.enableCustomHotkeys); addInputListener(enableCustomHotkeys, settings.enableCustomHotkeys);
addInputListener(enableDiscord, settings.enableDiscord); addInputListener(enableDiscord, settings.enableDiscord, switchesWithSettings.discord);
addInputListener(enableWaylandSupport, settings.flags.enableWaylandSupport);
addInputListener(gpuRasterization, settings.flags.gpuRasterization); addInputListener(gpuRasterization, settings.flags.gpuRasterization);
addInputListener(menuBar, settings.menuBar); addInputListener(menuBar, settings.menuBar);
addInputListener(minimizeOnClose, settings.minimizeOnClose); addInputListener(minimizeOnClose, settings.minimizeOnClose);
@@ -206,4 +288,18 @@ window.addEventListener("DOMContentLoaded", () => {
addSelectListener(theme, settings.theme); addSelectListener(theme, settings.theme);
addInputListener(trayIcon, settings.trayIcon); addInputListener(trayIcon, settings.trayIcon);
addInputListener(updateFrequency, settings.updateFrequency); addInputListener(updateFrequency, settings.updateFrequency);
addInputListener(
enableListenBrainz,
settings.ListenBrainz.enabled,
switchesWithSettings.listenBrainz
);
addInputListener(ListenBrainzAPI, settings.ListenBrainz.api);
addInputListener(ListenBrainzToken, settings.ListenBrainz.token);
addInputListener(listenbrainz_delay, settings.ListenBrainz.delay);
addInputListener(discord_details_prefix, settings.discord.detailsPrefix);
addInputListener(discord_include_timestamps, settings.discord.includeTimestamps);
addInputListener(discord_button_text, settings.discord.buttonText);
addInputListener(discord_show_song, settings.discord.showSong, switchesWithSettings.discord_show_song);
addInputListener(discord_idle_text, settings.discord.idleText);
addInputListener(discord_using_text, settings.discord.usingText);
}); });

View File

@@ -7,6 +7,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" />
<link rel="stylesheet" href="./settings.css" /> <link rel="stylesheet" href="./settings.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
</head> </head>
<body class="settings-window"> <body class="settings-window">
@@ -201,6 +202,9 @@
<span class="switch__slider"></span> <span class="switch__slider"></span>
</label> </label>
</div> </div>
</div>
<div class="group">
<p class="group__title">Discord</p>
<div class="group__option"> <div class="group__option">
<div class="group__description"> <div class="group__description">
<h4>Discord RPC</h4> <h4>Discord RPC</h4>
@@ -211,6 +215,101 @@
<span class="switch__slider"></span> <span class="switch__slider"></span>
</label> </label>
</div> </div>
<div id="discord_options">
<div class="group__option" class="hidden">
<div class="group__description">
<h4>Idle Text</h4>
<p>The text displayed on Discord's rich presence while idling in the app.</p>
<input id="discord_idle_text" type="text" class="text-input" name="discord_idle_text" />
</div>
</div>
<div class="group__option" class="hidden">
<div class="group__description">
<h4>Using Tidal Text</h4>
<p>The text displayed on Discord's rich presence while "showSong" is turned off</p>
<input id="discord_using_text" type="text" class="text-input" name="discord_using_text" />
</div>
</div>
<div class="group__option" class="hidden">
<div class="group__description">
<h4>Show song</h4>
<p>Show the current song in the Discord client</p>
</div>
<label class="switch">
<input id="discord_show_song" type="checkbox" />
<span class="switch__slider"></span>
</label>
</div>
<div id="discord_show_song_options" class="hidden">
<div class="group__option" class="hidden">
<div class="group__description">
<h4>Include timestamps</h4>
<p>Show current/end playtime in the Discord client</p>
</div>
<label class="switch">
<input id="discord_include_timestamps" type="checkbox" />
<span class="switch__slider"></span>
</label>
</div>
<div class="group__option" class="hidden">
<div class="group__description">
<h4>Details prefix</h4>
<p>Prefix for the "details" field of Discord's rich presence.</p>
<input id="discord_details_prefix" type="text" class="text-input" name="discord_details_prefix" />
</div>
</div>
<div class="group__option">
<div class="group__description">
<h4>Button text</h4>
<p>Text to display on the button below the song information.</p>
<input id="discord_button_text" type="text" class="text-input" name="discord_button_text" />
</div>
</div>
</div>
</div>
</div>
<div class="group">
<p class="group__title">ListenBrainz</p>
<div class="group__option">
<div class="group__description">
<h4>Enable ListenBrainz</h4>
<p>Scrobble your listens directly to ListenBrainz.</p>
</div>
<label class="switch">
<input id="enableListenBrainz" type="checkbox" />
<span class="switch__slider"></span>
</label>
</div>
<div id="listenbrainz__options" class="hidden">
<div class="group__option">
<div class="group__description">
<h4>ListenBrainz API Url</h4>
<p>There are multiple instances for ListenBrainz you can set the corresponding API url below.</p>
<input id="ListenBrainzAPI" type="text" class="text-input" name="ListenBrainzAPI" />
</div>
</div>
<div class="group__option">
<div class="group__description">
<h4>ListenBrainz User Token</h4>
<p>Provide the user token you can get from the settings page.</p>
<input id="ListenBrainzToken" type="text" class="text-input" name="ListenBrainzToken" />
</div>
</div>
</div>
<div class="group__description">
<h4>ScrobbleDelay</h4>
<p>The delay (in ms) to send a song to ListenBrainz. Prevents spamming the API when you fast forward
immediately</p>
<input id="listenbrainz_delay" type="number" class="text-input" name="listenbrainz_delay" />
</div>
</div> </div>
</section> </section>
@@ -221,7 +320,7 @@
<div class="group__description"> <div class="group__description">
<h4>Update frequency</h4> <h4>Update frequency</h4>
<p> <p>
The amount of time, in milliseconds, that tidal-hifi will refresh its playback info by scraping the The amount of time, in milliseconds, that TIDAL Hi-Fi will refresh its playback info by scraping the
website. website.
The default of 500 seems to work in more cases but if you are fine with a bit more resource usage you The default of 500 seems to work in more cases but if you are fine with a bit more resource usage you
can decrease it as well. can decrease it as well.
@@ -229,6 +328,7 @@
<input id="updateFrequency" type="number" class="text-input" name="updateFrequency" /> <input id="updateFrequency" type="number" class="text-input" name="updateFrequency" />
</div> </div>
</div> </div>
</div>
<div class="group"> <div class="group">
<p class="group__title">Flags</p> <p class="group__title">Flags</p>
<div class="group__option"> <div class="group__option">
@@ -266,6 +366,18 @@
<span class="switch__slider"></span> <span class="switch__slider"></span>
</label> </label>
</div> </div>
<div class="group__option">
<div class="group__description">
<h4>Wayland support</h4>
<p>
Adds a couple of Electron flags to help TIDAL Hi-Fi run smoothly on the Wayland window system.
</p>
</div>
<label class="switch">
<input id="enableWaylandSupport" type="checkbox" />
<span class="switch__slider"></span>
</label>
</div>
</div> </div>
</section> </section>
@@ -318,21 +430,24 @@
<section id="about-section" class="tabs__section about-section"> <section id="about-section" class="tabs__section about-section">
<img alt="tidal icon" class="about-section__icon" src="./icon.png" /> <img alt="tidal icon" class="about-section__icon" src="./icon.png" />
<p class="about-section__text"> <h4>TIDAL Hi-Fi</h4>
<a class="external-link" data-url="https://github.com/Mastermindzh/tidal-hifi">TIDAL Hi-Fi</a> <div class="about-section__version">
is made by <a href="https://github.com/Mastermindzh/tidal-hifi/releases/tag/5.9.0">5.9.0</a>
<a class="external-link" data-url="https://www.rickvanlieshout.com"> </div>
Rick van Lieshout</a>. <br />It uses <div class="about-section__links">
<a class="external-link" data-url="https://castlabs.com/">Castlabs'</a> <a href="https://github.com/mastermindzh/tidal-hifi/" class="about-section__button">Github <i
version of Electron for widevine support. class="fa fa-external-link"></i></a>
</p> <a href="https://github.com/Mastermindzh/tidal-hifi/issues" class="about-section__button">Report an issue <i
class="fa fa-external-link"></i></a>
<a href="https://github.com/Mastermindzh/tidal-hifi/graphs/contributors"
class="about-section__button">Contributors <i class="fa fa-external-link"></i></a>
</div>
</section> </section>
<footer class="footer"> <footer class="footer">
<p class="footer__note"> <p class="footer__note">
Some settings may require a restart of TIDAL Hi-Fi. To do so, click the button below: <strong>Note</strong>: some settings may require a restart of TIDAL Hi-Fi.
</p> </p>
<button class="footer__button" id="restart">Restart TIDAL Hi-Fi</button>
</footer> </footer>
</div> </div>
</main> </main>

View File

@@ -8,6 +8,7 @@ $tidal-grey: #72777f;
$tidal-grey-darker: #404248; $tidal-grey-darker: #404248;
$tidal-grey-darker-focus: #55585f; $tidal-grey-darker-focus: #55585f;
$tidal-grey-darkest: #242528; $tidal-grey-darkest: #242528;
$tidal-grey-darkest-focus: #2e2f33;
// --- Fonts --- // --- Fonts ---
@@ -309,26 +310,79 @@ html {
} }
.about-section { .about-section {
padding-top: 120px; padding-top: 40px;
text-align: center; text-align: center;
&__icon { &__icon {
display: inline-block; display: inline-block;
width: 100px; width: 200px;
} }
&__text { &__text {
display: block; display: block;
max-width: 350px; max-width: 500px;
margin: 20px auto 0; margin: -15px auto 0;
} }
// --- Footer --- &__table {
width: 120px;
margin: 0 auto;
td {
text-align: left;
}
}
&__version {
margin: -10px 0 30px;
a {
background-color: $tidal-grey-darker;
border: none;
color: $tidal-blue;
padding: 8px 20px;
font-weight: bold;
text-align: center;
text-decoration: none;
display: inline-block;
border-radius: 100px;
}
}
&__links {
width: 300px;
margin: 0 auto;
a {
border-radius: 10px;
border: none;
color: $white;
padding: 10px 10px 10px 20px;
margin: 8px;
text-align: left;
font-size: 16px;
line-height: 30px;
display: flex;
text-decoration: none;
justify-content: space-between;
background-color: $tidal-grey-darkest;
i {
color: $white;
line-height: 30px;
font-size: 18px;
}
&:hover {
background-color: $tidal-grey-darkest-focus;
}
}
}
} }
// --- Footer ---
.footer { .footer {
position: sticky; position: sticky;
top: calc(100% - 120px);
height: 100px; height: 100px;
padding-top: 20px; padding-top: 20px;
text-align: center; text-align: center;
@@ -368,10 +422,11 @@ html {
align-items: center; align-items: center;
width: 100%; width: 100%;
max-width: 100%; max-width: 100%;
padding: 25px 0 25px 0px; padding: 25px 0;
border: 1px dashed $tidal-grey; border: 1px dashed $tidal-grey;
border-radius: 3px; border-radius: 3px;
transition: 0.2s; transition: 0.2s;
&.is-active { &.is-active {
background-color: $black; background-color: $black;
} }
@@ -409,6 +464,7 @@ html {
width: 100%; width: 100%;
cursor: pointer; cursor: pointer;
opacity: 0; opacity: 0;
&:focus { &:focus {
outline: none; outline: none;
} }
@@ -443,3 +499,7 @@ html {
} }
} }
} }
.hidden {
display: none !important;
}

View File

@@ -1,4 +1,5 @@
import fs from "fs"; import fs from "fs";
import { Logger } from "../../features/logger";
const cssFilter = (file: string) => file.endsWith(".css"); const cssFilter = (file: string) => file.endsWith(".css");
const sort = (a: string, b: string) => a.toLowerCase().localeCompare(b.toLowerCase()); const sort = (a: string, b: string) => a.toLowerCase().localeCompare(b.toLowerCase());
@@ -36,7 +37,7 @@ export const getThemeListFromDirectory = (directory: string): string[] => {
makeUserThemesDirectory(directory); makeUserThemesDirectory(directory);
return fs.readdirSync(directory).filter(cssFilter).sort(sort); return fs.readdirSync(directory).filter(cssFilter).sort(sort);
} catch (err) { } catch (err) {
console.error(err); Logger.log(`Failed to get files from ${directory}`, err);
return []; return [];
} }
}; };
@@ -49,6 +50,6 @@ export const makeUserThemesDirectory = (directory: string) => {
try { try {
fs.mkdirSync(directory, { recursive: true }); fs.mkdirSync(directory, { recursive: true });
} catch (err) { } catch (err) {
console.error(err); Logger.log(`Failed to make user theme directory: ${directory}`, err);
} }
}; };

View File

@@ -1,21 +1,33 @@
import { Notification, app, dialog } from "@electron/remote"; import { app, dialog, Notification } from "@electron/remote";
import { ipcRenderer } from "electron"; import { clipboard, ipcRenderer } from "electron";
import fs from "fs";
import Player from "mpris-service"; import Player from "mpris-service";
import { globalEvents } from "./constants/globalEvents"; import { globalEvents } from "./constants/globalEvents";
import { settings } from "./constants/settings"; import { settings } from "./constants/settings";
import { statuses } from "./constants/statuses"; import {
ListenBrainz,
ListenBrainzConstants,
ListenBrainzStore,
} from "./features/listenbrainz/listenbrainz";
import { StoreData } from "./features/listenbrainz/models/storeData";
import { Logger } from "./features/logger";
import { Songwhip } from "./features/songwhip/songwhip";
import { addCustomCss } from "./features/theming/theming";
import { MediaStatus } from "./models/mediaStatus";
import { Options } from "./models/options"; import { Options } from "./models/options";
import { downloadFile } from "./scripts/download"; import { downloadFile } from "./scripts/download";
import { addHotkey } from "./scripts/hotkeys"; import { addHotkey } from "./scripts/hotkeys";
import { settingsStore } from "./scripts/settings"; import { settingsStore } from "./scripts/settings";
import { setTitle } from "./scripts/window-functions"; import { setTitle } from "./scripts/window-functions";
const notificationPath = `${app.getPath("userData")}/notification.jpg`; const notificationPath = `${app.getPath("userData")}/notification.jpg`;
const appName = "Tidal Hifi"; const appName = "TIDAL Hi-Fi";
let currentSong = ""; let currentSong = "";
let player: Player; let player: Player;
let currentPlayStatus = statuses.paused; let currentPlayStatus = MediaStatus.paused;
let currentListenBrainzDelayId: ReturnType<typeof setTimeout>;
let scrobbleWaitingForDelay = false;
let wasJustPausedOrResumed = false;
let currentMediaInfo: Options;
const elements = { const elements = {
play: '*[data-test="play"]', play: '*[data-test="play"]',
@@ -25,25 +37,26 @@ const elements = {
title: '*[data-test^="footer-track-title"]', title: '*[data-test^="footer-track-title"]',
artists: '*[data-test^="grid-item-detail-text-title-artist"]', artists: '*[data-test^="grid-item-detail-text-title-artist"]',
home: '*[data-test="menu--home"]', home: '*[data-test="menu--home"]',
back: '[class^="backwardButton"]', back: '[title^="Back"]',
forward: '[class^="forwardButton"]', forward: '[title^="Next"]',
search: '[class^="searchField"]', search: '[class^="searchField"]',
shuffle: '*[data-test="shuffle"]', shuffle: '*[data-test="shuffle"]',
repeat: '*[data-test="repeat"]', repeat: '*[data-test="repeat"]',
block: '[class="blockButton"]', account: '*[class^="profileOptions"]',
account: '*[data-test^="profile-image-button"]',
settings: '*[data-test^="open-settings"]', settings: '*[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: '*[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']",
album_header_title: '.header-details [data-test="title"]', album_header_title: '.header-details [data-test="title"]',
playing_title: 'span[data-test="table-cell-title"].css-1vjc1xk', currentlyPlaying: "[class^='isPlayingIcon'], [data-test-is-playing='true']",
album_name_cell: '[data-test="table-cell-album"]', album_name_cell: '[class^="album"]',
tracklist_row: '[data-test="tracklist-row"]', tracklist_row: '[data-test="tracklist-row"]',
volume: '*[data-test="volume"]', volume: '*[data-test="volume"]',
favorite: '*[data-test="footer-favorite-button"]',
/** /**
* Get an element from the dom * Get an element from the dom
* @param {*} key key in elements object to fetch * @param {*} key key in elements object to fetch
@@ -104,8 +117,10 @@ const elements = {
window.location.href.includes("/playlist/") || window.location.href.includes("/playlist/") ||
window.location.href.includes("/mix/") window.location.href.includes("/mix/")
) { ) {
if (currentPlayStatus === statuses.playing) { if (currentPlayStatus === MediaStatus.playing) {
const row = window.document.querySelector(this.playing_title).closest(this.tracklist_row); // 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) { if (row) {
return row.querySelector(this.album_name_cell).textContent; return row.querySelector(this.album_name_cell).textContent;
} }
@@ -119,6 +134,10 @@ const elements = {
return this.get("volume").getAttribute("aria-checked") === "false"; // it's muted if aria-checked is false 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 * Shorthand function to get the text of a dom element
* @param {*} key key in elements object to fetch * @param {*} key key in elements object to fetch
@@ -146,36 +165,12 @@ const elements = {
}, },
}; };
function addCustomCss() {
window.addEventListener("DOMContentLoaded", () => {
const selectedTheme = settingsStore.get(settings.theme);
if (selectedTheme !== "none") {
const themeFile = `${process.resourcesPath}/${selectedTheme}`;
fs.readFile(themeFile, "utf-8", (err, data) => {
if (err) {
alert("An error ocurred reading the theme file.");
return;
}
const themeStyle = document.createElement("style");
themeStyle.innerHTML = data;
document.head.appendChild(themeStyle);
});
}
// read customCSS (it will override the theme)
const style = document.createElement("style");
style.innerHTML = settingsStore.get<string, string[]>(settings.customCSS).join("\n");
document.head.appendChild(style);
});
}
/** /**
* Get the update frequency from the store * Get the update frequency from the store
* make sure it returns a number, if not use the default * make sure it returns a number, if not use the default
*/ */
function getUpdateFrequency() { function getUpdateFrequency() {
const storeValue = settingsStore.get(settings.updateFrequency) as number; const storeValue = settingsStore.get<string, number>(settings.updateFrequency);
const defaultValue = 500; const defaultValue = 500;
if (!isNaN(storeValue)) { if (!isNaN(storeValue)) {
@@ -189,6 +184,7 @@ function getUpdateFrequency() {
* Play or pause the current song * Play or pause the current song
*/ */
function playPause() { function playPause() {
wasJustPausedOrResumed = true;
const play = elements.get("play"); const play = elements.get("play");
if (play) { if (play) {
@@ -198,6 +194,11 @@ function playPause() {
} }
} }
/**
* Clears the old listenbrainz data on launch
*/
ListenBrainzStore.clear();
/** /**
* Add hotkeys for when tidal is focused * Add hotkeys for when tidal is focused
* Reflects the desktop hotkeys found on: * Reflects the desktop hotkeys found on:
@@ -206,12 +207,19 @@ function playPause() {
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").click("settings"); elements.click("account");
setTimeout(() => {
elements.click("settings");
}, 100);
}); });
addHotkey("Control+l", function () { addHotkey("Control+l", function () {
handleLogout(); handleLogout();
}); });
addHotkey("Control+a", function () {
elements.click("favorite");
});
addHotkey("Control+h", function () { addHotkey("Control+h", function () {
elements.click("home"); elements.click("home");
}); });
@@ -232,6 +240,15 @@ function addHotKeys() {
addHotkey("control+r", function () { addHotkey("control+r", function () {
elements.click("repeat"); elements.click("repeat");
}); });
addHotkey("control+w", async function () {
const result = await ipcRenderer.invoke(globalEvents.whip, getTrackURL());
const url = Songwhip.getWhipUrl(result);
clipboard.writeText(url);
new Notification({
title: `Successfully whipped: `,
body: `URL copied to clipboard: ${url}`,
}).show();
});
} }
// always add the hotkey for the settings window // always add the hotkey for the settings window
@@ -259,7 +276,7 @@ function handleLogout() {
defaultId: 2, defaultId: 2,
}) })
.then((result: { response: number }) => { .then((result: { response: number }) => {
if (logoutOptions.indexOf("Yes, please") == result.response) { if (logoutOptions.indexOf("Yes, please") === result.response) {
for (let i = 0; i < window.localStorage.length; i++) { for (let i = 0; i < window.localStorage.length; i++) {
const key = window.localStorage.key(i); const key = window.localStorage.key(i);
if (key.startsWith("_TIDAL_activeSession")) { if (key.startsWith("_TIDAL_activeSession")) {
@@ -287,6 +304,8 @@ function addIPCEventListeners() {
ipcRenderer.on("globalEvent", (_event, args) => { ipcRenderer.on("globalEvent", (_event, args) => {
switch (args) { switch (args) {
case globalEvents.playPause: case globalEvents.playPause:
case globalEvents.play:
case globalEvents.pause:
playPause(); playPause();
break; break;
case globalEvents.next: case globalEvents.next:
@@ -295,11 +314,10 @@ function addIPCEventListeners() {
case globalEvents.previous: case globalEvents.previous:
elements.click("previous"); elements.click("previous");
break; break;
case globalEvents.play: case globalEvents.toggleFavorite:
elements.click("play"); elements.click("favorite");
break; break;
case globalEvents.pause: default:
elements.click("pause");
break; break;
} }
}); });
@@ -315,9 +333,9 @@ function getCurrentlyPlayingStatus() {
// if pause button is visible tidal is playing // if pause button is visible tidal is playing
if (pause) { if (pause) {
status = statuses.playing; status = MediaStatus.playing;
} else { } else {
status = statuses.paused; status = MediaStatus.paused;
} }
return status; return status;
} }
@@ -338,10 +356,71 @@ function convertDuration(duration: string) {
*/ */
function updateMediaInfo(options: Options, notify: boolean) { function updateMediaInfo(options: Options, notify: boolean) {
if (options) { if (options) {
currentMediaInfo = options;
ipcRenderer.send(globalEvents.updateInfo, options); ipcRenderer.send(globalEvents.updateInfo, options);
if (settingsStore.get(settings.notifications) && notify) { if (settingsStore.get(settings.notifications) && notify) {
new Notification({ title: options.title, body: options.artists, icon: options.icon }).show(); new Notification({ title: options.title, body: options.artists, icon: options.icon }).show();
} }
updateMpris(options);
updateListenBrainz(options);
}
}
function addMPRIS() {
if (process.platform === "linux" && settingsStore.get(settings.mpris)) {
try {
player = Player({
name: "tidal-hifi",
identity: "tidal-hifi",
supportedUriSchemes: ["file"],
supportedMimeTypes: [
"audio/mpeg",
"audio/flac",
"audio/x-flac",
"application/ogg",
"audio/wav",
],
supportedInterfaces: ["player"],
desktopEntry: "tidal-hifi",
});
// Events
const events = {
next: "next",
previous: "previous",
pause: "pause",
playpause: "playpause",
stop: "stop",
play: "play",
loopStatus: "repeat",
shuffle: "shuffle",
seek: "seek",
} as { [key: string]: string };
Object.keys(events).forEach(function (eventName) {
player.on(eventName, function () {
const eventValue = events[eventName];
switch (events[eventValue]) {
case events.playpause:
playPause();
break;
default:
elements.click(eventValue);
}
});
});
// Override get position function
player.getPosition = function () {
return convertDuration(elements.getText("current")) * 1000 * 1000;
};
player.on("quit", function () {
app.quit();
});
} catch (exception) {
Logger.log("MPRIS player api not working", exception);
}
}
}
function updateMpris(options: Options) {
if (player) { if (player) {
player.metadata = { player.metadata = {
...player.metadata, ...player.metadata,
@@ -354,7 +433,36 @@ function updateMediaInfo(options: Options, notify: boolean) {
"mpris:trackid": "/org/mpris/MediaPlayer2/track/" + getTrackID(), "mpris:trackid": "/org/mpris/MediaPlayer2/track/" + getTrackID(),
}, },
}; };
player.playbackStatus = options.status == statuses.paused ? "Paused" : "Playing"; player.playbackStatus = options.status === MediaStatus.paused ? "Paused" : "Playing";
}
}
/**
* Update the listenbrainz service with new data based on a few conditions
*/
function updateListenBrainz(options: Options) {
if (settingsStore.get(settings.ListenBrainz.enabled)) {
const oldData = ListenBrainzStore.get(ListenBrainzConstants.oldData) as StoreData;
if (
(!oldData && options.status === MediaStatus.playing) ||
(oldData && oldData.title !== options.title)
) {
if (!scrobbleWaitingForDelay) {
scrobbleWaitingForDelay = true;
clearTimeout(currentListenBrainzDelayId);
currentListenBrainzDelayId = setTimeout(
() => {
ListenBrainz.scrobble(
options.title,
options.artists,
options.status,
convertDuration(options.duration)
);
scrobbleWaitingForDelay = false;
},
settingsStore.get(settings.ListenBrainz.delay) ?? 0
);
}
} }
} }
} }
@@ -402,13 +510,20 @@ setInterval(function () {
const title = elements.getText("title"); const title = elements.getText("title");
const artistsArray = elements.getArtistsArray(); const artistsArray = elements.getArtistsArray();
const artistsString = elements.getArtistsString(artistsArray); const artistsString = elements.getArtistsString(artistsArray);
const songDashArtistTitle = `${title} - ${artistsString}`;
const titleOrArtistsChanged = currentSong !== songDashArtistTitle;
const current = elements.getText("current");
const currentStatus = getCurrentlyPlayingStatus();
// update info if song changed or was just paused/resumed
if (titleOrArtistsChanged || wasJustPausedOrResumed) {
if (wasJustPausedOrResumed) {
wasJustPausedOrResumed = false;
}
skipArtistsIfFoundInSkippedArtistsList(artistsArray); skipArtistsIfFoundInSkippedArtistsList(artistsArray);
const album = elements.getAlbumName(); const album = elements.getAlbumName();
const current = elements.getText("current");
const duration = elements.getText("duration"); const duration = elements.getText("duration");
const songDashArtistTitle = `${title} - ${artistsString}`;
const currentStatus = getCurrentlyPlayingStatus();
const options = { const options = {
title, title,
artists: artistsString, artists: artistsString,
@@ -420,10 +535,9 @@ setInterval(function () {
"app-name": appName, "app-name": appName,
image: "", image: "",
icon: "", icon: "",
favorite: elements.isFavorite(),
}; };
const titleOrArtistsChanged = currentSong !== songDashArtistTitle;
// update title, url and play info with new info // update title, url and play info with new info
setTitle(songDashArtistTitle); setTitle(songDashArtistTitle);
getTrackURL(); getTrackURL();
@@ -455,6 +569,10 @@ setInterval(function () {
updateMediaSession(options); updateMediaSession(options);
} }
}); });
} else {
// just update the time
updateMediaInfo({ ...currentMediaInfo, ...{ current } }, false);
}
/** /**
* automatically skip a song if the artists are found in the list of artists to skip * automatically skip a song if the artists are found in the list of artists to skip
@@ -475,61 +593,8 @@ setInterval(function () {
} }
}, getUpdateFrequency()); }, getUpdateFrequency());
if (process.platform === "linux" && settingsStore.get(settings.mpris)) { addMPRIS();
try { addCustomCss(app);
player = Player({
name: "tidal-hifi",
identity: "tidal-hifi",
supportedUriSchemes: ["file"],
supportedMimeTypes: [
"audio/mpeg",
"audio/flac",
"audio/x-flac",
"application/ogg",
"audio/wav",
],
supportedInterfaces: ["player"],
desktopEntry: "tidal-hifi",
});
// Events
const events = {
next: "next",
previous: "previous",
pause: "pause",
playpause: "playpause",
stop: "stop",
play: "play",
loopStatus: "repeat",
shuffle: "shuffle",
seek: "seek",
} as { [key: string]: string };
Object.keys(events).forEach(function (eventName) {
player.on(eventName, function () {
const eventValue = events[eventName];
switch (events[eventValue]) {
case events.playpause:
playPause();
break;
default:
elements.click(eventValue);
}
});
});
// Override get position function
player.getPosition = function () {
return convertDuration(elements.getText("current")) * 1000 * 1000;
};
player.on("quit", function () {
app.quit();
});
} catch (exception) {
console.log("player api not working");
}
}
addCustomCss();
addHotKeys(); addHotKeys();
addIPCEventListeners(); addIPCEventListeners();
addFullScreenListeners(); addFullScreenListeners();

View File

@@ -1,8 +1,11 @@
import { Client } from "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 { Logger } from "../features/logger";
import { MediaStatus } from "../models/mediaStatus"; import { MediaStatus } from "../models/mediaStatus";
import { mediaInfo } from "./mediaInfo"; import { mediaInfo } from "./mediaInfo";
import { settingsStore } from "./settings";
const clientId = "833617820704440341"; const clientId = "833617820704440341";
@@ -15,48 +18,75 @@ function timeToSeconds(timeArray: string[]) {
export let rpc: Client; export let rpc: Client;
const observer = () => { const observer = () => {
if (mediaInfo.status == MediaStatus.paused && rpc) { if (rpc) {
rpc.setActivity(idleStatus); rpc.setActivity(getActivity());
} else if (rpc) { }
};
const defaultPresence = {
largeImageKey: "tidal-hifi-icon",
largeImageText: `TIDAL Hi-Fi ${app.getVersion()}`,
instance: false,
};
const getActivity = (): Presence => {
const presence: Presence = { ...defaultPresence };
if (mediaInfo.status === MediaStatus.paused) {
presence.details =
settingsStore.get<string, string>(settings.discord.idleText) ?? "Browsing Tidal";
} else {
const showSong = settingsStore.get<string, boolean>(settings.discord.showSong) ?? false;
if (showSong) {
const { includeTimestamps, detailsPrefix, buttonText } = getFromStore();
includeTimeStamps(includeTimestamps);
setPresenceFromMediaInfo(detailsPrefix, buttonText);
} else {
presence.details =
settingsStore.get<string, string>(settings.discord.usingText) ?? "Playing media on TIDAL";
}
}
return presence;
function getFromStore() {
const includeTimestamps =
settingsStore.get<string, boolean>(settings.discord.includeTimestamps) ?? true;
const detailsPrefix =
settingsStore.get<string, string>(settings.discord.detailsPrefix) ?? "Listening to ";
const buttonText =
settingsStore.get<string, string>(settings.discord.buttonText) ?? "Play on TIDAL";
return { includeTimestamps, detailsPrefix, buttonText };
}
function setPresenceFromMediaInfo(detailsPrefix: any, buttonText: any) {
if (mediaInfo.url) {
presence.details = `${detailsPrefix}${mediaInfo.title}`;
presence.state = mediaInfo.artists ? mediaInfo.artists : "unknown artist(s)";
presence.largeImageKey = mediaInfo.image;
if (mediaInfo.album) {
presence.largeImageText = mediaInfo.album;
}
presence.buttons = [{ label: buttonText, url: mediaInfo.url }];
} else {
presence.details = `Watching ${mediaInfo.title}`;
presence.state = mediaInfo.artists;
}
}
function includeTimeStamps(includeTimestamps: any) {
if (includeTimestamps) {
const currentSeconds = timeToSeconds(mediaInfo.current.split(":")); const currentSeconds = timeToSeconds(mediaInfo.current.split(":"));
const durationSeconds = timeToSeconds(mediaInfo.duration.split(":")); const durationSeconds = timeToSeconds(mediaInfo.duration.split(":"));
const date = new Date(); const date = new Date();
const now = (date.getTime() / 1000) | 0; const now = (date.getTime() / 1000) | 0;
const remaining = date.setSeconds(date.getSeconds() + (durationSeconds - currentSeconds)); const remaining = date.setSeconds(date.getSeconds() + (durationSeconds - currentSeconds));
if (mediaInfo.url) { presence.startTimestamp = now;
rpc.setActivity({ presence.endTimestamp = remaining;
...idleStatus,
...{
details: `Listening to ${mediaInfo.title}`,
state: mediaInfo.artists ? mediaInfo.artists : "unknown artist(s)",
startTimestamp: now,
endTimestamp: remaining,
largeImageKey: mediaInfo.image,
largeImageText: mediaInfo.album ? mediaInfo.album : `${idleStatus.largeImageText}`,
buttons: [{ label: "Play on Tidal", url: mediaInfo.url }],
},
});
} else {
rpc.setActivity({
...idleStatus,
...{
details: `Watching ${mediaInfo.title}`,
state: mediaInfo.artists,
startTimestamp: now,
endTimestamp: remaining,
},
});
} }
} }
}; };
const idleStatus = {
details: `Browsing Tidal`,
largeImageKey: "tidal-hifi-icon",
largeImageText: `Tidal HiFi ${app.getVersion()}`,
instance: false,
};
/** /**
* Set up the discord rpc and listen on globalEvents.updateInfo * Set up the discord rpc and listen on globalEvents.updateInfo
*/ */
@@ -65,12 +95,12 @@ export const initRPC = () => {
rpc.login({ clientId }).then( rpc.login({ clientId }).then(
() => { () => {
rpc.on("ready", () => { rpc.on("ready", () => {
rpc.setActivity(idleStatus); rpc.setActivity(getActivity());
}); });
ipcMain.on(globalEvents.updateInfo, observer); ipcMain.on(globalEvents.updateInfo, observer);
}, },
() => { () => {
console.error("Can't connect to Discord, is it running?"); Logger.log("Can't connect to Discord, is it running?");
} }
); );
}; };

View File

@@ -1,14 +1,14 @@
import { BrowserWindow, dialog } from "electron"; import { BrowserWindow, dialog } from "electron";
import express, { Response } from "express"; import express, { Response } from "express";
import fs from "fs"; import fs from "fs";
import { settings } from "../constants/settings";
import { MediaStatus } from "../models/mediaStatus";
import { globalEvents } from "./../constants/globalEvents"; import { globalEvents } from "./../constants/globalEvents";
import { statuses } from "./../constants/statuses";
import { mediaInfo } from "./mediaInfo"; import { mediaInfo } from "./mediaInfo";
import { settingsStore } from "./settings"; import { settingsStore } from "./settings";
import { settings } from "../constants/settings";
/** /**
* Function to enable tidal-hifi's express api * Function to enable TIDAL Hi-Fi's express api
*/ */
// expressModule.run = function (mainWindow) // expressModule.run = function (mainWindow)
@@ -40,11 +40,14 @@ export const startExpress = (mainWindow: BrowserWindow) => {
if (settingsStore.get(settings.playBackControl)) { if (settingsStore.get(settings.playBackControl)) {
expressApp.get("/play", (req, res) => handleGlobalEvent(res, globalEvents.play)); expressApp.get("/play", (req, res) => handleGlobalEvent(res, globalEvents.play));
expressApp.post("/favorite/toggle", (req, res) =>
handleGlobalEvent(res, globalEvents.toggleFavorite)
);
expressApp.get("/pause", (req, res) => handleGlobalEvent(res, globalEvents.pause)); expressApp.get("/pause", (req, res) => handleGlobalEvent(res, globalEvents.pause));
expressApp.get("/next", (req, res) => handleGlobalEvent(res, globalEvents.next)); expressApp.get("/next", (req, res) => handleGlobalEvent(res, globalEvents.next));
expressApp.get("/previous", (req, res) => handleGlobalEvent(res, globalEvents.previous)); expressApp.get("/previous", (req, res) => handleGlobalEvent(res, globalEvents.previous));
expressApp.get("/playpause", (req, res) => { expressApp.get("/playpause", (req, res) => {
if (mediaInfo.status == statuses.playing) { if (mediaInfo.status === MediaStatus.playing) {
handleGlobalEvent(res, globalEvents.pause); handleGlobalEvent(res, globalEvents.pause);
} else { } else {
handleGlobalEvent(res, globalEvents.play); handleGlobalEvent(res, globalEvents.play);

View File

@@ -1,16 +1,17 @@
import { MediaInfo } from "../models/mediaInfo"; import { MediaInfo } from "../models/mediaInfo";
import { statuses } from "./../constants/statuses"; import { MediaStatus } from "../models/mediaStatus";
export const mediaInfo = { export const mediaInfo = {
title: "", title: "",
artists: "", artists: "",
album: "", album: "",
icon: "", icon: "",
status: statuses.paused, status: MediaStatus.paused as string,
url: "", url: "",
current: "", current: "",
duration: "", duration: "",
image: "tidal-hifi-icon", image: "tidal-hifi-icon",
favorite: false,
}; };
export const updateMediaInfo = (arg: MediaInfo) => { export const updateMediaInfo = (arg: MediaInfo) => {
@@ -23,6 +24,7 @@ export const updateMediaInfo = (arg: MediaInfo) => {
mediaInfo.current = propOrDefault(arg.current); mediaInfo.current = propOrDefault(arg.current);
mediaInfo.duration = propOrDefault(arg.duration); mediaInfo.duration = propOrDefault(arg.duration);
mediaInfo.image = propOrDefault(arg.image); mediaInfo.image = propOrDefault(arg.image);
mediaInfo.favorite = arg.favorite;
}; };
/** /**

View File

@@ -33,7 +33,6 @@ export const getMenu = function (mainWindow: BrowserWindow) {
{ {
label: name, label: name,
submenu: [ submenu: [
{ role: "about" },
settingsMenuEntry, settingsMenuEntry,
{ type: "separator" }, { type: "separator" },
{ role: "services" }, { role: "services" },
@@ -101,12 +100,6 @@ export const getMenu = function (mainWindow: BrowserWindow) {
], ],
}, },
settingsMenuEntry, settingsMenuEntry,
{
label: "About",
click() {
showSettingsWindow("about");
},
},
toggleWindow, toggleWindow,
quitMenuEntry, quitMenuEntry,
]; ];

View File

@@ -1,10 +1,29 @@
import Store from "electron-store"; import Store from "electron-store";
import { settings } from "../constants/settings";
import path from "path";
import { BrowserWindow } from "electron"; import { BrowserWindow } from "electron";
import path from "path";
import { settings } from "../constants/settings";
let settingsWindow: BrowserWindow; let settingsWindow: BrowserWindow;
/**
* Build a migration step for several settings.
* All settings will be checked and set to the default if non-existent.
* @param version
* @param migrationStore
* @param options
*/
const buildMigration = (
version: string,
migrationStore: { get: (str: string) => string; set: (str: string, val: unknown) => void },
options: Array<{ key: string; value: unknown }>
) => {
console.log(`running migrations for ${version}`);
options.forEach(({ key, value }) => {
const valueToSet = migrationStore.get(key) ?? value;
console.log(` - setting ${key} to ${value}`);
migrationStore.set(key, valueToSet);
});
};
export const settingsStore = new Store({ export const settingsStore = new Store({
defaults: { defaults: {
@@ -18,9 +37,24 @@ export const settingsStore = new Store({
disableHardwareMediaKeys: false, disableHardwareMediaKeys: false,
enableCustomHotkeys: false, enableCustomHotkeys: false,
enableDiscord: false, enableDiscord: false,
discord: {
showSong: true,
idleText: "Browsing Tidal",
usingText: "Playing media on TIDAL",
includeTimestamps: true,
detailsPrefix: "Listening to ",
buttonText: "Play on Tidal",
},
ListenBrainz: {
enabled: false,
api: "https://api.listenbrainz.org",
token: "",
delay: 5000,
},
flags: { flags: {
gpuRasterization: true,
disableHardwareMediaKeys: false, disableHardwareMediaKeys: false,
enableWaylandSupport: true,
gpuRasterization: true,
}, },
menuBar: true, menuBar: true,
minimizeOnClose: false, minimizeOnClose: false,
@@ -43,6 +77,30 @@ export const settingsStore = new Store({
migrationStore.get("disableHardwareMediaKeys") ?? false migrationStore.get("disableHardwareMediaKeys") ?? false
); );
}, },
"5.7.0": (migrationStore) => {
console.log("running migrations for 5.7.0");
migrationStore.set(
settings.ListenBrainz.delay,
migrationStore.get(settings.ListenBrainz.delay) ?? 5000
);
},
"5.8.0": (migrationStore) => {
console.log("running migrations for 5.8.0");
migrationStore.set(
settings.discord.includeTimestamps,
migrationStore.get(settings.discord.includeTimestamps) ?? true
);
},
"5.9.0": (migrationStore) => {
buildMigration("5.9.0", migrationStore, [
{ key: settings.discord.showSong, value: "true" },
{ key: settings.discord.idleText, value: "Browsing Tidal" },
{
key: settings.discord.usingText,
value: "Playing media on TIDAL",
},
]);
},
}, },
}); });
@@ -53,8 +111,8 @@ const settingsModule = {
export const createSettingsWindow = function () { export const createSettingsWindow = function () {
settingsWindow = new BrowserWindow({ settingsWindow = new BrowserWindow({
width: 700, width: 650,
height: 600, height: 700,
resizable: true, resizable: true,
show: false, show: false,
transparent: true, transparent: true,
@@ -80,6 +138,10 @@ export const createSettingsWindow = function () {
}; };
export const showSettingsWindow = function (tab = "general") { export const showSettingsWindow = function (tab = "general") {
if (!settingsWindow) {
console.log("Settings window is not initialized. Attempting to create it.");
createSettingsWindow();
}
settingsWindow.webContents.send("goToTab", tab); settingsWindow.webContents.send("goToTab", tab);
// refresh data just before showing the window // refresh data just before showing the window

View File

@@ -17,37 +17,37 @@
--search-dialog-background: #24283b; --search-dialog-background: #24283b;
--right-queue-background: #24283b; --right-queue-background: #24283b;
} }
.player--fNPGt.notFullscreen--ugyc2 { .player--gAOQG.notFullscreen--xbpBL {
background-color: var(--footer-player-background); background-color: var(--footer-player-background);
} }
.sidebar--WvRg_ { .sidebar--jVJai {
background-color: var(--sidebar-background); background-color: var(--sidebar-background);
contain: strict; contain: strict;
flex-grow: 1; flex-grow: 1;
overflow-y: auto; overflow-y: auto;
} }
.item--VTpWS:hover { .item--buEQw:hover {
background-color: var(--sidebar-hover-background); background-color: var(--sidebar-hover-background);
} }
.main--LUnJp { .main--jxfcQ {
background-color: var(--main-background); background-color: var(--main-background);
} }
button.button--ncJwL { button.button--yO9Cd {
background-color: var(--main-navigation-control-background); background-color: var(--main-navigation-control-background);
} }
.player--fNPGt.lossLess--g5Jss button.withBackground[aria-checked="true"] path { .player--gAOQG.lossLess--ON3FI button.withBackground[aria-checked="true"] path {
fill: var(--player-control-active-button); fill: var(--player-control-active-button);
} }
.player--fNPGt.lossLess--g5Jss button.withBackground[aria-checked="true"] { .player--gAOQG.lossLess--ON3FI button.withBackground[aria-checked="true"] {
background-color: var(--player-control-background); background-color: var(--player-control-background);
} }
.activeItem--qV6eL .activeItem--qV6eL .playlistItem--YARJh .section--FI41E.playingItem--eWkYS { .activeItem--kFIk0 .activeItem--kFIk0 .playlistItem--mQrxp .section--PSIay.playingItem--eWkYS {
color: #565f89; color: #565f89;
} }
.progressBarWrapper--WZfox { .progressBarWrapper--IBBI9 {
color: var(--player-progress-bar); color: var(--player-progress-bar);
} }
.playbackControls--FLeZA button .tidal-ui__icon { .playbackControls--FhKVf button .tidal-ui__icon {
transform: scale(1); transform: scale(1);
} }
.css-11m9iw3 { .css-11m9iw3 {
@@ -56,27 +56,149 @@ button.button--ncJwL {
.css-11m9iw3 span { .css-11m9iw3 span {
color: var(--indicator-hifi-span); color: var(--indicator-hifi-span);
} }
.activeItem--qV6eL { .activeItem--kFIk0 {
color: var(--sidebar-menu-top-text); color: var(--sidebar-menu-top-text);
} }
.activeItem--qV6eL .playlistItem--YARJh { .activeItem--kFIk0 .playlistItem--mQrxp {
color: var(--sidebar-menu-playlist-text); color: var(--sidebar-menu-playlist-text);
} }
button.feedBell--B8anb { button.feedBell--kvAbD {
background-color: var(--main-feed-button-background); background-color: var(--main-feed-button-background);
} }
.baseContainer--cbf17 { .baseContainer--jxCbW {
background-color: var(--search-dialog-background); background-color: var(--search-dialog-background);
} }
.favoriteButton--TtBlM.is-favorite path { .favoriteButton--Qladw.is-favorite path {
fill: var(--player-control-favorite); fill: var(--player-control-favorite);
} }
.container--mkEWd { .container--PFTHk {
background-color: var(--right-queue-background); background-color: var(--right-queue-background);
} }
.container--vJVjO { .container--cl4MJ {
background-color: var(--search-background); background-color: var(--search-background);
} }
.searchFieldHighlighted--Fitvs { .searchFieldHighlighted--Fitvs {
color: var(--snow-white); color: var(--snow-white);
} }
.searchField--EGBSq {
background-color: var(--search-background);
}
// Settings window styling
.settings-window {
color: var(--sidebar-menu-playlist-text);
}
.settings-window__wrapper {
background: var(--main-background);
box-shadow: inset 0 0 2px 0 var(--main-feed-button-background);
}
.settings-window__close-button:hover {
background: var(--main-feed-button-background);
}
.settings input:checked + label {
border-bottom: 2px solid var(--player-control-active-button);
color: var(--player-control-active-button);
}
.tabs::-webkit-scrollbar-thumb {
background-color: #404248;
box-shadow: inset 0 0 10px 2px var(--search-background);
}
.group {
border-bottom: 1px solid #333;
}
.group__description p {
color: var(--sidebar-menu-top-text);
}
.group__description .text-input {
border-bottom: solid 1px #333;
color: var(--sidebar-menu-top-text);
}
.group__description .text-input:focus {
border-color: var(--player-control-active-button);
color: var(--sidebar-menu-playlist-text);
}
.switch input:checked + .switch__slider {
background-color: var(--player-control-active-button);
}
.switch input:checked + .switch__slider::before {
background-color: var(--sidebar-menu-playlist-text);
}
.switch input:focus + .switch__slider {
box-shadow: inset 0 0 0 1px var(--player-control-active-button);
}
.switch__slider {
background-color: var(--search-background);
}
.switch__slider::before {
background-color: var(--sidebar-menu-playlist-text);
}
.textarea {
background: var(--search-background);
color: var(--sidebar-menu-top-text);
}
.textarea:focus {
border-color: var(--player-control-active-button);
color: var(--sidebar-menu-playlist-text);
}
.about-section__version a {
background-color: #404248;
color: var(--player-control-active-button);
}
.about-section__links a {
color: var(--sidebar-menu-playlist-text);
background-color: var(--search-background);
}
.about-section__links a i {
color: var(--sidebar-menu-playlist-text);
}
.about-section__links a:hover {
background-color: var(--player-control-favorite);
}
.footer__note {
color: var(--sidebar-menu-top-text);
}
.footer__button {
background: #404248;
color: var(--sidebar-menu-playlist-text);
}
.footer__button:hover {
background: #55585f;
}
.file-drop-area {
border: 1px dashed var(--sidebar-menu-top-text);
}
.file-drop-area.is-active {
background-color: #17171a;
}
.file-btn {
background-color: #17171a;
border: 1px solid var(--sidebar-menu-top-text);
}
.select-input {
border-bottom: solid 1px #333;
color: var(--sidebar-menu-top-text);
}
.select-input:focus {
border-color: var(--player-control-active-button);
color: var(--sidebar-menu-playlist-text);
}
.select-input option {
background-color: var(--search-background);
}
.select-input option:disabled {
color: var(--sidebar-menu-playlist-text);
}

View File

@@ -1,8 +1,9 @@
{ {
"compilerOptions": { "compilerOptions": {
"typeRoots": ["src/types"], "typeRoots": ["src/types", "node_modules/@types"],
"module": "commonjs", "module": "commonjs",
"target": "ES6", "target": "ES6",
"lib": ["ES2020", "DOM"],
"noImplicitAny": true, "noImplicitAny": true,
"sourceMap": true, "sourceMap": true,
"allowJs": true, "allowJs": true,