Only adds user to Hidden Users after 5 duplicated messages.

pull/147/head
Vitor Pamplona 2023-02-21 09:42:41 -05:00
rodzic 8886ad83e0
commit 2595d6fa2a
3 zmienionych plików z 106 dodań i 34 usunięć

Wyświetl plik

@ -420,13 +420,13 @@ class Account(
reconnectIfRelaysHaveChanged()
}
}
LocalCache.liveSpam.observeForever {
LocalCache.antiSpam.liveSpam.observeForever {
GlobalScope.launch(Dispatchers.IO) {
LocalCache.spamMessages.snapshot().values.forEach {
if (it !in hiddenUsers) {
val userToBlock = LocalCache.getOrCreateUser(it)
it.cache.spamMessages.snapshot().values.forEach {
if (it.pubkeyHex !in transientHiddenUsers && it.duplicatedMessages > 5) {
val userToBlock = LocalCache.getOrCreateUser(it.pubkeyHex)
if (userToBlock != userProfile() && userToBlock !in userProfile().follows) {
transientHiddenUsers = transientHiddenUsers + it
transientHiddenUsers = transientHiddenUsers + it.pubkeyHex
}
}
}

Wyświetl plik

@ -0,0 +1,92 @@
package com.vitorpamplona.amethyst.model
import android.util.Log
import android.util.LruCache
import androidx.lifecycle.LiveData
import java.util.concurrent.atomic.AtomicBoolean
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import nostr.postr.events.Event
import nostr.postr.toHex
class AntiSpamFilter {
val recentMessages = LruCache<Int, String>(1000)
val spamMessages = LruCache<Int, Spammer>(1000)
@Synchronized
fun isSpam(event: Event): Boolean {
val idHex = event.id.toHexKey()
// if already processed, ok
if (LocalCache.notes[idHex] != null) return false
// if short message, ok
if (event.content.length < 50) return false
// double list strategy:
// if duplicated, it goes into spam. 1000 spam messages are saved into the spam list.
// Considers tags so that same replies to different people don't count.
val hash = (event.content + event.tags.flatten().joinToString(",")).hashCode()
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 down offenders
if (spamMessages.get(hash) == null) {
spamMessages.put(hash, Spammer(event.pubKey.toHexKey(), 2))
liveSpam.invalidateData()
} else {
spamMessages.get(hash).duplicatedMessages++
liveSpam.invalidateData()
}
return true
}
recentMessages.put(hash, idHex)
return false
}
val liveSpam: AntiSpamLiveData = AntiSpamLiveData(this)
}
class AntiSpamLiveData(val cache: AntiSpamFilter): LiveData<AntiSpamState>(AntiSpamState(cache)) {
// Refreshes observers in batches.
var handlerWaiting = AtomicBoolean()
@Synchronized
fun invalidateData() {
if (!hasActiveObservers()) return
if (handlerWaiting.getAndSet(true)) return
handlerWaiting.set(true)
val scope = CoroutineScope(Job() + Dispatchers.Main)
scope.launch {
try {
delay(100)
refresh()
} finally {
withContext(NonCancellable) {
handlerWaiting.set(false)
}
}
}
}
private fun refresh() {
postValue(AntiSpamState(cache))
}
}
class AntiSpamState(val cache: AntiSpamFilter) {
}

Wyświetl plik

@ -41,21 +41,22 @@ import nostr.postr.events.PrivateDmEvent
import nostr.postr.events.RecommendRelayEvent
import nostr.postr.events.TextNoteEvent
import nostr.postr.toHex
import nostr.postr.toNpub
data class Spammer(val pubkeyHex: HexKey, var duplicatedMessages: Int)
object LocalCache {
val metadataParser = jacksonObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.readerFor(UserMetadata::class.java)
val antiSpam = AntiSpamFilter()
val users = ConcurrentHashMap<HexKey, User>()
val notes = ConcurrentHashMap<HexKey, Note>()
val channels = ConcurrentHashMap<HexKey, Channel>()
val recentMessages = LruCache<Int, String>(1000)
val spamMessages = LruCache<Int, String>(1000)
fun checkGetOrCreateUser(key: HexKey): User? {
fun checkGetOrCreateUser(key: String): User? {
return try {
val checkHex = Hex.decode(key) // Checks if this is a valid Hex
getOrCreateUser(key)
@ -74,7 +75,7 @@ object LocalCache {
}
}
fun checkGetOrCreateNote(key: HexKey): Note? {
fun checkGetOrCreateNote(key: String): Note? {
return try {
val checkHex = Hex.decode(key) // Checks if this is a valid Hex
getOrCreateNote(key)
@ -93,7 +94,7 @@ object LocalCache {
}
}
fun checkGetOrCreateChannel(key: HexKey): Channel? {
fun checkGetOrCreateChannel(key: String): Channel? {
return try {
val checkHex = Hex.decode(key) // Checks if this is a valid Hex
getOrCreateChannel(key)
@ -141,29 +142,9 @@ object LocalCache {
.format(DateTimeFormatter.ofPattern("uuuu MMM d hh:mm a"))
}
@Synchronized
fun isRepeatedMessageSpam(event: Event): Boolean {
val idHex = event.id.toHexKey()
// if already processed, return
if (notes[idHex] != null) return false
// double list strategy:
// if duplicated, it goes into spam. 1000 spam messages are saved into the spam list.
val hash = (event.content + event.tags.flatten().joinToString(",")).hashCode()
if (event.content.length > 50 && ((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", " | ")}")
if (spamMessages.get(hash) == null) {
spamMessages.put(hash, event.pubKey.toHexKey())
liveSpam.invalidateData()
}
return true
}
recentMessages.put(hash, idHex)
return false
}
fun consume(event: TextNoteEvent, relay: Relay? = null) {
if (isRepeatedMessageSpam(event)) return
if (antiSpam.isSpam(event)) return
val note = getOrCreateNote(event.id.toHex())
val author = getOrCreateUser(event.pubKey.toHexKey())
@ -450,7 +431,7 @@ object LocalCache {
fun consume(event: ChannelMessageEvent, relay: Relay?) {
if (event.channel.isNullOrBlank()) return
if (isRepeatedMessageSpam(event)) return
if (antiSpam.isSpam(event)) return
val channel = checkGetOrCreateChannel(event.channel) ?: return
@ -667,7 +648,6 @@ object LocalCache {
// Observers line up here.
val live: LocalCacheLiveData = LocalCacheLiveData(this)
val liveSpam: LocalCacheLiveData = LocalCacheLiveData(this)
private fun refreshObservers() {
live.invalidateData()