This commit is contained in:
jblu 2022-07-29 16:02:19 -05:00
parent 26ac36a164
commit fca54f8952
6 changed files with 328 additions and 328 deletions

152
README.md
View File

@ -1,77 +1,77 @@
# qbit-maid # qbit-maid
Warning: This application removes torrents that aren't downloading and that aren't from iptorrents. Age in the config.json only controls the age for torrents from iptorrents. Warning: This application removes torrents that aren't downloading and that aren't from iptorrents. Age in the config.json only controls the age for torrents from iptorrents.
The objective is to remove torrents based on the following criteria: The objective is to remove torrents based on the following criteria:
- tracker domain name - tracker domain name
- age - age
- ratio - ratio
- state - state
```mermaid ```mermaid
graph TD; graph TD;
qbit-maid.py-->qlogging.py; qbit-maid.py-->qlogging.py;
qbit-maid.py-->qlist.py; qbit-maid.py-->qlist.py;
qbit-maid.py-->qprocess.py; qbit-maid.py-->qprocess.py;
qlogging.py-->qbit-maid.py; qlogging.py-->qbit-maid.py;
qlist.py-->qbit-maid.py; qlist.py-->qbit-maid.py;
qprocess.py-->qbit-maid.py; qprocess.py-->qbit-maid.py;
``` ```
| File | Purpose | | File | Purpose |
| --- | --- | | --- | --- |
| qbit-maid.py | Client to the qbit api and calls functions from the other files | | qbit-maid.py | Client to the qbit api and calls functions from the other files |
| qlist.py | Builds out torrent lists | | qlist.py | Builds out torrent lists |
| qlogging.py | Logging and push notification communication | | qlogging.py | Logging and push notification communication |
| qprocess.py | Submits qualifying torrents for deletion | | qprocess.py | Submits qualifying torrents for deletion |
You will need a config.json in the root directory. You will need a config.json in the root directory.
It should look something like this: It should look something like this:
Config.json Config.json
``` ```
{ {
"host": "192.168.1.1", "host": "192.168.1.1",
"port": 8080, "port": 8080,
"username": "admin", "username": "admin",
"password": "admin", "password": "admin",
"loglevel": "INFO", "loglevel": "INFO",
"logpath": "./qc.log", "logpath": "./qc.log",
"protected_tag": "ipt", "protected_tag": "ipt",
"non_protected_tag": "public", "non_protected_tag": "public",
"age": 2419200, "age": 2419200,
"minimum_age": 432000, "minimum_age": 432000,
"use_pushover": false, "use_pushover": false,
"use_log": true, "use_log": true,
"po_key": "", "po_key": "",
"po_token": "", "po_token": "",
"delete_torrents": false "delete_torrents": false
} }
``` ```
You will need a category-whitelist.json in the root directory. This will ignore any of the categories found in the values of the entries. You will need a category-whitelist.json in the root directory. This will ignore any of the categories found in the values of the entries.
``` ```
{ {
"example": "general", "example": "general",
"example2": "sonarr" "example2": "sonarr"
} }
``` ```
| Key | Value | | Key | Value |
| --- | --- | | --- | --- |
| host | string, ip or hostname of qbittorrent server | | host | string, ip or hostname of qbittorrent server |
| port | number, port of admin gui(used for api aswell) | | port | number, port of admin gui(used for api aswell) |
| username | admin account for qbittorrent | | username | admin account for qbittorrent |
| password | password for admin account | | password | password for admin account |
| loglevel | is what log messages are written to the log file. INFO or DEBUG are valid entries(case sensitive) | | loglevel | is what log messages are written to the log file. INFO or DEBUG are valid entries(case sensitive) |
| protected_tag | used to mark torrents to handle with care | | protected_tag | used to mark torrents to handle with care |
| non_protected_tag | we don't care about these torrents | | non_protected_tag | we don't care about these torrents |
| logpath | will write a log in root directory if left as is other wise specify other path using forward slashes | | logpath | will write a log in root directory if left as is other wise specify other path using forward slashes |
| age | number, seconds for how long we keep torrents from IPTORRENTS | | age | number, seconds for how long we keep torrents from IPTORRENTS |
| minimum_age | age in seconds torrents should reached before they are removed | | minimum_age | age in seconds torrents should reached before they are removed |
| use_pushover | true or false to enable or disable pushover notification summary | | use_pushover | true or false to enable or disable pushover notification summary |
| use_log | true or false to enable or disable writing to alog file | | use_log | true or false to enable or disable writing to alog file |
| po_key | pushover key | | po_key | pushover key |
| po_token | pushover api token | | po_token | pushover api token |
| delete_torrents | true or false to enable or disable deletion. Useful for dry-runs | | delete_torrents | true or false to enable or disable deletion. Useful for dry-runs |

View File

@ -1,17 +1,17 @@
{ {
"host": "192.168.1.1", "host": "192.168.1.1",
"port": 8080, "port": 8080,
"username": "admin", "username": "admin",
"password": "admin", "password": "admin",
"loglevel": "INFO", "loglevel": "INFO",
"logpath": "./qc.log", "logpath": "./qc.log",
"protected_tag": "ipt", "protected_tag": "ipt",
"non_protected_tag": "public", "non_protected_tag": "public",
"age": 2419200, "age": 2419200,
"minimum_age": 432000, "minimum_age": 432000,
"use_pushover": true, "use_pushover": true,
"use_log": true, "use_log": true,
"po_key": "", "po_key": "",
"po_token": "", "po_token": "",
"delete_torrents": false "delete_torrents": false
} }

170
qbit-maid.py Normal file → Executable file
View File

@ -1,86 +1,86 @@
#The first file shall contain an client to the qbit api and the processing of the torrents. #The first file shall contain an client to the qbit api and the processing of the torrents.
import qbittorrentapi import qbittorrentapi
import pushover import pushover
from json import load from json import load
from qlist import * from qlist import *
from qlogging import * from qlogging import *
from qprocess import * from qprocess import *
import time import time
import logging import logging
from collections import Counter from collections import Counter
class Qbt: class Qbt:
def __init__(self): def __init__(self):
"""Main object, should be calling functions from qlist.py, qlogging.py and qprocess.py""" """Main object, should be calling functions from qlist.py, qlogging.py and qprocess.py"""
# Open the config. Needs a json file with the data in config.json.example # Open the config. Needs a json file with the data in config.json.example
c = open('./config.json') c = open('./config.json')
self.config = load(c) self.config = load(c)
w = open('./category-whitelist.json') w = open('./category-whitelist.json')
self.cat_whitelist = load(w) self.cat_whitelist = load(w)
# Create the api object # Create the api object
self.qbt_client = qbittorrentapi.Client( self.qbt_client = qbittorrentapi.Client(
host=self.config["host"], host=self.config["host"],
port=self.config["port"], port=self.config["port"],
username=self.config["username"], username=self.config["username"],
password=self.config["password"], password=self.config["password"],
) )
# Create the logging and pushover objects # Create the logging and pushover objects
self.tl = logging self.tl = logging
self.po = pushover self.po = pushover
self.ct = Counter self.ct = Counter
# Variables torlog uses from config.json # Variables torlog uses from config.json
self.use_pushover = self.config["use_pushover"] self.use_pushover = self.config["use_pushover"]
self.use_log = self.config["use_log"] self.use_log = self.config["use_log"]
self.po_key = self.config["po_key"] self.po_key = self.config["po_key"]
self.po_token = self.config["po_token"] self.po_token = self.config["po_token"]
self.logpath = self.config["logpath"] self.logpath = self.config["logpath"]
self.loglevel = self.config["loglevel"] self.loglevel = self.config["loglevel"]
self.tracker_protected_tag = self.config["protected_tag"] self.tracker_protected_tag = self.config["protected_tag"]
self.tracker_non_protected_tag = self.config["non_protected_tag"] self.tracker_non_protected_tag = self.config["non_protected_tag"]
self.minimum_age = self.config["minimum_age"] self.minimum_age = self.config["minimum_age"]
self.age = self.config["age"] self.age = self.config["age"]
# Calling log and notify functions # Calling log and notify functions
torlog(self) torlog(self)
tornotify(self) tornotify(self)
self.t = time self.t = time
# Pulling domain names to treat carefully # Pulling domain names to treat carefully
f = open('./tracker-whitelist.json') f = open('./tracker-whitelist.json')
self.tracker_whitelist = load(f) self.tracker_whitelist = load(f)
self.tracker_list = [] self.tracker_list = []
self.up_tor_counter = 0 self.up_tor_counter = 0
self.preme_tor_counter = 0 self.preme_tor_counter = 0
self.ignored_counter = 0 self.ignored_counter = 0
self.torrent_hash_delete_list = [] self.torrent_hash_delete_list = []
if self.use_log: if self.use_log:
self.tl.debug(self.tracker_whitelist) self.tl.debug(self.tracker_whitelist)
#logging in #logging in
try: try:
self.tl.info('Connecting to host.') self.tl.info('Connecting to host.')
self.qbt_client.auth_log_in() self.qbt_client.auth_log_in()
self.tl.info('Connected.') self.tl.info('Connected.')
except qbittorrentapi.APIError as e: except qbittorrentapi.APIError as e:
self.tl.exception(e) self.tl.exception(e)
self.poc.send_message(e, title="qbit-maid API ERROR") self.poc.send_message(e, title="qbit-maid API ERROR")
self.torrentlist = {} self.torrentlist = {}
# Pulling all torrent data # Pulling all torrent data
self.torrentlist = self.qbt_client.torrents_info() self.torrentlist = self.qbt_client.torrents_info()
#Main process block #Main process block
if self.use_log: if self.use_log:
listqbitapiinfo(self) listqbitapiinfo(self)
listfirsttor(self) listfirsttor(self)
buildtorlist(self) buildtorlist(self)
processcounts(self) processcounts(self)
#tordeletetags(self) #tordeletetags(self)
if self.use_log: if self.use_log:
torrentcount(self) torrentcount(self)
torprocessor(self) torprocessor(self)
if self.use_log: if self.use_log:
printprocessor(self) printprocessor(self)
if self.use_pushover: if self.use_pushover:
tornotifysummary(self) tornotifysummary(self)
if self.config["delete_torrents"]: if self.config["delete_torrents"]:
tordelete(self) tordelete(self)
# Run # Run
if __name__== "__main__": if __name__== "__main__":
Qbt() Qbt()

View File

@ -1,35 +1,35 @@
def buildtorlist(self): def buildtorlist(self):
"""Builds multiple lists of torrents to be sorted. Also adds tags to the torents. """Builds multiple lists of torrents to be sorted. Also adds tags to the torents.
There are more effecient ways of doing things but I did this rather quickly. There are more effecient ways of doing things but I did this rather quickly.
V2 will certainly be more performant. The reason two lists were used was so that torrents V2 will certainly be more performant. The reason two lists were used was so that torrents
that are in public trackers woudln't be around as long as torrents from a private tracker. that are in public trackers woudln't be around as long as torrents from a private tracker.
""" """
self.total_torrents = len(self.torrentlist) self.total_torrents = len(self.torrentlist)
while self.torrentlist: while self.torrentlist:
torrent = self.torrentlist.pop() torrent = self.torrentlist.pop()
if self.use_log: if self.use_log:
self.tl.debug(f'["{torrent["name"][0:20]}..."] {torrent["infohash_v1"]}') self.tl.debug(f'["{torrent["name"][0:20]}..."] {torrent["infohash_v1"]}')
if torrent['added_on'] + self.minimum_age <= self.t.time(): if torrent['added_on'] + self.minimum_age <= self.t.time():
self.preme_tor_counter += 1 self.preme_tor_counter += 1
continue continue
if torrent['category'] in self.cat_whitelist.values(): if torrent['category'] in self.cat_whitelist.values():
self.tl.info(f'Ignored torrent:["{torrent["name"][0:20]}..."]') self.tl.info(f'Ignored torrent:["{torrent["name"][0:20]}..."]')
self.ignored_counter += 1 self.ignored_counter += 1
continue continue
if torrent['tracker'] == '': if torrent['tracker'] == '':
if self.use_log: if self.use_log:
self.tl.warning(f'Torrent doesn\'t have a tracker ["{torrent["name"][0:20]}..."] [{torrent["tracker"]}]hash: {torrent["hash"]}') self.tl.warning(f'Torrent doesn\'t have a tracker ["{torrent["name"][0:20]}..."] [{torrent["tracker"]}]hash: {torrent["hash"]}')
self.ignored_counter += 1 self.ignored_counter += 1
continue continue
if torrent['tracker'].split('/')[2] in self.tracker_whitelist.values(): if torrent['tracker'].split('/')[2] in self.tracker_whitelist.values():
if self.use_log: if self.use_log:
self.tl.debug(f'Protected torrent: {torrent["tracker"]}hash: {torrent["hash"]}') self.tl.debug(f'Protected torrent: {torrent["tracker"]}hash: {torrent["hash"]}')
if torrent['tags'] == '': if torrent['tags'] == '':
self.qbt_client.torrents_add_tags(self.tracker_protected_tag,torrent['hash']) self.qbt_client.torrents_add_tags(self.tracker_protected_tag,torrent['hash'])
self.tracker_list.append(torrent) self.tracker_list.append(torrent)
if torrent['tracker'].split('/')[2] not in self.tracker_whitelist.values(): if torrent['tracker'].split('/')[2] not in self.tracker_whitelist.values():
if self.use_log: if self.use_log:
self.tl.debug(f'Non-protected torrent: {torrent["tracker"]}hash: {torrent["hash"]}') self.tl.debug(f'Non-protected torrent: {torrent["tracker"]}hash: {torrent["hash"]}')
if torrent['tags'] == '': if torrent['tags'] == '':
self.qbt_client.torrents_add_tags(self.tracker_non_protected_tag,torrent['hash']) self.qbt_client.torrents_add_tags(self.tracker_non_protected_tag,torrent['hash'])
self.tracker_list.append(torrent) self.tracker_list.append(torrent)

View File

@ -1,75 +1,75 @@
def torlog(self): def torlog(self):
"""Setting up the log file, if self.use_log is set to true and self.loglevel is DEBUG OR INFO""" """Setting up the log file, if self.use_log is set to true and self.loglevel is DEBUG OR INFO"""
if self.use_log: if self.use_log:
if self.loglevel == 'DEBUG': if self.loglevel == 'DEBUG':
self.tl.basicConfig(filename=self.logpath, format='%(asctime)s:%(levelname)s:%(message)s', encoding='utf-8', datefmt='%m/%d/%Y %I:%M:%S %p',level=self.tl.DEBUG) self.tl.basicConfig(filename=self.logpath, format='%(asctime)s:%(levelname)s:%(message)s', encoding='utf-8', datefmt='%m/%d/%Y %I:%M:%S %p',level=self.tl.DEBUG)
elif self.loglevel == 'INFO': elif self.loglevel == 'INFO':
self.tl.basicConfig(filename=self.logpath, format='%(asctime)s:%(levelname)s:%(message)s', encoding='utf-8', datefmt='%m/%d/%Y %I:%M:%S %p',level=self.tl.INFO) self.tl.basicConfig(filename=self.logpath, format='%(asctime)s:%(levelname)s:%(message)s', encoding='utf-8', datefmt='%m/%d/%Y %I:%M:%S %p',level=self.tl.INFO)
def tornotify(self): def tornotify(self):
"""Seting up to use pushover, if self.use_pushover is set to true and """Seting up to use pushover, if self.use_pushover is set to true and
if valid self.po_key and self.po_token is provided in the config file""" if valid self.po_key and self.po_token is provided in the config file"""
if self.use_pushover: if self.use_pushover:
self.poc = self.po.Client(self.po_key, api_token=self.po_token) self.poc = self.po.Client(self.po_key, api_token=self.po_token)
def tornotifytest(self): def tornotifytest(self):
"""Used to make sure tornotify is working and messages are getting to the client""" """Used to make sure tornotify is working and messages are getting to the client"""
self.poc.send_message("Test Message", title="qbit-maid") self.poc.send_message("Test Message", title="qbit-maid")
def processcounts(self): def processcounts(self):
self.c = self.ct() self.c = self.ct()
for item in self.tracker_list: for item in self.tracker_list:
self.c[item["tags"]] += 1 self.c[item["tags"]] += 1
def printprocessor(self): def printprocessor(self):
"""Print summary of torrents""" """Print summary of torrents"""
self.tl.info(f'Total: {self.total_torrents}') self.tl.info(f'Total: {self.total_torrents}')
self.tl.info(f'Premature: {self.preme_tor_counter}') self.tl.info(f'Premature: {self.preme_tor_counter}')
self.tl.info(f'Ignored: {self.ignored_counter}') self.tl.info(f'Ignored: {self.ignored_counter}')
self.tl.info(f'Protected: {self.c[self.tracker_protected_tag]}') self.tl.info(f'Protected: {self.c[self.tracker_protected_tag]}')
self.tl.info(f'Non-protected: {self.c[self.tracker_non_protected_tag]}') self.tl.info(f'Non-protected: {self.c[self.tracker_non_protected_tag]}')
self.tl.info(f'Orphaned: {self.up_tor_counter}') self.tl.info(f'Orphaned: {self.up_tor_counter}')
self.tl.info(f'Marked for deletion: {len(self.torrent_hash_delete_list)}') self.tl.info(f'Marked for deletion: {len(self.torrent_hash_delete_list)}')
def tornotifysummary(self): def tornotifysummary(self):
"""Main notification method when the app is used in an automated fashion""" """Main notification method when the app is used in an automated fashion"""
self.poc.send_message(f" Total: {self.total_torrents}\n\ self.poc.send_message(f" Total: {self.total_torrents}\n\
Premature: {self.preme_tor_counter}\n\ Premature: {self.preme_tor_counter}\n\
Ignored: {self.ignored_counter}\n\ Ignored: {self.ignored_counter}\n\
Protected: {self.c[self.tracker_protected_tag]}\n\ Protected: {self.c[self.tracker_protected_tag]}\n\
Non-protected: {self.c[self.tracker_non_protected_tag]}\n\ Non-protected: {self.c[self.tracker_non_protected_tag]}\n\
Orphaned: {self.up_tor_counter}\n\ Orphaned: {self.up_tor_counter}\n\
Marked for deletion: {len(self.torrent_hash_delete_list)}", title="--- qbit-maid summary ---") Marked for deletion: {len(self.torrent_hash_delete_list)}", title="--- qbit-maid summary ---")
def getunixtimestamp(self): def getunixtimestamp(self):
"""Used for debuging and development related to unixtimestamps, not used in main script but useful""" """Used for debuging and development related to unixtimestamps, not used in main script but useful"""
self.uts = self.t.time() self.uts = self.t.time()
self.tl.info(self.uts) self.tl.info(self.uts)
def writetor(self, filepath='./torrentinfo.txt'): def writetor(self, filepath='./torrentinfo.txt'):
"""Write all torrent data to a file. """Write all torrent data to a file.
Useful for development of new features. Useful for development of new features.
""" """
with open(filepath, 'w') as fp: with open(filepath, 'w') as fp:
fp.write(str(self.torrentlist)) fp.write(str(self.torrentlist))
def listfirsttor(self, index=0): def listfirsttor(self, index=0):
"""Only lists the first torrent""" """Only lists the first torrent"""
self.tl.debug('First torrent in the list:') self.tl.debug('First torrent in the list:')
torrent = self.torrentlist[index] torrent = self.torrentlist[index]
for k,v in torrent.items(): for k,v in torrent.items():
self.tl.debug(f'{k}: {v}') self.tl.debug(f'{k}: {v}')
self.tl.debug('\n') self.tl.debug('\n')
def listqbitapiinfo(self): def listqbitapiinfo(self):
"""Writes torrent info to log file""" """Writes torrent info to log file"""
self.tl.debug(f'qBittorrent: {self.qbt_client.app.version}') self.tl.debug(f'qBittorrent: {self.qbt_client.app.version}')
self.tl.debug(f'qBittorrent Web API: {self.qbt_client.app.web_api_version}') self.tl.debug(f'qBittorrent Web API: {self.qbt_client.app.web_api_version}')
def torrentcount(self): def torrentcount(self):
"""write torrent counts to log file""" """write torrent counts to log file"""
self.tl.debug(f'torrents that are protected {self.tracker_list.count("ipt")}') self.tl.debug(f'torrents that are protected {self.tracker_list.count("ipt")}')
self.tl.debug(f'torrents that aren\'t protected {self.tracker_list.count("public")}') self.tl.debug(f'torrents that aren\'t protected {self.tracker_list.count("public")}')
def torlisttags(self): def torlisttags(self):
pass pass

View File

@ -1,44 +1,44 @@
def torprocessor(self): def torprocessor(self):
"""Main logic to sort through both self.tracker_nonprotected_list and self.tracker_protected_list """Main logic to sort through both self.tracker_nonprotected_list and self.tracker_protected_list
If torrent meets criteria for deletion, its infohash_v1 will be appended to self.torrent_hash_delete_list If torrent meets criteria for deletion, its infohash_v1 will be appended to self.torrent_hash_delete_list
""" """
for canidate in self.tracker_list: for canidate in self.tracker_list:
if canidate['state'] == 'downloading': if canidate['state'] == 'downloading':
if self.use_log: if self.use_log:
self.tl.info(f'["{canidate["name"][0:20]}..."] is still downloading and will be skipped.') self.tl.info(f'["{canidate["name"][0:20]}..."] is still downloading and will be skipped.')
continue continue
elif canidate['ratio'] < float(1.05) and self.tracker_protected_tag in canidate["tags"]: elif canidate['ratio'] < float(1.05) and self.tracker_protected_tag in canidate["tags"]:
if self.use_log: if self.use_log:
self.tl.debug(f'["{canidate["name"][0:20]}..."] is below a 1.05 ratio({canidate["ratio"]})') self.tl.debug(f'["{canidate["name"][0:20]}..."] is below a 1.05 ratio({canidate["ratio"]})')
if canidate['added_on'] + self.age <= self.t.time(): if canidate['added_on'] + self.age <= self.t.time():
if self.use_log: if self.use_log:
self.tl.debug(f'["{canidate["name"][0:20]}..."] Seconds old: {self.t.time() - self.age - canidate["added_on"]}') self.tl.debug(f'["{canidate["name"][0:20]}..."] Seconds old: {self.t.time() - self.age - canidate["added_on"]}')
self.torrent_hash_delete_list.append(canidate['infohash_v1']) self.torrent_hash_delete_list.append(canidate['infohash_v1'])
if self.use_log: if self.use_log:
self.tl.info(f'Submitted ["{canidate["name"][0:20]}..."] for deletion from the protected list.') self.tl.info(f'Submitted ["{canidate["name"][0:20]}..."] for deletion from the protected list.')
elif canidate['ratio'] >= float(1.05) and self.tracker_protected_tag in canidate["tags"]: elif canidate['ratio'] >= float(1.05) and self.tracker_protected_tag in canidate["tags"]:
if self.use_log: if self.use_log:
self.tl.debug(f'["{canidate["name"][0:20]}..."] is above a 1.05 ratio({canidate["ratio"]}).') self.tl.debug(f'["{canidate["name"][0:20]}..."] is above a 1.05 ratio({canidate["ratio"]}).')
self.torrent_hash_delete_list.append(canidate['infohash_v1']) self.torrent_hash_delete_list.append(canidate['infohash_v1'])
if self.use_log: if self.use_log:
self.tl.info(f'Submitted ["{canidate["name"][0:20]}..."] for deletion from the protected list.') self.tl.info(f'Submitted ["{canidate["name"][0:20]}..."] for deletion from the protected list.')
elif self.tracker_non_protected_tag in canidate["tags"]: elif self.tracker_non_protected_tag in canidate["tags"]:
self.torrent_hash_delete_list.append(canidate['infohash_v1']) self.torrent_hash_delete_list.append(canidate['infohash_v1'])
if self.use_log: if self.use_log:
self.tl.info(f'Submitted ["{canidate["name"][0:20]}..."] for deletion.') self.tl.info(f'Submitted ["{canidate["name"][0:20]}..."] for deletion.')
else: else:
self.tl.info(f'["{canidate["name"][0:20]}..."] is orphaned.') self.tl.info(f'["{canidate["name"][0:20]}..."] is orphaned.')
self.up_tor_counter += 1 self.up_tor_counter += 1
continue continue
def tordeletetags(self): def tordeletetags(self):
tag_list = [self.tracker_protected_tag, self.tracker_non_protected_tag] tag_list = [self.tracker_protected_tag, self.tracker_non_protected_tag]
self.qbt_client.torrents_delete_tags(tag_list) self.qbt_client.torrents_delete_tags(tag_list)
def tordelete(self): def tordelete(self):
"""Remove torrents, will also delete files, this keeps the filesystem clean. """Remove torrents, will also delete files, this keeps the filesystem clean.
Only pass self.torrent_hash_delete_list if you would like to keep the files.""" Only pass self.torrent_hash_delete_list if you would like to keep the files."""
if self.use_log: if self.use_log:
self.tl.debug('Hash list submitted for deletion:') self.tl.debug('Hash list submitted for deletion:')
self.tl.debug(self.torrent_hash_delete_list) self.tl.debug(self.torrent_hash_delete_list)
self.qbt_client.torrents_delete(True, self.torrent_hash_delete_list) self.qbt_client.torrents_delete(True, self.torrent_hash_delete_list)