Compare commits
10 Commits
f1cfa36427
...
6b48dd3d61
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b48dd3d61 | ||
|
|
7e15610a34 | ||
|
|
a57dff60d8 | ||
|
|
7bfae2f004 | ||
|
|
ba3487852c | ||
|
|
ccc9ea86f3 | ||
|
|
7fb3ffda5d | ||
|
|
e9038f7194 | ||
|
|
49b95cf5a5 | ||
|
|
eaf9ee8a3a |
0
.gitattributes
vendored
Normal file → Executable file
0
.gitattributes
vendored
Normal file → Executable file
0
.gitignore
vendored
Normal file → Executable file
0
.gitignore
vendored
Normal file → Executable file
0
config/config.go
Normal file → Executable file
0
config/config.go
Normal file → Executable file
0
config/config_test.go
Normal file → Executable file
0
config/config_test.go
Normal file → Executable file
115
exchange/exchange.go
Normal file → Executable file
115
exchange/exchange.go
Normal file → Executable file
@@ -5,12 +5,15 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"local/logger"
|
"io"
|
||||||
"local/rssmon2/monitor"
|
"local/rssmon2/monitor"
|
||||||
"local/rssmon2/rss"
|
"local/rssmon2/rss"
|
||||||
"local/rssmon2/server"
|
"local/rssmon2/server"
|
||||||
"local/rssmon2/store"
|
"local/rssmon2/store"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -25,6 +28,7 @@ type Exchange struct {
|
|||||||
Srv *server.Server
|
Srv *server.Server
|
||||||
allFeeds map[string]*rss.Feed
|
allFeeds map[string]*rss.Feed
|
||||||
toraddr string
|
toraddr string
|
||||||
|
savepath string
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(mon *monitor.Monitor, sclient store.Client, srv *server.Server, toraddr string) *Exchange {
|
func New(mon *monitor.Monitor, sclient store.Client, srv *server.Server, toraddr string) *Exchange {
|
||||||
@@ -34,6 +38,7 @@ func New(mon *monitor.Monitor, sclient store.Client, srv *server.Server, toraddr
|
|||||||
Srv: srv,
|
Srv: srv,
|
||||||
allFeeds: make(map[string]*rss.Feed),
|
allFeeds: make(map[string]*rss.Feed),
|
||||||
toraddr: toraddr,
|
toraddr: toraddr,
|
||||||
|
savepath: "/mnt/podcasts",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,12 +67,12 @@ func (ex *Exchange) LoadDB() error {
|
|||||||
func (ex *Exchange) NewFeed(url, itemFilter, contentFilter string, tags []string, interval time.Duration) {
|
func (ex *Exchange) NewFeed(url, itemFilter, contentFilter string, tags []string, interval time.Duration) {
|
||||||
feed, err := rss.New(url, itemFilter, contentFilter, tags, interval)
|
feed, err := rss.New(url, itemFilter, contentFilter, tags, interval)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Logf("can't create new RSS %q: %v", url, err)
|
log.Printf("can't create new RSS %q: %v", url, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ex.allFeeds[url] = feed
|
ex.allFeeds[url] = feed
|
||||||
if err := ex.Mon.Submit(url, feed.Interval); err != nil {
|
if err := ex.Mon.Submit(url, feed.Interval); err != nil {
|
||||||
logger.Logf("Cannot accept new feed %q: %v", url, err)
|
log.Printf("Cannot accept new feed %q: %v", url, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,6 +111,32 @@ func (ex *Exchange) GetFeedItem(ID string) (string, error) {
|
|||||||
return item.Content, nil
|
return item.Content, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ex *Exchange) ListTag(tag string, n int) (string, error) {
|
||||||
|
feeds := []*rss.Feed{}
|
||||||
|
feedNames, err := ex.SClient.List(nsForFeeds, "", true, -1)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
for _, feedName := range feedNames {
|
||||||
|
b, err := ex.SClient.Get(nsForFeeds, feedName)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
feed, err := rss.Deserialize(b)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range feed.Tags {
|
||||||
|
if feed.Tags[i] == tag {
|
||||||
|
feeds = append(feeds, feed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b, err := json.Marshal(feeds)
|
||||||
|
return string(b), err
|
||||||
|
}
|
||||||
|
|
||||||
func (ex *Exchange) GetFeedTagRSS(tag string) (string, error) {
|
func (ex *Exchange) GetFeedTagRSS(tag string) (string, error) {
|
||||||
feedNames, err := ex.SClient.List(nsForFeeds, "", true, -1)
|
feedNames, err := ex.SClient.List(nsForFeeds, "", true, -1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -159,48 +190,48 @@ func (ex *Exchange) UpdateFeed(url string) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
f, err := rss.New(url, "", "", nil, time.Minute)
|
f, err := rss.New(url, "", "", nil, time.Minute)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Logf("cannot identify unknown feed triggered in monitor: %q: %v", url, err)
|
log.Printf("cannot identify unknown feed triggered in monitor: %q: %v", url, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
b, err := ex.SClient.Get(nsForFeeds, f.ID())
|
b, err := ex.SClient.Get(nsForFeeds, f.ID())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Logf("cannot get unknown feed triggered in monitor: %q: %v", url, err)
|
log.Printf("cannot get unknown feed triggered in monitor: %q: %v", url, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
feed, err = rss.Deserialize(b)
|
feed, err = rss.Deserialize(b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Logf("cannot deserialize feed triggered in monitor: %q: %v", url, err)
|
log.Printf("cannot deserialize feed triggered in monitor: %q: %v", url, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
items, err := ex.allFeeds[url].Update()
|
items, err := ex.allFeeds[url].Update()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Logf("can't update old RSS %q: %v", url, err)
|
log.Printf("can't update old RSS %q: %v", url, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
b, err := feed.Serialize()
|
b, err := feed.Serialize()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Logf("can't serialize to save RSS %q: %v", url, err)
|
log.Printf("can't serialize to save RSS %q: %v", url, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := ex.SClient.Set(nsForFeeds, feed.ID(), b); err != nil {
|
if err := ex.SClient.Set(nsForFeeds, feed.ID(), b); err != nil {
|
||||||
logger.Logf("can't save RSS %q.%q: %v", nsForFeeds, feed.ID(), err)
|
log.Printf("can't save RSS %q.%q: %v", nsForFeeds, feed.ID(), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
logger.Log("Saved feed", feed)
|
log.Print("Saved feed", feed)
|
||||||
for i := range items {
|
for i := range items {
|
||||||
b, err := items[i].Serialize()
|
b, err := items[i].Serialize()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Logf("can't save rss item %q.%q: %v", url, items[i].Link, err)
|
log.Printf("can't save rss item %q.%q: %v", url, items[i].Link, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := ex.SClient.Set(feed.ID(), items[i].ID(), b); err != nil {
|
if err := ex.SClient.Set(feed.ID(), items[i].ID(), b); err != nil {
|
||||||
logger.Logf("can't save rss item %q.%q: %v", feed.ID(), items[i].ID(), err)
|
log.Printf("can't save rss item %q.%q: %v", feed.ID(), items[i].ID(), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
//logger.Log("Saved feed item", feed.ID(), items[i].ID(), items[i])
|
//log.Print("Saved feed item", feed.ID(), items[i].ID(), items[i])
|
||||||
}
|
}
|
||||||
logger.Logf("Saved %d feed items for %s", len(items), feed.Title)
|
log.Printf("Saved %d feed items for %s", len(items), feed.Title)
|
||||||
go ex.handlerByTag(feed.Tags, items)
|
go ex.handlerByTag(feed.Tags, items)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,53 +241,93 @@ func (ex *Exchange) handlerByTag(tags []string, items []*rss.Item) {
|
|||||||
switch tags[i] {
|
switch tags[i] {
|
||||||
case "torrent":
|
case "torrent":
|
||||||
ex.handlerTorrent(items[j])
|
ex.handlerTorrent(items[j])
|
||||||
|
case "podcast":
|
||||||
|
ex.handlerPodcast(items[j])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ex *Exchange) handlerPodcast(item *rss.Item) {
|
||||||
|
os.MkdirAll(ex.savepath, os.ModePerm)
|
||||||
|
|
||||||
|
if len(item.Enclosures) < 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i := range item.Enclosures {
|
||||||
|
link := item.Enclosures[i]
|
||||||
|
link = strings.TrimSpace(link)
|
||||||
|
if !strings.Contains(link, ".mp3") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
savePath := path.Join(ex.savepath, path.Base(link))
|
||||||
|
if _, err := os.Stat(savePath); !os.IsNotExist(err) {
|
||||||
|
log.Printf("err: %v already exists", savePath)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.Get(link)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("cannot get podcast %q: %v", link, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
out, err := os.Create(savePath)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("cannot create file %q for saving: %v", savePath, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, err := io.Copy(out, resp.Body); err != nil {
|
||||||
|
log.Printf("failed to write: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (ex *Exchange) handlerTorrent(item *rss.Item) {
|
func (ex *Exchange) handlerTorrent(item *rss.Item) {
|
||||||
magnet := regexp.MustCompile("magnet:.xt[^ $]*")
|
magnet := regexp.MustCompile("magnet:.xt[^ $]*")
|
||||||
if !magnet.MatchString(item.Content) {
|
if !magnet.MatchString(item.Content) {
|
||||||
logger.Log("no magnet link: ", item.Content)
|
log.Print("no magnet link: ", item.Content)
|
||||||
}
|
}
|
||||||
match := magnet.FindString(item.Content)
|
match := magnet.FindString(item.Content)
|
||||||
|
|
||||||
resp, err := http.Get(ex.toraddr)
|
resp, err := http.Get(ex.toraddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Logf("cannot get session id at %v: %v", ex.toraddr, err)
|
log.Printf("cannot get session id at %v: %v", ex.toraddr, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
sessionID := resp.Header.Get("X-Transmission-Session-Id")
|
sessionID := resp.Header.Get("X-Transmission-Session-Id")
|
||||||
requestBody, err := json.Marshal(map[string]interface{}{
|
requestBody, err := json.Marshal(map[string]interface{}{
|
||||||
"method": "torrent-add",
|
"method": "torrent-add",
|
||||||
"arguments": map[string]interface{}{
|
"arguments": map[string]interface{}{
|
||||||
"filename": match,
|
"filename": match,
|
||||||
|
"download-dir": "/data/completed-rss",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Log("cannot build request body: %v", err)
|
log.Print("cannot build request body: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
req, err := http.NewRequest("POST", ex.toraddr, bytes.NewBuffer(requestBody))
|
req, err := http.NewRequest("POST", ex.toraddr, bytes.NewBuffer(requestBody))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Log("cannot POST request: %v", err)
|
log.Print("cannot POST request: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
req.Header.Add("X-Transmission-Session-Id", sessionID)
|
req.Header.Add("X-Transmission-Session-Id", sessionID)
|
||||||
resp, err = (&http.Client{}).Do(req)
|
resp, err = (&http.Client{}).Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Log("failed POST: %v", err)
|
log.Print("failed POST: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var out map[string]interface{}
|
var out map[string]interface{}
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
|
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
|
||||||
logger.Log("cannot decode response: %v", err)
|
log.Print("cannot decode response: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if v, ok := out["result"]; !ok || v != "success" {
|
if v, ok := out["result"]; !ok || v != "success" {
|
||||||
logger.Logf("failed magnet submission for feed item %v: %v", item.Name, v)
|
log.Printf("failed magnet submission for feed item %v: %v", item.Name, v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
5
main.go
Normal file → Executable file
5
main.go
Normal file → Executable file
@@ -1,12 +1,12 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"local/logger"
|
|
||||||
"local/rssmon2/config"
|
"local/rssmon2/config"
|
||||||
"local/rssmon2/exchange"
|
"local/rssmon2/exchange"
|
||||||
"local/rssmon2/monitor"
|
"local/rssmon2/monitor"
|
||||||
"local/rssmon2/server"
|
"local/rssmon2/server"
|
||||||
"local/rssmon2/store"
|
"local/rssmon2/store"
|
||||||
|
"log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -42,6 +42,7 @@ func core() {
|
|||||||
ex.GetFeedRSS,
|
ex.GetFeedRSS,
|
||||||
ex.GetFeedItem,
|
ex.GetFeedItem,
|
||||||
ex.GetFeedTagRSS,
|
ex.GetFeedTagRSS,
|
||||||
|
ex.ListTag,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@@ -51,7 +52,7 @@ func core() {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Log("Starting with", config)
|
log.Print("Starting with", config)
|
||||||
if err := srv.Serve(); err != nil {
|
if err := srv.Serve(); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|||||||
5
main_test.go
Normal file → Executable file
5
main_test.go
Normal file → Executable file
@@ -110,6 +110,11 @@ func Test_Core(t *testing.T) {
|
|||||||
status: 200,
|
status: 200,
|
||||||
post: func() { time.Sleep(time.Second * 15) },
|
post: func() { time.Sleep(time.Second * 15) },
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
method: "get",
|
||||||
|
path: "api/list/tag/gotest",
|
||||||
|
status: 200,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
c.method = strings.ToUpper(c.method)
|
c.method = strings.ToUpper(c.method)
|
||||||
|
|||||||
0
monitor/item.go
Normal file → Executable file
0
monitor/item.go
Normal file → Executable file
0
monitor/item_test.go
Normal file → Executable file
0
monitor/item_test.go
Normal file → Executable file
12
monitor/monitor.go
Normal file → Executable file
12
monitor/monitor.go
Normal file → Executable file
@@ -2,7 +2,7 @@ package monitor
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"local/logger"
|
"log"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/golang-collections/go-datastructures/queue"
|
"github.com/golang-collections/go-datastructures/queue"
|
||||||
@@ -59,29 +59,29 @@ func (monitor *Monitor) loop() error {
|
|||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
if nextEvent, err = nextEventTime(queue); err != nil {
|
if nextEvent, err = nextEventTime(queue); err != nil {
|
||||||
logger.Log("no next event time", err)
|
log.Print("no next event time", err)
|
||||||
nextEvent = time.Now().Add(time.Minute * 60)
|
nextEvent = time.Now().Add(time.Minute * 60)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
case <-time.After(nextEvent.Sub(time.Now())):
|
case <-time.After(nextEvent.Sub(time.Now())):
|
||||||
items, err := queue.Get(1)
|
items, err := queue.Get(1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Fatal("can't get item")
|
log.Fatal("can't get item")
|
||||||
}
|
}
|
||||||
if len(items) == 0 {
|
if len(items) == 0 {
|
||||||
logger.Log("no items in queue")
|
log.Print("no items in queue")
|
||||||
nextEvent = time.Now().Add(time.Minute * 60)
|
nextEvent = time.Now().Add(time.Minute * 60)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
item, ok := items[0].(*Item)
|
item, ok := items[0].(*Item)
|
||||||
if !ok {
|
if !ok {
|
||||||
logger.Fatal("queue contains illegal item")
|
log.Fatal("queue contains illegal item")
|
||||||
}
|
}
|
||||||
go monitor.trigger(item.URL)
|
go monitor.trigger(item.URL)
|
||||||
item.increment()
|
item.increment()
|
||||||
queue.Put(item)
|
queue.Put(item)
|
||||||
if nextEvent, err = nextEventTime(queue); err != nil {
|
if nextEvent, err = nextEventTime(queue); err != nil {
|
||||||
logger.Log("no next event time", err)
|
log.Print("no next event time", err)
|
||||||
nextEvent = time.Now().Add(time.Minute * 60)
|
nextEvent = time.Now().Add(time.Minute * 60)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
0
monitor/monitor_test.go
Normal file → Executable file
0
monitor/monitor_test.go
Normal file → Executable file
13
rss/feed.go
Normal file → Executable file
13
rss/feed.go
Normal file → Executable file
@@ -5,7 +5,7 @@ import (
|
|||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"local/logger"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -107,8 +107,11 @@ func (feed *Feed) fromGofeed(gofeed *gofeed.Feed) ([]*Item, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
feed.Updated = latest.Add(time.Second) //time.Now() //*updated //time.Now().UTC() //*updated
|
if latest != feed.Updated {
|
||||||
|
feed.Updated = latest.Add(time.Second)
|
||||||
|
}
|
||||||
feed.Title = gofeed.Title
|
feed.Title = gofeed.Title
|
||||||
|
log.Printf("found %v new items for %v", len(newitems), feed.Title)
|
||||||
return newitems, nil
|
return newitems, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,19 +121,19 @@ func (feed *Feed) appendNewItems(items []*gofeed.Item) ([]*Item, time.Time, erro
|
|||||||
for i := range items {
|
for i := range items {
|
||||||
t := gofeedItemTS(items[i])
|
t := gofeedItemTS(items[i])
|
||||||
if *t != (time.Time{}) && t.Before(feed.Updated) {
|
if *t != (time.Time{}) && t.Before(feed.Updated) {
|
||||||
logger.Logf("\tnot adding %v because its timestamp before %v", items[i].Link, feed.Updated)
|
log.Printf("\tnot adding %v because its timestamp (%v) before updated (%v)", items[i].Link, *t, feed.Updated)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if t.After(latest) {
|
if t.After(latest) {
|
||||||
latest = *t
|
latest = *t
|
||||||
}
|
}
|
||||||
if ok, _ := regexp.MatchString(feed.ItemFilter, items[i].Title); !ok {
|
if ok, _ := regexp.MatchString(feed.ItemFilter, items[i].Title); !ok {
|
||||||
logger.Logf("\tnot adding %v because its title doesnt match item filter %v", items[i].Link, feed.ItemFilter)
|
//log.Printf("\tnot adding %v because its title doesnt match item filter %v", items[i].Link, feed.ItemFilter)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
item := FromGofeedItem(items[i], feed.ContentFilter)
|
item := FromGofeedItem(items[i], feed.ContentFilter)
|
||||||
newitems = append(newitems, item)
|
newitems = append(newitems, item)
|
||||||
}
|
}
|
||||||
logger.Logf("\tnew latest is %v", latest)
|
log.Printf("\tnew latest is %v from %v", latest, feed.Updated)
|
||||||
return newitems, latest, nil
|
return newitems, latest, nil
|
||||||
}
|
}
|
||||||
|
|||||||
0
rss/feed_test.go
Normal file → Executable file
0
rss/feed_test.go
Normal file → Executable file
25
rss/item.go
Normal file → Executable file
25
rss/item.go
Normal file → Executable file
@@ -16,17 +16,19 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Item struct {
|
type Item struct {
|
||||||
Name string
|
Name string
|
||||||
Link string
|
Link string
|
||||||
Content string
|
Content string
|
||||||
TS time.Time
|
Enclosures []string
|
||||||
|
TS time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func (item *Item) String() string {
|
func (item *Item) String() string {
|
||||||
return fmt.Sprintf("Name %v, Link %v, Content %v, TS %v",
|
return fmt.Sprintf("Name %v, Link %v, Content %v, Enclosures %v, TS %v",
|
||||||
item.Name,
|
item.Name,
|
||||||
item.Link,
|
item.Link,
|
||||||
len(item.Content),
|
len(item.Content),
|
||||||
|
item.Enclosures,
|
||||||
item.TS.Local(),
|
item.TS.Local(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -51,11 +53,16 @@ func (item *Item) Serialize() ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func FromGofeedItem(gfitem *gofeed.Item, filter string) *Item {
|
func FromGofeedItem(gfitem *gofeed.Item, filter string) *Item {
|
||||||
|
enclosures := []string{}
|
||||||
|
for i := range gfitem.Enclosures {
|
||||||
|
enclosures = append(enclosures, gfitem.Enclosures[i].URL)
|
||||||
|
}
|
||||||
item := &Item{
|
item := &Item{
|
||||||
Name: gfitem.Title,
|
Name: gfitem.Title,
|
||||||
Link: gfitem.Link,
|
Link: gfitem.Link,
|
||||||
Content: "",
|
Content: "",
|
||||||
TS: *gofeedItemTS(gfitem),
|
Enclosures: enclosures,
|
||||||
|
TS: *gofeedItemTS(gfitem),
|
||||||
}
|
}
|
||||||
content := gfitem.Content
|
content := gfitem.Content
|
||||||
if content == "" {
|
if content == "" {
|
||||||
|
|||||||
0
rss/item_test.go
Normal file → Executable file
0
rss/item_test.go
Normal file → Executable file
5
rss/xml.go
Normal file → Executable file
5
rss/xml.go
Normal file → Executable file
@@ -19,8 +19,9 @@ func ToRSS(feed *Feed, items []*Item) (string, error) {
|
|||||||
v := &url.Values{}
|
v := &url.Values{}
|
||||||
v.Add("url", feed.ID()+"."+items[i].ID())
|
v.Add("url", feed.ID()+"."+items[i].ID())
|
||||||
root.Items[i] = &feeds.Item{
|
root.Items[i] = &feeds.Item{
|
||||||
Title: items[i].Name,
|
Title: items[i].Name,
|
||||||
Link: &feeds.Link{Href: "/api/feed/item?" + v.Encode()},
|
//Link: &feeds.Link{Href: "/api/feed/item?" + v.Encode()},
|
||||||
|
Link: &feeds.Link{Href: items[i].Link},
|
||||||
Description: items[i].Content,
|
Description: items[i].Content,
|
||||||
Created: items[i].TS,
|
Created: items[i].TS,
|
||||||
}
|
}
|
||||||
|
|||||||
39
server/server.go
Normal file → Executable file
39
server/server.go
Normal file → Executable file
@@ -3,7 +3,7 @@ package server
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"local/logger"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
@@ -22,15 +22,24 @@ type Server struct {
|
|||||||
getFeedHandler func(string, int) (string, error)
|
getFeedHandler func(string, int) (string, error)
|
||||||
getFeedItemHandler func(string) (string, error)
|
getFeedItemHandler func(string) (string, error)
|
||||||
getFeedTagHandler func(string) (string, error)
|
getFeedTagHandler func(string) (string, error)
|
||||||
|
getListTagHandler func(string, int) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(addr string, newFeedHandler func(string, string, string, []string, time.Duration), getFeedHandler func(string, int) (string, error), getFeedItemHandler func(string) (string, error), getFeedTagHandler func(string) (string, error)) (*Server, error) {
|
func New(
|
||||||
|
addr string,
|
||||||
|
newFeedHandler func(string, string, string, []string, time.Duration),
|
||||||
|
getFeedHandler func(string, int) (string, error),
|
||||||
|
getFeedItemHandler func(string) (string, error),
|
||||||
|
getFeedTagHandler func(string) (string, error),
|
||||||
|
getListTagHandler func(string, int) (string, error),
|
||||||
|
) (*Server, error) {
|
||||||
return &Server{
|
return &Server{
|
||||||
addr: addr,
|
addr: addr,
|
||||||
newFeedHandler: newFeedHandler,
|
newFeedHandler: newFeedHandler,
|
||||||
getFeedHandler: getFeedHandler,
|
getFeedHandler: getFeedHandler,
|
||||||
getFeedItemHandler: getFeedItemHandler,
|
getFeedItemHandler: getFeedItemHandler,
|
||||||
getFeedTagHandler: getFeedTagHandler,
|
getFeedTagHandler: getFeedTagHandler,
|
||||||
|
getListTagHandler: getListTagHandler,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,6 +90,8 @@ func (s *Server) api(w http.ResponseWriter, r *http.Request) {
|
|||||||
switch advance(r) {
|
switch advance(r) {
|
||||||
case "feed":
|
case "feed":
|
||||||
s.feed(w, r)
|
s.feed(w, r)
|
||||||
|
case "list":
|
||||||
|
s.list(w, r)
|
||||||
default:
|
default:
|
||||||
s.notFound(w, r)
|
s.notFound(w, r)
|
||||||
}
|
}
|
||||||
@@ -155,7 +166,7 @@ func (s *Server) getFeedTag(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
feedBody, err := s.getFeedTagHandler(url)
|
feedBody, err := s.getFeedTagHandler(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Logf("cannot get feed tag %s: %v", url, err)
|
log.Printf("cannot get feed tag %s: %v", url, err)
|
||||||
s.mybad(w, r)
|
s.mybad(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -170,7 +181,7 @@ func (s *Server) getFeedItem(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
feedBody, err := s.getFeedItemHandler(url)
|
feedBody, err := s.getFeedItemHandler(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Logf("cannot get feed item %s: %v", url, err)
|
log.Printf("cannot get feed item %s: %v", url, err)
|
||||||
s.mybad(w, r)
|
s.mybad(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -191,13 +202,31 @@ func (s *Server) getFeed(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
feedBody, err := s.getFeedHandler(url, limit)
|
feedBody, err := s.getFeedHandler(url, limit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Logf("cannot get feed %s: %v", url, err)
|
log.Printf("cannot get feed %s: %v", url, err)
|
||||||
s.mybad(w, r)
|
s.mybad(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Fprintln(w, feedBody)
|
fmt.Fprintln(w, feedBody)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) list(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch advance(r) {
|
||||||
|
case "tag":
|
||||||
|
s.listTag(w, r)
|
||||||
|
default:
|
||||||
|
s.notFound(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) listTag(w http.ResponseWriter, r *http.Request) {
|
||||||
|
tag := advance(r)
|
||||||
|
out, err := s.getListTagHandler(tag, -1)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Fprintln(w, out)
|
||||||
|
}
|
||||||
|
|
||||||
func validURL(loc string) bool {
|
func validURL(loc string) bool {
|
||||||
_, err := url.ParseRequestURI(loc)
|
_, err := url.ParseRequestURI(loc)
|
||||||
return err == nil
|
return err == nil
|
||||||
|
|||||||
8
server/server_test.go
Normal file → Executable file
8
server/server_test.go
Normal file → Executable file
@@ -19,7 +19,7 @@ func Test_Server(t *testing.T) {
|
|||||||
testPort = strings.Split(server.Listener.Addr().String(), ":")[1]
|
testPort = strings.Split(server.Listener.Addr().String(), ":")[1]
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
s, err := New(testPort, func(string, string, string, []string, time.Duration) {}, func(string, int) (string, error) { return "", nil }, func(string) (string, error) { return "", nil }, func(string) (string, error) { return "", nil })
|
s, err := New(testPort, func(string, string, string, []string, time.Duration) {}, func(string, int) (string, error) { return "", nil }, func(string) (string, error) { return "", nil }, func(string) (string, error) { return "", nil }, func(string, int) (string, error) { return "", nil })
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("failed to create server: %v", err)
|
t.Errorf("failed to create server: %v", err)
|
||||||
}
|
}
|
||||||
@@ -58,6 +58,12 @@ func Test_Server(t *testing.T) {
|
|||||||
if err := checkStatus("GET", "api/feed/tag/b", http.StatusOK); err != nil {
|
if err := checkStatus("GET", "api/feed/tag/b", http.StatusOK); err != nil {
|
||||||
t.Errorf(err.Error())
|
t.Errorf(err.Error())
|
||||||
}
|
}
|
||||||
|
if err := checkStatus("GET", "api/feed/tag/b", http.StatusOK); err != nil {
|
||||||
|
t.Errorf(err.Error())
|
||||||
|
}
|
||||||
|
if err := checkStatus("GET", "api/list/tag/b", http.StatusOK); err != nil {
|
||||||
|
t.Errorf(err.Error())
|
||||||
|
}
|
||||||
syscall.Kill(syscall.Getpid(), syscall.SIGINT)
|
syscall.Kill(syscall.Getpid(), syscall.SIGINT)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
0
store/bolt.go
Normal file → Executable file
0
store/bolt.go
Normal file → Executable file
0
store/bolt_test.go
Normal file → Executable file
0
store/bolt_test.go
Normal file → Executable file
0
store/store.go
Normal file → Executable file
0
store/store.go
Normal file → Executable file
Reference in New Issue
Block a user