120 lines
3.3 KiB
Go
120 lines
3.3 KiB
Go
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}
|
|
}
|