commit 8dcda4bfa6fa25bed6d6bed7d84757a46f2a5df7 Author: shiran Date: Tue Mar 11 18:02:52 2025 +0800 init diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..271f1b8 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/alist/client.go b/alist/client.go new file mode 100644 index 0000000..8d393ff --- /dev/null +++ b/alist/client.go @@ -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 +} diff --git a/alist/functions.go b/alist/functions.go new file mode 100644 index 0000000..429fccf --- /dev/null +++ b/alist/functions.go @@ -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) +} diff --git a/alist/resp.go b/alist/resp.go new file mode 100644 index 0000000..1be395c --- /dev/null +++ b/alist/resp.go @@ -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"` +} diff --git a/alist/type.go b/alist/type.go new file mode 100644 index 0000000..cacf7ce --- /dev/null +++ b/alist/type.go @@ -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 +} diff --git a/cmd/download/download.go b/cmd/download/download.go new file mode 100644 index 0000000..f99dd4e --- /dev/null +++ b/cmd/download/download.go @@ -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 +} diff --git a/cmd/update/update.go b/cmd/update/update.go new file mode 100644 index 0000000..788932f --- /dev/null +++ b/cmd/update/update.go @@ -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) + } + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..876dd76 --- /dev/null +++ b/go.mod @@ -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 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..cfeae09 --- /dev/null +++ b/go.sum @@ -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= diff --git a/mods/123 b/mods/123 new file mode 100644 index 0000000..d800886 --- /dev/null +++ b/mods/123 @@ -0,0 +1 @@ +123 \ No newline at end of file