作者:E4b9a6, 创建:2024-12-03, 字数:6082, 已阅:229, 最后更新:2024-12-03
在使用 qBittorrent 的 RSS 订阅功能后,可以实现自动下载当前热门的新种子来赚上传量
但 QBitorrent 的自动删除的功能比较鸡肋,只有分享率大于某个值或者做种时间超过某个值时自动删除
抱着赚取上传量的念头这两个都不是很合适,查阅了资料,发现可以借助 Golang 的代码来调用 QBitorrent 的 API 来实现自定义删除规则
我需要的规则如下:
实现后,已将代码和打包好的二进制程序发布到 github ,仓库如下:
使用方法是从 Releases 中下载系统对应的客户端版本,然后执行如下:
qBittorrent-manager --username=admin --password=admin
在 crontab 中设置循环 7 天执行,则每 7 天检查一次上传率的增长情况,若当前轮次增长少于 0.5 则删除该种子以及种子所下载的文件
代码逻辑如下:
package main
import (
"bytes"
"encoding/json"
"errors"
"flag"
"fmt"
"net/http"
"net/http/cookiejar"
"os"
"time"
)
// Torrent 结构体定义
type Torrent struct {
Hash string `json:"hash"`
Name string `json:"name"`
Ratio float64 `json:"ratio"`
Addon int64 `json:"added_on"`
}
// 登录并获取会话 cookie
func login(client *http.Client, qbURL, username, password string) error {
loginData := fmt.Sprintf("username=%s&password=%s", username, password)
req, err := http.NewRequest("POST", qbURL+"/api/v2/auth/login", bytes.NewBufferString(loginData))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("failed to login, status code: %d", resp.StatusCode)
}
fmt.Println("Logged in successfully.")
return nil
}
// 获取当前所有种子的状态
func getTorrents(client *http.Client, qbURL string) ([]Torrent, error) {
req, err := http.NewRequest("GET", qbURL+"/api/v2/torrents/info", nil)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to fetch torrents info, status code: %d", resp.StatusCode)
}
var torrents []Torrent
if err := json.NewDecoder(resp.Body).Decode(&torrents); err != nil {
return nil, err
}
fmt.Println("Torrents information fetched successfully.")
return torrents, nil
}
// 删除种子
func deleteTorrent(client *http.Client, qbURL, hash string) error {
data := fmt.Sprintf("hashes=%s&deleteFiles=true", hash)
req, err := http.NewRequest("POST", qbURL+"/api/v2/torrents/delete", bytes.NewBufferString(data))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("failed to delete torrent, status code: %d", resp.StatusCode)
}
return nil
}
func searchTorrentOnLocal(hash, recordFile string) (Torrent, error) {
var torrent Torrent
var torrents []Torrent
if _, err := os.Stat(recordFile); os.IsNotExist(err) {
fmt.Println("No existing torrents found. Starting fresh.")
return torrent, err
}
data, err := os.ReadFile(recordFile)
if err != nil {
return torrent, err
}
if err := json.Unmarshal(data, &torrents); err != nil {
return torrent, err
}
for _, torrent := range torrents {
if torrent.Hash == hash {
return torrent, nil
}
}
return torrent, errors.New("hash Not Found")
}
// 保存记录
func saveTorrentToLocal(torrents []Torrent, recordFile string) error {
data, err := json.MarshalIndent(torrents, "", " ")
if err != nil {
return err
}
if err := os.WriteFile(recordFile, data, 0644); err != nil {
return err
}
fmt.Printf("Records saved to %s.\n", recordFile)
return nil
}
func main() {
// 定义命令行参数
qbURL := flag.String("url", "http://localhost:8080", "The URL of the qBittorrent Web UI")
username := flag.String("username", "admin", "The username for qBittorrent Web UI")
password := flag.String("password", "adminadmin", "The password for qBittorrent Web UI")
recordFile := flag.String("recordFile", "torrent-records.json", "The file to save torrent records")
ratioIncrease := flag.Float64("ratioIncrease", 0.5, "The radio increases each time")
protectionPeriod := flag.Int("protectionPeriod", 7, "The protection period for torrent")
try := flag.Bool("try", false, "Display the deletion target without actually deleting the torrent")
// 解析命令行参数
flag.Parse()
// 创建一个 cookie jar 来管理会话
jar, err := cookiejar.New(nil)
if err != nil {
fmt.Printf("Error creating cookie jar: %v\n", err)
return
}
// 创建 HTTP 客户端并设置 cookie jar
client := &http.Client{
Jar: jar,
}
// 执行登录
if err := login(client, *qbURL, *username, *password); err != nil {
fmt.Printf("Error: %v\n", err)
return
}
// 获取种子信息
torrents, err := getTorrents(client, *qbURL)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
// 检查上传率
nowTime := time.Now()
for _, torrent := range torrents {
fmt.Printf("Processing torrent <%s>\n", torrent.Name)
if record, err := searchTorrentOnLocal(torrent.Hash, *recordFile); err != nil {
// 种子在本地没有记录则跳过
fmt.Printf("Torrent <%s> name not found\n", torrent.Name)
continue
} else {
// 保护期内的种子不会被删除
addTime := time.Unix(torrent.Addon, 0)
daysDiffDays := int(nowTime.Sub(addTime).Hours() / 24)
if daysDiffDays < *protectionPeriod {
fmt.Printf("Torrent <%s> not more than %d days\n", torrent.Name, *protectionPeriod)
continue
}
// 比对分享率增长
fmt.Printf("Torrent <%s> local record ratio %.2f and torrent current radio %.2f\n", record.Name, record.Ratio, torrent.Ratio)
if torrent.Ratio-record.Ratio < *ratioIncrease {
// 删除分享率上升不足的种子
fmt.Printf("Deleting torrent <%s> due to insufficient share ratio increase(radio increase %.2f).\n", torrent.Name, torrent.Ratio-record.Ratio)
if err := deleteTorrent(client, *qbURL, torrent.Hash); err != nil && !*try {
fmt.Printf("Failed to delete torrent %s error: %v\n", torrent.Name, err)
}
}
}
}
// 保存本次查询结果,方便下次查询比对分享率的增长情况
if err := saveTorrentToLocal(torrents, *recordFile); err != nil {
fmt.Printf("Error saving torrents to local file: %v\n", err)
return
}
fmt.Println("All records processed and updated.")
}