Optimize and upgrade docking parameters (#696)

This commit is contained in:
pings 2024-10-19 22:00:11 +08:00 committed by GitHub
parent 34726d1659
commit 63c1a18a7d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 237 additions and 355 deletions

View File

@ -2,92 +2,71 @@ package gov2panel
import ( import (
"bufio" "bufio"
"encoding/json" "context"
"errors" "errors"
"fmt" "fmt"
"log"
"os" "os"
"regexp" "regexp"
"strconv"
"strings"
"sync/atomic"
"time" "time"
log "github.com/sirupsen/logrus" "github.com/XrayR-project/XrayR/api"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/bitly/go-simplejson" "github.com/gogf/gf/v2/frame/g"
"github.com/go-resty/resty/v2" "github.com/gogf/gf/v2/net/gclient"
"github.com/gogf/gf/v2/util/gconv"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/infra/conf" "github.com/xtls/xray-core/infra/conf"
"github.com/XrayR-project/XrayR/api"
) )
// APIClient create an api client to the panel. // APIClient API config
type APIClient struct { type APIClient struct {
client *resty.Client ctx context.Context
APIHost string APIHost string
NodeID int NodeID int
Key string Key string
NodeType string NodeType string
EnableVless bool EnableVless bool
VlessFlow string VlessFlow string
Timeout int
SpeedLimit float64 SpeedLimit float64
DeviceLimit int DeviceLimit int
RuleListPath string
DisableCustomConfig bool
LocalRuleList []api.DetectRule LocalRuleList []api.DetectRule
resp atomic.Value
eTags map[string]string
} }
// New create an api instance // New create an api instance
func New(apiConfig *api.Config) *APIClient { func New(apiConfig *api.Config) *APIClient {
client := resty.New()
client.SetRetryCount(3) //https://goframe.org/pages/viewpage.action?pageId=1114381
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{ apiClient := &APIClient{
client: client, ctx: context.Background(),
APIHost: apiConfig.APIHost,
NodeID: apiConfig.NodeID, NodeID: apiConfig.NodeID,
Key: apiConfig.Key, Key: apiConfig.Key,
APIHost: apiConfig.APIHost,
NodeType: apiConfig.NodeType, NodeType: apiConfig.NodeType,
EnableVless: apiConfig.EnableVless, EnableVless: apiConfig.EnableVless,
VlessFlow: apiConfig.VlessFlow, VlessFlow: apiConfig.VlessFlow,
SpeedLimit: apiConfig.SpeedLimit, Timeout: apiConfig.Timeout,
DeviceLimit: apiConfig.DeviceLimit, DeviceLimit: apiConfig.DeviceLimit,
LocalRuleList: localRuleList, RuleListPath: apiConfig.RuleListPath,
eTags: make(map[string]string), DisableCustomConfig: apiConfig.DisableCustomConfig,
LocalRuleList: readLocalRuleList(apiConfig.RuleListPath), //加载本地路由规则
} }
return apiClient return apiClient
} }
// readLocalRuleList reads the local rule list file // readLocalRuleList reads the local rule list file
func readLocalRuleList(path string) (LocalRuleList []api.DetectRule) { func readLocalRuleList(path string) (LocalRuleList []api.DetectRule) {
LocalRuleList = make([]api.DetectRule, 0)
LocalRuleList = make([]api.DetectRule, 0)
if path != "" { if path != "" {
// open the file // open the file
file, err := os.Open(path) file, err := os.Open(path)
defer file.Close()
// handle errors while opening // handle errors while opening
if err != nil { if err != nil {
log.Printf("Error when opening file: %s", err) log.Printf("Error when opening file: %s", err)
@ -108,96 +87,77 @@ func readLocalRuleList(path string) (LocalRuleList []api.DetectRule) {
log.Fatalf("Error while reading file: %s", err) log.Fatalf("Error while reading file: %s", err)
return return
} }
file.Close()
} }
return LocalRuleList 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) { func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
server := new(serverConfig)
path := "/api/server/config"
res, err := c.client.R(). apiPath := "/api/server/config"
SetHeader("If-None-Match", c.eTags["node"]). reslutJson, err := c.sendRequest(
ForceContentType("application/json"). nil,
Get(path) "POST",
apiPath,
// Etag identifier for a specific version of a resource. StatusCode = 304 means no changed g.Map{})
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 { if err != nil {
return nil, err return nil, err
} }
b, _ := nodeInfoResp.Encode()
json.Unmarshal(b, server)
if gconv.Uint32(server.Port) == 0 { if reslutJson.Get("data").String() == "" {
return nil, errors.New("gov2panel node config data is null")
}
if reslutJson.Get("data.port").Int() == 0 {
return nil, errors.New("server port must > 0") return nil, errors.New("server port must > 0")
} }
c.resp.Store(server) nodeInfo = new(api.NodeInfo)
err = reslutJson.Get("data").Scan(nodeInfo)
switch c.NodeType {
case "V2ray", "Vmess", "Vless":
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 { if err != nil {
return nil, fmt.Errorf("parse node info failed: %s, \nError: %v", res.String(), err) return nil, fmt.Errorf("parse node info failed: \nError: %v", err)
} }
routes := make([]route, 0)
err = reslutJson.Get("data.routes").Scan(&routes)
if err != nil {
return nil, fmt.Errorf("parse node routes failed: \nError: %v", err)
}
nodeInfo.NodeType = c.NodeType
nodeInfo.NodeID = c.NodeID
nodeInfo.EnableVless = c.EnableVless
nodeInfo.VlessFlow = c.VlessFlow
nodeInfo.AlterID = 0
nodeInfo.NameServerConfig = parseDNSConfig(routes)
return nodeInfo, nil return nodeInfo, nil
}
func parseDNSConfig(routes []route) (nameServerList []*conf.NameServerConfig) {
nameServerList = make([]*conf.NameServerConfig, 0)
for i := range routes {
if routes[i].Action == "dns" {
nameServerList = append(nameServerList, &conf.NameServerConfig{
Address: &conf.Address{Address: net.ParseAddress(routes[i].ActionValue)},
Domains: routes[i].Match,
})
}
}
return
} }
// GetUserList will pull user form panel // GetUserList will pull user form panel
func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) { func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
var users []*user
path := "/api/server/user" apiPath := "/api/server/user"
switch c.NodeType { switch c.NodeType {
case "V2ray", "Trojan", "Shadowsocks", "Vmess", "Vless": case "V2ray", "Trojan", "Shadowsocks", "Vmess", "Vless":
@ -206,29 +166,17 @@ func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
return nil, fmt.Errorf("unsupported node type: %s", c.NodeType) return nil, fmt.Errorf("unsupported node type: %s", c.NodeType)
} }
res, err := c.client.R(). reslutJson, err := c.sendRequest(
SetHeader("If-None-Match", c.eTags["users"]). nil,
ForceContentType("application/json"). "GET",
Get(path) apiPath,
g.Map{})
// 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 { if err != nil {
return nil, err return nil, err
} }
b, _ := usersResp.Get("users").Encode()
json.Unmarshal(b, &users) var users []*user
if len(users) == 0 { reslutJson.Get("data.users").Scan(&users)
return nil, errors.New("users is null")
}
userList := make([]api.UserInfo, len(users)) userList := make([]api.UserInfo, len(users))
for i := 0; i < len(users); i++ { for i := 0; i < len(users); i++ {
@ -255,148 +203,126 @@ func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
return &userList, nil return &userList, nil
} }
// ReportUserTraffic reports the user traffic func (c *APIClient) ReportNodeStatus(nodeStatus *api.NodeStatus) (err error) {
func (c *APIClient) ReportUserTraffic(userTraffic *[]api.UserTraffic) error { return
path := "/api/server/push" }
res, err := c.client.R().SetBody(userTraffic).ForceContentType("application/json").Post(path) func (c *APIClient) ReportNodeOnlineUsers(onlineUser *[]api.OnlineUser) (err error) {
_, err = c.parseResponse(res, path, err) return
}
// ReportUserTraffic reports the user traffic
func (c *APIClient) ReportUserTraffic(userTraffic *[]api.UserTraffic) (err error) {
apiPath := "/api/server/push"
reslutJson, err := c.sendRequest(
nil,
"POST",
apiPath,
g.Map{
"data": userTraffic,
})
if err != nil { if err != nil {
return err return err
} }
return nil if reslutJson.Get("code").Int() != 0 {
} return errors.New(reslutJson.Get("message").String())
}
// GetNodeRule implements the API interface
func (c *APIClient) GetNodeRule() (*[]api.DetectRule, error) { return
routes := c.resp.Load().(*serverConfig).Routes }
ruleList := c.LocalRuleList func (c *APIClient) Describe() api.ClientInfo {
return api.ClientInfo{APIHost: c.APIHost, NodeID: c.NodeID, Key: c.Key, NodeType: c.NodeType}
for i := range routes { }
if routes[i].Action == "block" {
// GetNodeRule implements the API interface
ruleList = append(ruleList, api.DetectRule{ func (c *APIClient) GetNodeRule() (*[]api.DetectRule, error) {
ID: i, ruleList := c.LocalRuleList
Pattern: regexp.MustCompile(strings.Join(routes[i].Match, "|")),
}) apiPath := "/api/server/config"
} reslutJson, err := c.sendRequest(
} nil,
"POST",
return &ruleList, nil apiPath,
} g.Map{})
if err != nil {
// ReportNodeStatus implements the API interface return nil, err
func (c *APIClient) ReportNodeStatus(nodeStatus *api.NodeStatus) (err error) { }
return nil
} routes := make([]route, 0)
err = reslutJson.Get("data.routes").Scan(&routes)
// ReportNodeOnlineUsers implements the API interface if err != nil {
func (c *APIClient) ReportNodeOnlineUsers(onlineUserList *[]api.OnlineUser) error { return nil, fmt.Errorf("parse node routes failed: \nError: %v", err)
return nil }
}
for i := range routes {
// ReportIllegal implements the API interface if routes[i].Action == "block" {
func (c *APIClient) ReportIllegal(detectResultList *[]api.DetectResult) error { for _, v := range routes[i].Match {
return nil ruleList = append(ruleList, api.DetectRule{
} ID: i,
Pattern: regexp.MustCompile(v),
// 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), return &ruleList, nil
TransportProtocol: "tcp",
EnableTLS: true, }
Host: s.Host,
ServiceName: s.Sni, func (c *APIClient) ReportIllegal(detectResultList *[]api.DetectResult) (err error) {
NameServerConfig: s.parseDNSConfig(), return
} }
return nodeInfo, nil
} func (c *APIClient) Debug() {
// parseSSNodeResponse parse the response for the given nodeInfo format }
func (c *APIClient) parseSSNodeResponse(s *serverConfig) (*api.NodeInfo, error) {
var header json.RawMessage // request 统一请求接口
func (c *APIClient) sendRequest(headerM map[string]string, method string, url string, data g.Map) (reslutJson *gjson.Json, err error) {
if s.Obfs == "http" { url = c.APIHost + url
path := "/"
if p := s.ObfsSettings.Path; p != "" { client := gclient.New()
if strings.HasPrefix(p, "/") {
path = p var gResponse *gclient.Response
} else {
path += p if c.Timeout > 0 {
} client.SetTimeout(time.Duration(c.Timeout) * time.Second) //方法用于设置当前请求超时时间
} } else {
h := simplejson.New() client.SetTimeout(5 * time.Second)
h.Set("type", "http") }
h.SetPath([]string{"request", "path"}, path) client.Retry(3, 10*time.Second) //方法用于设置请求失败时重连次数和重连间隔。
header, _ = h.Encode()
} client.SetHeaderMap(headerM)
// Create GeneralNodeInfo client.SetHeader("Content-Type", "application/json")
return &api.NodeInfo{
NodeType: c.NodeType, data["token"] = c.Key
NodeID: c.NodeID, data["node_id"] = c.NodeID
Port: gconv.Uint32(s.Port),
TransportProtocol: "tcp", switch method {
CypherMethod: s.Encryption, case "GET":
ServerKey: s.ServerKey, // shadowsocks2022 share key gResponse, err = client.Get(c.ctx, url, data)
NameServerConfig: s.parseDNSConfig(), case "POST":
Header: header, gResponse, err = client.Post(c.ctx, url, data)
}, nil default:
} err = fmt.Errorf("unsupported method: %s", method)
return
// parseV2rayNodeResponse parse the response for the given nodeInfo format }
func (c *APIClient) parseV2rayNodeResponse(s *serverConfig) (*api.NodeInfo, error) {
var ( if err != nil {
header json.RawMessage return
enableTLS bool }
) defer gResponse.Close()
switch s.Net { reslutJson = gjson.New(gResponse.ReadAllString())
case "tcp": if reslutJson == nil {
if s.Header != nil { err = fmt.Errorf("http reslut to json, err : %s", gResponse.ReadAllString())
if httpHeader, err := s.Header.MarshalJSON(); err != nil { }
return nil, err if reslutJson.Get("code").Int() != 0 {
} else { err = errors.New(reslutJson.Get("message").String())
header = httpHeader return
}
}
}
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 return

View File

@ -5,56 +5,31 @@ import (
"github.com/XrayR-project/XrayR/api" "github.com/XrayR-project/XrayR/api"
"github.com/XrayR-project/XrayR/api/gov2panel" "github.com/XrayR-project/XrayR/api/gov2panel"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/util/gconv"
) )
func CreateClient() api.API { func CreateClient() api.API {
apiConfig := &api.Config{ apiConfig := &api.Config{
APIHost: "http://localhost:8080", APIHost: "http://localhost:8080",
Key: "123456", Key: "123456",
NodeID: 1, NodeID: 90,
NodeType: "V2ray", NodeType: "V2ray",
} }
client := gov2panel.New(apiConfig) client := gov2panel.New(apiConfig)
return client return client
} }
func TestGetV2rayNodeInfo(t *testing.T) { func TestGetNodeInfo(t *testing.T) {
client := CreateClient() client := CreateClient()
nodeInfo, err := client.GetNodeInfo() nodeInfo, err := client.GetNodeInfo()
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
t.Log(nodeInfo)
}
func TestGetSSNodeInfo(t *testing.T) { nodeInfoJson := gjson.New(nodeInfo)
apiConfig := &api.Config{ t.Log(nodeInfoJson.String())
APIHost: "http://127.0.0.1:668", t.Log(nodeInfoJson.String())
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) { func TestGetUserList(t *testing.T) {
@ -65,6 +40,7 @@ func TestGetUserList(t *testing.T) {
t.Error(err) t.Error(err)
} }
t.Log(len(*userList))
t.Log(userList) t.Log(userList)
} }
@ -74,24 +50,30 @@ func TestReportReportUserTraffic(t *testing.T) {
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
t.Log(userList)
generalUserTraffic := make([]api.UserTraffic, len(*userList)) generalUserTraffic := make([]api.UserTraffic, len(*userList))
for i, userInfo := range *userList { for i, userInfo := range *userList {
generalUserTraffic[i] = api.UserTraffic{ generalUserTraffic[i] = api.UserTraffic{
UID: userInfo.UID, UID: userInfo.UID,
Upload: 1111, Upload: 1073741824,
Download: 2222, Download: 1073741824,
} }
} }
// client.Debug()
t.Log(gconv.String(generalUserTraffic))
client = CreateClient()
err = client.ReportUserTraffic(&generalUserTraffic) err = client.ReportUserTraffic(&generalUserTraffic)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
t.Error(err)
} }
func TestGetNodeRule(t *testing.T) { func TestGetNodeRule(t *testing.T) {
client := CreateClient() client := CreateClient()
client.Debug() client.Debug()
ruleList, err := client.GetNodeRule() ruleList, err := client.GetNodeRule()
if err != nil { if err != nil {
t.Error(err) t.Error(err)

View File

@ -1,35 +1,9 @@
package gov2panel package gov2panel
import "encoding/json" type user struct {
Id int `json:"id"`
type serverConfig struct { Uuid string `json:"uuid"`
v2ray SpeedLimit int `json:"speed_limit"`
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 { type route struct {
@ -38,9 +12,3 @@ type route struct {
Action string `json:"action"` Action string `json:"action"`
ActionValue string `json:"action_value"` ActionValue string `json:"action_value"`
} }
type user struct {
Id int `json:"id"`
Uuid string `json:"uuid"`
SpeedLimit int `json:"speed_limit"`
}

6
go.mod
View File

@ -85,6 +85,7 @@ require (
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
github.com/chenzhuoyu/iasm v0.9.1 // indirect github.com/chenzhuoyu/iasm v0.9.1 // indirect
github.com/civo/civogo v0.3.63 // indirect github.com/civo/civogo v0.3.63 // indirect
github.com/clbanning/mxj/v2 v2.7.0 // indirect
github.com/cloudflare/circl v1.3.9 // indirect github.com/cloudflare/circl v1.3.9 // indirect
github.com/cloudflare/cloudflare-go v0.90.0 // indirect github.com/cloudflare/cloudflare-go v0.90.0 // indirect
github.com/cpu/goacmedns v0.1.1 // indirect github.com/cpu/goacmedns v0.1.1 // indirect
@ -95,6 +96,7 @@ require (
github.com/dimchansky/utfbom v1.1.1 // indirect github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/dnsimple/dnsimple-go v1.7.0 // indirect github.com/dnsimple/dnsimple-go v1.7.0 // indirect
github.com/exoscale/egoscale v1.19.0 // indirect github.com/exoscale/egoscale v1.19.0 // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/fatih/structs v1.1.0 // indirect github.com/fatih/structs v1.1.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/flosch/pongo2/v4 v4.0.2 // indirect github.com/flosch/pongo2/v4 v4.0.2 // indirect
@ -135,6 +137,7 @@ require (
github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 // indirect github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 // indirect
github.com/gorilla/css v1.0.1 // indirect github.com/gorilla/css v1.0.1 // indirect
github.com/gorilla/websocket v1.5.3 // indirect github.com/gorilla/websocket v1.5.3 // indirect
github.com/grokify/html-strip-tags-go v0.0.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect
@ -173,6 +176,7 @@ require (
github.com/mailru/easyjson v0.7.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/microcosm-cc/bluemonday v1.0.26 // indirect github.com/microcosm-cc/bluemonday v1.0.26 // indirect
github.com/miekg/dns v1.1.61 // indirect github.com/miekg/dns v1.1.61 // indirect
github.com/mimuret/golang-iij-dpf v0.9.1 // indirect github.com/mimuret/golang-iij-dpf v0.9.1 // indirect
@ -192,6 +196,7 @@ require (
github.com/nrdcg/nodion v0.1.0 // indirect github.com/nrdcg/nodion v0.1.0 // indirect
github.com/nrdcg/porkbun v0.3.0 // indirect github.com/nrdcg/porkbun v0.3.0 // indirect
github.com/nzdjb/go-metaname v1.0.0 // indirect github.com/nzdjb/go-metaname v1.0.0 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/onsi/ginkgo/v2 v2.19.0 // indirect github.com/onsi/ginkgo/v2 v2.19.0 // indirect
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
github.com/oracle/oci-go-sdk v24.3.0+incompatible // indirect github.com/oracle/oci-go-sdk v24.3.0+incompatible // indirect
@ -212,6 +217,7 @@ require (
github.com/quic-go/quic-go v0.45.1 // indirect github.com/quic-go/quic-go v0.45.1 // indirect
github.com/refraction-networking/utls v1.6.7 // indirect github.com/refraction-networking/utls v1.6.7 // indirect
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sacloud/api-client-go v0.2.10 // indirect github.com/sacloud/api-client-go v0.2.10 // indirect
github.com/sacloud/go-http v0.1.8 // indirect github.com/sacloud/go-http v0.1.8 // indirect