Hello everyone :)
Firstly, I'm not anyhow related to programming at ALL ! I can put together some easy to use bash scripts, to automate some stuff, while copy/pasting from the web and doing A LOT of trial an error. It sometimes took me a whole week to have a functional script. I also sometimes asked for some help here on Lemmy and still uses some of those script people helped me out to build up from the ground !
Secondly, I'm not really into the AI slope and have a lot of arguments why I hate it (Unauthorized webscrapping, High energy consumption, Privacy nightmare....).
However, I have to say I'm quite impressed how good my first experience with AI was, considering my very limited knowledge in programming. The script works perfectly for my use case. I had to switch between Claude and O4-mini to get the best results and took me a whole day of prompting around and testing before it behaved like I wanted it to !
Without going to much into details, I was looking for a way to interface with Qbittorrent's API and manage my torrents and move them around in new categories in an automated way. What this python script does is to export the .torrent file in specific directory (not the files) and stop the torrent and move it in a new category if desired based on specific criteria (ratio, category, tags, seeding time...) . If correctly configured, directories and sub-directories are also created on the fly.
My own opinion after this experience is that it probably won't write a fully functional software (not yet?), but for something like scripting or learning basic programming skills it's a very capable assistant!
-
What do you think of the code overall? (see below)
-
Also, do you think it's still relevant to get proficient and learn all the details or just stick to the basic and let AI do the heavy lifting?
DISCLAIMER
Keep in mind this works perfectly for my use case and maybe won't work like you expect. It has it's flaws and will probably break in more niche or specific use cases. Don't use it if you don't know what you're doing and proper testing ! I'm not responsible if all your torrents are gone !!!
## Made by duckduckgo AI ##
## Required to install requests with pip install requests ##
## see duck.ai_2025-07-13_16-44-24.txt ##
import requests
import os
# Configuration
QB_URL = "http://localhost:8080/" # Ensure this is correctly formatted
USERNAME = "" # Replace with your qBittorrent username
PASSWORD = "" # Replace with your qBittorrent password
MIN_RATIO = 0.0 # Minimum ratio to filter torrents
MIN_SEEDING_TIME = 3600 # Minimum seeding time in seconds
OUTPUT_DIR = "./directory" # Replace with your desired output directory
NEW_CATEGORY = "" # Specify the new category name
NEW_PATH = "~/Downloads"
# Optional filtering criteria
FILTER_CATEGORIES = ["cats"] # Leave empty to include all categories
FILTER_TAGS = [] # Leave empty to include all tags
FILTER_UNTAGGED = False # Set to True to include untagged torrents
FILTER_UNCATEGORIZED = False # Set to True to include uncategorized torrents
# Function to log in to qBittorrent
def login():
session = requests.Session()
response = session.post(f"{QB_URL}/api/v2/auth/login", data={'username': USERNAME, 'password': PASSWORD})
if response.status_code == 200:
print("Login successful.")
return session
else:
print("Login failed.")
return None
# Function to get torrents
def get_torrents(session):
response = session.get(f"{QB_URL}/api/v2/torrents/info")
if response.status_code == 200:
print("Retrieved torrents successfully.")
return response.json()
else:
print("Failed to retrieve torrents.")
return []
# Function to stop a torrent
def stop_torrent(session, torrent_hash):
response = session.post(f"{QB_URL}/api/v2/torrents/stop", data={'hashes': torrent_hash})
if response.status_code == 200:
print(f"Stopped torrent: {torrent_hash}")
else:
print(f"Failed to stop torrent: {torrent_hash}")
# Function to start a torrent
def start_torrent(session, torrent_hash):
response = session.post(f"{QB_URL}/api/v2/torrents/start", data={'hashes': torrent_hash})
if response.status_code == 200:
print(f"Started torrent: {torrent_hash}")
else:
print(f"Failed to start torrent: {torrent_hash}")
# Function to create a category if it doesn't exist
def create_category(session, category_name, save_path):
# Skip category creation if category or save path is empty
if not category_name or not save_path:
print("Skipping category creation: category or save path is empty.")
return
# Check existing categories
response = session.get(f"{QB_URL}/api/v2/torrents/categories")
if response.status_code == 200:
categories = response.json()
if category_name not in categories:
# Create the new category with savePath
payload = {
'category': category_name,
'savePath': save_path
}
response = session.post(f"{QB_URL}/api/v2/torrents/createCategory", data=payload)
if response.status_code == 200:
print(f"Category '{category_name}' created with save path '{save_path}'.")
else:
print(f"Failed to create category '{category_name}'. Status code: {response.status_code}")
else:
print(f"Category '{category_name}' already exists.")
else:
print("Failed to retrieve categories. Status code:", response.status_code)
# Function to set the category for a torrent
def set_torrent_category(session, torrent_hash, category_name, save_path):
# If either category or path is missing, remove the category
if not category_name or not save_path:
response = session.post(f"{QB_URL}/api/v2/torrents/setCategory", data={'hashes': torrent_hash, 'category': ''})
if response.status_code == 200:
print(f"Removed category for torrent: {torrent_hash}")
else:
print(f"Failed to remove category for torrent: {torrent_hash}")
return
def is_category_match(torrent_category, filter_categories):
"""
Check if the torrent's category matches any of the filter categories.
Supports partial category matching.
Args:
torrent_category (str): The category of the torrent
filter_categories (list): List of categories to filter by
Returns:
bool: True if the category matches, False otherwise
"""
# If no filter categories are specified, return True
if not filter_categories:
return True
# Check if the torrent's category starts with any of the filter categories
return any(
torrent_category == category or
torrent_category.startswith(f"{category}/")
for category in filter_categories
)
# Modify the export_torrents function to use the new category matching
def export_torrents(session, torrents):
# Create the output directory if it doesn't exist
os.makedirs(OUTPUT_DIR, exist_ok=True)
for torrent in torrents:
ratio = torrent['ratio']
seeding_time = torrent['seeding_time']
category = torrent.get('category', '')
tags = torrent.get('tags', '')
# Use the new category matching function
if (ratio >= MIN_RATIO and
seeding_time >= MIN_SEEDING_TIME and
is_category_match(category, FILTER_CATEGORIES) and
(not FILTER_TAGS or any(tag in tags for tag in FILTER_TAGS)) and
(not FILTER_UNTAGGED or not tags) and
(not FILTER_UNCATEGORIZED or category == '')):
torrent_hash = torrent['hash']
torrent_name = torrent['name']
export_url = f"{QB_URL}/api/v2/torrents/export?hash={torrent_hash}"
# Export the torrent file
response = session.get(export_url)
if response.status_code == 200:
# Save the torrent file with its original name in the specified output directory
output_path = os.path.join(OUTPUT_DIR, f"{torrent_name}.torrent")
with open(output_path, 'wb') as f:
f.write(response.content)
print(f"Exported: {output_path}")
# Stop the torrent after exporting
stop_torrent(session, torrent_hash)
# Create the new category if it doesn't exist
create_category(session, NEW_CATEGORY, NEW_PATH)
# Set the category for the stopped torrent
set_torrent_category(session, torrent_hash, NEW_CATEGORY, NEW_PATH)
else:
print(f"Failed to export {torrent_name}.torrent")
# Main function
def main():
session = login()
if session:
torrents = get_torrents(session)
export_torrents(session, torrents)
if __name__ == "__main__":
main()