Merge pull request #3452 from vitorpamplona/claude/quartz-searchable-event-audit-fhvjws

Implement SearchableEvent interface for NIP-50 search support
This commit is contained in:
Vitor Pamplona
2026-07-02 09:13:10 -04:00
committed by GitHub
16 changed files with 85 additions and 16 deletions
@@ -25,6 +25,7 @@ import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nip01Core.tags.events.firstTaggedEvent
import com.vitorpamplona.quartz.nip50Search.SearchableEvent
import com.vitorpamplona.quartz.utils.TimeUtils
@Immutable
@@ -35,11 +36,14 @@ class TextNoteModificationEvent(
tags: Array<Array<String>>,
content: String,
sig: HexKey,
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
) : Event(id, pubKey, createdAt, KIND, tags, content, sig),
SearchableEvent {
fun editedNote() = firstTaggedEvent()
fun summary() = tags.firstOrNull { it.size > 1 && it[0] == "summary" }?.get(1)
override fun indexableContent() = listOfNotNull(content, summary()).joinToString("\n")
companion object {
const val KIND = 1010
@@ -29,6 +29,7 @@ import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate
import com.vitorpamplona.quartz.nip01Core.tags.dTag.dTag
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hashtags
import com.vitorpamplona.quartz.nip21UriScheme.toNostrUri
import com.vitorpamplona.quartz.nip50Search.SearchableEvent
import com.vitorpamplona.quartz.utils.Log
import com.vitorpamplona.quartz.utils.TimeUtils
import kotlinx.coroutines.CancellationException
@@ -41,7 +42,8 @@ class AuctionEvent(
tags: Array<Array<String>>,
content: String,
sig: HexKey,
) : BaseAddressableEvent(id, pubKey, createdAt, KIND, tags, content, sig) {
) : BaseAddressableEvent(id, pubKey, createdAt, KIND, tags, content, sig),
SearchableEvent {
override fun isContentEncoded() = true
fun auctionData(): AuctionData? =
@@ -53,6 +55,8 @@ class AuctionEvent(
null
}
override fun indexableContent() = auctionData()?.let { listOfNotNull(it.name, it.description).joinToString("\n") } ?: ""
companion object {
const val KIND = 30020
@@ -28,6 +28,7 @@ import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate
import com.vitorpamplona.quartz.nip01Core.tags.dTag.dTag
import com.vitorpamplona.quartz.nip21UriScheme.toNostrUri
import com.vitorpamplona.quartz.nip50Search.SearchableEvent
import com.vitorpamplona.quartz.utils.Log
import com.vitorpamplona.quartz.utils.TimeUtils
import kotlinx.coroutines.CancellationException
@@ -40,7 +41,8 @@ class MarketplaceEvent(
tags: Array<Array<String>>,
content: String,
sig: HexKey,
) : BaseAddressableEvent(id, pubKey, createdAt, KIND, tags, content, sig) {
) : BaseAddressableEvent(id, pubKey, createdAt, KIND, tags, content, sig),
SearchableEvent {
override fun isContentEncoded() = true
fun marketplaceData(): MarketplaceData? =
@@ -52,6 +54,8 @@ class MarketplaceEvent(
null
}
override fun indexableContent() = marketplaceData()?.let { listOfNotNull(it.name, it.about).joinToString("\n") } ?: ""
companion object {
const val KIND = 30019
@@ -29,6 +29,7 @@ import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate
import com.vitorpamplona.quartz.nip01Core.tags.dTag.dTag
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hashtags
import com.vitorpamplona.quartz.nip21UriScheme.toNostrUri
import com.vitorpamplona.quartz.nip50Search.SearchableEvent
import com.vitorpamplona.quartz.utils.Log
import com.vitorpamplona.quartz.utils.TimeUtils
import kotlinx.coroutines.CancellationException
@@ -41,7 +42,8 @@ class ProductEvent(
tags: Array<Array<String>>,
content: String,
sig: HexKey,
) : BaseAddressableEvent(id, pubKey, createdAt, KIND, tags, content, sig) {
) : BaseAddressableEvent(id, pubKey, createdAt, KIND, tags, content, sig),
SearchableEvent {
override fun isContentEncoded() = true
fun productData(): ProductData? =
@@ -55,6 +57,8 @@ class ProductEvent(
fun categories() = tags.hashtags()
override fun indexableContent() = productData()?.let { (listOfNotNull(it.name, it.description) + categories()).joinToString("\n") } ?: ""
companion object {
const val KIND = 30018
@@ -28,6 +28,7 @@ import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate
import com.vitorpamplona.quartz.nip01Core.tags.dTag.dTag
import com.vitorpamplona.quartz.nip21UriScheme.toNostrUri
import com.vitorpamplona.quartz.nip50Search.SearchableEvent
import com.vitorpamplona.quartz.utils.Log
import com.vitorpamplona.quartz.utils.TimeUtils
import kotlinx.coroutines.CancellationException
@@ -40,7 +41,8 @@ class StallEvent(
tags: Array<Array<String>>,
content: String,
sig: HexKey,
) : BaseAddressableEvent(id, pubKey, createdAt, KIND, tags, content, sig) {
) : BaseAddressableEvent(id, pubKey, createdAt, KIND, tags, content, sig),
SearchableEvent {
override fun isContentEncoded() = true
fun stallData(): StallData? =
@@ -52,6 +54,8 @@ class StallEvent(
null
}
override fun indexableContent() = stallData()?.let { listOfNotNull(it.name, it.description).joinToString("\n") } ?: ""
companion object {
const val KIND = 30017
@@ -24,8 +24,10 @@ 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.firstTagValue
import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate
import com.vitorpamplona.quartz.nip29RelayGroups.metadata.GroupMetadataEvent
import com.vitorpamplona.quartz.nip50Search.SearchableEvent
import com.vitorpamplona.quartz.utils.TimeUtils
@Immutable
@@ -36,11 +38,18 @@ class EditMetadataEvent(
tags: Array<Array<String>>,
content: String,
sig: HexKey,
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
) : Event(id, pubKey, createdAt, KIND, tags, content, sig),
SearchableEvent {
fun groupId() = tags.groupId()
fun name() = tags.firstTagValue("name")
fun about() = tags.firstTagValue("about")
fun previousEvents() = tags.previousEvents()
override fun indexableContent() = listOfNotNull(name(), about()).joinToString("\n")
companion object {
const val KIND = 9002
@@ -41,6 +41,7 @@ import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
import com.vitorpamplona.quartz.nip01Core.tags.people.pTag
import com.vitorpamplona.quartz.nip32Labeling.tags.LabelNamespaceTag
import com.vitorpamplona.quartz.nip32Labeling.tags.LabelTag
import com.vitorpamplona.quartz.nip50Search.SearchableEvent
import com.vitorpamplona.quartz.utils.TimeUtils
/**
@@ -61,7 +62,10 @@ class LabelEvent(
) : Event(id, pubKey, createdAt, KIND, tags, content, sig),
EventHintProvider,
PubKeyHintProvider,
AddressHintProvider {
AddressHintProvider,
SearchableEvent {
override fun indexableContent() = (listOf(content) + labels().map { it.label }).filter { it.isNotEmpty() }.joinToString("\n")
override fun pubKeyHints(): List<PubKeyHint> = tags.mapNotNull(PTag::parseAsHint)
override fun linkedPubKeys(): List<HexKey> = tags.mapNotNull(PTag::parseKey)
@@ -28,6 +28,7 @@ import com.vitorpamplona.quartz.nip01Core.hints.PubKeyHintProvider
import com.vitorpamplona.quartz.nip01Core.tags.aTag.ATag
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
import com.vitorpamplona.quartz.nip10Notes.tags.MarkedETag
import com.vitorpamplona.quartz.nip50Search.SearchableEvent
/**
* Common base for NIP-34 status events (kinds 1630/1631/1632/1633). Each
@@ -46,7 +47,10 @@ abstract class GitStatusEvent(
) : Event(id, pubKey, createdAt, kind, tags, content, sig),
PubKeyHintProvider,
EventHintProvider,
AddressHintProvider {
AddressHintProvider,
SearchableEvent {
override fun indexableContent() = content
override fun pubKeyHints() = tags.mapNotNull(PTag::parseAsHint)
override fun linkedPubKeys() = tags.mapNotNull(PTag::parseKey)
@@ -33,6 +33,7 @@ import com.vitorpamplona.quartz.nip01Core.tags.dTag.dTag
import com.vitorpamplona.quartz.nip01Core.tags.events.ETag
import com.vitorpamplona.quartz.nip01Core.tags.events.firstTaggedEvent
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
import com.vitorpamplona.quartz.nip50Search.SearchableEvent
import com.vitorpamplona.quartz.nip52Calendar.appt.tags.RSVPStatusTag
import com.vitorpamplona.quartz.nip52Calendar.rsvp.tags.FreeBusyTag
import com.vitorpamplona.quartz.utils.TimeUtils
@@ -48,7 +49,10 @@ class CalendarRSVPEvent(
content: String,
sig: HexKey,
) : BaseAddressableEvent(id, pubKey, createdAt, KIND, tags, content, sig),
PubKeyHintProvider {
PubKeyHintProvider,
SearchableEvent {
override fun indexableContent() = content
override fun pubKeyHints() = tags.mapNotNull(PTag::parseAsHint)
override fun linkedPubKeys() = tags.mapNotNull(PTag::parseKey)
@@ -30,6 +30,7 @@ import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle
import com.vitorpamplona.quartz.nip01Core.hints.types.AddressHint
import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate
import com.vitorpamplona.quartz.nip01Core.tags.aTag.ATag
import com.vitorpamplona.quartz.nip50Search.SearchableEvent
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
import com.vitorpamplona.quartz.utils.TimeUtils
@@ -53,7 +54,10 @@ class LiveActivitiesRaidEvent(
content: String,
sig: HexKey,
) : Event(id, pubKey, createdAt, KIND, tags, content, sig),
AddressHintProvider {
AddressHintProvider,
SearchableEvent {
override fun indexableContent() = content
override fun addressHints(): List<AddressHint> = tags.mapNotNull(ATag::parseAsHint)
override fun linkedAddressIds(): List<String> = tags.mapNotNull(ATag::parseAddressId)
@@ -27,6 +27,7 @@ import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate
import com.vitorpamplona.quartz.nip01Core.tags.dTag.dTag
import com.vitorpamplona.quartz.nip40Expiration.expiration
import com.vitorpamplona.quartz.nip50Search.SearchableEvent
import com.vitorpamplona.quartz.nip69P2pOrderEvents.tags.FiatAmountTag
import com.vitorpamplona.quartz.nip69P2pOrderEvents.tags.OrderStatus
import com.vitorpamplona.quartz.nip69P2pOrderEvents.tags.OrderType
@@ -42,7 +43,10 @@ class P2POrderEvent(
tags: Array<Array<String>>,
content: String,
sig: HexKey,
) : BaseAddressableEvent(id, pubKey, createdAt, KIND, tags, content, sig) {
) : BaseAddressableEvent(id, pubKey, createdAt, KIND, tags, content, sig),
SearchableEvent {
override fun indexableContent() = (listOfNotNull(makerName(), currency()) + paymentMethods().orEmpty()).joinToString(" ")
fun orderType() = tags.orderType()
fun currency() = tags.currency()
@@ -25,6 +25,7 @@ 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.signers.eventTemplate
import com.vitorpamplona.quartz.nip50Search.SearchableEvent
import com.vitorpamplona.quartz.nip87Ecash.cashu.CashuMintEvent
import com.vitorpamplona.quartz.nip87Ecash.fedimint.FedimintEvent
import com.vitorpamplona.quartz.utils.TimeUtils
@@ -37,7 +38,10 @@ class MintRecommendationEvent(
tags: Array<Array<String>>,
content: String,
sig: HexKey,
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
) : Event(id, pubKey, createdAt, KIND, tags, content, sig),
SearchableEvent {
override fun indexableContent() = content
fun mintUrls() = tags.mintUrls()
fun mintEventKind() = tags.mintEventKind()
@@ -25,6 +25,7 @@ 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.signers.eventTemplate
import com.vitorpamplona.quartz.nip50Search.SearchableEvent
import com.vitorpamplona.quartz.nip90Dvms.tags.InputTag
import com.vitorpamplona.quartz.nip90Dvms.tags.dvmParam
import com.vitorpamplona.quartz.nip90Dvms.tags.firstInputByType
@@ -42,7 +43,10 @@ class NIP90ImageGenerationRequestEvent(
tags: Array<Array<String>>,
content: String,
sig: HexKey,
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
) : Event(id, pubKey, createdAt, KIND, tags, content, sig),
SearchableEvent {
override fun indexableContent() = listOfNotNull(prompt(), negativePrompt()).joinToString(" ")
fun inputs(): List<InputTag> = tags.inputs()
fun prompt(): String? = tags.firstInputByType("text")?.value
@@ -25,6 +25,7 @@ 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.signers.eventTemplate
import com.vitorpamplona.quartz.nip50Search.SearchableEvent
import com.vitorpamplona.quartz.nip90Dvms.tags.InputTag
import com.vitorpamplona.quartz.nip90Dvms.tags.dvmParam
import com.vitorpamplona.quartz.nip90Dvms.tags.inputPrompt
@@ -42,7 +43,10 @@ class NIP90TextGenerationRequestEvent(
tags: Array<Array<String>>,
content: String,
sig: HexKey,
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
) : Event(id, pubKey, createdAt, KIND, tags, content, sig),
SearchableEvent {
override fun indexableContent() = inputs().filter { it.type == "prompt" || it.type == "text" }.joinToString(" ") { it.value }
fun inputs(): List<InputTag> = tags.inputs()
fun outputMimeType(): String? = tags.outputMimeType()
@@ -25,6 +25,7 @@ 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.signers.eventTemplate
import com.vitorpamplona.quartz.nip50Search.SearchableEvent
import com.vitorpamplona.quartz.nip90Dvms.tags.InputTag
import com.vitorpamplona.quartz.nip90Dvms.tags.dvmParam
import com.vitorpamplona.quartz.nip90Dvms.tags.firstInputByType
@@ -41,7 +42,10 @@ class NIP90TextToSpeechRequestEvent(
tags: Array<Array<String>>,
content: String,
sig: HexKey,
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
) : Event(id, pubKey, createdAt, KIND, tags, content, sig),
SearchableEvent {
override fun indexableContent() = text() ?: ""
fun inputs(): List<InputTag> = tags.inputs()
fun text(): String? = tags.firstInputByType("text")?.value
@@ -29,6 +29,7 @@ import com.vitorpamplona.quartz.nip01Core.tags.dTag.dTag
import com.vitorpamplona.quartz.nip22Comments.RootScope
import com.vitorpamplona.quartz.nip31Alts.AltTag
import com.vitorpamplona.quartz.nip31Alts.alt
import com.vitorpamplona.quartz.nip50Search.SearchableEvent
import com.vitorpamplona.quartz.nipXXPodcasting20.episode.tags.PubDateTag
import com.vitorpamplona.quartz.nipXXPodcasting20.episode.tags.TitleTag
import com.vitorpamplona.quartz.nipXXPodcasting20.trailer.tags.LengthTag
@@ -51,7 +52,10 @@ class Podcasting20TrailerEvent(
content: String,
sig: HexKey,
) : BaseAddressableEvent(id, pubKey, createdAt, KIND, tags, content, sig),
RootScope {
RootScope,
SearchableEvent {
override fun indexableContent() = listOfNotNull(title(), content).joinToString("\n")
fun title() = tags.firstNotNullOfOrNull(TitleTag::parse)
fun url() = tags.firstNotNullOfOrNull(UrlTag::parse)