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.LiveChessGameEndEvent
|
||||||
import com.vitorpamplona.quartz.nip64Chess.LiveChessMoveEvent
|
import com.vitorpamplona.quartz.nip64Chess.LiveChessMoveEvent
|
||||||
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
|
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.nip68Picture.PictureEvent
|
||||||
import com.vitorpamplona.quartz.nip71Video.VideoHorizontalEvent
|
import com.vitorpamplona.quartz.nip71Video.VideoHorizontalEvent
|
||||||
import com.vitorpamplona.quartz.nip71Video.VideoNormalEvent
|
import com.vitorpamplona.quartz.nip71Video.VideoNormalEvent
|
||||||
@@ -582,6 +584,8 @@ private fun kindDisplayName(kind: Int): Int =
|
|||||||
ReactionEvent.KIND -> R.string.kind_reactions
|
ReactionEvent.KIND -> R.string.kind_reactions
|
||||||
ContactCardEvent.KIND -> R.string.kind_contact_card
|
ContactCardEvent.KIND -> R.string.kind_contact_card
|
||||||
RelayAuthEvent.KIND -> R.string.kind_relay_auth
|
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
|
RelaySetEvent.KIND -> R.string.kind_relay_set
|
||||||
ReportEvent.KIND -> R.string.kind_reports
|
ReportEvent.KIND -> R.string.kind_reports
|
||||||
RepostEvent.KIND -> R.string.kind_reposts
|
RepostEvent.KIND -> R.string.kind_reposts
|
||||||
|
|||||||
@@ -1663,6 +1663,8 @@
|
|||||||
<string name="kind_reactions">Reactions</string>
|
<string name="kind_reactions">Reactions</string>
|
||||||
<string name="kind_contact_card">Contact Card</string>
|
<string name="kind_contact_card">Contact Card</string>
|
||||||
<string name="kind_relay_auth">Relay Auth</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_relay_set">Relay Set</string>
|
||||||
<string name="kind_reports">Reports</string>
|
<string name="kind_reports">Reports</string>
|
||||||
<string name="kind_reposts">Reposts</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.LiveChessGameEndEvent
|
||||||
import com.vitorpamplona.quartz.nip64Chess.LiveChessMoveEvent
|
import com.vitorpamplona.quartz.nip64Chess.LiveChessMoveEvent
|
||||||
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
|
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.nip68Picture.PictureEvent
|
||||||
import com.vitorpamplona.quartz.nip71Video.VideoHorizontalEvent
|
import com.vitorpamplona.quartz.nip71Video.VideoHorizontalEvent
|
||||||
import com.vitorpamplona.quartz.nip71Video.VideoNormalEvent
|
import com.vitorpamplona.quartz.nip71Video.VideoNormalEvent
|
||||||
@@ -281,6 +283,8 @@ class EventFactory {
|
|||||||
ReactionEvent.KIND -> ReactionEvent(id, pubKey, createdAt, tags, content, sig)
|
ReactionEvent.KIND -> ReactionEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
ContactCardEvent.KIND -> ContactCardEvent(id, pubKey, createdAt, tags, content, sig)
|
ContactCardEvent.KIND -> ContactCardEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
RelayAuthEvent.KIND -> RelayAuthEvent(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)
|
RelaySetEvent.KIND -> RelaySetEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
ReportEvent.KIND -> ReportEvent(id, pubKey, createdAt, tags, content, sig)
|
ReportEvent.KIND -> ReportEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
RepostEvent.KIND -> RepostEvent(id, pubKey, createdAt, tags, content, sig)
|
RepostEvent.KIND -> RepostEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|||||||
Reference in New Issue
Block a user