diff --git a/crawler.go b/crawler.go index ad6f457..013e0f4 100644 --- a/crawler.go +++ b/crawler.go @@ -227,7 +227,15 @@ func (c *Crawler) StartPublishLoop() { if len(description) > 256 { description = description[:253] + "..." } - if err := publisher.UpdateProfile(session, displayName, description, nil); err != nil { + // Fetch and upload favicon as avatar + var avatar *BlobRef + if feedInfo.SiteURL != "" { + faviconURL := publisher.FetchFavicon(feedInfo.SiteURL) + if faviconURL != "" { + avatar = publisher.fetchAndUploadImage(session, faviconURL) + } + } + if err := publisher.UpdateProfile(session, displayName, description, avatar); err != nil { fmt.Printf("Publish: failed to set profile for %s: %v\n", account, err) } else { fmt.Printf("Publish: set profile for %s\n", account) @@ -297,7 +305,7 @@ func (c *Crawler) getFeedInfo(feedURL string) *FeedInfo { // RefreshAllProfiles updates profiles for all existing accounts with feed URLs func (c *Crawler) RefreshAllProfiles(publisher *Publisher, feedPassword string) { rows, err := c.db.Query(` - SELECT url, title, description, publish_account + SELECT url, title, description, site_url, publish_account FROM feeds WHERE publish_account IS NOT NULL AND publish_account <> '' `) @@ -309,8 +317,8 @@ func (c *Crawler) RefreshAllProfiles(publisher *Publisher, feedPassword string) for rows.Next() { var feedURL, account string - var title, description *string - if err := rows.Scan(&feedURL, &title, &description, &account); err != nil { + var title, description, siteURL *string + if err := rows.Scan(&feedURL, &title, &description, &siteURL, &account); err != nil { continue } @@ -342,7 +350,16 @@ func (c *Crawler) RefreshAllProfiles(publisher *Publisher, feedPassword string) desc = desc[:253] + "..." } - if err := publisher.UpdateProfile(session, displayName, desc, nil); err != nil { + // Fetch and upload favicon as avatar + var avatar *BlobRef + if siteURL != nil && *siteURL != "" { + faviconURL := publisher.FetchFavicon(*siteURL) + if faviconURL != "" { + avatar = publisher.fetchAndUploadImage(session, faviconURL) + } + } + + if err := publisher.UpdateProfile(session, displayName, desc, avatar); err != nil { fmt.Printf("RefreshProfiles: update failed for %s: %v\n", account, err) } else { fmt.Printf("RefreshProfiles: updated %s\n", account) diff --git a/publisher.go b/publisher.go index 245f2c5..595bd76 100644 --- a/publisher.go +++ b/publisher.go @@ -473,6 +473,47 @@ func (p *Publisher) uploadImages(session *PDSSession, imageURLs []string, altTex } // fetchAndUploadImage downloads an image and uploads it to the PDS +// FetchFavicon tries to get a favicon URL for a site +// Returns the favicon URL or empty string if not found +func (p *Publisher) FetchFavicon(siteURL string) string { + if siteURL == "" { + return "" + } + + // Parse the site URL to get the host + if !strings.Contains(siteURL, "://") { + siteURL = "https://" + siteURL + } + u, err := url.Parse(siteURL) + if err != nil { + return "" + } + + // Try common favicon locations + faviconURLs := []string{ + fmt.Sprintf("https://%s/favicon.ico", u.Host), + fmt.Sprintf("https://%s/favicon.png", u.Host), + fmt.Sprintf("https://%s/apple-touch-icon.png", u.Host), + } + + for _, faviconURL := range faviconURLs { + resp, err := p.httpClient.Head(faviconURL) + if err != nil { + continue + } + resp.Body.Close() + if resp.StatusCode == http.StatusOK { + contentType := resp.Header.Get("Content-Type") + if strings.HasPrefix(contentType, "image/") || strings.HasSuffix(faviconURL, ".ico") { + return faviconURL + } + } + } + + // Fallback to Google's favicon service (reliable, returns PNG) + return fmt.Sprintf("https://www.google.com/s2/favicons?domain=%s&sz=128", u.Host) +} + func (p *Publisher) fetchAndUploadImage(session *PDSSession, imageURL string) *BlobRef { // Fetch the image resp, err := p.httpClient.Get(imageURL)