Massive refactoring of Quartz to:

- Define each tag in their own class.
- Allow extension functions to additional responsibilities to other classes
- Migrate from hardcoded tag filters in events to the Tag's parser and assemble functions.
- Migrate hardcoded event.create to builders that use extension functions
- Restructures threading infrastructure for NIP-10
- Decouple the event signing from the Event building functions via event templates
- Create classes to represent Tags and TagArrays and use extension functions to add domain-related methods to the tag array of each nip.
- Uses external functions on event template builders to better point to which functions and which tags can be used in which event kinds.
- Separates Event kinds in packages inside each nip.
- Improves support for NIP-89
- Correctly establishes which imeta params can be used in each nip (video, picture, files)
- Decouples the iMeta builder from any nip.
- Fixes mute list word and user removal when inserted from a different client.
- Migrates the Account class to avoiding having to build each Event inside of it
This commit is contained in:
Vitor Pamplona
2025-02-13 17:39:13 -05:00
parent 0f3b3c4b2d
commit 9c21a8a7da
614 changed files with 15380 additions and 7132 deletions
@@ -35,7 +35,7 @@ import com.vitorpamplona.amethyst.ui.actions.mediaServers.DEFAULT_MEDIA_SERVERS
import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerName
import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerType
import com.vitorpamplona.quartz.CryptoUtils
import com.vitorpamplona.quartz.nip01Core.KeyPair
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
import com.vitorpamplona.quartz.nip01Core.toHexKey
import junit.framework.TestCase.assertEquals
import junit.framework.TestCase.fail
@@ -23,10 +23,11 @@ package com.vitorpamplona.amethyst
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.vitorpamplona.amethyst.service.ots.OkHttpBlockstreamExplorer
import com.vitorpamplona.amethyst.service.ots.OkHttpCalendarBuilder
import com.vitorpamplona.quartz.nip01Core.KeyPair
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
import com.vitorpamplona.quartz.nip01Core.jackson.EventMapper
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
import com.vitorpamplona.quartz.nip03Timestamp.OtsEvent
import com.vitorpamplona.quartz.nip03Timestamp.OtsResolver
import com.vitorpamplona.quartz.nip03Timestamp.ots.OpenTimestamps
import junit.framework.TestCase.assertEquals
import org.junit.Assert
@@ -46,27 +47,27 @@ class OkHttpOtsTest {
@Before
fun setup() {
OtsEvent.otsInstance = OpenTimestamps(OkHttpBlockstreamExplorer(forceProxy = { false }), OkHttpCalendarBuilder(forceProxy = { false }))
OtsResolver.ots = OpenTimestamps(OkHttpBlockstreamExplorer(forceProxy = { false }), OkHttpCalendarBuilder(forceProxy = { false }))
}
@Test
fun verifyNostrEvent() {
val ots = EventMapper.fromJson(otsEvent) as OtsEvent
println(ots.info())
println(OtsResolver.info(ots.otsByteArray()))
assertEquals(1707688818L, ots.verify())
}
@Test
fun verifyNostrEvent2() {
val ots = EventMapper.fromJson(otsEvent2) as OtsEvent
println(ots.info())
println(OtsResolver.info(ots.otsByteArray()))
assertEquals(1706322179L, ots.verify())
}
@Test
fun verifyNostrPendingEvent() {
val ots = EventMapper.fromJson(otsPendingEvent) as OtsEvent
println(ots.info())
println(OtsResolver.info(ots.otsByteArray()))
assertEquals(null, ots.verify())
}
@@ -79,7 +80,7 @@ class OkHttpOtsTest {
val otsFile = OtsEvent.stamp(otsEvent2Digest)
OtsEvent.create(otsEvent2Digest, otsFile, signer) {
signer.sign(OtsEvent.build(otsEvent2Digest, otsFile)) {
ots = it
countDownLatch.countDown()
}
@@ -87,7 +88,7 @@ class OkHttpOtsTest {
Assert.assertTrue(countDownLatch.await(1, TimeUnit.SECONDS))
println(ots!!.toJson())
println(ots!!.info())
println(OtsResolver.info(ots!!.otsByteArray()))
// Should not be valid because we need to wait for confirmations
assertEquals(null, ots!!.verify())
@@ -26,11 +26,11 @@ import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.AccountSettings
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.ui.dal.ThreadFeedFilter
import com.vitorpamplona.quartz.nip01Core.KeyPair
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.hasValidSignature
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
import com.vitorpamplona.quartz.nip01Core.jackson.EventMapper
import com.vitorpamplona.quartz.nip01Core.tags.addressables.ATag
import com.vitorpamplona.quartz.nip01Core.verify
import junit.framework.TestCase
import junit.framework.TestCase.assertEquals
import kotlinx.coroutines.CoroutineScope
@@ -125,7 +125,7 @@ class ThreadAssemblerTest {
var counter = 0
eventArray.forEach {
TestCase.assertTrue("${it.id} failed signature check", it.hasValidSignature())
TestCase.assertTrue("${it.id} failed signature check", it.verify())
LocalCache.verifyAndConsume(it, null)
counter++
}
@@ -27,7 +27,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.ui.actions.buildAnnotatedStringWithUrlHighlighting
import com.vitorpamplona.quartz.nip01Core.UserMetadata
import com.vitorpamplona.quartz.nip01Core.metadata.UserMetadata
import com.vitorpamplona.quartz.nip01Core.toHexKey
import com.vitorpamplona.quartz.nip19Bech32.decodePublicKey
import org.junit.Assert.assertEquals
@@ -30,7 +30,7 @@ import com.vitorpamplona.amethyst.LocalPreferences
import com.vitorpamplona.amethyst.service.notifications.NotificationUtils.getOrCreateDMChannel
import com.vitorpamplona.amethyst.service.notifications.NotificationUtils.getOrCreateZapChannel
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip59Giftwrap.GiftWrapEvent
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -46,14 +46,14 @@ import com.vitorpamplona.amethyst.ui.tor.TorType
import com.vitorpamplona.ammolite.relays.RelaySetupInfo
import com.vitorpamplona.quartz.experimental.edits.PrivateOutboxRelayListEvent
import com.vitorpamplona.quartz.nip01Core.HexKey
import com.vitorpamplona.quartz.nip01Core.KeyPair
import com.vitorpamplona.quartz.nip01Core.MetadataEvent
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
import com.vitorpamplona.quartz.nip01Core.hexToByteArray
import com.vitorpamplona.quartz.nip01Core.jackson.EventMapper
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
import com.vitorpamplona.quartz.nip01Core.toHexKey
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
import com.vitorpamplona.quartz.nip17Dm.ChatMessageRelayListEvent
import com.vitorpamplona.quartz.nip17Dm.settings.ChatMessageRelayListEvent
import com.vitorpamplona.quartz.nip19Bech32.toNpub
import com.vitorpamplona.quartz.nip47WalletConnect.Nip47WalletConnect
import com.vitorpamplona.quartz.nip50Search.SearchRelayListEvent
@@ -58,7 +58,7 @@ import com.vitorpamplona.amethyst.ui.tor.TorManager
import com.vitorpamplona.amethyst.ui.tor.TorType
import com.vitorpamplona.ammolite.relays.NostrClient
import com.vitorpamplona.quartz.nip01Core.toHexKey
import com.vitorpamplona.quartz.nip03Timestamp.OtsEvent
import com.vitorpamplona.quartz.nip03Timestamp.OtsResolver
import com.vitorpamplona.quartz.nip03Timestamp.ots.OpenTimestamps
import com.vitorpamplona.quartz.nip19Bech32.bech32.bechToBytes
import com.vitorpamplona.quartz.nip19Bech32.decodePublicKeyAsHexOrNull
@@ -110,13 +110,13 @@ class ServiceManager(
else -> HttpClientManager.setDefaultProxy(null)
}
OtsEvent.otsInstance =
OtsResolver.ots =
OpenTimestamps(
OkHttpBlockstreamExplorer(myAccount::shouldUseTorForMoneyOperations),
OkHttpCalendarBuilder(myAccount::shouldUseTorForMoneyOperations),
)
} else {
OtsEvent.otsInstance =
OtsResolver.ots =
OpenTimestamps(
OkHttpBlockstreamExplorer { false },
OkHttpCalendarBuilder { false },
File diff suppressed because it is too large Load Diff
@@ -31,12 +31,12 @@ import com.vitorpamplona.ammolite.relays.Constants
import com.vitorpamplona.ammolite.relays.RelaySetupInfo
import com.vitorpamplona.quartz.experimental.edits.PrivateOutboxRelayListEvent
import com.vitorpamplona.quartz.nip01Core.HexKey
import com.vitorpamplona.quartz.nip01Core.KeyPair
import com.vitorpamplona.quartz.nip01Core.MetadataEvent
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
import com.vitorpamplona.quartz.nip01Core.toHexKey
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
import com.vitorpamplona.quartz.nip17Dm.ChatMessageRelayListEvent
import com.vitorpamplona.quartz.nip17Dm.settings.ChatMessageRelayListEvent
import com.vitorpamplona.quartz.nip47WalletConnect.Nip47WalletConnect
import com.vitorpamplona.quartz.nip50Search.SearchRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.MuteListEvent
@@ -28,12 +28,15 @@ import com.vitorpamplona.amethyst.service.checkNotInMainThread
import com.vitorpamplona.amethyst.ui.dal.DefaultFeedOrder
import com.vitorpamplona.amethyst.ui.note.toShortenHex
import com.vitorpamplona.ammolite.relays.BundledUpdate
import com.vitorpamplona.ammolite.relays.Relay
import com.vitorpamplona.ammolite.relays.RelayBriefInfoCache
import com.vitorpamplona.quartz.nip01Core.HexKey
import com.vitorpamplona.quartz.nip01Core.tags.addressables.ATag
import com.vitorpamplona.quartz.nip19Bech32.toNAddr
import com.vitorpamplona.quartz.nip19Bech32.toNEvent
import com.vitorpamplona.quartz.nip28PublicChat.ChannelCreateEvent
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesEvent
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent
import com.vitorpamplona.quartz.nip28PublicChat.base.ChannelData
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
import com.vitorpamplona.quartz.utils.Hex
import kotlinx.coroutines.Dispatchers
@@ -41,11 +44,23 @@ import kotlinx.coroutines.Dispatchers
class PublicChatChannel(
idHex: String,
) : Channel(idHex) {
var info = ChannelCreateEvent.ChannelData(null, null, null)
var event: ChannelCreateEvent? = null
var info = ChannelData(null, null, null, null)
override fun relays() = info.relays ?: super.relays()
fun updateChannelInfo(
creator: User,
channelInfo: ChannelCreateEvent.ChannelData,
channelInfo: ChannelCreateEvent,
updatedAt: Long,
) {
this.event = channelInfo
updateChannelInfo(creator, channelInfo.channelInfo(), updatedAt)
}
fun updateChannelInfo(
creator: User,
channelInfo: ChannelData,
updatedAt: Long,
) {
this.info = channelInfo
@@ -76,6 +91,8 @@ class LiveActivitiesChannel(
fun address() = address
override fun relays() = info?.allRelayUrls() ?: super.relays()
fun updateChannelInfo(
creator: User,
channelInfo: LiveActivitiesEvent,
@@ -97,6 +114,10 @@ class LiveActivitiesChannel(
.isNotEmpty()
}
data class Counter(
var number: Int = 0,
)
@Stable
abstract class Channel(
val idHex: String,
@@ -105,6 +126,7 @@ abstract class Channel(
var updatedMetadataAt: Long = 0
val notes = LargeCache<HexKey, Note>()
var lastNoteCreatedAt: Long = 0
private var relays = mapOf<RelayBriefInfoCache.RelayBriefInfo, Counter>()
open fun id() = Hex.decode(idHex)
@@ -120,6 +142,14 @@ abstract class Channel(
open fun profilePicture(): String? = creator?.info?.banner
open fun relays() =
relays.keys
.toSortedSet { o1, o2 ->
val o1Count = relays[o1]?.number ?: 0
val o2Count = relays[o2]?.number ?: 0
o2Count.compareTo(o1Count) // descending
}.map { it.url }
open fun updateChannelInfo(
creator: User,
updatedAt: Long,
@@ -130,12 +160,31 @@ abstract class Channel(
live.invalidateData()
}
fun addNote(note: Note) {
@Synchronized
fun addRelaySync(briefInfo: RelayBriefInfoCache.RelayBriefInfo) {
if (briefInfo !in relays) {
relays = relays + Pair(briefInfo, Counter(1))
}
}
fun addNote(
note: Note,
relay: Relay? = null,
) {
notes.put(note.idHex, note)
if ((note.createdAt() ?: 0) > lastNoteCreatedAt) {
lastNoteCreatedAt = note.createdAt() ?: 0
}
if (relay != null) {
val counter = relays[relay.brief]
if (counter != null) {
counter.number++
} else {
addRelaySync(relay.brief)
}
}
}
fun removeNote(note: Note) {
@@ -31,49 +31,51 @@ import com.vitorpamplona.amethyst.model.observables.LatestByKindWithETag
import com.vitorpamplona.amethyst.service.checkNotInMainThread
import com.vitorpamplona.ammolite.relays.BundledInsert
import com.vitorpamplona.ammolite.relays.Relay
import com.vitorpamplona.ammolite.relays.RelayBriefInfoCache
import com.vitorpamplona.quartz.blossom.BlossomServersEvent
import com.vitorpamplona.quartz.experimental.audio.AudioHeaderEvent
import com.vitorpamplona.quartz.experimental.audio.AudioTrackEvent
import com.vitorpamplona.quartz.experimental.audio.header.AudioHeaderEvent
import com.vitorpamplona.quartz.experimental.audio.track.AudioTrackEvent
import com.vitorpamplona.quartz.experimental.edits.PrivateOutboxRelayListEvent
import com.vitorpamplona.quartz.experimental.edits.TextNoteModificationEvent
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryPrologueEvent
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryReadingStateEvent
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStorySceneEvent
import com.vitorpamplona.quartz.experimental.medical.FhirResourceEvent
import com.vitorpamplona.quartz.experimental.nip95.FileStorageEvent
import com.vitorpamplona.quartz.experimental.nip95.FileStorageHeaderEvent
import com.vitorpamplona.quartz.experimental.nip95.data.FileStorageEvent
import com.vitorpamplona.quartz.experimental.nip95.header.FileStorageHeaderEvent
import com.vitorpamplona.quartz.experimental.nns.NNSEvent
import com.vitorpamplona.quartz.experimental.profileGallery.ProfileGalleryEntryEvent
import com.vitorpamplona.quartz.experimental.relationshipStatus.RelationshipStatusEvent
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
import com.vitorpamplona.quartz.nip01Core.HexKey
import com.vitorpamplona.quartz.nip01Core.MetadataEvent
import com.vitorpamplona.quartz.nip01Core.checkSignature
import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
import com.vitorpamplona.quartz.nip01Core.core.BaseAddressableEvent
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.core.tagValueContains
import com.vitorpamplona.quartz.nip01Core.hasValidSignature
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
import com.vitorpamplona.quartz.nip01Core.tags.addressables.ATag
import com.vitorpamplona.quartz.nip01Core.tags.addressables.mapTaggedAddress
import com.vitorpamplona.quartz.nip01Core.tags.addressables.taggedAddresses
import com.vitorpamplona.quartz.nip01Core.tags.events.ETag
import com.vitorpamplona.quartz.nip01Core.tags.events.GenericETag
import com.vitorpamplona.quartz.nip01Core.tags.events.forEachTaggedEventId
import com.vitorpamplona.quartz.nip01Core.tags.events.isTaggedEvent
import com.vitorpamplona.quartz.nip01Core.tags.events.mapTaggedEventId
import com.vitorpamplona.quartz.nip01Core.tags.events.taggedEvents
import com.vitorpamplona.quartz.nip01Core.tags.people.isTaggedUsers
import com.vitorpamplona.quartz.nip01Core.verify
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
import com.vitorpamplona.quartz.nip03Timestamp.OtsEvent
import com.vitorpamplona.quartz.nip03Timestamp.VerificationState
import com.vitorpamplona.quartz.nip04Dm.PrivateDmEvent
import com.vitorpamplona.quartz.nip09Deletions.DeletionEvent
import com.vitorpamplona.quartz.nip10Notes.BaseTextNoteEvent
import com.vitorpamplona.quartz.nip10Notes.BaseThreadedEvent
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
import com.vitorpamplona.quartz.nip17Dm.ChatMessageEncryptedFileHeaderEvent
import com.vitorpamplona.quartz.nip17Dm.ChatMessageEvent
import com.vitorpamplona.quartz.nip17Dm.ChatMessageRelayListEvent
import com.vitorpamplona.quartz.nip17Dm.ChatroomKey
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey
import com.vitorpamplona.quartz.nip17Dm.files.ChatMessageEncryptedFileHeaderEvent
import com.vitorpamplona.quartz.nip17Dm.messages.ChatMessageEvent
import com.vitorpamplona.quartz.nip17Dm.settings.ChatMessageRelayListEvent
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
import com.vitorpamplona.quartz.nip19Bech32.decodeEventIdAsHexOrNull
@@ -82,18 +84,18 @@ import com.vitorpamplona.quartz.nip19Bech32.isATag
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
import com.vitorpamplona.quartz.nip28PublicChat.ChannelCreateEvent
import com.vitorpamplona.quartz.nip28PublicChat.ChannelHideMessageEvent
import com.vitorpamplona.quartz.nip28PublicChat.ChannelListEvent
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMessageEvent
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMetadataEvent
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMuteUserEvent
import com.vitorpamplona.quartz.nip30CustomEmoji.EmojiPackEvent
import com.vitorpamplona.quartz.nip30CustomEmoji.EmojiPackSelectionEvent
import com.vitorpamplona.quartz.nip34Git.GitIssueEvent
import com.vitorpamplona.quartz.nip34Git.GitPatchEvent
import com.vitorpamplona.quartz.nip34Git.GitReplyEvent
import com.vitorpamplona.quartz.nip34Git.GitRepositoryEvent
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelHideMessageEvent
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelMetadataEvent
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelMuteUserEvent
import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
import com.vitorpamplona.quartz.nip30CustomEmoji.pack.EmojiPackEvent
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.nip34Git.repository.GitRepositoryEvent
import com.vitorpamplona.quartz.nip35Torrents.TorrentCommentEvent
import com.vitorpamplona.quartz.nip35Torrents.TorrentEvent
import com.vitorpamplona.quartz.nip37Drafts.DraftEvent
@@ -113,8 +115,8 @@ import com.vitorpamplona.quartz.nip52Calendar.CalendarDateSlotEvent
import com.vitorpamplona.quartz.nip52Calendar.CalendarEvent
import com.vitorpamplona.quartz.nip52Calendar.CalendarRSVPEvent
import com.vitorpamplona.quartz.nip52Calendar.CalendarTimeSlotEvent
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesChatMessageEvent
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesEvent
import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
import com.vitorpamplona.quartz.nip54Wiki.WikiNoteEvent
import com.vitorpamplona.quartz.nip56Reports.ReportEvent
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
@@ -122,27 +124,28 @@ import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent
import com.vitorpamplona.quartz.nip58Badges.BadgeAwardEvent
import com.vitorpamplona.quartz.nip58Badges.BadgeDefinitionEvent
import com.vitorpamplona.quartz.nip58Badges.BadgeProfilesEvent
import com.vitorpamplona.quartz.nip59Giftwrap.GiftWrapEvent
import com.vitorpamplona.quartz.nip59Giftwrap.SealedRumorEvent
import com.vitorpamplona.quartz.nip59Giftwrap.WrappedEvent
import com.vitorpamplona.quartz.nip59Giftwrap.seals.SealedRumorEvent
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
import com.vitorpamplona.quartz.nip65RelayList.RelayUrlFormatter
import com.vitorpamplona.quartz.nip68Picture.PictureEvent
import com.vitorpamplona.quartz.nip71Video.VideoHorizontalEvent
import com.vitorpamplona.quartz.nip71Video.VideoVerticalEvent
import com.vitorpamplona.quartz.nip72ModCommunities.CommunityDefinitionEvent
import com.vitorpamplona.quartz.nip72ModCommunities.CommunityListEvent
import com.vitorpamplona.quartz.nip72ModCommunities.CommunityPostApprovalEvent
import com.vitorpamplona.quartz.nip72ModCommunities.approval.CommunityPostApprovalEvent
import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent
import com.vitorpamplona.quartz.nip78AppData.AppSpecificDataEvent
import com.vitorpamplona.quartz.nip84Highlights.HighlightEvent
import com.vitorpamplona.quartz.nip89AppHandlers.AppDefinitionEvent
import com.vitorpamplona.quartz.nip89AppHandlers.AppRecommendationEvent
import com.vitorpamplona.quartz.nip89AppHandlers.definition.AppDefinitionEvent
import com.vitorpamplona.quartz.nip89AppHandlers.recommendation.AppRecommendationEvent
import com.vitorpamplona.quartz.nip90Dvms.NIP90ContentDiscoveryRequestEvent
import com.vitorpamplona.quartz.nip90Dvms.NIP90ContentDiscoveryResponseEvent
import com.vitorpamplona.quartz.nip90Dvms.NIP90StatusEvent
import com.vitorpamplona.quartz.nip90Dvms.NIP90UserDiscoveryRequestEvent
import com.vitorpamplona.quartz.nip90Dvms.NIP90UserDiscoveryResponseEvent
import com.vitorpamplona.quartz.nip94FileMetadata.FileHeaderEvent
import com.vitorpamplona.quartz.nip96FileStorage.FileServersEvent
import com.vitorpamplona.quartz.nip96FileStorage.config.FileServersEvent
import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent
import com.vitorpamplona.quartz.utils.Hex
import com.vitorpamplona.quartz.utils.TimeUtils
@@ -287,7 +290,7 @@ object LocalCache {
fun getOrCreateNote(event: Event): Note =
if (event is AddressableEvent) {
getOrCreateAddressableNote(event.address())
getOrCreateAddressableNote(event.aTag())
} else {
getOrCreateNote(event.id)
}
@@ -301,6 +304,8 @@ object LocalCache {
return null
}
fun checkGetOrCreateNote(atag: ATag) = getOrCreateAddressableNote(atag)
fun checkGetOrCreateNote(key: String): Note? {
checkNotInMainThread()
@@ -312,7 +317,7 @@ object LocalCache {
val noteEvent = note.event
return if (noteEvent is AddressableEvent) {
// upgrade to the latest
val newNote = checkGetOrCreateAddressableNote(noteEvent.address().toTag())
val newNote = checkGetOrCreateAddressableNote(noteEvent.aTag().toTag())
if (newNote != null && newNote.event == null) {
val author = note.author ?: getOrCreateUser(noteEvent.pubKey)
@@ -412,13 +417,18 @@ object LocalCache {
return note
}
fun getOrCreateNote(key: ETag): Note {
fun getOrCreateNote(key: GenericETag): Note {
val note = getOrCreateNote(key.eventId)
// Loads the user outside a Syncronized block to avoid blocking
val possibleAuthor = key.authorPubKeyHex
val possibleAuthor = key.author
if (note.author == null && possibleAuthor != null) {
note.author = checkGetOrCreateUser(possibleAuthor)
}
val relayHint = key.relay
if (!relayHint.isNullOrBlank()) {
val relay = RelayBriefInfoCache.get(RelayUrlFormatter.normalize(relayHint))
note.addRelayBrief(relay)
}
return note
}
@@ -521,7 +531,7 @@ object LocalCache {
val replyTo = computeReplyTo(event)
if (event is BaseTextNoteEvent && antiSpam.isSpam(event, relay)) {
if (event is BaseThreadedEvent && antiSpam.isSpam(event, relay)) {
return
}
@@ -588,7 +598,7 @@ object LocalCache {
relay: Relay?,
) {
val version = getOrCreateNote(event.id)
val note = getOrCreateAddressableNote(event.address())
val note = getOrCreateAddressableNote(event.aTag())
val author = getOrCreateUser(event.pubKey)
if (version.event == null) {
@@ -622,7 +632,7 @@ object LocalCache {
relay: Relay?,
) {
val version = getOrCreateNote(event.id)
val note = getOrCreateAddressableNote(event.address())
val note = getOrCreateAddressableNote(event.aTag())
val author = getOrCreateUser(event.pubKey)
if (version.event == null) {
@@ -681,7 +691,9 @@ object LocalCache {
is GenericRepostEvent ->
event.boostedPost().mapNotNull { checkGetOrCreateNote(it) } +
event.taggedAddresses().map { getOrCreateAddressableNote(it) }
is CommunityPostApprovalEvent -> event.approvedEvents().mapNotNull { checkGetOrCreateNote(it) }
is CommunityPostApprovalEvent ->
event.approvedEvents().mapNotNull { checkGetOrCreateNote(it) } +
event.approvedAddresses().map { checkGetOrCreateNote(it) }
is ReactionEvent ->
event.originalPost().mapNotNull { checkGetOrCreateNote(it) } +
event.taggedAddresses().map { getOrCreateAddressableNote(it) }
@@ -691,7 +703,7 @@ object LocalCache {
is ChannelMessageEvent ->
event
.tagsWithoutCitations()
.filter { it != event.channel() }
.filter { it != event.channelId() }
.mapNotNull { checkGetOrCreateNote(it) }
is LiveActivitiesChatMessageEvent ->
event
@@ -718,7 +730,7 @@ object LocalCache {
relay: Relay?,
) {
val version = getOrCreateNote(event.id)
val note = getOrCreateAddressableNote(event.address())
val note = getOrCreateAddressableNote(event.aTag())
val author = getOrCreateUser(event.pubKey)
if (version.event == null) {
@@ -735,7 +747,7 @@ object LocalCache {
getOrCreateChannel(note.idHex) { LiveActivitiesChannel(note.address) }
as? LiveActivitiesChannel
val creator = event.host()?.ifBlank { null }?.let { checkGetOrCreateUser(it) } ?: author
val creator = event.host()?.let { checkGetOrCreateUser(it.pubKey) } ?: author
channel?.updateChannelInfo(creator, event, event.createdAt)
@@ -888,7 +900,7 @@ object LocalCache {
relay: Relay?,
) {
val version = getOrCreateNote(event.id)
val note = getOrCreateAddressableNote(event.address())
val note = getOrCreateAddressableNote(event.aTag())
val author = getOrCreateUser(event.pubKey)
if (version.event == null) {
@@ -945,7 +957,7 @@ object LocalCache {
fun consume(event: BadgeProfilesEvent) {
val version = getOrCreateNote(event.id)
val note = getOrCreateAddressableNote(event.address())
val note = getOrCreateAddressableNote(event.aTag())
val author = getOrCreateUser(event.pubKey)
if (version.event == null) {
@@ -1017,7 +1029,7 @@ object LocalCache {
relay: Relay?,
) {
val version = getOrCreateNote(event.id)
val note = getOrCreateAddressableNote(event.address())
val note = getOrCreateAddressableNote(event.aTag())
val author = getOrCreateUser(event.pubKey)
val replyTos = computeReplyTo(event)
@@ -1182,7 +1194,7 @@ object LocalCache {
getChannelIfExists(it.toTag())?.removeNote(deleteNote)
}
(deletedEvent as? TorrentCommentEvent)?.torrent()?.let {
(deletedEvent as? TorrentCommentEvent)?.torrentIds()?.let {
getNoteIfExists(it)?.removeReply(deleteNote)
}
@@ -1353,14 +1365,17 @@ object LocalCache {
refreshObservers(note)
}
fun consume(event: ChannelCreateEvent) {
fun consume(
event: ChannelCreateEvent,
relay: Relay?,
) {
// Log.d("MT", "New Event ${event.content} ${event.id.toHex()}")
val oldChannel = getOrCreateChannel(event.id) { PublicChatChannel(it) }
val author = getOrCreateUser(event.pubKey)
val note = getOrCreateNote(event.id)
if (note.event == null) {
oldChannel.addNote(note)
oldChannel.addNote(note, relay)
note.loadEvent(event, author, emptyList())
refreshObservers(note)
@@ -1371,13 +1386,16 @@ object LocalCache {
}
if (oldChannel.creator == null || oldChannel.creator == author) {
if (oldChannel is PublicChatChannel) {
oldChannel.updateChannelInfo(author, event.channelInfo(), event.createdAt)
oldChannel.updateChannelInfo(author, event, event.createdAt)
}
}
}
fun consume(event: ChannelMetadataEvent) {
val channelId = event.channel()
fun consume(
event: ChannelMetadataEvent,
relay: Relay?,
) {
val channelId = event.channelId()
// Log.d("MT", "New PublicChatMetadata ${event.channelInfo()}")
if (channelId.isNullOrBlank()) return
@@ -1396,7 +1414,7 @@ object LocalCache {
val note = getOrCreateNote(event.id)
if (note.event == null) {
oldChannel.addNote(note)
oldChannel.addNote(note, relay)
note.loadEvent(event, author, emptyList())
refreshObservers(note)
@@ -1407,14 +1425,14 @@ object LocalCache {
event: ChannelMessageEvent,
relay: Relay?,
) {
val channelId = event.channel()
val channelId = event.channelId()
if (channelId.isNullOrBlank()) return
val channel = checkGetOrCreateChannel(channelId) ?: return
val note = getOrCreateNote(event.id)
channel.addNote(note)
channel.addNote(note, relay)
val author = getOrCreateUser(event.pubKey)
@@ -1457,7 +1475,7 @@ object LocalCache {
val channel = getOrCreateChannel(activityId.toTag()) { LiveActivitiesChannel(activityId) }
val note = getOrCreateNote(event.id)
channel.addNote(note)
channel.addNote(note, relay)
val author = getOrCreateUser(event.pubKey)
@@ -2102,7 +2120,7 @@ object LocalCache {
val noteEvent = note.event
if (noteEvent is AddressableEvent) {
noteEvent.createdAt <
(addressables.get(noteEvent.address().toTag())?.event?.createdAt ?: 0)
(addressables.get(noteEvent.aTag().toTag())?.event?.createdAt ?: 0)
} else {
false
}
@@ -2111,7 +2129,7 @@ object LocalCache {
val childrenToBeRemoved = mutableListOf<Note>()
toBeRemoved.forEach {
val newerVersion = (it.event as? AddressableEvent)?.address()?.toTag()?.let { tag -> addressables.get(tag) }
val newerVersion = (it.event as? AddressableEvent)?.aTag()?.toTag()?.let { tag -> addressables.get(tag) }
if (newerVersion != null) {
it.moveAllReferencesTo(newerVersion)
}
@@ -2295,7 +2313,7 @@ object LocalCache {
fun justVerify(event: Event): Boolean {
checkNotInMainThread()
return if (!event.hasValidSignature()) {
return if (!event.verify()) {
try {
event.checkSignature()
} catch (e: Exception) {
@@ -2327,7 +2345,7 @@ object LocalCache {
draftWrap: DraftEvent,
draft: Event,
) {
val note = getOrCreateAddressableNote(draftWrap.address())
val note = getOrCreateAddressableNote(draftWrap.aTag())
val author = getOrCreateUser(draftWrap.pubKey)
when (draft) {
@@ -2378,9 +2396,9 @@ object LocalCache {
}
}
is ChannelMessageEvent -> {
draft.channel()?.let { channelId ->
draft.channelId()?.let { channelId ->
checkGetOrCreateChannel(channelId)?.let { channel ->
channel.addNote(note)
channel.addNote(note, null)
}
}
}
@@ -2447,7 +2465,7 @@ object LocalCache {
}
}
is ChannelMessageEvent -> {
draft.channel()?.let { channelId ->
draft.channelId()?.let { channelId ->
checkGetOrCreateChannel(channelId)?.let { channel ->
channel.removeNote(draftWrap)
}
@@ -2506,11 +2524,11 @@ object LocalCache {
is CalendarDateSlotEvent -> consume(event, relay)
is CalendarTimeSlotEvent -> consume(event, relay)
is CalendarRSVPEvent -> consume(event, relay)
is ChannelCreateEvent -> consume(event)
is ChannelCreateEvent -> consume(event, relay)
is ChannelListEvent -> consume(event, relay)
is ChannelHideMessageEvent -> consume(event)
is ChannelMessageEvent -> consume(event, relay)
is ChannelMetadataEvent -> consume(event)
is ChannelMetadataEvent -> consume(event, relay)
is ChannelMuteUserEvent -> consume(event)
is ChatMessageEncryptedFileHeaderEvent -> consume(event, relay)
is ChatMessageEvent -> consume(event, relay)
@@ -37,35 +37,40 @@ import com.vitorpamplona.ammolite.relays.BundledUpdate
import com.vitorpamplona.ammolite.relays.Relay
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.lightning.LnInvoiceUtil
import com.vitorpamplona.quartz.nip01Core.EventHintBundle
import com.vitorpamplona.quartz.nip01Core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nip01Core.tags.addressables.ATag
import com.vitorpamplona.quartz.nip01Core.tags.events.ETag
import com.vitorpamplona.quartz.nip01Core.tags.events.EventReference
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.anyHashTag
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.isTaggedHash
import com.vitorpamplona.quartz.nip02FollowList.ImmutableListOfLists
import com.vitorpamplona.quartz.nip10Notes.BaseTextNoteEvent
import com.vitorpamplona.quartz.nip10Notes.BaseThreadedEvent
import com.vitorpamplona.quartz.nip10Notes.tags.MarkedETag
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
import com.vitorpamplona.quartz.nip19Bech32.entities.NEvent
import com.vitorpamplona.quartz.nip19Bech32.toNAddr
import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent
import com.vitorpamplona.quartz.nip28PublicChat.ChannelCreateEvent
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMessageEvent
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMetadataEvent
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.nip36SensitiveContent.isSensitiveOrNSFW
import com.vitorpamplona.quartz.nip37Drafts.DraftEvent
import com.vitorpamplona.quartz.nip47WalletConnect.LnZapPaymentRequestEvent
import com.vitorpamplona.quartz.nip47WalletConnect.LnZapPaymentResponseEvent
import com.vitorpamplona.quartz.nip47WalletConnect.PayInvoiceSuccessResponse
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesChatMessageEvent
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesEvent
import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent
import com.vitorpamplona.quartz.nip59Giftwrap.WrappedEvent
import com.vitorpamplona.quartz.nip72ModCommunities.CommunityPostApprovalEvent
import com.vitorpamplona.quartz.nip72ModCommunities.approval.CommunityPostApprovalEvent
import com.vitorpamplona.quartz.utils.Hex
import com.vitorpamplona.quartz.utils.TimeUtils
import com.vitorpamplona.quartz.utils.containsAny
@@ -108,6 +113,8 @@ class AddressableNote(
val thisEvent = event
return deletionAddressables.contains(address) || (thisEvent != null && deletionEvents.contains(thisEvent.id))
}
override fun toATag() = ATag.parse(idHex, relayHintUrl())
}
@Stable
@@ -195,11 +202,11 @@ open class Note(
event is LiveActivitiesChatMessageEvent ||
event is LiveActivitiesEvent
) {
(event as? ChannelMessageEvent)?.channel()
?: (event as? ChannelMetadataEvent)?.channel()
(event as? ChannelMessageEvent)?.channelId()
?: (event as? ChannelMetadataEvent)?.channelId()
?: (event as? ChannelCreateEvent)?.id
?: (event as? LiveActivitiesChatMessageEvent)?.activity()?.toTag()
?: (event as? LiveActivitiesEvent)?.address()?.toTag()
?: (event as? LiveActivitiesEvent)?.aTag()?.toTag()
} else {
null
}
@@ -442,6 +449,13 @@ open class Note(
}
}
fun addRelayBrief(brief: RelayBriefInfoCache.RelayBriefInfo) {
if (brief !in relays) {
addRelaySync(brief)
flowSet?.relays?.invalidateData()
}
}
private suspend fun isPaidByCalculation(
account: Account,
zapEvents: List<Pair<Note, Note?>>,
@@ -702,7 +716,7 @@ open class Note(
fun hasPledgeBy(user: User): Boolean =
replies
.filter { it.event?.isTaggedHash("bounty-added-reward") ?: false }
.filter { it.event?.hasAdditionalReward() ?: false }
.any {
val pledgeValue =
try {
@@ -716,18 +730,7 @@ open class Note(
pledgeValue != null && it.author == user
}
fun pledgedAmountByOthers(): BigDecimal =
replies
.filter { it.event?.isTaggedHash("bounty-added-reward") ?: false }
.mapNotNull {
try {
BigDecimal(it.event?.content)
} catch (e: Exception) {
if (e is CancellationException) throw e
null
// do nothing if it can't convert to bigdecimal
}
}.sumOf { it }
fun pledgedAmountByOthers(): BigDecimal = replies.sumOf { it.event?.addedRewardValue() ?: BigDecimal.ZERO }
fun hasAnyReports(): Boolean {
val dayAgo = TimeUtils.oneDayAgo()
@@ -839,7 +842,7 @@ open class Note(
}
if (accountChoices.hiddenWordsCase.isNotEmpty()) {
if (thisEvent is BaseTextNoteEvent && thisEvent.content.containsAny(accountChoices.hiddenWordsCase)) {
if (thisEvent is BaseThreadedEvent && thisEvent.content.containsAny(accountChoices.hiddenWordsCase)) {
return true
}
@@ -915,7 +918,46 @@ open class Note(
deletionAddressables: Set<ATag>,
): Boolean {
val thisEvent = event
return deletionEvents.contains(idHex) || (thisEvent is AddressableEvent && deletionAddressables.contains(thisEvent.address()))
return deletionEvents.contains(idHex) || (thisEvent is AddressableEvent && deletionAddressables.contains(thisEvent.aTag()))
}
fun toETag(): ETag {
val noteEvent = event
return if (noteEvent != null) {
ETag(noteEvent.id, relayHintUrl(), noteEvent.pubKey)
} else {
ETag(idHex, relayHintUrl(), author?.pubkeyHex)
}
}
fun toEId(): EventReference {
val noteEvent = event
return if (noteEvent != null) {
// uses the confirmed event id if available
EventReference(noteEvent.id, noteEvent.pubKey, relayHintUrl())
} else {
EventReference(idHex, author?.pubkeyHex, relayHintUrl())
}
}
fun <T : Event> toEventHint() = (event as? T)?.let { EventHintBundle(it, relayHintUrl(), author?.bestRelayHint()) }
fun toMarkedETag(marker: MarkedETag.MARKER): MarkedETag {
val noteEvent = event
return if (noteEvent != null) {
MarkedETag(noteEvent.id, relayHintUrl(), marker, noteEvent.pubKey)
} else {
MarkedETag(idHex, relayHintUrl(), marker, author?.pubkeyHex)
}
}
open fun toATag(): ATag? {
val noteEvent = event
return if (noteEvent is AddressableEvent) {
ATag(noteEvent.kind, noteEvent.pubKey, noteEvent.dTag(), relayHintUrl())
} else {
null
}
}
}
@@ -142,7 +142,7 @@ class OnlyLatestVersionSet : MutableSet<Note> {
return if (element is AddressableNote && loadedCreatedAt != null) {
innerAdd(element.address, element, loadedCreatedAt)
} else if (noteEvent is AddressableEvent && loadedCreatedAt != null) {
innerAdd(noteEvent.address(), element, loadedCreatedAt)
innerAdd(noteEvent.aTag(), element, loadedCreatedAt)
} else {
set.add(element)
}
@@ -193,7 +193,7 @@ class OnlyLatestVersionSet : MutableSet<Note> {
element.address()?.let {
map.remove(it)
}
(element.event as? AddressableEvent)?.address()?.let {
(element.event as? AddressableEvent)?.aTag()?.let {
map.remove(it)
}
@@ -33,14 +33,15 @@ import com.vitorpamplona.ammolite.relays.Relay
import com.vitorpamplona.ammolite.relays.filters.EOSETime
import com.vitorpamplona.quartz.lightning.Lud06
import com.vitorpamplona.quartz.nip01Core.HexKey
import com.vitorpamplona.quartz.nip01Core.MetadataEvent
import com.vitorpamplona.quartz.nip01Core.UserMetadata
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
import com.vitorpamplona.quartz.nip01Core.metadata.UserMetadata
import com.vitorpamplona.quartz.nip01Core.tags.geohash.isTaggedGeoHash
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.isTaggedHash
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
import com.vitorpamplona.quartz.nip01Core.tags.people.isTaggedUser
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
import com.vitorpamplona.quartz.nip02FollowList.toImmutableListOfLists
import com.vitorpamplona.quartz.nip17Dm.ChatroomKey
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey
import com.vitorpamplona.quartz.nip19Bech32.entities.NProfile
import com.vitorpamplona.quartz.nip19Bech32.toNpub
import com.vitorpamplona.quartz.nip51Lists.BookmarkListEvent
@@ -86,14 +87,15 @@ class User(
fun pubkeyDisplayHex() = pubkeyNpub().toShortenHex()
fun toNProfile(): String {
val relayList = (LocalCache.getAddressableNoteIfExists(AdvertisedRelayListEvent.createAddressTag(pubkeyHex))?.event as? AdvertisedRelayListEvent)?.writeRelays()
fun authorRelayList() = (LocalCache.getAddressableNoteIfExists(AdvertisedRelayListEvent.createAddressTag(pubkeyHex))?.event as? AdvertisedRelayListEvent)
return NProfile.create(
pubkeyHex,
relayList?.take(3) ?: listOfNotNull(latestMetadataRelay),
)
}
fun toNProfile() = NProfile.create(pubkeyHex, relayHints())
fun relayHints() = authorRelayList()?.writeRelays()?.take(3) ?: listOfNotNull(latestMetadataRelay)
fun bestRelayHint() = authorRelayList()?.writeRelays()?.firstOrNull() ?: latestMetadataRelay
fun toPTag() = PTag(pubkeyHex, bestRelayHint())
fun toNostrUri() = "nostr:${toNProfile()}"
@@ -44,7 +44,7 @@ abstract class AmethystNostrDataSource(
val note = LocalCache.getNoteIfExists(eventId)
val noteEvent = note?.event
if (noteEvent is AddressableEvent) {
LocalCache.getAddressableNoteIfExists(noteEvent.address().toTag())?.addRelay(relay)
LocalCache.getAddressableNoteIfExists(noteEvent.aTag().toTag())?.addRelay(relay)
} else {
note?.addRelay(relay)
}
@@ -36,20 +36,20 @@ import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStory
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStorySceneEvent
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
import com.vitorpamplona.quartz.nip01Core.HexKey
import com.vitorpamplona.quartz.nip01Core.MetadataEvent
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
import com.vitorpamplona.quartz.nip17Dm.ChatMessageRelayListEvent
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.ChannelMessageEvent
import com.vitorpamplona.quartz.nip30CustomEmoji.EmojiPackSelectionEvent
import com.vitorpamplona.quartz.nip34Git.GitIssueEvent
import com.vitorpamplona.quartz.nip34Git.GitPatchEvent
import com.vitorpamplona.quartz.nip34Git.GitReplyEvent
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
@@ -64,12 +64,12 @@ 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.GiftWrapEvent
import com.vitorpamplona.quartz.nip59Giftwrap.SealedRumorEvent
import com.vitorpamplona.quartz.nip59Giftwrap.seals.SealedRumorEvent
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.FileServersEvent
import com.vitorpamplona.quartz.nip96FileStorage.config.FileServersEvent
import com.vitorpamplona.quartz.utils.TimeUtils
// TODO: Migrate this to a property of AccountVi
@@ -27,8 +27,8 @@ import com.vitorpamplona.amethyst.model.PublicChatChannel
import com.vitorpamplona.ammolite.relays.FeedType
import com.vitorpamplona.ammolite.relays.TypedFilter
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMessageEvent
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesChatMessageEvent
import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent
object NostrChannelDataSource : AmethystNostrDataSource("ChatroomFeed") {
var account: Account? = null
@@ -26,7 +26,7 @@ import com.vitorpamplona.ammolite.relays.FeedType
import com.vitorpamplona.ammolite.relays.TypedFilter
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
import com.vitorpamplona.quartz.nip04Dm.PrivateDmEvent
import com.vitorpamplona.quartz.nip17Dm.ChatroomKey
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey
object NostrChatroomDataSource : AmethystNostrDataSource("ChatroomFeed") {
lateinit var account: Account
@@ -27,9 +27,9 @@ import com.vitorpamplona.ammolite.relays.FeedType
import com.vitorpamplona.ammolite.relays.TypedFilter
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
import com.vitorpamplona.quartz.nip04Dm.PrivateDmEvent
import com.vitorpamplona.quartz.nip28PublicChat.ChannelCreateEvent
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMessageEvent
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMetadataEvent
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelMetadataEvent
import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
object NostrChatroomListDataSource : AmethystNostrDataSource("MailBoxFeed") {
lateinit var account: Account
@@ -24,8 +24,8 @@ import com.vitorpamplona.amethyst.model.AddressableNote
import com.vitorpamplona.ammolite.relays.COMMON_FEED_TYPES
import com.vitorpamplona.ammolite.relays.TypedFilter
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
import com.vitorpamplona.quartz.nip72ModCommunities.CommunityDefinitionEvent
import com.vitorpamplona.quartz.nip72ModCommunities.CommunityPostApprovalEvent
import com.vitorpamplona.quartz.nip72ModCommunities.approval.CommunityPostApprovalEvent
import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent
object NostrCommunityDataSource : AmethystNostrDataSource("SingleCommunityFeed") {
private var communityToWatch: AddressableNote? = null
@@ -38,7 +38,7 @@ object NostrCommunityDataSource : AmethystNostrDataSource("SingleCommunityFeed")
val authors =
community
.moderators()
.map { it.key }
.map { it.pubKey }
.plus(listOfNotNull(myCommunityToWatch.author?.pubkeyHex))
if (authors.isEmpty()) return null
@@ -27,14 +27,14 @@ import com.vitorpamplona.ammolite.relays.FeedType
import com.vitorpamplona.ammolite.relays.TypedFilter
import com.vitorpamplona.ammolite.relays.filters.SinceAuthorPerRelayFilter
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
import com.vitorpamplona.quartz.nip28PublicChat.ChannelCreateEvent
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMessageEvent
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMetadataEvent
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesChatMessageEvent
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesEvent
import com.vitorpamplona.quartz.nip72ModCommunities.CommunityDefinitionEvent
import com.vitorpamplona.quartz.nip72ModCommunities.CommunityPostApprovalEvent
import com.vitorpamplona.quartz.nip89AppHandlers.AppDefinitionEvent
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.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.Dispatchers
import kotlinx.coroutines.Job
@@ -23,13 +23,13 @@ package com.vitorpamplona.amethyst.service
import com.vitorpamplona.ammolite.relays.COMMON_FEED_TYPES
import com.vitorpamplona.ammolite.relays.TypedFilter
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
import com.vitorpamplona.quartz.experimental.audio.AudioHeaderEvent
import com.vitorpamplona.quartz.experimental.audio.AudioTrackEvent
import com.vitorpamplona.quartz.experimental.audio.header.AudioHeaderEvent
import com.vitorpamplona.quartz.experimental.audio.track.AudioTrackEvent
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMessageEvent
import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
import com.vitorpamplona.quartz.nip54Wiki.WikiNoteEvent
import com.vitorpamplona.quartz.nip84Highlights.HighlightEvent
import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent
@@ -23,15 +23,15 @@ package com.vitorpamplona.amethyst.service
import com.vitorpamplona.ammolite.relays.COMMON_FEED_TYPES
import com.vitorpamplona.ammolite.relays.TypedFilter
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
import com.vitorpamplona.quartz.experimental.audio.AudioHeaderEvent
import com.vitorpamplona.quartz.experimental.audio.AudioTrackEvent
import com.vitorpamplona.quartz.experimental.audio.header.AudioHeaderEvent
import com.vitorpamplona.quartz.experimental.audio.track.AudioTrackEvent
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStorySceneEvent
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMessageEvent
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesChatMessageEvent
import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent
import com.vitorpamplona.quartz.nip54Wiki.WikiNoteEvent
import com.vitorpamplona.quartz.nip84Highlights.HighlightEvent
import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent
@@ -27,22 +27,22 @@ import com.vitorpamplona.ammolite.relays.FeedType
import com.vitorpamplona.ammolite.relays.TypedFilter
import com.vitorpamplona.ammolite.relays.filters.SinceAuthorPerRelayFilter
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
import com.vitorpamplona.quartz.experimental.audio.AudioHeaderEvent
import com.vitorpamplona.quartz.experimental.audio.AudioTrackEvent
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.zapPolls.PollNoteEvent
import com.vitorpamplona.quartz.nip01Core.MetadataEvent
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
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.nip23LongContent.LongTextNoteEvent
import com.vitorpamplona.quartz.nip51Lists.PinListEvent
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesChatMessageEvent
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesEvent
import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
import com.vitorpamplona.quartz.nip54Wiki.WikiNoteEvent
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
import com.vitorpamplona.quartz.nip72ModCommunities.CommunityPostApprovalEvent
import com.vitorpamplona.quartz.nip72ModCommunities.approval.CommunityPostApprovalEvent
import com.vitorpamplona.quartz.nip84Highlights.HighlightEvent
import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent
import kotlinx.coroutines.Dispatchers
@@ -24,14 +24,14 @@ import com.vitorpamplona.ammolite.relays.ALL_FEED_TYPES
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.AudioHeaderEvent
import com.vitorpamplona.quartz.experimental.audio.AudioTrackEvent
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.KeyPair
import com.vitorpamplona.quartz.nip01Core.MetadataEvent
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
import com.vitorpamplona.quartz.nip01Core.tags.addressables.ATag
import com.vitorpamplona.quartz.nip01Core.toHexKey
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
@@ -44,19 +44,18 @@ 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.parse
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent
import com.vitorpamplona.quartz.nip28PublicChat.ChannelCreateEvent
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMetadataEvent
import com.vitorpamplona.quartz.nip30CustomEmoji.EmojiPackEvent
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.PeopleListEvent
import com.vitorpamplona.quartz.nip51Lists.PinListEvent
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesEvent
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
import com.vitorpamplona.quartz.nip54Wiki.WikiNoteEvent
import com.vitorpamplona.quartz.nip58Badges.BadgeDefinitionEvent
import com.vitorpamplona.quartz.nip72ModCommunities.CommunityDefinitionEvent
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
@@ -27,8 +27,8 @@ 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.SincePerRelayFilter
import com.vitorpamplona.quartz.nip28PublicChat.ChannelCreateEvent
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMetadataEvent
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelMetadataEvent
object NostrSingleChannelDataSource : AmethystNostrDataSource("SingleChannelFeed") {
private var channelsToWatch = setOf<Channel>()
@@ -35,12 +35,12 @@ 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.GitReplyEvent
import com.vitorpamplona.quartz.nip34Git.reply.GitReplyEvent
import com.vitorpamplona.quartz.nip35Torrents.TorrentCommentEvent
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesChatMessageEvent
import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent
import com.vitorpamplona.quartz.nip56Reports.ReportEvent
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
import com.vitorpamplona.quartz.nip72ModCommunities.CommunityPostApprovalEvent
import com.vitorpamplona.quartz.nip72ModCommunities.approval.CommunityPostApprovalEvent
import com.vitorpamplona.quartz.nip90Dvms.NIP90ContentDiscoveryResponseEvent
import com.vitorpamplona.quartz.nip90Dvms.NIP90StatusEvent
@@ -26,8 +26,8 @@ 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.MetadataEvent
import com.vitorpamplona.quartz.nip17Dm.ChatMessageRelayListEvent
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
@@ -24,12 +24,12 @@ import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.ammolite.relays.COMMON_FEED_TYPES
import com.vitorpamplona.ammolite.relays.TypedFilter
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
import com.vitorpamplona.quartz.experimental.audio.AudioHeaderEvent
import com.vitorpamplona.quartz.experimental.audio.AudioTrackEvent
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.profileGallery.ProfileGalleryEntryEvent
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
import com.vitorpamplona.quartz.nip01Core.MetadataEvent
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
@@ -49,7 +49,7 @@ import com.vitorpamplona.quartz.nip68Picture.PictureEvent
import com.vitorpamplona.quartz.nip71Video.VideoHorizontalEvent
import com.vitorpamplona.quartz.nip71Video.VideoVerticalEvent
import com.vitorpamplona.quartz.nip84Highlights.HighlightEvent
import com.vitorpamplona.quartz.nip89AppHandlers.AppRecommendationEvent
import com.vitorpamplona.quartz.nip89AppHandlers.recommendation.AppRecommendationEvent
object NostrUserProfileDataSource : AmethystNostrDataSource("UserProfileFeed") {
var user: User? = null
@@ -27,7 +27,7 @@ import com.vitorpamplona.ammolite.relays.FeedType
import com.vitorpamplona.ammolite.relays.TypedFilter
import com.vitorpamplona.ammolite.relays.filters.SinceAuthorPerRelayFilter
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
import com.vitorpamplona.quartz.experimental.nip95.FileStorageHeaderEvent
import com.vitorpamplona.quartz.experimental.nip95.header.FileStorageHeaderEvent
import com.vitorpamplona.quartz.nip68Picture.PictureEvent
import com.vitorpamplona.quartz.nip71Video.VideoHorizontalEvent
import com.vitorpamplona.quartz.nip71Video.VideoVerticalEvent
@@ -32,14 +32,14 @@ import com.vitorpamplona.amethyst.service.NostrUserProfileDataSource.user
import com.vitorpamplona.amethyst.service.lnurl.LightningAddressResolver
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.quartz.nip47WalletConnect.PayInvoiceErrorResponse
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesEvent
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
import com.vitorpamplona.quartz.nip57Zaps.splits.BaseZapSplitSetup
import com.vitorpamplona.quartz.nip57Zaps.splits.ZapSplitSetup
import com.vitorpamplona.quartz.nip57Zaps.splits.ZapSplitSetupLnAddress
import com.vitorpamplona.quartz.nip57Zaps.splits.zapSplitSetup
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
import com.vitorpamplona.quartz.nip89AppHandlers.AppDefinitionEvent
import com.vitorpamplona.quartz.nip89AppHandlers.definition.AppDefinitionEvent
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.Dispatchers
@@ -77,7 +77,7 @@ class ZapPaymentHandler(
if (!zapSplitSetup.isNullOrEmpty()) {
zapSplitSetup
} else if (noteEvent is LiveActivitiesEvent && noteEvent.hasHost()) {
noteEvent.hosts().map { ZapSplitSetup(it.pubKeyHex, it.relay, weight = 1.0) }
noteEvent.hosts().map { ZapSplitSetup(it.pubKey, it.relayHint, weight = 1.0) }
} else if (noteEvent is AppDefinitionEvent) {
val appLud16 = noteEvent.appMetaData()?.lnAddress()
if (appLud16 != null) {
@@ -37,18 +37,18 @@ import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nip01Core.tags.people.isTaggedUser
import com.vitorpamplona.quartz.nip01Core.tags.people.taggedUsers
import com.vitorpamplona.quartz.nip01Core.tags.people.taggedUserIds
import com.vitorpamplona.quartz.nip04Dm.PrivateDmEvent
import com.vitorpamplona.quartz.nip17Dm.ChatMessageEncryptedFileHeaderEvent
import com.vitorpamplona.quartz.nip17Dm.ChatMessageEvent
import com.vitorpamplona.quartz.nip17Dm.files.ChatMessageEncryptedFileHeaderEvent
import com.vitorpamplona.quartz.nip17Dm.messages.ChatMessageEvent
import com.vitorpamplona.quartz.nip19Bech32.toNpub
import com.vitorpamplona.quartz.nip21UriScheme.toNostrUri
import com.vitorpamplona.quartz.nip37Drafts.DraftEvent
import com.vitorpamplona.quartz.nip55AndroidSigner.NostrSignerExternal
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent
import com.vitorpamplona.quartz.nip59Giftwrap.GiftWrapEvent
import com.vitorpamplona.quartz.nip59Giftwrap.SealedRumorEvent
import com.vitorpamplona.quartz.nip59Giftwrap.seals.SealedRumorEvent
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
import com.vitorpamplona.quartz.utils.TimeUtils
import java.math.BigDecimal
import kotlin.coroutines.cancellation.CancellationException
@@ -137,7 +137,7 @@ class EventNotificationConsumer(
Log.d(TAG, "New Notification Arrived")
if (!LocalCache.justVerify(event)) return
val users = event.taggedUsers().map { LocalCache.getOrCreateUser(it) }
val users = event.taggedUserIds().map { LocalCache.getOrCreateUser(it) }
val npubs = users.map { it.pubkeyNpub() }.toSet()
// PushNotification Wraps don't include a receiver.
@@ -21,8 +21,8 @@
package com.vitorpamplona.amethyst.service.okhttp
import android.util.Log
import com.vitorpamplona.quartz.nip17Dm.AESGCM
import com.vitorpamplona.quartz.nip17Dm.NostrCipher
import com.vitorpamplona.quartz.nip17Dm.files.encryption.AESGCM
import com.vitorpamplona.quartz.nip17Dm.files.encryption.NostrCipher
import okhttp3.Interceptor
import okhttp3.Response
import okhttp3.ResponseBody.Companion.toResponseBody
@@ -21,7 +21,7 @@
package com.vitorpamplona.amethyst.service.okhttp
import android.util.LruCache
import com.vitorpamplona.quartz.nip17Dm.NostrCipher
import com.vitorpamplona.quartz.nip17Dm.files.encryption.NostrCipher
/**
* Neigther ExoPlayer, nor Coil support passing key and nonce to the Interceptor via
@@ -21,7 +21,7 @@
package com.vitorpamplona.amethyst.service.okhttp
import android.util.Log
import com.vitorpamplona.quartz.nip17Dm.NostrCipher
import com.vitorpamplona.quartz.nip17Dm.files.encryption.NostrCipher
import okhttp3.OkHttpClient
import java.net.InetSocketAddress
import java.net.Proxy
@@ -20,10 +20,10 @@
*/
package com.vitorpamplona.amethyst.service.okhttp
import com.vitorpamplona.quartz.nip01Core.relays.sockets.WebSocket
import com.vitorpamplona.quartz.nip01Core.relays.sockets.WebSocketListener
import com.vitorpamplona.quartz.nip01Core.relays.sockets.WebsocketBuilder
import com.vitorpamplona.quartz.nip01Core.relays.sockets.WebsocketBuilderFactory
import com.vitorpamplona.quartz.nip01Core.relay.sockets.WebSocket
import com.vitorpamplona.quartz.nip01Core.relay.sockets.WebSocketListener
import com.vitorpamplona.quartz.nip01Core.relay.sockets.WebsocketBuilder
import com.vitorpamplona.quartz.nip01Core.relay.sockets.WebsocketBuilderFactory
import okhttp3.Request
import okhttp3.Response
@@ -25,7 +25,7 @@ import android.net.Uri
import androidx.core.net.toUri
import com.vitorpamplona.quartz.CryptoUtils
import com.vitorpamplona.quartz.nip01Core.toHexKey
import com.vitorpamplona.quartz.nip17Dm.NostrCipher
import com.vitorpamplona.quartz.nip17Dm.files.encryption.NostrCipher
import java.io.File
class EncryptFilesResult(
@@ -32,7 +32,7 @@ import com.vitorpamplona.amethyst.commons.blurhash.toBlurhash
import com.vitorpamplona.amethyst.service.Blurhash
import com.vitorpamplona.quartz.CryptoUtils
import com.vitorpamplona.quartz.nip01Core.toHexKey
import com.vitorpamplona.quartz.nip94FileMetadata.Dimension
import com.vitorpamplona.quartz.nip94FileMetadata.tags.DimensionTag
import kotlinx.coroutines.CancellationException
import java.io.IOException
@@ -40,7 +40,7 @@ class FileHeader(
val mimeType: String?,
val hash: String,
val size: Int,
val dim: Dimension?,
val dim: DimensionTag?,
val blurHash: Blurhash?,
) {
class UnableToDownload(
@@ -51,7 +51,7 @@ class FileHeader(
suspend fun prepare(
fileUrl: String,
mimeType: String?,
dimPrecomputed: Dimension?,
dimPrecomputed: DimensionTag?,
forceProxy: Boolean,
): Result<FileHeader> =
try {
@@ -71,7 +71,7 @@ class FileHeader(
fun prepare(
data: ByteArray,
mimeType: String?,
dimPrecomputed: Dimension?,
dimPrecomputed: DimensionTag?,
): Result<FileHeader> =
try {
val hash = CryptoUtils.sha256(data).toHexKey()
@@ -82,7 +82,7 @@ class FileHeader(
val opt = BitmapFactory.Options()
opt.inPreferredConfig = Bitmap.Config.ARGB_8888
val mBitmap = BitmapFactory.decodeByteArray(data, 0, data.size, opt)
Pair(Blurhash(mBitmap.toBlurhash()), Dimension(mBitmap.width, mBitmap.height))
Pair(Blurhash(mBitmap.toBlurhash()), DimensionTag(mBitmap.width, mBitmap.height))
} else if (mimeType?.startsWith("video/") == true) {
val mediaMetadataRetriever = MediaMetadataRetriever()
mediaMetadataRetriever.setDataSource(ByteArrayMediaDataSource(data))
@@ -135,12 +135,12 @@ fun MediaMetadataRetriever.getThumbnail(): Bitmap? {
}
}
fun MediaMetadataRetriever.prepareDimFromVideo(): Dimension? {
fun MediaMetadataRetriever.prepareDimFromVideo(): DimensionTag? {
val width = prepareVideoWidth() ?: return null
val height = prepareVideoHeight() ?: return null
return if (width > 0 && height > 0) {
Dimension(width, height)
DimensionTag(width, height)
} else {
null
}
@@ -21,7 +21,7 @@
package com.vitorpamplona.amethyst.service.uploads
import com.vitorpamplona.quartz.nip01Core.HexKey
import com.vitorpamplona.quartz.nip94FileMetadata.Dimension
import com.vitorpamplona.quartz.nip94FileMetadata.tags.DimensionTag
data class MediaUploadResult(
// A publicly accessible URL to the BUD-01 GET /<sha256> endpoint (optionally with a file extension)
@@ -35,7 +35,7 @@ data class MediaUploadResult(
// upload time
val uploaded: Long? = null,
// dimensions
val dimension: Dimension? = null,
val dimension: DimensionTag? = null,
// magnet link
val magnet: String? = null,
// info hash
@@ -26,7 +26,7 @@ import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerName
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMediaProcessing
import com.vitorpamplona.quartz.nip17Dm.NostrCipher
import com.vitorpamplona.quartz.nip17Dm.files.encryption.NostrCipher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.joinAll
@@ -49,7 +49,7 @@ class MultiOrchestrator(
suspend fun upload(
scope: CoroutineScope,
alt: String?,
sensitiveContent: Boolean,
contentWarningReason: String?,
mediaQuality: CompressorQuality,
server: ServerName,
account: Account,
@@ -62,7 +62,7 @@ class MultiOrchestrator(
item.media.uri,
item.media.mimeType,
alt,
sensitiveContent,
contentWarningReason,
mediaQuality,
server,
account,
@@ -79,7 +79,7 @@ class MultiOrchestrator(
suspend fun uploadEncrypted(
scope: CoroutineScope,
alt: String?,
sensitiveContent: Boolean,
contentWarningReason: String?,
mediaQuality: CompressorQuality,
cipher: NostrCipher,
server: ServerName,
@@ -93,7 +93,7 @@ class MultiOrchestrator(
item.media.uri,
item.media.mimeType,
alt,
sensitiveContent,
contentWarningReason,
mediaQuality,
cipher,
server,
@@ -126,5 +126,5 @@ class MultiOrchestrator(
fun size() = list.size
fun get(index: Int) = list.get(index)
fun get(index: Int) = list[index]
}
@@ -29,7 +29,7 @@ import com.vitorpamplona.amethyst.service.uploads.blossom.BlossomUploader
import com.vitorpamplona.amethyst.service.uploads.nip96.Nip96Uploader
import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerName
import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerType
import com.vitorpamplona.quartz.nip17Dm.NostrCipher
import com.vitorpamplona.quartz.nip17Dm.files.encryption.NostrCipher
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.map
import kotlin.coroutines.cancellation.CancellationException
@@ -132,7 +132,7 @@ class UploadOrchestrator {
contentType: String?,
size: Long?,
alt: String?,
sensitiveContent: Boolean,
contentWarningReason: String?,
serverBaseUrl: String,
contentTypeForResult: String?,
originalHash: String?,
@@ -147,7 +147,7 @@ class UploadOrchestrator {
contentType = contentType,
size = size,
alt = alt,
sensitiveContent = if (sensitiveContent) "" else null,
sensitiveContent = contentWarningReason,
serverBaseUrl = serverBaseUrl,
forceProxy = account::shouldUseTorForNIP96,
onProgress = { percent: Float ->
@@ -175,7 +175,7 @@ class UploadOrchestrator {
contentType: String?,
size: Long?,
alt: String?,
sensitiveContent: Boolean,
contentWarningReason: String?,
serverBaseUrl: String,
contentTypeForResult: String?,
originalHash: String?,
@@ -191,7 +191,7 @@ class UploadOrchestrator {
contentType = contentType,
size = size,
alt = alt,
sensitiveContent = if (sensitiveContent) "" else null,
sensitiveContent = contentWarningReason,
serverBaseUrl = serverBaseUrl,
forceProxy = account::shouldUseTorForNIP96,
httpAuth = account::createBlossomUploadAuth,
@@ -292,7 +292,7 @@ class UploadOrchestrator {
uri: Uri,
mimeType: String?,
alt: String?,
sensitiveContent: Boolean,
contentWarningReason: String?,
compressionQuality: CompressorQuality,
server: ServerName,
account: Account,
@@ -302,8 +302,8 @@ class UploadOrchestrator {
return when (server.type) {
ServerType.NIP95 -> uploadNIP95(compressed.uri, compressed.contentType, null, null, context)
ServerType.NIP96 -> uploadNIP96(compressed.uri, compressed.contentType, compressed.size, alt, sensitiveContent, server.baseUrl, null, null, account, context)
ServerType.Blossom -> uploadBlossom(compressed.uri, compressed.contentType, compressed.size, alt, sensitiveContent, server.baseUrl, null, null, account, context)
ServerType.NIP96 -> uploadNIP96(compressed.uri, compressed.contentType, compressed.size, alt, contentWarningReason, server.baseUrl, null, null, account, context)
ServerType.Blossom -> uploadBlossom(compressed.uri, compressed.contentType, compressed.size, alt, contentWarningReason, server.baseUrl, null, null, account, context)
}
}
@@ -311,7 +311,7 @@ class UploadOrchestrator {
uri: Uri,
mimeType: String?,
alt: String?,
sensitiveContent: Boolean,
contentWarningReason: String?,
compressionQuality: CompressorQuality,
encrypt: NostrCipher,
server: ServerName,
@@ -323,8 +323,8 @@ class UploadOrchestrator {
return when (server.type) {
ServerType.NIP95 -> uploadNIP95(encrypted.uri, encrypted.contentType, compressed.contentType, encrypted.originalHash, context)
ServerType.NIP96 -> uploadNIP96(encrypted.uri, encrypted.contentType, encrypted.size, alt, sensitiveContent, server.baseUrl, compressed.contentType, encrypted.originalHash, account, context)
ServerType.Blossom -> uploadBlossom(encrypted.uri, encrypted.contentType, encrypted.size, alt, sensitiveContent, server.baseUrl, compressed.contentType, encrypted.originalHash, account, context)
ServerType.NIP96 -> uploadNIP96(encrypted.uri, encrypted.contentType, encrypted.size, alt, contentWarningReason, server.baseUrl, compressed.contentType, encrypted.originalHash, account, context)
ServerType.Blossom -> uploadBlossom(encrypted.uri, encrypted.contentType, encrypted.size, alt, contentWarningReason, server.baseUrl, compressed.contentType, encrypted.originalHash, account, context)
}
}
}
@@ -34,13 +34,12 @@ import com.vitorpamplona.amethyst.service.checkNotInMainThread
import com.vitorpamplona.amethyst.service.okhttp.HttpClientManager
import com.vitorpamplona.amethyst.service.uploads.MediaUploadResult
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.quartz.nip36SensitiveContent.CONTENT_WARNING
import com.vitorpamplona.quartz.nip94FileMetadata.Dimension
import com.vitorpamplona.quartz.nip96FileStorage.AuthToken
import com.vitorpamplona.quartz.nip96FileStorage.Nip96Result
import com.vitorpamplona.quartz.nip96FileStorage.PartialEvent
import com.vitorpamplona.quartz.nip96FileStorage.ResultParser
import com.vitorpamplona.quartz.nip96FileStorage.ServerInfo
import com.vitorpamplona.quartz.nip36SensitiveContent.ContentWarningTag
import com.vitorpamplona.quartz.nip94FileMetadata.tags.DimensionTag
import com.vitorpamplona.quartz.nip96FileStorage.actions.DeleteResult
import com.vitorpamplona.quartz.nip96FileStorage.actions.PartialEvent
import com.vitorpamplona.quartz.nip96FileStorage.actions.UploadResult
import com.vitorpamplona.quartz.nip96FileStorage.info.ServerInfo
import com.vitorpamplona.quartz.nip98HttpAuth.HTTPAuthorizationEvent
import kotlinx.coroutines.delay
import okhttp3.MediaType.Companion.toMediaType
@@ -153,7 +152,7 @@ class Nip96Uploader {
.addFormDataPart("size", length.toString())
.also { body ->
alt?.ifBlank { null }?.let { body.addFormDataPart("alt", it) }
sensitiveContent?.let { body.addFormDataPart(CONTENT_WARNING, it) }
sensitiveContent?.let { body.addFormDataPart(ContentWarningTag.TAG_NAME, it) }
contentType?.let { body.addFormDataPart("content_type", it) }
}.addFormDataPart(
"file",
@@ -169,7 +168,7 @@ class Nip96Uploader {
},
).build()
httpAuth(server.apiUrl, "POST", null)?.let { requestBuilder.addHeader("Authorization", AuthToken().encodeAuth(it)) }
httpAuth(server.apiUrl, "POST", null)?.let { requestBuilder.addHeader("Authorization", it.toAuthToken()) }
requestBuilder
.addHeader("User-Agent", "Amethyst/${BuildConfig.VERSION_NAME}")
@@ -181,7 +180,7 @@ class Nip96Uploader {
client.newCall(request).execute().use { response ->
if (response.isSuccessful) {
response.body.use { body ->
val result = ResultParser().parseResults(body.string())
val result = UploadResult.parse(body.string())
if (!result.processingUrl.isNullOrBlank()) {
return waitProcessing(result, server, forceProxy, onProgress)
} else if (result.status == "success") {
@@ -244,7 +243,7 @@ class Nip96Uploader {
?.firstOrNull { it.size > 1 && it[0] == "dim" }
?.get(1)
?.ifBlank { null }
?.let { Dimension.parse(it) }
?.let { DimensionTag.parse(it) }
val magnet =
nip96.tags
?.firstOrNull { it.size > 1 && it[0] == "magnet" }
@@ -265,7 +264,7 @@ class Nip96Uploader {
contentType: String?,
server: ServerInfo,
forceProxy: (String) -> Boolean,
httpAuth: (String, String, ByteArray?) -> HTTPAuthorizationEvent,
httpAuth: (String, String, ByteArray?) -> HTTPAuthorizationEvent?,
context: Context,
): Boolean {
val extension =
@@ -275,7 +274,7 @@ class Nip96Uploader {
val requestBuilder = Request.Builder()
httpAuth(server.apiUrl, "DELETE", null)?.let { requestBuilder.addHeader("Authorization", AuthToken().encodeAuth(it)) }
httpAuth(server.apiUrl, "DELETE", null)?.let { requestBuilder.addHeader("Authorization", it.toAuthToken()) }
val request =
requestBuilder
@@ -287,7 +286,7 @@ class Nip96Uploader {
client.newCall(request).execute().use { response ->
if (response.isSuccessful) {
response.body.use { body ->
val result = ResultParser().parseDeleteResults(body.string())
val result = DeleteResult.parse(body.string())
return result.status == "success"
}
} else {
@@ -302,7 +301,7 @@ class Nip96Uploader {
}
private suspend fun waitProcessing(
result: Nip96Result,
result: UploadResult,
server: ServerInfo,
forceProxy: (String) -> Boolean,
onProgress: (percentage: Float) -> Unit,
@@ -324,7 +323,7 @@ class Nip96Uploader {
val client = HttpClientManager.getHttpClient(forceProxy(procUrl))
client.newCall(request).execute().use {
if (it.isSuccessful) {
it.body.use { currentResult = ResultParser().parseResults(it.string()) }
it.body.use { currentResult = UploadResult.parse(it.string()) }
}
}
@@ -23,8 +23,8 @@ package com.vitorpamplona.amethyst.service.uploads.nip96
import android.util.Log
import com.vitorpamplona.amethyst.service.checkNotInMainThread
import com.vitorpamplona.amethyst.service.okhttp.HttpClientManager
import com.vitorpamplona.quartz.nip96FileStorage.ServerInfo
import com.vitorpamplona.quartz.nip96FileStorage.ServerInfoParser
import com.vitorpamplona.quartz.nip96FileStorage.info.ServerInfo
import com.vitorpamplona.quartz.nip96FileStorage.info.ServerInfoParser
import kotlinx.coroutines.CancellationException
import okhttp3.Request
@@ -55,12 +55,12 @@ 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.Note
import com.vitorpamplona.quartz.nip28PublicChat.ChannelCreateEvent
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMessageEvent
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMetadataEvent
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.nip47WalletConnect.Nip47WalletConnect
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesEvent
import com.vitorpamplona.quartz.nip72ModCommunities.CommunityDefinitionEvent
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
@@ -46,10 +46,19 @@ import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMediaProcessing
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.ammolite.relays.RelaySetupInfo
import com.vitorpamplona.quartz.experimental.nip95.FileStorageEvent
import com.vitorpamplona.quartz.experimental.nip95.FileStorageHeaderEvent
import com.vitorpamplona.quartz.experimental.nip95.data.FileStorageEvent
import com.vitorpamplona.quartz.experimental.nip95.header.FileStorageHeaderEvent
import com.vitorpamplona.quartz.nip92IMeta.IMetaTag
import com.vitorpamplona.quartz.nip92IMeta.IMetaTagBuilder
import com.vitorpamplona.quartz.nip94FileMetadata.alt
import com.vitorpamplona.quartz.nip94FileMetadata.blurhash
import com.vitorpamplona.quartz.nip94FileMetadata.dims
import com.vitorpamplona.quartz.nip94FileMetadata.hash
import com.vitorpamplona.quartz.nip94FileMetadata.magnet
import com.vitorpamplona.quartz.nip94FileMetadata.mimeType
import com.vitorpamplona.quartz.nip94FileMetadata.originalHash
import com.vitorpamplona.quartz.nip94FileMetadata.sensitiveContent
import com.vitorpamplona.quartz.nip94FileMetadata.size
import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -165,7 +174,7 @@ open class EditPostViewModel : ViewModel() {
myMultiOrchestrator.upload(
viewModelScope,
alt,
sensitiveContent,
if (sensitiveContent) "" else null,
MediaCompressor.intToCompressorQuality(mediaQuality),
server,
myAccount,
@@ -175,7 +184,12 @@ open class EditPostViewModel : ViewModel() {
if (results.allGood) {
results.successful.forEach { state ->
if (state.result is UploadOrchestrator.OrchestratorResult.NIP95Result) {
account?.createNip95(state.result.bytes, headerInfo = state.result.fileHeader, alt, sensitiveContent) { nip95 ->
account?.createNip95(
state.result.bytes,
headerInfo = state.result.fileHeader,
alt,
if (sensitiveContent) "" else null,
) { nip95 ->
nip95attachments = nip95attachments + nip95
val note = nip95.let { it1 -> account?.consumeNip95(it1.first, it1.second) }
@@ -26,6 +26,10 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.PublicChatChannel
import com.vitorpamplona.quartz.nip01Core.EventHintBundle
import com.vitorpamplona.quartz.nip01Core.tags.events.ETag
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelMetadataEvent
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -53,19 +57,44 @@ class NewChannelViewModel : ViewModel() {
fun create() {
viewModelScope.launch(Dispatchers.IO) {
account?.let { account ->
if (originalChannel == null) {
account.sendCreateNewChannel(
channelName.value.text,
channelDescription.value.text,
channelPicture.value.text,
)
val channel = originalChannel
if (channel == null) {
val template =
ChannelCreateEvent.build(
channelName.value.text,
channelDescription.value.text,
channelPicture.value.text,
null,
)
account.sendCreateNewChannel(template)
} else {
account.sendChangeChannel(
channelName.value.text,
channelDescription.value.text,
channelPicture.value.text,
originalChannel!!,
)
val event = channel.event
val template =
if (event != null) {
val hint = EventHintBundle<ChannelCreateEvent>(event, channel.relays().firstOrNull())
ChannelMetadataEvent.build(
channelName.value.text,
channelDescription.value.text,
channelPicture.value.text,
null,
hint,
)
} else {
val eTag = ETag(channel.idHex, channel.relays().firstOrNull())
ChannelMetadataEvent.build(
channelName.value.text,
channelDescription.value.text,
channelPicture.value.text,
null,
eTag,
)
}
account.sendChangeChannel(template)
}
}
@@ -99,7 +99,7 @@ open class NewMediaModel : ViewModel() {
myMultiOrchestrator.upload(
viewModelScope,
caption,
sensitiveContent,
if (sensitiveContent) "" else null,
MediaCompressor.intToCompressorQuality(mediaQualitySlider),
serverToUse,
myAccount,
@@ -140,7 +140,7 @@ open class NewMediaModel : ViewModel() {
viewModelScope.launch(Dispatchers.IO) {
withTimeoutOrNull(30000) {
suspendCancellableCoroutine { continuation ->
account?.createNip95(it.bytes, headerInfo = it.fileHeader, caption, sensitiveContent) { nip95 ->
account?.createNip95(it.bytes, headerInfo = it.fileHeader, caption, if (sensitiveContent) "" else null) { nip95 ->
account?.consumeAndSendNip95(nip95.first, nip95.second, relayList)
continuation.resume(true)
}
@@ -160,7 +160,7 @@ open class NewMediaModel : ViewModel() {
it.magnet,
it.fileHeader,
caption,
sensitiveContent,
if (sensitiveContent) "" else null,
it.uploadedHash,
relayList,
) {
@@ -180,7 +180,7 @@ open class NewMediaModel : ViewModel() {
account?.sendAllAsOnePictureEvent(
imageUrls,
caption,
sensitiveContent,
if (sensitiveContent) "" else null,
relayList,
) {
continuation.resume(true)
@@ -24,7 +24,7 @@ import androidx.compose.runtime.Immutable
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.quartz.nip01Core.HexKey
import com.vitorpamplona.quartz.nip01Core.KeyPair
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
import com.vitorpamplona.quartz.nip19Bech32.Nip19Parser
import com.vitorpamplona.quartz.nip19Bech32.bech32.Bech32
import com.vitorpamplona.quartz.nip19Bech32.bech32.bechToBytes
@@ -52,7 +52,7 @@ fun NewPollClosing(pollViewModel: NewPostViewModel) {
pollViewModel.isValidClosedAt.value = true
if (text.isNotEmpty()) {
try {
val int = text.toInt()
val int = text.toLong()
if (int < 0) {
pollViewModel.isValidClosedAt.value = false
} else {
File diff suppressed because it is too large Load Diff
@@ -53,7 +53,7 @@ import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
import com.vitorpamplona.amethyst.ui.theme.imageModifier
import com.vitorpamplona.quartz.nip01Core.relays.RelayStat
import com.vitorpamplona.quartz.nip01Core.relay.RelayStat
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -53,7 +53,7 @@ import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
import com.vitorpamplona.amethyst.ui.theme.imageModifier
import com.vitorpamplona.quartz.nip01Core.relays.RelayStat
import com.vitorpamplona.quartz.nip01Core.relay.RelayStat
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -61,7 +61,7 @@ import com.vitorpamplona.amethyst.ui.theme.RowColSpacing
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
import com.vitorpamplona.amethyst.ui.theme.grayText
import com.vitorpamplona.ammolite.relays.Constants
import com.vitorpamplona.quartz.nip01Core.relays.RelayStat
import com.vitorpamplona.quartz.nip01Core.relay.RelayStat
@Composable
fun AllRelayListView(
@@ -24,7 +24,7 @@ import androidx.compose.runtime.Immutable
import com.vitorpamplona.ammolite.relays.FeedType
import com.vitorpamplona.ammolite.relays.RelayBriefInfoCache
import com.vitorpamplona.quartz.nip01Core.HexKey
import com.vitorpamplona.quartz.nip01Core.relays.RelayStat
import com.vitorpamplona.quartz.nip01Core.relay.RelayStat
@Immutable
data class BasicRelaySetupInfo(
@@ -94,7 +94,7 @@ import com.vitorpamplona.ammolite.relays.Constants
import com.vitorpamplona.ammolite.relays.Constants.activeTypesGlobalChats
import com.vitorpamplona.ammolite.relays.FeedType
import com.vitorpamplona.ammolite.relays.RelayBriefInfoCache
import com.vitorpamplona.quartz.nip01Core.relays.RelayStat
import com.vitorpamplona.quartz.nip01Core.relay.RelayStat
import com.vitorpamplona.quartz.nip65RelayList.RelayUrlFormatter
import kotlinx.coroutines.launch
@@ -63,7 +63,7 @@ import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
import com.vitorpamplona.amethyst.ui.theme.allGoodColor
import com.vitorpamplona.amethyst.ui.theme.largeRelayIconModifier
import com.vitorpamplona.ammolite.relays.COMMON_FEED_TYPES
import com.vitorpamplona.quartz.nip01Core.relays.RelayStat
import com.vitorpamplona.quartz.nip01Core.relay.RelayStat
@OptIn(ExperimentalLayoutApi::class)
@Composable
@@ -40,7 +40,7 @@ import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
import com.vitorpamplona.amethyst.ui.theme.Size10dp
import com.vitorpamplona.amethyst.ui.theme.placeholderText
import com.vitorpamplona.quartz.nip01Core.relays.RelayStat
import com.vitorpamplona.quartz.nip01Core.relay.RelayStat
import com.vitorpamplona.quartz.nip65RelayList.RelayUrlFormatter
@Composable
@@ -77,7 +77,7 @@ 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.toNIP19
import com.vitorpamplona.quartz.nip28PublicChat.ChannelCreateEvent
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent
import com.vitorpamplona.quartz.nip30CustomEmoji.CustomEmoji
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.ImmutableMap
@@ -119,8 +119,8 @@ import com.vitorpamplona.amethyst.ui.theme.Size75dp
import com.vitorpamplona.amethyst.ui.theme.VolumeBottomIconSize
import com.vitorpamplona.amethyst.ui.theme.imageModifier
import com.vitorpamplona.amethyst.ui.theme.videoGalleryModifier
import com.vitorpamplona.quartz.nip94FileMetadata.Dimension
import kotlinx.collections.immutable.ImmutableList
import com.vitorpamplona.quartz.experimental.audio.header.tags.WaveformTag
import com.vitorpamplona.quartz.nip94FileMetadata.tags.DimensionTag
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
@@ -208,10 +208,10 @@ fun VideoView(
roundedCorner: Boolean,
gallery: Boolean = false,
contentScale: ContentScale,
waveform: ImmutableList<Int>? = null,
waveform: WaveformTag? = null,
artworkUri: String? = null,
authorName: String? = null,
dimensions: Dimension? = null,
dimensions: DimensionTag? = null,
blurhash: String? = null,
nostrUriCallback: String? = null,
onDialog: ((Boolean) -> Unit)? = null,
@@ -239,10 +239,10 @@ fun VideoView(
thumb: VideoThumb? = null,
borderModifier: Modifier,
contentScale: ContentScale,
waveform: ImmutableList<Int>? = null,
waveform: WaveformTag? = null,
artworkUri: String? = null,
authorName: String? = null,
dimensions: Dimension? = null,
dimensions: DimensionTag? = null,
blurhash: String? = null,
nostrUriCallback: String? = null,
onDialog: ((Boolean) -> Unit)? = null,
@@ -355,7 +355,7 @@ fun VideoViewInner(
showControls: Boolean = true,
contentScale: ContentScale,
borderModifier: Modifier,
waveform: ImmutableList<Int>? = null,
waveform: WaveformTag? = null,
artworkUri: String? = null,
authorName: String? = null,
nostrUriCallback: String? = null,
@@ -732,7 +732,7 @@ private fun RenderVideoPlayer(
showControls: Boolean = true,
contentScale: ContentScale,
nostrUriCallback: String?,
waveform: ImmutableList<Int>? = null,
waveform: WaveformTag? = null,
keepPlaying: MutableState<Boolean>,
automaticallyStartPlayback: State<Boolean>,
activeOnScreen: MutableState<Boolean>,
@@ -863,7 +863,7 @@ private fun pollCurrentDuration(controller: MediaController) =
@Composable
fun Waveform(
waveform: ImmutableList<Int>,
waveform: WaveformTag,
controller: MediaController,
modifier: Modifier,
) {
@@ -897,13 +897,13 @@ fun Waveform(
@Composable
fun DrawWaveform(
waveform: ImmutableList<Int>,
waveform: WaveformTag,
waveformProgress: MutableFloatState,
modifier: Modifier,
) {
AudioWaveformReadOnly(
modifier = modifier.padding(start = 10.dp, end = 10.dp),
amplitudes = waveform,
amplitudes = waveform.wave,
progress = waveformProgress.floatValue,
progressBrush =
Brush.infiniteLinearGradient(
@@ -97,7 +97,7 @@ import com.vitorpamplona.quartz.CryptoUtils
import com.vitorpamplona.quartz.nip01Core.toHexKey
import com.vitorpamplona.quartz.nip19Bech32.Nip19Parser
import com.vitorpamplona.quartz.nip19Bech32.entities.NEvent
import com.vitorpamplona.quartz.nip94FileMetadata.Dimension
import com.vitorpamplona.quartz.nip94FileMetadata.tags.DimensionTag
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.Dispatchers
@@ -514,7 +514,7 @@ fun ShowHash(content: MediaUrlContent) {
verifiedHash?.let { HashVerificationSymbol(it) }
}
fun aspectRatio(dim: Dimension?): Float? {
fun aspectRatio(dim: DimensionTag?): Float? {
if (dim == null) return null
return dim.width.toFloat() / dim.height.toFloat()
@@ -641,7 +641,7 @@ fun ShareImageAction(
videoUri: String?,
postNostrUri: String?,
blurhash: String?,
dim: Dimension?,
dim: DimensionTag?,
hash: String?,
mimeType: String?,
onDismiss: () -> Unit,
@@ -48,8 +48,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.LoadedBechLink
import com.vitorpamplona.amethyst.ui.theme.Font17SP
import com.vitorpamplona.amethyst.ui.theme.Size17Modifier
import com.vitorpamplona.quartz.nip02FollowList.EmptyTagList
import com.vitorpamplona.quartz.nip02FollowList.ImmutableListOfLists
import com.vitorpamplona.quartz.nip19Bech32.entities.NAddress
import com.vitorpamplona.quartz.nip19Bech32.entities.NEmbed
import com.vitorpamplona.quartz.nip19Bech32.entities.NEvent
@@ -57,11 +55,12 @@ 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.nip92IMeta.IMetaTag
import kotlinx.coroutines.runBlocking
class MarkdownMediaRenderer(
val startOfText: String,
val tags: ImmutableListOfLists<String>?,
val imetaByUrl: Map<String, IMetaTag>,
val canPreview: Boolean,
val quotesLeft: Int,
val backgroundColor: MutableState<Color>,
@@ -94,7 +93,7 @@ class MarkdownMediaRenderer(
val content =
parser.createMediaContent(
fullUrl = uri,
eventTags = tags ?: EmptyTagList,
eventTags = imetaByUrl,
description = title?.ifEmpty { null } ?: startOfText,
) ?: MediaUrlImage(url = uri, description = title?.ifEmpty { null } ?: startOfText)
@@ -116,7 +115,7 @@ class MarkdownMediaRenderer(
uri: String,
richTextStringBuilder: RichTextString.Builder,
) {
val content = parser.createMediaContent(uri, eventTags = tags ?: EmptyTagList, startOfText, callbackUri)
val content = parser.createMediaContent(uri, imetaByUrl, startOfText, callbackUri)
if (canPreview) {
if (content != null) {
@@ -49,10 +49,11 @@ import com.vitorpamplona.amethyst.ui.theme.MarkdownTextStyle
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonRow
import com.vitorpamplona.amethyst.ui.theme.markdownStyle
import com.vitorpamplona.amethyst.ui.uriToRoute
import com.vitorpamplona.quartz.nip01Core.MetadataEvent
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
import com.vitorpamplona.quartz.nip02FollowList.EmptyTagList
import com.vitorpamplona.quartz.nip02FollowList.ImmutableListOfLists
import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent
import com.vitorpamplona.quartz.nip92IMeta.imetasByUrl
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
@@ -93,7 +94,7 @@ fun RenderContentAsMarkdown(
remember(content) {
MarkdownMediaRenderer(
startOfText = content.take(100),
tags = tags,
imetaByUrl = tags?.lists?.imetasByUrl() ?: emptyMap(),
canPreview = canPreview,
quotesLeft = quotesLeft,
backgroundColor = backgroundColor,
@@ -22,7 +22,7 @@ package com.vitorpamplona.amethyst.ui.dal
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.quartz.nip17Dm.ChatroomKey
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey
class ChatroomFeedFilter(
val withUser: ChatroomKey,
@@ -25,9 +25,9 @@ import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.ui.actions.relays.updated
import com.vitorpamplona.quartz.nip01Core.tags.events.taggedEventIds
import com.vitorpamplona.quartz.nip17Dm.ChatroomKey
import com.vitorpamplona.quartz.nip17Dm.ChatroomKeyable
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMessageEvent
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKeyable
import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
class ChatroomListKnownFeedFilter(
val account: Account,
@@ -24,8 +24,8 @@ import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.ui.actions.relays.updated
import com.vitorpamplona.quartz.nip04Dm.PrivateDmEvent
import com.vitorpamplona.quartz.nip17Dm.ChatroomKey
import com.vitorpamplona.quartz.nip17Dm.ChatroomKeyable
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKeyable
class ChatroomListNewFeedFilter(
val account: Account,
@@ -26,7 +26,7 @@ import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.quartz.nip01Core.HexKey
import com.vitorpamplona.quartz.nip01Core.tags.addressables.isTaggedAddressableNote
import com.vitorpamplona.quartz.nip72ModCommunities.CommunityPostApprovalEvent
import com.vitorpamplona.quartz.nip72ModCommunities.approval.CommunityPostApprovalEvent
class CommunityFeedFilter(
val note: AddressableNote,
@@ -24,8 +24,8 @@ import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.PublicChatChannel
import com.vitorpamplona.quartz.nip28PublicChat.ChannelCreateEvent
import com.vitorpamplona.quartz.nip28PublicChat.IsInPublicChatChannel
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent
import com.vitorpamplona.quartz.nip28PublicChat.base.IsInPublicChatChannel
import com.vitorpamplona.quartz.nip51Lists.MuteListEvent
import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent
@@ -85,7 +85,7 @@ open class DiscoverChatFeedFilter(
null
}
} else if (noteEvent is IsInPublicChatChannel) {
val channel = noteEvent.channel()?.let { LocalCache.checkGetOrCreateNote(it) }
val channel = noteEvent.channelId()?.let { LocalCache.checkGetOrCreateNote(it) }
val channelEvent = channel?.event
if (channel != null &&
@@ -27,8 +27,8 @@ import com.vitorpamplona.quartz.nip01Core.tags.addressables.ATag
import com.vitorpamplona.quartz.nip19Bech32.parseAtagUnckecked
import com.vitorpamplona.quartz.nip51Lists.MuteListEvent
import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent
import com.vitorpamplona.quartz.nip72ModCommunities.CommunityDefinitionEvent
import com.vitorpamplona.quartz.nip72ModCommunities.CommunityPostApprovalEvent
import com.vitorpamplona.quartz.nip72ModCommunities.approval.CommunityPostApprovalEvent
import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent
open class DiscoverCommunityFeedFilter(
val account: Account,
@@ -26,10 +26,8 @@ import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.ParticipantListBuilder
import com.vitorpamplona.quartz.nip51Lists.MuteListEvent
import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesEvent
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesEvent.Companion.STATUS_ENDED
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesEvent.Companion.STATUS_LIVE
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesEvent.Companion.STATUS_PLANNED
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.tags.StatusTag
open class DiscoverLiveFeedFilter(
val account: Account,
@@ -93,9 +91,9 @@ open class DiscoverLiveFeedFilter(
fun convertStatusToOrder(status: String?): Int =
when (status) {
STATUS_LIVE -> 2
STATUS_PLANNED -> 1
STATUS_ENDED -> 0
StatusTag.STATUS.LIVE.code -> 2
StatusTag.STATUS.PLANNED.code -> 1
StatusTag.STATUS.ENDED.code -> 0
else -> 0
}
}
@@ -25,7 +25,7 @@ import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.quartz.nip51Lists.MuteListEvent
import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent
import com.vitorpamplona.quartz.nip89AppHandlers.AppDefinitionEvent
import com.vitorpamplona.quartz.nip89AppHandlers.definition.AppDefinitionEvent
import com.vitorpamplona.quartz.utils.TimeUtils
open class DiscoverNIP89FeedFilter(
@@ -76,7 +76,7 @@ open class DiscoverNIP89FeedFilter(
val filterParams = buildFilterParams(account)
return noteEvent.appMetaData()?.subscription != true &&
filterParams.match(noteEvent) &&
noteEvent.includeKind("5300") &&
noteEvent.includeKind(5300) &&
noteEvent.createdAt > TimeUtils.now() - lastAnnounced // && params.match(noteEvent)
}
@@ -31,7 +31,7 @@ import com.vitorpamplona.quartz.nip01Core.tags.hashtags.isTaggedHashes
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
import com.vitorpamplona.quartz.nip51Lists.MuteListEvent
import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesEvent
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
import com.vitorpamplona.quartz.utils.TimeUtils
class FilterByListParams(
@@ -23,13 +23,13 @@ package com.vitorpamplona.amethyst.ui.dal
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.quartz.experimental.audio.AudioHeaderEvent
import com.vitorpamplona.quartz.experimental.audio.header.AudioHeaderEvent
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
import com.vitorpamplona.quartz.nip01Core.tags.geohash.isTaggedGeoHash
import com.vitorpamplona.quartz.nip04Dm.PrivateDmEvent
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMessageEvent
import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
import com.vitorpamplona.quartz.nip54Wiki.WikiNoteEvent
class GeoHashFeedFilter(
@@ -23,7 +23,7 @@ package com.vitorpamplona.amethyst.ui.dal
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.quartz.experimental.audio.AudioHeaderEvent
import com.vitorpamplona.quartz.experimental.audio.header.AudioHeaderEvent
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.isTaggedHash
import com.vitorpamplona.quartz.nip04Dm.PrivateDmEvent
@@ -31,7 +31,7 @@ import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMessageEvent
import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
import com.vitorpamplona.quartz.nip54Wiki.WikiNoteEvent
class HashtagFeedFilter(
@@ -27,10 +27,10 @@ import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMessageEvent
import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
import com.vitorpamplona.quartz.nip51Lists.MuteListEvent
import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesChatMessageEvent
import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent
class HomeConversationsFeedFilter(
val account: Account,
@@ -23,8 +23,8 @@ package com.vitorpamplona.amethyst.ui.dal
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.quartz.experimental.audio.AudioHeaderEvent
import com.vitorpamplona.quartz.experimental.audio.AudioTrackEvent
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.zapPolls.PollNoteEvent
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
@@ -23,23 +23,25 @@ package com.vitorpamplona.amethyst.ui.dal
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.quartz.experimental.forks.forkFromVersion
import com.vitorpamplona.quartz.experimental.forks.isForkFromAddressWithPubkey
import com.vitorpamplona.quartz.nip01Core.HexKey
import com.vitorpamplona.quartz.nip01Core.tags.people.isTaggedUser
import com.vitorpamplona.quartz.nip10Notes.BaseTextNoteEvent
import com.vitorpamplona.quartz.nip10Notes.BaseThreadedEvent
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
import com.vitorpamplona.quartz.nip28PublicChat.ChannelCreateEvent
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMetadataEvent
import com.vitorpamplona.quartz.nip34Git.GitIssueEvent
import com.vitorpamplona.quartz.nip34Git.GitPatchEvent
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelMetadataEvent
import com.vitorpamplona.quartz.nip34Git.issue.GitIssueEvent
import com.vitorpamplona.quartz.nip34Git.patch.GitPatchEvent
import com.vitorpamplona.quartz.nip51Lists.MuteListEvent
import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent
import com.vitorpamplona.quartz.nip58Badges.BadgeDefinitionEvent
import com.vitorpamplona.quartz.nip58Badges.BadgeProfilesEvent
import com.vitorpamplona.quartz.nip59Giftwrap.GiftWrapEvent
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
import com.vitorpamplona.quartz.nip84Highlights.HighlightEvent
import com.vitorpamplona.quartz.nip90Dvms.NIP90ContentDiscoveryRequestEvent
import com.vitorpamplona.quartz.nip90Dvms.NIP90ContentDiscoveryResponseEvent
@@ -138,13 +140,13 @@ class NotificationFeedFilter(
return true
}
if (event is BaseTextNoteEvent) {
if (event is BaseThreadedEvent) {
if (note.replyTo?.any { it.author?.pubkeyHex == authorHex } == true) return true
val isAuthoredPostCited = event.findCitations().any { LocalCache.getNoteIfExists(it)?.author?.pubkeyHex == authorHex }
val isAuthorDirectlyCited = event.citedUsers().contains(authorHex)
val isAuthorOfAFork =
event.isForkFromAddressWithPubkey(authorHex) || (event.forkFromVersion()?.let { LocalCache.getNoteIfExists(it)?.author?.pubkeyHex == authorHex } == true)
event.isForkFromAddressWithPubkey(authorHex) || (event.forkFromVersion()?.let { LocalCache.getNoteIfExists(it.eventId)?.author?.pubkeyHex == authorHex } == true)
return isAuthoredPostCited || isAuthorDirectlyCited || isAuthorOfAFork
}
@@ -23,7 +23,8 @@ package com.vitorpamplona.amethyst.ui.dal
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.quartz.nip89AppHandlers.AppRecommendationEvent
import com.vitorpamplona.quartz.nip01Core.tags.addressables.ATag
import com.vitorpamplona.quartz.nip89AppHandlers.recommendation.AppRecommendationEvent
class UserProfileAppRecommendationsFeedFilter(
val user: User,
@@ -47,7 +48,7 @@ class UserProfileAppRecommendationsFeedFilter(
val noteEvent = it.event
if (noteEvent is AppRecommendationEvent) {
if (noteEvent.pubKey == user.pubkeyHex) {
return noteEvent.recommendations().map { LocalCache.getOrCreateAddressableNote(it) }
return noteEvent.recommendations().map { LocalCache.getOrCreateAddressableNote(ATag(it.address)) }
}
}
@@ -27,9 +27,9 @@ import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMessageEvent
import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
import com.vitorpamplona.quartz.nip35Torrents.TorrentCommentEvent
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesChatMessageEvent
import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent
class UserProfileConversationsFeedFilter(
val user: User,
@@ -25,8 +25,8 @@ import com.vitorpamplona.amethyst.model.AddressableNote
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.quartz.experimental.audio.AudioHeaderEvent
import com.vitorpamplona.quartz.experimental.audio.AudioTrackEvent
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.zapPolls.PollNoteEvent
import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
@@ -26,7 +26,7 @@ import com.vitorpamplona.amethyst.model.AddressableNote
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.service.SUPPORTED_VIDEO_FEED_MIME_TYPES_SET
import com.vitorpamplona.quartz.experimental.nip95.FileStorageHeaderEvent
import com.vitorpamplona.quartz.experimental.nip95.header.FileStorageHeaderEvent
import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
import com.vitorpamplona.quartz.nip51Lists.MuteListEvent
import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent
@@ -27,15 +27,15 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.quartz.nip01Core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip17Dm.ChatroomKey
import com.vitorpamplona.quartz.nip17Dm.ChatroomKeyable
import com.vitorpamplona.quartz.nip28PublicChat.ChannelCreateEvent
import com.vitorpamplona.quartz.nip28PublicChat.IsInPublicChatChannel
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKeyable
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent
import com.vitorpamplona.quartz.nip28PublicChat.base.IsInPublicChatChannel
import com.vitorpamplona.quartz.nip37Drafts.DraftEvent
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesChatMessageEvent
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesEvent
import com.vitorpamplona.quartz.nip72ModCommunities.CommunityDefinitionEvent
import com.vitorpamplona.quartz.nip89AppHandlers.AppDefinitionEvent
import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent
import com.vitorpamplona.quartz.nip89AppHandlers.definition.AppDefinitionEvent
import kotlinx.collections.immutable.persistentSetOf
import java.net.URLEncoder
@@ -56,11 +56,11 @@ fun routeFor(
val innerEvent = noteEvent.preCachedDraft(loggedIn.pubkeyHex)
if (innerEvent is IsInPublicChatChannel) {
innerEvent.channel()?.let {
innerEvent.channelId()?.let {
return "Channel/$it"
}
} else if (innerEvent is LiveActivitiesEvent) {
innerEvent.address().toTag().let {
innerEvent.aTag().toTag().let {
return "Channel/${URLEncoder.encode(it, "utf-8")}"
}
} else if (innerEvent is LiveActivitiesChatMessageEvent) {
@@ -72,20 +72,20 @@ fun routeFor(
loggedIn.createChatroom(room)
return "Room/${room.hashCode()}"
} else if (innerEvent is AddressableEvent) {
return "Note/${URLEncoder.encode(noteEvent.address().toTag(), "utf-8")}"
return "Note/${URLEncoder.encode(noteEvent.aTag().toTag(), "utf-8")}"
} else {
return "Note/${URLEncoder.encode(noteEvent.id, "utf-8")}"
}
} else if (noteEvent is AppDefinitionEvent) {
return "ContentDiscovery/${noteEvent.id}"
} else if (noteEvent is IsInPublicChatChannel) {
noteEvent.channel()?.let {
noteEvent.channelId()?.let {
return "Channel/$it"
}
} else if (noteEvent is ChannelCreateEvent) {
return "Channel/${noteEvent.id}"
} else if (noteEvent is LiveActivitiesEvent) {
noteEvent.address().toTag().let {
noteEvent.aTag().toTag().let {
return "Channel/${URLEncoder.encode(it, "utf-8")}"
}
} else if (noteEvent is LiveActivitiesChatMessageEvent) {
@@ -97,9 +97,9 @@ fun routeFor(
loggedIn.createChatroom(room)
return "Room/${room.hashCode()}"
} else if (noteEvent is CommunityDefinitionEvent) {
return "Community/${URLEncoder.encode(noteEvent.address().toTag(), "utf-8")}"
return "Community/${URLEncoder.encode(noteEvent.aTag().toTag(), "utf-8")}"
} else if (noteEvent is AddressableEvent) {
return "Note/${URLEncoder.encode(noteEvent.address().toTag(), "utf-8")}"
return "Note/${URLEncoder.encode(noteEvent.aTag().toTag(), "utf-8")}"
} else {
return "Note/${URLEncoder.encode(noteEvent.id, "utf-8")}"
}
@@ -102,16 +102,15 @@ import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
import com.vitorpamplona.amethyst.ui.theme.bitcoinColor
import com.vitorpamplona.amethyst.ui.theme.grayText
import com.vitorpamplona.amethyst.ui.theme.nip05
import com.vitorpamplona.quartz.experimental.audio.Participant
import com.vitorpamplona.quartz.nip28PublicChat.ChannelCreateEvent
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesEvent
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesEvent.Companion.STATUS_ENDED
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesEvent.Companion.STATUS_LIVE
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesEvent.Companion.STATUS_PLANNED
import com.vitorpamplona.quartz.nip72ModCommunities.CommunityDefinitionEvent
import com.vitorpamplona.quartz.nip89AppHandlers.AppDefinitionEvent
import com.vitorpamplona.quartz.nip01Core.HexKey
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.tags.ParticipantTag
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.tags.StatusTag
import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent
import com.vitorpamplona.quartz.nip89AppHandlers.definition.AppDefinitionEvent
import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent
import com.vitorpamplona.quartz.nip99Classifieds.Price
import com.vitorpamplona.quartz.nip99Classifieds.tags.PriceTag
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
@@ -295,7 +294,7 @@ private fun RenderNoteRow(
data class ClassifiedsThumb(
val image: String?,
val title: String?,
val price: Price?,
val price: PriceTag?,
)
@Composable
@@ -339,7 +338,7 @@ fun RenderClassifiedsThumbPreview() {
ClassifiedsThumb(
image = null,
title = "Like New",
price = Price("800000", "SATS", null),
price = PriceTag("800000", "SATS", null),
),
note = Note("hex"),
)
@@ -416,7 +415,7 @@ data class LiveActivityCard(
val media: String?,
val subject: String?,
val content: String?,
val participants: ImmutableList<Participant>,
val participants: ImmutableList<ParticipantTag>,
val status: String?,
val starts: Long?,
)
@@ -485,7 +484,7 @@ fun RenderLiveActivityThumb(
Box(Modifier.padding(10.dp)) {
CrossfadeIfEnabled(targetState = card.status, label = "RenderLiveActivityThumb", accountViewModel = accountViewModel) {
when (it) {
STATUS_LIVE -> {
StatusTag.STATUS.LIVE.code -> {
val url = card.media
if (url.isNullOrBlank()) {
LiveFlag()
@@ -499,10 +498,10 @@ fun RenderLiveActivityThumb(
}
}
}
STATUS_ENDED -> {
StatusTag.STATUS.ENDED.code -> {
EndedFlag()
}
STATUS_PLANNED -> {
StatusTag.STATUS.PLANNED.code -> {
ScheduledFlag(card.starts)
}
else -> {
@@ -544,7 +543,7 @@ data class CommunityCard(
val name: String,
val description: String?,
val cover: String?,
val moderators: ImmutableList<Participant>,
val moderators: ImmutableList<HexKey>,
)
@Immutable
@@ -574,16 +573,16 @@ fun RenderCommunitiesThumb(
CommunityCard(
name = noteEvent?.dTag() ?: "",
description = noteEvent?.description(),
cover = noteEvent?.image()?.ifBlank { null },
moderators = noteEvent?.moderators()?.toImmutableList() ?: persistentListOf(),
cover = noteEvent?.image()?.imageUrl,
moderators = noteEvent?.moderatorKeys()?.toImmutableList() ?: persistentListOf(),
)
}.distinctUntilChanged()
.observeAsState(
CommunityCard(
name = noteEvent.dTag(),
description = noteEvent.description(),
cover = noteEvent.image()?.ifBlank { null },
moderators = noteEvent.moderators().toImmutableList(),
cover = noteEvent.image()?.imageUrl,
moderators = noteEvent.moderatorKeys().toImmutableList(),
),
)
@@ -655,7 +654,7 @@ fun RenderCommunitiesThumb(
@Composable
fun LoadModerators(
moderators: ImmutableList<Participant>,
moderators: ImmutableList<String>,
baseNote: Note,
accountViewModel: AccountViewModel,
content: @Composable (ImmutableList<User>) -> Unit,
@@ -670,8 +669,8 @@ fun LoadModerators(
launch(Dispatchers.IO) {
val hosts =
moderators.mapNotNull { part ->
if (part.key != baseNote.author?.pubkeyHex) {
LocalCache.checkGetOrCreateUser(part.key)
if (part != baseNote.author?.pubkeyHex) {
LocalCache.checkGetOrCreateUser(part)
} else {
null
}
@@ -706,7 +705,7 @@ fun LoadModerators(
@Composable
private fun LoadParticipants(
participants: ImmutableList<Participant>,
participants: ImmutableList<ParticipantTag>,
baseNote: Note,
accountViewModel: AccountViewModel,
inner: @Composable (ImmutableList<User>) -> Unit,
@@ -721,8 +720,8 @@ private fun LoadParticipants(
launch(Dispatchers.IO) {
val hosts =
participants.mapNotNull { part ->
if (part.key != baseNote.author?.pubkeyHex) {
LocalCache.checkGetOrCreateUser(part.key)
if (part.pubKey != baseNote.author?.pubkeyHex) {
LocalCache.checkGetOrCreateUser(part.pubKey)
} else {
null
}
@@ -42,6 +42,7 @@ import com.vitorpamplona.amethyst.ui.navigation.routeFor
import com.vitorpamplona.amethyst.ui.note.elements.NoteDropDownMenu
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.MessageSetCard
import com.vitorpamplona.amethyst.ui.theme.StdStartPadding
import kotlinx.coroutines.launch
@OptIn(ExperimentalFoundationApi::class)
@@ -53,7 +54,7 @@ fun MessageSetCompose(
accountViewModel: AccountViewModel,
nav: INav,
) {
val baseNote = remember { messageSetCard.note }
val baseNote = messageSetCard.note
val popupExpanded = remember { mutableStateOf(false) }
val enablePopup = remember { { popupExpanded.value = true } }
@@ -90,15 +91,9 @@ fun MessageSetCompose(
Column(columnModifier) {
Row(Modifier.fillMaxWidth()) {
Box(
modifier = remember { Modifier.width(55.dp).padding(top = 5.dp, end = 5.dp) },
) {
MessageIcon(
remember { Modifier.size(16.dp).align(Alignment.TopEnd) },
)
}
MessageIconBox()
Column(modifier = remember { Modifier.padding(start = 10.dp) }) {
Column(modifier = StdStartPadding) {
NoteCompose(
baseNote = baseNote,
routeForLastRead = null,
@@ -117,3 +112,10 @@ fun MessageSetCompose(
}
}
}
@Composable
fun MessageIconBox() {
Box(Modifier.width(55.dp).padding(top = 5.dp, end = 5.dp)) {
MessageIcon(Modifier.size(16.dp).align(Alignment.TopEnd))
}
}
@@ -72,7 +72,7 @@ import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
import com.vitorpamplona.amethyst.ui.theme.lessImportantLink
import com.vitorpamplona.amethyst.ui.theme.nip05
import com.vitorpamplona.amethyst.ui.theme.placeholderText
import com.vitorpamplona.quartz.nip01Core.UserMetadata
import com.vitorpamplona.quartz.nip01Core.metadata.UserMetadata
import com.vitorpamplona.quartz.nip01Core.tags.addressables.ATag
import com.vitorpamplona.quartz.nip01Core.tags.addressables.firstTaggedAddress
import com.vitorpamplona.quartz.nip01Core.tags.events.ETag
@@ -149,36 +149,36 @@ import com.vitorpamplona.amethyst.ui.theme.newItemBackgroundColor
import com.vitorpamplona.amethyst.ui.theme.normalWithTopMarginNoteModifier
import com.vitorpamplona.amethyst.ui.theme.placeholderText
import com.vitorpamplona.amethyst.ui.theme.replyModifier
import com.vitorpamplona.quartz.experimental.audio.AudioHeaderEvent
import com.vitorpamplona.quartz.experimental.audio.AudioTrackEvent
import com.vitorpamplona.quartz.experimental.bounties.getReward
import com.vitorpamplona.quartz.experimental.audio.header.AudioHeaderEvent
import com.vitorpamplona.quartz.experimental.audio.track.AudioTrackEvent
import com.vitorpamplona.quartz.experimental.bounties.bountyBaseReward
import com.vitorpamplona.quartz.experimental.edits.TextNoteModificationEvent
import com.vitorpamplona.quartz.experimental.forks.isAFork
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryBaseEvent
import com.vitorpamplona.quartz.experimental.medical.FhirResourceEvent
import com.vitorpamplona.quartz.experimental.nip95.FileStorageHeaderEvent
import com.vitorpamplona.quartz.experimental.nip95.header.FileStorageHeaderEvent
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
import com.vitorpamplona.quartz.nip01Core.tags.addressables.isTaggedAddressableKind
import com.vitorpamplona.quartz.nip01Core.tags.geohash.getGeoHash
import com.vitorpamplona.quartz.nip04Dm.PrivateDmEvent
import com.vitorpamplona.quartz.nip10Notes.BaseTextNoteEvent
import com.vitorpamplona.quartz.nip10Notes.BaseThreadedEvent
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
import com.vitorpamplona.quartz.nip13Pow.pow
import com.vitorpamplona.quartz.nip13Pow.strongPoWOrNull
import com.vitorpamplona.quartz.nip17Dm.ChatMessageEncryptedFileHeaderEvent
import com.vitorpamplona.quartz.nip17Dm.ChatMessageEvent
import com.vitorpamplona.quartz.nip17Dm.ChatMessageRelayListEvent
import com.vitorpamplona.quartz.nip17Dm.files.ChatMessageEncryptedFileHeaderEvent
import com.vitorpamplona.quartz.nip17Dm.messages.ChatMessageEvent
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.nip23LongContent.LongTextNoteEvent
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
import com.vitorpamplona.quartz.nip28PublicChat.ChannelCreateEvent
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMessageEvent
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMetadataEvent
import com.vitorpamplona.quartz.nip30CustomEmoji.EmojiPackEvent
import com.vitorpamplona.quartz.nip34Git.GitIssueEvent
import com.vitorpamplona.quartz.nip34Git.GitPatchEvent
import com.vitorpamplona.quartz.nip34Git.GitRepositoryEvent
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.nip30CustomEmoji.pack.EmojiPackEvent
import com.vitorpamplona.quartz.nip34Git.issue.GitIssueEvent
import com.vitorpamplona.quartz.nip34Git.patch.GitPatchEvent
import com.vitorpamplona.quartz.nip34Git.repository.GitRepositoryEvent
import com.vitorpamplona.quartz.nip35Torrents.TorrentCommentEvent
import com.vitorpamplona.quartz.nip35Torrents.TorrentEvent
import com.vitorpamplona.quartz.nip37Drafts.DraftEvent
@@ -186,8 +186,8 @@ import com.vitorpamplona.quartz.nip50Search.SearchRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent
import com.vitorpamplona.quartz.nip51Lists.PinListEvent
import com.vitorpamplona.quartz.nip51Lists.RelaySetEvent
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesChatMessageEvent
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesEvent
import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
import com.vitorpamplona.quartz.nip54Wiki.WikiNoteEvent
import com.vitorpamplona.quartz.nip56Reports.ReportEvent
import com.vitorpamplona.quartz.nip57Zaps.splits.hasZapSplitSetup
@@ -198,10 +198,10 @@ import com.vitorpamplona.quartz.nip68Picture.PictureEvent
import com.vitorpamplona.quartz.nip71Video.VideoEvent
import com.vitorpamplona.quartz.nip71Video.VideoHorizontalEvent
import com.vitorpamplona.quartz.nip71Video.VideoVerticalEvent
import com.vitorpamplona.quartz.nip72ModCommunities.CommunityDefinitionEvent
import com.vitorpamplona.quartz.nip72ModCommunities.CommunityPostApprovalEvent
import com.vitorpamplona.quartz.nip72ModCommunities.approval.CommunityPostApprovalEvent
import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent
import com.vitorpamplona.quartz.nip84Highlights.HighlightEvent
import com.vitorpamplona.quartz.nip89AppHandlers.AppDefinitionEvent
import com.vitorpamplona.quartz.nip89AppHandlers.definition.AppDefinitionEvent
import com.vitorpamplona.quartz.nip90Dvms.NIP90ContentDiscoveryResponseEvent
import com.vitorpamplona.quartz.nip90Dvms.NIP90StatusEvent
import com.vitorpamplona.quartz.nip94FileMetadata.FileHeaderEvent
@@ -963,7 +963,7 @@ fun SecondUserInfoRow(
verticalAlignment = CenterVertically,
modifier = UserNameMaxRowHeight,
) {
if (noteEvent is BaseTextNoteEvent && noteEvent.isAFork()) {
if (noteEvent is BaseThreadedEvent && noteEvent.isAFork()) {
ShowForkInformation(noteEvent, remember(noteEvent) { Modifier.weight(1f) }, accountViewModel, nav)
} else {
ObserveDisplayNip05Status(noteAuthor, remember(noteEvent) { Modifier.weight(1f) }, accountViewModel, nav)
@@ -975,7 +975,7 @@ fun SecondUserInfoRow(
DisplayLocation(geo, nav)
}
val baseReward = remember(noteEvent) { noteEvent.getReward()?.let { Reward(it) } }
val baseReward = remember(noteEvent) { noteEvent.bountyBaseReward()?.let { Reward(it) } }
if (baseReward != null) {
Spacer(StdHorzSpacer)
DisplayReward(baseReward, note, accountViewModel, nav)
@@ -95,8 +95,8 @@ import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.WarningColor
import com.vitorpamplona.amethyst.ui.theme.isLight
import com.vitorpamplona.amethyst.ui.theme.secondaryButtonBackground
import com.vitorpamplona.quartz.experimental.audio.AudioTrackEvent
import com.vitorpamplona.quartz.experimental.bounties.getReward
import com.vitorpamplona.quartz.experimental.audio.track.AudioTrackEvent
import com.vitorpamplona.quartz.experimental.bounties.bountyBaseReward
import com.vitorpamplona.quartz.nip19Bech32.toNAddr
import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent
import com.vitorpamplona.quartz.nip94FileMetadata.FileHeaderEvent
@@ -124,7 +124,7 @@ val njumpLink = { nip19BechAddress: String ->
val externalLinkForNote = { note: Note ->
if (note is AddressableNote) {
if (note.event?.getReward() != null) {
if (note.event?.bountyBaseReward() != null) {
"https://nostrbounties.com/b/${note.address().toNAddr()}"
} else if (note.event is PeopleListEvent) {
"https://listr.lol/a/${note.address().toNAddr()}"
@@ -97,7 +97,7 @@ import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
import com.vitorpamplona.quartz.nip02FollowList.EmptyTagList
import com.vitorpamplona.quartz.nip02FollowList.ImmutableListOfLists
import com.vitorpamplona.quartz.nip02FollowList.toImmutableListOfLists
import com.vitorpamplona.quartz.nip31Alts.AltTagSerializer
import com.vitorpamplona.quartz.nip31Alts.AltTag
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
@@ -124,7 +124,7 @@ fun PollNotePreview() {
arrayOf("poll_option", "2", "OP3"),
arrayOf("value_maximum", "2"),
arrayOf("value_minimum", "2"),
AltTagSerializer.toTagArray("Poll event"),
arrayOf("alt", "Poll event"),
),
)
@@ -196,7 +196,7 @@ fun PollNotePreview2() {
arrayOf("poll_option", "1", "Pesquisa em ingles"),
arrayOf("value_maximum", "2"),
arrayOf("value_minimum", "2"),
AltTagSerializer.toTagArray("Poll event"),
AltTag.assemble("Poll event"),
),
)
@@ -28,11 +28,7 @@ import androidx.lifecycle.viewModelScope
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.quartz.experimental.zapPolls.CLOSED_AT
import com.vitorpamplona.quartz.experimental.zapPolls.CONSENSUS_THRESHOLD
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
import com.vitorpamplona.quartz.experimental.zapPolls.VALUE_MAXIMUM
import com.vitorpamplona.quartz.experimental.zapPolls.VALUE_MINIMUM
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent
import com.vitorpamplona.quartz.utils.TimeUtils
@@ -81,17 +77,12 @@ class PollNoteViewModel : ViewModel() {
pollNote = note
pollEvent = pollNote?.event as PollNoteEvent
pollOptions = pollEvent?.pollOptions()
valueMaximum = pollEvent?.getTagLong(VALUE_MAXIMUM)
valueMinimum = pollEvent?.getTagLong(VALUE_MINIMUM)
valueMaximum = pollEvent?.maxAmount()
valueMinimum = pollEvent?.minAmount()
valueMinimumBD = valueMinimum?.let { BigDecimal(it) }
valueMaximumBD = valueMaximum?.let { BigDecimal(it) }
consensusThreshold =
pollEvent
?.getTagLong(CONSENSUS_THRESHOLD)
?.toFloat()
?.div(100)
?.toBigDecimal()
closedAt = pollEvent?.getTagLong(CLOSED_AT)
consensusThreshold = pollEvent?.consensusThreshold()?.toBigDecimal()
closedAt = pollEvent?.closedAt()
totalZapped = BigDecimal.ZERO
wasZappedByLoggedInAccount = false
@@ -147,7 +147,7 @@ import com.vitorpamplona.amethyst.ui.theme.placeholderText
import com.vitorpamplona.amethyst.ui.theme.reactionBox
import com.vitorpamplona.amethyst.ui.theme.ripple24dp
import com.vitorpamplona.amethyst.ui.theme.selectedReactionBoxModifier
import com.vitorpamplona.quartz.nip10Notes.BaseTextNoteEvent
import com.vitorpamplona.quartz.nip10Notes.BaseThreadedEvent
import com.vitorpamplona.quartz.nip30CustomEmoji.CustomEmoji
import com.vitorpamplona.quartz.nip57Zaps.zapraiser.zapraiserAmount
import kotlinx.collections.immutable.ImmutableList
@@ -579,7 +579,7 @@ private fun BoostWithDialog(
val replyTo =
remember(wantsToFork) {
val forkEvent = wantsToFork?.event
if (forkEvent is BaseTextNoteEvent) {
if (forkEvent is BaseThreadedEvent) {
val hex = forkEvent.replyingTo()
wantsToFork?.replyTo?.filter { it.event?.id == hex }?.firstOrNull()
} else {
@@ -54,7 +54,7 @@ import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.Size10dp
import com.vitorpamplona.quartz.nip01Core.tags.addressables.taggedAddresses
import com.vitorpamplona.quartz.nip30CustomEmoji.EmojiPackSelectionEvent
import com.vitorpamplona.quartz.nip30CustomEmoji.selection.EmojiPackSelectionEvent
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.Flow
@@ -89,8 +89,8 @@ import com.vitorpamplona.amethyst.ui.theme.placeholderText
import com.vitorpamplona.quartz.nip01Core.tags.addressables.ATag
import com.vitorpamplona.quartz.nip01Core.tags.addressables.taggedAddresses
import com.vitorpamplona.quartz.nip30CustomEmoji.CustomEmoji
import com.vitorpamplona.quartz.nip30CustomEmoji.EmojiPackSelectionEvent
import com.vitorpamplona.quartz.nip30CustomEmoji.EmojiUrl
import com.vitorpamplona.quartz.nip30CustomEmoji.EmojiUrlTag
import com.vitorpamplona.quartz.nip30CustomEmoji.selection.EmojiPackSelectionEvent
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
@@ -116,7 +116,7 @@ class UpdateReactionTypeViewModel : ViewModel() {
nextChoice = TextFieldValue("")
}
fun addChoice(customEmoji: EmojiUrl) {
fun addChoice(customEmoji: EmojiUrlTag) {
reactionSet = reactionSet + (customEmoji.encode())
}
@@ -337,7 +337,7 @@ private fun RenderReactionOption(
private fun EmojiSelector(
accountViewModel: AccountViewModel,
nav: INav,
onClick: ((EmojiUrl) -> Unit)? = null,
onClick: ((EmojiUrlTag) -> Unit)? = null,
) {
LoadAddressableNote(
aTag =
@@ -372,7 +372,7 @@ fun EmojiCollectionGallery(
emojiCollections: ImmutableList<ATag>,
accountViewModel: AccountViewModel,
nav: INav,
onClick: ((EmojiUrl) -> Unit)? = null,
onClick: ((EmojiUrlTag) -> Unit)? = null,
) {
val color = MaterialTheme.colorScheme.background
val bgColor = remember { mutableStateOf(color) }
@@ -396,7 +396,7 @@ private fun WatchAndRenderNote(
bgColor: MutableState<Color>,
accountViewModel: AccountViewModel,
nav: INav,
onClick: ((EmojiUrl) -> Unit)?,
onClick: ((EmojiUrlTag) -> Unit)?,
) {
val scope = rememberCoroutineScope()
@@ -46,7 +46,7 @@ import com.vitorpamplona.amethyst.ui.navigation.INav
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.LoadUser
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.quartz.nip17Dm.ChatroomKey
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey
@Composable
fun NoteAuthorPicture(
@@ -52,7 +52,7 @@ import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
import com.vitorpamplona.amethyst.ui.theme.imageModifier
import com.vitorpamplona.quartz.nip01Core.HexKey
import com.vitorpamplona.quartz.nip17Dm.ChatMessageRelayListEvent
import com.vitorpamplona.quartz.nip17Dm.settings.ChatMessageRelayListEvent
@Preview
@Composable
@@ -35,7 +35,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.HalfStartPadding
import com.vitorpamplona.quartz.nip01Core.tags.addressables.ATag
import com.vitorpamplona.quartz.nip01Core.tags.addressables.getTagOfAddressableKind
import com.vitorpamplona.quartz.nip72ModCommunities.CommunityDefinitionEvent
import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent
@Composable
fun DisplayFollowingCommunityInPost(
@@ -177,24 +177,19 @@ class AddBountyAmountViewModel : ViewModel() {
}
fun sendPost() {
val newValue = nextAmount.text.trim().toLongOrNull()
val newValue = nextAmount.text.trim().toBigDecimalOrNull()
if (newValue != null) {
viewModelScope.launch {
account?.let {
it.sendPost(
message = newValue.toString(),
replyTo = listOfNotNull(bounty),
mentions = listOfNotNull(bounty?.author),
tags = listOf("bounty-added-reward"),
wantsToMarkAsSensitive = false,
replyingTo = null,
root = null,
directMentions = setOf(),
forkedFrom = null,
draftTag = null,
relayList = it.activeWriteRelays().toImmutableList(),
)
account?.let { myAccount ->
bounty?.let { bountyInner ->
myAccount.sendAddBounty(
newValue,
bountyInner,
draftTag = null,
relayList = myAccount.activeWriteRelays().toImmutableList(),
)
}
}
nextAmount = TextFieldValue("")
@@ -45,11 +45,13 @@ import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.Font14SP
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
import com.vitorpamplona.amethyst.ui.theme.nip05
import com.vitorpamplona.quartz.nip10Notes.BaseTextNoteEvent
import com.vitorpamplona.quartz.experimental.forks.forkFromAddress
import com.vitorpamplona.quartz.experimental.forks.forkFromVersion
import com.vitorpamplona.quartz.nip10Notes.BaseThreadedEvent
@Composable
fun ShowForkInformation(
noteEvent: BaseTextNoteEvent,
noteEvent: BaseThreadedEvent,
modifier: Modifier,
accountViewModel: AccountViewModel,
nav: INav,
@@ -66,7 +68,7 @@ fun ShowForkInformation(
}
}
} else if (forkedEvent != null) {
LoadNote(forkedEvent, accountViewModel = accountViewModel) { event ->
LoadNote(forkedEvent.eventId, accountViewModel = accountViewModel) { event ->
if (event != null) {
ForkInformationRowLightColor(event, modifier, accountViewModel, nav)
}
@@ -72,8 +72,8 @@ import com.vitorpamplona.amethyst.ui.theme.Size35dp
import com.vitorpamplona.amethyst.ui.theme.placeholderText
import com.vitorpamplona.quartz.nip02FollowList.EmptyTagList
import com.vitorpamplona.quartz.nip02FollowList.toImmutableListOfLists
import com.vitorpamplona.quartz.nip89AppHandlers.AppDefinitionEvent
import com.vitorpamplona.quartz.nip89AppHandlers.AppMetadata
import com.vitorpamplona.quartz.nip89AppHandlers.definition.AppDefinitionEvent
import com.vitorpamplona.quartz.nip89AppHandlers.definition.AppMetadata
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@@ -51,15 +51,12 @@ import com.vitorpamplona.amethyst.ui.note.ClickableUserPicture
import com.vitorpamplona.amethyst.ui.note.UsernameDisplay
import com.vitorpamplona.amethyst.ui.note.elements.DisplayUncitedHashtags
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.placeholderText
import com.vitorpamplona.quartz.experimental.audio.AudioHeaderEvent
import com.vitorpamplona.quartz.experimental.audio.AudioTrackEvent
import com.vitorpamplona.quartz.experimental.audio.Participant
import com.vitorpamplona.quartz.experimental.audio.header.AudioHeaderEvent
import com.vitorpamplona.quartz.experimental.audio.track.AudioTrackEvent
import com.vitorpamplona.quartz.experimental.audio.track.tags.ParticipantTag
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hasHashtags
import com.vitorpamplona.quartz.nip02FollowList.toImmutableListOfLists
import com.vitorpamplona.quartz.nip14Subject.subject
import kotlinx.collections.immutable.toImmutableList
import java.util.Locale
@Composable
fun RenderAudioTrack(
@@ -84,10 +81,9 @@ fun AudioTrackHeader(
val media = remember { noteEvent.media() }
val cover = remember { noteEvent.cover() }
val subject = remember { noteEvent.subject() }
val content = remember { noteEvent.content }
val participants = remember { noteEvent.participants() }
var participantUsers by remember { mutableStateOf<List<Pair<Participant, User>>>(emptyList()) }
var participantUsers by remember { mutableStateOf<List<Pair<ParticipantTag, User>>>(emptyList()) }
LaunchedEffect(key1 = participants) {
accountViewModel.loadParticipants(participants) { participantUsers = it }
@@ -129,13 +125,6 @@ fun AudioTrackHeader(
Spacer(Modifier.width(5.dp))
UsernameDisplay(it.second, Modifier.weight(1f), accountViewModel = accountViewModel)
Spacer(Modifier.width(5.dp))
it.first.role?.let {
Text(
text = it.capitalize(Locale.ROOT),
color = MaterialTheme.colorScheme.placeholderText,
maxLines = 1,
)
}
}
}
@@ -193,7 +182,7 @@ fun AudioHeader(
nav: INav,
) {
val media = remember { noteEvent.stream() ?: noteEvent.download() }
val waveform = remember { noteEvent.wavefrom()?.toImmutableList()?.ifEmpty { null } }
val waveform = remember { noteEvent.wavefrom() }
val content = remember { noteEvent.content.ifBlank { null } }
val defaultBackground = MaterialTheme.colorScheme.background
@@ -155,7 +155,7 @@ fun RenderBadgeAward(
Text(text = stringRes(R.string.award_granted_to))
LaunchedEffect(key1 = note) { accountViewModel.loadUsers(noteEvent.awardees()) { awardees = it } }
LaunchedEffect(key1 = note) { accountViewModel.loadUsers(noteEvent.awardeeIds()) { awardees = it } }
FlowRow(modifier = Modifier.padding(top = 5.dp)) {
awardees.take(100).forEach { user ->
@@ -36,7 +36,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.ChannelHeader
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
import com.vitorpamplona.amethyst.ui.theme.replyModifier
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMessageEvent
import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
@Composable
fun RenderChannelMessage(
@@ -53,7 +53,7 @@ fun RenderChannelMessage(
val showChannelInfo =
remember(noteEvent) {
if (noteEvent is ChannelMessageEvent) {
noteEvent.channel()
noteEvent.channelId()
} else {
null
}

Some files were not shown because too many files have changed in this diff Show More