fix(sub): preserve external link names in Clash/JSON (#6049)

expandEntry cleared Name for external subscriptions and single links
with empty remark, so Clash/JSON fell back to the client email. Keep
each link's original name (#fragment / vmess ps); use the row remark
when set. Pass remark from the client form for single external links.

Fixes #6032
This commit is contained in:
H-TTTTT
2026-07-21 21:50:33 +08:00
committed by GitHub
parent c80e5e276b
commit 11f602fe04
3 changed files with 85 additions and 6 deletions
+45 -4
View File
@@ -74,18 +74,59 @@ func (s *SubService) getClientExternalLinksBySubId(subId string) ([]externalLink
}
// expandEntry turns one entry into the concrete share links it contributes. A
// "subscription" entry is fetched (cached) and its links are kept with their own
// names; a "link" entry yields the single link with the row's remark.
// "subscription" entry is fetched (cached) and its links keep their own names
// (URL #fragment / vmess ps). A "link" entry uses the row remark when set,
// otherwise the link's original name — never blank, so Clash/JSON do not fall
// back to the client email.
func expandEntry(e externalLinkEntry) []expandedLink {
if e.Kind == model.ExternalLinkKindSubscription {
links := fetchSubscriptionLinks(e.Value)
out := make([]expandedLink, 0, len(links))
for _, l := range links {
out = append(out, expandedLink{Link: l, Name: ""})
out = append(out, expandedLink{Link: l, Name: linkDisplayName(l)})
}
return out
}
return []expandedLink{{Link: e.Value, Name: e.Remark}}
name := strings.TrimSpace(e.Remark)
if name == "" {
name = linkDisplayName(e.Value)
}
return []expandedLink{{Link: e.Value, Name: name}}
}
// linkDisplayName extracts the human-readable name already carried by a share
// link: vmess JSON `ps`, or the URL #fragment for every other scheme.
func linkDisplayName(rawLink string) string {
rawLink = strings.TrimSpace(rawLink)
if rawLink == "" {
return ""
}
if strings.HasPrefix(rawLink, "vmess://") {
b64 := strings.TrimPrefix(rawLink, "vmess://")
raw, err := base64.StdEncoding.DecodeString(padBase64Sub(b64))
if err != nil {
raw, err = base64.RawURLEncoding.DecodeString(strings.TrimRight(b64, "="))
}
if err != nil {
return ""
}
var j map[string]any
if err := json.Unmarshal(raw, &j); err != nil {
return ""
}
if ps, ok := j["ps"].(string); ok {
return strings.TrimSpace(ps)
}
return ""
}
if i := strings.IndexByte(rawLink, '#'); i >= 0 && i+1 < len(rawLink) {
frag := rawLink[i+1:]
if decoded, err := url.PathUnescape(frag); err == nil {
return strings.TrimSpace(decoded)
}
return strings.TrimSpace(frag)
}
return ""
}
// applyRemarkToLink rewrites a share link's display name to remark (when set),