- Upgrades NIP-72 communities to NIP-22 comments
- Adds moderation queue feed - Adds in-app approval procedures - Adds drafts for community posts - Redesigns About us section of communities.
This commit is contained in:
@@ -32,8 +32,12 @@ fun Event.isTaggedAddressableNotes(addressIds: Set<String>) = tags.isTaggedAddre
|
||||
|
||||
fun Event.isTaggedAddressableKind(kind: Int) = tags.isTaggedAddressableKind(kind)
|
||||
|
||||
fun Event.isTaggedAddressableKind(kindStr: String) = tags.isTaggedAddressableKind(kindStr)
|
||||
|
||||
fun Event.getTagOfAddressableKind(kind: Int) = tags.getTagOfAddressableKind(kind)
|
||||
|
||||
fun Event.getTagOfAddressableKind(kindStr: String) = tags.getTagOfAddressableKind(kindStr)
|
||||
|
||||
fun Event.taggedATags() = tags.taggedATags()
|
||||
|
||||
fun Event.firstTaggedATag() = tags.firstTaggedATag()
|
||||
|
||||
+4
@@ -35,8 +35,12 @@ fun TagArray.isTaggedAddressableNotes(addressIds: Set<String>) = this.any(ATag::
|
||||
|
||||
fun TagArray.isTaggedAddressableKind(kind: Int) = this.any(ATag::isTaggedWithKind, kind.toString())
|
||||
|
||||
fun TagArray.isTaggedAddressableKind(kindStr: String) = this.any(ATag::isTaggedWithKind, kindStr)
|
||||
|
||||
fun TagArray.getTagOfAddressableKind(kind: Int) = this.fastFirstNotNullOfOrNull(ATag::parseIfOfKind, kind.toString())
|
||||
|
||||
fun TagArray.getTagOfAddressableKind(kindStr: String) = this.fastFirstNotNullOfOrNull(ATag::parseIfOfKind, kindStr.toString())
|
||||
|
||||
fun TagArray.taggedATags() = this.mapNotNull(ATag::parse)
|
||||
|
||||
fun TagArray.firstTaggedATag() = this.fastFirstNotNullOfOrNull(ATag::parse)
|
||||
|
||||
@@ -72,16 +72,18 @@ open class BaseThreadedEvent(
|
||||
return newStyleReply ?: newStyleRoot ?: oldStylePositional
|
||||
}
|
||||
|
||||
fun tagsWithoutCitations(): List<String> {
|
||||
open fun tagsWithoutCitations(): List<String> {
|
||||
val certainRepliesTo = markedReplyTos()
|
||||
val uncertainRepliesTo = unmarkedReplyTos()
|
||||
|
||||
val tagAddresses =
|
||||
taggedATags()
|
||||
.filter {
|
||||
it.kind != CommunityDefinitionEvent.KIND && (kind != WikiNoteEvent.KIND || it.kind != WikiNoteEvent.KIND)
|
||||
.filter { aTag ->
|
||||
aTag.kind != CommunityDefinitionEvent.KIND && (kind != WikiNoteEvent.KIND || aTag.kind != WikiNoteEvent.KIND)
|
||||
// removes forks from itself.
|
||||
}.map { it.toTag() }
|
||||
}.map {
|
||||
it.toTag()
|
||||
}
|
||||
|
||||
if (certainRepliesTo.isEmpty() && uncertainRepliesTo.isEmpty() && tagAddresses.isEmpty()) return emptyList()
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
|
||||
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.hints.AddressHintProvider
|
||||
import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle
|
||||
import com.vitorpamplona.quartz.nip01Core.hints.EventHintProvider
|
||||
@@ -33,6 +34,7 @@ import com.vitorpamplona.quartz.nip01Core.hints.types.AddressHint
|
||||
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.addressables.Address
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.geohash.GeoHashTag
|
||||
import com.vitorpamplona.quartz.nip10Notes.BaseThreadedEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.quotes.QTag
|
||||
@@ -55,10 +57,12 @@ import com.vitorpamplona.quartz.nip22Comments.tags.RootIdentifierTag
|
||||
import com.vitorpamplona.quartz.nip22Comments.tags.RootKindTag
|
||||
import com.vitorpamplona.quartz.nip31Alts.alt
|
||||
import com.vitorpamplona.quartz.nip50Search.SearchableEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent
|
||||
import com.vitorpamplona.quartz.nip73ExternalIds.ExternalId
|
||||
import com.vitorpamplona.quartz.nip73ExternalIds.location.GeohashId
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import com.vitorpamplona.quartz.utils.lastNotNullOfOrNull
|
||||
import kotlin.collections.mapNotNull
|
||||
|
||||
@Immutable
|
||||
class CommentEvent(
|
||||
@@ -126,6 +130,28 @@ class CommentEvent(
|
||||
return aHints + qHints + nip19Hints
|
||||
}
|
||||
|
||||
fun rootEventIds() = tags.mapNotNull(RootEventTag::parseKey)
|
||||
|
||||
fun replyEventIds() = tags.mapNotNull(ReplyEventTag::parseKey)
|
||||
|
||||
fun rootAddressIds() = tags.mapNotNull(RootAddressTag::parseAddressId)
|
||||
|
||||
fun hasRootAddress(addressId: String) = tags.any(RootAddressTag::isTagged, addressId)
|
||||
|
||||
fun hasReplyAddress(addressId: String) = tags.any(ReplyAddressTag::isTagged, addressId)
|
||||
|
||||
fun replyAddressIds() = tags.mapNotNull(ReplyAddressTag::parseAddressId)
|
||||
|
||||
fun replyAddress() = tags.mapNotNull(ReplyAddressTag::parseAddress)
|
||||
|
||||
fun rootScopes() = tags.filter { RootIdentifierTag.match(it) || RootAddressTag.match(it) || RootEventTag.match(it) }
|
||||
|
||||
fun rootKinds() = tags.filter(RootKindTag::match)
|
||||
|
||||
fun directReplies() = tags.filter { ReplyIdentifierTag.match(it) || ReplyAddressTag.match(it) || ReplyEventTag.match(it) }
|
||||
|
||||
fun directKinds() = tags.filter(ReplyKindTag::match)
|
||||
|
||||
fun rootAuthor() = tags.firstNotNullOfOrNull(RootAuthorTag::parse)
|
||||
|
||||
fun replyAuthor() = tags.firstNotNullOfOrNull(ReplyAuthorTag::parse)
|
||||
@@ -142,14 +168,6 @@ class CommentEvent(
|
||||
|
||||
fun replyAuthorHints() = tags.mapNotNull(ReplyAuthorTag::parseAsHint)
|
||||
|
||||
fun rootScopes() = tags.filter { RootIdentifierTag.match(it) || RootAddressTag.match(it) || RootEventTag.match(it) }
|
||||
|
||||
fun rootKinds() = tags.filter(RootKindTag::match)
|
||||
|
||||
fun directReplies() = tags.filter { ReplyIdentifierTag.match(it) || ReplyAddressTag.match(it) || ReplyEventTag.match(it) }
|
||||
|
||||
fun directKinds() = tags.filter(ReplyKindTag::match)
|
||||
|
||||
/** root and reply scope search */
|
||||
fun isTaggedScope(scopeId: String) = tags.any { RootIdentifierTag.isTagged(it, scopeId) || ReplyIdentifierTag.isTagged(it, scopeId) }
|
||||
|
||||
@@ -164,9 +182,9 @@ class CommentEvent(
|
||||
|
||||
fun isScoped(scopeTest: (String) -> Boolean) = tags.any { RootIdentifierTag.isTagged(it, scopeTest) || ReplyIdentifierTag.isTagged(it, scopeTest) }
|
||||
|
||||
fun hasRootScopeKind(kind: String) = tags.any { RootKindTag.isKind(it, kind) }
|
||||
fun hasRootScopeKind(kind: String) = tags.any(RootKindTag::isKind, kind)
|
||||
|
||||
fun hasReplyScopeKind(kind: String) = tags.any { ReplyKindTag.isKind(it, kind) }
|
||||
fun hasReplyScopeKind(kind: String) = tags.any(ReplyKindTag::isKind, kind)
|
||||
|
||||
fun hasScopeKind(kind: String) = tags.any { RootKindTag.isKind(it, kind) || ReplyKindTag.isKind(it, kind) }
|
||||
|
||||
@@ -184,12 +202,31 @@ class CommentEvent(
|
||||
tags.lastNotNullOfOrNull(ReplyEventTag::parseKey)
|
||||
?: tags.lastNotNullOfOrNull(RootEventTag::parseKey)
|
||||
|
||||
fun rootAddress() = tags.mapNotNull(RootAddressTag::parseAddress)
|
||||
|
||||
fun rootAddressId() = tags.mapNotNull(RootAddressTag::parseAddressId)
|
||||
|
||||
fun replyingToAddressId(): String? =
|
||||
tags.lastNotNullOfOrNull(RootAddressTag::parseAddressId)
|
||||
?: tags.lastNotNullOfOrNull(ReplyAddressTag::parseAddressId)
|
||||
tags.lastNotNullOfOrNull(ReplyAddressTag::parseAddressId)
|
||||
?: tags.lastNotNullOfOrNull(RootAddressTag::parseAddressId)
|
||||
|
||||
override fun replyingToAddressOrEvent(): HexKey? = replyingToAddressId() ?: replyingTo()
|
||||
|
||||
override fun tagsWithoutCitations(): List<String> {
|
||||
val rootAddress = rootAddressIds()
|
||||
val replyAddress = replyAddressIds()
|
||||
|
||||
if (
|
||||
rootAddress.any { Address.isOfKind(it, CommunityDefinitionEvent.KIND_STR) } &&
|
||||
replyAddress.any { Address.isOfKind(it, CommunityDefinitionEvent.KIND_STR) }
|
||||
) {
|
||||
// this is the root of the community post
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
return rootAddress + replyAddress + rootEventIds() + replyEventIds()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val KIND = 1111
|
||||
const val ALT = "Reply to "
|
||||
|
||||
@@ -44,6 +44,12 @@ class ReplyAddressTag(
|
||||
@JvmStatic
|
||||
fun match(tag: Tag) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty()
|
||||
|
||||
@JvmStatic
|
||||
fun isTagged(
|
||||
tag: Array<String>,
|
||||
addressId: String,
|
||||
) = tag.has(1) && tag[0] == RootAddressTag.Companion.TAG_NAME && tag[1] == addressId
|
||||
|
||||
@JvmStatic
|
||||
fun parse(tag: Array<String>): ReplyAddressTag? {
|
||||
ensure(tag.has(1)) { return null }
|
||||
@@ -55,6 +61,15 @@ class ReplyAddressTag(
|
||||
return ReplyAddressTag(tag[1], relayHint)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun parseAddress(tag: Array<String>): Address? {
|
||||
ensure(tag.has(1)) { return null }
|
||||
ensure(tag[0] == TAG_NAME) { return null }
|
||||
ensure(tag[1].isNotEmpty()) { return null }
|
||||
|
||||
return Address.parse(tag[1])
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun parseAddressId(tag: Array<String>): String? {
|
||||
ensure(tag.has(1)) { return null }
|
||||
|
||||
@@ -67,6 +67,15 @@ class RootAddressTag(
|
||||
return RootAddressTag(tag[1], relayHint)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun parseAddress(tag: Array<String>): Address? {
|
||||
ensure(tag.has(1)) { return null }
|
||||
ensure(tag[0] == TAG_NAME) { return null }
|
||||
ensure(tag[1].isNotEmpty()) { return null }
|
||||
|
||||
return Address.parse(tag[1])
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun parseValidAddress(tag: Array<String>): String? {
|
||||
ensure(tag.has(1)) { return null }
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* 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.nip72ModCommunities
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.core.any
|
||||
import com.vitorpamplona.quartz.nip01Core.core.fastFirstNotNullOfOrNull
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.addressables.Address
|
||||
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.follow.tags.CommunityTag
|
||||
|
||||
fun Event.isACommunityPost(): Boolean =
|
||||
if (this is CommentEvent) {
|
||||
this.isCommunityScoped()
|
||||
} else {
|
||||
this.tags.any(CommunityTag::isTaggedWithKind, CommunityDefinitionEvent.KIND_STR)
|
||||
}
|
||||
|
||||
fun Event.isForCommunity(communityAddressId: String): Boolean =
|
||||
if (this is CommentEvent) {
|
||||
this.hasRootAddress(communityAddressId)
|
||||
} else {
|
||||
tags.any(CommunityTag::isTagged, communityAddressId)
|
||||
}
|
||||
|
||||
fun Event.communityAddress(): Address? =
|
||||
if (this is CommentEvent) {
|
||||
this.rootAddress().firstNotNullOfOrNull {
|
||||
if (it.kind == CommunityDefinitionEvent.KIND) it else null
|
||||
}
|
||||
} else {
|
||||
this.tags.fastFirstNotNullOfOrNull(CommunityTag::parseAddress)
|
||||
}
|
||||
|
||||
fun CommentEvent.isCommunityScoped() = hasRootScopeKind(CommunityDefinitionEvent.KIND_STR)
|
||||
|
||||
fun CommentEvent.communityScope() = this.tags.fastFirstNotNullOfOrNull(CommunityTag::parseAddress)
|
||||
|
||||
fun CommentEvent.communityScopes() = this.tags.mapNotNull(CommunityTag::parseAddress)
|
||||
+28
-15
@@ -25,22 +25,29 @@ 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.hints.AddressHintProvider
|
||||
import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle
|
||||
import com.vitorpamplona.quartz.nip01Core.hints.EventHintProvider
|
||||
import com.vitorpamplona.quartz.nip01Core.hints.PubKeyHintProvider
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.addressables.ATag
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.addressables.taggedATags
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.addressables.taggedAddresses
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.events.ETag
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.events.taggedEvents
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.addressables.ATag.Companion.isTagged
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.addressables.Address
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.kinds.kind
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
|
||||
import com.vitorpamplona.quartz.nip18Reposts.quotes.QTag
|
||||
import com.vitorpamplona.quartz.nip31Alts.alt
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.approval.tags.ApprovedAddressTag
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.approval.tags.ApprovedAddressTag.Companion.parseAddress
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.approval.tags.ApprovedAddressTag.Companion.parseAddressId
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.approval.tags.ApprovedAddressTag.Companion.parseAsHint
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.approval.tags.ApprovedEventTag
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.approval.tags.ApprovedEventTag.Companion.parse
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.approval.tags.ApprovedEventTag.Companion.parseAsHint
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.approval.tags.ApprovedEventTag.Companion.parseId
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.follow.tags.CommunityTag
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import kotlin.collections.mapNotNull
|
||||
|
||||
@Immutable
|
||||
class CommunityPostApprovalEvent(
|
||||
@@ -54,13 +61,17 @@ class CommunityPostApprovalEvent(
|
||||
EventHintProvider,
|
||||
AddressHintProvider,
|
||||
PubKeyHintProvider {
|
||||
override fun eventHints() = tags.mapNotNull(ETag::parseAsHint) + tags.mapNotNull(QTag::parseEventAsHint)
|
||||
override fun eventHints() = tags.mapNotNull(ApprovedEventTag::parseAsHint)
|
||||
|
||||
override fun linkedEventIds() = tags.mapNotNull(ETag::parseId) + tags.mapNotNull(QTag::parseEventId)
|
||||
override fun linkedEventIds() = tags.mapNotNull(ApprovedEventTag::parseId)
|
||||
|
||||
override fun addressHints() = tags.mapNotNull(ATag::parseAsHint) + tags.mapNotNull(QTag::parseAddressAsHint)
|
||||
override fun addressHints() =
|
||||
tags.mapNotNull(ApprovedAddressTag::parseAsHint) +
|
||||
tags.mapNotNull(CommunityTag::parseAsHint)
|
||||
|
||||
override fun linkedAddressIds() = tags.mapNotNull(ATag::parseAddressId) + tags.mapNotNull(QTag::parseAddressId)
|
||||
override fun linkedAddressIds() =
|
||||
tags.mapNotNull(ApprovedAddressTag::parseAddressId) +
|
||||
tags.mapNotNull(CommunityTag::parseAddressId)
|
||||
|
||||
override fun pubKeyHints() = tags.mapNotNull(PTag::parseAsHint)
|
||||
|
||||
@@ -77,15 +88,17 @@ class CommunityPostApprovalEvent(
|
||||
null
|
||||
}
|
||||
|
||||
fun communities() = taggedATags().filter { it.kind == CommunityDefinitionEvent.KIND }
|
||||
fun isForCommunity(communityAddressId: String): Boolean = tags.any(CommunityTag::isTagged, communityAddressId)
|
||||
|
||||
fun communityAddresses() = taggedAddresses().filter { it.kind == CommunityDefinitionEvent.KIND }
|
||||
fun isForCommunity(community: Address): Boolean = isForCommunity(community.toValue())
|
||||
|
||||
fun approvedEvents() = taggedEvents()
|
||||
fun communities(): List<CommunityTag> = tags.mapNotNull(CommunityTag::parse)
|
||||
|
||||
fun approvedATags() = taggedATags().filter { it.kind != CommunityDefinitionEvent.KIND }
|
||||
fun communityAddresses(): List<Address> = tags.mapNotNull(CommunityTag::parseAddress)
|
||||
|
||||
fun approvedAddresses() = taggedAddresses().filter { it.kind != CommunityDefinitionEvent.KIND }
|
||||
fun approvedEvents(): List<HexKey> = tags.mapNotNull(ApprovedEventTag::parseId)
|
||||
|
||||
fun approvedAddresses(): List<Address> = tags.mapNotNull(ApprovedAddressTag::parseAddress)
|
||||
|
||||
companion object {
|
||||
const val KIND = 4550
|
||||
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
/**
|
||||
* 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.nip72ModCommunities.approval.tags
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.has
|
||||
import com.vitorpamplona.quartz.nip01Core.hints.types.AddressHint
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.addressables.Address
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent
|
||||
import com.vitorpamplona.quartz.utils.arrayOfNotNull
|
||||
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
|
||||
import com.vitorpamplona.quartz.utils.ensure
|
||||
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
|
||||
|
||||
class ApprovedAddressTag(
|
||||
val address: Address,
|
||||
val relayHint: NormalizedRelayUrl? = null,
|
||||
) {
|
||||
fun countMemory(): Long = 2 * pointerSizeInBytes + address.countMemory() + (relayHint?.url?.bytesUsedInMemory() ?: 0)
|
||||
|
||||
fun toTag() = Address.assemble(address.kind, address.pubKeyHex, address.dTag)
|
||||
|
||||
fun toTagArray() = assemble(address, relayHint)
|
||||
|
||||
fun toTagIdOnly() = assemble(address, null)
|
||||
|
||||
companion object {
|
||||
const val TAG_NAME = "a"
|
||||
|
||||
@JvmStatic
|
||||
fun isTagged(tag: Array<String>) = tag.has(1) && tag[0] == TAG_NAME && !Address.isOfKind(tag[1], CommunityDefinitionEvent.KIND_STR)
|
||||
|
||||
@JvmStatic
|
||||
fun isTagged(
|
||||
tag: Array<String>,
|
||||
addressId: String,
|
||||
) = tag.has(1) && tag[0] == TAG_NAME && tag[1] == addressId
|
||||
|
||||
@JvmStatic
|
||||
fun isTagged(
|
||||
tag: Array<String>,
|
||||
address: ApprovedAddressTag,
|
||||
) = tag.has(1) && tag[0] == TAG_NAME && tag[1] == address.toTag()
|
||||
|
||||
@JvmStatic
|
||||
fun isIn(
|
||||
tag: Array<String>,
|
||||
addressIds: Set<String>,
|
||||
) = tag.has(1) && tag[0] == TAG_NAME && tag[1] in addressIds
|
||||
|
||||
@JvmStatic
|
||||
fun parse(tag: Array<String>): ApprovedAddressTag? {
|
||||
ensure(tag.has(1)) { return null }
|
||||
ensure(tag[0] == TAG_NAME) { return null }
|
||||
ensure(!Address.isOfKind(tag[1], CommunityDefinitionEvent.KIND_STR)) { return null }
|
||||
|
||||
val address = Address.parse(tag[1]) ?: return null
|
||||
val relayHint = tag.getOrNull(2)?.let { RelayUrlNormalizer.normalizeOrNull(it) }
|
||||
return ApprovedAddressTag(address, relayHint)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun parseValidAddress(tag: Array<String>): String? {
|
||||
ensure(tag.has(1)) { return null }
|
||||
ensure(tag[0] == TAG_NAME) { return null }
|
||||
ensure(!Address.isOfKind(tag[1], CommunityDefinitionEvent.KIND_STR)) { return null }
|
||||
return Address.parse(tag[1])?.toValue()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun parseAddress(tag: Array<String>): Address? {
|
||||
ensure(tag.has(1)) { return null }
|
||||
ensure(tag[0] == TAG_NAME) { return null }
|
||||
ensure(tag[1].isNotEmpty()) { return null }
|
||||
val address = Address.parse(tag[1]) ?: return null
|
||||
ensure(address.kind != CommunityDefinitionEvent.KIND) { return null }
|
||||
return address
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun parseAddressId(tag: Array<String>): String? {
|
||||
ensure(tag.has(1)) { return null }
|
||||
ensure(tag[0] == TAG_NAME) { return null }
|
||||
ensure(!Address.isOfKind(tag[1], CommunityDefinitionEvent.KIND_STR)) { return null }
|
||||
return tag[1]
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun parseAsHint(tag: Array<String>): AddressHint? {
|
||||
ensure(tag.has(2)) { return null }
|
||||
ensure(tag[0] == TAG_NAME) { return null }
|
||||
ensure(!Address.isOfKind(tag[1], CommunityDefinitionEvent.KIND_STR)) { return null }
|
||||
ensure(tag[1].contains(':')) { return null }
|
||||
ensure(tag[2].isNotEmpty()) { return null }
|
||||
|
||||
val relayHint = RelayUrlNormalizer.normalizeOrNull(tag[2])
|
||||
ensure(relayHint != null) { return null }
|
||||
|
||||
return AddressHint(tag[1], relayHint)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun assemble(
|
||||
aTagId: HexKey,
|
||||
relay: NormalizedRelayUrl?,
|
||||
) = arrayOfNotNull(TAG_NAME, aTagId, relay?.url)
|
||||
|
||||
@JvmStatic
|
||||
fun assemble(
|
||||
address: Address,
|
||||
relay: NormalizedRelayUrl?,
|
||||
) = arrayOfNotNull(TAG_NAME, address.toValue(), relay?.url)
|
||||
|
||||
@JvmStatic
|
||||
fun assemble(
|
||||
kind: Int,
|
||||
pubKey: String,
|
||||
dTag: String,
|
||||
relay: NormalizedRelayUrl?,
|
||||
) = assemble(Address.assemble(kind, pubKey, dTag), relay)
|
||||
}
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* 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.nip72ModCommunities.approval.tags
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Tag
|
||||
import com.vitorpamplona.quartz.nip01Core.core.has
|
||||
import com.vitorpamplona.quartz.nip01Core.hints.types.EventIdHint
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.events.EventReference
|
||||
import com.vitorpamplona.quartz.utils.Hex
|
||||
import com.vitorpamplona.quartz.utils.arrayOfNotNull
|
||||
import com.vitorpamplona.quartz.utils.ensure
|
||||
|
||||
@Immutable
|
||||
class ApprovedEventTag(
|
||||
val ref: EventReference,
|
||||
) {
|
||||
constructor(eventId: String, relayHint: NormalizedRelayUrl?, pubkey: String?) : this(EventReference(eventId, pubkey, relayHint))
|
||||
|
||||
fun toTagArray() = assemble(ref)
|
||||
|
||||
companion object {
|
||||
const val TAG_NAME = "e"
|
||||
|
||||
@JvmStatic
|
||||
fun match(tag: Tag) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty()
|
||||
|
||||
@JvmStatic
|
||||
fun isTagged(
|
||||
tag: Array<String>,
|
||||
eventId: String,
|
||||
) = tag.has(1) && tag[0] == TAG_NAME && tag[1] == eventId
|
||||
|
||||
@JvmStatic
|
||||
fun isIn(
|
||||
tag: Array<String>,
|
||||
eventIds: Set<String>,
|
||||
) = tag.has(1) && tag[0] == TAG_NAME && tag[1] in eventIds
|
||||
|
||||
@JvmStatic
|
||||
fun parse(tag: Array<String>): ApprovedEventTag? {
|
||||
ensure(tag.has(1)) { return null }
|
||||
ensure(tag[0] == TAG_NAME) { return null }
|
||||
ensure(tag[1].length == 64) { return null }
|
||||
return ApprovedEventTag(tag[1], tag.getOrNull(2)?.let { RelayUrlNormalizer.normalizeOrNull(it) }, tag.getOrNull(3))
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun parseId(tag: Array<String>): String? {
|
||||
ensure(tag.has(1)) { return null }
|
||||
ensure(tag[0] == TAG_NAME) { return null }
|
||||
ensure(tag[1].length == 64) { return null }
|
||||
return tag[1]
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun parseValidId(tag: Array<String>): String? {
|
||||
ensure(tag.has(1)) { return null }
|
||||
ensure(tag[0] == TAG_NAME) { return null }
|
||||
ensure(tag[1].length == 64) { return null }
|
||||
ensure(Hex.isHex(tag[1])) { return null }
|
||||
return tag[1]
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun parseAsHint(tag: Array<String>): EventIdHint? {
|
||||
ensure(tag.has(2)) { return null }
|
||||
ensure(tag[0] == TAG_NAME) { return null }
|
||||
ensure(tag[1].length == 64) { return null }
|
||||
ensure(tag[2].isNotEmpty()) { return null }
|
||||
|
||||
val relayHint = RelayUrlNormalizer.normalizeOrNull(tag[2])
|
||||
ensure(relayHint != null) { return null }
|
||||
|
||||
return EventIdHint(tag[1], relayHint)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun assemble(
|
||||
eventId: HexKey,
|
||||
relay: NormalizedRelayUrl?,
|
||||
pubkey: String?,
|
||||
) = arrayOfNotNull(TAG_NAME, eventId, relay?.url, pubkey)
|
||||
|
||||
@JvmStatic
|
||||
fun assemble(ref: EventReference) = assemble(ref.eventId, ref.relayHint, ref.author)
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -80,7 +80,7 @@ class CommunityDefinitionEvent(
|
||||
|
||||
fun moderators() = tags.mapNotNull(ModeratorTag::parse)
|
||||
|
||||
fun moderatorKeys() = tags.mapNotNull(ModeratorTag::parseKey)
|
||||
fun moderatorKeys() = tags.mapNotNull(ModeratorTag::parseKey).plus(pubKey)
|
||||
|
||||
fun relays() = tags.mapNotNull(RelayTag::parse)
|
||||
|
||||
@@ -88,6 +88,7 @@ class CommunityDefinitionEvent(
|
||||
|
||||
companion object {
|
||||
const val KIND = 34550
|
||||
const val KIND_STR = "34550"
|
||||
const val ALT_DESCRIPTION = "Community definition"
|
||||
|
||||
fun build(
|
||||
|
||||
+17
-20
@@ -26,6 +26,7 @@ import com.vitorpamplona.quartz.nip01Core.hints.types.AddressHint
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.addressables.Address
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent
|
||||
import com.vitorpamplona.quartz.utils.arrayOfNotNull
|
||||
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
|
||||
import com.vitorpamplona.quartz.utils.ensure
|
||||
@@ -47,7 +48,7 @@ class CommunityTag(
|
||||
const val TAG_NAME = "a"
|
||||
|
||||
@JvmStatic
|
||||
fun isTagged(tag: Array<String>) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty()
|
||||
fun isTagged(tag: Array<String>) = tag.has(1) && tag[0] == TAG_NAME && Address.isOfKind(tag[1], CommunityDefinitionEvent.KIND_STR)
|
||||
|
||||
@JvmStatic
|
||||
fun isTagged(
|
||||
@@ -61,12 +62,6 @@ class CommunityTag(
|
||||
address: CommunityTag,
|
||||
) = tag.has(1) && tag[0] == TAG_NAME && tag[1] == address.toTag()
|
||||
|
||||
@JvmStatic
|
||||
fun isIn(
|
||||
tag: Array<String>,
|
||||
addressIds: Set<String>,
|
||||
) = tag.has(1) && tag[0] == TAG_NAME && tag[1] in addressIds
|
||||
|
||||
@JvmStatic
|
||||
fun isTaggedWithKind(
|
||||
tag: Array<String>,
|
||||
@@ -74,26 +69,27 @@ class CommunityTag(
|
||||
) = tag.has(1) && tag[0] == TAG_NAME && Address.isOfKind(tag[1], kind)
|
||||
|
||||
@JvmStatic
|
||||
fun parse(
|
||||
aTagId: String,
|
||||
relay: String?,
|
||||
) = Address.parse(aTagId)?.let {
|
||||
CommunityTag(it, relay?.let { RelayUrlNormalizer.normalizeOrNull(it) })
|
||||
}
|
||||
fun isIn(
|
||||
tag: Array<String>,
|
||||
addressIds: Set<String>,
|
||||
) = tag.has(1) && tag[0] == TAG_NAME && tag[1] in addressIds
|
||||
|
||||
@JvmStatic
|
||||
fun parse(tag: Array<String>): CommunityTag? {
|
||||
ensure(tag.has(1)) { return null }
|
||||
ensure(tag[0] == TAG_NAME) { return null }
|
||||
ensure(tag[1].isNotEmpty()) { return null }
|
||||
return parse(tag[1], tag.getOrNull(2))
|
||||
ensure(Address.isOfKind(tag[1], CommunityDefinitionEvent.KIND_STR)) { return null }
|
||||
|
||||
val address = Address.parse(tag[1]) ?: return null
|
||||
val relayHint = tag.getOrNull(2)?.let { RelayUrlNormalizer.normalizeOrNull(it) }
|
||||
return CommunityTag(address, relayHint)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun parseValidAddress(tag: Array<String>): String? {
|
||||
ensure(tag.has(1)) { return null }
|
||||
ensure(tag[0] == TAG_NAME) { return null }
|
||||
ensure(tag[1].isNotEmpty()) { return null }
|
||||
ensure(Address.isOfKind(tag[1], CommunityDefinitionEvent.KIND_STR)) { return null }
|
||||
return Address.parse(tag[1])?.toValue()
|
||||
}
|
||||
|
||||
@@ -102,14 +98,16 @@ class CommunityTag(
|
||||
ensure(tag.has(1)) { return null }
|
||||
ensure(tag[0] == TAG_NAME) { return null }
|
||||
ensure(tag[1].isNotEmpty()) { return null }
|
||||
return Address.parse(tag[1])
|
||||
val address = Address.parse(tag[1]) ?: return null
|
||||
ensure(address.kind == CommunityDefinitionEvent.KIND) { return null }
|
||||
return address
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun parseAddressId(tag: Array<String>): String? {
|
||||
ensure(tag.has(1)) { return null }
|
||||
ensure(tag[0] == TAG_NAME) { return null }
|
||||
ensure(tag[1].isNotEmpty()) { return null }
|
||||
ensure(Address.isOfKind(tag[1], CommunityDefinitionEvent.KIND_STR)) { return null }
|
||||
return tag[1]
|
||||
}
|
||||
|
||||
@@ -117,8 +115,7 @@ class CommunityTag(
|
||||
fun parseAsHint(tag: Array<String>): AddressHint? {
|
||||
ensure(tag.has(2)) { return null }
|
||||
ensure(tag[0] == TAG_NAME) { return null }
|
||||
ensure(tag[1].isNotEmpty()) { return null }
|
||||
ensure(tag[1].contains(':')) { return null }
|
||||
ensure(Address.isOfKind(tag[1], CommunityDefinitionEvent.KIND_STR)) { return null }
|
||||
ensure(tag[2].isNotEmpty()) { return null }
|
||||
|
||||
val relayHint = RelayUrlNormalizer.normalizeOrNull(tag[2])
|
||||
|
||||
Reference in New Issue
Block a user