Improvements on NIP-11
- Support for self, privacy_policy, terms_of_service, grasps - New UI for the Relay Information Screen - Improvements to the Debug Message - Compose-stable objects - Clickable elements for NIPs, external links, grasps, etc
This commit is contained in:
@@ -95,7 +95,7 @@ class AntiSpamFilter {
|
||||
if (spammer.shouldHide() && relay != null) {
|
||||
Amethyst.instance.relayStats
|
||||
.get(relay)
|
||||
.newSpam("$link1 $link2")
|
||||
.newSpam(link1, link2)
|
||||
}
|
||||
|
||||
flowSpam.tryEmit(AntiSpamState(this))
|
||||
@@ -123,7 +123,7 @@ class AntiSpamFilter {
|
||||
if (spammer.shouldHide() && relay != null) {
|
||||
Amethyst.instance.relayStats
|
||||
.get(relay)
|
||||
.newSpam("$link1 $link2")
|
||||
.newSpam(link1, link2)
|
||||
}
|
||||
|
||||
flowSpam.tryEmit(AntiSpamState(this))
|
||||
|
||||
@@ -116,6 +116,14 @@ val njumpLink = { nip19BechAddress: String ->
|
||||
"https://njump.to/$nip19BechAddress"
|
||||
}
|
||||
|
||||
val nipLink = { nipNumber: String ->
|
||||
"https://nostrhub.io/$nipNumber"
|
||||
}
|
||||
|
||||
val graspLink = { graspNumber: String ->
|
||||
"https://gitworkshop.dev/danconwaydev.com/grasp/tree/master/$graspNumber.md"
|
||||
}
|
||||
|
||||
val externalLinkForNote = { note: Note ->
|
||||
if (note is AddressableNote) {
|
||||
if (note.event?.bountyBaseReward() != null) {
|
||||
|
||||
@@ -56,6 +56,12 @@ fun showAmountInteger(amount: BigDecimal?): String {
|
||||
}
|
||||
}
|
||||
|
||||
fun showAmountInteger(amount: Int?): String {
|
||||
if (amount == null) return "0"
|
||||
|
||||
return showAmountIntegerWithZero(BigDecimal.valueOf(amount.toLong()))
|
||||
}
|
||||
|
||||
fun showAmountIntegerWithZero(amount: BigDecimal?): String {
|
||||
if (amount == null) return "0"
|
||||
if (amount.abs() < BigDecimal(0.01)) return "0"
|
||||
|
||||
+813
-289
File diff suppressed because it is too large
Load Diff
@@ -17,6 +17,8 @@
|
||||
<string name="could_not_decrypt_the_message">Could not decrypt the message</string>
|
||||
<string name="group_picture">Group Picture</string>
|
||||
<string name="explicit_content">Explicit Content</string>
|
||||
<string name="relay_notice">Relay Notice</string>
|
||||
<string name="duplicated_post">Duplicated Post</string>
|
||||
<string name="spam">Spam</string>
|
||||
<string name="spam_description">The number of spamming events coming from this relay</string>
|
||||
<string name="impersonation">Impersonation</string>
|
||||
@@ -133,6 +135,7 @@
|
||||
<string name="relay_address">Relay Address</string>
|
||||
<string name="posts">Posts</string>
|
||||
<string name="bytes">Bytes</string>
|
||||
<string name="error">Error</string>
|
||||
<string name="errors">Errors</string>
|
||||
<string name="errors_description">The number of connection errors in this session</string>
|
||||
<string name="home_feed">Home Feed</string>
|
||||
@@ -747,28 +750,64 @@
|
||||
<string name="read_from_relay_description">The amount in bytes that was received from this relay, including filters and events</string>
|
||||
<string name="an_error_occurred_trying_to_get_relay_information">An error occurred trying to get relay information from %1$s</string>
|
||||
<string name="owner">Owner</string>
|
||||
<string name="self">Service Key</string>
|
||||
<string name="running_software">Running %1$s</string>
|
||||
<string name="running_software_version">Running %1$s (%2$s)</string>
|
||||
<string name="version">Version</string>
|
||||
<string name="software">Software</string>
|
||||
<string name="contact">Contact</string>
|
||||
<string name="supports">Supported NIPs</string>
|
||||
<string name="supported_grasps">Supported Grasps</string>
|
||||
<string name="admission_fees">Admission Fees</string>
|
||||
<string name="admission">Admission</string>
|
||||
<string name="subscription">Subscription</string>
|
||||
<string name="publication">Publication</string>
|
||||
<string name="payment_link">Payments %1$s</string>
|
||||
<string name="payments_url">Payments url</string>
|
||||
<string name="target_audience">Target Audience</string>
|
||||
<string name="policies_and_links">Policies & Links</string>
|
||||
<string name="fees_and_payments">Fees & Payments</string>
|
||||
<string name="limitations">Limitations</string>
|
||||
<string name="countries">Countries</string>
|
||||
<string name="languages">Languages</string>
|
||||
<string name="tags">Tags</string>
|
||||
<string name="topics">Topics</string>
|
||||
<string name="all_countries">All Countries</string>
|
||||
<string name="all_languages">All Languages</string>
|
||||
<string name="posting_policy">Posting policy</string>
|
||||
<string name="privacy_policy">Privacy Policy</string>
|
||||
<string name="terms_and_conditions">Terms & Conditions</string>
|
||||
<string name="not_available_acronym">N/A</string>
|
||||
<string name="relay_error_messages">Errors and Notices from this Relay</string>
|
||||
<string name="message_length">Message length</string>
|
||||
<string name="subscriptions">Subscriptions</string>
|
||||
<string name="filters">Filters</string>
|
||||
<string name="subscription_id_length">Subscription id length</string>
|
||||
<string name="minimum_prefix">Minimum prefix</string>
|
||||
<string name="maximum_event_tags">Maximum event tags</string>
|
||||
<string name="minimum_prefix">Min Prefix</string>
|
||||
<string name="maximum_event_tags">Max Event Tags</string>
|
||||
<string name="content_length">Content length</string>
|
||||
<string name="minimum_pow">Minimum PoW</string>
|
||||
<string name="max_content_length">Max Content Length</string>
|
||||
<string name="discards_older_than">Discards older than</string>
|
||||
<string name="accepts_up_to">Accepts up to</string>
|
||||
<string name="time_in_the_future">%1$s in the future</string>
|
||||
<string name="time_in_the_past">%1$s ago</string>
|
||||
<string name="amount_in_zeros">%1$s zeros</string>
|
||||
<string name="amount_in_bits">%1$s bits</string>
|
||||
<string name="event_retention">Event Retention</string>
|
||||
<string name="content_size">Content Size</string>
|
||||
<string name="connectivity">Connectivity</string>
|
||||
<string name="access_control">Access Control</string>
|
||||
<string name="minimum_pow">Min PoW Difficulty</string>
|
||||
<string name="auth">Auth</string>
|
||||
<string name="auth_required">Auth Required</string>
|
||||
<string name="payment">Payment</string>
|
||||
<string name="payment_required">Payment Required</string>
|
||||
<string name="max_message_length">Max Message Length</string>
|
||||
<string name="max_subs">Max Subscriptions</string>
|
||||
<string name="max_filters_per_sub">Max Filters per Sub</string>
|
||||
<string name="max_limit_events_returning">Max Limit (Events Returning)</string>
|
||||
<string name="default_limit">Default Limit (Events Returning)</string>
|
||||
<string name="max_subid_length">Max SubID Length</string>
|
||||
<string name="cashu">Cashu Token</string>
|
||||
<string name="cashu_redeem">Redeem</string>
|
||||
<string name="cashu_redeem_to_zap">Send to Zap Wallet</string>
|
||||
|
||||
+45
@@ -20,6 +20,7 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.commons.util
|
||||
|
||||
import com.sun.org.apache.xalan.internal.lib.ExsltDatetime.time
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
@@ -81,6 +82,50 @@ fun timeAgo(
|
||||
}
|
||||
}
|
||||
|
||||
fun timeDiffAgoLong(timeDifference: Int): String =
|
||||
when {
|
||||
timeDifference > TimeUtils.ONE_YEAR -> {
|
||||
(timeDifference / TimeUtils.ONE_YEAR).toString() + " years"
|
||||
}
|
||||
timeDifference > TimeUtils.ONE_MONTH -> {
|
||||
(timeDifference / TimeUtils.ONE_MONTH).toString() + " months"
|
||||
}
|
||||
timeDifference > TimeUtils.ONE_DAY -> {
|
||||
(timeDifference / TimeUtils.ONE_DAY).toString() + " days"
|
||||
}
|
||||
timeDifference > TimeUtils.ONE_HOUR -> {
|
||||
(timeDifference / TimeUtils.ONE_HOUR).toString() + " hours"
|
||||
}
|
||||
timeDifference > TimeUtils.ONE_MINUTE -> {
|
||||
(timeDifference / TimeUtils.ONE_MINUTE).toString() + " minutes"
|
||||
}
|
||||
else -> {
|
||||
"now"
|
||||
}
|
||||
}
|
||||
|
||||
fun timeDiffAgoShortish(timeDifference: Int): String =
|
||||
when {
|
||||
timeDifference > TimeUtils.ONE_YEAR -> {
|
||||
(timeDifference / TimeUtils.ONE_YEAR).toString() + " yrs"
|
||||
}
|
||||
timeDifference > TimeUtils.ONE_MONTH -> {
|
||||
(timeDifference / TimeUtils.ONE_MONTH).toString() + " mos"
|
||||
}
|
||||
timeDifference > TimeUtils.ONE_DAY -> {
|
||||
(timeDifference / TimeUtils.ONE_DAY).toString() + " days"
|
||||
}
|
||||
timeDifference > TimeUtils.ONE_HOUR -> {
|
||||
(timeDifference / TimeUtils.ONE_HOUR).toString() + " hrs"
|
||||
}
|
||||
timeDifference > TimeUtils.ONE_MINUTE -> {
|
||||
(timeDifference / TimeUtils.ONE_MINUTE).toString() + " mins"
|
||||
}
|
||||
else -> {
|
||||
"now"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a Unix timestamp as a date string.
|
||||
* For recent dates (< 1 day), returns the provided today string.
|
||||
|
||||
+29
-21
@@ -21,8 +21,10 @@
|
||||
package com.vitorpamplona.quartz.nip01Core.relay.client.stats
|
||||
|
||||
import androidx.collection.LruCache
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
@Stable
|
||||
class RelayStat(
|
||||
var receivedBytes: Int = 0,
|
||||
var sentBytes: Int = 0,
|
||||
@@ -31,12 +33,11 @@ class RelayStat(
|
||||
var pingInMs: Int = 0,
|
||||
var compression: Boolean = false,
|
||||
) {
|
||||
val messages = LruCache<RelayDebugMessage, RelayDebugMessage>(100)
|
||||
val messages = LruCache<IRelayDebugMessage, IRelayDebugMessage>(100)
|
||||
|
||||
fun newNotice(notice: String?) {
|
||||
val debugMessage =
|
||||
RelayDebugMessage(
|
||||
type = RelayDebugMessageType.NOTICE,
|
||||
NoticeDebugMessage(
|
||||
message = notice ?: "No error message provided",
|
||||
)
|
||||
|
||||
@@ -47,8 +48,7 @@ class RelayStat(
|
||||
errorCounter++
|
||||
|
||||
val debugMessage =
|
||||
RelayDebugMessage(
|
||||
type = RelayDebugMessageType.ERROR,
|
||||
ErrorDebugMessage(
|
||||
message = error ?: "No error message provided",
|
||||
)
|
||||
|
||||
@@ -63,27 +63,35 @@ class RelayStat(
|
||||
sentBytes += bytesUsedInMemory
|
||||
}
|
||||
|
||||
fun newSpam(spamDescriptor: String) {
|
||||
fun newSpam(
|
||||
link1: String,
|
||||
link2: String,
|
||||
) {
|
||||
spamCounter++
|
||||
|
||||
val debugMessage =
|
||||
RelayDebugMessage(
|
||||
type = RelayDebugMessageType.SPAM,
|
||||
message = spamDescriptor,
|
||||
)
|
||||
val debugMessage = SpamDebugMessage(link1, link2)
|
||||
|
||||
messages.put(debugMessage, debugMessage)
|
||||
}
|
||||
}
|
||||
|
||||
class RelayDebugMessage(
|
||||
val type: RelayDebugMessageType,
|
||||
val message: String,
|
||||
val time: Long = TimeUtils.now(),
|
||||
)
|
||||
|
||||
enum class RelayDebugMessageType {
|
||||
SPAM,
|
||||
NOTICE,
|
||||
ERROR,
|
||||
@Stable
|
||||
sealed interface IRelayDebugMessage {
|
||||
val time: Long
|
||||
}
|
||||
|
||||
class SpamDebugMessage(
|
||||
val link1: String,
|
||||
val link2: String,
|
||||
override val time: Long = TimeUtils.now(),
|
||||
) : IRelayDebugMessage
|
||||
|
||||
class NoticeDebugMessage(
|
||||
val message: String,
|
||||
override val time: Long = TimeUtils.now(),
|
||||
) : IRelayDebugMessage
|
||||
|
||||
class ErrorDebugMessage(
|
||||
val message: String,
|
||||
override val time: Long = TimeUtils.now(),
|
||||
) : IRelayDebugMessage
|
||||
|
||||
+3
@@ -20,6 +20,9 @@
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip01Core.relay.normalizer
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
|
||||
@Stable
|
||||
data class NormalizedRelayUrl(
|
||||
val url: String,
|
||||
) : Comparable<NormalizedRelayUrl> {
|
||||
|
||||
+7
-7
@@ -21,6 +21,7 @@
|
||||
package com.vitorpamplona.quartz.nip11RelayInfo
|
||||
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
import com.vitorpamplona.quartz.utils.text
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.builtins.ListSerializer
|
||||
@@ -32,15 +33,14 @@ import kotlinx.serialization.json.JsonArray
|
||||
import kotlinx.serialization.json.JsonDecoder
|
||||
import kotlinx.serialization.json.JsonNull
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import kotlinx.serialization.json.int
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
|
||||
object FlexibleIntListSerializer : KSerializer<List<Int>?> {
|
||||
private val listSerializer = ListSerializer(Int.serializer())
|
||||
object FlexibleIntListSerializer : KSerializer<List<String>?> {
|
||||
private val listSerializer = ListSerializer(String.serializer())
|
||||
|
||||
override val descriptor: SerialDescriptor = listSerializer.descriptor
|
||||
|
||||
override fun deserialize(decoder: Decoder): List<Int>? {
|
||||
override fun deserialize(decoder: Decoder): List<String>? {
|
||||
require(decoder is JsonDecoder) { "This serializer can only be used with Json format" }
|
||||
|
||||
return when (val element = decoder.decodeJsonElement()) {
|
||||
@@ -51,7 +51,7 @@ object FlexibleIntListSerializer : KSerializer<List<Int>?> {
|
||||
is JsonArray -> {
|
||||
element.mapNotNull { arrayElement ->
|
||||
try {
|
||||
arrayElement.jsonPrimitive.int
|
||||
arrayElement.jsonPrimitive.text
|
||||
} catch (e: Exception) {
|
||||
// Skip elements that aren't valid integers (strings, booleans, floats, etc.)
|
||||
Log.w("FlexibleIntListSerializer", "Invalid element in array: $arrayElement", e)
|
||||
@@ -63,7 +63,7 @@ object FlexibleIntListSerializer : KSerializer<List<Int>?> {
|
||||
// Handle single integer format (malformed but found in the wild): 1
|
||||
is JsonPrimitive if !element.isString -> {
|
||||
try {
|
||||
listOf(element.int)
|
||||
listOf(element.text)
|
||||
} catch (e: Exception) {
|
||||
// Can't parse as integer (e.g., float, boolean), treat as missing data
|
||||
Log.w("FlexibleIntListSerializer", "Invalid primitive: $element", e)
|
||||
@@ -79,7 +79,7 @@ object FlexibleIntListSerializer : KSerializer<List<Int>?> {
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
override fun serialize(
|
||||
encoder: Encoder,
|
||||
value: List<Int>?,
|
||||
value: List<String>?,
|
||||
) {
|
||||
if (value == null) {
|
||||
encoder.encodeNull()
|
||||
|
||||
+53
-43
@@ -21,19 +21,22 @@
|
||||
package com.vitorpamplona.quartz.nip11RelayInfo
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.JsonMapper
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Stable
|
||||
@Serializable
|
||||
class Nip11RelayInformation(
|
||||
val id: String? = null,
|
||||
val name: String? = null,
|
||||
val description: String? = null,
|
||||
val icon: String? = null,
|
||||
val pubkey: String? = null,
|
||||
val pubkey: HexKey? = null,
|
||||
val self: HexKey? = null,
|
||||
val contact: String? = null,
|
||||
@Serializable(with = FlexibleIntListSerializer::class)
|
||||
val supported_nips: List<Int>? = null,
|
||||
val supported_nips: List<String>? = null,
|
||||
val supported_nip_extensions: List<String>? = null,
|
||||
val software: String? = null,
|
||||
val version: String? = null,
|
||||
@@ -42,53 +45,60 @@ class Nip11RelayInformation(
|
||||
val language_tags: List<String>? = null,
|
||||
val tags: List<String>? = null,
|
||||
val posting_policy: String? = null,
|
||||
val privacy_policy: String? = null,
|
||||
val terms_of_service: String? = null,
|
||||
val payments_url: String? = null,
|
||||
val retention: List<RelayInformationRetentionData>? = null,
|
||||
val fees: RelayInformationFees? = null,
|
||||
val nip50: List<String>? = null,
|
||||
val supported_grasps: List<String>? = null,
|
||||
) {
|
||||
companion object {
|
||||
fun fromJson(json: String): Nip11RelayInformation = JsonMapper.fromJson<Nip11RelayInformation>(json)
|
||||
}
|
||||
|
||||
@Stable
|
||||
@Serializable
|
||||
class RelayInformationFee(
|
||||
val amount: Int? = null,
|
||||
val unit: String? = null,
|
||||
val period: Int? = null,
|
||||
val kinds: List<Int>? = null,
|
||||
)
|
||||
|
||||
@Stable
|
||||
@Serializable
|
||||
class RelayInformationFees(
|
||||
val admission: List<RelayInformationFee>? = null,
|
||||
val subscription: List<RelayInformationFee>? = null,
|
||||
val publication: List<RelayInformationFee>? = null,
|
||||
)
|
||||
|
||||
@Stable
|
||||
@Serializable
|
||||
class RelayInformationLimitation(
|
||||
val max_message_length: Int? = null,
|
||||
val max_subscriptions: Int? = null,
|
||||
val max_filters: Int? = null,
|
||||
val max_limit: Int? = null,
|
||||
val default_limit: Int? = null,
|
||||
val max_subid_length: Int? = null,
|
||||
val min_prefix: Int? = null,
|
||||
val max_event_tags: Int? = null,
|
||||
val max_content_length: Int? = null,
|
||||
val min_pow_difficulty: Int? = null,
|
||||
val auth_required: Boolean? = null,
|
||||
val payment_required: Boolean? = null,
|
||||
val restricted_writes: Boolean? = null,
|
||||
val created_at_lower_limit: Int? = null,
|
||||
val created_at_upper_limit: Int? = null,
|
||||
)
|
||||
|
||||
@Stable
|
||||
@Serializable
|
||||
class RelayInformationRetentionData(
|
||||
val kinds: ArrayList<Int>? = null,
|
||||
val time: Int? = null,
|
||||
val count: Int? = null,
|
||||
)
|
||||
}
|
||||
|
||||
@Stable
|
||||
@Serializable
|
||||
class RelayInformationFee(
|
||||
val amount: Int? = null,
|
||||
val unit: String? = null,
|
||||
val period: Int? = null,
|
||||
val kinds: List<Int>? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
class RelayInformationFees(
|
||||
val admission: List<RelayInformationFee>? = null,
|
||||
val subscription: List<RelayInformationFee>? = null,
|
||||
val publication: List<RelayInformationFee>? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
class RelayInformationLimitation(
|
||||
val max_message_length: Int? = null,
|
||||
val max_subscriptions: Int? = null,
|
||||
val max_filters: Int? = null,
|
||||
val max_limit: Int? = null,
|
||||
val max_subid_length: Int? = null,
|
||||
val min_prefix: Int? = null,
|
||||
val max_event_tags: Int? = null,
|
||||
val max_content_length: Int? = null,
|
||||
val min_pow_difficulty: Int? = null,
|
||||
val auth_required: Boolean? = null,
|
||||
val payment_required: Boolean? = null,
|
||||
val restricted_writes: Boolean? = null,
|
||||
val created_at_lower_limit: Int? = null,
|
||||
val created_at_upper_limit: Int? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
class RelayInformationRetentionData(
|
||||
val kinds: ArrayList<Int>? = null,
|
||||
val time: Int? = null,
|
||||
val count: Int? = null,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user