feat: thumbnail extraction for Reader — fixes all clients
All checks were successful
Security Checks / dockerfile-lint (push) Successful in 4s
Security Checks / dependency-audit (push) Successful in 13s
Security Checks / secret-scanning (push) Successful in 3s

Server-side (dashboard + iOS + any client):
- Added thumbnail column to reader_entries
- Worker extracts from media:thumbnail, media:content, enclosures, HTML img
- API returns thumbnail in EntryOut with & decoding
- Backfilled 260 existing entries

iOS:
- Prefers API thumbnail, falls back to client-side extraction
- Decodes HTML entities in URLs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Yusuf Suleman
2026-04-03 19:32:47 -05:00
parent 798ba17a93
commit a3eabf3e3b
4 changed files with 59 additions and 1 deletions

View File

@@ -18,6 +18,7 @@ struct ReaderEntry: Codable, Identifiable, Hashable {
let status: String
let starred: Bool
let readingTime: Int
let thumbnail: String?
let feed: ReaderFeedRef?
var isRead: Bool { status == "read" }
@@ -49,7 +50,12 @@ struct ReaderEntry: Codable, Identifiable, Hashable {
}
var thumbnailURL: URL? {
ReaderEntry.extractThumbnail(from: content ?? fullContent ?? "")
// Prefer server-provided thumbnail
if let thumb = thumbnail, !thumb.isEmpty, let url = URL(string: thumb) {
return url
}
// Fallback: extract from content
return ReaderEntry.extractThumbnail(from: content ?? fullContent ?? "")
}
private static let imgRegex = try! NSRegularExpression(
@@ -68,6 +74,9 @@ struct ReaderEntry: Codable, Identifiable, Hashable {
return nil
}
let src = String(searchRange[srcRange])
.replacingOccurrences(of: "&amp;", with: "&")
.replacingOccurrences(of: "&lt;", with: "<")
.replacingOccurrences(of: "&gt;", with: ">")
return URL(string: src)
}