github.com/fzfile/BaiduPCS-Go@v0.0.0-20200606205115-4408961cf336/main.go (about)

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