Skip to content

Commit

Permalink
Merge pull request #7 from imba3r/rss
Browse files Browse the repository at this point in the history
New feature: RSS
  • Loading branch information
Michael Robinson authored May 9, 2017
2 parents 6c0c614 + acd52ba commit 56a726e
Show file tree
Hide file tree
Showing 9 changed files with 386 additions and 34 deletions.
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

0 comments on commit 56a726e

Please sign in to comment.