Updates NIP-56 to the newest tag structure for Quartz

This commit is contained in:
Vitor Pamplona 2025-03-12 14:51:23 -04:00
parent 5766bb1887
commit b017e03728
18 changed files with 561 additions and 100 deletions

View File

@ -149,6 +149,7 @@ import com.vitorpamplona.quartz.nip51Lists.GeneralListEvent
import com.vitorpamplona.quartz.nip51Lists.MuteListEvent
import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent
import com.vitorpamplona.quartz.nip56Reports.ReportEvent
import com.vitorpamplona.quartz.nip56Reports.ReportType
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent
import com.vitorpamplona.quartz.nip57Zaps.splits.ZapSplitSetup
@ -1616,18 +1617,18 @@ class Account(
suspend fun report(
note: Note,
type: ReportEvent.ReportType,
type: ReportType,
content: String = "",
) {
if (!isWriteable()) return
if (note.hasReacted(userProfile(), "⚠️")) {
// has already liked this note
if (note.hasReport(userProfile(), type)) {
// has already reported this note
return
}
note.event?.let {
ReportEvent.create(it, type, signer, content) {
signer.sign(ReportEvent.build(it, type)) {
Amethyst.instance.client.send(it)
LocalCache.justConsume(it, null)
}
@ -1636,7 +1637,7 @@ class Account(
suspend fun report(
user: User,
type: ReportEvent.ReportType,
type: ReportType,
) {
if (!isWriteable()) return
@ -1645,7 +1646,8 @@ class Account(
return
}
ReportEvent.create(user.pubkeyHex, type, signer) {
val template = ReportEvent.build(user.pubkeyHex, type)
signer.sign(template) {
Amethyst.instance.client.send(it)
LocalCache.justConsume(it, null)
}

View File

@ -695,8 +695,8 @@ object LocalCache {
event.originalPost().mapNotNull { checkGetOrCreateNote(it) } +
event.taggedAddresses().map { getOrCreateAddressableNote(it) }
is ReportEvent ->
event.reportedPost().mapNotNull { checkGetOrCreateNote(it.key) } +
event.taggedAddresses().map { getOrCreateAddressableNote(it) }
event.reportedPost().mapNotNull { checkGetOrCreateNote(it.eventId) } +
event.reportedAddresses().map { getOrCreateAddressableNote(it.address) }
is ChannelMessageEvent ->
event
.tagsWithoutCitations()
@ -1342,7 +1342,7 @@ object LocalCache {
// Already processed this event.
if (note.event != null) return
val mentions = event.reportedAuthor().mapNotNull { checkGetOrCreateUser(it.key) }
val mentions = event.reportedAuthor().mapNotNull { checkGetOrCreateUser(it.pubkey) }
val repliesTo = computeReplyTo(event)
note.loadEvent(event, author, repliesTo)
@ -2218,7 +2218,7 @@ object LocalCache {
}
if (noteEvent is ReportEvent) {
noteEvent.reportedAuthor().mapNotNull {
val author = getUserIfExists(it.key)
val author = getUserIfExists(it.pubkey)
author?.removeReport(note)
author?.clearEOSE()
}

View File

@ -68,6 +68,8 @@ import com.vitorpamplona.quartz.nip47WalletConnect.LnZapPaymentResponseEvent
import com.vitorpamplona.quartz.nip47WalletConnect.PayInvoiceSuccessResponse
import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
import com.vitorpamplona.quartz.nip56Reports.ReportEvent
import com.vitorpamplona.quartz.nip56Reports.ReportType
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent
import com.vitorpamplona.quartz.nip59Giftwrap.WrappedEvent
@ -717,6 +719,15 @@ open class Note(
)
}
fun hasReport(
loggedIn: User,
type: ReportType,
): Boolean =
reports[loggedIn]?.firstOrNull {
it.event is ReportEvent &&
(it.event as ReportEvent).reportedAuthor().any { it.type == type }
} != null
fun hasPledgeBy(user: User): Boolean =
replies
.filter { it.event?.hasAdditionalReward() ?: false }

View File

@ -46,6 +46,7 @@ import com.vitorpamplona.quartz.nip19Bech32.entities.NProfile
import com.vitorpamplona.quartz.nip19Bech32.toNpub
import com.vitorpamplona.quartz.nip51Lists.BookmarkListEvent
import com.vitorpamplona.quartz.nip56Reports.ReportEvent
import com.vitorpamplona.quartz.nip56Reports.ReportType
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
import com.vitorpamplona.quartz.utils.DualCase
@ -357,11 +358,11 @@ class User(
fun hasReport(
loggedIn: User,
type: ReportEvent.ReportType,
type: ReportType,
): Boolean =
reports[loggedIn]?.firstOrNull {
it.event is ReportEvent &&
(it.event as ReportEvent).reportedAuthor().any { it.reportType == type }
(it.event as ReportEvent).reportedAuthor().any { it.type == type }
} != null
fun containsAny(hiddenWordsCase: List<DualCase>): Boolean {

View File

@ -36,6 +36,7 @@ import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.replyModifier
import com.vitorpamplona.quartz.nip02FollowList.EmptyTagList
import com.vitorpamplona.quartz.nip56Reports.ReportEvent
import com.vitorpamplona.quartz.nip56Reports.ReportType
@Composable
fun RenderReport(
@ -52,16 +53,17 @@ fun RenderReport(
val reportType =
base
.map {
when (it.reportType) {
ReportEvent.ReportType.EXPLICIT -> stringRes(R.string.explicit_content)
ReportEvent.ReportType.NUDITY -> stringRes(R.string.nudity)
ReportEvent.ReportType.PROFANITY -> stringRes(R.string.profanity_hateful_speech)
ReportEvent.ReportType.SPAM -> stringRes(R.string.spam)
ReportEvent.ReportType.IMPERSONATION -> stringRes(R.string.impersonation)
ReportEvent.ReportType.ILLEGAL -> stringRes(R.string.illegal_behavior)
ReportEvent.ReportType.MALWARE -> stringRes(R.string.malware)
ReportEvent.ReportType.MOD -> stringRes(R.string.mod)
ReportEvent.ReportType.OTHER -> stringRes(R.string.other)
when (it.type) {
ReportType.EXPLICIT -> stringRes(R.string.explicit_content)
ReportType.NUDITY -> stringRes(R.string.nudity)
ReportType.PROFANITY -> stringRes(R.string.profanity_hateful_speech)
ReportType.SPAM -> stringRes(R.string.spam)
ReportType.IMPERSONATION -> stringRes(R.string.impersonation)
ReportType.ILLEGAL -> stringRes(R.string.illegal_behavior)
ReportType.MALWARE -> stringRes(R.string.malware)
ReportType.MOD -> stringRes(R.string.mod)
ReportType.OTHER -> stringRes(R.string.other)
null -> stringRes(R.string.other)
}
}.toSet()
.joinToString(", ")

View File

@ -102,7 +102,7 @@ import com.vitorpamplona.quartz.nip37Drafts.DraftEvent
import com.vitorpamplona.quartz.nip47WalletConnect.Response
import com.vitorpamplona.quartz.nip50Search.SearchRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.GeneralListEvent
import com.vitorpamplona.quartz.nip56Reports.ReportEvent
import com.vitorpamplona.quartz.nip56Reports.ReportType
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent
import com.vitorpamplona.quartz.nip57Zaps.zapraiser.zapraiserAmount
@ -741,7 +741,7 @@ class AccountViewModel(
fun report(
note: Note,
type: ReportEvent.ReportType,
type: ReportType,
content: String = "",
) {
viewModelScope.launch(Dispatchers.IO) { account.report(note, type, content) }
@ -749,7 +749,7 @@ class AccountViewModel(
fun report(
user: User,
type: ReportEvent.ReportType,
type: ReportType,
) {
viewModelScope.launch(Dispatchers.IO) {
account.report(user, type)

View File

@ -63,7 +63,7 @@ import com.vitorpamplona.amethyst.ui.note.ArrowBackIcon
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.WarningColor
import com.vitorpamplona.quartz.nip56Reports.ReportEvent
import com.vitorpamplona.quartz.nip56Reports.ReportType
import kotlinx.collections.immutable.toImmutableList
@OptIn(ExperimentalMaterial3Api::class)
@ -75,13 +75,13 @@ fun ReportNoteDialog(
) {
val reportTypes =
listOf(
Pair(ReportEvent.ReportType.SPAM, stringRes(R.string.report_dialog_spam)),
Pair(ReportEvent.ReportType.PROFANITY, stringRes(R.string.report_dialog_profanity)),
Pair(ReportEvent.ReportType.IMPERSONATION, stringRes(R.string.report_dialog_impersonation)),
Pair(ReportEvent.ReportType.NUDITY, stringRes(R.string.report_dialog_nudity)),
Pair(ReportEvent.ReportType.ILLEGAL, stringRes(R.string.report_dialog_illegal)),
Pair(ReportEvent.ReportType.MALWARE, stringRes(R.string.report_malware)),
Pair(ReportEvent.ReportType.MOD, stringRes(R.string.report_mod)),
Pair(ReportType.SPAM, stringRes(R.string.report_dialog_spam)),
Pair(ReportType.PROFANITY, stringRes(R.string.report_dialog_profanity)),
Pair(ReportType.IMPERSONATION, stringRes(R.string.report_dialog_impersonation)),
Pair(ReportType.NUDITY, stringRes(R.string.report_dialog_nudity)),
Pair(ReportType.ILLEGAL, stringRes(R.string.report_dialog_illegal)),
Pair(ReportType.MALWARE, stringRes(R.string.report_malware)),
Pair(ReportType.MOD, stringRes(R.string.report_mod)),
)
val reasonOptions = remember { reportTypes.map { TitleExplainer(it.second) }.toImmutableList() }

View File

@ -36,7 +36,7 @@ import com.vitorpamplona.amethyst.ui.note.externalLinkForUser
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.quartz.nip56Reports.ReportEvent
import com.vitorpamplona.quartz.nip56Reports.ReportType
@Composable
fun UserProfileDropDownMenu(
@ -108,42 +108,42 @@ fun UserProfileDropDownMenu(
DropdownMenuItem(
text = { Text(stringRes(id = R.string.report_spam_scam)) },
onClick = {
accountViewModel.report(user, ReportEvent.ReportType.SPAM)
accountViewModel.report(user, ReportType.SPAM)
onDismiss()
},
)
DropdownMenuItem(
text = { Text(stringRes(R.string.report_hateful_speech)) },
onClick = {
accountViewModel.report(user, ReportEvent.ReportType.PROFANITY)
accountViewModel.report(user, ReportType.PROFANITY)
onDismiss()
},
)
DropdownMenuItem(
text = { Text(stringRes(id = R.string.report_impersonation)) },
onClick = {
accountViewModel.report(user, ReportEvent.ReportType.IMPERSONATION)
accountViewModel.report(user, ReportType.IMPERSONATION)
onDismiss()
},
)
DropdownMenuItem(
text = { Text(stringRes(R.string.report_nudity_porn)) },
onClick = {
accountViewModel.report(user, ReportEvent.ReportType.NUDITY)
accountViewModel.report(user, ReportType.NUDITY)
onDismiss()
},
)
DropdownMenuItem(
text = { Text(stringRes(id = R.string.report_illegal_behaviour)) },
onClick = {
accountViewModel.report(user, ReportEvent.ReportType.ILLEGAL)
accountViewModel.report(user, ReportType.ILLEGAL)
onDismiss()
},
)
DropdownMenuItem(
text = { Text(stringRes(id = R.string.report_malware)) },
onClick = {
accountViewModel.report(user, ReportEvent.ReportType.MALWARE)
accountViewModel.report(user, ReportType.MALWARE)
onDismiss()
},
)

View File

@ -25,14 +25,15 @@ 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.signers.NostrSigner
import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate
import com.vitorpamplona.quartz.nip31Alts.AltTag
import com.vitorpamplona.quartz.nip31Alts.alt
import com.vitorpamplona.quartz.nip56Reports.tags.DefaultReportTag
import com.vitorpamplona.quartz.nip56Reports.tags.ReportedAddressTag
import com.vitorpamplona.quartz.nip56Reports.tags.ReportedAuthorTag
import com.vitorpamplona.quartz.nip56Reports.tags.ReportedEventTag
import com.vitorpamplona.quartz.utils.TimeUtils
@Immutable data class ReportedKey(
val key: String,
val reportType: ReportEvent.ReportType,
)
// NIP 56 event.
@Immutable
class ReportEvent(
@ -43,48 +44,35 @@ class ReportEvent(
content: String,
sig: HexKey,
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
@Transient
private var defaultType: ReportType? = null
private fun defaultReportTypes() = tags.mapNotNull(DefaultReportTag::parse)
private fun defaultReportType(): ReportType {
defaultType?.let { return it }
// Works with old and new structures for report.
var reportType =
tags
.filter { it.firstOrNull() == "report" }
.mapNotNull { it.getOrNull(1) }
.map { ReportType.valueOf(it.uppercase()) }
.firstOrNull()
var reportType = defaultReportTypes().firstOrNull()
if (reportType == null) {
reportType =
tags.mapNotNull { it.getOrNull(2) }.map { ReportType.valueOf(it.uppercase()) }.firstOrNull()
reportType = tags.mapNotNull { it.getOrNull(2) }.map { ReportType.parseOrNull(it, emptyArray()) }.firstOrNull()
}
if (reportType == null) {
reportType = ReportType.SPAM
}
defaultType = reportType
return reportType
}
fun reportedPost() =
tags
.filter { it.size > 1 && it[0] == "e" }
.map {
ReportedKey(
it[1],
it.getOrNull(2)?.uppercase()?.let { it1 -> ReportType.valueOf(it1) }
?: defaultReportType(),
)
}
fun reportedPost() = tags.mapNotNull { ReportedEventTag.parse(it, defaultReportType()) }
fun reportedAuthor() =
tags
.filter { it.size > 1 && it[0] == "p" }
.map {
ReportedKey(
it[1],
it.getOrNull(2)?.uppercase()?.let { it1 -> ReportType.valueOf(it1) }
?: defaultReportType(),
)
}
fun reportedAddresses() = tags.mapNotNull { ReportedAddressTag.parse(it, defaultReportType()) }
fun reportedAuthor() = tags.mapNotNull { ReportedAuthorTag.parse(it, defaultReportType()) }
companion object {
const val KIND = 1984
const val ALT_PREFIX = "Report for "
fun create(
reportedPost: Event,
@ -108,32 +96,27 @@ class ReportEvent(
signer.sign(createdAt, KIND, tags, content, onReady)
}
fun create(
reportedUser: String,
fun build(
reportedPost: Event,
type: ReportType,
signer: NostrSigner,
createdAt: Long = TimeUtils.now(),
onReady: (ReportEvent) -> Unit,
) {
val content = ""
) = eventTemplate(KIND, "", createdAt) {
alt(ALT_PREFIX + type.code)
event(reportedPost.id, type)
user(reportedPost.pubKey, type)
val reportAuthorTag = arrayOf("p", reportedUser, type.name.lowercase())
val alt = AltTag.assemble("Report for ${type.name}")
if (reportedPost is AddressableEvent) {
address(reportedPost.address(), type)
}
}
val tags: Array<Array<String>> = arrayOf(reportAuthorTag, alt)
signer.sign(createdAt, KIND, tags, content, onReady)
fun build(
reportedUser: HexKey,
type: ReportType,
createdAt: Long = TimeUtils.now(),
) = eventTemplate(KIND, "", createdAt) {
alt(ALT_PREFIX + type.code)
user(reportedUser, type)
}
}
enum class ReportType {
EXPLICIT, // Not used anymore.
ILLEGAL,
SPAM,
IMPERSONATION,
NUDITY,
PROFANITY,
MALWARE,
MOD,
OTHER,
}
}

View File

@ -0,0 +1,61 @@
/**
* 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.nip56Reports
import android.util.Log
enum class ReportType(
val code: String,
) {
EXPLICIT("explicit"), // Not used anymore.
ILLEGAL("illegal"),
SPAM("spam"),
IMPERSONATION("impersonation"),
NUDITY("nudity"),
PROFANITY("profanity"),
MALWARE("malware"),
MOD("mod"),
OTHER("other"),
;
companion object {
fun parseOrNull(
code: String,
tag: Array<String>,
): ReportType? =
when (code) {
EXPLICIT.code -> EXPLICIT
ILLEGAL.code -> ILLEGAL
SPAM.code -> SPAM
IMPERSONATION.code -> IMPERSONATION
NUDITY.code -> NUDITY
PROFANITY.code -> PROFANITY
MALWARE.code -> MALWARE
MOD.code -> MOD
"MOD" -> MOD
OTHER.code -> OTHER
else -> {
Log.w("ReportedEventTag", "Report type not supported: $code ${tag.joinToString(", ")}")
null
}
}
}
}

View File

@ -0,0 +1,52 @@
/**
* 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.nip56Reports
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
import com.vitorpamplona.quartz.nip01Core.tags.addressables.Address
import com.vitorpamplona.quartz.nip56Reports.tags.HashSha256Tag
import com.vitorpamplona.quartz.nip56Reports.tags.ReportedAddressTag
import com.vitorpamplona.quartz.nip56Reports.tags.ReportedAuthorTag
import com.vitorpamplona.quartz.nip56Reports.tags.ReportedEventTag
import com.vitorpamplona.quartz.nip56Reports.tags.ServerTag
fun TagArrayBuilder<ReportEvent>.event(
eventId: HexKey,
reportType: ReportType,
) = addUnique(ReportedEventTag.assemble(eventId, reportType))
fun TagArrayBuilder<ReportEvent>.address(
address: Address,
reportType: ReportType,
) = addUnique(ReportedAddressTag.assemble(address, reportType))
fun TagArrayBuilder<ReportEvent>.user(
pubkey: HexKey,
reportType: ReportType,
) = addUnique(ReportedAuthorTag.assemble(pubkey, reportType))
fun TagArrayBuilder<ReportEvent>.hash(
x: String,
reportType: ReportType,
) = addUnique(HashSha256Tag.assemble(x, reportType))
fun TagArrayBuilder<ReportEvent>.server(url: String) = addUnique(ServerTag.assemble(url))

View File

@ -18,10 +18,10 @@
* 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.nip70ProtectedEvts
package com.vitorpamplona.quartz.nip56Reports.tags
class ProtectedTagSerializer {
companion object {
fun toTagArray(reason: String = "") = arrayOf("-", reason)
}
import com.vitorpamplona.quartz.nip56Reports.ReportType
interface BaseReportTag {
val type: ReportType?
}

View File

@ -0,0 +1,46 @@
/**
* 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.nip56Reports.tags
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.nip56Reports.ReportType
import com.vitorpamplona.quartz.utils.arrayOfNotNull
import com.vitorpamplona.quartz.utils.ensure
@Immutable
class DefaultReportTag {
companion object {
const val TAG_NAME = "report"
@JvmStatic
fun parse(tag: Array<String>): ReportType? {
ensure(tag.has(1)) { return null }
ensure(tag[0] == TAG_NAME) { return null }
ensure(tag[1].isNotEmpty()) { return null }
return ReportType.parseOrNull(tag[1], tag)
}
@JvmStatic
fun assemble(type: ReportType? = null) = arrayOfNotNull(TAG_NAME, type?.code)
}
}

View File

@ -0,0 +1,55 @@
/**
* 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.nip56Reports.tags
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.nip56Reports.ReportType
import com.vitorpamplona.quartz.utils.arrayOfNotNull
import com.vitorpamplona.quartz.utils.ensure
class HashSha256Tag(
val hash: HexKey,
override val type: ReportType? = null,
) : BaseReportTag {
companion object {
const val TAG_NAME = "x"
@JvmStatic
fun parse(
tag: Array<String>,
defaultReportType: ReportType? = null,
): HashSha256Tag? {
ensure(tag.has(1)) { return null }
ensure(tag[0] == TAG_NAME) { return null }
ensure(tag[1].length == 64) { return null }
val type = tag.getOrNull(2)?.let { ReportType.parseOrNull(it, tag) } ?: defaultReportType
return HashSha256Tag(tag[1], type)
}
@JvmStatic
fun assemble(
hash: String,
type: ReportType? = null,
) = arrayOfNotNull(TAG_NAME, hash, type?.code)
}
}

View File

@ -0,0 +1,71 @@
/**
* 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.nip56Reports.tags
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.nip01Core.tags.addressables.Address
import com.vitorpamplona.quartz.nip56Reports.ReportType
import com.vitorpamplona.quartz.utils.arrayOfNotNull
import com.vitorpamplona.quartz.utils.ensure
@Immutable
class ReportedAddressTag(
val address: Address,
override val type: ReportType? = null,
) : BaseReportTag {
fun toTagArray() = assemble(address, type)
companion object {
const val TAG_NAME = "a"
@JvmStatic
fun parse(
tag: Array<String>,
defaultReportType: ReportType? = null,
): ReportedAddressTag? {
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])
ensure(address != null) { return null }
val type =
if (tag.size == 2) {
defaultReportType
} else if (tag.size == 3) {
ReportType.parseOrNull(tag[2], tag) ?: defaultReportType
} else {
ReportType.parseOrNull(tag[3], tag) ?: defaultReportType
}
return ReportedAddressTag(address, type)
}
@JvmStatic
fun assemble(
address: Address,
type: ReportType? = null,
) = arrayOfNotNull(TAG_NAME, address.toValue(), type?.code)
}
}

View File

@ -0,0 +1,67 @@
/**
* 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.nip56Reports.tags
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.nip56Reports.ReportType
import com.vitorpamplona.quartz.utils.arrayOfNotNull
import com.vitorpamplona.quartz.utils.ensure
@Immutable
class ReportedAuthorTag(
val pubkey: HexKey,
override val type: ReportType? = null,
) : BaseReportTag {
fun toTagArray() = assemble(pubkey, type)
companion object {
const val TAG_NAME = "p"
@JvmStatic
fun parse(
tag: Array<String>,
defaultReportType: ReportType? = null,
): ReportedAuthorTag? {
ensure(tag.has(1)) { return null }
ensure(tag[0] == TAG_NAME) { return null }
ensure(tag[1].length == 64) { return null }
val type =
if (tag.size == 2) {
defaultReportType
} else if (tag.size == 3) {
ReportType.parseOrNull(tag[2], tag) ?: defaultReportType
} else {
ReportType.parseOrNull(tag[3], tag) ?: defaultReportType
}
return ReportedAuthorTag(tag[1], type)
}
@JvmStatic
fun assemble(
pubkey: HexKey,
type: ReportType? = null,
) = arrayOfNotNull(TAG_NAME, pubkey, type?.code)
}
}

View File

@ -0,0 +1,67 @@
/**
* 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.nip56Reports.tags
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.nip56Reports.ReportType
import com.vitorpamplona.quartz.utils.arrayOfNotNull
import com.vitorpamplona.quartz.utils.ensure
@Immutable
class ReportedEventTag(
val eventId: HexKey,
override val type: ReportType? = null,
) : BaseReportTag {
fun toTagArray() = assemble(eventId, type)
companion object {
const val TAG_NAME = "e"
@JvmStatic
fun parse(
tag: Array<String>,
defaultReportType: ReportType? = null,
): ReportedEventTag? {
ensure(tag.has(1)) { return null }
ensure(tag[0] == TAG_NAME) { return null }
ensure(tag[1].length == 64) { return null }
val type =
if (tag.size == 2) {
defaultReportType
} else if (tag.size == 3) {
ReportType.parseOrNull(tag[2], tag) ?: defaultReportType
} else {
ReportType.parseOrNull(tag[3], tag) ?: defaultReportType
}
return ReportedEventTag(tag[1], type)
}
@JvmStatic
fun assemble(
eventId: HexKey,
type: ReportType? = null,
) = arrayOfNotNull(TAG_NAME, eventId, type?.code)
}
}

View File

@ -0,0 +1,43 @@
/**
* 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.nip56Reports.tags
import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.utils.ensure
class ServerTag {
companion object {
const val TAG_NAME = "server"
@JvmStatic
fun parse(tag: Array<String>): String? {
ensure(tag.has(1)) { return null }
ensure(tag[0] == TAG_NAME) { return null }
return tag[1]
}
@JvmStatic
fun assemble(url: String) = arrayOf(TAG_NAME, url)
@JvmStatic
fun assemble(urls: List<String>) = urls.map { assemble(it) }
}
}