CLI tool to manage your torrent client queues. Primary focus is on removing torrents that meet specific criteria.
This is a fork from l3uddz.
clients:
deluge:
enabled: true
filter: default
download_path: /mnt/local/downloads/torrents/deluge
free_space_path: /mnt/local/downloads/torrents/deluge # Required for Deluge with path that exists on server
download_path_mapping:
/downloads/torrents/deluge: /mnt/local/downloads/torrents/deluge
host: localhost
login: localclient
password: password-from-/opt/deluge/auth
port: 58846
type: deluge
v2: true
qbt:
download_path: /mnt/local/downloads/torrents/qbittorrent/completed
# free_space_path is not needed for qBittorrent as it checks globally via API
download_path_mapping:
/downloads/torrents/qbittorrent/completed: /mnt/local/downloads/torrents/qbittorrent/completed
enabled: true
filter: default
create_tags_upfront: false # Only sets tags that matches torrents, prevents empty tags
type: qbittorrent
url: https://qbittorrent.domain.com/
user: user
password: password
# qBittorrent 5.2.0+ supports API key authentication as an alternative
# to user/password. Generate one in qBittorrent: Preferences > WebUI > API Key.
# When set, user/password are not used for the WebAPI login.
# api_key: qbt_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
# NEW: If this option is set to true, AutoTmm aka Auto Torrent Managment Mode,
# will be enabled for torrents after a relabel.
# This ensures the torrent is also moved in the filesystem to the new category path, and not only changes category in qbit
# enableAutoTmmAfterRelabel: true
notifications:
# if detailed is true, TQM will send detailed information about each action it takes
# if it is false it will only send a summary notification
detailed: true
# if skip_empty_run is true, TQM will skip sending a notification if the action didn't change anything
skip_empty_run: true
service:
discord:
webhook_url: https://discord.com/api/webhooks/yourwebhookid/yourwebhooktoken
# Both username and avatar_url are optional and only needed if you want to
# change the name and the picture of the webhook account
username: yourusername
avatar_url: youravatarurl
filters:
default:
# if true, data will be deleted from disk when removing torrents (default: true)
#DeleteData: false
ignore:
# general
- IsTrackerDown()
- Downloaded == false && !IsUnregistered()
- SeedingHours < 26 && !IsUnregistered()
# permaseed / un-sorted (unless torrent has been deleted)
- Label startsWith "permaseed-" && !IsUnregistered()
# Filter based on qbittorrent tags (only qbit at the moment)
- '"permaseed" in Tags && !IsUnregistered()'
# Example: Ignore private torrents unless they are unregistered
# - IsPrivate == true && !IsUnregistered()
remove:
# general
- IsUnregistered()
# Example: Remove non-private torrents that meet ratio/seed time criteria
# - IsPrivate == false && (Ratio > 2.0 || SeedingDays >= 7.0)
# imported
- Label in ["sonarr-imported", "radarr-imported", "lidarr-imported"] && (Ratio > 4.0 || SeedingDays >= 15.0)
# ipt
- Label in ["autoremove-ipt"] && (Ratio > 3.0 || SeedingDays >= 15.0)
# hdt
- Label in ["autoremove-hdt"] && (Ratio > 3.0 || SeedingDays >= 15.0)
# bhd
- Label in ["autoremove-bhd"] && (Ratio > 3.0 || SeedingDays >= 15.0)
# ptp
- Label in ["autoremove-ptp"] && (Ratio > 3.0 || SeedingDays >= 15.0)
# btn
- Label in ["autoremove-btn"] && (Ratio > 3.0 || SeedingDays >= 15.0)
# hdb
- Label in ["autoremove-hdb"] && (Ratio > 3.0 || SeedingDays >= 15.0)
# Qbit tag utilities
- HasAllTags("480p", "bad-encode") # match if all tags are present
- HasAnyTag("remove-me", "gross") # match if at least 1 tag is present
pause: # New section for pausing torrents
# Pause public torrents
- IsPrivate == false
#- IsPublic # same as above, but easier to remember
# Pause torrents seeding for more than 7 days with a ratio below 0.5
- Ratio < 0.5 && SeedingDays > 7
# Pause incomplete torrents older than 2 weeks
- Downloaded == false && AddedDays > 14
label:
# btn 1080p season packs to permaseed (all must evaluate to true)
- name: permaseed-btn
update:
- Label == "sonarr-imported"
- TrackerName == "landof.tv"
- Name contains "1080p"
- len(Files) >= 3
# cleanup btn season packs to autoremove-btn (all must evaluate to true)
- name: autoremove-btn
update:
- Label == "sonarr-imported"
- TrackerName == "landof.tv"
- not (Name contains "1080p")
- len(Files) >= 3
# Change qbit tags based on filters
tag:
- name: low-seed
# Mode is optional and defaults to "full" if not specified
# "mode: full" means tag will be added to
# torrent if matched and removed from torrent if not
# use `add` or `remove` to only add/remove respectivly
# NOTE: Mode does not change the way torrents are flagged,
# meaning, even with "mode: remove",
# tags will be removed if the torrent does NOT match the conditions.
# "mode: remove" simply means that tags will not be added
# to torrents that do match.
mode: full
# uploadKb: 50 # Optional: Apply 50 KiB/s upload limit if conditions match (-1 for unlimited)
update:
- Seeds <= 3
# Example: Limit upload speed for public torrents that have seeded for over 2 days
# - name: limit-public-seed-time
# mode: add # Add tag (optional, could just limit speed without tagging)
# uploadKb: 100 # Limit to 100 KiB/s
# update:
# - IsPrivate == false # Only target public torrents
# - SeedingDays > 2.0
# Example: Tag without mode specification (defaults to "full")
# - name: completed-torrents
# update:
# - Downloaded == true
- name: inactive
mode: add
update:
- LastActivityDays > 30
# Orphan configuration
orphan:
# grace period for recently modified files (default: 10m)
# valid time units are: ns, us (or µs), ms, s, m, h
grace_period: 10m
# paths that will be ignored during the orphaned files check
ignore_paths:
- /mnt/local/downloads/torrents/qbittorrent/completed/tv-4k
- /mnt/local/downloads/torrents/qbittorrent/completed/movie-4k
## Optional - Tracker Configuration
trackers:
bhd:
api_key: your-api-key
btn:
api_key: your-api-key
ptp:
api_user: your-api-user
api_key: your-api-key
hdb:
username: your-username
passkey: your-passkey
red:
api_key: your-api-key
ops:
api_key: your-api-key
unit3d:
aither:
api_key: your_api_key
domain: aither.cc
blutopia:
api_key: your_api_key
domain: blutopia.cc
Allows tqm to validate if a torrent was removed from the tracker using the tracker's own API.
Currently implements:
Note for BTN users: When first using the BTN API, you may need to authorize your IP address. Check your BTN notices/messages for the authorization request.
The language definition used in the configuration filters is available here
The following torrent fields (along with their types) can be used in the configuration when filtering torrents:
type Torrent struct {
Hash string
Name string
Path string
TotalBytes int64
DownloadedBytes int64
State string
Files []string
Tags []string
Downloaded bool
Seeding bool
Ratio float32
AddedSeconds int64
AddedHours float32
AddedDays float32
SeedingSeconds int64
SeedingHours float32
SeedingDays float32
LastActivitySeconds int64
LastActivityHours float32
LastActivityDays float32
Label string
Seeds int64
Peers int64
IsPrivate bool
IsPublic bool
FreeSpaceGB func() float64
FreeSpaceSet bool
TrackerName string
TrackerStatus string
}
Number fields of types int64, float32 and float64 support arithmetic and comparison operators.
Fields of type string support string operators.
Fields of type []string (lists) such as the Tags and Files fields support membership checks and various built in functions.
All of this and more can be noted in the language definition mentioned above.
The following helper functions are available for usage while filtering, usage examples are available in the example config above.
IsUnregistered() bool // Evaluates to true if torrent is unregistered in the tracker
IsTrackerDown() bool // Evaluates to true if the tracker appears to be down/unreachable
HasAllTags(tags ...string) bool // True if torrent has ALL tags specified
HasAnyTag(tags ...string) bool // True if torrent has at least one tag specified
HasMissingFiles() bool // True if any of the torrent's files are missing from disk
Log(n float64) float64 // The natural logarithm function
You can use either IsPublic or IsPrivate to filter torrents - they are complementary fields. Always use explicit comparisons (== true or == false).
Example filters:
filters:
default:
ignore:
# These achieve the same result:
- IsPublic == false && !IsUnregistered() # private torrents
- IsPrivate == true && !IsUnregistered() # private torrents
remove:
# These achieve the same result:
- IsPublic == true && Ratio > 2.0 # public torrents
- IsPrivate == false && Ratio > 2.0 # public torrents
tag:
- name: public-torrent
mode: full
update:
# These achieve the same result:
- IsPublic == true # public torrents
- IsPrivate == false # public torrents
You can apply upload speed limits to torrents conditionally based on matching tag rules. This is useful for throttling specific groups of torrents (e.g., public torrents).
uploadKb field to any rule within the tag: section of your filter definition.-1 to signify an unlimited upload speed. 500 for 500Kb.update: conditions for a tag rule that includes uploadKb, the specified speed limit will be applied to that torrent.tqm retag <client> command.Example:
filters:
default:
tag:
# Tag public torrents AND limit their upload speed to 50 KiB/s
- name: public
mode: add
uploadKb: 50
update:
- IsPrivate == false
# Tag private torrents AND remove any upload speed limit
- name: private
mode: add
uploadKb: -1
update:
- IsPrivate == true
Within each filter definition in your config.yaml, you can optionally include the MapHardlinksFor setting. This setting controls when tqm performs the (potentially time-consuming) process of scanning torrent files to identify hardlinks.
filters:
default:
MapHardlinksFor:
- clean
ignore:
- Downloaded == false
- IsTrackerDown()
- HardlinkedOutsideClient == true && !isUnregistered() # this makes sure we never remove torrents that has a hardlink (unless they are unregistered)
Recommendation:
MapHardlinksFor only if your filter rules for that specific command use the HardlinkedOutsideClient field.HardlinkedOutsideClient, you can omit the MapHardlinksFor setting entirely for better performance.When using both IsUnregistered() and IsTrackerDown() in filters:
IsUnregistered() has built-in protection against tracker down states - it will return false if the tracker is downIsTrackerDown() checks if the tracker status indicates the tracker is unreachable/down