Use image included in feed as feed icon

This commit is contained in:
Ryan Stafford 2023-06-04 18:01:59 -04:00 committed by GitHub
parent 228bb62df4
commit 1aeb1b20da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 92 additions and 28 deletions

View File

@ -52,6 +52,7 @@ type Feed struct {
FetchViaProxy bool `json:"fetch_via_proxy"` FetchViaProxy bool `json:"fetch_via_proxy"`
Category *Category `json:"category,omitempty"` Category *Category `json:"category,omitempty"`
Entries Entries `json:"entries,omitempty"` Entries Entries `json:"entries,omitempty"`
IconURL string `json:"icon_url"`
Icon *FeedIcon `json:"icon"` Icon *FeedIcon `json:"icon"`
HideGlobally bool `json:"hide_globally"` HideGlobally bool `json:"hide_globally"`
UnreadCount int `json:"-"` UnreadCount int `json:"-"`

View File

@ -28,6 +28,7 @@ type atom10Feed struct {
ID string `xml:"id"` ID string `xml:"id"`
Title atom10Text `xml:"title"` Title atom10Text `xml:"title"`
Authors atomAuthors `xml:"author"` Authors atomAuthors `xml:"author"`
Icon string `xml:"icon"`
Links atomLinks `xml:"link"` Links atomLinks `xml:"link"`
Entries []atom10Entry `xml:"entry"` Entries []atom10Entry `xml:"entry"`
} }
@ -54,6 +55,8 @@ func (a *atom10Feed) Transform(baseURL string) *model.Feed {
feed.Title = feed.SiteURL feed.Title = feed.SiteURL
} }
feed.IconURL = strings.TrimSpace(a.Icon)
for _, entry := range a.Entries { for _, entry := range a.Entries {
item := entry.Transform() item := entry.Transform()
entryURL, err := url.AbsoluteURL(feed.SiteURL, item.URL) entryURL, err := url.AbsoluteURL(feed.SiteURL, item.URL)

View File

@ -96,6 +96,7 @@ func CreateFeed(store *storage.Storage, userID int64, feedCreationRequest *model
store, store,
subscription.ID, subscription.ID,
subscription.SiteURL, subscription.SiteURL,
subscription.IconURL,
feedCreationRequest.UserAgent, feedCreationRequest.UserAgent,
feedCreationRequest.FetchViaProxy, feedCreationRequest.FetchViaProxy,
feedCreationRequest.AllowSelfSignedCertificates, feedCreationRequest.AllowSelfSignedCertificates,
@ -189,6 +190,7 @@ func RefreshFeed(store *storage.Storage, userID, feedID int64) error {
store, store,
originalFeed.ID, originalFeed.ID,
originalFeed.SiteURL, originalFeed.SiteURL,
updatedFeed.IconURL,
originalFeed.UserAgent, originalFeed.UserAgent,
originalFeed.FetchViaProxy, originalFeed.FetchViaProxy,
originalFeed.AllowSelfSignedCertificates, originalFeed.AllowSelfSignedCertificates,
@ -208,9 +210,9 @@ func RefreshFeed(store *storage.Storage, userID, feedID int64) error {
return nil 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) { if !store.HasIcon(feedID) {
icon, err := icon.FindIcon(websiteURL, userAgent, fetchViaProxy, allowSelfSignedCertificates) icon, err := icon.FindIcon(websiteURL, iconURL, userAgent, fetchViaProxy, allowSelfSignedCertificates)
if err != nil { if err != nil {
logger.Debug(`[CheckFeedIcon] %v (feedID=%d websiteURL=%s)`, err, feedID, websiteURL) logger.Debug(`[CheckFeedIcon] %v (feedID=%d websiteURL=%s)`, err, feedID, websiteURL)
} else if icon == nil { } else if icon == nil {

View File

@ -23,30 +23,32 @@ import (
) )
// FindIcon try to find the website's icon. // FindIcon try to find the website's icon.
func FindIcon(websiteURL, userAgent string, fetchViaProxy, allowSelfSignedCertificates bool) (*model.Icon, error) { func FindIcon(websiteURL, iconURL, userAgent string, fetchViaProxy, allowSelfSignedCertificates bool) (*model.Icon, error) {
rootURL := url.RootURL(websiteURL) if iconURL == "" {
logger.Debug("[FindIcon] Trying to find an icon: rootURL=%q websiteURL=%q userAgent=%q", rootURL, websiteURL, userAgent) 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 := client.NewClientWithConfig(rootURL, config.Opts)
clt.WithUserAgent(userAgent) clt.WithUserAgent(userAgent)
clt.AllowSelfSignedCertificates = allowSelfSignedCertificates clt.AllowSelfSignedCertificates = allowSelfSignedCertificates
if fetchViaProxy { if fetchViaProxy {
clt.WithProxy() clt.WithProxy()
} }
response, err := clt.Get() response, err := clt.Get()
if err != nil { if err != nil {
return nil, fmt.Errorf("icon: unable to download website index page: %v", err) return nil, fmt.Errorf("icon: unable to download website index page: %v", err)
} }
if response.HasServerFailure() { if response.HasServerFailure() {
return nil, fmt.Errorf("icon: unable to download website index page: status=%d", response.StatusCode) return nil, fmt.Errorf("icon: unable to download website index page: status=%d", response.StatusCode)
} }
iconURL, err := parseDocument(rootURL, response.Body) iconURL, err = parseDocument(rootURL, response.Body)
if err != nil { if err != nil {
return nil, err return nil, err
}
} }
if strings.HasPrefix(iconURL, "data:") { if strings.HasPrefix(iconURL, "data:") {

View File

@ -17,13 +17,15 @@ import (
) )
type jsonFeed struct { type jsonFeed struct {
Version string `json:"version"` Version string `json:"version"`
Title string `json:"title"` Title string `json:"title"`
SiteURL string `json:"home_page_url"` SiteURL string `json:"home_page_url"`
FeedURL string `json:"feed_url"` IconURL string `json:"icon"`
Authors []jsonAuthor `json:"authors"` FaviconURL string `json:"favicon"`
Author jsonAuthor `json:"author"` FeedURL string `json:"feed_url"`
Items []jsonItem `json:"items"` Authors []jsonAuthor `json:"authors"`
Author jsonAuthor `json:"author"`
Items []jsonItem `json:"items"`
} }
type jsonAuthor struct { type jsonAuthor struct {
@ -76,6 +78,12 @@ func (j *jsonFeed) Transform(baseURL string) *model.Feed {
feed.SiteURL = j.SiteURL 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) feed.Title = strings.TrimSpace(j.Title)
if feed.Title == "" { if feed.Title == "" {
feed.Title = feed.SiteURL feed.Title = feed.SiteURL

View File

@ -15,6 +15,8 @@ func TestParseJsonFeed(t *testing.T) {
data := `{ data := `{
"version": "https://jsonfeed.org/version/1", "version": "https://jsonfeed.org/version/1",
"title": "My Example Feed", "title": "My Example Feed",
"icon": "https://micro.blog/jsonfeed/avatar.jpg",
"favicon": "https://micro.blog/jsonfeed/favicon.png",
"home_page_url": "https://example.org/", "home_page_url": "https://example.org/",
"feed_url": "https://example.org/feed.json", "feed_url": "https://example.org/feed.json",
"items": [ "items": [
@ -48,6 +50,10 @@ func TestParseJsonFeed(t *testing.T) {
t.Errorf("Incorrect site URL, got: %s", feed.SiteURL) 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 { if len(feed.Entries) != 2 {
t.Errorf("Incorrect number of entries, got: %d", len(feed.Entries)) 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) 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": "<p>Hello, world!</p>",
"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)
}
}

View File

@ -18,6 +18,11 @@ func TestParseRss2Sample(t *testing.T) {
<title>Liftoff News</title> <title>Liftoff News</title>
<link>http://liftoff.msfc.nasa.gov/</link> <link>http://liftoff.msfc.nasa.gov/</link>
<description>Liftoff to Space Exploration.</description> <description>Liftoff to Space Exploration.</description>
<image>
<url>http://liftoff.msfc.nasa.gov/HomePageXtra/MeatBall.gif</url>
<title>NASA</title>
<link>http://liftoff.msfc.nasa.gov/</link>
</image>
<language>en-us</language> <language>en-us</language>
<pubDate>Tue, 10 Jun 2003 04:00:00 GMT</pubDate> <pubDate>Tue, 10 Jun 2003 04:00:00 GMT</pubDate>
<lastBuildDate>Tue, 10 Jun 2003 09:41:01 GMT</lastBuildDate> <lastBuildDate>Tue, 10 Jun 2003 09:41:01 GMT</lastBuildDate>
@ -71,6 +76,10 @@ func TestParseRss2Sample(t *testing.T) {
t.Errorf("Incorrect site URL, got: %s", feed.SiteURL) 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 { if len(feed.Entries) != 4 {
t.Errorf("Incorrect number of entries, got: %d", len(feed.Entries)) t.Errorf("Incorrect number of entries, got: %d", len(feed.Entries))
} }

View File

@ -27,6 +27,7 @@ type rssFeed struct {
Version string `xml:"version,attr"` Version string `xml:"version,attr"`
Title string `xml:"channel>title"` Title string `xml:"channel>title"`
Links []rssLink `xml:"channel>link"` Links []rssLink `xml:"channel>link"`
ImageURL string `xml:"channel>image>url"`
Language string `xml:"channel>language"` Language string `xml:"channel>language"`
Description string `xml:"channel>description"` Description string `xml:"channel>description"`
PubDate string `xml:"channel>pubDate"` PubDate string `xml:"channel>pubDate"`
@ -58,6 +59,8 @@ func (r *rssFeed) Transform(baseURL string) *model.Feed {
feed.Title = feed.SiteURL feed.Title = feed.SiteURL
} }
feed.IconURL = strings.TrimSpace(r.ImageURL)
for _, item := range r.Items { for _, item := range r.Items {
entry := item.Transform() entry := item.Transform()
if entry.Author == "" { if entry.Author == "" {