@@ -3,6 +3,7 @@ package dev.typetype.server.services
33import dev.typetype.server.models.RssFeedItem
44import dev.typetype.server.models.VideoItem
55import java.io.ByteArrayOutputStream
6+ import java.net.URI
67import java.net.URLEncoder
78import java.nio.charset.StandardCharsets
89import java.time.Instant
@@ -23,6 +24,7 @@ internal object RssDocumentRenderer {
2324 writer.writeStartDocument(StandardCharsets .UTF_8 .name(), " 1.0" )
2425 writer.writeStartElement(" rss" )
2526 writer.writeAttribute(" version" , " 2.0" )
27+ writer.writeNamespace(" media" , MEDIA_NAMESPACE )
2628 writer.writeStartElement(" channel" )
2729 writer.element(" title" , feed.name)
2830 writer.element(" link" , publicBaseUrl)
@@ -52,6 +54,11 @@ internal object RssDocumentRenderer {
5254 writeEndElement()
5355 element(" author" , video.uploaderName)
5456 video.shortDescription?.takeIf (String ::isNotBlank)?.let { element(" description" , it) }
57+ video.thumbnailUrl.httpUrlOrNull()?.let { thumbnailUrl ->
58+ writeStartElement(" media" , " thumbnail" , MEDIA_NAMESPACE )
59+ writeAttribute(" url" , thumbnailUrl)
60+ writeEndElement()
61+ }
5562 RssVideoMetadata .publishedAtMillis(video).takeIf { it > 0 }
5663 ?.let { element(" pubDate" , RFC_1123 .format(Instant .ofEpochMilli(it))) }
5764 writeEndElement()
@@ -63,5 +70,14 @@ internal object RssDocumentRenderer {
6370 writeEndElement()
6471 }
6572
73+ private fun String.httpUrlOrNull (): String? = runCatching {
74+ URI (this ).takeIf { uri ->
75+ uri.isAbsolute && uri.host != null &&
76+ (uri.scheme.equals(" http" , ignoreCase = true ) ||
77+ uri.scheme.equals(" https" , ignoreCase = true ))
78+ }?.toString()
79+ }.getOrNull()
80+
81+ private const val MEDIA_NAMESPACE = " http://search.yahoo.com/mrss/"
6682 private val RFC_1123 = DateTimeFormatter .RFC_1123_DATE_TIME .withZone(ZoneOffset .UTC )
6783}
0 commit comments