Skip to content

Advanced Topics

Stephen Cox edited this page Dec 10, 2025 · 3 revisions

Performance Optimization

Field Access Optimization

The system caches API responses per-torrent to minimize calls:

# First access to trackers.url: API call made and cached
if torrent["trackers.url"] contains "example.com":
    ...

# Second access to trackers.url: Uses cached data (no API call)
if torrent["trackers.url"] contains "private":
    ...

Best practices:

  1. Access fields in order of expense:

    # Check cheap fields first
    all:
      - field: info.category    # Free
        operator: "=="
        value: "Movies"
      - field: files.name        # Expensive, but only if category matches
        operator: matches
        value: '.*\.mkv$'
  2. Reuse field references:

    # BAD: Multiple string operations
    any:
      - field: info.name
        operator: contains
        value: "1080p"
      - field: info.name
        operator: contains
        value: "2160p"
    
    # GOOD: Single regex
    any:
      - field: info.name
        operator: matches
        value: '(?i).*(1080p|2160p).*'

Rule Ordering

Rules execute top-to-bottom. Order matters for performance:

# GOOD: Specific rules first, then general
- name: "High-priority: Private trackers"
  stop_on_match: true
  conditions:
    all:
      - field: trackers.url
        operator: contains
        value: "privatehd.to"

- name: "General cleanup"
  conditions:
    # This only runs if private tracker rule didn't match

Security Best Practices

Credential Management

Never hardcode credentials in config files:

# BAD
qbittorrent:
  username: admin
  password: MyPassword123

# GOOD
qbittorrent:
  username: ${QB_USERNAME}
  password: ${QB_PASSWORD}

Set environment variables:

export QB_USERNAME="admin"
export QB_PASSWORD="secure_password"

File Permissions

Protect config files containing credentials:

# Restrict access to config files
chmod 600 config/config.yml
chown qbittorrent:qbittorrent config/config.yml

Network Security

If running webhook listeners:

  1. Use authentication (implement token validation)
  2. Bind to localhost if qBittorrent is local:
    python triggers/on_added.py --host 127.0.0.1 --port 8081
  3. Use reverse proxy with HTTPS for remote access
  4. Implement rate limiting to prevent abuse

Integration Patterns

Integration with Sonarr/Radarr

Scenario: Auto-categorize torrents added by Sonarr/Radarr

  1. Run webhook listener:

    python triggers/on_added.py --port 8081
  2. Configure qBittorrent webhook (if supported by your setup)

  3. Create categorization rules:

    - name: "Categorize Sonarr downloads"
      conditions:
        trigger: on_added
        all:
          - field: info.category
            operator: "=="
            value: "tv-sonarr"
      actions:
        - type: add_tag
          params:
            tag: "sonarr"

Integration with External Scripts

Scenario: Run custom script after specific condition

While the system doesn't directly support script execution, you can:

  1. Tag torrents with automation rules
  2. Monitor tags with external script:
    #!/bin/bash
    # Watch for "process-me" tag
    TAGGED=$(curl -s http://localhost:8080/api/v2/torrents/info | \
      jq -r '.[] | select(.tags | contains("process-me")) | .hash')
    
    for hash in $TAGGED; do
      # Run your custom logic
      ./process-torrent.sh "$hash"
    done

Integration with Notification Services

Scenario: Send notifications when rules match

Implement custom action type (requires code modification):

# lib/actions.py
def send_notification(torrent, params):
    import requests
    message = params.get("message", "Rule matched")
    requests.post("https://ntfy.sh/qbittorrent",
                  data=message.encode('utf-8'))

Then use in rules:

actions:
  - type: send_notification
    params:
      message: "Torrent {info.name} completed!"

Extending the System

Adding Custom Operators

Edit lib/engine.py to add new comparison operators:

def evaluate_condition(torrent, condition):
    # ... existing code ...

    elif operator == "starts_with":
        return str(field_value).startswith(str(value))
    elif operator == "ends_with":
        return str(field_value).endswith(str(value))

Then use in rules:

- field: info.name
  operator: starts_with
  value: "Ubuntu"

Adding Custom Actions

Edit lib/actions.py:

def set_priority(api, torrent_hash, params):
    """Set torrent priority"""
    priority = params.get("priority", "normal")
    # Implement priority logic
    api.post(f"/api/v2/torrents/setPriority",
             data={"hashes": torrent_hash, "priority": priority})

Register in action mapping:

ACTION_MAP = {
    # ... existing actions ...
    "set_priority": set_priority,
}

Use in rules:

actions:
  - type: set_priority
    params:
      priority: "high"

Adding Custom Triggers

Create new trigger file triggers/on_error.py:

#!/usr/bin/env python3
from lib.config import load_config
from lib.api import QBittorrentAPI
from lib.engine import RulesEngine

def main():
    config = load_config()
    api = QBittorrentAPI(config["qbittorrent"])
    api.login()

    # Fetch only errored torrents
    torrents = api.get("/api/v2/torrents/info",
                       params={"filter": "errored"})

    # Load and execute rules with trigger: on_error
    engine = RulesEngine(config["rules"], trigger_type="on_error")
    engine.execute(api, torrents)

if __name__ == "__main__":
    main()

Use in rules:

- name: "Handle errored torrents"
  conditions:
    trigger: on_error
    all:
      - field: info.state
        operator: "=="
        value: "error"
  actions:
    - type: recheck

Scaling Strategies

Horizontal Scaling

Run multiple instances with different rule subsets:

Instance 1 - Categorization (runs frequently):

python triggers/scheduled.py --rules config/rules-categorization.yml

Instance 2 - Cleanup (runs daily):

python triggers/scheduled.py --rules config/rules-cleanup.yml

Instance 3 - Webhooks (always running):

python triggers/on_added.py --port 8081

Batch Processing

For very large torrent libraries (10,000+), implement batch processing:

# Process torrents in chunks of 100
BATCH_SIZE = 100
for i in range(0, len(all_torrents), BATCH_SIZE):
    batch = all_torrents[i:i+BATCH_SIZE]
    engine.execute(api, batch)
    time.sleep(1)  # Rate limiting

🎯 qbt-rules

v0.3.0 | GitHub


📚 Getting Started

📖 Core Concepts

🔧 Configuration

🆘 Help & Support

🤝 Contributing


🆕 What's New (v0.3.0)

  • ✅ Unified CLI (qbt-rules)
  • ✅ Custom triggers
  • ✅ Trigger-agnostic mode
  • ✅ Size operators (50GB)

🔗 Quick Links

Clone this wiki locally