diff --git a/cmd/fake-server/main.go b/cmd/fake-server/main.go
index 684f6cc..44e5728 100644
--- a/cmd/fake-server/main.go
+++ b/cmd/fake-server/main.go
@@ -24,6 +24,13 @@ var akAnnoTests = [6]string{
}
var akAnnoIdx = 0
+var sirenTests = [6]string{
+ "tests/siren/00.json",
+ "tests/siren/01.json",
+ "tests/siren/750450.json",
+}
+var sirenIdx = 0
+
func weiboHandler(w http.ResponseWriter, r *http.Request) {
data, _ := ioutil.ReadFile(weiboTests[weiboIdx])
log.Printf("Deliverd %v\n", weiboTests[weiboIdx])
@@ -38,10 +45,18 @@ func akAnnoHandler(w http.ResponseWriter, r *http.Request) {
w.Write(data)
}
+func sirenHandler(w http.ResponseWriter, r *http.Request) {
+ data, _ := ioutil.ReadFile(sirenTests[sirenIdx])
+ log.Printf("Deliverd %v\n", sirenTests[sirenIdx])
+ sirenIdx = (sirenIdx + 1) % 3
+ w.Write(data)
+}
+
func main() {
listenAddr := ":8088"
http.HandleFunc("/weibo", weiboHandler)
http.HandleFunc("/akanno", akAnnoHandler)
+ http.HandleFunc("/siren", sirenHandler)
log.Printf("Listen at %v\n", listenAddr)
log.Fatal(http.ListenAndServe(listenAddr, nil))
diff --git a/dr-feeder.go b/dr-feeder.go
index 584fb53..9b55edd 100644
--- a/dr-feeder.go
+++ b/dr-feeder.go
@@ -14,7 +14,7 @@ import (
)
// Version is current `git describe --tags` infomation.
-var Version string = "v2.2.0"
+var Version string = "v2.3.0"
func consume(ch chan common.NotifyPayload, notifiers []notifier.Notifier) {
for {
diff --git a/tests/siren/00.json b/tests/siren/00.json
new file mode 100644
index 0000000..0daf689
--- /dev/null
+++ b/tests/siren/00.json
@@ -0,0 +1 @@
+{"code":0,"msg":"","data":{"list":[{"cid":"750451","title":"Loyal to the beat正式上架","cate":1,"date":"2021-04-02"},{"cid":"241306","title":"#EMPEROR","cate":8,"date":"2021-04-01"},{"cid":"992679","title":"#4 主流音乐","cate":5,"date":"2021-03-29"},{"cid":"605964","title":"#3 混音技巧","cate":5,"date":"2021-03-28"},{"cid":"336215","title":"#2 音色偏好","cate":5,"date":"2021-03-27"},{"cid":"578837","title":"歌曲上线公告","cate":1,"date":"2021-03-26"},{"cid":"863542","title":"#1 试音环节","cate":5,"date":"2021-03-26"},{"cid":"114093","title":"官方致辞","cate":5,"date":"2021-03-23"}],"end":true}}
diff --git a/tests/siren/01.json b/tests/siren/01.json
new file mode 100644
index 0000000..b3962d5
--- /dev/null
+++ b/tests/siren/01.json
@@ -0,0 +1 @@
+{"code":0,"msg":"","data":{"list":[{"cid":"750450","title":"#1","cate":7,"date":"2021-04-02"},{"cid":"750451","title":"Loyal to the beat正式上架","cate":1,"date":"2021-04-02"},{"cid":"241306","title":"#EMPEROR","cate":8,"date":"2021-04-01"},{"cid":"992679","title":"#4 主流音乐","cate":5,"date":"2021-03-29"},{"cid":"605964","title":"#3 混音技巧","cate":5,"date":"2021-03-28"},{"cid":"336215","title":"#2 音色偏好","cate":5,"date":"2021-03-27"},{"cid":"578837","title":"歌曲上线公告","cate":1,"date":"2021-03-26"},{"cid":"863542","title":"#1 试音环节","cate":5,"date":"2021-03-26"},{"cid":"114093","title":"官方致辞","cate":5,"date":"2021-03-23"}],"end":true}}
diff --git a/tests/siren/750450.json b/tests/siren/750450.json
new file mode 100644
index 0000000..d7a3e59
--- /dev/null
+++ b/tests/siren/750450.json
@@ -0,0 +1 @@
+{"code":0,"msg":"","data":{"cid":"750450","title":"#1","cate":7,"author":"塞壬唱片","content":"
乐章裹暮色,约定在次月的余晖里。
待黑夜浸入大海,幕布开启,故人自远方归来。
使黑白染上彩色的,是与风浪合唱的,启航时的歌声。
","date":"2021-04-02"}}
\ No newline at end of file
diff --git a/watcher/siren.go b/watcher/siren.go
new file mode 100644
index 0000000..36420b7
--- /dev/null
+++ b/watcher/siren.go
@@ -0,0 +1,186 @@
+package watcher
+
+import (
+ "encoding/json"
+ "fmt"
+ "log"
+ "strings"
+
+ "github.com/antchfx/htmlquery"
+ "github.com/gocolly/colly/v2"
+ "github.com/hguandl/dr-feeder/v2/common"
+ "github.com/mitchellh/mapstructure"
+
+ bolt "go.etcd.io/bbolt"
+)
+
+type sirenWatcher struct {
+ name string
+ latestNews sirenNewsData
+ debugURL string
+ db *bolt.DB
+}
+
+// NewSirenWatcher creates a Watcher of news from Monster Siren.
+func NewSirenWatcher(dbPath string, debugURL string) (Watcher, error) {
+ var err error = nil
+
+ watcher := new(sirenWatcher)
+ watcher.name = "塞壬唱片"
+ watcher.debugURL = debugURL
+
+ watcher.db, err = bolt.Open(dbPath, 0666, nil)
+ if err != nil {
+ return watcher, err
+ }
+
+ err = watcher.setup()
+ return watcher, err
+}
+
+func (watcher sirenWatcher) apiURL(newsID string) string {
+ if watcher.debugURL != "" {
+ return watcher.debugURL
+ }
+ return fmt.Sprintf("%s%s",
+ "https://monster-siren.hypergryph.com/api/news/",
+ newsID,
+ )
+}
+
+func (watcher sirenWatcher) fetchAPI(newID string) (sirenAPIPayload, error) {
+ var err error = nil
+ var data sirenAPIPayload
+
+ c := colly.NewCollector(
+ colly.UserAgent(safariUA),
+ )
+
+ c.OnError(func(_ *colly.Response, e error) {
+ err = e
+ })
+
+ c.OnResponse(func(r *colly.Response) {
+ err = json.Unmarshal(r.Body, &data)
+ })
+
+ c.Visit(watcher.apiURL(newID))
+ c.Wait()
+
+ return data, err
+}
+
+func (watcher sirenWatcher) getNewsList() ([]sirenNewsData, error) {
+ data, err := watcher.fetchAPI("")
+ if err != nil {
+ return nil, err
+ }
+
+ var content sirenListData
+ err = mapstructure.Decode(data.Data, &content)
+ if err != nil {
+ return nil, err
+ }
+
+ return content.List, nil
+}
+
+func (watcher sirenWatcher) getNews(newsID string) (sirenNewsData, error) {
+ var content sirenNewsData
+ data, err := watcher.fetchAPI(newsID)
+ if err != nil {
+ return content, err
+ }
+
+ err = mapstructure.Decode(data.Data, &content)
+ if err != nil {
+ return content, err
+ }
+
+ return content, nil
+}
+
+func (watcher *sirenWatcher) setup() error {
+ newsList, err := watcher.getNewsList()
+ if err != nil {
+ return err
+ }
+
+ watcher.storeNews(newsList)
+
+ return nil
+}
+
+func (watcher *sirenWatcher) storeNews(newsList []sirenNewsData) error {
+ err := watcher.db.Update(func(tx *bolt.Tx) error {
+ b, err := tx.CreateBucketIfNotExists([]byte("Siren"))
+ for _, news := range newsList {
+ err = b.Put([]byte(news.Cid), []byte(news.Title))
+ }
+ return err
+ })
+
+ return err
+}
+
+func (watcher *sirenWatcher) update() bool {
+ newsList, err := watcher.getNewsList()
+ if err != nil {
+ log.Println(err)
+ return false
+ }
+
+ ret := false
+ err = watcher.db.Update(func(tx *bolt.Tx) error {
+ b, err := tx.CreateBucketIfNotExists([]byte("Siren"))
+ for _, news := range newsList {
+ v := b.Get([]byte(news.Cid))
+ if v == nil {
+ watcher.latestNews = news
+ ret = true
+ err = b.Put([]byte(news.Cid), []byte(news.Title))
+ break
+ }
+ }
+ return err
+ })
+
+ if err != nil {
+ log.Println(err)
+ return false
+ }
+
+ return ret
+}
+
+func (watcher sirenWatcher) parseContent() common.NotifyPayload {
+ news := watcher.latestNews
+ fullNews, err := watcher.getNews(news.Cid)
+ texts := news.Title + "\n"
+ if err == nil {
+ doc, _ := htmlquery.Parse(
+ strings.NewReader(fullNews.Content),
+ )
+ nodes, _ := htmlquery.QueryAll(doc, "//text()")
+
+ for _, node := range nodes {
+ texts += "\n"
+ texts += strings.Trim(node.Data, " \n")
+ }
+ }
+
+ return common.NotifyPayload{
+ Title: watcher.name,
+ Body: texts,
+ URL: fmt.Sprintf("%s%s", "https://monster-siren.hypergryph.com/info/", news.Cid),
+ }
+}
+
+func (watcher *sirenWatcher) Produce(ch chan common.NotifyPayload) {
+ if watcher.update() {
+ log.Printf("New post from \"%s\"...\n", watcher.name)
+ ch <- watcher.parseContent()
+ } else {
+ log.Printf("Waiting for post \"%s\"...\n", watcher.name)
+ }
+}
diff --git a/watcher/sirenmodels.go b/watcher/sirenmodels.go
new file mode 100644
index 0000000..52e3c47
--- /dev/null
+++ b/watcher/sirenmodels.go
@@ -0,0 +1,21 @@
+package watcher
+
+type sirenAPIPayload struct {
+ Code int `json:"code"`
+ Msg string `json:"msg"`
+ Data map[string]interface{} `json:"data"`
+}
+
+type sirenListData struct {
+ List []sirenNewsData `mapstructure:"list"`
+ End bool `mapstructure:"end"`
+}
+
+type sirenNewsData struct {
+ Cid string `mapstructure:"cid"`
+ Title string `mapstructure:"title"`
+ Cate int `mapstructure:"cate"`
+ Author string `mapstructure:"author,omitempty"`
+ Content string `mapstructure:"content,omitempty"`
+ Date string `mapstructure:"date"`
+}
diff --git a/watcher/watcher.go b/watcher/watcher.go
index 7b0047f..daea95c 100644
--- a/watcher/watcher.go
+++ b/watcher/watcher.go
@@ -24,6 +24,10 @@ type akAnnoConfig struct {
DebugURL string `mapstructure:"debug_url"`
}
+type sirenConfig struct {
+ DebugURL string `mapstructure:"debug_url"`
+}
+
func wrapDebug(debugURL string, debugMode bool) string {
if debugURL != "" {
if debugMode {
@@ -65,6 +69,12 @@ func ParseWatchers(configs []map[string]interface{}, dataPath string, debugMode
break
}
ret[idx], err = NewAkAnnounceWatcher(path.Join(dataPath, "akanno.db"), wrapDebug(akConfig.DebugURL, debugMode))
+ case "siren":
+ var akConfig sirenConfig
+ if err = mapstructure.Decode(config, &akConfig); err != nil {
+ break
+ }
+ ret[idx], err = NewSirenWatcher(path.Join(dataPath, "siren.db"), wrapDebug(akConfig.DebugURL, debugMode))
default:
err = fmt.Errorf("unknown watcher #%d with type \"%s\"", idx, watcherType)
}