Migrates Events to the App's source code as opposed to NostrPostr:
- Changes them to remove all secondary fields and turn them into functions - Changes them to from being based in ByteArrays to String (since we use Hex everywhere and strings are immutable, we avoid duplicating memory with ByteArrays)
This commit is contained in:
Vendored
+1
-1
@@ -25,7 +25,7 @@
|
|||||||
-keep class fr.acinq.secp256k1.jni.** { *; }
|
-keep class fr.acinq.secp256k1.jni.** { *; }
|
||||||
# For the NostrPostr library
|
# For the NostrPostr library
|
||||||
-keep class nostr.postr.** { *; }
|
-keep class nostr.postr.** { *; }
|
||||||
-keep class nostr.postr.events.** { *; }
|
-keep class com.vitorpamplona.amethyst.service.model.** { *; }
|
||||||
# Json parsing
|
# Json parsing
|
||||||
-keep class com.google.gson.reflect.** { *; }
|
-keep class com.google.gson.reflect.** { *; }
|
||||||
-keep class * extends com.google.gson.reflect.TypeToken
|
-keep class * extends com.google.gson.reflect.TypeToken
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ import com.vitorpamplona.amethyst.model.RelaySetupInfo
|
|||||||
import com.vitorpamplona.amethyst.model.toByteArray
|
import com.vitorpamplona.amethyst.model.toByteArray
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import nostr.postr.Persona
|
import nostr.postr.Persona
|
||||||
import nostr.postr.events.ContactListEvent
|
import com.vitorpamplona.amethyst.service.model.ContactListEvent
|
||||||
import nostr.postr.events.Event
|
import com.vitorpamplona.amethyst.service.model.Event
|
||||||
import nostr.postr.events.Event.Companion.getRefinedEvent
|
import com.vitorpamplona.amethyst.service.model.Event.Companion.getRefinedEvent
|
||||||
import nostr.postr.toHex
|
import nostr.postr.toHex
|
||||||
|
|
||||||
class LocalPreferences(context: Context) {
|
class LocalPreferences(context: Context) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import androidx.lifecycle.LiveData
|
|||||||
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
|
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent
|
import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.model.Contact
|
||||||
import com.vitorpamplona.amethyst.service.model.LnZapRequestEvent
|
import com.vitorpamplona.amethyst.service.model.LnZapRequestEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.ReactionEvent
|
import com.vitorpamplona.amethyst.service.model.ReactionEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.ReportEvent
|
import com.vitorpamplona.amethyst.service.model.ReportEvent
|
||||||
@@ -26,16 +27,12 @@ import kotlinx.coroutines.NonCancellable
|
|||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import nostr.postr.Contact
|
|
||||||
import nostr.postr.Persona
|
import nostr.postr.Persona
|
||||||
import nostr.postr.Utils
|
import com.vitorpamplona.amethyst.service.model.ContactListEvent
|
||||||
import nostr.postr.events.ContactListEvent
|
import com.vitorpamplona.amethyst.service.model.DeletionEvent
|
||||||
import nostr.postr.events.DeletionEvent
|
import com.vitorpamplona.amethyst.service.model.MetadataEvent
|
||||||
import nostr.postr.events.Event
|
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
|
||||||
import nostr.postr.events.MetadataEvent
|
|
||||||
import nostr.postr.events.PrivateDmEvent
|
|
||||||
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
||||||
import nostr.postr.toHex
|
|
||||||
|
|
||||||
val DefaultChannels = setOf(
|
val DefaultChannels = setOf(
|
||||||
"25e5c82273a271cb1a840d0060391a0bf4965cafeb029d5ab55350b418953fbb", // -> Anigma's Nostr
|
"25e5c82273a271cb1a840d0060391a0bf4965cafeb029d5ab55350b418953fbb", // -> Anigma's Nostr
|
||||||
@@ -89,10 +86,11 @@ class Account(
|
|||||||
if (!isWriteable()) return
|
if (!isWriteable()) return
|
||||||
|
|
||||||
val contactList = userProfile().latestContactList
|
val contactList = userProfile().latestContactList
|
||||||
|
val follows = contactList?.follows() ?: emptyList()
|
||||||
|
|
||||||
if (contactList != null && contactList.follows.size > 0) {
|
if (contactList != null && follows.isNotEmpty()) {
|
||||||
val event = ContactListEvent.create(
|
val event = ContactListEvent.create(
|
||||||
contactList.follows,
|
follows,
|
||||||
relays,
|
relays,
|
||||||
loggedIn.privKey!!)
|
loggedIn.privKey!!)
|
||||||
|
|
||||||
@@ -111,14 +109,7 @@ class Account(
|
|||||||
if (!isWriteable()) return
|
if (!isWriteable()) return
|
||||||
|
|
||||||
loggedIn.privKey?.let {
|
loggedIn.privKey?.let {
|
||||||
val createdAt = Date().time / 1000
|
val event = MetadataEvent.create(toString, loggedIn.privKey!!)
|
||||||
val content = toString
|
|
||||||
val pubKey = Utils.pubkeyCreate(it)
|
|
||||||
val tags = listOf<List<String>>()
|
|
||||||
val id = Event.generateId(pubKey, createdAt, MetadataEvent.kind, tags, content)
|
|
||||||
val sig = Utils.sign(id, it)
|
|
||||||
val event = MetadataEvent(id, pubKey, createdAt, tags, content, sig)
|
|
||||||
|
|
||||||
Client.send(event)
|
Client.send(event)
|
||||||
LocalCache.consume(event)
|
LocalCache.consume(event)
|
||||||
}
|
}
|
||||||
@@ -250,10 +241,11 @@ class Account(
|
|||||||
if (!isWriteable()) return
|
if (!isWriteable()) return
|
||||||
|
|
||||||
val contactList = userProfile().latestContactList
|
val contactList = userProfile().latestContactList
|
||||||
|
val follows = contactList?.follows() ?: emptyList()
|
||||||
|
|
||||||
val event = if (contactList != null && contactList.follows.size > 0) {
|
val event = if (contactList != null && follows.isNotEmpty()) {
|
||||||
ContactListEvent.create(
|
ContactListEvent.create(
|
||||||
contactList.follows.plus(Contact(user.pubkeyHex, null)),
|
follows.plus(Contact(user.pubkeyHex, null)),
|
||||||
userProfile().relays,
|
userProfile().relays,
|
||||||
loggedIn.privKey!!)
|
loggedIn.privKey!!)
|
||||||
} else {
|
} else {
|
||||||
@@ -273,10 +265,11 @@ class Account(
|
|||||||
if (!isWriteable()) return
|
if (!isWriteable()) return
|
||||||
|
|
||||||
val contactList = userProfile().latestContactList
|
val contactList = userProfile().latestContactList
|
||||||
|
val follows = contactList?.follows() ?: emptyList()
|
||||||
|
|
||||||
if (contactList != null && contactList.follows.size > 0) {
|
if (contactList != null && follows.isNotEmpty()) {
|
||||||
val event = ContactListEvent.create(
|
val event = ContactListEvent.create(
|
||||||
contactList.follows.filter { it.pubKeyHex != user.pubkeyHex },
|
follows.filter { it.pubKeyHex != user.pubkeyHex },
|
||||||
userProfile().relays,
|
userProfile().relays,
|
||||||
loggedIn.privKey!!)
|
loggedIn.privKey!!)
|
||||||
|
|
||||||
@@ -320,37 +313,6 @@ class Account(
|
|||||||
LocalCache.consume(signedEvent, null)
|
LocalCache.consume(signedEvent, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createPrivateMessageWithReply(
|
|
||||||
recipientPubKey: ByteArray,
|
|
||||||
msg: String,
|
|
||||||
replyTos: List<String>? = null, mentions: List<String>? = null,
|
|
||||||
privateKey: ByteArray,
|
|
||||||
createdAt: Long = Date().time / 1000,
|
|
||||||
publishedRecipientPubKey: ByteArray? = null,
|
|
||||||
advertiseNip18: Boolean = true
|
|
||||||
): PrivateDmEvent {
|
|
||||||
val content = Utils.encrypt(
|
|
||||||
if (advertiseNip18) {
|
|
||||||
PrivateDmEvent.nip18Advertisement
|
|
||||||
} else { "" } + msg,
|
|
||||||
privateKey,
|
|
||||||
recipientPubKey)
|
|
||||||
val pubKey = Utils.pubkeyCreate(privateKey)
|
|
||||||
val tags = mutableListOf<List<String>>()
|
|
||||||
publishedRecipientPubKey?.let {
|
|
||||||
tags.add(listOf("p", publishedRecipientPubKey.toHex()))
|
|
||||||
}
|
|
||||||
replyTos?.forEach {
|
|
||||||
tags.add(listOf("e", it))
|
|
||||||
}
|
|
||||||
mentions?.forEach {
|
|
||||||
tags.add(listOf("p", it))
|
|
||||||
}
|
|
||||||
val id = Event.generateId(pubKey, createdAt, PrivateDmEvent.kind, tags, content)
|
|
||||||
val sig = Utils.sign(id, privateKey)
|
|
||||||
return PrivateDmEvent(id, pubKey, createdAt, tags, content, sig)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun sendPrivateMeesage(message: String, toUser: String, replyingTo: Note? = null) {
|
fun sendPrivateMeesage(message: String, toUser: String, replyingTo: Note? = null) {
|
||||||
if (!isWriteable()) return
|
if (!isWriteable()) return
|
||||||
val user = LocalCache.users[toUser] ?: return
|
val user = LocalCache.users[toUser] ?: return
|
||||||
@@ -358,7 +320,7 @@ class Account(
|
|||||||
val repliesToHex = listOfNotNull(replyingTo?.idHex).ifEmpty { null }
|
val repliesToHex = listOfNotNull(replyingTo?.idHex).ifEmpty { null }
|
||||||
val mentionsHex = emptyList<String>()
|
val mentionsHex = emptyList<String>()
|
||||||
|
|
||||||
val signedEvent = createPrivateMessageWithReply(
|
val signedEvent = PrivateDmEvent.create(
|
||||||
recipientPubKey = user.pubkey(),
|
recipientPubKey = user.pubkey(),
|
||||||
publishedRecipientPubKey = user.pubkey(),
|
publishedRecipientPubKey = user.pubkey(),
|
||||||
msg = message,
|
msg = message,
|
||||||
@@ -386,7 +348,7 @@ class Account(
|
|||||||
Client.send(event)
|
Client.send(event)
|
||||||
LocalCache.consume(event)
|
LocalCache.consume(event)
|
||||||
|
|
||||||
joinChannel(event.id.toHex())
|
joinChannel(event.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun joinChannel(idHex: String) {
|
fun joinChannel(idHex: String) {
|
||||||
@@ -438,7 +400,7 @@ class Account(
|
|||||||
Client.send(event)
|
Client.send(event)
|
||||||
LocalCache.consume(event)
|
LocalCache.consume(event)
|
||||||
|
|
||||||
joinChannel(event.id.toHex())
|
joinChannel(event.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun decryptContent(note: Note): String? {
|
fun decryptContent(note: Note): String? {
|
||||||
@@ -446,26 +408,12 @@ class Account(
|
|||||||
return if (event is PrivateDmEvent && loggedIn.privKey != null) {
|
return if (event is PrivateDmEvent && loggedIn.privKey != null) {
|
||||||
var pubkeyToUse = event.pubKey
|
var pubkeyToUse = event.pubKey
|
||||||
|
|
||||||
val recepientPK = event.recipientPubKey
|
val recepientPK = event.recipientPubKey()
|
||||||
|
|
||||||
if (note.author == userProfile() && recepientPK != null)
|
if (note.author == userProfile() && recepientPK != null)
|
||||||
pubkeyToUse = recepientPK
|
pubkeyToUse = recepientPK
|
||||||
|
|
||||||
return try {
|
event.plainContent(loggedIn.privKey!!, pubkeyToUse.toByteArray())
|
||||||
val sharedSecret = Utils.getSharedSecret(loggedIn.privKey!!, pubkeyToUse)
|
|
||||||
|
|
||||||
val retVal = Utils.decrypt(event.content, sharedSecret)
|
|
||||||
|
|
||||||
if (retVal.startsWith(PrivateDmEvent.nip18Advertisement)) {
|
|
||||||
retVal.substring(16)
|
|
||||||
} else {
|
|
||||||
retVal
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
null
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
event?.content
|
event?.content
|
||||||
}
|
}
|
||||||
@@ -495,10 +443,10 @@ class Account(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun updateContactListTo(newContactList: ContactListEvent?) {
|
private fun updateContactListTo(newContactList: ContactListEvent?) {
|
||||||
if (newContactList?.follows.isNullOrEmpty()) return
|
if (newContactList?.follows().isNullOrEmpty()) return
|
||||||
|
|
||||||
// Events might be different objects, we have to compare their ids.
|
// Events might be different objects, we have to compare their ids.
|
||||||
if (backupContactList?.id?.toHex() != newContactList?.id?.toHex()) {
|
if (backupContactList?.id != newContactList?.id) {
|
||||||
backupContactList = newContactList
|
backupContactList = newContactList
|
||||||
saveable.invalidateData()
|
saveable.invalidateData()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import kotlinx.coroutines.NonCancellable
|
|||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import nostr.postr.events.Event
|
import com.vitorpamplona.amethyst.service.model.Event
|
||||||
import nostr.postr.toHex
|
import nostr.postr.toHex
|
||||||
|
|
||||||
data class Spammer(val pubkeyHex: HexKey, var duplicatedMessages: Set<HexKey>)
|
data class Spammer(val pubkeyHex: HexKey, var duplicatedMessages: Set<HexKey>)
|
||||||
@@ -22,7 +22,7 @@ class AntiSpamFilter {
|
|||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun isSpam(event: Event): Boolean {
|
fun isSpam(event: Event): Boolean {
|
||||||
val idHex = event.id.toHexKey()
|
val idHex = event.id
|
||||||
|
|
||||||
// if already processed, ok
|
// if already processed, ok
|
||||||
if (LocalCache.notes[idHex] != null) return false
|
if (LocalCache.notes[idHex] != null) return false
|
||||||
@@ -37,15 +37,15 @@ class AntiSpamFilter {
|
|||||||
val hash = (event.content + event.tags.flatten().joinToString(",")).hashCode()
|
val hash = (event.content + event.tags.flatten().joinToString(",")).hashCode()
|
||||||
|
|
||||||
if ((recentMessages[hash] != null && recentMessages[hash] != idHex) || spamMessages[hash] != null) {
|
if ((recentMessages[hash] != null && recentMessages[hash] != idHex) || spamMessages[hash] != null) {
|
||||||
Log.w("Potential SPAM Message", "${event.id.toHex()} ${recentMessages[hash]} ${spamMessages[hash] != null} ${event.content.replace("\n", " | ")}")
|
Log.w("Potential SPAM Message", "${event.id} ${recentMessages[hash]} ${spamMessages[hash] != null} ${event.content.replace("\n", " | ")}")
|
||||||
|
|
||||||
// Log down offenders
|
// Log down offenders
|
||||||
if (spamMessages.get(hash) == null) {
|
if (spamMessages.get(hash) == null) {
|
||||||
spamMessages.put(hash, Spammer(event.pubKey.toHexKey(), setOf(recentMessages[hash], event.id.toHex())))
|
spamMessages.put(hash, Spammer(event.pubKey, setOf(recentMessages[hash], event.id)))
|
||||||
liveSpam.invalidateData()
|
liveSpam.invalidateData()
|
||||||
} else {
|
} else {
|
||||||
val spammer = spamMessages.get(hash)
|
val spammer = spamMessages.get(hash)
|
||||||
spammer.duplicatedMessages = spammer.duplicatedMessages + event.id.toHex()
|
spammer.duplicatedMessages = spammer.duplicatedMessages + event.id
|
||||||
|
|
||||||
liveSpam.invalidateData()
|
liveSpam.invalidateData()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,13 @@ import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent
|
|||||||
import com.vitorpamplona.amethyst.service.model.ReactionEvent
|
import com.vitorpamplona.amethyst.service.model.ReactionEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.ReportEvent
|
import com.vitorpamplona.amethyst.service.model.ReportEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.RepostEvent
|
import com.vitorpamplona.amethyst.service.model.RepostEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.model.ContactListEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.model.DeletionEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.model.Event
|
||||||
|
import com.vitorpamplona.amethyst.service.model.MetadataEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.model.RecommendRelayEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
||||||
import com.vitorpamplona.amethyst.service.relays.Relay
|
import com.vitorpamplona.amethyst.service.relays.Relay
|
||||||
import fr.acinq.secp256k1.Hex
|
import fr.acinq.secp256k1.Hex
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
@@ -32,13 +39,6 @@ import kotlinx.coroutines.NonCancellable
|
|||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import nostr.postr.events.ContactListEvent
|
|
||||||
import nostr.postr.events.DeletionEvent
|
|
||||||
import nostr.postr.events.Event
|
|
||||||
import nostr.postr.events.MetadataEvent
|
|
||||||
import nostr.postr.events.PrivateDmEvent
|
|
||||||
import nostr.postr.events.RecommendRelayEvent
|
|
||||||
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
|
||||||
import nostr.postr.toHex
|
import nostr.postr.toHex
|
||||||
import nostr.postr.toNpub
|
import nostr.postr.toNpub
|
||||||
|
|
||||||
@@ -139,7 +139,7 @@ object LocalCache {
|
|||||||
|
|
||||||
fun consume(event: MetadataEvent) {
|
fun consume(event: MetadataEvent) {
|
||||||
// new event
|
// new event
|
||||||
val oldUser = getOrCreateUser(event.pubKey.toHexKey())
|
val oldUser = getOrCreateUser(event.pubKey)
|
||||||
if (oldUser.info == null || event.createdAt > oldUser.info!!.updatedMetadataAt) {
|
if (oldUser.info == null || event.createdAt > oldUser.info!!.updatedMetadataAt) {
|
||||||
val newUser = try {
|
val newUser = try {
|
||||||
metadataParser.readValue(
|
metadataParser.readValue(
|
||||||
@@ -173,8 +173,8 @@ object LocalCache {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val note = getOrCreateNote(event.id.toHex())
|
val note = getOrCreateNote(event.id)
|
||||||
val author = getOrCreateUser(event.pubKey.toHexKey())
|
val author = getOrCreateUser(event.pubKey)
|
||||||
|
|
||||||
if (relay != null) {
|
if (relay != null) {
|
||||||
author.addRelayBeingUsed(relay, event.createdAt)
|
author.addRelayBeingUsed(relay, event.createdAt)
|
||||||
@@ -220,7 +220,7 @@ object LocalCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val note = getOrCreateAddressableNote(event.address())
|
val note = getOrCreateAddressableNote(event.address())
|
||||||
val author = getOrCreateUser(event.pubKey.toHexKey())
|
val author = getOrCreateUser(event.pubKey)
|
||||||
|
|
||||||
if (relay != null) {
|
if (relay != null) {
|
||||||
author.addRelayBeingUsed(relay, event.createdAt)
|
author.addRelayBeingUsed(relay, event.createdAt)
|
||||||
@@ -228,7 +228,7 @@ object LocalCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Already processed this event.
|
// Already processed this event.
|
||||||
if (note.event?.id?.toHex() == event.id.toHex()) return
|
if (note.event?.id == event.id) return
|
||||||
|
|
||||||
val mentions = event.mentions().mapNotNull { checkGetOrCreateUser(it) }
|
val mentions = event.mentions().mapNotNull { checkGetOrCreateUser(it) }
|
||||||
val replyTo = replyToWithoutCitations(event).mapNotNull { checkGetOrCreateNote(it) }
|
val replyTo = replyToWithoutCitations(event).mapNotNull { checkGetOrCreateNote(it) }
|
||||||
@@ -284,14 +284,15 @@ object LocalCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun consume(event: ContactListEvent) {
|
fun consume(event: ContactListEvent) {
|
||||||
val user = getOrCreateUser(event.pubKey.toHexKey())
|
val user = getOrCreateUser(event.pubKey)
|
||||||
|
val follows = event.follows()
|
||||||
|
|
||||||
if (event.createdAt > user.updatedFollowsAt && event.follows.isNotEmpty()) {
|
if (event.createdAt > user.updatedFollowsAt && !follows.isNullOrEmpty()) {
|
||||||
// Saves relay list only if it's a user that is currently been seen
|
// Saves relay list only if it's a user that is currently been seen
|
||||||
user.latestContactList = event
|
user.latestContactList = event
|
||||||
|
|
||||||
user.updateFollows(
|
user.updateFollows(
|
||||||
event.follows.map {
|
follows.map {
|
||||||
try {
|
try {
|
||||||
val pubKey = decodePublicKey(it.pubKeyHex)
|
val pubKey = decodePublicKey(it.pubKeyHex)
|
||||||
getOrCreateUser(pubKey.toHexKey())
|
getOrCreateUser(pubKey.toHexKey())
|
||||||
@@ -316,20 +317,17 @@ object LocalCache {
|
|||||||
user.updateRelays(relays)
|
user.updateRelays(relays)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
println("relay import issue")
|
Log.w("Relay List Parser","Relay import issue ${e.message}", e)
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.d(
|
Log.d("CL", "AAA ${user.toBestDisplayName()} ${follows.size}")
|
||||||
"CL",
|
|
||||||
"AAA ${user.toBestDisplayName()} ${event.follows.size}"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun consume(event: PrivateDmEvent, relay: Relay?) {
|
fun consume(event: PrivateDmEvent, relay: Relay?) {
|
||||||
val note = getOrCreateNote(event.id.toHex())
|
val note = getOrCreateNote(event.id)
|
||||||
val author = getOrCreateUser(event.pubKey.toHexKey())
|
val author = getOrCreateUser(event.pubKey)
|
||||||
|
|
||||||
if (relay != null) {
|
if (relay != null) {
|
||||||
author.addRelayBeingUsed(relay, event.createdAt)
|
author.addRelayBeingUsed(relay, event.createdAt)
|
||||||
@@ -339,7 +337,7 @@ object LocalCache {
|
|||||||
// Already processed this event.
|
// Already processed this event.
|
||||||
if (note.event != null) return
|
if (note.event != null) return
|
||||||
|
|
||||||
val recipient = event.recipientPubKey?.let { getOrCreateUser(it.toHexKey()) }
|
val recipient = event.recipientPubKey()?.let { getOrCreateUser(it) }
|
||||||
|
|
||||||
//Log.d("PM", "${author.toBestDisplayName()} to ${recipient?.toBestDisplayName()}")
|
//Log.d("PM", "${author.toBestDisplayName()} to ${recipient?.toBestDisplayName()}")
|
||||||
|
|
||||||
@@ -359,9 +357,9 @@ object LocalCache {
|
|||||||
fun consume(event: DeletionEvent) {
|
fun consume(event: DeletionEvent) {
|
||||||
var deletedAtLeastOne = false
|
var deletedAtLeastOne = false
|
||||||
|
|
||||||
event.deleteEvents.mapNotNull { notes[it] }.forEach { deleteNote ->
|
event.deleteEvents().mapNotNull { notes[it] }.forEach { deleteNote ->
|
||||||
// must be the same author
|
// must be the same author
|
||||||
if (deleteNote.author?.pubkeyHex == event.pubKey.toHexKey()) {
|
if (deleteNote.author?.pubkeyHex == event.pubKey) {
|
||||||
deleteNote.author?.removeNote(deleteNote)
|
deleteNote.author?.removeNote(deleteNote)
|
||||||
|
|
||||||
// reverts the add
|
// reverts the add
|
||||||
@@ -395,14 +393,14 @@ object LocalCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun consume(event: RepostEvent) {
|
fun consume(event: RepostEvent) {
|
||||||
val note = getOrCreateNote(event.id.toHex())
|
val note = getOrCreateNote(event.id)
|
||||||
|
|
||||||
// Already processed this event.
|
// Already processed this event.
|
||||||
if (note.event != null) return
|
if (note.event != null) return
|
||||||
|
|
||||||
//Log.d("TN", "New Boost (${notes.size},${users.size}) ${note.author?.toBestDisplayName()} ${formattedDateTime(event.createdAt)}")
|
//Log.d("TN", "New Boost (${notes.size},${users.size}) ${note.author?.toBestDisplayName()} ${formattedDateTime(event.createdAt)}")
|
||||||
|
|
||||||
val author = getOrCreateUser(event.pubKey.toHexKey())
|
val author = getOrCreateUser(event.pubKey)
|
||||||
val mentions = event.originalAuthor().mapNotNull { checkGetOrCreateUser(it) }
|
val mentions = event.originalAuthor().mapNotNull { checkGetOrCreateUser(it) }
|
||||||
val repliesTo = event.boostedPost().mapNotNull { checkGetOrCreateNote(it) } +
|
val repliesTo = event.boostedPost().mapNotNull { checkGetOrCreateNote(it) } +
|
||||||
event.taggedAddresses().mapNotNull { getOrCreateAddressableNote(it) }
|
event.taggedAddresses().mapNotNull { getOrCreateAddressableNote(it) }
|
||||||
@@ -429,12 +427,12 @@ object LocalCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun consume(event: ReactionEvent) {
|
fun consume(event: ReactionEvent) {
|
||||||
val note = getOrCreateNote(event.id.toHexKey())
|
val note = getOrCreateNote(event.id)
|
||||||
|
|
||||||
// Already processed this event.
|
// Already processed this event.
|
||||||
if (note.event != null) return
|
if (note.event != null) return
|
||||||
|
|
||||||
val author = getOrCreateUser(event.pubKey.toHexKey())
|
val author = getOrCreateUser(event.pubKey)
|
||||||
val mentions = event.originalAuthor().mapNotNull { checkGetOrCreateUser(it) }
|
val mentions = event.originalAuthor().mapNotNull { checkGetOrCreateUser(it) }
|
||||||
val repliesTo = event.originalPost().mapNotNull { checkGetOrCreateNote(it) } +
|
val repliesTo = event.originalPost().mapNotNull { checkGetOrCreateNote(it) } +
|
||||||
event.taggedAddresses().mapNotNull { getOrCreateAddressableNote(it) }
|
event.taggedAddresses().mapNotNull { getOrCreateAddressableNote(it) }
|
||||||
@@ -475,8 +473,8 @@ object LocalCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun consume(event: ReportEvent, relay: Relay?) {
|
fun consume(event: ReportEvent, relay: Relay?) {
|
||||||
val note = getOrCreateNote(event.id.toHex())
|
val note = getOrCreateNote(event.id)
|
||||||
val author = getOrCreateUser(event.pubKey.toHexKey())
|
val author = getOrCreateUser(event.pubKey)
|
||||||
|
|
||||||
if (relay != null) {
|
if (relay != null) {
|
||||||
author.addRelayBeingUsed(relay, event.createdAt)
|
author.addRelayBeingUsed(relay, event.createdAt)
|
||||||
@@ -507,13 +505,13 @@ object LocalCache {
|
|||||||
fun consume(event: ChannelCreateEvent) {
|
fun consume(event: ChannelCreateEvent) {
|
||||||
//Log.d("MT", "New Event ${event.content} ${event.id.toHex()}")
|
//Log.d("MT", "New Event ${event.content} ${event.id.toHex()}")
|
||||||
// new event
|
// new event
|
||||||
val oldChannel = getOrCreateChannel(event.id.toHex())
|
val oldChannel = getOrCreateChannel(event.id)
|
||||||
val author = getOrCreateUser(event.pubKey.toHexKey())
|
val author = getOrCreateUser(event.pubKey)
|
||||||
if (event.createdAt > oldChannel.updatedMetadataAt) {
|
if (event.createdAt > oldChannel.updatedMetadataAt) {
|
||||||
if (oldChannel.creator == null || oldChannel.creator == author) {
|
if (oldChannel.creator == null || oldChannel.creator == author) {
|
||||||
oldChannel.updateChannelInfo(author, event.channelInfo(), event.createdAt)
|
oldChannel.updateChannelInfo(author, event.channelInfo(), event.createdAt)
|
||||||
|
|
||||||
val note = getOrCreateNote(event.id.toHex())
|
val note = getOrCreateNote(event.id)
|
||||||
oldChannel.addNote(note)
|
oldChannel.addNote(note)
|
||||||
note.loadEvent(event, author, emptyList(), emptyList())
|
note.loadEvent(event, author, emptyList(), emptyList())
|
||||||
|
|
||||||
@@ -530,12 +528,12 @@ object LocalCache {
|
|||||||
|
|
||||||
// new event
|
// new event
|
||||||
val oldChannel = checkGetOrCreateChannel(channelId) ?: return
|
val oldChannel = checkGetOrCreateChannel(channelId) ?: return
|
||||||
val author = getOrCreateUser(event.pubKey.toHexKey())
|
val author = getOrCreateUser(event.pubKey)
|
||||||
if (event.createdAt > oldChannel.updatedMetadataAt) {
|
if (event.createdAt > oldChannel.updatedMetadataAt) {
|
||||||
if (oldChannel.creator == null || oldChannel.creator == author) {
|
if (oldChannel.creator == null || oldChannel.creator == author) {
|
||||||
oldChannel.updateChannelInfo(author, event.channelInfo(), event.createdAt)
|
oldChannel.updateChannelInfo(author, event.channelInfo(), event.createdAt)
|
||||||
|
|
||||||
val note = getOrCreateNote(event.id.toHex())
|
val note = getOrCreateNote(event.id)
|
||||||
oldChannel.addNote(note)
|
oldChannel.addNote(note)
|
||||||
note.loadEvent(event, author, emptyList(), emptyList())
|
note.loadEvent(event, author, emptyList(), emptyList())
|
||||||
|
|
||||||
@@ -559,10 +557,10 @@ object LocalCache {
|
|||||||
|
|
||||||
val channel = checkGetOrCreateChannel(channelId) ?: return
|
val channel = checkGetOrCreateChannel(channelId) ?: return
|
||||||
|
|
||||||
val note = getOrCreateNote(event.id.toHex())
|
val note = getOrCreateNote(event.id)
|
||||||
channel.addNote(note)
|
channel.addNote(note)
|
||||||
|
|
||||||
val author = getOrCreateUser(event.pubKey.toHexKey())
|
val author = getOrCreateUser(event.pubKey)
|
||||||
|
|
||||||
if (relay != null) {
|
if (relay != null) {
|
||||||
author.addRelayBeingUsed(relay, event.createdAt)
|
author.addRelayBeingUsed(relay, event.createdAt)
|
||||||
@@ -606,14 +604,14 @@ object LocalCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun consume(event: LnZapEvent) {
|
fun consume(event: LnZapEvent) {
|
||||||
val note = getOrCreateNote(event.id.toHexKey())
|
val note = getOrCreateNote(event.id)
|
||||||
|
|
||||||
// Already processed this event.
|
// Already processed this event.
|
||||||
if (note.event != null) return
|
if (note.event != null) return
|
||||||
|
|
||||||
val zapRequest = event.containedPost()?.id?.toHexKey()?.let { getOrCreateNote(it) }
|
val zapRequest = event.containedPost()?.id?.let { getOrCreateNote(it) }
|
||||||
|
|
||||||
val author = getOrCreateUser(event.pubKey.toHexKey())
|
val author = getOrCreateUser(event.pubKey)
|
||||||
val mentions = event.zappedAuthor().mapNotNull { checkGetOrCreateUser(it) }
|
val mentions = event.zappedAuthor().mapNotNull { checkGetOrCreateUser(it) }
|
||||||
val repliesTo = event.zappedPost().mapNotNull { checkGetOrCreateNote(it) } +
|
val repliesTo = event.zappedPost().mapNotNull { checkGetOrCreateNote(it) } +
|
||||||
event.taggedAddresses().map { getOrCreateAddressableNote(it) } +
|
event.taggedAddresses().map { getOrCreateAddressableNote(it) } +
|
||||||
@@ -645,15 +643,15 @@ object LocalCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun consume(event: LnZapRequestEvent) {
|
fun consume(event: LnZapRequestEvent) {
|
||||||
val note = getOrCreateNote(event.id.toHexKey())
|
val note = getOrCreateNote(event.id)
|
||||||
|
|
||||||
// Already processed this event.
|
// Already processed this event.
|
||||||
if (note.event != null) return
|
if (note.event != null) return
|
||||||
|
|
||||||
val author = getOrCreateUser(event.pubKey.toHexKey())
|
val author = getOrCreateUser(event.pubKey)
|
||||||
val mentions = event.zappedAuthor().mapNotNull { checkGetOrCreateUser(it) }
|
val mentions = event.zappedAuthor().mapNotNull { checkGetOrCreateUser(it) }
|
||||||
val repliesTo = event.zappedPost().mapNotNull { checkGetOrCreateNote(it) } +
|
val repliesTo = event.zappedPost().mapNotNull { checkGetOrCreateNote(it) } +
|
||||||
event.taggedAddresses().mapNotNull { getOrCreateAddressableNote(it) }
|
event.taggedAddresses().map { getOrCreateAddressableNote(it) }
|
||||||
|
|
||||||
note.loadEvent(event, author, mentions, repliesTo)
|
note.loadEvent(event, author, mentions, repliesTo)
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import kotlinx.coroutines.NonCancellable
|
|||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import nostr.postr.events.Event
|
import com.vitorpamplona.amethyst.service.model.Event
|
||||||
|
|
||||||
val tagSearch = Pattern.compile("(?:\\s|\\A)\\#\\[([0-9]+)\\]")
|
val tagSearch = Pattern.compile("(?:\\s|\\A)\\#\\[([0-9]+)\\]")
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ open class Note(val idHex: String) {
|
|||||||
val channelHex =
|
val channelHex =
|
||||||
(event as? ChannelMessageEvent)?.channel() ?:
|
(event as? ChannelMessageEvent)?.channel() ?:
|
||||||
(event as? ChannelMetadataEvent)?.channel() ?:
|
(event as? ChannelMetadataEvent)?.channel() ?:
|
||||||
(event as? ChannelCreateEvent)?.let { it.id.toHexKey() }
|
(event as? ChannelCreateEvent)?.let { it.id }
|
||||||
|
|
||||||
return channelHex?.let { LocalCache.checkGetOrCreateChannel(it) }
|
return channelHex?.let { LocalCache.checkGetOrCreateChannel(it) }
|
||||||
}
|
}
|
||||||
@@ -251,6 +251,22 @@ open class Note(val idHex: String) {
|
|||||||
}?.isNotEmpty() ?: false)
|
}?.isNotEmpty() ?: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun directlyCiteUsersHex(): Set<HexKey> {
|
||||||
|
val matcher = tagSearch.matcher(event?.content ?: "")
|
||||||
|
val returningList = mutableSetOf<String>()
|
||||||
|
while (matcher.find()) {
|
||||||
|
try {
|
||||||
|
val tag = matcher.group(1)?.let { event?.tags?.get(it.toInt()) }
|
||||||
|
if (tag != null && tag[0] == "p") {
|
||||||
|
returningList.add(tag[1])
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return returningList
|
||||||
|
}
|
||||||
|
|
||||||
fun directlyCiteUsers(): Set<User> {
|
fun directlyCiteUsers(): Set<User> {
|
||||||
val matcher = tagSearch.matcher(event?.content ?: "")
|
val matcher = tagSearch.matcher(event?.content ?: "")
|
||||||
val returningList = mutableSetOf<User>()
|
val returningList = mutableSetOf<User>()
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ import kotlinx.coroutines.delay
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import nostr.postr.Bech32
|
import nostr.postr.Bech32
|
||||||
import nostr.postr.events.ContactListEvent
|
import com.vitorpamplona.amethyst.service.model.ContactListEvent
|
||||||
import nostr.postr.events.MetadataEvent
|
import com.vitorpamplona.amethyst.service.model.MetadataEvent
|
||||||
import nostr.postr.toNpub
|
import nostr.postr.toNpub
|
||||||
|
|
||||||
val lnurlpPattern = Pattern.compile("(?i:http|https):\\/\\/((.+)\\/)*\\.well-known\\/lnurlp\\/(.*)")
|
val lnurlpPattern = Pattern.compile("(?i:http|https):\\/\\/((.+)\\/)*\\.well-known\\/lnurlp\\/(.*)")
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import com.vitorpamplona.amethyst.service.model.RepostEvent
|
|||||||
import com.vitorpamplona.amethyst.service.relays.FeedType
|
import com.vitorpamplona.amethyst.service.relays.FeedType
|
||||||
import com.vitorpamplona.amethyst.service.relays.TypedFilter
|
import com.vitorpamplona.amethyst.service.relays.TypedFilter
|
||||||
import nostr.postr.JsonFilter
|
import nostr.postr.JsonFilter
|
||||||
import nostr.postr.events.ContactListEvent
|
import com.vitorpamplona.amethyst.service.model.ContactListEvent
|
||||||
import nostr.postr.events.MetadataEvent
|
import com.vitorpamplona.amethyst.service.model.MetadataEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
||||||
|
|
||||||
object NostrAccountDataSource: NostrDataSource("AccountData") {
|
object NostrAccountDataSource: NostrDataSource("AccountData") {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import com.vitorpamplona.amethyst.model.User
|
|||||||
import com.vitorpamplona.amethyst.service.relays.FeedType
|
import com.vitorpamplona.amethyst.service.relays.FeedType
|
||||||
import com.vitorpamplona.amethyst.service.relays.TypedFilter
|
import com.vitorpamplona.amethyst.service.relays.TypedFilter
|
||||||
import nostr.postr.JsonFilter
|
import nostr.postr.JsonFilter
|
||||||
import nostr.postr.events.PrivateDmEvent
|
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
|
||||||
|
|
||||||
object NostrChatroomDataSource: NostrDataSource("ChatroomFeed") {
|
object NostrChatroomDataSource: NostrDataSource("ChatroomFeed") {
|
||||||
lateinit var account: Account
|
lateinit var account: Account
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent
|
|||||||
import com.vitorpamplona.amethyst.service.relays.FeedType
|
import com.vitorpamplona.amethyst.service.relays.FeedType
|
||||||
import com.vitorpamplona.amethyst.service.relays.TypedFilter
|
import com.vitorpamplona.amethyst.service.relays.TypedFilter
|
||||||
import nostr.postr.JsonFilter
|
import nostr.postr.JsonFilter
|
||||||
import nostr.postr.events.PrivateDmEvent
|
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
|
||||||
|
|
||||||
object NostrChatroomListDataSource: NostrDataSource("MailBoxFeed") {
|
object NostrChatroomListDataSource: NostrDataSource("MailBoxFeed") {
|
||||||
lateinit var account: Account
|
lateinit var account: Account
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.vitorpamplona.amethyst.service
|
package com.vitorpamplona.amethyst.service
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
|
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.ChannelHideMessageEvent
|
import com.vitorpamplona.amethyst.service.model.ChannelHideMessageEvent
|
||||||
@@ -15,7 +16,6 @@ import com.vitorpamplona.amethyst.service.model.RepostEvent
|
|||||||
import com.vitorpamplona.amethyst.service.relays.Client
|
import com.vitorpamplona.amethyst.service.relays.Client
|
||||||
import com.vitorpamplona.amethyst.service.relays.Relay
|
import com.vitorpamplona.amethyst.service.relays.Relay
|
||||||
import com.vitorpamplona.amethyst.service.relays.Subscription
|
import com.vitorpamplona.amethyst.service.relays.Subscription
|
||||||
import com.vitorpamplona.amethyst.service.relays.hasValidSignature
|
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
@@ -26,12 +26,12 @@ import kotlinx.coroutines.NonCancellable
|
|||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import nostr.postr.events.ContactListEvent
|
import com.vitorpamplona.amethyst.service.model.ContactListEvent
|
||||||
import nostr.postr.events.DeletionEvent
|
import com.vitorpamplona.amethyst.service.model.DeletionEvent
|
||||||
import nostr.postr.events.Event
|
import com.vitorpamplona.amethyst.service.model.Event
|
||||||
import nostr.postr.events.MetadataEvent
|
import com.vitorpamplona.amethyst.service.model.MetadataEvent
|
||||||
import nostr.postr.events.PrivateDmEvent
|
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
|
||||||
import nostr.postr.events.RecommendRelayEvent
|
import com.vitorpamplona.amethyst.service.model.RecommendRelayEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
||||||
|
|
||||||
abstract class NostrDataSource(val debugName: String) {
|
abstract class NostrDataSource(val debugName: String) {
|
||||||
@@ -62,39 +62,32 @@ abstract class NostrDataSource(val debugName: String) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
when (event) {
|
when (event) {
|
||||||
is MetadataEvent -> LocalCache.consume(event)
|
is ChannelCreateEvent -> LocalCache.consume(event)
|
||||||
//is TextNoteEvent -> LocalCache.consume(event, relay) overrides default TextNote
|
is ChannelHideMessageEvent -> LocalCache.consume(event)
|
||||||
is RecommendRelayEvent -> LocalCache.consume(event)
|
is ChannelMessageEvent -> LocalCache.consume(event, relay)
|
||||||
|
is ChannelMetadataEvent -> LocalCache.consume(event)
|
||||||
|
is ChannelMuteUserEvent -> LocalCache.consume(event)
|
||||||
is ContactListEvent -> LocalCache.consume(event)
|
is ContactListEvent -> LocalCache.consume(event)
|
||||||
is PrivateDmEvent -> LocalCache.consume(event, relay)
|
|
||||||
is DeletionEvent -> LocalCache.consume(event)
|
is DeletionEvent -> LocalCache.consume(event)
|
||||||
else -> when (event.kind) {
|
|
||||||
TextNoteEvent.kind -> LocalCache.consume(TextNoteEvent(event.id, event.pubKey, event.createdAt, event.tags, event.content, event.sig), relay)
|
|
||||||
|
|
||||||
RepostEvent.kind -> {
|
is LnZapEvent -> {
|
||||||
val repostEvent = RepostEvent(event.id, event.pubKey, event.createdAt, event.tags, event.content, event.sig)
|
event.containedPost()?.let { onEvent(it, subscriptionId, relay) }
|
||||||
|
LocalCache.consume(event)
|
||||||
repostEvent.containedPost()?.let { onEvent(it, subscriptionId, relay) }
|
}
|
||||||
LocalCache.consume(repostEvent)
|
is LnZapRequestEvent -> LocalCache.consume(event)
|
||||||
}
|
is LongTextNoteEvent -> LocalCache.consume(event, relay)
|
||||||
ReactionEvent.kind -> LocalCache.consume(ReactionEvent(event.id, event.pubKey, event.createdAt, event.tags, event.content, event.sig))
|
is MetadataEvent -> LocalCache.consume(event)
|
||||||
ReportEvent.kind -> LocalCache.consume(ReportEvent(event.id, event.pubKey, event.createdAt, event.tags, event.content, event.sig), relay)
|
is PrivateDmEvent -> LocalCache.consume(event, relay)
|
||||||
|
is ReactionEvent -> LocalCache.consume(event)
|
||||||
LnZapEvent.kind -> {
|
is RecommendRelayEvent -> LocalCache.consume(event)
|
||||||
val zapEvent = LnZapEvent(event.id, event.pubKey, event.createdAt, event.tags, event.content, event.sig)
|
is ReportEvent -> LocalCache.consume(event, relay)
|
||||||
|
is RepostEvent -> {
|
||||||
zapEvent.containedPost()?.let { onEvent(it, subscriptionId, relay) }
|
event.containedPost()?.let { onEvent(it, subscriptionId, relay) }
|
||||||
LocalCache.consume(zapEvent)
|
LocalCache.consume(event)
|
||||||
}
|
}
|
||||||
LnZapRequestEvent.kind -> LocalCache.consume(LnZapRequestEvent(event.id, event.pubKey, event.createdAt, event.tags, event.content, event.sig))
|
is TextNoteEvent -> LocalCache.consume(event, relay)
|
||||||
|
else -> {
|
||||||
ChannelCreateEvent.kind -> LocalCache.consume(ChannelCreateEvent(event.id, event.pubKey, event.createdAt, event.tags, event.content, event.sig))
|
Log.w("Event Not Supported", event.toJson())
|
||||||
ChannelMetadataEvent.kind -> LocalCache.consume(ChannelMetadataEvent(event.id, event.pubKey, event.createdAt, event.tags, event.content, event.sig))
|
|
||||||
ChannelMessageEvent.kind -> LocalCache.consume(ChannelMessageEvent(event.id, event.pubKey, event.createdAt, event.tags, event.content, event.sig), relay)
|
|
||||||
ChannelHideMessageEvent.kind -> LocalCache.consume(ChannelHideMessageEvent(event.id, event.pubKey, event.createdAt, event.tags, event.content, event.sig))
|
|
||||||
ChannelMuteUserEvent.kind -> LocalCache.consume(ChannelMuteUserEvent(event.id, event.pubKey, event.createdAt, event.tags, event.content, event.sig))
|
|
||||||
|
|
||||||
LongTextNoteEvent.kind -> LocalCache.consume(LongTextNoteEvent(event.id, event.pubKey, event.createdAt, event.tags, event.content, event.sig), relay)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@ import com.vitorpamplona.amethyst.service.relays.FeedType
|
|||||||
import com.vitorpamplona.amethyst.service.relays.TypedFilter
|
import com.vitorpamplona.amethyst.service.relays.TypedFilter
|
||||||
import nostr.postr.JsonFilter
|
import nostr.postr.JsonFilter
|
||||||
import nostr.postr.bechToBytes
|
import nostr.postr.bechToBytes
|
||||||
import nostr.postr.events.MetadataEvent
|
import com.vitorpamplona.amethyst.service.model.MetadataEvent
|
||||||
import nostr.postr.toHex
|
import nostr.postr.toHex
|
||||||
|
|
||||||
object NostrSearchEventOrUserDataSource: NostrDataSource("SingleEventFeed") {
|
object NostrSearchEventOrUserDataSource: NostrDataSource("SingleEventFeed") {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import com.vitorpamplona.amethyst.service.model.ReportEvent
|
|||||||
import com.vitorpamplona.amethyst.service.relays.FeedType
|
import com.vitorpamplona.amethyst.service.relays.FeedType
|
||||||
import com.vitorpamplona.amethyst.service.relays.TypedFilter
|
import com.vitorpamplona.amethyst.service.relays.TypedFilter
|
||||||
import nostr.postr.JsonFilter
|
import nostr.postr.JsonFilter
|
||||||
import nostr.postr.events.MetadataEvent
|
import com.vitorpamplona.amethyst.service.model.MetadataEvent
|
||||||
|
|
||||||
object NostrSingleUserDataSource: NostrDataSource("SingleUserFeed") {
|
object NostrSingleUserDataSource: NostrDataSource("SingleUserFeed") {
|
||||||
var usersToWatch = setOf<User>()
|
var usersToWatch = setOf<User>()
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent
|
|||||||
import com.vitorpamplona.amethyst.service.relays.FeedType
|
import com.vitorpamplona.amethyst.service.relays.FeedType
|
||||||
import com.vitorpamplona.amethyst.service.relays.TypedFilter
|
import com.vitorpamplona.amethyst.service.relays.TypedFilter
|
||||||
import nostr.postr.JsonFilter
|
import nostr.postr.JsonFilter
|
||||||
import nostr.postr.events.ContactListEvent
|
import com.vitorpamplona.amethyst.service.model.ContactListEvent
|
||||||
import nostr.postr.events.MetadataEvent
|
import com.vitorpamplona.amethyst.service.model.MetadataEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
||||||
|
|
||||||
object NostrUserProfileDataSource: NostrDataSource("UserProfileFeed") {
|
object NostrUserProfileDataSource: NostrDataSource("UserProfileFeed") {
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ data class ATag(val kind: Int, val pubKeyHex: String, val dTag: String) {
|
|||||||
|
|
||||||
val fullArray =
|
val fullArray =
|
||||||
byteArrayOf(NIP19TLVTypes.SPECIAL.id, dTag.size.toByte()) + dTag +
|
byteArrayOf(NIP19TLVTypes.SPECIAL.id, dTag.size.toByte()) + dTag +
|
||||||
byteArrayOf(NIP19TLVTypes.AUTHOR.id, addr.size.toByte()) + addr +
|
byteArrayOf(NIP19TLVTypes.AUTHOR.id, addr.size.toByte()) + addr +
|
||||||
byteArrayOf(NIP19TLVTypes.KIND.id, kind.size.toByte()) + kind
|
byteArrayOf(NIP19TLVTypes.KIND.id, kind.size.toByte()) + kind
|
||||||
|
|
||||||
return Bech32.encodeBytes(hrp = "naddr", fullArray, Bech32.Encoding.Bech32)
|
return Bech32.encodeBytes(hrp = "naddr", fullArray, Bech32.Encoding.Bech32)
|
||||||
}
|
}
|
||||||
@@ -41,7 +41,7 @@ data class ATag(val kind: Int, val pubKeyHex: String, val dTag: String) {
|
|||||||
Hex.decode(parts[1])
|
Hex.decode(parts[1])
|
||||||
ATag(parts[0].toInt(), parts[1], parts[2])
|
ATag(parts[0].toInt(), parts[1], parts[2])
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
Log.w("Address", "Error parsing A Tag: ${atag}: ${t.message}")
|
Log.w("ATag", "Error parsing A Tag: ${atag}: ${t.message}")
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -62,7 +62,7 @@ data class ATag(val kind: Int, val pubKeyHex: String, val dTag: String) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
println("Issue trying to Decode NIP19 ${this}: ${e.message}")
|
Log.w( "ATag", "Issue trying to Decode NIP19 ${this}: ${e.message}")
|
||||||
//e.printStackTrace()
|
//e.printStackTrace()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
package com.vitorpamplona.amethyst.service.model
|
package com.vitorpamplona.amethyst.service.model
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import nostr.postr.Utils
|
import nostr.postr.Utils
|
||||||
import nostr.postr.events.Event
|
|
||||||
import nostr.postr.events.MetadataEvent
|
|
||||||
|
|
||||||
class ChannelCreateEvent (
|
class ChannelCreateEvent (
|
||||||
id: ByteArray,
|
id: HexKey,
|
||||||
pubKey: ByteArray,
|
pubKey: HexKey,
|
||||||
createdAt: Long,
|
createdAt: Long,
|
||||||
tags: List<List<String>>,
|
tags: List<List<String>>,
|
||||||
content: String,
|
content: String,
|
||||||
sig: ByteArray
|
sig: HexKey
|
||||||
): Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
): Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
||||||
fun channelInfo() = try {
|
fun channelInfo() = try {
|
||||||
MetadataEvent.gson.fromJson(content, ChannelData::class.java)
|
MetadataEvent.gson.fromJson(content, ChannelData::class.java)
|
||||||
@@ -35,11 +35,11 @@ class ChannelCreateEvent (
|
|||||||
""
|
""
|
||||||
}
|
}
|
||||||
|
|
||||||
val pubKey = Utils.pubkeyCreate(privateKey)
|
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
||||||
val tags = emptyList<List<String>>()
|
val tags = emptyList<List<String>>()
|
||||||
val id = generateId(pubKey, createdAt, kind, tags, content)
|
val id = generateId(pubKey, createdAt, kind, tags, content)
|
||||||
val sig = Utils.sign(id, privateKey)
|
val sig = Utils.sign(id, privateKey)
|
||||||
return ChannelCreateEvent(id, pubKey, createdAt, tags, content, sig)
|
return ChannelCreateEvent(id.toHexKey(), pubKey, createdAt, tags, content, sig.toHexKey())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+7
-6
@@ -1,16 +1,17 @@
|
|||||||
package com.vitorpamplona.amethyst.service.model
|
package com.vitorpamplona.amethyst.service.model
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import nostr.postr.Utils
|
import nostr.postr.Utils
|
||||||
import nostr.postr.events.Event
|
|
||||||
|
|
||||||
class ChannelHideMessageEvent (
|
class ChannelHideMessageEvent (
|
||||||
id: ByteArray,
|
id: HexKey,
|
||||||
pubKey: ByteArray,
|
pubKey: HexKey,
|
||||||
createdAt: Long,
|
createdAt: Long,
|
||||||
tags: List<List<String>>,
|
tags: List<List<String>>,
|
||||||
content: String,
|
content: String,
|
||||||
sig: ByteArray
|
sig: HexKey
|
||||||
): Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
): Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
||||||
fun eventsToHide() = tags.filter { it.firstOrNull() == "e" }.mapNotNull { it.getOrNull(1) }
|
fun eventsToHide() = tags.filter { it.firstOrNull() == "e" }.mapNotNull { it.getOrNull(1) }
|
||||||
|
|
||||||
@@ -19,7 +20,7 @@ class ChannelHideMessageEvent (
|
|||||||
|
|
||||||
fun create(reason: String, messagesToHide: List<String>?, mentions: List<String>?, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ChannelHideMessageEvent {
|
fun create(reason: String, messagesToHide: List<String>?, mentions: List<String>?, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ChannelHideMessageEvent {
|
||||||
val content = reason
|
val content = reason
|
||||||
val pubKey = Utils.pubkeyCreate(privateKey)
|
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
||||||
val tags =
|
val tags =
|
||||||
messagesToHide?.map {
|
messagesToHide?.map {
|
||||||
listOf("e", it)
|
listOf("e", it)
|
||||||
@@ -27,7 +28,7 @@ class ChannelHideMessageEvent (
|
|||||||
|
|
||||||
val id = generateId(pubKey, createdAt, kind, tags, content)
|
val id = generateId(pubKey, createdAt, kind, tags, content)
|
||||||
val sig = Utils.sign(id, privateKey)
|
val sig = Utils.sign(id, privateKey)
|
||||||
return ChannelHideMessageEvent(id, pubKey, createdAt, tags, content, sig)
|
return ChannelHideMessageEvent(id.toHexKey(), pubKey, createdAt, tags, content, sig.toHexKey())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,16 +1,17 @@
|
|||||||
package com.vitorpamplona.amethyst.service.model
|
package com.vitorpamplona.amethyst.service.model
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import nostr.postr.Utils
|
import nostr.postr.Utils
|
||||||
import nostr.postr.events.Event
|
|
||||||
|
|
||||||
class ChannelMessageEvent (
|
class ChannelMessageEvent (
|
||||||
id: ByteArray,
|
id: HexKey,
|
||||||
pubKey: ByteArray,
|
pubKey: HexKey,
|
||||||
createdAt: Long,
|
createdAt: Long,
|
||||||
tags: List<List<String>>,
|
tags: List<List<String>>,
|
||||||
content: String,
|
content: String,
|
||||||
sig: ByteArray
|
sig: HexKey
|
||||||
): Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
): Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
||||||
|
|
||||||
fun channel() = tags.firstOrNull { it[0] == "e" && it.size > 3 && it[3] == "root" }?.getOrNull(1) ?: tags.firstOrNull { it.firstOrNull() == "e" }?.getOrNull(1)
|
fun channel() = tags.firstOrNull { it[0] == "e" && it.size > 3 && it[3] == "root" }?.getOrNull(1) ?: tags.firstOrNull { it.firstOrNull() == "e" }?.getOrNull(1)
|
||||||
@@ -22,7 +23,7 @@ class ChannelMessageEvent (
|
|||||||
|
|
||||||
fun create(message: String, channel: String, replyTos: List<String>? = null, mentions: List<String>? = null, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ChannelMessageEvent {
|
fun create(message: String, channel: String, replyTos: List<String>? = null, mentions: List<String>? = null, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ChannelMessageEvent {
|
||||||
val content = message
|
val content = message
|
||||||
val pubKey = Utils.pubkeyCreate(privateKey)
|
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
||||||
val tags = mutableListOf(
|
val tags = mutableListOf(
|
||||||
listOf("e", channel, "", "root")
|
listOf("e", channel, "", "root")
|
||||||
)
|
)
|
||||||
@@ -35,7 +36,7 @@ class ChannelMessageEvent (
|
|||||||
|
|
||||||
val id = generateId(pubKey, createdAt, kind, tags, content)
|
val id = generateId(pubKey, createdAt, kind, tags, content)
|
||||||
val sig = Utils.sign(id, privateKey)
|
val sig = Utils.sign(id, privateKey)
|
||||||
return ChannelMessageEvent(id, pubKey, createdAt, tags, content, sig)
|
return ChannelMessageEvent(id.toHexKey(), pubKey, createdAt, tags, content, sig.toHexKey())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,18 +1,18 @@
|
|||||||
package com.vitorpamplona.amethyst.service.model
|
package com.vitorpamplona.amethyst.service.model
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import nostr.postr.Utils
|
import nostr.postr.Utils
|
||||||
import nostr.postr.events.Event
|
|
||||||
import nostr.postr.events.MetadataEvent
|
|
||||||
|
|
||||||
class ChannelMetadataEvent (
|
class ChannelMetadataEvent (
|
||||||
id: ByteArray,
|
id: HexKey,
|
||||||
pubKey: ByteArray,
|
pubKey: HexKey,
|
||||||
createdAt: Long,
|
createdAt: Long,
|
||||||
tags: List<List<String>>,
|
tags: List<List<String>>,
|
||||||
content: String,
|
content: String,
|
||||||
sig: ByteArray
|
sig: HexKey
|
||||||
): Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
): Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
||||||
fun channel() = tags.firstOrNull { it.firstOrNull() == "e" }?.getOrNull(1)
|
fun channel() = tags.firstOrNull { it.firstOrNull() == "e" }?.getOrNull(1)
|
||||||
fun channelInfo() =
|
fun channelInfo() =
|
||||||
@@ -33,11 +33,11 @@ class ChannelMetadataEvent (
|
|||||||
else
|
else
|
||||||
""
|
""
|
||||||
|
|
||||||
val pubKey = Utils.pubkeyCreate(privateKey)
|
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
||||||
val tags = listOf( listOf("e", originalChannelIdHex, "", "root") )
|
val tags = listOf( listOf("e", originalChannelIdHex, "", "root") )
|
||||||
val id = generateId(pubKey, createdAt, kind, tags, content)
|
val id = generateId(pubKey, createdAt, kind, tags, content)
|
||||||
val sig = Utils.sign(id, privateKey)
|
val sig = Utils.sign(id, privateKey)
|
||||||
return ChannelMetadataEvent(id, pubKey, createdAt, tags, content, sig)
|
return ChannelMetadataEvent(id.toHexKey(), pubKey, createdAt, tags, content, sig.toHexKey())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,27 +1,27 @@
|
|||||||
package com.vitorpamplona.amethyst.service.model
|
package com.vitorpamplona.amethyst.service.model
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import nostr.postr.Utils
|
import nostr.postr.Utils
|
||||||
import nostr.postr.events.Event
|
|
||||||
|
|
||||||
class ChannelMuteUserEvent (
|
class ChannelMuteUserEvent (
|
||||||
id: ByteArray,
|
id: HexKey,
|
||||||
pubKey: ByteArray,
|
pubKey: HexKey,
|
||||||
createdAt: Long,
|
createdAt: Long,
|
||||||
tags: List<List<String>>,
|
tags: List<List<String>>,
|
||||||
content: String,
|
content: String,
|
||||||
sig: ByteArray
|
sig: HexKey
|
||||||
): Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
): Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
||||||
|
|
||||||
fun usersToMute() = tags.filter { it.firstOrNull() == "p" }.mapNotNull { it.getOrNull(1) }
|
fun usersToMute() = tags.filter { it.firstOrNull() == "p" }.mapNotNull { it.getOrNull(1) }
|
||||||
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val kind = 44
|
const val kind = 44
|
||||||
|
|
||||||
fun create(reason: String, usersToMute: List<String>?, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ChannelMuteUserEvent {
|
fun create(reason: String, usersToMute: List<String>?, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ChannelMuteUserEvent {
|
||||||
val content = reason
|
val content = reason
|
||||||
val pubKey = Utils.pubkeyCreate(privateKey)
|
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
||||||
val tags =
|
val tags =
|
||||||
usersToMute?.map {
|
usersToMute?.map {
|
||||||
listOf("p", it)
|
listOf("p", it)
|
||||||
@@ -29,7 +29,7 @@ class ChannelMuteUserEvent (
|
|||||||
|
|
||||||
val id = generateId(pubKey, createdAt, kind, tags, content)
|
val id = generateId(pubKey, createdAt, kind, tags, content)
|
||||||
val sig = Utils.sign(id, privateKey)
|
val sig = Utils.sign(id, privateKey)
|
||||||
return ChannelMuteUserEvent(id, pubKey, createdAt, tags, content, sig)
|
return ChannelMuteUserEvent(id.toHexKey(), pubKey, createdAt, tags, content, sig.toHexKey())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package com.vitorpamplona.amethyst.service.model
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import com.google.gson.reflect.TypeToken
|
||||||
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
|
import java.util.Date
|
||||||
|
import nostr.postr.Utils
|
||||||
|
|
||||||
|
data class Contact(val pubKeyHex: String, val relayUri: String?)
|
||||||
|
|
||||||
|
class ContactListEvent(
|
||||||
|
id: HexKey,
|
||||||
|
pubKey: HexKey,
|
||||||
|
createdAt: Long,
|
||||||
|
tags: List<List<String>>,
|
||||||
|
content: String,
|
||||||
|
sig: HexKey
|
||||||
|
): Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
||||||
|
fun follows() = try {
|
||||||
|
tags.filter { it[0] == "p" }.map { Contact(it[1], it.getOrNull(2)) }
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("ContactListEvent", "can't parse tags as follows: $tags", e)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun relayUse() = try {
|
||||||
|
if (content.isNotEmpty())
|
||||||
|
gson.fromJson(content, object: TypeToken<Map<String, ReadWrite>>() {}.type)
|
||||||
|
else
|
||||||
|
null
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("ContactListEvent", "can't parse content as relay lists: $tags", e)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val kind = 3
|
||||||
|
|
||||||
|
fun create(follows: List<Contact>, relayUse: Map<String, ReadWrite>?, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ContactListEvent {
|
||||||
|
val content = if (relayUse != null)
|
||||||
|
gson.toJson(relayUse)
|
||||||
|
else
|
||||||
|
""
|
||||||
|
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
||||||
|
val tags = follows.map {
|
||||||
|
if (it.relayUri != null)
|
||||||
|
listOf("p", it.pubKeyHex, it.relayUri)
|
||||||
|
else
|
||||||
|
listOf("p", it.pubKeyHex)
|
||||||
|
}
|
||||||
|
val id = generateId(pubKey, createdAt, kind, tags, content)
|
||||||
|
val sig = Utils.sign(id, privateKey)
|
||||||
|
return ContactListEvent(id.toHexKey(), pubKey, createdAt, tags, content, sig.toHexKey())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ReadWrite(val read: Boolean, val write: Boolean)
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.vitorpamplona.amethyst.service.model
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
|
import java.util.Date
|
||||||
|
import nostr.postr.Utils
|
||||||
|
|
||||||
|
class DeletionEvent(
|
||||||
|
id: HexKey,
|
||||||
|
pubKey: HexKey,
|
||||||
|
createdAt: Long,
|
||||||
|
tags: List<List<String>>,
|
||||||
|
content: String,
|
||||||
|
sig: HexKey
|
||||||
|
): Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
||||||
|
fun deleteEvents() = tags.map { it[1] }
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val kind = 5
|
||||||
|
|
||||||
|
fun create(deleteEvents: List<String>, privateKey: ByteArray, createdAt: Long = Date().time / 1000): DeletionEvent {
|
||||||
|
val content = ""
|
||||||
|
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
||||||
|
val tags = deleteEvents.map { listOf("e", it) }
|
||||||
|
val id = generateId(pubKey, createdAt, kind, tags, content)
|
||||||
|
val sig = Utils.sign(id, privateKey)
|
||||||
|
return DeletionEvent(id.toHexKey(), pubKey, createdAt, tags, content, sig.toHexKey())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
package com.vitorpamplona.amethyst.service.model
|
||||||
|
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.GsonBuilder
|
||||||
|
import com.google.gson.JsonArray
|
||||||
|
import com.google.gson.JsonDeserializationContext
|
||||||
|
import com.google.gson.JsonDeserializer
|
||||||
|
import com.google.gson.JsonElement
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import com.google.gson.JsonPrimitive
|
||||||
|
import com.google.gson.JsonSerializationContext
|
||||||
|
import com.google.gson.JsonSerializer
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
|
import fr.acinq.secp256k1.Hex
|
||||||
|
import fr.acinq.secp256k1.Secp256k1
|
||||||
|
import java.lang.reflect.Type
|
||||||
|
import java.security.MessageDigest
|
||||||
|
import java.util.Date
|
||||||
|
import nostr.postr.Utils
|
||||||
|
import nostr.postr.toHex
|
||||||
|
|
||||||
|
open class Event(
|
||||||
|
val id: HexKey,
|
||||||
|
@SerializedName("pubkey") val pubKey: HexKey,
|
||||||
|
@SerializedName("created_at") val createdAt: Long,
|
||||||
|
val kind: Int,
|
||||||
|
val tags: List<List<String>>,
|
||||||
|
val content: String,
|
||||||
|
val sig: HexKey
|
||||||
|
) {
|
||||||
|
fun toJson(): String = gson.toJson(this)
|
||||||
|
|
||||||
|
fun generateId(): String {
|
||||||
|
val rawEvent = listOf(
|
||||||
|
0,
|
||||||
|
pubKey,
|
||||||
|
createdAt,
|
||||||
|
kind,
|
||||||
|
tags,
|
||||||
|
content
|
||||||
|
)
|
||||||
|
val rawEventJson = gson.toJson(rawEvent)
|
||||||
|
return sha256.digest(rawEventJson.toByteArray()).toHexKey()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the ID is correct and then if the pubKey's secret key signed the event.
|
||||||
|
*/
|
||||||
|
fun checkSignature() {
|
||||||
|
if (!id.contentEquals(generateId())) {
|
||||||
|
throw Exception(
|
||||||
|
"""|Unexpected ID.
|
||||||
|
| Event: ${toJson()}
|
||||||
|
| Actual ID: ${id}
|
||||||
|
| Generated: ${generateId()}""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (!secp256k1.verifySchnorr(Hex.decode(sig), Hex.decode(id), Hex.decode(pubKey))) {
|
||||||
|
throw Exception("""Bad signature!""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hasValidSignature(): Boolean {
|
||||||
|
if (!id.contentEquals(generateId())) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (!Secp256k1.get().verifySchnorr(Hex.decode(sig), Hex.decode(id), Hex.decode(pubKey))) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
class EventDeserializer : JsonDeserializer<Event> {
|
||||||
|
override fun deserialize(
|
||||||
|
json: JsonElement,
|
||||||
|
typeOfT: Type?,
|
||||||
|
context: JsonDeserializationContext?
|
||||||
|
): Event {
|
||||||
|
val jsonObject = json.asJsonObject
|
||||||
|
return Event(
|
||||||
|
id = jsonObject.get("id").asString,
|
||||||
|
pubKey = jsonObject.get("pubkey").asString,
|
||||||
|
createdAt = jsonObject.get("created_at").asLong,
|
||||||
|
kind = jsonObject.get("kind").asInt,
|
||||||
|
tags = jsonObject.get("tags").asJsonArray.map {
|
||||||
|
it.asJsonArray.map { s -> s.asString }
|
||||||
|
},
|
||||||
|
content = jsonObject.get("content").asString,
|
||||||
|
sig = jsonObject.get("sig").asString
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EventSerializer : JsonSerializer<Event> {
|
||||||
|
override fun serialize(
|
||||||
|
src: Event,
|
||||||
|
typeOfSrc: Type?,
|
||||||
|
context: JsonSerializationContext?
|
||||||
|
): JsonElement {
|
||||||
|
return JsonObject().apply {
|
||||||
|
addProperty("id", src.id)
|
||||||
|
addProperty("pubkey", src.pubKey)
|
||||||
|
addProperty("created_at", src.createdAt)
|
||||||
|
addProperty("kind", src.kind)
|
||||||
|
add("tags", JsonArray().also { jsonTags ->
|
||||||
|
src.tags.forEach { tag ->
|
||||||
|
jsonTags.add(JsonArray().also { jsonTagElement ->
|
||||||
|
tag.forEach { tagElement ->
|
||||||
|
jsonTagElement.add(tagElement)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
addProperty("content", src.content)
|
||||||
|
addProperty("sig", src.sig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ByteArrayDeserializer : JsonDeserializer<ByteArray> {
|
||||||
|
override fun deserialize(
|
||||||
|
json: JsonElement,
|
||||||
|
typeOfT: Type?,
|
||||||
|
context: JsonDeserializationContext?
|
||||||
|
): ByteArray = Hex.decode(json.asString)
|
||||||
|
}
|
||||||
|
|
||||||
|
class ByteArraySerializer : JsonSerializer<ByteArray> {
|
||||||
|
override fun serialize(
|
||||||
|
src: ByteArray,
|
||||||
|
typeOfSrc: Type?,
|
||||||
|
context: JsonSerializationContext?
|
||||||
|
) = JsonPrimitive(src.toHex())
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val secp256k1 = Secp256k1.get()
|
||||||
|
|
||||||
|
val sha256: MessageDigest = MessageDigest.getInstance("SHA-256")
|
||||||
|
val gson: Gson = GsonBuilder()
|
||||||
|
.disableHtmlEscaping()
|
||||||
|
.registerTypeAdapter(Event::class.java, EventSerializer())
|
||||||
|
.registerTypeAdapter(Event::class.java, EventDeserializer())
|
||||||
|
.registerTypeAdapter(ByteArray::class.java, ByteArraySerializer())
|
||||||
|
.registerTypeAdapter(ByteArray::class.java, ByteArrayDeserializer())
|
||||||
|
.create()
|
||||||
|
|
||||||
|
fun fromJson(json: String, lenient: Boolean = false): Event = gson.fromJson(json, Event::class.java).getRefinedEvent(lenient)
|
||||||
|
|
||||||
|
fun fromJson(json: JsonElement, lenient: Boolean = false): Event = gson.fromJson(json, Event::class.java).getRefinedEvent(lenient)
|
||||||
|
|
||||||
|
fun Event.getRefinedEvent(lenient: Boolean = false): Event = when (kind) {
|
||||||
|
ChannelCreateEvent.kind -> ChannelCreateEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
ChannelHideMessageEvent.kind -> ChannelHideMessageEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
ChannelMessageEvent.kind -> ChannelMessageEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
ChannelMetadataEvent.kind -> ChannelMetadataEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
ChannelMuteUserEvent.kind -> ChannelMuteUserEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
ContactListEvent.kind -> ContactListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
DeletionEvent.kind -> DeletionEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
|
LnZapEvent.kind -> LnZapEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
LnZapRequestEvent.kind -> LnZapRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
LongTextNoteEvent.kind -> LongTextNoteEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
MetadataEvent.kind -> MetadataEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
PrivateDmEvent.kind -> PrivateDmEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
ReactionEvent.kind -> ReactionEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
RecommendRelayEvent.kind -> RecommendRelayEvent(id, pubKey, createdAt, tags, content, sig, lenient)
|
||||||
|
ReportEvent.kind -> ReportEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
RepostEvent.kind -> RepostEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
TextNoteEvent.kind -> TextNoteEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
else -> this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun generateId(pubKey: HexKey, createdAt: Long, kind: Int, tags: List<List<String>>, content: String): ByteArray {
|
||||||
|
val rawEvent = listOf(
|
||||||
|
0,
|
||||||
|
pubKey,
|
||||||
|
createdAt,
|
||||||
|
kind,
|
||||||
|
tags,
|
||||||
|
content
|
||||||
|
)
|
||||||
|
val rawEventJson = gson.toJson(rawEvent)
|
||||||
|
return sha256.digest(rawEventJson.toByteArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun create(privateKey: ByteArray, kind: Int, tags: List<List<String>> = emptyList(), content: String = "", createdAt: Long = Date().time / 1000): Event {
|
||||||
|
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
||||||
|
val id = Companion.generateId(pubKey, createdAt, kind, tags, content)
|
||||||
|
val sig = Utils.sign(id, privateKey).toHexKey()
|
||||||
|
return Event(id.toHexKey(), pubKey, createdAt, kind, tags, content, sig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +1,16 @@
|
|||||||
package com.vitorpamplona.amethyst.service.model
|
package com.vitorpamplona.amethyst.service.model
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
import com.vitorpamplona.amethyst.service.lnurl.LnInvoiceUtil
|
import com.vitorpamplona.amethyst.service.lnurl.LnInvoiceUtil
|
||||||
import com.vitorpamplona.amethyst.service.relays.Client
|
import com.vitorpamplona.amethyst.service.relays.Client
|
||||||
import java.math.BigDecimal
|
|
||||||
import nostr.postr.events.Event
|
|
||||||
|
|
||||||
class LnZapEvent (
|
class LnZapEvent (
|
||||||
id: ByteArray,
|
id: HexKey,
|
||||||
pubKey: ByteArray,
|
pubKey: HexKey,
|
||||||
createdAt: Long,
|
createdAt: Long,
|
||||||
tags: List<List<String>>,
|
tags: List<List<String>>,
|
||||||
content: String,
|
content: String,
|
||||||
sig: ByteArray
|
sig: HexKey
|
||||||
): Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
): Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
||||||
|
|
||||||
fun zappedPost() = tags.filter { it.firstOrNull() == "e" }.mapNotNull { it.getOrNull(1) }
|
fun zappedPost() = tags.filter { it.firstOrNull() == "e" }.mapNotNull { it.getOrNull(1) }
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
package com.vitorpamplona.amethyst.service.model
|
package com.vitorpamplona.amethyst.service.model
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import nostr.postr.Utils
|
import nostr.postr.Utils
|
||||||
import nostr.postr.events.Event
|
|
||||||
import nostr.postr.toHex
|
import nostr.postr.toHex
|
||||||
|
|
||||||
class LnZapRequestEvent (
|
class LnZapRequestEvent (
|
||||||
id: ByteArray,
|
id: HexKey,
|
||||||
pubKey: ByteArray,
|
pubKey: HexKey,
|
||||||
createdAt: Long,
|
createdAt: Long,
|
||||||
tags: List<List<String>>,
|
tags: List<List<String>>,
|
||||||
content: String,
|
content: String,
|
||||||
sig: ByteArray
|
sig: HexKey
|
||||||
): Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
): Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
||||||
fun zappedPost() = tags.filter { it.firstOrNull() == "e" }.mapNotNull { it.getOrNull(1) }
|
fun zappedPost() = tags.filter { it.firstOrNull() == "e" }.mapNotNull { it.getOrNull(1) }
|
||||||
fun zappedAuthor() = tags.filter { it.firstOrNull() == "p" }.mapNotNull { it.getOrNull(1) }
|
fun zappedAuthor() = tags.filter { it.firstOrNull() == "p" }.mapNotNull { it.getOrNull(1) }
|
||||||
@@ -22,10 +23,10 @@ class LnZapRequestEvent (
|
|||||||
|
|
||||||
fun create(originalNote: Event, relays: Set<String>, privateKey: ByteArray, createdAt: Long = Date().time / 1000): LnZapRequestEvent {
|
fun create(originalNote: Event, relays: Set<String>, privateKey: ByteArray, createdAt: Long = Date().time / 1000): LnZapRequestEvent {
|
||||||
val content = ""
|
val content = ""
|
||||||
val pubKey = Utils.pubkeyCreate(privateKey)
|
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
||||||
var tags = listOf(
|
var tags = listOf(
|
||||||
listOf("e", originalNote.id.toHex()),
|
listOf("e", originalNote.id),
|
||||||
listOf("p", originalNote.pubKey.toHex()),
|
listOf("p", originalNote.pubKey),
|
||||||
listOf("relays") + relays
|
listOf("relays") + relays
|
||||||
)
|
)
|
||||||
if (originalNote is LongTextNoteEvent) {
|
if (originalNote is LongTextNoteEvent) {
|
||||||
@@ -34,19 +35,19 @@ class LnZapRequestEvent (
|
|||||||
|
|
||||||
val id = generateId(pubKey, createdAt, kind, tags, content)
|
val id = generateId(pubKey, createdAt, kind, tags, content)
|
||||||
val sig = Utils.sign(id, privateKey)
|
val sig = Utils.sign(id, privateKey)
|
||||||
return LnZapRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
return LnZapRequestEvent(id.toHexKey(), pubKey, createdAt, tags, content, sig.toHexKey())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun create(userHex: String, relays: Set<String>, privateKey: ByteArray, createdAt: Long = Date().time / 1000): LnZapRequestEvent {
|
fun create(userHex: String, relays: Set<String>, privateKey: ByteArray, createdAt: Long = Date().time / 1000): LnZapRequestEvent {
|
||||||
val content = ""
|
val content = ""
|
||||||
val pubKey = Utils.pubkeyCreate(privateKey)
|
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
||||||
val tags = listOf(
|
val tags = listOf(
|
||||||
listOf("p", userHex),
|
listOf("p", userHex),
|
||||||
listOf("relays") + relays
|
listOf("relays") + relays
|
||||||
)
|
)
|
||||||
val id = generateId(pubKey, createdAt, kind, tags, content)
|
val id = generateId(pubKey, createdAt, kind, tags, content)
|
||||||
val sig = Utils.sign(id, privateKey)
|
val sig = Utils.sign(id, privateKey)
|
||||||
return LnZapRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
return LnZapRequestEvent(id.toHexKey(), pubKey, createdAt, tags, content, sig.toHexKey())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
package com.vitorpamplona.amethyst.service.model
|
package com.vitorpamplona.amethyst.service.model
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
import com.vitorpamplona.amethyst.model.toHexKey
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import nostr.postr.Utils
|
import nostr.postr.Utils
|
||||||
import nostr.postr.events.Event
|
|
||||||
|
|
||||||
class LongTextNoteEvent(
|
class LongTextNoteEvent(
|
||||||
id: ByteArray,
|
id: HexKey,
|
||||||
pubKey: ByteArray,
|
pubKey: HexKey,
|
||||||
createdAt: Long,
|
createdAt: Long,
|
||||||
tags: List<List<String>>,
|
tags: List<List<String>>,
|
||||||
content: String,
|
content: String,
|
||||||
sig: ByteArray
|
sig: HexKey
|
||||||
): Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
): Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
||||||
fun replyTos() = tags.filter { it.firstOrNull() == "e" }.mapNotNull { it.getOrNull(1) }
|
fun replyTos() = tags.filter { it.firstOrNull() == "e" }.mapNotNull { it.getOrNull(1) }
|
||||||
fun mentions() = tags.filter { it.firstOrNull() == "p" }.mapNotNull { it.getOrNull(1) }
|
fun mentions() = tags.filter { it.firstOrNull() == "p" }.mapNotNull { it.getOrNull(1) }
|
||||||
|
|
||||||
fun dTag() = tags.filter { it.firstOrNull() == "d" }.mapNotNull { it.getOrNull(1) }.firstOrNull() ?: ""
|
fun dTag() = tags.filter { it.firstOrNull() == "d" }.mapNotNull { it.getOrNull(1) }.firstOrNull() ?: ""
|
||||||
fun address() = ATag(kind, pubKey.toHexKey(), dTag())
|
fun address() = ATag(kind, pubKey, dTag())
|
||||||
|
|
||||||
fun topics() = tags.filter { it.firstOrNull() == "t" }.mapNotNull { it.getOrNull(1) }
|
fun topics() = tags.filter { it.firstOrNull() == "t" }.mapNotNull { it.getOrNull(1) }
|
||||||
fun title() = tags.filter { it.firstOrNull() == "title" }.mapNotNull { it.getOrNull(1) }.firstOrNull()
|
fun title() = tags.filter { it.firstOrNull() == "title" }.mapNotNull { it.getOrNull(1) }.firstOrNull()
|
||||||
@@ -34,7 +34,7 @@ class LongTextNoteEvent(
|
|||||||
const val kind = 30023
|
const val kind = 30023
|
||||||
|
|
||||||
fun create(msg: String, replyTos: List<String>?, mentions: List<String>?, privateKey: ByteArray, createdAt: Long = Date().time / 1000): LongTextNoteEvent {
|
fun create(msg: String, replyTos: List<String>?, mentions: List<String>?, privateKey: ByteArray, createdAt: Long = Date().time / 1000): LongTextNoteEvent {
|
||||||
val pubKey = Utils.pubkeyCreate(privateKey)
|
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
||||||
val tags = mutableListOf<List<String>>()
|
val tags = mutableListOf<List<String>>()
|
||||||
replyTos?.forEach {
|
replyTos?.forEach {
|
||||||
tags.add(listOf("e", it))
|
tags.add(listOf("e", it))
|
||||||
@@ -44,7 +44,7 @@ class LongTextNoteEvent(
|
|||||||
}
|
}
|
||||||
val id = generateId(pubKey, createdAt, kind, tags, msg)
|
val id = generateId(pubKey, createdAt, kind, tags, msg)
|
||||||
val sig = Utils.sign(id, privateKey)
|
val sig = Utils.sign(id, privateKey)
|
||||||
return LongTextNoteEvent(id, pubKey, createdAt, tags, msg, sig)
|
return LongTextNoteEvent(id.toHexKey(), pubKey, createdAt, tags, msg, sig.toHexKey())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package com.vitorpamplona.amethyst.service.model
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
|
import java.util.Date
|
||||||
|
import nostr.postr.Utils
|
||||||
|
|
||||||
|
data class ContactMetaData(
|
||||||
|
val name: String,
|
||||||
|
val picture: String,
|
||||||
|
val about: String,
|
||||||
|
val nip05: String?)
|
||||||
|
|
||||||
|
class MetadataEvent(
|
||||||
|
id: HexKey,
|
||||||
|
pubKey: HexKey,
|
||||||
|
createdAt: Long,
|
||||||
|
tags: List<List<String>>,
|
||||||
|
content: String,
|
||||||
|
sig: HexKey
|
||||||
|
): Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
||||||
|
fun contactMetaData() = try {
|
||||||
|
gson.fromJson(content, ContactMetaData::class.java)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("MetadataEvent", "Can't parse $content", e)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val kind = 0
|
||||||
|
val gson = Gson()
|
||||||
|
|
||||||
|
fun create(contactMetaData: ContactMetaData, privateKey: ByteArray, createdAt: Long = Date().time / 1000): MetadataEvent {
|
||||||
|
return create(gson.toJson(contactMetaData), privateKey, createdAt = createdAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun create(contactMetaData: String, privateKey: ByteArray, createdAt: Long = Date().time / 1000): MetadataEvent {
|
||||||
|
val content = contactMetaData
|
||||||
|
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
||||||
|
val tags = listOf<List<String>>()
|
||||||
|
val id = generateId(pubKey, createdAt, kind, tags, content)
|
||||||
|
val sig = Utils.sign(id, privateKey)
|
||||||
|
return MetadataEvent(id.toHexKey(), pubKey, createdAt, tags, content, sig.toHexKey())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
package com.vitorpamplona.amethyst.service.model
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
|
import fr.acinq.secp256k1.Hex
|
||||||
|
import java.util.Date
|
||||||
|
import nostr.postr.Utils
|
||||||
|
import nostr.postr.toHex
|
||||||
|
|
||||||
|
class PrivateDmEvent(
|
||||||
|
id: HexKey,
|
||||||
|
pubKey: HexKey,
|
||||||
|
createdAt: Long,
|
||||||
|
tags: List<List<String>>,
|
||||||
|
content: String,
|
||||||
|
sig: HexKey
|
||||||
|
) : Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
||||||
|
/**
|
||||||
|
* This may or may not be the actual recipient's pub key. The event is intended to look like a
|
||||||
|
* nip-04 EncryptedDmEvent but may omit the recipient, too. This value can be queried and used
|
||||||
|
* for initial messages.
|
||||||
|
*/
|
||||||
|
fun recipientPubKey() = tags.firstOrNull { it.firstOrNull() == "p" }?.run { Hex.decode(this[1]).toHexKey() } // makes sure its a valid one
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To be fully compatible with nip-04, we read e-tags that are in violation to nip-18.
|
||||||
|
*
|
||||||
|
* Nip-18 messages should refer to other events by inline references in the content like
|
||||||
|
* `[](e/c06f795e1234a9a1aecc731d768d4f3ca73e80031734767067c82d67ce82e506).
|
||||||
|
*/
|
||||||
|
fun replyTo() = tags.firstOrNull { it.firstOrNull() == "e" }?.getOrNull(1)
|
||||||
|
|
||||||
|
fun plainContent(privKey: ByteArray, pubKey: ByteArray): String? {
|
||||||
|
return try {
|
||||||
|
val sharedSecret = Utils.getSharedSecret(privKey, pubKey)
|
||||||
|
|
||||||
|
val retVal = Utils.decrypt(content, sharedSecret)
|
||||||
|
|
||||||
|
if (retVal.startsWith(nip18Advertisement)) {
|
||||||
|
retVal.substring(16)
|
||||||
|
} else {
|
||||||
|
retVal
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w("PrivateDM", "Error decrypting the message ${e.message}")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val kind = 4
|
||||||
|
|
||||||
|
const val nip18Advertisement = "[//]: # (nip18)\n"
|
||||||
|
|
||||||
|
fun create(
|
||||||
|
recipientPubKey: ByteArray,
|
||||||
|
msg: String,
|
||||||
|
replyTos: List<String>? = null, mentions: List<String>? = null,
|
||||||
|
privateKey: ByteArray,
|
||||||
|
createdAt: Long = Date().time / 1000,
|
||||||
|
publishedRecipientPubKey: ByteArray? = null,
|
||||||
|
advertiseNip18: Boolean = true
|
||||||
|
): PrivateDmEvent {
|
||||||
|
val content = Utils.encrypt(
|
||||||
|
if (advertiseNip18) { nip18Advertisement } else { "" } + msg,
|
||||||
|
privateKey,
|
||||||
|
recipientPubKey)
|
||||||
|
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
||||||
|
val tags = mutableListOf<List<String>>()
|
||||||
|
publishedRecipientPubKey?.let {
|
||||||
|
tags.add(listOf("p", publishedRecipientPubKey.toHex()))
|
||||||
|
}
|
||||||
|
replyTos?.forEach {
|
||||||
|
tags.add(listOf("e", it))
|
||||||
|
}
|
||||||
|
mentions?.forEach {
|
||||||
|
tags.add(listOf("p", it))
|
||||||
|
}
|
||||||
|
val id = generateId(pubKey, createdAt, kind, tags, content)
|
||||||
|
val sig = Utils.sign(id, privateKey)
|
||||||
|
return PrivateDmEvent(id.toHexKey(), pubKey, createdAt, tags, content, sig.toHexKey())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +1,18 @@
|
|||||||
package com.vitorpamplona.amethyst.service.model
|
package com.vitorpamplona.amethyst.service.model
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import nostr.postr.Utils
|
import nostr.postr.Utils
|
||||||
import nostr.postr.events.Event
|
|
||||||
import nostr.postr.toHex
|
import nostr.postr.toHex
|
||||||
|
|
||||||
class ReactionEvent (
|
class ReactionEvent (
|
||||||
id: ByteArray,
|
id: HexKey,
|
||||||
pubKey: ByteArray,
|
pubKey: HexKey,
|
||||||
createdAt: Long,
|
createdAt: Long,
|
||||||
tags: List<List<String>>,
|
tags: List<List<String>>,
|
||||||
content: String,
|
content: String,
|
||||||
sig: ByteArray
|
sig: HexKey
|
||||||
): Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
): Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
||||||
|
|
||||||
fun originalPost() = tags.filter { it.firstOrNull() == "e" }.mapNotNull { it.getOrNull(1) }
|
fun originalPost() = tags.filter { it.firstOrNull() == "e" }.mapNotNull { it.getOrNull(1) }
|
||||||
@@ -30,16 +31,16 @@ class ReactionEvent (
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun create(content: String, originalNote: Event, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ReactionEvent {
|
fun create(content: String, originalNote: Event, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ReactionEvent {
|
||||||
val pubKey = Utils.pubkeyCreate(privateKey)
|
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
||||||
|
|
||||||
var tags = listOf( listOf("e", originalNote.id.toHex()), listOf("p", originalNote.pubKey.toHex()))
|
var tags = listOf( listOf("e", originalNote.id), listOf("p", originalNote.pubKey))
|
||||||
if (originalNote is LongTextNoteEvent) {
|
if (originalNote is LongTextNoteEvent) {
|
||||||
tags = tags + listOf( listOf("a", originalNote.address().toTag()) )
|
tags = tags + listOf( listOf("a", originalNote.address().toTag()) )
|
||||||
}
|
}
|
||||||
|
|
||||||
val id = generateId(pubKey, createdAt, kind, tags, content)
|
val id = generateId(pubKey, createdAt, kind, tags, content)
|
||||||
val sig = Utils.sign(id, privateKey)
|
val sig = Utils.sign(id, privateKey)
|
||||||
return ReactionEvent(id, pubKey, createdAt, tags, content, sig)
|
return ReactionEvent(id.toHexKey(), pubKey, createdAt, tags, content, sig.toHexKey())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.vitorpamplona.amethyst.service.model
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
|
import java.net.URI
|
||||||
|
import java.util.Date
|
||||||
|
import nostr.postr.Utils
|
||||||
|
|
||||||
|
class RecommendRelayEvent(
|
||||||
|
id: HexKey,
|
||||||
|
pubKey: HexKey,
|
||||||
|
createdAt: Long,
|
||||||
|
tags: List<List<String>>,
|
||||||
|
content: String,
|
||||||
|
sig: HexKey,
|
||||||
|
val lenient: Boolean = false
|
||||||
|
): Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
||||||
|
|
||||||
|
fun relay() = if (lenient)
|
||||||
|
URI.create(content.trim())
|
||||||
|
else
|
||||||
|
URI.create(content)
|
||||||
|
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val kind = 2
|
||||||
|
|
||||||
|
fun create(relay: URI, privateKey: ByteArray, createdAt: Long = Date().time / 1000): RecommendRelayEvent {
|
||||||
|
val content = relay.toString()
|
||||||
|
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
||||||
|
val tags = listOf<List<String>>()
|
||||||
|
val id = generateId(pubKey, createdAt, kind, tags, content)
|
||||||
|
val sig = Utils.sign(id, privateKey)
|
||||||
|
return RecommendRelayEvent(id.toHexKey(), pubKey, createdAt, tags, content, sig.toHexKey())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +1,21 @@
|
|||||||
package com.vitorpamplona.amethyst.service.model
|
package com.vitorpamplona.amethyst.service.model
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import nostr.postr.Utils
|
import nostr.postr.Utils
|
||||||
import nostr.postr.events.Event
|
|
||||||
import nostr.postr.toHex
|
import nostr.postr.toHex
|
||||||
|
|
||||||
data class ReportedKey(val key: String, val reportType: ReportEvent.ReportType)
|
data class ReportedKey(val key: String, val reportType: ReportEvent.ReportType)
|
||||||
|
|
||||||
// NIP 56 event.
|
// NIP 56 event.
|
||||||
class ReportEvent (
|
class ReportEvent (
|
||||||
id: ByteArray,
|
id: HexKey,
|
||||||
pubKey: ByteArray,
|
pubKey: HexKey,
|
||||||
createdAt: Long,
|
createdAt: Long,
|
||||||
tags: List<List<String>>,
|
tags: List<List<String>>,
|
||||||
content: String,
|
content: String,
|
||||||
sig: ByteArray
|
sig: HexKey
|
||||||
): Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
): Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
||||||
|
|
||||||
private fun defaultReportType(): ReportType {
|
private fun defaultReportType(): ReportType {
|
||||||
@@ -55,10 +56,10 @@ class ReportEvent (
|
|||||||
fun create(reportedPost: Event, type: ReportType, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ReportEvent {
|
fun create(reportedPost: Event, type: ReportType, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ReportEvent {
|
||||||
val content = ""
|
val content = ""
|
||||||
|
|
||||||
val reportPostTag = listOf("e", reportedPost.id.toHex(), type.name.toLowerCase())
|
val reportPostTag = listOf("e", reportedPost.id, type.name.toLowerCase())
|
||||||
val reportAuthorTag = listOf("p", reportedPost.pubKey.toHex(), type.name.toLowerCase())
|
val reportAuthorTag = listOf("p", reportedPost.pubKey, type.name.toLowerCase())
|
||||||
|
|
||||||
val pubKey = Utils.pubkeyCreate(privateKey)
|
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
||||||
var tags:List<List<String>> = listOf(reportPostTag, reportAuthorTag)
|
var tags:List<List<String>> = listOf(reportPostTag, reportAuthorTag)
|
||||||
|
|
||||||
if (reportedPost is LongTextNoteEvent) {
|
if (reportedPost is LongTextNoteEvent) {
|
||||||
@@ -67,7 +68,7 @@ class ReportEvent (
|
|||||||
|
|
||||||
val id = generateId(pubKey, createdAt, kind, tags, content)
|
val id = generateId(pubKey, createdAt, kind, tags, content)
|
||||||
val sig = Utils.sign(id, privateKey)
|
val sig = Utils.sign(id, privateKey)
|
||||||
return ReportEvent(id, pubKey, createdAt, tags, content, sig)
|
return ReportEvent(id.toHexKey(), pubKey, createdAt, tags, content, sig.toHexKey())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun create(reportedUser: String, type: ReportType, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ReportEvent {
|
fun create(reportedUser: String, type: ReportType, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ReportEvent {
|
||||||
@@ -75,11 +76,11 @@ class ReportEvent (
|
|||||||
|
|
||||||
val reportAuthorTag = listOf("p", reportedUser, type.name.toLowerCase())
|
val reportAuthorTag = listOf("p", reportedUser, type.name.toLowerCase())
|
||||||
|
|
||||||
val pubKey = Utils.pubkeyCreate(privateKey)
|
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
||||||
val tags:List<List<String>> = listOf(reportAuthorTag)
|
val tags:List<List<String>> = listOf(reportAuthorTag)
|
||||||
val id = generateId(pubKey, createdAt, kind, tags, content)
|
val id = generateId(pubKey, createdAt, kind, tags, content)
|
||||||
val sig = Utils.sign(id, privateKey)
|
val sig = Utils.sign(id, privateKey)
|
||||||
return ReportEvent(id, pubKey, createdAt, tags, content, sig)
|
return ReportEvent(id.toHexKey(), pubKey, createdAt, tags, content, sig.toHexKey())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
package com.vitorpamplona.amethyst.service.model
|
package com.vitorpamplona.amethyst.service.model
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import com.vitorpamplona.amethyst.service.relays.Client
|
import com.vitorpamplona.amethyst.service.relays.Client
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import nostr.postr.Utils
|
import nostr.postr.Utils
|
||||||
import nostr.postr.events.Event
|
|
||||||
import nostr.postr.toHex
|
import nostr.postr.toHex
|
||||||
|
|
||||||
class RepostEvent (
|
class RepostEvent (
|
||||||
id: ByteArray,
|
id: HexKey,
|
||||||
pubKey: ByteArray,
|
pubKey: HexKey,
|
||||||
createdAt: Long,
|
createdAt: Long,
|
||||||
tags: List<List<String>>,
|
tags: List<List<String>>,
|
||||||
content: String,
|
content: String,
|
||||||
sig: ByteArray
|
sig: HexKey
|
||||||
): Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
): Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
||||||
|
|
||||||
|
|
||||||
@@ -32,10 +33,10 @@ class RepostEvent (
|
|||||||
fun create(boostedPost: Event, privateKey: ByteArray, createdAt: Long = Date().time / 1000): RepostEvent {
|
fun create(boostedPost: Event, privateKey: ByteArray, createdAt: Long = Date().time / 1000): RepostEvent {
|
||||||
val content = boostedPost.toJson()
|
val content = boostedPost.toJson()
|
||||||
|
|
||||||
val replyToPost = listOf("e", boostedPost.id.toHex())
|
val replyToPost = listOf("e", boostedPost.id)
|
||||||
val replyToAuthor = listOf("p", boostedPost.pubKey.toHex())
|
val replyToAuthor = listOf("p", boostedPost.pubKey)
|
||||||
|
|
||||||
val pubKey = Utils.pubkeyCreate(privateKey)
|
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
||||||
var tags:List<List<String>> = boostedPost.tags.plus(listOf(replyToPost, replyToAuthor))
|
var tags:List<List<String>> = boostedPost.tags.plus(listOf(replyToPost, replyToAuthor))
|
||||||
|
|
||||||
if (boostedPost is LongTextNoteEvent) {
|
if (boostedPost is LongTextNoteEvent) {
|
||||||
@@ -44,7 +45,7 @@ class RepostEvent (
|
|||||||
|
|
||||||
val id = generateId(pubKey, createdAt, kind, tags, content)
|
val id = generateId(pubKey, createdAt, kind, tags, content)
|
||||||
val sig = Utils.sign(id, privateKey)
|
val sig = Utils.sign(id, privateKey)
|
||||||
return RepostEvent(id, pubKey, createdAt, tags, content, sig)
|
return RepostEvent(id.toHexKey(), pubKey, createdAt, tags, content, sig.toHexKey())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,16 +1,17 @@
|
|||||||
package com.vitorpamplona.amethyst.service.model
|
package com.vitorpamplona.amethyst.service.model
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import nostr.postr.Utils
|
import nostr.postr.Utils
|
||||||
import nostr.postr.events.Event
|
|
||||||
|
|
||||||
class TextNoteEvent(
|
class TextNoteEvent(
|
||||||
id: ByteArray,
|
id: HexKey,
|
||||||
pubKey: ByteArray,
|
pubKey: HexKey,
|
||||||
createdAt: Long,
|
createdAt: Long,
|
||||||
tags: List<List<String>>,
|
tags: List<List<String>>,
|
||||||
content: String,
|
content: String,
|
||||||
sig: ByteArray
|
sig: HexKey
|
||||||
): Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
): Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
||||||
fun mentions() = tags.filter { it.firstOrNull() == "p" }.mapNotNull { it.getOrNull(1) }
|
fun mentions() = tags.filter { it.firstOrNull() == "p" }.mapNotNull { it.getOrNull(1) }
|
||||||
fun taggedAddresses() = tags.filter { it.firstOrNull() == "a" }.mapNotNull { it.getOrNull(1) }.mapNotNull { ATag.parse(it) }
|
fun taggedAddresses() = tags.filter { it.firstOrNull() == "a" }.mapNotNull { it.getOrNull(1) }.mapNotNull { ATag.parse(it) }
|
||||||
@@ -20,7 +21,7 @@ class TextNoteEvent(
|
|||||||
const val kind = 1
|
const val kind = 1
|
||||||
|
|
||||||
fun create(msg: String, replyTos: List<String>?, mentions: List<String>?, addresses: List<ATag>?, privateKey: ByteArray, createdAt: Long = Date().time / 1000): TextNoteEvent {
|
fun create(msg: String, replyTos: List<String>?, mentions: List<String>?, addresses: List<ATag>?, privateKey: ByteArray, createdAt: Long = Date().time / 1000): TextNoteEvent {
|
||||||
val pubKey = Utils.pubkeyCreate(privateKey)
|
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
||||||
val tags = mutableListOf<List<String>>()
|
val tags = mutableListOf<List<String>>()
|
||||||
replyTos?.forEach {
|
replyTos?.forEach {
|
||||||
tags.add(listOf("e", it))
|
tags.add(listOf("e", it))
|
||||||
@@ -33,7 +34,7 @@ class TextNoteEvent(
|
|||||||
}
|
}
|
||||||
val id = generateId(pubKey, createdAt, kind, tags, msg)
|
val id = generateId(pubKey, createdAt, kind, tags, msg)
|
||||||
val sig = Utils.sign(id, privateKey)
|
val sig = Utils.sign(id, privateKey)
|
||||||
return TextNoteEvent(id, pubKey, createdAt, tags, msg, sig)
|
return TextNoteEvent(id.toHexKey(), pubKey, createdAt, tags, msg, sig.toHexKey())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,7 @@ import java.util.UUID
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import nostr.postr.events.Event
|
import com.vitorpamplona.amethyst.service.model.Event
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Nostr Client manages multiple personae the user may switch between. Events are received and
|
* The Nostr Client manages multiple personae the user may switch between. Events are received and
|
||||||
@@ -38,9 +38,7 @@ object Client: RelayPool.Listener {
|
|||||||
if (relays.size != newRelayConfig.size) return false
|
if (relays.size != newRelayConfig.size) return false
|
||||||
|
|
||||||
relays.forEach { oldRelayInfo ->
|
relays.forEach { oldRelayInfo ->
|
||||||
val newRelayInfo = newRelayConfig.find { it.url == oldRelayInfo.url }
|
val newRelayInfo = newRelayConfig.find { it.url == oldRelayInfo.url } ?: return false
|
||||||
|
|
||||||
if (newRelayInfo == null) return false
|
|
||||||
|
|
||||||
if (!oldRelayInfo.isSameRelayConfig(newRelayInfo)) return false
|
if (!oldRelayInfo.isSameRelayConfig(newRelayInfo)) return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
package com.vitorpamplona.amethyst.service.relays
|
|
||||||
|
|
||||||
import fr.acinq.secp256k1.Secp256k1
|
|
||||||
import nostr.postr.events.Event
|
|
||||||
import nostr.postr.events.generateId
|
|
||||||
|
|
||||||
fun Event.hasValidSignature(): Boolean {
|
|
||||||
if (!id.contentEquals(generateId())) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (!Secp256k1.get().verifySchnorr(sig, id, pubKey)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,7 @@ package com.vitorpamplona.amethyst.service.relays
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.google.gson.JsonElement
|
import com.google.gson.JsonElement
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import nostr.postr.events.Event
|
import com.vitorpamplona.amethyst.service.model.Event
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import kotlinx.coroutines.CoroutineScope
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import nostr.postr.events.Event
|
import com.vitorpamplona.amethyst.service.model.Event
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RelayPool manages the connection to multiple Relays and lets consumers deal with simple events.
|
* RelayPool manages the connection to multiple Relays and lets consumers deal with simple events.
|
||||||
|
|||||||
@@ -38,7 +38,9 @@ import com.vitorpamplona.amethyst.RoboHashCache
|
|||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
|
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent
|
import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.ReactionEvent
|
import com.vitorpamplona.amethyst.service.model.ReactionEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.ReportEvent
|
import com.vitorpamplona.amethyst.service.model.ReportEvent
|
||||||
@@ -51,8 +53,9 @@ import com.vitorpamplona.amethyst.ui.components.UrlPreviewCard
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Following
|
import com.vitorpamplona.amethyst.ui.theme.Following
|
||||||
import kotlin.time.ExperimentalTime
|
import kotlin.time.ExperimentalTime
|
||||||
import nostr.postr.events.PrivateDmEvent
|
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChannelHeader
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
@@ -85,6 +88,7 @@ fun NoteCompose(
|
|||||||
var moreActionsExpanded by remember { mutableStateOf(false) }
|
var moreActionsExpanded by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
val noteEvent = note?.event
|
val noteEvent = note?.event
|
||||||
|
val baseChannel = note?.channel()
|
||||||
|
|
||||||
if (noteEvent == null) {
|
if (noteEvent == null) {
|
||||||
BlankNote(modifier.combinedClickable(
|
BlankNote(modifier.combinedClickable(
|
||||||
@@ -100,6 +104,8 @@ fun NoteCompose(
|
|||||||
navController,
|
navController,
|
||||||
onClick = { showHiddenNote = true }
|
onClick = { showHiddenNote = true }
|
||||||
)
|
)
|
||||||
|
} else if ((noteEvent is ChannelCreateEvent || noteEvent is ChannelMetadataEvent) && baseChannel != null) {
|
||||||
|
ChannelHeader(baseChannel = baseChannel, account = account, navController = navController)
|
||||||
} else {
|
} else {
|
||||||
var isNew by remember { mutableStateOf<Boolean>(false) }
|
var isNew by remember { mutableStateOf<Boolean>(false) }
|
||||||
|
|
||||||
@@ -134,9 +140,11 @@ fun NoteCompose(
|
|||||||
launchSingleTop = true
|
launchSingleTop = true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
note.channel()?.let {
|
note
|
||||||
navController.navigate("Channel/${it.idHex}")
|
.channel()
|
||||||
}
|
?.let {
|
||||||
|
navController.navigate("Channel/${it.idHex}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLongClick = { popupExpanded = true }
|
onLongClick = { popupExpanded = true }
|
||||||
@@ -176,7 +184,6 @@ fun NoteCompose(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// boosted picture
|
// boosted picture
|
||||||
val baseChannel = note.channel()
|
|
||||||
if (noteEvent is ChannelMessageEvent && baseChannel != null) {
|
if (noteEvent is ChannelMessageEvent && baseChannel != null) {
|
||||||
val channelState by baseChannel.live.observeAsState()
|
val channelState by baseChannel.live.observeAsState()
|
||||||
val channel = channelState?.channel
|
val channel = channelState?.channel
|
||||||
|
|||||||
@@ -121,7 +121,6 @@ fun ChannelScreen(channelId: String?, accountViewModel: AccountViewModel, accoun
|
|||||||
Column(Modifier.fillMaxHeight()) {
|
Column(Modifier.fillMaxHeight()) {
|
||||||
ChannelHeader(
|
ChannelHeader(
|
||||||
channel, account,
|
channel, account,
|
||||||
accountStateViewModel = accountStateViewModel,
|
|
||||||
navController = navController
|
navController = navController
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -213,7 +212,7 @@ fun ChannelScreen(channelId: String?, accountViewModel: AccountViewModel, accoun
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChannelHeader(baseChannel: Channel, account: Account, accountStateViewModel: AccountStateViewModel, navController: NavController) {
|
fun ChannelHeader(baseChannel: Channel, account: Account, navController: NavController) {
|
||||||
val channelState by baseChannel.live.observeAsState()
|
val channelState by baseChannel.live.observeAsState()
|
||||||
val channel = channelState?.channel ?: return
|
val channel = channelState?.channel ?: return
|
||||||
|
|
||||||
|
|||||||
+18
-3
@@ -91,7 +91,7 @@ fun TabKnown(accountViewModel: AccountViewModel, navController: NavController) {
|
|||||||
|
|
||||||
LaunchedEffect(accountViewModel) {
|
LaunchedEffect(accountViewModel) {
|
||||||
NostrChatroomListDataSource.resetFilters()
|
NostrChatroomListDataSource.resetFilters()
|
||||||
feedViewModel.invalidateData()
|
feedViewModel.refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
val lifeCycleOwner = LocalLifecycleOwner.current
|
val lifeCycleOwner = LocalLifecycleOwner.current
|
||||||
@@ -99,7 +99,7 @@ fun TabKnown(accountViewModel: AccountViewModel, navController: NavController) {
|
|||||||
val observer = LifecycleEventObserver { source, event ->
|
val observer = LifecycleEventObserver { source, event ->
|
||||||
if (event == Lifecycle.Event.ON_RESUME) {
|
if (event == Lifecycle.Event.ON_RESUME) {
|
||||||
NostrChatroomListDataSource.resetFilters()
|
NostrChatroomListDataSource.resetFilters()
|
||||||
feedViewModel.invalidateData()
|
feedViewModel.refresh()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,7 +128,22 @@ fun TabNew(accountViewModel: AccountViewModel, navController: NavController) {
|
|||||||
|
|
||||||
LaunchedEffect(accountViewModel) {
|
LaunchedEffect(accountViewModel) {
|
||||||
NostrChatroomListDataSource.resetFilters()
|
NostrChatroomListDataSource.resetFilters()
|
||||||
feedViewModel.invalidateData() // refresh view
|
feedViewModel.refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
val lifeCycleOwner = LocalLifecycleOwner.current
|
||||||
|
DisposableEffect(accountViewModel) {
|
||||||
|
val observer = LifecycleEventObserver { source, event ->
|
||||||
|
if (event == Lifecycle.Event.ON_RESUME) {
|
||||||
|
NostrChatroomListDataSource.resetFilters()
|
||||||
|
feedViewModel.refresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lifeCycleOwner.lifecycle.addObserver(observer)
|
||||||
|
onDispose {
|
||||||
|
lifeCycleOwner.lifecycle.removeObserver(observer)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(Modifier.fillMaxHeight()) {
|
Column(Modifier.fillMaxHeight()) {
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ fun ChatroomScreen(userId: String?, accountViewModel: AccountViewModel, navContr
|
|||||||
val lifeCycleOwner = LocalLifecycleOwner.current
|
val lifeCycleOwner = LocalLifecycleOwner.current
|
||||||
|
|
||||||
LaunchedEffect(userId) {
|
LaunchedEffect(userId) {
|
||||||
feedViewModel.invalidateData()
|
feedViewModel.refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
DisposableEffect(userId) {
|
DisposableEffect(userId) {
|
||||||
@@ -87,7 +87,7 @@ fun ChatroomScreen(userId: String?, accountViewModel: AccountViewModel, navContr
|
|||||||
if (event == Lifecycle.Event.ON_RESUME) {
|
if (event == Lifecycle.Event.ON_RESUME) {
|
||||||
println("Private Message Start")
|
println("Private Message Start")
|
||||||
NostrChatroomDataSource.start()
|
NostrChatroomDataSource.start()
|
||||||
feedViewModel.invalidateData()
|
feedViewModel.refresh()
|
||||||
}
|
}
|
||||||
if (event == Lifecycle.Event.ON_PAUSE) {
|
if (event == Lifecycle.Event.ON_PAUSE) {
|
||||||
println("Private Message Stop")
|
println("Private Message Stop")
|
||||||
|
|||||||
@@ -55,8 +55,8 @@ fun HomeScreen(accountViewModel: AccountViewModel, navController: NavController)
|
|||||||
LaunchedEffect(accountViewModel) {
|
LaunchedEffect(accountViewModel) {
|
||||||
NostrHomeDataSource.resetFilters()
|
NostrHomeDataSource.resetFilters()
|
||||||
|
|
||||||
feedViewModel.invalidateData()
|
feedViewModel.refresh()
|
||||||
feedViewModelReplies.invalidateData()
|
feedViewModelReplies.refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
val lifeCycleOwner = LocalLifecycleOwner.current
|
val lifeCycleOwner = LocalLifecycleOwner.current
|
||||||
@@ -64,8 +64,8 @@ fun HomeScreen(accountViewModel: AccountViewModel, navController: NavController)
|
|||||||
val observer = LifecycleEventObserver { source, event ->
|
val observer = LifecycleEventObserver { source, event ->
|
||||||
if (event == Lifecycle.Event.ON_RESUME) {
|
if (event == Lifecycle.Event.ON_RESUME) {
|
||||||
NostrHomeDataSource.resetFilters()
|
NostrHomeDataSource.resetFilters()
|
||||||
feedViewModel.invalidateData()
|
feedViewModel.refresh()
|
||||||
feedViewModelReplies.invalidateData()
|
feedViewModelReplies.refresh()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -36,7 +36,7 @@ fun NotificationScreen(accountViewModel: AccountViewModel, navController: NavCon
|
|||||||
DisposableEffect(accountViewModel) {
|
DisposableEffect(accountViewModel) {
|
||||||
val observer = LifecycleEventObserver { source, event ->
|
val observer = LifecycleEventObserver { source, event ->
|
||||||
if (event == Lifecycle.Event.ON_RESUME) {
|
if (event == Lifecycle.Event.ON_RESUME) {
|
||||||
feedViewModel.invalidateData()
|
feedViewModel.refresh()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -86,8 +86,8 @@ fun SearchScreen(accountViewModel: AccountViewModel, navController: NavControlle
|
|||||||
val feedViewModel: NostrGlobalFeedViewModel = viewModel()
|
val feedViewModel: NostrGlobalFeedViewModel = viewModel()
|
||||||
val lifeCycleOwner = LocalLifecycleOwner.current
|
val lifeCycleOwner = LocalLifecycleOwner.current
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(accountViewModel) {
|
||||||
feedViewModel.invalidateData()
|
feedViewModel.refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
DisposableEffect(accountViewModel) {
|
DisposableEffect(accountViewModel) {
|
||||||
@@ -95,7 +95,7 @@ fun SearchScreen(accountViewModel: AccountViewModel, navController: NavControlle
|
|||||||
if (event == Lifecycle.Event.ON_RESUME) {
|
if (event == Lifecycle.Event.ON_RESUME) {
|
||||||
println("Global Start")
|
println("Global Start")
|
||||||
NostrGlobalDataSource.start()
|
NostrGlobalDataSource.start()
|
||||||
feedViewModel.invalidateData()
|
feedViewModel.refresh()
|
||||||
}
|
}
|
||||||
if (event == Lifecycle.Event.ON_PAUSE) {
|
if (event == Lifecycle.Event.ON_PAUSE) {
|
||||||
println("Global Stop")
|
println("Global Stop")
|
||||||
|
|||||||
Reference in New Issue
Block a user