package indexer import ( "bytes" "errors" "net/http" ) // MagnetLink is a download link that turned out to be a magnet. Returned as // an error by a .torrent fetch so the grab hands the magnet to the download // client instead. Public trackers do this routinely: Prowlarr's download link // answers with a redirect to `magnet:?xt=…`, which Go's client would otherwise // fail on as an unsupported scheme. type MagnetLink struct{ URI string } func (m *MagnetLink) Error() string { return "the download link is a magnet link" } // AsMagnet says whether err is a magnet link, and which. func AsMagnet(err error) (string, bool) { var m *MagnetLink if errors.As(err, &m) && m.URI != "" { return m.URI, true } return "", false } // StopAtMagnet returns a copy of c that stops at a redirect to a magnet URI // and reports it as a *MagnetLink. Every other redirect behaves as c would. func StopAtMagnet(c *http.Client) *http.Client { if c == nil { c = http.DefaultClient } out := *c next := c.CheckRedirect out.CheckRedirect = func(req *http.Request, via []*http.Request) error { if req.URL != nil && req.URL.Scheme == "magnet" { return &MagnetLink{URI: req.URL.String()} } if next != nil { return next(req, via) } if len(via) >= 10 { return errors.New("stopped after 10 redirects") } return nil } return &out } // MagnetBody is a response whose whole body is a magnet URI, which some // indexers send with a 200 instead of a redirect. Nil otherwise. func MagnetBody(body []byte) error { trimmed := bytes.TrimSpace(body) if bytes.HasPrefix(trimmed, []byte("magnet:")) && !bytes.ContainsAny(trimmed, " \n<") { return &MagnetLink{URI: string(trimmed)} } return nil }