diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt index a3e0d81ad..5dbf72f7a 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -39,7 +39,9 @@ import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.amethyst.service.relays.Client import com.vitorpamplona.amethyst.service.relays.Constants import com.vitorpamplona.amethyst.service.relays.FeedType +import com.vitorpamplona.amethyst.service.relays.JsonFilter import com.vitorpamplona.amethyst.service.relays.Relay +import com.vitorpamplona.amethyst.service.relays.TypedFilter import com.vitorpamplona.amethyst.ui.components.BundledUpdate import com.vitorpamplona.quartz.crypto.KeyPair import com.vitorpamplona.quartz.encoders.ATag @@ -856,7 +858,22 @@ class Account( fun broadcast(note: Note) { note.event?.let { if (it is WrappedEvent && it.host != null) { - it.host?.let { hostEvent -> Client.send(hostEvent) } + it.host?.let { + Client.sendFilterAndStopOnFirstResponse( + filters = + listOf( + TypedFilter( + setOf(FeedType.FOLLOWS, FeedType.PRIVATE_DMS, FeedType.GLOBAL), + JsonFilter( + ids = listOf(it.id), + ), + ), + ), + onResponse = { + Client.send(it) + }, + ) + } } else { Client.send(it) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt index d9ceb5441..81eb50a7d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt @@ -1808,7 +1808,7 @@ object LocalCache { // Already processed this event. if (note.event != null) return - note.loadEvent(event, author, emptyList()) + note.loadEvent(event.copyNoContent(), author, emptyList()) refreshObservers(note) } @@ -1828,7 +1828,7 @@ object LocalCache { // Already processed this event. if (note.event != null) return - note.loadEvent(event, author, emptyList()) + note.loadEvent(event.copyNoContent(), author, emptyList()) refreshObservers(note) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt index f7de6ef50..4d511d4e4 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt @@ -152,7 +152,7 @@ open class Note(val idHex: String) { Nip19Bech32.createNEvent( host.id, host.pubKey, - host.kind(), + host.kind, relays.firstOrNull()?.url, ) } else { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt index 63a92b1f0..ee61046db 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt @@ -239,6 +239,14 @@ object NostrAccountDataSource : NostrDataSource("AccountData") { JsonFilter( kinds = listOf(GiftWrapEvent.KIND), tags = mapOf("p" to listOf(account.userProfile().pubkeyHex)), + since = + latestEOSEs.users[account.userProfile()] + ?.followList + ?.get("&&((GIFTWRAPS_EOSE))&&") + ?.relayList + ?.mapValues { + EOSETime(it.value.time - TimeUtils.twoDays()) + }, ), ) @@ -251,6 +259,13 @@ object NostrAccountDataSource : NostrDataSource("AccountData") { relayUrl, time, ) + + latestEOSEs.addOrUpdate( + account.userProfile(), + "&&((GIFTWRAPS_EOSE))&&", + relayUrl, + time, + ) } else { hasLoadedTheBasics[account.userProfile()] = true diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Client.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Client.kt index 8153b49d6..cd6bff1c0 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Client.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Client.kt @@ -100,6 +100,34 @@ object Client : RelayPool.Listener { RelayPool.sendFilter(subscriptionId, filters) } + fun sendFilterAndStopOnFirstResponse( + subscriptionId: String = UUID.randomUUID().toString().substring(0..10), + filters: List = listOf(), + onResponse: (Event) -> Unit, + ) { + checkNotInMainThread() + + subscribe( + object : Listener() { + override fun onEvent( + event: Event, + subId: String, + relay: Relay, + afterEOSE: Boolean, + ) { + if (subId == subscriptionId) { + onResponse(event) + unsubscribe(this) + close(subscriptionId) + } + } + }, + ) + + subscriptions = subscriptions + Pair(subscriptionId, filters) + RelayPool.sendFilter(subscriptionId, filters) + } + fun sendFilterOnlyIfDisconnected( subscriptionId: String = UUID.randomUUID().toString().substring(0..10), filters: List = listOf(), diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/Event.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/Event.kt index aa44265db..1e169bff3 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/Event.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/Event.kt @@ -526,9 +526,15 @@ open class WrappedEvent( content: String, sig: HexKey, ) : Event(id, pubKey, createdAt, kind, tags, content, sig) { - @Transient var host: Event? = null // host event to broadcast when needed + @Transient var host: HostStub? = null // host event to broadcast when needed } +class HostStub( + val id: HexKey, + val pubKey: HexKey, + val kind: Int, +) + @Immutable interface AddressableEvent { fun dTag(): String diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/GiftWrapEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/GiftWrapEvent.kt index 4e6fb06d5..9d4e8950c 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/GiftWrapEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/GiftWrapEvent.kt @@ -38,6 +38,22 @@ class GiftWrapEvent( ) : Event(id, pubKey, createdAt, KIND, tags, content, sig) { @Transient private var cachedInnerEvent: Map = mapOf() + fun copyNoContent(): GiftWrapEvent { + val copy = + GiftWrapEvent( + id, + pubKey, + createdAt, + tags, + "", + sig, + ) + + copy.cachedInnerEvent = cachedInnerEvent + + return copy + } + override fun isContentEncoded() = true fun preCachedGift(signer: NostrSigner): Event? { @@ -61,7 +77,7 @@ class GiftWrapEvent( } unwrap(signer) { gift -> if (gift is WrappedEvent) { - gift.host = this + gift.host = HostStub(this.id, this.pubKey, this.kind) } addToCache(signer.pubKey, gift) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/SealedGossipEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/SealedGossipEvent.kt index cae38d586..b6411f162 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/SealedGossipEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/SealedGossipEvent.kt @@ -39,6 +39,23 @@ class SealedGossipEvent( ) : WrappedEvent(id, pubKey, createdAt, KIND, tags, content, sig) { @Transient private var cachedInnerEvent: Map = mapOf() + fun copyNoContent(): SealedGossipEvent { + val copy = + SealedGossipEvent( + id, + pubKey, + createdAt, + tags, + "", + sig, + ) + + copy.cachedInnerEvent = cachedInnerEvent + copy.host = host + + return copy + } + override fun isContentEncoded() = true fun preCachedGossip(signer: NostrSigner): Event? { @@ -64,7 +81,7 @@ class SealedGossipEvent( unseal(signer) { gossip -> val event = gossip.mergeWith(this) if (event is WrappedEvent) { - event.host = host ?: this + event.host = host ?: HostStub(this.id, this.pubKey, this.kind) } addToCache(signer.pubKey, event) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/utils/TimeUtils.kt b/quartz/src/main/java/com/vitorpamplona/quartz/utils/TimeUtils.kt index 31bf69730..023fc9fb6 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/utils/TimeUtils.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/utils/TimeUtils.kt @@ -51,6 +51,8 @@ object TimeUtils { fun eightHoursAgo() = now() - EIGHT_HOURS + fun twoDays() = ONE_DAY * 2 + fun oneWeekAgo() = now() - ONE_WEEK fun randomWithinAWeek() = System.currentTimeMillis() / 1000 - CryptoUtils.randomInt(ONE_WEEK)