diff --git a/sub/default.json b/sub/default.json
new file mode 100644
index 00000000..ba13f6fb
--- /dev/null
+++ b/sub/default.json
@@ -0,0 +1,105 @@
+{
+ "dns": {
+ "tag": "dns_out",
+ "queryStrategy": "UseIP",
+ "servers": [
+ {
+ "address": "8.8.8.8",
+ "skipFallback": false
+ }
+ ]
+ },
+ "inbounds": [
+ {
+ "port": 10808,
+ "protocol": "socks",
+ "settings": {
+ "auth": "noauth",
+ "udp": true,
+ "userLevel": 8
+ },
+ "sniffing": {
+ "destOverride": [
+ "http",
+ "tls",
+ "fakedns"
+ ],
+ "enabled": true
+ },
+ "tag": "socks"
+ },
+ {
+ "port": 10809,
+ "protocol": "http",
+ "settings": {
+ "userLevel": 8
+ },
+ "tag": "http"
+ }
+ ],
+ "log": {
+ "loglevel": "warning"
+ },
+ "outbounds": [
+ {
+ "tag": "direct",
+ "protocol": "freedom",
+ "settings": {
+ "domainStrategy": "UseIP"
+ }
+ },
+ {
+ "tag": "block",
+ "protocol": "blackhole",
+ "settings": {
+ "response": {
+ "type": "http"
+ }
+ }
+ }
+ ],
+ "policy": {
+ "levels": {
+ "8": {
+ "connIdle": 300,
+ "downlinkOnly": 1,
+ "handshake": 4,
+ "uplinkOnly": 1
+ }
+ },
+ "system": {
+ "statsOutboundUplink": true,
+ "statsOutboundDownlink": true
+ }
+ },
+ "routing": {
+ "domainStrategy": "AsIs",
+ "rules": [
+ {
+ "type": "field",
+ "network": "tcp,udp",
+ "balancerTag": "all"
+ }
+ ],
+ "balancers": [
+ {
+ "tag": "all",
+ "selector": [
+ "proxy"
+ ],
+ "strategy": {
+ "type": "leastPing"
+ }
+ }
+ ]
+ },
+ "observatory": {
+ "probeInterval": "5m",
+ "probeURL": "https://api.github.com/_private/browser/stats",
+ "subjectSelector": [
+ "proxy"
+ ],
+ "EnableConcurrency": true
+ },
+ "stats": {}
+}
\ No newline at end of file
diff --git a/sub/sub.go b/sub/sub.go
index b642f7f2..2a4a37f4 100644
--- a/sub/sub.go
+++ b/sub/sub.go
@@ -47,11 +47,6 @@ func (s *Server) initRouter() (*gin.Engine, error) {
engine := gin.Default()
- subPath, err := s.settingService.GetSubPath()
- if err != nil {
- return nil, err
- }
-
subDomain, err := s.settingService.GetSubDomain()
if err != nil {
return nil, err
@@ -61,9 +56,44 @@ func (s *Server) initRouter() (*gin.Engine, error) {
engine.Use(middleware.DomainValidatorMiddleware(subDomain))
}
- g := engine.Group(subPath)
+ LinksPath, err := s.settingService.GetSubPath()
+ if err != nil {
+ return nil, err
+ }
- s.sub = NewSUBController(g)
+ JsonPath, err := s.settingService.GetSubJsonPath()
+ if err != nil {
+ return nil, err
+ }
+
+ Encrypt, err := s.settingService.GetSubEncrypt()
+ if err != nil {
+ return nil, err
+ }
+
+ ShowInfo, err := s.settingService.GetSubShowInfo()
+ if err != nil {
+ return nil, err
+ }
+
+ RemarkModel, err := s.settingService.GetRemarkModel()
+ if err != nil {
+ RemarkModel = "-ieo"
+ }
+
+ SubUpdates, err := s.settingService.GetSubUpdates()
+ if err != nil {
+ SubUpdates = "10"
+ }
+
+ SubJsonFragment, err := s.settingService.GetSubJsonFragment()
+ if err != nil {
+ SubJsonFragment = ""
+ }
+
+ g := engine.Group("/")
+
+ s.sub = NewSUBController(g, LinksPath, JsonPath, Encrypt, ShowInfo, RemarkModel, SubUpdates, SubJsonFragment)
return engine, nil
}
diff --git a/sub/subController.go b/sub/subController.go
index 5f7c69cf..e0b641df 100644
--- a/sub/subController.go
+++ b/sub/subController.go
@@ -3,34 +3,57 @@ package sub
import (
"encoding/base64"
"strings"
- "x-ui/web/service"
"github.com/gin-gonic/gin"
)
type SUBController struct {
- subService SubService
- settingService service.SettingService
+ subPath string
+ subJsonPath string
+ subEncrypt bool
+ updateInterval string
+
+ subService *SubService
+ subJsonService *SubJsonService
}
-func NewSUBController(g *gin.RouterGroup) *SUBController {
- a := &SUBController{}
+func NewSUBController(
+ g *gin.RouterGroup,
+ subPath string,
+ jsonPath string,
+ encrypt bool,
+ showInfo bool,
+ rModel string,
+ update string,
+ jsonFragment string) *SUBController {
+
+ a := &SUBController{
+ subPath: subPath,
+ subJsonPath: jsonPath,
+ subEncrypt: encrypt,
+ updateInterval: update,
+
+ subService: NewSubService(showInfo, rModel),
+ subJsonService: NewSubJsonService(jsonFragment),
+ }
a.initRouter(g)
return a
}
func (a *SUBController) initRouter(g *gin.RouterGroup) {
- g = g.Group("/")
+ gLink := g.Group(a.subPath)
+ gJson := g.Group(a.subJsonPath)
- g.GET("/:subid", a.subs)
+ gLink.GET(":subid", a.subs)
+
+ gJson.GET(":subid", a.subJsons)
}
func (a *SUBController) subs(c *gin.Context) {
- subEncrypt, _ := a.settingService.GetSubEncrypt()
- subShowInfo, _ := a.settingService.GetSubShowInfo()
+ println(c.Request.Header["User-Agent"][0])
subId := c.Param("subid")
host := strings.Split(c.Request.Host, ":")[0]
- subs, headers, err := a.subService.GetSubs(subId, host, subShowInfo)
+ subs, header, err := a.subService.GetSubs(subId, host)
if err != nil || len(subs) == 0 {
c.String(400, "Error!")
} else {
@@ -40,14 +63,32 @@ func (a *SUBController) subs(c *gin.Context) {
}
// Add headers
- c.Writer.Header().Set("Subscription-Userinfo", headers[0])
- c.Writer.Header().Set("Profile-Update-Interval", headers[1])
- c.Writer.Header().Set("Profile-Title", headers[2])
+ c.Writer.Header().Set("Subscription-Userinfo", header)
+ c.Writer.Header().Set("Profile-Update-Interval", a.updateInterval)
+ c.Writer.Header().Set("Profile-Title", subId)
- if subEncrypt {
+ if a.subEncrypt {
c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
} else {
c.String(200, result)
}
}
}
+
+func (a *SUBController) subJsons(c *gin.Context) {
+ println(c.Request.Header["User-Agent"][0])
+ subId := c.Param("subid")
+ host := strings.Split(c.Request.Host, ":")[0]
+ jsonSub, header, err := a.subJsonService.GetJson(subId, host)
+ if err != nil || len(jsonSub) == 0 {
+ c.String(400, "Error!")
+ } else {
+
+ // Add headers
+ c.Writer.Header().Set("Subscription-Userinfo", header)
+ c.Writer.Header().Set("Profile-Update-Interval", a.updateInterval)
+ c.Writer.Header().Set("Profile-Title", subId)
+
+ c.String(200, jsonSub)
+ }
+}
diff --git a/sub/subJsonService.go b/sub/subJsonService.go
new file mode 100644
index 00000000..92519f3e
--- /dev/null
+++ b/sub/subJsonService.go
@@ -0,0 +1,355 @@
+package sub
+
+import (
+ _ "embed"
+ "encoding/json"
+ "fmt"
+ "strings"
+ "x-ui/database/model"
+ "x-ui/logger"
+ "x-ui/util/json_util"
+ "x-ui/util/random"
+ "x-ui/web/service"
+ "x-ui/xray"
+)
+
+//go:embed default.json
+var defaultJson string
+
+type SubJsonService struct {
+ fragmanet string
+
+ inboundService service.InboundService
+ SubService
+}
+
+func NewSubJsonService(fragment string) *SubJsonService {
+ return &SubJsonService{
+ fragmanet: fragment,
+ }
+}
+
+func (s *SubJsonService) GetJson(subId string, host string) (string, string, error) {
+ inbounds, err := s.SubService.getInboundsBySubId(subId)
+ if err != nil || len(inbounds) == 0 {
+ return "", "", err
+ }
+
+ var header string
+ var traffic xray.ClientTraffic
+ var clientTraffics []xray.ClientTraffic
+ var configJson map[string]interface{}
+ var defaultOutbounds []json_util.RawMessage
+
+ json.Unmarshal([]byte(defaultJson), &configJson)
+ if outboundSlices, ok := configJson["outbounds"].([]interface{}); ok {
+ for _, defaultOutbound := range outboundSlices {
+ jsonBytes, _ := json.Marshal(defaultOutbound)
+ defaultOutbounds = append(defaultOutbounds, jsonBytes)
+ }
+ }
+
+ outbounds := []json_util.RawMessage{}
+ startIndex := 0
+ // Prepare Inbounds
+ for _, inbound := range inbounds {
+ clients, err := s.inboundService.GetClients(inbound)
+ if err != nil {
+ logger.Error("SubJsonService - GetClients: Unable to get clients from inbound")
+ }
+ if clients == nil {
+ continue
+ }
+ if len(inbound.Listen) > 0 && inbound.Listen[0] == '@' {
+ listen, port, streamSettings, err := s.getFallbackMaster(inbound.Listen, inbound.StreamSettings)
+ if err == nil {
+ inbound.Listen = listen
+ inbound.Port = port
+ inbound.StreamSettings = streamSettings
+ }
+ }
+
+ var subClients []model.Client
+ for _, client := range clients {
+ if client.Enable && client.SubID == subId {
+ subClients = append(subClients, client)
+ clientTraffics = append(clientTraffics, s.SubService.getClientTraffics(inbound.ClientStats, client.Email))
+ }
+ }
+
+ outbound := s.getOutbound(inbound, subClients, host, startIndex)
+ if outbound != nil {
+ outbounds = append(outbounds, outbound...)
+ startIndex += len(outbound)
+ }
+ }
+
+ if len(outbounds) == 0 {
+ return "", "", nil
+ }
+
+ // Prepare statistics
+ for index, clientTraffic := range clientTraffics {
+ if index == 0 {
+ traffic.Up = clientTraffic.Up
+ traffic.Down = clientTraffic.Down
+ traffic.Total = clientTraffic.Total
+ if clientTraffic.ExpiryTime > 0 {
+ traffic.ExpiryTime = clientTraffic.ExpiryTime
+ }
+ } else {
+ traffic.Up += clientTraffic.Up
+ traffic.Down += clientTraffic.Down
+ if traffic.Total == 0 || clientTraffic.Total == 0 {
+ traffic.Total = 0
+ } else {
+ traffic.Total += clientTraffic.Total
+ }
+ if clientTraffic.ExpiryTime != traffic.ExpiryTime {
+ traffic.ExpiryTime = 0
+ }
+ }
+ }
+
+ if s.fragmanet != "" {
+ outbounds = append(outbounds, json_util.RawMessage(s.fragmanet))
+ }
+
+ // Combile outbounds
+ outbounds = append(outbounds, defaultOutbounds...)
+ configJson["outbounds"] = outbounds
+ finalJson, _ := json.MarshalIndent(configJson, "", " ")
+
+ header = fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000)
+ return string(finalJson), header, nil
+}
+
+func (s *SubJsonService) getOutbound(inbound *model.Inbound, clients []model.Client, host string, startIndex int) []json_util.RawMessage {
+ var newOutbounds []json_util.RawMessage
+ stream := s.streamData(inbound.StreamSettings)
+
+ externalProxies, ok := stream["externalProxy"].([]interface{})
+ if !ok || len(externalProxies) == 0 {
+ externalProxies = []interface{}{
+ map[string]interface{}{
+ "forceTls": "same",
+ "dest": host,
+ "port": float64(inbound.Port),
+ },
+ }
+ }
+
+ delete(stream, "externalProxy")
+
+ config_index := startIndex
+ for _, ep := range externalProxies {
+ extPrxy := ep.(map[string]interface{})
+ inbound.Listen = extPrxy["dest"].(string)
+ inbound.Port = int(extPrxy["port"].(float64))
+ newStream := stream
+ switch extPrxy["forceTls"].(string) {
+ case "tls":
+ if newStream["security"] != "tls" {
+ newStream["security"] = "tls"
+ newStream["tslSettings"] = map[string]interface{}{}
+ }
+ case "none":
+ if newStream["security"] != "none" {
+ newStream["security"] = "none"
+ delete(newStream, "tslSettings")
+ }
+ }
+ streamSettings, _ := json.MarshalIndent(newStream, "", " ")
+ inbound.StreamSettings = string(streamSettings)
+
+ for _, client := range clients {
+ inbound.Tag = fmt.Sprintf("proxy_%d", config_index)
+ switch inbound.Protocol {
+ case "vmess", "vless":
+ newOutbounds = append(newOutbounds, s.genVnext(inbound, client))
+ case "trojan", "shadowsocks":
+ newOutbounds = append(newOutbounds, s.genServer(inbound, client))
+ }
+ config_index += 1
+ }
+ }
+
+ return newOutbounds
+}
+
+func (s *SubJsonService) streamData(stream string) map[string]interface{} {
+ var streamSettings map[string]interface{}
+ json.Unmarshal([]byte(stream), &streamSettings)
+ security, _ := streamSettings["security"].(string)
+ if security == "tls" {
+ streamSettings["tlsSettings"] = s.tlsData(streamSettings["tlsSettings"].(map[string]interface{}))
+ } else if security == "reality" {
+ streamSettings["realitySettings"] = s.realityData(streamSettings["realitySettings"].(map[string]interface{}))
+ }
+ delete(streamSettings, "sockopt")
+
+ if s.fragmanet != "" {
+ streamSettings["sockopt"] = json_util.RawMessage(`{"dialerProxy": "fragment", "tcpKeepAliveIdle": 100, "TcpNoDelay": true}`)
+ }
+
+ // remove proxy protocol
+ network, _ := streamSettings["network"].(string)
+ switch network {
+ case "tcp":
+ streamSettings["tcpSettings"] = s.removeAcceptProxy(streamSettings["tcpSettings"])
+ case "ws":
+ streamSettings["wsSettings"] = s.removeAcceptProxy(streamSettings["wsSettings"])
+ }
+
+ return streamSettings
+}
+
+func (s *SubJsonService) removeAcceptProxy(setting interface{}) map[string]interface{} {
+ netSettings, ok := setting.(map[string]interface{})
+ if ok {
+ delete(netSettings, "acceptProxyProtocol")
+ }
+ return netSettings
+}
+
+func (s *SubJsonService) tlsData(tData map[string]interface{}) map[string]interface{} {
+ tlsData := make(map[string]interface{}, 1)
+ tlsClientSettings := tData["settings"].(map[string]interface{})
+
+ tlsData["serverName"] = tData["serverName"]
+ tlsData["alpn"] = tData["alpn"]
+ if allowInsecure, ok := tlsClientSettings["allowInsecure"].(string); ok {
+ tlsData["allowInsecure"] = allowInsecure
+ }
+ if fingerprint, ok := tlsClientSettings["fingerprint"].(string); ok {
+ tlsData["fingerprint"] = fingerprint
+ }
+ return tlsData
+}
+
+func (s *SubJsonService) realityData(rData map[string]interface{}) map[string]interface{} {
+ rltyData := make(map[string]interface{}, 1)
+ rltyClientSettings := rData["settings"].(map[string]interface{})
+
+ rltyData["show"] = false
+ rltyData["publicKey"] = rltyClientSettings["publicKey"]
+ rltyData["fingerprint"] = rltyClientSettings["fingerprint"]
+
+ // Set random data
+ rltyData["spiderX"] = "/" + random.Seq(15)
+ shortIds, ok := rData["shortIds"].([]interface{})
+ if ok && len(shortIds) > 0 {
+ rltyData["shortId"] = shortIds[random.Num(len(shortIds))].(string)
+ } else {
+ rltyData["shortId"] = ""
+ }
+ serverNames, ok := rData["serverNames"].([]interface{})
+ if ok && len(serverNames) > 0 {
+ rltyData["serverName"] = serverNames[random.Num(len(serverNames))].(string)
+ } else {
+ rltyData["serverName"] = ""
+ }
+
+ return rltyData
+}
+
+func (s *SubJsonService) genVnext(inbound *model.Inbound, client model.Client) json_util.RawMessage {
+ outbound := Outbound{}
+ usersData := make([]UserVnext, 1)
+
+ usersData[0].ID = client.ID
+ usersData[0].Level = 8
+ if inbound.Protocol == model.VLESS {
+ usersData[0].Flow = client.Flow
+ usersData[0].Encryption = "none"
+ }
+
+ vnextData := make([]VnextSetting, 1)
+ vnextData[0] = VnextSetting{
+ Address: inbound.Listen,
+ Port: inbound.Port,
+ Users: usersData,
+ }
+
+ outbound.Protocol = string(inbound.Protocol)
+ outbound.Tag = inbound.Tag
+ outbound.StreamSettings = json_util.RawMessage(inbound.StreamSettings)
+ outbound.Settings = OutboundSettings{
+ Vnext: vnextData,
+ }
+
+ result, _ := json.MarshalIndent(outbound, "", " ")
+ return result
+}
+
+func (s *SubJsonService) genServer(inbound *model.Inbound, client model.Client) json_util.RawMessage {
+ outbound := Outbound{}
+
+ serverData := make([]ServerSetting, 1)
+ serverData[0] = ServerSetting{
+ Address: inbound.Listen,
+ Port: inbound.Port,
+ Level: 8,
+ Password: client.Password,
+ }
+
+ if inbound.Protocol == model.Shadowsocks {
+ var inboundSettings map[string]interface{}
+ json.Unmarshal([]byte(inbound.Settings), &inboundSettings)
+ method, _ := inboundSettings["method"].(string)
+ serverData[0].Method = method
+
+ // server password in multi-user 2022 protocols
+ if strings.HasPrefix(method, "2022") {
+ if serverPassword, ok := inboundSettings["password"].(string); ok {
+ serverData[0].Password = fmt.Sprintf("%s:%s", serverPassword, client.Password)
+ }
+ }
+ }
+
+ outbound.Protocol = string(inbound.Protocol)
+ outbound.Tag = inbound.Tag
+ outbound.StreamSettings = json_util.RawMessage(inbound.StreamSettings)
+ outbound.Settings = OutboundSettings{
+ Servers: serverData,
+ }
+
+ result, _ := json.MarshalIndent(outbound, "", " ")
+ return result
+}
+
+type Outbound struct {
+ Protocol string `json:"protocol"`
+ Tag string `json:"tag"`
+ StreamSettings json_util.RawMessage `json:"streamSettings"`
+ Mux map[string]interface{} `json:"mux,omitempty"`
+ ProxySettings map[string]interface{} `json:"proxySettings,omitempty"`
+ Settings OutboundSettings `json:"settings,omitempty"`
+}
+
+type OutboundSettings struct {
+ Vnext []VnextSetting `json:"vnext,omitempty"`
+ Servers []ServerSetting `json:"servers,omitempty"`
+}
+
+type VnextSetting struct {
+ Address string `json:"address"`
+ Port int `json:"port"`
+ Users []UserVnext `json:"users"`
+}
+
+type UserVnext struct {
+ Encryption string `json:"encryption,omitempty"`
+ Flow string `json:"flow,omitempty"`
+ ID string `json:"id"`
+ Level int `json:"level"`
+}
+
+type ServerSetting struct {
+ Password string `json:"password"`
+ Level int `json:"level"`
+ Address string `json:"address"`
+ Port int `json:"port"`
+ Flow string `json:"flow,omitempty"`
+ Method string `json:"method,omitempty"`
+}
diff --git a/sub/subService.go b/sub/subService.go
index ddf9692b..06d1ed0a 100644
--- a/sub/subService.go
+++ b/sub/subService.go
@@ -25,47 +25,42 @@ type SubService struct {
settingService service.SettingService
}
-func (s *SubService) GetSubs(subId string, host string, showInfo bool) ([]string, []string, error) {
+func NewSubService(showInfo bool, remarkModel string) *SubService {
+ return &SubService{
+ showInfo: showInfo,
+ remarkModel: remarkModel,
+ }
+}
+
+func (s *SubService) GetSubs(subId string, host string) ([]string, string, error) {
s.address = host
- s.showInfo = showInfo
var result []string
- var headers []string
+ var header string
var traffic xray.ClientTraffic
var clientTraffics []xray.ClientTraffic
inbounds, err := s.getInboundsBySubId(subId)
if err != nil {
- return nil, nil, err
- }
- s.remarkModel, err = s.settingService.GetRemarkModel()
- if err != nil {
- s.remarkModel = "-ieo"
+ return nil, "", err
}
+
s.datepicker, err = s.settingService.GetDatepicker()
- if err != nil {
- s.datepicker = "gregorian"
- }
+ if err != nil {
+ s.datepicker = "gregorian"
+ }
for _, inbound := range inbounds {
clients, err := s.inboundService.GetClients(inbound)
if err != nil {
- logger.Error("SubService - GetSub: Unable to get clients from inbound")
+ logger.Error("SubService - GetClients: Unable to get clients from inbound")
}
if clients == nil {
continue
}
if len(inbound.Listen) > 0 && inbound.Listen[0] == '@' {
- fallbackMaster, err := s.getFallbackMaster(inbound.Listen)
+ listen, port, streamSettings, err := s.getFallbackMaster(inbound.Listen, inbound.StreamSettings)
if err == nil {
- inbound.Listen = fallbackMaster.Listen
- inbound.Port = fallbackMaster.Port
- var stream map[string]interface{}
- json.Unmarshal([]byte(inbound.StreamSettings), &stream)
- var masterStream map[string]interface{}
- json.Unmarshal([]byte(fallbackMaster.StreamSettings), &masterStream)
- stream["security"] = masterStream["security"]
- stream["tlsSettings"] = masterStream["tlsSettings"]
- stream["externalProxy"] = masterStream["externalProxy"]
- modifiedStream, _ := json.MarshalIndent(stream, "", " ")
- inbound.StreamSettings = string(modifiedStream)
+ inbound.Listen = listen
+ inbound.Port = port
+ inbound.StreamSettings = streamSettings
}
}
for _, client := range clients {
@@ -76,6 +71,8 @@ func (s *SubService) GetSubs(subId string, host string, showInfo bool) ([]string
}
}
}
+
+ // Prepare statistics
for index, clientTraffic := range clientTraffics {
if index == 0 {
traffic.Up = clientTraffic.Up
@@ -97,11 +94,8 @@ func (s *SubService) GetSubs(subId string, host string, showInfo bool) ([]string
}
}
}
- headers = append(headers, fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000))
- updateInterval, _ := s.settingService.GetSubUpdates()
- headers = append(headers, fmt.Sprintf("%d", updateInterval))
- headers = append(headers, subId)
- return result, headers, nil
+ header = fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000)
+ return result, header, nil
}
func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) {
@@ -130,7 +124,7 @@ func (s *SubService) getClientTraffics(traffics []xray.ClientTraffic, email stri
return xray.ClientTraffic{}
}
-func (s *SubService) getFallbackMaster(dest string) (*model.Inbound, error) {
+func (s *SubService) getFallbackMaster(dest string, streamSettings string) (string, int, string, error) {
db := database.GetDB()
var inbound *model.Inbound
err := db.Model(model.Inbound{}).
@@ -138,9 +132,19 @@ func (s *SubService) getFallbackMaster(dest string) (*model.Inbound, error) {
Where("EXISTS (SELECT * FROM json_each(settings, '$.fallbacks') WHERE json_extract(value, '$.dest') = ?)", dest).
Find(&inbound).Error
if err != nil {
- return nil, err
+ return "", 0, "", err
}
- return inbound, nil
+
+ var stream map[string]interface{}
+ json.Unmarshal([]byte(streamSettings), &stream)
+ var masterStream map[string]interface{}
+ json.Unmarshal([]byte(inbound.StreamSettings), &masterStream)
+ stream["security"] = masterStream["security"]
+ stream["tlsSettings"] = masterStream["tlsSettings"]
+ stream["externalProxy"] = masterStream["externalProxy"]
+ modifiedStream, _ := json.MarshalIndent(stream, "", " ")
+
+ return inbound.Listen, inbound.Port, string(modifiedStream), nil
}
func (s *SubService) getLink(inbound *model.Inbound, email string) string {
@@ -578,6 +582,7 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
if sniValue, ok := searchKey(tlsSetting, "serverName"); ok {
params["sni"], _ = sniValue.(string)
}
+
tlsSettings, _ := searchKey(tlsSetting, "settings")
if tlsSetting != nil {
if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
diff --git a/web/assets/js/model/setting.js b/web/assets/js/model/setting.js
index ddf1a0e1..637830e8 100644
--- a/web/assets/js/model/setting.js
+++ b/web/assets/js/model/setting.js
@@ -28,6 +28,7 @@ class AllSetting {
this.subListen = "";
this.subPort = "2096";
this.subPath = "/sub/";
+ this.subJsonPath = "/json/";
this.subDomain = "";
this.subCertFile = "";
this.subKeyFile = "";
@@ -35,6 +36,8 @@ class AllSetting {
this.subEncrypt = true;
this.subShowInfo = false;
this.subURI = '';
+ this.subJsonURI = '';
+ this.subJsonFragment = '';
this.timeLocation = "Asia/Tehran";
diff --git a/web/controller/xray_setting.go b/web/controller/xray_setting.go
index 28f55b54..2dddb44b 100644
--- a/web/controller/xray_setting.go
+++ b/web/controller/xray_setting.go
@@ -81,7 +81,6 @@ func (a *XraySettingController) warp(c *gin.Context) {
resp, err = a.XraySettingService.RegWarp(skey, pkey)
case "license":
license := c.PostForm("license")
- println(license)
resp, err = a.XraySettingService.SetWarpLicence(license)
}
diff --git a/web/entity/entity.go b/web/entity/entity.go
index 8ab06399..06850128 100644
--- a/web/entity/entity.go
+++ b/web/entity/entity.go
@@ -48,6 +48,9 @@ type AllSetting struct {
SubEncrypt bool `json:"subEncrypt" form:"subEncrypt"`
SubShowInfo bool `json:"subShowInfo" form:"subShowInfo"`
SubURI string `json:"subURI" form:"subURI"`
+ SubJsonPath string `json:"subJsonPath" form:"subJsonPath"`
+ SubJsonURI string `json:"subJsonURI" form:"subJsonURI"`
+ SubJsonFragment string `json:"subJsonFragment" form:"subJsonFragment"`
Datepicker string `json:"datepicker" form:"datepicker"`
}
@@ -105,6 +108,13 @@ func (s *AllSetting) CheckValid() error {
s.SubPath += "/"
}
+ if !strings.HasPrefix(s.SubJsonPath, "/") {
+ s.SubJsonPath = "/" + s.SubJsonPath
+ }
+ if !strings.HasSuffix(s.SubJsonPath, "/") {
+ s.SubJsonPath += "/"
+ }
+
_, err := time.LoadLocation(s.TimeLocation)
if err != nil {
return common.NewError("time location not exist:", s.TimeLocation)
diff --git a/web/html/common/qrcode_modal.html b/web/html/common/qrcode_modal.html
index 3bda4c29..7cc317ef 100644
--- a/web/html/common/qrcode_modal.html
+++ b/web/html/common/qrcode_modal.html
@@ -8,13 +8,23 @@
{{ i18n "pages.inbounds.clickOnQRcode" }}
- Subscription
-
+ {{ i18n "pages.settings.subSettings"}}
+
+ {{ i18n "pages.settings.subSettings"}} Json
+
{{ i18n "pages.inbounds.client" }}
- [[ row.remark ]]
-
+ [[ row.remark ]]
+
@@ -82,12 +92,16 @@
},
genSubLink(subID) {
return app.subSettings.subURI+subID;
+ },
+ genSubJsonLink(subID) {
+ return app.subSettings.subJsonURI+subID;
}
},
updated() {
if (qrModal.client && qrModal.client.subId) {
qrModal.subId = qrModal.client.subId;
this.setQrCode("qrCode-sub", this.genSubLink(qrModal.subId));
+ this.setQrCode("qrCode-subJson", this.genSubJsonLink(qrModal.subId));
}
qrModal.qrcodes.forEach((element, index) => {
this.setQrCode("qrCode-" + index, element.link);
@@ -96,4 +110,4 @@
});
-{{end}}
+{{end}}
\ No newline at end of file
diff --git a/web/html/xui/inbound_info_modal.html b/web/html/xui/inbound_info_modal.html
index 23f8bd47..c8341651 100644
--- a/web/html/xui/inbound_info_modal.html
+++ b/web/html/xui/inbound_info_modal.html
@@ -166,7 +166,7 @@
Subscription URL
- [[ infoModal.subLink ]]
+ SUB: [[ infoModal.subLink ]]
+
+ JSON: [[ infoModal.subJsonLink ]]
+
+
+
+
+
+
Telegram ID
@@ -345,6 +355,7 @@
index: null,
isExpired: false,
subLink: '',
+ subJsonLink: '',
show(dbInbound, index) {
this.index = index;
this.inbound = dbInbound.toInbound();
@@ -360,6 +371,7 @@
if (this.clientSettings) {
if (this.clientSettings.subId) {
this.subLink = this.genSubLink(this.clientSettings.subId);
+ this.subJsonLink = this.genSubJsonLink(this.clientSettings.subId);
}
}
this.visible = true;
@@ -369,6 +381,9 @@
},
genSubLink(subID) {
return app.subSettings.subURI+subID;
+ },
+ genSubJsonLink(subID) {
+ return app.subSettings.subJsonURI+subID;
}
};
diff --git a/web/html/xui/inbounds.html b/web/html/xui/inbounds.html
index f1f14275..a2365cde 100644
--- a/web/html/xui/inbounds.html
+++ b/web/html/xui/inbounds.html
@@ -64,11 +64,6 @@
>
-
-
- Please go to the panel settings as soon as possible to modify the username and password, otherwise there may be a risk of leaking account information
-
-
@@ -576,7 +571,8 @@
refreshInterval: Number(localStorage.getItem("refreshInterval")) || 5000,
subSettings: {
enable : false,
- subURI : ''
+ subURI : '',
+ subJsonURI : '',
},
remarkModel: '-ieo',
datepicker: 'gregorian',
@@ -623,7 +619,8 @@
this.tgBotEnable = tgBotEnable;
this.subSettings = {
enable : subEnable,
- subURI: subURI
+ subURI: subURI,
+ subJsonURI: subJsonURI
};
this.pageSize = pageSize;
this.remarkModel = remarkModel;
@@ -997,7 +994,7 @@
},
delInbound(dbInboundId) {
this.$confirm({
- title: '{{ i18n "pages.inbounds.deleteInbound"}}',
+ title: '{{ i18n "pages.inbounds.deleteInbound"}}' + ' #' + dbInboundId,
content: '{{ i18n "pages.inbounds.deleteInboundContent"}}',
class: themeSwitcher.currentTheme,
okText: '{{ i18n "delete"}}',
@@ -1010,7 +1007,7 @@
clientId = this.getClientId(dbInbound.protocol, client);
if (confirmation){
this.$confirm({
- title: '{{ i18n "pages.inbounds.deleteClient"}}',
+ title: '{{ i18n "pages.inbounds.deleteClient"}}' + ' ' + client.email,
content: '{{ i18n "pages.inbounds.deleteClientContent"}}',
class: themeSwitcher.currentTheme,
okText: '{{ i18n "delete"}}',
@@ -1301,7 +1298,7 @@
pagination(obj){
if (this.pageSize > 0 && obj.length>this.pageSize) {
// Set page options based on object size
- sizeOptions = []
+ sizeOptions = [];
for (i=this.pageSize;i<=obj.length;i=i+this.pageSize) {
sizeOptions.push(i.toString());
}
@@ -1314,8 +1311,8 @@
position: 'bottom',
pageSize: this.pageSize,
pageSizeOptions: sizeOptions
- }
- return p
+ };
+ return p;
}
return false
},
@@ -1369,7 +1366,6 @@
}
},
});
-
{{template "inboundModal"}}
@@ -1379,6 +1375,5 @@
{{template "inboundInfoModal"}}
{{template "clientsModal"}}
{{template "clientsBulkModal"}}
-