对接GoV2Panel面板 (#474)

* Update actions

* up

---------

Co-authored-by: thank243 <thank243@gmail.com>
This commit is contained in:
pings 2023-10-08 12:56:26 +08:00 committed by GitHub
parent b8d40c201b
commit 4c52e33adb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 564 additions and 2 deletions

View File

@ -62,6 +62,7 @@ A Xray backend framework that can easily support many panels.
| [PMPanel](https://github.com/ByteInternetHK/PMPanel) | √ | √ | √ | | [PMPanel](https://github.com/ByteInternetHK/PMPanel) | √ | √ | √ |
| [ProxyPanel](https://github.com/ProxyPanel/ProxyPanel) | √ | √ | √ | | [ProxyPanel](https://github.com/ProxyPanel/ProxyPanel) | √ | √ | √ |
| [WHMCS (V2RaySocks)](https://v2raysocks.doxtex.com/) | √ | √ | √ | | [WHMCS (V2RaySocks)](https://v2raysocks.doxtex.com/) | √ | √ | √ |
| [GoV2Panel](https://github.com/pingProMax/gov2panel) | √ | √ | √ |
## 软件安装 ## 软件安装

402
api/gov2panel/gov2panel.go Normal file
View File

@ -0,0 +1,402 @@
package gov2panel
import (
"bufio"
"encoding/json"
"errors"
"fmt"
"log"
"os"
"regexp"
"strconv"
"strings"
"sync/atomic"
"time"
"github.com/bitly/go-simplejson"
"github.com/go-resty/resty/v2"
"github.com/gogf/gf/v2/util/gconv"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/infra/conf"
"github.com/XrayR-project/XrayR/api"
)
// APIClient create an api client to the panel.
type APIClient struct {
client *resty.Client
APIHost string
NodeID int
Key string
NodeType string
EnableVless bool
VlessFlow string
SpeedLimit float64
DeviceLimit int
LocalRuleList []api.DetectRule
resp atomic.Value
eTags map[string]string
}
// New create an api instance
func New(apiConfig *api.Config) *APIClient {
client := resty.New()
client.SetRetryCount(3)
if apiConfig.Timeout > 0 {
client.SetTimeout(time.Duration(apiConfig.Timeout) * time.Second)
} else {
client.SetTimeout(5 * time.Second)
}
client.OnError(func(req *resty.Request, err error) {
if v, ok := err.(*resty.ResponseError); ok {
// v.Response contains the last response from the server
// v.Err contains the original error
log.Print(v.Err)
}
})
client.SetBaseURL(apiConfig.APIHost)
// Create Key for each requests
client.SetQueryParams(map[string]string{
"node_id": strconv.Itoa(apiConfig.NodeID),
"node_type": strings.ToLower(apiConfig.NodeType),
"token": apiConfig.Key,
})
// Read local rule list
localRuleList := readLocalRuleList(apiConfig.RuleListPath)
apiClient := &APIClient{
client: client,
NodeID: apiConfig.NodeID,
Key: apiConfig.Key,
APIHost: apiConfig.APIHost,
NodeType: apiConfig.NodeType,
EnableVless: apiConfig.EnableVless,
VlessFlow: apiConfig.VlessFlow,
SpeedLimit: apiConfig.SpeedLimit,
DeviceLimit: apiConfig.DeviceLimit,
LocalRuleList: localRuleList,
eTags: make(map[string]string),
}
return apiClient
}
// readLocalRuleList reads the local rule list file
func readLocalRuleList(path string) (LocalRuleList []api.DetectRule) {
LocalRuleList = make([]api.DetectRule, 0)
if path != "" {
// open the file
file, err := os.Open(path)
defer file.Close()
// handle errors while opening
if err != nil {
log.Printf("Error when opening file: %s", err)
return LocalRuleList
}
fileScanner := bufio.NewScanner(file)
// read line by line
for fileScanner.Scan() {
LocalRuleList = append(LocalRuleList, api.DetectRule{
ID: -1,
Pattern: regexp.MustCompile(fileScanner.Text()),
})
}
// handle first encountered error while reading
if err := fileScanner.Err(); err != nil {
log.Fatalf("Error while reading file: %s", err)
return
}
}
return LocalRuleList
}
// Describe return a description of the client
func (c *APIClient) Describe() api.ClientInfo {
return api.ClientInfo{APIHost: c.APIHost, NodeID: c.NodeID, Key: c.Key, NodeType: c.NodeType}
}
// Debug set the client debug for client
func (c *APIClient) Debug() {
c.client.SetDebug(true)
}
func (c *APIClient) assembleURL(path string) string {
return c.APIHost + path
}
func (c *APIClient) parseResponse(res *resty.Response, path string, err error) (*simplejson.Json, error) {
if err != nil {
return nil, fmt.Errorf("request %s failed: %v", c.assembleURL(path), err)
}
if res.StatusCode() > 399 {
return nil, fmt.Errorf("request %s failed: %s, %v", c.assembleURL(path), res.String(), err)
}
rtn, err := simplejson.NewJson(res.Body())
if err != nil {
return nil, fmt.Errorf("ret %s invalid", res.String())
}
return rtn, nil
}
// GetNodeInfo will pull NodeInfo Config from panel
func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
server := new(serverConfig)
path := "/api/server/config"
res, err := c.client.R().
SetHeader("If-None-Match", c.eTags["node"]).
ForceContentType("application/json").
Get(path)
// Etag identifier for a specific version of a resource. StatusCode = 304 means no changed
if res.StatusCode() == 304 {
return nil, errors.New(api.NodeNotModified)
}
// update etag
if res.Header().Get("Etag") != "" && res.Header().Get("Etag") != c.eTags["node"] {
c.eTags["node"] = res.Header().Get("Etag")
}
nodeInfoResp, err := c.parseResponse(res, path, err)
if err != nil {
return nil, err
}
b, _ := nodeInfoResp.Encode()
json.Unmarshal(b, server)
if gconv.Uint32(server.Port) == 0 {
return nil, errors.New("server port must > 0")
}
c.resp.Store(server)
switch c.NodeType {
case "V2ray":
nodeInfo, err = c.parseV2rayNodeResponse(server)
case "Trojan":
nodeInfo, err = c.parseTrojanNodeResponse(server)
case "Shadowsocks":
nodeInfo, err = c.parseSSNodeResponse(server)
default:
return nil, fmt.Errorf("unsupported node type: %s", c.NodeType)
}
if err != nil {
return nil, fmt.Errorf("parse node info failed: %s, \nError: %v", res.String(), err)
}
return nodeInfo, nil
}
// GetUserList will pull user form panel
func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
var users []*user
path := "/api/server/user"
switch c.NodeType {
case "V2ray", "Trojan", "Shadowsocks":
break
default:
return nil, fmt.Errorf("unsupported node type: %s", c.NodeType)
}
res, err := c.client.R().
SetHeader("If-None-Match", c.eTags["users"]).
ForceContentType("application/json").
Get(path)
// Etag identifier for a specific version of a resource. StatusCode = 304 means no changed
if res.StatusCode() == 304 {
return nil, errors.New(api.UserNotModified)
}
// update etag
if res.Header().Get("Etag") != "" && res.Header().Get("Etag") != c.eTags["users"] {
c.eTags["users"] = res.Header().Get("Etag")
}
usersResp, err := c.parseResponse(res, path, err)
if err != nil {
return nil, err
}
b, _ := usersResp.Get("users").Encode()
json.Unmarshal(b, &users)
if len(users) == 0 {
return nil, errors.New("users is null")
}
userList := make([]api.UserInfo, len(users))
for i := 0; i < len(users); i++ {
u := api.UserInfo{
UID: users[i].Id,
UUID: users[i].Uuid,
}
// Support 1.7.1 speed limit
if c.SpeedLimit > 0 {
u.SpeedLimit = uint64(c.SpeedLimit * 1000000 / 8)
} else {
u.SpeedLimit = uint64(users[i].SpeedLimit * 1000000 / 8)
}
u.DeviceLimit = c.DeviceLimit // todo waiting v2board send configuration
u.Email = u.UUID + "@gov2panel.user"
if c.NodeType == "Shadowsocks" {
u.Passwd = u.UUID
}
userList[i] = u
}
return &userList, nil
}
// ReportUserTraffic reports the user traffic
func (c *APIClient) ReportUserTraffic(userTraffic *[]api.UserTraffic) error {
path := "/api/server/push"
res, err := c.client.R().SetBody(userTraffic).ForceContentType("application/json").Post(path)
_, err = c.parseResponse(res, path, err)
if err != nil {
return err
}
return nil
}
// GetNodeRule implements the API interface
func (c *APIClient) GetNodeRule() (*[]api.DetectRule, error) {
routes := c.resp.Load().(*serverConfig).Routes
ruleList := c.LocalRuleList
for i := range routes {
if routes[i].Action == "block" {
ruleList = append(ruleList, api.DetectRule{
ID: i,
Pattern: regexp.MustCompile(strings.Join(routes[i].Match, "|")),
})
}
}
return &ruleList, nil
}
// ReportNodeStatus implements the API interface
func (c *APIClient) ReportNodeStatus(nodeStatus *api.NodeStatus) (err error) {
return nil
}
// ReportNodeOnlineUsers implements the API interface
func (c *APIClient) ReportNodeOnlineUsers(onlineUserList *[]api.OnlineUser) error {
return nil
}
// ReportIllegal implements the API interface
func (c *APIClient) ReportIllegal(detectResultList *[]api.DetectResult) error {
return nil
}
// parseTrojanNodeResponse parse the response for the given nodeInfo format
func (c *APIClient) parseTrojanNodeResponse(s *serverConfig) (*api.NodeInfo, error) {
// Create GeneralNodeInfo
nodeInfo := &api.NodeInfo{
NodeType: c.NodeType,
NodeID: c.NodeID,
Port: gconv.Uint32(s.Port),
TransportProtocol: "tcp",
EnableTLS: true,
Host: s.Host,
ServiceName: s.Sni,
NameServerConfig: s.parseDNSConfig(),
}
return nodeInfo, nil
}
// parseSSNodeResponse parse the response for the given nodeInfo format
func (c *APIClient) parseSSNodeResponse(s *serverConfig) (*api.NodeInfo, error) {
var header json.RawMessage
if s.Obfs == "http" {
path := "/"
if p := s.ObfsSettings.Path; p != "" {
if strings.HasPrefix(p, "/") {
path = p
} else {
path += p
}
}
h := simplejson.New()
h.Set("type", "http")
h.SetPath([]string{"request", "path"}, path)
header, _ = h.Encode()
}
// Create GeneralNodeInfo
return &api.NodeInfo{
NodeType: c.NodeType,
NodeID: c.NodeID,
Port: gconv.Uint32(s.Port),
TransportProtocol: "tcp",
CypherMethod: s.Encryption,
ServerKey: s.ServerKey, // shadowsocks2022 share key
NameServerConfig: s.parseDNSConfig(),
Header: header,
}, nil
}
// parseV2rayNodeResponse parse the response for the given nodeInfo format
func (c *APIClient) parseV2rayNodeResponse(s *serverConfig) (*api.NodeInfo, error) {
var (
header json.RawMessage
enableTLS bool
)
switch s.Net {
case "tcp":
if s.Header != nil {
if httpHeader, err := s.Header.MarshalJSON(); err != nil {
return nil, err
} else {
header = httpHeader
}
}
}
if s.TLS == "tls" {
enableTLS = true
}
// Create GeneralNodeInfo
return &api.NodeInfo{
NodeType: c.NodeType,
NodeID: c.NodeID,
Port: gconv.Uint32(s.Port),
AlterID: 0,
TransportProtocol: s.Net,
EnableTLS: enableTLS,
Path: s.Path,
Host: s.Host,
EnableVless: c.EnableVless,
VlessFlow: c.VlessFlow,
ServiceName: s.Sni,
Header: header,
NameServerConfig: s.parseDNSConfig(),
}, nil
}
func (s *serverConfig) parseDNSConfig() (nameServerList []*conf.NameServerConfig) {
for i := range s.Routes {
if s.Routes[i].Action == "dns" {
nameServerList = append(nameServerList, &conf.NameServerConfig{
Address: &conf.Address{Address: net.ParseAddress(s.Routes[i].ActionValue)},
Domains: s.Routes[i].Match,
})
}
}
return
}

View File

@ -0,0 +1,101 @@
package gov2panel_test
import (
"testing"
"github.com/XrayR-project/XrayR/api"
"github.com/XrayR-project/XrayR/api/gov2panel"
)
func CreateClient() api.API {
apiConfig := &api.Config{
APIHost: "http://localhost:8080",
Key: "123456",
NodeID: 1,
NodeType: "V2ray",
}
client := gov2panel.New(apiConfig)
return client
}
func TestGetV2rayNodeInfo(t *testing.T) {
client := CreateClient()
nodeInfo, err := client.GetNodeInfo()
if err != nil {
t.Error(err)
}
t.Log(nodeInfo)
}
func TestGetSSNodeInfo(t *testing.T) {
apiConfig := &api.Config{
APIHost: "http://127.0.0.1:668",
Key: "qwertyuiopasdfghjkl",
NodeID: 1,
NodeType: "Shadowsocks",
}
client := gov2panel.New(apiConfig)
nodeInfo, err := client.GetNodeInfo()
if err != nil {
t.Error(err)
}
t.Log(nodeInfo)
}
func TestGetTrojanNodeInfo(t *testing.T) {
apiConfig := &api.Config{
APIHost: "http://127.0.0.1:668",
Key: "qwertyuiopasdfghjkl",
NodeID: 1,
NodeType: "Trojan",
}
client := gov2panel.New(apiConfig)
nodeInfo, err := client.GetNodeInfo()
if err != nil {
t.Error(err)
}
t.Log(nodeInfo)
}
func TestGetUserList(t *testing.T) {
client := CreateClient()
userList, err := client.GetUserList()
if err != nil {
t.Error(err)
}
t.Log(userList)
}
func TestReportReportUserTraffic(t *testing.T) {
client := CreateClient()
userList, err := client.GetUserList()
if err != nil {
t.Error(err)
}
generalUserTraffic := make([]api.UserTraffic, len(*userList))
for i, userInfo := range *userList {
generalUserTraffic[i] = api.UserTraffic{
UID: userInfo.UID,
Upload: 1111,
Download: 2222,
}
}
// client.Debug()
err = client.ReportUserTraffic(&generalUserTraffic)
if err != nil {
t.Error(err)
}
}
func TestGetNodeRule(t *testing.T) {
client := CreateClient()
client.Debug()
ruleList, err := client.GetNodeRule()
if err != nil {
t.Error(err)
}
t.Log(ruleList)
}

46
api/gov2panel/model.go Normal file
View File

@ -0,0 +1,46 @@
package gov2panel
import "encoding/json"
type serverConfig struct {
v2ray
shadowsocks
//---
Routes []route `json:"routes"`
Header *json.RawMessage `json:"header"`
}
type v2ray struct {
Port string `json:"port"`
Scy string `json:"scy"`
Net string `json:"net"`
Type string `json:"type"`
Host string `json:"host"`
Path string `json:"path"`
TLS string `json:"tls"`
Sni string `json:"sni"`
Alpn string `json:"alpn"`
}
type shadowsocks struct {
Encryption string `json:"encryption"`
Obfs string `json:"obfs"`
ObfsSettings struct {
Path string `json:"path"`
Host string `json:"host"`
} `json:"obfs_settings"`
ServerKey string `json:"server_key"`
}
type route struct {
Id int `json:"id"`
Match []string `json:"match"`
Action string `json:"action"`
ActionValue string `json:"action_value"`
}
type user struct {
Id int `json:"id"`
Uuid string `json:"uuid"`
SpeedLimit int `json:"speed_limit"`
}

3
go.mod
View File

@ -14,6 +14,7 @@ require (
github.com/fsnotify/fsnotify v1.6.0 github.com/fsnotify/fsnotify v1.6.0
github.com/go-acme/lego/v4 v4.14.2 github.com/go-acme/lego/v4 v4.14.2
github.com/go-resty/resty/v2 v2.9.1 github.com/go-resty/resty/v2 v2.9.1
github.com/gogf/gf/v2 v2.5.4
github.com/patrickmn/go-cache v2.1.0+incompatible github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/r3labs/diff/v2 v2.15.1 github.com/r3labs/diff/v2 v2.15.1
github.com/redis/go-redis/v9 v9.2.1 github.com/redis/go-redis/v9 v9.2.1
@ -202,6 +203,8 @@ require (
github.com/yandex-cloud/go-sdk v0.0.0-20220805164847-cf028e604997 // indirect github.com/yandex-cloud/go-sdk v0.0.0-20220805164847-cf028e604997 // indirect
github.com/yusufpapurcu/wmi v1.2.3 // indirect github.com/yusufpapurcu/wmi v1.2.3 // indirect
go.opencensus.io v0.24.0 // indirect go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/otel v1.14.0 // indirect
go.opentelemetry.io/otel/trace v1.14.0 // indirect
go.uber.org/atomic v1.11.0 // indirect go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect
go.uber.org/ratelimit v0.2.0 // indirect go.uber.org/ratelimit v0.2.0 // indirect

6
go.sum
View File

@ -298,6 +298,8 @@ github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b/go.mod h1:Xo4aNUOrJnVr
github.com/goccy/go-json v0.7.8/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.7.8/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogf/gf/v2 v2.5.4 h1:UBCSw8mInkHmEqL0E1LYc6QhSpaNFY/wHcFrTI/rzTk=
github.com/gogf/gf/v2 v2.5.4/go.mod h1:7yf5qp0BznfsYx7Sw49m3mQvBsHpwAjJk3Q9ZnKoUEc=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
@ -911,6 +913,10 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/otel v1.14.0 h1:/79Huy8wbf5DnIPhemGB+zEPVwnN6fuQybr/SRXa6hM=
go.opentelemetry.io/otel v1.14.0/go.mod h1:o4buv+dJzx8rohcUeRmWUZhqupFvzWis188WlggnNeU=
go.opentelemetry.io/otel/trace v1.14.0 h1:wp2Mmvj41tDsyAJXiWDWpfNsOiIyd38fy85pyKcFq/M=
go.opentelemetry.io/otel/trace v1.14.0/go.mod h1:8avnQLK+CG77yNLUae4ea2JDQ6iT+gozhnZjy/rw9G8=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=

View File

@ -13,7 +13,7 @@ ConnectionConfig:
DownlinkOnly: 4 # Time limit when the connection is closed after the uplink is closed, Second DownlinkOnly: 4 # Time limit when the connection is closed after the uplink is closed, Second
BufferSize: 64 # The internal cache size of each connection, kB BufferSize: 64 # The internal cache size of each connection, kB
Nodes: Nodes:
- PanelType: "SSpanel" # Panel type: SSpanel, NewV2board, PMpanel, Proxypanel, V2RaySocks - PanelType: "SSpanel" # Panel type: SSpanel, NewV2board, PMpanel, Proxypanel, V2RaySocks, GoV2Panel
ApiConfig: ApiConfig:
ApiHost: "http://127.0.0.1:667" ApiHost: "http://127.0.0.1:667"
ApiKey: "123" ApiKey: "123"
@ -78,7 +78,7 @@ Nodes:
ALICLOUD_ACCESS_KEY: aaa ALICLOUD_ACCESS_KEY: aaa
ALICLOUD_SECRET_KEY: bbb ALICLOUD_SECRET_KEY: bbb
# - PanelType: "SSpanel" # Panel type: SSpanel, V2board, NewV2board, PMpanel, Proxypanel, V2RaySocks # - PanelType: "SSpanel" # Panel type: SSpanel, V2board, NewV2board, PMpanel, Proxypanel, V2RaySocks, GoV2Panel
# ApiConfig: # ApiConfig:
# ApiHost: "http://127.0.0.1:668" # ApiHost: "http://127.0.0.1:668"
# ApiKey: "123" # ApiKey: "123"

View File

@ -6,6 +6,7 @@ import (
"os" "os"
"sync" "sync"
"github.com/XrayR-project/XrayR/api/gov2panel"
"github.com/XrayR-project/XrayR/api/newV2board" "github.com/XrayR-project/XrayR/api/newV2board"
"github.com/XrayR-project/XrayR/app/mydispatcher" "github.com/XrayR-project/XrayR/app/mydispatcher"
@ -184,6 +185,8 @@ func (p *Panel) Start() {
apiClient = proxypanel.New(nodeConfig.ApiConfig) apiClient = proxypanel.New(nodeConfig.ApiConfig)
case "V2RaySocks": case "V2RaySocks":
apiClient = v2raysocks.New(nodeConfig.ApiConfig) apiClient = v2raysocks.New(nodeConfig.ApiConfig)
case "GoV2Panel":
apiClient = gov2panel.New(nodeConfig.ApiConfig)
default: default:
log.Panicf("Unsupport panel type: %s", nodeConfig.PanelType) log.Panicf("Unsupport panel type: %s", nodeConfig.PanelType)
} }