feat(mattermost): separate group policy for threads vs channels

This commit is contained in:
Kenneth Zhao 2026-08-03 17:20:04 +00:00 committed by chengyongru
parent be5af019b9
commit cd4c1d0f6e
13 changed files with 154 additions and 17 deletions

View File

@ -47,6 +47,7 @@ class MattermostConfig(Base):
allow_from_match_mode: str = "id" allow_from_match_mode: str = "id"
allow_from: list[str] = Field(default_factory=list) allow_from: list[str] = Field(default_factory=list)
group_policy: str = "mention" group_policy: str = "mention"
group_policy_in_thread: str = "open"
group_allow_from: list[str] = Field(default_factory=list) group_allow_from: list[str] = Field(default_factory=list)
reply_in_thread: bool = True reply_in_thread: bool = True
include_thread_context: bool = True include_thread_context: bool = True
@ -244,8 +245,10 @@ class MattermostChannel(BaseChannel):
) )
return return
if not is_dm and not self._should_respond_in_channel(message_text, channel_id): if not is_dm:
return in_thread = bool(root_id)
if not self._should_respond_in_channel(message_text, channel_id, in_thread=in_thread):
return
message_text = self._strip_bot_mention(message_text) message_text = self._strip_bot_mention(message_text)
@ -360,12 +363,18 @@ class MattermostChannel(BaseChannel):
return chat_id in self.config.group_allow_from return chat_id in self.config.group_allow_from
return True return True
def _should_respond_in_channel(self, text: str, chat_id: str) -> bool: def _should_respond_in_channel(
if self.config.group_policy == "open": self, text: str, chat_id: str, *, in_thread: bool = False,
) -> bool:
policy = (
self.config.group_policy_in_thread if in_thread
else self.config.group_policy
)
if policy == "open":
return True return True
if self.config.group_policy == "mention": if policy == "mention":
return self._is_mentioned(text) return self._is_mentioned(text)
if self.config.group_policy == "allowlist": if policy == "allowlist":
return chat_id in self.config.group_allow_from return chat_id in self.config.group_allow_from
return False return False

View File

@ -375,6 +375,53 @@ async def test_group_policy_allowlist():
assert channel._should_respond_in_channel("msg", "c2") is False assert channel._should_respond_in_channel("msg", "c2") is False
@pytest.mark.asyncio
async def test_group_policy_in_thread_default_open():
"""Thread uses open policy by default, so no mention needed in threads."""
channel, fake = _make_channel({"groupPolicy": "mention"})
channel._self_username = "nanobot"
# In a main channel (not thread), mention is required
assert channel._should_respond_in_channel("hello", "c1", in_thread=False) is False
assert channel._should_respond_in_channel("@nanobot hello", "c1", in_thread=False) is True
# In a thread, no mention needed (default open policy)
assert channel._should_respond_in_channel("hello", "c1", in_thread=True) is True
@pytest.mark.asyncio
async def test_group_policy_in_thread_mention():
"""Thread can also use mention policy when configured."""
channel, fake = _make_channel({
"groupPolicy": "mention",
"groupPolicyInThread": "mention",
})
channel._self_username = "nanobot"
# In a thread with mention policy, mention is required
assert channel._should_respond_in_channel("hello", "c1", in_thread=True) is False
assert channel._should_respond_in_channel("@nanobot hello", "c1", in_thread=True) is True
@pytest.mark.asyncio
async def test_group_policy_in_thread_open():
"""Thread uses open policy when explicitly configured."""
channel, fake = _make_channel({
"groupPolicy": "mention",
"groupPolicyInThread": "open",
})
assert channel._should_respond_in_channel("hello", "c1", in_thread=True) is True
@pytest.mark.asyncio
async def test_group_policy_in_thread_allowlist():
"""Thread uses allowlist policy when configured."""
channel, fake = _make_channel({
"groupPolicy": "mention",
"groupPolicyInThread": "allowlist",
"groupAllowFrom": ["c1"],
})
assert channel._should_respond_in_channel("msg", "c1", in_thread=True) is True
assert channel._should_respond_in_channel("msg", "c2", in_thread=True) is False
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Match mode: id / username / email # Match mode: id / username / email
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------

View File

@ -15,6 +15,7 @@ export default {
{ key: "channels.mattermost.token" }, { key: "channels.mattermost.token" },
{ key: "channels.mattermost.teamId" }, { key: "channels.mattermost.teamId" },
{ key: "channels.mattermost.groupPolicy" }, { key: "channels.mattermost.groupPolicy" },
{ key: "channels.mattermost.groupPolicyInThread" },
], ],
}, },
}, },

View File

@ -27,13 +27,21 @@
"placeholder": "Optional team ID" "placeholder": "Optional team ID"
}, },
"groupPolicy": { "groupPolicy": {
"label": "Group behavior", "label": "Channel behavior",
"choices": { "choices": {
"mention": "Mention only", "mention": "Mention only",
"open": "All messages", "open": "All messages",
"allowlist": "Allowlist" "allowlist": "Allowlist"
} }
}, },
"groupPolicyInThread": {
"label": "Thread behavior",
"choices": {
"mention": "Mention only",
"open": "All messages (no mention needed)",
"allowlist": "Allowlist"
}
},
"allowFrom": { "allowFrom": {
"label": "Allowed users", "label": "Allowed users",
"placeholder": "User IDs, comma separated" "placeholder": "User IDs, comma separated"

View File

@ -27,13 +27,21 @@
"placeholder": "ID de equipo opcional" "placeholder": "ID de equipo opcional"
}, },
"groupPolicy": { "groupPolicy": {
"label": "Comportamiento en grupos", "label": "Comportamiento en canales",
"choices": { "choices": {
"mention": "Solo menciones", "mention": "Solo menciones",
"open": "Todos los mensajes", "open": "Todos los mensajes",
"allowlist": "Lista permitida" "allowlist": "Lista permitida"
} }
}, },
"groupPolicyInThread": {
"label": "Comportamiento en hilos",
"choices": {
"mention": "Solo menciones",
"open": "Todos los mensajes (sin mención)",
"allowlist": "Lista permitida"
}
},
"allowFrom": { "allowFrom": {
"label": "Usuarios permitidos", "label": "Usuarios permitidos",
"placeholder": "ID de usuario separados por comas" "placeholder": "ID de usuario separados por comas"

View File

@ -27,11 +27,19 @@
"placeholder": "ID déquipe facultatif" "placeholder": "ID déquipe facultatif"
}, },
"groupPolicy": { "groupPolicy": {
"label": "Comportement en groupe", "label": "Comportement en canal",
"choices": { "choices": {
"mention": "Mentions uniquement", "mention": "Mentions uniquement",
"open": "Tous les messages", "open": "Tous les messages",
"allowlist": "Liste dautorisation" "allowlist": "Liste d'autorisation"
}
},
"groupPolicyInThread": {
"label": "Comportement en fil",
"choices": {
"mention": "Mentions uniquement",
"open": "Tous les messages (sans mention)",
"allowlist": "Liste d'autorisation"
} }
}, },
"allowFrom": { "allowFrom": {

View File

@ -27,13 +27,21 @@
"placeholder": "ID tim opsional" "placeholder": "ID tim opsional"
}, },
"groupPolicy": { "groupPolicy": {
"label": "Perilaku grup", "label": "Perilaku kanal",
"choices": { "choices": {
"mention": "Hanya sebutan", "mention": "Hanya sebutan",
"open": "Semua pesan", "open": "Semua pesan",
"allowlist": "Daftar izin" "allowlist": "Daftar izin"
} }
}, },
"groupPolicyInThread": {
"label": "Perilaku thread",
"choices": {
"mention": "Hanya sebutan",
"open": "Semua pesan (tanpa sebutan)",
"allowlist": "Daftar izin"
}
},
"allowFrom": { "allowFrom": {
"label": "Pengguna yang diizinkan", "label": "Pengguna yang diizinkan",
"placeholder": "ID pengguna, dipisahkan koma" "placeholder": "ID pengguna, dipisahkan koma"

View File

@ -27,13 +27,21 @@
"placeholder": "任意のチーム ID" "placeholder": "任意のチーム ID"
}, },
"groupPolicy": { "groupPolicy": {
"label": "グループでの動作", "label": "チャンネルでの動作",
"choices": { "choices": {
"mention": "メンションのみ", "mention": "メンションのみ",
"open": "すべてのメッセージ", "open": "すべてのメッセージ",
"allowlist": "許可リスト" "allowlist": "許可リスト"
} }
}, },
"groupPolicyInThread": {
"label": "スレッドでの動作",
"choices": {
"mention": "メンションのみ",
"open": "すべてのメッセージ (メンション不要)",
"allowlist": "許可リスト"
}
},
"allowFrom": { "allowFrom": {
"label": "許可するユーザー", "label": "許可するユーザー",
"placeholder": "ユーザー IDカンマ区切り" "placeholder": "ユーザー IDカンマ区切り"

View File

@ -27,13 +27,21 @@
"placeholder": "선택적 팀 ID" "placeholder": "선택적 팀 ID"
}, },
"groupPolicy": { "groupPolicy": {
"label": "그룹 동작", "label": "채널 동작",
"choices": { "choices": {
"mention": "멘션만", "mention": "멘션만",
"open": "모든 메시지", "open": "모든 메시지",
"allowlist": "허용 목록" "allowlist": "허용 목록"
} }
}, },
"groupPolicyInThread": {
"label": "스레드 동작",
"choices": {
"mention": "멘션만",
"open": "모든 메시지 (언급 불필요)",
"allowlist": "허용 목록"
}
},
"allowFrom": { "allowFrom": {
"label": "허용된 사용자", "label": "허용된 사용자",
"placeholder": "사용자 ID, 쉼표로 구분" "placeholder": "사용자 ID, 쉼표로 구분"

View File

@ -27,13 +27,21 @@
"placeholder": "ID de equipe opcional" "placeholder": "ID de equipe opcional"
}, },
"groupPolicy": { "groupPolicy": {
"label": "Comportamento em grupos", "label": "Comportamento em canais",
"choices": { "choices": {
"mention": "Somente menções", "mention": "Somente menções",
"open": "Todas as mensagens", "open": "Todas as mensagens",
"allowlist": "Lista de permissão" "allowlist": "Lista de permissão"
} }
}, },
"groupPolicyInThread": {
"label": "Comportamento em threads",
"choices": {
"mention": "Somente menções",
"open": "Todas as mensagens (sem menção)",
"allowlist": "Lista de permissão"
}
},
"allowFrom": { "allowFrom": {
"label": "Usuários permitidos", "label": "Usuários permitidos",
"placeholder": "IDs de usuário separados por vírgulas" "placeholder": "IDs de usuário separados por vírgulas"

View File

@ -27,13 +27,21 @@
"placeholder": "ID nhóm tùy chọn" "placeholder": "ID nhóm tùy chọn"
}, },
"groupPolicy": { "groupPolicy": {
"label": "Hành vi trong nhóm", "label": "Hành vi trong nh",
"choices": { "choices": {
"mention": "Chỉ khi được nhắc", "mention": "Chỉ khi được nhắc",
"open": "Mọi tin nhắn", "open": "Mọi tin nhắn",
"allowlist": "Danh sách cho phép" "allowlist": "Danh sách cho phép"
} }
}, },
"groupPolicyInThread": {
"label": "Hành vi trong thread",
"choices": {
"mention": "Chỉ khi được nhắc",
"open": "Mọi tin nhắn (không cần nhắc)",
"allowlist": "Danh sách cho phép"
}
},
"allowFrom": { "allowFrom": {
"label": "Người dùng được phép", "label": "Người dùng được phép",
"placeholder": "ID người dùng, phân tách bằng dấu phẩy" "placeholder": "ID người dùng, phân tách bằng dấu phẩy"

View File

@ -27,13 +27,21 @@
"placeholder": "可选的团队 ID" "placeholder": "可选的团队 ID"
}, },
"groupPolicy": { "groupPolicy": {
"label": "群组行为", "label": "频道行为",
"choices": { "choices": {
"mention": "仅提及时", "mention": "仅提及时",
"open": "所有消息", "open": "所有消息",
"allowlist": "白名单" "allowlist": "白名单"
} }
}, },
"groupPolicyInThread": {
"label": "线程行为",
"choices": {
"mention": "仅提及时",
"open": "所有消息(无需提及)",
"allowlist": "白名单"
}
},
"allowFrom": { "allowFrom": {
"label": "允许的用户", "label": "允许的用户",
"placeholder": "用户 ID用逗号分隔" "placeholder": "用户 ID用逗号分隔"

View File

@ -27,13 +27,21 @@
"placeholder": "可選的團隊 ID" "placeholder": "可選的團隊 ID"
}, },
"groupPolicy": { "groupPolicy": {
"label": "群組行為", "label": "頻道行為",
"choices": { "choices": {
"mention": "僅提及時", "mention": "僅提及時",
"open": "所有訊息", "open": "所有訊息",
"allowlist": "允許清單" "allowlist": "允許清單"
} }
}, },
"groupPolicyInThread": {
"label": "線程行為",
"choices": {
"mention": "僅提及時",
"open": "所有訊息(無需提及)",
"allowlist": "允許清單"
}
},
"allowFrom": { "allowFrom": {
"label": "允許的使用者", "label": "允許的使用者",
"placeholder": "使用者 ID以逗號分隔" "placeholder": "使用者 ID以逗號分隔"