diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/application/SoftwareApplicationEvent.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/application/SoftwareApplicationEvent.kt new file mode 100644 index 000000000..3f92df0ab --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/application/SoftwareApplicationEvent.kt @@ -0,0 +1,83 @@ +/* + * 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.experimental.nip82SoftwareApps.application + +import androidx.compose.runtime.Immutable +import com.vitorpamplona.quartz.nip01Core.core.BaseAddressableEvent +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.nip31Alts.alt +import com.vitorpamplona.quartz.utils.TimeUtils + +/** + * NIP-82 Software Application (kind 32267). + * + * A parameterizable replaceable event describing a software application. + * Published per-application by the maintainer, identified by the `d` tag + * (usually the reverse-domain bundle id, e.g. `com.example.app`). + */ +@Immutable +class SoftwareApplicationEvent( + id: HexKey, + pubKey: HexKey, + createdAt: Long, + tags: Array>, + content: String, + sig: HexKey, +) : BaseAddressableEvent(id, pubKey, createdAt, KIND, tags, content, sig) { + fun appId() = dTag() + + fun name() = tags.name() + + fun summary() = tags.summary() + + fun icon() = tags.icon() + + fun images() = tags.images() + + fun url() = tags.url() + + fun repository() = tags.repository() + + fun platforms() = tags.platforms() + + fun license() = tags.license() + + companion object { + const val KIND = 32267 + const val ALT_DESCRIPTION = "Software application" + + fun build( + appId: String, + name: String, + description: String = "", + createdAt: Long = TimeUtils.now(), + initializer: TagArrayBuilder.() -> Unit = {}, + ) = eventTemplate(KIND, description, createdAt) { + dTag(appId) + alt(ALT_DESCRIPTION) + name(name) + initializer() + } + } +} diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/application/TagArrayBuilderExt.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/application/TagArrayBuilderExt.kt new file mode 100644 index 000000000..b95b36776 --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/application/TagArrayBuilderExt.kt @@ -0,0 +1,51 @@ +/* + * 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.experimental.nip82SoftwareApps.application + +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.application.tags.IconTag +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.application.tags.ImageTag +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.application.tags.LicenseTag +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.application.tags.NameTag +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.application.tags.RepositoryTag +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.application.tags.SummaryTag +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.application.tags.UrlTag +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.shared.PlatformTag +import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder + +fun TagArrayBuilder.name(name: String) = addUnique(NameTag.assemble(name)) + +fun TagArrayBuilder.summary(summary: String) = addUnique(SummaryTag.assemble(summary)) + +fun TagArrayBuilder.icon(iconUrl: String) = addUnique(IconTag.assemble(iconUrl)) + +fun TagArrayBuilder.image(imageUrl: String) = add(ImageTag.assemble(imageUrl)) + +fun TagArrayBuilder.images(imageUrls: List) = addAll(ImageTag.assemble(imageUrls)) + +fun TagArrayBuilder.url(websiteUrl: String) = addUnique(UrlTag.assemble(websiteUrl)) + +fun TagArrayBuilder.repository(repositoryUrl: String) = addUnique(RepositoryTag.assemble(repositoryUrl)) + +fun TagArrayBuilder.platform(platform: String) = add(PlatformTag.assemble(platform)) + +fun TagArrayBuilder.platforms(platforms: List) = addAll(PlatformTag.assemble(platforms)) + +fun TagArrayBuilder.license(spdxId: String) = addUnique(LicenseTag.assemble(spdxId)) diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/application/TagArrayExt.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/application/TagArrayExt.kt new file mode 100644 index 000000000..2b289ea48 --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/application/TagArrayExt.kt @@ -0,0 +1,47 @@ +/* + * 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.experimental.nip82SoftwareApps.application + +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.application.tags.IconTag +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.application.tags.ImageTag +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.application.tags.LicenseTag +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.application.tags.NameTag +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.application.tags.RepositoryTag +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.application.tags.SummaryTag +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.application.tags.UrlTag +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.shared.PlatformTag +import com.vitorpamplona.quartz.nip01Core.core.TagArray + +fun TagArray.name() = firstNotNullOfOrNull(NameTag::parse) + +fun TagArray.summary() = firstNotNullOfOrNull(SummaryTag::parse) + +fun TagArray.icon() = firstNotNullOfOrNull(IconTag::parse) + +fun TagArray.images() = mapNotNull(ImageTag::parse) + +fun TagArray.url() = firstNotNullOfOrNull(UrlTag::parse) + +fun TagArray.repository() = firstNotNullOfOrNull(RepositoryTag::parse) + +fun TagArray.platforms() = mapNotNull(PlatformTag::parse) + +fun TagArray.license() = firstNotNullOfOrNull(LicenseTag::parse) diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/application/tags/IconTag.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/application/tags/IconTag.kt new file mode 100644 index 000000000..abdcb2159 --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/application/tags/IconTag.kt @@ -0,0 +1,39 @@ +/* + * 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.experimental.nip82SoftwareApps.application.tags + +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + +class IconTag { + companion object { + const val TAG_NAME = "icon" + + fun parse(tag: Array): String? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } + return tag[1] + } + + fun assemble(iconUrl: String) = arrayOf(TAG_NAME, iconUrl) + } +} diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/application/tags/ImageTag.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/application/tags/ImageTag.kt new file mode 100644 index 000000000..cc88c6b28 --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/application/tags/ImageTag.kt @@ -0,0 +1,41 @@ +/* + * 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.experimental.nip82SoftwareApps.application.tags + +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + +class ImageTag { + companion object { + const val TAG_NAME = "image" + + fun parse(tag: Array): String? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } + return tag[1] + } + + fun assemble(imageUrl: String) = arrayOf(TAG_NAME, imageUrl) + + fun assemble(imageUrls: List) = imageUrls.map { assemble(it) } + } +} diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/application/tags/LicenseTag.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/application/tags/LicenseTag.kt new file mode 100644 index 000000000..d1bf9125c --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/application/tags/LicenseTag.kt @@ -0,0 +1,40 @@ +/* + * 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.experimental.nip82SoftwareApps.application.tags + +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + +/** NIP-82: `license` tag — SPDX license identifier (e.g. "MIT", "Apache-2.0"). */ +class LicenseTag { + companion object { + const val TAG_NAME = "license" + + fun parse(tag: Array): String? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } + return tag[1] + } + + fun assemble(spdxId: String) = arrayOf(TAG_NAME, spdxId) + } +} diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/application/tags/NameTag.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/application/tags/NameTag.kt new file mode 100644 index 000000000..52475d62b --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/application/tags/NameTag.kt @@ -0,0 +1,39 @@ +/* + * 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.experimental.nip82SoftwareApps.application.tags + +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + +class NameTag { + companion object { + const val TAG_NAME = "name" + + fun parse(tag: Array): String? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } + return tag[1] + } + + fun assemble(name: String) = arrayOf(TAG_NAME, name) + } +} diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/application/tags/RepositoryTag.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/application/tags/RepositoryTag.kt new file mode 100644 index 000000000..731236959 --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/application/tags/RepositoryTag.kt @@ -0,0 +1,43 @@ +/* + * 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.experimental.nip82SoftwareApps.application.tags + +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + +/** + * NIP-82: `repository` tag — source code repository URL + * (e.g. "https://github.com/example/android"). Must be git-cloneable. + */ +class RepositoryTag { + companion object { + const val TAG_NAME = "repository" + + fun parse(tag: Array): String? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } + return tag[1] + } + + fun assemble(repositoryUrl: String) = arrayOf(TAG_NAME, repositoryUrl) + } +} diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/application/tags/SummaryTag.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/application/tags/SummaryTag.kt new file mode 100644 index 000000000..02872601e --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/application/tags/SummaryTag.kt @@ -0,0 +1,39 @@ +/* + * 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.experimental.nip82SoftwareApps.application.tags + +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + +class SummaryTag { + companion object { + const val TAG_NAME = "summary" + + fun parse(tag: Array): String? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } + return tag[1] + } + + fun assemble(summary: String) = arrayOf(TAG_NAME, summary) + } +} diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/application/tags/UrlTag.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/application/tags/UrlTag.kt new file mode 100644 index 000000000..ea7e01c95 --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/application/tags/UrlTag.kt @@ -0,0 +1,39 @@ +/* + * 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.experimental.nip82SoftwareApps.application.tags + +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + +class UrlTag { + companion object { + const val TAG_NAME = "url" + + fun parse(tag: Array): String? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } + return tag[1] + } + + fun assemble(url: String) = arrayOf(TAG_NAME, url) + } +} diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/asset/SoftwareAssetEvent.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/asset/SoftwareAssetEvent.kt new file mode 100644 index 000000000..a58e4ddf5 --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/asset/SoftwareAssetEvent.kt @@ -0,0 +1,106 @@ +/* + * 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.experimental.nip82SoftwareApps.asset + +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.signers.eventTemplate +import com.vitorpamplona.quartz.nip31Alts.alt +import com.vitorpamplona.quartz.utils.TimeUtils + +/** + * NIP-82 Software Asset (kind 3063). + * + * A regular non-replaceable event that cryptographically ties an app install + * artifact to a SHA-256 hash. It extends the NIP-94 kind 1063 tag set with + * software-asset-specific tags such as `version`, `f` (platform) and + * `apk_certificate_hash`. + */ +@Immutable +class SoftwareAssetEvent( + id: HexKey, + pubKey: HexKey, + createdAt: Long, + tags: Array>, + content: String, + sig: HexKey, +) : Event(id, pubKey, createdAt, KIND, tags, content, sig) { + fun appId() = tags.appId() + + fun url() = tags.url() + + fun mimeType() = tags.mimeType() + + fun hash() = tags.hash() + + fun sizeInBytes() = tags.sizeInBytes() + + fun version() = tags.version() + + fun platforms() = tags.platforms() + + fun minPlatformVersion() = tags.minPlatformVersion() + + fun targetPlatformVersion() = tags.targetPlatformVersion() + + fun supportedNips() = tags.supportedNips() + + fun variant() = tags.variant() + + fun commit() = tags.commit() + + fun minAllowedVersion() = tags.minAllowedVersion() + + fun versionCode() = tags.versionCode() + + fun minAllowedVersionCode() = tags.minAllowedVersionCode() + + fun apkCertificateHashes() = tags.apkCertificateHashes() + + fun webContent() = tags.webContent() + + companion object { + const val KIND = 3063 + const val ALT_DESCRIPTION = "Software asset" + + fun build( + appId: String, + mimeType: String, + hash: String, + version: String, + assetUrl: String? = null, + sizeInBytes: Int? = null, + createdAt: Long = TimeUtils.now(), + initializer: TagArrayBuilder.() -> Unit = {}, + ) = eventTemplate(KIND, "", createdAt) { + alt(ALT_DESCRIPTION) + appId(appId) + mimeType(mimeType) + hash(hash) + version(version) + assetUrl?.let { url(it) } + sizeInBytes?.let { sizeInBytes(it) } + initializer() + } + } +} diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/asset/TagArrayBuilderExt.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/asset/TagArrayBuilderExt.kt new file mode 100644 index 000000000..cc3c0354f --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/asset/TagArrayBuilderExt.kt @@ -0,0 +1,80 @@ +/* + * 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.experimental.nip82SoftwareApps.asset + +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.asset.tags.ApkCertificateHashTag +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.asset.tags.CommitTag +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.asset.tags.MinAllowedVersionCodeTag +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.asset.tags.MinAllowedVersionTag +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.asset.tags.MinPlatformVersionTag +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.asset.tags.SupportedNipTag +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.asset.tags.TargetPlatformVersionTag +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.asset.tags.VariantTag +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.asset.tags.VersionCodeTag +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.asset.tags.WebContentTag +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.release.tags.AppIdTag +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.release.tags.VersionTag +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.shared.PlatformTag +import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder +import com.vitorpamplona.quartz.nip94FileMetadata.tags.HashSha256Tag +import com.vitorpamplona.quartz.nip94FileMetadata.tags.MimeTypeTag +import com.vitorpamplona.quartz.nip94FileMetadata.tags.SizeTag +import com.vitorpamplona.quartz.nip94FileMetadata.tags.UrlTag + +fun TagArrayBuilder.appId(appId: String) = addUnique(AppIdTag.assemble(appId)) + +fun TagArrayBuilder.url(url: String) = addUnique(UrlTag.assemble(url)) + +fun TagArrayBuilder.mimeType(mimeType: String) = addUnique(MimeTypeTag.assemble(mimeType)) + +fun TagArrayBuilder.hash(hash: String) = addUnique(HashSha256Tag.assemble(hash)) + +fun TagArrayBuilder.sizeInBytes(sizeInBytes: Int) = addUnique(SizeTag.assemble(sizeInBytes)) + +fun TagArrayBuilder.version(version: String) = addUnique(VersionTag.assemble(version)) + +fun TagArrayBuilder.platform(platform: String) = add(PlatformTag.assemble(platform)) + +fun TagArrayBuilder.platforms(platforms: List) = addAll(PlatformTag.assemble(platforms)) + +fun TagArrayBuilder.minPlatformVersion(version: String) = addUnique(MinPlatformVersionTag.assemble(version)) + +fun TagArrayBuilder.targetPlatformVersion(version: String) = addUnique(TargetPlatformVersionTag.assemble(version)) + +fun TagArrayBuilder.supportedNip(nipId: String) = add(SupportedNipTag.assemble(nipId)) + +fun TagArrayBuilder.supportedNips(nipIds: List) = addAll(SupportedNipTag.assemble(nipIds)) + +fun TagArrayBuilder.variant(variant: String) = addUnique(VariantTag.assemble(variant)) + +fun TagArrayBuilder.commit(commit: String) = addUnique(CommitTag.assemble(commit)) + +fun TagArrayBuilder.minAllowedVersion(version: String) = addUnique(MinAllowedVersionTag.assemble(version)) + +fun TagArrayBuilder.versionCode(versionCode: Long) = addUnique(VersionCodeTag.assemble(versionCode)) + +fun TagArrayBuilder.minAllowedVersionCode(versionCode: Long) = addUnique(MinAllowedVersionCodeTag.assemble(versionCode)) + +fun TagArrayBuilder.apkCertificateHash(hash: String) = add(ApkCertificateHashTag.assemble(hash)) + +fun TagArrayBuilder.apkCertificateHashes(hashes: List) = addAll(ApkCertificateHashTag.assemble(hashes)) + +fun TagArrayBuilder.webContent(url: String) = addUnique(WebContentTag.assemble(url)) diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/asset/TagArrayExt.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/asset/TagArrayExt.kt new file mode 100644 index 000000000..da2e8b576 --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/asset/TagArrayExt.kt @@ -0,0 +1,74 @@ +/* + * 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.experimental.nip82SoftwareApps.asset + +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.asset.tags.ApkCertificateHashTag +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.asset.tags.CommitTag +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.asset.tags.MinAllowedVersionCodeTag +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.asset.tags.MinAllowedVersionTag +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.asset.tags.MinPlatformVersionTag +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.asset.tags.SupportedNipTag +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.asset.tags.TargetPlatformVersionTag +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.asset.tags.VariantTag +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.asset.tags.VersionCodeTag +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.asset.tags.WebContentTag +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.release.tags.AppIdTag +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.release.tags.VersionTag +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.shared.PlatformTag +import com.vitorpamplona.quartz.nip01Core.core.TagArray +import com.vitorpamplona.quartz.nip94FileMetadata.tags.HashSha256Tag +import com.vitorpamplona.quartz.nip94FileMetadata.tags.MimeTypeTag +import com.vitorpamplona.quartz.nip94FileMetadata.tags.SizeTag +import com.vitorpamplona.quartz.nip94FileMetadata.tags.UrlTag + +fun TagArray.appId() = firstNotNullOfOrNull(AppIdTag::parse) + +fun TagArray.url() = firstNotNullOfOrNull(UrlTag::parse) + +fun TagArray.mimeType() = firstNotNullOfOrNull(MimeTypeTag::parse) + +fun TagArray.hash() = firstNotNullOfOrNull(HashSha256Tag::parse) + +fun TagArray.sizeInBytes() = firstNotNullOfOrNull(SizeTag::parse) + +fun TagArray.version() = firstNotNullOfOrNull(VersionTag::parse) + +fun TagArray.platforms() = mapNotNull(PlatformTag::parse) + +fun TagArray.minPlatformVersion() = firstNotNullOfOrNull(MinPlatformVersionTag::parse) + +fun TagArray.targetPlatformVersion() = firstNotNullOfOrNull(TargetPlatformVersionTag::parse) + +fun TagArray.supportedNips() = mapNotNull(SupportedNipTag::parse) + +fun TagArray.variant() = firstNotNullOfOrNull(VariantTag::parse) + +fun TagArray.commit() = firstNotNullOfOrNull(CommitTag::parse) + +fun TagArray.minAllowedVersion() = firstNotNullOfOrNull(MinAllowedVersionTag::parse) + +fun TagArray.versionCode() = firstNotNullOfOrNull(VersionCodeTag::parse) + +fun TagArray.minAllowedVersionCode() = firstNotNullOfOrNull(MinAllowedVersionCodeTag::parse) + +fun TagArray.apkCertificateHashes() = mapNotNull(ApkCertificateHashTag::parse) + +fun TagArray.webContent() = firstNotNullOfOrNull(WebContentTag::parse) diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/asset/tags/ApkCertificateHashTag.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/asset/tags/ApkCertificateHashTag.kt new file mode 100644 index 000000000..ac80999d3 --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/asset/tags/ApkCertificateHashTag.kt @@ -0,0 +1,45 @@ +/* + * 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.experimental.nip82SoftwareApps.asset.tags + +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + +/** + * NIP-82: `apk_certificate_hash` tag — certificate hash of the APK signing + * certificate. REQUIRED for Android APK assets (may be repeated). + */ +class ApkCertificateHashTag { + companion object { + const val TAG_NAME = "apk_certificate_hash" + + fun parse(tag: Array): String? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } + return tag[1] + } + + fun assemble(hash: String) = arrayOf(TAG_NAME, hash) + + fun assemble(hashes: List) = hashes.map { assemble(it) } + } +} diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/asset/tags/CommitTag.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/asset/tags/CommitTag.kt new file mode 100644 index 000000000..625a8d632 --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/asset/tags/CommitTag.kt @@ -0,0 +1,40 @@ +/* + * 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.experimental.nip82SoftwareApps.asset.tags + +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + +/** NIP-82: `commit` tag — commit id used to build the asset (same as NIP-34). */ +class CommitTag { + companion object { + const val TAG_NAME = "commit" + + fun parse(tag: Array): String? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } + return tag[1] + } + + fun assemble(commit: String) = arrayOf(TAG_NAME, commit) + } +} diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/asset/tags/MinAllowedVersionCodeTag.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/asset/tags/MinAllowedVersionCodeTag.kt new file mode 100644 index 000000000..2a754fb70 --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/asset/tags/MinAllowedVersionCodeTag.kt @@ -0,0 +1,43 @@ +/* + * 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.experimental.nip82SoftwareApps.asset.tags + +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + +/** + * NIP-82: `min_allowed_version_code` tag — minimum allowed integer version + * code of the asset. + */ +class MinAllowedVersionCodeTag { + companion object { + const val TAG_NAME = "min_allowed_version_code" + + fun parse(tag: Array): Long? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } + return tag[1].toLongOrNull() + } + + fun assemble(versionCode: Long) = arrayOf(TAG_NAME, versionCode.toString()) + } +} diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/asset/tags/MinAllowedVersionTag.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/asset/tags/MinAllowedVersionTag.kt new file mode 100644 index 000000000..6a0b751f8 --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/asset/tags/MinAllowedVersionTag.kt @@ -0,0 +1,43 @@ +/* + * 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.experimental.nip82SoftwareApps.asset.tags + +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + +/** + * NIP-82: `min_allowed_version` tag — defines the minimum asset version + * still considered valid. + */ +class MinAllowedVersionTag { + companion object { + const val TAG_NAME = "min_allowed_version" + + fun parse(tag: Array): String? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } + return tag[1] + } + + fun assemble(version: String) = arrayOf(TAG_NAME, version) + } +} diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/asset/tags/MinPlatformVersionTag.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/asset/tags/MinPlatformVersionTag.kt new file mode 100644 index 000000000..9de1816fb --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/asset/tags/MinPlatformVersionTag.kt @@ -0,0 +1,43 @@ +/* + * 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.experimental.nip82SoftwareApps.asset.tags + +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + +/** + * NIP-82: `min_platform_version` tag — minimum platform/runtime version an + * asset supports. + */ +class MinPlatformVersionTag { + companion object { + const val TAG_NAME = "min_platform_version" + + fun parse(tag: Array): String? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } + return tag[1] + } + + fun assemble(version: String) = arrayOf(TAG_NAME, version) + } +} diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/asset/tags/SupportedNipTag.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/asset/tags/SupportedNipTag.kt new file mode 100644 index 000000000..74ae07a57 --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/asset/tags/SupportedNipTag.kt @@ -0,0 +1,42 @@ +/* + * 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.experimental.nip82SoftwareApps.asset.tags + +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + +/** NIP-82: `supported_nip` tag — a NIP identifier supported by the software. */ +class SupportedNipTag { + companion object { + const val TAG_NAME = "supported_nip" + + fun parse(tag: Array): String? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } + return tag[1] + } + + fun assemble(nipId: String) = arrayOf(TAG_NAME, nipId) + + fun assemble(nipIds: List) = nipIds.map { assemble(it) } + } +} diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/asset/tags/TargetPlatformVersionTag.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/asset/tags/TargetPlatformVersionTag.kt new file mode 100644 index 000000000..7a0b89c1a --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/asset/tags/TargetPlatformVersionTag.kt @@ -0,0 +1,43 @@ +/* + * 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.experimental.nip82SoftwareApps.asset.tags + +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + +/** + * NIP-82: `target_platform_version` tag — platform/runtime version the asset + * was designed for. + */ +class TargetPlatformVersionTag { + companion object { + const val TAG_NAME = "target_platform_version" + + fun parse(tag: Array): String? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } + return tag[1] + } + + fun assemble(version: String) = arrayOf(TAG_NAME, version) + } +} diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/asset/tags/VariantTag.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/asset/tags/VariantTag.kt new file mode 100644 index 000000000..972fcf548 --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/asset/tags/VariantTag.kt @@ -0,0 +1,43 @@ +/* + * 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.experimental.nip82SoftwareApps.asset.tags + +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + +/** + * NIP-82: `variant` tag — human-readable differentiator between sibling + * assets (e.g. "offline"), falling back to filename when omitted. + */ +class VariantTag { + companion object { + const val TAG_NAME = "variant" + + fun parse(tag: Array): String? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } + return tag[1] + } + + fun assemble(variant: String) = arrayOf(TAG_NAME, variant) + } +} diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/asset/tags/VersionCodeTag.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/asset/tags/VersionCodeTag.kt new file mode 100644 index 000000000..4c686cbf7 --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/asset/tags/VersionCodeTag.kt @@ -0,0 +1,43 @@ +/* + * 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.experimental.nip82SoftwareApps.asset.tags + +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + +/** + * NIP-82: `version_code` tag — integer version code (Android and others). + * REQUIRED for Android APK assets. + */ +class VersionCodeTag { + companion object { + const val TAG_NAME = "version_code" + + fun parse(tag: Array): Long? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } + return tag[1].toLongOrNull() + } + + fun assemble(versionCode: Long) = arrayOf(TAG_NAME, versionCode.toString()) + } +} diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/asset/tags/WebContentTag.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/asset/tags/WebContentTag.kt new file mode 100644 index 000000000..d8202172a --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/asset/tags/WebContentTag.kt @@ -0,0 +1,43 @@ +/* + * 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.experimental.nip82SoftwareApps.asset.tags + +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + +/** + * NIP-82: `r` tag for a Software Asset — URL of the original web content + * (e.g. a PWA start URL). + */ +class WebContentTag { + companion object { + const val TAG_NAME = "r" + + fun parse(tag: Array): String? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } + return tag[1] + } + + fun assemble(url: String) = arrayOf(TAG_NAME, url) + } +} diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/release/SoftwareReleaseEvent.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/release/SoftwareReleaseEvent.kt new file mode 100644 index 000000000..7e802fd24 --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/release/SoftwareReleaseEvent.kt @@ -0,0 +1,99 @@ +/* + * 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.experimental.nip82SoftwareApps.release + +import androidx.compose.runtime.Immutable +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.asset.SoftwareAssetEvent +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.release.tags.AssetTag +import com.vitorpamplona.quartz.nip01Core.core.BaseAddressableEvent +import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder +import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle +import com.vitorpamplona.quartz.nip01Core.hints.EventHintProvider +import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate +import com.vitorpamplona.quartz.nip01Core.tags.dTag.dTag +import com.vitorpamplona.quartz.nip31Alts.alt +import com.vitorpamplona.quartz.utils.TimeUtils + +/** + * NIP-82 Software Release (kind 30063). + * + * A parameterizable replaceable event grouping a set of [SoftwareAssetEvent]s + * under a named release version. The `d` tag is `@` and the + * event's `created_at` must be the release date. + * + * NOTE: kind 30063 was also used by NIP-51 `ReleaseArtifactSetEvent`. This + * experimental NIP-82 event is intentionally not registered in `EventFactory` + * to avoid colliding with the existing registration. Callers can instantiate + * or wrap [SoftwareReleaseEvent] directly when they know the event is a NIP-82 + * release. + */ +@Immutable +class SoftwareReleaseEvent( + id: HexKey, + pubKey: HexKey, + createdAt: Long, + tags: Array>, + content: String, + sig: HexKey, +) : BaseAddressableEvent(id, pubKey, createdAt, KIND, tags, content, sig), + EventHintProvider { + override fun eventHints() = tags.mapNotNull(AssetTag::parseAsHint) + + override fun linkedEventIds() = tags.mapNotNull(AssetTag::parseId) + + fun appId() = tags.appId() + + fun version() = tags.version() + + fun channel() = tags.channel() + + fun assets() = tags.assets() + + companion object { + const val KIND = 30063 + const val ALT_DESCRIPTION = "Software release" + + /** NIP-82 requires `d = @`. */ + fun buildDTag( + appId: String, + version: String, + ) = "$appId@$version" + + fun build( + appId: String, + version: String, + channel: String, + assets: List>, + releaseNotes: String = "", + createdAt: Long = TimeUtils.now(), + initializer: TagArrayBuilder.() -> Unit = {}, + ) = eventTemplate(KIND, releaseNotes, createdAt) { + dTag(buildDTag(appId, version)) + alt(ALT_DESCRIPTION) + appId(appId) + version(version) + channel(channel) + assets(assets) + initializer() + } + } +} diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/release/TagArrayBuilderExt.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/release/TagArrayBuilderExt.kt new file mode 100644 index 000000000..42d9195f6 --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/release/TagArrayBuilderExt.kt @@ -0,0 +1,39 @@ +/* + * 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.experimental.nip82SoftwareApps.release + +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.asset.SoftwareAssetEvent +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.release.tags.AppIdTag +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.release.tags.AssetTag +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.release.tags.ChannelTag +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.release.tags.VersionTag +import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder +import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle + +fun TagArrayBuilder.appId(appId: String) = addUnique(AppIdTag.assemble(appId)) + +fun TagArrayBuilder.version(version: String) = addUnique(VersionTag.assemble(version)) + +fun TagArrayBuilder.channel(channel: String) = addUnique(ChannelTag.assemble(channel)) + +fun TagArrayBuilder.asset(asset: EventHintBundle) = add(AssetTag.assemble(asset)) + +fun TagArrayBuilder.assets(assets: List>) = addAll(AssetTag.assemble(assets)) diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/release/TagArrayExt.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/release/TagArrayExt.kt new file mode 100644 index 000000000..cf657fceb --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/release/TagArrayExt.kt @@ -0,0 +1,35 @@ +/* + * 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.experimental.nip82SoftwareApps.release + +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.release.tags.AppIdTag +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.release.tags.AssetTag +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.release.tags.ChannelTag +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.release.tags.VersionTag +import com.vitorpamplona.quartz.nip01Core.core.TagArray + +fun TagArray.appId() = firstNotNullOfOrNull(AppIdTag::parse) + +fun TagArray.version() = firstNotNullOfOrNull(VersionTag::parse) + +fun TagArray.channel() = firstNotNullOfOrNull(ChannelTag::parse) + +fun TagArray.assets() = mapNotNull(AssetTag::parse) diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/release/tags/AppIdTag.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/release/tags/AppIdTag.kt new file mode 100644 index 000000000..ec74b761a --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/release/tags/AppIdTag.kt @@ -0,0 +1,45 @@ +/* + * 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.experimental.nip82SoftwareApps.release.tags + +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + +/** + * NIP-82: `i` tag (identifier) used in a Software Release / Software Asset to + * point back to the Software Application's `d` tag. + * + * NOTE: here "i" stands for "identifier", not "infohash". + */ +class AppIdTag { + companion object { + const val TAG_NAME = "i" + + fun parse(tag: Array): String? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } + return tag[1] + } + + fun assemble(appId: String) = arrayOf(TAG_NAME, appId) + } +} diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/release/tags/AssetTag.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/release/tags/AssetTag.kt new file mode 100644 index 000000000..317bdc3f2 --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/release/tags/AssetTag.kt @@ -0,0 +1,97 @@ +/* + * 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.experimental.nip82SoftwareApps.release.tags + +import androidx.compose.runtime.Immutable +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.asset.SoftwareAssetEvent +import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle +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.GenericETag +import com.vitorpamplona.quartz.utils.arrayOfNotNull +import com.vitorpamplona.quartz.utils.ensure + +/** + * NIP-82: `e` tag referencing a Software Asset (kind 3063) from a Software Release. + */ +@Immutable +data class AssetTag( + override val eventId: HexKey, +) : GenericETag { + override var relay: NormalizedRelayUrl? = null + override var author: HexKey? = null + + constructor(eventId: HexKey, relayHint: NormalizedRelayUrl? = null, authorPubKeyHex: HexKey? = null) : this(eventId) { + this.relay = relayHint + this.author = authorPubKeyHex + } + + override fun toTagArray() = assemble(eventId, relay, author) + + companion object { + const val TAG_NAME = "e" + + fun isTagged(tag: Array) = tag.has(1) && tag[0] == TAG_NAME && tag[1].length == 64 + + fun parse(tag: Array): AssetTag? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].length == 64) { return null } + + val hint = tag.getOrNull(2)?.let { RelayUrlNormalizer.normalizeOrNull(it) } + + return AssetTag(tag[1], hint, tag.getOrNull(3)) + } + + fun parseId(tag: Array): String? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].length == 64) { return null } + return tag[1] + } + + fun parseAsHint(tag: Array): 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 hint = RelayUrlNormalizer.normalizeOrNull(tag[2]) + + ensure(hint != null) { return null } + + return EventIdHint(tag[1], hint) + } + + fun assemble( + eventId: HexKey, + relay: NormalizedRelayUrl?, + author: HexKey?, + ) = arrayOfNotNull(TAG_NAME, eventId, relay?.url, author) + + fun assemble(eventHint: EventHintBundle) = assemble(eventHint.event.id, eventHint.relay, eventHint.event.pubKey) + + fun assemble(eventHints: List>) = eventHints.map { assemble(it) } + } +} diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/release/tags/ChannelTag.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/release/tags/ChannelTag.kt new file mode 100644 index 000000000..82d5c406c --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/release/tags/ChannelTag.kt @@ -0,0 +1,51 @@ +/* + * 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.experimental.nip82SoftwareApps.release.tags + +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + +/** + * NIP-82 Appendix B release channel identifiers. + * Custom channel identifiers MAY also be used. + */ +object ReleaseChannel { + const val MAIN = "main" + const val BETA = "beta" + const val NIGHTLY = "nightly" + const val DEV = "dev" +} + +/** NIP-82: `c` tag — release channel identifier. */ +class ChannelTag { + companion object { + const val TAG_NAME = "c" + + fun parse(tag: Array): String? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } + return tag[1] + } + + fun assemble(channel: String) = arrayOf(TAG_NAME, channel) + } +} diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/release/tags/VersionTag.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/release/tags/VersionTag.kt new file mode 100644 index 000000000..2cddfcc4c --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/release/tags/VersionTag.kt @@ -0,0 +1,40 @@ +/* + * 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.experimental.nip82SoftwareApps.release.tags + +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + +/** NIP-82: `version` tag — release or asset version string (e.g. "1.2.3", "v0.1.2-rc1"). */ +class VersionTag { + companion object { + const val TAG_NAME = "version" + + fun parse(tag: Array): String? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } + return tag[1] + } + + fun assemble(version: String) = arrayOf(TAG_NAME, version) + } +} diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/shared/PlatformTag.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/shared/PlatformTag.kt new file mode 100644 index 000000000..cf1531917 --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/nip82SoftwareApps/shared/PlatformTag.kt @@ -0,0 +1,72 @@ +/* + * 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.experimental.nip82SoftwareApps.shared + +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + +/** + * NIP-82 Appendix A platform identifiers, loosely based on `uname -sm`. + * Publishers may also use custom values not listed here. + */ +object Platform { + const val ANDROID_ARM64_V8A = "android-arm64-v8a" + const val ANDROID_ARMEABI_V7A = "android-armeabi-v7a" + const val ANDROID_X86 = "android-x86" + const val ANDROID_X86_64 = "android-x86_64" + const val DARWIN_ARM64 = "darwin-arm64" + const val DARWIN_X86_64 = "darwin-x86_64" + const val LINUX_AARCH64 = "linux-aarch64" + const val LINUX_X86_64 = "linux-x86_64" + const val WINDOWS_AARCH64 = "windows-aarch64" + const val WINDOWS_X86_64 = "windows-x86_64" + const val IOS_ARM64 = "ios-arm64" + const val FREEBSD_X86_64 = "freebsd-x86_64" + const val FREEBSD_AARCH64 = "freebsd-aarch64" + const val LINUX_ARMV7L = "linux-armv7l" + const val LINUX_RISCV64 = "linux-riscv64" + const val WASM32 = "wasm32" + const val WASM64 = "wasm64" + const val WASI_WASM32 = "wasi-wasm32" + const val WASI_WASM64 = "wasi-wasm64" +} + +/** + * NIP-82: `f` tag — platform identifier restricting compatibility when the MIME type + * alone does not fully determine the target platform. See [Platform] for known + * identifiers and NIP-82 Appendix A. + */ +class PlatformTag { + companion object { + const val TAG_NAME = "f" + + fun parse(tag: Array): String? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } + return tag[1] + } + + fun assemble(platform: String) = arrayOf(TAG_NAME, platform) + + fun assemble(platforms: List) = platforms.map { assemble(it) } + } +} diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/EventFactory.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/EventFactory.kt index 46c155835..b7d3de13d 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/EventFactory.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/EventFactory.kt @@ -35,6 +35,8 @@ import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStory import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryReadingStateEvent import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStorySceneEvent import com.vitorpamplona.quartz.experimental.medical.FhirResourceEvent +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.application.SoftwareApplicationEvent +import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.asset.SoftwareAssetEvent import com.vitorpamplona.quartz.experimental.nip95.data.FileStorageEvent import com.vitorpamplona.quartz.experimental.nip95.header.FileStorageHeaderEvent import com.vitorpamplona.quartz.experimental.nipA3.PaymentTargetsEvent @@ -528,6 +530,8 @@ class EventFactory { SealedRumorEvent.KIND -> SealedRumorEvent(id, pubKey, createdAt, tags, content, sig) SearchRelayListEvent.KIND -> SearchRelayListEvent(id, pubKey, createdAt, tags, content, sig) SimpleGroupListEvent.KIND -> SimpleGroupListEvent(id, pubKey, createdAt, tags, content, sig) + SoftwareApplicationEvent.KIND -> SoftwareApplicationEvent(id, pubKey, createdAt, tags, content, sig) + SoftwareAssetEvent.KIND -> SoftwareAssetEvent(id, pubKey, createdAt, tags, content, sig) StallEvent.KIND -> StallEvent(id, pubKey, createdAt, tags, content, sig) StatusEvent.KIND -> StatusEvent(id, pubKey, createdAt, tags, content, sig) TextNoteEvent.KIND -> TextNoteEvent(id, pubKey, createdAt, tags, content, sig)