feat: add agent-friendly ufile CLI
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const sessionCookieName = "ufile_session"
|
||||
|
||||
type apiClient struct {
|
||||
host string
|
||||
session string
|
||||
http *http.Client
|
||||
}
|
||||
|
||||
type apiError struct {
|
||||
Status int
|
||||
Code string
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e *apiError) Error() string { return e.Message }
|
||||
|
||||
type userResponse struct {
|
||||
ID uint `json:"id"`
|
||||
Username string `json:"username"`
|
||||
CanManageUsers bool `json:"canManageUsers"`
|
||||
CanManageFiles bool `json:"canManageFiles"`
|
||||
CanUpload bool `json:"canUpload"`
|
||||
CanAdvancedUpload bool `json:"canAdvancedUpload"`
|
||||
MustChangePassword bool `json:"mustChangePassword"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type fileResponse struct {
|
||||
ID uint `json:"id"`
|
||||
Hash string `json:"hash"`
|
||||
Name string `json:"name"`
|
||||
MimeType string `json:"mimeType"`
|
||||
Size int64 `json:"size"`
|
||||
ExpiresAt *time.Time `json:"expiresAt"`
|
||||
MaxDownloads *int `json:"maxDownloads"`
|
||||
DownloadCount int `json:"downloadCount"`
|
||||
}
|
||||
|
||||
func newAPIClient(host, session string, timeout time.Duration) *apiClient {
|
||||
return &apiClient{host: host, session: session, http: &http.Client{Timeout: timeout}}
|
||||
}
|
||||
|
||||
func (c *apiClient) newRequest(ctx context.Context, method, path string, body io.Reader) (*http.Request, error) {
|
||||
request, err := http.NewRequestWithContext(ctx, method, c.host+path, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
request.Header.Set("Accept", "application/json")
|
||||
request.Header.Set("User-Agent", "ufile-cli/"+version)
|
||||
if c.session != "" {
|
||||
request.AddCookie(&http.Cookie{Name: sessionCookieName, Value: c.session})
|
||||
}
|
||||
return request, nil
|
||||
}
|
||||
|
||||
func (c *apiClient) doJSON(ctx context.Context, method, path string, requestBody, responseBody any) (*http.Response, error) {
|
||||
var body io.Reader
|
||||
if requestBody != nil {
|
||||
data, err := json.Marshal(requestBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
body = bytes.NewReader(data)
|
||||
}
|
||||
request, err := c.newRequest(ctx, method, path, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if requestBody != nil {
|
||||
request.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
response, err := c.http.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode < 200 || response.StatusCode >= 300 {
|
||||
return response, decodeAPIError(response)
|
||||
}
|
||||
if responseBody == nil || response.StatusCode == http.StatusNoContent {
|
||||
_, _ = io.Copy(io.Discard, response.Body)
|
||||
return response, nil
|
||||
}
|
||||
if err := json.NewDecoder(io.LimitReader(response.Body, 2<<20)).Decode(responseBody); err != nil {
|
||||
return response, fmt.Errorf("服务端返回了无效的 JSON: %w", err)
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func decodeAPIError(response *http.Response) error {
|
||||
payload := struct {
|
||||
Error string `json:"error"`
|
||||
Code string `json:"code"`
|
||||
}{}
|
||||
data, _ := io.ReadAll(io.LimitReader(response.Body, 1<<20))
|
||||
_ = json.Unmarshal(data, &payload)
|
||||
message := strings.TrimSpace(payload.Error)
|
||||
if message == "" {
|
||||
message = strings.TrimSpace(string(data))
|
||||
}
|
||||
if message == "" {
|
||||
message = response.Status
|
||||
}
|
||||
return &apiError{Status: response.StatusCode, Code: payload.Code, Message: message}
|
||||
}
|
||||
Reference in New Issue
Block a user