From 52e79580bf11379205dd86f2a5aa09a14206ccd7 Mon Sep 17 00:00:00 2001 From: Believethehype <1097224+believethehype@users.noreply.github.com> Date: Tue, 14 May 2024 17:03:13 +0200 Subject: [PATCH] attempt to add own filters and event kinds for NIP90 content related events --- .../amethyst/model/LocalCache.kt | 60 ++++++ .../service/NostrDiscoveryDataSource.kt | 22 +++ .../ui/dal/NIP90ContentDiscoveryFilter.kt | 62 ++++-- .../vitorpamplona/amethyst/ui/note/Loaders.kt | 22 --- .../amethyst/ui/note/NoteCompose.kt | 15 ++ .../types/NIP90ContentDiscoveryResponse.kt | 181 ++++++++++++++++++ .../amethyst/ui/screen/FeedViewModel.kt | 4 +- .../loggedIn/NIP90ContentDiscoveryScreen.kt | 16 +- .../NIP90ContentDiscoveryRequestEvent.kt | 8 +- .../NIP90ContentDiscoveryResponseEvent.kt | 53 +++++ .../quartz/events/NIP90StatusEvent.kt | 53 +++++ 11 files changed, 448 insertions(+), 48 deletions(-) create mode 100644 app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/NIP90ContentDiscoveryResponse.kt create mode 100644 quartz/src/main/java/com/vitorpamplona/quartz/events/NIP90ContentDiscoveryResponseEvent.kt create mode 100644 quartz/src/main/java/com/vitorpamplona/quartz/events/NIP90StatusEvent.kt diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt index 82346fcef..7f3b11e71 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt @@ -89,6 +89,8 @@ import com.vitorpamplona.quartz.events.LnZapRequestEvent import com.vitorpamplona.quartz.events.LongTextNoteEvent import com.vitorpamplona.quartz.events.MetadataEvent import com.vitorpamplona.quartz.events.MuteListEvent +import com.vitorpamplona.quartz.events.NIP90ContentDiscoveryRequestEvent +import com.vitorpamplona.quartz.events.NIP90ContentDiscoveryResponseEvent import com.vitorpamplona.quartz.events.NNSEvent import com.vitorpamplona.quartz.events.OtsEvent import com.vitorpamplona.quartz.events.PeopleListEvent @@ -384,6 +386,62 @@ object LocalCache { refreshObservers(note) } + fun consume( + event: NIP90ContentDiscoveryResponseEvent, + relay: Relay? = null, + ) { + val note = getOrCreateNote(event.id) + val author = getOrCreateUser(event.pubKey) + + if (relay != null) { + author.addRelayBeingUsed(relay, event.createdAt) + note.addRelay(relay) + } + + // Already processed this event. + if (note.event != null) return + + val replyTo = computeReplyTo(event) + + note.loadEvent(event, author, replyTo) + + // Log.d("TN", "New Note (${notes.size},${users.size}) ${note.author?.toBestDisplayName()} + // ${note.event?.content()?.split("\n")?.take(100)} ${formattedDateTime(event.createdAt)}") + + // Counts the replies + replyTo.forEach { it.addReply(note) } + + refreshObservers(note) + } + + fun consume( + event: NIP90ContentDiscoveryRequestEvent, + relay: Relay? = null, + ) { + val note = getOrCreateNote(event.id) + val author = getOrCreateUser(event.pubKey) + + if (relay != null) { + author.addRelayBeingUsed(relay, event.createdAt) + note.addRelay(relay) + } + + // Already processed this event. + if (note.event != null) return + + val replyTo = computeReplyTo(event) + + note.loadEvent(event, author, replyTo) + + // Log.d("TN", "New Note (${notes.size},${users.size}) ${note.author?.toBestDisplayName()} + // ${note.event?.content()?.split("\n")?.take(100)} ${formattedDateTime(event.createdAt)}") + + // Counts the replies + replyTo.forEach { it.addReply(note) } + + refreshObservers(note) + } + fun consume( event: GitPatchEvent, relay: Relay? = null, @@ -2299,6 +2357,8 @@ object LocalCache { } } is LnZapRequestEvent -> consume(event) + is NIP90ContentDiscoveryResponseEvent -> consume(event, relay) + is NIP90ContentDiscoveryRequestEvent -> consume(event, relay) is LnZapPaymentRequestEvent -> consume(event) is LnZapPaymentResponseEvent -> consume(event) is LongTextNoteEvent -> consume(event, relay) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDiscoveryDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDiscoveryDataSource.kt index 91c572357..01018a695 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDiscoveryDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDiscoveryDataSource.kt @@ -35,6 +35,8 @@ import com.vitorpamplona.quartz.events.CommunityDefinitionEvent import com.vitorpamplona.quartz.events.CommunityPostApprovalEvent import com.vitorpamplona.quartz.events.LiveActivitiesChatMessageEvent import com.vitorpamplona.quartz.events.LiveActivitiesEvent +import com.vitorpamplona.quartz.events.NIP90ContentDiscoveryResponseEvent +import com.vitorpamplona.quartz.events.NIP90StatusEvent import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.launch @@ -151,6 +153,24 @@ object NostrDiscoveryDataSource : NostrDataSource("DiscoveryFeed") { ) } + fun createNIP90ResponseFilter(): List { + return listOfNotNull( + TypedFilter( + types = setOf(FeedType.GLOBAL), + filter = + JsonFilter( + kinds = listOf(NIP90ContentDiscoveryResponseEvent.KIND, NIP90StatusEvent.KIND), + limit = 300, + since = + latestEOSEs.users[account.userProfile()] + ?.followList + ?.get(account.defaultDiscoveryFollowList.value) + ?.relayList, + ), + ), + ) + } + fun createLiveStreamFilter(): List { val follows = account.liveDiscoveryFollowLists.value?.users?.toList() @@ -425,6 +445,7 @@ object NostrDiscoveryDataSource : NostrDataSource("DiscoveryFeed") { discoveryFeedChannel.typedFilters = createLiveStreamFilter() .plus(createNIP89Filter(listOf("5300"))) + .plus(createNIP90ResponseFilter()) .plus(createPublicChatFilter()) .plus(createMarketplaceFilter()) .plus( @@ -438,6 +459,7 @@ object NostrDiscoveryDataSource : NostrDataSource("DiscoveryFeed") { createPublicChatsGeohashesFilter(), ), ) + .toList() .ifEmpty { null } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/NIP90ContentDiscoveryFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/NIP90ContentDiscoveryFilter.kt index 25161069a..411e76d05 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/NIP90ContentDiscoveryFilter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/NIP90ContentDiscoveryFilter.kt @@ -23,24 +23,64 @@ package com.vitorpamplona.amethyst.ui.dal import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.quartz.events.MuteListEvent +import com.vitorpamplona.quartz.events.NIP90ContentDiscoveryResponseEvent +import com.vitorpamplona.quartz.events.PeopleListEvent -class NIP90ContentDiscoveryFilter(val account: Account) : FeedFilter() { +open class NIP90ContentDiscoveryFilter( + val account: Account, +) : AdditiveFeedFilter() { override fun feedKey(): String { - return account.userProfile().latestBookmarkList?.id ?: "" + return account.userProfile().pubkeyHex + "-" + followList() + } + + open fun followList(): String { + return account.defaultDiscoveryFollowList.value + } + + override fun showHiddenKey(): Boolean { + return followList() == PeopleListEvent.blockListFor(account.userProfile().pubkeyHex) || + followList() == MuteListEvent.blockListFor(account.userProfile().pubkeyHex) } - // TODO override fun feed(): List { - val bookmarks = account.userProfile().latestBookmarkList + val params = buildFilterParams(account) val notes = - bookmarks?.taggedEvents()?.mapNotNull { LocalCache.checkGetOrCreateNote(it) } ?: emptyList() - val addresses = - bookmarks?.taggedAddresses()?.map { LocalCache.getOrCreateAddressableNote(it) } ?: emptyList() + LocalCache.notes.filterIntoSet { _, it -> + val noteEvent = it.event + noteEvent is NIP90ContentDiscoveryResponseEvent // && params.match(noteEvent) + } - return notes - .plus(addresses) - .toSet() - .sortedWith(DefaultFeedOrder) + return sort(notes) + } + + override fun applyFilter(collection: Set): Set { + var result = innerApplyFilter(collection) + println("Test World") + println(result) + return result + } + + fun buildFilterParams(account: Account): FilterByListParams { + return FilterByListParams.create( + account.userProfile().pubkeyHex, + account.defaultDiscoveryFollowList.value, + account.liveDiscoveryFollowLists.value, + account.flowHiddenUsers.value, + ) + } + + protected open fun innerApplyFilter(collection: Collection): Set { + val params = buildFilterParams(account) + + return collection.filterTo(HashSet()) { + val noteEvent = it.event + noteEvent is NIP90ContentDiscoveryResponseEvent // && params.match(noteEvent) + } + } + + override fun sort(collection: Set): List { + return collection.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).reversed() } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/Loaders.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/Loaders.kt index f7bab1a9d..3379d9025 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/Loaders.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/Loaders.kt @@ -234,25 +234,3 @@ fun LoadChannel( channel?.let { content(it) } } - -@Composable -fun LoadDVMNip89( - baseChannelHex: String, - accountViewModel: AccountViewModel, - content: @Composable (Channel) -> Unit, -) { - var channel by - remember(baseChannelHex) { - mutableStateOf(accountViewModel.getChannelIfExists(baseChannelHex)) - } - - if (channel == null) { - LaunchedEffect(key1 = baseChannelHex) { - accountViewModel.checkGetOrCreateChannel(baseChannelHex) { newChannel -> - launch(Dispatchers.Main) { channel = newChannel } - } - } - } - - channel?.let { content(it) } -} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt index 8b0d0dc87..9171ed4fa 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt @@ -96,6 +96,7 @@ import com.vitorpamplona.amethyst.ui.note.types.RenderHighlight import com.vitorpamplona.amethyst.ui.note.types.RenderLiveActivityChatMessage import com.vitorpamplona.amethyst.ui.note.types.RenderLiveActivityEvent import com.vitorpamplona.amethyst.ui.note.types.RenderLongFormContent +import com.vitorpamplona.amethyst.ui.note.types.RenderNIP90ContentDiscoveryResponse import com.vitorpamplona.amethyst.ui.note.types.RenderPinListEvent import com.vitorpamplona.amethyst.ui.note.types.RenderPoll import com.vitorpamplona.amethyst.ui.note.types.RenderPostApproval @@ -157,6 +158,7 @@ import com.vitorpamplona.quartz.events.HighlightEvent import com.vitorpamplona.quartz.events.LiveActivitiesChatMessageEvent import com.vitorpamplona.quartz.events.LiveActivitiesEvent import com.vitorpamplona.quartz.events.LongTextNoteEvent +import com.vitorpamplona.quartz.events.NIP90ContentDiscoveryResponseEvent import com.vitorpamplona.quartz.events.PeopleListEvent import com.vitorpamplona.quartz.events.PinListEvent import com.vitorpamplona.quartz.events.PollNoteEvent @@ -646,6 +648,19 @@ private fun RenderNoteRow( nav, ) } + is NIP90ContentDiscoveryResponseEvent -> + RenderNIP90ContentDiscoveryResponse( + baseNote, + makeItShort, + canPreview, + quotesLeft, + unPackReply, + backgroundColor, + editState, + accountViewModel, + nav, + ) + is PollNoteEvent -> { RenderPoll( baseNote, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/NIP90ContentDiscoveryResponse.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/NIP90ContentDiscoveryResponse.kt new file mode 100644 index 000000000..dad8a5460 --- /dev/null +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/NIP90ContentDiscoveryResponse.kt @@ -0,0 +1,181 @@ +/** + * Copyright (c) 2024 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.note.types + +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.State +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.style.TextOverflow +import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.ui.components.GenericLoadable +import com.vitorpamplona.amethyst.ui.components.SensitivityWarning +import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer +import com.vitorpamplona.amethyst.ui.note.LoadDecryptedContent +import com.vitorpamplona.amethyst.ui.note.ReplyNoteComposition +import com.vitorpamplona.amethyst.ui.note.elements.DisplayUncitedHashtags +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer +import com.vitorpamplona.amethyst.ui.theme.placeholderText +import com.vitorpamplona.quartz.events.BaseTextNoteEvent +import com.vitorpamplona.quartz.events.CommunityDefinitionEvent +import com.vitorpamplona.quartz.events.EmptyTagList +import com.vitorpamplona.quartz.events.TextNoteEvent +import com.vitorpamplona.quartz.events.toImmutableListOfLists +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList + +@Composable +fun RenderNIP90ContentDiscoveryResponse( + note: Note, + makeItShort: Boolean, + canPreview: Boolean, + quotesLeft: Int, + unPackReply: Boolean, + backgroundColor: MutableState, + editState: State>, + accountViewModel: AccountViewModel, + nav: (String) -> Unit, +) { + val noteEvent = note.event + val modifier = remember(note) { Modifier.fillMaxWidth() } + + if (noteEvent != null) { + TranslatableRichTextViewer( + content = noteEvent.content(), + canPreview = canPreview && !makeItShort, + quotesLeft = quotesLeft, + modifier = modifier, + tags = noteEvent.tags().toImmutableListOfLists(), + backgroundColor = backgroundColor, + id = note.idHex, + accountViewModel = accountViewModel, + nav = nav, + ) + } + + val showReply by + remember(note) { + derivedStateOf { + noteEvent is BaseTextNoteEvent && !makeItShort && unPackReply && (note.replyTo != null || noteEvent.hasAnyTaggedUser()) + } + } + + if (showReply) { + val replyingDirectlyTo = + remember(note) { + if (noteEvent is BaseTextNoteEvent) { + val replyingTo = noteEvent.replyingToAddressOrEvent() + if (replyingTo != null) { + val newNote = accountViewModel.getNoteIfExists(replyingTo) + if (newNote != null && newNote.channelHex() == null && newNote.event?.kind() != CommunityDefinitionEvent.KIND) { + newNote + } else { + note.replyTo?.lastOrNull { it.event?.kind() != CommunityDefinitionEvent.KIND } + } + } else { + note.replyTo?.lastOrNull { it.event?.kind() != CommunityDefinitionEvent.KIND } + } + } else { + note.replyTo?.lastOrNull { it.event?.kind() != CommunityDefinitionEvent.KIND } + } + } + if (replyingDirectlyTo != null && unPackReply) { + ReplyNoteComposition(replyingDirectlyTo, backgroundColor, accountViewModel, nav) + Spacer(modifier = StdVertSpacer) + } + } + + LoadDecryptedContent( + note, + accountViewModel, + ) { body -> + val eventContent by + remember(note.event) { + derivedStateOf { + val subject = (note.event as? TextNoteEvent)?.subject()?.ifEmpty { null } + val newBody = + if (editState.value is GenericLoadable.Loaded) { + val state = + (editState.value as? GenericLoadable.Loaded)?.loaded?.modificationToShow + state?.value?.event?.content() ?: body + } else { + body + } + + if (!subject.isNullOrBlank() && !newBody.split("\n")[0].contains(subject)) { + "### $subject\n$newBody" + } else { + newBody + } + } + } + + val isAuthorTheLoggedUser = + remember(note.event) { accountViewModel.isLoggedUser(note.author) } + + if (makeItShort && isAuthorTheLoggedUser) { + Text( + text = eventContent, + color = MaterialTheme.colorScheme.placeholderText, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + ) + } else { + SensitivityWarning( + note = note, + accountViewModel = accountViewModel, + ) { + val modifier = remember(note) { Modifier.fillMaxWidth() } + val tags = + remember(note) { note.event?.tags()?.toImmutableListOfLists() ?: EmptyTagList } + + TranslatableRichTextViewer( + content = eventContent, + canPreview = canPreview && !makeItShort, + quotesLeft = quotesLeft, + modifier = modifier, + tags = tags, + backgroundColor = backgroundColor, + id = note.idHex, + accountViewModel = accountViewModel, + nav = nav, + ) + } + + if (note.event?.hasHashtags() == true) { + val hashtags = + remember(note.event) { + note.event?.hashtags()?.toImmutableList() ?: persistentListOf() + } + DisplayUncitedHashtags(hashtags, eventContent, nav) + } + } + } +} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt index a10a8a1a0..0169f92c3 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt @@ -54,6 +54,7 @@ import com.vitorpamplona.amethyst.ui.dal.GeoHashFeedFilter import com.vitorpamplona.amethyst.ui.dal.HashtagFeedFilter import com.vitorpamplona.amethyst.ui.dal.HomeConversationsFeedFilter import com.vitorpamplona.amethyst.ui.dal.HomeNewThreadFeedFilter +import com.vitorpamplona.amethyst.ui.dal.NIP90ContentDiscoveryFilter import com.vitorpamplona.amethyst.ui.dal.ThreadFeedFilter import com.vitorpamplona.amethyst.ui.dal.UserProfileAppRecommendationsFeedFilter import com.vitorpamplona.amethyst.ui.dal.UserProfileBookmarksFeedFilter @@ -283,7 +284,8 @@ class NostrBookmarkPrivateFeedViewModel(val account: Account) : @Stable class NostrNIP90ContentDiscoveryFeedViewModel(val account: Account) : - FeedViewModel(BookmarkPrivateFeedFilter(account)) { + // FeedViewModel(BookmarkPrivateFeedFilter(account)) { + FeedViewModel(NIP90ContentDiscoveryFilter(account)) { class Factory(val account: Account) : ViewModelProvider.Factory { override fun create(modelClass: Class): NostrNIP90ContentDiscoveryFeedViewModel { return NostrNIP90ContentDiscoveryFeedViewModel(account) as NostrNIP90ContentDiscoveryFeedViewModel diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NIP90ContentDiscoveryScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NIP90ContentDiscoveryScreen.kt index b4ac148e7..fa9e7721d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NIP90ContentDiscoveryScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NIP90ContentDiscoveryScreen.kt @@ -74,18 +74,18 @@ private fun RenderNostrNIP90ContentDiscoveryScreen( } if (DVMID != null) { - // TODO 1 Send KIND 5300 Event with p tag = DVMID (crashes) + // TODO 1 Send KIND 5300 Event with p tag = DVMID (crashes, because cant map to event) - /* - var signer = accountViewModel.account.signer + /*val signer: NostrSigner = NostrSignerInternal(accountViewModel.account.keyPair) + println(accountViewModel.account.keyPair.pubKey.toHexKey()) NIP90ContentDiscoveryRequestEvent.create(DVMID, signer) { - // Client.send(it) - // LocalCache.justConsume(it, null) - } + Client.send(it) + LocalCache.justConsume(it, null) + } */ - */ + // var keyPair = accountViewModel.account.keyPair - // TODO 2 PARSE AND LOAD RESULTS FROM KIND 6300 REPLY to resultfeedmodel (RN this still is the bookmark list) + // TODO 2 PARSE AND LOAD RESULTS FROM KIND 6300 REPLY to resultfeedmodel (RN this doesnt show events) // TODO 3 Render Results (hopefully works when 2 is working) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/NIP90ContentDiscoveryRequestEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/NIP90ContentDiscoveryRequestEvent.kt index d8dbf27b8..d205a743b 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/NIP90ContentDiscoveryRequestEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/NIP90ContentDiscoveryRequestEvent.kt @@ -35,9 +35,7 @@ class NIP90ContentDiscoveryRequestEvent( tags: Array>, content: String, sig: HexKey, -) : BaseAddressableEvent(id, pubKey, createdAt, KIND, tags, content, sig) { - @Transient private var cachedMetadata: AppMetadata? = null - +) : Event(id, pubKey, createdAt, KIND, tags, content, sig) { companion object { const val KIND = 5300 @@ -48,12 +46,10 @@ class NIP90ContentDiscoveryRequestEvent( onReady: (NIP90ContentDiscoveryRequestEvent) -> Unit, ) { val content = "" - - // val clientTag = arrayOf("client", "Amethyst") - val tags = mutableListOf>() tags.add(arrayOf("p", addressedDVM)) tags.add(arrayOf("alt", "NIP90 Content Discovery request")) + tags.add(arrayOf("client", "Amethyst")) signer.sign(createdAt, KIND, tags.toTypedArray(), content, onReady) } } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/NIP90ContentDiscoveryResponseEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/NIP90ContentDiscoveryResponseEvent.kt new file mode 100644 index 000000000..8f76eb125 --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/NIP90ContentDiscoveryResponseEvent.kt @@ -0,0 +1,53 @@ +/** + * Copyright (c) 2024 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.events + +import androidx.compose.runtime.Immutable +import com.vitorpamplona.quartz.encoders.HexKey +import com.vitorpamplona.quartz.signers.NostrSigner +import com.vitorpamplona.quartz.utils.TimeUtils + +@Immutable +class NIP90ContentDiscoveryResponseEvent( + id: HexKey, + pubKey: HexKey, + createdAt: Long, + tags: Array>, + content: String, + sig: HexKey, +) : Event(id, pubKey, createdAt, KIND, tags, content, sig) { + companion object { + const val KIND = 6300 + const val ALT = "NIP90 Content Discovery reply" + + fun create( + signer: NostrSigner, + createdAt: Long = TimeUtils.now(), + onReady: (AppRecommendationEvent) -> Unit, + ) { + val tags = + arrayOf( + arrayOf("alt", ALT), + ) + signer.sign(createdAt, KIND, tags, "", onReady) + } + } +} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/NIP90StatusEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/NIP90StatusEvent.kt new file mode 100644 index 000000000..2445aa41d --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/NIP90StatusEvent.kt @@ -0,0 +1,53 @@ +/** + * Copyright (c) 2024 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.events + +import androidx.compose.runtime.Immutable +import com.vitorpamplona.quartz.encoders.HexKey +import com.vitorpamplona.quartz.signers.NostrSigner +import com.vitorpamplona.quartz.utils.TimeUtils + +@Immutable +class NIP90StatusEvent( + id: HexKey, + pubKey: HexKey, + createdAt: Long, + tags: Array>, + content: String, + sig: HexKey, +) : Event(id, pubKey, createdAt, KIND, tags, content, sig) { + companion object { + const val KIND = 7000 + const val ALT = "NIP90 Content Discovery reply" + + fun create( + signer: NostrSigner, + createdAt: Long = TimeUtils.now(), + onReady: (AppRecommendationEvent) -> Unit, + ) { + val tags = + arrayOf( + arrayOf("alt", ALT), + ) + signer.sign(createdAt, KIND, tags, "", onReady) + } + } +}