2021-06-11 20:15:40 +02:00
|
|
|
package com.keylesspalace.tusky.network
|
|
|
|
|
|
|
|
import android.text.TextUtils
|
|
|
|
import com.keylesspalace.tusky.entity.Filter
|
|
|
|
import com.keylesspalace.tusky.entity.Status
|
|
|
|
import java.util.regex.Pattern
|
|
|
|
import javax.inject.Inject
|
|
|
|
|
|
|
|
/**
|
|
|
|
* One-stop for status filtering logic using Mastodon's filters.
|
|
|
|
*
|
|
|
|
* 1. You init with [initWithFilters], this compiles regex pattern.
|
|
|
|
* 2. You call [shouldFilterStatus] to figure out what to display when you load statuses.
|
|
|
|
*/
|
|
|
|
class FilterModel @Inject constructor() {
|
|
|
|
private var pattern: Pattern? = null
|
|
|
|
|
|
|
|
fun initWithFilters(filters: List<Filter>) {
|
|
|
|
this.pattern = makeFilter(filters)
|
|
|
|
}
|
|
|
|
|
|
|
|
fun shouldFilterStatus(status: Status): Boolean {
|
|
|
|
// Patterns are expensive and thread-safe, matchers are neither.
|
|
|
|
val matcher = pattern?.matcher("") ?: return false
|
|
|
|
|
|
|
|
if (status.poll != null) {
|
|
|
|
val pollMatches = status.poll.options.any { matcher.reset(it.title).find() }
|
|
|
|
if (pollMatches) return true
|
|
|
|
}
|
|
|
|
|
|
|
|
val spoilerText = status.actionableStatus.spoilerText
|
2022-01-04 19:28:49 +01:00
|
|
|
val attachmentsDescriptions = status.attachments
|
|
|
|
.mapNotNull { it.description }
|
|
|
|
|
2021-06-28 21:13:24 +02:00
|
|
|
return (
|
|
|
|
matcher.reset(status.actionableStatus.content).find() ||
|
2022-01-04 19:28:49 +01:00
|
|
|
(spoilerText.isNotEmpty() && matcher.reset(spoilerText).find()) ||
|
|
|
|
(
|
|
|
|
attachmentsDescriptions.isNotEmpty() &&
|
|
|
|
matcher.reset(attachmentsDescriptions.joinToString("\n")).find()
|
|
|
|
)
|
2021-06-28 21:13:24 +02:00
|
|
|
)
|
2021-06-11 20:15:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private fun filterToRegexToken(filter: Filter): String? {
|
|
|
|
val phrase = filter.phrase
|
|
|
|
val quotedPhrase = Pattern.quote(phrase)
|
|
|
|
return if (filter.wholeWord && ALPHANUMERIC.matcher(phrase).matches()) {
|
|
|
|
String.format("(^|\\W)%s($|\\W)", quotedPhrase)
|
|
|
|
} else {
|
|
|
|
quotedPhrase
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun makeFilter(filters: List<Filter>): Pattern? {
|
|
|
|
if (filters.isEmpty()) return null
|
|
|
|
val tokens = filters.map { filterToRegexToken(it) }
|
|
|
|
|
2021-06-28 21:13:24 +02:00
|
|
|
return Pattern.compile(TextUtils.join("|", tokens), Pattern.CASE_INSENSITIVE)
|
2021-06-11 20:15:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
companion object {
|
|
|
|
private val ALPHANUMERIC = Pattern.compile("^\\w+$")
|
|
|
|
}
|
2021-06-28 21:13:24 +02:00
|
|
|
}
|