diff --git a/database/model/model.go b/database/model/model.go index 63f4a1b4..5fa934c0 100644 --- a/database/model/model.go +++ b/database/model/model.go @@ -21,6 +21,7 @@ const ( Shadowsocks Protocol = "shadowsocks" Mixed Protocol = "mixed" WireGuard Protocol = "wireguard" + Hysteria Protocol = "hysteria" ) // User represents a user account in the 3x-ui panel. @@ -118,10 +119,11 @@ type CustomGeoResource struct { // Client represents a client configuration for Xray inbounds with traffic limits and settings. type Client struct { - ID string `json:"id"` // Unique client identifier + ID string `json:"id,omitempty"` // Unique client identifier Security string `json:"security"` // Security method (e.g., "auto", "aes-128-gcm") - Password string `json:"password"` // Client password - Flow string `json:"flow"` // Flow control (XTLS) + Password string `json:"password,omitempty"` // Client password + Flow string `json:"flow,omitempty"` // Flow control (XTLS) + Auth string `json:"auth,omitempty"` // Auth password (Hysteria) Email string `json:"email"` // Client email identifier LimitIP int `json:"limitIp"` // IP limit for this client TotalGB int64 `json:"totalGB" form:"totalGB"` // Total traffic limit in GB diff --git a/sub/subJsonService.go b/sub/subJsonService.go index 05552fe8..7ce93e22 100644 --- a/sub/subJsonService.go +++ b/sub/subJsonService.go @@ -194,6 +194,8 @@ func (s *SubJsonService) getConfig(inbound *model.Inbound, client model.Client, newOutbounds = append(newOutbounds, s.genVless(inbound, streamSettings, client)) case "trojan", "shadowsocks": newOutbounds = append(newOutbounds, s.genServer(inbound, streamSettings, client)) + case "hysteria": + newOutbounds = append(newOutbounds, s.genHy(inbound, newStream, client)) } newOutbounds = append(newOutbounds, s.defaultOutbounds...) @@ -389,6 +391,49 @@ func (s *SubJsonService) genServer(inbound *model.Inbound, streamSettings json_u return result } +func (s *SubJsonService) genHy(inbound *model.Inbound, newStream map[string]any, client model.Client) json_util.RawMessage { + outbound := Outbound{} + + outbound.Protocol = string(inbound.Protocol) + outbound.Tag = "proxy" + + if s.mux != "" { + outbound.Mux = json_util.RawMessage(s.mux) + } + + var settings, stream map[string]any + json.Unmarshal([]byte(inbound.Settings), &settings) + version, _ := settings["version"].(float64) + outbound.Settings = map[string]any{ + "version": int(version), + "address": inbound.Listen, + "port": inbound.Port, + } + + json.Unmarshal([]byte(inbound.StreamSettings), &stream) + hyStream := stream["hysteriaSettings"].(map[string]any) + outHyStream := map[string]any{ + "version": int(version), + "auth": client.Auth, + } + if udpIdleTimeout, ok := hyStream["udpIdleTimeout"].(float64); ok { + outHyStream["udpIdleTimeout"] = int(udpIdleTimeout) + } + newStream["hysteriaSettings"] = outHyStream + + if finalmask, ok := hyStream["finalmask"].(map[string]any); ok { + newStream["finalmask"] = finalmask + } + + newStream["network"] = "hysteria" + newStream["security"] = "tls" + + outbound.StreamSettings, _ = json.MarshalIndent(newStream, "", " ") + + result, _ := json.MarshalIndent(outbound, "", " ") + return result +} + type Outbound struct { Protocol string `json:"protocol"` Tag string `json:"tag"` diff --git a/sub/subService.go b/sub/subService.go index b6422dd4..a4d38485 100644 --- a/sub/subService.go +++ b/sub/subService.go @@ -120,7 +120,7 @@ func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) FROM inbounds, JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client WHERE - protocol in ('vmess','vless','trojan','shadowsocks') + protocol in ('vmess','vless','trojan','shadowsocks','hysteria') AND JSON_EXTRACT(client.value, '$.subId') = ? AND enable = ? )`, subId, true).Find(&inbounds).Error if err != nil { @@ -171,6 +171,8 @@ func (s *SubService) getLink(inbound *model.Inbound, email string) string { return s.genTrojanLink(inbound, email) case "shadowsocks": return s.genShadowsocksLink(inbound, email) + case "hysteria": + return s.genHysteriaLink(inbound, email) } return "" } @@ -885,6 +887,70 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st return url.String() } +func (s *SubService) genHysteriaLink(inbound *model.Inbound, email string) string { + address := s.address + if inbound.Protocol != model.Hysteria { + return "" + } + var stream map[string]interface{} + 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 + } + } + auth := clients[clientIndex].Auth + port := inbound.Port + params := make(map[string]string) + + params["security"] = "tls" + tlsSetting, _ := stream["tlsSettings"].(map[string]interface{}) + alpns, _ := tlsSetting["alpn"].([]interface{}) + var alpn []string + for _, a := range alpns { + alpn = append(alpn, a.(string)) + } + if len(alpn) > 0 { + params["alpn"] = strings.Join(alpn, ",") + } + 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 { + params["fp"], _ = fpValue.(string) + } + if insecure, ok := searchKey(tlsSettings, "allowInsecure"); ok { + if insecure.(bool) { + params["insecure"] = "1" + } + } + } + + var settings map[string]interface{} + json.Unmarshal([]byte(inbound.Settings), &settings) + version, _ := settings["version"].(float64) + protocol := "hysteria2" + if int(version) == 1 { + protocol = "hysteria" + } + + link := fmt.Sprintf("%s://%s@%s:%d", protocol, auth, address, port) + url, _ := url.Parse(link) + q := url.Query() + for k, v := range params { + q.Add(k, v) + } + url.RawQuery = q.Encode() + url.Fragment = s.genRemark(inbound, email, "") + return url.String() +} + func (s *SubService) genRemark(inbound *model.Inbound, email string, extra string) string { separationChar := string(s.remarkModel[0]) orderChars := s.remarkModel[1:] diff --git a/web/assets/js/model/dbinbound.js b/web/assets/js/model/dbinbound.js index c347a7eb..e1802635 100644 --- a/web/assets/js/model/dbinbound.js +++ b/web/assets/js/model/dbinbound.js @@ -125,7 +125,7 @@ class DBInbound { sniffing: sniffing, clientStats: this.clientStats, }; - + this._cachedInbound = Inbound.fromJson(config); return this._cachedInbound; } @@ -147,6 +147,7 @@ class DBInbound { case Protocols.VMESS: case Protocols.VLESS: case Protocols.TROJAN: + case Protocols.HYSTERIA: return true; case Protocols.SHADOWSOCKS: return this.toInbound().isSSMultiUser; @@ -161,6 +162,7 @@ class DBInbound { case Protocols.VLESS: case Protocols.TROJAN: case Protocols.SHADOWSOCKS: + case Protocols.HYSTERIA: return true; default: return false; diff --git a/web/assets/js/model/inbound.js b/web/assets/js/model/inbound.js index b6059cf7..f74b2736 100644 --- a/web/assets/js/model/inbound.js +++ b/web/assets/js/model/inbound.js @@ -8,6 +8,7 @@ const Protocols = { HTTP: 'http', WIREGUARD: 'wireguard', TUN: 'tun', + HYSTERIA: 'hysteria', }; const SSMethods = { @@ -589,6 +590,106 @@ class xHTTPStreamSettings extends XrayCommonClass { } } +class HysteriaStreamSettings extends XrayCommonClass { + constructor( + protocol, + version = 2, + auth = '', + udpIdleTimeout = 60, + masquerade, + ) { + super(protocol); + this.version = version; + this.auth = auth; + this.udpIdleTimeout = udpIdleTimeout; + this.masquerade = masquerade; + } + + static fromJson(json = {}) { + return new HysteriaStreamSettings( + json.protocol, + json.version ?? 2, + json.auth ?? '', + json.udpIdleTimeout ?? 60, + json.masquerade ? HysteriaMasquerade.fromJson(json.masquerade) : undefined, + ); + } + + toJson() { + return { + protocol: this.protocol, + version: this.version, + auth: this.auth, + udpIdleTimeout: this.udpIdleTimeout, + masquerade: this.masqueradeSwitch ? this.masquerade.toJson() : undefined, + }; + } + + get masqueradeSwitch() { + return this.masquerade != undefined; + } + + set masqueradeSwitch(value) { + this.masquerade = value ? new HysteriaMasquerade() : undefined; + } +}; + +class HysteriaMasquerade extends XrayCommonClass { + constructor( + type = 'proxy', + dir = '', + url = '', + rewriteHost = false, + insecure = false, + content = '', + headers = [], + statusCode = 0, + ) { + super(); + this.type = type; + this.dir = dir; + this.url = url; + this.rewriteHost = rewriteHost; + this.insecure = insecure; + this.content = content; + this.headers = headers; + this.statusCode = statusCode; + } + + addHeader(name, value) { + this.headers.push({ name: name, value: value }); + } + + removeHeader(index) { + this.headers.splice(index, 1); + } + + static fromJson(json = {}) { + return new HysteriaMasquerade( + json.type, + json.dir, + json.url, + json.rewriteHost, + json.insecure, + json.content, + XrayCommonClass.toHeaders(json.headers), + json.statusCode, + ); + } + + toJson() { + return { + type: this.type, + dir: this.dir, + url: this.url, + rewriteHost: this.rewriteHost, + insecure: this.insecure, + content: this.content, + headers: XrayCommonClass.toV2Headers(this.headers, false), + statusCode: this.statusCode, + }; + } +}; class TlsStreamSettings extends XrayCommonClass { constructor( serverName = '', @@ -987,6 +1088,12 @@ class UdpMask extends XrayCommonClass { case 'header-wechat': case 'header-wireguard': return {}; + case 'header-custom': + return { client: [], server: [] }; + case 'noise': + return { reset: 0, noise: [] }; + case 'sudoku': + return { ascii: '', customTable: '', customTables: [], paddingMin: 0, paddingMax: 0 }; default: return settings; } @@ -1021,7 +1128,6 @@ class FinalMaskStreamSettings extends XrayCommonClass { return { udp: this.udp.map(udp => udp.toJson()) }; - } } @@ -1037,6 +1143,7 @@ class StreamSettings extends XrayCommonClass { grpcSettings = new GrpcStreamSettings(), httpupgradeSettings = new HTTPUpgradeStreamSettings(), xhttpSettings = new xHTTPStreamSettings(), + hysteriaSettings = new HysteriaStreamSettings(), finalmask = new FinalMaskStreamSettings(), sockopt = undefined, ) { @@ -1052,6 +1159,7 @@ class StreamSettings extends XrayCommonClass { this.grpc = grpcSettings; this.httpupgrade = httpupgradeSettings; this.xhttp = xhttpSettings; + this.hysteria = hysteriaSettings; this.finalmask = finalmask; this.sockopt = sockopt; } @@ -1116,6 +1224,7 @@ class StreamSettings extends XrayCommonClass { GrpcStreamSettings.fromJson(json.grpcSettings), HTTPUpgradeStreamSettings.fromJson(json.httpupgradeSettings), xHTTPStreamSettings.fromJson(json.xhttpSettings), + HysteriaStreamSettings.fromJson(json.hysteriaSettings), FinalMaskStreamSettings.fromJson(json.finalmask), SockoptStreamSettings.fromJson(json.sockopt), ); @@ -1135,6 +1244,7 @@ class StreamSettings extends XrayCommonClass { grpcSettings: network === 'grpc' ? this.grpc.toJson() : undefined, httpupgradeSettings: network === 'httpupgrade' ? this.httpupgrade.toJson() : undefined, xhttpSettings: network === 'xhttp' ? this.xhttp.toJson() : undefined, + hysteriaSettings: network === 'hysteria' ? this.hysteria.toJson() : undefined, finalmask: this.hasFinalMask ? this.finalmask.toJson() : undefined, sockopt: this.sockopt != undefined ? this.sockopt.toJson() : undefined, }; @@ -1201,6 +1311,7 @@ class Inbound extends XrayCommonClass { case Protocols.VLESS: return this.settings.vlesses; case Protocols.TROJAN: return this.settings.trojans; case Protocols.SHADOWSOCKS: return this.isSSMultiUser ? this.settings.shadowsockses : null; + case Protocols.HYSTERIA: return this.settings.hysterias; default: return null; } } @@ -1212,9 +1323,14 @@ class Inbound extends XrayCommonClass { set protocol(protocol) { this._protocol = protocol; this.settings = Inbound.Settings.getSettings(protocol); + this.stream = new StreamSettings(); if (protocol === Protocols.TROJAN) { this.tls = false; } + if (protocol === Protocols.HYSTERIA) { + this.stream.network = 'hysteria'; + this.stream.security = 'tls'; + } } get network() { @@ -1316,6 +1432,7 @@ class Inbound extends XrayCommonClass { } canEnableTls() { + if (this.protocol === Protocols.HYSTERIA) return true; if (![Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS].includes(this.protocol)) return false; return ["tcp", "ws", "http", "grpc", "httpupgrade", "xhttp"].includes(this.network); } @@ -1342,7 +1459,7 @@ class Inbound extends XrayCommonClass { } canEnableStream() { - return [Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS].includes(this.protocol); + return [Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS, Protocols.HYSTERIA].includes(this.protocol); } reset() { @@ -1689,6 +1806,26 @@ class Inbound extends XrayCommonClass { return url.toString(); } + genHysteriaLink(address = '', port = this.port, remark = '', clientAuth) { + const protocol = this.settings.version == 2 ? "hysteria2" : "hysteria"; + const link = `${protocol}://${clientAuth}@${address}:${port}`; + + const params = new Map(); + params.set("security", "tls"); + if (this.stream.tls.settings.fingerprint?.length > 0) params.set("fp", this.stream.tls.settings.fingerprint); + if (this.stream.tls.alpn?.length > 0) params.set("alpn", this.stream.tls.alpn); + if (this.stream.tls.settings.allowInsecure) params.set("insecure", "1"); + if (this.stream.tls.settings.echConfigList?.length > 0) params.set("ech", this.stream.tls.settings.echConfigList.join(',')); + if (this.stream.tls.sni?.length > 0) params.set("sni", this.stream.tls.sni); + + const url = new URL(link); + for (const [key, value] of params) { + url.searchParams.set(key, value); + } + url.hash = encodeURIComponent(remark); + return url.toString(); + } + getWireguardLink(address, port, remark, peerId) { let txt = `[Interface]\n` txt += `PrivateKey = ${this.settings.peers[peerId].privateKey}\n` @@ -1721,6 +1858,8 @@ class Inbound extends XrayCommonClass { return this.genSSLink(address, port, forceTls, remark, this.isSSMultiUser ? client.password : ''); case Protocols.TROJAN: return this.genTrojanLink(address, port, forceTls, remark, client.password); + case Protocols.HYSTERIA: + return this.genHysteriaLink(address, port, remark, client.auth.length > 0 ? client.auth : this.stream.hysteria.auth); default: return ''; } } @@ -1827,6 +1966,7 @@ Inbound.Settings = class extends XrayCommonClass { case Protocols.HTTP: return new Inbound.HttpSettings(protocol); case Protocols.WIREGUARD: return new Inbound.WireguardSettings(protocol); case Protocols.TUN: return new Inbound.TunSettings(protocol); + case Protocols.HYSTERIA: return new Inbound.HysteriaSettings(protocol); default: return null; } } @@ -1842,6 +1982,7 @@ Inbound.Settings = class extends XrayCommonClass { case Protocols.HTTP: return Inbound.HttpSettings.fromJson(json); case Protocols.WIREGUARD: return Inbound.WireguardSettings.fromJson(json); case Protocols.TUN: return Inbound.TunSettings.fromJson(json); + case Protocols.HYSTERIA: return Inbound.HysteriaSettings.fromJson(json); default: return null; } } @@ -1851,6 +1992,94 @@ Inbound.Settings = class extends XrayCommonClass { } }; +/** Shared user-quota fields and UI helpers for multi-user protocol clients. */ +Inbound.ClientBase = class extends XrayCommonClass { + constructor( + email = RandomUtil.randomLowerAndNum(8), + limitIp = 0, + totalGB = 0, + expiryTime = 0, + enable = true, + tgId = '', + subId = RandomUtil.randomLowerAndNum(16), + comment = '', + reset = 0, + created_at = undefined, + updated_at = undefined, + ) { + super(); + this.email = email; + this.limitIp = limitIp; + this.totalGB = totalGB; + this.expiryTime = expiryTime; + this.enable = enable; + this.tgId = tgId; + this.subId = subId; + this.comment = comment; + this.reset = reset; + this.created_at = created_at; + this.updated_at = updated_at; + } + + static commonArgsFromJson(json = {}) { + return [ + json.email, + json.limitIp, + json.totalGB, + json.expiryTime, + json.enable, + json.tgId, + json.subId, + json.comment, + json.reset, + json.created_at, + json.updated_at, + ]; + } + + _clientBaseToJson() { + return { + email: this.email, + limitIp: this.limitIp, + totalGB: this.totalGB, + expiryTime: this.expiryTime, + enable: this.enable, + tgId: this.tgId, + subId: this.subId, + comment: this.comment, + reset: this.reset, + created_at: this.created_at, + updated_at: this.updated_at, + }; + } + + get _expiryTime() { + if (this.expiryTime === 0 || this.expiryTime === '') { + return null; + } + if (this.expiryTime < 0) { + return this.expiryTime / -86400000; + } + return moment(this.expiryTime); + } + + set _expiryTime(t) { + if (t == null || t === '') { + this.expiryTime = 0; + } else { + this.expiryTime = t.valueOf(); + } + } + + get _totalGB() { + return NumberFormatter.toFixed(this.totalGB / SizeFormatter.ONE_GB, 2); + } + + set _totalGB(gb) { + this.totalGB = NumberFormatter.toFixed(gb * SizeFormatter.ONE_GB, 0); + } +}; + Inbound.VmessSettings = class extends Inbound.Settings { constructor(protocol, vmesses = [new Inbound.VmessSettings.VMESS()]) { @@ -1879,7 +2108,7 @@ Inbound.VmessSettings = class extends Inbound.Settings { static fromJson(json = {}) { return new Inbound.VmessSettings( Protocols.VMESS, - json.clients.map(client => Inbound.VmessSettings.VMESS.fromJson(client)), + (json.clients || []).map(client => Inbound.VmessSettings.VMESS.fromJson(client)), ); } @@ -1890,80 +2119,32 @@ Inbound.VmessSettings = class extends Inbound.Settings { } }; -Inbound.VmessSettings.VMESS = class extends XrayCommonClass { +Inbound.VmessSettings.VMESS = class extends Inbound.ClientBase { constructor( id = RandomUtil.randomUUID(), security = USERS_SECURITY.AUTO, - email = RandomUtil.randomLowerAndNum(8), - limitIp = 0, - totalGB = 0, - expiryTime = 0, - enable = true, - tgId = '', - subId = RandomUtil.randomLowerAndNum(16), - comment = '', - reset = 0, - created_at = undefined, - updated_at = undefined + email, limitIp, totalGB, expiryTime, enable, tgId, subId, comment, reset, created_at, updated_at, ) { - super(); + super(email, limitIp, totalGB, expiryTime, enable, tgId, subId, comment, reset, created_at, updated_at); this.id = id; this.security = security; - this.email = email; - this.limitIp = limitIp; - this.totalGB = totalGB; - this.expiryTime = expiryTime; - this.enable = enable; - this.tgId = tgId; - this.subId = subId; - this.comment = comment; - this.reset = reset; - this.created_at = created_at; - this.updated_at = updated_at; } static fromJson(json = {}) { return new Inbound.VmessSettings.VMESS( json.id, json.security, - json.email, - json.limitIp, - json.totalGB, - json.expiryTime, - json.enable, - json.tgId, - json.subId, - json.comment, - json.reset, - json.created_at, - json.updated_at, + ...Inbound.ClientBase.commonArgsFromJson(json), ); } - get _expiryTime() { - if (this.expiryTime === 0 || this.expiryTime === "") { - return null; - } - if (this.expiryTime < 0) { - return this.expiryTime / -86400000; - } - return moment(this.expiryTime); - } - set _expiryTime(t) { - if (t == null || t === "") { - this.expiryTime = 0; - } else { - this.expiryTime = t.valueOf(); - } + toJson() { + return { + id: this.id, + security: this.security, + ...this._clientBaseToJson(), + }; } - get _totalGB() { - return NumberFormatter.toFixed(this.totalGB / SizeFormatter.ONE_GB, 2); - } - - set _totalGB(gb) { - this.totalGB = NumberFormatter.toFixed(gb * SizeFormatter.ONE_GB, 0); - } - }; Inbound.VLESSSettings = class extends Inbound.Settings { @@ -2041,85 +2222,36 @@ Inbound.VLESSSettings = class extends Inbound.Settings { return json; } - - }; -Inbound.VLESSSettings.VLESS = class extends XrayCommonClass { +Inbound.VLESSSettings.VLESS = class extends Inbound.ClientBase { constructor( id = RandomUtil.randomUUID(), flow = '', - email = RandomUtil.randomLowerAndNum(8), - limitIp = 0, - totalGB = 0, - expiryTime = 0, - enable = true, - tgId = '', - subId = RandomUtil.randomLowerAndNum(16), - comment = '', - reset = 0, - created_at = undefined, - updated_at = undefined + email, limitIp, totalGB, expiryTime, enable, tgId, subId, comment, reset, created_at, updated_at, ) { - super(); + super(email, limitIp, totalGB, expiryTime, enable, tgId, subId, comment, reset, created_at, updated_at); this.id = id; this.flow = flow; - this.email = email; - this.limitIp = limitIp; - this.totalGB = totalGB; - this.expiryTime = expiryTime; - this.enable = enable; - this.tgId = tgId; - this.subId = subId; - this.comment = comment; - this.reset = reset; - this.created_at = created_at; - this.updated_at = updated_at; } static fromJson(json = {}) { return new Inbound.VLESSSettings.VLESS( json.id, json.flow, - json.email, - json.limitIp, - json.totalGB, - json.expiryTime, - json.enable, - json.tgId, - json.subId, - json.comment, - json.reset, - json.created_at, - json.updated_at, + ...Inbound.ClientBase.commonArgsFromJson(json), ); } - get _expiryTime() { - if (this.expiryTime === 0 || this.expiryTime === "") { - return null; - } - if (this.expiryTime < 0) { - return this.expiryTime / -86400000; - } - return moment(this.expiryTime); - } - - set _expiryTime(t) { - if (t == null || t === "") { - this.expiryTime = 0; - } else { - this.expiryTime = t.valueOf(); - } - } - get _totalGB() { - return NumberFormatter.toFixed(this.totalGB / SizeFormatter.ONE_GB, 2); - } - - set _totalGB(gb) { - this.totalGB = NumberFormatter.toFixed(gb * SizeFormatter.ONE_GB, 0); + toJson() { + return { + id: this.id, + flow: this.flow, + ...this._clientBaseToJson(), + }; } }; + Inbound.VLESSSettings.Fallback = class extends XrayCommonClass { constructor(name = "", alpn = '', path = '', dest = '', xver = 0) { super(); @@ -2179,7 +2311,7 @@ Inbound.TrojanSettings = class extends Inbound.Settings { static fromJson(json = {}) { return new Inbound.TrojanSettings( Protocols.TROJAN, - json.clients.map(client => Inbound.TrojanSettings.Trojan.fromJson(client)), + (json.clients || []).map(client => Inbound.TrojanSettings.Trojan.fromJson(client)), Inbound.TrojanSettings.Fallback.fromJson(json.fallbacks),); } @@ -2191,95 +2323,28 @@ Inbound.TrojanSettings = class extends Inbound.Settings { } }; -Inbound.TrojanSettings.Trojan = class extends XrayCommonClass { +Inbound.TrojanSettings.Trojan = class extends Inbound.ClientBase { constructor( password = RandomUtil.randomSeq(10), - email = RandomUtil.randomLowerAndNum(8), - limitIp = 0, - totalGB = 0, - expiryTime = 0, - enable = true, - tgId = '', - subId = RandomUtil.randomLowerAndNum(16), - comment = '', - reset = 0, - created_at = undefined, - updated_at = undefined + email, limitIp, totalGB, expiryTime, enable, tgId, subId, comment, reset, created_at, updated_at, ) { - super(); + super(email, limitIp, totalGB, expiryTime, enable, tgId, subId, comment, reset, created_at, updated_at); this.password = password; - this.email = email; - this.limitIp = limitIp; - this.totalGB = totalGB; - this.expiryTime = expiryTime; - this.enable = enable; - this.tgId = tgId; - this.subId = subId; - this.comment = comment; - this.reset = reset; - this.created_at = created_at; - this.updated_at = updated_at; } toJson() { return { password: this.password, - email: this.email, - limitIp: this.limitIp, - totalGB: this.totalGB, - expiryTime: this.expiryTime, - enable: this.enable, - tgId: this.tgId, - subId: this.subId, - comment: this.comment, - reset: this.reset, - created_at: this.created_at, - updated_at: this.updated_at, + ...this._clientBaseToJson(), }; } static fromJson(json = {}) { return new Inbound.TrojanSettings.Trojan( json.password, - json.email, - json.limitIp, - json.totalGB, - json.expiryTime, - json.enable, - json.tgId, - json.subId, - json.comment, - json.reset, - json.created_at, - json.updated_at, + ...Inbound.ClientBase.commonArgsFromJson(json), ); } - - get _expiryTime() { - if (this.expiryTime === 0 || this.expiryTime === "") { - return null; - } - if (this.expiryTime < 0) { - return this.expiryTime / -86400000; - } - return moment(this.expiryTime); - } - - set _expiryTime(t) { - if (t == null || t === "") { - this.expiryTime = 0; - } else { - this.expiryTime = t.valueOf(); - } - } - get _totalGB() { - return NumberFormatter.toFixed(this.totalGB / SizeFormatter.ONE_GB, 2); - } - - set _totalGB(gb) { - this.totalGB = NumberFormatter.toFixed(gb * SizeFormatter.ONE_GB, 0); - } - }; Inbound.TrojanSettings.Fallback = class extends XrayCommonClass { @@ -2343,7 +2408,7 @@ Inbound.ShadowsocksSettings = class extends Inbound.Settings { json.method, json.password, json.network, - json.clients.map(client => Inbound.ShadowsocksSettings.Shadowsocks.fromJson(client)), + (json.clients || []).map(client => Inbound.ShadowsocksSettings.Shadowsocks.fromJson(client)), json.ivCheck, ); } @@ -2359,53 +2424,22 @@ Inbound.ShadowsocksSettings = class extends Inbound.Settings { } }; -Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass { +Inbound.ShadowsocksSettings.Shadowsocks = class extends Inbound.ClientBase { constructor( method = '', password = RandomUtil.randomShadowsocksPassword(), - email = RandomUtil.randomLowerAndNum(8), - limitIp = 0, - totalGB = 0, - expiryTime = 0, - enable = true, - tgId = '', - subId = RandomUtil.randomLowerAndNum(16), - comment = '', - reset = 0, - created_at = undefined, - updated_at = undefined + email, limitIp, totalGB, expiryTime, enable, tgId, subId, comment, reset, created_at, updated_at, ) { - super(); + super(email, limitIp, totalGB, expiryTime, enable, tgId, subId, comment, reset, created_at, updated_at); this.method = method; this.password = password; - this.email = email; - this.limitIp = limitIp; - this.totalGB = totalGB; - this.expiryTime = expiryTime; - this.enable = enable; - this.tgId = tgId; - this.subId = subId; - this.comment = comment; - this.reset = reset; - this.created_at = created_at; - this.updated_at = updated_at; } toJson() { return { method: this.method, password: this.password, - email: this.email, - limitIp: this.limitIp, - totalGB: this.totalGB, - expiryTime: this.expiryTime, - enable: this.enable, - tgId: this.tgId, - subId: this.subId, - comment: this.comment, - reset: this.reset, - created_at: this.created_at, - updated_at: this.updated_at, + ...this._clientBaseToJson(), }; } @@ -2413,45 +2447,56 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass { return new Inbound.ShadowsocksSettings.Shadowsocks( json.method, json.password, - json.email, - json.limitIp, - json.totalGB, - json.expiryTime, - json.enable, - json.tgId, - json.subId, - json.comment, - json.reset, - json.created_at, - json.updated_at, + ...Inbound.ClientBase.commonArgsFromJson(json), + ); + } +}; + +Inbound.HysteriaSettings = class extends Inbound.Settings { + constructor(protocol, version = 2, hysterias = [new Inbound.HysteriaSettings.Hysteria()]) { + super(protocol); + this.version = version; + this.hysterias = hysterias; + } + + static fromJson(json = {}) { + return new Inbound.HysteriaSettings( + Protocols.HYSTERIA, + json.version ?? 2, + (json.clients || []).map(client => Inbound.HysteriaSettings.Hysteria.fromJson(client)), ); } - get _expiryTime() { - if (this.expiryTime === 0 || this.expiryTime === "") { - return null; - } - if (this.expiryTime < 0) { - return this.expiryTime / -86400000; - } - return moment(this.expiryTime); + toJson() { + return { + version: this.version, + clients: Inbound.HysteriaSettings.toJsonArray(this.hysterias), + }; + } +}; + +Inbound.HysteriaSettings.Hysteria = class extends Inbound.ClientBase { + constructor( + auth = RandomUtil.randomSeq(10), + email, limitIp, totalGB, expiryTime, enable, tgId, subId, comment, reset, created_at, updated_at, + ) { + super(email, limitIp, totalGB, expiryTime, enable, tgId, subId, comment, reset, created_at, updated_at); + this.auth = auth; } - set _expiryTime(t) { - if (t == null || t === "") { - this.expiryTime = 0; - } else { - this.expiryTime = t.valueOf(); - } - } - get _totalGB() { - return NumberFormatter.toFixed(this.totalGB / SizeFormatter.ONE_GB, 2); + toJson() { + return { + auth: this.auth, + ...this._clientBaseToJson(), + }; } - set _totalGB(gb) { - this.totalGB = NumberFormatter.toFixed(gb * SizeFormatter.ONE_GB, 0); + static fromJson(json = {}) { + return new Inbound.HysteriaSettings.Hysteria( + json.auth, + ...Inbound.ClientBase.commonArgsFromJson(json), + ); } - }; Inbound.TunnelSettings = class extends Inbound.Settings { @@ -2707,4 +2752,4 @@ Inbound.TunSettings = class extends Inbound.Settings { userLevel: this.userLevel || 0, }; } -}; +}; \ No newline at end of file diff --git a/web/assets/js/model/outbound.js b/web/assets/js/model/outbound.js index 56606231..b8ec4c12 100644 --- a/web/assets/js/model/outbound.js +++ b/web/assets/js/model/outbound.js @@ -782,8 +782,8 @@ class Outbound extends CommonClass { } canEnableTls() { - if (![Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks, Protocols.Hysteria].includes(this.protocol)) return false; - if (this.protocol === Protocols.Hysteria) return this.stream.network === 'hysteria'; + if (this.protocol === Protocols.Hysteria) return true; + if (![Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks].includes(this.protocol)) return false; return ["tcp", "ws", "http", "grpc", "httpupgrade", "xhttp"].includes(this.stream.network); } diff --git a/web/html/component/aClientTable.html b/web/html/component/aClientTable.html index d9a9b5f5..0e32d45c 100644 --- a/web/html/component/aClientTable.html +++ b/web/html/component/aClientTable.html @@ -30,7 +30,7 @@ {{ i18n "pages.inbounds.email" }} - + - + - + + + + + - - - [[ key ]] + + + [[ key + ]] @@ -51,7 +76,8 @@ {{ i18n "pages.inbounds.subscriptionDesc" }} Subscription - + @@ -66,7 +92,8 @@ - + @@ -77,19 +104,21 @@ - {{ i18n "pages.inbounds.IPLimit"}} + {{ i18n "pages.inbounds.IPLimit"}} - + - + @@ -98,19 +127,24 @@ {{ i18n "pages.inbounds.IPLimitlogclear" }} - + - - - {{ i18n "none" }} - [[ key ]] + + {{ i18n "none" }} + [[ key + ]] @@ -123,45 +157,57 @@ - + - + [[ SizeFormatter.sizeFormat(clientStats.up) ]] / [[ SizeFormatter.sizeFormat(clientStats.down) ]] ([[ SizeFormatter.sizeFormat(clientStats.up + clientStats.down) ]]) - + - + - - + + - - + + Expired + + +