Browse Source

Use image included in feed as feed icon

Ryan Stafford 2 years ago
parent
commit
1aeb1b20da

+ 1 - 0
model/feed.go

@@ -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:"-"`

+ 3 - 0
reader/atom/atom_10.go

@@ -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)

+ 4 - 2
reader/handler/handler.go

@@ -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 {

+ 21 - 19
reader/icon/finder.go

@@ -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) {
-	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:") {
 	if strings.HasPrefix(iconURL, "data:") {

+ 15 - 7
reader/json/json.go

@@ -17,13 +17,15 @@ import (
 )
 )
 
 
 type jsonFeed struct {
 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 {
 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

+ 36 - 0
reader/json/parser_test.go

@@ -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)
+	}
+}

+ 9 - 0
reader/rss/parser_test.go

@@ -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))
 	}
 	}

+ 3 - 0
reader/rss/rss.go

@@ -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 == "" {