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) } }