diff --git a/service-api/src/commonMain/kotlin/catchup/service/api/CatchUpItem.kt b/service-api/src/commonMain/kotlin/catchup/service/api/CatchUpItem.kt index e461d5644..7e3aa54b7 100644 --- a/service-api/src/commonMain/kotlin/catchup/service/api/CatchUpItem.kt +++ b/service-api/src/commonMain/kotlin/catchup/service/api/CatchUpItem.kt @@ -42,6 +42,7 @@ data class CatchUpItem( val serviceId: String? = null, val indexInResponse: Int? = null, val contentType: ContentType? = null, // Null indicates unset, try to infer it + val imagePreviewUrl: String? = null, ) { val clickUrl: String? @@ -99,6 +100,7 @@ data class CatchUpItem( serviceId = serviceId, indexInResponse = index, contentType = HTML, + imagePreviewUrl = "https://picsum.photos/seed/$index/300/300", ) } } diff --git a/service-api/src/commonMain/kotlin/catchup/service/api/DbMapping.kt b/service-api/src/commonMain/kotlin/catchup/service/api/DbMapping.kt index 3fc49c2f3..e5ae99d21 100644 --- a/service-api/src/commonMain/kotlin/catchup/service/api/DbMapping.kt +++ b/service-api/src/commonMain/kotlin/catchup/service/api/DbMapping.kt @@ -20,6 +20,7 @@ fun CatchUpItem.toCatchUpDbItem(): CatchUpDbItem { serviceId = serviceId, indexInResponse = indexInResponse, contentType = contentType?.name, + imagePreviewUrl = imagePreviewUrl, imageUrl = imageInfo?.url, imageDetailUrl = imageInfo?.detailUrl, imageAnimatable = imageInfo?.animatable, @@ -55,6 +56,7 @@ fun CatchUpDbItem.toCatchUpItem(): CatchUpItem { serviceId = serviceId, indexInResponse = indexInResponse, contentType = contentType?.let { ContentType.valueOf(it) }, + imagePreviewUrl = imagePreviewUrl, imageInfo = imageUrl?.let { ImageInfo( diff --git a/service-api/src/commonMain/kotlin/catchup/service/api/ServiceMeta.kt b/service-api/src/commonMain/kotlin/catchup/service/api/ServiceMeta.kt index 337dd97c9..77d4b4e23 100644 --- a/service-api/src/commonMain/kotlin/catchup/service/api/ServiceMeta.kt +++ b/service-api/src/commonMain/kotlin/catchup/service/api/ServiceMeta.kt @@ -30,6 +30,7 @@ data class ServiceMeta( val firstPageKey: Int?, val pagesAreNumeric: Boolean = false, val enabled: Boolean = true, + val supportsRichTextItems: Boolean = false, ) { val enabledPreferenceKey = "service_config_${id}_enabled" } diff --git a/service-db/src/commonMain/sqldelight/catchup/service/db/service.sq b/service-db/src/commonMain/sqldelight/catchup/service/db/service.sq index 331cae39f..67bcdbf02 100644 --- a/service-db/src/commonMain/sqldelight/catchup/service/db/service.sq +++ b/service-db/src/commonMain/sqldelight/catchup/service/db/service.sq @@ -22,6 +22,7 @@ CREATE TABLE IF NOT EXISTS catchUpDbItem ( serviceId TEXT, indexInResponse INTEGER AS Int, contentType TEXT, + imagePreviewUrl TEXT, -- Image info imageUrl TEXT, imageDetailUrl TEXT, @@ -83,6 +84,7 @@ INSERT OR REPLACE INTO catchUpDbItem ( serviceId, indexInResponse, contentType, + imagePreviewUrl, imageUrl, imageDetailUrl, imageAnimatable, diff --git a/services/producthunt/src/main/kotlin/catchup/service/producthunt/ProductHuntService.kt b/services/producthunt/src/main/kotlin/catchup/service/producthunt/ProductHuntService.kt index 3fca01b0b..7530b18ad 100644 --- a/services/producthunt/src/main/kotlin/catchup/service/producthunt/ProductHuntService.kt +++ b/services/producthunt/src/main/kotlin/catchup/service/producthunt/ProductHuntService.kt @@ -119,6 +119,7 @@ constructor( indexInResponse = index + request.pageOffset, serviceId = meta().id, contentType = ContentType.HTML, + imagePreviewUrl = thumbnail?.url, ) } } diff --git a/services/reddit/src/main/kotlin/catchup/service/reddit/RedditService.kt b/services/reddit/src/main/kotlin/catchup/service/reddit/RedditService.kt index a801c3e28..99d7874b2 100644 --- a/services/reddit/src/main/kotlin/catchup/service/reddit/RedditService.kt +++ b/services/reddit/src/main/kotlin/catchup/service/reddit/RedditService.kt @@ -89,6 +89,7 @@ constructor(@InternalApi private val serviceMeta: ServiceMeta, private val api: // If it's a selftext, mark it as HTML for summarizing. contentType = if (link.isSelf) ContentType.HTML else null, detailKey = link.id, + imagePreviewUrl = link.getPreviewUrl(), ) } DataResult(data, redditListingRedditResponse.data.after) @@ -166,6 +167,7 @@ abstract class RedditMetaModule { R.color.catchup_service_reddit_accent, R.drawable.catchup_service_reddit_logo, firstPageKey = null, + supportsRichTextItems = true, ) } } diff --git a/services/reddit/src/main/kotlin/catchup/service/reddit/model/RedditObject.kt b/services/reddit/src/main/kotlin/catchup/service/reddit/model/RedditObject.kt index 12d8b5199..dd97c6965 100755 --- a/services/reddit/src/main/kotlin/catchup/service/reddit/model/RedditObject.kt +++ b/services/reddit/src/main/kotlin/catchup/service/reddit/model/RedditObject.kt @@ -18,6 +18,7 @@ package catchup.service.reddit.model import androidx.annotation.Keep import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import kotlin.math.abs import kotlinx.datetime.Instant @Keep @JsonClass(generateAdapter = false) sealed interface RedditObject @@ -83,7 +84,8 @@ data class RedditLink( val selftext: String?, @Json(name = "selftext_html") val selftextHtml: String?, val stickied: Boolean, - val thumbnail: String, + val thumbnail: String?, + val preview: RedditPreview?, val title: String, val url: String, val visited: Boolean, @@ -102,7 +104,18 @@ data class RedditLink( override val score: Int, override val subreddit: String, override val ups: Int, -) : RedditObject, RedditSubmission +) : RedditObject, RedditSubmission { + fun getPreviewUrl(preferredWidthPx: Int = Int.MAX_VALUE): String? { + if (preview == null) return null + if (!preview.enabled) return null + if (preview.images.isEmpty()) return null + preview.images.firstOrNull()?.resolutions?.let { resolutions -> + val bestFit = resolutions.minByOrNull { abs(it.width - preferredWidthPx) } + return bestFit?.url + } + return thumbnail?.takeUnless(String::isBlank) + } +} @Keep @JsonClass(generateAdapter = true) @@ -140,3 +153,13 @@ data class RedditMore( val depth: Int, val children: List, ) : RedditObject + +@Keep +@JsonClass(generateAdapter = true) +data class RedditPreview(val images: List, val enabled: Boolean) : RedditObject { + @JsonClass(generateAdapter = true) + data class RedditImage(val source: RedditImageSource, val resolutions: List) { + @JsonClass(generateAdapter = true) + data class RedditImageSource(val url: String, val width: Int, val height: Int) + } +}