package service import ( "errors" "fmt" "gopkg.in/yaml.v3" "hy2xs-admin/dao" "hy2xs-admin/model/bo" "hy2xs-admin/model/constant" "hy2xs-admin/proxy" "net/url" "strings" "time" ) func Hysteria2Auth(conPass string) (int64, string, error) { if !Hysteria2IsRunning() { return 0, "", errors.New("hysteria2 is not running") } now := time.Now().UnixMilli() account, err := dao.GetAccount("con_pass = ? and deleted = 0 and (quota < 0 or quota > download + upload) and ? < expire_time and ? > kick_util_time", conPass, now, now) if err != nil { return 0, "", err } // 限制设备数 onlineUsers, err := Hysteria2Online() if err != nil { return 0, "", err } device, exist := onlineUsers[*account.Username] if exist && *account.DeviceNo <= device { return 0, "", errors.New("device limited") } return *account.Id, *account.Username, nil } func Hysteria2Online() (map[string]int64, error) { if !Hysteria2IsRunning() { return map[string]int64{}, nil } apiPort, err := GetHysteria2ApiPort() if err != nil { return nil, errors.New("get hysteria2 apiPort err") } jwtSecretConfig, err := dao.GetConfig("key = ?", constant.JwtSecret) if err != nil { return nil, err } onlineUsers, err := proxy.NewHysteria2Api(apiPort).OnlineUsers(*jwtSecretConfig.Value) if err != nil { return nil, err } return onlineUsers, nil } func Hysteria2Kick(ids []int64, kickUtilTime int64) error { if !Hysteria2IsRunning() { return errors.New("hysteria2 is not running") } if err := dao.UpdateAccount(ids, map[string]interface{}{"kick_util_time": kickUtilTime}); err != nil { return err } accounts, err := dao.ListAccount("id in ?", ids) if err != nil { return err } var keys []string for _, item := range accounts { keys = append(keys, *item.Username) } apiPort, err := GetHysteria2ApiPort() if err != nil { return errors.New("get hysteria2 apiPort err") } jwtSecretConfig, err := dao.GetConfig("key = ?", constant.JwtSecret) if err != nil { return err } if err = proxy.NewHysteria2Api(apiPort).KickUsers(keys, *jwtSecretConfig.Value); err != nil { return err } return nil } func Hysteria2SubscribeUrl(accountId int64, protocol string, host string) (string, error) { account, err := dao.GetAccount("id = ?", accountId) if err != nil { return "", err } config, err := dao.GetConfig("key = ?", constant.HUIWebContext) if err != nil { return "", err } webContext := "" if config.Value != nil && *config.Value != "/" && strings.HasPrefix(*config.Value, "/") { webContext = *config.Value } return fmt.Sprintf("%s//%s%s/hui/%s", protocol, host, webContext, url.QueryEscape(*account.ConPass)), nil } func Hysteria2Subscribe(conPass string, clientType string, host string) (string, string, error) { hysteria2Config, err := GetHysteria2Config() if err != nil { return "", "", err } if hysteria2Config.Listen == nil || *hysteria2Config.Listen == "" { return "", "", errors.New("hysteria2 config is empty") } account, err := dao.GetAccount("con_pass = ?", conPass) if err != nil { return "", "", err } hysteria2Name := "hysteria2" hysteria2ConfigRemark, err := dao.GetConfig("key = ?", constant.Hysteria2ConfigRemark) if err != nil { return "", "", err } if *hysteria2ConfigRemark.Value != "" { hysteria2Name = *hysteria2ConfigRemark.Value } hysteria2ConfigPortHopping, err := dao.GetConfig("key = ?", constant.Hysteria2ConfigPortHopping) if err != nil { return "", "", err } userInfo := "" configStr := "" if clientType == constant.Shadowrocket || clientType == constant.Clash { userInfo = fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", *account.Upload, *account.Download, *account.Quota, *account.ExpireTime/1000) hysteria2 := bo.Hysteria2{ Name: hysteria2Name, Type: "hysteria2", Server: strings.Split(host, ":")[0], Port: strings.Split(*hysteria2Config.Listen, ":")[1], Ports: *hysteria2ConfigPortHopping.Value, Password: conPass, } if hysteria2Config.Bandwidth != nil { if hysteria2Config.Bandwidth.Up != nil && *hysteria2Config.Bandwidth.Up != "" { hysteria2.Up = *hysteria2Config.Bandwidth.Up } if hysteria2Config.Bandwidth.Down != nil && *hysteria2Config.Bandwidth.Down != "" { hysteria2.Down = *hysteria2Config.Bandwidth.Down } } if hysteria2Config.Obfs != nil && hysteria2Config.Obfs.Type != nil && *hysteria2Config.Obfs.Type == "salamander" && hysteria2Config.Obfs.Salamander != nil && hysteria2Config.Obfs.Salamander.Password != nil && *hysteria2Config.Obfs.Salamander.Password != "" { if clientType == constant.Shadowrocket { hysteria2.Obfs = *hysteria2Config.Obfs.Salamander.Password } else { hysteria2.Obfs = "salamander" hysteria2.ObfsPassword = *hysteria2Config.Obfs.Salamander.Password } } if hysteria2Config.ACME != nil && hysteria2Config.ACME.Domains != nil && len(hysteria2Config.ACME.Domains) > 0 { hysteria2.Sni = hysteria2Config.ACME.Domains[0] } hysteria2.SkipCertVerify = false proxyGroup := bo.ProxyGroup{ Name: "PROXY", Type: "select", Proxies: []string{hysteria2Name}, } clashConfig := bo.ClashConfig{ ProxyGroups: []bo.ProxyGroup{ proxyGroup, }, Proxies: []interface{}{hysteria2}, } clashConfigYaml, err := yaml.Marshal(&clashConfig) if err != nil { return "", "", err } configStr = string(clashConfigYaml) if clientType == constant.Clash { clashExtension, err := GetConfig(constant.ClashExtension) if err != nil { return "", "", err } if clashExtension.Value != nil && *clashExtension.Value != "" { configStr = fmt.Sprintf("%s%s", configStr, *clashExtension.Value) } } } else if clientType == constant.V2rayN { hysteria2Url, err := Hysteria2Url(*account.Id, strings.Split(host, ":")[0]) if err != nil { return "", "", err } configStr = hysteria2Url } return userInfo, configStr, nil } func Hysteria2Url(accountId int64, hostname string) (string, error) { hysteria2Config, err := GetHysteria2Config() if err != nil { return "", err } if hysteria2Config.Listen == nil || *hysteria2Config.Listen == "" { return "", errors.New("hysteria2 config is empty") } account, err := dao.GetAccount("id = ?", accountId) if err != nil { return "", err } urlConfig := "" if hysteria2Config.Obfs != nil && hysteria2Config.Obfs.Type != nil && *hysteria2Config.Obfs.Type == "salamander" && hysteria2Config.Obfs.Salamander != nil && hysteria2Config.Obfs.Salamander.Password != nil && *hysteria2Config.Obfs.Salamander.Password != "" { urlConfig += fmt.Sprintf("&obfs=salamander&obfs-password=%s", *hysteria2Config.Obfs.Salamander.Password) } if hysteria2Config.ACME != nil && hysteria2Config.ACME.Domains != nil && len(hysteria2Config.ACME.Domains) > 0 { urlConfig += fmt.Sprintf("&sni=%s", hysteria2Config.ACME.Domains[0]) // shadowrocket urlConfig += fmt.Sprintf("&peer=%s", hysteria2Config.ACME.Domains[0]) } urlConfig += "&insecure=0" if hysteria2Config.Bandwidth != nil && hysteria2Config.Bandwidth.Down != nil && *hysteria2Config.Bandwidth.Down != "" { // shadowrocket urlConfig += fmt.Sprintf("&downmbps=%s", url.PathEscape(*hysteria2Config.Bandwidth.Down)) } hysteria2ConfigPortHopping, err := dao.GetConfig("key = ?", constant.Hysteria2ConfigPortHopping) if err != nil { return "", err } if *hysteria2ConfigPortHopping.Value != "" { // shadowrocket urlConfig += fmt.Sprintf("&mport=%s", *hysteria2ConfigPortHopping.Value) } hysteria2ConfigRemark, err := dao.GetConfig("key = ?", constant.Hysteria2ConfigRemark) if err != nil { return "", err } if *hysteria2ConfigRemark.Value != "" { urlConfig += fmt.Sprintf("#%s", *hysteria2ConfigRemark.Value) } if urlConfig != "" { urlConfig = "/?" + strings.TrimPrefix(urlConfig, "&") } return fmt.Sprintf("hysteria2://%s@%s%s", *account.ConPass, hostname, *hysteria2Config.Listen) + urlConfig, nil }