Correctly downloads Nest events

This commit is contained in:
Vitor Pamplona
2026-04-27 19:23:52 -04:00
parent 3c3e327bcb
commit a50a360964
12 changed files with 593 additions and 13 deletions
@@ -22,7 +22,6 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip53LiveActiviti
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.allcommunities.AllCommunitiesTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip90DVMs.subassemblies.filterContentDVMsAllCommunities
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
@@ -78,7 +77,7 @@ fun filterLiveActivitiesByAllCommunities(
return communitySet.set
.mapNotNull {
filterContentDVMsAllCommunities(
filterLiveActivitiesAllCommunities(
relay = it.key,
communities = it.value.communities,
since = since?.get(it.key)?.time ?: defaultSince,
@@ -47,7 +47,6 @@ import com.vitorpamplona.quartz.nip53LiveActivities.presence.MeetingRoomPresence
@Stable
class NestRoomQueryState(
val roomATag: String,
val localPubkey: String,
val account: Account,
)
@@ -102,7 +101,7 @@ class NestRoomFilterSubAssembler(
filter =
Filter(
kinds = listOf(AdminCommandEvent.KIND),
tags = mapOf("a" to listOf(key.roomATag), "p" to listOf(key.localPubkey)),
tags = mapOf("a" to listOf(key.roomATag), "p" to listOf(key.account.pubKey)),
since = since?.get(relay)?.time,
),
),
@@ -110,7 +109,7 @@ class NestRoomFilterSubAssembler(
}
}
override fun id(key: NestRoomQueryState): String = "${key.roomATag}|${key.localPubkey}"
override fun id(key: NestRoomQueryState): String = key.roomATag
}
@Stable
@@ -137,11 +136,9 @@ class NestRoomFilterAssembler(
@Composable
fun NestRoomFilterAssemblerSubscription(
roomATag: String,
localPubkey: String,
accountViewModel: AccountViewModel,
) = NestRoomFilterAssemblerSubscription(
roomATag,
localPubkey,
accountViewModel.account,
accountViewModel.dataSources().nestRoom,
)
@@ -149,13 +146,12 @@ fun NestRoomFilterAssemblerSubscription(
@Composable
fun NestRoomFilterAssemblerSubscription(
roomATag: String,
localPubkey: String,
account: Account,
filterAssembler: NestRoomFilterAssembler,
) {
val state =
remember(roomATag, localPubkey) {
NestRoomQueryState(roomATag, localPubkey, account)
remember(roomATag, account.pubKey) {
NestRoomQueryState(roomATag, account)
}
KeyDataSourceSubscription(state, filterAssembler)
}
@@ -24,7 +24,6 @@ import com.vitorpamplona.amethyst.model.TopFilter
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUserAndFollowListEoseManager
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip53LiveActivities.makeLiveActivitiesFilter
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
import com.vitorpamplona.quartz.nip01Core.relay.client.subscriptions.Subscription
@@ -50,7 +49,7 @@ class NestsSubAssembler(
since: SincePerRelayMap?,
): List<RelayBasedFilter> {
val feedSettings = key.followsPerRelay()
return makeLiveActivitiesFilter(feedSettings, since, key.feedStates.nestsFeed.lastNoteCreatedAtIfFilled())
return makeNestsFilter(feedSettings, since, key.feedStates.nestsFeed.lastNoteCreatedAtIfFilled())
}
override fun user(key: NestsQueryState) = key.account.userProfile()
@@ -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.amethyst.ui.screen.loggedIn.nests.datasource
import com.vitorpamplona.amethyst.model.topNavFeeds.IFeedTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.aroundMe.LocationTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.global.GlobalTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.hashtag.HashtagTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.allcommunities.AllCommunitiesTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.community.SingleCommunityTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.datasource.subassemblies.filterNestsByAllCommunities
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.datasource.subassemblies.filterNestsByAuthors
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.datasource.subassemblies.filterNestsByCommunity
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.datasource.subassemblies.filterNestsByFollows
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.datasource.subassemblies.filterNestsByGeohash
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.datasource.subassemblies.filterNestsByHashtag
import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.datasource.subassemblies.filterNestsGlobal
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
fun makeNestsFilter(
feedSettings: IFeedTopNavPerRelayFilterSet,
since: SincePerRelayMap?,
defaultSince: Long? = null,
): List<RelayBasedFilter> =
when (feedSettings) {
is AllCommunitiesTopNavPerRelayFilterSet -> filterNestsByAllCommunities(feedSettings, since, defaultSince)
is AllFollowsTopNavPerRelayFilterSet -> filterNestsByFollows(feedSettings, since, defaultSince)
is AuthorsTopNavPerRelayFilterSet -> filterNestsByAuthors(feedSettings, since, defaultSince)
is GlobalTopNavPerRelayFilterSet -> filterNestsGlobal(feedSettings, since, defaultSince)
is HashtagTopNavPerRelayFilterSet -> filterNestsByHashtag(feedSettings, since, defaultSince)
is LocationTopNavPerRelayFilterSet -> filterNestsByGeohash(feedSettings, since, defaultSince)
is MutedAuthorsTopNavPerRelayFilterSet -> filterNestsByAuthors(feedSettings, since, defaultSince)
is SingleCommunityTopNavPerRelayFilterSet -> filterNestsByCommunity(feedSettings, since, defaultSince)
else -> emptyList()
}
@@ -0,0 +1,85 @@
/*
* 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.amethyst.ui.screen.loggedIn.nests.datasource.subassemblies
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.allcommunities.AllCommunitiesTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingRoomEvent
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingSpaceEvent
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
import com.vitorpamplona.quartz.nip72ModCommunities.approval.CommunityPostApprovalEvent
fun filterNestsAllCommunities(
relay: NormalizedRelayUrl,
communities: Set<String>,
since: Long? = null,
): List<RelayBasedFilter> {
val communityList = communities.sorted()
return listOf(
// approved
RelayBasedFilter(
relay = relay,
filter =
Filter(
kinds = CommunityPostApprovalEvent.KIND_LIST,
tags =
mapOf(
"a" to communityList,
"k" to listOf(LiveActivitiesEvent.KIND.toString(), MeetingSpaceEvent.KIND.toString(), MeetingRoomEvent.KIND.toString()),
),
limit = 300,
since = since,
),
),
// not approved
RelayBasedFilter(
relay = relay,
filter =
Filter(
tags = mapOf("k" to listOf("5300"), "a" to communityList),
kinds = listOf(MeetingSpaceEvent.KIND, MeetingRoomEvent.KIND),
limit = 300,
since = since,
),
),
)
}
fun filterNestsByAllCommunities(
communitySet: AllCommunitiesTopNavPerRelayFilterSet,
since: SincePerRelayMap?,
defaultSince: Long? = null,
): List<RelayBasedFilter> {
if (communitySet.set.isEmpty()) return emptyList()
return communitySet.set
.mapNotNull {
filterNestsAllCommunities(
relay = it.key,
communities = it.value.communities,
since = since?.get(it.key)?.time ?: defaultSince,
)
}.flatten()
}
@@ -0,0 +1,104 @@
/*
* 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.amethyst.ui.screen.loggedIn.nests.datasource.subassemblies
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingRoomEvent
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingSpaceEvent
fun filterNestsAuthors(
relay: NormalizedRelayUrl,
authors: Set<HexKey>,
since: Long? = null,
): List<RelayBasedFilter> {
val authorList = authors.sorted()
return listOf(
RelayBasedFilter(
relay = relay,
filter =
Filter(
authors = authorList,
kinds = listOf(MeetingSpaceEvent.KIND, MeetingRoomEvent.KIND),
limit = 300,
since = since,
),
),
// authors are participating in the live event.
RelayBasedFilter(
relay = relay,
filter =
Filter(
tags = mapOf("p" to authorList),
kinds = listOf(MeetingSpaceEvent.KIND, MeetingRoomEvent.KIND),
limit = 100,
since = since,
),
),
)
}
fun filterNestsByAuthors(
authorSet: AuthorsTopNavPerRelayFilterSet,
since: SincePerRelayMap?,
defaultSince: Long? = null,
): List<RelayBasedFilter> {
if (authorSet.set.isEmpty()) return emptyList()
return authorSet.set
.mapNotNull {
if (it.value.authors.isEmpty()) {
null
} else {
filterNestsAuthors(
relay = it.key,
authors = it.value.authors,
since = since?.get(it.key)?.time ?: defaultSince,
)
}
}.flatten()
}
fun filterNestsByAuthors(
authorSet: MutedAuthorsTopNavPerRelayFilterSet,
since: SincePerRelayMap?,
defaultSince: Long? = null,
): List<RelayBasedFilter> {
if (authorSet.set.isEmpty()) return emptyList()
return authorSet.set
.mapNotNull {
if (it.value.authors.isEmpty()) {
null
} else {
filterNestsAuthors(
relay = it.key,
authors = it.value.authors,
since = since?.get(it.key)?.time ?: defaultSince,
)
}
}.flatten()
}
@@ -0,0 +1,87 @@
/*
* 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.amethyst.ui.screen.loggedIn.nests.datasource.subassemblies
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.community.SingleCommunityTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingRoomEvent
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingSpaceEvent
import com.vitorpamplona.quartz.nip72ModCommunities.approval.CommunityPostApprovalEvent
fun filterNestsByCommunity(
relay: NormalizedRelayUrl,
community: String,
authors: Set<String>?,
since: Long? = null,
): List<RelayBasedFilter> {
val authors = authors?.sorted()
return listOf(
// approved
RelayBasedFilter(
relay = relay,
filter =
Filter(
authors = authors,
kinds = CommunityPostApprovalEvent.KIND_LIST,
tags =
mapOf(
"a" to listOf(community),
"k" to listOf(MeetingSpaceEvent.KIND.toString(), MeetingRoomEvent.KIND.toString()),
),
limit = 300,
since = since,
),
),
// not approved
RelayBasedFilter(
relay = relay,
filter =
Filter(
authors = authors,
tags = mapOf("k" to listOf("5300"), "a" to listOf(community)),
kinds = listOf(MeetingSpaceEvent.KIND, MeetingRoomEvent.KIND),
limit = 300,
since = since,
),
),
)
}
fun filterNestsByCommunity(
communitySet: SingleCommunityTopNavPerRelayFilterSet,
since: SincePerRelayMap?,
defaultSince: Long? = null,
): List<RelayBasedFilter> {
if (communitySet.set.isEmpty()) return emptyList()
return communitySet.set
.mapNotNull {
filterNestsByCommunity(
relay = it.key,
community = it.value.community,
authors = it.value.authors,
since = since?.get(it.key)?.time ?: defaultSince,
)
}.flatten()
}
@@ -0,0 +1,53 @@
/*
* 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.amethyst.ui.screen.loggedIn.nests.datasource.subassemblies
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
fun filterNestsByFollows(
followsSet: AllFollowsTopNavPerRelayFilterSet,
since: SincePerRelayMap?,
defaultSince: Long? = null,
): List<RelayBasedFilter> {
if (followsSet.set.isEmpty()) return emptyList()
return followsSet.set.flatMap {
val since = since?.get(it.key)?.time ?: defaultSince
val relay = it.key
listOfNotNull(
it.value.authors?.let {
filterNestsAuthors(relay, it, since)
},
it.value.geotags?.let {
filterNestsByGeohash(relay, it, since)
},
it.value.hashtags?.let {
filterNestsByHashtag(relay, it, since)
},
it.value.communities?.let {
filterNestsAllCommunities(relay, it, since)
},
).flatten()
}
}
@@ -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.amethyst.ui.screen.loggedIn.nests.datasource.subassemblies
import com.vitorpamplona.amethyst.model.topNavFeeds.aroundMe.LocationTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingRoomEvent
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingSpaceEvent
fun filterNestsByGeohash(
relay: NormalizedRelayUrl,
geotags: Set<String>,
since: Long? = null,
): List<RelayBasedFilter> {
if (geotags.isEmpty()) return emptyList()
val geoHashes = geotags.sorted()
return listOf(
RelayBasedFilter(
relay = relay,
filter =
Filter(
kinds = listOf(MeetingSpaceEvent.KIND, MeetingRoomEvent.KIND),
tags = mapOf("g" to geoHashes),
limit = 300,
since = since,
),
),
)
}
fun filterNestsByGeohash(
geoSet: LocationTopNavPerRelayFilterSet,
since: SincePerRelayMap?,
defaultSince: Long? = null,
): List<RelayBasedFilter> {
if (geoSet.set.isEmpty()) return emptyList()
return geoSet.set
.mapNotNull {
if (it.value.geotags.isEmpty()) {
null
} else {
filterNestsByGeohash(
relay = it.key,
geotags = it.value.geotags,
since = since?.get(it.key)?.time ?: defaultSince,
)
}
}.flatten()
}
@@ -0,0 +1,74 @@
/*
* 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.amethyst.ui.screen.loggedIn.nests.datasource.subassemblies
import com.vitorpamplona.amethyst.model.topNavFeeds.hashtag.HashtagTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hashtagAlts
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingRoomEvent
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingSpaceEvent
fun filterNestsByHashtag(
relay: NormalizedRelayUrl,
hashtags: Set<String>,
since: Long?,
): List<RelayBasedFilter> {
if (hashtags.isEmpty()) return emptyList()
val hashtags = hashtags.flatMap(::hashtagAlts).distinct().sorted()
return listOf(
RelayBasedFilter(
relay = relay,
filter =
Filter(
kinds = listOf(MeetingSpaceEvent.KIND, MeetingRoomEvent.KIND),
tags = mapOf("t" to hashtags),
limit = 300,
since = since,
),
),
)
}
fun filterNestsByHashtag(
hashSet: HashtagTopNavPerRelayFilterSet,
since: SincePerRelayMap?,
defaultSince: Long? = null,
): List<RelayBasedFilter> {
if (hashSet.set.isEmpty()) return emptyList()
return hashSet.set
.mapNotNull { relayHashSet ->
if (relayHashSet.value.hashtags.isEmpty()) {
null
} else {
filterNestsByHashtag(
relay = relayHashSet.key,
hashtags = relayHashSet.value.hashtags,
since = since?.get(relayHashSet.key)?.time ?: defaultSince,
)
}
}.flatten()
}
@@ -0,0 +1,53 @@
/*
* 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.amethyst.ui.screen.loggedIn.nests.datasource.subassemblies
import com.vitorpamplona.amethyst.model.topNavFeeds.global.GlobalTopNavPerRelayFilterSet
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingRoomEvent
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingSpaceEvent
import com.vitorpamplona.quartz.utils.TimeUtils
fun filterNestsGlobal(
relays: GlobalTopNavPerRelayFilterSet,
since: SincePerRelayMap?,
defaultSince: Long? = null,
): List<RelayBasedFilter> {
if (relays.set.isEmpty()) return emptyList()
return relays.set
.map {
val since = since?.get(it.key)?.time ?: defaultSince
listOf(
RelayBasedFilter(
relay = it.key,
filter =
Filter(
kinds = listOf(MeetingSpaceEvent.KIND, MeetingRoomEvent.KIND),
limit = 300,
since = since ?: TimeUtils.oneWeekAgo(),
),
),
)
}.flatten()
}
@@ -143,7 +143,7 @@ private fun NestActivityBody(
// Single REQ per relay covering chat, presence, reactions, admin
// commands. See NestRoomFilterAssembler for the filter shape.
NestRoomFilterAssemblerSubscription(roomATag, localPubkey, accountViewModel)
NestRoomFilterAssemblerSubscription(roomATag, accountViewModel)
// Read side: LocalCache → VM. Each kind has its own collector
// (different VM hook per kind) plus the two eviction tickers.