Words containing my name should not trigger notifications (Fixes #1781)

It adds a specific behavior for rule with id RuleIds.RULE_ID_CONTAIN_USER_NAME
This commit is contained in:
Benoit Marty 2020-08-25 17:26:16 +02:00 committed by Benoit Marty
parent bf7a096a18
commit 2e618999d9
6 changed files with 49 additions and 25 deletions

View File

@ -9,6 +9,7 @@ Improvements 🙌:
Bugfix 🐛: Bugfix 🐛:
- Display name not shown under Settings/General (#1926) - Display name not shown under Settings/General (#1926)
- Words containing my name should not trigger notifications (#1781)
Translations 🗣: Translations 🗣:
- -

View File

@ -18,6 +18,7 @@ package org.matrix.android.sdk.api.pushrules
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.di.MoshiProvider
import org.matrix.android.sdk.internal.util.caseInsensitiveFind
import org.matrix.android.sdk.internal.util.hasSpecialGlobChar import org.matrix.android.sdk.internal.util.hasSpecialGlobChar
import org.matrix.android.sdk.internal.util.simpleGlobToRegExp import org.matrix.android.sdk.internal.util.simpleGlobToRegExp
import timber.log.Timber import timber.log.Timber
@ -31,14 +32,18 @@ class EventMatchCondition(
* The glob-style pattern to match against. Patterns with no special glob characters should * The glob-style pattern to match against. Patterns with no special glob characters should
* be treated as having asterisks prepended and appended when testing the condition. * be treated as having asterisks prepended and appended when testing the condition.
*/ */
val pattern: String val pattern: String,
/**
* true to match only words. In this case pattern will not be considered as a glob
*/
val wordsOnly: Boolean
) : Condition { ) : Condition {
override fun isSatisfied(event: Event, conditionResolver: ConditionResolver): Boolean { override fun isSatisfied(event: Event, conditionResolver: ConditionResolver): Boolean {
return conditionResolver.resolveEventMatchCondition(event, this) return conditionResolver.resolveEventMatchCondition(event, this)
} }
override fun technicalDescription() = "'$key' Matches '$pattern'" override fun technicalDescription() = "'$key' Matches '$pattern', words only '$wordsOnly'"
fun isSatisfied(event: Event): Boolean { fun isSatisfied(event: Event): Boolean {
// TODO encrypted events? // TODO encrypted events?
@ -48,14 +53,18 @@ class EventMatchCondition(
// Patterns with no special glob characters should be treated as having asterisks prepended // Patterns with no special glob characters should be treated as having asterisks prepended
// and appended when testing the condition. // and appended when testing the condition.
try { return try {
val modPattern = if (pattern.hasSpecialGlobChar()) pattern.simpleGlobToRegExp() else "*$pattern*".simpleGlobToRegExp() if (wordsOnly) {
val regex = Regex(modPattern, RegexOption.DOT_MATCHES_ALL) value.caseInsensitiveFind(pattern)
return regex.containsMatchIn(value) } else {
val modPattern = if (pattern.hasSpecialGlobChar()) pattern.simpleGlobToRegExp() else "*$pattern*".simpleGlobToRegExp()
val regex = Regex(modPattern, RegexOption.DOT_MATCHES_ALL)
regex.containsMatchIn(value)
}
} catch (e: Throwable) { } catch (e: Throwable) {
// e.g PatternSyntaxException // e.g PatternSyntaxException
Timber.e(e, "Failed to evaluate push condition") Timber.e(e, "Failed to evaluate push condition")
return false false
} }
} }

View File

@ -23,6 +23,7 @@ import org.matrix.android.sdk.api.pushrules.ContainsDisplayNameCondition
import org.matrix.android.sdk.api.pushrules.EventMatchCondition import org.matrix.android.sdk.api.pushrules.EventMatchCondition
import org.matrix.android.sdk.api.pushrules.Kind import org.matrix.android.sdk.api.pushrules.Kind
import org.matrix.android.sdk.api.pushrules.RoomMemberCountCondition import org.matrix.android.sdk.api.pushrules.RoomMemberCountCondition
import org.matrix.android.sdk.api.pushrules.RuleIds
import org.matrix.android.sdk.api.pushrules.SenderNotificationPermissionCondition import org.matrix.android.sdk.api.pushrules.SenderNotificationPermissionCondition
import timber.log.Timber import timber.log.Timber
@ -59,11 +60,11 @@ data class PushCondition(
val iz: String? = null val iz: String? = null
) { ) {
fun asExecutableCondition(): Condition? { fun asExecutableCondition(rule: PushRule): Condition? {
return when (Kind.fromString(kind)) { return when (Kind.fromString(kind)) {
Kind.EventMatch -> { Kind.EventMatch -> {
if (key != null && pattern != null) { if (key != null && pattern != null) {
EventMatchCondition(key, pattern) EventMatchCondition(key, pattern, rule.ruleId == RuleIds.RULE_ID_CONTAIN_USER_NAME)
} else { } else {
Timber.e("Malformed Event match condition") Timber.e("Malformed Event match condition")
null null

View File

@ -100,7 +100,7 @@ internal class DefaultProcessEventForPushTask @Inject constructor(
return rules.firstOrNull { rule -> return rules.firstOrNull { rule ->
// All conditions must hold true for an event in order to apply the action for the event. // All conditions must hold true for an event in order to apply the action for the event.
rule.enabled && rule.conditions?.all { rule.enabled && rule.conditions?.all {
it.asExecutableCondition()?.isSatisfied(event, conditionResolver) ?: false it.asExecutableCondition(rule)?.isSatisfied(event, conditionResolver) ?: false
} ?: false } ?: false
} }
} }

View File

@ -16,6 +16,12 @@
package org.matrix.android.sdk.api.pushrules package org.matrix.android.sdk.api.pushrules
import io.mockk.every
import io.mockk.mockk
import org.amshove.kluent.shouldBe
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import org.matrix.android.sdk.MatrixTest import org.matrix.android.sdk.MatrixTest
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toContent
@ -24,12 +30,6 @@ import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
import org.matrix.android.sdk.internal.session.room.RoomGetter import org.matrix.android.sdk.internal.session.room.RoomGetter
import io.mockk.every
import io.mockk.mockk
import org.amshove.kluent.shouldBe
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
class PushrulesConditionTest: MatrixTest { class PushrulesConditionTest: MatrixTest {
@ -39,7 +39,7 @@ class PushrulesConditionTest: MatrixTest {
@Test @Test
fun test_eventmatch_type_condition() { fun test_eventmatch_type_condition() {
val condition = EventMatchCondition("type", "m.room.message") val condition = EventMatchCondition("type", "m.room.message", false)
val simpleTextEvent = Event( val simpleTextEvent = Event(
type = "m.room.message", type = "m.room.message",
@ -65,7 +65,7 @@ class PushrulesConditionTest: MatrixTest {
@Test @Test
fun test_eventmatch_path_condition() { fun test_eventmatch_path_condition() {
val condition = EventMatchCondition("content.msgtype", "m.text") val condition = EventMatchCondition("content.msgtype", "m.text", false)
val simpleTextEvent = Event( val simpleTextEvent = Event(
type = "m.room.message", type = "m.room.message",
@ -86,13 +86,13 @@ class PushrulesConditionTest: MatrixTest {
).toContent(), ).toContent(),
originServerTs = 0 originServerTs = 0
).apply { ).apply {
assert(EventMatchCondition("content.membership", "invite").isSatisfied(this)) assert(EventMatchCondition("content.membership", "invite", false).isSatisfied(this))
} }
} }
@Test @Test
fun test_eventmatch_cake_condition() { fun test_eventmatch_cake_condition() {
val condition = EventMatchCondition("content.body", "cake") val condition = EventMatchCondition("content.body", "cake", false)
Event( Event(
type = "m.room.message", type = "m.room.message",
@ -115,7 +115,7 @@ class PushrulesConditionTest: MatrixTest {
@Test @Test
fun test_eventmatch_cakelie_condition() { fun test_eventmatch_cakelie_condition() {
val condition = EventMatchCondition("content.body", "cake*lie") val condition = EventMatchCondition("content.body", "cake*lie", false)
val simpleTextEvent = Event( val simpleTextEvent = Event(
type = "m.room.message", type = "m.room.message",
@ -126,9 +126,22 @@ class PushrulesConditionTest: MatrixTest {
assert(condition.isSatisfied(simpleTextEvent)) assert(condition.isSatisfied(simpleTextEvent))
} }
@Test
fun test_eventmatch_words_only_condition() {
val condition = EventMatchCondition("content.body", "ben", true)
val simpleTextEvent = Event(
type = "m.room.message",
eventId = "mx0",
content = MessageTextContent("m.text", "benoit").toContent(),
originServerTs = 0)
assertFalse(condition.isSatisfied(simpleTextEvent))
}
@Test @Test
fun test_notice_condition() { fun test_notice_condition() {
val conditionEqual = EventMatchCondition("content.msgtype", "m.notice") val conditionEqual = EventMatchCondition("content.msgtype", "m.notice", false)
Event( Event(
type = "m.room.message", type = "m.room.message",

View File

@ -26,11 +26,11 @@ import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass import com.airbnb.epoxy.EpoxyModelClass
import com.airbnb.epoxy.EpoxyModelWithHolder import com.airbnb.epoxy.EpoxyModelWithHolder
import org.matrix.android.sdk.api.pushrules.getActions
import org.matrix.android.sdk.api.pushrules.rest.PushRule
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.epoxy.VectorEpoxyHolder import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.features.notifications.toNotificationAction import im.vector.app.features.notifications.toNotificationAction
import org.matrix.android.sdk.api.pushrules.getActions
import org.matrix.android.sdk.api.pushrules.rest.PushRule
@EpoxyModelClass(layout = R.layout.item_pushrule_raw) @EpoxyModelClass(layout = R.layout.item_pushrule_raw)
abstract class PushRuleItem : EpoxyModelWithHolder<PushRuleItem.Holder>() { abstract class PushRuleItem : EpoxyModelWithHolder<PushRuleItem.Holder>() {
@ -68,7 +68,7 @@ abstract class PushRuleItem : EpoxyModelWithHolder<PushRuleItem.Holder>() {
val description = StringBuffer() val description = StringBuffer()
pushRule.conditions?.forEachIndexed { i, condition -> pushRule.conditions?.forEachIndexed { i, condition ->
if (i > 0) description.append("\n") if (i > 0) description.append("\n")
description.append(condition.asExecutableCondition()?.technicalDescription() description.append(condition.asExecutableCondition(pushRule)?.technicalDescription()
?: "UNSUPPORTED") ?: "UNSUPPORTED")
} }
if (description.isBlank()) { if (description.isBlank()) {