修复SS2022订阅下发错误的问题
This commit is contained in:
@@ -264910,7 +264910,7 @@ function dqt({ className: e }) {
|
|||||||
e.change >= 0
|
e.change >= 0
|
||||||
? Q.jsx(iat, { className: "mr-1 h-3 w-3" })
|
? Q.jsx(iat, { className: "mr-1 h-3 w-3" })
|
||||||
: Q.jsx(eat, { className: "mr-1 h-3 w-3" }),
|
: Q.jsx(eat, { className: "mr-1 h-3 w-3" }),
|
||||||
Math.abs(e.change),
|
Math.abs(Number(e.change || 0)).toFixed(2),
|
||||||
"%",
|
"%",
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
@@ -264971,7 +264971,11 @@ function dqt({ className: e }) {
|
|||||||
"font-medium",
|
"font-medium",
|
||||||
e.change >= 0 ? "text-green-600" : "text-red-600",
|
e.change >= 0 ? "text-green-600" : "text-red-600",
|
||||||
),
|
),
|
||||||
children: [e.change >= 0 ? "+" : "", e.change, "%"],
|
children: [
|
||||||
|
e.change >= 0 ? "+" : "",
|
||||||
|
Number(e.change || 0).toFixed(2),
|
||||||
|
"%",
|
||||||
|
],
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
@@ -265063,7 +265067,7 @@ function dqt({ className: e }) {
|
|||||||
e.change >= 0
|
e.change >= 0
|
||||||
? Q.jsx(iat, { className: "mr-1 h-3 w-3" })
|
? Q.jsx(iat, { className: "mr-1 h-3 w-3" })
|
||||||
: Q.jsx(eat, { className: "mr-1 h-3 w-3" }),
|
: Q.jsx(eat, { className: "mr-1 h-3 w-3" }),
|
||||||
Math.abs(e.change),
|
Math.abs(Number(e.change || 0)).toFixed(2),
|
||||||
"%",
|
"%",
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
@@ -265124,7 +265128,11 @@ function dqt({ className: e }) {
|
|||||||
"font-medium",
|
"font-medium",
|
||||||
e.change >= 0 ? "text-green-600" : "text-red-600",
|
e.change >= 0 ? "text-green-600" : "text-red-600",
|
||||||
),
|
),
|
||||||
children: [e.change >= 0 ? "+" : "", e.change, "%"],
|
children: [
|
||||||
|
e.change >= 0 ? "+" : "",
|
||||||
|
Number(e.change || 0).toFixed(2),
|
||||||
|
"%",
|
||||||
|
],
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
@@ -272190,8 +272198,8 @@ function HGt({ open: e, onOpenChange: t, result: n }) {
|
|||||||
Q.jsx("div", { children: n.config.encryption || "无" }),
|
Q.jsx("div", { children: n.config.encryption || "无" }),
|
||||||
Q.jsx("div", { className: "text-muted-foreground", children: "发件人" }),
|
Q.jsx("div", { className: "text-muted-foreground", children: "发件人" }),
|
||||||
Q.jsx("div", {
|
Q.jsx("div", {
|
||||||
children: n.config.from.address
|
children: (n.config.from?.address || n.config.from_address)
|
||||||
? `${n.config.from.address}${n.config.from.name ? ` (${n.config.from.name})` : ""}`
|
? `${n.config.from?.address || n.config.from_address}${n.config.from?.name || n.config.from_name ? ` (${n.config.from?.name || n.config.from_name})` : ""}`
|
||||||
: "未设置",
|
: "未设置",
|
||||||
}),
|
}),
|
||||||
Q.jsx("div", { className: "text-muted-foreground", children: "用户名" }),
|
Q.jsx("div", { className: "text-muted-foreground", children: "用户名" }),
|
||||||
|
|||||||
@@ -28,7 +28,8 @@ func GenerateClashWithTemplate(templateName string, servers []model.Server, user
|
|||||||
proxyNames := make([]string, 0, len(servers))
|
proxyNames := make([]string, 0, len(servers))
|
||||||
for _, s := range servers {
|
for _, s := range servers {
|
||||||
conf := service.BuildNodeConfig(&s)
|
conf := service.BuildNodeConfig(&s)
|
||||||
proxy := buildClashProxy(conf, user)
|
password := service.GenerateServerPassword(&s, &user)
|
||||||
|
proxy := buildClashProxy(conf, password)
|
||||||
if proxy == nil {
|
if proxy == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -52,7 +53,7 @@ func GenerateClashWithTemplate(templateName string, servers []model.Server, user
|
|||||||
return output, nil
|
return output, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildClashProxy(conf service.NodeServerConfig, user model.User) map[string]any {
|
func buildClashProxy(conf service.NodeServerConfig, password string) map[string]any {
|
||||||
switch conf.Protocol {
|
switch conf.Protocol {
|
||||||
case "shadowsocks":
|
case "shadowsocks":
|
||||||
cipher, _ := conf.Cipher.(string)
|
cipher, _ := conf.Cipher.(string)
|
||||||
@@ -62,7 +63,7 @@ func buildClashProxy(conf service.NodeServerConfig, user model.User) map[string]
|
|||||||
"server": conf.RawHost,
|
"server": conf.RawHost,
|
||||||
"port": conf.ServerPort,
|
"port": conf.ServerPort,
|
||||||
"cipher": cipher,
|
"cipher": cipher,
|
||||||
"password": user.UUID,
|
"password": password,
|
||||||
}
|
}
|
||||||
case "vmess":
|
case "vmess":
|
||||||
return map[string]any{
|
return map[string]any{
|
||||||
@@ -70,7 +71,7 @@ func buildClashProxy(conf service.NodeServerConfig, user model.User) map[string]
|
|||||||
"type": "vmess",
|
"type": "vmess",
|
||||||
"server": conf.RawHost,
|
"server": conf.RawHost,
|
||||||
"port": conf.ServerPort,
|
"port": conf.ServerPort,
|
||||||
"uuid": user.UUID,
|
"uuid": password,
|
||||||
"alterId": 0,
|
"alterId": 0,
|
||||||
"cipher": "auto",
|
"cipher": "auto",
|
||||||
"udp": true,
|
"udp": true,
|
||||||
@@ -81,7 +82,7 @@ func buildClashProxy(conf service.NodeServerConfig, user model.User) map[string]
|
|||||||
"type": "trojan",
|
"type": "trojan",
|
||||||
"server": conf.RawHost,
|
"server": conf.RawHost,
|
||||||
"port": conf.ServerPort,
|
"port": conf.ServerPort,
|
||||||
"password": user.UUID,
|
"password": password,
|
||||||
"udp": true,
|
"udp": true,
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@@ -161,7 +162,8 @@ func generateClashFallback(servers []model.Server, user model.User) string {
|
|||||||
var proxyNames []string
|
var proxyNames []string
|
||||||
for _, s := range servers {
|
for _, s := range servers {
|
||||||
conf := service.BuildNodeConfig(&s)
|
conf := service.BuildNodeConfig(&s)
|
||||||
proxy := buildClashProxy(conf, user)
|
password := service.GenerateServerPassword(&s, &user)
|
||||||
|
proxy := buildClashProxy(conf, password)
|
||||||
if proxy == nil {
|
if proxy == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -174,7 +176,7 @@ func generateClashFallback(servers []model.Server, user model.User) string {
|
|||||||
conf.RawHost,
|
conf.RawHost,
|
||||||
conf.ServerPort,
|
conf.ServerPort,
|
||||||
fmt.Sprint(proxy["cipher"]),
|
fmt.Sprint(proxy["cipher"]),
|
||||||
user.UUID,
|
fmt.Sprint(proxy["password"]),
|
||||||
))
|
))
|
||||||
case "vmess":
|
case "vmess":
|
||||||
builder.WriteString(fmt.Sprintf(
|
builder.WriteString(fmt.Sprintf(
|
||||||
@@ -182,7 +184,7 @@ func generateClashFallback(servers []model.Server, user model.User) string {
|
|||||||
conf.Name,
|
conf.Name,
|
||||||
conf.RawHost,
|
conf.RawHost,
|
||||||
conf.ServerPort,
|
conf.ServerPort,
|
||||||
user.UUID,
|
fmt.Sprint(proxy["uuid"]),
|
||||||
))
|
))
|
||||||
case "trojan":
|
case "trojan":
|
||||||
builder.WriteString(fmt.Sprintf(
|
builder.WriteString(fmt.Sprintf(
|
||||||
@@ -190,7 +192,7 @@ func generateClashFallback(servers []model.Server, user model.User) string {
|
|||||||
conf.Name,
|
conf.Name,
|
||||||
conf.RawHost,
|
conf.RawHost,
|
||||||
conf.ServerPort,
|
conf.ServerPort,
|
||||||
user.UUID,
|
fmt.Sprint(proxy["password"]),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
proxyNames = append(proxyNames, fmt.Sprintf("\"%s\"", conf.Name))
|
proxyNames = append(proxyNames, fmt.Sprintf("\"%s\"", conf.Name))
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ func GenerateGeneralLinks(servers []model.Server, user model.User) string {
|
|||||||
var links []string
|
var links []string
|
||||||
for _, s := range servers {
|
for _, s := range servers {
|
||||||
conf := service.BuildNodeConfig(&s)
|
conf := service.BuildNodeConfig(&s)
|
||||||
link := BuildLink(conf, user)
|
password := service.GenerateServerPassword(&s, &user)
|
||||||
|
link := BuildLink(conf, password)
|
||||||
if link != "" {
|
if link != "" {
|
||||||
links = append(links, link)
|
links = append(links, link)
|
||||||
}
|
}
|
||||||
@@ -22,36 +23,35 @@ func GenerateGeneralLinks(servers []model.Server, user model.User) string {
|
|||||||
return strings.Join(links, "\n")
|
return strings.Join(links, "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildLink(c service.NodeServerConfig, user model.User) string {
|
func BuildLink(c service.NodeServerConfig, password string) string {
|
||||||
switch c.Protocol {
|
switch c.Protocol {
|
||||||
case "shadowsocks":
|
case "shadowsocks":
|
||||||
return buildShadowsocks(c, user)
|
return buildShadowsocks(c, password)
|
||||||
case "vmess":
|
case "vmess":
|
||||||
return buildVmess(c, user)
|
return buildVmess(c, password)
|
||||||
case "vless":
|
case "vless":
|
||||||
return buildVless(c, user)
|
return buildVless(c, password)
|
||||||
case "trojan":
|
case "trojan":
|
||||||
return buildTrojan(c, user)
|
return buildTrojan(c, password)
|
||||||
case "hysteria", "hysteria2":
|
case "hysteria", "hysteria2":
|
||||||
return buildHysteria(c, user)
|
return buildHysteria(c, password)
|
||||||
case "tuic":
|
case "tuic":
|
||||||
return buildTuic(c, user)
|
return buildTuic(c, password)
|
||||||
case "anytls":
|
case "anytls":
|
||||||
return buildAnyTLS(c, user)
|
return buildAnyTLS(c, password)
|
||||||
case "socks":
|
case "socks":
|
||||||
return buildSocks(c, user)
|
return buildSocks(c, password)
|
||||||
case "http":
|
case "http":
|
||||||
return buildHttp(c, user)
|
return buildHttp(c, password)
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildShadowsocks(c service.NodeServerConfig, user model.User) string {
|
func buildShadowsocks(c service.NodeServerConfig, password string) string {
|
||||||
cipher := toString(c.Cipher)
|
cipher := toString(c.Cipher)
|
||||||
password := user.UUID
|
|
||||||
userInfo := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", cipher, password)))
|
userInfo := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", cipher, password)))
|
||||||
userInfo = strings.TrimRight(strings.ReplaceAll(strings.ReplaceAll(userInfo, "+", "-"), "/", "_"), "=")
|
userInfo = strings.TrimRight(strings.ReplaceAll(strings.ReplaceAll(userInfo, "+", "-"), "/", "_"), "=")
|
||||||
|
|
||||||
link := fmt.Sprintf("ss://%s@%s:%d", userInfo, wrapIPv6(c.RawHost), c.ServerPort)
|
link := fmt.Sprintf("ss://%s@%s:%d", userInfo, wrapIPv6(c.RawHost), c.ServerPort)
|
||||||
params := url.Values{}
|
params := url.Values{}
|
||||||
if plugin := toString(c.Plugin); plugin != "" {
|
if plugin := toString(c.Plugin); plugin != "" {
|
||||||
@@ -64,13 +64,13 @@ func buildShadowsocks(c service.NodeServerConfig, user model.User) string {
|
|||||||
return link + "#" + url.PathEscape(c.Name)
|
return link + "#" + url.PathEscape(c.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildVmess(c service.NodeServerConfig, user model.User) string {
|
func buildVmess(c service.NodeServerConfig, password string) string {
|
||||||
m := map[string]any{
|
m := map[string]any{
|
||||||
"v": "2",
|
"v": "2",
|
||||||
"ps": c.Name,
|
"ps": c.Name,
|
||||||
"add": c.RawHost,
|
"add": c.RawHost,
|
||||||
"port": fmt.Sprintf("%d", c.ServerPort),
|
"port": fmt.Sprintf("%d", c.ServerPort),
|
||||||
"id": user.UUID,
|
"id": password,
|
||||||
"aid": "0",
|
"aid": "0",
|
||||||
"net": toString(c.Network),
|
"net": toString(c.Network),
|
||||||
"type": "none",
|
"type": "none",
|
||||||
@@ -81,7 +81,7 @@ func buildVmess(c service.NodeServerConfig, user model.User) string {
|
|||||||
if c.Tls != nil && toInt(c.Tls) > 0 {
|
if c.Tls != nil && toInt(c.Tls) > 0 {
|
||||||
m["tls"] = "tls"
|
m["tls"] = "tls"
|
||||||
}
|
}
|
||||||
|
|
||||||
if settings, ok := c.NetworkSettings.(map[string]any); ok {
|
if settings, ok := c.NetworkSettings.(map[string]any); ok {
|
||||||
switch toString(c.Network) {
|
switch toString(c.Network) {
|
||||||
case "ws":
|
case "ws":
|
||||||
@@ -96,7 +96,7 @@ func buildVmess(c service.NodeServerConfig, user model.User) string {
|
|||||||
m["host"] = toString(settings["host"])
|
m["host"] = toString(settings["host"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if tlsSettings, ok := c.TlsSettings.(map[string]any); ok {
|
if tlsSettings, ok := c.TlsSettings.(map[string]any); ok {
|
||||||
m["sni"] = toString(tlsSettings["server_name"])
|
m["sni"] = toString(tlsSettings["server_name"])
|
||||||
}
|
}
|
||||||
@@ -105,13 +105,13 @@ func buildVmess(c service.NodeServerConfig, user model.User) string {
|
|||||||
return "vmess://" + base64.StdEncoding.EncodeToString(b)
|
return "vmess://" + base64.StdEncoding.EncodeToString(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildVless(c service.NodeServerConfig, user model.User) string {
|
func buildVless(c service.NodeServerConfig, password string) string {
|
||||||
params := url.Values{}
|
params := url.Values{}
|
||||||
params.Set("encryption", "none")
|
params.Set("encryption", "none")
|
||||||
if c.Flow != nil {
|
if c.Flow != nil {
|
||||||
params.Set("flow", toString(c.Flow))
|
params.Set("flow", toString(c.Flow))
|
||||||
}
|
}
|
||||||
|
|
||||||
security := "none"
|
security := "none"
|
||||||
switch toInt(c.Tls) {
|
switch toInt(c.Tls) {
|
||||||
case 1:
|
case 1:
|
||||||
@@ -132,7 +132,7 @@ func buildVless(c service.NodeServerConfig, user model.User) string {
|
|||||||
}
|
}
|
||||||
params.Set("security", security)
|
params.Set("security", security)
|
||||||
params.Set("type", toString(c.Network))
|
params.Set("type", toString(c.Network))
|
||||||
|
|
||||||
if settings, ok := c.NetworkSettings.(map[string]any); ok {
|
if settings, ok := c.NetworkSettings.(map[string]any); ok {
|
||||||
switch toString(c.Network) {
|
switch toString(c.Network) {
|
||||||
case "ws":
|
case "ws":
|
||||||
@@ -145,10 +145,10 @@ func buildVless(c service.NodeServerConfig, user model.User) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf("vless://%s@%s:%d?%s#%s", user.UUID, wrapIPv6(c.RawHost), c.ServerPort, params.Encode(), url.PathEscape(c.Name))
|
return fmt.Sprintf("vless://%s@%s:%d?%s#%s", password, wrapIPv6(c.RawHost), c.ServerPort, params.Encode(), url.PathEscape(c.Name))
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildTrojan(c service.NodeServerConfig, user model.User) string {
|
func buildTrojan(c service.NodeServerConfig, password string) string {
|
||||||
params := url.Values{}
|
params := url.Values{}
|
||||||
security := "tls"
|
security := "tls"
|
||||||
if toInt(c.Tls) == 2 {
|
if toInt(c.Tls) == 2 {
|
||||||
@@ -164,7 +164,7 @@ func buildTrojan(c service.NodeServerConfig, user model.User) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
params.Set("security", security)
|
params.Set("security", security)
|
||||||
|
|
||||||
if settings, ok := c.NetworkSettings.(map[string]any); ok {
|
if settings, ok := c.NetworkSettings.(map[string]any); ok {
|
||||||
switch toString(c.Network) {
|
switch toString(c.Network) {
|
||||||
case "ws":
|
case "ws":
|
||||||
@@ -176,51 +176,55 @@ func buildTrojan(c service.NodeServerConfig, user model.User) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf("trojan://%s@%s:%d?%s#%s", user.UUID, wrapIPv6(c.RawHost), c.ServerPort, params.Encode(), url.PathEscape(c.Name))
|
return fmt.Sprintf("trojan://%s@%s:%d?%s#%s", password, wrapIPv6(c.RawHost), c.ServerPort, params.Encode(), url.PathEscape(c.Name))
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildHysteria(c service.NodeServerConfig, user model.User) string {
|
func buildHysteria(c service.NodeServerConfig, password string) string {
|
||||||
params := url.Values{}
|
params := url.Values{}
|
||||||
params.Set("sni", toString(c.ServerName))
|
params.Set("sni", toString(c.ServerName))
|
||||||
|
|
||||||
if c.Protocol == "hysteria2" || toInt(c.Version) == 2 {
|
if c.Protocol == "hysteria2" || toInt(c.Version) == 2 {
|
||||||
if obfs := toString(c.Obfs); obfs != "" {
|
if obfs := toString(c.Obfs); obfs != "" {
|
||||||
params.Set("obfs", "salamander")
|
params.Set("obfs", "salamander")
|
||||||
params.Set("obfs-password", toString(c.ObfsPassword))
|
params.Set("obfs-password", toString(c.ObfsPassword))
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("hysteria2://%s@%s:%d?%s#%s", user.UUID, wrapIPv6(c.RawHost), c.ServerPort, params.Encode(), url.PathEscape(c.Name))
|
return fmt.Sprintf("hysteria2://%s@%s:%d?%s#%s", password, wrapIPv6(c.RawHost), c.ServerPort, params.Encode(), url.PathEscape(c.Name))
|
||||||
}
|
}
|
||||||
|
|
||||||
params.Set("protocol", "udp")
|
params.Set("protocol", "udp")
|
||||||
params.Set("auth", user.UUID)
|
params.Set("auth", password)
|
||||||
if c.UpMbps != nil { params.Set("upmbps", fmt.Sprintf("%v", c.UpMbps)) }
|
if c.UpMbps != nil {
|
||||||
if c.DownMbps != nil { params.Set("downmbps", fmt.Sprintf("%v", c.DownMbps)) }
|
params.Set("upmbps", fmt.Sprintf("%v", c.UpMbps))
|
||||||
|
}
|
||||||
|
if c.DownMbps != nil {
|
||||||
|
params.Set("downmbps", fmt.Sprintf("%v", c.DownMbps))
|
||||||
|
}
|
||||||
|
|
||||||
return fmt.Sprintf("hysteria://%s:%d?%s#%s", wrapIPv6(c.RawHost), c.ServerPort, params.Encode(), url.PathEscape(c.Name))
|
return fmt.Sprintf("hysteria://%s:%d?%s#%s", wrapIPv6(c.RawHost), c.ServerPort, params.Encode(), url.PathEscape(c.Name))
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildTuic(c service.NodeServerConfig, user model.User) string {
|
func buildTuic(c service.NodeServerConfig, password string) string {
|
||||||
params := url.Values{}
|
params := url.Values{}
|
||||||
params.Set("sni", toString(c.ServerName))
|
params.Set("sni", toString(c.ServerName))
|
||||||
params.Set("congestion_control", toString(c.CongestionControl))
|
params.Set("congestion_control", toString(c.CongestionControl))
|
||||||
params.Set("udp-relay-mode", "native")
|
params.Set("udp-relay-mode", "native")
|
||||||
|
|
||||||
return fmt.Sprintf("tuic://%s:%s@%s:%d?%s#%s", user.UUID, user.UUID, wrapIPv6(c.RawHost), c.ServerPort, params.Encode(), url.PathEscape(c.Name))
|
return fmt.Sprintf("tuic://%s:%s@%s:%d?%s#%s", password, password, wrapIPv6(c.RawHost), c.ServerPort, params.Encode(), url.PathEscape(c.Name))
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildAnyTLS(c service.NodeServerConfig, user model.User) string {
|
func buildAnyTLS(c service.NodeServerConfig, password string) string {
|
||||||
params := url.Values{}
|
params := url.Values{}
|
||||||
params.Set("sni", toString(c.ServerName))
|
params.Set("sni", toString(c.ServerName))
|
||||||
return fmt.Sprintf("anytls://%s@%s:%d?%s#%s", user.UUID, wrapIPv6(c.RawHost), c.ServerPort, params.Encode(), url.PathEscape(c.Name))
|
return fmt.Sprintf("anytls://%s@%s:%d?%s#%s", password, wrapIPv6(c.RawHost), c.ServerPort, params.Encode(), url.PathEscape(c.Name))
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildSocks(c service.NodeServerConfig, user model.User) string {
|
func buildSocks(c service.NodeServerConfig, password string) string {
|
||||||
auth := base64.StdEncoding.EncodeToString([]byte(user.UUID + ":" + user.UUID))
|
auth := base64.StdEncoding.EncodeToString([]byte(password + ":" + password))
|
||||||
return fmt.Sprintf("socks://%s@%s:%d#%s", auth, wrapIPv6(c.RawHost), c.ServerPort, url.PathEscape(c.Name))
|
return fmt.Sprintf("socks://%s@%s:%d#%s", auth, wrapIPv6(c.RawHost), c.ServerPort, url.PathEscape(c.Name))
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildHttp(c service.NodeServerConfig, user model.User) string {
|
func buildHttp(c service.NodeServerConfig, password string) string {
|
||||||
auth := base64.StdEncoding.EncodeToString([]byte(user.UUID + ":" + user.UUID))
|
auth := base64.StdEncoding.EncodeToString([]byte(password + ":" + password))
|
||||||
link := fmt.Sprintf("http://%s@%s:%d", auth, wrapIPv6(c.RawHost), c.ServerPort)
|
link := fmt.Sprintf("http://%s@%s:%d", auth, wrapIPv6(c.RawHost), c.ServerPort)
|
||||||
if toInt(c.Tls) > 0 {
|
if toInt(c.Tls) > 0 {
|
||||||
params := url.Values{}
|
params := url.Values{}
|
||||||
@@ -238,16 +242,23 @@ func wrapIPv6(host string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func toString(v any) string {
|
func toString(v any) string {
|
||||||
if s, ok := v.(string); ok { return s }
|
if s, ok := v.(string); ok {
|
||||||
if v == nil { return "" }
|
return s
|
||||||
|
}
|
||||||
|
if v == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
return fmt.Sprintf("%v", v)
|
return fmt.Sprintf("%v", v)
|
||||||
}
|
}
|
||||||
|
|
||||||
func toInt(v any) int {
|
func toInt(v any) int {
|
||||||
switch typed := v.(type) {
|
switch typed := v.(type) {
|
||||||
case int: return typed
|
case int:
|
||||||
case int64: return int(typed)
|
return typed
|
||||||
case float64: return int(typed)
|
case int64:
|
||||||
|
return int(typed)
|
||||||
|
case float64:
|
||||||
|
return int(typed)
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ func GenerateSingBox(servers []model.Server, user model.User) (string, error) {
|
|||||||
proxyTags := make([]string, 0, len(servers))
|
proxyTags := make([]string, 0, len(servers))
|
||||||
for _, s := range servers {
|
for _, s := range servers {
|
||||||
conf := service.BuildNodeConfig(&s)
|
conf := service.BuildNodeConfig(&s)
|
||||||
outbound := buildSingBoxOutbound(conf, user)
|
password := service.GenerateServerPassword(&s, &user)
|
||||||
|
outbound := buildSingBoxOutbound(conf, password)
|
||||||
if outbound == nil {
|
if outbound == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -59,7 +60,7 @@ func GenerateSingBox(servers []model.Server, user model.User) (string, error) {
|
|||||||
return string(data), nil
|
return string(data), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildSingBoxOutbound(conf service.NodeServerConfig, user model.User) map[string]any {
|
func buildSingBoxOutbound(conf service.NodeServerConfig, password string) map[string]any {
|
||||||
outbound := map[string]any{
|
outbound := map[string]any{
|
||||||
"tag": conf.Name,
|
"tag": conf.Name,
|
||||||
"server": conf.RawHost,
|
"server": conf.RawHost,
|
||||||
@@ -70,14 +71,14 @@ func buildSingBoxOutbound(conf service.NodeServerConfig, user model.User) map[st
|
|||||||
case "shadowsocks":
|
case "shadowsocks":
|
||||||
outbound["type"] = "shadowsocks"
|
outbound["type"] = "shadowsocks"
|
||||||
outbound["method"] = conf.Cipher
|
outbound["method"] = conf.Cipher
|
||||||
outbound["password"] = user.UUID
|
outbound["password"] = password
|
||||||
case "vmess":
|
case "vmess":
|
||||||
outbound["type"] = "vmess"
|
outbound["type"] = "vmess"
|
||||||
outbound["uuid"] = user.UUID
|
outbound["uuid"] = password
|
||||||
outbound["security"] = "auto"
|
outbound["security"] = "auto"
|
||||||
case "trojan":
|
case "trojan":
|
||||||
outbound["type"] = "trojan"
|
outbound["type"] = "trojan"
|
||||||
outbound["password"] = user.UUID
|
outbound["password"] = password
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -91,6 +92,7 @@ func generateSingBoxFallback(servers []model.Server, user model.User) (string, e
|
|||||||
|
|
||||||
for _, s := range servers {
|
for _, s := range servers {
|
||||||
conf := service.BuildNodeConfig(&s)
|
conf := service.BuildNodeConfig(&s)
|
||||||
|
password := service.GenerateServerPassword(&s, &user)
|
||||||
outbound := map[string]interface{}{
|
outbound := map[string]interface{}{
|
||||||
"tag": conf.Name,
|
"tag": conf.Name,
|
||||||
"server": conf.RawHost,
|
"server": conf.RawHost,
|
||||||
@@ -101,14 +103,14 @@ func generateSingBoxFallback(servers []model.Server, user model.User) (string, e
|
|||||||
case "shadowsocks":
|
case "shadowsocks":
|
||||||
outbound["type"] = "shadowsocks"
|
outbound["type"] = "shadowsocks"
|
||||||
outbound["method"] = conf.Cipher
|
outbound["method"] = conf.Cipher
|
||||||
outbound["password"] = user.UUID
|
outbound["password"] = password
|
||||||
case "vmess":
|
case "vmess":
|
||||||
outbound["type"] = "vmess"
|
outbound["type"] = "vmess"
|
||||||
outbound["uuid"] = user.UUID
|
outbound["uuid"] = password
|
||||||
outbound["security"] = "auto"
|
outbound["security"] = "auto"
|
||||||
case "trojan":
|
case "trojan":
|
||||||
outbound["type"] = "trojan"
|
outbound["type"] = "trojan"
|
||||||
outbound["password"] = user.UUID
|
outbound["password"] = password
|
||||||
default:
|
default:
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,11 +57,15 @@ func LoadEmailConfig() EmailConfig {
|
|||||||
|
|
||||||
func (cfg EmailConfig) DebugConfig() map[string]any {
|
func (cfg EmailConfig) DebugConfig() map[string]any {
|
||||||
return map[string]any{
|
return map[string]any{
|
||||||
"driver": "smtp",
|
"driver": "smtp",
|
||||||
"host": cfg.Host,
|
"host": cfg.Host,
|
||||||
"port": cfg.Port,
|
"port": cfg.Port,
|
||||||
"encryption": cfg.Encryption,
|
"encryption": cfg.Encryption,
|
||||||
"username": cfg.Username,
|
"username": cfg.Username,
|
||||||
|
"from": map[string]any{
|
||||||
|
"address": cfg.SenderAddress(),
|
||||||
|
"name": cfg.FromName,
|
||||||
|
},
|
||||||
"from_address": cfg.SenderAddress(),
|
"from_address": cfg.SenderAddress(),
|
||||||
"from_name": cfg.FromName,
|
"from_name": cfg.FromName,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,17 +21,35 @@ var serverTypeAliases = map[string]string{
|
|||||||
}
|
}
|
||||||
|
|
||||||
var validServerTypes = map[string]struct{}{
|
var validServerTypes = map[string]struct{}{
|
||||||
"anytls": {},
|
"anytls": {},
|
||||||
"http": {},
|
"http": {},
|
||||||
"hysteria": {},
|
"hysteria": {},
|
||||||
"mieru": {},
|
"mieru": {},
|
||||||
"naive": {},
|
"naive": {},
|
||||||
"shadowsocks": {},
|
"shadowsocks": {},
|
||||||
"socks": {},
|
"socks": {},
|
||||||
"trojan": {},
|
"trojan": {},
|
||||||
"tuic": {},
|
"tuic": {},
|
||||||
"vless": {},
|
"vless": {},
|
||||||
"vmess": {},
|
"vmess": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
var shadowsocks2022CipherConfigs = map[string]struct {
|
||||||
|
serverKeySize int
|
||||||
|
userKeySize int
|
||||||
|
}{
|
||||||
|
"2022-blake3-aes-128-gcm": {
|
||||||
|
serverKeySize: 16,
|
||||||
|
userKeySize: 16,
|
||||||
|
},
|
||||||
|
"2022-blake3-aes-256-gcm": {
|
||||||
|
serverKeySize: 32,
|
||||||
|
userKeySize: 32,
|
||||||
|
},
|
||||||
|
"2022-blake3-chacha20-poly1305": {
|
||||||
|
serverKeySize: 32,
|
||||||
|
userKeySize: 32,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
type NodeUser struct {
|
type NodeUser struct {
|
||||||
@@ -47,41 +65,41 @@ type NodeBaseConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type NodeServerConfig struct {
|
type NodeServerConfig struct {
|
||||||
Name string `json:"-"`
|
Name string `json:"-"`
|
||||||
Protocol string `json:"protocol"`
|
Protocol string `json:"protocol"`
|
||||||
RawHost string `json:"-"`
|
RawHost string `json:"-"`
|
||||||
ListenIP string `json:"listen_ip"`
|
ListenIP string `json:"listen_ip"`
|
||||||
ServerPort int `json:"server_port"`
|
ServerPort int `json:"server_port"`
|
||||||
Network any `json:"network"`
|
Network any `json:"network"`
|
||||||
NetworkSettings any `json:"networkSettings"`
|
NetworkSettings any `json:"networkSettings"`
|
||||||
Cipher any `json:"cipher,omitempty"`
|
Cipher any `json:"cipher,omitempty"`
|
||||||
Plugin any `json:"plugin,omitempty"`
|
Plugin any `json:"plugin,omitempty"`
|
||||||
PluginOpts any `json:"plugin_opts,omitempty"`
|
PluginOpts any `json:"plugin_opts,omitempty"`
|
||||||
ServerKey any `json:"server_key,omitempty"`
|
ServerKey any `json:"server_key,omitempty"`
|
||||||
Host any `json:"host,omitempty"`
|
Host any `json:"host,omitempty"`
|
||||||
ServerName any `json:"server_name,omitempty"`
|
ServerName any `json:"server_name,omitempty"`
|
||||||
Tls any `json:"tls,omitempty"`
|
Tls any `json:"tls,omitempty"`
|
||||||
TlsSettings any `json:"tls_settings,omitempty"`
|
TlsSettings any `json:"tls_settings,omitempty"`
|
||||||
Flow any `json:"flow,omitempty"`
|
Flow any `json:"flow,omitempty"`
|
||||||
Multiplex any `json:"multiplex,omitempty"`
|
Multiplex any `json:"multiplex,omitempty"`
|
||||||
UpMbps any `json:"up_mbps,omitempty"`
|
UpMbps any `json:"up_mbps,omitempty"`
|
||||||
DownMbps any `json:"down_mbps,omitempty"`
|
DownMbps any `json:"down_mbps,omitempty"`
|
||||||
Version any `json:"version,omitempty"`
|
Version any `json:"version,omitempty"`
|
||||||
Obfs any `json:"obfs,omitempty"`
|
Obfs any `json:"obfs,omitempty"`
|
||||||
ObfsPassword any `json:"obfs-password,omitempty"`
|
ObfsPassword any `json:"obfs-password,omitempty"`
|
||||||
CongestionControl any `json:"congestion_control,omitempty"`
|
CongestionControl any `json:"congestion_control,omitempty"`
|
||||||
AuthTimeout any `json:"auth_timeout,omitempty"`
|
AuthTimeout any `json:"auth_timeout,omitempty"`
|
||||||
ZeroRTTHandshake any `json:"zero_rtt_handshake,omitempty"`
|
ZeroRTTHandshake any `json:"zero_rtt_handshake,omitempty"`
|
||||||
Heartbeat any `json:"heartbeat,omitempty"`
|
Heartbeat any `json:"heartbeat,omitempty"`
|
||||||
PaddingScheme any `json:"padding_scheme,omitempty"`
|
PaddingScheme any `json:"padding_scheme,omitempty"`
|
||||||
Transport any `json:"transport,omitempty"`
|
Transport any `json:"transport,omitempty"`
|
||||||
TrafficPattern any `json:"traffic_pattern,omitempty"`
|
TrafficPattern any `json:"traffic_pattern,omitempty"`
|
||||||
Decryption any `json:"decryption,omitempty"`
|
Decryption any `json:"decryption,omitempty"`
|
||||||
Routes []model.ServerRoute `json:"routes,omitempty"`
|
Routes []model.ServerRoute `json:"routes,omitempty"`
|
||||||
CustomOutbounds any `json:"custom_outbounds,omitempty"`
|
CustomOutbounds any `json:"custom_outbounds,omitempty"`
|
||||||
CustomRoutes any `json:"custom_routes,omitempty"`
|
CustomRoutes any `json:"custom_routes,omitempty"`
|
||||||
CertConfig any `json:"cert_config,omitempty"`
|
CertConfig any `json:"cert_config,omitempty"`
|
||||||
BaseConfig NodeBaseConfig `json:"base_config"`
|
BaseConfig NodeBaseConfig `json:"base_config"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NormalizeServerType(serverType string) string {
|
func NormalizeServerType(serverType string) string {
|
||||||
@@ -165,6 +183,31 @@ func AvailableServersForUser(user *model.User) ([]model.Server, error) {
|
|||||||
return filtered, nil
|
return filtered, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GenerateServerPassword(node *model.Server, user *model.User) string {
|
||||||
|
if node == nil || user == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if NormalizeServerType(node.Type) != "shadowsocks" {
|
||||||
|
return user.UUID
|
||||||
|
}
|
||||||
|
|
||||||
|
settings := parseObject(node.ProtocolSettings)
|
||||||
|
cipher := getMapString(settings, "cipher")
|
||||||
|
config, ok := shadowsocks2022CipherConfigs[cipher]
|
||||||
|
if !ok {
|
||||||
|
return user.UUID
|
||||||
|
}
|
||||||
|
|
||||||
|
createdAt := resolveServerCreatedAt(node)
|
||||||
|
serverKey := serverKey(createdAt, config.serverKeySize)
|
||||||
|
if serverKey == "" {
|
||||||
|
return user.UUID
|
||||||
|
}
|
||||||
|
|
||||||
|
return serverKey + ":" + uuidPrefixBase64(user.UUID, config.userKeySize)
|
||||||
|
}
|
||||||
|
|
||||||
func CurrentRate(server *model.Server) float64 {
|
func CurrentRate(server *model.Server) float64 {
|
||||||
if !server.RateTimeEnable {
|
if !server.RateTimeEnable {
|
||||||
return float64(server.Rate)
|
return float64(server.Rate)
|
||||||
@@ -322,7 +365,10 @@ func serverKey(createdAt *time.Time, size int) string {
|
|||||||
if createdAt == nil {
|
if createdAt == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
sum := md5.Sum([]byte(strconv.FormatInt(createdAt.Unix(), 10)))
|
|
||||||
|
// Match XBoard's Helper::getServerKey(created_at, size):
|
||||||
|
// base64_encode(substr(md5($timestamp), 0, size))
|
||||||
|
sum := md5.Sum([]byte(createdAt.Format("2006-01-02 15:04:05")))
|
||||||
hex := fmt.Sprintf("%x", sum)
|
hex := fmt.Sprintf("%x", sum)
|
||||||
if size > len(hex) {
|
if size > len(hex) {
|
||||||
size = len(hex)
|
size = len(hex)
|
||||||
@@ -330,6 +376,38 @@ func serverKey(createdAt *time.Time, size int) string {
|
|||||||
return base64.StdEncoding.EncodeToString([]byte(hex[:size]))
|
return base64.StdEncoding.EncodeToString([]byte(hex[:size]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func resolveServerCreatedAt(node *model.Server) *time.Time {
|
||||||
|
if node == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if node.ParentID == nil || *node.ParentID <= 0 {
|
||||||
|
return node.CreatedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
var parent struct {
|
||||||
|
CreatedAt *time.Time `gorm:"column:created_at"`
|
||||||
|
}
|
||||||
|
if err := database.DB.Model(&model.Server{}).
|
||||||
|
Select("created_at").
|
||||||
|
Where("id = ?", *node.ParentID).
|
||||||
|
First(&parent).Error; err == nil && parent.CreatedAt != nil {
|
||||||
|
return parent.CreatedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
return node.CreatedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
func uuidPrefixBase64(uuid string, size int) string {
|
||||||
|
if size <= 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if size > len(uuid) {
|
||||||
|
size = len(uuid)
|
||||||
|
}
|
||||||
|
return base64.StdEncoding.EncodeToString([]byte(uuid[:size]))
|
||||||
|
}
|
||||||
|
|
||||||
func parseIntSlice(raw *string) []int {
|
func parseIntSlice(raw *string) []int {
|
||||||
if raw == nil || strings.TrimSpace(*raw) == "" {
|
if raw == nil || strings.TrimSpace(*raw) == "" {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
Reference in New Issue
Block a user