diff --git a/go.mod b/go.mod index 17da617..8daaf3c 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/michael/flowreader go 1.24.0 require ( + github.com/PuerkitoBio/goquery v1.8.0 github.com/go-chi/chi/v5 v5.0.11 github.com/google/uuid v1.6.0 github.com/gorilla/websocket v1.5.3 @@ -13,7 +14,6 @@ require ( ) require ( - github.com/PuerkitoBio/goquery v1.8.0 // indirect github.com/andybalholm/cascadia v1.3.1 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/gorilla/css v1.0.1 // indirect diff --git a/internal/parser/parser.go b/internal/parser/parser.go index 09f079c..0eb1748 100644 --- a/internal/parser/parser.go +++ b/internal/parser/parser.go @@ -7,6 +7,9 @@ import ( "net/http" "time" + "strings" + + "github.com/PuerkitoBio/goquery" "github.com/google/uuid" "github.com/michael/flowreader/internal/domain" "github.com/mmcdole/gofeed" @@ -108,6 +111,8 @@ func (p *FeedParser) Parse(ctx context.Context, feedURL string, feedID uuid.UUID if item.Image != nil && item.Image.URL != "" { article.ImageURL = item.Image.URL + } else { + article.ImageURL = findImage(item) } if item.PublishedParsed != nil { @@ -134,3 +139,44 @@ func getGUID(item *gofeed.Item) string { } return item.Title // Last resort fallback } + +// findImage attempts to find the best image for a feed item. +func findImage(item *gofeed.Item) string { + // 1. Check Enclosures + for _, enc := range item.Enclosures { + if strings.HasPrefix(enc.Type, "image/") { + return enc.URL + } + } + + // 2. Check Media Extensions (media:content, media:thumbnail) + if media, ok := item.Extensions["media"]; ok { + if content, ok := media["content"]; ok && len(content) > 0 { + if url := content[0].Attrs["url"]; url != "" { + return url + } + } + if thumbnail, ok := media["thumbnail"]; ok && len(thumbnail) > 0 { + if url := thumbnail[0].Attrs["url"]; url != "" { + return url + } + } + } + + // 3. Extract from Content/Description as fallback + htmlContent := item.Content + if htmlContent == "" { + htmlContent = item.Description + } + + if htmlContent != "" { + doc, err := goquery.NewDocumentFromReader(strings.NewReader(htmlContent)) + if err == nil { + if imgURL, exists := doc.Find("img").First().Attr("src"); exists { + return imgURL + } + } + } + + return "" +} diff --git a/web/src/components/ArticleCard.tsx b/web/src/components/ArticleCard.tsx index cda29e0..1f72441 100644 --- a/web/src/components/ArticleCard.tsx +++ b/web/src/components/ArticleCard.tsx @@ -16,8 +16,14 @@ export function ArticleCard({ article, onClick, onToggleRead, onToggleFavorite }
- {/* Visual placeholder */} -
+ {/* Visual placeholder or real image */} +
{/* Meta on top of image */}