mock_search now takes the source and gives Soulseek / Torrent / Usenet distinct hits — different release-group pools, seeder magnitudes, and which qualities show up (Soulseek drops the top remux, Usenet drops the cam, etc.), the way real indexers differ. Endpoint forwards body.source. Fixes 'click Torrent shows the same results as Soulseek'. +1 test.
112 lines
4.1 KiB
Python
112 lines
4.1 KiB
Python
"""Mock indexer — a stand-in for a real slskd/Prowlarr search while the download
|
|
engine is being built.
|
|
|
|
Given a scope (movie / episode / season / series), a title, and a SOURCE, it returns
|
|
plausible raw 'indexer hits' ({title, size_bytes, seeders}) — and each source returns
|
|
a DIFFERENT set (different release groups, seeder counts, and which qualities show up),
|
|
the way real indexers do. The real parse → evaluate → rank pipeline (release_parse +
|
|
quality_eval) runs on these exactly as it will on real hits. Deterministic (no RNG) so
|
|
results are stable for tests and reloads. THIS is the single swap-point: replace
|
|
``mock_search`` with a real indexer client and nothing downstream changes.
|
|
|
|
Pure (no DB, no network). Isolated — imports only typing; the music side never imports it.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
_GB = 1024 ** 3
|
|
|
|
# Quality strings (WITHOUT a release group — the group is appended per source below)
|
|
# and an approximate size in GB. Ordered best→worst; the evaluator re-ranks anyway.
|
|
_MOVIE = [
|
|
("2160p.UHD.BluRay.REMUX.HDR.DV.TrueHD.Atmos", 58),
|
|
("2160p.WEB-DL.DDP5.1.HDR.HEVC", 19),
|
|
("1080p.BluRay.x265.10bit.DTS-HD.MA.5.1", 11),
|
|
("1080p.WEB-DL.DDP5.1.H264", 7),
|
|
("1080p.WEBRip.x264.AAC", 4),
|
|
("720p.HDTV.x264", 2),
|
|
("HDCAM.x264", 2),
|
|
]
|
|
_EPISODE = [
|
|
("2160p.WEB-DL.DDP5.1.HDR.HEVC", 5),
|
|
("1080p.BluRay.x265", 3),
|
|
("1080p.WEB-DL.H264", 2),
|
|
("720p.HDTV.x264", 1),
|
|
]
|
|
_SEASON = [
|
|
("2160p.WEB-DL.DDP5.1.HDR.HEVC", 48),
|
|
("1080p.BluRay.x265.10bit", 26),
|
|
("1080p.WEB-DL.H264", 18),
|
|
("720p.HDTV.x264", 9),
|
|
]
|
|
_SERIES = [
|
|
("COMPLETE.1080p.BluRay.x265.10bit", 120),
|
|
("COMPLETE.1080p.WEB-DL.H264", 88),
|
|
("COMPLETE.720p.WEB-DL.x264", 40),
|
|
]
|
|
|
|
# Per-source "flavour": which slice of the quality list shows up, a seeder multiplier,
|
|
# and a release-group pool. Makes Soulseek vs Torrent vs Usenet return distinct hits.
|
|
_SRC_FLAVOR = {
|
|
"soulseek": {"slice": (1, None), "seed": 0.5, "groups": ["YIFY", "GalaxyRG", "RARBG"]},
|
|
"torrent": {"slice": (0, None), "seed": 1.0, "groups": ["FraMeSToR", "FLUX", "NTb", "RARBG"]},
|
|
"usenet": {"slice": (0, -1), "seed": 1.5, "groups": ["NTb", "FLUX", "TEPES", "playWEB"]},
|
|
"youtube": {"slice": (2, None), "seed": 1.0, "groups": ["YT"]},
|
|
}
|
|
_DEFAULT_FLAVOR = _SRC_FLAVOR["torrent"]
|
|
|
|
|
|
def _slug(title: Any) -> str:
|
|
s = "".join(c if (c.isalnum() or c == " ") else " " for c in str(title or "").strip())
|
|
return ".".join(p for p in s.split() if p) or "Unknown"
|
|
|
|
|
|
def _seeders(slug: str, i: int) -> int:
|
|
# Deterministic spread (no RNG): a stable hash of the title + index.
|
|
h = 0
|
|
for ch in slug:
|
|
h = (h * 31 + ord(ch)) & 0xFFFFFFFF
|
|
return ((h >> (i % 7)) % 240) + 3
|
|
|
|
|
|
def _ss(n) -> str:
|
|
try:
|
|
return "S%02d" % int(n)
|
|
except (TypeError, ValueError):
|
|
return "S01"
|
|
|
|
|
|
def mock_search(scope: str, title: Any, *, year: Any = None, season: Any = None,
|
|
episode: Any = None, season_end: Any = None, source: Any = "torrent") -> list:
|
|
"""Return plausible raw hits for a scope + source. Replace with a real indexer later."""
|
|
slug = _slug(title)
|
|
scope = (scope or "movie").lower()
|
|
if scope == "movie":
|
|
prefix, base = slug + ("." + str(year) if year else ""), _MOVIE
|
|
elif scope == "episode":
|
|
prefix = slug + "." + _ss(season) + ("E%02d" % int(episode) if episode is not None else "E01")
|
|
base = _EPISODE
|
|
elif scope == "season":
|
|
prefix, base = slug + "." + _ss(season), _SEASON
|
|
elif scope == "series":
|
|
prefix, base = slug + ".S01-" + _ss(season_end or 5), _SERIES
|
|
else:
|
|
return []
|
|
|
|
fl = _SRC_FLAVOR.get(str(source or "").lower(), _DEFAULT_FLAVOR)
|
|
lo, hi = fl["slice"]
|
|
chosen = list(enumerate(base))[lo:hi]
|
|
hits = []
|
|
for pos, (i, (suffix, gb)) in enumerate(chosen):
|
|
group = fl["groups"][pos % len(fl["groups"])]
|
|
hits.append({
|
|
"title": prefix + "." + suffix + "-" + group,
|
|
"size_bytes": int(gb * _GB),
|
|
"seeders": max(1, int(_seeders(slug, i) * fl["seed"])),
|
|
})
|
|
return hits
|
|
|
|
|
|
__all__ = ["mock_search"]
|