package panel import ( "encoding/base64" "fmt" "github.com/InazumaV/V2bX/common/crypt" "reflect" "strconv" "strings" "time" "github.com/goccy/go-json" ) // Security type const ( None = 0 Tls = 1 Reality = 2 ) type NodeInfo struct { Id int Type string Security int PushInterval time.Duration PullInterval time.Duration RawDNS RawDNS Rules Rules // origin VAllss *VAllssNode Shadowsocks *ShadowsocksNode Trojan *TrojanNode Hysteria *HysteriaNode Common *CommonNode } type CommonNode struct { Host string `json:"host"` ServerPort int `json:"server_port"` ServerName string `json:"server_name"` Routes []Route `json:"routes"` BaseConfig *BaseConfig `json:"base_config"` } type Route struct { Id int `json:"id"` Match interface{} `json:"match"` Action string `json:"action"` ActionValue string `json:"action_value"` } type BaseConfig struct { PushInterval any `json:"push_interval"` PullInterval any `json:"pull_interval"` } // VAllssNode is vmess and vless node info type VAllssNode struct { CommonNode Tls int `json:"tls"` TlsSettings TlsSettings `json:"tls_settings"` Network string `json:"network"` NetworkSettings json.RawMessage `json:"networkSettings"` ServerName string `json:"server_name"` // vless only Flow string `json:"flow"` RealityConfig RealityConfig } type TlsSettings struct { ServerName []string `json:"sever_name"` ServerPort string `json:"server_port"` ShortIds []string `json:"short_ids"` PrivateKey string `json:"-"` } type RealityConfig struct { Dest string `json:"Dest"` Xver uint64 `json:"Xver"` MinClientVer string `json:"MinClientVer"` MaxClientVer string `json:"MaxClientVer"` MaxTimeDiff uint64 `json:"MaxTimeDiff"` } type ShadowsocksNode struct { CommonNode Cipher string `json:"cipher"` ServerKey string `json:"server_key"` } type TrojanNode CommonNode type HysteriaNode struct { CommonNode UpMbps int `json:"up_mbps"` DownMbps int `json:"down_mbps"` Obfs string `json:"obfs"` } type RawDNS struct { DNSMap map[string]map[string]interface{} DNSJson []byte } type Rules struct { Regexp []string Protocol []string } func (c *Client) GetNodeInfo() (node *NodeInfo, err error) { const path = "/api/v1/server/UniProxy/config" r, err := c.client. R(). SetHeader("If-None-Match", c.nodeEtag). Get(path) if err = c.checkResponse(r, path, err); err != nil { return } if r.StatusCode() == 304 { return nil, nil } node = &NodeInfo{ Id: c.NodeId, Type: c.NodeType, RawDNS: RawDNS{ DNSMap: make(map[string]map[string]interface{}), DNSJson: []byte(""), }, } // parse protocol params var cm *CommonNode switch c.NodeType { case "vmess", "vless": rsp := &VAllssNode{} err = json.Unmarshal(r.Body(), rsp) if err != nil { return nil, fmt.Errorf("decode v2ray params error: %s", err) } cm = &rsp.CommonNode node.VAllss = rsp node.Security = node.VAllss.Tls if len(rsp.NetworkSettings) > 0 { /*err = json.Unmarshal(rsp.NetworkSettings, &rsp.RealityConfig) if err != nil { return nil, fmt.Errorf("decode reality config error: %s", err) }*/ if node.Security == Reality { key := crypt.GenX25519Private([]byte(strconv.Itoa(c.NodeId) + c.NodeType + c.Token)) rsp.TlsSettings.PrivateKey = base64.RawURLEncoding.EncodeToString(key) } } case "shadowsocks": rsp := &ShadowsocksNode{} err = json.Unmarshal(r.Body(), rsp) if err != nil { return nil, fmt.Errorf("decode v2ray params error: %s", err) } cm = &rsp.CommonNode node.Shadowsocks = rsp node.Security = None case "trojan": rsp := &TrojanNode{} err = json.Unmarshal(r.Body(), rsp) if err != nil { return nil, fmt.Errorf("decode v2ray params error: %s", err) } cm = (*CommonNode)(rsp) node.Trojan = rsp node.Security = Tls case "hysteria": rsp := &HysteriaNode{} err = json.Unmarshal(r.Body(), rsp) if err != nil { return nil, fmt.Errorf("decode v2ray params error: %s", err) } cm = &rsp.CommonNode node.Hysteria = rsp node.Security = Tls } // parse rules and dns for i := range cm.Routes { var matchs []string if _, ok := cm.Routes[i].Match.(string); ok { matchs = strings.Split(cm.Routes[i].Match.(string), ",") } else if _, ok = cm.Routes[i].Match.([]string); ok { matchs = cm.Routes[i].Match.([]string) } else { temp := cm.Routes[i].Match.([]interface{}) matchs = make([]string, len(temp)) for i := range temp { matchs[i] = temp[i].(string) } } switch cm.Routes[i].Action { case "block": for _, v := range matchs { if strings.HasPrefix(v, "protocol:") { // protocol node.Rules.Protocol = append(node.Rules.Protocol, strings.TrimPrefix(v, "protocol:")) } else { // domain node.Rules.Regexp = append(node.Rules.Regexp, strings.TrimPrefix(v, "regexp:")) } } case "dns": var domains []string for _, v := range matchs { domains = append(domains, v) } if matchs[0] != "main" { node.RawDNS.DNSMap[strconv.Itoa(i)] = map[string]interface{}{ "address": cm.Routes[i].ActionValue, "domains": domains, } } else { dns := []byte(strings.Join(matchs[1:], "")) node.RawDNS.DNSJson = dns break } } } // set interval node.PushInterval = intervalToTime(cm.BaseConfig.PushInterval) node.PullInterval = intervalToTime(cm.BaseConfig.PullInterval) node.Common = cm // clear cm.Routes = nil cm.BaseConfig = nil c.nodeEtag = r.Header().Get("ETag") return } func intervalToTime(i interface{}) time.Duration { switch reflect.TypeOf(i).Kind() { case reflect.Int: return time.Duration(i.(int)) * time.Second case reflect.String: i, _ := strconv.Atoi(i.(string)) return time.Duration(i) * time.Second case reflect.Float64: return time.Duration(i.(float64)) * time.Second default: return time.Duration(reflect.ValueOf(i).Int()) * time.Second } }