Detecting images by pinging the server instead of relying on the url.

This commit is contained in:
Vitor Pamplona
2023-12-16 11:55:09 -05:00
parent ecc0f39f7d
commit ed21d4cafb
5 changed files with 44 additions and 36 deletions
@@ -34,7 +34,7 @@ object UrlCachedPreviewer {
}
}
val state = if (urlInfo.allFetchComplete() && urlInfo.url == url) {
val state = if (urlInfo.fetchComplete() && urlInfo.url == url) {
UrlPreviewState.Loaded(urlInfo)
} else {
UrlPreviewState.Empty
@@ -1,13 +1,9 @@
package com.vitorpamplona.amethyst.service.previews
import android.net.Uri
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.util.Locale
class BahaUrlPreview(val url: String, var callback: IUrlPreviewCallback?) {
private val imageExtensionArray = arrayOf(".gif", ".png", ".jpg", ".jpeg", ".bmp", ".webp")
suspend fun fetchUrlPreview(timeOut: Int = 30000) = withContext(Dispatchers.IO) {
try {
fetch(timeOut)
@@ -17,26 +13,7 @@ class BahaUrlPreview(val url: String, var callback: IUrlPreviewCallback?) {
}
private suspend fun fetch(timeOut: Int = 30000) {
lateinit var urlInfoItem: UrlInfoItem
if (checkIsImageUrl()) {
urlInfoItem = UrlInfoItem(url = url, image = url)
} else {
val document = getDocument(url, timeOut)
urlInfoItem = parseHtml(url, document)
}
callback?.onComplete(urlInfoItem)
}
private fun checkIsImageUrl(): Boolean {
val uri = Uri.parse(url)
var isImage = false
for (imageExtension in imageExtensionArray) {
if (uri.path != null && uri.path!!.toLowerCase(Locale.getDefault()).endsWith(imageExtension)) {
isImage = true
break
}
}
return isImage
callback?.onComplete(getDocument(url, timeOut))
}
fun cleanUp() {
@@ -1,14 +1,16 @@
package com.vitorpamplona.amethyst.service.previews
import androidx.compose.runtime.Immutable
import okhttp3.MediaType
import java.net.URL
@Immutable
data class UrlInfoItem(
class UrlInfoItem(
val url: String = "",
val title: String = "",
val description: String = "",
val image: String = ""
val image: String = "",
val mimeType: MediaType
) {
val verifiedUrl = kotlin.runCatching { URL(url) }.getOrNull()
val imageUrlFullPath =
@@ -18,6 +20,10 @@ data class UrlInfoItem(
image
}
fun fetchComplete(): Boolean {
return url.isNotEmpty() && image.isNotEmpty()
}
fun allFetchComplete(): Boolean {
return title.isNotEmpty() && description.isNotEmpty() && image.isNotEmpty()
}
@@ -3,6 +3,8 @@ package com.vitorpamplona.amethyst.service.previews
import com.vitorpamplona.amethyst.service.HttpClient
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.Request
import org.jsoup.Jsoup
import org.jsoup.nodes.Document
@@ -46,21 +48,31 @@ private val META_X_IMAGE = arrayOf(
private const val CONTENT = "content"
suspend fun getDocument(url: String, timeOut: Int = 30000): Document =
suspend fun getDocument(url: String, timeOut: Int = 30000): UrlInfoItem =
withContext(Dispatchers.IO) {
val request: Request = Request.Builder().url(url).get().build()
val html = HttpClient.getHttpClient().newCall(request).execute().use {
HttpClient.getHttpClient().newCall(request).execute().use {
if (it.isSuccessful) {
it.body.string()
val mimeType = it.headers.get("Content-Type")?.toMediaType()
?: throw IllegalArgumentException("Website returned unknown mimetype: ${it.headers.get("Content-Type")}")
if (mimeType.type == "text" && mimeType.subtype == "html") {
val document = Jsoup.parse(it.body.string())
parseHtml(url, document, mimeType)
} else if (mimeType.type == "image") {
UrlInfoItem(url, image = url, mimeType = mimeType)
} else if (mimeType.type == "video") {
UrlInfoItem(url, image = url, mimeType = mimeType)
} else {
throw IllegalArgumentException("Website returned unknown encoding for previews: $mimeType")
}
} else {
throw IllegalArgumentException("Website returned: " + it.code)
}
}
Jsoup.parse(html)
}
suspend fun parseHtml(url: String, document: Document): UrlInfoItem =
suspend fun parseHtml(url: String, document: Document, type: MediaType): UrlInfoItem =
withContext(Dispatchers.IO) {
val metaTags = document.getElementsByTag(ELEMENT_TAG_META)
@@ -106,8 +118,8 @@ suspend fun parseHtml(url: String, document: Document): UrlInfoItem =
}
if (title.isNotEmpty() && description.isNotEmpty() && image.isNotEmpty()) {
return@withContext UrlInfoItem(url, title, description, image)
return@withContext UrlInfoItem(url, title, description, image, type)
}
}
return@withContext UrlInfoItem(url, title, description, image)
return@withContext UrlInfoItem(url, title, description, image, type)
}
@@ -2,6 +2,7 @@ package com.vitorpamplona.amethyst.ui.components
import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
@@ -10,6 +11,8 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import com.vitorpamplona.amethyst.model.UrlCachedPreviewer
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.HalfVertPadding
import kotlinx.collections.immutable.persistentListOf
@Composable
fun LoadUrlPreview(url: String, urlText: String, accountViewModel: AccountViewModel) {
@@ -42,7 +45,17 @@ fun LoadUrlPreview(url: String, urlText: String, accountViewModel: AccountViewMo
) { state ->
when (state) {
is UrlPreviewState.Loaded -> {
UrlPreviewCard(url, state.previewInfo)
if (state.previewInfo.mimeType.type == "image") {
Box(modifier = HalfVertPadding) {
ZoomableContentView(ZoomableUrlImage(url), persistentListOf(), roundedCorner = true, accountViewModel)
}
} else if (state.previewInfo.mimeType.type == "video") {
Box(modifier = HalfVertPadding) {
ZoomableContentView(ZoomableUrlVideo(url), persistentListOf(), roundedCorner = true, accountViewModel)
}
} else {
UrlPreviewCard(url, state.previewInfo)
}
}
else -> {