Adds community and public chat list events from nip51

This commit is contained in:
Vitor Pamplona 2024-02-09 15:54:56 -05:00
parent f7ab963db9
commit 72503b9061
4 changed files with 574 additions and 2 deletions

View File

@ -50,6 +50,7 @@ import com.vitorpamplona.quartz.events.CalendarRSVPEvent
import com.vitorpamplona.quartz.events.CalendarTimeSlotEvent
import com.vitorpamplona.quartz.events.ChannelCreateEvent
import com.vitorpamplona.quartz.events.ChannelHideMessageEvent
import com.vitorpamplona.quartz.events.ChannelListEvent
import com.vitorpamplona.quartz.events.ChannelMessageEvent
import com.vitorpamplona.quartz.events.ChannelMetadataEvent
import com.vitorpamplona.quartz.events.ChannelMuteUserEvent
@ -57,6 +58,7 @@ import com.vitorpamplona.quartz.events.ChatMessageEvent
import com.vitorpamplona.quartz.events.ChatroomKey
import com.vitorpamplona.quartz.events.ClassifiedsEvent
import com.vitorpamplona.quartz.events.CommunityDefinitionEvent
import com.vitorpamplona.quartz.events.CommunityListEvent
import com.vitorpamplona.quartz.events.CommunityPostApprovalEvent
import com.vitorpamplona.quartz.events.ContactListEvent
import com.vitorpamplona.quartz.events.DeletionEvent
@ -463,6 +465,20 @@ object LocalCache {
consumeBaseReplaceable(event, relay)
}
fun consume(
event: CommunityListEvent,
relay: Relay?,
) {
consumeBaseReplaceable(event, relay)
}
fun consume(
event: ChannelListEvent,
relay: Relay?,
) {
consumeBaseReplaceable(event, relay)
}
fun consume(
event: FileServersEvent,
relay: Relay?,
@ -1772,6 +1788,7 @@ object LocalCache {
is CalendarTimeSlotEvent -> consume(event, relay)
is CalendarRSVPEvent -> consume(event, relay)
is ChannelCreateEvent -> consume(event)
is ChannelListEvent -> consume(event, relay)
is ChannelHideMessageEvent -> consume(event)
is ChannelMessageEvent -> consume(event, relay)
is ChannelMetadataEvent -> consume(event)
@ -1779,6 +1796,7 @@ object LocalCache {
is ChatMessageEvent -> consume(event, relay)
is ClassifiedsEvent -> consume(event, relay)
is CommunityDefinitionEvent -> consume(event, relay)
is CommunityListEvent -> consume(event, relay)
is CommunityPostApprovalEvent -> {
event.containedPost()?.let { verifyAndConsume(it, relay) }
consume(event)

View File

@ -0,0 +1,274 @@
/**
* Copyright (c) 2023 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
import kotlinx.collections.immutable.ImmutableSet
@Immutable
class ChannelListEvent(
id: HexKey,
pubKey: HexKey,
createdAt: Long,
tags: Array<Array<String>>,
content: String,
sig: HexKey,
) : GeneralListEvent(id, pubKey, createdAt, KIND, tags, content, sig) {
@Transient var publicAndPrivateEventCache: ImmutableSet<HexKey>? = null
override fun dTag() = FIXED_D_TAG
fun publicAndPrivateEvents(
signer: NostrSigner,
onReady: (ImmutableSet<HexKey>) -> Unit,
) {
publicAndPrivateEventCache?.let { eventList ->
onReady(eventList)
return
}
privateTagsOrEmpty(signer) {
publicAndPrivateEventCache = filterTagList("e", it)
publicAndPrivateEventCache?.let { eventList ->
onReady(eventList)
}
}
}
companion object {
const val KIND = 10005
const val FIXED_D_TAG = ""
const val ALT = "Public Chat List"
fun blockListFor(pubKeyHex: HexKey): String {
return "$KIND:$pubKeyHex:"
}
fun createListWithTag(
key: String,
tag: String,
isPrivate: Boolean,
signer: NostrSigner,
createdAt: Long = TimeUtils.now(),
onReady: (ChannelListEvent) -> Unit,
) {
if (isPrivate) {
encryptTags(arrayOf(arrayOf(key, tag)), signer) { encryptedTags ->
create(
content = encryptedTags,
tags = emptyArray(),
signer = signer,
createdAt = createdAt,
onReady = onReady,
)
}
} else {
create(
content = "",
tags = arrayOf(arrayOf(key, tag)),
signer = signer,
createdAt = createdAt,
onReady = onReady,
)
}
}
fun createListWithEvent(
eventId: HexKey,
isPrivate: Boolean,
signer: NostrSigner,
createdAt: Long = TimeUtils.now(),
onReady: (ChannelListEvent) -> Unit,
) {
return createListWithTag("e", eventId, isPrivate, signer, createdAt, onReady)
}
fun addEvents(
earlierVersion: ChannelListEvent,
listEvents: List<HexKey>,
isPrivate: Boolean,
signer: NostrSigner,
createdAt: Long = TimeUtils.now(),
onReady: (ChannelListEvent) -> Unit,
) {
if (isPrivate) {
earlierVersion.privateTagsOrEmpty(signer) { privateTags ->
encryptTags(
privateTags =
privateTags.plus(
listEvents.map { arrayOf("e", it) },
),
signer = signer,
) { encryptedTags ->
create(
content = encryptedTags,
tags = earlierVersion.tags,
signer = signer,
createdAt = createdAt,
onReady = onReady,
)
}
}
} else {
create(
content = earlierVersion.content,
tags =
earlierVersion.tags.plus(
listEvents.map { arrayOf("e", it) },
),
signer = signer,
createdAt = createdAt,
onReady = onReady,
)
}
}
fun addEvent(
earlierVersion: ChannelListEvent,
event: HexKey,
isPrivate: Boolean,
signer: NostrSigner,
createdAt: Long = TimeUtils.now(),
onReady: (ChannelListEvent) -> Unit,
) {
return addTag(earlierVersion, "e", event, isPrivate, signer, createdAt, onReady)
}
fun addTag(
earlierVersion: ChannelListEvent,
key: String,
tag: String,
isPrivate: Boolean,
signer: NostrSigner,
createdAt: Long = TimeUtils.now(),
onReady: (ChannelListEvent) -> Unit,
) {
earlierVersion.isTagged(key, tag, isPrivate, signer) { isTagged ->
if (!isTagged) {
if (isPrivate) {
earlierVersion.privateTagsOrEmpty(signer) { privateTags ->
encryptTags(
privateTags = privateTags.plus(element = arrayOf(key, tag)),
signer = signer,
) { encryptedTags ->
create(
content = encryptedTags,
tags = earlierVersion.tags,
signer = signer,
createdAt = createdAt,
onReady = onReady,
)
}
}
} else {
create(
content = earlierVersion.content,
tags = earlierVersion.tags.plus(element = arrayOf(key, tag)),
signer = signer,
createdAt = createdAt,
onReady = onReady,
)
}
}
}
}
fun removeEvent(
earlierVersion: ChannelListEvent,
event: HexKey,
isPrivate: Boolean,
signer: NostrSigner,
createdAt: Long = TimeUtils.now(),
onReady: (ChannelListEvent) -> Unit,
) {
return removeTag(earlierVersion, "e", event, isPrivate, signer, createdAt, onReady)
}
fun removeTag(
earlierVersion: ChannelListEvent,
key: String,
tag: String,
isPrivate: Boolean,
signer: NostrSigner,
createdAt: Long = TimeUtils.now(),
onReady: (ChannelListEvent) -> Unit,
) {
earlierVersion.isTagged(key, tag, isPrivate, signer) { isTagged ->
if (isTagged) {
if (isPrivate) {
earlierVersion.privateTagsOrEmpty(signer) { privateTags ->
encryptTags(
privateTags =
privateTags
.filter { it.size > 1 && !(it[0] == key && it[1] == tag) }
.toTypedArray(),
signer = signer,
) { encryptedTags ->
create(
content = encryptedTags,
tags =
earlierVersion.tags
.filter { it.size > 1 && !(it[0] == key && it[1] == tag) }
.toTypedArray(),
signer = signer,
createdAt = createdAt,
onReady = onReady,
)
}
}
} else {
create(
content = earlierVersion.content,
tags =
earlierVersion.tags
.filter { it.size > 1 && !(it[0] == key && it[1] == tag) }
.toTypedArray(),
signer = signer,
createdAt = createdAt,
onReady = onReady,
)
}
}
}
}
fun create(
content: String,
tags: Array<Array<String>>,
signer: NostrSigner,
createdAt: Long = TimeUtils.now(),
onReady: (ChannelListEvent) -> Unit,
) {
val newTags =
if (tags.any { it.size > 1 && it[0] == "alt" }) {
tags
} else {
tags + arrayOf("alt", ALT)
}
signer.sign(createdAt, KIND, newTags, content, onReady)
}
}
}

View File

@ -0,0 +1,279 @@
/**
* Copyright (c) 2023 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.ATag
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.signers.NostrSigner
import com.vitorpamplona.quartz.utils.TimeUtils
import kotlinx.collections.immutable.ImmutableSet
import kotlinx.collections.immutable.toImmutableSet
@Immutable
class CommunityListEvent(
id: HexKey,
pubKey: HexKey,
createdAt: Long,
tags: Array<Array<String>>,
content: String,
sig: HexKey,
) : GeneralListEvent(id, pubKey, createdAt, KIND, tags, content, sig) {
@Transient var publicAndPrivateEventCache: ImmutableSet<ATag>? = null
override fun dTag() = FIXED_D_TAG
fun publicAndPrivateEvents(
signer: NostrSigner,
onReady: (ImmutableSet<ATag>) -> Unit,
) {
publicAndPrivateEventCache?.let { eventList ->
onReady(eventList)
return
}
privateTagsOrEmpty(signer) {
publicAndPrivateEventCache =
filterTagList("a", it)
.mapNotNull { ATag.parseAtag(it, null) }
.toImmutableSet()
publicAndPrivateEventCache?.let { eventList ->
onReady(eventList)
}
}
}
companion object {
const val KIND = 10004
const val FIXED_D_TAG = ""
const val ALT = "Community List"
fun blockListFor(pubKeyHex: HexKey): String {
return "$KIND:$pubKeyHex:"
}
fun createListWithTag(
key: String,
tag: String,
isPrivate: Boolean,
signer: NostrSigner,
createdAt: Long = TimeUtils.now(),
onReady: (CommunityListEvent) -> Unit,
) {
if (isPrivate) {
encryptTags(arrayOf(arrayOf(key, tag)), signer) { encryptedTags ->
create(
content = encryptedTags,
tags = emptyArray(),
signer = signer,
createdAt = createdAt,
onReady = onReady,
)
}
} else {
create(
content = "",
tags = arrayOf(arrayOf(key, tag)),
signer = signer,
createdAt = createdAt,
onReady = onReady,
)
}
}
fun createListWithEvent(
address: ATag,
isPrivate: Boolean,
signer: NostrSigner,
createdAt: Long = TimeUtils.now(),
onReady: (CommunityListEvent) -> Unit,
) {
return createListWithTag("a", address.toTag(), isPrivate, signer, createdAt, onReady)
}
fun addEvents(
earlierVersion: CommunityListEvent,
listAddresses: List<ATag>,
isPrivate: Boolean,
signer: NostrSigner,
createdAt: Long = TimeUtils.now(),
onReady: (CommunityListEvent) -> Unit,
) {
if (isPrivate) {
earlierVersion.privateTagsOrEmpty(signer) { privateTags ->
encryptTags(
privateTags =
privateTags.plus(
listAddresses.map { arrayOf("a", it.toTag()) },
),
signer = signer,
) { encryptedTags ->
create(
content = encryptedTags,
tags = earlierVersion.tags,
signer = signer,
createdAt = createdAt,
onReady = onReady,
)
}
}
} else {
create(
content = earlierVersion.content,
tags =
earlierVersion.tags.plus(
listAddresses.map { arrayOf("a", it.toTag()) },
),
signer = signer,
createdAt = createdAt,
onReady = onReady,
)
}
}
fun addEvent(
earlierVersion: CommunityListEvent,
address: ATag,
isPrivate: Boolean,
signer: NostrSigner,
createdAt: Long = TimeUtils.now(),
onReady: (CommunityListEvent) -> Unit,
) {
return addTag(earlierVersion, "a", address.toTag(), isPrivate, signer, createdAt, onReady)
}
fun addTag(
earlierVersion: CommunityListEvent,
key: String,
tag: String,
isPrivate: Boolean,
signer: NostrSigner,
createdAt: Long = TimeUtils.now(),
onReady: (CommunityListEvent) -> Unit,
) {
earlierVersion.isTagged(key, tag, isPrivate, signer) { isTagged ->
if (!isTagged) {
if (isPrivate) {
earlierVersion.privateTagsOrEmpty(signer) { privateTags ->
encryptTags(
privateTags = privateTags.plus(element = arrayOf(key, tag)),
signer = signer,
) { encryptedTags ->
create(
content = encryptedTags,
tags = earlierVersion.tags,
signer = signer,
createdAt = createdAt,
onReady = onReady,
)
}
}
} else {
create(
content = earlierVersion.content,
tags = earlierVersion.tags.plus(element = arrayOf(key, tag)),
signer = signer,
createdAt = createdAt,
onReady = onReady,
)
}
}
}
}
fun removeEvent(
earlierVersion: CommunityListEvent,
address: ATag,
isPrivate: Boolean,
signer: NostrSigner,
createdAt: Long = TimeUtils.now(),
onReady: (CommunityListEvent) -> Unit,
) {
return removeTag(earlierVersion, "a", address.toTag(), isPrivate, signer, createdAt, onReady)
}
fun removeTag(
earlierVersion: CommunityListEvent,
key: String,
tag: String,
isPrivate: Boolean,
signer: NostrSigner,
createdAt: Long = TimeUtils.now(),
onReady: (CommunityListEvent) -> Unit,
) {
earlierVersion.isTagged(key, tag, isPrivate, signer) { isTagged ->
if (isTagged) {
if (isPrivate) {
earlierVersion.privateTagsOrEmpty(signer) { privateTags ->
encryptTags(
privateTags =
privateTags
.filter { it.size > 1 && !(it[0] == key && it[1] == tag) }
.toTypedArray(),
signer = signer,
) { encryptedTags ->
create(
content = encryptedTags,
tags =
earlierVersion.tags
.filter { it.size > 1 && !(it[0] == key && it[1] == tag) }
.toTypedArray(),
signer = signer,
createdAt = createdAt,
onReady = onReady,
)
}
}
} else {
create(
content = earlierVersion.content,
tags =
earlierVersion.tags
.filter { it.size > 1 && !(it[0] == key && it[1] == tag) }
.toTypedArray(),
signer = signer,
createdAt = createdAt,
onReady = onReady,
)
}
}
}
}
fun create(
content: String,
tags: Array<Array<String>>,
signer: NostrSigner,
createdAt: Long = TimeUtils.now(),
onReady: (CommunityListEvent) -> Unit,
) {
val newTags =
if (tags.any { it.size > 1 && it[0] == "alt" }) {
tags
} else {
tags + arrayOf("alt", ALT)
}
signer.sign(createdAt, KIND, newTags, content, onReady)
}
}
}

View File

@ -53,14 +53,14 @@ class EventFactory {
ChannelCreateEvent.KIND -> ChannelCreateEvent(id, pubKey, createdAt, tags, content, sig)
ChannelHideMessageEvent.KIND ->
ChannelHideMessageEvent(id, pubKey, createdAt, tags, content, sig)
ChannelListEvent.KIND -> ChannelListEvent(id, pubKey, createdAt, tags, content, sig)
ChannelMessageEvent.KIND -> ChannelMessageEvent(id, pubKey, createdAt, tags, content, sig)
ChannelMetadataEvent.KIND -> ChannelMetadataEvent(id, pubKey, createdAt, tags, content, sig)
ChannelMuteUserEvent.KIND -> ChannelMuteUserEvent(id, pubKey, createdAt, tags, content, sig)
ChatMessageEvent.KIND -> {
if (id.isBlank()) {
val newId = Event.generateId(pubKey, createdAt, kind, tags, content).toHexKey()
ChatMessageEvent(
newId,
Event.generateId(pubKey, createdAt, kind, tags, content).toHexKey(),
pubKey,
createdAt,
tags,
@ -74,6 +74,7 @@ class EventFactory {
ClassifiedsEvent.KIND -> ClassifiedsEvent(id, pubKey, createdAt, tags, content, sig)
CommunityDefinitionEvent.KIND ->
CommunityDefinitionEvent(id, pubKey, createdAt, tags, content, sig)
CommunityListEvent.KIND -> CommunityListEvent(id, pubKey, createdAt, tags, content, sig)
CommunityPostApprovalEvent.KIND ->
CommunityPostApprovalEvent(id, pubKey, createdAt, tags, content, sig)
ContactListEvent.KIND -> ContactListEvent(id, pubKey, createdAt, tags, content, sig)