import { store } from "./vuex-store.js"; Vue.use(VueHorizontal); Vue.use(VueObserveVisibility); Vue.use(BootstrapVue); /* @namespace */ const app = new Vue({ store: store, data: { version: ipcRenderer.sendSync("get-version"), appMode: "player", ipcRenderer: ipcRenderer, cfg: ipcRenderer.sendSync("getStore"), isDev: ipcRenderer.sendSync("is-dev"), clientPort: ipcRenderer.sendSync("get-port"), drawertest: false, platform: "", mk: {}, pluginInstalled: false, pluginMenuEntries: [], lz: ipcRenderer.sendSync("get-i18n", "en_US"), lzListing: ipcRenderer.sendSync("get-i18n-listing"), search: { term: "", cursor: -1, hints: [], showHints: false, results: {}, resultsSocial: {}, resultsLibrary: {}, limit: 10, }, fullscreenLyrics: false, fullscreenState: ipcRenderer.sendSync("getFullScreen"), playerLCD: { playbackDuration: 0, desiredDuration: 0, userInteraction: false, }, drawer: { open: false, panel: "", }, browsepage: [], listennow: [], madeforyou: [], radio: { personal: {}, recent: {}, amlive: {}, }, mklang: "en", webview: { url: "", title: "", loading: false, }, showingPlaylist: [], appleCurator: [], multiroom: [], artistPage: { data: {}, }, library: { backgroundNotification: { show: false, message: "", total: 0, progress: 0, }, songs: { sortingOptions: { albumName: "0", artistName: "0", name: "0", genre: "0", releaseDate: "0", durationInMillis: "0", dateAdded: "0", }, sorting: "name", sortOrder: "asc", listing: [], meta: { total: 0, progress: 0 }, search: "", displayListing: [], downloadState: 0, // 0 = not started, 1 = in progress, 2 = complete, 3 = empty library }, albums: { sortingOptions: { artistName: "0", name: "0", genre: "0", releaseDate: "0", }, viewAs: "covers", sorting: ["dateAdded", "name"], // [0] = recentlyadded page, [1] = albums page sortOrder: ["desc", "asc"], // [0] = recentlyadded page, [1] = albums page listing: [], meta: { total: 0, progress: 0 }, search: "", displayListing: [], downloadState: 0, // 0 = not started, 1 = in progress, 2 = complete, 3 = empty library }, artists: { sortingOptions: { artistName: "0", name: "0", genre: "0", releaseDate: "0", }, viewAs: "covers", sorting: ["dateAdded", "name"], // [0] = recentlyadded page, [1] = albums page sortOrder: ["desc", "asc"], // [0] = recentlyadded page, [1] = albums page listing: [], meta: { total: 0, progress: 0 }, search: "", displayListing: [], downloadState: 0, // 0 = not started, 1 = in progress, 2 = complete, 3 = empty library }, localsongs: [], }, playlists: { listing: [], details: {}, loadingState: 0, // 0 loading, 1 loaded, 2 error id: "", trackMapping: {}, }, webremoteurl: "", webremoteqr: "", mxmtoken: "", mkIsReady: false, animateBackground: false, currentArtUrl: "", currentArtUrlRaw: "", lyricon: false, currentTrackID: "", lyrics: [], currentLyricsLine: 0, richlyrics: [], lyricsMediaItem: {}, lyricsDebug: { current: 0, start: 0, end: 0, }, lyricOffset: 0, v3: { requestBody: { platform: "web", }, }, tmpHeight: "", tmpWidth: "", tmpX: "", tmpY: "", miniTmpX: "", miniTmpY: "", tmpVar: [], notification: false, chrome: { sidebarCollapsed: false, nativeControls: false, contentScrollPosY: 0, appliedTheme: { location: "", info: {}, }, windowState: "normal", desiredPageTransition: "wpfade_transform", hideUserInfo: ipcRenderer.sendSync("is-dev") || false, artworkReady: false, userinfo: { id: "", attributes: { name: "Cider User", handle: "CiderUser", artwork: { url: "./assets/logocut.png" }, }, }, forceDirectives: {}, menuOpened: false, maximized: false, drawerOpened: false, drawerState: "queue", topChromeVisible: true, progresshover: false, windowControlPosition: "right", contentAreaScrolling: true, showCursor: false, }, collectionList: { response: {}, title: "", type: "", }, MVsource: null, prevButtonBackIndicator: false, currentSongInfo: {}, page: "", pageHistory: [], songstest: false, hangtimer: null, selectedMediaItems: [], routes: ["browse", "listen_now", "radio"], musicBaseUrl: "https://api.music.apple.com/", modals: { addToPlaylist: false, spatialProperties: false, qrcode: false, equalizer: false, audioSettings: false, pluginMenu: false, audioControls: false, audioPlaybackRate: false, showPlaylist: false, castMenu: false, pathMenu: false, moreInfo: false, airplayPW: false, settings: false, }, socialBadges: { badgeMap: {}, version: "", mediaItems: [], mediaItemDLState: 0, // 0 = not started, 1 = in progress, 2 = complete }, menuPanel: { visible: false, event: null, content: { name: "", items: {}, headerItems: {}, }, }, pauseButtonTimer: null, activeCasts: [], pluginPages: { page: "hello-world", pages: [], }, moreinfodata: [], notyf: notyf, idleTimer: null, idleState: false, appVisible: true, }, watch: { cfg: { handler: function (val, oldVal) { console.debug(`Config changed: ${JSON.stringify(val)}`); ipcRenderer.send("setStore", val); }, deep: true, }, page: () => { document.getElementById("app-content").scrollTo(0, 0); app.resetState(); }, showingPlaylist: () => { if (!app.modals.showPlaylist) { document.getElementById("app-content").scrollTo(0, 0); app.resetState(); } }, artistPage: () => { document.getElementById("app-content").scrollTo(0, 0); app.resetState(); }, }, mounted() { window.addEventListener( "hashchange", function (event) { let currentPath = window.location.hash.slice(1); console.debug("hashchange", currentPath); }, false ); }, methods: { setWindowHash(route = "") { window.location.hash = `#${route}`; }, async oobeInit() { this.appMode = "oobe"; this.setLz(this.cfg.general.language); this.setLzManual(); clearTimeout(this.hangtimer); document.body.removeAttribute("loading"); ipcRenderer.invoke("renderer-ready", true); document.querySelector("#LOADER").remove(); }, getAppStyle() { let finalStyle = {}; if (this.cfg.visual.window_background_style === "color") { finalStyle["background-color"] = this.cfg.visual.windowColor; } if (this.cfg.visual.customAccentColor) { finalStyle["--keyColor"] = this.cfg.visual.accentColor; finalStyle["--songProgressColor"] = this.cfg.visual.accentColor; } else if (this.cfg.visual.purplePodcastPlaybackBar && MusicKit.getInstance().nowPlayingItem?.type == "podcast-episodes") { finalStyle["--songProgressColor"] = "#6929D0"; } return finalStyle; }, setTimeout(func, time) { return setTimeout(func, time); }, songLinkShare(amUrl) { notyf.open({ type: "info", className: "notyf-info", message: app.getLz("term.song.link.generate"), }); let self = this; let httpRequest = new XMLHttpRequest(); httpRequest.open("GET", `https://api.song.link/v1-alpha.1/links?url=${amUrl}&userCountry=US`, true); httpRequest.send(); httpRequest.onreadystatechange = function () { if (httpRequest.readyState === 4) { if (httpRequest.status === 200) { let response = JSON.parse(httpRequest.responseText); console.debug(response); self.copyToClipboard(response.pageUrl); } else { console.warn("There was a problem with the request."); notyf.error(app.getLz("term.requestError")); } } }; }, formatVolumeTooltip() { let advancedTooltip = this.cfg.audio.dBSPL ? (Number(this.cfg.audio.dBSPLcalibration) + Math.log10(this.mk.volume) * 20).toFixed(2) + " dB SPL" : (Math.log10(this.mk.volume) * 20).toFixed(2) + " dBFS"; return this.cfg.audio.advanced ? advancedTooltip : (this.mk.volume * 100).toFixed(0) + "%"; }, mainMenuVisibility(val) { if (val) { this.mk.isAuthorized ? (this.chrome.menuOpened = !this.chrome.menuOpened) : false; if (!this.mk.isAuthorized) { this.mk.authorize(); } } else { setTimeout(() => { this.chrome.menuOpened = false; }, 100); } }, stringTemplateParser(expression, valueObj) { const templateMatcher = /{{\s?([^{}\s]*)\s?}}/g; let text = expression.replace(templateMatcher, (substring, value, index) => { value = valueObj[value]; return value; }); return text; // stringTemplateParser('my name is {{name}} and age is {{age}}', {name: 'Tom', age:100}) }, async setLz(lang) { if (lang == "") { lang = this.cfg.general.language; } this.lz = ipcRenderer.sendSync("get-i18n", lang); this.mklang = await this.MKJSLang(); try { this.listennow.timestamp = 0; this.browsepage.timestamp = 0; this.radio.timestamp = 0; } catch (e) {} }, /** * Grabs translation for localization. * @param {string} message - The key to grab the translated term * @param {object} options - Optional options * @author booploops#7139 * @memberOf app */ getLz(message, options = {}) { if (this.lz[message]) { if (options["count"]) { if (typeof this.lz[message] === "object") { let type = window.fastPluralRules.getPluralFormNameForCardinalByLocale(this.cfg.general.language.replace("_", "-"), options["count"]); return this.lz[message][type] ?? this.lz[message][Object.keys(this.lz[message])[0]] ?? this.lz[message]; } else { // fallback English plural forms ( old i18n ) if (options["count"] > 1) { return this.lz[message + "s"] ?? this.lz[message]; } else { return this.lz[message] ?? this.lz[message + "s"]; } } } else if (typeof this.lz[message] === "object") { return this.lz[message][Object.keys(this.lz[message])[0]]; } return this.lz[message]; } else { return message; } }, getProfileLz(type, name) { // For Spatial and CAR. let result = ""; // Hard-coded shiz switch (name) { case "Maikiwi": return "Maikiwi"; break; case "Maikiwi+": return "Maikiwi+"; break; case "Minimal+": return this.getLz("settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile.minimal") + "+"; break; case "live": return "LIVE"; break; } switch (type) { case "CAR": result = this.getLz("settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode." + name); if (result === "settings.option.audio.enableAdvancedFunctionality.atmosphereRealizerMode." + name) { return name; } else { return result; } break; case "CTS": result = this.getLz("settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile." + name.toLowerCase()); if (result === "settings.option.audio.enableAdvancedFunctionality.tunedAudioSpatialization.profile." + name.toLowerCase()) { return name; } else { return result; } break; default: return name; } }, setLzManual() { app.$data.library.songs.sortingOptions = { albumName: app.getLz("term.sortBy.album"), artistName: app.getLz("term.sortBy.artist"), name: app.getLz("term.sortBy.name"), genre: app.getLz("term.sortBy.genre"), releaseDate: app.getLz("term.sortBy.releaseDate"), durationInMillis: app.getLz("term.sortBy.duration"), dateAdded: app.getLz("term.sortBy.dateAdded"), }; app.$data.library.albums.sortingOptions = { artistName: app.getLz("term.sortBy.artist"), name: app.getLz("term.sortBy.name"), genre: app.getLz("term.sortBy.genre"), releaseDate: app.getLz("term.sortBy.releaseDate"), }; app.$data.library.artists.sortingOptions = { artistName: app.getLz("term.sortBy.artist"), name: app.getLz("term.sortBy.name"), genre: app.getLz("term.sortBy.genre"), releaseDate: app.getLz("term.sortBy.releaseDate"), }; }, async showSocialListeningTo() { let contentIds = Object.keys(app.socialBadges.badgeMap); app.showCollection({ data: this.socialBadges.mediaItems }, "Friends Listening To", "albums"); if (this.socialBadges.mediaItemDLState == 1 || this.socialBadges.mediaItemDLState == 2) { return; } this.socialBadges.mediaItemDLState = 2; await asyncForEach(contentIds, async (item) => { try { let type = "albums"; if (item.includes("pl.")) { type = "playlists"; } if (item.includes("ra.")) { type = "stations"; } let found = await app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/${type}/${item}`); this.socialBadges.mediaItems.push(found.data.data[0]); } catch (e) {} }); }, quit() { ipcRenderer.invoke("quit-app"); }, async openAppleMusicURL(url) { let properties = MusicKit.formattedMediaURL(url); let item = { id: properties.contentId, attributes: { playParams: { id: properties.contentId, kind: properties.kind, }, }, type: properties.kind, kind: properties.kind, }; app.routeView(item); }, saveFile(fileName, urlFile) { let a = document.createElement("a"); a.style = "display: none"; document.body.appendChild(a); a.href = urlFile; a.download = fileName; a.click(); window.URL.revokeObjectURL(url); a.remove(); }, async showMenuPanel(data, event) { app.menuPanel.visible = true; app.menuPanel.content.name = data.name ?? ""; app.menuPanel.content.items = data.items ?? {}; app.menuPanel.content.headerItems = data.headerItems ?? {}; if (event) { app.menuPanel.event = event; } }, async getSvgIcon(url) { let response = await fetch(url); let data = await response.text(); return data; }, getSocialBadges(cb = () => {}) { let self = this; try { app.mk.api.v3.music("/v1/social/badging-map").then((data) => { self.socialBadges.badgeMap = data.data.results.badgingMap; cb(data.data.results.badgingMap); }); } catch (ex) { this.socialBadges.badgeMap = {}; } }, addFavorite(id, type) { this.cfg.home.favoriteItems.push({ id: id, type: type, }); }, modularUITest(val = false) { this.fullscreenLyrics = val; if (val) { document.querySelector("#app-main").classList.add("modular-fs"); } else { document.querySelector("#app-main").classList.remove("modular-fs"); } }, navigateBack() { this.chrome.desiredPageTransition = "wpfade_transform_backwards"; return new Promise((resolve, reject) => { history.back(); setTimeout(() => { resolve((this.chrome.desiredPageTransition = "wpfade_transform")); }, 100); }); }, goToGrouping(url = "https://music.apple.com/WebObjects/MZStore.woa/wa/viewGrouping?cc=us&id=34") { if (url.includes("viewTop")) { window.location.hash = `#charts/top`; } else { const id = url.split("id=")[1]; window.location.hash = `#groupings/${id}`; } }, navigateForward() { history.forward(); }, resetState() { this.menuPanel.visible = false; app.selectedMediaItems = []; this.chrome.contentAreaScrolling = true; for (let key in app.modals) { app.modals[key] = false; } }, resumeTabs() { if (app.cfg.general.resumeTabs.tab == "dynamic") { this.appRoute(app.cfg.general.resumeTabs.dynamicData); } else { this.appRoute(app.cfg.general.resumeTabs.tab); } }, promptAddToPlaylist() { app.modals.addToPlaylist = true; }, async addSelectedToNewPlaylist() { let self = this; let pl_items = []; for (let i = 0; i < self.selectedMediaItems.length; i++) { if (self.selectedMediaItems[i].kind == "song" || self.selectedMediaItems[i].kind == "songs") { self.selectedMediaItems[i].kind = "songs"; pl_items.push({ id: self.selectedMediaItems[i].id, type: self.selectedMediaItems[i].kind, }); } else if ((self.selectedMediaItems[i].kind == "album" || self.selectedMediaItems[i].kind == "albums") && self.selectedMediaItems[i].isLibrary != true) { self.selectedMediaItems[i].kind = "albums"; let res = await self.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/albums/${self.selectedMediaItems[i].id}/tracks`); let ids = res.data.data.map(function (i) { return { id: i.id, type: i.type }; }); pl_items = pl_items.concat(ids); } else if (self.selectedMediaItems[i].kind == "library-song" || self.selectedMediaItems[i].kind == "library-songs") { self.selectedMediaItems[i].kind = "library-songs"; pl_items.push({ id: self.selectedMediaItems[i].id, type: self.selectedMediaItems[i].kind, }); } else if (self.selectedMediaItems[i].kind == "library-album" || self.selectedMediaItems[i].kind == "library-albums" || (self.selectedMediaItems[i].kind == "album" && self.selectedMediaItems[i].isLibrary == true)) { self.selectedMediaItems[i].kind = "library-albums"; let res = await self.mk.api.v3.music(`/v1/me/library/albums/${self.selectedMediaItems[i].id}/tracks`); let ids = res.data.data.map(function (i) { return { id: i.id, type: i.type }; }); pl_items = pl_items.concat(ids); } else { pl_items.push({ id: self.selectedMediaItems[i].id, type: self.selectedMediaItems[i].kind, }); } } this.modals.addToPlaylist = false; app.newPlaylist(app.getLz("term.newPlaylist"), pl_items); }, async addSelectedToPlaylist(playlist_id) { let self = this; let pl_items = []; for (let i = 0; i < self.selectedMediaItems.length; i++) { if (self.selectedMediaItems[i].kind == "song" || self.selectedMediaItems[i].kind == "songs") { self.selectedMediaItems[i].kind = "songs"; pl_items.push({ id: self.selectedMediaItems[i].id, type: self.selectedMediaItems[i].kind, }); } else if ((self.selectedMediaItems[i].kind == "album" || self.selectedMediaItems[i].kind == "albums") && self.selectedMediaItems[i].isLibrary != true) { self.selectedMediaItems[i].kind = "albums"; let res = await self.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/albums/${self.selectedMediaItems[i].id}/tracks`); let ids = res.data.data.map(function (i) { return { id: i.id, type: i.type }; }); pl_items = pl_items.concat(ids); } else if (self.selectedMediaItems[i].kind == "library-song" || self.selectedMediaItems[i].kind == "library-songs") { self.selectedMediaItems[i].kind = "library-songs"; pl_items.push({ id: self.selectedMediaItems[i].id, type: self.selectedMediaItems[i].kind, }); } else if (self.selectedMediaItems[i].kind == "library-album" || self.selectedMediaItems[i].kind == "library-albums" || (self.selectedMediaItems[i].kind == "album" && self.selectedMediaItems[i].isLibrary == true)) { self.selectedMediaItems[i].kind = "library-albums"; let res = await self.mk.api.v3.music(`/v1/me/library/albums/${self.selectedMediaItems[i].id}/tracks`); let ids = res.data.data.map(function (i) { return { id: i.id, type: i.type }; }); pl_items = pl_items.concat(ids); } else { pl_items.push({ id: self.selectedMediaItems[i].id, type: self.selectedMediaItems[i].kind, }); } } this.modals.addToPlaylist = false; await app.mk.api.v3 .music( `/v1/me/library/playlists/${playlist_id}/tracks`, {}, { fetchOptions: { method: "POST", body: JSON.stringify({ data: pl_items, }), }, } ) .then(() => { if (this.page == "playlist_" + this.showingPlaylist.id) { this.getPlaylistFromID(this.showingPlaylist.id, true); } }); }, async init() { let self = this; if (!localStorage.getItem("seenOOBE")) { localStorage.setItem("seenOOBE", 1); } if (this.cfg.visual.styles.length != 0) { await this.reloadStyles(); } if (this.platform == "darwin") { this.chrome.windowControlPosition = "left"; } if (this.cfg.visual.nativeTitleBar) { this.chrome.nativeControls = true; } this.setLz(this.cfg.general.language); this.setLzManual(); clearTimeout(this.hangtimer); this.mk = MusicKit.getInstance(); let needsReload = typeof localStorage["music.ampwebplay.media-user-token"] == "undefined"; this.mk.authorize().then(() => { self.mkIsReady = true; if (needsReload) { document.location.reload(); } }); this.$forceUpdate(); if (this.isDev) { this.mk.privateEnabled = true; // Hide UserInfo if Dev mode } else { // Get Hide User from Settings this.chrome.hideUserInfo = !this.cfg.visual.showuserinfo; this.mk.privateEnabled = this.cfg.general.privateEnabled; } if (this.cfg.visual.hw_acceleration == "disabled") { document.body.classList.add("no-gpu"); } this.mk._services.timing.mode = 0; this.platform = this.cfg.main.PLATFORM; this.mklang = await this.MKJSLang(); this.mk._playbackController._storekit.overrideRestrictEnabled(false); try { // Set profile name this.chrome.userinfo = (await app.mk.api.v3.music(`/v1/me/social-profile`)).data.data[0]; // check if this.chrome.userinfo.attributes.artwork exists if (this.chrome.userinfo.attributes.artwork && !this.chrome.hideUserInfo) { document.documentElement.style.setProperty("--cvar-userprofileimg", `url("${this.getMediaItemArtwork(this.chrome.userinfo.attributes.artwork.url)}")`); } } catch (err) {} // Used to get a scale factor for the window for CSS scaling window.addEventListener("resize", (e) => this.setWindowScaleFactor()); this.setWindowScaleFactor(); this.mk._bag.features["seamless-audio-transitions"] = this.cfg.audio.seamless_audio; this.mk._bag.features["broadcast-radio"] = true; this.mk._services.apiManager.store.storekit._restrictedEnabled = false; // API Fallback if (!this.chrome.userinfo) { this.chrome.userinfo = { id: "", attributes: { name: "Cider User", handle: "CiderUser", artwork: { url: "./assets/logocut.png" }, }, }; } MusicKitInterop.init(); // Set the volume // Check the value of this.cfg.audio.muted if (!this.cfg.audio.muted) { // Set the mk.volume to the last stored volume data this.mk.volume = this.cfg.audio.volume; } else if (this.cfg.audio.muted) { // Set mk.volume to -1 (setting to 0 wont work, so temp solution setting to -1) this.mk.volume = -1; } // load cached library let librarySongs = await CiderCache.getCache("library-songs"); let libraryAlbums = await CiderCache.getCache("library-albums"); if (librarySongs) { this.library.songs.listing = librarySongs; this.library.songs.displayListing = this.library.songs.listing; } if (libraryAlbums) { this.library.albums.listing = libraryAlbums; this.library.albums.displayListing = this.library.albums.listing; } if (typeof MusicKit.PlaybackBitrate[app.cfg.audio.quality] !== "string") { app.mk.bitrate = MusicKit.PlaybackBitrate[app.cfg.audio.quality]; } else { app.mk.bitrate = 256; app.cfg.audio.quality = "HIGH"; } switch (this.cfg.general.resumeOnStartupBehavior) { default: case "local": // load last played track try { let lastItem = window.localStorage.getItem("currentTrack"); let time = window.localStorage.getItem("currentTime"); let queue = window.localStorage.getItem("currentQueue"); app.mk.queue.position = 0; // Reset queue position. if (lastItem != null) { lastItem = JSON.parse(lastItem); let kind = lastItem.attributes.playParams.kind; let truekind = !kind.endsWith("s") ? kind + "s" : kind; app.mk.setQueue({ [truekind]: [lastItem.attributes.playParams.id], parameters: { l: app.mklang }, }); app.mk.mute(); setTimeout(() => { app.mk.play().then(() => { app.mk.pause().then(() => { if (time != null) { app.mk.seekToTime(time); } app.mk.unmute(); if (queue != null) { queue = JSON.parse(queue); if (queue && queue.length > 0) { let ids = queue.map((e) => (e.playParams ? e.playParams.id : e.item.attributes.playParams ? e.item.attributes.playParams.id : "")); let i = 0; if (ids.length > 0) { for (let id of ids) { if (!(i == 0 && ids[0] == lastItem.attributes.playParams.id)) { try { app.mk.playLater({ songs: [id] }); } catch (err) {} } i++; } } } } }); }); }, 1500); } } catch (e) { console.log(e); } break; case "history": let history = await app.mk.api.v3.music(`/v1/me/recent/played/tracks`, { l: app.mklang }); if (history.data.data.length > 0) { let lastItem = history.data.data[0]; let kind = lastItem.attributes.playParams.kind; let truekind = !kind.endsWith("s") ? kind + "s" : kind; app.mk.setQueue({ [truekind]: [lastItem.attributes.playParams.id], parameters: { l: app.mklang }, }); app.mk.mute(); setTimeout(() => { app.mk.play().then(() => { app.mk.pause().then(() => { app.mk.unmute(); }); }); }, 1500); } break; case "disabled": break; } MusicKit.getInstance().videoContainerElement = document.getElementById("apple-music-video-player"); ipcRenderer.on("setStoreValue", (e, key, value) => { app.cfg[key] = value; }); ipcRenderer.on("theme-update", async (event, arg) => { await less.refresh(true, true, true); self.setTheme(self.cfg.visual.theme, true); if (app.cfg.visual.styles.length != 0) { app.reloadStyles(); } }); /** * DiscordRPC Reload Return Event * @author @coredev-uk */ ipcRenderer.on("rpcReloaded", (e, user) => { if (user.username) { app.notyf.success( app.stringTemplateParser(app.getLz("settings.option.connectivity.discordRPC.reconnectedToUser"), { user: `${user.username}#${user.discriminator}`, userid: user.id, }) ); } }); ipcRenderer.on("getUpdatedLocalList", (event, data) => { // console.log("cider-local", data); this.library.localsongs = data; }); ipcRenderer.on("window-state-changed", (event, data) => { this.chrome.windowState = data; }); ipcRenderer.on("SoundCheckTag", (event, tag) => { // let replaygain = self.parseSCTagToRG(tag) try { if (app.mk.nowPlayingItem.type !== "song") { CiderAudio.audioNodes.gainNode.gain.value = 0.70794578438; } else { let soundcheck = tag.split(" "); let numbers = []; for (let item of soundcheck) { numbers.push(parseInt(item, 16)); } numbers.shift(); let peak = Math.max(numbers[6], numbers[7]) / 32768.0; let gain = Math.pow(10, (-1.7 - Math.log10(peak) * 20) / 20); // EBU R 128 Compliant console.debug(`[Cider][MaikiwiSoundCheck] Peak Gain: '${(Math.log10(peak) * 20).toFixed(2)}' dB | Adjusting '${(Math.log10(gain) * 20).toFixed(2)}' dB`); try { //CiderAudio.audioNodes.gainNode.gain.value = (Math.min(Math.pow(10, (replaygain.gain / 20)), (1 / replaygain.peak))) CiderAudio.audioNodes.gainNode.gain.value = gain; CiderAudio.hierarchical_loading(); } catch (e) {} } } catch (e) { try { ipcRenderer.send("SoundCheckTag", event, tag); } catch (e) { try { ipcRenderer.send("SoundCheckTag", event, tag); } catch (e) { console.log("[Cider][MaikiwiSoundCheck] Error [Gave up after 3 consecutive attempts]: " + e); } } } // brute force until it works }); ipcRenderer.on("play", function (_event, mode, id) { if (mode !== "url") { self.mk.setQueue({ [mode]: id, parameters: { l: self.mklang } }).then(() => { app.mk.play(); }); } else { app.openAppleMusicURL(id); } }); this.mk.addEventListener(MusicKit.Events.playbackStateDidChange, (event) => { ipcRenderer.send("wsapi-updatePlaybackState", wsapi.getAttributes()); document.body.setAttribute("playback-state", event.state == 2 ? "playing" : "paused"); }); this.mk.addEventListener(MusicKit.Events.playbackTimeDidChange, (a) => { // self.lyriccurrenttime = self.mk.currentPlaybackTime - app.lyricOffset this.currentSongInfo = a; self.playerLCD.playbackDuration = self.mk.currentPlaybackTime; // wsapi ipcRenderer.send("wsapi-updatePlaybackState", wsapi.getAttributes()); }); this.mk.addEventListener(MusicKit.Events.queueItemsDidChange, () => { if (self.$refs.queue) { setTimeout(() => { self.$refs.queue.updateQueue(); }, 100); } }); // Used for Live Radio stations to set Metadata this.mk.addEventListener(MusicKit.Events.timedMetadataDidChange, (e) => { app.mk.nowPlayingItem.attributes.name = e.title; app.mk.nowPlayingItem.attributes.artistName = e.performer; app.mk.nowPlayingItem.attributes.albumName = e.album; if (e.links[1]) { app.currentArtUrl = e.links[1].url; app.currentArtUrlRaw = e.links[1].url; } else { app.currentArtUrl = e.links[0].url; app.currentArtUrlRaw = e.links[0].url; } app.mk.nowPlayingItem._songId = e._adamId ? e._adamId : -1; app.mk.nowPlayingItem.id = e._adamId ? e._adamId : -1; }); this.mk.addEventListener(MusicKit.Events.nowPlayingItemDidChange, (a) => { if (self.$refs.queue) { self.$refs.queue.updateQueue(); } this.currentSongInfo = a; if (this.currentSongInfo === null || this.currentSongInfo === undefined) { return; } // EVIL EMPTY OBJECTS BE GONE try { if ((MusicKit.getInstance().nowPlayingItem["type"] ?? "").includes("ideo")) { setTimeout(() => { this.MVsource = CiderAudio.context.createMediaElementSource(document.querySelector("div#apple-music-video-player > video")); this.MVsource.connect(CiderAudio.audioNodes.intelliGainComp); }, 300); } else { this.MVsource.disconnect(); this.MVsource = null; } } catch (e) { // console.log(e); } let localFiles = false; try { if (app.mk.nowPlayingItem.flavor.includes("64") && app.mk.nowPlayingItem.flavor.includes(":")) { localStorage.setItem("playingBitrate", "64"); } else if (app.mk.nowPlayingItem.flavor.includes("256") && app.mk.nowPlayingItem.flavor.includes(":")) { localStorage.setItem("playingBitrate", "256"); } else { localFiles = true; localStorage.setItem("playingBitrate", app.mk.nowPlayingItem.flavor); } } catch (e) { localFiles = true; try { localStorage.setItem("playingBitrate", app.mk.nowPlayingItem.flavor); } catch (e) {} } if (app.cfg.audio.normalization === false) { CiderAudio.hierarchical_loading(); } // Just Reload for Adaptive CAP if norm is off else { // get unencrypted audio previews to get SoundCheck's normalization tag try { let previewURL = null; try { previewURL = app.mk.nowPlayingItem.previewURL; } catch (e) { if (e instanceof TypeError === false) { console.debug("[Cider][MaikiwiSoundCheck] normalizer function err: " + e); } else { if (localFiles === true) { CiderAudio.audioNodes.gainNode.gain.value = 0.822242649947; } } } if (previewURL == null && (app.mk.nowPlayingItem?._songId ?? app.mk.nowPlayingItem["songId"] ?? app.mk.nowPlayingItem.relationships.catalog.data[0].id) != -1) { app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/songs/${app.mk.nowPlayingItem?._songId ?? app.mk.nowPlayingItem["songId"] ?? app.mk.nowPlayingItem.relationships.catalog.data[0].id}`).then((response) => { try { previewURL = response.data.data[0].attributes.previews[0].url; } catch (e) { if (e instanceof TypeError === false) { console.debug("[Cider][MaikiwiSoundCheck] normalizer function err: " + e); } else { if (localFiles === true) { CiderAudio.audioNodes.gainNode.gain.value = 0.822242649947; } } } if (previewURL) { console.debug("[Cider][MaikiwiSoundCheck] previewURL response.data.data[0].attributes.previews[0].url: " + previewURL); ipcRenderer.send("getPreviewURL", previewURL); } else { if (localFiles === true) { CiderAudio.audioNodes.gainNode.gain.value = 0.822242649947; } } }); } else { if (previewURL) { console.debug("[Cider][MaikiwiSoundCheck] previewURL in app.mk.nowPlayingItem.previewURL: " + previewURL); ipcRenderer.send("getPreviewURL", previewURL); } } } catch (e) { if (e instanceof TypeError === false) { console.debug("[Cider][MaikiwiSoundCheck] normalizer function err: " + e); } else { if (localFiles === true) { CiderAudio.audioNodes.gainNode.gain.value = 0.822242649947; } } } } try { a = a.item.attributes; } catch (_) {} let type = self.mk.nowPlayingItem != null ? self.mk.nowPlayingItem["type"] ?? "" : ""; if (type.includes("musicVideo") || type.includes("uploadedVideo") || type.includes("music-movie") || (self.mk.nowPlayingItem?.type == "radioStation") & (self.mk.nowPlayingItem?.attributes?.mediaKind == "video")) { document.getElementById("apple-music-video-container").style.display = "block"; document.body.setAttribute("video-playing", "true"); // app.chrome.topChromeVisible = false } else { document.body.removeAttribute("video-playing"); document.getElementById("apple-music-video-container").style.display = "none"; // app.chrome.topChromeVisible = true } self.chrome.artworkReady = false; self.lyrics = []; self.richlyrics = []; app.getCurrentArtURL(); // app.getNowPlayingArtwork(42); app.getNowPlayingArtworkBG(32); app.loadLyrics(); setTimeout(() => { let i = document.querySelector("#apple-music-player")?.src ?? ""; if (i.endsWith(".m3u8") || i.endsWith(".m3u")) { this._playRadioStream(i); } }, 1500); }); this.mk.addEventListener(MusicKit.Events.playbackVolumeDidChange, (_a) => { this.cfg.audio.volume = this.mk.volume; }); this.refreshPlaylists(this.isDev); document.body.removeAttribute("loading"); if (window.location.hash != "") { this.appRoute(window.location.hash); } if (this.page != "home") { this.resumeTabs(); } this.mediaKeyFixes(); setTimeout(() => { this.getSocialBadges(); this.getBrowsePage(); this.$forceUpdate(); }, 500); document.querySelector("#apple-music-video-player-controls").addEventListener("mousemove", () => { this.showFoo(".music-player-info", 2000); }); ipcRenderer.invoke("renderer-ready", true); document.querySelector("#LOADER").remove(); if (this.cfg.general.themeUpdateNotification && !this.isDev) { this.checkForThemeUpdates(); } ipcRenderer.invoke("scanLibrary"); }, setWindowScaleFactor() { let scale = (((window.devicePixelRatio * window.innerWidth) / 1280) * window.innerHeight) / 720; let desiredScale = clamp(parseFloat(app.cfg.visual.maxElementScale == -1 ? 1.5 : app.cfg.visual.maxElementScale), 1, 1.5); app.$store.state.windowRelativeScale = scale; if (scale <= 1) { scale = 1; } else if (scale >= desiredScale) { scale = desiredScale; } document.documentElement.style.setProperty("--windowRelativeScale", scale); }, showFoo(querySelector, time) { clearTimeout(this.idleTimer); if (this.idleState == true) { document.querySelector(querySelector).classList.remove("inactive"); } this.idleState = false; this.idleTimer = setTimeout(() => { document.querySelector(querySelector).classList.add("inactive"); this.idleState = true; }, time); }, setContentScrollPos(scroll) { this.chrome.contentScrollPosY = scroll.target.scrollTop; }, async checkForThemeUpdates() { let self = this; const themes = ipcRenderer.sendSync("get-themes"); await asyncForEach(themes, async (theme) => { if (theme.commit != "") { await fetch(`https://api.github.com/repos/${theme.github_repo}/commits`) .then((res) => res.json()) .then((res) => { if (res[0].sha != theme.commit) { const notify = notyf.open({ className: "notyf-info", type: "info", message: `[Themes] ${theme.name} has an update available.`, }); notify.on("click", () => { app.openSettingsPage("github-themes"); notyf.dismiss(notify); }); } }); } }); }, async setTheme(theme = "", onlyPrefs = false) { console.debug(theme); if (this.cfg.visual.theme == "") { this.cfg.visual.theme = "default.less"; } if (theme == "") { theme = this.cfg.visual.theme; } else { this.cfg.visual.theme = ""; this.cfg.visual.theme = theme; } const info = {}; try { const infoResponse = await fetch("themes/" + app.cfg.visual.theme.replace("index.less", "theme.json")); this.chrome.appliedTheme.info = await infoResponse.json(); } catch (e) { e = null; console.warn("failed to get theme.json"); this.chrome.appliedTheme.info = {}; } if (!onlyPrefs) { document.querySelector("#userTheme").href = `themes/${this.cfg.visual.theme}`; document.querySelectorAll(`[id*='less']`).forEach((el) => { el.remove(); }); await less.refresh(); } }, async reloadStyles() { const styles = this.cfg.visual.styles; document.querySelectorAll(`[id*='less']`).forEach((el) => { if (el.id != "less:style") { el.remove(); } }); this.chrome.appliedTheme.info = {}; await asyncForEach(styles, async (style) => { let styleEl = document.createElement("link"); styleEl.id = `less-${style.replace(".less", "")}`; styleEl.rel = "stylesheet/less"; styleEl.href = `themes/${style}`; styleEl.type = "text/css"; document.head.appendChild(styleEl); try { let infoResponse = await fetch("themes/" + style.replace("index.less", "theme.json")); this.chrome.appliedTheme.info = Object.assign(this.chrome.appliedTheme.info, await infoResponse.json()); } catch (e) { e = null; console.warn("failed to get theme.json"); } }); less.registerStylesheetsImmediately(); await less.refresh(true, true, true); this.$forceUpdate(); return; }, macOSEmu() { this.chrome.forceDirectives["macosemu"] = { value: true, }; this.chrome.windowControlPosition = "left"; }, getThemeDirective(directive = "") { let directives = {}; if (typeof this.chrome.appliedTheme.info.directives == "object") { directives = this.chrome.appliedTheme.info.directives; } directives = Object.assign(directives, this.chrome.forceDirectives); if (directives[directive]) { return directives[directive].value; } else if (this.cfg.visual.directives[directive]) { return this.cfg.visual.directives[directive]; } else { return false; } }, unauthorize() { this.confirm(app.getLz("term.confirmLogout"), function (result) { if (result) { app.mk.unauthorize(); document.location.reload(); } }); }, getAppClasses() { let classes = {}; switch (this.getThemeDirective("forceUI") ?? "none") { case "compact": classes.compact = true; break; case "standard": classes.compact = false; break; default: if (this.cfg.advanced.experiments.includes("compactui")) { classes.compact = true; } break; } if (this.cfg.visual.window_background_style == "none") { classes.simplebg = true; } if (this.platform !== "darwin") { switch (parseInt(this.cfg.visual.windowControlPosition)) { default: case 0: this.chrome.windowControlPosition = "right"; this.chrome.forceDirectives["macosemu"] = { value: false, }; break; case 1: this.chrome.windowControlPosition = "left"; this.chrome.forceDirectives["macosemu"] = { value: true, }; break; } } if (this.getThemeDirective("windowLayout") == "twopanel") { classes.twopanel = true; } if (this.getThemeDirective("appNavigation") == "seperate") { classes.navbar = true; } if (this.getThemeDirective("macosemu") == true) { classes.macosemu = true; } return classes; }, invokeDrawer(panel) { if (this.drawer.panel == panel && this.drawer.open) { if (panel == "lyrics") { this.lyricon = false; } this.drawer.panel = ""; this.drawer.open = false; } else { if (panel == "lyrics") { this.lyricon = true; } else { this.lyricon = false; } this.drawer.open = true; this.drawer.panel = panel; } }, select_removeMediaItem(id) { this.selectedMediaItems .filter((item) => item.guid == id) .forEach((item) => { this.selectedMediaItems.splice(this.selectedMediaItems.indexOf(item), 1); }); }, select_hasMediaItem(id) { let found = this.selectedMediaItems.find((item) => item.guid == id); if (found) { return true; } else { return false; } }, select_selectMediaItem(id, kind, index, guid, library) { if (!this.select_hasMediaItem(guid)) { this.selectedMediaItems.push({ id: id, kind: kind, index: index, guid: guid, isLibrary: library, }); } }, getPlaylistFolderChildren(id) { return this.playlists.listing.filter((playlist) => { if (playlist.parent == id) { return playlist; } }); }, async syncFavorites() { const notify = notyf.open({ className: "notyf-info", type: "info", message: `[${app.getLz("home.syncFavorites")}] ${app.getLz("home.syncFavorites.gettingArtists")}`, }); const results = await MusicKitTools.v3Continuous({ href: "/v1/me/library/artists", options: { include: ["catalog"], "fields[artists]": ["inFavorites"], }, }); let favs = []; // for each result results.forEach((result) => { try { if (result.relationships?.catalog?.data[0]?.attributes?.inFavorites) { if (!favs.includes(result.relationships?.catalog?.data[0].id)) { favs.push(result.relationships?.catalog?.data[0].id); } } } catch (e) { e = null; } }); notyf.success(`[${app.getLz("home.syncFavorites")}] ${app.getLz("action.done")}`); app.cfg.home.followedArtists = favs; return favs; }, async setArtistFavorite(id, val = true) { if (val) { if (!app.cfg.home.followedArtists.includes(id)) { app.cfg.home.followedArtists.push(id); } await app.mk.api.v3.music( `/v1/me/favorites`, { "art[url]": "f", "ids[artists]": app.artistPage.data.id, l: app.mklang, platform: "web", }, { fetchOptions: { method: "POST", }, } ); } else { if (app.cfg.home.followedArtists.includes(id)) { app.cfg.home.followedArtists.splice(app.cfg.home.followedArtists.indexOf(id), 1); } await app.mk.api.v3.music( `/v1/me/favorites`, { "art[url]": "f", "ids[artists]": app.artistPage.data.id, l: app.mklang, platform: "web", }, { fetchOptions: { method: "DELETE", }, } ); } }, async refreshPlaylists(localOnly = false, useCachedPlaylists = true) { let self = this; let trackMap = this.cfg.advanced.playlistTrackMapping; let newListing = []; let trackMapping = {}; if (useCachedPlaylists) { const cachedPlaylist = await CiderCache.getCache("library-playlists"); const cachedTrackMapping = await CiderCache.getCache("library-playlists-tracks"); if (cachedPlaylist) { console.debug("using cached playlists"); this.playlists.listing = cachedPlaylist; self.sortPlaylists(); } else { console.debug("playlist has no cache"); } if (cachedTrackMapping) { console.debug("using cached track mapping"); this.playlists.trackMapping = cachedTrackMapping; } if (localOnly) { return; } } this.library.backgroundNotification.message = "Building playlist cache..."; this.library.backgroundNotification.show = true; async function deepScan(parent = "p.playlistsroot") { console.debug(`scanning ${parent}`); // const playlistData = await app.mk.api.v3.music(`/v1/me/library/playlist-folders/${parent}/children/`) const playlistData = await MusicKitTools.v3Continuous({ href: `/v1/me/library/playlist-folders/${parent}/children/`, }); console.log(playlistData); await asyncForEach(playlistData, async (playlist) => { playlist.parent = parent; if (playlist.type != "library-playlist-folders" && typeof playlist.attributes.playParams["versionHash"] != "undefined") { playlist.parent = "p.applemusic"; } playlist.children = []; playlist.tracks = []; try { if (trackMap) { let tracks = await app.mk.api.v3.music(playlist.href + "/tracks").catch((e) => { // no tracks e = null; }); tracks.data.data.forEach((track) => { if (!trackMapping[track.id]) { trackMapping[track.id] = []; } trackMapping[track.id].push(playlist.id); if (typeof track.attributes.playParams.catalogId == "string") { if (!trackMapping[track.attributes.playParams.catalogId]) { trackMapping[track.attributes.playParams.catalogId] = []; } trackMapping[track.attributes.playParams.catalogId].push(playlist.id); } }); } } catch (e) {} if (playlist.type == "library-playlist-folders") { try { await deepScan(playlist.id).catch((e) => {}); } catch (e) {} } newListing.push(playlist); }); } await deepScan(); this.library.backgroundNotification.show = false; this.playlists.listing = newListing; self.sortPlaylists(); if (trackMap) { CiderCache.putCache("library-playlists-tracks", trackMapping); this.playlists.trackMapping = trackMapping; } CiderCache.putCache("library-playlists", newListing); }, sortPlaylists() { this.playlists.listing.sort((a, b) => { if (a.type === "library-playlist-folders" && b.type !== "library-playlist-folders") { return -1; } else if (a.type !== "library-playlist-folders" && b.type === "library-playlist-folders") { return 1; } else { return 0; } }); }, playlistHeaderContextMenu(event) { let menu = { items: [ { name: app.getLz("term.createNewPlaylist"), action: () => { this.newPlaylist(); }, }, { name: app.getLz("term.createNewPlaylistFolder"), action: () => { this.newPlaylistFolder(); }, }, { name: app.getLz("action.refresh"), action: () => { this.refreshPlaylists(); }, }, ], }; this.showMenuPanel(menu, event); }, async editPlaylistFolder(id, name = app.getLz("term.newPlaylist")) { let self = this; this.mk.api.v3 .music( `/v1/me/library/playlist-folders/${id}`, {}, { fetchOptions: { method: "PATCH", body: JSON.stringify({ attributes: { name: name }, }), }, } ) .then((res) => { self.refreshPlaylists(false, false); }); }, async editPlaylist(id, name = app.getLz("term.newPlaylist")) { let self = this; this.mk.api.v3 .music( `/v1/me/library/playlists/${id}`, {}, { fetchOptions: { method: "PATCH", body: JSON.stringify({ attributes: { name: name }, }), }, } ) .then((res) => { self.refreshPlaylists(false, false); }); }, async editPlaylistDescription(id, name = app.getLz("term.newPlaylist")) { let self = this; this.mk.api.v3 .music( `/v1/me/library/playlists/${id}`, {}, { fetchOptions: { method: "PATCH", body: JSON.stringify({ attributes: { description: name }, }), }, } ) .then((res) => { self.refreshPlaylists(false, false); }); }, copyToClipboard(str) { // if (navigator.userAgent.includes('Darwin') || navigator.appVersion.indexOf("Mac") != -1) { // this.darwinShare(str) // } else { notyf.success(app.getLz("term.share.success")); navigator.clipboard.writeText(str).then((r) => console.debug("Copied to clipboard.")); // } }, newPlaylist(name = app.getLz("term.newPlaylist"), tracks = []) { let self = this; let request = { name: name, }; if (tracks.length > 0) { request.tracks = tracks; } app.mk.api.v3 .music( `/v1/me/library/playlists`, {}, { fetchOptions: { method: "POST", body: JSON.stringify({ attributes: { name: name }, relationships: { tracks: { data: tracks }, }, }), }, } ) .then((res) => { res = res.data.data[0]; console.debug(res); self.appRoute(`playlist_` + res.id); self.showingPlaylist = []; self.getPlaylistFromID(app.page.substring(9), true); self.playlists.listing.push({ id: res.id, attributes: { name: name, }, parent: "p.playlistsroot", }); self.sortPlaylists(); setTimeout(() => { app.refreshPlaylists(false, false); }, 8000); }); }, deletePlaylist(id) { let self = this; this.confirm(app.getLz("term.deletePlaylist"), (ok) => { if (ok) { app.mk.api.v3 .music( `/v1/me/library/playlists/${id}`, {}, { fetchOptions: { method: "DELETE", }, } ) .then((res) => { // remove this playlist from playlists.listing if it exists let found = self.playlists.listing.find((item) => item.id == id); if (found) { self.playlists.listing.splice(self.playlists.listing.indexOf(found), 1); } setTimeout(() => { app.refreshPlaylists(false, false); }, 8000); }); } }); }, /** * @param {string} url, href for the initial request * @memberof app */ async showRoom(url) { let self = this; const response = await this.mk.api.v3.music(url); let room = response.data.data[0]; this.showCollection(room.relationships.contents, room.attributes.title); }, async showCollection(response, title, type, requestBody = {}) { let self = this; console.debug(response); this.collectionList.requestBody = {}; this.collectionList.response = response; this.collectionList.title = title; this.collectionList.type = type; this.collectionList.requestBody = requestBody; app.appRoute("collection-list"); }, async showArtistView(artist, title, view) { let response = (await app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/artists/${artist}/view/${view}?l=${this.mklang}`, {}, { includeResponseMeta: !0 })).data; console.debug(response); await this.showCollection(response, title, "artists"); }, async showRecordLabelView(label, title, view) { let response = (await app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/record-labels/${label}/view/${view}?l=${this.mklang}`)).data; await this.showCollection(response, title, "record-labels"); }, async showSearchView(term, group, title) { let requestBody = { platform: "web", groups: group, types: "activities,albums,apple-curators,artists,curators,editorial-items,music-movies,music-videos,playlists,songs,stations,tv-episodes,uploaded-videos,record-labels", limit: 25, relate: { editorialItems: ["contents"], }, include: { albums: ["artists"], songs: ["artists"], "music-videos": ["artists"], }, extend: "artistUrl", fields: { artists: "url,name,artwork,hero", albums: "artistName,artistUrl,artwork,contentRating,editorialArtwork,name,playParams,releaseDate,url", }, with: "serverBubbles,lyricHighlights", art: { url: "cf", }, omit: { resource: ["autos"], }, l: this.mklang, }; let response = await app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/search?term=${term}`, requestBody, { includeResponseMeta: !0, }); console.debug("searchres", response); let responseFormat = { data: response.data.results[group].data, next: response.data.results[group].next, groups: group, }; await this.showCollection(responseFormat, title, "search", requestBody); }, async getPlaylistContinuous(response, transient = false) { response = response.data.data[0]; let self = this; let playlistId = response.id; this.playlists.loadingState = !transient ? 0 : 1; this.showingPlaylist = response; if (!response.relationships?.tracks?.next) { this.playlists.loadingState = 1; return; } function getPlaylistTracks(next) { app.apiCall(app.musicBaseUrl + next, (res) => { if (self.showingPlaylist.id != playlistId) { return; } self.showingPlaylist.relationships.tracks.data = self.showingPlaylist.relationships.tracks.data.concat(res.data); if (res.next) { getPlaylistTracks(res.next); } else { self.playlists.loadingState = 1; } }); } getPlaylistTracks(response.relationships.tracks.next); }, async getPlaylistFromID(id, transient = false) { let self = this; const params = { include: "tracks", platform: "web", "include[library-playlists]": "catalog,tracks", "fields[playlists]": "curatorName,playlistType,name,artwork,url,playParams", "include[library-songs]": "catalog,artists,albums,playParams,name,artwork,url", "fields[catalog]": "artistUrl,albumUrl,url", "fields[songs]": "artistUrl,albumUrl,playParams,name,artwork,url,artistName,albumName,durationInMillis", l: this.mklang, }; if (!transient) { this.playlists.loadingState = 0; } app.mk.api.v3 .music(`/v1/me/library/playlists/${id}`, params) .then((res) => { self.getPlaylistContinuous(res, transient); }) .catch((e) => { console.debug(e); try { app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/playlists/${id}`, params).then((res) => { self.getPlaylistContinuous(res, transient); }); } catch (err) { console.debug(err); } }); }, async getArtistFromID(id) { this.page = ""; const artistData = await this.mkapi( "artists", false, id, { views: "featured-release,full-albums,appears-on-albums,featured-albums,featured-on-albums,singles,compilation-albums,live-albums,latest-release,top-music-videos,similar-artists,top-songs,playlists,more-to-hear,more-to-see", extend: "centeredFullscreenBackground,artistBio,bornOrFormed,editorialArtwork,editorialVideo,isGroup,origin,hero", "extend[playlists]": "trackCount", "include[songs]": "albums", "fields[albums]": "artistName,artistUrl,artwork,contentRating,editorialArtwork,editorialVideo,name,playParams,releaseDate,url,trackCount", "limit[artists:top-songs]": 20, "art[url]": "f", l: this.mklang, }, { includeResponseMeta: !0 } ); console.debug(artistData.data.data[0]); this.artistPage.data = artistData.data.data[0]; this.page = "artist-page"; }, progressBarStyle() { let val = this.playerLCD.playbackDuration; if (this.playerLCD.desiredDuration > 0) { val = this.playerLCD.desiredDuration; } let min = 0; let max = this.mk.currentPlaybackDuration; let value = ((val - min) / (max - min)) * 100; return { background: "linear-gradient(to right, var(--songProgressColor) 0%, var(--songProgressColor) " + value + "%, var(--songProgressBackground) " + value + "%, var(--songProgressBackground) 100%)", }; }, async getRecursive(response) { // if response has a .next() property run it and keep running until .next is null or undefined // and then return the response concatenated with the results of the next() call function executeRequest() { if (response.next) { return response.next().then(executeRequest); } else { return response; } } return executeRequest(); }, async getRecursive2(response, sendTo) { let returnData = { data: [], meta: {}, }; if (response.next) { console.debug("has next"); returnData.data.concat(response.data); returnData.meta = response.meta; return await this.getRecursive(await response.next()); } else { console.debug("no next"); returnData.data.concat(response.data); return returnData; } }, async getSearchHints() { if (this.search.term == "") { this.search.hints = []; return; } let hints = await (await app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/search/hints?term=${this.search.term}`)).data.results; this.search.hints = hints ? hints.terms : []; }, getSongProgress() { if (this.playerLCD.userInteraction) { return this.playerLCD.desiredDuration; } else { return this.playerLCD.playbackDuration; } }, /** * Converts seconds to dd:hh:mm:ss / Days:Hours:Minutes:Seconds * @param {number} seconds * @param {string} format (short, long) * @returns {string} * @author Core#1034 * @memberOf app */ convertTime(seconds, format = "short") { if (isNaN(seconds) || seconds === Infinity) { seconds = 0; } const datetime = new Date(seconds * 1000); if (format === "long") { const d = Math.floor(seconds / (3600 * 24)); const h = Math.floor((seconds % (3600 * 24)) / 3600); const m = Math.floor((seconds % 3600) / 60); const s = Math.floor(seconds % 60); const dDisplay = d > 0 ? `${d} ${app.getLz("term.time.day", { count: d })}` : ""; const hDisplay = h > 0 ? `${h} ${app.getLz("term.time.hour", { count: h })}` : ""; const mDisplay = m > 0 ? `${m} ${app.getLz("term.time.minute", { count: m })}` : ""; return dDisplay + (dDisplay && hDisplay ? ", " : "") + hDisplay + (hDisplay && mDisplay ? ", " : "") + mDisplay; } else { return MusicKit.formatMediaTime(seconds); } }, hashCode(str) { let hash = 0, i, chr; if (str.length === 0) return hash; for (i = 0; i < str.length; i++) { chr = str.charCodeAt(i); hash = (hash << 5) - hash + chr; hash |= 0; // Convert to 32bit integer } return hash; }, appRoute(route) { if (route == "" || route == "#" || route == "/") { return; } route = route.replace(/#/g, ""); if (app.cfg.general.resumeTabs.tab == "dynamic") { if (route == "home" || route == "listen_now" || route == "browse" || route == "radio" || route == "library-songs" || route == "library-albums" || route == "library-artists" || route == "library-videos" || route == "podcasts") { app.cfg.general.resumeTabs.dynamicData = route; } else { app.cfg.general.resumeTabs.dynamicData = "home"; } } // if the route contains does not include a / then route to the page directly if (route.indexOf("/") == -1) { this.page = route; window.location.hash = this.page; // if (this.page == "settings") { // this.version // } return; } let hash = route.split("/"); let page = hash[0]; let id = hash[1]; let isLibrary = hash[2] ?? false; if (page == "plugin") { this.pluginPages.page = "plugin." + id; this.page = "plugin-renderer"; return; } this.routeView({ kind: page, id: id, attributes: { playParams: { kind: page, id: id, isLibrary: isLibrary }, }, }); }, routeView(item) { let kind = item.attributes?.playParams ? item.attributes?.playParams?.kind ?? item.type ?? "" : item.type ?? ""; let id = item.attributes?.playParams ? item.attributes?.playParams?.id ?? item.id ?? "" : item.id ?? ""; let isLibrary = item.attributes?.playParams ? item.attributes?.playParams?.isLibrary ?? false : false; if (kind.includes("playlist") || kind.includes("album")) { app.showingPlaylist = []; } if (kind.toString().includes("apple-curator")) { kind = "appleCurator"; app.getTypeFromID("appleCurator", id, false, { platform: "web", include: "grouping,playlists", extend: "editorialArtwork", "art[url]": "f", }); window.location.hash = `${kind}/${id}`; document.querySelector("#app-content").scrollTop = 0; } else if (kind == "editorial-elements" || kind == "editorial-items") { console.debug(item); if (item.relationships?.contents?.data != null && item.relationships?.contents?.data.length > 0) { this.routeView(item.relationships.contents.data[0]); } else if (item.attributes?.link?.url != null) { if (item.attributes.link.url.includes("viewMultiRoom")) { const params = new Proxy(new URLSearchParams(new URL(item.attributes.link.url).search), { get: (searchParams, prop) => searchParams.get(prop), }); id = params.fcId; app .getTypeFromID("multiroom", id, false, { platform: "web", extend: "editorialArtwork,uber,lockupStyle", }) .then(() => { kind = "multiroom"; window.location.hash = `${kind}/${id}`; document.querySelector("#app-content").scrollTop = 0; }); return; } else if (item.attributes.link.url.includes("viewFeature")) { const params = new Proxy(new URLSearchParams(new URL(item.attributes.link.url).search), { get: (searchParams, prop) => searchParams.get(prop), }); id = params.id; app.mk.api.v3.music(`/v1/editorial/${app.mk.storefrontId}/multiplex/${id}?art%5Burl%5D=f&format%5Bresources%5D=map&platform=web`).then((data) => { let item = data.data.results?.target ?? []; app.routeView(item); }); } else { window.open(item.attributes.link.url); } } } else if (kind == "multiplex") { app.mk.api.v3.music(`/v1/editorial/${app.mk.storefrontId}/multiplex/${id}?art%5Burl%5D=f&format%5Bresources%5D=map&platform=web`).then((data) => { let item = data.data.results?.target ?? []; app.routeView(item); }); } if (kind == "multirooms") { app .getTypeFromID("multiroom", id, false, { platform: "web", extend: "editorialArtwork,uber,lockupStyle", }) .then(() => { kind = "multiroom"; window.location.hash = `${kind}/${id}`; document.querySelector("#app-content").scrollTop = 0; }); } else if (kind.toString().includes("artist")) { app.getArtistInfo(id, isLibrary); window.location.hash = `${kind}/${id}${isLibrary ? "/" + isLibrary : ""}`; document.querySelector("#app-content").scrollTop = 0; } else if (kind.toString().includes("record-label") || kind.toString().includes("curator")) { if (kind.toString().includes("record-label")) { kind = "recordLabel"; } else { kind = "curator"; } app.page = kind + "_" + id; app.getTypeFromID(kind, id, isLibrary, { extend: "editorialVideo", include: "grouping,playlists", views: "top-releases,latest-releases,top-artists", }); window.location.hash = `${kind}/${id}`; document.querySelector("#app-content").scrollTop = 0; } else if (kind.toString().includes("social-profiles")) { app.page = kind + "_" + id; app.mk.api.v3 .music(`/v1/social/${app.mk.storefrontId}/social-profiles/${id}`, { include: "shared-playlists", }) .then((data) => { console.log(data); app.showingPlaylist = data.data?.data[0]; window.location.hash = `${kind}/${id}`; document.querySelector("#app-content").scrollTop = 0; }); // app.getTypeFromID((kind), (id), (isLibrary), { // extend: "editorialVideo", // include: 'grouping,playlists', // views: 'top-releases,latest-releases,top-artists' // }); } else if (!kind.toString().includes("radioStation") && !kind.toString().includes("song") && !kind.toString().includes("musicVideo") && !kind.toString().includes("uploadedVideo") && !kind.toString().includes("music-movie")) { let params = { extend: "offers,editorialVideo", views: "appears-on,more-by-artist,related-videos,other-versions,you-might-also-like,video-extras,audio-extras", }; if (kind.includes("playlist")) { params["include"] = "tracks"; } if (kind.includes("album")) { params["include[albums]"] = "artists"; params["fields[artists]"] = "name,url"; params["omit[resource]"] = "autos"; params["meta[albums:tracks]"] = "popularity"; params["fields[albums]"] = "artistName,artistUrl,artwork,contentRating,editorialArtwork,editorialNotes,editorialVideo,name,playParams,releaseDate,url,copyright,genreNames"; } if (kind.includes("playlist") || kind.includes("album")) { app.page = kind + "_" + id; window.location.hash = `${kind}/${id}${isLibrary ? "/" + isLibrary : ""}`; app.getTypeFromID(kind, id, isLibrary, params); } else { app.page = kind; window.location.hash = `${kind}/${id}${isLibrary ? "/" + isLibrary : ""}`; } // app.getTypeFromID((kind), (id), (isLibrary), params); } else { app.playMediaItemById(id, kind, isLibrary, item.attributes.url ?? ""); } }, prevButton() { if (!app.prevButtonBackIndicator && app.mk.nowPlayingItem && app.mk.currentPlaybackTime > 2) { app.prevButtonBackIndicator = true; try { clearTimeout(app.pauseButtonTimer); } catch (e) {} app.mk.seekToTime(0); app.pauseButtonTimer = setTimeout(() => { app.prevButtonBackIndicator = false; }, 3000); } else { try { clearTimeout(app.pauseButtonTimer); } catch (e) {} app.prevButtonBackIndicator = false; app.skipToPreviousItem(); } }, isDisabled() { if (!app.mk.nowPlayingItem || app.mk.nowPlayingItem.attributes.playParams.kind == "radioStation") { return true; } return false; }, isPrevDisabled() { if (this.isDisabled() || (app.mk.queue._position == 0 && app.mk.currentPlaybackTime <= 2)) { return true; } return false; }, isNextDisabled() { if (this.isDisabled() || app.mk.queue._position + 1 == app.mk.queue.length) { return true; } return false; }, async getNowPlayingItemDetailed(target) { let nowPlayingItem = JSON.parse(JSON.stringify(this.mk.nowPlayingItem)); if (nowPlayingItem.type === "radioStation" && app.mk.nowPlayingItem.id !== -1) { nowPlayingItem.playParams = { kind: "songs" }; nowPlayingItem.attributes.playParams.catalogId = app.mk.nowPlayingItem.id; nowPlayingItem.attributes.playParams.id = app.mk.nowPlayingItem.id; nowPlayingItem.id = app.mk.nowPlayingItem.id; } try { let u = await app.mkapi(nowPlayingItem.playParams.kind, nowPlayingItem.songId == -1, nowPlayingItem.songId != -1 ? nowPlayingItem.songId : nowPlayingItem["id"], { "include[songs]": "albums,artists", l: app.mklang }); app.searchAndNavigate(u.data.data[0], target); } catch (e) { app.searchAndNavigate(nowPlayingItem, target); } }, async searchAndNavigate(item, target) { let self = this; app.tmpVar = item; switch (target) { case "artist": let artistId = ""; try { if (item.relationships.artists && item.relationships.artists.data.length > 0 && !item.relationships.artists.data[0].type.includes("library")) { if (item.relationships.artists.data[0].type === "artist" || item.relationships.artists.data[0].type === "artists") { artistId = item.relationships.artists.data[0].id; } } if (artistId == "") { const url = item.relationships.catalog.data[0].attributes.artistUrl; artistId = url.substring(url.lastIndexOf("/") + 1); if (artistId.includes("viewCollaboration")) { artistId = artistId.substring(artistId.lastIndexOf("ids=") + 4, artistId.lastIndexOf("-")); } } } catch (_) {} if (artistId == "") { let artistQuery = ( await app.mk.api.v3.music(`v1/catalog/${app.mk.storefrontId}/search?term=${item.attributes.artistName}`, { limit: 1, types: "artists", }) ).data.results; try { if (artistQuery.artists.data.length > 0) { artistId = artistQuery.artists.data[0].id; console.debug(artistId); } } catch (e) {} } console.debug(artistId); if (artistId != "") self.appRoute(`artist/${artistId}`); break; case "album": let albumId = ""; try { if ((item.type ?? item.playParams?.kind ?? "") == "albums") { albumId = item.id ?? ""; } else if (item.relationships.albums && item.relationships.albums.data.length > 0 && !item.relationships.albums.data[0].type.includes("library")) { if (item.relationships.albums.data[0].type === "album" || item.relationships.albums.data[0].type === "albums") { albumId = item.relationships.albums.data[0].id; } } if (albumId == "") { const url = item.relationships.catalog.data[0].attributes.url; albumId = url.substring(url.lastIndexOf("/") + 1); if (albumId.includes("?i=")) { albumId = albumId.substring(0, albumId.indexOf("?i=")); } } } catch (_) {} if (albumId == "") { try { let albumQuery = ( await app.mk.api.v3.music(`v1/catalog/${app.mk.storefrontId}/search?term=${(item.attributes.albumName ?? item.attributes.name ?? "") + " " + (item.attributes.artistName ?? "")}`, { limit: 1, types: "albums", }) ).data.results; if (albumQuery.albums.data.length > 0) { albumId = albumQuery.albums.data[0].id; console.debug(albumId); } } catch (e) {} } if (albumId != "") { self.appRoute(`album/${albumId}`); } break; case "recordLabel": let labelId = ""; try { labelId = item.relationships["record-labels"].data[0].id; } catch (_) {} if (labelId == "") { try { let labelQuery = ( await app.mk.api.v3.music(`v1/catalog/${app.mk.storefrontId}/search?term=${item.attributes.recordLabel}`, { limit: 1, types: "record-labels", }) ).data.results; if (labelQuery["record-labels"].data.length > 0) { labelId = labelQuery["record-labels"].data[0].id; console.debug(labelId); } } catch (e) {} } if (labelId != "") { app.showingPlaylist = []; await app.getTypeFromID("recordLabel", labelId, false, { views: "top-releases,latest-releases,top-artists", }); app.page = "recordLabel_" + labelId; } break; } }, exitMV() { MusicKit.getInstance().stop(); document.getElementById("apple-music-video-container").style.display = "none"; }, getArtistInfo(id, isLibrary) { this.getArtistFromID(id); //this.getTypeFromID("artist",id,isLibrary,query) }, followingArtist(id) { console.debug(`check for ${id}`); return this.cfg.home.followedArtists.includes(id); }, playMediaItem(item) { let kind = item.attributes.playParams ? item.attributes.playParams.kind ?? item.type ?? "" : item.type ?? ""; let id = item.attributes.playParams ? item.attributes.playParams.id ?? item.id ?? "" : item.id ?? ""; let isLibrary = item.attributes.playParams ? item.attributes.playParams.isLibrary ?? false : false; let truekind = !kind.endsWith("s") ? kind + "s" : kind; // console.log(kind, id, isLibrary) app.mk.stop().then(() => { if (kind.includes("artist")) { app.mk.setStationQueue({ artist: "a-" + id }).then(() => { app.mk.play(); }); } else { app.playMediaItemById(id, kind, isLibrary, item.attributes.url ?? ""); } }); }, async getTypeFromID(kind, id, isLibrary = false, params = {}, params2 = {}) { let a; if ((kind == "album") | (kind == "albums")) { params["include"] = "tracks,artists,record-labels,catalog"; } params["l"] = this.mklang; try { a = await this.mkapi(kind.toString(), isLibrary, id.toString(), params, params2); } catch (e) { console.debug(e); try { a = await this.mkapi(kind.toString(), !isLibrary, id.toString(), params, params2); } catch (err) { console.log(err); a = []; } finally { if (kind == "appleCurator") { app.appleCurator = a.data.data[0]; } else if (kind == "multiroom") { app.multiroom = a.data.data[0]; } else { this.getPlaylistContinuous(a, true); } } } finally { if (kind == "appleCurator") { app.appleCurator = a.data.data[0]; } else if (kind == "multiroom") { app.multiroom = a.data.data[0]; } else { this.getPlaylistContinuous(a, true); } } }, searchLibrarySongs() { let self = this; let prefs = this.cfg.libraryPrefs.songs; const albumAdded = {}; for (const listing of self.library?.albums?.listing ?? []) { albumAdded[listing.id] = listing.attributes?.dateAdded; } let startTime = new Date().getTime(); function sortSongs() { // sort this.library.songs.displayListing by song.attributes[self.library.songs.sorting] in descending or ascending order based on alphabetical order and numeric order // check if song.attributes[self.library.songs.sorting] is a number and if so, sort by number if not, sort by alphabetical order ignoring case self.library.songs.displayListing.sort((a, b) => { let aa = a.attributes[prefs.sort]; let bb = b.attributes[prefs.sort]; if (prefs.sort == "genre") { aa = a.attributes.genreNames[0]; bb = b.attributes.genreNames[0]; } else if (prefs.sort == "dateAdded") { let albumida = a.relationships?.albums?.data[0]?.id; let albumidb = b.relationships?.albums?.data[0]?.id; aa = startTime - new Date(albumAdded[albumida] ?? "1970-01-01T00:01:01Z").getTime(); bb = startTime - new Date(albumAdded[albumidb] ?? "1970-01-01T00:01:01Z").getTime(); } if (aa == null) { aa = ""; } if (bb == null) { bb = ""; } if (prefs.sortOrder == "asc") { if (aa.toString().match(/^\d+$/) && bb.toString().match(/^\d+$/)) { return aa - bb; } else { return aa.toString().toLowerCase().localeCompare(bb.toString().toLowerCase()); } } else if (prefs.sortOrder == "desc") { if (aa.toString().match(/^\d+$/) && bb.toString().match(/^\d+$/)) { return bb - aa; } else { return bb.toString().toLowerCase().localeCompare(aa.toString().toLowerCase()); } } }); } if (this.library.songs.search == "") { this.library.songs.displayListing = this.library.songs.listing; sortSongs(); } else { this.library.songs.displayListing = this.library.songs.listing.filter((item) => { let itemName = item.attributes.name.toLowerCase(); let searchTerm = this.library.songs.search.toLowerCase(); let artistName = ""; let albumName = ""; if (item.attributes.artistName != null) { artistName = item.attributes.artistName.toLowerCase(); } if (item.attributes.albumName != null) { albumName = item.attributes.albumName.toLowerCase(); } // remove any non-alphanumeric characters and spaces from search term and item name searchTerm = searchTerm.replace(/[^\p{L}\p{N} ]/gu, ""); itemName = itemName.replace(/[^\p{L}\p{N} ]/gu, ""); artistName = artistName.replace(/[^\p{L}\p{N} ]/gu, ""); albumName = albumName.replace(/[^\p{L}\p{N} ]/gu, ""); if (itemName.includes(searchTerm) || artistName.includes(searchTerm) || albumName.includes(searchTerm)) { return item; } }); sortSongs(); } }, getAlbumSort() { this.library.albums.sortOrder[1] = this.cfg.libraryPrefs.albums.sortOrder; this.library.albums.sorting[1] = this.cfg.libraryPrefs.albums.sort; }, // make a copy of searchLibrarySongs except use Albums instead of Songs searchLibraryAlbums(index) { let self = this; function sortAlbums() { // sort this.library.albums.displayListing by album.attributes[self.library.albums.sorting[index]] in descending or ascending order based on alphabetical order and numeric order // check if album.attributes[self.library.albums.sorting[index]] is a number and if so, sort by number if not, sort by alphabetical order ignoring case self.library.albums.displayListing.sort((a, b) => { let aa = a.attributes[self.library.albums.sorting[index]]; let bb = b.attributes[self.library.albums.sorting[index]]; if (self.library.albums.sorting[index] == "genre") { aa = a.attributes.genreNames[0]; bb = b.attributes.genreNames[0]; } if (aa == null) { aa = ""; } if (bb == null) { bb = ""; } if (self.library.albums.sortOrder[index] == "asc") { if (aa.toString().match(/^\d+$/) && bb.toString().match(/^\d+$/)) { return aa - bb; } else { return aa.toString().toLowerCase().localeCompare(bb.toString().toLowerCase()); } } else if (self.library.albums.sortOrder[index] == "desc") { if (aa.toString().match(/^\d+$/) && bb.toString().match(/^\d+$/)) { return bb - aa; } else { return bb.toString().toLowerCase().localeCompare(aa.toString().toLowerCase()); } } }); } if (this.library.albums.search == "") { this.library.albums.displayListing = this.library.albums.listing; sortAlbums(); } else { this.library.albums.displayListing = this.library.albums.listing.filter((item) => { let itemName = item.attributes.name.toLowerCase(); let searchTerm = this.library.albums.search.toLowerCase(); let artistName = ""; let albumName = ""; if (item.attributes.artistName != null) { artistName = item.attributes.artistName.toLowerCase(); } if (item.attributes.albumName != null) { albumName = item.attributes.albumName.toLowerCase(); } // remove any non-alphanumeric characters and spaces from search term and item name searchTerm = searchTerm.replace(/[^\p{L}\p{N} ]/gu, ""); itemName = itemName.replace(/[^\p{L}\p{N} ]/gu, ""); artistName = artistName.replace(/[^\p{L}\p{N} ]/gu, ""); albumName = albumName.replace(/[^\p{L}\p{N} ]/gu, ""); if (itemName.includes(searchTerm) || artistName.includes(searchTerm) || albumName.includes(searchTerm)) { return item; } }); sortAlbums(); } }, // make a copy of searchLibrarySongs except use Albums instead of Songs searchLibraryArtists(index) { let self = this; function sortArtists() { // sort this.library.albums.displayListing by album.attributes[self.library.albums.sorting[index]] in descending or ascending order based on alphabetical order and numeric order // check if album.attributes[self.library.albums.sorting[index]] is a number and if so, sort by number if not, sort by alphabetical order ignoring case self.library.artists.displayListing.sort((a, b) => { let aa = a.attributes[self.library.artists.sorting[index]]; let bb = b.attributes[self.library.artists.sorting[index]]; if (self.library.artists.sorting[index] == "genre") { aa = a.attributes.genreNames[0]; bb = b.attributes.genreNames[0]; } if (aa == null) { aa = ""; } if (bb == null) { bb = ""; } if (self.library.artists.sortOrder[index] == "asc") { if (aa.toString().match(/^\d+$/) && bb.toString().match(/^\d+$/)) { return aa - bb; } else { return aa.toString().toLowerCase().localeCompare(bb.toString().toLowerCase()); } } else if (self.library.artists.sortOrder[index] == "desc") { if (aa.toString().match(/^\d+$/) && bb.toString().match(/^\d+$/)) { return bb - aa; } else { return bb.toString().toLowerCase().localeCompare(aa.toString().toLowerCase()); } } }); } if (this.library.artists.search == "") { this.library.artists.displayListing = this.library.artists.listing; sortArtists(); } else { this.library.artists.displayListing = this.library.artists.listing.filter((item) => { let itemName = item.attributes.name.toLowerCase(); let searchTerm = this.library.artists.search.toLowerCase(); let artistName = ""; let albumName = ""; // if (item.attributes.artistName != null) { // artistName = item.attributes.artistName.toLowerCase() // } // if (item.attributes.albumName != null) { // albumName = item.attributes.albumName.toLowerCase() // } // remove any non-alphanumeric characters and spaces from search term and item name searchTerm = searchTerm.replace(/[^\p{L}\p{N} ]/gu, ""); itemName = itemName.replace(/[^\p{L}\p{N} ]/gu, ""); if (itemName.includes(searchTerm) || artistName.includes(searchTerm) || albumName.includes(searchTerm)) { return item; } }); sortArtists(); } }, focusSearch() { app.appRoute("search"); const search = document.getElementsByClassName("search-input"); if (search.length > 0) { search[0].focus(); } }, getSidebarItemClass(page) { if (this.page == page) { return ["active"]; } else { return []; } }, async mkapi(method, library = false, term, params = {}, params2 = {}, attempts = 0) { if (method.includes(`recordLabel`)) { method = `record-labels`; } if (method.includes(`appleCurator`)) { method = `apple-curators`; } if (attempts > 3) { return; } let truemethod = !method.endsWith("s") ? method + "s" : method; try { if (method.includes(`multiroom`)) { return await this.mk.api.v3.music(`v1/editorial/${app.mk.storefrontId}/${truemethod}/${term.toString()}`, params, params2); } else if (library) { return await this.mk.api.v3.music(`v1/me/library/${truemethod}/${term.toString()}`, params, params2); } else { return await this.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/${truemethod}/${term.toString()}`, params, params2); } } catch (e) { console.debug(e); return await this.mkapi(method, library, term, params, params2, attempts + 1); } }, getLibraryGenres() { let genres = []; genres = []; this.library.songs.listing.forEach((item) => { item.attributes.genreNames.forEach((genre) => { if (!genres.includes(genre)) { genres.push(genre); } }); }); return genres; }, async getLibrarySongsFull(force = false) { let self = this; let library = []; let cacheId = "library-songs"; let downloaded = null; this.$store.commit("resetRecentlyAdded"); if (this.library.songs.downloadState == 2 && !force) { return; } if (this.library.songs.downloadState == 1) { return; } let librarySongs = await CiderCache.getCache(cacheId); if (librarySongs) { this.library.songs.listing.data = librarySongs; this.searchLibrarySongs(); } if (this.songstest) { return; } this.library.songs.downloadState = 1; this.library.backgroundNotification.show = true; this.library.backgroundNotification.message = app.getLz("notification.updatingLibrarySongs"); library = await MusicKitTools.v3Continuous({ href: `/v1/me/library/songs/`, options: { "include[library-songs]": "catalog,artists,albums", "fields[artists]": "name,url,id", "fields[albums]": "name,url,id", platform: "web", "fields[catalog]": "artistUrl,albumUrl", "fields[songs]": "artistName,artistUrl,artwork,contentRating,editorialArtwork,name,playParams,releaseDate,url", limit: 100, l: app.mklang, }, onProgress: (data) => { console.debug(`${data.total}/${data.response.data.meta.total}`); self.library.backgroundNotification.show = true; self.library.backgroundNotification.message = app.getLz("notification.updatingLibrarySongs"); self.library.backgroundNotification.total = data.response.data.meta.total; self.library.backgroundNotification.progress = data.total; }, onSuccess: () => {}, }); self.library.songs.listing = library; self.library.songs.downloadState = 2; self.library.backgroundNotification.show = false; self.searchLibrarySongs(); CiderCache.putCache(cacheId, library); console.debug("Done!"); return; }, // copy the getLibrarySongsFull function except change Songs to Albums async getLibraryAlbumsFull(force = false, index) { let self = this; let library = []; let cacheId = "library-albums"; let downloaded = null; if ((this.library.albums.downloadState == 2 || this.library.albums.downloadState == 1) && !force) { return; } let libraryAlbums = await CiderCache.getCache(cacheId); if (libraryAlbums) { this.library.albums.listing = libraryAlbums; this.searchLibraryAlbums(index); } if (this.songstest) { return; } this.library.albums.downloadState = 1; this.library.backgroundNotification.show = true; this.library.backgroundNotification.message = app.getLz("notification.updatingLibraryAlbums"); function downloadChunk() { self.library.albums.downloadState = 1; const params = { "include[library-albums]": "catalog,artists,albums", "fields[artists]": "name,url,id", // "fields[albums]": "name,url,id", platform: "web", "fields[catalog]": "artistUrl,albumUrl", "fields[albums]": "artistName,artistUrl,artwork,contentRating,editorialArtwork,name,playParams,releaseDate,url", limit: 100, l: self.mklang, }; const safeparams = { platform: "web", limit: "60", "include[library-albums]": "artists", "include[library-artists]": "catalog", "include[albums]": "artists", "fields[artists]": "name,url", "fields[albums]": "artistName,artistUrl,artwork,contentRating,editorialArtwork,name,playParams,releaseDate,url", includeOnly: "catalog,artists", }; if (downloaded == null) { app.mk.api.v3 .music(`/v1/me/library/albums/`, params) .then((response) => { processChunk(response.data); }) .catch((error) => { console.debug("safe loading"); app.mk.api.v3 .music(`/v1/me/library/albums/`, safeparams) .then((response) => { processChunk(response.data); }) .catch((error) => { console.log("safe loading failed", error); app.library.albums.downloadState = 2; app.library.backgroundNotification.show = false; }); }); } else { if (downloaded.next != null) { app.mk.api.v3 .music(downloaded.next, params) .then((response) => { processChunk(response.data); }) .catch((error) => { console.debug("safe loading"); app.mk.api.v3 .music(downloaded.next, safeparams) .then((response) => { processChunk(response.data); }) .catch((error) => { console.log("safe loading failed", error); app.library.albums.downloadState = 2; app.library.backgroundNotification.show = false; }); }); } else { console.debug("Download next", downloaded.next); } } } function processChunk(response) { downloaded = response; library = library.concat(downloaded.data); self.library.backgroundNotification.show = true; self.library.backgroundNotification.message = app.getLz("notification.updatingLibraryAlbums"); self.library.backgroundNotification.total = downloaded.meta.total; self.library.backgroundNotification.progress = library.length; if (downloaded.meta.total == 0) { self.library.albums.downloadState = 3; return; } if (typeof downloaded.next == "undefined") { console.debug("downloaded.next is undefined"); self.library.albums.listing = library; self.library.albums.downloadState = 2; self.library.backgroundNotification.show = false; CiderCache.putCache(cacheId, library); self.searchLibraryAlbums(index); } if (downloaded.meta.total > library.length || typeof downloaded.meta.next != "undefined") { console.debug(`downloading next chunk - ${library.length} albums so far`); downloadChunk(); } else { self.library.albums.listing = library; self.library.albums.downloadState = 2; self.library.backgroundNotification.show = false; CiderCache.putCache(cacheId, library); self.searchLibraryAlbums(index); // console.log(library) } } downloadChunk(); }, // copy the getLibrarySongsFull function except change Songs to Albums async getLibraryArtistsFull(force = false, index) { let self = this; let library = []; let cacheId = "library-artists"; let downloaded = null; if ((this.library.artists.downloadState == 2 || this.library.artists.downloadState == 1) && !force) { return; } let libraryArtists = await CiderCache.getCache(cacheId); if (libraryArtists) { this.library.artists.listing = libraryArtists; this.searchLibraryArtists(index); } if (this.songstest) { return; } this.library.artists.downloadState = 1; this.library.backgroundNotification.show = true; this.library.backgroundNotification.message = app.getLz("notification.updatingLibraryArtists"); function downloadChunk() { self.library.artists.downloadState = 1; const params = { include: "catalog", // "include[library-artists]": "catalog,artists,albums", // "fields[artists]": "name,url,id", // "fields[albums]": "name,url,id", platform: "web", // "fields[catalog]": "artistUrl,albumUrl", // "fields[artists]": "artistName,artistUrl,artwork,contentRating,editorialArtwork,name,playParams,releaseDate,url", limit: 100, l: self.mklang, }; const safeparams = { include: "catalog", platform: "web", limit: 50, }; if (downloaded == null) { app.mk.api.v3 .music(`/v1/me/library/artists/`, params) .then((response) => { processChunk(response.data); }) .catch((error) => { console.debug("safe loading"); app.mk.api.v3 .music(`/v1/me/library/artists/`, safeparams) .then((response) => { processChunk(response.data); }) .catch((error) => { console.log("safe loading failed", error); app.library.artists.downloadState = 2; app.library.backgroundNotification.show = false; }); }); } else { if (downloaded.next != null) { app.mk.api.v3 .music(downloaded.next, params) .then((response) => { processChunk(response.data); }) .catch((error) => { console.log("safe loading"); app.mk.api.v3 .music(downloaded.next, safeparams) .then((response) => { processChunk(response.data); }) .catch((error) => { console.log("safe loading failed", error); app.library.artists.downloadState = 2; app.library.backgroundNotification.show = false; }); }); } else { console.log("Download next", downloaded.next); } } } function processChunk(response) { downloaded = response; library = library.concat(downloaded.data); self.library.backgroundNotification.show = true; self.library.backgroundNotification.message = app.getLz("notification.updatingLibraryArtists"); self.library.backgroundNotification.total = downloaded.meta.total; self.library.backgroundNotification.progress = library.length; if (downloaded.meta.total == 0) { self.library.albums.downloadState = 3; return; } if (typeof downloaded.next == "undefined") { console.log("downloaded.next is undefined"); self.library.artists.listing = library; self.library.artists.downloadState = 2; self.library.artists.show = false; CiderCache.putCache(cacheId, library); self.searchLibraryArtists(index); } if (downloaded.meta.total > library.length || typeof downloaded.meta.next != "undefined") { console.log(`downloading next chunk - ${library.length} artists so far`); downloadChunk(); } else { self.library.artists.listing = library; self.library.artists.downloadState = 2; self.library.backgroundNotification.show = false; CiderCache.putCache(cacheId, library); self.searchLibraryArtists(index); // console.log(library) } } downloadChunk(); }, /** * Gets the total duration in seconds of a playlist * @returns {string} Total tracks, and duration * @author Core#1034 * @memberOf app */ getTotalTime() { try { if (app.showingPlaylist.relationships.tracks.data.length === 0) return ""; const timeInSeconds = Math.round([].concat(...app.showingPlaylist.relationships.tracks.data).reduce((a, { attributes: { durationInMillis } }) => a + durationInMillis, 0) / 1000); return `${app.showingPlaylist.relationships.tracks.data.length} ${app.getLz("term.track", { count: app.showingPlaylist.relationships.tracks.data.length, })}, ${app.convertTime(timeInSeconds, "long")}`; } catch (err) { return ""; } }, async getLibrarySongs() { let response = await this.mkapi("songs", true, "", { limit: 100, l: this.mklang }, { includeResponseMeta: !0 }); this.library.songs.listing = response.data.data; this.library.songs.meta = response.data.meta; }, async getLibraryAlbums() { let response = await this.mkapi("albums", true, "", { limit: 100, l: this.mklang }, { includeResponseMeta: !0 }); this.library.albums.listing = response.data.data; this.library.albums.meta = response.data.meta; }, async getListenNow(attempt = 0) { if (this.listennow.timestamp > Date.now() - 120000) { return; } if (attempt > 3) { return; } try { this.listennow = ( await this.mk.api.v3.music( `v1/me/recommendations?timezone=${encodeURIComponent(this.formatTimezoneOffset())}`, { name: "listen-now", with: "friendsMix,library,social", "art[social-profiles:url]": "c", "art[url]": "c,f", "omit[resource]": "autos", "relate[editorial-items]": "contents", extend: ["editorialCard", "editorialVideo"], "extend[albums]": ["artistUrl"], "extend[library-albums]": ["artistUrl", "editorialVideo"], "extend[playlists]": ["artistNames", "editorialArtwork", "editorialVideo"], "extend[library-playlists]": ["artistNames", "editorialArtwork", "editorialVideo"], "extend[social-profiles]": "topGenreNames", "include[albums]": "artists", "include[songs]": "artists", "include[music-videos]": "artists", "include[personal-recommendation]": "primary-content", "fields[albums]": ["artistName", "artistUrl", "artwork", "contentRating", "editorialArtwork", "editorialVideo", "name", "playParams", "releaseDate", "url"], "fields[artists]": ["name", "url", "artwork"], "extend[stations]": ["airDate", "supportsAirTimeUpdates"], "meta[stations]": "inflectionPoints", types: "artists,albums,editorial-items,library-albums,library-playlists,music-movies,music-videos,playlists,stations,uploaded-audios,uploaded-videos,activities,apple-curators,curators,tv-shows,social-upsells", platform: "web", l: this.mklang, }, { includeResponseMeta: !0, reload: !0, } ) ).data; this.listennow.timestamp = Date.now(); console.debug(this.listennow); } catch (e) { console.log(e); this.getListenNow(attempt + 1); } }, async getBrowsePage(attempt = 0) { if (this.browsepage.timestamp > Date.now() - 120000) { return; } if (attempt > 3) { return; } try { let browse = await app.mk.api.v3.music(`/v1/editorial/${app.mk.storefrontId}/groupings`, { platform: "web", name: "music", "omit[resource:artists]": "relationships", "include[albums]": "artists", "include[songs]": "artists", "include[music-videos]": "artists", extend: "editorialArtwork,artistUrl", "fields[artists]": "name,url,artwork,editorialArtwork,genreNames,editorialNotes", "art[url]": "f", l: this.mklang, }); this.browsepage = browse.data.data[0]; this.browsepage.timestamp = Date.now(); console.debug(this.browsepage); } catch (e) { console.log(e); this.getBrowsePage(attempt + 1); } }, async getMadeForYou(attempt = 0) { if (attempt > 3) { return; } try { let mfu = await app.mk.api.v3.music( "/v1/me/library/playlists?platform=web&extend=editorialVideo&fields%5Bplaylists%5D=lastModifiedDate&filter%5Bfeatured%5D=made-for-you&include%5Blibrary-playlists%5D=catalog&fields%5Blibrary-playlists%5D=artwork%2Cname%2CplayParams%2CdateAdded" ); this.madeforyou = mfu.data; } catch (e) { console.log(e); this.getMadeForYou(attempt + 1); } }, newPlaylistFolder(name = app.getLz("term.newPlaylistFolder")) { let self = this; this.mk.api.v3 .music( "/v1/me/library/playlist-folders/", {}, { fetchOptions: { method: "POST", body: JSON.stringify({ attributes: { name: name }, }), }, } ) .then((res) => { let playlist = res.data.data[0]; self.playlists.listing.push({ id: playlist.id, attributes: { name: playlist.attributes.name, }, type: "library-playlist-folders", parent: "p.playlistsroot", }); self.sortPlaylists(); setTimeout(() => { app.refreshPlaylists(false, false); }, 13000); }); }, showSearch() { this.page = "search"; }, loadLyrics() { const musicType = MusicKit.getInstance().nowPlayingItem != null ? MusicKit.getInstance().nowPlayingItem["type"] ?? "" : ""; // console.log("mt", musicType) if (musicType === "musicVideo") { this.loadYTLyrics(); } else { // if (app.cfg.lyrics.enable_mxm) { this.loadMXM(); // } else { // this.loadAMLyrics(); // } } }, loadAMLyrics() { const songID = this.mk.nowPlayingItem != null ? this.mk.nowPlayingItem["_songId"] ?? this.mk.nowPlayingItem["songId"] ?? -1 : -1; // this.getMXM( trackName, artistName, 'en', duration); if (songID != -1) { this.mk.api.v3.music(`v1/catalog/${this.mk.storefrontId}/songs/${songID}/lyrics`).then((response) => { this.lyricsMediaItem = response.data?.data[0]?.attributes["ttml"]; this.parseTTML(); }); } }, addToLibrary(id) { let self = this; this.mk.addToLibrary(id).then((data) => { self.getLibrarySongsFull(true); }); notyf.success(app.getLz("action.addToLibrary.success")); }, removeFromLibrary(kind, id) { let self = this; let truekind = !kind.endsWith("s") ? kind + "s" : kind; app.mk.api.v3 .music( `v1/me/library/${truekind}/${id.toString()}`, {}, { fetchOptions: { method: "DELETE", }, } ) .then((data) => { self.getLibrarySongsFull(true); }); notyf.success(app.getLz("action.removeFromLibrary.success")); }, async loadYTLyrics() { const track = this.mk.nowPlayingItem != null ? this.mk.nowPlayingItem.title ?? "" : ""; const artist = this.mk.nowPlayingItem != null ? this.mk.nowPlayingItem.artistName ?? "" : ""; const time = this.mk.nowPlayingItem != null ? Math.round((this.mk.nowPlayingItem.attributes["durationInMillis"] ?? -1000) / 1000) ?? -1 : -1; ipcRenderer.invoke("getYTLyrics", track, artist).then((result) => { if (result.length > 0) { let ytid = result[0]["id"]["videoId"]; if (app.cfg.lyrics.enable_yt) { loadYT(ytid, app.cfg.lyrics.mxm_language ?? "en"); } else { app.loadMXM(); } } else { app.loadMXM(); } function loadYT(id, lang) { let req = new XMLHttpRequest(); let url = `https://www.youtube.com/watch?&v=${id}`; req.open("GET", url, true); req.onerror = function (e) { this.loadMXM(); }; req.onload = function () { // console.log(this.responseText); let res = this.responseText; let captionurl1 = res.substring(res.indexOf(`{"playerCaptionsRenderer":{"baseUrl":"`) + `{"playerCaptionsRenderer":{"baseUrl":"`.length); let captionurl = captionurl1.substring(0, captionurl1.indexOf(`"`)); if (captionurl.includes("timedtext")) { let json = JSON.parse(`{"url": "${captionurl}"}`); let newurl = json.url + `&lang=${lang}&format=ttml`; let req2 = new XMLHttpRequest(); req2.open("GET", newurl, true); req2.onerror = function (e) { app.loadMXM(); }; req2.onload = function () { try { const ttmlLyrics = this.responseText; if (ttmlLyrics) { this.lyricsMediaItem = ttmlLyrics; this.parseTTML(); } } catch (e) { app.loadMXM(); } }; req2.send(); } else { app.loadMXM(); } }; req.send(); } }); }, loadMXM() { let attempt = 0; const track = encodeURIComponent(this.mk.nowPlayingItem != null ? this.mk.nowPlayingItem.title ?? "" : ""); const artist = encodeURIComponent(this.mk.nowPlayingItem != null ? this.mk.nowPlayingItem.artistName ?? "" : ""); const time = encodeURIComponent(this.mk.nowPlayingItem != null ? Math.round((this.mk.nowPlayingItem.attributes["durationInMillis"] ?? -1000) / 1000) ?? -1 : -1); let id = null; let vanity_id = null; if (this.mk.nowPlayingItem != null && app.mk.nowPlayingItem.localFilesMetadata != null) { const id = encodeURIComponent(""); } else { id = encodeURIComponent(this.mk.nowPlayingItem != null ? app.mk.nowPlayingItem._songId ?? app.mk.nowPlayingItem["songId"] ?? "" : ""); } let lrcfile = ""; let richsync = []; const lang = app.cfg.lyrics.mxm_language; // translation language function getMXMSubs(track, artist, lang, time, id) { let richsyncQuery = app.cfg.lyrics.mxm_karaoke; let itunesid = id && id != "" ? id : ""; // Mode 1 -> Subs let url = "https://api.cider.sh/v1/lyrics?" + "mode=1" + "&richsyncQuery=" + richsyncQuery + "&track=" + track + "&artist=" + artist + "&songID=" + itunesid + "&source=mxm" + "&lang=" + lang + "&time=" + time; let req = new XMLHttpRequest(); req.overrideMimeType("application/json"); req.onload = function () { try { let jsonResponse = JSON.parse(this.responseText); console.debug(jsonResponse); let status1 = jsonResponse["message"]["header"]["status_code"]; if (status1 == 200) { let id, songLang = ""; try { if ( jsonResponse["message"]["body"]["macro_calls"]["matcher.track.get"]["message"]["header"]["status_code"] == 200 && jsonResponse["message"]["body"]["macro_calls"]["track.subtitles.get"]["message"]["header"]["status_code"] == 200 ) { id = jsonResponse["message"]["body"]["macro_calls"]["matcher.track.get"]["message"]["body"]["track"]["track_id"] ?? ""; lrcfile = jsonResponse["message"]["body"]["macro_calls"]["track.subtitles.get"]["message"]["body"]["subtitle_list"][0]["subtitle"]["subtitle_body"]; vanity_id = jsonResponse["message"]["body"]["macro_calls"]["matcher.track.get"]["message"]["body"]["track"]["commontrack_vanity_id"]; songLang = jsonResponse["message"]["body"]["macro_calls"]["track.lyrics.get"]["message"]["body"]["lyrics"]["lyrics_language_description"]; try { let lrcrich = jsonResponse["message"]["body"]["macro_calls"]["track.richsync.get"]["message"]["body"]["richsync"]["richsync_body"]; richsync = JSON.parse(lrcrich); app.richlyrics = richsync; } catch (_) {} } if (lrcfile === "") { app.loadQQLyrics(); // app.loadAMLyrics() } else { if (richsync == [] || richsync.length == 0) { console.log("musixmatch worki"); // process lrcfile to json here app.lyricsMediaItem = lrcfile; let u = app.lyricsMediaItem.split(/[\r\n]/); let preLrc = []; for (var i = u.length - 1; i >= 0; i--) { let xline = /(\[[0-9.:\[\]]*\])+(.*)/.exec(u[i]); let end = preLrc.length > 0 ? preLrc[preLrc.length - 1].startTime ?? 99999 : 99999; preLrc.push({ startTime: app.toMS(xline[1].substring(1, xline[1].length - 2)) ?? 0, endTime: end, line: xline[2], translation: "", }); } if (preLrc.length > 0) preLrc.push({ startTime: 0, endTime: preLrc[preLrc.length - 1].startTime, line: "lrcInstrumental", translation: "", }); app.lyrics = preLrc.reverse(); } else { let preLrc = richsync.map(function (item) { return { startTime: item.ts, endTime: item.te, line: item.x, translation: "", }; }); if (preLrc.length > 0) preLrc.unshift({ startTime: 0, endTime: preLrc[0].startTime, line: "lrcInstrumental", translation: "", }); app.lyrics = preLrc; } // Load translation if (songLang.toLowerCase() !== lang) { getMXMTrans(lang, vanity_id); } } } catch (e) { console.log(e); app.loadQQLyrics(); // app.loadAMLyrics() } } } catch (e) { console.error(e); app.loadQQLyrics(); //app.loadAMLyrics() } }; req.onerror = function () { app.loadQQLyrics(); console.log("error"); // app.loadAMLyrics(); }; req.open("POST", url, true); req.send(); } function getMXMTrans(lang, vanity_id) { try { if (lang !== "disabled" && vanity_id !== "") { // Mode 2 -> Trans let url = "https://api.cider.sh/v1/lyrics?mode=2&vanityID=" + vanity_id + "&source=mxm&lang=" + lang; let req = new XMLHttpRequest(); req.overrideMimeType("application/json"); req.onload = function () { if (req.status == 200) { // If it's not 200, 237890127389012 things could go wrong and I don't really care what those things are. let jsonResponse = JSON.parse(this.responseText); let applied = 0; for (let i = 0; applied < app.lyrics.length; i++) { if (app.lyrics[applied].line.trim() === "") { applied += 1; } if (app.lyrics[applied].line.trim() === jsonResponse[i]) { // Do Nothing applied += 1; } else { if (app.lyrics[applied].line === "lrcInstrumental") { if (app.lyrics[applied + 1].line.trim() === jsonResponse[i]) { // Do Nothing applied += 2; } else { app.lyrics[applied + 1].translation = jsonResponse[i]; applied += 2; } } else { app.lyrics[applied].translation = jsonResponse[i]; applied += 1; } } } } }; req.onerror = function () { console.log("MXM Translation somehow died. Don't need to know why."); }; req.open("POST", url, true); req.send(); } } catch (e) { console.debug("Error while parsing MXM Trans: " + e); } } if ((track != "") & (track != "No Title Found")) { getMXMSubs(track, artist, lang, time, id); } }, loadNeteaseLyrics() { const track = encodeURIComponent(this.mk.nowPlayingItem != null ? this.mk.nowPlayingItem.title ?? "" : ""); const artist = encodeURIComponent(this.mk.nowPlayingItem != null ? this.mk.nowPlayingItem.artistName ?? "" : ""); const time = encodeURIComponent(this.mk.nowPlayingItem != null ? Math.round((this.mk.nowPlayingItem.attributes["durationInMillis"] ?? -1000) / 1000) ?? -1 : -1); var url = `http://music.163.com/api/search/get/?csrf_token=hlpretag=&hlposttag=&s=${track + " " + artist}&type=1&offset=0&total=true&limit=6`; var req = new XMLHttpRequest(); req.overrideMimeType("application/json"); req.open("GET", url, true); req.onload = function () { try { var jsonResponse = JSON.parse(req.responseText); var id = jsonResponse["result"]["songs"][0]["id"]; var url2 = "https://music.163.com/api/song/lyric?os=pc&id=" + id + "&lv=-1&kv=-1&tv=-1"; var req2 = new XMLHttpRequest(); req2.overrideMimeType("application/json"); req2.open("GET", url2, true); req2.onload = function () { try { var jsonResponse2 = JSON.parse(req2.responseText); var lrcfile = jsonResponse2["lrc"]["lyric"]; app.lyricsMediaItem = lrcfile; let u = app.lyricsMediaItem.split(/[\n]/); let preLrc = []; for (var i = u.length - 1; i >= 0; i--) { let xline = /(\[[0-9.:\[\]]*\])+(.*)/.exec(u[i]); if (xline != null) { let end = preLrc.length > 0 ? preLrc[preLrc.length - 1].startTime ?? 99999 : 99999; preLrc.push({ startTime: app.toMS(xline[1].substring(1, xline[1].length - 2)) ?? 0, endTime: end, line: xline[2], translation: "", }); } } if (preLrc.length > 0) preLrc.push({ startTime: 0, endTime: preLrc[preLrc.length - 1].startTime, line: "lrcInstrumental", translation: "", }); app.lyrics = preLrc.reverse(); } catch (e) { app.lyrics = ""; } }; req2.onerror = function () {}; req2.send(); } catch (e) { app.lyrics = ""; } }; req.send(); req.onerror = function () {}; }, loadQQLyrics() { if (!app.cfg.lyrics.enable_qq) return; const track = encodeURIComponent(this.mk.nowPlayingItem != null ? this.mk.nowPlayingItem.title ?? "" : ""); const artist = encodeURIComponent(this.mk.nowPlayingItem != null ? this.mk.nowPlayingItem.artistName ?? "" : ""); const time = encodeURIComponent(this.mk.nowPlayingItem != null ? Math.round((this.mk.nowPlayingItem.attributes["durationInMillis"] ?? -1000) / 1000) ?? -1 : -1); var url = `https://c.y.qq.com/soso/fcgi-bin/client_search_cp?w=${track + " " + artist}&t=0&n=1&page=1&cr=1&new_json=1&format=json&platform=yqq.json`; var req = new XMLHttpRequest(); req.overrideMimeType("application/json"); req.open("GET", url, true); req.onload = function () { try { var jsonResponse = JSON.parse(req.responseText); let id = jsonResponse?.data?.song?.list[0]?.mid; console.log(jsonResponse); let usz = new Date().getTime(); var url2 = `https://c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric_new.fcg?-=MusicJsonCallback_lrc&songmid=${id}&pcachetime=${usz}&g_tk=5381&loginUin=3003436226&hostUin=0&inCharset=utf-8&outCharset=utf-8¬ice=0&platform=yqq.json&needNewCode=0`; var req2 = new XMLHttpRequest(); req2.overrideMimeType("application/json"); req2.open("GET", url2, true); req2.onload = function () { try { function b64_to_utf8(str) { return decodeURIComponent(escape(window.atob(str))); } const htmlDecode = (input) => { const doc = new DOMParser().parseFromString(input, "text/html"); return doc.documentElement.textContent; }; var jsonResponse2 = JSON.parse(req2.responseText.replace("MusicJsonCallback(", "").replace("})", "}")); var lrcfile = htmlDecode(b64_to_utf8(jsonResponse2["lyric"])); app.lyricsMediaItem = lrcfile; let u = app.lyricsMediaItem.split(/[\n]/); let preLrc = []; for (var i = u.length - 1; i >= 0; i--) { let xline = /(\[[0-9.:\[\]]*\])+(.*)/.exec(u[i]); if (xline != null) { let end = preLrc.length > 0 ? preLrc[preLrc.length - 1].startTime ?? 99999 : 99999; preLrc.push({ startTime: app.toMS(xline[1].substring(1, xline[1].length - 2)) ?? 0, endTime: end, line: xline[2], translation: "", }); } } if (preLrc.length > 0) preLrc.push({ startTime: 0, endTime: preLrc[preLrc.length - 1].startTime, line: "lrcInstrumental", translation: "", }); app.lyrics = preLrc.reverse(); if (app.lyrics[5].line == "") { app.loadNeteaseLyrics(); } // Detect incomplete QQ lyrics. } catch (e) { console.log(e); app.loadNeteaseLyrics(); app.lyrics = ""; } }; req2.onerror = function () { app.loadNeteaseLyrics(); }; req2.send(); } catch (e) { console.log(e); app.loadNeteaseLyrics(); app.lyrics = ""; } }; req.onerror = function () { app.loadNeteaseLyrics(); }; req.send(); }, toMS(str) { let rawTime = str.match(/(\d+:)?(\d+:)?(\d+)(\.\d+)?/); let hours = rawTime[2] != null ? rawTime[1].replace(":", "") : 0; let minutes = rawTime[2] != null ? hours * 60 + rawTime[2].replace(":", "") * 1 : rawTime[1] != null ? rawTime[1].replace(":", "") : 0; let seconds = rawTime[3] != null ? rawTime[3] : 0; let milliseconds = rawTime[4] != null ? rawTime[4].replace(".", "") : 0; return parseFloat(`${minutes * 60 + seconds * 1}.${milliseconds * 1}`); }, parseTTML() { this.lyrics = []; let preLrc = []; let xml = this.stringToXml(this.lyricsMediaItem); let lyricsLines = xml.getElementsByTagName("p"); let synced = true; let endTimes = []; if (xml.getElementsByTagName("tt")[0].getAttribute("itunes:timing") === "None") { synced = false; } endTimes.push(0); if (synced) { for (let element of lyricsLines) { let start = this.toMS(element.getAttribute("begin")); let end = this.toMS(element.getAttribute("end")); if (start - endTimes[endTimes.length - 1] > 5 && endTimes[endTimes.length - 1] != 0) { preLrc.push({ startTime: endTimes[endTimes.length - 1], endTime: start, line: "lrcInstrumental", }); } preLrc.push({ startTime: start, endTime: end, line: element.textContent, }); endTimes.push(end); } // first line dot if (preLrc.length > 0) preLrc.unshift({ startTime: 0, endTime: preLrc[0].startTime, line: "lrcInstrumental", }); } else { for (let element of lyricsLines) { preLrc.push({ startTime: 9999999, endTime: 9999999, line: element.textContent, }); } } this.lyrics = preLrc; }, parseLyrics() { let xml = this.stringToXml(this.lyricsMediaItem); let json = xmlToJson(xml); this.lyrics = json; }, stringToXml(st) { // string to xml let xml = new DOMParser().parseFromString(st, "text/xml"); return xml; }, getCurrentTime() { return parseFloat(this.hmsToSecondsOnly(this.parseTime(this.mk.nowPlayingItem.attributes.durationInMillis - app.mk.currentPlaybackTimeRemaining * 1000))); }, seekTo(time) { this.mk.seekToTime(time); }, parseTime(value) { let minutes = Math.floor(value / 60000); let seconds = ((value % 60000) / 1000).toFixed(0); return minutes + ":" + (seconds < 10 ? "0" : "") + seconds; }, parseTimeDecimal(value) { let minutes = Math.floor(value / 60000); let seconds = ((value % 60000) / 1000).toFixed(0); return minutes + "." + (seconds < 10 ? "0" : "") + seconds; }, hmsToSecondsOnly(str) { let p = str.split(":"), s = 0, m = 1; while (p.length > 0) { s += m * parseInt(p.pop(), 10); m *= 60; } return s; }, getLyricBGStyle(start, end) { let currentTime = this.getCurrentTime(); // let duration = this.mk.nowPlayingItem.attributes.durationInMillis let start2 = this.hmsToSecondsOnly(start); let end2 = this.hmsToSecondsOnly(end); // let currentProgress = ((100 * (currentTime)) / (end2)) // check if currenttime is between start and end this.player.lyricsDebug.start = start2; this.player.lyricsDebug.end = end2; this.player.lyricsDebug.current = currentTime; if (currentTime >= start2 && currentTime <= end2) { return { "--bgSpeed": `${end2 - start2}s`, }; } else { return {}; } }, playMediaItemById(id, kind, isLibrary, raurl = "") { let truekind = !kind.endsWith("s") ? kind + "s" : kind; console.debug(id, truekind, isLibrary); try { if (truekind.includes("artist")) { app.mk.setStationQueue({ artist: "a-" + id }).then(() => { app.mk.play(); }); } else if (truekind == "radioStations") { this.mk.setStationQueue({ url: raurl }).then(function (queue) { MusicKit.getInstance().play(); }); } else { this.mk .setQueue({ [truekind]: [id], parameters: { l: this.mklang }, }) .then(function (queue) { MusicKit.getInstance().play(); }); } } catch (err) { console.log(err); this.playMediaItemById(id, kind, isLibrary, raurl); } }, queueParentandplayChild(parent, childIndex, item) { /* Randomize array in-place using Durstenfeld shuffle algorithm */ function shuffleArray(array) { for (var i = array.length - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); var temp = array[i]; array[i] = array[j]; array[j] = temp; } } let kind = parent.substring(0, parent.indexOf(":")); let id = parent.substring(parent.indexOf(":") + 1, parent.length); let truekind = !kind.endsWith("s") ? kind + "s" : kind; console.log(truekind, id); try { if (parent == "playlist:ciderlocal") { let u = app.library.localsongs.map((i) => { return i.id; }); app.mk.setQueue({ episodes: u }).then(() => { let id = app.mk.queue._itemIDs.findIndex((element) => element == item.id); app.mk.changeToMediaAtIndex(id); }); } else if (app.library.songs.displayListing.length > childIndex && parent == "librarysongs") { console.log(item); if (item && app.library.songs.displayListing[childIndex].id != item.id) { childIndex = app.library.songs.displayListing.indexOf(item); } let query = app.library.songs.displayListing.map((item) => new MusicKit.MediaItem(item)); app.mk.stop().then(() => { if (item) { app.mk .setQueue({ [item.attributes.playParams.kind ?? item.type]: item.attributes.playParams.id ?? item.id, parameters: { l: app.mklang }, }) .then(function () { app.mk.play().then(() => { if (app.mk.shuffleMode == 1) { shuffleArray(query); } else { for (let i = 0; i < query.length; i++) { if (query[i].id == item.id) { query.splice(0, i + 1); break; } } } app.mk.queue.append(query); }); }); } else { app.mk.queue.splice(0, app.mk.queue._itemIDs.length); if (app.mk.shuffleMode == 1) { shuffleArray(query); } app.mk.queue.append(query); if (childIndex != -1) { app.mk.changeToMediaAtIndex(childIndex); } else { app.mk.play(); } } }); } else if (parent.startsWith("listitem-hr")) { app.mk.stop().then(() => { if (app.mk.shuffleMode == 1) { app.mk .setQueue({ [item.attributes.playParams.kind ?? item.type]: item.attributes.playParams.id ?? item.id, }) .then(function () { app.mk.play().then(() => { let data = JSON.parse(parent.split("listitem-hr")[1] ?? "[]"); let itemsToPlay = {}; let u = data.map((x) => x.id); try { data.splice(u.indexOf(item.attributes.playParams.id ?? item.id), 1); } catch (e) {} if (app.mk.shuffleMode == 1) { shuffleArray(data); } data.forEach((item) => { if (!itemsToPlay[item.kind]) { itemsToPlay[item.kind] = []; } itemsToPlay[item.kind].push(item.id); }); // loop through itemsToPlay for (let kind in itemsToPlay) { let ids = itemsToPlay[kind]; if (ids.length > 0) { app.mk.playLater({ [kind + "s"]: itemsToPlay[kind] }); } } }); }); } else { let data = JSON.parse(parent.split("listitem-hr")[1] ?? "[]"); let itemsToPlay = {}; data.forEach((item) => { if (!itemsToPlay[item.kind]) { itemsToPlay[item.kind] = []; } itemsToPlay[item.kind].push(item.id); }); // loop through itemsToPlay app.mk.queue.splice(0, app.mk.queue._itemIDs.length); let ind = 0; for (let kind in itemsToPlay) { let ids = itemsToPlay[kind]; if (ids.length > 0) { if (app.mk.queue._itemIDs.length > 0) { app.mk.playLater({ [kind + "s"]: itemsToPlay[kind] }).then(function () { ind += 1; console.log(ind, Object.keys(itemsToPlay).length); if (ind >= Object.keys(itemsToPlay).length) { app.mk.changeToMediaAtIndex(app.mk.queue._itemIDs.indexOf(item.attributes.playParams.id ?? item.id)); } }); } else { app.mk.setQueue({ [kind + "s"]: itemsToPlay[kind] }).then(function () { ind += 1; console.log(ind, Object.keys(itemsToPlay).length); if (ind >= Object.keys(itemsToPlay).length) { app.mk.changeToMediaAtIndex(app.mk.queue._itemIDs.indexOf(item.attributes.playParams.id ?? item.id)); } }); } } } } }); } else { app.mk.stop().then(() => { if (truekind == "playlists" && (id.startsWith("p.") || id.startsWith("pl.u"))) { app.mk .setQueue({ [item.attributes.playParams.kind ?? item.type]: item.attributes.playParams.id ?? item.id, parameters: { l: app.mklang }, }) .then(function () { app.mk.changeToMediaAtIndex(app.mk.queue._itemIDs.indexOf(item.id) ?? 1).then(function () { if (app.showingPlaylist && app.showingPlaylist.id == id) { let query = app.showingPlaylist.relationships.tracks.data.map((item) => new MusicKit.MediaItem(item)); let u = query; if (app.mk.shuffleMode == 1) { shuffleArray(u); } else { for (let i = 0; i < app.showingPlaylist.relationships.tracks.data.length; i++) { if (app.showingPlaylist.relationships.tracks.data[i].id == item.id) { u.splice(0, i + 1); break; } } } app.mk.queue.append(u); } else { app.getPlaylistFromID(id, true).then(function () { let query = app.showingPlaylist.relationships.tracks.data.map((item) => new MusicKit.MediaItem(item)); let u = query; if (app.mk.shuffleMode == 1) { shuffleArray(u); } else { for (let i = 0; i < app.showingPlaylist.relationships.tracks.data.length; i++) { if (app.showingPlaylist.relationships.tracks.data[i].id == item.id) { u.splice(0, i + 1); break; } } } app.mk.queue.append(u); }); } }); }); } else { this.mk .setQueue({ [truekind]: [id], parameters: { l: this.mklang }, }) .then(function (queue) { if (item && queue._itemIDs[childIndex] != item.id) { childIndex = queue._itemIDs.indexOf(item.id); } if (childIndex != -1) { app.mk.changeToMediaAtIndex(childIndex); } else if (item) { app.mk .playNext({ [item.attributes.playParams.kind ?? item.type]: item.attributes.playParams.id ?? item.id, }) .then(function () { app.mk.changeToMediaAtIndex(app.mk.queue._itemIDs.indexOf(item.id) ?? 1); app.mk.play(); }); } else { app.mk.play(); } }); } }); } } catch (err) { console.log(err); try { app.mk.stop(); } catch (e) {} this.playMediaItemById(item.attributes.playParams.id ?? item.id, item.attributes.playParams.kind ?? item.type, item.attributes.playParams.isLibrary ?? false, item.attributes.url); } }, friendlyTypes(type) { // use switch statement to return friendly name for media types "songs,artists,albums,playlists,music-videos,stations,apple-curators,curators" switch (type) { case "library-songs": return app.getLz("term.songs"); break; case "library-artists": return app.getLz("term.artists"); break; case "library-albums": return app.getLz("term.albums"); break; case "library-playlists": return app.getLz("term.playlists"); break; case "song": return app.getLz("term.songs"); break; case "artist": return app.getLz("term.artists"); break; case "album": return app.getLz("term.albums"); break; case "playlist": return app.getLz("term.playlists"); break; case "music_video": return app.getLz("term.musicVideos"); break; case "station": return app.getLz("term.stations"); break; case "apple-curator": return app.getLz("term.appleCurators"); break; case "radio_show": return app.getLz("term.radioShows"); break; case "record_label": return app.getLz("term.recordLabels"); break; case "radio_episode": return app.getLz("podcast.episodes"); break; case "video_extra": return app.getLz("term.videoExtras"); break; case "curator": return app.getLz("term.curators"); break; case "top": return app.getLz("term.top"); break; default: return type; break; } }, searchCursor(e) { if (e.keyCode == "40") { if (this.search.hints.length - 1 < this.search.cursor + 1) return; this.search.cursor++; this.search.term = this.search.hints[this.search.cursor]; } else if (e.keyCode == "38") { if (this.search.cursor == 0) return; this.search.cursor--; this.search.term = this.search.hints[this.search.cursor]; } }, async searchQuery(term = this.search.term) { let self = this; if (term == "") { return; } //this.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/search?term=${this.search.term}` this.mk.api.v3 .music(`/v1/catalog/${app.mk.storefrontId}/search?term=${encodeURIComponent(this.search.term)}`, { types: "activities,albums,apple-curators,artists,curators,editorial-items,music-movies,music-videos,playlists,songs,stations,tv-episodes,uploaded-videos,record-labels", "relate[editorial-items]": "contents", "include[editorial-items]": "contents", "include[albums]": "artists", "include[artists]": "artists", "include[songs]": "artists,albums", "include[music-videos]": "artists", extend: "artistUrl", "fields[artists]": "url,name,artwork,hero", "fields[albums]": "artistName,artistUrl,artwork,contentRating,editorialArtwork,editorialVideo,name,playParams,releaseDate,url", with: "serverBubbles,lyricHighlights", "art[url]": "c,f", "omit[resource]": "autos", platform: "web", limit: 25, l: this.mklang, }) .then(function (results) { results.data.results["meta"] = results.data.meta; self.search.results = results.data.results; }); await app.mk.api.v3 .music( `v1/social/${app.mk.storefrontId}/search?term=${app.search.term}`, { types: ["playlists", "social-profiles"], limit: 25, with: ["serverBubbles", "lyricSnippet"], "art[url]": "f", "art[social-profiles:url]": "c", }, { includeResponseMeta: !0 } ) .then(function (results) { results.data.results["meta"] = results.data.meta; self.search.resultsSocial = results.data.results; }); this.search.resultsLibrary = await app.mk.api.library.search(app.search.term, { types: "library-songs,library-albums,library-playlists,library-artists", limit: 25, offset: 0, }); }, async inLibrary(items = []) { let types = []; for (let item of items) { let type = item.type; if (type.slice(-1) != "s") { type += "s"; } type = type.replace("library-", ""); let id = item.attributes.playParams?.catalogId ?? item.attributes.playParams.id ?? item.id; let index = types.findIndex(function (type) { return type.type == this; }, type); if (index == -1) { types.push({ type: type, id: [id] }); } else { types[index].id.push(id); } } let types2 = types.map(function (item) { return { [`ids[${item.type}]`]: [item.id], }; }); types2 = types2.reduce(function (result, item) { var key = Object.keys(item)[0]; //first property: a, b, c result[key] = item[key]; return result; }, {}); return ( await this.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}`, { ...{ "omit[resource]": "autos", relate: "library", fields: "inLibrary", }, ...types2, }) ).data.data; }, isInLibrary(playParams) { let self = this; let id = ""; // ugly code to check if current playback item is in library if (typeof playParams == "undefined") { return true; } if (playParams["isLibrary"]) { return true; } else if (playParams["catalogId"]) { id = playParams["catalogId"]; } else if (playParams["id"]) { id = playParams["id"]; } let found = this.library.songs.listing.filter((item) => { if (item["attributes"]) { if (item["attributes"]["playParams"] && item["attributes"]["playParams"]["catalogId"] == id) { return item; } } }); if (found.length != 0) { return true; } else { return false; } }, mkReady() { if (this.mk["nowPlayingItem"]) { return true; } else { return false; } }, getMediaItemArtwork(url, height = 64, width) { if (typeof url == "undefined" || url == "") { return "./assets/MissingArtwork.svg"; } height = parseInt(height * window.devicePixelRatio); if (width) { width = parseInt(width * window.devicePixelRatio); } let newurl = `${url .replace("{w}", width ?? height) .replace("{h}", height) .replace("{f}", "webp") .replace("{c}", width === 900 || width === 380 || width === 600 ? "sr" : "cc")}`; if (newurl.includes("900x516")) { newurl = newurl.replace("900x516cc", "900x516sr").replace("900x516bb", "900x516sr"); } return newurl; }, _rgbToRgb(rgb = [0, 0, 0]) { // if rgb return `rgb(${rgb[0]},${rgb[1]},${rgb[2]})`; }, getNowPlayingArtworkBG(size = 32, force = false) { let self = this; if (typeof this.mk.nowPlayingItem === "undefined") return; let bginterval = setInterval(() => { if (!this.mkReady()) { return ""; } try { if ((this.mk.nowPlayingItem && this.mk.nowPlayingItem["id"] != this.currentTrackID && document.querySelector(".bg-artwork")) || force) { if (document.querySelector(".bg-artwork")) { clearInterval(bginterval); } this.currentTrackID = this.mk.nowPlayingItem["id"]; document.querySelector(".bg-artwork").src = ""; if (this.mk["nowPlayingItem"]["attributes"]["artwork"]["url"]) { getBase64FromUrl(this.mk["nowPlayingItem"]["attributes"]["artwork"]["url"].replace("{w}", size).replace("{h}", size)).then((img) => { document.querySelectorAll(".bg-artwork").forEach((artwork) => { artwork.src = img; }); self.$store.commit("setLCDArtwork", img); }); try { clearInterval(bginterval); } catch (err) {} } else { this.setLibraryArtBG(); } } else if (this.mk.nowPlayingItem["id"] == this.currentTrackID) { try { clearInterval(bginterval); } catch (err) {} } } catch (e) { if (this.mk.nowPlayingItem && this.mk.nowPlayingItem["id"] && document.querySelector(".bg-artwork")) { this.setLibraryArtBG(); try { clearInterval(bginterval); } catch (err) {} } } }, 200); }, async getCurrentArtURL() { try { let artworkSize = 50; if (app.getThemeDirective("lcdArtworkSize") != "") { artworkSize = app.getThemeDirective("lcdArtworkSize"); } else if (this.cfg.visual.directives.windowLayout == "twopanel") { artworkSize = 110; } this.currentArtUrl = ""; this.currentArtUrlRaw = ""; if ( app.mk.nowPlayingItem != null && app.mk.nowPlayingItem.attributes != null && app.mk.nowPlayingItem.attributes.artwork != null && app.mk.nowPlayingItem.attributes.artwork.url != null && app.mk.nowPlayingItem.attributes.artwork.url != "" ) { this.currentArtUrlRaw = this.mk["nowPlayingItem"]["attributes"]["artwork"]["url"] ?? ""; this.currentArtUrl = (this.mk["nowPlayingItem"]["attributes"]["artwork"]["url"] ?? "").replace("{w}", artworkSize).replace("{h}", artworkSize); if (this.mk.nowPlayingItem._assets[0].artworkURL) { this.currentArtUrl = this.mk.nowPlayingItem._assets[0].artworkURL; } try { // document.querySelector('.app-playback-controls .artwork').style.setProperty('--artwork', `url("${this.currentArtUrl}")`); } catch (e) {} } else { let data = await this.mk.api.v3.music(`/v1/me/library/songs/${this.mk.nowPlayingItem.id}`); data = data.data.data[0]; if (data != null && data !== "" && data.attributes != null && data.attributes.artwork != null) { this.currentArtUrlRaw = data["attributes"]["artwork"]["url"] ?? ""; this.currentArtUrl = (data["attributes"]["artwork"]["url"] ?? "").replace("{w}", artworkSize).replace("{h}", artworkSize); if (this.mk.nowPlayingItem._assets[0].artworkURL) { this.currentArtUrl = this.mk.nowPlayingItem._assets[0].artworkURL; } ipcRenderer.send("updateRPCImage", this.currentArtUrl ?? ""); try { // document.querySelector('.app-playback-controls .artwork').style.setProperty('--artwork', `url("${this.currentArtUrl}")`); } catch (e) {} } else { this.currentArtUrlRaw = ""; this.currentArtUrl = ""; try { // document.querySelector('.app-playback-controls .artwork').style.setProperty('--artwork', `url("${this.currentArtUrl}")`); } catch (e) {} } } } catch (e) {} }, async setLibraryArt() { if (typeof this.mk.nowPlayingItem === "undefined") return; try { let data = await this.mk.api.v3.music(`/v1/me/library/songs/${this.mk.nowPlayingItem.id}`); data = data.data.data[0]; if (data != null && data !== "") { document.querySelector(".app-playback-controls .artwork").style.setProperty("--artwork", 'url("' + data["attributes"]["artwork"]["url"].toString() + '")'); } else { document.querySelector(".app-playback-controls .artwork").style.setProperty("--artwork", `url("")`); } } catch (e) {} }, async setLibraryArtBG() { if (typeof this.mk.nowPlayingItem === "undefined") return; try { let data = await this.mk.api.v3.music(`/v1/me/library/songs/${this.mk.nowPlayingItem.id}`); data = data.data.data[0]; if (data != null && data !== "") { getBase64FromUrl(data["attributes"]["artwork"]["url"].toString()).then((img) => { document.querySelector(".bg-artwork").forEach((artwork) => { artwork.src = img; }); self.$store.commit("setLCDArtwork", img); }); } } catch (e) {} }, quickPlay(query) { let self = this; MusicKit.getInstance() .api.search(query, { limit: 2, types: "songs" }) .then(function (data) { MusicKit.getInstance() .setQueue({ song: data["songs"]["data"][0]["id"], parameters: { l: app.mklang }, }) .then(function (queue) { MusicKit.getInstance().play(); setTimeout(() => { self.$forceUpdate(); }, 1000); }); }); }, async getRating(item) { let type = item.type.slice(-1) === "s" ? item.type : item.type + "s"; let id = item.attributes?.playParams?.catalogId ? item.attributes.playParams.catalogId : item.attributes?.playParams?.id ?? item.id; if (item.id != null && item.id.toString().startsWith("i.")) { if (!type.startsWith("library-")) { type = "library-" + type; } id = item.id; } let response = await this.mk.api.v3.music(`/v1/me/ratings/${type}?platform=web&ids=${type.includes("library") ? item.id : id}`); if (response.data.data.length != 0) { let value = response.data.data[0].attributes.value; return value; } else { return 0; } }, love(item) { let type = item.type.slice(-1) === "s" ? item.type : item.type + "s"; let id = item.attributes?.playParams?.catalogId ? item.attributes.playParams.catalogId : item.attributes?.playParams?.id ?? item.id; if (item.id != null && item.id.toString().startsWith("i.")) { if (!type.startsWith("library-")) { type = "library-" + type; } id = item.id; } this.mk.api.v3.music( `/v1/me/ratings/${type}/${id}`, {}, { fetchOptions: { method: "PUT", body: JSON.stringify({ type: "rating", attributes: { value: 1, }, }), }, } ); }, dislike(item) { let type = item.type.slice(-1) === "s" ? item.type : item.type + "s"; let id = item.attributes?.playParams?.catalogId ? item.attributes.playParams.catalogId : item.attributes?.playParams?.id ?? item.id; if (item.id != null && item.id.toString().startsWith("i.")) { if (!type.startsWith("library-")) { type = "library-" + type; } id = item.id; } this.mk.api.v3.music( `/v1/me/ratings/${type}/${id}`, {}, { fetchOptions: { method: "PUT", body: JSON.stringify({ type: "rating", attributes: { value: -1, }, }), }, } ); }, unlove(item) { let type = item.type.slice(-1) === "s" ? item.type : item.type + "s"; let id = item.attributes.playParams.catalogId ? item.attributes.playParams.catalogId : item.id; if (item.id.startsWith("i.")) { if (!type.startsWith("library-")) { type = "library-" + type; } id = item.id; } this.mk.api.v3.music( `/v1/me/ratings/${type}/${id}`, {}, { fetchOptions: { method: "DELETE", }, } ); }, checkScrollDirectionIsUp(event) { if (event.wheelDelta) { return event.wheelDelta > 0; } return event.deltaY < 0; }, volumeUp() { if (app.mk.volume + app.cfg.audio.volumeStep > app.cfg.audio.maxVolume) { app.mk.volume = app.cfg.audio.maxVolume; } else { app.mk.volume = (Math.floor(app.mk.volume * 100) + app.cfg.audio.volumeStep * 100) / 100; } }, volumeDown() { if (app.mk.volume - app.cfg.audio.volumeStep < 0) { app.mk.volume = 0; } else { app.mk.volume = (Math.floor(app.mk.volume * 100) - app.cfg.audio.volumeStep * 100) / 100; } }, volumeWheel(event) { app.checkScrollDirectionIsUp(event) ? this.volumeUp() : this.volumeDown(); }, muteButtonPressed() { if (this.cfg.audio.muted) { this.mk.volume = this.cfg.audio.lastVolume; this.cfg.audio.muted = false; } else { this.cfg.audio.lastVolume = this.cfg.audio.volume; this.mk.volume = 0; this.cfg.audio.muted = true; } }, checkMuteChange() { if (this.cfg.audio.muted) { this.cfg.audio.muted = false; } }, async apiCall(url, callback) { const xmlHttp = new XMLHttpRequest(); xmlHttp.onreadystatechange = (e) => { if (xmlHttp.readyState !== 4) { return; } if (xmlHttp.status === 200) { // console.log('SUCCESS', xmlHttp.responseText); callback(JSON.parse(xmlHttp.responseText)); } else { console.warn("request_error"); } }; xmlHttp.open("GET", url); xmlHttp.setRequestHeader("Authorization", "Bearer " + MusicKit.getInstance().developerToken); xmlHttp.setRequestHeader("Music-User-Token", "" + MusicKit.getInstance().musicUserToken); xmlHttp.setRequestHeader("Accept", "application/json"); xmlHttp.setRequestHeader("Content-Type", "application/json"); xmlHttp.responseType = "text"; xmlHttp.send(); }, fetchPlaylist(id, callback) { // id can be found in playlist.attributes.playParams.globalId // this.mk.api. this.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/playlists/${id}`).then((res) => { callback(res.data.data[0]); }); // tracks are found in relationship.data }, setAirPlayCodeUI() { this.modals.airplayPW = true; }, sendAirPlaySuccess() { notyf.success("Device paired successfully!"); }, sendAirPlayFailed() { notyf.success("Device paring failed!"); }, airplayDisconnect(dropped, array = []) { // if (dropped) { // let [ipv4, ipport, sepassword, title, artist, album, artworkURL, txt, airplay2dv] = array; // ipcRenderer.send("performAirplayPCM", ipv4, ipport, sepassword, title, artist, album, artworkURL, txt, airplay2dv); // } else { // app.activeCasts = []; // notyf.error("Devices disconnected!"); // } }, windowFocus(val) { if (val) { document.querySelectorAll(".animated-artwork-video").forEach((el) => { el.play(); }); document.querySelector("body").classList.remove("stopanimation"); document.body.setAttribute("focus-state", "focused"); this.animateBackground = true; } else { document.querySelectorAll(".animated-artwork-video").forEach((el) => { el.pause(); }); document.querySelector("body").classList.add("stopanimation"); document.body.setAttribute("focus-state", "blurred"); this.animateBackground = false; } }, async nowPlayingContextMenu(event) { let self = this; let data_type = this.mk.nowPlayingItem.playParams.kind; let item_id = this.mk.nowPlayingItem.attributes.playParams.id ?? this.mk.nowPlayingItem.id; let isLibrary = this.mk.nowPlayingItem.attributes.playParams.isLibrary ?? false; let params = { "fields[songs]": "inLibrary", "fields[albums]": "inLibrary", relate: "library", t: "1", }; app.selectedMediaItems = []; app.select_selectMediaItem(item_id, data_type, 0, "12344", isLibrary); let useMenu = "normal"; let menus = { multiple: { items: [], }, normal: { headerItems: [ { icon: "./assets/feather/heart.svg", id: "love", name: app.getLz("action.love"), hidden: false, disabled: true, action: function () { app.love(app.mk.nowPlayingItem); }, }, { icon: "./assets/feather/heart.svg", id: "unlove", active: true, name: app.getLz("action.unlove"), hidden: true, action: function () { app.unlove(app.mk.nowPlayingItem); }, }, { icon: "./assets/feather/thumbs-down.svg", id: "dislike", name: app.getLz("action.dislike"), hidden: false, disabled: true, action: function () { app.dislike(app.mk.nowPlayingItem); }, }, { icon: "./assets/feather/thumbs-down.svg", id: "undo_dislike", name: app.getLz("action.undoDislike"), active: true, hidden: true, action: function () { app.unlove(app.mk.nowPlayingItem); }, }, ], items: [ { icon: "./assets/feather/plus.svg", id: "addToLibrary", name: app.getLz("action.addToLibrary") + " ...", disabled: true, action: function () { app.addToLibrary(app.mk.nowPlayingItem.id); }, }, { id: "removeFromLibrary", icon: "./assets/feather/x-circle.svg", name: app.getLz("action.removeFromLibrary"), hidden: true, action: function () { self.removeFromLibrary(); }, }, { icon: "./assets/feather/list.svg", name: app.getLz("action.addToPlaylist") + " ...", action: function () { app.promptAddToPlaylist(); }, }, { icon: "./assets/feather/radio.svg", name: app.getLz("action.startRadio"), action: function () { app.mk.setStationQueue({ song: app.mk.nowPlayingItem.id }).then(() => { app.mk.play(); app.selectedMediaItems = []; }); }, }, { icon: "./assets/feather/user.svg", name: app.getLz("action.goToArtist"), action: function () { app.appRoute(`artist/${app.mk.nowPlayingItem.relationships.artists.data[0].id}`); }, }, { icon: "./assets/feather/disc.svg", name: app.getLz("action.goToAlbum"), action: function () { app.appRoute(`album/${app.mk.nowPlayingItem.relationships.albums.data[0].id}`); }, }, { id: "showInMusic", icon: "./assets/music.svg", hidden: true, name: app.getLz("action.showInAppleMusic"), action: function () { app.routeView(app.mk.nowPlayingItem._container); }, }, { icon: "./assets/feather/share.svg", name: app.getLz("action.share"), action: function () { app.mkapi(app.mk.nowPlayingItem.attributes?.playParams?.kind ?? app.mk.nowPlayingItem.type ?? "songs", false, app.mk.nowPlayingItem._songId ?? app.mk.nowPlayingItem.songId ?? app.mk.nowPlayingItem.id ?? "").then((u) => { app.copyToClipboard(u.data.data.length && u.data.data.length > 0 ? u.data.data[0].attributes.url : u.data.data.attributes.url); }); }, }, { icon: "./assets/feather/share.svg", name: `${app.getLz("action.share")} (song.link)`, action: function () { app.mkapi(app.mk.nowPlayingItem.attributes?.playParams?.kind ?? app.mk.nowPlayingItem.type ?? "songs", false, app.mk.nowPlayingItem._songId ?? app.mk.nowPlayingItem.songId ?? app.mk.nowPlayingItem.id ?? "").then((u) => { app.songLinkShare(u.data.data.length && u.data.data.length > 0 ? u.data.data[0].attributes.url : u.data.data.attributes.url); }); }, }, { id: "equalizer", icon: "../views/svg/speaker.svg", name: app.getLz("term.equalizer"), hidden: false, action: function () { app.modals.equalizer = true; app.modals.audioSettings = false; }, }, { id: "audioLab", icon: "../views/svg/speaker.svg", name: app.getLz("settings.option.audio.audioLab"), hidden: false, action: function () { app.openSettingsPage("audiolabs"); }, }, ], }, }; /* if (this.cfg.advanced.AudioContext) { menus.normal.items.find(i => i.id === 'audioLab').hidden = false menus.normal.items.find(i => i.id === 'equalizer').hidden = false } */ if (this.contextExt) { if (this.contextExt.normal) { menus.normal.items = menus.normal.items.concat(this.contextExt.normal); } } const nowPlayingContainer = app.mk.nowPlayingItem._container; if (nowPlayingContainer && nowPlayingContainer["attributes"] && nowPlayingContainer.name != "station") { menus.normal.items.find((x) => x.id == "showInMusic").hidden = false; } this.showMenuPanel(menus[useMenu], event); try { // if its a radio station, then change the attributes to match a song const nowPlayingItem = JSON.parse(JSON.stringify(this.mk.nowPlayingItem)); if (nowPlayingItem.type == "radioStation" && app.mk.nowPlayingItem.id != -1) { nowPlayingItem.type = "song"; nowPlayingItem.attributes.playParams.catalogId = app.mk.nowPlayingItem.id; nowPlayingItem.attributes.playParams.id = app.mk.nowPlayingItem.id; nowPlayingItem.id = app.mk.nowPlayingItem.id; } let result = await this.inLibrary([nowPlayingItem]); if (result[0].attributes.inLibrary) { menus.normal.items.find((x) => x.id == "addToLibrary").hidden = true; menus.normal.items.find((x) => x.id == "removeFromLibrary").hidden = false; } else { menus.normal.items.find((x) => x.id == "addToLibrary").disabled = false; } } catch (e) { e = null; } try { let rating = await app.getRating(app.mk.nowPlayingItem); if (rating == 0) { menus.normal.headerItems.find((x) => x.id == "love").disabled = false; menus.normal.headerItems.find((x) => x.id == "dislike").disabled = false; } else if (rating == 1) { menus.normal.headerItems.find((x) => x.id == "unlove").hidden = false; menus.normal.headerItems.find((x) => x.id == "love").hidden = true; } else if (rating == -1) { menus.normal.headerItems.find((x) => x.id == "undo_dislike").hidden = false; menus.normal.headerItems.find((x) => x.id == "dislike").hidden = true; } } catch (err) {} }, openSettingsPage(page) { switch (page) { case "general": this.$store.state.pageState.settings.currentTabIndex = 0; break; case "audio": this.$store.state.pageState.settings.currentTabIndex = 1; break; case "audiolabs": this.$store.state.pageState.settings.currentTabIndex = 2; break; case "styles": this.$store.state.pageState.settings.currentTabIndex = 3; break; case "visual": this.$store.state.pageState.settings.currentTabIndex = 4; break; case "github-plugins": this.$store.state.pageState.settings.currentTabIndex = 5; break; case "lyrics": this.$store.state.pageState.settings.currentTabIndex = 6; break; case "connectivity": this.$store.state.pageState.settings.currentTabIndex = 7; break; case "advanced": this.$store.state.pageState.settings.currentTabIndex = 8; break; case "keybindings": this.$store.state.pageState.settings.currentTabIndex = 9; break; case "github-themes": this.$store.state.pageState.settings.currentTabIndex = 10; break; } app.modals.settings = true; }, fullscreen(flag) { this.fullscreenState = flag; if (flag) { ipcRenderer.send("setFullScreen", true); app.appMode = "fullscreen"; document.addEventListener("keydown", (event) => { if (event.key === "Escape" && app.appMode === "fullscreen") { this.fullscreen(false); } }); } else { ipcRenderer.send("setFullScreen", false); app.appMode = "player"; } }, pip() { document.querySelector("video#apple-music-video-player").requestPictureInPicture(); // .then(pictureInPictureWindow => { // pictureInPictureWindow.addEventListener("resize", () => { // console.log("[PIP] Resized") // }, false); // }) }, miniPlayer(flag) { if (flag) { this.tmpWidth = window.innerWidth; this.tmpHeight = window.innerHeight; this.tmpX = window.screenX; this.tmpY = window.screenY; ipcRenderer.send("unmaximize"); ipcRenderer.send("windowmin", 250, 250); if (this.miniTmpX !== "" && this.miniTmpY !== "") ipcRenderer.send("windowmove", this.miniTmpX, this.miniTmpY); ipcRenderer.send("windowresize", 300, 300, false); app.appMode = "mini"; } else { this.miniTmpX = window.screenX; this.miniTmpY = window.screenY; ipcRenderer.send("windowmin", 844, 410); ipcRenderer.send("windowresize", this.tmpWidth, this.tmpHeight, false); ipcRenderer.send("windowmove", this.tmpX, this.tmpY); ipcRenderer.send("windowontop", false); //this.cfg.visual.miniplayer_top_toggle = true; app.appMode = "player"; } }, pinMiniPlayer(status = false) { if (!status) { if (!this.cfg.visual.miniplayer_top_toggle) { ipcRenderer.send("windowontop", true); this.cfg.visual.miniplayer_top_toggle = true; } else { ipcRenderer.send("windowontop", false); this.cfg.visual.miniplayer_top_toggle = false; } } else { ipcRenderer.send("windowontop", this.cfg.visual.miniplayer_top_toggle ?? false); } }, formatTimezoneOffset: (e = new Date()) => { let leadingZeros = (e, s = 2) => { let n = "" + e; for (; n.length < s; ) n = "0" + n; return n; }; const s = e.getTimezoneOffset(), n = Math.floor(Math.abs(s) / 60), d = Math.round(Math.abs(s) % 60); let h = "+"; return 0 !== s && (h = s > 0 ? "-" : "+"), `${h}${leadingZeros(n, 2)}:${leadingZeros(d, 2)}`; }, toggleHideUserInfo() { if (this.chrome.hideUserInfo) { this.cfg.visual.showuserinfo = true; this.chrome.hideUserInfo = false; } else { this.cfg.visual.showuserinfo = false; this.chrome.hideUserInfo = true; } }, isElementOverflowing(selector) { try { let element = document.querySelector(selector); var overflowX = element.offsetWidth < element.scrollWidth, overflowY = element.offsetHeight < element.scrollHeight; element.setAttribute("data-value", "\xa0\xa0\xa0\xa0" + element.textContent); return overflowX || overflowY; } catch (e) { return false; } }, async showWebRemoteQR() { //this.webremoteqr = await ipcRenderer.invoke('setRemoteQR','') this.webremoteurl = await ipcRenderer.invoke("showQR", ""); //this.modals.qrcode = true; }, checkMarquee() { if (isElementOverflowing("#app-main > div.app-chrome > div.app-chrome--center > div > div > div.playback-info > div.song-artist") == true) { document.getElementsByClassName("song-artist")[0].classList.add("marquee"); document.getElementsByClassName("song-artist")[1].classList.add("marquee-after"); } if (isElementOverflowing("#app-main > div.app-chrome > div.app-chrome--center > div > div > div.playback-info > div.song-name") == true) { document.getElementsByClassName("song-name")[0].classList.add("marquee"); document.getElementsByClassName("song-name")[1].classList.add("marquee-after"); } }, closeWindow() { ipcRenderer.send("close"); }, darwinShare(url) { ipcRenderer.send("share-menu", url); }, arrayToChunk(arr, chunkSize) { let R = []; for (let i = 0, len = arr.length; i < len; i += chunkSize) { R.push(arr.slice(i, i + chunkSize)); } return R; }, SpacePause() { const elems = document.querySelectorAll("input"); for (let elem of elems) { if (elem === document.activeElement) { return; } } if (!this.isDev) { // disable in dev mode to keep my sanity MusicKitInterop.playPause(); } }, async MKJSLang() { let u = this.cfg.general.language; // use MusicKit.getInstance or crash try { let item = await MusicKit.getInstance().api.v3.music(`v1/storefronts/${app.mk.storefrontId}`); let langcodes = item.data.data[0].attributes.supportedLanguageTags; if (langcodes) langcodes = langcodes.map(function (u) { return u.replace(/-Han[s|t]/i, "").toLowerCase(); }); console.log(langcodes); let sellang = ""; if (u && langcodes.includes(u.toLowerCase().replace("_", "-"))) { sellang = u.toLowerCase().replace("_", "-"); } else if (u && u.includes("_") && langcodes.includes(u.toLowerCase().replace("_", "-").split("-")[0])) { sellang = u.toLowerCase().replace("_", "-").split("-")[0]; } if (sellang == "") sellang = item.data.data[0].attributes.defaultLanguageTag.toLowerCase(); // Fix weird locales: if (sellang == "iw") sellang = "iw-il"; sellang = sellang.replace(/-Han[s|t]/i, "").toLowerCase(); console.log(sellang); return await sellang; } catch (err) { console.log("locale err", err); let langcodes = [ "af", "sq", "ar", "eu", "bg", "be", "ca", "zh", "zh-tw", "zh-cn", "zh-hk", "zh-sg", "hr", "cs", "da", "nl", "nl-be", "en", "en-us", "en-eg", "en-au", "en-gb", "en-ca", "en-nz", "en-ie", "en-za", "en-jm", "en-bz", "en-tt", "en-001", "et", "fo", "fa", "fi", "fr", "fr-ca", "gd", "de", "de-ch", "el", "he", "hi", "hu", "is", "id", "it", "ja", "ko", "lv", "lt", "mk", "mt", "no", "nb", "nn", "pl", "pt-br", "pt", "rm", "ro", "ru", "sr", "sk", "sl", "es", "es-mx", "es-419", "sv", "th", "ts", "tn", "tr", "uk", "ur", "ve", "vi", "xh", "yi", "zu", "ms", "iw", "lo", "tl", "kk", "ta", "te", "bn", "ga", "ht", "la", "pa", "sa", ]; let sellang = "en"; if (u && langcodes.includes(u.toLowerCase().replace("_", "-"))) { sellang = u.toLowerCase().replace("_", "-"); } else if (u && u.includes("_") && langcodes.includes(u.toLowerCase().replace("_", "-").split("-")[0])) { sellang = u.toLowerCase().replace("_", "-").split("-")[0]; } if (sellang.startsWith("en") && this.mk.storefrontId != "us") sellang = "en-gb"; return await sellang; } }, skipToNextItem() { app.prevButtonBackIndicator = false; // app.mk.skipToNextItem() is buggy somehow so use this if (this.mk.queue.nextPlayableItemIndex != -1 && this.mk.queue.nextPlayableItemIndex != null) this.mk.changeToMediaAtIndex(this.mk.queue.nextPlayableItemIndex); }, skipToPreviousItem() { // app.mk.skipToPreviousItem() is buggy somehow so use this if (this.mk.queue.previousPlayableItemIndex != -1 && this.mk.queue.previousPlayableItemIndex != null) this.mk.changeToMediaAtIndex(this.mk.queue.previousPlayableItemIndex); }, mediaKeyFixes() { navigator.mediaSession.setActionHandler("previoustrack", function () { app.prevButton(); }); navigator.mediaSession.setActionHandler("nexttrack", function () { app.skipToNextItem(); }); }, authCC() { ipcRenderer.send("cc-auth"); }, _playRadioStream(e) { var xhr = new XMLHttpRequest(); xhr.onreadystatechange = process; xhr.open("GET", e, true); xhr.send(); let self = this; function process() { if (xhr.readyState == 4) { let sources = xhr.responseText.match(/^(?!#)(?!\s).*$/gm).filter(function (element) { return element; }); // Load first source let src = sources[0]; app.mk._services.mediaItemPlayback._currentPlayer._playAssetURL(src, false); } } }, confirm(message, callback) { bootbox.confirm(this.getBootboxParams(null, message, callback)); }, prompt(title, callback) { bootbox.prompt(this.getBootboxParams(title, null, callback)); }, getBootboxParams(title, message, callback) { return { title: title, message: message, buttons: { confirm: { label: app.getLz("dialog.ok"), }, cancel: { label: app.getLz("dialog.cancel"), }, }, callback: function (result) { if (callback) callback(result); }, }; }, }, }); export { app };