diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip30CustomEmoji/pack/EmojiPackEvent.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip30CustomEmoji/pack/EmojiPackEvent.kt index 0a6681dc5..4b67f1311 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip30CustomEmoji/pack/EmojiPackEvent.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip30CustomEmoji/pack/EmojiPackEvent.kt @@ -26,9 +26,11 @@ 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.nip34Git.repository.GitRepositoryEvent -import com.vitorpamplona.quartz.nip34Git.repository.name import com.vitorpamplona.quartz.nip51Lists.PrivateTagArrayEvent +import com.vitorpamplona.quartz.nip51Lists.tags.DescriptionTag +import com.vitorpamplona.quartz.nip51Lists.tags.ImageTag +import com.vitorpamplona.quartz.nip51Lists.tags.NameTag +import com.vitorpamplona.quartz.nip51Lists.tags.TitleTag import com.vitorpamplona.quartz.utils.TimeUtils import kotlin.uuid.ExperimentalUuidApi import kotlin.uuid.Uuid @@ -42,6 +44,18 @@ class EmojiPackEvent( content: String, sig: HexKey, ) : PrivateTagArrayEvent(id, pubKey, createdAt, KIND, tags, content, sig) { + @Deprecated("NIP-51 has deprecated name. Use title instead", ReplaceWith("title()")) + fun name() = tags.firstNotNullOfOrNull(NameTag::parse) + + fun title() = tags.firstNotNullOfOrNull(TitleTag::parse) + + @Suppress("DEPRECATION") + fun titleOrName() = title() ?: name() + + fun description() = tags.firstNotNullOfOrNull(DescriptionTag::parse) + + fun image() = tags.firstNotNullOfOrNull(ImageTag::parse) + companion object { const val KIND = 30030 const val ALT_DESCRIPTION = "Emoji pack" @@ -51,11 +65,11 @@ class EmojiPackEvent( name: String, dTag: String = Uuid.random().toString(), createdAt: Long = TimeUtils.now(), - initializer: TagArrayBuilder.() -> Unit = {}, - ) = eventTemplate(KIND, "", createdAt) { + initializer: TagArrayBuilder.() -> Unit = {}, + ) = eventTemplate(KIND, "", createdAt) { alt(ALT_DESCRIPTION) dTag(dTag) - name(name) + title(name) initializer() } } diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip30CustomEmoji/pack/TagArrayBuilderExt.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip30CustomEmoji/pack/TagArrayBuilderExt.kt new file mode 100644 index 000000000..ca8499310 --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip30CustomEmoji/pack/TagArrayBuilderExt.kt @@ -0,0 +1,32 @@ +/* + * 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.nip30CustomEmoji.pack + +import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder +import com.vitorpamplona.quartz.nip51Lists.tags.DescriptionTag +import com.vitorpamplona.quartz.nip51Lists.tags.ImageTag +import com.vitorpamplona.quartz.nip51Lists.tags.TitleTag + +fun TagArrayBuilder.title(title: String) = addUnique(TitleTag.assemble(title)) + +fun TagArrayBuilder.description(listDescription: String) = addUnique(DescriptionTag.assemble(listDescription)) + +fun TagArrayBuilder.image(url: String) = addUnique(ImageTag.assemble(url)) diff --git a/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nip30CustomEmoji/EmojiPackEventBuildTest.kt b/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nip30CustomEmoji/EmojiPackEventBuildTest.kt new file mode 100644 index 000000000..bf8f04c8e --- /dev/null +++ b/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nip30CustomEmoji/EmojiPackEventBuildTest.kt @@ -0,0 +1,92 @@ +/* + * 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.nip30CustomEmoji + +import com.vitorpamplona.quartz.nip30CustomEmoji.pack.EmojiPackEvent +import com.vitorpamplona.quartz.nip30CustomEmoji.pack.description +import com.vitorpamplona.quartz.nip30CustomEmoji.pack.image +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class EmojiPackEventBuildTest { + @Test + fun buildIncludesTitleDTagAltAndEmojis() { + val template = + EmojiPackEvent.build(name = "My Pack", dTag = "my-pack") { + description("A test pack") + image("https://example.com/cover.jpg") + emoji("smile", "https://example.com/smile.png") + } + + assertEquals(EmojiPackEvent.KIND, template.kind) + assertEquals("", template.content) + + val tagsByName = template.tags.groupBy { it[0] } + assertTrue(tagsByName.containsKey("d")) + assertEquals("my-pack", tagsByName["d"]!!.first()[1]) + assertTrue(tagsByName.containsKey("title")) + assertEquals("My Pack", tagsByName["title"]!!.first()[1]) + assertTrue(tagsByName.containsKey("description")) + assertEquals("A test pack", tagsByName["description"]!!.first()[1]) + assertTrue(tagsByName.containsKey("image")) + assertEquals("https://example.com/cover.jpg", tagsByName["image"]!!.first()[1]) + assertTrue(tagsByName.containsKey("alt")) + assertTrue(tagsByName.containsKey("emoji")) + assertEquals("smile", tagsByName["emoji"]!!.first()[1]) + assertEquals("https://example.com/smile.png", tagsByName["emoji"]!!.first()[2]) + } + + @Test + fun emojiTagUsesEventTypedBuilder() { + // Regression: EmojiPackEvent.build previously used TagArrayBuilder, + // which caused callers to get a template of the wrong type. This test locks in the fix. + val template = EmojiPackEvent.build(name = "test") + assertEquals(EmojiPackEvent.KIND, template.kind) + } + + @Test + fun titleDescriptionImageAccessorsReadTags() { + val event = + EmojiPackEvent( + id = "00", + pubKey = "00", + createdAt = 0L, + tags = + arrayOf( + arrayOf("d", "mypack"), + arrayOf("title", "My Pack"), + arrayOf("description", "desc"), + arrayOf("image", "https://example.com/cover.png"), + arrayOf("emoji", "smile", "https://example.com/smile.png"), + ), + content = "", + sig = "00", + ) + + assertEquals("My Pack", event.title()) + assertEquals("desc", event.description()) + assertEquals("https://example.com/cover.png", event.image()) + assertEquals("My Pack", event.titleOrName()) + assertEquals(1, event.taggedEmojis().size) + assertEquals("smile", event.taggedEmojis().first().code) + } +}