Skip to main content

qbt-rules

A powerful Python-based rules engine for automating torrent management in qBittorrent using declarative YAML configuration.

GitHub Release License: Unlicense


What is qbt-rules?

qbt-rules is a Python-based rules engine that automates torrent management through the qBittorrent Web API v5.0+. Define YAML-based rules to automatically categorize, tag, pause, resume, delete, and manage torrents based on flexible conditions.

Key Features:

  • Declarative YAML Rules - No coding required, just write rules
  • Multiple Trigger Types - Manual, scheduled (cron), webhooks (on_added, on_completed)
  • Powerful Conditions - Complex logic with AND/OR/NOT groups, 17+ operators
  • Rich Field Access - Dot notation access to all torrent metadata (info., trackers., files.*, etc.)
  • Idempotent Actions - Safe to run repeatedly, actions skip if already applied
  • Dry-Run Mode - Test rules without making changes
  • Comprehensive Logging - Trace mode for debugging

Quick Start

Installation

Option 1: Install from PyPI (recommended)

# Install the package
pip install qbt-rules

# Create config directory
mkdir -p ~/.config/qbt-rules

# Download example configs
curl -o ~/.config/qbt-rules/config.yml \
  https://raw.githubusercontent.com/andronics/qbt-rules/main/config/config.example.yml
curl -o ~/.config/qbt-rules/rules.yml \
  https://raw.githubusercontent.com/andronics/qbt-rules/main/config/rules.example.yml

# Edit config with your qBittorrent credentials
nano ~/.config/qbt-rules/config.yml

Option 2: Install from source

# Clone the repository
git clone https://github.com/andronics/qbt-rules.git
cd qbt-rules

# Install in editable mode with development dependencies
pip install -e ".[dev]"

# Copy example configs
cp config/config.example.yml config/config.yml
cp config/rules.example.yml config/rules.yml

# Edit config with your qBittorrent credentials
nano config/config.yml

Your First Rule

Create a simple rule in config/rules.yml:

rules:
  - name: "Auto-categorize HD movies"
    enabled: true
    stop_on_match: true
    conditions:
      trigger: on_added
      all:
        - field: info.name
          operator: matches
          value: '(?i).*(1080p|2160p|4k).*'
    actions:
      - type: set_category
        params:
          category: "Movies-HD"
      - type: add_tag
        params:
          tag: "movies"

Run It

Using console script (after pip install):

# Test with dry-run (shows what would happen)
qbt-rules --dry-run

# Run rules WITHOUT a trigger filter (default behavior)
qbt-rules

# Enable trace logging for debugging
qbt-rules --trace

# Run rules WITH a specific trigger
qbt-rules --trigger scheduled

# Process specific torrent with trigger
qbt-rules --trigger on_completed --hash abc123...

# Run ALL rules (ignore trigger filters)
qbt-rules --trigger none

Using Python module:

python -m qbt_rules.cli --dry-run

How It Works

┌─────────────────────────────────────────────────────────────┐
│                    TRIGGER ACTIVATION                       │
│  (Manual, Scheduled, on_added, on_completed)               │
└────────────────────────┬────────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────────┐
│              FETCH TORRENTS FROM qBittorrent               │
│         (Optional: Apply filters by category/state)         │
└────────────────────────┬────────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────────┐
│                   FOR EACH TORRENT:                        │
│  ┌──────────────────────────────────────────────────────┐  │
│  │  Evaluate Rules in File Order                        │  │
│  │  ┌────────────────────────────────────────────────┐  │  │
│  │  │  Check Conditions (all/any/none groups)        │  │  │
│  │  │  ├─ Access fields (info.*, trackers.*, etc.)   │  │  │
│  │  │  ├─ Apply operators (==, >, contains, etc.)    │  │  │
│  │  │  └─ Combine with AND/OR/NOT logic             │  │  │
│  │  └────────────┬───────────────────────────────────┘  │  │
│  │               │                                       │  │
│  │               ├─ Match? ──► Execute Actions          │  │
│  │               │             (stop, categorize, etc.)  │  │
│  │               │                                       │  │
│  │               └─ No Match? ──► Next Rule             │  │
│  └──────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘

Rules execute top-to-bottom. Use stop_on_match: true to prevent later rules from running after a match.


Core Concepts

Triggers

Triggers are rule filters that control when rules execute. Rules specify a trigger condition, and you use the --trigger flag to run matching rules.

How Trigger Filtering Works:

# No --trigger flag: Runs ONLY rules WITHOUT a trigger condition
qbt-rules

# --trigger <name>: Runs ONLY rules WITH that trigger name
qbt-rules --trigger scheduled
qbt-rules --trigger on_added --hash abc123

# --trigger none: Runs ALL rules (ignores trigger filters)
qbt-rules --trigger none

There are no built-in triggers - all trigger names are arbitrary. You define them in your rules and specify them at runtime.

Common Trigger Naming Conventions:

Trigger Name Typical Use Case Example Schedule
scheduled Periodic maintenance Cron/systemd hourly/daily
on_added qBittorrent webhook When torrent added
on_completed qBittorrent webhook When download completes
hourly Frequent checks Every hour
nightly Daily cleanup 3 AM daily
weekly Weekly tasks Sunday midnight

Example Rules:

# This rule runs when: qbt-rules --trigger scheduled
- name: "Cleanup old torrents"
  conditions:
    trigger: scheduled  # Only runs with --trigger scheduled
    all:
      - field: info.completion_on
        operator: older_than
        value: "30 days"
  actions:
    - type: delete_torrent

# This rule runs when: qbt-rules (no --trigger flag)
- name: "Always check large downloads"
  conditions:
    # No trigger specified - runs by default
    all:
      - field: info.size
        operator: larger_than
        value: "50GB"
  actions:
    - type: add_tag
      params:
        tag: "large"

📖 Detailed Trigger Documentation

Conditions

Combine conditions with logical groups:

  • all - AND logic (all conditions must match)
  • any - OR logic (at least one condition must match)
  • none - NOT logic (no conditions can match)

Available Operators:

==, !=, >, <, >=, <=, contains, not_contains, matches, in, not_in, older_than, newer_than, smaller_than, larger_than

📖 Detailed Condition Documentation

Available Fields

Access torrent data using dot notation across 8 API categories:

Prefix Description Example Fields
info.* Main torrent info info.name, info.size, info.ratio, info.state
trackers.* Tracker data (collection) trackers.url, trackers.status, trackers.msg
files.* File list (collection) files.name, files.size, files.progress
properties.* Extended properties properties.save_path, properties.comment
transfer.* Global transfer stats transfer.dl_speed_global, transfer.up_speed_global
app.* Application info app.version, app.api_version
peers.* Peer data (collection) peers.ip, peers.client, peers.progress
webseeds.* Web seed data (collection) webseeds.url

📖 Complete Field Reference

Actions

Execute one or more actions when conditions match:

Action Description
stop / start / force_start Control torrent state
recheck / reannounce Maintenance operations
delete_torrent Remove torrent (optionally with files)
set_category Set torrent category
add_tag / remove_tag / set_tags Manage tags
set_upload_limit / set_download_limit Speed limiting

📖 Detailed Action Documentation


Example Rules

Auto-Categorize by Content Type

- name: "Categorize TV shows"
  enabled: true
  stop_on_match: true
  conditions:
    trigger: on_added
    all:
      - field: info.name
        operator: matches
        value: '(?i).*[Ss]\d{2}[Ee]\d{2}.*'
  actions:
    - type: set_category
      params:
        category: "TV-Shows"

Cleanup Old Completed Torrents

- name: "Delete old seeded torrents"
  enabled: true
  conditions:
    trigger: scheduled
    all:
      - field: info.state
        operator: in
        value: ["uploading", "pausedUP", "stalledUP"]
      - field: info.completion_on
        operator: older_than
        value: "30 days"  # Human-readable duration
      - field: info.ratio
        operator: ">="
        value: 2.0
  actions:
    - type: delete_torrent
      params:
        delete_files: false

Force Seed Low-Seeded Content

- name: "Force start underseded torrents"
  enabled: true
  conditions:
    trigger: scheduled
    all:
      - field: info.num_complete
        operator: "<="
        value: 2
      - field: info.state
        operator: in
        value: ["pausedUP", "stalledUP"]
  actions:
    - type: force_start

Pause Large Downloads

- name: "Pause very large downloads"
  enabled: true
  conditions:
    trigger: on_added
    all:
      - field: info.size
        operator: larger_than
        value: "50 GB"
      - field: info.state
        operator: in
        value: ["downloading", "metaDL"]
  actions:
    - type: stop
    - type: add_tag
      params:
        tag: "large-download"

📖 More Examples


Deployment

Scheduled Execution with Cron

# Run every hour
0 * * * * qbt-rules --trigger scheduled

# Run daily at 3 AM
0 3 * * * qbt-rules --trigger scheduled

# Custom trigger at midnight
0 0 * * * qbt-rules --trigger nightly_cleanup

Systemd Timer

Option 1: Single Timer (Simple)

Create /etc/systemd/system/qbt-rules.service:

[Unit]
Description=qbt-rules - qBittorrent Automation
After=network.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/qbt-rules --trigger scheduled
User=qbittorrent
Environment="PATH=/usr/local/bin:/usr/bin:/bin"

Create /etc/systemd/system/qbt-rules.timer:

[Unit]
Description=Run qbt-rules hourly

[Timer]
OnCalendar=hourly
Persistent=true

[Install]
WantedBy=timers.target

Enable:

sudo systemctl enable qbt-rules.timer
sudo systemctl start qbt-rules.timer

Option 2: Template Service (Multiple Triggers)

For running different triggers on different schedules, use systemd templates.

Create /etc/systemd/system/qbt-rules@.service:

[Unit]
Description=qbt-rules - %i trigger
After=network.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/qbt-rules --trigger %i
User=qbittorrent
Environment="PATH=/usr/local/bin:/usr/bin:/bin"

Create multiple timer files for different triggers:

/etc/systemd/system/qbt-rules@hourly.timer:

[Unit]
Description=Run qbt-rules hourly trigger

[Timer]
OnCalendar=hourly
Persistent=true

[Install]
WantedBy=timers.target

/etc/systemd/system/qbt-rules@nightly.timer:

[Unit]
Description=Run qbt-rules nightly trigger

[Timer]
OnCalendar=daily
OnCalendar=03:00
Persistent=true

[Install]
WantedBy=timers.target

/etc/systemd/system/qbt-rules@weekly.timer:

[Unit]
Description=Run qbt-rules weekly trigger

[Timer]
OnCalendar=weekly
Persistent=true

[Install]
WantedBy=timers.target

Enable the timers you need:

# Enable hourly trigger
sudo systemctl enable qbt-rules@hourly.timer
sudo systemctl start qbt-rules@hourly.timer

# Enable nightly trigger
sudo systemctl enable qbt-rules@nightly.timer
sudo systemctl start qbt-rules@nightly.timer

# Enable weekly trigger
sudo systemctl enable qbt-rules@weekly.timer
sudo systemctl start qbt-rules@weekly.timer

# Check timer status
sudo systemctl list-timers qbt-rules@*

Docker

FROM python:3.11-slim

WORKDIR /app

# Install the package
RUN pip install qbt-rules

# Copy configuration files
COPY config/ /app/config/

# Run scheduled trigger every hour
CMD ["sh", "-c", "while true; do qbt-rules --trigger scheduled; sleep 3600; done"]

📖 Deployment Guide


Documentation

Complete documentation is available on the GitHub Wiki:


Requirements

  • Python 3.8+
  • qBittorrent v5.0+ with Web UI enabled
  • Dependencies: requests, pyyaml

Contributing

Contributions are welcome! Please see the Contributing Guide on the wiki.


Changelog

See CHANGELOG.md for version history and release notes.

Latest Release: v0.3.0 - 2024-12-13


License

This project is released into the public domain under the Unlicense.

You are free to use, modify, and distribute this software for any purpose without attribution.


Happy automating! 🚀

For detailed documentation, visit the GitHub Wiki.

Metadata

Release files for qbt-rules 0.3.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for qbt-rules 0.3.2
File Size Uploaded Provenance
qbt_rules-0.3.2.tar.gz 29.8 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for qbt-rules 0.3.2
File Interpreter ABI Platform
qbt_rules-0.3.2-py3-none-any.whl Python 3 none any Details

Total release size: 57.0 kB

Release files / qbt_rules-0.3.2.tar.gz

Download URL qbt_rules-0.3.2.tar.gz
Size 29.8 kB
Tags Source
SHA-256 checksum
How to use checksums
bc44a194ca59c729f6527ffeca9fb60589c64e42a6cd32b6125d02b9b3f5e285
BLAKE2b-256 checksum
How to use checksums
7b4c73361028f90b8ab259620dace9d495240e735b99e8505125356f62f7c3a5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Dec 13, 2025.

Transparency log

Release files / qbt_rules-0.3.2-py3-none-any.whl

Download URL qbt_rules-0.3.2-py3-none-any.whl
Size 27.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
27c757b41402a481f0997c373d7c27fe4e42cc682f9c3a479e468d48d33be08a
BLAKE2b-256 checksum
How to use checksums
114a02b01a793efce78f459e1d4cc2d979bb8048c9b341084a5a43645fc0832f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Dec 13, 2025.

Transparency log

Security for qbt-rules 0.3.2

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for qbt_rules-0.3.2.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Dec 13, 2025.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page