mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-06 11:48:24 +00:00
- Refactors the entire EOSE caching structure.
- Refactors all Datasource classes into a 3 layer design with composeSubscriptions being registered, passed to a group of EOSE managers that can change eose caching capabilities based on needs, and migrates the old subscription Orchestrator into a simpler controller - Breaks down all Datasource into multiple filter functions - Improves EOSE support for many of the base filters like on NIP-04 DMs, notifications, etc - Moves EOSE cache for public, non-user-dependent items like channels, hashtags, etc to their own caching system. - Improves the Relay speed logger to better track how many events are being received, and check for duplications
This commit is contained in:
@@ -878,15 +878,17 @@ class Account(
|
||||
}
|
||||
|
||||
val liveHomeListAuthorsPerRelay: StateFlow<Map<String, List<HexKey>>?> by lazy {
|
||||
liveHomeListAuthorsPerRelayFlow.flowOn(Dispatchers.Default).stateIn(
|
||||
scope,
|
||||
SharingStarted.Eagerly,
|
||||
authorsPerRelay(
|
||||
liveHomeFollowLists.value?.authorsPlusMe?.map { getNIP65RelayListNote(it) } ?: emptyList(),
|
||||
connectToRelays.value.filter { it.feedTypes.contains(FeedType.FOLLOWS) && it.read }.map { it.url },
|
||||
settings.torSettings.torType.value,
|
||||
).ifEmpty { null },
|
||||
)
|
||||
liveHomeListAuthorsPerRelayFlow
|
||||
.flowOn(Dispatchers.Default)
|
||||
.stateIn(
|
||||
scope,
|
||||
SharingStarted.Eagerly,
|
||||
authorsPerRelay(
|
||||
liveHomeFollowLists.value?.authorsPlusMe?.map { getNIP65RelayListNote(it) } ?: emptyList(),
|
||||
connectToRelays.value.filter { it.feedTypes.contains(FeedType.FOLLOWS) && it.read }.map { it.url },
|
||||
settings.torSettings.torType.value,
|
||||
).ifEmpty { null },
|
||||
)
|
||||
}
|
||||
|
||||
val liveNotificationFollowLists: StateFlow<LiveFollowList?> by lazy {
|
||||
@@ -1677,6 +1679,7 @@ class Account(
|
||||
fun broadcast(note: Note) {
|
||||
note.event?.let {
|
||||
if (it is WrappedEvent && it.host != null) {
|
||||
// download the event and send it.
|
||||
it.host?.let {
|
||||
Amethyst.instance.client.sendFilterAndStopOnFirstResponse(
|
||||
filters =
|
||||
|
||||
@@ -25,12 +25,12 @@ import androidx.compose.runtime.Stable
|
||||
import com.vitorpamplona.amethyst.launchAndWaitAll
|
||||
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||
import com.vitorpamplona.amethyst.service.firstFullCharOrEmoji
|
||||
import com.vitorpamplona.amethyst.service.relays.EOSERelayList
|
||||
import com.vitorpamplona.amethyst.service.replace
|
||||
import com.vitorpamplona.amethyst.tryAndWait
|
||||
import com.vitorpamplona.amethyst.ui.note.toShortenHex
|
||||
import com.vitorpamplona.ammolite.relays.BundledUpdate
|
||||
import com.vitorpamplona.ammolite.relays.RelayBriefInfoCache
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.quartz.experimental.bounties.addedRewardValue
|
||||
import com.vitorpamplona.quartz.experimental.bounties.hasAdditionalReward
|
||||
import com.vitorpamplona.quartz.experimental.ephemChat.chat.EphemeralChatEvent
|
||||
@@ -162,7 +162,7 @@ open class Note(
|
||||
var relays = listOf<RelayBriefInfoCache.RelayBriefInfo>()
|
||||
private set
|
||||
|
||||
var lastReactionsDownloadTime: Map<String, EOSETime> = emptyMap()
|
||||
var lastReactionsDownloadTime: EOSERelayList = EOSERelayList()
|
||||
|
||||
fun id() = Hex.decode(idHex)
|
||||
|
||||
@@ -298,7 +298,7 @@ open class Note(
|
||||
zapPayments = mapOf<Note, Note?>()
|
||||
zapsAmount = BigDecimal.ZERO
|
||||
relays = listOf<RelayBriefInfoCache.RelayBriefInfo>()
|
||||
lastReactionsDownloadTime = emptyMap()
|
||||
lastReactionsDownloadTime.clear()
|
||||
|
||||
if (repliesChanged) flowSet?.replies?.invalidateData()
|
||||
if (reactionsChanged) flowSet?.reactions?.invalidateData()
|
||||
@@ -831,7 +831,7 @@ open class Note(
|
||||
}
|
||||
|
||||
fun clearEOSE() {
|
||||
lastReactionsDownloadTime = emptyMap()
|
||||
lastReactionsDownloadTime.clear()
|
||||
}
|
||||
|
||||
fun isHiddenFor(accountChoices: Account.LiveHiddenUsers): Boolean {
|
||||
|
||||
@@ -57,14 +57,13 @@ class ThreadAssembler {
|
||||
|
||||
// recursive
|
||||
val roots =
|
||||
note.replyTo
|
||||
?.map {
|
||||
if (it !in testedNotes) {
|
||||
searchRoot(it, testedNotes)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}?.filterNotNull()
|
||||
note.replyTo?.mapNotNull {
|
||||
if (it !in testedNotes) {
|
||||
searchRoot(it, testedNotes)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
if (roots != null && roots.isNotEmpty()) {
|
||||
return roots[0]
|
||||
@@ -79,6 +78,18 @@ class ThreadAssembler {
|
||||
val allNotes: ImmutableSet<Note>,
|
||||
)
|
||||
|
||||
fun findRoot(noteId: String): Note? {
|
||||
val note = LocalCache.checkGetOrCreateNote(noteId) ?: return null
|
||||
|
||||
return if (note.event != null) {
|
||||
val thread = OnlyLatestVersionSet()
|
||||
|
||||
searchRoot(note, thread) ?: note
|
||||
} else {
|
||||
note
|
||||
}
|
||||
}
|
||||
|
||||
fun findThreadFor(noteId: String): ThreadInfo? {
|
||||
checkNotInMainThread()
|
||||
|
||||
|
||||
+1
-5
@@ -50,9 +50,5 @@ class CacheClientConnector(
|
||||
private fun markAsSeen(
|
||||
eventId: HexKey,
|
||||
relay: RelayBriefInfoCache.RelayBriefInfo,
|
||||
) {
|
||||
val note = LocalCache.getNoteIfExists(eventId)
|
||||
|
||||
note?.addRelay(relay)
|
||||
}
|
||||
) = LocalCache.getNoteIfExists(eventId)?.addRelay(relay)
|
||||
}
|
||||
|
||||
+5
-5
@@ -22,14 +22,14 @@ package com.vitorpamplona.amethyst.service.relayClient
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.QueryBasedSubscriptionOrchestrator
|
||||
import com.vitorpamplona.amethyst.service.relayClient.searchCommand.MutableQueryBasedSubscriptionOrchestrator
|
||||
import com.vitorpamplona.amethyst.service.relayClient.searchCommand.MutableQueryState
|
||||
import com.vitorpamplona.amethyst.service.relayClient.composeSubscriptionManagers.ComposeSubscriptionManager
|
||||
import com.vitorpamplona.amethyst.service.relayClient.composeSubscriptionManagers.MutableComposeSubscriptionManager
|
||||
import com.vitorpamplona.amethyst.service.relayClient.composeSubscriptionManagers.MutableQueryState
|
||||
|
||||
@Composable
|
||||
fun <T> KeyDataSourceSubscription(
|
||||
state: T,
|
||||
dataSource: QueryBasedSubscriptionOrchestrator<T>,
|
||||
dataSource: ComposeSubscriptionManager<T>,
|
||||
) = DisposableEffect(state) {
|
||||
dataSource.subscribe(state)
|
||||
onDispose {
|
||||
@@ -40,7 +40,7 @@ fun <T> KeyDataSourceSubscription(
|
||||
@Composable
|
||||
fun <T : MutableQueryState> KeyDataSourceSubscription(
|
||||
state: T,
|
||||
dataSource: MutableQueryBasedSubscriptionOrchestrator<T>,
|
||||
dataSource: MutableComposeSubscriptionManager<T>,
|
||||
) = DisposableEffect(state) {
|
||||
dataSource.subscribe(state)
|
||||
onDispose {
|
||||
|
||||
+56
-11
@@ -21,10 +21,63 @@
|
||||
package com.vitorpamplona.amethyst.service.relayClient
|
||||
|
||||
import android.util.Log
|
||||
import com.vitorpamplona.amethyst.service.relayClient.RelaySpeedLogger.Companion.TAG
|
||||
import com.vitorpamplona.ammolite.relays.NostrClient
|
||||
import com.vitorpamplona.ammolite.relays.Relay
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import com.vitorpamplona.quartz.utils.LargeCache
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import kotlin.collections.forEach
|
||||
import kotlin.collections.get
|
||||
import kotlin.concurrent.timer
|
||||
import kotlin.text.get
|
||||
import kotlin.text.set
|
||||
|
||||
class FrameStat {
|
||||
var eventCount = AtomicInteger(0)
|
||||
var kinds = LargeCache<Int, AtomicInteger>()
|
||||
|
||||
fun increment(kind: Int) {
|
||||
eventCount.incrementAndGet()
|
||||
|
||||
val kindCount = kinds.get(kind)
|
||||
if (kindCount != null) {
|
||||
kindCount.incrementAndGet()
|
||||
} else {
|
||||
kinds.put(kind, AtomicInteger(1))
|
||||
}
|
||||
}
|
||||
|
||||
fun hasAnything() = eventCount.get() > 0
|
||||
|
||||
fun reset() {
|
||||
eventCount.set(0)
|
||||
kinds.forEach { key, value ->
|
||||
value.set(0)
|
||||
}
|
||||
}
|
||||
|
||||
fun log() {
|
||||
Log.d(TAG, "Events Per Second: ${eventCount.get()}")
|
||||
kinds.forEach { key, value ->
|
||||
if (value.get() > 0) {
|
||||
Log.d(TAG, "-- Events Per Second Debug: $key $value")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the timer in the constructor. This ensures it starts when the
|
||||
// ResettableCounter object is created.
|
||||
init {
|
||||
// Use a timer to reset the counter every second.
|
||||
timer(name = "EventsPerSecondCounter", period = 1000, daemon = true) {
|
||||
if (hasAnything()) {
|
||||
log()
|
||||
reset()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens to NostrClient's onNotify messages from the relay
|
||||
@@ -36,8 +89,7 @@ class RelaySpeedLogger(
|
||||
val TAG = RelaySpeedLogger::class.java.simpleName
|
||||
}
|
||||
|
||||
var time = TimeUtils.now() / 10
|
||||
var onEventCount: Long = 0L
|
||||
var current = FrameStat()
|
||||
|
||||
private val clientListener =
|
||||
object : NostrClient.Listener {
|
||||
@@ -49,14 +101,7 @@ class RelaySpeedLogger(
|
||||
arrivalTime: Long,
|
||||
afterEOSE: Boolean,
|
||||
) {
|
||||
val now = TimeUtils.now() / 10
|
||||
if (time == now) {
|
||||
onEventCount++
|
||||
} else {
|
||||
Log.d(TAG, "EPS: ${onEventCount / 10} ")
|
||||
time = now
|
||||
onEventCount = 0
|
||||
}
|
||||
current.increment(event.kind)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+15
-22
@@ -18,56 +18,49 @@
|
||||
* 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.service.relayClient.reqCommand
|
||||
package com.vitorpamplona.amethyst.service.relayClient.composeSubscriptionManagers
|
||||
|
||||
import com.vitorpamplona.ammolite.relays.NostrClient
|
||||
import com.vitorpamplona.ammolite.relays.datasources.SubscriptionOrchestrator
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
/**
|
||||
* Creates a data source where the filters are derived from
|
||||
* data (keys) that is being watched in the UI.
|
||||
* This allows composables to directly register their queries
|
||||
* to relays. There may be multiple duplications in these
|
||||
* subscriptions since we do not control when screens are removed.
|
||||
*/
|
||||
abstract class QueryBasedSubscriptionOrchestrator<T>(
|
||||
client: NostrClient,
|
||||
) : SubscriptionOrchestrator(client) {
|
||||
private var queries: ConcurrentHashMap<T, T> = ConcurrentHashMap()
|
||||
abstract class ComposeSubscriptionManager<T> : ComposeSubscriptionManagerControls {
|
||||
private var composeSubscriptions: ConcurrentHashMap<T, T> = ConcurrentHashMap()
|
||||
|
||||
// This is called by main. Keep it really fast.
|
||||
fun subscribe(query: T?) {
|
||||
if (query == null) return
|
||||
|
||||
val wasEmpty = queries.isEmpty()
|
||||
val wasEmpty = composeSubscriptions.isEmpty()
|
||||
|
||||
queries.put(query, query)
|
||||
composeSubscriptions.put(query, query)
|
||||
|
||||
if (wasEmpty) {
|
||||
start()
|
||||
}
|
||||
|
||||
invalidateFilters()
|
||||
invalidateKeys()
|
||||
}
|
||||
|
||||
// This is called by main. Keep it really fast.
|
||||
fun unsubscribe(query: T?) {
|
||||
if (query == null) return
|
||||
|
||||
queries.remove(query)
|
||||
composeSubscriptions.remove(query)
|
||||
|
||||
invalidateFilters()
|
||||
invalidateKeys()
|
||||
|
||||
if (queries.isEmpty()) {
|
||||
if (composeSubscriptions.isEmpty()) {
|
||||
stop()
|
||||
}
|
||||
}
|
||||
|
||||
fun allKeys() = composeSubscriptions.keys
|
||||
|
||||
fun forEachSubscriber(action: (T) -> Unit) {
|
||||
queries.keys.forEach(action)
|
||||
composeSubscriptions.keys.forEach(action)
|
||||
}
|
||||
|
||||
final override fun updateSubscriptions() {
|
||||
updateSubscriptions(queries.keys)
|
||||
}
|
||||
|
||||
abstract fun updateSubscriptions(keys: Set<T>)
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.service.relayClient.composeSubscriptionManagers
|
||||
|
||||
interface ComposeSubscriptionManagerControls {
|
||||
fun start()
|
||||
|
||||
fun stop()
|
||||
|
||||
fun invalidateKeys()
|
||||
|
||||
fun invalidateFilters()
|
||||
|
||||
fun destroy()
|
||||
|
||||
fun printStats()
|
||||
}
|
||||
+27
-29
@@ -18,10 +18,8 @@
|
||||
* 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.service.relayClient.searchCommand
|
||||
package com.vitorpamplona.amethyst.service.relayClient.composeSubscriptionManagers
|
||||
|
||||
import com.vitorpamplona.ammolite.relays.NostrClient
|
||||
import com.vitorpamplona.ammolite.relays.datasources.SubscriptionOrchestrator
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@@ -29,31 +27,31 @@ import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
interface MutableQueryState {
|
||||
fun flow(): Flow<*>
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a data source where the filters are derived from
|
||||
* data (keys) that is being watched in the UI.
|
||||
* This allows composables to directly register their queries
|
||||
* to relays. There may be multiple duplications in these
|
||||
* subscriptions since we do not control when screens are removed.
|
||||
*
|
||||
* This is similar to QueryBasedSubscriptionOrchestrator, but it
|
||||
* also allows the subscription itself to change over time as a
|
||||
* flow, which trigger an update on the relay subscriptions
|
||||
*/
|
||||
abstract class MutableQueryBasedSubscriptionOrchestrator<T : MutableQueryState>(
|
||||
client: NostrClient,
|
||||
abstract class MutableComposeSubscriptionManager<T : MutableQueryState>(
|
||||
val scope: CoroutineScope,
|
||||
) : SubscriptionOrchestrator(client) {
|
||||
private var queries: ConcurrentHashMap<T, Job?> = ConcurrentHashMap()
|
||||
) : ComposeSubscriptionManagerControls {
|
||||
private var composeSubscriptions: ConcurrentHashMap<T, Job?> = ConcurrentHashMap()
|
||||
|
||||
// This is called by main. Keep it really fast.
|
||||
fun subscribe(query: T?) {
|
||||
if (query == null) return
|
||||
|
||||
val wasEmpty = queries.isEmpty()
|
||||
val wasEmpty = composeSubscriptions.isEmpty()
|
||||
|
||||
queries[query]?.cancel()
|
||||
queries[query] =
|
||||
composeSubscriptions[query]?.cancel()
|
||||
composeSubscriptions[query] =
|
||||
scope.launch {
|
||||
query.flow().collectLatest {
|
||||
invalidateFilters()
|
||||
invalidateKeys()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,30 +59,30 @@ abstract class MutableQueryBasedSubscriptionOrchestrator<T : MutableQueryState>(
|
||||
start()
|
||||
}
|
||||
|
||||
invalidateFilters()
|
||||
invalidateKeys()
|
||||
}
|
||||
|
||||
// This is called by main. Keep it really fast.
|
||||
fun unsubscribe(query: T?) {
|
||||
if (query == null) return
|
||||
|
||||
queries[query]?.cancel()
|
||||
queries.remove(query)
|
||||
composeSubscriptions[query]?.cancel()
|
||||
composeSubscriptions.remove(query)
|
||||
|
||||
invalidateFilters()
|
||||
invalidateKeys()
|
||||
|
||||
if (queries.isEmpty()) {
|
||||
if (composeSubscriptions.isEmpty()) {
|
||||
stop()
|
||||
}
|
||||
}
|
||||
|
||||
fun allKeys() = composeSubscriptions.keys
|
||||
|
||||
fun forEachSubscriber(action: (T) -> Unit) {
|
||||
queries.keys.forEach(action)
|
||||
composeSubscriptions.keys.forEach(action)
|
||||
}
|
||||
|
||||
final override fun updateSubscriptions() {
|
||||
updateSubscriptions(queries.keys)
|
||||
}
|
||||
|
||||
abstract fun updateSubscriptions(keys: Set<T>)
|
||||
}
|
||||
|
||||
interface MutableQueryState {
|
||||
fun flow(): Flow<*>
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.service.relayClient.eoseManagers
|
||||
|
||||
import com.vitorpamplona.ammolite.relays.NostrClient
|
||||
import com.vitorpamplona.ammolite.relays.datasources.SubscriptionController
|
||||
|
||||
abstract class BaseEoseManager<T>(
|
||||
val client: NostrClient,
|
||||
val allKeys: () -> Set<T>,
|
||||
) {
|
||||
val orchestrator =
|
||||
SubscriptionController(client) {
|
||||
updateSubscriptions(allKeys())
|
||||
}
|
||||
|
||||
abstract fun updateSubscriptions(keys: Set<T>)
|
||||
|
||||
fun printStats() = orchestrator.printStats(this.javaClass.simpleName)
|
||||
|
||||
fun invalidateFilters() = orchestrator.invalidateFilters()
|
||||
|
||||
fun start() = orchestrator.start()
|
||||
|
||||
fun stop() = orchestrator.stop()
|
||||
|
||||
fun destroy() = orchestrator.destroy()
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.service.relayClient.eoseManagers
|
||||
|
||||
import com.vitorpamplona.amethyst.service.relays.EOSEFollowList
|
||||
import com.vitorpamplona.ammolite.relays.NostrClient
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.datasources.Subscription
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import kotlin.collections.distinctBy
|
||||
|
||||
/**
|
||||
* This query type creates a new relay subscription for every SubID.id()
|
||||
* that is subscribed into the keys. It is ideal for screen loading that
|
||||
* can be shared among multiple logged-in users.
|
||||
*
|
||||
* This class keeps EOSEs for each SubID.id() for as long as possible and
|
||||
* shares all EOSEs among all users.
|
||||
*/
|
||||
abstract class PerUniqueIdEoseManager<T>(
|
||||
client: NostrClient,
|
||||
allKeys: () -> Set<T>,
|
||||
val invalidateAfterEose: Boolean = false,
|
||||
) : BaseEoseManager<T>(client, allKeys) {
|
||||
// long term EOSE cache
|
||||
private val latestEOSEs = EOSEFollowList()
|
||||
|
||||
// map between each query Id and each subscription id
|
||||
private val userSubscriptionMap = mutableMapOf<String, String>()
|
||||
|
||||
fun since(key: T) = latestEOSEs.since(id(key))
|
||||
|
||||
fun newEose(
|
||||
key: T,
|
||||
relayUrl: String,
|
||||
time: Long,
|
||||
) {
|
||||
latestEOSEs.newEose(id(key), relayUrl, time)
|
||||
if (invalidateAfterEose) {
|
||||
invalidateFilters()
|
||||
}
|
||||
}
|
||||
|
||||
open fun newSub(key: T): Subscription =
|
||||
orchestrator.requestNewSubscription { time, relayUrl ->
|
||||
newEose(key, relayUrl, time)
|
||||
}
|
||||
|
||||
open fun endSub(
|
||||
key: String,
|
||||
subId: String,
|
||||
) {
|
||||
orchestrator.dismissSubscription(subId)
|
||||
}
|
||||
|
||||
fun findOrCreateSubFor(key: T): Subscription {
|
||||
val id = id(key)
|
||||
var subId = userSubscriptionMap[id]
|
||||
return if (subId == null) {
|
||||
newSub(key).also { userSubscriptionMap[id] = it.id }
|
||||
} else {
|
||||
orchestrator.getSub(subId) ?: newSub(key).also { userSubscriptionMap[id] = it.id }
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateSubscriptions(keys: Set<T>) {
|
||||
val uniqueSubscribedAccounts = keys.distinctBy { id(it) }
|
||||
|
||||
val updated = mutableSetOf<String>()
|
||||
|
||||
uniqueSubscribedAccounts.forEach {
|
||||
val mainKey = id(it)
|
||||
findOrCreateSubFor(it).typedFilters = updateFilter(it, since(it))?.ifEmpty { null }
|
||||
|
||||
updated.add(mainKey)
|
||||
}
|
||||
|
||||
userSubscriptionMap.forEach {
|
||||
if (it.key !in updated) {
|
||||
endSub(it.key, it.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun updateFilter(
|
||||
key: T,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>?
|
||||
|
||||
abstract fun id(key: T): String
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.service.relayClient.eoseManagers
|
||||
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.service.relays.EOSEAccount
|
||||
import com.vitorpamplona.ammolite.relays.NostrClient
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.datasources.Subscription
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
|
||||
/**
|
||||
* This query type creates a new relay subscription for every logged-in
|
||||
* user AND each follow list they select in the top of the screen.
|
||||
*
|
||||
* It is ideal for the home, video, discovery and notification screens
|
||||
* since they all have lists to choose from.
|
||||
*
|
||||
* This class keeps EOSEs for each user & list for as long as possible and
|
||||
* does NOT share EOSEs with other users. Changing the list will not make the
|
||||
* app reuse the EOSE because it assumes the filter is going to be different
|
||||
*/
|
||||
abstract class PerUserAndFollowListEoseManager<T>(
|
||||
client: NostrClient,
|
||||
allKeys: () -> Set<T>,
|
||||
val invalidateAfterEose: Boolean = false,
|
||||
) : BaseEoseManager<T>(client, allKeys) {
|
||||
private val latestEOSEs = EOSEAccount()
|
||||
private val userSubscriptionMap = mutableMapOf<User, String>()
|
||||
|
||||
fun since(key: T) = latestEOSEs.since(user(key), list(key))
|
||||
|
||||
fun newEose(
|
||||
key: T,
|
||||
relayUrl: String,
|
||||
time: Long,
|
||||
) = latestEOSEs.newEose(user(key), list(key), relayUrl, time)
|
||||
|
||||
open fun newSub(key: T): Subscription =
|
||||
orchestrator.requestNewSubscription { time, relayUrl ->
|
||||
newEose(key, relayUrl, time)
|
||||
if (invalidateAfterEose) {
|
||||
invalidateFilters()
|
||||
}
|
||||
}
|
||||
|
||||
open fun endSub(
|
||||
key: User,
|
||||
subId: String,
|
||||
) {
|
||||
orchestrator.dismissSubscription(subId)
|
||||
}
|
||||
|
||||
fun findOrCreateSubFor(key: T): Subscription {
|
||||
val user = user(key)
|
||||
var subId = userSubscriptionMap[user]
|
||||
return if (subId == null) {
|
||||
newSub(key).also { userSubscriptionMap[user] = it.id }
|
||||
} else {
|
||||
orchestrator.getSub(subId) ?: newSub(key).also { userSubscriptionMap[user] = it.id }
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateSubscriptions(keys: Set<T>) {
|
||||
val uniqueSubscribedAccounts = keys.distinctBy { user(it) }
|
||||
|
||||
val updated = mutableSetOf<User>()
|
||||
|
||||
uniqueSubscribedAccounts.forEach {
|
||||
val user = user(it)
|
||||
findOrCreateSubFor(it).typedFilters = updateFilter(it, since(it))?.ifEmpty { null }
|
||||
|
||||
updated.add(user)
|
||||
}
|
||||
|
||||
userSubscriptionMap.forEach {
|
||||
if (it.key !in updated) {
|
||||
endSub(it.key, it.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun updateFilter(
|
||||
key: T,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>?
|
||||
|
||||
abstract fun user(key: T): User
|
||||
|
||||
abstract fun list(key: T): String
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.service.relayClient.eoseManagers
|
||||
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.service.relays.EOSEAccountFast
|
||||
import com.vitorpamplona.ammolite.relays.NostrClient
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.datasources.Subscription
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import kotlin.collections.distinctBy
|
||||
|
||||
/**
|
||||
* This query type creates a new relay subscription for every distinct
|
||||
* user that is subscribed into the keys. It is ideal for screens that
|
||||
* CANNOT be shared among multiple logged-in users.
|
||||
*
|
||||
* This class keeps EOSEs for each user for as long as possible and
|
||||
* does NOT share EOSEs with other users. The EOSEs are kept even when
|
||||
* the subscription disappears and comes back later.
|
||||
*/
|
||||
abstract class PerUserEoseManager<T>(
|
||||
client: NostrClient,
|
||||
allKeys: () -> Set<T>,
|
||||
val invalidateAfterEose: Boolean = false,
|
||||
) : BaseEoseManager<T>(client, allKeys) {
|
||||
private val latestEOSEs = EOSEAccountFast()
|
||||
private val userSubscriptionMap = mutableMapOf<User, String>()
|
||||
|
||||
fun since(key: T) = latestEOSEs.since(user(key))
|
||||
|
||||
fun newEose(
|
||||
key: T,
|
||||
relayUrl: String,
|
||||
time: Long,
|
||||
) = latestEOSEs.newEose(user(key), relayUrl, time)
|
||||
|
||||
open fun newSub(key: T): Subscription =
|
||||
orchestrator.requestNewSubscription { time, relayUrl ->
|
||||
newEose(key, relayUrl, time)
|
||||
if (invalidateAfterEose) {
|
||||
invalidateFilters()
|
||||
}
|
||||
}
|
||||
|
||||
open fun endSub(
|
||||
key: User,
|
||||
subId: String,
|
||||
) {
|
||||
orchestrator.dismissSubscription(subId)
|
||||
}
|
||||
|
||||
fun findOrCreateSubFor(key: T): Subscription {
|
||||
val user = user(key)
|
||||
var subId = userSubscriptionMap[user]
|
||||
return if (subId == null) {
|
||||
newSub(key).also { userSubscriptionMap[user] = it.id }
|
||||
} else {
|
||||
orchestrator.getSub(subId) ?: newSub(key).also { userSubscriptionMap[user] = it.id }
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateSubscriptions(keys: Set<T>) {
|
||||
val uniqueSubscribedAccounts = keys.distinctBy { user(it) }
|
||||
|
||||
val updated = mutableSetOf<User>()
|
||||
|
||||
uniqueSubscribedAccounts.forEach {
|
||||
val user = user(it)
|
||||
findOrCreateSubFor(it).typedFilters = updateFilter(it, since(it))?.ifEmpty { null }
|
||||
|
||||
updated.add(user)
|
||||
}
|
||||
|
||||
userSubscriptionMap.forEach {
|
||||
if (it.key !in updated) {
|
||||
endSub(it.key, it.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun updateFilter(
|
||||
key: T,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>?
|
||||
|
||||
abstract fun user(key: T): User
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.service.relayClient.eoseManagers
|
||||
|
||||
import com.vitorpamplona.amethyst.service.relays.EOSERelayList
|
||||
import com.vitorpamplona.ammolite.relays.NostrClient
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import kotlin.collections.distinctBy
|
||||
|
||||
/**
|
||||
* This query type creates only ONE relay subscription. It filters duplicates
|
||||
* by DistinctById.uniqueId() but it doesn't create a new subscription for each.
|
||||
*
|
||||
* All filters are passed as a single sub.
|
||||
*
|
||||
* It is ideal for temporary filters, including event, user finding that must be
|
||||
* disabled after the user is found.
|
||||
*
|
||||
* This class keeps EOSEs for as long as possible and
|
||||
* shares all EOSEs among all users.
|
||||
*/
|
||||
abstract class SingleSubEoseManager<T>(
|
||||
client: NostrClient,
|
||||
allKeys: () -> Set<T>,
|
||||
val invalidateAfterEose: Boolean = false,
|
||||
) : BaseEoseManager<T>(client, allKeys) {
|
||||
// long term EOSE cache
|
||||
private val latestEOSEs = EOSERelayList()
|
||||
|
||||
fun since() = latestEOSEs.since()
|
||||
|
||||
open fun newEose(
|
||||
relayUrl: String,
|
||||
time: Long,
|
||||
) = latestEOSEs.newEose(relayUrl, time)
|
||||
|
||||
val sub =
|
||||
orchestrator.requestNewSubscription { time, relayUrl ->
|
||||
newEose(relayUrl, time)
|
||||
if (invalidateAfterEose) {
|
||||
invalidateFilters()
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateSubscriptions(keys: Set<T>) {
|
||||
val uniqueSubscribedAccounts = keys.distinctBy { distinct(it) }
|
||||
|
||||
sub.typedFilters = updateFilter(uniqueSubscribedAccounts, since())?.ifEmpty { null }
|
||||
}
|
||||
|
||||
abstract fun updateFilter(
|
||||
key: List<T>,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>?
|
||||
|
||||
abstract fun distinct(key: T): Any
|
||||
}
|
||||
+5
-4
@@ -21,8 +21,9 @@
|
||||
package com.vitorpamplona.amethyst.service.relayClient.reqCommand
|
||||
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.service.relayClient.composeSubscriptionManagers.ComposeSubscriptionManagerControls
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.AccountFilterAssembler
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.ChannelFinderFilterAssembler
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.ChannelFinderFilterAssemblyGroup
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.EventFinderFilterAssembler
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.nwc.NWCPaymentFilterAssembler
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.UserFinderFilterAssembler
|
||||
@@ -57,7 +58,7 @@ class RelaySubscriptionsCoordinator(
|
||||
|
||||
// loaders of content that is not yet in the device.
|
||||
// they are active when looking at events, users, channels.
|
||||
val channelFinder = ChannelFinderFilterAssembler(client)
|
||||
val channelFinder = ChannelFinderFilterAssemblyGroup(client)
|
||||
val eventFinder = EventFinderFilterAssembler(client)
|
||||
val userFinder = UserFinderFilterAssembler(client)
|
||||
|
||||
@@ -77,7 +78,7 @@ class RelaySubscriptionsCoordinator(
|
||||
val nwc = NWCPaymentFilterAssembler(client)
|
||||
|
||||
val all =
|
||||
listOf(
|
||||
listOf<ComposeSubscriptionManagerControls>(
|
||||
account,
|
||||
home,
|
||||
chatroomList,
|
||||
@@ -103,5 +104,5 @@ class RelaySubscriptionsCoordinator(
|
||||
|
||||
fun destroy() = all.forEach { it.destroy() }
|
||||
|
||||
fun printCounters() = all.forEach { it.stats.printCounter("${it.javaClass.simpleName}") }
|
||||
fun printCounters() = all.forEach { it.printStats() }
|
||||
}
|
||||
|
||||
+20
-281
@@ -21,302 +21,41 @@
|
||||
package com.vitorpamplona.amethyst.service.relayClient.reqCommand.account
|
||||
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.QueryBasedSubscriptionOrchestrator
|
||||
import com.vitorpamplona.amethyst.service.relays.EOSERelayList
|
||||
import com.vitorpamplona.ammolite.relays.COMMON_FEED_TYPES
|
||||
import com.vitorpamplona.ammolite.relays.EVENT_FINDER_TYPES
|
||||
import com.vitorpamplona.amethyst.service.relayClient.composeSubscriptionManagers.ComposeSubscriptionManager
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.metadata.AccountMetadataEoseManager
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.nip01Notifications.AccountNotificationsEoseManager
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.nip59GiftWraps.AccountGiftWrapsEoseManager
|
||||
import com.vitorpamplona.ammolite.relays.NostrClient
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.blossom.BlossomServersEvent
|
||||
import com.vitorpamplona.quartz.experimental.edits.PrivateOutboxRelayListEvent
|
||||
import com.vitorpamplona.quartz.experimental.ephemChat.chat.EphemeralChatEvent
|
||||
import com.vitorpamplona.quartz.experimental.ephemChat.list.EphemeralChatListEvent
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryPrologueEvent
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStorySceneEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip17Dm.settings.ChatMessageRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
|
||||
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
|
||||
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.list.ChannelListEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
|
||||
import com.vitorpamplona.quartz.nip30CustomEmoji.selection.EmojiPackSelectionEvent
|
||||
import com.vitorpamplona.quartz.nip34Git.issue.GitIssueEvent
|
||||
import com.vitorpamplona.quartz.nip34Git.patch.GitPatchEvent
|
||||
import com.vitorpamplona.quartz.nip34Git.reply.GitReplyEvent
|
||||
import com.vitorpamplona.quartz.nip37Drafts.DraftEvent
|
||||
import com.vitorpamplona.quartz.nip38UserStatus.StatusEvent
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.LnZapPaymentResponseEvent
|
||||
import com.vitorpamplona.quartz.nip50Search.SearchRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip51Lists.BookmarkListEvent
|
||||
import com.vitorpamplona.quartz.nip51Lists.FollowListEvent
|
||||
import com.vitorpamplona.quartz.nip51Lists.MuteListEvent
|
||||
import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent
|
||||
import com.vitorpamplona.quartz.nip52Calendar.CalendarDateSlotEvent
|
||||
import com.vitorpamplona.quartz.nip52Calendar.CalendarRSVPEvent
|
||||
import com.vitorpamplona.quartz.nip52Calendar.CalendarTimeSlotEvent
|
||||
import com.vitorpamplona.quartz.nip56Reports.ReportEvent
|
||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
|
||||
import com.vitorpamplona.quartz.nip58Badges.BadgeAwardEvent
|
||||
import com.vitorpamplona.quartz.nip58Badges.BadgeProfilesEvent
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
||||
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip78AppData.AppSpecificDataEvent
|
||||
import com.vitorpamplona.quartz.nip84Highlights.HighlightEvent
|
||||
import com.vitorpamplona.quartz.nip96FileStorage.config.FileServersEvent
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
// This allows multiple screen to be listening to tags, even the same tag
|
||||
// This allows multiple screen to be listening to logged-in accounts.
|
||||
class AccountQueryState(
|
||||
val account: Account,
|
||||
val otherAccounts: Set<HexKey>,
|
||||
)
|
||||
|
||||
/**
|
||||
* This assembler loads everything eech account needs.
|
||||
*/
|
||||
class AccountFilterAssembler(
|
||||
client: NostrClient,
|
||||
) : QueryBasedSubscriptionOrchestrator<AccountQueryState>(client) {
|
||||
val latestEOSE = EOSERelayList()
|
||||
var hasLoadedTheBasics: Boolean = false
|
||||
|
||||
fun createAccountMetadataFilter(authorsHexes: List<String>): TypedFilter =
|
||||
TypedFilter(
|
||||
types = COMMON_FEED_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds =
|
||||
listOf(
|
||||
MetadataEvent.KIND,
|
||||
ContactListEvent.KIND,
|
||||
StatusEvent.KIND,
|
||||
AdvertisedRelayListEvent.KIND,
|
||||
ChatMessageRelayListEvent.KIND,
|
||||
SearchRelayListEvent.KIND,
|
||||
FileServersEvent.KIND,
|
||||
BlossomServersEvent.KIND,
|
||||
PrivateOutboxRelayListEvent.KIND,
|
||||
),
|
||||
authors = authorsHexes,
|
||||
limit = 20 * authorsHexes.size,
|
||||
),
|
||||
) : ComposeSubscriptionManager<AccountQueryState>() {
|
||||
val group =
|
||||
listOf(
|
||||
AccountMetadataEoseManager(client, ::allKeys),
|
||||
AccountGiftWrapsEoseManager(client, ::allKeys),
|
||||
AccountNotificationsEoseManager(client, ::allKeys),
|
||||
)
|
||||
|
||||
fun createOtherAccountsBaseFilter(otherAccounts: List<HexKey>): TypedFilter? {
|
||||
if (otherAccounts.isEmpty()) return null
|
||||
return TypedFilter(
|
||||
types = EVENT_FINDER_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds =
|
||||
listOf(
|
||||
MetadataEvent.KIND,
|
||||
ContactListEvent.KIND,
|
||||
AdvertisedRelayListEvent.KIND,
|
||||
ChatMessageRelayListEvent.KIND,
|
||||
SearchRelayListEvent.KIND,
|
||||
FileServersEvent.KIND,
|
||||
BlossomServersEvent.KIND,
|
||||
MuteListEvent.KIND,
|
||||
PeopleListEvent.KIND,
|
||||
),
|
||||
authors = otherAccounts,
|
||||
limit = otherAccounts.size * 20,
|
||||
),
|
||||
)
|
||||
}
|
||||
override fun start() = group.forEach { it.start() }
|
||||
|
||||
fun createAccountSettingsFilter(authorsHexes: List<String>): TypedFilter =
|
||||
TypedFilter(
|
||||
types = COMMON_FEED_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds =
|
||||
listOf(
|
||||
PeopleListEvent.KIND,
|
||||
FollowListEvent.KIND,
|
||||
MuteListEvent.KIND,
|
||||
BadgeProfilesEvent.KIND,
|
||||
EmojiPackSelectionEvent.KIND,
|
||||
EphemeralChatListEvent.KIND,
|
||||
ChannelListEvent.KIND,
|
||||
),
|
||||
authors = authorsHexes,
|
||||
limit = 100 * authorsHexes.size,
|
||||
),
|
||||
)
|
||||
override fun stop() = group.forEach { it.stop() }
|
||||
|
||||
fun createAccountSettings2Filter(authorsHexes: List<String>): TypedFilter =
|
||||
TypedFilter(
|
||||
types = COMMON_FEED_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds =
|
||||
listOf(
|
||||
AppSpecificDataEvent.KIND,
|
||||
),
|
||||
authors = authorsHexes,
|
||||
tags = mapOf("d" to listOf(Account.APP_SPECIFIC_DATA_D_TAG)),
|
||||
limit = 2 * authorsHexes.size,
|
||||
),
|
||||
)
|
||||
override fun invalidateKeys() = invalidateFilters()
|
||||
|
||||
fun createAccountLastPostsListFilter(authorsHexes: List<String>): TypedFilter =
|
||||
TypedFilter(
|
||||
types = COMMON_FEED_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
authors = authorsHexes,
|
||||
limit = 500,
|
||||
),
|
||||
)
|
||||
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
|
||||
|
||||
fun createAccountReportsAndDraftsFilter(authorsHexes: List<String>): TypedFilter =
|
||||
TypedFilter(
|
||||
types = COMMON_FEED_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds =
|
||||
listOf(
|
||||
DraftEvent.KIND,
|
||||
ReportEvent.KIND,
|
||||
BookmarkListEvent.KIND,
|
||||
),
|
||||
authors = authorsHexes,
|
||||
since = latestEOSE.relayList,
|
||||
),
|
||||
)
|
||||
override fun destroy() = group.forEach { it.start() }
|
||||
|
||||
fun createGiftWrapsToMeFilter(authorsHexes: List<String>) =
|
||||
TypedFilter(
|
||||
types = COMMON_FEED_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(GiftWrapEvent.KIND),
|
||||
tags = mapOf("p" to authorsHexes),
|
||||
since =
|
||||
latestEOSE.relayList.mapValues {
|
||||
EOSETime(it.value.time - TimeUtils.twoDays())
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
fun createNotificationFilter(authorsHexes: List<String>) =
|
||||
TypedFilter(
|
||||
types = COMMON_FEED_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds =
|
||||
listOf(
|
||||
TextNoteEvent.KIND,
|
||||
ReactionEvent.KIND,
|
||||
RepostEvent.KIND,
|
||||
GenericRepostEvent.KIND,
|
||||
ReportEvent.KIND,
|
||||
LnZapEvent.KIND,
|
||||
LnZapPaymentResponseEvent.KIND,
|
||||
ChannelMessageEvent.KIND,
|
||||
EphemeralChatEvent.KIND,
|
||||
BadgeAwardEvent.KIND,
|
||||
),
|
||||
tags = mapOf("p" to authorsHexes),
|
||||
limit = 4000,
|
||||
since = latestEOSE.relayList,
|
||||
),
|
||||
)
|
||||
|
||||
fun createNotificationFilter2(authorsHexes: List<String>) =
|
||||
TypedFilter(
|
||||
types = COMMON_FEED_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds =
|
||||
listOf(
|
||||
GitReplyEvent.KIND,
|
||||
GitIssueEvent.KIND,
|
||||
GitPatchEvent.KIND,
|
||||
HighlightEvent.KIND,
|
||||
CommentEvent.KIND,
|
||||
CalendarDateSlotEvent.KIND,
|
||||
CalendarTimeSlotEvent.KIND,
|
||||
CalendarRSVPEvent.KIND,
|
||||
InteractiveStoryPrologueEvent.KIND,
|
||||
InteractiveStorySceneEvent.KIND,
|
||||
),
|
||||
tags = mapOf("p" to authorsHexes),
|
||||
limit = 400,
|
||||
since = latestEOSE.relayList,
|
||||
),
|
||||
)
|
||||
|
||||
fun createNotificationFilter3(authorsHexes: List<String>) =
|
||||
TypedFilter(
|
||||
types = COMMON_FEED_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds =
|
||||
listOf(
|
||||
PollNoteEvent.KIND,
|
||||
),
|
||||
tags = mapOf("p" to authorsHexes),
|
||||
limit = 100,
|
||||
since = latestEOSE.relayList,
|
||||
),
|
||||
)
|
||||
|
||||
fun mergeAllFilters(
|
||||
mainAccounts: List<HexKey>,
|
||||
otherAccounts: List<HexKey>,
|
||||
): List<TypedFilter>? =
|
||||
if (hasLoadedTheBasics) {
|
||||
// gets everything about the user logged in
|
||||
listOfNotNull(
|
||||
createAccountMetadataFilter(mainAccounts),
|
||||
createAccountSettings2Filter(mainAccounts),
|
||||
createNotificationFilter(mainAccounts),
|
||||
createNotificationFilter2(mainAccounts),
|
||||
createNotificationFilter3(mainAccounts),
|
||||
createGiftWrapsToMeFilter(mainAccounts),
|
||||
createAccountReportsAndDraftsFilter(mainAccounts),
|
||||
createAccountSettingsFilter(mainAccounts),
|
||||
createAccountLastPostsListFilter(mainAccounts),
|
||||
createOtherAccountsBaseFilter(otherAccounts),
|
||||
).ifEmpty { null }
|
||||
} else {
|
||||
// just the basics.
|
||||
listOf(
|
||||
createAccountMetadataFilter(mainAccounts),
|
||||
createAccountSettingsFilter(mainAccounts),
|
||||
createAccountSettings2Filter(mainAccounts),
|
||||
).ifEmpty { null }
|
||||
}
|
||||
|
||||
val accountChannel =
|
||||
requestNewSubscription { time, relayUrl ->
|
||||
if (hasLoadedTheBasics) {
|
||||
latestEOSE.addOrUpdate(relayUrl, time)
|
||||
} else {
|
||||
hasLoadedTheBasics = true
|
||||
|
||||
invalidateFilters()
|
||||
}
|
||||
}
|
||||
|
||||
// One sub per subscribed account
|
||||
override fun updateSubscriptions(keys: Set<AccountQueryState>) {
|
||||
val mainAccounts = mutableSetOf<HexKey>()
|
||||
val otherAccounts = mutableSetOf<HexKey>()
|
||||
|
||||
keys.forEach {
|
||||
mainAccounts.add(it.account.userProfile().pubkeyHex)
|
||||
otherAccounts.addAll(it.otherAccounts)
|
||||
}
|
||||
|
||||
accountChannel.typedFilters = mergeAllFilters(mainAccounts.toList(), (otherAccounts - mainAccounts).toList())
|
||||
}
|
||||
override fun printStats() = group.forEach { it.printStats() }
|
||||
}
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.service.relayClient.reqCommand.account.metadata
|
||||
|
||||
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUserEoseManager
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.AccountQueryState
|
||||
import com.vitorpamplona.ammolite.relays.NostrClient
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
|
||||
class AccountMetadataEoseManager(
|
||||
client: NostrClient,
|
||||
allKeys: () -> Set<AccountQueryState>,
|
||||
) : PerUserEoseManager<AccountQueryState>(client, allKeys) {
|
||||
override fun user(query: AccountQueryState) = query.account.userProfile()
|
||||
|
||||
override fun updateFilter(
|
||||
key: AccountQueryState,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? =
|
||||
listOfNotNull(
|
||||
filterAccountInfoAndListsFromKey(user(key).pubkeyHex, since),
|
||||
filterFollowsAndMutesFromKey(user(key).pubkeyHex, since),
|
||||
filterDraftsAndReportsFromKey(user(key).pubkeyHex, since),
|
||||
filterLastPostsFromKey(user(key).pubkeyHex, since),
|
||||
filterBasicAccountInfoFromKeys(key.otherAccounts.minus(key.account.userProfile().pubkeyHex).toList(), since),
|
||||
).flatten()
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.service.relayClient.reqCommand.account.metadata
|
||||
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.ammolite.relays.COMMON_FEED_TYPES
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.blossom.BlossomServersEvent
|
||||
import com.vitorpamplona.quartz.experimental.edits.PrivateOutboxRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
|
||||
import com.vitorpamplona.quartz.nip17Dm.settings.ChatMessageRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip38UserStatus.StatusEvent
|
||||
import com.vitorpamplona.quartz.nip50Search.SearchRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip78AppData.AppSpecificDataEvent
|
||||
import com.vitorpamplona.quartz.nip96FileStorage.config.FileServersEvent
|
||||
|
||||
fun filterAccountInfoAndListsFromKey(
|
||||
pubkey: HexKey?,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? {
|
||||
if (pubkey == null || pubkey.isEmpty()) return null
|
||||
|
||||
return listOf(
|
||||
TypedFilter(
|
||||
types = COMMON_FEED_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds =
|
||||
listOf(
|
||||
MetadataEvent.KIND,
|
||||
ContactListEvent.KIND,
|
||||
StatusEvent.KIND,
|
||||
AdvertisedRelayListEvent.KIND,
|
||||
ChatMessageRelayListEvent.KIND,
|
||||
SearchRelayListEvent.KIND,
|
||||
FileServersEvent.KIND,
|
||||
BlossomServersEvent.KIND,
|
||||
PrivateOutboxRelayListEvent.KIND,
|
||||
),
|
||||
authors = listOf(pubkey),
|
||||
limit = 20,
|
||||
since = since,
|
||||
),
|
||||
),
|
||||
TypedFilter(
|
||||
types = COMMON_FEED_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(AppSpecificDataEvent.KIND),
|
||||
authors = listOf(pubkey),
|
||||
tags = mapOf("d" to listOf(Account.APP_SPECIFIC_DATA_D_TAG)),
|
||||
limit = 1,
|
||||
since = since,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.service.relayClient.reqCommand.account.metadata
|
||||
|
||||
import com.vitorpamplona.ammolite.relays.EVENT_FINDER_TYPES
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.blossom.BlossomServersEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
|
||||
import com.vitorpamplona.quartz.nip17Dm.settings.ChatMessageRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip50Search.SearchRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip96FileStorage.config.FileServersEvent
|
||||
|
||||
fun filterBasicAccountInfoFromKeys(
|
||||
otherAccounts: List<HexKey>?,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? {
|
||||
if (otherAccounts == null || otherAccounts.isEmpty()) return null
|
||||
|
||||
return listOf(
|
||||
TypedFilter(
|
||||
types = EVENT_FINDER_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds =
|
||||
listOf(
|
||||
MetadataEvent.KIND,
|
||||
ContactListEvent.KIND,
|
||||
AdvertisedRelayListEvent.KIND,
|
||||
ChatMessageRelayListEvent.KIND,
|
||||
SearchRelayListEvent.KIND,
|
||||
FileServersEvent.KIND,
|
||||
BlossomServersEvent.KIND,
|
||||
),
|
||||
authors = otherAccounts.toList(),
|
||||
limit = otherAccounts.size * 8,
|
||||
since = since,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.service.relayClient.reqCommand.account.metadata
|
||||
|
||||
import com.vitorpamplona.ammolite.relays.COMMON_FEED_TYPES
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip37Drafts.DraftEvent
|
||||
import com.vitorpamplona.quartz.nip51Lists.BookmarkListEvent
|
||||
import com.vitorpamplona.quartz.nip56Reports.ReportEvent
|
||||
|
||||
fun filterDraftsAndReportsFromKey(
|
||||
pubkey: HexKey?,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? {
|
||||
if (pubkey == null || pubkey.isEmpty()) return null
|
||||
|
||||
return listOf(
|
||||
TypedFilter(
|
||||
types = COMMON_FEED_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds =
|
||||
listOf(
|
||||
DraftEvent.KIND,
|
||||
ReportEvent.KIND,
|
||||
BookmarkListEvent.KIND,
|
||||
),
|
||||
authors = listOf(pubkey),
|
||||
since = since,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.service.relayClient.reqCommand.account.metadata
|
||||
|
||||
import com.vitorpamplona.ammolite.relays.COMMON_FEED_TYPES
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.experimental.ephemChat.list.EphemeralChatListEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.list.ChannelListEvent
|
||||
import com.vitorpamplona.quartz.nip30CustomEmoji.selection.EmojiPackSelectionEvent
|
||||
import com.vitorpamplona.quartz.nip51Lists.FollowListEvent
|
||||
import com.vitorpamplona.quartz.nip51Lists.MuteListEvent
|
||||
import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent
|
||||
import com.vitorpamplona.quartz.nip58Badges.BadgeProfilesEvent
|
||||
|
||||
fun filterFollowsAndMutesFromKey(
|
||||
pubkey: HexKey?,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? {
|
||||
if (pubkey == null || pubkey.isEmpty()) return null
|
||||
|
||||
return listOf(
|
||||
TypedFilter(
|
||||
types = COMMON_FEED_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds =
|
||||
listOf(
|
||||
PeopleListEvent.KIND,
|
||||
FollowListEvent.KIND,
|
||||
MuteListEvent.KIND,
|
||||
BadgeProfilesEvent.KIND,
|
||||
EmojiPackSelectionEvent.KIND,
|
||||
EphemeralChatListEvent.KIND,
|
||||
ChannelListEvent.KIND,
|
||||
),
|
||||
authors = listOf(pubkey),
|
||||
limit = 100,
|
||||
since = since,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.service.relayClient.reqCommand.account.metadata
|
||||
|
||||
import com.vitorpamplona.ammolite.relays.COMMON_FEED_TYPES
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
|
||||
fun filterLastPostsFromKey(
|
||||
pubkey: HexKey?,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? {
|
||||
if (pubkey == null || pubkey.isEmpty()) return null
|
||||
|
||||
return listOf(
|
||||
TypedFilter(
|
||||
types = COMMON_FEED_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
authors = listOf(pubkey),
|
||||
limit = 100,
|
||||
since = since,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.service.relayClient.reqCommand.account.nip01Notifications
|
||||
|
||||
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUserEoseManager
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.AccountQueryState
|
||||
import com.vitorpamplona.ammolite.relays.NostrClient
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
|
||||
class AccountNotificationsEoseManager(
|
||||
client: NostrClient,
|
||||
allKeys: () -> Set<AccountQueryState>,
|
||||
) : PerUserEoseManager<AccountQueryState>(client, allKeys) {
|
||||
override fun user(query: AccountQueryState) = query.account.userProfile()
|
||||
|
||||
override fun updateFilter(
|
||||
key: AccountQueryState,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? = filterNotificationsToPubkey(user(key).pubkeyHex, since)
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.service.relayClient.reqCommand.account.nip01Notifications
|
||||
|
||||
import com.vitorpamplona.ammolite.relays.COMMON_FEED_TYPES
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.experimental.ephemChat.chat.EphemeralChatEvent
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryPrologueEvent
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStorySceneEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
|
||||
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
|
||||
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
|
||||
import com.vitorpamplona.quartz.nip34Git.issue.GitIssueEvent
|
||||
import com.vitorpamplona.quartz.nip34Git.patch.GitPatchEvent
|
||||
import com.vitorpamplona.quartz.nip34Git.reply.GitReplyEvent
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.LnZapPaymentResponseEvent
|
||||
import com.vitorpamplona.quartz.nip52Calendar.CalendarDateSlotEvent
|
||||
import com.vitorpamplona.quartz.nip52Calendar.CalendarRSVPEvent
|
||||
import com.vitorpamplona.quartz.nip52Calendar.CalendarTimeSlotEvent
|
||||
import com.vitorpamplona.quartz.nip56Reports.ReportEvent
|
||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
|
||||
import com.vitorpamplona.quartz.nip58Badges.BadgeAwardEvent
|
||||
import com.vitorpamplona.quartz.nip84Highlights.HighlightEvent
|
||||
|
||||
fun filterNotificationsToPubkey(
|
||||
pubkey: HexKey?,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? {
|
||||
if (pubkey == null || pubkey.isEmpty()) return null
|
||||
|
||||
return listOf(
|
||||
TypedFilter(
|
||||
types = COMMON_FEED_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds =
|
||||
listOf(
|
||||
TextNoteEvent.KIND,
|
||||
ReactionEvent.KIND,
|
||||
RepostEvent.KIND,
|
||||
GenericRepostEvent.KIND,
|
||||
ReportEvent.KIND,
|
||||
LnZapEvent.KIND,
|
||||
LnZapPaymentResponseEvent.KIND,
|
||||
ChannelMessageEvent.KIND,
|
||||
EphemeralChatEvent.KIND,
|
||||
BadgeAwardEvent.KIND,
|
||||
),
|
||||
tags = mapOf("p" to listOf(pubkey)),
|
||||
limit = 2000,
|
||||
since = since,
|
||||
),
|
||||
),
|
||||
TypedFilter(
|
||||
types = COMMON_FEED_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds =
|
||||
listOf(
|
||||
GitReplyEvent.KIND,
|
||||
GitIssueEvent.KIND,
|
||||
GitPatchEvent.KIND,
|
||||
HighlightEvent.KIND,
|
||||
CommentEvent.KIND,
|
||||
CalendarDateSlotEvent.KIND,
|
||||
CalendarTimeSlotEvent.KIND,
|
||||
CalendarRSVPEvent.KIND,
|
||||
InteractiveStoryPrologueEvent.KIND,
|
||||
InteractiveStorySceneEvent.KIND,
|
||||
),
|
||||
tags = mapOf("p" to listOf(pubkey)),
|
||||
limit = 400,
|
||||
since = since,
|
||||
),
|
||||
),
|
||||
TypedFilter(
|
||||
types = COMMON_FEED_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(PollNoteEvent.KIND),
|
||||
tags = mapOf("p" to listOf(pubkey)),
|
||||
limit = 100,
|
||||
since = since,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.service.relayClient.reqCommand.account.nip59GiftWraps
|
||||
|
||||
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUserEoseManager
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.AccountQueryState
|
||||
import com.vitorpamplona.ammolite.relays.NostrClient
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
|
||||
class AccountGiftWrapsEoseManager(
|
||||
client: NostrClient,
|
||||
allKeys: () -> Set<AccountQueryState>,
|
||||
) : PerUserEoseManager<AccountQueryState>(client, allKeys) {
|
||||
override fun user(query: AccountQueryState) = query.account.userProfile()
|
||||
|
||||
override fun updateFilter(
|
||||
key: AccountQueryState,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? = filterGiftWrapsToPubkey(user(key).pubkeyHex, since)
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.service.relayClient.reqCommand.account.nip59GiftWraps
|
||||
|
||||
import com.vitorpamplona.ammolite.relays.COMMON_FEED_TYPES
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
fun filterGiftWrapsToPubkey(
|
||||
pubkey: HexKey?,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? {
|
||||
if (pubkey == null || pubkey.isEmpty()) return null
|
||||
|
||||
return listOf(
|
||||
TypedFilter(
|
||||
types = COMMON_FEED_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(GiftWrapEvent.KIND),
|
||||
tags = mapOf("p" to listOf(pubkey)),
|
||||
since =
|
||||
since?.mapValues {
|
||||
EOSETime(it.value.time - TimeUtils.twoDays())
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
-131
@@ -1,131 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.service.relayClient.reqCommand.channel
|
||||
|
||||
import com.vitorpamplona.amethyst.model.Channel
|
||||
import com.vitorpamplona.amethyst.model.LiveActivitiesChannel
|
||||
import com.vitorpamplona.amethyst.model.PublicChatChannel
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.QueryBasedSubscriptionOrchestrator
|
||||
import com.vitorpamplona.ammolite.relays.EVENT_FINDER_TYPES
|
||||
import com.vitorpamplona.ammolite.relays.FeedType
|
||||
import com.vitorpamplona.ammolite.relays.NostrClient
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.NormalFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelMetadataEvent
|
||||
|
||||
// This allows multiple screen to be listening to tags, even the same tag
|
||||
class ChannelFinderQueryState(
|
||||
val channel: Channel,
|
||||
)
|
||||
|
||||
class ChannelFinderFilterAssembler(
|
||||
client: NostrClient,
|
||||
) : QueryBasedSubscriptionOrchestrator<ChannelFinderQueryState>(client) {
|
||||
private fun createMetadataChangeFilter(keys: Set<ChannelFinderQueryState>): List<TypedFilter> {
|
||||
val channelsToWatch = keys.filter { it.channel is PublicChatChannel }
|
||||
|
||||
if (channelsToWatch.isEmpty()) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
// downloads all the reactions to a given event.
|
||||
return channelsToWatch.map {
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.PUBLIC_CHATS),
|
||||
filter =
|
||||
NormalFilter(
|
||||
kinds = listOf(ChannelMetadataEvent.KIND),
|
||||
tags = mapOf("e" to listOf(it.channel.idHex)),
|
||||
since = it.channel.updatedMetadataAt + 1,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun createLoadEventsIfNotLoadedFilter(keys: Set<ChannelFinderQueryState>): TypedFilter? {
|
||||
val interestedEvents =
|
||||
keys.mapNotNullTo(mutableSetOf()) {
|
||||
if (it.channel is PublicChatChannel && it.channel.event == null) {
|
||||
it.channel.idHex
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
if (interestedEvents.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
|
||||
// downloads linked events to this event.
|
||||
return TypedFilter(
|
||||
types = EVENT_FINDER_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(ChannelCreateEvent.KIND),
|
||||
ids = interestedEvents.toList(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun createLoadStreamingIfNotLoadedFilter(keys: Set<ChannelFinderQueryState>): List<TypedFilter> {
|
||||
val directEventsToLoad =
|
||||
keys.mapNotNull {
|
||||
if (it.channel is LiveActivitiesChannel && it.channel.info == null) {
|
||||
it.channel
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
val interestedEvents = directEventsToLoad.map { it.idHex }.toSet()
|
||||
|
||||
if (interestedEvents.isEmpty()) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
// downloads linked events to this event.
|
||||
return directEventsToLoad.map {
|
||||
it.address().let { aTag ->
|
||||
TypedFilter(
|
||||
types = EVENT_FINDER_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(aTag.kind),
|
||||
tags = mapOf("d" to listOf(aTag.dTag)),
|
||||
authors = listOf(aTag.pubKeyHex),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val singleChannelChannel = requestNewSubscription()
|
||||
|
||||
override fun updateSubscriptions(keys: Set<ChannelFinderQueryState>) {
|
||||
val missing = createLoadEventsIfNotLoadedFilter(keys)
|
||||
val missingStreaming = createLoadStreamingIfNotLoadedFilter(keys)
|
||||
val metadataWatcher = createMetadataChangeFilter(keys)
|
||||
|
||||
singleChannelChannel.typedFilters = (listOfNotNull(missing) + missingStreaming + metadataWatcher).ifEmpty { null }
|
||||
}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.service.relayClient.reqCommand.channel
|
||||
|
||||
import com.vitorpamplona.amethyst.model.Channel
|
||||
import com.vitorpamplona.amethyst.service.relayClient.composeSubscriptionManagers.ComposeSubscriptionManager
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.mixChatsLive.ChannelMetadataAndLiveActivityWatcherSubAssembler
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.nip28PublicChats.ChannelLoaderSubAssembler
|
||||
import com.vitorpamplona.ammolite.relays.NostrClient
|
||||
|
||||
// This allows multiple screen to be listening to tags, even the same tag
|
||||
class ChannelFinderQueryState(
|
||||
val channel: Channel,
|
||||
)
|
||||
|
||||
class ChannelFinderFilterAssemblyGroup(
|
||||
client: NostrClient,
|
||||
) : ComposeSubscriptionManager<ChannelFinderQueryState>() {
|
||||
val group =
|
||||
listOf(
|
||||
// this is a constantly rotating filter, better to keep it isolated
|
||||
ChannelLoaderSubAssembler(client, ::allKeys),
|
||||
// Later we could switch these and use one subscription for each query
|
||||
// ChannelMetadataWatcherSubAssembler(client, ::allKeys),
|
||||
// LiveActivityWatcherSubAssembly(client, ::allKeys)
|
||||
ChannelMetadataAndLiveActivityWatcherSubAssembler(client, ::allKeys),
|
||||
)
|
||||
|
||||
override fun start() = group.forEach { it.start() }
|
||||
|
||||
override fun stop() = group.forEach { it.stop() }
|
||||
|
||||
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
|
||||
|
||||
override fun invalidateKeys() = invalidateFilters()
|
||||
|
||||
override fun destroy() = group.forEach { it.destroy() }
|
||||
|
||||
override fun printStats() = group.forEach { it.printStats() }
|
||||
}
|
||||
+1
-1
@@ -35,7 +35,7 @@ fun ChannelFinderFilterAssemblerSubscription(
|
||||
@Composable
|
||||
fun ChannelFinderFilterAssemblerSubscription(
|
||||
channel: Channel,
|
||||
dataSource: ChannelFinderFilterAssembler,
|
||||
dataSource: ChannelFinderFilterAssemblyGroup,
|
||||
) {
|
||||
// different screens get different states
|
||||
// even if they are tracking the same tag.
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.service.relayClient.reqCommand.channel.mixChatsLive
|
||||
|
||||
import com.vitorpamplona.amethyst.model.LiveActivitiesChannel
|
||||
import com.vitorpamplona.amethyst.model.PublicChatChannel
|
||||
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.SingleSubEoseManager
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.ChannelFinderQueryState
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.nip28PublicChats.filterChannelMetadataUpdatesById
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.nip53LiveActivities.filterLiveStreamUpdatesByAddress
|
||||
import com.vitorpamplona.ammolite.relays.NostrClient
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
|
||||
/**
|
||||
* This assembler observes modifications to the LiveActivity root events
|
||||
* since they are replaceable and updates to the public chat metadata events.
|
||||
*
|
||||
* They are all cramped as multiple filters in a single subscription with
|
||||
* only one EOSE for everybody.
|
||||
*/
|
||||
class ChannelMetadataAndLiveActivityWatcherSubAssembler(
|
||||
client: NostrClient,
|
||||
allKeys: () -> Set<ChannelFinderQueryState>,
|
||||
) : SingleSubEoseManager<ChannelFinderQueryState>(client, allKeys) {
|
||||
override fun updateFilter(
|
||||
keys: List<ChannelFinderQueryState>,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? =
|
||||
keys
|
||||
.mapNotNull { key ->
|
||||
if (key.channel is PublicChatChannel) {
|
||||
filterChannelMetadataUpdatesById(key.channel, since)
|
||||
} else if (key.channel is LiveActivitiesChannel) {
|
||||
filterLiveStreamUpdatesByAddress(key.channel, since)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}.flatten()
|
||||
|
||||
override fun distinct(key: ChannelFinderQueryState) = key.channel.idHex
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.service.relayClient.reqCommand.channel.nip28PublicChats
|
||||
|
||||
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.SingleSubEoseManager
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.ChannelFinderQueryState
|
||||
import com.vitorpamplona.ammolite.relays.NostrClient
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
|
||||
/**
|
||||
* This assembler observes loads missing public chats when needed.
|
||||
*
|
||||
* It is a fast rotating filter that tends to zero as new events arrive.
|
||||
* Because of that, we isolate to avoid interrupting the EOSE of filters
|
||||
* with long loading times.
|
||||
*
|
||||
* This is one filter for everybody.
|
||||
*/
|
||||
class ChannelLoaderSubAssembler(
|
||||
client: NostrClient,
|
||||
allKeys: () -> Set<ChannelFinderQueryState>,
|
||||
) : SingleSubEoseManager<ChannelFinderQueryState>(client, allKeys, invalidateAfterEose = true) {
|
||||
override fun updateFilter(
|
||||
keys: List<ChannelFinderQueryState>,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? = filterMissingChannelsById(keys, since)
|
||||
|
||||
override fun distinct(key: ChannelFinderQueryState) = key.channel.idHex
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.service.relayClient.reqCommand.channel.nip28PublicChats
|
||||
|
||||
import com.vitorpamplona.amethyst.model.PublicChatChannel
|
||||
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUniqueIdEoseManager
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.ChannelFinderQueryState
|
||||
import com.vitorpamplona.ammolite.relays.NostrClient
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
|
||||
class ChannelMetadataWatcherSubAssembler(
|
||||
client: NostrClient,
|
||||
allKeys: () -> Set<ChannelFinderQueryState>,
|
||||
) : PerUniqueIdEoseManager<ChannelFinderQueryState>(client, allKeys) {
|
||||
override fun updateFilter(
|
||||
key: ChannelFinderQueryState,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? =
|
||||
if (key.channel is PublicChatChannel) {
|
||||
filterChannelMetadataUpdatesById(key.channel, since)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
/**
|
||||
* Only one key per channel.
|
||||
*/
|
||||
override fun id(key: ChannelFinderQueryState) = key.channel.idHex
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.service.relayClient.reqCommand.channel.nip28PublicChats
|
||||
|
||||
import com.vitorpamplona.amethyst.model.PublicChatChannel
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.ChannelFinderQueryState
|
||||
import com.vitorpamplona.ammolite.relays.EVENT_FINDER_TYPES
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent
|
||||
|
||||
fun filterMissingChannelsById(
|
||||
keys: List<ChannelFinderQueryState>,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? {
|
||||
val firstTimers =
|
||||
keys.mapNotNullTo(mutableSetOf()) {
|
||||
if (it.channel is PublicChatChannel && it.channel.event == null) {
|
||||
it.channel.idHex
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
if (firstTimers.isEmpty()) return null
|
||||
|
||||
return listOf(
|
||||
TypedFilter(
|
||||
types = EVENT_FINDER_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(ChannelCreateEvent.KIND),
|
||||
ids = firstTimers.toList(),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.service.relayClient.reqCommand.channel.nip28PublicChats
|
||||
|
||||
import com.vitorpamplona.amethyst.model.PublicChatChannel
|
||||
import com.vitorpamplona.ammolite.relays.FeedType
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelMetadataEvent
|
||||
|
||||
fun filterChannelMetadataUpdatesById(
|
||||
channel: PublicChatChannel,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter> =
|
||||
listOf(
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.PUBLIC_CHATS),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(ChannelMetadataEvent.KIND),
|
||||
tags = mapOf("e" to listOf(channel.idHex)),
|
||||
since = since,
|
||||
),
|
||||
),
|
||||
)
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.service.relayClient.reqCommand.channel.nip53LiveActivities
|
||||
|
||||
import com.vitorpamplona.amethyst.model.LiveActivitiesChannel
|
||||
import com.vitorpamplona.ammolite.relays.EVENT_FINDER_TYPES
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
|
||||
fun filterLiveStreamUpdatesByAddress(
|
||||
channel: LiveActivitiesChannel,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter> {
|
||||
val stream = channel.address()
|
||||
return listOf(
|
||||
TypedFilter(
|
||||
types = EVENT_FINDER_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(stream.kind),
|
||||
tags = mapOf("d" to listOf(stream.dTag)),
|
||||
authors = listOf(stream.pubKeyHex),
|
||||
since = since,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.service.relayClient.reqCommand.channel.nip53LiveActivities
|
||||
|
||||
import com.vitorpamplona.amethyst.model.LiveActivitiesChannel
|
||||
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUniqueIdEoseManager
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.ChannelFinderQueryState
|
||||
import com.vitorpamplona.ammolite.relays.NostrClient
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
|
||||
/**
|
||||
* This assembler observes modifications to the LiveActivity root events
|
||||
* since they are replaceable.
|
||||
*/
|
||||
class LiveActivityWatcherSubAssembly(
|
||||
client: NostrClient,
|
||||
allKeys: () -> Set<ChannelFinderQueryState>,
|
||||
) : PerUniqueIdEoseManager<ChannelFinderQueryState>(client, allKeys) {
|
||||
override fun updateFilter(
|
||||
key: ChannelFinderQueryState,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? =
|
||||
if (key.channel is LiveActivitiesChannel) {
|
||||
filterLiveStreamUpdatesByAddress(key.channel, since)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
/**
|
||||
* Only one key per channel.
|
||||
*/
|
||||
override fun id(key: ChannelFinderQueryState) = key.channel.idHex
|
||||
}
|
||||
+14
-299
@@ -20,32 +20,11 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.service.relayClient.reqCommand.event
|
||||
|
||||
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.QueryBasedSubscriptionOrchestrator
|
||||
import com.vitorpamplona.ammolite.relays.EVENT_FINDER_TYPES
|
||||
import com.vitorpamplona.amethyst.service.relayClient.composeSubscriptionManagers.ComposeSubscriptionManager
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.loaders.NoteEventLoaderSubAssembler
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.watchers.EventWatcherSubAssembler
|
||||
import com.vitorpamplona.ammolite.relays.NostrClient
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.experimental.edits.TextNoteModificationEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.OtsEvent
|
||||
import com.vitorpamplona.quartz.nip09Deletions.DeletionEvent
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
|
||||
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
|
||||
import com.vitorpamplona.quartz.nip34Git.reply.GitReplyEvent
|
||||
import com.vitorpamplona.quartz.nip35Torrents.TorrentCommentEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent
|
||||
import com.vitorpamplona.quartz.nip56Reports.ReportEvent
|
||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.approval.CommunityPostApprovalEvent
|
||||
import com.vitorpamplona.quartz.nip90Dvms.NIP90ContentDiscoveryResponseEvent
|
||||
import com.vitorpamplona.quartz.nip90Dvms.NIP90StatusEvent
|
||||
|
||||
// This allows multiple screen to be listening to tags, even the same tag
|
||||
class EventFinderQueryState(
|
||||
@@ -54,286 +33,22 @@ class EventFinderQueryState(
|
||||
|
||||
class EventFinderFilterAssembler(
|
||||
client: NostrClient,
|
||||
) : QueryBasedSubscriptionOrchestrator<EventFinderQueryState>(client) {
|
||||
private fun createReactionsToWatchInAddressFilter(keys: Set<AddressableNote>): List<TypedFilter>? {
|
||||
if (keys.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
|
||||
return groupByEOSEPresence(keys)
|
||||
.map {
|
||||
listOf(
|
||||
TypedFilter(
|
||||
types = EVENT_FINDER_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds =
|
||||
listOf(
|
||||
TextNoteEvent.KIND,
|
||||
ReactionEvent.KIND,
|
||||
RepostEvent.KIND,
|
||||
GenericRepostEvent.KIND,
|
||||
ReportEvent.KIND,
|
||||
LnZapEvent.KIND,
|
||||
PollNoteEvent.KIND,
|
||||
CommunityPostApprovalEvent.KIND,
|
||||
LiveActivitiesChatMessageEvent.KIND,
|
||||
),
|
||||
tags = mapOf("a" to it.mapNotNull { it.address()?.toValue() }),
|
||||
since = findMinimumEOSEs(it),
|
||||
// Max amount of "replies" to download on a specific event.
|
||||
limit = 1000,
|
||||
),
|
||||
),
|
||||
TypedFilter(
|
||||
types = EVENT_FINDER_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds =
|
||||
listOf(
|
||||
DeletionEvent.KIND,
|
||||
),
|
||||
tags = mapOf("a" to it.mapNotNull { it.address()?.toValue() }),
|
||||
since = findMinimumEOSEs(it),
|
||||
// Max amount of "replies" to download on a specific event.
|
||||
limit = 10,
|
||||
),
|
||||
),
|
||||
)
|
||||
}.flatten()
|
||||
}
|
||||
|
||||
private fun createAddressFilter(keys: Set<AddressableNote>): List<TypedFilter>? {
|
||||
val myAddressesToWatch = keys.filter { it.event == null }
|
||||
|
||||
if (myAddressesToWatch.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
|
||||
return myAddressesToWatch.map {
|
||||
it.address().let { aTag ->
|
||||
if (aTag.kind < 25000 && aTag.dTag.isBlank()) {
|
||||
TypedFilter(
|
||||
types = EVENT_FINDER_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(aTag.kind),
|
||||
authors = listOf(aTag.pubKeyHex),
|
||||
limit = 5,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
TypedFilter(
|
||||
types = EVENT_FINDER_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(aTag.kind),
|
||||
tags = mapOf("d" to listOf(aTag.dTag)),
|
||||
authors = listOf(aTag.pubKeyHex),
|
||||
limit = 5,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createRepliesAndReactionsFilter(keys: Set<Note>): List<TypedFilter>? {
|
||||
if (keys.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
|
||||
return groupByEOSEPresence(keys)
|
||||
.map {
|
||||
listOf(
|
||||
TypedFilter(
|
||||
types = EVENT_FINDER_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds =
|
||||
listOf(
|
||||
TextNoteEvent.KIND,
|
||||
ReactionEvent.KIND,
|
||||
RepostEvent.KIND,
|
||||
GenericRepostEvent.KIND,
|
||||
ReportEvent.KIND,
|
||||
LnZapEvent.KIND,
|
||||
PollNoteEvent.KIND,
|
||||
OtsEvent.KIND,
|
||||
TextNoteModificationEvent.KIND,
|
||||
GitReplyEvent.KIND,
|
||||
),
|
||||
tags = mapOf("e" to it.map { it.idHex }),
|
||||
since = findMinimumEOSEs(it),
|
||||
// Max amount of "replies" to download on a specific event.
|
||||
limit = 10000,
|
||||
),
|
||||
),
|
||||
TypedFilter(
|
||||
types = EVENT_FINDER_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds =
|
||||
listOf(
|
||||
DeletionEvent.KIND,
|
||||
NIP90ContentDiscoveryResponseEvent.KIND,
|
||||
NIP90StatusEvent.KIND,
|
||||
TorrentCommentEvent.KIND,
|
||||
),
|
||||
tags = mapOf("e" to it.map { it.idHex }),
|
||||
since = findMinimumEOSEs(it),
|
||||
limit = 100,
|
||||
),
|
||||
),
|
||||
)
|
||||
}.flatten()
|
||||
}
|
||||
|
||||
private fun createQuotesFilter(keys: Set<Note>): List<TypedFilter>? =
|
||||
groupByEOSEPresence(keys)
|
||||
.map {
|
||||
listOf(
|
||||
TypedFilter(
|
||||
types = EVENT_FINDER_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(TextNoteEvent.KIND),
|
||||
tags = mapOf("q" to it.map { it.idHex }),
|
||||
since = findMinimumEOSEs(it),
|
||||
// Max amount of "replies" to download on a specific event.
|
||||
limit = 1000,
|
||||
),
|
||||
),
|
||||
)
|
||||
}.flatten()
|
||||
|
||||
fun createLoadEventsIfNotLoadedFilter(keys: Set<Note>): List<TypedFilter>? {
|
||||
val directEventsToLoad = keys.filter { it.event == null }
|
||||
|
||||
val threadingEventsToLoad =
|
||||
keys
|
||||
.mapNotNull { it.replyTo }
|
||||
.flatten()
|
||||
.filter { it !is AddressableNote && it.event == null }
|
||||
|
||||
val interestedEvents = (directEventsToLoad + threadingEventsToLoad).map { it.idHex }.toSet().toList()
|
||||
|
||||
if (interestedEvents.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
|
||||
// downloads linked events to this event.
|
||||
return listOf(
|
||||
TypedFilter(
|
||||
types = EVENT_FINDER_TYPES,
|
||||
filter = SincePerRelayFilter(ids = interestedEvents),
|
||||
),
|
||||
) : ComposeSubscriptionManager<EventFinderQueryState>() {
|
||||
val group =
|
||||
listOf(
|
||||
NoteEventLoaderSubAssembler(client, ::allKeys),
|
||||
EventWatcherSubAssembler(client, ::allKeys),
|
||||
)
|
||||
}
|
||||
|
||||
val singleEventChannel =
|
||||
requestNewSubscription { time, relayUrl ->
|
||||
// Ignores EOSE if it is in the middle of a filter change.
|
||||
if (isUpdatingFilters()) return@requestNewSubscription
|
||||
override fun start() = group.forEach { it.start() }
|
||||
|
||||
checkNotInMainThread()
|
||||
override fun stop() = group.forEach { it.stop() }
|
||||
|
||||
forEachSubscriber {
|
||||
val eose = it.note.lastReactionsDownloadTime[relayUrl]
|
||||
if (eose == null) {
|
||||
it.note.lastReactionsDownloadTime += Pair(relayUrl, EOSETime(time))
|
||||
} else {
|
||||
eose.time = time
|
||||
}
|
||||
}
|
||||
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
|
||||
|
||||
// Many relays operate with limits in the amount of filters.
|
||||
// As information comes, the filters will be rotated to get more data.
|
||||
invalidateFilters()
|
||||
}
|
||||
override fun invalidateKeys() = invalidateFilters()
|
||||
|
||||
override fun updateSubscriptions(keys: Set<EventFinderQueryState>) {
|
||||
val addressables =
|
||||
keys
|
||||
.mapNotNullTo(mutableSetOf()) {
|
||||
it.note as? AddressableNote
|
||||
}.toSet()
|
||||
override fun destroy() = group.forEach { it.destroy() }
|
||||
|
||||
val events =
|
||||
keys
|
||||
.mapNotNullTo(mutableSetOf()) {
|
||||
if (it.note !is AddressableNote) {
|
||||
it.note
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}.toSet()
|
||||
|
||||
val eventReactions = createRepliesAndReactionsFilter(events)
|
||||
val addressReactions = createReactionsToWatchInAddressFilter(addressables)
|
||||
|
||||
val missingEvents = createLoadEventsIfNotLoadedFilter(events)
|
||||
val missingAddresses = createAddressFilter(addressables)
|
||||
|
||||
val quotes = createQuotesFilter(events + addressables)
|
||||
|
||||
singleEventChannel.typedFilters =
|
||||
listOfNotNull(missingEvents, missingAddresses, eventReactions, addressReactions, quotes).flatten().ifEmpty { null }
|
||||
}
|
||||
}
|
||||
|
||||
fun groupByEOSEPresence(notes: Set<Note>): Collection<List<Note>> =
|
||||
notes
|
||||
.groupBy {
|
||||
it.lastReactionsDownloadTime.keys
|
||||
.sorted()
|
||||
.joinToString(",")
|
||||
}.values
|
||||
.map {
|
||||
it.sortedBy { it.idHex } // important to keep in order otherwise the Relay thinks the filter has changed and we REQ again
|
||||
}
|
||||
|
||||
fun groupByEOSEPresence(users: Iterable<User>): Collection<List<User>> =
|
||||
users
|
||||
.groupBy {
|
||||
it.latestEOSEs.keys
|
||||
.sorted()
|
||||
.joinToString(",")
|
||||
}.values
|
||||
.map {
|
||||
it.sortedBy { it.pubkeyHex } // important to keep in order otherwise the Relay thinks the filter has changed and we REQ again
|
||||
}
|
||||
|
||||
fun findMinimumEOSEs(notes: List<Note>): Map<String, EOSETime> {
|
||||
val minLatestEOSEs = mutableMapOf<String, EOSETime>()
|
||||
|
||||
notes.forEach { note ->
|
||||
note.lastReactionsDownloadTime.forEach {
|
||||
val minEose = minLatestEOSEs[it.key]
|
||||
if (minEose == null) {
|
||||
minLatestEOSEs.put(it.key, EOSETime(it.value.time))
|
||||
} else if (it.value.time < minEose.time) {
|
||||
minEose.time = it.value.time
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return minLatestEOSEs
|
||||
}
|
||||
|
||||
fun findMinimumEOSEsForUsers(users: List<User>): Map<String, EOSETime> {
|
||||
val minLatestEOSEs = mutableMapOf<String, EOSETime>()
|
||||
|
||||
users.forEach {
|
||||
it.latestEOSEs.forEach {
|
||||
val minEose = minLatestEOSEs[it.key]
|
||||
if (minEose == null) {
|
||||
minLatestEOSEs.put(it.key, EOSETime(it.value.time))
|
||||
} else if (it.value.time < minEose.time) {
|
||||
minEose.time = it.value.time
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return minLatestEOSEs
|
||||
override fun printStats() = group.forEach { it.printStats() }
|
||||
}
|
||||
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.service.relayClient.reqCommand.event.loaders
|
||||
|
||||
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.EventFinderQueryState
|
||||
import com.vitorpamplona.ammolite.relays.EVENT_FINDER_TYPES
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.addressables.Address
|
||||
|
||||
fun filterMissingAddressables(
|
||||
keys: List<EventFinderQueryState>,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? {
|
||||
val missingAddressables = mutableSetOf<Address>()
|
||||
|
||||
keys.forEach {
|
||||
if (it.note is AddressableNote && it.note.event == null) {
|
||||
missingAddressables.add(it.note.address())
|
||||
}
|
||||
|
||||
// loads threading that is event-based
|
||||
it.note.replyTo?.forEach {
|
||||
if (it is AddressableNote && it.event == null) {
|
||||
missingAddressables.add(it.address())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return filterMissingAddressables(missingAddressables, since)
|
||||
}
|
||||
|
||||
fun filterMissingAddressables(
|
||||
missingAddressables: Set<Address>,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? {
|
||||
if (missingAddressables.isEmpty()) return null
|
||||
|
||||
return missingAddressables.map { aTag ->
|
||||
if (aTag.kind < 25000 && aTag.dTag.isBlank()) {
|
||||
TypedFilter(
|
||||
types = EVENT_FINDER_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(aTag.kind),
|
||||
authors = listOf(aTag.pubKeyHex),
|
||||
limit = 1,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
TypedFilter(
|
||||
types = EVENT_FINDER_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(aTag.kind),
|
||||
tags = mapOf("d" to listOf(aTag.dTag)),
|
||||
authors = listOf(aTag.pubKeyHex),
|
||||
limit = 1,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.service.relayClient.reqCommand.event.loaders
|
||||
|
||||
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.EventFinderQueryState
|
||||
import com.vitorpamplona.ammolite.relays.EVENT_FINDER_TYPES
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
|
||||
fun filterMissingEvents(
|
||||
keys: List<EventFinderQueryState>,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? {
|
||||
val missingEvents = mutableSetOf<String>()
|
||||
|
||||
keys.forEach {
|
||||
if (it.note !is AddressableNote && it.note.event == null) {
|
||||
missingEvents.add(it.note.idHex)
|
||||
}
|
||||
|
||||
// loads threading that is event-based
|
||||
it.note.replyTo?.forEach {
|
||||
if (it !is AddressableNote && it.event == null) {
|
||||
missingEvents.add(it.idHex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return filterMissingEvents(missingEvents, since)
|
||||
}
|
||||
|
||||
fun filterMissingEvents(
|
||||
missingEventIds: Set<HexKey>,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? {
|
||||
if (missingEventIds.isEmpty()) return null
|
||||
|
||||
return listOf(
|
||||
TypedFilter(
|
||||
types = EVENT_FINDER_TYPES,
|
||||
filter = SincePerRelayFilter(ids = missingEventIds.toList()),
|
||||
),
|
||||
)
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.service.relayClient.reqCommand.event.loaders
|
||||
|
||||
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.SingleSubEoseManager
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.EventFinderQueryState
|
||||
import com.vitorpamplona.ammolite.relays.NostrClient
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
|
||||
class NoteEventLoaderSubAssembler(
|
||||
client: NostrClient,
|
||||
allKeys: () -> Set<EventFinderQueryState>,
|
||||
) : SingleSubEoseManager<EventFinderQueryState>(client, allKeys, invalidateAfterEose = true) {
|
||||
override fun updateFilter(
|
||||
keys: List<EventFinderQueryState>,
|
||||
since: Map<String, EOSETime>?,
|
||||
) = listOfNotNull(
|
||||
filterMissingEvents(keys, since),
|
||||
filterMissingAddressables(keys, since),
|
||||
).flatten()
|
||||
|
||||
override fun distinct(key: EventFinderQueryState) = key.note
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.service.relayClient.reqCommand.event.watchers
|
||||
|
||||
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.SingleSubEoseManager
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.EventFinderQueryState
|
||||
import com.vitorpamplona.ammolite.relays.NostrClient
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
|
||||
class EventWatcherSubAssembler(
|
||||
client: NostrClient,
|
||||
allKeys: () -> Set<EventFinderQueryState>,
|
||||
) : SingleSubEoseManager<EventFinderQueryState>(client, allKeys) {
|
||||
var lastNotesOnFilter = emptyList<Note>()
|
||||
|
||||
override fun newEose(
|
||||
relayUrl: String,
|
||||
time: Long,
|
||||
) {
|
||||
lastNotesOnFilter.forEach {
|
||||
it.lastReactionsDownloadTime.newEose(relayUrl, time)
|
||||
}
|
||||
super.newEose(relayUrl, time)
|
||||
}
|
||||
|
||||
override fun updateFilter(
|
||||
keys: List<EventFinderQueryState>,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? {
|
||||
if (keys.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
|
||||
lastNotesOnFilter = keys.map { it.note }
|
||||
val addressables = lastNotesOnFilter.filterIsInstance<AddressableNote>()
|
||||
val events = keys.mapNotNull { if (it.note !is AddressableNote) it.note else null }
|
||||
|
||||
return groupByEOSEPresence(lastNotesOnFilter)
|
||||
.map {
|
||||
listOfNotNull(
|
||||
filterRepliesAndReactionsToNotes(events),
|
||||
filterRepliesAndReactionsToAddresses(addressables),
|
||||
filterQuotesToNotes(it),
|
||||
).flatten()
|
||||
}.flatten()
|
||||
}
|
||||
|
||||
override fun distinct(key: EventFinderQueryState) = key.note
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.service.relayClient.reqCommand.event.watchers
|
||||
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.ammolite.relays.EVENT_FINDER_TYPES
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
|
||||
fun filterQuotesToNotes(keys: List<Note>): List<TypedFilter>? {
|
||||
if (keys.isEmpty()) return null
|
||||
|
||||
return listOf(
|
||||
TypedFilter(
|
||||
types = EVENT_FINDER_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(TextNoteEvent.KIND),
|
||||
tags = mapOf("q" to keys.map { it.idHex }),
|
||||
since = findMinimumEOSEs(keys),
|
||||
// Max amount of "replies" to download on a specific event.
|
||||
limit = 1000,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.service.relayClient.reqCommand.event.watchers
|
||||
|
||||
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||
import com.vitorpamplona.ammolite.relays.EVENT_FINDER_TYPES
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.nip09Deletions.DeletionEvent
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
|
||||
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent
|
||||
import com.vitorpamplona.quartz.nip56Reports.ReportEvent
|
||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.approval.CommunityPostApprovalEvent
|
||||
|
||||
fun filterRepliesAndReactionsToAddresses(keys: List<AddressableNote>): List<TypedFilter>? {
|
||||
if (keys.isEmpty()) return null
|
||||
|
||||
val addresses = keys.map { it.address().toValue() }
|
||||
|
||||
return listOf(
|
||||
TypedFilter(
|
||||
types = EVENT_FINDER_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds =
|
||||
listOf(
|
||||
TextNoteEvent.KIND,
|
||||
ReactionEvent.KIND,
|
||||
RepostEvent.KIND,
|
||||
GenericRepostEvent.KIND,
|
||||
ReportEvent.KIND,
|
||||
LnZapEvent.KIND,
|
||||
PollNoteEvent.KIND,
|
||||
CommunityPostApprovalEvent.KIND,
|
||||
LiveActivitiesChatMessageEvent.KIND,
|
||||
),
|
||||
tags = mapOf("a" to addresses),
|
||||
since = findMinimumEOSEs(keys),
|
||||
// Max amount of "replies" to download on a specific event.
|
||||
limit = 1000,
|
||||
),
|
||||
),
|
||||
TypedFilter(
|
||||
types = EVENT_FINDER_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds =
|
||||
listOf(
|
||||
DeletionEvent.KIND,
|
||||
),
|
||||
tags = mapOf("a" to addresses),
|
||||
since = findMinimumEOSEs(keys),
|
||||
// Max amount of "replies" to download on a specific event.
|
||||
limit = 10,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.service.relayClient.reqCommand.event.watchers
|
||||
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.ammolite.relays.EVENT_FINDER_TYPES
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.experimental.edits.TextNoteModificationEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.OtsEvent
|
||||
import com.vitorpamplona.quartz.nip09Deletions.DeletionEvent
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
|
||||
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
|
||||
import com.vitorpamplona.quartz.nip34Git.reply.GitReplyEvent
|
||||
import com.vitorpamplona.quartz.nip35Torrents.TorrentCommentEvent
|
||||
import com.vitorpamplona.quartz.nip56Reports.ReportEvent
|
||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
|
||||
import com.vitorpamplona.quartz.nip90Dvms.NIP90ContentDiscoveryResponseEvent
|
||||
import com.vitorpamplona.quartz.nip90Dvms.NIP90StatusEvent
|
||||
|
||||
fun filterRepliesAndReactionsToNotes(keys: List<Note>): List<TypedFilter>? {
|
||||
if (keys.isEmpty()) return null
|
||||
|
||||
return listOf(
|
||||
TypedFilter(
|
||||
types = EVENT_FINDER_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds =
|
||||
listOf(
|
||||
TextNoteEvent.KIND,
|
||||
ReactionEvent.KIND,
|
||||
RepostEvent.KIND,
|
||||
GenericRepostEvent.KIND,
|
||||
ReportEvent.KIND,
|
||||
LnZapEvent.KIND,
|
||||
PollNoteEvent.KIND,
|
||||
OtsEvent.KIND,
|
||||
TextNoteModificationEvent.KIND,
|
||||
GitReplyEvent.KIND,
|
||||
),
|
||||
tags = mapOf("e" to keys.map { it.idHex }),
|
||||
since = findMinimumEOSEs(keys),
|
||||
// Max amount of "replies" to download on a specific event.
|
||||
limit = 1000,
|
||||
),
|
||||
),
|
||||
TypedFilter(
|
||||
types = EVENT_FINDER_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds =
|
||||
listOf(
|
||||
DeletionEvent.KIND,
|
||||
NIP90ContentDiscoveryResponseEvent.KIND,
|
||||
NIP90StatusEvent.KIND,
|
||||
TorrentCommentEvent.KIND,
|
||||
),
|
||||
tags = mapOf("e" to keys.map { it.idHex }),
|
||||
since = findMinimumEOSEs(keys),
|
||||
limit = 100,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.service.relayClient.reqCommand.event.watchers
|
||||
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
|
||||
fun groupByEOSEPresence(notes: Iterable<Note>): Collection<List<Note>> =
|
||||
notes
|
||||
.groupBy {
|
||||
it.lastReactionsDownloadTime.relayList.keys
|
||||
.sorted()
|
||||
.joinToString(",")
|
||||
}.values
|
||||
.map {
|
||||
it.sortedBy { it.idHex } // important to keep in order otherwise the Relay thinks the filter has changed and we REQ again
|
||||
}
|
||||
|
||||
fun findMinimumEOSEs(notes: List<Note>): Map<String, EOSETime> {
|
||||
val minLatestEOSEs = mutableMapOf<String, EOSETime>()
|
||||
|
||||
notes.forEach { note ->
|
||||
note.lastReactionsDownloadTime.relayList.forEach {
|
||||
val minEose = minLatestEOSEs[it.key]
|
||||
if (minEose == null) {
|
||||
minLatestEOSEs.put(it.key, EOSETime(it.value.time))
|
||||
} else if (it.value.time < minEose.time) {
|
||||
minEose.time = it.value.time
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return minLatestEOSEs
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.service.relayClient.reqCommand.nwc
|
||||
|
||||
import com.vitorpamplona.ammolite.relays.FeedType
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.LnZapPaymentResponseEvent
|
||||
|
||||
fun filterNWCPaymentsFromRequests(
|
||||
serviceKeys: Set<HexKey>,
|
||||
paymentRequets: Set<HexKey>,
|
||||
fromUsers: Set<HexKey>,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter> =
|
||||
listOf(
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.WALLET_CONNECT),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(LnZapPaymentResponseEvent.KIND),
|
||||
authors = serviceKeys.toList(),
|
||||
tags =
|
||||
mapOf(
|
||||
"e" to paymentRequets.toList(),
|
||||
"p" to fromUsers.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
+15
-36
@@ -20,12 +20,8 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.service.relayClient.reqCommand.nwc
|
||||
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.QueryBasedSubscriptionOrchestrator
|
||||
import com.vitorpamplona.ammolite.relays.FeedType
|
||||
import com.vitorpamplona.amethyst.service.relayClient.composeSubscriptionManagers.ComposeSubscriptionManager
|
||||
import com.vitorpamplona.ammolite.relays.NostrClient
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.LnZapPaymentResponseEvent
|
||||
|
||||
// This allows multiple screen to be listening to tags, even the same tag
|
||||
class NWCPaymentQueryState(
|
||||
@@ -36,38 +32,21 @@ class NWCPaymentQueryState(
|
||||
|
||||
class NWCPaymentFilterAssembler(
|
||||
client: NostrClient,
|
||||
) : QueryBasedSubscriptionOrchestrator<NWCPaymentQueryState>(client) {
|
||||
fun createAccountMetadataFilter(keys: Set<NWCPaymentQueryState>): TypedFilter? {
|
||||
val fromAuthors = keys.mapTo(mutableSetOf()) { it.fromServiceHex }
|
||||
val replyingToPayments = keys.mapTo(mutableSetOf()) { it.replyingToHex }
|
||||
val aboutUsers = keys.mapTo(mutableSetOf()) { it.toUserHex }
|
||||
|
||||
if (fromAuthors.isEmpty()) return null
|
||||
|
||||
return TypedFilter(
|
||||
types = setOf(FeedType.WALLET_CONNECT),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(LnZapPaymentResponseEvent.KIND),
|
||||
authors = fromAuthors.toList(),
|
||||
tags =
|
||||
mapOf(
|
||||
"e" to replyingToPayments.toList(),
|
||||
"p" to aboutUsers.toList(),
|
||||
),
|
||||
limit = 1,
|
||||
),
|
||||
) : ComposeSubscriptionManager<NWCPaymentQueryState>() {
|
||||
val group =
|
||||
listOf(
|
||||
NWCPaymentWatcherSubAssembler(client, ::allKeys),
|
||||
)
|
||||
}
|
||||
|
||||
val channel = requestNewSubscription()
|
||||
override fun start() = group.forEach { it.start() }
|
||||
|
||||
override fun updateSubscriptions(keys: Set<NWCPaymentQueryState>) {
|
||||
val filter = createAccountMetadataFilter(keys)
|
||||
if (filter != null) {
|
||||
channel.typedFilters = listOf(filter)
|
||||
} else {
|
||||
channel.typedFilters = null
|
||||
}
|
||||
}
|
||||
override fun stop() = group.forEach { it.stop() }
|
||||
|
||||
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
|
||||
|
||||
override fun invalidateKeys() = invalidateFilters()
|
||||
|
||||
override fun destroy() = group.forEach { it.destroy() }
|
||||
|
||||
override fun printStats() = group.forEach { it.printStats() }
|
||||
}
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.service.relayClient.reqCommand.nwc
|
||||
|
||||
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.SingleSubEoseManager
|
||||
import com.vitorpamplona.ammolite.relays.NostrClient
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
|
||||
class NWCPaymentWatcherSubAssembler(
|
||||
client: NostrClient,
|
||||
allKeys: () -> Set<NWCPaymentQueryState>,
|
||||
) : SingleSubEoseManager<NWCPaymentQueryState>(client, allKeys) {
|
||||
override fun updateFilter(
|
||||
keys: List<NWCPaymentQueryState>,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? {
|
||||
val fromAuthors = keys.mapTo(mutableSetOf()) { it.fromServiceHex }
|
||||
val replyingToPayments = keys.mapTo(mutableSetOf()) { it.replyingToHex }
|
||||
val aboutUsers = keys.mapTo(mutableSetOf()) { it.toUserHex }
|
||||
|
||||
if (fromAuthors.isEmpty()) return null
|
||||
|
||||
return filterNWCPaymentsFromRequests(fromAuthors, replyingToPayments, aboutUsers, since)
|
||||
}
|
||||
|
||||
override fun distinct(key: NWCPaymentQueryState) = key.replyingToHex
|
||||
}
|
||||
+14
-92
@@ -21,20 +21,10 @@
|
||||
package com.vitorpamplona.amethyst.service.relayClient.reqCommand.user
|
||||
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.QueryBasedSubscriptionOrchestrator
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.findMinimumEOSEsForUsers
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.groupByEOSEPresence
|
||||
import com.vitorpamplona.ammolite.relays.EVENT_FINDER_TYPES
|
||||
import com.vitorpamplona.amethyst.service.relayClient.composeSubscriptionManagers.ComposeSubscriptionManager
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.loaders.UserLoaderSubAssembler
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.watchers.UserWatcherSubAssembler
|
||||
import com.vitorpamplona.ammolite.relays.NostrClient
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.experimental.relationshipStatus.RelationshipStatusEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip17Dm.settings.ChatMessageRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip38UserStatus.StatusEvent
|
||||
import com.vitorpamplona.quartz.nip56Reports.ReportEvent
|
||||
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
|
||||
|
||||
// This allows multiple screen to be listening to tags, even the same tag
|
||||
class UserFinderQueryState(
|
||||
@@ -43,90 +33,22 @@ class UserFinderQueryState(
|
||||
|
||||
class UserFinderFilterAssembler(
|
||||
client: NostrClient,
|
||||
) : QueryBasedSubscriptionOrchestrator<UserFinderQueryState>(client) {
|
||||
fun createUserMetadataFilter(keys: Set<UserFinderQueryState>): List<TypedFilter>? {
|
||||
if (keys.isEmpty()) return null
|
||||
|
||||
val firstTimers = keys.filter { it.user.latestMetadata == null }.map { it.user.pubkeyHex }.distinct()
|
||||
|
||||
if (firstTimers.isEmpty()) return null
|
||||
|
||||
return listOf(
|
||||
TypedFilter(
|
||||
types = EVENT_FINDER_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(MetadataEvent.KIND, AdvertisedRelayListEvent.KIND),
|
||||
authors = firstTimers,
|
||||
),
|
||||
),
|
||||
) : ComposeSubscriptionManager<UserFinderQueryState>() {
|
||||
val group =
|
||||
listOf(
|
||||
UserLoaderSubAssembler(client, ::allKeys),
|
||||
UserWatcherSubAssembler(client, ::allKeys),
|
||||
)
|
||||
}
|
||||
|
||||
fun createUserMetadataStatusReportFilter(keys: Set<UserFinderQueryState>): List<TypedFilter>? {
|
||||
if (keys.isEmpty()) return null
|
||||
override fun start() = group.forEach { it.start() }
|
||||
|
||||
val secondTimers = keys.filter { it.user.latestMetadata != null }.map { it.user }.toSet()
|
||||
override fun stop() = group.forEach { it.stop() }
|
||||
|
||||
if (secondTimers.isEmpty()) return null
|
||||
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
|
||||
|
||||
return groupByEOSEPresence(secondTimers)
|
||||
.map { group ->
|
||||
val groupIds = group.map { it.pubkeyHex }
|
||||
val minEOSEs = findMinimumEOSEsForUsers(group)
|
||||
override fun invalidateKeys() = invalidateFilters()
|
||||
|
||||
if (groupIds.isNotEmpty()) {
|
||||
listOf(
|
||||
TypedFilter(
|
||||
types = EVENT_FINDER_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds =
|
||||
listOf(
|
||||
MetadataEvent.KIND,
|
||||
StatusEvent.KIND,
|
||||
RelationshipStatusEvent.KIND,
|
||||
AdvertisedRelayListEvent.KIND,
|
||||
ChatMessageRelayListEvent.KIND,
|
||||
),
|
||||
authors = groupIds,
|
||||
since = minEOSEs,
|
||||
),
|
||||
),
|
||||
TypedFilter(
|
||||
types = EVENT_FINDER_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(ReportEvent.KIND),
|
||||
tags = mapOf("p" to groupIds),
|
||||
since = minEOSEs,
|
||||
),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
listOf()
|
||||
}
|
||||
}.flatten()
|
||||
}
|
||||
override fun destroy() = group.forEach { it.destroy() }
|
||||
|
||||
val userChannel =
|
||||
requestNewSubscription { time, relayUrl ->
|
||||
forEachSubscriber {
|
||||
val eose = it.user.latestEOSEs[relayUrl]
|
||||
if (eose == null) {
|
||||
it.user.latestEOSEs = it.user.latestEOSEs + Pair(relayUrl, EOSETime(time))
|
||||
} else {
|
||||
eose.time = time
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateSubscriptions(keys: Set<UserFinderQueryState>) {
|
||||
userChannel.typedFilters =
|
||||
listOfNotNull(
|
||||
createUserMetadataFilter(keys),
|
||||
createUserMetadataStatusReportFilter(keys),
|
||||
).flatten()
|
||||
.ifEmpty { null }
|
||||
}
|
||||
override fun printStats() = group.forEach { it.printStats() }
|
||||
}
|
||||
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.service.relayClient.reqCommand.user.loaders
|
||||
|
||||
import com.vitorpamplona.ammolite.relays.EVENT_FINDER_TYPES
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
|
||||
|
||||
fun filterNewUserMetadataForKey(
|
||||
authors: Set<HexKey>,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter> =
|
||||
listOf(
|
||||
TypedFilter(
|
||||
types = EVENT_FINDER_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds =
|
||||
listOf(
|
||||
MetadataEvent.KIND,
|
||||
AdvertisedRelayListEvent.KIND,
|
||||
),
|
||||
authors = authors.toList(),
|
||||
),
|
||||
),
|
||||
)
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.service.relayClient.reqCommand.user.loaders
|
||||
|
||||
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.SingleSubEoseManager
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.UserFinderQueryState
|
||||
import com.vitorpamplona.ammolite.relays.NostrClient
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
|
||||
class UserLoaderSubAssembler(
|
||||
client: NostrClient,
|
||||
allKeys: () -> Set<UserFinderQueryState>,
|
||||
) : SingleSubEoseManager<UserFinderQueryState>(client, allKeys, invalidateAfterEose = true) {
|
||||
override fun updateFilter(
|
||||
keys: List<UserFinderQueryState>,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? {
|
||||
val firstTimers = keys.filter { it.user.latestMetadata == null }.mapTo(mutableSetOf()) { it.user.pubkeyHex }
|
||||
|
||||
if (firstTimers.isEmpty()) return null
|
||||
|
||||
return filterNewUserMetadataForKey(firstTimers, since)
|
||||
}
|
||||
|
||||
override fun distinct(key: UserFinderQueryState) = key.user
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.service.relayClient.reqCommand.user.watchers
|
||||
|
||||
import com.vitorpamplona.ammolite.relays.EVENT_FINDER_TYPES
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip56Reports.ReportEvent
|
||||
|
||||
fun filterReportsToKeys(
|
||||
authors: Set<HexKey>,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter> =
|
||||
listOf(
|
||||
TypedFilter(
|
||||
types = EVENT_FINDER_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(ReportEvent.KIND),
|
||||
tags = mapOf("p" to authors.toList()),
|
||||
since = since,
|
||||
),
|
||||
),
|
||||
)
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.service.relayClient.reqCommand.user.watchers
|
||||
|
||||
import com.vitorpamplona.ammolite.relays.EVENT_FINDER_TYPES
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.experimental.relationshipStatus.RelationshipStatusEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip17Dm.settings.ChatMessageRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip38UserStatus.StatusEvent
|
||||
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
|
||||
|
||||
fun filterUserMetadataForKey(
|
||||
authors: Set<HexKey>,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter> =
|
||||
listOf(
|
||||
TypedFilter(
|
||||
types = EVENT_FINDER_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds =
|
||||
listOf(
|
||||
MetadataEvent.KIND,
|
||||
StatusEvent.KIND,
|
||||
RelationshipStatusEvent.KIND,
|
||||
AdvertisedRelayListEvent.KIND,
|
||||
ChatMessageRelayListEvent.KIND,
|
||||
),
|
||||
authors = authors.toList(),
|
||||
since = since,
|
||||
),
|
||||
),
|
||||
)
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.service.relayClient.reqCommand.user.watchers
|
||||
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.SingleSubEoseManager
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.UserFinderQueryState
|
||||
import com.vitorpamplona.amethyst.service.relays.EOSEAccountFast
|
||||
import com.vitorpamplona.ammolite.relays.NostrClient
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
|
||||
class UserWatcherSubAssembler(
|
||||
client: NostrClient,
|
||||
allKeys: () -> Set<UserFinderQueryState>,
|
||||
) : SingleSubEoseManager<UserFinderQueryState>(client, allKeys) {
|
||||
var lastUsersOnFilter: Set<User> = emptySet()
|
||||
var latestEOSEs: EOSEAccountFast = EOSEAccountFast(2000)
|
||||
|
||||
override fun newEose(
|
||||
relayUrl: String,
|
||||
time: Long,
|
||||
) {
|
||||
lastUsersOnFilter.forEach {
|
||||
latestEOSEs.newEose(it, relayUrl, time)
|
||||
}
|
||||
super.newEose(relayUrl, time)
|
||||
}
|
||||
|
||||
override fun updateFilter(
|
||||
keys: List<UserFinderQueryState>,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? {
|
||||
if (keys.isEmpty()) return null
|
||||
|
||||
lastUsersOnFilter = keys.filter { it.user.latestMetadata != null }.map { it.user }.toSet()
|
||||
|
||||
if (lastUsersOnFilter.isEmpty()) return null
|
||||
|
||||
return groupByRelayPresence(lastUsersOnFilter, latestEOSEs)
|
||||
.map { group ->
|
||||
val groupIds = group.map { it.pubkeyHex }.toSet()
|
||||
if (groupIds.isNotEmpty()) {
|
||||
val minEOSEs = findMinimumEOSEsForUsers(group, latestEOSEs)
|
||||
filterUserMetadataForKey(groupIds, minEOSEs) + filterReportsToKeys(groupIds, minEOSEs)
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
}.flatten()
|
||||
}
|
||||
|
||||
fun groupByRelayPresence(
|
||||
users: Iterable<User>,
|
||||
eoseCache: EOSEAccountFast,
|
||||
): Collection<List<User>> =
|
||||
users
|
||||
.groupBy { eoseCache.since(it)?.keys?.hashCode() }
|
||||
.values
|
||||
.map {
|
||||
// important to keep in order otherwise the Relay thinks the filter has changed and we REQ again
|
||||
it.sortedBy { it.pubkeyHex }
|
||||
}
|
||||
|
||||
fun findMinimumEOSEsForUsers(
|
||||
users: List<User>,
|
||||
eoseCache: EOSEAccountFast,
|
||||
): Map<String, EOSETime> {
|
||||
val minLatestEOSEs = mutableMapOf<String, EOSETime>()
|
||||
|
||||
users.forEach {
|
||||
eoseCache.since(it)?.forEach {
|
||||
val minEose = minLatestEOSEs[it.key]
|
||||
if (minEose == null) {
|
||||
minLatestEOSEs.put(it.key, EOSETime(it.value.time))
|
||||
} else if (it.value.time < minEose.time) {
|
||||
minEose.time = it.value.time
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return minLatestEOSEs
|
||||
}
|
||||
|
||||
override fun distinct(key: UserFinderQueryState) = key.user
|
||||
}
|
||||
+12
-202
@@ -21,50 +21,11 @@
|
||||
package com.vitorpamplona.amethyst.service.relayClient.searchCommand
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.vitorpamplona.amethyst.logTime
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.ammolite.relays.ALL_FEED_TYPES
|
||||
import com.vitorpamplona.ammolite.relays.FeedType
|
||||
import com.vitorpamplona.amethyst.service.relayClient.composeSubscriptionManagers.MutableComposeSubscriptionManager
|
||||
import com.vitorpamplona.amethyst.service.relayClient.composeSubscriptionManagers.MutableQueryState
|
||||
import com.vitorpamplona.amethyst.service.relayClient.searchCommand.subassemblies.SearchWatcherSubAssembler
|
||||
import com.vitorpamplona.ammolite.relays.NostrClient
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.experimental.audio.header.AudioHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.track.AudioTrackEvent
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryPrologueEvent
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStorySceneEvent
|
||||
import com.vitorpamplona.quartz.experimental.nns.NNSEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
|
||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.Nip01
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip19Bech32.Nip19Parser
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NAddress
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NEmbed
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NEvent
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NProfile
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NPub
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NRelay
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NSec
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.Note
|
||||
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
|
||||
import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelMetadataEvent
|
||||
import com.vitorpamplona.quartz.nip30CustomEmoji.pack.EmojiPackEvent
|
||||
import com.vitorpamplona.quartz.nip51Lists.BookmarkListEvent
|
||||
import com.vitorpamplona.quartz.nip51Lists.FollowListEvent
|
||||
import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent
|
||||
import com.vitorpamplona.quartz.nip51Lists.PinListEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
|
||||
import com.vitorpamplona.quartz.nip54Wiki.WikiNoteEvent
|
||||
import com.vitorpamplona.quartz.nip58Badges.BadgeDefinitionEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent
|
||||
import com.vitorpamplona.quartz.nip84Highlights.HighlightEvent
|
||||
import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent
|
||||
import com.vitorpamplona.quartz.utils.Hex
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
@@ -80,172 +41,21 @@ class SearchFilterAssembler(
|
||||
val cache: LocalCache,
|
||||
client: NostrClient,
|
||||
scope: CoroutineScope,
|
||||
) : MutableQueryBasedSubscriptionOrchestrator<SearchQueryState>(client, scope) {
|
||||
fun filterByAuthor(pubKey: HexKey) =
|
||||
) : MutableComposeSubscriptionManager<SearchQueryState>(scope) {
|
||||
val group =
|
||||
listOf(
|
||||
TypedFilter(
|
||||
types = ALL_FEED_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(MetadataEvent.KIND),
|
||||
authors = listOfNotNull(pubKey),
|
||||
limit = 1,
|
||||
),
|
||||
),
|
||||
SearchWatcherSubAssembler(cache, client, ::allKeys),
|
||||
)
|
||||
|
||||
fun filterByEvent(eventId: HexKey) =
|
||||
listOf(
|
||||
TypedFilter(
|
||||
types = ALL_FEED_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
ids = listOfNotNull(eventId),
|
||||
),
|
||||
),
|
||||
)
|
||||
override fun start() = group.forEach { it.start() }
|
||||
|
||||
fun filterByAddress(parsed: NAddress) =
|
||||
listOf(
|
||||
TypedFilter(
|
||||
types = ALL_FEED_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(MetadataEvent.KIND),
|
||||
authors = listOfNotNull(parsed.author),
|
||||
limit = 1,
|
||||
),
|
||||
),
|
||||
TypedFilter(
|
||||
types = ALL_FEED_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(parsed.kind),
|
||||
authors = listOfNotNull(parsed.author),
|
||||
tags = mapOf("d" to listOf(parsed.dTag)),
|
||||
limit = 5,
|
||||
),
|
||||
),
|
||||
)
|
||||
override fun stop() = group.forEach { it.stop() }
|
||||
|
||||
private fun createAnythingWithIDFilter(keys: Set<SearchQueryState>): List<TypedFilter> {
|
||||
if (keys.isEmpty()) return emptyList()
|
||||
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
|
||||
|
||||
val uniqueQueries =
|
||||
keys.mapNotNullTo(mutableSetOf()) {
|
||||
if (!it.searchQuery.value.isBlank()) {
|
||||
it.searchQuery.value
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
override fun invalidateKeys() = invalidateFilters()
|
||||
|
||||
return uniqueQueries
|
||||
.map { mySearchString ->
|
||||
val directFilters =
|
||||
runCatching {
|
||||
if (Hex.isHex(mySearchString)) {
|
||||
val key = Hex.decode(mySearchString).toHexKey()
|
||||
filterByAuthor(key) + filterByEvent(key)
|
||||
} else {
|
||||
when (val parsed = Nip19Parser.uriToRoute(mySearchString)?.entity) {
|
||||
is NSec -> filterByAuthor(Nip01.pubKeyCreate(parsed.hex.hexToByteArray()).toHexKey())
|
||||
is NPub -> filterByAuthor(parsed.hex)
|
||||
is NProfile -> filterByAuthor(parsed.hex)
|
||||
is Note -> filterByEvent(parsed.hex)
|
||||
is NEvent -> filterByEvent(parsed.hex)
|
||||
is NEmbed -> {
|
||||
cache.justConsume(parsed.event, null)
|
||||
emptyList()
|
||||
}
|
||||
override fun destroy() = group.forEach { it.destroy() }
|
||||
|
||||
is NRelay -> emptyList()
|
||||
is NAddress -> filterByAddress(parsed)
|
||||
else -> emptyList()
|
||||
}
|
||||
}
|
||||
}.getOrDefault(emptyList())
|
||||
|
||||
val searchFilters =
|
||||
listOfNotNull(
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.SEARCH),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(MetadataEvent.KIND),
|
||||
search = mySearchString,
|
||||
limit = 1000,
|
||||
),
|
||||
),
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.SEARCH),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds =
|
||||
listOf(
|
||||
TextNoteEvent.KIND,
|
||||
LongTextNoteEvent.KIND,
|
||||
BadgeDefinitionEvent.KIND,
|
||||
PeopleListEvent.KIND,
|
||||
BookmarkListEvent.KIND,
|
||||
AudioHeaderEvent.KIND,
|
||||
AudioTrackEvent.KIND,
|
||||
PinListEvent.KIND,
|
||||
PollNoteEvent.KIND,
|
||||
ChannelCreateEvent.KIND,
|
||||
),
|
||||
search = mySearchString,
|
||||
limit = 100,
|
||||
),
|
||||
),
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.SEARCH),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds =
|
||||
listOf(
|
||||
ChannelMetadataEvent.KIND,
|
||||
ClassifiedsEvent.KIND,
|
||||
CommunityDefinitionEvent.KIND,
|
||||
EmojiPackEvent.KIND,
|
||||
HighlightEvent.KIND,
|
||||
LiveActivitiesEvent.KIND,
|
||||
PollNoteEvent.KIND,
|
||||
NNSEvent.KIND,
|
||||
WikiNoteEvent.KIND,
|
||||
CommentEvent.KIND,
|
||||
),
|
||||
search = mySearchString,
|
||||
limit = 100,
|
||||
),
|
||||
),
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.SEARCH),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds =
|
||||
listOf(
|
||||
InteractiveStoryPrologueEvent.KIND,
|
||||
InteractiveStorySceneEvent.KIND,
|
||||
FollowListEvent.KIND,
|
||||
),
|
||||
search = mySearchString,
|
||||
limit = 100,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
directFilters + searchFilters
|
||||
}.flatten()
|
||||
}
|
||||
|
||||
val searchChannel = requestNewSubscription()
|
||||
|
||||
override fun updateSubscriptions(keys: Set<SearchQueryState>) {
|
||||
searchChannel.typedFilters =
|
||||
logTime(
|
||||
debugMessage = { "Search DataSource UpdateSubscriptions with ${it?.size} filter size" },
|
||||
block = { createAnythingWithIDFilter(keys).ifEmpty { null } },
|
||||
)
|
||||
}
|
||||
override fun printStats() = group.forEach { it.printStats() }
|
||||
}
|
||||
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.service.relayClient.searchCommand.subassemblies
|
||||
|
||||
import com.fasterxml.jackson.databind.deser.std.FromStringDeserializer.types
|
||||
import com.vitorpamplona.ammolite.relays.ALL_FEED_TYPES
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NAddress
|
||||
|
||||
fun filterByAddress(address: NAddress) =
|
||||
listOf(
|
||||
TypedFilter(
|
||||
types = ALL_FEED_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(MetadataEvent.KIND),
|
||||
authors = listOfNotNull(address.author),
|
||||
limit = 1,
|
||||
),
|
||||
),
|
||||
if (address.kind < 25000 && address.dTag.isBlank()) {
|
||||
TypedFilter(
|
||||
types = ALL_FEED_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(address.kind),
|
||||
authors = listOf(address.author),
|
||||
limit = 5,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
TypedFilter(
|
||||
types = ALL_FEED_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(address.kind),
|
||||
tags = mapOf("d" to listOf(address.dTag)),
|
||||
authors = listOf(address.author),
|
||||
limit = 5,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.service.relayClient.searchCommand.subassemblies
|
||||
|
||||
import com.vitorpamplona.ammolite.relays.ALL_FEED_TYPES
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
|
||||
fun filterByAuthor(pubKey: HexKey) =
|
||||
listOf(
|
||||
TypedFilter(
|
||||
types = ALL_FEED_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(MetadataEvent.KIND),
|
||||
authors = listOfNotNull(pubKey),
|
||||
limit = 1,
|
||||
),
|
||||
),
|
||||
)
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.service.relayClient.searchCommand.subassemblies
|
||||
|
||||
import com.vitorpamplona.ammolite.relays.ALL_FEED_TYPES
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
|
||||
fun filterByEvent(eventId: HexKey) =
|
||||
listOf(
|
||||
TypedFilter(
|
||||
types = ALL_FEED_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
ids = listOfNotNull(eventId),
|
||||
),
|
||||
),
|
||||
)
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.service.relayClient.searchCommand.subassemblies
|
||||
|
||||
import com.vitorpamplona.ammolite.relays.FeedType
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
|
||||
fun searchPeopleByName(searchString: HexKey) =
|
||||
listOf(
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.SEARCH),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(MetadataEvent.KIND),
|
||||
search = searchString,
|
||||
limit = 1000,
|
||||
),
|
||||
),
|
||||
)
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.service.relayClient.searchCommand.subassemblies
|
||||
|
||||
import com.vitorpamplona.ammolite.relays.FeedType
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.experimental.audio.header.AudioHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.track.AudioTrackEvent
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryPrologueEvent
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStorySceneEvent
|
||||
import com.vitorpamplona.quartz.experimental.nns.NNSEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
|
||||
import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelMetadataEvent
|
||||
import com.vitorpamplona.quartz.nip30CustomEmoji.pack.EmojiPackEvent
|
||||
import com.vitorpamplona.quartz.nip51Lists.BookmarkListEvent
|
||||
import com.vitorpamplona.quartz.nip51Lists.FollowListEvent
|
||||
import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent
|
||||
import com.vitorpamplona.quartz.nip51Lists.PinListEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
|
||||
import com.vitorpamplona.quartz.nip54Wiki.WikiNoteEvent
|
||||
import com.vitorpamplona.quartz.nip58Badges.BadgeDefinitionEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent
|
||||
import com.vitorpamplona.quartz.nip84Highlights.HighlightEvent
|
||||
import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent
|
||||
|
||||
fun searchPostsByText(searchString: HexKey) =
|
||||
listOf(
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.SEARCH),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds =
|
||||
listOf(
|
||||
TextNoteEvent.KIND,
|
||||
LongTextNoteEvent.KIND,
|
||||
BadgeDefinitionEvent.KIND,
|
||||
PeopleListEvent.KIND,
|
||||
BookmarkListEvent.KIND,
|
||||
AudioHeaderEvent.KIND,
|
||||
AudioTrackEvent.KIND,
|
||||
PinListEvent.KIND,
|
||||
PollNoteEvent.KIND,
|
||||
ChannelCreateEvent.KIND,
|
||||
),
|
||||
search = searchString,
|
||||
limit = 100,
|
||||
),
|
||||
),
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.SEARCH),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds =
|
||||
listOf(
|
||||
ChannelMetadataEvent.KIND,
|
||||
ClassifiedsEvent.KIND,
|
||||
CommunityDefinitionEvent.KIND,
|
||||
EmojiPackEvent.KIND,
|
||||
HighlightEvent.KIND,
|
||||
LiveActivitiesEvent.KIND,
|
||||
PollNoteEvent.KIND,
|
||||
NNSEvent.KIND,
|
||||
WikiNoteEvent.KIND,
|
||||
CommentEvent.KIND,
|
||||
),
|
||||
search = searchString,
|
||||
limit = 100,
|
||||
),
|
||||
),
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.SEARCH),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds =
|
||||
listOf(
|
||||
InteractiveStoryPrologueEvent.KIND,
|
||||
InteractiveStorySceneEvent.KIND,
|
||||
FollowListEvent.KIND,
|
||||
),
|
||||
search = searchString,
|
||||
limit = 100,
|
||||
),
|
||||
),
|
||||
)
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.service.relayClient.searchCommand.subassemblies
|
||||
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUniqueIdEoseManager
|
||||
import com.vitorpamplona.amethyst.service.relayClient.searchCommand.SearchQueryState
|
||||
import com.vitorpamplona.ammolite.relays.NostrClient
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
|
||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.Nip01
|
||||
import com.vitorpamplona.quartz.nip19Bech32.Nip19Parser
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NAddress
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NEmbed
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NEvent
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NProfile
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NPub
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NRelay
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NSec
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.Note
|
||||
import com.vitorpamplona.quartz.utils.Hex
|
||||
|
||||
/**
|
||||
* Creates a new sub for each Search Screen. The StateFlow of the text field
|
||||
* that has the search ter is the id of the sub.
|
||||
*/
|
||||
class SearchWatcherSubAssembler(
|
||||
val cache: LocalCache,
|
||||
client: NostrClient,
|
||||
allKeys: () -> Set<SearchQueryState>,
|
||||
) : PerUniqueIdEoseManager<SearchQueryState>(client, allKeys) {
|
||||
override fun updateFilter(
|
||||
key: SearchQueryState,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? {
|
||||
val mySearchString = key.searchQuery.value
|
||||
|
||||
if (mySearchString.isBlank()) return null
|
||||
|
||||
val directFilters =
|
||||
runCatching {
|
||||
if (Hex.isHex(mySearchString)) {
|
||||
val key = Hex.decode(mySearchString).toHexKey()
|
||||
filterByAuthor(key) + filterByEvent(key)
|
||||
} else {
|
||||
when (val parsed = Nip19Parser.uriToRoute(mySearchString)?.entity) {
|
||||
is NSec -> filterByAuthor(Nip01.pubKeyCreate(parsed.hex.hexToByteArray()).toHexKey())
|
||||
is NPub -> filterByAuthor(parsed.hex)
|
||||
is NProfile -> filterByAuthor(parsed.hex)
|
||||
is Note -> filterByEvent(parsed.hex)
|
||||
is NEvent -> filterByEvent(parsed.hex)
|
||||
is NEmbed -> {
|
||||
cache.justConsume(parsed.event, null)
|
||||
emptyList()
|
||||
}
|
||||
|
||||
is NRelay -> emptyList()
|
||||
is NAddress -> filterByAddress(parsed)
|
||||
else -> emptyList()
|
||||
}
|
||||
}
|
||||
}.getOrDefault(emptyList())
|
||||
|
||||
val searchFilters = searchPeopleByName(mySearchString) + searchPostsByText(mySearchString)
|
||||
|
||||
return directFilters + searchFilters
|
||||
}
|
||||
|
||||
override fun id(key: SearchQueryState) = key.searchQuery.hashCode().toString()
|
||||
}
|
||||
@@ -21,12 +21,14 @@
|
||||
package com.vitorpamplona.amethyst.service.relays
|
||||
|
||||
import androidx.collection.LruCache
|
||||
import com.vitorpamplona.amethyst.model.LocalCache.users
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import kotlin.collections.plus
|
||||
|
||||
class EOSERelayList {
|
||||
var relayList: Map<String, EOSETime> = emptyMap()
|
||||
|
||||
class EOSERelayList(
|
||||
var relayList: Map<String, EOSETime> = emptyMap(),
|
||||
) {
|
||||
fun addOrUpdate(
|
||||
relayUrl: String,
|
||||
time: Long,
|
||||
@@ -35,14 +37,29 @@ class EOSERelayList(
|
||||
if (eose == null) {
|
||||
relayList = relayList + Pair(relayUrl, EOSETime(time))
|
||||
} else {
|
||||
eose.time = time
|
||||
if (time > eose.time) {
|
||||
eose.update(time)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
relayList = emptyMap()
|
||||
}
|
||||
|
||||
fun since() = relayList
|
||||
|
||||
fun newEose(
|
||||
relayUrl: String,
|
||||
time: Long,
|
||||
) = addOrUpdate(relayUrl, time)
|
||||
}
|
||||
|
||||
class EOSEFollowList(
|
||||
var followList: Map<String, EOSERelayList> = emptyMap(),
|
||||
cacheSize: Int = 200,
|
||||
) {
|
||||
var followList: LruCache<String, EOSERelayList> = LruCache<String, EOSERelayList>(cacheSize)
|
||||
|
||||
fun addOrUpdate(
|
||||
listCode: String,
|
||||
relayUrl: String,
|
||||
@@ -52,16 +69,26 @@ class EOSEFollowList(
|
||||
if (relayList == null) {
|
||||
val newList = EOSERelayList()
|
||||
newList.addOrUpdate(relayUrl, time)
|
||||
followList = followList + mapOf(listCode to newList)
|
||||
followList.put(listCode, newList)
|
||||
} else {
|
||||
relayList.addOrUpdate(relayUrl, time)
|
||||
}
|
||||
}
|
||||
|
||||
fun since(listCode: String) = followList[listCode]?.relayList
|
||||
|
||||
fun newEose(
|
||||
listCode: String,
|
||||
relayUrl: String,
|
||||
time: Long,
|
||||
) = addOrUpdate(listCode, relayUrl, time)
|
||||
}
|
||||
|
||||
class EOSEAccount(
|
||||
var users: LruCache<User, EOSEFollowList> = LruCache<User, EOSEFollowList>(20),
|
||||
cacheSize: Int = 20,
|
||||
) {
|
||||
var users: LruCache<User, EOSEFollowList> = LruCache<User, EOSEFollowList>(cacheSize)
|
||||
|
||||
fun addOrUpdate(
|
||||
user: User,
|
||||
listCode: String,
|
||||
@@ -81,4 +108,50 @@ class EOSEAccount(
|
||||
fun removeDataFor(user: User) {
|
||||
users.remove(user)
|
||||
}
|
||||
|
||||
fun since(
|
||||
key: User,
|
||||
listCode: String,
|
||||
) = users[key]?.followList?.get(listCode)?.relayList
|
||||
|
||||
fun newEose(
|
||||
user: User,
|
||||
listCode: String,
|
||||
relayUrl: String,
|
||||
time: Long,
|
||||
) = addOrUpdate(user, listCode, relayUrl, time)
|
||||
}
|
||||
|
||||
class EOSEAccountFast(
|
||||
cacheSize: Int = 20,
|
||||
) {
|
||||
private val users: LruCache<User, EOSERelayList> = LruCache<User, EOSERelayList>(cacheSize)
|
||||
|
||||
fun addOrUpdate(
|
||||
user: User,
|
||||
relayUrl: String,
|
||||
time: Long,
|
||||
) {
|
||||
val relayList = users[user]
|
||||
if (relayList == null) {
|
||||
val newList = EOSERelayList()
|
||||
users.put(user, newList)
|
||||
|
||||
newList.addOrUpdate(relayUrl, time)
|
||||
} else {
|
||||
relayList.addOrUpdate(relayUrl, time)
|
||||
}
|
||||
}
|
||||
|
||||
fun removeDataFor(user: User) {
|
||||
users.remove(user)
|
||||
}
|
||||
|
||||
fun since(key: User) = users[key]?.relayList
|
||||
|
||||
fun newEose(
|
||||
user: User,
|
||||
relayUrl: String,
|
||||
time: Long,
|
||||
) = addOrUpdate(user, relayUrl, time)
|
||||
}
|
||||
|
||||
+14
-83
@@ -21,104 +21,35 @@
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.datasource
|
||||
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.QueryBasedSubscriptionOrchestrator
|
||||
import com.vitorpamplona.amethyst.service.relays.EOSEAccount
|
||||
import com.vitorpamplona.ammolite.relays.FeedType
|
||||
import com.vitorpamplona.amethyst.service.relayClient.composeSubscriptionManagers.ComposeSubscriptionManager
|
||||
import com.vitorpamplona.ammolite.relays.NostrClient
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.datasources.Subscription
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
|
||||
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey
|
||||
|
||||
// This allows multiple screen to be listening to tags, even the same tag
|
||||
class ChatroomQueryState(
|
||||
val room: ChatroomKey,
|
||||
val account: Account,
|
||||
)
|
||||
) {
|
||||
val listId = room.hashCode().toString()
|
||||
}
|
||||
|
||||
class ChatroomFilterAssembler(
|
||||
client: NostrClient,
|
||||
) : QueryBasedSubscriptionOrchestrator<ChatroomQueryState>(client) {
|
||||
private val latestEOSEs = EOSEAccount()
|
||||
|
||||
fun createMessagesToMeFilter(key: ChatroomQueryState): TypedFilter? =
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.PRIVATE_DMS),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(PrivateDmEvent.KIND),
|
||||
authors = key.room.users.toList(),
|
||||
tags = mapOf("p" to listOf(key.account.userProfile().pubkeyHex)),
|
||||
since =
|
||||
latestEOSEs.users[key.account.userProfile()]
|
||||
?.followList
|
||||
?.get(key.room.hashCode().toString())
|
||||
?.relayList,
|
||||
),
|
||||
) : ComposeSubscriptionManager<ChatroomQueryState>() {
|
||||
val group =
|
||||
listOf(
|
||||
ChatroomFilterSubAssembler(client, ::allKeys),
|
||||
)
|
||||
|
||||
fun createMessagesFromMeFilter(key: ChatroomQueryState): TypedFilter? =
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.PRIVATE_DMS),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(PrivateDmEvent.KIND),
|
||||
authors = listOf(key.account.userProfile().pubkeyHex),
|
||||
tags = mapOf("p" to key.room.users.map { it }),
|
||||
since =
|
||||
latestEOSEs.users[key.account.userProfile()]
|
||||
?.followList
|
||||
?.get(key.room.hashCode().toString())
|
||||
?.relayList,
|
||||
),
|
||||
)
|
||||
override fun start() = group.forEach { it.start() }
|
||||
|
||||
fun clearEOSEs(account: Account) {
|
||||
latestEOSEs.removeDataFor(account.userProfile())
|
||||
}
|
||||
override fun stop() = group.forEach { it.stop() }
|
||||
|
||||
fun mergeAllFilters(key: ChatroomQueryState): List<TypedFilter>? =
|
||||
listOfNotNull(
|
||||
createMessagesToMeFilter(key),
|
||||
createMessagesFromMeFilter(key),
|
||||
).ifEmpty { null }
|
||||
override fun invalidateKeys() = invalidateFilters()
|
||||
|
||||
fun newSub(key: ChatroomQueryState): Subscription =
|
||||
requestNewSubscription { time, relayUrl ->
|
||||
latestEOSEs.addOrUpdate(key.account.userProfile(), key.room.hashCode().toString(), relayUrl, time)
|
||||
}
|
||||
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
|
||||
|
||||
val userSubscriptionMap = mutableMapOf<User, String>()
|
||||
override fun destroy() = group.forEach { it.start() }
|
||||
|
||||
fun findOrCreateSubFor(key: ChatroomQueryState): Subscription {
|
||||
var subId = userSubscriptionMap[key.account.userProfile()]
|
||||
return if (subId == null) {
|
||||
newSub(key).also { userSubscriptionMap[key.account.userProfile()] = it.id }
|
||||
} else {
|
||||
getSub(subId) ?: newSub(key).also { userSubscriptionMap[key.account.userProfile()] = it.id }
|
||||
}
|
||||
}
|
||||
|
||||
// One sub per subscribed account
|
||||
override fun updateSubscriptions(keys: Set<ChatroomQueryState>) {
|
||||
val uniqueSubscribedAccounts = keys.distinctBy { it.account }
|
||||
|
||||
val updated = mutableSetOf<User>()
|
||||
|
||||
uniqueSubscribedAccounts.forEach {
|
||||
val user = it.account.userProfile()
|
||||
val sub = findOrCreateSubFor(it)
|
||||
sub.typedFilters = mergeAllFilters(it)
|
||||
|
||||
updated.add(user)
|
||||
}
|
||||
|
||||
userSubscriptionMap.forEach {
|
||||
if (it.key !in updated) {
|
||||
dismissSubscription(it.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
override fun printStats() = group.forEach { it.printStats() }
|
||||
}
|
||||
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.chats.privateDM.datasource
|
||||
|
||||
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUserAndFollowListEoseManager
|
||||
import com.vitorpamplona.ammolite.relays.NostrClient
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
|
||||
class ChatroomFilterSubAssembler(
|
||||
client: NostrClient,
|
||||
allKeys: () -> Set<ChatroomQueryState>,
|
||||
) : PerUserAndFollowListEoseManager<ChatroomQueryState>(client, allKeys) {
|
||||
override fun updateFilter(
|
||||
key: ChatroomQueryState,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? =
|
||||
filterNip04DMs(
|
||||
key.room.users,
|
||||
key.account.userProfile().pubkeyHex,
|
||||
since,
|
||||
)
|
||||
|
||||
override fun user(key: ChatroomQueryState) = key.account.userProfile()
|
||||
|
||||
override fun list(key: ChatroomQueryState) = key.listId
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.chats.privateDM.datasource
|
||||
|
||||
import com.vitorpamplona.ammolite.relays.FeedType
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
|
||||
import kotlin.collections.toList
|
||||
|
||||
fun filterNip04DMs(
|
||||
group: Set<HexKey>?,
|
||||
user: HexKey?,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? {
|
||||
if (group == null || group.isEmpty() || user == null || user.isEmpty()) return null
|
||||
|
||||
return listOf(
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.PRIVATE_DMS),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(PrivateDmEvent.KIND),
|
||||
authors = group.toList(),
|
||||
tags = mapOf("p" to listOf(user)),
|
||||
since = since,
|
||||
),
|
||||
),
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.PRIVATE_DMS),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(PrivateDmEvent.KIND),
|
||||
authors = listOf(user),
|
||||
tags = mapOf("p" to group.toList()),
|
||||
since = since,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
+16
-172
@@ -22,21 +22,10 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.datas
|
||||
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.Channel
|
||||
import com.vitorpamplona.amethyst.model.EphemeralChatChannel
|
||||
import com.vitorpamplona.amethyst.model.LiveActivitiesChannel
|
||||
import com.vitorpamplona.amethyst.model.PublicChatChannel
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.QueryBasedSubscriptionOrchestrator
|
||||
import com.vitorpamplona.amethyst.service.relays.EOSEAccount
|
||||
import com.vitorpamplona.amethyst.service.relays.EOSEFollowList
|
||||
import com.vitorpamplona.ammolite.relays.FeedType
|
||||
import com.vitorpamplona.amethyst.service.relayClient.composeSubscriptionManagers.ComposeSubscriptionManager
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.datasource.subassemblies.ChannelFromUserFilterSubAssembler
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.datasource.subassemblies.ChannelPublicFilterSubAssembler
|
||||
import com.vitorpamplona.ammolite.relays.NostrClient
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.datasources.Subscription
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.experimental.ephemChat.chat.EphemeralChatEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent
|
||||
|
||||
// This allows multiple screen to be listening to tags, even the same tag
|
||||
class ChannelQueryState(
|
||||
@@ -46,169 +35,24 @@ class ChannelQueryState(
|
||||
|
||||
class ChannelFilterAssembler(
|
||||
client: NostrClient,
|
||||
) : QueryBasedSubscriptionOrchestrator<ChannelQueryState>(client) {
|
||||
companion object {
|
||||
val RELAY_SET =
|
||||
setOf(
|
||||
FeedType.FOLLOWS,
|
||||
FeedType.PRIVATE_DMS,
|
||||
FeedType.GLOBAL,
|
||||
FeedType.SEARCH,
|
||||
)
|
||||
}
|
||||
|
||||
private val latestEOSEPerAccount = EOSEAccount()
|
||||
private val latestEOSEPerChannel = EOSEFollowList()
|
||||
|
||||
fun sinceUser(key: ChannelQueryState) =
|
||||
latestEOSEPerAccount.users[key.account.userProfile()]
|
||||
?.followList
|
||||
?.get(key.channel.idHex)
|
||||
?.relayList
|
||||
|
||||
fun since(key: ChannelQueryState) = latestEOSEPerChannel.followList.get(key.channel.idHex)?.relayList
|
||||
|
||||
fun newEose(
|
||||
key: ChannelQueryState,
|
||||
relayUrl: String,
|
||||
time: Long,
|
||||
) {
|
||||
latestEOSEPerAccount.addOrUpdate(
|
||||
key.account.userProfile(),
|
||||
key.channel.idHex,
|
||||
relayUrl,
|
||||
time,
|
||||
) : ComposeSubscriptionManager<ChannelQueryState>() {
|
||||
val group =
|
||||
listOf(
|
||||
// Requests the latest messages
|
||||
ChannelPublicFilterSubAssembler(client, ::allKeys),
|
||||
// Requests the latest messages by logged in user
|
||||
ChannelFromUserFilterSubAssembler(client, ::allKeys),
|
||||
)
|
||||
|
||||
latestEOSEPerChannel.addOrUpdate(key.channel.idHex, relayUrl, time)
|
||||
}
|
||||
override fun start() = group.forEach { it.start() }
|
||||
|
||||
fun createMessagesByMeToChannelFilter(key: ChannelQueryState): TypedFilter? =
|
||||
when (key.channel) {
|
||||
is PublicChatChannel ->
|
||||
TypedFilter(
|
||||
types = RELAY_SET,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(ChannelMessageEvent.KIND),
|
||||
authors = listOf(key.account.userProfile().pubkeyHex),
|
||||
limit = 50,
|
||||
since = sinceUser(key),
|
||||
),
|
||||
)
|
||||
is LiveActivitiesChannel ->
|
||||
TypedFilter(
|
||||
types = RELAY_SET,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(LiveActivitiesChatMessageEvent.KIND),
|
||||
authors = listOf(key.account.userProfile().pubkeyHex),
|
||||
limit = 50,
|
||||
since = sinceUser(key),
|
||||
),
|
||||
)
|
||||
is EphemeralChatChannel ->
|
||||
null // there is nothing in the past
|
||||
else -> {
|
||||
null
|
||||
}
|
||||
}
|
||||
override fun stop() = group.forEach { it.stop() }
|
||||
|
||||
fun createMessagesToChannelFilter(key: ChannelQueryState): TypedFilter? {
|
||||
val channel = key.channel
|
||||
override fun invalidateKeys() = invalidateFilters()
|
||||
|
||||
return when (channel) {
|
||||
is PublicChatChannel ->
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.PUBLIC_CHATS),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(ChannelMessageEvent.KIND),
|
||||
tags = mapOf("e" to listOfNotNull(channel.idHex)),
|
||||
limit = 200,
|
||||
since = since(key),
|
||||
),
|
||||
)
|
||||
is LiveActivitiesChannel ->
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.PUBLIC_CHATS),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(LiveActivitiesChatMessageEvent.KIND),
|
||||
tags = mapOf("a" to listOfNotNull(channel.idHex)),
|
||||
limit = 200,
|
||||
since = since(key),
|
||||
),
|
||||
)
|
||||
is EphemeralChatChannel -> {
|
||||
TypedFilter(
|
||||
types =
|
||||
setOf(
|
||||
FeedType.FOLLOWS,
|
||||
FeedType.PUBLIC_CHATS,
|
||||
FeedType.GLOBAL,
|
||||
),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(EphemeralChatEvent.KIND),
|
||||
tags =
|
||||
if (channel.roomId.id.isBlank()) {
|
||||
mapOf("d" to listOf("_"))
|
||||
} else {
|
||||
mapOf("d" to listOfNotNull(channel.roomId.id))
|
||||
},
|
||||
limit = 200,
|
||||
since = since(key),
|
||||
),
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
null
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
|
||||
|
||||
fun mergeAllFilters(key: ChannelQueryState): List<TypedFilter>? =
|
||||
listOfNotNull(
|
||||
createMessagesToChannelFilter(key),
|
||||
createMessagesByMeToChannelFilter(key),
|
||||
).ifEmpty { null }
|
||||
override fun destroy() = group.forEach { it.start() }
|
||||
|
||||
fun newSub(key: ChannelQueryState): Subscription =
|
||||
requestNewSubscription { time, relayUrl ->
|
||||
newEose(key, relayUrl, time)
|
||||
}
|
||||
|
||||
val userSubscriptionMap = mutableMapOf<User, String>()
|
||||
|
||||
fun findOrCreateSubFor(key: ChannelQueryState): Subscription {
|
||||
var subId = userSubscriptionMap[key.account.userProfile()]
|
||||
return if (subId == null) {
|
||||
newSub(key).also { userSubscriptionMap[key.account.userProfile()] = it.id }
|
||||
} else {
|
||||
getSub(subId) ?: newSub(key).also { userSubscriptionMap[key.account.userProfile()] = it.id }
|
||||
}
|
||||
}
|
||||
|
||||
// One sub per subscribed account
|
||||
override fun updateSubscriptions(keys: Set<ChannelQueryState>) {
|
||||
val uniqueSubscribedAccounts = keys.distinctBy { it.account }
|
||||
|
||||
val updated = mutableSetOf<User>()
|
||||
|
||||
uniqueSubscribedAccounts.forEach {
|
||||
val user = it.account.userProfile()
|
||||
val sub = findOrCreateSubFor(it)
|
||||
sub.typedFilters = mergeAllFilters(it)
|
||||
|
||||
updated.add(user)
|
||||
}
|
||||
|
||||
userSubscriptionMap.forEach {
|
||||
if (it.key !in updated) {
|
||||
dismissSubscription(it.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
override fun printStats() = group.forEach { it.printStats() }
|
||||
}
|
||||
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.chats.publicChannels.datasource.subassemblies
|
||||
|
||||
import com.vitorpamplona.amethyst.model.EphemeralChatChannel
|
||||
import com.vitorpamplona.amethyst.model.LiveActivitiesChannel
|
||||
import com.vitorpamplona.amethyst.model.PublicChatChannel
|
||||
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUserAndFollowListEoseManager
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.datasource.ChannelQueryState
|
||||
import com.vitorpamplona.ammolite.relays.NostrClient
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
|
||||
class ChannelFromUserFilterSubAssembler(
|
||||
client: NostrClient,
|
||||
allKeys: () -> Set<ChannelQueryState>,
|
||||
) : PerUserAndFollowListEoseManager<ChannelQueryState>(client, allKeys) {
|
||||
override fun updateFilter(
|
||||
key: ChannelQueryState,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? =
|
||||
when (val channel = key.channel) {
|
||||
is EphemeralChatChannel -> filterMyMessagesToEphemeralChat(channel, userHex(key), since)
|
||||
is PublicChatChannel -> filterMyMessagesToPublicChat(channel, user(key).pubkeyHex, since)
|
||||
is LiveActivitiesChannel -> filterMyMessagesToLiveActivities(channel, userHex(key), since)
|
||||
else -> null
|
||||
}
|
||||
|
||||
fun userHex(key: ChannelQueryState) = key.account.userProfile().pubkeyHex
|
||||
|
||||
override fun user(key: ChannelQueryState) = key.account.userProfile()
|
||||
|
||||
override fun list(key: ChannelQueryState) = key.channel.idHex
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.chats.publicChannels.datasource.subassemblies
|
||||
|
||||
import com.vitorpamplona.amethyst.model.EphemeralChatChannel
|
||||
import com.vitorpamplona.amethyst.model.LiveActivitiesChannel
|
||||
import com.vitorpamplona.amethyst.model.PublicChatChannel
|
||||
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUniqueIdEoseManager
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.datasource.ChannelQueryState
|
||||
import com.vitorpamplona.ammolite.relays.NostrClient
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
|
||||
class ChannelPublicFilterSubAssembler(
|
||||
client: NostrClient,
|
||||
allKeys: () -> Set<ChannelQueryState>,
|
||||
) : PerUniqueIdEoseManager<ChannelQueryState>(client, allKeys) {
|
||||
override fun updateFilter(
|
||||
key: ChannelQueryState,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? =
|
||||
when (val channel = key.channel) {
|
||||
is EphemeralChatChannel -> filterMessagesToEphemeralChat(channel, since)
|
||||
is PublicChatChannel -> filterMessagesToPublicChat(channel, since)
|
||||
is LiveActivitiesChannel -> filterMessagesToLiveActivities(channel, since)
|
||||
else -> null
|
||||
}
|
||||
|
||||
override fun id(key: ChannelQueryState) = key.channel.idHex
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.chats.publicChannels.datasource.subassemblies
|
||||
|
||||
import com.vitorpamplona.amethyst.model.EphemeralChatChannel
|
||||
import com.vitorpamplona.ammolite.relays.FeedType
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.experimental.ephemChat.chat.EphemeralChatEvent
|
||||
|
||||
fun filterMessagesToEphemeralChat(
|
||||
channel: EphemeralChatChannel,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? =
|
||||
listOf(
|
||||
TypedFilter(
|
||||
types =
|
||||
setOf(FeedType.FOLLOWS, FeedType.PUBLIC_CHATS, FeedType.GLOBAL),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(EphemeralChatEvent.KIND),
|
||||
tags =
|
||||
if (channel.roomId.id.isBlank()) {
|
||||
mapOf("d" to listOf("_"))
|
||||
} else {
|
||||
mapOf("d" to listOfNotNull(channel.roomId.id))
|
||||
},
|
||||
limit = 200,
|
||||
since = since,
|
||||
),
|
||||
),
|
||||
)
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.chats.publicChannels.datasource.subassemblies
|
||||
|
||||
import com.vitorpamplona.amethyst.model.LiveActivitiesChannel
|
||||
import com.vitorpamplona.ammolite.relays.FeedType
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent
|
||||
|
||||
fun filterMessagesToLiveActivities(
|
||||
channel: LiveActivitiesChannel,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? =
|
||||
listOf(
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.PUBLIC_CHATS),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(LiveActivitiesChatMessageEvent.KIND),
|
||||
tags = mapOf("a" to listOfNotNull(channel.idHex)),
|
||||
limit = 200,
|
||||
since = since,
|
||||
),
|
||||
),
|
||||
)
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.chats.publicChannels.datasource.subassemblies
|
||||
|
||||
import com.vitorpamplona.amethyst.model.PublicChatChannel
|
||||
import com.vitorpamplona.ammolite.relays.FeedType
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
|
||||
|
||||
fun filterMessagesToPublicChat(
|
||||
channel: PublicChatChannel,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? =
|
||||
listOf(
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.PUBLIC_CHATS),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(ChannelMessageEvent.KIND),
|
||||
tags = mapOf("e" to listOfNotNull(channel.idHex)),
|
||||
limit = 200,
|
||||
since = since,
|
||||
),
|
||||
),
|
||||
)
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.chats.publicChannels.datasource.subassemblies
|
||||
|
||||
import com.vitorpamplona.amethyst.model.EphemeralChatChannel
|
||||
import com.vitorpamplona.ammolite.relays.FeedType
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.experimental.ephemChat.chat.EphemeralChatEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
|
||||
fun filterMyMessagesToEphemeralChat(
|
||||
channel: EphemeralChatChannel,
|
||||
pubKey: HexKey,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? =
|
||||
listOf(
|
||||
TypedFilter(
|
||||
types =
|
||||
setOf(FeedType.FOLLOWS, FeedType.PUBLIC_CHATS, FeedType.GLOBAL),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(EphemeralChatEvent.KIND),
|
||||
tags =
|
||||
if (channel.roomId.id.isBlank()) {
|
||||
mapOf("d" to listOf("_"))
|
||||
} else {
|
||||
mapOf("d" to listOfNotNull(channel.roomId.id))
|
||||
},
|
||||
authors = listOf(pubKey),
|
||||
limit = 50,
|
||||
since = since,
|
||||
),
|
||||
),
|
||||
)
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.chats.publicChannels.datasource.subassemblies
|
||||
|
||||
import com.vitorpamplona.amethyst.model.LiveActivitiesChannel
|
||||
import com.vitorpamplona.ammolite.relays.FeedType
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent
|
||||
|
||||
fun filterMyMessagesToLiveActivities(
|
||||
channel: LiveActivitiesChannel,
|
||||
pubKey: HexKey,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? =
|
||||
listOf(
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.FOLLOWS, FeedType.PRIVATE_DMS, FeedType.GLOBAL, FeedType.SEARCH),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(LiveActivitiesChatMessageEvent.KIND),
|
||||
tags = mapOf("a" to listOfNotNull(channel.idHex)),
|
||||
authors = listOf(pubKey),
|
||||
limit = 50,
|
||||
since = since,
|
||||
),
|
||||
),
|
||||
)
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.chats.publicChannels.datasource.subassemblies
|
||||
|
||||
import com.vitorpamplona.amethyst.model.PublicChatChannel
|
||||
import com.vitorpamplona.ammolite.relays.FeedType
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
|
||||
|
||||
fun filterMyMessagesToPublicChat(
|
||||
channel: PublicChatChannel,
|
||||
pubKey: HexKey,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? =
|
||||
listOf(
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.FOLLOWS, FeedType.PRIVATE_DMS, FeedType.GLOBAL, FeedType.SEARCH),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(ChannelMessageEvent.KIND),
|
||||
tags = mapOf("e" to listOfNotNull(channel.idHex)),
|
||||
authors = listOf(pubKey),
|
||||
limit = 50,
|
||||
since = since,
|
||||
),
|
||||
),
|
||||
)
|
||||
+13
-198
@@ -21,22 +21,8 @@
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.datasource
|
||||
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.QueryBasedSubscriptionOrchestrator
|
||||
import com.vitorpamplona.amethyst.service.relays.EOSEAccount
|
||||
import com.vitorpamplona.ammolite.relays.EVENT_FINDER_TYPES
|
||||
import com.vitorpamplona.ammolite.relays.FeedType
|
||||
import com.vitorpamplona.amethyst.service.relayClient.composeSubscriptionManagers.ComposeSubscriptionManager
|
||||
import com.vitorpamplona.ammolite.relays.NostrClient
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.datasources.Subscription
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.experimental.ephemChat.chat.EphemeralChatEvent
|
||||
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelMetadataEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
|
||||
import kotlinx.coroutines.flow.toList
|
||||
import kotlin.collections.flatten
|
||||
|
||||
// This allows multiple screen to be listening to tags, even the same tag
|
||||
class ChatroomListState(
|
||||
@@ -45,194 +31,23 @@ class ChatroomListState(
|
||||
|
||||
class ChatroomListFilterAssembler(
|
||||
client: NostrClient,
|
||||
) : QueryBasedSubscriptionOrchestrator<ChatroomListState>(client) {
|
||||
val latestEOSEs = EOSEAccount()
|
||||
val chatRoomListKey = "ChatroomList"
|
||||
|
||||
fun since(key: ChatroomListState) =
|
||||
latestEOSEs.users[key.account.userProfile()]
|
||||
?.followList
|
||||
?.get(chatRoomListKey)
|
||||
?.relayList
|
||||
|
||||
fun newEose(
|
||||
key: ChatroomListState,
|
||||
relayUrl: String,
|
||||
time: Long,
|
||||
) {
|
||||
latestEOSEs.addOrUpdate(
|
||||
key.account.userProfile(),
|
||||
chatRoomListKey,
|
||||
relayUrl,
|
||||
time,
|
||||
)
|
||||
}
|
||||
|
||||
fun createMessagesToMeFilter(key: ChatroomListState) =
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.PRIVATE_DMS),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(PrivateDmEvent.KIND),
|
||||
tags = mapOf("p" to listOf(key.account.userProfile().pubkeyHex)),
|
||||
since = since(key),
|
||||
),
|
||||
) : ComposeSubscriptionManager<ChatroomListState>() {
|
||||
val group =
|
||||
listOf(
|
||||
DMsFromUserFilterSubAssembler(client, ::allKeys),
|
||||
FollowingPublicChatSubAssembler(client, ::allKeys),
|
||||
FollowingEphemeralChatSubAssembler(client, ::allKeys),
|
||||
)
|
||||
|
||||
fun createMessagesFromMeFilter(key: ChatroomListState) =
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.PRIVATE_DMS),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(PrivateDmEvent.KIND),
|
||||
authors = listOf(key.account.userProfile().pubkeyHex),
|
||||
since = since(key),
|
||||
),
|
||||
)
|
||||
override fun start() = group.forEach { it.start() }
|
||||
|
||||
fun createChannelsCreatedbyMeFilter(key: ChatroomListState) =
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.PUBLIC_CHATS),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds =
|
||||
listOf(
|
||||
ChannelCreateEvent.KIND,
|
||||
ChannelMetadataEvent.KIND,
|
||||
),
|
||||
authors = listOf(key.account.userProfile().pubkeyHex),
|
||||
since = since(key),
|
||||
),
|
||||
)
|
||||
override fun stop() = group.forEach { it.stop() }
|
||||
|
||||
fun createMyChannelsFilter(key: ChatroomListState): TypedFilter? {
|
||||
val followingEvents = key.account.publicChatList.livePublicChatEventIdSet.value
|
||||
override fun invalidateKeys() = invalidateFilters()
|
||||
|
||||
if (followingEvents.isEmpty()) return null
|
||||
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
|
||||
|
||||
return TypedFilter(
|
||||
// Metadata comes from any relay
|
||||
types = EVENT_FINDER_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(ChannelCreateEvent.KIND),
|
||||
ids = followingEvents.toList(),
|
||||
since = since(key),
|
||||
),
|
||||
)
|
||||
}
|
||||
override fun destroy() = group.forEach { it.start() }
|
||||
|
||||
fun createLastChannelInfoFilter(key: ChatroomListState): List<TypedFilter>? {
|
||||
val followingEvents =
|
||||
key.account.publicChatList.livePublicChatEventIdSet.value
|
||||
|
||||
if (followingEvents.isEmpty()) return null
|
||||
|
||||
return followingEvents.map {
|
||||
TypedFilter(
|
||||
// Metadata comes from any relay
|
||||
types = EVENT_FINDER_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(ChannelMetadataEvent.KIND),
|
||||
tags = mapOf("e" to listOf(it)),
|
||||
since = since(key),
|
||||
limit = 1,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun createLastMessageOfEachChannelFilter(key: ChatroomListState): List<TypedFilter>? {
|
||||
val followingEvents =
|
||||
key.account.publicChatList.livePublicChatEventIdSet.value
|
||||
|
||||
if (followingEvents.isEmpty()) return null
|
||||
|
||||
return followingEvents.map {
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.PUBLIC_CHATS),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(ChannelMessageEvent.KIND),
|
||||
tags = mapOf("e" to listOf(it)),
|
||||
since = since(key),
|
||||
// Remember to consider spam that is being removed from the UI
|
||||
limit = 50,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun createLastMessageOfEachEphemeralChatFilter(key: ChatroomListState): List<TypedFilter>? {
|
||||
val followingEvents =
|
||||
key.account.ephemeralChatList.liveEphemeralChatList.value
|
||||
.map { it.id }
|
||||
|
||||
if (followingEvents.isEmpty()) return null
|
||||
|
||||
return listOf(
|
||||
TypedFilter(
|
||||
// Metadata comes from any relay
|
||||
types = setOf(FeedType.PUBLIC_CHATS),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(EphemeralChatEvent.KIND),
|
||||
tags = mapOf("d" to followingEvents),
|
||||
since = since(key),
|
||||
// Remember to consider spam that is being removed from the UI
|
||||
limit = 50,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun mergeAllFilters(key: ChatroomListState): List<TypedFilter>? =
|
||||
listOfNotNull(
|
||||
listOfNotNull(
|
||||
createMessagesToMeFilter(key),
|
||||
createMessagesFromMeFilter(key),
|
||||
createMyChannelsFilter(key),
|
||||
),
|
||||
createLastChannelInfoFilter(key),
|
||||
createLastMessageOfEachChannelFilter(key),
|
||||
createLastMessageOfEachEphemeralChatFilter(key),
|
||||
).flatten().ifEmpty { null }
|
||||
|
||||
fun newSub(key: ChatroomListState): Subscription =
|
||||
requestNewSubscription { time, relayUrl ->
|
||||
newEose(key, relayUrl, time)
|
||||
}
|
||||
|
||||
val userSubscriptionMap = mutableMapOf<User, String>()
|
||||
|
||||
fun findOrCreateSubFor(key: ChatroomListState): Subscription {
|
||||
var subId = userSubscriptionMap[key.account.userProfile()]
|
||||
return if (subId == null) {
|
||||
newSub(key).also { userSubscriptionMap[key.account.userProfile()] = it.id }
|
||||
} else {
|
||||
getSub(subId) ?: newSub(key).also { userSubscriptionMap[key.account.userProfile()] = it.id }
|
||||
}
|
||||
}
|
||||
|
||||
// One sub per subscribed account
|
||||
override fun updateSubscriptions(keys: Set<ChatroomListState>) {
|
||||
val uniqueSubscribedAccounts = keys.distinctBy { it.account }
|
||||
|
||||
val updated = mutableSetOf<User>()
|
||||
|
||||
uniqueSubscribedAccounts.forEach {
|
||||
val user = it.account.userProfile()
|
||||
val sub = findOrCreateSubFor(it)
|
||||
sub.typedFilters = mergeAllFilters(it)
|
||||
|
||||
updated.add(user)
|
||||
}
|
||||
|
||||
userSubscriptionMap.forEach {
|
||||
if (it.key !in updated) {
|
||||
dismissSubscription(it.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
override fun printStats() = group.forEach { it.printStats() }
|
||||
}
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.chats.rooms.datasource
|
||||
|
||||
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUserEoseManager
|
||||
import com.vitorpamplona.ammolite.relays.NostrClient
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
|
||||
class DMsFromUserFilterSubAssembler(
|
||||
client: NostrClient,
|
||||
allKeys: () -> Set<ChatroomListState>,
|
||||
) : PerUserEoseManager<ChatroomListState>(client, allKeys) {
|
||||
override fun updateFilter(
|
||||
key: ChatroomListState,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? =
|
||||
listOfNotNull(
|
||||
filterNip04DMsToAndFromMe(key.account.userProfile().pubkeyHex, since),
|
||||
).flatten()
|
||||
|
||||
override fun user(key: ChatroomListState) = key.account.userProfile()
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.chats.rooms.datasource
|
||||
|
||||
import com.vitorpamplona.ammolite.relays.FeedType
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.experimental.ephemChat.chat.EphemeralChatEvent
|
||||
import com.vitorpamplona.quartz.experimental.ephemChat.chat.RoomId
|
||||
|
||||
fun filterFollowingEphemeralChats(
|
||||
followingChannels: Set<RoomId>?,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? {
|
||||
if (followingChannels == null || followingChannels.isEmpty()) return null
|
||||
|
||||
val dTags =
|
||||
followingChannels.map {
|
||||
if (it.id.isBlank()) {
|
||||
"_"
|
||||
} else {
|
||||
it.id
|
||||
}
|
||||
}
|
||||
|
||||
return listOf(
|
||||
TypedFilter(
|
||||
// Metadata comes from any relay
|
||||
types = setOf(FeedType.PUBLIC_CHATS),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(EphemeralChatEvent.KIND),
|
||||
tags = mapOf("d" to dTags),
|
||||
limit = 100,
|
||||
since = since,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.chats.rooms.datasource
|
||||
|
||||
import com.vitorpamplona.ammolite.relays.EVENT_FINDER_TYPES
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent
|
||||
import kotlin.collections.toList
|
||||
|
||||
fun filterFollowingPublicChats(
|
||||
followingChannels: Set<HexKey>?,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? {
|
||||
if (followingChannels == null || followingChannels.isEmpty()) return null
|
||||
|
||||
return listOf(
|
||||
TypedFilter(
|
||||
types = EVENT_FINDER_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(ChannelCreateEvent.KIND),
|
||||
ids = followingChannels.toList(),
|
||||
since = since,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.chats.rooms.datasource
|
||||
|
||||
import com.vitorpamplona.ammolite.relays.EVENT_FINDER_TYPES
|
||||
import com.vitorpamplona.ammolite.relays.FeedType
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelMetadataEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
|
||||
import kotlin.collections.toList
|
||||
|
||||
fun filterLastMessageFollowingPublicChats(
|
||||
followingChannels: Set<HexKey>?,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? {
|
||||
if (followingChannels == null || followingChannels.isEmpty()) return null
|
||||
|
||||
return listOf(
|
||||
TypedFilter(
|
||||
// Metadata comes from any relay
|
||||
types = EVENT_FINDER_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(ChannelMetadataEvent.KIND),
|
||||
tags = mapOf("e" to followingChannels.toList()),
|
||||
since = since,
|
||||
limit = 1,
|
||||
),
|
||||
),
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.PUBLIC_CHATS),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(ChannelMessageEvent.KIND),
|
||||
tags = mapOf("e" to followingChannels.toList()),
|
||||
since = since,
|
||||
// Remember to consider spam that is being removed from the UI
|
||||
limit = 100,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.chats.rooms.datasource
|
||||
|
||||
import com.vitorpamplona.ammolite.relays.FeedType
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
|
||||
|
||||
fun filterNip04DMsToAndFromMe(
|
||||
user: HexKey?,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? {
|
||||
if (user == null || user.isEmpty()) return null
|
||||
|
||||
return listOf(
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.PRIVATE_DMS),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(PrivateDmEvent.KIND),
|
||||
tags = mapOf("p" to listOf(user)),
|
||||
since = since,
|
||||
),
|
||||
),
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.PRIVATE_DMS),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(PrivateDmEvent.KIND),
|
||||
authors = listOf(user),
|
||||
since = since,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.chats.rooms.datasource
|
||||
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUserEoseManager
|
||||
import com.vitorpamplona.ammolite.relays.NostrClient
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.datasources.Subscription
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.sample
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.collections.forEach
|
||||
|
||||
class FollowingEphemeralChatSubAssembler(
|
||||
client: NostrClient,
|
||||
allKeys: () -> Set<ChatroomListState>,
|
||||
) : PerUserEoseManager<ChatroomListState>(client, allKeys) {
|
||||
override fun updateFilter(
|
||||
key: ChatroomListState,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? =
|
||||
listOfNotNull(
|
||||
filterFollowingEphemeralChats(key.account.ephemeralChatList.liveEphemeralChatList.value, since),
|
||||
).flatten()
|
||||
|
||||
override fun user(key: ChatroomListState) = key.account.userProfile()
|
||||
|
||||
val userJobMap = mutableMapOf<User, List<Job>>()
|
||||
|
||||
@OptIn(FlowPreview::class)
|
||||
override fun newSub(key: ChatroomListState): Subscription {
|
||||
userJobMap[key.account.userProfile()]?.forEach { it.cancel() }
|
||||
userJobMap[key.account.userProfile()] =
|
||||
listOf(
|
||||
key.account.scope.launch(Dispatchers.Default) {
|
||||
key.account.ephemeralChatList.liveEphemeralChatList.sample(5000).collectLatest {
|
||||
invalidateFilters()
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
return super.newSub(key)
|
||||
}
|
||||
|
||||
override fun endSub(
|
||||
key: User,
|
||||
subId: String,
|
||||
) {
|
||||
return super.endSub(key, subId)
|
||||
userJobMap[key]?.forEach { it.cancel() }
|
||||
}
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.chats.rooms.datasource
|
||||
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUserEoseManager
|
||||
import com.vitorpamplona.ammolite.relays.NostrClient
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.datasources.Subscription
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.sample
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.collections.forEach
|
||||
|
||||
class FollowingPublicChatSubAssembler(
|
||||
client: NostrClient,
|
||||
allKeys: () -> Set<ChatroomListState>,
|
||||
) : PerUserEoseManager<ChatroomListState>(client, allKeys) {
|
||||
override fun updateFilter(
|
||||
key: ChatroomListState,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? =
|
||||
listOfNotNull(
|
||||
filterLastMessageFollowingPublicChats(key.account.publicChatList.livePublicChatEventIdSet.value, since),
|
||||
filterFollowingPublicChats(key.account.publicChatList.livePublicChatEventIdSet.value, since),
|
||||
).flatten()
|
||||
|
||||
override fun user(key: ChatroomListState) = key.account.userProfile()
|
||||
|
||||
val userJobMap = mutableMapOf<User, List<Job>>()
|
||||
|
||||
@OptIn(FlowPreview::class)
|
||||
override fun newSub(key: ChatroomListState): Subscription {
|
||||
userJobMap[key.account.userProfile()]?.forEach { it.cancel() }
|
||||
userJobMap[key.account.userProfile()] =
|
||||
listOf(
|
||||
key.account.scope.launch(Dispatchers.Default) {
|
||||
key.account.publicChatList.livePublicChatEventIdSet.sample(5000).collectLatest {
|
||||
invalidateFilters()
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
return super.newSub(key)
|
||||
}
|
||||
|
||||
override fun endSub(
|
||||
key: User,
|
||||
subId: String,
|
||||
) {
|
||||
return super.endSub(key, subId)
|
||||
userJobMap[key]?.forEach { it.cancel() }
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.communities.datasource
|
||||
|
||||
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.SingleSubEoseManager
|
||||
import com.vitorpamplona.ammolite.relays.NostrClient
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent
|
||||
|
||||
class CommunityFeedFilterSubAssembler(
|
||||
client: NostrClient,
|
||||
allKeys: () -> Set<CommunityQueryState>,
|
||||
) : SingleSubEoseManager<CommunityQueryState>(client, allKeys) {
|
||||
override fun updateFilter(
|
||||
keys: List<CommunityQueryState>,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? {
|
||||
if (keys.isEmpty()) return emptyList()
|
||||
|
||||
return keys.mapNotNull {
|
||||
val commEvent = it.community.event
|
||||
if (commEvent is CommunityDefinitionEvent) {
|
||||
filterCommunityPosts(commEvent, since)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun distinct(key: CommunityQueryState) = key.community.idHex
|
||||
}
|
||||
+14
-35
@@ -21,13 +21,8 @@
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.communities.datasource
|
||||
|
||||
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.QueryBasedSubscriptionOrchestrator
|
||||
import com.vitorpamplona.ammolite.relays.COMMON_FEED_TYPES
|
||||
import com.vitorpamplona.amethyst.service.relayClient.composeSubscriptionManagers.ComposeSubscriptionManager
|
||||
import com.vitorpamplona.ammolite.relays.NostrClient
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.approval.CommunityPostApprovalEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent
|
||||
|
||||
// This allows multiple screen to be listening to tags, even the same tag
|
||||
class CommunityQueryState(
|
||||
@@ -36,37 +31,21 @@ class CommunityQueryState(
|
||||
|
||||
class CommunityFilterAssembler(
|
||||
client: NostrClient,
|
||||
) : QueryBasedSubscriptionOrchestrator<CommunityQueryState>(client) {
|
||||
private fun createLoadCommunityFilter(keys: Set<CommunityQueryState>): List<TypedFilter> {
|
||||
if (keys.isEmpty()) return emptyList()
|
||||
) : ComposeSubscriptionManager<CommunityQueryState>() {
|
||||
val group =
|
||||
listOf(
|
||||
CommunityFeedFilterSubAssembler(client, ::allKeys),
|
||||
)
|
||||
|
||||
val uniqueCommunities =
|
||||
keys.associate {
|
||||
it.community.address.toValue() to it.community
|
||||
}
|
||||
override fun start() = group.forEach { it.start() }
|
||||
|
||||
return uniqueCommunities.mapNotNull {
|
||||
val commEvent = it.value.event
|
||||
if (commEvent is CommunityDefinitionEvent) {
|
||||
TypedFilter(
|
||||
types = COMMON_FEED_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
authors = commEvent.moderators().map { it.pubKey }.plus(listOfNotNull(it.value.author?.pubkeyHex)),
|
||||
tags = mapOf("a" to listOf(it.value.address.toValue())),
|
||||
kinds = listOf(CommunityPostApprovalEvent.KIND),
|
||||
limit = 500,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
override fun stop() = group.forEach { it.stop() }
|
||||
|
||||
val loadCommunityChannel = requestNewSubscription()
|
||||
override fun invalidateKeys() = invalidateFilters()
|
||||
|
||||
override fun updateSubscriptions(keys: Set<CommunityQueryState>) {
|
||||
loadCommunityChannel.typedFilters = createLoadCommunityFilter(keys).ifEmpty { null }
|
||||
}
|
||||
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
|
||||
|
||||
override fun destroy() = group.forEach { it.start() }
|
||||
|
||||
override fun printStats() = group.forEach { it.printStats() }
|
||||
}
|
||||
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.communities.datasource
|
||||
|
||||
import com.vitorpamplona.ammolite.relays.COMMON_FEED_TYPES
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.approval.CommunityPostApprovalEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent
|
||||
|
||||
fun filterCommunityPosts(
|
||||
community: CommunityDefinitionEvent,
|
||||
since: Map<String, EOSETime>?,
|
||||
): TypedFilter =
|
||||
TypedFilter(
|
||||
types = COMMON_FEED_TYPES,
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
authors = community.moderators().map { it.pubKey }.plus(community.pubKey),
|
||||
tags = mapOf("a" to listOf(community.addressTag())),
|
||||
kinds = listOf(CommunityPostApprovalEvent.KIND),
|
||||
limit = 500,
|
||||
since = since,
|
||||
),
|
||||
)
|
||||
+12
-520
@@ -21,31 +21,9 @@
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.datasource
|
||||
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.QueryBasedSubscriptionOrchestrator
|
||||
import com.vitorpamplona.amethyst.service.relays.EOSEAccount
|
||||
import com.vitorpamplona.ammolite.relays.FeedType
|
||||
import com.vitorpamplona.amethyst.service.relayClient.composeSubscriptionManagers.ComposeSubscriptionManager
|
||||
import com.vitorpamplona.ammolite.relays.NostrClient
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.datasources.Subscription
|
||||
import com.vitorpamplona.ammolite.relays.filters.SinceAuthorPerRelayFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelMetadataEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
|
||||
import com.vitorpamplona.quartz.nip51Lists.FollowListEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.approval.CommunityPostApprovalEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent
|
||||
import com.vitorpamplona.quartz.nip89AppHandlers.definition.AppDefinitionEvent
|
||||
import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.Locale
|
||||
|
||||
// This allows multiple screen to be listening to tags, even the same tag
|
||||
class DiscoveryQueryState(
|
||||
@@ -55,508 +33,22 @@ class DiscoveryQueryState(
|
||||
|
||||
class DiscoveryFilterAssembler(
|
||||
client: NostrClient,
|
||||
) : QueryBasedSubscriptionOrchestrator<DiscoveryQueryState>(client) {
|
||||
val latestEOSEs = EOSEAccount()
|
||||
|
||||
fun since(key: DiscoveryQueryState) =
|
||||
latestEOSEs.users[key.account.userProfile()]
|
||||
?.followList
|
||||
?.get(key.account.settings.defaultDiscoveryFollowList.value)
|
||||
?.relayList
|
||||
|
||||
fun newEose(
|
||||
key: DiscoveryQueryState,
|
||||
relayUrl: String,
|
||||
time: Long,
|
||||
) {
|
||||
latestEOSEs.addOrUpdate(
|
||||
key.account.userProfile(),
|
||||
key.account.settings.defaultDiscoveryFollowList.value,
|
||||
relayUrl,
|
||||
time,
|
||||
)
|
||||
}
|
||||
|
||||
fun createMarketplaceFilter(key: DiscoveryQueryState): List<TypedFilter> {
|
||||
val follows =
|
||||
key.account.liveDiscoveryListAuthorsPerRelay.value
|
||||
?.ifEmpty { null }
|
||||
val hashToLoad =
|
||||
key.account.liveDiscoveryFollowLists.value
|
||||
?.hashtags
|
||||
?.toList()
|
||||
?.ifEmpty { null }
|
||||
val geohashToLoad =
|
||||
key.account.liveDiscoveryFollowLists.value
|
||||
?.geotags
|
||||
?.toList()
|
||||
?.ifEmpty { null }
|
||||
|
||||
return listOfNotNull(
|
||||
TypedFilter(
|
||||
types = if (follows == null) setOf(FeedType.GLOBAL) else setOf(FeedType.FOLLOWS),
|
||||
filter =
|
||||
SinceAuthorPerRelayFilter(
|
||||
authors = follows,
|
||||
kinds = listOf(ClassifiedsEvent.KIND),
|
||||
limit = 300,
|
||||
since = since(key),
|
||||
),
|
||||
),
|
||||
hashToLoad?.let {
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.GLOBAL),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(ClassifiedsEvent.KIND),
|
||||
tags =
|
||||
mapOf(
|
||||
"t" to
|
||||
it
|
||||
.map {
|
||||
listOf(
|
||||
it,
|
||||
it.lowercase(),
|
||||
it.uppercase(),
|
||||
it.replaceFirstChar {
|
||||
if (it.isLowerCase()) it.titlecase(Locale.ROOT) else it.toString()
|
||||
},
|
||||
)
|
||||
}.flatten(),
|
||||
),
|
||||
limit = 300,
|
||||
since = since(key),
|
||||
),
|
||||
)
|
||||
},
|
||||
geohashToLoad?.let {
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.GLOBAL),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(ClassifiedsEvent.KIND),
|
||||
tags =
|
||||
mapOf(
|
||||
"g" to it,
|
||||
),
|
||||
limit = 300,
|
||||
since = since(key),
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fun createNIP89Filter(key: DiscoveryQueryState): List<TypedFilter> =
|
||||
listOfNotNull(
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.GLOBAL),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(AppDefinitionEvent.KIND),
|
||||
limit = 300,
|
||||
tags = mapOf("k" to listOf("5300")),
|
||||
since = since(key),
|
||||
),
|
||||
),
|
||||
) : ComposeSubscriptionManager<DiscoveryQueryState>() {
|
||||
val group =
|
||||
listOf(
|
||||
DiscoveryFollowsDiscoverySubAssembler(client, ::allKeys),
|
||||
MixGeohashHashtagsDiscoverySubAssembler(client, ::allKeys),
|
||||
)
|
||||
|
||||
fun createLiveStreamFilter(key: DiscoveryQueryState): List<TypedFilter> {
|
||||
val follows =
|
||||
key.account.liveDiscoveryFollowLists.value
|
||||
?.authors
|
||||
?.toList()
|
||||
?.ifEmpty { null }
|
||||
override fun start() = group.forEach { it.start() }
|
||||
|
||||
val followsRelays = key.account.liveDiscoveryListAuthorsPerRelay.value
|
||||
override fun stop() = group.forEach { it.stop() }
|
||||
|
||||
return listOfNotNull(
|
||||
TypedFilter(
|
||||
types = if (follows == null) setOf(FeedType.GLOBAL) else setOf(FeedType.FOLLOWS),
|
||||
filter =
|
||||
SinceAuthorPerRelayFilter(
|
||||
authors = followsRelays,
|
||||
kinds =
|
||||
listOf(
|
||||
LiveActivitiesChatMessageEvent.KIND,
|
||||
LiveActivitiesEvent.KIND,
|
||||
),
|
||||
limit = 300,
|
||||
since = since(key),
|
||||
),
|
||||
),
|
||||
follows?.let {
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.FOLLOWS),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
tags = mapOf("p" to it),
|
||||
kinds = listOf(LiveActivitiesEvent.KIND),
|
||||
limit = 100,
|
||||
since = since(key),
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
override fun invalidateKeys() = invalidateFilters()
|
||||
|
||||
fun createPublicChatFilter(key: DiscoveryQueryState): List<TypedFilter> {
|
||||
val follows =
|
||||
key.account.liveDiscoveryListAuthorsPerRelay.value
|
||||
?.ifEmpty { null }
|
||||
val followChats =
|
||||
key.account.publicChatList.livePublicChatEventIdSet.value
|
||||
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
|
||||
|
||||
return listOfNotNull(
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.PUBLIC_CHATS),
|
||||
filter =
|
||||
SinceAuthorPerRelayFilter(
|
||||
authors = follows,
|
||||
kinds = listOf(ChannelMessageEvent.KIND),
|
||||
limit = 500,
|
||||
since = since(key),
|
||||
),
|
||||
),
|
||||
if (followChats.isNotEmpty()) {
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.PUBLIC_CHATS),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
ids = followChats.toList(),
|
||||
kinds =
|
||||
listOf(
|
||||
ChannelCreateEvent.KIND,
|
||||
ChannelMessageEvent.KIND,
|
||||
),
|
||||
limit = 300,
|
||||
since = since(key),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
null
|
||||
},
|
||||
)
|
||||
}
|
||||
override fun destroy() = group.forEach { it.start() }
|
||||
|
||||
fun createFollowSetFilter(key: DiscoveryQueryState): List<TypedFilter> {
|
||||
val follows =
|
||||
key.account.liveDiscoveryListAuthorsPerRelay.value
|
||||
?.ifEmpty { null }
|
||||
|
||||
return listOfNotNull(
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.FOLLOWS),
|
||||
filter =
|
||||
SinceAuthorPerRelayFilter(
|
||||
authors = follows,
|
||||
kinds = listOf(FollowListEvent.KIND),
|
||||
limit = 300,
|
||||
since = since(key),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun createLongFormFilter(key: DiscoveryQueryState): List<TypedFilter> {
|
||||
val follows =
|
||||
key.account.liveDiscoveryListAuthorsPerRelay.value
|
||||
?.ifEmpty { null }
|
||||
|
||||
return listOfNotNull(
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.FOLLOWS),
|
||||
filter =
|
||||
SinceAuthorPerRelayFilter(
|
||||
authors = follows,
|
||||
kinds = listOf(LongTextNoteEvent.KIND),
|
||||
limit = 300,
|
||||
since = since(key),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun createCommunitiesFilter(key: DiscoveryQueryState): TypedFilter {
|
||||
val follows = key.account.liveDiscoveryListAuthorsPerRelay.value
|
||||
|
||||
return TypedFilter(
|
||||
types = if (follows == null) setOf(FeedType.GLOBAL) else setOf(FeedType.FOLLOWS),
|
||||
filter =
|
||||
SinceAuthorPerRelayFilter(
|
||||
authors = follows,
|
||||
kinds =
|
||||
listOf(
|
||||
CommunityDefinitionEvent.KIND,
|
||||
CommunityPostApprovalEvent.KIND,
|
||||
),
|
||||
limit = 300,
|
||||
since = since(key),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun createLiveStreamTagsFilter(key: DiscoveryQueryState): TypedFilter? {
|
||||
val hashToLoad =
|
||||
key.account.liveDiscoveryFollowLists.value
|
||||
?.hashtags
|
||||
?.toList()
|
||||
|
||||
if (hashToLoad.isNullOrEmpty()) return null
|
||||
|
||||
return TypedFilter(
|
||||
types = setOf(FeedType.GLOBAL),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds =
|
||||
listOf(
|
||||
LiveActivitiesChatMessageEvent.KIND,
|
||||
LiveActivitiesEvent.KIND,
|
||||
),
|
||||
tags =
|
||||
mapOf(
|
||||
"t" to
|
||||
hashToLoad
|
||||
.map {
|
||||
listOf(
|
||||
it,
|
||||
it.lowercase(),
|
||||
it.uppercase(),
|
||||
it.replaceFirstChar {
|
||||
if (it.isLowerCase()) it.titlecase(Locale.ROOT) else it.toString()
|
||||
},
|
||||
)
|
||||
}.flatten(),
|
||||
),
|
||||
limit = 300,
|
||||
since = since(key),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun createLiveStreamGeohashesFilter(key: DiscoveryQueryState): TypedFilter? {
|
||||
val hashToLoad =
|
||||
key.account.liveDiscoveryFollowLists.value
|
||||
?.geotags
|
||||
?.toList()
|
||||
|
||||
if (hashToLoad.isNullOrEmpty()) return null
|
||||
|
||||
return TypedFilter(
|
||||
types = setOf(FeedType.GLOBAL),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds =
|
||||
listOf(
|
||||
LiveActivitiesChatMessageEvent.KIND,
|
||||
LiveActivitiesEvent.KIND,
|
||||
),
|
||||
tags = mapOf("g" to hashToLoad),
|
||||
limit = 300,
|
||||
since = since(key),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun createPublicChatsTagsFilter(key: DiscoveryQueryState): TypedFilter? {
|
||||
val hashToLoad =
|
||||
key.account.liveDiscoveryFollowLists.value
|
||||
?.hashtags
|
||||
?.toList()
|
||||
|
||||
if (hashToLoad.isNullOrEmpty()) return null
|
||||
|
||||
return TypedFilter(
|
||||
types = setOf(FeedType.PUBLIC_CHATS),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds =
|
||||
listOf(
|
||||
ChannelCreateEvent.KIND,
|
||||
ChannelMetadataEvent.KIND,
|
||||
ChannelMessageEvent.KIND,
|
||||
),
|
||||
tags =
|
||||
mapOf(
|
||||
"t" to
|
||||
hashToLoad
|
||||
.map {
|
||||
listOf(
|
||||
it,
|
||||
it.lowercase(),
|
||||
it.uppercase(),
|
||||
it.replaceFirstChar {
|
||||
if (it.isLowerCase()) it.titlecase(Locale.ROOT) else it.toString()
|
||||
},
|
||||
)
|
||||
}.flatten(),
|
||||
),
|
||||
limit = 300,
|
||||
since = since(key),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun createPublicChatsGeohashesFilter(key: DiscoveryQueryState): TypedFilter? {
|
||||
val hashToLoad =
|
||||
key.account.liveDiscoveryFollowLists.value
|
||||
?.geotags
|
||||
?.toList()
|
||||
|
||||
if (hashToLoad.isNullOrEmpty()) return null
|
||||
|
||||
return TypedFilter(
|
||||
types = setOf(FeedType.PUBLIC_CHATS),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds =
|
||||
listOf(
|
||||
ChannelCreateEvent.KIND,
|
||||
ChannelMetadataEvent.KIND,
|
||||
ChannelMessageEvent.KIND,
|
||||
),
|
||||
tags =
|
||||
mapOf("g" to hashToLoad),
|
||||
limit = 300,
|
||||
since = since(key),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun createCommunitiesTagsFilter(key: DiscoveryQueryState): TypedFilter? {
|
||||
val hashToLoad =
|
||||
key.account.liveDiscoveryFollowLists.value
|
||||
?.hashtags
|
||||
?.toList()
|
||||
|
||||
if (hashToLoad.isNullOrEmpty()) return null
|
||||
|
||||
return TypedFilter(
|
||||
types = setOf(FeedType.GLOBAL),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds =
|
||||
listOf(
|
||||
CommunityDefinitionEvent.KIND,
|
||||
CommunityPostApprovalEvent.KIND,
|
||||
),
|
||||
tags =
|
||||
mapOf(
|
||||
"t" to
|
||||
hashToLoad
|
||||
.map {
|
||||
listOf(
|
||||
it,
|
||||
it.lowercase(),
|
||||
it.uppercase(),
|
||||
it.replaceFirstChar {
|
||||
if (it.isLowerCase()) it.titlecase(Locale.ROOT) else it.toString()
|
||||
},
|
||||
)
|
||||
}.flatten(),
|
||||
),
|
||||
limit = 300,
|
||||
since = since(key),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun createCommunitiesGeohashesFilter(key: DiscoveryQueryState): TypedFilter? {
|
||||
val hashToLoad =
|
||||
key.account.liveDiscoveryFollowLists.value
|
||||
?.geotags
|
||||
?.toList()
|
||||
|
||||
if (hashToLoad.isNullOrEmpty()) return null
|
||||
|
||||
return TypedFilter(
|
||||
types = setOf(FeedType.GLOBAL),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds =
|
||||
listOf(
|
||||
CommunityDefinitionEvent.KIND,
|
||||
CommunityPostApprovalEvent.KIND,
|
||||
),
|
||||
tags = mapOf("g" to hashToLoad),
|
||||
limit = 300,
|
||||
since = since(key),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun mergeAllFilters(key: DiscoveryQueryState): List<TypedFilter>? =
|
||||
createLiveStreamFilter(key)
|
||||
.plus(createNIP89Filter(key))
|
||||
.plus(createPublicChatFilter(key))
|
||||
.plus(createFollowSetFilter(key))
|
||||
.plus(createLongFormFilter(key))
|
||||
.plus(createMarketplaceFilter(key))
|
||||
.plus(
|
||||
listOfNotNull(
|
||||
createLiveStreamTagsFilter(key),
|
||||
createLiveStreamGeohashesFilter(key),
|
||||
createCommunitiesFilter(key),
|
||||
createCommunitiesTagsFilter(key),
|
||||
createCommunitiesGeohashesFilter(key),
|
||||
createPublicChatsTagsFilter(key),
|
||||
createPublicChatsGeohashesFilter(key),
|
||||
),
|
||||
).toList()
|
||||
.ifEmpty { null }
|
||||
|
||||
val userJobMap = mutableMapOf<User, Job>()
|
||||
val userSubscriptionMap = mutableMapOf<User, String>()
|
||||
|
||||
fun newSub(key: DiscoveryQueryState): Subscription {
|
||||
userJobMap[key.account.userProfile()]?.cancel()
|
||||
userJobMap[key.account.userProfile()] =
|
||||
key.scope.launch(Dispatchers.Default) {
|
||||
key.account.liveDiscoveryFollowLists.collect {
|
||||
invalidateFilters()
|
||||
}
|
||||
}
|
||||
|
||||
return requestNewSubscription { time, relayUrl ->
|
||||
newEose(key, relayUrl, time)
|
||||
}
|
||||
}
|
||||
|
||||
fun endSub(
|
||||
key: User,
|
||||
subId: String,
|
||||
) {
|
||||
dismissSubscription(subId)
|
||||
userJobMap[key]?.cancel()
|
||||
}
|
||||
|
||||
fun findOrCreateSubFor(key: DiscoveryQueryState): Subscription {
|
||||
var subId = userSubscriptionMap[key.account.userProfile()]
|
||||
return if (subId == null) {
|
||||
newSub(key).also { userSubscriptionMap[key.account.userProfile()] = it.id }
|
||||
} else {
|
||||
getSub(subId) ?: newSub(key).also { userSubscriptionMap[key.account.userProfile()] = it.id }
|
||||
}
|
||||
}
|
||||
|
||||
// One sub per subscribed account
|
||||
override fun updateSubscriptions(keys: Set<DiscoveryQueryState>) {
|
||||
val uniqueSubscribedAccounts = keys.distinctBy { it.account }
|
||||
|
||||
val updated = mutableSetOf<User>()
|
||||
|
||||
uniqueSubscribedAccounts.forEach {
|
||||
val user = it.account.userProfile()
|
||||
val sub = findOrCreateSubFor(it)
|
||||
sub.typedFilters = mergeAllFilters(it)
|
||||
|
||||
updated.add(user)
|
||||
}
|
||||
|
||||
userSubscriptionMap.forEach {
|
||||
if (it.key !in updated) {
|
||||
endSub(it.key, it.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
override fun printStats() = group.forEach { it.printStats() }
|
||||
}
|
||||
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.discover.datasource
|
||||
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUserAndFollowListEoseManager
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip23LongForm.subassemblies.filterLongFormByFollows
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip28Chats.subassemblies.filterPublicChatsByFollows
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip51FollowSets.subassemblies.filterFollowSetsByFollows
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip53LiveActivities.subassemblies.filterLiveActivitiesByFollows
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip72Communities.subassemblies.filterCommunitiesByFollows
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip90DVMs.subassemblies.filterContentDVMsByFollows
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip99Classifieds.subassemblies.filterClassifiedsByFollows
|
||||
import com.vitorpamplona.ammolite.relays.NostrClient
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.datasources.Subscription
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.sample
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.collections.flatten
|
||||
|
||||
class DiscoveryFollowsDiscoverySubAssembler(
|
||||
client: NostrClient,
|
||||
allKeys: () -> Set<DiscoveryQueryState>,
|
||||
) : PerUserAndFollowListEoseManager<DiscoveryQueryState>(client, allKeys) {
|
||||
override fun updateFilter(
|
||||
key: DiscoveryQueryState,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? = updateFilter(key.followsPerRelay(), key.followLists(), since)
|
||||
|
||||
override fun user(key: DiscoveryQueryState) = key.account.userProfile()
|
||||
|
||||
override fun list(key: DiscoveryQueryState) = key.listName()
|
||||
|
||||
fun DiscoveryQueryState.listNameFlow() = account.settings.defaultDiscoveryFollowList
|
||||
|
||||
fun DiscoveryQueryState.listName() = listNameFlow().value
|
||||
|
||||
fun DiscoveryQueryState.followListsFlow() = account.liveDiscoveryFollowLists
|
||||
|
||||
fun DiscoveryQueryState.followLists() = followListsFlow().value
|
||||
|
||||
fun DiscoveryQueryState.followsPerRelayFlow() = account.liveDiscoveryListAuthorsPerRelay
|
||||
|
||||
fun DiscoveryQueryState.followsPerRelay() = followsPerRelayFlow().value
|
||||
|
||||
fun updateFilter(
|
||||
follows: Map<String, List<HexKey>>?,
|
||||
followLists: Account.LiveFollowList?,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? {
|
||||
if (follows != null && follows.isEmpty()) return null
|
||||
|
||||
return listOfNotNull(
|
||||
filterClassifiedsByFollows(follows, since),
|
||||
filterFollowSetsByFollows(follows, since),
|
||||
filterLongFormByFollows(follows, since),
|
||||
filterPublicChatsByFollows(follows, since),
|
||||
filterContentDVMsByFollows(follows, since),
|
||||
filterLiveActivitiesByFollows(follows, followLists?.authorsPlusMe, since),
|
||||
filterCommunitiesByFollows(follows, since),
|
||||
).flatten()
|
||||
}
|
||||
|
||||
val userJobMap = mutableMapOf<User, List<Job>>()
|
||||
|
||||
@OptIn(FlowPreview::class)
|
||||
override fun newSub(key: DiscoveryQueryState): Subscription {
|
||||
val user = user(key)
|
||||
userJobMap[user]?.forEach { it.cancel() }
|
||||
userJobMap[user] =
|
||||
listOf(
|
||||
key.scope.launch(Dispatchers.Default) {
|
||||
key.listNameFlow().collectLatest {
|
||||
invalidateFilters()
|
||||
}
|
||||
},
|
||||
key.scope.launch(Dispatchers.Default) {
|
||||
key.followsPerRelayFlow().sample(5000).collectLatest {
|
||||
invalidateFilters()
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
return super.newSub(key)
|
||||
}
|
||||
|
||||
override fun endSub(
|
||||
key: User,
|
||||
subId: String,
|
||||
) {
|
||||
return super.endSub(key, subId)
|
||||
userJobMap[key]?.forEach { it.cancel() }
|
||||
}
|
||||
}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.discover.datasource
|
||||
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUserEoseManager
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip23LongForm.subassemblies.filterLongFormByGeohash
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip23LongForm.subassemblies.filterLongFormByHashtag
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip28Chats.subassemblies.filterPublicChatsByGeohash
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip28Chats.subassemblies.filterPublicChatsByHashtag
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip51FollowSets.subassemblies.filterFollowSetsByGeohash
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip51FollowSets.subassemblies.filterFollowSetsByHashtag
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip53LiveActivities.subassemblies.filterLiveActivitiesByGeohash
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip53LiveActivities.subassemblies.filterLiveActivitiesByHashtag
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip72Communities.subassemblies.filterCommunityPostsByGeohash
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip72Communities.subassemblies.filterCommunityPostsByHashtag
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip90DVMs.subassemblies.filterContentDVMsByGeohash
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip90DVMs.subassemblies.filterContentDVMsByHashtag
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip99Classifieds.subassemblies.filterClassifiedsByGeohash
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip99Classifieds.subassemblies.filterClassifiedsByHashtag
|
||||
import com.vitorpamplona.ammolite.relays.NostrClient
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.datasources.Subscription
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class MixGeohashHashtagsDiscoverySubAssembler(
|
||||
client: NostrClient,
|
||||
allKeys: () -> Set<DiscoveryQueryState>,
|
||||
) : PerUserEoseManager<DiscoveryQueryState>(client, allKeys) {
|
||||
override fun updateFilter(
|
||||
key: DiscoveryQueryState,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? = updateFilter(key.followLists()?.hashtags, key.followLists()?.geotags, since)
|
||||
|
||||
fun updateFilter(
|
||||
hashtags: Set<String>?,
|
||||
geotags: Set<String>?,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? {
|
||||
val hashtagFilters =
|
||||
if (hashtags != null && !hashtags.isEmpty()) {
|
||||
listOfNotNull(
|
||||
filterClassifiedsByHashtag(hashtags, since),
|
||||
filterPublicChatsByHashtag(hashtags, since),
|
||||
filterFollowSetsByHashtag(hashtags, since),
|
||||
filterLongFormByHashtag(hashtags, since),
|
||||
filterContentDVMsByHashtag(hashtags, since),
|
||||
filterLiveActivitiesByHashtag(hashtags, since),
|
||||
filterCommunityPostsByHashtag(hashtags, since),
|
||||
).flatten()
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
|
||||
val geoHashFilters =
|
||||
if (geotags != null && !geotags.isEmpty()) {
|
||||
listOfNotNull(
|
||||
filterClassifiedsByGeohash(geotags, since),
|
||||
filterPublicChatsByGeohash(geotags, since),
|
||||
filterFollowSetsByGeohash(geotags, since),
|
||||
filterLongFormByGeohash(geotags, since),
|
||||
filterContentDVMsByGeohash(geotags, since),
|
||||
filterLiveActivitiesByGeohash(geotags, since),
|
||||
filterCommunityPostsByGeohash(geotags, since),
|
||||
).flatten()
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
|
||||
return hashtagFilters + geoHashFilters
|
||||
}
|
||||
|
||||
override fun user(key: DiscoveryQueryState) = key.account.userProfile()
|
||||
|
||||
fun DiscoveryQueryState.followListsFlow() = account.liveDiscoveryFollowLists
|
||||
|
||||
fun DiscoveryQueryState.followLists() = followListsFlow().value
|
||||
|
||||
val userJobMap = mutableMapOf<User, Job>()
|
||||
|
||||
override fun newSub(key: DiscoveryQueryState): Subscription {
|
||||
val user = user(key)
|
||||
userJobMap[user]?.cancel()
|
||||
userJobMap[user] =
|
||||
key.scope.launch(Dispatchers.Default) {
|
||||
key.followListsFlow().collectLatest {
|
||||
invalidateFilters()
|
||||
}
|
||||
}
|
||||
|
||||
return super.newSub(key)
|
||||
}
|
||||
|
||||
override fun endSub(
|
||||
key: User,
|
||||
subId: String,
|
||||
) {
|
||||
return super.endSub(key, subId)
|
||||
userJobMap[key]?.cancel()
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.discover.nip23LongForm.subassemblies
|
||||
|
||||
import com.vitorpamplona.ammolite.relays.FeedType
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.ammolite.relays.filters.SinceAuthorPerRelayFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent
|
||||
|
||||
fun filterLongFormByFollows(
|
||||
follows: Map<String, List<HexKey>>?,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? {
|
||||
if (follows != null && follows.isEmpty()) return null
|
||||
|
||||
return listOf(
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.FOLLOWS),
|
||||
filter =
|
||||
SinceAuthorPerRelayFilter(
|
||||
authors = follows,
|
||||
kinds = listOf(LongTextNoteEvent.KIND),
|
||||
limit = 300,
|
||||
since = since,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.discover.nip23LongForm.subassemblies
|
||||
|
||||
import com.vitorpamplona.ammolite.relays.FeedType
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent
|
||||
|
||||
fun filterLongFormByGeohash(
|
||||
hashToLoad: Set<String>?,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? {
|
||||
if (hashToLoad == null || hashToLoad.isEmpty()) return null
|
||||
|
||||
val geoHashes = hashToLoad.toList()
|
||||
|
||||
return listOf(
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.FOLLOWS),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(LongTextNoteEvent.KIND),
|
||||
tags = mapOf("g" to geoHashes),
|
||||
limit = 300,
|
||||
since = since,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.discover.nip23LongForm.subassemblies
|
||||
|
||||
import com.vitorpamplona.ammolite.relays.FeedType
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hashtagAlts
|
||||
import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent
|
||||
|
||||
fun filterLongFormByHashtag(
|
||||
hashToLoad: Set<String>?,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? {
|
||||
if (hashToLoad == null || hashToLoad.isEmpty()) return null
|
||||
|
||||
val hashtags = hashtagAlts(hashToLoad).toList()
|
||||
|
||||
return listOf(
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.FOLLOWS),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds =
|
||||
listOf(
|
||||
LongTextNoteEvent.KIND,
|
||||
),
|
||||
tags = mapOf("t" to hashtags),
|
||||
limit = 300,
|
||||
since = since,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.discover.nip28Chats.subassemblies
|
||||
|
||||
import com.vitorpamplona.ammolite.relays.FeedType
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.ammolite.relays.filters.SinceAuthorPerRelayFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
|
||||
|
||||
fun filterPublicChatsByFollows(
|
||||
follows: Map<String, List<HexKey>>?,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? {
|
||||
if (follows != null && follows.isEmpty()) return null
|
||||
|
||||
return listOf(
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.PUBLIC_CHATS),
|
||||
filter =
|
||||
SinceAuthorPerRelayFilter(
|
||||
authors = follows,
|
||||
kinds = listOf(ChannelMessageEvent.KIND),
|
||||
limit = 500,
|
||||
since = since,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.discover.nip28Chats.subassemblies
|
||||
|
||||
import com.vitorpamplona.ammolite.relays.FeedType
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelMetadataEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
|
||||
|
||||
fun filterPublicChatsByGeohash(
|
||||
hashToLoad: Set<String>?,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? {
|
||||
if (hashToLoad == null || hashToLoad.isEmpty()) return null
|
||||
|
||||
val geoHashes = hashToLoad.toList()
|
||||
|
||||
return listOf(
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.PUBLIC_CHATS),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds =
|
||||
listOf(
|
||||
ChannelCreateEvent.KIND,
|
||||
ChannelMetadataEvent.KIND,
|
||||
ChannelMessageEvent.KIND,
|
||||
),
|
||||
tags = mapOf("g" to geoHashes),
|
||||
limit = 300,
|
||||
since = since,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.discover.nip28Chats.subassemblies
|
||||
|
||||
import com.vitorpamplona.ammolite.relays.FeedType
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hashtagAlts
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelMetadataEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
|
||||
|
||||
fun filterPublicChatsByHashtag(
|
||||
hashToLoad: Set<String>?,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? {
|
||||
if (hashToLoad == null || hashToLoad.isEmpty()) return null
|
||||
|
||||
val hashtags = hashtagAlts(hashToLoad).toList()
|
||||
|
||||
return listOf(
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.PUBLIC_CHATS),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds =
|
||||
listOf(
|
||||
ChannelCreateEvent.KIND,
|
||||
ChannelMetadataEvent.KIND,
|
||||
ChannelMessageEvent.KIND,
|
||||
),
|
||||
tags = mapOf("t" to hashtags),
|
||||
limit = 300,
|
||||
since = since,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.discover.nip51FollowSets.subassemblies
|
||||
|
||||
import com.vitorpamplona.ammolite.relays.FeedType
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.ammolite.relays.filters.SinceAuthorPerRelayFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip51Lists.FollowListEvent
|
||||
|
||||
fun filterFollowSetsByFollows(
|
||||
follows: Map<String, List<HexKey>>?,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? {
|
||||
if (follows != null && follows.isEmpty()) return null
|
||||
|
||||
return listOf(
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.FOLLOWS),
|
||||
filter =
|
||||
SinceAuthorPerRelayFilter(
|
||||
authors = follows,
|
||||
kinds = listOf(FollowListEvent.KIND),
|
||||
limit = 300,
|
||||
since = since,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.discover.nip51FollowSets.subassemblies
|
||||
|
||||
import com.vitorpamplona.ammolite.relays.FeedType
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent
|
||||
|
||||
fun filterFollowSetsByGeohash(
|
||||
hashToLoad: Set<String>?,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? {
|
||||
if (hashToLoad == null || hashToLoad.isEmpty()) return null
|
||||
|
||||
val geoHashes = hashToLoad.toList()
|
||||
|
||||
return listOf(
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.FOLLOWS),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds = listOf(LongTextNoteEvent.KIND),
|
||||
tags = mapOf("g" to geoHashes),
|
||||
limit = 300,
|
||||
since = since,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.discover.nip51FollowSets.subassemblies
|
||||
|
||||
import com.vitorpamplona.ammolite.relays.FeedType
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hashtagAlts
|
||||
import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent
|
||||
|
||||
fun filterFollowSetsByHashtag(
|
||||
hashToLoad: Set<String>?,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? {
|
||||
if (hashToLoad == null || hashToLoad.isEmpty()) return null
|
||||
|
||||
val hashtags = hashtagAlts(hashToLoad).toList()
|
||||
|
||||
return listOf(
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.FOLLOWS),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds =
|
||||
listOf(
|
||||
LongTextNoteEvent.KIND,
|
||||
),
|
||||
tags = mapOf("t" to hashtags),
|
||||
limit = 300,
|
||||
since = since,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.discover.nip53LiveActivities.subassemblies
|
||||
|
||||
import com.vitorpamplona.ammolite.relays.FeedType
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.ammolite.relays.filters.SinceAuthorPerRelayFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
|
||||
|
||||
fun filterLiveActivitiesByFollows(
|
||||
follows: Map<String, List<HexKey>>?,
|
||||
followKeys: Set<String>?,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? {
|
||||
if (follows != null && follows.isEmpty()) return null
|
||||
|
||||
return listOfNotNull(
|
||||
TypedFilter(
|
||||
types = if (follows == null) setOf(FeedType.GLOBAL) else setOf(FeedType.FOLLOWS),
|
||||
filter =
|
||||
SinceAuthorPerRelayFilter(
|
||||
authors = follows,
|
||||
kinds =
|
||||
listOf(
|
||||
LiveActivitiesChatMessageEvent.KIND,
|
||||
LiveActivitiesEvent.KIND,
|
||||
),
|
||||
limit = 300,
|
||||
since = since,
|
||||
),
|
||||
),
|
||||
followKeys?.let {
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.FOLLOWS),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
tags = mapOf("p" to it.toList()),
|
||||
kinds = listOf(LiveActivitiesEvent.KIND),
|
||||
limit = 100,
|
||||
since = since,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.discover.nip53LiveActivities.subassemblies
|
||||
|
||||
import com.vitorpamplona.ammolite.relays.FeedType
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
|
||||
|
||||
fun filterLiveActivitiesByGeohash(
|
||||
hashToLoad: Set<String>?,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? {
|
||||
if (hashToLoad == null || hashToLoad.isEmpty()) return null
|
||||
|
||||
val geoHashes = hashToLoad.toList()
|
||||
|
||||
return listOf(
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.GLOBAL),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds =
|
||||
listOf(
|
||||
LiveActivitiesChatMessageEvent.KIND,
|
||||
LiveActivitiesEvent.KIND,
|
||||
),
|
||||
tags = mapOf("g" to geoHashes),
|
||||
limit = 300,
|
||||
since = since,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.discover.nip53LiveActivities.subassemblies
|
||||
|
||||
import com.vitorpamplona.ammolite.relays.FeedType
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hashtagAlts
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
|
||||
|
||||
fun filterLiveActivitiesByHashtag(
|
||||
hashToLoad: Set<String>?,
|
||||
since: Map<String, EOSETime>?,
|
||||
): List<TypedFilter>? {
|
||||
if (hashToLoad == null || hashToLoad.isEmpty()) return null
|
||||
|
||||
val hashtags = hashtagAlts(hashToLoad).toList()
|
||||
|
||||
return listOf(
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.GLOBAL),
|
||||
filter =
|
||||
SincePerRelayFilter(
|
||||
kinds =
|
||||
listOf(
|
||||
LiveActivitiesChatMessageEvent.KIND,
|
||||
LiveActivitiesEvent.KIND,
|
||||
),
|
||||
tags = mapOf("t" to hashtags),
|
||||
limit = 300,
|
||||
since = since,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user