175 lines
4.0 KiB
Go
175 lines
4.0 KiB
Go
package alist
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/krau/SaveAny-Bot/config"
|
|
"github.com/krau/SaveAny-Bot/logger"
|
|
)
|
|
|
|
type Alist struct {
|
|
client *http.Client
|
|
token string
|
|
baseURL string
|
|
loginInfo *loginRequest
|
|
}
|
|
|
|
var (
|
|
ErrAlistLoginFailed = errors.New("failed to login to Alist")
|
|
)
|
|
|
|
type loginRequest struct {
|
|
Username string `json:"username"`
|
|
Password string `json:"password"`
|
|
}
|
|
|
|
type loginResponse struct {
|
|
Code int `json:"code"`
|
|
Message string `json:"message"`
|
|
Data struct {
|
|
Token string `json:"token"`
|
|
} `json:"data"`
|
|
}
|
|
|
|
type putResponse struct {
|
|
Code int `json:"code"`
|
|
Message string `json:"message"`
|
|
Data struct {
|
|
Task struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
State int `json:"state"`
|
|
Status string `json:"status"`
|
|
Progress int `json:"progress"`
|
|
Error string `json:"error"`
|
|
} `json:"task"`
|
|
} `json:"data"`
|
|
}
|
|
|
|
func (a *Alist) getToken() error {
|
|
loginBody, err := json.Marshal(a.loginInfo)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal login request: %w", err)
|
|
}
|
|
|
|
req, err := http.NewRequest(http.MethodPost, a.baseURL+"/api/auth/login", bytes.NewBuffer(loginBody))
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create login request: %w", err)
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
resp, err := a.client.Do(req)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to send login request: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read login response: %w", err)
|
|
}
|
|
|
|
var loginResp loginResponse
|
|
if err := json.Unmarshal(body, &loginResp); err != nil {
|
|
return fmt.Errorf("failed to unmarshal login response: %w", err)
|
|
}
|
|
|
|
if loginResp.Code != http.StatusOK {
|
|
return fmt.Errorf("%w: %s", ErrAlistLoginFailed, loginResp.Message)
|
|
}
|
|
|
|
a.token = loginResp.Data.Token
|
|
return nil
|
|
}
|
|
|
|
func (a *Alist) refreshToken() {
|
|
for {
|
|
time.Sleep(time.Duration(config.Cfg.Storage.Alist.TokenExp) * time.Second)
|
|
if err := a.getToken(); err != nil {
|
|
logger.L.Errorf("Failed to refresh jwt token: %v", err)
|
|
continue
|
|
}
|
|
logger.L.Info("Refreshed Alist jwt token")
|
|
}
|
|
}
|
|
|
|
func (a *Alist) Init() {
|
|
a.baseURL = config.Cfg.Storage.Alist.URL
|
|
a.client = &http.Client{
|
|
Timeout: 12 * time.Hour,
|
|
Transport: &http.Transport{
|
|
TLSHandshakeTimeout: 10 * time.Second,
|
|
},
|
|
}
|
|
a.loginInfo = &loginRequest{
|
|
Username: config.Cfg.Storage.Alist.Username,
|
|
Password: config.Cfg.Storage.Alist.Password,
|
|
}
|
|
|
|
if err := a.getToken(); err != nil {
|
|
logger.L.Fatalf("Failed to login to Alist: %v", err)
|
|
os.Exit(1)
|
|
}
|
|
logger.L.Debug("Logged in to Alist")
|
|
|
|
go a.refreshToken()
|
|
}
|
|
|
|
func (a *Alist) Save(ctx context.Context, filePath, storagePath string) error {
|
|
file, err := os.Open(filePath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open file: %w", err)
|
|
}
|
|
defer file.Close()
|
|
|
|
filestat, err := file.Stat()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get file stats: %w", err)
|
|
}
|
|
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodPut, a.baseURL+"/api/fs/put", file)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
req.Header.Set("Authorization", a.token)
|
|
req.Header.Set("File-Path", url.PathEscape(storagePath))
|
|
req.Header.Set("As-Task", "true")
|
|
req.Header.Set("Content-Type", "application/octet-stream")
|
|
req.ContentLength = filestat.Size()
|
|
|
|
resp, err := a.client.Do(req)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to send request: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return fmt.Errorf("failed to save file to Alist: %s", resp.Status)
|
|
}
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read response body: %w", err)
|
|
}
|
|
|
|
var putResp putResponse
|
|
if err := json.Unmarshal(body, &putResp); err != nil {
|
|
return fmt.Errorf("failed to unmarshal put response: %w", err)
|
|
}
|
|
|
|
if putResp.Code != http.StatusOK {
|
|
return fmt.Errorf("failed to save file to Alist: %d, %s", putResp.Code, putResp.Message)
|
|
}
|
|
|
|
return nil
|
|
}
|