sane-bt-search: refactor: split out Tracker details into own class

This commit is contained in:
2025-07-07 20:54:16 +00:00
parent cf38651e8d
commit 604599b3b6

View File

@@ -20,7 +20,7 @@ returns select results and magnet links.
from dataclasses import dataclass
from datetime import datetime
from enum import Enum
from enum import Enum, IntEnum
import argparse
import logging
import json
@@ -46,6 +46,14 @@ class SortMethod(Enum):
Seeders = "seeders"
Tracker = "tracker"
class TrackerQuality(IntEnum):
Authoritative = 0
Trustworthy = 1
Good = 2
Mediocre = 3
Bad = 4
Unknown = 5
class BadCliArgs(Exception):
def __init__(self, msg: str | None = None) -> None:
helpstr = __doc__
@@ -68,37 +76,59 @@ def try_parse_time(t: str) -> datetime:
def parse_time(t: str) -> datetime:
return try_parse_time(t).astimezone() or epoch
@dataclass(eq=True, order=True, unsafe_hash=True)
class Tracker:
name: str
# preference: major, minor.
# lower value = more preferable.
# trackers with the same major are on a similar "tier"
quality: TrackerQuality
order: int
# preference, best to worst
TRACKER_RANKS = [
[ 'BitMagnet (Local DHT)', ],
[
'BakaBT',
'SubsPlease',
'Nyaa.si',
'sukebei.nyaa.si',
],
[ 'YTS', ],
[ 'MioBT', ],
[ 'Internet Archive', ],
[ 'The Pirate Bay', ],
# haven't sorted these
[
'1337x',
'Bengumi Moe',
'kickasstorrents.to',
'Tokyo Toshokan',
'Torlock',
]
]
# returns the tracker rank, as a tuple of (major, minor).
# trackers with the same major rank are _roughly_ equal.
def tracker_rank(tracker: str) -> tuple[int, int]:
for major, trackers in enumerate(TRACKER_RANKS):
if tracker in trackers:
return major, trackers.index(tracker)
logger.warning(f"unknown tracker: {tracker!r}")
return len(TRACKER_RANKS), 0
@staticmethod
def by_name(name: str) -> 'Tracker':
if name in KNOWN_TRACKERS:
return KNOWN_TRACKERS[name]
logger.warning(f"unknown tracker: {name!r}")
return Tracker(name, TrackerQuality.Unknown, len(KNOWN_TRACKERS))
def __repr__(self) -> str:
return self.name
def _KNOWN_TRACKERS() -> dict[str, Tracker]:
trackers = {}
order = 0
def add_tracker(*args):
nonlocal order
t = Tracker(*args, order)
order += 1
trackers[t.name] = t
# the order of this list (even withint sections) is significant:
# most preferred -> least preferred
add_tracker('BitMagnet (Local DHT)', TrackerQuality.Authoritative)
add_tracker('BakaBT', TrackerQuality.Trustworthy)
add_tracker('SubsPlease', TrackerQuality.Trustworthy)
add_tracker('Nyaa.si', TrackerQuality.Trustworthy)
add_tracker('sukebei.nyaa.si', TrackerQuality.Trustworthy)
add_tracker('YTS', TrackerQuality.Good)
add_tracker('Tokyo Toshokan', TrackerQuality.Good)
add_tracker('Internet Archive', TrackerQuality.Mediocre)
add_tracker('The Pirate Bay', TrackerQuality.Mediocre)
add_tracker('MioBT', TrackerQuality.Mediocre)
add_tracker('Bengumi Moe', TrackerQuality.Mediocre)
add_tracker('Torlock', TrackerQuality.Mediocre)
add_tracker('1337x', TrackerQuality.Bad)
add_tracker('kickasstorrents.to', TrackerQuality.Bad)
return trackers
KNOWN_TRACKERS = _KNOWN_TRACKERS()
DROP_CATS = { "dvd", "hd", "misc", "other", "sd", "uhd" }
BOOK_CATS = { "audio", "books", "ebook" }
@@ -167,7 +197,7 @@ class Torrent:
seeders: int
pub_date: datetime
size: int
tracker: str
tracker: Tracker
title: str
magnet: str | None
http_dl_uri: str | None # probably a .torrent file but it COULD be a referral to a magnet:// URI
@@ -226,7 +256,7 @@ class Torrent:
if seeders is not None and pub_date is not None and title is not None and (magnet is not None or http_dl_uri is not None):
pub_date = parse_time(pub_date)
return Torrent(seeders, pub_date, size, tracker, title, magnet, http_dl_uri, tracker_uri, categories=categories)
return Torrent(seeders, pub_date, size, Tracker.by_name(tracker), title, magnet, http_dl_uri, tracker_uri, categories=categories)
def to_dict(self) -> dict:
# N.B.: not all fields: needs to be kept in sync with consumers like mx-sanebot
@@ -269,12 +299,11 @@ class Client:
def sort_results(torrents: list[Torrent], by: SortMethod) -> list[Torrent]:
def key(t: Torrent) -> tuple[int, int, Torrent]:
rank_seeders = -t.seeders
rank_tracker_major, rank_tracker_minor = tracker_rank(t.tracker)
# TODO: `Balanced` should consider `size` and `pub_date`
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),
SortMethod.Balanced: (t.tracker.quality, rank_seeders, t.tracker.order, t),
SortMethod.Seeders: (rank_seeders, t.tracker.quality, t.tracker.order, t),
SortMethod.Tracker: (t.tracker.quality, t.tracker.order, rank_seeders, t),
}[by]
return sorted(torrents, key=key)