diff --git a/README.md b/README.md index dce91b635..7fda6ae95 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ height="70">](https://github.com/vitorpamplona/amethyst/releases) - [ ] Relay-based Groups (NIP-29) - [x] Custom Emoji (NIP-30) - [x] Event alt descriptors (NIP-31) -- [ ] Labeling (NIP-32) +- [x] Labeling (NIP-32) - [x] Git Stuff (NIP-34) - [x] Torrents (NIP-35) - [x] Sensitive Content (NIP-36) diff --git a/amethyst/src/main/res/values-hu-rHU/strings.xml b/amethyst/src/main/res/values-hu-rHU/strings.xml index 0f96a3028..607f92a46 100644 --- a/amethyst/src/main/res/values-hu-rHU/strings.xml +++ b/amethyst/src/main/res/values-hu-rHU/strings.xml @@ -387,6 +387,9 @@ Hozzáadás a nyilvános könyvjelzőkhöz Törlés a privát könyvjelzőkből Törlés a nyilvános könyvjelzőkből + Rögzített bejegyzések + Rögzítés a profilhoz + Rögzítés megszüntetése a profiltól Könyvjelzőlisták Könyvjelzőlista ikonja Új könyvjelzőlista @@ -719,6 +722,17 @@ Általános szerződési feltételek Nem érhető el Hibák és megjegyzések ettől az átjátszótól + Átjátszófigyelési jelentések + Megnyitás + Olvasás + Írás + RTT (válaszidő) + Hálózat + Típus + Támogatott NIP-ek + Követelmények + Utoljára ellenőrizve + %1$d ms Aktív előfizetések Függőben lévő kimenő események Szükséges előfizetések (%1$d) @@ -1655,8 +1669,12 @@ nincsenek események Bitcoin felfedező (OTS) események + Öntől származó események + Önnek szóló események + kereshető események Közvetlen üzenetek profilok + kimenő átjátszók listája átjátszóbeállítások Utoljára %1$s ezelőtt látták <%1$s @@ -1721,5 +1739,94 @@ Törli ezt a webes könyvjelzőt? Webcím megnyitása + Ez a gyűjtési cél lezárult + %1$s gyűlt össze a(z) %2$s satoshis célból + Célösszeg (satoshiban) + 100 000 + Cél leírása, bemutatása + Mire gyűjt adományokat? + Rövid összefoglalás + Rövid leírás, amely megjelenik az előnézetekben + Kép webcíme (nem kötelező) + https://pelda.hu/kep.jpg + Weboldal webcíme (nem kötelező) + https://pelda.hu + Határidő (nem kötelező) + Határidő beállítása + Új cél + Cél létrehozása + Törlési kérés + Kérés az átjátszóknak, hogy a kiválasztott dátumig visszamenőleg véglegesen töröljék az Ön összes adatát. Ez a művelet a NIP-62 szabványon alapul, és egyes joghatóságokban jogilag kötelező érvényű. + Egy átjátszó kiválasztása + Célátjátszók + Összes átjátszó + Ezzel az ÖSSZES átjátszót arra kéri, hogy a kiválasztott dátumig töröljön mindent, ami az Ön kulcsához kapcsolódik. Ezt az eseményt a lehető legszélesebb körben közzétesszük. Ez a művelet nem vonható vissza. + Adatok törlése eddig: + Az ezen időpont előtt létrehozott összes esemény törlését kérelmezi a kiválasztott átjátszótól. + Indoklás (nem kötelező) + Indoklás vagy jogi nyilatkozat az átjátszó üzemeltetőjének + Törlési kérés küldése + Törlési kérés megerősítése + Ön arra kéri a(z) %1$s átjátszót, hogy a kiválasztott dátum előtt létrehozott összes adatát végleges törölje. Ez a művelet nem vonható vissza. + Ön arra kéri az ÖSSZES átjátszót, hogy a kiválasztott dátum előtt létrehozott összes adatát végleges törölje. Ez a művelet közvetítve lesz mindenhová és nem vonható vissza. + Törlési kérés elküldve + Dátum kiválasztása + Idő kiválasztása + Törlési előzmények + Frissítés + Ezek az Ön korábbi, a kapcsolódott átjátszókon talált törlési kérései. Az ezekben az eseményekben megjelölt átjátszók nem tárolhatják az Ön adatait az esemény dátuma előtti időszakból. + Nem találhatók törlési kérések + Ön még nem küldött törlési kérést. + Célátjátszók + Ez a kérés érinti az összes átjátszót. Használja a törlési kérés menüjét az egyes átjátszók megfelelőségének teszteléséhez. + Tesztelés + Megfelelő + Nem megfelelő + Hiba + Törlési előzmények + %1$s kezelése + Átjátszókezelési funkciók betöltése… + Nem sikerült kapcsolódni az átjátszókezeléshez + Nem érhető el kezelési eljárás + Elvetés + Felhasználók + Események + Típusok + IP-címek + Beállítások + Letiltott felhasználók + Nincsenek letiltott felhasználók + Engedélyezett felhasználók + Nincsenek engedélyezett felhasználók + Moderációs várólista + Nincsenek moderálandó események + Letiltott események + Nincsenek letiltott események + Engedélyezett típusok + Nincsenek engedélyezett típusok + Letiltott IP-címek + Nincsenek letiltott IP-címek + Hozzáadás + Eltávolítás + Engedélyezés + Letiltás + Nyilvános kulcs letiltása + Nyilvános kulcs engedélyezése + Esemény letiltása + Típus engedélyezése + IP-cím letiltása + Nyilvános kulcs (hex) + Eseményazonosító (hex) + Típus száma + IP-cím + Indoklás (nem kötelező) + Megerősítés + Mégse + Alkamaz + Átjátszó neve + Átjátszó leírása + Átjátszó ikonjának webcíme + Átjátszó kezelése + Kezelés diff --git a/amethyst/src/main/res/values-pl-rPL/strings.xml b/amethyst/src/main/res/values-pl-rPL/strings.xml index a646715c6..183489745 100644 --- a/amethyst/src/main/res/values-pl-rPL/strings.xml +++ b/amethyst/src/main/res/values-pl-rPL/strings.xml @@ -384,6 +384,9 @@ Dodaj do publicznych zakładek Usuń z prywatnych zakładek Usuń z publicznych zakładek + Przypięte notatki + Przypnij do profilu + Odepnij z profilu Listy zakładek Ikona listy zakładek Nowa lista zakładek @@ -716,6 +719,16 @@ Zasady i warunki użytkowania Nie dotyczy Błędy i powiadomienia z tego transmitera + Raporty z monitoringu transmitera + Otwórz + Odczyt + Zapis + Sieć + Typ + Obsługiwane Nipy + Wymagania + Ostatnie sprawdzenie + %1$d ms Aktywne subskrypcje Wydarzenia oczekujące na wysłanie REQ Subskrypcje (%1$d) @@ -1652,6 +1665,7 @@ wydarzeń DMs profile + lista transmiterów wysyłających ustawienia transmiterów Ostatnio widziano %1$s temu <%1$s @@ -1716,5 +1730,56 @@ Usunąć tę zakładkę? Otwórz adres URL + Ta zbiórka została zamknięta + Sfinansowano %1$s z %2$s satoszów + Kwota zbiorki (w satoszach) + 100000 + Opisz cel zbiórki + Na co zbierasz fundusze? + Krótkie podsumowanie + Krótki opis wyświetlany w podglądzie + Adres URL zdjęcia (opcjonalnie) + https://domena.pl/img.jpg + Adres strony (opcjonalnie) + https://domena.pl + Termin ukończenia (opcjonalnie) + Ustaw ostateczny termin + Nowy cel zbiórki + Utwórz zbiórkę + Prośba o usunięcie + Poproś transmitery o całkowite usunięcie wszystkich Twoich danych z okresu do wybranej daty. Działanie to opiera się na standardzie NIP-62 i w niektórych jurysdykcjach ma moc prawną. + Wybierz transmiter + Transmitery docelowe + WSZYSTKIE TRANSMITERY + Spowoduje to wysłanie żądania do WSZYSTKICH przekaźników o usunięcie wszystkich danych powiązanych z Twoim kluczem do wybranej daty. Informacja ta zostanie rozpowszechniona tak szeroko, jak to tylko możliwe. Czynności tej nie można cofnąć. + Usuń dane aż do + Wszystkie wydarzenia utworzone przed tą datą zostaną zgłoszone do usunięcia z wybranego transmitera. + Powód (Opcjonalne) + Uzasadnienie lub informacja prawna dla operatora transmitera + Wyślij prośbę o usunięcie + Potwierdź żądanie usunięcia + Zamierzasz poprosić %1$s o trwałe usunięcie wszystkich danych utworzonych przed wybraną datą. Czynności tej nie można cofnąć. + Za chwilę wyślesz żądanie do KAŻDEGO transmitera, aby trwale usunął wszystkie Twoje dane utworzone przed wybraną datą. Komunikat ten zostanie rozesłany wszędzie i nie będzie można go cofnąć. + Wysłano prośbę o usunięcie + Wybierz datę + Wybierz godzinę + Wyczyść historię + Odśwież + Oto Twoje wcześniejsze prośby o usunięcie danych znalezione na podłączonych transmiterach. Transmitery oznaczone w tych wydarzeniach nie powinny przechowywać żadnych Twoich danych sprzed daty wydarzenia. + Nie znaleziono wniosków o usunięcie + Nie wysłano jeszcze żadnych żądań do usunięcia wydarzeń. + Transmitery docelowe + To żądanie dotyczy wszystkich transmiterów. Aby sprawdzić zgodność poszczególnych transmiterów, skorzystaj z opcji \"Prośba o usunięcie\". + Test + Zgodny + Niezgodny + Błąd + Wyczyść historię + Zarządzaj %1$s + Wczytywanie funkcji zarządzania transmiterem… + Brak dostępnych metod zarządzania + Ignoruj + Zarządzaj Transmiterem + Zarządzaj diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip32Labeling/EventExt.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip32Labeling/EventExt.kt new file mode 100644 index 000000000..5a1adc81a --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip32Labeling/EventExt.kt @@ -0,0 +1,33 @@ +/* + * 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.nip32Labeling + +import com.vitorpamplona.quartz.nip01Core.core.Event +import com.vitorpamplona.quartz.nip32Labeling.tags.LabelNamespaceTag +import com.vitorpamplona.quartz.nip32Labeling.tags.LabelTag + +/** + * NIP-32 self-reporting: `l` and `L` tags MAY be added to any event kind. + * For non-1985 events, labels refer to the event itself. + */ +fun Event.selfReportLabels() = tags.mapNotNull(LabelTag::parse) + +fun Event.selfReportNamespaces() = tags.mapNotNull(LabelNamespaceTag::parse) diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip32Labeling/LabelEvent.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip32Labeling/LabelEvent.kt new file mode 100644 index 000000000..9615773a0 --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip32Labeling/LabelEvent.kt @@ -0,0 +1,153 @@ +/* + * 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.nip32Labeling + +import androidx.compose.runtime.Immutable +import com.vitorpamplona.quartz.nip01Core.core.Event +import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.hints.AddressHintProvider +import com.vitorpamplona.quartz.nip01Core.hints.EventHintProvider +import com.vitorpamplona.quartz.nip01Core.hints.PubKeyHintProvider +import com.vitorpamplona.quartz.nip01Core.hints.types.AddressHint +import com.vitorpamplona.quartz.nip01Core.hints.types.EventIdHint +import com.vitorpamplona.quartz.nip01Core.hints.types.PubKeyHint +import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer +import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate +import com.vitorpamplona.quartz.nip01Core.tags.aTag.ATag +import com.vitorpamplona.quartz.nip01Core.tags.events.ETag +import com.vitorpamplona.quartz.nip01Core.tags.events.eTag +import com.vitorpamplona.quartz.nip01Core.tags.hashtags.HashtagTag +import com.vitorpamplona.quartz.nip01Core.tags.people.PTag +import com.vitorpamplona.quartz.nip01Core.tags.people.pTag +import com.vitorpamplona.quartz.nip31Alts.alt +import com.vitorpamplona.quartz.nip32Labeling.tags.LabelNamespaceTag +import com.vitorpamplona.quartz.nip32Labeling.tags.LabelTag +import com.vitorpamplona.quartz.utils.TimeUtils + +/** + * NIP-32: Label Event (kind 1985) + * + * Attaches labels to existing events, pubkeys, relays, or topics. + * Supports distributed moderation, collection management, license assignment, + * and content classification. + */ +@Immutable +class LabelEvent( + id: HexKey, + pubKey: HexKey, + createdAt: Long, + tags: Array>, + content: String, + sig: HexKey, +) : Event(id, pubKey, createdAt, KIND, tags, content, sig), + EventHintProvider, + PubKeyHintProvider, + AddressHintProvider { + override fun pubKeyHints(): List = tags.mapNotNull(PTag::parseAsHint) + + override fun linkedPubKeys(): List = tags.mapNotNull(PTag::parseKey) + + override fun eventHints(): List = tags.mapNotNull(ETag::parseAsHint) + + override fun linkedEventIds(): List = tags.mapNotNull(ETag::parseId) + + override fun addressHints(): List = tags.mapNotNull(ATag::parseAsHint) + + override fun linkedAddressIds(): List = tags.mapNotNull(ATag::parseAddressId) + + /** All label namespace (`L`) tags on this event. */ + fun namespaces() = tags.mapNotNull(LabelNamespaceTag::parse) + + /** All label (`l`) tags on this event. */ + fun labels() = tags.mapNotNull(LabelTag::parse) + + /** Labels filtered by a specific namespace. */ + fun labelsByNamespace(namespace: String) = labels().filter { it.namespace == namespace } + + /** Referenced event IDs (label targets). */ + fun labeledEvents() = tags.mapNotNull(ETag::parseId) + + /** Referenced pubkeys (label targets). */ + fun labeledPubKeys() = tags.mapNotNull(PTag::parseKey) + + /** Referenced addresses (label targets). */ + fun labeledAddresses() = tags.mapNotNull(ATag::parseAddressId) + + /** Referenced hashtags/topics (label targets via `t` tag). */ + fun labeledHashtags() = tags.mapNotNull(HashtagTag::parse) + + /** Referenced relay URLs (label targets via `r` tag). */ + fun labeledRelayUrls(): List = + tags + .filter { it.size >= 2 && it[0] == "r" && it[1].isNotEmpty() } + .map { it[1] } + + companion object { + const val KIND = 1985 + const val ALT = "Label event" + + /** + * Build a label event for labeling events. + */ + fun buildEventLabel( + labeledEventId: HexKey, + labeledEventRelay: String? = null, + labeledEventAuthor: HexKey? = null, + labels: List, + content: String = "", + createdAt: Long = TimeUtils.now(), + ) = eventTemplate(KIND, content, createdAt) { + alt(ALT) + eTag(ETag(labeledEventId, labeledEventRelay?.let { RelayUrlNormalizer.normalizeOrNull(it) }, labeledEventAuthor)) + labels.map { it.namespace }.distinct().forEach { labelNamespace(it) } + labels.forEach { label(it) } + } + + /** + * Build a label event for labeling pubkeys. + */ + fun buildPubKeyLabel( + labeledPubKey: HexKey, + labeledPubKeyRelay: String? = null, + labels: List, + content: String = "", + createdAt: Long = TimeUtils.now(), + ) = eventTemplate(KIND, content, createdAt) { + alt(ALT) + pTag(labeledPubKey, labeledPubKeyRelay?.let { RelayUrlNormalizer.normalizeOrNull(it) }) + labels.map { it.namespace }.distinct().forEach { labelNamespace(it) } + labels.forEach { label(it) } + } + + /** + * Build a label event with custom tag targets and labels. + */ + fun build( + labels: List, + content: String = "", + createdAt: Long = TimeUtils.now(), + ) = eventTemplate(KIND, content, createdAt) { + alt(ALT) + labels.map { it.namespace }.distinct().forEach { labelNamespace(it) } + labels.forEach { label(it) } + } + } +} diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip32Labeling/TagArrayBuilderExt.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip32Labeling/TagArrayBuilderExt.kt new file mode 100644 index 000000000..f8830b89f --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip32Labeling/TagArrayBuilderExt.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.nip32Labeling + +import com.vitorpamplona.quartz.nip01Core.core.Event +import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder +import com.vitorpamplona.quartz.nip32Labeling.tags.LabelNamespaceTag +import com.vitorpamplona.quartz.nip32Labeling.tags.LabelTag + +/** Adds a label (`l`) tag. */ +fun TagArrayBuilder.label(tag: LabelTag) = add(tag.toTagArray()) + +/** Adds a label (`l`) tag with explicit label and namespace values. */ +fun TagArrayBuilder.label( + label: String, + namespace: String, +) = add(LabelTag.assemble(label, namespace)) + +/** Adds a label namespace (`L`) tag. */ +fun TagArrayBuilder.labelNamespace(namespace: String) = add(LabelNamespaceTag.assemble(namespace)) + +/** Adds a label namespace (`L`) tag. */ +fun TagArrayBuilder.labelNamespace(tag: LabelNamespaceTag) = add(tag.toTagArray()) diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip32Labeling/tags/LabelNamespaceTag.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip32Labeling/tags/LabelNamespaceTag.kt new file mode 100644 index 000000000..fdeb864fc --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip32Labeling/tags/LabelNamespaceTag.kt @@ -0,0 +1,77 @@ +/* + * 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.nip32Labeling.tags + +import androidx.compose.runtime.Immutable +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + +/** + * NIP-32: `L` tag — label namespace. + * + * Format: `["L", ""]` + * + * Namespaces SHOULD be unambiguous (ISO standard or reverse domain name notation). + * The special `ugc` namespace MAY be used when label content is provided by an end user. + * `L` tags starting with `#` indicate that the label target should be associated + * with the label's value (attaching standard nostr tags to events, pubkeys, etc.). + */ +@Immutable +data class LabelNamespaceTag( + val namespace: String, +) { + fun toTagArray() = assemble(namespace) + + /** + * Returns true if this namespace is a tag-association namespace (starts with `#`). + * When `L` = `#t`, an `l` tag like `["l", "bitcoin", "#t"]` means the target + * should be associated with the hashtag `bitcoin`. + */ + fun isTagAssociation() = namespace.startsWith("#") + + companion object { + const val TAG_NAME = "L" + + fun isTagged(tag: Array) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty() + + fun isTagged( + tag: Array, + namespace: String, + ) = tag.has(1) && tag[0] == TAG_NAME && tag[1] == namespace + + fun parse(tag: Array): LabelNamespaceTag? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } + + return LabelNamespaceTag(tag[1]) + } + + fun parseNamespace(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(namespace: String) = arrayOf(TAG_NAME, namespace) + } +} diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip32Labeling/tags/LabelTag.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip32Labeling/tags/LabelTag.kt new file mode 100644 index 000000000..807480240 --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip32Labeling/tags/LabelTag.kt @@ -0,0 +1,78 @@ +/* + * 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.nip32Labeling.tags + +import androidx.compose.runtime.Immutable +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + +/** + * NIP-32: `l` tag — label value with an optional namespace mark. + * + * Format: `["l", "