From fa53ff2c7801b6af13270eda1684f87b835fd670 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Wed, 30 Jul 2025 12:47:31 -0400 Subject: [PATCH] Adds an iMeta cache for Picture and Video kinds and speeds up the procedure to verify if a video or picture is supported. --- .../loggedIn/video/dal/SupportedContent.kt | 46 ++++++++++++ .../loggedIn/video/dal/VideoFeedFilter.kt | 36 +++------ .../video/dal/SupportedContentTest.kt | 56 ++++++++++++++ .../commons/richtext/RichTextParser.kt | 6 +- .../quartz/nip68Picture/PictureEvent.kt | 73 +++---------------- .../quartz/nip68Picture/tags/LocationTag.kt | 41 +++++++++++ .../quartz/nip71Video/VideoEvent.kt | 72 ++---------------- .../quartz/nip92IMeta/IMetaTag.kt | 2 +- 8 files changed, 176 insertions(+), 156 deletions(-) create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/dal/SupportedContent.kt create mode 100644 amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/dal/SupportedContentTest.kt create mode 100644 quartz/src/main/java/com/vitorpamplona/quartz/nip68Picture/tags/LocationTag.kt diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/dal/SupportedContent.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/dal/SupportedContent.kt new file mode 100644 index 000000000..cd41fb200 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/dal/SupportedContent.kt @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.screen.loggedIn.video.dal + +class SupportedContent( + val blockedUrls: List, + val mimeTypes: Set, + val supportedFileExtensions: Set, +) { + private fun validExtension(fullUrl: String): Boolean { + val queryIndex = fullUrl.indexOf('?') + if (queryIndex > 0) { + return supportedFileExtensions.any { fullUrl.startsWith(it, queryIndex - it.length) } + } + + val fragmentIndex = fullUrl.indexOf('#') + if (fragmentIndex > 0) { + return supportedFileExtensions.any { fullUrl.startsWith(it, fragmentIndex - it.length) } + } + + return supportedFileExtensions.any { fullUrl.endsWith(it) } + } + + fun acceptableUrl( + url: String, + mimeType: String?, + ) = blockedUrls.none { url.contains(it) } && ((mimeType != null && mimeTypes.contains(mimeType)) || validExtension(url)) +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/dal/VideoFeedFilter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/dal/VideoFeedFilter.kt index ea2fb6d62..b0744b123 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/dal/VideoFeedFilter.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/dal/VideoFeedFilter.kt @@ -43,6 +43,13 @@ import com.vitorpamplona.quartz.nip94FileMetadata.FileHeaderEvent class VideoFeedFilter( val account: Account, ) : AdditiveFeedFilter() { + val videoFeedSupport = + SupportedContent( + blockedUrls = listOf("youtu.be", "youtube.com"), + mimeTypes = SUPPORTED_VIDEO_FEED_MIME_TYPES_SET, + supportedFileExtensions = (RichTextParser.videoExtensions + RichTextParser.imageExtensions).toSet(), + ) + override fun feedKey(): String = account.userProfile().pubkeyHex + "-" + account.settings.defaultStoriesFollowList.value override fun limit() = 300 @@ -76,24 +83,11 @@ class VideoFeedFilter( fun acceptableUrls( baseUrls: List, mimeType: String?, - ): Boolean { - // we don't have an youtube player - val urls = baseUrls.filter { !(it.contains("youtu.be") || it.contains("youtube.com")) } + ) = baseUrls.any { videoFeedSupport.acceptableUrl(it, mimeType) } - val isSupportedMimeType = mimeType?.let { SUPPORTED_VIDEO_FEED_MIME_TYPES_SET.contains(it) } == true + fun acceptableVideoiMetas(iMetas: List): Boolean = iMetas.any { videoFeedSupport.acceptableUrl(it.url, it.mimeType) } - return urls.isNotEmpty() && (urls.any { RichTextParser.Companion.isImageOrVideoUrl(it) } || isSupportedMimeType) - } - - fun acceptableVideoiMetas(iMetas: List): Boolean = - iMetas.any { - !it.url.contains("youtu.be") && !it.url.contains("youtube.com") && (RichTextParser.isImageOrVideoUrl(it.url) || (it.mimeType == null || SUPPORTED_VIDEO_FEED_MIME_TYPES_SET.contains(it.mimeType))) - } - - fun acceptablePictureiMetas(iMetas: List): Boolean = - iMetas.any { - !it.url.contains("youtu.be") && !it.url.contains("youtube.com") && (RichTextParser.isImageOrVideoUrl(it.url) || (it.mimeType == null || SUPPORTED_VIDEO_FEED_MIME_TYPES_SET.contains(it.mimeType))) - } + fun acceptablePictureiMetas(iMetas: List): Boolean = iMetas.any { videoFeedSupport.acceptableUrl(it.url, it.mimeType) } fun acceptanceEvent(noteEvent: PictureEvent) = acceptablePictureiMetas(noteEvent.imetaTags()) @@ -117,14 +111,8 @@ class VideoFeedFilter( (noteEvent is FileHeaderEvent && acceptanceEvent(noteEvent)) || (noteEvent is VideoVerticalEvent && acceptanceEvent(noteEvent)) || (noteEvent is VideoHorizontalEvent && acceptanceEvent(noteEvent)) || - ( - noteEvent is FileStorageHeaderEvent && - noteEvent.isOneOf( - SUPPORTED_VIDEO_FEED_MIME_TYPES_SET, - ) - ) || - noteEvent is PictureEvent && - acceptanceEvent(noteEvent) + (noteEvent is FileStorageHeaderEvent && noteEvent.isOneOf(SUPPORTED_VIDEO_FEED_MIME_TYPES_SET)) || + (noteEvent is PictureEvent && acceptanceEvent(noteEvent)) ) && params.match(noteEvent) && (params.isHiddenList || account.isAcceptable(note)) diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/dal/SupportedContentTest.kt b/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/dal/SupportedContentTest.kt new file mode 100644 index 000000000..3219cde5b --- /dev/null +++ b/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/dal/SupportedContentTest.kt @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.screen.loggedIn.video.dal + +import junit.framework.TestCase.assertTrue +import org.junit.Test + +class SupportedContentTest { + @Test + fun acceptableUrl() { + val supportedExtensions = setOf(".mp4", ".webm", ".jpg", ".png") + val mimeTypes = setOf("video/mp4", "video/webm", "image/jpeg", "image/png") + val blockedUrls = listOf("youtube.com", "youtu.be") + + val testVideoUrl = "https://example.com/video.mp4" + val blockedUrl = "https://youtube.com/watch?v=example" + val urlWithQuery = "https://example.com/media.jpg?param=1" + val urlWithFragment = "https://example.com/data.png#section" + + val contentSupport = SupportedContent(blockedUrls, mimeTypes, supportedExtensions) + + // Valid scenarios + assertTrue(contentSupport.acceptableUrl(testVideoUrl, "video/mp4")) // Valid extension and mime + assertTrue(contentSupport.acceptableUrl(urlWithQuery, "image/jpeg")) // Valid query param extension + assertTrue(contentSupport.acceptableUrl(urlWithFragment, "image/png")) // Valid fragment extension + + assertTrue(contentSupport.acceptableUrl(testVideoUrl, null)) + assertTrue(contentSupport.acceptableUrl(urlWithQuery, null)) + assertTrue(contentSupport.acceptableUrl(urlWithFragment, null)) + + // Blocked URL scenarios + assertTrue(!contentSupport.acceptableUrl(blockedUrl, null)) // Blocked URL + + // Invalid scenarios + assertTrue(!contentSupport.acceptableUrl("https://example.com/file.docx", "application/docx")) // Unsupported extension/mime + assertTrue(!contentSupport.acceptableUrl("https://example.com/file.docx", null)) // Unsupported extension/mime + } +} diff --git a/commons/src/main/java/com/vitorpamplona/amethyst/commons/richtext/RichTextParser.kt b/commons/src/main/java/com/vitorpamplona/amethyst/commons/richtext/RichTextParser.kt index 830689398..6d17f3189 100644 --- a/commons/src/main/java/com/vitorpamplona/amethyst/commons/richtext/RichTextParser.kt +++ b/commons/src/main/java/com/vitorpamplona/amethyst/commons/richtext/RichTextParser.kt @@ -367,11 +367,11 @@ class RichTextParser { private fun removeQueryParamsForExtensionComparison(fullUrl: String): String = if (fullUrl.contains("?")) { - fullUrl.split("?")[0].lowercase() + fullUrl.split("?")[0] } else if (fullUrl.contains("#")) { - fullUrl.split("#")[0].lowercase() + fullUrl.split("#")[0] } else { - fullUrl.lowercase() + fullUrl } fun isImageOrVideoUrl(url: String): Boolean { diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip68Picture/PictureEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip68Picture/PictureEvent.kt index bb0bca02a..7315ecf39 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip68Picture/PictureEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip68Picture/PictureEvent.kt @@ -24,25 +24,16 @@ import androidx.compose.runtime.Immutable import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder -import com.vitorpamplona.quartz.nip01Core.core.any import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate +import com.vitorpamplona.quartz.nip01Core.tags.geohash.geohashes +import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hashtags import com.vitorpamplona.quartz.nip22Comments.RootScope import com.vitorpamplona.quartz.nip23LongContent.tags.TitleTag import com.vitorpamplona.quartz.nip31Alts.alt +import com.vitorpamplona.quartz.nip68Picture.tags.LocationTag import com.vitorpamplona.quartz.nip92IMeta.imetas -import com.vitorpamplona.quartz.nip94FileMetadata.tags.BlurhashTag -import com.vitorpamplona.quartz.nip94FileMetadata.tags.DimensionTag -import com.vitorpamplona.quartz.nip94FileMetadata.tags.FallbackTag import com.vitorpamplona.quartz.nip94FileMetadata.tags.HashSha256Tag -import com.vitorpamplona.quartz.nip94FileMetadata.tags.ImageTag -import com.vitorpamplona.quartz.nip94FileMetadata.tags.MagnetTag import com.vitorpamplona.quartz.nip94FileMetadata.tags.MimeTypeTag -import com.vitorpamplona.quartz.nip94FileMetadata.tags.ServiceTag -import com.vitorpamplona.quartz.nip94FileMetadata.tags.SizeTag -import com.vitorpamplona.quartz.nip94FileMetadata.tags.ThumbTag -import com.vitorpamplona.quartz.nip94FileMetadata.tags.TorrentInfoHash -import com.vitorpamplona.quartz.nip94FileMetadata.tags.UrlTag -import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent import com.vitorpamplona.quartz.utils.TimeUtils @Immutable @@ -55,63 +46,21 @@ class PictureEvent( sig: HexKey, ) : Event(id, pubKey, createdAt, KIND, tags, content, sig), RootScope { - // --------------- - // current - // -------------- + @Transient var iMetas: List? = null fun title() = tags.firstNotNullOfOrNull(TitleTag::parse) - /** old standard didnt use IMetas **/ - private fun url() = tags.firstNotNullOfOrNull(UrlTag::parse) + fun mimeType() = tags.firstNotNullOfOrNull(MimeTypeTag::parse) - private fun urls() = tags.mapNotNull(UrlTag::parse) + fun hash() = tags.firstNotNullOfOrNull(HashSha256Tag::parse) - private fun mimeType() = tags.firstNotNullOfOrNull(MimeTypeTag::parse) + fun hashtags() = tags.hashtags() - private fun hash() = tags.firstNotNullOfOrNull(HashSha256Tag::parse) + fun geohashes() = tags.geohashes() - private fun size() = tags.firstNotNullOfOrNull(SizeTag::parse) + fun location() = tags.mapNotNull(LocationTag::parse) - private fun dimensions() = tags.firstNotNullOfOrNull(DimensionTag::parse) - - private fun magnetURI() = tags.firstNotNullOfOrNull(MagnetTag::parse) - - private fun torrentInfoHash() = tags.firstNotNullOfOrNull(TorrentInfoHash::parse) - - private fun blurhash() = tags.firstNotNullOfOrNull(BlurhashTag::parse) - - private fun hasUrl() = tags.any(UrlTag::isTag) - - private fun isOneOf(mimeTypes: Set) = tags.any(MimeTypeTag::isIn, mimeTypes) - - private fun image() = tags.firstNotNullOfOrNull(ImageTag::parse) - - private fun images() = tags.mapNotNull(ImageTag::parse) - - private fun thumb() = tags.firstNotNullOfOrNull(ThumbTag::parse) - - private fun service() = tags.firstNotNullOfOrNull(ServiceTag::parse) - - private fun fallbacks() = tags.mapNotNull(FallbackTag::parse) - - // hack to fix pablo's bug - fun rootImage() = - url()?.let { - PictureMeta( - url = it, - mimeType = mimeType(), - blurhash = blurhash(), - alt = alt(), - hash = hash(), - dimension = dimensions(), - size = size(), - service = service(), - fallback = fallbacks(), - annotations = emptyList(), - ) - } - - fun imetaTags() = imetas().map { PictureMeta.parse(it) }.plus(rootImage()).filterNotNull() + fun imetaTags() = iMetas ?: imetas().map { PictureMeta.parse(it) }.also { iMetas = it } companion object { const val KIND = 20 @@ -142,7 +91,7 @@ class PictureEvent( createdAt: Long = TimeUtils.now(), initializer: TagArrayBuilder.() -> Unit = {}, ) = eventTemplate(KIND, description, createdAt) { - alt(ClassifiedsEvent.ALT_DESCRIPTION) + alt(ALT_DESCRIPTION) initializer() } } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip68Picture/tags/LocationTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip68Picture/tags/LocationTag.kt new file mode 100644 index 000000000..a46e0e2ee --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip68Picture/tags/LocationTag.kt @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.quartz.nip68Picture.tags + +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + +class LocationTag { + companion object { + const val TAG_NAME = "location" + + @JvmStatic + fun parse(tag: Array): String? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } + return tag[1] + } + + @JvmStatic + fun assemble(locationName: String) = arrayOf(TAG_NAME, locationName) + } +} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip71Video/VideoEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip71Video/VideoEvent.kt index 0edd44b83..2d2b61cc9 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip71Video/VideoEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip71Video/VideoEvent.kt @@ -23,29 +23,17 @@ package com.vitorpamplona.quartz.nip71Video import androidx.compose.runtime.Immutable import com.vitorpamplona.quartz.nip01Core.core.BaseAddressableEvent import com.vitorpamplona.quartz.nip01Core.core.HexKey -import com.vitorpamplona.quartz.nip01Core.core.any import com.vitorpamplona.quartz.nip01Core.tags.events.ETag import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hashtags import com.vitorpamplona.quartz.nip01Core.tags.people.PTag import com.vitorpamplona.quartz.nip22Comments.RootScope import com.vitorpamplona.quartz.nip23LongContent.tags.PublishedAtTag import com.vitorpamplona.quartz.nip23LongContent.tags.TitleTag -import com.vitorpamplona.quartz.nip31Alts.alt import com.vitorpamplona.quartz.nip71Video.tags.DurationTag import com.vitorpamplona.quartz.nip71Video.tags.SegmentTag import com.vitorpamplona.quartz.nip92IMeta.imetas -import com.vitorpamplona.quartz.nip94FileMetadata.tags.BlurhashTag -import com.vitorpamplona.quartz.nip94FileMetadata.tags.DimensionTag -import com.vitorpamplona.quartz.nip94FileMetadata.tags.FallbackTag import com.vitorpamplona.quartz.nip94FileMetadata.tags.HashSha256Tag -import com.vitorpamplona.quartz.nip94FileMetadata.tags.ImageTag -import com.vitorpamplona.quartz.nip94FileMetadata.tags.MagnetTag import com.vitorpamplona.quartz.nip94FileMetadata.tags.MimeTypeTag -import com.vitorpamplona.quartz.nip94FileMetadata.tags.ServiceTag -import com.vitorpamplona.quartz.nip94FileMetadata.tags.SizeTag -import com.vitorpamplona.quartz.nip94FileMetadata.tags.ThumbTag -import com.vitorpamplona.quartz.nip94FileMetadata.tags.TorrentInfoHash -import com.vitorpamplona.quartz.nip94FileMetadata.tags.UrlTag @Immutable abstract class VideoEvent( @@ -58,59 +46,7 @@ abstract class VideoEvent( sig: HexKey, ) : BaseAddressableEvent(id, pubKey, createdAt, kind, tags, content, sig), RootScope { - /** old standard didnt use IMetas **/ - private fun url() = tags.firstNotNullOfOrNull(UrlTag::parse) - - private fun urls() = tags.mapNotNull(UrlTag::parse) - - private fun mimeType() = tags.firstNotNullOfOrNull(MimeTypeTag::parse) - - private fun hash() = tags.firstNotNullOfOrNull(HashSha256Tag::parse) - - private fun size() = tags.firstNotNullOfOrNull(SizeTag::parse) - - private fun dimensions() = tags.firstNotNullOfOrNull(DimensionTag::parse) - - private fun magnetURI() = tags.firstNotNullOfOrNull(MagnetTag::parse) - - private fun torrentInfoHash() = tags.firstNotNullOfOrNull(TorrentInfoHash::parse) - - private fun blurhash() = tags.firstNotNullOfOrNull(BlurhashTag::parse) - - private fun hasUrl() = tags.any(UrlTag::isTag) - - private fun isOneOf(mimeTypes: Set) = tags.any(MimeTypeTag::isIn, mimeTypes) - - private fun image() = tags.firstNotNullOfOrNull(ImageTag::parse) - - private fun images() = tags.mapNotNull(ImageTag::parse) - - private fun thumb() = tags.firstNotNullOfOrNull(ThumbTag::parse) - - private fun service() = tags.firstNotNullOfOrNull(ServiceTag::parse) - - private fun fallbacks() = tags.mapNotNull(FallbackTag::parse) - - // hack to fix pablo's bug - fun rootVideo() = - url()?.let { - VideoMeta( - url = it, - mimeType = mimeType(), - blurhash = blurhash(), - alt = alt(), - hash = hash(), - dimension = dimensions(), - size = size(), - service = service(), - fallback = fallbacks(), - image = images().map { it.imageUrl }, - ) - } - - // --------------- - // current - // -------------- + @Transient var iMetas: List? = null fun title() = tags.firstNotNullOfOrNull(TitleTag::parse) @@ -126,5 +62,9 @@ abstract class VideoEvent( fun hashtags() = tags.hashtags() - fun imetaTags() = imetas().map { VideoMeta.parse(it) }.plus(rootVideo()).filterNotNull() + private fun mimeType() = tags.firstNotNullOfOrNull(MimeTypeTag::parse) + + private fun hash() = tags.firstNotNullOfOrNull(HashSha256Tag::parse) + + fun imetaTags() = iMetas ?: imetas().map { VideoMeta.parse(it) }.also { iMetas = it } } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip92IMeta/IMetaTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip92IMeta/IMetaTag.kt index 3bcba1c14..237b11d6b 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip92IMeta/IMetaTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip92IMeta/IMetaTag.kt @@ -48,7 +48,7 @@ class IMetaTag( ensure(tag[1].isNotEmpty()) { return null } val allTags = parseIMeta(tag) - val url = allTags.get(ANCHOR_PROPERTY)?.firstOrNull() + val url = allTags[ANCHOR_PROPERTY]?.firstOrNull() return if (url != null) { IMetaTag(url, allTags.minus(ANCHOR_PROPERTY))