mirror of
https://github.com/simon-ding/polaris.git
synced 2026-03-02 07:30:47 +08:00
182 lines
4.8 KiB
Go
182 lines
4.8 KiB
Go
package torznab
|
|
|
|
import (
|
|
"context"
|
|
"encoding/xml"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"polaris/db"
|
|
"polaris/log"
|
|
"slices"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
type Response struct {
|
|
XMLName xml.Name `xml:"rss"`
|
|
Text string `xml:",chardata"`
|
|
Version string `xml:"version,attr"`
|
|
Atom string `xml:"atom,attr"`
|
|
Torznab string `xml:"torznab,attr"`
|
|
Channel struct {
|
|
Text string `xml:",chardata"`
|
|
Link struct {
|
|
Text string `xml:",chardata"`
|
|
Href string `xml:"href,attr"`
|
|
Rel string `xml:"rel,attr"`
|
|
Type string `xml:"type,attr"`
|
|
} `xml:"link"`
|
|
Title string `xml:"title"`
|
|
Description string `xml:"description"`
|
|
Language string `xml:"language"`
|
|
Category string `xml:"category"`
|
|
Item []Item `xml:"item"`
|
|
} `xml:"channel"`
|
|
}
|
|
|
|
type Item struct {
|
|
Text string `xml:",chardata"`
|
|
Title string `xml:"title"`
|
|
Guid string `xml:"guid"`
|
|
Jackettindexer struct {
|
|
Text string `xml:",chardata"`
|
|
ID string `xml:"id,attr"`
|
|
} `xml:"jackettindexer"`
|
|
Type string `xml:"type"`
|
|
Comments string `xml:"comments"`
|
|
PubDate string `xml:"pubDate"`
|
|
Size string `xml:"size"`
|
|
Description string `xml:"description"`
|
|
Link string `xml:"link"`
|
|
Category []string `xml:"category"`
|
|
Enclosure struct {
|
|
Text string `xml:",chardata"`
|
|
URL string `xml:"url,attr"`
|
|
Length string `xml:"length,attr"`
|
|
Type string `xml:"type,attr"`
|
|
} `xml:"enclosure"`
|
|
Attr []struct {
|
|
Text string `xml:",chardata"`
|
|
Name string `xml:"name,attr"`
|
|
Value string `xml:"value,attr"`
|
|
} `xml:"attr"`
|
|
}
|
|
|
|
func (i *Item) GetAttr(key string) string {
|
|
for _, a := range i.Attr {
|
|
if a.Name == key {
|
|
return a.Value
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
func (r *Response) ToResults(indexer *db.TorznabInfo) []Result {
|
|
var res []Result
|
|
for _, item := range r.Channel.Item {
|
|
if slices.Contains(item.Category, "3000") { //exclude audio files
|
|
continue
|
|
}
|
|
r := Result{
|
|
Name: item.Title,
|
|
Link: item.Link,
|
|
Size: mustAtoI(item.Size),
|
|
Seeders: mustAtoI(item.GetAttr("seeders")),
|
|
Peers: mustAtoI(item.GetAttr("peers")),
|
|
Category: mustAtoI(item.GetAttr("category")),
|
|
ImdbId: item.GetAttr("imdbid"),
|
|
DownloadVolumeFactor: tryParseFloat(item.GetAttr("downloadvolumefactor")),
|
|
UploadVolumeFactor: tryParseFloat(item.GetAttr("uploadvolumefactor")),
|
|
Source: indexer.Name,
|
|
IndexerId: indexer.ID,
|
|
Priority: indexer.Priority,
|
|
IsPrivate: item.Type == "private",
|
|
}
|
|
res = append(res, r)
|
|
}
|
|
return res
|
|
}
|
|
|
|
func mustAtoI(key string) int {
|
|
i, err := strconv.Atoi(key)
|
|
if err != nil {
|
|
log.Errorf("must atoi error: %v", err)
|
|
panic(err)
|
|
}
|
|
return i
|
|
}
|
|
|
|
func tryParseFloat(s string) float32 {
|
|
r, err := strconv.ParseFloat(s, 32)
|
|
if err != nil {
|
|
log.Warnf("parse float error: %v", err)
|
|
return 0
|
|
}
|
|
return float32(r)
|
|
}
|
|
|
|
func Search(indexer *db.TorznabInfo, keyWord string) ([]Result, error) {
|
|
ctx, cancel := context.WithTimeout(context.TODO(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, indexer.URL, nil)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "new request")
|
|
}
|
|
var q = url.Values{}
|
|
q.Add("apikey", indexer.ApiKey)
|
|
q.Add("t", "search")
|
|
q.Add("q", keyWord)
|
|
req.URL.RawQuery = q.Encode()
|
|
key := fmt.Sprintf("%s: %s", indexer.Name, keyWord)
|
|
|
|
cacheRes, ok := cc.Get(key)
|
|
if !ok {
|
|
res, err := doRequest(req)
|
|
if err != nil {
|
|
cc.Set(key, &Response{})
|
|
return nil, errors.Wrap(err, "do http request")
|
|
}
|
|
cacheRes = res
|
|
cc.Set(key, cacheRes)
|
|
}
|
|
return cacheRes.ToResults(indexer), nil
|
|
}
|
|
|
|
func doRequest(req *http.Request) (*Response, error) {
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "do http")
|
|
}
|
|
defer resp.Body.Close()
|
|
data, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "read http body")
|
|
}
|
|
var res Response
|
|
err = xml.Unmarshal(data, &res)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "xml unmarshal data: %v", string(data))
|
|
}
|
|
return &res, nil
|
|
}
|
|
|
|
type Result struct {
|
|
Name string `json:"name"`
|
|
Link string `json:"link"`
|
|
Size int `json:"size"`
|
|
Seeders int `json:"seeders"`
|
|
Peers int `json:"peers"`
|
|
Category int `json:"category"`
|
|
Source string `json:"source"`
|
|
DownloadVolumeFactor float32 `json:"download_volume_factor"`
|
|
UploadVolumeFactor float32 `json:"upload_volume_factor"`
|
|
IndexerId int `json:"indexer_id"`
|
|
Priority int `json:"priority"`
|
|
IsPrivate bool `json:"is_private"`
|
|
ImdbId string `json:"imdb_id"`
|
|
}
|