merge with recent changes to main
This commit is contained in:
+2
-2
@@ -12,8 +12,8 @@ android {
|
|||||||
applicationId "com.vitorpamplona.amethyst"
|
applicationId "com.vitorpamplona.amethyst"
|
||||||
minSdk 26
|
minSdk 26
|
||||||
targetSdk 33
|
targetSdk 33
|
||||||
versionCode 109
|
versionCode 113
|
||||||
versionName "0.30.2"
|
versionName "0.31.3"
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables {
|
vectorDrawables {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.vitorpamplona.amethyst.model
|
|||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import com.vitorpamplona.amethyst.service.NostrSingleEventDataSource
|
import com.vitorpamplona.amethyst.service.NostrSingleEventDataSource
|
||||||
import com.vitorpamplona.amethyst.service.model.*
|
import com.vitorpamplona.amethyst.service.model.*
|
||||||
|
import com.vitorpamplona.amethyst.service.relays.EOSETime
|
||||||
import com.vitorpamplona.amethyst.service.relays.Relay
|
import com.vitorpamplona.amethyst.service.relays.Relay
|
||||||
import com.vitorpamplona.amethyst.ui.components.BundledUpdate
|
import com.vitorpamplona.amethyst.ui.components.BundledUpdate
|
||||||
import com.vitorpamplona.amethyst.ui.note.toShortenHex
|
import com.vitorpamplona.amethyst.ui.note.toShortenHex
|
||||||
@@ -46,7 +47,7 @@ open class Note(val idHex: String) {
|
|||||||
var relays = setOf<String>()
|
var relays = setOf<String>()
|
||||||
private set
|
private set
|
||||||
|
|
||||||
var lastReactionsDownloadTime: Map<String, Long> = emptyMap()
|
var lastReactionsDownloadTime: Map<String, EOSETime> = emptyMap()
|
||||||
|
|
||||||
fun id() = Hex.decode(idHex)
|
fun id() = Hex.decode(idHex)
|
||||||
open fun idNote() = id().toNote()
|
open fun idNote() = id().toNote()
|
||||||
|
|||||||
@@ -53,6 +53,11 @@ class ThreadAssembler {
|
|||||||
val threadRoot = searchRoot(note, thread) ?: note
|
val threadRoot = searchRoot(note, thread) ?: note
|
||||||
|
|
||||||
loadDown(threadRoot, thread)
|
loadDown(threadRoot, thread)
|
||||||
|
// adds the replies of the note in case the search for Root
|
||||||
|
// did not added them.
|
||||||
|
note.replies.forEach {
|
||||||
|
loadDown(it, thread)
|
||||||
|
}
|
||||||
|
|
||||||
thread.toSet()
|
thread.toSet()
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -3,10 +3,6 @@ package com.vitorpamplona.amethyst.model
|
|||||||
import com.baha.url.preview.BahaUrlPreview
|
import com.baha.url.preview.BahaUrlPreview
|
||||||
import com.baha.url.preview.IUrlPreviewCallback
|
import com.baha.url.preview.IUrlPreviewCallback
|
||||||
import com.baha.url.preview.UrlInfoItem
|
import com.baha.url.preview.UrlInfoItem
|
||||||
import com.vitorpamplona.amethyst.ui.components.imageExtension
|
|
||||||
import com.vitorpamplona.amethyst.ui.components.isValidURL
|
|
||||||
import com.vitorpamplona.amethyst.ui.components.noProtocolUrlValidator
|
|
||||||
import com.vitorpamplona.amethyst.ui.components.videoExtension
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
@@ -47,29 +43,4 @@ object UrlCachedPreviewer {
|
|||||||
).fetchUrlPreview()
|
).fetchUrlPreview()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun findUrlsInMessage(message: String): List<String> {
|
|
||||||
return message.split('\n').map { paragraph ->
|
|
||||||
paragraph.split(' ').filter { word: String ->
|
|
||||||
isValidURL(word) || noProtocolUrlValidator.matcher(word).matches()
|
|
||||||
}
|
|
||||||
}.flatten()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun preloadPreviewsFor(note: Note) {
|
|
||||||
note.event?.content()?.let {
|
|
||||||
findUrlsInMessage(it).forEach {
|
|
||||||
val removedParamsFromUrl = it.split("?")[0].lowercase()
|
|
||||||
if (imageExtension.matcher(removedParamsFromUrl).matches()) {
|
|
||||||
// Preload Images? Isn't this too heavy?
|
|
||||||
} else if (videoExtension.matcher(removedParamsFromUrl).matches()) {
|
|
||||||
// Do nothing for now.
|
|
||||||
} else if (isValidURL(removedParamsFromUrl)) {
|
|
||||||
previewInfo(it)
|
|
||||||
} else {
|
|
||||||
previewInfo("https://$it")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import com.vitorpamplona.amethyst.service.model.ContactListEvent
|
|||||||
import com.vitorpamplona.amethyst.service.model.LnZapEvent
|
import com.vitorpamplona.amethyst.service.model.LnZapEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.MetadataEvent
|
import com.vitorpamplona.amethyst.service.model.MetadataEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.ReportEvent
|
import com.vitorpamplona.amethyst.service.model.ReportEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.relays.EOSETime
|
||||||
import com.vitorpamplona.amethyst.service.relays.Relay
|
import com.vitorpamplona.amethyst.service.relays.Relay
|
||||||
import com.vitorpamplona.amethyst.ui.components.BundledUpdate
|
import com.vitorpamplona.amethyst.ui.components.BundledUpdate
|
||||||
import com.vitorpamplona.amethyst.ui.note.toShortenHex
|
import com.vitorpamplona.amethyst.ui.note.toShortenHex
|
||||||
@@ -31,7 +32,7 @@ class User(val pubkeyHex: String) {
|
|||||||
var reports = mapOf<User, Set<Note>>()
|
var reports = mapOf<User, Set<Note>>()
|
||||||
private set
|
private set
|
||||||
|
|
||||||
var latestEOSEs: Map<String, Long> = emptyMap()
|
var latestEOSEs: Map<String, EOSETime> = emptyMap()
|
||||||
|
|
||||||
var zaps = mapOf<Note, Note?>()
|
var zaps = mapOf<Note, Note?>()
|
||||||
private set
|
private set
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import com.vitorpamplona.amethyst.service.model.ReactionEvent
|
|||||||
import com.vitorpamplona.amethyst.service.model.ReportEvent
|
import com.vitorpamplona.amethyst.service.model.ReportEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.RepostEvent
|
import com.vitorpamplona.amethyst.service.model.RepostEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.relays.EOSEAccount
|
||||||
import com.vitorpamplona.amethyst.service.relays.FeedType
|
import com.vitorpamplona.amethyst.service.relays.FeedType
|
||||||
import com.vitorpamplona.amethyst.service.relays.JsonFilter
|
import com.vitorpamplona.amethyst.service.relays.JsonFilter
|
||||||
import com.vitorpamplona.amethyst.service.relays.TypedFilter
|
import com.vitorpamplona.amethyst.service.relays.TypedFilter
|
||||||
@@ -20,6 +21,8 @@ import com.vitorpamplona.amethyst.service.relays.TypedFilter
|
|||||||
object NostrAccountDataSource : NostrDataSource("AccountData") {
|
object NostrAccountDataSource : NostrDataSource("AccountData") {
|
||||||
lateinit var account: Account
|
lateinit var account: Account
|
||||||
|
|
||||||
|
val latestEOSEs = EOSEAccount()
|
||||||
|
|
||||||
fun createAccountContactListFilter(): TypedFilter {
|
fun createAccountContactListFilter(): TypedFilter {
|
||||||
return TypedFilter(
|
return TypedFilter(
|
||||||
types = FeedType.values().toSet(),
|
types = FeedType.values().toSet(),
|
||||||
@@ -69,7 +72,8 @@ object NostrAccountDataSource : NostrDataSource("AccountData") {
|
|||||||
types = FeedType.values().toSet(),
|
types = FeedType.values().toSet(),
|
||||||
filter = JsonFilter(
|
filter = JsonFilter(
|
||||||
kinds = listOf(ReportEvent.kind),
|
kinds = listOf(ReportEvent.kind),
|
||||||
authors = listOf(account.userProfile().pubkeyHex)
|
authors = listOf(account.userProfile().pubkeyHex),
|
||||||
|
since = latestEOSEs.users[account.userProfile()]?.relayList
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -88,11 +92,14 @@ object NostrAccountDataSource : NostrDataSource("AccountData") {
|
|||||||
BadgeAwardEvent.kind
|
BadgeAwardEvent.kind
|
||||||
),
|
),
|
||||||
tags = mapOf("p" to listOf(account.userProfile().pubkeyHex)),
|
tags = mapOf("p" to listOf(account.userProfile().pubkeyHex)),
|
||||||
limit = 200
|
limit = 400,
|
||||||
|
since = latestEOSEs.users[account.userProfile()]?.relayList
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
val accountChannel = requestNewChannel()
|
val accountChannel = requestNewChannel { time, relayUrl ->
|
||||||
|
latestEOSEs.addOrUpdate(account.userProfile(), relayUrl, time)
|
||||||
|
}
|
||||||
|
|
||||||
override fun updateChannelFilters() {
|
override fun updateChannelFilters() {
|
||||||
// gets everthing about the user logged in
|
// gets everthing about the user logged in
|
||||||
|
|||||||
+15
-5
@@ -5,6 +5,7 @@ import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
|
|||||||
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent
|
import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
|
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.relays.EOSEAccount
|
||||||
import com.vitorpamplona.amethyst.service.relays.FeedType
|
import com.vitorpamplona.amethyst.service.relays.FeedType
|
||||||
import com.vitorpamplona.amethyst.service.relays.JsonFilter
|
import com.vitorpamplona.amethyst.service.relays.JsonFilter
|
||||||
import com.vitorpamplona.amethyst.service.relays.TypedFilter
|
import com.vitorpamplona.amethyst.service.relays.TypedFilter
|
||||||
@@ -12,11 +13,14 @@ import com.vitorpamplona.amethyst.service.relays.TypedFilter
|
|||||||
object NostrChatroomListDataSource : NostrDataSource("MailBoxFeed") {
|
object NostrChatroomListDataSource : NostrDataSource("MailBoxFeed") {
|
||||||
lateinit var account: Account
|
lateinit var account: Account
|
||||||
|
|
||||||
|
val latestEOSEs = EOSEAccount()
|
||||||
|
|
||||||
fun createMessagesToMeFilter() = TypedFilter(
|
fun createMessagesToMeFilter() = TypedFilter(
|
||||||
types = setOf(FeedType.PRIVATE_DMS),
|
types = setOf(FeedType.PRIVATE_DMS),
|
||||||
filter = JsonFilter(
|
filter = JsonFilter(
|
||||||
kinds = listOf(PrivateDmEvent.kind),
|
kinds = listOf(PrivateDmEvent.kind),
|
||||||
tags = mapOf("p" to listOf(account.userProfile().pubkeyHex))
|
tags = mapOf("p" to listOf(account.userProfile().pubkeyHex)),
|
||||||
|
since = latestEOSEs.users[account.userProfile()]?.relayList
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -24,7 +28,8 @@ object NostrChatroomListDataSource : NostrDataSource("MailBoxFeed") {
|
|||||||
types = setOf(FeedType.PRIVATE_DMS),
|
types = setOf(FeedType.PRIVATE_DMS),
|
||||||
filter = JsonFilter(
|
filter = JsonFilter(
|
||||||
kinds = listOf(PrivateDmEvent.kind),
|
kinds = listOf(PrivateDmEvent.kind),
|
||||||
authors = listOf(account.userProfile().pubkeyHex)
|
authors = listOf(account.userProfile().pubkeyHex),
|
||||||
|
since = latestEOSEs.users[account.userProfile()]?.relayList
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -32,7 +37,8 @@ object NostrChatroomListDataSource : NostrDataSource("MailBoxFeed") {
|
|||||||
types = setOf(FeedType.PUBLIC_CHATS),
|
types = setOf(FeedType.PUBLIC_CHATS),
|
||||||
filter = JsonFilter(
|
filter = JsonFilter(
|
||||||
kinds = listOf(ChannelCreateEvent.kind, ChannelMetadataEvent.kind),
|
kinds = listOf(ChannelCreateEvent.kind, ChannelMetadataEvent.kind),
|
||||||
authors = listOf(account.userProfile().pubkeyHex)
|
authors = listOf(account.userProfile().pubkeyHex),
|
||||||
|
since = latestEOSEs.users[account.userProfile()]?.relayList
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -40,7 +46,8 @@ object NostrChatroomListDataSource : NostrDataSource("MailBoxFeed") {
|
|||||||
types = FeedType.values().toSet(), // Metadata comes from any relay
|
types = FeedType.values().toSet(), // Metadata comes from any relay
|
||||||
filter = JsonFilter(
|
filter = JsonFilter(
|
||||||
kinds = listOf(ChannelCreateEvent.kind),
|
kinds = listOf(ChannelCreateEvent.kind),
|
||||||
ids = account.followingChannels.toList()
|
ids = account.followingChannels.toList(),
|
||||||
|
since = latestEOSEs.users[account.userProfile()]?.relayList
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -64,13 +71,16 @@ object NostrChatroomListDataSource : NostrDataSource("MailBoxFeed") {
|
|||||||
filter = JsonFilter(
|
filter = JsonFilter(
|
||||||
kinds = listOf(ChannelMessageEvent.kind),
|
kinds = listOf(ChannelMessageEvent.kind),
|
||||||
tags = mapOf("e" to listOf(it)),
|
tags = mapOf("e" to listOf(it)),
|
||||||
|
since = latestEOSEs.users[account.userProfile()]?.relayList,
|
||||||
limit = 25 // Remember to consider spam that is being removed from the UI
|
limit = 25 // Remember to consider spam that is being removed from the UI
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val chatroomListChannel = requestNewChannel()
|
val chatroomListChannel = requestNewChannel { time, relayUrl ->
|
||||||
|
latestEOSEs.addOrUpdate(account.userProfile(), relayUrl, time)
|
||||||
|
}
|
||||||
|
|
||||||
override fun updateChannelFilters() {
|
override fun updateChannelFilters() {
|
||||||
val list = listOf(
|
val list = listOf(
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import com.vitorpamplona.amethyst.model.UserState
|
|||||||
import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent
|
import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.PollNoteEvent
|
import com.vitorpamplona.amethyst.service.model.PollNoteEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.relays.EOSEAccount
|
||||||
import com.vitorpamplona.amethyst.service.relays.FeedType
|
import com.vitorpamplona.amethyst.service.relays.FeedType
|
||||||
import com.vitorpamplona.amethyst.service.relays.JsonFilter
|
import com.vitorpamplona.amethyst.service.relays.JsonFilter
|
||||||
import com.vitorpamplona.amethyst.service.relays.TypedFilter
|
import com.vitorpamplona.amethyst.service.relays.TypedFilter
|
||||||
@@ -16,6 +17,8 @@ import kotlinx.coroutines.launch
|
|||||||
object NostrHomeDataSource : NostrDataSource("HomeFeed") {
|
object NostrHomeDataSource : NostrDataSource("HomeFeed") {
|
||||||
lateinit var account: Account
|
lateinit var account: Account
|
||||||
|
|
||||||
|
val latestEOSEs = EOSEAccount()
|
||||||
|
|
||||||
private val cacheListener: (UserState) -> Unit = {
|
private val cacheListener: (UserState) -> Unit = {
|
||||||
invalidateFilters()
|
invalidateFilters()
|
||||||
}
|
}
|
||||||
@@ -54,7 +57,8 @@ object NostrHomeDataSource : NostrDataSource("HomeFeed") {
|
|||||||
filter = JsonFilter(
|
filter = JsonFilter(
|
||||||
kinds = listOf(TextNoteEvent.kind, LongTextNoteEvent.kind, PollNoteEvent.kind),
|
kinds = listOf(TextNoteEvent.kind, LongTextNoteEvent.kind, PollNoteEvent.kind),
|
||||||
authors = followSet,
|
authors = followSet,
|
||||||
limit = 400
|
limit = 400,
|
||||||
|
since = latestEOSEs.users[account.userProfile()]?.relayList
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -73,12 +77,15 @@ object NostrHomeDataSource : NostrDataSource("HomeFeed") {
|
|||||||
listOf(it, it.lowercase(), it.uppercase(), it.capitalize())
|
listOf(it, it.lowercase(), it.uppercase(), it.capitalize())
|
||||||
}.flatten()
|
}.flatten()
|
||||||
),
|
),
|
||||||
limit = 100
|
limit = 100,
|
||||||
|
since = latestEOSEs.users[account.userProfile()]?.relayList
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val followAccountChannel = requestNewChannel()
|
val followAccountChannel = requestNewChannel { time, relayUrl ->
|
||||||
|
latestEOSEs.addOrUpdate(account.userProfile(), relayUrl, time)
|
||||||
|
}
|
||||||
|
|
||||||
override fun updateChannelFilters() {
|
override fun updateChannelFilters() {
|
||||||
followAccountChannel.typedFilters = listOfNotNull(createFollowAccountsFilter(), createFollowTagsFilter()).ifEmpty { null }
|
followAccountChannel.typedFilters = listOfNotNull(createFollowAccountsFilter(), createFollowTagsFilter()).ifEmpty { null }
|
||||||
|
|||||||
@@ -2,7 +2,20 @@ package com.vitorpamplona.amethyst.service
|
|||||||
|
|
||||||
import com.vitorpamplona.amethyst.model.AddressableNote
|
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.service.model.*
|
import com.vitorpamplona.amethyst.service.model.BadgeAwardEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.model.BadgeDefinitionEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.model.BadgeProfilesEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.model.LnZapEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.model.LnZapRequestEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.model.ReactionEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.model.ReportEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.model.RepostEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.relays.EOSETime
|
||||||
import com.vitorpamplona.amethyst.service.relays.FeedType
|
import com.vitorpamplona.amethyst.service.relays.FeedType
|
||||||
import com.vitorpamplona.amethyst.service.relays.JsonFilter
|
import com.vitorpamplona.amethyst.service.relays.JsonFilter
|
||||||
import com.vitorpamplona.amethyst.service.relays.TypedFilter
|
import com.vitorpamplona.amethyst.service.relays.TypedFilter
|
||||||
@@ -127,8 +140,14 @@ object NostrSingleEventDataSource : NostrDataSource("SingleEventFeed") {
|
|||||||
|
|
||||||
val singleEventChannel = requestNewChannel { time, relayUrl ->
|
val singleEventChannel = requestNewChannel { time, relayUrl ->
|
||||||
eventsToWatch.forEach {
|
eventsToWatch.forEach {
|
||||||
it.lastReactionsDownloadTime = it.lastReactionsDownloadTime + Pair(relayUrl, time)
|
val eose = it.lastReactionsDownloadTime[relayUrl]
|
||||||
|
if (eose == null) {
|
||||||
|
it.lastReactionsDownloadTime = it.lastReactionsDownloadTime + Pair(relayUrl, EOSETime(time))
|
||||||
|
} else {
|
||||||
|
eose.time = time
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Many relays operate with limits in the amount of filters.
|
// Many relays operate with limits in the amount of filters.
|
||||||
// As information comes, the filters will be rotated to get more data.
|
// As information comes, the filters will be rotated to get more data.
|
||||||
invalidateFilters()
|
invalidateFilters()
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.vitorpamplona.amethyst.service
|
|||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.service.model.MetadataEvent
|
import com.vitorpamplona.amethyst.service.model.MetadataEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.ReportEvent
|
import com.vitorpamplona.amethyst.service.model.ReportEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.relays.EOSETime
|
||||||
import com.vitorpamplona.amethyst.service.relays.FeedType
|
import com.vitorpamplona.amethyst.service.relays.FeedType
|
||||||
import com.vitorpamplona.amethyst.service.relays.JsonFilter
|
import com.vitorpamplona.amethyst.service.relays.JsonFilter
|
||||||
import com.vitorpamplona.amethyst.service.relays.TypedFilter
|
import com.vitorpamplona.amethyst.service.relays.TypedFilter
|
||||||
@@ -42,8 +43,14 @@ object NostrSingleUserDataSource : NostrDataSource("SingleUserFeed") {
|
|||||||
|
|
||||||
val userChannel = requestNewChannel() { time, relayUrl ->
|
val userChannel = requestNewChannel() { time, relayUrl ->
|
||||||
usersToWatch.forEach {
|
usersToWatch.forEach {
|
||||||
it.latestEOSEs = it.latestEOSEs + Pair(relayUrl, time)
|
val eose = it.latestEOSEs[relayUrl]
|
||||||
|
if (eose == null) {
|
||||||
|
it.latestEOSEs = it.latestEOSEs + Pair(relayUrl, EOSETime(time))
|
||||||
|
} else {
|
||||||
|
eose.time = time
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Many relays operate with limits in the amount of filters.
|
// Many relays operate with limits in the amount of filters.
|
||||||
// As information comes, the filters will be rotated to get more data.
|
// As information comes, the filters will be rotated to get more data.
|
||||||
invalidateFilters()
|
invalidateFilters()
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.vitorpamplona.amethyst.service.relays
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.model.User
|
||||||
|
|
||||||
|
class EOSETime(var time: Long)
|
||||||
|
|
||||||
|
class EOSERelayList(var relayList: Map<String, EOSETime> = emptyMap()) {
|
||||||
|
fun addOrUpdate(relayUrl: String, time: Long) {
|
||||||
|
val eose = relayList[relayUrl]
|
||||||
|
if (eose == null) {
|
||||||
|
relayList = relayList + Pair(relayUrl, EOSETime(time))
|
||||||
|
} else {
|
||||||
|
eose.time = time
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EOSEAccount(var users: Map<User, EOSERelayList> = emptyMap()) {
|
||||||
|
fun addOrUpdate(user: User, relayUrl: String, time: Long) {
|
||||||
|
val relayList = users[user]
|
||||||
|
if (relayList == null) {
|
||||||
|
users = users + mapOf(user to EOSERelayList(mapOf(relayUrl to EOSETime(time))))
|
||||||
|
} else {
|
||||||
|
relayList.addOrUpdate(relayUrl, time)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,14 +4,13 @@ import com.google.gson.Gson
|
|||||||
import com.google.gson.GsonBuilder
|
import com.google.gson.GsonBuilder
|
||||||
import com.google.gson.JsonArray
|
import com.google.gson.JsonArray
|
||||||
import com.google.gson.JsonObject
|
import com.google.gson.JsonObject
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class JsonFilter(
|
class JsonFilter(
|
||||||
val ids: List<String>? = null,
|
val ids: List<String>? = null,
|
||||||
val authors: List<String>? = null,
|
val authors: List<String>? = null,
|
||||||
val kinds: List<Int>? = null,
|
val kinds: List<Int>? = null,
|
||||||
val tags: Map<String, List<String>>? = null,
|
val tags: Map<String, List<String>>? = null,
|
||||||
val since: Map<String, Long>? = null,
|
val since: Map<String, EOSETime>? = null,
|
||||||
val until: Long? = null,
|
val until: Long? = null,
|
||||||
val limit: Int? = null,
|
val limit: Int? = null,
|
||||||
val search: String? = null
|
val search: String? = null
|
||||||
@@ -37,7 +36,7 @@ class JsonFilter(
|
|||||||
if (forRelay != null) {
|
if (forRelay != null) {
|
||||||
val relaySince = get(forRelay)
|
val relaySince = get(forRelay)
|
||||||
if (relaySince != null) {
|
if (relaySince != null) {
|
||||||
jsonObject.addProperty("since", relaySince)
|
jsonObject.addProperty("since", relaySince.time)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val jsonObjectSince = JsonObject()
|
val jsonObjectSince = JsonObject()
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ class Relay(
|
|||||||
|
|
||||||
var closingTime = 0L
|
var closingTime = 0L
|
||||||
|
|
||||||
|
var afterEOSE = false
|
||||||
|
|
||||||
fun register(listener: Listener) {
|
fun register(listener: Listener) {
|
||||||
listeners = listeners.plus(listener)
|
listeners = listeners.plus(listener)
|
||||||
}
|
}
|
||||||
@@ -74,6 +76,7 @@ class Relay(
|
|||||||
val listener = object : WebSocketListener() {
|
val listener = object : WebSocketListener() {
|
||||||
|
|
||||||
override fun onOpen(webSocket: WebSocket, response: Response) {
|
override fun onOpen(webSocket: WebSocket, response: Response) {
|
||||||
|
afterEOSE = false
|
||||||
isReady = true
|
isReady = true
|
||||||
ping = response.receivedResponseAtMillis - response.sentRequestAtMillis
|
ping = response.receivedResponseAtMillis - response.sentRequestAtMillis
|
||||||
// Log.w("Relay", "Relay OnOpen, Loading All subscriptions $url")
|
// Log.w("Relay", "Relay OnOpen, Loading All subscriptions $url")
|
||||||
@@ -89,12 +92,19 @@ class Relay(
|
|||||||
val msg = Event.gson.fromJson(text, JsonElement::class.java).asJsonArray
|
val msg = Event.gson.fromJson(text, JsonElement::class.java).asJsonArray
|
||||||
val type = msg[0].asString
|
val type = msg[0].asString
|
||||||
val channel = msg[1].asString
|
val channel = msg[1].asString
|
||||||
|
|
||||||
when (type) {
|
when (type) {
|
||||||
"EVENT" -> {
|
"EVENT" -> {
|
||||||
// Log.w("Relay", "Relay onEVENT $url, $channel")
|
// Log.w("Relay", "Relay onEVENT $url, $channel")
|
||||||
listeners.forEach { it.onEvent(this@Relay, channel, Event.fromJson(msg[2], Client.lenient)) }
|
listeners.forEach {
|
||||||
|
it.onEvent(this@Relay, channel, Event.fromJson(msg[2], Client.lenient))
|
||||||
|
if (afterEOSE) {
|
||||||
|
it.onRelayStateChange(this@Relay, Type.EOSE, channel)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
"EOSE" -> listeners.forEach {
|
"EOSE" -> listeners.forEach {
|
||||||
|
afterEOSE = true
|
||||||
// Log.w("Relay", "Relay onEOSE $url, $channel")
|
// Log.w("Relay", "Relay onEOSE $url, $channel")
|
||||||
it.onRelayStateChange(this@Relay, Type.EOSE, channel)
|
it.onRelayStateChange(this@Relay, Type.EOSE, channel)
|
||||||
}
|
}
|
||||||
@@ -136,6 +146,7 @@ class Relay(
|
|||||||
override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
|
override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
|
||||||
socket = null
|
socket = null
|
||||||
isReady = false
|
isReady = false
|
||||||
|
afterEOSE = false
|
||||||
closingTime = Date().time / 1000
|
closingTime = Date().time / 1000
|
||||||
listeners.forEach { it.onRelayStateChange(this@Relay, Type.DISCONNECT, null) }
|
listeners.forEach { it.onRelayStateChange(this@Relay, Type.DISCONNECT, null) }
|
||||||
}
|
}
|
||||||
@@ -147,6 +158,7 @@ class Relay(
|
|||||||
// Failures disconnect the relay.
|
// Failures disconnect the relay.
|
||||||
socket = null
|
socket = null
|
||||||
isReady = false
|
isReady = false
|
||||||
|
afterEOSE = false
|
||||||
closingTime = Date().time / 1000
|
closingTime = Date().time / 1000
|
||||||
|
|
||||||
Log.w("Relay", "Relay onFailure $url, ${response?.message} $response")
|
Log.w("Relay", "Relay onFailure $url, ${response?.message} $response")
|
||||||
@@ -161,6 +173,7 @@ class Relay(
|
|||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
errorCounter++
|
errorCounter++
|
||||||
isReady = false
|
isReady = false
|
||||||
|
afterEOSE = false
|
||||||
closingTime = Date().time / 1000
|
closingTime = Date().time / 1000
|
||||||
Log.e("Relay", "Relay Invalid $url")
|
Log.e("Relay", "Relay Invalid $url")
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
@@ -173,6 +186,7 @@ class Relay(
|
|||||||
socket?.close(1000, "Normal close")
|
socket?.close(1000, "Normal close")
|
||||||
socket = null
|
socket = null
|
||||||
isReady = false
|
isReady = false
|
||||||
|
afterEOSE = false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendFilter(requestId: String) {
|
fun sendFilter(requestId: String) {
|
||||||
@@ -186,6 +200,7 @@ class Relay(
|
|||||||
// println("FILTERSSENT $url $request")
|
// println("FILTERSSENT $url $request")
|
||||||
socket?.send(request)
|
socket?.send(request)
|
||||||
eventUploadCounterInBytes += request.bytesUsedInMemory()
|
eventUploadCounterInBytes += request.bytesUsedInMemory()
|
||||||
|
afterEOSE = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -138,5 +138,5 @@ object ImageSaver {
|
|||||||
MediaScannerConnection.scanFile(context, arrayOf(outputFile.toString()), null, null)
|
MediaScannerConnection.scanFile(context, arrayOf(outputFile.toString()), null, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
private const val PICTURES_SUBDIRECTORY = "Amethyst " + BuildConfig.VERSION_NAME
|
private const val PICTURES_SUBDIRECTORY = "Amethyst"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n
|
|||||||
if (isValidURL(myUrlPreview)) {
|
if (isValidURL(myUrlPreview)) {
|
||||||
val removedParamsFromUrl =
|
val removedParamsFromUrl =
|
||||||
myUrlPreview.split("?")[0].lowercase()
|
myUrlPreview.split("?")[0].lowercase()
|
||||||
if (imageExtension.matcher(removedParamsFromUrl).matches()) {
|
if (imageExtensions.any { removedParamsFromUrl.endsWith(it, true) }) {
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
model = myUrlPreview,
|
model = myUrlPreview,
|
||||||
contentDescription = myUrlPreview,
|
contentDescription = myUrlPreview,
|
||||||
@@ -182,9 +182,7 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n
|
|||||||
RoundedCornerShape(15.dp)
|
RoundedCornerShape(15.dp)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
} else if (videoExtension.matcher(removedParamsFromUrl)
|
} else if (videoExtensions.any { removedParamsFromUrl.endsWith(it, true) }) {
|
||||||
.matches()
|
|
||||||
) {
|
|
||||||
VideoView(myUrlPreview)
|
VideoView(myUrlPreview)
|
||||||
} else {
|
} else {
|
||||||
UrlPreview(myUrlPreview, myUrlPreview)
|
UrlPreview(myUrlPreview, myUrlPreview)
|
||||||
|
|||||||
@@ -46,8 +46,8 @@ import java.net.URISyntaxException
|
|||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
val imageExtension = Pattern.compile("(.*/)*.+\\.(png|jpg|gif|bmp|jpeg|webp|svg)$", Pattern.CASE_INSENSITIVE)
|
val imageExtensions = listOf("png", "jpg", "gif", "bmp", "jpeg", "webp", "svg")
|
||||||
val videoExtension = Pattern.compile("(.*/)*.+\\.(mp4|avi|wmv|mpg|amv|webm|mov)$", Pattern.CASE_INSENSITIVE)
|
val videoExtensions = listOf("mp4", "avi", "wmv", "mpg", "amv", "webm", "mov")
|
||||||
|
|
||||||
// Group 1 = url, group 4 additional chars
|
// Group 1 = url, group 4 additional chars
|
||||||
val noProtocolUrlValidator = Pattern.compile("(([\\w\\d-]+\\.)*[a-zA-Z][\\w-]+[\\.\\:]\\w+([\\/\\?\\=\\&\\#\\.]?[\\w-]+)*\\/?)(.*)")
|
val noProtocolUrlValidator = Pattern.compile("(([\\w\\d-]+\\.)*[a-zA-Z][\\w-]+[\\.\\:]\\w+([\\/\\?\\=\\&\\#\\.]?[\\w-]+)*\\/?)(.*)")
|
||||||
@@ -138,10 +138,10 @@ fun RichTextViewer(
|
|||||||
// sequence of images will render in a slideview
|
// sequence of images will render in a slideview
|
||||||
if (isValidURL(word)) {
|
if (isValidURL(word)) {
|
||||||
val removedParamsFromUrl = word.split("?")[0].lowercase()
|
val removedParamsFromUrl = word.split("?")[0].lowercase()
|
||||||
if (imageExtension.matcher(removedParamsFromUrl).matches()) {
|
if (imageExtensions.any { word.endsWith(it, true) }) {
|
||||||
imagesForPager.add(word)
|
imagesForPager.add(word)
|
||||||
}
|
}
|
||||||
if (videoExtension.matcher(removedParamsFromUrl).matches()) {
|
if (videoExtensions.any { word.endsWith(it, true) }) {
|
||||||
imagesForPager.add(word)
|
imagesForPager.add(word)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -160,9 +160,9 @@ fun RichTextViewer(
|
|||||||
|
|
||||||
if (isValidURL(word)) {
|
if (isValidURL(word)) {
|
||||||
val removedParamsFromUrl = word.split("?")[0].lowercase()
|
val removedParamsFromUrl = word.split("?")[0].lowercase()
|
||||||
if (imageExtension.matcher(removedParamsFromUrl).matches()) {
|
if (imageExtensions.any { word.endsWith(it, true) }) {
|
||||||
ZoomableImageView(word, imagesForPager)
|
ZoomableImageView(word, imagesForPager)
|
||||||
} else if (videoExtension.matcher(removedParamsFromUrl).matches()) {
|
} else if (videoExtensions.any { word.endsWith(it, true) }) {
|
||||||
ZoomableImageView(word, imagesForPager)
|
ZoomableImageView(word, imagesForPager)
|
||||||
} else {
|
} else {
|
||||||
UrlPreview(word, "$word ")
|
UrlPreview(word, "$word ")
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ fun ZoomableImageView(word: String, images: List<String> = listOf(word)) {
|
|||||||
mutableStateOf<AsyncImagePainter.State?>(null)
|
mutableStateOf<AsyncImagePainter.State?>(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (imageExtension.matcher(word).matches()) {
|
if (imageExtensions.any { word.endsWith(it, true) }) {
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
model = word,
|
model = word,
|
||||||
contentDescription = word,
|
contentDescription = word,
|
||||||
@@ -171,7 +171,7 @@ fun ZoomableImageDialog(imageUrl: String, allImages: List<String> = listOf(image
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun RenderImageOrVideo(imageUrl: String) {
|
private fun RenderImageOrVideo(imageUrl: String) {
|
||||||
if (imageExtension.matcher(imageUrl).matches()) {
|
if (imageExtensions.any { imageUrl.endsWith(it, true) }) {
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
model = imageUrl,
|
model = imageUrl,
|
||||||
contentDescription = stringResource(id = R.string.profile_image),
|
contentDescription = stringResource(id = R.string.profile_image),
|
||||||
|
|||||||
@@ -23,7 +23,9 @@ object NotificationFeedFilter : FeedFilter<Note>() {
|
|||||||
}
|
}
|
||||||
.filter { it ->
|
.filter { it ->
|
||||||
it.event !is TextNoteEvent ||
|
it.event !is TextNoteEvent ||
|
||||||
it.replyTo?.any { it.author == loggedInUser } == true ||
|
(it.event as? TextNoteEvent)?.taggedEvents()?.any {
|
||||||
|
LocalCache.checkGetOrCreateNote(it)?.author == loggedInUser
|
||||||
|
} == true ||
|
||||||
loggedInUser in it.directlyCiteUsers()
|
loggedInUser in it.directlyCiteUsers()
|
||||||
}
|
}
|
||||||
.filter { it ->
|
.filter { it ->
|
||||||
|
|||||||
Reference in New Issue
Block a user