Merge branch 'main' of https://github.com/vitorpamplona/amethyst
* 'main' of https://github.com/vitorpamplona/amethyst: feat: add NIP-66 relay monitor and discovery support to quartz
This commit is contained in:
+4
@@ -229,6 +229,8 @@ import com.vitorpamplona.quartz.nip64Chess.LiveChessGameChallengeEvent
|
||||
import com.vitorpamplona.quartz.nip64Chess.LiveChessGameEndEvent
|
||||
import com.vitorpamplona.quartz.nip64Chess.LiveChessMoveEvent
|
||||
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip66RelayMonitor.discovery.RelayDiscoveryEvent
|
||||
import com.vitorpamplona.quartz.nip66RelayMonitor.monitor.RelayMonitorEvent
|
||||
import com.vitorpamplona.quartz.nip68Picture.PictureEvent
|
||||
import com.vitorpamplona.quartz.nip71Video.VideoHorizontalEvent
|
||||
import com.vitorpamplona.quartz.nip71Video.VideoNormalEvent
|
||||
@@ -582,6 +584,8 @@ private fun kindDisplayName(kind: Int): Int =
|
||||
ReactionEvent.KIND -> R.string.kind_reactions
|
||||
ContactCardEvent.KIND -> R.string.kind_contact_card
|
||||
RelayAuthEvent.KIND -> R.string.kind_relay_auth
|
||||
RelayDiscoveryEvent.KIND -> R.string.kind_relay_discovery
|
||||
RelayMonitorEvent.KIND -> R.string.kind_relay_monitor
|
||||
RelaySetEvent.KIND -> R.string.kind_relay_set
|
||||
ReportEvent.KIND -> R.string.kind_reports
|
||||
RepostEvent.KIND -> R.string.kind_reposts
|
||||
|
||||
@@ -1663,6 +1663,8 @@
|
||||
<string name="kind_reactions">Reactions</string>
|
||||
<string name="kind_contact_card">Contact Card</string>
|
||||
<string name="kind_relay_auth">Relay Auth</string>
|
||||
<string name="kind_relay_discovery">Relay Discovery</string>
|
||||
<string name="kind_relay_monitor">Relay Monitor Announcement</string>
|
||||
<string name="kind_relay_set">Relay Set</string>
|
||||
<string name="kind_reports">Reports</string>
|
||||
<string name="kind_reposts">Reposts</string>
|
||||
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* 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.nip66RelayMonitor.discovery
|
||||
|
||||
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.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.dTag.dTag
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.geohash.geohashes
|
||||
import com.vitorpamplona.quartz.nip31Alts.alt
|
||||
import com.vitorpamplona.quartz.nip66RelayMonitor.discovery.tags.RttType
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
/**
|
||||
* NIP-66 Kind 30166: Relay Discovery Event
|
||||
*
|
||||
* Published by relay monitors to document the state of a specific relay.
|
||||
* The `d` tag holds the normalized relay URL, making each relay URL
|
||||
* addressable per monitor pubkey.
|
||||
*
|
||||
* Carries metrics and metadata observed during monitoring:
|
||||
* - Round-trip times (rtt-open, rtt-read, rtt-write)
|
||||
* - Network type (n), relay type (T)
|
||||
* - Supported NIPs (N), accepted kinds (k)
|
||||
* - Requirements (R: auth, payment, pow, writes)
|
||||
* - Topics (t) and geohash (g)
|
||||
*/
|
||||
@Immutable
|
||||
class RelayDiscoveryEvent(
|
||||
id: HexKey,
|
||||
pubKey: HexKey,
|
||||
createdAt: Long,
|
||||
tags: Array<Array<String>>,
|
||||
content: String,
|
||||
sig: HexKey,
|
||||
) : BaseAddressableEvent(id, pubKey, createdAt, KIND, tags, content, sig) {
|
||||
fun relay(): NormalizedRelayUrl? = RelayUrlNormalizer.normalizeOrNull(dTag())
|
||||
|
||||
fun rtt() = tags.rtts()
|
||||
|
||||
fun rttOpen() = tags.rtt(RttType.OPEN)
|
||||
|
||||
fun rttRead() = tags.rtt(RttType.READ)
|
||||
|
||||
fun rttWrite() = tags.rtt(RttType.WRITE)
|
||||
|
||||
fun networkTypes() = tags.networkTypes()
|
||||
|
||||
fun relayTypes() = tags.relayTypes()
|
||||
|
||||
fun supportedNips() = tags.supportedNips()
|
||||
|
||||
fun requirements() = tags.requirements()
|
||||
|
||||
fun acceptedKinds() = tags.acceptedKinds()
|
||||
|
||||
fun topics() = tags.topics()
|
||||
|
||||
fun geohashes() = tags.geohashes()
|
||||
|
||||
companion object {
|
||||
const val KIND = 30166
|
||||
const val ALT_DESCRIPTION = "Relay discovery"
|
||||
|
||||
fun build(
|
||||
relayUrl: NormalizedRelayUrl,
|
||||
content: String = "",
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
initializer: TagArrayBuilder<RelayDiscoveryEvent>.() -> Unit = {},
|
||||
) = eventTemplate<RelayDiscoveryEvent>(KIND, content, createdAt) {
|
||||
alt(ALT_DESCRIPTION)
|
||||
dTag(relayUrl.url)
|
||||
initializer()
|
||||
}
|
||||
|
||||
fun build(
|
||||
relayUrl: String,
|
||||
content: String = "",
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
initializer: TagArrayBuilder<RelayDiscoveryEvent>.() -> Unit = {},
|
||||
) = build(
|
||||
relayUrl = RelayUrlNormalizer.normalize(relayUrl),
|
||||
content = content,
|
||||
createdAt = createdAt,
|
||||
initializer = initializer,
|
||||
)
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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.nip66RelayMonitor.discovery
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
|
||||
import com.vitorpamplona.quartz.nip66RelayMonitor.discovery.tags.AcceptedKindTag
|
||||
import com.vitorpamplona.quartz.nip66RelayMonitor.discovery.tags.NetworkType
|
||||
import com.vitorpamplona.quartz.nip66RelayMonitor.discovery.tags.NetworkTypeTag
|
||||
import com.vitorpamplona.quartz.nip66RelayMonitor.discovery.tags.RelayTypeTag
|
||||
import com.vitorpamplona.quartz.nip66RelayMonitor.discovery.tags.RequirementTag
|
||||
import com.vitorpamplona.quartz.nip66RelayMonitor.discovery.tags.RttTag
|
||||
import com.vitorpamplona.quartz.nip66RelayMonitor.discovery.tags.RttType
|
||||
import com.vitorpamplona.quartz.nip66RelayMonitor.discovery.tags.SupportedNipTag
|
||||
|
||||
fun TagArrayBuilder<RelayDiscoveryEvent>.rtt(
|
||||
type: RttType,
|
||||
milliseconds: Long,
|
||||
) = addUnique(RttTag.assemble(type, milliseconds))
|
||||
|
||||
fun TagArrayBuilder<RelayDiscoveryEvent>.networkType(network: NetworkType) = addUnique(NetworkTypeTag.assemble(network))
|
||||
|
||||
fun TagArrayBuilder<RelayDiscoveryEvent>.networkTypes(networks: List<NetworkType>) = addAll(NetworkTypeTag.assemble(networks))
|
||||
|
||||
fun TagArrayBuilder<RelayDiscoveryEvent>.relayType(type: String) = addUnique(RelayTypeTag.assemble(type))
|
||||
|
||||
fun TagArrayBuilder<RelayDiscoveryEvent>.relayTypes(types: List<String>) = addAll(RelayTypeTag.assemble(types))
|
||||
|
||||
fun TagArrayBuilder<RelayDiscoveryEvent>.supportedNip(nip: Int) = add(SupportedNipTag.assemble(nip))
|
||||
|
||||
fun TagArrayBuilder<RelayDiscoveryEvent>.supportedNips(nips: List<Int>) = addAll(SupportedNipTag.assemble(nips))
|
||||
|
||||
fun TagArrayBuilder<RelayDiscoveryEvent>.requirement(
|
||||
value: String,
|
||||
negated: Boolean = false,
|
||||
) = add(RequirementTag.assemble(value, negated))
|
||||
|
||||
fun TagArrayBuilder<RelayDiscoveryEvent>.requirements(reqs: List<RequirementTag>) = addAll(RequirementTag.assemble(reqs))
|
||||
|
||||
fun TagArrayBuilder<RelayDiscoveryEvent>.acceptedKind(
|
||||
kind: Int,
|
||||
negated: Boolean = false,
|
||||
) = add(AcceptedKindTag.assemble(kind, negated))
|
||||
|
||||
fun TagArrayBuilder<RelayDiscoveryEvent>.acceptedKinds(kinds: List<AcceptedKindTag>) = addAll(AcceptedKindTag.assemble(kinds))
|
||||
+47
@@ -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.nip66RelayMonitor.discovery
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.TagArray
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hashtags
|
||||
import com.vitorpamplona.quartz.nip66RelayMonitor.discovery.tags.AcceptedKindTag
|
||||
import com.vitorpamplona.quartz.nip66RelayMonitor.discovery.tags.NetworkTypeTag
|
||||
import com.vitorpamplona.quartz.nip66RelayMonitor.discovery.tags.RelayTypeTag
|
||||
import com.vitorpamplona.quartz.nip66RelayMonitor.discovery.tags.RequirementTag
|
||||
import com.vitorpamplona.quartz.nip66RelayMonitor.discovery.tags.RttTag
|
||||
import com.vitorpamplona.quartz.nip66RelayMonitor.discovery.tags.RttType
|
||||
import com.vitorpamplona.quartz.nip66RelayMonitor.discovery.tags.SupportedNipTag
|
||||
|
||||
fun TagArray.rtts() = mapNotNull(RttTag::parse)
|
||||
|
||||
fun TagArray.rtt(type: RttType) = mapNotNull(RttTag::parse).firstOrNull { it.type == type }?.milliseconds
|
||||
|
||||
fun TagArray.networkTypes() = mapNotNull(NetworkTypeTag::parse)
|
||||
|
||||
fun TagArray.relayTypes() = mapNotNull(RelayTypeTag::parse)
|
||||
|
||||
fun TagArray.supportedNips() = mapNotNull(SupportedNipTag::parse)
|
||||
|
||||
fun TagArray.requirements() = mapNotNull(RequirementTag::parse)
|
||||
|
||||
fun TagArray.acceptedKinds() = mapNotNull(AcceptedKindTag::parse)
|
||||
|
||||
fun TagArray.topics() = hashtags()
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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.nip66RelayMonitor.discovery.tags
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.vitorpamplona.quartz.nip01Core.core.has
|
||||
import com.vitorpamplona.quartz.utils.ensure
|
||||
|
||||
/**
|
||||
* ["k", "<kind>"] - an event kind accepted by this relay.
|
||||
*
|
||||
* A leading "!" negates, meaning the relay does NOT accept that kind.
|
||||
* Examples: "1" (accepts kind 1), "!1" (rejects kind 1)
|
||||
*/
|
||||
@Stable
|
||||
class AcceptedKindTag(
|
||||
val kind: Int,
|
||||
val negated: Boolean,
|
||||
) {
|
||||
companion object {
|
||||
const val TAG_NAME = "k"
|
||||
private const val NEGATION_PREFIX = "!"
|
||||
|
||||
fun isTag(tag: Array<String>) = tag.has(1) && tag[0] == TAG_NAME
|
||||
|
||||
fun parse(tag: Array<String>): AcceptedKindTag? {
|
||||
ensure(tag.has(1)) { return null }
|
||||
ensure(tag[0] == TAG_NAME) { return null }
|
||||
ensure(tag[1].isNotEmpty()) { return null }
|
||||
val raw = tag[1]
|
||||
return if (raw.startsWith(NEGATION_PREFIX)) {
|
||||
val kind = raw.removePrefix(NEGATION_PREFIX).toIntOrNull() ?: return null
|
||||
AcceptedKindTag(kind = kind, negated = true)
|
||||
} else {
|
||||
val kind = raw.toIntOrNull() ?: return null
|
||||
AcceptedKindTag(kind = kind, negated = false)
|
||||
}
|
||||
}
|
||||
|
||||
fun assemble(
|
||||
kind: Int,
|
||||
negated: Boolean = false,
|
||||
) = arrayOf(TAG_NAME, if (negated) "$NEGATION_PREFIX$kind" else kind.toString())
|
||||
|
||||
fun assemble(entry: AcceptedKindTag) = assemble(entry.kind, entry.negated)
|
||||
|
||||
fun assemble(kinds: List<AcceptedKindTag>) = kinds.map { assemble(it) }
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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.nip66RelayMonitor.discovery.tags
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.has
|
||||
import com.vitorpamplona.quartz.utils.ensure
|
||||
|
||||
/** Network accessibility types for relay connections */
|
||||
enum class NetworkType(
|
||||
val code: String,
|
||||
) {
|
||||
CLEARNET("clearnet"),
|
||||
TOR("tor"),
|
||||
I2P("i2p"),
|
||||
LOKI("loki"),
|
||||
}
|
||||
|
||||
/** ["n", "<network-type>"] - the network type the relay is accessible on */
|
||||
class NetworkTypeTag {
|
||||
companion object {
|
||||
const val TAG_NAME = "n"
|
||||
|
||||
fun isTag(tag: Array<String>) = tag.has(1) && tag[0] == TAG_NAME
|
||||
|
||||
fun parse(tag: Array<String>): NetworkType? {
|
||||
ensure(tag.has(1)) { return null }
|
||||
ensure(tag[0] == TAG_NAME) { return null }
|
||||
ensure(tag[1].isNotEmpty()) { return null }
|
||||
return NetworkType.entries.find { it.code == tag[1] }
|
||||
}
|
||||
|
||||
fun assemble(network: NetworkType) = arrayOf(TAG_NAME, network.code)
|
||||
|
||||
fun assemble(networks: List<NetworkType>) = networks.map { assemble(it) }
|
||||
}
|
||||
}
|
||||
+47
@@ -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.nip66RelayMonitor.discovery.tags
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.has
|
||||
import com.vitorpamplona.quartz.utils.ensure
|
||||
|
||||
/**
|
||||
* ["T", "<relay-type>"] - the type of relay in PascalCase (e.g. PrivateInbox, PublicOutbox).
|
||||
* Values are arbitrary strings defined by relay operators; not an enum.
|
||||
*/
|
||||
class RelayTypeTag {
|
||||
companion object {
|
||||
const val TAG_NAME = "T"
|
||||
|
||||
fun isTag(tag: Array<String>) = tag.has(1) && tag[0] == TAG_NAME
|
||||
|
||||
fun parse(tag: Array<String>): 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(relayType: String) = arrayOf(TAG_NAME, relayType)
|
||||
|
||||
fun assemble(relayTypes: List<String>) = relayTypes.map { assemble(it) }
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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.nip66RelayMonitor.discovery.tags
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.vitorpamplona.quartz.nip01Core.core.has
|
||||
import com.vitorpamplona.quartz.utils.ensure
|
||||
|
||||
/**
|
||||
* ["R", "<requirement>"] - access requirement enforced by the relay.
|
||||
*
|
||||
* A leading "!" negates the requirement, meaning the relay does NOT enforce it.
|
||||
* Examples: "auth", "payment", "!payment" (no payment required), "pow", "writes"
|
||||
*/
|
||||
@Stable
|
||||
class RequirementTag(
|
||||
val value: String,
|
||||
val negated: Boolean,
|
||||
) {
|
||||
companion object {
|
||||
const val TAG_NAME = "R"
|
||||
private const val NEGATION_PREFIX = "!"
|
||||
|
||||
fun isTag(tag: Array<String>) = tag.has(1) && tag[0] == TAG_NAME
|
||||
|
||||
fun parse(tag: Array<String>): RequirementTag? {
|
||||
ensure(tag.has(1)) { return null }
|
||||
ensure(tag[0] == TAG_NAME) { return null }
|
||||
ensure(tag[1].isNotEmpty()) { return null }
|
||||
val raw = tag[1]
|
||||
return if (raw.startsWith(NEGATION_PREFIX)) {
|
||||
RequirementTag(value = raw.removePrefix(NEGATION_PREFIX), negated = true)
|
||||
} else {
|
||||
RequirementTag(value = raw, negated = false)
|
||||
}
|
||||
}
|
||||
|
||||
fun assemble(
|
||||
value: String,
|
||||
negated: Boolean = false,
|
||||
) = arrayOf(TAG_NAME, if (negated) "$NEGATION_PREFIX$value" else value)
|
||||
|
||||
fun assemble(req: RequirementTag) = assemble(req.value, req.negated)
|
||||
|
||||
fun assemble(requirements: List<RequirementTag>) = requirements.map { assemble(it) }
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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.nip66RelayMonitor.discovery.tags
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.vitorpamplona.quartz.nip01Core.core.has
|
||||
import com.vitorpamplona.quartz.utils.ensure
|
||||
|
||||
/** Round-trip time measurement types */
|
||||
enum class RttType(
|
||||
val tagName: String,
|
||||
) {
|
||||
OPEN("rtt-open"),
|
||||
READ("rtt-read"),
|
||||
WRITE("rtt-write"),
|
||||
}
|
||||
|
||||
/**
|
||||
* Round-trip time tags measured in milliseconds.
|
||||
*
|
||||
* ["rtt-open", "<milliseconds>"]
|
||||
* ["rtt-read", "<milliseconds>"]
|
||||
* ["rtt-write", "<milliseconds>"]
|
||||
*/
|
||||
@Stable
|
||||
class RttTag(
|
||||
val type: RttType,
|
||||
val milliseconds: Long,
|
||||
) {
|
||||
companion object {
|
||||
fun isTag(tag: Array<String>) = tag.has(1) && RttType.entries.any { it.tagName == tag[0] }
|
||||
|
||||
fun parse(tag: Array<String>): RttTag? {
|
||||
ensure(tag.has(1)) { return null }
|
||||
val type = RttType.entries.find { it.tagName == tag[0] } ?: return null
|
||||
val ms = tag[1].toLongOrNull() ?: return null
|
||||
return RttTag(type, ms)
|
||||
}
|
||||
|
||||
fun assemble(
|
||||
type: RttType,
|
||||
milliseconds: Long,
|
||||
) = arrayOf(type.tagName, milliseconds.toString())
|
||||
|
||||
fun assemble(rtt: RttTag) = assemble(rtt.type, rtt.milliseconds)
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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.nip66RelayMonitor.discovery.tags
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.has
|
||||
import com.vitorpamplona.quartz.utils.ensure
|
||||
|
||||
/** ["N", "<nip-number>"] - a NIP number supported by this relay (repeated per supported NIP) */
|
||||
class SupportedNipTag {
|
||||
companion object {
|
||||
const val TAG_NAME = "N"
|
||||
|
||||
fun isTag(tag: Array<String>) = tag.has(1) && tag[0] == TAG_NAME
|
||||
|
||||
fun parse(tag: Array<String>): Int? {
|
||||
ensure(tag.has(1)) { return null }
|
||||
ensure(tag[0] == TAG_NAME) { return null }
|
||||
ensure(tag[1].isNotEmpty()) { return null }
|
||||
return tag[1].toIntOrNull()
|
||||
}
|
||||
|
||||
fun assemble(nip: Int) = arrayOf(TAG_NAME, nip.toString())
|
||||
|
||||
fun assemble(nips: List<Int>) = nips.map { assemble(it) }
|
||||
}
|
||||
}
|
||||
+84
@@ -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.nip66RelayMonitor.monitor
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.quartz.nip01Core.core.BaseReplaceableEvent
|
||||
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.geohash.geohash
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.geohash.geohashes
|
||||
import com.vitorpamplona.quartz.nip31Alts.alt
|
||||
import com.vitorpamplona.quartz.nip66RelayMonitor.monitor.tags.CheckType
|
||||
import com.vitorpamplona.quartz.nip66RelayMonitor.monitor.tags.TimeoutTag
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
/**
|
||||
* NIP-66 Kind 10166: Relay Monitor Announcement
|
||||
*
|
||||
* Published by monitoring services to advertise themselves. Declares:
|
||||
* - How frequently the monitor publishes results (frequency tag, required)
|
||||
* - Which types of checks it performs (c tags)
|
||||
* - Timeout values used for each check type (timeout tags)
|
||||
* - Geographical location of the monitor (g tags)
|
||||
*
|
||||
* Monitors should also publish complementary kind 0 (metadata) and
|
||||
* kind 10002 (relay list) events for full discoverability.
|
||||
*/
|
||||
@Immutable
|
||||
class RelayMonitorEvent(
|
||||
id: HexKey,
|
||||
pubKey: HexKey,
|
||||
createdAt: Long,
|
||||
tags: Array<Array<String>>,
|
||||
content: String,
|
||||
sig: HexKey,
|
||||
) : BaseReplaceableEvent(id, pubKey, createdAt, KIND, tags, content, sig) {
|
||||
fun frequency() = tags.frequency()
|
||||
|
||||
fun checks() = tags.checks()
|
||||
|
||||
fun timeouts() = tags.timeouts()
|
||||
|
||||
fun geohashes() = tags.geohashes()
|
||||
|
||||
companion object {
|
||||
const val KIND = 10166
|
||||
const val ALT_DESCRIPTION = "Relay monitor announcement"
|
||||
|
||||
fun build(
|
||||
frequencySeconds: Long,
|
||||
checks: List<CheckType> = emptyList(),
|
||||
timeouts: List<TimeoutTag> = emptyList(),
|
||||
geohash: String? = null,
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
initializer: TagArrayBuilder<RelayMonitorEvent>.() -> Unit = {},
|
||||
) = eventTemplate<RelayMonitorEvent>(KIND, "", createdAt) {
|
||||
alt(ALT_DESCRIPTION)
|
||||
frequency(frequencySeconds)
|
||||
checks(checks)
|
||||
timeouts(timeouts)
|
||||
geohash?.let { geohash(it) }
|
||||
initializer()
|
||||
}
|
||||
}
|
||||
}
|
||||
+33
@@ -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.nip66RelayMonitor.monitor
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
|
||||
import com.vitorpamplona.quartz.nip66RelayMonitor.monitor.tags.CheckTag
|
||||
import com.vitorpamplona.quartz.nip66RelayMonitor.monitor.tags.CheckType
|
||||
import com.vitorpamplona.quartz.nip66RelayMonitor.monitor.tags.FrequencyTag
|
||||
import com.vitorpamplona.quartz.nip66RelayMonitor.monitor.tags.TimeoutTag
|
||||
|
||||
fun TagArrayBuilder<RelayMonitorEvent>.frequency(seconds: Long) = addUnique(FrequencyTag.assemble(seconds))
|
||||
|
||||
fun TagArrayBuilder<RelayMonitorEvent>.checks(checks: List<CheckType>) = addAll(CheckTag.assemble(checks))
|
||||
|
||||
fun TagArrayBuilder<RelayMonitorEvent>.timeouts(timeouts: List<TimeoutTag>) = addAll(TimeoutTag.assemble(timeouts))
|
||||
+32
@@ -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.nip66RelayMonitor.monitor
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.TagArray
|
||||
import com.vitorpamplona.quartz.nip66RelayMonitor.monitor.tags.CheckTag
|
||||
import com.vitorpamplona.quartz.nip66RelayMonitor.monitor.tags.FrequencyTag
|
||||
import com.vitorpamplona.quartz.nip66RelayMonitor.monitor.tags.TimeoutTag
|
||||
|
||||
fun TagArray.frequency() = firstNotNullOfOrNull(FrequencyTag::parse)
|
||||
|
||||
fun TagArray.checks() = mapNotNull(CheckTag::parse)
|
||||
|
||||
fun TagArray.timeouts() = mapNotNull(TimeoutTag::parse)
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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.nip66RelayMonitor.monitor.tags
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.has
|
||||
import com.vitorpamplona.quartz.utils.ensure
|
||||
|
||||
/** Types of checks a relay monitor can perform */
|
||||
enum class CheckType(
|
||||
val code: String,
|
||||
) {
|
||||
OPEN("open"),
|
||||
READ("read"),
|
||||
WRITE("write"),
|
||||
AUTH("auth"),
|
||||
NIP11("nip11"),
|
||||
DNS("dns"),
|
||||
GEO("geo"),
|
||||
}
|
||||
|
||||
/** ["c", "<check-type>"] - a capability check conducted by this monitor */
|
||||
class CheckTag {
|
||||
companion object {
|
||||
const val TAG_NAME = "c"
|
||||
|
||||
fun isTag(tag: Array<String>) = tag.has(1) && tag[0] == TAG_NAME
|
||||
|
||||
fun parse(tag: Array<String>): CheckType? {
|
||||
ensure(tag.has(1)) { return null }
|
||||
ensure(tag[0] == TAG_NAME) { return null }
|
||||
ensure(tag[1].isNotEmpty()) { return null }
|
||||
return CheckType.entries.find { it.code == tag[1] }
|
||||
}
|
||||
|
||||
fun assemble(check: CheckType) = arrayOf(TAG_NAME, check.code)
|
||||
|
||||
fun assemble(checks: List<CheckType>) = checks.map { assemble(it) }
|
||||
}
|
||||
}
|
||||
+42
@@ -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.nip66RelayMonitor.monitor.tags
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.has
|
||||
import com.vitorpamplona.quartz.utils.ensure
|
||||
|
||||
/** ["frequency", "<seconds>"] - how often this monitor publishes check results */
|
||||
class FrequencyTag {
|
||||
companion object {
|
||||
const val TAG_NAME = "frequency"
|
||||
|
||||
fun isTag(tag: Array<String>) = tag.has(1) && tag[0] == TAG_NAME
|
||||
|
||||
fun parse(tag: Array<String>): 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(seconds: Long) = arrayOf(TAG_NAME, seconds.toString())
|
||||
}
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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.nip66RelayMonitor.monitor.tags
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.vitorpamplona.quartz.nip01Core.core.has
|
||||
import com.vitorpamplona.quartz.utils.ensure
|
||||
|
||||
/**
|
||||
* Timeout tag for relay monitors.
|
||||
*
|
||||
* General form: ["timeout", "<milliseconds>"]
|
||||
* Per-check form: ["timeout", "<check-type>", "<milliseconds>"]
|
||||
*
|
||||
* Where check-type is one of: open, read, write, nip11
|
||||
*/
|
||||
@Stable
|
||||
class TimeoutTag(
|
||||
val checkType: String?,
|
||||
val milliseconds: Long,
|
||||
) {
|
||||
companion object {
|
||||
const val TAG_NAME = "timeout"
|
||||
|
||||
fun isTag(tag: Array<String>) = tag.has(1) && tag[0] == TAG_NAME
|
||||
|
||||
fun parse(tag: Array<String>): TimeoutTag? {
|
||||
ensure(tag.has(1)) { return null }
|
||||
ensure(tag[0] == TAG_NAME) { return null }
|
||||
return if (tag.has(2)) {
|
||||
val ms = tag[2].toLongOrNull() ?: return null
|
||||
TimeoutTag(checkType = tag[1], milliseconds = ms)
|
||||
} else {
|
||||
val ms = tag[1].toLongOrNull() ?: return null
|
||||
TimeoutTag(checkType = null, milliseconds = ms)
|
||||
}
|
||||
}
|
||||
|
||||
fun assemble(milliseconds: Long) = arrayOf(TAG_NAME, milliseconds.toString())
|
||||
|
||||
fun assemble(
|
||||
checkType: String,
|
||||
milliseconds: Long,
|
||||
) = arrayOf(TAG_NAME, checkType, milliseconds.toString())
|
||||
|
||||
fun assemble(timeout: TimeoutTag) =
|
||||
if (timeout.checkType != null) {
|
||||
assemble(timeout.checkType, timeout.milliseconds)
|
||||
} else {
|
||||
assemble(timeout.milliseconds)
|
||||
}
|
||||
|
||||
fun assemble(timeouts: List<TimeoutTag>) = timeouts.map { assemble(it) }
|
||||
}
|
||||
}
|
||||
@@ -121,6 +121,8 @@ import com.vitorpamplona.quartz.nip64Chess.LiveChessGameChallengeEvent
|
||||
import com.vitorpamplona.quartz.nip64Chess.LiveChessGameEndEvent
|
||||
import com.vitorpamplona.quartz.nip64Chess.LiveChessMoveEvent
|
||||
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip66RelayMonitor.discovery.RelayDiscoveryEvent
|
||||
import com.vitorpamplona.quartz.nip66RelayMonitor.monitor.RelayMonitorEvent
|
||||
import com.vitorpamplona.quartz.nip68Picture.PictureEvent
|
||||
import com.vitorpamplona.quartz.nip71Video.VideoHorizontalEvent
|
||||
import com.vitorpamplona.quartz.nip71Video.VideoNormalEvent
|
||||
@@ -281,6 +283,8 @@ class EventFactory {
|
||||
ReactionEvent.KIND -> ReactionEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
ContactCardEvent.KIND -> ContactCardEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
RelayAuthEvent.KIND -> RelayAuthEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
RelayDiscoveryEvent.KIND -> RelayDiscoveryEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
RelayMonitorEvent.KIND -> RelayMonitorEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
RelaySetEvent.KIND -> RelaySetEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
ReportEvent.KIND -> ReportEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
RepostEvent.KIND -> RepostEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
|
||||
Reference in New Issue
Block a user