mirror of
				https://github.com/Mastermindzh/tidal-hifi.git
				synced 2025-10-31 08:49:35 +01:00 
			
		
		
		
	Compare commits
	
		
			280 Commits
		
	
	
		
			5.14.0
			...
			c89fe5e573
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| c89fe5e573 | |||
|  | 255b59c87c | ||
| 2bbca7df24 | |||
| eecae61405 | |||
|  | 15dbadcc73 | ||
|  | 14b92cc5de | ||
| 959fc6631f | |||
| 91f70752c8 | |||
|  | 4f0dcee359 | ||
|  | a4a963c17b | ||
| 2ecacff0e1 | |||
|  | b426f0857e | ||
| 36c9887552 | |||
|  | 9efdbb8234 | ||
| 4d5b6a35e2 | |||
|  | 7923303e18 | ||
| cc95089201 | |||
| 9b010ea2bf | |||
| 3828591177 | |||
|  | 1e471df549 | ||
|  | 96acbd3559 | ||
|  | 224689b15b | ||
| 5f1fc8731a | |||
| 02ad1289c5 | |||
| b684d290bd | |||
|  | f35fd542b2 | ||
|  | 6831e55681 | ||
| 69ad060485 | |||
| 985831e961 | |||
|  | 218f7652cd | ||
|  | e46b34e076 | ||
|  | 5f49d0d3a9 | ||
| c1850436e1 | |||
|  | 7efbd52553 | ||
| 5ea91bbfde | |||
| 76b8adb038 | |||
| a746e53f7a | |||
|  | 149c0c975f | ||
|  | 98339444f7 | ||
|  | fd5bc505da | ||
| 5f94cfbf63 | |||
| 466c39134f | |||
| da8baebf29 | |||
|  | aa6a8b3417 | ||
| 4a1152175c | |||
|  | 721c3f5047 | ||
|  | d4b0552c14 | ||
|  | 44c8c01b8b | ||
| 8d4dd1ec9d | |||
| d012ecb04e | |||
|  | fa4b4da9a5 | ||
|  | 8f1a5dc344 | ||
| 604f34b31f | |||
| 3ff37f78e8 | |||
| 8eaca2221f | |||
| 8e9e9fed4f | |||
|  | d839dba58d | ||
| bbbd919655 | |||
| d4104b21e4 | |||
| 6a23873d3d | |||
| 8d6f08a7bb | |||
| 521b8b2170 | |||
|  | 99e4cb7f48 | ||
|  | 4cd130ee0d | ||
|  | a2a2c800b6 | ||
|  | 83210f9e1d | ||
| 5d40d2d736 | |||
| 405c2d7c9f | |||
|  | 9f860d2eec | ||
|  | 86487863f8 | ||
| 3312211df2 | |||
|  | a84bb9efde | ||
| 31c37b6c7f | |||
| be9724f0cb | |||
|  | 7277482c06 | ||
|  | 323b591f4f | ||
| c63b46ee06 | |||
|  | b4a8e155af | ||
| 2702d99aca | |||
|  | 7cf9ae5c36 | ||
| 3a1402b47a | |||
|  | 4366caa16a | ||
|  | 9389b4195e | ||
| 87d8bc07ad | |||
|  | 316bf054f5 | ||
| 0d4975ce62 | |||
|  | 912f064fed | ||
| bff95bcf10 | |||
| b59dce9c9b | |||
|  | f71fb60d38 | ||
|  | fab566e9da | ||
| d2e93e7062 | |||
|  | 52a78a6a01 | ||
| f878371fab | |||
| e81aaf1384 | |||
|  | 98b4068b62 | ||
|  | b7c1cee64d | ||
| 350b4434be | |||
| 702a16357f | |||
| c6619be068 | |||
| bf7cd70f20 | |||
| 03cb14e31e | |||
| 697298da38 | |||
| 5f5b3b11a8 | |||
| d474b7b78f | |||
| ef374ea283 | |||
|  | 96ff46737b | ||
|  | dee0044876 | ||
| 3b1f456d4c | |||
|  | f380839631 | ||
| fca5194873 | |||
|  | 92d1fc159d | ||
| b632c287b0 | |||
| 5a98b13e70 | |||
| 3543253f3a | |||
|  | fff8399c9b | ||
|  | 78b9b32dbd | ||
|  | c773e59432 | ||
| 535de65e17 | |||
| b80599eab2 | |||
| c161ef8f60 | |||
| 7ed160f266 | |||
| b23975227f | |||
| 314e69dd4b | |||
|  | 10251e2f30 | ||
|  | cf06655008 | ||
|  | d5804ed4da | ||
|  | 76500ca6b8 | ||
|  | f5c80e97c5 | ||
|  | 154698c084 | ||
| 9837e77768 | |||
| 340bd82fe6 | |||
| b765ab9f48 | |||
| 6562549897 | |||
| 9089bc1d4e | |||
| 5a3609676e | |||
| 76c3082d5c | |||
| 7f7a8b7f5b | |||
|  | 2d49504818 | ||
|  | 5f4f0c67d5 | ||
|  | 3a5f1f155a | ||
|  | 3c9328fb92 | ||
|  | f262c54be2 | ||
|  | cb6e3e8b18 | ||
|  | 7f9b8dcee4 | ||
|  | 99740f4335 | ||
| 985bc0f1d5 | |||
| 1f9b3a297a | |||
| 98ca73d17d | |||
| 396ed223d3 | |||
|  | b0458f4de5 | ||
|  | b875d091ec | ||
|  | 1a0b69d042 | ||
|  | e23a71a1a6 | ||
| 510ffa1509 | |||
|  | e17ef32cde | ||
| d4c3999d9c | |||
| b0695600b1 | |||
|  | 22ecc7ade7 | ||
| a5102ebd03 | |||
|  | d01f08508f | ||
| e87d8f6922 | |||
|  | c044476014 | ||
| 75df43864b | |||
|  | 174d0a38a1 | ||
|  | cf1aeefe67 | ||
| 280d927a03 | |||
|  | f62294ef20 | ||
| 237e354f80 | |||
|  | 3a5172cb23 | ||
|  | f8f12cca62 | ||
| 79bbab6a82 | |||
| 7376df7f30 | |||
| 7f8760c4e9 | |||
|  | 1cf5a01ad7 | ||
| 8fc49f500e | |||
|  | 49f5a01b58 | ||
| 48f4fe47ef | |||
| a819e1eb45 | |||
| 57e6342b5f | |||
| 58a543a3c8 | |||
| 1c915d99fe | |||
|  | 8994360415 | ||
| a5c0d9e6e8 | |||
| f8eb36f4c7 | |||
| 5ca90c25d3 | |||
| cf86969cfc | |||
| afd05ae88b | |||
| 72c25dfdc1 | |||
|  | 25b30edf31 | ||
| 35ed4807e3 | |||
| deff9524a8 | |||
| 34c7777eeb | |||
| 048e949f30 | |||
|  | 4a976bc58c | ||
| ff4c51234b | |||
|  | c4ee6b51b9 | ||
| 45fe336598 | |||
|  | fe9f50aaf5 | ||
| aaf7a1b662 | |||
| 755be0ee30 | |||
|  | 9d736b2bd9 | ||
| f4d4b1a1df | |||
|  | 0c27c815f5 | ||
|  | a3aa45a96b | ||
| ae699887b2 | |||
| a8c635932f | |||
| 66d0d004bf | |||
| d1e0321058 | |||
| 2ab5a556ab | |||
| baf719fc60 | |||
| 15c8f6a418 | |||
| f73521e2e5 | |||
|  | 0f5e00c4df | ||
|  | 974877ea4f | ||
|  | 6be5774001 | ||
|  | f96cf2e8da | ||
|  | f832bd2712 | ||
|  | cb8667bd41 | ||
| 91156e5936 | |||
|  | 57e9a2dcb8 | ||
|  | 21edcd6ad5 | ||
|  | 3dc42eceb0 | ||
|  | b1830f5684 | ||
|  | 1a0c15e17f | ||
| 6e59d59a1d | |||
|  | fab7497311 | ||
| fccbcc77ea | |||
|  | 041c19fb52 | ||
| e36c562afa | |||
| 1589aa5251 | |||
| 8c672dd1eb | |||
|  | 3769550f24 | ||
|  | 5a06b6c53f | ||
|  | 1d4ef66d27 | ||
| e02f07401b | |||
| 118f92e75a | |||
|  | 788d302ce8 | ||
|  | cc09f35b49 | ||
| ef13933c66 | |||
| 4f72e1b35d | |||
| 2c1c76d2d0 | |||
| e2a84e119a | |||
| 5b85e59fc3 | |||
| c3b772919a | |||
|  | 70f2f5c248 | ||
|  | ffcb563b35 | ||
|  | 2dd96dd48e | ||
| 65f3b251f4 | |||
| 45f8c13c5b | |||
| 9dc7267a4d | |||
| f5c56ae8c5 | |||
| 308b527469 | |||
| f4aa8e070e | |||
| 19c12b57de | |||
| b5713651de | |||
| 268027734c | |||
| f711ea9000 | |||
|  | 506f86f014 | ||
| 4e827e120f | |||
|  | 5b62154ebc | ||
| 15cc6bb6d4 | |||
| e0e9d99173 | |||
| daa797fc00 | |||
|  | 4e011a47d8 | ||
|  | 965d19318e | ||
| 9cd89c9f31 | |||
|  | c7b97a49c4 | ||
| c63e05357e | |||
| 42dac4a7f1 | |||
|  | 595895dbc1 | ||
| 1ff94e45a7 | |||
|  | 95776d1aab | ||
| 5669cb4c6a | |||
| 5946c47442 | |||
|  | 111238f6b2 | ||
|  | 65a4600c4d | ||
| dd6f81386f | |||
| 54316d31b5 | |||
| 28a9458dfc | 
| @@ -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 | ||||||
|   | |||||||
							
								
								
									
										42
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										42
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							| @@ -21,26 +21,30 @@ jobs: | |||||||
|       - uses: actions/checkout@master |       - uses: actions/checkout@master | ||||||
|       - uses: actions/setup-node@master |       - uses: actions/setup-node@master | ||||||
|         with: |         with: | ||||||
|           node-version: 19 |           node-version: 22.12.0 | ||||||
|       - run: npm install |       - run: npm install | ||||||
|       - run: npm run build |       - run: npm run build | ||||||
|  |       # - uses: actions/upload-artifact@master | ||||||
|  |       #   with: | ||||||
|  |       #     name: linux-builds | ||||||
|  |       #     path: dist/ | ||||||
|  |  | ||||||
|   build_on_mac: |   # build_on_mac: | ||||||
|     runs-on: macos-latest |   #   runs-on: macos-latest | ||||||
|     steps: |   #   steps: | ||||||
|       - uses: actions/checkout@master |   #     - uses: actions/checkout@master | ||||||
|       - uses: actions/setup-node@master |   #     - uses: actions/setup-node@master | ||||||
|         with: |   #       with: | ||||||
|           node-version: 19 |   #         node-version: 22.4 | ||||||
|       - run: npm install |   #     - run: npm install | ||||||
|       - run: npm run build |   #     - run: npm run build | ||||||
|  |  | ||||||
|   build_on_win: |   # build_on_win: | ||||||
|     runs-on: windows-latest |   #   runs-on: windows-latest | ||||||
|     steps: |   #   steps: | ||||||
|       - uses: actions/checkout@master |   #     - uses: actions/checkout@master | ||||||
|       - uses: actions/setup-node@master |   #     - uses: actions/setup-node@master | ||||||
|         with: |   #       with: | ||||||
|           node-version: 19 |   #         node-version: 22.4 | ||||||
|       - run: npm install |   #     - run: npm install | ||||||
|       - run: npm run build |   #     - run: npm run build | ||||||
|   | |||||||
							
								
								
									
										54
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										54
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @@ -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: 19 |           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 | ||||||
| @@ -29,30 +29,30 @@ jobs: | |||||||
|           name: linux-builds |           name: linux-builds | ||||||
|           path: dist/ |           path: dist/ | ||||||
|  |  | ||||||
|   build_on_mac: |   # build_on_mac: | ||||||
|     runs-on: macos-latest |   #   runs-on: macos-latest | ||||||
|     steps: |   #   steps: | ||||||
|       - uses: actions/checkout@master |   #     - uses: actions/checkout@master | ||||||
|       - uses: actions/setup-node@master |   #     - uses: actions/setup-node@master | ||||||
|         with: |   #       with: | ||||||
|           node-version: 19 |   #         node-version: 22.4 | ||||||
|       - run: npm install |   #     - run: npm install | ||||||
|       - run: npm run build |   #     - run: npm run build | ||||||
|       - uses: actions/upload-artifact@master |   #     - uses: actions/upload-artifact@master | ||||||
|         with: |   #       with: | ||||||
|           name: mac-builds |   #         name: mac-builds | ||||||
|           path: ./dist/ |   #         path: ./dist/ | ||||||
|  |  | ||||||
|   build_on_win: |   # build_on_win: | ||||||
|     runs-on: windows-latest |   #   runs-on: windows-latest | ||||||
|     steps: |   #   steps: | ||||||
|       - uses: actions/checkout@master |   #     - uses: actions/checkout@master | ||||||
|       - uses: actions/setup-node@master |   #     - uses: actions/setup-node@master | ||||||
|         with: |   #       with: | ||||||
|           node-version: 19 |   #         node-version: 22.4 | ||||||
|       - run: npm install |   #     - run: npm install | ||||||
|       - run: npm run build |   #     - run: npm run build | ||||||
|       - uses: actions/upload-artifact@master |   #     - uses: actions/upload-artifact@master | ||||||
|         with: |   #       with: | ||||||
|           name: windows-builds |   #         name: windows-builds | ||||||
|           path: dist/ |   #         path: dist/ | ||||||
|   | |||||||
							
								
								
									
										5
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							| @@ -14,11 +14,12 @@ | |||||||
|     "rescrobbler", |     "rescrobbler", | ||||||
|     "scrobble", |     "scrobble", | ||||||
|     "scrobbling", |     "scrobbling", | ||||||
|     "Songwhip", |  | ||||||
|     "trackid", |     "trackid", | ||||||
|     "tracklist", |     "tracklist", | ||||||
|     "widevine", |     "widevine", | ||||||
|     "xesam" |     "wvcus", | ||||||
|  |     "xesam", | ||||||
|  |     "xhayper" | ||||||
|   ], |   ], | ||||||
|   "sonarlint.connectedMode.project": { |   "sonarlint.connectedMode.project": { | ||||||
|     "connectionId": "public-sonarcloud", |     "connectionId": "public-sonarcloud", | ||||||
|   | |||||||
							
								
								
									
										63
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										63
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -4,6 +4,69 @@ 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] | ||||||
|  |  | ||||||
|  | - Reverted to sass 1.79.4 to fix `Nix` builds | ||||||
|  | - Changed electron-builder.base.yml to now generate the correct .desktop entries again | ||||||
|  |   - Should fix flatpak build | ||||||
|  |  | ||||||
|  | ## [5.18.1] | ||||||
|  |  | ||||||
|  | - Fixed the login bug | ||||||
|  | - Upgraded electron to 35.1.1 | ||||||
|  | - Added Widevine/CDM info to startup | ||||||
|  | - delayed remote electron initializer | ||||||
|  |  | ||||||
|  | ## [5.18.0] | ||||||
|  |  | ||||||
|  | - [Dianoga](https://github.com/Dianoga) fixed the duration selector, restoring mpris & partial API data. | ||||||
|  |   - PR: #554 | ||||||
|  | - Added `xesam:url` property to mpris metadata fixes [#506](https://github.com/Mastermindzh/tidal-hifi/issues/506) | ||||||
|  |  | ||||||
|  | ## [5.17.0] | ||||||
|  |  | ||||||
|  | - Added an option to disable the dynamic title and set it to a static one, [#491](https://github.com/Mastermindzh/tidal-hifi/pull/491) | ||||||
|  | - Discord integration now says "Listening to" instead of "playing" [#488](https://github.com/Mastermindzh/tidal-hifi/pull/488) && [#454](https://github.com/Mastermindzh/tidal-hifi/pull/454) | ||||||
|  | - Fixed several element names in the dom scraper | ||||||
|  | - Removed the Songwhip (they shut down) integration and replaced it with TIDAL's universal link system | ||||||
|  |  | ||||||
|  | ## [5.16.0] | ||||||
|  |  | ||||||
|  | - Fix issue #449 Discord RPC stuck on "Browsing Tidal". | ||||||
|  | - Fix issue #448 Add option to disable the discord rpc idle text | ||||||
|  | - Notifications are now send at the end of the update process, allowing other events to happen sooner. | ||||||
|  |  | ||||||
|  | ## [5.15.0] | ||||||
|  |  | ||||||
|  | - Added all missing swagger/openApi info with the help of [Times-Z](https://github.com/Times-Z) | ||||||
|  | - Updated most dependency versions | ||||||
|  |   - This includes Electron 31! | ||||||
|  |  | ||||||
|  | - Added a channel selector so we can now use Tidal's staging environment directly from the app | ||||||
|  |   - implements [#437](https://github.com/Mastermindzh/tidal-hifi/issues/437) | ||||||
|  |  | ||||||
|  | ## [5.14.1] | ||||||
|  |  | ||||||
|  | - Fixed `getAlbumName` not finding album name whilst on queue page | ||||||
|  | - Added all mediaInfo to mpris interface using the `custom:` prefix | ||||||
|  |  | ||||||
| ## [5.14] | ## [5.14] | ||||||
|  |  | ||||||
| - Simplified `MediaInfo` & `Options` types | - Simplified `MediaInfo` & `Options` types | ||||||
|   | |||||||
| @@ -26,7 +26,7 @@ The web version of [listen.tidal.com](https://listen.tidal.com) running in elect | |||||||
|     - [Using source](#using-source) |     - [Using source](#using-source) | ||||||
|   - [Integrations](#integrations) |   - [Integrations](#integrations) | ||||||
|   - [Known bugs](#known-bugs) |   - [Known bugs](#known-bugs) | ||||||
|     - [DRM not working on Windows](#drm-not-working-on-windows) |     - [DRM not working on Windows (error S6007)](#drm-not-working-on-windows-error-s6007) | ||||||
|   - [Special thanks to](#special-thanks-to) |   - [Special thanks to](#special-thanks-to) | ||||||
|   - [Donations](#donations) |   - [Donations](#donations) | ||||||
|   - [Images](#images) |   - [Images](#images) | ||||||
| @@ -48,7 +48,6 @@ The web version of [listen.tidal.com](https://listen.tidal.com) running in elect | |||||||
| - AlbumArt in integrations ([best-effort](https://github.com/Mastermindzh/tidal-hifi/pull/88#pullrequestreview-840814847)) | - AlbumArt in integrations ([best-effort](https://github.com/Mastermindzh/tidal-hifi/pull/88#pullrequestreview-840814847)) | ||||||
| - Custom [integrations](#integrations) | - Custom [integrations](#integrations) | ||||||
|   - [ListenBrainz](https://listenbrainz.org/?redirect=false) integration |   - [ListenBrainz](https://listenbrainz.org/?redirect=false) integration | ||||||
|   - Songwhip.com integration (hotkey `ctrl + w`) |  | ||||||
|   - Discord RPC integration (showing "now listening", "Browsing", etc) |   - Discord RPC integration (showing "now listening", "Browsing", etc) | ||||||
|     - Flatpak version only works if both Discord and Tidal-HiFi are flatpaks |     - Flatpak version only works if both Discord and Tidal-HiFi are flatpaks | ||||||
|   - MPRIS integration |   - MPRIS integration | ||||||
| @@ -131,7 +130,7 @@ nix-env -iA nixpkgs.tidal-hifi | |||||||
|  |  | ||||||
| To install and work with the code on this project follow these steps: | To install and work with the code on this project follow these steps: | ||||||
|  |  | ||||||
| - `git clone [https://github.com/Mastermindzh/tidal-hifi.git](https://github.com/Mastermindzh/tidal-hifi.git)` | - `git clone https://github.com/Mastermindzh/tidal-hifi.git` | ||||||
| - `cd tidal-hifi` | - `cd tidal-hifi` | ||||||
| - `npm install` | - `npm install` | ||||||
| - `npm run watch` to watch for auto-reload of Typescript/SCSS changes. | - `npm run watch` to watch for auto-reload of Typescript/SCSS changes. | ||||||
| @@ -153,11 +152,13 @@ Integrations with other projects that are not included natively: | |||||||
|  |  | ||||||
| ## Known bugs | ## Known bugs | ||||||
|  |  | ||||||
| ### DRM not working on Windows | ### DRM not working on Windows (error S6007) | ||||||
|  |  | ||||||
| Most Windows users run into DRM issues when trying to use TIDAL Hi-Fi. | Most Windows users run into DRM issues when trying to use TIDAL Hi-Fi. | ||||||
| Nothing I can do about that I'm afraid... Tidal is working on removing/changing DRM so when they finish with that we can give it another shot. | Nothing I can do about that I'm afraid... Tidal is working on removing/changing DRM so when they finish with that we can give it another shot. | ||||||
|  |  | ||||||
|  | Until then you'll have to use the official app unfortunately. | ||||||
|  |  | ||||||
| ## Special thanks to | ## Special thanks to | ||||||
|  |  | ||||||
| - [Castlabs](https://castlabs.com/) | - [Castlabs](https://castlabs.com/) | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| appId: com.rickvanlieshout.tidal-hifi | appId: com.rickvanlieshout.tidal-hifi | ||||||
| electronVersion: 28.1.1 | electronVersion: 37.2.5 | ||||||
| electronDownload: | electronDownload: | ||||||
|   version: 28.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,24 +17,25 @@ 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: | ||||||
|     Encoding: UTF-8 |     entry: | ||||||
|     Name: TIDAL Hi-Fi |       Encoding: "UTF-8" | ||||||
|     GenericName: TIDAL Hi-Fi |       Name: "TIDAL Hi-Fi" | ||||||
|     Comment: The web version of listen.tidal.com running in electron with hifi support thanks to widevine. |       GenericName: "TIDAL Hi-Fi" | ||||||
|     Icon: tidal-hifi |       Comment: "The web version of listen.tidal.com running in electron with hifi support thanks to widevine." | ||||||
|     StartupNotify: true |       Icon: "tidal-hifi" | ||||||
|     Terminal: false |       StartupNotify: "true" | ||||||
|     Type: Application |       Terminal: "false" | ||||||
|     Categories: Network;Application;AudioVideo;Audio;Video |       Type: "Application" | ||||||
|     StartupWMClass: tidal-hifi |       Categories: "Network;Application;AudioVideo;Audio;Video" | ||||||
|     X-PulseAudio-Properties: media.role=music |       StartupWMClass: "tidal-hifi" | ||||||
|     MimeType: x-scheme-handler/tidal; |       X-PulseAudio-Properties: "media.role=music" | ||||||
|  |       MimeType: "x-scheme-handler/tidal;" | ||||||
| mac: | mac: | ||||||
|   category: public.app-category.entertainment |   category: public.app-category.entertainment | ||||||
| win: | win: | ||||||
|   | |||||||
							
								
								
									
										6156
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										6156
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										54
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										54
									
								
								package.json
									
									
									
									
									
								
							| @@ -1,10 +1,10 @@ | |||||||
| { | { | ||||||
|   "name": "tidal-hifi", |   "name": "tidal-hifi", | ||||||
|   "version": "5.14.0", |   "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": { | ||||||
|     "start": "electron --inspect=0.0.0.0:5858 .", |     "start": "electron --inspect=0.0.0.0:5858 --remote-debugging-port=8315 --remote-allow-origins=* .", | ||||||
|     "watchStart": "nodemon dist -x \"npm run start\"", |     "watchStart": "nodemon dist -x \"npm run start\"", | ||||||
|     "compile": "tsc && npm run sass-and-copy", |     "compile": "tsc && npm run sass-and-copy", | ||||||
|     "deps": "npm run watch", |     "deps": "npm run watch", | ||||||
| @@ -40,45 +40,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", | ||||||
|     "axios": "^1.6.8", |     "@xhayper/discord-rpc": "1.3.0", | ||||||
|  |     "axios": "^1.12.0", | ||||||
|     "cors": "^2.8.5", |     "cors": "^2.8.5", | ||||||
|     "discord-rpc": "^4.0.1", |  | ||||||
|     "electron-store": "^8.2.0", |     "electron-store": "^8.2.0", | ||||||
|     "express": "^4.19.2", |     "express": "^5.1.0", | ||||||
|     "hotkeys-js": "^3.13.7", |     "hotkeys-js": "^3.13.15", | ||||||
|     "mpris-service": "^2.1.2", |     "mpris-service": "^2.1.2", | ||||||
|     "request": "^2.88.2", |     "request": "^2.88.2", | ||||||
|     "sass": "^1.75.0", |     "sass": "1.93.2", | ||||||
|     "swagger-ui-express": "^5.0.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/discord-rpc": "^4.0.8", |     "@types/express": "^5.0.3", | ||||||
|     "@types/express": "^4.17.21", |     "@types/node": "^22.16.2", | ||||||
|     "@types/node": "^20.12.12", |  | ||||||
|     "@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": "^6.18.0", |     "@typescript-eslint/eslint-plugin": "^8.36.0", | ||||||
|     "@typescript-eslint/parser": "^6.18.0", |     "@typescript-eslint/parser": "^8.36.0", | ||||||
|     "copyfiles": "^2.4.1", |     "copyfiles": "^2.4.1", | ||||||
|     "electron": "git+https://github.com/castlabs/electron-releases#v28.1.1+wvcus", |     "electron": "github:castlabs/electron-releases#v37.2.5+wvcus", | ||||||
|     "electron-builder": "^24.9.1", |     "electron-builder": "~26.0.12", | ||||||
|     "eslint": "^8.56.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.0.2", |     "node-abi": "^4.12.0", | ||||||
|     "prettier": "^3.1.1", |     "nodemon": "^3.1.10", | ||||||
|     "stylelint": "^16.1.0", |     "prettier": "^3.6.2", | ||||||
|     "stylelint-config-standard": "^36.0.0", |     "stylelint": "^16.21.1", | ||||||
|     "stylelint-config-standard-scss": "^13.0.0", |     "stylelint-config-standard": "^39.0.0", | ||||||
|     "stylelint-prettier": "^5.0.0", |     "stylelint-config-standard-scss": "^16.0.0", | ||||||
|  |     "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.0.4", |     "tsc-watch": "^7.1.1", | ||||||
|     "typescript": "^5.3.3" |     "typescript": "^5.8.3" | ||||||
|   }, |   }, | ||||||
|   "prettier": "@mastermindzh/prettier-config" |   "prettier": "@mastermindzh/prettier-config" | ||||||
| } | } | ||||||
							
								
								
									
										4
									
								
								renovate.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								renovate.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | |||||||
|  | { | ||||||
|  |   "$schema": "https://docs.renovatebot.com/renovate-schema.json", | ||||||
|  |   "ignoreDeps": ["@types/node", "electron-store", "@xhayper/discord-rpc"] | ||||||
|  | } | ||||||
| @@ -16,17 +16,19 @@ | |||||||
|     search: '[class^="searchField"]', |     search: '[class^="searchField"]', | ||||||
|     shuffle: '*[data-test="shuffle"]', |     shuffle: '*[data-test="shuffle"]', | ||||||
|     repeat: '*[data-test="repeat"]', |     repeat: '*[data-test="repeat"]', | ||||||
|     account: '*[class^="profileOptions"]', |     account: '*[data-test^="profile-image-button"]', | ||||||
|     settings: '*[data-test^="open-settings"]', |     settings: '*[data-test^="sidebar-menu-button"]', | ||||||
|  |     openSettings: '*[data-test^="open-settings"]', | ||||||
|     media: '*[data-test="current-media-imagery"]', |     media: '*[data-test="current-media-imagery"]', | ||||||
|     image: "img", |     image: "img", | ||||||
|     current: '*[data-test="current-time"]', |     current: '*[data-test="current-time"]', | ||||||
|     duration: '*[class^=playbackControlsContainer] *[data-test="duration"]', |     duration: '*[class^=_playbackControlsContainer] *[data-test="duration"]', | ||||||
|     bar: '*[data-test="progress-bar"]', |     bar: '*[data-test="progress-bar"]', | ||||||
|     footer: "#footerPlayer", |     footer: "#footerPlayer", | ||||||
|     mediaItem: "[data-type='mediaItem']", |     mediaItem: "[data-type='mediaItem']", | ||||||
|     album_header_title: '*[class^="playingFrom"] span:nth-child(2)', |     album_header_title: '*[class^="_playingFrom"] span:nth-child(2)', | ||||||
|     playingFrom: '*[class^="playingFrom"] span:nth-child(2)', |     playing_from: '*[class^="_playingFrom"] span:nth-child(2)', | ||||||
|  |     queue_album: "*[class^=playQueueItemsContainer] *[class^=groupTitle] span:nth-child(2)", | ||||||
|     currentlyPlaying: "[class^='isPlayingIcon'], [data-test-is-playing='true']", |     currentlyPlaying: "[class^='isPlayingIcon'], [data-test-is-playing='true']", | ||||||
|     album_name_cell: '[class^="album"]', |     album_name_cell: '[class^="album"]', | ||||||
|     tracklist_row: '[data-test="tracklist-row"]', |     tracklist_row: '[data-test="tracklist-row"]', | ||||||
|   | |||||||
| @@ -10,7 +10,7 @@ export const globalEvents = { | |||||||
|   showSettings: "showSettings", |   showSettings: "showSettings", | ||||||
|   storeChanged: "storeChanged", |   storeChanged: "storeChanged", | ||||||
|   error: "error", |   error: "error", | ||||||
|   whip: "whip", |   getUniversalLink: "getUniversalLink", | ||||||
|   log: "log", |   log: "log", | ||||||
|   toggleFavorite: "toggleFavorite", |   toggleFavorite: "toggleFavorite", | ||||||
|   toggleShuffle: "toggleShuffle", |   toggleShuffle: "toggleShuffle", | ||||||
|   | |||||||
| @@ -10,6 +10,10 @@ | |||||||
|  */ |  */ | ||||||
| export const settings = { | export const settings = { | ||||||
|   adBlock: "adBlock", |   adBlock: "adBlock", | ||||||
|  |   advanced: { | ||||||
|  |     root: "advanced", | ||||||
|  |     tidalUrl: "advanced.tidalUrl", | ||||||
|  |   }, | ||||||
|   api: "api", |   api: "api", | ||||||
|   apiSettings: { |   apiSettings: { | ||||||
|     root: "apiSettings", |     root: "apiSettings", | ||||||
| @@ -26,6 +30,7 @@ export const settings = { | |||||||
|     buttonText: "discord.buttonText", |     buttonText: "discord.buttonText", | ||||||
|     includeTimestamps: "discord.includeTimestamps", |     includeTimestamps: "discord.includeTimestamps", | ||||||
|     showSong: "discord.showSong", |     showSong: "discord.showSong", | ||||||
|  |     showIdle: "discord.showIdle", | ||||||
|     idleText: "discord.idleText", |     idleText: "discord.idleText", | ||||||
|     usingText: "discord.usingText", |     usingText: "discord.usingText", | ||||||
|   }, |   }, | ||||||
| @@ -50,6 +55,7 @@ export const settings = { | |||||||
|   singleInstance: "singleInstance", |   singleInstance: "singleInstance", | ||||||
|   skipArtists: "skipArtists", |   skipArtists: "skipArtists", | ||||||
|   skippedArtists: "skippedArtists", |   skippedArtists: "skippedArtists", | ||||||
|  |   staticWindowTitle: "staticWindowTitle", | ||||||
|   theme: "theme", |   theme: "theme", | ||||||
|   trayIcon: "trayIcon", |   trayIcon: "trayIcon", | ||||||
|   updateFrequency: "updateFrequency", |   updateFrequency: "updateFrequency", | ||||||
|   | |||||||
| @@ -3,7 +3,111 @@ import fs from "fs"; | |||||||
| import { mediaInfo } from "../../../scripts/mediaInfo"; | import { mediaInfo } from "../../../scripts/mediaInfo"; | ||||||
|  |  | ||||||
| export const addCurrentInfo = (expressApp: Router) => { | export const addCurrentInfo = (expressApp: Router) => { | ||||||
|   expressApp.get("/current", (req, res) => res.json({ ...mediaInfo, artist: mediaInfo.artists })); |   /** | ||||||
|  |    * @swagger | ||||||
|  |    * tags: | ||||||
|  |    *   name: current | ||||||
|  |    *   description: The current media info API | ||||||
|  |    * components: | ||||||
|  |    *   schemas: | ||||||
|  |    *     MediaInfo: | ||||||
|  |    *       type: object | ||||||
|  |    *       properties: | ||||||
|  |    *         title: | ||||||
|  |    *           type: string | ||||||
|  |    *         artists: | ||||||
|  |    *           type: string | ||||||
|  |    *         album: | ||||||
|  |    *           type: string | ||||||
|  |    *         icon: | ||||||
|  |    *           type: string | ||||||
|  |    *           format: uri | ||||||
|  |    *         playingFrom: | ||||||
|  |    *           type: string | ||||||
|  |    *         status: | ||||||
|  |    *           type: string | ||||||
|  |    *         url: | ||||||
|  |    *           type: string | ||||||
|  |    *           format: uri | ||||||
|  |    *         current: | ||||||
|  |    *           type: string | ||||||
|  |    *         currentInSeconds: | ||||||
|  |    *           type: integer | ||||||
|  |    *         duration: | ||||||
|  |    *           type: string | ||||||
|  |    *         durationInSeconds: | ||||||
|  |    *           type: integer | ||||||
|  |    *         image: | ||||||
|  |    *           type: string | ||||||
|  |    *           format: uri | ||||||
|  |    *         favorite: | ||||||
|  |    *           type: boolean | ||||||
|  |    *         player: | ||||||
|  |    *           type: object | ||||||
|  |    *           properties: | ||||||
|  |    *             status: | ||||||
|  |    *               type: string | ||||||
|  |    *             shuffle: | ||||||
|  |    *               type: boolean | ||||||
|  |    *             repeat: | ||||||
|  |    *               type: string | ||||||
|  |    *         artist: | ||||||
|  |    *           type: string | ||||||
|  |    *       example: | ||||||
|  |    *         title: "Sample Title" | ||||||
|  |    *         artists: "Sample Artist" | ||||||
|  |    *         album: "Sample Album" | ||||||
|  |    *         icon: "/path/to/sample/icon.jpg" | ||||||
|  |    *         playingFrom: "Sample Playlist" | ||||||
|  |    *         status: "playing" | ||||||
|  |    *         url: "https://tidal.com/browse/track/sample" | ||||||
|  |    *         current: "1:23" | ||||||
|  |    *         currentInSeconds: 83 | ||||||
|  |    *         duration: "3:45" | ||||||
|  |    *         durationInSeconds: 225 | ||||||
|  |    *         image: "https://example.com/sample-image.jpg" | ||||||
|  |    *         favorite: true | ||||||
|  |    *         player: | ||||||
|  |    *           status: "playing" | ||||||
|  |    *           shuffle: true | ||||||
|  |    *           repeat: "one" | ||||||
|  |    *         artist: "Sample Artist" | ||||||
|  |    */ | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * @swagger | ||||||
|  |    * /current: | ||||||
|  |    *   get: | ||||||
|  |    *     summary: Get current media info | ||||||
|  |    *     tags: [current] | ||||||
|  |    *     responses: | ||||||
|  |    *       200: | ||||||
|  |    *         description: Current media info | ||||||
|  |    *         content: | ||||||
|  |    *           application/json: | ||||||
|  |    *             schema: | ||||||
|  |    *               $ref: '#/components/schemas/MediaInfo' | ||||||
|  |    */ | ||||||
|  |   expressApp.get("/current", (_req, res) => { | ||||||
|  |     res.json({ ...mediaInfo, artist: mediaInfo.artists }); | ||||||
|  |   }); | ||||||
|  |   /** | ||||||
|  |    * @swagger | ||||||
|  |    * /current/image: | ||||||
|  |    *   get: | ||||||
|  |    *     summary: Get current media image | ||||||
|  |    *     tags: [current] | ||||||
|  |    *     responses: | ||||||
|  |    *       200: | ||||||
|  |    *         description: Current media image | ||||||
|  |    *         content: | ||||||
|  |    *           image/png: | ||||||
|  |    *             schema: | ||||||
|  |    *               type: string | ||||||
|  |    *               format: binary | ||||||
|  |    *       404: | ||||||
|  |    *         description: Not found | ||||||
|  |    */ | ||||||
|   expressApp.get("/current/image", getCurrentImage); |   expressApp.get("/current/image", getCurrentImage); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,4 +1,3 @@ | |||||||
| /* eslint-disable @typescript-eslint/no-explicit-any */ |  | ||||||
| import { BrowserWindow } from "electron"; | import { BrowserWindow } from "electron"; | ||||||
| import { Router } from "express"; | import { Router } from "express"; | ||||||
| import { globalEvents } from "../../../constants/globalEvents"; | import { globalEvents } from "../../../constants/globalEvents"; | ||||||
| @@ -12,19 +11,148 @@ export const addPlaybackControl = (expressApp: Router, mainWindow: BrowserWindow | |||||||
|   const windowEvent = handleWindowEvent(mainWindow); |   const windowEvent = handleWindowEvent(mainWindow); | ||||||
|   const createRoute = (route: string) => `/player${route}`; |   const createRoute = (route: string) => `/player${route}`; | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * @swagger | ||||||
|  |    * tags: | ||||||
|  |    *   name: player | ||||||
|  |    *   description: The player control API | ||||||
|  |    * components: | ||||||
|  |    *   schemas: | ||||||
|  |    *     OkResponse: | ||||||
|  |    *       type: string | ||||||
|  |    *       example: "OK" | ||||||
|  |    */ | ||||||
|   const createPlayerAction = (route: string, action: string) => { |   const createPlayerAction = (route: string, action: string) => { | ||||||
|     expressApp.post(createRoute(route), (req, res) => windowEvent(res, action)); |     expressApp.post(createRoute(route), (req, res) => windowEvent(res, action)); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   if (settingsStore.get(settings.playBackControl)) { |   if (settingsStore.get(settings.playBackControl)) { | ||||||
|  |     /** | ||||||
|  |      * @swagger | ||||||
|  |      * /player/play: | ||||||
|  |      *   post: | ||||||
|  |      *     summary: Play the current media | ||||||
|  |      *     tags: [player] | ||||||
|  |      *     responses: | ||||||
|  |      *       200: | ||||||
|  |      *         description: Ok | ||||||
|  |      *         content: | ||||||
|  |      *           text/plain: | ||||||
|  |      *             schema: | ||||||
|  |      *               $ref: '#/components/schemas/OkResponse' | ||||||
|  |      */ | ||||||
|     createPlayerAction("/play", globalEvents.play); |     createPlayerAction("/play", globalEvents.play); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @swagger | ||||||
|  |      * /player/favorite/toggle: | ||||||
|  |      *   post: | ||||||
|  |      *     summary: Add the current media to your favorites, or remove it if its already added to your favorites | ||||||
|  |      *     tags: [player] | ||||||
|  |      *     responses: | ||||||
|  |      *       200: | ||||||
|  |      *         description: Ok | ||||||
|  |      *         content: | ||||||
|  |      *           text/plain: | ||||||
|  |      *             schema: | ||||||
|  |      *               $ref: '#/components/schemas/OkResponse' | ||||||
|  |      */ | ||||||
|     createPlayerAction("/favorite/toggle", globalEvents.toggleFavorite); |     createPlayerAction("/favorite/toggle", globalEvents.toggleFavorite); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @swagger | ||||||
|  |      * /player/pause: | ||||||
|  |      *   post: | ||||||
|  |      *     summary: Pause the current media | ||||||
|  |      *     tags: [player] | ||||||
|  |      *     responses: | ||||||
|  |      *       200: | ||||||
|  |      *         description: Ok | ||||||
|  |      *         content: | ||||||
|  |      *           text/plain: | ||||||
|  |      *             schema: | ||||||
|  |      *               $ref: '#/components/schemas/OkResponse' | ||||||
|  |      */ | ||||||
|     createPlayerAction("/pause", globalEvents.pause); |     createPlayerAction("/pause", globalEvents.pause); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @swagger | ||||||
|  |      * /player/next: | ||||||
|  |      *   post: | ||||||
|  |      *     summary: Play the next song | ||||||
|  |      *     tags: [player] | ||||||
|  |      *     responses: | ||||||
|  |      *       200: | ||||||
|  |      *         description: Ok | ||||||
|  |      *         content: | ||||||
|  |      *           text/plain: | ||||||
|  |      *             schema: | ||||||
|  |      *               $ref: '#/components/schemas/OkResponse' | ||||||
|  |      */ | ||||||
|     createPlayerAction("/next", globalEvents.next); |     createPlayerAction("/next", globalEvents.next); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @swagger | ||||||
|  |      * /player/previous: | ||||||
|  |      *   post: | ||||||
|  |      *     summary: Play the previous song | ||||||
|  |      *     tags: [player] | ||||||
|  |      *     responses: | ||||||
|  |      *       200: | ||||||
|  |      *         description: Ok | ||||||
|  |      *         content: | ||||||
|  |      *           text/plain: | ||||||
|  |      *             schema: | ||||||
|  |      *               $ref: '#/components/schemas/OkResponse' | ||||||
|  |      */ | ||||||
|     createPlayerAction("/previous", globalEvents.previous); |     createPlayerAction("/previous", globalEvents.previous); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @swagger | ||||||
|  |      * /player/shuffle/toggle: | ||||||
|  |      *   post: | ||||||
|  |      *     summary: Play the previous song | ||||||
|  |      *     tags: [player] | ||||||
|  |      *     responses: | ||||||
|  |      *       200: | ||||||
|  |      *         description: Ok | ||||||
|  |      *         content: | ||||||
|  |      *           text/plain: | ||||||
|  |      *             schema: | ||||||
|  |      *               $ref: '#/components/schemas/OkResponse' | ||||||
|  |      */ | ||||||
|     createPlayerAction("/shuffle/toggle", globalEvents.toggleShuffle); |     createPlayerAction("/shuffle/toggle", globalEvents.toggleShuffle); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @swagger | ||||||
|  |      * /player/repeat/toggle: | ||||||
|  |      *   post: | ||||||
|  |      *     summary: Toggle the repeat status, toggles between "off" , "single" and "all" | ||||||
|  |      *     tags: [player] | ||||||
|  |      *     responses: | ||||||
|  |      *       200: | ||||||
|  |      *         description: Ok | ||||||
|  |      *         content: | ||||||
|  |      *           text/plain: | ||||||
|  |      *             schema: | ||||||
|  |      *               $ref: '#/components/schemas/OkResponse' | ||||||
|  |      */ | ||||||
|     createPlayerAction("/repeat/toggle", globalEvents.toggleRepeat); |     createPlayerAction("/repeat/toggle", globalEvents.toggleRepeat); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @swagger | ||||||
|  |      * /player/playpause: | ||||||
|  |      *   post: | ||||||
|  |      *     summary: Start playing the media if paused, or pause the media if playing | ||||||
|  |      *     tags: [player] | ||||||
|  |      *     responses: | ||||||
|  |      *       200: | ||||||
|  |      *         description: Ok | ||||||
|  |      *         content: | ||||||
|  |      *           text/plain: | ||||||
|  |      *             schema: | ||||||
|  |      *               $ref: '#/components/schemas/OkResponse' | ||||||
|  |      */ | ||||||
|     expressApp.post(createRoute("/playpause"), (req, res) => { |     expressApp.post(createRoute("/playpause"), (req, res) => { | ||||||
|       if (mediaInfo.status === MediaStatus.playing) { |       if (mediaInfo.status === MediaStatus.playing) { | ||||||
|         windowEvent(res, globalEvents.pause); |         windowEvent(res, globalEvents.pause); | ||||||
|   | |||||||
| @@ -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); | ||||||
|   | |||||||
| @@ -13,19 +13,132 @@ import { getCurrentImage } from "./features/current"; | |||||||
|  * @param mainWindow |  * @param mainWindow | ||||||
|  */ |  */ | ||||||
| export const addLegacyApi = (expressApp: Router, mainWindow: BrowserWindow) => { | export const addLegacyApi = (expressApp: Router, mainWindow: BrowserWindow) => { | ||||||
|  |   /** | ||||||
|  |    * @swagger | ||||||
|  |    * /image: | ||||||
|  |    *   get: | ||||||
|  |    *     summary: Get current image | ||||||
|  |    *     tags: [legacy] | ||||||
|  |    *     deprecated: true | ||||||
|  |    *     responses: | ||||||
|  |    *       200: | ||||||
|  |    *         description: Current image | ||||||
|  |    *         content: | ||||||
|  |    *           image/png: | ||||||
|  |    *             schema: | ||||||
|  |    *               type: string | ||||||
|  |    *               format: binary | ||||||
|  |    *       404: | ||||||
|  |    *         description: Not found | ||||||
|  |    */ | ||||||
|   expressApp.get("/image", getCurrentImage); |   expressApp.get("/image", getCurrentImage); | ||||||
|  |  | ||||||
|   if (settingsStore.get(settings.playBackControl)) { |   if (settingsStore.get(settings.playBackControl)) { | ||||||
|     addLegacyControls(); |     addLegacyControls(); | ||||||
|   } |   } | ||||||
|   function addLegacyControls() { |   function addLegacyControls() { | ||||||
|  |     /** | ||||||
|  |      * @swagger | ||||||
|  |      * /play: | ||||||
|  |      *   get: | ||||||
|  |      *     summary: Play the current media | ||||||
|  |      *     tags: [legacy] | ||||||
|  |      *     deprecated: true | ||||||
|  |      *     responses: | ||||||
|  |      *       200: | ||||||
|  |      *         description: Action performed | ||||||
|  |      *         content: | ||||||
|  |      *           text/plain: | ||||||
|  |      *             schema: | ||||||
|  |      *               $ref: '#/components/schemas/OkResponse' | ||||||
|  |      */ | ||||||
|     expressApp.get("/play", ({ res }) => handleGlobalEvent(res, globalEvents.play)); |     expressApp.get("/play", ({ res }) => handleGlobalEvent(res, globalEvents.play)); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @swagger | ||||||
|  |      * /favorite/toggle: | ||||||
|  |      *   get: | ||||||
|  |      *     summary: Add the current media to your favorites, or remove it if its already added to your favorites | ||||||
|  |      *     tags: [legacy] | ||||||
|  |      *     deprecated: true | ||||||
|  |      *     responses: | ||||||
|  |      *       200: | ||||||
|  |      *         description: Ok | ||||||
|  |      *         content: | ||||||
|  |      *           text/plain: | ||||||
|  |      *             schema: | ||||||
|  |      *               $ref: '#/components/schemas/OkResponse' | ||||||
|  |      */ | ||||||
|     expressApp.post("/favorite/toggle", (req, res) => |     expressApp.post("/favorite/toggle", (req, res) => | ||||||
|       handleGlobalEvent(res, globalEvents.toggleFavorite) |       handleGlobalEvent(res, globalEvents.toggleFavorite) | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @swagger | ||||||
|  |      * /pause: | ||||||
|  |      *   get: | ||||||
|  |      *     summary: Pause the current media | ||||||
|  |      *     tags: [legacy] | ||||||
|  |      *     deprecated: true | ||||||
|  |      *     responses: | ||||||
|  |      *       200: | ||||||
|  |      *         description: Ok | ||||||
|  |      *         content: | ||||||
|  |      *           text/plain: | ||||||
|  |      *             schema: | ||||||
|  |      *               $ref: '#/components/schemas/OkResponse' | ||||||
|  |      */ | ||||||
|     expressApp.get("/pause", (req, res) => handleGlobalEvent(res, globalEvents.pause)); |     expressApp.get("/pause", (req, res) => handleGlobalEvent(res, globalEvents.pause)); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @swagger | ||||||
|  |      * /next: | ||||||
|  |      *   get: | ||||||
|  |      *     summary: Play the next song | ||||||
|  |      *     tags: [legacy] | ||||||
|  |      *     deprecated: true | ||||||
|  |      *     responses: | ||||||
|  |      *       200: | ||||||
|  |      *         description: Ok | ||||||
|  |      *         content: | ||||||
|  |      *           text/plain: | ||||||
|  |      *             schema: | ||||||
|  |      *               $ref: '#/components/schemas/OkResponse' | ||||||
|  |      */ | ||||||
|     expressApp.get("/next", (req, res) => handleGlobalEvent(res, globalEvents.next)); |     expressApp.get("/next", (req, res) => handleGlobalEvent(res, globalEvents.next)); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @swagger | ||||||
|  |      * /previous: | ||||||
|  |      *   get: | ||||||
|  |      *     summary: Play the previous song | ||||||
|  |      *     tags: [legacy] | ||||||
|  |      *     deprecated: true | ||||||
|  |      *     responses: | ||||||
|  |      *       200: | ||||||
|  |      *         description: Ok | ||||||
|  |      *         content: | ||||||
|  |      *           text/plain: | ||||||
|  |      *             schema: | ||||||
|  |      *               $ref: '#/components/schemas/OkResponse' | ||||||
|  |      */ | ||||||
|     expressApp.get("/previous", (req, res) => handleGlobalEvent(res, globalEvents.previous)); |     expressApp.get("/previous", (req, res) => handleGlobalEvent(res, globalEvents.previous)); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @swagger | ||||||
|  |      * /playpause: | ||||||
|  |      *   get: | ||||||
|  |      *     summary: Toggle play/pause | ||||||
|  |      *     tags: [legacy] | ||||||
|  |      *     deprecated: true | ||||||
|  |      *     responses: | ||||||
|  |      *       200: | ||||||
|  |      *         description: Ok | ||||||
|  |      *         content: | ||||||
|  |      *           text/plain: | ||||||
|  |      *             schema: | ||||||
|  |      *               $ref: '#/components/schemas/OkResponse' | ||||||
|  |      */ | ||||||
|     expressApp.get("/playpause", (req, res) => { |     expressApp.get("/playpause", (req, res) => { | ||||||
|       if (mediaInfo.status === MediaStatus.playing) { |       if (mediaInfo.status === MediaStatus.playing) { | ||||||
|         handleGlobalEvent(res, globalEvents.pause); |         handleGlobalEvent(res, globalEvents.pause); | ||||||
|   | |||||||
| @@ -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.14.0", |     "version": "5.20.1", | ||||||
|     "description": "", |     "description": "", | ||||||
|     "license": { |     "license": { | ||||||
|       "name": "MIT", |       "name": "MIT", | ||||||
| @@ -18,12 +18,194 @@ | |||||||
|     "url": "swagger.json" |     "url": "swagger.json" | ||||||
|   }, |   }, | ||||||
|   "paths": { |   "paths": { | ||||||
|  |     "/current": { | ||||||
|  |       "get": { | ||||||
|  |         "summary": "Get current media info", | ||||||
|  |         "tags": ["current"], | ||||||
|  |         "responses": { | ||||||
|  |           "200": { | ||||||
|  |             "description": "Current media info", | ||||||
|  |             "content": { | ||||||
|  |               "application/json": { | ||||||
|  |                 "schema": { | ||||||
|  |                   "$ref": "#/components/schemas/MediaInfo" | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "/current/image": { | ||||||
|  |       "get": { | ||||||
|  |         "summary": "Get current media image", | ||||||
|  |         "tags": ["current"], | ||||||
|  |         "responses": { | ||||||
|  |           "200": { | ||||||
|  |             "description": "Current media image", | ||||||
|  |             "content": { | ||||||
|  |               "image/png": { | ||||||
|  |                 "schema": { | ||||||
|  |                   "type": "string", | ||||||
|  |                   "format": "binary" | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           }, | ||||||
|  |           "404": { | ||||||
|  |             "description": "Not found" | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "/player/play": { | ||||||
|  |       "post": { | ||||||
|  |         "summary": "Play the current media", | ||||||
|  |         "tags": ["player"], | ||||||
|  |         "responses": { | ||||||
|  |           "200": { | ||||||
|  |             "description": "Ok", | ||||||
|  |             "content": { | ||||||
|  |               "text/plain": { | ||||||
|  |                 "schema": { | ||||||
|  |                   "$ref": "#/components/schemas/OkResponse" | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "/player/favorite/toggle": { | ||||||
|  |       "post": { | ||||||
|  |         "summary": "Add the current media to your favorites, or remove it if its already added to your favorites", | ||||||
|  |         "tags": ["player"], | ||||||
|  |         "responses": { | ||||||
|  |           "200": { | ||||||
|  |             "description": "Ok", | ||||||
|  |             "content": { | ||||||
|  |               "text/plain": { | ||||||
|  |                 "schema": { | ||||||
|  |                   "$ref": "#/components/schemas/OkResponse" | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "/player/pause": { | ||||||
|  |       "post": { | ||||||
|  |         "summary": "Pause the current media", | ||||||
|  |         "tags": ["player"], | ||||||
|  |         "responses": { | ||||||
|  |           "200": { | ||||||
|  |             "description": "Ok", | ||||||
|  |             "content": { | ||||||
|  |               "text/plain": { | ||||||
|  |                 "schema": { | ||||||
|  |                   "$ref": "#/components/schemas/OkResponse" | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "/player/next": { | ||||||
|  |       "post": { | ||||||
|  |         "summary": "Play the next song", | ||||||
|  |         "tags": ["player"], | ||||||
|  |         "responses": { | ||||||
|  |           "200": { | ||||||
|  |             "description": "Ok", | ||||||
|  |             "content": { | ||||||
|  |               "text/plain": { | ||||||
|  |                 "schema": { | ||||||
|  |                   "$ref": "#/components/schemas/OkResponse" | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "/player/previous": { | ||||||
|  |       "post": { | ||||||
|  |         "summary": "Play the previous song", | ||||||
|  |         "tags": ["player"], | ||||||
|  |         "responses": { | ||||||
|  |           "200": { | ||||||
|  |             "description": "Ok", | ||||||
|  |             "content": { | ||||||
|  |               "text/plain": { | ||||||
|  |                 "schema": { | ||||||
|  |                   "$ref": "#/components/schemas/OkResponse" | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "/player/shuffle/toggle": { | ||||||
|  |       "post": { | ||||||
|  |         "summary": "Play the previous song", | ||||||
|  |         "tags": ["player"], | ||||||
|  |         "responses": { | ||||||
|  |           "200": { | ||||||
|  |             "description": "Ok", | ||||||
|  |             "content": { | ||||||
|  |               "text/plain": { | ||||||
|  |                 "schema": { | ||||||
|  |                   "$ref": "#/components/schemas/OkResponse" | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "/player/repeat/toggle": { | ||||||
|  |       "post": { | ||||||
|  |         "summary": "Toggle the repeat status, toggles between \"off\" , \"single\" and \"all\"", | ||||||
|  |         "tags": ["player"], | ||||||
|  |         "responses": { | ||||||
|  |           "200": { | ||||||
|  |             "description": "Ok", | ||||||
|  |             "content": { | ||||||
|  |               "text/plain": { | ||||||
|  |                 "schema": { | ||||||
|  |                   "$ref": "#/components/schemas/OkResponse" | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "/player/playpause": { | ||||||
|  |       "post": { | ||||||
|  |         "summary": "Start playing the media if paused, or pause the media if playing", | ||||||
|  |         "tags": ["player"], | ||||||
|  |         "responses": { | ||||||
|  |           "200": { | ||||||
|  |             "description": "Ok", | ||||||
|  |             "content": { | ||||||
|  |               "text/plain": { | ||||||
|  |                 "schema": { | ||||||
|  |                   "$ref": "#/components/schemas/OkResponse" | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     "/settings/skipped-artists": { |     "/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": [ |         "tags": ["settings"], | ||||||
|           "settings" |  | ||||||
|         ], |  | ||||||
|         "responses": { |         "responses": { | ||||||
|           "200": { |           "200": { | ||||||
|             "description": "The list book.", |             "description": "The list book.", | ||||||
| @@ -39,9 +221,7 @@ | |||||||
|       }, |       }, | ||||||
|       "post": { |       "post": { | ||||||
|         "summary": "Add new artists to the list of skipped artists", |         "summary": "Add new artists to the list of skipped artists", | ||||||
|         "tags": [ |         "tags": ["settings"], | ||||||
|           "settings" |  | ||||||
|         ], |  | ||||||
|         "requestBody": { |         "requestBody": { | ||||||
|           "required": true, |           "required": true, | ||||||
|           "content": { |           "content": { | ||||||
| @@ -62,9 +242,7 @@ | |||||||
|     "/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": [ |         "tags": ["settings"], | ||||||
|           "settings" |  | ||||||
|         ], |  | ||||||
|         "requestBody": { |         "requestBody": { | ||||||
|           "required": true, |           "required": true, | ||||||
|           "content": { |           "content": { | ||||||
| @@ -85,9 +263,7 @@ | |||||||
|     "/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": [ |         "tags": ["settings"], | ||||||
|           "settings" |  | ||||||
|         ], |  | ||||||
|         "responses": { |         "responses": { | ||||||
|           "200": { |           "200": { | ||||||
|             "description": "Ok" |             "description": "Ok" | ||||||
| @@ -96,32 +272,261 @@ | |||||||
|       }, |       }, | ||||||
|       "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": [ |         "tags": ["settings"], | ||||||
|           "settings" |  | ||||||
|         ], |  | ||||||
|         "responses": { |         "responses": { | ||||||
|           "200": { |           "200": { | ||||||
|             "description": "Ok" |             "description": "Ok" | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|  |     }, | ||||||
|  |     "/image": { | ||||||
|  |       "get": { | ||||||
|  |         "summary": "Get current image", | ||||||
|  |         "tags": ["legacy"], | ||||||
|  |         "deprecated": true, | ||||||
|  |         "responses": { | ||||||
|  |           "200": { | ||||||
|  |             "description": "Current image", | ||||||
|  |             "content": { | ||||||
|  |               "image/png": { | ||||||
|  |                 "schema": { | ||||||
|  |                   "type": "string", | ||||||
|  |                   "format": "binary" | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           }, | ||||||
|  |           "404": { | ||||||
|  |             "description": "Not found" | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "/play": { | ||||||
|  |       "get": { | ||||||
|  |         "summary": "Play the current media", | ||||||
|  |         "tags": ["legacy"], | ||||||
|  |         "deprecated": true, | ||||||
|  |         "responses": { | ||||||
|  |           "200": { | ||||||
|  |             "description": "Action performed", | ||||||
|  |             "content": { | ||||||
|  |               "text/plain": { | ||||||
|  |                 "schema": { | ||||||
|  |                   "$ref": "#/components/schemas/OkResponse" | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "/favorite/toggle": { | ||||||
|  |       "get": { | ||||||
|  |         "summary": "Add the current media to your favorites, or remove it if its already added to your favorites", | ||||||
|  |         "tags": ["legacy"], | ||||||
|  |         "deprecated": true, | ||||||
|  |         "responses": { | ||||||
|  |           "200": { | ||||||
|  |             "description": "Ok", | ||||||
|  |             "content": { | ||||||
|  |               "text/plain": { | ||||||
|  |                 "schema": { | ||||||
|  |                   "$ref": "#/components/schemas/OkResponse" | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "/pause": { | ||||||
|  |       "get": { | ||||||
|  |         "summary": "Pause the current media", | ||||||
|  |         "tags": ["legacy"], | ||||||
|  |         "deprecated": true, | ||||||
|  |         "responses": { | ||||||
|  |           "200": { | ||||||
|  |             "description": "Ok", | ||||||
|  |             "content": { | ||||||
|  |               "text/plain": { | ||||||
|  |                 "schema": { | ||||||
|  |                   "$ref": "#/components/schemas/OkResponse" | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "/next": { | ||||||
|  |       "get": { | ||||||
|  |         "summary": "Play the next song", | ||||||
|  |         "tags": ["legacy"], | ||||||
|  |         "deprecated": true, | ||||||
|  |         "responses": { | ||||||
|  |           "200": { | ||||||
|  |             "description": "Ok", | ||||||
|  |             "content": { | ||||||
|  |               "text/plain": { | ||||||
|  |                 "schema": { | ||||||
|  |                   "$ref": "#/components/schemas/OkResponse" | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "/previous": { | ||||||
|  |       "get": { | ||||||
|  |         "summary": "Play the previous song", | ||||||
|  |         "tags": ["legacy"], | ||||||
|  |         "deprecated": true, | ||||||
|  |         "responses": { | ||||||
|  |           "200": { | ||||||
|  |             "description": "Ok", | ||||||
|  |             "content": { | ||||||
|  |               "text/plain": { | ||||||
|  |                 "schema": { | ||||||
|  |                   "$ref": "#/components/schemas/OkResponse" | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "/playpause": { | ||||||
|  |       "get": { | ||||||
|  |         "summary": "Toggle play/pause", | ||||||
|  |         "tags": ["legacy"], | ||||||
|  |         "deprecated": true, | ||||||
|  |         "responses": { | ||||||
|  |           "200": { | ||||||
|  |             "description": "Ok", | ||||||
|  |             "content": { | ||||||
|  |               "text/plain": { | ||||||
|  |                 "schema": { | ||||||
|  |                   "$ref": "#/components/schemas/OkResponse" | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   "components": { |   "components": { | ||||||
|     "schemas": { |     "schemas": { | ||||||
|  |       "MediaInfo": { | ||||||
|  |         "type": "object", | ||||||
|  |         "properties": { | ||||||
|  |           "title": { | ||||||
|  |             "type": "string" | ||||||
|  |           }, | ||||||
|  |           "artists": { | ||||||
|  |             "type": "string" | ||||||
|  |           }, | ||||||
|  |           "album": { | ||||||
|  |             "type": "string" | ||||||
|  |           }, | ||||||
|  |           "icon": { | ||||||
|  |             "type": "string", | ||||||
|  |             "format": "uri" | ||||||
|  |           }, | ||||||
|  |           "playingFrom": { | ||||||
|  |             "type": "string" | ||||||
|  |           }, | ||||||
|  |           "status": { | ||||||
|  |             "type": "string" | ||||||
|  |           }, | ||||||
|  |           "url": { | ||||||
|  |             "type": "string", | ||||||
|  |             "format": "uri" | ||||||
|  |           }, | ||||||
|  |           "current": { | ||||||
|  |             "type": "string" | ||||||
|  |           }, | ||||||
|  |           "currentInSeconds": { | ||||||
|  |             "type": "integer" | ||||||
|  |           }, | ||||||
|  |           "duration": { | ||||||
|  |             "type": "string" | ||||||
|  |           }, | ||||||
|  |           "durationInSeconds": { | ||||||
|  |             "type": "integer" | ||||||
|  |           }, | ||||||
|  |           "image": { | ||||||
|  |             "type": "string", | ||||||
|  |             "format": "uri" | ||||||
|  |           }, | ||||||
|  |           "favorite": { | ||||||
|  |             "type": "boolean" | ||||||
|  |           }, | ||||||
|  |           "player": { | ||||||
|  |             "type": "object", | ||||||
|  |             "properties": { | ||||||
|  |               "status": { | ||||||
|  |                 "type": "string" | ||||||
|  |               }, | ||||||
|  |               "shuffle": { | ||||||
|  |                 "type": "boolean" | ||||||
|  |               }, | ||||||
|  |               "repeat": { | ||||||
|  |                 "type": "string" | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           }, | ||||||
|  |           "artist": { | ||||||
|  |             "type": "string" | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |         "example": { | ||||||
|  |           "title": "Sample Title", | ||||||
|  |           "artists": "Sample Artist", | ||||||
|  |           "album": "Sample Album", | ||||||
|  |           "icon": "/path/to/sample/icon.jpg", | ||||||
|  |           "playingFrom": "Sample Playlist", | ||||||
|  |           "status": "playing", | ||||||
|  |           "url": "https://tidal.com/browse/track/sample", | ||||||
|  |           "current": "1:23", | ||||||
|  |           "currentInSeconds": 83, | ||||||
|  |           "duration": "3:45", | ||||||
|  |           "durationInSeconds": 225, | ||||||
|  |           "image": "https://example.com/sample-image.jpg", | ||||||
|  |           "favorite": true, | ||||||
|  |           "player": { | ||||||
|  |             "status": "playing", | ||||||
|  |             "shuffle": true, | ||||||
|  |             "repeat": "one" | ||||||
|  |           }, | ||||||
|  |           "artist": "Sample Artist" | ||||||
|  |         } | ||||||
|  |       }, | ||||||
|  |       "OkResponse": { | ||||||
|  |         "type": "string", | ||||||
|  |         "example": "OK" | ||||||
|  |       }, | ||||||
|       "StringArray": { |       "StringArray": { | ||||||
|         "type": "array", |         "type": "array", | ||||||
|         "items": { |         "items": { | ||||||
|           "type": "string" |           "type": "string" | ||||||
|         }, |         }, | ||||||
|         "example": [ |         "example": ["Artist1", "Artist2"] | ||||||
|           "Artist1", |  | ||||||
|           "Artist2" |  | ||||||
|         ] |  | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   "tags": [ |   "tags": [ | ||||||
|  |     { | ||||||
|  |       "name": "current", | ||||||
|  |       "description": "The current media info API" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "name": "player", | ||||||
|  |       "description": "The player control API" | ||||||
|  |     }, | ||||||
|     { |     { | ||||||
|       "name": "settings", |       "name": "settings", | ||||||
|       "description": "The settings management API" |       "description": "The settings management API" | ||||||
|   | |||||||
| @@ -48,6 +48,9 @@ export class ListenBrainz { | |||||||
|       } else { |       } else { | ||||||
|         // Fetches the oldData required for scrobbling and proceeds to construct a playing_now data payload for the Playing Now area |         // Fetches the oldData required for scrobbling and proceeds to construct a playing_now data payload for the Playing Now area | ||||||
|         const oldData = ListenBrainzStore.get(ListenBrainzConstants.oldData) as StoreData; |         const oldData = ListenBrainzStore.get(ListenBrainzConstants.oldData) as StoreData; | ||||||
|  |         const tidalUrl = | ||||||
|  |           settingsStore.get<string, string>(settings.advanced.tidalUrl) || | ||||||
|  |           "https://listen.tidal.com"; | ||||||
|         const playing_data = { |         const playing_data = { | ||||||
|           listen_type: "playing_now", |           listen_type: "playing_now", | ||||||
|           payload: [ |           payload: [ | ||||||
| @@ -95,7 +98,7 @@ export class ListenBrainz { | |||||||
|                     additional_info: { |                     additional_info: { | ||||||
|                       media_player: "Tidal Hi-Fi", |                       media_player: "Tidal Hi-Fi", | ||||||
|                       submission_client: "Tidal Hi-Fi", |                       submission_client: "Tidal Hi-Fi", | ||||||
|                       music_service: "listen.tidal.com", |                       music_service: tidalUrl, | ||||||
|                       duration: oldData.duration, |                       duration: oldData.duration, | ||||||
|                     }, |                     }, | ||||||
|                     artist_name: oldData.artists, |                     artist_name: oldData.artists, | ||||||
|   | |||||||
							
								
								
									
										10
									
								
								src/features/sharingService/sharingService.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/features/sharingService/sharingService.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | export class SharingService { | ||||||
|  |   /** | ||||||
|  |    * Retrieve the universal link given a regular track link | ||||||
|  |    * @param currentUrl | ||||||
|  |    * @returns | ||||||
|  |    */ | ||||||
|  |   public static getUniversalLink(currentUrl: string): string { | ||||||
|  |     return `${currentUrl}?u`; | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -1,21 +0,0 @@ | |||||||
| import { ServiceLinks } from "./ServiceLinks"; |  | ||||||
|  |  | ||||||
| export interface Artist { |  | ||||||
|   type: string; |  | ||||||
|   id: number; |  | ||||||
|   path: string; |  | ||||||
|   name: string; |  | ||||||
|   sourceUrl: string; |  | ||||||
|   sourceCountry: string; |  | ||||||
|   url: string; |  | ||||||
|   image: string; |  | ||||||
|   createdAt: string; |  | ||||||
|   updatedAt: string; |  | ||||||
|   refreshedAt: string; |  | ||||||
|   serviceIds: { [key: string]: string }; |  | ||||||
|   orchardId: string; |  | ||||||
|   spotifyId: string; |  | ||||||
|   links: { [key: string]: ServiceLinks[] }; |  | ||||||
|   linksCountries: string[]; |  | ||||||
|   description: string; |  | ||||||
| } |  | ||||||
| @@ -1,4 +0,0 @@ | |||||||
| export interface ServiceLinks { |  | ||||||
|   link: string; |  | ||||||
|   countries: string[]; |  | ||||||
| } |  | ||||||
| @@ -1,27 +0,0 @@ | |||||||
| import { Artist } from "./Artist"; |  | ||||||
| import { ServiceLinks } from "./ServiceLinks"; |  | ||||||
|  |  | ||||||
| export interface WhippedResult { |  | ||||||
|   status: string; |  | ||||||
|   data: { |  | ||||||
|     item: { |  | ||||||
|       type: string; |  | ||||||
|       id: number; |  | ||||||
|       path: string; |  | ||||||
|       name: string; |  | ||||||
|       url: string; |  | ||||||
|       sourceUrl: string; |  | ||||||
|       sourceCountry: string; |  | ||||||
|       releaseDate: string; |  | ||||||
|       createdAt: string; |  | ||||||
|       updatedAt: string; |  | ||||||
|       refreshedAt: string; |  | ||||||
|       image: string; |  | ||||||
|       isrc: string; |  | ||||||
|       isExplicit: boolean; |  | ||||||
|       links: { [key: string]: ServiceLinks[] }; |  | ||||||
|       linksCountries: string[]; |  | ||||||
|       artists: Artist[]; |  | ||||||
|     }; |  | ||||||
|   }; |  | ||||||
| } |  | ||||||
| @@ -1,32 +0,0 @@ | |||||||
| import { WhippedResult } from "./models/whip"; |  | ||||||
| import axios from "axios"; |  | ||||||
|  |  | ||||||
| export class Songwhip { |  | ||||||
|   /** |  | ||||||
|    * Call the songwhip API and create a shareable songwhip page |  | ||||||
|    * @param currentUrl |  | ||||||
|    * @returns |  | ||||||
|    */ |  | ||||||
|   public static async whip(currentUrl: string): Promise<WhippedResult> { |  | ||||||
|     try { |  | ||||||
|       const response = await axios.post("https://songwhip.com/api/songwhip/create", { |  | ||||||
|         url: currentUrl, |  | ||||||
|         // doesn't actually matter.. returns everything the same way anyway |  | ||||||
|         country: "NL", |  | ||||||
|       }); |  | ||||||
|  |  | ||||||
|       return response.data; |  | ||||||
|     } catch (error) { |  | ||||||
|       console.log(JSON.stringify(error)); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   /** |  | ||||||
|    * Transform a songwhip response into a shareable url |  | ||||||
|    * @param response |  | ||||||
|    * @returns |  | ||||||
|    */ |  | ||||||
|   public static getWhipUrl(response: WhippedResult) { |  | ||||||
|     return `https://songwhip.com${response.data.item.url}`; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
							
								
								
									
										16
									
								
								src/main.ts
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								src/main.ts
									
									
									
									
									
								
							| @@ -10,7 +10,7 @@ import { | |||||||
|   releaseInhibitorIfActive, |   releaseInhibitorIfActive, | ||||||
| } from "./features/idleInhibitor/idleInhibitor"; | } from "./features/idleInhibitor/idleInhibitor"; | ||||||
| import { Logger } from "./features/logger"; | import { Logger } from "./features/logger"; | ||||||
| import { Songwhip } from "./features/songwhip/songwhip"; | import { SharingService } from "./features/sharingService/sharingService"; | ||||||
| import { MediaInfo } from "./models/mediaInfo"; | import { MediaInfo } from "./models/mediaInfo"; | ||||||
| import { MediaStatus } from "./models/mediaStatus"; | import { MediaStatus } from "./models/mediaStatus"; | ||||||
| import { initRPC, rpc, unRPC } from "./scripts/discord"; | import { initRPC, rpc, unRPC } from "./scripts/discord"; | ||||||
| @@ -24,10 +24,8 @@ import { | |||||||
|   showSettingsWindow, |   showSettingsWindow, | ||||||
| } from "./scripts/settings"; | } from "./scripts/settings"; | ||||||
| import { addTray, refreshTray } from "./scripts/tray"; | import { addTray, refreshTray } from "./scripts/tray"; | ||||||
| const tidalUrl = "https://listen.tidal.com"; |  | ||||||
| let mainInhibitorId = -1; | let mainInhibitorId = -1; | ||||||
|  |  | ||||||
| initialize(); |  | ||||||
| let mainWindow: BrowserWindow; | let mainWindow: BrowserWindow; | ||||||
| const icon = path.join(__dirname, "../assets/icon.png"); | const icon = path.join(__dirname, "../assets/icon.png"); | ||||||
| const PROTOCOL_PREFIX = "tidal"; | const PROTOCOL_PREFIX = "tidal"; | ||||||
| @@ -40,6 +38,9 @@ const windowPreferences = { | |||||||
| setDefaultFlags(app); | setDefaultFlags(app); | ||||||
| setManagedFlagsFromSettings(app); | setManagedFlagsFromSettings(app); | ||||||
|  |  | ||||||
|  | const tidalUrl = | ||||||
|  |   settingsStore.get<string, string>(settings.advanced.tidalUrl) || "https://listen.tidal.com"; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Update the menuBarVisibility according to the store value |  * Update the menuBarVisibility according to the store value | ||||||
|  * |  * | ||||||
| @@ -96,6 +97,7 @@ function createWindow(options = { x: 0, y: 0, backgroundColor: "white" }) { | |||||||
|       }, |       }, | ||||||
|     }, |     }, | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   enable(mainWindow.webContents); |   enable(mainWindow.webContents); | ||||||
|   registerHttpProtocols(); |   registerHttpProtocols(); | ||||||
|   syncMenuBarWithStore(); |   syncMenuBarWithStore(); | ||||||
| @@ -124,6 +126,7 @@ function createWindow(options = { x: 0, y: 0, backgroundColor: "white" }) { | |||||||
|     } |     } | ||||||
|     return false; |     return false; | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   // Emitted when the window is closed. |   // Emitted when the window is closed. | ||||||
|   mainWindow.on("closed", function () { |   mainWindow.on("closed", function () { | ||||||
|     releaseInhibitorIfActive(mainInhibitorId); |     releaseInhibitorIfActive(mainInhibitorId); | ||||||
| @@ -176,6 +179,7 @@ app.on("ready", async () => { | |||||||
|  |  | ||||||
|   if (isMainInstance() || isMultipleInstancesAllowed()) { |   if (isMainInstance() || isMultipleInstancesAllowed()) { | ||||||
|     await components.whenReady(); |     await components.whenReady(); | ||||||
|  |     initialize(); | ||||||
|  |  | ||||||
|     // Adblock |     // Adblock | ||||||
|     if (settingsStore.get(settings.adBlock)) { |     if (settingsStore.get(settings.adBlock)) { | ||||||
| @@ -186,6 +190,8 @@ app.on("ready", async () => { | |||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     Logger.log("components ready:", components.status()); | ||||||
|  |  | ||||||
|     createWindow(); |     createWindow(); | ||||||
|     addMenu(mainWindow); |     addMenu(mainWindow); | ||||||
|     createSettingsWindow(); |     createSettingsWindow(); | ||||||
| @@ -248,8 +254,8 @@ ipcMain.on(globalEvents.error, (event) => { | |||||||
|   console.log(event); |   console.log(event); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| ipcMain.handle(globalEvents.whip, async (event, url) => { | ipcMain.handle(globalEvents.getUniversalLink, async (event, url) => { | ||||||
|   return Songwhip.whip(url); |   return SharingService.getUniversalLink(url); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| Logger.watch(ipcMain); | Logger.watch(ipcMain); | ||||||
|   | |||||||
| @@ -29,6 +29,7 @@ const switchesWithSettings = { | |||||||
|  |  | ||||||
| let adBlock: HTMLInputElement, | let adBlock: HTMLInputElement, | ||||||
|   api: HTMLInputElement, |   api: HTMLInputElement, | ||||||
|  |   channel: HTMLSelectElement, | ||||||
|   customCSS: HTMLInputElement, |   customCSS: HTMLInputElement, | ||||||
|   disableBackgroundThrottle: HTMLInputElement, |   disableBackgroundThrottle: HTMLInputElement, | ||||||
|   disableHardwareMediaKeys: HTMLInputElement, |   disableHardwareMediaKeys: HTMLInputElement, | ||||||
| @@ -45,6 +46,7 @@ let adBlock: HTMLInputElement, | |||||||
|   singleInstance: HTMLInputElement, |   singleInstance: HTMLInputElement, | ||||||
|   skipArtists: HTMLInputElement, |   skipArtists: HTMLInputElement, | ||||||
|   skippedArtists: HTMLInputElement, |   skippedArtists: HTMLInputElement, | ||||||
|  |   staticWindowTitle: HTMLInputElement, | ||||||
|   theme: HTMLSelectElement, |   theme: HTMLSelectElement, | ||||||
|   trayIcon: HTMLInputElement, |   trayIcon: HTMLInputElement, | ||||||
|   updateFrequency: HTMLInputElement, |   updateFrequency: HTMLInputElement, | ||||||
| @@ -57,6 +59,7 @@ let adBlock: HTMLInputElement, | |||||||
|   discord_include_timestamps: HTMLInputElement, |   discord_include_timestamps: HTMLInputElement, | ||||||
|   discord_button_text: HTMLInputElement, |   discord_button_text: HTMLInputElement, | ||||||
|   discord_show_song: HTMLInputElement, |   discord_show_song: HTMLInputElement, | ||||||
|  |   discord_show_idle: HTMLInputElement, | ||||||
|   discord_idle_text: HTMLInputElement, |   discord_idle_text: HTMLInputElement, | ||||||
|   discord_using_text: HTMLInputElement; |   discord_using_text: HTMLInputElement; | ||||||
|  |  | ||||||
| @@ -121,6 +124,7 @@ function refreshSettings() { | |||||||
|   try { |   try { | ||||||
|     adBlock.checked = settingsStore.get(settings.adBlock); |     adBlock.checked = settingsStore.get(settings.adBlock); | ||||||
|     api.checked = settingsStore.get(settings.api); |     api.checked = settingsStore.get(settings.api); | ||||||
|  |     channel.value = settingsStore.get(settings.advanced.tidalUrl); | ||||||
|     customCSS.value = settingsStore.get<string, string[]>(settings.customCSS).join("\n"); |     customCSS.value = settingsStore.get<string, string[]>(settings.customCSS).join("\n"); | ||||||
|     disableBackgroundThrottle.checked = settingsStore.get(settings.disableBackgroundThrottle); |     disableBackgroundThrottle.checked = settingsStore.get(settings.disableBackgroundThrottle); | ||||||
|     disableHardwareMediaKeys.checked = settingsStore.get(settings.flags.disableHardwareMediaKeys); |     disableHardwareMediaKeys.checked = settingsStore.get(settings.flags.disableHardwareMediaKeys); | ||||||
| @@ -137,8 +141,9 @@ function refreshSettings() { | |||||||
|     port.value = settingsStore.get(settings.apiSettings.port); |     port.value = settingsStore.get(settings.apiSettings.port); | ||||||
|     singleInstance.checked = settingsStore.get(settings.singleInstance); |     singleInstance.checked = settingsStore.get(settings.singleInstance); | ||||||
|     skipArtists.checked = settingsStore.get(settings.skipArtists); |     skipArtists.checked = settingsStore.get(settings.skipArtists); | ||||||
|     theme.value = settingsStore.get(settings.theme); |  | ||||||
|     skippedArtists.value = settingsStore.get<string, string[]>(settings.skippedArtists).join("\n"); |     skippedArtists.value = settingsStore.get<string, string[]>(settings.skippedArtists).join("\n"); | ||||||
|  |     staticWindowTitle.checked = settingsStore.get(settings.staticWindowTitle); | ||||||
|  |     theme.value = settingsStore.get(settings.theme); | ||||||
|     trayIcon.checked = settingsStore.get(settings.trayIcon); |     trayIcon.checked = settingsStore.get(settings.trayIcon); | ||||||
|     updateFrequency.value = settingsStore.get(settings.updateFrequency); |     updateFrequency.value = settingsStore.get(settings.updateFrequency); | ||||||
|     enableListenBrainz.checked = settingsStore.get(settings.ListenBrainz.enabled); |     enableListenBrainz.checked = settingsStore.get(settings.ListenBrainz.enabled); | ||||||
| @@ -149,6 +154,7 @@ function refreshSettings() { | |||||||
|     discord_include_timestamps.checked = settingsStore.get(settings.discord.includeTimestamps); |     discord_include_timestamps.checked = settingsStore.get(settings.discord.includeTimestamps); | ||||||
|     discord_button_text.value = settingsStore.get(settings.discord.buttonText); |     discord_button_text.value = settingsStore.get(settings.discord.buttonText); | ||||||
|     discord_show_song.checked = settingsStore.get(settings.discord.showSong); |     discord_show_song.checked = settingsStore.get(settings.discord.showSong); | ||||||
|  |     discord_show_idle.checked = settingsStore.get(settings.discord.showIdle); | ||||||
|     discord_idle_text.value = settingsStore.get(settings.discord.idleText); |     discord_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); | ||||||
|  |  | ||||||
| @@ -238,6 +244,7 @@ window.addEventListener("DOMContentLoaded", () => { | |||||||
|  |  | ||||||
|   adBlock = get("adBlock"); |   adBlock = get("adBlock"); | ||||||
|   api = get("apiCheckbox"); |   api = get("apiCheckbox"); | ||||||
|  |   channel = get<HTMLSelectElement>("channel"); | ||||||
|   customCSS = get("customCSS"); |   customCSS = get("customCSS"); | ||||||
|   disableBackgroundThrottle = get("disableBackgroundThrottle"); |   disableBackgroundThrottle = get("disableBackgroundThrottle"); | ||||||
|   disableHardwareMediaKeys = get("disableHardwareMediaKeys"); |   disableHardwareMediaKeys = get("disableHardwareMediaKeys"); | ||||||
| @@ -256,6 +263,7 @@ window.addEventListener("DOMContentLoaded", () => { | |||||||
|   trayIcon = get("trayIcon"); |   trayIcon = get("trayIcon"); | ||||||
|   skipArtists = get("skipArtists"); |   skipArtists = get("skipArtists"); | ||||||
|   skippedArtists = get("skippedArtists"); |   skippedArtists = get("skippedArtists"); | ||||||
|  |   staticWindowTitle = get("staticWindowTitle"); | ||||||
|   singleInstance = get("singleInstance"); |   singleInstance = get("singleInstance"); | ||||||
|   updateFrequency = get("updateFrequency"); |   updateFrequency = get("updateFrequency"); | ||||||
|   enableListenBrainz = get("enableListenBrainz"); |   enableListenBrainz = get("enableListenBrainz"); | ||||||
| @@ -266,12 +274,14 @@ window.addEventListener("DOMContentLoaded", () => { | |||||||
|   listenbrainz_delay = get("listenbrainz_delay"); |   listenbrainz_delay = get("listenbrainz_delay"); | ||||||
|   discord_button_text = get("discord_button_text"); |   discord_button_text = get("discord_button_text"); | ||||||
|   discord_show_song = get("discord_show_song"); |   discord_show_song = get("discord_show_song"); | ||||||
|  |   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"); | ||||||
|  |  | ||||||
|   refreshSettings(); |   refreshSettings(); | ||||||
|   addInputListener(adBlock, settings.adBlock); |   addInputListener(adBlock, settings.adBlock); | ||||||
|   addInputListener(api, settings.api); |   addInputListener(api, settings.api); | ||||||
|  |   addSelectListener(channel, settings.advanced.tidalUrl); | ||||||
|   addTextAreaListener(customCSS, settings.customCSS); |   addTextAreaListener(customCSS, settings.customCSS); | ||||||
|   addInputListener(disableBackgroundThrottle, settings.disableBackgroundThrottle); |   addInputListener(disableBackgroundThrottle, settings.disableBackgroundThrottle); | ||||||
|   addInputListener(disableHardwareMediaKeys, settings.flags.disableHardwareMediaKeys); |   addInputListener(disableHardwareMediaKeys, settings.flags.disableHardwareMediaKeys); | ||||||
| @@ -288,6 +298,7 @@ window.addEventListener("DOMContentLoaded", () => { | |||||||
|   addInputListener(port, settings.apiSettings.port); |   addInputListener(port, settings.apiSettings.port); | ||||||
|   addInputListener(skipArtists, settings.skipArtists); |   addInputListener(skipArtists, settings.skipArtists); | ||||||
|   addTextAreaListener(skippedArtists, settings.skippedArtists); |   addTextAreaListener(skippedArtists, settings.skippedArtists); | ||||||
|  |   addInputListener(staticWindowTitle, settings.staticWindowTitle); | ||||||
|   addInputListener(singleInstance, settings.singleInstance); |   addInputListener(singleInstance, settings.singleInstance); | ||||||
|   addSelectListener(theme, settings.theme); |   addSelectListener(theme, settings.theme); | ||||||
|   addInputListener(trayIcon, settings.trayIcon); |   addInputListener(trayIcon, settings.trayIcon); | ||||||
| @@ -308,6 +319,7 @@ window.addEventListener("DOMContentLoaded", () => { | |||||||
|     settings.discord.showSong, |     settings.discord.showSong, | ||||||
|     switchesWithSettings.discord_show_song |     switchesWithSettings.discord_show_song | ||||||
|   ); |   ); | ||||||
|  |   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); | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -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.1/css/font-awesome.min.css"> | ||||||
| </head> | </head> | ||||||
|  |  | ||||||
| <body class="settings-window"> | <body class="settings-window"> | ||||||
| @@ -49,7 +49,7 @@ | |||||||
|             <div class="group__option"> |             <div class="group__option"> | ||||||
|               <div class="group__description"> |               <div class="group__description"> | ||||||
|                 <h4>Notifications</h4> |                 <h4>Notifications</h4> | ||||||
|                 <p>Show a notification when a new song starts.</p> |                 <p>Show a notification when new media starts.</p> | ||||||
|               </div> |               </div> | ||||||
|               <label class="switch"> |               <label class="switch"> | ||||||
|                 <input id="notifications" type="checkbox" /> |                 <input id="notifications" type="checkbox" /> | ||||||
| @@ -106,6 +106,16 @@ | |||||||
|                 <span class="switch__slider"></span> |                 <span class="switch__slider"></span> | ||||||
|               </label> |               </label> | ||||||
|             </div> |             </div> | ||||||
|  |             <div class="group__option"> | ||||||
|  |               <div class="group__description"> | ||||||
|  |                 <h4>Static Window Title</h4> | ||||||
|  |                 <p>Makes the window title "TIDAL Hi-Fi" instead of changing to the currently playing song.</p> | ||||||
|  |               </div> | ||||||
|  |               <label class="switch"> | ||||||
|  |                 <input id="staticWindowTitle" type="checkbox" /> | ||||||
|  |                 <span class="switch__slider"></span> | ||||||
|  |               </label> | ||||||
|  |             </div> | ||||||
|             <div class="group__option"> |             <div class="group__option"> | ||||||
|               <div class="group__description"> |               <div class="group__description"> | ||||||
|                 <h4>Minimize on Close</h4> |                 <h4>Minimize on Close</h4> | ||||||
| @@ -147,7 +157,7 @@ | |||||||
|             <p class="group__title">API</p> |             <p class="group__title">API</p> | ||||||
|             <div class="group__description"> |             <div class="group__description"> | ||||||
|               <p> |               <p> | ||||||
|                 TIDAL Hi-Fi has a built-in web API to allow users to get current song information. |                 TIDAL Hi-Fi has a built-in web API to allow users to get current media information. | ||||||
|                 You can optionally enable playback control as well. |                 You can optionally enable playback control as well. | ||||||
|               </p> |               </p> | ||||||
|             </div> |             </div> | ||||||
| @@ -227,6 +237,17 @@ | |||||||
|             </div> |             </div> | ||||||
|             <div id="discord_options"> |             <div id="discord_options"> | ||||||
|  |  | ||||||
|  |               <div class="group__option" class="hidden"> | ||||||
|  |                 <div class="group__description"> | ||||||
|  |                   <h4>Show Idle Text</h4> | ||||||
|  |                   <p>Should the idle text be shown when idle?</p> | ||||||
|  |                 </div> | ||||||
|  |                 <label class="switch"> | ||||||
|  |                   <input id="discord_show_idle" type="checkbox" /> | ||||||
|  |                   <span class="switch__slider"></span> | ||||||
|  |                 </label> | ||||||
|  |               </div> | ||||||
|  |  | ||||||
|               <div class="group__option" class="hidden"> |               <div class="group__option" class="hidden"> | ||||||
|                 <div class="group__description"> |                 <div class="group__description"> | ||||||
|                   <h4>Idle Text</h4> |                   <h4>Idle Text</h4> | ||||||
| @@ -245,8 +266,8 @@ | |||||||
|  |  | ||||||
|               <div class="group__option" class="hidden"> |               <div class="group__option" class="hidden"> | ||||||
|                 <div class="group__description"> |                 <div class="group__description"> | ||||||
|                   <h4>Show song</h4> |                   <h4>Show media</h4> | ||||||
|                   <p>Show the current song in the Discord client</p> |                   <p>Show the current media in the Discord client</p> | ||||||
|                 </div> |                 </div> | ||||||
|                 <label class="switch"> |                 <label class="switch"> | ||||||
|                   <input id="discord_show_song" type="checkbox" /> |                   <input id="discord_show_song" type="checkbox" /> | ||||||
| @@ -278,7 +299,7 @@ | |||||||
|                 <div class="group__option"> |                 <div class="group__option"> | ||||||
|                   <div class="group__description"> |                   <div class="group__description"> | ||||||
|                     <h4>Button text</h4> |                     <h4>Button text</h4> | ||||||
|                     <p>Text to display on the button below the song information.</p> |                     <p>Text to display on the button below the media information.</p> | ||||||
|                     <input id="discord_button_text" type="text" class="text-input" name="discord_button_text" /> |                     <input id="discord_button_text" type="text" class="text-input" name="discord_button_text" /> | ||||||
|                   </div> |                   </div> | ||||||
|                 </div> |                 </div> | ||||||
| @@ -316,7 +337,7 @@ | |||||||
|             </div> |             </div> | ||||||
|             <div class="group__description"> |             <div class="group__description"> | ||||||
|               <h4>ScrobbleDelay</h4> |               <h4>ScrobbleDelay</h4> | ||||||
|               <p>The delay (in ms) to send a song to ListenBrainz. Prevents spamming the API when you fast forward |               <p>The delay (in ms) to send a listen to ListenBrainz. Prevents spamming the API when you fast forward | ||||||
|                 immediately</p> |                 immediately</p> | ||||||
|               <input id="listenbrainz_delay" type="number" class="text-input" name="listenbrainz_delay" /> |               <input id="listenbrainz_delay" type="number" class="text-input" name="listenbrainz_delay" /> | ||||||
|             </div> |             </div> | ||||||
| @@ -338,6 +359,20 @@ | |||||||
|                 <input id="updateFrequency" type="number" class="text-input" name="updateFrequency" /> |                 <input id="updateFrequency" type="number" class="text-input" name="updateFrequency" /> | ||||||
|               </div> |               </div> | ||||||
|             </div> |             </div> | ||||||
|  |  | ||||||
|  |             <div class="group__option"> | ||||||
|  |               <div class="group__description"> | ||||||
|  |                 <h4>Tidal channel / URL</h4> | ||||||
|  |                 <p> | ||||||
|  |                   Which URL Tidal Hi-Fi should use. | ||||||
|  |                   <strong>note! Beta might break at any time</strong> | ||||||
|  |                 </p> | ||||||
|  |                 <select class="select-input" id="channel" name="channel"> | ||||||
|  |                   <option value="https://listen.tidal.com">Stable (listen.tidal.com)</option> | ||||||
|  |                   <option value="https://listen.stage.tidal.com">Staging (listen.stage.tidal.com)</option> | ||||||
|  |                 </select> | ||||||
|  |               </div> | ||||||
|  |             </div> | ||||||
|           </div> |           </div> | ||||||
|           <div class="group"> |           <div class="group"> | ||||||
|             <p class="group__title">Flags</p> |             <p class="group__title">Flags</p> | ||||||
| @@ -443,7 +478,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.14.0">5.14.0</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/" | ||||||
|   | |||||||
							
								
								
									
										117
									
								
								src/preload.ts
									
									
									
									
									
								
							
							
						
						
									
										117
									
								
								src/preload.ts
									
									
									
									
									
								
							| @@ -10,7 +10,7 @@ import { | |||||||
| } from "./features/listenbrainz/listenbrainz"; | } from "./features/listenbrainz/listenbrainz"; | ||||||
| import { StoreData } from "./features/listenbrainz/models/storeData"; | import { StoreData } from "./features/listenbrainz/models/storeData"; | ||||||
| import { Logger } from "./features/logger"; | import { Logger } from "./features/logger"; | ||||||
| import { Songwhip } from "./features/songwhip/songwhip"; | import { SharingService } from "./features/sharingService/sharingService"; | ||||||
| import { addCustomCss } from "./features/theming/theming"; | import { addCustomCss } from "./features/theming/theming"; | ||||||
| import { convertDurationToSeconds } from "./features/time/parse"; | import { convertDurationToSeconds } from "./features/time/parse"; | ||||||
| import { MediaInfo } from "./models/mediaInfo"; | import { MediaInfo } from "./models/mediaInfo"; | ||||||
| @@ -18,6 +18,7 @@ import { MediaStatus } from "./models/mediaStatus"; | |||||||
| import { RepeatState } from "./models/repeatState"; | import { RepeatState } from "./models/repeatState"; | ||||||
| import { downloadFile } from "./scripts/download"; | import { downloadFile } from "./scripts/download"; | ||||||
| import { addHotkey } from "./scripts/hotkeys"; | import { addHotkey } from "./scripts/hotkeys"; | ||||||
|  | import { ObjectToDotNotation } from "./scripts/objectUtilities"; | ||||||
| import { settingsStore } from "./scripts/settings"; | import { settingsStore } from "./scripts/settings"; | ||||||
| import { setTitle } from "./scripts/window-functions"; | import { setTitle } from "./scripts/window-functions"; | ||||||
|  |  | ||||||
| @@ -47,17 +48,19 @@ const elements = { | |||||||
|   search: '[class^="searchField"]', |   search: '[class^="searchField"]', | ||||||
|   shuffle: '*[data-test="shuffle"]', |   shuffle: '*[data-test="shuffle"]', | ||||||
|   repeat: '*[data-test="repeat"]', |   repeat: '*[data-test="repeat"]', | ||||||
|   account: '*[class^="profileOptions"]', |   account: '*[data-test^="profile-image-button"]', | ||||||
|   settings: '*[data-test^="open-settings"]', |   settings: '*[data-test^="sidebar-menu-button"]', | ||||||
|  |   openSettings: '*[data-test^="open-settings"]', | ||||||
|   media: '*[data-test="current-media-imagery"]', |   media: '*[data-test="current-media-imagery"]', | ||||||
|   image: "img", |   image: "img", | ||||||
|   current: '*[data-test="current-time"]', |   current: '*[data-test="current-time"]', | ||||||
|   duration: '*[class^=playbackControlsContainer] *[data-test="duration"]', |   duration: '*[class^=_playbackControlsContainer] *[data-test="duration"]', | ||||||
|   bar: '*[data-test="progress-bar"]', |   bar: '*[data-test="progress-bar"]', | ||||||
|   footer: "#footerPlayer", |   footer: "#footerPlayer", | ||||||
|   mediaItem: "[data-type='mediaItem']", |   mediaItem: "[data-type='mediaItem']", | ||||||
|   album_header_title: '*[class^="playingFrom"] span:nth-child(2)', |   album_header_title: '*[class^="_playingFrom"] span:nth-child(2)', | ||||||
|   playing_from: '*[class^="playingFrom"] span:nth-child(2)', |   playing_from: '*[class^="_playingFrom"] span:nth-child(2)', | ||||||
|  |   queue_album: "*[class^=playQueueItemsContainer] *[class^=groupTitle] span:nth-child(2)", | ||||||
|   currentlyPlaying: "[class^='isPlayingIcon'], [data-test-is-playing='true']", |   currentlyPlaying: "[class^='isPlayingIcon'], [data-test-is-playing='true']", | ||||||
|   album_name_cell: '[class^="album"]', |   album_name_cell: '[class^="album"]', | ||||||
|   tracklist_row: '[data-test="tracklist-row"]', |   tracklist_row: '[data-test="tracklist-row"]', | ||||||
| @@ -72,7 +75,7 @@ const elements = { | |||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
|    * Get the icon of the current song |    * Get the icon of the current media | ||||||
|    */ |    */ | ||||||
|   getSongIcon: function () { |   getSongIcon: function () { | ||||||
|     const figure = this.get("media"); |     const figure = this.get("media"); | ||||||
| @@ -88,7 +91,7 @@ const elements = { | |||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
|    * returns an array of all artists in the current song |    * returns an array of all artists in the current media | ||||||
|    * @returns {Array} artists |    * @returns {Array} artists | ||||||
|    */ |    */ | ||||||
|   getArtistsArray: function () { |   getArtistsArray: function () { | ||||||
| @@ -112,28 +115,38 @@ const elements = { | |||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   getAlbumName: function () { |   getAlbumName: function () { | ||||||
|     //If listening to an album, get its name from the header title |     try { | ||||||
|     if (window.location.href.includes("/album/")) { |       //If listening to an album, get its name from the header title | ||||||
|       const albumName = window.document.querySelector(this.album_header_title); |       if (globalThis.location.href.includes("/album/")) { | ||||||
|       if (albumName) { |         const albumName = globalThis.document.querySelector(this.album_header_title); | ||||||
|         return albumName.textContent; |         if (albumName) { | ||||||
|       } |           return albumName.textContent; | ||||||
|       //If listening to a playlist or a mix, get album name from the list |         } | ||||||
|     } else if ( |         //If listening to a playlist or a mix, get album name from the list | ||||||
|       window.location.href.includes("/playlist/") || |       } else if ( | ||||||
|       window.location.href.includes("/mix/") |         globalThis.location.href.includes("/playlist/") || | ||||||
|     ) { |         globalThis.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. |         if (this.currentlyPlaying === MediaStatus.playing) { | ||||||
|         // document.querySelector("[class^='isPlayingIcon'], [data-test-is-playing='true']").closest('[data-type="mediaItem"]').querySelector('[class^="album"]').textContent |           // 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. | ||||||
|         const row = window.document.querySelector(this.currentlyPlaying).closest(this.mediaItem); |           // document.querySelector("[class^='isPlayingIcon'], [data-test-is-playing='true']").closest('[data-type="mediaItem"]').querySelector('[class^="album"]').textContent | ||||||
|         if (row) { |           const row = window.document.querySelector(this.currentlyPlaying).closest(this.mediaItem); | ||||||
|           return row.querySelector(this.album_name_cell).textContent; |           if (row) { | ||||||
|  |             return row.querySelector(this.album_name_cell).textContent; | ||||||
|  |           } | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return ""; |       // 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 () { |   isMuted: function () { | ||||||
| @@ -187,7 +200,7 @@ function getUpdateFrequency() { | |||||||
| } | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Play or pause the current song |  * Play or pause the current media | ||||||
|  */ |  */ | ||||||
| function playPause() { | function playPause() { | ||||||
|   const play = elements.get("play"); |   const play = elements.get("play"); | ||||||
| @@ -212,9 +225,9 @@ ListenBrainzStore.clear(); | |||||||
| function addHotKeys() { | function addHotKeys() { | ||||||
|   if (settingsStore.get(settings.enableCustomHotkeys)) { |   if (settingsStore.get(settings.enableCustomHotkeys)) { | ||||||
|     addHotkey("Control+p", function () { |     addHotkey("Control+p", function () { | ||||||
|       elements.click("account"); |       elements.click("settings"); | ||||||
|       setTimeout(() => { |       setTimeout(() => { | ||||||
|         elements.click("settings"); |         elements.click("openSettings"); | ||||||
|       }, 100); |       }, 100); | ||||||
|     }); |     }); | ||||||
|     addHotkey("Control+l", function () { |     addHotkey("Control+l", function () { | ||||||
| @@ -246,11 +259,10 @@ function addHotKeys() { | |||||||
|       elements.click("repeat"); |       elements.click("repeat"); | ||||||
|     }); |     }); | ||||||
|     addHotkey("control+w", async function () { |     addHotkey("control+w", async function () { | ||||||
|       const result = await ipcRenderer.invoke(globalEvents.whip, getTrackURL()); |       const url = SharingService.getUniversalLink(getTrackURL()); | ||||||
|       const url = Songwhip.getWhipUrl(result); |  | ||||||
|       clipboard.writeText(url); |       clipboard.writeText(url); | ||||||
|       new Notification({ |       new Notification({ | ||||||
|         title: `Successfully whipped: `, |         title: `Universal link generated: `, | ||||||
|         body: `URL copied to clipboard: ${url}`, |         body: `URL copied to clipboard: ${url}`, | ||||||
|       }).show(); |       }).show(); | ||||||
|     }); |     }); | ||||||
| @@ -386,18 +398,30 @@ function updateMediaInfo(mediaInfo: MediaInfo, notify: boolean) { | |||||||
|   if (mediaInfo) { |   if (mediaInfo) { | ||||||
|     currentMediaInfo = mediaInfo; |     currentMediaInfo = mediaInfo; | ||||||
|     ipcRenderer.send(globalEvents.updateInfo, mediaInfo); |     ipcRenderer.send(globalEvents.updateInfo, mediaInfo); | ||||||
|     if (settingsStore.get(settings.notifications) && notify) { |  | ||||||
|       if (currentNotification) currentNotification.close(); |  | ||||||
|       currentNotification = new Notification({ |  | ||||||
|         title: mediaInfo.title, |  | ||||||
|         body: mediaInfo.artists, |  | ||||||
|         icon: mediaInfo.icon, |  | ||||||
|       }); |  | ||||||
|       currentNotification.show(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     updateMpris(mediaInfo); |     updateMpris(mediaInfo); | ||||||
|     updateListenBrainz(mediaInfo); |     updateListenBrainz(mediaInfo); | ||||||
|  |     if (notify) { | ||||||
|  |       sendNotification(mediaInfo); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * send a desktop notification if enabled in settings | ||||||
|  |  * @param mediaInfo | ||||||
|  |  * @param notify Whether to notify | ||||||
|  |  */ | ||||||
|  | async function sendNotification(mediaInfo: MediaInfo) { | ||||||
|  |   if (settingsStore.get(settings.notifications)) { | ||||||
|  |     if (currentNotification) { | ||||||
|  |       currentNotification.close(); | ||||||
|  |     } | ||||||
|  |     currentNotification = new Notification({ | ||||||
|  |       title: mediaInfo.title, | ||||||
|  |       body: mediaInfo.artists, | ||||||
|  |       icon: mediaInfo.icon, | ||||||
|  |     }); | ||||||
|  |     currentNotification.show(); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -463,10 +487,12 @@ function updateMpris(mediaInfo: MediaInfo) { | |||||||
|         "xesam:title": mediaInfo.title, |         "xesam:title": mediaInfo.title, | ||||||
|         "xesam:artist": [mediaInfo.artists], |         "xesam:artist": [mediaInfo.artists], | ||||||
|         "xesam:album": mediaInfo.album, |         "xesam:album": mediaInfo.album, | ||||||
|  |         "xesam:url": mediaInfo.url, | ||||||
|         "mpris:artUrl": mediaInfo.image, |         "mpris:artUrl": mediaInfo.image, | ||||||
|         "mpris:length": convertDuration(mediaInfo.duration) * 1000 * 1000, |         "mpris:length": convertDuration(mediaInfo.duration) * 1000 * 1000, | ||||||
|         "mpris:trackid": "/org/mpris/MediaPlayer2/track/" + getTrackID(), |         "mpris:trackid": "/org/mpris/MediaPlayer2/track/" + getTrackID(), | ||||||
|       }, |       }, | ||||||
|  |       ...ObjectToDotNotation(mediaInfo, "custom:"), | ||||||
|     }; |     }; | ||||||
|     player.playbackStatus = mediaInfo.status === MediaStatus.paused ? "Paused" : "Playing"; |     player.playbackStatus = mediaInfo.status === MediaStatus.paused ? "Paused" : "Playing"; | ||||||
|   } |   } | ||||||
| @@ -529,6 +555,7 @@ setInterval(function () { | |||||||
|   const artistsArray = elements.getArtistsArray(); |   const artistsArray = elements.getArtistsArray(); | ||||||
|   const artistsString = elements.getArtistsString(artistsArray); |   const artistsString = elements.getArtistsString(artistsArray); | ||||||
|   const songDashArtistTitle = `${title} - ${artistsString}`; |   const songDashArtistTitle = `${title} - ${artistsString}`; | ||||||
|  |   const staticTitle = "TIDAL Hi-Fi"; | ||||||
|   const titleOrArtistsChanged = currentSong !== songDashArtistTitle; |   const titleOrArtistsChanged = currentSong !== songDashArtistTitle; | ||||||
|   const current = elements.getText("current"); |   const current = elements.getText("current"); | ||||||
|   const currentStatus = getCurrentlyPlayingStatus(); |   const currentStatus = getCurrentlyPlayingStatus(); | ||||||
| @@ -573,7 +600,9 @@ setInterval(function () { | |||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     // update title, url and play info with new info |     // update title, url and play info with new info | ||||||
|     setTitle(songDashArtistTitle); |     settingsStore.get(settings.staticWindowTitle) | ||||||
|  |       ? setTitle(staticTitle) | ||||||
|  |       : setTitle(songDashArtistTitle); | ||||||
|     getTrackURL(); |     getTrackURL(); | ||||||
|     currentSong = songDashArtistTitle; |     currentSong = songDashArtistTitle; | ||||||
|     currentPlayStatus = currentStatus; |     currentPlayStatus = currentStatus; | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| import { Client, Presence } from "discord-rpc"; | import { Client, SetActivity } from "@xhayper/discord-rpc"; | ||||||
| import { app, ipcMain } from "electron"; | import { app, ipcMain } from "electron"; | ||||||
| import { globalEvents } from "../constants/globalEvents"; | import { globalEvents } from "../constants/globalEvents"; | ||||||
| import { settings } from "../constants/settings"; | import { settings } from "../constants/settings"; | ||||||
| @@ -12,9 +12,13 @@ const clientId = "833617820704440341"; | |||||||
|  |  | ||||||
| export let rpc: Client; | export let rpc: Client; | ||||||
|  |  | ||||||
|  | const ACTIVITY_LISTENING = 2; | ||||||
|  | const MAX_RETRIES = 5; | ||||||
|  | const RETRY_DELAY = 10000; | ||||||
|  |  | ||||||
| const observer = () => { | const observer = () => { | ||||||
|   if (rpc) { |   if (rpc) { | ||||||
|     rpc.setActivity(getActivity()); |     updateActivity(); | ||||||
|   } |   } | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @@ -22,10 +26,20 @@ 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 | ||||||
| }; | }; | ||||||
|  |  | ||||||
| const getActivity = (): Presence => { | const updateActivity = () => { | ||||||
|   const presence: Presence = { ...defaultPresence }; |   const showIdle = settingsStore.get<string, boolean>(settings.discord.showIdle) ?? true; | ||||||
|  |   if (mediaInfo.status === MediaStatus.paused && !showIdle) { | ||||||
|  |     rpc.user?.clearActivity(); | ||||||
|  |   } else { | ||||||
|  |     rpc.user?.setActivity(getActivity()); | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const getActivity = (): SetActivity => { | ||||||
|  |   const presence: SetActivity = { ...defaultPresence }; | ||||||
|  |  | ||||||
|   if (mediaInfo.status === MediaStatus.paused) { |   if (mediaInfo.status === MediaStatus.paused) { | ||||||
|     presence.details = |     presence.details = | ||||||
| @@ -41,6 +55,7 @@ const getActivity = (): Presence => { | |||||||
|         settingsStore.get<string, string>(settings.discord.usingText) ?? "Playing media on TIDAL"; |         settingsStore.get<string, string>(settings.discord.usingText) ?? "Playing media on TIDAL"; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   return presence; |   return presence; | ||||||
|  |  | ||||||
|   function getFromStore() { |   function getFromStore() { | ||||||
| @@ -54,18 +69,33 @@ const getActivity = (): Presence => { | |||||||
|     return { includeTimestamps, detailsPrefix, buttonText }; |     return { includeTimestamps, detailsPrefix, buttonText }; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * Pad a string using spaces to at least 2 characters | ||||||
|  |    * @param input string to pad with 2 characters | ||||||
|  |    * @returns | ||||||
|  |    */ | ||||||
|  |   function pad(input: string): string { | ||||||
|  |     return input.padEnd(2, " "); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   function setPresenceFromMediaInfo(detailsPrefix: string, buttonText: string) { |   function setPresenceFromMediaInfo(detailsPrefix: string, buttonText: string) { | ||||||
|  |     // discord requires a minimum of 2 characters | ||||||
|  |     const title = pad(mediaInfo.title); | ||||||
|  |     const album = pad(mediaInfo.album); | ||||||
|  |     const artists = pad(mediaInfo.artists); | ||||||
|  |  | ||||||
|     if (mediaInfo.url) { |     if (mediaInfo.url) { | ||||||
|       presence.details = `${detailsPrefix}${mediaInfo.title}`; |       presence.details = `${detailsPrefix}${title}`; | ||||||
|       presence.state = mediaInfo.artists ? mediaInfo.artists : "unknown artist(s)"; |       presence.state = artists ? artists : "unknown artist(s)"; | ||||||
|       presence.largeImageKey = mediaInfo.image; |       presence.largeImageKey = mediaInfo.image; | ||||||
|       if (mediaInfo.album) { |       if (album) { | ||||||
|         presence.largeImageText = mediaInfo.album; |         presence.largeImageText = album; | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       presence.buttons = [{ label: buttonText, url: mediaInfo.url }]; |       presence.buttons = [{ label: buttonText, url: mediaInfo.url }]; | ||||||
|     } else { |     } else { | ||||||
|       presence.details = `Watching ${mediaInfo.title}`; |       presence.details = `Watching ${title}`; | ||||||
|       presence.state = mediaInfo.artists; |       presence.state = artists; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -73,11 +103,29 @@ const getActivity = (): Presence => { | |||||||
|     if (includeTimestamps) { |     if (includeTimestamps) { | ||||||
|       const currentSeconds = convertDurationToSeconds(mediaInfo.current); |       const currentSeconds = convertDurationToSeconds(mediaInfo.current); | ||||||
|       const durationSeconds = convertDurationToSeconds(mediaInfo.duration); |       const durationSeconds = convertDurationToSeconds(mediaInfo.duration); | ||||||
|       const date = new Date(); |       const now = Math.trunc((Date.now() + 500) / 1000); | ||||||
|       const now = (date.getTime() / 1000) | 0; |       presence.startTimestamp = now - currentSeconds; | ||||||
|       const remaining = date.setSeconds(date.getSeconds() + (durationSeconds - currentSeconds)); |       presence.endTimestamp = presence.startTimestamp + durationSeconds; | ||||||
|       presence.startTimestamp = now; |     } | ||||||
|       presence.endTimestamp = remaining; |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Try to login to RPC and retry if it errors | ||||||
|  |  * @param retryCount Max retry count | ||||||
|  |  */ | ||||||
|  | const connectWithRetry = async (retryCount = 0) => { | ||||||
|  |   try { | ||||||
|  |     await rpc.login(); | ||||||
|  |     Logger.log('Connected to Discord'); | ||||||
|  |     rpc.on("ready", updateActivity); | ||||||
|  |     Object.values(globalEvents).forEach(event => ipcMain.on(event, observer)); | ||||||
|  |   } catch (error) { | ||||||
|  |     if (retryCount < MAX_RETRIES) { | ||||||
|  |       Logger.log(`Failed to connect to Discord, retrying in ${RETRY_DELAY/1000} seconds... (Attempt ${retryCount + 1}/${MAX_RETRIES})`); | ||||||
|  |       setTimeout(() => connectWithRetry(retryCount + 1), RETRY_DELAY); | ||||||
|  |     } else { | ||||||
|  |       Logger.log('Failed to connect to Discord after maximum retry attempts'); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| }; | }; | ||||||
| @@ -86,18 +134,8 @@ const getActivity = (): Presence => { | |||||||
|  * Set up the discord rpc and listen on globalEvents.updateInfo |  * Set up the discord rpc and listen on globalEvents.updateInfo | ||||||
|  */ |  */ | ||||||
| export const initRPC = () => { | export const initRPC = () => { | ||||||
|   rpc = new Client({ transport: "ipc" }); |   rpc = new Client({ transport: {type: "ipc"}, clientId }); | ||||||
|   rpc.login({ clientId }).then( |   connectWithRetry(); | ||||||
|     () => { |  | ||||||
|       rpc.on("ready", () => { |  | ||||||
|         rpc.setActivity(getActivity()); |  | ||||||
|       }); |  | ||||||
|       ipcMain.on(globalEvents.updateInfo, observer); |  | ||||||
|     }, |  | ||||||
|     () => { |  | ||||||
|       Logger.log("Can't connect to Discord, is it running?"); |  | ||||||
|     } |  | ||||||
|   ); |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -105,7 +143,7 @@ export const initRPC = () => { | |||||||
|  */ |  */ | ||||||
| export const unRPC = () => { | export const unRPC = () => { | ||||||
|   if (rpc) { |   if (rpc) { | ||||||
|     rpc.clearActivity(); |     rpc.user?.clearActivity(); | ||||||
|     rpc.destroy(); |     rpc.destroy(); | ||||||
|     rpc = null; |     rpc = null; | ||||||
|     ipcMain.removeListener(globalEvents.updateInfo, observer); |     ipcMain.removeListener(globalEvents.updateInfo, observer); | ||||||
|   | |||||||
							
								
								
									
										13
									
								
								src/scripts/objectUtilities.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/scripts/objectUtilities.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||||||
|  | export const ObjectToDotNotation = (obj: any, prefix: string = "", target: any = {}) => { | ||||||
|  |   Object.keys(obj).forEach((key: string) => { | ||||||
|  |     if (typeof obj[key] === "object" && obj[key] !== null) { | ||||||
|  |       ObjectToDotNotation(obj[key], prefix + key + ".", target); | ||||||
|  |     } else { | ||||||
|  |       const dotLocation = prefix + key; | ||||||
|  |       target[dotLocation] = obj[key]; | ||||||
|  |       return target; | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  |   return target; | ||||||
|  | }; | ||||||
| @@ -28,6 +28,9 @@ const buildMigration = ( | |||||||
| export const settingsStore = new Store({ | export const settingsStore = new Store({ | ||||||
|   defaults: { |   defaults: { | ||||||
|     adBlock: false, |     adBlock: false, | ||||||
|  |     advanced: { | ||||||
|  |       tidalUrl: "https://listen.tidal.com", | ||||||
|  |     }, | ||||||
|     api: true, |     api: true, | ||||||
|     apiSettings: { |     apiSettings: { | ||||||
|       port: 47836, |       port: 47836, | ||||||
| @@ -40,6 +43,7 @@ export const settingsStore = new Store({ | |||||||
|     enableDiscord: false, |     enableDiscord: false, | ||||||
|     discord: { |     discord: { | ||||||
|       showSong: true, |       showSong: true, | ||||||
|  |       showIdle: true, | ||||||
|       idleText: "Browsing Tidal", |       idleText: "Browsing Tidal", | ||||||
|       usingText: "Playing media on TIDAL", |       usingText: "Playing media on TIDAL", | ||||||
|       includeTimestamps: true, |       includeTimestamps: true, | ||||||
| @@ -65,6 +69,7 @@ export const settingsStore = new Store({ | |||||||
|     singleInstance: true, |     singleInstance: true, | ||||||
|     skipArtists: false, |     skipArtists: false, | ||||||
|     skippedArtists: [""], |     skippedArtists: [""], | ||||||
|  |     staticWindowTitle: false, | ||||||
|     theme: "none", |     theme: "none", | ||||||
|     trayIcon: true, |     trayIcon: true, | ||||||
|     updateFrequency: 500, |     updateFrequency: 500, | ||||||
| @@ -107,6 +112,14 @@ export const settingsStore = new Store({ | |||||||
|         { key: settings.apiSettings.hostname, value: "127.0.0.1" }, |         { key: settings.apiSettings.hostname, value: "127.0.0.1" }, | ||||||
|       ]); |       ]); | ||||||
|     }, |     }, | ||||||
|  |     "5.15.0": (migrationStore) => { | ||||||
|  |       buildMigration("5.15.0", migrationStore, [ | ||||||
|  |         { key: settings.advanced.tidalUrl, value: "https://listen.tidal.com" }, | ||||||
|  |       ]); | ||||||
|  |     }, | ||||||
|  |     "5.16.0": (migrationStore) => { | ||||||
|  |       buildMigration("5.16.0", migrationStore, [{ key: settings.discord.showIdle, value: "true" }]); | ||||||
|  |     }, | ||||||
|   }, |   }, | ||||||
| }); | }); | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user