Merge branch 'vitorpamplona:main' into profiles-list-management

This commit is contained in:
KotlinGeekDev
2025-08-20 21:54:12 +00:00
committed by GitHub
86 changed files with 646 additions and 248 deletions
@@ -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)
@@ -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) {
@@ -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))
@@ -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))
}
}
@@ -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) {
@@ -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()
@@ -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())
@@ -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,
)
@@ -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)
@@ -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,
@@ -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)
@@ -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)
@@ -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)
@@ -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()
}
}
}
@@ -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()
}
}
}
@@ -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()
@@ -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)
@@ -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)
@@ -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
}