Files
3x-ui/internal/sub/links.go
T
Nethmina Yasas bc1b750e00 feat(clients): bind host rules to individual clients in subscriptions (#6620)
Host rules applied per inbound only, so every client of an inbound got the same
subscription endpoints (#6618). A client can now be bound to one or more host
rules, and its raw, JSON and Clash subscriptions render only those hosts, in
either subscription order; an unbound client is unchanged. A binding whose hosts
are deleted, disabled or excluded from the format fails closed: the client gets
no links there but stays in traffic accounting. Host edits keep row ids, so
bindings survive them; nil leaves a stored binding alone and an empty list
clears it.
2026-10-24 12:33:02 +02:00

78 lines
1.9 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)
baseEps := svc.hostEndpoints(inbound, "raw")
if client, ok := svc.clientForLink(inbound, email); ok {
effectiveEps, bound := svc.clientHostEndpoints(inbound, client, "raw", baseEps)
if bound && len(effectiveEps) == 0 {
return nil
}
if len(effectiveEps) > 0 {
if link := svc.linkFromHosts(inbound, client, effectiveEps); link != "" {
return splitLinkLines(link)
}
}
}
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
}