diff --git a/router/router.go b/router/router.go index a1493b40..8c5c4bbd 100755 --- a/router/router.go +++ b/router/router.go @@ -58,7 +58,9 @@ func init() { Router.HandleFunc("/feed", RSSHandler).Name("feed") Router.HandleFunc("/feed/{page:[0-9]+}", RSSHandler).Name("feed_page") Router.HandleFunc("/feed/torznab", RSSTorznabHandler).Name("feed_torznab") + Router.HandleFunc("/feed/torznab/{page:[0-9]+}", RSSTorznabHandler).Name("feed_torznab_page") Router.HandleFunc("/feed/eztv", RSSEztvHandler).Name("feed_eztv") + Router.HandleFunc("/feed/eztv/{page:[0-9]+}", RSSEztvHandler).Name("feed_eztv_page") // !!! This line need to have the same download location as the one define in config.TorrentStorageLink !!! Router.Handle("/download/{hash}", wrapHandler(downloadTorrentHandler)).Name("torrent_download") diff --git a/router/rss_handler.go b/router/rss_handler.go index 7cd9ff49..2776c333 100644 --- a/router/rss_handler.go +++ b/router/rss_handler.go @@ -7,10 +7,14 @@ import ( "strconv" "time" + "sort" + "github.com/NyaaPantsu/nyaa/config" "github.com/NyaaPantsu/nyaa/model" userService "github.com/NyaaPantsu/nyaa/service/user" + "github.com/NyaaPantsu/nyaa/util/categories" "github.com/NyaaPantsu/nyaa/util/feeds" + "github.com/NyaaPantsu/nyaa/util/publicSettings" "github.com/NyaaPantsu/nyaa/util/search" "github.com/gorilla/feeds" "github.com/gorilla/mux" @@ -122,60 +126,130 @@ func RSSEztvHandler(w http.ResponseWriter, r *http.Request) { } } -// RSSEztvHandler : Controller for displaying rss feed, accepting common search arguments +// RSSTorznabHandler : Controller for displaying rss feed, accepting common search arguments func RSSTorznabHandler(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() - - // We only get the basic variable for rss based on search param - torrents, createdAsTime, title, err := getTorrentList(r) - - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return + t := r.URL.Query().Get("t") + rss := "" + title := "Nyaa Pantsu" + if config.IsSukebei() { + title = "Sukebei Pantsu" } + if t == "caps" { + T := publicSettings.GetTfuncFromRequest(r) + cat := categories.GetCategoriesSelect(true) + var categories []*nyaafeeds.RssCategoryTorznab + var keys []string + for name := range cat { + keys = append(keys, name) + } + sort.Strings(keys) + last := -1 + for _, key := range keys { + if len(cat[key]) <= 2 { + categories = append(categories, &nyaafeeds.RssCategoryTorznab{ + ID: nyaafeeds.ConvertFromCat(cat[key]), + Name: string(T(key)), + }) + last++ + } else { + categories[last].Subcat = append(categories[last].Subcat, &nyaafeeds.RssSubCat{ + ID: nyaafeeds.ConvertFromCat(cat[key]), + Name: string(T(key)), + }) + } + } + feed := &nyaafeeds.RssCaps{ + Server: &nyaafeeds.RssServer{ + Version: "1.0", + Title: title, + Strapline: "...", + Email: config.Conf.Email.From, + URL: config.WebAddress(), + Image: config.WebAddress() + "/img/logo.png", + }, + Limits: &nyaafeeds.RssLimits{ + Max: "300", + Default: "50", + }, + Registration: &nyaafeeds.RssRegistration{ + Available: "yes", + Open: "yes", + }, + Searching: &nyaafeeds.RssSearching{ + Search: &nyaafeeds.RssSearch{ + Available: "yes", + SupportedParams: "q", + }, + TvSearch: &nyaafeeds.RssSearch{ + Available: "no", + }, + MovieSearch: &nyaafeeds.RssSearch{ + Available: "no", + }, + }, + Categories: &nyaafeeds.RssCategories{ + Category: categories, + }, + } + var rssErr error + rss, rssErr = feeds.ToXML(feed) + if rssErr != nil { + http.Error(w, rssErr.Error(), http.StatusInternalServerError) + } + } else { + // We only get the basic variable for rss based on search param + torrents, createdAsTime, title, err := getTorrentList(r) - feed := &nyaafeeds.RssFeed{ - Title: title, - Link: config.WebAddress() + "/", - PubDate: createdAsTime.String(), - } - feed.Items = make([]*nyaafeeds.RssItem, len(torrents)) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } - for i, torrent := range torrents { - torrentJSON := torrent.ToJSON() - feed.Items[i] = &nyaafeeds.RssItem{ - Title: torrentJSON.Name, - Link: config.WebAddress() + "/download/" + torrentJSON.Hash, - Category: &nyaafeeds.RssCategory{ - Domain: config.WebAddress() + "/search?c=" + torrentJSON.Category + "_" + torrentJSON.SubCategory, - }, - Description: string(torrentJSON.Description), - Comments: config.WebAddress() + "/view/" + strconv.FormatUint(uint64(torrentJSON.ID), 10), - PubDate: torrent.Date.String(), - GUID: config.WebAddress() + "/view/" + strconv.FormatUint(uint64(torrentJSON.ID), 10), - Enclosure: &nyaafeeds.RssEnclosure{ - URL: config.WebAddress() + "/download/" + torrentJSON.Hash, - Length: strconv.FormatUint(uint64(torrentJSON.Filesize), 10), - Type: "application/x-bittorrent", - }, - Torznab: &nyaafeeds.RssTorznab{ - Xmlns: "http://torznab.com/schemas/2015/feed", - Size: strconv.FormatUint(uint64(torrentJSON.Filesize), 10), - Files: strconv.Itoa(len(torrentJSON.FileList)), - Grabs: strconv.Itoa(torrentJSON.Downloads), - Seeders: strconv.Itoa(int(torrentJSON.Seeders)), - Leechers: strconv.Itoa(int(torrentJSON.Leechers)), - Infohash: torrentJSON.Hash, - MagnetURL: string(torrentJSON.Magnet), - }, + feed := &nyaafeeds.RssFeed{ + Title: title, + Link: config.WebAddress() + "/", + PubDate: createdAsTime.String(), + } + feed.Items = make([]*nyaafeeds.RssItem, len(torrents)) + + for i, torrent := range torrents { + torrentJSON := torrent.ToJSON() + feed.Items[i] = &nyaafeeds.RssItem{ + Title: torrentJSON.Name, + Link: config.WebAddress() + "/download/" + torrentJSON.Hash, + Category: &nyaafeeds.RssCategory{ + Domain: config.WebAddress() + "/search?c=" + torrentJSON.Category + "_" + torrentJSON.SubCategory, + }, + Description: string(torrentJSON.Description), + Comments: config.WebAddress() + "/view/" + strconv.FormatUint(uint64(torrentJSON.ID), 10), + PubDate: torrent.Date.String(), + GUID: config.WebAddress() + "/view/" + strconv.FormatUint(uint64(torrentJSON.ID), 10), + Enclosure: &nyaafeeds.RssEnclosure{ + URL: config.WebAddress() + "/download/" + torrentJSON.Hash, + Length: strconv.FormatUint(uint64(torrentJSON.Filesize), 10), + Type: "application/x-bittorrent", + }, + Torznab: &nyaafeeds.RssTorznab{ + Xmlns: "http://torznab.com/schemas/2015/feed", + Size: strconv.FormatUint(uint64(torrentJSON.Filesize), 10), + Files: strconv.Itoa(len(torrentJSON.FileList)), + Grabs: strconv.Itoa(torrentJSON.Downloads), + Seeders: strconv.Itoa(int(torrentJSON.Seeders)), + Leechers: strconv.Itoa(int(torrentJSON.Leechers)), + Infohash: torrentJSON.Hash, + MagnetURL: string(torrentJSON.Magnet), + }, + } + } + var rssErr error + rss, rssErr = feeds.ToXML(feed) + if rssErr != nil { + http.Error(w, rssErr.Error(), http.StatusInternalServerError) } } // allow cross domain AJAX requests w.Header().Set("Access-Control-Allow-Origin", "*") - rss, rssErr := feeds.ToXML(feed) - if rssErr != nil { - http.Error(w, rssErr.Error(), http.StatusInternalServerError) - } _, writeErr := w.Write([]byte(rss)) if writeErr != nil { @@ -187,6 +261,7 @@ func getTorrentList(r *http.Request) (torrents []model.Torrent, createdAsTime ti vars := mux.Vars(r) page := vars["page"] userID := vars["id"] + cat := r.URL.Query().Get("cat") offset := r.URL.Query().Get("offset") pagenum := 1 @@ -222,6 +297,12 @@ func getTorrentList(r *http.Request) (torrents []model.Torrent, createdAsTime ti r.URL.RawQuery = query.Encode() } + if cat != "" { + query := r.URL.Query() + c, sub := nyaafeeds.ConvertToCat(cat) + query.Set("c", c+"_"+sub) + } + _, torrents, err = search.SearchByQueryNoCount(r, pagenum) createdAsTime = time.Now() diff --git a/service/torrent/torrent.go b/service/torrent/torrent.go index 432fdbb2..4b4279a0 100644 --- a/service/torrent/torrent.go +++ b/service/torrent/torrent.go @@ -161,7 +161,7 @@ func getTorrentsOrderBy(parameters *serviceBase.WhereParams, orderBy string, lim if countAll { dbQ = dbQ.Preload("Comments") } - err = dbQ.Raw(dbQuery, params...).Find(&torrents).Error + err = dbQ.Preload("FileList").Raw(dbQuery, params...).Find(&torrents).Error return } diff --git a/util/feeds/convert.go b/util/feeds/convert.go new file mode 100644 index 00000000..c4afd26a --- /dev/null +++ b/util/feeds/convert.go @@ -0,0 +1,25 @@ +package nyaafeeds + +import ( + "strings" +) + +// ConvertToCat : Convert a torznab cat to our cat +func ConvertToCat(cat string) (string, string) { + c := strings.Split(cat, "0000") + if len(c) < 2 { + return cat, "" + } + return c[0], c[1] +} + +// ConvertFromCat : Convert a cat to a torznab cat +func ConvertFromCat(category string) (cat string) { + c := strings.Split(category, "_") + if len(c) < 2 { + cat = c[0] + "0000" + return + } + cat = c[0] + "0000" + c[1] + return +} diff --git a/util/feeds/rss.go b/util/feeds/rss.go index 2dd8eec5..b0c0d8bd 100644 --- a/util/feeds/rss.go +++ b/util/feeds/rss.go @@ -15,9 +15,11 @@ import ( // private wrapper around the RssFeed which gives us the .. xml type rssFeedXML struct { - XMLName xml.Name `xml:"rss"` - Version string `xml:"version,attr"` - Channel *RssFeed + XMLName xml.Name `xml:"rss"` + Version string `xml:"version,attr"` + Encoding string `xml:"encoding,attr"` + Channel *RssFeed `xml:"channel,omitempty"` + Caps *RssCaps `xml:"caps,omitempty"` } type RssImage struct { @@ -77,6 +79,70 @@ type RssItem struct { Torznab *RssTorznab `xml:"torznab,omitempty"` } +type RssCaps struct { + XMLName xml.Name `xml:"caps"` + Server *RssServer `xml:"server,omitempty"` + Limits *RssLimits `xml:"limits,omitempty"` + Registration *RssRegistration `xml:"registration,omitempty"` + Searching *RssSearching `xml:"searching,omitempty"` + Categories *RssCategories `xml:"categories,omitempty"` +} + +type RssServer struct { + XMLName xml.Name `xml:"server"` + Xmlns string `xml:"xmlns,attr"` + Version string `xml:"version,attr"` + Title string `xml:"title,attr"` + Strapline string `xml:"strapline,attr"` + Email string `xml:"email,attr"` + URL string `xml:"url,attr"` + Image string `xml:"image,attr"` +} + +type RssLimits struct { + XMLName xml.Name `xml:"limits"` + Max string `xml:"limits,attr"` + Default string `xml:"default,attr"` +} + +type RssRegistration struct { + XMLName xml.Name `xml:"registration"` + Available string `xml:"available,attr"` + Open string `xml:"open,attr"` +} + +type RssSearching struct { + XMLName xml.Name `xml:"searching"` + Search *RssSearch `xml:"search,omitempty"` + TvSearch *RssSearch `xml:"tv-search,omitempty"` + MovieSearch *RssSearch `xml:"movie-search,omitempty"` +} + +type RssSearch struct { + Available string `xml:"available,attr"` + SupportedParams string `xml:"supportedParams,attr,omitempty"` +} + +type RssCategories struct { + XMLName xml.Name `xml:"categories"` + Category []*RssCategoryTorznab +} + +type RssCategoryTorznab struct { + XMLName xml.Name `xml:"category"` + ID string `xml:"id,attr"` + Name string `xml:"name,attr"` + Subcat []*RssSubCat + Description string `xml:"description,attr,omitempty"` +} + +type RssSubCat struct { + XMLName xml.Name `xml:"subcat"` + ID string `xml:"id"` + Name string `xml:"name"` + Description string `xml:"description,omitempty"` +} + type RssTorrent struct { XMLName xml.Name `xml:"torrent"` Xmlns string `xml:"xmlns,attr"` @@ -195,5 +261,10 @@ func (r *Rss) FeedXml() interface{} { // FeedXml : return an XML-ready object for an RssFeed object func (r *RssFeed) FeedXml() interface{} { - return &rssFeedXML{Version: "2.0", Channel: r} + return &rssFeedXML{Version: "2.0", Encoding: "UTF-8", Channel: r} +} + +// FeedXml : return an XML-ready object for an RssFeed object +func (r *RssCaps) FeedXml() interface{} { + return &rssFeedXML{Version: "2.0", Encoding: "UTF-8", Caps: r} }