sane-bt-search: improve sort metrics

This commit is contained in:
2025-07-07 20:05:03 +00:00
parent d5711e7de7
commit 48bd6d304f

View File

@@ -20,6 +20,7 @@ returns select results and magnet links.
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime from datetime import datetime
from enum import Enum
import argparse import argparse
import logging import logging
import json import json
@@ -40,8 +41,13 @@ epoch = datetime(1970, 1, 1)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class SortMethod(Enum):
Balanced = "balanced"
Seeders = "seeders"
Tracker = "tracker"
class BadCliArgs(Exception): class BadCliArgs(Exception):
def __init__(self, msg: str = None): def __init__(self, msg: str | None = None) -> None:
helpstr = __doc__ helpstr = __doc__
if msg: if msg:
super().__init__(f"{msg}\n\n{helpstr}") super().__init__(f"{msg}\n\n{helpstr}")
@@ -49,7 +55,7 @@ class BadCliArgs(Exception):
super().__init__(helpstr) super().__init__(helpstr)
def try_parse_time(t: str): def try_parse_time(t: str) -> datetime:
try: try:
return datetime.fromisoformat(t) return datetime.fromisoformat(t)
except ValueError: pass except ValueError: pass
@@ -65,23 +71,34 @@ def parse_time(t: str) -> datetime:
# preference, best to worst # preference, best to worst
TRACKER_RANKS = [ TRACKER_RANKS = [
'bakabt', [ 'BitMagnet (Local DHT)', ],
'subsplease', [
'nyaa.si', 'BakaBT',
'miobt', 'SubsPlease',
'yts', 'Nyaa.si',
'internet archive', 'sukebei.nyaa.si',
'the pirate bay', ],
[ 'YTS', ],
[ 'MioBT', ],
[ 'Internet Archive', ],
[ 'The Pirate Bay', ],
# haven't sorted these # haven't sorted these
'1337x', [
'kickasstorrents.to', '1337x',
'Bengumi Moe',
'kickasstorrents.to',
'Tokyo Toshokan',
'Torlock',
]
] ]
def tracker_rank(tracker: str): # returns the tracker rank, as a tuple of (major, minor).
tracker_ = tracker.lower() # trackers with the same major rank are _roughly_ equal.
if tracker_ in TRACKER_RANKS: def tracker_rank(tracker: str) -> tuple[int, int]:
return TRACKER_RANKS.index(tracker_) for major, trackers in enumerate(TRACKER_RANKS):
logger.warning(f"unknown tracker: {tracker_!r}") if tracker in trackers:
return len(TRACKER_RANKS) return major, trackers.index(tracker)
logger.warning(f"unknown tracker: {tracker!r}")
return len(TRACKER_RANKS), 0
DROP_CATS = { "dvd", "hd", "misc", "other", "sd", "uhd" } DROP_CATS = { "dvd", "hd", "misc", "other", "sd", "uhd" }
BOOK_CATS = { "audio", "books", "ebook" } BOOK_CATS = { "audio", "books", "ebook" }
@@ -249,12 +266,17 @@ class Client:
return sorted(torrents, reverse=True) return sorted(torrents, reverse=True)
def sort_results(torrents: list[Torrent], by: str) -> list[Torrent]: def sort_results(torrents: list[Torrent], by: SortMethod) -> list[Torrent]:
if by == 'seeders': def key(t: Torrent) -> tuple[int, int, Torrent]:
return sorted(torrents, key=lambda t: (t.seeders, t), reverse=True) rank_seeders = -t.seeders
elif by == 'tracker': rank_tracker_major, rank_tracker_minor = tracker_rank(t.tracker)
return sorted(torrents, key=lambda t: (-tracker_rank(t.tracker), t), reverse=True) # TODO: `Balanced` should consider `size` and `pub_date`
assert False, f"unknown sort method: {by}" return {
SortMethod.Balanced: (rank_tracker_major, rank_seeders, rank_tracker_minor, t),
SortMethod.Seeders: (rank_seeders, rank_tracker_major, rank_tracker_minor, t),
SortMethod.Tracker: (rank_tracker_major, rank_tracker_minor, rank_seeders, t),
}[by]
return sorted(torrents, key=key)
def format_results(all_results: list[Torrent], filtered_results: list[Torrent], json: bool): def format_results(all_results: list[Torrent], filtered_results: list[Torrent], json: bool):
if json: if json:
@@ -274,7 +296,7 @@ def main(args: list[str]):
parser = argparse.ArgumentParser(description='search torrent trackers') parser = argparse.ArgumentParser(description='search torrent trackers')
parser.add_argument('--full', action='store_true', help='show all results') parser.add_argument('--full', action='store_true', help='show all results')
parser.add_argument('--top', help=f'how many results to show (default: {DEFAULT_RESULT_COUNT})') parser.add_argument('--top', help=f'how many results to show (default: {DEFAULT_RESULT_COUNT})')
parser.add_argument('--sort-by', default='seeders', help='how to rank matches (seeders, tracker)') parser.add_argument('--sort-by', default=SortMethod.Balanced, type=SortMethod, help='how to rank matches (seeders, tracker)')
parser.add_argument('--json', action='store_true', help='output results in json') parser.add_argument('--json', action='store_true', help='output results in json')
parser.add_argument('--verbose', action='store_true') parser.add_argument('--verbose', action='store_true')
parser.add_argument('--book', action='store_true', help='show only book (ebook or audiobook) results') parser.add_argument('--book', action='store_true', help='show only book (ebook or audiobook) results')