Skip to content

Commit

Permalink
add 'config' and 'renametag' cmd
Browse files Browse the repository at this point in the history
  • Loading branch information
sagan committed Feb 22, 2024
1 parent 4b0b002 commit 443e4ad
Show file tree
Hide file tree
Showing 14 changed files with 400 additions and 15 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,13 @@
# Dependency directories (remove the comment below to include it)
# vendor/

# config file
# config files
ptool.toml
ptool.yaml
ptool.lock
ptool_history
ptool_stats.txt
iyuu.db

# local
devtest/
Expand Down
16 changes: 10 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

## 快速开始(刷流)

将本程序的可执行文件 ptool (Linux) 或 ptool.exe (Windows) 放到任意目录,在同目录下创建名为 "ptool.toml" 的配置文件,内容示例如下
将本程序的可执行文件 ptool (Linux) 或 ptool.exe (Windows) 放到任意目录(推荐放到 PATH 路径里),运行 `ptool config create` 创建本程序使用的 ptool.toml 配置文件。创建的文件位于当前系统用户主目录的 `.config/ptool/` 路径下。编辑这个文件配置 BT 客户端和 PT 站点信息

```toml
[[clients]]
Expand All @@ -40,7 +40,7 @@ type = "mteam"
cookie = "cookie_here" # 浏览器 F12 获取的网站 cookie
```

然后在当前目录下运行 `ptool brush local mteam` 即可执行刷流任务。程序会从 M-Team 获取最新的种子、根据一定规则筛选出适合的种子添加到本地的 qBittorrent 客户端里,同时自动从 BT 客户端里删除(已经没有上传的)旧的刷流种子。刷流任务添加到客户端里的种子会放到 `_brush` 分类(Category)里。程序只会对这个分类里的种子进行管理或删除等操作。
然后运行 `ptool brush local mteam` 即可执行刷流任务。程序会从 M-Team 获取最新的种子、根据一定规则筛选出适合的种子添加到本地的 qBittorrent 客户端里,同时自动从 BT 客户端里删除(已经没有上传的)旧的刷流种子。刷流任务添加到客户端里的种子会放到 `_brush` 分类(Category)里。程序只会对这个分类里的种子进行管理或删除等操作。

使用 Linux cron job / Windows 计划任务 (taskschd.msc) 等方式定时执行上面的刷流任务命令(例如每隔 10 分钟执行一次)即可。

Expand Down Expand Up @@ -80,7 +80,7 @@ cookie = "cookie_here" # 浏览器 F12 获取的网站 cookie

程序支持自动与浏览器同步站点 Cookies 或导入站点信息。详细信息请参考本文档 "cookiecloud" 命令说明部分。

参考程序代码根目录下的 `ptool.example.toml``ptool.example.yaml` 示例配置文件了解常用配置项信息。
参考程序代码 config/ 目录下的 `ptool.example.toml``ptool.example.yaml` 示例配置文件了解常用配置项信息。

查看程序代码 [config/config.go](https://github.com/sagan/ptool/blob/master/config/config.go) 文件里的 type ConfigStruct struct 获取全部可配置项信息。

Expand All @@ -102,12 +102,13 @@ ptool <command> args... [flags]
- search : 在某个站点搜索指定关键词的种子。
- add : 将种子添加到 BT 客户端。
- dltorrent : 下载站点的种子。
- BT 客户端控制命令集: clientctl / show / pause / resume / delete / reannounce / recheck / getcategories / createcategory / removecategories / setcategory / gettags / createtags / deletetags / addtags / removetags / edittracker / addtrackers / removetrackers / setsavepath。
- BT 客户端控制命令集: clientctl / show / pause / resume / delete / reannounce / recheck / getcategories / createcategory / removecategories / setcategory / gettags / createtags / deletetags / addtags / removetags / renametag / edittracker / addtrackers / removetrackers / setsavepath。
- parsetorrent : 显示种子(torrent)文件信息。
- verifytorrent : 测试种子(torrent)文件与硬盘上的文件内容一致。
- partialdownload : 拆包下载。
- cookiecloud (v0.1.8+): 使用 [CookieCloud](https://github.com/easychen/CookieCloud) 同步站点的 Cookies 或导入站点。
- cookiecloud : 使用 [CookieCloud](https://github.com/easychen/CookieCloud) 同步站点的 Cookies 或导入站点。
- sites : 显示本程序内置支持的所有 PT 站点列表。
- config : 显示当前 ptool.toml 配置文件信息。
- shell : 进入交互式终端环境。
- version : 显示本程序版本信息。

Expand Down Expand Up @@ -275,7 +276,7 @@ ptool delete local 31a615d5984cb63c6f999f72bb3961dce49c194a
ptool show local 31a615d5984cb63c6f999f72bb3961dce49c194a
```

#### 管理 BT 客户端里的的种子分类 / 标签 / Trackers 等(getcategories / createcategory / removecategories / setcategory / gettags / createtags / deletetags / addtags / removetags / edittracker / addtrackers / removetrackers / setsavepath)
#### 管理 BT 客户端里的的种子分类 / 标签 / Trackers 等(getcategories / createcategory / removecategories / setcategory / gettags / createtags / deletetags / addtags / removetags / renametag / edittracker / addtrackers / removetrackers / setsavepath)

```
# 获取所有分类
Expand Down Expand Up @@ -305,6 +306,9 @@ ptool addtags <client> <tags> <infoHashes>...
# 为客户端里种子删除tag
ptool removetags <client> <tags> <infoHashes>...
# 重命名客户端里的 tag
ptool renametag <client> <old-tag> <new-tag>
# 修改种子的 tracker。只有 old tracker 存在的种子会被修改
ptool edittracker <client> _all --old-tracker "https://..." --new-tracker "https://..."
Expand Down
2 changes: 1 addition & 1 deletion client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -635,7 +635,7 @@ func Exit() {
defer resourcesWaitGroup.Done()
log.Tracef("Close client %s instance", clientName)
clientInstance.Close()
delete(clients, clientName)
// delete(clients, clientName) // may lead to race condition
}(clientName, clientInstance)
}
resourcesWaitGroup.Wait()
Expand Down
2 changes: 2 additions & 0 deletions cmd/all/all.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
_ "github.com/sagan/ptool/cmd/batchdl"
_ "github.com/sagan/ptool/cmd/brush"
_ "github.com/sagan/ptool/cmd/clientctl"
_ "github.com/sagan/ptool/cmd/configcmd/all"
_ "github.com/sagan/ptool/cmd/cookiecloud/all"
_ "github.com/sagan/ptool/cmd/createcategory"
_ "github.com/sagan/ptool/cmd/createtags"
Expand All @@ -27,6 +28,7 @@ import (
_ "github.com/sagan/ptool/cmd/removecategories"
_ "github.com/sagan/ptool/cmd/removetags"
_ "github.com/sagan/ptool/cmd/removetrackers"
_ "github.com/sagan/ptool/cmd/renametag"
_ "github.com/sagan/ptool/cmd/resume"
_ "github.com/sagan/ptool/cmd/run"
_ "github.com/sagan/ptool/cmd/search"
Expand Down
6 changes: 6 additions & 0 deletions cmd/configcmd/all/all.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package all

import (
_ "github.com/sagan/ptool/cmd/configcmd"
_ "github.com/sagan/ptool/cmd/configcmd/create"
)
189 changes: 189 additions & 0 deletions cmd/configcmd/configcmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
package configcmd

// @todo (maybe): implement an interactive config mode (like "rclone config")

import (
"fmt"
"os"
"path"
"path/filepath"
"sort"
"strings"

"github.com/spf13/cobra"

"github.com/sagan/ptool/client"
"github.com/sagan/ptool/cmd"
"github.com/sagan/ptool/config"
"github.com/sagan/ptool/site"
"github.com/sagan/ptool/site/tpl"
"github.com/sagan/ptool/util"
)

var Command = &cobra.Command{
Use: "config",
Short: "Display or manage config file contents.",
Long: `Display or manage config file contents.`,
Args: cobra.MatchAll(cobra.ExactArgs(0), cobra.OnlyValidArgs),
RunE: configcmd,
}

var (
filter = ""
)

func init() {
Command.Flags().StringVarP(&filter, "filter", "", "", "Only show config item which name or note contains this")
cmd.RootCmd.AddCommand(Command)
}

func configcmd(cmd *cobra.Command, args []string) error {
fmt.Printf("Config file: %s%c%s\n", config.ConfigDir, filepath.Separator, config.ConfigFile)
if _, err := os.Stat(path.Join(config.ConfigDir, config.ConfigFile)); err != nil {
if os.IsNotExist(err) {
fmt.Printf("<config file not exists>\n")
} else {
fmt.Printf("<config file can not be accessed: %v>\n", err)
}
return nil
}
clients := util.CopySlice(config.Get().Clients)
sites := util.CopySlice(config.Get().Sites)
groups := util.CopySlice(config.Get().Groups)
aliases := util.CopySlice(config.Get().Aliases)
cookieclouds := util.CopySlice(config.Get().Cookieclouds)
sort.Slice(clients, func(i, j int) bool {
return clients[i].Name < clients[j].Name
})
sort.Slice(sites, func(i, j int) bool {
return sites[i].Name < sites[j].Name
})
sort.Slice(groups, func(i, j int) bool {
return groups[i].Name < groups[j].Name
})
sort.Slice(aliases, func(i, j int) bool {
return aliases[i].Name < aliases[j].Name
})
sort.Slice(cookieclouds, func(i, j int) bool {
if cookieclouds[i].Name != cookieclouds[j].Name {
return cookieclouds[i].Name < cookieclouds[j].Name
}
if cookieclouds[i].Server != cookieclouds[j].Server {
return cookieclouds[i].Server < cookieclouds[j].Server
}
return cookieclouds[i].Uuid < cookieclouds[j].Uuid
})
fmt.Printf("All: %d clients, %d sites, %d groups, %d cookiecloud profiles, %d aliases\n",
len(clients), len(sites), len(groups), len(cookieclouds), len(aliases))
emptyListPlaceholder := "<none found>\n"
emptyFlag := false
if filter != "" {
emptyListPlaceholder = "<none matched found>\n"
fmt.Printf("Applying filter '%s'\n", filter)
} else {
fmt.Printf(`To filter, use "--filter string" flag` + "\n")
}
fmt.Printf("\n")

fmt.Printf(`Clients: (test: "ptool status <name> -t")` + "\n")
fmt.Printf("%-15s %-15s %-s\n", "Name", "Type", "Note")
emptyFlag = true
for _, clientConfig := range clients {
note := ""
if filter != "" && !clientConfig.MatchFilter(filter) {
continue
}
emptyFlag = false
if clientInstance, err := client.CreateClient(clientConfig.Name); err != nil {
note = fmt.Sprintf("<error>: %v", err)
} else {
note = clientInstance.GetClientConfig().Url
}
fmt.Printf("%-15s %-15s %-s\n", clientConfig.Name, clientConfig.Type, note)
}
if emptyFlag {
fmt.Print(emptyListPlaceholder)
}
fmt.Printf("\n")

fmt.Printf(`Sites: (internal: *) (test: "ptool status <name> -t")` + "\n")
fmt.Printf("%-15s %-15s %-15s %-s\n", "Name", "Type", "Flags", "Note")
emptyFlag = true
for _, siteConfig := range sites {
flags := []string{}
note := ""
show := filter == "" || siteConfig.MatchFilter(filter)
for id := range tpl.SITES {
if id == siteConfig.Type {
flags = append(flags, "*")
break
}
}
if siteInstance, err := site.CreateSite(siteConfig.GetName()); err != nil {
flags = append(flags, "<error>")
note = fmt.Sprintf("%v", err)
} else {
note = siteInstance.GetSiteConfig().Url
if util.ContainsI(note, filter) {
show = true
}
}
if !show {
continue
}
emptyFlag = false
fmt.Printf("%-15s %-15s %-15s %-s\n", siteConfig.GetName(), siteConfig.Type, strings.Join(flags, ", "), note)
}
if emptyFlag {
fmt.Print(emptyListPlaceholder)
}
fmt.Printf("\n")

fmt.Printf("Groups:\n")
fmt.Printf("%-15s %-s\n", "Name", "Sites")
emptyFlag = true
for _, groupConfig := range groups {
if filter != "" && !groupConfig.MatchFilter(filter) {
continue
}
emptyFlag = false
fmt.Printf("%-15s %-s\n", groupConfig.Name, strings.Join(groupConfig.Sites, ", "))
}
if emptyFlag {
fmt.Print(emptyListPlaceholder)
}
fmt.Printf("\n")

fmt.Printf(`Cookiecloud profiles: (test: "ptool cookiecloud status")` + "\n")
fmt.Printf("%-15s %-15s %-15s %s\n", "Name", "Server", "Uuid", "Sites")
emptyFlag = true
for _, cookiecloud := range cookieclouds {
if filter != "" && !cookiecloud.MatchFilter(filter) {
continue
}
emptyFlag = false
fmt.Printf("%-15s %-15s %-15s %s\n", cookiecloud.Name, util.GetUrlDomain(cookiecloud.Server),
cookiecloud.Uuid, strings.Join(cookiecloud.Sites, ", "))
}
if emptyFlag {
fmt.Print(emptyListPlaceholder)
}
fmt.Printf("\n")

fmt.Printf("Aliases:\n")
fmt.Printf("%-15s %-s\n", "Name", "Cmd")
emptyFlag = true
for _, aliasConfig := range aliases {
if filter != "" && !aliasConfig.MatchFilter(filter) {
continue
}
emptyFlag = false
fmt.Printf("%-15s %-s\n", aliasConfig.Name, aliasConfig.Cmd)
}
if emptyFlag {
fmt.Print(emptyListPlaceholder)
}
fmt.Printf("\n")

return nil
}
28 changes: 28 additions & 0 deletions cmd/configcmd/create/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package create

import (
"fmt"
"path/filepath"

"github.com/spf13/cobra"

"github.com/sagan/ptool/cmd/configcmd"
"github.com/sagan/ptool/config"
)

var command = &cobra.Command{
Use: "create",
Short: "Create initial pool config file.",
Long: `Create initial pool config file.`,
Args: cobra.MatchAll(cobra.ExactArgs(0), cobra.OnlyValidArgs),
RunE: create,
}

func init() {
configcmd.Command.AddCommand(command)
}

func create(cmd *cobra.Command, args []string) error {
fmt.Printf("Creating config file %s%c%s\n", config.ConfigDir, filepath.Separator, config.ConfigFile)
return config.CreateDefaultConfig()
}
61 changes: 61 additions & 0 deletions cmd/renametag/renametag.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package renametag

import (
"fmt"
"strings"

"github.com/spf13/cobra"

"github.com/sagan/ptool/client"
"github.com/sagan/ptool/cmd"
"github.com/sagan/ptool/util"
)

var command = &cobra.Command{
Use: "renametag {client} {old-tag(s)} {new-tag}",
Annotations: map[string]string{"cobra-prompt-dynamic-suggestions": "renametag"},
Short: "Rename tag in client.",
Long: `Rename tag in client.
Note currently it works by adding new tag to torrents and then deleting old tag(s) from client.
{old-tag(s)}: comma-separated list, all tags in list will be "renamed" to new tag`,
Args: cobra.MatchAll(cobra.ExactArgs(3), cobra.OnlyValidArgs),
RunE: renametag,
}

func init() {
cmd.RootCmd.AddCommand(command)
}

func renametag(cmd *cobra.Command, args []string) error {
clientName := args[0]
oldTag := args[1]
newTag := args[2]
if oldTag == "" || newTag == "" {
return fmt.Errorf("old-tag and new-tag can NOT be empty")
}
clientInstance, err := client.CreateClient(clientName)
if err != nil {
return fmt.Errorf("failed to create client: %v", err)
}
torrents, err := client.QueryTorrents(clientInstance, "", oldTag, "")
if err != nil {
return fmt.Errorf("failed to query client torrents of old-tag: %v", err)
}
if len(torrents) > 0 {
infoHashes := util.Map(torrents, func(t client.Torrent) string { return t.InfoHash })
err = clientInstance.AddTagsToTorrents(infoHashes, []string{newTag})
if err != nil {
return fmt.Errorf("failed to add new-tag to client torrents: %v", err)
}
} else {
err = clientInstance.CreateTags(newTag)
if err != nil {
return fmt.Errorf("failed to create new-tag in client: %v", err)
}
}
err = clientInstance.DeleteTags(strings.Split(oldTag, ",")...)
if err != nil {
return fmt.Errorf("failed to delete old-tag(s) from client: %v", err)
}
return nil
}

0 comments on commit 443e4ad

Please sign in to comment.