Files
3x-ui-agent/internal/sub/links.go
T
Muse 3413ccca9f fix three V1.0.1 leftover tails
1. per-client links (LinkProvider.LinksForClient) now apply the
   inbound's managed Host records with the same precedence as
   subscriptions and export-all (host endpoints after fallback
   projection); zero-host inbounds keep the legacy output.
2. agent node writes no longer store the form placeholder
   address='agent', port=2053: CreateFromRequest/UpdateFromRequest
   sanitize it to the node's resolved agent address (update) or
   empty (create / unreported), port 0; normalize gains a lenient
   endpoint mode for agent writes only.
3. runtime Remote.baseURL refuses the legacy 'agent' placeholder
   with a clear no-panel-endpoint error instead of splicing it into
   a dialable https://agent:2053 URL.
2026-10-22 17:55:19 +00:00

76 lines
2.0 KiB
Go

package sub
import (
"strings"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
"github.com/mhsanaei/3x-ui/v3/internal/web/service"
)
type LinkProvider struct {
settingService service.SettingService
}
func NewLinkProvider() *LinkProvider {
return &LinkProvider{}
}
func (p *LinkProvider) build(host string) *SubService {
remarkTemplate, _ := p.settingService.GetRemarkTemplate()
svc := NewSubService(remarkTemplate)
svc.PrepareForRequest(host)
return svc
}
func (p *LinkProvider) SubLinksForSubId(host, subId string) ([]string, error) {
svc := p.build(host)
links, _, _, _, err := svc.GetSubs(subId, host)
if err != nil {
return nil, err
}
out := make([]string, 0, len(links))
for _, l := range links {
out = append(out, splitLinkLines(l)...)
}
return out, nil
}
func (p *LinkProvider) LinksForClient(host string, inbound *model.Inbound, email string) []string {
svc := p.build(host)
svc.projectThroughFallbackMaster(inbound)
// Managed Host records apply to per-client links exactly as they do
// to subscriptions (getSubs) and export-all (inboundLinks): after the
// fallback projection, a host's address/TLS wins over the plain
// endpoint. Inbounds without hosts keep the legacy output unchanged;
// a client that cannot be resolved falls back to the plain link too.
if eps := svc.hostEndpoints(inbound, "raw"); len(eps) > 0 {
if client, ok := svc.clientForLink(inbound, email); ok {
return splitLinkLines(svc.linkFromHosts(inbound, client, eps))
}
}
return splitLinkLines(svc.GetLink(inbound, email))
}
func (p *LinkProvider) LinksForInbounds(host string, inbounds []*model.Inbound) []string {
svc := p.build(host)
var out []string
for _, inbound := range inbounds {
out = append(out, svc.inboundLinks(inbound)...)
}
return out
}
func splitLinkLines(raw string) []string {
if raw == "" {
return nil
}
parts := strings.Split(raw, "\n")
out := make([]string, 0, len(parts))
for _, p := range parts {
if p = strings.TrimSpace(p); p != "" {
out = append(out, p)
}
}
return out
}