mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-09-25 20:47:49 +02:00
Merge branch 'main' of https://github.com/vitorpamplona/amethyst
This commit is contained in:
@@ -173,7 +173,7 @@ class AccountViewModel(
|
||||
scope = viewModelScope,
|
||||
)
|
||||
|
||||
val newNotesPreProcessor = DecryptAndIndexProcessor(account, LocalCache)
|
||||
val newNotesPreProcessor = EventProcessor(account, LocalCache)
|
||||
|
||||
var firstRoute: Route? = null
|
||||
|
||||
@@ -949,7 +949,7 @@ class AccountViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun loadReactionTo(note: Note?): String? {
|
||||
fun loadReactionTo(note: Note?): String? {
|
||||
if (note == null) return null
|
||||
|
||||
return note.getReactionBy(userProfile())
|
||||
@@ -1027,7 +1027,7 @@ class AccountViewModel(
|
||||
|
||||
fun getUserIfExists(hex: HexKey): User? = LocalCache.getUserIfExists(hex)
|
||||
|
||||
private suspend fun checkGetOrCreateNote(key: HexKey): Note? = LocalCache.checkGetOrCreateNote(key)
|
||||
private fun checkGetOrCreateNote(key: HexKey): Note? = LocalCache.checkGetOrCreateNote(key)
|
||||
|
||||
override suspend fun getOrCreateNote(key: HexKey): Note = LocalCache.getOrCreateNote(key)
|
||||
|
||||
@@ -1102,11 +1102,11 @@ class AccountViewModel(
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun checkGetOrCreatePublicChatChannel(key: HexKey): PublicChatChannel? = LocalCache.getOrCreatePublicChatChannel(key)
|
||||
fun checkGetOrCreatePublicChatChannel(key: HexKey): PublicChatChannel? = LocalCache.getOrCreatePublicChatChannel(key)
|
||||
|
||||
suspend fun checkGetOrCreateLiveActivityChannel(key: Address): LiveActivitiesChannel? = LocalCache.getOrCreateLiveChannel(key)
|
||||
fun checkGetOrCreateLiveActivityChannel(key: Address): LiveActivitiesChannel? = LocalCache.getOrCreateLiveChannel(key)
|
||||
|
||||
suspend fun checkGetOrCreateEphemeralChatChannel(key: RoomId): EphemeralChatChannel? = LocalCache.getOrCreateEphemeralChannel(key)
|
||||
fun checkGetOrCreateEphemeralChatChannel(key: RoomId): EphemeralChatChannel? = LocalCache.getOrCreateEphemeralChannel(key)
|
||||
|
||||
fun checkGetOrCreateChannel(
|
||||
key: HexKey,
|
||||
@@ -1660,7 +1660,7 @@ class AccountViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun findUsersStartingWithSync(prefix: String) = LocalCache.findUsersStartingWith(prefix, account)
|
||||
fun findUsersStartingWithSync(prefix: String) = LocalCache.findUsersStartingWith(prefix, account)
|
||||
|
||||
fun relayStatusFlow() = app.client.relayStatusFlow()
|
||||
|
||||
|
@@ -29,6 +29,7 @@ import com.vitorpamplona.quartz.experimental.ephemChat.chat.EphemeralChatEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.OtsEvent
|
||||
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
|
||||
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.nip28PublicChat.message.ChannelMessageEvent
|
||||
@@ -39,241 +40,178 @@ import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.seals.SealedRumorEvent
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
class DecryptAndIndexProcessor(
|
||||
val account: Account,
|
||||
val cache: LocalCache,
|
||||
class EventProcessor(
|
||||
private val account: Account,
|
||||
private val cache: LocalCache,
|
||||
) {
|
||||
private val decryptionService = DecryptionService(account)
|
||||
private val indexingService = IndexingService(account, cache)
|
||||
private val chatroomService = ChatroomService(account)
|
||||
private val eventHandlers = createEventHandlers()
|
||||
|
||||
suspend fun consume(note: Note) {
|
||||
val noteEvent = note.event
|
||||
if (noteEvent != null) {
|
||||
note.event?.let { event ->
|
||||
try {
|
||||
consumeAlreadyVerified(noteEvent, note, note)
|
||||
processEvent(event, note, note)
|
||||
} catch (e: Exception) {
|
||||
Log.e("PrecacheNewNotesProcessor", "Error processing note", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun consumeAlreadyVerified(
|
||||
event: Event,
|
||||
eventNote: Note,
|
||||
publicNote: Note,
|
||||
) {
|
||||
when (event) {
|
||||
is PrivateDmEvent -> {
|
||||
if (event.canDecrypt(account.signer.pubKey)) {
|
||||
val talkingWith = event.chatroomKey(account.signer.pubKey)
|
||||
account.chatroomList.addMessage(talkingWith, eventNote)
|
||||
}
|
||||
}
|
||||
|
||||
is ChatMessageEvent -> {
|
||||
if (event.isIncluded(account.signer.pubKey)) {
|
||||
val key = event.chatroomKey(account.signer.pubKey)
|
||||
account.chatroomList.addMessage(key, eventNote)
|
||||
}
|
||||
}
|
||||
|
||||
is ChatMessageEncryptedFileHeaderEvent -> {
|
||||
if (event.isIncluded(account.signer.pubKey)) {
|
||||
val key = event.chatroomKey(account.signer.pubKey)
|
||||
account.chatroomList.addMessage(key, eventNote)
|
||||
}
|
||||
}
|
||||
|
||||
is OtsEvent -> {
|
||||
// verifies new OTS upon arrival
|
||||
Amethyst.instance.otsVerifCache.cacheVerify(event, account.otsResolverBuilder)
|
||||
}
|
||||
|
||||
is DraftEvent -> {
|
||||
// Avoid decrypting over and over again if the event already exist.
|
||||
if (event.pubKey == account.signer.pubKey) {
|
||||
if (!event.isDeleted()) {
|
||||
val rumor = account.draftsDecryptionCache.preCachedDraft(event) ?: account.draftsDecryptionCache.cachedDraft(event)
|
||||
if (rumor != null) {
|
||||
indexDraftAsRealEvent(eventNote, rumor)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is GiftWrapEvent -> {
|
||||
if (event.recipientPubKey() == account.signer.pubKey) {
|
||||
val innerGiftId = event.innerEventId
|
||||
if (innerGiftId == null) {
|
||||
event.unwrapOrNull(account.signer)?.let { innerGift ->
|
||||
// clear the encrypted payload to save memory
|
||||
eventNote.event = event.copyNoContent()
|
||||
if (cache.justConsume(innerGift, null, false)) {
|
||||
cache.copyRelaysFromTo(publicNote, innerGift)
|
||||
val innerGiftNote = cache.getOrCreateNote(innerGift.id)
|
||||
consumeAlreadyVerified(innerGift, innerGiftNote, publicNote)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
cache.copyRelaysFromTo(publicNote, innerGiftId)
|
||||
|
||||
val innerGiftNote = cache.getOrCreateNote(innerGiftId)
|
||||
val innerGift = innerGiftNote.event
|
||||
if (innerGift != null) {
|
||||
consumeAlreadyVerified(innerGift, innerGiftNote, publicNote)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is SealedRumorEvent -> {
|
||||
val rumorId = event.innerEventId
|
||||
if (rumorId == null) {
|
||||
event.unsealOrNull(account.signer)?.let { innerRumor ->
|
||||
// clear the encrypted payload to save memory
|
||||
eventNote.event = event.copyNoContent()
|
||||
|
||||
// rumors cannot be verified
|
||||
cache.justConsume(innerRumor, null, true)
|
||||
|
||||
cache.copyRelaysFromTo(publicNote, innerRumor)
|
||||
val innerRumorNote = cache.getOrCreateNote(innerRumor.id)
|
||||
consumeAlreadyVerified(innerRumor, innerRumorNote, publicNote)
|
||||
}
|
||||
} else {
|
||||
cache.copyRelaysFromTo(publicNote, rumorId)
|
||||
|
||||
val innerRumorNote = cache.getOrCreateNote(rumorId)
|
||||
val innerRumor = innerRumorNote.event
|
||||
if (innerRumor != null) {
|
||||
consumeAlreadyVerified(innerRumor, innerRumorNote, publicNote)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is LnZapRequestEvent -> {
|
||||
if (account.privateZapsDecryptionCache.cachedPrivateZap(event) == null && event.isPrivateZap()) {
|
||||
account.privateZapsDecryptionCache.decryptPrivateZap(event)
|
||||
}
|
||||
Log.e("EventProcessor", "Error processing note", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun delete(note: Note) {
|
||||
val noteEvent = note.event
|
||||
if (noteEvent != null) {
|
||||
note.event?.let { event ->
|
||||
try {
|
||||
delete(noteEvent, note)
|
||||
deleteEvent(event, note)
|
||||
} catch (e: Exception) {
|
||||
Log.e("PrecacheNewNotesProcessor", "Error processing note", e)
|
||||
Log.e("EventProcessor", "Error deleting note", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun delete(
|
||||
internal suspend fun processEvent(
|
||||
event: Event,
|
||||
eventNote: Note,
|
||||
publicNote: Note,
|
||||
) {
|
||||
eventHandlers[event::class]?.process(event, eventNote, publicNote)
|
||||
}
|
||||
|
||||
internal suspend fun deleteEvent(
|
||||
event: Event,
|
||||
eventNote: Note,
|
||||
) {
|
||||
when (event) {
|
||||
is PrivateDmEvent -> {
|
||||
if (event.canDecrypt(account.signer.pubKey)) {
|
||||
// Avoid decrypting over and over again if the event already exist.
|
||||
val talkingWith = event.chatroomKey(account.signer.pubKey)
|
||||
account.chatroomList.removeMessage(talkingWith, eventNote)
|
||||
}
|
||||
}
|
||||
eventHandlers[event::class]?.delete(event, eventNote)
|
||||
}
|
||||
|
||||
is ChatMessageEvent -> {
|
||||
if (event.isIncluded(account.signer.pubKey)) {
|
||||
val key = event.chatroomKey(account.signer.pubKey)
|
||||
account.chatroomList.removeMessage(key, eventNote)
|
||||
}
|
||||
}
|
||||
private fun createEventHandlers(): Map<KClass<out Event>, EventHandler> =
|
||||
mapOf(
|
||||
PrivateDmEvent::class to PrivateDmHandler(chatroomService),
|
||||
ChatMessageEvent::class to ChatMessageHandler(chatroomService),
|
||||
ChatMessageEncryptedFileHeaderEvent::class to ChatMessageEncryptedFileHeaderHandler(chatroomService),
|
||||
OtsEvent::class to OtsEventHandler(account),
|
||||
DraftEvent::class to DraftEventHandler(decryptionService, indexingService),
|
||||
GiftWrapEvent::class to GiftWrapEventHandler(decryptionService, cache, this),
|
||||
SealedRumorEvent::class to SealedRumorEventHandler(decryptionService, cache, this),
|
||||
LnZapRequestEvent::class to LnZapRequestEventHandler(decryptionService),
|
||||
LnZapEvent::class to LnZapEventHandler(decryptionService),
|
||||
)
|
||||
|
||||
is ChatMessageEncryptedFileHeaderEvent -> {
|
||||
if (event.isIncluded(account.signer.pubKey)) {
|
||||
val key = event.chatroomKey(account.signer.pubKey)
|
||||
account.chatroomList.removeMessage(key, eventNote)
|
||||
}
|
||||
}
|
||||
|
||||
is DraftEvent -> {
|
||||
// Avoid decrypting over and over again if the event already exist.
|
||||
if (event.pubKey == account.signer.pubKey) {
|
||||
// already deindexed by LocalCache
|
||||
// deindexDraftAsRealEvent(eventNote)
|
||||
}
|
||||
}
|
||||
|
||||
is GiftWrapEvent -> {
|
||||
if (event.recipientPubKey() == account.signer.pubKey) {
|
||||
val innerGiftId = event.innerEventId
|
||||
if (innerGiftId != null) {
|
||||
val innerGiftNote = cache.getNoteIfExists(innerGiftId)
|
||||
val innerGift = innerGiftNote?.event
|
||||
if (innerGift != null) {
|
||||
delete(innerGift, innerGiftNote)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is SealedRumorEvent -> {
|
||||
val rumorId = event.innerEventId
|
||||
if (rumorId != null) {
|
||||
val innerRumorNote = cache.getNoteIfExists(rumorId)
|
||||
val innerRumor = innerRumorNote?.event
|
||||
if (innerRumor != null) {
|
||||
delete(innerRumor, innerRumorNote)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is LnZapEvent -> {
|
||||
event.zapRequest?.let { req ->
|
||||
if (req.isPrivateZap()) {
|
||||
// We can't know which account this was for without going through it.
|
||||
account.privateZapsDecryptionCache.delete(req)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is LnZapRequestEvent -> {
|
||||
if (event.isPrivateZap()) {
|
||||
account.privateZapsDecryptionCache.delete(event)
|
||||
}
|
||||
}
|
||||
// ..
|
||||
suspend fun runNew(newNotes: Set<Note>) {
|
||||
try {
|
||||
newNotes.forEach { consume(it) }
|
||||
handleDeletedDrafts(newNotes)
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
Log.e("EventProcessor", "Error processing batch", e)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun runDeleted(notes: Set<Note>) {
|
||||
try {
|
||||
notes.forEach { delete(it) }
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
Log.e("EventProcessor", "Error deleting batch", e)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun handleDeletedDrafts(newNotes: Set<Note>) {
|
||||
val deletedDrafts =
|
||||
newNotes.mapNotNull { note ->
|
||||
val event = note.event
|
||||
if (event is DraftEvent && event.isDeleted()) note else null
|
||||
}
|
||||
|
||||
if (deletedDrafts.isNotEmpty()) {
|
||||
Log.w("EventProcessor", "Deleting ${deletedDrafts.size} draft notes")
|
||||
account.delete(deletedDrafts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class DecryptionService(
|
||||
val account: Account,
|
||||
) {
|
||||
suspend fun unwrapGiftWrap(event: GiftWrapEvent): Event? = event.unwrapOrNull(account.signer)
|
||||
|
||||
suspend fun unsealRumor(event: SealedRumorEvent): Event? = event.unsealOrNull(account.signer)
|
||||
|
||||
suspend fun decryptDraft(event: DraftEvent): Event? =
|
||||
account.draftsDecryptionCache.preCachedDraft(event)
|
||||
?: account.draftsDecryptionCache.cachedDraft(event)
|
||||
|
||||
suspend fun handlePrivateZap(event: LnZapRequestEvent) {
|
||||
if (account.privateZapsDecryptionCache.cachedPrivateZap(event) == null && event.isPrivateZap()) {
|
||||
account.privateZapsDecryptionCache.decryptPrivateZap(event)
|
||||
}
|
||||
}
|
||||
|
||||
fun deletePrivateZap(event: LnZapRequestEvent) {
|
||||
if (event.isPrivateZap()) {
|
||||
account.privateZapsDecryptionCache.delete(event)
|
||||
}
|
||||
}
|
||||
|
||||
fun deletePrivateZapFromZapEvent(event: LnZapEvent) {
|
||||
event.zapRequest?.let { req ->
|
||||
if (req.isPrivateZap()) {
|
||||
account.privateZapsDecryptionCache.delete(req)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class IndexingService(
|
||||
private val account: Account,
|
||||
private val cache: LocalCache,
|
||||
) {
|
||||
fun indexDraftAsRealEvent(
|
||||
draftEventWrap: Note,
|
||||
rumor: Event,
|
||||
) {
|
||||
setupReplyRelationships(draftEventWrap, rumor)
|
||||
indexByEventType(draftEventWrap, rumor)
|
||||
}
|
||||
|
||||
private fun setupReplyRelationships(
|
||||
draftEventWrap: Note,
|
||||
rumor: Event,
|
||||
) {
|
||||
draftEventWrap.replyTo = cache.computeReplyTo(rumor)
|
||||
draftEventWrap.replyTo?.forEach { it.addReply(draftEventWrap) }
|
||||
}
|
||||
|
||||
private fun indexByEventType(
|
||||
draftEventWrap: Note,
|
||||
rumor: Event,
|
||||
) {
|
||||
val chatroomService = ChatroomService(account)
|
||||
|
||||
when (rumor) {
|
||||
is PrivateDmEvent -> {
|
||||
if (rumor.canDecrypt(account.signer)) {
|
||||
val talkingWith = rumor.chatroomKey(account.signer.pubKey)
|
||||
account.chatroomList.addMessage(talkingWith, draftEventWrap)
|
||||
val key = rumor.chatroomKey(account.signer.pubKey)
|
||||
chatroomService.addMessage(key, draftEventWrap)
|
||||
}
|
||||
}
|
||||
is ChatMessageEvent -> {
|
||||
if (rumor.isIncluded(account.signer.pubKey)) {
|
||||
val key = rumor.chatroomKey(account.signer.pubKey)
|
||||
account.chatroomList.addMessage(key, draftEventWrap)
|
||||
chatroomService.addMessage(key, draftEventWrap)
|
||||
}
|
||||
}
|
||||
is ChatMessageEncryptedFileHeaderEvent -> {
|
||||
if (rumor.isIncluded(account.signer.pubKey)) {
|
||||
val key = rumor.chatroomKey(account.signer.pubKey)
|
||||
account.chatroomList.addMessage(key, draftEventWrap)
|
||||
chatroomService.addMessage(key, draftEventWrap)
|
||||
}
|
||||
}
|
||||
is EphemeralChatEvent -> {
|
||||
rumor.roomId()?.let {
|
||||
val channel = cache.getOrCreateEphemeralChannel(it)
|
||||
rumor.roomId()?.let { roomId ->
|
||||
val channel = cache.getOrCreateEphemeralChannel(roomId)
|
||||
channel.addNote(draftEventWrap, null)
|
||||
}
|
||||
}
|
||||
@@ -291,41 +229,303 @@ class DecryptAndIndexProcessor(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun runNew(newNotes: Set<Note>) {
|
||||
try {
|
||||
newNotes.forEach {
|
||||
consume(it)
|
||||
}
|
||||
class ChatroomService(
|
||||
val account: Account,
|
||||
) {
|
||||
fun addMessage(
|
||||
chatroomKey: ChatroomKey,
|
||||
note: Note,
|
||||
) {
|
||||
account.chatroomList.addMessage(chatroomKey, note)
|
||||
}
|
||||
|
||||
val toDelete =
|
||||
newNotes.mapNotNull {
|
||||
val noteEvent = it.event
|
||||
if (noteEvent is DraftEvent && noteEvent.isDeleted()) {
|
||||
it
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
fun removeMessage(
|
||||
chatroomKey: ChatroomKey,
|
||||
note: Note,
|
||||
) {
|
||||
account.chatroomList.removeMessage(chatroomKey, note)
|
||||
}
|
||||
}
|
||||
|
||||
if (toDelete.isNotEmpty()) {
|
||||
Log.w("PrecacheNewNotesProcessor", "Deleting ${toDelete.size} draft notes that should have been deleted already")
|
||||
account.delete(toDelete)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
Log.e("PrecacheNewNotesProcessor", "This shouldn't happen", e)
|
||||
interface EventHandler {
|
||||
suspend fun process(
|
||||
event: Event,
|
||||
eventNote: Note,
|
||||
publicNote: Note,
|
||||
) {}
|
||||
|
||||
suspend fun delete(
|
||||
event: Event,
|
||||
eventNote: Note,
|
||||
) {}
|
||||
}
|
||||
|
||||
class PrivateDmHandler(
|
||||
private val chatroomService: ChatroomService,
|
||||
) : EventHandler {
|
||||
override suspend fun process(
|
||||
event: Event,
|
||||
eventNote: Note,
|
||||
publicNote: Note,
|
||||
) {
|
||||
event as PrivateDmEvent
|
||||
if (event.canDecrypt(chatroomService.account.signer.pubKey)) {
|
||||
val key = event.chatroomKey(chatroomService.account.signer.pubKey)
|
||||
chatroomService.addMessage(key, eventNote)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun runDeleted(newNotes: Set<Note>) {
|
||||
try {
|
||||
newNotes.forEach {
|
||||
delete(it)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
Log.e("PrecacheNewNotesProcessor", "This shouldn't happen", e)
|
||||
override suspend fun delete(
|
||||
event: Event,
|
||||
eventNote: Note,
|
||||
) {
|
||||
event as PrivateDmEvent
|
||||
if (event.canDecrypt(chatroomService.account.signer.pubKey)) {
|
||||
val key = event.chatroomKey(chatroomService.account.signer.pubKey)
|
||||
chatroomService.removeMessage(key, eventNote)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ChatMessageHandler(
|
||||
private val chatroomService: ChatroomService,
|
||||
) : EventHandler {
|
||||
override suspend fun process(
|
||||
event: Event,
|
||||
eventNote: Note,
|
||||
publicNote: Note,
|
||||
) {
|
||||
event as ChatMessageEvent
|
||||
if (event.isIncluded(chatroomService.account.signer.pubKey)) {
|
||||
val key = event.chatroomKey(chatroomService.account.signer.pubKey)
|
||||
chatroomService.addMessage(key, eventNote)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun delete(
|
||||
event: Event,
|
||||
eventNote: Note,
|
||||
) {
|
||||
event as ChatMessageEvent
|
||||
if (event.isIncluded(chatroomService.account.signer.pubKey)) {
|
||||
val key = event.chatroomKey(chatroomService.account.signer.pubKey)
|
||||
chatroomService.removeMessage(key, eventNote)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ChatMessageEncryptedFileHeaderHandler(
|
||||
private val chatroomService: ChatroomService,
|
||||
) : EventHandler {
|
||||
override suspend fun process(
|
||||
event: Event,
|
||||
eventNote: Note,
|
||||
publicNote: Note,
|
||||
) {
|
||||
event as ChatMessageEncryptedFileHeaderEvent
|
||||
if (event.isIncluded(chatroomService.account.signer.pubKey)) {
|
||||
val key = event.chatroomKey(chatroomService.account.signer.pubKey)
|
||||
chatroomService.addMessage(key, eventNote)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun delete(
|
||||
event: Event,
|
||||
eventNote: Note,
|
||||
) {
|
||||
event as ChatMessageEncryptedFileHeaderEvent
|
||||
if (event.isIncluded(chatroomService.account.signer.pubKey)) {
|
||||
val key = event.chatroomKey(chatroomService.account.signer.pubKey)
|
||||
chatroomService.removeMessage(key, eventNote)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class OtsEventHandler(
|
||||
private val account: Account,
|
||||
) : EventHandler {
|
||||
override suspend fun process(
|
||||
event: Event,
|
||||
eventNote: Note,
|
||||
publicNote: Note,
|
||||
) {
|
||||
event as OtsEvent
|
||||
Amethyst.instance.otsVerifCache.cacheVerify(event, account.otsResolverBuilder)
|
||||
}
|
||||
}
|
||||
|
||||
class DraftEventHandler(
|
||||
private val decryptionService: DecryptionService,
|
||||
private val indexingService: IndexingService,
|
||||
) : EventHandler {
|
||||
override suspend fun process(
|
||||
event: Event,
|
||||
eventNote: Note,
|
||||
publicNote: Note,
|
||||
) {
|
||||
event as DraftEvent
|
||||
if (event.pubKey == decryptionService.account.signer.pubKey && !event.isDeleted()) {
|
||||
val rumor = decryptionService.decryptDraft(event)
|
||||
rumor?.let { indexingService.indexDraftAsRealEvent(eventNote, it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class GiftWrapEventHandler(
|
||||
private val decryptionService: DecryptionService,
|
||||
private val cache: LocalCache,
|
||||
private val eventProcessor: EventProcessor,
|
||||
) : EventHandler {
|
||||
override suspend fun process(
|
||||
event: Event,
|
||||
eventNote: Note,
|
||||
publicNote: Note,
|
||||
) {
|
||||
event as GiftWrapEvent
|
||||
if (event.recipientPubKey() != decryptionService.account.signer.pubKey) return
|
||||
|
||||
val innerGiftId = event.innerEventId
|
||||
if (innerGiftId == null) {
|
||||
processNewGiftWrap(event, eventNote, publicNote)
|
||||
} else {
|
||||
processExistingGiftWrap(innerGiftId, publicNote)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun delete(
|
||||
event: Event,
|
||||
eventNote: Note,
|
||||
) {
|
||||
event as GiftWrapEvent
|
||||
if (event.recipientPubKey() != decryptionService.account.signer.pubKey) return
|
||||
|
||||
event.innerEventId?.let { innerGiftId ->
|
||||
val innerGiftNote = cache.getNoteIfExists(innerGiftId)
|
||||
innerGiftNote?.event?.let { innerGift ->
|
||||
eventProcessor.deleteEvent(innerGift, innerGiftNote)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun processNewGiftWrap(
|
||||
event: GiftWrapEvent,
|
||||
eventNote: Note,
|
||||
publicNote: Note,
|
||||
) {
|
||||
val innerGift = decryptionService.unwrapGiftWrap(event) ?: return
|
||||
|
||||
eventNote.event = event.copyNoContent()
|
||||
if (cache.justConsume(innerGift, null, false)) {
|
||||
cache.copyRelaysFromTo(publicNote, innerGift)
|
||||
val innerGiftNote = cache.getOrCreateNote(innerGift.id)
|
||||
eventProcessor.processEvent(innerGift, innerGiftNote, publicNote)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun processExistingGiftWrap(
|
||||
innerGiftId: String,
|
||||
publicNote: Note,
|
||||
) {
|
||||
cache.copyRelaysFromTo(publicNote, innerGiftId)
|
||||
val innerGiftNote = cache.getOrCreateNote(innerGiftId)
|
||||
innerGiftNote.event?.let { innerGift ->
|
||||
eventProcessor.processEvent(innerGift, innerGiftNote, publicNote)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SealedRumorEventHandler(
|
||||
private val decryptionService: DecryptionService,
|
||||
private val cache: LocalCache,
|
||||
private val eventProcessor: EventProcessor,
|
||||
) : EventHandler {
|
||||
override suspend fun process(
|
||||
event: Event,
|
||||
eventNote: Note,
|
||||
publicNote: Note,
|
||||
) {
|
||||
event as SealedRumorEvent
|
||||
|
||||
val rumorId = event.innerEventId
|
||||
if (rumorId == null) {
|
||||
processNewSealedRumor(event, eventNote, publicNote)
|
||||
} else {
|
||||
processExistingSealedRumor(rumorId, publicNote)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun delete(
|
||||
event: Event,
|
||||
eventNote: Note,
|
||||
) {
|
||||
event as SealedRumorEvent
|
||||
|
||||
event.innerEventId?.let { rumorId ->
|
||||
val innerRumorNote = cache.getNoteIfExists(rumorId)
|
||||
innerRumorNote?.event?.let { innerRumor ->
|
||||
eventProcessor.deleteEvent(innerRumor, innerRumorNote)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun processNewSealedRumor(
|
||||
event: SealedRumorEvent,
|
||||
eventNote: Note,
|
||||
publicNote: Note,
|
||||
) {
|
||||
val innerRumor = decryptionService.unsealRumor(event) ?: return
|
||||
|
||||
eventNote.event = event.copyNoContent()
|
||||
cache.justConsume(innerRumor, null, true)
|
||||
cache.copyRelaysFromTo(publicNote, innerRumor)
|
||||
|
||||
val innerRumorNote = cache.getOrCreateNote(innerRumor.id)
|
||||
eventProcessor.processEvent(innerRumor, innerRumorNote, publicNote)
|
||||
}
|
||||
|
||||
private suspend fun processExistingSealedRumor(
|
||||
rumorId: String,
|
||||
publicNote: Note,
|
||||
) {
|
||||
cache.copyRelaysFromTo(publicNote, rumorId)
|
||||
val innerRumorNote = cache.getOrCreateNote(rumorId)
|
||||
innerRumorNote.event?.let { innerRumor ->
|
||||
eventProcessor.processEvent(innerRumor, innerRumorNote, publicNote)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class LnZapRequestEventHandler(
|
||||
private val decryptionService: DecryptionService,
|
||||
) : EventHandler {
|
||||
override suspend fun process(
|
||||
event: Event,
|
||||
eventNote: Note,
|
||||
publicNote: Note,
|
||||
) {
|
||||
event as LnZapRequestEvent
|
||||
decryptionService.handlePrivateZap(event)
|
||||
}
|
||||
|
||||
override suspend fun delete(
|
||||
event: Event,
|
||||
eventNote: Note,
|
||||
) {
|
||||
event as LnZapRequestEvent
|
||||
decryptionService.deletePrivateZap(event)
|
||||
}
|
||||
}
|
||||
|
||||
class LnZapEventHandler(
|
||||
private val decryptionService: DecryptionService,
|
||||
) : EventHandler {
|
||||
override suspend fun delete(
|
||||
event: Event,
|
||||
eventNote: Note,
|
||||
) {
|
||||
event as LnZapEvent
|
||||
decryptionService.deletePrivateZapFromZapEvent(event)
|
||||
}
|
||||
}
|
||||
|
@@ -50,7 +50,14 @@
|
||||
<string name="login_with_a_private_key_to_be_able_to_follow">Używasz klucza publicznego, a klucze publiczne są tylko do odczytu. Zaloguj się za pomocą klucza prywatnego, aby móc obserwować</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_unfollow">Używasz klucza publicznego, a klucze publiczne są tylko do odczytu. Zaloguj się za pomocą klucza prywatnego, aby móc przestać obserwować</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_hide_word">Używasz klucza publicznego, a klucze publiczne są tylko do odczytu. Zaloguj się za pomocą klucza prywatnego, aby móc ukryć słowo lub zdanie</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_show_word">Używasz klucza publicznego, a klucze publiczne są tylko do odczytu. Zaloguj się za pomocą klucza prywatnego, aby móc pokazać słowo lub zdanie</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_show_word">Używasz klucza publicznego, a klucze publiczne są tylko do odczytu. Zaloguj się za pomocą klucza prywatnego, aby móc pokazać wyraz lub zdanie</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_change_settings">Używasz klucza publicznego i kluczy publicznych tylko do odczytu. Zaloguj się za pomocą klucza prywatnego, aby móc zmieniać ustawienia</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_upload">Używasz klucza publicznego i kluczy publicznych tylko do odczytu. Zaloguj się za pomocą klucza prywatnego, aby móc wgrać</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_sign_events">Używasz klucza publicznego i kluczy publicznych tylko do odczytu. Zaloguj się za pomocą klucza prywatnego, aby móc zapisać się na udział w wydarzeniu</string>
|
||||
<string name="unauthorized_exception">Nieautoryzowane odszyfrowywanie</string>
|
||||
<string name="unauthorized_exception_description">Sygnatariusz nie autoryzował deszyfrowania wymaganego do wykonania tej operacji. Aktywuj deszyfrowanie NIP-44 w aplikacji podpisującej i spróbuj ponownie</string>
|
||||
<string name="signer_not_found_exception">Nie odnaleziono sygnatariusza</string>
|
||||
<string name="signer_not_found_exception_description">Czy aplikacja sygnatariusza została odinstalowana? Sprawdź, czy aplikacja sygnatariusza jest zainstalowana i czy ma to konto. Wyloguj się i zaloguj ponownie, jeśli aplikacja sygnatariusza uległa zmianie.</string>
|
||||
<string name="zaps">Zapy</string>
|
||||
<string name="view_count">Liczba wyświetleń</string>
|
||||
<string name="boost">Promuj</string>
|
||||
@@ -98,6 +105,7 @@
|
||||
<string name="my_awesome_group">Nazwa Grupy</string>
|
||||
<string name="picture_url">Adres Url obrazka</string>
|
||||
<string name="description">Opis</string>
|
||||
<string name="no_description">Nie znaleziono opisu</string>
|
||||
<string name="about_us">"Informacja o grupie…"</string>
|
||||
<string name="what_s_on_your_mind">Co masz na myśli?</string>
|
||||
<string name="write_a_message">Napisz wiadomość…</string>
|
||||
@@ -139,6 +147,10 @@
|
||||
<string name="video_saved_to_the_gallery">Film zapisany w galerii filmów</string>
|
||||
<string name="failed_to_save_the_video">Nie udało się zapisać filmu</string>
|
||||
<string name="upload_image">Dodaj zdjęcie</string>
|
||||
<string name="take_a_picture">Zrób zdjęcie</string>
|
||||
<string name="record_a_message">Nagraj wiadomość</string>
|
||||
<string name="record_a_message_title">Nagrywanie wiadomości</string>
|
||||
<string name="record_a_message_description">Kliknij i przytrzymaj aby nagrać wiadomość</string>
|
||||
<string name="uploading">Wgrywanie…</string>
|
||||
<string name="user_does_not_have_a_lightning_address_setup_to_receive_sats">Użytkownik nie ma skonfigurowanego adresu LN, aby odbierać satsy</string>
|
||||
<string name="reply_here">"odpowiedz tutaj.. "</string>
|
||||
@@ -228,6 +240,8 @@
|
||||
<string name="public_chat_explainer">Czaty publiczne są widoczne dla wszystkich użytkowników Nostr i każdy może w nich uczestniczyć. Są idealne dla otwartych społeczności skupionych wokół konkretnych tematów. Moderację można kontrolować, usuwając posty z transmiterów</string>
|
||||
<string name="public_chat_relays_title">Transmitery</string>
|
||||
<string name="public_chat_relays_explainer">Wstaw od 1 do 3 transmiterów, które obsługują tę grupę. Klienci Nostr używają tego ustawienia, aby wiedzieć, skąd pobierać wiadomości i do kogo je wysyłać.</string>
|
||||
<string name="paid_relay">Płatny transmiter</string>
|
||||
<string name="tor_relay">Wymusza Tor podczas łączenia</string>
|
||||
<string name="posts_received">odebranych wiadomości</string>
|
||||
<string name="remove">Usuń</string>
|
||||
<string name="translations_auto">Automatycznie</string>
|
||||
@@ -272,6 +286,7 @@
|
||||
<string name="biometric_error">Błąd</string>
|
||||
<string name="badge_created_by">"Utworzony przez %1$s"</string>
|
||||
<string name="badge_award_image_for">"Wizerunek odznaki dla %1$s"</string>
|
||||
<string name="badge_award_image">Wizerunek odznaki</string>
|
||||
<string name="new_badge_award_notif">Otrzymałeś nową odznakę</string>
|
||||
<string name="award_granted_to">Nagroda przyznana dla</string>
|
||||
<string name="copied_note_text_to_clipboard">Skopiowano tekst wpisu do schowka</string>
|
||||
@@ -419,6 +434,7 @@
|
||||
<string name="no">Nie</string>
|
||||
<string name="follow_list_selection">Lista obserwowanych</string>
|
||||
<string name="follow_list_kind3follows">Obserwowane</string>
|
||||
<string name="follow_list_kind3follows_proxy">Obserwuje przez proxy</string>
|
||||
<string name="follow_list_aroundme">W pobliżu</string>
|
||||
<string name="follow_list_global">Wszystkie</string>
|
||||
<string name="follow_list_mute_list">Zablokowane</string>
|
||||
@@ -690,6 +706,7 @@
|
||||
<string name="wallet_number">Portfel %1$s</string>
|
||||
<string name="error_opening_external_signer">Błąd podczas otwierania aplikacji podpisującej</string>
|
||||
<string name="error_opening_external_signer_description">Nie można odnaleźć aplikacji podpisującej. Sprawdź, czy aplikacja nie została odinstalowana</string>
|
||||
<string name="sign_request_rejected2">Prośba o zalogowanie została odrzucona</string>
|
||||
<string name="sign_request_rejected">Odrzucono aplikację podpisującego</string>
|
||||
<string name="sign_request_rejected_description">Upewnij się, że aplikacja podpisującego autoryzuje tę transakcję</string>
|
||||
<string name="no_wallet_found_with_error">Nie znaleziono portfeli do zapłacenia faktury z Lightning (Error: %1$s). Proszę zainstalować Lightning wallet, aby używać zapów</string>
|
||||
@@ -873,12 +890,29 @@
|
||||
<string name="private_outbox_section_explainer">Wstaw od 1 do 3 transmiterów przechowujących dane zdarzeń, których nikt inny nie widzi, takich jak Wersje robocze i/lub ustawienia aplikacji. Idealnie byłoby, gdyby te transmitery były lokalne lub wymagały uwierzytelnienia przed pobraniem treści każdego użytkownika.</string>
|
||||
<string name="kind_3_section">Transmitery ogólne</string>
|
||||
<string name="kind_3_section_description">Amethyst używa tych przekaźników, aby pobrać dla Ciebie posty.</string>
|
||||
<string name="connected_section">Podłączone Transmitery</string>
|
||||
<string name="connected_section_description">Aktualna lista używanych transmiterów</string>
|
||||
<string name="kind_3_recommended_section">Polecane Transmitery</string>
|
||||
<string name="kind_3_recommended_section_description">Dodaj te transmitery do głównej listy, aby otrzymywać wiadomości od wymienionych użytkowników.</string>
|
||||
<string name="search_section">Transmitery wyszukujące</string>
|
||||
<string name="search_section_explainer">Lista transmiterów używanych podczas wyszukiwania treści lub użytkowników. Tagowanie i wyszukiwanie nie będą działać, jeśli nie będą dostępne żadne opcje. Upewnij się, że zaimplementowano NIP-50.</string>
|
||||
<string name="local_section">Lokalne Transmitery</string>
|
||||
<string name="local_section_explainer">Lista transmiterów działających na tym urządzeniu.</string>
|
||||
<string name="trusted_relays_title">Zaufane Transmitery</string>
|
||||
<string name="trusted_section">Zaufane Transmitery</string>
|
||||
<string name="trusted_section_explainer">Transmitery, którym ufasz, nie potrzebujesz połączenia Tor</string>
|
||||
<string name="proxy_relays_title">Transmitery Proxy</string>
|
||||
<string name="proxy_section">Transmitery Proxy</string>
|
||||
<string name="proxy_section_explainer">Transmitery agregatora, z których aplikacja musi pobierać kanały, takie jak filter.nostr.wine. To zastępuje model skrzynki nadawczej i sprawia, że aplikacja łączy się tylko z transmiterami z listy.</string>
|
||||
<string name="broadcast_relays_title">Transmitery Nadawcze</string>
|
||||
<string name="broadcast_section">Transmitery Nadawania</string>
|
||||
<string name="broadcast_section_explainer">Transmitery, które specjalizują się w przesyłaniu wpisów do wszystkich innych transmiterów, takich jak sendit.nosflare.com. Amethyst doda ten transmiter do wszystkich nowych wydarzeń utworzonych przez użytkownika</string>
|
||||
<string name="indexer_relays_title">Transmitery Indeksera</string>
|
||||
<string name="indexer_section">Transmitery indeksera</string>
|
||||
<string name="indexer_section_explainer">Transmitery, które specjalizują się w hostowaniu metadanych i list wszystkich użytkowników, takie jak purplepag.es. Amethyst użyje tych transmiterów do znalezienia użytkowników, którzy nie znajdują się na twoich listach.</string>
|
||||
<string name="blocked_relays_title">Zablokowane Transmitery</string>
|
||||
<string name="blocked_section">Zablokowane Transmitery</string>
|
||||
<string name="blocked_section_explainer">Amethyst nigdy nie połączy się z tymi transmiterami</string>
|
||||
<string name="zap_the_devs_title">Wspieraj deweloperów!</string>
|
||||
<string name="zap_the_devs_description">Twoja darowizna pomaga nam coś zmienić. Każdy sat się liczy!</string>
|
||||
<string name="donate_now">Przekaż darowiznę</string>
|
||||
@@ -968,6 +1002,7 @@
|
||||
<string name="select_list_to_filter">Wybierz listę, aby filtrować kanał</string>
|
||||
<string name="temporary_account">Wyloguj się przy blokowaniu urządzenia</string>
|
||||
<string name="private_message">Wiadomość prywatna</string>
|
||||
<string name="public_message">Publiczna wiadomość</string>
|
||||
<string name="group_relay">Transmiter Czatu</string>
|
||||
<string name="group_relay_explanation">Transmiter, z którym łączą się wszyscy użytkownicy tego czatu</string>
|
||||
<string name="share_image">Udostępnij zdjęcie…</string>
|
||||
|
@@ -51,6 +51,13 @@
|
||||
<string name="login_with_a_private_key_to_be_able_to_unfollow">你正在使用公钥,公钥是只读的。使用私钥登录以便能够取消关注</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_hide_word">你正在使用公钥,公钥是只读的。使用私钥登录以便能够隐藏单词或句子</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_show_word">你正在使用公钥,公钥是只读的。使用私钥登录以便能够显示单词或句子</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_change_settings">你正在使用公钥,公钥是只读的。要更改设置请用私钥登录</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_upload">你正在使用公钥,公钥是只读的。要上传请使用私钥登录</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_sign_events">你正在使用公钥,公钥是只读的。要报名参加活动请使用私钥登录</string>
|
||||
<string name="unauthorized_exception">未授权的解密</string>
|
||||
<string name="unauthorized_exception_description">签名人没有授权进行此操作所需的解密。在签名人应用中激活 NIP-44 解密并重试</string>
|
||||
<string name="signer_not_found_exception">未找到签名人</string>
|
||||
<string name="signer_not_found_exception_description">签名人应用被卸载了吗?检查是否安装了签名人以及是否签名人有该账户。注销并再次登录,签名人应用已更改。</string>
|
||||
<string name="zaps">打闪</string>
|
||||
<string name="view_count">浏览次数</string>
|
||||
<string name="boost">提升</string>
|
||||
@@ -98,6 +105,7 @@
|
||||
<string name="my_awesome_group">我的精彩群聊</string>
|
||||
<string name="picture_url">图片链接</string>
|
||||
<string name="description">描述</string>
|
||||
<string name="no_description">未找到描述</string>
|
||||
<string name="about_us">"关于我们.. "</string>
|
||||
<string name="what_s_on_your_mind">你在想什么?</string>
|
||||
<string name="write_a_message">写一条消息…</string>
|
||||
@@ -139,6 +147,10 @@
|
||||
<string name="video_saved_to_the_gallery">视频已保存到媒体库</string>
|
||||
<string name="failed_to_save_the_video">保存视频失败</string>
|
||||
<string name="upload_image">上传图片</string>
|
||||
<string name="take_a_picture">拍照</string>
|
||||
<string name="record_a_message">录制消息</string>
|
||||
<string name="record_a_message_title">录制消息</string>
|
||||
<string name="record_a_message_description">单击并按住来录制消息</string>
|
||||
<string name="uploading">上传中…</string>
|
||||
<string name="user_does_not_have_a_lightning_address_setup_to_receive_sats">用户尚未设置闪电地址以接收聪</string>
|
||||
<string name="reply_here">"🔏在此回复… "</string>
|
||||
@@ -231,6 +243,8 @@
|
||||
<string name="public_chat_relays_title">中继器</string>
|
||||
<string name="public_chat_relays_explainer">使用 1~3 个中继托管这个群组。
|
||||
让 Nostr 客户端知道应该使用这些中继配置来发送和下载消息。</string>
|
||||
<string name="paid_relay">付费中继</string>
|
||||
<string name="tor_relay">连接时强制使用 Tor</string>
|
||||
<string name="posts_received">收到文章</string>
|
||||
<string name="remove">移除</string>
|
||||
<string name="translations_auto">自动</string>
|
||||
@@ -275,6 +289,7 @@
|
||||
<string name="biometric_error">错误</string>
|
||||
<string name="badge_created_by">"由 %1$s 创建"</string>
|
||||
<string name="badge_award_image_for">"颁发给 %1$s 的徽章图片"</string>
|
||||
<string name="badge_award_image">\"徽章奖品图片</string>
|
||||
<string name="new_badge_award_notif">你收到了新的徽章奖励</string>
|
||||
<string name="award_granted_to">徽章奖励授予</string>
|
||||
<string name="copied_note_text_to_clipboard">文本已复制到剪贴板</string>
|
||||
@@ -422,6 +437,7 @@
|
||||
<string name="no">否</string>
|
||||
<string name="follow_list_selection">关注列表</string>
|
||||
<string name="follow_list_kind3follows">所有关注</string>
|
||||
<string name="follow_list_kind3follows_proxy">通过代理关注</string>
|
||||
<string name="follow_list_aroundme">周围的人</string>
|
||||
<string name="follow_list_global">全球</string>
|
||||
<string name="follow_list_mute_list">静音列表</string>
|
||||
@@ -693,6 +709,7 @@
|
||||
<string name="wallet_number">钱包 %1$s</string>
|
||||
<string name="error_opening_external_signer">打开签名应用时出错</string>
|
||||
<string name="error_opening_external_signer_description">找不到签名应用。检查应用是否已被卸载</string>
|
||||
<string name="sign_request_rejected2">签名请求被拒绝了</string>
|
||||
<string name="sign_request_rejected">签名请求被拒绝了</string>
|
||||
<string name="sign_request_rejected_description">请确保签名应用程序已授权此交易</string>
|
||||
<string name="no_wallet_found_with_error">找不到支付闪电发票的钱包(错误:%1$s)。请安装闪电钱包来使用打闪</string>
|
||||
@@ -876,12 +893,29 @@
|
||||
<string name="private_outbox_section_explainer">设置 1 ~ 3 个中继用于保存其他人无法看到的事件,例如您的草稿和软件设置。理想情况下,这些应该是本地中继,或者是在获取用户内容时需要经过身份验证的中继。</string>
|
||||
<string name="kind_3_section">通用中继</string>
|
||||
<string name="kind_3_section_description">Amethyst 会通过这些中继为您获取帖子。</string>
|
||||
<string name="connected_section">已连接的中继</string>
|
||||
<string name="connected_section_description">当前正在使用的中继列表</string>
|
||||
<string name="kind_3_recommended_section">推荐的中继器</string>
|
||||
<string name="kind_3_recommended_section_description">在通用中继列表中添加下列中继以接收列表中用户的帖子。</string>
|
||||
<string name="search_section">搜索中继</string>
|
||||
<string name="search_section_explainer">用于关键词和标签检索的中继列表。如果没有可用选项,也就无法使用关键词和标签检索。需要确保它们支持 NIP-50。</string>
|
||||
<string name="local_section">本地中继</string>
|
||||
<string name="local_section_explainer">在该设备中运行的中继列表。</string>
|
||||
<string name="trusted_relays_title">可信中继</string>
|
||||
<string name="trusted_section">可信中继</string>
|
||||
<string name="trusted_section_explainer">你信任的无需 Tor 连接的中继</string>
|
||||
<string name="proxy_relays_title">代理中继</string>
|
||||
<string name="proxy_section">代理中继</string>
|
||||
<string name="proxy_section_explainer">聚合器中继下载 feed 必须使用的应用的流量,类似 filter.nostr.wine。这替代 outbox 模型,让应用只连接到列表中的中继。</string>
|
||||
<string name="broadcast_relays_title">广播中继</string>
|
||||
<string name="broadcast_section">广播中继</string>
|
||||
<string name="broadcast_section_explainer">专门推送便笩到所有其他中继的中继,类似 sendit.nosflare.com。Amethyst 会将这个中继添加到所有你所做的新事件</string>
|
||||
<string name="indexer_relays_title">索引器中继</string>
|
||||
<string name="indexer_section">索引器中继</string>
|
||||
<string name="indexer_section_explainer">专门托管每个人的元数据和中继列表的中继,类似 purplepag.es。Amethyst将使用这些中继来查找不在列表中的用户。</string>
|
||||
<string name="blocked_relays_title">屏蔽的中继</string>
|
||||
<string name="blocked_section">屏蔽的中继</string>
|
||||
<string name="blocked_section_explainer">Amethyst 永远不会连接到这些中继</string>
|
||||
<string name="zap_the_devs_title">打闪开发人员!</string>
|
||||
<string name="zap_the_devs_description">你的捐赠帮助我们做出不同的贡献。每个聪都很重要!</string>
|
||||
<string name="donate_now">立即捐款</string>
|
||||
@@ -971,6 +1005,7 @@
|
||||
<string name="select_list_to_filter">选择一个用于过滤订阅源的列表</string>
|
||||
<string name="temporary_account">当设备锁定时注销</string>
|
||||
<string name="private_message">私信</string>
|
||||
<string name="public_message">公开消息</string>
|
||||
<string name="group_relay">聊天中继</string>
|
||||
<string name="group_relay_explanation">此聊天所有用户都连接到的中继</string>
|
||||
<string name="share_image">分享图片…</string>
|
||||
|
Reference in New Issue
Block a user