From b03f86861fae63e7e3ff36be0d5f88daefed554a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Guillot?= Date: Fri, 26 Sep 2025 20:01:30 -0700 Subject: [PATCH] fix(jsonfeed): fallback to `external_url` when `url` is missing --- internal/reader/json/adapter.go | 13 +++++-- internal/reader/json/parser_test.go | 57 +++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/internal/reader/json/adapter.go b/internal/reader/json/adapter.go index e1a8bdda..eb2b5552 100644 --- a/internal/reader/json/adapter.go +++ b/internal/reader/json/adapter.go @@ -68,11 +68,16 @@ func (j *JSONAdapter) BuildFeed(baseURL string) *model.Feed { for _, item := range j.jsonFeed.Items { entry := model.NewEntry() entry.Title = strings.TrimSpace(item.Title) - entry.URL = strings.TrimSpace(item.URL) - // Make sure the entry URL is absolute. - if entryURL, err := urllib.AbsoluteURL(feed.SiteURL, entry.URL); err == nil { - entry.URL = entryURL + for _, itemURL := range []string{item.URL, item.ExternalURL} { + itemURL = strings.TrimSpace(itemURL) + if itemURL != "" { + // Make sure the entry URL is absolute. + if entryURL, err := urllib.AbsoluteURL(feed.SiteURL, itemURL); err == nil { + entry.URL = entryURL + } + break + } } // The entry title is optional, so we need to find a fallback. diff --git a/internal/reader/json/parser_test.go b/internal/reader/json/parser_test.go index dc628527..9b450569 100644 --- a/internal/reader/json/parser_test.go +++ b/internal/reader/json/parser_test.go @@ -415,6 +415,63 @@ func TestParseItemWithRelativeURL(t *testing.T) { } } +func TestParseItemWithExternalURLAndNoURL(t *testing.T) { + data := `{ + "version": "https://jsonfeed.org/version/1", + "title": "Example", + "home_page_url": "https://example.org/", + "feed_url": "https://example.org/feed.json", + "items": [ + { + "id": "1234259", + "external_url": "some_page.html" + } + ] + }` + + feed, err := Parse("https://example.org/feed.json", bytes.NewBufferString(data)) + if err != nil { + t.Fatal(err) + } + + if len(feed.Entries) != 1 { + t.Fatalf("Incorrect number of entries, got: %d", len(feed.Entries)) + } + + if feed.Entries[0].URL != "https://example.org/some_page.html" { + t.Errorf("Incorrect entry URL, got: %s", feed.Entries[0].URL) + } +} + +func TestParseItemWithExternalURLAndURL(t *testing.T) { + data := `{ + "version": "https://jsonfeed.org/version/1", + "title": "Example", + "home_page_url": "https://example.org/", + "feed_url": "https://example.org/feed.json", + "items": [ + { + "id": "1234259", + "url": "https://example.org/article", + "external_url": "https://example.org/another-article" + } + ] + }` + + feed, err := Parse("https://example.org/feed.json", bytes.NewBufferString(data)) + if err != nil { + t.Fatal(err) + } + + if len(feed.Entries) != 1 { + t.Fatalf("Incorrect number of entries, got: %d", len(feed.Entries)) + } + + if feed.Entries[0].URL != "https://example.org/article" { + t.Errorf("Incorrect entry URL, got: %s", feed.Entries[0].URL) + } +} + func TestParseItemWithLegacyAuthorField(t *testing.T) { data := `{ "version": "https://jsonfeed.org/version/1",