From cfcb890469f743d7c1436a55bbf57757f8bd4519 Mon Sep 17 00:00:00 2001 From: afkarxyz Date: Thu, 2 Apr 2026 10:14:49 +0700 Subject: [PATCH] .link resolver --- backend/config.go | 32 ++++++++++++++++++ backend/link_resolver.go | 41 +++++++++++++++-------- frontend/src/assets/icons/songlink.ico | Bin 0 -> 15086 bytes frontend/src/assets/icons/songstats.png | Bin 0 -> 12084 bytes frontend/src/components/SettingsPage.tsx | 40 ++++++++++++++++++++++ frontend/src/lib/settings.ts | 16 +++++++++ 6 files changed, 115 insertions(+), 14 deletions(-) create mode 100644 frontend/src/assets/icons/songlink.ico create mode 100644 frontend/src/assets/icons/songstats.png diff --git a/backend/config.go b/backend/config.go index 18b56be..3c7af37 100644 --- a/backend/config.go +++ b/backend/config.go @@ -4,6 +4,7 @@ import ( "encoding/json" "os" "path/filepath" + "strings" ) func GetDefaultMusicPath() string { @@ -67,3 +68,34 @@ func GetSpotFetchAPISettings() (bool, string) { return true, apiURL } + +func GetLinkResolverSetting() string { + settings, err := LoadConfigSettings() + if err != nil || settings == nil { + return linkResolverProviderSongstats + } + + resolver, _ := settings["linkResolver"].(string) + switch strings.TrimSpace(strings.ToLower(resolver)) { + case "songlink", linkResolverProviderDeezerSongLink: + return linkResolverProviderDeezerSongLink + case "", "songstats": + return linkResolverProviderSongstats + default: + return linkResolverProviderSongstats + } +} + +func GetLinkResolverAllowFallback() bool { + settings, err := LoadConfigSettings() + if err != nil || settings == nil { + return true + } + + allowFallback, ok := settings["allowResolverFallback"].(bool) + if !ok { + return true + } + + return allowFallback +} diff --git a/backend/link_resolver.go b/backend/link_resolver.go index b614ab1..3eaf044 100644 --- a/backend/link_resolver.go +++ b/backend/link_resolver.go @@ -30,32 +30,23 @@ func (s *SongLinkClient) resolveSpotifyTrackLinks(spotifyTrackID string, region } if links.ISRC != "" { - resolvers := prioritizeProviders("link_resolver", []string{ - linkResolverProviderSongstats, - linkResolverProviderDeezerSongLink, - }) + resolvers := orderedLinkResolvers() for _, resolver := range resolvers { switch resolver { case linkResolverProviderSongstats: addedData, songstatsErr := s.resolveLinksViaSongstats(links) - if addedData { - recordProviderSuccess("link_resolver", linkResolverProviderSongstats) - } else if songstatsErr != nil { - recordProviderFailure("link_resolver", linkResolverProviderSongstats) - } if songstatsErr != nil { attempts = append(attempts, fmt.Sprintf("songstats: %v", songstatsErr)) + } else if addedData { + fmt.Println("Using Songstats as configured link resolver") } case linkResolverProviderDeezerSongLink: addedData, deezerSongLinkErr := s.resolveLinksViaDeezerSongLink(links, region) - if addedData { - recordProviderSuccess("link_resolver", linkResolverProviderDeezerSongLink) - } else if deezerSongLinkErr != nil { - recordProviderFailure("link_resolver", linkResolverProviderDeezerSongLink) - } if deezerSongLinkErr != nil { attempts = append(attempts, fmt.Sprintf("deezer-songlink: %v", deezerSongLinkErr)) + } else if addedData { + fmt.Println("Using Songlink as configured link resolver") } } @@ -76,6 +67,28 @@ func (s *SongLinkClient) resolveSpotifyTrackLinks(spotifyTrackID string, region return links, errors.New(strings.Join(attempts, " | ")) } +func orderedLinkResolvers() []string { + preferred := GetLinkResolverSetting() + if !GetLinkResolverAllowFallback() { + if preferred == linkResolverProviderDeezerSongLink { + return []string{linkResolverProviderDeezerSongLink} + } + return []string{linkResolverProviderSongstats} + } + + if preferred == linkResolverProviderDeezerSongLink { + return []string{ + linkResolverProviderDeezerSongLink, + linkResolverProviderSongstats, + } + } + + return []string{ + linkResolverProviderSongstats, + linkResolverProviderDeezerSongLink, + } +} + func (s *SongLinkClient) resolveLinksViaSongstats(links *resolvedTrackLinks) (bool, error) { if links == nil || links.ISRC == "" { return false, fmt.Errorf("ISRC is required for Songstats resolver") diff --git a/frontend/src/assets/icons/songlink.ico b/frontend/src/assets/icons/songlink.ico new file mode 100644 index 0000000000000000000000000000000000000000..4fdec815bdf1b18c3bca0505ab75aa4a189c457f GIT binary patch literal 15086 zcmdU$3A9(^7QoMKBvL|>DfH5yVHqk4DN+exnX*)dlA#r4nU-`-SxsvxQc@A|8iZKJ zrmIxKV?~Bq^sdTkAZegz*l+*#J>Plfe>&&?KmXhHdhNCLJ?Hx+=cgdD5Tas9oc{#k` z8R-KuAzL;;Z_QP-elw4ZMLx1KeiJ54klSv%&2;GFk3TMxCQXu}q9Xb3yYEudf+xHq zYYEx1=~i^_ZYO{H_U&cu+O?_2_p7hIlC#e~JId3>qH^WRa^#UmO1pOL#uEG*RNkM^XAQy`|i6>x_9p` z4?XmdJoeaQ(z9nzx$wdZrAd<}a>Nlw813rStINcR6SLBGACV!Vmyi)z1JF6bPGG-{ zez|hxOrJR#(dX~K|89C48*tcRhe^$vHKjp=26F!S=gW;Z-Y5eH4wN}_=E(N#+pQ_j zUw-*TmM&dt>xwl&{o>-{DCyYW=vHKM^7*ocZCe8^3&tLMlC*A^_W=V2NTo`Z%;w+^ zfCn9PP{z99H+}mOufFPvb3Ty?*_@od%*f6;Vx23;ugYd$8C&YICc`jm z)+{;r;DhDRLl2eld-iOz1K5DATeq6sV7%FL=+c{R zx=A{B?kv=2?Vvxg$@v1ZA)}Mmmwk=<%^4}~>=EVj;)^e4j03t9eN(@FeQDjgwXGfZ zjxPbQ_ONwF9d(q|{qoB%qhxdbMmA)0^6C@Wi49m8`Z^6D3%>3>_uM0$I(3p}&6-(% zs#K|BV@rRWy?O4r=K_+fU&j8-GtbO9&q1Te3o<%+ec1zlhx2N_fDUCJUIkTE1bvhS7ras1zS;|&}CQ%^lr@R0y~4yX57 zf2D;O`SV^sxp+VDzymf$_(IN>EMLCd))@A|o#9xA?t3np?t9Go$$S4gT5->9+qRj% z$sC9MgV$ew-E{YX2OcPG+O&~NFTGT-;n!b(z4@qj+;N9YojNtc*NDbIQV@3{r890ZR|ViEoxH&0{W7mKl8F*VR~`z>|bnT)22-Y zUlYLRiJU`X<-hE*%jB=W{)!`OWeN5xwf~SvIP?Mg%i8JIty{1b&`psuNUZ!(pDIMw zUK9L>y!^K)&G2t@DLxPK<4ZeW+)h04L_04K`@#lLCycDO4?q0SHQD2t#ed7of8K*| zsDOAHa#pQcRTeE;6nsI?ELpO|Yy&#! zSPa|71G|aeQX2T+gAasrI=(UfDf9j2n{Ued@4qke=g&92iH}qo;7sLgx<7C3{DS

<$=am2R@`l7Uh zch30v%Aex=PCtlA1ap86&lNuj_k;7+$tRy2$QhP7{Ei*S2hzk(l)i&f$FJD0>{}(2 zi1?*5X3R(}exC6w`cUfl;p)|^<+|&xla?)8%4w&aW@iBA1AT<uvXpCdoHg8eyvK_B+p z{1L+YK<j#cWY`R~B8t z0dDua!-=y}>q8~*oM%z`npiu`eNC>ktk+t|0=gM!s z`Nr~j+hClXg#l`xxwXVml6qmvMi=#b;f4_~~*0K2La~FLyvI8rNVh zJ9Ow^@pQMo7<=;72y?@l_vu@Il8a_6ZNx3Os$@w%^HN#{uKZx*9xquq<4- z(B^>rICo%(C6oJv9^+1Km$+aq2<6M+y%|}OV$9^3(Cw@{^;T{>Tr$`|i6o zHp7Muvv|f2Km3r3z47w<4{vO!*^#7%>>bwbd+)s`W5FeYhZXc7Kd$3KoY`j$EGuJ@sUk3arcs#U9IYk~E` zeFWn4@4WL)?0qigIyC-S*Pk(n{Bik`(W6J(7oI-7jAv5bD6;+%_g{o<@RL|Qd3Be^ zy5WW!GWa@KuD$l!VBfmNG5ar3|F|~MC;4LTfnXP~_2?n)KCvGfHf(73EKWG#1bN|w z7otr~$3^r{R{a|#;O_aHb=FzJ-5PS+=mplmnl)>LJOH+t_*|SGjM2YY?N5|H`MqTh)3x;b`F^b_|ek|2xyg@0=QgWNB93v@lP z9hW;H2hYAtf*k$_GNkgqu&vx7Bp-@T^2#f(*u4g~7vlJ*@W0>>>`VW%L;}yt@W1h) z@ul`Nf?TeKbG@%U- zc)=6i#3=|%)KLuL6uBb@m9=_D#mTCFR_nQD2go~R{x|BxK`kvDss8Imd?xN>Vn;yU zDdQV;6sa2qe$U{JBlk7q?ut{!H|kJV(b1@+?;|Pa-UGm%c4vwaBSu(0+u1DY_%x7% zwjEWj4RhztwYKoM`|i8#UiHm4-z-z6Oc8uC&M6M4OB>oM{*%7p$KwOwgYcbuPxue? z|K*oo7U$DIgTBy)wrcNIxpS#&PhK8h?()koHyZ3sbaNPJLtEwt9uBzcG*Is__UqTr z?iE1`nWA|>+PL`*w|8{-zXZ;o_yL^%xbF;YcMf*#!}7bfJiqwj3p>xz4ttJWCs&DI z3pn}R_(kWO`<9$}$iL$MaxP&mIIo4rk2wg>pApwTef#z`TZ;dR9~Fkk{Kc*x#-DQ# z=l_{AX9mq<){lx{Yq7NJm-fKk?4J?}76Hv~|875CgL=B7i&P^Q7L=eP7Ja(!)TIq= F{{xWc&%*!! literal 0 HcmV?d00001 diff --git a/frontend/src/assets/icons/songstats.png b/frontend/src/assets/icons/songstats.png new file mode 100644 index 0000000000000000000000000000000000000000..fc8a223965fd45e827bfb2260d4f0e00c4c6805a GIT binary patch literal 12084 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 void; onResetRequest?: (resetFn: () => void) => void; @@ -230,6 +232,44 @@ export function SettingsPage({ onUnsavedChangesChange, onResetRequest, }: Settin

+
+ +
+ + +
+ setTempSettings((prev) => ({ + ...prev, + allowResolverFallback: checked, + }))}/> + +
+
+
+
diff --git a/frontend/src/lib/settings.ts b/frontend/src/lib/settings.ts index 0920b4e..b80443f 100644 --- a/frontend/src/lib/settings.ts +++ b/frontend/src/lib/settings.ts @@ -5,6 +5,8 @@ export type FilenamePreset = "title" | "title-artist" | "artist-title" | "track- export interface Settings { downloadPath: string; downloader: "auto" | "tidal" | "qobuz" | "amazon"; + linkResolver: "songstats" | "songlink"; + allowResolverFallback: boolean; theme: string; themeMode: "auto" | "light" | "dark"; fontFamily: FontFamily; @@ -93,6 +95,8 @@ function detectOS(): "Windows" | "linux/MacOS" { export const DEFAULT_SETTINGS: Settings = { downloadPath: "", downloader: "auto", + linkResolver: "songstats", + allowResolverFallback: true, theme: "yellow", themeMode: "auto", fontFamily: "google-sans", @@ -225,6 +229,12 @@ function getSettingsFromLocalStorage(): Settings { if (!('allowFallback' in parsed)) { parsed.allowFallback = true; } + if (!('linkResolver' in parsed)) { + parsed.linkResolver = "songstats"; + } + if (!('allowResolverFallback' in parsed)) { + parsed.allowResolverFallback = true; + } if (!('separator' in parsed)) { parsed.separator = "semicolon"; } @@ -304,6 +314,12 @@ export async function loadSettings(): Promise { if (!('allowFallback' in parsed)) { parsed.allowFallback = true; } + if (!('linkResolver' in parsed)) { + parsed.linkResolver = "songstats"; + } + if (!('allowResolverFallback' in parsed)) { + parsed.allowResolverFallback = true; + } if (!('createPlaylistFolder' in parsed)) { parsed.createPlaylistFolder = true; }