From 1d8bee28563fcc4d1a5b030f84176f9b8a41e992 Mon Sep 17 00:00:00 2001 From: Colin Date: Sun, 20 Aug 2023 09:14:19 +0000 Subject: [PATCH] sane-bt-search: add a flag to sort by tracker reputation --- .../sane-scripts/src/sane-bt-search | 61 ++++++++++++------- 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/pkgs/additional/sane-scripts/src/sane-bt-search b/pkgs/additional/sane-scripts/src/sane-bt-search index 92a901b9..1da9687c 100755 --- a/pkgs/additional/sane-scripts/src/sane-bt-search +++ b/pkgs/additional/sane-scripts/src/sane-bt-search @@ -67,6 +67,22 @@ def parse_time(t: str) -> datetime: return try_parse_time(t).astimezone() or epoch +# preference, best to worst +TRACKER_RANKS = [ + 'bakabt', + 'nyaa.si', + 'yts', + 'internet archive', + # haven't sorted these + '1337x', + 'kickasstorrents.to', +] +def tracker_rank(tracker: str): + if tracker.lower() in TRACKER_RANKS: + return TRACKER_RANKS.index(tracker.lower()) + logger.warn(f"unknown tracker: {tracker!r}") + return len(TRACKER_RANKS) + DROP_CATS = { "dvd", "hd", "misc", "other", "sd", "uhd" } MANGA_CATS = { "books", "comics", "ebook" } VIDEO_CATS = { "anime", "movies", "tv" } @@ -95,26 +111,29 @@ class Filter: self.manga = manga self.video = video - def filter(self, t: 'Torrent', default: bool = False) -> bool: + def filter(self, torrents: list['Torrent']) -> list['Torrent']: + return [t for t in torrents if self.is_match(t)] + + def is_match(self, t: 'Torrent', default: bool = False) -> bool: valid = True - valid = valid and (not self.h265 or self.filter_h265(t)) - valid = valid and (not self.manga or self.filter_manga(t, default)) - valid = valid and (not self.video or self.filter_video(t, default)) + valid = valid and (not self.h265 or self.is_h265(t)) + valid = valid and (not self.manga or self.is_manga(t, default)) + valid = valid and (not self.video or self.is_video(t, default)) return valid @staticmethod - def filter_h265(t: 'Torrent') -> bool: + def is_h265(t: 'Torrent') -> bool: meta = t.title.lower() return "h265" in meta \ or "x265" in meta \ or "HEVC" in meta @staticmethod - def filter_manga(t: 'Torrent', default: bool = False) -> bool: + def is_manga(t: 'Torrent', default: bool = False) -> bool: return is_cat(t.categories, MANGA_CATS, default) @staticmethod - def filter_video(t: 'Torrent', default: bool = False) -> bool: + def is_video(t: 'Torrent', default: bool = False) -> bool: return is_cat(t.categories, VIDEO_CATS, default) @@ -208,15 +227,12 @@ class Client: return sorted(torrents, reverse=True) - -def filter_results(results: list[Torrent], filter: Filter, top: int | None) -> list[Torrent]: - """ - take the complete query and filter further based on CLI options - """ - results = [t for t in results if filter.filter(t)] - if top is not None: - results = results[:top] - return results +def sort_results(torrents: list[Torrent], by: str) -> list[Torrent]: + if by == 'seeders': + return sorted(torrents, key=lambda t: (t.seeders, t), reverse=True) + elif by == 'source': + return sorted(torrents, key=lambda t: (-tracker_rank(t.tracker), t), reverse=True) + assert False, f"unknown sort method: {by}" def format_results(all_results: list[Torrent], filtered_results: list[Torrent], json: bool): if json: @@ -236,6 +252,7 @@ def main(args: list[str]): parser = argparse.ArgumentParser(description='search torrent trackers') parser.add_argument('--full', action='store_true', help='show all results') parser.add_argument('--top', help='how many results to show (default: 5)') + parser.add_argument('--sort-by', default='seeders', help='how to rank matches (seeders, source)') parser.add_argument('--json', action='store_true', help='output results in json') parser.add_argument('--verbose', action='store_true') parser.add_argument('--h265', action='store_true', help='show only H.265/HEVC results (might cause false negatives)') @@ -252,13 +269,13 @@ def main(args: list[str]): all_results = client.query(args.query) filter = Filter(h265=args.h265, manga=args.manga, video=args.video) - filtered_results = filter_results( - all_results, - filter, - None if args.full else int(args.top or "5"), - ) + filtered_results = filter.filter(all_results) + ordered_results = sort_results(filtered_results, args.sort_by) - format_results(all_results, filtered_results, args.json) + if not args.full: + ordered_results = ordered_results[:int(args.top or "5")] + + format_results(all_results, ordered_results, args.json) if __name__ == "__main__": main(sys.argv[1:])