diff --git a/app.go b/app.go index 2e31cb3..ddf4b25 100644 --- a/app.go +++ b/app.go @@ -510,15 +510,15 @@ func (a *App) DownloadTrack(req DownloadRequest) (DownloadResponse, error) { backend.CompleteDownloadItem(itemID, filename, 0) } - go func(fPath, track, artist, album, sID, cover, format string) { + go func(fPath, track, artist, album, sID, cover, format, source string) { + time.Sleep(2 * time.Second) + quality := "Unknown" - durationStr := "--:--" + durationStr := "0:00" meta, err := backend.GetTrackMetadata(fPath) - if err == nil && meta != nil { - if meta.BitsPerSample > 0 { - quality = fmt.Sprintf("%d-bit/%.1fkHz", meta.BitsPerSample, float64(meta.SampleRate)/1000.0) - } else if meta.Bitrate > 0 { + if err == nil { + if meta.Bitrate > 0 { quality = fmt.Sprintf("%dkbps/%.1fkHz", meta.Bitrate/1000, float64(meta.SampleRate)/1000.0) } else if meta.SampleRate > 0 { quality = fmt.Sprintf("%.1fkHz", float64(meta.SampleRate)/1000.0) @@ -539,6 +539,7 @@ func (a *App) DownloadTrack(req DownloadRequest) (DownloadResponse, error) { Quality: quality, Format: strings.ToUpper(format), Path: fPath, + Source: source, } if item.Format == "" || item.Format == "LOSSLESS" { @@ -554,7 +555,7 @@ func (a *App) DownloadTrack(req DownloadRequest) (DownloadResponse, error) { } backend.AddHistoryItem(item, "SpotiFLAC") - }(filename, req.TrackName, req.ArtistName, req.AlbumName, req.SpotifyID, req.CoverURL, req.AudioFormat) + }(filename, req.TrackName, req.ArtistName, req.AlbumName, req.SpotifyID, req.CoverURL, req.AudioFormat, req.Service) } return DownloadResponse{ diff --git a/backend/history.go b/backend/history.go index 11b6856..754db43 100644 --- a/backend/history.go +++ b/backend/history.go @@ -22,6 +22,7 @@ type HistoryItem struct { Quality string `json:"quality"` Format string `json:"format"` Path string `json:"path"` + Source string `json:"source"` Timestamp int64 `json:"timestamp"` } diff --git a/backend/spotfetch.go b/backend/spotfetch.go index c584d94..4ca171c 100644 --- a/backend/spotfetch.go +++ b/backend/spotfetch.go @@ -555,7 +555,7 @@ func FilterTrack(data map[string]interface{}, separator string, albumFetchData . copyrightData := getMap(albumData, "copyright") if len(copyrightData) > 0 { copyrightItems := getSlice(copyrightData, "items") - if copyrightItems != nil { + if len(copyrightItems) > 0 { for _, item := range copyrightItems { itemMap, ok := item.(map[string]interface{}) if !ok { @@ -574,7 +574,7 @@ func FilterTrack(data map[string]interface{}, separator string, albumFetchData . if len(tracksData) > 0 { discNumbers := make(map[int]bool) trackItems := getSlice(tracksData, "items") - if trackItems != nil { + if len(trackItems) > 0 { for _, item := range trackItems { itemMap, ok := item.(map[string]interface{}) if !ok { @@ -656,7 +656,7 @@ func FilterTrack(data map[string]interface{}, separator string, albumFetchData . albumArtistsString := "" albumLabel := "" - if albumFetchDataMap != nil && len(albumFetchDataMap) > 0 { + if len(albumFetchDataMap) > 0 { albumUnionData := getMap(getMap(albumFetchDataMap, "data"), "albumUnion") if len(albumUnionData) > 0 { albumArtists := extractArtists(getMap(albumUnionData, "artists")) @@ -957,21 +957,9 @@ func FilterPlaylist(data map[string]interface{}, separator string) map[string]in avatarData := getMap(ownerData, "avatar") if len(avatarData) > 0 { sources := getSlice(avatarData, "sources") - if sources != nil { - for _, source := range sources { - sourceMap, ok := source.(map[string]interface{}) - if !ok { - continue - } - if getFloat64(sourceMap, "width") == 300 { - avatarURL = getString(sourceMap, "url") - break - } - } - if avatarURL == nil && len(sources) > 0 { - if firstSource, ok := sources[0].(map[string]interface{}); ok { - avatarURL = getString(firstSource, "url") - } + if len(sources) > 0 { + if firstSource, ok := sources[0].(map[string]interface{}); ok { + avatarURL = getString(firstSource, "url") } } } @@ -1291,7 +1279,7 @@ func extractDiscographyItems(itemsData map[string]interface{}) []map[string]inte } func stripHTMLTags(s string) string { - re := regexp.MustCompile(`<[^>]*>`) + re := regexp.MustCompile(`(?s)<[^>]*>`) return re.ReplaceAllString(s, "") } diff --git a/frontend/src/components/HistoryPage.tsx b/frontend/src/components/HistoryPage.tsx index 9f34b4c..a5c488c 100644 --- a/frontend/src/components/HistoryPage.tsx +++ b/frontend/src/components/HistoryPage.tsx @@ -9,6 +9,7 @@ import { Pagination, PaginationContent, PaginationEllipsis, PaginationItem, Pagi import { GetDownloadHistory, ClearDownloadHistory, GetPreviewURL, GetFetchHistory, DeleteDownloadHistoryItem, DeleteFetchHistoryItem, ClearFetchHistoryByType } from "../../wailsjs/go/main/App"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import { openExternal } from "@/lib/utils"; +import { TidalIcon, QobuzIcon, AmazonIcon } from "./PlatformIcons"; const formatDate = (timestamp: number) => { const date = new Date(timestamp * 1000); const year = date.getFullYear(); @@ -30,6 +31,7 @@ interface DownloadHistoryItem { quality: string; format: string; path: string; + source: string; timestamp: number; } interface FetchHistoryItem { @@ -62,10 +64,35 @@ export function HistoryPage({ onHistorySelect }: HistoryPageProps) { const [fetchSearchQuery, setFetchSearchQuery] = useState(""); const [fetchCurrentPage, setFetchCurrentPage] = useState(1); const ITEMS_PER_PAGE = 50; + const getTrackLink = (spotifyId: string) => { + if (spotifyId?.startsWith("tidal_")) + return { url: `https://listen.tidal.com/track/${spotifyId.replace("tidal_", "")}`, label: "Open in Tidal" }; + if (spotifyId?.startsWith("qobuz_")) + return { url: `https://www.qobuz.com/track/${spotifyId.replace("qobuz_", "")}`, label: "Open in Qobuz" }; + if (spotifyId?.startsWith("amazon_")) + return { url: `https://music.amazon.com/tracks/${spotifyId.replace("amazon_", "")}`, label: "Open in Amazon Music" }; + if (spotifyId?.startsWith("deezer_")) + return { url: `https://www.deezer.com/track/${spotifyId.replace("deezer_", "")}`, label: "Open in Deezer" }; + return { url: `https://open.spotify.com/track/${spotifyId}`, label: "Open in Spotify" }; + }; + const getSourceIcon = (source: string) => { + const s = source?.toLowerCase() || ""; + if (s.includes("tidal")) + return ; + if (s.includes("qobuz")) + return ; + if (s.includes("amazon")) + return ; + if (s.includes("deezer")) + return ; + if (s.includes("spotify")) + return ; + return ; + }; const fetchDownloadHistory = async () => { try { const items = await GetDownloadHistory(); - setDownloadHistory(items || []); + setDownloadHistory((items || []) as unknown as DownloadHistoryItem[]); } catch (err) { console.error("Failed to fetch download history:", err); @@ -228,8 +255,8 @@ export function HistoryPage({ onHistorySelect }: HistoryPageProps) {

Downloads

- {downloadHistory.length > 0 && ( - {downloadHistory.length.toLocaleString('en-US')} + {filteredDownloadHistory.length > 0 && ( + {filteredDownloadHistory.length.toLocaleString('en-US')} )}
+
+ {getSourceIcon(item.source)} +
-

{playingPreviewId === item.id ? "Pause Preview" : "Play Preview"}

+

{item.source || "Unknown"}

+
+ + +
+ {!(item.spotify_id?.startsWith('tidal_') || item.spotify_id?.startsWith('qobuz_') || item.spotify_id?.startsWith('amazon_') || item.spotify_id?.startsWith('deezer_')) && ( + + + + + +

{playingPreviewId === item.id ? "Pause Preview" : "Play Preview"}

+
+
+
)} - -

Open in Spotify

+

{getTrackLink(item.spotify_id).label}