Skip to content

Commit

Permalink
Feature read correct favicon from feed rss
Browse files Browse the repository at this point in the history
  • Loading branch information
luigihenrick committed Jan 29, 2025
1 parent e7b3f10 commit 8b94299
Show file tree
Hide file tree
Showing 9 changed files with 56 additions and 44 deletions.
25 changes: 14 additions & 11 deletions ...k/src/commonMain/kotlin/dev/sasikanth/rss/reader/core/network/parser/AtomContentParser.kt
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import dev.sasikanth.rss.reader.core.network.parser.FeedParser.Companion.ATTR_RE
import dev.sasikanth.rss.reader.core.network.parser.FeedParser.Companion.ATTR_VALUE_ALTERNATE
import dev.sasikanth.rss.reader.core.network.parser.FeedParser.Companion.TAG_ATOM_ENTRY
import dev.sasikanth.rss.reader.core.network.parser.FeedParser.Companion.TAG_ATOM_FEED
import dev.sasikanth.rss.reader.core.network.parser.FeedParser.Companion.TAG_ATOM_ICON
import dev.sasikanth.rss.reader.core.network.parser.FeedParser.Companion.TAG_ATOM_LOGO
import dev.sasikanth.rss.reader.core.network.parser.FeedParser.Companion.TAG_CONTENT
import dev.sasikanth.rss.reader.core.network.parser.FeedParser.Companion.TAG_LINK
import dev.sasikanth.rss.reader.core.network.parser.FeedParser.Companion.TAG_PUBLISHED
Expand All @@ -32,7 +34,6 @@ import dev.sasikanth.rss.reader.core.network.parser.FeedParser.Companion.TAG_TIT
import dev.sasikanth.rss.reader.core.network.parser.FeedParser.Companion.TAG_UPDATED
import dev.sasikanth.rss.reader.util.dateStringToEpochMillis
import dev.sasikanth.rss.reader.util.decodeHTMLString
import io.ktor.http.Url
import kotlinx.datetime.Clock
import org.kobjects.ktxml.api.EventType
import org.kobjects.ktxml.api.XmlPullParser
Expand All @@ -47,6 +48,7 @@ internal object AtomContentParser : ContentParser() {
var title: String? = null
var description: String? = null
var link: String? = null
var iconUrl: String? = null

while (parser.next() != EventType.END_TAG) {
if (parser.eventType != EventType.START_TAG) continue
Expand All @@ -67,6 +69,16 @@ internal object AtomContentParser : ContentParser() {
TAG_ATOM_ENTRY -> {
posts.add(readAtomEntry(parser, link))
}
TAG_ATOM_ICON -> {
iconUrl = parser.nextText()
}
TAG_ATOM_LOGO -> {
if (iconUrl.isNullOrBlank()) {
iconUrl = parser.nextText()
} else {
parser.skip()
}
}
else -> parser.skip()
}
}
Expand All @@ -75,19 +87,10 @@ internal object AtomContentParser : ContentParser() {
link = feedUrl
}

val domain = Url(link)
val host =
if (domain.host != "localhost") {
domain.host
} else {
throw NullPointerException("Unable to get host domain")
}
val iconUrl = FeedParser.feedIcon(host)

return FeedPayload(
name = FeedParser.cleanText(title ?: link)!!.decodeHTMLString(),
description = FeedParser.cleanText(description).orEmpty().decodeHTMLString(),
icon = iconUrl,
icon = iconUrl ?: link,
homepageLink = link,
link = feedUrl,
posts = posts.filterNotNull()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ class FeedParser(private val dispatchersProvider: DispatchersProvider) {

internal const val TAG_RSS_CHANNEL = "channel"
internal const val TAG_ATOM_FEED = "feed"
internal const val TAG_ATOM_ICON = "icon"
internal const val TAG_ATOM_LOGO = "logo"
internal const val TAG_RSS_ITEM = "item"
internal const val TAG_ATOM_ENTRY = "entry"

Expand All @@ -101,17 +103,14 @@ class FeedParser(private val dispatchersProvider: DispatchersProvider) {
internal const val ATTR_URL = "url"
internal const val ATTR_TYPE = "type"
internal const val ATTR_REL = "rel"
internal const val ATTR_RDF_RESOURCE = "rdf:resource"
internal const val ATTR_HREF = "href"

internal const val ATTR_VALUE_ALTERNATE = "alternate"
internal const val ATTR_VALUE_IMAGE = "image/jpeg"

fun cleanText(text: String?) = text?.replace(htmlTag, "")?.replace(blankLine, "")?.trim()

fun feedIcon(host: String): String {
return "https://icon.horse/icon/$host"
}

fun safeUrl(host: String?, url: String?): String? {
if (host.isNullOrBlank()) return null

Expand Down
28 changes: 16 additions & 12 deletions ...rk/src/commonMain/kotlin/dev/sasikanth/rss/reader/core/network/parser/RDFContentParser.kt
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,18 @@ package dev.sasikanth.rss.reader.core.network.parser

import dev.sasikanth.rss.reader.core.model.remote.FeedPayload
import dev.sasikanth.rss.reader.core.model.remote.PostPayload
import dev.sasikanth.rss.reader.core.network.parser.FeedParser.Companion.ATTR_RDF_RESOURCE
import dev.sasikanth.rss.reader.core.network.parser.FeedParser.Companion.TAG_CONTENT_ENCODED
import dev.sasikanth.rss.reader.core.network.parser.FeedParser.Companion.TAG_DC_DATE
import dev.sasikanth.rss.reader.core.network.parser.FeedParser.Companion.TAG_DESCRIPTION
import dev.sasikanth.rss.reader.core.network.parser.FeedParser.Companion.TAG_FEED_IMAGE
import dev.sasikanth.rss.reader.core.network.parser.FeedParser.Companion.TAG_LINK
import dev.sasikanth.rss.reader.core.network.parser.FeedParser.Companion.TAG_PUB_DATE
import dev.sasikanth.rss.reader.core.network.parser.FeedParser.Companion.TAG_RSS_CHANNEL
import dev.sasikanth.rss.reader.core.network.parser.FeedParser.Companion.TAG_RSS_ITEM
import dev.sasikanth.rss.reader.core.network.parser.FeedParser.Companion.TAG_TITLE
import dev.sasikanth.rss.reader.util.dateStringToEpochMillis
import dev.sasikanth.rss.reader.util.decodeHTMLString
import io.ktor.http.Url
import kotlinx.datetime.Clock
import org.kobjects.ktxml.api.EventType
import org.kobjects.ktxml.api.XmlPullParser
Expand All @@ -44,12 +45,13 @@ internal object RDFContentParser : ContentParser() {
var title: String? = null
var link: String? = null
var description: String? = null
var iconUrl: String? = null

// Parse channel
while (parser.next() != EventType.END_TAG) {
if (parser.eventType != EventType.START_TAG) continue

when (parser.name) {
when (val name = parser.name) {
TAG_TITLE -> {
title = parser.nextText()
}
Expand All @@ -63,6 +65,9 @@ internal object RDFContentParser : ContentParser() {
TAG_DESCRIPTION -> {
description = parser.nextText()
}
TAG_FEED_IMAGE -> {
iconUrl = readRdfLink(name, parser)
}
else -> parser.skip()
}
}
Expand All @@ -82,19 +87,10 @@ internal object RDFContentParser : ContentParser() {
link = feedUrl
}

val domain = Url(link)
val host =
if (domain.host != "localhost") {
domain.host
} else {
throw NullPointerException("Unable to get host domain")
}
val iconUrl = FeedParser.feedIcon(host)

return FeedPayload(
name = FeedParser.cleanText(title ?: link)!!.decodeHTMLString(),
description = FeedParser.cleanText(description).orEmpty().decodeHTMLString(),
icon = iconUrl,
icon = iconUrl ?: link,
homepageLink = link,
link = feedUrl,
posts = posts.filterNotNull()
Expand Down Expand Up @@ -153,4 +149,12 @@ internal object RDFContentParser : ContentParser() {
commentsLink = commentsLink?.trim()
)
}

private fun readRdfLink(tagName: String, parser: XmlPullParser): String? {
parser.require(EventType.START_TAG, parser.namespace, tagName)
val link = parser.getAttributeValue(parser.namespace, ATTR_RDF_RESOURCE)
parser.nextTag()
parser.require(EventType.END_TAG, parser.namespace, tagName)
return link
}
}
28 changes: 17 additions & 11 deletions ...rk/src/commonMain/kotlin/dev/sasikanth/rss/reader/core/network/parser/RSSContentParser.kt
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import dev.sasikanth.rss.reader.core.network.parser.FeedParser.Companion.TAG_CON
import dev.sasikanth.rss.reader.core.network.parser.FeedParser.Companion.TAG_DESCRIPTION
import dev.sasikanth.rss.reader.core.network.parser.FeedParser.Companion.TAG_ENCLOSURE
import dev.sasikanth.rss.reader.core.network.parser.FeedParser.Companion.TAG_FEATURED_IMAGE
import dev.sasikanth.rss.reader.core.network.parser.FeedParser.Companion.TAG_FEED_IMAGE
import dev.sasikanth.rss.reader.core.network.parser.FeedParser.Companion.TAG_LINK
import dev.sasikanth.rss.reader.core.network.parser.FeedParser.Companion.TAG_PUB_DATE
import dev.sasikanth.rss.reader.core.network.parser.FeedParser.Companion.TAG_RSS_CHANNEL
Expand All @@ -34,7 +35,6 @@ import dev.sasikanth.rss.reader.core.network.parser.FeedParser.Companion.TAG_TIT
import dev.sasikanth.rss.reader.core.network.parser.FeedParser.Companion.TAG_URL
import dev.sasikanth.rss.reader.util.dateStringToEpochMillis
import dev.sasikanth.rss.reader.util.decodeHTMLString
import io.ktor.http.Url
import kotlinx.datetime.Clock
import org.kobjects.ktxml.api.EventType
import org.kobjects.ktxml.api.XmlPullParser
Expand All @@ -50,6 +50,7 @@ internal object RSSContentParser : ContentParser() {
var title: String? = null
var link: String? = null
var description: String? = null
var iconUrl: String? = null

while (parser.next() != EventType.END_TAG) {
if (parser.eventType != EventType.START_TAG) continue
Expand All @@ -71,6 +72,9 @@ internal object RSSContentParser : ContentParser() {
TAG_RSS_ITEM -> {
posts.add(readRssItem(parser, link))
}
TAG_FEED_IMAGE -> {
iconUrl = readFeedIcon(name, parser)
}
else -> parser.skip()
}
}
Expand All @@ -79,19 +83,10 @@ internal object RSSContentParser : ContentParser() {
link = feedUrl
}

val domain = Url(link)
val host =
if (domain.host != "localhost") {
domain.host
} else {
throw NullPointerException("Unable to get host domain")
}
val iconUrl = FeedParser.feedIcon(host)

return FeedPayload(
name = FeedParser.cleanText(title ?: link)!!.decodeHTMLString(),
description = FeedParser.cleanText(description).orEmpty().decodeHTMLString(),
icon = iconUrl,
icon = iconUrl ?: link,
homepageLink = link,
link = feedUrl,
posts = posts.filterNotNull()
Expand Down Expand Up @@ -163,6 +158,17 @@ internal object RSSContentParser : ContentParser() {
)
}

private fun readFeedIcon(tagName: String, parser: XmlPullParser): String? {
var link: String? = null
parser.require(EventType.START_TAG, parser.namespace, tagName)
while (parser.next() != EventType.END_TAG) {
if (parser.eventType != EventType.START_TAG) continue
if (parser.name == TAG_URL) link = parser.nextText() else parser.skip()
}
parser.require(EventType.END_TAG, parser.namespace, tagName)
return link
}

private fun hasRssImageUrl(name: String, parser: XmlPullParser) =
(FeedParser.imageTags.contains(name) ||
(name == TAG_ENCLOSURE &&
Expand Down
2 changes: 1 addition & 1 deletion shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/feed/ui/FeedInfoBottomSheet.kt
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ private fun FeedLabelInput(
.fillMaxWidth()
) {
FeedFavIcon(
url = feed.homepageLink,
url = feed.icon,
contentDescription = feed.name,
modifier = Modifier.requiredSize(56.dp).clip(RoundedCornerShape(16.dp)),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ internal fun BottomSheetCollapsedContent(
is Feed -> {
FeedBottomBarItem(
badgeCount = source.numberOfUnreadPosts,
homePageUrl = source.homepageLink,
iconUrl = source.icon,
canShowUnreadPostsCount = canShowUnreadPostsCount,
onClick = { onSourceClick(source) },
selected = activeSource?.id == source.id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import dev.sasikanth.rss.reader.utils.Constants.BADGE_COUNT_TRIM_LIMIT
@Composable
internal fun FeedBottomBarItem(
badgeCount: Long,
homePageUrl: String,
iconUrl: String,
canShowUnreadPostsCount: Boolean,
onClick: () -> Unit,
modifier: Modifier = Modifier,
Expand All @@ -52,7 +52,7 @@ internal fun FeedBottomBarItem(
Box(contentAlignment = Alignment.Center) {
SelectionIndicator(selected = selected, animationProgress = 1f)
FeedFavIcon(
url = homePageUrl,
url = iconUrl,
contentDescription = null,
modifier =
Modifier.requiredSize(56.dp).clip(RoundedCornerShape(16.dp)).clickable(onClick = onClick)
Expand Down
2 changes: 1 addition & 1 deletion shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/feeds/ui/FeedListItem.kt
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ internal fun FeedListItem(
) {
Row(modifier = Modifier.padding(all = 8.dp), verticalAlignment = Alignment.CenterVertically) {
FeedFavIcon(
url = feed.homepageLink,
url = feed.icon,
contentDescription = null,
modifier = Modifier.requiredSize(36.dp).clip(RoundedCornerShape(8.dp)),
contentScale = ContentScale.Crop,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ private fun SourceIcon(source: Source?, modifier: Modifier = Modifier) {
}
is Feed -> {
FeedFavIcon(
url = source.homepageLink,
url = source.icon,
contentDescription = null,
modifier = Modifier.clip(MaterialTheme.shapes.small).requiredSize(24.dp)
)
Expand Down

0 comments on commit 8b94299

Please sign in to comment.