639 lines
17 KiB
Go
639 lines
17 KiB
Go
package alistsdk
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
VERSION = "1.0.0"
|
|
)
|
|
|
|
type Client struct {
|
|
base string //Alist API base url
|
|
token string //Alist API access token
|
|
username string //Alist username
|
|
password string //Alist password
|
|
inscure bool //Skip TLS verification
|
|
timeout int //Request timeout
|
|
}
|
|
|
|
// NewClient creates a new instance of the Client struct.
|
|
//
|
|
// Parameters:
|
|
// - endpoint: the API endpoint.
|
|
// - username: the username for authentication.
|
|
// - password: the password for authentication.
|
|
// - insecure: whether to ignore SSL certificate verification.
|
|
// - timeout: the timeout in seconds for API requests.
|
|
//
|
|
// Returns:
|
|
// - a pointer to the Client struct.
|
|
func NewClient(endpoint, username, password string, insecure bool, timeout int) *Client {
|
|
return &Client{
|
|
base: endpoint,
|
|
username: username,
|
|
password: password,
|
|
inscure: insecure,
|
|
timeout: timeout,
|
|
}
|
|
}
|
|
|
|
// NewClientWithToken creates a new instance of the Client struct with a token.
|
|
//
|
|
// Parameters:
|
|
// - endpoint: the API endpoint.
|
|
// - token: the access token for authentication.
|
|
// - insecure: whether to ignore SSL certificate verification.
|
|
// - timeout: the timeout in seconds for API requests.
|
|
//
|
|
// Returns:
|
|
// - a pointer to the Client struct.
|
|
func NewClientWithToken(endpoint, token string, insecure bool, timeout int) *Client {
|
|
return &Client{
|
|
base: endpoint,
|
|
token: token,
|
|
inscure: insecure,
|
|
timeout: timeout,
|
|
}
|
|
}
|
|
|
|
// Login performs the login operation for the Client.
|
|
//
|
|
// It sends a POST request to the "/api/auth/login" endpoint with the provided
|
|
// username and password in the request body as JSON. If successful, it
|
|
// extracts the JWT token from the response and sets it as the authorization
|
|
// header for future requests. Then, it sends a GET request to the "/api/me"
|
|
// endpoint to retrieve the user information. If successful, it returns the
|
|
// user information as a *User object.
|
|
//
|
|
// Returns:
|
|
// - *User: The user information if the login is successful.
|
|
// - error: An error if the login or user information retrieval fails.
|
|
func (c *Client) Login() (*User, error) {
|
|
if !c.isLogin() {
|
|
body := `{"username": "` + c.username + `","password": "` + c.password + `"}`
|
|
respByts, err := do("POST", c.base+"/api/auth/login", bytes.NewBufferString(body), c.token, &RequestOptions{
|
|
Insecure: c.inscure,
|
|
Timeout: c.timeout,
|
|
RetryTimes: 0,
|
|
MaxRetryTimes: 3,
|
|
RetryIntervalSecond: 5,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
loginResp := &LoginResp{}
|
|
err = json.Unmarshal(respByts, loginResp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if loginResp.Code != 200 {
|
|
return nil, errors.New(loginResp.Message)
|
|
}
|
|
c.token = loginResp.Data.Token
|
|
}
|
|
|
|
respByts, err := do("GET", c.base+"/api/me", nil, c.token, &RequestOptions{
|
|
Insecure: c.inscure,
|
|
Timeout: c.timeout,
|
|
RetryTimes: 0,
|
|
MaxRetryTimes: 3,
|
|
RetryIntervalSecond: 5,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
userResp := &UserInfoResp{}
|
|
err = json.Unmarshal(respByts, userResp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if userResp.Code != 200 {
|
|
return nil, errors.New(userResp.Message)
|
|
}
|
|
u := &User{}
|
|
u = userResp.Data
|
|
return u, nil
|
|
}
|
|
|
|
func (c *Client) isLogin() bool {
|
|
return c.token != ""
|
|
}
|
|
|
|
// MkDir creates a directory at the specified path.
|
|
//
|
|
// path: the path of the directory to be created.
|
|
// error: returns an error if the directory creation fails.
|
|
func (c *Client) MkDir(path string) error {
|
|
if !c.isLogin() {
|
|
return errors.New("not login yet")
|
|
}
|
|
body := `{
|
|
"path": "` + path + `"
|
|
}`
|
|
|
|
respByts, err := do("POST", c.base+"/api/fs/mkdir", bytes.NewBufferString(body), c.token, &RequestOptions{
|
|
Insecure: c.inscure,
|
|
Timeout: c.timeout,
|
|
RetryTimes: 0,
|
|
MaxRetryTimes: 3,
|
|
RetryIntervalSecond: 5,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
comResp := &CommonResp{}
|
|
err = json.Unmarshal(respByts, comResp)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if comResp.Code != 200 {
|
|
return errors.New(comResp.Message)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// PutUpload 使用流式方式上传文件
|
|
func (c *Client) PutUpload(filePath, targetPath string, asTask bool) error {
|
|
if !c.isLogin() {
|
|
return errors.New("not login yet")
|
|
}
|
|
file, err := os.Open(filePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer file.Close()
|
|
fileInfo, err := file.Stat()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fileSize := fileInfo.Size()
|
|
req, err := http.NewRequest("PUT", c.base+"/api/fs/put", file)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
req.Header.Set("Authorization", c.token)
|
|
req.Header.Set("Content-Type", "application/octet-stream")
|
|
req.Header.Set("Content-Length", strconv.FormatInt(fileSize, 10))
|
|
req.Header.Set("File-Path", url.QueryEscape(targetPath))
|
|
if asTask {
|
|
req.Header.Set("As-Task", "true")
|
|
}
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
var uploadResp struct {
|
|
Code int `json:"code"`
|
|
Message string `json:"message"`
|
|
Data interface{} `json:"data"`
|
|
}
|
|
err = json.NewDecoder(resp.Body).Decode(&uploadResp)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if uploadResp.Code != 200 {
|
|
return fmt.Errorf("流式上传失败: %s", uploadResp.Message)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Client) GetFileDownloadUrl(path, userBasePath string) string {
|
|
return c.base + "/d" + userBasePath + path
|
|
}
|
|
|
|
// Rename renames a file or directory to a new name.
|
|
//
|
|
// Parameters:
|
|
// - newName: the new name to rename to (string)
|
|
// - path: the path of the file or directory to rename (string)
|
|
//
|
|
// Returns:
|
|
// - error: an error if the renaming process fails (error)
|
|
func (c *Client) Rename(newName, path string) error {
|
|
if !c.isLogin() {
|
|
return errors.New("not login yet")
|
|
}
|
|
body := `{
|
|
"name": "` + newName + `",
|
|
"path": "` + path + `"
|
|
}`
|
|
respByts, err := do("POST", c.base+"/api/fs/rename", bytes.NewBufferString(body), c.token, &RequestOptions{
|
|
Insecure: c.inscure,
|
|
Timeout: c.timeout,
|
|
RetryTimes: 0,
|
|
MaxRetryTimes: 3,
|
|
RetryIntervalSecond: 5,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
comResp := &CommonResp{}
|
|
err = json.Unmarshal(respByts, comResp)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if comResp.Code != 200 {
|
|
return errors.New(comResp.Message)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Remove removes the specified files or directories from the client's filesystem.
|
|
//
|
|
// Parameters:
|
|
// - dir (string): The directory where the files or directories are located.
|
|
// - names ([]string): The names of the files or directories to be removed.
|
|
//
|
|
// Returns:
|
|
// - error: An error if the removal operation fails, nil otherwise.
|
|
func (c *Client) Remove(dir string, names []string) error {
|
|
if !c.isLogin() {
|
|
return errors.New("not login yet")
|
|
}
|
|
body := `{
|
|
"names": ["` + strings.Join(names, `","`) + `"],
|
|
"dir": "` + dir + `"
|
|
}`
|
|
|
|
respByts, err := do("POST", c.base+"/api/fs/remove", bytes.NewBufferString(body), c.token, &RequestOptions{
|
|
Insecure: c.inscure,
|
|
Timeout: c.timeout,
|
|
RetryTimes: 0,
|
|
MaxRetryTimes: 3,
|
|
RetryIntervalSecond: 5,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
comResp := &CommonResp{}
|
|
err = json.Unmarshal(respByts, comResp)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if comResp.Code != 200 {
|
|
return errors.New(comResp.Message)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// RemoveEmptyDir removes an empty directory.
|
|
//
|
|
// The `dir` parameter is a string that represents the directory path to be removed.
|
|
// It returns an error if there was a problem removing the directory.
|
|
func (c *Client) RemoveEmptyDir(dir string) error {
|
|
if !c.isLogin() {
|
|
return errors.New("not login yet")
|
|
}
|
|
body := `{
|
|
"src_dir": "` + dir + `"
|
|
}`
|
|
|
|
respByts, err := do("POST", c.base+"/api/fs/remove_empty_directory", bytes.NewBufferString(body), c.token, &RequestOptions{
|
|
Insecure: c.inscure,
|
|
Timeout: c.timeout,
|
|
RetryTimes: 0,
|
|
MaxRetryTimes: 3,
|
|
RetryIntervalSecond: 5,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
comResp := &CommonResp{}
|
|
err = json.Unmarshal(respByts, comResp)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if comResp.Code != 200 {
|
|
return errors.New(comResp.Message)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Copy copies files from source directory to destination directory.
|
|
//
|
|
// srcDir: the source directory from which files will be copied.
|
|
// destDir: the destination directory to which files will be copied.
|
|
// names: a slice of string containing the names of the files to be copied.
|
|
// error: an error indicating any failure during the copy operation.
|
|
func (c *Client) Copy(srcDir, destDir string, names []string) error {
|
|
if !c.isLogin() {
|
|
return errors.New("not login yet")
|
|
}
|
|
body := `{
|
|
"src_dir": "` + srcDir + `",
|
|
"dst_dir": "` + destDir + `",
|
|
"names": ["` + strings.Join(names, `","`) + `"]
|
|
}`
|
|
|
|
respByts, err := do("POST", c.base+"/api/fs/copy", bytes.NewBufferString(body), c.token, &RequestOptions{
|
|
Insecure: c.inscure,
|
|
Timeout: c.timeout,
|
|
RetryTimes: 0,
|
|
MaxRetryTimes: 3,
|
|
RetryIntervalSecond: 5,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
comResp := &CommonResp{}
|
|
err = json.Unmarshal(respByts, comResp)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if comResp.Code != 200 {
|
|
return errors.New(comResp.Message)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// RecursiveMove 递归移动
|
|
// srcDir: 源目录
|
|
// destDir: 目标目录
|
|
func (c *Client) RecursiveMove(srcDir, destDir string) error {
|
|
if !c.isLogin() {
|
|
return errors.New("not login yet")
|
|
}
|
|
body := `{
|
|
"src_dir": "` + srcDir + `",
|
|
"dst_dir": "` + destDir + `"
|
|
}`
|
|
|
|
respByts, err := do("POST", c.base+"/api/fs/recursive_move", bytes.NewBufferString(body), c.token, &RequestOptions{
|
|
Insecure: c.inscure,
|
|
Timeout: c.timeout,
|
|
RetryTimes: 0,
|
|
MaxRetryTimes: 3,
|
|
RetryIntervalSecond: 5,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
comResp := &CommonResp{}
|
|
err = json.Unmarshal(respByts, comResp)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if comResp.Code != 200 {
|
|
return errors.New(comResp.Message)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Move 移动文件
|
|
// srcDir: 源目录
|
|
// destDir: 目标目录
|
|
// names: 文件名
|
|
func (c *Client) Move(srcDir, destDir string, names []string) error {
|
|
if !c.isLogin() {
|
|
return errors.New("not login yet")
|
|
}
|
|
body := `{
|
|
"src_dir": "` + srcDir + `",
|
|
"dst_dir": "` + destDir + `",
|
|
"names": ["` + strings.Join(names, `","`) + `"]
|
|
}`
|
|
|
|
respByts, err := do("POST", c.base+"/api/fs/move", bytes.NewBufferString(body), c.token, &RequestOptions{
|
|
Insecure: c.inscure,
|
|
Timeout: c.timeout,
|
|
RetryTimes: 0,
|
|
MaxRetryTimes: 3,
|
|
RetryIntervalSecond: 5,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
comResp := &CommonResp{}
|
|
err = json.Unmarshal(respByts, comResp)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if comResp.Code != 200 {
|
|
return errors.New(comResp.Message)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Client) batchRename(endpoint, srcDir string, nameKV map[string]string) error {
|
|
if !c.isLogin() {
|
|
return errors.New("not login yet")
|
|
}
|
|
kvs := make([]string, len(nameKV))
|
|
for k, v := range nameKV {
|
|
kvs = append(kvs, `"`+k+`":"`+v+`"`)
|
|
}
|
|
|
|
body := `{
|
|
"src_dir": "` + srcDir + `",
|
|
"rename_objects": [{` + strings.Join(kvs, `,`) + `}]
|
|
}`
|
|
|
|
respByts, err := do("POST", c.base+endpoint, bytes.NewBufferString(body), c.token, &RequestOptions{
|
|
Insecure: c.inscure,
|
|
Timeout: c.timeout,
|
|
RetryTimes: 0,
|
|
MaxRetryTimes: 3,
|
|
RetryIntervalSecond: 5,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
comResp := &CommonResp{}
|
|
err = json.Unmarshal(respByts, comResp)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if comResp.Code != 200 {
|
|
return errors.New(comResp.Message)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// RegexRename 正则重命名
|
|
// srcDir: 源目录
|
|
// srcName: 源文件名正则匹配表达式
|
|
// newName: 新文件名正则引用表达式
|
|
func (c *Client) RegexRename(srcDir string, regexKV map[string]string) error {
|
|
return c.batchRename("/api/fs/regex_rename", srcDir, regexKV)
|
|
}
|
|
|
|
// BatchRename 批量重命名
|
|
// BatchRename renames multiple files in a given source directory.
|
|
//
|
|
// Parameters:
|
|
// - srcDir: the source directory where the files are located.
|
|
// - batchKV: a map containing the source file names as keys and the new file names as values.
|
|
//
|
|
// Returns:
|
|
// - error: an error if the batch rename operation fails.
|
|
func (c *Client) BatchRename(srcDir string, batchKV map[string]string) error {
|
|
return c.batchRename("/api/fs/batch_rename", srcDir, batchKV)
|
|
}
|
|
|
|
// Dirs 获取目录
|
|
// Dirs retrieves a list of directories from the server.
|
|
//
|
|
// It takes the following parameters:
|
|
// - path: the path of the directory to retrieve.
|
|
// - dirPassword: the password for the directory.
|
|
// - forceRoot: a flag indicating whether to the root directory.
|
|
//
|
|
// It returns a slice of Dir structs and an error.
|
|
func (c *Client) Dirs(path, dirPassword string, forceRoot bool) ([]Dir, error) {
|
|
if !c.isLogin() {
|
|
return nil, errors.New("not login yet")
|
|
}
|
|
body := `{
|
|
"path": "` + path + `",
|
|
"password": "` + dirPassword + `",
|
|
"force_root": ` + strconv.FormatBool(forceRoot) + `
|
|
}`
|
|
|
|
respByts, err := do("POST", c.base+"/api/fs/dirs", bytes.NewBufferString(body), c.token, &RequestOptions{
|
|
Insecure: c.inscure,
|
|
Timeout: c.timeout,
|
|
RetryTimes: 0,
|
|
MaxRetryTimes: 3,
|
|
RetryIntervalSecond: 5,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dirsResp := &DirsResp{}
|
|
err = json.Unmarshal(respByts, dirsResp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if dirsResp.Code != 200 {
|
|
return nil, errors.New(dirsResp.Message)
|
|
}
|
|
return dirsResp.Data, nil
|
|
}
|
|
|
|
// List 列出文件目录
|
|
// List returns a list of files in the specified directory.
|
|
//
|
|
// Parameters:
|
|
// - path: the path of the directory.
|
|
// - dirPassword: the password of the directory (if applicable).
|
|
// - pageNum: the page number for pagination.
|
|
// - pageSize: the number of files per page.
|
|
// - refresh: indicates whether to refresh the file list.
|
|
//
|
|
// Returns:
|
|
// - a slice of File structs representing the files in the directory.
|
|
// - an error if any error occurs.
|
|
func (c *Client) List(path, dirPassword string, pageNum, pageSize int, refresh bool) ([]File, error) {
|
|
if !c.isLogin() {
|
|
return nil, errors.New("not login yet")
|
|
}
|
|
body := `{
|
|
"path": "` + path + `",
|
|
"password": "` + dirPassword + `",
|
|
"page_num": ` + strconv.Itoa(pageNum) + `,
|
|
"per_page": ` + strconv.Itoa(pageSize) + `,
|
|
"refresh": ` + strconv.FormatBool(refresh) + `
|
|
}`
|
|
|
|
respByts, err := do("POST", c.base+"/api/fs/list", bytes.NewBufferString(body), c.token, &RequestOptions{
|
|
Insecure: c.inscure,
|
|
Timeout: c.timeout,
|
|
RetryTimes: 0,
|
|
MaxRetryTimes: 3,
|
|
RetryIntervalSecond: 5,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
listResp := &ListResp{}
|
|
err = json.Unmarshal(respByts, listResp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if listResp.Code != 200 {
|
|
return nil, errors.New(listResp.Message)
|
|
}
|
|
return listResp.Data.Content, nil
|
|
}
|
|
|
|
// Get 获取某个文件/目录信息
|
|
// Get retrieves a file or directory from the server.
|
|
//
|
|
// Parameters:
|
|
// - path: the path of the file or directory.
|
|
// - dirPassword: the password of the directory (if applicable).
|
|
//
|
|
// Returns:
|
|
// - a File struct representing the file or directory.
|
|
// - an error if any error occurs.
|
|
func (c *Client) Get(path, dirPassword string) (*File, error) {
|
|
if !c.isLogin() {
|
|
return nil, errors.New("not login yet")
|
|
}
|
|
body := `{
|
|
"path": "` + path + `",
|
|
"password": "` + dirPassword + `"
|
|
}`
|
|
|
|
respByts, err := do("POST", c.base+"/api/fs/get", bytes.NewBufferString(body), c.token, &RequestOptions{
|
|
Insecure: c.inscure,
|
|
Timeout: c.timeout,
|
|
RetryTimes: 0,
|
|
MaxRetryTimes: 3,
|
|
RetryIntervalSecond: 5,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
getResp := &GetResp{}
|
|
err = json.Unmarshal(respByts, getResp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if getResp.Code != 200 {
|
|
return nil, errors.New(getResp.Message)
|
|
}
|
|
return getResp.Data, nil
|
|
}
|
|
|
|
// GetSettings 获取设置
|
|
// GetSettings retrieves the settings from the client.
|
|
//
|
|
// This function does not take any parameters.
|
|
// It returns a pointer to Settings and an error.
|
|
func (c *Client) GetSettings() (*Settings, error) {
|
|
if !c.isLogin() {
|
|
return nil, errors.New("not login yet")
|
|
}
|
|
respByts, err := do("GET", c.base+"/api/settings", nil, c.token, &RequestOptions{
|
|
Insecure: c.inscure,
|
|
Timeout: c.timeout,
|
|
RetryTimes: 0,
|
|
MaxRetryTimes: 3,
|
|
RetryIntervalSecond: 5,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
settingsResp := &SettingsResp{}
|
|
err = json.Unmarshal(respByts, settingsResp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if settingsResp.Code != 200 {
|
|
return nil, errors.New(settingsResp.Message)
|
|
}
|
|
return settingsResp.Data, nil
|
|
}
|