mirror of
				https://github.com/Mastermindzh/tidal-hifi.git
				synced 2025-10-31 08:49:35 +01:00 
			
		
		
		
	- 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
This commit is contained in:
		| @@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file. | ||||
| The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), | ||||
| and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). | ||||
|  | ||||
| ## [5.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 | ||||
|  | ||||
| ## [5.8.0] | ||||
|  | ||||
| - Updated Electron to 28.1.1 (fixes [325](https://github.com/Mastermindzh/tidal-hifi/issues/325)) | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "tidal-hifi", | ||||
|   "version": "5.8.0", | ||||
|   "version": "5.9.0", | ||||
|   "description": "Tidal on Electron with widevine(hifi) support", | ||||
|   "main": "ts-dist/main.js", | ||||
|   "scripts": { | ||||
|   | ||||
| @@ -26,7 +26,7 @@ export const settings = { | ||||
|     includeTimestamps: "discord.includeTimestamps", | ||||
|     showSong: "discord.showSong", | ||||
|     idleText: "discord.idleText", | ||||
|     listeningText: "discord.listeningText", | ||||
|     usingText: "discord.usingText", | ||||
|   }, | ||||
|   ListenBrainz: { | ||||
|     root: "ListenBrainz", | ||||
|   | ||||
| @@ -57,7 +57,7 @@ let adBlock: HTMLInputElement, | ||||
|   discord_button_text: HTMLInputElement, | ||||
|   discord_show_song: HTMLInputElement, | ||||
|   discord_idle_text: HTMLInputElement, | ||||
|   discord_listening_text: HTMLInputElement; | ||||
|   discord_using_text: HTMLInputElement; | ||||
|  | ||||
| addCustomCss(app); | ||||
|  | ||||
| @@ -148,7 +148,7 @@ function refreshSettings() { | ||||
|     discord_button_text.value = settingsStore.get(settings.discord.buttonText); | ||||
|     discord_show_song.checked = settingsStore.get(settings.discord.showSong); | ||||
|     discord_idle_text.value = settingsStore.get(settings.discord.idleText); | ||||
|     discord_listening_text.value = settingsStore.get(settings.discord.listeningText); | ||||
|     discord_using_text.value = settingsStore.get(settings.discord.usingText); | ||||
|  | ||||
|     // set state of all switches with additional settings | ||||
|     Object.values(switchesWithSettings).forEach((settingSwitch) => { | ||||
| @@ -263,7 +263,7 @@ window.addEventListener("DOMContentLoaded", () => { | ||||
|   listenbrainz_delay = get("listenbrainz_delay"); | ||||
|   discord_button_text = get("discord_button_text"); | ||||
|   discord_show_song = get("discord_show_song"); | ||||
|   discord_listening_text = get("discord_listening_text"); | ||||
|   discord_using_text = get("discord_using_text"); | ||||
|   discord_idle_text = get("discord_idle_text") | ||||
|  | ||||
|   refreshSettings(); | ||||
| @@ -301,5 +301,5 @@ window.addEventListener("DOMContentLoaded", () => { | ||||
|   addInputListener(discord_button_text, settings.discord.buttonText); | ||||
|   addInputListener(discord_show_song, settings.discord.showSong, switchesWithSettings.discord_show_song); | ||||
|   addInputListener(discord_idle_text, settings.discord.idleText); | ||||
|   addInputListener(discord_listening_text, settings.discord.listeningText); | ||||
|   addInputListener(discord_using_text, settings.discord.usingText); | ||||
| }); | ||||
|   | ||||
| @@ -227,9 +227,9 @@ | ||||
|  | ||||
|               <div class="group__option" class="hidden"> | ||||
|                 <div class="group__description"> | ||||
|                   <h4>Listening Text</h4> | ||||
|                   <p>The text displayed on Discord's rich presence while listening to a song.</p> | ||||
|                   <input id="discord_listening_text" type="text" class="text-input" name="discord_listening_text" /> | ||||
|                   <h4>Using Tidal Text</h4> | ||||
|                   <p>The text displayed on Discord's rich presence while "showSong" is turned off</p> | ||||
|                   <input id="discord_using_text" type="text" class="text-input" name="discord_using_text" /> | ||||
|                 </div> | ||||
|               </div> | ||||
|  | ||||
| @@ -432,7 +432,7 @@ | ||||
|           <img alt="tidal icon" class="about-section__icon" src="./icon.png" /> | ||||
|           <h4>TIDAL Hi-Fi</h4> | ||||
|           <div class="about-section__version"> | ||||
|             <a href="https://github.com/Mastermindzh/tidal-hifi/releases/tag/5.8.0">5.8.0</a> | ||||
|             <a href="https://github.com/Mastermindzh/tidal-hifi/releases/tag/5.9.0">5.9.0</a> | ||||
|           </div> | ||||
|           <div class="about-section__links"> | ||||
|             <a href="https://github.com/mastermindzh/tidal-hifi/" class="about-section__button">Github <i | ||||
| @@ -454,4 +454,4 @@ | ||||
|   </div> | ||||
| </body> | ||||
|  | ||||
| </html> | ||||
| </html> | ||||
							
								
								
									
										121
									
								
								src/preload.ts
									
									
									
									
									
								
							
							
						
						
									
										121
									
								
								src/preload.ts
									
									
									
									
									
								
							| @@ -26,6 +26,8 @@ let player: Player; | ||||
| let currentPlayStatus = MediaStatus.paused; | ||||
| let currentListenBrainzDelayId: ReturnType<typeof setTimeout>; | ||||
| let scrobbleWaitingForDelay = false; | ||||
| let wasJustPausedOrResumed = false; | ||||
| let currentMediaInfo: Options; | ||||
|  | ||||
| const elements = { | ||||
|   play: '*[data-test="play"]', | ||||
| @@ -182,6 +184,7 @@ function getUpdateFrequency() { | ||||
|  * Play or pause the current song | ||||
|  */ | ||||
| function playPause() { | ||||
|   wasJustPausedOrResumed = true; | ||||
|   const play = elements.get("play"); | ||||
|  | ||||
|   if (play) { | ||||
| @@ -301,6 +304,8 @@ function addIPCEventListeners() { | ||||
|     ipcRenderer.on("globalEvent", (_event, args) => { | ||||
|       switch (args) { | ||||
|         case globalEvents.playPause: | ||||
|         case globalEvents.play: | ||||
|         case globalEvents.pause: | ||||
|           playPause(); | ||||
|           break; | ||||
|         case globalEvents.next: | ||||
| @@ -309,12 +314,6 @@ function addIPCEventListeners() { | ||||
|         case globalEvents.previous: | ||||
|           elements.click("previous"); | ||||
|           break; | ||||
|         case globalEvents.play: | ||||
|           elements.click("play"); | ||||
|           break; | ||||
|         case globalEvents.pause: | ||||
|           elements.click("pause"); | ||||
|           break; | ||||
|         case globalEvents.toggleFavorite: | ||||
|           elements.click("favorite"); | ||||
|           break; | ||||
| @@ -357,6 +356,7 @@ function convertDuration(duration: string) { | ||||
|  */ | ||||
| function updateMediaInfo(options: Options, notify: boolean) { | ||||
|   if (options) { | ||||
|     currentMediaInfo = options; | ||||
|     ipcRenderer.send(globalEvents.updateInfo, options); | ||||
|     if (settingsStore.get(settings.notifications) && notify) { | ||||
|       new Notification({ title: options.title, body: options.artists, icon: options.icon }).show(); | ||||
| @@ -510,60 +510,69 @@ setInterval(function () { | ||||
|   const title = elements.getText("title"); | ||||
|   const artistsArray = elements.getArtistsArray(); | ||||
|   const artistsString = elements.getArtistsString(artistsArray); | ||||
|   skipArtistsIfFoundInSkippedArtistsList(artistsArray); | ||||
|  | ||||
|   const album = elements.getAlbumName(); | ||||
|   const current = elements.getText("current"); | ||||
|   const duration = elements.getText("duration"); | ||||
|   const songDashArtistTitle = `${title} - ${artistsString}`; | ||||
|   const currentStatus = getCurrentlyPlayingStatus(); | ||||
|   const options = { | ||||
|     title, | ||||
|     artists: artistsString, | ||||
|     album: album, | ||||
|     status: currentStatus, | ||||
|     url: getTrackURL(), | ||||
|     current, | ||||
|     duration, | ||||
|     "app-name": appName, | ||||
|     image: "", | ||||
|     icon: "", | ||||
|     favorite: elements.isFavorite(), | ||||
|   }; | ||||
|  | ||||
|   const titleOrArtistsChanged = currentSong !== songDashArtistTitle; | ||||
|   const current = elements.getText("current"); | ||||
|   const currentStatus = getCurrentlyPlayingStatus(); | ||||
|  | ||||
|   // update title, url and play info with new info | ||||
|   setTitle(songDashArtistTitle); | ||||
|   getTrackURL(); | ||||
|   currentSong = songDashArtistTitle; | ||||
|   currentPlayStatus = currentStatus; | ||||
|  | ||||
|   const image = elements.getSongIcon(); | ||||
|  | ||||
|   new Promise<void>((resolve) => { | ||||
|     if (image.startsWith("http")) { | ||||
|       options.image = image; | ||||
|       downloadFile(image, notificationPath).then( | ||||
|         () => { | ||||
|           options.icon = notificationPath; | ||||
|           resolve(); | ||||
|         }, | ||||
|         () => { | ||||
|           // if the image can't be downloaded then continue without it | ||||
|           resolve(); | ||||
|         } | ||||
|       ); | ||||
|     } else { | ||||
|       // if the image can't be found on the page continue without it | ||||
|       resolve(); | ||||
|   // update info if song changed or was just paused/resumed | ||||
|   if (titleOrArtistsChanged || wasJustPausedOrResumed) { | ||||
|     if (wasJustPausedOrResumed) { | ||||
|       wasJustPausedOrResumed = false; | ||||
|     } | ||||
|   }).then(() => { | ||||
|     updateMediaInfo(options, titleOrArtistsChanged); | ||||
|     if (titleOrArtistsChanged) { | ||||
|       updateMediaSession(options); | ||||
|     } | ||||
|   }); | ||||
|     skipArtistsIfFoundInSkippedArtistsList(artistsArray); | ||||
|  | ||||
|     const album = elements.getAlbumName(); | ||||
|     const duration = elements.getText("duration"); | ||||
|     const options = { | ||||
|       title, | ||||
|       artists: artistsString, | ||||
|       album: album, | ||||
|       status: currentStatus, | ||||
|       url: getTrackURL(), | ||||
|       current, | ||||
|       duration, | ||||
|       "app-name": appName, | ||||
|       image: "", | ||||
|       icon: "", | ||||
|       favorite: elements.isFavorite(), | ||||
|     }; | ||||
|  | ||||
|     // update title, url and play info with new info | ||||
|     setTitle(songDashArtistTitle); | ||||
|     getTrackURL(); | ||||
|     currentSong = songDashArtistTitle; | ||||
|     currentPlayStatus = currentStatus; | ||||
|  | ||||
|     const image = elements.getSongIcon(); | ||||
|  | ||||
|     new Promise<void>((resolve) => { | ||||
|       if (image.startsWith("http")) { | ||||
|         options.image = image; | ||||
|         downloadFile(image, notificationPath).then( | ||||
|           () => { | ||||
|             options.icon = notificationPath; | ||||
|             resolve(); | ||||
|           }, | ||||
|           () => { | ||||
|             // if the image can't be downloaded then continue without it | ||||
|             resolve(); | ||||
|           } | ||||
|         ); | ||||
|       } else { | ||||
|         // if the image can't be found on the page continue without it | ||||
|         resolve(); | ||||
|       } | ||||
|     }).then(() => { | ||||
|       updateMediaInfo(options, titleOrArtistsChanged); | ||||
|       if (titleOrArtistsChanged) { | ||||
|         updateMediaSession(options); | ||||
|       } | ||||
|     }); | ||||
|   } else { | ||||
|     // just update the time | ||||
|     updateMediaInfo({ ...currentMediaInfo, ...{ current } }, false); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * automatically skip a song if the artists are found in the list of artists to skip | ||||
|   | ||||
| @@ -22,50 +22,69 @@ const observer = () => { | ||||
|     rpc.setActivity(getActivity()); | ||||
|   } | ||||
| }; | ||||
| const getActivity = (): Presence | undefined => { | ||||
|   const presence: Presence | undefined = { | ||||
|     largeImageKey: "tidal-hifi-icon", | ||||
|     largeImageText: `TIDAL Hi-Fi ${app.getVersion()}`, | ||||
|     instance: false, | ||||
|   }; | ||||
|  | ||||
| const defaultPresence = { | ||||
|   largeImageKey: "tidal-hifi-icon", | ||||
|   largeImageText: `TIDAL Hi-Fi ${app.getVersion()}`, | ||||
|   instance: false, | ||||
| }; | ||||
|  | ||||
| const getActivity = (): Presence => { | ||||
|   const presence: Presence = { ...defaultPresence }; | ||||
|  | ||||
|   if (mediaInfo.status === MediaStatus.paused) { | ||||
|     presence.details = settingsStore.get<string, string>(settings.discord.idleText) ?? "Browsing Tidal"; | ||||
|     presence.details = | ||||
|       settingsStore.get<string, string>(settings.discord.idleText) ?? "Browsing Tidal"; | ||||
|   } else { | ||||
|     const showSong = settingsStore.get<string, boolean>(settings.discord.showSong) ?? false; | ||||
|     if (showSong) { | ||||
|       const includeTimestamps = | ||||
|         settingsStore.get<string, boolean>(settings.discord.includeTimestamps) ?? true; | ||||
|  | ||||
|       if (includeTimestamps) { | ||||
|         const currentSeconds = timeToSeconds(mediaInfo.current.split(":")); | ||||
|         const durationSeconds = timeToSeconds(mediaInfo.duration.split(":")); | ||||
|         const date = new Date(); | ||||
|         const now = (date.getTime() / 1000) | 0; | ||||
|         const remaining = date.setSeconds(date.getSeconds() + (durationSeconds - currentSeconds)); | ||||
|         presence.startTimestamp = now; | ||||
|         presence.endTimestamp = remaining; | ||||
|       } | ||||
|  | ||||
|       const detailsPrefix = settingsStore.get<string, string>(settings.discord.detailsPrefix) ?? "Listening to "; | ||||
|       const buttonText = settingsStore.get<string, string>(settings.discord.buttonText) ?? "Play on TIDAL"; | ||||
|  | ||||
|       if (mediaInfo.url) { | ||||
|         presence.details = `${detailsPrefix}${mediaInfo.title}`; | ||||
|         presence.state = mediaInfo.artists ? mediaInfo.artists : "unknown artist(s)"; | ||||
|         presence.largeImageKey = mediaInfo.image; | ||||
|         if (mediaInfo.album) { | ||||
|           presence.largeImageText = mediaInfo.album; | ||||
|         } | ||||
|         presence.buttons = [{ label: buttonText, url: mediaInfo.url }]; | ||||
|       } else { | ||||
|         presence.details = `Watching ${mediaInfo.title}`; | ||||
|         presence.state = mediaInfo.artists; | ||||
|       } | ||||
|       const { includeTimestamps, detailsPrefix, buttonText } = getFromStore(); | ||||
|       includeTimeStamps(includeTimestamps); | ||||
|       setPresenceFromMediaInfo(detailsPrefix, buttonText); | ||||
|     } else { | ||||
|       presence.details = settingsStore.get<string, string>(settings.discord.listeningText) ?? "Listening Tidal"; | ||||
|       presence.details = | ||||
|         settingsStore.get<string, string>(settings.discord.usingText) ?? "Playing media on TIDAL"; | ||||
|     } | ||||
|   } | ||||
|   return presence; | ||||
|  | ||||
|   function getFromStore() { | ||||
|     const includeTimestamps = | ||||
|       settingsStore.get<string, boolean>(settings.discord.includeTimestamps) ?? true; | ||||
|     const detailsPrefix = | ||||
|       settingsStore.get<string, string>(settings.discord.detailsPrefix) ?? "Listening to "; | ||||
|     const buttonText = | ||||
|       settingsStore.get<string, string>(settings.discord.buttonText) ?? "Play on TIDAL"; | ||||
|  | ||||
|     return { includeTimestamps, detailsPrefix, buttonText }; | ||||
|   } | ||||
|  | ||||
|   function setPresenceFromMediaInfo(detailsPrefix: any, buttonText: any) { | ||||
|     if (mediaInfo.url) { | ||||
|       presence.details = `${detailsPrefix}${mediaInfo.title}`; | ||||
|       presence.state = mediaInfo.artists ? mediaInfo.artists : "unknown artist(s)"; | ||||
|       presence.largeImageKey = mediaInfo.image; | ||||
|       if (mediaInfo.album) { | ||||
|         presence.largeImageText = mediaInfo.album; | ||||
|       } | ||||
|       presence.buttons = [{ label: buttonText, url: mediaInfo.url }]; | ||||
|     } else { | ||||
|       presence.details = `Watching ${mediaInfo.title}`; | ||||
|       presence.state = mediaInfo.artists; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   function includeTimeStamps(includeTimestamps: any) { | ||||
|     if (includeTimestamps) { | ||||
|       const currentSeconds = timeToSeconds(mediaInfo.current.split(":")); | ||||
|       const durationSeconds = timeToSeconds(mediaInfo.duration.split(":")); | ||||
|       const date = new Date(); | ||||
|       const now = (date.getTime() / 1000) | 0; | ||||
|       const remaining = date.setSeconds(date.getSeconds() + (durationSeconds - currentSeconds)); | ||||
|       presence.startTimestamp = now; | ||||
|       presence.endTimestamp = remaining; | ||||
|     } | ||||
|   } | ||||
| }; | ||||
|  | ||||
| /** | ||||
|   | ||||
| @@ -5,6 +5,25 @@ import path from "path"; | ||||
| import { settings } from "../constants/settings"; | ||||
|  | ||||
| let settingsWindow: BrowserWindow; | ||||
| /** | ||||
|  * Build a migration step for several settings. | ||||
|  * All settings will be checked and set to the default if non-existent. | ||||
|  * @param version | ||||
|  * @param migrationStore | ||||
|  * @param options | ||||
|  */ | ||||
| const buildMigration = ( | ||||
|   version: string, | ||||
|   migrationStore: { get: (str: string) => string; set: (str: string, val: unknown) => void }, | ||||
|   options: Array<{ key: string; value: unknown }> | ||||
| ) => { | ||||
|   console.log(`running migrations for ${version}`); | ||||
|   options.forEach(({ key, value }) => { | ||||
|     const valueToSet = migrationStore.get(key) ?? value; | ||||
|     console.log(`  - setting ${key} to ${value}`); | ||||
|     migrationStore.set(key, valueToSet); | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| export const settingsStore = new Store({ | ||||
|   defaults: { | ||||
| @@ -19,12 +38,12 @@ export const settingsStore = new Store({ | ||||
|     enableCustomHotkeys: false, | ||||
|     enableDiscord: false, | ||||
|     discord: { | ||||
|       showSong: false, | ||||
|       showSong: true, | ||||
|       idleText: "Browsing Tidal", | ||||
|       listeningText: "Listening Tidal", | ||||
|       usingText: "Playing media on TIDAL", | ||||
|       includeTimestamps: true, | ||||
|       detailsPrefix: "Listening to ", | ||||
|       buttonText: "Play on Tidal", | ||||
|       includeTimestamps: true, | ||||
|     }, | ||||
|     ListenBrainz: { | ||||
|       enabled: false, | ||||
| @@ -72,6 +91,16 @@ export const settingsStore = new Store({ | ||||
|         migrationStore.get(settings.discord.includeTimestamps) ?? true | ||||
|       ); | ||||
|     }, | ||||
|     "5.9.0": (migrationStore) => { | ||||
|       buildMigration("5.9.0", migrationStore, [ | ||||
|         { key: settings.discord.showSong, value: "true" }, | ||||
|         { key: settings.discord.idleText, value: "Browsing Tidal" }, | ||||
|         { | ||||
|           key: settings.discord.usingText, | ||||
|           value: "Playing media on TIDAL", | ||||
|         }, | ||||
|       ]); | ||||
|     }, | ||||
|   }, | ||||
| }); | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user