- Adds support for creating and replying to NIP-22 geo scope posts
- Adds support for creating and replying to NIP-22 hashtag scope posts - Refactors NIP-73 and NIP-22 on quartz to better support scoped replies - Creates New post screens for both hashtags and geolocated posts.
This commit is contained in:
+95
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* 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.nip22Comments
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerSync
|
||||
import com.vitorpamplona.quartz.nip01Core.verify
|
||||
import com.vitorpamplona.quartz.nip73ExternalIds.location.GeohashId
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import junit.framework.TestCase.assertFalse
|
||||
import junit.framework.TestCase.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class GeolocatedComments {
|
||||
private val privateKey = "f410f88bcec6cbfda04d6a273c7b1dd8bba144cd45b71e87109cfa11dd7ed561".hexToByteArray()
|
||||
private val signer = NostrSignerSync(KeyPair(privateKey))
|
||||
|
||||
val event =
|
||||
CommentEvent(
|
||||
"fecb2ecf61a1433d417a784d10bd1e8ec19a916170a53ca8fb3a15fc666a6592",
|
||||
"f8ff11c7a7d3478355d3b4d174e5a473797a906ea4aa61aa9b6bc0652c1ea17a",
|
||||
1747753115,
|
||||
arrayOf(
|
||||
arrayOf("alt", "Reply to geo:drt3n"),
|
||||
arrayOf("I", "geo:drt3n"),
|
||||
arrayOf("I", "geo:drt3"),
|
||||
arrayOf("I", "geo:drt"),
|
||||
arrayOf("I", "geo:dr"),
|
||||
arrayOf("I", "geo:d"),
|
||||
arrayOf("K", "geo"),
|
||||
arrayOf("i", "geo:drt3n"),
|
||||
arrayOf("i", "geo:drt3"),
|
||||
arrayOf("i", "geo:drt"),
|
||||
arrayOf("i", "geo:dr"),
|
||||
arrayOf("i", "geo:d"),
|
||||
arrayOf("k", "geo"),
|
||||
),
|
||||
"testing",
|
||||
"12070e663272f1227c639fb834eb2122fc7bb995f4c49e55ebb1dfe2135ef7347d44810bacd2e64fd26b8826fd47d2800ce6c3d3b579bb3afe39088ffd4faa60",
|
||||
)
|
||||
|
||||
@Test
|
||||
fun verifyEvent() {
|
||||
assertTrue(event.verify())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testScopes() {
|
||||
assertEquals("drt3n", event.geohashedScope())
|
||||
assertTrue(event.isTaggedScope("drt3n", GeohashId::match))
|
||||
assertTrue(event.isTaggedScope(GeohashId.toScope("drt3n")))
|
||||
assertFalse(event.isTaggedScope("drt3n"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCreation() {
|
||||
val event =
|
||||
signer.sign(
|
||||
CommentEvent.replyExternalIdentity(
|
||||
"Message",
|
||||
GeohashId("drt3n"),
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals("drt3n", event!!.geohashedScope())
|
||||
assertTrue(event.isTaggedScope(GeohashId.toScope("drt3n")))
|
||||
assertFalse(event.isTaggedScope("drt3n"))
|
||||
|
||||
assertTrue(event.hasScopeKind(GeohashId.KIND))
|
||||
assertTrue(event.hasRootScopeKind(GeohashId.KIND))
|
||||
assertTrue(event.hasReplyScopeKind(GeohashId.KIND))
|
||||
}
|
||||
}
|
||||
@@ -22,38 +22,21 @@ package com.vitorpamplona.quartz.nip01Core.tags.geohash
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
|
||||
import com.vitorpamplona.quartz.nip73ExternalIds.location.geohashedScope
|
||||
|
||||
fun Event.hasGeohashes() =
|
||||
if (this is CommentEvent) {
|
||||
this.hasGeohashes()
|
||||
} else {
|
||||
tags.hasGeohashes()
|
||||
}
|
||||
fun Event.hasGeohashes() = tags.hasGeohashes()
|
||||
|
||||
fun Event.isTaggedGeoHashes(hashtags: Set<String>) =
|
||||
if (this is CommentEvent) {
|
||||
this.isTaggedGeoHashes(hashtags)
|
||||
} else {
|
||||
tags.isTaggedGeoHashes(hashtags)
|
||||
}
|
||||
fun Event.isTaggedGeoHashes(hashtags: Set<String>) = tags.isTaggedGeoHashes(hashtags)
|
||||
|
||||
fun Event.isTaggedGeoHash(hashtag: String) =
|
||||
if (this is CommentEvent) {
|
||||
this.isTaggedGeoHash(hashtag)
|
||||
} else {
|
||||
tags.isTaggedGeoHash(hashtag)
|
||||
}
|
||||
fun Event.isTaggedGeoHash(hashtag: String) = tags.isTaggedGeoHash(hashtag)
|
||||
|
||||
fun Event.geohashes() =
|
||||
if (this is CommentEvent) {
|
||||
geohashes()
|
||||
} else {
|
||||
tags.geohashes()
|
||||
}
|
||||
fun Event.geohashes() = tags.geohashes()
|
||||
|
||||
fun Event.getGeoHash(): String? =
|
||||
fun Event.getGeoHash(): String? = tags.getGeoHash()
|
||||
|
||||
fun Event.geoHashOrScope() =
|
||||
if (this is CommentEvent) {
|
||||
getGeoHash()
|
||||
geohashedScope()
|
||||
} else {
|
||||
tags.getGeoHash()
|
||||
}
|
||||
|
||||
@@ -21,9 +21,6 @@
|
||||
package com.vitorpamplona.quartz.nip01Core.tags.hashtags
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
|
||||
fun Event.forEachHashTag(onEach: (eventId: HexKey) -> Unit) = tags.forEachHashTag(onEach)
|
||||
|
||||
fun Event.anyHashTag(onEach: (str: String) -> Boolean) = tags.anyHashTag(onEach)
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip01Core.tags.kinds
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Tag
|
||||
import com.vitorpamplona.quartz.nip01Core.core.has
|
||||
import com.vitorpamplona.quartz.utils.ensure
|
||||
|
||||
@@ -27,7 +28,7 @@ class KindTag {
|
||||
companion object {
|
||||
const val TAG_NAME = "k"
|
||||
|
||||
fun match(tag: Array<String>) = tag.has(1) && tag[0] == TAG_NAME
|
||||
fun match(tag: Tag) = tag.has(1) && tag[0] == TAG_NAME
|
||||
|
||||
fun isTagged(
|
||||
tag: Array<String>,
|
||||
|
||||
@@ -90,19 +90,29 @@ class CommentEvent(
|
||||
|
||||
fun directKinds() = tags.filter(ReplyKindTag::match)
|
||||
|
||||
fun isGeohashTag(tag: Array<String>) = tag.size > 1 && (tag[0] == "i" || tag[0] == "I") && tag[1].startsWith("geo:")
|
||||
/** root and reply scope search */
|
||||
fun isTaggedScope(scopeId: String) = tags.any { RootIdentifierTag.isTagged(it, scopeId) || ReplyIdentifierTag.isTagged(it, scopeId) }
|
||||
|
||||
private fun getGeoHashList() = tags.filter { isGeohashTag(it) }
|
||||
fun isTaggedScopes(scopeIds: Set<String>) = tags.any { RootIdentifierTag.isTagged(it, scopeIds) || ReplyIdentifierTag.isTagged(it, scopeIds) }
|
||||
|
||||
fun hasGeohashes() = tags.any { isGeohashTag(it) }
|
||||
fun isTaggedScope(
|
||||
value: String,
|
||||
match: (String, String) -> Boolean,
|
||||
) = tags.any { RootIdentifierTag.isTagged(it, value, match) || ReplyIdentifierTag.isTagged(it, value, match) }
|
||||
|
||||
fun geohashes() = getGeoHashList().map { it[1].drop(4).lowercase() }
|
||||
fun firstTaggedScopeIn(scopeIds: Set<String>) = tags.firstNotNullOfOrNull { RootIdentifierTag.matchOrNull(it, scopeIds) ?: ReplyIdentifierTag.matchOrNull(it, scopeIds) }
|
||||
|
||||
fun getGeoHash(): String? = geohashes().maxByOrNull { it.length }
|
||||
fun isScoped(scopeTest: (String) -> Boolean) = tags.any { RootIdentifierTag.isTagged(it, scopeTest) || ReplyIdentifierTag.isTagged(it, scopeTest) }
|
||||
|
||||
fun isTaggedGeoHash(hashtag: String) = tags.any { isGeohashTag(it) && it[1].endsWith(hashtag, true) }
|
||||
fun hasRootScopeKind(kind: String) = tags.any { RootKindTag.isKind(it, kind) }
|
||||
|
||||
fun isTaggedGeoHashes(hashtags: Set<String>) = geohashes().any { it in hashtags }
|
||||
fun hasReplyScopeKind(kind: String) = tags.any { ReplyKindTag.isKind(it, kind) }
|
||||
|
||||
fun hasScopeKind(kind: String) = tags.any { RootKindTag.isKind(it, kind) || ReplyKindTag.isKind(it, kind) }
|
||||
|
||||
fun scopeValues(parser: (String) -> String?) = tags.mapNotNull { RootIdentifierTag.parse(it)?.let { parser(it) } }
|
||||
|
||||
fun firstScopeValue(parser: (String) -> String?) = tags.firstNotNullOfOrNull { RootIdentifierTag.parse(it)?.let { parser(it) } }
|
||||
|
||||
override fun markedReplyTos(): List<HexKey> =
|
||||
tags.mapNotNull(ReplyEventTag::parseKey) +
|
||||
|
||||
@@ -78,7 +78,7 @@ fun TagArrayBuilder<CommentEvent>.replyKind(kind: String) = addUnique(ReplyKindT
|
||||
|
||||
fun TagArrayBuilder<CommentEvent>.replyKind(kind: Int) = addUnique(ReplyKindTag.assemble(kind))
|
||||
|
||||
fun TagArrayBuilder<CommentEvent>.replyKind(id: ExternalId) = addUnique(RootKindTag.assemble(id))
|
||||
fun TagArrayBuilder<CommentEvent>.replyKind(id: ExternalId) = addUnique(ReplyKindTag.assemble(id))
|
||||
|
||||
fun TagArrayBuilder<CommentEvent>.replyAuthor(
|
||||
pubKey: HexKey,
|
||||
|
||||
+39
-2
@@ -25,7 +25,7 @@ import com.vitorpamplona.quartz.nip01Core.core.Tag
|
||||
import com.vitorpamplona.quartz.nip01Core.core.has
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.geohash.GeoHashTag
|
||||
import com.vitorpamplona.quartz.nip73ExternalIds.ExternalId
|
||||
import com.vitorpamplona.quartz.nip73ExternalIds.GeohashId
|
||||
import com.vitorpamplona.quartz.nip73ExternalIds.location.GeohashId
|
||||
import com.vitorpamplona.quartz.utils.arrayOfNotNull
|
||||
import com.vitorpamplona.quartz.utils.ensure
|
||||
|
||||
@@ -37,6 +37,43 @@ class ReplyIdentifierTag {
|
||||
@JvmStatic
|
||||
fun match(tag: Tag) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty()
|
||||
|
||||
@JvmStatic
|
||||
fun isTagged(
|
||||
tag: Array<String>,
|
||||
encodedScope: String,
|
||||
) = tag.has(1) && tag[0] == TAG_NAME && tag[1] == encodedScope
|
||||
|
||||
fun isTagged(
|
||||
tag: Array<String>,
|
||||
encodedScope: Set<String>,
|
||||
) = tag.has(1) && tag[0] == TAG_NAME && tag[1] in encodedScope
|
||||
|
||||
fun matchOrNull(
|
||||
tag: Array<String>,
|
||||
encodedScope: Set<String>,
|
||||
) = if (tag.has(1) && tag[0] == RootIdentifierTag.Companion.TAG_NAME && tag[1] in encodedScope) {
|
||||
tag[1]
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
fun isTagged(
|
||||
tag: Array<String>,
|
||||
test: (String) -> Boolean,
|
||||
) = tag.has(1) && tag[0] == TAG_NAME && test(tag[1])
|
||||
|
||||
fun isTagged(
|
||||
tag: Array<String>,
|
||||
value: String,
|
||||
match: (String, String) -> Boolean,
|
||||
) = tag.has(1) && tag[0] == TAG_NAME && match(tag[1], value)
|
||||
|
||||
fun isTagged(
|
||||
tag: Array<String>,
|
||||
value: Set<String>,
|
||||
match: (String, Set<String>) -> Boolean,
|
||||
) = tag.has(1) && tag[0] == TAG_NAME && match(tag[1], value)
|
||||
|
||||
@JvmStatic
|
||||
fun parse(tag: Tag): String? {
|
||||
ensure(tag.has(1)) { return null }
|
||||
@@ -54,7 +91,7 @@ class ReplyIdentifierTag {
|
||||
@JvmStatic
|
||||
fun assemble(id: ExternalId): List<Array<String>> =
|
||||
when (id) {
|
||||
is GeohashId -> GeoHashTag.geoMipMap(id.geohash).map { assemble(it, id.hint) }
|
||||
is GeohashId -> GeoHashTag.geoMipMap(id.geohash).map { assemble(GeohashId.toScope(it), id.hint) }
|
||||
else -> listOf(assemble(id.toScope(), id.hint()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,12 @@ class ReplyKindTag {
|
||||
@JvmStatic
|
||||
fun match(tag: Tag) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty()
|
||||
|
||||
@JvmStatic
|
||||
fun isKind(
|
||||
tag: Tag,
|
||||
kind: String,
|
||||
) = tag.has(1) && tag[0] == TAG_NAME && tag[1] == kind
|
||||
|
||||
@JvmStatic
|
||||
fun isTagged(
|
||||
tag: Tag,
|
||||
@@ -59,7 +65,7 @@ class ReplyKindTag {
|
||||
fun assemble(kind: Int) = arrayOf(TAG_NAME, kind.toString())
|
||||
|
||||
@JvmStatic
|
||||
fun assemble(id: ExternalId) = RootKindTag.assemble(id.toKind())
|
||||
fun assemble(id: ExternalId) = assemble(id.toKind())
|
||||
|
||||
@JvmStatic
|
||||
fun assemble(kinds: List<String>): List<Tag> = kinds.map { assemble(it) }
|
||||
|
||||
+38
-2
@@ -25,18 +25,54 @@ import com.vitorpamplona.quartz.nip01Core.core.Tag
|
||||
import com.vitorpamplona.quartz.nip01Core.core.has
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.geohash.GeoHashTag
|
||||
import com.vitorpamplona.quartz.nip73ExternalIds.ExternalId
|
||||
import com.vitorpamplona.quartz.nip73ExternalIds.GeohashId
|
||||
import com.vitorpamplona.quartz.nip73ExternalIds.location.GeohashId
|
||||
import com.vitorpamplona.quartz.utils.arrayOfNotNull
|
||||
import com.vitorpamplona.quartz.utils.ensure
|
||||
|
||||
@Immutable
|
||||
class RootIdentifierTag {
|
||||
class RootIdentifierTag<T : ExternalId> {
|
||||
companion object {
|
||||
const val TAG_NAME = "I"
|
||||
|
||||
@JvmStatic
|
||||
fun match(tag: Tag) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty()
|
||||
|
||||
fun isTagged(
|
||||
tag: Array<String>,
|
||||
encodedScope: String,
|
||||
) = tag.has(1) && tag[0] == TAG_NAME && tag[1] == encodedScope
|
||||
|
||||
fun isTagged(
|
||||
tag: Array<String>,
|
||||
encodedScope: Set<String>,
|
||||
) = tag.has(1) && tag[0] == TAG_NAME && tag[1] in encodedScope
|
||||
|
||||
fun matchOrNull(
|
||||
tag: Array<String>,
|
||||
encodedScope: Set<String>,
|
||||
) = if (tag.has(1) && tag[0] == TAG_NAME && tag[1] in encodedScope) {
|
||||
tag[1]
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
fun isTagged(
|
||||
tag: Array<String>,
|
||||
test: (String) -> Boolean,
|
||||
) = tag.has(1) && tag[0] == TAG_NAME && test(tag[1])
|
||||
|
||||
fun isTagged(
|
||||
tag: Array<String>,
|
||||
value: String,
|
||||
match: (String, String) -> Boolean,
|
||||
) = tag.has(1) && tag[0] == TAG_NAME && match(tag[1], value)
|
||||
|
||||
fun isTagged(
|
||||
tag: Array<String>,
|
||||
value: Set<String>,
|
||||
match: (String, Set<String>) -> Boolean,
|
||||
) = tag.has(1) && tag[0] == TAG_NAME && match(tag[1], value)
|
||||
|
||||
@JvmStatic
|
||||
fun parse(tag: Tag): String? {
|
||||
ensure(tag.has(1)) { return null }
|
||||
|
||||
@@ -32,6 +32,12 @@ class RootKindTag {
|
||||
@JvmStatic
|
||||
fun match(tag: Tag) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty()
|
||||
|
||||
@JvmStatic
|
||||
fun isKind(
|
||||
tag: Tag,
|
||||
kind: String,
|
||||
) = tag.has(1) && tag[0] == TAG_NAME && tag[1] == kind
|
||||
|
||||
@JvmStatic
|
||||
fun parse(tag: Tag): String? {
|
||||
ensure(tag.has(1)) { return null }
|
||||
|
||||
+3
-2
@@ -20,6 +20,7 @@
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip70ProtectedEvts.tags
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Tag
|
||||
import com.vitorpamplona.quartz.nip01Core.core.has
|
||||
import com.vitorpamplona.quartz.utils.ensure
|
||||
|
||||
@@ -28,14 +29,14 @@ class ProtectedTag {
|
||||
const val TAG_NAME = "-"
|
||||
|
||||
@JvmStatic
|
||||
fun match(tag: Array<String>): Boolean {
|
||||
fun match(tag: Tag): Boolean {
|
||||
ensure(tag.has(0)) { return false }
|
||||
ensure(tag[0] == TAG_NAME) { return false }
|
||||
return true
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun parse(tag: Array<String>): Boolean? {
|
||||
fun parse(tag: Tag): Boolean? {
|
||||
ensure(tag.has(0)) { return null }
|
||||
ensure(tag[0] == TAG_NAME) { return null }
|
||||
return true
|
||||
|
||||
+23
-4
@@ -18,7 +18,10 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip73ExternalIds
|
||||
package com.vitorpamplona.quartz.nip73ExternalIds.books
|
||||
|
||||
import com.vitorpamplona.quartz.nip73ExternalIds.ExternalId
|
||||
import com.vitorpamplona.quartz.utils.ensure
|
||||
|
||||
class BookId(
|
||||
val isbn: String,
|
||||
@@ -31,9 +34,25 @@ class BookId(
|
||||
override fun hint() = hint
|
||||
|
||||
companion object {
|
||||
// "isbn:9780765382030"
|
||||
fun toScope(isbn: String) = "isbn:" + isbn.lowercase().replace("-", "")
|
||||
const val KIND = "isbn"
|
||||
const val PREFIX_COLON = "isbn:"
|
||||
|
||||
fun toKind(isbn: String) = "isbn"
|
||||
// "isbn:9780765382030"
|
||||
fun toScope(isbn: String) = PREFIX_COLON + isbn.lowercase().replace("-", "")
|
||||
|
||||
fun toKind(isbn: String) = KIND
|
||||
|
||||
fun match(
|
||||
encoded: String,
|
||||
value: String,
|
||||
): Boolean {
|
||||
ensure(encoded.startsWith(PREFIX_COLON)) { return false }
|
||||
return encoded.indexOf(value, PREFIX_COLON.length) > 0
|
||||
}
|
||||
|
||||
fun parse(encoded: String): String? {
|
||||
ensure(encoded.startsWith(PREFIX_COLON)) { return null }
|
||||
return encoded.substring(PREFIX_COLON.length)
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-14
@@ -18,21 +18,12 @@
|
||||
* 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.nip73ExternalIds
|
||||
package com.vitorpamplona.quartz.nip73ExternalIds.books
|
||||
|
||||
class GeohashId(
|
||||
val geohash: String,
|
||||
val hint: String? = null,
|
||||
) : ExternalId {
|
||||
override fun toScope() = toScope(geohash)
|
||||
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
|
||||
|
||||
override fun toKind() = toKind(geohash)
|
||||
fun CommentEvent.isBookScoped() = hasScopeKind(BookId.KIND)
|
||||
|
||||
override fun hint() = hint
|
||||
fun CommentEvent.bookScope() = firstScopeValue(BookId::parse)
|
||||
|
||||
companion object {
|
||||
fun toScope(geohash: String) = "geo:" + geohash.lowercase()
|
||||
|
||||
fun toKind(geohash: String) = "geo"
|
||||
}
|
||||
}
|
||||
fun CommentEvent.bookScopes() = scopeValues(BookId::parse)
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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.nip73ExternalIds.location
|
||||
|
||||
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
|
||||
|
||||
fun CommentEvent.isGeohashedScoped() = hasScopeKind(GeohashId.KIND)
|
||||
|
||||
fun CommentEvent.geohashedScope() = scopeValues(GeohashId::parse).maxByOrNull { it.length }
|
||||
+25
-9
@@ -18,24 +18,40 @@
|
||||
* 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.nip73ExternalIds
|
||||
package com.vitorpamplona.quartz.nip73ExternalIds.location
|
||||
|
||||
import kotlin.math.min
|
||||
import com.vitorpamplona.quartz.nip73ExternalIds.ExternalId
|
||||
import com.vitorpamplona.quartz.utils.ensure
|
||||
|
||||
class MovieId(
|
||||
val isan: String,
|
||||
class GeohashId(
|
||||
val geohash: String,
|
||||
val hint: String? = null,
|
||||
) : ExternalId {
|
||||
override fun toScope() = toScope(isan)
|
||||
override fun toScope() = toScope(geohash)
|
||||
|
||||
override fun toKind() = toKind(isan)
|
||||
override fun toKind() = toKind(geohash)
|
||||
|
||||
override fun hint() = hint
|
||||
|
||||
companion object {
|
||||
// "isan:0000-0000-401A-0000-7"
|
||||
fun toScope(isan: String) = "isan:" + isan.lowercase().substring(0, min(21, isan.length))
|
||||
const val KIND = "geo"
|
||||
const val PREFIX_COLON = "geo:"
|
||||
|
||||
fun toKind(isan: String) = "isan"
|
||||
fun toScope(geohash: String) = PREFIX_COLON + geohash.lowercase()
|
||||
|
||||
fun toKind(geohash: String) = KIND
|
||||
|
||||
fun match(
|
||||
encoded: String,
|
||||
value: String,
|
||||
): Boolean {
|
||||
ensure(encoded.startsWith(PREFIX_COLON)) { return false }
|
||||
return encoded.indexOf(value, PREFIX_COLON.length) > 0
|
||||
}
|
||||
|
||||
fun parse(encoded: String): String? {
|
||||
ensure(encoded.startsWith(PREFIX_COLON)) { return null }
|
||||
return encoded.substring(PREFIX_COLON.length)
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-15
@@ -18,22 +18,12 @@
|
||||
* 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.nip73ExternalIds
|
||||
package com.vitorpamplona.quartz.nip73ExternalIds.movies
|
||||
|
||||
class PaperId(
|
||||
val doi: String,
|
||||
val hint: String? = null,
|
||||
) : ExternalId {
|
||||
override fun toScope() = toScope(doi)
|
||||
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
|
||||
|
||||
override fun toKind() = toKind(doi)
|
||||
fun CommentEvent.isMovieScoped() = hasScopeKind(MovieId.KIND)
|
||||
|
||||
override fun hint() = hint
|
||||
fun CommentEvent.movieScope() = firstScopeValue(MovieId::parse)
|
||||
|
||||
companion object {
|
||||
// "doi:10.1000/182"
|
||||
fun toScope(doi: String) = "doi:" + doi.lowercase()
|
||||
|
||||
fun toKind(doi: String) = "doi"
|
||||
}
|
||||
}
|
||||
fun CommentEvent.movieScopes() = scopeValues(MovieId::parse)
|
||||
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* 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.nip73ExternalIds.movies
|
||||
|
||||
import com.vitorpamplona.quartz.nip73ExternalIds.ExternalId
|
||||
import com.vitorpamplona.quartz.utils.ensure
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
class MovieId(
|
||||
val isan: String,
|
||||
val hint: String? = null,
|
||||
) : ExternalId {
|
||||
override fun toScope() = toScope(isan)
|
||||
|
||||
override fun toKind() = toKind(isan)
|
||||
|
||||
override fun hint() = hint
|
||||
|
||||
companion object {
|
||||
const val KIND = "isan"
|
||||
const val PREFIX_COLON = "isan:"
|
||||
|
||||
// "isan:0000-0000-401A-0000-7"
|
||||
fun toScope(isan: String) =
|
||||
PREFIX_COLON +
|
||||
isan.lowercase().substring(
|
||||
0,
|
||||
min(21, isan.length),
|
||||
)
|
||||
|
||||
fun toKind(isan: String) = KIND
|
||||
|
||||
fun match(
|
||||
encoded: String,
|
||||
value: String,
|
||||
): Boolean {
|
||||
ensure(encoded.startsWith(PREFIX_COLON)) { return false }
|
||||
return encoded.indexOf(value, PREFIX_COLON.length) > 0
|
||||
}
|
||||
|
||||
fun parse(encoded: String): String? {
|
||||
ensure(encoded.startsWith(PREFIX_COLON)) { return null }
|
||||
return encoded.substring(PREFIX_COLON.length)
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-15
@@ -18,22 +18,12 @@
|
||||
* 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.nip73ExternalIds
|
||||
package com.vitorpamplona.quartz.nip73ExternalIds.papers
|
||||
|
||||
class PodcastFeedId(
|
||||
val guid: String,
|
||||
val hint: String? = null,
|
||||
) : ExternalId {
|
||||
override fun toScope() = toScope(guid)
|
||||
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
|
||||
|
||||
override fun toKind() = toKind(guid)
|
||||
fun CommentEvent.isPaperScoped() = hasScopeKind(PaperId.KIND)
|
||||
|
||||
override fun hint() = hint
|
||||
fun CommentEvent.paperScope() = firstScopeValue(PaperId::parse)
|
||||
|
||||
companion object {
|
||||
// "isbn:9780765382030"
|
||||
fun toScope(guid: String) = "podcast:guid:" + guid
|
||||
|
||||
fun toKind(guid: String) = "podcast:guid"
|
||||
}
|
||||
}
|
||||
fun CommentEvent.paperScopes() = scopeValues(PaperId::parse)
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip73ExternalIds.papers
|
||||
|
||||
import com.vitorpamplona.quartz.nip73ExternalIds.ExternalId
|
||||
import com.vitorpamplona.quartz.utils.ensure
|
||||
|
||||
class PaperId(
|
||||
val doi: String,
|
||||
val hint: String? = null,
|
||||
) : ExternalId {
|
||||
override fun toScope() = toScope(doi)
|
||||
|
||||
override fun toKind() = toKind(doi)
|
||||
|
||||
override fun hint() = hint
|
||||
|
||||
companion object {
|
||||
const val KIND = "doi"
|
||||
const val PREFIX_COLON = "doi:"
|
||||
|
||||
// "doi:10.1000/182"
|
||||
fun toScope(doi: String) = PREFIX_COLON + doi.lowercase()
|
||||
|
||||
fun toKind(doi: String) = KIND
|
||||
|
||||
fun match(
|
||||
encoded: String,
|
||||
value: String,
|
||||
): Boolean {
|
||||
ensure(encoded.startsWith(PREFIX_COLON)) { return false }
|
||||
return encoded.indexOf(value, PREFIX_COLON.length) > 0
|
||||
}
|
||||
|
||||
fun parse(encoded: String): String? {
|
||||
ensure(encoded.startsWith(PREFIX_COLON)) { return null }
|
||||
return encoded.substring(PREFIX_COLON.length)
|
||||
}
|
||||
}
|
||||
}
|
||||
+41
@@ -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.nip73ExternalIds.podcasts
|
||||
|
||||
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
|
||||
|
||||
fun CommentEvent.isPodcastEpisodeScoped() = hasScopeKind(PodcastEpisodeId.KIND)
|
||||
|
||||
fun CommentEvent.podcastEpisodeScope() = firstScopeValue(PodcastEpisodeId::parse)
|
||||
|
||||
fun CommentEvent.podcastEpisodeScopes() = scopeValues(PodcastEpisodeId::parse)
|
||||
|
||||
fun CommentEvent.isPodcastFeedScoped() = hasScopeKind(PodcastFeedId.KIND)
|
||||
|
||||
fun CommentEvent.podcastFeedScope() = firstScopeValue(PodcastFeedId::parse)
|
||||
|
||||
fun CommentEvent.podcastFeedScopes() = scopeValues(PodcastFeedId::parse)
|
||||
|
||||
fun CommentEvent.isPodcastPublisherScoped() = hasScopeKind(PodcastPublisherId.KIND)
|
||||
|
||||
fun CommentEvent.podcastPublisherScope() = firstScopeValue(PodcastPublisherId::parse)
|
||||
|
||||
fun CommentEvent.podcastPublisherScopes() = scopeValues(PodcastPublisherId::parse)
|
||||
+22
-4
@@ -18,7 +18,10 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip73ExternalIds
|
||||
package com.vitorpamplona.quartz.nip73ExternalIds.podcasts
|
||||
|
||||
import com.vitorpamplona.quartz.nip73ExternalIds.ExternalId
|
||||
import com.vitorpamplona.quartz.utils.ensure
|
||||
|
||||
class PodcastEpisodeId(
|
||||
val guid: String,
|
||||
@@ -31,9 +34,24 @@ class PodcastEpisodeId(
|
||||
override fun hint() = hint
|
||||
|
||||
companion object {
|
||||
// "isbn:9780765382030"
|
||||
fun toScope(guid: String) = "podcast:item:guid:" + guid
|
||||
const val KIND = "podcast:item:guid:guid"
|
||||
const val PREFIX_COLON = "podcast:item:guid:"
|
||||
|
||||
fun toKind(guid: String) = "podcast:item:guid"
|
||||
fun toScope(guid: String) = PREFIX_COLON + guid
|
||||
|
||||
fun toKind(guid: String) = KIND
|
||||
|
||||
fun match(
|
||||
encoded: String,
|
||||
value: String,
|
||||
): Boolean {
|
||||
ensure(encoded.startsWith(PREFIX_COLON)) { return false }
|
||||
return encoded.indexOf(value, PREFIX_COLON.length) > 0
|
||||
}
|
||||
|
||||
fun parse(encoded: String): String? {
|
||||
ensure(encoded.startsWith(PREFIX_COLON)) { return null }
|
||||
return encoded.substring(PREFIX_COLON.length)
|
||||
}
|
||||
}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip73ExternalIds.podcasts
|
||||
|
||||
import com.vitorpamplona.quartz.nip73ExternalIds.ExternalId
|
||||
import com.vitorpamplona.quartz.utils.ensure
|
||||
|
||||
class PodcastFeedId(
|
||||
val guid: String,
|
||||
val hint: String? = null,
|
||||
) : ExternalId {
|
||||
override fun toScope() = toScope(guid)
|
||||
|
||||
override fun toKind() = toKind(guid)
|
||||
|
||||
override fun hint() = hint
|
||||
|
||||
companion object {
|
||||
const val KIND = "podcast:guid"
|
||||
const val PREFIX_COLON = "podcast:guid:"
|
||||
|
||||
// "isbn:9780765382030"
|
||||
fun toScope(guid: String) = PREFIX_COLON + guid
|
||||
|
||||
fun toKind(guid: String) = KIND
|
||||
|
||||
fun match(
|
||||
encoded: String,
|
||||
value: String,
|
||||
): Boolean {
|
||||
ensure(encoded.startsWith(PREFIX_COLON)) { return false }
|
||||
return encoded.indexOf(value, PREFIX_COLON.length) > 0
|
||||
}
|
||||
|
||||
fun parse(encoded: String): String? {
|
||||
ensure(encoded.startsWith(PREFIX_COLON)) { return null }
|
||||
return encoded.substring(PREFIX_COLON.length)
|
||||
}
|
||||
}
|
||||
}
|
||||
+23
-4
@@ -18,7 +18,10 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip73ExternalIds
|
||||
package com.vitorpamplona.quartz.nip73ExternalIds.podcasts
|
||||
|
||||
import com.vitorpamplona.quartz.nip73ExternalIds.ExternalId
|
||||
import com.vitorpamplona.quartz.utils.ensure
|
||||
|
||||
class PodcastPublisherId(
|
||||
val guid: String,
|
||||
@@ -31,9 +34,25 @@ class PodcastPublisherId(
|
||||
override fun hint() = hint
|
||||
|
||||
companion object {
|
||||
// "isbn:9780765382030"
|
||||
fun toScope(guid: String) = "podcast:publisher:guid:" + guid
|
||||
const val KIND = "podcast:publisher:guid"
|
||||
const val PREFIX_COLON = "podcast:publisher:guid:"
|
||||
|
||||
fun toKind(guid: String) = "podcast:publisher:guid"
|
||||
// "isbn:9780765382030"
|
||||
fun toScope(guid: String) = PREFIX_COLON + guid
|
||||
|
||||
fun toKind(guid: String) = KIND
|
||||
|
||||
fun match(
|
||||
encoded: String,
|
||||
value: String,
|
||||
): Boolean {
|
||||
ensure(encoded.startsWith(PREFIX_COLON)) { return false }
|
||||
return encoded.indexOf(value, PREFIX_COLON.length) > 0
|
||||
}
|
||||
|
||||
fun parse(encoded: String): String? {
|
||||
ensure(encoded.startsWith(PREFIX_COLON)) { return null }
|
||||
return encoded.substring(PREFIX_COLON.length)
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-14
@@ -18,21 +18,12 @@
|
||||
* 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.nip73ExternalIds
|
||||
package com.vitorpamplona.quartz.nip73ExternalIds.topics
|
||||
|
||||
class HashtagId(
|
||||
val topic: String,
|
||||
val hint: String? = null,
|
||||
) : ExternalId {
|
||||
override fun toScope() = toScope(topic)
|
||||
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
|
||||
|
||||
override fun toKind() = toKind(topic)
|
||||
fun CommentEvent.isHashtagScoped() = hasScopeKind(HashtagId.KIND)
|
||||
|
||||
override fun hint() = hint
|
||||
fun CommentEvent.hashtagScope() = firstScopeValue(HashtagId::parse)
|
||||
|
||||
companion object {
|
||||
fun toScope(topic: String) = "#" + topic.lowercase()
|
||||
|
||||
fun toKind(topic: String) = "#"
|
||||
}
|
||||
}
|
||||
fun CommentEvent.hashtagScopes() = scopeValues(HashtagId::parse)
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* 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.nip73ExternalIds.topics
|
||||
|
||||
import com.vitorpamplona.quartz.nip73ExternalIds.ExternalId
|
||||
import com.vitorpamplona.quartz.utils.ensure
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
class HashtagId(
|
||||
val topic: String,
|
||||
val hint: String? = null,
|
||||
) : ExternalId {
|
||||
override fun toScope() = toScope(topic)
|
||||
|
||||
override fun toKind() = toKind(topic)
|
||||
|
||||
override fun hint() = hint
|
||||
|
||||
companion object {
|
||||
const val KIND = "#"
|
||||
const val KIND_CHR = '#'
|
||||
|
||||
fun toScope(topic: String) = KIND + topic.lowercase()
|
||||
|
||||
fun toKind(topic: String) = KIND
|
||||
|
||||
fun match(
|
||||
encoded: String,
|
||||
value: String,
|
||||
): Boolean {
|
||||
ensure(encoded.length > 1) { return false }
|
||||
ensure(encoded[0] == KIND_CHR) { return false }
|
||||
return encoded.indexOf(value, 1) > 0
|
||||
}
|
||||
|
||||
fun parse(encoded: String): String? {
|
||||
return if (encoded.length > 1 && encoded[0] == KIND_CHR) {
|
||||
encoded.substring(1)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 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.nip73ExternalIds.urls
|
||||
|
||||
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
|
||||
|
||||
fun CommentEvent.isUrlScoped() = hasScopeKind(UrlId.KIND)
|
||||
|
||||
fun CommentEvent.urlScope() = firstScopeValue(UrlId::parse)
|
||||
|
||||
fun CommentEvent.urlScopes() = scopeValues(UrlId::parse)
|
||||
+21
-2
@@ -18,8 +18,10 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip73ExternalIds
|
||||
package com.vitorpamplona.quartz.nip73ExternalIds.urls
|
||||
|
||||
import com.vitorpamplona.quartz.nip73ExternalIds.ExternalId
|
||||
import com.vitorpamplona.quartz.utils.ensure
|
||||
import com.vitorpamplona.quartz.utils.toStringNoFragment
|
||||
import org.czeal.rfc3986.URIReference
|
||||
|
||||
@@ -34,8 +36,25 @@ class UrlId(
|
||||
override fun hint() = hint
|
||||
|
||||
companion object {
|
||||
const val KIND = "web"
|
||||
const val PREFIX1 = "https://"
|
||||
const val PREFIX2 = "http://"
|
||||
|
||||
fun toScope(url: String) = URIReference.parse(url).normalize().toStringNoFragment()
|
||||
|
||||
fun toKind(url: String) = "web"
|
||||
fun toKind(url: String) = KIND
|
||||
|
||||
fun match(
|
||||
encoded: String,
|
||||
value: String,
|
||||
): Boolean {
|
||||
ensure(encoded.startsWith(PREFIX1) || encoded.startsWith(PREFIX2)) { return false }
|
||||
return encoded == value
|
||||
}
|
||||
|
||||
fun parse(encoded: String): String? {
|
||||
ensure(encoded.startsWith(PREFIX1) || encoded.startsWith(PREFIX2)) { return null }
|
||||
return encoded
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user