Skip to content

Commit 0ee6bb7

Browse files
authored
Fix #561 matchers can be called even when app.message keyword does not match (#577)
* Fix #561 matchers can be called even when app.message keyword does not match * Fix a bug
1 parent 8cf6087 commit 0ee6bb7

File tree

5 files changed

+107
-36
lines changed

5 files changed

+107
-36
lines changed

slack_bolt/app/app.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -810,7 +810,9 @@ def __call__(*args, **kwargs):
810810
"thread_broadcast",
811811
),
812812
}
813-
primary_matcher = builtin_matchers.event(constraints=constraints)
813+
primary_matcher = builtin_matchers.message_event(
814+
keyword=keyword, constraints=constraints
815+
)
814816
middleware.insert(0, MessageListenerMatches(keyword))
815817
return self._register_listener(
816818
list(functions), primary_matcher, matchers, middleware, True

slack_bolt/app/async_app.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -860,8 +860,8 @@ def __call__(*args, **kwargs):
860860
"thread_broadcast",
861861
),
862862
}
863-
primary_matcher = builtin_matchers.event(
864-
constraints=constraints, asyncio=True
863+
primary_matcher = builtin_matchers.message_event(
864+
constraints=constraints, keyword=keyword, asyncio=True
865865
)
866866
middleware.insert(0, AsyncMessageListenerMatches(keyword))
867867
return self._register_listener(

slack_bolt/listener_matcher/builtins.py

Lines changed: 63 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# pytype: skip-file
22
import inspect
3+
import re
34
import sys
45

56
from slack_bolt.error import BoltError
@@ -95,37 +96,10 @@ def func(body: Dict[str, Any]) -> bool:
9596

9697
def func(body: Dict[str, Any]) -> bool:
9798
if is_event(body):
98-
event = body["event"]
99-
if not _matches(constraints["type"], event["type"]):
100-
return False
101-
if "subtype" in constraints:
102-
expected_subtype: Union[
103-
str, Sequence[Optional[Union[str, Pattern]]]
104-
] = constraints["subtype"]
105-
if expected_subtype is None:
106-
# "subtype" in constraints is intentionally None for this pattern
107-
return "subtype" not in event
108-
elif isinstance(expected_subtype, (str, Pattern)):
109-
return "subtype" in event and _matches(
110-
expected_subtype, event["subtype"]
111-
)
112-
elif isinstance(expected_subtype, Sequence):
113-
subtypes: Sequence[
114-
Optional[Union[str, Pattern]]
115-
] = expected_subtype
116-
for expected in subtypes:
117-
actual: Optional[str] = event.get("subtype")
118-
if expected is None:
119-
if actual is None:
120-
return True
121-
elif actual is not None and _matches(expected, actual):
122-
return True
123-
return False
124-
else:
125-
return "subtype" in event and _matches(
126-
expected_subtype, event["subtype"]
127-
)
128-
return True
99+
return _check_event_subtype(
100+
event_payload=body["event"],
101+
constraints=constraints,
102+
)
129103
return False
130104

131105
return build_listener_matcher(func, asyncio)
@@ -135,6 +109,64 @@ def func(body: Dict[str, Any]) -> bool:
135109
)
136110

137111

112+
def message_event(
113+
constraints: Dict[str, Union[str, Sequence[Optional[Union[str, Pattern]]]]],
114+
keyword: Union[str, Pattern],
115+
asyncio: bool = False,
116+
) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
117+
if "type" in constraints and keyword is not None:
118+
_verify_message_event_type(constraints["type"])
119+
120+
def func(body: Dict[str, Any]) -> bool:
121+
if is_event(body):
122+
is_valid_subtype = _check_event_subtype(
123+
event_payload=body["event"],
124+
constraints=constraints,
125+
)
126+
if is_valid_subtype is True:
127+
# Check keyword matching
128+
text = body.get("event", {}).get("text", "")
129+
match_result = re.findall(keyword, text)
130+
if match_result is not None and match_result != []:
131+
return True
132+
return False
133+
134+
return build_listener_matcher(func, asyncio)
135+
136+
raise BoltError(f"event ({constraints}: {type(constraints)}) must be dict")
137+
138+
139+
def _check_event_subtype(event_payload: dict, constraints: dict) -> bool:
140+
if not _matches(constraints["type"], event_payload["type"]):
141+
return False
142+
if "subtype" in constraints:
143+
expected_subtype: Union[
144+
str, Sequence[Optional[Union[str, Pattern]]]
145+
] = constraints["subtype"]
146+
if expected_subtype is None:
147+
# "subtype" in constraints is intentionally None for this pattern
148+
return "subtype" not in event_payload
149+
elif isinstance(expected_subtype, (str, Pattern)):
150+
return "subtype" in event_payload and _matches(
151+
expected_subtype, event_payload["subtype"]
152+
)
153+
elif isinstance(expected_subtype, Sequence):
154+
subtypes: Sequence[Optional[Union[str, Pattern]]] = expected_subtype
155+
for expected in subtypes:
156+
actual: Optional[str] = event_payload.get("subtype")
157+
if expected is None:
158+
if actual is None:
159+
return True
160+
elif actual is not None and _matches(expected, actual):
161+
return True
162+
return False
163+
else:
164+
return "subtype" in event_payload and _matches(
165+
expected_subtype, event_payload["subtype"]
166+
)
167+
return True
168+
169+
138170
def _verify_message_event_type(event_type: str) -> None:
139171
if isinstance(event_type, str) and event_type.startswith("message."):
140172
raise ValueError(error_message_event_type(event_type))

tests/scenario_tests/test_message.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,24 @@ def second():
205205
assert called["first"] == False
206206
assert called["second"] == True
207207

208+
def test_issue_561_matchers(self):
209+
app = App(
210+
client=self.web_client,
211+
signing_secret=self.signing_secret,
212+
)
213+
214+
def just_fail():
215+
raise "This matcher should not be called!"
216+
217+
@app.message("xxx", matchers=[just_fail])
218+
def just_ack():
219+
raise "This listener should not be called!"
220+
221+
request = self.build_request()
222+
response = app.dispatch(request)
223+
assert response.status == 404
224+
assert_auth_test_count(self, 1)
225+
208226

209227
message_body = {
210228
"token": "verification_token",

tests/scenario_tests_async/test_message.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -219,8 +219,27 @@ async def second():
219219
assert response.status == 200
220220

221221
await asyncio.sleep(0.3)
222-
assert called["first"] == False
223-
assert called["second"] == True
222+
assert called["first"] is False
223+
assert called["second"] is True
224+
225+
@pytest.mark.asyncio
226+
async def test_issue_561_matchers(self):
227+
app = AsyncApp(
228+
client=self.web_client,
229+
signing_secret=self.signing_secret,
230+
)
231+
232+
async def just_fail():
233+
raise "This matcher should not be called!"
234+
235+
@app.message("xxx", matchers=[just_fail])
236+
async def just_ack():
237+
raise "This listener should not be called!"
238+
239+
request = self.build_request()
240+
response = await app.async_dispatch(request)
241+
assert response.status == 404
242+
await assert_auth_test_count_async(self, 1)
224243

225244

226245
message_body = {

0 commit comments

Comments
 (0)