Skip to content

Commit

Permalink
integrate 'addlocal' cmd to 'add' cmd
Browse files Browse the repository at this point in the history
  • Loading branch information
sagan committed Feb 19, 2024
1 parent 0d04736 commit ce3a5f7
Show file tree
Hide file tree
Showing 8 changed files with 156 additions and 289 deletions.
29 changes: 13 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,8 @@ ptool <command> args... [flags]
- status : 显示 BT 客户端或 PT 站点当前状态信息。
- stats : 显示刷流任务流量统计。
- search : 在某个站点搜索指定关键词的种子。
- add : 将某个站点的指定种子添加到 BT 客户端。
- add : 将种子添加到 BT 客户端。
- dltorrent : 下载站点的种子。
- addlocal : 将本地的种子文件添加到 BT 客户端。
- BT 客户端控制命令集: clientctl / show / pause / resume / delete / reannounce / recheck / getcategories / createcategory / removecategories / setcategory / gettags / createtags / deletetags / addtags / removetags / edittracker / addtrackers / removetrackers / setsavepath。
- parsetorrent : 显示种子(torrent)文件信息。
- verifytorrent : 测试种子(torrent)文件与硬盘上的文件内容一致。
Expand Down Expand Up @@ -153,7 +152,7 @@ ptool brush local mteam

其它说明:

- No-Add 模式:如果 BT 客户端里当前存在 "\_noadd" 这个标签(tag),刷流任务不会添加任何新种子到客户端。
- No-Add 模式:如果 BT 客户端里当前存在 `_noadd` 这个标签(tag),刷流任务不会添加任何新种子到客户端。

### 自动辅种 (iyuu)

Expand Down Expand Up @@ -348,13 +347,19 @@ ptool stats [client...]

只有刷流任务添加和管理的 BT 客户端的种子(即 `_brush` 分类的种子)的流量信息会被记录和统计。目前设计只有在刷流任务从 BT 客户端删除某个种子时才会记录和统计该种子产生的流量信息。

### 添加站点种子到 BT 客户端 (add)
### 添加种子到 BT 客户端 (add)

```
ptool add <client> <torrentIdOrUrl>...
ptool add <client> <torrentFileNameOrIdOrUrl>...
```

示例:
参数可以是本地硬盘里的种子文件名(支持 `*` 通配符)、站点的种子 id 或 url。例如:

```
ptool add local *.torrent
```

以上命令将当前目录下所有 ".torrent" 种子文件添加到 "local" BT 客户端。

```
ptool add local mteam.488424
Expand All @@ -371,20 +376,12 @@ ptool add local "https://kp.m-team.cc/download.php?id=488424"
ptool dltorrent <torrentIdOrUrl>...
```

类似 add 命令,但只会将种子下载到本地
将站点的种子文件下载到本地。参数是站点的种子 id 或 url(参考上面 "add" 命令

参数
可选参数

- --download-dir : 下载的种子文件保存路径。默认为当前目录(CWD)。

### 添加本地种子到 BT 客户端 (addlocal)

```
ptool addlocal <client> <filename.torrent>...
```

将本地硬盘里的种子文件添加到 BT 客户端。种子文件名支持使用 _ 通配符,例如 "_.torrent"。

### 搜索 PT 站点种子 (search)

```
Expand Down
194 changes: 136 additions & 58 deletions cmd/add/add.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package add

import (
"fmt"
"io"
"os"
"strings"

log "github.com/sirupsen/logrus"
Expand All @@ -16,19 +18,26 @@ import (
)

var command = &cobra.Command{
Use: "add {client} {torrentId | torrentUrl}...",
Use: "add {client} {torrentFilename | torrentId | torrentUrl}...",
Aliases: []string{"addlocal"},
Annotations: map[string]string{"cobra-prompt-dynamic-suggestions": "add"},
Short: "Add site torrents to client.",
Long: `Add site torrents to client.`,
Args: cobra.MatchAll(cobra.MinimumNArgs(2), cobra.OnlyValidArgs),
RunE: add,
Short: "Add torrents to client.",
Long: `Add torrents to client.
Each arg could be a local filename (e.g. "*.torrent" or "[M-TEAM]CLANNAD (2007).torrent"),
torrent id (e.g.: "mteam.488424"), or torrent url (e.g.: "https://kp.m-team.cc/details.php?id=488424").`,
Args: cobra.MatchAll(cobra.MinimumNArgs(2), cobra.OnlyValidArgs),
RunE: add,
}

var (
addCategoryAuto = false
addPaused = false
skipCheck = false
sequentialDownload = false
renameAdded = false
deleteAdded = false
forceLocal = false
rename = ""
addCategory = ""
defaultSite = ""
addTags = ""
Expand All @@ -42,9 +51,14 @@ func init() {
"Automatically set category of added torrent to corresponding sitename")
command.Flags().BoolVarP(&sequentialDownload, "sequential-download", "", false,
"(qbittorrent only) Enable sequential download")
command.Flags().BoolVarP(&renameAdded, "rename-added", "", false,
"Rename successfully added *.torrent file to *.torrent.added")
command.Flags().BoolVarP(&deleteAdded, "delete-added", "", false, "Delete successfully added *.torrent file")
command.Flags().BoolVarP(&forceLocal, "force-local", "", false, "Force treat all arg as local torrent filename")
command.Flags().StringVarP(&rename, "rename", "", "", "Rename added torrent (for adding single torrent only)")
command.Flags().StringVarP(&addCategory, "add-category", "", "", "Set category of added torrents")
command.Flags().StringVarP(&savePath, "add-save-path", "", "", "Set save path of added torrents")
command.Flags().StringVarP(&defaultSite, "site", "", "", "Set default site of torrents")
command.Flags().StringVarP(&defaultSite, "site", "", "", "Set default site of added torrents")
command.Flags().StringVarP(&addTags, "add-tags", "", "", "Add tags to added torrent (comma-separated)")
cmd.RootCmd.AddCommand(command)
command2.Flags().AddFlagSet(command.Flags())
Expand All @@ -53,80 +67,124 @@ func init() {

func add(cmd *cobra.Command, args []string) error {
clientName := args[0]
torrentIds := args[1:]
if renameAdded && deleteAdded {
return fmt.Errorf("--rename-added and --delete-added flags are NOT compatible")
}
torrents := util.ParseFilenameArgs(args[1:]...)
if rename != "" && len(torrents) > 1 {
return fmt.Errorf("--rename flag can only be used with exact one torrent arg")
}
clientInstance, err := client.CreateClient(clientName)
if err != nil {
return fmt.Errorf("failed to create client: %v", err)
}
domainSiteMap := map[string]string{}
siteInstanceMap := map[string]site.Site{}
errorCnt := int64(0)
option := &client.TorrentOption{
Pause: addPaused,
SavePath: savePath,
SkipChecking: skipCheck,
SequentialDownload: sequentialDownload,
Name: rename,
}
var fixedTags []string
if addTags != "" {
fixedTags = strings.Split(addTags, ",")
}
domainSiteMap := map[string]string{}
siteInstanceMap := map[string]site.Site{}
cntError := int64(0)
cntAdded := int64(0)
sizeAdded := int64(0)
cntAll := len(torrents)

for _, torrentId := range torrentIds {
siteName := defaultSite
if !util.IsUrl(torrentId) {
i := strings.Index(torrentId, ".")
if i != -1 && i < len(torrentId)-1 {
siteName = torrentId[:i]
torrentId = torrentId[i+1:]
for i, torrent := range torrents {
var isLocal bool
var siteName string
var torrentContent []byte
var err error
var hr bool
if forceLocal || torrent == "-" || !util.IsUrl(torrent) && strings.HasSuffix(torrent, ".torrent") {
isLocal = true
}
if !isLocal {
// site torrent
isLocal = false
siteName = defaultSite
if !util.IsUrl(torrent) {
i := strings.Index(torrent, ".")
if i != -1 && i < len(torrent)-1 {
siteName = torrent[:i]
torrent = torrent[i+1:]
}
} else {
domain := util.GetUrlDomain(torrent)
if domain == "" {
fmt.Printf("✕add (%d/%d) %s error: failed to parse domain", i+1, cntAll, torrent)
cntError++
continue
}
sitename := ""
ok := false
if sitename, ok = domainSiteMap[domain]; !ok {
domainSiteMap[domain], err = tpl.GuessSiteByDomain(domain, defaultSite)
if err != nil {
log.Warnf("Failed to find match site for %s: %v", domain, err)
}
sitename = domainSiteMap[domain]
}
if sitename == "" {
log.Warnf("Torrent %s: url does not match any site. will use provided default site", torrent)
} else {
siteName = sitename
}
}
} else {
domain := util.GetUrlDomain(torrentId)
if domain == "" {
fmt.Printf("✕torrent %s: failed to parse domain", torrentId)
errorCnt++
if siteName == "" {
fmt.Printf("✕add (%d/%d) %s error: no site found or provided\n", i+1, cntAll, torrent)
cntError++
continue
}
sitename := ""
ok := false
if sitename, ok = domainSiteMap[domain]; !ok {
domainSiteMap[domain], err = tpl.GuessSiteByDomain(domain, defaultSite)
if siteInstanceMap[siteName] == nil {
siteInstance, err := site.CreateSite(siteName)
if err != nil {
log.Warnf("Failed to find match site for %s: %v", domain, err)
return fmt.Errorf("failed to create site %s: %v", siteName, err)
}
sitename = domainSiteMap[domain]
siteInstanceMap[siteName] = siteInstance
}
if sitename == "" {
log.Warnf("Torrent %s: url does not match any site. will use provided default site", torrentId)
} else {
siteName = sitename
siteInstance := siteInstanceMap[siteName]
hr = siteInstance.GetSiteConfig().GlobalHnR
torrentContent, _, err = siteInstance.DownloadTorrent(torrent)
} else {
isLocal = true
if strings.HasSuffix(torrent, ".added") {
fmt.Printf("-skip (%d/%d) %s\n", i+1, cntAll, torrent)
continue
}
}
if siteName == "" {
fmt.Printf("✕torrent %s: no site found or provided\n", torrentId)
errorCnt++
continue
}
if siteInstanceMap[siteName] == nil {
siteInstance, err := site.CreateSite(siteName)
if err != nil {
return fmt.Errorf("failed to create site %s: %v", siteName, err)
if torrent == "-" {
torrentContent, err = io.ReadAll(os.Stdin)
} else {
torrentContent, err = os.ReadFile(torrent)
}
siteInstanceMap[siteName] = siteInstance
}
siteInstance := siteInstanceMap[siteName]
torrentContent, _, err := siteInstance.DownloadTorrent(torrentId)

if err != nil {
fmt.Printf("✕add site %s torrent %s error: failed to get site torrent: %v\n", siteInstance.GetName(), torrentId, err)
errorCnt++
fmt.Printf("✕add (%d/%d) %s (site=%s) error: failed to get torrent: %v\n",
i+1, cntAll, torrent, siteName, err)
cntError++
continue
}
tinfo, err := torrentutil.ParseTorrent(torrentContent, 99)
if err != nil {
fmt.Printf("✕add site %s torrent %s error: failed to parse torrent: %v\n", siteInstance.GetName(), torrentId, err)
errorCnt++
fmt.Printf("✕add (%d/%d) %s (site=%s) error: failed to parse torrent: %v\n",
i+1, cntAll, torrent, siteName, err)
cntError++
continue
}
if siteName == "" {
if sitename, err := tpl.GuessSiteByTrackers(tinfo.Trackers, defaultSite); err != nil {
log.Warnf("Failed to find match site for %s by trackers: %v", torrent, err)
} else {
siteName = sitename
}
}
if addCategoryAuto {
if siteName != "" {
option.Category = siteName
Expand All @@ -138,30 +196,50 @@ func add(cmd *cobra.Command, args []string) error {
} else {
option.Category = addCategory
}
option.Tags = []string{client.GenerateTorrentTagFromSite(siteName)}
option.Tags = append(option.Tags, fixedTags...)
if siteInstance.GetSiteConfig().GlobalHnR {
if siteName != "" {
option.Tags = append(option.Tags, client.GenerateTorrentTagFromSite(siteName))
}
if hr {
option.Tags = append(option.Tags, "_hr")
}
option.Tags = append(option.Tags, fixedTags...)
err = clientInstance.AddTorrent(torrentContent, option, nil)
if err != nil {
fmt.Printf("✕add site %s torrent %s error: failed to add torrent to client: %v // %s\n", siteInstance.GetName(), torrentId, err, tinfo.ContentPath)
errorCnt++
fmt.Printf("✕add (%d/%d) %s (site=%s) error: failed to add torrent to client: %v // %s\n",
i+1, cntAll, torrent, siteName, err, tinfo.ContentPath)
cntError++
continue
}
fmt.Printf("✓add site %s torrent %s success. infoHash=%s // %s\n", siteInstance.GetName(), torrentId, tinfo.InfoHash, tinfo.ContentPath)
if isLocal {
if renameAdded {
if err := os.Rename(torrent, torrent+".added"); err != nil {
log.Debugf("Failed to rename %s to *.added: %v // %s", torrent, err, tinfo.ContentPath)
}
} else if deleteAdded {
if err := os.Remove(torrent); err != nil {
log.Debugf("Failed to delete %s: %v // %s", torrent, err, tinfo.ContentPath)
}
}
}
cntAdded++
sizeAdded += tinfo.Size
fmt.Printf("✓add (%d/%d) %s (site=%s) success. infoHash=%s // %s\n",
i+1, cntAll, torrent, siteName, tinfo.InfoHash, tinfo.ContentPath)
}
if errorCnt > 0 {
return fmt.Errorf("%d errors", errorCnt)
fmt.Printf("\nDone. Added torrent (Size/Cnt): %s / %d; ErrorCnt: %d\n",
util.BytesSize(float64(sizeAdded)), cntAdded, cntError)
if cntError > 0 {
return fmt.Errorf("%d errors", cntError)
}
return nil
}

var command2 = &cobra.Command{
Use: "add2 [args]",
Short: `Alias of "add --add-category-auto [args]"`,
Short: `Alias of "add --add-category-auto --sequential-download [args]"`,
RunE: func(cmd *cobra.Command, args []string) error {
addCategoryAuto = true
sequentialDownload = true
return command.RunE(cmd, args)
},
}
4 changes: 2 additions & 2 deletions cmd/add/suggest.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ func init() {
return nil
}
}
if info.LastArgIndex != 1 {
return nil
if info.LastArgIndex > 1 {
return suggest.FileArg(info.MatchingPrefix, ".torrent", false)
}
return suggest.ClientArg(info.MatchingPrefix)
})
Expand Down

0 comments on commit ce3a5f7

Please sign in to comment.