3x client

This commit is contained in:
royalcat 2024-10-10 20:25:05 +03:00
parent 45762d8483
commit 89a13f4edb
6 changed files with 50 additions and 212 deletions

1
go.mod
View file

@ -6,7 +6,6 @@ require (
connectrpc.com/authn v0.2.0 connectrpc.com/authn v0.2.0
connectrpc.com/connect v1.17.0 connectrpc.com/connect v1.17.0
github.com/bufbuild/buf v1.45.0 github.com/bufbuild/buf v1.45.0
github.com/carlmjohnson/requests v0.24.2
github.com/coreos/go-oidc/v3 v3.11.0 github.com/coreos/go-oidc/v3 v3.11.0
github.com/knadh/koanf/providers/env v1.0.0 github.com/knadh/koanf/providers/env v1.0.0
github.com/knadh/koanf/v2 v2.1.1 github.com/knadh/koanf/v2 v2.1.1

2
go.sum
View file

@ -40,8 +40,6 @@ github.com/bufbuild/protoplugin v0.0.0-20240911180120-7bb73e41a54a h1:l3RhVoG0Rt
github.com/bufbuild/protoplugin v0.0.0-20240911180120-7bb73e41a54a/go.mod h1:c5D8gWRIZ2HLWO3gXYTtUfw/hbJyD8xikv2ooPxnklQ= github.com/bufbuild/protoplugin v0.0.0-20240911180120-7bb73e41a54a/go.mod h1:c5D8gWRIZ2HLWO3gXYTtUfw/hbJyD8xikv2ooPxnklQ=
github.com/bufbuild/protovalidate-go v0.7.2 h1:UuvKyZHl5p7u3ztEjtRtqtDxOjRKX5VUOgKFq6p6ETk= github.com/bufbuild/protovalidate-go v0.7.2 h1:UuvKyZHl5p7u3ztEjtRtqtDxOjRKX5VUOgKFq6p6ETk=
github.com/bufbuild/protovalidate-go v0.7.2/go.mod h1:PHV5pFuWlRzdDW02/cmVyNzdiQ+RNNwo7idGxdzS7o4= github.com/bufbuild/protovalidate-go v0.7.2/go.mod h1:PHV5pFuWlRzdDW02/cmVyNzdiQ+RNNwo7idGxdzS7o4=
github.com/carlmjohnson/requests v0.24.2 h1:JDakhAmTIKL/qL/1P7Kkc2INGBJIkIFP6xUeUmPzLso=
github.com/carlmjohnson/requests v0.24.2/go.mod h1:duYA/jDnyZ6f3xbcF5PpZ9N8clgopubP2nK5i6MVMhU=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=

View file

@ -6,19 +6,19 @@ import (
) )
type X3Client struct { type X3Client struct {
Host string basePath string
httpClient *http.Client httpClient *http.Client
} }
func NewX3Client(host, port string) (*X3Client, error) { func NewX3Client(basePath string) (*X3Client, error) {
jar, err := cookiejar.New(nil) jar, err := cookiejar.New(nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &X3Client{ return &X3Client{
Host: host + ":" + port, basePath: basePath,
httpClient: &http.Client{ httpClient: &http.Client{
Jar: jar, Jar: jar,

View file

@ -26,7 +26,7 @@ func makeGetRequest[R any](httpClient *http.Client, url string) (R, error) {
return res, nil return res, nil
} }
func makePostRequest[Q, R any](httpClient *http.Client, url string, req Q) (R, error) { func makePostRequest[R any](httpClient *http.Client, url string, req any) (R, error) {
var res R var res R
body, err := json.Marshal(req) body, err := json.Marshal(req)
if err != nil { if err != nil {
@ -50,114 +50,128 @@ func makePostRequest[Q, R any](httpClient *http.Client, url string, req Q) (R, e
const loginURL = "/login" const loginURL = "/login"
func (c *X3Client) Login(request LoginRequest) (GeneralResponse[LoginResponse], error) { func (c *X3Client) Login(request LoginRequest) (GeneralResponse[LoginResponse], error) {
return makePostRequest[interface{}, GeneralResponse[LoginResponse]](c.httpClient, c.Host+loginURL+fmt.Sprintf("?username=%s&password=%s", request.Username, request.Password), nil) return makePostRequest[GeneralResponse[LoginResponse]](c.httpClient, c.basePath+loginURL+fmt.Sprintf("?username=%s&password=%s", request.Username, request.Password), nil)
} }
const inboundsURL = "/panel/api/inbounds/list" const inboundsURL = "/panel/api/inbounds/list"
func (c *X3Client) GetInbounds() (GeneralResponse[[]Inbound], error) { func (c *X3Client) GetInbounds() (GeneralResponse[[]Inbound], error) {
return makeGetRequest[GeneralResponse[[]Inbound]](c.httpClient, c.Host+inboundsURL) return makeGetRequest[GeneralResponse[[]Inbound]](c.httpClient, c.basePath+inboundsURL)
} }
const inboundURL = "/panel/api/inbounds/get/%d" const inboundURL = "/panel/api/inbounds/get/%d"
func (c *X3Client) GetInbound(inboundId int) (GeneralResponse[Inbound], error) { func (c *X3Client) GetInbound(inboundId int) (GeneralResponse[Inbound], error) {
return makeGetRequest[GeneralResponse[Inbound]](c.httpClient, c.Host+fmt.Sprintf(inboundURL, inboundId)) return makeGetRequest[GeneralResponse[Inbound]](c.httpClient, c.basePath+fmt.Sprintf(inboundURL, inboundId))
} }
const inboundClientTrafficURL = "/panel/api/inbounds/getClientTraffics/%s" const inboundClientTrafficURL = "/panel/api/inbounds/getClientTraffics/%s"
func (c *X3Client) GetInboundClientTraffic(email string) (GeneralResponse[ClientStats], error) { func (c *X3Client) GetInboundClientTraffic(email string) (GeneralResponse[ClientStats], error) {
return makeGetRequest[GeneralResponse[ClientStats]](c.httpClient, c.Host+fmt.Sprintf(inboundClientTrafficURL, email)) return makeGetRequest[GeneralResponse[ClientStats]](c.httpClient, c.basePath+fmt.Sprintf(inboundClientTrafficURL, email))
} }
const inboundClientTrafficByIdURL = "/panel/api/inbounds/getClientTrafficsById/%s" const inboundClientTrafficByIdURL = "/panel/api/inbounds/getClientTrafficsById/%s"
func (c *X3Client) GetInboundClientTrafficById(uuid string) (GeneralResponse[ClientStats], error) { func (c *X3Client) GetInboundClientTrafficById(uuid string) (GeneralResponse[ClientStats], error) {
return makeGetRequest[GeneralResponse[ClientStats]](c.httpClient, c.Host+fmt.Sprintf(inboundClientTrafficByIdURL, uuid)) return makeGetRequest[GeneralResponse[ClientStats]](c.httpClient, c.basePath+fmt.Sprintf(inboundClientTrafficByIdURL, uuid))
} }
const inboundCreateBackupURL = "/panel/api/inbounds/createbackup" const inboundCreateBackupURL = "/panel/api/inbounds/createbackup"
func (c *X3Client) CreateInboundBackup() (GeneralResponse[interface{}], error) { func (c *X3Client) CreateInboundBackup() (GeneralResponse[interface{}], error) {
return makeGetRequest[GeneralResponse[interface{}]](c.httpClient, c.Host+inboundCreateBackupURL) return makeGetRequest[GeneralResponse[interface{}]](c.httpClient, c.basePath+inboundCreateBackupURL)
} }
const clientIpsURL = "/panel/api/inbounds/clientIps/%s" const clientIpsURL = "/panel/api/inbounds/clientIps/%s"
func (c *X3Client) GetClientIps(email string) (GeneralResponse[string], error) { func (c *X3Client) GetClientIps(email string) (GeneralResponse[string], error) {
return makePostRequest[interface{}, GeneralResponse[string]](c.httpClient, c.Host+fmt.Sprintf(clientIpsURL, email), nil) return makePostRequest[GeneralResponse[string]](c.httpClient, c.basePath+fmt.Sprintf(clientIpsURL, email), nil)
} }
const addInboundURL = "/panel/api/inbounds/add" const addInboundURL = "/panel/api/inbounds/add"
func (c *X3Client) AddInbound(inbound Inbound) (GeneralResponse[Inbound], error) { func (c *X3Client) AddInbound(inbound Inbound) (GeneralResponse[Inbound], error) {
return makePostRequest[Inbound, GeneralResponse[Inbound]](c.httpClient, c.Host+addInboundURL, inbound) return makePostRequest[GeneralResponse[Inbound]](c.httpClient, c.basePath+addInboundURL, inbound)
} }
const addClientToInboundURL = "/panel/api/inbounds/addClient" const addClientToInboundURL = "/panel/api/inbounds/addClient"
func (c *X3Client) AddClientToInbound(client CreateClient) (GeneralResponse[interface{}], error) { type createClient struct {
return makePostRequest[CreateClient, GeneralResponse[interface{}]](c.httpClient, c.Host+addClientToInboundURL, client) Id int `json:"id"`
Settings json.RawMessage `json:"settings"`
}
func (c *X3Client) AddClientToInbound(inboundID int, client []Client) (GeneralResponse[interface{}], error) {
req := createClient{
Id: inboundID,
}
var err error
req.Settings, err = json.Marshal(struct{ client []Client }{client: client})
if err != nil {
return GeneralResponse[interface{}]{}, err
}
return makePostRequest[GeneralResponse[interface{}]](c.httpClient, c.basePath+addClientToInboundURL, req)
} }
const updateInboundURL = "/panel/api/inbounds/update/%d" const updateInboundURL = "/panel/api/inbounds/update/%d"
func (c *X3Client) UpdateInbound(inbound Inbound) (GeneralResponse[Inbound], error) { func (c *X3Client) UpdateInbound(inbound Inbound) (GeneralResponse[Inbound], error) {
return makePostRequest[Inbound, GeneralResponse[Inbound]](c.httpClient, c.Host+fmt.Sprintf(updateInboundURL, inbound.Id), inbound) return makePostRequest[GeneralResponse[Inbound]](c.httpClient, c.basePath+fmt.Sprintf(updateInboundURL, inbound.Id), inbound)
} }
// TODO: Check if client id is int not an uuid // TODO: Check if client id is int not an uuid
const updateClientInboundURL = "/panel/api/inbounds/updateClient/%d" const updateClientInboundURL = "/panel/api/inbounds/updateClient/%d"
func (c *X3Client) UpdateClientInbound(client CreateClient) (GeneralResponse[interface{}], error) { func (c *X3Client) UpdateClientInbound(client createClient) (GeneralResponse[interface{}], error) {
return makePostRequest[CreateClient, GeneralResponse[interface{}]](c.httpClient, c.Host+fmt.Sprintf(updateClientInboundURL, client.Id), client) return makePostRequest[GeneralResponse[interface{}]](c.httpClient, c.basePath+fmt.Sprintf(updateClientInboundURL, client.Id), client)
} }
const clearClientIpsURL = "/panel/api/inbounds/clearClientIps/%s" const clearClientIpsURL = "/panel/api/inbounds/clearClientIps/%s"
func (c *X3Client) ClearClientIps(email string) (GeneralResponse[interface{}], error) { func (c *X3Client) ClearClientIps(email string) (GeneralResponse[interface{}], error) {
return makePostRequest[interface{}, GeneralResponse[interface{}]](c.httpClient, c.Host+fmt.Sprintf(clearClientIpsURL, email), nil) return makePostRequest[GeneralResponse[interface{}]](c.httpClient, c.basePath+fmt.Sprintf(clearClientIpsURL, email), nil)
} }
const resetAllTrafficURL = "/panel/api/inbounds/resetAllTraffics" const resetAllTrafficURL = "/panel/api/inbounds/resetAllTraffics"
func (c *X3Client) ResetAllTraffic() (GeneralResponse[interface{}], error) { func (c *X3Client) ResetAllTraffic() (GeneralResponse[interface{}], error) {
return makePostRequest[interface{}, GeneralResponse[interface{}]](c.httpClient, c.Host+resetAllTrafficURL, nil) return makePostRequest[GeneralResponse[interface{}]](c.httpClient, c.basePath+resetAllTrafficURL, nil)
} }
const resetAllClientTrafficByInboundURL = "/panel/api/inbounds/resetAllClientTraffics/%d" const resetAllClientTrafficByInboundURL = "/panel/api/inbounds/resetAllClientTraffics/%d"
func (c *X3Client) ResetAllClientTrafficByInbound(inboundId int) (GeneralResponse[interface{}], error) { func (c *X3Client) ResetAllClientTrafficByInbound(inboundId int) (GeneralResponse[interface{}], error) {
return makePostRequest[interface{}, GeneralResponse[interface{}]](c.httpClient, c.Host+fmt.Sprintf(resetAllClientTrafficByInboundURL, inboundId), nil) return makePostRequest[GeneralResponse[interface{}]](c.httpClient, c.basePath+fmt.Sprintf(resetAllClientTrafficByInboundURL, inboundId), nil)
} }
const resetClientTrafficURL = "/panel/api/inbounds/%d/resetClientTraffic/%s" const resetClientTrafficURL = "/panel/api/inbounds/%d/resetClientTraffic/%s"
func (c *X3Client) ResetClientTraffic(inboundId int, email string) (GeneralResponse[interface{}], error) { func (c *X3Client) ResetClientTraffic(inboundId int, email string) (GeneralResponse[interface{}], error) {
return makePostRequest[interface{}, GeneralResponse[interface{}]](c.httpClient, c.Host+fmt.Sprintf(resetClientTrafficURL, inboundId, email), nil) return makePostRequest[GeneralResponse[interface{}]](c.httpClient, c.basePath+fmt.Sprintf(resetClientTrafficURL, inboundId, email), nil)
} }
const deleteClientURL = "/panel/api/inbounds/%d/delClient/%s" const deleteClientURL = "/panel/api/inbounds/%d/delClient/%s"
func (c *X3Client) DeleteClient(inboundId int, uuid string) (GeneralResponse[interface{}], error) { func (c *X3Client) DeleteClient(inboundId int, uuid string) (GeneralResponse[interface{}], error) {
return makePostRequest[interface{}, GeneralResponse[interface{}]](c.httpClient, c.Host+fmt.Sprintf(deleteClientURL, inboundId, uuid), nil) return makePostRequest[GeneralResponse[interface{}]](c.httpClient, c.basePath+fmt.Sprintf(deleteClientURL, inboundId, uuid), nil)
} }
const deleteInboundURL = "/panel/api/inbounds/del/%d" const deleteInboundURL = "/panel/api/inbounds/del/%d"
func (c *X3Client) DeleteInbound(inboundId int) (GeneralResponse[interface{}], error) { func (c *X3Client) DeleteInbound(inboundId int) (GeneralResponse[interface{}], error) {
return makePostRequest[interface{}, GeneralResponse[interface{}]](c.httpClient, c.Host+fmt.Sprintf(deleteInboundURL, inboundId), nil) return makePostRequest[GeneralResponse[interface{}]](c.httpClient, c.basePath+fmt.Sprintf(deleteInboundURL, inboundId), nil)
} }
const deleteDepletedClientsURL = "/panel/api/inbounds/delDepletedClients/%d" const deleteDepletedClientsURL = "/panel/api/inbounds/delDepletedClients/%d"
func (c *X3Client) DeleteDepletedClients(inboundId int) (GeneralResponse[interface{}], error) { func (c *X3Client) DeleteDepletedClients(inboundId int) (GeneralResponse[interface{}], error) {
return makePostRequest[interface{}, GeneralResponse[interface{}]](c.httpClient, c.Host+fmt.Sprintf(deleteDepletedClientsURL, inboundId), nil) return makePostRequest[GeneralResponse[interface{}]](c.httpClient, c.basePath+fmt.Sprintf(deleteDepletedClientsURL, inboundId), nil)
} }
const onlineClientsURL = "/panel/api/inbounds/onlines" const onlineClientsURL = "/panel/api/inbounds/onlines"
func (c *X3Client) GetOnlineClients() (GeneralResponse[interface{}], error) { func (c *X3Client) GetOnlineClients() (GeneralResponse[interface{}], error) {
return makePostRequest[interface{}, GeneralResponse[interface{}]](c.httpClient, c.Host+onlineClientsURL, nil) return makePostRequest[GeneralResponse[interface{}]](c.httpClient, c.basePath+onlineClientsURL, nil)
} }

View file

@ -45,7 +45,15 @@ type ClientStats struct {
Reset int `json:"reset"` Reset int `json:"reset"`
} }
type CreateClient struct { type Client struct {
Id int `json:"id"` ID string `json:"id"`
Settings string `json:"settings"` Flow string `json:"flow"`
Email string `json:"email"`
LimitIP int64 `json:"limitIp"`
TotalGB int64 `json:"totalGB"`
ExpiryTime int64 `json:"expiryTime"`
Enable bool `json:"enable"`
TgID string `json:"tgId"`
SubID string `json:"subId"`
Reset int64 `json:"reset"`
} }

View file

@ -1,181 +0,0 @@
package tresx
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/cookiejar"
"net/url"
"github.com/carlmjohnson/requests"
)
type XUIPriovider struct {
baseUrl string
client *http.Client
}
func NewTresXProvider(basePath string) *XUIPriovider {
jar, err := cookiejar.New(nil)
if err != nil {
panic(err)
}
return &XUIPriovider{
baseUrl: basePath,
client: &http.Client{
Jar: jar,
},
}
}
type basicResponse struct {
Success bool `json:"success"`
Message string `json:"msg"`
Object json.RawMessage `json:"obj"`
}
func (t *XUIPriovider) Login(login, password string) error {
params := url.Values{}
params.Add("username", login)
params.Add("password", password)
url := fmt.Sprintf("%s/login?%s", t.baseUrl, params.Encode())
req, err := http.NewRequest("POST", url, nil)
if err != nil {
return err
}
res, err := t.client.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
if err != nil {
return err
}
var resp basicResponse
err = json.Unmarshal(body, &resp)
if err != nil {
return err
}
if !resp.Success {
return fmt.Errorf("login failed: %s", resp.Message)
}
return nil
}
type Inbound struct {
ID int64 `json:"id"`
Up int64 `json:"up"`
Down int64 `json:"down"`
Total int64 `json:"total"`
Remark string `json:"remark"`
Enable bool `json:"enable"`
ExpiryTime int64 `json:"expiryTime"`
ClientStats []ClientStat `json:"clientStats"`
Listen string `json:"listen"`
Port int64 `json:"port"`
Protocol string `json:"protocol"`
Settings string `json:"settings"`
StreamSettings string `json:"streamSettings"`
Tag string `json:"tag"`
Sniffing string `json:"sniffing"`
Allocate string `json:"allocate"`
}
type ClientStat struct {
ID int64 `json:"id"`
InboundID int64 `json:"inboundId"`
Enable bool `json:"enable"`
Email string `json:"email"`
Up int64 `json:"up"`
Down int64 `json:"down"`
ExpiryTime int64 `json:"expiryTime"`
Total int64 `json:"total"`
Reset int64 `json:"reset"`
}
func (t *XUIPriovider) Inbounds(ctx context.Context) error {
var resp basicResponse
err := requests.
URL(t.baseUrl).
Path("panel/api/inbounds/list").
Client(t.client).
ToJSON(&resp).
Fetch(ctx)
if err != nil {
return err
}
if !resp.Success {
return fmt.Errorf("logout failed: %s", resp.Message)
}
var inbounds []Inbound
err = json.Unmarshal(resp.Object, &inbounds)
if err != nil {
return err
}
return nil
}
type addClientRequest struct {
ID int `json:"id"`
Settings json.RawMessage `json:"settings"`
}
type Client struct {
ID string `json:"id"`
Flow string `json:"flow"`
Email string `json:"email"`
LimitIP int64 `json:"limitIp"`
TotalGB int64 `json:"totalGB"`
ExpiryTime int64 `json:"expiryTime"`
Enable bool `json:"enable"`
TgID string `json:"tgId"`
SubID string `json:"subId"`
Reset int64 `json:"reset"`
}
func (t *XUIPriovider) AddClientToInbound(ctx context.Context, inboundID int, clients []Client) error {
addReq := addClientRequest{
ID: inboundID,
}
settings := struct {
Clients []Client `json:"clients"`
}{
Clients: clients,
}
var err error
addReq.Settings, err = json.Marshal(settings)
if err != nil {
return err
}
var resp basicResponse
err = requests.
URL(t.baseUrl).
Path("panel/api/inbounds/addClient").
Post().
BodyJSON(addReq).
Client(t.client).
ToJSON(&resp).
Fetch(ctx)
if err != nil {
return err
}
if !resp.Success {
return fmt.Errorf("logout failed: %s", resp.Message)
}
return nil
}