Merge pull request #3743 from vitorpamplona/claude/event-searchable-review-swvvqu

Add SearchableEvent implementation to 18 event types
This commit is contained in:
Vitor Pamplona
2026-07-27 11:55:27 -04:00
committed by GitHub
23 changed files with 85 additions and 23 deletions
@@ -25,6 +25,7 @@ import com.vitorpamplona.quartz.nip01Core.core.BaseReplaceableEvent
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.utils.TimeUtils
import kotlinx.coroutines.CancellationException
@@ -47,7 +48,10 @@ class AgentProfileEvent(
tags: Array<Array<String>>,
content: String,
sig: HexKey,
) : BaseReplaceableEvent(id, pubKey, createdAt, KIND, tags, content, sig) {
) : BaseReplaceableEvent(id, pubKey, createdAt, KIND, tags, content, sig),
SearchableEvent {
override fun indexableContent() = profileOrNull()?.let { listOfNotNull(it.name, it.displayName).joinToString("\n") } ?: ""
/** Parses the profile metadata, or throws if the JSON is malformed. */
fun profile(): AgentProfileContent = AgentProfileContent.decodeFromJson(content)
@@ -26,6 +26,7 @@ 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.nip01Core.tags.dTag.dTag
import com.vitorpamplona.quartz.nip50Search.SearchableEvent
import com.vitorpamplona.quartz.utils.TimeUtils
import kotlinx.coroutines.CancellationException
@@ -46,7 +47,10 @@ class PersonaEvent(
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() = personaOrNull()?.let { listOfNotNull(it.displayName, it.systemPrompt).joinToString("\n") } ?: ""
/** The persona slug — the `d` tag. */
fun slug() = dTag()
@@ -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.utils.TimeUtils
/**
@@ -41,7 +42,10 @@ class ForumCommentEvent(
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
/** The channel UUID (the `h` tag) this comment belongs to. */
fun channel() = tags.forumChannel()
@@ -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.utils.TimeUtils
/**
@@ -41,7 +42,10 @@ class ForumPostEvent(
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
/** The channel UUID (the `h` tag) this post belongs to. */
fun channel() = tags.forumChannel()
@@ -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.utils.TimeUtils
/**
@@ -45,7 +46,10 @@ class HuddleGuidelinesEvent(
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
/** The channel UUID these guidelines apply to — the `h` tag. */
fun channelId(): String? = tags.huddleChannel()
@@ -26,6 +26,7 @@ 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.nip01Core.tags.dTag.dTag
import com.vitorpamplona.quartz.nip50Search.SearchableEvent
import com.vitorpamplona.quartz.utils.TimeUtils
import kotlinx.coroutines.CancellationException
@@ -47,7 +48,10 @@ class ManagedAgentEvent(
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() = agentOrNull()?.let { listOfNotNull(it.name, it.systemPrompt).joinToString("\n") } ?: ""
/** The managed agent's pubkey — the `d` tag. */
fun agentPubKey() = dTag()
@@ -26,6 +26,7 @@ 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.nip01Core.tags.dTag.dTag
import com.vitorpamplona.quartz.nip50Search.SearchableEvent
import com.vitorpamplona.quartz.utils.TimeUtils
import kotlinx.coroutines.CancellationException
@@ -45,7 +46,10 @@ class TeamEvent(
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() = teamOrNull()?.let { listOfNotNull(it.name, it.description, it.instructions).joinToString("\n") } ?: ""
/** The team's stable id — the `d` tag. */
fun teamId() = dTag()
@@ -26,6 +26,7 @@ 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.nip01Core.tags.dTag.dTag
import com.vitorpamplona.quartz.nip50Search.SearchableEvent
import com.vitorpamplona.quartz.utils.TimeUtils
/**
@@ -43,7 +44,10 @@ class WorkflowDefEvent(
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(name(), content).joinToString("\n")
/** The workflow UUID - the NIP-33 `d` tag. */
fun workflowId() = dTag()
@@ -24,6 +24,7 @@ import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.tags.events.firstTaggedEvent
import com.vitorpamplona.quartz.nip50Search.SearchableEvent
/**
* A Concord Chat Plane **message edit** (CORD-02 Appendix B, `kind:3302`).
@@ -45,7 +46,10 @@ class ConcordChatEditEvent(
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
/** The id of the message this edit replaces (its `e` tag), or null if malformed. */
fun editedMessageId(): HexKey? = firstTaggedEvent()?.eventId
@@ -37,6 +37,7 @@ import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate
import com.vitorpamplona.quartz.nip01Core.tags.aTag.ATag
import com.vitorpamplona.quartz.nip01Core.tags.dTag.dTag
import com.vitorpamplona.quartz.nip01Core.tags.events.ETag
import com.vitorpamplona.quartz.nip50Search.SearchableEvent
import com.vitorpamplona.quartz.utils.TimeUtils
@Immutable
@@ -49,7 +50,10 @@ class AttestationEvent(
sig: HexKey,
) : BaseAddressableEvent(id, pubKey, createdAt, KIND, tags, content, sig),
EventHintProvider,
AddressHintProvider {
AddressHintProvider,
SearchableEvent {
override fun indexableContent() = content
override fun eventHints(): List<EventIdHint> = tags.mapNotNull(ETag::parseAsHint)
override fun linkedEventIds(): List<HexKey> = tags.mapNotNull(ETag::parseId)
@@ -37,6 +37,7 @@ import com.vitorpamplona.quartz.nip01Core.tags.aTag.ATag
import com.vitorpamplona.quartz.nip01Core.tags.dTag.dTag
import com.vitorpamplona.quartz.nip01Core.tags.events.ETag
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
import com.vitorpamplona.quartz.nip50Search.SearchableEvent
import com.vitorpamplona.quartz.utils.TimeUtils
@Immutable
@@ -50,7 +51,10 @@ class AttestationRequestEvent(
) : BaseAddressableEvent(id, pubKey, createdAt, KIND, tags, content, sig),
EventHintProvider,
AddressHintProvider,
PubKeyHintProvider {
PubKeyHintProvider,
SearchableEvent {
override fun indexableContent() = content
override fun eventHints(): List<EventIdHint> = tags.mapNotNull(ETag::parseAsHint)
override fun linkedEventIds(): List<HexKey> = tags.mapNotNull(ETag::parseId)
@@ -57,7 +57,7 @@ class BirdexEvent(
sig: HexKey,
) : BaseReplaceableEvent(id, pubKey, createdAt, KIND, tags, content, sig),
SearchableEvent {
override fun indexableContent() = listOfNotNull(summary()).joinToString("\n")
override fun indexableContent() = (listOfNotNull(summary()) + speciesNames()).joinToString("\n")
/** Scientific names of the collected species, in event order, from the `n` tags. */
fun speciesNames() = tags.mapValueTagged("n") { it }
@@ -60,7 +60,7 @@ class Ps1SaveEvent(
sig: HexKey,
) : BaseAddressableEvent(id, pubKey, createdAt, KIND, tags, content, sig),
SearchableEvent {
override fun indexableContent() = listOfNotNull(summary(), saveTitle(), filename()).joinToString("\n")
override fun indexableContent() = listOfNotNull(summary(), saveTitle(), region(), filename()).joinToString("\n")
/** Human-readable save name, from the `title` tag (may be null). */
fun saveTitle() = tags.firstTagValue("title")
@@ -33,6 +33,7 @@ import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate
import com.vitorpamplona.quartz.nip01Core.tags.geohash.geohashes
import com.vitorpamplona.quartz.nip31Alts.AltTag
import com.vitorpamplona.quartz.nip40Expiration.expiration
import com.vitorpamplona.quartz.nip50Search.SearchableEvent
import com.vitorpamplona.quartz.utils.TimeUtils
/**
@@ -60,7 +61,10 @@ class RoadEventReportEvent(
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
/** The parsed [RoadEventType], or null when the `t` tag is missing/unknown. */
fun roadEventType() = tags.roadEventType()
@@ -55,7 +55,7 @@ class AuctionEvent(
null
}
override fun indexableContent() = auctionData()?.let { listOfNotNull(it.name, it.description).joinToString("\n") } ?: ""
override fun indexableContent() = auctionData()?.let { (listOfNotNull(it.name, it.description) + tags.hashtags()).joinToString("\n") } ?: ""
companion object {
const val KIND = 30020
@@ -36,6 +36,7 @@ import com.vitorpamplona.quartz.nip01Core.hints.types.EventIdHint
import com.vitorpamplona.quartz.nip01Core.hints.types.PubKeyHint
import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate
import com.vitorpamplona.quartz.nip01Core.tags.geohash.GeoHashTag
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hashtags
import com.vitorpamplona.quartz.nip10Notes.BaseThreadedEvent
import com.vitorpamplona.quartz.nip18Reposts.quotes.QTag
import com.vitorpamplona.quartz.nip19Bech32.addressHints
@@ -75,7 +76,7 @@ class CommentEvent(
PubKeyHintProvider,
AddressHintProvider,
SearchableEvent {
override fun indexableContent() = content
override fun indexableContent() = (listOf(content) + tags.hashtags()).joinToString("\n")
override fun pubKeyHints(): List<PubKeyHint> {
val pHints =
@@ -66,7 +66,7 @@ class EditMetadataEvent(
fun previousEvents() = tags.previousEvents()
override fun indexableContent() = listOfNotNull(name(), about()).joinToString("\n")
override fun indexableContent() = (listOfNotNull(name(), about()) + hashtags()).joinToString("\n")
companion object {
const val KIND = 9002
@@ -35,6 +35,7 @@ import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate
import com.vitorpamplona.quartz.nip01Core.tags.aTag.ATag
import com.vitorpamplona.quartz.nip01Core.tags.aTag.aTag
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.nip10Notes.BaseThreadedEvent
import com.vitorpamplona.quartz.nip10Notes.tags.markedETag
@@ -65,7 +66,7 @@ class LiveActivitiesChatMessageEvent(
AddressHintProvider,
ExposeInDraft,
SearchableEvent {
override fun indexableContent() = content
override fun indexableContent() = (listOf(content) + tags.hashtags()).joinToString("\n")
override fun eventHints(): List<EventIdHint> {
val eHints = tags.mapNotNull(ETag::parseAsHint)
@@ -53,7 +53,7 @@ class ContactCardEvent(
SearchableEvent {
// Only the public summary tag is indexed; the rest of the card lives in
// NIP-44 encrypted content and is intentionally never indexed.
override fun indexableContent() = listOfNotNull(summary()).joinToString("\n")
override fun indexableContent() = (listOfNotNull(summary()) + topics()).joinToString("\n")
fun aboutUser() = tags.dTag()
@@ -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 NIP90ContentSearchRequestEvent(
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() = searchQuery() ?: ""
fun inputs(): List<InputTag> = tags.inputs()
fun searchQuery(): 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.firstInputByType
@@ -41,7 +42,10 @@ class NIP90PeopleSearchRequestEvent(
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() = searchQuery() ?: ""
fun inputs(): List<InputTag> = tags.inputs()
fun searchQuery(): String? = tags.firstInputByType("text")?.value
@@ -36,6 +36,7 @@ import com.vitorpamplona.quartz.nip01Core.tags.events.ETag
import com.vitorpamplona.quartz.nip01Core.tags.events.toETag
import com.vitorpamplona.quartz.nip01Core.tags.kinds.KindTag
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
import com.vitorpamplona.quartz.nip50Search.SearchableEvent
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.tags.AmountTag
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.tags.OfferTag
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.tags.ZapIdTag
@@ -65,7 +66,10 @@ class Bolt12ZapIntentEvent(
) : Event(id, pubKey, createdAt, KIND, tags, content, sig),
EventHintProvider,
AddressHintProvider,
PubKeyHintProvider {
PubKeyHintProvider,
SearchableEvent {
override fun indexableContent() = content
override fun pubKeyHints() = tags.mapNotNull(PTag::parseAsHint)
override fun linkedPubKeys() = tags.mapNotNull(PTag::parseKey)
@@ -74,7 +74,7 @@ class Podcasting20EpisodeEvent(
PodcastEpisode,
RootScope,
SearchableEvent {
override fun indexableContent() = listOfNotNull(title(), description(), content).joinToString("\n")
override fun indexableContent() = (listOfNotNull(title(), description(), content) + topics()).joinToString("\n")
fun title() = tags.firstNotNullOfOrNull(TitleTag::parse)