Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions database/model/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ type Client struct {
Enable bool `json:"enable" form:"enable"` // Whether the client is enabled
TgID int64 `json:"tgId" form:"tgId"` // Telegram user ID for notifications
SubID string `json:"subId" form:"subId"` // Subscription identifier
SubHost string `json:"subHost,omitempty"` // Optional host/IP override for exported client links
Comment string `json:"comment" form:"comment"` // Client comment
Reset int `json:"reset" form:"reset"` // Reset period in days
CreatedAt int64 `json:"created_at,omitempty"` // Creation timestamp
Expand Down
5 changes: 4 additions & 1 deletion sub/subJsonService.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ func (s *SubJsonService) GetJson(subId string, host string) (string, string, err
if err != nil || len(inbounds) == 0 {
return "", "", err
}
requestHost := strings.TrimSpace(host)
defaultClientHost := s.SubService.getDefaultClientHost()

var header string
var traffic xray.ClientTraffic
Expand Down Expand Up @@ -103,7 +105,8 @@ func (s *SubJsonService) GetJson(subId string, host string) (string, string, err
for _, client := range clients {
if client.Enable && client.SubID == subId {
clientTraffics = append(clientTraffics, s.SubService.getClientTraffics(inbound.ClientStats, client.Email))
newConfigs := s.getConfig(inbound, client, host)
clientHost := s.SubService.ResolveClientHostWithDefault(inbound, client, requestHost, defaultClientHost)
newConfigs := s.getConfig(inbound, client, clientHost)
configArray = append(configArray, newConfigs...)
}
}
Expand Down
184 changes: 103 additions & 81 deletions sub/subService.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,8 @@ import (

// SubService provides business logic for generating subscription links and managing subscription data.
type SubService struct {
address string
showInfo bool
remarkModel string
datepicker string
inboundService service.InboundService
settingService service.SettingService
}
Expand All @@ -40,7 +38,8 @@ func NewSubService(showInfo bool, remarkModel string) *SubService {

// GetSubs retrieves subscription links for a given subscription ID and host.
func (s *SubService) GetSubs(subId string, host string) ([]string, int64, xray.ClientTraffic, error) {
s.address = host
requestHost := strings.TrimSpace(host)
defaultClientHost := s.getDefaultClientHost()
var result []string
var traffic xray.ClientTraffic
var lastOnline int64
Expand All @@ -54,10 +53,6 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, int64, xray.C
return nil, 0, traffic, common.NewError("No inbounds found with ", subId)
}

s.datepicker, err = s.settingService.GetDatepicker()
if err != nil {
s.datepicker = "gregorian"
}
for _, inbound := range inbounds {
clients, err := s.inboundService.GetClients(inbound)
if err != nil {
Expand All @@ -76,7 +71,7 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, int64, xray.C
}
for _, client := range clients {
if client.Enable && client.SubID == subId {
link := s.getLink(inbound, client.Email)
link := s.getLink(inbound, client.Email, requestHost, defaultClientHost)
result = append(result, link)
ct := s.getClientTraffics(inbound.ClientStats, client.Email)
clientTraffics = append(clientTraffics, ct)
Expand Down Expand Up @@ -161,33 +156,87 @@ func (s *SubService) getFallbackMaster(dest string, streamSettings string) (stri
return inbound.Listen, inbound.Port, string(modifiedStream), nil
}

func (s *SubService) getLink(inbound *model.Inbound, email string) string {
func (s *SubService) getDefaultClientHost() string {
defaultHost, err := s.settingService.GetSubDefaultHost()
if err != nil {
return ""
}
return strings.TrimSpace(defaultHost)
}

func isWildcardListen(listen string) bool {
switch strings.ToLower(strings.TrimSpace(listen)) {
case "", "0.0.0.0", "::", "::0":
return true
default:
return false
}
}

func (s *SubService) getInboundSubHost(inbound *model.Inbound) string {
var settings map[string]any
if err := json.Unmarshal([]byte(inbound.Settings), &settings); err != nil {
return ""
}
subHost, _ := settings["subHost"].(string)
return strings.TrimSpace(subHost)
}

func (s *SubService) resolveAddress(inbound *model.Inbound, client model.Client, requestHost string, defaultClientHost string) string {
if host := strings.TrimSpace(client.SubHost); host != "" {
return host
}
if host := s.getInboundSubHost(inbound); host != "" {
return host
}
if host := strings.TrimSpace(defaultClientHost); host != "" {
return host
}
if !isWildcardListen(inbound.Listen) {
return inbound.Listen
}
return requestHost
}

func (s *SubService) ResolveClientHost(inbound *model.Inbound, client model.Client, requestHost string) string {
return s.ResolveClientHostWithDefault(inbound, client, requestHost, s.getDefaultClientHost())
}

func (s *SubService) ResolveClientHostWithDefault(inbound *model.Inbound, client model.Client, requestHost string, defaultClientHost string) string {
host := strings.TrimSpace(requestHost)
return s.resolveAddress(inbound, client, host, defaultClientHost)
}

func findClientByEmail(clients []model.Client, email string) (model.Client, int) {
for i, client := range clients {
if client.Email == email {
return client, i
}
}
return model.Client{}, -1
}

func (s *SubService) getLink(inbound *model.Inbound, email string, requestHost string, defaultClientHost string) string {
switch inbound.Protocol {
case "vmess":
return s.genVmessLink(inbound, email)
return s.genVmessLink(inbound, email, requestHost, defaultClientHost)
case "vless":
return s.genVlessLink(inbound, email)
return s.genVlessLink(inbound, email, requestHost, defaultClientHost)
case "trojan":
return s.genTrojanLink(inbound, email)
return s.genTrojanLink(inbound, email, requestHost, defaultClientHost)
case "shadowsocks":
return s.genShadowsocksLink(inbound, email)
return s.genShadowsocksLink(inbound, email, requestHost, defaultClientHost)
}
return ""
}

func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
func (s *SubService) genVmessLink(inbound *model.Inbound, email string, requestHost string, defaultClientHost string) string {
if inbound.Protocol != model.VMESS {
return ""
}
var address string
if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
address = s.address
} else {
address = inbound.Listen
}
obj := map[string]any{
"v": "2",
"add": address,
"add": "",
"port": inbound.Port,
"type": "none",
}
Expand Down Expand Up @@ -274,15 +323,13 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
}

clients, _ := s.inboundService.GetClients(inbound)
clientIndex := -1
for i, client := range clients {
if client.Email == email {
clientIndex = i
break
}
client, clientIndex := findClientByEmail(clients, email)
if clientIndex < 0 {
return ""
}
obj["id"] = clients[clientIndex].ID
obj["scy"] = clients[clientIndex].Security
obj["add"] = s.resolveAddress(inbound, client, requestHost, defaultClientHost)
obj["id"] = client.ID
obj["scy"] = client.Security

externalProxies, _ := stream["externalProxy"].([]any)

Expand Down Expand Up @@ -319,28 +366,18 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
return "vmess://" + base64.StdEncoding.EncodeToString(jsonStr)
}

func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
var address string
if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
address = s.address
} else {
address = inbound.Listen
}

func (s *SubService) genVlessLink(inbound *model.Inbound, email string, requestHost string, defaultClientHost string) string {
if inbound.Protocol != model.VLESS {
return ""
}
var stream map[string]any
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
clients, _ := s.inboundService.GetClients(inbound)
clientIndex := -1
for i, client := range clients {
if client.Email == email {
clientIndex = i
break
}
client, clientIndex := findClientByEmail(clients, email)
if clientIndex < 0 {
return ""
}
uuid := clients[clientIndex].ID
uuid := client.ID
port := inbound.Port
streamNetwork := stream["network"].(string)
params := make(map[string]string)
Expand Down Expand Up @@ -430,8 +467,8 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
}
}

if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
params["flow"] = clients[clientIndex].Flow
if streamNetwork == "tcp" && len(client.Flow) > 0 {
params["flow"] = client.Flow
}
}

Expand Down Expand Up @@ -464,8 +501,8 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
params["spx"] = "/" + random.Seq(15)
}

if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
params["flow"] = clients[clientIndex].Flow
if streamNetwork == "tcp" && len(client.Flow) > 0 {
params["flow"] = client.Flow
}
}

Expand Down Expand Up @@ -508,6 +545,7 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
return strings.Join(links, "\n")
}

address := s.resolveAddress(inbound, client, requestHost, defaultClientHost)
link := fmt.Sprintf("vless://%s@%s:%d", uuid, address, port)
url, _ := url.Parse(link)
q := url.Query()
Expand All @@ -523,27 +561,18 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
return url.String()
}

func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string {
var address string
if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
address = s.address
} else {
address = inbound.Listen
}
func (s *SubService) genTrojanLink(inbound *model.Inbound, email string, requestHost string, defaultClientHost string) string {
if inbound.Protocol != model.Trojan {
return ""
}
var stream map[string]any
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
clients, _ := s.inboundService.GetClients(inbound)
clientIndex := -1
for i, client := range clients {
if client.Email == email {
clientIndex = i
break
}
client, clientIndex := findClientByEmail(clients, email)
if clientIndex < 0 {
return ""
}
password := clients[clientIndex].Password
password := client.Password
port := inbound.Port
streamNetwork := stream["network"].(string)
params := make(map[string]string)
Expand Down Expand Up @@ -656,8 +685,8 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
params["spx"] = "/" + random.Seq(15)
}

if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
params["flow"] = clients[clientIndex].Flow
if streamNetwork == "tcp" && len(client.Flow) > 0 {
params["flow"] = client.Flow
}
}

Expand Down Expand Up @@ -703,6 +732,7 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
return links
}

address := s.resolveAddress(inbound, client, requestHost, defaultClientHost)
link := fmt.Sprintf("trojan://%s@%s:%d", password, address, port)

url, _ := url.Parse(link)
Expand All @@ -719,13 +749,7 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
return url.String()
}

func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) string {
var address string
if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
address = s.address
} else {
address = inbound.Listen
}
func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string, requestHost string, defaultClientHost string) string {
if inbound.Protocol != model.Shadowsocks {
return ""
}
Expand All @@ -737,12 +761,9 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
json.Unmarshal([]byte(inbound.Settings), &settings)
inboundPassword := settings["password"].(string)
method := settings["method"].(string)
clientIndex := -1
for i, client := range clients {
if client.Email == email {
clientIndex = i
break
}
client, clientIndex := findClientByEmail(clients, email)
if clientIndex < 0 {
return ""
}
streamNetwork := stream["network"].(string)
params := make(map[string]string)
Expand Down Expand Up @@ -827,9 +848,9 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
}
}

encPart := fmt.Sprintf("%s:%s", method, clients[clientIndex].Password)
encPart := fmt.Sprintf("%s:%s", method, client.Password)
if method[0] == '2' {
encPart = fmt.Sprintf("%s:%s:%s", method, inboundPassword, clients[clientIndex].Password)
encPart = fmt.Sprintf("%s:%s:%s", method, inboundPassword, client.Password)
}

externalProxies, _ := stream["externalProxy"].([]any)
Expand Down Expand Up @@ -870,6 +891,7 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
return links
}

address := s.resolveAddress(inbound, client, requestHost, defaultClientHost)
link := fmt.Sprintf("ss://%s@%s:%d", base64.StdEncoding.EncodeToString([]byte(encPart)), address, inbound.Port)
url, _ := url.Parse(link)
q := url.Query()
Expand Down Expand Up @@ -1161,8 +1183,8 @@ func (s *SubService) BuildPageData(subId string, hostHeader string, traffic xray
remained = common.FormatTraffic(left)
}

datepicker := s.datepicker
if datepicker == "" {
datepicker, err := s.settingService.GetDatepicker()
if err != nil || datepicker == "" {
datepicker = "gregorian"
}

Expand Down
6 changes: 3 additions & 3 deletions web/assets/js/model/dbinbound.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,8 @@ class DBInbound {
}
}

genInboundLinks(remarkModel) {
genInboundLinks(remarkModel, defaultHost = '') {
const inbound = this.toInbound();
return inbound.genInboundLinks(this.remark, remarkModel);
return inbound.genInboundLinks(this.remark, remarkModel, defaultHost);
}
}
}
Loading