Documentation
¶
Overview ¶
Package resolver is KnightLoader's plugin seam. Everything that turns a pasted link into a concrete, downloadable target — a direct URL, a premium hoster, a debrid unlock, yt-dlp, or a headless-JD delegation — implements Resolver. v1 ships built-in resolvers; native hoster plugins come later.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Answers ¶
func Answers(got []core.Availability, want int) []core.Availability
Answers squares what a Checker returned against the number of links it was asked about, filling anything missing with core.AvailUncheckable and dropping anything extra.
It exists because the alternative is an index-out-of-range in the caller, and the input is a remote service's JSON: the day a provider adds an entry for a link it expanded, or omits one it did not recognise, is a day this app must still be able to draw its list.
Types ¶
type Checker ¶
Checker is the optional other half of a resolver: a backend that can be asked whether a link is still there without fetching it. Implementing it is what moves a service's links off core.AvailUnknown, which says "nobody has looked" and is a lie the moment somebody presses Check.
The batch is the interface and not an optimisation inside it. Every service that answers this question answers it for a list, and a caller holding fifty links that asks fifty times is a caller whose key gets rate-limited - so the one-link form is deliberately absent, because it is the shape that would get written by accident.
The contract is one verdict per URL, in the order they were given. A service that cannot answer for a particular link returns core.AvailUncheckable for it rather than dropping it, because a short slice silently re-aligns every verdict after the gap onto the wrong link. Callers should still run the answer through Answers, which is the only cheap defence against a service that changes its mind about that.
An error means the batch was not answered at all - a refused key, a service that is down. It never means "these links are gone": the caller files the whole batch as uncheckable and says so.
type Direct ¶
type Direct struct{}
Direct handles plain http(s) links whose path names a file; the URL is already the download target and is fetched by the embedded engine.
type HTTPFallback ¶
type HTTPFallback struct{}
HTTPFallback is the last resort: it takes any http(s) link that nothing else managed to fetch and simply asks the engine to download it. This is what catches a plain file whose URL carries no extension — a shape the strict Direct rule cannot recognise, and which a media extractor cannot handle either. It sits at the bottom of the priority list, so it only ever runs after every real backend has had its turn.
func (HTTPFallback) Info ¶
func (HTTPFallback) Info() Info
func (HTTPFallback) Match ¶
func (HTTPFallback) Match(raw string) bool
type HostCache ¶
type HostCache struct {
// Fetch asks the live source for a fresh host set. Required; a nil Fetch
// makes Refresh a no-op rather than a panic, which is what lets a
// zero-value HostCache with only Load set still answer Hosts() from disk.
Fetch func(ctx context.Context) (map[string]bool, error)
// Load seeds the cache before its first use, so a process that restarts
// mid-outage still serves yesterday's list rather than an empty one for
// however long the source stays down. ok is false for "nothing was ever
// persisted", which is not the same as an empty set. Nil means "nothing
// to seed from".
Load func() (hosts map[string]bool, fetchedAt time.Time, ok bool)
// Save is called after every SUCCESSFUL refresh, never after a failed
// one - a failed fetch has nothing new worth writing down, and rewriting
// the same bytes back on every failed retry would only wear the disk for
// no reason. Nil means "keep it in memory only".
Save func(hosts map[string]bool, fetchedAt time.Time)
// contains filtered or unexported fields
}
HostCache is a supported-host set that refreshes itself from a live source and never lets a failed refresh empty what it is already holding.
THE FAILURE MODE THIS EXISTS FOR: a Resolver's Match reads a host set built from this cache, and an empty set reads as "this service supports nothing" - see debrid.HostInSet and the identical helper in torbox and ytdlp, all of which treat a nil or empty map as "matches nothing", on purpose, for a service that genuinely has no hosts configured. Before this existed, whatever asked a service for its host list and got a transient error (a timeout, a 500, a rate limit) handed that same empty result straight to Registry.Register, and the resolver kept its slot in the registry while silently matching nothing - not until the next successful refresh, but until the process restarted and asked again from a clean slate. So a failed Refresh leaves Hosts() exactly as it was, and only FetchedAt and LastError move.
It is deliberately unopinionated about persistence: Load and Save are nil by default, which makes the type pure and trivially testable with nothing but a fake Fetch func, and a caller that wants a refresh to survive a restart wires real storage in once, at construction.
func (*HostCache) FetchedAt ¶
FetchedAt is when the current set was actually obtained - a live fetch if one has succeeded, else whenever the persisted set was last written, else the zero time. It is what "host list last refreshed" reads off this cache, and it is deliberately untouched by a failed Refresh: the point is to say how stale the list really is, not to reset the clock on every retry.
func (*HostCache) Hosts ¶
Hosts is the set to match against right now: the last successful fetch (or the persisted set nothing has yet had a chance to replace). Before anything has ever succeeded and nothing was ever persisted, it is nil - which every Resolver.Match built on top of this already reads as "matches nothing", truthfully: no refresh has run yet, this is not a claim about the service.
func (*HostCache) LastError ¶
LastError is the most recent refresh failure, or nil once a refresh has succeeded since - what explains a "last refreshed" stamp older than expected.
type HostCapper ¶
HostCapper is the optional other half of a resolver that can state a ceiling on how many chunks one download against a given host may safely open - the per-host fact a multihoster account sometimes has an opinion about (see internal/resolver/debrid.HostLimiter), read by app.connsFor as one more ceiling in its chain.
Kept off Resolver itself for the same reason Checker is: a resolver with nothing to say about a host must not be forced to grow a method that invents a number. 0 means "no opinion" - read by the caller exactly like every other absent ceiling in connsFor, never as "zero connections".
type Info ¶
Info identifies a resolver and sets its routing priority (higher wins).
Tagged for JSON because PriorityFor exists to make that order visible to a user, not only to act on internally - see Registry.PriorityFor.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry keeps resolvers ordered by descending priority. It is safe for concurrent use: adding an account rebuilds the routing table while downloads are running.
func NewRegistry ¶
func NewRegistry() *Registry
func (*Registry) All ¶
All returns every resolver that matches the URL, highest priority first. It is what makes a fallback chain possible: when the first backend cannot actually fetch the link, the next one gets a turn.
func (*Registry) AllInfo ¶
AllInfo lists every registered resolver's identity, in the exact order resolverForTaskLocked would try them for a URL every one of them matched - highest priority first, ties broken by registration order (see Register). It is host-independent: what a user configures determines who is even in this list, priority alone determines the order within it.
This is the "user-visible" half of routing priority. Before it, the only way to answer "which of my two debrid accounts actually gets asked first" was to read Info.Prio in the source of each resolver package - a deterministic order nobody could see was, in every way that matters to the person who configured it, the same as no order at all.
func (*Registry) PriorityFor ¶
PriorityFor narrows AllInfo to the services that would actually be asked for one host - the chain resolverForTaskLocked and nextResolverLocked walk when a link on that host comes in, in the order they walk it.
host is turned into a URL because Match is written against one: every resolver in this tree only ever inspects the scheme and the hostname, so a synthetic "https://<host>/" matches exactly what a real link on that host would.
func (*Registry) Register ¶
Register adds a resolver, keeping the list sorted by priority (highest first). A resolver with an ID that is already registered replaces it, so re-wiring after a credential change cannot leave two of the same backend behind.
func (*Registry) Unregister ¶
Unregister drops the resolver with this ID, if present. Removing a credential has to actually stop routing links to that service.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package debrid is the shared seam for one-shot debrid providers: services that turn a supported file-hoster link into a direct URL in a single call (AllDebrid, Real-Debrid).
|
Package debrid is the shared seam for one-shot debrid providers: services that turn a supported file-hoster link into a direct URL in a single call (AllDebrid, Real-Debrid). |
|
Package jd talks to a headless JDownloader through its local "Deprecated API" (plain HTTP JSON on :3128, no cloud, no crypto).
|
Package jd talks to a headless JDownloader through its local "Deprecated API" (plain HTTP JSON on :3128, no cloud, no crypto). |
|
Package torbox is the debrid resolver: it turns a supported file-hoster link into a direct CDN URL via TorBox's Web Downloads / Debrid API, which the embedded engine then downloads.
|
Package torbox is the debrid resolver: it turns a supported file-hoster link into a direct CDN URL via TorBox's Web Downloads / Debrid API, which the embedded engine then downloads. |
|
Package torrent is KnightLoader's BitTorrent intake: it recognises magnet links and uploaded .torrent files, refuses the ones that are malformed or built to be hostile, and hands the rest to the embedded download engine.
|
Package torrent is KnightLoader's BitTorrent intake: it recognises magnet links and uploaded .torrent files, refuses the ones that are malformed or built to be hostile, and hands the rest to the embedded download engine. |
|
Package ytdlp is the media-extraction backend: it delegates the download to the yt-dlp binary (which handles ~1800 sites incl.
|
Package ytdlp is the media-extraction backend: it delegates the download to the yt-dlp binary (which handles ~1800 sites incl. |