Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New feature: RSS #7

Merged
merged 3 commits into from
May 9, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 18 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,22 @@ https://godoc.org/github.com/mrobinsn/go-newznab/newznab
- Get comments for a NZB
- Get NZB download URL
- Download NZB
- Get latest releases via RSS

## Installation
To install the package run `go get github.com/mrobinsn/go-newznab`
To use it in your application, import `github.com/mrobinsn/go-newznab/newznab`

## Library Usage

Initialize a client:
### Initialize a client:
```
client := newznab.New("http://my-usenet-indexer/api", "my-api-key", false)
client := newznab.New("http://my-usenet-indexer", "my-api-key", 1234, false)

```
Note the missing `/api` part of the URL. Depending on the called method either `/api` or `/rss` will be appended to the given base URL. A valid user ID is only required for RSS methods.

Search using a tvrage id:
### Search using a tvrage id:
```
categories := []int{
newznab.CategoryTVHD,
Expand All @@ -33,7 +35,7 @@ categories := []int{
results, _ := client.SearchWithTVRage(categories, 35048, 3, 1)
```

Search using an imdb id:
### Search using an imdb id:
```
categories := []int{
newznab.CategoryMovieHD,
Expand All @@ -42,15 +44,25 @@ categories := []int{
results, _ := client.SearchWithIMDB(categories, "0364569")
```

Search using a name and set of categories:
### Search using a name and set of categories:
```
results, _ := client.SearchWithQueries(categories, "Oldboy", "movie")
```

Get latest releases for set of categories:
### Get latest releases for set of categories:
```
results, _ := client.SearchWithQuery(categories, "", "movie")
```

### Load latest releases via RSS:
```
results, _ := client.LoadRSSFeed(categories, 50)
```

### Load latest releases via RSS up to a given NZB id:
```
results, _ := client.LoadRSSFeedUntilNZBID(categories, 50, "nzb-guid", 15)
```

## Contributing
Pull requests welcome.
98 changes: 87 additions & 11 deletions newznab/newznab.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"strings"
"time"

"fmt"

log "github.com/Sirupsen/logrus"
)

Expand Down Expand Up @@ -50,14 +52,16 @@ const (
type Client struct {
apikey string
apiBaseURL string
apiUserID int
client *http.Client
}

// New returns a new instance of Client
func New(baseURL string, apikey string, insecure bool) Client {
func New(baseURL string, apikey string, userID int, insecure bool) Client {
ret := Client{
apikey: apikey,
apiBaseURL: baseURL,
apiUserID: userID,
}
if insecure {
ret.client = &http.Client{
Expand Down Expand Up @@ -110,6 +114,43 @@ func (c Client) SearchWithQuery(categories []int, query string, searchType strin
})
}

// LoadRSSFeed returns up to <num> of the most recent NZBs of the given categories.
func (c Client) LoadRSSFeed(categories []int, num int) ([]NZB, error) {
return c.rss(url.Values{
"num": []string{strconv.Itoa(num)},
"t": c.splitCats(categories),
"dl": []string{"1"},
})
}

// LoadRSSFeedUntilNZBID fetches NZBs until a given NZB id is reached.
func (c Client) LoadRSSFeedUntilNZBID(categories []int, num int, id string, maxRequests int) ([]NZB, error) {
count := 0
var nzbs []NZB
for {
partition, err := c.rss(url.Values{
"num": []string{strconv.Itoa(num)},
"t": c.splitCats(categories),
"dl": []string{"1"},
"offset": []string{strconv.Itoa(num * count)},
})
count++
if err != nil {
return nil, err
}
for k, nzb := range partition {
if nzb.ID == id {
return append(nzbs, partition[:k]...), nil
}
}
nzbs = append(nzbs, partition...)
if maxRequests != 0 && count == maxRequests {
break
}
}
return nzbs, nil
}

func (c Client) splitCats(cats []int) []string {
var categories, catsOut []string
for _, v := range cats {
Expand All @@ -118,12 +159,22 @@ func (c Client) splitCats(cats []int) []string {
catsOut = append(catsOut, strings.Join(categories, ","))
return catsOut
}

func (c Client) rss(vals url.Values) ([]NZB, error) {
vals.Set("r", c.apikey)
vals.Set("i", strconv.Itoa(c.apiUserID))
return c.process(vals, rssPath)
}

func (c Client) search(vals url.Values) ([]NZB, error) {
vals.Set("apikey", c.apikey)
//vals.Set("t", "tvsearch")
return c.process(vals, apiPath)
}

func (c Client) process(vals url.Values, path string) ([]NZB, error) {
var nzbs []NZB
log.Debug("newznab:Client:Search: searching")
resp, err := c.getURL(c.buildURL(vals))
resp, err := c.getURL(c.buildURL(vals, path))
if err != nil {
return nzbs, err
}
Expand All @@ -132,7 +183,10 @@ func (c Client) search(vals url.Values) ([]NZB, error) {
if err != nil {
return nil, err
}
log.WithField("num", len(feed.Channel.NZBs)).Info("newznab:Client:Search: found NZBs")
if feed.ErrorCode != 0 {
return nil, fmt.Errorf("newznab api error %d: %s", feed.ErrorCode, feed.ErrorDesc)
}
log.WithField("num", len(feed.Channel.NZBs)).Debug("newznab:Client:Search: found NZBs")
for _, gotNZB := range feed.Channel.NZBs {
nzb := NZB{
Title: gotNZB.Title,
Expand All @@ -142,12 +196,11 @@ func (c Client) search(vals url.Values) ([]NZB, error) {
SourceEndpoint: c.apiBaseURL,
SourceAPIKey: c.apikey,
}

for _, attr := range gotNZB.Attributes {
switch attr.Name {
case "tvairdate":
if parsedAirDate, err := time.Parse(time.RFC1123Z, attr.Value); err != nil {
log.Errorf("newznab:Client:Search: failed to parse date: %v: %v", attr.Value, err)
if parsedAirDate, err := parseDate(attr.Value); err != nil {
log.Errorf("newznab:Client:Search: failed to parse tvairdate: %v", err)
} else {
nzb.AirDate = parsedAirDate
}
Expand Down Expand Up @@ -204,6 +257,12 @@ func (c Client) search(vals url.Values) ([]NZB, error) {
nzb.IMDBScore = float32(parsedFloat)
case "coverurl":
nzb.CoverURL = attr.Value
case "usenetdate":
if parsedUsetnetDate, err := parseDate(attr.Value); err != nil {
log.Errorf("newznab:Client:Search: failed to parse usenetdate: %v", err)
} else {
nzb.UsenetDate = parsedUsetnetDate
}
default:
log.WithFields(log.Fields{
"name": attr.Name,
Expand All @@ -226,7 +285,7 @@ func (c Client) PopulateComments(nzb *NZB) error {
"t": []string{"comments"},
"id": []string{nzb.ID},
"apikey": []string{c.apikey},
}))
}, apiPath))
if err != nil {
return err
}
Expand Down Expand Up @@ -260,7 +319,7 @@ func (c Client) NZBDownloadURL(nzb NZB) string {
"t": []string{"get"},
"id": []string{nzb.ID},
"apikey": []string{c.apikey},
})
}, apiPath)
}

// DownloadNZB returns the bytes of the actual NZB file for the given NZB
Expand All @@ -287,8 +346,8 @@ func (c Client) getURL(url string) ([]byte, error) {
return data, nil
}

func (c Client) buildURL(vals url.Values) string {
parsedURL, err := url.Parse(c.apiBaseURL)
func (c Client) buildURL(vals url.Values, path string) string {
parsedURL, err := url.Parse(c.apiBaseURL + path)
if err != nil {
log.WithError(err).Error("failed to parse base API url")
return ""
Expand All @@ -298,6 +357,23 @@ func (c Client) buildURL(vals url.Values) string {
return parsedURL.String()
}

func parseDate(date string) (time.Time, error) {
formats := []string{time.RFC3339, time.RFC1123Z}
var parsedTime time.Time
var err error
for _, format := range formats {
if parsedTime, err = time.Parse(format, date); err == nil {
return parsedTime, nil
}
}
return parsedTime, fmt.Errorf("failed to parse date %s as one of %s", date, strings.Join(formats, ", "))
}

const (
apiPath = "/api"
rssPath = "/rss"
)

type commentResponse struct {
Channel struct {
Comments []rssComment `xml:"item"`
Expand Down
Loading