A community based topic aggregation platform built on atproto

fix(posts): unfurl thumbnails for trusted aggregators without thumbnailUrl

Previously, trusted aggregators that didn't provide a thumbnailUrl would
get no thumbnail because unfurl was completely skipped.

Now the logic is:
- If trusted aggregator provides thumbnailUrl → use it (unchanged)
- If trusted aggregator has no thumbnail AND URL is unfurl-supported →
fetch thumbnail via unfurl service
- Regular users get full unfurl as before

This allows the Reddit Highlights aggregator to get Streamable thumbnails
automatically via the backend unfurl service, without needing to fetch
them client-side.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

+29 -17
+29 -17
internal/core/posts/service.go
··· 252 252 } 253 253 254 254 // Unfurl enhancement (optional, only if URL is supported) 255 - // Skip unfurl for trusted aggregators - they provide their own metadata 256 - if !isTrustedAggregator { 255 + // For trusted aggregators: only unfurl for thumbnail if they didn't provide one 256 + // For regular users: full unfurl for all metadata 257 + needsThumbnailUnfurl := isTrustedAggregator && external["thumb"] == nil && (req.ThumbnailURL == nil || *req.ThumbnailURL == "") 258 + needsFullUnfurl := !isTrustedAggregator 259 + 260 + if needsThumbnailUnfurl || needsFullUnfurl { 257 261 if uri, ok := external["uri"].(string); ok && uri != "" { 258 262 // Check if we support unfurling this URL 259 263 if s.unfurlService != nil && s.unfurlService.IsSupported(uri) { 260 - log.Printf("[POST-CREATE] Unfurling URL: %s", uri) 264 + log.Printf("[POST-CREATE] Unfurling URL: %s (thumbnailOnly=%v)", uri, needsThumbnailUnfurl) 261 265 262 266 // Unfurl with timeout (non-fatal if it fails) 263 267 unfurlCtx, cancel := context.WithTimeout(ctx, 10*time.Second) ··· 268 272 // Log but don't fail - user can still post with manual metadata 269 273 log.Printf("[POST-CREATE] Warning: Failed to unfurl URL %s: %v", uri, err) 270 274 } else { 271 - // Enhance embed with fetched metadata (only if client didn't provide) 272 - // Note: We respect client-provided values, even empty strings 273 - // If client sends title="", we assume they want no title 274 - if external["title"] == nil { 275 - external["title"] = result.Title 276 - } 277 - if external["description"] == nil { 278 - external["description"] = result.Description 275 + // For regular users: enhance embed with fetched metadata 276 + // For trusted aggregators: skip metadata, they provide their own 277 + if needsFullUnfurl { 278 + // Enhance embed with fetched metadata (only if client didn't provide) 279 + // Note: We respect client-provided values, even empty strings 280 + // If client sends title="", we assume they want no title 281 + if external["title"] == nil { 282 + external["title"] = result.Title 283 + } 284 + if external["description"] == nil { 285 + external["description"] = result.Description 286 + } 287 + // Always set metadata fields (provider, domain, type) 288 + external["embedType"] = result.Type 289 + external["provider"] = result.Provider 290 + external["domain"] = result.Domain 279 291 } 280 - // Always set metadata fields (provider, domain, type) 281 - external["embedType"] = result.Type 282 - external["provider"] = result.Provider 283 - external["domain"] = result.Domain 284 292 285 293 // Upload thumbnail from unfurl if client didn't provide one 286 294 // (Thumb validation already happened above) ··· 299 307 } 300 308 } 301 309 302 - log.Printf("[POST-CREATE] Successfully enhanced embed with unfurl data (provider: %s, type: %s)", 303 - result.Provider, result.Type) 310 + if needsFullUnfurl { 311 + log.Printf("[POST-CREATE] Successfully enhanced embed with unfurl data (provider: %s, type: %s)", 312 + result.Provider, result.Type) 313 + } else { 314 + log.Printf("[POST-CREATE] Fetched thumbnail via unfurl for trusted aggregator") 315 + } 304 316 } 305 317 } 306 318 }