From 157841baa5c569e4b0e0dc4adfbc4046c3b40a96 Mon Sep 17 00:00:00 2001 From: Devin Buhl Date: Wed, 23 Aug 2023 11:53:45 -0400 Subject: [PATCH] feat: add expired command Signed-off-by: Devin Buhl --- qbittools/commands/expired.py | 122 ++++++++++++++++++++++++++++++++++ qbittools/qbittools.py | 4 +- 2 files changed, 124 insertions(+), 2 deletions(-) create mode 100644 qbittools/commands/expired.py diff --git a/qbittools/commands/expired.py b/qbittools/commands/expired.py new file mode 100644 index 0000000..ab638cc --- /dev/null +++ b/qbittools/commands/expired.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python3 + +import tldextract + +import qbittools + +INDEXER_SPECS = { + 'filelist': { + 'name': 'filelist', + 'urls': ['filelist.io', 'flro.org'], + 'required_seed_ratio': 1.05, + 'required_seed_days': 2.5, + }, + 'torrentleech': { + 'name': 'torrentleech', + 'urls': ['tleechreload.org', 'torrentleech.org'], + 'required_seed_ratio': 1.05, + 'required_seed_days': 10.5, + }, + 'hd-torrents': { + 'name': 'hd-torrents', + 'urls': ['hdts-announce.ru'], + 'required_seed_ratio': 1.05, + 'required_seed_days': 10.5, + }, + 'hd-space': { + 'name': 'hd-space', + 'urls': ['hd-space.pw'], + 'required_seed_ratio': 1.05, + 'required_seed_days': 10.5, + }, + 'scenetime': { + 'name': 'scenetime', + 'urls': ['scenetime.com'], + 'required_seed_ratio': 0, + 'required_seed_days': 3.5, + }, + 'iptorrents': { + 'name': 'iptorrents', + 'urls': ['bgp.technology', 'empirehost.me', 'stackoverflow.tech'], + 'required_seed_ratio': 1.05, + 'required_seed_days': 14.5, + }, + 'torrentseeds': { + 'name': 'torrentseeds', + 'urls': ['torrentseeds.org'], + 'required_seed_ratio': 1.05, + 'required_seed_days': 5.5, + }, +} + +DHT_MATCHES = [ + '** [DHT] **', + '** [PeX] **', + '** [LSD] **' +] + +def filter_indexer_by_args(indexer_list): + results = [] + for indexer_name in indexer_list: + indexer_spec = INDEXER_SPECS.get(indexer_name) + if indexer_spec: + results.append(indexer_spec) + return results + +def filter_indexer_by_url(indexer_list, domain): + for indexer in indexer_list: + if domain in indexer['urls']: + return indexer + return None + +def seconds(days: int) -> int: + seconds_in_a_day = 86400 + seconds = days * seconds_in_a_day + return seconds + +def days(seconds: int) -> int: + seconds_in_a_day = 86400 + days = seconds / seconds_in_a_day + return days + +def dhms(total_seconds: int) -> str: + seconds = total_seconds % 60 + total_minutes = total_seconds // 60 + total_hours = total_minutes // 60 + minutes = total_minutes % 60 + days = total_hours // 24 + hours = total_hours % 24 + return f"{days}d{hours}h{minutes}m{seconds}s" + +def __init__(args, logger): + client = qbittools.qbit_client(args) + indexers = [item for sublist in args.indexer for item in sublist] + filtered_indexer_list = filter_indexer_by_args(indexers) + + logger.info(f"Checking for expired torrents in qBittorrent") + logger.info(f"Using indexers '{' '.join(indexers)}'") + if args.dry_run: + logger.info(f"Dry run mode initiated, no torrents will be deleted") + + torrents_info = client.torrents.info() + for torrent in torrents_info: + real_trackers = list(filter(lambda s: not s.url in DHT_MATCHES, torrent.trackers)) + domain = tldextract.extract(sorted(real_trackers, key=lambda x: x.url)[0].url).registered_domain + + indexer = filter_indexer_by_url(filtered_indexer_list, domain) + if indexer: + if torrent['ratio'] >= indexer['required_seed_ratio']: + logger.info(f"Removing torrent {torrent['name']} ({domain}) with matching indexer {indexer['name']} due to an expired ratio ({round(torrent['ratio'], 2)})") + if not args.dry_run: + torrent.delete(delete_files=False) + elif torrent['seeding_time'] >= seconds(indexer['required_seed_days']): + logger.info(f"Removing torrent {torrent['name']} ({domain}) with matching indexer {indexer['name']} due to an expired seeding time ({dhms(torrent['seeding_time'])})") + if not args.dry_run: + torrent.delete(delete_files=False) + +def add_arguments(subparser): + parser = subparser.add_parser('expired') + parser.add_argument('--dry-run', action='store_true', help='Do not delete the torrents only log them', required=False) + parser.add_argument('--indexer', nargs='*', action='append', metavar='myindexer', default=[], help='Indexer, can be repeated multiple times', required=False) + + qbittools.add_default_args(parser) diff --git a/qbittools/qbittools.py b/qbittools/qbittools.py index 117cecc..aaf6875 100755 --- a/qbittools/qbittools.py +++ b/qbittools/qbittools.py @@ -9,7 +9,7 @@ os.environ["PYOXIDIZER"] = "1" import qbittorrentapi -import commands.orphaned, commands.reannounce, commands.tagging +import commands.expired, commands.orphaned, commands.reannounce, commands.tagging class QbitConfig(NamedTuple): host: str @@ -110,7 +110,7 @@ def main(): parser = argparse.ArgumentParser() subparsers = parser.add_subparsers(dest="command") - for cmd in ["reannounce", "tagging", "orphaned"]: + for cmd in ["expired", "reannounce", "tagging", "orphaned"]: mod = getattr(globals()["commands"], cmd) getattr(mod, "add_arguments")(subparsers)