.status icons update

This commit is contained in:
afkarxyz
2026-04-14 06:35:18 +07:00
parent 4c5bba73ce
commit f123caf5b0
19 changed files with 105 additions and 20 deletions
+50 -1
View File
@@ -85,6 +85,42 @@ func containsStreamingURL(body []byte) bool {
return isStreamingURL(trimmedBody) return isStreamingURL(trimmedBody)
} }
func containsLRCLIBResults(body []byte) bool {
trimmedBody := strings.TrimSpace(string(body))
if trimmedBody == "" {
return false
}
var searchResults []map[string]interface{}
if err := json.Unmarshal(body, &searchResults); err == nil {
return len(searchResults) > 0
}
var exactResult map[string]interface{}
if err := json.Unmarshal(body, &exactResult); err == nil {
return len(exactResult) > 0
}
return false
}
func containsMusicBrainzResults(body []byte) bool {
trimmedBody := strings.TrimSpace(string(body))
if trimmedBody == "" {
return false
}
var payload struct {
Count int `json:"count"`
Recordings []json.RawMessage `json:"recordings"`
}
if err := json.Unmarshal(body, &payload); err != nil {
return false
}
return payload.Count > 0 || len(payload.Recordings) > 0
}
func isStreamingURL(raw string) bool { func isStreamingURL(raw string) bool {
candidate := strings.TrimSpace(raw) candidate := strings.TrimSpace(raw)
if candidate == "" { if candidate == "" {
@@ -948,6 +984,10 @@ func (a *App) CheckAPIStatus(apiType string, apiURL string) bool {
checkURL = fmt.Sprintf("%s/api/track/360735657?quality=27", apiURL) checkURL = fmt.Sprintf("%s/api/track/360735657?quality=27", apiURL)
} else if apiType == "amazon" { } else if apiType == "amazon" {
checkURL = fmt.Sprintf("%s/status", apiURL) checkURL = fmt.Sprintf("%s/status", apiURL)
} else if apiType == "lrclib" {
checkURL = fmt.Sprintf("%s/api/search?artist_name=Adele&track_name=Hello", strings.TrimRight(apiURL, "/"))
} else if apiType == "musicbrainz" {
checkURL = fmt.Sprintf("%s/ws/2/recording?query=%s&fmt=json&limit=1", strings.TrimRight(apiURL, "/"), url.QueryEscape(`recording:"Hello" AND artist:"Adele"`))
} else { } else {
checkURL = apiURL checkURL = apiURL
} }
@@ -958,6 +998,7 @@ func (a *App) CheckAPIStatus(apiType string, apiURL string) bool {
return false, err return false, err
} }
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36") req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36")
req.Header.Set("Accept", "application/json")
maxRetries := 3 maxRetries := 3
for i := 0; i < maxRetries; i++ { for i := 0; i < maxRetries; i++ {
@@ -981,7 +1022,15 @@ func (a *App) CheckAPIStatus(apiType string, apiURL string) bool {
return true, nil return true, nil
} }
if apiType != "amazon" && apiType != "qobuz" && apiType != "qbz" && statusCode == 200 { if apiType == "lrclib" && statusCode == 200 && containsLRCLIBResults(body) {
return true, nil
}
if apiType == "musicbrainz" && statusCode == 200 && containsMusicBrainzResults(body) {
return true, nil
}
if apiType != "amazon" && apiType != "qobuz" && apiType != "qbz" && apiType != "lrclib" && apiType != "musicbrainz" && statusCode == 200 {
return true, nil return true, nil
} }
} }
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 345 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

+3 -3
View File
@@ -1,6 +1,6 @@
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { RefreshCw, CheckCircle2, XCircle, Loader2 } from "lucide-react"; import { RefreshCw, CheckCircle2, XCircle, Loader2 } from "lucide-react";
import { TidalIcon, QobuzIcon, AmazonIcon } from "./PlatformIcons"; import { TidalIcon, QobuzIcon, AmazonIcon, LrclibIcon, MusicBrainzIcon } from "./PlatformIcons";
import { useApiStatus } from "@/hooks/useApiStatus"; import { useApiStatus } from "@/hooks/useApiStatus";
export function ApiStatusTab() { export function ApiStatusTab() {
const { sources, statuses, isCheckingAll, refreshAll } = useApiStatus(); const { sources, statuses, isCheckingAll, refreshAll } = useApiStatus();
@@ -12,12 +12,12 @@ export function ApiStatusTab() {
</Button> </Button>
</div> </div>
<div className="grid grid-cols-4 gap-4"> <div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
{sources.map((source) => { {sources.map((source) => {
const status = statuses[source.id] || "idle"; const status = statuses[source.id] || "idle";
return (<div key={source.id} className="flex items-center justify-between p-4 border rounded-lg bg-card text-card-foreground shadow-sm"> return (<div key={source.id} className="flex items-center justify-between p-4 border rounded-lg bg-card text-card-foreground shadow-sm">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
{source.type === "tidal" ? <TidalIcon className="w-5 h-5 shrink-0 text-muted-foreground"/> : source.type === "amazon" ? <AmazonIcon className="w-5 h-5 shrink-0 text-muted-foreground"/> : <QobuzIcon className="w-5 h-5 shrink-0 text-muted-foreground"/>} {source.type === "tidal" ? <TidalIcon className="w-5 h-5 shrink-0 text-muted-foreground"/> : source.type === "amazon" ? <AmazonIcon className="w-5 h-5 shrink-0 text-muted-foreground"/> : source.type === "lrclib" ? <LrclibIcon className="w-5 h-5 shrink-0 text-muted-foreground"/> : source.type === "musicbrainz" ? <MusicBrainzIcon className="w-5 h-5 shrink-0 text-muted-foreground"/> : <QobuzIcon className="w-5 h-5 shrink-0 text-muted-foreground"/>}
<p className="font-medium leading-none">{source.name}</p> <p className="font-medium leading-none">{source.name}</p>
</div> </div>
+47 -11
View File
@@ -1,11 +1,13 @@
import amazonMusicIcon from "../assets/icons/amazon-music.png"; import amazonMusicIcon from "../assets/icons/amzn.png";
import qobuzIcon from "../assets/icons/qobuz.png"; import lrclibIcon from "../assets/icons/lrclib.png";
import tidalIcon from "../assets/icons/tidal.png"; import musicBrainzDarkIcon from "../assets/icons/musicbrainz_d.png";
const PLATFORM_ICON_URLS = { import musicBrainzLightIcon from "../assets/icons/musicbrainz_l.png";
tidal: tidalIcon, import qobuzIcon from "../assets/icons/qbz.png";
qobuz: qobuzIcon, import songlinkDarkIcon from "../assets/icons/songlink_d.png";
amazon: amazonMusicIcon, import songlinkLightIcon from "../assets/icons/songlink_l.png";
} as const; import songstatsIcon from "../assets/icons/songstats.png";
import tidalDarkIcon from "../assets/icons/tidal_d.png";
import tidalLightIcon from "../assets/icons/tidal_l.png";
type PlatformIconProps = { type PlatformIconProps = {
className?: string; className?: string;
}; };
@@ -48,14 +50,48 @@ function PlatformIcon({ src, alt, className = "w-4 h-4", defaultClassName = "" }
.join(" "); .join(" ");
return <img src={src} alt={alt} className={imageClassName} loading="lazy" referrerPolicy="no-referrer"/>; return <img src={src} alt={alt} className={imageClassName} loading="lazy" referrerPolicy="no-referrer"/>;
} }
function ThemedPlatformIcon({ lightSrc, darkSrc, alt, className = "w-4 h-4", defaultClassName = "" }: {
lightSrc: string;
darkSrc: string;
alt: string;
className?: string;
defaultClassName?: string;
}) {
const cleanedClassName = sanitizeClassName(className);
const statusClasses = getStatusClasses(className);
const wrapperClassName = [
cleanedClassName || "w-4 h-4",
"relative inline-flex shrink-0",
!hasRoundedClass(cleanedClassName) ? defaultClassName : "",
statusClasses,
]
.filter(Boolean)
.join(" ");
return <span role="img" aria-label={alt} className={wrapperClassName}>
<img src={lightSrc} alt="" aria-hidden="true" className="h-full w-full object-contain dark:hidden" loading="lazy" referrerPolicy="no-referrer"/>
<img src={darkSrc} alt="" aria-hidden="true" className="hidden h-full w-full object-contain dark:block" loading="lazy" referrerPolicy="no-referrer"/>
</span>;
}
export function TidalIcon({ className = "w-4 h-4" }: PlatformIconProps) { export function TidalIcon({ className = "w-4 h-4" }: PlatformIconProps) {
return <PlatformIcon src={PLATFORM_ICON_URLS.tidal} alt="Tidal" className={className} defaultClassName="rounded-[4px]"/>; return <ThemedPlatformIcon lightSrc={tidalLightIcon} darkSrc={tidalDarkIcon} alt="Tidal" className={className} defaultClassName="rounded-[4px]"/>;
} }
export function QobuzIcon({ className = "w-4 h-4" }: PlatformIconProps) { export function QobuzIcon({ className = "w-4 h-4" }: PlatformIconProps) {
return <PlatformIcon src={PLATFORM_ICON_URLS.qobuz} alt="Qobuz" className={className}/>; return <PlatformIcon src={qobuzIcon} alt="Qobuz" className={className}/>;
} }
export function AmazonIcon({ className = "w-4 h-4" }: PlatformIconProps) { export function AmazonIcon({ className = "w-4 h-4" }: PlatformIconProps) {
return <PlatformIcon src={PLATFORM_ICON_URLS.amazon} alt="Amazon Music" className={className} defaultClassName="rounded-[4px]"/>; return <PlatformIcon src={amazonMusicIcon} alt="Amazon Music" className={className} defaultClassName="rounded-[4px]"/>;
}
export function LrclibIcon({ className = "w-4 h-4" }: PlatformIconProps) {
return <PlatformIcon src={lrclibIcon} alt="LRCLIB" className={className}/>;
}
export function MusicBrainzIcon({ className = "w-4 h-4" }: PlatformIconProps) {
return <ThemedPlatformIcon lightSrc={musicBrainzLightIcon} darkSrc={musicBrainzDarkIcon} alt="MusicBrainz" className={className}/>;
}
export function SonglinkIcon({ className = "w-4 h-4" }: PlatformIconProps) {
return <ThemedPlatformIcon lightSrc={songlinkLightIcon} darkSrc={songlinkDarkIcon} alt="Songlink" className={className} defaultClassName="rounded-[3px]"/>;
}
export function SongstatsIcon({ className = "w-4 h-4" }: PlatformIconProps) {
return <PlatformIcon src={songstatsIcon} alt="Songstats" className={className} defaultClassName="rounded-[3px]"/>;
} }
export function TidalAvailabilityIcon({ className = "w-4 h-4" }: PlatformIconProps) { export function TidalAvailabilityIcon({ className = "w-4 h-4" }: PlatformIconProps) {
return <svg viewBox="0 0 24 24" className={`${className} fill-current`}> return <svg viewBox="0 0 24 24" className={`${className} fill-current`}>
+3 -5
View File
@@ -13,9 +13,7 @@ import { themes, applyTheme } from "@/lib/themes";
import { SelectFolder, OpenConfigFolder } from "../../wailsjs/go/main/App"; import { SelectFolder, OpenConfigFolder } from "../../wailsjs/go/main/App";
import { toastWithSound as toast } from "@/lib/toast-with-sound"; import { toastWithSound as toast } from "@/lib/toast-with-sound";
import { ApiStatusTab } from "./ApiStatusTab"; import { ApiStatusTab } from "./ApiStatusTab";
import { AmazonIcon, QobuzIcon, TidalIcon } from "./PlatformIcons"; import { AmazonIcon, QobuzIcon, SonglinkIcon, SongstatsIcon, TidalIcon } from "./PlatformIcons";
import songlinkIcon from "@/assets/icons/songlink.ico";
import songstatsIcon from "@/assets/icons/songstats.png";
interface SettingsPageProps { interface SettingsPageProps {
onUnsavedChangesChange?: (hasUnsavedChanges: boolean) => void; onUnsavedChangesChange?: (hasUnsavedChanges: boolean) => void;
onResetRequest?: (resetFn: () => void) => void; onResetRequest?: (resetFn: () => void) => void;
@@ -245,13 +243,13 @@ export function SettingsPage({ onUnsavedChangesChange, onResetRequest, }: Settin
<SelectContent> <SelectContent>
<SelectItem value="songlink"> <SelectItem value="songlink">
<span className="flex items-center gap-2"> <span className="flex items-center gap-2">
<img src={songlinkIcon} alt="Songlink" className="h-4 w-4 shrink-0 rounded-[3px] object-contain" loading="lazy"/> <SonglinkIcon className="h-4 w-4 shrink-0"/>
Songlink Songlink
</span> </span>
</SelectItem> </SelectItem>
<SelectItem value="songstats"> <SelectItem value="songstats">
<span className="flex items-center gap-2"> <span className="flex items-center gap-2">
<img src={songstatsIcon} alt="Songstats" className="h-4 w-4 shrink-0 rounded-[3px] object-contain" loading="lazy"/> <SongstatsIcon className="h-4 w-4 shrink-0"/>
Songstats Songstats
</span> </span>
</SelectItem> </SelectItem>
+2
View File
@@ -19,6 +19,8 @@ export const API_SOURCES: ApiSource[] = [
{ id: "qobuz2", type: "qobuz", name: "Qobuz B", url: "https://dabmusic.xyz" }, { id: "qobuz2", type: "qobuz", name: "Qobuz B", url: "https://dabmusic.xyz" },
{ id: "qobuz3", type: "qbz", name: "Qobuz C", url: "https://qbz.afkarxyz.qzz.io" }, { id: "qobuz3", type: "qbz", name: "Qobuz C", url: "https://qbz.afkarxyz.qzz.io" },
{ id: "amazon1", type: "amazon", name: "Amazon Music", url: "https://amzn.afkarxyz.qzz.io" }, { id: "amazon1", type: "amazon", name: "Amazon Music", url: "https://amzn.afkarxyz.qzz.io" },
{ id: "lrclib", type: "lrclib", name: "LRCLIB", url: "https://lrclib.net" },
{ id: "musicbrainz", type: "musicbrainz", name: "MusicBrainz", url: "https://musicbrainz.org" },
]; ];
type ApiStatusState = { type ApiStatusState = {
isCheckingAll: boolean; isCheckingAll: boolean;