init
This commit is contained in:
Generated
+8
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/alistControl.iml" filepath="$PROJECT_DIR$/.idea/alistControl.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
+638
@@ -0,0 +1,638 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package alistsdk
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
DEFAULT_USERAGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36"
|
||||
DEFAULT_TIMEOUT = 30
|
||||
)
|
||||
|
||||
func do(method string, url string, body io.Reader, token string, options *RequestOptions) ([]byte, error) {
|
||||
client := &http.Client{
|
||||
Timeout: time.Duration(func() int {
|
||||
if options.Timeout > 0 {
|
||||
return options.Timeout
|
||||
} else {
|
||||
return DEFAULT_TIMEOUT
|
||||
}
|
||||
}()) * time.Second,
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: options.Insecure},
|
||||
},
|
||||
}
|
||||
request, err := http.NewRequest(method, url, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
request.Header.Set("Content-Type", "application/json; charset=utf-8")
|
||||
request.Header.Set("Authorization", token)
|
||||
request.Header.Set("User-Agent", DEFAULT_USERAGENT)
|
||||
resp, err := client.Do(request)
|
||||
if err != nil {
|
||||
if options.RetryTimes < options.MaxRetryTimes {
|
||||
options.RetryTimes++
|
||||
time.Sleep(time.Duration(options.RetryIntervalSecond) * time.Second)
|
||||
return do(method, url, body, token, options) // retry
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
return io.ReadAll(resp.Body)
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package alistsdk
|
||||
|
||||
type CommonResp struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type LoginResp struct {
|
||||
CommonResp
|
||||
Data struct {
|
||||
Token string `json:"token"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type Login2FAResp struct {
|
||||
CommonResp
|
||||
Data struct {
|
||||
QR string `json:"qr"`
|
||||
Secret string `json:"secret"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type UserInfoResp struct {
|
||||
CommonResp
|
||||
Data *User `json:"data"`
|
||||
}
|
||||
|
||||
type DirsResp struct {
|
||||
CommonResp
|
||||
Data []Dir `json:"data"`
|
||||
}
|
||||
|
||||
type ListResp struct {
|
||||
CommonResp
|
||||
Data struct {
|
||||
Content []File `json:"content"`
|
||||
Total int `json:"total"`
|
||||
Readme string `json:"readme"`
|
||||
Write bool `json:"write"`
|
||||
Provider string `json:"provider"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type GetResp struct {
|
||||
CommonResp
|
||||
Data *File `json:"data"`
|
||||
}
|
||||
|
||||
type SettingsResp struct {
|
||||
CommonResp
|
||||
Data *Settings `json:"data"`
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package alistsdk
|
||||
|
||||
type Dir struct {
|
||||
Name string `json:"name"`
|
||||
Modified string `json:"modified"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
ID int `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Salt string `json:"Salt"`
|
||||
Password string `json:"password"`
|
||||
BasePath string `json:"base_path"`
|
||||
Role int `json:"role"`
|
||||
Disabled bool `json:"disabled"`
|
||||
Permission int `json:"permission"`
|
||||
SSOID string `json:"sso_id"`
|
||||
OTP bool `json:"otp"`
|
||||
}
|
||||
|
||||
type File struct {
|
||||
Name string `json:"name"`
|
||||
Modified string `json:"modified"`
|
||||
Size int64 `json:"size"`
|
||||
IsDir bool `json:"is_dir"`
|
||||
Sign string `json:"sign"`
|
||||
Thumb string `json:"thumb"`
|
||||
Type int `json:"type"`
|
||||
RawURL string `json:"raw_url"`
|
||||
}
|
||||
|
||||
type Settings struct {
|
||||
AllowIndexed string `json:"allow_indexed"`
|
||||
AllowMounted string `json:"allow_mounted"`
|
||||
Announcement string `json:"announcement"`
|
||||
AudioAutoplay string `json:"audio_autoplay"`
|
||||
AudioCover string `json:"audio_cover"`
|
||||
AutoUpdateIndex string `json:"auto_update_index"`
|
||||
DefaultPageSize string `json:"default_page_size"`
|
||||
ExternalPreviews string `json:"external_previews"`
|
||||
Favicon string `json:"favicon"`
|
||||
FilenameCharMapping string `json:"filename_char_mapping"`
|
||||
ForwardDrectLinkParams string `json:"forward_drect_link_params"`
|
||||
HideFiles string `json:"hide_files"`
|
||||
HomeContainer string `json:"home_container"`
|
||||
HomeIcon string `json:"home_icon"`
|
||||
IframePreviews string `json:"iframe_previews"`
|
||||
Logo string `json:"logo"`
|
||||
MainColor string `json:"main_color"`
|
||||
OCRAPI string `json:"ocr_api"`
|
||||
PackageDownload string `json:"package_download"`
|
||||
PaginationType string `json:"pagination_type"`
|
||||
RobotsTXT string `json:"robots_txt"`
|
||||
SearchIndex string `json:"search_index"`
|
||||
SettingsLayout string `json:"settings_layout"`
|
||||
SiteTitle string `json:"site_title"`
|
||||
SSOLoginEnabled string `json:"sso_login_enabled"`
|
||||
SSOLoginPlatform string `json:"sso_login_platform"`
|
||||
Version string `json:"version"`
|
||||
VideoAutoplay string `json:"video_autoplay"`
|
||||
}
|
||||
|
||||
type RequestOptions struct {
|
||||
Timeout int
|
||||
Insecure bool
|
||||
RetryTimes int
|
||||
RetryIntervalSecond int
|
||||
MaxRetryTimes int
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"alistControl/alist"
|
||||
"fmt"
|
||||
"github.com/schollz/progressbar/v3"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func main() {
|
||||
client := alistsdk.NewClient("https://alist-home.s1f.ren", "kyou", "WFBDGnBfjjkVowen2", false, 200)
|
||||
user, err := client.Login()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(user)
|
||||
path := "/游戏/我的世界/常青藤Mods"
|
||||
list, err := client.List(path, "", 1, 100, false)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
// 获取当前文件夹下 modes 目录的的文件列表,判断是否存在,不存在则上传
|
||||
currentDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
fmt.Println("无法获取当前工作目录:", err)
|
||||
return
|
||||
}
|
||||
// 构建mods文件夹的路径
|
||||
modsPath := currentDir + "/mods"
|
||||
// 读取mods文件夹中的所有文件和子目录
|
||||
files, err := os.ReadDir(modsPath)
|
||||
if err != nil {
|
||||
fmt.Println("无法读取mods文件夹:", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 打印所有文件和子目录的名称
|
||||
for _, alistFile := range list {
|
||||
hasFile := false
|
||||
for _, file := range files {
|
||||
fileInfo, _ := file.Info()
|
||||
fileSize := fileInfo.Size()
|
||||
if file.Name() == alistFile.Name && fileSize == alistFile.Size {
|
||||
fmt.Println("文件已存在")
|
||||
hasFile = true
|
||||
}
|
||||
if !hasFile {
|
||||
fmt.Printf("%s文件不存在,开始下载\n", file.Name())
|
||||
// 获取mods文件夹中的文件路径
|
||||
filePath := modsPath + "/" + file.Name()
|
||||
fileUrl := client.GetFileDownloadUrl(path+"/"+file.Name(), user.BasePath)
|
||||
err = downloadFile(filePath, fileUrl)
|
||||
if err != nil {
|
||||
fmt.Println("下载文件失败:", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// downloadFile 下载文件并保存到指定路径,显示文件名和进度条
|
||||
func downloadFile(filePath string, fileUrl string) error {
|
||||
// 发送HTTP GET请求
|
||||
resp, err := http.Get(fileUrl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 获取文件名
|
||||
fileName := filepath.Base(filePath)
|
||||
fmt.Printf("Downloading: %s\n", fileName)
|
||||
|
||||
// 创建文件
|
||||
out, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
// 获取文件大小
|
||||
totalSize := resp.ContentLength
|
||||
|
||||
// 创建进度条
|
||||
bar := progressbar.NewOptions64(
|
||||
totalSize,
|
||||
progressbar.OptionSetDescription("Progress"),
|
||||
progressbar.OptionSetWidth(30),
|
||||
progressbar.OptionShowBytes(true),
|
||||
progressbar.OptionShowCount(),
|
||||
progressbar.OptionSetTheme(progressbar.Theme{
|
||||
Saucer: "=",
|
||||
SaucerHead: ">",
|
||||
SaucerPadding: " ",
|
||||
BarStart: "[",
|
||||
BarEnd: "]",
|
||||
}),
|
||||
)
|
||||
|
||||
// 创建带进度条的writer
|
||||
writer := io.MultiWriter(out, bar)
|
||||
|
||||
// 将响应体复制到文件中并更新进度
|
||||
_, err = io.Copy(writer, resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 确保进度条达到100%
|
||||
bar.Finish()
|
||||
fmt.Println() // 换行
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"alistControl/alist"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
client := alistsdk.NewClient("https://alist-home.s1f.ren", "kyou", "WFBDGnBfjjkVowen2", false, 200)
|
||||
user, err := client.Login()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(user)
|
||||
path := "/游戏/我的世界/常青藤Mods"
|
||||
list, err := client.List(path, "", 1, 100, false)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
// 获取当前文件夹下 modes 目录的的文件列表,判断是否存在,不存在则上传
|
||||
currentDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
fmt.Println("无法获取当前工作目录:", err)
|
||||
return
|
||||
}
|
||||
// 构建mods文件夹的路径
|
||||
modsPath := currentDir + "/mods"
|
||||
// 读取mods文件夹中的所有文件和子目录
|
||||
files, err := os.ReadDir(modsPath)
|
||||
if err != nil {
|
||||
fmt.Println("无法读取mods文件夹:", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 打印所有文件和子目录的名称
|
||||
for _, file := range files {
|
||||
hasFile := false
|
||||
fileInfo, _ := file.Info()
|
||||
fileSize := fileInfo.Size()
|
||||
for _, alistFile := range list {
|
||||
if file.Name() == alistFile.Name && fileSize == alistFile.Size {
|
||||
fmt.Println("文件已存在")
|
||||
hasFile = true
|
||||
}
|
||||
}
|
||||
if !hasFile {
|
||||
fmt.Printf("%s文件不存在,开始上传\n", file.Name())
|
||||
// 获取mods文件夹中的文件路径
|
||||
filePath := modsPath + "/" + file.Name()
|
||||
client.PutUpload(filePath, path+"/"+file.Name(), false)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
module alistControl
|
||||
|
||||
go 1.23.7
|
||||
|
||||
require github.com/schollz/progressbar/v3 v3.18.0
|
||||
|
||||
require (
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
golang.org/x/term v0.28.0 // indirect
|
||||
)
|
||||
@@ -0,0 +1,22 @@
|
||||
github.com/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7mk9/PwM=
|
||||
github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0iwWCOK1q10rlY=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/schollz/progressbar/v3 v3.18.0 h1:uXdoHABRFmNIjUfte/Ex7WtuyVslrw2wVPQmCN62HpA=
|
||||
github.com/schollz/progressbar/v3 v3.18.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
|
||||
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
Reference in New Issue
Block a user