Merge branch 'vitorpamplona:main' into profiles-list-management
This commit is contained in:
@@ -108,6 +108,8 @@ import com.vitorpamplona.quartz.nip62RequestToVanish.RequestToVanishEvent
|
||||
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip68Picture.PictureEvent
|
||||
import com.vitorpamplona.quartz.nip71Video.VideoHorizontalEvent
|
||||
import com.vitorpamplona.quartz.nip71Video.VideoNormalEvent
|
||||
import com.vitorpamplona.quartz.nip71Video.VideoShortEvent
|
||||
import com.vitorpamplona.quartz.nip71Video.VideoVerticalEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.approval.CommunityPostApprovalEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent
|
||||
@@ -286,6 +288,8 @@ class EventFactory {
|
||||
TrustedRelayListEvent.KIND -> TrustedRelayListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
VideoHorizontalEvent.KIND -> VideoHorizontalEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
VideoVerticalEvent.KIND -> VideoVerticalEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
VideoNormalEvent.KIND -> VideoNormalEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
VideoShortEvent.KIND -> VideoShortEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
VoiceEvent.KIND -> VoiceEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
VoiceReplyEvent.KIND -> VoiceReplyEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
WikiNoteEvent.KIND -> WikiNoteEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
|
||||
+1
-1
@@ -54,7 +54,7 @@ class AudioHeaderEvent(
|
||||
description: String,
|
||||
downloadUrl: String,
|
||||
streamUrl: String? = null,
|
||||
wavefront: List<Int>? = null,
|
||||
wavefront: List<Float>? = null,
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
initializer: TagArrayBuilder<AudioHeaderEvent>.() -> Unit = {},
|
||||
) = eventTemplate(KIND, description, createdAt) {
|
||||
|
||||
+1
-1
@@ -29,4 +29,4 @@ fun TagArrayBuilder<AudioHeaderEvent>.downloadUrl(downloadUrlTag: String) = addU
|
||||
|
||||
fun TagArrayBuilder<AudioHeaderEvent>.streamUrl(streamUrl: String) = addUnique(StreamUrlTag.assemble(streamUrl))
|
||||
|
||||
fun TagArrayBuilder<AudioHeaderEvent>.wavefront(wave: List<Int>) = addUnique(WaveformTag.assemble(wave))
|
||||
fun TagArrayBuilder<AudioHeaderEvent>.wavefront(wave: List<Float>) = addUnique(WaveformTag.assemble(wave))
|
||||
|
||||
+3
-3
@@ -26,7 +26,7 @@ import com.vitorpamplona.quartz.nip01Core.jackson.JsonMapper
|
||||
import com.vitorpamplona.quartz.utils.ensure
|
||||
|
||||
class WaveformTag(
|
||||
val wave: List<Int>,
|
||||
val wave: List<Float>,
|
||||
) {
|
||||
fun toTagArray() = assemble(wave)
|
||||
|
||||
@@ -38,12 +38,12 @@ class WaveformTag(
|
||||
ensure(tag.has(1)) { return null }
|
||||
ensure(tag[0] == TAG_NAME) { return null }
|
||||
ensure(tag[1].isNotEmpty()) { return null }
|
||||
val wave = runCatching { JsonMapper.mapper.readValue<List<Int>>(tag[1]) }.getOrNull()
|
||||
val wave = runCatching { JsonMapper.mapper.readValue<List<Float>>(tag[1]) }.getOrNull()
|
||||
if (wave.isNullOrEmpty()) return null
|
||||
return WaveformTag(wave)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun assemble(wave: List<Int>) = arrayOf(TAG_NAME, JsonMapper.mapper.writeValueAsString(wave))
|
||||
fun assemble(wave: List<Float>) = arrayOf(TAG_NAME, JsonMapper.mapper.writeValueAsString(wave))
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -128,7 +128,7 @@ class EphemeralChatListEvent(
|
||||
signer: NostrSigner,
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
) = resign(
|
||||
content = PrivateTagsInContent.encryptNip04(privateTags, signer),
|
||||
content = PrivateTagsInContent.encryptNip44(privateTags, signer),
|
||||
tags = tags,
|
||||
signer = signer,
|
||||
createdAt = createdAt,
|
||||
@@ -168,7 +168,7 @@ class EphemeralChatListEvent(
|
||||
initializer: TagArrayBuilder<EphemeralChatListEvent>.() -> Unit = {},
|
||||
) = eventTemplate<EphemeralChatListEvent>(
|
||||
kind = KIND,
|
||||
description = PrivateTagsInContent.encryptNip04(privateRooms.map { it.toTagArray() }.toTypedArray(), signer),
|
||||
description = PrivateTagsInContent.encryptNip44(privateRooms.map { it.toTagArray() }.toTypedArray(), signer),
|
||||
createdAt = createdAt,
|
||||
) {
|
||||
alt(ALT)
|
||||
|
||||
@@ -133,6 +133,8 @@ class NostrClient(
|
||||
relayPool.disconnect()
|
||||
}
|
||||
|
||||
fun isActive() = isActive
|
||||
|
||||
@Synchronized
|
||||
fun reconnect(onlyIfChanged: Boolean = false) {
|
||||
if (onlyIfChanged) {
|
||||
|
||||
+16
-12
@@ -75,23 +75,27 @@ class RelayPool(
|
||||
|
||||
fun reconnectIfNeedsToORIfItIsTime() {
|
||||
if (lastReconnectCall < TimeUtils.oneMinuteAgo()) {
|
||||
relays.forEach { url, relay ->
|
||||
if (relay.isConnected()) {
|
||||
if (relay.needsToReconnect()) {
|
||||
// network has changed, force reconnect
|
||||
relay.disconnect()
|
||||
relay.connect()
|
||||
}
|
||||
} else {
|
||||
// relay is not connected. Connect if it is time
|
||||
relay.connectAndSyncFiltersIfDisconnected()
|
||||
}
|
||||
}
|
||||
reconnectIfNeedsTo()
|
||||
|
||||
lastReconnectCall = TimeUtils.now()
|
||||
}
|
||||
}
|
||||
|
||||
fun reconnectIfNeedsTo() {
|
||||
relays.forEach { url, relay ->
|
||||
if (relay.isConnected()) {
|
||||
if (relay.needsToReconnect()) {
|
||||
// network has changed, force reconnect
|
||||
relay.disconnect()
|
||||
relay.connect()
|
||||
}
|
||||
} else {
|
||||
// relay is not connected. Connect if it is time
|
||||
relay.connectAndSyncFiltersIfDisconnected()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun connect() =
|
||||
relays.forEach { url, relay ->
|
||||
relay.connect()
|
||||
|
||||
+3
-3
@@ -30,7 +30,7 @@ import okhttp3.Response
|
||||
|
||||
class BasicOkHttpWebSocket(
|
||||
val url: NormalizedRelayUrl,
|
||||
val httpClient: OkHttpClient,
|
||||
val httpClient: (NormalizedRelayUrl) -> OkHttpClient,
|
||||
val out: WebSocketListener,
|
||||
) : WebSocket {
|
||||
private var socket: okhttp3.WebSocket? = null
|
||||
@@ -74,7 +74,7 @@ class BasicOkHttpWebSocket(
|
||||
) = out.onFailure(t, r?.code, r?.message)
|
||||
}
|
||||
|
||||
socket = httpClient.newWebSocket(request, listener)
|
||||
socket = httpClient(url).newWebSocket(request, listener)
|
||||
}
|
||||
|
||||
override fun disconnect() {
|
||||
@@ -85,7 +85,7 @@ class BasicOkHttpWebSocket(
|
||||
override fun send(msg: String): Boolean = socket?.send(msg) ?: false
|
||||
|
||||
class Builder(
|
||||
val httpClient: OkHttpClient,
|
||||
val httpClient: (NormalizedRelayUrl) -> OkHttpClient,
|
||||
) : WebsocketBuilder {
|
||||
// Called when connecting.
|
||||
override fun build(
|
||||
|
||||
@@ -118,6 +118,15 @@ class TextNoteEvent(
|
||||
return ALT + msg.take(50) + "..."
|
||||
}
|
||||
|
||||
fun build(
|
||||
note: String,
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
initializer: TagArrayBuilder<TextNoteEvent>.() -> Unit = {},
|
||||
) = eventTemplate(KIND, note, createdAt) {
|
||||
alt(shortedMessageForAlt(note))
|
||||
initializer()
|
||||
}
|
||||
|
||||
fun build(
|
||||
note: String,
|
||||
replyingTo: EventHintBundle<TextNoteEvent>? = null,
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip10Notes.tags
|
||||
|
||||
import android.R.attr.tag
|
||||
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
|
||||
import com.vitorpamplona.quartz.nip10Notes.BaseThreadedEvent
|
||||
@@ -27,7 +28,7 @@ import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
|
||||
fun <T : BaseThreadedEvent> TagArrayBuilder<T>.markedETag(tag: MarkedETag) = add(tag.toTagArray())
|
||||
|
||||
fun <T : BaseThreadedEvent> TagArrayBuilder<T>.markedETags(tag: List<MarkedETag>) = addAll(tag.map { it.toTagArray() })
|
||||
fun <T : BaseThreadedEvent> TagArrayBuilder<T>.markedETags(tags: List<MarkedETag>) = addAll(tags.map { it.toTagArray() })
|
||||
|
||||
fun TagArrayBuilder<TextNoteEvent>.notify(tag: PTag) = add(tag.toTagArray())
|
||||
|
||||
|
||||
+2
-2
@@ -155,7 +155,7 @@ class ChannelListEvent(
|
||||
signer: NostrSigner,
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
) = resign(
|
||||
content = PrivateTagsInContent.encryptNip04(privateTags, signer),
|
||||
content = PrivateTagsInContent.encryptNip44(privateTags, signer),
|
||||
tags = tags,
|
||||
signer = signer,
|
||||
createdAt = createdAt,
|
||||
@@ -206,7 +206,7 @@ class ChannelListEvent(
|
||||
initializer: TagArrayBuilder<ChannelListEvent>.() -> Unit = {},
|
||||
) = eventTemplate<ChannelListEvent>(
|
||||
kind = KIND,
|
||||
description = PrivateTagsInContent.encryptNip04(privateChannels.map { it.toTagArray() }.toTypedArray(), signer),
|
||||
description = PrivateTagsInContent.encryptNip44(privateChannels.map { it.toTagArray() }.toTypedArray(), signer),
|
||||
createdAt = createdAt,
|
||||
) {
|
||||
alt(ALT)
|
||||
|
||||
@@ -34,6 +34,7 @@ import com.vitorpamplona.quartz.nip01Core.tags.addressables.Address
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.dTags.dTag
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.kinds.kind
|
||||
import com.vitorpamplona.quartz.nip31Alts.alt
|
||||
import com.vitorpamplona.quartz.nip40Expiration.expiration
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
@Immutable
|
||||
@@ -91,6 +92,11 @@ class DraftWrapEvent(
|
||||
alt(ALT_DESCRIPTION)
|
||||
dTag(dTag)
|
||||
kind(draft.kind)
|
||||
expiration(TimeUtils.ninetyDaysFromNow())
|
||||
|
||||
if (draft is ExposeInDraft) {
|
||||
addAll(draft.exposeInDraft())
|
||||
}
|
||||
|
||||
initializer()
|
||||
}
|
||||
@@ -105,6 +111,7 @@ class DraftWrapEvent(
|
||||
createdAt = createdAt,
|
||||
) {
|
||||
alt(ALT_DESCRIPTION)
|
||||
expiration(TimeUtils.oneMinuteFromNow())
|
||||
dTag(dTag)
|
||||
initializer()
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ class PrivateTagArrayBuilder {
|
||||
): Pair<String, Array<Array<String>>> =
|
||||
if (toPrivate) {
|
||||
val encryptedTags =
|
||||
PrivateTagsInContent.encryptNip04(
|
||||
PrivateTagsInContent.encryptNip44(
|
||||
privateTags = tags,
|
||||
signer = signer,
|
||||
)
|
||||
@@ -51,7 +51,7 @@ class PrivateTagArrayBuilder {
|
||||
if (toPrivate) {
|
||||
val privateTags = current.privateTags(signer) ?: throw SignerExceptions.UnauthorizedDecryptionException()
|
||||
val encryptedTags =
|
||||
PrivateTagsInContent.encryptNip04(
|
||||
PrivateTagsInContent.encryptNip44(
|
||||
privateTags = privateTags.plus(newTag),
|
||||
signer = signer,
|
||||
)
|
||||
@@ -69,7 +69,7 @@ class PrivateTagArrayBuilder {
|
||||
if (toPrivate) {
|
||||
val privateTags = current.privateTags(signer) ?: throw SignerExceptions.UnauthorizedDecryptionException()
|
||||
val encryptedTags =
|
||||
PrivateTagsInContent.encryptNip04(
|
||||
PrivateTagsInContent.encryptNip44(
|
||||
privateTags = privateTags.plus(newTag),
|
||||
signer = signer,
|
||||
)
|
||||
@@ -112,7 +112,7 @@ class PrivateTagArrayBuilder {
|
||||
): Pair<String, Array<Array<String>>> {
|
||||
val privateTags = current.privateTags(signer) ?: throw SignerExceptions.UnauthorizedDecryptionException()
|
||||
val encryptedTags =
|
||||
PrivateTagsInContent.encryptNip04(
|
||||
PrivateTagsInContent.encryptNip44(
|
||||
privateTags = privateTags.replaceAll(oldTagStartsWith, newTag),
|
||||
signer = signer,
|
||||
)
|
||||
@@ -127,7 +127,7 @@ class PrivateTagArrayBuilder {
|
||||
): Pair<String, Array<Array<String>>> {
|
||||
val privateTags = current.privateTags(signer) ?: throw SignerExceptions.UnauthorizedDecryptionException()
|
||||
val encryptedTags =
|
||||
PrivateTagsInContent.encryptNip04(
|
||||
PrivateTagsInContent.encryptNip44(
|
||||
privateTags = privateTags.remove(oldTagStartsWith),
|
||||
signer = signer,
|
||||
)
|
||||
@@ -141,7 +141,7 @@ class PrivateTagArrayBuilder {
|
||||
): Pair<String, Array<Array<String>>> {
|
||||
val privateTags = current.privateTags(signer) ?: throw SignerExceptions.UnauthorizedDecryptionException()
|
||||
val encryptedTags =
|
||||
PrivateTagsInContent.encryptNip04(
|
||||
PrivateTagsInContent.encryptNip44(
|
||||
privateTags = privateTags.remove(oldTagStartsWith),
|
||||
signer = signer,
|
||||
)
|
||||
@@ -161,7 +161,7 @@ class PrivateTagArrayBuilder {
|
||||
): Pair<String, Array<Array<String>>> {
|
||||
val privateTags = current.privateTags(signer) ?: throw SignerExceptions.UnauthorizedDecryptionException()
|
||||
val encryptedTags =
|
||||
PrivateTagsInContent.encryptNip04(
|
||||
PrivateTagsInContent.encryptNip44(
|
||||
privateTags = privateTags.remove(oldTagStartsWith),
|
||||
signer = signer,
|
||||
)
|
||||
@@ -174,7 +174,7 @@ class PrivateTagArrayBuilder {
|
||||
signer: NostrSigner,
|
||||
): Pair<String, Array<Array<String>>> {
|
||||
val encryptedTags =
|
||||
PrivateTagsInContent.encryptNip04(
|
||||
PrivateTagsInContent.encryptNip44(
|
||||
privateTags = arrayOf(newTag),
|
||||
signer = signer,
|
||||
)
|
||||
|
||||
+2
-2
@@ -154,7 +154,7 @@ class BookmarkListEvent(
|
||||
signer: NostrSigner,
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
) = resign(
|
||||
content = PrivateTagsInContent.encryptNip04(privateTags, signer),
|
||||
content = PrivateTagsInContent.encryptNip44(privateTags, signer),
|
||||
tags = tags,
|
||||
signer = signer,
|
||||
createdAt = createdAt,
|
||||
@@ -198,7 +198,7 @@ class BookmarkListEvent(
|
||||
initializer: TagArrayBuilder<BookmarkListEvent>.() -> Unit = {},
|
||||
) = eventTemplate(
|
||||
kind = KIND,
|
||||
description = PrivateTagsInContent.encryptNip04(privateBookmarks.map { it.toTagArray() }.toTypedArray(), signer),
|
||||
description = PrivateTagsInContent.encryptNip44(privateBookmarks.map { it.toTagArray() }.toTypedArray(), signer),
|
||||
createdAt = createdAt,
|
||||
) {
|
||||
dTag(dTag)
|
||||
|
||||
-18
@@ -48,15 +48,6 @@ class PrivateTagsInContent {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun encryptNip04(
|
||||
privateTags: Array<Array<String>>? = null,
|
||||
signer: NostrSigner,
|
||||
): String =
|
||||
signer.nip04Encrypt(
|
||||
if (privateTags.isNullOrEmpty()) "" else encode(privateTags),
|
||||
signer.pubKey,
|
||||
)
|
||||
|
||||
suspend fun encryptNip44(
|
||||
privateTags: Array<Array<String>>? = null,
|
||||
signer: NostrSigner,
|
||||
@@ -74,15 +65,6 @@ class PrivateTagsInContent {
|
||||
return decode(json)
|
||||
}
|
||||
|
||||
fun encryptNip04(
|
||||
privateTags: Array<Array<String>>? = null,
|
||||
signer: NostrSignerSync,
|
||||
): String =
|
||||
signer.nip04Encrypt(
|
||||
if (privateTags.isNullOrEmpty()) "" else encode(privateTags),
|
||||
signer.pubKey,
|
||||
)
|
||||
|
||||
fun encryptNip44(
|
||||
privateTags: Array<Array<String>>? = null,
|
||||
signer: NostrSignerSync,
|
||||
|
||||
+2
-2
@@ -157,7 +157,7 @@ class GeohashListEvent(
|
||||
signer: NostrSigner,
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
) = resign(
|
||||
content = PrivateTagsInContent.encryptNip04(privateTags, signer),
|
||||
content = PrivateTagsInContent.encryptNip44(privateTags, signer),
|
||||
tags = tags,
|
||||
signer = signer,
|
||||
createdAt = createdAt,
|
||||
@@ -208,7 +208,7 @@ class GeohashListEvent(
|
||||
initializer: TagArrayBuilder<GeohashListEvent>.() -> Unit = {},
|
||||
) = eventTemplate<GeohashListEvent>(
|
||||
kind = KIND,
|
||||
description = PrivateTagsInContent.encryptNip04(privateGeohashes.map { GeoHashTag.assembleSingle(it) }.toTypedArray(), signer),
|
||||
description = PrivateTagsInContent.encryptNip44(privateGeohashes.map { GeoHashTag.assembleSingle(it) }.toTypedArray(), signer),
|
||||
createdAt = createdAt,
|
||||
) {
|
||||
alt(ALT)
|
||||
|
||||
+2
-2
@@ -154,7 +154,7 @@ class HashtagListEvent(
|
||||
signer: NostrSigner,
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
) = resign(
|
||||
content = PrivateTagsInContent.encryptNip04(privateTags, signer),
|
||||
content = PrivateTagsInContent.encryptNip44(privateTags, signer),
|
||||
tags = tags,
|
||||
signer = signer,
|
||||
createdAt = createdAt,
|
||||
@@ -205,7 +205,7 @@ class HashtagListEvent(
|
||||
initializer: TagArrayBuilder<HashtagListEvent>.() -> Unit = {},
|
||||
) = eventTemplate<HashtagListEvent>(
|
||||
kind = KIND,
|
||||
description = PrivateTagsInContent.encryptNip04(privateHashtags.map { HashtagTag.assemble(it) }.toTypedArray(), signer),
|
||||
description = PrivateTagsInContent.encryptNip44(privateHashtags.map { HashtagTag.assemble(it) }.toTypedArray(), signer),
|
||||
createdAt = createdAt,
|
||||
) {
|
||||
alt(ALT)
|
||||
|
||||
@@ -139,7 +139,7 @@ class MuteListEvent(
|
||||
signer: NostrSigner,
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
) = resign(
|
||||
content = PrivateTagsInContent.encryptNip04(privateTags, signer),
|
||||
content = PrivateTagsInContent.encryptNip44(privateTags, signer),
|
||||
tags = publicTags,
|
||||
signer = signer,
|
||||
createdAt = createdAt,
|
||||
@@ -179,7 +179,7 @@ class MuteListEvent(
|
||||
initializer: TagArrayBuilder<MuteListEvent>.() -> Unit = {},
|
||||
) = eventTemplate<MuteListEvent>(
|
||||
kind = KIND,
|
||||
description = PrivateTagsInContent.encryptNip04(privateMutes.map { it.toTagArray() }.toTypedArray(), signer),
|
||||
description = PrivateTagsInContent.encryptNip44(privateMutes.map { it.toTagArray() }.toTypedArray(), signer),
|
||||
createdAt = createdAt,
|
||||
) {
|
||||
alt(ALT)
|
||||
|
||||
+2
-2
@@ -161,7 +161,7 @@ class PeopleListEvent(
|
||||
signer: NostrSigner,
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
) = resign(
|
||||
content = PrivateTagsInContent.encryptNip04(privateTags, signer),
|
||||
content = PrivateTagsInContent.encryptNip44(privateTags, signer),
|
||||
tags = publicTags,
|
||||
signer = signer,
|
||||
createdAt = createdAt,
|
||||
@@ -205,7 +205,7 @@ class PeopleListEvent(
|
||||
initializer: TagArrayBuilder<PeopleListEvent>.() -> Unit = {},
|
||||
) = eventTemplate<PeopleListEvent>(
|
||||
kind = KIND,
|
||||
description = PrivateTagsInContent.encryptNip04(privatePeople.map { it.toTagArray() }.toTypedArray(), signer),
|
||||
description = PrivateTagsInContent.encryptNip44(privatePeople.map { it.toTagArray() }.toTypedArray(), signer),
|
||||
createdAt = createdAt,
|
||||
) {
|
||||
dTag(dTag)
|
||||
|
||||
+1
-1
@@ -108,7 +108,7 @@ class BlockedRelayListEvent(
|
||||
initializer: TagArrayBuilder<BlockedRelayListEvent>.() -> Unit = {},
|
||||
) = eventTemplate<BlockedRelayListEvent>(
|
||||
kind = KIND,
|
||||
description = PrivateTagsInContent.encryptNip04(privateRelays.map { RelayTag.assemble(it) }.toTypedArray(), signer),
|
||||
description = PrivateTagsInContent.encryptNip44(privateRelays.map { RelayTag.assemble(it) }.toTypedArray(), signer),
|
||||
createdAt = createdAt,
|
||||
) {
|
||||
alt(ALT)
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* 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.nip71Video
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
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.dTags.dTag
|
||||
import com.vitorpamplona.quartz.nip22Comments.RootScope
|
||||
import com.vitorpamplona.quartz.nip31Alts.alt
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import java.util.UUID
|
||||
|
||||
@Immutable
|
||||
class VideoNormalEvent(
|
||||
id: HexKey,
|
||||
pubKey: HexKey,
|
||||
createdAt: Long,
|
||||
tags: Array<Array<String>>,
|
||||
content: String,
|
||||
sig: HexKey,
|
||||
) : VideoEvent(id, pubKey, createdAt, KIND, tags, content, sig),
|
||||
RootScope {
|
||||
companion object {
|
||||
const val KIND = 21
|
||||
const val ALT_DESCRIPTION = "Horizontal Video"
|
||||
|
||||
fun build(
|
||||
video: VideoMeta,
|
||||
description: String,
|
||||
dTag: String = UUID.randomUUID().toString(),
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
initializer: TagArrayBuilder<VideoNormalEvent>.() -> Unit = {},
|
||||
) = build(description, dTag, createdAt) {
|
||||
videoIMeta(video)
|
||||
initializer()
|
||||
}
|
||||
|
||||
fun build(
|
||||
video: List<VideoMeta>,
|
||||
description: String,
|
||||
dTag: String = UUID.randomUUID().toString(),
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
initializer: TagArrayBuilder<VideoNormalEvent>.() -> Unit = {},
|
||||
) = build(description, dTag, createdAt) {
|
||||
videoIMetas(video)
|
||||
initializer()
|
||||
}
|
||||
|
||||
fun build(
|
||||
description: String,
|
||||
dTag: String = UUID.randomUUID().toString(),
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
initializer: TagArrayBuilder<VideoNormalEvent>.() -> Unit = {},
|
||||
) = eventTemplate(KIND, description, createdAt) {
|
||||
dTag(dTag)
|
||||
alt(ALT_DESCRIPTION)
|
||||
initializer()
|
||||
}
|
||||
}
|
||||
}
|
||||
+37
-22
@@ -18,37 +18,52 @@
|
||||
* 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.nip37Drafts
|
||||
package com.vitorpamplona.quartz.nip71Video
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
||||
import androidx.compose.runtime.Immutable
|
||||
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.dTags.dTag
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.kinds.kind
|
||||
import com.vitorpamplona.quartz.nip22Comments.RootScope
|
||||
import com.vitorpamplona.quartz.nip31Alts.alt
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import java.util.UUID
|
||||
|
||||
class DraftBuilder {
|
||||
@Immutable
|
||||
class VideoShortEvent(
|
||||
id: HexKey,
|
||||
pubKey: HexKey,
|
||||
createdAt: Long,
|
||||
tags: Array<Array<String>>,
|
||||
content: String,
|
||||
sig: HexKey,
|
||||
) : VideoEvent(id, pubKey, createdAt, KIND, tags, content, sig),
|
||||
RootScope {
|
||||
companion object {
|
||||
suspend fun <T : Event> encryptAndSign(
|
||||
dTag: String,
|
||||
draft: T,
|
||||
signer: NostrSigner,
|
||||
const val KIND = 22
|
||||
const val ALT_DESCRIPTION = "Vertical Video"
|
||||
|
||||
fun build(
|
||||
video: VideoMeta,
|
||||
description: String,
|
||||
dTag: String = UUID.randomUUID().toString(),
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
): DraftWrapEvent {
|
||||
val encryptedContent = signer.nip44Encrypt(draft.toJson(), signer.pubKey)
|
||||
val template =
|
||||
eventTemplate<DraftWrapEvent>(DraftWrapEvent.KIND, encryptedContent, createdAt) {
|
||||
alt(DraftWrapEvent.ALT_DESCRIPTION)
|
||||
dTag(dTag)
|
||||
kind(draft.kind)
|
||||
initializer: TagArrayBuilder<VideoShortEvent>.() -> Unit = {},
|
||||
) = build(description, dTag, createdAt) {
|
||||
videoIMeta(video)
|
||||
initializer()
|
||||
}
|
||||
|
||||
if (draft is ExposeInDraft) {
|
||||
addAll(draft.exposeInDraft())
|
||||
}
|
||||
}
|
||||
|
||||
return signer.sign(template)
|
||||
fun build(
|
||||
description: String,
|
||||
dTag: String = UUID.randomUUID().toString(),
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
initializer: TagArrayBuilder<VideoShortEvent>.() -> Unit = {},
|
||||
) = eventTemplate(KIND, description, createdAt) {
|
||||
dTag(dTag)
|
||||
alt(ALT_DESCRIPTION)
|
||||
initializer()
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -178,7 +178,7 @@ class CommunityListEvent(
|
||||
signer: NostrSigner,
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
) = resign(
|
||||
content = PrivateTagsInContent.encryptNip04(privateTags, signer),
|
||||
content = PrivateTagsInContent.encryptNip44(privateTags, signer),
|
||||
tags = tags,
|
||||
signer = signer,
|
||||
createdAt = createdAt,
|
||||
@@ -218,7 +218,7 @@ class CommunityListEvent(
|
||||
initializer: TagArrayBuilder<CommunityListEvent>.() -> Unit = {},
|
||||
) = eventTemplate<CommunityListEvent>(
|
||||
kind = KIND,
|
||||
description = PrivateTagsInContent.encryptNip04(privateCommunities.map { it.toTagArray() }.toTypedArray(), signer),
|
||||
description = PrivateTagsInContent.encryptNip44(privateCommunities.map { it.toTagArray() }.toTypedArray(), signer),
|
||||
createdAt = createdAt,
|
||||
) {
|
||||
alt(ALT)
|
||||
|
||||
@@ -29,7 +29,7 @@ data class AudioMeta(
|
||||
val mimeType: String? = null,
|
||||
val hash: String? = null,
|
||||
val duration: Int? = null,
|
||||
val waveform: List<Int>? = null,
|
||||
val waveform: List<Float>? = null,
|
||||
) {
|
||||
fun toIMetaArray(): Array<String> =
|
||||
IMetaTagBuilder(url)
|
||||
@@ -37,7 +37,7 @@ data class AudioMeta(
|
||||
mimeType?.let { mimeType(it) }
|
||||
hash?.let { hash(it) }
|
||||
duration?.let { duration(it) }
|
||||
waveform?.let { waveform(it) }
|
||||
waveform?.let { waveformFloat(it) }
|
||||
}.build()
|
||||
.toTagArray()
|
||||
|
||||
|
||||
+3
-1
@@ -34,6 +34,8 @@ fun IMetaTagBuilder.hash(hash: HexKey) = add(HashSha256Tag.TAG_NAME, hash)
|
||||
|
||||
fun IMetaTagBuilder.duration(size: Int) = add(DurationTag.TAG_NAME, size.toString())
|
||||
|
||||
fun IMetaTagBuilder.waveform(wave: List<Int>) = add(WaveformTag.TAG_NAME, WaveformTag.assembleWave(wave))
|
||||
fun IMetaTagBuilder.waveformInt(wave: List<Int>) = add(WaveformTag.TAG_NAME, WaveformTag.assembleWaveInt(wave))
|
||||
|
||||
fun IMetaTagBuilder.waveformFloat(wave: List<Float>) = add(WaveformTag.TAG_NAME, WaveformTag.assembleWaveFloat(wave))
|
||||
|
||||
fun IMetaTagBuilder.mimeType(mime: String) = add(MimeTypeTag.TAG_NAME, mime)
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ fun <T : BaseVoiceEvent> TagArrayBuilder<T>.audioIMeta(
|
||||
mimeType: String? = null,
|
||||
hash: String? = null,
|
||||
duration: Int? = null,
|
||||
waveform: List<Int>? = null,
|
||||
waveform: List<Float>? = null,
|
||||
) = audioIMeta(AudioMeta(url, mimeType, hash, duration, waveform))
|
||||
|
||||
fun <T : BaseVoiceEvent> TagArrayBuilder<T>.audioIMeta(imeta: AudioMeta): TagArrayBuilder<T> {
|
||||
|
||||
@@ -43,7 +43,7 @@ class VoiceEvent(
|
||||
mimeType: String?,
|
||||
hash: String,
|
||||
duration: Int,
|
||||
waveform: List<Int>,
|
||||
waveform: List<Float>,
|
||||
) = build(AudioMeta(url, mimeType, hash, duration, waveform))
|
||||
|
||||
fun build(
|
||||
|
||||
@@ -64,7 +64,7 @@ class VoiceReplyEvent(
|
||||
mimeType: String?,
|
||||
hash: String,
|
||||
duration: Int,
|
||||
waveform: List<Int>,
|
||||
waveform: List<Float>,
|
||||
replyingTo: EventHintBundle<VoiceEvent>,
|
||||
) = build(AudioMeta(url, mimeType, hash, duration, waveform), replyingTo)
|
||||
|
||||
|
||||
+15
-8
@@ -22,11 +22,12 @@ package com.vitorpamplona.quartz.nipA0VoiceMessages.tags
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.has
|
||||
import com.vitorpamplona.quartz.utils.ensure
|
||||
import kotlin.collections.joinToString
|
||||
|
||||
class WaveformTag(
|
||||
val wave: List<Int>,
|
||||
val wave: List<Float>,
|
||||
) {
|
||||
fun toTagArray() = assemble(wave)
|
||||
fun toTagArray() = assembleFloat(wave)
|
||||
|
||||
companion object {
|
||||
const val TAG_NAME = "waveform"
|
||||
@@ -35,26 +36,32 @@ class WaveformTag(
|
||||
fun parse(tag: Array<String>): WaveformTag? = parseWave(tag)?.let { WaveformTag(it) }
|
||||
|
||||
@JvmStatic
|
||||
fun parseWave(tag: Array<String>): List<Int>? {
|
||||
fun parseWave(tag: Array<String>): List<Float>? {
|
||||
ensure(tag.has(1)) { return null }
|
||||
ensure(tag[0] == TAG_NAME) { return null }
|
||||
ensure(tag[1].isNotEmpty()) { return null }
|
||||
|
||||
val wave = tag[1].split(" ").mapNotNull { it.toIntOrNull() }
|
||||
val wave = tag[1].split(" ").mapNotNull { it.toFloatOrNull() }
|
||||
if (wave.isEmpty()) return null
|
||||
return wave
|
||||
}
|
||||
|
||||
fun parseWave(wave: String): List<Int>? {
|
||||
val wave = wave.split(" ").mapNotNull { it.toIntOrNull() }
|
||||
fun parseWave(wave: String): List<Float>? {
|
||||
val wave = wave.split(" ").mapNotNull { it.toFloatOrNull() }
|
||||
if (wave.isEmpty()) return null
|
||||
return wave
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun assembleWave(wave: List<Int>) = wave.joinToString(" ")
|
||||
fun assembleWaveInt(wave: List<Int>) = wave.joinToString(" ")
|
||||
|
||||
@JvmStatic
|
||||
fun assemble(wave: List<Int>) = arrayOf(TAG_NAME, assembleWave(wave))
|
||||
fun assembleInt(wave: List<Int>) = arrayOf(TAG_NAME, assembleWaveInt(wave))
|
||||
|
||||
@JvmStatic
|
||||
fun assembleWaveFloat(wave: List<Float>) = wave.joinToString(" ")
|
||||
|
||||
@JvmStatic
|
||||
fun assembleFloat(wave: List<Float>) = arrayOf(TAG_NAME, assembleWaveFloat(wave))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,18 +31,24 @@ class LargeSoftCache<K, V> : CacheOperations<K, V> {
|
||||
|
||||
fun get(key: K): V? {
|
||||
val softRef = cache.get(key)
|
||||
val value = softRef?.get()
|
||||
if (softRef == null) return null
|
||||
val value = softRef.get()
|
||||
|
||||
return if (value != null) {
|
||||
value
|
||||
} else {
|
||||
cache.remove(key)
|
||||
cache.remove(key, softRef)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun remove(key: K) = cache.remove(key)
|
||||
|
||||
fun removeIf(
|
||||
key: K,
|
||||
value: WeakReference<V>,
|
||||
) = cache.remove(key, value)
|
||||
|
||||
override fun size() = cache.size
|
||||
|
||||
fun isEmpty() = cache.isEmpty()
|
||||
@@ -79,13 +85,21 @@ class LargeSoftCache<K, V> : CacheOperations<K, V> {
|
||||
builder: (key: K) -> V,
|
||||
): V {
|
||||
val softRef = cache[key]
|
||||
val value = softRef?.get()
|
||||
|
||||
return if (value != null) {
|
||||
value
|
||||
} else {
|
||||
return if (softRef == null) {
|
||||
val newObject = builder(key)
|
||||
cache.putIfAbsent(key, WeakReference(newObject))?.get() ?: newObject
|
||||
} else {
|
||||
val value = softRef.get()
|
||||
if (value != null) {
|
||||
value
|
||||
} else {
|
||||
// removes first to make sure the putIfAbsent works.
|
||||
// another thread may put in between
|
||||
cache.remove(key, softRef)
|
||||
val newObject = builder(key)
|
||||
return cache.putIfAbsent(key, WeakReference(newObject))?.get() ?: newObject
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,14 +109,14 @@ class LargeSoftCache<K, V> : CacheOperations<K, V> {
|
||||
* this method can be called periodically or when memory pressure is high.
|
||||
*/
|
||||
fun cleanUp() {
|
||||
val keysToRemove = mutableListOf<K>()
|
||||
forEach { key, softRef ->
|
||||
if (softRef == null) {
|
||||
keysToRemove.add(key)
|
||||
val keysToRemove = mutableMapOf<K, WeakReference<V>>()
|
||||
cache.forEach { key, softRef ->
|
||||
if (softRef.get() == null) {
|
||||
keysToRemove.put(key, softRef)
|
||||
}
|
||||
}
|
||||
keysToRemove.forEach { key ->
|
||||
cache.remove(key)
|
||||
keysToRemove.forEach { key, value ->
|
||||
cache.remove(key, value)
|
||||
println("Cleaned up entry for key: $key (object was garbage collected)")
|
||||
}
|
||||
println("Cache cleanup completed. Remaining size: ${cache.size}")
|
||||
@@ -122,7 +136,7 @@ class LargeSoftCache<K, V> : CacheOperations<K, V> {
|
||||
) {
|
||||
val value = ref.get()
|
||||
if (value == null) {
|
||||
cache.remove(k)
|
||||
cache.removeIf(k, ref)
|
||||
} else {
|
||||
inner.accept(k, value)
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ object TimeUtils {
|
||||
const val ONE_HOUR = 60 * ONE_MINUTE
|
||||
const val EIGHT_HOURS = 8 * ONE_HOUR
|
||||
const val ONE_DAY = 24 * ONE_HOUR
|
||||
const val NINETY_DAYS = 90 * ONE_DAY
|
||||
const val ONE_WEEK = 7 * ONE_DAY
|
||||
const val ONE_MONTH = 30 * ONE_DAY
|
||||
const val ONE_YEAR = 365 * ONE_DAY
|
||||
@@ -61,4 +62,6 @@ object TimeUtils {
|
||||
fun oneMonthAgo() = now() - ONE_MONTH
|
||||
|
||||
fun randomWithTwoDays() = now() - RandomInstance.int(twoDays())
|
||||
|
||||
fun ninetyDaysFromNow() = now() + NINETY_DAYS
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user