mirror of
https://github.com/StuffAnThings/qbit_manage.git
synced 2025-11-09 16:00:53 +08:00
refactor category and tags
This commit is contained in:
parent
c180eba390
commit
bece9526e3
7 changed files with 532 additions and 494 deletions
20
modules/__init__.py
Normal file
20
modules/__init__.py
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Define an empty version_info tuple
|
||||||
|
__version_info__ = ()
|
||||||
|
|
||||||
|
# Get the path to the project directory
|
||||||
|
project_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
# Get the path to the VERSION file
|
||||||
|
version_file_path = os.path.join(project_dir, "..", "VERSION")
|
||||||
|
|
||||||
|
# Read the version from the file
|
||||||
|
with open(version_file_path) as f:
|
||||||
|
version_str = f.read().strip()
|
||||||
|
|
||||||
|
# Convert the version string to a tuple of integers
|
||||||
|
__version_info__ = tuple(map(int, version_str.split(".")))
|
||||||
|
|
||||||
|
# Define the version string using the version_info tuple
|
||||||
|
__version__ = ".".join(str(i) for i in __version_info__)
|
||||||
|
|
@ -474,240 +474,6 @@ class Config:
|
||||||
self.notify(e, "Config")
|
self.notify(e, "Config")
|
||||||
raise Failed(e)
|
raise Failed(e)
|
||||||
|
|
||||||
# Get tags from config file based on keyword
|
|
||||||
def get_tags(self, trackers):
|
|
||||||
urls = [x.url for x in trackers if x.url.startswith("http")]
|
|
||||||
tracker = {}
|
|
||||||
tracker["tag"] = None
|
|
||||||
tracker["max_ratio"] = None
|
|
||||||
tracker["min_seeding_time"] = None
|
|
||||||
tracker["max_seeding_time"] = None
|
|
||||||
tracker["limit_upload_speed"] = None
|
|
||||||
tracker["notifiarr"] = None
|
|
||||||
tracker["url"] = None
|
|
||||||
tracker_other_tag = self.util.check_for_attribute(
|
|
||||||
self.data, "tag", parent="tracker", subparent="other", default_is_none=True, var_type="list", save=False
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
tracker["url"] = util.trunc_val(urls[0], os.sep)
|
|
||||||
except IndexError as e:
|
|
||||||
tracker["url"] = None
|
|
||||||
if not urls:
|
|
||||||
urls = []
|
|
||||||
if not tracker_other_tag:
|
|
||||||
tracker_other_tag = ["other"]
|
|
||||||
tracker["url"] = "No http URL found"
|
|
||||||
else:
|
|
||||||
logger.debug(f"Tracker Url:{urls}")
|
|
||||||
logger.debug(e)
|
|
||||||
if "tracker" in self.data and self.data["tracker"] is not None:
|
|
||||||
tag_values = self.data["tracker"]
|
|
||||||
for tag_url, tag_details in tag_values.items():
|
|
||||||
for url in urls:
|
|
||||||
if tag_url in url:
|
|
||||||
if tracker["url"] is None:
|
|
||||||
default_tag = tracker_other_tag
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
tracker["url"] = util.trunc_val(url, os.sep)
|
|
||||||
default_tag = tracker["url"].split(os.sep)[2].split(":")[0]
|
|
||||||
except IndexError as e:
|
|
||||||
logger.debug(f"Tracker Url:{url}")
|
|
||||||
logger.debug(e)
|
|
||||||
# Tracker Format 1 deprecated.
|
|
||||||
if isinstance(tag_details, str):
|
|
||||||
e = (
|
|
||||||
"Config Error: Tracker format invalid. Please see config.yml.sample for correct format and fix "
|
|
||||||
f"`{tag_details}` in the Tracker section of the config."
|
|
||||||
)
|
|
||||||
self.notify(e, "Config")
|
|
||||||
raise Failed(e)
|
|
||||||
# Using new Format
|
|
||||||
else:
|
|
||||||
tracker["tag"] = self.util.check_for_attribute(
|
|
||||||
self.data, "tag", parent="tracker", subparent=tag_url, default=tag_url, var_type="list"
|
|
||||||
)
|
|
||||||
if tracker["tag"] == [tag_url]:
|
|
||||||
self.data["tracker"][tag_url]["tag"] = [tag_url]
|
|
||||||
if isinstance(tracker["tag"], str):
|
|
||||||
tracker["tag"] = [tracker["tag"]]
|
|
||||||
is_max_ratio_defined = self.data["tracker"].get("max_ratio")
|
|
||||||
is_max_seeding_time_defined = self.data["tracker"].get("max_seeding_time")
|
|
||||||
if is_max_ratio_defined or is_max_seeding_time_defined:
|
|
||||||
tracker["max_ratio"] = self.util.check_for_attribute(
|
|
||||||
self.data,
|
|
||||||
"max_ratio",
|
|
||||||
parent="tracker",
|
|
||||||
subparent=tag_url,
|
|
||||||
var_type="float",
|
|
||||||
min_int=-2,
|
|
||||||
do_print=False,
|
|
||||||
default=-1,
|
|
||||||
save=False,
|
|
||||||
)
|
|
||||||
tracker["max_seeding_time"] = self.util.check_for_attribute(
|
|
||||||
self.data,
|
|
||||||
"max_seeding_time",
|
|
||||||
parent="tracker",
|
|
||||||
subparent=tag_url,
|
|
||||||
var_type="int",
|
|
||||||
min_int=-2,
|
|
||||||
do_print=False,
|
|
||||||
default=-1,
|
|
||||||
save=False,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
tracker["max_ratio"] = self.util.check_for_attribute(
|
|
||||||
self.data,
|
|
||||||
"max_ratio",
|
|
||||||
parent="tracker",
|
|
||||||
subparent=tag_url,
|
|
||||||
var_type="float",
|
|
||||||
min_int=-2,
|
|
||||||
do_print=False,
|
|
||||||
default_is_none=True,
|
|
||||||
save=False,
|
|
||||||
)
|
|
||||||
tracker["max_seeding_time"] = self.util.check_for_attribute(
|
|
||||||
self.data,
|
|
||||||
"max_seeding_time",
|
|
||||||
parent="tracker",
|
|
||||||
subparent=tag_url,
|
|
||||||
var_type="int",
|
|
||||||
min_int=-2,
|
|
||||||
do_print=False,
|
|
||||||
default_is_none=True,
|
|
||||||
save=False,
|
|
||||||
)
|
|
||||||
tracker["min_seeding_time"] = self.util.check_for_attribute(
|
|
||||||
self.data,
|
|
||||||
"min_seeding_time",
|
|
||||||
parent="tracker",
|
|
||||||
subparent=tag_url,
|
|
||||||
var_type="int",
|
|
||||||
min_int=0,
|
|
||||||
do_print=False,
|
|
||||||
default=0,
|
|
||||||
save=False,
|
|
||||||
)
|
|
||||||
tracker["limit_upload_speed"] = self.util.check_for_attribute(
|
|
||||||
self.data,
|
|
||||||
"limit_upload_speed",
|
|
||||||
parent="tracker",
|
|
||||||
subparent=tag_url,
|
|
||||||
var_type="int",
|
|
||||||
min_int=-1,
|
|
||||||
do_print=False,
|
|
||||||
default=0,
|
|
||||||
save=False,
|
|
||||||
)
|
|
||||||
tracker["notifiarr"] = self.util.check_for_attribute(
|
|
||||||
self.data,
|
|
||||||
"notifiarr",
|
|
||||||
parent="tracker",
|
|
||||||
subparent=tag_url,
|
|
||||||
default_is_none=True,
|
|
||||||
do_print=False,
|
|
||||||
save=False,
|
|
||||||
)
|
|
||||||
return tracker
|
|
||||||
if tracker_other_tag:
|
|
||||||
tracker["tag"] = tracker_other_tag
|
|
||||||
tracker["max_ratio"] = self.util.check_for_attribute(
|
|
||||||
self.data,
|
|
||||||
"max_ratio",
|
|
||||||
parent="tracker",
|
|
||||||
subparent="other",
|
|
||||||
var_type="float",
|
|
||||||
min_int=-2,
|
|
||||||
do_print=False,
|
|
||||||
default=-1,
|
|
||||||
save=False,
|
|
||||||
)
|
|
||||||
tracker["min_seeding_time"] = self.util.check_for_attribute(
|
|
||||||
self.data,
|
|
||||||
"min_seeding_time",
|
|
||||||
parent="tracker",
|
|
||||||
subparent="other",
|
|
||||||
var_type="int",
|
|
||||||
min_int=0,
|
|
||||||
do_print=False,
|
|
||||||
default=-1,
|
|
||||||
save=False,
|
|
||||||
)
|
|
||||||
tracker["max_seeding_time"] = self.util.check_for_attribute(
|
|
||||||
self.data,
|
|
||||||
"max_seeding_time",
|
|
||||||
parent="tracker",
|
|
||||||
subparent="other",
|
|
||||||
var_type="int",
|
|
||||||
min_int=-2,
|
|
||||||
do_print=False,
|
|
||||||
default=-1,
|
|
||||||
save=False,
|
|
||||||
)
|
|
||||||
tracker["limit_upload_speed"] = self.util.check_for_attribute(
|
|
||||||
self.data,
|
|
||||||
"limit_upload_speed",
|
|
||||||
parent="tracker",
|
|
||||||
subparent="other",
|
|
||||||
var_type="int",
|
|
||||||
min_int=-1,
|
|
||||||
do_print=False,
|
|
||||||
default=0,
|
|
||||||
save=False,
|
|
||||||
)
|
|
||||||
tracker["notifiarr"] = self.util.check_for_attribute(
|
|
||||||
self.data,
|
|
||||||
"notifiarr",
|
|
||||||
parent="tracker",
|
|
||||||
subparent="other",
|
|
||||||
default_is_none=True,
|
|
||||||
do_print=False,
|
|
||||||
save=False,
|
|
||||||
)
|
|
||||||
return tracker
|
|
||||||
if tracker["url"]:
|
|
||||||
logger.trace(f"tracker url: {tracker['url']}")
|
|
||||||
if tracker_other_tag:
|
|
||||||
default_tag = tracker_other_tag
|
|
||||||
else:
|
|
||||||
default_tag = tracker["url"].split(os.sep)[2].split(":")[0]
|
|
||||||
tracker["tag"] = self.util.check_for_attribute(
|
|
||||||
self.data, "tag", parent="tracker", subparent=default_tag, default=default_tag, var_type="list"
|
|
||||||
)
|
|
||||||
if isinstance(tracker["tag"], str):
|
|
||||||
tracker["tag"] = [tracker["tag"]]
|
|
||||||
try:
|
|
||||||
self.data["tracker"][default_tag]["tag"] = [default_tag]
|
|
||||||
except Exception:
|
|
||||||
self.data["tracker"][default_tag] = {"tag": [default_tag]}
|
|
||||||
e = f'No tags matched for {tracker["url"]}. Please check your config.yml file. Setting tag to {default_tag}'
|
|
||||||
self.notify(e, "Tag", False)
|
|
||||||
logger.warning(e)
|
|
||||||
return tracker
|
|
||||||
|
|
||||||
# Get category from config file based on path provided
|
|
||||||
def get_category(self, path):
|
|
||||||
category = ""
|
|
||||||
path = os.path.join(path, "")
|
|
||||||
if "cat" in self.data and self.data["cat"] is not None:
|
|
||||||
cat_path = self.data["cat"]
|
|
||||||
for cat, save_path in cat_path.items():
|
|
||||||
if os.path.join(save_path, "") == path:
|
|
||||||
category = cat
|
|
||||||
break
|
|
||||||
|
|
||||||
if not category:
|
|
||||||
default_cat = path.split(os.sep)[-2]
|
|
||||||
category = str(default_cat)
|
|
||||||
self.util.check_for_attribute(self.data, default_cat, parent="cat", default=path)
|
|
||||||
self.data["cat"][str(default_cat)] = path
|
|
||||||
e = f"No categories matched for the save path {path}. Check your config.yml file. - Setting category to {default_cat}"
|
|
||||||
self.notify(e, "Category", False)
|
|
||||||
logger.warning(e)
|
|
||||||
return category
|
|
||||||
|
|
||||||
# Empty old files from recycle bin or orphaned
|
# Empty old files from recycle bin or orphaned
|
||||||
def cleanup_dirs(self, location):
|
def cleanup_dirs(self, location):
|
||||||
num_del = 0
|
num_del = 0
|
||||||
|
|
|
||||||
3
modules/core/__init__.py
Normal file
3
modules/core/__init__.py
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
"""
|
||||||
|
modules.core contains all the core functions of qbit_manage such as updating categories/tags etc..
|
||||||
|
"""
|
||||||
78
modules/core/category.py
Normal file
78
modules/core/category.py
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
from qbittorrentapi import Conflict409Error
|
||||||
|
|
||||||
|
from modules import util
|
||||||
|
|
||||||
|
logger = util.logger
|
||||||
|
|
||||||
|
|
||||||
|
class Category:
|
||||||
|
def __init__(self, qbit_manager):
|
||||||
|
self.qbt = qbit_manager
|
||||||
|
self.config = qbit_manager.config
|
||||||
|
self.client = qbit_manager.client
|
||||||
|
self.stats = 0
|
||||||
|
|
||||||
|
self.category()
|
||||||
|
|
||||||
|
def category(self):
|
||||||
|
"""Update category for torrents that don't have any category defined and returns total number categories updated"""
|
||||||
|
self.stats = 0
|
||||||
|
|
||||||
|
logger.separator("Updating Categories", space=False, border=False)
|
||||||
|
torrent_list = self.qbt.get_torrents({"category": "", "status_filter": "completed"})
|
||||||
|
for torrent in torrent_list:
|
||||||
|
new_cat = self.qbt.get_category(torrent.save_path)
|
||||||
|
self.update_cat(torrent, new_cat, False)
|
||||||
|
|
||||||
|
# Change categories
|
||||||
|
if self.config.cat_change:
|
||||||
|
for old_cat in self.config.cat_change:
|
||||||
|
torrent_list = self.qbt.get_torrents({"category": old_cat, "status_filter": "completed"})
|
||||||
|
for torrent in torrent_list:
|
||||||
|
new_cat = self.config.cat_change[old_cat]
|
||||||
|
self.update_cat(torrent, new_cat, True)
|
||||||
|
|
||||||
|
if self.stats >= 1:
|
||||||
|
logger.print_line(
|
||||||
|
f"{'Did not update' if self.config.dry_run else 'Updated'} {self.stats} new categories.", self.config.loglevel
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.print_line("No new torrents to categorize.", self.config.loglevel)
|
||||||
|
|
||||||
|
def update_cat(self, torrent, new_cat, cat_change):
|
||||||
|
"""Update category based on the torrent information"""
|
||||||
|
tracker = self.qbt.get_tags(torrent.trackers)
|
||||||
|
old_cat = torrent.category
|
||||||
|
if not self.config.dry_run:
|
||||||
|
try:
|
||||||
|
torrent.set_category(category=new_cat)
|
||||||
|
if torrent.auto_tmm is False and self.config.settings["force_auto_tmm"]:
|
||||||
|
torrent.set_auto_management(True)
|
||||||
|
except Conflict409Error:
|
||||||
|
ex = logger.print_line(
|
||||||
|
f'Existing category "{new_cat}" not found for save path {torrent.save_path}, category will be created.',
|
||||||
|
self.config.loglevel,
|
||||||
|
)
|
||||||
|
self.config.notify(ex, "Update Category", False)
|
||||||
|
self.client.torrent_categories.create_category(name=new_cat, save_path=torrent.save_path)
|
||||||
|
torrent.set_category(category=new_cat)
|
||||||
|
body = []
|
||||||
|
body += logger.print_line(logger.insert_space(f"Torrent Name: {torrent.name}", 3), self.config.loglevel)
|
||||||
|
if cat_change:
|
||||||
|
body += logger.print_line(logger.insert_space(f"Old Category: {old_cat}", 3), self.config.loglevel)
|
||||||
|
title = "Moving Categories"
|
||||||
|
else:
|
||||||
|
title = "Updating Categories"
|
||||||
|
body += logger.print_line(logger.insert_space(f"New Category: {new_cat}", 3), self.config.loglevel)
|
||||||
|
body += logger.print_line(logger.insert_space(f'Tracker: {tracker["url"]}', 8), self.config.loglevel)
|
||||||
|
attr = {
|
||||||
|
"function": "cat_update",
|
||||||
|
"title": title,
|
||||||
|
"body": "\n".join(body),
|
||||||
|
"torrent_name": torrent.name,
|
||||||
|
"torrent_category": new_cat,
|
||||||
|
"torrent_tracker": tracker["url"],
|
||||||
|
"notifiarr_indexer": tracker["notifiarr"],
|
||||||
|
}
|
||||||
|
self.config.send_notifications(attr)
|
||||||
|
self.stats += 1
|
||||||
62
modules/core/tags.py
Normal file
62
modules/core/tags.py
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
from modules import util
|
||||||
|
|
||||||
|
logger = util.logger
|
||||||
|
|
||||||
|
|
||||||
|
class Tags:
|
||||||
|
def __init__(self, qbit_manager):
|
||||||
|
self.qbt = qbit_manager
|
||||||
|
self.config = qbit_manager.config
|
||||||
|
self.client = qbit_manager.client
|
||||||
|
self.stats = 0
|
||||||
|
|
||||||
|
self.tags()
|
||||||
|
|
||||||
|
def tags(self):
|
||||||
|
"""Update tags for torrents"""
|
||||||
|
self.stats = 0
|
||||||
|
ignore_tags = self.config.settings["ignoreTags_OnUpdate"]
|
||||||
|
logger.separator("Updating Tags", space=False, border=False)
|
||||||
|
for torrent in self.qbt.torrent_list:
|
||||||
|
check_tags = util.get_list(torrent.tags)
|
||||||
|
if torrent.tags == "" or (len([trk for trk in check_tags if trk not in ignore_tags]) == 0):
|
||||||
|
tracker = self.qbt.get_tags(torrent.trackers)
|
||||||
|
if tracker["tag"]:
|
||||||
|
self.stats += len(tracker["tag"])
|
||||||
|
body = []
|
||||||
|
body += logger.print_line(logger.insert_space(f"Torrent Name: {torrent.name}", 3), self.config.loglevel)
|
||||||
|
body += logger.print_line(
|
||||||
|
logger.insert_space(f'New Tag{"s" if len(tracker["tag"]) > 1 else ""}: {", ".join(tracker["tag"])}', 8),
|
||||||
|
self.config.loglevel,
|
||||||
|
)
|
||||||
|
body += logger.print_line(logger.insert_space(f'Tracker: {tracker["url"]}', 8), self.config.loglevel)
|
||||||
|
body.extend(
|
||||||
|
self.qbt.set_tags_and_limits(
|
||||||
|
torrent,
|
||||||
|
tracker["max_ratio"],
|
||||||
|
tracker["max_seeding_time"],
|
||||||
|
tracker["limit_upload_speed"],
|
||||||
|
tracker["tag"],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
category = self.qbt.get_category(torrent.save_path) if torrent.category == "" else torrent.category
|
||||||
|
attr = {
|
||||||
|
"function": "tag_update",
|
||||||
|
"title": "Updating Tags",
|
||||||
|
"body": "\n".join(body),
|
||||||
|
"torrent_name": torrent.name,
|
||||||
|
"torrent_category": category,
|
||||||
|
"torrent_tag": ", ".join(tracker["tag"]),
|
||||||
|
"torrent_tracker": tracker["url"],
|
||||||
|
"notifiarr_indexer": tracker["notifiarr"],
|
||||||
|
"torrent_max_ratio": tracker["max_ratio"],
|
||||||
|
"torrent_max_seeding_time": tracker["max_seeding_time"],
|
||||||
|
"torrent_limit_upload_speed": tracker["limit_upload_speed"],
|
||||||
|
}
|
||||||
|
self.config.send_notifications(attr)
|
||||||
|
if self.stats >= 1:
|
||||||
|
logger.print_line(
|
||||||
|
f"{'Did not update' if self.config.dry_run else 'Updated'} {self.stats} new tags.", self.config.loglevel
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.print_line("No new torrents to tag.", self.config.loglevel)
|
||||||
|
|
@ -7,7 +7,6 @@ from fnmatch import fnmatch
|
||||||
|
|
||||||
from qbittorrentapi import APIConnectionError
|
from qbittorrentapi import APIConnectionError
|
||||||
from qbittorrentapi import Client
|
from qbittorrentapi import Client
|
||||||
from qbittorrentapi import Conflict409Error
|
|
||||||
from qbittorrentapi import LoginFailed
|
from qbittorrentapi import LoginFailed
|
||||||
from qbittorrentapi import NotFound404Error
|
from qbittorrentapi import NotFound404Error
|
||||||
from qbittorrentapi import Version
|
from qbittorrentapi import Version
|
||||||
|
|
@ -26,6 +25,7 @@ class Qbt:
|
||||||
|
|
||||||
SUPPORTED_VERSION = Version.latest_supported_app_version()
|
SUPPORTED_VERSION = Version.latest_supported_app_version()
|
||||||
MIN_SUPPORTED_VERSION = "v4.3.0"
|
MIN_SUPPORTED_VERSION = "v4.3.0"
|
||||||
|
TORRENT_DICT_COMMANDS = ["recheck", "cross_seed", "rem_unregistered", "tag_tracker_error", "tag_nohardlinks"]
|
||||||
|
|
||||||
def __init__(self, config, params):
|
def __init__(self, config, params):
|
||||||
self.config = config
|
self.config = config
|
||||||
|
|
@ -83,256 +83,127 @@ class Qbt:
|
||||||
self.global_max_seeding_time_enabled = self.client.app.preferences.max_seeding_time_enabled
|
self.global_max_seeding_time_enabled = self.client.app.preferences.max_seeding_time_enabled
|
||||||
self.global_max_seeding_time = self.client.app.preferences.max_seeding_time
|
self.global_max_seeding_time = self.client.app.preferences.max_seeding_time
|
||||||
|
|
||||||
def get_torrent_info(torrent_list):
|
if any(config.commands.get(command, False) for command in self.TORRENT_DICT_COMMANDS):
|
||||||
"""
|
|
||||||
Will create a 2D Dictionary with the torrent name as the key
|
|
||||||
torrentdict = {'TorrentName1' : {'Category':'TV', 'save_path':'/data/torrents/TV', 'count':1, 'msg':'[]'...},
|
|
||||||
'TorrentName2' : {'Category':'Movies', 'save_path':'/data/torrents/Movies'}, 'count':2, 'msg':'[]'...}
|
|
||||||
List of dictionary key definitions
|
|
||||||
Category = Returns category of the torrent (str)
|
|
||||||
save_path = Returns the save path of the torrent (str)
|
|
||||||
count = Returns a count of the total number of torrents with the same name (int)
|
|
||||||
msg = Returns a list of torrent messages by name (list of str)
|
|
||||||
status = Returns the list of status numbers of the torrent by name
|
|
||||||
(0: Tracker is disabled (used for DHT, PeX, and LSD),
|
|
||||||
1: Tracker has not been contacted yet,
|
|
||||||
2: Tracker has been contacted and is working,
|
|
||||||
3: Tracker is updating,
|
|
||||||
4: Tracker has been contacted, but it is not working (or doesn't send proper replies)
|
|
||||||
is_complete = Returns the state of torrent
|
|
||||||
(Returns True if at least one of the torrent with the State is categorized as Complete.)
|
|
||||||
first_hash = Returns the hash number of the original torrent (Assuming the torrent list is sorted by date added (Asc))
|
|
||||||
Takes in a number n, returns the square of n
|
|
||||||
"""
|
|
||||||
torrentdict = {}
|
|
||||||
t_obj_unreg = [] # list of unregistered torrent objects
|
|
||||||
t_obj_valid = [] # list of working torrents
|
|
||||||
t_obj_list = [] # list of all torrent objects
|
|
||||||
settings = self.config.settings
|
|
||||||
logger.separator("Checking Settings", space=False, border=False)
|
|
||||||
if settings["force_auto_tmm"]:
|
|
||||||
logger.print_line(
|
|
||||||
"force_auto_tmm set to True. Will force Auto Torrent Management for all torrents.", self.config.loglevel
|
|
||||||
)
|
|
||||||
logger.separator("Gathering Torrent Information", space=True, border=True)
|
|
||||||
for torrent in torrent_list:
|
|
||||||
is_complete = False
|
|
||||||
msg = None
|
|
||||||
status = None
|
|
||||||
working_tracker = None
|
|
||||||
issue = {"potential": False}
|
|
||||||
if (
|
|
||||||
torrent.auto_tmm is False
|
|
||||||
and settings["force_auto_tmm"]
|
|
||||||
and torrent.category != ""
|
|
||||||
and not self.config.dry_run
|
|
||||||
):
|
|
||||||
torrent.set_auto_management(True)
|
|
||||||
try:
|
|
||||||
torrent_name = torrent.name
|
|
||||||
torrent_hash = torrent.hash
|
|
||||||
torrent_is_complete = torrent.state_enum.is_complete
|
|
||||||
save_path = torrent.save_path
|
|
||||||
category = torrent.category
|
|
||||||
torrent_trackers = torrent.trackers
|
|
||||||
except Exception as ex:
|
|
||||||
self.config.notify(ex, "Get Torrent Info", False)
|
|
||||||
logger.warning(ex)
|
|
||||||
if torrent_name in torrentdict:
|
|
||||||
t_obj_list.append(torrent)
|
|
||||||
t_count = torrentdict[torrent_name]["count"] + 1
|
|
||||||
msg_list = torrentdict[torrent_name]["msg"]
|
|
||||||
status_list = torrentdict[torrent_name]["status"]
|
|
||||||
is_complete = True if torrentdict[torrent_name]["is_complete"] is True else torrent_is_complete
|
|
||||||
first_hash = torrentdict[torrent_name]["first_hash"]
|
|
||||||
else:
|
|
||||||
t_obj_list = [torrent]
|
|
||||||
t_count = 1
|
|
||||||
msg_list = []
|
|
||||||
status_list = []
|
|
||||||
is_complete = torrent_is_complete
|
|
||||||
first_hash = torrent_hash
|
|
||||||
for trk in torrent_trackers:
|
|
||||||
if trk.url.startswith("http"):
|
|
||||||
status = trk.status
|
|
||||||
msg = trk.msg.upper()
|
|
||||||
exception = [
|
|
||||||
"DOWN",
|
|
||||||
"DOWN.",
|
|
||||||
"IT MAY BE DOWN,",
|
|
||||||
"UNREACHABLE",
|
|
||||||
"(UNREACHABLE)",
|
|
||||||
"BAD GATEWAY",
|
|
||||||
"TRACKER UNAVAILABLE",
|
|
||||||
]
|
|
||||||
if trk.status == 2:
|
|
||||||
working_tracker = True
|
|
||||||
break
|
|
||||||
# Add any potential unregistered torrents to a list
|
|
||||||
if trk.status == 4 and not list_in_text(msg, exception):
|
|
||||||
issue["potential"] = True
|
|
||||||
issue["msg"] = msg
|
|
||||||
issue["status"] = status
|
|
||||||
if working_tracker:
|
|
||||||
status = 2
|
|
||||||
msg = ""
|
|
||||||
t_obj_valid.append(torrent)
|
|
||||||
elif issue["potential"]:
|
|
||||||
status = issue["status"]
|
|
||||||
msg = issue["msg"]
|
|
||||||
t_obj_unreg.append(torrent)
|
|
||||||
if msg is not None:
|
|
||||||
msg_list.append(msg)
|
|
||||||
if status is not None:
|
|
||||||
status_list.append(status)
|
|
||||||
torrentattr = {
|
|
||||||
"torrents": t_obj_list,
|
|
||||||
"Category": category,
|
|
||||||
"save_path": save_path,
|
|
||||||
"count": t_count,
|
|
||||||
"msg": msg_list,
|
|
||||||
"status": status_list,
|
|
||||||
"is_complete": is_complete,
|
|
||||||
"first_hash": first_hash,
|
|
||||||
}
|
|
||||||
torrentdict[torrent_name] = torrentattr
|
|
||||||
return torrentdict, t_obj_unreg, t_obj_valid
|
|
||||||
|
|
||||||
self.torrentinfo = None
|
|
||||||
self.torrentissue = None
|
|
||||||
self.torrentvalid = None
|
|
||||||
if (
|
|
||||||
config.commands["recheck"]
|
|
||||||
or config.commands["cross_seed"]
|
|
||||||
or config.commands["rem_unregistered"]
|
|
||||||
or config.commands["tag_tracker_error"]
|
|
||||||
or config.commands["tag_nohardlinks"]
|
|
||||||
):
|
|
||||||
# Get an updated torrent dictionary information of the torrents
|
# Get an updated torrent dictionary information of the torrents
|
||||||
self.torrentinfo, self.torrentissue, self.torrentvalid = get_torrent_info(self.torrent_list)
|
self.get_torrent_info()
|
||||||
|
else:
|
||||||
|
self.torrentinfo = None
|
||||||
|
self.torrentissue = None
|
||||||
|
self.torrentvalid = None
|
||||||
|
|
||||||
|
def get_torrent_info(self):
|
||||||
|
"""
|
||||||
|
Will create a 2D Dictionary with the torrent name as the key
|
||||||
|
self.torrentinfo = {'TorrentName1' : {'Category':'TV', 'save_path':'/data/torrents/TV', 'count':1, 'msg':'[]'...},
|
||||||
|
'TorrentName2' : {'Category':'Movies', 'save_path':'/data/torrents/Movies'}, 'count':2, 'msg':'[]'...}
|
||||||
|
List of dictionary key definitions
|
||||||
|
Category = Returns category of the torrent (str)
|
||||||
|
save_path = Returns the save path of the torrent (str)
|
||||||
|
count = Returns a count of the total number of torrents with the same name (int)
|
||||||
|
msg = Returns a list of torrent messages by name (list of str)
|
||||||
|
status = Returns the list of status numbers of the torrent by name
|
||||||
|
(0: Tracker is disabled (used for DHT, PeX, and LSD),
|
||||||
|
1: Tracker has not been contacted yet,
|
||||||
|
2: Tracker has been contacted and is working,
|
||||||
|
3: Tracker is updating,
|
||||||
|
4: Tracker has been contacted, but it is not working (or doesn't send proper replies)
|
||||||
|
is_complete = Returns the state of torrent
|
||||||
|
(Returns True if at least one of the torrent with the State is categorized as Complete.)
|
||||||
|
first_hash = Returns the hash number of the original torrent (Assuming the torrent list is sorted by date added (Asc))
|
||||||
|
Takes in a number n, returns the square of n
|
||||||
|
"""
|
||||||
|
self.torrentinfo = {}
|
||||||
|
self.torrentissue = [] # list of unregistered torrent objects
|
||||||
|
self.torrentvalid = [] # list of working torrents
|
||||||
|
t_obj_list = [] # list of all torrent objects
|
||||||
|
settings = self.config.settings
|
||||||
|
logger.separator("Checking Settings", space=False, border=False)
|
||||||
|
if settings["force_auto_tmm"]:
|
||||||
|
logger.print_line(
|
||||||
|
"force_auto_tmm set to True. Will force Auto Torrent Management for all torrents.", self.config.loglevel
|
||||||
|
)
|
||||||
|
logger.separator("Gathering Torrent Information", space=True, border=True)
|
||||||
|
for torrent in self.torrent_list:
|
||||||
|
is_complete = False
|
||||||
|
msg = None
|
||||||
|
status = None
|
||||||
|
working_tracker = None
|
||||||
|
issue = {"potential": False}
|
||||||
|
if torrent.auto_tmm is False and settings["force_auto_tmm"] and torrent.category != "" and not self.config.dry_run:
|
||||||
|
torrent.set_auto_management(True)
|
||||||
|
try:
|
||||||
|
torrent_name = torrent.name
|
||||||
|
torrent_hash = torrent.hash
|
||||||
|
torrent_is_complete = torrent.state_enum.is_complete
|
||||||
|
save_path = torrent.save_path
|
||||||
|
category = torrent.category
|
||||||
|
torrent_trackers = torrent.trackers
|
||||||
|
except Exception as ex:
|
||||||
|
self.config.notify(ex, "Get Torrent Info", False)
|
||||||
|
logger.warning(ex)
|
||||||
|
if torrent_name in self.torrentinfo:
|
||||||
|
t_obj_list.append(torrent)
|
||||||
|
t_count = self.torrentinfo[torrent_name]["count"] + 1
|
||||||
|
msg_list = self.torrentinfo[torrent_name]["msg"]
|
||||||
|
status_list = self.torrentinfo[torrent_name]["status"]
|
||||||
|
is_complete = True if self.torrentinfo[torrent_name]["is_complete"] is True else torrent_is_complete
|
||||||
|
first_hash = self.torrentinfo[torrent_name]["first_hash"]
|
||||||
|
else:
|
||||||
|
t_obj_list = [torrent]
|
||||||
|
t_count = 1
|
||||||
|
msg_list = []
|
||||||
|
status_list = []
|
||||||
|
is_complete = torrent_is_complete
|
||||||
|
first_hash = torrent_hash
|
||||||
|
for trk in torrent_trackers:
|
||||||
|
if trk.url.startswith("http"):
|
||||||
|
status = trk.status
|
||||||
|
msg = trk.msg.upper()
|
||||||
|
exception = [
|
||||||
|
"DOWN",
|
||||||
|
"DOWN.",
|
||||||
|
"IT MAY BE DOWN,",
|
||||||
|
"UNREACHABLE",
|
||||||
|
"(UNREACHABLE)",
|
||||||
|
"BAD GATEWAY",
|
||||||
|
"TRACKER UNAVAILABLE",
|
||||||
|
]
|
||||||
|
if trk.status == 2:
|
||||||
|
working_tracker = True
|
||||||
|
break
|
||||||
|
# Add any potential unregistered torrents to a list
|
||||||
|
if trk.status == 4 and not list_in_text(msg, exception):
|
||||||
|
issue["potential"] = True
|
||||||
|
issue["msg"] = msg
|
||||||
|
issue["status"] = status
|
||||||
|
if working_tracker:
|
||||||
|
status = 2
|
||||||
|
msg = ""
|
||||||
|
self.torrentvalid.append(torrent)
|
||||||
|
elif issue["potential"]:
|
||||||
|
status = issue["status"]
|
||||||
|
msg = issue["msg"]
|
||||||
|
self.torrentissue.append(torrent)
|
||||||
|
if msg is not None:
|
||||||
|
msg_list.append(msg)
|
||||||
|
if status is not None:
|
||||||
|
status_list.append(status)
|
||||||
|
torrentattr = {
|
||||||
|
"torrents": t_obj_list,
|
||||||
|
"Category": category,
|
||||||
|
"save_path": save_path,
|
||||||
|
"count": t_count,
|
||||||
|
"msg": msg_list,
|
||||||
|
"status": status_list,
|
||||||
|
"is_complete": is_complete,
|
||||||
|
"first_hash": first_hash,
|
||||||
|
}
|
||||||
|
self.torrentinfo[torrent_name] = torrentattr
|
||||||
|
|
||||||
def get_torrents(self, params):
|
def get_torrents(self, params):
|
||||||
"""Get torrents from qBittorrent"""
|
"""Get torrents from qBittorrent"""
|
||||||
return self.client.torrents.info(**params)
|
return self.client.torrents.info(**params)
|
||||||
|
|
||||||
def category(self):
|
|
||||||
"""Update category for torrents"""
|
|
||||||
num_cat = 0
|
|
||||||
|
|
||||||
def update_cat(new_cat, cat_change):
|
|
||||||
nonlocal torrent, num_cat
|
|
||||||
tracker = self.config.get_tags(torrent.trackers)
|
|
||||||
old_cat = torrent.category
|
|
||||||
if not self.config.dry_run:
|
|
||||||
try:
|
|
||||||
torrent.set_category(category=new_cat)
|
|
||||||
if torrent.auto_tmm is False and self.config.settings["force_auto_tmm"]:
|
|
||||||
torrent.set_auto_management(True)
|
|
||||||
except Conflict409Error:
|
|
||||||
ex = logger.print_line(
|
|
||||||
f'Existing category "{new_cat}" not found for save path {torrent.save_path}, category will be created.',
|
|
||||||
self.config.loglevel,
|
|
||||||
)
|
|
||||||
self.config.notify(ex, "Update Category", False)
|
|
||||||
self.client.torrent_categories.create_category(name=new_cat, save_path=torrent.save_path)
|
|
||||||
torrent.set_category(category=new_cat)
|
|
||||||
body = []
|
|
||||||
body += logger.print_line(logger.insert_space(f"Torrent Name: {torrent.name}", 3), self.config.loglevel)
|
|
||||||
if cat_change:
|
|
||||||
body += logger.print_line(logger.insert_space(f"Old Category: {old_cat}", 3), self.config.loglevel)
|
|
||||||
title = "Moving Categories"
|
|
||||||
else:
|
|
||||||
title = "Updating Categories"
|
|
||||||
body += logger.print_line(logger.insert_space(f"New Category: {new_cat}", 3), self.config.loglevel)
|
|
||||||
body += logger.print_line(logger.insert_space(f'Tracker: {tracker["url"]}', 8), self.config.loglevel)
|
|
||||||
attr = {
|
|
||||||
"function": "cat_update",
|
|
||||||
"title": title,
|
|
||||||
"body": "\n".join(body),
|
|
||||||
"torrent_name": torrent.name,
|
|
||||||
"torrent_category": new_cat,
|
|
||||||
"torrent_tracker": tracker["url"],
|
|
||||||
"notifiarr_indexer": tracker["notifiarr"],
|
|
||||||
}
|
|
||||||
self.config.send_notifications(attr)
|
|
||||||
num_cat += 1
|
|
||||||
|
|
||||||
if self.config.commands["cat_update"]:
|
|
||||||
logger.separator("Updating Categories", space=False, border=False)
|
|
||||||
torrent_list = self.get_torrents({"category": "", "status_filter": "completed"})
|
|
||||||
for torrent in torrent_list:
|
|
||||||
new_cat = self.config.get_category(torrent.save_path)
|
|
||||||
update_cat(new_cat, False)
|
|
||||||
|
|
||||||
# Change categories
|
|
||||||
if self.config.cat_change:
|
|
||||||
for old_cat in self.config.cat_change:
|
|
||||||
torrent_list = self.get_torrents({"category": old_cat, "status_filter": "completed"})
|
|
||||||
for torrent in torrent_list:
|
|
||||||
new_cat = self.config.cat_change[old_cat]
|
|
||||||
update_cat(new_cat, True)
|
|
||||||
|
|
||||||
if num_cat >= 1:
|
|
||||||
logger.print_line(
|
|
||||||
f"{'Did not update' if self.config.dry_run else 'Updated'} {num_cat} new categories.", self.config.loglevel
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
logger.print_line("No new torrents to categorize.", self.config.loglevel)
|
|
||||||
return num_cat
|
|
||||||
|
|
||||||
def tags(self):
|
|
||||||
"""Update tags for torrents"""
|
|
||||||
num_tags = 0
|
|
||||||
ignore_tags = self.config.settings["ignoreTags_OnUpdate"]
|
|
||||||
if self.config.commands["tag_update"]:
|
|
||||||
logger.separator("Updating Tags", space=False, border=False)
|
|
||||||
for torrent in self.torrent_list:
|
|
||||||
check_tags = util.get_list(torrent.tags)
|
|
||||||
if torrent.tags == "" or (len([trk for trk in check_tags if trk not in ignore_tags]) == 0):
|
|
||||||
tracker = self.config.get_tags(torrent.trackers)
|
|
||||||
if tracker["tag"]:
|
|
||||||
num_tags += len(tracker["tag"])
|
|
||||||
body = []
|
|
||||||
body += logger.print_line(logger.insert_space(f"Torrent Name: {torrent.name}", 3), self.config.loglevel)
|
|
||||||
body += logger.print_line(
|
|
||||||
logger.insert_space(
|
|
||||||
f'New Tag{"s" if len(tracker["tag"]) > 1 else ""}: {", ".join(tracker["tag"])}', 8
|
|
||||||
),
|
|
||||||
self.config.loglevel,
|
|
||||||
)
|
|
||||||
body += logger.print_line(logger.insert_space(f'Tracker: {tracker["url"]}', 8), self.config.loglevel)
|
|
||||||
body.extend(
|
|
||||||
self.set_tags_and_limits(
|
|
||||||
torrent,
|
|
||||||
tracker["max_ratio"],
|
|
||||||
tracker["max_seeding_time"],
|
|
||||||
tracker["limit_upload_speed"],
|
|
||||||
tracker["tag"],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
category = self.config.get_category(torrent.save_path) if torrent.category == "" else torrent.category
|
|
||||||
attr = {
|
|
||||||
"function": "tag_update",
|
|
||||||
"title": "Updating Tags",
|
|
||||||
"body": "\n".join(body),
|
|
||||||
"torrent_name": torrent.name,
|
|
||||||
"torrent_category": category,
|
|
||||||
"torrent_tag": ", ".join(tracker["tag"]),
|
|
||||||
"torrent_tracker": tracker["url"],
|
|
||||||
"notifiarr_indexer": tracker["notifiarr"],
|
|
||||||
"torrent_max_ratio": tracker["max_ratio"],
|
|
||||||
"torrent_max_seeding_time": tracker["max_seeding_time"],
|
|
||||||
"torrent_limit_upload_speed": tracker["limit_upload_speed"],
|
|
||||||
}
|
|
||||||
self.config.send_notifications(attr)
|
|
||||||
if num_tags >= 1:
|
|
||||||
logger.print_line(
|
|
||||||
f"{'Did not update' if self.config.dry_run else 'Updated'} {num_tags} new tags.", self.config.loglevel
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
logger.print_line("No new torrents to tag.", self.config.loglevel)
|
|
||||||
return num_tags
|
|
||||||
|
|
||||||
def set_tags_and_limits(
|
def set_tags_and_limits(
|
||||||
self, torrent, max_ratio, max_seeding_time, limit_upload_speed=None, tags=None, restore=False, do_print=True
|
self, torrent, max_ratio, max_seeding_time, limit_upload_speed=None, tags=None, restore=False, do_print=True
|
||||||
):
|
):
|
||||||
|
|
@ -460,6 +331,240 @@ class Qbt:
|
||||||
return body
|
return body
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def get_tags(self, trackers):
|
||||||
|
"""Get tags from config file based on keyword"""
|
||||||
|
urls = [x.url for x in trackers if x.url.startswith("http")]
|
||||||
|
tracker = {}
|
||||||
|
tracker["tag"] = None
|
||||||
|
tracker["max_ratio"] = None
|
||||||
|
tracker["min_seeding_time"] = None
|
||||||
|
tracker["max_seeding_time"] = None
|
||||||
|
tracker["limit_upload_speed"] = None
|
||||||
|
tracker["notifiarr"] = None
|
||||||
|
tracker["url"] = None
|
||||||
|
tracker_other_tag = self.config.util.check_for_attribute(
|
||||||
|
self.config.data, "tag", parent="tracker", subparent="other", default_is_none=True, var_type="list", save=False
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
tracker["url"] = util.trunc_val(urls[0], os.sep)
|
||||||
|
except IndexError as e:
|
||||||
|
tracker["url"] = None
|
||||||
|
if not urls:
|
||||||
|
urls = []
|
||||||
|
if not tracker_other_tag:
|
||||||
|
tracker_other_tag = ["other"]
|
||||||
|
tracker["url"] = "No http URL found"
|
||||||
|
else:
|
||||||
|
logger.debug(f"Tracker Url:{urls}")
|
||||||
|
logger.debug(e)
|
||||||
|
if "tracker" in self.config.data and self.config.data["tracker"] is not None:
|
||||||
|
tag_values = self.config.data["tracker"]
|
||||||
|
for tag_url, tag_details in tag_values.items():
|
||||||
|
for url in urls:
|
||||||
|
if tag_url in url:
|
||||||
|
if tracker["url"] is None:
|
||||||
|
default_tag = tracker_other_tag
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
tracker["url"] = util.trunc_val(url, os.sep)
|
||||||
|
default_tag = tracker["url"].split(os.sep)[2].split(":")[0]
|
||||||
|
except IndexError as e:
|
||||||
|
logger.debug(f"Tracker Url:{url}")
|
||||||
|
logger.debug(e)
|
||||||
|
# Tracker Format 1 deprecated.
|
||||||
|
if isinstance(tag_details, str):
|
||||||
|
e = (
|
||||||
|
"Config Error: Tracker format invalid. Please see config.yml.sample for correct format and fix "
|
||||||
|
f"`{tag_details}` in the Tracker section of the config."
|
||||||
|
)
|
||||||
|
self.config.notify(e, "Config")
|
||||||
|
raise Failed(e)
|
||||||
|
# Using new Format
|
||||||
|
else:
|
||||||
|
tracker["tag"] = self.config.util.check_for_attribute(
|
||||||
|
self.config.data, "tag", parent="tracker", subparent=tag_url, default=tag_url, var_type="list"
|
||||||
|
)
|
||||||
|
if tracker["tag"] == [tag_url]:
|
||||||
|
self.config.data["tracker"][tag_url]["tag"] = [tag_url]
|
||||||
|
if isinstance(tracker["tag"], str):
|
||||||
|
tracker["tag"] = [tracker["tag"]]
|
||||||
|
is_max_ratio_defined = self.config.data["tracker"].get("max_ratio")
|
||||||
|
is_max_seeding_time_defined = self.config.data["tracker"].get("max_seeding_time")
|
||||||
|
if is_max_ratio_defined or is_max_seeding_time_defined:
|
||||||
|
tracker["max_ratio"] = self.config.util.check_for_attribute(
|
||||||
|
self.config.data,
|
||||||
|
"max_ratio",
|
||||||
|
parent="tracker",
|
||||||
|
subparent=tag_url,
|
||||||
|
var_type="float",
|
||||||
|
min_int=-2,
|
||||||
|
do_print=False,
|
||||||
|
default=-1,
|
||||||
|
save=False,
|
||||||
|
)
|
||||||
|
tracker["max_seeding_time"] = self.config.util.check_for_attribute(
|
||||||
|
self.config.data,
|
||||||
|
"max_seeding_time",
|
||||||
|
parent="tracker",
|
||||||
|
subparent=tag_url,
|
||||||
|
var_type="int",
|
||||||
|
min_int=-2,
|
||||||
|
do_print=False,
|
||||||
|
default=-1,
|
||||||
|
save=False,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
tracker["max_ratio"] = self.config.util.check_for_attribute(
|
||||||
|
self.config.data,
|
||||||
|
"max_ratio",
|
||||||
|
parent="tracker",
|
||||||
|
subparent=tag_url,
|
||||||
|
var_type="float",
|
||||||
|
min_int=-2,
|
||||||
|
do_print=False,
|
||||||
|
default_is_none=True,
|
||||||
|
save=False,
|
||||||
|
)
|
||||||
|
tracker["max_seeding_time"] = self.config.util.check_for_attribute(
|
||||||
|
self.config.data,
|
||||||
|
"max_seeding_time",
|
||||||
|
parent="tracker",
|
||||||
|
subparent=tag_url,
|
||||||
|
var_type="int",
|
||||||
|
min_int=-2,
|
||||||
|
do_print=False,
|
||||||
|
default_is_none=True,
|
||||||
|
save=False,
|
||||||
|
)
|
||||||
|
tracker["min_seeding_time"] = self.config.util.check_for_attribute(
|
||||||
|
self.config.data,
|
||||||
|
"min_seeding_time",
|
||||||
|
parent="tracker",
|
||||||
|
subparent=tag_url,
|
||||||
|
var_type="int",
|
||||||
|
min_int=0,
|
||||||
|
do_print=False,
|
||||||
|
default=0,
|
||||||
|
save=False,
|
||||||
|
)
|
||||||
|
tracker["limit_upload_speed"] = self.config.util.check_for_attribute(
|
||||||
|
self.config.data,
|
||||||
|
"limit_upload_speed",
|
||||||
|
parent="tracker",
|
||||||
|
subparent=tag_url,
|
||||||
|
var_type="int",
|
||||||
|
min_int=-1,
|
||||||
|
do_print=False,
|
||||||
|
default=0,
|
||||||
|
save=False,
|
||||||
|
)
|
||||||
|
tracker["notifiarr"] = self.config.util.check_for_attribute(
|
||||||
|
self.config.data,
|
||||||
|
"notifiarr",
|
||||||
|
parent="tracker",
|
||||||
|
subparent=tag_url,
|
||||||
|
default_is_none=True,
|
||||||
|
do_print=False,
|
||||||
|
save=False,
|
||||||
|
)
|
||||||
|
return tracker
|
||||||
|
if tracker_other_tag:
|
||||||
|
tracker["tag"] = tracker_other_tag
|
||||||
|
tracker["max_ratio"] = self.config.util.check_for_attribute(
|
||||||
|
self.config.data,
|
||||||
|
"max_ratio",
|
||||||
|
parent="tracker",
|
||||||
|
subparent="other",
|
||||||
|
var_type="float",
|
||||||
|
min_int=-2,
|
||||||
|
do_print=False,
|
||||||
|
default=-1,
|
||||||
|
save=False,
|
||||||
|
)
|
||||||
|
tracker["min_seeding_time"] = self.config.util.check_for_attribute(
|
||||||
|
self.config.data,
|
||||||
|
"min_seeding_time",
|
||||||
|
parent="tracker",
|
||||||
|
subparent="other",
|
||||||
|
var_type="int",
|
||||||
|
min_int=0,
|
||||||
|
do_print=False,
|
||||||
|
default=-1,
|
||||||
|
save=False,
|
||||||
|
)
|
||||||
|
tracker["max_seeding_time"] = self.config.util.check_for_attribute(
|
||||||
|
self.config.data,
|
||||||
|
"max_seeding_time",
|
||||||
|
parent="tracker",
|
||||||
|
subparent="other",
|
||||||
|
var_type="int",
|
||||||
|
min_int=-2,
|
||||||
|
do_print=False,
|
||||||
|
default=-1,
|
||||||
|
save=False,
|
||||||
|
)
|
||||||
|
tracker["limit_upload_speed"] = self.config.util.check_for_attribute(
|
||||||
|
self.config.data,
|
||||||
|
"limit_upload_speed",
|
||||||
|
parent="tracker",
|
||||||
|
subparent="other",
|
||||||
|
var_type="int",
|
||||||
|
min_int=-1,
|
||||||
|
do_print=False,
|
||||||
|
default=0,
|
||||||
|
save=False,
|
||||||
|
)
|
||||||
|
tracker["notifiarr"] = self.config.util.check_for_attribute(
|
||||||
|
self.config.data,
|
||||||
|
"notifiarr",
|
||||||
|
parent="tracker",
|
||||||
|
subparent="other",
|
||||||
|
default_is_none=True,
|
||||||
|
do_print=False,
|
||||||
|
save=False,
|
||||||
|
)
|
||||||
|
return tracker
|
||||||
|
if tracker["url"]:
|
||||||
|
logger.trace(f"tracker url: {tracker['url']}")
|
||||||
|
if tracker_other_tag:
|
||||||
|
default_tag = tracker_other_tag
|
||||||
|
else:
|
||||||
|
default_tag = tracker["url"].split(os.sep)[2].split(":")[0]
|
||||||
|
tracker["tag"] = self.config.util.check_for_attribute(
|
||||||
|
self.config.data, "tag", parent="tracker", subparent=default_tag, default=default_tag, var_type="list"
|
||||||
|
)
|
||||||
|
if isinstance(tracker["tag"], str):
|
||||||
|
tracker["tag"] = [tracker["tag"]]
|
||||||
|
try:
|
||||||
|
self.config.data["tracker"][default_tag]["tag"] = [default_tag]
|
||||||
|
except Exception:
|
||||||
|
self.config.data["tracker"][default_tag] = {"tag": [default_tag]}
|
||||||
|
e = f'No tags matched for {tracker["url"]}. Please check your config.yml file. Setting tag to {default_tag}'
|
||||||
|
self.config.notify(e, "Tag", False)
|
||||||
|
logger.warning(e)
|
||||||
|
return tracker
|
||||||
|
|
||||||
|
def get_category(self, path):
|
||||||
|
"""Get category from config file based on path provided"""
|
||||||
|
category = ""
|
||||||
|
path = os.path.join(path, "")
|
||||||
|
if "cat" in self.config.data and self.config.data["cat"] is not None:
|
||||||
|
cat_path = self.config.data["cat"]
|
||||||
|
for cat, save_path in cat_path.items():
|
||||||
|
if os.path.join(save_path, "") == path:
|
||||||
|
category = cat
|
||||||
|
break
|
||||||
|
|
||||||
|
if not category:
|
||||||
|
default_cat = path.split(os.sep)[-2]
|
||||||
|
category = str(default_cat)
|
||||||
|
self.config.util.check_for_attribute(self.config.data, default_cat, parent="cat", default=path)
|
||||||
|
self.config.data["cat"][str(default_cat)] = path
|
||||||
|
e = f"No categories matched for the save path {path}. Check your config.yml file. - Setting category to {default_cat}"
|
||||||
|
self.config.notify(e, "Category", False)
|
||||||
|
logger.warning(e)
|
||||||
|
return category
|
||||||
|
|
||||||
def tag_nohardlinks(self):
|
def tag_nohardlinks(self):
|
||||||
"""Tag torrents with no hardlinks"""
|
"""Tag torrents with no hardlinks"""
|
||||||
num_tags = 0 # counter for the number of torrents that has no hardlinks
|
num_tags = 0 # counter for the number of torrents that has no hardlinks
|
||||||
|
|
@ -530,7 +635,7 @@ class Qbt:
|
||||||
logger.warning(ex)
|
logger.warning(ex)
|
||||||
continue
|
continue
|
||||||
for torrent in torrent_list:
|
for torrent in torrent_list:
|
||||||
tracker = self.config.get_tags(torrent.trackers)
|
tracker = self.get_tags(torrent.trackers)
|
||||||
has_nohardlinks = util.nohardlink(torrent["content_path"].replace(root_dir, remote_dir), self.config.notify)
|
has_nohardlinks = util.nohardlink(torrent["content_path"].replace(root_dir, remote_dir), self.config.notify)
|
||||||
if any(tag in torrent.tags for tag in nohardlinks[category]["exclude_tags"]):
|
if any(tag in torrent.tags for tag in nohardlinks[category]["exclude_tags"]):
|
||||||
# Skip to the next torrent if we find any torrents that are in the exclude tag
|
# Skip to the next torrent if we find any torrents that are in the exclude tag
|
||||||
|
|
@ -539,7 +644,7 @@ class Qbt:
|
||||||
# Checks for any hardlinks and not already tagged
|
# Checks for any hardlinks and not already tagged
|
||||||
# Cleans up previously tagged nohardlinks_tag torrents that no longer have hardlinks
|
# Cleans up previously tagged nohardlinks_tag torrents that no longer have hardlinks
|
||||||
if has_nohardlinks:
|
if has_nohardlinks:
|
||||||
tracker = self.config.get_tags(torrent.trackers)
|
tracker = self.get_tags(torrent.trackers)
|
||||||
# Determine min_seeding_time.
|
# Determine min_seeding_time.
|
||||||
# If only tracker setting is set, use tracker's min_seeding_time
|
# If only tracker setting is set, use tracker's min_seeding_time
|
||||||
# If only nohardlinks category setting is set, use nohardlinks category's min_seeding_time
|
# If only nohardlinks category setting is set, use nohardlinks category's min_seeding_time
|
||||||
|
|
@ -666,7 +771,7 @@ class Qbt:
|
||||||
t_status = self.torrentinfo[t_name]["status"]
|
t_status = self.torrentinfo[t_name]["status"]
|
||||||
# Double check that the content path is the same before we delete anything
|
# Double check that the content path is the same before we delete anything
|
||||||
if torrent["content_path"].replace(root_dir, remote_dir) == tdel_dict[t_hash]["content_path"]:
|
if torrent["content_path"].replace(root_dir, remote_dir) == tdel_dict[t_hash]["content_path"]:
|
||||||
tracker = self.config.get_tags(torrent.trackers)
|
tracker = self.get_tags(torrent.trackers)
|
||||||
body = []
|
body = []
|
||||||
body += logger.print_line(logger.insert_space(f"Torrent Name: {t_name}", 3), self.config.loglevel)
|
body += logger.print_line(logger.insert_space(f"Torrent Name: {t_name}", 3), self.config.loglevel)
|
||||||
body += logger.print_line(
|
body += logger.print_line(
|
||||||
|
|
@ -852,7 +957,7 @@ class Qbt:
|
||||||
check_tags = util.get_list(torrent.tags)
|
check_tags = util.get_list(torrent.tags)
|
||||||
# Remove any error torrents Tags that are no longer unreachable.
|
# Remove any error torrents Tags that are no longer unreachable.
|
||||||
if tag_error in check_tags:
|
if tag_error in check_tags:
|
||||||
tracker = self.config.get_tags(torrent.trackers)
|
tracker = self.get_tags(torrent.trackers)
|
||||||
num_untag += 1
|
num_untag += 1
|
||||||
body = []
|
body = []
|
||||||
body += logger.print_line(
|
body += logger.print_line(
|
||||||
|
|
@ -884,7 +989,7 @@ class Qbt:
|
||||||
try:
|
try:
|
||||||
for trk in torrent.trackers:
|
for trk in torrent.trackers:
|
||||||
if trk.url.startswith("http"):
|
if trk.url.startswith("http"):
|
||||||
tracker = self.config.get_tags([trk])
|
tracker = self.get_tags([trk])
|
||||||
msg_up = trk.msg.upper()
|
msg_up = trk.msg.upper()
|
||||||
msg = trk.msg
|
msg = trk.msg
|
||||||
# Tag any error torrents
|
# Tag any error torrents
|
||||||
|
|
@ -964,16 +1069,16 @@ class Qbt:
|
||||||
for file in cs_files:
|
for file in cs_files:
|
||||||
tr_name = file.split("]", 2)[2].split(".torrent")[0]
|
tr_name = file.split("]", 2)[2].split(".torrent")[0]
|
||||||
t_tracker = file.split("]", 2)[1][1:]
|
t_tracker = file.split("]", 2)[1][1:]
|
||||||
# Substring Key match in dictionary (used because t_name might not match exactly with torrentdict key)
|
# Substring Key match in dictionary (used because t_name might not match exactly with self.torrentinfo key)
|
||||||
# Returned the dictionary of filtered item
|
# Returned the dictionary of filtered item
|
||||||
torrentdict_file = dict(filter(lambda item: tr_name in item[0], self.torrentinfo.items()))
|
torrentdict_file = dict(filter(lambda item: tr_name in item[0], self.torrentinfo.items()))
|
||||||
if torrentdict_file:
|
if torrentdict_file:
|
||||||
# Get the exact torrent match name from torrentdict
|
# Get the exact torrent match name from self.torrentinfo
|
||||||
t_name = next(iter(torrentdict_file))
|
t_name = next(iter(torrentdict_file))
|
||||||
dest = os.path.join(self.torrentinfo[t_name]["save_path"], "")
|
dest = os.path.join(self.torrentinfo[t_name]["save_path"], "")
|
||||||
src = os.path.join(dir_cs, file)
|
src = os.path.join(dir_cs, file)
|
||||||
dir_cs_out = os.path.join(dir_cs, "qbit_manage_added", file)
|
dir_cs_out = os.path.join(dir_cs, "qbit_manage_added", file)
|
||||||
category = self.config.get_category(dest)
|
category = self.get_category(dest)
|
||||||
# Only add cross-seed torrent if original torrent is complete
|
# Only add cross-seed torrent if original torrent is complete
|
||||||
if self.torrentinfo[t_name]["is_complete"]:
|
if self.torrentinfo[t_name]["is_complete"]:
|
||||||
categories.append(category)
|
categories.append(category)
|
||||||
|
|
@ -1023,7 +1128,7 @@ class Qbt:
|
||||||
and self.torrentinfo[t_name]["count"] > 1
|
and self.torrentinfo[t_name]["count"] > 1
|
||||||
and self.torrentinfo[t_name]["first_hash"] != torrent.hash
|
and self.torrentinfo[t_name]["first_hash"] != torrent.hash
|
||||||
):
|
):
|
||||||
tracker = self.config.get_tags(torrent.trackers)
|
tracker = self.get_tags(torrent.trackers)
|
||||||
tagged += 1
|
tagged += 1
|
||||||
body = logger.print_line(
|
body = logger.print_line(
|
||||||
f"{'Not Adding' if self.config.dry_run else 'Adding'} 'cross-seed' tag to {t_name}", self.config.loglevel
|
f"{'Not Adding' if self.config.dry_run else 'Adding'} 'cross-seed' tag to {t_name}", self.config.loglevel
|
||||||
|
|
@ -1069,7 +1174,7 @@ class Qbt:
|
||||||
torrent_list = self.get_torrents({"status_filter": "paused", "sort": "size"})
|
torrent_list = self.get_torrents({"status_filter": "paused", "sort": "size"})
|
||||||
if torrent_list:
|
if torrent_list:
|
||||||
for torrent in torrent_list:
|
for torrent in torrent_list:
|
||||||
tracker = self.config.get_tags(torrent.trackers)
|
tracker = self.get_tags(torrent.trackers)
|
||||||
# Resume torrent if completed
|
# Resume torrent if completed
|
||||||
if torrent.progress == 1:
|
if torrent.progress == 1:
|
||||||
if torrent.max_ratio < 0 and torrent.max_seeding_time < 0:
|
if torrent.max_ratio < 0 and torrent.max_seeding_time < 0:
|
||||||
|
|
|
||||||
|
|
@ -287,6 +287,8 @@ util.logger = logger
|
||||||
from modules.config import Config # noqa
|
from modules.config import Config # noqa
|
||||||
from modules.util import GracefulKiller # noqa
|
from modules.util import GracefulKiller # noqa
|
||||||
from modules.util import Failed # noqa
|
from modules.util import Failed # noqa
|
||||||
|
from modules.core.category import Category # noqa
|
||||||
|
from modules.core.tags import Tags # noqa
|
||||||
|
|
||||||
|
|
||||||
def my_except_hook(exctype, value, tbi):
|
def my_except_hook(exctype, value, tbi):
|
||||||
|
|
@ -369,6 +371,8 @@ def start():
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cfg = Config(default_dir, args)
|
cfg = Config(default_dir, args)
|
||||||
|
qbit_manager = cfg.qbt
|
||||||
|
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
if "Qbittorrent Error" in ex.args[0]:
|
if "Qbittorrent Error" in ex.args[0]:
|
||||||
logger.print_line(ex, "CRITICAL")
|
logger.print_line(ex, "CRITICAL")
|
||||||
|
|
@ -379,14 +383,14 @@ def start():
|
||||||
logger.stacktrace()
|
logger.stacktrace()
|
||||||
logger.print_line(ex, "CRITICAL")
|
logger.print_line(ex, "CRITICAL")
|
||||||
|
|
||||||
if cfg:
|
if qbit_manager:
|
||||||
# Set Category
|
# Set Category
|
||||||
num_categorized = cfg.qbt.category()
|
if cfg.commands["cat_update"]:
|
||||||
stats["categorized"] += num_categorized
|
stats["categorized"] += Category(qbit_manager).stats
|
||||||
|
|
||||||
# Set Tags
|
# Set Tags
|
||||||
num_tagged = cfg.qbt.tags()
|
if cfg.commands["tag_update"]:
|
||||||
stats["tagged"] += num_tagged
|
stats["tagged"] += Tags(qbit_manager).stats
|
||||||
|
|
||||||
# Remove Unregistered Torrents
|
# Remove Unregistered Torrents
|
||||||
num_deleted, num_deleted_contents, num_tagged, num_untagged = cfg.qbt.rem_unregistered()
|
num_deleted, num_deleted_contents, num_tagged, num_untagged = cfg.qbt.rem_unregistered()
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue