Compare commits

..

147 Commits

Author SHA1 Message Date
40907f7b7f changed mediaSessionController to API Controller 2025-09-01 13:08:42 +02:00
9cbb55fae7 Merge branch 'master' of github.com:Mastermindzh/tidal-hifi into feature/tidalControllers 2025-09-01 11:33:40 +02:00
8d4dd1ec9d Merge pull request #672 from Mastermindzh/renovate/sass-1.x
fix(deps): update sass to 1.91.0
2025-08-26 09:15:51 +02:00
d012ecb04e Merge pull request #671 from Mastermindzh/renovate/typescript-eslint-monorepo
chore(deps): update typescript-eslint monorepo to 8.41.0
2025-08-26 09:15:34 +02:00
Renovate Bot
fa4b4da9a5 fix(deps): update sass to 1.91.0 2025-08-26 01:30:43 +02:00
Renovate Bot
8f1a5dc344 chore(deps): update typescript-eslint monorepo to 8.41.0 2025-08-26 01:30:30 +02:00
604f34b31f Merge pull request #670 from Mastermindzh/next
electron update to 37.2.5
2025-08-25 12:11:33 +02:00
3ff37f78e8 version 5.20.1 2025-08-25 11:42:55 +02:00
8eaca2221f Merge pull request #669 from Mastermindzh/renovate/eslint-monorepo
chore(deps): update eslint to 9.34.0
2025-08-25 11:13:10 +02:00
8e9e9fed4f electron update to 37.2.5 2025-08-25 11:06:41 +02:00
Renovate Bot
d839dba58d chore(deps): update eslint to 9.34.0 2025-08-25 09:31:58 +02:00
bbbd919655 Merge pull request #645 from Mastermindzh/renovate/font-awesome-7.x
chore(deps): update font-awesome to 7.0.0
2025-08-25 09:14:16 +02:00
d4104b21e4 Merge pull request #658 from Mastermindzh/renovate/typescript-5.x-lockfile
chore(deps): update typescript to 5.9.2
2025-08-25 09:13:42 +02:00
6a23873d3d Merge pull request #659 from Mastermindzh/renovate/eslint-monorepo
chore(deps): update eslint to 9.33.0
2025-08-25 09:11:04 +02:00
8d6f08a7bb Merge pull request #662 from Mastermindzh/snyk-upgrade-5cdb32eaab1d0704488b8d5ad4e3ff42
[Snyk] Upgrade @xhayper/discord-rpc from 1.2.1 to 1.3.0
2025-08-25 09:06:49 +02:00
521b8b2170 Merge pull request #660 from Mastermindzh/renovate/typescript-eslint-monorepo
chore(deps): update typescript-eslint monorepo to 8.40.0
2025-08-25 09:05:06 +02:00
Renovate Bot
99e4cb7f48 chore(deps): update typescript-eslint monorepo to 8.40.0 2025-08-18 20:01:08 +02:00
snyk-bot
4cd130ee0d fix: upgrade @xhayper/discord-rpc from 1.2.1 to 1.3.0
Snyk has created this PR to upgrade @xhayper/discord-rpc from 1.2.1 to 1.3.0.

See this package in npm:
@xhayper/discord-rpc

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-08-16 09:19:45 +00:00
Renovate Bot
a2a2c800b6 chore(deps): update eslint to 9.33.0 2025-08-09 06:31:41 +02:00
Renovate Bot
83210f9e1d chore(deps): update typescript to 5.9.2 2025-08-08 12:47:13 +02:00
5d40d2d736 Merge pull request #654 from Mastermindzh/renovate/typescript-eslint-monorepo
chore(deps): update typescript-eslint monorepo to 8.39.0
2025-08-08 10:03:47 +02:00
405c2d7c9f Merge pull request #657 from Mastermindzh/renovate/stylelint-16.x-lockfile
chore(deps): update stylelint to 16.23.1
2025-08-08 09:58:46 +02:00
Renovate Bot
9f860d2eec chore(deps): update stylelint to 16.23.1 2025-08-07 20:39:11 +02:00
Renovate Bot
86487863f8 chore(deps): update typescript-eslint monorepo to 8.39.0 2025-08-06 12:42:07 +02:00
3312211df2 Merge pull request #656 from Mastermindzh/renovate/sass-1.x
fix(deps): update sass to 1.90.0
2025-08-06 11:04:03 +02:00
Renovate Bot
a84bb9efde fix(deps): update sass to 1.90.0 2025-08-06 04:43:00 +02:00
31c37b6c7f Merge pull request #650 from Mastermindzh/renovate/stylelint-16.x-lockfile
chore(deps): update stylelint to 16.23.0
2025-07-30 13:23:38 +02:00
be9724f0cb Merge pull request #651 from Mastermindzh/renovate/stylelint-config-standard-39.x
chore(deps): update stylelint-config-standard to 39.0.0
2025-07-30 13:23:14 +02:00
Renovate Bot
7277482c06 chore(deps): update stylelint-config-standard to 39.0.0 2025-07-29 20:42:09 +02:00
Renovate Bot
323b591f4f chore(deps): update stylelint to 16.23.0 2025-07-29 20:41:47 +02:00
c63b46ee06 Merge pull request #649 from Mastermindzh/renovate/request-2.x-lockfile
chore(deps): update @types/request to 2.48.13
2025-07-29 10:05:43 +02:00
Renovate Bot
b4a8e155af chore(deps): update @types/request to 2.48.13 2025-07-29 04:43:02 +02:00
2702d99aca Merge pull request #648 from Mastermindzh/renovate/eslint-monorepo
chore(deps): update eslint to 9.32.0
2025-07-28 09:39:07 +02:00
Renovate Bot
7cf9ae5c36 chore(deps): update eslint to 9.32.0 2025-07-25 20:56:13 +02:00
3a1402b47a Merge pull request #647 from Mastermindzh/renovate/npm-axios-vulnerability
fix(deps): update axios to 1.11.0 [security]
2025-07-24 10:22:05 +02:00
Renovate Bot
4366caa16a fix(deps): update axios to 1.11.0 [security] 2025-07-23 20:52:01 +02:00
Renovate Bot
9389b4195e chore(deps): update font-awesome to 7.0.0 2025-07-22 20:35:28 +02:00
87d8bc07ad Merge pull request #644 from Mastermindzh/renovate/typescript-eslint-monorepo
chore(deps): update typescript-eslint monorepo to 8.38.0
2025-07-22 01:40:04 +02:00
Renovate Bot
316bf054f5 chore(deps): update typescript-eslint monorepo to 8.38.0 2025-07-21 20:47:06 +02:00
0d4975ce62 Merge pull request #641 from Mastermindzh/renovate/stylelint-16.x-lockfile
chore(deps): update stylelint to 16.22.0
2025-07-18 13:52:11 +02:00
Renovate Bot
912f064fed chore(deps): update stylelint to 16.22.0 2025-07-18 13:18:24 +02:00
bff95bcf10 Merge pull request #639 from Mastermindzh/renovate/mastermindzh-prettier-config-1.x-lockfile
chore(deps): update @mastermindzh/prettier-config to 1.1.0
2025-07-16 00:49:23 +02:00
b59dce9c9b Merge pull request #640 from Mastermindzh/renovate/electron-35.x
chore(deps): update electron to v35.7.2+wvcus
2025-07-16 00:49:14 +02:00
Renovate Bot
f71fb60d38 chore(deps): update electron to v35.7.2+wvcus 2025-07-15 22:35:31 +02:00
Renovate Bot
fab566e9da chore(deps): update @mastermindzh/prettier-config to 1.1.0 2025-07-15 14:50:26 +02:00
d2e93e7062 Merge pull request #638 from Mastermindzh/renovate/typescript-eslint-monorepo
chore(deps): update typescript-eslint monorepo to 8.37.0
2025-07-15 09:23:16 +02:00
Renovate Bot
52a78a6a01 chore(deps): update typescript-eslint monorepo to 8.37.0 2025-07-14 19:46:27 +02:00
f878371fab Merge pull request #634 from Mastermindzh/renovate/eslint-monorepo
chore(deps): update eslint to 9.31.0
2025-07-14 10:51:12 +02:00
e81aaf1384 Merge pull request #635 from Mastermindzh/renovate/electron-35.x
chore(deps): update electron to v35.7.1+wvcus
2025-07-14 10:51:04 +02:00
Renovate Bot
98b4068b62 chore(deps): update electron to v35.7.1+wvcus 2025-07-12 01:50:08 +02:00
Renovate Bot
b7c1cee64d chore(deps): update eslint to 9.31.0 2025-07-11 22:50:18 +02:00
350b4434be Merge pull request #633 from Mastermindzh/next
Next
2025-07-10 14:57:19 +02:00
702a16357f chore: copilot review changes 2025-07-10 11:18:46 +02:00
c6619be068 chore: update version numbers 2025-07-10 11:11:57 +02:00
bf7cd70f20 updated some dev deps 2025-07-10 11:10:43 +02:00
03cb14e31e added nvmrc 2025-07-10 11:05:26 +02:00
697298da38 fix: updated Electron to 37, fixes #580 2025-07-10 10:56:58 +02:00
5f5b3b11a8 pulled in edocli PR and updated dependencies 2025-07-10 10:44:57 +02:00
d474b7b78f Merge pull request #632 from Mastermindzh/renovate/hotkeys-js-3.x-lockfile
fix(deps): update hotkeys-js to 3.13.15
2025-07-10 10:31:59 +02:00
ef374ea283 Merge pull request #631 from Mastermindzh/renovate/node-22.x-lockfile
chore(deps): update @types/node to 22.16.2
2025-07-10 10:31:44 +02:00
Renovate Bot
96ff46737b fix(deps): update hotkeys-js to 3.13.15 2025-07-09 11:52:31 +02:00
Renovate Bot
dee0044876 chore(deps): update @types/node to 22.16.2 2025-07-09 02:28:17 +02:00
3b1f456d4c Merge pull request #630 from Mastermindzh/renovate/node-22.x-lockfile
chore(deps): update @types/node to 22.16.1
2025-07-08 20:24:05 +02:00
Renovate Bot
f380839631 chore(deps): update @types/node to 22.16.1 2025-07-08 19:21:47 +02:00
fca5194873 Merge pull request #628 from Mastermindzh/renovate/electron-remote-2.x-lockfile
fix(deps): update @electron/remote to 2.1.3
2025-07-07 23:24:38 +02:00
Renovate Bot
92d1fc159d fix(deps): update @electron/remote to 2.1.3 2025-07-07 20:31:42 +02:00
b632c287b0 Merge pull request #622 from Mastermindzh/renovate/stylelint-config-standard-scss-15.x
chore(deps): update stylelint-config-standard-scss to 15.0.1
2025-07-07 20:27:40 +02:00
5a98b13e70 Merge pull request #627 from Mastermindzh/renovate/major-eslint-monorepo
chore(deps): update eslint to 9.30.1
2025-07-07 20:19:51 +02:00
3543253f3a Merge pull request #626 from Mastermindzh/renovate/typescript-eslint-monorepo
chore(deps): update typescript-eslint monorepo to 8.36.0
2025-07-07 20:19:22 +02:00
Renovate Bot
fff8399c9b chore(deps): update stylelint-config-standard-scss to 15.0.1 2025-07-07 19:46:47 +02:00
Renovate Bot
78b9b32dbd chore(deps): update eslint to 9.30.1 2025-07-07 19:46:30 +02:00
Renovate Bot
c773e59432 chore(deps): update typescript-eslint monorepo to 8.36.0 2025-07-07 19:46:15 +02:00
535de65e17 Merge pull request #624 from Mastermindzh/renovate/major-typescript-eslint-monorepo
chore(deps): update typescript-eslint monorepo to 8.36.0 (major)
2025-07-07 19:38:53 +02:00
b80599eab2 Merge pull request #623 from Mastermindzh/renovate/tsc-watch-7.x
chore(deps): update tsc-watch to 7.1.1
2025-07-07 19:32:21 +02:00
c161ef8f60 Merge pull request #621 from Mastermindzh/renovate/stylelint-config-standard-38.x
chore(deps): update stylelint-config-standard to 38.0.0
2025-07-07 19:02:14 +02:00
7ed160f266 Merge pull request #620 from Mastermindzh/renovate/node-22.x
chore(deps): update node.js to v22.17.0
2025-07-07 18:41:31 +02:00
b23975227f Merge pull request #619 from Mastermindzh/renovate/font-awesome-6.x
chore(deps): update font-awesome to 6.7.2
2025-07-07 18:37:38 +02:00
314e69dd4b Merge pull request #618 from Mastermindzh/renovate/major-eslint-monorepo
chore(deps): update eslint to 9.30.1
2025-07-07 18:37:18 +02:00
Renovate Bot
10251e2f30 chore(deps): update typescript-eslint monorepo to 8.35.1 2025-07-07 18:10:32 +02:00
Renovate Bot
cf06655008 chore(deps): update tsc-watch to 7.1.1 2025-07-07 18:10:17 +02:00
Renovate Bot
d5804ed4da chore(deps): update stylelint-config-standard to 38.0.0 2025-07-07 18:09:52 +02:00
Renovate Bot
76500ca6b8 chore(deps): update node.js to v22.17.0 2025-07-07 18:07:30 +02:00
Renovate Bot
f5c80e97c5 chore(deps): update font-awesome to 6.7.2 2025-07-07 18:05:39 +02:00
Renovate Bot
154698c084 chore(deps): update eslint to 9.30.1 2025-07-07 17:50:09 +02:00
9837e77768 Merge pull request #616 from Mastermindzh/renovate/axios-1.x-lockfile
fix(deps): update axios to 1.10.0
2025-07-07 16:25:54 +02:00
340bd82fe6 Merge pull request #615 from Mastermindzh/renovate/tsc-watch-6.x-lockfile
chore(deps): update tsc-watch to 6.3.1
2025-07-07 16:24:04 +02:00
b765ab9f48 Merge pull request #614 from Mastermindzh/renovate/stylelint-16.x-lockfile
chore(deps): update stylelint to 16.21.1
2025-07-07 16:23:28 +02:00
6562549897 Merge pull request #612 from Mastermindzh/renovate/prettier-3.x-lockfile
chore(deps): update prettier to 3.6.2
2025-07-07 16:23:02 +02:00
9089bc1d4e Merge pull request #607 from Mastermindzh/renovate/hotkeys-js-3.x-lockfile
fix(deps): update hotkeys-js to 3.13.14
2025-07-07 16:22:42 +02:00
5a3609676e Merge pull request #609 from Mastermindzh/renovate/node-20.x-lockfile
chore(deps): update @types/node to 20.19.4
2025-07-07 16:22:26 +02:00
76c3082d5c Merge pull request #605 from Mastermindzh/renovate/nodemon-3.x-lockfile
chore(deps): update nodemon to 3.1.10
2025-07-07 15:49:18 +02:00
7f7a8b7f5b Merge pull request #604 from Mastermindzh/renovate/express-5.x-lockfile
chore(deps): update @types/express to 5.0.3
2025-07-07 15:22:11 +02:00
Renovate Bot
2d49504818 fix(deps): update axios to 1.10.0 2025-07-07 14:32:14 +02:00
Renovate Bot
5f4f0c67d5 chore(deps): update tsc-watch to 6.3.1 2025-07-07 14:32:10 +02:00
Renovate Bot
3a5f1f155a chore(deps): update stylelint to 16.21.1 2025-07-07 14:32:06 +02:00
Renovate Bot
3c9328fb92 chore(deps): update prettier to 3.6.2 2025-07-07 14:32:02 +02:00
Renovate Bot
f262c54be2 chore(deps): update @types/node to 20.19.4 2025-07-07 14:31:58 +02:00
Renovate Bot
cb6e3e8b18 fix(deps): update hotkeys-js to 3.13.14 2025-07-07 14:31:54 +02:00
Renovate Bot
7f9b8dcee4 chore(deps): update nodemon to 3.1.10 2025-07-07 14:31:45 +02:00
Renovate Bot
99740f4335 chore(deps): update @types/express to 5.0.3 2025-07-07 14:31:41 +02:00
985bc0f1d5 Merge pull request #610 from Mastermindzh/renovate/electron-35.x
chore(deps): update electron to v35.7.0+wvcus
2025-07-07 14:31:22 +02:00
1f9b3a297a Merge pull request #611 from Mastermindzh/renovate/node-19.x
chore(deps): update node.js to v19.9.0
2025-07-07 14:28:41 +02:00
98ca73d17d Merge pull request #608 from Mastermindzh/renovate/sass-1.x
fix(deps): update sass to 1.89.2
2025-07-07 14:27:35 +02:00
396ed223d3 Merge pull request #603 from Mastermindzh/renovate/cors-2.x-lockfile
chore(deps): update @types/cors to 2.8.19
2025-07-07 13:27:08 +02:00
Renovate Bot
b0458f4de5 chore(deps): update node.js to v19.9.0 2025-07-07 13:09:34 +02:00
Renovate Bot
b875d091ec chore(deps): update electron to v35.7.0+wvcus 2025-07-07 13:09:30 +02:00
Renovate Bot
1a0b69d042 fix(deps): update sass to 1.89.2 2025-07-07 13:09:16 +02:00
Renovate Bot
e23a71a1a6 chore(deps): update @types/cors to 2.8.19 2025-07-07 13:08:49 +02:00
510ffa1509 Merge pull request #602 from Mastermindzh/renovate/configure
chore: Configure Renovate
2025-07-07 13:05:04 +02:00
Renovate Bot
e17ef32cde Add renovate.json 2025-07-07 12:00:56 +02:00
d4c3999d9c Merge pull request #593 from edocli/master
feat(build): Update Wayland launch flags for Linux
2025-07-07 09:11:38 +02:00
b0695600b1 Merge pull request #599 from Mastermindzh/snyk-upgrade-c222f288d19352ce1096b7bea79acbab
[Snyk] Upgrade hotkeys-js from 3.13.10 to 3.13.11
2025-07-07 09:10:17 +02:00
snyk-bot
22ecc7ade7 fix: upgrade hotkeys-js from 3.13.10 to 3.13.11
Snyk has created this PR to upgrade hotkeys-js from 3.13.10 to 3.13.11.

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-07-05 06:52:09 +00:00
a5102ebd03 Merge pull request #596 from Mastermindzh/snyk-upgrade-8471eaf4e087a1ad71b342b81e6741ae
[Snyk] Upgrade sass from 1.89.0 to 1.89.1
2025-06-23 12:46:05 +02:00
snyk-bot
d01f08508f fix: upgrade sass from 1.89.0 to 1.89.1
Snyk has created this PR to upgrade sass from 1.89.0 to 1.89.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-06-21 09:48:24 +00:00
e87d8f6922 Merge pull request #594 from Mastermindzh/snyk-upgrade-2487864f695c4105f8d0392b9d406127
[Snyk] Upgrade sass from 1.87.0 to 1.89.0
2025-06-15 10:13:13 +02:00
snyk-bot
c044476014 fix: upgrade sass from 1.87.0 to 1.89.0
Snyk has created this PR to upgrade sass from 1.87.0 to 1.89.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-06-14 11:15:21 +00:00
75df43864b Merge pull request #588 from Mastermindzh/snyk-upgrade-5a5f2fcd2f171f53dcc34ab5933a3c09
[Snyk] Upgrade sass from 1.86.3 to 1.87.0
2025-06-09 11:00:33 +03:00
Edoc
174d0a38a1 Update electron-builder.base.yml
Adds the `--use-angle` flag to work around driver compatibility issues that cause the NVIDIA GPU process to crash repeatedly on Wayland. The native EGL/GBM backend fails with "Cannot create bo" errors.
2025-06-09 03:09:37 -04:00
Edoc
cf1aeefe67 Update electron-builder.base.yml
- Remove obsolete --enable-features=UseOzonePlatform flag
- Add --enable-wayland-ime for IME support on Wayland
2025-06-08 18:46:01 -04:00
280d927a03 Merge pull request #590 from Mastermindzh/snyk-upgrade-218353a4201f743fe837fca092089385
[Snyk] Upgrade hotkeys-js from 3.13.9 to 3.13.10
2025-05-31 14:16:42 +02:00
snyk-bot
f62294ef20 fix: upgrade hotkeys-js from 3.13.9 to 3.13.10
Snyk has created this PR to upgrade hotkeys-js from 3.13.9 to 3.13.10.

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-05-31 08:20:25 +00:00
237e354f80 Merge pull request #587 from Mastermindzh/snyk-upgrade-7cf34416a89cb7ffe31756c1f2c4b62d
[Snyk] Upgrade axios from 1.8.4 to 1.9.0
2025-05-26 09:46:38 +02:00
snyk-bot
3a5172cb23 fix: upgrade sass from 1.86.3 to 1.87.0
Snyk has created this PR to upgrade sass from 1.86.3 to 1.87.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-05-24 08:12:39 +00:00
snyk-bot
f8f12cca62 fix: upgrade axios from 1.8.4 to 1.9.0
Snyk has created this PR to upgrade axios from 1.8.4 to 1.9.0.

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
2025-05-24 08:12:36 +00:00
c5082e546b Merge branch 'master' of github.com:Mastermindzh/tidal-hifi into feature/tidalControllers 2025-04-22 15:59:22 +02:00
79bbab6a82 Merge pull request #584 from Mastermindzh/next
albumInfo fix, updated dependencies
2025-04-22 11:40:41 +02:00
7376df7f30 albumInfo fix, updated dependencies 2025-04-22 11:14:19 +02:00
53e7f42c59 chore: giving up on state, reverted to MediaSessionController 2025-04-20 14:25:09 +02:00
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
4330994b32 chore: added debug possibilities 2025-04-20 13:04:01 +02:00
90d81d2178 feat: replace hardcoded selector with setting implements #543 2025-04-20 12:15:03 +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
6385375fd6 Merge branch 'master' of github.com:Mastermindzh/tidal-hifi into feature/tidalControllers 2025-04-18 14:53:12 +02: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
a08fad421a Merge branch 'master' of github.com:Mastermindzh/tidal-hifi into feature/tidalControllers 2025-03-18 10:55:07 +01:00
c975ecb16b incorporated next into this branch 2025-03-17 10:28:08 +01:00
f553fe98e7 finished interface for TidalController and moved most domLogic to the domController 2025-03-09 14:47:30 +01:00
aed40ec4ef Merge branch 'master' of github.com:Mastermindzh/tidal-hifi into feature/tidalControllers 2025-03-07 09:28:20 +01:00
d3e126d86b chore: bump modules 2024-10-28 22:52:33 +01:00
d36bc7480f extracted yet more of the domControl into the new tidalController 2024-10-28 22:49:00 +01:00
f608d42747 rename to TidalController 2024-10-27 23:07:52 +01:00
286c7789c0 Merge branch 'develop' of github.com:Mastermindzh/tidal-hifi into feature/tidalControllers 2024-10-27 23:06:40 +01:00
d67f62c0dc Merge branch 'master' of github.com:Mastermindzh/tidal-hifi into develop 2024-10-27 23:06:20 +01:00
01c5ec7ebb PoC: separated tidal interface controller into separate controllers 2024-10-27 23:05:55 +01:00
b2afd44dd6 prettier :) 2024-10-27 21:31:31 +01:00
33 changed files with 2216 additions and 1511 deletions

View File

@@ -4,12 +4,12 @@ name: default
steps: steps:
- name: install - name: install
image: node:19.4.0 image: node:22.17.0
commands: commands:
- npm install - npm install
- name: build_with_linux - name: build_with_linux
image: node:19.4.0 image: node:22.17.0
commands: commands:
- apt-get update && apt-get upgrade -y - apt-get update && apt-get upgrade -y
- apt-get install -y libarchive-tools rpm - apt-get install -y libarchive-tools rpm

View File

@@ -21,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: 22.4 node-version: 22.12.0
- run: npm install - run: npm install
- run: npm run build - run: npm run build
# - uses: actions/upload-artifact@master # - uses: actions/upload-artifact@master

View File

@@ -21,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: 22.4 node-version: 22.12.0
- run: npm install - run: npm install
- run: npm run build - run: npm run build
- uses: actions/upload-artifact@master - uses: actions/upload-artifact@master

2
.nvmrc
View File

@@ -1 +1 @@
19.8.1 v22.12.0

View File

@@ -1,16 +1,10 @@
{ {
"plugins": [ "plugins": ["stylelint-prettier"],
"stylelint-prettier" "extends": ["stylelint-config-standard-scss"],
], "ignoreFiles": ["src/themes/**.scss"],
"extends": [
"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,
"no-descending-specificity": null "no-descending-specificity": null
} }
} }

36
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,36 @@
{
"version": "0.2.0",
"configurations": [
{
// we can use this to debug the main process
"name": "Electron: Main",
"type": "node",
"request": "launch",
"runtimeExecutable": "${workspaceFolder}/node_modules/electron/dist/electron",
"args": [
"${workspaceFolder}/ts-dist/main.js",
"--no-sandbox",
"--disable-gpu",
"--disable-software-rasterizer",
"--in-process-gpu",
"--inspect=0.0.0.0:5858",
"--remote-debugging-port=8315"
],
"protocol": "inspector",
"env": {
"ELECTRON_DISABLE_SECURITY_WARNINGS": "false"
},
"outputCapture": "std"
},
// first run, then connect this to make sure we debug the UI
{
"name": "Electron: Renderer",
"type": "chrome",
"request": "attach",
"port": 8315,
"webRoot": "${workspaceFolder}",
"sourceMaps": true,
"skipFiles": ["<node_internals>/**"]
}
]
}

View File

@@ -17,7 +17,9 @@
"trackid", "trackid",
"tracklist", "tracklist",
"widevine", "widevine",
"xesam" "wvcus",
"xesam",
"xhayper"
], ],
"sonarlint.connectedMode.project": { "sonarlint.connectedMode.project": {
"connectionId": "public-sonarcloud", "connectionId": "public-sonarcloud",

View File

@@ -4,6 +4,23 @@ 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.20.1]
- Updated electron to 37.2.5
## [5.20.0]
- Removes the `--enable-features=UseOzonePlatform` flag, as the Ozone platform has been the default on Linux since Electron 28 and this flag is no longer necessary.
- Adds the `--enable-wayland-ime` flag to enable Input Method Editor (IME) support in Wayland environments, improving the input experience for CJK and other users.
- Updated various dependencies
- Updated Electron to 37, potentially fixing [#580](https://github.com/Mastermindzh/tidal-hifi/issues/580)
## [5.19.0]
- Fixed the issue where media updates would cease to work after album names can't be found
- Will simply report an empty string when it can't find the album
- Updated various dependencies
## [5.18.2] ## [5.18.2]
- Reverted to sass 1.79.4 to fix `Nix` builds - Reverted to sass 1.79.4 to fix `Nix` builds
@@ -40,6 +57,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added all missing swagger/openApi info with the help of [Times-Z](https://github.com/Times-Z) - Added all missing swagger/openApi info with the help of [Times-Z](https://github.com/Times-Z)
- Updated most dependency versions - Updated most dependency versions
- This includes Electron 31! - This includes Electron 31!
- Added a channel selector so we can now use Tidal's staging environment directly from the app - Added a channel selector so we can now use Tidal's staging environment directly from the app
@@ -131,10 +149,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Updated Electron to 28.1.1 (fixes [325](https://github.com/Mastermindzh/tidal-hifi/issues/325)) - Updated Electron to 28.1.1 (fixes [325](https://github.com/Mastermindzh/tidal-hifi/issues/325))
- Updated dependencies to latest - Updated dependencies to latest
- added theme files to stylelint ignore - added theme files to stylelint ignore
- fixed other stylelint errors - fixed other stylelint errors
- Added functionality to favorite a song (fixes [#323](https://github.com/Mastermindzh/tidal-hifi/issues/323)) - 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 a hotkey to favorite ("Add to collection") songs: Control+a
- Added the "favorite" field in the `mediaInfo` and the API `/current` endpoint - 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` - Added an endpoint to toggle favoriting a song: `http://localhost:47836/favorite/toggle`
@@ -155,10 +175,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added settings to customize the Discord rich presence information - Added settings to customize the Discord rich presence information
- Discord settings are now also collapsible like the ListenBrainz ones are - Discord settings are now also collapsible like the ListenBrainz ones are
- Restyled settings menu to include version number and useful links on the about page - Restyled settings menu to include version number and useful links on the about page
![The new about page](./docs/images/new-about.png) ![The new about page](./docs/images/new-about.png)
- The ListenBrainz integration has been extended with a configurable (5 seconds by default) delay in song reporting so that it doesn't spam the API when you are cycling through songs. - The ListenBrainz integration has been extended with a configurable (5 seconds by default) delay in song reporting so that it doesn't spam the API when you are cycling through songs.
- Custom CSS now also applies to settings window - Custom CSS now also applies to settings window
![Tokyo Night theme on settings window](./docs/images/customcss-menu.png) ![Tokyo Night theme on settings window](./docs/images/customcss-menu.png)
## [5.6.0] ## [5.6.0]

View File

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

View File

@@ -1,7 +1,7 @@
appId: com.rickvanlieshout.tidal-hifi appId: com.rickvanlieshout.tidal-hifi
electronVersion: 35.1.1 electronVersion: 37.2.5
electronDownload: electronDownload:
version: 35.1.1+wvcus version: 37.2.5+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:
@@ -17,9 +17,10 @@ linux:
executableName: tidal-hifi executableName: tidal-hifi
executableArgs: executableArgs:
[ [
"--enable-features=UseOzonePlatform",
"--ozone-platform-hint=auto", "--ozone-platform-hint=auto",
"--enable-features=WaylandWindowDecorations", "--enable-features=WaylandWindowDecorations",
"--enable-wayland-ime",
"--use-angle",
] ]
desktop: desktop:
entry: entry:

2276
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "tidal-hifi", "name": "tidal-hifi",
"version": "5.18.2", "version": "5.20.1",
"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": {
@@ -23,6 +23,7 @@
"build-mac": "npm run builder -- -c ./build/electron-builder.yml -m", "build-mac": "npm run builder -- -c ./build/electron-builder.yml -m",
"build-base": "npm run builder -- -c ./build/electron-builder.base.yml", "build-base": "npm run builder -- -c ./build/electron-builder.base.yml",
"prebuilder": "npm run compile", "prebuilder": "npm run compile",
"prettier": "prettier . --write",
"builder": "electron-builder --publish=never", "builder": "electron-builder --publish=never",
"sass": "sass ./src/pages/settings/settings.scss ./src/pages/settings/settings.css && sass --no-source-map src/themes:themes", "sass": "sass ./src/pages/settings/settings.scss ./src/pages/settings/settings.css && sass --no-source-map src/themes:themes",
"style-lint": "npx stylelint **/*.scss", "style-lint": "npx stylelint **/*.scss",
@@ -40,44 +41,45 @@
"homepage": "https://github.com/Mastermindzh/tidal-hifi", "homepage": "https://github.com/Mastermindzh/tidal-hifi",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@electron/remote": "^2.1.2", "@electron/remote": "^2.1.3",
"@types/swagger-jsdoc": "^6.0.4", "@types/swagger-jsdoc": "^6.0.4",
"@xhayper/discord-rpc": "^1.2.1", "@xhayper/discord-rpc": "1.3.0",
"axios": "^1.8.4", "axios": "^1.10.0",
"electron-store": "^8.2.0",
"express": "^4.21.2",
"hotkeys-js": "^3.13.9",
"mpris-service": "^2.1.2",
"sass": "1.79.4",
"swagger-ui-express": "^5.0.1",
"cors": "^2.8.5", "cors": "^2.8.5",
"request": "^2.88.2" "electron-store": "^8.2.0",
"express": "^5.1.0",
"hotkeys-js": "^3.13.15",
"mpris-service": "^2.1.2",
"request": "^2.88.2",
"sass": "1.91.0",
"swagger-ui-express": "^5.0.1"
}, },
"devDependencies": { "devDependencies": {
"@mastermindzh/prettier-config": "^1.0.0", "@mastermindzh/prettier-config": "^1.0.0",
"@types/cors": "^2.8.17", "@types/cors": "^2.8.19",
"@types/express": "^4.17.21", "@types/express": "^5.0.3",
"@types/node": "^20.14.10", "@types/node": "^22.16.2",
"@types/request": "^2.48.12", "@types/request": "^2.48.12",
"@types/swagger-ui-express": "^4.1.6", "@types/swagger-ui-express": "^4.1.8",
"@typescript-eslint/eslint-plugin": "^7.16.0", "@typescript-eslint/eslint-plugin": "^8.36.0",
"@typescript-eslint/parser": "^7.15.0", "@typescript-eslint/parser": "^8.36.0",
"copyfiles": "^2.4.1", "copyfiles": "^2.4.1",
"electron": "github:castlabs/electron-releases#v35.1.1+wvcus", "electron": "github:castlabs/electron-releases#v37.2.5+wvcus",
"electron-builder": "~26.0.12", "electron-builder": "~26.0.12",
"eslint": "^8.57.0", "eslint": "^9.30.1",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"markdown-toc": "^1.2.0", "markdown-toc": "^1.2.0",
"nodemon": "^3.1.4", "node-abi": "^4.12.0",
"prettier": "^3.3.2", "nodemon": "^3.1.10",
"stylelint": "^16.6.1", "prettier": "^3.6.2",
"stylelint-config-standard": "^36.0.1", "stylelint": "^16.21.1",
"stylelint-config-standard-scss": "^13.1.0", "stylelint-config-standard": "^39.0.0",
"stylelint-prettier": "^5.0.0", "stylelint-config-standard-scss": "^15.0.1",
"stylelint-prettier": "^5.0.3",
"swagger-jsdoc": "^6.2.8", "swagger-jsdoc": "^6.2.8",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"tsc-watch": "^6.2.0", "tsc-watch": "^7.1.1",
"typescript": "^5.5.3" "typescript": "^5.8.3"
}, },
"prettier": "@mastermindzh/prettier-config" "prettier": "@mastermindzh/prettier-config"
} }

4
renovate.json Normal file
View File

@@ -0,0 +1,4 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"ignoreDeps": ["@types/node", "electron-store", "@xhayper/discord-rpc"]
}

View File

@@ -0,0 +1,6 @@
export type DomControllerOptions = {
/**
* Interval that tidal-hifi scrapes the dom for information
*/
refreshInterval: number;
};

View File

@@ -0,0 +1,354 @@
import { convertDurationToSeconds } from "../../features/time/parse";
import { MediaInfo } from "../../models/mediaInfo";
import { MediaStatus } from "../../models/mediaStatus";
import { RepeatState } from "../../models/repeatState";
import { TidalController } from "../TidalController";
import { DomControllerOptions } from "./DomControllerOptions";
export class DomTidalController implements TidalController<DomControllerOptions> {
private updateSubscriber: (state: Partial<MediaInfo>) => void;
private currentlyPlaying = MediaStatus.paused;
private currentRepeatState: RepeatState = RepeatState.off;
private currentShuffleState = false;
private readonly elements = {
play: '*[data-test="play"]',
pause: '*[data-test="pause"]',
next: '*[data-test="next"]',
previous: 'button[data-test="previous"]',
title: '*[data-test^="footer-track-title"]',
artists: '*[data-test^="grid-item-detail-text-title-artist"]',
home: '*[data-test="menu--home"]',
back: '[title^="Back"]',
forward: '[title^="Next"]',
search: '[class^="searchField"]',
shuffle: '*[data-test="shuffle"]',
repeat: '*[data-test="repeat"]',
account: '*[data-test^="profile-image-button"]',
settings: '*[data-test^="sidebar-menu-button"]',
openSettings: '*[data-test^="open-settings"]',
media: '*[data-test="current-media-imagery"]',
image: "img",
current: '*[data-test="current-time"]',
duration: '*[class^=_playbackControlsContainer] *[data-test="duration"]',
bar: '*[data-test="progress-bar"]',
footer: "#footerPlayer",
mediaItem: "[data-type='mediaItem']",
album_header_title: '*[class^="playingFrom"] span:nth-child(2)',
playing_from: '*[class^="playingFrom"] span:nth-child(2)',
queue_album: "*[class^=playQueueItemsContainer] *[class^=groupTitle] span:nth-child(2)",
currentlyPlaying: "[class^='isPlayingIcon'], [data-test-is-playing='true']",
album_name_cell: '[class^="album"]',
tracklist_row: '[data-test="tracklist-row"]',
volume: '*[data-test="volume"]',
favorite: '*[data-test="footer-favorite-button"]',
/**
* Get an element from the dom
* @param {*} key key in elements object to fetch
*/
get: function (key: string) {
return globalThis.document.querySelector(this[key.toLowerCase()]) ?? "";
},
/**
* Get the icon of the current media
*/
getSongIcon: function () {
const figure = this.get("media");
if (figure) {
const mediaElement = figure.querySelector(this["image"]);
if (mediaElement) {
return mediaElement.src.replace("80x80", "640x640");
}
}
return "";
},
/**
* returns an array of all artists in the current media
* @returns {Array} artists
*/
getArtistsArray: function () {
const footer = this.get("footer");
if (footer) {
const artists = footer.querySelectorAll(this.artists);
if (artists)
return Array.from(artists).map((artist) => (artist as HTMLElement).textContent);
}
return [];
},
/**
* unify the artists array into a string separated by commas
* @param {Array} artistsArray
* @returns {String} artists
*/
getArtistsString: function (artistsArray: string[]) {
if (artistsArray.length > 0) return artistsArray.join(", ");
return "unknown artist(s)";
},
getAlbumName: function () {
try {
//If listening to an album, get its name from the header title
if (globalThis.location.href.includes("/album/")) {
const albumName = globalThis.document.querySelector(this.album_header_title);
if (albumName) {
return albumName.textContent;
}
//If listening to a playlist or a mix, get album name from the list
} else if (
globalThis.location.href.includes("/playlist/") ||
globalThis.location.href.includes("/mix/")
) {
if (this.currentlyPlaying === MediaStatus.playing) {
// find the currently playing element from the list (which might be in an album icon), traverse back up to the mediaItem (row) and select the album cell.
// document.querySelector("[class^='isPlayingIcon'], [data-test-is-playing='true']").closest('[data-type="mediaItem"]').querySelector('[class^="album"]').textContent
const row = window.document
.querySelector(this.currentlyPlaying)
.closest(this.mediaItem);
if (row) {
return row.querySelector(this.album_name_cell).textContent;
}
}
}
// see whether we're on the queue page and get it from there
const queueAlbumName = this.getText("queue_album");
if (queueAlbumName) {
return queueAlbumName;
}
return "";
} catch {
return "";
}
},
isMuted: function () {
return this.get("volume").getAttribute("aria-checked") === "false"; // it's muted if aria-checked is false
},
isFavorite: function () {
return this.get("favorite").getAttribute("aria-checked") === "true";
},
/**
* Shorthand function to get the text of a dom element
* @param {*} key key in elements object to fetch
*/
getText: function (key: string) {
const element = this.get(key);
return element ? element.textContent : "";
},
/**
* Shorthand function to click a dom element
* @param {*} key key in elements object to fetch
*/
click: function (key: string) {
this.get(key).click();
return this;
},
/**
* Shorthand function to focus a dom element
* @param {*} key key in elements object to fetch
*/
focus: function (key: string) {
return this.get(key).focus();
},
};
onMediaInfoUpdate(callback: (state: Partial<MediaInfo>) => void): void {
this.updateSubscriber = callback;
}
bootstrap(options: DomControllerOptions): void {
/**
* 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
*/
const getTrackURL = () => {
const id = this.getTrackId();
return `https://tidal.com/browse/track/${id}`;
};
setInterval(async () => {
const title = this.getTitle();
const artistsArray = this.getArtists();
const artistsString = this.getArtistsString();
const current = this.getCurrentTime();
const currentStatus = this.getCurrentlyPlayingStatus();
const shuffleState = this.getCurrentShuffleState();
const repeatState = this.getCurrentRepeatState();
const playStateChanged = currentStatus != this.currentlyPlaying;
const shuffleStateChanged = shuffleState != this.currentShuffleState;
const repeatStateChanged = repeatState != this.currentRepeatState;
if (playStateChanged) this.currentlyPlaying = currentStatus;
if (shuffleStateChanged) this.currentShuffleState = shuffleState;
if (repeatStateChanged) this.currentRepeatState = repeatState;
const album = this.getAlbumName();
const duration = this.getDuration();
const updatedInfo = {
title,
artists: artistsString,
artistsArray,
album: album,
playingFrom: this.getPlayingFrom(),
status: currentStatus,
url: getTrackURL(),
current,
currentInSeconds: convertDurationToSeconds(current),
duration,
durationInSeconds: convertDurationToSeconds(duration),
image: this.getSongIcon(),
favorite: this.isFavorite(),
player: {
status: currentStatus,
shuffle: shuffleState,
repeat: repeatState,
},
};
this.updateSubscriber(updatedInfo);
}, options.refreshInterval);
}
playPause = (): void => {
const play = this.elements.get("play");
if (play) {
this.elements.click("play");
} else {
this.elements.click("pause");
}
};
goToHome(): void {
this.elements.click("home");
}
openSettings(): void {
this.elements.click("settings");
setTimeout(() => {
this.elements.click("openSettings");
}, 100);
}
toggleFavorite(): void {
this.elements.click("favorite");
}
back(): void {
this.elements.click("back");
}
forward(): void {
this.elements.click("forward");
}
repeat(): void {
this.elements.click("repeat");
}
next(): void {
this.elements.click("next");
}
previous(): void {
this.elements.click("previous");
}
toggleShuffle(): void {
this.elements.click("shuffle");
}
getCurrentlyPlayingStatus() {
const pause = this.elements.get("pause");
// if pause button is visible tidal is playing
if (pause) {
return MediaStatus.playing;
} else {
return MediaStatus.paused;
}
}
getCurrentShuffleState() {
const shuffle = this.elements.get("shuffle");
return shuffle?.getAttribute("aria-checked") === "true";
}
getCurrentRepeatState() {
const repeat = this.elements.get("repeat");
switch (repeat?.getAttribute("data-type")) {
case "button__repeatAll":
return RepeatState.all;
case "button__repeatSingle":
return RepeatState.single;
default:
return RepeatState.off;
}
}
play(): void {
this.playPause();
}
pause(): void {
this.playPause();
}
stop(): void {
this.playPause();
}
getCurrentPosition() {
return this.elements.getText("current");
}
getCurrentPositionInSeconds(): number {
return convertDurationToSeconds(this.getCurrentPosition());
}
getTrackId(): string {
const URLelement = this.elements.get("title").querySelector("a");
if (URLelement !== null) {
const id = URLelement.href.replace(/\D/g, "");
return id;
}
return window.location.toString();
}
getCurrentTime(): string {
return this.elements.getText("current");
}
getDuration(): string {
return this.elements.getText("duration");
}
getAlbumName(): string {
return this.elements.getAlbumName();
}
getTitle(): string {
return this.elements.getText("title");
}
getArtists(): string[] {
return this.elements.getArtistsArray();
}
getArtistsString(): string {
return this.elements.getArtistsString(this.getArtists());
}
getPlayingFrom(): string {
return this.elements.getText("playing_from");
}
isFavorite(): boolean {
return this.elements.isFavorite();
}
getSongIcon(): string {
return this.elements.getSongIcon();
}
}

View File

@@ -0,0 +1,53 @@
import { MediaInfo } from "../models/mediaInfo";
import { MediaStatus } from "../models/mediaStatus";
import { RepeatState } from "../models/repeatState";
export interface TidalController<TBootstrapOptions = object> {
goToHome(): void;
openSettings(): void;
/**
* Play or pause the current media
*/
playPause(): void;
play(): void;
pause(): void;
stop(): void;
toggleFavorite(): void;
back(): void;
forward(): void;
repeat(): void;
next(): void;
previous(): void;
toggleShuffle(): void;
/**
* Optional setup/startup/bootstrap for this controller
*/
bootstrap(options: TBootstrapOptions): void;
/**
* Method that triggers every time the MediaInfo updates
* @param callback function that receives the updated media info
*/
onMediaInfoUpdate(callback: (state: Partial<MediaInfo>) => void): void;
/**
* Update the current status of tidal (e.g playing or paused)
*/
getCurrentlyPlayingStatus(): MediaStatus;
getCurrentShuffleState(): boolean;
getCurrentRepeatState(): RepeatState;
getCurrentPosition(): string;
getCurrentPositionInSeconds(): number;
getTrackId(): string;
getCurrentTime(): string;
getDuration(): string;
getAlbumName(): string;
getTitle(): string;
getArtists(): string[];
getArtistsString(): string;
getPlayingFrom(): string;
getSongIcon(): string;
isFavorite(): boolean;
}

View File

@@ -0,0 +1,137 @@
import { Logger } from "../../features/logger";
import { MediaInfo } from "../../models/mediaInfo";
import { MediaStatus } from "../../models/mediaStatus";
import { RepeatState } from "../../models/repeatState";
import { DomTidalController } from "../DomController/DomTidalController";
import { TidalController } from "../TidalController";
export class TidalApiController implements TidalController {
public domMediaController: TidalController;
constructor() {
this.domMediaController = new DomTidalController();
Logger.log("[TidalApiController] - Initialized domController as a backup controller");
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
onMediaInfoUpdate(callback: (state: Partial<MediaInfo>) => void): void {
globalThis.alert("method not implemented");
throw new Error("Method not implemented.");
}
bootstrap(): void {
globalThis.alert("Method not implemented: ");
throw new Error("Method not implemented.");
}
// example of using the original domMediaController as a fallback
goToHome(): void {
this.domMediaController.goToHome();
}
getDuration(): string {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
getAlbumName(): string {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
getTitle(): string {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
getArtists(): string[] {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
getArtistsString(): string {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
getPlayingFrom(): string {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
isFavorite(): boolean {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
getSongIcon(): string {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
getCurrentTime(): string {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
getCurrentPosition(): string {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
getCurrentPositionInSeconds(): number {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
getTrackId(): string {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
play(): void {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
pause(): void {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
stop(): void {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
getCurrentShuffleState(): boolean {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
getCurrentRepeatState(): RepeatState {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
getCurrentlyPlayingStatus(): MediaStatus {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
back(): void {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
forward(): void {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
repeat(): void {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
next(): void {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
previous(): void {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
toggleShuffle(): void {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
openSettings(): void {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
toggleFavorite(): void {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
playPause(): void {
globalThis.alert("Method not implemented");
throw new Error("Method not implemented.");
}
}

View File

@@ -0,0 +1,4 @@
export const tidalControllers = {
domController: "domController",
tidalApiController: "tidalApiController",
};

View File

@@ -13,6 +13,7 @@ export const settings = {
advanced: { advanced: {
root: "advanced", root: "advanced",
tidalUrl: "advanced.tidalUrl", tidalUrl: "advanced.tidalUrl",
controllerType: "advanced.controllerType",
}, },
api: "api", api: "api",
apiSettings: { apiSettings: {

View File

@@ -88,8 +88,9 @@ export const addCurrentInfo = (expressApp: Router) => {
* schema: * schema:
* $ref: '#/components/schemas/MediaInfo' * $ref: '#/components/schemas/MediaInfo'
*/ */
expressApp.get("/current", (req, res) => res.json({ ...mediaInfo, artist: mediaInfo.artists })); expressApp.get("/current", (_req, res) => {
res.json({ ...mediaInfo, artist: mediaInfo.artists });
});
/** /**
* @swagger * @swagger
* /current/image: * /current/image:

View File

@@ -21,8 +21,12 @@ export const startApi = (mainWindow: BrowserWindow) => {
expressApp.use(cors()); expressApp.use(cors());
expressApp.use(express.json()); expressApp.use(express.json());
expressApp.use("/docs", swaggerUi.serve, swaggerUi.setup(swaggerSpec)); expressApp.use("/docs", swaggerUi.serve, swaggerUi.setup(swaggerSpec));
expressApp.get("/", (req, res) => res.send("Hello World!")); expressApp.get("/", (req, res) => {
expressApp.get("/swagger.json", (req, res) => res.json(swaggerSpec)); res.send("Hello World!");
});
expressApp.get("/swagger.json", (req, res) => {
res.json(swaggerSpec);
});
// add features // add features
addLegacyApi(expressApp, mainWindow); addLegacyApi(expressApp, mainWindow);

View File

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

View File

@@ -0,0 +1,15 @@
import { downloadFile } from "../../scripts/download";
import { Logger } from "../logger";
export const downloadIcon = async (imagePath: string, destination: string): Promise<string> => {
if (imagePath.startsWith("http")) {
try {
return await downloadFile(imagePath, destination);
} catch (error) {
Logger.log("Downloading file failed", { error });
return "";
}
}
return "";
};

15
src/features/tidal/url.ts Normal file
View File

@@ -0,0 +1,15 @@
/**
* Build a track url given the id
*/
export const getTrackURL = (trackId: string) => {
return `https://tidal.com/browse/track/${trackId}`;
};
/**
* Retrieve the universal link given a regular track link
* @param trackLink
* @returns
*/
export const getUniversalLink = (trackLink: string) => {
return `${trackLink}?u`;
};

View File

@@ -32,7 +32,8 @@ const PROTOCOL_PREFIX = "tidal";
const windowPreferences = { const windowPreferences = {
sandbox: false, sandbox: false,
plugins: true, plugins: true,
devTools: true, // I like tinkering, others might too devTools: true, // Ensure devTools is enabled for debugging
contextIsolation: false, // Disable context isolation for debugging
}; };
setDefaultFlags(app); setDefaultFlags(app);

View File

@@ -1,9 +1,11 @@
import { MediaPlayerInfo } from "./mediaPlayerInfo"; import { MediaPlayerInfo } from "./mediaPlayerInfo";
import { MediaStatus } from "./mediaStatus"; import { MediaStatus } from "./mediaStatus";
import { RepeatState } from "./repeatState";
export interface MediaInfo { export interface MediaInfo {
title: string; title: string;
artists: string; artists: string;
artistsArray?: string[];
album: string; album: string;
icon: string; icon: string;
status: MediaStatus; status: MediaStatus;
@@ -17,3 +19,30 @@ export interface MediaInfo {
favorite: boolean; favorite: boolean;
player?: MediaPlayerInfo; player?: MediaPlayerInfo;
} }
export const getEmptyMediaInfo = () => {
const emptyState: MediaInfo = {
title: "",
artists: "",
artistsArray: [],
album: "",
playingFrom: "",
status: MediaStatus.playing,
url: "",
current: "00:00",
currentInSeconds: 100,
duration: "00:00",
durationInSeconds: 100,
image: "",
icon: "",
favorite: true,
player: {
status: MediaStatus.playing,
shuffle: true,
repeat: RepeatState.all,
},
};
return emptyState;
};

View File

@@ -61,7 +61,8 @@ let adBlock: HTMLInputElement,
discord_show_song: HTMLInputElement, discord_show_song: HTMLInputElement,
discord_show_idle: HTMLInputElement, discord_show_idle: HTMLInputElement,
discord_idle_text: HTMLInputElement, discord_idle_text: HTMLInputElement,
discord_using_text: HTMLInputElement; discord_using_text: HTMLInputElement,
controllerType: HTMLSelectElement;
addCustomCss(app); addCustomCss(app);
@@ -157,6 +158,7 @@ function refreshSettings() {
discord_show_idle.checked = settingsStore.get(settings.discord.showIdle); discord_show_idle.checked = settingsStore.get(settings.discord.showIdle);
discord_idle_text.value = settingsStore.get(settings.discord.idleText); discord_idle_text.value = settingsStore.get(settings.discord.idleText);
discord_using_text.value = settingsStore.get(settings.discord.usingText); discord_using_text.value = settingsStore.get(settings.discord.usingText);
controllerType.value = settingsStore.get(settings.advanced.controllerType);
// 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) => {
@@ -277,6 +279,7 @@ window.addEventListener("DOMContentLoaded", () => {
discord_show_idle = get("discord_show_idle"); discord_show_idle = get("discord_show_idle");
discord_using_text = get("discord_using_text"); discord_using_text = get("discord_using_text");
discord_idle_text = get("discord_idle_text"); discord_idle_text = get("discord_idle_text");
controllerType = get<HTMLSelectElement>("controllerType");
refreshSettings(); refreshSettings();
addInputListener(adBlock, settings.adBlock); addInputListener(adBlock, settings.adBlock);
@@ -322,4 +325,5 @@ window.addEventListener("DOMContentLoaded", () => {
addInputListener(discord_show_idle, settings.discord.showIdle); addInputListener(discord_show_idle, settings.discord.showIdle);
addInputListener(discord_idle_text, settings.discord.idleText); addInputListener(discord_idle_text, settings.discord.idleText);
addInputListener(discord_using_text, settings.discord.usingText); addInputListener(discord_using_text, settings.discord.usingText);
addSelectListener(controllerType, settings.advanced.controllerType);
}); });

View File

@@ -1,4 +1,4 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
@@ -7,7 +7,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" />
<link rel="stylesheet" href="./settings.css" /> <link rel="stylesheet" href="./settings.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.0/css/font-awesome.min.css">
</head> </head>
<body class="settings-window"> <body class="settings-window">
@@ -109,7 +109,10 @@
<div class="group__option"> <div class="group__option">
<div class="group__description"> <div class="group__description">
<h4>Static Window Title</h4> <h4>Static Window Title</h4>
<p>Makes the window title "TIDAL Hi-Fi" instead of changing to the currently playing song.</p> <p>
Makes the window title "TIDAL Hi-Fi" instead of changing to the currently
playing song.
</p>
</div> </div>
<label class="switch"> <label class="switch">
<input id="staticWindowTitle" type="checkbox" /> <input id="staticWindowTitle" type="checkbox" />
@@ -157,8 +160,8 @@
<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 media information. TIDAL Hi-Fi has a built-in web API to allow users to get current media
You can optionally enable playback control as well. information. You can optionally enable playback control as well.
</p> </p>
</div> </div>
<div class="group__option"> <div class="group__option">
@@ -180,7 +183,8 @@
<div class="group__option"> <div class="group__option">
<div class="group__description"> <div class="group__description">
<h4>API hostname</h4> <h4>API hostname</h4>
<p>By default (127.0.0.1) only local apps can interface with the API. <br /> <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 /> Change to 0.0.0.0 to allow <strong>anyone</strong> to interact with it. <br />
Other options are available Other options are available
</p> </p>
@@ -236,7 +240,6 @@
</label> </label>
</div> </div>
<div id="discord_options"> <div id="discord_options">
<div class="group__option" class="hidden"> <div class="group__option" class="hidden">
<div class="group__description"> <div class="group__description">
<h4>Show Idle Text</h4> <h4>Show Idle Text</h4>
@@ -259,7 +262,9 @@
<div class="group__option" class="hidden"> <div class="group__option" class="hidden">
<div class="group__description"> <div class="group__description">
<h4>Using Tidal Text</h4> <h4>Using Tidal Text</h4>
<p>The text displayed on Discord's rich presence while "showSong" is turned off</p> <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" /> <input id="discord_using_text" type="text" class="text-input" name="discord_using_text" />
</div> </div>
</div> </div>
@@ -276,7 +281,6 @@
</div> </div>
<div id="discord_show_song_options" class="hidden"> <div id="discord_show_song_options" class="hidden">
<div class="group__option" class="hidden"> <div class="group__option" class="hidden">
<div class="group__description"> <div class="group__description">
<h4>Include timestamps</h4> <h4>Include timestamps</h4>
@@ -304,7 +308,6 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="group"> <div class="group">
@@ -323,7 +326,10 @@
<div class="group__option"> <div class="group__option">
<div class="group__description"> <div class="group__description">
<h4>ListenBrainz API Url</h4> <h4>ListenBrainz API Url</h4>
<p>There are multiple instances for ListenBrainz you can set the corresponding API url below.</p> <p>
There are multiple instances for ListenBrainz you can set the corresponding
API url below.
</p>
<input id="ListenBrainzAPI" type="text" class="text-input" name="ListenBrainzAPI" /> <input id="ListenBrainzAPI" type="text" class="text-input" name="ListenBrainzAPI" />
</div> </div>
</div> </div>
@@ -337,8 +343,10 @@
</div> </div>
<div class="group__description"> <div class="group__description">
<h4>ScrobbleDelay</h4> <h4>ScrobbleDelay</h4>
<p>The delay (in ms) to send a listen to ListenBrainz. Prevents spamming the API when you fast forward <p>
immediately</p> The delay (in ms) to send a listen to ListenBrainz. Prevents spamming the API when
you fast forward 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>
</div> </div>
@@ -351,10 +359,9 @@
<div class="group__description"> <div class="group__description">
<h4>Update frequency</h4> <h4>Update frequency</h4>
<p> <p>
The amount of time, in milliseconds, that TIDAL Hi-Fi will refresh its playback info by scraping the The amount of time, in milliseconds, that TIDAL Hi-Fi will refresh its playback
website. info by scraping the website. The default of 500 seems to work in more cases but
The default of 500 seems to work in more cases but if you are fine with a bit more resource usage you if you are fine with a bit more resource usage you can decrease it as well.
can decrease it as well.
</p> </p>
<input id="updateFrequency" type="number" class="text-input" name="updateFrequency" /> <input id="updateFrequency" type="number" class="text-input" name="updateFrequency" />
</div> </div>
@@ -369,7 +376,19 @@
</p> </p>
<select class="select-input" id="channel" name="channel"> <select class="select-input" id="channel" name="channel">
<option value="https://listen.tidal.com">Stable (listen.tidal.com)</option> <option value="https://listen.tidal.com">Stable (listen.tidal.com)</option>
<option value="https://listen.stage.tidal.com">Staging (listen.stage.tidal.com)</option> <option value="https://listen.stage.tidal.com">
Staging (listen.stage.tidal.com)
</option>
</select>
</div>
</div>
<div class="group__option">
<div class="group__description">
<h4>Controller Type</h4>
<p>Select the type of controller to use.</p>
<select id="controllerType" class="select-input">
<option value="domController">DOM Controller</option>
<option value="tidalApiController">Tidal Api Controller (beta)</option>
</select> </select>
</div> </div>
</div> </div>
@@ -415,7 +434,8 @@
<div class="group__description"> <div class="group__description">
<h4>Wayland support</h4> <h4>Wayland support</h4>
<p> <p>
Adds a couple of Electron flags to help TIDAL Hi-Fi run smoothly on the Wayland window system. Adds a couple of Electron flags to help TIDAL Hi-Fi run smoothly on the Wayland
window system.
</p> </p>
</div> </div>
<label class="switch"> <label class="switch">
@@ -433,7 +453,8 @@
<div class="group__description"> <div class="group__description">
<h4>Custom CSS</h4> <h4>Custom CSS</h4>
<p> <p>
The css that you put in here will be injected into a style tag in the head of the document. The css that you put in here will be injected into a style tag in the head of
the document.
</p> </p>
</div> </div>
</div> </div>
@@ -448,9 +469,7 @@
<p> <p>
Select a theme below or "Tidal - Default" to return to the original Tidal look. Select a theme below or "Tidal - Default" to return to the original Tidal look.
</p> </p>
<select class="select-input" id="themesList" name="themesList"> <select class="select-input" id="themesList" name="themesList"></select>
</select>
</div> </div>
</div> </div>
@@ -458,14 +477,14 @@
<div class="group__description"> <div class="group__description">
<h4>Upload new themes</h4> <h4>Upload new themes</h4>
<p> <p>
Click the button and select the css files to import. They will be added to the theme list Click the button and select the css files to import. They will be added to the
automatically. theme list automatically.
</p> </p>
<div class="file-drop-area"> <div class="file-drop-area">
<div> <div>
<span class="file-btn">Choose files</span> <span class="file-btn">Choose files</span>
<span id="file-message" class="file-msg">or drag and drop files here</span> <span id="file-message" class="file-msg">or drag and drop files here</span>
<input id="theme-files" class="file-input" type="file" accept=".css" multiple> <input id="theme-files" class="file-input" type="file" accept=".css" multiple />
</div> </div>
</div> </div>
</div> </div>
@@ -478,7 +497,7 @@
<h4>TIDAL Hi-Fi</h4> <h4>TIDAL Hi-Fi</h4>
<div class="about-section__version"> <div class="about-section__version">
<a target="_blank" rel="noopener" <a target="_blank" rel="noopener"
href="https://github.com/Mastermindzh/tidal-hifi/releases/tag/5.18.2">5.18.2</a> href="https://github.com/Mastermindzh/tidal-hifi/releases/tag/5.20.1">5.20.1</a>
</div> </div>
<div class="about-section__links"> <div class="about-section__links">
<a target="_blank" rel="noopener" href="https://github.com/mastermindzh/tidal-hifi/" <a target="_blank" rel="noopener" href="https://github.com/mastermindzh/tidal-hifi/"

View File

@@ -1,8 +1,10 @@
import { app, dialog, Notification } from "@electron/remote"; import { app, dialog, Notification } from "@electron/remote";
import { clipboard, ipcRenderer } from "electron"; import { clipboard, ipcRenderer } from "electron";
import Player from "mpris-service"; import Player from "mpris-service";
import { tidalControllers } from "./constants/controller";
import { globalEvents } from "./constants/globalEvents"; import { globalEvents } from "./constants/globalEvents";
import { settings } from "./constants/settings"; import { settings } from "./constants/settings";
import { downloadIcon } from "./features/icon/downloadIcon";
import { import {
ListenBrainz, ListenBrainz,
ListenBrainzConstants, ListenBrainzConstants,
@@ -10,181 +12,57 @@ import {
} from "./features/listenbrainz/listenbrainz"; } from "./features/listenbrainz/listenbrainz";
import { StoreData } from "./features/listenbrainz/models/storeData"; import { StoreData } from "./features/listenbrainz/models/storeData";
import { Logger } from "./features/logger"; import { Logger } from "./features/logger";
import { SharingService } from "./features/sharingService/sharingService";
import { addCustomCss } from "./features/theming/theming"; import { addCustomCss } from "./features/theming/theming";
import { getTrackURL, getUniversalLink } from "./features/tidal/url";
import { convertDurationToSeconds } from "./features/time/parse"; import { convertDurationToSeconds } from "./features/time/parse";
import { MediaInfo } from "./models/mediaInfo"; import { getEmptyMediaInfo, MediaInfo } from "./models/mediaInfo";
import { MediaStatus } from "./models/mediaStatus"; import { MediaStatus } from "./models/mediaStatus";
import { RepeatState } from "./models/repeatState";
import { downloadFile } from "./scripts/download";
import { addHotkey } from "./scripts/hotkeys"; import { addHotkey } from "./scripts/hotkeys";
import { ObjectToDotNotation } from "./scripts/objectUtilities"; import { ObjectToDotNotation } from "./scripts/objectUtilities";
import { settingsStore } from "./scripts/settings"; import { settingsStore } from "./scripts/settings";
import { setTitle } from "./scripts/window-functions"; import { setTitle } from "./scripts/window-functions";
import { TidalApiController } from "./TidalControllers/apiController/TidalApiController";
import { DomControllerOptions } from "./TidalControllers/DomController/DomControllerOptions";
import { DomTidalController } from "./TidalControllers/DomController/DomTidalController";
import { TidalController } from "./TidalControllers/TidalController";
const notificationPath = `${app.getPath("userData")}/notification.jpg`; const notificationPath = `${app.getPath("userData")}/notification.jpg`;
const staticTitle = "TIDAL Hi-Fi";
let currentSong = ""; let currentSong = "";
let player: Player; let player: Player;
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; let currentNotification: Electron.Notification;
const elements = { let tidalController: TidalController;
play: '*[data-test="play"]', let controllerOptions = {};
pause: '*[data-test="pause"]', let currentMediaInfo = getEmptyMediaInfo();
next: '*[data-test="next"]',
previous: 'button[data-test="previous"]',
title: '*[data-test^="footer-track-title"]',
artists: '*[data-test^="grid-item-detail-text-title-artist"]',
home: '*[data-test="menu--home"]',
back: '[title^="Back"]',
forward: '[title^="Next"]',
search: '[class^="searchField"]',
shuffle: '*[data-test="shuffle"]',
repeat: '*[data-test="repeat"]',
account: '*[data-test^="profile-image-button"]',
settings: '*[data-test^="sidebar-menu-button"]',
openSettings: '*[data-test^="open-settings"]',
media: '*[data-test="current-media-imagery"]',
image: "img",
current: '*[data-test="current-time"]',
duration: '*[class^=_playbackControlsContainer] *[data-test="duration"]',
bar: '*[data-test="progress-bar"]',
footer: "#footerPlayer",
mediaItem: "[data-type='mediaItem']",
album_header_title: '*[class^="_playingFrom"] span:nth-child(2)',
playing_from: '*[class^="_playingFrom"] span:nth-child(2)',
queue_album: "*[class^=playQueueItemsContainer] *[class^=groupTitle] span:nth-child(2)",
currentlyPlaying: "[class^='isPlayingIcon'], [data-test-is-playing='true']",
album_name_cell: '[class^="album"]',
tracklist_row: '[data-test="tracklist-row"]',
volume: '*[data-test="volume"]',
favorite: '*[data-test="footer-favorite-button"]',
/**
* Get an element from the dom
* @param {*} key key in elements object to fetch
*/
get: function (key: string) {
return window.document.querySelector(this[key.toLowerCase()]);
},
/** switch (settingsStore.get(settings.advanced.controllerType)) {
* Get the icon of the current media case tidalControllers.tidalApiController: {
*/ tidalController = new TidalApiController();
getSongIcon: function () { Logger.log("TidalApiController initialized");
const figure = this.get("media"); break;
}
if (figure) { default: {
const mediaElement = figure.querySelector(this["image"]); tidalController = new DomTidalController();
if (mediaElement) { const domControllerOptions: DomControllerOptions = {
return mediaElement.src.replace("80x80", "640x640"); refreshInterval: getDomUpdateFrequency(),
} };
} controllerOptions = domControllerOptions;
Logger.log("domController initialized");
return ""; break;
}, }
}
/**
* returns an array of all artists in the current media
* @returns {Array} artists
*/
getArtistsArray: function () {
const footer = this.get("footer");
if (footer) {
const artists = footer.querySelectorAll(this.artists);
if (artists) return Array.from(artists).map((artist) => (artist as HTMLElement).textContent);
}
return [];
},
/**
* unify the artists array into a string separated by commas
* @param {Array} artistsArray
* @returns {String} artists
*/
getArtistsString: function (artistsArray: string[]) {
if (artistsArray.length > 0) return artistsArray.join(", ");
return "unknown artist(s)";
},
getAlbumName: function () {
//If listening to an album, get its name from the header title
if (window.location.href.includes("/album/")) {
const albumName = window.document.querySelector(this.album_header_title);
if (albumName) {
return albumName.textContent;
}
//If listening to a playlist or a mix, get album name from the list
} else if (
window.location.href.includes("/playlist/") ||
window.location.href.includes("/mix/")
) {
if (currentPlayStatus === MediaStatus.playing) {
// find the currently playing element from the list (which might be in an album icon), traverse back up to the mediaItem (row) and select the album cell.
// document.querySelector("[class^='isPlayingIcon'], [data-test-is-playing='true']").closest('[data-type="mediaItem"]').querySelector('[class^="album"]').textContent
const row = window.document.querySelector(this.currentlyPlaying).closest(this.mediaItem);
if (row) {
return row.querySelector(this.album_name_cell).textContent;
}
}
}
// see whether we're on the queue page and get it from there
const queueAlbumName = elements.getText("queue_album");
if (queueAlbumName) {
return queueAlbumName;
}
return "";
},
isMuted: function () {
return this.get("volume").getAttribute("aria-checked") === "false"; // it's muted if aria-checked is false
},
isFavorite: function () {
return this.get("favorite").getAttribute("aria-checked") === "true";
},
/**
* Shorthand function to get the text of a dom element
* @param {*} key key in elements object to fetch
*/
getText: function (key: string) {
const element = this.get(key);
return element ? element.textContent : "";
},
/**
* Shorthand function to click a dom element
* @param {*} key key in elements object to fetch
*/
click: function (key: string) {
this.get(key).click();
return this;
},
/**
* Shorthand function to focus a dom element
* @param {*} key key in elements object to fetch
*/
focus: function (key: string) {
return this.get(key).focus();
},
};
/** /**
* Get the update frequency from the store * Get the update frequency from the store
* make sure it returns a number, if not use the default * make sure it returns a number, if not use the default
*/ */
function getUpdateFrequency() { function getDomUpdateFrequency() {
const storeValue = settingsStore.get<string, number>(settings.updateFrequency); const storeValue = settingsStore.get<string, number>(settings.updateFrequency);
const defaultValue = 500; const defaultValue = 500;
@@ -195,19 +73,6 @@ function getUpdateFrequency() {
} }
} }
/**
* Play or pause the current media
*/
function playPause() {
const play = elements.get("play");
if (play) {
elements.click("play");
} else {
elements.click("pause");
}
}
/** /**
* Clears the old listenbrainz data on launch * Clears the old listenbrainz data on launch
*/ */
@@ -220,30 +85,26 @@ ListenBrainzStore.clear();
*/ */
function addHotKeys() { function addHotKeys() {
if (settingsStore.get(settings.enableCustomHotkeys)) { if (settingsStore.get(settings.enableCustomHotkeys)) {
addHotkey("Control+p", function () { addHotkey("Control+p", () => {
elements.click("settings"); tidalController.openSettings();
setTimeout(() => {
elements.click("openSettings");
}, 100);
}); });
addHotkey("Control+l", function () { addHotkey("Control+l", () => {
handleLogout(); handleLogout();
}); });
addHotkey("Control+a", () => {
addHotkey("Control+a", function () { tidalController.toggleFavorite();
elements.click("favorite");
}); });
addHotkey("Control+h", function () { addHotkey("Control+h", () => {
elements.click("home"); tidalController.goToHome();
}); });
addHotkey("backspace", function () { addHotkey("backspace", function () {
elements.click("back"); tidalController.back();
}); });
addHotkey("shift+backspace", function () { addHotkey("shift+backspace", function () {
elements.click("forward"); tidalController.forward();
}); });
addHotkey("control+u", function () { addHotkey("control+u", function () {
@@ -252,10 +113,10 @@ function addHotKeys() {
}); });
addHotkey("control+r", function () { addHotkey("control+r", function () {
elements.click("repeat"); tidalController.repeat();
}); });
addHotkey("control+w", async function () { addHotkey("control+w", async function () {
const url = SharingService.getUniversalLink(getTrackURL()); const url = getUniversalLink(getTrackURL(tidalController.getTrackId()));
clipboard.writeText(url); clipboard.writeText(url);
new Notification({ new Notification({
title: `Universal link generated: `, title: `Universal link generated: `,
@@ -319,22 +180,22 @@ function addIPCEventListeners() {
case globalEvents.playPause: case globalEvents.playPause:
case globalEvents.play: case globalEvents.play:
case globalEvents.pause: case globalEvents.pause:
playPause(); tidalController.playPause();
break; break;
case globalEvents.next: case globalEvents.next:
elements.click("next"); tidalController.next();
break; break;
case globalEvents.previous: case globalEvents.previous:
elements.click("previous"); tidalController.previous();
break; break;
case globalEvents.toggleFavorite: case globalEvents.toggleFavorite:
elements.click("favorite"); tidalController.toggleFavorite();
break; break;
case globalEvents.toggleShuffle: case globalEvents.toggleShuffle:
elements.click("shuffle"); tidalController.toggleShuffle();
break; break;
case globalEvents.toggleRepeat: case globalEvents.toggleRepeat:
elements.click("repeat"); tidalController.repeat();
break; break;
default: default:
break; break;
@@ -343,48 +204,6 @@ function addIPCEventListeners() {
}); });
} }
/**
* Update the current status of tidal (e.g playing or paused)
*/
function getCurrentlyPlayingStatus() {
const pause = elements.get("pause");
let status = undefined;
// if pause button is visible tidal is playing
if (pause) {
status = MediaStatus.playing;
} else {
status = MediaStatus.paused;
}
return status;
}
function getCurrentShuffleState() {
const shuffle = elements.get("shuffle");
return shuffle?.getAttribute("aria-checked") === "true";
}
function getCurrentRepeatState() {
const repeat = elements.get("repeat");
switch (repeat?.getAttribute("data-type")) {
case "button__repeatAll":
return RepeatState.all;
case "button__repeatSingle":
return RepeatState.single;
default:
return RepeatState.off;
}
}
/**
* Convert the duration from MM:SS to seconds
* @param {*} duration
*/
function convertDuration(duration: string) {
const parts = duration.split(":");
return parseInt(parts[1]) + 60 * parseInt(parts[0]);
}
/** /**
* Update Tidal-hifi's media info * Update Tidal-hifi's media info
* *
@@ -392,7 +211,6 @@ function convertDuration(duration: string) {
*/ */
function updateMediaInfo(mediaInfo: MediaInfo, notify: boolean) { function updateMediaInfo(mediaInfo: MediaInfo, notify: boolean) {
if (mediaInfo) { if (mediaInfo) {
currentMediaInfo = mediaInfo;
ipcRenderer.send(globalEvents.updateInfo, mediaInfo); ipcRenderer.send(globalEvents.updateInfo, mediaInfo);
updateMpris(mediaInfo); updateMpris(mediaInfo);
updateListenBrainz(mediaInfo); updateListenBrainz(mediaInfo);
@@ -455,16 +273,35 @@ function addMPRIS() {
const eventValue = events[eventName]; const eventValue = events[eventName];
switch (events[eventValue]) { switch (events[eventValue]) {
case events.playpause: case events.playpause:
playPause(); tidalController.playPause();
break;
case events.next:
tidalController.next();
break;
case events.previous:
tidalController.previous();
break;
case events.pause:
tidalController.pause();
break;
case events.stop:
tidalController.stop();
break;
case events.play:
tidalController.play();
break;
case events.loopStatus:
tidalController.repeat();
break;
case events.shuffle:
tidalController.toggleShuffle();
break; break;
default:
elements.click(eventValue);
} }
}); });
}); });
// Override get position function // Override get position function
player.getPosition = function () { player.getPosition = function () {
return convertDuration(elements.getText("current")) * 1000 * 1000; return tidalController.getCurrentPositionInSeconds();
}; };
player.on("quit", function () { player.on("quit", function () {
app.quit(); app.quit();
@@ -485,8 +322,8 @@ function updateMpris(mediaInfo: MediaInfo) {
"xesam:album": mediaInfo.album, "xesam:album": mediaInfo.album,
"xesam:url": mediaInfo.url, "xesam:url": mediaInfo.url,
"mpris:artUrl": mediaInfo.image, "mpris:artUrl": mediaInfo.image,
"mpris:length": convertDuration(mediaInfo.duration) * 1000 * 1000, "mpris:length": convertDurationToSeconds(mediaInfo.duration) * 1000 * 1000,
"mpris:trackid": "/org/mpris/MediaPlayer2/track/" + getTrackID(), "mpris:trackid": "/org/mpris/MediaPlayer2/track/" + tidalController.getTrackId(),
}, },
...ObjectToDotNotation(mediaInfo, "custom:"), ...ObjectToDotNotation(mediaInfo, "custom:"),
}; };
@@ -513,124 +350,47 @@ function updateListenBrainz(mediaInfo: MediaInfo) {
mediaInfo.title, mediaInfo.title,
mediaInfo.artists, mediaInfo.artists,
mediaInfo.status, mediaInfo.status,
convertDuration(mediaInfo.duration) convertDurationToSeconds(mediaInfo.duration),
); );
scrobbleWaitingForDelay = false; scrobbleWaitingForDelay = false;
}, },
settingsStore.get(settings.ListenBrainz.delay) ?? 0 settingsStore.get(settings.ListenBrainz.delay) ?? 0,
); );
} }
} }
} }
} }
/** tidalController.bootstrap(controllerOptions);
* Checks if Tidal is playing a video or song by grabbing the "a" element from the title. tidalController.onMediaInfoUpdate(async (newState) => {
* If it's a song it returns the track URL, if not it will return undefined currentMediaInfo = { ...currentMediaInfo, ...newState };
*/
function getTrackURL() {
const id = getTrackID();
return `https://tidal.com/browse/track/${id}`;
}
function getTrackID() { const songDashArtistTitle = `${currentMediaInfo.title} - ${currentMediaInfo.artists}`;
const URLelement = elements.get("title").querySelector("a"); const isNewSong = currentSong !== songDashArtistTitle;
if (URLelement !== null) {
const id = URLelement.href.replace(/\D/g, "");
return id;
}
return window.location; if (isNewSong) {
} // check whether one of the artists is in the "skip artist" array, if so, skip...
skipArtistsIfFoundInSkippedArtistsList(currentMediaInfo.artistsArray ?? []);
/** // update the currently playing song
* Watch for song changes and update title + notify currentSong = songDashArtistTitle;
*/
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; // update the window title with the new info
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) settingsStore.get(settings.staticWindowTitle)
? setTitle(staticTitle) ? setTitle(staticTitle)
: setTitle(songDashArtistTitle); : setTitle(`${currentMediaInfo.title} - ${currentMediaInfo.artists}`);
getTrackURL();
currentSong = songDashArtistTitle;
currentPlayStatus = currentStatus;
const image = elements.getSongIcon(); // download a new icon and use it for the media info
if (!newState.icon && newState.image) {
currentMediaInfo.icon = await downloadIcon(currentMediaInfo.image, notificationPath);
} else {
currentMediaInfo.icon = "";
}
new Promise<void>((resolve) => { updateMediaInfo(currentMediaInfo, true);
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 { } else {
// just update the time // if titleOrArtists didn't change then only minor mediaInfo (like timings) changed, so don't bother the user with notifications
updateMediaInfo( updateMediaInfo(currentMediaInfo, false);
{ ...currentMediaInfo, ...{ current, currentInSeconds: convertDurationToSeconds(current) } },
false
);
} }
/** /**
@@ -645,13 +405,12 @@ setInterval(function () {
const artistNames = Object.values(artists).map((artist) => artist); const artistNames = Object.values(artists).map((artist) => artist);
const foundArtist = artistNames.some((artist) => artistsToSkip.includes(artist)); const foundArtist = artistNames.some((artist) => artistsToSkip.includes(artist));
if (foundArtist) { if (foundArtist) {
elements.click("next"); tidalController.next();
} }
} }
} }
} }
}, getUpdateFrequency()); });
addMPRIS(); addMPRIS();
addCustomCss(app); addCustomCss(app);
addHotKeys(); addHotKeys();

View File

@@ -26,7 +26,7 @@ const defaultPresence = {
largeImageKey: "tidal-hifi-icon", largeImageKey: "tidal-hifi-icon",
largeImageText: `TIDAL Hi-Fi ${app.getVersion()}`, largeImageText: `TIDAL Hi-Fi ${app.getVersion()}`,
instance: false, instance: false,
type: ACTIVITY_LISTENING type: ACTIVITY_LISTENING,
}; };
const updateActivity = () => { const updateActivity = () => {
@@ -117,15 +117,17 @@ const getActivity = (): SetActivity => {
const connectWithRetry = async (retryCount = 0) => { const connectWithRetry = async (retryCount = 0) => {
try { try {
await rpc.login(); await rpc.login();
Logger.log('Connected to Discord'); Logger.log("Connected to Discord");
rpc.on("ready", updateActivity); rpc.on("ready", updateActivity);
Object.values(globalEvents).forEach(event => ipcMain.on(event, observer)); Object.values(globalEvents).forEach((event) => ipcMain.on(event, observer));
} catch (error) { } catch (error) {
if (retryCount < MAX_RETRIES) { if (retryCount < MAX_RETRIES) {
Logger.log(`Failed to connect to Discord, retrying in ${RETRY_DELAY/1000} seconds... (Attempt ${retryCount + 1}/${MAX_RETRIES})`); Logger.log(
`Failed to connect to Discord, retrying in ${RETRY_DELAY / 1000} seconds... (Attempt ${retryCount + 1}/${MAX_RETRIES})`
);
setTimeout(() => connectWithRetry(retryCount + 1), RETRY_DELAY); setTimeout(() => connectWithRetry(retryCount + 1), RETRY_DELAY);
} else { } else {
Logger.log('Failed to connect to Discord after maximum retry attempts'); Logger.log("Failed to connect to Discord after maximum retry attempts");
} }
} }
}; };
@@ -134,7 +136,7 @@ const connectWithRetry = async (retryCount = 0) => {
* Set up the discord rpc and listen on globalEvents.updateInfo * Set up the discord rpc and listen on globalEvents.updateInfo
*/ */
export const initRPC = () => { export const initRPC = () => {
rpc = new Client({ transport: {type: "ipc"}, clientId }); rpc = new Client({ transport: { type: "ipc" }, clientId });
connectWithRetry(); connectWithRetry();
}; };

View File

@@ -6,8 +6,8 @@ import request from "request";
* @param {string} fileUrl url to download * @param {string} fileUrl url to download
* @param {string} targetPath path to save it at * @param {string} targetPath path to save it at
*/ */
export const downloadFile = function (fileUrl: string, targetPath: string) { export const downloadFile = function (fileUrl: string, targetPath: string): Promise<string> {
return new Promise((resolve, reject) => { return new Promise<string>((resolve, reject) => {
const req = request({ const req = request({
method: "GET", method: "GET",
uri: fileUrl, uri: fileUrl,
@@ -16,7 +16,9 @@ export const downloadFile = function (fileUrl: string, targetPath: string) {
const out = fs.createWriteStream(targetPath); const out = fs.createWriteStream(targetPath);
req.pipe(out); req.pipe(out);
req.on("end", resolve); req.on("end", () => {
resolve(targetPath);
});
req.on("error", reject); req.on("error", reject);
}); });

View File

@@ -1,28 +1,6 @@
import { MediaInfo } from "../models/mediaInfo"; import { getEmptyMediaInfo, MediaInfo } from "../models/mediaInfo";
import { MediaStatus } from "../models/mediaStatus";
import { RepeatState } from "../models/repeatState";
const defaultInfo: MediaInfo = { const defaultInfo: MediaInfo = getEmptyMediaInfo();
title: "",
artists: "",
album: "",
icon: "",
playingFrom: "",
status: MediaStatus.paused,
url: "",
current: "",
currentInSeconds: 0,
duration: "",
durationInSeconds: 0,
image: "tidal-hifi-icon",
favorite: false,
player: {
status: MediaStatus.paused,
shuffle: false,
repeat: RepeatState.off,
},
};
export let mediaInfo: MediaInfo = { ...defaultInfo }; export let mediaInfo: MediaInfo = { ...defaultInfo };

View File

@@ -30,6 +30,7 @@ export const settingsStore = new Store({
adBlock: false, adBlock: false,
advanced: { advanced: {
tidalUrl: "https://listen.tidal.com", tidalUrl: "https://listen.tidal.com",
controllerType: "domController",
}, },
api: true, api: true,
apiSettings: { apiSettings: {
@@ -120,6 +121,11 @@ export const settingsStore = new Store({
"5.16.0": (migrationStore) => { "5.16.0": (migrationStore) => {
buildMigration("5.16.0", migrationStore, [{ key: settings.discord.showIdle, value: "true" }]); buildMigration("5.16.0", migrationStore, [{ key: settings.discord.showIdle, value: "true" }]);
}, },
"5.19.0": (migrationStore) => {
buildMigration("5.19.0", migrationStore, [
{ key: settings.advanced.controllerType, value: "domController" },
]);
},
}, },
}); });