github.com/qjfoidnh/BaiduPCS-Go@v0.0.0-20231011165705-caa18a3765f3/main.go (about)

     1  package main
     2  
     3  import (
     4  	"encoding/hex"
     5  	"fmt"
     6  	"os"
     7  	"os/exec"
     8  	"path"
     9  	"path/filepath"
    10  	"runtime"
    11  	"sort"
    12  	"strconv"
    13  	"strings"
    14  	"unicode"
    15  
    16  	"github.com/olekukonko/tablewriter"
    17  	"github.com/peterh/liner"
    18  	"github.com/qjfoidnh/BaiduPCS-Go/baidupcs"
    19  	"github.com/qjfoidnh/BaiduPCS-Go/internal/pcscommand"
    20  	"github.com/qjfoidnh/BaiduPCS-Go/internal/pcsconfig"
    21  	"github.com/qjfoidnh/BaiduPCS-Go/internal/pcsfunctions/pcsdownload"
    22  	_ "github.com/qjfoidnh/BaiduPCS-Go/internal/pcsinit"
    23  	"github.com/qjfoidnh/BaiduPCS-Go/internal/pcsupdate"
    24  	"github.com/qjfoidnh/BaiduPCS-Go/pcsliner"
    25  	"github.com/qjfoidnh/BaiduPCS-Go/pcsliner/args"
    26  	"github.com/qjfoidnh/BaiduPCS-Go/pcstable"
    27  	"github.com/qjfoidnh/BaiduPCS-Go/pcsutil"
    28  	"github.com/qjfoidnh/BaiduPCS-Go/pcsutil/checksum"
    29  	"github.com/qjfoidnh/BaiduPCS-Go/pcsutil/converter"
    30  	"github.com/qjfoidnh/BaiduPCS-Go/pcsutil/escaper"
    31  	"github.com/qjfoidnh/BaiduPCS-Go/pcsutil/getip"
    32  	"github.com/qjfoidnh/BaiduPCS-Go/pcsutil/pcstime"
    33  	"github.com/qjfoidnh/BaiduPCS-Go/pcsverbose"
    34  	"github.com/urfave/cli"
    35  )
    36  
    37  const (
    38  	// NameShortDisplayNum 文件名缩略显示长度
    39  	NameShortDisplayNum = 16
    40  
    41  	cryptoDescription = `
    42  	可用的方法 <method>:
    43  		aes-128-ctr, aes-192-ctr, aes-256-ctr,
    44  		aes-128-cfb, aes-192-cfb, aes-256-cfb,
    45  		aes-128-ofb, aes-192-ofb, aes-256-ofb.
    46  
    47  	密钥 <key>:
    48  		aes-128 对应key长度为16, aes-192 对应key长度为24, aes-256 对应key长度为32,
    49  		如果key长度不符合, 则自动修剪key, 舍弃超出长度的部分, 长度不足的部分用'\0'填充.
    50  
    51  	GZIP <disable-gzip>:
    52  		在文件加密之前, 启用GZIP压缩文件; 文件解密之后启用GZIP解压缩文件, 默认启用,
    53  		如果不启用, 则无法检测文件是否解密成功, 解密文件时会保留源文件, 避免解密失败造成文件数据丢失.`
    54  )
    55  
    56  var (
    57  	// Version 版本号
    58  	//Version = "v3.9.4-devel"
    59  	Version = "v3.9.5-devel"
    60  
    61  	historyFilePath = filepath.Join(pcsconfig.GetConfigDir(), "pcs_command_history.txt")
    62  	reloadFn        = func(c *cli.Context) error {
    63  		err := pcsconfig.Config.Reload()
    64  		if err != nil {
    65  			fmt.Printf("重载配置错误: %s\n", err)
    66  		}
    67  		return nil
    68  	}
    69  	saveFunc = func(c *cli.Context) error {
    70  		err := pcsconfig.Config.Save()
    71  		if err != nil {
    72  			fmt.Printf("保存配置错误: %s\n", err)
    73  		}
    74  		return nil
    75  	}
    76  
    77  	isCli bool
    78  )
    79  
    80  func init() {
    81  	pcsutil.ChWorkDir()
    82  
    83  	err := pcsconfig.Config.Init()
    84  	switch err {
    85  	case nil:
    86  	case pcsconfig.ErrConfigFileNoPermission, pcsconfig.ErrConfigContentsParseError:
    87  		fmt.Fprintf(os.Stderr, "FATAL ERROR: config file error: %s\n", err)
    88  		os.Exit(1)
    89  	default:
    90  		fmt.Printf("WARNING: config init error: %s\n", err)
    91  	}
    92  }
    93  
    94  func main() {
    95  	defer pcsconfig.Config.Close()
    96  
    97  	app := cli.NewApp()
    98  	app.Name = "BaiduPCS-Go"
    99  	app.Version = Version
   100  	app.Author = "qjfoidnh/BaiduPCS-Go: https://github.com/qjfoidnh/BaiduPCS-Go"
   101  	app.Copyright = "(c) 2016-2020 iikira."
   102  	app.Usage = "百度网盘客户端 for " + runtime.GOOS + "/" + runtime.GOARCH
   103  	app.Description = `BaiduPCS-Go 使用Go语言编写的百度网盘命令行客户端, 为操作百度网盘, 提供实用功能.
   104  	具体功能, 参见 COMMANDS 列表
   105  
   106  	特色:
   107  		网盘内列出文件和目录, 支持通配符匹配路径;
   108  		下载网盘内文件, 支持网盘内目录 (文件夹) 下载, 支持多个文件或目录下载, 支持断点续传和高并发高速下载.
   109  
   110  	---------------------------------------------------
   111  	前往 https://github.com/qjfoidnh/BaiduPCS-Go 以获取更多帮助信息!
   112  	前往 https://github.com/qjfoidnh/BaiduPCS-Go/releases 以获取程序更新信息!
   113  	---------------------------------------------------
   114  
   115  	交流反馈:
   116  		提交Issue: https://github.com/qjfoidnh/BaiduPCS-Go/issues
   117  		邮箱: qjfoidnh@126.com`
   118  
   119  	app.Flags = []cli.Flag{
   120  		cli.BoolFlag{
   121  			Name:        "verbose",
   122  			Usage:       "启用调试",
   123  			EnvVar:      pcsverbose.EnvVerbose,
   124  			Destination: &pcsverbose.IsVerbose,
   125  		},
   126  	}
   127  	app.Action = func(c *cli.Context) {
   128  		if c.NArg() != 0 {
   129  			fmt.Printf("未找到命令: %s\n运行命令 %s help 获取帮助\n", c.Args().Get(0), app.Name)
   130  			return
   131  		}
   132  
   133  		isCli = true
   134  		pcsverbose.Verbosef("VERBOSE: 这是一条调试信息\n\n")
   135  
   136  		var (
   137  			line = pcsliner.NewLiner()
   138  			err  error
   139  		)
   140  
   141  		line.History, err = pcsliner.NewLineHistory(historyFilePath)
   142  		if err != nil {
   143  			fmt.Printf("警告: 读取历史命令文件错误, %s\n", err)
   144  		}
   145  
   146  		line.ReadHistory()
   147  		defer func() {
   148  			line.DoWriteHistory()
   149  			line.Close()
   150  		}()
   151  
   152  		// tab 自动补全命令
   153  		line.State.SetCompleter(func(line string) (s []string) {
   154  			var (
   155  				lineArgs                   = args.Parse(line)
   156  				numArgs                    = len(lineArgs)
   157  				acceptCompleteFileCommands = []string{
   158  					"cd", "cp", "download", "export", "fixmd5", "locate", "ls", "meta", "mkdir", "mv", "rapidupload", "rm", "setastoken", "share", "transfer", "tree", "upload",
   159  				}
   160  				closed = strings.LastIndex(line, " ") == len(line)-1
   161  			)
   162  
   163  			for _, cmd := range app.Commands {
   164  				for _, name := range cmd.Names() {
   165  					if !strings.HasPrefix(name, line) {
   166  						continue
   167  					}
   168  
   169  					s = append(s, name+" ")
   170  				}
   171  			}
   172  
   173  			switch numArgs {
   174  			case 0:
   175  				return
   176  			case 1:
   177  				if !closed {
   178  					return
   179  				}
   180  			}
   181  
   182  			thisCmd := app.Command(lineArgs[0])
   183  			if thisCmd == nil {
   184  				return
   185  			}
   186  
   187  			if !pcsutil.ContainsString(acceptCompleteFileCommands, thisCmd.FullName()) {
   188  				return
   189  			}
   190  
   191  			var (
   192  				activeUser  = pcsconfig.Config.ActiveUser()
   193  				pcs         = pcsconfig.Config.ActiveUserBaiduPCS()
   194  				runeFunc    = unicode.IsSpace
   195  				pcsRuneFunc = func(r rune) bool {
   196  					switch r {
   197  					case '\'', '"':
   198  						return true
   199  					}
   200  					return unicode.IsSpace(r)
   201  				}
   202  				targetPath string
   203  			)
   204  
   205  			if !closed {
   206  				targetPath = lineArgs[numArgs-1]
   207  				escaper.EscapeStringsByRuneFunc(lineArgs[:numArgs-1], runeFunc) // 转义
   208  			} else {
   209  				escaper.EscapeStringsByRuneFunc(lineArgs, runeFunc)
   210  			}
   211  
   212  			switch {
   213  			case targetPath == "." || strings.HasSuffix(targetPath, "/."):
   214  				s = append(s, line+"/")
   215  				return
   216  			case targetPath == ".." || strings.HasSuffix(targetPath, "/.."):
   217  				s = append(s, line+"/")
   218  				return
   219  			}
   220  
   221  			var (
   222  				targetDir string
   223  				isAbs     = path.IsAbs(targetPath)
   224  				isDir     = strings.LastIndex(targetPath, "/") == len(targetPath)-1
   225  			)
   226  
   227  			if isAbs {
   228  				targetDir = path.Dir(targetPath)
   229  			} else {
   230  				targetDir = path.Join(activeUser.Workdir, targetPath)
   231  				if !isDir {
   232  					targetDir = path.Dir(targetDir)
   233  				}
   234  			}
   235  			files, err := pcs.CacheFilesDirectoriesList(targetDir, baidupcs.DefaultOrderOptions)
   236  			if err != nil {
   237  				return
   238  			}
   239  
   240  			// fmt.Println("-", targetDir, targetPath, "-")
   241  
   242  			for _, file := range files {
   243  				if file == nil {
   244  					continue
   245  				}
   246  
   247  				var (
   248  					appendLine string
   249  				)
   250  
   251  				// 已经有的情况
   252  				if !closed {
   253  					if !strings.HasPrefix(file.Path, path.Clean(path.Join(targetDir, path.Base(targetPath)))) {
   254  						if path.Base(targetDir) == path.Base(targetPath) {
   255  							appendLine = strings.Join(append(lineArgs[:numArgs-1], escaper.EscapeByRuneFunc(path.Join(targetPath, file.Filename), pcsRuneFunc)), " ")
   256  							goto handle
   257  						}
   258  						// fmt.Println(file.Path, targetDir, targetPath)
   259  						continue
   260  					}
   261  					// fmt.Println(path.Clean(path.Join(path.Dir(targetPath), file.Filename)), targetPath, file.Filename)
   262  					appendLine = strings.Join(append(lineArgs[:numArgs-1], escaper.EscapeByRuneFunc(path.Clean(path.Join(path.Dir(targetPath), file.Filename)), pcsRuneFunc)), " ")
   263  					goto handle
   264  				}
   265  				// 没有的情况
   266  				appendLine = strings.Join(append(lineArgs, escaper.EscapeByRuneFunc(file.Filename, pcsRuneFunc)), " ")
   267  				goto handle
   268  
   269  			handle:
   270  				if file.Isdir {
   271  					s = append(s, appendLine+"/")
   272  					continue
   273  				}
   274  				s = append(s, appendLine+" ")
   275  				continue
   276  			}
   277  
   278  			return
   279  		})
   280  
   281  		fmt.Printf("提示: 方向键上下可切换历史命令.\n")
   282  		fmt.Printf("提示: Ctrl + A / E 跳转命令 首 / 尾.\n")
   283  		fmt.Printf("提示: 输入 help 获取帮助.\n")
   284  
   285  		for {
   286  			var (
   287  				prompt     string
   288  				activeUser = pcsconfig.Config.ActiveUser()
   289  			)
   290  
   291  			if activeUser.Name != "" {
   292  				// 格式: BaiduPCS-Go:<工作目录> <百度ID>$
   293  				// 工作目录太长时, 会自动缩略
   294  				prompt = app.Name + ":" + converter.ShortDisplay(path.Base(activeUser.Workdir), NameShortDisplayNum) + " " + activeUser.Name + "$ "
   295  			} else {
   296  				// BaiduPCS-Go >
   297  				prompt = app.Name + " > "
   298  			}
   299  
   300  			commandLine, err := line.State.Prompt(prompt)
   301  			switch err {
   302  			case liner.ErrPromptAborted:
   303  				return
   304  			case nil:
   305  				// continue
   306  			default:
   307  				fmt.Println(err)
   308  				return
   309  			}
   310  
   311  			line.State.AppendHistory(commandLine)
   312  
   313  			cmdArgs := args.Parse(commandLine)
   314  			if len(cmdArgs) == 0 {
   315  				continue
   316  			}
   317  
   318  			s := []string{os.Args[0]}
   319  			s = append(s, cmdArgs...)
   320  
   321  			// 恢复原始终端状态
   322  			// 防止运行命令时程序被结束, 终端出现异常
   323  			line.Pause()
   324  			c.App.Run(s)
   325  			line.Resume()
   326  		}
   327  	}
   328  
   329  	app.Commands = []cli.Command{
   330  		{
   331  			Name:     "run",
   332  			Usage:    "执行系统命令",
   333  			Category: "其他",
   334  			Action: func(c *cli.Context) error {
   335  				if c.NArg() == 0 {
   336  					cli.ShowCommandHelp(c, c.Command.Name)
   337  					return nil
   338  				}
   339  
   340  				cmd := exec.Command(c.Args().First(), c.Args().Tail()...)
   341  				cmd.Stdout = os.Stdout
   342  				cmd.Stdin = os.Stdin
   343  				cmd.Stderr = os.Stderr
   344  
   345  				err := cmd.Run()
   346  				if err != nil {
   347  					fmt.Println(err)
   348  				}
   349  
   350  				return nil
   351  			},
   352  		},
   353  		{
   354  			Name:  "env",
   355  			Usage: "显示程序环境变量",
   356  			Description: `
   357  	BAIDUPCS_GO_CONFIG_DIR: 配置文件路径,
   358  	BAIDUPCS_GO_VERBOSE: 是否启用调试.
   359  `,
   360  			Category: "其他",
   361  			Action: func(c *cli.Context) error {
   362  				envStr := "%s=\"%s\"\n"
   363  				envVar, ok := os.LookupEnv(pcsverbose.EnvVerbose)
   364  				if ok {
   365  					fmt.Printf(envStr, pcsverbose.EnvVerbose, envVar)
   366  				} else {
   367  					fmt.Printf(envStr, pcsverbose.EnvVerbose, "0")
   368  				}
   369  
   370  				envVar, ok = os.LookupEnv(pcsconfig.EnvConfigDir)
   371  				if ok {
   372  					fmt.Printf(envStr, pcsconfig.EnvConfigDir, envVar)
   373  				} else {
   374  					fmt.Printf(envStr, pcsconfig.EnvConfigDir, pcsconfig.GetConfigDir())
   375  				}
   376  
   377  				return nil
   378  			},
   379  		},
   380  		{
   381  			Name:     "update",
   382  			Usage:    "检测程序更新",
   383  			Category: "其他",
   384  			Action: func(c *cli.Context) error {
   385  				if c.IsSet("y") {
   386  					if !c.Bool("y") {
   387  						return nil
   388  					}
   389  				}
   390  				pcsupdate.CheckUpdate(app.Version, c.Bool("y"))
   391  				return nil
   392  			},
   393  			Flags: []cli.Flag{
   394  				cli.BoolFlag{
   395  					Name:  "y",
   396  					Usage: "确认更新",
   397  				},
   398  			},
   399  		},
   400  		{
   401  			Name:  "login",
   402  			Usage: "登录百度账号",
   403  			Description: `
   404  	示例:
   405  		BaiduPCS-Go login
   406  		BaiduPCS-Go login -username=liuhua
   407  		BaiduPCS-Go login -bduss=123456789 -stoken=atahsrweoog
   408  		BaiduPCS-Go login -cookies="BDUSS=xxxxx; BAIDUID=yyyyyy; STOKEN=zzzzz; ...."
   409  
   410  	常规登录:
   411  		按提示一步一步来即可.
   412  
   413  	百度BDUSS获取方法:
   414  		百度搜索: 获取百度BDUSS
   415  		
   416  	百度Cookies获取办法:
   417  	以Chrome为例,登录到自己的百度网盘主页,F12,然后切换到Network标签,刷新页面,Network标签下会刷出一大堆东西
   418  	找到第一条,点击,看到右侧出现的详情,往下翻到Cookies: xxxx; xxxxx; xxx...这样的字段,从冒号后(没有空格)一直复制到字段末尾`,
   419  			Category: "百度帐号",
   420  			Before:   reloadFn,
   421  			After:    saveFunc,
   422  			Action: func(c *cli.Context) error {
   423  				var bduss, ptoken, stoken, cookies string
   424  				if c.IsSet("cookies") {
   425  					cookies = c.String("cookies")
   426  				} else if c.IsSet("bduss") {
   427  					bduss = c.String("bduss")
   428  					ptoken = c.String("ptoken")
   429  					stoken = c.String("stoken")
   430  				} else if c.NArg() == 0 {
   431  					var err error
   432  					bduss, ptoken, stoken, cookies, err = pcscommand.RunLogin(c.String("username"), c.String("password"))
   433  					if err != nil {
   434  						fmt.Println(err)
   435  						return err
   436  					}
   437  				} else {
   438  					cli.ShowCommandHelp(c, c.Command.Name)
   439  					return nil
   440  				}
   441  
   442  				baidu, err := pcsconfig.Config.SetupUserByBDUSS(bduss, ptoken, stoken, cookies)
   443  				if err != nil {
   444  					fmt.Println(err)
   445  					return nil
   446  				}
   447  
   448  				fmt.Println("百度帐号登录成功:", baidu.Name)
   449  				return nil
   450  			},
   451  			Flags: []cli.Flag{
   452  				cli.StringFlag{
   453  					Name:  "username",
   454  					Usage: "登录百度帐号的用户名(手机号/邮箱/用户名)",
   455  				},
   456  				cli.StringFlag{
   457  					Name:  "password",
   458  					Usage: "登录百度帐号的用户名的密码",
   459  				},
   460  				cli.StringFlag{
   461  					Name:  "bduss",
   462  					Usage: "使用百度 BDUSS 来登录百度帐号",
   463  				},
   464  				cli.StringFlag{
   465  					Name:  "ptoken",
   466  					Usage: "百度 PTOKEN, 配合 -bduss 参数使用 (可选)",
   467  				},
   468  				cli.StringFlag{
   469  					Name:  "stoken",
   470  					Usage: "百度 STOKEN, 配合 -bduss 参数使用 (可选, 欲使用转存功能则必选)",
   471  				},
   472  				cli.StringFlag{
   473  					Name:  "cookies",
   474  					Usage: "使用百度 Cookies 来登录百度账号",
   475  				},
   476  			},
   477  		},
   478  		{
   479  			Name:  "su",
   480  			Usage: "切换百度帐号",
   481  			Description: `
   482  	切换已登录的百度帐号:
   483  	如果运行该条命令没有提供参数, 程序将会列出所有的百度帐号, 供选择切换.
   484  
   485  	示例:
   486  	BaiduPCS-Go su
   487  	BaiduPCS-Go su <uid or name>
   488  `,
   489  			Category: "百度帐号",
   490  			Before:   reloadFn,
   491  			After:    saveFunc,
   492  			Action: func(c *cli.Context) error {
   493  				if c.NArg() >= 2 {
   494  					cli.ShowCommandHelp(c, c.Command.Name)
   495  					return nil
   496  				}
   497  
   498  				numLogins := pcsconfig.Config.NumLogins()
   499  
   500  				if numLogins == 0 {
   501  					fmt.Printf("未设置任何百度帐号, 不能切换\n")
   502  					return nil
   503  				}
   504  
   505  				var (
   506  					inputData = c.Args().Get(0)
   507  					uid       uint64
   508  				)
   509  
   510  				if c.NArg() == 1 {
   511  					// 直接切换
   512  					uid, _ = strconv.ParseUint(inputData, 10, 64)
   513  				} else if c.NArg() == 0 {
   514  					// 输出所有帐号供选择切换
   515  					cli.HandleAction(app.Command("loglist").Action, c)
   516  
   517  					// 提示输入 index
   518  					var index string
   519  					fmt.Printf("输入要切换帐号的 # 值 > ")
   520  					_, err := fmt.Scanln(&index)
   521  					if err != nil {
   522  						return nil
   523  					}
   524  
   525  					if n, err := strconv.Atoi(index); err == nil && n >= 0 && n < numLogins {
   526  						uid = pcsconfig.Config.BaiduUserList[n].UID
   527  					} else {
   528  						fmt.Printf("切换用户失败, 请检查 # 值是否正确\n")
   529  						return nil
   530  					}
   531  				} else {
   532  					cli.ShowCommandHelp(c, c.Command.Name)
   533  				}
   534  
   535  				switchedUser, err := pcsconfig.Config.SwitchUser(&pcsconfig.BaiduBase{
   536  					Name: inputData,
   537  				})
   538  				if err != nil {
   539  					switchedUser, err = pcsconfig.Config.SwitchUser(&pcsconfig.BaiduBase{
   540  						UID: uid,
   541  					})
   542  					if err != nil {
   543  						fmt.Printf("切换用户失败, %s\n", err)
   544  						return nil
   545  					}
   546  				}
   547  
   548  				fmt.Printf("切换用户: %s\n", switchedUser.Name)
   549  				return nil
   550  			},
   551  		},
   552  		{
   553  			Name:        "logout",
   554  			Usage:       "退出百度帐号",
   555  			Description: "退出当前登录的百度帐号",
   556  			Category:    "百度帐号",
   557  			Before:      reloadFn,
   558  			After:       saveFunc,
   559  			Action: func(c *cli.Context) error {
   560  				if pcsconfig.Config.NumLogins() == 0 {
   561  					fmt.Println("未设置任何百度帐号, 不能退出")
   562  					return nil
   563  				}
   564  
   565  				var (
   566  					confirm    string
   567  					activeUser = pcsconfig.Config.ActiveUser()
   568  				)
   569  
   570  				if !c.Bool("y") {
   571  					fmt.Printf("确认退出百度帐号: %s ? (y/n) > ", activeUser.Name)
   572  					_, err := fmt.Scanln(&confirm)
   573  					if err != nil || (confirm != "y" && confirm != "Y") {
   574  						return err
   575  					}
   576  				}
   577  
   578  				deletedUser, err := pcsconfig.Config.DeleteUser(&pcsconfig.BaiduBase{
   579  					UID: activeUser.UID,
   580  				})
   581  				if err != nil {
   582  					fmt.Printf("退出用户 %s, 失败, 错误: %s\n", activeUser.Name, err)
   583  				}
   584  
   585  				fmt.Printf("退出用户成功, %s\n", deletedUser.Name)
   586  				return nil
   587  			},
   588  			Flags: []cli.Flag{
   589  				cli.BoolFlag{
   590  					Name:  "y",
   591  					Usage: "确认退出帐号",
   592  				},
   593  			},
   594  		},
   595  		{
   596  			Name:        "loglist",
   597  			Usage:       "列出帐号列表",
   598  			Description: "列出所有已登录的百度帐号",
   599  			Category:    "百度帐号",
   600  			Before:      reloadFn,
   601  			Action: func(c *cli.Context) error {
   602  				fmt.Println(pcsconfig.Config.BaiduUserList.String())
   603  				return nil
   604  			},
   605  		},
   606  		{
   607  			Name:        "setastoken",
   608  			Usage:       "设定当前账号的accessToken",
   609  			Description: `
   610  	设定当前登录帐号的accessToken:
   611  	若不使用秒传链接转存, 可不设定; accessToken申请及获取教程:
   612  	https://github.com/qjfoidnh/BaiduPCS-Go/wiki/accessToken%E8%8E%B7%E5%8F%96%E6%95%99%E7%A8%8B
   613  	注意accessToken的有效期为一个月, 过期后请按教程指导更新token
   614  
   615  	示例:
   616  	BaiduPCS-Go setastoken 156.182v9052tgf1006c89891bsfb2401974.YmKOAwBD9yGaG2s4p5NNkX4CXeIbJxx4hAxotfS.PyuHEs
   617  `,
   618  			Category:    "百度帐号",
   619  			Before:      reloadFn,
   620  			After:       saveFunc,
   621  			Action: func(c *cli.Context) error {
   622  				activeUser := pcsconfig.Config.ActiveUser()
   623  				if activeUser.UID == 0 {
   624  					fmt.Println("请先登录")
   625  					return nil
   626  				}
   627  				if c.NArg() >= 2 {
   628  					cli.ShowCommandHelp(c, c.Command.Name)
   629  					return nil
   630  				} else if c.NArg() == 0 {
   631  					cli.ShowCommandHelp(c, c.Command.Name)
   632  					return nil
   633  				}
   634  				activeUser.AccessToken = c.Args().Get(0)
   635  				pcsconfig.Config.ActiveUserBaiduPCS().SetaccessToken(c.Args().Get(0))
   636  				fmt.Printf("当前用户名: %s 成功设置accessToken: %s\n", activeUser.Name, activeUser.AccessToken)
   637  				return nil
   638  			},
   639  		},
   640  		{
   641  			Name:        "who",
   642  			Usage:       "获取当前帐号",
   643  			Description: "获取当前帐号的信息",
   644  			Category:    "百度帐号",
   645  			Before:      reloadFn,
   646  			Action: func(c *cli.Context) error {
   647  				activeUser := pcsconfig.Config.ActiveUser()
   648  				fmt.Printf("当前帐号 uid: %d, 用户名: %s, 性别: %s, 年龄: %.1f\n", activeUser.UID, activeUser.Name, activeUser.Sex, activeUser.Age)
   649  				return nil
   650  			},
   651  		},
   652  		{
   653  			Name:        "quota",
   654  			Usage:       "获取网盘配额",
   655  			Description: "获取网盘的总储存空间, 和已使用的储存空间",
   656  			Category:    "百度网盘",
   657  			Before:      reloadFn,
   658  			Action: func(c *cli.Context) error {
   659  				pcscommand.RunGetQuota()
   660  				return nil
   661  			},
   662  		},
   663  		{
   664  			Name:     "cd",
   665  			Category: "百度网盘",
   666  			Usage:    "切换工作目录",
   667  			Description: `
   668  	BaiduPCS-Go cd <目录, 绝对路径或相对路径>
   669  
   670  	示例:
   671  
   672  	切换 /我的资源 工作目录:
   673  	BaiduPCS-Go cd /我的资源
   674  
   675  	切换上级目录:
   676  	BaiduPCS-Go cd ..
   677  
   678  	切换根目录:
   679  	BaiduPCS-Go cd /
   680  
   681  	切换 /我的资源 工作目录, 并自动列出 /我的资源 下的文件和目录
   682  	BaiduPCS-Go cd -l 我的资源
   683  
   684  	使用通配符:
   685  	BaiduPCS-Go cd /我的*
   686  `,
   687  			Before: reloadFn,
   688  			After:  saveFunc,
   689  			Action: func(c *cli.Context) error {
   690  				if c.NArg() == 0 {
   691  					cli.ShowCommandHelp(c, c.Command.Name)
   692  					return nil
   693  				}
   694  
   695  				pcscommand.RunChangeDirectory(c.Args().Get(0), c.Bool("l"))
   696  
   697  				return nil
   698  			},
   699  			Flags: []cli.Flag{
   700  				cli.BoolFlag{
   701  					Name:  "l",
   702  					Usage: "切换工作目录后自动列出工作目录下的文件和目录",
   703  				},
   704  			},
   705  		},
   706  		{
   707  			Name:      "ls",
   708  			Aliases:   []string{"l", "ll"},
   709  			Usage:     "列出目录",
   710  			UsageText: app.Name + " ls <目录>",
   711  			Description: `
   712  	列出当前工作目录内的文件和目录, 或指定目录内的文件和目录
   713  
   714  	示例:
   715  
   716  	列出 我的资源 内的文件和目录
   717  	BaiduPCS-Go ls 我的资源
   718  
   719  	绝对路径
   720  	BaiduPCS-Go ls /我的资源
   721  
   722  	降序排序
   723  	BaiduPCS-Go ls -desc 我的资源
   724  
   725  	按文件大小降序排序
   726  	BaiduPCS-Go ls -size -desc 我的资源
   727  
   728  	使用通配符
   729  	BaiduPCS-Go ls /我的*
   730  `,
   731  			Category: "百度网盘",
   732  			Before:   reloadFn,
   733  			Action: func(c *cli.Context) error {
   734  				orderOptions := &baidupcs.OrderOptions{}
   735  				switch {
   736  				case c.IsSet("asc"):
   737  					orderOptions.Order = baidupcs.OrderAsc
   738  				case c.IsSet("desc"):
   739  					orderOptions.Order = baidupcs.OrderDesc
   740  				default:
   741  					orderOptions.Order = baidupcs.OrderAsc
   742  				}
   743  
   744  				switch {
   745  				case c.IsSet("time"):
   746  					orderOptions.By = baidupcs.OrderByTime
   747  				case c.IsSet("name"):
   748  					orderOptions.By = baidupcs.OrderByName
   749  				case c.IsSet("size"):
   750  					orderOptions.By = baidupcs.OrderBySize
   751  				default:
   752  					orderOptions.By = baidupcs.OrderByName
   753  				}
   754  
   755  				pcscommand.RunLs(c.Args().Get(0), &pcscommand.LsOptions{
   756  					Total: c.Bool("l") || c.Parent().Args().Get(0) == "ll",
   757  				}, orderOptions)
   758  
   759  				return nil
   760  			},
   761  			Flags: []cli.Flag{
   762  				cli.BoolFlag{
   763  					Name:  "l",
   764  					Usage: "详细显示",
   765  				},
   766  				cli.BoolFlag{
   767  					Name:  "asc",
   768  					Usage: "升序排序",
   769  				},
   770  				cli.BoolFlag{
   771  					Name:  "desc",
   772  					Usage: "降序排序",
   773  				},
   774  				cli.BoolFlag{
   775  					Name:  "time",
   776  					Usage: "根据时间排序",
   777  				},
   778  				cli.BoolFlag{
   779  					Name:  "name",
   780  					Usage: "根据文件名排序",
   781  				},
   782  				cli.BoolFlag{
   783  					Name:  "size",
   784  					Usage: "根据大小排序",
   785  				},
   786  			},
   787  		},
   788  		{
   789  			Name:      "search",
   790  			Aliases:   []string{"s"},
   791  			Usage:     "搜索文件",
   792  			UsageText: app.Name + " search [-path=<需要检索的目录>] [-r] 关键字",
   793  			Description: `
   794  	按文件名搜索文件(不支持查找目录)。
   795  	默认在当前工作目录搜索.
   796  
   797  	示例:
   798  
   799  	搜索根目录的文件
   800  	BaiduPCS-Go search -path=/ 关键字
   801  
   802  	搜索当前工作目录的文件
   803  	BaiduPCS-Go search 关键字
   804  
   805  	递归搜索当前工作目录的文件
   806  	BaiduPCS-Go search -r 关键字
   807  `,
   808  			Category: "百度网盘",
   809  			Before:   reloadFn,
   810  			Action: func(c *cli.Context) error {
   811  				if c.NArg() < 1 {
   812  					cli.ShowCommandHelp(c, c.Command.Name)
   813  					return nil
   814  				}
   815  
   816  				pcscommand.RunSearch(c.String("path"), c.Args().Get(0), &pcscommand.SearchOptions{
   817  					Total:   c.Bool("l"),
   818  					Recurse: c.Bool("r"),
   819  				})
   820  
   821  				return nil
   822  			},
   823  			Flags: []cli.Flag{
   824  				cli.BoolFlag{
   825  					Name:  "l",
   826  					Usage: "详细显示",
   827  				},
   828  				cli.BoolFlag{
   829  					Name:  "r",
   830  					Usage: "递归搜索",
   831  				},
   832  				cli.StringFlag{
   833  					Name:  "path",
   834  					Usage: "需要检索的目录",
   835  					Value: ".",
   836  				},
   837  			},
   838  		},
   839  		{
   840  			Name:      "tree",
   841  			Aliases:   []string{"t"},
   842  			Usage:     "列出目录的树形图",
   843  			UsageText: app.Name + " tree <目录>",
   844  			Description: `
   845  	列出目录树形图。
   846  	默认从当前工作目录开始列出.
   847  
   848  	示例:
   849  
   850  	从根目录开始列出
   851  	BaiduPCS-Go tree /
   852  
   853  	只列出两层深度
   854  	BaiduPCS-Go tree --depth 2
   855  
   856  	同时显示文件名和fsid
   857  	BaiduPCS-Go tree --fsid
   858  `,
   859  			Category: "百度网盘",
   860  			Before:   reloadFn,
   861  			Action: func(c *cli.Context) error {
   862  				pcscommand.RunTree(c.Args().Get(0), 0, &pcscommand.TreeOptions{
   863  					Depth:    c.Int("depth"),
   864  					ShowFsid: c.Bool("fsid"),
   865  				})
   866  				return nil
   867  			},
   868  			Flags: []cli.Flag{
   869  				cli.IntFlag{
   870  					Name:  "depth",
   871  					Usage: "显示深度",
   872  					Value: -1,
   873  				},
   874  				cli.BoolFlag{
   875  					Name:  "fsid",
   876  					Usage: "带fsid显示",
   877  				},
   878  			},
   879  		},
   880  		{
   881  			Name:      "pwd",
   882  			Usage:     "输出工作目录",
   883  			UsageText: app.Name + " pwd",
   884  			Category:  "百度网盘",
   885  			Before:    reloadFn,
   886  			Action: func(c *cli.Context) error {
   887  				fmt.Println(pcsconfig.Config.ActiveUser().Workdir)
   888  				return nil
   889  			},
   890  		},
   891  		{
   892  			Name:        "meta",
   893  			Usage:       "获取文件/目录的元信息",
   894  			UsageText:   app.Name + " meta <文件/目录1> <文件/目录2> <文件/目录3> ...",
   895  			Description: "默认获取工作目录元信息",
   896  			Category:    "百度网盘",
   897  			Before:      reloadFn,
   898  			Action: func(c *cli.Context) error {
   899  				var (
   900  					ca = c.Args()
   901  					as []string
   902  				)
   903  				if len(ca) == 0 {
   904  					as = []string{""}
   905  				} else {
   906  					as = ca
   907  				}
   908  
   909  				pcscommand.RunGetMeta(as...)
   910  				return nil
   911  			},
   912  		},
   913  		{
   914  			Name:      "rm",
   915  			Usage:     "删除文件/目录",
   916  			UsageText: app.Name + " rm <文件/目录的路径1> <文件/目录2> <文件/目录3> ...",
   917  			Description: `
   918  	注意: 删除多个文件和目录时, 请确保每一个文件和目录都存在, 否则删除操作会失败.
   919  	被删除的文件或目录可在网盘文件回收站找回.
   920  
   921  	示例:
   922  
   923  	删除 /我的资源/1.mp4
   924  	BaiduPCS-Go rm /我的资源/1.mp4
   925  
   926  	删除 /我的资源/1.mp4 和 /我的资源/2.mp4
   927  	BaiduPCS-Go rm /我的资源/1.mp4 /我的资源/2.mp4
   928  
   929  	删除 /我的资源 内的所有文件和目录, 但不删除该目录
   930  	BaiduPCS-Go rm /我的资源/*
   931  
   932  	删除 /我的资源 整个目录 !!
   933  	BaiduPCS-Go rm /我的资源
   934  `,
   935  			Category: "百度网盘",
   936  			Before:   reloadFn,
   937  			Action: func(c *cli.Context) error {
   938  				if c.NArg() == 0 {
   939  					cli.ShowCommandHelp(c, c.Command.Name)
   940  					return nil
   941  				}
   942  
   943  				pcscommand.RunRemove(c.Args()...)
   944  				return nil
   945  			},
   946  		},
   947  		{
   948  			Name:      "mkdir",
   949  			Usage:     "创建目录",
   950  			UsageText: app.Name + " mkdir <目录>",
   951  			Category:  "百度网盘",
   952  			Before:    reloadFn,
   953  			Action: func(c *cli.Context) error {
   954  				if c.NArg() == 0 {
   955  					cli.ShowCommandHelp(c, c.Command.Name)
   956  					return nil
   957  				}
   958  
   959  				pcscommand.RunMkdir(c.Args().Get(0))
   960  				return nil
   961  			},
   962  		},
   963  		{
   964  			Name:  "cp",
   965  			Usage: "拷贝文件/目录",
   966  			UsageText: `BaiduPCS-Go cp <文件/目录> <目标文件/目录>
   967  	BaiduPCS-Go cp <文件/目录1> <文件/目录2> <文件/目录3> ... <目标目录>`,
   968  			Description: `
   969  	注意: 拷贝多个文件和目录时, 请确保每一个文件和目录都存在, 否则拷贝操作会失败.
   970  
   971  	示例:
   972  
   973  	将 /我的资源/1.mp4 复制到 根目录 /
   974  	BaiduPCS-Go cp /我的资源/1.mp4 /
   975  
   976  	将 /我的资源/1.mp4 和 /我的资源/2.mp4 复制到 根目录 /
   977  	BaiduPCS-Go cp /我的资源/1.mp4 /我的资源/2.mp4 /
   978  `,
   979  			Category: "百度网盘",
   980  			Before:   reloadFn,
   981  			Action: func(c *cli.Context) error {
   982  				if c.NArg() <= 1 {
   983  					cli.ShowCommandHelp(c, c.Command.Name)
   984  					return nil
   985  				}
   986  
   987  				pcscommand.RunCopy(c.Args()...)
   988  				return nil
   989  			},
   990  		},
   991  		{
   992  			Name:  "mv",
   993  			Usage: "移动/重命名文件/目录",
   994  			UsageText: `移动:
   995  	BaiduPCS-Go mv <文件/目录1> <文件/目录2> <文件/目录3> ... <目标目录>
   996  
   997  	重命名:
   998  	BaiduPCS-Go mv <文件/目录> <重命名的文件/目录>`,
   999  			Description: `
  1000  	注意: 移动多个文件和目录时, 请确保每一个文件和目录都存在, 否则移动操作会失败.
  1001  
  1002  	示例:
  1003  
  1004  	将 /我的资源/1.mp4 移动到 根目录 /
  1005  	BaiduPCS-Go mv /我的资源/1.mp4 /
  1006  
  1007  	将 /我的资源/1.mp4 重命名为 /我的资源/3.mp4
  1008  	BaiduPCS-Go mv /我的资源/1.mp4 /我的资源/3.mp4
  1009  `,
  1010  			Category: "百度网盘",
  1011  			Before:   reloadFn,
  1012  			Action: func(c *cli.Context) error {
  1013  				if c.NArg() <= 1 {
  1014  					cli.ShowCommandHelp(c, c.Command.Name)
  1015  					return nil
  1016  				}
  1017  
  1018  				pcscommand.RunMove(c.Args()...)
  1019  				return nil
  1020  			},
  1021  		},
  1022  		{
  1023  			Name:      "download",
  1024  			Aliases:   []string{"d"},
  1025  			Usage:     "下载文件/目录",
  1026  			UsageText: app.Name + " download <文件/目录路径1> <文件/目录2> <文件/目录3> ...",
  1027  			Description: `
  1028  	下载的文件默认保存到, 程序所在目录的 download/ 目录.
  1029  	通过 BaiduPCS-Go config set -savedir <savedir>, 自定义保存的目录.
  1030  	支持多个文件或目录下载.
  1031  	支持下载完成后自动校验文件, 但并不是所有的文件都支持校验!
  1032  	自动跳过下载重名的文件!
  1033  
  1034  	下载模式说明:
  1035  		pcs: 通过百度网盘的 PCS API 下载, locate模式提示user is not authorized可尝试此模式
  1036  		stream: 通过百度网盘的 PCS API, 以流式文件的方式下载, 效果同 pcs
  1037  		locate: 默认的下载模式。从百度网盘 Android 客户端, 获取下载链接的方式来下载
  1038  
  1039  	示例:
  1040  
  1041  	设置保存目录, 保存到 D:\Downloads
  1042  	注意区别反斜杠 "\" 和 斜杠 "/" !!!
  1043  	BaiduPCS-Go config set -savedir D:\\Downloads
  1044  	或者
  1045  	BaiduPCS-Go config set -savedir D:/Downloads
  1046  
  1047  	下载 /我的资源/1.mp4
  1048  	BaiduPCS-Go d /我的资源/1.mp4
  1049  
  1050  	下载 /我的资源 整个目录!!
  1051  	BaiduPCS-Go d /我的资源
  1052  
  1053  	下载网盘内的全部文件!!
  1054  	BaiduPCS-Go d /
  1055  	BaiduPCS-Go d *
  1056  `,
  1057  			Category: "百度网盘",
  1058  			Before:   reloadFn,
  1059  			Action: func(c *cli.Context) error {
  1060  				if c.NArg() == 0 {
  1061  					cli.ShowCommandHelp(c, c.Command.Name)
  1062  					return nil
  1063  				}
  1064  
  1065  				// 处理saveTo
  1066  				var (
  1067  					saveTo string
  1068  				)
  1069  				if c.Bool("save") {
  1070  					saveTo = "."
  1071  				} else if c.String("saveto") != "" {
  1072  					saveTo = filepath.Clean(c.String("saveto"))
  1073  				}
  1074  
  1075  				// 处理解析downloadMode
  1076  				var (
  1077  					downloadMode pcsdownload.DownloadMode
  1078  				)
  1079  				switch c.String("mode") {
  1080  				case "pcs":
  1081  					downloadMode = pcsdownload.DownloadModePCS
  1082  				case "stream":
  1083  					downloadMode = pcsdownload.DownloadModeStreaming
  1084  				case "locate":
  1085  					downloadMode = pcsdownload.DownloadModeLocate
  1086  				default:
  1087  					fmt.Println("下载方式解析失败")
  1088  					cli.ShowCommandHelp(c, c.Command.Name)
  1089  					return nil
  1090  				}
  1091  
  1092  				do := &pcscommand.DownloadOptions{
  1093  					IsTest:               c.Bool("test"),
  1094  					IsPrintStatus:        c.Bool("status"),
  1095  					IsExecutedPermission: c.Bool("x"),
  1096  					IsOverwrite:          c.Bool("ow"),
  1097  					DownloadMode:         downloadMode,
  1098  					SaveTo:               saveTo,
  1099  					Parallel:             c.Int("p"),
  1100  					Load:                 c.Int("l"),
  1101  					MaxRetry:             c.Int("retry"),
  1102  					NoCheck:              c.Bool("nocheck"),
  1103  					LinkPrefer:           c.Int("dindex"),
  1104  					ModifyMTime:          c.Bool("mtime"),
  1105  					FullPath:             c.Bool("fullpath"),
  1106  				}
  1107  
  1108  				pcscommand.RunDownload(c.Args(), do)
  1109  
  1110  				return nil
  1111  			},
  1112  			Flags: []cli.Flag{
  1113  				cli.BoolFlag{
  1114  					Name:  "test",
  1115  					Usage: "测试下载, 此操作不会保存文件到本地",
  1116  				},
  1117  				cli.BoolFlag{
  1118  					Name:  "ow",
  1119  					Usage: "overwrite, 覆盖已存在的文件",
  1120  				},
  1121  				cli.BoolFlag{
  1122  					Name:  "status",
  1123  					Usage: "输出所有线程的工作状态",
  1124  				},
  1125  				cli.BoolFlag{
  1126  					Name:  "save",
  1127  					Usage: "将下载的文件直接保存到当前工作目录",
  1128  				},
  1129  				cli.StringFlag{
  1130  					Name:  "saveto",
  1131  					Usage: "将下载的文件直接保存到指定的目录",
  1132  				},
  1133  				cli.BoolFlag{
  1134  					Name:  "x",
  1135  					Usage: "为文件加上执行权限, (windows系统无效)",
  1136  				},
  1137  				cli.StringFlag{
  1138  					Name:  "mode",
  1139  					Usage: "下载模式, 可选值: pcs, stream, locate, 默认为 locate, 相关说明见上面的帮助",
  1140  					Value: "locate",
  1141  				},
  1142  				cli.IntFlag{
  1143  					Name:  "p",
  1144  					Usage: "指定下载线程数",
  1145  				},
  1146  				cli.IntFlag{
  1147  					Name:  "l",
  1148  					Usage: "指定同时进行下载文件的数量",
  1149  				},
  1150  				cli.IntFlag{
  1151  					Name:  "retry",
  1152  					Usage: "下载失败最大重试次数",
  1153  					Value: pcsdownload.DefaultDownloadMaxRetry,
  1154  				},
  1155  				cli.BoolFlag{
  1156  					Name:  "nocheck",
  1157  					Usage: "下载文件完成后不校验文件",
  1158  				},
  1159  				cli.BoolFlag{
  1160  					Name:  "mtime",
  1161  					Usage: "将本地文件的修改时间设置为服务器上的修改时间",
  1162  				},
  1163  				cli.IntFlag{
  1164  					Name: "dindex",
  1165  					Usage: "使用备选下载链接中的第几个,默认第一个",
  1166  				},
  1167  				cli.BoolFlag{
  1168  					Name:  "fullpath",
  1169  					Usage: "以网盘完整路径保存到本地",
  1170  				},
  1171  			},
  1172  		},
  1173  		{
  1174  			Name:      "upload",
  1175  			Aliases:   []string{"u"},
  1176  			Usage:     "上传文件/目录",
  1177  			UsageText: app.Name + " upload <本地文件/目录的路径1> <文件/目录2> <文件/目录3> ... <目标目录>",
  1178  			Description: `
  1179  	上传默认采用分片上传的方式, 上传的文件将会保存到, <目标目录>.
  1180  	当上传的文件名和网盘的目录名称相同时, 不会覆盖目录, 防止丢失数据.
  1181  
  1182  	注意: 
  1183  
  1184  	分片上传之后, 服务器可能会记录到错误的文件md5, 可使用 fixmd5 命令尝试修复文件的MD5值, 修复md5不一定能成功, 但文件的完整性是没问题的.
  1185  	fixmd5 命令使用方法:
  1186  	BaiduPCS-Go fixmd5 -h
  1187  
  1188  	禁用分片上传可以保证服务器记录到正确的md5.
  1189  	禁用分片上传时只能使用单线程上传, 指定的单个文件上传最大线程数将会无效.
  1190  
  1191  	示例:
  1192  
  1193  	1. 将本地的 C:\Users\Administrator\Desktop\1.mp4 上传到网盘 /视频 目录
  1194  	注意区别反斜杠 "\" 和 斜杠 "/" !!!
  1195  	BaiduPCS-Go upload C:/Users/Administrator/Desktop/1.mp4 /视频
  1196  
  1197  	2. 将本地的 C:\Users\Administrator\Desktop\1.mp4 和 C:\Users\Administrator\Desktop\2.mp4 上传到网盘 /视频 目录
  1198  	BaiduPCS-Go upload C:/Users/Administrator/Desktop/1.mp4 C:/Users/Administrator/Desktop/2.mp4 /视频
  1199  
  1200  	3. 将本地的 C:\Users\Administrator\Desktop 整个目录上传到网盘 /视频 目录
  1201  	BaiduPCS-Go upload C:/Users/Administrator/Desktop /视频
  1202  
  1203  	4. 使用相对路径
  1204  	BaiduPCS-Go upload 1.mp4 /视频
  1205  `,
  1206  			Category: "百度网盘",
  1207  			Before:   reloadFn,
  1208  			Action: func(c *cli.Context) error {
  1209  				if c.NArg() < 2 {
  1210  					cli.ShowCommandHelp(c, c.Command.Name)
  1211  					return nil
  1212  				}
  1213  
  1214  				subArgs := c.Args()
  1215  				pcscommand.RunUpload(subArgs[:c.NArg()-1], subArgs[c.NArg()-1], &pcscommand.UploadOptions{
  1216  					Parallel:      c.Int("p"),
  1217  					MaxRetry:      c.Int("retry"),
  1218  					Load:          c.Int("l"),
  1219  					NoRapidUpload: c.Bool("norapid"),
  1220  					NoSplitFile:   c.Bool("nosplit"),
  1221  					Policy:        c.String("policy"),
  1222  				})
  1223  				return nil
  1224  			},
  1225  			Flags: []cli.Flag{
  1226  				cli.IntFlag{
  1227  					Name:  "p",
  1228  					Usage: "指定单个文件上传的最大线程数",
  1229  				},
  1230  				cli.IntFlag{
  1231  					Name:  "retry",
  1232  					Usage: "上传失败最大重试次数",
  1233  					Value: pcscommand.DefaultUploadMaxRetry,
  1234  				},
  1235  				cli.IntFlag{
  1236  					Name:  "l",
  1237  					Usage: "指定同时上传的最大文件数",
  1238  				},
  1239  				cli.BoolFlag{
  1240  					Name:  "norapid",
  1241  					Usage: "不检测秒传",
  1242  				},
  1243  				cli.BoolFlag{
  1244  					Name:  "nosplit",
  1245  					Usage: "禁用分片上传",
  1246  				},
  1247  				cli.StringFlag{
  1248  					Name:  "policy",
  1249  					Usage: "对同名文件的处理策略",
  1250  				},
  1251  			},
  1252  		},
  1253  		{
  1254  			Name:      "locate",
  1255  			Aliases:   []string{"lt"},
  1256  			Usage:     "获取下载直链",
  1257  			UsageText: app.Name + " locate <文件1> <文件2> ...",
  1258  			Description: fmt.Sprintf(`
  1259  	获取下载直链
  1260  
  1261  	若该功能无法正常使用, 提示"user is not authorized, hitcode:xxx", 尝试更换 User-Agent 为 %s:
  1262  	BaiduPCS-Go config set -user_agent "%s"
  1263  `, baidupcs.NetdiskUA, baidupcs.NetdiskUA),
  1264  			Category: "百度网盘",
  1265  			Before:   reloadFn,
  1266  			Action: func(c *cli.Context) error {
  1267  				if c.NArg() < 1 {
  1268  					cli.ShowCommandHelp(c, c.Command.Name)
  1269  					return nil
  1270  				}
  1271  
  1272  				opt := &pcscommand.LocateDownloadOption{
  1273  					FromPan: c.Bool("pan"),
  1274  				}
  1275  
  1276  				pcscommand.RunLocateDownload(c.Args(), opt)
  1277  				return nil
  1278  			},
  1279  			Flags: []cli.Flag{
  1280  				cli.BoolFlag{
  1281  					Name:  "pan",
  1282  					Usage: "从百度网盘首页获取下载链接",
  1283  				},
  1284  			},
  1285  		},
  1286  		{
  1287  			Name:      "rapidupload",
  1288  			Aliases:   []string{"ru"},
  1289  			Usage:     "手动秒传文件",
  1290  			UsageText: app.Name + " rapidupload -length=<文件的大小> -md5=<文件的md5值> -slicemd5=<文件前256KB切片的md5值(可选)> -crc32=<文件的crc32值(可选)> <保存的网盘路径, 需包含文件名>",
  1291  			Description: `
  1292  	使用此功能秒传文件, 前提是知道文件的大小, md5, 前256KB切片的 md5 (可选), crc32 (可选), 且百度网盘中存在一模一样的文件.
  1293  	上传的文件将会保存到网盘的目标目录.
  1294  	遇到同名文件将会自动覆盖!
  1295  
  1296  	可能无法秒传 20GB 以上的文件!!
  1297  
  1298  	示例:
  1299  
  1300  	1. 如果秒传成功, 则保存到网盘路径 /test
  1301  	BaiduPCS-Go rapidupload -length=56276137 -md5=fbe082d80e90f90f0fb1f94adbbcfa7f -slicemd5=38c6a75b0ec4499271d4ea38a667ab61 -crc32=314332359 /test
  1302  `,
  1303  			Category: "百度网盘",
  1304  			Before:   reloadFn,
  1305  			Action: func(c *cli.Context) error {
  1306  				if c.NArg() <= 0 || !c.IsSet("md5") || !c.IsSet("length") || !c.IsSet("slicemd5") {
  1307  					cli.ShowCommandHelp(c, c.Command.Name)
  1308  					return nil
  1309  				}
  1310  				pcscommand.RunRapidUpload(c.Args().Get(0), c.String("md5"), c.String("slicemd5"), c.Int64("length"))
  1311  				return nil
  1312  			},
  1313  			Flags: []cli.Flag{
  1314  				cli.StringFlag{
  1315  					Name:  "md5",
  1316  					Usage: "文件的 md5 值",
  1317  				},
  1318  				cli.StringFlag{
  1319  					Name:  "slicemd5",
  1320  					Usage: "文件前 256KB 切片的 md5 值 (可选)",
  1321  				},
  1322  				cli.StringFlag{
  1323  					Name:  "crc32",
  1324  					Usage: "文件的 crc32 值 (可选)",
  1325  				},
  1326  				cli.Int64Flag{
  1327  					Name:  "length",
  1328  					Usage: "文件的大小",
  1329  				},
  1330  			},
  1331  		},
  1332  		{
  1333  			Name:      "createsuperfile",
  1334  			Aliases:   []string{"csf"},
  1335  			Usage:     "手动分片上传—合并分片文件",
  1336  			UsageText: app.Name + " createsuperfile -path=<保存的网盘路径, 需包含文件名> block1 block2 ... ",
  1337  			Description: `
  1338  	block1, block2 ... 为文件分片的md5值
  1339  	上传的文件将会保存到网盘的目标目录.
  1340  	遇到同名文件默认覆盖, 可以--policy参数指定, 支持newcopy, skip, overwrite, fail四种模式
  1341  
  1342  	示例:
  1343  
  1344  	BaiduPCS-Go createsuperfile -path=1.mp4 ec87a838931d4d5d2e94a04644788a55 ec87a838931d4d5d2e94a04644788a55
  1345  `,
  1346  			Category: "百度网盘",
  1347  			Before:   reloadFn,
  1348  			Action: func(c *cli.Context) error {
  1349  				if c.NArg() < 1 {
  1350  					cli.ShowCommandHelp(c, c.Command.Name)
  1351  					return nil
  1352  				}
  1353  
  1354  				pcscommand.RunCreateSuperFile(c.String("policy"), c.String("path"), c.Args()...)
  1355  				return nil
  1356  			},
  1357  			Flags: []cli.Flag{
  1358  				cli.StringFlag{
  1359  					Name:  "path",
  1360  					Usage: "保存的网盘路径",
  1361  					Value: "superfile",
  1362  				},
  1363  				cli.StringFlag{
  1364  					Name:  "policy",
  1365  					Usage: "同名文件处理策略",
  1366  					Value: "overwrite",
  1367  				},
  1368  			},
  1369  		},
  1370  		{
  1371  			Name:      "fixmd5",
  1372  			Usage:     "修复文件MD5",
  1373  			UsageText: app.Name + " fixmd5 <文件1> <文件2> <文件3> ...",
  1374  			Description: `
  1375  	尝试修复文件的MD5值, 以便于校验文件的完整性和导出文件.
  1376  
  1377  	使用分片上传文件, 当文件分片数大于1时, 百度网盘服务端最终计算所得的md5值和本地的不一致, 这可能是百度网盘的bug.
  1378  	不过把上传的文件下载到本地后,对比md5值是匹配的, 也就是文件在传输中没有发生损坏.
  1379  
  1380  	对于MD5值可能有误的文件, 程序会在获取文件的元信息时, 给出MD5值 "可能不正确" 的提示, 表示此文件可以尝试进行MD5值修复.
  1381  	修复文件MD5不一定能成功, 原因可能是服务器未刷新, 可过几天后再尝试.
  1382  	修复文件MD5的原理为秒传文件, 即修复文件MD5成功后, 文件的创建日期, 修改日期, fs_id, 版本历史等信息将会被覆盖, 修复的MD5值将覆盖原先的MD5值, 但不影响文件的完整性.
  1383  
  1384  	注意: 无法修复 20GB 以上文件的 md5!!
  1385  
  1386  	示例:
  1387  
  1388  	1. 修复 /我的资源/1.mp4 的 MD5 值
  1389  	BaiduPCS-Go fixmd5 /我的资源/1.mp4
  1390  `,
  1391  			Category: "百度网盘",
  1392  			Before:   reloadFn,
  1393  			Action: func(c *cli.Context) error {
  1394  				if c.NArg() <= 0 {
  1395  					cli.ShowCommandHelp(c, c.Command.Name)
  1396  					return nil
  1397  				}
  1398  
  1399  				pcscommand.RunFixMD5(c.Args()...)
  1400  				return nil
  1401  			},
  1402  		},
  1403  		{
  1404  			Name:      "sumfile",
  1405  			Aliases:   []string{"sf"},
  1406  			Usage:     "获取本地文件的秒传信息(目前秒传功能已失效)",
  1407  			UsageText: app.Name + " sumfile <本地文件的路径1> <本地文件的路径2> ...",
  1408  			Description: `
  1409  	获取本地文件的大小, md5, 前256KB切片的md5, crc32, 曾经可用于秒传文件.
  1410  
  1411  	示例:
  1412  
  1413  	获取 C:\Users\Administrator\Desktop\1.mp4 的秒传信息
  1414  	BaiduPCS-Go sumfile C:/Users/Administrator/Desktop/1.mp4
  1415  `,
  1416  			Category: "其他",
  1417  			Before:   reloadFn,
  1418  			Action: func(c *cli.Context) error {
  1419  				if c.NArg() <= 0 {
  1420  					cli.ShowCommandHelp(c, c.Command.Name)
  1421  					return nil
  1422  				}
  1423  
  1424  				for k, filePath := range c.Args() {
  1425  					lp, err := checksum.GetFileSum(filePath, checksum.CHECKSUM_MD5|checksum.CHECKSUM_SLICE_MD5|checksum.CHECKSUM_CRC32)
  1426  					if err != nil {
  1427  						fmt.Printf("[%d] %s\n", k+1, err)
  1428  						continue
  1429  					}
  1430  
  1431  					fmt.Printf("[%d] - [%s]:\n", k+1, filePath)
  1432  
  1433  					strLength, strMd5, strSliceMd5, strCrc32 := strconv.FormatInt(lp.Length, 10), hex.EncodeToString(lp.MD5), hex.EncodeToString(lp.SliceMD5), strconv.FormatUint(uint64(lp.CRC32), 10)
  1434  					fileName := filepath.Base(filePath)
  1435  					regFileName := strings.Replace(fileName, " ", "_", -1)
  1436  					regFileName = strings.Replace(regFileName, "#", "_", -1)
  1437  					tb := pcstable.NewTable(os.Stdout)
  1438  					tb.SetColumnAlignment([]int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT})
  1439  					tb.AppendBulk([][]string{
  1440  						[]string{"文件大小", strLength},
  1441  						[]string{"md5", strMd5},
  1442  						[]string{"前256KB切片的md5", strSliceMd5},
  1443  						[]string{"crc32", strCrc32},
  1444  						[]string{"秒传命令", app.Name + " rapidupload -length=" + strLength + " -md5=" + strMd5 + " -slicemd5=" + strSliceMd5 + " -crc32=" + strCrc32 + " " + fileName},
  1445  						[]string{"通用秒传链接", strMd5 + "#" + strSliceMd5 + "#" + strLength + "#" + regFileName},
  1446  					})
  1447  					tb.Render()
  1448  					fmt.Printf("\n")
  1449  				}
  1450  
  1451  				return nil
  1452  			},
  1453  		},
  1454  		{
  1455  			Name:      "transfer",
  1456  			Usage:     "转存文件/目录",
  1457  			UsageText: app.Name + " transfer <分享链接> <提取码>(如果有)",
  1458  			Category:  "百度网盘",
  1459  			Before:    reloadFn,
  1460  			Description: `
  1461  			转存文件/目录
  1462  	如果没有提取码或为整合式链接,则第二个位置留空;只能转存到当前网盘目录下,
  1463  	分享链接支持常规百度云链接, 支持长短秒传链接
  1464  	
  1465  	实例:
  1466  	BaiduPCS-Go transfer pan.baidu.com/s/1VYzSl7465sdrQXe8GT5RdQ 704e
  1467  	BaiduPCS-Go transfer https://pan.baidu.com/s/1VYzSl7465sdrQXe8GT5RdQ 704e
  1468  	BaiduPCS-Go transfer https://pan.baidu.com/s/1VYzSl7465sdrQXe8GT5RdQ?pwd=704e
  1469  
  1470  	`,
  1471  			Action: func(c *cli.Context) error {
  1472  				if c.NArg() < 1 || c.NArg() > 2 {
  1473  					cli.ShowCommandHelp(c, c.Command.Name)
  1474  					return nil
  1475  				}
  1476  				opt := &baidupcs.TransferOption{
  1477  					Download: c.Bool("download"),
  1478  					Collect:  c.Bool("collect"),
  1479  					Rname:    c.Bool("rname"),
  1480  				}
  1481  				pcscommand.RunShareTransfer(c.Args(), opt)
  1482  				return nil
  1483  			},
  1484  			Flags: []cli.Flag{
  1485  				cli.BoolFlag{
  1486  					Name:  "download",
  1487  					Usage: "转存后直接下载到本地默认目录",
  1488  				},
  1489  				cli.BoolFlag{
  1490  					Name:  "collect",
  1491  					Usage: "多文件整合到一个文件夹中转存",
  1492  				},
  1493  				cli.BoolFlag{
  1494  					Name:  "rname",
  1495  					Usage: "秒传随机替换4位文件名提高成功率",
  1496  				},
  1497  			},
  1498  		},
  1499  		{
  1500  			Name:      "share",
  1501  			Usage:     "分享文件/目录",
  1502  			UsageText: app.Name + " share",
  1503  			Category:  "百度网盘",
  1504  			Before:    reloadFn,
  1505  			Action: func(c *cli.Context) error {
  1506  				cli.ShowCommandHelp(c, c.Command.Name)
  1507  				return nil
  1508  			},
  1509  			Subcommands: []cli.Command{
  1510  				{
  1511  					Name:        "set",
  1512  					Aliases:     []string{"s"},
  1513  					Usage:       "设置分享文件/目录",
  1514  					UsageText:   app.Name + " share set <文件/目录1> <文件/目录2> ...",
  1515  					Description: `支持任意有效天数, 支持自定义提取码.`,
  1516  					Action: func(c *cli.Context) error {
  1517  						if c.NArg() < 1 {
  1518  							cli.ShowCommandHelp(c, c.Command.Name)
  1519  							return nil
  1520  						}
  1521  						opt := &baidupcs.ShareOption{
  1522  							Password: c.String("p"),
  1523  							Period:   c.Int("period"),
  1524  							IsCombined: c.Bool("f"),
  1525  						}
  1526  						pcscommand.RunShareSet(c.Args(), opt)
  1527  						return nil
  1528  					},
  1529  					Flags: []cli.Flag{
  1530  						cli.StringFlag{
  1531  							Name:  "p",
  1532  							Usage: "提取码",
  1533  							Value: "",
  1534  						},
  1535  						cli.IntFlag{
  1536  							Name:  "period",
  1537  							Usage: "有效天数, 0为永久",
  1538  							Value: 0,
  1539  						},
  1540  						cli.BoolFlag{
  1541  							Name: "f",
  1542  							Usage: "输出带密码的完整链接格式",
  1543  						},
  1544  					},
  1545  				},
  1546  				{
  1547  					Name:      "list",
  1548  					Aliases:   []string{"l"},
  1549  					Usage:     "列出已分享文件/目录",
  1550  					UsageText: app.Name + " share list",
  1551  					Action: func(c *cli.Context) error {
  1552  						pcscommand.RunShareList(c.Int("page"))
  1553  						return nil
  1554  					},
  1555  					Flags: []cli.Flag{
  1556  						cli.IntFlag{
  1557  							Name:  "page",
  1558  							Usage: "分享列表的页数",
  1559  							Value: 1,
  1560  						},
  1561  					},
  1562  				},
  1563  				{
  1564  					Name:        "cancel",
  1565  					Aliases:     []string{"c"},
  1566  					Usage:       "取消分享文件/目录",
  1567  					UsageText:   app.Name + " share cancel <shareid_1> <shareid_2> ...",
  1568  					Description: `目前只支持通过分享id (shareid) 来取消分享.`,
  1569  					Action: func(c *cli.Context) error {
  1570  						if c.NArg() < 1 {
  1571  							cli.ShowCommandHelp(c, c.Command.Name)
  1572  							return nil
  1573  						}
  1574  						pcscommand.RunShareCancel(converter.SliceStringToInt64(c.Args()))
  1575  						return nil
  1576  					},
  1577  				},
  1578  			},
  1579  		},
  1580  		{
  1581  			Name:      "export",
  1582  			Aliases:   []string{"ep"},
  1583  			Usage:     "导出文件/目录",
  1584  			UsageText: app.Name + " export <文件/目录1> <文件/目录2> ...",
  1585  			Description: `
  1586  	导出网盘内的文件或目录, 原理为秒传文件, 此操作会生成导出文件或目录的命令.
  1587  
  1588  	注意!!! :
  1589  	由于秒传已经失效, 导出信息已无法用做公开分享
  1590  	无法导出 20GB 以上的文件!!
  1591  	无法导出文件的版本历史等数据!!
  1592  	并不是所有的文件都能导出成功, 程序会列出无法导出的文件列表.
  1593  
  1594  	示例:
  1595  
  1596  	导出当前工作目录:
  1597  	BaiduPCS-Go export
  1598  
  1599  	导出所有文件和目录, 并设置新的根目录为 /root 
  1600  	BaiduPCS-Go export -root=/root /
  1601  
  1602  	导出 /我的资源
  1603  	BaiduPCS-Go export /我的资源
  1604  `,
  1605  			Category: "百度网盘",
  1606  			Before:   reloadFn,
  1607  			Action: func(c *cli.Context) error {
  1608  				pcspaths := c.Args()
  1609  				if len(pcspaths) == 0 {
  1610  					pcspaths = []string{"."}
  1611  				}
  1612  
  1613  				pcscommand.RunExport(pcspaths, &pcscommand.ExportOptions{
  1614  					RootPath:   c.String("root"),
  1615  					SavePath:   c.String("out"),
  1616  					MaxRetry:   c.Int("retry"),
  1617  					Recursive:  c.Bool("r"),
  1618  					LinkFormat: c.Bool("link"),
  1619  					StdOut:     c.Bool("stdout"),
  1620  				})
  1621  				return nil
  1622  			},
  1623  			Flags: []cli.Flag{
  1624  				cli.StringFlag{
  1625  					Name:  "root",
  1626  					Usage: "设置要导出文件或目录的根路径, 可以是相对路径",
  1627  				},
  1628  				cli.StringFlag{
  1629  					Name:  "out",
  1630  					Usage: "导出文件信息的保存路径",
  1631  				},
  1632  				cli.IntFlag{
  1633  					Name:  "retry",
  1634  					Usage: "导出失败的重试次数",
  1635  					Value: 3,
  1636  				},
  1637  				cli.BoolFlag{
  1638  					Name:  "r",
  1639  					Usage: "递归导出",
  1640  				},
  1641  				cli.BoolFlag{
  1642  					Name:  "link",
  1643  					Usage: "以通用秒传链接格式导出(将丢失路径信息)",
  1644  				},
  1645  				cli.BoolFlag{
  1646  					Name:  "stdout",
  1647  					Usage: "导出信息不存文件, 直接打印至标准输出",
  1648  				},
  1649  			},
  1650  		},
  1651  		{
  1652  			Name:    "offlinedl",
  1653  			Aliases: []string{"clouddl", "od"},
  1654  			Usage:   "离线下载",
  1655  			Description: `支持http/https/ftp/电驴/磁力链协议
  1656  	离线下载同时进行的任务数量有限, 超出限制的部分将无法添加.
  1657  
  1658  	示例:
  1659  
  1660  	1. 将百度和腾讯主页, 离线下载到根目录 /
  1661  	BaiduPCS-Go offlinedl add -path=/ http://baidu.com http://qq.com
  1662  
  1663  	2. 添加磁力链接任务
  1664  	BaiduPCS-Go offlinedl add magnet:?xt=urn:btih:xxx
  1665  
  1666  	3. 查询任务ID为 12345 的离线下载任务状态
  1667  	BaiduPCS-Go offlinedl query 12345
  1668  
  1669  	4. 取消任务ID为 12345 的离线下载任务
  1670  	BaiduPCS-Go offlinedl cancel 12345`,
  1671  			Category: "百度网盘",
  1672  			Before:   reloadFn,
  1673  			Action: func(c *cli.Context) error {
  1674  				cli.ShowCommandHelp(c, c.Command.Name)
  1675  				return nil
  1676  			},
  1677  			Subcommands: []cli.Command{
  1678  				{
  1679  					Name:      "add",
  1680  					Aliases:   []string{"a"},
  1681  					Usage:     "添加离线下载任务",
  1682  					UsageText: app.Name + " offlinedl add -path=<离线下载文件保存的路径> 资源地址1 地址2 ...",
  1683  					Action: func(c *cli.Context) error {
  1684  						if c.NArg() < 1 {
  1685  							cli.ShowCommandHelp(c, c.Command.Name)
  1686  							return nil
  1687  						}
  1688  
  1689  						pcscommand.RunCloudDlAddTask(c.Args(), c.String("path"))
  1690  						return nil
  1691  					},
  1692  					Flags: []cli.Flag{
  1693  						cli.StringFlag{
  1694  							Name:  "path",
  1695  							Usage: "离线下载文件保存的路径, 默认为工作目录",
  1696  						},
  1697  					},
  1698  				},
  1699  				{
  1700  					Name:      "query",
  1701  					Aliases:   []string{"q"},
  1702  					Usage:     "精确查询离线下载任务",
  1703  					UsageText: app.Name + " offlinedl query 任务ID1 任务ID2 ...",
  1704  					Action: func(c *cli.Context) error {
  1705  						if c.NArg() < 1 {
  1706  							cli.ShowCommandHelp(c, c.Command.Name)
  1707  							return nil
  1708  						}
  1709  
  1710  						taskIDs := converter.SliceStringToInt64(c.Args())
  1711  
  1712  						if len(taskIDs) == 0 {
  1713  							fmt.Printf("未找到合法的任务ID, task_id\n")
  1714  							return nil
  1715  						}
  1716  
  1717  						pcscommand.RunCloudDlQueryTask(taskIDs)
  1718  						return nil
  1719  					},
  1720  				},
  1721  				{
  1722  					Name:      "list",
  1723  					Aliases:   []string{"ls", "l"},
  1724  					Usage:     "查询离线下载任务列表",
  1725  					UsageText: app.Name + " offlinedl list",
  1726  					Action: func(c *cli.Context) error {
  1727  						pcscommand.RunCloudDlListTask()
  1728  						return nil
  1729  					},
  1730  				},
  1731  				{
  1732  					Name:      "cancel",
  1733  					Aliases:   []string{"c"},
  1734  					Usage:     "取消离线下载任务",
  1735  					UsageText: app.Name + " offlinedl cancel 任务ID1 任务ID2 ...",
  1736  					Action: func(c *cli.Context) error {
  1737  						if c.NArg() < 1 {
  1738  							cli.ShowCommandHelp(c, c.Command.Name)
  1739  							return nil
  1740  						}
  1741  
  1742  						taskIDs := converter.SliceStringToInt64(c.Args())
  1743  
  1744  						if len(taskIDs) == 0 {
  1745  							fmt.Printf("未找到合法的任务ID, task_id\n")
  1746  							return nil
  1747  						}
  1748  
  1749  						pcscommand.RunCloudDlCancelTask(taskIDs)
  1750  						return nil
  1751  					},
  1752  				},
  1753  				{
  1754  					Name:      "delete",
  1755  					Aliases:   []string{"del", "d"},
  1756  					Usage:     "删除离线下载任务",
  1757  					UsageText: app.Name + " offlinedl delete 任务ID1 任务ID2 ...",
  1758  					Action: func(c *cli.Context) error {
  1759  						isClear := c.Bool("all")
  1760  						if c.NArg() < 1 && !isClear {
  1761  							cli.ShowCommandHelp(c, c.Command.Name)
  1762  							return nil
  1763  						}
  1764  
  1765  						// 清空离线下载任务记录
  1766  						if isClear {
  1767  							pcscommand.RunCloudDlClearTask()
  1768  							return nil
  1769  						}
  1770  
  1771  						// 删除特定的离线下载任务记录
  1772  						taskIDs := converter.SliceStringToInt64(c.Args())
  1773  						if len(taskIDs) == 0 {
  1774  							fmt.Printf("未找到合法的任务ID, task_id\n")
  1775  							return nil
  1776  						}
  1777  
  1778  						pcscommand.RunCloudDlDeleteTask(taskIDs)
  1779  						return nil
  1780  					},
  1781  					Flags: []cli.Flag{
  1782  						cli.BoolFlag{
  1783  							Name:  "all",
  1784  							Usage: "清空离线下载任务记录, 程序不会进行二次确认, 谨慎操作!!!",
  1785  						},
  1786  					},
  1787  				},
  1788  			},
  1789  		},
  1790  		{
  1791  			Name:  "recycle",
  1792  			Usage: "回收站",
  1793  			Description: `
  1794  	回收站操作.
  1795  
  1796  	示例:
  1797  
  1798  	1. 从回收站还原两个文件, 其中的两个文件的 fs_id 分别为 1013792297798440 和 643596340463870
  1799  	BaiduPCS-Go recycle restore 1013792297798440 643596340463870
  1800  
  1801  	2. 从回收站删除两个文件, 其中的两个文件的 fs_id 分别为 1013792297798440 和 643596340463870
  1802  	BaiduPCS-Go recycle delete 1013792297798440 643596340463870
  1803  
  1804  	3. 清空回收站, 程序不会进行二次确认, 谨慎操作!!!
  1805  	BaiduPCS-Go recycle delete -all
  1806  `,
  1807  			Category: "百度网盘",
  1808  			Before:   reloadFn,
  1809  			Action: func(c *cli.Context) error {
  1810  				if c.NumFlags() <= 0 || c.NArg() <= 0 {
  1811  					cli.ShowCommandHelp(c, c.Command.Name)
  1812  				}
  1813  				return nil
  1814  			},
  1815  			Subcommands: []cli.Command{
  1816  				{
  1817  					Name:      "list",
  1818  					Aliases:   []string{"ls", "l"},
  1819  					Usage:     baidupcs.OperationRecycleList,
  1820  					UsageText: app.Name + " recycle list",
  1821  					Action: func(c *cli.Context) error {
  1822  						pcscommand.RunRecycleList(c.Int("page"))
  1823  						return nil
  1824  					},
  1825  					Flags: []cli.Flag{
  1826  						cli.IntFlag{
  1827  							Name:  "page",
  1828  							Usage: "回收站文件列表页数",
  1829  							Value: 1,
  1830  						},
  1831  					},
  1832  				},
  1833  				{
  1834  					Name:        "restore",
  1835  					Aliases:     []string{"r"},
  1836  					Usage:       baidupcs.OperationRecycleRestore,
  1837  					UsageText:   app.Name + " recycle restore <fs_id 1> <fs_id 2> <fs_id 3> ...",
  1838  					Description: `根据文件/目录的 fs_id, 还原回收站指定的文件或目录`,
  1839  					Action: func(c *cli.Context) error {
  1840  						if c.NArg() <= 0 {
  1841  							cli.ShowCommandHelp(c, c.Command.Name)
  1842  							return nil
  1843  						}
  1844  						pcscommand.RunRecycleRestore(c.Args()...)
  1845  						return nil
  1846  					},
  1847  				},
  1848  				{
  1849  					Name:        "delete",
  1850  					Aliases:     []string{"d"},
  1851  					Usage:       baidupcs.OperationRecycleDelete + "/" + baidupcs.OperationRecycleClear,
  1852  					UsageText:   app.Name + " recycle delete [-all] <fs_id 1> <fs_id 2> <fs_id 3> ...",
  1853  					Description: `根据文件/目录的 fs_id 或 -all 参数, 删除回收站指定的文件或目录或清空回收站`,
  1854  					Action: func(c *cli.Context) error {
  1855  						if c.Bool("all") {
  1856  							// 清空回收站
  1857  							pcscommand.RunRecycleClear()
  1858  							return nil
  1859  						}
  1860  
  1861  						if c.NArg() <= 0 {
  1862  							cli.ShowCommandHelp(c, c.Command.Name)
  1863  							return nil
  1864  						}
  1865  						pcscommand.RunRecycleDelete(c.Args()...)
  1866  						return nil
  1867  					},
  1868  					Flags: []cli.Flag{
  1869  						cli.BoolFlag{
  1870  							Name:  "all",
  1871  							Usage: "清空回收站, 程序不会进行二次确认, 谨慎操作!!!",
  1872  						},
  1873  					},
  1874  				},
  1875  			},
  1876  		},
  1877  		{
  1878  			Name:        "config",
  1879  			Usage:       "显示和修改程序配置项",
  1880  			Description: "显示和修改程序配置项",
  1881  			Category:    "配置",
  1882  			Before:      reloadFn,
  1883  			After:       saveFunc,
  1884  			Action: func(c *cli.Context) error {
  1885  				fmt.Printf("----\n运行 %s config set 可进行设置配置\n\n当前配置:\n", app.Name)
  1886  				pcsconfig.Config.PrintTable()
  1887  				return nil
  1888  			},
  1889  			Subcommands: []cli.Command{
  1890  				{
  1891  					Name:      "set",
  1892  					Usage:     "修改程序配置项",
  1893  					UsageText: app.Name + " config set [arguments...]",
  1894  					Description: `
  1895  	注意:
  1896  		可通过设置环境变量 BAIDUPCS_GO_CONFIG_DIR, 指定配置文件存放的目录.
  1897  
  1898  		谨慎修改 appid, user_agent, pcs_ua, pan_ua 的值, 否则访问网盘服务器时, 可能会出现错误
  1899  		cache_size 的值支持可选设置单位了, 单位不区分大小写, b 和 B 均表示字节的意思, 如 64KB, 1MB, 32kb, 65536b, 65536
  1900  		max_download_rate, max_upload_rate 的值支持可选设置单位了, 单位为每秒的传输速率, 后缀'/s' 可省略, 如 2MB/s, 2MB, 2m, 2mb 均为一个意思
  1901  
  1902  	例子:
  1903  		BaiduPCS-Go config set -appid=266719
  1904  		BaiduPCS-Go config set -enable_https=false
  1905  		BaiduPCS-Go config set -user_agent="netdisk;2.2.51.6;netdisk;10.0.63;PC;android-android"
  1906  		BaiduPCS-Go config set -cache_size 64KB
  1907  		BaiduPCS-Go config set -cache_size 16384 -max_parallel 200 -savedir D:/download`,
  1908  					Action: func(c *cli.Context) error {
  1909  						if c.NumFlags() <= 0 || c.NArg() > 0 {
  1910  							cli.ShowCommandHelp(c, c.Command.Name)
  1911  							return nil
  1912  						}
  1913  
  1914  						if c.IsSet("appid") {
  1915  							pcsconfig.Config.SetAppID(c.Int("appid"))
  1916  						}
  1917  						if c.IsSet("enable_https") {
  1918  							pcsconfig.Config.SetEnableHTTPS(c.Bool("enable_https"))
  1919  						}
  1920  						if c.IsSet("ignore_illegal") {
  1921  							pcsconfig.Config.SetIgnoreIllegal(c.Bool("ignore_illegal"))
  1922  						}
  1923  						if c.IsSet("force_login_username") {
  1924  							pcsconfig.Config.SetForceLogin(c.String("force_login_username"))
  1925  						}
  1926  						if c.IsSet("no_check") {
  1927  							pcsconfig.Config.SetNoCheck(c.Bool("no_check"))
  1928  						}
  1929  						if c.IsSet("upload_policy") {
  1930  							pcsconfig.Config.SetUploadPolicy(c.String("upload_policy"))
  1931  						}
  1932  						if c.IsSet("user_agent") {
  1933  							pcsconfig.Config.SetUserAgent(c.String("user_agent"))
  1934  						}
  1935  						if c.IsSet("pcs_ua") {
  1936  							pcsconfig.Config.SetPCSUA(c.String("pcs_ua"))
  1937  						}
  1938  						if c.IsSet("pcs_addr") {
  1939  							match := pcsconfig.Config.SETPCSAddr(c.String("pcs_addr"))
  1940  							if !match {
  1941  								fmt.Println("设置 pcs_addr 错误: pcs服务器地址不合法")
  1942  								return nil
  1943  							}
  1944  						}
  1945  						if c.IsSet("pan_ua") {
  1946  							pcsconfig.Config.SetPanUA(c.String("pan_ua"))
  1947  						}
  1948  						if c.IsSet("cache_size") {
  1949  							err := pcsconfig.Config.SetCacheSizeByStr(c.String("cache_size"))
  1950  							if err != nil {
  1951  								fmt.Printf("设置 cache_size 错误: %s\n", err)
  1952  								return nil
  1953  							}
  1954  						}
  1955  						if c.IsSet("max_parallel") {
  1956  							pcsconfig.Config.MaxParallel = c.Int("max_parallel")
  1957  						}
  1958  						if c.IsSet("max_upload_parallel") {
  1959  							pcsconfig.Config.MaxUploadParallel = c.Int("max_upload_parallel")
  1960  						}
  1961  						if c.IsSet("max_download_load") {
  1962  							pcsconfig.Config.MaxDownloadLoad = c.Int("max_download_load")
  1963  						}
  1964  						if c.IsSet("max_upload_load") {
  1965  							pcsconfig.Config.MaxUploadLoad = c.Int("max_upload_load")
  1966  						}
  1967  						if c.IsSet("max_download_rate") {
  1968  							err := pcsconfig.Config.SetMaxDownloadRateByStr(c.String("max_download_rate"))
  1969  							if err != nil {
  1970  								fmt.Printf("设置 max_download_rate 错误: %s\n", err)
  1971  								return nil
  1972  							}
  1973  						}
  1974  						if c.IsSet("max_upload_rate") {
  1975  							err := pcsconfig.Config.SetMaxUploadRateByStr(c.String("max_upload_rate"))
  1976  							if err != nil {
  1977  								fmt.Printf("设置 max_upload_rate 错误: %s\n", err)
  1978  								return nil
  1979  							}
  1980  						}
  1981  						if c.IsSet("savedir") {
  1982  							pcsconfig.Config.SaveDir = c.String("savedir")
  1983  						}
  1984  						if c.IsSet("proxy") {
  1985  							pcsconfig.Config.SetProxy(c.String("proxy"))
  1986  						}
  1987  						if c.IsSet("local_addrs") {
  1988  							pcsconfig.Config.SetLocalAddrs(c.String("local_addrs"))
  1989  						}
  1990  
  1991  						err := pcsconfig.Config.Save()
  1992  						if err != nil {
  1993  							fmt.Println(err)
  1994  							return err
  1995  						}
  1996  
  1997  						pcsconfig.Config.PrintTable()
  1998  						fmt.Printf("\n保存配置成功!\n\n")
  1999  
  2000  						return nil
  2001  					},
  2002  					Flags: []cli.Flag{
  2003  						cli.IntFlag{
  2004  							Name:  "appid",
  2005  							Usage: "百度 PCS 应用ID",
  2006  						},
  2007  						cli.StringFlag{
  2008  							Name:  "cache_size",
  2009  							Usage: "下载缓存",
  2010  						},
  2011  						cli.IntFlag{
  2012  							Name:  "max_parallel",
  2013  							Usage: "下载网络全部连接的最大并发量",
  2014  						},
  2015  						cli.IntFlag{
  2016  							Name:  "max_upload_parallel",
  2017  							Usage: "上传网络单个连接的最大并发量",
  2018  						},
  2019  						cli.IntFlag{
  2020  							Name:  "max_download_load",
  2021  							Usage: "同时进行下载文件的最大数量",
  2022  						},
  2023  						cli.IntFlag{
  2024  							Name:  "max_upload_load",
  2025  							Usage: "同时进行上传文件的最大数量",
  2026  						},
  2027  						cli.StringFlag{
  2028  							Name:  "max_download_rate",
  2029  							Usage: "限制最大下载速度, 0代表不限制",
  2030  						},
  2031  						cli.StringFlag{
  2032  							Name:  "max_upload_rate",
  2033  							Usage: "限制最大上传速度, 0代表不限制",
  2034  						},
  2035  						cli.StringFlag{
  2036  							Name:  "savedir",
  2037  							Usage: "下载文件的储存目录",
  2038  						},
  2039  						cli.BoolFlag{
  2040  							Name:  "enable_https",
  2041  							Usage: "启用 https",
  2042  						},
  2043  						cli.BoolFlag{
  2044  							Name:  "ignore_illegal",
  2045  							Usage: "忽略上传时文件名中的非法字符",
  2046  						},
  2047  						cli.StringFlag{
  2048  							Name:  "force_login_username",
  2049  							Usage: "强制登录指定用户名, 只适用于tieba接口失效的情况",
  2050  						},
  2051  						cli.BoolFlag{
  2052  							Name:  "no_check",
  2053  							Usage: "关闭下载文件md5校验",
  2054  						},
  2055  						cli.StringFlag{
  2056  							Name:  "upload_policy",
  2057  							Usage: "设置上传遇到同名文件时的策略",
  2058  						},
  2059  						cli.StringFlag{
  2060  							Name:  "user_agent",
  2061  							Usage: "浏览器标识",
  2062  						},
  2063  						cli.StringFlag{
  2064  							Name:  "pcs_ua",
  2065  							Usage: "PCS 浏览器标识",
  2066  						},
  2067  						cli.StringFlag{
  2068  							Name:  "pcs_addr",
  2069  							Usage: "PCS 服务器地址",
  2070  						},
  2071  						cli.StringFlag{
  2072  							Name:  "pan_ua",
  2073  							Usage: "Pan 浏览器标识",
  2074  						},
  2075  						cli.StringFlag{
  2076  							Name:  "proxy",
  2077  							Usage: "设置代理, 支持 http/socks5 代理",
  2078  						},
  2079  						cli.StringFlag{
  2080  							Name:  "local_addrs",
  2081  							Usage: "设置本地网卡地址, 多个地址用逗号隔开",
  2082  						},
  2083  					},
  2084  				},
  2085  				{
  2086  					Name:        "reset",
  2087  					Usage:       "恢复默认配置项",
  2088  					UsageText:   app.Name + " config reset",
  2089  					Description: "",
  2090  					Action: func(c *cli.Context) error {
  2091  						pcsconfig.Config.InitDefaultConfig()
  2092  						err := pcsconfig.Config.Save()
  2093  						if err != nil {
  2094  							fmt.Println(err)
  2095  							return err
  2096  						}
  2097  						pcsconfig.Config.PrintTable()
  2098  						fmt.Println("恢复默认配置成功")
  2099  						return nil
  2100  					},
  2101  				},
  2102  			},
  2103  		},
  2104  		{
  2105  			Name:      "match",
  2106  			Usage:     "测试通配符",
  2107  			UsageText: app.Name + " match <通配符表达式>",
  2108  			Description: `
  2109  	测试通配符匹配路径, 操作成功则输出所有匹配到的路径.
  2110  
  2111  	示例:
  2112  
  2113  	1. 匹配 /我的资源 目录下所有mp4格式的文件
  2114  	BaiduPCS-Go match /我的资源/*.mp4
  2115  `,
  2116  			Category: "百度网盘",
  2117  			Before:   reloadFn,
  2118  			Action: func(c *cli.Context) error {
  2119  				if c.NArg() != 1 {
  2120  					cli.ShowCommandHelp(c, c.Command.Name)
  2121  					return nil
  2122  				}
  2123  
  2124  				pcscommand.RunTestShellPattern(c.Args()[0])
  2125  				return nil
  2126  			},
  2127  		},
  2128  		{
  2129  			Name:  "tool",
  2130  			Usage: "工具箱",
  2131  			Action: func(c *cli.Context) error {
  2132  				cli.ShowCommandHelp(c, c.Command.Name)
  2133  				return nil
  2134  			},
  2135  			Subcommands: []cli.Command{
  2136  				{
  2137  					Name:  "showtime",
  2138  					Usage: "显示当前时间(北京时间)",
  2139  					Action: func(c *cli.Context) error {
  2140  						fmt.Printf(pcstime.BeijingTimeOption("printLog"))
  2141  						return nil
  2142  					},
  2143  				},
  2144  				{
  2145  					Name:  "getip",
  2146  					Usage: "获取IP地址",
  2147  					Action: func(c *cli.Context) error {
  2148  						fmt.Printf("内网IP地址: \n")
  2149  						for _, address := range pcsutil.ListAddresses() {
  2150  							fmt.Printf("%s\n", address)
  2151  						}
  2152  						fmt.Printf("\n")
  2153  
  2154  						ipAddr, err := getip.IPInfoFromTechainBaiduByClient(pcsconfig.Config.HTTPClient())
  2155  						if err != nil {
  2156  							fmt.Printf("获取公网IP错误: %s\n", err)
  2157  							return nil
  2158  						}
  2159  
  2160  						fmt.Printf("公网IP地址: %s\n", ipAddr)
  2161  						return nil
  2162  					},
  2163  				},
  2164  				{
  2165  					Name:        "enc",
  2166  					Usage:       "加密文件",
  2167  					UsageText:   app.Name + " enc -method=<method> -key=<key> [files...]",
  2168  					Description: cryptoDescription,
  2169  					Action: func(c *cli.Context) error {
  2170  						if c.NArg() <= 0 {
  2171  							cli.ShowCommandHelp(c, c.Command.Name)
  2172  							return nil
  2173  						}
  2174  
  2175  						for _, filePath := range c.Args() {
  2176  							encryptedFilePath, err := pcsutil.EncryptFile(c.String("method"), []byte(c.String("key")), filePath, !c.Bool("disable-gzip"))
  2177  							if err != nil {
  2178  								fmt.Printf("%s\n", err)
  2179  								continue
  2180  							}
  2181  
  2182  							fmt.Printf("加密成功, %s -> %s\n", filePath, encryptedFilePath)
  2183  						}
  2184  
  2185  						return nil
  2186  					},
  2187  					Flags: []cli.Flag{
  2188  						cli.StringFlag{
  2189  							Name:  "method",
  2190  							Usage: "加密方法",
  2191  							Value: "aes-128-ctr",
  2192  						},
  2193  						cli.StringFlag{
  2194  							Name:  "key",
  2195  							Usage: "加密密钥",
  2196  							Value: app.Name,
  2197  						},
  2198  						cli.BoolFlag{
  2199  							Name:  "disable-gzip",
  2200  							Usage: "不启用GZIP",
  2201  						},
  2202  					},
  2203  				},
  2204  				{
  2205  					Name:        "dec",
  2206  					Usage:       "解密文件",
  2207  					UsageText:   app.Name + " dec -method=<method> -key=<key> [files...]",
  2208  					Description: cryptoDescription,
  2209  					Action: func(c *cli.Context) error {
  2210  						if c.NArg() <= 0 {
  2211  							cli.ShowCommandHelp(c, c.Command.Name)
  2212  							return nil
  2213  						}
  2214  
  2215  						for _, filePath := range c.Args() {
  2216  							decryptedFilePath, err := pcsutil.DecryptFile(c.String("method"), []byte(c.String("key")), filePath, !c.Bool("disable-gzip"))
  2217  							if err != nil {
  2218  								fmt.Printf("%s\n", err)
  2219  								continue
  2220  							}
  2221  
  2222  							fmt.Printf("解密成功, %s -> %s\n", filePath, decryptedFilePath)
  2223  						}
  2224  
  2225  						return nil
  2226  					},
  2227  					Flags: []cli.Flag{
  2228  						cli.StringFlag{
  2229  							Name:  "method",
  2230  							Usage: "加密方法",
  2231  							Value: "aes-128-ctr",
  2232  						},
  2233  						cli.StringFlag{
  2234  							Name:  "key",
  2235  							Usage: "加密密钥",
  2236  							Value: app.Name,
  2237  						},
  2238  						cli.BoolFlag{
  2239  							Name:  "disable-gzip",
  2240  							Usage: "不启用GZIP",
  2241  						},
  2242  					},
  2243  				},
  2244  			},
  2245  		},
  2246  		{
  2247  			Name:        "clear",
  2248  			Aliases:     []string{"cls"},
  2249  			Usage:       "清空控制台",
  2250  			UsageText:   app.Name + " clear",
  2251  			Description: "清空控制台屏幕",
  2252  			Category:    "其他",
  2253  			Action: func(c *cli.Context) error {
  2254  				pcsliner.ClearScreen()
  2255  				return nil
  2256  			},
  2257  		},
  2258  		{
  2259  			Name:    "quit",
  2260  			Aliases: []string{"exit"},
  2261  			Usage:   "退出程序",
  2262  			Action: func(c *cli.Context) error {
  2263  				return cli.NewExitError("", 0)
  2264  			},
  2265  			Hidden:   true,
  2266  			HideHelp: true,
  2267  		},
  2268  	}
  2269  
  2270  	sort.Sort(cli.FlagsByName(app.Flags))
  2271  	sort.Sort(cli.CommandsByName(app.Commands))
  2272  
  2273  	app.Run(os.Args)
  2274  }