[sub] json + fragment

This commit is contained in:
Alireza Ahmadi
2024-02-20 11:29:37 +01:00
parent cdb90da138
commit 08403bc8f9
18 changed files with 738 additions and 81 deletions

105
sub/default.json Normal file
View File

@@ -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": {}
}

View File

@@ -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
}

View File

@@ -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)
}
}

359
sub/subJsonService.go Normal file
View File

@@ -0,0 +1,359 @@
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...)
var outboundStrings []json_util.RawMessage
for _, outbound := range outbounds {
outboundStrings = append(outboundStrings, outbound)
}
configJson["outbounds"] = outboundStrings
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"`
}

View File

@@ -18,50 +18,46 @@ import (
)
type SubService struct {
address string
showInfo bool
remarkModel string
address string
showInfo bool
remarkModel string
inboundService service.InboundService
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
}
// Prepare Inbounds
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 {
@@ -72,6 +68,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
@@ -93,11 +91,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) {
@@ -126,7 +121,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{}).
@@ -134,9 +129,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 {

View File

@@ -24,6 +24,7 @@ class AllSetting {
this.subListen = "";
this.subPort = "2096";
this.subPath = "/sub/";
this.subJsonPath = "/json/";
this.subDomain = "";
this.subCertFile = "";
this.subKeyFile = "";
@@ -31,6 +32,8 @@ class AllSetting {
this.subEncrypt = true;
this.subShowInfo = false;
this.subURI = '';
this.subJsonURI = '';
this.subJsonFragment = '';
this.timeLocation = "Asia/Tehran";

View File

@@ -78,7 +78,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)
}

View File

@@ -46,6 +46,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"`
}
func (s *AllSetting) CheckValid() error {
@@ -103,6 +106,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)

View File

@@ -5,10 +5,16 @@
width="300px" :class="themeSwitcher.currentTheme">
<a-tag color="green" style="margin-bottom: 10px;display: block;text-align: center;" >{{ i18n "pages.inbounds.clickOnQRcode" }}</a-tag>
<template v-if="app.subSettings.enable && qrModal.subId">
<a-divider>Subscription</a-divider>
<a-divider>{{ i18n "pages.settings.subSettings"}}</a-divider>
<canvas @click="copyToClipboard('qrCode-sub',genSubLink(qrModal.client.subId))"
id="qrCode-sub"
style="width: 100%; height: 100%; display: flex; border-radius: 1rem;"></canvas>
style="width: 100%; height: 100%; display: flex; border-radius: 1rem;">
</canvas>
<a-divider>{{ i18n "pages.settings.subSettings"}} Json</a-divider>
<canvas @click="copyToClipboard('qrCode-subJson',genSubJsonLink(qrModal.client.subId))"
id="qrCode-subJson"
style="width: 100%; height: 100%; display: flex; border-radius: 1rem;">
</canvas>
</template>
<a-divider>{{ i18n "pages.inbounds.client" }}</a-divider>
<template v-for="(row, index) in qrModal.qrcodes">
@@ -83,12 +89,16 @@
},
genSubLink(subID) {
return app.subSettings.subURI+subID+'?name='+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);

View File

@@ -162,7 +162,7 @@
<template v-if="app.subSettings.enable && infoModal.clientSettings.subId">
<a-divider>Subscription URL</a-divider>
<a-row>
<a-col :sx="24" :md="22"><a :href="[[ infoModal.subLink ]]" target="_blank">[[ infoModal.subLink ]]</a></a-col>
<a-col :sx="24" :md="22">SUB: <a :href="[[ infoModal.subLink ]]" target="_blank">[[ infoModal.subLink ]]</a></a-col>
<a-col :sx="24" :md="2" style="text-align: right;">
<a-tooltip title='{{ i18n "copy" }}'>
<button class="ant-btn ant-btn-primary" id="copy-sub-link" @click="copyToClipboard('copy-sub-link', infoModal.subLink)">
@@ -171,6 +171,16 @@
</a-tooltip>
</a-col>
</a-row>
<a-row>
<a-col :sx="24" :md="22">JSON: <a :href="[[ infoModal.subJsonLink ]]" target="_blank">[[ infoModal.subJsonLink ]]</a></a-col>
<a-col :sx="24" :md="2" style="text-align: right;">
<a-tooltip title='{{ i18n "copy" }}'>
<button class="ant-btn ant-btn-primary" id="copy-subJson-link" @click="copyToClipboard('copy-subJson-link', infoModal.subJsonLink)">
<a-icon type="snippets"></a-icon>
</button>
</a-tooltip>
</a-col>
</a-row>
</template>
<template v-if="app.tgBotEnable && infoModal.clientSettings.tgId">
<a-divider>Telegram ID</a-divider>
@@ -341,6 +351,7 @@
index: null,
isExpired: false,
subLink: '',
subJsonLink: '',
tgLink: '',
show(dbInbound, index) {
this.index = index;
@@ -357,6 +368,7 @@
if (this.clientSettings) {
if (this.clientSettings.subId) {
this.subLink = this.genSubLink(this.clientSettings.subId);
this.subJsonLink = this.genSubJsonLink(this.clientSettings.subId);
}
if (this.clientSettings.tgId) {
this.tgLink = "https://t.me/" + this.clientSettings.tgId;
@@ -369,6 +381,9 @@
},
genSubLink(subID) {
return app.subSettings.subURI+subID+'?name='+subID;
},
genSubJsonLink(subID) {
return app.subSettings.subJsonURI+subID+'?name='+subID;;
}
};

View File

@@ -564,7 +564,8 @@
refreshInterval: Number(localStorage.getItem("refreshInterval")) || 5000,
subSettings: {
enable : false,
subURI : ''
subURI : '',
subJsonURI : '',
},
remarkModel: '-ieo',
tgBotEnable: false,
@@ -609,7 +610,8 @@
this.tgBotEnable = tgBotEnable;
this.subSettings = {
enable : subEnable,
subURI: subURI
subURI: subURI,
subJsonURI: subJsonURI
};
this.pageSize = pageSize;
this.remarkModel = remarkModel;
@@ -646,8 +648,12 @@
clientCount = clients.length;
if (dbInbound.enable) {
clients.forEach(client => {
client.enable ? active.push(client.email) : deactive.push(client.email);
if(this.isClientOnline(client.email)) online.push(client.email);
if (client.enable) {
active.push(client.email);
if(this.isClientOnline(client.email)) online.push(client.email);
} else {
deactive.push(client.email);
}
});
clientStats.forEach(client => {
if (!client.enable) {

View File

@@ -211,6 +211,17 @@
<setting-list-item type="number" title='{{ i18n "pages.settings.subUpdates"}}' desc='{{ i18n "pages.settings.subUpdatesDesc"}}' v-model="allSetting.subUpdates"></setting-list-item>
</a-list>
</a-tab-pane>
<a-tab-pane key="5" tab='{{ i18n "pages.settings.subSettings" }} Json' v-if="allSetting.subEnable">
<a-list item-layout="horizontal">
<setting-list-item type="text" title='{{ i18n "pages.settings.subPath"}}' desc='{{ i18n "pages.settings.subPathDesc"}}' v-model="allSetting.subJsonPath"></setting-list-item>
<setting-list-item type="text" title='{{ i18n "pages.settings.subURI"}}' desc='{{ i18n "pages.settings.subURIDesc"}}' v-model="allSetting.subJsonURI" placeholder="(http|https)://domain[:port]/path/"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.settings.fragment"}}' desc='{{ i18n "pages.settings.fragmentDesc"}}' v-model="fragment"></setting-list-item>
<template v-if="fragment">
<setting-list-item type="text" title='length' v-model="fragmentLength" placeholder="100-200"></setting-list-item>
<setting-list-item type="text" title='Interval' v-model="fragmentInterval" placeholder="10-20"></setting-list-item>
</template>
</a-list>
</a-tab-pane>
</a-tabs>
</a-space>
</a-spin>
@@ -240,6 +251,24 @@
remarkModels: {i:'Inbound',e:'Email',o:'Other'},
remarkSeparators: [' ','-','_','@',':','~','|',',','.','/'],
remarkSample: '',
defaultFragment: {
tag: "fragment",
protocol: "freedom",
settings: {
domainStrategy: "AsIs",
fragment: {
packets: "tlshello",
length: "100-200",
interval: "10-20"
}
},
streamSettings: {
sockopt: {
tcpKeepAliveIdle: 100,
TcpNoDelay: true
}
}
},
get remarkModel() {
rm = this.allSetting.remarkModel;
return rm.length>1 ? rm.substring(1).split('') : [];
@@ -321,6 +350,32 @@
}
},
computed: {
fragment: {
get: function() { return this.allSetting?.subJsonFragment != ""; },
set: function (v) {
this.allSetting.subJsonFragment = v ? JSON.stringify(this.defaultFragment) : "";
}
},
fragmentLength: {
get: function() { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).settings.fragment.length : ""; },
set: function(v) {
if (v != ""){
newFragment = JSON.parse(this.allSetting.subJsonFragment);
newFragment.settings.fragment.length = v;
this.allSetting.subJsonFragment = JSON.stringify(newFragment);
}
}
},
fragmentInterval: {
get: function() { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).settings.fragment.interval : ""; },
set: function(v) {
if (v != ""){
newFragment = JSON.parse(this.allSetting.subJsonFragment);
newFragment.settings.fragment.interval = v;
this.allSetting.subJsonFragment = JSON.stringify(newFragment);
}
}
},
confAlerts: {
get: function() {
if (!this.allSetting) return [];
@@ -330,6 +385,8 @@
if (panelPath && this.allSetting.webBasePath == '/') alerts.push('{{ i18n "pages.settings.panelConfig"}} {{ i18n "pages.settings.panelUrlPath"}}');
subPath = this.allSetting.subURI.length >0 ? new URL(this.allSetting.subURI).pathname : this.allSetting.subPath;
if (subPath == '/sub/') alerts.push('{{ i18n "pages.settings.subSettings"}} {{ i18n "pages.settings.subPath"}}');
subJsonPath = this.allSetting.subJsonURI.length >0 ? new URL(this.allSetting.subJsonURI).pathname : this.allSetting.subJsonPath;
if (subJsonPath == '/json/') alerts.push('JSON {{ i18n "pages.settings.subPath"}}');
return alerts
}
}

View File

@@ -55,6 +55,9 @@ var defaultValueMap = map[string]string{
"subEncrypt": "true",
"subShowInfo": "false",
"subURI": "",
"subJsonPath": "/json/",
"subJsonURI": "",
"subJsonFragment": "",
"warp": "",
}
@@ -353,17 +356,11 @@ func (s *SettingService) GetSubPort() (int, error) {
}
func (s *SettingService) GetSubPath() (string, error) {
subPath, err := s.getString("subPath")
if err != nil {
return "", err
}
if !strings.HasPrefix(subPath, "/") {
subPath = "/" + subPath
}
if !strings.HasSuffix(subPath, "/") {
subPath += "/"
}
return subPath, nil
return s.getString("subPath")
}
func (s *SettingService) GetSubJsonPath() (string, error) {
return s.getString("subJsonPath")
}
func (s *SettingService) GetSubDomain() (string, error) {
@@ -378,8 +375,8 @@ func (s *SettingService) GetSubKeyFile() (string, error) {
return s.getString("subKeyFile")
}
func (s *SettingService) GetSubUpdates() (int, error) {
return s.getInt("subUpdates")
func (s *SettingService) GetSubUpdates() (string, error) {
return s.getString("subUpdates")
}
func (s *SettingService) GetSubEncrypt() (bool, error) {
@@ -398,6 +395,14 @@ func (s *SettingService) GetSubURI() (string, error) {
return s.getString("subURI")
}
func (s *SettingService) GetSubJsonURI() (string, error) {
return s.getString("subJsonURI")
}
func (s *SettingService) GetSubJsonFragment() (string, error) {
return s.getString("subJsonFragment")
}
func (s *SettingService) GetWarp() (string, error) {
return s.getString("warp")
}
@@ -446,6 +451,7 @@ func (s *SettingService) GetDefaultSettings(host string) (interface{}, error) {
"tgBotEnable": func() (interface{}, error) { return s.GetTgbotenabled() },
"subEnable": func() (interface{}, error) { return s.GetSubEnable() },
"subURI": func() (interface{}, error) { return s.GetSubURI() },
"subJsonURI": func() (interface{}, error) { return s.GetSubJsonURI() },
"remarkModel": func() (interface{}, error) { return s.GetRemarkModel() },
}
@@ -459,10 +465,11 @@ func (s *SettingService) GetDefaultSettings(host string) (interface{}, error) {
result[key] = value
}
if result["subEnable"].(bool) && result["subURI"].(string) == "" {
if result["subEnable"].(bool) && (result["subURI"].(string) == "" || result["subJsonURI"].(string) == "") {
subURI := ""
subPort, _ := s.GetSubPort()
subPath, _ := s.GetSubPath()
subJsonPath, _ := s.GetSubJsonPath()
subDomain, _ := s.GetSubDomain()
subKeyFile, _ := s.GetSubKeyFile()
subCertFile, _ := s.GetSubCertFile()
@@ -483,12 +490,12 @@ func (s *SettingService) GetDefaultSettings(host string) (interface{}, error) {
} else {
subURI += fmt.Sprintf("%s:%d", subDomain, subPort)
}
if subPath[0] == byte('/') {
subURI += subPath
} else {
subURI += "/" + subPath
if result["subURI"].(string) == "" {
result["subURI"] = subURI + subPath
}
if result["subJsonURI"].(string) == "" {
result["subJsonURI"] = subURI + subJsonPath
}
result["subURI"] = subURI
}
return result, nil

View File

@@ -294,6 +294,8 @@
"subShowInfoDesc" = "The remaining traffic and date will be displayed in the client apps."
"subURI" = "Reverse Proxy URI"
"subURIDesc" = "The subscription service will use the URI that has been set up behind reverse proxies."
"fragment" = "Fragmentation"
"fragmentDesc" = "Enable fragmentation for TLS hello packet"
[pages.settings.toasts]
"modifySettings" = "Modify Settings"

View File

@@ -293,6 +293,8 @@
"subShowInfoDesc" = "ترافیک و زمان باقی‌مانده را در برنامه‌های کاربری نمایش می‌دهد"
"subURI" = "پراکسی معکوس URI"
"subURIDesc" = "سابسکریپشن از لینکی که در پشت پراکسی‌های معکوس تنظیم شده‌، استفاده خواهدکرد"
"fragment" = "تکه‌تکه شدن"
"fragmentDesc" = "فعال کردن تکه تکه شدن برای بسته نخست تی‌ال‌اس"
[pages.settings.toasts]
"modifySettings" = "ویرایش تنظیمات"

View File

@@ -294,6 +294,8 @@
"subShowInfoDesc" = "Показывать восстановленный трафик и дату после имени конфигурации"
"subURI" = "URI обратного прокси"
"subURIDesc" = "Изменить базовый URI URL-адреса подписки для использования за прокси-серверами"
"fragment" = "Фрагментация"
"fragmentDesc" = "Включить фрагментацию для пакета приветствия TLS"
[pages.settings.toasts]
"modifySettings" = "Изменение настроек"

View File

@@ -294,6 +294,8 @@
"subShowInfoDesc" = "Hiển thị lưu lượng truy cập còn lại và ngày sau tên cấu hình"
"subURI" = "URI proxy ngược"
"subURIDesc" = "Thay đổi URI cơ sở của URL đăng ký để sử dụng ở phía sau proxy"
"fragment" = "Sự phân mảnh"
"fragmentDesc" = "Kích hoạt phân mảnh cho gói TLS hello"
[pages.settings.toasts]
"modifySettings" = "Sửa đổi cài đặt"

View File

@@ -294,6 +294,8 @@
"subShowInfoDesc" = "在配置名称后显示剩余流量和日期"
"subURI" = "反向代理 URI"
"subURIDesc" = "更改订阅 URL 的基本 URI 以在代理后面使用"
"fragment" = "碎片"
"fragmentDesc" = "启用 TLS hello 数据包分段"
[pages.settings.toasts]
"modifySettings" = "修改设置"