From a2be3405bffe8cf2b97a237691a45e3542ce0b57 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 19 Apr 2026 07:46:56 +0000 Subject: [PATCH 1/5] fix download-time metadata enrichment and artist video interactions Agent-Logs-Url: https://github.com/DanTheMan827/monochrome/sessions/1ba1ce02-f54a-4d49-99db-1ea858734abe Co-authored-by: DanTheMan827 <790119+DanTheMan827@users.noreply.github.com> --- js/api.js | 51 ++++++++++++++++++++++++++++++++++++++++++++++++- js/downloads.js | 30 ++++++++++++++++++++--------- js/events.js | 26 +++++++++++++++++++++++++ js/ui.js | 6 +++--- 4 files changed, 100 insertions(+), 13 deletions(-) diff --git a/js/api.js b/js/api.js index 544917a41..988943b5f 100644 --- a/js/api.js +++ b/js/api.js @@ -289,6 +289,16 @@ export class LosslessAPI { normalized = { ...normalized, audioQuality: derivedQuality }; } + const copyrightText = + typeof normalized.copyright === 'string' + ? normalized.copyright + : normalized.copyright && typeof normalized.copyright === 'object' + ? normalized.copyright.text + : normalized.copyright; + if (copyrightText !== normalized.copyright) { + normalized = { ...normalized, copyright: copyrightText ?? '' }; + } + normalized.isUnavailable = isTrackUnavailable(normalized); return normalized.type == 'video' ? new PreparedVideo(normalized) : new PreparedTrack(normalized); @@ -312,6 +322,20 @@ export class LosslessAPI { normalized.artist = video.artists[0]; } + if (!normalized.imageId) { + const imageCandidate = video.imageId || video.squareImage || video.image || video.cover; + if (typeof imageCandidate === 'string' || typeof imageCandidate === 'number') { + normalized.imageId = imageCandidate; + } + } + + if (!normalized.image) { + const imageCandidate = video.image || video.squareImage || video.cover || normalized.imageId; + if (typeof imageCandidate === 'string' || typeof imageCandidate === 'number') { + normalized.image = imageCandidate; + } + } + return normalized; } @@ -1614,7 +1638,32 @@ export class LosslessAPI { } const id = input?.id || input; - const track = typeof input === 'object' ? input : await this.getTrack(id, downloadQuality); + const hasMissingDownloadMetadata = (candidate) => + candidate?.trackNumber == null || + (candidate?.volumeNumber == null && candidate?.discNumber == null) || + candidate?.album?.numberOfTracks == null; + + let track = typeof input === 'object' ? this.prepareTrack(input) : await this.getTrack(id, downloadQuality); + if (typeof input === 'object' && !track?.type?.toLowerCase?.().includes('video') && hasMissingDownloadMetadata(track)) { + try { + const fullTrack = await this.getTrackMetadata(id); + track = this.prepareTrack({ + ...fullTrack, + ...track, + trackNumber: track?.trackNumber ?? fullTrack?.trackNumber, + volumeNumber: track?.volumeNumber ?? fullTrack?.volumeNumber, + discNumber: track?.discNumber ?? fullTrack?.discNumber, + album: { + ...(fullTrack?.album || {}), + ...(track?.album || {}), + }, + artist: track?.artist || fullTrack?.artist, + artists: track?.artists?.length ? track.artists : fullTrack?.artists, + }); + } catch (e) { + console.warn('Failed to hydrate full track metadata for download:', e); + } + } const isVideo = track?.type?.toLowerCase().includes('video'); downloadQuality = isCustomFormat(downloadQuality) ? 'LOSSLESS' : downloadQuality; diff --git a/js/downloads.js b/js/downloads.js index 65476e483..fdef72c8f 100644 --- a/js/downloads.js +++ b/js/downloads.js @@ -315,8 +315,19 @@ function removeBulkDownloadTask(notifEl) { } async function downloadTrackBlob(track, quality, api, signal = null, onProgress = null) { + const tidalAPI = api.tidalAPI || api; + let downloadTrack = track; + try { + if (typeof tidalAPI.enrichTrack === 'function') { + const { enrichedTrack } = await tidalAPI.enrichTrack(track, { downloadQuality: quality }); + if (enrichedTrack) downloadTrack = enrichedTrack; + } + } catch (e) { + console.warn('Failed to enrich track metadata before bulk download:', e); + } + const blob = await api.downloadTrack(track.id, quality, undefined, { - track, + track: downloadTrack, signal, onProgress, triggerDownload: false, @@ -326,7 +337,7 @@ async function downloadTrackBlob(track, quality, api, signal = null, onProgress // Detect actual format from blob signature BEFORE adding metadata const extension = await getExtensionFromBlob(blob); - return { blob, extension }; + return { blob, extension, track: downloadTrack }; } async function bulkDownload({ @@ -365,7 +376,7 @@ async function bulkDownload({ updateBulkDownloadProgress(notification, i, tracks.length, trackTitle); try { - const { blob, extension } = await downloadTrackBlob(track, quality, api, signal, (p) => { + const { blob, extension, track: enrichedTrack } = await downloadTrackBlob(track, quality, api, signal, (p) => { if (p instanceof DownloadProgress && p.totalBytes && p.receivedBytes) { fileFraction = p.receivedBytes / p.totalBytes; } else if (p instanceof SegmentedDownloadProgress && p.currentSegment && p.totalSegments) { @@ -375,7 +386,8 @@ async function bulkDownload({ fileFraction = Math.min(fileFraction, 0.99); // Cap at 99% to avoid showing 100% before finalization updateBulkDownloadProgress(notification, i + fileFraction, tracks.length, trackTitle, p); }); - const filename = buildTrackFilename(track, quality, extension); + const effectiveTrack = enrichedTrack || track; + const filename = buildTrackFilename(effectiveTrack, quality, extension); const discNumber = discLayout.resolveDiscNumber(i); const discPath = separateByDisc ? `${getDiscFolderName(discNumber)}/${filename}` : filename; @@ -389,9 +401,9 @@ async function bulkDownload({ if (lyricsManager && lyricsSettings.shouldDownloadLyrics()) { try { - const lyricsData = await lyricsManager.fetchLyrics(track.id, track); + const lyricsData = await lyricsManager.fetchLyrics(effectiveTrack.id, effectiveTrack); if (lyricsData) { - const lrcContent = lyricsManager.generateLRCContent(lyricsData, track); + const lrcContent = lyricsManager.generateLRCContent(lyricsData, effectiveTrack); if (lrcContent) { const lrcFilename = filename.replace(/\.[^.]+$/, '.lrc'); yield { @@ -1079,7 +1091,7 @@ export async function downloadTrackWithMetadata( triggerDownload: false, }); - const finalFilename = buildTrackFilename(track, quality, await getExtensionFromBlob(blob)) + const finalFilename = buildTrackFilename(enrichedTrack, quality, await getExtensionFromBlob(blob)) .split('/') .pop(); @@ -1102,13 +1114,13 @@ export async function downloadTrackWithMetadata( if (lyricsManager && lyricsSettings.shouldDownloadLyrics()) { try { - const lyricsData = await lyricsManager.fetchLyrics(track.id, track); + const lyricsData = await lyricsManager.fetchLyrics(enrichedTrack.id, enrichedTrack); if (lyricsData) { await folderWriter.write( singleWriterEntry({ name: [...entryName.split('.').slice(0, -1), 'lrc'].join('.'), lastModified: new Date(), - input: lyricsManager.getLRC(lyricsData, track), + input: lyricsManager.getLRC(lyricsData, enrichedTrack), }) ); } diff --git a/js/events.js b/js/events.js index 4e1f4e813..b7a418d59 100644 --- a/js/events.js +++ b/js/events.js @@ -2273,6 +2273,32 @@ export function initializeTrackInteractions(player, api, mainContent, contextMen return; } + if (card.dataset.videoId) { + if (card.classList.contains('blocked')) return; + if (e.target.closest('.like-btn') || e.target.closest('.card-menu-btn')) { + return; + } + + e.preventDefault(); + const clickedVideo = trackDataStore.get(card); + if (!clickedVideo) return; + + const parentContainer = card.parentElement || mainContent; + const allVideoElements = Array.from(parentContainer.querySelectorAll('.video-card[data-video-id]')); + const videoList = allVideoElements.map((el) => trackDataStore.get(el)).filter(Boolean); + + if (videoList.length > 0) { + const startIndex = videoList.findIndex((v) => String(v.id) === String(card.dataset.videoId)); + player.setQueue(videoList, startIndex >= 0 ? startIndex : 0); + player.enableAutoplay(); + document.getElementById('shuffle-btn').classList.remove('active'); + player.playTrackFromQueue(); + } else { + player.playVideo(clickedVideo); + } + return; + } + const href = card.dataset.href; if (href) { // Allow native links inside card to work if any exist diff --git a/js/ui.js b/js/ui.js index a3680b93c..0390ca142 100644 --- a/js/ui.js +++ b/js/ui.js @@ -820,12 +820,12 @@ export class UIRenderer { const duration = formatTime(video.duration); const artistName = getTrackArtists(video); - const videoCoverCandidate = video.imageId || video.image || video.cover || null; + const videoCoverCandidate = video.imageId || video.image || video.squareImage || video.cover || null; const videoCoverUrl = videoCoverCandidate && (typeof videoCoverCandidate === 'string' || typeof videoCoverCandidate === 'number') ? this.api.getVideoCoverUrl(videoCoverCandidate) : null; - const coverFallback = video.image || video.cover; + const coverFallback = video.image || video.squareImage || video.cover; const coverPrimitive = coverFallback != null && (typeof coverFallback === 'string' || typeof coverFallback === 'number') ? coverFallback @@ -5513,7 +5513,7 @@ export class UIRenderer { const el = videosContainer.querySelector(`[data-video-id="${video.id}"]`); if (el) { trackDataStore.set(el, video); - await this.updateLikeState(el, 'track', video.id); + await this.updateLikeState(el, 'video', video.id); } } } else { From b6b074824f8a0f56224b9adcb4932ffd08205317 Mon Sep 17 00:00:00 2001 From: Daniel <790119+DanTheMan827@users.noreply.github.com> Date: Sun, 19 Apr 2026 04:18:15 -0500 Subject: [PATCH 2/5] Fix dash downloading --- js/api.js | 8 ++++++-- js/dash-downloader.ts | 1 + js/downloads.js | 6 +++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/js/api.js b/js/api.js index 988943b5f..af660bd85 100644 --- a/js/api.js +++ b/js/api.js @@ -1644,7 +1644,11 @@ export class LosslessAPI { candidate?.album?.numberOfTracks == null; let track = typeof input === 'object' ? this.prepareTrack(input) : await this.getTrack(id, downloadQuality); - if (typeof input === 'object' && !track?.type?.toLowerCase?.().includes('video') && hasMissingDownloadMetadata(track)) { + if ( + typeof input === 'object' && + !track?.type?.toLowerCase?.().includes('video') && + hasMissingDownloadMetadata(track) + ) { try { const fullTrack = await this.getTrackMetadata(id); track = this.prepareTrack({ @@ -1823,7 +1827,7 @@ export class LosslessAPI { if (streamUrl.startsWith('blob:')) { try { const downloader = new DashDownloader(); - blob = await downloader.downloadDashStream(getProxyUrl(streamUrl), { + blob = await downloader.downloadDashStream(streamUrl, { signal: options.signal, onProgress, calculateDashBytes: calculateDashBytes ?? true, diff --git a/js/dash-downloader.ts b/js/dash-downloader.ts index d328a1bef..3b322611d 100644 --- a/js/dash-downloader.ts +++ b/js/dash-downloader.ts @@ -1,5 +1,6 @@ import { AbortError } from './errorTypes'; import { SegmentedDownloadProgress } from './progressEvents'; +import { getProxyUrl } from './proxy-utils'; export interface DashDownloadOptions { onProgress?: MonochromeProgressListener; diff --git a/js/downloads.js b/js/downloads.js index fdef72c8f..a2c95b910 100644 --- a/js/downloads.js +++ b/js/downloads.js @@ -376,7 +376,11 @@ async function bulkDownload({ updateBulkDownloadProgress(notification, i, tracks.length, trackTitle); try { - const { blob, extension, track: enrichedTrack } = await downloadTrackBlob(track, quality, api, signal, (p) => { + const { + blob, + extension, + track: enrichedTrack, + } = await downloadTrackBlob(track, quality, api, signal, (p) => { if (p instanceof DownloadProgress && p.totalBytes && p.receivedBytes) { fileFraction = p.receivedBytes / p.totalBytes; } else if (p instanceof SegmentedDownloadProgress && p.currentSegment && p.totalSegments) { From 312e0ad02758424f405177ebc178f71276b7d433 Mon Sep 17 00:00:00 2001 From: Daniel <790119+DanTheMan827@users.noreply.github.com> Date: Sun, 19 Apr 2026 06:00:12 -0500 Subject: [PATCH 3/5] feat(vite): re-implement `/proxy` route This re-adds the proxy, but only for local development. If not dev, it uses the remote proxy. --- js/api.js | 4 +-- js/app.js | 3 +++ js/dash-downloader.ts | 4 +-- js/global.d.ts | 1 + js/hls-downloader.js | 6 ++--- js/player.js | 2 +- js/proxy-utils.js | 43 ++++++++++++++++++++++++++++++- vite.config.ts | 59 +++++++++++++++++++++++++++++++++++++++---- 8 files changed, 108 insertions(+), 14 deletions(-) diff --git a/js/api.js b/js/api.js index af660bd85..405239ff6 100644 --- a/js/api.js +++ b/js/api.js @@ -1846,7 +1846,7 @@ export class LosslessAPI { } else if (streamUrl.includes('.m3u8') || streamUrl.includes('application/vnd.apple.mpegurl')) { try { const downloader = new HlsDownloader(); - blob = await downloader.downloadHlsStream(getProxyUrl(streamUrl), { + blob = await downloader.downloadHlsStream(streamUrl, { signal: options.signal, onProgress, }); @@ -1871,7 +1871,7 @@ export class LosslessAPI { /* ignore HEAD failure; proceed with GET */ } - const response = await fetch(getProxyUrl(streamUrl), { + const response = await fetch(streamUrl, { cache: 'no-store', signal: options.signal, }); diff --git a/js/app.js b/js/app.js index 817722c02..0692030e8 100644 --- a/js/app.js +++ b/js/app.js @@ -56,6 +56,9 @@ import { SVG_RESET, } from './icons.js'; import { HiFiClient } from './HiFi.js'; +import { patchFetch } from './proxy-utils'; + +patchFetch(); // Capture real iOS state before spoofing (needed for background audio) if (typeof window !== 'undefined') { diff --git a/js/dash-downloader.ts b/js/dash-downloader.ts index 3b322611d..0c1d8d159 100644 --- a/js/dash-downloader.ts +++ b/js/dash-downloader.ts @@ -31,7 +31,7 @@ export class DashDownloader { await Promise.all( urls.map(async (url) => { - const result = await fetch(getProxyUrl(url), { method: 'HEAD', signal }); + const result = await fetch(url, { method: 'HEAD', signal }); if (result.ok) { const contentLength = result.headers.get('Content-Length'); @@ -76,7 +76,7 @@ export class DashDownloader { onProgress?.(new SegmentedDownloadProgress(downloadedBytes, totalSize ?? undefined, i, totalSegments)); - const url = getProxyUrl(urls[i]); + const url = urls[i]; const segmentResponse = await fetch(url, { signal }); if (!segmentResponse.ok) { diff --git a/js/global.d.ts b/js/global.d.ts index 3a8480ac6..46aefbea7 100644 --- a/js/global.d.ts +++ b/js/global.d.ts @@ -34,4 +34,5 @@ type WithRequiredKeys = { declare global { const __COMMIT_HASH__: string | undefined; + const __VITE_PROXY__: string | undefined; } diff --git a/js/hls-downloader.js b/js/hls-downloader.js index 3355e5c58..2ee56176a 100644 --- a/js/hls-downloader.js +++ b/js/hls-downloader.js @@ -7,12 +7,12 @@ export class HlsDownloader { async downloadHlsStream(masterUrl, options = {}) { const { onProgress, signal } = options; - const response = await fetch(getProxyUrl(masterUrl), { signal }); + const response = await fetch(masterUrl, { signal }); const masterText = await response.text(); const variantUrl = this.getBestVariantUrl(masterUrl, masterText); - const mediaResponse = await fetch(getProxyUrl(variantUrl), { signal }); + const mediaResponse = await fetch(variantUrl, { signal }); const mediaText = await mediaResponse.text(); const segments = this.parseMediaPlaylist(variantUrl, mediaText); @@ -30,7 +30,7 @@ export class HlsDownloader { onProgress?.(new SegmentedDownloadProgress(downloadedBytes, undefined, i, totalSegments)); const segmentUrl = segments[i]; - const segmentResponse = await fetch(getProxyUrl(segmentUrl), { signal }); + const segmentResponse = await fetch(segmentUrl, { signal }); if (!segmentResponse.ok) { throw new Error(`Failed to fetch segment ${i}: ${segmentResponse.status}`); diff --git a/js/player.js b/js/player.js index 16fdf0965..28f781964 100644 --- a/js/player.js +++ b/js/player.js @@ -148,7 +148,7 @@ export class Player { const uris = request.uris; for (let i = 0; i < uris.length; i++) { if (uris[i].includes('tidal.com')) { - uris[i] = getProxyUrl(uris[i]); + uris[i] = uris[i]; } } } diff --git a/js/proxy-utils.js b/js/proxy-utils.js index 92d6669fe..205229dca 100644 --- a/js/proxy-utils.js +++ b/js/proxy-utils.js @@ -1,4 +1,45 @@ +/* eslint-disable no-undef */ export const getProxyUrl = (url) => { if (window.__tidalOriginExtension) return url; - return `https://audio-proxy.binimum.org/proxy-audio?url=${url}`; + return `${__VITE_PROXY__}?url=${encodeURIComponent(url)}`; }; + +export function patchFetch() { + if (__VITE_PROXY__ && !window.__tidalOriginExtension) { + const originalSrcDescriptor = Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'src'); + + Object.defineProperty(HTMLMediaElement.prototype, 'src', { + set(value) { + console.log(value); + const alreadyProxied = value.includes(__VITE_PROXY__); + + if (!alreadyProxied) { + const realUrl = new URL(value, window.location.href).href; + if (originalSrcDescriptor && originalSrcDescriptor.set) { + originalSrcDescriptor.set.call(this, getProxyUrl(realUrl)); + } else { + this.setAttribute('src', getProxyUrl(realUrl)); + } + } + }, + get() { + return originalSrcDescriptor ? originalSrcDescriptor.get.call(this) : this.getAttribute('src'); + }, + }); + + const ogFetch = window.fetch; + window.fetch = async function (input, init, ...rest) { + let url = typeof input === 'string' ? new URL(input, window.location.href) : input.url; + if (url.href.includes(__VITE_PROXY__)) { + return await ogFetch(input, init, ...rest); + } else if ( + url.hostname.toLowerCase().endsWith('tidal.com') && + !url.hostname.toLowerCase().endsWith('api.tidal.com') + ) { + return await ogFetch(getProxyUrl(url.href), init, ...rest); + } else { + return await ogFetch(input, init, ...rest); + } + }; + } +} diff --git a/vite.config.ts b/vite.config.ts index 1b680762d..e1a748138 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,20 +1,66 @@ -import path from 'path'; import { defineConfig } from 'vite'; import { VitePWA } from 'vite-plugin-pwa'; import authGatePlugin from './vite-plugin-auth-gate.js'; +import path from 'path'; +import uploadPlugin from './vite-plugin-upload.js'; import blobAssetPlugin from './vite-plugin-blob.js'; import svgUse from './vite-plugin-svg-use.js'; -import uploadPlugin from './vite-plugin-upload.js'; // import purgecss from 'vite-plugin-purgecss'; -import { playwright } from '@vitest/browser-playwright'; -import { execSync } from 'child_process'; import purgecss from 'vite-plugin-purgecss'; +import { execSync } from 'child_process'; +import { playwright } from '@vitest/browser-playwright'; +import type { IncomingMessage, ServerResponse } from 'http'; function proxyAudioPlugin() { return { name: 'proxy-audio-dev', configureServer(server) { - // No longer needed: local proxy-audio middleware replaced by remote proxy + server.middlewares.use('/proxy', async (req: IncomingMessage, res: ServerResponse) => { + const url = new URL(req.url ?? '', 'http://localhost'); + const targetUrl = url.searchParams.get('url'); + + if (!targetUrl) { + res.writeHead(400); + res.end('Missing url parameter'); + return; + } + + try { + const headers = new Headers(); + headers.set('Origin', 'https://listen.tidal.com'); + headers.set('User-Agent', req.headers['user-agent']); + + const upstream = await fetch(targetUrl, { + method: req.method, + headers, + redirect: 'follow', + }); + + const resHead = new Headers(upstream.headers); + Object.entries({ + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, HEAD, OPTIONS', + 'Access-Control-Expose-Headers': '*', + }).forEach(([key, value]) => resHead.set(key, value)); + + res.writeHead(upstream.status, { ...Object.fromEntries(resHead.entries()) }); + + const reader = upstream.body?.getReader(); + + if (reader) { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + await res.write(value); + } + } + + res.end(); + } catch (error) { + res.writeHead(500); + res.end('Proxy Error: ' + error.message); + } + }); }, }; } @@ -44,6 +90,9 @@ export default defineConfig((_options) => { define: { __COMMIT_HASH__: JSON.stringify(commitHash), __VITEST__: !!process.env.VITEST, + __VITE_PROXY__: JSON.stringify( + _options.mode == 'development' ? '/proxy' : 'https://audio-proxy.binimum.org/proxy-audio' + ), }, worker: { format: 'es', From b77df88bb43343dd18239f816c375868be1eaa29 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 19 Apr 2026 21:18:44 +0000 Subject: [PATCH 4/5] fix artist page missing album aggregation Agent-Logs-Url: https://github.com/DanTheMan827/monochrome/sessions/8437a3f1-f7de-49d3-af64-b279ee108459 Co-authored-by: DanTheMan827 <790119+DanTheMan827@users.noreply.github.com> --- js/api.js | 40 +++++++++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/js/api.js b/js/api.js index 405239ff6..b81ac8d28 100644 --- a/js/api.js +++ b/js/api.js @@ -1157,6 +1157,23 @@ export class LosslessAPI { }; if (!options.lightweight) { + try { + // v2 /artist?id can return a partial album relationship set; merge with + // the dedicated releases route to avoid dropping albums on artist pages. + const releasesResponse = await this.fetchWithRetry(`/artist/?f=${artistId}&skip_tracks=true`); + const releasesJson = await releasesResponse.json(); + const releasesData = releasesJson.data || releasesJson; + const releaseItems = releasesData?.albums?.items || []; + for (const entry of releaseItems) { + const release = entry?.item || entry; + if (release?.id) { + albumMap.set(release.id, this.prepareAlbum(release)); + } + } + } catch (e) { + console.warn('Failed to fetch additional artist releases:', e); + } + try { const videoSearch = await this.searchVideos(artist.name); if (videoSearch && videoSearch.items) { @@ -1171,7 +1188,23 @@ export class LosslessAPI { } } - const rawReleases = Array.from(albumMap.values()).filter(matchesArtistId); + const topTracksPool = Array.from(trackMap.values()).filter(matchesArtistId); + for (const track of topTracksPool) { + if (!track?.album?.id || albumMap.has(track.album.id)) continue; + albumMap.set( + track.album.id, + this.prepareAlbum({ + ...track.album, + artist: track.artist || track.album.artist, + artists: track.artists?.length ? track.artists : track.album.artists, + }) + ); + } + + const topTrackAlbumIds = new Set(topTracksPool.map((track) => Number(track?.album?.id)).filter(Boolean)); + const rawReleases = Array.from(albumMap.values()).filter( + (album) => matchesArtistId(album) || topTrackAlbumIds.has(Number(album?.id)) + ); const allReleases = this.deduplicateAlbums(rawReleases).sort( (a, b) => new Date(b.releaseDate || 0) - new Date(a.releaseDate || 0) ); @@ -1179,10 +1212,7 @@ export class LosslessAPI { const eps = allReleases.filter((a) => a.type === 'EP' || a.type === 'SINGLE'); const albums = allReleases.filter((a) => !eps.includes(a)); - const topTracks = Array.from(trackMap.values()) - .filter(matchesArtistId) - .sort((a, b) => (b.popularity || 0) - (a.popularity || 0)) - .slice(0, 15); + const topTracks = topTracksPool.sort((a, b) => (b.popularity || 0) - (a.popularity || 0)).slice(0, 15); const videos = Array.from(videoMap.values()).sort( (a, b) => new Date(b.releaseDate || 0) - new Date(a.releaseDate || 0) From 1c73c2932473a5853f2a05f8b94ad5b7431b6473 Mon Sep 17 00:00:00 2001 From: Daniel <790119+DanTheMan827@users.noreply.github.com> Date: Mon, 27 Apr 2026 11:00:55 -0500 Subject: [PATCH 5/5] fix proxy --- js/api.js | 2 +- js/proxy-utils.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/js/api.js b/js/api.js index b81ac8d28..457e473c2 100644 --- a/js/api.js +++ b/js/api.js @@ -183,7 +183,7 @@ export class LosslessAPI { return response; } - const shouldTryNative = type !== 'streaming'; + const shouldTryNative = type !== 'streaming' || localStorage.getItem('allTidal') === 'true'; if (shouldTryNative) { try { diff --git a/js/proxy-utils.js b/js/proxy-utils.js index 205229dca..4b7f10464 100644 --- a/js/proxy-utils.js +++ b/js/proxy-utils.js @@ -34,7 +34,8 @@ export function patchFetch() { return await ogFetch(input, init, ...rest); } else if ( url.hostname.toLowerCase().endsWith('tidal.com') && - !url.hostname.toLowerCase().endsWith('api.tidal.com') + !url.hostname.toLowerCase().endsWith('api.tidal.com') && + !url.hostname.toLowerCase().endsWith('auth.tidal.com') ) { return await ogFetch(getProxyUrl(url.href), init, ...rest); } else {