MCPcopy Index your code
hub / github.com/autobrr/tqm

github.com/autobrr/tqm @v1.20.0

Chat with this repo
repository ↗ · DeepWiki ↗ · release v1.20.0 ↗ · + Follow
355 symbols 968 edges 62 files 49 documented · 14% updated 17d agov1.20.0 · 2026-05-26★ 895 open issues
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

made-with-golang License: GPL v3 Contributing

tqm

CLI tool to manage your torrent client queues. Primary focus is on removing torrents that meet specific criteria.

This is a fork from l3uddz.

Example Configuration

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:

  • Beyond-HD
  • BTN
  • HDB
  • OPS
  • PTP
  • RED
  • UNIT3D trackers

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.

Filtering Language Definition

The language definition used in the configuration filters is available here

Filterable Fields

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.

Helper Filtering Options

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

Filtering by Private/Public Status

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

Conditional Upload Speed Limiting via Tags

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

  • Add an optional uploadKb field to any rule within the tag: section of your filter definition.
  • The value should be the desired upload speed limit in KiB/s.
  • Use -1 to signify an unlimited upload speed. 500 for 500Kb.
  • If a torrent matches the update: conditions for a tag rule that includes uploadKb, the specified speed limit will be applied to that torrent.
  • This speed limit is applied when you run the 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

MapHardlinksFor

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:

  • Include a command name in MapHardlinksFor only if your filter rules for that specific command use the HardlinkedOutsideClient field.
  • If none of your filter rules use HardlinkedOutsideClient, you can omit the MapHardlinksFor setting entirely for better performance.

IsUnregistered and IsTrackerDown

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 down
  • IsTrackerDown() checks if the tracker status indicates the tracker is unreachable/down
  • The functions are independen

Extension points exported contracts — how you extend this code

HardlinkFileMapI (Interface)
(no doc) [2 implementers]
pkg/hardlinkfilemap/interface.go
Validator (Interface)
(no doc) [2 implementers]
pkg/config/validator.go
TagInterface (Interface)
(no doc) [1 implementers]
pkg/client/tagInterface.go
Sender (Interface)
(no doc) [1 implementers]
pkg/notification/notification.go
Interface (Interface)
(no doc)
pkg/tracker/interface.go
Interface (Interface)
(no doc)
pkg/client/interface.go

Core symbols most depended-on inside this repo

GetLogger
called by 26
pkg/logger/log.go
escapeDiscordMarkdown
called by 14
pkg/notification/discord.go
Add
called by 14
pkg/torrentfilemap/torrentfilemap.go
GetFreeSpace
called by 10
pkg/client/interface.go
TagsSlice
called by 10
pkg/config/torrent.go
getClientConfigString
called by 10
cmd/root.go
GetCurrentFreeSpace
called by 8
pkg/client/interface.go
NewRetryableHttpClient
called by 8
pkg/httputils/httputils.go

Shape

Method 174
Function 104
Struct 67
Interface 6
TypeAlias 3
FuncType 1

Languages

Go100%

Modules by API surface

pkg/notification/discord.go27 symbols
pkg/client/qbittorrent.go25 symbols
pkg/client/deluge.go19 symbols
pkg/client/interface.go18 symbols
pkg/config/torrent.go16 symbols
pkg/tracker/btn.go12 symbols
pkg/torrentfilemap/torrentfilemap.go11 symbols
pkg/tracker/unit3d.go10 symbols
pkg/tracker/hdb.go10 symbols
pkg/tracker/bhd.go10 symbols
pkg/hardlinkfilemap/hardlinkfilemap.go10 symbols
pkg/expression/compile.go10 symbols

For agents

$ claude mcp add tqm \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact