mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-04-09 20:39:24 +02:00
NIP-09 Event Deletion support: Works with TextNotes, Likes, Boosts and Reports.
This commit is contained in:
parent
2eff0626ec
commit
c087c5017c
@ -30,6 +30,7 @@ import nostr.postr.Contact
|
||||
import nostr.postr.Persona
|
||||
import nostr.postr.Utils
|
||||
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
|
||||
@ -123,10 +124,26 @@ class Account(
|
||||
}
|
||||
}
|
||||
|
||||
fun reactionTo(note: Note): List<Note> {
|
||||
return note.reactedBy(userProfile(), "+")
|
||||
}
|
||||
|
||||
fun hasBoosted(note: Note): Boolean {
|
||||
return boostsTo(note).isNotEmpty()
|
||||
}
|
||||
|
||||
fun boostsTo(note: Note): List<Note> {
|
||||
return note.boostedBy(userProfile())
|
||||
}
|
||||
|
||||
fun hasReacted(note: Note): Boolean {
|
||||
return note.hasReacted(userProfile(), "+")
|
||||
}
|
||||
|
||||
fun reactTo(note: Note) {
|
||||
if (!isWriteable()) return
|
||||
|
||||
if (note.hasReacted(userProfile(), "+")) {
|
||||
if (hasReacted(note)) {
|
||||
// has already liked this note
|
||||
return
|
||||
}
|
||||
@ -151,6 +168,7 @@ class Account(
|
||||
fun createZapRequestFor(user: User): LnZapRequestEvent? {
|
||||
return createZapRequestFor(user.pubkeyHex)
|
||||
}
|
||||
|
||||
fun createZapRequestFor(userPubKeyHex: String): LnZapRequestEvent? {
|
||||
if (!isWriteable()) return null
|
||||
|
||||
@ -174,7 +192,7 @@ class Account(
|
||||
note.event?.let {
|
||||
val event = ReportEvent.create(it, type, loggedIn.privKey!!)
|
||||
Client.send(event)
|
||||
LocalCache.consume(event)
|
||||
LocalCache.consume(event, null)
|
||||
}
|
||||
}
|
||||
|
||||
@ -188,13 +206,29 @@ class Account(
|
||||
|
||||
val event = ReportEvent.create(user.pubkeyHex, type, loggedIn.privKey!!)
|
||||
Client.send(event)
|
||||
LocalCache.consume(event)
|
||||
LocalCache.consume(event, null)
|
||||
}
|
||||
|
||||
fun delete(note: Note) {
|
||||
delete(listOf(note))
|
||||
}
|
||||
|
||||
fun delete(notes: List<Note>) {
|
||||
if (!isWriteable()) return
|
||||
|
||||
val myNotes = notes.filter { it.author == userProfile() }.map { it.idHex }
|
||||
|
||||
if (myNotes.isNotEmpty()) {
|
||||
val event = DeletionEvent.create(myNotes, loggedIn.privKey!!)
|
||||
Client.send(event)
|
||||
LocalCache.consume(event)
|
||||
}
|
||||
}
|
||||
|
||||
fun boost(note: Note) {
|
||||
if (!isWriteable()) return
|
||||
|
||||
if (note.hasBoosted(userProfile())) {
|
||||
if (note.hasBoostedInTheLast5Minutes(userProfile())) {
|
||||
// has already bosted in the past 5mins
|
||||
return
|
||||
}
|
||||
@ -532,8 +566,6 @@ class Account(
|
||||
saveable.invalidateData()
|
||||
}
|
||||
|
||||
|
||||
|
||||
init {
|
||||
backupContactList?.let {
|
||||
println("Loading saved contacts ${it.toJson()}")
|
||||
|
@ -281,7 +281,41 @@ object LocalCache {
|
||||
}
|
||||
|
||||
fun consume(event: DeletionEvent) {
|
||||
//Log.d("DEL", event.toJson())
|
||||
var deletedAtLeastOne = false
|
||||
|
||||
event.deleteEvents.mapNotNull { notes[it] }.forEach { deleteNote ->
|
||||
// must be the same author
|
||||
if (deleteNote.author?.pubkeyHex == event.pubKey.toHexKey()) {
|
||||
deleteNote.author?.removeNote(deleteNote)
|
||||
|
||||
// reverts the add
|
||||
deleteNote.mentions?.forEach { user ->
|
||||
user.removeTaggedPost(deleteNote)
|
||||
user.removeReport(deleteNote)
|
||||
}
|
||||
|
||||
deleteNote.replyTo?.forEach { replyingNote ->
|
||||
replyingNote.author?.removeTaggedPost(deleteNote)
|
||||
}
|
||||
|
||||
// Counts the replies
|
||||
deleteNote.replyTo?.forEach { masterNote ->
|
||||
masterNote.removeReply(deleteNote)
|
||||
masterNote.removeBoost(deleteNote)
|
||||
masterNote.removeReaction(deleteNote)
|
||||
masterNote.removeZap(deleteNote)
|
||||
masterNote.removeReport(deleteNote)
|
||||
}
|
||||
|
||||
notes.remove(deleteNote.idHex)
|
||||
|
||||
deletedAtLeastOne = true
|
||||
}
|
||||
}
|
||||
|
||||
if (deletedAtLeastOne) {
|
||||
live.invalidateData()
|
||||
}
|
||||
}
|
||||
|
||||
fun consume(event: RepostEvent) {
|
||||
@ -362,13 +396,18 @@ object LocalCache {
|
||||
}
|
||||
}
|
||||
|
||||
fun consume(event: ReportEvent) {
|
||||
fun consume(event: ReportEvent, relay: Relay?) {
|
||||
val note = getOrCreateNote(event.id.toHex())
|
||||
val author = getOrCreateUser(event.pubKey.toHexKey())
|
||||
|
||||
if (relay != null) {
|
||||
author.addRelayBeingUsed(relay, event.createdAt)
|
||||
note.addRelay(relay)
|
||||
}
|
||||
|
||||
// Already processed this event.
|
||||
if (note.event != null) return
|
||||
|
||||
val author = getOrCreateUser(event.pubKey.toHexKey())
|
||||
val mentions = event.reportedAuthor.mapNotNull { checkGetOrCreateUser(it) }
|
||||
val repliesTo = event.reportedPost.mapNotNull { checkGetOrCreateNote(it) }
|
||||
|
||||
@ -658,6 +697,8 @@ object LocalCache {
|
||||
}
|
||||
|
||||
toBeRemoved.forEach {
|
||||
it.author?.removeNote(it)
|
||||
|
||||
// reverts the add
|
||||
it.mentions?.forEach { user ->
|
||||
user.removeTaggedPost(it)
|
||||
@ -672,6 +713,7 @@ object LocalCache {
|
||||
masterNote.removeBoost(it)
|
||||
masterNote.removeReaction(it)
|
||||
masterNote.removeZap(it)
|
||||
masterNote.removeReport(it)
|
||||
}
|
||||
|
||||
notes.remove(it.idHex)
|
||||
|
@ -21,7 +21,6 @@ import java.util.Date
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.regex.Pattern
|
||||
import nostr.postr.events.Event
|
||||
import nostr.postr.toNpub
|
||||
|
||||
val tagSearch = Pattern.compile("(?:\\s|\\A)\\#\\[([0-9]+)\\]")
|
||||
|
||||
@ -116,6 +115,18 @@ class Note(val idHex: String) {
|
||||
reactions = reactions - note
|
||||
liveSet?.reactions?.invalidateData()
|
||||
}
|
||||
|
||||
fun removeReport(deleteNote: Note) {
|
||||
val author = deleteNote.author ?: return
|
||||
|
||||
if (author in reports.keys && reports[author]?.contains(deleteNote) == true ) {
|
||||
reports[author]?.let {
|
||||
reports = reports + Pair(author, it.minus(deleteNote))
|
||||
liveSet?.reports?.invalidateData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun removeZap(note: Note) {
|
||||
if (zaps[note] != null) {
|
||||
zaps = zaps.minus(note)
|
||||
@ -248,14 +259,22 @@ class Note(val idHex: String) {
|
||||
}
|
||||
|
||||
fun hasReacted(loggedIn: User, content: String): Boolean {
|
||||
return reactions.firstOrNull { it.author == loggedIn && it.event?.content == content } != null
|
||||
return reactedBy(loggedIn, content).isNotEmpty()
|
||||
}
|
||||
|
||||
fun hasBoosted(loggedIn: User): Boolean {
|
||||
fun reactedBy(loggedIn: User, content: String): List<Note> {
|
||||
return reactions.filter { it.author == loggedIn && it.event?.content == content }
|
||||
}
|
||||
|
||||
fun hasBoostedInTheLast5Minutes(loggedIn: User): Boolean {
|
||||
val currentTime = Date().time / 1000
|
||||
return boosts.firstOrNull { it.author == loggedIn && (it.event?.createdAt ?: 0) > currentTime - (60 * 5)} != null // 5 minute protection
|
||||
}
|
||||
|
||||
fun boostedBy(loggedIn: User): List<Note> {
|
||||
return boosts.filter { it.author == loggedIn }
|
||||
}
|
||||
|
||||
var liveSet: NoteLiveSet? = null
|
||||
|
||||
fun live(): NoteLiveSet {
|
||||
|
@ -134,6 +134,10 @@ class User(val pubkeyHex: String) {
|
||||
}
|
||||
}
|
||||
|
||||
fun removeNote(note: Note) {
|
||||
notes = notes - note
|
||||
}
|
||||
|
||||
fun clearNotes() {
|
||||
notes = setOf<Note>()
|
||||
}
|
||||
@ -155,6 +159,17 @@ class User(val pubkeyHex: String) {
|
||||
}
|
||||
}
|
||||
|
||||
fun removeReport(deleteNote: Note) {
|
||||
val author = deleteNote.author ?: return
|
||||
|
||||
if (author in reports.keys && reports[author]?.contains(deleteNote) == true ) {
|
||||
reports[author]?.let {
|
||||
reports = reports + Pair(author, it.minus(deleteNote))
|
||||
liveSet?.reports?.invalidateData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun addZap(zapRequest: Note, zap: Note?) {
|
||||
if (zapRequest !in zaps.keys) {
|
||||
zaps = zaps + Pair(zapRequest, zap)
|
||||
|
@ -74,7 +74,7 @@ abstract class NostrDataSource(val debugName: String) {
|
||||
LocalCache.consume(repostEvent)
|
||||
}
|
||||
ReactionEvent.kind -> LocalCache.consume(ReactionEvent(event.id, event.pubKey, event.createdAt, event.tags, event.content, event.sig))
|
||||
ReportEvent.kind -> LocalCache.consume(ReportEvent(event.id, event.pubKey, event.createdAt, event.tags, event.content, event.sig))
|
||||
ReportEvent.kind -> LocalCache.consume(ReportEvent(event.id, event.pubKey, event.createdAt, event.tags, event.content, event.sig), relay)
|
||||
|
||||
LnZapEvent.kind -> {
|
||||
val zapEvent = LnZapEvent(event.id, event.pubKey, event.createdAt, event.tags, event.content, event.sig)
|
||||
|
@ -42,7 +42,7 @@ object NostrUserProfileDataSource: NostrDataSource("UserProfileFeed") {
|
||||
filter = JsonFilter(
|
||||
kinds = listOf(TextNoteEvent.kind),
|
||||
authors = listOf(it.pubkeyHex),
|
||||
limit = 100
|
||||
limit = 200
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -536,6 +536,12 @@ fun NoteDropDownMenu(note: Note, popupExpanded: Boolean, onDismiss: () -> Unit,
|
||||
DropdownMenuItem(onClick = { accountViewModel.broadcast(note); onDismiss() }) {
|
||||
Text("Broadcast")
|
||||
}
|
||||
if (note.author == accountViewModel.accountLiveData.value?.account?.userProfile()) {
|
||||
Divider()
|
||||
DropdownMenuItem(onClick = { accountViewModel.delete(note); onDismiss() }) {
|
||||
Text("Request Deletion")
|
||||
}
|
||||
}
|
||||
if (note.author != accountViewModel.accountLiveData.value?.account?.userProfile()) {
|
||||
Divider()
|
||||
DropdownMenuItem(onClick = {
|
||||
@ -546,7 +552,7 @@ fun NoteDropDownMenu(note: Note, popupExpanded: Boolean, onDismiss: () -> Unit,
|
||||
)
|
||||
}; onDismiss()
|
||||
}) {
|
||||
Text("Block & Hide User")
|
||||
Text("Block & Hide Author")
|
||||
}
|
||||
Divider()
|
||||
DropdownMenuItem(onClick = {
|
||||
|
@ -192,9 +192,13 @@ private fun BoostReaction(
|
||||
IconButton(
|
||||
modifier = Modifier.then(Modifier.size(20.dp)),
|
||||
onClick = {
|
||||
if (accountViewModel.isWriteable())
|
||||
wantsToBoost = true
|
||||
else
|
||||
if (accountViewModel.isWriteable()) {
|
||||
if (accountViewModel.hasBoosted(baseNote)) {
|
||||
accountViewModel.deleteBoostsTo(baseNote)
|
||||
} else {
|
||||
wantsToBoost = true
|
||||
}
|
||||
} else
|
||||
scope.launch {
|
||||
Toast.makeText(
|
||||
context,
|
||||
@ -250,7 +254,7 @@ fun LikeReaction(
|
||||
textModifier: Modifier = Modifier
|
||||
) {
|
||||
val reactionsState by baseNote.live().reactions.observeAsState()
|
||||
val reactedNote = reactionsState?.note
|
||||
val reactedNote = reactionsState?.note ?: return
|
||||
|
||||
val grayTint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
val context = LocalContext.current
|
||||
@ -259,9 +263,13 @@ fun LikeReaction(
|
||||
IconButton(
|
||||
modifier = Modifier.then(Modifier.size(20.dp)),
|
||||
onClick = {
|
||||
if (accountViewModel.isWriteable())
|
||||
accountViewModel.reactTo(baseNote)
|
||||
else
|
||||
if (accountViewModel.isWriteable()) {
|
||||
if (accountViewModel.hasReactedTo(baseNote)) {
|
||||
accountViewModel.deleteReactionTo(baseNote)
|
||||
} else {
|
||||
accountViewModel.reactTo(baseNote)
|
||||
}
|
||||
} else
|
||||
scope.launch {
|
||||
Toast.makeText(
|
||||
context,
|
||||
|
@ -34,6 +34,22 @@ class AccountViewModel(private val account: Account): ViewModel() {
|
||||
account.reactTo(note)
|
||||
}
|
||||
|
||||
fun hasReactedTo(baseNote: Note): Boolean {
|
||||
return account.hasReacted(baseNote)
|
||||
}
|
||||
|
||||
fun deleteReactionTo(note: Note) {
|
||||
account.delete(account.reactionTo(note))
|
||||
}
|
||||
|
||||
fun hasBoosted(baseNote: Note): Boolean {
|
||||
return account.hasBoosted(baseNote)
|
||||
}
|
||||
|
||||
fun deleteBoostsTo(note: Note) {
|
||||
account.delete(account.boostsTo(note))
|
||||
}
|
||||
|
||||
fun zap(note: Note, amount: Long, message: String, context: Context, onError: (String) -> Unit) {
|
||||
val lud16 = note.author?.info?.lud16?.trim() ?: note.author?.info?.lud06?.trim()
|
||||
|
||||
@ -71,6 +87,10 @@ class AccountViewModel(private val account: Account): ViewModel() {
|
||||
account.broadcast(note)
|
||||
}
|
||||
|
||||
fun delete(note: Note) {
|
||||
account.delete(note)
|
||||
}
|
||||
|
||||
fun decrypt(note: Note): String? {
|
||||
return account.decryptContent(note)
|
||||
}
|
||||
@ -94,4 +114,8 @@ class AccountViewModel(private val account: Account): ViewModel() {
|
||||
fun prefer(source: String, target: String, preference: String) {
|
||||
account.prefer(source, target, preference)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user