Add support for multiple images from Twitter links
- Fetch all images from Twitter syndication API (photos array) - Store images with unified 'image' media type (first = thumbnail by ID order) - Display multi-image tweets in grid layout on detail page - Add hover cycling through images on home grid (2s interval) - Show image count indicator on multi-image items - Extract shared downloadAndStoreImages() helper for create/refresh - Add migration to convert existing thumbnail/gallery types to image - Make images clickable to open in new tab on detail page
This commit is contained in:
parent
e917e67930
commit
007e167707
9 changed files with 625 additions and 403 deletions
|
|
@ -6,6 +6,7 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
|
@ -34,10 +35,61 @@ func nullStr(s string) sql.Null[string] {
|
|||
return sql.Null[string]{V: s, Valid: true}
|
||||
}
|
||||
|
||||
// downloadAndStoreImages downloads images from URLs and stores them for an item.
|
||||
// All images are stored with media_type "image". The first image (by ID order) serves as the thumbnail.
|
||||
// If imageURLs is empty but imageURL is set, stores that single image.
|
||||
func downloadAndStoreImages(
|
||||
ctx context.Context,
|
||||
db *sql.DB,
|
||||
logger *slog.Logger,
|
||||
itemID int64,
|
||||
imageURL string,
|
||||
imageURLs []string,
|
||||
) {
|
||||
if len(imageURLs) > 0 {
|
||||
// Multi-image (e.g., Twitter with multiple photos)
|
||||
for i, imgURL := range imageURLs {
|
||||
imgData, contentType, err := opengraph.DownloadImage(ctx, imgURL)
|
||||
if err != nil {
|
||||
logger.Warn("failed to download image", "url", imgURL, "index", i, "error", err)
|
||||
continue
|
||||
}
|
||||
_, err = media.QCreate(ctx, db, media.CreateParams{
|
||||
ItemID: itemID,
|
||||
MediaType: "image",
|
||||
ContentType: contentType,
|
||||
Data: imgData,
|
||||
SourceURL: &imgURL,
|
||||
})
|
||||
if err != nil {
|
||||
logger.Warn("failed to store image", "index", i, "error", err)
|
||||
}
|
||||
}
|
||||
} else if imageURL != "" {
|
||||
// Single image
|
||||
imgData, contentType, err := opengraph.DownloadImage(ctx, imageURL)
|
||||
if err != nil {
|
||||
logger.Warn("failed to download image", "url", imageURL, "error", err)
|
||||
return
|
||||
}
|
||||
_, err = media.QCreate(ctx, db, media.CreateParams{
|
||||
ItemID: itemID,
|
||||
MediaType: "image",
|
||||
ContentType: contentType,
|
||||
Data: imgData,
|
||||
SourceURL: &imageURL,
|
||||
})
|
||||
if err != nil {
|
||||
logger.Warn("failed to store image", "error", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type urlMetadata struct {
|
||||
Title string
|
||||
Description string
|
||||
ImageURL string
|
||||
ImageURLs []string // All image URLs (for multi-image tweets)
|
||||
SiteName string
|
||||
IsEmbed bool
|
||||
Provider string
|
||||
|
|
@ -54,6 +106,7 @@ func fetchURLMetadata(ctx context.Context, url string) (*urlMetadata, error) {
|
|||
Title: videoInfo.Title,
|
||||
Description: videoInfo.Description,
|
||||
ImageURL: videoInfo.ThumbnailURL,
|
||||
ImageURLs: videoInfo.ThumbnailURLs,
|
||||
IsEmbed: true,
|
||||
Provider: string(videoInfo.Provider),
|
||||
VideoID: videoInfo.VideoID,
|
||||
|
|
@ -169,14 +222,16 @@ func HandleCreateFromLink(rc *RequestContext, w http.ResponseWriter, r *http.Req
|
|||
itemType = "link"
|
||||
}
|
||||
|
||||
// For embeds, fetch thumbnail and video URL
|
||||
// For embeds, fetch thumbnail(s) and video URL
|
||||
var imageURL, videoURL string
|
||||
var imageURLs []string // For multi-image tweets
|
||||
if req.ImageURL != nil {
|
||||
imageURL = *req.ImageURL
|
||||
}
|
||||
if itemType == "embed" && embedProvider != nil {
|
||||
if videoInfo, err := embed.Detect(ctx, req.URL); err == nil && videoInfo != nil {
|
||||
imageURL = videoInfo.ThumbnailURL
|
||||
imageURLs = videoInfo.ThumbnailURLs
|
||||
videoURL = videoInfo.VideoURL
|
||||
}
|
||||
}
|
||||
|
|
@ -196,24 +251,8 @@ func HandleCreateFromLink(rc *RequestContext, w http.ResponseWriter, r *http.Req
|
|||
return err
|
||||
}
|
||||
|
||||
// Download and store thumbnail
|
||||
if imageURL != "" {
|
||||
imgData, contentType, err := opengraph.DownloadImage(ctx, imageURL)
|
||||
if err != nil {
|
||||
rc.Logger.Warn("failed to download image", "url", imageURL, "error", err)
|
||||
} else {
|
||||
_, err = media.QCreate(ctx, rc.DB, media.CreateParams{
|
||||
ItemID: it.ID,
|
||||
MediaType: "thumbnail",
|
||||
ContentType: contentType,
|
||||
Data: imgData,
|
||||
SourceURL: &imageURL,
|
||||
})
|
||||
if err != nil {
|
||||
rc.Logger.Warn("failed to store image", "error", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Download and store images
|
||||
downloadAndStoreImages(ctx, rc.DB, rc.Logger, it.ID, imageURL, imageURLs)
|
||||
|
||||
// Set tags
|
||||
if len(req.Tags) > 0 {
|
||||
|
|
@ -547,26 +586,11 @@ func HandleRefreshMetadata(rc *RequestContext, w http.ResponseWriter, r *http.Re
|
|||
item.QUpdateVideoURL(ctx, rc.DB, it.ID, meta.VideoURL)
|
||||
}
|
||||
|
||||
// Download and replace thumbnail
|
||||
if meta.ImageURL != "" {
|
||||
// Delete existing thumbnails
|
||||
// Download and replace images
|
||||
if len(meta.ImageURLs) > 0 || meta.ImageURL != "" {
|
||||
// Delete existing media (thumbnails and gallery)
|
||||
media.QDeleteByItemID(ctx, rc.DB, it.ID)
|
||||
|
||||
imgData, contentType, err := opengraph.DownloadImage(ctx, meta.ImageURL)
|
||||
if err != nil {
|
||||
rc.Logger.Warn("failed to download image during refresh", "url", meta.ImageURL, "error", err)
|
||||
} else {
|
||||
_, err = media.QCreate(ctx, rc.DB, media.CreateParams{
|
||||
ItemID: it.ID,
|
||||
MediaType: "thumbnail",
|
||||
ContentType: contentType,
|
||||
Data: imgData,
|
||||
SourceURL: &meta.ImageURL,
|
||||
})
|
||||
if err != nil {
|
||||
rc.Logger.Warn("failed to store refreshed image", "error", err)
|
||||
}
|
||||
}
|
||||
downloadAndStoreImages(ctx, rc.DB, rc.Logger, it.ID, meta.ImageURL, meta.ImageURLs)
|
||||
}
|
||||
|
||||
// Refetch and return updated item
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue