diff --git a/model/feed.go b/model/feed.go index de93f2b6..3294c0d6 100644 --- a/model/feed.go +++ b/model/feed.go @@ -52,6 +52,7 @@ type Feed struct { FetchViaProxy bool `json:"fetch_via_proxy"` Category *Category `json:"category,omitempty"` Entries Entries `json:"entries,omitempty"` + IconURL string `json:"icon_url"` Icon *FeedIcon `json:"icon"` HideGlobally bool `json:"hide_globally"` UnreadCount int `json:"-"` diff --git a/reader/atom/atom_10.go b/reader/atom/atom_10.go index 344b0aba..350c0660 100644 --- a/reader/atom/atom_10.go +++ b/reader/atom/atom_10.go @@ -28,6 +28,7 @@ type atom10Feed struct { ID string `xml:"id"` Title atom10Text `xml:"title"` Authors atomAuthors `xml:"author"` + Icon string `xml:"icon"` Links atomLinks `xml:"link"` Entries []atom10Entry `xml:"entry"` } @@ -54,6 +55,8 @@ func (a *atom10Feed) Transform(baseURL string) *model.Feed { feed.Title = feed.SiteURL } + feed.IconURL = strings.TrimSpace(a.Icon) + for _, entry := range a.Entries { item := entry.Transform() entryURL, err := url.AbsoluteURL(feed.SiteURL, item.URL) diff --git a/reader/handler/handler.go b/reader/handler/handler.go index be9d6a63..09445766 100644 --- a/reader/handler/handler.go +++ b/reader/handler/handler.go @@ -96,6 +96,7 @@ func CreateFeed(store *storage.Storage, userID int64, feedCreationRequest *model store, subscription.ID, subscription.SiteURL, + subscription.IconURL, feedCreationRequest.UserAgent, feedCreationRequest.FetchViaProxy, feedCreationRequest.AllowSelfSignedCertificates, @@ -189,6 +190,7 @@ func RefreshFeed(store *storage.Storage, userID, feedID int64) error { store, originalFeed.ID, originalFeed.SiteURL, + updatedFeed.IconURL, originalFeed.UserAgent, originalFeed.FetchViaProxy, originalFeed.AllowSelfSignedCertificates, @@ -208,9 +210,9 @@ func RefreshFeed(store *storage.Storage, userID, feedID int64) error { return nil } -func checkFeedIcon(store *storage.Storage, feedID int64, websiteURL, userAgent string, fetchViaProxy, allowSelfSignedCertificates bool) { +func checkFeedIcon(store *storage.Storage, feedID int64, websiteURL, iconURL, userAgent string, fetchViaProxy, allowSelfSignedCertificates bool) { if !store.HasIcon(feedID) { - icon, err := icon.FindIcon(websiteURL, userAgent, fetchViaProxy, allowSelfSignedCertificates) + icon, err := icon.FindIcon(websiteURL, iconURL, userAgent, fetchViaProxy, allowSelfSignedCertificates) if err != nil { logger.Debug(`[CheckFeedIcon] %v (feedID=%d websiteURL=%s)`, err, feedID, websiteURL) } else if icon == nil { diff --git a/reader/icon/finder.go b/reader/icon/finder.go index a3985b3f..df583240 100644 --- a/reader/icon/finder.go +++ b/reader/icon/finder.go @@ -23,30 +23,32 @@ import ( ) // FindIcon try to find the website's icon. -func FindIcon(websiteURL, userAgent string, fetchViaProxy, allowSelfSignedCertificates bool) (*model.Icon, error) { - rootURL := url.RootURL(websiteURL) - logger.Debug("[FindIcon] Trying to find an icon: rootURL=%q websiteURL=%q userAgent=%q", rootURL, websiteURL, userAgent) +func FindIcon(websiteURL, iconURL, userAgent string, fetchViaProxy, allowSelfSignedCertificates bool) (*model.Icon, error) { + if iconURL == "" { + rootURL := url.RootURL(websiteURL) + logger.Debug("[FindIcon] Trying to find an icon: rootURL=%q websiteURL=%q userAgent=%q", rootURL, websiteURL, userAgent) - clt := client.NewClientWithConfig(rootURL, config.Opts) - clt.WithUserAgent(userAgent) - clt.AllowSelfSignedCertificates = allowSelfSignedCertificates + clt := client.NewClientWithConfig(rootURL, config.Opts) + clt.WithUserAgent(userAgent) + clt.AllowSelfSignedCertificates = allowSelfSignedCertificates - if fetchViaProxy { - clt.WithProxy() - } + if fetchViaProxy { + clt.WithProxy() + } - response, err := clt.Get() - if err != nil { - return nil, fmt.Errorf("icon: unable to download website index page: %v", err) - } + response, err := clt.Get() + if err != nil { + return nil, fmt.Errorf("icon: unable to download website index page: %v", err) + } - if response.HasServerFailure() { - return nil, fmt.Errorf("icon: unable to download website index page: status=%d", response.StatusCode) - } + if response.HasServerFailure() { + return nil, fmt.Errorf("icon: unable to download website index page: status=%d", response.StatusCode) + } - iconURL, err := parseDocument(rootURL, response.Body) - if err != nil { - return nil, err + iconURL, err = parseDocument(rootURL, response.Body) + if err != nil { + return nil, err + } } if strings.HasPrefix(iconURL, "data:") { diff --git a/reader/json/json.go b/reader/json/json.go index 759b10c7..faddbafb 100644 --- a/reader/json/json.go +++ b/reader/json/json.go @@ -17,13 +17,15 @@ import ( ) type jsonFeed struct { - Version string `json:"version"` - Title string `json:"title"` - SiteURL string `json:"home_page_url"` - FeedURL string `json:"feed_url"` - Authors []jsonAuthor `json:"authors"` - Author jsonAuthor `json:"author"` - Items []jsonItem `json:"items"` + Version string `json:"version"` + Title string `json:"title"` + SiteURL string `json:"home_page_url"` + IconURL string `json:"icon"` + FaviconURL string `json:"favicon"` + FeedURL string `json:"feed_url"` + Authors []jsonAuthor `json:"authors"` + Author jsonAuthor `json:"author"` + Items []jsonItem `json:"items"` } type jsonAuthor struct { @@ -76,6 +78,12 @@ func (j *jsonFeed) Transform(baseURL string) *model.Feed { feed.SiteURL = j.SiteURL } + feed.IconURL = strings.TrimSpace(j.IconURL) + + if feed.IconURL == "" { + feed.IconURL = strings.TrimSpace(j.FaviconURL) + } + feed.Title = strings.TrimSpace(j.Title) if feed.Title == "" { feed.Title = feed.SiteURL diff --git a/reader/json/parser_test.go b/reader/json/parser_test.go index 186baca6..b488acc3 100644 --- a/reader/json/parser_test.go +++ b/reader/json/parser_test.go @@ -15,6 +15,8 @@ func TestParseJsonFeed(t *testing.T) { data := `{ "version": "https://jsonfeed.org/version/1", "title": "My Example Feed", + "icon": "https://micro.blog/jsonfeed/avatar.jpg", + "favicon": "https://micro.blog/jsonfeed/favicon.png", "home_page_url": "https://example.org/", "feed_url": "https://example.org/feed.json", "items": [ @@ -48,6 +50,10 @@ func TestParseJsonFeed(t *testing.T) { t.Errorf("Incorrect site URL, got: %s", feed.SiteURL) } + if feed.IconURL != "https://micro.blog/jsonfeed/avatar.jpg" { + t.Errorf("Incorrect icon URL, got: %s", feed.IconURL) + } + if len(feed.Entries) != 2 { t.Errorf("Incorrect number of entries, got: %d", len(feed.Entries)) } @@ -617,3 +623,33 @@ func TestParseTags(t *testing.T) { t.Errorf("Incorrect entry tag, got %q instead of %q", result, expected) } } + +func TestParseFavicon(t *testing.T) { + data := `{ + "version": "https://jsonfeed.org/version/1", + "title": "My Example Feed", + "favicon": "https://micro.blog/jsonfeed/favicon.png", + "home_page_url": "https://example.org/", + "feed_url": "https://example.org/feed.json", + "items": [ + { + "id": "2", + "content_text": "This is a second item.", + "url": "https://example.org/second-item" + }, + { + "id": "1", + "content_html": "

Hello, world!

", + "url": "https://example.org/initial-post" + } + ] + }` + + feed, err := Parse("https://example.org/feed.json", bytes.NewBufferString(data)) + if err != nil { + t.Fatal(err) + } + if feed.IconURL != "https://micro.blog/jsonfeed/favicon.png" { + t.Errorf("Incorrect icon URL, got: %s", feed.IconURL) + } +} diff --git a/reader/rss/parser_test.go b/reader/rss/parser_test.go index d0662c60..20f885f1 100644 --- a/reader/rss/parser_test.go +++ b/reader/rss/parser_test.go @@ -18,6 +18,11 @@ func TestParseRss2Sample(t *testing.T) { Liftoff News http://liftoff.msfc.nasa.gov/ Liftoff to Space Exploration. + + http://liftoff.msfc.nasa.gov/HomePageXtra/MeatBall.gif + NASA + http://liftoff.msfc.nasa.gov/ + en-us Tue, 10 Jun 2003 04:00:00 GMT Tue, 10 Jun 2003 09:41:01 GMT @@ -71,6 +76,10 @@ func TestParseRss2Sample(t *testing.T) { t.Errorf("Incorrect site URL, got: %s", feed.SiteURL) } + if feed.IconURL != "http://liftoff.msfc.nasa.gov/HomePageXtra/MeatBall.gif" { + t.Errorf("Incorrect image URL, got: %s", feed.IconURL) + } + if len(feed.Entries) != 4 { t.Errorf("Incorrect number of entries, got: %d", len(feed.Entries)) } diff --git a/reader/rss/rss.go b/reader/rss/rss.go index 2969f6bc..b8c7cf4c 100644 --- a/reader/rss/rss.go +++ b/reader/rss/rss.go @@ -27,6 +27,7 @@ type rssFeed struct { Version string `xml:"version,attr"` Title string `xml:"channel>title"` Links []rssLink `xml:"channel>link"` + ImageURL string `xml:"channel>image>url"` Language string `xml:"channel>language"` Description string `xml:"channel>description"` PubDate string `xml:"channel>pubDate"` @@ -58,6 +59,8 @@ func (r *rssFeed) Transform(baseURL string) *model.Feed { feed.Title = feed.SiteURL } + feed.IconURL = strings.TrimSpace(r.ImageURL) + for _, item := range r.Items { entry := item.Transform() if entry.Author == "" {