Compare commits

...

65 Commits

Author SHA1 Message Date
46074c5de5 Merge pull request #407 from Mastermindzh/new-version
5.13.0
2024-05-20 16:01:28 +02:00
2667f62674 chore: changelog update 2024-05-20 15:48:46 +02:00
ac949dc211 Merge pull request #401 from Mjokfox/api/add_cors
Add cors to the express api
2024-05-20 15:47:56 +02:00
40bc20582f Merge branch 'new-version' of github.com:Mastermindzh/tidal-hifi into api/add_cors 2024-05-20 15:46:02 +02:00
mjokfox
180d9c97a7 Add the cors module and use it with express api 2024-05-20 15:42:49 +02:00
5dc136138b fix: possible type confusion 2024-05-20 15:34:38 +02:00
1edc6a1b2b chore: versioning 2024-05-20 15:24:15 +02:00
7c6831c771 added swagger docs 2024-05-20 15:23:26 +02:00
d47da91e93 Added an API to add & delete entries from the skippedArtists list in the settings. fixes [#405] 2024-05-20 14:24:47 +02:00
b481108af1 fix: fixes #403 - cannot read shuffle of undefined error 2024-05-20 12:18:52 +02:00
3740ce5a12 Merge pull request #402 from Mastermindzh/5.12
5.12
2024-05-14 23:08:55 +02:00
a0f9faa753 chore: updating versions 2024-05-14 22:53:07 +02:00
5e3583534b Merge pull request #396 from ThatGravyBoat/api/shuffle-repeat-state
Api Feature: Add shuffle and repeat state to API
2024-05-14 22:50:59 +02:00
ThatGravyBoat
5f8cf33249 Fix mismatched import styling 2024-05-06 03:55:09 -02:30
ThatGravyBoat
2d94b4bf49 Add shuffle and repeat to current state api 2024-05-06 03:50:41 -02:30
6e43cbb4d7 Merge pull request #395 from Mastermindzh/next-version
Next version
2024-05-05 20:46:21 +02:00
f95f13b44a chore: doc fix 2024-05-05 20:35:58 +02:00
f911564d8a chore: version increase 2024-05-05 20:34:58 +02:00
db8a2c2741 feat: reworked the api, added duration/current in seconds + shuffle & repeat 2024-05-05 20:12:10 +02:00
000853414e feat: switched to TIDAL's universal link format in the entire app 2024-05-05 15:09:54 +02:00
53603c4cad Merge pull request #392 from TheRockYT/MediaSessionService
Media session service
2024-05-05 14:25:02 +02:00
0b595f920f Merge pull request #391 from Mastermindzh/dependabot/npm_and_yarn/ejs-3.1.10
chore(deps-dev): bump ejs from 3.1.9 to 3.1.10
2024-05-05 13:57:59 +02:00
81143af3fa Merge pull request #394 from Mastermindzh/snyk-upgrade-0d65fc86b872e70883e2ebe344b05405
[Snyk] Upgrade sass from 1.74.1 to 1.75.0
2024-05-05 13:57:41 +02:00
snyk-bot
8d1ac3be3b fix: upgrade sass from 1.74.1 to 1.75.0
Snyk has created this PR to upgrade sass from 1.74.1 to 1.75.0.

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

See this project in Snyk:
https://app.snyk.io/org/mastermindzh/project/dade8f03-2064-49a3-8957-edbacec3887c?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-05-03 22:13:17 +00:00
dependabot[bot]
666e602c02 chore(deps-dev): bump ejs from 3.1.9 to 3.1.10
Bumps [ejs](https://github.com/mde/ejs) from 3.1.9 to 3.1.10.
- [Release notes](https://github.com/mde/ejs/releases)
- [Commits](https://github.com/mde/ejs/compare/v3.1.9...v3.1.10)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-02 10:01:32 +00:00
TheRockYT
04ec850005 Merge branch 'Mastermindzh:master' into MediaSessionService 2024-05-01 20:55:39 +02:00
943d9b5bd8 Merge pull request #386 from Mastermindzh/snyk-upgrade-2399381ec4ed8a254535fddca6093bb9
[Snyk] Upgrade sass from 1.72.0 to 1.74.1
2024-04-25 10:26:22 +02:00
snyk-bot
755816c2b8 fix: upgrade sass from 1.72.0 to 1.74.1
Snyk has created this PR to upgrade sass from 1.72.0 to 1.74.1.

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

See this project in Snyk:
https://app.snyk.io/org/mastermindzh/project/dade8f03-2064-49a3-8957-edbacec3887c?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-04-25 04:29:39 +00:00
25afd05ad7 Merge pull request #374 from TheRockYT/custom_protocol_fix
Custom protocol fix (tidal://...)
2024-04-22 11:23:47 +02:00
6e5024742a Merge pull request #382 from Mastermindzh/snyk-upgrade-e8583a1804fc58a60a199bdcdfddb237
[Snyk] Upgrade sass from 1.71.1 to 1.72.0
2024-04-18 12:34:41 +02:00
417afaab85 Merge pull request #381 from Mastermindzh/snyk-upgrade-eddeada86cf707578042602e7a9acfd4
[Snyk] Upgrade electron-store from 8.1.0 to 8.2.0
2024-04-18 12:34:27 +02:00
d225c0056b Merge pull request #380 from Mastermindzh/snyk-upgrade-636acae2850bc80d0d46d524748be780
[Snyk] Upgrade axios from 1.6.5 to 1.6.8
2024-04-18 12:34:18 +02:00
snyk-bot
a75b0336db fix: upgrade sass from 1.71.1 to 1.72.0
Snyk has created this PR to upgrade sass from 1.71.1 to 1.72.0.

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

See this project in Snyk:
https://app.snyk.io/org/mastermindzh/project/dade8f03-2064-49a3-8957-edbacec3887c?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-04-16 20:25:03 +00:00
snyk-bot
29465ce13a fix: upgrade electron-store from 8.1.0 to 8.2.0
Snyk has created this PR to upgrade electron-store from 8.1.0 to 8.2.0.

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

See this project in Snyk:
https://app.snyk.io/org/mastermindzh/project/dade8f03-2064-49a3-8957-edbacec3887c?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-04-16 20:25:00 +00:00
snyk-bot
d333047269 fix: upgrade axios from 1.6.5 to 1.6.8
Snyk has created this PR to upgrade axios from 1.6.5 to 1.6.8.

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

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

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

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

See this project in Snyk:
https://app.snyk.io/org/mastermindzh/project/dade8f03-2064-49a3-8957-edbacec3887c?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-03-21 17:32:39 +00:00
dependabot[bot]
2d9f268866 chore(deps): bump follow-redirects from 1.15.4 to 1.15.6
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.4 to 1.15.6.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.4...v1.15.6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-16 22:30:30 +00:00
ae65e57e32 Merge pull request #367 from Mastermindzh/snyk-upgrade-0829d5b61286f530a986191886019e0f
[Snyk] Upgrade sass from 1.71.0 to 1.71.1
2024-03-14 16:08:02 +01:00
snyk-bot
3f2d69f2f4 fix: upgrade sass from 1.71.0 to 1.71.1
Snyk has created this PR to upgrade sass from 1.71.0 to 1.71.1.

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

See this project in Snyk:
https://app.snyk.io/org/mastermindzh/project/dade8f03-2064-49a3-8957-edbacec3887c?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-03-14 01:58:53 +00:00
5ff2cc68d3 Merge pull request #359 from Mastermindzh/snyk-upgrade-f5ef319e06f8e24c1358e777b2415e49
[Snyk] Upgrade hotkeys-js from 3.13.6 to 3.13.7
2024-03-12 09:21:24 +01:00
daabe5bdbb Merge pull request #363 from Mastermindzh/snyk-upgrade-63565ec96f33448467289d767c1fb965
[Snyk] Upgrade sass from 1.70.0 to 1.71.0
2024-03-12 09:20:35 +01:00
Lennart
456727c0e0 feature: Track current notification to replace it on track change 2024-03-09 17:49:18 +01:00
snyk-bot
ba50e0c095 fix: upgrade sass from 1.70.0 to 1.71.0
Snyk has created this PR to upgrade sass from 1.70.0 to 1.71.0.

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

See this project in Snyk:
https://app.snyk.io/org/mastermindzh/project/dade8f03-2064-49a3-8957-edbacec3887c?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-03-08 19:44:36 +00:00
snyk-bot
312e90e8cb fix: upgrade hotkeys-js from 3.13.6 to 3.13.7
Snyk has created this PR to upgrade hotkeys-js from 3.13.6 to 3.13.7.

See this package in npm:
https://www.npmjs.com/package/hotkeys-js

See this project in Snyk:
https://app.snyk.io/org/mastermindzh/project/dade8f03-2064-49a3-8957-edbacec3887c?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-03-01 19:11:09 +00:00
76769dfab3 Merge pull request #354 from Mastermindzh/snyk-upgrade-d64f8964b9d005d97354597114dfafa3
[Snyk] Upgrade hotkeys-js from 3.13.5 to 3.13.6
2024-02-29 09:11:22 +01:00
snyk-bot
565d32ae3d fix: upgrade hotkeys-js from 3.13.5 to 3.13.6
Snyk has created this PR to upgrade hotkeys-js from 3.13.5 to 3.13.6.

See this package in npm:
https://www.npmjs.com/package/hotkeys-js

See this project in Snyk:
https://app.snyk.io/org/mastermindzh/project/dade8f03-2064-49a3-8957-edbacec3887c?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-02-24 02:43:08 +00:00
7be6f79040 Merge pull request #349 from Mastermindzh/snyk-upgrade-f0f8b19cd2b7f7662ced7cf1b914e52a
[Snyk] Upgrade @electron/remote from 2.1.1 to 2.1.2
2024-02-16 13:36:45 +01:00
snyk-bot
f894c82b12 fix: upgrade @electron/remote from 2.1.1 to 2.1.2
Snyk has created this PR to upgrade @electron/remote from 2.1.1 to 2.1.2.

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

See this project in Snyk:
https://app.snyk.io/org/mastermindzh/project/dade8f03-2064-49a3-8957-edbacec3887c?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-02-16 02:47:27 +00:00
40 changed files with 1393 additions and 324 deletions

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,6 +4,52 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [5.13.0]
- Fixed [#403](https://github.com/Mastermindzh/tidal-hifi/issues/403) "cannot read shuffle of undefined" error
- Added an API to add & delete entries from the skippedArtists list in the settings. fixes [#405](https://github.com/Mastermindzh/tidal-hifi/issues/405)
- `GET /settings/skipped-artists` -> get list of skipped artists
- `POST /settings/skipped-artists` -> add to the list of skipped artists
- `POST /settings/skipped-artists/delete` -> delete from the list of skipped artists
- `POST /settings/skipped-artists/current` -> skip the current artist
- `DELETE /settings/skipped-artists/current` -> delete the current artist from the skip list
- Added Swagger documentation to the new endpoints:
![picture of swagger documentation](./docs/images/swagger.png)
- CORS support added by [Mjokfox](https://github.com/Mjokfox)
## [5.12.0]
- Added Shuffle and Repeat state to API response - By [ThatGravyBoat](https://github.com/ThatGravyBoat)
## [5.11.0]
- Re-implemented the API, added support for duration/current in seconds & shuffle+repeat
- made the original API "legacy" (still works the same)
- Now using the correct HTTP verb for all new endpoints
- Implemented TIDAL's universal links. All links are now universal.
- Custom `tidal://` protocol fixed - By [TheRockYT](https://github.com/TheRockYT)
- Global media shortcuts removed since TIDAL includes them by default - By [TheRockYT](https://github.com/TheRockYT)
- Fixes
- [#390](https://github.com/Mastermindzh/tidal-hifi/issues/390)
- [#376](https://github.com/Mastermindzh/tidal-hifi/issues/376)
- [#383](https://github.com/Mastermindzh/tidal-hifi/issues/383)
- [#393](https://github.com/Mastermindzh/tidal-hifi/issues/393)
## [5.10.0]
- TIDAL will now close the previous notification if a new one is sent whilst the old is still visible. [#364](https://github.com/Mastermindzh/tidal-hifi/pull/364)
- Updated developer documentation to get started in README [#365](https://github.com/Mastermindzh/tidal-hifi/pull/365)
- Links in the about window now open in the user's default browser. fixes [#360](https://github.com/Mastermindzh/tidal-hifi/issues/360)
- Refactored "nowPlaying" code to always display the current state, even when the built-in UI is updated.
- fixes [#351](https://github.com/Mastermindzh/tidal-hifi/issues/351)
- fixes [#356](https://github.com/Mastermindzh/tidal-hifi/issues/356)
- fixes [#370](https://github.com/Mastermindzh/tidal-hifi/issues/370)
- Reverted to using old icon syntax with icons in the build directory. fixes [#350](https://github.com/Mastermindzh/tidal-hifi/issues/350)
- Enabled wayland platform flags by default when launching through .desktop file
- fixes [#273](https://github.com/Mastermindzh/tidal-hifi/issues/273)
- fixes [#347](https://github.com/Mastermindzh/tidal-hifi/issues/347)
## [5.9.0]
- More Discord options:

View File

@@ -43,7 +43,7 @@ The web version of [listen.tidal.com](https://listen.tidal.com) running in elect
- Custom hotkeys ([source](https://defkey.com/tidal-desktop-shortcuts))
- Better icons thanks to [Papirus-icon-theme](https://github.com/PapirusDevelopmentTeam/papirus-icon-theme/)
- [Settings feature](./docs/images/settings.png) to disable certain functionality. (`ctrl+=` or `ctrl+0`)
- API for status and playback
- API for status, playback and settings (see the [/docs](http://localhost:47836/docs/) route)
- Disabled audio & visual ads, unlocked lyrics, suggested track, track info, and unlimited skips thanks to uBlockOrigin custom filters ([source](https://github.com/uBlockOrigin/uAssets/issues/17495))
- AlbumArt in integrations ([best-effort](https://github.com/Mastermindzh/tidal-hifi/pull/88#pullrequestreview-840814847))
- Custom [integrations](#integrations)
@@ -130,10 +130,13 @@ nix-env -iA nixpkgs.tidal-hifi
To install and work with the code on this project follow these steps:
- git clone [https://github.com/Mastermindzh/tidal-hifi.git](https://github.com/Mastermindzh/tidal-hifi.git)
- cd TIDAL Hi-Fi
- npm install
- npm start
- `git clone [https://github.com/Mastermindzh/tidal-hifi.git](https://github.com/Mastermindzh/tidal-hifi.git)`
- `cd tidal-hifi`
- `npm install`
- `npm run watch` to watch for auto-reload of Typescript/SCSS changes.
- `npm run compile` can be used to trigger it once
- `npm watchStart` to auto watch for any updates files and reload Tidal Hi-Fi
- `npm start` can be used to run Tidal Hi-Fi manually once
## Integrations

View File

@@ -11,10 +11,16 @@ extraResources:
- "themes/**"
linux:
category: AudioVideo
icon: build/icons/256x256.png
icon: build/icons
target:
- dir
executableName: tidal-hifi
executableArgs:
[
"--enable-features=UseOzonePlatform",
"--ozone-platform-hint=auto",
"--enable-features=WaylandWindowDecorations",
]
desktop:
Encoding: UTF-8
Name: TIDAL Hi-Fi

BIN
build/icons/128x128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

BIN
build/icons/16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
build/icons/22x22.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
build/icons/24x24.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

BIN
build/icons/32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
build/icons/384x384.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
build/icons/48x48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
build/icons/64x64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
docs/images/swagger.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

932
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "tidal-hifi",
"version": "5.9.0",
"version": "5.13.0",
"description": "Tidal on Electron with widevine(hifi) support",
"main": "ts-dist/main.js",
"scripts": {
@@ -39,22 +39,28 @@
"homepage": "https://github.com/Mastermindzh/tidal-hifi",
"license": "MIT",
"dependencies": {
"@electron/remote": "^2.1.1",
"axios": "^1.6.5",
"@electron/remote": "^2.1.2",
"@types/swagger-jsdoc": "^6.0.4",
"axios": "^1.6.8",
"cors": "^2.8.5",
"discord-rpc": "^4.0.1",
"electron-store": "^8.1.0",
"express": "^4.18.2",
"hotkeys-js": "^3.13.5",
"electron-store": "^8.2.0",
"express": "^4.19.2",
"hotkeys-js": "^3.13.7",
"mpris-service": "^2.1.2",
"request": "^2.88.2",
"sass": "^1.70.0"
"sass": "^1.75.0",
"swagger-jsdoc": "^6.2.8",
"swagger-ui-express": "^5.0.0"
},
"devDependencies": {
"@mastermindzh/prettier-config": "^1.0.0",
"@types/cors": "^2.8.17",
"@types/discord-rpc": "^4.0.8",
"@types/express": "^4.17.21",
"@types/node": "^20.10.6",
"@types/request": "^2.48.12",
"@types/swagger-ui-express": "^4.1.6",
"@typescript-eslint/eslint-plugin": "^6.18.0",
"@typescript-eslint/parser": "^6.18.0",
"copyfiles": "^2.4.1",
@@ -73,4 +79,4 @@
"typescript": "^5.3.3"
},
"prettier": "@mastermindzh/prettier-config"
}
}

View File

@@ -13,4 +13,6 @@ export const globalEvents = {
whip: "whip",
log: "log",
toggleFavorite: "toggleFavorite",
toggleShuffle: "toggleShuffle",
toggleRepeat: "toggleRepeat",
};

View File

@@ -0,0 +1,20 @@
import { Request, Response, Router } from "express";
import fs from "fs";
import { mediaInfo } from "../../../scripts/mediaInfo";
export const addCurrentInfo = (expressApp: Router) => {
expressApp.get("/current", (req, res) => res.json({ ...mediaInfo, artist: mediaInfo.artists }));
expressApp.get("/current/image", getCurrentImage);
};
export const getCurrentImage = (req: Request, res: Response) => {
const stream = fs.createReadStream(mediaInfo.icon);
stream.on("open", function () {
res.set("Content-Type", "image/png");
stream.pipe(res);
});
stream.on("error", function () {
res.set("Content-Type", "text/plain");
res.status(404).end("Not found");
});
};

View File

@@ -0,0 +1,36 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { BrowserWindow } from "electron";
import { Router } from "express";
import { globalEvents } from "../../../constants/globalEvents";
import { settings } from "../../../constants/settings";
import { MediaStatus } from "../../../models/mediaStatus";
import { mediaInfo } from "../../../scripts/mediaInfo";
import { settingsStore } from "../../../scripts/settings";
import { handleWindowEvent } from "../helpers/handleWindowEvent";
export const addPlaybackControl = (expressApp: Router, mainWindow: BrowserWindow) => {
const windowEvent = handleWindowEvent(mainWindow);
const createRoute = (route: string) => `/player${route}`;
const createPlayerAction = (route: string, action: string) => {
expressApp.post(createRoute(route), (req, res) => windowEvent(res, action));
};
if (settingsStore.get(settings.playBackControl)) {
createPlayerAction("/play", globalEvents.play);
createPlayerAction("/favorite/toggle", globalEvents.toggleFavorite);
createPlayerAction("/pause", globalEvents.pause);
createPlayerAction("/next", globalEvents.next);
createPlayerAction("/previous", globalEvents.previous);
createPlayerAction("/shuffle/toggle", globalEvents.toggleShuffle);
createPlayerAction("/repeat/toggle", globalEvents.toggleRepeat);
expressApp.post(createRoute("/playpause"), (req, res) => {
if (mediaInfo.status === MediaStatus.playing) {
windowEvent(res, globalEvents.pause);
} else {
windowEvent(res, globalEvents.play);
}
});
}
};

View File

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

View File

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

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

@@ -0,0 +1,69 @@
import { BrowserWindow, dialog } from "electron";
import express from "express";
import swaggerjsdoc from "swagger-jsdoc";
import swaggerUi from "swagger-ui-express";
import { settingsStore } from "../../scripts/settings";
import { settings } from "./../../constants/settings";
import { addCurrentInfo } from "./features/current";
import { addPlaybackControl } from "./features/player";
import { addSettingsAPI } from "./features/settings/settings";
import { addLegacyApi } from "./legacy";
import cors from "cors";
/**
* Function to enable TIDAL Hi-Fi's express api
*/
export const startApi = (mainWindow: BrowserWindow) => {
const port = settingsStore.get<string, number>(settings.apiSettings.port);
const specs = swaggerjsdoc({
definition: {
openapi: "3.1.0",
info: {
title: "TIDAL Hi-Fi API",
version: "5.13.0",
description: "",
license: {
name: "MIT",
url: "https://github.com/Mastermindzh/tidal-hifi/blob/master/LICENSE",
},
contact: {
name: "Rick <mastermindzh> van Lieshout",
url: "https://www.rickvanlieshout.com",
},
},
servers: [
{
url: `http://localhost:${port}`,
},
],
externalDocs: {
description: "swagger.json",
url: "swagger.json",
},
},
apis: ["**/*.ts"],
});
const expressApp = express();
expressApp.use(cors());
expressApp.use(express.json());
expressApp.use("/docs", swaggerUi.serve, swaggerUi.setup(specs));
expressApp.get("/", (req, res) => res.send("Hello World!"));
expressApp.get("/swagger.json", (req, res) => res.json(specs));
// add features
addLegacyApi(expressApp, mainWindow);
addPlaybackControl(expressApp, mainWindow);
addCurrentInfo(expressApp);
addSettingsAPI(expressApp, mainWindow);
const expressInstance = expressApp.listen(port, "127.0.0.1");
expressInstance.on("error", function (e: { code: string }) {
let message = e.code;
if (e.code === "EADDRINUSE") {
message = `Port ${port} in use.`;
}
dialog.showErrorBox("Api failed to start.", message);
});
};

View File

@@ -0,0 +1,47 @@
import { BrowserWindow } from "electron";
import { Response, Router } from "express";
import { globalEvents } from "../../constants/globalEvents";
import { settings } from "../../constants/settings";
import { MediaStatus } from "../../models/mediaStatus";
import { mediaInfo } from "../../scripts/mediaInfo";
import { settingsStore } from "../../scripts/settings";
import { getCurrentImage } from "./features/current";
/**
* The legacy API, this will not be maintained and probably has duplicate code :)
* @param expressApp
* @param mainWindow
*/
export const addLegacyApi = (expressApp: Router, mainWindow: BrowserWindow) => {
expressApp.get("/image", getCurrentImage);
if (settingsStore.get(settings.playBackControl)) {
addLegacyControls();
}
function addLegacyControls() {
expressApp.get("/play", ({ res }) => handleGlobalEvent(res, globalEvents.play));
expressApp.post("/favorite/toggle", (req, res) =>
handleGlobalEvent(res, globalEvents.toggleFavorite)
);
expressApp.get("/pause", (req, res) => handleGlobalEvent(res, globalEvents.pause));
expressApp.get("/next", (req, res) => handleGlobalEvent(res, globalEvents.next));
expressApp.get("/previous", (req, res) => handleGlobalEvent(res, globalEvents.previous));
expressApp.get("/playpause", (req, res) => {
if (mediaInfo.status === MediaStatus.playing) {
handleGlobalEvent(res, globalEvents.pause);
} else {
handleGlobalEvent(res, globalEvents.play);
}
});
}
/**
* Shorthand to handle a fire and forget global event
* @param {*} res
* @param {*} action
*/
function handleGlobalEvent(res: Response, action: string) {
mainWindow.webContents.send("globalEvent", action);
res.sendStatus(200);
}
};

View File

@@ -9,7 +9,6 @@ import { Logger } from "../logger";
*/
export function setDefaultFlags(app: App) {
setFlag(app, "disable-seccomp-filter-sandbox");
setFlag(app, "disable-features", "MediaSessionService");
}
/**

View File

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

View File

@@ -1,17 +1,9 @@
import { enable, initialize } from "@electron/remote/main";
import {
app,
BrowserWindow,
components,
globalShortcut,
ipcMain,
protocol,
session,
} from "electron";
import { BrowserWindow, app, components, ipcMain, session } from "electron";
import path from "path";
import { globalEvents } from "./constants/globalEvents";
import { mediaKeys } from "./constants/mediaKeys";
import { settings } from "./constants/settings";
import { startApi } from "./features/api";
import { setDefaultFlags, setManagedFlagsFromSettings } from "./features/flags/flags";
import {
acquireInhibitorIfInactive,
@@ -22,7 +14,6 @@ import { Songwhip } from "./features/songwhip/songwhip";
import { MediaInfo } from "./models/mediaInfo";
import { MediaStatus } from "./models/mediaStatus";
import { initRPC, rpc, unRPC } from "./scripts/discord";
import { startExpress } from "./scripts/express";
import { updateMediaInfo } from "./scripts/mediaInfo";
import { addMenu } from "./scripts/menu";
import {
@@ -61,20 +52,31 @@ function syncMenuBarWithStore() {
}
/**
* Determine whether the current window is the main window
* if singleInstance is requested.
* If singleInstance isn't requested simply return true
* @returns true if singInstance is not requested, otherwise true/false based on whether the current window is the main window
* @returns true/false based on whether the current window is the main window
*/
function isMainInstanceOrMultipleInstancesAllowed() {
if (settingsStore.get(settings.singleInstance)) {
const gotTheLock = app.requestSingleInstanceLock();
function isMainInstance() {
return app.requestSingleInstanceLock();
}
if (!gotTheLock) {
return false;
}
/**
* @returns true/false based on whether multiple instances are allowed
*/
function isMultipleInstancesAllowed() {
return !settingsStore.get(settings.singleInstance);
}
/**
* @param args the arguments passed to the app
* @returns the custom protocol url if it exists, otherwise null
*/
function getCustomProtocolUrl(args: string[]) {
const customProtocolArg = args.find((arg) => arg.startsWith(PROTOCOL_PREFIX));
if (!customProtocolArg) {
return null;
}
return true;
return tidalUrl + "/" + customProtocolArg.substring(PROTOCOL_PREFIX.length + 3);
}
function createWindow(options = { x: 0, y: 0, backgroundColor: "white" }) {
@@ -98,8 +100,16 @@ function createWindow(options = { x: 0, y: 0, backgroundColor: "white" }) {
registerHttpProtocols();
syncMenuBarWithStore();
// load the Tidal website
mainWindow.loadURL(tidalUrl);
// find the custom protocol argument
const customProtocolUrl = getCustomProtocolUrl(process.argv);
if (customProtocolUrl) {
// load the url received from the custom protocol
mainWindow.loadURL(customProtocolUrl);
} else {
// load the Tidal website
mainWindow.loadURL(tidalUrl);
}
if (settingsStore.get(settings.disableBackgroundThrottle)) {
// prevent setInterval lag
@@ -139,27 +149,32 @@ function createWindow(options = { x: 0, y: 0, backgroundColor: "white" }) {
}
function registerHttpProtocols() {
protocol.registerHttpProtocol(PROTOCOL_PREFIX, (request) => {
mainWindow.loadURL(`${tidalUrl}/${request.url.substring(PROTOCOL_PREFIX.length + 3)}`);
});
if (!app.isDefaultProtocolClient(PROTOCOL_PREFIX)) {
app.setAsDefaultProtocolClient(PROTOCOL_PREFIX);
}
}
function addGlobalShortcuts() {
Object.keys(mediaKeys).forEach((key) => {
globalShortcut.register(`${key}`, () => {
mainWindow.webContents.send("globalEvent", `${(mediaKeys as any)[key]}`);
});
});
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on("ready", async () => {
if (isMainInstanceOrMultipleInstancesAllowed()) {
// check if the app is the main instance and multiple instances are not allowed
if (isMainInstance() && !isMultipleInstancesAllowed()) {
app.on("second-instance", (_, commandLine) => {
const customProtocolUrl = getCustomProtocolUrl(commandLine);
if (customProtocolUrl) {
mainWindow.loadURL(customProtocolUrl);
}
if (mainWindow) {
if (mainWindow.isMinimized()) mainWindow.restore();
mainWindow.focus();
}
});
}
if (isMainInstance() || isMultipleInstancesAllowed()) {
await components.whenReady();
// Adblock
@@ -174,12 +189,11 @@ app.on("ready", async () => {
createWindow();
addMenu(mainWindow);
createSettingsWindow();
addGlobalShortcuts();
if (settingsStore.get(settings.trayIcon)) {
addTray(mainWindow, { icon });
refreshTray(mainWindow);
}
settingsStore.get(settings.api) && startExpress(mainWindow);
settingsStore.get(settings.api) && startApi(mainWindow);
settingsStore.get(settings.enableDiscord) && initRPC();
} else {
app.quit();

View File

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

View File

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

View File

@@ -5,7 +5,9 @@ export interface Options {
status: string;
url: string;
current: string;
currentInSeconds: number;
duration: string;
durationInSeconds: number;
"app-name": string;
image: string;
icon: string;

View File

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

View File

@@ -432,14 +432,16 @@
<img alt="tidal icon" class="about-section__icon" src="./icon.png" />
<h4>TIDAL Hi-Fi</h4>
<div class="about-section__version">
<a href="https://github.com/Mastermindzh/tidal-hifi/releases/tag/5.9.0">5.9.0</a>
<a target="_blank" rel="noopener"
href="https://github.com/Mastermindzh/tidal-hifi/releases/tag/5.13.0">5.13.0</a>
</div>
<div class="about-section__links">
<a href="https://github.com/mastermindzh/tidal-hifi/" class="about-section__button">Github <i
class="fa fa-external-link"></i></a>
<a href="https://github.com/Mastermindzh/tidal-hifi/issues" class="about-section__button">Report an issue <i
class="fa fa-external-link"></i></a>
<a href="https://github.com/Mastermindzh/tidal-hifi/graphs/contributors"
<a target="_blank" rel="noopener" href="https://github.com/mastermindzh/tidal-hifi/"
class="about-section__button">Github
<i class="fa fa-external-link"></i></a>
<a target="_blank" rel="noopener" href="https://github.com/Mastermindzh/tidal-hifi/issues"
class="about-section__button">Report an issue <i class="fa fa-external-link"></i></a>
<a target="_blank" rel="noopener" href="https://github.com/Mastermindzh/tidal-hifi/graphs/contributors"
class="about-section__button">Contributors <i class="fa fa-external-link"></i></a>
</div>
</section>

View File

@@ -415,7 +415,6 @@ html {
}
// file upload
.file-drop-area {
position: relative;
display: flex;

View File

@@ -12,12 +12,14 @@ import { StoreData } from "./features/listenbrainz/models/storeData";
import { Logger } from "./features/logger";
import { Songwhip } from "./features/songwhip/songwhip";
import { addCustomCss } from "./features/theming/theming";
import { convertDurationToSeconds } from "./features/time/parse";
import { MediaStatus } from "./models/mediaStatus";
import { Options } from "./models/options";
import { downloadFile } from "./scripts/download";
import { addHotkey } from "./scripts/hotkeys";
import { settingsStore } from "./scripts/settings";
import { setTitle } from "./scripts/window-functions";
import { RepeatState } from "./models/repeatState";
const notificationPath = `${app.getPath("userData")}/notification.jpg`;
const appName = "TIDAL Hi-Fi";
@@ -26,8 +28,12 @@ let player: Player;
let currentPlayStatus = MediaStatus.paused;
let currentListenBrainzDelayId: ReturnType<typeof setTimeout>;
let scrobbleWaitingForDelay = false;
let wasJustPausedOrResumed = false;
let currentlyPlaying = MediaStatus.paused;
let currentRepeatState: RepeatState = RepeatState.off;
let currentShuffleState = false;
let currentMediaInfo: Options;
let currentNotification: Electron.Notification;
const elements = {
play: '*[data-test="play"]',
@@ -184,7 +190,6 @@ function getUpdateFrequency() {
* Play or pause the current song
*/
function playPause() {
wasJustPausedOrResumed = true;
const play = elements.get("play");
if (play) {
@@ -317,6 +322,12 @@ function addIPCEventListeners() {
case globalEvents.toggleFavorite:
elements.click("favorite");
break;
case globalEvents.toggleShuffle:
elements.click("shuffle");
break;
case globalEvents.toggleRepeat:
elements.click("repeat");
break;
default:
break;
}
@@ -340,6 +351,23 @@ function getCurrentlyPlayingStatus() {
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
@@ -359,7 +387,13 @@ function updateMediaInfo(options: Options, notify: boolean) {
currentMediaInfo = options;
ipcRenderer.send(globalEvents.updateInfo, options);
if (settingsStore.get(settings.notifications) && notify) {
new Notification({ title: options.title, body: options.artists, icon: options.icon }).show();
if (currentNotification) currentNotification.close();
currentNotification = new Notification({
title: options.title,
body: options.artists,
icon: options.icon,
});
currentNotification.show();
}
updateMpris(options);
updateListenBrainz(options);
@@ -486,23 +520,6 @@ function getTrackID() {
return window.location;
}
function updateMediaSession(options: Options) {
if ("mediaSession" in navigator) {
navigator.mediaSession.metadata = new MediaMetadata({
title: options.title,
artist: options.artists,
album: options.album,
artwork: [
{
src: options.icon,
sizes: "640x640",
type: "image/png",
},
],
});
}
}
/**
* Watch for song changes and update title + notify
*/
@@ -514,12 +531,21 @@ setInterval(function () {
const titleOrArtistsChanged = currentSong !== songDashArtistTitle;
const current = elements.getText("current");
const currentStatus = getCurrentlyPlayingStatus();
const shuffleState = getCurrentShuffleState();
const repeatState = getCurrentRepeatState();
const playStateChanged = currentStatus != currentlyPlaying;
const shuffleStateChanged = shuffleState != currentShuffleState;
const repeatStateChanged = repeatState != currentRepeatState;
const hasStateChanged = playStateChanged || shuffleStateChanged || repeatStateChanged;
// update info if song changed or was just paused/resumed
if (titleOrArtistsChanged || wasJustPausedOrResumed) {
if (wasJustPausedOrResumed) {
wasJustPausedOrResumed = false;
}
if (titleOrArtistsChanged || hasStateChanged) {
if (playStateChanged) currentlyPlaying = currentStatus;
if (shuffleStateChanged) currentShuffleState = shuffleState;
if (repeatStateChanged) currentRepeatState = repeatState;
skipArtistsIfFoundInSkippedArtistsList(artistsArray);
const album = elements.getAlbumName();
@@ -531,11 +557,19 @@ setInterval(function () {
status: currentStatus,
url: getTrackURL(),
current,
currentInSeconds: convertDurationToSeconds(current),
duration,
durationInSeconds: convertDurationToSeconds(duration),
"app-name": appName,
image: "",
icon: "",
favorite: elements.isFavorite(),
player: {
status: currentStatus,
shuffle: shuffleState,
repeat: repeatState,
},
};
// update title, url and play info with new info
@@ -565,13 +599,13 @@ setInterval(function () {
}
}).then(() => {
updateMediaInfo(options, titleOrArtistsChanged);
if (titleOrArtistsChanged) {
updateMediaSession(options);
}
});
} else {
// just update the time
updateMediaInfo({ ...currentMediaInfo, ...{ current } }, false);
updateMediaInfo(
{ ...currentMediaInfo, ...{ current, currentInSeconds: convertDurationToSeconds(current) } },
false
);
}
/**

View File

@@ -3,18 +3,13 @@ import { app, ipcMain } from "electron";
import { globalEvents } from "../constants/globalEvents";
import { settings } from "../constants/settings";
import { Logger } from "../features/logger";
import { convertDurationToSeconds } from "../features/time/parse";
import { MediaStatus } from "../models/mediaStatus";
import { mediaInfo } from "./mediaInfo";
import { settingsStore } from "./settings";
const clientId = "833617820704440341";
function timeToSeconds(timeArray: string[]) {
const minutes = parseInt(timeArray[0]) * 1;
const seconds = minutes * 60 + parseInt(timeArray[1]) * 1;
return seconds;
}
export let rpc: Client;
const observer = () => {
@@ -59,7 +54,7 @@ const getActivity = (): Presence => {
return { includeTimestamps, detailsPrefix, buttonText };
}
function setPresenceFromMediaInfo(detailsPrefix: any, buttonText: any) {
function setPresenceFromMediaInfo(detailsPrefix: string, buttonText: string) {
if (mediaInfo.url) {
presence.details = `${detailsPrefix}${mediaInfo.title}`;
presence.state = mediaInfo.artists ? mediaInfo.artists : "unknown artist(s)";
@@ -74,10 +69,10 @@ const getActivity = (): Presence => {
}
}
function includeTimeStamps(includeTimestamps: any) {
function includeTimeStamps(includeTimestamps: boolean) {
if (includeTimestamps) {
const currentSeconds = timeToSeconds(mediaInfo.current.split(":"));
const durationSeconds = timeToSeconds(mediaInfo.duration.split(":"));
const currentSeconds = convertDurationToSeconds(mediaInfo.current);
const durationSeconds = convertDurationToSeconds(mediaInfo.duration);
const date = new Date();
const now = (date.getTime() / 1000) | 0;
const remaining = date.setSeconds(date.getSeconds() + (durationSeconds - currentSeconds));

View File

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

View File

@@ -1,5 +1,6 @@
import { MediaInfo } from "../models/mediaInfo";
import { MediaStatus } from "../models/mediaStatus";
import { RepeatState } from "../models/repeatState";
export const mediaInfo = {
title: "",
@@ -9,9 +10,17 @@ export const mediaInfo = {
status: MediaStatus.paused as string,
url: "",
current: "",
currentInSeconds: 0,
duration: "",
durationInSeconds: 0,
image: "tidal-hifi-icon",
favorite: false,
player: {
status: MediaStatus.paused as string,
shuffle: false,
repeat: RepeatState.off as string,
},
};
export const updateMediaInfo = (arg: MediaInfo) => {
@@ -19,12 +28,20 @@ export const updateMediaInfo = (arg: MediaInfo) => {
mediaInfo.artists = propOrDefault(arg.artists);
mediaInfo.album = propOrDefault(arg.album);
mediaInfo.icon = propOrDefault(arg.icon);
mediaInfo.url = propOrDefault(arg.url);
mediaInfo.url = toUniversalUrl(propOrDefault(arg.url));
mediaInfo.status = propOrDefault(arg.status);
mediaInfo.current = propOrDefault(arg.current);
mediaInfo.currentInSeconds = arg.currentInSeconds ?? 0;
mediaInfo.duration = propOrDefault(arg.duration);
mediaInfo.durationInSeconds = arg.durationInSeconds ?? 0;
mediaInfo.image = propOrDefault(arg.image);
mediaInfo.favorite = arg.favorite;
mediaInfo.player = {
status: propOrDefault(arg.player?.status),
shuffle: arg.player?.shuffle ?? false,
repeat: propOrDefault(arg.player?.repeat),
};
};
/**
@@ -33,5 +50,18 @@ export const updateMediaInfo = (arg: MediaInfo) => {
* @param {*} defaultValue defaults to ""
*/
function propOrDefault(prop: string, defaultValue = "") {
return prop ? prop : defaultValue;
return prop || defaultValue;
}
/**
* Append the universal link syntax (?u) to any url
* @param url url to append the universal link syntax to
* @returns url with `?u` appended, or the original value of url if falsy
*/
function toUniversalUrl(url: string) {
if (url) {
const queryParamsSet = url.indexOf("?");
return queryParamsSet > -1 ? `${url}&u` : `${url}?u`;
}
return url;
}

View File

@@ -1,6 +1,6 @@
import Store from "electron-store";
import { BrowserWindow } from "electron";
import { BrowserWindow, shell } from "electron";
import path from "path";
import { settings } from "../constants/settings";
@@ -134,6 +134,10 @@ export const createSettingsWindow = function () {
settingsWindow.loadURL(`file://${__dirname}/../pages/settings/settings.html`);
settingsWindow.webContents.setWindowOpenHandler(({ url }) => {
shell.openExternal(url);
return { action: "deny" };
});
settingsModule.settingsWindow = settingsWindow;
};
@@ -155,3 +159,25 @@ export const hideSettingsWindow = function () {
export const closeSettingsWindow = function () {
settingsWindow = null;
};
/**
* add artists to the list of skipped artists
* @param artists list of artists to append
*/
export const addSkippedArtists = (artists: string[]) => {
const { skippedArtists } = settings;
const previousStoreValue = settingsStore.get<string, string[]>(skippedArtists);
settingsStore.set(skippedArtists, Array.from(new Set([...previousStoreValue, ...artists])));
};
/**
* Remove artists from the list of skipped artists
* @param artists list of artists to remove
*/
export const removeSkippedArtists = (artists: string[]) => {
const { skippedArtists } = settings;
const previousStoreValue = settingsStore.get<string, string[]>(skippedArtists);
const filteredArtists = previousStoreValue.filter((value) => ![...artists].includes(value));
settingsStore.set(skippedArtists, filteredArtists);
};