From 97fb1e5afbfa2a4219c9af06c755475aa349a242 Mon Sep 17 00:00:00 2001 From: trinidz Date: Tue, 4 Jun 2024 10:00:51 +0000 Subject: [PATCH] use freshrss api to load rss feeds --- internal/feed/rss.go | 78 +++++++++++++++++++++++++++++++++++++ internal/widget/freshrss.go | 76 ++++++++++++++++++++++++++++++++++++ internal/widget/widget.go | 2 + 3 files changed, 156 insertions(+) create mode 100644 internal/widget/freshrss.go diff --git a/internal/feed/rss.go b/internal/feed/rss.go index 4d22af6e..6652f9f7 100644 --- a/internal/feed/rss.go +++ b/internal/feed/rss.go @@ -1,10 +1,15 @@ package feed import ( + "bytes" "context" + "crypto/md5" + "encoding/json" "fmt" "html" + "io" "log/slog" + "net/http" "net/url" "regexp" "sort" @@ -25,6 +30,29 @@ type RSSFeedItem struct { PublishedAt time.Time } +type FreshRssFeedsGroups struct { + Group_id int + Feed_ids string +} + +type FreshRssFeed struct { + Id int + Favicon_id int + Title string + Url string + Site_url string + Is_spark int + Last_updated_on_time int +} + +type FreshRSSFeedsAPI struct { + Api_version uint + Auth uint + Last_refreshed_on_time int + Feeds []FreshRssFeed + Feeds_groups []FreshRssFeedsGroups +} + // doesn't cover all cases but works the vast majority of the time var htmlTagsWithAttributesPattern = regexp.MustCompile(`<\/?[a-zA-Z0-9-]+ *(?:[a-zA-Z-]+=(?:"|').*?(?:"|') ?)* *\/?>`) var sequentialWhitespacePattern = regexp.MustCompile(`\s+`) @@ -195,3 +223,53 @@ func GetItemsFromRSSFeeds(requests []RSSFeedRequest) (RSSFeedItems, error) { return entries, nil } + +func GetItemsFromFreshRssFeeds(freshrssUrl string, freshrssUser string, freshrsspass string) (RSSFeedItems, error) { + var p FreshRSSFeedsAPI + var feedReqs []RSSFeedRequest + var param = url.Values{} + + user_credentials := []byte(fmt.Sprintf("%v:%v", freshrssUser, freshrsspass)) + api_key := fmt.Sprintf("%x", md5.Sum(user_credentials)) + + param.Set("api_key", api_key) + param.Set("feeds", "") + var payload = bytes.NewBufferString(param.Encode()) + + requestURL := fmt.Sprintf("%v/api/fever.php?api", freshrssUrl) + req, err := http.NewRequest(http.MethodPost, requestURL, payload) + + if err != nil { + return nil, fmt.Errorf("could not create freshRss request: %v ", err) + } + + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + client := http.Client{ + Timeout: 10 * time.Second, + } + + res, err := client.Do(req) + if err != nil || res.StatusCode != 200 { + return nil, fmt.Errorf("could not connect to freshRss instance: %v", err) + } + + resBody, err := io.ReadAll(res.Body) + if err != nil { + return nil, fmt.Errorf("could not read freshRss response body: %v", err) + } + + errr := json.Unmarshal(resBody, &p) + if errr != nil { + return nil, fmt.Errorf("could not unmarshal freshrss response body: %v", errr) + } + + for i := range p.Feeds { + var feedReq RSSFeedRequest + feedReq.Url = p.Feeds[i].Url + feedReq.Title = p.Feeds[i].Title + feedReqs = append(feedReqs, feedReq) + } + + return GetItemsFromRSSFeeds(feedReqs) +} diff --git a/internal/widget/freshrss.go b/internal/widget/freshrss.go new file mode 100644 index 00000000..63f51336 --- /dev/null +++ b/internal/widget/freshrss.go @@ -0,0 +1,76 @@ +package widget + +import ( + "context" + "html/template" + "time" + + "github.com/glanceapp/glance/internal/assets" + "github.com/glanceapp/glance/internal/feed" +) + +type FreshRSS struct { + widgetBase `yaml:",inline"` + FeedRequests []feed.RSSFeedRequest `yaml:"feeds"` + Style string `yaml:"style"` + ThumbnailHeight float64 `yaml:"thumbnail-height"` + CardHeight float64 `yaml:"card-height"` + Items feed.RSSFeedItems `yaml:"-"` + Limit int `yaml:"limit"` + CollapseAfter int `yaml:"collapse-after"` + FreshRSSUrl string `yaml:"freshrss-url"` + FreshRSSUser string `yaml:"freshrss-user"` + FreshRSSApiPass string `yaml:"freshrss-api-pass"` +} + +func (widget *FreshRSS) Initialize() error { + widget.withTitle("FreshRSS Feed").withCacheDuration(1 * time.Hour) + + if widget.Limit <= 0 { + widget.Limit = 25 + } + + if widget.CollapseAfter == 0 || widget.CollapseAfter < -1 { + widget.CollapseAfter = 5 + } + + if widget.ThumbnailHeight < 0 { + widget.ThumbnailHeight = 0 + } + + if widget.CardHeight < 0 { + widget.CardHeight = 0 + } + + return nil +} + +func (widget *FreshRSS) Update(ctx context.Context) { + + var items feed.RSSFeedItems + var err error + + items, err = feed.GetItemsFromFreshRssFeeds(widget.FreshRSSUrl, widget.FreshRSSUser, widget.FreshRSSApiPass) + + if !widget.canContinueUpdateAfterHandlingErr(err) { + return + } + + if len(items) > widget.Limit { + items = items[:widget.Limit] + } + + widget.Items = items +} + +func (widget *FreshRSS) Render() template.HTML { + if widget.Style == "horizontal-cards" { + return widget.render(widget, assets.RSSHorizontalCardsTemplate) + } + + if widget.Style == "horizontal-cards-2" { + return widget.render(widget, assets.RSSHorizontalCards2Template) + } + + return widget.render(widget, assets.RSSListTemplate) +} diff --git a/internal/widget/widget.go b/internal/widget/widget.go index 0ccb3de4..ce9c1a8e 100644 --- a/internal/widget/widget.go +++ b/internal/widget/widget.go @@ -41,6 +41,8 @@ func New(widgetType string) (Widget, error) { return &Reddit{}, nil case "rss": return &RSS{}, nil + case "freshrss": + return &FreshRSS{}, nil case "monitor": return &Monitor{}, nil case "twitch-top-games":