87 lines
2.3 KiB
Go
87 lines
2.3 KiB
Go
package main
|
|
|
|
import (
|
|
"log/slog"
|
|
"os"
|
|
"regexp"
|
|
"time"
|
|
|
|
"git.sbstp.ca/sbstp/qbittorrent-sentinel/api"
|
|
"github.com/samber/lo"
|
|
)
|
|
|
|
var rxExecutable = regexp.MustCompile(`\.(exe|scr|lnk|bat|vbs|js|cmd)$`)
|
|
|
|
func isExecutable(t *api.TorrentFile) bool {
|
|
// - File has extension commonly used for viruses/executables
|
|
return rxExecutable.MatchString(t.Name)
|
|
}
|
|
|
|
func isCandidate(t *api.TorrentInfo) bool {
|
|
// - Less downloaded than total size, indicates it's stalled
|
|
// - Size is less than total size, indicates some files have been filtered out
|
|
return t.Downloaded < t.TotalSize && t.Size < t.TotalSize && time.Since(t.AddedOn.Time) > time.Minute*15
|
|
}
|
|
|
|
func checkTorrents(baseURL, username, password string) error {
|
|
client, err := api.NewClient(baseURL, username, password)
|
|
if err != nil {
|
|
slog.Error("Could not connect to qBittorrent", slog.String("error", err.Error()))
|
|
return err
|
|
}
|
|
|
|
list, err := client.LisTorrents()
|
|
if err != nil {
|
|
slog.Error("Could not list torrents", slog.String("error", err.Error()))
|
|
return err
|
|
}
|
|
slog.Info("Successfully listed torrents", slog.Int("num", len(list)))
|
|
|
|
for _, t := range list {
|
|
if isCandidate(&t) {
|
|
slog.Info("Torrent is a candidate", slog.String("hash", t.Hash), slog.String("name", t.Name))
|
|
files, err := client.TorrentFiles(t.Hash)
|
|
if err != nil {
|
|
slog.Error("Could not get torrent files", slog.String("error", err.Error()))
|
|
continue
|
|
}
|
|
|
|
executables := lo.Filter(files, func(f api.TorrentFile, _ int) bool {
|
|
return isExecutable(&f)
|
|
})
|
|
if len(executables) > 0 {
|
|
slog.Info("Torrent contains executables")
|
|
for _, exe := range executables {
|
|
slog.Info("Torrent file is executable", slog.String("name", exe.Name))
|
|
}
|
|
slog.Info("Deleting torrent...")
|
|
if err := client.DeleteTorrent(t.Hash, true); err != nil {
|
|
slog.Error("Failed to delete torrent", slog.String("error", err.Error()))
|
|
} else {
|
|
slog.Info("Torrent deleted successfully")
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
slog.Info("Done")
|
|
return nil
|
|
}
|
|
|
|
func main() {
|
|
baseURL := os.Getenv("QBITTORRENT_BASE_URL")
|
|
username := os.Getenv("QBITTORRENT_USERNAME")
|
|
password := os.Getenv("QBITTORRENT_PASSWORD")
|
|
|
|
if len(baseURL) == 0 {
|
|
slog.Error("QBITTORRENT_BASE_URL environment variable is required")
|
|
os.Exit(2)
|
|
}
|
|
|
|
if err := checkTorrents(baseURL, username, password); err != nil {
|
|
os.Exit(1)
|
|
}
|
|
}
|