Compare commits
	
		
			631 Commits
		
	
	
		
			1
			...
			renovate/a
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | fe6137d549 | ||
| 3c782c460c | |||
|  | feb9e2cb53 | ||
| be2c00f3b4 | |||
|  | f992ca3ed0 | ||
| 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 | |||
| 3641f07558 | |||
| ecbfa7e226 | |||
| 9321acc06e | |||
| 5b656ae229 | |||
| 0a8efc730d | |||
| b49bd925da | |||
| 1e6b9f7dcf | |||
| 51f7a96634 | |||
| 46074c5de5 | |||
| 2667f62674 | |||
| ac949dc211 | |||
| 40bc20582f | |||
|  | 180d9c97a7 | ||
| 5dc136138b | |||
| 1edc6a1b2b | |||
| 7c6831c771 | |||
| d47da91e93 | |||
| b481108af1 | |||
| 3740ce5a12 | |||
| a0f9faa753 | |||
| 5e3583534b | |||
|  | 5f8cf33249 | ||
|  | 2d94b4bf49 | ||
| 6e43cbb4d7 | |||
| f95f13b44a | |||
| f911564d8a | |||
| db8a2c2741 | |||
| 000853414e | |||
| 53603c4cad | |||
| 0b595f920f | |||
| 81143af3fa | |||
|  | 8d1ac3be3b | ||
|  | 666e602c02 | ||
|  | 04ec850005 | ||
| 943d9b5bd8 | |||
|  | 755816c2b8 | ||
| 25afd05ad7 | |||
| 6e5024742a | |||
| 417afaab85 | |||
| d225c0056b | |||
|  | a75b0336db | ||
|  | 29465ce13a | ||
|  | d333047269 | ||
|  | 712330f8f1 | ||
|  | 84fd35ce0e | ||
|  | 326038f262 | ||
| a6c1d35a60 | |||
|  | c09a4bc4a8 | ||
|  | 554cb12a01 | ||
| 2e31b5d913 | |||
| 2fd29c1b83 | |||
| b2f27a2afe | |||
| 8e11fd7f09 | |||
| 17b2818b70 | |||
| 4ef76c262e | |||
| fd0dae2762 | |||
| aa59bdc6dd | |||
| 5b5b6ecb38 | |||
| 5983145857 | |||
| 0c7d579951 | |||
|  | 235d916749 | ||
|  | 2d9f268866 | ||
| ae65e57e32 | |||
|  | 3f2d69f2f4 | ||
| 5ff2cc68d3 | |||
| daabe5bdbb | |||
|  | 456727c0e0 | ||
|  | ba50e0c095 | ||
|  | 312e90e8cb | ||
| 76769dfab3 | |||
|  | 565d32ae3d | ||
| 7be6f79040 | |||
|  | f894c82b12 | ||
| 21cb0ea79d | |||
| 49bc737485 | |||
| 10a4af8e90 | |||
| 317a685813 | |||
| 4da6d9feda | |||
| b11dbbd6d8 | |||
| 887a3d8a45 | |||
|  | 2e17b066a3 | ||
|  | e37b2f99cc | ||
| 1afd4d22a6 | |||
|  | 12a919df45 | ||
|  | 36a2367397 | ||
|  | 7c6d2df16a | ||
|  | 22383a9f45 | ||
|  | 623033ccd7 | ||
| 5bd28913da | |||
| 5240f1eeeb | |||
| 5e82c18d8a | |||
| 1d19857977 | |||
| 98f75418eb | |||
|  | 0d1a533f71 | ||
| 000bade444 | |||
| 69f2e26ca9 | |||
|  | 60d7da4652 | ||
|  | 6ef6bc0d40 | ||
| 89592bcf4d | |||
| f5185c6627 | |||
|  | a01fcd0791 | ||
|  | 69eef58f8e | ||
| ff060a31e5 | |||
| cbc7fc4a4e | |||
| 173c502143 | |||
|  | 5111af6a71 | ||
|  | f094139794 | ||
| b3d9b187c1 | |||
|  | 276632ea9d | ||
|  | c298b73773 | ||
| d14dbad9ea | |||
| 9e8f6a61f3 | |||
| 387a544b0f | |||
|  | 76fa8de96c | ||
|  | fc2d5d20ca | ||
|  | 9ba2d0fe26 | ||
| d94d42e2bd | |||
|  | 00db9f753e | ||
| 696a2730be | |||
| 6a5814c446 | |||
|  | 2326c6dd6a | ||
| 0dadce4596 | |||
| 9e2cbaed38 | |||
| 33070c157a | |||
| eb91b66ac6 | |||
| 68f76a9e63 | |||
| cbb22ba688 | |||
| 3df82b93db | |||
|  | 789ba83936 | ||
| a962029b0b | |||
|  | b3fffc78ec | ||
| d8e4a493b9 | |||
| 3d0b38361a | |||
| 1c7385fa50 | |||
| 510812f384 | |||
| 5144f67fbc | |||
| 81b81580c6 | |||
| 534547ce67 | |||
|  | 1610e3cc05 | ||
|  | e50e7de12e | ||
|  | 42be522b8e | ||
| 40d80e0872 | |||
| 239139e674 | |||
| dc87b20ab8 | |||
| c7b3921514 | |||
| 89f1ff4228 | |||
| a0c73596e4 | |||
| aa17d80450 | |||
| 5ea3972053 | |||
| 4b81378423 | |||
| c7931cf913 | |||
| c6dff0b0e5 | |||
| 644beea2a6 | |||
| df1c45982b | |||
| ec82aa8401 | |||
| 586f7b595b | |||
|  | de8a5a1b07 | ||
|  | 38c1f05c35 | ||
|  | ed6f04b6d4 | ||
|  | ffe8278c8c | ||
|  | e9434cc5ea | ||
|  | d81912db0c | ||
|  | c0110632e6 | ||
|  | 3571289d28 | ||
| 11cc209025 | |||
| f5ccbda7d9 | |||
| e8cf1783e8 | |||
| 8037a73e57 | |||
| 45e191dae0 | |||
| f147536b12 | |||
| d03bb58afa | |||
| a39fef8d49 | |||
| 41ca1d5a43 | |||
| 6969de8270 | |||
| ad05b767d8 | |||
|  | 6d873ce287 | ||
| 63d123f96a | |||
| f038412c50 | |||
| ff02287df7 | |||
|  | f221ded108 | ||
| 1440f70100 | |||
| 439333e15a | |||
| b9854e0595 | |||
| 8b56c28d75 | |||
| 700a14fe88 | |||
| 3c835077d5 | |||
| 194de286c8 | |||
| a7dee5c2c9 | |||
| 8036cbb919 | |||
| 90cf231c76 | |||
| 42a70534f2 | |||
| b07865d98b | |||
| cc26bfa080 | |||
| 822bdf401e | |||
| a169c57a52 | |||
| 60eb1bbef9 | |||
| 1761c8dd40 | |||
| 62244f432a | |||
| a408a6a8cc | |||
| 6e5a2c626c | |||
| 4350ab9bd9 | |||
| 77a853e980 | |||
| 757f8511c0 | |||
| 2ef457be2c | |||
| 2c5d2b9530 | |||
| 757bd0da80 | |||
| d823f07ed8 | |||
| 32ade76ae3 | |||
| a1c02dfed3 | |||
| 21d6e57cb9 | |||
| 53e4711c39 | |||
| e8509d42e7 | |||
| 46d030cf8e | |||
| 412f1ae3e3 | |||
| 68f0c89ec2 | |||
| 8d44ff8afb | |||
| bccc979f43 | |||
| 6849952c41 | |||
| 07be74af9f | |||
| fc6adc25ca | |||
| 4498e8a73e | |||
| 3d2a9c3992 | |||
| af6bfaf55e | |||
|  | 8bac90e0f1 | ||
|  | 887c75f61a | ||
|  | cde7408cc4 | ||
| 05b422e045 | |||
| 35289d8216 | |||
| ea42b79cd8 | |||
| 6d859cf780 | |||
| af20092053 | |||
| 166ca353cf | |||
| b807aa2f76 | |||
|  | ef8ffe47f5 | ||
| ba7b2a5717 | |||
| 0d93bedb4d | |||
|  | 1de71aa82b | ||
|  | b2e68f5a8f | ||
|  | a2a2023853 | ||
|  | 26c8a38350 | ||
| eb93fbc35d | |||
|  | d3c56fa445 | ||
| 6998992011 | |||
| 108e1d65d4 | |||
| 1097f83911 | |||
| 8c734777cc | |||
|  | de17ac6113 | ||
|  | ced41c00d7 | ||
|  | 744016f307 | ||
|  | ad8ef71c6b | ||
|  | 0120391418 | ||
| d0f9a34f9c | |||
| 3b316f2301 | |||
| c0d9cd2834 | |||
| 0620d87d8b | |||
| 57b7f9148f | |||
| 63ccff97ea | |||
| 3a4d23738f | |||
| c96bdb0d28 | |||
|  | 115d8c6c5c | ||
|  | cd2a068470 | ||
| bf260b14e0 | |||
| d161a68c95 | |||
|  | 9de8cea50e | ||
| 5f330a7c48 | |||
| 732710c3ef | |||
| 4941aae950 | |||
| 1439a11969 | |||
|  | 3a3e0e1a2d | ||
| fa9ab22867 | |||
| 207a61d199 | |||
|  | 7b18322e17 | ||
| 8f47756244 | |||
|  | cdcf9431bf | ||
| 374f3da740 | |||
| 3965ada0a2 | |||
| 79ff02d06c | |||
| 7b2afd2290 | |||
| 5fde20ace1 | |||
| 7f5f5e7f62 | |||
| 6a1a1efe74 | |||
| 94e1bb1780 | |||
| d66dd8cc9e | |||
|  | de97ac8a00 | ||
|  | 82ac5edf22 | ||
|  | 909c8ee8ba | ||
|  | 15b6b13e14 | ||
| 89589b75e1 | |||
| 6a7b3eefd4 | |||
|  | 0583c4a188 | ||
|  | 8855e7f89f | ||
|  | 4fbb598c50 | ||
| 101fe967d3 | |||
|  | 53468e0dc3 | ||
| 53cecbcd18 | |||
| 5a65f60cc5 | |||
| d51d5cdc24 | |||
| 0dec967e71 | |||
|  | c940d0991d | ||
| 662ef6ad7b | |||
| 5313ab13d3 | |||
| f43f227191 | |||
|  | ae25d88e94 | ||
| 5ef6074015 | |||
| 8fea5265e7 | |||
| 8d2e03ca6b | |||
| 1074de228b | |||
|  | 64d1aa4041 | ||
|  | 6608330ed3 | ||
| aa562c4a30 | |||
| d6f63ac560 | |||
| cdc0f49789 | |||
| 2f290f83fd | |||
| d34ddfeb75 | |||
| b7f163c1a1 | |||
|  | 791a92a446 | ||
|  | 10c1e57680 | ||
|  | c65d1a56c8 | ||
|  | d8f2dbd0c2 | ||
|  | 43ce85bb28 | ||
|  | 8201e23e4b | ||
|  | 3cc288e014 | ||
|  | 08ec7fadac | ||
|  | 7a30b125ec | ||
|  | cac5db123f | ||
|  | 59f8b2d0b5 | ||
|  | 81a536bbdb | ||
|  | 5e952e3899 | ||
|  | df887b8628 | ||
|  | 31d90a342c | ||
|  | 8607337580 | ||
| 4fe42a3671 | |||
| 148d1746ad | |||
| ae51f9610c | |||
| 5eb3b8d95f | |||
| ebdae6bc88 | |||
|  | ab25bf16b2 | ||
| 31670d0c2b | |||
| 9ca3d3b37d | |||
| fb9082e995 | |||
| 87a4ff3fc5 | |||
| 1e5b7d61f5 | |||
| 8177e6e3ca | |||
| 9f26db22fc | |||
| e2ea4d13c4 | |||
| c222113cf1 | 
							
								
								
									
										16
									
								
								.drone.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,16 @@ | |||||||
|  | kind: pipeline | ||||||
|  | type: docker | ||||||
|  | name: default | ||||||
|  |  | ||||||
|  | steps: | ||||||
|  |   - name: install | ||||||
|  |     image: node:22.17.0 | ||||||
|  |     commands: | ||||||
|  |       - npm install | ||||||
|  |  | ||||||
|  |   - name: build_with_linux | ||||||
|  |     image: node:22.17.0 | ||||||
|  |     commands: | ||||||
|  |       - apt-get update && apt-get upgrade -y | ||||||
|  |       - apt-get install -y libarchive-tools rpm | ||||||
|  |       - npm run build-unpacked | ||||||
| @@ -10,6 +10,10 @@ insert_final_newline = true | |||||||
| indent_style = space | indent_style = space | ||||||
| indent_size = 2 | indent_size = 2 | ||||||
|  |  | ||||||
|  | [**.ts] | ||||||
|  | indent_style = space | ||||||
|  | indent_size = 2 | ||||||
|  |  | ||||||
| [**.json] | [**.json] | ||||||
| indent_style = space | indent_style = space | ||||||
| indent_size = 2 | indent_size = 2 | ||||||
|   | |||||||
							
								
								
									
										16
									
								
								.eslintrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,16 @@ | |||||||
|  | { | ||||||
|  |   "root": true, | ||||||
|  |   "env": { | ||||||
|  |     "node": true, | ||||||
|  |     "browser": true | ||||||
|  |   }, | ||||||
|  |   "parser": "@typescript-eslint/parser", | ||||||
|  |   "plugins": [ | ||||||
|  |     "@typescript-eslint" | ||||||
|  |   ], | ||||||
|  |   "extends": [ | ||||||
|  |     "eslint:recommended", | ||||||
|  |     "plugin:@typescript-eslint/eslint-recommended", | ||||||
|  |     "plugin:@typescript-eslint/recommended" | ||||||
|  |   ] | ||||||
|  | } | ||||||
							
								
								
									
										51
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -5,33 +5,46 @@ on: | |||||||
|     branches-ignore: |     branches-ignore: | ||||||
|       - master |       - master | ||||||
|       - develop |       - develop | ||||||
|  |   pull_request: | ||||||
|  |     branches-ignore: | ||||||
|  |       - master | ||||||
|  |       - develop | ||||||
|  |   workflow_dispatch: | ||||||
| jobs: | jobs: | ||||||
|   build_on_linux: |   build_on_linux: | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|  |       - name: update apt | ||||||
|  |         run: sudo apt-get update | ||||||
|  |       - name: Install libarchive-tools | ||||||
|  |         run: sudo apt-get install -y libarchive-tools | ||||||
|       - uses: actions/checkout@master |       - uses: actions/checkout@master | ||||||
|       - uses: actions/setup-node@master |       - uses: actions/setup-node@master | ||||||
|         with: |         with: | ||||||
|           node-version: 12 |           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: 12 |   #         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: 12 |   #         node-version: 22.4 | ||||||
|       - run: npm install |   #     - run: npm install | ||||||
|       - run: npm run build |   #     - run: npm run build | ||||||
|   | |||||||
							
								
								
									
										63
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -5,14 +5,23 @@ on: | |||||||
|     branches: |     branches: | ||||||
|       - master |       - master | ||||||
|       - develop |       - develop | ||||||
|  |   pull_request: | ||||||
|  |     branches: | ||||||
|  |       - master | ||||||
|  |   workflow_dispatch: | ||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   build_on_linux: |   build_on_linux: | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|  |       - name: update apt | ||||||
|  |         run: sudo apt-get update | ||||||
|  |       - name: Install libarchive-tools | ||||||
|  |         run: sudo apt-get install -y libarchive-tools | ||||||
|       - uses: actions/checkout@master |       - uses: actions/checkout@master | ||||||
|       - uses: actions/setup-node@master |       - uses: actions/setup-node@master | ||||||
|         with: |         with: | ||||||
|           node-version: 12 |           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 | ||||||
| @@ -20,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: 12 |   #         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: 12 |   #         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/ | ||||||
|   | |||||||
							
								
								
									
										10
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -7,3 +7,13 @@ build/linux/arch/* | |||||||
| !build/linux/arch/.SRCINFO | !build/linux/arch/.SRCINFO | ||||||
| !build/linux/arch/tidal-hifi.desktop | !build/linux/arch/tidal-hifi.desktop | ||||||
| !build/linux/arch/install.sh | !build/linux/arch/install.sh | ||||||
|  | *.css | ||||||
|  | *.css.map | ||||||
|  |  | ||||||
|  | # JetBrains IDE configuration | ||||||
|  | .idea | ||||||
|  | ts-dist/** | ||||||
|  | ts-dist | ||||||
|  | themes | ||||||
|  | !src/themes | ||||||
|  | .sass-cache | ||||||
|   | |||||||
							
								
								
									
										16
									
								
								.stylelintrc.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,16 @@ | |||||||
|  | { | ||||||
|  |   "plugins": [ | ||||||
|  |     "stylelint-prettier" | ||||||
|  |   ], | ||||||
|  |   "extends": [ | ||||||
|  |     "stylelint-config-standard-scss" | ||||||
|  |   ], | ||||||
|  |   "ignoreFiles": [ | ||||||
|  |     "src/themes/**.scss" | ||||||
|  |   ], | ||||||
|  |   "rules": { | ||||||
|  |     "prettier/prettier": true, | ||||||
|  |     "scss/at-extend-no-missing-placeholder": null, | ||||||
|  |     "no-descending-specificity": null | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										5
									
								
								.vscode/http/settings/skipped-artists/addArtists.http
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,5 @@ | |||||||
|  | POST /settings/skipped-artists HTTP/1.1 | ||||||
|  | Host: localhost:47836 | ||||||
|  | Content-Type: application/json | ||||||
|  |  | ||||||
|  | ["abc", "def"] | ||||||
							
								
								
									
										2
									
								
								.vscode/http/settings/skipped-artists/addCurrent.http
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,2 @@ | |||||||
|  | POST /settings/skipped-artists/current HTTP/1.1 | ||||||
|  | Host: localhost:47836 | ||||||
							
								
								
									
										5
									
								
								.vscode/http/settings/skipped-artists/removeArtists.http
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,5 @@ | |||||||
|  | POST /settings/skipped-artists/delete HTTP/1.1 | ||||||
|  | Host: localhost:47836 | ||||||
|  | Content-Type: application/json | ||||||
|  |  | ||||||
|  | ["abc", "def"] | ||||||
							
								
								
									
										2
									
								
								.vscode/http/settings/skipped-artists/removeCurrent.http
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,2 @@ | |||||||
|  | DELETE /settings/skipped-artists/current HTTP/1.1 | ||||||
|  | Host: localhost:47836 | ||||||
							
								
								
									
										28
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,28 @@ | |||||||
|  | { | ||||||
|  |   "cSpell.words": [ | ||||||
|  |     "Brainz", | ||||||
|  |     "Castlabs", | ||||||
|  |     "Fi's", | ||||||
|  |     "flac", | ||||||
|  |     "Flatpak", | ||||||
|  |     "geqnfr", | ||||||
|  |     "hifi", | ||||||
|  |     "libnotify", | ||||||
|  |     "listenbrainz", | ||||||
|  |     "playpause", | ||||||
|  |     "prs", | ||||||
|  |     "rescrobbler", | ||||||
|  |     "scrobble", | ||||||
|  |     "scrobbling", | ||||||
|  |     "trackid", | ||||||
|  |     "tracklist", | ||||||
|  |     "widevine", | ||||||
|  |     "wvcus", | ||||||
|  |     "xesam", | ||||||
|  |     "xhayper" | ||||||
|  |   ], | ||||||
|  |   "sonarlint.connectedMode.project": { | ||||||
|  |     "connectionId": "public-sonarcloud", | ||||||
|  |     "projectKey": "Mastermindzh_tidal-hifi" | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										419
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						| @@ -4,10 +4,427 @@ 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] | ||||||
|  |  | ||||||
|  | - Simplified `MediaInfo` & `Options` types | ||||||
|  | - Added `playingFrom` information to the info API | ||||||
|  |   - also changed the way we update Album info since Playing From now shows the correct Album. | ||||||
|  | - API now allows you to set the `hostname` so you can control who can interact with the API. | ||||||
|  | - Reworked swagger generation hotfix to properly generate `swagger.json` during the compile step | ||||||
|  |   - Might switch to tsoa in the future, idk yet. | ||||||
|  | - Added [Tidal Magazine](https://tidal.com/magazine/) integration (in the menubar or use `Ctrl + M`) | ||||||
|  |  | ||||||
|  | ## [5.13.1] | ||||||
|  |  | ||||||
|  | - removed Swagger generation step in favor of pre-generated file. | ||||||
|  |   - This also fixes the API issue [#409](https://github.com/Mastermindzh/tidal-hifi/issues/409) | ||||||
|  |   - This also stops TIDAL-hifi from scanning your entire home directory... the glob was very broad apparently. | ||||||
|  |  | ||||||
|  | ## [5.13.0] | ||||||
|  |  | ||||||
|  | - Fixed [#403](https://github.com/Mastermindzh/tidal-hifi/issues/403) "cannot read shuffle of undefined" error | ||||||
|  | - Added an API to add & delete entries from the skippedArtists list in the settings. fixes [#405](https://github.com/Mastermindzh/tidal-hifi/issues/405) | ||||||
|  |   - `GET /settings/skipped-artists` -> get list of skipped artists | ||||||
|  |   - `POST /settings/skipped-artists` -> add to the list of skipped artists | ||||||
|  |   - `POST /settings/skipped-artists/delete` -> delete from the list of skipped artists | ||||||
|  |   - `POST /settings/skipped-artists/current` -> skip the current artist | ||||||
|  |   - `DELETE /settings/skipped-artists/current` -> delete the current artist from the skip list | ||||||
|  |   - Added Swagger documentation to the new endpoints: | ||||||
|  |      | ||||||
|  | - CORS support added by [Mjokfox](https://github.com/Mjokfox) | ||||||
|  |  | ||||||
|  | ## [5.12.0] | ||||||
|  |  | ||||||
|  | - Added Shuffle and Repeat state to API response - By [ThatGravyBoat](https://github.com/ThatGravyBoat) | ||||||
|  |  | ||||||
|  | ## [5.11.0] | ||||||
|  |  | ||||||
|  | - Re-implemented the API, added support for duration/current in seconds & shuffle+repeat | ||||||
|  |   - made the original API "legacy" (still works the same) | ||||||
|  |   - Now using the correct HTTP verb for all new endpoints | ||||||
|  | - Implemented TIDAL's universal links. All links are now universal. | ||||||
|  | - Custom `tidal://` protocol fixed - By [TheRockYT](https://github.com/TheRockYT) | ||||||
|  | - Global media shortcuts removed since TIDAL includes them by default - By [TheRockYT](https://github.com/TheRockYT) | ||||||
|  |  | ||||||
|  | - Fixes | ||||||
|  |   - [#390](https://github.com/Mastermindzh/tidal-hifi/issues/390) | ||||||
|  |   - [#376](https://github.com/Mastermindzh/tidal-hifi/issues/376) | ||||||
|  |   - [#383](https://github.com/Mastermindzh/tidal-hifi/issues/383) | ||||||
|  |   - [#393](https://github.com/Mastermindzh/tidal-hifi/issues/393) | ||||||
|  |  | ||||||
|  | ## [5.10.0] | ||||||
|  |  | ||||||
|  | - TIDAL will now close the previous notification if a new one is sent whilst the old is still visible. [#364](https://github.com/Mastermindzh/tidal-hifi/pull/364) | ||||||
|  | - Updated developer documentation to get started in README [#365](https://github.com/Mastermindzh/tidal-hifi/pull/365) | ||||||
|  | - Links in the about window now open in the user's default browser. fixes [#360](https://github.com/Mastermindzh/tidal-hifi/issues/360) | ||||||
|  | - Refactored "nowPlaying" code to always display the current state, even when the built-in UI is updated. | ||||||
|  |   - fixes [#351](https://github.com/Mastermindzh/tidal-hifi/issues/351) | ||||||
|  |   - fixes [#356](https://github.com/Mastermindzh/tidal-hifi/issues/356) | ||||||
|  |   - fixes [#370](https://github.com/Mastermindzh/tidal-hifi/issues/370) | ||||||
|  | - Reverted to using old icon syntax with icons in the build directory. fixes [#350](https://github.com/Mastermindzh/tidal-hifi/issues/350) | ||||||
|  | - Enabled wayland platform flags by default when launching through .desktop file | ||||||
|  |   - fixes [#273](https://github.com/Mastermindzh/tidal-hifi/issues/273) | ||||||
|  |   - fixes [#347](https://github.com/Mastermindzh/tidal-hifi/issues/347) | ||||||
|  |  | ||||||
|  | ## [5.9.0] | ||||||
|  |  | ||||||
|  | - More Discord options: | ||||||
|  |   - Added the ability to hide the current song from the discord activity and display a custom text instead | ||||||
|  |   - Added the ability to customize the text that is shown when no song is playing | ||||||
|  |   - Discord now reacts to pausing/unpausing events | ||||||
|  | - Refactored media info updates so it only updates the required info, fixes #342, #306 | ||||||
|  | - Added 5.9.0 logs/versions/migrations | ||||||
|  |  | ||||||
|  | ### Fixed | ||||||
|  |  | ||||||
|  | - Fixed chromium mediaSession instance showing up. fixes #338 #198 | ||||||
|  | - Set a new icon, should fix #302 | ||||||
|  | - Made sure settingsWindow exists before operating on it. fixes #344 | ||||||
|  |  | ||||||
|  | ## [5.8.0] | ||||||
|  |  | ||||||
|  | - Updated Electron to 28.1.1 (fixes [325](https://github.com/Mastermindzh/tidal-hifi/issues/325)) | ||||||
|  | - Updated dependencies to latest | ||||||
|  |   - added theme files to stylelint ignore | ||||||
|  |   - fixed other stylelint errors | ||||||
|  |  | ||||||
|  | - Added functionality to favorite a song (fixes [#323](https://github.com/Mastermindzh/tidal-hifi/issues/323)) | ||||||
|  |   - Added a hotkey to favorite ("Add to collection") songs: Control+a | ||||||
|  |   - Added the "favorite" field in the `mediaInfo` and the API `/current` endpoint | ||||||
|  |   - Added an endpoint to toggle favoriting a song: `http://localhost:47836/favorite/toggle` | ||||||
|  |  | ||||||
|  | - Fixed wrong "end time stamp" for currently playing song (fixes [#282](https://github.com/Mastermindzh/tidal-hifi/issues/282)) | ||||||
|  |   - Affected the API + all integrations | ||||||
|  |   - As requested we also added toggle to sync the timestamps to Discord (default = true) | ||||||
|  |  | ||||||
|  | ## [5.7.1] | ||||||
|  |  | ||||||
|  | - Fixed mpris not being set up correctly due to capitalization of the instance name. | ||||||
|  |  | ||||||
|  | ## [5.7.0] | ||||||
|  |  | ||||||
|  | - Renamed app to TIDAL Hi-Fi. | ||||||
|  | - Made sure all windows run with the same web preferences set (compared to main app). | ||||||
|  |   - Fixes the last.fm bug. | ||||||
|  | - Added settings to customize the Discord rich presence information | ||||||
|  |   - Discord settings are now also collapsible like the ListenBrainz ones are | ||||||
|  | - Restyled settings menu to include version number and useful links on the about page | ||||||
|  |      | ||||||
|  | - The ListenBrainz integration has been extended with a configurable (5 seconds by default) delay in song reporting so that it doesn't spam the API when you are cycling through songs. | ||||||
|  | - Custom CSS now also applies to settings window | ||||||
|  |      | ||||||
|  |  | ||||||
|  | ## [5.6.0] | ||||||
|  |  | ||||||
|  | - Added support for Wayland (on by default) fixes [#262](https://github.com/Mastermindzh/tidal-hifi/issues/262) and [#157](https://github.com/Mastermindzh/tidal-hifi/issues/157) | ||||||
|  | - Made it clear in the readme that this TIDAL Hi-Fi client supports High & Max audio settings. fixes [#261](https://github.com/Mastermindzh/tidal-hifi/issues/261) | ||||||
|  | - Added app suspension inhibitors when music is playing. fixes [#257](https://github.com/Mastermindzh/tidal-hifi/issues/257) | ||||||
|  | - Fixed bug with theme files from user directory trying to load: "an error occurred reading the theme file" | ||||||
|  | - Fixed: config flags not being set correctly | ||||||
|  | - [DEV]: | ||||||
|  |   - Logger is now static and will automatically call either ipcRenderer or ipcMain | ||||||
|  |  | ||||||
|  | ## 5.5.0 | ||||||
|  |  | ||||||
|  | - ListenBrainz integration added (thanks @Mar0xy) | ||||||
|  |  | ||||||
|  | ## 5.4.0 | ||||||
|  |  | ||||||
|  | - Removed Windows builds (from publishes) as they don't work anymore. | ||||||
|  | - Added [Songwhip](https://songwhip.com/) integration | ||||||
|  | - Fixed bug with several hotkeys not working due to Tidal's HTML/css changes | ||||||
|  | - [DEV]: | ||||||
|  |   - added a logger to log into STDout | ||||||
|  |   - added "watchStart" which will automatically restart electron when it detects a source code change | ||||||
|  |   - added "listen.tidal.com-parsing-scripts" folder with a script to verify whether all elements (in the main preload.ts) are present on the page | ||||||
|  |  | ||||||
|  | ## 5.3.0 | ||||||
|  |  | ||||||
|  | - SPKChaosPhoenix updated the beautiful Tokyo Night theme: | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## 5.2.0 | ||||||
|  |  | ||||||
|  | - moved from Javascript to Typescript for all files | ||||||
|  |  | ||||||
|  |   - use `npm run watch` to watch for changes & recompile typescript and sass files | ||||||
|  |  | ||||||
|  | - Added support for theming the application | ||||||
|  | - Added drone build file use `drone exec` or drone.ci to build it | ||||||
|  |  | ||||||
|  | ## 5.1.0 | ||||||
|  |  | ||||||
|  | ### New features | ||||||
|  |  | ||||||
|  | - Added proper updates through the MediaSession API | ||||||
|  | - You can now add custom CSS in the "advanced" settings tab | ||||||
|  | - You can now configure the updateFrequency in the settings window | ||||||
|  |   - Default value is set to 500 and will overwrite the hardcoded value of 100 | ||||||
|  |  | ||||||
|  | ### Fixes | ||||||
|  |  | ||||||
|  | - Any songs **including** an artist listed in the `skipped artists` setting will now be skipped even if the song is a collaboration. | ||||||
|  | - Linux desktop icons have been fixed. See [#222](https://github.com/Mastermindzh/tidal-hifi/pull/222) for details. | ||||||
|  |  | ||||||
|  | ## 5.0.0 | ||||||
|  |  | ||||||
|  | - Replaced "muting artists" with a full implementation of an Adblock mechanism | ||||||
|  |  | ||||||
|  |   > Disabled audio & visual ads, unlocked lyrics, suggested track, track info, unlimited skips thanks to uBlockOrigin custom filters ([source](https://github.com/uBlockOrigin/uAssets/issues/17495)) | ||||||
|  |  | ||||||
|  | - @thanasistrisp updated Electron to 24.1.2 and fixed the tray bug :) | ||||||
|  |  | ||||||
|  | ## 4.4.0 | ||||||
|  |  | ||||||
|  | - Updated shortcut hint on the menubar to reflect the new `ctrl+=` shortcut. | ||||||
|  | - Reverted icon path to `icon.png` instead of the hardcoded linux path. | ||||||
|  | - Add support to autoHide the menubar and showing it with the `alt` key. | ||||||
|  | - Move the quit command from the system sub-menu to the main menu | ||||||
|  | - Added single click focus/show on the tray icon | ||||||
|  |   - Doesn't work on all platforms. Nothing I can do about that unfortunately! | ||||||
|  | - Added a list of artists to automatically skip. | ||||||
|  |   - I don't like the vast majority of dutch music so I added one of them to my list to test: [./docs/no-dutch-music.mp4](./docs/no-dutch-music.mp4) | ||||||
|  |  | ||||||
|  | ## 4.3.1 | ||||||
|  |  | ||||||
|  | - fix: App always requests a default-url-handler-scheme change on start | ||||||
|  |  | ||||||
|  | ## 4.3.0 | ||||||
|  |  | ||||||
|  | - Added a setting to disable background throttling ([docs](https://www.electronjs.org/docs/latest/api/browser-window)) | ||||||
|  |  | ||||||
|  | ## 4.2.0 | ||||||
|  |  | ||||||
|  | - New settings window by BlueManCZ | ||||||
|  | - Fixed the desktop files in electron-builder | ||||||
|  |   - icon is set to new static path based on Arch/Debian | ||||||
|  |   - Name has changed to TIDAL Hi-Fi | ||||||
|  |  | ||||||
|  | ## 4.1.2 | ||||||
|  |  | ||||||
|  | - Changed the category of the desktop file to AudioVideo | ||||||
|  | - Changed desktop file name to "TIDAL Hi-Fi" | ||||||
|  |  | ||||||
|  | ## 4.1.1 | ||||||
|  |  | ||||||
|  | - Fixed `cannot read property of undefined` error because of not passing mainWindow around. | ||||||
|  | - vincens2005, fixed inconsistent auto muting | ||||||
|  |  | ||||||
|  | ## 4.1.0 | ||||||
|  |  | ||||||
|  | - Added `tidal://` protocol support | ||||||
|  | - Switched icon strategies to fix bugs with icons | ||||||
|  | - Fixed tray icon bugs | ||||||
|  |   - Menu now shows in KDE as well | ||||||
|  |   - Toggle window is supported from tray icon | ||||||
|  |   - regular click is still ignored, see [this issue](https://github.com/electron/electron/issues/6773) | ||||||
|  | - Fixed about tab not showing | ||||||
|  | - Fixed playback, mpris and API issues | ||||||
|  |  | ||||||
|  | ## 4.0.1 | ||||||
|  |  | ||||||
|  | - Updated build config to make use of a base file that doesn't build anything. | ||||||
|  |   - This fixes the issue of unwanted extra build targets that were introduced with the electron-builder update | ||||||
|  |  | ||||||
|  | ## 4.0.0 | ||||||
|  |  | ||||||
|  | - Updated to Electron 19.0.5 | ||||||
|  |  | ||||||
|  | ## 3.1.1 | ||||||
|  |  | ||||||
|  | - Media update timeout set to 500 instead of 200 | ||||||
|  | - Updated property name for duration because of a tidal update | ||||||
|  | - flag for "disable hardware media keys" now working again | ||||||
|  |  | ||||||
|  | ## 3.1.0 | ||||||
|  |  | ||||||
|  | - Added a separate advanced options settings panel with flags | ||||||
|  |   - Added gpu-rasterization flag | ||||||
|  | - config setting `disableHardwareMediaKeys` moved to `flags.disableHardwareMediaKeys`, it will be migrated automatically | ||||||
|  |  | ||||||
|  | ## 3.0.0 | ||||||
|  |  | ||||||
|  | - Updated to Electron 15 | ||||||
|  | - Fixed the develop "build-unpacked" command | ||||||
|  | - Added setting to disable multiple TIDAL Hi-Fi windows (defaults to true) | ||||||
|  | - Added setting to disable HardwareMediaKeyHandling (defaults to false) | ||||||
|  |  | ||||||
|  | ## 2.8.2 | ||||||
|  |  | ||||||
|  | - Updated dependencies | ||||||
|  | - Downgraded packaged version of electron to 8.5.2, doesn't seem to like a newer build | ||||||
|  | - Fixed the annoying (and useless) terminal warning about `allowRendererProcessReuse` | ||||||
|  |  | ||||||
|  | ## 2.8.1 | ||||||
|  |  | ||||||
|  | - Mar0xy fixed some build issues (thanks!) | ||||||
|  | - vincens2005 fixed the quit button in the menubar | ||||||
|  |  | ||||||
|  | ## 2.8.0 | ||||||
|  |  | ||||||
|  | - Added the ability to mute artists automatically | ||||||
|  | - Added better error handling for discord rpc | ||||||
|  |  | ||||||
|  | ## 2.7.2 | ||||||
|  |  | ||||||
|  | - Disabled sandboxing to fix a display compositor issue on Linux. | ||||||
|  |  | ||||||
|  | ## 2.7.1 | ||||||
|  |  | ||||||
|  | - Fixed bug: Triggering full screen from the Tidal web app would cause the menubar to be visible even if it was disabled in the settings | ||||||
|  |  | ||||||
|  | ## 2.7.0 | ||||||
|  |  | ||||||
|  | - Switched to the native Notifier (removed node-notifier) | ||||||
|  | - Album art now also has a name, based on [best effort](https://github.com/Mastermindzh/tidal-hifi/pull/88#pullrequestreview-840814847) | ||||||
|  |  | ||||||
|  | ## 2.6.0 | ||||||
|  |  | ||||||
|  | - Add album images to media info and discord | ||||||
|  |  | ||||||
|  | ## 2.5.0 | ||||||
|  |  | ||||||
|  | - Notify-send now correctly shows "Tidal Hi-Fi" as the program name | ||||||
|  | - Updated dependencies (including electron itself) | ||||||
|  |  | ||||||
|  | ### known issues | ||||||
|  |  | ||||||
|  | - Requires older version of nodejs due to electron-builder (use lts/gallium) | ||||||
|  |  | ||||||
|  | ### builds | ||||||
|  |  | ||||||
|  | updated to nodejs 16 in actions | ||||||
|  |  | ||||||
|  | ## 2.4.0 | ||||||
|  |  | ||||||
|  | - Added more MPRIS settings | ||||||
|  | - Added instruction for rescrobbler to get last.fm working without sandbox mode | ||||||
|  |  | ||||||
|  | ## 2.3.0 | ||||||
|  |  | ||||||
|  | - Added a setting to minimize to tray on app close (off by default) | ||||||
|  | - Added the main menu to the tray icon | ||||||
|  |  | ||||||
|  | ## 2.2.1 | ||||||
|  |  | ||||||
|  | - artists is now gotten specifically from the footer. This fixes the [unknown artists bug](https://github.com/Mastermindzh/tidal-hifi/issues/45). | ||||||
|  | - the discord module will check whether the artists is empty and if so substitute it with a default message. This is to prevent sending an empty state to Discord (which it doesn't support). fixes [#45](https://github.com/Mastermindzh/tidal-hifi/issues/54) | ||||||
|  |  | ||||||
|  | ### removed arch build details from source control | ||||||
|  |  | ||||||
|  | moved to: [https://github.com/Mastermindzh/tidal-hifi-aur](https://github.com/Mastermindzh/tidal-hifi-aur) | ||||||
|  |  | ||||||
|  | ## 2.2.0 | ||||||
|  |  | ||||||
|  | - The discord integration now adds a time remaining field based on the song duration | ||||||
|  | - All fields (current, remaining, and url are also available in the API\*) | ||||||
|  | - the artist field is now correctly identified | ||||||
|  |  | ||||||
|  | \* current time only updates on play/pause. | ||||||
|  |  | ||||||
|  | ## 2.1.1 | ||||||
|  |  | ||||||
|  | - The discord integration now doesn't send an update every 15 seconds it sends an update whenever the media info changes | ||||||
|  | - consolidated updating the media info changes with the status changes into a single global event | ||||||
|  |  | ||||||
|  | ## 2.1.0 | ||||||
|  |  | ||||||
|  | - [Mar0xy](https://github.com/Mar0xy) added Discord integration. | ||||||
|  | - Several versions have been bumped to fix vulnerabilities | ||||||
|  |  | ||||||
|  | ## 2.0.0 | ||||||
|  |  | ||||||
|  | ### Breaking changes | ||||||
|  |  | ||||||
|  | - Changed settings hotkey from "ctrl+/" to "ctrl+=" to avoid a conflict with the default Tidal hotkeys | ||||||
|  |  | ||||||
|  | ## Other changes | ||||||
|  |  | ||||||
|  | - Added a setting to disable custom hotkeys | ||||||
|  | - Fixed the bug that the previous song hotkey would register 3 times. (Twice due to a duplicate block of code + once from the default tidal hotkey) | ||||||
|  |  | ||||||
|  | ## 1.3.0 | ||||||
|  |  | ||||||
|  | -- re-enabled MPRIS-service wit the electron downloader fixes | ||||||
|  |  | ||||||
|  | ## 1.2.0 | ||||||
|  |  | ||||||
|  | - Added the ability to disable the tray icon | ||||||
|  |  | ||||||
|  | ## 1.1.1 | ||||||
|  |  | ||||||
|  | Bugfixes: | ||||||
|  |  | ||||||
|  | - Arch AUR install failed before, it is fixed now by using the included build scripts | ||||||
|  |  | ||||||
| ## 1.1.0 | ## 1.1.0 | ||||||
|  |  | ||||||
| - updated to electron 8.0.0 | - updated to electron 8.0.0 | ||||||
| - Added a beta-version of the mpris service | - Added a beta-version of the MPRIS service | ||||||
|  |  | ||||||
| - Bugfixes: | - Bugfixes: | ||||||
|   - icon on gnome not showing in launcher |   - icon on gnome not showing in launcher | ||||||
|   | |||||||
							
								
								
									
										179
									
								
								README.md
									
									
									
									
									
								
							
							
						
						| @@ -1,45 +1,102 @@ | |||||||
| <h1> | # TIDAL Hi-Fi (Max quality)<img src = "./build/icon.png" height="40" align="right"/> | ||||||
| Tidal-hifi |  | ||||||
| <img src = "./build/icon.png" height="40" align="right" /> |  | ||||||
| </h1> |  | ||||||
|  |  | ||||||
| The web version of [listen.tidal.com](listen.tidal.com) running in electron with hifi support thanks to widevine. |  [](https://github.com/Mastermindzh/tidal-hifi/actions) [](https://ci.mastermindzh.tech/Mastermindzh/tidal-hifi) [](https://discord.gg/yhNwf4v4He) | ||||||
|  |  | ||||||
|  | The web version of [listen.tidal.com](https://listen.tidal.com) running in electron with Hi-Fi (High & Max) support thanks to widevine. | ||||||
|  |  | ||||||
| ## Table of contents |  | ||||||
|  |  | ||||||
|  | ## Table of Contents | ||||||
|  |  | ||||||
| <!-- toc --> | <!-- toc --> | ||||||
|  |  | ||||||
| - [Installation](#installation) | - [TIDAL Hi-Fi (Max quality)](#tidal-hi-fi-max-quality) | ||||||
|   - [Using releases](#using-releases) |   - [Table of Contents](#table-of-contents) | ||||||
|     - [Snap install](#snap-install) |   - [Features](#features) | ||||||
|   - [Using source](#using-source) |   - [Contributions](#contributions) | ||||||
| - [features](#features) |   - [Why did I create TIDAL Hi-Fi?](#why-did-i-create-tidal-hi-fi) | ||||||
| - [Integrations](#integrations) |  | ||||||
| - [Why](#why) |  | ||||||
|     - [Why not extend existing projects?](#why-not-extend-existing-projects) |     - [Why not extend existing projects?](#why-not-extend-existing-projects) | ||||||
| - [Special thanks to..](#special-thanks-to) |   - [Installation](#installation) | ||||||
|  |     - [Dependencies](#dependencies) | ||||||
|  |     - [Using releases](#using-releases) | ||||||
|  |     - [Snap](#snap) | ||||||
|  |     - [Arch Linux](#arch-linux) | ||||||
|  |     - [Flatpak](#flatpak) | ||||||
|  |     - [Nix](#nix) | ||||||
|  |     - [Using source](#using-source) | ||||||
|  |   - [Integrations](#integrations) | ||||||
|  |   - [Known bugs](#known-bugs) | ||||||
|  |     - [DRM not working on Windows (error S6007)](#drm-not-working-on-windows-error-s6007) | ||||||
|  |   - [Special thanks to](#special-thanks-to) | ||||||
|  |   - [Donations](#donations) | ||||||
|  |   - [Images](#images) | ||||||
|  |     - [Settings window](#settings-window) | ||||||
|  |     - [User setups](#user-setups) | ||||||
|  |  | ||||||
| <!-- tocstop --> | <!-- tocstop --> | ||||||
|  |  | ||||||
|  | ## Features | ||||||
|  |  | ||||||
|  | - HiFi playback (High & Max settings) | ||||||
|  | - Notifications | ||||||
|  | - Custom [theming](./docs/theming.md) | ||||||
|  | - Custom hotkeys ([source](https://defkey.com/tidal-desktop-shortcuts)) | ||||||
|  | - Better icons thanks to [Papirus-icon-theme](https://github.com/PapirusDevelopmentTeam/papirus-icon-theme/) | ||||||
|  | - [Settings feature](./docs/images/settings.png) to disable certain functionality. (`ctrl+=` or `ctrl+0`) | ||||||
|  | - API for status, playback and settings (see the [/docs](http://localhost:47836/docs/) route) | ||||||
|  | - Disabled audio & visual ads, unlocked lyrics, suggested track, track info, and unlimited skips thanks to uBlockOrigin custom filters ([source](https://github.com/uBlockOrigin/uAssets/issues/17495)) | ||||||
|  | - AlbumArt in integrations ([best-effort](https://github.com/Mastermindzh/tidal-hifi/pull/88#pullrequestreview-840814847)) | ||||||
|  | - Custom [integrations](#integrations) | ||||||
|  |   - [ListenBrainz](https://listenbrainz.org/?redirect=false) integration | ||||||
|  |   - Discord RPC integration (showing "now listening", "Browsing", etc) | ||||||
|  |     - Flatpak version only works if both Discord and Tidal-HiFi are flatpaks | ||||||
|  |   - MPRIS integration | ||||||
|  | - UI + Json config (`~/.config/tidal-hifi/`, or `~/.var/app/com.mastermindzh.tidal-hifi/` for Flatpak) | ||||||
|  |  | ||||||
|  | ## Contributions | ||||||
|  |  | ||||||
|  | To contribute you can use the standard GitHub features (issues, prs, etc.) or join the discord server to talk with like-minded individuals. | ||||||
|  |  | ||||||
|  | -  [Join the Discord server](https://discord.gg/yhNwf4v4He) | ||||||
|  |  | ||||||
|  | ## Why did I create TIDAL Hi-Fi? | ||||||
|  |  | ||||||
|  | I moved from Spotify over to Tidal and found Linux support to be lacking. | ||||||
|  | When I started this project there weren't any Linux apps that offered Tidal's "hifi" options nor any scripts to control it. | ||||||
|  | I made this app to support the highest quality audio available on the Linux platform. It used to be "hifi" but now is ["High & Max"](https://tidal.com/sound-quality). | ||||||
|  |  | ||||||
|  | ### Why not extend existing projects? | ||||||
|  |  | ||||||
|  | Whilst there are a handful of projects attempting to run Tidal on Electron they are all unappealing to me because of various reasons: | ||||||
|  |  | ||||||
|  | - Lack of maintainers/developers. (no hotfixes, no issues being handled etc) | ||||||
|  | - Most are simple web wrappers, not my cup of tea. | ||||||
|  | - Some are DE-oriented. I want this to work on WM's too. | ||||||
|  | - None have Widevine working at the moment | ||||||
|  |  | ||||||
|  | Sometimes it's just easier to start over, cover my own needs and after that making it available to the public :) | ||||||
|  |  | ||||||
| ## Installation | ## Installation | ||||||
|  |  | ||||||
|  | ### Dependencies | ||||||
|  |  | ||||||
|  | Note that you **need** a notification library such as [libnotify](https://github.com/GNOME/libnotify) or [dunst](https://github.com/dunst-project/dunst) for the software to work properly. | ||||||
|  |  | ||||||
| ### Using releases | ### Using releases | ||||||
|  |  | ||||||
| Various packaged versions of the software are available on the [releases](https://github.com/Mastermindzh/tidal-hifi/releases) tab. | Various packaged versions of the software are available on the [releases](https://github.com/Mastermindzh/tidal-hifi/releases) tab. | ||||||
|  |  | ||||||
| #### Snap install | ### Snap | ||||||
|  |  | ||||||
| To install with `snap` you need to download the pre-packaged snap-package from this repository, found under releases: | To install with `snap` you need to download the pre-packaged snap-package from this repository, found under releases: | ||||||
|  |  | ||||||
| 1. Download: | 1. Download | ||||||
|  |  | ||||||
|    ```sh |    ```sh | ||||||
|    wget <URI> #for instance: https://github.com/Mastermindzh/tidal-hifi/releases/download/1.0/tidal-hifi_1.0.0_amd64.snap |    wget <URI> #for instance: https://github.com/Mastermindzh/tidal-hifi/releases/download/1.0/tidal-hifi_1.0.0_amd64.snap | ||||||
|    ``` |    ``` | ||||||
|  |  | ||||||
| 2. Install: | 2. Install | ||||||
|  |  | ||||||
|    ```sh |    ```sh | ||||||
|    snap install --dangerous <path> #for instance: tidal-hifi_1.0.0_amd64.snap |    snap install --dangerous <path> #for instance: tidal-hifi_1.0.0_amd64.snap | ||||||
| @@ -47,55 +104,77 @@ snap install --dangerous <path> #for instance: tidal-hifi_1.0.0_amd64.snap | |||||||
|  |  | ||||||
| ### Arch Linux | ### Arch Linux | ||||||
|  |  | ||||||
| Arch Linux users can use the AUR to install tidal-hifi: | Arch Linux users can use the AUR to install TIDAL Hi-Fi: | ||||||
|  |  | ||||||
| ```sh | ```sh | ||||||
| trizen tidal-hifi | trizen tidal-hifi-git | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### Flatpak | ||||||
|  |  | ||||||
|  | To install via [Flatpak](https://flathub.org/apps/details/com.mastermindzh.tidal-hifi) run the following command: | ||||||
|  |  | ||||||
|  | ```sh | ||||||
|  | flatpak install flathub com.mastermindzh.tidal-hifi | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### Nix | ||||||
|  |  | ||||||
|  | To install with Nix run the following command: | ||||||
|  |  | ||||||
|  | ```sh | ||||||
|  | nix-env -iA nixpkgs.tidal-hifi | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| ### Using source | ### Using source | ||||||
|  |  | ||||||
| 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 | - `git clone https://github.com/Mastermindzh/tidal-hifi.git` | ||||||
| - cd tidal-hifi | - `cd tidal-hifi` | ||||||
| - npm install | - `npm install` | ||||||
| - npm start | - `npm run watch` to watch for auto-reload of Typescript/SCSS changes. | ||||||
|  |   - `npm run compile` can be used to trigger it once | ||||||
| ## features | - `npm watchStart` to auto watch for any updates files and reload Tidal Hi-Fi | ||||||
|  |   - `npm start` can be used to run Tidal Hi-Fi manually once | ||||||
| - HiFi playback |  | ||||||
| - Notifications |  | ||||||
| - Shortcuts ([source](https://defkey.com/tidal-desktop-shortcuts)) |  | ||||||
| - API for status and playback |  | ||||||
| - [Settings feature](./docs/settings.png) to disable certain functionality. (`ctrl+/`) |  | ||||||
|  |  | ||||||
| ## Integrations | ## Integrations | ||||||
|  |  | ||||||
|  | TIDAL Hi-Fi comes with several integrations out of the box. | ||||||
|  | You can find these in the settings menu (`ctrl + =` by default) under the "integrations" tab. | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Integrations with other projects that are not included natively: | ||||||
|  |  | ||||||
| - [i3 blocks config](https://github.com/Mastermindzh/dotfiles/commit/9714b2fa1d670108ce811d5511fd3b7a43180647) - My dotfiles where I use this app to fetch currently playing music (direct commit) | - [i3 blocks config](https://github.com/Mastermindzh/dotfiles/commit/9714b2fa1d670108ce811d5511fd3b7a43180647) - My dotfiles where I use this app to fetch currently playing music (direct commit) | ||||||
|  | - [neptune](https://github.com/uwu/neptune) third party plugins & theming | ||||||
|  |  | ||||||
| ### Known bugs | ## Known bugs | ||||||
|  |  | ||||||
| - [Last.fm login doesn't work](https://github.com/Mastermindzh/tidal-hifi/issues/4). | ### DRM not working on Windows (error S6007) | ||||||
|  |  | ||||||
| ## Why | 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. | ||||||
|  |  | ||||||
| I moved from Spotify over to Tidal and found Linux support to be lacking. | Until then you'll have to use the official app unfortunately. | ||||||
|  |  | ||||||
| When I started this project there weren't any Linux apps that offered Tidal's "hifi" options nor any scripts to control it. | ## Special thanks to | ||||||
|  |  | ||||||
| ## Why not extend existing projects? |  | ||||||
|  |  | ||||||
| Whilst there are a handful of projects attempting to run Tidal on Electron they are all unappealing to me because of various reasons: |  | ||||||
|  |  | ||||||
| - Lack of a maintainers/developers. (no hotfixes, no issues being handled etc) |  | ||||||
| - Most are simple web wrappers, not my cup of tea. |  | ||||||
| - Some are DE oriented. I want this to work on WM's too. |  | ||||||
| - None have widevine working at the moment |  | ||||||
|  |  | ||||||
| Sometimes it's just easier to start over, cover my own needs and then making it available to the public :) |  | ||||||
|  |  | ||||||
| ## Special thanks to.. |  | ||||||
|  |  | ||||||
| - [Castlabs](https://castlabs.com/) | - [Castlabs](https://castlabs.com/) | ||||||
|   For maintaining Electron with Widevine CDM installation, Verified Media Path (VMP), and persistent licenses (StorageID) |   For maintaining Electron with Widevine CDM installation, Verified Media Path (VMP), and persistent licenses (StorageID) | ||||||
|  |  | ||||||
|  | ## Donations | ||||||
|  |  | ||||||
|  | You can find my Github sponsorship page at: [https://github.com/sponsors/Mastermindzh](https://github.com/sponsors/Mastermindzh) | ||||||
|  |  | ||||||
|  | ## Images | ||||||
|  |  | ||||||
|  | ### Settings window | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ### User setups | ||||||
|  |  | ||||||
|  | Some of our users are kind enough to share their usage pictures. | ||||||
|  | If you want to see them or possibly even add one please do so in the following issue: [#3 - image thread](https://github.com/Mastermindzh/tidal-hifi/issues/3). | ||||||
|   | |||||||
							
								
								
									
										11
									
								
								SECURITY.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,11 @@ | |||||||
|  | # Security Policy | ||||||
|  |  | ||||||
|  | ## Supported Versions | ||||||
|  |  | ||||||
|  | Only the very latest 😄. | ||||||
|  |  | ||||||
|  | ## Reporting a Vulnerability | ||||||
|  |  | ||||||
|  | If you find a vulnerability just add it as an issue. | ||||||
|  | If there's an especially bad vulnerability that you don't want to make public just send me a private message (email, discord, wherever). | ||||||
|  |  | ||||||
| Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 24 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/icon.png
									
									
									
									
									
								
							
							
						
						| Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 32 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/icons/128x128.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 4.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/icons/16x16.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.2 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/icons/22x22.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/icons/24x24.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.5 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/icons/256x256.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 9.0 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/icons/32x32.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.7 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/icons/384x384.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 14 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/icons/48x48.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.2 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/icons/64x64.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.6 KiB | 
							
								
								
									
										49
									
								
								build/electron-builder.base.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,49 @@ | |||||||
|  | appId: com.rickvanlieshout.tidal-hifi | ||||||
|  | electronVersion: 37.2.5 | ||||||
|  | electronDownload: | ||||||
|  |   version: 37.2.5+wvcus | ||||||
|  |   mirror: https://github.com/castlabs/electron-releases/releases/download/v | ||||||
|  | snap: | ||||||
|  |   plugs: | ||||||
|  |     - default | ||||||
|  |     - screen-inhibit-control | ||||||
|  | extraResources: | ||||||
|  |   - "themes/**" | ||||||
|  | linux: | ||||||
|  |   category: AudioVideo | ||||||
|  |   icon: build/icons | ||||||
|  |   target: | ||||||
|  |     - dir | ||||||
|  |   executableName: tidal-hifi | ||||||
|  |   executableArgs: | ||||||
|  |     [ | ||||||
|  |       "--ozone-platform-hint=auto", | ||||||
|  |       "--enable-features=WaylandWindowDecorations", | ||||||
|  |       "--enable-wayland-ime", | ||||||
|  |       "--use-angle", | ||||||
|  |     ] | ||||||
|  |   desktop: | ||||||
|  |     entry: | ||||||
|  |       Encoding: "UTF-8" | ||||||
|  |       Name: "TIDAL Hi-Fi" | ||||||
|  |       GenericName: "TIDAL Hi-Fi" | ||||||
|  |       Comment: "The web version of listen.tidal.com running in electron with hifi support thanks to widevine." | ||||||
|  |       Icon: "tidal-hifi" | ||||||
|  |       StartupNotify: "true" | ||||||
|  |       Terminal: "false" | ||||||
|  |       Type: "Application" | ||||||
|  |       Categories: "Network;Application;AudioVideo;Audio;Video" | ||||||
|  |       StartupWMClass: "tidal-hifi" | ||||||
|  |       X-PulseAudio-Properties: "media.role=music" | ||||||
|  |       MimeType: "x-scheme-handler/tidal;" | ||||||
|  | mac: | ||||||
|  |   category: public.app-category.entertainment | ||||||
|  | win: | ||||||
|  |   icon: icon.png | ||||||
|  |   artifactName: "tidalhifi" | ||||||
|  |   appId: com.rickvanlieshout.tidalhifi | ||||||
|  |   executableName: tidalhifi | ||||||
|  | protocols: | ||||||
|  |   name: "tidal" | ||||||
|  |   role: "Viewer" | ||||||
|  |   schemes: ["tidal"] | ||||||
| @@ -1,6 +1,4 @@ | |||||||
| extends: ./build/electron-builder.yml | extends: ./build/electron-builder.base.yml | ||||||
| linux: | linux: | ||||||
|   category: Audio |  | ||||||
|   icon: ./assets/icon.png |  | ||||||
|   target: |   target: | ||||||
|     - deb |     - deb | ||||||
|   | |||||||
| @@ -1,6 +1,4 @@ | |||||||
| extends: ./build/electron-builder.yml | extends: ./build/electron-builder.base.yml | ||||||
| linux: | linux: | ||||||
|   category: Audio |  | ||||||
|   icon: ./assets/icon.png |  | ||||||
|   target: |   target: | ||||||
|     - pacman |     - pacman | ||||||
|   | |||||||
| @@ -1,6 +1,4 @@ | |||||||
| extends: ./build/electron-builder.yml | extends: ./build/electron-builder.base.yml | ||||||
| linux: | linux: | ||||||
|     category: Audio |  | ||||||
|     icon: ./assets/TIDAL.icns |  | ||||||
|   target: |   target: | ||||||
|     - rpm |     - rpm | ||||||
|   | |||||||
| @@ -1,6 +1,4 @@ | |||||||
| extends: ./build/electron-builder.yml | extends: ./build/electron-builder.base.yml | ||||||
| linux: | linux: | ||||||
|   category: Audio |  | ||||||
|   icon: ./assets/icon.png |  | ||||||
|   target: |   target: | ||||||
|     - snap |     - snap | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								build/electron-builder.unpacked.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,4 @@ | |||||||
|  | extends: ./build/electron-builder.base.yml | ||||||
|  | linux: | ||||||
|  |   target: | ||||||
|  |     - dir | ||||||
| @@ -1,34 +1,16 @@ | |||||||
| appId: com.rickvanlieshout.tidal-hifi | extends: ./build/electron-builder.base.yml | ||||||
| electronDownload: |  | ||||||
|   mirror: https://github.com/castlabs/electron-releases/releases/download/v |  | ||||||
| snap: |  | ||||||
|   plugs: |  | ||||||
|     - default |  | ||||||
|     - screen-inhibit-control |  | ||||||
| linux: | linux: | ||||||
|   category: Audio |  | ||||||
|   target: |   target: | ||||||
|     # - pacman |     - pacman | ||||||
|     - tar.gz |     - tar.gz | ||||||
|     - deb |     - deb | ||||||
|     - rpm |     - rpm | ||||||
|     - AppImage |     - AppImage | ||||||
|     - snap |     - snap | ||||||
|     - freebsd |     - freebsd | ||||||
|   executableName: tidal-hifi |  | ||||||
|   desktop: |  | ||||||
|     Encoding: UTF-8 |  | ||||||
|     Name: tidal-hifi |  | ||||||
|     GenericName: tidal-hifi |  | ||||||
|     Comment: The web version of listen.tidal.com running in electron with hifi support thanks to widevine. |  | ||||||
|     Icon: assets/icon.png |  | ||||||
|     StartupNotify: true |  | ||||||
|     Terminal: false |  | ||||||
|     Type: Application |  | ||||||
|     Categories: Network;Application;Audio;Video |  | ||||||
|     StartupWMClass: tidal-hifi |  | ||||||
|     X-PulseAudio-Properties: media.role=music |  | ||||||
| mac: |  | ||||||
|   category: public.app-category.entertainment |  | ||||||
| win: | win: | ||||||
|   target: msi |   target: msi | ||||||
|  |   icon: icon.png | ||||||
|  |   artifactName: "tidalhifi" | ||||||
|  |   appId: com.rickvanlieshout.tidalhifi | ||||||
|  |   executableName: tidalhifi | ||||||
|   | |||||||
							
								
								
									
										
											BIN
										
									
								
								build/icon-inverted.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 24 KiB | 
							
								
								
									
										
											BIN
										
									
								
								build/icon.icns
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								build/icon.png
									
									
									
									
									
								
							
							
						
						| Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 32 KiB | 
							
								
								
									
										
											BIN
										
									
								
								build/icons/128x128.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 4.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								build/icons/16x16.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.2 KiB | 
							
								
								
									
										
											BIN
										
									
								
								build/icons/22x22.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								build/icons/24x24.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.5 KiB | 
							
								
								
									
										
											BIN
										
									
								
								build/icons/256x256 copy.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 9.0 KiB | 
							
								
								
									
										
											BIN
										
									
								
								build/icons/256x256.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 14 KiB | 
							
								
								
									
										
											BIN
										
									
								
								build/icons/32x32.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.7 KiB | 
							
								
								
									
										
											BIN
										
									
								
								build/icons/384x384.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 14 KiB | 
							
								
								
									
										
											BIN
										
									
								
								build/icons/48x48.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.2 KiB | 
							
								
								
									
										
											BIN
										
									
								
								build/icons/64x64.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.6 KiB | 
							
								
								
									
										
											BIN
										
									
								
								build/icons/icon-inverted.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 24 KiB | 
							
								
								
									
										
											BIN
										
									
								
								build/icons/icon.icns
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								build/icons/icon.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 32 KiB | 
| @@ -1,20 +0,0 @@ | |||||||
| pkgbase = tidal-hifi-git |  | ||||||
| 	pkgdesc = The web version of listen.tidal.com running in electron with hifi support thanks to widevine. |  | ||||||
| 	pkgver = 1.1.1 |  | ||||||
| 	pkgrel = 1 |  | ||||||
| 	url = https://github.com/Mastermindzh/tidal-hifi |  | ||||||
| 	arch = x86_64 |  | ||||||
| 	license = custom:MIT |  | ||||||
| 	makedepends = npm |  | ||||||
| 	makedepends = git |  | ||||||
| 	depends = libxss |  | ||||||
| 	depends = nss |  | ||||||
| 	depends = gtk3 |  | ||||||
| 	provides = tidal-hifi |  | ||||||
| 	source = https://github.com/Mastermindzh/tidal-hifi/archive/1.1.1.zip |  | ||||||
| 	source = tidal-hifi.desktop |  | ||||||
| 	sha512sums = a538b8b18c31c21017b2591e7fc2afa40ac4d33ffdbd59d7a14fd3c53aa6be11ee749d679ee4626a543c2b7f9dd154cef97e8ed3257cf8d46f389a0db765330d |  | ||||||
| 	sha512sums = 35f38ac308b871c1822d7f6f760f2fb54c3748cf769822cb0f0dfb90f0f5754ba9316da5e903a0d2e9839de3a43ec76f238f3f2e44021956fa1da19142081349 |  | ||||||
|  |  | ||||||
| pkgname = tidal-hifi-git |  | ||||||
|  |  | ||||||
| @@ -1,56 +0,0 @@ | |||||||
| # Maintainer: Rick van Lieshout <info@rickvanlieshout.com> |  | ||||||
|  |  | ||||||
| _pkgname=tidal-hifi |  | ||||||
| pkgname="$_pkgname-git" |  | ||||||
| pkgver=1.1.1 |  | ||||||
| pkgrel=1 |  | ||||||
| pkgdesc="The web version of listen.tidal.com running in electron with hifi support thanks to widevine." |  | ||||||
| arch=("x86_64") |  | ||||||
| url="https://github.com/Mastermindzh/tidal-hifi" |  | ||||||
| license=("custom:MIT") |  | ||||||
|  |  | ||||||
| depends=("libxss" "nss" "gtk3") |  | ||||||
| makedepends=("npm" "git") |  | ||||||
| provides=("$_pkgname") |  | ||||||
|  |  | ||||||
| source=("https://github.com/Mastermindzh/tidal-hifi/archive/$pkgver.zip" |  | ||||||
|         "${_pkgname}.desktop") |  | ||||||
| sha512sums=('a538b8b18c31c21017b2591e7fc2afa40ac4d33ffdbd59d7a14fd3c53aa6be11ee749d679ee4626a543c2b7f9dd154cef97e8ed3257cf8d46f389a0db765330d' |  | ||||||
|             '35f38ac308b871c1822d7f6f760f2fb54c3748cf769822cb0f0dfb90f0f5754ba9316da5e903a0d2e9839de3a43ec76f238f3f2e44021956fa1da19142081349') |  | ||||||
|  |  | ||||||
| cdToPkg(){ |  | ||||||
|     cd "tidal-hifi-$pkgver" |  | ||||||
| } |  | ||||||
|  |  | ||||||
| prepare() { |  | ||||||
|     cdToPkg |  | ||||||
|  |  | ||||||
|     # install build dependencies |  | ||||||
|     npm install |  | ||||||
| } |  | ||||||
|  |  | ||||||
| build() { |  | ||||||
|     cdToPkg |  | ||||||
|  |  | ||||||
|     # We are not using the systems Electron as we need castlab's Electron. |  | ||||||
|     npm run build-arch |  | ||||||
| } |  | ||||||
|  |  | ||||||
| package() { |  | ||||||
|     cdToPkg |  | ||||||
|  |  | ||||||
|     install -d "${pkgdir}/opt/${_pkgname}/" "${pkgdir}/usr/bin" "${pkgdir}/usr/share/doc" "${pkgdir}/usr/share/licenses" |  | ||||||
|  |  | ||||||
|     cp -r dist/linux-unpacked/* "${pkgdir}/opt/${_pkgname}/" |  | ||||||
|     chmod +x "${pkgdir}/opt/${_pkgname}/${_pkgname}" |  | ||||||
|  |  | ||||||
|     ln -s "/opt/${_pkgname}/${_pkgname}" "${pkgdir}/usr/bin/${_pkgname}" |  | ||||||
|  |  | ||||||
|     install -Dm 644 "build/icon.png" "${pkgdir}/usr/share/pixmaps/${_pkgname}.png" |  | ||||||
|     install -Dm 644 "${srcdir}/${_pkgname}.desktop" "${pkgdir}/usr/share/applications/${_pkgname}.desktop" |  | ||||||
|  |  | ||||||
|     install -Dm 644 "README.md" "${pkgdir}/usr/share/doc/${pkgname}/README.md" |  | ||||||
|     install -Dm 644 "LICENSE" "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE" |  | ||||||
|     ln -s "/opt/${_pkgname}/LICENSE.electron.txt" "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE.electron.txt" |  | ||||||
|     ln -s "/opt/${_pkgname}/LICENSES.chromium.html" "${pkgdir}/usr/share/licenses/${pkgname}/LICENSES.chromium.html" |  | ||||||
| } |  | ||||||
| @@ -1,18 +0,0 @@ | |||||||
| #!/bin/bash |  | ||||||
| # Will generate a correctly formatted SRCINFO file |  | ||||||
|  |  | ||||||
| SCRIPT_DIST=".SRCINFO" |  | ||||||
|  |  | ||||||
| # generate SRCINFO |  | ||||||
| makepkg --printsrcinfo > $SCRIPT_DIST |  | ||||||
|  |  | ||||||
| # replace pkgbase with tidal-hifi-git |  | ||||||
| pkgName="tidal-hifi-git" |  | ||||||
| sed -i "1s/.*/pkgbase = $pkgName/" $SCRIPT_DIST |  | ||||||
|  |  | ||||||
| # replace pkgbase with tidal-hifi-git |  | ||||||
| sed -i '/^pkgname/ d' $SCRIPT_DIST |  | ||||||
| echo "pkgname = $pkgName" >> $SCRIPT_DIST |  | ||||||
|  |  | ||||||
| # remove double line breaks and replace with single line breaks |  | ||||||
| sed -i '/^$/N;/^\n$/D' $SCRIPT_DIST |  | ||||||
| @@ -1,13 +0,0 @@ | |||||||
| [Desktop Entry] |  | ||||||
| Encoding=UTF-8 |  | ||||||
| Name=tidal-hifi |  | ||||||
| GenericName=tidal-hifi |  | ||||||
| Comment=The web version of listen.tidal.com running in electron with hifi support thanks to widevine. |  | ||||||
| Exec=tidal-hifi %u |  | ||||||
| Icon=tidal-hifi.png |  | ||||||
| StartupNotify=true |  | ||||||
| Terminal=false |  | ||||||
| Type=Application |  | ||||||
| Categories=Network;Application;Audio;Video |  | ||||||
| StartupWMClass=tidal-hifi |  | ||||||
| X-PulseAudio-Properties=media.role=music |  | ||||||
							
								
								
									
										
											BIN
										
									
								
								docs/images/customcss-config.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 38 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/images/customcss-menu.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 262 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/images/customcss.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 36 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/images/discord.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 4.5 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/images/integrations.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 63 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/images/new-about.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 41 KiB | 
| Before Width: | Height: | Size: 800 KiB After Width: | Height: | Size: 800 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/images/settings-preview.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 726 KiB | 
| Before Width: | Height: | Size: 317 KiB After Width: | Height: | Size: 317 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/images/swagger.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 88 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/images/theming.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 49 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/images/tokyo-night.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.7 MiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/no-dutch-music.mp4
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										38
									
								
								docs/theming.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,38 @@ | |||||||
|  | # Theming TIDAL Hi-Fi | ||||||
|  |  | ||||||
|  | ## Table of contents | ||||||
|  |  | ||||||
|  | <!-- toc --> | ||||||
|  |  | ||||||
|  | - [Theming TIDAL Hi-Fi](#theming-TIDAL Hi-Fi) | ||||||
|  |   - [Table of contents](#table-of-contents) | ||||||
|  |   - [Custom CSS](#custom-css) | ||||||
|  |   - [config](#config) | ||||||
|  |   - [Warning! Themes might break](#warning-themes-might-break) | ||||||
|  |  | ||||||
|  | <!-- tocstop --> | ||||||
|  |  | ||||||
|  | By default TIDAL Hi-Fi comes with a few themes. | ||||||
|  | You can select these in the settings window under the theming tab as shown below. | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## Custom CSS | ||||||
|  |  | ||||||
|  | The custom CSS will be added to the HTML document last. | ||||||
|  | This means that it will overwrite any existing CSS, even that of themes, unless the original has an access modifier such as `$important`. | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## config | ||||||
|  |  | ||||||
|  | The theme selector and customCSS are stored in the config file. | ||||||
|  | The custom CSS is stored as a list of lines. | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## Warning! Themes might break | ||||||
|  |  | ||||||
|  | Themes might break at any point. Tidal changes their webpage structure a ton (they probably generate classNames and don't provide roles/ids/attributes.) | ||||||
|  |  | ||||||
|  | If one breaks you can create an Issue on GitHub or ask for assistance in the [Discord channel](https://discord.gg/yhNwf4v4He). | ||||||
							
								
								
									
										12829
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
							
								
								
									
										85
									
								
								package.json
									
									
									
									
									
								
							
							
						
						| @@ -1,41 +1,84 @@ | |||||||
| { | { | ||||||
|   "name": "tidal-hifi", |   "name": "tidal-hifi", | ||||||
|   "version": "1.1.1", |   "version": "5.20.1", | ||||||
|   "description": "Tidal on Electron with widevine(hifi) support", |   "description": "Tidal on Electron with widevine(hifi) support", | ||||||
|   "main": "src/main.js", |   "main": "ts-dist/main.js", | ||||||
|   "scripts": { |   "scripts": { | ||||||
|     "start": "electron .", |     "start": "electron --inspect=0.0.0.0:5858 --remote-debugging-port=8315 --remote-allow-origins=* .", | ||||||
|     "build": "electron-builder --publish=never -c ./build/electron-builder.yml", |     "watchStart": "nodemon dist -x \"npm run start\"", | ||||||
|     "build-deb": "electron-builder --publish=never -c ./build/electron-builder.deb.yml", |     "compile": "tsc && npm run sass-and-copy", | ||||||
|     "build-rpm": "electron-builder --publish=never -c ./build/electron-builder.rpm.yml", |     "deps": "npm run watch", | ||||||
|     "build-snap": "electron-builder --publish=never -c ./build/electron-builder.snap.yml", |     "watch": "tsc-watch --onSuccess \"npm run compile-all\"", | ||||||
|     "build-arch": "electron-builder --publish=never -c ./build/electron-builder.pacman.yml", |     "copy-files": "copyfiles -u 1 --exclude './src/**/*.ts' --exclude './src/**/*.scss' \"./src/**/*\" ts-dist", | ||||||
|     "build-wl": "electron-builder --publish=never -c ./build/electron-builder.yml -wl", |     "copy-themes-dev": "copyfiles -u 1 \"./themes/*\" node_modules/electron/dist/resources", | ||||||
|     "build-mac": "electron-builder --publish=never -c ./build/electron-builder.yml -m" |     "compile-all": "npm run sass-and-copy && ts-node scripts/generate-swagger.ts", | ||||||
|  |     "sass-and-copy": "npm run sass && npm run copy-files && npm run copy-themes-dev", | ||||||
|  |     "build": "npm run builder -- -c ./build/electron-builder.yml", | ||||||
|  |     "build-deb": "npm run builder -- -c ./build/electron-builder.deb.yml", | ||||||
|  |     "build-unpacked": "npm run builder -- -c ./build/electron-builder.unpacked.yml", | ||||||
|  |     "build-rpm": "npm run builder -- -c ./build/electron-builder.rpm.yml", | ||||||
|  |     "build-snap": "npm run builder -- -c ./build/electron-builder.snap.yml", | ||||||
|  |     "build-arch": "npm run builder -- -c ./build/electron-builder.pacman.yml", | ||||||
|  |     "build-wl": "npm run builder -- -c ./build/electron-builder.yml -wl", | ||||||
|  |     "build-mac": "npm run builder -- -c ./build/electron-builder.yml -m", | ||||||
|  |     "build-base": "npm run builder -- -c ./build/electron-builder.base.yml", | ||||||
|  |     "prebuilder": "npm run compile", | ||||||
|  |     "builder": "electron-builder --publish=never", | ||||||
|  |     "sass": "sass ./src/pages/settings/settings.scss ./src/pages/settings/settings.css && sass --no-source-map src/themes:themes", | ||||||
|  |     "style-lint": "npx stylelint **/*.scss", | ||||||
|  |     "style-lint-fix": "npx stylelint --fix **/*.scss" | ||||||
|   }, |   }, | ||||||
|   "keywords": [ |   "keywords": [ | ||||||
|     "electron", |     "electron", | ||||||
|     "hifi", |     "hifi", | ||||||
|     "widevine", |     "widevine", | ||||||
|     "linux" |     "linux", | ||||||
|  |     "drm", | ||||||
|  |     "castlabs" | ||||||
|   ], |   ], | ||||||
|   "author": "Rick van Lieshout <info@rickvanlieshout.com> (http://rickvanlieshout.com)", |   "author": "Rick van Lieshout <info@rickvanlieshout.com> (http://rickvanlieshout.com)", | ||||||
|   "homepage": "https://github.com/Mastermindzh/tidal-hifi", |   "homepage": "https://github.com/Mastermindzh/tidal-hifi", | ||||||
|   "license": "MIT", |   "license": "MIT", | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "electron-store": "^5.1.1", |     "@electron/remote": "^2.1.3", | ||||||
|     "express": "^4.17.1", |     "@types/swagger-jsdoc": "^6.0.4", | ||||||
|     "hotkeys-js": "^3.7.6", |     "@xhayper/discord-rpc": "1.3.0", | ||||||
|     "node-notifier": "^6.0.0", |     "axios": "^1.12.0", | ||||||
|     "request": "^2.88.2" |     "cors": "^2.8.5", | ||||||
|  |     "electron-store": "^8.2.0", | ||||||
|  |     "express": "^5.1.0", | ||||||
|  |     "hotkeys-js": "^3.13.15", | ||||||
|  |     "mpris-service": "^2.1.2", | ||||||
|  |     "request": "^2.88.2", | ||||||
|  |     "sass": "1.93.2", | ||||||
|  |     "swagger-ui-express": "^5.0.1" | ||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "@mastermindzh/prettier-config": "^1.0.0", |     "@mastermindzh/prettier-config": "^1.0.0", | ||||||
|     "electron": "git+https://github.com/castlabs/electron-releases.git#v8.5.2-wvvmp", |     "@types/cors": "^2.8.19", | ||||||
|     "electron-builder": "^21.2.0", |     "@types/express": "^5.0.3", | ||||||
|     "electron-reload": "^1.5.0", |     "@types/node": "^22.16.2", | ||||||
|     "prettier": "^2.0.4", |     "@types/request": "^2.48.12", | ||||||
|     "dot-prop": ">=4.2.1" |     "@types/swagger-ui-express": "^4.1.8", | ||||||
|  |     "@typescript-eslint/eslint-plugin": "^8.36.0", | ||||||
|  |     "@typescript-eslint/parser": "^8.36.0", | ||||||
|  |     "copyfiles": "^2.4.1", | ||||||
|  |     "electron": "github:castlabs/electron-releases#v37.2.5+wvcus", | ||||||
|  |     "electron-builder": "~26.0.12", | ||||||
|  |     "eslint": "^9.30.1", | ||||||
|  |     "js-yaml": "^4.1.0", | ||||||
|  |     "markdown-toc": "^1.2.0", | ||||||
|  |     "node-abi": "^4.12.0", | ||||||
|  |     "nodemon": "^3.1.10", | ||||||
|  |     "prettier": "^3.6.2", | ||||||
|  |     "stylelint": "^16.21.1", | ||||||
|  |     "stylelint-config-standard": "^39.0.0", | ||||||
|  |     "stylelint-config-standard-scss": "^16.0.0", | ||||||
|  |     "stylelint-prettier": "^5.0.3", | ||||||
|  |     "swagger-jsdoc": "^6.2.8", | ||||||
|  |     "ts-node": "^10.9.2", | ||||||
|  |     "tsc-watch": "^7.1.1", | ||||||
|  |     "typescript": "^5.8.3" | ||||||
|   }, |   }, | ||||||
|   "prettier": "@mastermindzh/prettier-config" |   "prettier": "@mastermindzh/prettier-config" | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										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"] | ||||||
|  | } | ||||||
							
								
								
									
										30
									
								
								scripts/generate-swagger.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,30 @@ | |||||||
|  | import fs from "fs"; | ||||||
|  | import swaggerjsdoc from "swagger-jsdoc"; | ||||||
|  | import packagejson from "./../package.json"; | ||||||
|  |  | ||||||
|  | const specs = swaggerjsdoc({ | ||||||
|  |   definition: { | ||||||
|  |     openapi: "3.1.0", | ||||||
|  |     info: { | ||||||
|  |       title: "TIDAL Hi-Fi API", | ||||||
|  |       version: packagejson.version, | ||||||
|  |       description: "", | ||||||
|  |       license: { | ||||||
|  |         name: packagejson.license, | ||||||
|  |         url: "https://github.com/Mastermindzh/tidal-hifi/blob/master/LICENSE", | ||||||
|  |       }, | ||||||
|  |       contact: { | ||||||
|  |         name: "Rick <mastermindzh> van Lieshout", | ||||||
|  |         url: "https://www.rickvanlieshout.com", | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     externalDocs: { | ||||||
|  |       description: "swagger.json", | ||||||
|  |       url: "swagger.json", | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  |   apis: ["**/*.ts"], | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | fs.writeFileSync("src/features/api/swagger.json", JSON.stringify(specs, null, 2), "utf8"); | ||||||
|  | console.log("Written swagger.json"); | ||||||
							
								
								
									
										16
									
								
								scripts/resize-icons.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,16 @@ | |||||||
|  | #!/bin/bash | ||||||
|  |  | ||||||
|  | if [ "$1" != "" ]; then # check if arg 1 is present | ||||||
|  |     FILE=$1 | ||||||
|  | else | ||||||
|  |     echo "Please provide a file as an argument." | ||||||
|  |     exit 1 | ||||||
|  | fi | ||||||
|  |  | ||||||
|  | SIZES=("16x16" "22x22" "24x24" "32x32" "48x48" "64x64" "128x128" "256x256" "384x384") | ||||||
|  |  | ||||||
|  | echo "Resizing $FILE..." | ||||||
|  |  | ||||||
|  | for i in "${SIZES[@]}"; do | ||||||
|  |     convert "$FILE" -resize "$i" "$i.png" | ||||||
|  | done | ||||||
							
								
								
									
										48
									
								
								scripts/verifyElements.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,48 @@ | |||||||
|  | // for some dumb reason `listen.tidal.com` has disabled console.log | ||||||
|  | // we can simply return an array with values though... | ||||||
|  | // run this on a playlist or mix page and observe the result | ||||||
|  | // NOTE: play & pause can't live together so one or the other will throw an error | ||||||
|  | (() => { | ||||||
|  |   let elements = { | ||||||
|  |     play: '*[data-test="play"]', | ||||||
|  |     pause: '*[data-test="pause"]', | ||||||
|  |     next: '*[data-test="next"]', | ||||||
|  |     previous: 'button[data-test="previous"]', | ||||||
|  |     title: '*[data-test^="footer-track-title"]', | ||||||
|  |     artists: '*[data-test^="grid-item-detail-text-title-artist"]', | ||||||
|  |     home: '*[data-test="menu--home"]', | ||||||
|  |     back: '[title^="Back"]', | ||||||
|  |     forward: '[title^="Next"]', | ||||||
|  |     search: '[class^="searchField"]', | ||||||
|  |     shuffle: '*[data-test="shuffle"]', | ||||||
|  |     repeat: '*[data-test="repeat"]', | ||||||
|  |     account: '*[data-test^="profile-image-button"]', | ||||||
|  |     settings: '*[data-test^="sidebar-menu-button"]', | ||||||
|  |     openSettings: '*[data-test^="open-settings"]', | ||||||
|  |     media: '*[data-test="current-media-imagery"]', | ||||||
|  |     image: "img", | ||||||
|  |     current: '*[data-test="current-time"]', | ||||||
|  |     duration: '*[class^=_playbackControlsContainer] *[data-test="duration"]', | ||||||
|  |     bar: '*[data-test="progress-bar"]', | ||||||
|  |     footer: "#footerPlayer", | ||||||
|  |     mediaItem: "[data-type='mediaItem']", | ||||||
|  |     album_header_title: '*[class^="_playingFrom"] span:nth-child(2)', | ||||||
|  |     playing_from: '*[class^="_playingFrom"] span:nth-child(2)', | ||||||
|  |     queue_album: "*[class^=playQueueItemsContainer] *[class^=groupTitle] span:nth-child(2)", | ||||||
|  |     currentlyPlaying: "[class^='isPlayingIcon'], [data-test-is-playing='true']", | ||||||
|  |     album_name_cell: '[class^="album"]', | ||||||
|  |     tracklist_row: '[data-test="tracklist-row"]', | ||||||
|  |     volume: '*[data-test="volume"]', | ||||||
|  |     favorite: '*[data-test="footer-favorite-button"]', | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   let results = []; | ||||||
|  |  | ||||||
|  |   Object.entries(elements).forEach(([key, value]) => { | ||||||
|  |     const returnValue = document.querySelector(`${value}`); | ||||||
|  |     if (!returnValue) { | ||||||
|  |       results.push(`element ${key} not found`); | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  |   return results; | ||||||
|  | })(); | ||||||
							
								
								
									
										9
									
								
								src/constants/flags.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,9 @@ | |||||||
|  | export const flags: { [key: string]: { flag: string; value?: string }[] } = { | ||||||
|  |   gpuRasterization: [{ flag: "enable-gpu-rasterization", value: undefined }], | ||||||
|  |   disableHardwareMediaKeys: [{ flag: "disable-features", value: "HardwareMediaKeyHandling" }], | ||||||
|  |   enableWaylandSupport: [ | ||||||
|  |     { flag: "enable-features", value: "UseOzonePlatform" }, | ||||||
|  |     { flag: "ozone-platform-hint", value: "auto" }, | ||||||
|  |     { flag: "enable-features", value: "WaylandWindowDecorations" }, | ||||||
|  |   ], | ||||||
|  | }; | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| const globalEvents = { | export const globalEvents = { | ||||||
|   play: "play", |   play: "play", | ||||||
|   pause: "pause", |   pause: "pause", | ||||||
|   playPause: "playPause", |   playPause: "playPause", | ||||||
| @@ -6,10 +6,13 @@ const globalEvents = { | |||||||
|   previous: "previous", |   previous: "previous", | ||||||
|   updateInfo: "update-info", |   updateInfo: "update-info", | ||||||
|   hideSettings: "hideSettings", |   hideSettings: "hideSettings", | ||||||
|  |   refreshMenuBar: "refreshMenubar", | ||||||
|   showSettings: "showSettings", |   showSettings: "showSettings", | ||||||
|   updateStatus: "update-status", |  | ||||||
|   storeChanged: "storeChanged", |   storeChanged: "storeChanged", | ||||||
|   error: "error", |   error: "error", | ||||||
|  |   getUniversalLink: "getUniversalLink", | ||||||
|  |   log: "log", | ||||||
|  |   toggleFavorite: "toggleFavorite", | ||||||
|  |   toggleShuffle: "toggleShuffle", | ||||||
|  |   toggleRepeat: "toggleRepeat", | ||||||
| }; | }; | ||||||
| 
 |  | ||||||
| module.exports = globalEvents; |  | ||||||
| @@ -1,9 +1,7 @@ | |||||||
| const globalEvents = require("./globalEvents"); | import { globalEvents } from "./globalEvents"; | ||||||
| 
 | 
 | ||||||
| const mediaKeys = { | export const mediaKeys = { | ||||||
|   MediaPlayPause: globalEvents.playPause, |   MediaPlayPause: globalEvents.playPause, | ||||||
|   MediaNextTrack: globalEvents.next, |   MediaNextTrack: globalEvents.next, | ||||||
|   MediaPreviousTrack: globalEvents.previous, |   MediaPreviousTrack: globalEvents.previous, | ||||||
| }; | }; | ||||||
| 
 |  | ||||||
| module.exports = mediaKeys; |  | ||||||
| @@ -1,28 +0,0 @@ | |||||||
| /** |  | ||||||
|  * Object to type my settings file: |  | ||||||
|  * |  | ||||||
|  *    notifications: true, |  | ||||||
|  *    api: true, |  | ||||||
|  *    apiSettings: { |  | ||||||
|  *      port: 47836, |  | ||||||
|  *    }, |  | ||||||
|  *    windowBounds: { width: 800, height: 600 }, |  | ||||||
|  */ |  | ||||||
| const settings = { |  | ||||||
|   notifications: "notifications", |  | ||||||
|   api: "api", |  | ||||||
|   menuBar: "menuBar", |  | ||||||
|   playBackControl: "playBackControl", |  | ||||||
|   apiSettings: { |  | ||||||
|     root: "apiSettings", |  | ||||||
|     port: "apiSettings.port", |  | ||||||
|   }, |  | ||||||
|   mpris: "mpris", |  | ||||||
|   windowBounds: { |  | ||||||
|     root: "windowBounds", |  | ||||||
|     width: "windowBounds.width", |  | ||||||
|     height: "windowBounds.height", |  | ||||||
|   }, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| module.exports = settings; |  | ||||||
							
								
								
									
										67
									
								
								src/constants/settings.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,67 @@ | |||||||
|  | /** | ||||||
|  |  * Object to type my settings file: | ||||||
|  |  * | ||||||
|  |  *    notifications: true, | ||||||
|  |  *    api: true, | ||||||
|  |  *    apiSettings: { | ||||||
|  |  *      port: 47836, | ||||||
|  |  *    }, | ||||||
|  |  *    windowBounds: { width: 800, height: 600 }, | ||||||
|  |  */ | ||||||
|  | export const settings = { | ||||||
|  |   adBlock: "adBlock", | ||||||
|  |   advanced: { | ||||||
|  |     root: "advanced", | ||||||
|  |     tidalUrl: "advanced.tidalUrl", | ||||||
|  |   }, | ||||||
|  |   api: "api", | ||||||
|  |   apiSettings: { | ||||||
|  |     root: "apiSettings", | ||||||
|  |     port: "apiSettings.port", | ||||||
|  |     hostname: "apiSettings.hostname", | ||||||
|  |   }, | ||||||
|  |   customCSS: "customCSS", | ||||||
|  |   disableBackgroundThrottle: "disableBackgroundThrottle", | ||||||
|  |   disableHardwareMediaKeys: "disableHardwareMediaKeys", | ||||||
|  |   enableCustomHotkeys: "enableCustomHotkeys", | ||||||
|  |   enableDiscord: "enableDiscord", | ||||||
|  |   discord: { | ||||||
|  |     detailsPrefix: "discord.detailsPrefix", | ||||||
|  |     buttonText: "discord.buttonText", | ||||||
|  |     includeTimestamps: "discord.includeTimestamps", | ||||||
|  |     showSong: "discord.showSong", | ||||||
|  |     showIdle: "discord.showIdle", | ||||||
|  |     idleText: "discord.idleText", | ||||||
|  |     usingText: "discord.usingText", | ||||||
|  |   }, | ||||||
|  |   ListenBrainz: { | ||||||
|  |     root: "ListenBrainz", | ||||||
|  |     enabled: "ListenBrainz.enabled", | ||||||
|  |     api: "ListenBrainz.api", | ||||||
|  |     token: "ListenBrainz.token", | ||||||
|  |     delay: "ListenBrainz.delay", | ||||||
|  |   }, | ||||||
|  |   flags: { | ||||||
|  |     root: "flags", | ||||||
|  |     disableHardwareMediaKeys: "flags.disableHardwareMediaKeys", | ||||||
|  |     gpuRasterization: "flags.gpuRasterization", | ||||||
|  |     enableWaylandSupport: "flags.enableWaylandSupport", | ||||||
|  |   }, | ||||||
|  |   menuBar: "menuBar", | ||||||
|  |   minimizeOnClose: "minimizeOnClose", | ||||||
|  |   mpris: "mpris", | ||||||
|  |   notifications: "notifications", | ||||||
|  |   playBackControl: "playBackControl", | ||||||
|  |   singleInstance: "singleInstance", | ||||||
|  |   skipArtists: "skipArtists", | ||||||
|  |   skippedArtists: "skippedArtists", | ||||||
|  |   staticWindowTitle: "staticWindowTitle", | ||||||
|  |   theme: "theme", | ||||||
|  |   trayIcon: "trayIcon", | ||||||
|  |   updateFrequency: "updateFrequency", | ||||||
|  |   windowBounds: { | ||||||
|  |     root: "windowBounds", | ||||||
|  |     width: "windowBounds.width", | ||||||
|  |     height: "windowBounds.height", | ||||||
|  |   }, | ||||||
|  | }; | ||||||
| @@ -1,4 +0,0 @@ | |||||||
| module.exports = { |  | ||||||
|   playing: "playing", |  | ||||||
|   paused: "paused", |  | ||||||
| }; |  | ||||||
							
								
								
									
										3
									
								
								src/constants/values.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,3 @@ | |||||||
|  | export default { | ||||||
|  |   name: "TIDAL Hi-Fi", | ||||||
|  | }; | ||||||
							
								
								
									
										1
									
								
								src/declarations.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1 @@ | |||||||
|  | declare module "mpris-service"; | ||||||
							
								
								
									
										124
									
								
								src/features/api/features/current.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,124 @@ | |||||||
|  | import { Request, Response, Router } from "express"; | ||||||
|  | import fs from "fs"; | ||||||
|  | import { mediaInfo } from "../../../scripts/mediaInfo"; | ||||||
|  |  | ||||||
|  | export const addCurrentInfo = (expressApp: Router) => { | ||||||
|  |   /** | ||||||
|  |    * @swagger | ||||||
|  |    * tags: | ||||||
|  |    *   name: current | ||||||
|  |    *   description: The current media info API | ||||||
|  |    * components: | ||||||
|  |    *   schemas: | ||||||
|  |    *     MediaInfo: | ||||||
|  |    *       type: object | ||||||
|  |    *       properties: | ||||||
|  |    *         title: | ||||||
|  |    *           type: string | ||||||
|  |    *         artists: | ||||||
|  |    *           type: string | ||||||
|  |    *         album: | ||||||
|  |    *           type: string | ||||||
|  |    *         icon: | ||||||
|  |    *           type: string | ||||||
|  |    *           format: uri | ||||||
|  |    *         playingFrom: | ||||||
|  |    *           type: string | ||||||
|  |    *         status: | ||||||
|  |    *           type: string | ||||||
|  |    *         url: | ||||||
|  |    *           type: string | ||||||
|  |    *           format: uri | ||||||
|  |    *         current: | ||||||
|  |    *           type: string | ||||||
|  |    *         currentInSeconds: | ||||||
|  |    *           type: integer | ||||||
|  |    *         duration: | ||||||
|  |    *           type: string | ||||||
|  |    *         durationInSeconds: | ||||||
|  |    *           type: integer | ||||||
|  |    *         image: | ||||||
|  |    *           type: string | ||||||
|  |    *           format: uri | ||||||
|  |    *         favorite: | ||||||
|  |    *           type: boolean | ||||||
|  |    *         player: | ||||||
|  |    *           type: object | ||||||
|  |    *           properties: | ||||||
|  |    *             status: | ||||||
|  |    *               type: string | ||||||
|  |    *             shuffle: | ||||||
|  |    *               type: boolean | ||||||
|  |    *             repeat: | ||||||
|  |    *               type: string | ||||||
|  |    *         artist: | ||||||
|  |    *           type: string | ||||||
|  |    *       example: | ||||||
|  |    *         title: "Sample Title" | ||||||
|  |    *         artists: "Sample Artist" | ||||||
|  |    *         album: "Sample Album" | ||||||
|  |    *         icon: "/path/to/sample/icon.jpg" | ||||||
|  |    *         playingFrom: "Sample Playlist" | ||||||
|  |    *         status: "playing" | ||||||
|  |    *         url: "https://tidal.com/browse/track/sample" | ||||||
|  |    *         current: "1:23" | ||||||
|  |    *         currentInSeconds: 83 | ||||||
|  |    *         duration: "3:45" | ||||||
|  |    *         durationInSeconds: 225 | ||||||
|  |    *         image: "https://example.com/sample-image.jpg" | ||||||
|  |    *         favorite: true | ||||||
|  |    *         player: | ||||||
|  |    *           status: "playing" | ||||||
|  |    *           shuffle: true | ||||||
|  |    *           repeat: "one" | ||||||
|  |    *         artist: "Sample Artist" | ||||||
|  |    */ | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * @swagger | ||||||
|  |    * /current: | ||||||
|  |    *   get: | ||||||
|  |    *     summary: Get current media info | ||||||
|  |    *     tags: [current] | ||||||
|  |    *     responses: | ||||||
|  |    *       200: | ||||||
|  |    *         description: Current media info | ||||||
|  |    *         content: | ||||||
|  |    *           application/json: | ||||||
|  |    *             schema: | ||||||
|  |    *               $ref: '#/components/schemas/MediaInfo' | ||||||
|  |    */ | ||||||
|  |   expressApp.get("/current", (_req, res) => { | ||||||
|  |     res.json({ ...mediaInfo, artist: mediaInfo.artists }); | ||||||
|  |   }); | ||||||
|  |   /** | ||||||
|  |    * @swagger | ||||||
|  |    * /current/image: | ||||||
|  |    *   get: | ||||||
|  |    *     summary: Get current media image | ||||||
|  |    *     tags: [current] | ||||||
|  |    *     responses: | ||||||
|  |    *       200: | ||||||
|  |    *         description: Current media image | ||||||
|  |    *         content: | ||||||
|  |    *           image/png: | ||||||
|  |    *             schema: | ||||||
|  |    *               type: string | ||||||
|  |    *               format: binary | ||||||
|  |    *       404: | ||||||
|  |    *         description: Not found | ||||||
|  |    */ | ||||||
|  |   expressApp.get("/current/image", getCurrentImage); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export const getCurrentImage = (req: Request, res: Response) => { | ||||||
|  |   const stream = fs.createReadStream(mediaInfo.icon); | ||||||
|  |   stream.on("open", function () { | ||||||
|  |     res.set("Content-Type", "image/png"); | ||||||
|  |     stream.pipe(res); | ||||||
|  |   }); | ||||||
|  |   stream.on("error", function () { | ||||||
|  |     res.set("Content-Type", "text/plain"); | ||||||
|  |     res.status(404).end("Not found"); | ||||||
|  |   }); | ||||||
|  | }; | ||||||
							
								
								
									
										164
									
								
								src/features/api/features/player.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,164 @@ | |||||||
|  | import { BrowserWindow } from "electron"; | ||||||
|  | import { Router } from "express"; | ||||||
|  | import { globalEvents } from "../../../constants/globalEvents"; | ||||||
|  | import { settings } from "../../../constants/settings"; | ||||||
|  | import { MediaStatus } from "../../../models/mediaStatus"; | ||||||
|  | import { mediaInfo } from "../../../scripts/mediaInfo"; | ||||||
|  | import { settingsStore } from "../../../scripts/settings"; | ||||||
|  | import { handleWindowEvent } from "../helpers/handleWindowEvent"; | ||||||
|  |  | ||||||
|  | export const addPlaybackControl = (expressApp: Router, mainWindow: BrowserWindow) => { | ||||||
|  |   const windowEvent = handleWindowEvent(mainWindow); | ||||||
|  |   const createRoute = (route: string) => `/player${route}`; | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * @swagger | ||||||
|  |    * tags: | ||||||
|  |    *   name: player | ||||||
|  |    *   description: The player control API | ||||||
|  |    * components: | ||||||
|  |    *   schemas: | ||||||
|  |    *     OkResponse: | ||||||
|  |    *       type: string | ||||||
|  |    *       example: "OK" | ||||||
|  |    */ | ||||||
|  |   const createPlayerAction = (route: string, action: string) => { | ||||||
|  |     expressApp.post(createRoute(route), (req, res) => windowEvent(res, action)); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   if (settingsStore.get(settings.playBackControl)) { | ||||||
|  |     /** | ||||||
|  |      * @swagger | ||||||
|  |      * /player/play: | ||||||
|  |      *   post: | ||||||
|  |      *     summary: Play the current media | ||||||
|  |      *     tags: [player] | ||||||
|  |      *     responses: | ||||||
|  |      *       200: | ||||||
|  |      *         description: Ok | ||||||
|  |      *         content: | ||||||
|  |      *           text/plain: | ||||||
|  |      *             schema: | ||||||
|  |      *               $ref: '#/components/schemas/OkResponse' | ||||||
|  |      */ | ||||||
|  |     createPlayerAction("/play", globalEvents.play); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @swagger | ||||||
|  |      * /player/favorite/toggle: | ||||||
|  |      *   post: | ||||||
|  |      *     summary: Add the current media to your favorites, or remove it if its already added to your favorites | ||||||
|  |      *     tags: [player] | ||||||
|  |      *     responses: | ||||||
|  |      *       200: | ||||||
|  |      *         description: Ok | ||||||
|  |      *         content: | ||||||
|  |      *           text/plain: | ||||||
|  |      *             schema: | ||||||
|  |      *               $ref: '#/components/schemas/OkResponse' | ||||||
|  |      */ | ||||||
|  |     createPlayerAction("/favorite/toggle", globalEvents.toggleFavorite); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @swagger | ||||||
|  |      * /player/pause: | ||||||
|  |      *   post: | ||||||
|  |      *     summary: Pause the current media | ||||||
|  |      *     tags: [player] | ||||||
|  |      *     responses: | ||||||
|  |      *       200: | ||||||
|  |      *         description: Ok | ||||||
|  |      *         content: | ||||||
|  |      *           text/plain: | ||||||
|  |      *             schema: | ||||||
|  |      *               $ref: '#/components/schemas/OkResponse' | ||||||
|  |      */ | ||||||
|  |     createPlayerAction("/pause", globalEvents.pause); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @swagger | ||||||
|  |      * /player/next: | ||||||
|  |      *   post: | ||||||
|  |      *     summary: Play the next song | ||||||
|  |      *     tags: [player] | ||||||
|  |      *     responses: | ||||||
|  |      *       200: | ||||||
|  |      *         description: Ok | ||||||
|  |      *         content: | ||||||
|  |      *           text/plain: | ||||||
|  |      *             schema: | ||||||
|  |      *               $ref: '#/components/schemas/OkResponse' | ||||||
|  |      */ | ||||||
|  |     createPlayerAction("/next", globalEvents.next); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @swagger | ||||||
|  |      * /player/previous: | ||||||
|  |      *   post: | ||||||
|  |      *     summary: Play the previous song | ||||||
|  |      *     tags: [player] | ||||||
|  |      *     responses: | ||||||
|  |      *       200: | ||||||
|  |      *         description: Ok | ||||||
|  |      *         content: | ||||||
|  |      *           text/plain: | ||||||
|  |      *             schema: | ||||||
|  |      *               $ref: '#/components/schemas/OkResponse' | ||||||
|  |      */ | ||||||
|  |     createPlayerAction("/previous", globalEvents.previous); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @swagger | ||||||
|  |      * /player/shuffle/toggle: | ||||||
|  |      *   post: | ||||||
|  |      *     summary: Play the previous song | ||||||
|  |      *     tags: [player] | ||||||
|  |      *     responses: | ||||||
|  |      *       200: | ||||||
|  |      *         description: Ok | ||||||
|  |      *         content: | ||||||
|  |      *           text/plain: | ||||||
|  |      *             schema: | ||||||
|  |      *               $ref: '#/components/schemas/OkResponse' | ||||||
|  |      */ | ||||||
|  |     createPlayerAction("/shuffle/toggle", globalEvents.toggleShuffle); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @swagger | ||||||
|  |      * /player/repeat/toggle: | ||||||
|  |      *   post: | ||||||
|  |      *     summary: Toggle the repeat status, toggles between "off" , "single" and "all" | ||||||
|  |      *     tags: [player] | ||||||
|  |      *     responses: | ||||||
|  |      *       200: | ||||||
|  |      *         description: Ok | ||||||
|  |      *         content: | ||||||
|  |      *           text/plain: | ||||||
|  |      *             schema: | ||||||
|  |      *               $ref: '#/components/schemas/OkResponse' | ||||||
|  |      */ | ||||||
|  |     createPlayerAction("/repeat/toggle", globalEvents.toggleRepeat); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @swagger | ||||||
|  |      * /player/playpause: | ||||||
|  |      *   post: | ||||||
|  |      *     summary: Start playing the media if paused, or pause the media if playing | ||||||
|  |      *     tags: [player] | ||||||
|  |      *     responses: | ||||||
|  |      *       200: | ||||||
|  |      *         description: Ok | ||||||
|  |      *         content: | ||||||
|  |      *           text/plain: | ||||||
|  |      *             schema: | ||||||
|  |      *               $ref: '#/components/schemas/OkResponse' | ||||||
|  |      */ | ||||||
|  |     expressApp.post(createRoute("/playpause"), (req, res) => { | ||||||
|  |       if (mediaInfo.status === MediaStatus.playing) { | ||||||
|  |         windowEvent(res, globalEvents.pause); | ||||||
|  |       } else { | ||||||
|  |         windowEvent(res, globalEvents.play); | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | }; | ||||||
							
								
								
									
										121
									
								
								src/features/api/features/settings/settings.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,121 @@ | |||||||
|  | import { Request, Router } from "express"; | ||||||
|  | import { settings } from "../../../../constants/settings"; | ||||||
|  | import { mediaInfo } from "../../../../scripts/mediaInfo"; | ||||||
|  | import { | ||||||
|  |   addSkippedArtists, | ||||||
|  |   removeSkippedArtists, | ||||||
|  |   settingsStore, | ||||||
|  | } from "../../../../scripts/settings"; | ||||||
|  | import { BrowserWindow } from "electron"; | ||||||
|  | import { globalEvents } from "../../../../constants/globalEvents"; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @swagger | ||||||
|  |  * tags: | ||||||
|  |  *   name: settings | ||||||
|  |  *   description: The settings management API | ||||||
|  |  * components: | ||||||
|  |  *   schemas: | ||||||
|  |  *     StringArray: | ||||||
|  |  *       type: array | ||||||
|  |  *       items: | ||||||
|  |  *         type: string | ||||||
|  |  *       example: ["Artist1", "Artist2"] | ||||||
|  |  * | ||||||
|  |  * @param expressApp | ||||||
|  |  * @param mainWindow | ||||||
|  |  */ | ||||||
|  | export const addSettingsAPI = (expressApp: Router, mainWindow: BrowserWindow) => { | ||||||
|  |   /** | ||||||
|  |    * @swagger | ||||||
|  |    * /settings/skipped-artists: | ||||||
|  |    *   get: | ||||||
|  |    *     summary: get a list of artists that TIDAL Hi-Fi will skip if skipping is enabled | ||||||
|  |    *     tags: [settings] | ||||||
|  |    *     responses: | ||||||
|  |    *       200: | ||||||
|  |    *         description: The list book. | ||||||
|  |    *         content: | ||||||
|  |    *           application/json: | ||||||
|  |    *             schema: | ||||||
|  |    *              $ref: '#/components/schemas/StringArray' | ||||||
|  |    */ | ||||||
|  |   expressApp.get("/settings/skipped-artists", (req, res) => { | ||||||
|  |     res.json(settingsStore.get<string, string[]>(settings.skippedArtists)); | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * @swagger | ||||||
|  |    * /settings/skipped-artists: | ||||||
|  |    *   post: | ||||||
|  |    *     summary: Add new artists to the list of skipped artists | ||||||
|  |    *     tags: [settings] | ||||||
|  |    *     requestBody: | ||||||
|  |    *       required: true | ||||||
|  |    *       content: | ||||||
|  |    *         application/json: | ||||||
|  |    *           schema: | ||||||
|  |    *             $ref: '#/components/schemas/StringArray' | ||||||
|  |    *     responses: | ||||||
|  |    *       200: | ||||||
|  |    *         description: Ok | ||||||
|  |    */ | ||||||
|  |   expressApp.post("/settings/skipped-artists", (req: Request<object, object, string[]>, res) => { | ||||||
|  |     addSkippedArtists(req.body); | ||||||
|  |     res.sendStatus(200); | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * @swagger | ||||||
|  |    * /settings/skipped-artists/delete: | ||||||
|  |    *   post: | ||||||
|  |    *     summary: Remove artists from the list of skipped artists | ||||||
|  |    *     tags: [settings] | ||||||
|  |    *     requestBody: | ||||||
|  |    *       required: true | ||||||
|  |    *       content: | ||||||
|  |    *         application/json: | ||||||
|  |    *           schema: | ||||||
|  |    *             $ref: '#/components/schemas/StringArray' | ||||||
|  |    *     responses: | ||||||
|  |    *       200: | ||||||
|  |    *         description: Ok | ||||||
|  |    */ | ||||||
|  |   expressApp.post( | ||||||
|  |     "/settings/skipped-artists/delete", | ||||||
|  |     (req: Request<object, object, string[]>, res) => { | ||||||
|  |       removeSkippedArtists(req.body); | ||||||
|  |       res.sendStatus(200); | ||||||
|  |     } | ||||||
|  |   ); | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * @swagger | ||||||
|  |    * /settings/skipped-artists/current: | ||||||
|  |    *   post: | ||||||
|  |    *     summary: Add the current artist to the list of skipped artists | ||||||
|  |    *     tags: [settings] | ||||||
|  |    *     responses: | ||||||
|  |    *       200: | ||||||
|  |    *        description: Ok | ||||||
|  |    */ | ||||||
|  |   expressApp.post("/settings/skipped-artists/current", (req, res) => { | ||||||
|  |     addSkippedArtists([mediaInfo.artists]); | ||||||
|  |     mainWindow.webContents.send("globalEvent", globalEvents.next); | ||||||
|  |     res.sendStatus(200); | ||||||
|  |   }); | ||||||
|  |   /** | ||||||
|  |    * @swagger | ||||||
|  |    * /settings/skipped-artists/current: | ||||||
|  |    *   delete: | ||||||
|  |    *     summary: Remove the current artist from the list of skipped artists | ||||||
|  |    *     tags: [settings] | ||||||
|  |    *     responses: | ||||||
|  |    *       200: | ||||||
|  |    *        description: Ok | ||||||
|  |    */ | ||||||
|  |   expressApp.delete("/settings/skipped-artists/current", (req, res) => { | ||||||
|  |     removeSkippedArtists([mediaInfo.artists]); | ||||||
|  |     res.sendStatus(200); | ||||||
|  |   }); | ||||||
|  | }; | ||||||
							
								
								
									
										12
									
								
								src/features/api/helpers/handleWindowEvent.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,12 @@ | |||||||
|  | import { BrowserWindow } from "electron"; | ||||||
|  | import { Response } from "express"; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Shorthand to handle a fire and forget global event | ||||||
|  |  * @param {*} res | ||||||
|  |  * @param {*} action | ||||||
|  |  */ | ||||||
|  | export const handleWindowEvent = (mainWindow: BrowserWindow) => (res: Response, action: string) => { | ||||||
|  |   mainWindow.webContents.send("globalEvent", action); | ||||||
|  |   res.sendStatus(200); | ||||||
|  | }; | ||||||
							
								
								
									
										46
									
								
								src/features/api/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,46 @@ | |||||||
|  | import cors from "cors"; | ||||||
|  | import { BrowserWindow, dialog } from "electron"; | ||||||
|  | import express from "express"; | ||||||
|  | import swaggerUi from "swagger-ui-express"; | ||||||
|  | import { settingsStore } from "../../scripts/settings"; | ||||||
|  | import { settings } from "./../../constants/settings"; | ||||||
|  | import { addCurrentInfo } from "./features/current"; | ||||||
|  | import { addPlaybackControl } from "./features/player"; | ||||||
|  | import { addSettingsAPI } from "./features/settings/settings"; | ||||||
|  | import { addLegacyApi } from "./legacy"; | ||||||
|  | import swaggerSpec from "./swagger.json"; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Function to enable TIDAL Hi-Fi's express api | ||||||
|  |  */ | ||||||
|  | export const startApi = (mainWindow: BrowserWindow) => { | ||||||
|  |   const port = settingsStore.get<string, number>(settings.apiSettings.port); | ||||||
|  |   const hostname = settingsStore.get<string, string>(settings.apiSettings.hostname) ?? "127.0.0.1"; | ||||||
|  |  | ||||||
|  |   const expressApp = express(); | ||||||
|  |   expressApp.use(cors()); | ||||||
|  |   expressApp.use(express.json()); | ||||||
|  |   expressApp.use("/docs", swaggerUi.serve, swaggerUi.setup(swaggerSpec)); | ||||||
|  |   expressApp.get("/", (req, res) => { | ||||||
|  |     res.send("Hello World!"); | ||||||
|  |   }); | ||||||
|  |   expressApp.get("/swagger.json", (req, res) => { | ||||||
|  |     res.json(swaggerSpec); | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   // add features | ||||||
|  |   addLegacyApi(expressApp, mainWindow); | ||||||
|  |   addPlaybackControl(expressApp, mainWindow); | ||||||
|  |   addCurrentInfo(expressApp); | ||||||
|  |   addSettingsAPI(expressApp, mainWindow); | ||||||
|  |  | ||||||
|  |   const expressInstance = expressApp.listen(port, hostname); | ||||||
|  |   expressInstance.on("error", function (e: { code: string }) { | ||||||
|  |     let message = e.code; | ||||||
|  |     if (e.code === "EADDRINUSE") { | ||||||
|  |       message = `Port ${port} in use.`; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     dialog.showErrorBox("Api failed to start.", message); | ||||||
|  |   }); | ||||||
|  | }; | ||||||
							
								
								
									
										160
									
								
								src/features/api/legacy.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,160 @@ | |||||||
|  | import { BrowserWindow } from "electron"; | ||||||
|  | import { Response, Router } from "express"; | ||||||
|  | import { globalEvents } from "../../constants/globalEvents"; | ||||||
|  | import { settings } from "../../constants/settings"; | ||||||
|  | import { MediaStatus } from "../../models/mediaStatus"; | ||||||
|  | import { mediaInfo } from "../../scripts/mediaInfo"; | ||||||
|  | import { settingsStore } from "../../scripts/settings"; | ||||||
|  | import { getCurrentImage } from "./features/current"; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * The legacy API, this will not be maintained and probably has duplicate code :) | ||||||
|  |  * @param expressApp | ||||||
|  |  * @param mainWindow | ||||||
|  |  */ | ||||||
|  | export const addLegacyApi = (expressApp: Router, mainWindow: BrowserWindow) => { | ||||||
|  |   /** | ||||||
|  |    * @swagger | ||||||
|  |    * /image: | ||||||
|  |    *   get: | ||||||
|  |    *     summary: Get current image | ||||||
|  |    *     tags: [legacy] | ||||||
|  |    *     deprecated: true | ||||||
|  |    *     responses: | ||||||
|  |    *       200: | ||||||
|  |    *         description: Current image | ||||||
|  |    *         content: | ||||||
|  |    *           image/png: | ||||||
|  |    *             schema: | ||||||
|  |    *               type: string | ||||||
|  |    *               format: binary | ||||||
|  |    *       404: | ||||||
|  |    *         description: Not found | ||||||
|  |    */ | ||||||
|  |   expressApp.get("/image", getCurrentImage); | ||||||
|  |  | ||||||
|  |   if (settingsStore.get(settings.playBackControl)) { | ||||||
|  |     addLegacyControls(); | ||||||
|  |   } | ||||||
|  |   function addLegacyControls() { | ||||||
|  |     /** | ||||||
|  |      * @swagger | ||||||
|  |      * /play: | ||||||
|  |      *   get: | ||||||
|  |      *     summary: Play the current media | ||||||
|  |      *     tags: [legacy] | ||||||
|  |      *     deprecated: true | ||||||
|  |      *     responses: | ||||||
|  |      *       200: | ||||||
|  |      *         description: Action performed | ||||||
|  |      *         content: | ||||||
|  |      *           text/plain: | ||||||
|  |      *             schema: | ||||||
|  |      *               $ref: '#/components/schemas/OkResponse' | ||||||
|  |      */ | ||||||
|  |     expressApp.get("/play", ({ res }) => handleGlobalEvent(res, globalEvents.play)); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @swagger | ||||||
|  |      * /favorite/toggle: | ||||||
|  |      *   get: | ||||||
|  |      *     summary: Add the current media to your favorites, or remove it if its already added to your favorites | ||||||
|  |      *     tags: [legacy] | ||||||
|  |      *     deprecated: true | ||||||
|  |      *     responses: | ||||||
|  |      *       200: | ||||||
|  |      *         description: Ok | ||||||
|  |      *         content: | ||||||
|  |      *           text/plain: | ||||||
|  |      *             schema: | ||||||
|  |      *               $ref: '#/components/schemas/OkResponse' | ||||||
|  |      */ | ||||||
|  |     expressApp.post("/favorite/toggle", (req, res) => | ||||||
|  |       handleGlobalEvent(res, globalEvents.toggleFavorite) | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @swagger | ||||||
|  |      * /pause: | ||||||
|  |      *   get: | ||||||
|  |      *     summary: Pause the current media | ||||||
|  |      *     tags: [legacy] | ||||||
|  |      *     deprecated: true | ||||||
|  |      *     responses: | ||||||
|  |      *       200: | ||||||
|  |      *         description: Ok | ||||||
|  |      *         content: | ||||||
|  |      *           text/plain: | ||||||
|  |      *             schema: | ||||||
|  |      *               $ref: '#/components/schemas/OkResponse' | ||||||
|  |      */ | ||||||
|  |     expressApp.get("/pause", (req, res) => handleGlobalEvent(res, globalEvents.pause)); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @swagger | ||||||
|  |      * /next: | ||||||
|  |      *   get: | ||||||
|  |      *     summary: Play the next song | ||||||
|  |      *     tags: [legacy] | ||||||
|  |      *     deprecated: true | ||||||
|  |      *     responses: | ||||||
|  |      *       200: | ||||||
|  |      *         description: Ok | ||||||
|  |      *         content: | ||||||
|  |      *           text/plain: | ||||||
|  |      *             schema: | ||||||
|  |      *               $ref: '#/components/schemas/OkResponse' | ||||||
|  |      */ | ||||||
|  |     expressApp.get("/next", (req, res) => handleGlobalEvent(res, globalEvents.next)); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @swagger | ||||||
|  |      * /previous: | ||||||
|  |      *   get: | ||||||
|  |      *     summary: Play the previous song | ||||||
|  |      *     tags: [legacy] | ||||||
|  |      *     deprecated: true | ||||||
|  |      *     responses: | ||||||
|  |      *       200: | ||||||
|  |      *         description: Ok | ||||||
|  |      *         content: | ||||||
|  |      *           text/plain: | ||||||
|  |      *             schema: | ||||||
|  |      *               $ref: '#/components/schemas/OkResponse' | ||||||
|  |      */ | ||||||
|  |     expressApp.get("/previous", (req, res) => handleGlobalEvent(res, globalEvents.previous)); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @swagger | ||||||
|  |      * /playpause: | ||||||
|  |      *   get: | ||||||
|  |      *     summary: Toggle play/pause | ||||||
|  |      *     tags: [legacy] | ||||||
|  |      *     deprecated: true | ||||||
|  |      *     responses: | ||||||
|  |      *       200: | ||||||
|  |      *         description: Ok | ||||||
|  |      *         content: | ||||||
|  |      *           text/plain: | ||||||
|  |      *             schema: | ||||||
|  |      *               $ref: '#/components/schemas/OkResponse' | ||||||
|  |      */ | ||||||
|  |     expressApp.get("/playpause", (req, res) => { | ||||||
|  |       if (mediaInfo.status === MediaStatus.playing) { | ||||||
|  |         handleGlobalEvent(res, globalEvents.pause); | ||||||
|  |       } else { | ||||||
|  |         handleGlobalEvent(res, globalEvents.play); | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * Shorthand to handle a fire and forget global event | ||||||
|  |    * @param {*} res | ||||||
|  |    * @param {*} action | ||||||
|  |    */ | ||||||
|  |   function handleGlobalEvent(res: Response, action: string) { | ||||||
|  |     mainWindow.webContents.send("globalEvent", action); | ||||||
|  |     res.sendStatus(200); | ||||||
|  |   } | ||||||
|  | }; | ||||||
							
								
								
									
										535
									
								
								src/features/api/swagger.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,535 @@ | |||||||
|  | { | ||||||
|  |   "openapi": "3.1.0", | ||||||
|  |   "info": { | ||||||
|  |     "title": "TIDAL Hi-Fi API", | ||||||
|  |     "version": "5.20.1", | ||||||
|  |     "description": "", | ||||||
|  |     "license": { | ||||||
|  |       "name": "MIT", | ||||||
|  |       "url": "https://github.com/Mastermindzh/tidal-hifi/blob/master/LICENSE" | ||||||
|  |     }, | ||||||
|  |     "contact": { | ||||||
|  |       "name": "Rick <mastermindzh> van Lieshout", | ||||||
|  |       "url": "https://www.rickvanlieshout.com" | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   "externalDocs": { | ||||||
|  |     "description": "swagger.json", | ||||||
|  |     "url": "swagger.json" | ||||||
|  |   }, | ||||||
|  |   "paths": { | ||||||
|  |     "/current": { | ||||||
|  |       "get": { | ||||||
|  |         "summary": "Get current media info", | ||||||
|  |         "tags": ["current"], | ||||||
|  |         "responses": { | ||||||
|  |           "200": { | ||||||
|  |             "description": "Current media info", | ||||||
|  |             "content": { | ||||||
|  |               "application/json": { | ||||||
|  |                 "schema": { | ||||||
|  |                   "$ref": "#/components/schemas/MediaInfo" | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "/current/image": { | ||||||
|  |       "get": { | ||||||
|  |         "summary": "Get current media image", | ||||||
|  |         "tags": ["current"], | ||||||
|  |         "responses": { | ||||||
|  |           "200": { | ||||||
|  |             "description": "Current media image", | ||||||
|  |             "content": { | ||||||
|  |               "image/png": { | ||||||
|  |                 "schema": { | ||||||
|  |                   "type": "string", | ||||||
|  |                   "format": "binary" | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           }, | ||||||
|  |           "404": { | ||||||
|  |             "description": "Not found" | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "/player/play": { | ||||||
|  |       "post": { | ||||||
|  |         "summary": "Play the current media", | ||||||
|  |         "tags": ["player"], | ||||||
|  |         "responses": { | ||||||
|  |           "200": { | ||||||
|  |             "description": "Ok", | ||||||
|  |             "content": { | ||||||
|  |               "text/plain": { | ||||||
|  |                 "schema": { | ||||||
|  |                   "$ref": "#/components/schemas/OkResponse" | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "/player/favorite/toggle": { | ||||||
|  |       "post": { | ||||||
|  |         "summary": "Add the current media to your favorites, or remove it if its already added to your favorites", | ||||||
|  |         "tags": ["player"], | ||||||
|  |         "responses": { | ||||||
|  |           "200": { | ||||||
|  |             "description": "Ok", | ||||||
|  |             "content": { | ||||||
|  |               "text/plain": { | ||||||
|  |                 "schema": { | ||||||
|  |                   "$ref": "#/components/schemas/OkResponse" | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "/player/pause": { | ||||||
|  |       "post": { | ||||||
|  |         "summary": "Pause the current media", | ||||||
|  |         "tags": ["player"], | ||||||
|  |         "responses": { | ||||||
|  |           "200": { | ||||||
|  |             "description": "Ok", | ||||||
|  |             "content": { | ||||||
|  |               "text/plain": { | ||||||
|  |                 "schema": { | ||||||
|  |                   "$ref": "#/components/schemas/OkResponse" | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "/player/next": { | ||||||
|  |       "post": { | ||||||
|  |         "summary": "Play the next song", | ||||||
|  |         "tags": ["player"], | ||||||
|  |         "responses": { | ||||||
|  |           "200": { | ||||||
|  |             "description": "Ok", | ||||||
|  |             "content": { | ||||||
|  |               "text/plain": { | ||||||
|  |                 "schema": { | ||||||
|  |                   "$ref": "#/components/schemas/OkResponse" | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "/player/previous": { | ||||||
|  |       "post": { | ||||||
|  |         "summary": "Play the previous song", | ||||||
|  |         "tags": ["player"], | ||||||
|  |         "responses": { | ||||||
|  |           "200": { | ||||||
|  |             "description": "Ok", | ||||||
|  |             "content": { | ||||||
|  |               "text/plain": { | ||||||
|  |                 "schema": { | ||||||
|  |                   "$ref": "#/components/schemas/OkResponse" | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "/player/shuffle/toggle": { | ||||||
|  |       "post": { | ||||||
|  |         "summary": "Play the previous song", | ||||||
|  |         "tags": ["player"], | ||||||
|  |         "responses": { | ||||||
|  |           "200": { | ||||||
|  |             "description": "Ok", | ||||||
|  |             "content": { | ||||||
|  |               "text/plain": { | ||||||
|  |                 "schema": { | ||||||
|  |                   "$ref": "#/components/schemas/OkResponse" | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "/player/repeat/toggle": { | ||||||
|  |       "post": { | ||||||
|  |         "summary": "Toggle the repeat status, toggles between \"off\" , \"single\" and \"all\"", | ||||||
|  |         "tags": ["player"], | ||||||
|  |         "responses": { | ||||||
|  |           "200": { | ||||||
|  |             "description": "Ok", | ||||||
|  |             "content": { | ||||||
|  |               "text/plain": { | ||||||
|  |                 "schema": { | ||||||
|  |                   "$ref": "#/components/schemas/OkResponse" | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "/player/playpause": { | ||||||
|  |       "post": { | ||||||
|  |         "summary": "Start playing the media if paused, or pause the media if playing", | ||||||
|  |         "tags": ["player"], | ||||||
|  |         "responses": { | ||||||
|  |           "200": { | ||||||
|  |             "description": "Ok", | ||||||
|  |             "content": { | ||||||
|  |               "text/plain": { | ||||||
|  |                 "schema": { | ||||||
|  |                   "$ref": "#/components/schemas/OkResponse" | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "/settings/skipped-artists": { | ||||||
|  |       "get": { | ||||||
|  |         "summary": "get a list of artists that TIDAL Hi-Fi will skip if skipping is enabled", | ||||||
|  |         "tags": ["settings"], | ||||||
|  |         "responses": { | ||||||
|  |           "200": { | ||||||
|  |             "description": "The list book.", | ||||||
|  |             "content": { | ||||||
|  |               "application/json": { | ||||||
|  |                 "schema": { | ||||||
|  |                   "$ref": "#/components/schemas/StringArray" | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       }, | ||||||
|  |       "post": { | ||||||
|  |         "summary": "Add new artists to the list of skipped artists", | ||||||
|  |         "tags": ["settings"], | ||||||
|  |         "requestBody": { | ||||||
|  |           "required": true, | ||||||
|  |           "content": { | ||||||
|  |             "application/json": { | ||||||
|  |               "schema": { | ||||||
|  |                 "$ref": "#/components/schemas/StringArray" | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |         "responses": { | ||||||
|  |           "200": { | ||||||
|  |             "description": "Ok" | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "/settings/skipped-artists/delete": { | ||||||
|  |       "post": { | ||||||
|  |         "summary": "Remove artists from the list of skipped artists", | ||||||
|  |         "tags": ["settings"], | ||||||
|  |         "requestBody": { | ||||||
|  |           "required": true, | ||||||
|  |           "content": { | ||||||
|  |             "application/json": { | ||||||
|  |               "schema": { | ||||||
|  |                 "$ref": "#/components/schemas/StringArray" | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |         "responses": { | ||||||
|  |           "200": { | ||||||
|  |             "description": "Ok" | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "/settings/skipped-artists/current": { | ||||||
|  |       "post": { | ||||||
|  |         "summary": "Add the current artist to the list of skipped artists", | ||||||
|  |         "tags": ["settings"], | ||||||
|  |         "responses": { | ||||||
|  |           "200": { | ||||||
|  |             "description": "Ok" | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       }, | ||||||
|  |       "delete": { | ||||||
|  |         "summary": "Remove the current artist from the list of skipped artists", | ||||||
|  |         "tags": ["settings"], | ||||||
|  |         "responses": { | ||||||
|  |           "200": { | ||||||
|  |             "description": "Ok" | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "/image": { | ||||||
|  |       "get": { | ||||||
|  |         "summary": "Get current image", | ||||||
|  |         "tags": ["legacy"], | ||||||
|  |         "deprecated": true, | ||||||
|  |         "responses": { | ||||||
|  |           "200": { | ||||||
|  |             "description": "Current image", | ||||||
|  |             "content": { | ||||||
|  |               "image/png": { | ||||||
|  |                 "schema": { | ||||||
|  |                   "type": "string", | ||||||
|  |                   "format": "binary" | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           }, | ||||||
|  |           "404": { | ||||||
|  |             "description": "Not found" | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "/play": { | ||||||
|  |       "get": { | ||||||
|  |         "summary": "Play the current media", | ||||||
|  |         "tags": ["legacy"], | ||||||
|  |         "deprecated": true, | ||||||
|  |         "responses": { | ||||||
|  |           "200": { | ||||||
|  |             "description": "Action performed", | ||||||
|  |             "content": { | ||||||
|  |               "text/plain": { | ||||||
|  |                 "schema": { | ||||||
|  |                   "$ref": "#/components/schemas/OkResponse" | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "/favorite/toggle": { | ||||||
|  |       "get": { | ||||||
|  |         "summary": "Add the current media to your favorites, or remove it if its already added to your favorites", | ||||||
|  |         "tags": ["legacy"], | ||||||
|  |         "deprecated": true, | ||||||
|  |         "responses": { | ||||||
|  |           "200": { | ||||||
|  |             "description": "Ok", | ||||||
|  |             "content": { | ||||||
|  |               "text/plain": { | ||||||
|  |                 "schema": { | ||||||
|  |                   "$ref": "#/components/schemas/OkResponse" | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "/pause": { | ||||||
|  |       "get": { | ||||||
|  |         "summary": "Pause the current media", | ||||||
|  |         "tags": ["legacy"], | ||||||
|  |         "deprecated": true, | ||||||
|  |         "responses": { | ||||||
|  |           "200": { | ||||||
|  |             "description": "Ok", | ||||||
|  |             "content": { | ||||||
|  |               "text/plain": { | ||||||
|  |                 "schema": { | ||||||
|  |                   "$ref": "#/components/schemas/OkResponse" | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "/next": { | ||||||
|  |       "get": { | ||||||
|  |         "summary": "Play the next song", | ||||||
|  |         "tags": ["legacy"], | ||||||
|  |         "deprecated": true, | ||||||
|  |         "responses": { | ||||||
|  |           "200": { | ||||||
|  |             "description": "Ok", | ||||||
|  |             "content": { | ||||||
|  |               "text/plain": { | ||||||
|  |                 "schema": { | ||||||
|  |                   "$ref": "#/components/schemas/OkResponse" | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "/previous": { | ||||||
|  |       "get": { | ||||||
|  |         "summary": "Play the previous song", | ||||||
|  |         "tags": ["legacy"], | ||||||
|  |         "deprecated": true, | ||||||
|  |         "responses": { | ||||||
|  |           "200": { | ||||||
|  |             "description": "Ok", | ||||||
|  |             "content": { | ||||||
|  |               "text/plain": { | ||||||
|  |                 "schema": { | ||||||
|  |                   "$ref": "#/components/schemas/OkResponse" | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "/playpause": { | ||||||
|  |       "get": { | ||||||
|  |         "summary": "Toggle play/pause", | ||||||
|  |         "tags": ["legacy"], | ||||||
|  |         "deprecated": true, | ||||||
|  |         "responses": { | ||||||
|  |           "200": { | ||||||
|  |             "description": "Ok", | ||||||
|  |             "content": { | ||||||
|  |               "text/plain": { | ||||||
|  |                 "schema": { | ||||||
|  |                   "$ref": "#/components/schemas/OkResponse" | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   "components": { | ||||||
|  |     "schemas": { | ||||||
|  |       "MediaInfo": { | ||||||
|  |         "type": "object", | ||||||
|  |         "properties": { | ||||||
|  |           "title": { | ||||||
|  |             "type": "string" | ||||||
|  |           }, | ||||||
|  |           "artists": { | ||||||
|  |             "type": "string" | ||||||
|  |           }, | ||||||
|  |           "album": { | ||||||
|  |             "type": "string" | ||||||
|  |           }, | ||||||
|  |           "icon": { | ||||||
|  |             "type": "string", | ||||||
|  |             "format": "uri" | ||||||
|  |           }, | ||||||
|  |           "playingFrom": { | ||||||
|  |             "type": "string" | ||||||
|  |           }, | ||||||
|  |           "status": { | ||||||
|  |             "type": "string" | ||||||
|  |           }, | ||||||
|  |           "url": { | ||||||
|  |             "type": "string", | ||||||
|  |             "format": "uri" | ||||||
|  |           }, | ||||||
|  |           "current": { | ||||||
|  |             "type": "string" | ||||||
|  |           }, | ||||||
|  |           "currentInSeconds": { | ||||||
|  |             "type": "integer" | ||||||
|  |           }, | ||||||
|  |           "duration": { | ||||||
|  |             "type": "string" | ||||||
|  |           }, | ||||||
|  |           "durationInSeconds": { | ||||||
|  |             "type": "integer" | ||||||
|  |           }, | ||||||
|  |           "image": { | ||||||
|  |             "type": "string", | ||||||
|  |             "format": "uri" | ||||||
|  |           }, | ||||||
|  |           "favorite": { | ||||||
|  |             "type": "boolean" | ||||||
|  |           }, | ||||||
|  |           "player": { | ||||||
|  |             "type": "object", | ||||||
|  |             "properties": { | ||||||
|  |               "status": { | ||||||
|  |                 "type": "string" | ||||||
|  |               }, | ||||||
|  |               "shuffle": { | ||||||
|  |                 "type": "boolean" | ||||||
|  |               }, | ||||||
|  |               "repeat": { | ||||||
|  |                 "type": "string" | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           }, | ||||||
|  |           "artist": { | ||||||
|  |             "type": "string" | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |         "example": { | ||||||
|  |           "title": "Sample Title", | ||||||
|  |           "artists": "Sample Artist", | ||||||
|  |           "album": "Sample Album", | ||||||
|  |           "icon": "/path/to/sample/icon.jpg", | ||||||
|  |           "playingFrom": "Sample Playlist", | ||||||
|  |           "status": "playing", | ||||||
|  |           "url": "https://tidal.com/browse/track/sample", | ||||||
|  |           "current": "1:23", | ||||||
|  |           "currentInSeconds": 83, | ||||||
|  |           "duration": "3:45", | ||||||
|  |           "durationInSeconds": 225, | ||||||
|  |           "image": "https://example.com/sample-image.jpg", | ||||||
|  |           "favorite": true, | ||||||
|  |           "player": { | ||||||
|  |             "status": "playing", | ||||||
|  |             "shuffle": true, | ||||||
|  |             "repeat": "one" | ||||||
|  |           }, | ||||||
|  |           "artist": "Sample Artist" | ||||||
|  |         } | ||||||
|  |       }, | ||||||
|  |       "OkResponse": { | ||||||
|  |         "type": "string", | ||||||
|  |         "example": "OK" | ||||||
|  |       }, | ||||||
|  |       "StringArray": { | ||||||
|  |         "type": "array", | ||||||
|  |         "items": { | ||||||
|  |           "type": "string" | ||||||
|  |         }, | ||||||
|  |         "example": ["Artist1", "Artist2"] | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   "tags": [ | ||||||
|  |     { | ||||||
|  |       "name": "current", | ||||||
|  |       "description": "The current media info API" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "name": "player", | ||||||
|  |       "description": "The player control API" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "name": "settings", | ||||||
|  |       "description": "The settings management API" | ||||||
|  |     } | ||||||
|  |   ] | ||||||
|  | } | ||||||
							
								
								
									
										41
									
								
								src/features/flags/flags.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,41 @@ | |||||||
|  | import { App } from "electron"; | ||||||
|  | import { flags } from "../../constants/flags"; | ||||||
|  | import { settings } from "../../constants/settings"; | ||||||
|  | import { settingsStore } from "../../scripts/settings"; | ||||||
|  | import { Logger } from "../logger"; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Set default Electron flags | ||||||
|  |  */ | ||||||
|  | export function setDefaultFlags(app: App) { | ||||||
|  |   setFlag(app, "disable-seccomp-filter-sandbox"); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Set Tidal's managed flags from the user settings | ||||||
|  |  * @param app | ||||||
|  |  */ | ||||||
|  | export function setManagedFlagsFromSettings(app: App) { | ||||||
|  |   const flagsFromSettings = settingsStore.get(settings.flags.root); | ||||||
|  |   if (flagsFromSettings) { | ||||||
|  |     for (const [key, value] of Object.entries(flagsFromSettings)) { | ||||||
|  |       if (value) { | ||||||
|  |         flags[key].forEach((flag) => { | ||||||
|  |           setFlag(app, flag.flag, flag.value); | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Set a single flag for Electron | ||||||
|  |  * @param app app to set it on | ||||||
|  |  * @param flag flag name | ||||||
|  |  * @param value value to be set for the flag | ||||||
|  |  */ | ||||||
|  | // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||||||
|  | function setFlag(app: App, flag: string, value?: any) { | ||||||
|  |   Logger.log(`enabling command line option ${flag} with value ${value}`); | ||||||
|  |   app.commandLine.appendSwitch(flag, value); | ||||||
|  | } | ||||||
							
								
								
									
										65
									
								
								src/features/idleInhibitor/idleInhibitor.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,65 @@ | |||||||
|  | import { PowerSaveBlocker, powerSaveBlocker } from "electron"; | ||||||
|  | import { Logger } from "../logger"; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Start blocking idle/screen timeouts | ||||||
|  |  * @param blocker optional instance of the powerSaveBlocker to use | ||||||
|  |  * @returns id of current block | ||||||
|  |  */ | ||||||
|  | export const acquireInhibitor = (blocker?: PowerSaveBlocker): number => { | ||||||
|  |   const currentBlocker = blocker ?? powerSaveBlocker; | ||||||
|  |   const blockId = currentBlocker.start("prevent-app-suspension"); | ||||||
|  |   Logger.log(`Started preventing app suspension with id: ${blockId}`); | ||||||
|  |   return blockId; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Check whether there is a blocker active for the current id, if not start it. | ||||||
|  |  * @param id id of inhibitor you want to check activity against | ||||||
|  |  * @param blocker optional instance of the powerSaveBlocker to use | ||||||
|  |  */ | ||||||
|  | export const acquireInhibitorIfInactive = (id: number, blocker?: PowerSaveBlocker): number => { | ||||||
|  |   const currentBlocker = blocker ?? powerSaveBlocker; | ||||||
|  |   if (!isInhibitorActive(id, currentBlocker)) { | ||||||
|  |     return acquireInhibitor(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return id; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * stop blocking idle/screen timeouts | ||||||
|  |  * @param id id of inhibitor you want to check activity against | ||||||
|  |  * @param blocker optional instance of the powerSaveBlocker to use | ||||||
|  |  */ | ||||||
|  | export const releaseInhibitor = (id: number, blocker?: PowerSaveBlocker) => { | ||||||
|  |   try { | ||||||
|  |     const currentBlocker = blocker ?? powerSaveBlocker; | ||||||
|  |     currentBlocker.stop(id); | ||||||
|  |     Logger.log(`Released inhibitor with id: ${id}`); | ||||||
|  |   } catch (error) { | ||||||
|  |     Logger.log("Releasing inhibitor failed"); | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * stop blocking idle/screen timeouts if a inhibitor is active | ||||||
|  |  * @param id id of inhibitor you want to check activity against | ||||||
|  |  * @param blocker optional instance of the powerSaveBlocker to use | ||||||
|  |  */ | ||||||
|  | export const releaseInhibitorIfActive = (id: number, blocker?: PowerSaveBlocker) => { | ||||||
|  |   const currentBlocker = blocker ?? powerSaveBlocker; | ||||||
|  |   if (isInhibitorActive(id, currentBlocker)) { | ||||||
|  |     releaseInhibitor(id, currentBlocker); | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * check whether the inhibitor is active | ||||||
|  |  * @param id id of inhibitor you want to check activity against | ||||||
|  |  * @param blocker optional instance of the powerSaveBlocker to use | ||||||
|  |  */ | ||||||
|  | export const isInhibitorActive = (id: number, blocker?: PowerSaveBlocker) => { | ||||||
|  |   const currentBlocker = blocker ?? powerSaveBlocker; | ||||||
|  |   return currentBlocker.isStarted(id); | ||||||
|  | }; | ||||||
							
								
								
									
										135
									
								
								src/features/listenbrainz/listenbrainz.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,135 @@ | |||||||
|  | import axios from "axios"; | ||||||
|  | import Store from "electron-store"; | ||||||
|  | import { settings } from "../../constants/settings"; | ||||||
|  | import { MediaStatus } from "../../models/mediaStatus"; | ||||||
|  | import { settingsStore } from "../../scripts/settings"; | ||||||
|  | import { Logger } from "../logger"; | ||||||
|  | import { StoreData } from "./models/storeData"; | ||||||
|  |  | ||||||
|  | const ListenBrainzStore = new Store({ name: "listenbrainz" }); | ||||||
|  |  | ||||||
|  | export const ListenBrainzConstants = { | ||||||
|  |   oldData: "oldData", | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export class ListenBrainz { | ||||||
|  |   /** | ||||||
|  |    * Create the object to store old information in the Store :) | ||||||
|  |    * @param title | ||||||
|  |    * @param artists | ||||||
|  |    * @param duration | ||||||
|  |    * @returns data passed along in an object + a "listenedAt" key with the current time | ||||||
|  |    */ | ||||||
|  |   private static constructStoreData(title: string, artists: string, duration: number): StoreData { | ||||||
|  |     return { | ||||||
|  |       listenedAt: Math.floor(new Date().getTime() / 1000), | ||||||
|  |       title, | ||||||
|  |       artists, | ||||||
|  |       duration, | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * Call the ListenBrainz API and create playing now payload and scrobble old song | ||||||
|  |    * @param title | ||||||
|  |    * @param artists | ||||||
|  |    * @param status | ||||||
|  |    * @param duration | ||||||
|  |    */ | ||||||
|  |   public static async scrobble( | ||||||
|  |     title: string, | ||||||
|  |     artists: string, | ||||||
|  |     status: string, | ||||||
|  |     duration: number | ||||||
|  |   ): Promise<void> { | ||||||
|  |     try { | ||||||
|  |       if (status === MediaStatus.paused) { | ||||||
|  |         return; | ||||||
|  |       } else { | ||||||
|  |         // Fetches the oldData required for scrobbling and proceeds to construct a playing_now data payload for the Playing Now area | ||||||
|  |         const oldData = ListenBrainzStore.get(ListenBrainzConstants.oldData) as StoreData; | ||||||
|  |         const tidalUrl = | ||||||
|  |           settingsStore.get<string, string>(settings.advanced.tidalUrl) || | ||||||
|  |           "https://listen.tidal.com"; | ||||||
|  |         const playing_data = { | ||||||
|  |           listen_type: "playing_now", | ||||||
|  |           payload: [ | ||||||
|  |             { | ||||||
|  |               track_metadata: { | ||||||
|  |                 additional_info: { | ||||||
|  |                   media_player: "Tidal Hi-Fi", | ||||||
|  |                   submission_client: "Tidal Hi-Fi", | ||||||
|  |                   music_service: "tidal.com", | ||||||
|  |                   duration: duration, | ||||||
|  |                 }, | ||||||
|  |                 artist_name: artists, | ||||||
|  |                 track_name: title, | ||||||
|  |               }, | ||||||
|  |             }, | ||||||
|  |           ], | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         await axios.post( | ||||||
|  |           `${settingsStore.get<string, string>(settings.ListenBrainz.api)}/1/submit-listens`, | ||||||
|  |           playing_data, | ||||||
|  |           { | ||||||
|  |             headers: { | ||||||
|  |               "Content-Type": "application/json", | ||||||
|  |               Authorization: `Token ${settingsStore.get<string, string>( | ||||||
|  |                 settings.ListenBrainz.token | ||||||
|  |               )}`, | ||||||
|  |             }, | ||||||
|  |           } | ||||||
|  |         ); | ||||||
|  |         if (!oldData) { | ||||||
|  |           ListenBrainzStore.set( | ||||||
|  |             ListenBrainzConstants.oldData, | ||||||
|  |             this.constructStoreData(title, artists, duration) | ||||||
|  |           ); | ||||||
|  |         } else { | ||||||
|  |           if (oldData.title !== title) { | ||||||
|  |             // This constructs the data required to scrobble the data after the song finishes | ||||||
|  |             const scrobble_data = { | ||||||
|  |               listen_type: "single", | ||||||
|  |               payload: [ | ||||||
|  |                 { | ||||||
|  |                   listened_at: oldData.listenedAt, | ||||||
|  |                   track_metadata: { | ||||||
|  |                     additional_info: { | ||||||
|  |                       media_player: "Tidal Hi-Fi", | ||||||
|  |                       submission_client: "Tidal Hi-Fi", | ||||||
|  |                       music_service: tidalUrl, | ||||||
|  |                       duration: oldData.duration, | ||||||
|  |                     }, | ||||||
|  |                     artist_name: oldData.artists, | ||||||
|  |                     track_name: oldData.title, | ||||||
|  |                   }, | ||||||
|  |                 }, | ||||||
|  |               ], | ||||||
|  |             }; | ||||||
|  |             await axios.post( | ||||||
|  |               `${settingsStore.get<string, string>(settings.ListenBrainz.api)}/1/submit-listens`, | ||||||
|  |               scrobble_data, | ||||||
|  |               { | ||||||
|  |                 headers: { | ||||||
|  |                   "Content-Type": "application/json", | ||||||
|  |                   Authorization: `Token ${settingsStore.get<string, string>( | ||||||
|  |                     settings.ListenBrainz.token | ||||||
|  |                   )}`, | ||||||
|  |                 }, | ||||||
|  |               } | ||||||
|  |             ); | ||||||
|  |             ListenBrainzStore.set( | ||||||
|  |               ListenBrainzConstants.oldData, | ||||||
|  |               this.constructStoreData(title, artists, duration) | ||||||
|  |             ); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } catch (error) { | ||||||
|  |       Logger.log(JSON.stringify(error)); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export { ListenBrainzStore }; | ||||||
							
								
								
									
										9
									
								
								src/features/listenbrainz/models/storeData.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,9 @@ | |||||||
|  | /** | ||||||
|  |  * Data saved for ListenBrainz | ||||||
|  |  */ | ||||||
|  | export interface StoreData { | ||||||
|  |   listenedAt: number; | ||||||
|  |   title: string; | ||||||
|  |   artists: string; | ||||||
|  |   duration: number; | ||||||
|  | } | ||||||
							
								
								
									
										52
									
								
								src/features/logger.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,52 @@ | |||||||
|  | import { IpcMain, ipcMain, IpcMainEvent, ipcRenderer } from "electron"; | ||||||
|  | import { globalEvents } from "../constants/globalEvents"; | ||||||
|  |  | ||||||
|  | export class Logger { | ||||||
|  |   /** | ||||||
|  |    * Subscribe to watch for logs from the IPC client | ||||||
|  |    * @param ipcMain main thread IPC client so we can subscribe to events | ||||||
|  |    */ | ||||||
|  |   public static watch(ipcMain: IpcMain) { | ||||||
|  |     ipcMain.on( | ||||||
|  |       globalEvents.log, | ||||||
|  |       (event: IpcMainEvent | { content: string; message: string }, message) => { | ||||||
|  |         const { content, object } = message ?? event; | ||||||
|  |         this.logToSTDOut(content, object); | ||||||
|  |       } | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |   /** | ||||||
|  |    * Log content to STDOut | ||||||
|  |    * @param content | ||||||
|  |    * @param object js(on) object that will be prettyPrinted | ||||||
|  |    */ | ||||||
|  |   public static log(content: string, object: object = {}) { | ||||||
|  |     if (ipcRenderer) { | ||||||
|  |       ipcRenderer.send(globalEvents.log, { content, object }); | ||||||
|  |     } else { | ||||||
|  |       ipcMain.emit(globalEvents.log, { content, object }); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * Log content to STDOut and use the provided alert function to alert | ||||||
|  |    * @param content | ||||||
|  |    * @param object js(on) object that will be prettyPrinted | ||||||
|  |    */ | ||||||
|  |   // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||||||
|  |   public static alert(content: string, object: any = {}, alert?: (msg: string) => void) { | ||||||
|  |     Logger.log(content, object); | ||||||
|  |     if (alert) { | ||||||
|  |       alert(`${content} \n\nwith details: \n${JSON.stringify(object, null, 2)}`); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * Log to STDOut | ||||||
|  |    * @param content | ||||||
|  |    * @param object | ||||||
|  |    */ | ||||||
|  |   private static logToSTDOut(content: string, object = {}) { | ||||||
|  |     console.log(content, Object.keys(object).length > 0 ? JSON.stringify(object, null, 2) : ""); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										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`; | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										31
									
								
								src/features/theming/theming.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,31 @@ | |||||||
|  | import fs from "fs"; | ||||||
|  | import { settings } from "../../constants/settings"; | ||||||
|  | import { settingsStore } from "../../scripts/settings"; | ||||||
|  | import { Logger } from "../logger"; | ||||||
|  |  | ||||||
|  | // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||||||
|  | export function addCustomCss(app: any) { | ||||||
|  |   window.addEventListener("DOMContentLoaded", () => { | ||||||
|  |     const selectedTheme = settingsStore.get<string, string>(settings.theme); | ||||||
|  |     if (selectedTheme !== "none") { | ||||||
|  |       const userThemePath = `${app.getPath("userData")}/themes/${selectedTheme}`; | ||||||
|  |       const resourcesThemePath = `${process.resourcesPath}/${selectedTheme}`; | ||||||
|  |       const themeFile = fs.existsSync(userThemePath) ? userThemePath : resourcesThemePath; | ||||||
|  |       fs.readFile(themeFile, "utf-8", (err, data) => { | ||||||
|  |         if (err) { | ||||||
|  |           Logger.alert("An error ocurred reading the theme file.", err, alert); | ||||||
|  |           return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         const themeStyle = document.createElement("style"); | ||||||
|  |         themeStyle.innerHTML = data; | ||||||
|  |         document.head.appendChild(themeStyle); | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // read customCSS (it will override the theme) | ||||||
|  |     const style = document.createElement("style"); | ||||||
|  |     style.innerHTML = settingsStore.get<string, string[]>(settings.customCSS).join("\n"); | ||||||
|  |     document.head.appendChild(style); | ||||||
|  |   }); | ||||||
|  | } | ||||||
							
								
								
									
										14
									
								
								src/features/time/parse.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,14 @@ | |||||||
|  | /** | ||||||
|  |  * Convert a HH:MM:SS string (or variants such as MM:SS or SS) to plain seconds | ||||||
|  |  * @param duration in HH:MM:SS format | ||||||
|  |  * @returns number of seconds in duration | ||||||
|  |  */ | ||||||
|  | export const convertDurationToSeconds = (duration: string) => { | ||||||
|  |   return duration | ||||||
|  |     .split(":") | ||||||
|  |     .reverse() | ||||||
|  |     .map((val) => Number(val)) | ||||||
|  |     .reduce((previous, current, index) => { | ||||||
|  |       return index === 0 ? current : previous + current * Math.pow(60, index); | ||||||
|  |     }, 0); | ||||||
|  | }; | ||||||
							
								
								
									
										121
									
								
								src/main.js
									
									
									
									
									
								
							
							
						
						| @@ -1,121 +0,0 @@ | |||||||
| const { app, BrowserWindow, globalShortcut, ipcMain } = require("electron"); |  | ||||||
| const { |  | ||||||
|   settings, |  | ||||||
|   store, |  | ||||||
|   createSettingsWindow, |  | ||||||
|   showSettingsWindow, |  | ||||||
|   closeSettingsWindow, |  | ||||||
|   hideSettingsWindow, |  | ||||||
| } = require("./scripts/settings"); |  | ||||||
| const { addTray, refreshTray } = require("./scripts/tray"); |  | ||||||
| const { addMenu } = require("./scripts/menu"); |  | ||||||
|  |  | ||||||
| const path = require("path"); |  | ||||||
| const tidalUrl = "https://listen.tidal.com"; |  | ||||||
| const expressModule = require("./scripts/express"); |  | ||||||
| const mediaKeys = require("./constants/mediaKeys"); |  | ||||||
| const mediaInfoModule = require("./scripts/mediaInfo"); |  | ||||||
| const globalEvents = require("./constants/globalEvents"); |  | ||||||
|  |  | ||||||
| let mainWindow; |  | ||||||
| let icon = path.join(__dirname, "../assets/icon.png"); |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Enable live reload in development builds |  | ||||||
|  */ |  | ||||||
| if (!app.isPackaged) { |  | ||||||
|   require("electron-reload")(`${__dirname}`, { |  | ||||||
|     electron: require(`${__dirname}/../node_modules/electron`), |  | ||||||
|   }); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function createWindow(options = {}) { |  | ||||||
|   // Create the browser window. |  | ||||||
|   mainWindow = new BrowserWindow({ |  | ||||||
|     x: options.x, |  | ||||||
|     y: options.y, |  | ||||||
|     width: store && store.get(settings.windowBounds.width), |  | ||||||
|     height: store && store.get(settings.windowBounds.height), |  | ||||||
|     icon, |  | ||||||
|     tray: true, |  | ||||||
|     backgroundColor: options.backgroundColor, |  | ||||||
|     webPreferences: { |  | ||||||
|       affinity: "window", |  | ||||||
|       preload: path.join(__dirname, "preload.js"), |  | ||||||
|       plugins: true, |  | ||||||
|       devTools: true, // I like tinkering, others might too |  | ||||||
|     }, |  | ||||||
|   }); |  | ||||||
|  |  | ||||||
|   mainWindow.setMenuBarVisibility(store.get(settings.menuBar)); |  | ||||||
|  |  | ||||||
|   // load the Tidal website |  | ||||||
|   mainWindow.loadURL(tidalUrl); |  | ||||||
|  |  | ||||||
|   // run stuff after first load |  | ||||||
|   mainWindow.webContents.once("did-finish-load", () => {}); |  | ||||||
|  |  | ||||||
|   // Emitted when the window is closed. |  | ||||||
|   mainWindow.on("closed", function () { |  | ||||||
|     closeSettingsWindow(); |  | ||||||
|     app.quit(); |  | ||||||
|   }); |  | ||||||
|   mainWindow.on("resize", () => { |  | ||||||
|     let { width, height } = mainWindow.getBounds(); |  | ||||||
|  |  | ||||||
|     store.set(settings.windowBounds.root, { width, height }); |  | ||||||
|   }); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function addGlobalShortcuts() { |  | ||||||
|   Object.keys(mediaKeys).forEach((key) => { |  | ||||||
|     globalShortcut.register(`${key}`, () => { |  | ||||||
|       mainWindow.webContents.send("globalEvent", `${mediaKeys[key]}`); |  | ||||||
|     }); |  | ||||||
|   }); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // This method will be called when Electron has finished |  | ||||||
| // initialization and is ready to create browser windows. |  | ||||||
| // Some APIs can only be used after this event occurs. |  | ||||||
| app.on("ready", () => { |  | ||||||
|   createWindow(); |  | ||||||
|   addMenu(); |  | ||||||
|   createSettingsWindow(); |  | ||||||
|   addGlobalShortcuts(); |  | ||||||
|   addTray({ icon }); |  | ||||||
|   refreshTray(); |  | ||||||
|   store.get(settings.api) && expressModule.run(mainWindow); |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| app.on("activate", function () { |  | ||||||
|   // On OS X it's common to re-create a window in the app when the |  | ||||||
|   // dock icon is clicked and there are no other windows open. |  | ||||||
|   if (mainWindow === null) { |  | ||||||
|     createWindow(); |  | ||||||
|   } |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| // IPC |  | ||||||
|  |  | ||||||
| ipcMain.on(globalEvents.updateInfo, (event, arg) => { |  | ||||||
|   mediaInfoModule.update(arg); |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| ipcMain.on(globalEvents.hideSettings, (event, arg) => { |  | ||||||
|   hideSettingsWindow(); |  | ||||||
| }); |  | ||||||
| ipcMain.on(globalEvents.showSettings, (event, arg) => { |  | ||||||
|   showSettingsWindow(); |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| ipcMain.on(globalEvents.updateStatus, (event, arg) => { |  | ||||||
|   mediaInfoModule.updateStatus(arg); |  | ||||||
| }); |  | ||||||
| ipcMain.on(globalEvents.storeChanged, (event, arg) => { |  | ||||||
|   mainWindow.setMenuBarVisibility(store.get(settings.menuBar)); |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| ipcMain.on(globalEvents.error, (event, arg) => { |  | ||||||
|   console.log(event); |  | ||||||
| }); |  | ||||||
							
								
								
									
										261
									
								
								src/main.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,261 @@ | |||||||
|  | import { enable, initialize } from "@electron/remote/main"; | ||||||
|  | import { BrowserWindow, app, components, ipcMain, session } from "electron"; | ||||||
|  | import path from "path"; | ||||||
|  | import { globalEvents } from "./constants/globalEvents"; | ||||||
|  | import { settings } from "./constants/settings"; | ||||||
|  | import { startApi } from "./features/api"; | ||||||
|  | import { setDefaultFlags, setManagedFlagsFromSettings } from "./features/flags/flags"; | ||||||
|  | import { | ||||||
|  |   acquireInhibitorIfInactive, | ||||||
|  |   releaseInhibitorIfActive, | ||||||
|  | } from "./features/idleInhibitor/idleInhibitor"; | ||||||
|  | import { Logger } from "./features/logger"; | ||||||
|  | import { SharingService } from "./features/sharingService/sharingService"; | ||||||
|  | import { MediaInfo } from "./models/mediaInfo"; | ||||||
|  | import { MediaStatus } from "./models/mediaStatus"; | ||||||
|  | import { initRPC, rpc, unRPC } from "./scripts/discord"; | ||||||
|  | import { updateMediaInfo } from "./scripts/mediaInfo"; | ||||||
|  | import { addMenu } from "./scripts/menu"; | ||||||
|  | import { | ||||||
|  |   closeSettingsWindow, | ||||||
|  |   createSettingsWindow, | ||||||
|  |   hideSettingsWindow, | ||||||
|  |   settingsStore, | ||||||
|  |   showSettingsWindow, | ||||||
|  | } from "./scripts/settings"; | ||||||
|  | import { addTray, refreshTray } from "./scripts/tray"; | ||||||
|  | let mainInhibitorId = -1; | ||||||
|  |  | ||||||
|  | let mainWindow: BrowserWindow; | ||||||
|  | const icon = path.join(__dirname, "../assets/icon.png"); | ||||||
|  | const PROTOCOL_PREFIX = "tidal"; | ||||||
|  | const windowPreferences = { | ||||||
|  |   sandbox: false, | ||||||
|  |   plugins: true, | ||||||
|  |   devTools: true, // I like tinkering, others might too | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | setDefaultFlags(app); | ||||||
|  | setManagedFlagsFromSettings(app); | ||||||
|  |  | ||||||
|  | const tidalUrl = | ||||||
|  |   settingsStore.get<string, string>(settings.advanced.tidalUrl) || "https://listen.tidal.com"; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Update the menuBarVisibility according to the store value | ||||||
|  |  * | ||||||
|  |  */ | ||||||
|  | function syncMenuBarWithStore() { | ||||||
|  |   const fixedMenuBar = !!settingsStore.get(settings.menuBar); | ||||||
|  |  | ||||||
|  |   mainWindow.autoHideMenuBar = !fixedMenuBar; | ||||||
|  |   mainWindow.setMenuBarVisibility(fixedMenuBar); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @returns true/false based on whether the current window is the main window | ||||||
|  |  */ | ||||||
|  | function isMainInstance() { | ||||||
|  |   return app.requestSingleInstanceLock(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @returns true/false based on whether multiple instances are allowed | ||||||
|  |  */ | ||||||
|  | function isMultipleInstancesAllowed() { | ||||||
|  |   return !settingsStore.get(settings.singleInstance); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @param args the arguments passed to the app | ||||||
|  |  * @returns the custom protocol url if it exists, otherwise null | ||||||
|  |  */ | ||||||
|  | function getCustomProtocolUrl(args: string[]) { | ||||||
|  |   const customProtocolArg = args.find((arg) => arg.startsWith(PROTOCOL_PREFIX)); | ||||||
|  |  | ||||||
|  |   if (!customProtocolArg) { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return tidalUrl + "/" + customProtocolArg.substring(PROTOCOL_PREFIX.length + 3); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function createWindow(options = { x: 0, y: 0, backgroundColor: "white" }) { | ||||||
|  |   // Create the browser window. | ||||||
|  |   mainWindow = new BrowserWindow({ | ||||||
|  |     x: options.x, | ||||||
|  |     y: options.y, | ||||||
|  |     width: settingsStore?.get(settings.windowBounds.width), | ||||||
|  |     height: settingsStore?.get(settings.windowBounds.height), | ||||||
|  |     icon, | ||||||
|  |     backgroundColor: options.backgroundColor, | ||||||
|  |     autoHideMenuBar: true, | ||||||
|  |     webPreferences: { | ||||||
|  |       ...windowPreferences, | ||||||
|  |       ...{ | ||||||
|  |         preload: path.join(__dirname, "preload.js"), | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   enable(mainWindow.webContents); | ||||||
|  |   registerHttpProtocols(); | ||||||
|  |   syncMenuBarWithStore(); | ||||||
|  |  | ||||||
|  |   // find the custom protocol argument | ||||||
|  |   const customProtocolUrl = getCustomProtocolUrl(process.argv); | ||||||
|  |  | ||||||
|  |   if (customProtocolUrl) { | ||||||
|  |     // load the url received from the custom protocol | ||||||
|  |     mainWindow.loadURL(customProtocolUrl); | ||||||
|  |   } else { | ||||||
|  |     // load the Tidal website | ||||||
|  |     mainWindow.loadURL(tidalUrl); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (settingsStore.get(settings.disableBackgroundThrottle)) { | ||||||
|  |     // prevent setInterval lag | ||||||
|  |     mainWindow.webContents.setBackgroundThrottling(false); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   mainWindow.on("close", function (event: CloseEvent) { | ||||||
|  |     if (settingsStore.get(settings.minimizeOnClose)) { | ||||||
|  |       event.preventDefault(); | ||||||
|  |       mainWindow.hide(); | ||||||
|  |       refreshTray(mainWindow); | ||||||
|  |     } | ||||||
|  |     return false; | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   // Emitted when the window is closed. | ||||||
|  |   mainWindow.on("closed", function () { | ||||||
|  |     releaseInhibitorIfActive(mainInhibitorId); | ||||||
|  |     closeSettingsWindow(); | ||||||
|  |     app.quit(); | ||||||
|  |   }); | ||||||
|  |   mainWindow.on("resize", () => { | ||||||
|  |     const { width, height } = mainWindow.getBounds(); | ||||||
|  |     settingsStore.set(settings.windowBounds.root, { width, height }); | ||||||
|  |   }); | ||||||
|  |   mainWindow.webContents.setWindowOpenHandler(() => { | ||||||
|  |     return { | ||||||
|  |       action: "allow", | ||||||
|  |       overrideBrowserWindowOptions: { | ||||||
|  |         webPreferences: { | ||||||
|  |           sandbox: false, | ||||||
|  |           plugins: true, | ||||||
|  |           devTools: true, // I like tinkering, others might too | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|  |     }; | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function registerHttpProtocols() { | ||||||
|  |   if (!app.isDefaultProtocolClient(PROTOCOL_PREFIX)) { | ||||||
|  |     app.setAsDefaultProtocolClient(PROTOCOL_PREFIX); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // This method will be called when Electron has finished | ||||||
|  | // initialization and is ready to create browser windows. | ||||||
|  | // Some APIs can only be used after this event occurs. | ||||||
|  | app.on("ready", async () => { | ||||||
|  |   // check if the app is the main instance and multiple instances are not allowed | ||||||
|  |   if (isMainInstance() && !isMultipleInstancesAllowed()) { | ||||||
|  |     app.on("second-instance", (_, commandLine) => { | ||||||
|  |       const customProtocolUrl = getCustomProtocolUrl(commandLine); | ||||||
|  |  | ||||||
|  |       if (customProtocolUrl) { | ||||||
|  |         mainWindow.loadURL(customProtocolUrl); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       if (mainWindow) { | ||||||
|  |         if (mainWindow.isMinimized()) mainWindow.restore(); | ||||||
|  |         mainWindow.focus(); | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (isMainInstance() || isMultipleInstancesAllowed()) { | ||||||
|  |     await components.whenReady(); | ||||||
|  |     initialize(); | ||||||
|  |  | ||||||
|  |     // Adblock | ||||||
|  |     if (settingsStore.get(settings.adBlock)) { | ||||||
|  |       const filter = { urls: ["https://listen.tidal.com/*"] }; | ||||||
|  |       session.defaultSession.webRequest.onBeforeRequest(filter, (details, callback) => { | ||||||
|  |         if (details.url.match(/\/users\/.*\d\?country/)) callback({ cancel: true }); | ||||||
|  |         else callback({ cancel: false }); | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     Logger.log("components ready:", components.status()); | ||||||
|  |  | ||||||
|  |     createWindow(); | ||||||
|  |     addMenu(mainWindow); | ||||||
|  |     createSettingsWindow(); | ||||||
|  |     if (settingsStore.get(settings.trayIcon)) { | ||||||
|  |       addTray(mainWindow, { icon }); | ||||||
|  |       refreshTray(mainWindow); | ||||||
|  |     } | ||||||
|  |     settingsStore.get(settings.api) && startApi(mainWindow); | ||||||
|  |     settingsStore.get(settings.enableDiscord) && initRPC(); | ||||||
|  |   } else { | ||||||
|  |     app.quit(); | ||||||
|  |   } | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | app.on("activate", function () { | ||||||
|  |   // On OS X it's common to re-create a window in the app when the | ||||||
|  |   // dock icon is clicked and there are no other windows open. | ||||||
|  |   if (mainWindow === null) { | ||||||
|  |     createWindow(); | ||||||
|  |   } | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | app.on("browser-window-created", (_, window) => { | ||||||
|  |   enable(window.webContents); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | // IPC | ||||||
|  | ipcMain.on(globalEvents.updateInfo, (_event, arg: MediaInfo) => { | ||||||
|  |   updateMediaInfo(arg); | ||||||
|  |   if (arg.status === MediaStatus.playing) { | ||||||
|  |     mainInhibitorId = acquireInhibitorIfInactive(mainInhibitorId); | ||||||
|  |   } else { | ||||||
|  |     releaseInhibitorIfActive(mainInhibitorId); | ||||||
|  |     mainInhibitorId = -1; | ||||||
|  |   } | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | ipcMain.on(globalEvents.hideSettings, () => { | ||||||
|  |   hideSettingsWindow(); | ||||||
|  | }); | ||||||
|  | ipcMain.on(globalEvents.showSettings, () => { | ||||||
|  |   showSettingsWindow(); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | ipcMain.on(globalEvents.refreshMenuBar, () => { | ||||||
|  |   syncMenuBarWithStore(); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | ipcMain.on(globalEvents.storeChanged, () => { | ||||||
|  |   syncMenuBarWithStore(); | ||||||
|  |  | ||||||
|  |   if (settingsStore.get(settings.enableDiscord) && !rpc) { | ||||||
|  |     initRPC(); | ||||||
|  |   } else if (!settingsStore.get(settings.enableDiscord) && rpc) { | ||||||
|  |     unRPC(); | ||||||
|  |   } | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | ipcMain.on(globalEvents.error, (event) => { | ||||||
|  |   console.log(event); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | ipcMain.handle(globalEvents.getUniversalLink, async (event, url) => { | ||||||
|  |   return SharingService.getUniversalLink(url); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | Logger.watch(ipcMain); | ||||||