Compare commits

..

231 Commits

Author SHA1 Message Date
7f8760c4e9
Merge pull request #583 from Mastermindzh/snyk-upgrade-36ee0aede4ca42df3db4bef87530e654
[Snyk] Upgrade sass from 1.85.1 to 1.86.0
2025-04-20 13:24:05 +02:00
snyk-bot
1cf5a01ad7
fix: upgrade sass from 1.85.1 to 1.86.0
Snyk has created this PR to upgrade sass from 1.85.1 to 1.86.0.

See this package in npm:
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
2025-04-19 09:04:54 +00:00
8fc49f500e
Merge pull request #577 from Mastermindzh/snyk-upgrade-b144be84df6292449bfe4eacd9662b97
[Snyk] Upgrade sass from 1.79.4 to 1.85.1
2025-04-18 14:50:52 +02:00
snyk-bot
49f5a01b58
fix: upgrade sass from 1.79.4 to 1.85.1
Snyk has created this PR to upgrade sass from 1.79.4 to 1.85.1.

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

See this package in npm:
hotkeys-js

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

See this package in npm:
axios

See this project in Snyk:
https://app.snyk.io/org/mastermindzh/project/dade8f03-2064-49a3-8957-edbacec3887c?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-12-27 23:17:27 +00:00
aaf7a1b662
Merge pull request #504 from darkiox/master
Fix Discord RPC Timestamp jitter
2024-12-09 11:21:27 +01:00
755be0ee30
Merge pull request #515 from Mastermindzh/snyk-fix-82857ea1745fc2cfbc5f06a87a8ff0b8
[Snyk] Security upgrade express from 4.21.1 to 4.21.2
2024-12-09 11:05:23 +01:00
snyk-bot
9d736b2bd9
fix: package.json & package-lock.json to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-PATHTOREGEXP-8482416
2024-12-06 20:33:54 +00:00
f4d4b1a1df
Merge pull request #508 from Mastermindzh/snyk-fix-a6630fbdb3c8f0161d0b939cf0ec3ca3
[Snyk] Security upgrade axios from 1.7.7 to 1.7.8
2024-12-02 09:03:11 +01:00
snyk-bot
0c27c815f5
fix: package.json & package-lock.json to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-AXIOS-6671926
2024-11-30 06:57:38 +00:00
Darkiox
a3aa45a96b fix: discord rpc timestamp jitter 2024-11-25 19:15:26 -03:00
ae699887b2
Merge pull request #494 from Mastermindzh/develop
Develop
2024-10-27 21:51:50 +01:00
a8c635932f Merge branch 'develop' of github.com:Mastermindzh/tidal-hifi into develop 2024-10-27 21:27:38 +01:00
66d0d004bf discord listening to 2024-10-27 21:27:23 +01:00
d1e0321058
Merge pull request #488 from 3top1a/master
"Listening to" Discord RPC
2024-10-27 21:24:08 +01:00
2ab5a556ab Removed Songwhip (they shut down) and replaced it with TIDAL's universal link system 2024-10-27 21:01:37 +01:00
baf719fc60 Merge branch 'develop' of github.com:Mastermindzh/tidal-hifi into develop 2024-10-27 20:24:38 +01:00
15c8f6a418 prepping 5.17 2024-10-27 20:24:32 +01:00
f73521e2e5
Merge pull request #491 from darkiox/master
feat: Added ability to keep a static window title.
2024-10-27 20:20:23 +01:00
3top1a
0f5e00c4df Revert "Removed redundant RPC settings" 2024-10-23 19:14:36 +02:00
Darío Marmie
974877ea4f staticWindowTitle(fix): Order alphabetically on all appearances, change if to a ternary 2024-10-23 09:39:13 -03:00
3top1a
6be5774001 Added RPC connection retrying 2024-10-22 22:29:56 +02:00
3top1a
f96cf2e8da Removed redundant RPC settings 2024-10-22 22:27:29 +02:00
3top1a
f832bd2712 replaced discord rpc library 2024-10-22 17:20:06 +02:00
Darío Marmie
cb8667bd41 feat: Added ability to keep a static window title. 2024-10-21 18:28:03 -03:00
91156e5936
Merge pull request #490 from Mastermindzh/snyk-upgrade-aca3a223a3167341470045eeca25ec6e
[Snyk] Upgrade sass from 1.78.0 to 1.79.4
2024-10-21 09:12:32 +02:00
snyk-bot
57e9a2dcb8
fix: upgrade sass from 1.78.0 to 1.79.4
Snyk has created this PR to upgrade sass from 1.78.0 to 1.79.4.

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-10-19 06:05:50 +00:00
3top1a
21edcd6ad5 reverted more ugly/unnecessary changes 2024-10-17 20:51:23 +02:00
3top1a
3dc42eceb0 Reverted bad changes 2024-10-17 20:47:40 +02:00
3top1a
b1830f5684 Fixed impl 2024-10-17 20:16:22 +02:00
3top1a
1a0c15e17f Maybe? Can't build 2024-10-16 20:37:47 +02:00
6e59d59a1d
Merge pull request #484 from Mastermindzh/snyk-fix-a313b5a6ce537f9549a8ae3a45f1033f
[Snyk] Security upgrade express from 4.21.0 to 4.21.1
2024-10-16 09:51:56 +02:00
snyk-bot
fab7497311
fix: package.json & package-lock.json to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-COOKIE-8163060
2024-10-12 06:26:33 +00:00
fccbcc77ea
Merge pull request #478 from Mastermindzh/snyk-upgrade-28ef48ed574610f887d2ae8544baaf7d
[Snyk] Upgrade axios from 1.7.5 to 1.7.7
2024-09-26 11:09:22 +02:00
snyk-bot
041c19fb52
fix: upgrade axios from 1.7.5 to 1.7.7
Snyk has created this PR to upgrade axios from 1.7.5 to 1.7.7.

See this package in npm:
axios

See this project in Snyk:
https://app.snyk.io/org/mastermindzh/project/dade8f03-2064-49a3-8957-edbacec3887c?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-09-26 08:54:14 +00:00
e36c562afa
Merge pull request #472 from Mastermindzh/snyk-upgrade-6ce11eb8281ab57e87d8cb068d3cf64d
[Snyk] Upgrade axios from 1.7.4 to 1.7.5
2024-09-25 16:36:18 +02:00
1589aa5251
Merge pull request #473 from Mastermindzh/dependabot/npm_and_yarn/multi-cf87d80143
chore(deps): bump send and express
2024-09-25 16:35:39 +02:00
8c672dd1eb
Merge pull request #476 from Mastermindzh/snyk-upgrade-3215721a01f05f1d0592d13bf3fb865f
[Snyk] Upgrade sass from 1.77.8 to 1.78.0
2024-09-25 16:34:49 +02:00
snyk-bot
3769550f24
fix: upgrade sass from 1.77.8 to 1.78.0
Snyk has created this PR to upgrade sass from 1.77.8 to 1.78.0.

See this package in npm:
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-09-25 09:23:53 +00:00
dependabot[bot]
5a06b6c53f
chore(deps): bump send and express
Bumps [send](https://github.com/pillarjs/send) to 0.19.0 and updates ancestor dependency [express](https://github.com/expressjs/express). These dependencies need to be updated together.


Updates `send` from 0.18.0 to 0.19.0
- [Release notes](https://github.com/pillarjs/send/releases)
- [Changelog](https://github.com/pillarjs/send/blob/master/HISTORY.md)
- [Commits](https://github.com/pillarjs/send/compare/0.18.0...0.19.0)

Updates `express` from 4.20.0 to 4.21.0
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.0/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.20.0...4.21.0)

---
updated-dependencies:
- dependency-name: send
  dependency-type: indirect
- dependency-name: express
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-17 08:12:34 +00:00
snyk-bot
1d4ef66d27
fix: upgrade axios from 1.7.4 to 1.7.5
Snyk has created this PR to upgrade axios from 1.7.4 to 1.7.5.

See this package in npm:
axios

See this project in Snyk:
https://app.snyk.io/org/mastermindzh/project/dade8f03-2064-49a3-8957-edbacec3887c?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-09-14 08:18:21 +00:00
e02f07401b
Merge pull request #459 from Mastermindzh/snyk-fix-0bde81baefef700af2dc83cf83bf83d4
[Snyk] Security upgrade axios from 1.7.2 to 1.7.4
2024-09-10 15:27:26 +02:00
118f92e75a
Merge pull request #469 from Mastermindzh/dependabot/npm_and_yarn/multi-ceff1a497b
chore(deps): bump path-to-regexp and express
2024-09-10 15:05:56 +02:00
dependabot[bot]
788d302ce8
chore(deps): bump path-to-regexp and express
Bumps [path-to-regexp](https://github.com/pillarjs/path-to-regexp) to 0.1.10 and updates ancestor dependency [express](https://github.com/expressjs/express). These dependencies need to be updated together.


Updates `path-to-regexp` from 0.1.7 to 0.1.10
- [Release notes](https://github.com/pillarjs/path-to-regexp/releases)
- [Changelog](https://github.com/pillarjs/path-to-regexp/blob/master/History.md)
- [Commits](https://github.com/pillarjs/path-to-regexp/compare/v0.1.7...v0.1.10)

Updates `express` from 4.19.2 to 4.20.0
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/master/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.19.2...4.20.0)

---
updated-dependencies:
- dependency-name: path-to-regexp
  dependency-type: indirect
- dependency-name: express
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-10 10:21:44 +00:00
snyk-bot
cc09f35b49
fix: package.json & package-lock.json to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-AXIOS-7361793
2024-08-14 22:44:20 +00:00
ef13933c66
Merge pull request #456 from Mastermindzh/develop
Develop
2024-08-10 14:49:42 +02:00
4f72e1b35d fix: Notifications are now send at the end of the update process, allowing other events to happen sooner. 2024-08-10 14:46:08 +02:00
2c1c76d2d0 fix: all discord fields are now padded to 2+ chars 2024-08-10 14:20:13 +02:00
e2a84e119a
Merge pull request #448 from autumn-puffin/master
Add option to disable the discord rpc idle text
2024-08-05 18:28:53 +02:00
5b85e59fc3
Merge pull request #450 from HurleybirdJr/master
Fix issue #449 Discord RPC stuck on "Browsing Tidal"
2024-08-05 18:28:44 +02:00
c3b772919a
Merge pull request #451 from Mastermindzh/snyk-upgrade-ceb71bfecd1c7652906d5640e4d56a9b
[Snyk] Upgrade sass from 1.77.6 to 1.77.8
2024-08-02 14:27:00 +02:00
snyk-bot
70f2f5c248
fix: upgrade sass from 1.77.6 to 1.77.8
Snyk has created this PR to upgrade sass from 1.77.6 to 1.77.8.

See this package in npm:
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-08-02 08:17:34 +00:00
Will Hurley
ffcb563b35 Fix Discord RPC album length < 2 2024-08-01 12:10:37 +01:00
Autumn
2dd96dd48e Add option to disable the discord rpc idle text 2024-07-30 21:26:35 +10:00
65f3b251f4
Merge pull request #435 from Mastermindzh/feature/docs
feat: dependency udpate & openapi extension
2024-07-15 23:54:03 +02:00
45f8c13c5b feat: Added a channel selector so we can now use Tidal's staging environment directly from the app 2024-07-15 12:36:55 +02:00
9dc7267a4d renamed 'song' to 'media' in html 2024-07-15 09:40:08 +02:00
f5c56ae8c5 deps: bumpies 2024-07-10 14:57:00 +02:00
308b527469 Merge branch 'master' of github.com:Mastermindzh/tidal-hifi into feature/docs 2024-07-10 14:56:07 +02:00
f4aa8e070e
Merge pull request #434 from Mastermindzh/snyk-upgrade-ef2733c0cae4459854fe8a974ba4cb47
[Snyk] Upgrade sass from 1.77.5 to 1.77.6
2024-07-10 08:53:12 +02:00
19c12b57de ci: change to node 22.4 2024-07-09 11:06:57 +02:00
b5713651de Merge branch 'feature/docs' of github.com:Mastermindzh/tidal-hifi into feature/docs 2024-07-09 10:51:53 +02:00
268027734c feat: dependency udpate & openapi extension 2024-07-09 10:51:40 +02:00
f711ea9000 feat: dependency udpate & openapi extension 2024-07-09 10:41:31 +02:00
snyk-bot
506f86f014
fix: upgrade sass from 1.77.5 to 1.77.6
Snyk has created this PR to upgrade sass from 1.77.5 to 1.77.6.

See this package in npm:
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-07-09 06:24:36 +00:00
4e827e120f
Merge pull request #431 from Mastermindzh/snyk-upgrade-de4b5154b7445fb3cf5ec389f4c5715f
[Snyk] Upgrade sass from 1.77.4 to 1.77.5
2024-07-04 10:23:34 +02:00
snyk-bot
5b62154ebc
fix: upgrade sass from 1.77.4 to 1.77.5
Snyk has created this PR to upgrade sass from 1.77.4 to 1.77.5.

See this package in npm:
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-07-03 05:18:29 +00:00
15cc6bb6d4
Merge pull request #425 from Mastermindzh/snyk-upgrade-f3026437cbeaad8aa72daea7d39ede0c
[Snyk] Upgrade sass from 1.77.2 to 1.77.4
2024-06-24 11:26:17 +02:00
e0e9d99173
Merge branch 'master' into snyk-upgrade-f3026437cbeaad8aa72daea7d39ede0c 2024-06-24 11:13:10 +02:00
daa797fc00
Merge pull request #427 from Mastermindzh/snyk-upgrade-fc345622b53226751fadf4ff2a3da0b1
[Snyk] Upgrade swagger-ui-express from 5.0.0 to 5.0.1
2024-06-24 10:56:04 +02:00
snyk-bot
4e011a47d8
fix: upgrade swagger-ui-express from 5.0.0 to 5.0.1
Snyk has created this PR to upgrade swagger-ui-express from 5.0.0 to 5.0.1.

See this package in npm:
swagger-ui-express

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-06-22 20:28:34 +00:00
snyk-bot
965d19318e
fix: upgrade sass from 1.77.2 to 1.77.4
Snyk has created this PR to upgrade sass from 1.77.2 to 1.77.4.

See this package in npm:
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-06-21 20:55:14 +00:00
9cd89c9f31
Merge pull request #423 from Mastermindzh/dependabot/npm_and_yarn/ws-7.5.10
chore(deps): bump ws from 7.5.9 to 7.5.10
2024-06-20 15:10:02 +02:00
dependabot[bot]
c7b97a49c4
chore(deps): bump ws from 7.5.9 to 7.5.10
Bumps [ws](https://github.com/websockets/ws) from 7.5.9 to 7.5.10.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/7.5.9...7.5.10)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-18 19:01:45 +00:00
c63e05357e
Update README.md 2024-06-16 05:23:28 +02:00
42dac4a7f1
Merge pull request #420 from Mastermindzh/snyk-upgrade-9156bcae7d2be2477cf56414a066139f
[Snyk] Upgrade axios from 1.7.1 to 1.7.2
2024-06-13 13:38:10 +02:00
snyk-bot
595895dbc1
fix: upgrade axios from 1.7.1 to 1.7.2
Snyk has created this PR to upgrade axios from 1.7.1 to 1.7.2.

See this package in npm:
axios

See this project in Snyk:
https://app.snyk.io/org/mastermindzh/project/dade8f03-2064-49a3-8957-edbacec3887c?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-06-13 05:53:24 +00:00
1ff94e45a7
Merge pull request #418 from Mastermindzh/dependabot/npm_and_yarn/braces-3.0.3
chore(deps): bump braces from 3.0.2 to 3.0.3
2024-06-11 10:28:50 +02:00
dependabot[bot]
95776d1aab
chore(deps): bump braces from 3.0.2 to 3.0.3
Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3.
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-11 07:43:23 +00:00
5669cb4c6a
Merge pull request #417 from Mastermindzh/snyk-upgrade-338b304353e209764e7b225a59b9a6ea
[Snyk] Upgrade sass from 1.75.0 to 1.77.2
2024-06-11 09:42:56 +02:00
5946c47442
Merge pull request #416 from Mastermindzh/snyk-upgrade-81ed892136e18f6714c0baec6619f4b2
[Snyk] Upgrade axios from 1.6.8 to 1.7.1
2024-06-11 09:42:39 +02:00
snyk-bot
111238f6b2
fix: upgrade sass from 1.75.0 to 1.77.2
Snyk has created this PR to upgrade sass from 1.75.0 to 1.77.2.

See this package in npm:
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-06-11 04:40:11 +00:00
snyk-bot
65a4600c4d
fix: upgrade axios from 1.6.8 to 1.7.1
Snyk has created this PR to upgrade axios from 1.6.8 to 1.7.1.

See this package in npm:
axios

See this project in Snyk:
https://app.snyk.io/org/mastermindzh/project/dade8f03-2064-49a3-8957-edbacec3887c?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-06-11 04:40:07 +00:00
dd6f81386f
Merge pull request #414 from Mastermindzh/version/next
fix: Fixed  not finding album name whilst on queue page
2024-06-09 18:51:37 +02:00
54316d31b5 Added all mediaInfo to mpris interface using the prefix 2024-06-09 16:16:25 +02:00
28a9458dfc fix: Fixed not finding album name whilst on queue page 2024-06-09 15:50:49 +02:00
3641f07558
Merge pull request #413 from Mastermindzh/version/next
Version/next
2024-06-09 14:07:13 +02:00
ecbfa7e226 feat: Added [Tidal Magazine](https://tidal.com/magazine/) integration (in the menubar or use ) 2024-06-09 13:51:55 +02:00
9321acc06e fix: Reworked swagger generation hotfix to properly generate during the compile step 2024-06-09 13:28:43 +02:00
5b656ae229 feat: API now allows you to set the so you can control who can interact with the API. 2024-06-09 13:07:49 +02:00
0a8efc730d simplified mediaInfo & Options 2024-06-09 12:33:48 +02:00
b49bd925da
Merge pull request #410 from Mastermindzh/hotfix/swagger-issues
Hotfix/swagger issues
2024-05-27 13:05:24 +02:00
1e6b9f7dcf ci: added manual trigger to actions 2024-05-27 12:33:34 +02:00
51f7a96634 hotfix: fixed api not working due to swagger 2024-05-27 12:28:45 +02:00
46074c5de5
Merge pull request #407 from Mastermindzh/new-version
5.13.0
2024-05-20 16:01:28 +02:00
2667f62674 chore: changelog update 2024-05-20 15:48:46 +02:00
ac949dc211
Merge pull request #401 from Mjokfox/api/add_cors
Add cors to the express api
2024-05-20 15:47:56 +02:00
40bc20582f Merge branch 'new-version' of github.com:Mastermindzh/tidal-hifi into api/add_cors 2024-05-20 15:46:02 +02:00
mjokfox
180d9c97a7 Add the cors module and use it with express api 2024-05-20 15:42:49 +02:00
5dc136138b fix: possible type confusion 2024-05-20 15:34:38 +02:00
1edc6a1b2b chore: versioning 2024-05-20 15:24:15 +02:00
7c6831c771 added swagger docs 2024-05-20 15:23:26 +02:00
d47da91e93 Added an API to add & delete entries from the skippedArtists list in the settings. fixes [#405] 2024-05-20 14:24:47 +02:00
b481108af1 fix: fixes #403 - cannot read shuffle of undefined error 2024-05-20 12:18:52 +02:00
3740ce5a12
Merge pull request #402 from Mastermindzh/5.12
5.12
2024-05-14 23:08:55 +02:00
a0f9faa753 chore: updating versions 2024-05-14 22:53:07 +02:00
5e3583534b
Merge pull request #396 from ThatGravyBoat/api/shuffle-repeat-state
Api Feature: Add shuffle and repeat state to API
2024-05-14 22:50:59 +02:00
ThatGravyBoat
5f8cf33249
Fix mismatched import styling 2024-05-06 03:55:09 -02:30
ThatGravyBoat
2d94b4bf49
Add shuffle and repeat to current state api 2024-05-06 03:50:41 -02:30
6e43cbb4d7
Merge pull request #395 from Mastermindzh/next-version
Next version
2024-05-05 20:46:21 +02:00
f95f13b44a chore: doc fix 2024-05-05 20:35:58 +02:00
f911564d8a chore: version increase 2024-05-05 20:34:58 +02:00
db8a2c2741 feat: reworked the api, added duration/current in seconds + shuffle & repeat 2024-05-05 20:12:10 +02:00
000853414e feat: switched to TIDAL's universal link format in the entire app 2024-05-05 15:09:54 +02:00
53603c4cad
Merge pull request #392 from TheRockYT/MediaSessionService
Media session service
2024-05-05 14:25:02 +02:00
0b595f920f
Merge pull request #391 from Mastermindzh/dependabot/npm_and_yarn/ejs-3.1.10
chore(deps-dev): bump ejs from 3.1.9 to 3.1.10
2024-05-05 13:57:59 +02:00
81143af3fa
Merge pull request #394 from Mastermindzh/snyk-upgrade-0d65fc86b872e70883e2ebe344b05405
[Snyk] Upgrade sass from 1.74.1 to 1.75.0
2024-05-05 13:57:41 +02:00
snyk-bot
8d1ac3be3b
fix: upgrade sass from 1.74.1 to 1.75.0
Snyk has created this PR to upgrade sass from 1.74.1 to 1.75.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-05-03 22:13:17 +00:00
dependabot[bot]
666e602c02
chore(deps-dev): bump ejs from 3.1.9 to 3.1.10
Bumps [ejs](https://github.com/mde/ejs) from 3.1.9 to 3.1.10.
- [Release notes](https://github.com/mde/ejs/releases)
- [Commits](https://github.com/mde/ejs/compare/v3.1.9...v3.1.10)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-02 10:01:32 +00:00
TheRockYT
04ec850005
Merge branch 'Mastermindzh:master' into MediaSessionService 2024-05-01 20:55:39 +02:00
943d9b5bd8
Merge pull request #386 from Mastermindzh/snyk-upgrade-2399381ec4ed8a254535fddca6093bb9
[Snyk] Upgrade sass from 1.72.0 to 1.74.1
2024-04-25 10:26:22 +02:00
snyk-bot
755816c2b8
fix: upgrade sass from 1.72.0 to 1.74.1
Snyk has created this PR to upgrade sass from 1.72.0 to 1.74.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
2024-04-25 04:29:39 +00:00
25afd05ad7
Merge pull request #374 from TheRockYT/custom_protocol_fix
Custom protocol fix (tidal://...)
2024-04-22 11:23:47 +02:00
6e5024742a
Merge pull request #382 from Mastermindzh/snyk-upgrade-e8583a1804fc58a60a199bdcdfddb237
[Snyk] Upgrade sass from 1.71.1 to 1.72.0
2024-04-18 12:34:41 +02:00
417afaab85
Merge pull request #381 from Mastermindzh/snyk-upgrade-eddeada86cf707578042602e7a9acfd4
[Snyk] Upgrade electron-store from 8.1.0 to 8.2.0
2024-04-18 12:34:27 +02:00
d225c0056b
Merge pull request #380 from Mastermindzh/snyk-upgrade-636acae2850bc80d0d46d524748be780
[Snyk] Upgrade axios from 1.6.5 to 1.6.8
2024-04-18 12:34:18 +02:00
snyk-bot
a75b0336db
fix: upgrade sass from 1.71.1 to 1.72.0
Snyk has created this PR to upgrade sass from 1.71.1 to 1.72.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-04-16 20:25:03 +00:00
snyk-bot
29465ce13a
fix: upgrade electron-store from 8.1.0 to 8.2.0
Snyk has created this PR to upgrade electron-store from 8.1.0 to 8.2.0.

See this package in npm:
https://www.npmjs.com/package/electron-store

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-04-16 20:25:00 +00:00
snyk-bot
d333047269
fix: upgrade axios from 1.6.5 to 1.6.8
Snyk has created this PR to upgrade axios from 1.6.5 to 1.6.8.

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
2024-04-16 20:24:56 +00:00
TheRockYT
712330f8f1 Enable MediaSessionService flag to allow listen.tidal.com to control it. 2024-04-04 23:08:18 +02:00
TheRockYT
84fd35ce0e Remove implementation of global shortcuts for media control. 2024-04-04 23:06:29 +02:00
TheRockYT
326038f262 Remove custom implementation of MediaSessionService. It is disabled anyway. 2024-04-04 23:05:09 +02:00
a6c1d35a60
Merge pull request #375 from Mastermindzh/snyk-fix-a6b2d7614f87b9d818d7aaf7e6d7650d
[Snyk] Security upgrade express from 4.18.3 to 4.19.2
2024-03-28 14:09:29 +01:00
snyk-bot
c09a4bc4a8
fix: package.json & package-lock.json to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-EXPRESS-6474509
2024-03-26 21:37:31 +00:00
TheRockYT
554cb12a01 Fix Custom Protocol Handling
Fixed an issue where the custom protocol ('tidal://...') wasn't being recognized correctly. Now, the system checks for the presence of the custom protocol before the usual URL ('https://listen.tidal.com') is loaded.

Also implemented functionality to ensure that if only one instance is allowed at a time, the first instance changes its page accordingly.
2024-03-26 00:16:09 +01:00
2e31b5d913
Merge pull request #373 from Mastermindzh/develop
5.10.0
2024-03-24 21:11:58 +01:00
2fd29c1b83 added rel='noopener' to external links 2024-03-24 21:00:49 +01:00
b2f27a2afe Enabled wayland platform flags by default when launching through .desktop file fixes #273 #347 2024-03-24 20:54:46 +01:00
8e11fd7f09 Reverted to using old icon syntax with icons in the build directory. fixes #350 2024-03-24 16:17:46 +01:00
17b2818b70 Refactored nowPlaying code to always display the current state, even when the built-in UI is updated. fixes #351 #356 #370 2024-03-24 16:13:21 +01:00
4ef76c262e Links in the about window now open in the user's default browser. fixes #360 2024-03-24 15:55:33 +01:00
fd0dae2762 fix: rewrote docs to fix #365 2024-03-24 15:42:50 +01:00
aa59bdc6dd Merge branch 'master' of github.com:Mastermindzh/tidal-hifi into develop 2024-03-24 15:34:18 +01:00
5b5b6ecb38
Merge pull request #364 from lennart-k/master
feature: Track current notification to replace it on track change
2024-03-24 15:34:00 +01:00
5983145857
Merge pull request #369 from Mastermindzh/dependabot/npm_and_yarn/follow-redirects-1.15.6
chore(deps): bump follow-redirects from 1.15.4 to 1.15.6
2024-03-24 15:32:14 +01:00
0c7d579951
Merge pull request #371 from Mastermindzh/snyk-upgrade-30d8b55c8b39463bad9d0408a76bfa86
[Snyk] Upgrade express from 4.18.2 to 4.18.3
2024-03-24 15:32:02 +01:00
snyk-bot
235d916749
fix: upgrade express from 4.18.2 to 4.18.3
Snyk has created this PR to upgrade express from 4.18.2 to 4.18.3.

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

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-03-21 17:32:39 +00:00
dependabot[bot]
2d9f268866
chore(deps): bump follow-redirects from 1.15.4 to 1.15.6
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.4 to 1.15.6.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.4...v1.15.6)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-16 22:30:30 +00:00
ae65e57e32
Merge pull request #367 from Mastermindzh/snyk-upgrade-0829d5b61286f530a986191886019e0f
[Snyk] Upgrade sass from 1.71.0 to 1.71.1
2024-03-14 16:08:02 +01:00
snyk-bot
3f2d69f2f4
fix: upgrade sass from 1.71.0 to 1.71.1
Snyk has created this PR to upgrade sass from 1.71.0 to 1.71.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
2024-03-14 01:58:53 +00:00
5ff2cc68d3
Merge pull request #359 from Mastermindzh/snyk-upgrade-f5ef319e06f8e24c1358e777b2415e49
[Snyk] Upgrade hotkeys-js from 3.13.6 to 3.13.7
2024-03-12 09:21:24 +01:00
daabe5bdbb
Merge pull request #363 from Mastermindzh/snyk-upgrade-63565ec96f33448467289d767c1fb965
[Snyk] Upgrade sass from 1.70.0 to 1.71.0
2024-03-12 09:20:35 +01:00
Lennart
456727c0e0
feature: Track current notification to replace it on track change 2024-03-09 17:49:18 +01:00
snyk-bot
ba50e0c095
fix: upgrade sass from 1.70.0 to 1.71.0
Snyk has created this PR to upgrade sass from 1.70.0 to 1.71.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-03-08 19:44:36 +00:00
snyk-bot
312e90e8cb
fix: upgrade hotkeys-js from 3.13.6 to 3.13.7
Snyk has created this PR to upgrade hotkeys-js from 3.13.6 to 3.13.7.

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-03-01 19:11:09 +00:00
76769dfab3
Merge pull request #354 from Mastermindzh/snyk-upgrade-d64f8964b9d005d97354597114dfafa3
[Snyk] Upgrade hotkeys-js from 3.13.5 to 3.13.6
2024-02-29 09:11:22 +01:00
snyk-bot
565d32ae3d
fix: upgrade hotkeys-js from 3.13.5 to 3.13.6
Snyk has created this PR to upgrade hotkeys-js from 3.13.5 to 3.13.6.

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-24 02:43:08 +00:00
7be6f79040
Merge pull request #349 from Mastermindzh/snyk-upgrade-f0f8b19cd2b7f7662ced7cf1b914e52a
[Snyk] Upgrade @electron/remote from 2.1.1 to 2.1.2
2024-02-16 13:36:45 +01:00
snyk-bot
f894c82b12
fix: upgrade @electron/remote from 2.1.1 to 2.1.2
Snyk has created this PR to upgrade @electron/remote from 2.1.1 to 2.1.2.

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
2024-02-16 02:47:27 +00:00
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
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
62 changed files with 6500 additions and 3123 deletions

View File

@ -9,6 +9,7 @@ on:
branches-ignore: branches-ignore:
- master - master
- develop - develop
workflow_dispatch:
jobs: jobs:
build_on_linux: build_on_linux:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -20,26 +21,30 @@ jobs:
- uses: actions/checkout@master - uses: actions/checkout@master
- uses: actions/setup-node@master - uses: actions/setup-node@master
with: with:
node-version: 19 node-version: 22.4
- run: npm install - run: npm install
- run: npm run build - run: npm run build
# - uses: actions/upload-artifact@master
# with:
# name: linux-builds
# path: dist/
build_on_mac: # build_on_mac:
runs-on: macos-latest # runs-on: macos-latest
steps: # steps:
- uses: actions/checkout@master # - uses: actions/checkout@master
- uses: actions/setup-node@master # - uses: actions/setup-node@master
with: # with:
node-version: 19 # node-version: 22.4
- run: npm install # - run: npm install
- run: npm run build # - run: npm run build
build_on_win: # build_on_win:
runs-on: windows-latest # runs-on: windows-latest
steps: # steps:
- uses: actions/checkout@master # - uses: actions/checkout@master
- uses: actions/setup-node@master # - uses: actions/setup-node@master
with: # with:
node-version: 19 # node-version: 22.4
- run: npm install # - run: npm install
- run: npm run build # - run: npm run build

View File

@ -8,6 +8,7 @@ on:
pull_request: pull_request:
branches: branches:
- master - master
workflow_dispatch:
jobs: jobs:
build_on_linux: build_on_linux:
@ -20,7 +21,7 @@ jobs:
- uses: actions/checkout@master - uses: actions/checkout@master
- uses: actions/setup-node@master - uses: actions/setup-node@master
with: with:
node-version: 19 node-version: 22.4
- run: npm install - run: npm install
- run: npm run build - run: npm run build
- uses: actions/upload-artifact@master - uses: actions/upload-artifact@master
@ -28,30 +29,30 @@ jobs:
name: linux-builds name: linux-builds
path: dist/ path: dist/
build_on_mac: # build_on_mac:
runs-on: macos-latest # runs-on: macos-latest
steps: # steps:
- uses: actions/checkout@master # - uses: actions/checkout@master
- uses: actions/setup-node@master # - uses: actions/setup-node@master
with: # with:
node-version: 19 # node-version: 22.4
- run: npm install # - run: npm install
- run: npm run build # - run: npm run build
- uses: actions/upload-artifact@master # - uses: actions/upload-artifact@master
with: # with:
name: mac-builds # name: mac-builds
path: ./dist/ # path: ./dist/
build_on_win: # build_on_win:
runs-on: windows-latest # runs-on: windows-latest
steps: # steps:
- uses: actions/checkout@master # - uses: actions/checkout@master
- uses: actions/setup-node@master # - uses: actions/setup-node@master
with: # with:
node-version: 19 # node-version: 22.4
- run: npm install # - run: npm install
- run: npm run build # - run: npm run build
- uses: actions/upload-artifact@master # - uses: actions/upload-artifact@master
with: # with:
name: windows-builds # name: windows-builds
path: dist/ # path: dist/

View File

@ -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,

View File

@ -0,0 +1,5 @@
POST /settings/skipped-artists HTTP/1.1
Host: localhost:47836
Content-Type: application/json
["abc", "def"]

View File

@ -0,0 +1,2 @@
POST /settings/skipped-artists/current HTTP/1.1
Host: localhost:47836

View File

@ -0,0 +1,5 @@
POST /settings/skipped-artists/delete HTTP/1.1
Host: localhost:47836
Content-Type: application/json
["abc", "def"]

View File

@ -0,0 +1,2 @@
DELETE /settings/skipped-artists/current HTTP/1.1
Host: localhost:47836

View File

@ -2,6 +2,7 @@
"cSpell.words": [ "cSpell.words": [
"Brainz", "Brainz",
"Castlabs", "Castlabs",
"Fi's",
"flac", "flac",
"Flatpak", "Flatpak",
"geqnfr", "geqnfr",
@ -13,7 +14,6 @@
"rescrobbler", "rescrobbler",
"scrobble", "scrobble",
"scrobbling", "scrobbling",
"Songwhip",
"trackid", "trackid",
"tracklist", "tracklist",
"widevine", "widevine",

View File

@ -4,6 +4,149 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [5.18.2]
- Reverted to sass 1.79.4 to fix `Nix` builds
- Changed electron-builder.base.yml to now generate the correct .desktop entries again
- Should fix flatpak build
## [5.18.1]
- Fixed the login bug
- Upgraded electron to 35.1.1
- Added Widevine/CDM info to startup
- delayed remote electron initializer
## [5.18.0]
- [Dianoga](https://github.com/Dianoga) fixed the duration selector, restoring mpris & partial API data.
- PR: #554
- Added `xesam:url` property to mpris metadata fixes [#506](https://github.com/Mastermindzh/tidal-hifi/issues/506)
## [5.17.0]
- Added an option to disable the dynamic title and set it to a static one, [#491](https://github.com/Mastermindzh/tidal-hifi/pull/491)
- Discord integration now says "Listening to" instead of "playing" [#488](https://github.com/Mastermindzh/tidal-hifi/pull/488) && [#454](https://github.com/Mastermindzh/tidal-hifi/pull/454)
- Fixed several element names in the dom scraper
- Removed the Songwhip (they shut down) integration and replaced it with TIDAL's universal link system
## [5.16.0]
- Fix issue #449 Discord RPC stuck on "Browsing Tidal".
- Fix issue #448 Add option to disable the discord rpc idle text
- Notifications are now send at the end of the update process, allowing other events to happen sooner.
## [5.15.0]
- Added all missing swagger/openApi info with the help of [Times-Z](https://github.com/Times-Z)
- Updated most dependency versions
- This includes Electron 31!
- Added a channel selector so we can now use Tidal's staging environment directly from the app
- implements [#437](https://github.com/Mastermindzh/tidal-hifi/issues/437)
## [5.14.1]
- Fixed `getAlbumName` not finding album name whilst on queue page
- Added all mediaInfo to mpris interface using the `custom:` prefix
## [5.14]
- Simplified `MediaInfo` & `Options` types
- Added `playingFrom` information to the info API
- also changed the way we update Album info since Playing From now shows the correct Album.
- API now allows you to set the `hostname` so you can control who can interact with the API.
- Reworked swagger generation hotfix to properly generate `swagger.json` during the compile step
- Might switch to tsoa in the future, idk yet.
- Added [Tidal Magazine](https://tidal.com/magazine/) integration (in the menubar or use `Ctrl + M`)
## [5.13.1]
- removed Swagger generation step in favor of pre-generated file.
- This also fixes the API issue [#409](https://github.com/Mastermindzh/tidal-hifi/issues/409)
- This also stops TIDAL-hifi from scanning your entire home directory... the glob was very broad apparently.
## [5.13.0]
- Fixed [#403](https://github.com/Mastermindzh/tidal-hifi/issues/403) "cannot read shuffle of undefined" error
- Added an API to add & delete entries from the skippedArtists list in the settings. fixes [#405](https://github.com/Mastermindzh/tidal-hifi/issues/405)
- `GET /settings/skipped-artists` -> get list of skipped artists
- `POST /settings/skipped-artists` -> add to the list of skipped artists
- `POST /settings/skipped-artists/delete` -> delete from the list of skipped artists
- `POST /settings/skipped-artists/current` -> skip the current artist
- `DELETE /settings/skipped-artists/current` -> delete the current artist from the skip list
- Added Swagger documentation to the new endpoints:
![picture of swagger documentation](./docs/images/swagger.png)
- CORS support added by [Mjokfox](https://github.com/Mjokfox)
## [5.12.0]
- Added Shuffle and Repeat state to API response - By [ThatGravyBoat](https://github.com/ThatGravyBoat)
## [5.11.0]
- Re-implemented the API, added support for duration/current in seconds & shuffle+repeat
- made the original API "legacy" (still works the same)
- Now using the correct HTTP verb for all new endpoints
- Implemented TIDAL's universal links. All links are now universal.
- Custom `tidal://` protocol fixed - By [TheRockYT](https://github.com/TheRockYT)
- Global media shortcuts removed since TIDAL includes them by default - By [TheRockYT](https://github.com/TheRockYT)
- Fixes
- [#390](https://github.com/Mastermindzh/tidal-hifi/issues/390)
- [#376](https://github.com/Mastermindzh/tidal-hifi/issues/376)
- [#383](https://github.com/Mastermindzh/tidal-hifi/issues/383)
- [#393](https://github.com/Mastermindzh/tidal-hifi/issues/393)
## [5.10.0]
- TIDAL will now close the previous notification if a new one is sent whilst the old is still visible. [#364](https://github.com/Mastermindzh/tidal-hifi/pull/364)
- Updated developer documentation to get started in README [#365](https://github.com/Mastermindzh/tidal-hifi/pull/365)
- Links in the about window now open in the user's default browser. fixes [#360](https://github.com/Mastermindzh/tidal-hifi/issues/360)
- Refactored "nowPlaying" code to always display the current state, even when the built-in UI is updated.
- fixes [#351](https://github.com/Mastermindzh/tidal-hifi/issues/351)
- fixes [#356](https://github.com/Mastermindzh/tidal-hifi/issues/356)
- fixes [#370](https://github.com/Mastermindzh/tidal-hifi/issues/370)
- Reverted to using old icon syntax with icons in the build directory. fixes [#350](https://github.com/Mastermindzh/tidal-hifi/issues/350)
- Enabled wayland platform flags by default when launching through .desktop file
- fixes [#273](https://github.com/Mastermindzh/tidal-hifi/issues/273)
- fixes [#347](https://github.com/Mastermindzh/tidal-hifi/issues/347)
## [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] ## [5.7.0]
- Renamed app to TIDAL Hi-Fi. - Renamed app to TIDAL Hi-Fi.

View File

@ -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)
- [DRM not working on Windows](#drm-not-working-on-windows) - [DRM not working on Windows (error S6007)](#drm-not-working-on-windows-error-s6007)
- [Special thanks to](#special-thanks-to) - [Special thanks to](#special-thanks-to)
- [Donations](#donations) - [Donations](#donations)
- [Images](#images) - [Images](#images)
@ -43,13 +43,13 @@ The web version of [listen.tidal.com](https://listen.tidal.com) running in elect
- 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/) - 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`) - [Settings feature](./docs/images/settings.png) to disable certain functionality. (`ctrl+=` or `ctrl+0`)
- API for status and playback - API for status, playback and settings (see the [/docs](http://localhost:47836/docs/) route)
- 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))
- AlbumArt in integrations ([best-effort](https://github.com/Mastermindzh/tidal-hifi/pull/88#pullrequestreview-840814847)) - AlbumArt in integrations ([best-effort](https://github.com/Mastermindzh/tidal-hifi/pull/88#pullrequestreview-840814847))
- Custom [integrations](#integrations) - Custom [integrations](#integrations)
- [ListenBrainz](https://listenbrainz.org/?redirect=false) integration - [ListenBrainz](https://listenbrainz.org/?redirect=false) integration
- Songwhip.com integration (hotkey `ctrl + w`)
- Discord RPC integration (showing "now listening", "Browsing", etc) - Discord RPC integration (showing "now listening", "Browsing", etc)
- Flatpak version only works if both Discord and Tidal-HiFi are flatpaks
- MPRIS integration - MPRIS integration
- UI + Json config (`~/.config/tidal-hifi/`, or `~/.var/app/com.mastermindzh.tidal-hifi/` for Flatpak) - UI + Json config (`~/.config/tidal-hifi/`, or `~/.var/app/com.mastermindzh.tidal-hifi/` for Flatpak)
@ -130,10 +130,13 @@ 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`
- cd TIDAL Hi-Fi - `cd tidal-hifi`
- npm install - `npm install`
- npm start - `npm run watch` to watch for auto-reload of Typescript/SCSS changes.
- `npm run compile` can be used to trigger it once
- `npm watchStart` to auto watch for any updates files and reload Tidal Hi-Fi
- `npm start` can be used to run Tidal Hi-Fi manually once
## Integrations ## Integrations
@ -149,11 +152,13 @@ Integrations with other projects that are not included natively:
## Known bugs ## Known bugs
### DRM not working on Windows ### DRM not working on Windows (error S6007)
Most Windows users run into DRM issues when trying to use TIDAL Hi-Fi. Most Windows users run into DRM issues when trying to use TIDAL Hi-Fi.
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. 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.
Until then you'll have to use the official app unfortunately.
## Special thanks to ## Special thanks to
- [Castlabs](https://castlabs.com/) - [Castlabs](https://castlabs.com/)

View File

@ -1,7 +1,7 @@
appId: com.rickvanlieshout.tidal-hifi appId: com.rickvanlieshout.tidal-hifi
electronVersion: 24.1.2 electronVersion: 35.1.1
electronDownload: electronDownload:
version: 24.1.2+wvcus version: 35.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,24 +11,30 @@ extraResources:
- "themes/**" - "themes/**"
linux: linux:
category: AudioVideo category: AudioVideo
icon: assets/icons icon: build/icons
target: target:
- dir - dir
executableName: tidal-hifi executableName: tidal-hifi
executableArgs:
[
"--enable-features=UseOzonePlatform",
"--ozone-platform-hint=auto",
"--enable-features=WaylandWindowDecorations",
]
desktop: desktop:
Encoding: UTF-8 entry:
Name: TIDAL Hi-Fi Encoding: "UTF-8"
GenericName: TIDAL Hi-Fi Name: "TIDAL Hi-Fi"
Comment: The web version of listen.tidal.com running in electron with hifi support thanks to widevine. GenericName: "TIDAL Hi-Fi"
Icon: tidal-hifi Comment: "The web version of listen.tidal.com running in electron with hifi support thanks to widevine."
StartupNotify: true Icon: "tidal-hifi"
Terminal: false StartupNotify: "true"
Type: Application Terminal: "false"
Categories: Network;Application;AudioVideo;Audio;Video Type: "Application"
StartupWMClass: tidal-hifi Categories: "Network;Application;AudioVideo;Audio;Video"
X-PulseAudio-Properties: media.role=music StartupWMClass: "tidal-hifi"
MimeType: x-scheme-handler/tidal; X-PulseAudio-Properties: "media.role=music"
MimeType: "x-scheme-handler/tidal;"
mac: mac:
category: public.app-category.entertainment category: public.app-category.entertainment
win: win:

BIN
build/icons/128x128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

BIN
build/icons/16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
build/icons/22x22.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
build/icons/24x24.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

BIN
build/icons/256x256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
build/icons/32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
build/icons/384x384.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
build/icons/48x48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
build/icons/64x64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 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

BIN
docs/images/swagger.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

6778
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +1,17 @@
{ {
"name": "tidal-hifi", "name": "tidal-hifi",
"version": "5.7.0", "version": "5.18.2",
"description": "Tidal on Electron with widevine(hifi) support", "description": "Tidal on Electron with widevine(hifi) support",
"main": "ts-dist/main.js", "main": "ts-dist/main.js",
"scripts": { "scripts": {
"start": "electron --inspect=0.0.0.0:5858 .", "start": "electron --inspect=0.0.0.0:5858 --remote-debugging-port=8315 --remote-allow-origins=* .",
"watchStart": "nodemon dist -x \"npm run start\"", "watchStart": "nodemon dist -x \"npm run start\"",
"compile": "tsc && npm run sass-and-copy", "compile": "tsc && npm run sass-and-copy",
"watch": "tsc-watch --onSuccess \"npm run sass-and-copy\"", "deps": "npm run watch",
"watch": "tsc-watch --onSuccess \"npm run compile-all\"",
"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",
"compile-all": "npm run sass-and-copy && ts-node scripts/generate-swagger.ts",
"sass-and-copy": "npm run sass && npm run copy-files && npm run copy-themes-dev", "sass-and-copy": "npm run sass && npm run copy-files && npm run copy-themes-dev",
"build": "npm run builder -- -c ./build/electron-builder.yml", "build": "npm run builder -- -c ./build/electron-builder.yml",
"build-deb": "npm run builder -- -c ./build/electron-builder.deb.yml", "build-deb": "npm run builder -- -c ./build/electron-builder.deb.yml",
@ -38,38 +40,44 @@
"homepage": "https://github.com/Mastermindzh/tidal-hifi", "homepage": "https://github.com/Mastermindzh/tidal-hifi",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@electron/remote": "^2.0.10", "@electron/remote": "^2.1.2",
"axios": "^1.4.0", "@types/swagger-jsdoc": "^6.0.4",
"discord-rpc": "^4.0.1", "@xhayper/discord-rpc": "^1.2.1",
"electron-store": "^8.1.0", "axios": "^1.8.4",
"express": "^4.18.2", "electron-store": "^8.2.0",
"hotkeys-js": "^3.12.0", "express": "^4.21.2",
"hotkeys-js": "^3.13.9",
"mpris-service": "^2.1.2", "mpris-service": "^2.1.2",
"request": "^2.88.2", "sass": "1.86.0",
"sass": "^1.64.1" "swagger-ui-express": "^5.0.1",
"cors": "^2.8.5",
"request": "^2.88.2"
}, },
"devDependencies": { "devDependencies": {
"@mastermindzh/prettier-config": "^1.0.0", "@mastermindzh/prettier-config": "^1.0.0",
"@types/discord-rpc": "^4.0.5", "@types/cors": "^2.8.17",
"@types/express": "^4.17.17", "@types/express": "^4.17.21",
"@types/node": "^20.4.4", "@types/node": "^20.14.10",
"@types/request": "^2.48.8", "@types/request": "^2.48.12",
"@typescript-eslint/eslint-plugin": "^6.1.0", "@types/swagger-ui-express": "^4.1.6",
"@typescript-eslint/parser": "^6.1.0", "@typescript-eslint/eslint-plugin": "^7.16.0",
"@typescript-eslint/parser": "^7.15.0",
"copyfiles": "^2.4.1", "copyfiles": "^2.4.1",
"electron": "git+https://github.com/castlabs/electron-releases.git#v24.1.2+wvcus", "electron": "github:castlabs/electron-releases#v35.1.1+wvcus",
"electron-builder": "^24.4.0", "electron-builder": "~26.0.12",
"eslint": "^8.45.0", "eslint": "^8.57.0",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"markdown-toc": "^1.2.0", "markdown-toc": "^1.2.0",
"nodemon": "^3.0.1", "nodemon": "^3.1.4",
"prettier": "^3.0.0", "prettier": "^3.3.2",
"stylelint": "^15.10.2", "stylelint": "^16.6.1",
"stylelint-config-standard": "^34.0.0", "stylelint-config-standard": "^36.0.1",
"stylelint-config-standard-scss": "^10.0.0", "stylelint-config-standard-scss": "^13.1.0",
"stylelint-prettier": "^4.0.0", "stylelint-prettier": "^5.0.0",
"tsc-watch": "^6.0.4", "swagger-jsdoc": "^6.2.8",
"typescript": "^5.1.6" "ts-node": "^10.9.2",
"tsc-watch": "^6.2.0",
"typescript": "^5.5.3"
}, },
"prettier": "@mastermindzh/prettier-config" "prettier": "@mastermindzh/prettier-config"
} }

View File

@ -0,0 +1,30 @@
import fs from "fs";
import swaggerjsdoc from "swagger-jsdoc";
import packagejson from "./../package.json";
const specs = swaggerjsdoc({
definition: {
openapi: "3.1.0",
info: {
title: "TIDAL Hi-Fi API",
version: packagejson.version,
description: "",
license: {
name: packagejson.license,
url: "https://github.com/Mastermindzh/tidal-hifi/blob/master/LICENSE",
},
contact: {
name: "Rick <mastermindzh> van Lieshout",
url: "https://www.rickvanlieshout.com",
},
},
externalDocs: {
description: "swagger.json",
url: "swagger.json",
},
},
apis: ["**/*.ts"],
});
fs.writeFileSync("src/features/api/swagger.json", JSON.stringify(specs, null, 2), "utf8");
console.log("Written swagger.json");

View File

@ -17,18 +17,23 @@
shuffle: '*[data-test="shuffle"]', shuffle: '*[data-test="shuffle"]',
repeat: '*[data-test="repeat"]', repeat: '*[data-test="repeat"]',
account: '*[data-test^="profile-image-button"]', account: '*[data-test^="profile-image-button"]',
settings: '*[data-test^="sidebar-menu-button"]',
openSettings: '*[data-test^="open-settings"]',
media: '*[data-test="current-media-imagery"]', media: '*[data-test="current-media-imagery"]',
image: "img", image: "img",
current: '*[data-test="current-time"]', current: '*[data-test="current-time"]',
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']", mediaItem: "[data-type='mediaItem']",
album_header_title: '.header-details [data-test="title"]', album_header_title: '*[class^="_playingFrom"] span:nth-child(2)',
playing_from: '*[class^="_playingFrom"] span:nth-child(2)',
queue_album: "*[class^=playQueueItemsContainer] *[class^=groupTitle] span:nth-child(2)",
currentlyPlaying: "[class^='isPlayingIcon'], [data-test-is-playing='true']", currentlyPlaying: "[class^='isPlayingIcon'], [data-test-is-playing='true']",
album_name_cell: '[class^="album"]', album_name_cell: '[class^="album"]',
tracklist_row: '[data-test="tracklist-row"]', tracklist_row: '[data-test="tracklist-row"]',
volume: '*[data-test="volume"]', volume: '*[data-test="volume"]',
favorite: '*[data-test="footer-favorite-button"]',
}; };
let results = []; let results = [];

View File

@ -10,6 +10,9 @@ export const globalEvents = {
showSettings: "showSettings", showSettings: "showSettings",
storeChanged: "storeChanged", storeChanged: "storeChanged",
error: "error", error: "error",
whip: "whip", getUniversalLink: "getUniversalLink",
log: "log", log: "log",
toggleFavorite: "toggleFavorite",
toggleShuffle: "toggleShuffle",
toggleRepeat: "toggleRepeat",
}; };

View File

@ -10,10 +10,15 @@
*/ */
export const settings = { export const settings = {
adBlock: "adBlock", adBlock: "adBlock",
advanced: {
root: "advanced",
tidalUrl: "advanced.tidalUrl",
},
api: "api", api: "api",
apiSettings: { apiSettings: {
root: "apiSettings", root: "apiSettings",
port: "apiSettings.port", port: "apiSettings.port",
hostname: "apiSettings.hostname",
}, },
customCSS: "customCSS", customCSS: "customCSS",
disableBackgroundThrottle: "disableBackgroundThrottle", disableBackgroundThrottle: "disableBackgroundThrottle",
@ -23,6 +28,11 @@ export const settings = {
discord: { discord: {
detailsPrefix: "discord.detailsPrefix", detailsPrefix: "discord.detailsPrefix",
buttonText: "discord.buttonText", buttonText: "discord.buttonText",
includeTimestamps: "discord.includeTimestamps",
showSong: "discord.showSong",
showIdle: "discord.showIdle",
idleText: "discord.idleText",
usingText: "discord.usingText",
}, },
ListenBrainz: { ListenBrainz: {
root: "ListenBrainz", root: "ListenBrainz",
@ -45,6 +55,7 @@ export const settings = {
singleInstance: "singleInstance", singleInstance: "singleInstance",
skipArtists: "skipArtists", skipArtists: "skipArtists",
skippedArtists: "skippedArtists", skippedArtists: "skippedArtists",
staticWindowTitle: "staticWindowTitle",
theme: "theme", theme: "theme",
trayIcon: "trayIcon", trayIcon: "trayIcon",
updateFrequency: "updateFrequency", updateFrequency: "updateFrequency",

View File

@ -0,0 +1,123 @@
import { Request, Response, Router } from "express";
import fs from "fs";
import { mediaInfo } from "../../../scripts/mediaInfo";
export const addCurrentInfo = (expressApp: Router) => {
/**
* @swagger
* tags:
* name: current
* description: The current media info API
* components:
* schemas:
* MediaInfo:
* type: object
* properties:
* title:
* type: string
* artists:
* type: string
* album:
* type: string
* icon:
* type: string
* format: uri
* playingFrom:
* type: string
* status:
* type: string
* url:
* type: string
* format: uri
* current:
* type: string
* currentInSeconds:
* type: integer
* duration:
* type: string
* durationInSeconds:
* type: integer
* image:
* type: string
* format: uri
* favorite:
* type: boolean
* player:
* type: object
* properties:
* status:
* type: string
* shuffle:
* type: boolean
* repeat:
* type: string
* artist:
* type: string
* example:
* title: "Sample Title"
* artists: "Sample Artist"
* album: "Sample Album"
* icon: "/path/to/sample/icon.jpg"
* playingFrom: "Sample Playlist"
* status: "playing"
* url: "https://tidal.com/browse/track/sample"
* current: "1:23"
* currentInSeconds: 83
* duration: "3:45"
* durationInSeconds: 225
* image: "https://example.com/sample-image.jpg"
* favorite: true
* player:
* status: "playing"
* shuffle: true
* repeat: "one"
* artist: "Sample Artist"
*/
/**
* @swagger
* /current:
* get:
* summary: Get current media info
* tags: [current]
* responses:
* 200:
* description: Current media info
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/MediaInfo'
*/
expressApp.get("/current", (req, res) => res.json({ ...mediaInfo, artist: mediaInfo.artists }));
/**
* @swagger
* /current/image:
* get:
* summary: Get current media image
* tags: [current]
* responses:
* 200:
* description: Current media image
* content:
* image/png:
* schema:
* type: string
* format: binary
* 404:
* description: Not found
*/
expressApp.get("/current/image", getCurrentImage);
};
export const getCurrentImage = (req: Request, res: Response) => {
const stream = fs.createReadStream(mediaInfo.icon);
stream.on("open", function () {
res.set("Content-Type", "image/png");
stream.pipe(res);
});
stream.on("error", function () {
res.set("Content-Type", "text/plain");
res.status(404).end("Not found");
});
};

View File

@ -0,0 +1,164 @@
import { BrowserWindow } from "electron";
import { Router } from "express";
import { globalEvents } from "../../../constants/globalEvents";
import { settings } from "../../../constants/settings";
import { MediaStatus } from "../../../models/mediaStatus";
import { mediaInfo } from "../../../scripts/mediaInfo";
import { settingsStore } from "../../../scripts/settings";
import { handleWindowEvent } from "../helpers/handleWindowEvent";
export const addPlaybackControl = (expressApp: Router, mainWindow: BrowserWindow) => {
const windowEvent = handleWindowEvent(mainWindow);
const createRoute = (route: string) => `/player${route}`;
/**
* @swagger
* tags:
* name: player
* description: The player control API
* components:
* schemas:
* OkResponse:
* type: string
* example: "OK"
*/
const createPlayerAction = (route: string, action: string) => {
expressApp.post(createRoute(route), (req, res) => windowEvent(res, action));
};
if (settingsStore.get(settings.playBackControl)) {
/**
* @swagger
* /player/play:
* post:
* summary: Play the current media
* tags: [player]
* responses:
* 200:
* description: Ok
* content:
* text/plain:
* schema:
* $ref: '#/components/schemas/OkResponse'
*/
createPlayerAction("/play", globalEvents.play);
/**
* @swagger
* /player/favorite/toggle:
* post:
* summary: Add the current media to your favorites, or remove it if its already added to your favorites
* tags: [player]
* responses:
* 200:
* description: Ok
* content:
* text/plain:
* schema:
* $ref: '#/components/schemas/OkResponse'
*/
createPlayerAction("/favorite/toggle", globalEvents.toggleFavorite);
/**
* @swagger
* /player/pause:
* post:
* summary: Pause the current media
* tags: [player]
* responses:
* 200:
* description: Ok
* content:
* text/plain:
* schema:
* $ref: '#/components/schemas/OkResponse'
*/
createPlayerAction("/pause", globalEvents.pause);
/**
* @swagger
* /player/next:
* post:
* summary: Play the next song
* tags: [player]
* responses:
* 200:
* description: Ok
* content:
* text/plain:
* schema:
* $ref: '#/components/schemas/OkResponse'
*/
createPlayerAction("/next", globalEvents.next);
/**
* @swagger
* /player/previous:
* post:
* summary: Play the previous song
* tags: [player]
* responses:
* 200:
* description: Ok
* content:
* text/plain:
* schema:
* $ref: '#/components/schemas/OkResponse'
*/
createPlayerAction("/previous", globalEvents.previous);
/**
* @swagger
* /player/shuffle/toggle:
* post:
* summary: Play the previous song
* tags: [player]
* responses:
* 200:
* description: Ok
* content:
* text/plain:
* schema:
* $ref: '#/components/schemas/OkResponse'
*/
createPlayerAction("/shuffle/toggle", globalEvents.toggleShuffle);
/**
* @swagger
* /player/repeat/toggle:
* post:
* summary: Toggle the repeat status, toggles between "off" , "single" and "all"
* tags: [player]
* responses:
* 200:
* description: Ok
* content:
* text/plain:
* schema:
* $ref: '#/components/schemas/OkResponse'
*/
createPlayerAction("/repeat/toggle", globalEvents.toggleRepeat);
/**
* @swagger
* /player/playpause:
* post:
* summary: Start playing the media if paused, or pause the media if playing
* tags: [player]
* responses:
* 200:
* description: Ok
* content:
* text/plain:
* schema:
* $ref: '#/components/schemas/OkResponse'
*/
expressApp.post(createRoute("/playpause"), (req, res) => {
if (mediaInfo.status === MediaStatus.playing) {
windowEvent(res, globalEvents.pause);
} else {
windowEvent(res, globalEvents.play);
}
});
}
};

View File

@ -0,0 +1,121 @@
import { Request, Router } from "express";
import { settings } from "../../../../constants/settings";
import { mediaInfo } from "../../../../scripts/mediaInfo";
import {
addSkippedArtists,
removeSkippedArtists,
settingsStore,
} from "../../../../scripts/settings";
import { BrowserWindow } from "electron";
import { globalEvents } from "../../../../constants/globalEvents";
/**
* @swagger
* tags:
* name: settings
* description: The settings management API
* components:
* schemas:
* StringArray:
* type: array
* items:
* type: string
* example: ["Artist1", "Artist2"]
*
* @param expressApp
* @param mainWindow
*/
export const addSettingsAPI = (expressApp: Router, mainWindow: BrowserWindow) => {
/**
* @swagger
* /settings/skipped-artists:
* get:
* summary: get a list of artists that TIDAL Hi-Fi will skip if skipping is enabled
* tags: [settings]
* responses:
* 200:
* description: The list book.
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/StringArray'
*/
expressApp.get("/settings/skipped-artists", (req, res) => {
res.json(settingsStore.get<string, string[]>(settings.skippedArtists));
});
/**
* @swagger
* /settings/skipped-artists:
* post:
* summary: Add new artists to the list of skipped artists
* tags: [settings]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/StringArray'
* responses:
* 200:
* description: Ok
*/
expressApp.post("/settings/skipped-artists", (req: Request<object, object, string[]>, res) => {
addSkippedArtists(req.body);
res.sendStatus(200);
});
/**
* @swagger
* /settings/skipped-artists/delete:
* post:
* summary: Remove artists from the list of skipped artists
* tags: [settings]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/StringArray'
* responses:
* 200:
* description: Ok
*/
expressApp.post(
"/settings/skipped-artists/delete",
(req: Request<object, object, string[]>, res) => {
removeSkippedArtists(req.body);
res.sendStatus(200);
}
);
/**
* @swagger
* /settings/skipped-artists/current:
* post:
* summary: Add the current artist to the list of skipped artists
* tags: [settings]
* responses:
* 200:
* description: Ok
*/
expressApp.post("/settings/skipped-artists/current", (req, res) => {
addSkippedArtists([mediaInfo.artists]);
mainWindow.webContents.send("globalEvent", globalEvents.next);
res.sendStatus(200);
});
/**
* @swagger
* /settings/skipped-artists/current:
* delete:
* summary: Remove the current artist from the list of skipped artists
* tags: [settings]
* responses:
* 200:
* description: Ok
*/
expressApp.delete("/settings/skipped-artists/current", (req, res) => {
removeSkippedArtists([mediaInfo.artists]);
res.sendStatus(200);
});
};

View File

@ -0,0 +1,12 @@
import { BrowserWindow } from "electron";
import { Response } from "express";
/**
* Shorthand to handle a fire and forget global event
* @param {*} res
* @param {*} action
*/
export const handleWindowEvent = (mainWindow: BrowserWindow) => (res: Response, action: string) => {
mainWindow.webContents.send("globalEvent", action);
res.sendStatus(200);
};

42
src/features/api/index.ts Normal file
View File

@ -0,0 +1,42 @@
import cors from "cors";
import { BrowserWindow, dialog } from "electron";
import express from "express";
import swaggerUi from "swagger-ui-express";
import { settingsStore } from "../../scripts/settings";
import { settings } from "./../../constants/settings";
import { addCurrentInfo } from "./features/current";
import { addPlaybackControl } from "./features/player";
import { addSettingsAPI } from "./features/settings/settings";
import { addLegacyApi } from "./legacy";
import swaggerSpec from "./swagger.json";
/**
* Function to enable TIDAL Hi-Fi's express api
*/
export const startApi = (mainWindow: BrowserWindow) => {
const port = settingsStore.get<string, number>(settings.apiSettings.port);
const hostname = settingsStore.get<string, string>(settings.apiSettings.hostname) ?? "127.0.0.1";
const expressApp = express();
expressApp.use(cors());
expressApp.use(express.json());
expressApp.use("/docs", swaggerUi.serve, swaggerUi.setup(swaggerSpec));
expressApp.get("/", (req, res) => res.send("Hello World!"));
expressApp.get("/swagger.json", (req, res) => res.json(swaggerSpec));
// add features
addLegacyApi(expressApp, mainWindow);
addPlaybackControl(expressApp, mainWindow);
addCurrentInfo(expressApp);
addSettingsAPI(expressApp, mainWindow);
const expressInstance = expressApp.listen(port, hostname);
expressInstance.on("error", function (e: { code: string }) {
let message = e.code;
if (e.code === "EADDRINUSE") {
message = `Port ${port} in use.`;
}
dialog.showErrorBox("Api failed to start.", message);
});
};

160
src/features/api/legacy.ts Normal file
View File

@ -0,0 +1,160 @@
import { BrowserWindow } from "electron";
import { Response, Router } from "express";
import { globalEvents } from "../../constants/globalEvents";
import { settings } from "../../constants/settings";
import { MediaStatus } from "../../models/mediaStatus";
import { mediaInfo } from "../../scripts/mediaInfo";
import { settingsStore } from "../../scripts/settings";
import { getCurrentImage } from "./features/current";
/**
* The legacy API, this will not be maintained and probably has duplicate code :)
* @param expressApp
* @param mainWindow
*/
export const addLegacyApi = (expressApp: Router, mainWindow: BrowserWindow) => {
/**
* @swagger
* /image:
* get:
* summary: Get current image
* tags: [legacy]
* deprecated: true
* responses:
* 200:
* description: Current image
* content:
* image/png:
* schema:
* type: string
* format: binary
* 404:
* description: Not found
*/
expressApp.get("/image", getCurrentImage);
if (settingsStore.get(settings.playBackControl)) {
addLegacyControls();
}
function addLegacyControls() {
/**
* @swagger
* /play:
* get:
* summary: Play the current media
* tags: [legacy]
* deprecated: true
* responses:
* 200:
* description: Action performed
* content:
* text/plain:
* schema:
* $ref: '#/components/schemas/OkResponse'
*/
expressApp.get("/play", ({ res }) => handleGlobalEvent(res, globalEvents.play));
/**
* @swagger
* /favorite/toggle:
* get:
* summary: Add the current media to your favorites, or remove it if its already added to your favorites
* tags: [legacy]
* deprecated: true
* responses:
* 200:
* description: Ok
* content:
* text/plain:
* schema:
* $ref: '#/components/schemas/OkResponse'
*/
expressApp.post("/favorite/toggle", (req, res) =>
handleGlobalEvent(res, globalEvents.toggleFavorite)
);
/**
* @swagger
* /pause:
* get:
* summary: Pause the current media
* tags: [legacy]
* deprecated: true
* responses:
* 200:
* description: Ok
* content:
* text/plain:
* schema:
* $ref: '#/components/schemas/OkResponse'
*/
expressApp.get("/pause", (req, res) => handleGlobalEvent(res, globalEvents.pause));
/**
* @swagger
* /next:
* get:
* summary: Play the next song
* tags: [legacy]
* deprecated: true
* responses:
* 200:
* description: Ok
* content:
* text/plain:
* schema:
* $ref: '#/components/schemas/OkResponse'
*/
expressApp.get("/next", (req, res) => handleGlobalEvent(res, globalEvents.next));
/**
* @swagger
* /previous:
* get:
* summary: Play the previous song
* tags: [legacy]
* deprecated: true
* responses:
* 200:
* description: Ok
* content:
* text/plain:
* schema:
* $ref: '#/components/schemas/OkResponse'
*/
expressApp.get("/previous", (req, res) => handleGlobalEvent(res, globalEvents.previous));
/**
* @swagger
* /playpause:
* get:
* summary: Toggle play/pause
* tags: [legacy]
* deprecated: true
* responses:
* 200:
* description: Ok
* content:
* text/plain:
* schema:
* $ref: '#/components/schemas/OkResponse'
*/
expressApp.get("/playpause", (req, res) => {
if (mediaInfo.status === MediaStatus.playing) {
handleGlobalEvent(res, globalEvents.pause);
} else {
handleGlobalEvent(res, globalEvents.play);
}
});
}
/**
* Shorthand to handle a fire and forget global event
* @param {*} res
* @param {*} action
*/
function handleGlobalEvent(res: Response, action: string) {
mainWindow.webContents.send("globalEvent", action);
res.sendStatus(200);
}
};

View File

@ -0,0 +1,535 @@
{
"openapi": "3.1.0",
"info": {
"title": "TIDAL Hi-Fi API",
"version": "5.18.2",
"description": "",
"license": {
"name": "MIT",
"url": "https://github.com/Mastermindzh/tidal-hifi/blob/master/LICENSE"
},
"contact": {
"name": "Rick <mastermindzh> van Lieshout",
"url": "https://www.rickvanlieshout.com"
}
},
"externalDocs": {
"description": "swagger.json",
"url": "swagger.json"
},
"paths": {
"/current": {
"get": {
"summary": "Get current media info",
"tags": ["current"],
"responses": {
"200": {
"description": "Current media info",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MediaInfo"
}
}
}
}
}
}
},
"/current/image": {
"get": {
"summary": "Get current media image",
"tags": ["current"],
"responses": {
"200": {
"description": "Current media image",
"content": {
"image/png": {
"schema": {
"type": "string",
"format": "binary"
}
}
}
},
"404": {
"description": "Not found"
}
}
}
},
"/player/play": {
"post": {
"summary": "Play the current media",
"tags": ["player"],
"responses": {
"200": {
"description": "Ok",
"content": {
"text/plain": {
"schema": {
"$ref": "#/components/schemas/OkResponse"
}
}
}
}
}
}
},
"/player/favorite/toggle": {
"post": {
"summary": "Add the current media to your favorites, or remove it if its already added to your favorites",
"tags": ["player"],
"responses": {
"200": {
"description": "Ok",
"content": {
"text/plain": {
"schema": {
"$ref": "#/components/schemas/OkResponse"
}
}
}
}
}
}
},
"/player/pause": {
"post": {
"summary": "Pause the current media",
"tags": ["player"],
"responses": {
"200": {
"description": "Ok",
"content": {
"text/plain": {
"schema": {
"$ref": "#/components/schemas/OkResponse"
}
}
}
}
}
}
},
"/player/next": {
"post": {
"summary": "Play the next song",
"tags": ["player"],
"responses": {
"200": {
"description": "Ok",
"content": {
"text/plain": {
"schema": {
"$ref": "#/components/schemas/OkResponse"
}
}
}
}
}
}
},
"/player/previous": {
"post": {
"summary": "Play the previous song",
"tags": ["player"],
"responses": {
"200": {
"description": "Ok",
"content": {
"text/plain": {
"schema": {
"$ref": "#/components/schemas/OkResponse"
}
}
}
}
}
}
},
"/player/shuffle/toggle": {
"post": {
"summary": "Play the previous song",
"tags": ["player"],
"responses": {
"200": {
"description": "Ok",
"content": {
"text/plain": {
"schema": {
"$ref": "#/components/schemas/OkResponse"
}
}
}
}
}
}
},
"/player/repeat/toggle": {
"post": {
"summary": "Toggle the repeat status, toggles between \"off\" , \"single\" and \"all\"",
"tags": ["player"],
"responses": {
"200": {
"description": "Ok",
"content": {
"text/plain": {
"schema": {
"$ref": "#/components/schemas/OkResponse"
}
}
}
}
}
}
},
"/player/playpause": {
"post": {
"summary": "Start playing the media if paused, or pause the media if playing",
"tags": ["player"],
"responses": {
"200": {
"description": "Ok",
"content": {
"text/plain": {
"schema": {
"$ref": "#/components/schemas/OkResponse"
}
}
}
}
}
}
},
"/settings/skipped-artists": {
"get": {
"summary": "get a list of artists that TIDAL Hi-Fi will skip if skipping is enabled",
"tags": ["settings"],
"responses": {
"200": {
"description": "The list book.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/StringArray"
}
}
}
}
}
},
"post": {
"summary": "Add new artists to the list of skipped artists",
"tags": ["settings"],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/StringArray"
}
}
}
},
"responses": {
"200": {
"description": "Ok"
}
}
}
},
"/settings/skipped-artists/delete": {
"post": {
"summary": "Remove artists from the list of skipped artists",
"tags": ["settings"],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/StringArray"
}
}
}
},
"responses": {
"200": {
"description": "Ok"
}
}
}
},
"/settings/skipped-artists/current": {
"post": {
"summary": "Add the current artist to the list of skipped artists",
"tags": ["settings"],
"responses": {
"200": {
"description": "Ok"
}
}
},
"delete": {
"summary": "Remove the current artist from the list of skipped artists",
"tags": ["settings"],
"responses": {
"200": {
"description": "Ok"
}
}
}
},
"/image": {
"get": {
"summary": "Get current image",
"tags": ["legacy"],
"deprecated": true,
"responses": {
"200": {
"description": "Current image",
"content": {
"image/png": {
"schema": {
"type": "string",
"format": "binary"
}
}
}
},
"404": {
"description": "Not found"
}
}
}
},
"/play": {
"get": {
"summary": "Play the current media",
"tags": ["legacy"],
"deprecated": true,
"responses": {
"200": {
"description": "Action performed",
"content": {
"text/plain": {
"schema": {
"$ref": "#/components/schemas/OkResponse"
}
}
}
}
}
}
},
"/favorite/toggle": {
"get": {
"summary": "Add the current media to your favorites, or remove it if its already added to your favorites",
"tags": ["legacy"],
"deprecated": true,
"responses": {
"200": {
"description": "Ok",
"content": {
"text/plain": {
"schema": {
"$ref": "#/components/schemas/OkResponse"
}
}
}
}
}
}
},
"/pause": {
"get": {
"summary": "Pause the current media",
"tags": ["legacy"],
"deprecated": true,
"responses": {
"200": {
"description": "Ok",
"content": {
"text/plain": {
"schema": {
"$ref": "#/components/schemas/OkResponse"
}
}
}
}
}
}
},
"/next": {
"get": {
"summary": "Play the next song",
"tags": ["legacy"],
"deprecated": true,
"responses": {
"200": {
"description": "Ok",
"content": {
"text/plain": {
"schema": {
"$ref": "#/components/schemas/OkResponse"
}
}
}
}
}
}
},
"/previous": {
"get": {
"summary": "Play the previous song",
"tags": ["legacy"],
"deprecated": true,
"responses": {
"200": {
"description": "Ok",
"content": {
"text/plain": {
"schema": {
"$ref": "#/components/schemas/OkResponse"
}
}
}
}
}
}
},
"/playpause": {
"get": {
"summary": "Toggle play/pause",
"tags": ["legacy"],
"deprecated": true,
"responses": {
"200": {
"description": "Ok",
"content": {
"text/plain": {
"schema": {
"$ref": "#/components/schemas/OkResponse"
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"MediaInfo": {
"type": "object",
"properties": {
"title": {
"type": "string"
},
"artists": {
"type": "string"
},
"album": {
"type": "string"
},
"icon": {
"type": "string",
"format": "uri"
},
"playingFrom": {
"type": "string"
},
"status": {
"type": "string"
},
"url": {
"type": "string",
"format": "uri"
},
"current": {
"type": "string"
},
"currentInSeconds": {
"type": "integer"
},
"duration": {
"type": "string"
},
"durationInSeconds": {
"type": "integer"
},
"image": {
"type": "string",
"format": "uri"
},
"favorite": {
"type": "boolean"
},
"player": {
"type": "object",
"properties": {
"status": {
"type": "string"
},
"shuffle": {
"type": "boolean"
},
"repeat": {
"type": "string"
}
}
},
"artist": {
"type": "string"
}
},
"example": {
"title": "Sample Title",
"artists": "Sample Artist",
"album": "Sample Album",
"icon": "/path/to/sample/icon.jpg",
"playingFrom": "Sample Playlist",
"status": "playing",
"url": "https://tidal.com/browse/track/sample",
"current": "1:23",
"currentInSeconds": 83,
"duration": "3:45",
"durationInSeconds": 225,
"image": "https://example.com/sample-image.jpg",
"favorite": true,
"player": {
"status": "playing",
"shuffle": true,
"repeat": "one"
},
"artist": "Sample Artist"
}
},
"OkResponse": {
"type": "string",
"example": "OK"
},
"StringArray": {
"type": "array",
"items": {
"type": "string"
},
"example": ["Artist1", "Artist2"]
}
}
},
"tags": [
{
"name": "current",
"description": "The current media info API"
},
{
"name": "player",
"description": "The player control API"
},
{
"name": "settings",
"description": "The settings management API"
}
]
}

View File

@ -48,6 +48,9 @@ export class ListenBrainz {
} else { } else {
// Fetches the oldData required for scrobbling and proceeds to construct a playing_now data payload for the Playing Now area // 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 oldData = ListenBrainzStore.get(ListenBrainzConstants.oldData) as StoreData;
const tidalUrl =
settingsStore.get<string, string>(settings.advanced.tidalUrl) ||
"https://listen.tidal.com";
const playing_data = { const playing_data = {
listen_type: "playing_now", listen_type: "playing_now",
payload: [ payload: [
@ -95,7 +98,7 @@ export class ListenBrainz {
additional_info: { additional_info: {
media_player: "Tidal Hi-Fi", media_player: "Tidal Hi-Fi",
submission_client: "Tidal Hi-Fi", submission_client: "Tidal Hi-Fi",
music_service: "listen.tidal.com", music_service: tidalUrl,
duration: oldData.duration, duration: oldData.duration,
}, },
artist_name: oldData.artists, artist_name: oldData.artists,

View File

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

View File

@ -1,21 +0,0 @@
import { ServiceLinks } from "./ServiceLinks";
export interface Artist {
type: string;
id: number;
path: string;
name: string;
sourceUrl: string;
sourceCountry: string;
url: string;
image: string;
createdAt: string;
updatedAt: string;
refreshedAt: string;
serviceIds: { [key: string]: string };
orchardId: string;
spotifyId: string;
links: { [key: string]: ServiceLinks[] };
linksCountries: string[];
description: string;
}

View File

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

View File

@ -1,27 +0,0 @@
import { Artist } from "./Artist";
import { ServiceLinks } from "./ServiceLinks";
export interface WhippedResult {
status: string;
data: {
item: {
type: string;
id: number;
path: string;
name: string;
url: string;
sourceUrl: string;
sourceCountry: string;
releaseDate: string;
createdAt: string;
updatedAt: string;
refreshedAt: string;
image: string;
isrc: string;
isExplicit: boolean;
links: { [key: string]: ServiceLinks[] };
linksCountries: string[];
artists: Artist[];
};
};
}

View File

@ -1,32 +0,0 @@
import { WhippedResult } from "./models/whip";
import axios from "axios";
export class Songwhip {
/**
* Call the songwhip API and create a shareable songwhip page
* @param currentUrl
* @returns
*/
public static async whip(currentUrl: string): Promise<WhippedResult> {
try {
const response = await axios.post("https://songwhip.com/api/songwhip/create", {
url: currentUrl,
// doesn't actually matter.. returns everything the same way anyway
country: "NL",
});
return response.data;
} catch (error) {
console.log(JSON.stringify(error));
}
}
/**
* Transform a songwhip response into a shareable url
* @param response
* @returns
*/
public static getWhipUrl(response: WhippedResult) {
return `https://songwhip.com${response.data.item.url}`;
}
}

View File

@ -4,7 +4,7 @@ import { settingsStore } from "../../scripts/settings";
import { Logger } from "../logger"; import { Logger } from "../logger";
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
export function addCustomCss(app: any, logger: typeof Logger) { export function addCustomCss(app: any) {
window.addEventListener("DOMContentLoaded", () => { window.addEventListener("DOMContentLoaded", () => {
const selectedTheme = settingsStore.get<string, string>(settings.theme); const selectedTheme = settingsStore.get<string, string>(settings.theme);
if (selectedTheme !== "none") { if (selectedTheme !== "none") {
@ -13,7 +13,7 @@ export function addCustomCss(app: any, logger: typeof Logger) {
const themeFile = fs.existsSync(userThemePath) ? userThemePath : resourcesThemePath; const themeFile = fs.existsSync(userThemePath) ? userThemePath : resourcesThemePath;
fs.readFile(themeFile, "utf-8", (err, data) => { fs.readFile(themeFile, "utf-8", (err, data) => {
if (err) { if (err) {
logger.alert("An error ocurred reading the theme file.", err, alert); Logger.alert("An error ocurred reading the theme file.", err, alert);
return; return;
} }

View File

@ -0,0 +1,14 @@
/**
* Convert a HH:MM:SS string (or variants such as MM:SS or SS) to plain seconds
* @param duration in HH:MM:SS format
* @returns number of seconds in duration
*/
export const convertDurationToSeconds = (duration: string) => {
return duration
.split(":")
.reverse()
.map((val) => Number(val))
.reduce((previous, current, index) => {
return index === 0 ? current : previous + current * Math.pow(60, index);
}, 0);
};

View File

@ -1,28 +1,19 @@
import { enable, initialize } from "@electron/remote/main"; import { enable, initialize } from "@electron/remote/main";
import { import { BrowserWindow, app, components, ipcMain, session } from "electron";
app,
BrowserWindow,
components,
globalShortcut,
ipcMain,
protocol,
session,
} from "electron";
import path from "path"; import path from "path";
import { globalEvents } from "./constants/globalEvents"; import { globalEvents } from "./constants/globalEvents";
import { mediaKeys } from "./constants/mediaKeys";
import { settings } from "./constants/settings"; import { settings } from "./constants/settings";
import { startApi } from "./features/api";
import { setDefaultFlags, setManagedFlagsFromSettings } from "./features/flags/flags"; import { setDefaultFlags, setManagedFlagsFromSettings } from "./features/flags/flags";
import { import {
acquireInhibitorIfInactive, acquireInhibitorIfInactive,
releaseInhibitorIfActive, releaseInhibitorIfActive,
} from "./features/idleInhibitor/idleInhibitor"; } from "./features/idleInhibitor/idleInhibitor";
import { Logger } from "./features/logger"; import { Logger } from "./features/logger";
import { Songwhip } from "./features/songwhip/songwhip"; import { SharingService } from "./features/sharingService/sharingService";
import { MediaInfo } from "./models/mediaInfo"; import { MediaInfo } from "./models/mediaInfo";
import { MediaStatus } from "./models/mediaStatus"; import { MediaStatus } from "./models/mediaStatus";
import { initRPC, rpc, unRPC } from "./scripts/discord"; import { initRPC, rpc, unRPC } from "./scripts/discord";
import { startExpress } from "./scripts/express";
import { updateMediaInfo } from "./scripts/mediaInfo"; import { updateMediaInfo } from "./scripts/mediaInfo";
import { addMenu } from "./scripts/menu"; import { addMenu } from "./scripts/menu";
import { import {
@ -33,10 +24,8 @@ import {
showSettingsWindow, showSettingsWindow,
} from "./scripts/settings"; } from "./scripts/settings";
import { addTray, refreshTray } from "./scripts/tray"; import { addTray, refreshTray } from "./scripts/tray";
const tidalUrl = "https://listen.tidal.com";
let mainInhibitorId = -1; let mainInhibitorId = -1;
initialize();
let mainWindow: BrowserWindow; let mainWindow: BrowserWindow;
const icon = path.join(__dirname, "../assets/icon.png"); const icon = path.join(__dirname, "../assets/icon.png");
const PROTOCOL_PREFIX = "tidal"; const PROTOCOL_PREFIX = "tidal";
@ -49,6 +38,9 @@ const windowPreferences = {
setDefaultFlags(app); setDefaultFlags(app);
setManagedFlagsFromSettings(app); setManagedFlagsFromSettings(app);
const tidalUrl =
settingsStore.get<string, string>(settings.advanced.tidalUrl) || "https://listen.tidal.com";
/** /**
* Update the menuBarVisibility according to the store value * Update the menuBarVisibility according to the store value
* *
@ -61,20 +53,31 @@ function syncMenuBarWithStore() {
} }
/** /**
* Determine whether the current window is the main window * @returns true/false based on whether the current window is the main window
* if singleInstance is requested.
* If singleInstance isn't requested simply return true
* @returns true if singInstance is not requested, otherwise true/false based on whether the current window is the main window
*/ */
function isMainInstanceOrMultipleInstancesAllowed() { function isMainInstance() {
if (settingsStore.get(settings.singleInstance)) { return app.requestSingleInstanceLock();
const gotTheLock = app.requestSingleInstanceLock(); }
if (!gotTheLock) { /**
return false; * @returns true/false based on whether multiple instances are allowed
*/
function isMultipleInstancesAllowed() {
return !settingsStore.get(settings.singleInstance);
}
/**
* @param args the arguments passed to the app
* @returns the custom protocol url if it exists, otherwise null
*/
function getCustomProtocolUrl(args: string[]) {
const customProtocolArg = args.find((arg) => arg.startsWith(PROTOCOL_PREFIX));
if (!customProtocolArg) {
return null;
} }
}
return true; return tidalUrl + "/" + customProtocolArg.substring(PROTOCOL_PREFIX.length + 3);
} }
function createWindow(options = { x: 0, y: 0, backgroundColor: "white" }) { function createWindow(options = { x: 0, y: 0, backgroundColor: "white" }) {
@ -94,12 +97,21 @@ function createWindow(options = { x: 0, y: 0, backgroundColor: "white" }) {
}, },
}, },
}); });
enable(mainWindow.webContents); enable(mainWindow.webContents);
registerHttpProtocols(); registerHttpProtocols();
syncMenuBarWithStore(); syncMenuBarWithStore();
// find the custom protocol argument
const customProtocolUrl = getCustomProtocolUrl(process.argv);
if (customProtocolUrl) {
// load the url received from the custom protocol
mainWindow.loadURL(customProtocolUrl);
} else {
// load the Tidal website // load the Tidal website
mainWindow.loadURL(tidalUrl); mainWindow.loadURL(tidalUrl);
}
if (settingsStore.get(settings.disableBackgroundThrottle)) { if (settingsStore.get(settings.disableBackgroundThrottle)) {
// prevent setInterval lag // prevent setInterval lag
@ -114,6 +126,7 @@ function createWindow(options = { x: 0, y: 0, backgroundColor: "white" }) {
} }
return false; return false;
}); });
// Emitted when the window is closed. // Emitted when the window is closed.
mainWindow.on("closed", function () { mainWindow.on("closed", function () {
releaseInhibitorIfActive(mainInhibitorId); releaseInhibitorIfActive(mainInhibitorId);
@ -139,28 +152,34 @@ function createWindow(options = { x: 0, y: 0, backgroundColor: "white" }) {
} }
function registerHttpProtocols() { function registerHttpProtocols() {
protocol.registerHttpProtocol(PROTOCOL_PREFIX, (request) => {
mainWindow.loadURL(`${tidalUrl}/${request.url.substring(PROTOCOL_PREFIX.length + 3)}`);
});
if (!app.isDefaultProtocolClient(PROTOCOL_PREFIX)) { if (!app.isDefaultProtocolClient(PROTOCOL_PREFIX)) {
app.setAsDefaultProtocolClient(PROTOCOL_PREFIX); app.setAsDefaultProtocolClient(PROTOCOL_PREFIX);
} }
} }
function addGlobalShortcuts() {
Object.keys(mediaKeys).forEach((key) => {
globalShortcut.register(`${key}`, () => {
mainWindow.webContents.send("globalEvent", `${(mediaKeys as any)[key]}`);
});
});
}
// This method will be called when Electron has finished // This method will be called when Electron has finished
// initialization and is ready to create browser windows. // initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs. // Some APIs can only be used after this event occurs.
app.on("ready", async () => { app.on("ready", async () => {
if (isMainInstanceOrMultipleInstancesAllowed()) { // check if the app is the main instance and multiple instances are not allowed
if (isMainInstance() && !isMultipleInstancesAllowed()) {
app.on("second-instance", (_, commandLine) => {
const customProtocolUrl = getCustomProtocolUrl(commandLine);
if (customProtocolUrl) {
mainWindow.loadURL(customProtocolUrl);
}
if (mainWindow) {
if (mainWindow.isMinimized()) mainWindow.restore();
mainWindow.focus();
}
});
}
if (isMainInstance() || isMultipleInstancesAllowed()) {
await components.whenReady(); await components.whenReady();
initialize();
// Adblock // Adblock
if (settingsStore.get(settings.adBlock)) { if (settingsStore.get(settings.adBlock)) {
@ -171,15 +190,16 @@ app.on("ready", async () => {
}); });
} }
Logger.log("components ready:", components.status());
createWindow(); createWindow();
addMenu(mainWindow); addMenu(mainWindow);
createSettingsWindow(); createSettingsWindow();
addGlobalShortcuts();
if (settingsStore.get(settings.trayIcon)) { if (settingsStore.get(settings.trayIcon)) {
addTray(mainWindow, { icon }); addTray(mainWindow, { icon });
refreshTray(mainWindow); refreshTray(mainWindow);
} }
settingsStore.get(settings.api) && startExpress(mainWindow); settingsStore.get(settings.api) && startApi(mainWindow);
settingsStore.get(settings.enableDiscord) && initRPC(); settingsStore.get(settings.enableDiscord) && initRPC();
} else { } else {
app.quit(); app.quit();
@ -234,8 +254,8 @@ ipcMain.on(globalEvents.error, (event) => {
console.log(event); console.log(event);
}); });
ipcMain.handle(globalEvents.whip, async (event, url) => { ipcMain.handle(globalEvents.getUniversalLink, async (event, url) => {
return Songwhip.whip(url); return SharingService.getUniversalLink(url);
}); });
Logger.watch(ipcMain); Logger.watch(ipcMain);

View File

@ -1,3 +1,4 @@
import { MediaPlayerInfo } from "./mediaPlayerInfo";
import { MediaStatus } from "./mediaStatus"; import { MediaStatus } from "./mediaStatus";
export interface MediaInfo { export interface MediaInfo {
@ -7,7 +8,12 @@ export interface MediaInfo {
icon: string; icon: string;
status: MediaStatus; status: MediaStatus;
url: string; url: string;
playingFrom: string;
current: string; current: string;
currentInSeconds?: number;
duration: string; duration: string;
durationInSeconds?: number;
image: string; image: string;
favorite: boolean;
player?: MediaPlayerInfo;
} }

View File

@ -0,0 +1,8 @@
import { RepeatState } from "./repeatState";
import { MediaStatus } from "./mediaStatus";
export interface MediaPlayerInfo {
status: MediaStatus;
shuffle: boolean;
repeat: RepeatState;
}

View File

@ -1,12 +0,0 @@
export interface Options {
title: string;
artists: string;
album: string;
status: string;
url: string;
current: string;
duration: string;
"app-name": string;
image: string;
icon: string;
}

View File

@ -0,0 +1,5 @@
export enum RepeatState {
off = "off",
all = "all",
single = "single",
}

View File

@ -20,16 +20,23 @@ const switchesWithSettings = {
classToHide: "discord_options", classToHide: "discord_options",
settingsKey: settings.enableDiscord, 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,
channel: HTMLSelectElement,
customCSS: HTMLInputElement, customCSS: HTMLInputElement,
disableBackgroundThrottle: HTMLInputElement, disableBackgroundThrottle: HTMLInputElement,
disableHardwareMediaKeys: HTMLInputElement, disableHardwareMediaKeys: HTMLInputElement,
enableCustomHotkeys: HTMLInputElement, enableCustomHotkeys: HTMLInputElement,
enableDiscord: HTMLInputElement, enableDiscord: HTMLInputElement,
gpuRasterization: HTMLInputElement, gpuRasterization: HTMLInputElement,
hostname: HTMLInputElement,
menuBar: HTMLInputElement, menuBar: HTMLInputElement,
minimizeOnClose: HTMLInputElement, minimizeOnClose: HTMLInputElement,
mpris: HTMLInputElement, mpris: HTMLInputElement,
@ -39,6 +46,7 @@ let adBlock: HTMLInputElement,
singleInstance: HTMLInputElement, singleInstance: HTMLInputElement,
skipArtists: HTMLInputElement, skipArtists: HTMLInputElement,
skippedArtists: HTMLInputElement, skippedArtists: HTMLInputElement,
staticWindowTitle: HTMLInputElement,
theme: HTMLSelectElement, theme: HTMLSelectElement,
trayIcon: HTMLInputElement, trayIcon: HTMLInputElement,
updateFrequency: HTMLInputElement, updateFrequency: HTMLInputElement,
@ -48,9 +56,14 @@ let adBlock: HTMLInputElement,
listenbrainz_delay: HTMLInputElement, listenbrainz_delay: HTMLInputElement,
enableWaylandSupport: HTMLInputElement, enableWaylandSupport: HTMLInputElement,
discord_details_prefix: HTMLInputElement, discord_details_prefix: HTMLInputElement,
discord_button_text: HTMLInputElement; discord_include_timestamps: HTMLInputElement,
discord_button_text: HTMLInputElement,
discord_show_song: HTMLInputElement,
discord_show_idle: HTMLInputElement,
discord_idle_text: HTMLInputElement,
discord_using_text: HTMLInputElement;
addCustomCss(app, Logger.bind(this)); addCustomCss(app);
function getThemeFiles() { function getThemeFiles() {
const selectElement = document.getElementById("themesList") as HTMLSelectElement; const selectElement = document.getElementById("themesList") as HTMLSelectElement;
@ -111,6 +124,7 @@ function refreshSettings() {
try { 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);
channel.value = settingsStore.get(settings.advanced.tidalUrl);
customCSS.value = settingsStore.get<string, string[]>(settings.customCSS).join("\n"); customCSS.value = settingsStore.get<string, string[]>(settings.customCSS).join("\n");
disableBackgroundThrottle.checked = settingsStore.get(settings.disableBackgroundThrottle); disableBackgroundThrottle.checked = settingsStore.get(settings.disableBackgroundThrottle);
disableHardwareMediaKeys.checked = settingsStore.get(settings.flags.disableHardwareMediaKeys); disableHardwareMediaKeys.checked = settingsStore.get(settings.flags.disableHardwareMediaKeys);
@ -118,6 +132,7 @@ function refreshSettings() {
enableDiscord.checked = settingsStore.get(settings.enableDiscord); enableDiscord.checked = settingsStore.get(settings.enableDiscord);
enableWaylandSupport.checked = settingsStore.get(settings.flags.enableWaylandSupport); enableWaylandSupport.checked = settingsStore.get(settings.flags.enableWaylandSupport);
gpuRasterization.checked = settingsStore.get(settings.flags.gpuRasterization); gpuRasterization.checked = settingsStore.get(settings.flags.gpuRasterization);
hostname.value = settingsStore.get(settings.apiSettings.hostname);
menuBar.checked = settingsStore.get(settings.menuBar); menuBar.checked = settingsStore.get(settings.menuBar);
minimizeOnClose.checked = settingsStore.get(settings.minimizeOnClose); minimizeOnClose.checked = settingsStore.get(settings.minimizeOnClose);
mpris.checked = settingsStore.get(settings.mpris); mpris.checked = settingsStore.get(settings.mpris);
@ -126,8 +141,9 @@ function refreshSettings() {
port.value = settingsStore.get(settings.apiSettings.port); port.value = settingsStore.get(settings.apiSettings.port);
singleInstance.checked = settingsStore.get(settings.singleInstance); singleInstance.checked = settingsStore.get(settings.singleInstance);
skipArtists.checked = settingsStore.get(settings.skipArtists); skipArtists.checked = settingsStore.get(settings.skipArtists);
theme.value = settingsStore.get(settings.theme);
skippedArtists.value = settingsStore.get<string, string[]>(settings.skippedArtists).join("\n"); skippedArtists.value = settingsStore.get<string, string[]>(settings.skippedArtists).join("\n");
staticWindowTitle.checked = settingsStore.get(settings.staticWindowTitle);
theme.value = settingsStore.get(settings.theme);
trayIcon.checked = settingsStore.get(settings.trayIcon); trayIcon.checked = settingsStore.get(settings.trayIcon);
updateFrequency.value = settingsStore.get(settings.updateFrequency); updateFrequency.value = settingsStore.get(settings.updateFrequency);
enableListenBrainz.checked = settingsStore.get(settings.ListenBrainz.enabled); enableListenBrainz.checked = settingsStore.get(settings.ListenBrainz.enabled);
@ -135,7 +151,12 @@ function refreshSettings() {
ListenBrainzToken.value = settingsStore.get(settings.ListenBrainz.token); ListenBrainzToken.value = settingsStore.get(settings.ListenBrainz.token);
listenbrainz_delay.value = settingsStore.get(settings.ListenBrainz.delay); listenbrainz_delay.value = settingsStore.get(settings.ListenBrainz.delay);
discord_details_prefix.value = settingsStore.get(settings.discord.detailsPrefix); 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_button_text.value = settingsStore.get(settings.discord.buttonText);
discord_show_song.checked = settingsStore.get(settings.discord.showSong);
discord_show_idle.checked = settingsStore.get(settings.discord.showIdle);
discord_idle_text.value = settingsStore.get(settings.discord.idleText);
discord_using_text.value = settingsStore.get(settings.discord.usingText);
// set state of all switches with additional settings // set state of all switches with additional settings
Object.values(switchesWithSettings).forEach((settingSwitch) => { Object.values(switchesWithSettings).forEach((settingSwitch) => {
@ -223,6 +244,7 @@ window.addEventListener("DOMContentLoaded", () => {
adBlock = get("adBlock"); adBlock = get("adBlock");
api = get("apiCheckbox"); api = get("apiCheckbox");
channel = get<HTMLSelectElement>("channel");
customCSS = get("customCSS"); customCSS = get("customCSS");
disableBackgroundThrottle = get("disableBackgroundThrottle"); disableBackgroundThrottle = get("disableBackgroundThrottle");
disableHardwareMediaKeys = get("disableHardwareMediaKeys"); disableHardwareMediaKeys = get("disableHardwareMediaKeys");
@ -230,6 +252,7 @@ window.addEventListener("DOMContentLoaded", () => {
enableDiscord = get("enableDiscord"); enableDiscord = get("enableDiscord");
enableWaylandSupport = get("enableWaylandSupport"); enableWaylandSupport = get("enableWaylandSupport");
gpuRasterization = get("gpuRasterization"); gpuRasterization = get("gpuRasterization");
hostname = get("hostname");
menuBar = get("menuBar"); menuBar = get("menuBar");
minimizeOnClose = get("minimizeOnClose"); minimizeOnClose = get("minimizeOnClose");
mpris = get("mprisCheckbox"); mpris = get("mprisCheckbox");
@ -240,18 +263,25 @@ window.addEventListener("DOMContentLoaded", () => {
trayIcon = get("trayIcon"); trayIcon = get("trayIcon");
skipArtists = get("skipArtists"); skipArtists = get("skipArtists");
skippedArtists = get("skippedArtists"); skippedArtists = get("skippedArtists");
staticWindowTitle = get("staticWindowTitle");
singleInstance = get("singleInstance"); singleInstance = get("singleInstance");
updateFrequency = get("updateFrequency"); updateFrequency = get("updateFrequency");
enableListenBrainz = get("enableListenBrainz"); enableListenBrainz = get("enableListenBrainz");
ListenBrainzAPI = get("ListenBrainzAPI"); ListenBrainzAPI = get("ListenBrainzAPI");
ListenBrainzToken = get("ListenBrainzToken"); ListenBrainzToken = get("ListenBrainzToken");
discord_details_prefix = get("discord_details_prefix"); discord_details_prefix = get("discord_details_prefix");
discord_include_timestamps = get("discord_include_timestamps");
listenbrainz_delay = get("listenbrainz_delay"); listenbrainz_delay = get("listenbrainz_delay");
discord_button_text = get("discord_button_text"); discord_button_text = get("discord_button_text");
discord_show_song = get("discord_show_song");
discord_show_idle = get("discord_show_idle");
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);
addSelectListener(channel, settings.advanced.tidalUrl);
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);
@ -259,6 +289,7 @@ window.addEventListener("DOMContentLoaded", () => {
addInputListener(enableDiscord, settings.enableDiscord, switchesWithSettings.discord); addInputListener(enableDiscord, settings.enableDiscord, switchesWithSettings.discord);
addInputListener(enableWaylandSupport, settings.flags.enableWaylandSupport); addInputListener(enableWaylandSupport, settings.flags.enableWaylandSupport);
addInputListener(gpuRasterization, settings.flags.gpuRasterization); addInputListener(gpuRasterization, settings.flags.gpuRasterization);
addInputListener(hostname, settings.apiSettings.hostname);
addInputListener(menuBar, settings.menuBar); addInputListener(menuBar, settings.menuBar);
addInputListener(minimizeOnClose, settings.minimizeOnClose); addInputListener(minimizeOnClose, settings.minimizeOnClose);
addInputListener(mpris, settings.mpris); addInputListener(mpris, settings.mpris);
@ -267,6 +298,7 @@ window.addEventListener("DOMContentLoaded", () => {
addInputListener(port, settings.apiSettings.port); addInputListener(port, settings.apiSettings.port);
addInputListener(skipArtists, settings.skipArtists); addInputListener(skipArtists, settings.skipArtists);
addTextAreaListener(skippedArtists, settings.skippedArtists); addTextAreaListener(skippedArtists, settings.skippedArtists);
addInputListener(staticWindowTitle, settings.staticWindowTitle);
addInputListener(singleInstance, settings.singleInstance); addInputListener(singleInstance, settings.singleInstance);
addSelectListener(theme, settings.theme); addSelectListener(theme, settings.theme);
addInputListener(trayIcon, settings.trayIcon); addInputListener(trayIcon, settings.trayIcon);
@ -280,5 +312,14 @@ window.addEventListener("DOMContentLoaded", () => {
addInputListener(ListenBrainzToken, settings.ListenBrainz.token); addInputListener(ListenBrainzToken, settings.ListenBrainz.token);
addInputListener(listenbrainz_delay, settings.ListenBrainz.delay); addInputListener(listenbrainz_delay, settings.ListenBrainz.delay);
addInputListener(discord_details_prefix, settings.discord.detailsPrefix); addInputListener(discord_details_prefix, settings.discord.detailsPrefix);
addInputListener(discord_include_timestamps, settings.discord.includeTimestamps);
addInputListener(discord_button_text, settings.discord.buttonText); addInputListener(discord_button_text, settings.discord.buttonText);
addInputListener(
discord_show_song,
settings.discord.showSong,
switchesWithSettings.discord_show_song
);
addInputListener(discord_show_idle, settings.discord.showIdle);
addInputListener(discord_idle_text, settings.discord.idleText);
addInputListener(discord_using_text, settings.discord.usingText);
}); });

View File

@ -49,7 +49,7 @@
<div class="group__option"> <div class="group__option">
<div class="group__description"> <div class="group__description">
<h4>Notifications</h4> <h4>Notifications</h4>
<p>Show a notification when a new song starts.</p> <p>Show a notification when new media starts.</p>
</div> </div>
<label class="switch"> <label class="switch">
<input id="notifications" type="checkbox" /> <input id="notifications" type="checkbox" />
@ -106,6 +106,16 @@
<span class="switch__slider"></span> <span class="switch__slider"></span>
</label> </label>
</div> </div>
<div class="group__option">
<div class="group__description">
<h4>Static Window Title</h4>
<p>Makes the window title "TIDAL Hi-Fi" instead of changing to the currently playing song.</p>
</div>
<label class="switch">
<input id="staticWindowTitle" type="checkbox" />
<span class="switch__slider"></span>
</label>
</div>
<div class="group__option"> <div class="group__option">
<div class="group__description"> <div class="group__description">
<h4>Minimize on Close</h4> <h4>Minimize on Close</h4>
@ -147,7 +157,7 @@
<p class="group__title">API</p> <p class="group__title">API</p>
<div class="group__description"> <div class="group__description">
<p> <p>
TIDAL Hi-Fi has a built-in web API to allow users to get current song information. TIDAL Hi-Fi has a built-in web API to allow users to get current media information.
You can optionally enable playback control as well. You can optionally enable playback control as well.
</p> </p>
</div> </div>
@ -167,6 +177,16 @@
<input id="port" type="number" class="text-input" name="port" /> <input id="port" type="number" class="text-input" name="port" />
</div> </div>
</div> </div>
<div class="group__option">
<div class="group__description">
<h4>API hostname</h4>
<p>By default (127.0.0.1) only local apps can interface with the API. <br />
Change to 0.0.0.0 to allow <strong>anyone</strong> to interact with it. <br />
Other options are available
</p>
<input id="hostname" type="text" class="text-input" name="hostname" />
</div>
</div>
<div class="group__option"> <div class="group__option">
<div class="group__description"> <div class="group__description">
<h4>Playback control</h4> <h4>Playback control</h4>
@ -216,6 +236,58 @@
</label> </label>
</div> </div>
<div id="discord_options"> <div id="discord_options">
<div class="group__option" class="hidden">
<div class="group__description">
<h4>Show Idle Text</h4>
<p>Should the idle text be shown when idle?</p>
</div>
<label class="switch">
<input id="discord_show_idle" type="checkbox" />
<span class="switch__slider"></span>
</label>
</div>
<div class="group__option" class="hidden">
<div class="group__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 media</h4>
<p>Show the current media 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__option" class="hidden">
<div class="group__description"> <div class="group__description">
<h4>Details prefix</h4> <h4>Details prefix</h4>
@ -227,11 +299,13 @@
<div class="group__option"> <div class="group__option">
<div class="group__description"> <div class="group__description">
<h4>Button text</h4> <h4>Button text</h4>
<p>Text to display on the button below the song information.</p> <p>Text to display on the button below the media information.</p>
<input id="discord_button_text" type="text" class="text-input" name="discord_button_text" /> <input id="discord_button_text" type="text" class="text-input" name="discord_button_text" />
</div> </div>
</div> </div>
</div> </div>
</div>
</div> </div>
<div class="group"> <div class="group">
<p class="group__title">ListenBrainz</p> <p class="group__title">ListenBrainz</p>
@ -263,7 +337,7 @@
</div> </div>
<div class="group__description"> <div class="group__description">
<h4>ScrobbleDelay</h4> <h4>ScrobbleDelay</h4>
<p>The delay (in ms) to send a song to ListenBrainz. Prevents spamming the API when you fast forward <p>The delay (in ms) to send a listen to ListenBrainz. Prevents spamming the API when you fast forward
immediately</p> immediately</p>
<input id="listenbrainz_delay" type="number" class="text-input" name="listenbrainz_delay" /> <input id="listenbrainz_delay" type="number" class="text-input" name="listenbrainz_delay" />
</div> </div>
@ -285,6 +359,20 @@
<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 class="group__option">
<div class="group__description">
<h4>Tidal channel / URL</h4>
<p>
Which URL Tidal Hi-Fi should use.
<strong>note! Beta might break at any time</strong>
</p>
<select class="select-input" id="channel" name="channel">
<option value="https://listen.tidal.com">Stable (listen.tidal.com)</option>
<option value="https://listen.stage.tidal.com">Staging (listen.stage.tidal.com)</option>
</select>
</div>
</div>
</div> </div>
<div class="group"> <div class="group">
<p class="group__title">Flags</p> <p class="group__title">Flags</p>
@ -389,14 +477,16 @@
<img alt="tidal icon" class="about-section__icon" src="./icon.png" /> <img alt="tidal icon" class="about-section__icon" src="./icon.png" />
<h4>TIDAL Hi-Fi</h4> <h4>TIDAL Hi-Fi</h4>
<div class="about-section__version"> <div class="about-section__version">
<a href="">5.7.0</a> <a target="_blank" rel="noopener"
href="https://github.com/Mastermindzh/tidal-hifi/releases/tag/5.18.2">5.18.2</a>
</div> </div>
<div class="about-section__links"> <div class="about-section__links">
<a href="https://github.com/mastermindzh/tidal-hifi/" class="about-section__button">Github <i <a target="_blank" rel="noopener" href="https://github.com/mastermindzh/tidal-hifi/"
class="fa fa-external-link"></i></a> class="about-section__button">Github
<a href="https://github.com/Mastermindzh/tidal-hifi/issues" class="about-section__button">Report an issue <i <i class="fa fa-external-link"></i></a>
class="fa fa-external-link"></i></a> <a target="_blank" rel="noopener" href="https://github.com/Mastermindzh/tidal-hifi/issues"
<a href="https://github.com/Mastermindzh/tidal-hifi/graphs/contributors" class="about-section__button">Report an issue <i class="fa fa-external-link"></i></a>
<a target="_blank" rel="noopener" href="https://github.com/Mastermindzh/tidal-hifi/graphs/contributors"
class="about-section__button">Contributors <i class="fa fa-external-link"></i></a> class="about-section__button">Contributors <i class="fa fa-external-link"></i></a>
</div> </div>
</section> </section>

View File

@ -323,16 +323,19 @@ html {
max-width: 500px; max-width: 500px;
margin: -15px auto 0; margin: -15px auto 0;
} }
&__table { &__table {
width: 120px; width: 120px;
margin: 0 auto 0; margin: 0 auto;
td { td {
text-align: left; text-align: left;
} }
} }
&__version { &__version {
margin: -10px 0px 30px 0px; margin: -10px 0 30px;
a { a {
background-color: $tidal-grey-darker; background-color: $tidal-grey-darker;
border: none; border: none;
@ -349,6 +352,7 @@ html {
&__links { &__links {
width: 300px; width: 300px;
margin: 0 auto; margin: 0 auto;
a { a {
border-radius: 10px; border-radius: 10px;
border: none; border: none;
@ -411,17 +415,17 @@ html {
} }
// file upload // file upload
.file-drop-area { .file-drop-area {
position: relative; position: relative;
display: flex; display: flex;
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;
} }
@ -459,6 +463,7 @@ html {
width: 100%; width: 100%;
cursor: pointer; cursor: pointer;
opacity: 0; opacity: 0;
&:focus { &:focus {
outline: none; outline: none;
} }

View File

@ -10,23 +10,31 @@ import {
} from "./features/listenbrainz/listenbrainz"; } from "./features/listenbrainz/listenbrainz";
import { StoreData } from "./features/listenbrainz/models/storeData"; import { StoreData } from "./features/listenbrainz/models/storeData";
import { Logger } from "./features/logger"; import { Logger } from "./features/logger";
import { Songwhip } from "./features/songwhip/songwhip"; import { SharingService } from "./features/sharingService/sharingService";
import { addCustomCss } from "./features/theming/theming"; import { addCustomCss } from "./features/theming/theming";
import { convertDurationToSeconds } from "./features/time/parse";
import { MediaInfo } from "./models/mediaInfo";
import { MediaStatus } from "./models/mediaStatus"; import { MediaStatus } from "./models/mediaStatus";
import { Options } from "./models/options"; import { RepeatState } from "./models/repeatState";
import { downloadFile } from "./scripts/download"; import { downloadFile } from "./scripts/download";
import { addHotkey } from "./scripts/hotkeys"; import { addHotkey } from "./scripts/hotkeys";
import { ObjectToDotNotation } from "./scripts/objectUtilities";
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 Hi-Fi";
let currentSong = ""; let currentSong = "";
let player: Player; let player: Player;
let currentPlayStatus = MediaStatus.paused; let currentPlayStatus = MediaStatus.paused;
let currentListenBrainzDelayId: ReturnType<typeof setTimeout>; let currentListenBrainzDelayId: ReturnType<typeof setTimeout>;
let scrobbleWaitingForDelay = false; let scrobbleWaitingForDelay = false;
let currentlyPlaying = MediaStatus.paused;
let currentRepeatState: RepeatState = RepeatState.off;
let currentShuffleState = false;
let currentMediaInfo: MediaInfo;
let currentNotification: Electron.Notification;
const elements = { const elements = {
play: '*[data-test="play"]', play: '*[data-test="play"]',
pause: '*[data-test="pause"]', pause: '*[data-test="pause"]',
@ -40,20 +48,24 @@ const elements = {
search: '[class^="searchField"]', search: '[class^="searchField"]',
shuffle: '*[data-test="shuffle"]', shuffle: '*[data-test="shuffle"]',
repeat: '*[data-test="repeat"]', repeat: '*[data-test="repeat"]',
account: '*[class^="profileOptions"]', account: '*[data-test^="profile-image-button"]',
settings: '*[data-test^="open-settings"]', settings: '*[data-test^="sidebar-menu-button"]',
openSettings: '*[data-test^="open-settings"]',
media: '*[data-test="current-media-imagery"]', media: '*[data-test="current-media-imagery"]',
image: "img", image: "img",
current: '*[data-test="current-time"]', current: '*[data-test="current-time"]',
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']", mediaItem: "[data-type='mediaItem']",
album_header_title: '.header-details [data-test="title"]', album_header_title: '*[class^="_playingFrom"] span:nth-child(2)',
playing_from: '*[class^="_playingFrom"] span:nth-child(2)',
queue_album: "*[class^=playQueueItemsContainer] *[class^=groupTitle] span:nth-child(2)",
currentlyPlaying: "[class^='isPlayingIcon'], [data-test-is-playing='true']", currentlyPlaying: "[class^='isPlayingIcon'], [data-test-is-playing='true']",
album_name_cell: '[class^="album"]', album_name_cell: '[class^="album"]',
tracklist_row: '[data-test="tracklist-row"]', tracklist_row: '[data-test="tracklist-row"]',
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
@ -63,7 +75,7 @@ const elements = {
}, },
/** /**
* Get the icon of the current song * Get the icon of the current media
*/ */
getSongIcon: function () { getSongIcon: function () {
const figure = this.get("media"); const figure = this.get("media");
@ -79,7 +91,7 @@ const elements = {
}, },
/** /**
* returns an array of all artists in the current song * returns an array of all artists in the current media
* @returns {Array} artists * @returns {Array} artists
*/ */
getArtistsArray: function () { getArtistsArray: function () {
@ -124,6 +136,12 @@ const elements = {
} }
} }
// see whether we're on the queue page and get it from there
const queueAlbumName = elements.getText("queue_album");
if (queueAlbumName) {
return queueAlbumName;
}
return ""; return "";
}, },
@ -131,6 +149,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
@ -174,7 +196,7 @@ function getUpdateFrequency() {
} }
/** /**
* Play or pause the current song * Play or pause the current media
*/ */
function playPause() { function playPause() {
const play = elements.get("play"); const play = elements.get("play");
@ -199,15 +221,19 @@ ListenBrainzStore.clear();
function addHotKeys() { function addHotKeys() {
if (settingsStore.get(settings.enableCustomHotkeys)) { if (settingsStore.get(settings.enableCustomHotkeys)) {
addHotkey("Control+p", function () { addHotkey("Control+p", function () {
elements.click("account");
setTimeout(() => {
elements.click("settings"); elements.click("settings");
setTimeout(() => {
elements.click("openSettings");
}, 100); }, 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");
}); });
@ -229,11 +255,10 @@ function addHotKeys() {
elements.click("repeat"); elements.click("repeat");
}); });
addHotkey("control+w", async function () { addHotkey("control+w", async function () {
const result = await ipcRenderer.invoke(globalEvents.whip, getTrackURL()); const url = SharingService.getUniversalLink(getTrackURL());
const url = Songwhip.getWhipUrl(result);
clipboard.writeText(url); clipboard.writeText(url);
new Notification({ new Notification({
title: `Successfully whipped: `, title: `Universal link generated: `,
body: `URL copied to clipboard: ${url}`, body: `URL copied to clipboard: ${url}`,
}).show(); }).show();
}); });
@ -292,6 +317,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:
@ -300,11 +327,14 @@ 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: case globalEvents.toggleShuffle:
elements.click("pause"); elements.click("shuffle");
break;
case globalEvents.toggleRepeat:
elements.click("repeat");
break; break;
default: default:
break; break;
@ -329,6 +359,23 @@ function getCurrentlyPlayingStatus() {
return status; return status;
} }
function getCurrentShuffleState() {
const shuffle = elements.get("shuffle");
return shuffle?.getAttribute("aria-checked") === "true";
}
function getCurrentRepeatState() {
const repeat = elements.get("repeat");
switch (repeat?.getAttribute("data-type")) {
case "button__repeatAll":
return RepeatState.all;
case "button__repeatSingle":
return RepeatState.single;
default:
return RepeatState.off;
}
}
/** /**
* Convert the duration from MM:SS to seconds * Convert the duration from MM:SS to seconds
* @param {*} duration * @param {*} duration
@ -341,187 +388,45 @@ function convertDuration(duration: string) {
/** /**
* Update Tidal-hifi's media info * Update Tidal-hifi's media info
* *
* @param {*} options * @param {*} mediaInfo
*/ */
function updateMediaInfo(options: Options, notify: boolean) { function updateMediaInfo(mediaInfo: MediaInfo, notify: boolean) {
if (options) { if (mediaInfo) {
ipcRenderer.send(globalEvents.updateInfo, options); currentMediaInfo = mediaInfo;
if (settingsStore.get(settings.notifications) && notify) { ipcRenderer.send(globalEvents.updateInfo, mediaInfo);
new Notification({ title: options.title, body: options.artists, icon: options.icon }).show(); updateMpris(mediaInfo);
} updateListenBrainz(mediaInfo);
updateMpris(options); if (notify) {
updateListenBrainz(options); sendNotification(mediaInfo);
}
}
function updateMpris(options: Options) {
if (player) {
player.metadata = {
...player.metadata,
...{
"xesam:title": options.title,
"xesam:artist": [options.artists],
"xesam:album": options.album,
"mpris:artUrl": options.image,
"mpris:length": convertDuration(options.duration) * 1000 * 1000,
"mpris:trackid": "/org/mpris/MediaPlayer2/track/" + getTrackID(),
},
};
player.playbackStatus = options.status === MediaStatus.paused ? "Paused" : "Playing";
}
}
/**
* 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
);
}
} }
} }
} }
/** /**
* Checks if Tidal is playing a video or song by grabbing the "a" element from the title. * send a desktop notification if enabled in settings
* If it's a song it returns the track URL, if not it will return undefined * @param mediaInfo
* @param notify Whether to notify
*/ */
function getTrackURL() { async function sendNotification(mediaInfo: MediaInfo) {
const id = getTrackID(); if (settingsStore.get(settings.notifications)) {
return `https://tidal.com/browse/track/${id}`; if (currentNotification) {
} currentNotification.close();
function getTrackID() {
const URLelement = elements.get("title").querySelector("a");
if (URLelement !== null) {
const id = URLelement.href.replace(/\D/g, "");
return id;
} }
currentNotification = new Notification({
return window.location; title: mediaInfo.title,
} body: mediaInfo.artists,
icon: mediaInfo.icon,
function updateMediaSession(options: Options) {
if ("mediaSession" in navigator) {
navigator.mediaSession.metadata = new MediaMetadata({
title: options.title,
artist: options.artists,
album: options.album,
artwork: [
{
src: options.icon,
sizes: "640x640",
type: "image/png",
},
],
}); });
currentNotification.show();
} }
} }
/** function addMPRIS() {
* Watch for song changes and update title + notify if (process.platform === "linux" && settingsStore.get(settings.mpris)) {
*/
setInterval(function () {
const title = elements.getText("title");
const artistsArray = elements.getArtistsArray();
const artistsString = elements.getArtistsString(artistsArray);
skipArtistsIfFoundInSkippedArtistsList(artistsArray);
const album = elements.getAlbumName();
const current = elements.getText("current");
const duration = elements.getText("duration");
const songDashArtistTitle = `${title} - ${artistsString}`;
const currentStatus = getCurrentlyPlayingStatus();
const options = {
title,
artists: artistsString,
album: album,
status: currentStatus,
url: getTrackURL(),
current,
duration,
"app-name": appName,
image: "",
icon: "",
};
const titleOrArtistsChanged = currentSong !== songDashArtistTitle;
// update title, url and play info with new info
setTitle(songDashArtistTitle);
getTrackURL();
currentSong = songDashArtistTitle;
currentPlayStatus = currentStatus;
const image = elements.getSongIcon();
new Promise<void>((resolve) => {
if (image.startsWith("http")) {
options.image = image;
downloadFile(image, notificationPath).then(
() => {
options.icon = notificationPath;
resolve();
},
() => {
// if the image can't be downloaded then continue without it
resolve();
}
);
} else {
// if the image can't be found on the page continue without it
resolve();
}
}).then(() => {
updateMediaInfo(options, titleOrArtistsChanged);
if (titleOrArtistsChanged) {
updateMediaSession(options);
}
});
/**
* automatically skip a song if the artists are found in the list of artists to skip
* @param {*} artists array of artists
*/
function skipArtistsIfFoundInSkippedArtistsList(artists: string[]) {
if (settingsStore.get(settings.skipArtists)) {
const skippedArtists = settingsStore.get<string, string[]>(settings.skippedArtists);
if (skippedArtists.length > 0) {
const artistsToSkip = skippedArtists.map((artist) => artist);
const artistNames = Object.values(artists).map((artist) => artist);
const foundArtist = artistNames.some((artist) => artistsToSkip.includes(artist));
if (foundArtist) {
elements.click("next");
}
}
}
}
}, getUpdateFrequency());
if (process.platform === "linux" && settingsStore.get(settings.mpris)) {
try { try {
player = Player({ player = Player({
name: "TIDAL Hi-Fi", name: "tidal-hifi",
identity: "TIDAL Hi-Fi", identity: "tidal-hifi",
supportedUriSchemes: ["file"], supportedUriSchemes: ["file"],
supportedMimeTypes: [ supportedMimeTypes: [
"audio/mpeg", "audio/mpeg",
@ -552,7 +457,6 @@ if (process.platform === "linux" && settingsStore.get(settings.mpris)) {
case events.playpause: case events.playpause:
playPause(); playPause();
break; break;
default: default:
elements.click(eventValue); elements.click(eventValue);
} }
@ -562,16 +466,194 @@ if (process.platform === "linux" && settingsStore.get(settings.mpris)) {
player.getPosition = function () { player.getPosition = function () {
return convertDuration(elements.getText("current")) * 1000 * 1000; return convertDuration(elements.getText("current")) * 1000 * 1000;
}; };
player.on("quit", function () { player.on("quit", function () {
app.quit(); app.quit();
}); });
} catch (exception) { } catch (exception) {
console.log("player api not working"); Logger.log("MPRIS player api not working", exception);
}
} }
} }
addCustomCss(app, Logger.bind(this)); function updateMpris(mediaInfo: MediaInfo) {
if (player) {
player.metadata = {
...player.metadata,
...{
"xesam:title": mediaInfo.title,
"xesam:artist": [mediaInfo.artists],
"xesam:album": mediaInfo.album,
"xesam:url": mediaInfo.url,
"mpris:artUrl": mediaInfo.image,
"mpris:length": convertDuration(mediaInfo.duration) * 1000 * 1000,
"mpris:trackid": "/org/mpris/MediaPlayer2/track/" + getTrackID(),
},
...ObjectToDotNotation(mediaInfo, "custom:"),
};
player.playbackStatus = mediaInfo.status === MediaStatus.paused ? "Paused" : "Playing";
}
}
/**
* Update the listenbrainz service with new data based on a few conditions
*/
function updateListenBrainz(mediaInfo: MediaInfo) {
if (settingsStore.get(settings.ListenBrainz.enabled)) {
const oldData = ListenBrainzStore.get(ListenBrainzConstants.oldData) as StoreData;
if (
(!oldData && mediaInfo.status === MediaStatus.playing) ||
(oldData && oldData.title !== mediaInfo.title)
) {
if (!scrobbleWaitingForDelay) {
scrobbleWaitingForDelay = true;
clearTimeout(currentListenBrainzDelayId);
currentListenBrainzDelayId = setTimeout(
() => {
ListenBrainz.scrobble(
mediaInfo.title,
mediaInfo.artists,
mediaInfo.status,
convertDuration(mediaInfo.duration)
);
scrobbleWaitingForDelay = false;
},
settingsStore.get(settings.ListenBrainz.delay) ?? 0
);
}
}
}
}
/**
* Checks if Tidal is playing a video or song by grabbing the "a" element from the title.
* If it's a song it returns the track URL, if not it will return undefined
*/
function getTrackURL() {
const id = getTrackID();
return `https://tidal.com/browse/track/${id}`;
}
function getTrackID() {
const URLelement = elements.get("title").querySelector("a");
if (URLelement !== null) {
const id = URLelement.href.replace(/\D/g, "");
return id;
}
return window.location;
}
/**
* Watch for song changes and update title + notify
*/
setInterval(function () {
const title = elements.getText("title");
const artistsArray = elements.getArtistsArray();
const artistsString = elements.getArtistsString(artistsArray);
const songDashArtistTitle = `${title} - ${artistsString}`;
const staticTitle = "TIDAL Hi-Fi";
const titleOrArtistsChanged = currentSong !== songDashArtistTitle;
const current = elements.getText("current");
const currentStatus = getCurrentlyPlayingStatus();
const shuffleState = getCurrentShuffleState();
const repeatState = getCurrentRepeatState();
const playStateChanged = currentStatus != currentlyPlaying;
const shuffleStateChanged = shuffleState != currentShuffleState;
const repeatStateChanged = repeatState != currentRepeatState;
const hasStateChanged = playStateChanged || shuffleStateChanged || repeatStateChanged;
// update info if song changed or was just paused/resumed
if (titleOrArtistsChanged || hasStateChanged) {
if (playStateChanged) currentlyPlaying = currentStatus;
if (shuffleStateChanged) currentShuffleState = shuffleState;
if (repeatStateChanged) currentRepeatState = repeatState;
skipArtistsIfFoundInSkippedArtistsList(artistsArray);
const album = elements.getAlbumName();
const duration = elements.getText("duration");
const options: MediaInfo = {
title,
artists: artistsString,
album: album,
playingFrom: elements.getText("playing_from"),
status: currentStatus,
url: getTrackURL(),
current,
currentInSeconds: convertDurationToSeconds(current),
duration,
durationInSeconds: convertDurationToSeconds(duration),
image: "",
icon: "",
favorite: elements.isFavorite(),
player: {
status: currentStatus,
shuffle: shuffleState,
repeat: repeatState,
},
};
// update title, url and play info with new info
settingsStore.get(settings.staticWindowTitle)
? setTitle(staticTitle)
: setTitle(songDashArtistTitle);
getTrackURL();
currentSong = songDashArtistTitle;
currentPlayStatus = currentStatus;
const image = elements.getSongIcon();
new Promise<void>((resolve) => {
if (image.startsWith("http")) {
options.image = image;
downloadFile(image, notificationPath).then(
() => {
options.icon = notificationPath;
resolve();
},
() => {
// if the image can't be downloaded then continue without it
resolve();
}
);
} else {
// if the image can't be found on the page continue without it
resolve();
}
}).then(() => {
updateMediaInfo(options, titleOrArtistsChanged);
});
} else {
// just update the time
updateMediaInfo(
{ ...currentMediaInfo, ...{ current, currentInSeconds: convertDurationToSeconds(current) } },
false
);
}
/**
* automatically skip a song if the artists are found in the list of artists to skip
* @param {*} artists array of artists
*/
function skipArtistsIfFoundInSkippedArtistsList(artists: string[]) {
if (settingsStore.get(settings.skipArtists)) {
const skippedArtists = settingsStore.get<string, string[]>(settings.skippedArtists);
if (skippedArtists.length > 0) {
const artistsToSkip = skippedArtists.map((artist) => artist);
const artistNames = Object.values(artists).map((artist) => artist);
const foundArtist = artistNames.some((artist) => artistsToSkip.includes(artist));
if (foundArtist) {
elements.click("next");
}
}
}
}
}, getUpdateFrequency());
addMPRIS();
addCustomCss(app);
addHotKeys(); addHotKeys();
addIPCEventListeners(); addIPCEventListeners();
addFullScreenListeners(); addFullScreenListeners();

View File

@ -1,85 +1,141 @@
import { Client } from "discord-rpc"; import { Client, SetActivity } from "@xhayper/discord-rpc";
import { app, ipcMain } from "electron"; import { app, ipcMain } from "electron";
import { globalEvents } from "../constants/globalEvents"; import { globalEvents } from "../constants/globalEvents";
import { settings } from "../constants/settings"; import { settings } from "../constants/settings";
import { Logger } from "../features/logger"; import { Logger } from "../features/logger";
import { convertDurationToSeconds } from "../features/time/parse";
import { MediaStatus } from "../models/mediaStatus"; import { MediaStatus } from "../models/mediaStatus";
import { mediaInfo } from "./mediaInfo"; import { mediaInfo } from "./mediaInfo";
import { settingsStore } from "./settings"; import { settingsStore } from "./settings";
const clientId = "833617820704440341"; const clientId = "833617820704440341";
function timeToSeconds(timeArray: string[]) {
const minutes = parseInt(timeArray[0]) * 1;
const seconds = minutes * 60 + parseInt(timeArray[1]) * 1;
return seconds;
}
export let rpc: Client; export let rpc: Client;
const ACTIVITY_LISTENING = 2;
const MAX_RETRIES = 5;
const RETRY_DELAY = 10000;
const observer = () => { const observer = () => {
if (mediaInfo.status === MediaStatus.paused && rpc) { if (rpc) {
rpc.setActivity(idleStatus); updateActivity();
} else if (rpc) { }
const currentSeconds = timeToSeconds(mediaInfo.current.split(":")); };
const durationSeconds = timeToSeconds(mediaInfo.duration.split(":"));
const date = new Date(); const defaultPresence = {
const now = (date.getTime() / 1000) | 0; largeImageKey: "tidal-hifi-icon",
const remaining = date.setSeconds(date.getSeconds() + (durationSeconds - currentSeconds)); largeImageText: `TIDAL Hi-Fi ${app.getVersion()}`,
instance: false,
type: ACTIVITY_LISTENING
};
const updateActivity = () => {
const showIdle = settingsStore.get<string, boolean>(settings.discord.showIdle) ?? true;
if (mediaInfo.status === MediaStatus.paused && !showIdle) {
rpc.user?.clearActivity();
} else {
rpc.user?.setActivity(getActivity());
}
};
const getActivity = (): SetActivity => {
const presence: SetActivity = { ...defaultPresence };
if (mediaInfo.status === MediaStatus.paused) {
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 = const detailsPrefix =
settingsStore.get<string, string>(settings.discord.detailsPrefix) ?? "Listening to "; settingsStore.get<string, string>(settings.discord.detailsPrefix) ?? "Listening to ";
const buttonText = const buttonText =
settingsStore.get<string, string>(settings.discord.buttonText) ?? "Play on TIDAL"; settingsStore.get<string, string>(settings.discord.buttonText) ?? "Play on TIDAL";
return { includeTimestamps, detailsPrefix, buttonText };
}
/**
* Pad a string using spaces to at least 2 characters
* @param input string to pad with 2 characters
* @returns
*/
function pad(input: string): string {
return input.padEnd(2, " ");
}
function setPresenceFromMediaInfo(detailsPrefix: string, buttonText: string) {
// discord requires a minimum of 2 characters
const title = pad(mediaInfo.title);
const album = pad(mediaInfo.album);
const artists = pad(mediaInfo.artists);
if (mediaInfo.url) { if (mediaInfo.url) {
rpc.setActivity({ presence.details = `${detailsPrefix}${title}`;
...idleStatus, presence.state = artists ? artists : "unknown artist(s)";
...{ presence.largeImageKey = mediaInfo.image;
details: `${detailsPrefix}${mediaInfo.title}`, if (album) {
state: mediaInfo.artists ? mediaInfo.artists : "unknown artist(s)", presence.largeImageText = album;
startTimestamp: now, }
endTimestamp: remaining,
largeImageKey: mediaInfo.image, presence.buttons = [{ label: buttonText, url: mediaInfo.url }];
largeImageText: mediaInfo.album ? mediaInfo.album : `${idleStatus.largeImageText}`,
buttons: [{ label: buttonText, url: mediaInfo.url }],
},
});
} else { } else {
rpc.setActivity({ presence.details = `Watching ${title}`;
...idleStatus, presence.state = artists;
...{ }
details: `Watching ${mediaInfo.title}`, }
state: mediaInfo.artists,
startTimestamp: now, function includeTimeStamps(includeTimestamps: boolean) {
endTimestamp: remaining, if (includeTimestamps) {
}, const currentSeconds = convertDurationToSeconds(mediaInfo.current);
}); const durationSeconds = convertDurationToSeconds(mediaInfo.duration);
const now = Math.trunc((Date.now() + 500) / 1000);
presence.startTimestamp = now - currentSeconds;
presence.endTimestamp = presence.startTimestamp + durationSeconds;
} }
} }
}; };
const idleStatus = { /**
details: `Browsing Tidal`, * Try to login to RPC and retry if it errors
largeImageKey: "tidal-hifi-icon", * @param retryCount Max retry count
largeImageText: `TIDAL Hi-Fi ${app.getVersion()}`, */
instance: false, const connectWithRetry = async (retryCount = 0) => {
try {
await rpc.login();
Logger.log('Connected to Discord');
rpc.on("ready", updateActivity);
Object.values(globalEvents).forEach(event => ipcMain.on(event, observer));
} catch (error) {
if (retryCount < MAX_RETRIES) {
Logger.log(`Failed to connect to Discord, retrying in ${RETRY_DELAY/1000} seconds... (Attempt ${retryCount + 1}/${MAX_RETRIES})`);
setTimeout(() => connectWithRetry(retryCount + 1), RETRY_DELAY);
} else {
Logger.log('Failed to connect to Discord after maximum retry attempts');
}
}
}; };
/** /**
* Set up the discord rpc and listen on globalEvents.updateInfo * Set up the discord rpc and listen on globalEvents.updateInfo
*/ */
export const initRPC = () => { export const initRPC = () => {
rpc = new Client({ transport: "ipc" }); rpc = new Client({ transport: {type: "ipc"}, clientId });
rpc.login({ clientId }).then( connectWithRetry();
() => {
rpc.on("ready", () => {
rpc.setActivity(idleStatus);
});
ipcMain.on(globalEvents.updateInfo, observer);
},
() => {
Logger.log("Can't connect to Discord, is it running?");
}
);
}; };
/** /**
@ -87,7 +143,7 @@ export const initRPC = () => {
*/ */
export const unRPC = () => { export const unRPC = () => {
if (rpc) { if (rpc) {
rpc.clearActivity(); rpc.user?.clearActivity();
rpc.destroy(); rpc.destroy();
rpc = null; rpc = null;
ipcMain.removeListener(globalEvents.updateInfo, observer); ipcMain.removeListener(globalEvents.updateInfo, observer);

View File

@ -1,66 +0,0 @@
import { BrowserWindow, dialog } from "electron";
import express, { Response } from "express";
import fs from "fs";
import { settings } from "../constants/settings";
import { MediaStatus } from "../models/mediaStatus";
import { globalEvents } from "./../constants/globalEvents";
import { mediaInfo } from "./mediaInfo";
import { settingsStore } from "./settings";
/**
* Function to enable TIDAL Hi-Fi's express api
*/
// expressModule.run = function (mainWindow)
export const startExpress = (mainWindow: BrowserWindow) => {
/**
* Shorthand to handle a fire and forget global event
* @param {*} res
* @param {*} action
*/
function handleGlobalEvent(res: Response, action: string) {
mainWindow.webContents.send("globalEvent", action);
res.sendStatus(200);
}
const expressApp = express();
expressApp.get("/", (req, res) => res.send("Hello World!"));
expressApp.get("/current", (req, res) => res.json({ ...mediaInfo, artist: mediaInfo.artists }));
expressApp.get("/image", (req, res) => {
const stream = fs.createReadStream(mediaInfo.icon);
stream.on("open", function () {
res.set("Content-Type", "image/png");
stream.pipe(res);
});
stream.on("error", function () {
res.set("Content-Type", "text/plain");
res.status(404).end("Not found");
});
});
if (settingsStore.get(settings.playBackControl)) {
expressApp.get("/play", (req, res) => handleGlobalEvent(res, globalEvents.play));
expressApp.get("/pause", (req, res) => handleGlobalEvent(res, globalEvents.pause));
expressApp.get("/next", (req, res) => handleGlobalEvent(res, globalEvents.next));
expressApp.get("/previous", (req, res) => handleGlobalEvent(res, globalEvents.previous));
expressApp.get("/playpause", (req, res) => {
if (mediaInfo.status === MediaStatus.playing) {
handleGlobalEvent(res, globalEvents.pause);
} else {
handleGlobalEvent(res, globalEvents.play);
}
});
}
const port = settingsStore.get<string, number>(settings.apiSettings.port);
const expressInstance = expressApp.listen(port, "127.0.0.1");
expressInstance.on("error", function (e: { code: string }) {
let message = e.code;
if (e.code === "EADDRINUSE") {
message = `Port ${port} in use.`;
}
dialog.showErrorBox("Api failed to start.", message);
});
};

View File

@ -1,35 +1,45 @@
import { MediaInfo } from "../models/mediaInfo"; import { MediaInfo } from "../models/mediaInfo";
import { MediaStatus } from "../models/mediaStatus"; import { MediaStatus } from "../models/mediaStatus";
import { RepeatState } from "../models/repeatState";
export const mediaInfo = { const defaultInfo: MediaInfo = {
title: "", title: "",
artists: "", artists: "",
album: "", album: "",
icon: "", icon: "",
status: MediaStatus.paused as string, playingFrom: "",
status: MediaStatus.paused,
url: "", url: "",
current: "", current: "",
currentInSeconds: 0,
duration: "", duration: "",
durationInSeconds: 0,
image: "tidal-hifi-icon", image: "tidal-hifi-icon",
favorite: false,
player: {
status: MediaStatus.paused,
shuffle: false,
repeat: RepeatState.off,
},
}; };
export let mediaInfo: MediaInfo = { ...defaultInfo };
export const updateMediaInfo = (arg: MediaInfo) => { export const updateMediaInfo = (arg: MediaInfo) => {
mediaInfo.title = propOrDefault(arg.title); mediaInfo = { ...defaultInfo, ...arg };
mediaInfo.artists = propOrDefault(arg.artists); mediaInfo.url = toUniversalUrl(mediaInfo.url);
mediaInfo.album = propOrDefault(arg.album);
mediaInfo.icon = propOrDefault(arg.icon);
mediaInfo.url = propOrDefault(arg.url);
mediaInfo.status = propOrDefault(arg.status);
mediaInfo.current = propOrDefault(arg.current);
mediaInfo.duration = propOrDefault(arg.duration);
mediaInfo.image = propOrDefault(arg.image);
}; };
/** /**
* Return the property or a default value * Append the universal link syntax (?u) to any url
* @param {*} prop property to check * @param url url to append the universal link syntax to
* @param {*} defaultValue defaults to "" * @returns url with `?u` appended, or the original value of url if falsy
*/ */
function propOrDefault(prop: string, defaultValue = "") { function toUniversalUrl(url: string) {
return prop ? prop : defaultValue; if (url) {
const queryParamsSet = url.indexOf("?");
return queryParamsSet > -1 ? `${url}&u` : `${url}?u`;
}
return url;
} }

View File

@ -11,6 +11,23 @@ const settingsMenuEntry = {
accelerator: "Control+=", accelerator: "Control+=",
}; };
const tidalMagazineEntry = {
label: "Magazine",
click() {
const magazineWindow = new BrowserWindow({
autoHideMenuBar: true,
webPreferences: {
sandbox: false,
plugins: true,
devTools: true, // I like tinkering, others might too
},
});
magazineWindow.loadURL("https://tidal.com/magazine/");
magazineWindow.show();
},
accelerator: "Control+M",
};
const quitMenuEntry = { const quitMenuEntry = {
label: "Quit", label: "Quit",
click() { click() {
@ -41,6 +58,7 @@ export const getMenu = function (mainWindow: BrowserWindow) {
{ role: "hideothers" }, { role: "hideothers" },
{ role: "unhide" }, { role: "unhide" },
{ type: "separator" }, { type: "separator" },
tidalMagazineEntry,
quitMenuEntry, quitMenuEntry,
], ],
}, },
@ -48,7 +66,7 @@ export const getMenu = function (mainWindow: BrowserWindow) {
: []), : []),
{ {
label: "File", label: "File",
submenu: [settingsMenuEntry, isMac ? { role: "close" } : quitMenuEntry], submenu: [settingsMenuEntry, tidalMagazineEntry, isMac ? { role: "close" } : quitMenuEntry],
}, },
{ {
label: "Edit", label: "Edit",
@ -101,6 +119,7 @@ export const getMenu = function (mainWindow: BrowserWindow) {
}, },
settingsMenuEntry, settingsMenuEntry,
toggleWindow, toggleWindow,
tidalMagazineEntry,
quitMenuEntry, quitMenuEntry,
]; ];

View File

@ -0,0 +1,13 @@
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const ObjectToDotNotation = (obj: any, prefix: string = "", target: any = {}) => {
Object.keys(obj).forEach((key: string) => {
if (typeof obj[key] === "object" && obj[key] !== null) {
ObjectToDotNotation(obj[key], prefix + key + ".", target);
} else {
const dotLocation = prefix + key;
target[dotLocation] = obj[key];
return target;
}
});
return target;
};

View File

@ -1,17 +1,40 @@
import Store from "electron-store"; import Store from "electron-store";
import { BrowserWindow } from "electron"; import { BrowserWindow, shell } from "electron";
import path from "path"; import path from "path";
import { settings } from "../constants/settings"; 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: {
adBlock: false, adBlock: false,
advanced: {
tidalUrl: "https://listen.tidal.com",
},
api: true, api: true,
apiSettings: { apiSettings: {
port: 47836, port: 47836,
hostname: "127.0.0.1",
}, },
customCSS: [], customCSS: [],
disableBackgroundThrottle: true, disableBackgroundThrottle: true,
@ -19,6 +42,11 @@ export const settingsStore = new Store({
enableCustomHotkeys: false, enableCustomHotkeys: false,
enableDiscord: false, enableDiscord: false,
discord: { discord: {
showSong: true,
showIdle: true,
idleText: "Browsing Tidal",
usingText: "Playing media on TIDAL",
includeTimestamps: true,
detailsPrefix: "Listening to ", detailsPrefix: "Listening to ",
buttonText: "Play on Tidal", buttonText: "Play on Tidal",
}, },
@ -41,6 +69,7 @@ export const settingsStore = new Store({
singleInstance: true, singleInstance: true,
skipArtists: false, skipArtists: false,
skippedArtists: [""], skippedArtists: [""],
staticWindowTitle: false,
theme: "none", theme: "none",
trayIcon: true, trayIcon: true,
updateFrequency: 500, updateFrequency: 500,
@ -61,6 +90,36 @@ export const settingsStore = new Store({
migrationStore.get(settings.ListenBrainz.delay) ?? 5000 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",
},
]);
},
"5.14.0": (migrationStore) => {
buildMigration("5.14.0", migrationStore, [
{ key: settings.apiSettings.hostname, value: "127.0.0.1" },
]);
},
"5.15.0": (migrationStore) => {
buildMigration("5.15.0", migrationStore, [
{ key: settings.advanced.tidalUrl, value: "https://listen.tidal.com" },
]);
},
"5.16.0": (migrationStore) => {
buildMigration("5.16.0", migrationStore, [{ key: settings.discord.showIdle, value: "true" }]);
},
}, },
}); });
@ -94,10 +153,18 @@ export const createSettingsWindow = function () {
settingsWindow.loadURL(`file://${__dirname}/../pages/settings/settings.html`); settingsWindow.loadURL(`file://${__dirname}/../pages/settings/settings.html`);
settingsWindow.webContents.setWindowOpenHandler(({ url }) => {
shell.openExternal(url);
return { action: "deny" };
});
settingsModule.settingsWindow = settingsWindow; settingsModule.settingsWindow = settingsWindow;
}; };
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
@ -111,3 +178,25 @@ export const hideSettingsWindow = function () {
export const closeSettingsWindow = function () { export const closeSettingsWindow = function () {
settingsWindow = null; settingsWindow = null;
}; };
/**
* add artists to the list of skipped artists
* @param artists list of artists to append
*/
export const addSkippedArtists = (artists: string[]) => {
const { skippedArtists } = settings;
const previousStoreValue = settingsStore.get<string, string[]>(skippedArtists);
settingsStore.set(skippedArtists, Array.from(new Set([...previousStoreValue, ...artists])));
};
/**
* Remove artists from the list of skipped artists
* @param artists list of artists to remove
*/
export const removeSkippedArtists = (artists: string[]) => {
const { skippedArtists } = settings;
const previousStoreValue = settingsStore.get<string, string[]>(skippedArtists);
const filteredArtists = previousStoreValue.filter((value) => ![...artists].includes(value));
settingsStore.set(skippedArtists, filteredArtists);
};

View File

@ -8,6 +8,7 @@
"sourceMap": true, "sourceMap": true,
"allowJs": true, "allowJs": true,
"outDir": "ts-dist", "outDir": "ts-dist",
"resolveJsonModule": true,
"esModuleInterop": true, "esModuleInterop": true,
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {