From 5935b5448d8eb9810ab97b9bff9e7aeca6254d82 Mon Sep 17 00:00:00 2001 From: bobokun Date: Mon, 10 Apr 2023 14:55:14 -0400 Subject: [PATCH] refactor remove_unregistered --- modules/core/category.py | 2 - modules/core/remove_unregistered.py | 232 ++++++++++++++++++++++++++++ modules/core/tags.py | 1 - modules/qbittorrent.py | 202 ------------------------ qbit_manage.py | 18 ++- 5 files changed, 242 insertions(+), 213 deletions(-) create mode 100644 modules/core/remove_unregistered.py diff --git a/modules/core/category.py b/modules/core/category.py index e57ecce..6cfcbec 100644 --- a/modules/core/category.py +++ b/modules/core/category.py @@ -16,8 +16,6 @@ class 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: diff --git a/modules/core/remove_unregistered.py b/modules/core/remove_unregistered.py new file mode 100644 index 0000000..780a728 --- /dev/null +++ b/modules/core/remove_unregistered.py @@ -0,0 +1,232 @@ +from qbittorrentapi import NotFound404Error + +from modules import util +from modules.util import list_in_text + +logger = util.logger + + +class RemoveUnregistered: + UNREGISTERED_MSGS = [ + "UNREGISTERED", + "TORRENT NOT FOUND", + "TORRENT IS NOT FOUND", + "NOT REGISTERED", + "NOT EXIST", + "UNKNOWN TORRENT", + "TRUMP", + "RETITLED", + "TRUNCATED", + "TORRENT IS NOT AUTHORIZED FOR USE ON THIS TRACKER", + ] + + IGNORE_MSGS = [ + "YOU HAVE REACHED THE CLIENT LIMIT FOR THIS TORRENT", + "MISSING PASSKEY", + "MISSING INFO_HASH", + "PASSKEY IS INVALID", + "INVALID PASSKEY", + "EXPECTED VALUE (LIST, DICT, INT OR STRING) IN BENCODED STRING", + "COULD NOT PARSE BENCODED DATA", + "STREAM TRUNCATED", + ] + + # Tracker has been contacted, but it is not working (or doesn't send proper replies) + TORRENT_STATUS_NOT_WORKING = 4 + + def __init__(self, qbit_manager): + self.qbt = qbit_manager + self.config = qbit_manager.config + self.client = qbit_manager.client + self.stats_deleted = 0 + self.stats_deleted_contents = 0 + self.stats_tagged = 0 + self.stats_untagged = 0 + self.tor_error_summary = "" + self.tag_error = self.config.tracker_error_tag + self.cfg_rem_unregistered = self.config.commands["rem_unregistered"] + self.cfg_tag_error = self.config.commands["tag_tracker_error"] + + if self.cfg_tag_error: + logger.separator("Tagging Torrents with Tracker Errors", space=False, border=False) + elif self.cfg_rem_unregistered: + logger.separator("Removing Unregistered Torrents", space=False, border=False) + + self.rem_unregistered() + + def remove_previous_errors(self): + """Removes any previous torrents that were tagged as an error but are now working.""" + for torrent in self.qbt.torrentvalid: + check_tags = util.get_list(torrent.tags) + # Remove any error torrents Tags that are no longer unreachable. + if self.tag_error in check_tags: + tracker = self.get_tags(torrent.trackers) + self.stats_untagged += 1 + body = [] + body += logger.print_line( + f"Previous Tagged {self.tag_error} torrent currently has a working tracker.", self.config.loglevel + ) + body += logger.print_line(logger.insert_space(f"Torrent Name: {torrent.name}", 3), self.config.loglevel) + body += logger.print_line(logger.insert_space(f"Removed Tag: {self.tag_error}", 4), self.config.loglevel) + body += logger.print_line(logger.insert_space(f'Tracker: {tracker["url"]}', 8), self.config.loglevel) + if not self.config.dry_run: + torrent.remove_tags(tags=self.tag_error) + attr = { + "function": "untag_tracker_error", + "title": "Untagging Tracker Error Torrent", + "body": "\n".join(body), + "torrent_name": torrent.name, + "torrent_category": torrent.category, + "torrent_tag": self.tag_error, + "torrent_tracker": tracker["url"], + "notifiarr_indexer": tracker["notifiarr"], + } + self.config.send_notifications(attr) + + def process_torrent_issues(self): + for torrent in self.qbt.torrentissue: + self.t_name = torrent.name + self.t_cat = self.qbt.torrentinfo[self.t_name]["Category"] + self.t_msg = self.qbt.torrentinfo[self.t_name]["msg"] + self.t_status = self.qbt.torrentinfo[self.t_name]["status"] + check_tags = util.get_list(torrent.tags) + try: + for trk in torrent.trackers: + if trk.url.startswith("http"): + tracker = self.qbt.get_tags([trk]) + msg_up = trk.msg.upper() + msg = trk.msg + # Tag any error torrents + if self.cfg_tag_error: + if trk.status == self.TORRENT_STATUS_NOT_WORKING and self.tag_error not in check_tags: + self.tag_tracker_error() + if self.cfg_rem_unregistered: + # Tag any error torrents that are not unregistered + if ( + not list_in_text(msg_up, self.UNREGISTERED_MSGS) + and trk.status == self.TORRENT_STATUS_NOT_WORKING + and self.tag_error not in check_tags + ): + # Check for unregistered torrents using BHD API if the tracker is BHD + if ( + "tracker.beyond-hd.me" in tracker["url"] + and self.config.beyond_hd is not None + and not list_in_text(msg_up, self.IGNORE_MSGS) + ): + json = {"info_hash": torrent.hash} + response = self.config.beyond_hd.search(json) + if response["total_results"] == 0: + self.del_unregistered(msg, tracker, torrent) + break + self.tag_tracker_error() + if ( + list_in_text(msg_up, self.UNREGISTERED_MSGS) + and not list_in_text(msg_up, self.IGNORE_MSGS) + and trk.status == self.TORRENT_STATUS_NOT_WORKING + ): + self.del_unregistered() + break + except NotFound404Error: + continue + except Exception as ex: + logger.stacktrace() + self.config.notify(ex, "Remove Unregistered Torrents", False) + logger.error(f"Remove Unregistered Torrents Error: {ex}") + + def rem_unregistered(self): + """Remove torrents with unregistered trackers.""" + self.remove_previous_errors() + self.process_torrent_issues() + if self.cfg_rem_unregistered: + if self.stats_deleted >= 1 or self.stats_deleted_contents >= 1: + if self.stats_deleted >= 1: + logger.print_line( + f"{'Did not delete' if self.config.dry_run else 'Deleted'} {self.stats_deleted} " + f".torrent{'s' if self.stats_deleted > 1 else ''} but not content files.", + self.config.loglevel, + ) + if self.stats_deleted_contents >= 1: + logger.print_line( + f"{'Did not delete' if self.config.dry_run else 'Deleted'} {self.stats_deleted_contents} " + f".torrent{'s' if self.stats_deleted_contents > 1 else ''} AND content files.", + self.config.loglevel, + ) + else: + logger.print_line("No unregistered torrents found.", self.config.loglevel) + if self.stats_untagged >= 1: + logger.print_line( + f"{'Did not delete' if self.config.dry_run else 'Deleted'} {self.tag_error} tags for {self.stats_untagged} " + f".torrent{'s.' if self.stats_untagged > 1 else '.'}", + self.config.loglevel, + ) + if self.stats_tagged >= 1: + logger.separator( + f"{self.stats_tagged} Torrents with tracker errors found", + space=False, + border=False, + loglevel=self.config.loglevel, + ) + logger.print_line(self.tor_error_summary.rstrip(), self.config.loglevel) + + def tag_tracker_error(self, msg, tracker, torrent): + """Tags any trackers with errors""" + tor_error = "" + tor_error += logger.insert_space(f"Torrent Name: {self.t_name}", 3) + "\n" + tor_error += logger.insert_space(f"Status: {msg}", 9) + "\n" + tor_error += logger.insert_space(f'Tracker: {tracker["url"]}', 8) + "\n" + tor_error += logger.insert_space(f"Added Tag: {self.tag_error}", 6) + "\n" + self.tor_error_summary += tor_error + self.stats_tagged += 1 + attr = { + "function": "tag_tracker_error", + "title": "Tag Tracker Error Torrents", + "body": tor_error, + "torrent_name": self.t_name, + "torrent_category": self.t_cat, + "torrent_tag": self.tag_error, + "torrent_status": msg, + "torrent_tracker": tracker["url"], + "notifiarr_indexer": tracker["notifiarr"], + } + self.config.send_notifications(attr) + if not self.config.dry_run: + torrent.add_tags(tags=self.tag_error) + + def del_unregistered(self, msg, tracker, torrent): + """Deletes unregistered torrents""" + body = [] + body += logger.print_line(logger.insert_space(f"Torrent Name: {self.t_name}", 3), self.config.loglevel) + body += logger.print_line(logger.insert_space(f"Status: {msg}", 9), self.config.loglevel) + body += logger.print_line(logger.insert_space(f'Tracker: {tracker["url"]}', 8), self.config.loglevel) + attr = { + "function": "rem_unregistered", + "title": "Removing Unregistered Torrents", + "torrent_name": self.t_name, + "torrent_category": self.t_cat, + "torrent_status": msg, + "torrent_tracker": tracker["url"], + "notifiarr_indexer": tracker["notifiarr"], + } + if self.qbt.torrentinfo[self.t_name]["count"] > 1: + # Checks if any of the original torrents are working + if "" in self.t_msg or 2 in self.t_status: + attr["torrents_deleted_and_contents"] = False + if not self.config.dry_run: + self.qbt.tor_delete_recycle(torrent, attr) + body += logger.print_line(logger.insert_space("Deleted .torrent but NOT content files.", 8), self.config.loglevel) + self.stats_deleted += 1 + else: + attr["torrents_deleted_and_contents"] = True + if not self.config.dry_run: + self.qbt.tor_delete_recycle(torrent, attr) + body += logger.print_line(logger.insert_space("Deleted .torrent AND content files.", 8), self.config.loglevel) + self.stats_deleted_contents += 1 + else: + attr["torrents_deleted_and_contents"] = True + if not self.config.dry_run: + self.qbt.tor_delete_recycle(torrent, attr) + body += logger.print_line(logger.insert_space("Deleted .torrent AND content files.", 8), self.config.loglevel) + self.stats_deleted_contents += 1 + attr["body"] = "\n".join(body) + self.config.send_notifications(attr) + self.qbt.torrentinfo[self.t_name]["count"] -= 1 diff --git a/modules/core/tags.py b/modules/core/tags.py index f574e6e..4f67cf1 100644 --- a/modules/core/tags.py +++ b/modules/core/tags.py @@ -14,7 +14,6 @@ class 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: diff --git a/modules/qbittorrent.py b/modules/qbittorrent.py index cdbdd14..5e5bc48 100755 --- a/modules/qbittorrent.py +++ b/modules/qbittorrent.py @@ -850,208 +850,6 @@ class Qbt: ) return num_tags, num_untag, del_tor, del_tor_cont - def rem_unregistered(self): - """Remove torrents with unregistered trackers.""" - del_tor = 0 - del_tor_cont = 0 - num_tor_error = 0 - num_untag = 0 - tor_error_summary = "" - tag_error = self.config.tracker_error_tag - cfg_rem_unregistered = self.config.commands["rem_unregistered"] - cfg_tag_error = self.config.commands["tag_tracker_error"] - - def tag_tracker_error(): - nonlocal t_name, msg_up, msg, tracker, t_cat, torrent, tag_error, tor_error_summary, num_tor_error - tor_error = "" - tor_error += logger.insert_space(f"Torrent Name: {t_name}", 3) + "\n" - tor_error += logger.insert_space(f"Status: {msg}", 9) + "\n" - tor_error += logger.insert_space(f'Tracker: {tracker["url"]}', 8) + "\n" - tor_error += logger.insert_space(f"Added Tag: {tag_error}", 6) + "\n" - tor_error_summary += tor_error - num_tor_error += 1 - attr = { - "function": "tag_tracker_error", - "title": "Tag Tracker Error Torrents", - "body": tor_error, - "torrent_name": t_name, - "torrent_category": t_cat, - "torrent_tag": tag_error, - "torrent_status": msg, - "torrent_tracker": tracker["url"], - "notifiarr_indexer": tracker["notifiarr"], - } - self.config.send_notifications(attr) - if not self.config.dry_run: - torrent.add_tags(tags=tag_error) - - def del_unregistered(): - nonlocal del_tor, del_tor_cont, t_name, msg_up, msg, tracker, t_cat, t_msg, t_status, torrent - 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"Status: {msg}", 9), self.config.loglevel) - body += logger.print_line(logger.insert_space(f'Tracker: {tracker["url"]}', 8), self.config.loglevel) - attr = { - "function": "rem_unregistered", - "title": "Removing Unregistered Torrents", - "torrent_name": t_name, - "torrent_category": t_cat, - "torrent_status": msg, - "torrent_tracker": tracker["url"], - "notifiarr_indexer": tracker["notifiarr"], - } - if t_count > 1: - # Checks if any of the original torrents are working - if "" in t_msg or 2 in t_status: - attr["torrents_deleted_and_contents"] = False - if not self.config.dry_run: - self.tor_delete_recycle(torrent, attr) - body += logger.print_line( - logger.insert_space("Deleted .torrent but NOT content files.", 8), self.config.loglevel - ) - del_tor += 1 - else: - attr["torrents_deleted_and_contents"] = True - if not self.config.dry_run: - self.tor_delete_recycle(torrent, attr) - body += logger.print_line(logger.insert_space("Deleted .torrent AND content files.", 8), self.config.loglevel) - del_tor_cont += 1 - else: - attr["torrents_deleted_and_contents"] = True - if not self.config.dry_run: - self.tor_delete_recycle(torrent, attr) - body += logger.print_line(logger.insert_space("Deleted .torrent AND content files.", 8), self.config.loglevel) - del_tor_cont += 1 - attr["body"] = "\n".join(body) - self.config.send_notifications(attr) - self.torrentinfo[t_name]["count"] -= 1 - - if cfg_rem_unregistered or cfg_tag_error: - if cfg_tag_error: - logger.separator("Tagging Torrents with Tracker Errors", space=False, border=False) - elif cfg_rem_unregistered: - logger.separator("Removing Unregistered Torrents", space=False, border=False) - unreg_msgs = [ - "UNREGISTERED", - "TORRENT NOT FOUND", - "TORRENT IS NOT FOUND", - "NOT REGISTERED", - "NOT EXIST", - "UNKNOWN TORRENT", - "TRUMP", - "RETITLED", - "TRUNCATED", - "TORRENT IS NOT AUTHORIZED FOR USE ON THIS TRACKER", - ] - ignore_msgs = [ - "YOU HAVE REACHED THE CLIENT LIMIT FOR THIS TORRENT", - "MISSING PASSKEY", - "MISSING INFO_HASH", - "PASSKEY IS INVALID", - "INVALID PASSKEY", - "EXPECTED VALUE (LIST, DICT, INT OR STRING) IN BENCODED STRING", - "COULD NOT PARSE BENCODED DATA", - "STREAM TRUNCATED", - ] - for torrent in self.torrentvalid: - check_tags = util.get_list(torrent.tags) - # Remove any error torrents Tags that are no longer unreachable. - if tag_error in check_tags: - tracker = self.get_tags(torrent.trackers) - num_untag += 1 - body = [] - body += logger.print_line( - f"Previous Tagged {tag_error} torrent currently has a working tracker.", self.config.loglevel - ) - body += logger.print_line(logger.insert_space(f"Torrent Name: {torrent.name}", 3), self.config.loglevel) - body += logger.print_line(logger.insert_space(f"Removed Tag: {tag_error}", 4), self.config.loglevel) - body += logger.print_line(logger.insert_space(f'Tracker: {tracker["url"]}', 8), self.config.loglevel) - if not self.config.dry_run: - torrent.remove_tags(tags=tag_error) - attr = { - "function": "untag_tracker_error", - "title": "Untagging Tracker Error Torrent", - "body": "\n".join(body), - "torrent_name": torrent.name, - "torrent_category": torrent.category, - "torrent_tag": tag_error, - "torrent_tracker": tracker["url"], - "notifiarr_indexer": tracker["notifiarr"], - } - self.config.send_notifications(attr) - for torrent in self.torrentissue: - t_name = torrent.name - t_cat = self.torrentinfo[t_name]["Category"] - t_count = self.torrentinfo[t_name]["count"] - t_msg = self.torrentinfo[t_name]["msg"] - t_status = self.torrentinfo[t_name]["status"] - check_tags = util.get_list(torrent.tags) - try: - for trk in torrent.trackers: - if trk.url.startswith("http"): - tracker = self.get_tags([trk]) - msg_up = trk.msg.upper() - msg = trk.msg - # Tag any error torrents - if cfg_tag_error: - if trk.status == 4 and tag_error not in check_tags: - tag_tracker_error() - if cfg_rem_unregistered: - # Tag any error torrents that are not unregistered - if not list_in_text(msg_up, unreg_msgs) and trk.status == 4 and tag_error not in check_tags: - # Check for unregistered torrents using BHD API if the tracker is BHD - if ( - "tracker.beyond-hd.me" in tracker["url"] - and self.config.beyond_hd is not None - and not list_in_text(msg_up, ignore_msgs) - ): - json = {"info_hash": torrent.hash} - response = self.config.beyond_hd.search(json) - if response["total_results"] == 0: - del_unregistered() - break - tag_tracker_error() - if list_in_text(msg_up, unreg_msgs) and not list_in_text(msg_up, ignore_msgs) and trk.status == 4: - del_unregistered() - break - except NotFound404Error: - continue - except Exception as ex: - logger.stacktrace() - self.config.notify(ex, "Remove Unregistered Torrents", False) - logger.error(f"Remove Unregistered Torrents Error: {ex}") - if cfg_rem_unregistered: - if del_tor >= 1 or del_tor_cont >= 1: - if del_tor >= 1: - logger.print_line( - f"{'Did not delete' if self.config.dry_run else 'Deleted'} {del_tor} " - f".torrent{'s' if del_tor > 1 else ''} but not content files.", - self.config.loglevel, - ) - if del_tor_cont >= 1: - logger.print_line( - f"{'Did not delete' if self.config.dry_run else 'Deleted'} {del_tor_cont} " - f".torrent{'s' if del_tor_cont > 1 else ''} AND content files.", - self.config.loglevel, - ) - else: - logger.print_line("No unregistered torrents found.", self.config.loglevel) - if num_untag >= 1: - logger.print_line( - f"{'Did not delete' if self.config.dry_run else 'Deleted'} {tag_error} tags for {num_untag} " - f".torrent{'s.' if num_untag > 1 else '.'}", - self.config.loglevel, - ) - if num_tor_error >= 1: - logger.separator( - f"{num_tor_error} Torrents with tracker errors found", - space=False, - border=False, - loglevel=self.config.loglevel, - ) - logger.print_line(tor_error_summary.rstrip(), self.config.loglevel) - return del_tor, del_tor_cont, num_tor_error, num_untag - def cross_seed(self): """Move torrents from cross seed directory to correct save directory.""" added = 0 # Keep track of total torrents tagged diff --git a/qbit_manage.py b/qbit_manage.py index 7a4a918..652cc99 100755 --- a/qbit_manage.py +++ b/qbit_manage.py @@ -289,6 +289,7 @@ from modules.util import GracefulKiller # noqa from modules.util import Failed # noqa from modules.core.category import Category # noqa from modules.core.tags import Tags # noqa +from modules.core.remove_unregistered import RemoveUnregistered # noqa def my_except_hook(exctype, value, tbi): @@ -392,14 +393,15 @@ def start(): if cfg.commands["tag_update"]: stats["tagged"] += Tags(qbit_manager).stats - # Remove Unregistered Torrents - num_deleted, num_deleted_contents, num_tagged, num_untagged = cfg.qbt.rem_unregistered() - stats["rem_unreg"] += num_deleted + num_deleted_contents - stats["deleted"] += num_deleted - stats["deleted_contents"] += num_deleted_contents - stats["tagged_tracker_error"] += num_tagged - stats["untagged_tracker_error"] += num_untagged - stats["tagged"] += num_tagged + # Remove Unregistered Torrents and tag errors + if cfg.commands["rem_unregistered"] or cfg.commands["tag_tracker_error"]: + rem_unreg = RemoveUnregistered(qbit_manager) + stats["rem_unreg"] += rem_unreg.stats_deleted + rem_unreg.stats_deleted_contents + stats["deleted"] += rem_unreg.stats_deleted + stats["deleted_contents"] += rem_unreg.stats_deleted_contents + stats["tagged_tracker_error"] += rem_unreg.stats_tagged + stats["untagged_tracker_error"] += rem_unreg.stats_untagged + stats["tagged"] += rem_unreg.stats_tagged # Set Cross Seed num_added, num_tagged = cfg.qbt.cross_seed()