1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
|
package source
import (
"context"
"errors"
"fmt"
"log/slog"
"time"
"github.com/mmcdole/gofeed"
cm "go.neonxp.ru/conf/model"
"go.neonxp.ru/pose/internal/model"
)
var (
ErrNoFeedAddress = errors.New("no feed address")
)
type Feed struct {
logger *slog.Logger
feed string
scrapeInterval time.Duration
feedParser *gofeed.Parser
}
func NewFeed(cfg cm.Group, logger *slog.Logger) (*Feed, error) {
feedSource := cfg.Get("url").String()
if feedSource == "" {
return nil, ErrNoFeedAddress
}
durStr := cfg.Get("scrape_interval").String()
if durStr == "" {
durStr = "10m"
}
dur, err := time.ParseDuration(durStr)
if err != nil {
return nil, fmt.Errorf("failed parse duration at source: %w", err)
}
return &Feed{
feed: feedSource,
scrapeInterval: dur,
feedParser: gofeed.NewParser(),
}, nil
}
func (a *Feed) Retrive(ctx context.Context) <-chan model.Item {
out := make(chan model.Item)
ticker := time.NewTicker(a.scrapeInterval)
go func() {
<-ctx.Done()
ticker.Stop()
}()
go func() {
defer close(out)
for _, item := range a.scrapeFeed(ctx) {
out <- item
}
for range ticker.C {
for _, item := range a.scrapeFeed(ctx) {
out <- item
}
}
}()
return out
}
func (a *Feed) scrapeFeed(ctx context.Context) []model.Item {
feed, err := a.feedParser.ParseURLWithContext(a.feed, ctx)
if err != nil {
a.logger.Error("failed parse feed", slog.Any("err", err))
return nil
}
result := make([]model.Item, 0, len(feed.Items))
for _, it := range feed.Items {
date := time.Now()
if it.PublishedParsed != nil {
date = *it.PublishedParsed
}
image := ""
if it.Image != nil {
image = it.Image.URL
}
result = append(result, model.Item{
ID: it.GUID,
Date: date,
Title: it.Title,
Summary: it.Description,
Link: it.Link,
Img: image,
})
}
return result
}
|