notify.go 1.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. package rss
  2. import (
  3. "context"
  4. "net/http"
  5. "sync"
  6. "time"
  7. log "github.com/go-pkgz/lgr"
  8. "github.com/mmcdole/gofeed"
  9. )
  10. // Notify on RSS change
  11. type Notify struct {
  12. Feed string
  13. Duration time.Duration
  14. Timeout time.Duration
  15. once sync.Once
  16. ctx context.Context
  17. cancel context.CancelFunc
  18. }
  19. // Event from RSS
  20. type Event struct {
  21. ChanTitle string
  22. Title string
  23. Link string
  24. guid string
  25. }
  26. // Go starts notifier and returns events channel
  27. func (n *Notify) Go(ctx context.Context) <-chan Event {
  28. log.Printf("[INFO] start notifier for %s, every %s", n.Feed, n.Duration)
  29. n.once.Do(func() { n.ctx, n.cancel = context.WithCancel(ctx) })
  30. ch := make(chan Event)
  31. // wait for duration, can be terminated by ctx
  32. waitOrCancel := func(ctx context.Context) bool {
  33. select {
  34. case <-ctx.Done():
  35. return false
  36. case <-time.After(n.Duration):
  37. return true
  38. }
  39. }
  40. go func() {
  41. defer func() {
  42. close(ch)
  43. n.cancel()
  44. }()
  45. fp := gofeed.NewParser()
  46. fp.Client = &http.Client{Timeout: n.Timeout}
  47. lastGUID := ""
  48. for {
  49. feedData, err := fp.ParseURL(n.Feed)
  50. if err != nil {
  51. log.Printf("[WARN] failed to fetch from %s, %v", n.Feed, err)
  52. if !waitOrCancel(n.ctx) {
  53. return
  54. }
  55. continue
  56. }
  57. event := n.feedEvent(feedData)
  58. if lastGUID != event.guid {
  59. if lastGUID != "" { // don't notify on initial change
  60. log.Printf("[INFO] new event %s - %s", event.guid, event.Title)
  61. ch <- event
  62. }
  63. lastGUID = event.guid
  64. }
  65. if !waitOrCancel(n.ctx) {
  66. return
  67. }
  68. }
  69. }()
  70. return ch
  71. }
  72. // Shutdown notifier
  73. func (n *Notify) Shutdown() {
  74. n.cancel()
  75. <-n.ctx.Done()
  76. }
  77. // feedEvent gets latest item from rss feed
  78. func (n *Notify) feedEvent(feed *gofeed.Feed) (e Event) {
  79. e.ChanTitle = feed.Title
  80. if len(feed.Items) > 0 {
  81. e.Title = feed.Items[0].Title
  82. e.Link = feed.Items[0].Link
  83. e.guid = feed.Items[0].GUID
  84. }
  85. return e
  86. }